app/django/db/models/options.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     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.db.models import Manager
       
    15 from django.utils.translation import activate, deactivate_all, get_language, string_concat
       
    16 from django.utils.encoding import force_unicode, smart_str
       
    17 from django.utils.datastructures import SortedDict
       
    18 
       
    19 # Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces".
       
    20 get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).lower().strip()
       
    21 
       
    22 DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
       
    23                  'unique_together', 'permissions', 'get_latest_by',
       
    24                  'order_with_respect_to', 'app_label', 'db_tablespace',
       
    25                  'abstract')
       
    26 
       
    27 class Options(object):
       
    28     def __init__(self, meta):
       
    29         self.local_fields, self.local_many_to_many = [], []
       
    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, None
       
    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 
       
    48     def contribute_to_class(self, cls, name):
       
    49         cls._meta = self
       
    50         self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS
       
    51         # First, construct the default values for these options.
       
    52         self.object_name = cls.__name__
       
    53         self.module_name = self.object_name.lower()
       
    54         self.verbose_name = get_verbose_name(self.object_name)
       
    55 
       
    56         # Next, apply any overridden values from 'class Meta'.
       
    57         if self.meta:
       
    58             meta_attrs = self.meta.__dict__.copy()
       
    59             del meta_attrs['__module__']
       
    60             del meta_attrs['__doc__']
       
    61             for attr_name in DEFAULT_NAMES:
       
    62                 if attr_name in meta_attrs:
       
    63                     setattr(self, attr_name, meta_attrs.pop(attr_name))
       
    64                 elif hasattr(self.meta, attr_name):
       
    65                     setattr(self, attr_name, getattr(self.meta, attr_name))
       
    66 
       
    67             # unique_together can be either a tuple of tuples, or a single
       
    68             # tuple of two strings. Normalize it to a tuple of tuples, so that
       
    69             # calling code can uniformly expect that.
       
    70             ut = meta_attrs.pop('unique_together', getattr(self, 'unique_together'))
       
    71             if ut and not isinstance(ut[0], (tuple, list)):
       
    72                 ut = (ut,)
       
    73             setattr(self, 'unique_together', ut)
       
    74 
       
    75             # verbose_name_plural is a special case because it uses a 's'
       
    76             # by default.
       
    77             setattr(self, 'verbose_name_plural', meta_attrs.pop('verbose_name_plural', string_concat(self.verbose_name, 's')))
       
    78 
       
    79             # Any leftover attributes must be invalid.
       
    80             if meta_attrs != {}:
       
    81                 raise TypeError, "'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())
       
    82         else:
       
    83             self.verbose_name_plural = string_concat(self.verbose_name, 's')
       
    84         del self.meta
       
    85 
       
    86     def _prepare(self, model):
       
    87         from django.db import connection
       
    88         from django.db.backends.util import truncate_name
       
    89         if self.order_with_respect_to:
       
    90             self.order_with_respect_to = self.get_field(self.order_with_respect_to)
       
    91             self.ordering = ('_order',)
       
    92         else:
       
    93             self.order_with_respect_to = None
       
    94 
       
    95         if self.pk is None:
       
    96             if self.parents:
       
    97                 # Promote the first parent link in lieu of adding yet another
       
    98                 # field.
       
    99                 field = self.parents.value_for_index(0)
       
   100                 field.primary_key = True
       
   101                 self.pk = field
       
   102             else:
       
   103                 auto = AutoField(verbose_name='ID', primary_key=True,
       
   104                         auto_created=True)
       
   105                 model.add_to_class('id', auto)
       
   106 
       
   107         # If the db_table wasn't provided, use the app_label + module_name.
       
   108         if not self.db_table:
       
   109             self.db_table = "%s_%s" % (self.app_label, self.module_name)
       
   110             self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
       
   111 
       
   112     def add_field(self, field):
       
   113         # Insert the given field in the order in which it was created, using
       
   114         # the "creation_counter" attribute of the field.
       
   115         # Move many-to-many related fields from self.fields into
       
   116         # self.many_to_many.
       
   117         if field.rel and isinstance(field.rel, ManyToManyRel):
       
   118             self.local_many_to_many.insert(bisect(self.local_many_to_many, field), field)
       
   119             if hasattr(self, '_m2m_cache'):
       
   120                 del self._m2m_cache
       
   121         else:
       
   122             self.local_fields.insert(bisect(self.local_fields, field), field)
       
   123             self.setup_pk(field)
       
   124             if hasattr(self, '_field_cache'):
       
   125                 del self._field_cache
       
   126                 del self._field_name_cache
       
   127 
       
   128         if hasattr(self, '_name_map'):
       
   129             del self._name_map
       
   130 
       
   131     def setup_pk(self, field):
       
   132         if not self.pk and field.primary_key:
       
   133             self.pk = field
       
   134             field.serialize = False
       
   135 
       
   136     def __repr__(self):
       
   137         return '<Options for %s>' % self.object_name
       
   138 
       
   139     def __str__(self):
       
   140         return "%s.%s" % (smart_str(self.app_label), smart_str(self.module_name))
       
   141 
       
   142     def verbose_name_raw(self):
       
   143         """
       
   144         There are a few places where the untranslated verbose name is needed
       
   145         (so that we get the same value regardless of currently active
       
   146         locale).
       
   147         """
       
   148         lang = get_language()
       
   149         deactivate_all()
       
   150         raw = force_unicode(self.verbose_name)
       
   151         activate(lang)
       
   152         return raw
       
   153     verbose_name_raw = property(verbose_name_raw)
       
   154 
       
   155     def _fields(self):
       
   156         """
       
   157         The getter for self.fields. This returns the list of field objects
       
   158         available to this model (including through parent models).
       
   159 
       
   160         Callers are not permitted to modify this list, since it's a reference
       
   161         to this instance (not a copy).
       
   162         """
       
   163         try:
       
   164             self._field_name_cache
       
   165         except AttributeError:
       
   166             self._fill_fields_cache()
       
   167         return self._field_name_cache
       
   168     fields = property(_fields)
       
   169 
       
   170     def get_fields_with_model(self):
       
   171         """
       
   172         Returns a sequence of (field, model) pairs for all fields. The "model"
       
   173         element is None for fields on the current model. Mostly of use when
       
   174         constructing queries so that we know which model a field belongs to.
       
   175         """
       
   176         try:
       
   177             self._field_cache
       
   178         except AttributeError:
       
   179             self._fill_fields_cache()
       
   180         return self._field_cache
       
   181 
       
   182     def _fill_fields_cache(self):
       
   183         cache = []
       
   184         for parent in self.parents:
       
   185             for field, model in parent._meta.get_fields_with_model():
       
   186                 if model:
       
   187                     cache.append((field, model))
       
   188                 else:
       
   189                     cache.append((field, parent))
       
   190         cache.extend([(f, None) for f in self.local_fields])
       
   191         self._field_cache = tuple(cache)
       
   192         self._field_name_cache = [x for x, _ in cache]
       
   193 
       
   194     def _many_to_many(self):
       
   195         try:
       
   196             self._m2m_cache
       
   197         except AttributeError:
       
   198             self._fill_m2m_cache()
       
   199         return self._m2m_cache.keys()
       
   200     many_to_many = property(_many_to_many)
       
   201 
       
   202     def get_m2m_with_model(self):
       
   203         """
       
   204         The many-to-many version of get_fields_with_model().
       
   205         """
       
   206         try:
       
   207             self._m2m_cache
       
   208         except AttributeError:
       
   209             self._fill_m2m_cache()
       
   210         return self._m2m_cache.items()
       
   211 
       
   212     def _fill_m2m_cache(self):
       
   213         cache = SortedDict()
       
   214         for parent in self.parents:
       
   215             for field, model in parent._meta.get_m2m_with_model():
       
   216                 if model:
       
   217                     cache[field] = model
       
   218                 else:
       
   219                     cache[field] = parent
       
   220         for field in self.local_many_to_many:
       
   221             cache[field] = None
       
   222         self._m2m_cache = cache
       
   223 
       
   224     def get_field(self, name, many_to_many=True):
       
   225         """
       
   226         Returns the requested field by name. Raises FieldDoesNotExist on error.
       
   227         """
       
   228         to_search = many_to_many and (self.fields + self.many_to_many) or self.fields
       
   229         for f in to_search:
       
   230             if f.name == name:
       
   231                 return f
       
   232         raise FieldDoesNotExist, '%s has no field named %r' % (self.object_name, name)
       
   233 
       
   234     def get_field_by_name(self, name):
       
   235         """
       
   236         Returns the (field_object, model, direct, m2m), where field_object is
       
   237         the Field instance for the given name, model is the model containing
       
   238         this field (None for local fields), direct is True if the field exists
       
   239         on this model, and m2m is True for many-to-many relations. When
       
   240         'direct' is False, 'field_object' is the corresponding RelatedObject
       
   241         for this field (since the field doesn't have an instance associated
       
   242         with it).
       
   243 
       
   244         Uses a cache internally, so after the first access, this is very fast.
       
   245         """
       
   246         try:
       
   247             try:
       
   248                 return self._name_map[name]
       
   249             except AttributeError:
       
   250                 cache = self.init_name_map()
       
   251                 return cache[name]
       
   252         except KeyError:
       
   253             raise FieldDoesNotExist('%s has no field named %r'
       
   254                     % (self.object_name, name))
       
   255 
       
   256     def get_all_field_names(self):
       
   257         """
       
   258         Returns a list of all field names that are possible for this model
       
   259         (including reverse relation names).
       
   260         """
       
   261         try:
       
   262             cache = self._name_map
       
   263         except AttributeError:
       
   264             cache = self.init_name_map()
       
   265         names = cache.keys()
       
   266         names.sort()
       
   267         return names
       
   268 
       
   269     def init_name_map(self):
       
   270         """
       
   271         Initialises the field name -> field object mapping.
       
   272         """
       
   273         cache = dict([(f.name, (f, m, True, False)) for f, m in
       
   274                 self.get_fields_with_model()])
       
   275         for f, model in self.get_m2m_with_model():
       
   276             cache[f.name] = (f, model, True, True)
       
   277         for f, model in self.get_all_related_m2m_objects_with_model():
       
   278             cache[f.field.related_query_name()] = (f, model, False, True)
       
   279         for f, model in self.get_all_related_objects_with_model():
       
   280             cache[f.field.related_query_name()] = (f, model, False, False)
       
   281         if self.order_with_respect_to:
       
   282             cache['_order'] = OrderWrt(), None, True, False
       
   283         if app_cache_ready():
       
   284             self._name_map = cache
       
   285         return cache
       
   286 
       
   287     def get_add_permission(self):
       
   288         return 'add_%s' % self.object_name.lower()
       
   289 
       
   290     def get_change_permission(self):
       
   291         return 'change_%s' % self.object_name.lower()
       
   292 
       
   293     def get_delete_permission(self):
       
   294         return 'delete_%s' % self.object_name.lower()
       
   295 
       
   296     def get_all_related_objects(self, local_only=False):
       
   297         try:
       
   298             self._related_objects_cache
       
   299         except AttributeError:
       
   300             self._fill_related_objects_cache()
       
   301         if local_only:
       
   302             return [k for k, v in self._related_objects_cache.items() if not v]
       
   303         return self._related_objects_cache.keys()
       
   304 
       
   305     def get_all_related_objects_with_model(self):
       
   306         """
       
   307         Returns a list of (related-object, model) pairs. Similar to
       
   308         get_fields_with_model().
       
   309         """
       
   310         try:
       
   311             self._related_objects_cache
       
   312         except AttributeError:
       
   313             self._fill_related_objects_cache()
       
   314         return self._related_objects_cache.items()
       
   315 
       
   316     def _fill_related_objects_cache(self):
       
   317         cache = SortedDict()
       
   318         parent_list = self.get_parent_list()
       
   319         for parent in self.parents:
       
   320             for obj, model in parent._meta.get_all_related_objects_with_model():
       
   321                 if (obj.field.creation_counter < 0 or obj.field.rel.parent_link) and obj.model not in parent_list:
       
   322                     continue
       
   323                 if not model:
       
   324                     cache[obj] = parent
       
   325                 else:
       
   326                     cache[obj] = model
       
   327         for klass in get_models():
       
   328             for f in klass._meta.local_fields:
       
   329                 if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta:
       
   330                     cache[RelatedObject(f.rel.to, klass, f)] = None
       
   331         self._related_objects_cache = cache
       
   332 
       
   333     def get_all_related_many_to_many_objects(self, local_only=False):
       
   334         try:
       
   335             cache = self._related_many_to_many_cache
       
   336         except AttributeError:
       
   337             cache = self._fill_related_many_to_many_cache()
       
   338         if local_only:
       
   339             return [k for k, v in cache.items() if not v]
       
   340         return cache.keys()
       
   341 
       
   342     def get_all_related_m2m_objects_with_model(self):
       
   343         """
       
   344         Returns a list of (related-m2m-object, model) pairs. Similar to
       
   345         get_fields_with_model().
       
   346         """
       
   347         try:
       
   348             cache = self._related_many_to_many_cache
       
   349         except AttributeError:
       
   350             cache = self._fill_related_many_to_many_cache()
       
   351         return cache.items()
       
   352 
       
   353     def _fill_related_many_to_many_cache(self):
       
   354         cache = SortedDict()
       
   355         parent_list = self.get_parent_list()
       
   356         for parent in self.parents:
       
   357             for obj, model in parent._meta.get_all_related_m2m_objects_with_model():
       
   358                 if obj.field.creation_counter < 0 and obj.model not in parent_list:
       
   359                     continue
       
   360                 if not model:
       
   361                     cache[obj] = parent
       
   362                 else:
       
   363                     cache[obj] = model
       
   364         for klass in get_models():
       
   365             for f in klass._meta.local_many_to_many:
       
   366                 if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta:
       
   367                     cache[RelatedObject(f.rel.to, klass, f)] = None
       
   368         if app_cache_ready():
       
   369             self._related_many_to_many_cache = cache
       
   370         return cache
       
   371 
       
   372     def get_followed_related_objects(self, follow=None):
       
   373         if follow == None:
       
   374             follow = self.get_follow()
       
   375         return [f for f in self.get_all_related_objects() if follow.get(f.name, None)]
       
   376 
       
   377     def get_data_holders(self, follow=None):
       
   378         if follow == None:
       
   379             follow = self.get_follow()
       
   380         return [f for f in self.fields + self.many_to_many + self.get_all_related_objects() if follow.get(f.name, None)]
       
   381 
       
   382     def get_follow(self, override=None):
       
   383         follow = {}
       
   384         for f in self.fields + self.many_to_many + self.get_all_related_objects():
       
   385             if override and f.name in override:
       
   386                 child_override = override[f.name]
       
   387             else:
       
   388                 child_override = None
       
   389             fol = f.get_follow(child_override)
       
   390             if fol != None:
       
   391                 follow[f.name] = fol
       
   392         return follow
       
   393 
       
   394     def get_base_chain(self, model):
       
   395         """
       
   396         Returns a list of parent classes leading to 'model' (order from closet
       
   397         to most distant ancestor). This has to handle the case were 'model' is
       
   398         a granparent or even more distant relation.
       
   399         """
       
   400         if not self.parents:
       
   401             return
       
   402         if model in self.parents:
       
   403             return [model]
       
   404         for parent in self.parents:
       
   405             res = parent._meta.get_base_chain(model)
       
   406             if res:
       
   407                 res.insert(0, parent)
       
   408                 return res
       
   409         raise TypeError('%r is not an ancestor of this model'
       
   410                 % model._meta.module_name)
       
   411 
       
   412     def get_parent_list(self):
       
   413         """
       
   414         Returns a list of all the ancestor of this model as a list. Useful for
       
   415         determining if something is an ancestor, regardless of lineage.
       
   416         """
       
   417         result = set()
       
   418         for parent in self.parents:
       
   419             result.add(parent)
       
   420             result.update(parent._meta.get_parent_list())
       
   421         return result
       
   422 
       
   423     def get_ordered_objects(self):
       
   424         "Returns a list of Options objects that are ordered with respect to this object."
       
   425         if not hasattr(self, '_ordered_objects'):
       
   426             objects = []
       
   427             # TODO
       
   428             #for klass in get_models(get_app(self.app_label)):
       
   429             #    opts = klass._meta
       
   430             #    if opts.order_with_respect_to and opts.order_with_respect_to.rel \
       
   431             #        and self == opts.order_with_respect_to.rel.to._meta:
       
   432             #        objects.append(opts)
       
   433             self._ordered_objects = objects
       
   434         return self._ordered_objects
       
   435 
       
   436     def has_field_type(self, field_type, follow=None):
       
   437         """
       
   438         Returns True if this object's admin form has at least one of the given
       
   439         field_type (e.g. FileField).
       
   440         """
       
   441         # TODO: follow
       
   442         if not hasattr(self, '_field_types'):
       
   443             self._field_types = {}
       
   444         if field_type not in self._field_types:
       
   445             try:
       
   446                 # First check self.fields.
       
   447                 for f in self.fields:
       
   448                     if isinstance(f, field_type):
       
   449                         raise StopIteration
       
   450                 # Failing that, check related fields.
       
   451                 for related in self.get_followed_related_objects(follow):
       
   452                     for f in related.opts.fields:
       
   453                         if isinstance(f, field_type):
       
   454                             raise StopIteration
       
   455             except StopIteration:
       
   456                 self._field_types[field_type] = True
       
   457             else:
       
   458                 self._field_types[field_type] = False
       
   459         return self._field_types[field_type]
       
   460 
       
   461 class AdminOptions(object):
       
   462     def __init__(self, fields=None, js=None, list_display=None, list_display_links=None, list_filter=None,
       
   463         date_hierarchy=None, save_as=False, ordering=None, search_fields=None,
       
   464         save_on_top=False, list_select_related=False, manager=None, list_per_page=100):
       
   465         self.fields = fields
       
   466         self.js = js or []
       
   467         self.list_display = list_display or ['__str__']
       
   468         self.list_display_links = list_display_links or []
       
   469         self.list_filter = list_filter or []
       
   470         self.date_hierarchy = date_hierarchy
       
   471         self.save_as, self.ordering = save_as, ordering
       
   472         self.search_fields = search_fields or []
       
   473         self.save_on_top = save_on_top
       
   474         self.list_select_related = list_select_related
       
   475         self.list_per_page = list_per_page
       
   476         self.manager = manager or Manager()
       
   477 
       
   478     def get_field_sets(self, opts):
       
   479         "Returns a list of AdminFieldSet objects for this AdminOptions object."
       
   480         if self.fields is None:
       
   481             field_struct = ((None, {'fields': [f.name for f in opts.fields + opts.many_to_many if f.editable and not isinstance(f, AutoField)]}),)
       
   482         else:
       
   483             field_struct = self.fields
       
   484         new_fieldset_list = []
       
   485         for fieldset in field_struct:
       
   486             fs_options = fieldset[1]
       
   487             classes = fs_options.get('classes', ())
       
   488             description = fs_options.get('description', '')
       
   489             new_fieldset_list.append(AdminFieldSet(fieldset[0], classes,
       
   490                 opts.get_field, fs_options['fields'], description))
       
   491         return new_fieldset_list
       
   492 
       
   493     def contribute_to_class(self, cls, name):
       
   494         cls._meta.admin = self
       
   495         # Make sure the admin manager has access to the model
       
   496         self.manager.model = cls
       
   497 
       
   498 class AdminFieldSet(object):
       
   499     def __init__(self, name, classes, field_locator_func, line_specs, description):
       
   500         self.name = name
       
   501         self.field_lines = [AdminFieldLine(field_locator_func, line_spec) for line_spec in line_specs]
       
   502         self.classes = classes
       
   503         self.description = description
       
   504 
       
   505     def __repr__(self):
       
   506         return "FieldSet: (%s, %s)" % (self.name, self.field_lines)
       
   507 
       
   508     def bind(self, field_mapping, original, bound_field_set_class):
       
   509         return bound_field_set_class(self, field_mapping, original)
       
   510 
       
   511     def __iter__(self):
       
   512         for field_line in self.field_lines:
       
   513             yield field_line
       
   514 
       
   515     def __len__(self):
       
   516         return len(self.field_lines)
       
   517 
       
   518 class AdminFieldLine(object):
       
   519     def __init__(self, field_locator_func, linespec):
       
   520         if isinstance(linespec, basestring):
       
   521             self.fields = [field_locator_func(linespec)]
       
   522         else:
       
   523             self.fields = [field_locator_func(field_name) for field_name in linespec]
       
   524 
       
   525     def bind(self, field_mapping, original, bound_field_line_class):
       
   526         return bound_field_line_class(self, field_mapping, original)
       
   527 
       
   528     def __iter__(self):
       
   529         for field in self.fields:
       
   530             yield field
       
   531 
       
   532     def __len__(self):
       
   533         return len(self.fields)