From e18d87f84e1b6ce23c411b26aa060ae020244f8c Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Wed, 14 Dec 2005 18:49:48 +0000 Subject: [PATCH] magic-removal: Implemented hook for custom managers git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@1647 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/__init__.py | 58 +++++++++++++++------- django/db/models/fields.py | 2 +- tests/modeltests/custom_managers/models.py | 5 ++ 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index 9800de2bbf..5bb8571261 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -543,16 +543,23 @@ get_module_name = lambda class_name: class_name.lower() + 's' get_verbose_name = lambda class_name: re.sub('([A-Z])', ' \\1', class_name).lower().strip() class Manager(object): - def __init__(self, model_class): - self.klass = model_class + + # Tracks each time a Field instance is created. Used to retain order. + creation_counter = 0 + + def __init__(self): + # Increase the creation counter, and save our local copy. + self.creation_counter = Manager.creation_counter + Manager.creation_counter += 1 def __get__(self, instance, type=None): if instance != None: raise AttributeError, "Manager isn't accessible via %s instances" % self.klass.__name__ return self - def _prepare(self): + def _prepare(self, klass): # Creates some methods once self.klass._meta has been populated. + self.klass = klass if self.klass._meta.get_latest_by: self.get_latest = self.__get_latest for f in self.klass._meta.fields: @@ -735,7 +742,7 @@ class ModelBase(type): "Metaclass for all models" def __new__(cls, name, bases, attrs): # If this isn't a subclass of Model, don't do anything special. - if not bases: + if not bases or bases == (object,): return type.__new__(cls, name, bases, attrs) try: @@ -745,18 +752,22 @@ class ModelBase(type): except KeyError: meta_attrs = {} - # Gather all attributes that are Field instances. - fields = [] + # 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 in the order that they were created. The + # 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) opts = Options( module_name = meta_attrs.pop('module_name', get_module_name(name)), @@ -788,11 +799,6 @@ class ModelBase(type): # Create the class, because we need it to use in currying. new_class = type.__new__(cls, name, bases, attrs) - # Create the manager. - # TODO: Use weakref because of possible memory leak / circular reference. - # TODO: Allow for overriding of the name, and custom manager subclasses. - new_class.objects = Manager(new_class) - # 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])) @@ -816,8 +822,22 @@ class ModelBase(type): opts.db_table = "%s_%s" % (app_label, opts.module_name) new_class._meta = opts + # Create the default manager, if needed. + # TODO: Use weakref because of possible memory leak / circular reference. + if managers: + for m_name, m in managers: + m._prepare(new_class) + setattr(new_class, m_name, m) + new_class._default_manager = managers[0][1] + else: + if hasattr(new_class, 'objects'): + raise ValueError, "Model %s must specify a custom Manager, because it has a field named 'objects'" % name + m = Manager() + m._prepare(new_class) + new_class.objects = m + new_class._default_manager = m + new_class._prepare() - new_class.objects._prepare() return new_class @@ -1037,7 +1057,7 @@ class Model(object): file_name = getattr(self, 'get_%s_filename' % f.name)() # If the file exists and no other object of this type references it, # delete it from the filesystem. - if os.path.exists(file_name) and not self.objects.get_list(**{'%s__exact' % f.name: getattr(self, f.name)}): + if os.path.exists(file_name) and not self._default_manager.get_list(**{'%s__exact' % f.name: getattr(self, f.name)}): os.remove(file_name) # Run any post-delete hooks. @@ -1059,14 +1079,14 @@ class Model(object): kwargs.setdefault('params', []).extend([param, param, getattr(self, self._meta.pk.attname)]) kwargs['order_by'] = [(not is_next and '-' or '') + field.name, (not is_next and '-' or '') + self._meta.pk.name] kwargs['limit'] = 1 - return self.objects.get_object(**kwargs) + return self._default_manager.get_object(**kwargs) def __get_next_or_previous_in_order(self, is_next): cachename = "__%s_order_cache" % is_next if not hasattr(self, cachename): op = is_next and '>' or '<' order_field = self.order_with_respect_to - obj = self.objects.get_object(order_by=('_order',), + obj = self._default_manager.get_object(order_by=('_order',), where=['%s %s (SELECT %s FROM %s WHERE %s=%%s)' % \ (backend.quote_name('_order'), op, backend.quote_name('_order'), backend.quote_name(opts.db_table), backend.quote_name(opts.pk.column)), @@ -1153,7 +1173,7 @@ class Model(object): params = {'%s__%s__exact' % (field_with_rel.rel.field_name, other_field.rel.field_name): val} else: params = {'%s__exact' % field_with_rel.rel.field_name: val} - retrieved_obj = field_with_rel.rel.to.objects.get_object(**params) + retrieved_obj = field_with_rel.rel.to._default_manager.get_object(**params) setattr(self, cache_var, retrieved_obj) return getattr(self, cache_var) @@ -1213,7 +1233,7 @@ class Model(object): def __get_related(self, method_name, rel_class, rel_field, **kwargs): kwargs['%s__%s__exact' % (rel_field.name, rel_field.rel.to._meta.pk.name)] = getattr(self, rel_field.rel.get_related_field().attname) kwargs.update(rel_field.rel.lookup_overrides) - return getattr(rel_class.objects, method_name)(**kwargs) + return getattr(rel_class._default_manager, method_name)(**kwargs) def __add_related(self, rel_class, rel_field, *args, **kwargs): init_kwargs = dict(zip([f.attname for f in rel_class._meta.fields if f != rel_field and not isinstance(f, AutoField)], args)) @@ -1238,7 +1258,7 @@ class Model(object): # Examples: Album.get_song(), Album.get_song_list(), Album.get_song_count() def method_get_related_many_to_many(method_name, opts, rel_mod, rel_field, self, **kwargs): kwargs['%s__%s__exact' % (rel_field.name, opts.pk.name)] = getattr(self, opts.pk.attname) - return getattr(rel_mod.Klass.objects, method_name)(**kwargs) + return getattr(rel_mod.Klass._default_manager, method_name)(**kwargs) # Handles setting many-to-many related objects. # Example: Album.set_songs() diff --git a/django/db/models/fields.py b/django/db/models/fields.py index a99240c0d1..212dec036d 100644 --- a/django/db/models/fields.py +++ b/django/db/models/fields.py @@ -313,7 +313,7 @@ class Field(object): return first_choice + list(self.choices) rel_model = self.rel.to return first_choice + [(getattr(x, rel_model._meta.pk.attname), str(x)) - for x in rel_model.objects.get_list(**self.rel._meta.limit_choices_to)] + for x in rel_model._default_manager.get_list(**self.rel._meta.limit_choices_to)] def get_choices_default(self): if(self.radio_admin): diff --git a/tests/modeltests/custom_managers/models.py b/tests/modeltests/custom_managers/models.py index e1789abb07..d1257b2e27 100644 --- a/tests/modeltests/custom_managers/models.py +++ b/tests/modeltests/custom_managers/models.py @@ -83,4 +83,9 @@ AttributeError: type object 'Book' has no attribute 'objects' [Corvette, Neon] >>> Car.fast_cars.get_list() [Corvette] + +# Each model class gets a "_default_manager" attribute, which is a reference +# to the first manager defined in the class. In this case, it's "cars". +>>> Car._default_manager.get_list(order_by=('name',)) +[Corvette, Neon] """