Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

magic-removal: Added first stab at many-to-one descriptor lookup. Now…

… poll_obj.choice_set is created. poll_obj.get_choice_list(), poll_obj.get_choice() and poll_obj.get_choice_count() are still supported temporarily, but that code was marked with a TODO to be deleted.

git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@2141 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit b22ebaf466fe7a0d056eb7ef96299e18b72db7ea 1 parent a6139af
@adrianholovaty adrianholovaty authored
View
2  django/db/models/base.py
@@ -202,7 +202,7 @@ def __collect_sub_objects(self, seen_objs):
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)()
View
45 django/db/models/fields/related.py
@@ -6,6 +6,7 @@
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 @@ def do_related_class(self, other, cls):
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 @@ def contribute_to_class(self, cls, name):
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 @@ def contribute_to_class(self, cls, name):
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 @@ def contribute_to_class(self, cls, name):
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))
View
2  django/db/models/manipulators.py
@@ -154,7 +154,7 @@ def save(self, new_data):
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
View
21 django/db/models/related.py
@@ -44,7 +44,7 @@ def extract_data(self, data):
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 @@ def bind(self, field_mapping, original, bound_related_object_class=BoundRelatedO
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)
Please sign in to comment.
Something went wrong with that request. Please try again.