django-form-matrix 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_form_matrix-0.1.0/.github/workflows/compat-matrix.yml +27 -0
- django_form_matrix-0.1.0/.github/workflows/js-tests.yml +24 -0
- django_form_matrix-0.1.0/.github/workflows/pre-commit.yml +28 -0
- django_form_matrix-0.1.0/.github/workflows/pytest.yml +31 -0
- django_form_matrix-0.1.0/.github/workflows/release.yml +55 -0
- django_form_matrix-0.1.0/.gitignore +42 -0
- django_form_matrix-0.1.0/.pre-commit-config.yaml +66 -0
- django_form_matrix-0.1.0/CHANGELOG.md +21 -0
- django_form_matrix-0.1.0/CONTRIBUTING.md +113 -0
- django_form_matrix-0.1.0/LICENSE +21 -0
- django_form_matrix-0.1.0/PKG-INFO +217 -0
- django_form_matrix-0.1.0/README.md +171 -0
- django_form_matrix-0.1.0/biome.json +19 -0
- django_form_matrix-0.1.0/docs/examples/permissions.md +189 -0
- django_form_matrix-0.1.0/docs/screenshots/.gitkeep +0 -0
- django_form_matrix-0.1.0/docs/screenshots/hero.png +0 -0
- django_form_matrix-0.1.0/docs/screenshots/permissions.png +0 -0
- django_form_matrix-0.1.0/docs/screenshots/unfold-dark.png +0 -0
- django_form_matrix-0.1.0/docs/screenshots/unfold-permissions.png +0 -0
- django_form_matrix-0.1.0/docs/screenshots/unfold.png +0 -0
- django_form_matrix-0.1.0/example/demo/__init__.py +0 -0
- django_form_matrix-0.1.0/example/demo/admin.py +36 -0
- django_form_matrix-0.1.0/example/demo/dynamic_permissions.py +84 -0
- django_form_matrix-0.1.0/example/demo/forms.py +35 -0
- django_form_matrix-0.1.0/example/demo/migrations/0001_initial.py +31 -0
- django_form_matrix-0.1.0/example/demo/migrations/__init__.py +0 -0
- django_form_matrix-0.1.0/example/demo/models.py +22 -0
- django_form_matrix-0.1.0/example/demo/templates/demo/custom_matrix.html +7 -0
- django_form_matrix-0.1.0/example/demo/templates/demo/custom_permissions.html +7 -0
- django_form_matrix-0.1.0/example/example/__init__.py +0 -0
- django_form_matrix-0.1.0/example/example/settings.py +66 -0
- django_form_matrix-0.1.0/example/example/settings_unfold.py +6 -0
- django_form_matrix-0.1.0/example/example/urls.py +6 -0
- django_form_matrix-0.1.0/example/example/wsgi.py +7 -0
- django_form_matrix-0.1.0/example/manage.py +20 -0
- django_form_matrix-0.1.0/package-lock.json +2963 -0
- django_form_matrix-0.1.0/package.json +16 -0
- django_form_matrix-0.1.0/pyproject.toml +159 -0
- django_form_matrix-0.1.0/src/django_form_matrix/__init__.py +6 -0
- django_form_matrix-0.1.0/src/django_form_matrix/apps.py +7 -0
- django_form_matrix-0.1.0/src/django_form_matrix/contrib/__init__.py +0 -0
- django_form_matrix-0.1.0/src/django_form_matrix/contrib/permissions.py +177 -0
- django_form_matrix-0.1.0/src/django_form_matrix/contrib/unfold.py +23 -0
- django_form_matrix-0.1.0/src/django_form_matrix/fields.py +159 -0
- django_form_matrix-0.1.0/src/django_form_matrix/locale/es/LC_MESSAGES/django.mo +0 -0
- django_form_matrix-0.1.0/src/django_form_matrix/locale/es/LC_MESSAGES/django.po +73 -0
- django_form_matrix-0.1.0/src/django_form_matrix/py.typed +0 -0
- django_form_matrix-0.1.0/src/django_form_matrix/static/django_form_matrix/css/matrix.css +145 -0
- django_form_matrix-0.1.0/src/django_form_matrix/static/django_form_matrix/css/unfold.css +35 -0
- django_form_matrix-0.1.0/src/django_form_matrix/static/django_form_matrix/js/matrix.js +67 -0
- django_form_matrix-0.1.0/src/django_form_matrix/templates/django_form_matrix/matrix.html +78 -0
- django_form_matrix-0.1.0/src/django_form_matrix/templates/django_form_matrix/permissions.html +89 -0
- django_form_matrix-0.1.0/src/django_form_matrix/widgets.py +148 -0
- django_form_matrix-0.1.0/tests/__init__.py +0 -0
- django_form_matrix-0.1.0/tests/conftest.py +17 -0
- django_form_matrix-0.1.0/tests/e2e/__init__.py +0 -0
- django_form_matrix-0.1.0/tests/e2e/test_e2e.py +85 -0
- django_form_matrix-0.1.0/tests/js/matrix.test.js +115 -0
- django_form_matrix-0.1.0/tests/settings.py +45 -0
- django_form_matrix-0.1.0/tests/test_blocks.py +74 -0
- django_form_matrix-0.1.0/tests/test_dynamic_permissions.py +106 -0
- django_form_matrix-0.1.0/tests/test_fields.py +123 -0
- django_form_matrix-0.1.0/tests/test_i18n.py +122 -0
- django_form_matrix-0.1.0/tests/test_permissions.py +152 -0
- django_form_matrix-0.1.0/tests/test_unfold.py +18 -0
- django_form_matrix-0.1.0/tests/test_widgets.py +137 -0
- django_form_matrix-0.1.0/tests/urls.py +6 -0
- django_form_matrix-0.1.0/uv.lock +1591 -0
- django_form_matrix-0.1.0/vitest.config.js +15 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: Compatibility Matrix
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master, main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master, main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
tox:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Install uv
|
|
16
|
+
uses: astral-sh/setup-uv@v5
|
|
17
|
+
with:
|
|
18
|
+
enable-cache: true
|
|
19
|
+
|
|
20
|
+
- name: Install Python versions
|
|
21
|
+
run: uv python install 3.10 3.12 3.13 3.14
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: uv sync
|
|
25
|
+
|
|
26
|
+
- name: Run compatibility matrix (tox)
|
|
27
|
+
run: uv run tox run
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: JavaScript Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Setup Node.js
|
|
15
|
+
uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: "20"
|
|
18
|
+
cache: "npm"
|
|
19
|
+
|
|
20
|
+
- name: Install dependencies
|
|
21
|
+
run: npm ci
|
|
22
|
+
|
|
23
|
+
- name: Run Tests & Coverage
|
|
24
|
+
run: npm run coverage
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Pre-commit
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
pre-commit:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Install uv
|
|
15
|
+
uses: astral-sh/setup-uv@v5
|
|
16
|
+
with:
|
|
17
|
+
enable-cache: true
|
|
18
|
+
|
|
19
|
+
# The hooks run on python3.14 (default_language_version), found
|
|
20
|
+
# through the venv's bin on PATH under `uv run`.
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
run: uv python install 3.14
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: uv sync --python 3.14
|
|
26
|
+
|
|
27
|
+
- name: Run pre-commit
|
|
28
|
+
run: uv run pre-commit run --all-files
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Python and Playwright Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master, main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master, main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test-python:
|
|
11
|
+
name: Python Tests
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Install uv
|
|
17
|
+
uses: astral-sh/setup-uv@v5
|
|
18
|
+
with:
|
|
19
|
+
enable-cache: true
|
|
20
|
+
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
run: uv python install
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: uv sync
|
|
26
|
+
|
|
27
|
+
- name: Install Playwright browsers
|
|
28
|
+
run: uv run playwright install --with-deps chromium
|
|
29
|
+
|
|
30
|
+
- name: Run Pytest
|
|
31
|
+
run: uv run pytest
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types:
|
|
6
|
+
- published
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release-build:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Install uv
|
|
16
|
+
uses: astral-sh/setup-uv@v5
|
|
17
|
+
with:
|
|
18
|
+
enable-cache: true
|
|
19
|
+
|
|
20
|
+
- name: Show project version
|
|
21
|
+
run: |
|
|
22
|
+
grep "version" pyproject.toml
|
|
23
|
+
|
|
24
|
+
- name: Build release distributions
|
|
25
|
+
run: uv build
|
|
26
|
+
|
|
27
|
+
- name: Upload distributions
|
|
28
|
+
uses: actions/upload-artifact@v4
|
|
29
|
+
with:
|
|
30
|
+
name: release-dists
|
|
31
|
+
path: dist/
|
|
32
|
+
|
|
33
|
+
pypi-publish:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
needs:
|
|
36
|
+
- release-build
|
|
37
|
+
permissions:
|
|
38
|
+
id-token: write
|
|
39
|
+
contents: read
|
|
40
|
+
|
|
41
|
+
environment:
|
|
42
|
+
name: pypi
|
|
43
|
+
url: https://pypi.org/project/django-form-matrix/
|
|
44
|
+
|
|
45
|
+
steps:
|
|
46
|
+
- name: Retrieve release distributions
|
|
47
|
+
uses: actions/download-artifact@v4
|
|
48
|
+
with:
|
|
49
|
+
name: release-dists
|
|
50
|
+
path: dist/
|
|
51
|
+
|
|
52
|
+
- name: Publish release distributions to PyPI
|
|
53
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
54
|
+
with:
|
|
55
|
+
packages-dir: dist/
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.installed.cfg
|
|
9
|
+
*.egg
|
|
10
|
+
|
|
11
|
+
# Virtual environments
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
env/
|
|
15
|
+
|
|
16
|
+
# Testing / coverage
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.coverage
|
|
19
|
+
.coverage.*
|
|
20
|
+
coverage/
|
|
21
|
+
htmlcov/
|
|
22
|
+
.mypy_cache/
|
|
23
|
+
.ruff_cache/
|
|
24
|
+
.tox/
|
|
25
|
+
|
|
26
|
+
# Django
|
|
27
|
+
*.log
|
|
28
|
+
local_settings.py
|
|
29
|
+
db.sqlite3
|
|
30
|
+
db.sqlite3-journal
|
|
31
|
+
media/
|
|
32
|
+
|
|
33
|
+
# Node / JS
|
|
34
|
+
node_modules/
|
|
35
|
+
|
|
36
|
+
# Editors / OS
|
|
37
|
+
.vscode/
|
|
38
|
+
.idea/
|
|
39
|
+
.DS_Store
|
|
40
|
+
|
|
41
|
+
# Docs
|
|
42
|
+
docs/_build/
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
default_language_version:
|
|
2
|
+
python: &python-version python3.14
|
|
3
|
+
|
|
4
|
+
repos:
|
|
5
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
6
|
+
rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0
|
|
7
|
+
hooks:
|
|
8
|
+
- id: check-added-large-files
|
|
9
|
+
- id: check-case-conflict
|
|
10
|
+
- id: check-json
|
|
11
|
+
- id: check-merge-conflict
|
|
12
|
+
- id: check-symlinks
|
|
13
|
+
- id: check-toml
|
|
14
|
+
- id: debug-statements
|
|
15
|
+
- id: end-of-file-fixer
|
|
16
|
+
- id: trailing-whitespace
|
|
17
|
+
|
|
18
|
+
- repo: https://github.com/biomejs/pre-commit
|
|
19
|
+
rev: d1ce4972fb5ad09afd33b432bca71e8c5dfbb2b5 # frozen: v2.5.6
|
|
20
|
+
hooks:
|
|
21
|
+
- id: biome-check
|
|
22
|
+
additional_dependencies:
|
|
23
|
+
- '@biomejs/biome@2.3.8'
|
|
24
|
+
language_version: default
|
|
25
|
+
|
|
26
|
+
- repo: https://github.com/adamchainz/django-upgrade
|
|
27
|
+
rev: bdcc9646c249f00ec9781751791e7b75b48b3722 # frozen: 1.31.1
|
|
28
|
+
hooks:
|
|
29
|
+
- id: django-upgrade
|
|
30
|
+
args: [--target-version, "4.2"]
|
|
31
|
+
|
|
32
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
33
|
+
rev: 39d9ac5938dadb73df0564a45f163e25ff9fa6e2 # frozen: v0.16.1
|
|
34
|
+
hooks:
|
|
35
|
+
- id: ruff-check
|
|
36
|
+
args: [--fix]
|
|
37
|
+
- id: ruff-format
|
|
38
|
+
|
|
39
|
+
- repo: local
|
|
40
|
+
hooks:
|
|
41
|
+
- id: mypy
|
|
42
|
+
name: mypy
|
|
43
|
+
entry: mypy
|
|
44
|
+
language: python
|
|
45
|
+
additional_dependencies:
|
|
46
|
+
- mypy>=1.10
|
|
47
|
+
- django-stubs[compatible-mypy]>=5
|
|
48
|
+
- django>=4.2
|
|
49
|
+
- pytest-django>=4.8
|
|
50
|
+
types: [python]
|
|
51
|
+
files: ^(src|example)/
|
|
52
|
+
pass_filenames: false
|
|
53
|
+
args: [src/django_form_matrix, example]
|
|
54
|
+
|
|
55
|
+
# Only needed if the library ships Django templates (src/<pkg>/templates/).
|
|
56
|
+
# Remove this hook if it doesn't.
|
|
57
|
+
- repo: https://github.com/adamchainz/djade-pre-commit
|
|
58
|
+
rev: 52a7ce253113456c2a1113186a4a19b15c8b68af # frozen: 1.9.0
|
|
59
|
+
hooks:
|
|
60
|
+
- id: djade
|
|
61
|
+
files: '(/|^)templates/'
|
|
62
|
+
|
|
63
|
+
- repo: https://github.com/tox-dev/pyproject-fmt
|
|
64
|
+
rev: d600c142bb19f521ae6a6a345f94a2efd7937759 # frozen: v2.26.0
|
|
65
|
+
hooks:
|
|
66
|
+
- id: pyproject-fmt
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-10-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `MatrixField`, `MatrixWidget`, `Row` and `Column`: a table of rows and
|
|
13
|
+
columns with a form field in every cell, optional collapsible sections and
|
|
14
|
+
shortcuts to check a row, a column or a section.
|
|
15
|
+
- `django_form_matrix.contrib.permissions`: `PermissionMultipleChoiceField`,
|
|
16
|
+
the permissions of an app/model/action in a matrix.
|
|
17
|
+
- Blocks in `matrix.html` and `permissions.html` (`section_summary`,
|
|
18
|
+
`column_heading`, `cell`...) to extend them changing only a piece.
|
|
19
|
+
- `django_form_matrix.contrib.unfold.UnfoldMatrixMixin`: the matrix on
|
|
20
|
+
django-unfold's palette (light and dark).
|
|
21
|
+
- Spanish translation.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Development setup
|
|
4
|
+
|
|
5
|
+
With [uv](https://docs.astral.sh/uv/) (recommended):
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv sync
|
|
9
|
+
uv run playwright install --with-deps chromium
|
|
10
|
+
npm install
|
|
11
|
+
uv run pre-commit install
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Without uv:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install -e ".[dev]"
|
|
18
|
+
playwright install --with-deps chromium
|
|
19
|
+
npm install
|
|
20
|
+
pre-commit install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Running the example project
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cd example
|
|
27
|
+
python manage.py migrate
|
|
28
|
+
python manage.py createsuperuser
|
|
29
|
+
python manage.py runserver
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Tests
|
|
33
|
+
|
|
34
|
+
Python (pytest + pytest-django, coverage always on via `addopts`):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uv run pytest
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`tests/e2e/` (real-browser tests via pytest-playwright, driving a real
|
|
41
|
+
Chromium against `live_server`) runs as part of that same `uv run pytest`
|
|
42
|
+
— no separate command. They need `uv run playwright install chromium`
|
|
43
|
+
once (see Setup above) and are slower than the rest of the suite;
|
|
44
|
+
everything else under `tests/` is unit/integration-level and doesn't
|
|
45
|
+
touch a browser.
|
|
46
|
+
|
|
47
|
+
JavaScript (vitest + jsdom):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm test
|
|
51
|
+
npm run coverage
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Compatibility matrix (tox)
|
|
55
|
+
|
|
56
|
+
`uv run pytest` above only runs against whatever Django version `uv.lock`
|
|
57
|
+
resolved (the newest one satisfying `dependencies`). To check the full
|
|
58
|
+
supported range — every Django series in `classifiers`, against the
|
|
59
|
+
oldest and newest Python it supports (within this package's own
|
|
60
|
+
`requires-python` floor) — run the tox matrix instead:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv run tox run-parallel # every env, in parallel
|
|
64
|
+
uv run tox -e py314-dj52 # a single env, e.g. to debug one failure
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`[tool.tox]` in `pyproject.toml` lists the exact envs. Each one gets its own ephemeral venv (via
|
|
68
|
+
[tox-uv](https://github.com/tox-dev/tox-uv), using uv's own Python
|
|
69
|
+
builds — `uv python install <version>` once for any you don't have yet)
|
|
70
|
+
with only `pytest`/`pytest-django`/`pytest-cov` and that env's pinned
|
|
71
|
+
Django, not the full `dev` group, and skips `tests/e2e/` (no Playwright
|
|
72
|
+
in those envs). This only tests boundaries (oldest + newest Python per
|
|
73
|
+
Django series), not every valid combination — that catches most real
|
|
74
|
+
breakage while staying fast. Runs in CI as a separate
|
|
75
|
+
`compat-matrix.yml` workflow, alongside the regular `pytest.yml`. Update
|
|
76
|
+
the `env_list` here whenever `classifiers` gains or drops a Django
|
|
77
|
+
series.
|
|
78
|
+
|
|
79
|
+
## Lint / format / type-check
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
uv run ruff check .
|
|
83
|
+
uv run ruff format --check .
|
|
84
|
+
uv run mypy src example
|
|
85
|
+
# or, everything at once:
|
|
86
|
+
uv run pre-commit run --all-files
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The mypy pre-commit hook runs against the project venv (it needs
|
|
90
|
+
django-stubs to resolve model types), not an isolated pre-commit env.
|
|
91
|
+
|
|
92
|
+
## Before opening a PR
|
|
93
|
+
|
|
94
|
+
- [ ] `uv run pytest`
|
|
95
|
+
- [ ] `npm test`
|
|
96
|
+
- [ ] `uv run pre-commit run --all-files`
|
|
97
|
+
|
|
98
|
+
## Translations
|
|
99
|
+
|
|
100
|
+
Catalogs live in `src/django_form_matrix/locale/`. After changing
|
|
101
|
+
translatable text, from that package directory:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
cd src/django_form_matrix
|
|
105
|
+
DJANGO_SETTINGS_MODULE=tests.settings PYTHONPATH=../..:../../example \
|
|
106
|
+
uv run django-admin makemessages -l es --no-obsolete --add-location file --no-wrap
|
|
107
|
+
# translate the new/changed entries in locale/es/LC_MESSAGES/django.po, then:
|
|
108
|
+
DJANGO_SETTINGS_MODULE=tests.settings PYTHONPATH=../..:../../example \
|
|
109
|
+
uv run django-admin compilemessages
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Commit both the `.po` and the compiled `.mo`. `tests/test_i18n.py` fails if
|
|
113
|
+
an entry is untranslated or fuzzy, or if the `.mo` doesn't match the `.po`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rodolfo Becerra
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: django-form-matrix
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A form field to fill a table of rows and columns, with a form field in every cell (checkboxes, numbers, choices...), plus a ready-made permission matrix.
|
|
5
|
+
Project-URL: Homepage, https://github.com/rodolvbg/django-form-matrix
|
|
6
|
+
Project-URL: Issues, https://github.com/rodolvbg/django-form-matrix/issues
|
|
7
|
+
Project-URL: Repository, https://github.com/rodolvbg/django-form-matrix
|
|
8
|
+
Author-email: Rodolfo Valentín Becerra García <rodolvbg@gmail.com>
|
|
9
|
+
Maintainer-email: Rodolfo Valentín Becerra García <rodolvbg@gmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: admin,checkbox,django,form,grid,matrix,permissions,widget
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Web Environment
|
|
15
|
+
Classifier: Framework :: Django
|
|
16
|
+
Classifier: Framework :: Django :: 4.2
|
|
17
|
+
Classifier: Framework :: Django :: 5.0
|
|
18
|
+
Classifier: Framework :: Django :: 5.1
|
|
19
|
+
Classifier: Framework :: Django :: 5.2
|
|
20
|
+
Classifier: Intended Audience :: Developers
|
|
21
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
22
|
+
Classifier: Operating System :: OS Independent
|
|
23
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
28
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
29
|
+
Classifier: Typing :: Typed
|
|
30
|
+
Requires-Python: >=3.10
|
|
31
|
+
Requires-Dist: django>=4.2
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: coverage[toml]>=7.4; extra == 'dev'
|
|
34
|
+
Requires-Dist: django-stubs[compatible-mypy]>=5; extra == 'dev'
|
|
35
|
+
Requires-Dist: django-unfold>=0.60; extra == 'dev'
|
|
36
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
37
|
+
Requires-Dist: pre-commit>=4; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest-django>=4.8; extra == 'dev'
|
|
40
|
+
Requires-Dist: pytest-playwright>=0.5; extra == 'dev'
|
|
41
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
42
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
43
|
+
Requires-Dist: tox-uv>=1; extra == 'dev'
|
|
44
|
+
Requires-Dist: tox>=4; extra == 'dev'
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
|
|
47
|
+
# django-form-matrix
|
|
48
|
+
|
|
49
|
+
[](https://github.com/rodolvbg/django-form-matrix/actions/workflows/pytest.yml)
|
|
50
|
+
[](https://pypi.org/project/django-form-matrix/)
|
|
51
|
+
[](https://pypi.org/project/django-form-matrix/)
|
|
52
|
+
[](https://pypi.org/project/django-form-matrix/)
|
|
53
|
+
[](https://pepy.tech/project/django-form-matrix)
|
|
54
|
+
|
|
55
|
+
A Django form field to fill a **table**: one row per item, one column per property and, in every cell, a form field of its own — a checkbox, a number, a choice, text. Rows can be grouped in collapsible sections, and checkbox columns get shortcuts to check a row, a column or a whole section.
|
|
56
|
+
|
|
57
|
+
It works in any Django form and in the admin. A ready-made **permission matrix** (apps → models → add/view/change/delete) is included as a use case.
|
|
58
|
+
|
|
59
|
+

|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install django-form-matrix
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Add `django_form_matrix` to `INSTALLED_APPS` (it ships a template, a script and a stylesheet):
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
INSTALLED_APPS = [
|
|
71
|
+
# ...
|
|
72
|
+
"django_form_matrix",
|
|
73
|
+
]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Quick start
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from django import forms
|
|
80
|
+
from django_form_matrix import Column, MatrixField, Row
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class PlanForm(forms.Form):
|
|
84
|
+
features = MatrixField(
|
|
85
|
+
rows=[
|
|
86
|
+
Row("invoices", "Invoices", section="Billing"),
|
|
87
|
+
Row("quotes", "Quotes", section="Billing"),
|
|
88
|
+
Row("exports", "Exports", section="Analytics", columns=["enabled"]),
|
|
89
|
+
],
|
|
90
|
+
columns=[
|
|
91
|
+
Column("enabled", "Enabled"), # a checkbox unless said otherwise
|
|
92
|
+
Column(
|
|
93
|
+
"limit",
|
|
94
|
+
"Monthly limit",
|
|
95
|
+
forms.IntegerField(min_value=0, required=False),
|
|
96
|
+
),
|
|
97
|
+
],
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Render the form as usual and add `{{ form.media }}` (the admin does it for you). The cleaned value has one entry per row with the cells that exist:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
form.cleaned_data["features"]
|
|
105
|
+
# {"invoices": {"enabled": True, "limit": 50},
|
|
106
|
+
# "quotes": {"enabled": False, "limit": None},
|
|
107
|
+
# "exports": {"enabled": False}}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
That is what a `JSONField` stores, so a `ModelForm` can save it directly.
|
|
111
|
+
|
|
112
|
+
### Rows and columns
|
|
113
|
+
|
|
114
|
+
- `Column(key, label, field=BooleanField(required=False))`: the form field of the column. Its validation and its widget are Django's own, and a failing cell is reported with its row and column ("Invoices, Monthly limit: …").
|
|
115
|
+
- `Row(key, label, section=None, columns=None)`: `section` groups rows under a collapsible heading (rows without one go in a plain table); `columns` are the keys of the columns that have a cell in this row, the rest show a dash.
|
|
116
|
+
- `MatrixField(rows=…, columns=…)` is not required by default; with `required=True` at least one cell needs a value. `rows` and `columns` can be reassigned in a form's `__init__` to build the matrix dynamically.
|
|
117
|
+
|
|
118
|
+
Each cell is submitted as `<name>__<row key>__<column key>`, so it reads like any other widget.
|
|
119
|
+
|
|
120
|
+
### Shortcuts
|
|
121
|
+
|
|
122
|
+
Checkbox columns get a checkbox in their heading, every row with checkboxes gets one at its start, and each section gets an "All". They show whether their cells are all, some or none checked, and the heading of a section counts the checked ones. A section opens by itself when something in it is filled.
|
|
123
|
+
|
|
124
|
+
## Permissions
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from django_form_matrix.contrib.permissions import PermissionMultipleChoiceField
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class RoleAdmin(admin.ModelAdmin):
|
|
131
|
+
def formfield_for_manytomany(self, db_field, request, **kwargs):
|
|
132
|
+
if db_field.name == "permissions":
|
|
133
|
+
kwargs["form_class"] = PermissionMultipleChoiceField
|
|
134
|
+
return super().formfield_for_manytomany(db_field, request, **kwargs)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+

|
|
138
|
+
|
|
139
|
+
One section per app, one row per model and a column per action; the other permissions of a model go in an "Others" column. It submits like a `CheckboxSelectMultiple`, so validation and saving do not change, and the names of the permissions are shown translated. Needs `django.contrib.auth`. [docs/examples/permissions.md](https://github.com/rodolvbg/django-form-matrix/blob/HEAD/docs/examples/permissions.md) shows how to build the same matrix by hand with `MatrixField`, with rows that come from the database, for when you need more control.
|
|
140
|
+
|
|
141
|
+
## Customizing the HTML
|
|
142
|
+
|
|
143
|
+
Both templates are split in blocks, so a template of yours can extend one and change only a piece, without copying the rest:
|
|
144
|
+
|
|
145
|
+
```django
|
|
146
|
+
{% extends "django_form_matrix/matrix.html" %}
|
|
147
|
+
{% block section_summary %}<strong>{{ section.name }}</strong>{% endblock %}
|
|
148
|
+
{% block cell %}<div class="my-cell">{{ cell.html }}</div>{% endblock %}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Point a widget to it with `template_name`:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
class MyMatrixWidget(MatrixWidget):
|
|
155
|
+
template_name = "myapp/my_matrix.html"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
| Block | `matrix.html` | `permissions.html` | What it is |
|
|
159
|
+
| --- | --- | --- | --- |
|
|
160
|
+
| `section_summary` | ✓ | ✓ | The heading of a section (its name and count) |
|
|
161
|
+
| `section_toggle` | ✓ | ✓ | The "All" checkbox of a section |
|
|
162
|
+
| `column_heading` | ✓ | ✓ | The heading of a column |
|
|
163
|
+
| `others_heading` | | ✓ | The heading of the "Others" column |
|
|
164
|
+
| `row_heading` | ✓ | ✓ | The heading of a row |
|
|
165
|
+
| `cell` | ✓ | | The widget of a cell (`{{ cell.html }}`) |
|
|
166
|
+
| `permission` | | ✓ | The checkbox of a permission |
|
|
167
|
+
| `other_permission` | | ✓ | A permission of the "Others" column |
|
|
168
|
+
| `missing_cell` | ✓ | ✓ | A cell a row does not have |
|
|
169
|
+
| `empty` | ✓ | ✓ | What shows when there is nothing to fill |
|
|
170
|
+
|
|
171
|
+
`{{ block.super }}` keeps the original of a block. The classes (`.form-matrix-*`) and the `data-form-matrix-*` attributes are what the stylesheet and the script look for: keep them in your blocks and the shortcuts keep working.
|
|
172
|
+
|
|
173
|
+
Django looks for the widget templates of a form in the `templates/` of the installed apps, **not** in the `DIRS` of your project. To override `django_form_matrix/matrix.html` itself by name from the project's templates, set `FORM_RENDERER = "django.forms.renderers.TemplatesSetting"`, or put the template in an app listed before `django_form_matrix` in `INSTALLED_APPS`. Extending it with your own `template_name`, as above, needs neither.
|
|
174
|
+
|
|
175
|
+
## Styling
|
|
176
|
+
|
|
177
|
+
The stylesheet uses the colours of the Django admin when it is there. Anywhere else, set `--form-matrix-border`, `--form-matrix-heading-bg` and `--form-matrix-quiet` on `.form-matrix`.
|
|
178
|
+
|
|
179
|
+
### Unfold
|
|
180
|
+
|
|
181
|
+
[django-unfold](https://github.com/unfoldadmin/django-unfold) does not define the variables of Django's admin, so add `UnfoldMatrixMixin` to the admin: it puts the matrix on Unfold's palette, light and dark, and styles the number, text and select cells like Unfold's own inputs.
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from unfold.admin import ModelAdmin
|
|
185
|
+
from django_form_matrix.contrib.unfold import UnfoldMatrixMixin
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@admin.register(Plan)
|
|
189
|
+
class PlanAdmin(UnfoldMatrixMixin, ModelAdmin):
|
|
190
|
+
form = PlanForm
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+

|
|
194
|
+
|
|
195
|
+
The permission matrix, the use case this library started from, in Unfold:
|
|
196
|
+
|
|
197
|
+

|
|
198
|
+
|
|
199
|
+
And in Unfold's dark mode:
|
|
200
|
+
|
|
201
|
+

|
|
202
|
+
|
|
203
|
+
## Translations
|
|
204
|
+
|
|
205
|
+
Ships a Spanish (`es`) translation: the matrix's headings ("Add", "View",
|
|
206
|
+
"Change", "Delete", "Others"), the permissions' names ("Can view plan") and
|
|
207
|
+
its messages follow the active language, like the rest of the admin.
|
|
208
|
+
|
|
209
|
+
## Development
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
uv sync && npm install
|
|
213
|
+
uv run pytest # Python + end-to-end (needs `uv run playwright install chromium`)
|
|
214
|
+
npm test # JS
|
|
215
|
+
cd example && uv run python manage.py runserver # demo: Plans and Roles in the admin
|
|
216
|
+
DJANGO_SETTINGS_MODULE=example.settings_unfold uv run python manage.py runserver # the same, in Unfold
|
|
217
|
+
```
|