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:
parent
f59ca4e96c
commit
2ff5e5e765
|
@ -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]), []
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue