diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 2f8330ed1c..e93777a368 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -141,6 +141,24 @@ class ForeignRelatedObjectsDescriptor(object): return new_obj create.alters_data = True + # remove() and clear() are only provided if the ForeignKey can have a value of null. + if rel_field.null: + def remove(self, *objs): + val = getattr(instance, rel_field.rel.get_related_field().attname) + for obj in objs: + if getattr(obj, rel_field.attname) == val: + setattr(obj, rel_field.attname, None) + obj.save() + else: + raise rel_field.rel.to.DoesNotExist, "'%s' is not related to '%s'." % (obj, instance) + add.alters_data = True + + def clear(self): + for obj in self.all(): + setattr(obj, rel_field.attname, None) + obj.save() + add.alters_data = True + manager = RelatedManager() manager.core_filters = {'%s__pk' % rel_field.name: getattr(instance, rel_field.rel.get_related_field().attname)} manager.model = self.related.model diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py index 6fb90daa0d..4e765f29b0 100644 --- a/tests/modeltests/many_to_one/models.py +++ b/tests/modeltests/many_to_one/models.py @@ -71,6 +71,12 @@ 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') +False +>>> hasattr(r2.article_set, 'clear') +False + # Reporter objects have access to their related Article objects. >>> r.article_set.order_by('pub_date') [This is a test, John's second story] diff --git a/tests/modeltests/many_to_one_null/models.py b/tests/modeltests/many_to_one_null/models.py index bd4aa82325..d6fba683e4 100644 --- a/tests/modeltests/many_to_one_null/models.py +++ b/tests/modeltests/many_to_one_null/models.py @@ -20,6 +20,9 @@ class Article(models.Model): def __repr__(self): return self.headline + class Meta: + ordering = ('headline',) + API_TESTS = """ # Create a Reporter. >>> r = Reporter(name='John Smith') @@ -79,4 +82,40 @@ DoesNotExist # To retrieve the articles with no reporters set, use "reporter__isnull=True". >>> Article.objects.filter(reporter__isnull=True) [Third] + +# Set the reporter for the Third article +>>> r.article_set.add(a3) +>>> r.article_set.all() +[First, Second, Third] + +# Remove an article from the set, and check that it was removed. +>>> r.article_set.remove(a3) +>>> r.article_set.all() +[First, Second] +>>> Article.objects.filter(reporter__isnull=True) +[Third] + +# Create another article and reporter +>>> r2 = Reporter(name='Paul Jones') +>>> r2.save() +>>> a4 = r2.article_set.create(headline='Fourth') +>>> r2.article_set.all() +[Fourth] + +# Try to remove a4 from a set it does not belong to +>>> r.article_set.remove(a4) +Traceback (most recent call last): +... +DoesNotExist: 'Fourth' is not related to 'John Smith'. + +>>> r2.article_set.all() +[Fourth] + +# Clear the rest of the set +>>> r.article_set.clear() +>>> r.article_set.all() +[] +>>> Article.objects.filter(reporter__isnull=True) +[First, Second, Third] + """