Fixed #21216 -- Allow `OneToOneField` reverse accessor to be hidden.
This commit is contained in:
parent
c4db7f075e
commit
fa2e1371cd
|
@ -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
|
||||
|
|
|
@ -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
|
||||
^^^^^^^
|
||||
|
||||
|
|
|
@ -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+')
|
||||
|
|
|
@ -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),
|
||||
['<Target: Target object>']
|
||||
)
|
||||
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())
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue