﻿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
36351	CompositePrimaryKey fails in InlineAdmins with a JSONDecodeError	Dominik Bruhn		"Using a CompositePrimaryKey on a model used in an InlineAdmin fails on save with an `JSONDecodeError`. Maybe this is related to using a UUIDField for the parts of the composites?

Minimal Reproducible Example:

Models:

{{{

class User(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=255)

class Role(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=255)

class UserRole(models.Model):
    pk = models.CompositePrimaryKey(""user"", ""role"")
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    role = models.ForeignKey(Role, on_delete=models.CASCADE)
}}}

Admin
{{{
from django.contrib import admin
from .models import User, Role, UserRole

class UserRoleInline(admin.TabularInline):
    model = UserRole

@admin.register(User)
class UserAdmin(admin.ModelAdmin):
    inlines = [UserRoleInline]

# Not part of the bug, only required for creating an initial Role
@admin.register(Role)
class RoleAdmin(admin.ModelAdmin):
    pass
}}}

How to reproduce:
- Create a Role using the `RoleAdmin`
- Create a User using the `UserAdmin` and associcate it with a role (works!)
- Edit the User again using the `UserAdmin` and click on save (no need to change anything)

Expected:
- Model is saved

Instead:
- Exception is thrown:
{{{
Environment:


Request Method: POST
Request URL: http://127.0.0.1:8181/admin/polls/user/9a232a03-dfa8-4e9f-afcd-bd380aa0a396/change/

Django Version: 5.2
Python Version: 3.13.3
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'polls']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']



Traceback (most recent call last):
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/core/handlers/exception.py"", line 55, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/core/handlers/base.py"", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/contrib/admin/options.py"", line 719, in wrapper
    return self.admin_site.admin_view(view)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/utils/decorators.py"", line 192, in _view_wrapper
    result = _process_exception(request, e)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/utils/decorators.py"", line 190, in _view_wrapper
    response = view_func(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/views/decorators/cache.py"", line 80, in _view_wrapper
    response = view_func(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/contrib/admin/sites.py"", line 246, in inner
    return view(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/contrib/admin/options.py"", line 1987, in change_view
    return self.changeform_view(request, object_id, form_url, extra_context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/utils/decorators.py"", line 48, in _wrapper
    return bound_method(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/utils/decorators.py"", line 192, in _view_wrapper
    result = _process_exception(request, e)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/utils/decorators.py"", line 190, in _view_wrapper
    response = view_func(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/contrib/admin/options.py"", line 1843, in changeform_view
    return self._changeform_view(request, object_id, form_url, extra_context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/contrib/admin/options.py"", line 1893, in _changeform_view
    if all_valid(formsets) and form_validated:
       ^^^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/forms/formsets.py"", line 584, in all_valid
    return all([formset.is_valid() for formset in formsets])
                ^^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/forms/formsets.py"", line 384, in is_valid
    self.errors
    ^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/forms/formsets.py"", line 366, in errors
    self.full_clean()
    ^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/forms/formsets.py"", line 423, in full_clean
    for i, form in enumerate(self.forms):
                             ^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/utils/functional.py"", line 47, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/forms/formsets.py"", line 206, in forms
    self._construct_form(i, **self.get_form_kwargs(i))
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/forms/models.py"", line 1126, in _construct_form
    form = super()._construct_form(i, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/forms/models.py"", line 728, in _construct_form
    pk = to_python(pk)
         ^^^^^^^^^^^^^
  File ""/Users/dominik/Library/Caches/pypoetry/virtualenvs/django-composite-key-bug-VYP1oUgo-py3.13/lib/python3.13/site-packages/django/db/models/fields/composite.py"", line 151, in to_python
    vals = json.loads(value)
           ^^^^^^^^^^^^^^^^^
  File ""/opt/homebrew/Cellar/python@3.13/3.13.3/Frameworks/Python.framework/Versions/3.13/lib/python3.13/json/__init__.py"", line 346, in loads
    return _default_decoder.decode(s)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/opt/homebrew/Cellar/python@3.13/3.13.3/Frameworks/Python.framework/Versions/3.13/lib/python3.13/json/decoder.py"", line 345, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/opt/homebrew/Cellar/python@3.13/3.13.3/Frameworks/Python.framework/Versions/3.13/lib/python3.13/json/decoder.py"", line 363, in raw_decode
    raise JSONDecodeError(""Expecting value"", s, err.value) from None
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Exception Type: JSONDecodeError at /admin/polls/user/9a232a03-dfa8-4e9f-afcd-bd380aa0a396/change/
Exception Value: Expecting value: line 1 column 1 (char 0)
}}}

I created a Github Repo reproducing this issue:
https://github.com/theomega/django_composite_key_bug

Versions:
- django 5.2
- Python 3.13.3

Looking at the variables, the `value` variable in the `to_python` has the value of a tuple(?) 

{{{
(""(UUID('9a232a03-dfa8-4e9f-afcd-bd380aa0a396'), ""
 ""UUID('03f99517-aff2-47b6-9589-e820641229df'))"")
}}}

I don't think it really is a tuple, because the line before does an isinstance check for `str`. But, anyway this is not valid JSON, so this is why the decoder fails."	Uncategorized	closed	Uncategorized	5.2	Normal	duplicate		Dominik Bruhn	Unreviewed	0	0	0	0	0	0
