Commit 99b467f2 authored by Andrew Godwin's avatar Andrew Godwin
Browse files

Add related_query_name to ForeignKey/M2M. Refs #20244

parent e26b589b
Loading
Loading
Loading
Loading
+12 −7
Original line number Diff line number Diff line
@@ -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),
+8 −0
Original line number Diff line number Diff line
@@ -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)
+20 −1
Original line number Diff line number Diff line
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.