#35488: Unexpected traceback: A JSONField being in a UniqueConstraint is handled/documented poorly -------------------------------------+------------------------------------- Reporter: Hanne Moa | Owner: nobody Type: | Status: new Uncategorized | Component: | Version: 5.0 Uncategorized | Keywords: unique, Severity: Normal | UniqueConstraint, JSONField, json Triage Stage: | Has patch: 0 Unreviewed | Needs documentation: 0 | Needs tests: 0 Patch needs improvement: 0 | Easy pickings: 0 UI/UX: 0 | -------------------------------------+------------------------------------- Given a User based on AbstractUser and a model:
{{{ class DestinationConfig(model.Model): class Meta: constraints = [models.UniqueConstraint(fields=["user", "settings"], name="unique_destination_per_user")] user = models.ForeignKey(User, on_delete=models.CASCADE) settings = models.JSONField() }}} Admin like so: {{{ from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin class DestinationConfigInline(admin.TabularInline): model = DestinationConfig ordering = ["media", "label"] extra = 1 def get_formset(self, request, obj=None, **kwargs): self.parent_obj = obj return super(DestinationConfigInline, self).get_formset(request, obj, **kwargs) def get_queryset(self, request): qs = super(DestinationConfigInline, self).get_queryset(request) return qs.filter(user=self.parent_obj) class UserAdmin(BaseUserAdmin): inlines = [DestinationConfigInline] admin.site.register(User, UserAdmin) }}} Then attempting to save a change to a specific user leads to a traceback like this: {{{ Environment: Request Method: POST Request URL: https://ACENSORED.DOMAIN/admin/APP_auth/user/65/change/ Django Version: 5.0.6 Python Version: 3.10.12 Installed Applications: ['channels', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'corsheaders', 'social_django', 'rest_framework', 'rest_framework.authtoken', 'drf_spectacular', 'django_filters', 'phonenumber_field', 'argus.auth', 'argus.incident', 'argus.ws', 'argus.notificationprofile', 'argus.dev'] Installed Middleware: ['django.middleware.security.SecurityMiddleware', 'corsheaders.middleware.CorsMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', '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', 'social_django.middleware.SocialAuthExceptionMiddleware', 'django.contrib.auth.middleware.RemoteUserMiddleware'] Traceback (most recent call last): File "/usr/local/lib/python3.10/site- packages/django/core/handlers/exception.py", line 55, in inner response = get_response(request) File "/usr/local/lib/python3.10/site- packages/django/core/handlers/base.py", line 197, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/usr/local/lib/python3.10/site- packages/django/contrib/admin/options.py", line 688, in wrapper return self.admin_site.admin_view(view)(*args, **kwargs) File "/usr/local/lib/python3.10/site- packages/django/utils/decorators.py", line 134, in _wrapper_view response = view_func(request, *args, **kwargs) File "/usr/local/lib/python3.10/site- packages/django/views/decorators/cache.py", line 62, in _wrapper_view_func response = view_func(request, *args, **kwargs) File "/usr/local/lib/python3.10/site- packages/django/contrib/admin/sites.py", line 242, in inner return view(request, *args, **kwargs) File "/usr/local/lib/python3.10/site- packages/django/contrib/admin/options.py", line 1889, in change_view return self.changeform_view(request, object_id, form_url, extra_context) File "/usr/local/lib/python3.10/site- packages/django/utils/decorators.py", line 46, in _wrapper return bound_method(*args, **kwargs) File "/usr/local/lib/python3.10/site- packages/django/utils/decorators.py", line 134, in _wrapper_view response = view_func(request, *args, **kwargs) File "/usr/local/lib/python3.10/site- packages/django/contrib/admin/options.py", line 1747, in changeform_view return self._changeform_view(request, object_id, form_url, extra_context) File "/usr/local/lib/python3.10/site- packages/django/contrib/admin/options.py", line 1797, in _changeform_view if all_valid(formsets) and form_validated: File "/usr/local/lib/python3.10/site-packages/django/forms/formsets.py", line 579, in all_valid return all([formset.is_valid() for formset in formsets]) File "/usr/local/lib/python3.10/site-packages/django/forms/formsets.py", line 579, in <listcomp> return all([formset.is_valid() for formset in formsets]) File "/usr/local/lib/python3.10/site-packages/django/forms/formsets.py", line 384, in is_valid self.errors File "/usr/local/lib/python3.10/site-packages/django/forms/formsets.py", line 366, in errors self.full_clean() File "/usr/local/lib/python3.10/site-packages/django/forms/formsets.py", line 456, in full_clean self.clean() File "/usr/local/lib/python3.10/site-packages/django/forms/models.py", line 789, in clean self.validate_unique() File "/usr/local/lib/python3.10/site-packages/django/forms/models.py", line 831, in validate_unique if row_data in seen_data: Exception Type: TypeError at /admin/APP_auth/user/65/change/ Exception Value: unhashable type: 'dict' }}} * `row_data` was `('user', {'email_address': 'CENSORED@ANOTHERCENSORED.DOMAIN', 'html': True})` * `seen_data` was `set()`. Is it a design decision that JSONFields cannot be unique/in UniqueConstraints? * If yes, can it be documented, preferably with a note as to what to do if you *do* want a unique JSONField? * If no, can validate_unique be changed to work in this instance? This has also been tested on: * Django Version: 4.2.11 * Python Version: 3.10.14 -- Ticket URL: <https://code.djangoproject.com/ticket/35488> 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 django-updates+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-updates/0107018fc3bd4365-6224dfb0-6885-4d88-8271-29fad94e57ad-000000%40eu-central-1.amazonses.com.