diff -r 6641e941ef1e -r ff1a9aa48cfd app/django/db/models/options.py --- a/app/django/db/models/options.py Tue Oct 14 12:36:55 2008 +0000 +++ b/app/django/db/models/options.py Tue Oct 14 16:00:59 2008 +0000 @@ -11,7 +11,6 @@ from django.db.models.fields import AutoField, FieldDoesNotExist from django.db.models.fields.proxy import OrderWrt from django.db.models.loading import get_models, app_cache_ready -from django.db.models import Manager from django.utils.translation import activate, deactivate_all, get_language, string_concat from django.utils.encoding import force_unicode, smart_str from django.utils.datastructures import SortedDict @@ -25,15 +24,16 @@ 'abstract') class Options(object): - def __init__(self, meta): + def __init__(self, meta, app_label=None): self.local_fields, self.local_many_to_many = [], [] + self.virtual_fields = [] self.module_name, self.verbose_name = None, None self.verbose_name_plural = None self.db_table = '' self.ordering = [] self.unique_together = [] self.permissions = [] - self.object_name, self.app_label = None, None + self.object_name, self.app_label = None, app_label self.get_latest_by = None self.order_with_respect_to = None self.db_tablespace = settings.DEFAULT_TABLESPACE @@ -44,8 +44,15 @@ self.one_to_one_field = None self.abstract = False self.parents = SortedDict() + self.duplicate_targets = {} + # Managers that have been inherited from abstract base classes. These + # are passed onto any children. + self.abstract_managers = [] def contribute_to_class(self, cls, name): + from django.db import connection + from django.db.backends.util import truncate_name + cls._meta = self self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS # First, construct the default values for these options. @@ -56,8 +63,12 @@ # Next, apply any overridden values from 'class Meta'. if self.meta: meta_attrs = self.meta.__dict__.copy() - del meta_attrs['__module__'] - del meta_attrs['__doc__'] + for name in self.meta.__dict__: + # Ignore any private attributes that Django doesn't care about. + # NOTE: We can't modify a dictionary's contents while looping + # over it, so we loop over the *original* dictionary instead. + if name.startswith('_'): + del meta_attrs[name] for attr_name in DEFAULT_NAMES: if attr_name in meta_attrs: setattr(self, attr_name, meta_attrs.pop(attr_name)) @@ -83,9 +94,13 @@ self.verbose_name_plural = string_concat(self.verbose_name, 's') del self.meta + # If the db_table wasn't provided, use the app_label + module_name. + if not self.db_table: + self.db_table = "%s_%s" % (self.app_label, self.module_name) + self.db_table = truncate_name(self.db_table, connection.ops.max_name_length()) + + def _prepare(self, model): - from django.db import connection - from django.db.backends.util import truncate_name if self.order_with_respect_to: self.order_with_respect_to = self.get_field(self.order_with_respect_to) self.ordering = ('_order',) @@ -98,16 +113,29 @@ # field. field = self.parents.value_for_index(0) field.primary_key = True - self.pk = field + self.setup_pk(field) else: auto = AutoField(verbose_name='ID', primary_key=True, auto_created=True) model.add_to_class('id', auto) - # If the db_table wasn't provided, use the app_label + module_name. - if not self.db_table: - self.db_table = "%s_%s" % (self.app_label, self.module_name) - self.db_table = truncate_name(self.db_table, connection.ops.max_name_length()) + # Determine any sets of fields that are pointing to the same targets + # (e.g. two ForeignKeys to the same remote model). The query + # construction code needs to know this. At the end of this, + # self.duplicate_targets will map each duplicate field column to the + # columns it duplicates. + collections = {} + for column, target in self.duplicate_targets.iteritems(): + try: + collections[target].add(column) + except KeyError: + collections[target] = set([column]) + self.duplicate_targets = {} + for elt in collections.itervalues(): + if len(elt) == 1: + continue + for column in elt: + self.duplicate_targets[column] = elt.difference(set([column])) def add_field(self, field): # Insert the given field in the order in which it was created, using @@ -128,6 +156,9 @@ if hasattr(self, '_name_map'): del self._name_map + def add_virtual_field(self, field): + self.virtual_fields.append(field) + def setup_pk(self, field): if not self.pk and field.primary_key: self.pk = field @@ -256,7 +287,9 @@ def get_all_field_names(self): """ Returns a list of all field names that are possible for this model - (including reverse relation names). + (including reverse relation names). This is used for pretty printing + debugging output (a list of choices), so any internal-only field names + are not included. """ try: cache = self._name_map @@ -264,20 +297,25 @@ cache = self.init_name_map() names = cache.keys() names.sort() - return names + # Internal-only names end with "+" (symmetrical m2m related names being + # the main example). Trim them. + return [val for val in names if not val.endswith('+')] def init_name_map(self): """ Initialises the field name -> field object mapping. """ - cache = dict([(f.name, (f, m, True, False)) for f, m in - self.get_fields_with_model()]) - for f, model in self.get_m2m_with_model(): - cache[f.name] = (f, model, True, True) + cache = {} + # We intentionally handle related m2m objects first so that symmetrical + # m2m accessor names can be overridden, if necessary. for f, model in self.get_all_related_m2m_objects_with_model(): cache[f.field.related_query_name()] = (f, model, False, True) for f, model in self.get_all_related_objects_with_model(): cache[f.field.related_query_name()] = (f, model, False, False) + for f, model in self.get_m2m_with_model(): + cache[f.name] = (f, model, True, True) + for f, model in self.get_fields_with_model(): + cache[f.name] = (f, model, True, False) if self.order_with_respect_to: cache['_order'] = OrderWrt(), None, True, False if app_cache_ready(): @@ -369,28 +407,6 @@ self._related_many_to_many_cache = cache return cache - def get_followed_related_objects(self, follow=None): - if follow == None: - follow = self.get_follow() - return [f for f in self.get_all_related_objects() if follow.get(f.name, None)] - - def get_data_holders(self, follow=None): - if follow == None: - follow = self.get_follow() - return [f for f in self.fields + self.many_to_many + self.get_all_related_objects() if follow.get(f.name, None)] - - def get_follow(self, override=None): - follow = {} - for f in self.fields + self.many_to_many + self.get_all_related_objects(): - if override and f.name in override: - child_override = override[f.name] - else: - child_override = None - fol = f.get_follow(child_override) - if fol != None: - follow[f.name] = fol - return follow - def get_base_chain(self, model): """ Returns a list of parent classes leading to 'model' (order from closet @@ -432,102 +448,3 @@ # objects.append(opts) self._ordered_objects = objects return self._ordered_objects - - def has_field_type(self, field_type, follow=None): - """ - Returns True if this object's admin form has at least one of the given - field_type (e.g. FileField). - """ - # TODO: follow - if not hasattr(self, '_field_types'): - self._field_types = {} - if field_type not in self._field_types: - try: - # First check self.fields. - for f in self.fields: - if isinstance(f, field_type): - raise StopIteration - # Failing that, check related fields. - for related in self.get_followed_related_objects(follow): - for f in related.opts.fields: - if isinstance(f, field_type): - raise StopIteration - except StopIteration: - self._field_types[field_type] = True - else: - self._field_types[field_type] = False - return self._field_types[field_type] - -class AdminOptions(object): - def __init__(self, fields=None, js=None, list_display=None, list_display_links=None, list_filter=None, - date_hierarchy=None, save_as=False, ordering=None, search_fields=None, - save_on_top=False, list_select_related=False, manager=None, list_per_page=100): - self.fields = fields - self.js = js or [] - self.list_display = list_display or ['__str__'] - self.list_display_links = list_display_links or [] - self.list_filter = list_filter or [] - self.date_hierarchy = date_hierarchy - self.save_as, self.ordering = save_as, ordering - self.search_fields = search_fields or [] - self.save_on_top = save_on_top - self.list_select_related = list_select_related - self.list_per_page = list_per_page - self.manager = manager or Manager() - - def get_field_sets(self, opts): - "Returns a list of AdminFieldSet objects for this AdminOptions object." - if self.fields is None: - field_struct = ((None, {'fields': [f.name for f in opts.fields + opts.many_to_many if f.editable and not isinstance(f, AutoField)]}),) - else: - field_struct = self.fields - new_fieldset_list = [] - for fieldset in field_struct: - fs_options = fieldset[1] - classes = fs_options.get('classes', ()) - description = fs_options.get('description', '') - new_fieldset_list.append(AdminFieldSet(fieldset[0], classes, - opts.get_field, fs_options['fields'], description)) - return new_fieldset_list - - def contribute_to_class(self, cls, name): - cls._meta.admin = self - # Make sure the admin manager has access to the model - self.manager.model = cls - -class AdminFieldSet(object): - def __init__(self, name, classes, field_locator_func, line_specs, description): - self.name = name - self.field_lines = [AdminFieldLine(field_locator_func, line_spec) for line_spec in line_specs] - self.classes = classes - self.description = description - - def __repr__(self): - return "FieldSet: (%s, %s)" % (self.name, self.field_lines) - - def bind(self, field_mapping, original, bound_field_set_class): - return bound_field_set_class(self, field_mapping, original) - - def __iter__(self): - for field_line in self.field_lines: - yield field_line - - def __len__(self): - return len(self.field_lines) - -class AdminFieldLine(object): - def __init__(self, field_locator_func, linespec): - if isinstance(linespec, basestring): - self.fields = [field_locator_func(linespec)] - else: - self.fields = [field_locator_func(field_name) for field_name in linespec] - - def bind(self, field_mapping, original, bound_field_line_class): - return bound_field_line_class(self, field_mapping, original) - - def __iter__(self): - for field in self.fields: - yield field - - def __len__(self): - return len(self.fields)