diff --git a/django/db/models/base.py b/django/db/models/base.py index 80c9ba0045..8b69062e1c 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -202,7 +202,7 @@ class Model(object): seen_objs.setdefault(self.__class__, {})[pk_val] = self for related in self._meta.get_all_related_objects(): - rel_opts_name = related.get_accessor_name() + rel_opts_name = related.OLD_get_accessor_name() if isinstance(related.field.rel, OneToOne): try: sub_obj = getattr(self, 'get_%s' % rel_opts_name)() diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index f227273689..6d786b1c70 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -6,6 +6,7 @@ from django.utils.functional import curry from django.core import validators from django import forms from django.dispatch import dispatcher +import types # Values for Relation.edit_inline. TABULAR, STACKED = 1, 2 @@ -61,6 +62,38 @@ class RelatedField(object): related = RelatedObject(other, cls, self) self.contribute_to_related_class(other, related) +class RelatedObjectDescriptor(object): + # This class provides the functionality that makes the related-object + # managers available as attributes on a model class. + # In the example "poll.choice_set", the choice_set attribute is a + # RelatedObjectDescriptor instance. + def __init__(self, related): + self.related = related # RelatedObject instance + self.manager = None + + def __get__(self, instance, instance_type=None): + if instance is None: + raise AttributeError, "Manager must be accessed via instance" + else: + if not self.manager: + # Dynamically create a class that subclasses the related + # model's default manager. + self.manager = types.ClassType('RelatedManager', (self.related.model._default_manager.__class__,), {})() + + # Set core_filters on the new manager to limit it to the + # foreign-key relationship. + rel_field = self.related.field + self.manager.core_filters = {'%s__%s__exact' % (rel_field.name, rel_field.rel.to._meta.pk.name): getattr(instance, rel_field.rel.get_related_field().attname)} + + # Prepare the manager. + # TODO: We need to set self.manager.klass because + # self.manager._prepare() expects that self.manager.klass is + # set. This is slightly hackish. + self.manager.klass = self.related.model + self.manager._prepare() + + return self.manager + class ForeignKey(RelatedField, Field): empty_strings_allowed = False def __init__(self, to, to_field=None, **kwargs): @@ -151,7 +184,13 @@ class ForeignKey(RelatedField, Field): setattr(cls, 'get_%s' % self.name, curry(cls._get_foreign_key_object, field_with_rel=self)) def contribute_to_related_class(self, cls, related): - rel_obj_name = related.get_accessor_name() + setattr(cls, related.get_accessor_name(), RelatedObjectDescriptor(related)) + + # TODO: Delete the rest of this function and RelatedObject.OLD_get_accessor_name() + # to remove support for old-style related lookup. + + rel_obj_name = related.OLD_get_accessor_name + # 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)) @@ -206,7 +245,7 @@ class OneToOneField(RelatedField, IntegerField): setattr(cls, 'get_%s' % self.name, curry(cls._get_foreign_key_object, field_with_rel=self)) def contribute_to_related_class(self, cls, related): - rel_obj_name = related.get_accessor_name() + rel_obj_name = related.OLD_get_accessor_name() # Add "get_thingie" methods for one-to-one related objects. # EXAMPLE: Place.get_restaurants_restaurant() setattr(cls, 'get_%s' % rel_obj_name, @@ -296,7 +335,7 @@ class ManyToManyField(RelatedField, Field): setattr(cls, 'set_%s' % self.name, curry(cls._set_many_to_many_objects, field_with_rel=self)) def contribute_to_related_class(self, cls, related): - rel_obj_name = related.get_accessor_name() + rel_obj_name = related.OLD_get_accessor_name() setattr(cls, 'get_%s' % rel_obj_name, curry(cls._get_related_many_to_many, method_name='get_object', rel_class=related.model, rel_field=related.field)) setattr(cls, 'get_%s_count' % rel_obj_name, curry(cls._get_related_many_to_many, method_name='get_count', rel_class=related.model, rel_field=related.field)) setattr(cls, 'get_%s_list' % rel_obj_name, curry(cls._get_related_many_to_many, method_name='get_list', rel_class=related.model, rel_field=related.field)) diff --git a/django/db/models/manipulators.py b/django/db/models/manipulators.py index 8c08c19a09..a6c9d0e55e 100644 --- a/django/db/models/manipulators.py +++ b/django/db/models/manipulators.py @@ -154,7 +154,7 @@ class AutomaticManipulator(forms.Manipulator): if self.change: if rel_new_data[related.opts.pk.name][0]: try: - old_rel_obj = getattr(self.original_object, 'get_%s' % related.get_accessor_name() )(**{'%s__exact' % related.opts.pk.name: rel_new_data[related.opts.pk.attname][0]}) + old_rel_obj = getattr(self.original_object, 'get_%s' % related.OLD_get_accessor_name() )(**{'%s__exact' % related.opts.pk.name: rel_new_data[related.opts.pk.attname][0]}) except ObjectDoesNotExist: pass diff --git a/django/db/models/related.py b/django/db/models/related.py index a1bdb48d8b..733b495447 100644 --- a/django/db/models/related.py +++ b/django/db/models/related.py @@ -44,7 +44,7 @@ class RelatedObject(object): def get_list(self, parent_instance=None): "Get the list of this type of object from an instance of the parent class." if parent_instance != None: - func_name = 'get_%s_list' % self.get_accessor_name() + func_name = 'get_%s_list' % self.OLD_get_accessor_name() func = getattr(parent_instance, func_name) return func() else: @@ -80,19 +80,12 @@ class RelatedObject(object): def get_accessor_name(self): # This method encapsulates the logic that decides what name to give an # accessor descriptor that retrieves related many-to-one or - # many-to-many objects. Usually it just uses the lower-cased - # object_name, but if the related object is in another app, the related - # object's app_label is appended. - # - # Examples: - # - # # Normal case -- a related object in the same app. - # # This method returns "choice". - # Poll.choice_set - # - # # A related object in a different app. - # # This method returns "lcom_bestofaward". - # Place.lcom_bestofaward_set # "lcom_bestofaward" + # many-to-many objects. It uses the lower-cased object_name + "_set", + # but this can be overridden with the "related_name" option. + return self.field.rel.related_name or (self.opts.object_name.lower() + '_set') + + # TODO: Remove this. + def OLD_get_accessor_name(self): rel_obj_name = self.field.rel.related_name or self.opts.object_name.lower() if self.parent_model._meta.app_label != self.opts.app_label: rel_obj_name = '%s_%s' % (self.opts.app_label, rel_obj_name)