Add related_query_name to ForeignKey/M2M. Refs #20244

This commit is contained in:
Andrew Godwin 2013-06-27 14:44:21 +01:00
parent e26b589b8c
commit 99b467f272
3 changed files with 40 additions and 8 deletions

View File

@ -139,7 +139,7 @@ class RelatedField(Field):
# related object in a table-spanning query. It uses the lower-cased # related object in a table-spanning query. It uses the lower-cased
# object_name by default, but this can be overridden with the # object_name by default, but this can be overridden with the
# "related_name" option. # "related_name" option.
return self.rel.related_name or self.opts.model_name return self.rel.related_query_name or self.rel.related_name or self.opts.model_name
class RenameRelatedObjectDescriptorMethods(RenameMethodsBase): class RenameRelatedObjectDescriptorMethods(RenameMethodsBase):
@ -826,7 +826,7 @@ class ReverseManyRelatedObjectsDescriptor(object):
class ForeignObjectRel(object): class ForeignObjectRel(object):
def __init__(self, field, to, related_name=None, limit_choices_to=None, def __init__(self, field, to, related_name=None, limit_choices_to=None,
parent_link=False, on_delete=None): parent_link=False, on_delete=None, related_query_name=None):
try: try:
to._meta to._meta
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
@ -835,6 +835,7 @@ class ForeignObjectRel(object):
self.field = field self.field = field
self.to = to self.to = to
self.related_name = related_name self.related_name = related_name
self.related_query_name = related_query_name
self.limit_choices_to = {} if limit_choices_to is None else limit_choices_to self.limit_choices_to = {} if limit_choices_to is None else limit_choices_to
self.multiple = True self.multiple = True
self.parent_link = parent_link self.parent_link = parent_link
@ -862,10 +863,10 @@ class ForeignObjectRel(object):
class ManyToOneRel(ForeignObjectRel): class ManyToOneRel(ForeignObjectRel):
def __init__(self, field, to, field_name, related_name=None, limit_choices_to=None, def __init__(self, field, to, field_name, related_name=None, limit_choices_to=None,
parent_link=False, on_delete=None): parent_link=False, on_delete=None, related_query_name=None):
super(ManyToOneRel, self).__init__( super(ManyToOneRel, self).__init__(
field, to, related_name=related_name, limit_choices_to=limit_choices_to, field, to, related_name=related_name, limit_choices_to=limit_choices_to,
parent_link=parent_link, on_delete=on_delete) parent_link=parent_link, on_delete=on_delete, related_query_name=related_query_name)
self.field_name = field_name self.field_name = field_name
def get_related_field(self): def get_related_field(self):
@ -885,21 +886,22 @@ class ManyToOneRel(ForeignObjectRel):
class OneToOneRel(ManyToOneRel): class OneToOneRel(ManyToOneRel):
def __init__(self, field, to, field_name, related_name=None, limit_choices_to=None, def __init__(self, field, to, field_name, related_name=None, limit_choices_to=None,
parent_link=False, on_delete=None): parent_link=False, on_delete=None, related_query_name=None):
super(OneToOneRel, self).__init__(field, to, field_name, super(OneToOneRel, self).__init__(field, to, field_name,
related_name=related_name, limit_choices_to=limit_choices_to, related_name=related_name, limit_choices_to=limit_choices_to,
parent_link=parent_link, on_delete=on_delete parent_link=parent_link, on_delete=on_delete, related_query_name=related_query_name,
) )
self.multiple = False self.multiple = False
class ManyToManyRel(object): class ManyToManyRel(object):
def __init__(self, to, related_name=None, limit_choices_to=None, def __init__(self, to, related_name=None, limit_choices_to=None,
symmetrical=True, through=None, db_constraint=True): symmetrical=True, through=None, db_constraint=True, related_query_name=None):
if through and not db_constraint: if through and not db_constraint:
raise ValueError("Can't supply a through model and db_constraint=False") raise ValueError("Can't supply a through model and db_constraint=False")
self.to = to self.to = to
self.related_name = related_name self.related_name = related_name
self.related_query_name = related_query_name
if limit_choices_to is None: if limit_choices_to is None:
limit_choices_to = {} limit_choices_to = {}
self.limit_choices_to = limit_choices_to self.limit_choices_to = limit_choices_to
@ -933,6 +935,7 @@ class ForeignObject(RelatedField):
kwargs['rel'] = ForeignObjectRel( kwargs['rel'] = ForeignObjectRel(
self, to, self, to,
related_name=kwargs.pop('related_name', None), related_name=kwargs.pop('related_name', None),
related_query_name=kwargs.pop('related_query_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None), limit_choices_to=kwargs.pop('limit_choices_to', None),
parent_link=kwargs.pop('parent_link', False), parent_link=kwargs.pop('parent_link', False),
on_delete=kwargs.pop('on_delete', CASCADE), on_delete=kwargs.pop('on_delete', CASCADE),
@ -1141,6 +1144,7 @@ class ForeignKey(ForeignObject):
kwargs['rel'] = rel_class( kwargs['rel'] = rel_class(
self, to, to_field, self, to, to_field,
related_name=kwargs.pop('related_name', None), related_name=kwargs.pop('related_name', None),
related_query_name=kwargs.pop('related_query_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None), limit_choices_to=kwargs.pop('limit_choices_to', None),
parent_link=kwargs.pop('parent_link', False), parent_link=kwargs.pop('parent_link', False),
on_delete=kwargs.pop('on_delete', CASCADE), on_delete=kwargs.pop('on_delete', CASCADE),
@ -1340,6 +1344,7 @@ class ManyToManyField(RelatedField):
kwargs['verbose_name'] = kwargs.get('verbose_name', None) kwargs['verbose_name'] = kwargs.get('verbose_name', None)
kwargs['rel'] = ManyToManyRel(to, kwargs['rel'] = ManyToManyRel(to,
related_name=kwargs.pop('related_name', None), related_name=kwargs.pop('related_name', None),
related_query_name=kwargs.pop('related_query_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None), limit_choices_to=kwargs.pop('limit_choices_to', None),
symmetrical=kwargs.pop('symmetrical', to == RECURSIVE_RELATIONSHIP_CONSTANT), symmetrical=kwargs.pop('symmetrical', to == RECURSIVE_RELATIONSHIP_CONSTANT),
through=kwargs.pop('through', None), through=kwargs.pop('through', None),

View File

@ -150,3 +150,11 @@ class ArticleTranslation(models.Model):
class Meta: class Meta:
unique_together = ('article', 'lang') unique_together = ('article', 'lang')
ordering = ('active_translation__title',) ordering = ('active_translation__title',)
class ArticleTag(models.Model):
article = models.ForeignKey(Article, related_name="tags", related_query_name="tag")
name = models.CharField(max_length=255)
class ArticleIdea(models.Model):
articles = models.ManyToManyField(Article, related_name="ideas", related_query_name="idea_things")
name = models.CharField(max_length=255)

View File

@ -1,9 +1,10 @@
import datetime import datetime
from operator import attrgetter from operator import attrgetter
from .models import Country, Person, Group, Membership, Friendship, Article, ArticleTranslation from .models import Country, Person, Group, Membership, Friendship, Article, ArticleTranslation, ArticleTag, ArticleIdea
from django.test import TestCase from django.test import TestCase
from django.utils.translation import activate from django.utils.translation import activate
from django.core.exceptions import FieldError
from django import forms from django import forms
class MultiColumnFKTests(TestCase): class MultiColumnFKTests(TestCase):
@ -321,6 +322,24 @@ class MultiColumnFKTests(TestCase):
with self.assertRaisesMessage(Article.DoesNotExist, 'ArticleTranslation has no article'): with self.assertRaisesMessage(Article.DoesNotExist, 'ArticleTranslation has no article'):
referrer.article referrer.article
def test_foreign_key_related_query_name(self):
a1 = Article.objects.create(pub_date=datetime.date.today())
ArticleTag.objects.create(article=a1, name="foo")
self.assertEqual(Article.objects.filter(tag__name="foo").count(), 1)
self.assertEqual(Article.objects.filter(tag__name="bar").count(), 0)
with self.assertRaises(FieldError):
Article.objects.filter(tags__name="foo")
def test_many_to_many_related_query_name(self):
a1 = Article.objects.create(pub_date=datetime.date.today())
i1 = ArticleIdea.objects.create(name="idea1")
a1.ideas.add(i1)
self.assertEqual(Article.objects.filter(idea_things__name="idea1").count(), 1)
self.assertEqual(Article.objects.filter(idea_things__name="idea2").count(), 0)
with self.assertRaises(FieldError):
Article.objects.filter(ideas__name="idea1")
class FormsTests(TestCase): class FormsTests(TestCase):
# ForeignObjects should not have any form fields, currently the user needs # ForeignObjects should not have any form fields, currently the user needs
# to manually deal with the foreignobject relation. # to manually deal with the foreignobject relation.