~canonical-django/canonical-django/project-template

« back to all changes in this revision

Viewing changes to trunk/python-packages/django/db/models/options.py

  • Committer: Matthew Nuzum
  • Date: 2008-11-13 05:46:03 UTC
  • Revision ID: matthew.nuzum@canonical.com-20081113054603-v0kvr6z6xyexvqt3
adding to version control

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import re
 
2
from bisect import bisect
 
3
try:
 
4
    set
 
5
except NameError:
 
6
    from sets import Set as set     # Python 2.3 fallback
 
7
 
 
8
from django.conf import settings
 
9
from django.db.models.related import RelatedObject
 
10
from django.db.models.fields.related import ManyToManyRel
 
11
from django.db.models.fields import AutoField, FieldDoesNotExist
 
12
from django.db.models.fields.proxy import OrderWrt
 
13
from django.db.models.loading import get_models, app_cache_ready
 
14
from django.utils.translation import activate, deactivate_all, get_language, string_concat
 
15
from django.utils.encoding import force_unicode, smart_str
 
16
from django.utils.datastructures import SortedDict
 
17
 
 
18
# Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces".
 
19
get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).lower().strip()
 
20
 
 
21
DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
 
22
                 'unique_together', 'permissions', 'get_latest_by',
 
23
                 'order_with_respect_to', 'app_label', 'db_tablespace',
 
24
                 'abstract')
 
25
 
 
26
class Options(object):
 
27
    def __init__(self, meta, app_label=None):
 
28
        self.local_fields, self.local_many_to_many = [], []
 
29
        self.virtual_fields = []
 
30
        self.module_name, self.verbose_name = None, None
 
31
        self.verbose_name_plural = None
 
32
        self.db_table = ''
 
33
        self.ordering = []
 
34
        self.unique_together =  []
 
35
        self.permissions =  []
 
36
        self.object_name, self.app_label = None, app_label
 
37
        self.get_latest_by = None
 
38
        self.order_with_respect_to = None
 
39
        self.db_tablespace = settings.DEFAULT_TABLESPACE
 
40
        self.admin = None
 
41
        self.meta = meta
 
42
        self.pk = None
 
43
        self.has_auto_field, self.auto_field = False, None
 
44
        self.one_to_one_field = None
 
45
        self.abstract = False
 
46
        self.parents = SortedDict()
 
47
        self.duplicate_targets = {}
 
48
        # Managers that have been inherited from abstract base classes. These
 
49
        # are passed onto any children.
 
50
        self.abstract_managers = []
 
51
 
 
52
    def contribute_to_class(self, cls, name):
 
53
        from django.db import connection
 
54
        from django.db.backends.util import truncate_name
 
55
 
 
56
        cls._meta = self
 
57
        self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS
 
58
        # First, construct the default values for these options.
 
59
        self.object_name = cls.__name__
 
60
        self.module_name = self.object_name.lower()
 
61
        self.verbose_name = get_verbose_name(self.object_name)
 
62
 
 
63
        # Next, apply any overridden values from 'class Meta'.
 
64
        if self.meta:
 
65
            meta_attrs = self.meta.__dict__.copy()
 
66
            for name in self.meta.__dict__:
 
67
                # Ignore any private attributes that Django doesn't care about.
 
68
                # NOTE: We can't modify a dictionary's contents while looping
 
69
                # over it, so we loop over the *original* dictionary instead.
 
70
                if name.startswith('_'):
 
71
                    del meta_attrs[name]
 
72
            for attr_name in DEFAULT_NAMES:
 
73
                if attr_name in meta_attrs:
 
74
                    setattr(self, attr_name, meta_attrs.pop(attr_name))
 
75
                elif hasattr(self.meta, attr_name):
 
76
                    setattr(self, attr_name, getattr(self.meta, attr_name))
 
77
 
 
78
            # unique_together can be either a tuple of tuples, or a single
 
79
            # tuple of two strings. Normalize it to a tuple of tuples, so that
 
80
            # calling code can uniformly expect that.
 
81
            ut = meta_attrs.pop('unique_together', getattr(self, 'unique_together'))
 
82
            if ut and not isinstance(ut[0], (tuple, list)):
 
83
                ut = (ut,)
 
84
            setattr(self, 'unique_together', ut)
 
85
 
 
86
            # verbose_name_plural is a special case because it uses a 's'
 
87
            # by default.
 
88
            setattr(self, 'verbose_name_plural', meta_attrs.pop('verbose_name_plural', string_concat(self.verbose_name, 's')))
 
89
 
 
90
            # Any leftover attributes must be invalid.
 
91
            if meta_attrs != {}:
 
92
                raise TypeError, "'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())
 
93
        else:
 
94
            self.verbose_name_plural = string_concat(self.verbose_name, 's')
 
95
        del self.meta
 
96
 
 
97
        # If the db_table wasn't provided, use the app_label + module_name.
 
98
        if not self.db_table:
 
99
            self.db_table = "%s_%s" % (self.app_label, self.module_name)
 
100
            self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
 
101
 
 
102
 
 
103
    def _prepare(self, model):
 
104
        if self.order_with_respect_to:
 
105
            self.order_with_respect_to = self.get_field(self.order_with_respect_to)
 
106
            self.ordering = ('_order',)
 
107
        else:
 
108
            self.order_with_respect_to = None
 
109
 
 
110
        if self.pk is None:
 
111
            if self.parents:
 
112
                # Promote the first parent link in lieu of adding yet another
 
113
                # field.
 
114
                field = self.parents.value_for_index(0)
 
115
                field.primary_key = True
 
116
                self.setup_pk(field)
 
117
            else:
 
118
                auto = AutoField(verbose_name='ID', primary_key=True,
 
119
                        auto_created=True)
 
120
                model.add_to_class('id', auto)
 
121
 
 
122
        # Determine any sets of fields that are pointing to the same targets
 
123
        # (e.g. two ForeignKeys to the same remote model). The query
 
124
        # construction code needs to know this. At the end of this,
 
125
        # self.duplicate_targets will map each duplicate field column to the
 
126
        # columns it duplicates.
 
127
        collections = {}
 
128
        for column, target in self.duplicate_targets.iteritems():
 
129
            try:
 
130
                collections[target].add(column)
 
131
            except KeyError:
 
132
                collections[target] = set([column])
 
133
        self.duplicate_targets = {}
 
134
        for elt in collections.itervalues():
 
135
            if len(elt) == 1:
 
136
                continue
 
137
            for column in elt:
 
138
                self.duplicate_targets[column] = elt.difference(set([column]))
 
139
 
 
140
    def add_field(self, field):
 
141
        # Insert the given field in the order in which it was created, using
 
142
        # the "creation_counter" attribute of the field.
 
143
        # Move many-to-many related fields from self.fields into
 
144
        # self.many_to_many.
 
145
        if field.rel and isinstance(field.rel, ManyToManyRel):
 
146
            self.local_many_to_many.insert(bisect(self.local_many_to_many, field), field)
 
147
            if hasattr(self, '_m2m_cache'):
 
148
                del self._m2m_cache
 
149
        else:
 
150
            self.local_fields.insert(bisect(self.local_fields, field), field)
 
151
            self.setup_pk(field)
 
152
            if hasattr(self, '_field_cache'):
 
153
                del self._field_cache
 
154
                del self._field_name_cache
 
155
 
 
156
        if hasattr(self, '_name_map'):
 
157
            del self._name_map
 
158
 
 
159
    def add_virtual_field(self, field):
 
160
        self.virtual_fields.append(field)
 
161
 
 
162
    def setup_pk(self, field):
 
163
        if not self.pk and field.primary_key:
 
164
            self.pk = field
 
165
            field.serialize = False
 
166
 
 
167
    def __repr__(self):
 
168
        return '<Options for %s>' % self.object_name
 
169
 
 
170
    def __str__(self):
 
171
        return "%s.%s" % (smart_str(self.app_label), smart_str(self.module_name))
 
172
 
 
173
    def verbose_name_raw(self):
 
174
        """
 
175
        There are a few places where the untranslated verbose name is needed
 
176
        (so that we get the same value regardless of currently active
 
177
        locale).
 
178
        """
 
179
        lang = get_language()
 
180
        deactivate_all()
 
181
        raw = force_unicode(self.verbose_name)
 
182
        activate(lang)
 
183
        return raw
 
184
    verbose_name_raw = property(verbose_name_raw)
 
185
 
 
186
    def _fields(self):
 
187
        """
 
188
        The getter for self.fields. This returns the list of field objects
 
189
        available to this model (including through parent models).
 
190
 
 
191
        Callers are not permitted to modify this list, since it's a reference
 
192
        to this instance (not a copy).
 
193
        """
 
194
        try:
 
195
            self._field_name_cache
 
196
        except AttributeError:
 
197
            self._fill_fields_cache()
 
198
        return self._field_name_cache
 
199
    fields = property(_fields)
 
200
 
 
201
    def get_fields_with_model(self):
 
202
        """
 
203
        Returns a sequence of (field, model) pairs for all fields. The "model"
 
204
        element is None for fields on the current model. Mostly of use when
 
205
        constructing queries so that we know which model a field belongs to.
 
206
        """
 
207
        try:
 
208
            self._field_cache
 
209
        except AttributeError:
 
210
            self._fill_fields_cache()
 
211
        return self._field_cache
 
212
 
 
213
    def _fill_fields_cache(self):
 
214
        cache = []
 
215
        for parent in self.parents:
 
216
            for field, model in parent._meta.get_fields_with_model():
 
217
                if model:
 
218
                    cache.append((field, model))
 
219
                else:
 
220
                    cache.append((field, parent))
 
221
        cache.extend([(f, None) for f in self.local_fields])
 
222
        self._field_cache = tuple(cache)
 
223
        self._field_name_cache = [x for x, _ in cache]
 
224
 
 
225
    def _many_to_many(self):
 
226
        try:
 
227
            self._m2m_cache
 
228
        except AttributeError:
 
229
            self._fill_m2m_cache()
 
230
        return self._m2m_cache.keys()
 
231
    many_to_many = property(_many_to_many)
 
232
 
 
233
    def get_m2m_with_model(self):
 
234
        """
 
235
        The many-to-many version of get_fields_with_model().
 
236
        """
 
237
        try:
 
238
            self._m2m_cache
 
239
        except AttributeError:
 
240
            self._fill_m2m_cache()
 
241
        return self._m2m_cache.items()
 
242
 
 
243
    def _fill_m2m_cache(self):
 
244
        cache = SortedDict()
 
245
        for parent in self.parents:
 
246
            for field, model in parent._meta.get_m2m_with_model():
 
247
                if model:
 
248
                    cache[field] = model
 
249
                else:
 
250
                    cache[field] = parent
 
251
        for field in self.local_many_to_many:
 
252
            cache[field] = None
 
253
        self._m2m_cache = cache
 
254
 
 
255
    def get_field(self, name, many_to_many=True):
 
256
        """
 
257
        Returns the requested field by name. Raises FieldDoesNotExist on error.
 
258
        """
 
259
        to_search = many_to_many and (self.fields + self.many_to_many) or self.fields
 
260
        for f in to_search:
 
261
            if f.name == name:
 
262
                return f
 
263
        raise FieldDoesNotExist, '%s has no field named %r' % (self.object_name, name)
 
264
 
 
265
    def get_field_by_name(self, name):
 
266
        """
 
267
        Returns the (field_object, model, direct, m2m), where field_object is
 
268
        the Field instance for the given name, model is the model containing
 
269
        this field (None for local fields), direct is True if the field exists
 
270
        on this model, and m2m is True for many-to-many relations. When
 
271
        'direct' is False, 'field_object' is the corresponding RelatedObject
 
272
        for this field (since the field doesn't have an instance associated
 
273
        with it).
 
274
 
 
275
        Uses a cache internally, so after the first access, this is very fast.
 
276
        """
 
277
        try:
 
278
            try:
 
279
                return self._name_map[name]
 
280
            except AttributeError:
 
281
                cache = self.init_name_map()
 
282
                return cache[name]
 
283
        except KeyError:
 
284
            raise FieldDoesNotExist('%s has no field named %r'
 
285
                    % (self.object_name, name))
 
286
 
 
287
    def get_all_field_names(self):
 
288
        """
 
289
        Returns a list of all field names that are possible for this model
 
290
        (including reverse relation names). This is used for pretty printing
 
291
        debugging output (a list of choices), so any internal-only field names
 
292
        are not included.
 
293
        """
 
294
        try:
 
295
            cache = self._name_map
 
296
        except AttributeError:
 
297
            cache = self.init_name_map()
 
298
        names = cache.keys()
 
299
        names.sort()
 
300
        # Internal-only names end with "+" (symmetrical m2m related names being
 
301
        # the main example). Trim them.
 
302
        return [val for val in names if not val.endswith('+')]
 
303
 
 
304
    def init_name_map(self):
 
305
        """
 
306
        Initialises the field name -> field object mapping.
 
307
        """
 
308
        cache = {}
 
309
        # We intentionally handle related m2m objects first so that symmetrical
 
310
        # m2m accessor names can be overridden, if necessary.
 
311
        for f, model in self.get_all_related_m2m_objects_with_model():
 
312
            cache[f.field.related_query_name()] = (f, model, False, True)
 
313
        for f, model in self.get_all_related_objects_with_model():
 
314
            cache[f.field.related_query_name()] = (f, model, False, False)
 
315
        for f, model in self.get_m2m_with_model():
 
316
            cache[f.name] = (f, model, True, True)
 
317
        for f, model in self.get_fields_with_model():
 
318
            cache[f.name] = (f, model, True, False)
 
319
        if self.order_with_respect_to:
 
320
            cache['_order'] = OrderWrt(), None, True, False
 
321
        if app_cache_ready():
 
322
            self._name_map = cache
 
323
        return cache
 
324
 
 
325
    def get_add_permission(self):
 
326
        return 'add_%s' % self.object_name.lower()
 
327
 
 
328
    def get_change_permission(self):
 
329
        return 'change_%s' % self.object_name.lower()
 
330
 
 
331
    def get_delete_permission(self):
 
332
        return 'delete_%s' % self.object_name.lower()
 
333
 
 
334
    def get_all_related_objects(self, local_only=False):
 
335
        try:
 
336
            self._related_objects_cache
 
337
        except AttributeError:
 
338
            self._fill_related_objects_cache()
 
339
        if local_only:
 
340
            return [k for k, v in self._related_objects_cache.items() if not v]
 
341
        return self._related_objects_cache.keys()
 
342
 
 
343
    def get_all_related_objects_with_model(self):
 
344
        """
 
345
        Returns a list of (related-object, model) pairs. Similar to
 
346
        get_fields_with_model().
 
347
        """
 
348
        try:
 
349
            self._related_objects_cache
 
350
        except AttributeError:
 
351
            self._fill_related_objects_cache()
 
352
        return self._related_objects_cache.items()
 
353
 
 
354
    def _fill_related_objects_cache(self):
 
355
        cache = SortedDict()
 
356
        parent_list = self.get_parent_list()
 
357
        for parent in self.parents:
 
358
            for obj, model in parent._meta.get_all_related_objects_with_model():
 
359
                if (obj.field.creation_counter < 0 or obj.field.rel.parent_link) and obj.model not in parent_list:
 
360
                    continue
 
361
                if not model:
 
362
                    cache[obj] = parent
 
363
                else:
 
364
                    cache[obj] = model
 
365
        for klass in get_models():
 
366
            for f in klass._meta.local_fields:
 
367
                if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta:
 
368
                    cache[RelatedObject(f.rel.to, klass, f)] = None
 
369
        self._related_objects_cache = cache
 
370
 
 
371
    def get_all_related_many_to_many_objects(self, local_only=False):
 
372
        try:
 
373
            cache = self._related_many_to_many_cache
 
374
        except AttributeError:
 
375
            cache = self._fill_related_many_to_many_cache()
 
376
        if local_only:
 
377
            return [k for k, v in cache.items() if not v]
 
378
        return cache.keys()
 
379
 
 
380
    def get_all_related_m2m_objects_with_model(self):
 
381
        """
 
382
        Returns a list of (related-m2m-object, model) pairs. Similar to
 
383
        get_fields_with_model().
 
384
        """
 
385
        try:
 
386
            cache = self._related_many_to_many_cache
 
387
        except AttributeError:
 
388
            cache = self._fill_related_many_to_many_cache()
 
389
        return cache.items()
 
390
 
 
391
    def _fill_related_many_to_many_cache(self):
 
392
        cache = SortedDict()
 
393
        parent_list = self.get_parent_list()
 
394
        for parent in self.parents:
 
395
            for obj, model in parent._meta.get_all_related_m2m_objects_with_model():
 
396
                if obj.field.creation_counter < 0 and obj.model not in parent_list:
 
397
                    continue
 
398
                if not model:
 
399
                    cache[obj] = parent
 
400
                else:
 
401
                    cache[obj] = model
 
402
        for klass in get_models():
 
403
            for f in klass._meta.local_many_to_many:
 
404
                if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta:
 
405
                    cache[RelatedObject(f.rel.to, klass, f)] = None
 
406
        if app_cache_ready():
 
407
            self._related_many_to_many_cache = cache
 
408
        return cache
 
409
 
 
410
    def get_base_chain(self, model):
 
411
        """
 
412
        Returns a list of parent classes leading to 'model' (order from closet
 
413
        to most distant ancestor). This has to handle the case were 'model' is
 
414
        a granparent or even more distant relation.
 
415
        """
 
416
        if not self.parents:
 
417
            return
 
418
        if model in self.parents:
 
419
            return [model]
 
420
        for parent in self.parents:
 
421
            res = parent._meta.get_base_chain(model)
 
422
            if res:
 
423
                res.insert(0, parent)
 
424
                return res
 
425
        raise TypeError('%r is not an ancestor of this model'
 
426
                % model._meta.module_name)
 
427
 
 
428
    def get_parent_list(self):
 
429
        """
 
430
        Returns a list of all the ancestor of this model as a list. Useful for
 
431
        determining if something is an ancestor, regardless of lineage.
 
432
        """
 
433
        result = set()
 
434
        for parent in self.parents:
 
435
            result.add(parent)
 
436
            result.update(parent._meta.get_parent_list())
 
437
        return result
 
438
 
 
439
    def get_ordered_objects(self):
 
440
        "Returns a list of Options objects that are ordered with respect to this object."
 
441
        if not hasattr(self, '_ordered_objects'):
 
442
            objects = []
 
443
            # TODO
 
444
            #for klass in get_models(get_app(self.app_label)):
 
445
            #    opts = klass._meta
 
446
            #    if opts.order_with_respect_to and opts.order_with_respect_to.rel \
 
447
            #        and self == opts.order_with_respect_to.rel.to._meta:
 
448
            #        objects.append(opts)
 
449
            self._ordered_objects = objects
 
450
        return self._ordered_objects