magic-removal: Added ReverseManyRelatedObjectsDescriptor, which implements many-to-many lookup -- e.g. poll_obj.sites rather than poll_obj.get_site_list(). Many-to-many addition/deletion still needs to be done.

git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@2247 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2006-02-03 20:36:50 +00:00
parent f59ca4e96c
commit 2ff5e5e765
2 changed files with 50 additions and 22 deletions

View File

@ -357,22 +357,6 @@ class Model(object):
setattr(self, cachename, get_image_dimensions(filename))
return getattr(self, cachename)
def _get_many_to_many_objects(self, field_with_rel):
cache_var = '_%s_cache' % field_with_rel.name
if not hasattr(self, cache_var):
rel_opts = field_with_rel.rel.to._meta
sql = "SELECT %s FROM %s a, %s b WHERE a.%s = b.%s AND b.%s = %%s %s" % \
(','.join(['a.%s' % backend.quote_name(f.column) for f in rel_opts.fields]),
backend.quote_name(rel_opts.db_table),
backend.quote_name(field_with_rel.get_m2m_db_table(self._meta)),
backend.quote_name(rel_opts.pk.column),
backend.quote_name(rel_opts.object_name.lower() + '_id'),
backend.quote_name(self._meta.object_name.lower() + '_id'), rel_opts.get_order_sql('a'))
cursor = connection.cursor()
cursor.execute(sql, [self._get_pk_val()])
setattr(self, cache_var, [field_with_rel.rel.to(*row) for row in cursor.fetchall()])
return getattr(self, cache_var)
def _set_many_to_many_objects(self, id_list, field_with_rel):
current_ids = [obj._get_pk_val() for obj in self._get_many_to_many_objects(field_with_rel)]
ids_to_add, ids_to_delete = dict([(i, 1) for i in id_list]), []

View File

@ -1,3 +1,4 @@
from django.db import backend
from django.db.models import signals
from django.db.models.fields import AutoField, Field, IntegerField
from django.db.models.related import RelatedObject
@ -126,14 +127,59 @@ class ManyRelatedObjectsDescriptor(object):
# Prepare the manager.
# TODO: Fix this hack?
# We're setting self.manager.model here because
# self.manager._prepare() expects that self.manager.model is
# set. This is slightly hackish.
# We're setting manager.model here because manager._prepare() expects
# that manager.model is set. This is slightly hackish.
manager.model = self.related.model
manager._prepare()
return manager
class ReverseManyRelatedObjectsDescriptor(object):
# This class provides the functionality that makes the related-object
# managers available as attributes on a model class, for fields that have
# multiple "remote" values and have a ManyToManyField defined in their
# model (rather than having another model pointed *at* them).
# In the example "poll.sites", the sites attribute is a
# ManyRelatedObjectsDescriptor instance.
def __init__(self, m2m_field):
self.field = m2m_field
self.rel_model = m2m_field.rel.to
def __get__(self, instance, instance_type=None):
if instance is None:
raise AttributeError, "Manager must be accessed via instance"
qn = backend.quote_name
this_opts = instance.__class__._meta
rel_opts = self.rel_model._meta
join_table = backend.quote_name(self.field.get_m2m_db_table(this_opts))
# Dynamically create a class that subclasses the related
# model's default manager.
superclass = self.rel_model._default_manager.__class__
class_ = types.ClassType('RelatedManager', (superclass,), {})
# Override get_query_set on the RelatedManager
def get_query_set(self):
return superclass.get_query_set(self).extra(
tables=(join_table,),
where=[
'%s.%s = %s.%s' % (qn(rel_opts.db_table), qn(rel_opts.pk.column), join_table, rel_opts.object_name.lower() + '_id'),
'%s.%s = %%s' % (join_table, this_opts.object_name.lower() + '_id')
],
params = [instance._get_pk_val()]
)
class_.get_query_set = get_query_set
manager = class_()
# Prepare the manager.
# TODO: Fix this hack?
# We're setting manager.model here because manager._prepare() expects
# that manager.model is set. This is slightly hackish.
manager.model = self.rel_model
manager._prepare()
return manager
class ForeignKey(RelatedField, Field):
empty_strings_allowed = False
def __init__(self, to, to_field=None, **kwargs):
@ -336,9 +382,7 @@ class ManyToManyField(RelatedField, Field):
def contribute_to_class(self, cls, name):
super(ManyToManyField, self).contribute_to_class(cls, name)
# Add "get_thingie" methods for many-to-many related objects.
# EXAMPLES: Poll.get_site_list(), Story.get_byline_list()
setattr(cls, 'get_%s_list' % self.rel.singular, curry(cls._get_many_to_many_objects, field_with_rel=self))
setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self))
# Add "set_thingie" methods for many-to-many related objects.
# EXAMPLES: Poll.set_sites(), Story.set_bylines()