#35483 closed Bug (fixed)
ModelMultipleChoiceField with CharField key throws ValueError if data contains NUL (0x00) characters
Reported by: | Jennifer Richards | Owned by: | lotvall |
---|---|---|---|
Component: | Forms | Version: | 4.2 |
Severity: | Normal | Keywords: | ModelMultipleChoiceField null nul |
Cc: | 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
In a form using a ModelMultipleChoiceField with a model that has a CharField primary key, I'm getting server errors when a request submits input with a nul (0x00) character. This is resulting from a ValueError raised by the postgresql backend. It happens whether the data is submitted as a query string or in the request body.
I've tried adding an explicit ProhibitNullCharactersValidator to the field, but this fails because the field's clean method runs validators after evaluating whether the input is a member of the queryset.
A fairly minimal example that exhibits this is:
class MyModel(models.Model): slug = models.CharField(primary_key=True) class MyForm(forms.Form): field = forms.ModelMultipleChoiceField(queryset=MyModel.objects.all()) def my_view(request): form = MyForm(data=request.GET) if form.is_valid(): return HttpResponse("yay") return HttpResponse("boo", status=400)
With that running, the following triggers the error:
$ curl 'http://localhost:8000/my-view?field=hi%00'
The output from the Django dev server is
ERROR: django.request:241: Internal Server Error: /my-view Traceback (most recent call last): File "/home/dev/.local/lib/python3.9/site-packages/django/core/handlers/exception.py", line 55, in inner response = get_response(request) File "/home/dev/.local/lib/python3.9/site-packages/django/core/handlers/base.py", line 197, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/workspace/ietf/doc/views_search.py", line 98, in my_view if form.is_valid(): File "/home/dev/.local/lib/python3.9/site-packages/django/forms/forms.py", line 201, in is_valid return self.is_bound and not self.errors File "/home/dev/.local/lib/python3.9/site-packages/django/forms/forms.py", line 196, in errors self.full_clean() File "/home/dev/.local/lib/python3.9/site-packages/django/forms/forms.py", line 433, in full_clean self._clean_fields() File "/home/dev/.local/lib/python3.9/site-packages/django/forms/forms.py", line 445, in _clean_fields value = field.clean(value) File "/home/dev/.local/lib/python3.9/site-packages/django/forms/models.py", line 1590, in clean qs = self._check_values(value) File "/home/dev/.local/lib/python3.9/site-packages/django/forms/models.py", line 1623, in _check_values pks = {str(getattr(o, key)) for o in qs} File "/home/dev/.local/lib/python3.9/site-packages/django/db/models/query.py", line 398, in __iter__ self._fetch_all() File "/home/dev/.local/lib/python3.9/site-packages/django/db/models/query.py", line 1881, in _fetch_all self._result_cache = list(self._iterable_class(self)) File "/home/dev/.local/lib/python3.9/site-packages/django/db/models/query.py", line 91, in __iter__ results = compiler.execute_sql( File "/home/dev/.local/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1562, in execute_sql cursor.execute(sql, params) File "/home/dev/.local/lib/python3.9/site-packages/django/db/backends/utils.py", line 102, in execute return super().execute(sql, params) File "/home/dev/.local/lib/python3.9/site-packages/django/db/backends/utils.py", line 67, in execute return self._execute_with_wrappers( File "/home/dev/.local/lib/python3.9/site-packages/django/db/backends/utils.py", line 80, in _execute_with_wrappers return executor(sql, params, many, context) File "/home/dev/.local/lib/python3.9/site-packages/django/db/backends/utils.py", line 89, in _execute return self.cursor.execute(sql, params) ValueError: A string literal cannot contain NUL (0x00) characters. [27/May/2024 08:02:37] "GET /my-view?field=hi%00 HTTP/1.0" 500 180131
As a workaround, replacing forms.ModelMultipleChoiceField with
class MyModelMultipleChoiceField(forms.ModelMultipleChoiceField): validate_no_nulls = validators.ProhibitNullCharactersValidator() def clean(self, value): for item in value: self.validate_no_nulls(item) return super().clean(value)
correctly handles the same input, with only
[27/May/2024 08:04:22] "GET /my-view?field=hi%00 HTTP/1.0" 400 3
in the server log.
I'm seeing this with Django 4.2.13 using the postgresql backend with psycopg2 2.99 against PostgreSQL 14.6.
Change History (7)
comment:1 by , 6 months ago
Triage Stage: | Unreviewed → Accepted |
---|
comment:2 by , 5 months ago
Has patch: | set |
---|---|
Owner: | changed from | to
Status: | new → assigned |
comment:3 by , 5 months ago
Patch needs improvement: | set |
---|
comment:4 by , 5 months ago
Patch needs improvement: | unset |
---|
comment:5 by , 5 months ago
Triage Stage: | Accepted → Ready for checkin |
---|
Replicated, thank you for the report!
My failing test if it's useful
tests/model_forms/tests.py
Relates to #28201.