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):
|
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
|
||||||
|
|
|
@ -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
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -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,10 +31,12 @@ 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)
|
||||||
|
@ -41,11 +45,18 @@ class Favorites(models.Model):
|
||||||
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+')
|
||||||
|
|
|
@ -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())
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue