Skip to content

Commit

Permalink
magic-removal: Added first stab at many-to-one descriptor lookup. Now…
Browse files Browse the repository at this point in the history
… 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
adrianholovaty committed Jan 28, 2006
1 parent a6139af commit b22ebaf
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 19 deletions.
2 changes: 1 addition & 1 deletion django/db/models/base.py
Expand Up @@ -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)()
Expand Down
45 changes: 42 additions & 3 deletions django/db/models/fields/related.py
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion django/db/models/manipulators.py
Expand Up @@ -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

Expand Down
21 changes: 7 additions & 14 deletions django/db/models/related.py
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit b22ebaf

Please sign in to comment.