Formkit-Ninja
A Django-Ninja framework for FormKit schemas and form submissions
Why
FormKit out of the box has awesome schema support - this lets us integrate FormKit instances as Django models
- Upload / edit / download basic FormKit schemas
- Translated "option" values from the Django admin
- Reorder "options" and schema nodes
- List and Fetch schemas for different form types
Use
To use, pip install formkit-ninja and add the following to settings INSTALLED_APPS:
INSTALLED_APPS = [
...
"formkit_ninja",
"ninja",
...
]
Quick Start for New Users
⭐ NEW - Create complete data collection apps with minimal coding!
# 1. Create a FormKit schema
./manage.py create_schema --label "Contact Form"
# 2. Bootstrap a complete Django app
./manage.py bootstrap_app --schema-label "Contact Form" --app-name contacts
# 3. Add to INSTALLED_APPS and migrate
# (Edit settings.py to add 'contacts')
./manage.py makemigrations && ./manage.py migrate
# 4. Start collecting data!
./manage.py runserver
See the Quick Start Guide for a complete walkthrough.
Code Generation
⭐ NEW in v0.8.1 - Database-driven code generation! Configure type mappings and field overrides through Django admin without writing Python code.
formkit-ninja can automatically generate Django models, Pydantic schemas, admin classes, and API endpoints from your FormKit schemas.
Database-Driven Configuration
Configure code generation rules through the Django admin:
# Django Admin → Code generation configs
formkit_type = "text"
node_name = "district"
django_type = "ForeignKey"
django_args = {"to": "pnds_data.zDistrict", "on_delete": "models.CASCADE"}
Generates:
# models.py
district = models.ForeignKey("pnds_data.zDistrict", on_delete=models.CASCADE)
Quick Start
Generate code from all schemas in your database:
./manage.py generate_code --app-name myapp --output-dir ./myapp/generated
Generate code for a specific schema:
./manage.py generate_code --app-name myapp --output-dir ./myapp/generated --schema-label "My Form"
Generated Files
The code generator creates the following files:
models.py- Django models for groups and repeatersschemas.py- Django Ninja output schemasschemas_in.py- Django Ninja input schemas (Pydantic BaseModel)admin.py- Django admin classesapi.py- Django Ninja API endpoints
Extensibility
formkit-ninja provides multiple extension points for customizing code generation:
- Database-Driven Config: Configure through Django admin (no code needed!) ⭐ NEW
- Custom Type Converters: Add support for custom FormKit node types
- Custom NodePath: Extend NodePath with project-specific logic
- Plugin System: Bundle multiple extensions together
- Custom Templates: Override Jinja2 templates for generated code
See the Database-Driven Code Generation guide for the new database configuration feature, or the Code Generation Guide for detailed documentation and examples.
API
Formkit-Ninja provides a REST API for managing FormKit schema nodes. The API requires authentication and specific permissions.
Authentication
All API endpoints require:
- Authentication: User must be logged in (session-based authentication)
- Permission: User must have the
formkit_ninja.change_formkitschemanodepermission
Unauthenticated requests receive 401 Unauthorized. Authenticated users without the required permission receive 403 Forbidden.
Endpoints
Create or Update Node
POST /api/formkit/create_or_update_node
Creates a new node or updates an existing one.
Request Body:
uuid(optional): UUID of node to update. If omitted, a new node is created.parent_id(optional): UUID of parent node (must be a group or repeater)$formkit: FormKit node type (e.g., "text", "group", "repeater")- Other FormKit node properties (label, name, etc.)
Response:
200 OK: Success, returnsNodeReturnTypewith node data400 Bad Request: Invalid input (e.g., invalid parent, deleted node)403 Forbidden: Insufficient permissions404 Not Found: Node with provided UUID does not exist (for updates)500 Internal Server Error: Server error
Update Behavior:
- When
uuidis provided, the node with that UUID is updated - If the node doesn't exist, returns
404 Not Found - If the node is inactive (deleted), returns
400 Bad Request - Parent-child relationships are automatically created/updated when
parent_idis provided
Delete Node
DELETE /api/formkit/delete/{node_id}
Soft deletes a node (sets is_active=False).
Response:
200 OK: Success, returnsNodeInactiveType403 Forbidden: Insufficient permissions404 Not Found: Node does not exist
Response Formats
All successful responses return consistent data structures:
-
NodeReturnType: For active nodes
key: UUID of the nodenode: FormKit node datalast_updated: Timestamp of last changeprotected: Whether the node is protected from deletion
-
NodeInactiveType: For deleted nodes
key: UUID of the nodeis_active:falselast_updated: Timestamp of last changeprotected: Whether the node is protected
-
FormKitErrors: For error responses
errors: List of error messagesfield_errors: Dictionary of field-specific errors
Validation
The API validates:
- Parent existence: If
parent_idis provided, the parent node must exist and be a group or repeater - Node existence: If
uuidis provided for updates, the node must exist and be active - FormKit type: The
$formkitfield must be a valid FormKit node type
Test
Pull the repo:
gh repo clone catalpainternational/formkit-ninja
cd formkit-ninja
uv sync
Database Setup
Tests and submission queryset annotations (e.g. with_unresolved_flags(), with_import_failure()) require PostgreSQL (pgtrigger, and JSONBAgg/JSONObject for annotations). Start a PostgreSQL container before running tests:
# Using Podman (recommended)
podman run --replace -d --name formkit-postgres -p 5434:5432 -e POSTGRES_HOST_AUTH_METHOD=trust docker.io/library/postgres:14-alpine
# OR using Docker
docker run -d --name formkit-postgres -p 5434:5432 -e POSTGRES_HOST_AUTH_METHOD=trust postgres:14-alpine
Then run tests:
uv run pytest
Playwright
Some tests require playwright. Install it with:
uv run playwright install
Note: For full development setup with real data, see DEVELOPMENT.md.
Lint
Format and lint code using ruff:
# Check formatting
uv run ruff format --check .
# Check linting
uv run ruff check .
For Contributors
Prerequisites
- Python 3.10-3.14
uvfor package management- Podman or Docker for PostgreSQL database
- Playwright (for browser-based tests)
Development Workflow
-
Set up the project:
uv sync uv run playwright install # Start PostgreSQL (see Database Setup above)
-
Run tests:
uv run pytest
-
Check code quality:
uv run ruff format --check . uv run ruff check . uv run mypy formkit_ninja tests/test_wire.py
-
Test Driven Development (TDD):
- Write tests before implementing features
- Ensure new code is covered by tests
- Use
pytestas the testing framework
-
Code Style:
- Use
rufffor formatting and linting - Follow Python type hints for all function arguments and return values
- Adhere to SOLID principles
- Use
-
Commit Messages:
- Use Conventional Commits specification
- Format:
<type>(<scope>): <subject>
Updating 'Protected' Nodes
If a node's been protected you cannot change or delete it. To do so, you'll need to temporarily disable the trigger which is on it.
./manage.py pytrigger disable protect_node_deletes_and_updates
Make changes
./manage.py pgtrigger enable protect_node_deletes_and_updates
See the documentation for more details: https://django-pgtrigger.readthedocs.io/en/2.3.0/commands.html?highlight=disable
Metadata
Release files for formkit-ninja 6.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| formkit_ninja-6.1.2.tar.gz | 1.0 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| formkit_ninja-6.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.5 MB
Release files / formkit_ninja-6.1.2.tar.gz
| Download URL | formkit_ninja-6.1.2.tar.gz |
|---|---|
| Size | 1.0 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
28b1ae4f86813d9d4021c7c611ec3f09f6a1d5de7b1c7d770fe894eba025ae17
|
|
BLAKE2b-256 checksum How to use checksums |
17e0c7462ae833ad42f69b05613feaf1cea5055b77abaac521c0bc772d5be0d0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.
Transparency logRelease files / formkit_ninja-6.1.2-py3-none-any.whl
| Download URL | formkit_ninja-6.1.2-py3-none-any.whl |
|---|---|
| Size | 485.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
54fbdfb4439abea42a51d906725a0d44cce91c40aa5f9898157f0f72c7a2cf55
|
|
BLAKE2b-256 checksum How to use checksums |
8787c3fde4f1d4a7e6d4f3bd193f52417d5a1ff10eccba83b8ce68ff5cd15adf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.
Transparency log