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.
Installation
Install django-formset using
pip install django-formset
change settings.py
to
INSTALLED_APPS = [
...
'formset',
...
]
Documentation and Demo
Reference documentation can be found on Read The Docs.
A demo showing all combinations of fields.
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:
- Bootstrap 5,
- Bulma,
- Foundation 6,
- Tailwind [^1]
- UIKit
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"
ordisable-if="condition"
on input fields or fieldsets, one can hide or disable these marked fields. Thiscondition
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
Built Distribution
File details
Details for the file django-formset-0.9.tar.gz
.
File metadata
- Download URL: django-formset-0.9.tar.gz
- Upload date:
- Size: 234.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.9.13
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | f9e33cdd6253a27dc507db56ac90afb02315ac146d242bbae84dc1a30ba7ea75 |
|
MD5 | 8155882b55e1094f845ed6e75f9703e0 |
|
BLAKE2b-256 | 41a210c14ba849b6643f2ab440c32e245d01a66c3bdb520383f514aea0053b06 |
File details
Details for the file django_formset-0.9-py3-none-any.whl
.
File metadata
- Download URL: django_formset-0.9-py3-none-any.whl
- Upload date:
- Size: 281.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.9.13
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 5ec4aba2f0229119ce908dd4ce2c253c440ca94aded3280487ce5d275ca78ba7 |
|
MD5 | 53728a5095256a2e511103c2b3742457 |
|
BLAKE2b-256 | 6f74c5f179593ea92d0dfc14cf1df432c986bee66a25f9ba9277c4a25c05ecaa |