#36360: KeyError calling `update()` after an `annotate()` and a `values()`
-------------------------------------+-------------------------------------
     Reporter:  Gav O'Connor         |                     Type:  Bug
       Status:  new                  |                Component:  Database
                                     |  layer (models, ORM)
      Version:  5.2                  |                 Severity:  Normal
     Keywords:                       |             Triage Stage:
                                     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
 == Summary

 We have noticed an issue when migrating from Django 5.1 to 5.2 in that we
 now get a `KeyError` where it previously worked without issue. I can't see
 anything in the changelog that would point towards this being an intended
 change.

 The bug seems to happen when we try to call `.update()` on a QuerySet
 after we have called both `.annotate()` and `.values()` on it. A
 `KeyError` is raised with the name of the annotation.

 Our real-world example is quite complex, but I have managed to simplify it
 somewhat in the example below.

 == Example


 {{{#!python
 # models.py
 from django.db import models

 class Service(models.Model):
     name = models.CharField(max_length=255)
     slug = models.SlugField(max_length=255)

     def __str__(self):
         return self.name

 class Order(models.Model):
     service_slug = models.CharField(max_length=255)

     def __str__(self):
         return self.id

 class OrderLine(models.Model):
     order = models.ForeignKey(Order, on_delete=models.CASCADE)
     notes = models.CharField(max_length=255, blank=True, null=True)

     def __str__(self):
         return self.id
 }}}


 {{{#!python
 # tests.py
 from django.test import TestCase
 from django.db.models import OuterRef, Subquery
 from .models import Service, Order, OrderLine

 class MyTestCase(TestCase):
     def test_1(self):
         Service.objects.create(name="Green", slug="green")

         order = Order.objects.create(service_slug="green")
         OrderLine.objects.create(order=order)

         lines = OrderLine.objects.annotate(
             service=Subquery(
 
Service.objects.filter(slug=OuterRef('order__service_slug')).values_list('name')[:1]
             )
         ).values(
             'id',
             'service',
         )

         lines.update(notes='foo')
 }}}

 == Output

 === Django 5.1

 {{{
 ❯ uv add django==5.1
 ❯ uv run manage.py test
 Found 1 test(s).
 .
 ----------------------------------------------------------------------
 Ran 1 test in 0.001s

 OK
 }}}

 === Django 5.2

 {{{
 ❯ uv add django==5.2
 ❯ uv run manage.py test
 Found 1 test(s).
 E
 ======================================================================
 ERROR: test_1 (core.tests.MyTestCase.test_1)
 ----------------------------------------------------------------------
 Traceback (most recent call last):
   File "/Users/gav/code/annotatebug/core/tests.py", line 21, in test_1
     lines.update(notes='foo')
     ~~~~~~~~~~~~^^^^^^^^^^^^^
   File "/Users/gav/code/annotatebug/.venv/lib/python3.13/site-
 packages/django/db/models/query.py", line 1258, in update
     rows = query.get_compiler(self.db).execute_sql(ROW_COUNT)
   File "/Users/gav/code/annotatebug/.venv/lib/python3.13/site-
 packages/django/db/models/sql/compiler.py", line 2059, in execute_sql
     row_count = super().execute_sql(result_type)
   File "/Users/gav/code/annotatebug/.venv/lib/python3.13/site-
 packages/django/db/models/sql/compiler.py", line 1609, in execute_sql
     sql, params = self.as_sql()
                   ~~~~~~~~~~~^^
   File "/Users/gav/code/annotatebug/.venv/lib/python3.13/site-
 packages/django/db/models/sql/compiler.py", line 1988, in as_sql
     self.pre_sql_setup()
     ~~~~~~~~~~~~~~~~~~^^
   File "/Users/gav/code/annotatebug/.venv/lib/python3.13/site-
 packages/django/db/models/sql/compiler.py", line 2110, in pre_sql_setup
     super().pre_sql_setup()
     ~~~~~~~~~~~~~~~~~~~~~^^
   File "/Users/gav/code/annotatebug/.venv/lib/python3.13/site-
 packages/django/db/models/sql/compiler.py", line 85, in pre_sql_setup
     self.setup_query(with_col_aliases=with_col_aliases)
     ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/Users/gav/code/annotatebug/.venv/lib/python3.13/site-
 packages/django/db/models/sql/compiler.py", line 74, in setup_query
     self.select, self.klass_info, self.annotation_col_map =
 self.get_select(
 ~~~~~~~~~~~~~~~^
         with_col_aliases=with_col_aliases,
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     )
     ^
   File "/Users/gav/code/annotatebug/.venv/lib/python3.13/site-
 packages/django/db/models/sql/compiler.py", line 283, in get_select
     expression = self.query.annotations[expression]
                  ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
 KeyError: 'service'

 ----------------------------------------------------------------------
 Ran 1 test in 0.003s

 FAILED (errors=1)
 }}}
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36360>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/0107019681f2a212-c6e50072-6883-40a8-8bf1-34434b2f1b31-000000%40eu-central-1.amazonses.com.

Reply via email to