diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 80249817e23..0367d24fe89 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -139,7 +139,7 @@ class RelatedField(Field): # related object in a table-spanning query. It uses the lower-cased # object_name by default, but this can be overridden with the # "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): @@ -826,7 +826,7 @@ class ReverseManyRelatedObjectsDescriptor(object): class ForeignObjectRel(object): 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: to._meta 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.to = to 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.multiple = True self.parent_link = parent_link @@ -862,10 +863,10 @@ class ForeignObjectRel(object): class ManyToOneRel(ForeignObjectRel): 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__( 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 def get_related_field(self): @@ -885,21 +886,22 @@ class ManyToOneRel(ForeignObjectRel): class OneToOneRel(ManyToOneRel): 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, 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 class ManyToManyRel(object): 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: raise ValueError("Can't supply a through model and db_constraint=False") self.to = to self.related_name = related_name + self.related_query_name = related_query_name if limit_choices_to is None: limit_choices_to = {} self.limit_choices_to = limit_choices_to @@ -933,6 +935,7 @@ class ForeignObject(RelatedField): kwargs['rel'] = ForeignObjectRel( self, to, 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), parent_link=kwargs.pop('parent_link', False), on_delete=kwargs.pop('on_delete', CASCADE), @@ -1141,6 +1144,7 @@ class ForeignKey(ForeignObject): kwargs['rel'] = rel_class( self, to, to_field, 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), parent_link=kwargs.pop('parent_link', False), on_delete=kwargs.pop('on_delete', CASCADE), @@ -1340,6 +1344,7 @@ class ManyToManyField(RelatedField): kwargs['verbose_name'] = kwargs.get('verbose_name', None) kwargs['rel'] = ManyToManyRel(to, 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), symmetrical=kwargs.pop('symmetrical', to == RECURSIVE_RELATIONSHIP_CONSTANT), through=kwargs.pop('through', None), diff --git a/tests/foreign_object/models.py b/tests/foreign_object/models.py index 2d02b5624c9..eee8091a159 100644 --- a/tests/foreign_object/models.py +++ b/tests/foreign_object/models.py @@ -150,3 +150,11 @@ class ArticleTranslation(models.Model): class Meta: unique_together = ('article', 'lang') 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) diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py index 69636ee49bf..670fc94dc5b 100644 --- a/tests/foreign_object/tests.py +++ b/tests/foreign_object/tests.py @@ -1,9 +1,10 @@ import datetime 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.utils.translation import activate +from django.core.exceptions import FieldError from django import forms class MultiColumnFKTests(TestCase): @@ -321,6 +322,24 @@ class MultiColumnFKTests(TestCase): with self.assertRaisesMessage(Article.DoesNotExist, 'ArticleTranslation has no 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): # ForeignObjects should not have any form fields, currently the user needs # to manually deal with the foreignobject relation.