Fixed #21216 -- Allow `OneToOneField` reverse accessor to be hidden.

This commit is contained in:
Simon Charette 2013-08-20 01:52:32 -04:00
parent c4db7f075e
commit fa2e1371cd
4 changed files with 34 additions and 10 deletions

View File

@ -960,6 +960,7 @@ class ManyToManyRel(object):
class ForeignObject(RelatedField): class ForeignObject(RelatedField):
requires_unique_target = True requires_unique_target = True
generate_reverse_relation = True generate_reverse_relation = True
related_accessor_class = ForeignRelatedObjectsDescriptor
def __init__(self, to, from_fields, to_fields, **kwargs): def __init__(self, to, from_fields, to_fields, **kwargs):
self.from_fields = from_fields self.from_fields = from_fields
@ -1160,7 +1161,7 @@ class ForeignObject(RelatedField):
# Internal FK's - i.e., those with a related name ending with '+' - # Internal FK's - i.e., those with a related name ending with '+' -
# and swapped models don't get a related descriptor. # and swapped models don't get a related descriptor.
if not self.rel.is_hidden() and not related.model._meta.swapped: 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: if self.rel.limit_choices_to:
cls._meta.related_fkey_lookups.append(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), always returns the object pointed to (since there will only ever be one),
rather than returning a list. rather than returning a list.
""" """
related_accessor_class = SingleRelatedObjectDescriptor
description = _("One-to-one relationship") description = _("One-to-one relationship")
def __init__(self, to, to_field=None, **kwargs): def __init__(self, to, to_field=None, **kwargs):
@ -1346,10 +1348,6 @@ class OneToOneField(ForeignKey):
del kwargs['unique'] del kwargs['unique']
return name, path, args, kwargs 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): def formfield(self, **kwargs):
if self.rel.parent_link: if self.rel.parent_link:
return None return None

View File

@ -289,6 +289,11 @@ Models
* Explicit :class:`~django.db.models.OneToOneField` for * Explicit :class:`~django.db.models.OneToOneField` for
:ref:`multi-table-inheritance` are now discovered in abstract classes. :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 Signals
^^^^^^^ ^^^^^^^

View File

@ -12,6 +12,7 @@ class Place(models.Model):
def __str__(self): def __str__(self):
return "%s the place" % self.name return "%s the place" % self.name
@python_2_unicode_compatible @python_2_unicode_compatible
class Restaurant(models.Model): class Restaurant(models.Model):
place = models.OneToOneField(Place) place = models.OneToOneField(Place)
@ -21,6 +22,7 @@ class Restaurant(models.Model):
def __str__(self): def __str__(self):
return "%s the restaurant" % self.place.name return "%s the restaurant" % self.place.name
@python_2_unicode_compatible @python_2_unicode_compatible
class Bar(models.Model): class Bar(models.Model):
place = models.OneToOneField(Place) place = models.OneToOneField(Place)
@ -29,23 +31,32 @@ class Bar(models.Model):
def __str__(self): def __str__(self):
return "%s the bar" % self.place.name return "%s the bar" % self.place.name
class UndergroundBar(models.Model): class UndergroundBar(models.Model):
place = models.OneToOneField(Place, null=True) place = models.OneToOneField(Place, null=True)
serves_cocktails = models.BooleanField(default=True) serves_cocktails = models.BooleanField(default=True)
@python_2_unicode_compatible @python_2_unicode_compatible
class Favorites(models.Model): class Favorites(models.Model):
name = models.CharField(max_length = 50) name = models.CharField(max_length=50)
restaurants = models.ManyToManyField(Restaurant) restaurants = models.ManyToManyField(Restaurant)
def __str__(self): def __str__(self):
return "Favorites for %s" % self.name return "Favorites for %s" % self.name
class Target(models.Model): class Target(models.Model):
pass pass
class Pointer(models.Model): class Pointer(models.Model):
other = models.OneToOneField(Target, primary_key=True) other = models.OneToOneField(Target, primary_key=True)
class Pointer2(models.Model): 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+')

View File

@ -2,7 +2,8 @@ from __future__ import unicode_literals
from django.test import TestCase 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): class OneToOneRegressionTests(TestCase):
@ -125,11 +126,11 @@ class OneToOneRegressionTests(TestCase):
[] []
) )
self.assertQuerysetEqual( self.assertQuerysetEqual(
Target.objects.filter(pointer2=None), Target.objects.filter(second_pointer=None),
['<Target: Target object>'] ['<Target: Target object>']
) )
self.assertQuerysetEqual( self.assertQuerysetEqual(
Target.objects.exclude(pointer2=None), Target.objects.exclude(second_pointer=None),
[] []
) )
@ -250,3 +251,12 @@ class OneToOneRegressionTests(TestCase):
self.p1.delete() self.p1.delete()
self.assertTrue(UndergroundBar.objects.filter(pk=u.pk).exists()) self.assertTrue(UndergroundBar.objects.filter(pk=u.pk).exists())
self.assertIsNone(UndergroundBar.objects.get(pk=u.pk).place) 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())
)