mirror of
https://github.com/django/django.git
synced 2025-08-04 10:59:45 +00:00
Fixed #33450 -- Fixed RelatedExact lookups for GenericRelation and GenericRel with custom primary key types.
This commit is contained in:
parent
1823a80113
commit
381c61096b
5 changed files with 69 additions and 5 deletions
|
@ -288,6 +288,13 @@ class GenericRel(ForeignObjectRel):
|
|||
on_delete=DO_NOTHING,
|
||||
)
|
||||
|
||||
def get_effective_target_field(self):
|
||||
"""
|
||||
Return the field used to prepare lookup values when this relation is
|
||||
the target of a related exact lookup.
|
||||
"""
|
||||
return self.path_infos[0].from_opts.pk
|
||||
|
||||
|
||||
class GenericRelation(ForeignObject):
|
||||
"""
|
||||
|
@ -483,6 +490,13 @@ class GenericRelation(ForeignObject):
|
|||
qs = getattr(obj, self.name).all()
|
||||
return str([instance.pk for instance in qs])
|
||||
|
||||
def get_effective_target_field(self):
|
||||
"""
|
||||
Return the field used to prepare lookup values when this relation is
|
||||
the target of a related exact lookup.
|
||||
"""
|
||||
return self.path_infos[0].from_opts.pk
|
||||
|
||||
def contribute_to_class(self, cls, name, **kwargs):
|
||||
kwargs["private_only"] = True
|
||||
super().contribute_to_class(cls, name, **kwargs)
|
||||
|
|
|
@ -106,9 +106,16 @@ class RelatedLookupMixin:
|
|||
# doesn't have validation for non-integers, so we must run validation
|
||||
# using the target field.
|
||||
if self.prepare_rhs and hasattr(self.lhs.output_field, "path_infos"):
|
||||
# Get the target field. We can safely assume there is only one
|
||||
# as we don't get to the direct value branch otherwise.
|
||||
target_field = self.lhs.output_field.path_infos[-1].target_fields[-1]
|
||||
if hasattr(self.lhs.output_field, "get_effective_target_field"):
|
||||
# If the output_field specifies a field to prepare the rhs with,
|
||||
# use it.
|
||||
target_field = self.lhs.output_field.get_effective_target_field()
|
||||
else:
|
||||
# Get the target field. We can safely assume there is only one
|
||||
# as we don't get to the direct value branch otherwise.
|
||||
target_field = self.lhs.output_field.path_infos[-1].target_fields[
|
||||
-1
|
||||
]
|
||||
self.rhs = target_field.get_prep_value(self.rhs)
|
||||
|
||||
return super().get_prep_lookup()
|
||||
|
|
|
@ -255,8 +255,12 @@ class FieldGetDbPrepValueMixin:
|
|||
|
||||
def get_db_prep_lookup(self, value, connection):
|
||||
# For relational fields, use the 'target_field' attribute of the
|
||||
# output_field.
|
||||
field = getattr(self.lhs.output_field, "target_field", None)
|
||||
# output_field. If the field provides a get_effective_target_field
|
||||
# method, use that.
|
||||
if hasattr(self.lhs.output_field, "get_effective_target_field"):
|
||||
field = self.lhs.output_field.get_effective_target_field()
|
||||
else:
|
||||
field = getattr(self.lhs.output_field, "target_field", None)
|
||||
get_db_prep_value = (
|
||||
getattr(field, "get_db_prep_value", None)
|
||||
or self.lhs.output_field.get_db_prep_value
|
||||
|
|
|
@ -150,3 +150,25 @@ class AllowsNullGFK(models.Model):
|
|||
content_type = models.ForeignKey(ContentType, models.SET_NULL, null=True)
|
||||
object_id = models.PositiveIntegerField(null=True)
|
||||
content_object = GenericForeignKey()
|
||||
|
||||
|
||||
class Note(models.Model):
|
||||
title = models.CharField(max_length=30)
|
||||
content_type = models.ForeignKey(
|
||||
ContentType, on_delete=models.CASCADE, related_name="notes"
|
||||
)
|
||||
object_id = models.CharField(max_length=36)
|
||||
owner = GenericForeignKey()
|
||||
|
||||
|
||||
class Story(models.Model):
|
||||
name = models.CharField(max_length=30, primary_key=True)
|
||||
notes = GenericRelation("Note", related_query_name="story")
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True)
|
||||
object_id = models.PositiveIntegerField(null=True)
|
||||
inspiration = GenericForeignKey()
|
||||
|
||||
|
||||
class Idea(models.Model):
|
||||
description = models.TextField()
|
||||
stories = GenericRelation(Story)
|
||||
|
|
|
@ -13,10 +13,13 @@ from .models import (
|
|||
ForConcreteModelModel,
|
||||
ForProxyModelModel,
|
||||
Gecko,
|
||||
Idea,
|
||||
ManualPK,
|
||||
Mineral,
|
||||
Note,
|
||||
ProxyRelatedModel,
|
||||
Rock,
|
||||
Story,
|
||||
TaggedItem,
|
||||
ValuableRock,
|
||||
ValuableTaggedItem,
|
||||
|
@ -860,3 +863,17 @@ class TestInitWithNoneArgument(SimpleTestCase):
|
|||
# TaggedItem requires a content_type but initializing with None should
|
||||
# be allowed.
|
||||
TaggedItem(content_object=None)
|
||||
|
||||
|
||||
class GenericRelationQueryTests(TestCase):
|
||||
def test_generic_relation_related_exact_lookup_incompatible_pk_types(self):
|
||||
story = Story.objects.create(name="The Hobbit")
|
||||
note1 = Note.objects.create(title="Fantastic fantasy", owner=story)
|
||||
self.assertSequenceEqual(Note.objects.filter(story=story), [note1])
|
||||
self.assertSequenceEqual(Note.objects.filter(story=story.pk), [note1])
|
||||
|
||||
def test_generic_relation_reverse_related_exact_lookup_incompatible_pk_types(self):
|
||||
idea = Idea.objects.create(description="Eureka")
|
||||
story = Story.objects.create(name="Bathtime Serendipity", inspiration=idea)
|
||||
self.assertSequenceEqual(Idea.objects.filter(stories=story), [idea])
|
||||
self.assertSequenceEqual(Idea.objects.filter(stories=story.pk), [idea])
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue