14.9 Rendering a form: renderers and templates
A form, a bound field, a list of errors and a widget each draw themselves by handing a template’s name and a context to a renderer, an object of django/ that finds the template and renders it. get_default_renderer makes the one the setting FORM_RENDERER names, and the built-in templates under django/ nest, the form’s calling each field’s.
A form’s HTML must be changeable without its Python, for a project or a form class. So each object that draws has a template’s name and a get_context that makes the template’s dictionary, and the renderer, which alone knows where templates are found and in which engine, turns the two into a string.
Drawing oneself: RenderableMixin
RenderableMixin, called with no arguments, renders the object’s template_name with the dictionary of its get_context through the object’s own renderer, and marks the result safe; any of the three can be given instead. It is also the class’s __str__ and __html__, so {{ form }} in a template draws the form, and the safe string is not escaped again: a submitted value or a message is escaped once, by the innermost template that prints it (Autoescaping and safe strings).
BaseForm and BaseFormSet take it through RenderableFormMixin; BoundField through RenderableFieldMixin, whose __str__ draws the widget, with no label or errors, and whose RenderableFieldMixin is the whole field (BoundField: a field in a form); ErrorDict and ErrorList through RenderableErrorMixin. A widget has a method of its own, Widget.render, which hands its context to the renderer it is given, or to the default one (Widgets: Widget and its subclasses).
RenderableFormMixin adds as_div, as_p, as_table and as_ul, each rendering a name the class holds, such as BaseForm. str(form) renders BaseForm, a property that reads the renderer’s form template. In a run, a renderer whose form template was "django/ made str(form) draw paragraphs while as_div() still drew divisions.
The renderer a form uses
BaseForm settles a form’s renderer once: the argument; else the class’s BaseForm, made an instance if it is a class; else what get_default_renderer returns. Its bound fields, error lists and labels use it, and the bound field hands it to the widget. A widget or an ErrorList given no renderer uses the default.
get_default_renderer imports the class FORM_RENDERER names, by default "django, and makes an instance. The function is cached, so everything that falls back on it shares one renderer and one engine; under DjangoTemplates that engine’s loader is the cached one, which keeps each template it has compiled.
BaseRenderer: a name and a context in, a string out
BaseRenderer holds the names of the default templates, BaseRenderer, BaseRenderer and BaseRenderer, and BaseRenderer, the form’s last resort for its class of bound field. A subclass supplies BaseRenderer. BaseRenderer renders the template with the context and an optional request and strips white space from both ends; the blank lines inside a piece stay.
No caller in the forms library passes a request, so the backend renders with a plain context and no context processor runs for a form’s templates under Django’s renderers (The context: Context, RequestContext and context processors).
EngineMixin: an engine of the renderer’s own
The renderers DjangoTemplates and Jinja2 share EngineMixin, whose EngineMixin is made on first use and kept on the renderer: an instance of the renderer’s template backend, named "djangoforms", with empty "OPTIONS", "APP_DIRS" true, and in "DIRS" the directory beside renderers.py named after the backend’s app_dirname: django/, or django/, templates of the same names in Jinja2’s syntax. It stands apart from TEMPLATES, with the backend’s defaults: for Django’s, the cached loader, "debug" from settings.DEBUG, and no context processors (Template engines: TEMPLATES and the backends).
Both backends search "DIRS" before the applications’ directories. So a built-in template is found in Django’s directory first, and an application cannot replace it (Finding a template: Engine and the loaders). In a run, an application’s own "django/ was passed over; a template of a new name in it was found.
TemplatesSetting: the project’s engines
TemplatesSetting keeps no engine: it finds a template with django, which asks each backend of TEMPLATES in turn. The project’s directories, applications and options now decide, and its own "django/ can replace Django’s. The built-in templates are found only where the project’s engines look, as with "django.forms" in INSTALLED_APPS and an engine whose "APP_DIRS" is true, or with their directory in "DIRS". In a run with neither, as_p() raised TemplateDoesNotExist for "django/.
The form’s context: BaseForm.get_context
BaseForm gives the form’s template four keys: "form"; "fields", a pair for each visible field of its bound field and its errors; "hidden_fields", the bound fields with hidden widgets; and "errors", a copy of BaseForm with each hidden field’s errors added, in a run as "(Hidden field token) This field is required.". The templates draw a hidden field with no label or errors of its own. A bound field’s context is the bound field, under "field".
The built-in templates
An object’s template goes through the renderer; an {% include %} inside it is found by the same engine alone.
str(form) rendered for the berth form in a run, as they nest."django/ prints the form’s errors, then a <div> for each visible field holding the field’s group, and the hidden fields inside the last, if there is one. "django/, "django/ and "django/ follow the same plan, with no <table> or <ul> around their rows, and draw each field’s parts themselves.
The field’s template is its own, if it was given one, or the renderer’s. It draws the label, or a <fieldset> and <legend> for a widget whose use_fieldset is set; the help text; the field’s ErrorList, whose template is rendered even when it is empty and includes "django/, which writes nothing then (Validation: full_clean, cleaned_data and the errors); and the widget. The label is BaseForm, rendered by BoundField through the form. A widget renders its class’s template_name; most inputs’ include "django/. A formset’s default template prints its management form, then each form’s as_div() (Formsets: BaseFormSet and the management form).
Attributes are written by included templates: "django/ for a widget, "django/ for a label, in the dictionary’s order, leaving out False and writing True as a bare name. flatatt does the same in Python, for a media asset’s attributes among others, but sorts the names, puts the bare ones last and leaves out None, which a widget given {"placeholder": None} drew, in a run, as placeholder="None".