diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index 71366f4949..0f2d956804 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -17,7 +17,6 @@ from django.db.models.fields.related import * from django.core.exceptions import ObjectDoesNotExist from django.db.models.exceptions import FieldDoesNotExist, BadKeywordArguments - # Admin stages. ADD, CHANGE, BOTH = 1, 2, 3 @@ -28,7 +27,6 @@ ADD, CHANGE, BOTH = 1, 2, 3 #def get_app(app_label): # return __import__('%s.%s' % (MODEL_PREFIX, app_label), '', '', ['']) - class LazyDate: """ Use in limit_choices_to to compare the field to dates calculated at run time @@ -50,11 +48,6 @@ class LazyDate: def __get_value__(self): return datetime.datetime.now() + self.delta -################ -# MAIN CLASSES # -################ - - diff --git a/django/db/models/base.py b/django/db/models/base.py index c346db2eaa..91590affc7 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -1,6 +1,6 @@ from django.db.models.manipulators import ManipulatorDescriptor, ModelAddManipulator, ModelChangeManipulator from django.db.models.fields import Field, DateField, FileField, ImageField, AutoField -from django.db.models.fields.related import OneToOne, ManyToOne, ManyToMany, RECURSIVE_RELATIONSHIP_CONSTANT +from django.db.models.fields.related import RelatedField, OneToOne, ManyToOne, ManyToMany, RECURSIVE_RELATIONSHIP_CONSTANT from django.db.models.related import RelatedObject from django.db.models.manager import Manager, ManagerDescriptor from django.db.models.query import orderlist2sql @@ -36,23 +36,9 @@ class ModelBase(type): except KeyError: meta_attrs = {} - # Gather all attributes that are Field or Manager instances. - fields, managers = [], [] - for obj_name, obj in attrs.items(): - if isinstance(obj, Field): - obj.set_name(obj_name) - fields.append(obj) - del attrs[obj_name] - elif isinstance(obj, Manager): - managers.append((obj_name, obj)) - del attrs[obj_name] - - # Sort the fields and managers in the order that they were created. The - # "creation_counter" is needed because metaclasses don't preserve the - # attribute order. - fields.sort(lambda x, y: x.creation_counter - y.creation_counter) - managers.sort(lambda x, y: x[1].creation_counter - y[1].creation_counter) - + # Create the class, because we need it to use in currying. + new_class = type.__new__(cls, name, bases, { '__module__' : attrs.pop('__module__') }) + opts = Options( module_name = meta_attrs.pop('module_name', get_module_name(name)), # If the verbose_name wasn't given, use the class name, @@ -60,7 +46,6 @@ class ModelBase(type): verbose_name = meta_attrs.pop('verbose_name', get_verbose_name(name)), verbose_name_plural = meta_attrs.pop('verbose_name_plural', ''), db_table = meta_attrs.pop('db_table', ''), - fields = fields, ordering = meta_attrs.pop('ordering', None), unique_together = meta_attrs.pop('unique_together', None), admin = meta_attrs.pop('admin', None), @@ -76,38 +61,24 @@ class ModelBase(type): if meta_attrs != {}: raise TypeError, "'class META' got invalid attribute(s): %s" % ','.join(meta_attrs.keys()) - + new_class.add_to_class('_meta', opts) + # Create the DoesNotExist exception. - attrs['DoesNotExist'] = types.ClassType('DoesNotExist', (ObjectDoesNotExist,), {}) - - # Create the class, because we need it to use in currying. - new_class = type.__new__(cls, name, bases, attrs) - - # Give the class a docstring -- its definition. - if new_class.__doc__ is None: - new_class.__doc__ = "%s.%s(%s)" % (opts.module_name, name, ", ".join([f.name for f in opts.fields])) - - if hasattr(new_class, 'get_absolute_url'): - new_class.get_absolute_url = curry(get_absolute_url, opts, new_class.get_absolute_url) - + new_class.DoesNotExist = types.ClassType('DoesNotExist', (ObjectDoesNotExist,), {}) + # Figure out the app_label by looking one level up. + #FIXME: wrong for nested model modules app_package = sys.modules.get(new_class.__module__) app_label = app_package.__name__.replace('.models', '') app_label = app_label[app_label.rfind('.')+1:] - # Populate the _MODELS member on the module the class is in. - app_package.__dict__.setdefault('_MODELS', []).append(new_class) - # Cache the app label. opts.app_label = app_label - - # If the db_table wasn't provided, use the app_label + module_name. - if not opts.db_table: - opts.db_table = "%s_%s" % (app_label, opts.module_name) - new_class._meta = opts - - for m_name, m in managers: - new_class.add_to_class(m_name, m) + + #Add all attributes to the class + #fields, managers = [], [] + for obj_name, obj in attrs.items(): + new_class.add_to_class(obj_name, obj) if not hasattr(new_class, '_default_manager'): # Create the default manager, if needed. @@ -115,18 +86,25 @@ class ModelBase(type): raise ValueError, "Model %s must specify a custom Manager, because it has a field named 'objects'" % name new_class.add_to_class('objects', Manager()) - new_class._prepare() - - for field in fields: - if field.rel: - other = field.rel.to - if isinstance(other, basestring): - print "string lookup" - else: - related = RelatedObject(other._meta, new_class, field) - field.contribute_to_related_class(other, related) - + # Give the class a docstring -- its definition. + if new_class.__doc__ is None: + new_class.__doc__ = "%s.%s(%s)" % (opts.module_name, name, ", ".join([f.name for f in opts.fields])) + + if hasattr(new_class, 'get_absolute_url'): + new_class.get_absolute_url = curry(get_absolute_url, opts, new_class.get_absolute_url) + + opts._prepare() + new_class._prepare() + + # If the db_table wasn't provided, use the app_label + module_name. + if not opts.db_table: + opts.db_table = "%s_%s" % (app_label, opts.module_name) + + # Populate the _MODELS member on the module the class is in. + app_package.__dict__.setdefault('_MODELS', []).append(new_class) + + return new_class @@ -207,13 +185,6 @@ class Model(object): if not f.height_field: setattr(cls, 'get_%s_height' % f.name, curry(cls.__get_FIELD_height, field=f)) - # If the object has a relationship to itself, as designated by - # RECURSIVE_RELATIONSHIP_CONSTANT, create that relationship formally. - if f.rel and f.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT: - f.rel.to = cls - f.name = f.name or (f.rel.to._meta.object_name.lower() + '_' + f.rel.to._meta.pk.name) - f.verbose_name = f.verbose_name or f.rel.to._meta.verbose_name - f.rel.field_name = f.rel.field_name or f.rel.to._meta.pk.name # Add methods for many-to-one related objects. # EXAMPLES: Choice.get_poll(), Story.get_dateline() @@ -233,6 +204,8 @@ class Model(object): if cls._meta.order_with_respect_to: cls.get_next_in_order = curry(cls.__get_next_or_previous_in_order, is_next=True) cls.get_previous_in_order = curry(cls.__get_next_or_previous_in_order, is_next=False) + + RelatedField.do_pending_lookups(cls) _prepare = classmethod(_prepare) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index fd1c77974e..6c53eb01bb 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -88,7 +88,7 @@ class Field(object): # Tracks each time a Field instance is created. Used to retain order. creation_counter = 0 - + def __init__(self, verbose_name=None, name=None, primary_key=False, maxlength=None, unique=False, blank=False, null=False, db_index=False, core=False, rel=None, default=NOT_PROVIDED, editable=True, @@ -96,7 +96,8 @@ class Field(object): unique_for_year=None, validator_list=None, choices=None, radio_admin=None, help_text='', db_column=None): self.name = name - self.verbose_name = verbose_name or (name and name.replace('_', ' ')) + self.verbose_name = verbose_name + self.primary_key = primary_key self.maxlength, self.unique = maxlength, unique self.blank, self.null = blank, null @@ -110,16 +111,23 @@ class Field(object): self.radio_admin = radio_admin self.help_text = help_text self.db_column = db_column - - # Set db_index to True if the field has a relationship and doesn't explicitly set db_index. + # Set db_index to True if the field has a relationship and doesn't explicitly set db_index. self.db_index = db_index - # Increase the creation counter, and save our local copy. self.creation_counter = Field.creation_counter Field.creation_counter += 1 + + def __cmp__(self,other ): + #This is because bisect does not take a comparison function. grrr. + return cmp(self.creation_counter, other.creation_counter) + + def contribute_to_class(self, cls, name): + self.name = name self.attname, self.column = self.get_attname_column() + self.verbose_name = self.verbose_name or (name and name.replace('_', ' ')) + cls._meta.add_field(self) def set_name(self, name): self.name = name diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 6368a8473b..d065f2824d 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -1,21 +1,63 @@ from django.db.models.fields import Field, IntegerField +from django.db.models.related import RelatedObject from django.utils.translation import gettext_lazy, string_concat from django.utils.functional import curry from django.core import formfields + # Values for Relation.edit_inline. TABULAR, STACKED = 1, 2 RECURSIVE_RELATIONSHIP_CONSTANT = 'self' +#HACK +class RelatedField(object): + pending_lookups = {} + + def add_lookup(cls, rel_cls, field): + name = field.rel.to + module = rel_cls.__module__ + key = (module, name) + cls.pending_lookups.setdefault(key,[]).append( (rel_cls, field) ) + add_lookup = classmethod(add_lookup) + + def do_pending_lookups(cls, other_cls): + key = (other_cls.__module__, other_cls.__name__) + for (rel_cls,field) in cls.pending_lookups.setdefault(key,[]): + field.rel.to = other_cls + field.do_related_class(other_cls, rel_cls) + do_pending_lookups = classmethod(do_pending_lookups) + + def contribute_to_class(self, cls, name): + Field.contribute_to_class(self,cls,name) + other = self.rel.to + if isinstance(other, basestring): + if other == RECURSIVE_RELATIONSHIP_CONSTANT: + self.rel.to = cls.__name__ + self.add_lookup(cls, self) + else: + self.do_related_class(other, cls) + + def set_attributes_from_rel(self): + self.name = self.name or (self.rel.to._meta.object_name.lower() + '_' + self.rel.to._meta.pk.name) + self.verbose_name = self.verbose_name or self.rel.to._meta.verbose_name + self.rel.field_name = self.rel.field_name or self.rel.to._meta.pk.name + + def do_related_class(self, other, cls): + self.set_attributes_from_rel() + related = RelatedObject(other._meta, cls, self) + self.contribute_to_related_class(other, related) + + #HACK -class SharedMethods(object): +class SharedMethods(RelatedField): def get_attname(self): return '%s_id' % self.name def get_validator_unique_lookup_type(self): return '%s__%s__exact' % (self.name, self.rel.get_related_field().name) - + + class ForeignKey(SharedMethods,Field): empty_strings_allowed = False def __init__(self, to, to_field=None, **kwargs): @@ -98,7 +140,6 @@ class ForeignKey(SharedMethods,Field): def contribute_to_related_class(self, cls, related): rel_obj_name = related.get_method_name_part() - # Add "get_thingie" methods for many-to-one related objects. # EXAMPLE: Poll.get_choice() setattr(cls, 'get_%s' % rel_obj_name, curry(cls._get_related, method_name='get_object', rel_class=related.model, rel_field=related.field)) @@ -147,7 +188,7 @@ class OneToOneField(SharedMethods, IntegerField): rel_class=related.model, rel_field=related.field)) -class ManyToManyField(Field): +class ManyToManyField(RelatedField,Field): def __init__(self, to, **kwargs): kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural) kwargs['rel'] = ManyToMany(to, kwargs.pop('singular', None), @@ -225,6 +266,9 @@ class ManyToManyField(Field): func.alters_data = True setattr(cls, 'set_%s' % related.opts.module_name, func) + def set_attributes_from_rel(self): + pass + class ManyToOne: def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None, max_num_in_admin=None, num_extra_on_change=1, edit_inline=False, diff --git a/django/db/models/options.py b/django/db/models/options.py index 47a60e989e..7d9e8a5062 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -5,19 +5,17 @@ from django.db.models.loading import get_installed_model_modules from django.db.models.query import orderlist2sql from django.db.models.exceptions import FieldDoesNotExist +from bisect import bisect + class Options: def __init__(self, module_name='', verbose_name='', verbose_name_plural='', db_table='', - fields=None, ordering=None, unique_together=None, admin=None, + ordering=None, unique_together=None, admin=None, where_constraints=None, object_name=None, app_label=None, exceptions=None, permissions=None, get_latest_by=None, order_with_respect_to=None, module_constants=None): # Move many-to-many related fields from self.fields into self.many_to_many. self.fields, self.many_to_many = [], [] - for field in (fields or []): - if field.rel and isinstance(field.rel, ManyToMany): - self.many_to_many.append(field) - else: - self.fields.append(field) + self.module_name, self.verbose_name = module_name, verbose_name self.verbose_name_plural = verbose_name_plural or verbose_name + 's' self.db_table = db_table @@ -28,14 +26,22 @@ class Options: self.permissions = permissions or [] self.object_name, self.app_label = object_name, app_label self.get_latest_by = get_latest_by - if order_with_respect_to: + self.order_with_respect_to = order_with_respect_to + + self.module_constants = module_constants or {} + self.admin = admin + + def contribute_to_class(self, cls, name): + self.model = cls + cls._meta = self + + def _prepare(self): + if self.order_with_respect_to: self.order_with_respect_to = self.get_field(order_with_respect_to) self.ordering = ('_order',) else: self.order_with_respect_to = None - self.module_constants = module_constants or {} - self.admin = admin - + # Calculate one_to_one_field. self.one_to_one_field = None for f in self.fields: @@ -51,7 +57,9 @@ class Options: # If a primary_key field hasn't been specified, add an # auto-incrementing primary-key ID field automatically. if self.pk is None: - self.fields.insert(0, AutoField(name='id', verbose_name='ID', primary_key=True)) + auto = AutoField(verbose_name='ID', primary_key=True) + auto.creation_counter = -1 + self.model.add_to_class('id', auto) self.pk = self.fields[0] # Cache whether this has an AutoField. self.has_auto_field = False @@ -64,6 +72,15 @@ class Options: #HACK self.limit_choices_to = {} + def add_field(self, field): + # Insert the fields in the order that they were created. The + # "creation_counter" is needed because metaclasses don't preserve the + # attribute order. + if field.rel and isinstance(field.rel, ManyToMany): + self.many_to_many.insert(bisect(self.many_to_many, field), field) + else: + self.fields.insert(bisect(self.fields,field),field) + def __repr__(self): return '' % self.module_name