Skip to content

Commit 8c9796f

Browse files
fix: Update README.md
1 parent 02b5734 commit 8c9796f

File tree

3 files changed

+101
-288
lines changed

3 files changed

+101
-288
lines changed

README.md

Lines changed: 67 additions & 276 deletions
Original file line numberDiff line numberDiff line change
@@ -1,311 +1,102 @@
11
# django-formset
2-
## Better User Experience for Django Forms
32

4-
This library handles single forms and collections of forms with a way better user experience than
5-
the internal Django implementation for
6-
[formsets](https://docs.djangoproject.com/en/stable/topics/forms/formsets/) offers.
3+
`django-formset` delivers modern form handling for Django projects, combining the framework’s familiar server-side patterns with polished client-side experiences. It renders individual forms, formsets, and nested collections, wrapping them in a web component that keeps user interactions fast, accessible, and consistent across devices.
74

5+
## Why It Matters
86

9-
### News:
7+
- Present forms with layouts tailored to popular CSS frameworks without rewriting templates.
8+
- Validate inputs immediately in the browser using the same rules declared on the Python side.
9+
- Offload heavy interactions—file uploads, large select lists, nested collections—to purpose-built widgets.
10+
- Keep Django admin users and public-facing visitors on the same UX foundation.
11+
- Ship a single TypeScript-powered bundle, no external JavaScript dependencies required.
1012

11-
Starting with version 2, developers can use `ModelForm`s and `FormCollection`s in the django-admin.
12-
In version 2 it also is possible to store the content of multiple form fields or even an entire form
13-
collection inside a `JSONField`. Make sure to read the
14-
[Changelog](https://github.com/jrief/django-formset/blob/releases/2.0/CHANGELOG.md) for version 2
15-
before proceeding.
13+
## Installation
1614

17-
[![Interactive Documentation](https://img.shields.io/badge/Interactive-Documentation-532BEA)](https://django-formset.fly.dev/)
18-
[![Build Status](https://github.com/jrief/django-formset/actions/workflows/tests.yml/badge.svg)](https://github.com/jrief/django-formset/actions)
19-
[![PyPI version](https://img.shields.io/pypi/v/django-formset.svg)](https://pypi.python.org/pypi/django-formset)
20-
[![Django versions](https://img.shields.io/pypi/djversions/django-formset)](https://pypi.python.org/pypi/django-formset)
21-
[![Python versions](https://img.shields.io/pypi/pyversions/django-formset.svg)](https://pypi.python.org/pypi/django-formset)
22-
[![Software license](https://img.shields.io/pypi/l/django-formset.svg)](https://github.com/jrief/django-formset/blob/master/LICENSE)
23-
24-
**Before upgrading to version 2.0, please read the [Changelog](CHANGELOG.md).**
15+
```bash
16+
pip install django-formset
17+
```
2518

26-
Let's explain it using a short example. Say, we have a Django form with three fields:
19+
Activate the renderer and the app:
2720

2821
```python
29-
from django.forms import fields, forms
22+
# settings.py
23+
INSTALLED_APPS = [
24+
# ...
25+
"django_formset",
26+
]
3027

31-
class AddressForm(forms.Form):
32-
recipient = fields.CharField(label="Recipient")
33-
postal_code = fields.CharField(label="Postal Code")
34-
city = fields.CharField(label="City")
28+
FORM_RENDERER = "formset.renderers.FormsetRenderer"
3529
```
3630

37-
After creating a
38-
[Django FormView](https://docs.djangoproject.com/en/stable/ref/class-based-views/generic-editing/#django.views.generic.edit.FormView)
39-
we can render the above form using a slightly modified template:
31+
Collect static assets before deployment:
4032

41-
```html
42-
{% load formsetify %}
43-
{% render_form form "bootstrap" %}
33+
```bash
34+
python manage.py collectstatic
4435
```
4536

46-
This will render our form using the layout and CSS classes as proposed by
47-
[Bootstrap's style guide](https://getbootstrap.com/docs/5.1/forms/overview/):
48-
49-
![Address Form](readmeimg/bootstrap-address.png)
37+
## First Form
5038

51-
Or if rendered with alternative CSS classes:
52-
53-
```html
54-
{% load formsetify %}
55-
{% render_form form "bootstrap" field_css_classes="row mb-3" label_css_classes="col-sm-3" control_css_classes="col-sm-9" %}
39+
```python
40+
# forms.py
41+
from django import forms
42+
from formset.widgets import UploadedFileInput
43+
44+
class ContactForm(forms.Form):
45+
name = forms.CharField(label="Full name")
46+
email = forms.EmailField()
47+
resume = forms.FileField(widget=UploadedFileInput(), required=False)
48+
message = forms.CharField(widget=forms.Textarea)
5649
```
5750

58-
![Address Form (horizontal)](readmeimg/bootstrap-address-horizontal.png)
59-
60-
61-
Or if rendered with the Tailwind renderer:
62-
63-
```html
64-
{% load formsetify %}
65-
{% render_form form "tailwind" %}
51+
```python
52+
# views.py
53+
from django.views.generic import FormView
54+
from formset.views import FormSetMixin
55+
from .forms import ContactForm
56+
57+
class ContactView(FormSetMixin, FormView):
58+
template_name = "contact/form.html"
59+
form_class = ContactForm
60+
success_url = "/contact/thanks/"
6661
```
6762

68-
![Address Form (Tailwind CSS)](readmeimg/tailwind-address.png)
69-
70-
**django-formset** provides form renderers for all major CSS frameworks, such as
71-
[Bootstrap 5](https://getbootstrap.com/docs/5.1/forms/overview/),
72-
[Bulma](https://bulma.io/documentation/form/general/),
73-
[Foundation 6](https://get.foundation/sites/docs/forms.html),
74-
[Tailwind](https://tailwindcss.com/) and [UIkit](https://getuikit.com/).
75-
76-
77-
### Multiple Input Widgets
78-
79-
Furthermore, it can render all widgets provided by Django. This includes [multiple checkboxes](https://docs.djangoproject.com/en/stable/ref/forms/widgets/#checkboxselectmultiple)
80-
and radio selects, even with multiple option groups:
81-
82-
![Multiple Inputs](readmeimg/bootstrap-multiple-input.png)
83-
84-
85-
### File Uploading Widget
86-
87-
Uploading files is performed asynchronously, separating the payload upload from its form submission.
88-
It provides a drag-and-drop widget plus a file select button. This allows to preview uploaded files
89-
before form submission. It also makes the submission much faster, because the file is already in a
90-
temporary location on the server.
91-
92-
| Empty file upload | Pending file upload |
93-
|-------------------------------------------|-------------------------------------|
94-
| ![](readmeimg/bootstrap-upload-empty.png) | ![](readmeimg/bootstrap-upload.png) |
95-
96-
97-
### Alternatives for `<select>` and `<select multiple>` Widgets
98-
99-
The default HTML `<select>` widget can be replaced by a counterpart with autocompletion. No extra
100-
endpoint is required, because that's handled by the Django view already controlling the form.
101-
102-
The default HTML `<select multiple="multiple">` widget can be replaced by two different widgets, one
103-
which keeps the selected options inlined, and one which keeps them inside a "select-from" and a
104-
"selected option" field.
105-
106-
| Multi Select with autocomplete | Multi Select with source and target |
107-
|---------------------------------------|-------------------------------------------|
108-
| ![](readmeimg/tailwind-selectize.png) | ![](readmeimg/tailwind-dual-selector.png) |
109-
110-
Similar widgets can be found in the Django admin to make many-to-many relations editable. In
111-
**django-formset**, the right widget (with source and target) offers some additional features:
112-
113-
* It can handle relations where the source contains too many entries to be loaded once. Instead,
114-
this widget queries the database when searching for an option. It uses the same autocomplete
115-
endpoint.
116-
* The right part of the widget can be filtered as well.
117-
* The widget has a redo/undo functionality in case the user mistakenly selected wrong option(s).
118-
* Optionally, selected options in the right part of the widget can be sorted. This order then is
119-
reflected in an
120-
[extra field](https://docs.djangoproject.com/en/stable/topics/db/models/#intermediary-manytomany)
121-
on the many-to-many relationship.
122-
123-
124-
## Button actions
125-
126-
In **django-formset**, the button used for submission can hold a *chain of actions*. This for
127-
instance allows to disable the button, and/or add a spinning wheel while submitting data. It also is
128-
possible to specify the success page as an HTML link, rather than having it to hard-code inside the
129-
Django view. There is a complete set of predefined actions to select from, when designing the submit
130-
button.
131-
132-
![Button Actions (Bootstrap)](readmeimg/bootstrap-actions.gif)
133-
134-
135-
## Immediate Form Validation
136-
137-
Each field is validated as soon as it loses focus. This gives immediate feedback and signalizes if
138-
some user input will not be accepted, when submitting the form. The browser side validation
139-
constraints are excatly the same, as those defined for each Django field in Python.
140-
141-
Not every value or combination of thereof can be validated by the browser, but instead may be
142-
rejected by the backend application. For instance, the `clean()`- and/or `clean_FIELDNAME()`-methods
143-
may complain about values using some kind of internal logic.
144-
145-
Those serverside errors are sent back to the client and shown nearby the rejected fields without
146-
having to re-render the complete page. On success, a given page is loaded (or another alternative
147-
action is performed).
148-
149-
150-
## Grouping Forms
151-
152-
As the name "formset" suggests, **django-formset** allows to manage more than one form. It therefore
153-
is possible to create collections of forms and even nest those collections into each other.
154-
Collections can be declared to have siblings, allowing them to be instantiated multiple times. This
155-
is similar to Django's Stacked- and Tabular-Inlines, but allows an infinite number of nesting
156-
levels. Moreover, such collections with siblings can optionally be sorted.
157-
158-
[![Form Collections](readmeimg/bootstrap-contact.png)](https://youtu.be/dxyzzGOeNY4)
159-
160-
[watch as video](https://youtu.be/dxyzzGOeNY4)
161-
162-
A form collection is also useful to create an editor for models wich have a one-to-one relation.
163-
The Django admin for instance requires to use a Stacked- or Tabular-Inline, which however is
164-
designed to handle one-to-many relations. With collections these two interconnected models can be
165-
handled with seemingly the same form (although in the background those are separated entinties).
166-
167-
168-
## Conditional hiding/disabling
169-
170-
Since each formset holds its state (the current value of their fields), that information can be used
171-
to conditionally hide or disable other fields or even a complete fieldset.
172-
173-
By adding the special attributes `df-show="condition"`, `df-hide="condition"` or
174-
`df-disable="condition"` on an input fields or on a fieldsets, one can hide or disable these marked
175-
fields. This `condition` can be any expression evaluating the current field values of the formset.
176-
177-
## Alternative Widgets
178-
179-
**django-formset** provides alternative widgets for many fields provided by Django. For instance:
180-
181-
### Widgets for Django's `DateField`
182-
183-
Modern browsers provide a built-in date picker, which now can be used instead of the default
184-
`<input type="text">` widget. In addition to that, **django-formset** provides custom web components
185-
which adopt themselves to the chosen CSS framework. This allows to render the date picker in the
186-
same style as the rest of the form.
187-
188-
## Editing Richtext
189-
190-
**django-formset** integrates an extendable richtext editor, based on [TipTap](https://tiptap.dev/docs).
191-
192-
![Richtext Editor](readmeimg/richtext-editor.png)
193-
194-
Compared to most stand-alone solutions, this editor allows to integrate custom dialog forms and
195-
formsets into the editor itself.
196-
197-
198-
## Mapping of Form Fields to a `JSONField`
199-
200-
For unstructured data, it is possible to map multiple form fields and even collections into a
201-
[`JSONField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#jsonfield) in the model.
202-
203-
## Admin Integration
204-
205-
Forms and collections suitable for **django-formset** views can be used in the Django admin as well.
206-
This allows developers to adopt the same form design and user experience as in the frontend of their
207-
application.
208-
209-
![Admin Integration](docs/source/_static/admin-person-model.png)
210-
211-
212-
## How does this all work?
213-
214-
**django-formset** makes use of the
215-
[form renderer](https://docs.djangoproject.com/en/stable/ref/forms/renderers/) introduced in
216-
Django 4. This allows to create special renderers for each of the supported CSS frameworks. In
217-
addition to the form structure proposed by those framework vendors, this library adds private HTML
218-
tags to each field containg the constraint information as declared in Python.
219-
220-
The form or the collections of forms then is wrapped by the provided
221-
[webcomponent](https://developer.mozilla.org/en-US/docs/Web/Web_Components) `<django-formset>`.
222-
The JavaScript part (actually TypeScript) making up that webcomponent then handles the form
223-
validation, its submission, instantiation or removal of collection siblings, etc.
224-
225-
Some of the widgets described above (select with autocomplete, file upload) also require JavaScript
226-
code. The client side functionality of those widgets also is handled by that webcomponent.
227-
Widgets which require autocompletion use the same endpoint as that webcomponent itself. So there is
228-
no need to add extra endpoints to the URL router.
229-
230-
This finally means, that an enduser must _only_ import this single JavaScript file and wrap its
231-
single form or collection of forms into a single HTML element such as
232-
23363
```html
234-
<django-formset endpoint="/path/to/myproject/view" csrf-token="">
235-
64+
<!-- templates/contact/form.html -->
65+
{% load formsetify %}
66+
<django-formset endpoint="{% url 'contact' %}">
67+
{% render_form form "bootstrap" %}
68+
<button type="submit" df-action="submit disable show-spinner">Send</button>
23669
</django-formset>
23770
```
23871

239-
The Django view handling the form or collection of forms requires a special mixin class but
240-
otherwise is the same as those proposed by Django, for instance its
241-
[FormView](https://docs.djangoproject.com/en/stable/topics/class-based-views/generic-editing/).
242-
243-
The form classes can be reused unaltered, except for replacing the widgets if desired or required
244-
(the `FileField` requires a different widget).
245-
246-
247-
## Reference Documentation
72+
The web component serializes the form into JSON, talks to the Django view via fetch, and streams back validation errors or success actions without a full-page reload.
24873

249-
Reference documentation with interactive samples can be found at
250-
[https://django-formset.fly.dev/](https://django-formset.fly.dev/).
74+
## Feature Tour
25175

76+
- **Renderer library** for Bootstrap, Tailwind, Bulma, Foundation, and UIkit with matching HTML structure and classes.
77+
- **Widget suite** covering asynchronous uploads, autocomplete selects, dual-list selectors, date pickers, phone numbers with flags, and rich text editing.
78+
- **Action chains** to configure submit buttons with behaviors like disabling, showing spinners, redirecting, or broadcasting events.
79+
- **Nested collections** that let you add, remove, sort, and validate repeated form groups with undo/redo support.
80+
- **JSONField mapping** to persist an entire hierarchy of form data inside a relational model without custom serialization.
81+
- **Conditional visibility** using `df-show`, `df-hide`, and `df-disable` attributes that react instantly to field values.
25282

253-
## Motivation
83+
## Working in Django Admin
25484

255-
Instead of using a `<form>`-tag and include all its fields, here we wrap the complete form inside
256-
the special webcomponent `<django-formset>`. It allows the client to communicate with the Django
257-
view (we name this "endpoint") using the
258-
[fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch).
259-
This means, that multiple `<form>`-elements can be wrapped into a formset. It also means, that the
260-
submit `<button>` can be placed outside of the `<form>`-element. By doing so, the form's payload
261-
is sent using `Content-Type: application/json` instead of the usual
262-
`Content-Type: application/x-www-form-urlencoded`. By using JSON for the payload, the form data is
263-
mapped into JavaScript objects and collections of forms are represented by nested data structures.
85+
Reuse your public-facing forms inside the admin by wrapping them in `FormCollection` classes and enabling the provided admin mixins. Editors gain the same asynchronous uploads, validation messaging, and layout consistency without bespoke admin templates.
26486

265-
**When designing this library, the main goal was to keep the programming interface a near as
266-
possible to the way Django handles forms, models and views.**
87+
## Extending the Library
26788

89+
- Create tailored renderers by subclassing the base renderer and overriding template fragments.
90+
- Compose additional button actions to hook into custom analytics, notifications, or navigation flows.
91+
- Augment the web component with lightweight JavaScript modules that tap into lifecycle callbacks while keeping the main bundle intact.
26892

269-
## Summary
93+
## Testing Tips
27094

271-
* Before submitting, all form fields are prevalidated by the browser, using the same constraints as
272-
declared for each Django form or model field in Python.
273-
* The form's data is sent by an Ajax request, preventing a full page reload. This gives a much
274-
better user experience.
275-
* Server side validation errors are sent back to the browser, and rendered near the rejected
276-
form field.
277-
* Non-field validation errors are renderer together with the form.
278-
* CSRF-tokens are handled through an HTTP-Header, hence there is no need to add a hidden input field
279-
to each form.
280-
* Forms can be rendered for different CSS frameworks using their specific style-guides for arranging
281-
HTML. Curently **django-formset** includes renderers for:
95+
- Run `python manage.py test` as usual; formset views and mixins integrate with Django’s test client.
96+
- In browser dev tools, inspect the datasets attached to form elements to debug live validation or dependency expressions.
97+
- For large data sources, wire autocomplete widgets to search endpoints that reuse your view’s permission logic.
28298

283-
* [Bootstrap 5](https://getbootstrap.com/docs/5.0/forms/overview/),
284-
* [Bulma](https://bulma.io/documentation/form/general/),
285-
* [Foundation 6](https://get.foundation/sites/docs/forms.html),
286-
* [Tailwind](https://tailwindcss.com/) [^1]
287-
* [UIKit](https://getuikit.com/docs/form)
99+
## Resources
288100

289-
It usually takes about 50 lines of code to create a renderer and most widgets can even be rendered
290-
using the default template as provided by Django.
291-
* No external JavaScript dependencies are required. The client part is written in pure TypeScript
292-
and compiles to a single, portable JS-file.
293-
* Support for all standard widgets Django currently offers (except GeoSpacials).
294-
* File uploads are handled asynchronously, separating the payload upload from the form submission.
295-
* Select boxes with too many entries, can be filtered by the server using a search query.
296-
* Radio buttons and multiple checkboxes with only a few fields can be rendered inlined rather than
297-
beneath each other.
298-
* The submit button(s) can be configured as a chain of actions.
299-
* A formset can group multiple forms into a collection. Collections can be nested. On submission,
300-
the data from this form or collection of forms is sent to the server as a group a separate
301-
entities.
302-
* Such a form-collection can be declared to have a list siblings, which can be changed in length
303-
using one "Add" and multiple "Remove" buttons.
304-
* Form fields or fieldsets can be hidden or disabled using a Boolean expression as condition.
305-
* Special widgets for date and datetime fields, they can also be used for ranges.
306-
* Special widget for phone numbers and countries displaying their flags.
307-
* Special widget for editing Richtext.
101+
Interactive documentation, API references, and upgrade guides live at https://django-formset.fly.dev/.
308102

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

0 commit comments

Comments
 (0)