Home Features Docs Blog Security Examples Quick Start

Forms

Integrate Django Forms with real-time validation and seamless submission.

Quick Start

1. Create a Django Form

forms.py
1# forms.py
2fromdjangoimport forms
3
4classContactForm(forms.Form):
5    name = forms.CharField(max_length=100)
6    email = forms.EmailField()
7    message = forms.CharField(widget=forms.Textarea)

2. Create a LiveView with FormMixin

views.py
 1# views.py
 2fromdjustimport LiveView
 3fromdjust.formsimport FormMixin
 4from.formsimport ContactForm
 5
 6classContactView(FormMixin, LiveView):
 7    template_name = 'contact.html'
 8    form_class = ContactForm
 9
10    defform_valid(self, form):
11        send_email(to='support@example.com', body=form.cleaned_data['message'])
12        self.success_message = 'Message sent!'
13
14    defform_invalid(self, form):
15        pass  # Errors shown automatically

3. Create the Template

templates/contact.html
 1<div dj-liveview>
 2    {% if success_message %}
 3        <div class="alert alert-success">{{ success_message }}</div>
 4    {% else %}
 5        <form dj-submit="submit">
 6            {% csrf_token %}
 7
 8            <div class="mb-3">
 9                <label>Name</label>
10                <input name="name" dj-change="name" value="{{ form.name.value|default:'' }}">
11                {% if field_errors.name %}
12                    <div class="error">{{ field_errors.name }}</div>
13                {% endif %}
14            </div>
15
16            <!-- Repeat for other fields -->
17
18            <button type="submit">Send</button>
19        </form>
20    {% endif %}
21</div>

Real-Time Validation

On Blur (dj-change)

Validate when field loses focus:

<input name="email" dj-change="email" value="{{ form.email.value }}">

On Keystroke (dj-input with debounce)

fromdjustimport debounce

classSignupView(FormMixin, LiveView):
    @debounce(wait=0.3)
    defusername(self, value: str = "", **kwargs):
        self.form_data['username'] = value
        self.validate_field('username', value)

        # Custom async validation
        if User.objects.filter(username=value).exists():
            self.field_errors['username'] = 'Username already taken'

Custom Validation

defvalidate_field(self, field_name, value, **kwargs):
    super().validate_field(field_name, value, **kwargs)

    if field_name == 'email':
        if not value.endswith('@company.com'):
            self.field_errors['email'] = 'Must use company email'

ModelForms

classArticleForm(forms.ModelForm):
    classMeta:
        model = Article
        fields = ['title', 'content']

classArticleCreateView(FormMixin, LiveView):
    form_class = ArticleForm

    defform_valid(self, form):
        article = form.save(commit=False)
        article.author = self.request.user
        article.save()
        self.success = True

Multi-Step Forms

classWizardView(LiveView):
    defmount(self, request, **kwargs):
        self.step = 1
        self.data = {}

    defnext_step(self, **kwargs):
        if self.validate_current_step():
            self.step += 1

    defprev_step(self):
        if self.step > 1:
            self.step -= 1

    defsubmit(self, **kwargs):
        if self.step == 3 and self.validate_current_step():
            self.create_order()
            self.success = True

File Uploads

classUploadView(LiveView):
    defhandle_upload(self, file=None, **kwargs):
        if file:
            if file.size > 10 * 1024 * 1024:  # 10MB
                self.error = 'File too large'
                return

            fromdjango.core.files.storageimport default_storage
            path = default_storage.save(f'uploads/{file.name}', file)
            self.uploaded_file = path

Form Styling

Bootstrap 5

<div class="mb-3">
  <label class="form-label">Email</label>
  <input type="email" name="email"
         class="form-control {% if field_errors.email %}is-invalid{% endif %}"
         dj-change="email" value="{{ form.email.value|default:'' }}">
  {% if field_errors.email %}
    <div class="invalid-feedback">{{ field_errors.email }}</div>
  {% endif %}
</div>

Tailwind

<div class="mb-4">
  <label class="block text-sm font-medium text-gray-700">Email</label>
  <input type="email" name="email"
         class="mt-1 block w-full rounded-md border-gray-300
                {% if field_errors.email %}border-red-300{% endif %}"
         dj-change="email" value="{{ form.email.value|default:'' }}">
  {% if field_errors.email %}
    <p class="mt-2 text-sm text-red-600">{{ field_errors.email }}</p>
  {% endif %}
</div>

Best Practices

  • Always include
  • Use @debounce for real-time validation
  • Disable submit button during processing
  • Clear form after successful submission
  • Handle errors gracefully

Next Steps