From 8dc675d90f14a84ef95f16c7cc8100d9a04459b3 Mon Sep 17 00:00:00 2001 From: shanghui Date: Sun, 14 Jan 2018 10:58:21 +0800 Subject: [PATCH] Fixed #28838 -- Fixed Model.save() crash if the base manager annotates with a related field. --- django/db/models/query.py | 2 ++ tests/custom_managers/models.py | 14 +++++++++++++- tests/custom_managers/tests.py | 16 ++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index ab5de74ac0..256a817a64 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -701,6 +701,8 @@ class QuerySet: "Cannot update a query once a slice has been taken." query = self.query.chain(sql.UpdateQuery) query.add_update_fields(values) + # Clear any annotations so that they won't be present in subqueries. + query._annotations = None self._result_cache = None return query.get_compiler(self.db).execute_sql(CURSOR) _update.alters_data = True diff --git a/tests/custom_managers/models.py b/tests/custom_managers/models.py index f90425db3c..ca2e14b047 100644 --- a/tests/custom_managers/models.py +++ b/tests/custom_managers/models.py @@ -25,6 +25,13 @@ class PublishedBookManager(models.Manager): return super().get_queryset().filter(is_published=True) +class AnnotatedBookManager(models.Manager): + def get_queryset(self): + return super().get_queryset().annotate( + favorite_avg=models.Avg('favorite_books__favorite_thing_id') + ) + + class CustomQuerySet(models.QuerySet): def filter(self, *args, **kwargs): queryset = super().filter(fun=True) @@ -131,7 +138,6 @@ class Book(models.Model): title = models.CharField(max_length=50) author = models.CharField(max_length=30) is_published = models.BooleanField(default=False) - published_objects = PublishedBookManager() authors = models.ManyToManyField(Person, related_name='books') fun_authors = models.ManyToManyField(FunPerson, related_name='books') favorite_things = GenericRelation( @@ -145,6 +151,12 @@ class Book(models.Model): object_id_field='favorite_thing_id', ) + published_objects = PublishedBookManager() + annotated_objects = AnnotatedBookManager() + + class Meta: + base_manager_name = 'annotated_objects' + def __str__(self): return self.title diff --git a/tests/custom_managers/tests.py b/tests/custom_managers/tests.py index a545382d8f..8e5ab1418f 100644 --- a/tests/custom_managers/tests.py +++ b/tests/custom_managers/tests.py @@ -617,6 +617,22 @@ class CustomManagersRegressTestCase(TestCase): book.refresh_from_db() self.assertEqual(book.title, 'Hi') + def test_save_clears_annotations_from_base_manager(self): + """Model.save() clears annotations from the base manager.""" + self.assertEqual(Book._meta.base_manager.name, 'annotated_objects') + book = Book.annotated_objects.create(title='Hunting') + Person.objects.create( + first_name='Bugs', last_name='Bunny', fun=True, + favorite_book=book, favorite_thing_id=1, + ) + book = Book.annotated_objects.first() + self.assertEqual(book.favorite_avg, 1) # Annotation from the manager. + book.title = 'New Hunting' + # save() fails if annotations that involve related fields aren't + # cleared before the update query. + book.save() + self.assertEqual(Book.annotated_objects.first().title, 'New Hunting') + def test_delete_related_on_filtered_manager(self): """Deleting related objects should also not be distracted by a restricted manager on the related object. This is a regression