Re: [Django] #35488: BaseModelFormSet.validate_unique() raises TypeError unhashable type: 'dict' for JSONFields in a UniqueConstraint

6 views
Skip to first unread message

Django

unread,
May 29, 2024, 9:26:28 AM5/29/24
to django-...@googlegroups.com
#35488: BaseModelFormSet.validate_unique() raises TypeError unhashable type: 'dict'
for JSONFields in a UniqueConstraint
-------------------------------------+-------------------------------------
Reporter: Hanne Moa | Owner: nobody
Type: Bug | Status: new
Component: Forms | Version: 5.0
Severity: Normal | Resolution:
Keywords: JSONField, unique, | Triage Stage: Accepted
formset, json, hashable |
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Simon Charette):

* keywords: unique, UniqueConstraint, JSONField, json => JSONField,
unique, formset, json, hashable

Comment:

The problem happens to manifests itself for `JSONField` with
`UniqueConstraint` but it happens for any field with a non-hashable value
that has a unique constraint defined on it.

e.g.

{{{#!python
class FooBar(models.Model):
settings = models.JSONField(unique=True)
}}}

Would exhibit the same problem and the same could be said of `HStoreField`
or other custom fields dealing with `dict`, `set`, and other non-hashable
data types.

Using `django.utils.make_hashable` in `BaseModelFormSet.validate_unique`
seems like a potential low-lift solution but even this function can raise
a `TypeError` if dealing with non-hashable value so maybe we're better off
silencing these `TypeError` and let the model level unique constraint
validation kick in.

I think the latter would be a better approach because uniqueness on such
fields cannot be determined at the Python level. For example, `JSONField`
relies on the `jsonb` type on Postgres so the ordering of keys is not
preserved which means that `{"foo": "bar", "bar": "foo"}` would be
considered equal to `{"bar": "foo", "foo": "bar"}` but not on MySQL which
`json` type is a basically `longtext` with JSON validation and preserves
insertion order.
--
Ticket URL: <https://code.djangoproject.com/ticket/35488#comment:2>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
May 29, 2024, 10:30:10 AM5/29/24
to django-...@googlegroups.com
#35488: BaseModelFormSet.validate_unique() raises TypeError unhashable type: 'dict'
for JSONFields in a UniqueConstraint
-------------------------------------+-------------------------------------
Reporter: Hanne Moa | Owner: nobody
Type: Bug | Status: new
Component: Forms | Version: 5.0
Severity: Normal | Resolution:
Keywords: JSONField, unique, | Triage Stage: Accepted
formset, json, hashable |
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Simon Charette):

I think this has little to do with #23964 (which focuses on
`UniqueConstraint`) and is more of an analogous to #26819 (which was for
another field using non-hashable types) but for `JSONField`.

{{{#!diff
diff --git a/django/forms/models.py b/django/forms/models.py
index 4cda4e534e..42feeac5c2 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -23,6 +23,7 @@
SelectMultiple,
)
from django.utils.choices import BaseChoiceIterator
+from django.utils.hashable import make_hashable
from django.utils.text import capfirst, get_text_list
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
@@ -835,7 +836,7 @@ def validate_unique(self):
d._get_pk_val()
if hasattr(d, "_get_pk_val")
# Prevent "unhashable type: list" errors later
on.
- else tuple(d) if isinstance(d, list) else d
+ else make_hashable(d)
)
for d in row_data
)
}}}

The problem IMO is that 06a11ef6ecf324db0a1530b8cca727883698f442 focused
on one type on unhashable value instead of the generic problem.
--
Ticket URL: <https://code.djangoproject.com/ticket/35488#comment:3>
Reply all
Reply to author
Forward
0 new messages