django-kpforms
A reusable Django application for dynamic form templates and submissions, powered by Lit-based web components.
- Define form templates with sections, groups, and typed fields (numeric, string, text, radio, checkbox, datetime, calculated, ranged-radio)
- Built-in tolerance checking for numeric and string fields
- Interactive web component (
<dynamic-form>) for end-user submission - Visual form-builder web component (
<dynamic-form-builder>) for creating and editing templates - Printable-form component for generating PDFs via
html2pdf.js - Full version history for form templates
Table of Contents
- Requirements
- Installation
- Quick Start
- Django Models & Helpers
- Web Components
- CSS Customisation Variables
- Contributing / Building from Source
Requirements
- Python ≥ 3.14
- Django ≥ 5.0
- Pydantic ≥ 2.0
Installation
pip install django-kpforms
Or with uv:
uv add django-kpforms
Quick Start
1. Add to INSTALLED_APPS
# settings.py
INSTALLED_APPS = [
...
"kpforms",
]
2. Run migrations
python manage.py migrate
3. Serve static files
django-kpforms ships with a compiled JavaScript bundle (kpforms.js) and
KaTeX font files under kpforms/static/.
Make sure Django's static-file serving is configured and collectstatic has
been run in production:
python manage.py collectstatic
4. Load the bundle in your template
{% load static %}
<!-- Load the kpforms web-component bundle -->
<script type="module" src="{% static 'kpforms.js' %}"></script>
<!-- Optional: load html2pdf for the printable-form component -->
<script src="{% static 'html2pdf.bundle.min.js' %}"></script>
Django Models & Helpers
Models
| Model | Description |
|---|---|
FormTemplate |
A versioned form definition. The schema field holds a FormTemplateSchema JSON object. |
FormTemplateVersionHistory |
Immutable snapshot of a FormTemplate at each version. |
FormSubmission |
A single submission against a FormTemplate. Validates and stores tolerance results. |
Helper functions (kpforms.helpers)
from kpforms.helpers import (
create_form_template,
update_form_template,
submit_form_data,
get_form_by_template_id_and_version,
get_form_template_list,
get_form_template_list_paginated,
get_all_results_for_form_template,
)
| Function | Description |
|---|---|
create_form_template(schema, user) |
Creates a new FormTemplate from a CreateFormTemplateSchema. |
update_form_template(schema, user) |
Saves a new version of an existing template. |
submit_form_data(template_id, form_data, user) |
Validates and saves a FormSubmission. |
get_form_by_template_id_and_version(id, version=None) |
Returns the FormTemplateSchema for a given ID and optional version. |
get_form_template_list(only_active=True) |
Returns a list of {id, name, description} dicts. |
get_form_template_list_paginated(page_size, only_active) |
Returns a Django Paginator. |
get_all_results_for_form_template(template_id) |
Returns all submissions with labelled field data. |
Schema classes (kpforms.form_schema)
The core schema models are Pydantic models. The most important ones:
| Class | Description |
|---|---|
FormTemplateSchema |
Full form definition: title, instruction, sections. |
CreateFormTemplateSchema |
Input payload for creating a template. |
UpdateFormTemplateSchema |
Input payload for updating a template (adds guid). |
FormSubmissionSchema |
Submission payload: data (list of {fieldId, value}), fieldsOutOfTolerance. |
Web Components
Include kpforms.js once on the page (see Quick Start above) — all components
are self-registering and require no additional setup.
<dynamic-form>
Renders a form for end-user completion and submission.
<dynamic-form id="my-form"></dynamic-form>
<script type="module">
const formEl = document.getElementById("my-form");
// Pass the form schema (FormSchema JSON) as a property
formEl.schema = {
guid: "...",
title: "My Form",
sections: [...],
generatePdfOption: false,
generatePdfString: "Generate PDF",
submitFormString: "Submit",
submittingString: "Submitting…",
apiConfig: {
url: "/api/forms/submit/",
method: "POST",
successMessage: "Form submitted!",
errorMessage: "Submission failed."
}
};
// Optional: add extra keys to the submit POST body.
// Must be a JSON-formatted string representing an object.
formEl.extraPostParams = JSON.stringify({
project_id: "abc123",
station: "line-7"
});
</script>
Properties / attributes:
| Property | Type | Description |
|---|---|---|
schema |
FormSchema (object) |
The full form schema to render. |
lang |
string |
BCP-47 language tag for UI strings (e.g. "en", "sv"). Defaults to "en". |
extraPostParams |
string (JSON object) |
Optional key/value pairs merged into the POST payload when submitting form results. |
compact |
boolean |
Dense layout: smaller type/spacing, inline label column + value column. Default false. UI-only, never saved in the schema; also applies in view-only mode. See Compact mode. |
<dynamic-form-builder>
An interactive editor that lets users create and edit form templates.
<dynamic-form-builder id="builder"></dynamic-form-builder>
<script type="module">
const builder = document.getElementById("builder");
// Optional: load an existing template for editing
builder.schema = { guid: "...", name: "...", formSchema: { ... } };
// Optional: add extra keys to the save-schema POST body.
// Must be a JSON-formatted string representing an object.
builder.extraPostParams = JSON.stringify({
project_id: "abc123",
edited_by: "frontend"
});
// Listen for save events
builder.addEventListener("form-save", (e) => {
const payload = e.detail; // CreateFormTemplateSchema | UpdateFormTemplateSchema
fetch("/api/forms/templates/", {
method: payload.guid ? "PUT" : "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
});
</script>
Properties / attributes:
| Property | Type | Description |
|---|---|---|
schema |
UpdateFormTemplateSchema (object) |
Optional existing template to edit. |
lang |
string |
BCP-47 language tag. |
extraPostParams |
string (JSON object) |
Optional key/value pairs merged into the POST payload when saving the schema. |
compact |
boolean |
Dense editor (tight stack, labels above, no inline-label columns). Default false. See Compact mode. |
foldableGroups |
boolean |
Each input group gets a fold bar (title, id, field count). UI-only state, never saved. Default false. |
The builder's preview dialog has a Compact switch that renders the preview
<dynamic-form> with compact. It is independent of the builder's own compact.
Events emitted:
| Event | detail |
Description |
|---|---|---|
form-save |
CreateFormTemplateSchema | UpdateFormTemplateSchema |
Fired when the user clicks Save. |
<printable-form>
Renders a read-only printable view of a submitted form that can be exported as a PDF using html2pdf.js.
<printable-form id="print-form"></printable-form>
<script type="module">
const el = document.getElementById("print-form");
el.schema = { /* FormTemplateSchema */ };
el.submission = { /* FormSubmission data */ };
</script>
Properties:
| Property | Type | Description |
|---|---|---|
schema |
FormTemplateSchema (object) |
The form template. |
submission |
FormSubmissionSchema (object) |
The submission data to display. |
CSS Customisation Variables
All web components use CSS custom properties (variables) to control their appearance. Set these on the element itself or any ancestor to theme the components.
The public API variables are prefixed --df-*. Internal variables map them
to --form-* and to Shoelace design tokens.
Typography
| Variable | Default | Description |
|---|---|---|
--df-font-size-base |
1rem |
Base font size. |
--df-font-size-h1 |
1.8rem |
Form title size. |
--df-font-size-h2 |
1.4rem |
Section heading size. |
--df-font-size-label |
0.9rem |
Field label size. |
--df-font-size-description |
0.85rem |
Field description / helper text size. |
Colours
| Variable | Default | Description |
|---|---|---|
--df-color-text |
#333 |
Primary text colour. |
--df-color-input-text |
#111 |
Text colour inside input fields. |
--df-color-text-secondary |
#555 |
Secondary / description text colour. |
--df-color-text-label |
#111 |
Field label text colour. |
--df-color-background |
#ffffff |
Component background. |
--df-color-background-input-base |
#ffffff |
Input field background (resting). |
--df-color-background-input-focus |
#ffffff |
Input field background on focus. |
--df-color-background-section |
#f9f9f9 |
Form section background. |
--df-color-border |
#ccc |
Default border colour. |
Accent Colour
Used for buttons, active tabs, focused borders, and Shoelace's primary palette.
| Variable | Default | Description |
|---|---|---|
--df-color-accent |
#007bff |
Primary accent / brand colour. |
--df-color-accent-lighter |
#66b3ff |
Lighter variant (e.g. hover states). |
--df-color-accent-darker |
#0056b3 |
Darker variant (e.g. active states). |
--df-color-accent-text |
#ffffff |
Text on accent-coloured elements. |
--df-color-success |
#28a745 |
Success colour (Shoelace success palette). |
--df-color-success-darker |
#1e7e34 |
Darker success variant. |
--df-color-success-text |
#ffffff |
Text on success-coloured elements. |
--df-color-warning |
#fbb34e |
Warning colour. |
--df-color-warning-darker |
#fea429 |
Darker warning variant. |
--df-color-warning-text |
#ffffff |
Text on warning-coloured elements. |
--df-color-neutral |
#6c757d |
Neutral colour. |
--df-color-neutral-darker |
#5a6268 |
Darker neutral variant. |
--df-color-neutral-text |
#ffffff |
Text on neutral-coloured elements. |
--df-color-error |
#dc3545 |
Error / danger colour. |
--df-color-error-lighter |
#fa5969 |
Lighter error variant. |
--df-color-error-text |
#721c24 |
Text on error-coloured elements. |
Status / Tolerance Colours
These control how fields are highlighted when their values are within or outside the defined tolerance range.
| Variable | Default | Description |
|---|---|---|
--df-color-required-star |
#f98989 |
Colour of the asterisk on required fields. |
--df-color-within-tolerance-border |
#8fff8f |
Border colour when field value is within tolerance. |
--df-color-within-tolerance-background |
#b9fb9f |
Background when field value is within tolerance. |
--df-color-within-tolerance-input-text |
#214521 |
Text colour in input when within tolerance. |
--df-color-within-tolerance-text |
#3c763d |
Label/message text when within tolerance. |
--df-color-out-of-tolerance-border |
#fdf8b2 |
Border colour when field value is out of tolerance. |
--df-color-out-of-tolerance-text |
#d9534f |
Text/label colour when out of tolerance. |
--df-color-has-error-border |
(inherits error colour) | Border colour when field has a validation error. |
--df-color-has-error-background |
#d58d8b |
Background when field has a validation error. |
Layout
| Variable | Default | Description |
|---|---|---|
--df-border-radius |
4px |
Corner radius for inputs, sections, and the component itself. |
--df-spacing-medium |
1rem |
Medium spacing unit used for padding/gaps. |
--df-spacing-large |
1.5rem |
Large spacing unit used for section padding. |
--df-button-padding |
0.3rem 1rem |
Padding applied to Shoelace <sl-button> elements. |
The tokens below are also public --df-* variables. Defaults shown are the
regular (non-compact) values. Tokens marked -- in a compact column are not
changed by compact mode for that component.
| Variable | Default | Description |
|---|---|---|
--df-input-padding |
0.5rem 0.75rem |
Input padding. |
--df-label-gap |
0.25rem |
Space below field label. |
--df-field-gap |
var(--df-spacing-medium) |
Space between fields. |
--df-checkbox-size |
1.5em |
Checkbox/radio size. |
--df-option-gap |
0.5rem |
Gap between checkbox/radio options. |
--df-option-group-padding |
0.5rem |
Padding around an option group. |
--df-option-flow |
column |
flex-flow of option groups. |
--df-actions-gap |
1rem |
Gap between form action buttons. |
--df-group-padding |
var(--df-spacing-medium) |
Input group padding. |
--df-group-margin |
var(--df-spacing-large) |
Input group margin. |
--df-group-gutter |
1rem |
Builder: left indent of group content. |
--df-option-box-padding |
0.5rem |
Builder: option box padding. |
--df-option-box-gap |
0.5rem |
Builder: gap between option boxes. |
--df-icon-button-size |
3rem |
Add/remove option icon button size. |
--df-remove-button-size |
2rem |
Remove button size. |
--df-move-button-size |
1.75rem |
Builder: move field button size. |
--df-tab-padding |
0.5rem 1rem |
Tab padding. |
--df-instruction-button-padding |
0.5rem 1rem |
Instruction fold button padding. |
--df-field-display |
flex |
Field display. |
--df-field-columns |
none |
Field grid-template-columns (used when display is grid). |
--df-field-value-column |
auto |
Grid column of the field value. |
--df-field-column-gap |
0 |
Gap between label and value columns. |
--df-field-align |
normal |
Field align-items. |
Compact mode
Set the compact attribute on <dynamic-form> or <dynamic-form-builder>:
<dynamic-form compact></dynamic-form>
<dynamic-form-builder compact foldableGroups></dynamic-form-builder>
Compact mode only supplies fallback values (via internal --form-compact-*
variables). An explicit --df-* variable always wins over compact, so a
consumer theme keeps working unchanged. Example: dynamic-form { --df-font-size-base: 1.1rem; }
stays 1.1rem with compact, while other compact values still apply.
Compact values (blank = same as regular default):
| Variable | Regular | <dynamic-form compact> |
<dynamic-form-builder compact> |
|---|---|---|---|
--df-font-size-base |
1rem |
0.85rem |
0.9rem |
--df-font-size-label |
0.9rem |
0.8rem |
0.8rem |
--df-font-size-description |
0.85rem |
0.72rem |
0.75rem |
--df-font-size-h1 |
1.8rem |
1.3rem |
1.4rem |
--df-font-size-h2 |
1.4rem |
1rem |
1.1rem |
--df-spacing-medium |
1rem |
0.4rem |
0.5rem |
--df-spacing-large |
1.5rem |
0.5rem |
0.6rem |
--df-button-padding |
0.3rem 1rem |
0.1rem 0.5rem |
0.15rem 0.6rem |
--df-input-padding |
0.5rem 0.75rem |
0.2rem 0.4rem |
0.2rem 0.4rem |
--df-label-gap |
0.25rem |
0 |
0 |
--df-field-gap |
var(--df-spacing-medium) |
-- | 0.3rem |
--df-checkbox-size |
1.5em |
1.05em |
1.05em |
--df-option-gap |
0.5rem |
0.3rem |
0.1rem |
--df-option-group-padding |
0.5rem |
0.1rem |
-- |
--df-option-flow |
column |
row wrap |
-- |
--df-actions-gap |
1rem |
0.5rem |
0.5rem |
--df-group-padding |
var(--df-spacing-medium) |
-- | 0.4rem |
--df-group-margin |
var(--df-spacing-large) |
-- | 0.4rem |
--df-group-gutter |
1rem |
-- | 1.7rem |
--df-option-box-padding |
0.5rem |
-- | 0.25rem |
--df-option-box-gap |
0.5rem |
-- | 0.25rem |
--df-icon-button-size |
3rem |
-- | 1.6rem |
--df-remove-button-size |
2rem |
-- | 1.6rem |
--df-move-button-size |
1.75rem |
-- | 1.2rem |
--df-tab-padding |
0.5rem 1rem |
0.25rem 0.6rem |
0.25rem 0.6rem |
--df-instruction-button-padding |
0.5rem 1rem |
0.25rem 0.5rem |
-- |
--df-field-display |
flex |
grid |
-- |
--df-field-columns |
none |
minmax(7rem, 11rem) 1fr |
-- |
--df-field-value-column |
auto |
2 |
-- |
--df-field-column-gap |
0 |
0.5rem |
-- |
--df-field-align |
normal |
center |
-- |
Narrow forms fall back to labels above values.
Example: Dark Theme
dynamic-form {
--df-color-background: #1e1e2e;
--df-color-background-section: #2a2a3e;
--df-color-background-input-base: #2d2d42;
--df-color-text: #cdd6f4;
--df-color-text-label: #cdd6f4;
--df-color-input-text: #cdd6f4;
--df-color-border: #45475a;
--df-color-accent: #89b4fa;
--df-color-accent-darker: #74a0e8;
--df-color-accent-text: #1e1e2e;
}
Contributing / Building from Source
Prerequisites
- Python ≥ 3.14 (with uv)
- Node.js ≥ 20 with npm
Build
Use the included build.sh to produce a distributable wheel and sdist:
# Build both sdist and wheel (default)
./build.sh
# Build wheel only
./build.sh --wheel
# Build sdist only
./build.sh --sdist
The script will:
- Run
npm run buildinsidekpforms/kpforms/to compile the TypeScript web components and copy the output intokpforms/static/. - Run
python manage.py makemigrations kpformsto ensure migrations are up-to-date. - Run
uv buildto produce the final packages indist/.
Running Tests
uv run pytest
Licence
MIT — see LICENSE for details.
Metadata
Release files for django-kpforms 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_kpforms-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Release files / django_kpforms-1.0.0-py3-none-any.whl
| Download URL | django_kpforms-1.0.0-py3-none-any.whl |
|---|---|
| Size | 2.2 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7bbd87573db4e4685aabc89388d6fd469e24c13c8ab21920666c0c159c6e1407
|
|
BLAKE2b-256 checksum How to use checksums |
b404dee8c19dd30efc49a0b4a9bb7ce73f4be477c64f37e5781ddfe7ad3c69a1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.5
|
Security for django-kpforms 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.