From a2de78b3b8db5784cc1aecefd90f833ba7d9532e Mon Sep 17 00:00:00 2001
From: Simon Charette <charette.s@gmail.com>
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 6cd8d19..aa2a83b 100644
|
a
|
b
|
class ManyToManyRel(object):
|
| 960 | 960 | class ForeignObject(RelatedField): |
| 961 | 961 | requires_unique_target = True |
| 962 | 962 | generate_reverse_relation = True |
| | 963 | related_accessor_class = ForeignRelatedObjectsDescriptor |
| 963 | 964 | |
| 964 | 965 | def __init__(self, to, from_fields, to_fields, **kwargs): |
| 965 | 966 | self.from_fields = from_fields |
| … |
… |
class ForeignObject(RelatedField):
|
| 1160 | 1161 | # Internal FK's - i.e., those with a related name ending with '+' - |
| 1161 | 1162 | # and swapped models don't get a related descriptor. |
| 1162 | 1163 | if not self.rel.is_hidden() and not related.model._meta.swapped: |
| 1163 | | setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) |
| | 1164 | setattr(cls, related.get_accessor_name(), self.related_accessor_class(related)) |
| 1164 | 1165 | if self.rel.limit_choices_to: |
| 1165 | 1166 | cls._meta.related_fkey_lookups.append(self.rel.limit_choices_to) |
| 1166 | 1167 | |
| … |
… |
class OneToOneField(ForeignKey):
|
| 1334 | 1335 | always returns the object pointed to (since there will only ever be one), |
| 1335 | 1336 | rather than returning a list. |
| 1336 | 1337 | """ |
| | 1338 | related_accessor_class = SingleRelatedObjectDescriptor |
| 1337 | 1339 | description = _("One-to-one relationship") |
| 1338 | 1340 | |
| 1339 | 1341 | def __init__(self, to, to_field=None, **kwargs): |
| … |
… |
class OneToOneField(ForeignKey):
|
| 1346 | 1348 | del kwargs['unique'] |
| 1347 | 1349 | return name, path, args, kwargs |
| 1348 | 1350 | |
| 1349 | | def contribute_to_related_class(self, cls, related): |
| 1350 | | setattr(cls, related.get_accessor_name(), |
| 1351 | | SingleRelatedObjectDescriptor(related)) |
| 1352 | | |
| 1353 | 1351 | def formfield(self, **kwargs): |
| 1354 | 1352 | if self.rel.parent_link: |
| 1355 | 1353 | return None |
diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt
index d613220..8f650a1 100644
|
a
|
b
|
Models
|
| 289 | 289 | * Explicit :class:`~django.db.models.OneToOneField` for |
| 290 | 290 | :ref:`multi-table-inheritance` are now discovered in abstract classes. |
| 291 | 291 | |
| | 292 | * Is it now possible to avoid creating a backward relation for |
| | 293 | :class:`~django.db.models.OneToOneField` by setting its |
| | 294 | :attr:`~django.db.models.ForeignKey.related_name` to |
| | 295 | `'+'` or ending it with `'+'`. |
| | 296 | |
| 292 | 297 | Signals |
| 293 | 298 | ^^^^^^^ |
| 294 | 299 | |
diff --git a/tests/one_to_one_regress/models.py b/tests/one_to_one_regress/models.py
index 9b65edf..3cd4e17 100644
|
a
|
b
|
class Place(models.Model):
|
| 12 | 12 | def __str__(self): |
| 13 | 13 | return "%s the place" % self.name |
| 14 | 14 | |
| | 15 | |
| 15 | 16 | @python_2_unicode_compatible |
| 16 | 17 | class Restaurant(models.Model): |
| 17 | 18 | place = models.OneToOneField(Place) |
| … |
… |
class Restaurant(models.Model):
|
| 21 | 22 | def __str__(self): |
| 22 | 23 | return "%s the restaurant" % self.place.name |
| 23 | 24 | |
| | 25 | |
| 24 | 26 | @python_2_unicode_compatible |
| 25 | 27 | class Bar(models.Model): |
| 26 | 28 | place = models.OneToOneField(Place) |
| … |
… |
class Bar(models.Model):
|
| 29 | 31 | def __str__(self): |
| 30 | 32 | return "%s the bar" % self.place.name |
| 31 | 33 | |
| | 34 | |
| 32 | 35 | class UndergroundBar(models.Model): |
| 33 | 36 | place = models.OneToOneField(Place, null=True) |
| 34 | 37 | serves_cocktails = models.BooleanField(default=True) |
| 35 | 38 | |
| | 39 | |
| 36 | 40 | @python_2_unicode_compatible |
| 37 | 41 | class Favorites(models.Model): |
| 38 | | name = models.CharField(max_length = 50) |
| | 42 | name = models.CharField(max_length=50) |
| 39 | 43 | restaurants = models.ManyToManyField(Restaurant) |
| 40 | 44 | |
| 41 | 45 | def __str__(self): |
| 42 | 46 | return "Favorites for %s" % self.name |
| 43 | 47 | |
| | 48 | |
| 44 | 49 | class Target(models.Model): |
| 45 | 50 | pass |
| 46 | 51 | |
| | 52 | |
| 47 | 53 | class Pointer(models.Model): |
| 48 | 54 | other = models.OneToOneField(Target, primary_key=True) |
| 49 | 55 | |
| | 56 | |
| 50 | 57 | class Pointer2(models.Model): |
| 51 | | other = models.OneToOneField(Target) |
| | 58 | other = models.OneToOneField(Target, related_name='second_pointer') |
| | 59 | |
| | 60 | |
| | 61 | class HiddenPointer(models.Model): |
| | 62 | 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 3dfe3e4..da4309e 100644
|
a
|
b
|
from __future__ import unicode_literals
|
| 2 | 2 | |
| 3 | 3 | from django.test import TestCase |
| 4 | 4 | |
| 5 | | from .models import Place, Restaurant, Bar, Favorites, Target, UndergroundBar |
| | 5 | from .models import (Bar, Favorites, HiddenPointer, Place, Restaurant, Target, |
| | 6 | UndergroundBar) |
| 6 | 7 | |
| 7 | 8 | |
| 8 | 9 | class OneToOneRegressionTests(TestCase): |
| … |
… |
class OneToOneRegressionTests(TestCase):
|
| 125 | 126 | [] |
| 126 | 127 | ) |
| 127 | 128 | self.assertQuerysetEqual( |
| 128 | | Target.objects.filter(pointer2=None), |
| | 129 | Target.objects.filter(second_pointer=None), |
| 129 | 130 | ['<Target: Target object>'] |
| 130 | 131 | ) |
| 131 | 132 | self.assertQuerysetEqual( |
| 132 | | Target.objects.exclude(pointer2=None), |
| | 133 | Target.objects.exclude(second_pointer=None), |
| 133 | 134 | [] |
| 134 | 135 | ) |
| 135 | 136 | |
| … |
… |
class OneToOneRegressionTests(TestCase):
|
| 250 | 251 | self.p1.delete() |
| 251 | 252 | self.assertTrue(UndergroundBar.objects.filter(pk=u.pk).exists()) |
| 252 | 253 | self.assertIsNone(UndergroundBar.objects.get(pk=u.pk).place) |
| | 254 | |
| | 255 | def test_hidden_accessor(self): |
| | 256 | """ |
| | 257 | When a '+' ending related name is specified no reverse accessor should |
| | 258 | be added to the related model. |
| | 259 | """ |
| | 260 | self.assertFalse( |
| | 261 | hasattr(Target, HiddenPointer._meta.get_field('target').related.get_accessor_name()) |
| | 262 | ) |