Add related_query_name to ForeignKey/M2M. Refs #20244
This commit is contained in:
parent
e26b589b8c
commit
99b467f272
|
@ -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),
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue