Error Handling and Presentation
Validation errors in this codebase are managed through specialized collection classes that handle both the storage of error data and its presentation across different formats (HTML, text, and JSON). The system centers around ErrorList and ErrorDict in django/forms/utils.py, which utilize a mixin-based rendering architecture.
The Error Rendering Pipeline
The RenderableErrorMixin provides a consistent interface for converting error collections into user-facing formats. It leverages Django's template engine to decouple the error logic from the presentation layer.
Key methods provided by the mixin include:
as_ul(): Renders errors using thetemplate_name_ultemplate.as_text(): Renders errors using thetemplate_name_texttemplate.as_json(): Serializes error data to a JSON string viaget_json_data().
Both ErrorList and ErrorDict implement this mixin and define specific template paths for their default output.
Error Collections
ErrorList
ErrorList is a list-like collection (inheriting from UserList and list) used to store errors for a single field or non-field errors.
- Dual Inheritance: It inherits from both
UserListandlistfor backward compatibility. To prevent duplicate entries during serialization (like pickling), it overrides__reduce_ex__to nullify the list iterator. - String Conversion: When iterating over an
ErrorList, it automatically extracts the message string fromValidationErrorinstances via__getitem__. - CSS Classes: The constructor accepts an
error_classargument, which defaults to"errorlist". If a custom class is provided, it is appended (e.g.,"errorlist my-custom-class").
ErrorDict
ErrorDict is a dictionary subclass where keys are field names and values are ErrorList instances. It is primarily used by BaseForm to store all validation errors discovered during the full_clean() process.
# django/forms/forms.py:328
def full_clean(self):
self._errors = ErrorDict(renderer=self.renderer)
# ... validation logic populates self._errors
Customizing Error Presentation
There are two primary ways to customize how errors appear to users: subclassing the error collections or overriding the default templates.
Subclassing ErrorList
You can create a custom ErrorList to change the HTML structure entirely. For example, to wrap errors in <div> tags instead of the default <ul>, you can define a custom class and pass it to the form's constructor.
from django import forms
from django.utils.safestring import mark_safe
class DivErrorList(forms.utils.ErrorList):
def __str__(self):
return self.as_divs()
def as_divs(self):
if not self:
return ""
return mark_safe(
'<div class="error">%s</div>'
% "".join("<p>%s</p>" % e for e in self)
)
# Usage in a form instance
form = MyForm(data, error_class=DivErrorList)
The BaseForm in django/forms/forms.py stores this class in self.error_class and uses it whenever new error collections are instantiated (e.g., in add_error() or non_field_errors()).
Template Overriding
The default rendering behavior is controlled by templates defined in django/forms/utils.py. You can override these in your project's template directory to change the global appearance of errors:
- List Templates:
django/forms/errors/list/default.htmldjango/forms/errors/list/ul.htmldjango/forms/errors/list/text.txt
- Dictionary Templates:
django/forms/errors/dict/default.htmldjango/forms/errors/dict/ul.htmldjango/forms/errors/dict/text.txt
Programmatic and API Usage
For AJAX requests or API integrations, the error classes provide structured data accessors.
JSON Serialization
The as_json() method returns a JSON string. By default, it does not escape HTML, but you can enable it by passing escape_html=True.
# Example of JSON output structure from ErrorList.get_json_data()
# [{"message": "This field is required.", "code": "required"}]
errors_json = form.errors.as_json()
Raw Data Access
If you need to inspect the underlying ValidationError objects (including their parameters and codes), use as_data():
ErrorList.as_data(): Returns a list ofValidationErrorinstances.ErrorDict.as_data(): Returns a dictionary mapping field names to lists ofValidationErrorinstances.
This is useful when you need to perform logic based on specific error codes rather than just displaying the message.
# django/forms/forms.py:318
def has_error(self, field, code=None):
return field in self.errors and (
code is None
or any(error.code == code for error in self.errors.as_data()[field])
)
Integration with Forms
The BaseForm class manages the lifecycle of these error collections. When form.errors is accessed, it triggers full_clean(), which initializes an ErrorDict.
Specific field errors are associated with their HTML id via the field_id attribute in ErrorList, allowing for accessible error reporting (e.g., linking an error message to its input field). Non-field errors are specifically tagged with the "nonfield" CSS class via the non_field_errors() method.