Django provides multiple ways to render forms, from one-liner convenience methods to manual field-by-field control.

Key Concepts

  • as_p/as_table/as_ul/as_div: Convenience rendering methods.
  • BoundField: Combines field definition with submitted data and errors.
  • visible_fields/hidden_fields: Separate user-facing from hidden inputs.
  • label_tag/id_for_label: Accessible label rendering.

Real World Context

A pixel-perfect design mockup needs field-by-field rendering with custom CSS classes and error styling, not convenience methods.

Deep Dive

Form Rendering Options

Django provides multiple ways to render forms, from automatic to completely manual control.

Quick Rendering Methods

html
<!-- As paragraph elements -->
{{ form.as_p }}

<!-- As table rows (needs <table> wrapper) -->
<table>{{ form.as_table }}</table>

<!-- As unordered list -->
<ul>{{ form.as_ul }}</ul>

<!-- As div elements (Django 4.0+) -->
{{ form.as_div }}

Rendering Individual Fields

html
<form method="post">
    {% csrf_token %}
    
    <div class="form-group">
        {{ form.title.label_tag }}
        {{ form.title }}
        {{ form.title.help_text }}
        {% if form.title.errors %}
            <div class="errors">{{ form.title.errors }}</div>
        {% endif %}
    </div>
    
    <div class="form-group">
        {{ form.body.label_tag }}
        {{ form.body }}
    </div>
    
    <button type="submit">Submit</button>
</form>

Field Attributes

html
<!-- Access various field properties -->
{{ form.title.name }}           <!-- 'title' -->
{{ form.title.id_for_label }}   <!-- 'id_title' -->
{{ form.title.value }}          <!-- Current value -->
{{ form.title.html_name }}      <!-- HTML name attribute -->
{{ form.title.help_text }}      <!-- Help text -->
{{ form.title.label }}          <!-- Label text -->
{{ form.title.errors }}         <!-- Field errors -->
{{ form.title.is_hidden }}      <!-- Is hidden field -->

Looping Over Fields

html
<form method="post">
    {% csrf_token %}
    
    {% for field in form %}
        <div class="form-group {% if field.errors %}has-error{% endif %}">
            {{ field.label_tag }}
            {{ field }}
            
            {% if field.help_text %}
                <small class="help-text">{{ field.help_text }}</small>
            {% endif %}
            
            {% for error in field.errors %}
                <span class="error">{{ error }}</span>
            {% endfor %}
        </div>
    {% endfor %}
    
    <button type="submit">Submit</button>
</form>

Hidden Fields

html
<form method="post">
    {% csrf_token %}
    
    <!-- Render hidden fields first -->
    {% for hidden in form.hidden_fields %}
        {{ hidden }}
    {% endfor %}
    
    <!-- Render visible fields -->
    {% for field in form.visible_fields %}
        <div class="form-group">
            {{ field.label_tag }}
            {{ field }}
        </div>
    {% endfor %}
</form>

Custom Widget Attributes

python
# forms.py
class ContactForm(forms.Form):
    name = forms.CharField(
        widget=forms.TextInput(attrs={
            'class': 'form-control',
            'placeholder': 'Your name',
            'autocomplete': 'name',
        })
    )
    
    email = forms.EmailField(
        widget=forms.EmailInput(attrs={
            'class': 'form-control',
            'placeholder': 'your@email.com',
        })
    )
    
    message = forms.CharField(
        widget=forms.Textarea(attrs={
            'class': 'form-control',
            'rows': 5,
        })
    )

Non-Field Errors

html
<!-- Display form-level errors -->
{% if form.non_field_errors %}
    <div class="alert alert-danger">
        {% for error in form.non_field_errors %}
            <p>{{ error }}</p>
        {% endfor %}
    </div>
{% endif %}

<!-- All errors summary -->
{% if form.errors %}
    <div class="alert alert-danger">
        <strong>Please correct the errors below:</strong>
        {{ form.errors }}
    </div>
{% endif %}

Form Templates (Django 4.0+)

python
# settings.py
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'

TEMPLATES = [{
    # ...
    'DIRS': [BASE_DIR / 'templates'],
}]
html
<!-- templates/django/forms/div.html -->
{% for field in form %}
<div class="form-group mb-3">
    {% if field.label %}
        <label for="{{ field.id_for_label }}" class="form-label">
            {{ field.label }}
            {% if field.field.required %}<span class="required">*</span>{% endif %}
        </label>
    {% endif %}
    
    {{ field }}
    
    {% if field.help_text %}
        <div class="form-text">{{ field.help_text }}</div>
    {% endif %}
    
    {% for error in field.errors %}
        <div class="invalid-feedback d-block">{{ error }}</div>
    {% endfor %}
</div>
{% endfor %}

Common Pitfalls

  1. as_table without table wrapper -- Renders tr elements only.
  2. Missing hidden fields -- Causes 403 Forbidden (CSRF).
  3. Not showing non_field_errors -- Cross-field errors invisible.

Best Practices

  1. as_div for prototyping, manual for production -- Most designs need custom HTML.
  2. Render hidden fields first -- Before visible fields.
  3. Show required indicators -- Check field.field.required.

Summary

  • as_div, as_p, as_table, as_ul for quick rendering.
  • Iterate visible_fields for custom designs.
  • Always render hidden_fields separately.
  • Display non_field_errors at form top.
  • Use BoundField properties for accessible HTML.

Code Examples

python
# Quick rendering in templates:
# {{ form.as_div }}   -- wraps fields in <div> elements (Django 4.0+)
# {{ form.as_p }}     -- wraps fields in <p> elements
# {{ form.as_table }} -- renders as table rows

# Manual field rendering for full control:
# {{ form.title.label_tag }}
# {{ form.title }}
# {{ form.title.errors }}
# {{ form.title.help_text }}
✓ Completed