summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid-Wobrock <david.wobrock@gmail.com>2020-10-03 20:30:53 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2020-10-05 10:07:34 +0200
commitc32d8f33d8e988a376e44997b8f3606d821f305e (patch)
tree5d9b8dddb2ed708f5f527cd7fb72a21472dc00e9
parent292b3be698ef58aff9c215d62a444f66ead578c3 (diff)
Fixed #31926 -- Fixed recreating queryset with FilteredRelation when using a pickled Query.
In a pickled join, the join_fields had the same values, but weren't the same object (contrary to when not pickling the QuerySet).
-rw-r--r--django/db/models/fields/reverse_related.py35
-rw-r--r--tests/queryset_pickle/tests.py34
2 files changed, 69 insertions, 0 deletions
diff --git a/django/db/models/fields/reverse_related.py b/django/db/models/fields/reverse_related.py
index 77c5d23d69..57f1c4d389 100644
--- a/django/db/models/fields/reverse_related.py
+++ b/django/db/models/fields/reverse_related.py
@@ -11,6 +11,7 @@ they're the closest concept currently available.
from django.core import exceptions
from django.utils.functional import cached_property
+from django.utils.hashable import make_hashable
from . import BLANK_CHOICE_DASH
from .mixins import FieldCacheMixin
@@ -115,6 +116,28 @@ class ForeignObjectRel(FieldCacheMixin):
self.related_model._meta.model_name,
)
+ @property
+ def identity(self):
+ return (
+ self.field,
+ self.model,
+ self.related_name,
+ self.related_query_name,
+ tuple(sorted(make_hashable(self.limit_choices_to))),
+ self.parent_link,
+ self.on_delete,
+ self.symmetrical,
+ self.multiple,
+ )
+
+ def __eq__(self, other):
+ if not isinstance(other, self.__class__):
+ return NotImplemented
+ return self.identity == other.identity
+
+ def __hash__(self):
+ return hash(self.identity)
+
def get_choices(
self, include_blank=True, blank_choice=BLANK_CHOICE_DASH,
limit_choices_to=None, ordering=(),
@@ -215,6 +238,10 @@ class ManyToOneRel(ForeignObjectRel):
state.pop('related_model', None)
return state
+ @property
+ def identity(self):
+ return super().identity + (self.field_name,)
+
def get_related_field(self):
"""
Return the Field in the 'to' object to which this relationship is tied.
@@ -279,6 +306,14 @@ class ManyToManyRel(ForeignObjectRel):
self.symmetrical = symmetrical
self.db_constraint = db_constraint
+ @property
+ def identity(self):
+ return super().identity + (
+ self.through,
+ self.through_fields,
+ self.db_constraint,
+ )
+
def get_related_field(self):
"""
Return the field in the 'to' object to which this relationship is tied.
diff --git a/tests/queryset_pickle/tests.py b/tests/queryset_pickle/tests.py
index 8575c6fe81..76a5c332c7 100644
--- a/tests/queryset_pickle/tests.py
+++ b/tests/queryset_pickle/tests.py
@@ -219,6 +219,40 @@ class PickleabilityTestCase(TestCase):
with self.assertNumQueries(0):
self.assert_pickles(groups)
+ def test_pickle_filteredrelation(self):
+ group = Group.objects.create(name='group')
+ event_1 = Event.objects.create(title='Big event', group=group)
+ event_2 = Event.objects.create(title='Small event', group=group)
+ Happening.objects.bulk_create([
+ Happening(event=event_1, number1=5),
+ Happening(event=event_2, number1=3),
+ ])
+ groups = Group.objects.annotate(
+ big_events=models.FilteredRelation(
+ 'event',
+ condition=models.Q(event__title__startswith='Big'),
+ ),
+ ).annotate(sum_number=models.Sum('big_events__happening__number1'))
+ groups_query = pickle.loads(pickle.dumps(groups.query))
+ groups = Group.objects.all()
+ groups.query = groups_query
+ self.assertEqual(groups.get().sum_number, 5)
+
+ def test_pickle_filteredrelation_m2m(self):
+ group = Group.objects.create(name='group')
+ m2mmodel = M2MModel.objects.create()
+ m2mmodel.groups.add(group)
+ groups = Group.objects.annotate(
+ first_m2mmodels=models.FilteredRelation(
+ 'm2mmodel',
+ condition=models.Q(m2mmodel__pk__lt=10),
+ ),
+ ).annotate(count_groups=models.Count('first_m2mmodels__groups'))
+ groups_query = pickle.loads(pickle.dumps(groups.query))
+ groups = Group.objects.all()
+ groups.query = groups_query
+ self.assertEqual(groups.get().count_groups, 1)
+
def test_annotation_with_callable_default(self):
# Happening.when has a callable default of datetime.datetime.now.
qs = Happening.objects.annotate(latest_time=models.Max('when'))