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
This commit is contained in:
Adrian Holovaty 2006-01-28 00:12:26 +00:00
parent a6139af645
commit b22ebaf466
4 changed files with 51 additions and 19 deletions

View File

@ -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)()

View File

@ -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))

View File

@ -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

View File

@ -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)