Skip to content

Commit

Permalink
Make sure the form fields are properly sanitized
Browse files Browse the repository at this point in the history
  • Loading branch information
Savva Surenkov authored and Savva Surenkov committed Mar 24, 2024
1 parent 22f9c85 commit 56e036d
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 10 deletions.
26 changes: 17 additions & 9 deletions django_pydantic_field/v2/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,7 @@ def to_python(self, value: ty.Any) -> ty.Any:
return None

try:
if not isinstance(value, (str, bytes)):
# The form data may contain python objects for some cases (e.g. using django-constance).
value = self.adapter.validate_python(value)
elif not isinstance(value, JSONString):
# Otherwise, try to parse incoming JSON according to the schema.
value = self.adapter.validate_json(value)
value = self._try_coerce(value)
except pydantic.ValidationError as exc:
error_params = {"value": value, "title": exc.title, "detail": exc.json(), "errors": exc.errors()}
raise ValidationError(self.error_messages["schema_error"], code="invalid", params=error_params) from exc
Expand All @@ -76,10 +71,23 @@ def prepare_value(self, value):
if isinstance(value, InvalidJSONInput):
return value

value = self.adapter.validate_python(value)
value = self._try_coerce(value)
return self.adapter.dump_json(value).decode()

def has_changed(self, initial: ty.Any | None, data: ty.Any | None) -> bool:
if super(JSONField, self).has_changed(initial, data):
try:
initial = self._try_coerce(initial)
data = self._try_coerce(data)
return self.adapter.dump_python(initial) != self.adapter.dump_python(data)
except pydantic.ValidationError:
return True
return self.adapter.dump_json(initial) != self.adapter.dump_json(data)

def _try_coerce(self, value):
if not isinstance(value, (str, bytes)):
# The form data may contain python objects for some cases (e.g. using django-constance).
value = self.adapter.validate_python(value)
elif not isinstance(value, JSONString):
# Otherwise, try to parse incoming JSON according to the schema.
value = self.adapter.validate_json(value)

return value
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ dependencies = [
]

[project.optional-dependencies]
openapi = ["uritemplate"]
openapi = ["uritemplate", "inflection"]
coreapi = ["coreapi"]
dev = [
"build",
Expand Down
34 changes: 34 additions & 0 deletions tests/v2/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from datetime import date

import django
import pydantic
import pytest
from django.core.exceptions import ValidationError
from django.forms import Form, modelform_factory
Expand Down Expand Up @@ -45,6 +46,39 @@ def test_prepare_value():
assert expected == field.prepare_value({"stub_str": "abc", "stub_list": ["1970-01-01"]})


@pytest.mark.parametrize(
"value, expected",
[
([], "[]"),
([42], "[42]"),
("[42]", "[42]"),
],
)
def test_root_value_passes(value, expected):
RootModel = pydantic.RootModel[ty.List[int]]
field = forms.SchemaField(RootModel)
assert field.prepare_value(value) == expected


@pytest.mark.parametrize(
"value, initial, expected",
[
("[]", "[]", False),
([], [], False),
([], [42], True),
("[]", [], False),
("[42]", [], True),
([42], "[42]", False),
("[42]", "[42]", False),
("[42]", "[41]", True),
],
)
def test_root_value_has_changed(value, initial, expected):
RootModel = pydantic.RootModel[ty.List[int]]
field = forms.SchemaField(RootModel)
assert field.has_changed(initial, value) is expected


def test_empty_required_raises():
field = forms.SchemaField(InnerSchema)
with pytest.raises(ValidationError) as e:
Expand Down

0 comments on commit 56e036d

Please sign in to comment.