1
0
mirror of https://github.com/django/django.git synced 2025-06-16 00:49:12 +00:00

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)) setattr(self, cachename, get_image_dimensions(filename))
return getattr(self, cachename) 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): 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)] 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]), [] 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 import signals
from django.db.models.fields import AutoField, Field, IntegerField from django.db.models.fields import AutoField, Field, IntegerField
from django.db.models.related import RelatedObject from django.db.models.related import RelatedObject
@ -126,14 +127,59 @@ class ManyRelatedObjectsDescriptor(object):
# Prepare the manager. # Prepare the manager.
# TODO: Fix this hack? # TODO: Fix this hack?
# We're setting self.manager.model here because # We're setting manager.model here because manager._prepare() expects
# self.manager._prepare() expects that self.manager.model is # that manager.model is set. This is slightly hackish.
# set. This is slightly hackish.
manager.model = self.related.model manager.model = self.related.model
manager._prepare() manager._prepare()
return manager 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): class ForeignKey(RelatedField, Field):
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, to, to_field=None, **kwargs): def __init__(self, to, to_field=None, **kwargs):
@ -336,9 +382,7 @@ class ManyToManyField(RelatedField, Field):
def contribute_to_class(self, cls, name): def contribute_to_class(self, cls, name):
super(ManyToManyField, self).contribute_to_class(cls, name) super(ManyToManyField, self).contribute_to_class(cls, name)
# Add "get_thingie" methods for many-to-many related objects. setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self))
# 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))
# Add "set_thingie" methods for many-to-many related objects. # Add "set_thingie" methods for many-to-many related objects.
# EXAMPLES: Poll.set_sites(), Story.set_bylines() # EXAMPLES: Poll.set_sites(), Story.set_bylines()