Jinja2 macros¶
ModelForms¶
bs_form()¶
bs_form() macro allows to generate html representation of ModelForm:
{% extends 'base_min.htm' %}
{% from 'bs_form.htm' import bs_form with context %}
{% block main %}
{{ bs_form(form=form, action=tpl.url('my_url_name'), opts={
'class': 'form_css_class',
'title': page_context.get_view_title(),
'submit_text': 'My button'
}) }}
{% endblock main %}
Since the introduction of form renderers in version 0.8.0, bs_form() macro become a simple compatibility wrapper, while the actual HTML code of form is generated with the following render_form() call:
{{ render_form(request, 'standalone', form, {
'action': action,
'opts': opts,
'method': method,
}) }}
Note that the bs_form() macro generates html <form>
tag and wraps the whole form into Bootstrap card with the
heading / body. If you want to generate form body only (usual Django approach), call render_form() template context
function instead:
{{ render_form(request, 'body', form) }}
To read more about render_form() template context function and the built-in form / inline formsets renderers, see Forms.
Inline formsets¶
bs_inline_formsets()¶
bs_inline_formsets() is a macro that supports html rendering of one or zero Django ModelForm with one or multiple related inline formsets. It also supports two types of rendering layouts:
<div>
layout for real changeable submittable forms.<table>
layout primarily used to display read-only “forms” (see Forms).
Also it has support for inserting custom content between individual forms of formsets.
Example of form with inline formsets rendering:
{{
bs_inline_formsets(related_form=form, formsets=formsets, action=tpl.url('add_project', project_id=project.pk), opts={
'class': 'project',
'is_ajax': True,
'title': page_context.get_view_title(),
'submit_text': 'Add new project'
}) }}
- In this case form with formsets will be submitted and processed via AJAX POST request / response due to
is_ajax
=True
argument. - bs_inline_formsets() also supports
{% call() bs_inline_formsets() %}
syntax for complex formatting of formsets which is unused in this simplified example.
Changing bootstrap grid layout¶
One may use the custom layout_classes value as the key of the following macros opts
dict argument:
- bs_form(form, action, opts, method=’post’)
- bs_inline_formsets(related_form, formsets, action, opts)
to alter default Bootstrap inline form grid width, for example:
{{
bs_inline_formsets(related_form=form, formsets=formsets, action=tpl.url('project_candidate_add', project_id=project.pk), opts={
'class': 'project',
'is_ajax': True,
'title': page_context.get_view_title(),
'submit_text': 'Add candidate',
'layout_classes': {
'': {
'label': 'col-md-4', 'field': 'col-md-6',
}
}
}) }}
Default value of Bootstrap inline grid layout classes is defined in djk_ui
app conf.py module
LAYOUT_CLASSES
variable.
Inserting custom content¶
Calling bs_inline_formsets() macro with kwargs
argument allows to insert custom blocks of html at the following
points of form with related formsets rendering:
Begin of formset. formset_begin
will hold the instance of formset, allowing to distinguish one formset from another
one:
{{ caller({'formset_begin': formset, 'html': html}) }}
Begin of formset form:
{{ caller({'form_begin': form, 'html': html}) }}
End of formset form:
{{ caller({'form_end': form, 'html': html}) }}
End of formset. formset_end
will hold the instance of formset, allowing to distinguish one formset from another one
(see the example below):
{{ caller({'formset_end': formset, 'html': html}) }}
Adding custom buttons, for example many AJAX POST buttons each with different data-url
or data-route
html5
attributes. That allows to submit the same AJAX form to different Django views:
{{ caller({'buttons': True}) }}
The following example inserts custom submit button, which is supported when the 'is_ajax': True
parameter is
specified:
{% extends 'base_min.htm' %}
{% from 'bs_inline_formsets.htm' import bs_inline_formsets with context %}
{% call(kwargs)
bs_inline_formsets(related_form=form, formsets=formsets, action=tpl.url('project_update', project_id=project.pk), opts={
'class': 'project',
'is_ajax': True,
'title': page_context.get_view_title(),
'submit_text': 'Update project'
}) %}
{% if 'buttons' in kwargs %}
<button type="submit" data-url="{{ tpl.url('project_postpone', project_id=project.pk) }}" class="btn btn-primary">
Postpone project
</button>
{% endif %}
{% endcall %}
Resulting html will have two form submit buttons:
- one is automatically generated with submit
tpl.url('project_update', ...)
- another is manually inserted with submit
tpl.url('project_postpone', ...)
Different views may be called from the same Django AJAX form with inline formsets, depending on which html button is pressed.
The following example will insert total project read-only “form” (see Forms) extra cost columns after the end of
rendering related projectmember_set
inline formset:
{% extends 'base_min.htm' %}
{% from 'bs_inline_formsets.htm' import bs_inline_formsets with context %}
{% call(kwargs)
bs_inline_formsets(related_form=form, formsets=formsets, action='', opts={
'class': 'project',
'title': form.instance,
'submit_text': 'Review project'
}) %}
{% if 'formset_end' in kwargs and kwargs.formset_end.prefix == 'projectmember_set' %}
{% set total_cost = form.project.get_total_cost() %}
{% if total_cost > 0 %}
<div class="default-padding">
<table class="table">
<colgroup>
<col class="{{ kwargs.html.layout_classes.label }}">
<col class="{{ kwargs.html.layout_classes.field }}">
</colgroup>
<tr>
<th class="success">Total cost</th>
<td class="info">{{ total_cost }}</td>
</tr>
</table>
</div>
{% endif %}
{% endif %}
{% endcall %}
Wrapping each form of formset with div with custom attributes (to process these in custom Javascript):
{% call(kwargs)
bs_inline_formsets(related_form=form, formsets=formsets, action=tpl.url('project_update', project_id=project.pk), opts={
'class': 'project',
'is_ajax': True,
'title': form.instance,
'submit_text': 'Update project'
}) %}
{% if 'form_begin' in kwargs %}
<div id="revision-{{ kwargs.form_begin.instance.pk }}">
{% endif %}
{% if 'form_end' in kwargs %}
</div>
{% endif %}
{% endcall %}
Since version 0.8.0, the more flexible approach could be to override Renderers templates instead.