﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
36472	GeneratedField(primary_key=True) crashes on create(), and other issues	David Sanders	David Sanders	"Related to: #36466

Given the following model:

{{{
class Foo(Model):
    foo = IntegerField()
    bar = GeneratedField(
        expression=F(""foo"") + 5,
        output_field=IntegerField(),
        db_persist=True,
        primary_key=True,
    )
}}}

Django will crash on the following:

`create()`

{{{
>>> Foo.objects.create(foo=100)
Traceback (most recent call last):
  File ""<console>"", line 1, in <module>
  File ""/path/to/django/db/models/manager.py"", line 87, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/path/to/django/db/models/query.py"", line 665, in create
    obj.save(force_insert=True, using=self.db)
  File ""/path/to/django/db/models/base.py"", line 873, in save
    self.save_base(
  File ""/path/to/django/db/models/base.py"", line 965, in save_base
    updated = self._save_table(
              ^^^^^^^^^^^^^^^^^
  File ""/path/to/django/db/models/base.py"", line 1066, in _save_table
    if not self._is_pk_set(meta):
           ^^^^^^^^^^^^^^^^^^^^^
  File ""/path/to/django/db/models/base.py"", line 688, in _is_pk_set
    pk_val = self._get_pk_val(meta)
             ^^^^^^^^^^^^^^^^^^^^^^
  File ""/path/to/django/db/models/base.py"", line 677, in _get_pk_val
    return getattr(self, meta.pk.attname)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/path/to/django/db/models/query_utils.py"", line 221, in __get__
    val = self._check_parent_chain(instance)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/path/to/django/db/models/query_utils.py"", line 242, in _check_parent_chain
    return getattr(instance, link_field.attname)
                             ^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'attname'
}}}

if we patch `DeferredAttribute` to account for `None` with

{{{
--- a/django/db/models/query_utils.py
+++ b/django/db/models/query_utils.py
@@ -238,7 +238,7 @@ class DeferredAttribute:
         """"""
         opts = instance._meta
         link_field = opts.get_ancestor_link(self.field.model)
-        if self.field.primary_key and self.field != link_field:
+        if link_field and self.field.primary_key and self.field != link_field:
             return getattr(instance, link_field.attname)
         return None
}}}

then we get infinite recursion:

{{{
>>> Foo.objects.create(foo=100)
Traceback (most recent call last):
  File ""<console>"", line 1, in <module>
  File ""/path/to/django/db/models/manager.py"", line 87, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/path/to/django/db/models/query.py"", line 665, in create
    obj.save(force_insert=True, using=self.db)
  File ""/path/to/django/db/models/base.py"", line 873, in save
    self.save_base(
  File ""/path/to/django/db/models/base.py"", line 965, in save_base
    updated = self._save_table(
              ^^^^^^^^^^^^^^^^^
  File ""/path/to/django/db/models/base.py"", line 1066, in _save_table
    if not self._is_pk_set(meta):
           ^^^^^^^^^^^^^^^^^^^^^
  File ""/path/to/django/db/models/base.py"", line 688, in _is_pk_set
    pk_val = self._get_pk_val(meta)
             ^^^^^^^^^^^^^^^^^^^^^^
  File ""/path/to/django/db/models/base.py"", line 677, in _get_pk_val
    return getattr(self, meta.pk.attname)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/path/to/django/db/models/query_utils.py"", line 223, in __get__
    if not instance._is_pk_set():
           ^^^^^^^^^^^^^^^^^^^^^

    >>>> etc etc etc <<<

RecursionError: maximum recursion depth exceeded
}}}

Patching `DeferredAttribute` seems to address the infinite recursion and allow creation (but this will block the update of the generated field's value):

{{{
--- a/django/db/models/query_utils.py
+++ b/django/db/models/query_utils.py
@@ -219,7 +219,7 @@ class DeferredAttribute:
             # Let's see if the field is part of the parent chain. If so we
             # might be able to reuse the already loaded value. Refs #18343.
             val = self._check_parent_chain(instance)
-            if val is None:
+            if val is None and not self.field.generated:
                 if not instance._is_pk_set():
                     raise AttributeError(
                         f""Cannot retrieve deferred field {field_name!r} ""
}}}

Then there are issues around fetching, eg consider the following:

{{{
>>> foo = Foo.objects.get(...)
>>> foo.foo = <some other value>
>>> foo.save()
>>> foo.refresh_from_db() 
Traceback (most recent call last):
  File ""<console>"", line 1, in <module>
  File ""/path/to/django/db/models/base.py"", line 757, in refresh_from_db
    db_instance = db_instance_qs.get()
                  ^^^^^^^^^^^^^^^^^^^^
  File ""/path/to/django/db/models/query.py"", line 635, in get
    raise self.model.DoesNotExist(
generated_as_pk.models.Foo.DoesNotExist: Foo matching query does not exist.
}}}

These are just some basic tests, there may be other issues not yet noticed.

I don't think it's as bad as banning `primary_key=True` though.  Simon has a branch for #27222 which seems to fix the `refresh_from_db()` issue.  Perhaps the infinite recursion can be solved simply/elegantly as well?"	Bug	assigned	Database layer (models, ORM)	dev	Normal			Simon Charette	Accepted	1	0	0	1	0	0
