From a5bd0543f10dca603f384c075a528997cc2f84c6 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Mar 2006 03:58:11 +0000 Subject: [PATCH] magic-removal: Added descriptor code for assignment of related object sets (Reporter.article_set = [a,b,c]). git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@2510 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/fields/related.py | 34 +++++++++++++++++++++ tests/modeltests/many_to_many/models.py | 18 +++++++---- tests/modeltests/many_to_one/models.py | 17 +++++++++-- tests/modeltests/many_to_one_null/models.py | 10 ++++-- 4 files changed, 68 insertions(+), 11 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index d35e652b86..d60708496b 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -94,6 +94,8 @@ class SingleRelatedObjectDescriptor(object): return rel_obj def __set__(self, instance, value): + if instance is None: + raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name # Set the value of the related field setattr(value, self.related.field.attname, instance) @@ -126,6 +128,8 @@ class ReverseSingleRelatedObjectDescriptor(object): return rel_obj def __set__(self, instance, value): + if instance is None: + raise AttributeError, "%s must be accessed via instance" % self._field.name # Set the value of the related field try: val = getattr(value, self.field.rel.get_related_field().attname) @@ -197,6 +201,18 @@ class ForeignRelatedObjectsDescriptor(object): return manager + def __set__(self, instance, value): + if instance is None: + raise AttributeError, "Manager must be accessed via instance" + + manager = self.__get__(instance) + # If the foreign key can support nulls, then completely clear the related set. + # Otherwise, just move the named objects into the set. + if self.related.field.null: + manager.clear() + for obj in value: + manager.add(obj) + def _add_m2m_items(rel_manager_inst, managerclass, rel_model, join_table, source_col_name, target_col_name, source_pk_val, *objs): # Utility function used by the ManyRelatedObjectsDescriptors @@ -329,6 +345,15 @@ class ManyRelatedObjectsDescriptor(object): return manager + def __set__(self, instance, value): + if instance is None: + raise AttributeError, "Manager must be accessed via instance" + + manager = self.__get__(instance) + manager.clear() + for obj in value: + manager.add(obj) + 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 @@ -401,6 +426,15 @@ class ReverseManyRelatedObjectsDescriptor(object): return manager + def __set__(self, instance, value): + if instance is None: + raise AttributeError, "Manager must be accessed via instance" + + manager = self.__get__(instance) + manager.clear() + for obj in value: + manager.add(obj) + class ForeignKey(RelatedField, Field): empty_strings_allowed = False def __init__(self, to, to_field=None, **kwargs): diff --git a/tests/modeltests/many_to_many/models.py b/tests/modeltests/many_to_many/models.py index cfadb617f6..dc69f9a49f 100644 --- a/tests/modeltests/many_to_many/models.py +++ b/tests/modeltests/many_to_many/models.py @@ -144,12 +144,19 @@ API_TESTS = """ >>> a5.publications.all() [] -# You can clear the whole lot: -# (put some back first) ->>> p2.article_set.add(a4, a5) ->>> a4.publications.add(p3) +# Relation sets can be assigned. Assignment clears any existing set members +>>> p2.article_set = [a4, a5] +>>> p2.article_set.all() +[NASA finds intelligent life on Earth, Oxygen-free diet works wonders] >>> a4.publications.all() -[Science News, Science Weekly] +[Science News] +>>> a4.publications = [p3] +>>> p2.article_set.all() +[Oxygen-free diet works wonders] +>>> a4.publications.all() +[Science Weekly] + +# Relation sets can be cleared: >>> p2.article_set.clear() >>> p2.article_set.all() [] @@ -196,5 +203,4 @@ API_TESTS = """ >>> p1.article_set.all() [NASA uses Python] - """ diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py index 6f1ac96359..1b4ba03227 100644 --- a/tests/modeltests/many_to_one/models.py +++ b/tests/modeltests/many_to_one/models.py @@ -83,9 +83,20 @@ John Smith >>> r2.article_set.all() [] -# Set the article back again. ->>> new_article2.reporter = r2 ->>> new_article2.save() +# Set the article back again using set descriptor. +>>> r2.article_set = [new_article, new_article2] +>>> r.article_set.all() +[This is a test] +>>> r2.article_set.all() +[John's second story, Paul's story] + +# Funny case - assignment notation can only go so far; because the +# ForeignKey cannot be null, existing members of the set must remain +>>> r.article_set = [new_article] +>>> r.article_set.all() +[This is a test, John's second story] +>>> r2.article_set.all() +[Paul's story] # Reporter cannot be null - there should not be a clear or remove method >>> hasattr(r2.article_set, 'remove') diff --git a/tests/modeltests/many_to_one_null/models.py b/tests/modeltests/many_to_one_null/models.py index d6fba683e4..8a629aa137 100644 --- a/tests/modeltests/many_to_one_null/models.py +++ b/tests/modeltests/many_to_one_null/models.py @@ -49,7 +49,7 @@ Second 1 # Reporter objects have access to their related Article objects. ->>> r.article_set.order_by('headline') +>>> r.article_set.all() [First, Second] >>> r.article_set.filter(headline__startswith='Fir') [First] @@ -111,11 +111,17 @@ DoesNotExist: 'Fourth' is not related to 'John Smith'. >>> r2.article_set.all() [Fourth] +# Use descriptor assignment to allocate ForeignKey. Null is legal, so +# existing members of set that are not in the assignment set are set null +>>> r2.article_set = [a2, a3] +>>> r2.article_set.all() +[Second, Third] + # Clear the rest of the set >>> r.article_set.clear() >>> r.article_set.all() [] >>> Article.objects.filter(reporter__isnull=True) -[First, Second, Third] +[First, Fourth] """