From fa2e1371cda1e72d82b4133ad0b49a18e43ba411 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Tue, 20 Aug 2013 01:52:32 -0400 Subject: [PATCH] Fixed #21216 -- Allow `OneToOneField` reverse accessor to be hidden. --- django/db/models/fields/related.py | 8 +++----- docs/releases/1.7.txt | 5 +++++ tests/one_to_one_regress/models.py | 15 +++++++++++++-- tests/one_to_one_regress/tests.py | 16 +++++++++++++--- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 6cd8d193f53..aa2a83bd978 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -960,6 +960,7 @@ class ManyToManyRel(object): class ForeignObject(RelatedField): requires_unique_target = True generate_reverse_relation = True + related_accessor_class = ForeignRelatedObjectsDescriptor def __init__(self, to, from_fields, to_fields, **kwargs): self.from_fields = from_fields @@ -1160,7 +1161,7 @@ class ForeignObject(RelatedField): # Internal FK's - i.e., those with a related name ending with '+' - # and swapped models don't get a related descriptor. if not self.rel.is_hidden() and not related.model._meta.swapped: - setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) + setattr(cls, related.get_accessor_name(), self.related_accessor_class(related)) if self.rel.limit_choices_to: cls._meta.related_fkey_lookups.append(self.rel.limit_choices_to) @@ -1334,6 +1335,7 @@ class OneToOneField(ForeignKey): always returns the object pointed to (since there will only ever be one), rather than returning a list. """ + related_accessor_class = SingleRelatedObjectDescriptor description = _("One-to-one relationship") def __init__(self, to, to_field=None, **kwargs): @@ -1346,10 +1348,6 @@ class OneToOneField(ForeignKey): del kwargs['unique'] return name, path, args, kwargs - def contribute_to_related_class(self, cls, related): - setattr(cls, related.get_accessor_name(), - SingleRelatedObjectDescriptor(related)) - def formfield(self, **kwargs): if self.rel.parent_link: return None diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index d6132205080..8f650a1a9ee 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -289,6 +289,11 @@ Models * Explicit :class:`~django.db.models.OneToOneField` for :ref:`multi-table-inheritance` are now discovered in abstract classes. +* Is it now possible to avoid creating a backward relation for + :class:`~django.db.models.OneToOneField` by setting its + :attr:`~django.db.models.ForeignKey.related_name` to + `'+'` or ending it with `'+'`. + Signals ^^^^^^^ diff --git a/tests/one_to_one_regress/models.py b/tests/one_to_one_regress/models.py index 9b65edf2ab4..3cd4e176043 100644 --- a/tests/one_to_one_regress/models.py +++ b/tests/one_to_one_regress/models.py @@ -12,6 +12,7 @@ class Place(models.Model): def __str__(self): return "%s the place" % self.name + @python_2_unicode_compatible class Restaurant(models.Model): place = models.OneToOneField(Place) @@ -21,6 +22,7 @@ class Restaurant(models.Model): def __str__(self): return "%s the restaurant" % self.place.name + @python_2_unicode_compatible class Bar(models.Model): place = models.OneToOneField(Place) @@ -29,23 +31,32 @@ class Bar(models.Model): def __str__(self): return "%s the bar" % self.place.name + class UndergroundBar(models.Model): place = models.OneToOneField(Place, null=True) serves_cocktails = models.BooleanField(default=True) + @python_2_unicode_compatible class Favorites(models.Model): - name = models.CharField(max_length = 50) + name = models.CharField(max_length=50) restaurants = models.ManyToManyField(Restaurant) def __str__(self): return "Favorites for %s" % self.name + class Target(models.Model): pass + class Pointer(models.Model): other = models.OneToOneField(Target, primary_key=True) + class Pointer2(models.Model): - other = models.OneToOneField(Target) + other = models.OneToOneField(Target, related_name='second_pointer') + + +class HiddenPointer(models.Model): + target = models.OneToOneField(Target, related_name='hidden+') diff --git a/tests/one_to_one_regress/tests.py b/tests/one_to_one_regress/tests.py index 3dfe3e466e7..da4309e8219 100644 --- a/tests/one_to_one_regress/tests.py +++ b/tests/one_to_one_regress/tests.py @@ -2,7 +2,8 @@ from __future__ import unicode_literals from django.test import TestCase -from .models import Place, Restaurant, Bar, Favorites, Target, UndergroundBar +from .models import (Bar, Favorites, HiddenPointer, Place, Restaurant, Target, + UndergroundBar) class OneToOneRegressionTests(TestCase): @@ -125,11 +126,11 @@ class OneToOneRegressionTests(TestCase): [] ) self.assertQuerysetEqual( - Target.objects.filter(pointer2=None), + Target.objects.filter(second_pointer=None), [''] ) self.assertQuerysetEqual( - Target.objects.exclude(pointer2=None), + Target.objects.exclude(second_pointer=None), [] ) @@ -250,3 +251,12 @@ class OneToOneRegressionTests(TestCase): self.p1.delete() self.assertTrue(UndergroundBar.objects.filter(pk=u.pk).exists()) self.assertIsNone(UndergroundBar.objects.get(pk=u.pk).place) + + def test_hidden_accessor(self): + """ + When a '+' ending related name is specified no reverse accessor should + be added to the related model. + """ + self.assertFalse( + hasattr(Target, HiddenPointer._meta.get_field('target').related.get_accessor_name()) + )