#36522: Assigning expressions to model fields does not work if the field
participates in a composite primary key
-------------------------------------+-------------------------------------
     Reporter:  Jacob Walls          |                     Type:  Bug
       Status:  new                  |                Component:  Database
                                     |  layer (models, ORM)
      Version:  5.2                  |                 Severity:  Release
                                     |  blocker
     Keywords:                       |             Triage Stage:
  compositeprimarykey, F             |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
 For this model in the test suite:
 {{{#!py
 class Comment(models.Model):
     pk = models.CompositePrimaryKey("tenant", "id")
     tenant = models.ForeignKey(
         Tenant,
         on_delete=models.CASCADE,
         related_name="comments",
     )
     id = models.SmallIntegerField(unique=True, db_column="comment_id")
     ...
 }}}

 This fails:
 {{{#!diff
 diff --git a/tests/composite_pk/test_update.py
 b/tests/composite_pk/test_update.py
 index 697383b007..e76210e75e 100644
 --- a/tests/composite_pk/test_update.py
 +++ b/tests/composite_pk/test_update.py
 @@ -191,3 +191,7 @@ class CompositePKUpdateTests(TestCase):
          )
          with self.assertRaisesMessage(FieldError, msg):
              Comment.objects.update(text=F("pk"))
 +
 +    def test_update_fields_expression(self):
 +        self.comment_1.id = F("id") + 100
 +        self.comment_1.save()
 }}}
 {{{#!py
 ======================================================================
 ERROR: test_update_fields_expression
 (composite_pk.test_update.CompositePKUpdateTests.test_update_fields_expression)
 ----------------------------------------------------------------------
 Traceback (most recent call last):
   File "/Users/jwalls/django/django/db/models/fields/__init__.py", line
 2130, in get_prep_value
     return int(value)
 TypeError: int() argument must be a string, a bytes-like object or a real
 number, not 'CombinedExpression'

 The above exception was the direct cause of the following exception:

 Traceback (most recent call last):
   File "/Users/jwalls/django/tests/composite_pk/test_update.py", line 197,
 in test_update_fields_expression
     self.comment_1.save()
     ~~~~~~~~~~~~~~~~~~~^^
   File "/Users/jwalls/django/django/db/models/base.py", line 874, in save
     self.save_base(
     ~~~~~~~~~~~~~~^
         using=using,
         ^^^^^^^^^^^^
     ...<2 lines>...
         update_fields=update_fields,
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     )
     ^
   File "/Users/jwalls/django/django/db/models/base.py", line 966, in
 save_base
     updated = self._save_table(
         raw,
     ...<4 lines>...
         update_fields,
     )
   File "/Users/jwalls/django/django/db/models/base.py", line 1097, in
 _save_table
     updated = self._do_update(
         base_qs, using, pk_val, values, update_fields, forced_update
     )
   File "/Users/jwalls/django/django/db/models/base.py", line 1165, in
 _do_update
     return filtered._update(values) > 0
            ~~~~~~~~~~~~~~~~^^^^^^^^
   File "/Users/jwalls/django/django/db/models/query.py", line 1319, in
 _update
     return query.get_compiler(self.db).execute_sql(ROW_COUNT)
            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
   File "/Users/jwalls/django/django/db/models/sql/compiler.py", line 2103,
 in execute_sql
     row_count = super().execute_sql(result_type)
   File "/Users/jwalls/django/django/db/models/sql/compiler.py", line 1611,
 in execute_sql
     sql, params = self.as_sql()
                   ~~~~~~~~~~~^^
   File "/Users/jwalls/django/django/db/models/sql/compiler.py", line 2089,
 in as_sql
     where, params = self.compile(self.query.where)
                     ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
   File "/Users/jwalls/django/django/db/models/sql/compiler.py", line 578,
 in compile
     sql, params = node.as_sql(self, self.connection)
                   ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
   File "/Users/jwalls/django/django/db/models/sql/where.py", line 151, in
 as_sql
     sql, params = compiler.compile(child)
                   ~~~~~~~~~~~~~~~~^^^^^^^
   File "/Users/jwalls/django/django/db/models/sql/compiler.py", line 578,
 in compile
     sql, params = node.as_sql(self, self.connection)
                   ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
   File "/Users/jwalls/django/django/db/models/fields/tuple_lookups.py",
 line 130, in as_sql
     return super().as_sql(compiler, connection)
            ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
   File "/Users/jwalls/django/django/db/models/lookups.py", line 407, in
 as_sql
     return super().as_sql(compiler, connection)
            ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
   File "/Users/jwalls/django/django/db/models/lookups.py", line 239, in
 as_sql
     rhs_sql, rhs_params = self.process_rhs(compiler, connection)
                           ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
   File "/Users/jwalls/django/django/db/models/fields/tuple_lookups.py",
 line 109, in process_rhs
     return compiler.compile(Tuple(*args))
            ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
   File "/Users/jwalls/django/django/db/models/sql/compiler.py", line 576,
 in compile
     sql, params = vendor_impl(self, self.connection)
                   ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
   File "/Users/jwalls/django/django/db/models/fields/tuple_lookups.py",
 line 46, in as_sqlite
     return self.as_sql(compiler, connection)
            ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
   File "/Users/jwalls/django/django/db/models/expressions.py", line 1107,
 in as_sql
     arg_sql, arg_params = compiler.compile(arg)
                           ~~~~~~~~~~~~~~~~^^^^^
   File "/Users/jwalls/django/django/db/models/sql/compiler.py", line 576,
 in compile
     sql, params = vendor_impl(self, self.connection)
                   ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
   File "/Users/jwalls/django/django/db/models/expressions.py", line 29, in
 as_sqlite
     sql, params = self.as_sql(compiler, connection, **extra_context)
                   ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/Users/jwalls/django/django/db/models/expressions.py", line 1175,
 in as_sql
     val = output_field.get_db_prep_value(val, connection=connection)
   File "/Users/jwalls/django/django/db/models/fields/__init__.py", line
 2137, in get_db_prep_value
     value = super().get_db_prep_value(value, connection, prepared)
   File "/Users/jwalls/django/django/db/models/fields/__init__.py", line
 1006, in get_db_prep_value
     value = self.get_prep_value(value)
   File "/Users/jwalls/django/django/db/models/fields/__init__.py", line
 2132, in get_prep_value
     raise e.__class__(
         "Field '%s' expected a number but got %r." % (self.name, value),
     ) from e
 TypeError: Field 'id' expected a number but got <CombinedExpression:
 Col(composite_pk_comment, composite_pk.Comment.id) + Value(100)>.
 }}}
 ----
 The `+ 100` is not necessary to reproduce, just there for realism. Error
 without is the same: "expected a number but got Col(..."

 If support is difficult, a `FieldError` is perhaps better, see
 `test_update_value_not_composite()`.
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36522>
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/010701983c81b1a1-a3380aa6-8341-4eb5-8c43-92431b73d7cd-000000%40eu-central-1.amazonses.com.

Reply via email to