django-adr 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_adr-0.1.0/.github/workflows/ci.yml +92 -0
- django_adr-0.1.0/.gitignore +36 -0
- django_adr-0.1.0/.pre-commit-config.yaml +60 -0
- django_adr-0.1.0/CHANGELOG.md +25 -0
- django_adr-0.1.0/LICENSE +21 -0
- django_adr-0.1.0/Makefile +46 -0
- django_adr-0.1.0/PKG-INFO +176 -0
- django_adr-0.1.0/README.md +148 -0
- django_adr-0.1.0/django_adr/__init__.py +1 -0
- django_adr-0.1.0/django_adr/admin.py +22 -0
- django_adr-0.1.0/django_adr/api.py +14 -0
- django_adr-0.1.0/django_adr/apps.py +11 -0
- django_adr-0.1.0/django_adr/locale/en/LC_MESSAGES/django.mo +0 -0
- django_adr-0.1.0/django_adr/locale/en/LC_MESSAGES/django.po +147 -0
- django_adr-0.1.0/django_adr/management/__init__.py +1 -0
- django_adr-0.1.0/django_adr/management/commands/__init__.py +1 -0
- django_adr-0.1.0/django_adr/management/commands/create_adr.py +73 -0
- django_adr-0.1.0/django_adr/management/commands/export_adrs.py +63 -0
- django_adr-0.1.0/django_adr/migrations/0001_initial.py +67 -0
- django_adr-0.1.0/django_adr/migrations/__init__.py +0 -0
- django_adr-0.1.0/django_adr/models.py +88 -0
- django_adr-0.1.0/django_adr/serializers.py +37 -0
- django_adr-0.1.0/django_adr/templates/django_adr/adr_detail.html +35 -0
- django_adr-0.1.0/django_adr/templates/django_adr/adr_list.html +40 -0
- django_adr-0.1.0/django_adr/templates/django_adr/base_django_adr.html +12 -0
- django_adr-0.1.0/django_adr/urls.py +18 -0
- django_adr-0.1.0/django_adr/utils.py +10 -0
- django_adr-0.1.0/django_adr/views.py +49 -0
- django_adr-0.1.0/manage.py +17 -0
- django_adr-0.1.0/pyproject.toml +101 -0
- django_adr-0.1.0/tests/__init__.py +1 -0
- django_adr-0.1.0/tests/settings.py +41 -0
- django_adr-0.1.0/tests/test_admin.py +47 -0
- django_adr-0.1.0/tests/test_api.py +100 -0
- django_adr-0.1.0/tests/test_commands.py +88 -0
- django_adr-0.1.0/tests/test_export.py +55 -0
- django_adr-0.1.0/tests/test_models.py +159 -0
- django_adr-0.1.0/tests/test_views.py +123 -0
- django_adr-0.1.0/tests/urls.py +9 -0
- django_adr-0.1.0/uv.lock +334 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
concurrency:
|
|
8
|
+
group: ci-${{ github.ref }}
|
|
9
|
+
cancel-in-progress: true
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
validate:
|
|
13
|
+
name: Validate
|
|
14
|
+
runs-on: ubuntu-24.04
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout
|
|
17
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
18
|
+
|
|
19
|
+
- name: Validate
|
|
20
|
+
uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
|
|
21
|
+
|
|
22
|
+
test:
|
|
23
|
+
name: Test
|
|
24
|
+
runs-on: ubuntu-24.04
|
|
25
|
+
needs: [validate]
|
|
26
|
+
|
|
27
|
+
steps:
|
|
28
|
+
- name: Checkout
|
|
29
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
30
|
+
|
|
31
|
+
- name: Install uv
|
|
32
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
33
|
+
with:
|
|
34
|
+
enable-cache: true
|
|
35
|
+
python-version: "3.12"
|
|
36
|
+
|
|
37
|
+
- name: Install dependencies
|
|
38
|
+
run: uv sync --frozen --group test
|
|
39
|
+
|
|
40
|
+
- name: Test
|
|
41
|
+
run: |
|
|
42
|
+
uv run coverage run manage.py test tests --buffer --durations 10 --noinput --parallel --shuffle --timing
|
|
43
|
+
uv run coverage combine
|
|
44
|
+
uv run coverage report
|
|
45
|
+
|
|
46
|
+
build:
|
|
47
|
+
name: Build
|
|
48
|
+
runs-on: ubuntu-24.04
|
|
49
|
+
needs: [validate, test]
|
|
50
|
+
|
|
51
|
+
steps:
|
|
52
|
+
- name: Checkout
|
|
53
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
54
|
+
|
|
55
|
+
- name: Install uv
|
|
56
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
57
|
+
with:
|
|
58
|
+
enable-cache: true
|
|
59
|
+
|
|
60
|
+
- name: Build distributions
|
|
61
|
+
run: |
|
|
62
|
+
uv build
|
|
63
|
+
ls dist/
|
|
64
|
+
|
|
65
|
+
- name: Upload distributions
|
|
66
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
67
|
+
with:
|
|
68
|
+
name: dist
|
|
69
|
+
path: dist/
|
|
70
|
+
|
|
71
|
+
publish:
|
|
72
|
+
name: Publish to PyPI
|
|
73
|
+
runs-on: ubuntu-24.04
|
|
74
|
+
needs: [build]
|
|
75
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
76
|
+
environment: release
|
|
77
|
+
|
|
78
|
+
permissions:
|
|
79
|
+
id-token: write
|
|
80
|
+
|
|
81
|
+
steps:
|
|
82
|
+
- name: Download distributions
|
|
83
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
84
|
+
with:
|
|
85
|
+
name: dist
|
|
86
|
+
path: dist/
|
|
87
|
+
|
|
88
|
+
- name: Install uv
|
|
89
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
90
|
+
|
|
91
|
+
- name: Publish to PyPI
|
|
92
|
+
run: uv publish
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.egg
|
|
11
|
+
|
|
12
|
+
# Environments
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
env/
|
|
16
|
+
|
|
17
|
+
# Unit test / coverage reports
|
|
18
|
+
.coverage*
|
|
19
|
+
.coverages/
|
|
20
|
+
.pytest_cache/
|
|
21
|
+
htmlcov/
|
|
22
|
+
*.cover
|
|
23
|
+
|
|
24
|
+
# Django
|
|
25
|
+
*.log
|
|
26
|
+
db.sqlite3
|
|
27
|
+
|
|
28
|
+
# Ruff
|
|
29
|
+
.ruff_cache/
|
|
30
|
+
|
|
31
|
+
# macOS
|
|
32
|
+
.DS_Store
|
|
33
|
+
|
|
34
|
+
# IDEs
|
|
35
|
+
.idea/
|
|
36
|
+
.vscode/
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
default_language_version:
|
|
2
|
+
python: python3.12
|
|
3
|
+
repos:
|
|
4
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
5
|
+
rev: v6.0.0
|
|
6
|
+
hooks:
|
|
7
|
+
- id: check-added-large-files
|
|
8
|
+
args: ["--maxkb=1024"]
|
|
9
|
+
- id: check-case-conflict
|
|
10
|
+
- id: check-docstring-first
|
|
11
|
+
- id: check-json
|
|
12
|
+
- id: check-merge-conflict
|
|
13
|
+
- id: check-symlinks
|
|
14
|
+
- id: check-toml
|
|
15
|
+
- id: check-yaml
|
|
16
|
+
args: ["--allow-multiple-documents"]
|
|
17
|
+
- id: debug-statements
|
|
18
|
+
- id: detect-private-key
|
|
19
|
+
- id: end-of-file-fixer
|
|
20
|
+
- id: fix-byte-order-marker
|
|
21
|
+
- id: forbid-new-submodules
|
|
22
|
+
- id: mixed-line-ending
|
|
23
|
+
- id: trailing-whitespace
|
|
24
|
+
- repo: https://github.com/gitleaks/gitleaks
|
|
25
|
+
rev: v8.30.1
|
|
26
|
+
hooks:
|
|
27
|
+
- id: gitleaks
|
|
28
|
+
- repo: https://github.com/adhtruong/mirrors-typos
|
|
29
|
+
rev: v1.47.0
|
|
30
|
+
hooks:
|
|
31
|
+
- id: typos
|
|
32
|
+
- repo: https://github.com/asottile/pyupgrade
|
|
33
|
+
rev: v3.21.2
|
|
34
|
+
hooks:
|
|
35
|
+
- id: pyupgrade
|
|
36
|
+
args: [--py312-plus]
|
|
37
|
+
- repo: https://github.com/adamchainz/django-upgrade
|
|
38
|
+
rev: 1.30.0
|
|
39
|
+
hooks:
|
|
40
|
+
- id: django-upgrade
|
|
41
|
+
args: [--target-version, "4.2"]
|
|
42
|
+
- repo: https://github.com/UnknownPlatypus/djangofmt-pre-commit
|
|
43
|
+
rev: v0.2.9
|
|
44
|
+
hooks:
|
|
45
|
+
- id: djangofmt
|
|
46
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
47
|
+
rev: v0.15.15
|
|
48
|
+
hooks:
|
|
49
|
+
- id: ruff
|
|
50
|
+
args: [--fix]
|
|
51
|
+
- id: ruff-format
|
|
52
|
+
- repo: https://github.com/tox-dev/pyproject-fmt
|
|
53
|
+
rev: v2.23.0
|
|
54
|
+
hooks:
|
|
55
|
+
- id: pyproject-fmt
|
|
56
|
+
- repo: https://github.com/pycqa/bandit
|
|
57
|
+
rev: 1.9.4
|
|
58
|
+
hooks:
|
|
59
|
+
- id: bandit
|
|
60
|
+
args: ["-c", "pyproject.toml"]
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `ADR` model with auto-assigned sequential `number`, `title`, `status`, `date`, `context`, `decision`, `consequences`, and `superseded_by` self-reference
|
|
13
|
+
- Status lifecycle: `proposed`, `accepted`, `deprecated`, `superseded`, `rejected`
|
|
14
|
+
- `clean()` validation enforcing consistency between `status` and `superseded_by`
|
|
15
|
+
- Markdown rendering for `context`, `decision`, and `consequences` via `mistune`
|
|
16
|
+
- `status` filter on HTML list view (`?status=`)
|
|
17
|
+
- Django admin interface with custom fieldsets
|
|
18
|
+
- Read-only REST API (`djangorestframework`) with `superseded_by` exposed as ADR number
|
|
19
|
+
- Management command `create_adr` with optional `--supersedes` flag for atomic supersession
|
|
20
|
+
- Management command `export_adrs` to export ADRs as Markdown files
|
|
21
|
+
- Internationalization support (i18n) with English translations included
|
|
22
|
+
- GitHub Actions CI: `validate` → `build` on every push; `publish` to PyPI on `v*` tags via OIDC Trusted Publisher
|
|
23
|
+
- `uv` for dependency management and task running
|
|
24
|
+
- `Makefile` targets: `help`, `install`, `messages`, `migrate`, `migrations`, `showoutdated`, `test`, `update`, `validate`
|
|
25
|
+
- Pre-commit hooks: `ruff`, `ruff-format`, `pyupgrade`, `django-upgrade`, `djangofmt`, `pyproject-fmt`, `bandit`, `gitleaks`, `typos`
|
django_adr-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Niccolò Mineo
|
|
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,46 @@
|
|
|
1
|
+
.DEFAULT_GOAL := help
|
|
2
|
+
|
|
3
|
+
.PHONY: install
|
|
4
|
+
install: ## Install all dev dependencies
|
|
5
|
+
uv sync --group dev
|
|
6
|
+
|
|
7
|
+
.PHONY: help
|
|
8
|
+
help: ## Show this help
|
|
9
|
+
@echo "[Help] Makefile list commands:"
|
|
10
|
+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
|
11
|
+
|
|
12
|
+
.PHONY: messages
|
|
13
|
+
messages: ## Extract and compile i18n translation strings
|
|
14
|
+
uv run manage.py makemessages --add-location file --ignore .venv --locale en $(ARGS)
|
|
15
|
+
uv run manage.py compilemessages --ignore .venv $(ARGS)
|
|
16
|
+
|
|
17
|
+
.PHONY: migrate
|
|
18
|
+
migrate: ## Apply database migrations
|
|
19
|
+
uv run manage.py migrate --noinput $(ARGS)
|
|
20
|
+
|
|
21
|
+
.PHONY: migrations
|
|
22
|
+
migrations: ## Generate new Django migration files
|
|
23
|
+
uv run manage.py makemigrations --no-header $(ARGS)
|
|
24
|
+
|
|
25
|
+
.PHONY: showoutdated
|
|
26
|
+
showoutdated: ## Show outdated dependencies (Python, prek)
|
|
27
|
+
uv tree --all-groups --outdated | grep --color=always "(latest:.*)" || true
|
|
28
|
+
uv run prek auto-update --dry-run
|
|
29
|
+
|
|
30
|
+
.PHONY: test
|
|
31
|
+
test: ## Run tests with coverage
|
|
32
|
+
uv run coverage run manage.py test tests --buffer --durations 10 --noinput --parallel --shuffle --timing
|
|
33
|
+
uv run coverage combine
|
|
34
|
+
uv run coverage html
|
|
35
|
+
uv run coverage report
|
|
36
|
+
|
|
37
|
+
.PHONY: update
|
|
38
|
+
update: ## Update dependencies, pre-commit hooks and GitHub Actions versions
|
|
39
|
+
uv lock --upgrade
|
|
40
|
+
uv sync --group dev
|
|
41
|
+
uv run prek autoupdate
|
|
42
|
+
gha-update
|
|
43
|
+
|
|
44
|
+
.PHONY: validate
|
|
45
|
+
validate: ## Run pre-commit hooks on all files
|
|
46
|
+
uv run prek run --all-files
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-adr
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Django reusable package to manage Architectural Decision Records.
|
|
5
|
+
Project-URL: homepage, https://niccolomineo.com
|
|
6
|
+
Author: Niccolò Mineo
|
|
7
|
+
License: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Environment :: Web Environment
|
|
11
|
+
Classifier: Framework :: Django
|
|
12
|
+
Classifier: Framework :: Django :: 4.2
|
|
13
|
+
Classifier: Framework :: Django :: 5.0
|
|
14
|
+
Classifier: Framework :: Django :: 5.1
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
22
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Requires-Dist: django>=4.2
|
|
25
|
+
Requires-Dist: djangorestframework>=3.14
|
|
26
|
+
Requires-Dist: mistune>=3
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# django-adr
|
|
30
|
+
|
|
31
|
+
A Django reusable package to manage **[Architectural Decision Records (ADR)](https://niccolomineo.com/articles/django-architectural-decisions/)**.
|
|
32
|
+
|
|
33
|
+
> **Warning:** This package is not production ready. APIs and data models may change between versions.
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- Django Admin interface to create and manage ADRs
|
|
38
|
+
- HTML list and detail views with status filtering
|
|
39
|
+
- Read-only REST API (Django REST Framework) with pre-rendered Markdown HTML fields
|
|
40
|
+
- Management command `create_adr` to create ADRs from the CLI, with optional `--supersedes` to mark an existing ADR as superseded in one step
|
|
41
|
+
- Management command `export_adrs` to export all ADRs as Markdown files
|
|
42
|
+
- Markdown support for context, decision, and consequences fields
|
|
43
|
+
- Internationalization (i18n) support — English locale included
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install django-adr
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Add to `INSTALLED_APPS`:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
INSTALLED_APPS = [
|
|
55
|
+
...
|
|
56
|
+
"django_adr",
|
|
57
|
+
]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Include the URLs:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from django.urls import include, path
|
|
64
|
+
|
|
65
|
+
urlpatterns = [
|
|
66
|
+
...
|
|
67
|
+
path("adrs/", include("django_adr.urls", namespace="django_adr")),
|
|
68
|
+
]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Run migrations:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
python manage.py migrate
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Usage
|
|
78
|
+
|
|
79
|
+
### Admin
|
|
80
|
+
|
|
81
|
+
Visit `/admin/django_adr/adr/` to manage ADRs through the Django admin.
|
|
82
|
+
|
|
83
|
+
### HTML views
|
|
84
|
+
|
|
85
|
+
- `/adrs/` — list all ADRs
|
|
86
|
+
- `/adrs/?status=accepted` — filter by status
|
|
87
|
+
- `/adrs/<number>/` — view a single ADR
|
|
88
|
+
|
|
89
|
+
### REST API
|
|
90
|
+
|
|
91
|
+
- `GET /adrs/api/adrs/` — list all ADRs
|
|
92
|
+
- `GET /adrs/api/adrs/<number>/` — retrieve a single ADR
|
|
93
|
+
|
|
94
|
+
### Management commands
|
|
95
|
+
|
|
96
|
+
Create a new ADR:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
python manage.py create_adr "Use PostgreSQL" \
|
|
100
|
+
--context="We need a relational database." \
|
|
101
|
+
--decision="Use PostgreSQL." \
|
|
102
|
+
--consequences="Team must know SQL."
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Create a new ADR and supersede an existing one in a single step:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
python manage.py create_adr "Use CockroachDB" --supersedes=3
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
This creates the new ADR and automatically marks ADR-0003 as `Superseded`.
|
|
112
|
+
|
|
113
|
+
Export all ADRs as Markdown files:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
python manage.py export_adrs --output-dir=docs/adr
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Each ADR is written to `<output-dir>/<number>-<slug>.md`.
|
|
120
|
+
|
|
121
|
+
## ADR statuses
|
|
122
|
+
|
|
123
|
+
| Status | Description |
|
|
124
|
+
|--------|-------------|
|
|
125
|
+
| `proposed` | Under discussion |
|
|
126
|
+
| `accepted` | Agreed and in effect |
|
|
127
|
+
| `deprecated` | No longer relevant |
|
|
128
|
+
| `superseded` | Replaced by a newer ADR |
|
|
129
|
+
| `rejected` | Considered and not adopted |
|
|
130
|
+
|
|
131
|
+
## Protecting the views
|
|
132
|
+
|
|
133
|
+
The HTML views and REST API are public by default. The package does not enforce any authentication strategy — that is left to the host project.
|
|
134
|
+
|
|
135
|
+
**HTML views** — in `urls.py`, wrap the URL include with `login_required`:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from django.contrib.auth.decorators import login_required
|
|
139
|
+
from django.urls import include, path
|
|
140
|
+
|
|
141
|
+
urlpatterns = [
|
|
142
|
+
path("adrs/", login_required(include("django_adr.urls", namespace="django_adr"))),
|
|
143
|
+
]
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**REST API** — set `DEFAULT_PERMISSION_CLASSES` in `settings.py`:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
REST_FRAMEWORK = {
|
|
150
|
+
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Or scope it to the ADR router only by subclassing `ADRViewSet`:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from django_adr.api import ADRViewSet
|
|
158
|
+
from rest_framework.permissions import IsAuthenticated
|
|
159
|
+
|
|
160
|
+
class ProtectedADRViewSet(ADRViewSet):
|
|
161
|
+
permission_classes = [IsAuthenticated]
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Translations
|
|
165
|
+
|
|
166
|
+
All user-facing strings are translatable. The package ships with an English locale. To generate translations for your project:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
python manage.py makemessages -l it
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Ensure `USE_I18N = True` in your project settings.
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# django-adr
|
|
2
|
+
|
|
3
|
+
A Django reusable package to manage **[Architectural Decision Records (ADR)](https://niccolomineo.com/articles/django-architectural-decisions/)**.
|
|
4
|
+
|
|
5
|
+
> **Warning:** This package is not production ready. APIs and data models may change between versions.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Django Admin interface to create and manage ADRs
|
|
10
|
+
- HTML list and detail views with status filtering
|
|
11
|
+
- Read-only REST API (Django REST Framework) with pre-rendered Markdown HTML fields
|
|
12
|
+
- Management command `create_adr` to create ADRs from the CLI, with optional `--supersedes` to mark an existing ADR as superseded in one step
|
|
13
|
+
- Management command `export_adrs` to export all ADRs as Markdown files
|
|
14
|
+
- Markdown support for context, decision, and consequences fields
|
|
15
|
+
- Internationalization (i18n) support — English locale included
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install django-adr
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Add to `INSTALLED_APPS`:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
INSTALLED_APPS = [
|
|
27
|
+
...
|
|
28
|
+
"django_adr",
|
|
29
|
+
]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Include the URLs:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from django.urls import include, path
|
|
36
|
+
|
|
37
|
+
urlpatterns = [
|
|
38
|
+
...
|
|
39
|
+
path("adrs/", include("django_adr.urls", namespace="django_adr")),
|
|
40
|
+
]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Run migrations:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
python manage.py migrate
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
### Admin
|
|
52
|
+
|
|
53
|
+
Visit `/admin/django_adr/adr/` to manage ADRs through the Django admin.
|
|
54
|
+
|
|
55
|
+
### HTML views
|
|
56
|
+
|
|
57
|
+
- `/adrs/` — list all ADRs
|
|
58
|
+
- `/adrs/?status=accepted` — filter by status
|
|
59
|
+
- `/adrs/<number>/` — view a single ADR
|
|
60
|
+
|
|
61
|
+
### REST API
|
|
62
|
+
|
|
63
|
+
- `GET /adrs/api/adrs/` — list all ADRs
|
|
64
|
+
- `GET /adrs/api/adrs/<number>/` — retrieve a single ADR
|
|
65
|
+
|
|
66
|
+
### Management commands
|
|
67
|
+
|
|
68
|
+
Create a new ADR:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python manage.py create_adr "Use PostgreSQL" \
|
|
72
|
+
--context="We need a relational database." \
|
|
73
|
+
--decision="Use PostgreSQL." \
|
|
74
|
+
--consequences="Team must know SQL."
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Create a new ADR and supersede an existing one in a single step:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
python manage.py create_adr "Use CockroachDB" --supersedes=3
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This creates the new ADR and automatically marks ADR-0003 as `Superseded`.
|
|
84
|
+
|
|
85
|
+
Export all ADRs as Markdown files:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
python manage.py export_adrs --output-dir=docs/adr
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Each ADR is written to `<output-dir>/<number>-<slug>.md`.
|
|
92
|
+
|
|
93
|
+
## ADR statuses
|
|
94
|
+
|
|
95
|
+
| Status | Description |
|
|
96
|
+
|--------|-------------|
|
|
97
|
+
| `proposed` | Under discussion |
|
|
98
|
+
| `accepted` | Agreed and in effect |
|
|
99
|
+
| `deprecated` | No longer relevant |
|
|
100
|
+
| `superseded` | Replaced by a newer ADR |
|
|
101
|
+
| `rejected` | Considered and not adopted |
|
|
102
|
+
|
|
103
|
+
## Protecting the views
|
|
104
|
+
|
|
105
|
+
The HTML views and REST API are public by default. The package does not enforce any authentication strategy — that is left to the host project.
|
|
106
|
+
|
|
107
|
+
**HTML views** — in `urls.py`, wrap the URL include with `login_required`:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from django.contrib.auth.decorators import login_required
|
|
111
|
+
from django.urls import include, path
|
|
112
|
+
|
|
113
|
+
urlpatterns = [
|
|
114
|
+
path("adrs/", login_required(include("django_adr.urls", namespace="django_adr"))),
|
|
115
|
+
]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**REST API** — set `DEFAULT_PERMISSION_CLASSES` in `settings.py`:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
REST_FRAMEWORK = {
|
|
122
|
+
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Or scope it to the ADR router only by subclassing `ADRViewSet`:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from django_adr.api import ADRViewSet
|
|
130
|
+
from rest_framework.permissions import IsAuthenticated
|
|
131
|
+
|
|
132
|
+
class ProtectedADRViewSet(ADRViewSet):
|
|
133
|
+
permission_classes = [IsAuthenticated]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Translations
|
|
137
|
+
|
|
138
|
+
All user-facing strings are translatable. The package ships with an English locale. To generate translations for your project:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
python manage.py makemessages -l it
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Ensure `USE_I18N = True` in your project settings.
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Django ADR — a package to manage Architectural Decision Records."""
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Django admin configuration for ADRs."""
|
|
2
|
+
|
|
3
|
+
from django.contrib import admin
|
|
4
|
+
from django.utils.translation import gettext_lazy as _
|
|
5
|
+
|
|
6
|
+
from django_adr.models import ADR
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@admin.register(ADR)
|
|
10
|
+
class ADRAdmin(admin.ModelAdmin):
|
|
11
|
+
"""Admin interface for ADR."""
|
|
12
|
+
|
|
13
|
+
list_display = ("number", "title", "status", "date")
|
|
14
|
+
list_filter = ("status",)
|
|
15
|
+
search_fields = ("title", "context", "decision", "consequences")
|
|
16
|
+
readonly_fields = ("number", "date")
|
|
17
|
+
fieldsets = (
|
|
18
|
+
(None, {"fields": ("number", "title", "status", "date", "superseded_by")}),
|
|
19
|
+
(_("Context"), {"fields": ("context",)}),
|
|
20
|
+
(_("Decision"), {"fields": ("decision",)}),
|
|
21
|
+
(_("Consequences"), {"fields": ("consequences",)}),
|
|
22
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""DRF ViewSets for ADRs."""
|
|
2
|
+
|
|
3
|
+
from rest_framework.viewsets import ReadOnlyModelViewSet
|
|
4
|
+
|
|
5
|
+
from django_adr.models import ADR
|
|
6
|
+
from django_adr.serializers import ADRSerializer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ADRViewSet(ReadOnlyModelViewSet):
|
|
10
|
+
"""Provide read-only API access to ADRs."""
|
|
11
|
+
|
|
12
|
+
queryset = ADR.objects.select_related("superseded_by").all()
|
|
13
|
+
serializer_class = ADRSerializer
|
|
14
|
+
lookup_field = "number"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Django ADR application configuration."""
|
|
2
|
+
|
|
3
|
+
from django.apps import AppConfig
|
|
4
|
+
from django.utils.translation import gettext_lazy as _
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DjangoAdrConfig(AppConfig):
|
|
8
|
+
"""Django ADR application configuration."""
|
|
9
|
+
|
|
10
|
+
name = "django_adr"
|
|
11
|
+
verbose_name = _("Architectural Decision Records")
|
|
Binary file
|