Opened 3 weeks ago

Closed 3 weeks ago

Last modified 6 days ago

#37139 closed Bug (fixed)

InlineAdmin breaks when using db_default on primary key field without a python default

Reported by: Mariusz Felisiak Owned by: Mariusz Felisiak
Component: contrib.admin Version: 5.2
Severity: Normal Keywords:
Cc: Lily, Mariusz Felisiak Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Using db_default with the new UUID4 and UUID7 functions crashes in the admin. Model defined with models.UUIDField as primary key with db_default
e.g.

uuid = models.UUIDField(primary_key=True, db_default=UUID7(), editable=False, verbose_name="UUID")

crashes when opening "add" page in the admin:

ValidationError at /admin/someapp/somemodel/add/
['“<django.db.models.expressions.DatabaseDefault object at 0x7e81f7b76e40>” is not a valid UUID.']
Traceback (most recent call last):
  File "/app/.venv/lib/python3.14/site-packages/django/db/models/fields/__init__.py", line 2856, in to_python
    return uuid.UUID(**{input_form: value})
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.14/uuid.py", line 216, in __init__
    hex = hex.replace('urn:', '').replace('uuid:', '')
          ^^^^^^^^^^^

During handling of the above exception ('DatabaseDefault' object has no attribute 'replace'), another exception occurred:
  File "/app/.venv/lib/python3.14/site-packages/django/core/handlers/exception.py", line 56, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/core/handlers/base.py", line 199, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/contrib/admin/options.py", line 770, in wrapper
    return self.admin_site.admin_view(view)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/utils/decorators.py", line 191, in _view_wrapper
    result = _process_exception(request, e)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/utils/decorators.py", line 189, in _view_wrapper
    response = view_func(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/views/decorators/cache.py", line 79, in _view_wrapper
    response = view_func(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/contrib/admin/sites.py", line 248, in inner
    return view(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/contrib/admin/options.py", line 2225, in add_view
    return self.changeform_view(request, None, form_url, extra_context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/utils/decorators.py", line 47, in _wrapper
    return bound_method(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/utils/decorators.py", line 191, in _view_wrapper
    result = _process_exception(request, e)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/utils/decorators.py", line 189, in _view_wrapper
    response = view_func(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/contrib/admin/options.py", line 2039, in changeform_view
    return self._changeform_view(request, object_id, form_url, extra_context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/contrib/admin/options.py", line 2145, in _changeform_view
    formsets, inline_instances = self._create_formsets(
                                 
  File "/app/.venv/lib/python3.14/site-packages/django/contrib/admin/options.py", line 2645, in _create_formsets
    formset = FormSet(**formset_params)
              ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/forms/models.py", line 1132, in __init__
    qs = queryset.filter(**{self.fk.name: self.instance})
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/db/models/query.py", line 1653, in filter
    return self._filter_or_exclude(False, args, kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/db/models/query.py", line 1671, in _filter_or_exclude
    clone._filter_or_exclude_inplace(negate, args, kwargs)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/db/models/query.py", line 1681, in _filter_or_exclude_inplace
    self._query.add_q(Q(*args, **kwargs))
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/db/models/sql/query.py", line 1680, in add_q
    clause, _ = self._add_q(q_object, can_reuse)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/db/models/sql/query.py", line 1712, in _add_q
    child_clause, needed_inner = self.build_filter(
                                 
  File "/app/.venv/lib/python3.14/site-packages/django/db/models/sql/query.py", line 1612, in build_filter
    condition = self.build_lookup(lookups, col, value)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/db/models/sql/query.py", line 1439, in build_lookup
    lookup = lookup_class(lhs, rhs)
             ^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/db/models/lookups.py", line 35, in __init__
    self.rhs = self.get_prep_lookup()
               ^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/db/models/fields/related_lookups.py", line 113, in get_prep_lookup
    self.rhs = target_field.get_prep_value(self.rhs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/db/models/fields/__init__.py", line 2840, in get_prep_value
    return self.to_python(value)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/django/db/models/fields/__init__.py", line 2858, in to_python
    raise exceptions.ValidationError(
    ^

As far as I'm aware this a bug in a new feature, or maybe I did something wrong 🤔

Change History (13)

comment:1 by Mariusz Felisiak, 3 weeks ago

Cc: Mariusz Felisiak added

comment:2 by Jacob Walls, 3 weeks ago

Severity: Release blockerNormal
Version: 6.15.2

Reproduced on 5.2 with a project that had an equivalent custom function. I think admin inlines require having a python default on the field.

Offhand, I don't think we can easily add support for this. A docs tweak might be good?

comment:3 by Jacob Walls, 3 weeks ago

Summary: Using db_default with the new UUID4 and UUID7 functions crashes in the admin.InlineAdmin breaks when using db_default on primary key field without a python default

in reply to:  2 comment:4 by Mariusz Felisiak, 3 weeks ago

To be clear, inlines are involved, but uuid with db_default is in the main object not in the inlines.

Replying to Jacob Walls:

Reproduced on 5.2 with a project that had an equivalent custom function. I think admin inlines require having a python default on the field.

🤔 I'm not sure I bu that argument. This way we could copy code of all new features to the older version of Django and say it doesn't work there so it is not a bug in the function 😉😉, sure it's not a direct bug in UUID7() but a long standing issue in handling DatabaseDefault in inlines, however people will try to use new functions and hit the same. I need to check carefully why we pass an empty parent instance to the inline form etc., admin is not my area of expertise.

comment:5 by Jacob Walls, 3 weeks ago

I get that db_default is one of the main use cases for these new functions. But I presume we already had this problem with RandomUUID in contrib.postgres. The "new feature" here IMO is just the cross-database abstraction to replace RandomUUID. (I'm motivated to get the fix in before 6.1 beta, so the difference doesn't amount to much.)


I can get the add page to load when applying this tweak:

  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index d53da600d7..21b9c80687 100644
    a b class Model(AltersData, metaclass=ModelBase):  
    709709
    710710    def _is_pk_set(self, meta=None):
    711711        pk_val = self._get_pk_val(meta)
     712        is_set = lambda val: val is None or isinstance(val, DatabaseDefault)
    712713        return not (
    713             pk_val is None
    714             or (isinstance(pk_val, tuple) and any(f is None for f in pk_val))
     714            is_set(pk_val)
     715            or (isinstance(pk_val, tuple) and any(is_set(f) for f in pk_val))
    715716        )
    716717
    717718    def get_deferred_fields(self):

Obviously we'll need to check the other uses.

comment:6 by Jacob Walls, 3 weeks ago

Severity: NormalRelease blocker

Actually, I think this would qualify as a release blocker for 6.0 (via valid user code or RandomUUID) under the "crashing bug" criterion.

comment:7 by Jacob Walls, 3 weeks ago

Triage Stage: UnreviewedAccepted

Thanks for the report and for taking a look!

comment:8 by Mariusz Felisiak, 3 weeks ago

Has patch: set

comment:9 by Jacob Walls, 3 weeks ago

Severity: Release blockerNormal
Triage Stage: AcceptedReady for checkin

This change has the potential to start raising sanity-check style errors ("cannot do X operation on unsaved objects") in some cases, so it's not really a good candidate for a backport to a stable version.

comment:10 by GitHub <noreply@…>, 3 weeks ago

Resolution: fixed
Status: assignedclosed

In a2348c85:

Fixed #37139 -- Fixed inlines crash on parent models with db_default on primary key.

comment:11 by Mariusz Felisiak <felisiak.mariusz@…>, 3 weeks ago

In c3a3ea7:

[6.1.x] Fixed #37139 -- Fixed inlines crash on parent models with db_default on primary key.

Backport of a2348c85fc6c20087935c74cd99340dd4ef2dcdc from main

comment:12 by Jacob Walls <jacobtylerwalls@…>, 6 days ago

In f3e66a3:

Refs #37139 -- Renamed helper inside _is_pk_set().

comment:13 by Jacob Walls <jacobtylerwalls@…>, 6 days ago

In 1c6d477:

[6.1.x] Refs #37139 -- Renamed helper inside _is_pk_set().

Backport of f3e66a32a3bdbf3c362b70e06c16393639dd36f3 from main.

Note: See TracTickets for help on using tickets.
Back to Top