Skip to content

Commit 7f29a74

Browse files
committed
Handle list and string validation errors, not just the dictionary.
1 parent d55fc21 commit 7f29a74

File tree

5 files changed

+51
-14
lines changed

5 files changed

+51
-14
lines changed

django_unicorn/views/__init__.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import orjson
77
from bs4 import BeautifulSoup
88
from django.core.cache import caches
9+
from django.core.exceptions import NON_FIELD_ERRORS
910
from django.forms import ValidationError
1011
from django.http import HttpRequest, JsonResponse
1112
from django.http.response import HttpResponseNotModified
@@ -36,6 +37,9 @@
3637
logger.setLevel(logging.DEBUG)
3738

3839

40+
MIN_VALIDATION_ERROR_ARGS = 2
41+
42+
3943
def handle_error(view_func):
4044
"""
4145
Returns a JSON response with an error if necessary.
@@ -130,19 +134,28 @@ def _process_component_request(request: HttpRequest, component_request: Componen
130134
is_reset_called = is_reset_called | _is_reset_called
131135
validate_all_fields = validate_all_fields | _validate_all_fields
132136
except ValidationError as e:
133-
if hasattr(e, "error_list"):
134-
raise AssertionError("ValidationError must be instantiated with a dictionary") from e
135-
136-
for field, message in e.message_dict.items():
137-
if not e.args[1]:
138-
raise AssertionError("Error code must be specified") from e
137+
if len(e.args) < MIN_VALIDATION_ERROR_ARGS or not e.args[1]:
138+
raise AssertionError("Error code must be specified") from e
139139

140+
if hasattr(e, "error_list"):
140141
error_code = e.args[1]
141142

142-
if field in component.errors:
143-
component.errors[field].append({"code": error_code, "message": message})
144-
else:
145-
component.errors[field] = [{"code": error_code, "message": message}]
143+
for error in e.error_list:
144+
if NON_FIELD_ERRORS in component.errors:
145+
component.errors[NON_FIELD_ERRORS].append({"code": error_code, "message": error.message})
146+
else:
147+
component.errors[NON_FIELD_ERRORS] = [{"code": error_code, "message": error.message}]
148+
elif hasattr(e, "message_dict"):
149+
for field, message in e.message_dict.items():
150+
if not e.args[1]:
151+
raise AssertionError("Error code must be specified") from e
152+
153+
error_code = e.args[1]
154+
155+
if field in component.errors:
156+
component.errors[field].append({"code": error_code, "message": message})
157+
else:
158+
component.errors[field] = [{"code": error_code, "message": message}]
146159
else:
147160
raise UnicornViewError(f"Unknown action_type '{action.action_type}'")
148161

docs/source/changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 0.58.0-dev
4+
5+
- Handle a list of `ValidationError` or just a string instead of requiring a the `dict` version.
6+
37
## 0.57.1
48

59
- Fix: Correctly serialize forms that have a a field with a Select widget.

docs/source/validation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ There is a `unicorn_errors` template tag that shows all errors for the component
148148

149149
## ValidationError
150150

151-
If you do not want to create a form class or you want to specifically target a nested field you can raise a `ValidationError` inside of an action method. The `ValidationError` must be instantiated with a `dict` with the model name as the key and error message as the value. A `code` keyword argument must also be passed in. The typical error codes used are `required` or `invalid`.
151+
If you do not want to create a form class or you want to specifically target a nested field you can raise a `ValidationError` inside of an action method. The `ValidationError` can be instantiated with a `dict` with the model name as the key and error message as the value. A `code` keyword argument must also be passed in. The typical error codes used are `required` or `invalid`.
152152

153153
```python
154154
# book_validation_error.py

tests/views/fake_components.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,14 @@ def test_validation_error_no_code(self):
6262
def test_validation_error_string(self):
6363
raise ValidationError("Check is required", code="required")
6464

65+
def test_validation_error_string_no_code(self):
66+
raise ValidationError("Check is required")
67+
6568
def test_validation_error_list(self):
66-
raise ValidationError([ValidationError({"check": "Check is required"}, code="required")])
69+
raise ValidationError([ValidationError({"check": "Check is required"})], code="required")
70+
71+
def test_validation_error_list_no_code(self):
72+
raise ValidationError([ValidationError({"check": "Check is required"})])
6773

6874

6975
class FakeModelForm(forms.ModelForm):

tests/views/message/test_call_method.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,15 @@ def test_message_call_method_validation_error(client):
360360
def test_message_call_method_validation_error_list(client):
361361
body = _post_to_component(client, "test_validation_error_list")
362362

363+
assert body["errors"]
364+
assert body["errors"]["__all__"] == [{"code": "required", "message": "Check is required"}]
365+
366+
367+
def test_message_call_method_validation_error_list_no_code(client):
368+
body = _post_to_component(client, "test_validation_error_list_no_code")
369+
363370
assert body["error"]
364-
assert body["error"] == "ValidationError must be instantiated with a dictionary"
371+
assert body["error"] == "Error code must be specified"
365372

366373

367374
def test_message_call_method_validation_error_no_code(client):
@@ -374,5 +381,12 @@ def test_message_call_method_validation_error_no_code(client):
374381
def test_message_call_method_validation_error_string(client):
375382
body = _post_to_component(client, "test_validation_error_string")
376383

384+
assert body["errors"]
385+
assert body["errors"]["__all__"] == [{"code": "required", "message": "Check is required"}]
386+
387+
388+
def test_message_call_method_validation_error_string_no_code(client):
389+
body = _post_to_component(client, "test_validation_error_string_no_code")
390+
377391
assert body["error"]
378-
assert body["error"] == "ValidationError must be instantiated with a dictionary"
392+
assert body["error"] == "Error code must be specified"

0 commit comments

Comments
 (0)