Skip to main content

Prevalidate Django Forms in the browser

Project description

django-formset – Better UX for Django Forms

<django-formset> is a Webcomponent to wrap one or more Django Forms. This webcomponent is installed together with the Django app django-formset.

Build Status PyPI version Django versions Python versions Software license

Installation

Install django-formset using

pip install django-formset

change settings.py to

INSTALLED_APPS = [
    ...
    'formset',
    ...
]

Documentation

Reference documentation can be found on Read The Docs.

Usage

Say, we have a standard Django Form:

from django.forms import forms, fields

class SubscribeForm(forms.Form):
    last_name = fields.CharField(
        label="Last name",
        min_length=2,
        max_length=50,
    )

    # ... more fields

when rendering to HTML and using the Bootstrap 5 framework, we wrap that Form into the special Webcomponent <django-formset ...>:

{% load static formsetify %}
<html>
  <head>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <script type="module" src="{% static 'formset/js/django-formset.min.js' %}"></script>
  </head>
  <body>
    <!-- other stuff -->
    <django-formset endpoint="{{ request.path }}">
      {% render_form form "bootstrap" field_classes="mb-2" %}
      <button type="button" click="disable -> submit -> proceed !~ scrollToError" class="btn">Submit</button>
    </django-formset>
    <!-- other stuff -->
  </body>
</html>

in our urls.py we now wire everything together:

from django.urls import path
from formset.views import FormView

from .myforms import SubscribeForm


urlpatterns = [
    ...
    path('subscribe', FormView.as_view(
        form_class=SubscribeForm,
        template_name='my-subscribe-form.html',
        success_url='/success',
    )),
    ...
]

This renders SubscribeForm with a much better User Experience. We get immediate feedback if input entered into a field is not valid. Moreover, when this form is submitted but rejected by the server-side validation checker, errors are shown immediatly and without reloading the page. Only on success, a new page is loaded (or another alternative action is performed).

Motivation

Instead of using a <form>-tag and include all its fields, here we wrap the complete form inside the special Webcomponent <django-formset>. This allows us to communicate via Ajax with our Django view, using the named endpoint. This means, that we can wrap multiple <form>-elements into our Formset. It also means, that we now can place the Submit <button>-element outside of the <form>-element. By doing so, we can decouple the Form's business-logic from its technical constraint, of transferring a group of fields from and to the server.

When designing this library, the main goal was to keep the programming interface a near as possible to the way Django handles Forms, Models and Views.

Some Highlights

  • Before submitting, our Form is prevalidated by the browser, using the constraints we defined for each Django Field.

  • The Form's data is send by an Ajax request, preventing a full page reload. This gives a much better user experience.

  • Server side validation errors are sent back to the browser, and rendered near the offending Form Field.

  • Non-field validation errors are renderer together with the form.

  • CSRF-tokens are handled through a Cookie, hence there is no need to add that token to each form.

  • Forms can be rendered for different CSS frameworks using their specific style-guides for arranging HTML. Currently django-formset includes renderers for:

    It usually takes about 50 lines of code to create a renderer and most widgets can even be rendered using the default template as provided by Django.

  • It's JavaScript-framework agnostic. No external JavaScript dependencies are required. The client part is written in pure TypeScript and compiles to a single, portable JS-file.

  • Support for all standard widgets Django currently offers. This also includes radio buttons and multiple checkboxes with options.

  • File uploads are handled asynchronously. When the user opens the file dialog or drags a file into the form, this file then is uploaded immediately to a temporary folder on the server. On successful file upload, a unique signed handle is returned together with a thumbnail of that file. On form submission, this handle then is used to access that file and move it to its final destination. No extra endpoint is required for this feature.

  • Select boxes with too many entries, can be filtered by the server using a search query. No extra endpoint is required for this feature.

  • Radio buttons and multiple checkboxes with only a few fields can be rendered inlined rather than beneath each other.

  • The Submit buttons can be configured as a chain of actions. It for instance is possible to disable the button before submission. It also is possible to change the CSS depending on success or failure, add delays and specify the further proceedings. This for instance allows to specify the success page as a HTML link, rather than having it to hard-code inside the Django View.

  • A Formset can group multiple Forms into a collection. On submission, this collection then is sent to the server as a group a separate entities. After all Forms have been validated, the submitted data is provided as a nested Python dictionary.

  • Such a Form-Collection can be declared to have many Form entities of the same kind. This allows to create siblings of Forms, similar the Django's Admin Inline Forms. However, each of these siblings can contain other Form-Collections, which themselves can also be declared as siblings. This list of siblings can be extended or reduced using one "Add" and multiple "Remove" buttons.

  • By using the special attributes show-if="condition", hide-if="condition" or disable-if="condition" on input fields or fieldsets, one can hide or disable these marked fields. This condition can evaluate all field values of the current Formset by a Boolean expression.

[^1]: Tailwind is special here, since it doesn't include purpose-built form control classes out of the box. Instead django-formset offers an opinionated set of CSS classes suitable for Tailwind.

Running the Demo

Make sure you have a recent version of Python and npm.

To get a first impression of django-formset, run the demo site.

git clone https://github.com/jrief/django-formset.git
cd django-formset
python -m venv .venv
source .venv/bin/activate
pip install Django
pip install -r testapp/requirements.txt
pip install --no-deps -e .
npm install --also=dev
npm run tag-attributes
npm run tailwindcss
npm run build
testapp/manage.py migrate
testapp/manage.py runserver

Open http://localhost:8000/ in your browser. There is a link for each of the supported CSS frameworks. For each of them, there is a long list of forms for all kind of purposes.

Running the tests

Since there is a lot of interaction between the browser and the server, the client is tested using pytest together with Playwright in order to run end-to-end tests.

playwright install

Then run the testsuite

pytest testapp

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django-formset-0.8.2.tar.gz (232.2 kB view details)

Uploaded Source

Built Distribution

django_formset-0.8.2-py3-none-any.whl (276.4 kB view details)

Uploaded Python 3

File details

Details for the file django-formset-0.8.2.tar.gz.

File metadata

  • Download URL: django-formset-0.8.2.tar.gz
  • Upload date:
  • Size: 232.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.0 CPython/3.9.13

File hashes

Hashes for django-formset-0.8.2.tar.gz
Algorithm Hash digest
SHA256 4ebf9373217f787b121b941a5e658989f4cdacacc26f620caff22d8ab90dec7f
MD5 b06e1a3f22d7bc2d0df504bee8675d8f
BLAKE2b-256 482ddc333f056492feed033813155ac6c23f5bbd3a7bf1312ef9d47ee6f63198

See more details on using hashes here.

File details

Details for the file django_formset-0.8.2-py3-none-any.whl.

File metadata

File hashes

Hashes for django_formset-0.8.2-py3-none-any.whl
Algorithm Hash digest
SHA256 77efd497761f52730bcc9a86567bcb9662b60047c33fd7cb993937f312e75f31
MD5 fee158592d273a57e8c16df3c028fb4a
BLAKE2b-256 92c20c6a45bf113070b5e9bc76b5a03e56c9c943238507f4ffa3bb9d0f9ff514

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page