A CLI tool to detect architectural problems in Django projects.
Project description
django-arch-check
A command-line architectural health checker for Django projects.
It scans source code statically and flags structural issues before they become entrenched technical debt:
- Fat models
- God apps
- Circular imports
- Missing service layer boundaries
- Celery tasks without retry
- Direct SQL usage
- N+1 query risks
Analyzing: /home/user/myproject
── Fat Models ──────────────────────────────
[CRITICAL] core/models.py → UserProfile (34 methods)
Found 1 fat model(s).
── God Apps ────────────────────────────────
[WARNING] core/ owns 41% of total project code (860 / 2,102 lines)
Found 1 god app(s).
── Circular Imports ────────────────────────
No circular imports found.
── Missing Service Layer ────────────────────
[WARNING] orders/views.py → create_order() makes direct ORM calls
Found 1 missing service layer issue(s).
── Celery Tasks Without Retry ──────────────
[CRITICAL] payments/tasks.py → send_invoice_email() — high-stakes task, no retry configured
Found 1 Celery task(s) without retry.
Why
Django projects often drift toward the same architectural problems:
- Models absorb business logic until they become hard to reason about
- One app quietly turns into the center of the codebase
- View functions talk directly to the ORM and blur boundaries
- Celery tasks lose work because retries were never configured
- Circular imports pile up as module boundaries erode
These issues are easy to normalize and hard to see in code review. django-arch-check makes them visible early, in local development and in CI.
Installation
pip install django-arch-check
Requirements:
- Python 3.11+
- No Django runtime setup required
The tool is static-only: it reads source files, parses ASTs, and never imports your project code.
Quick Start
# Analyze a project and print findings to the terminal
django-arch-check analyze /path/to/project
# Generate a self-contained HTML report
django-arch-check analyze --format html /path/to/project
# Tune thresholds
django-arch-check analyze \
--fat-model-threshold 20 \
--god-app-threshold 40 \
/path/to/project
Ignore Detectors
Use --ignore to skip one or more detectors entirely.
django-arch-check analyze --ignore fat_models /path/to/project
django-arch-check analyze --ignore fat_models --ignore god_apps /path/to/project
Valid detector names:
fat_modelsgod_appscircular_importsmissing_service_layercelery_tasksdirect_sqln_plus_one
If an invalid detector name is passed, the CLI exits with a clear error:
Error: Unknown detector 'fat_modelz'. Valid detectors are: fat_models, god_apps, circular_imports, missing_service_layer, celery_tasks, direct_sql, n_plus_one
In HTML reports, skipped detectors are shown as:
⊘ Skipped (--ignore flag)
Ignore Paths
Use --ignore-path to skip files whose relative path contains a given substring.
django-arch-check analyze --ignore-path legacy/ /path/to/project
django-arch-check analyze --ignore-path legacy/ --ignore-path archive/ /path/to/project
This is applied across all detectors. If a file path contains the ignored string, that file is not analyzed.
Examples:
--ignore-path legacy/skips files under paths likelegacy/models.py--ignore-path archive/skips files under paths likeapps/orders/archive/tasks.py
Path ignores are substring-based, not glob-based.
CLI Options
Current django-arch-check analyze --help output:
Usage: main analyze [OPTIONS] PROJECT_PATH
Analyze a Django project at PROJECT_PATH for architectural issues.
Options:
--fat-model-threshold N Flag models with >= N non-dunder methods. [default:
15]
--god-app-threshold PCT Flag apps owning >= PCT% of total project LOC.
[default: 30]
--ignore DETECTOR Ignore a detector by name. Repeatable.
--ignore-path PATH Skip files whose path contains PATH. Repeatable.
--format [text|html] Output format: text (stdout) or html (arch-
report.html). [default: text]
--help Show this message and exit.
Exit Codes
| Code | Meaning |
|---|---|
0 |
No critical findings |
1 |
At least one critical finding, or a CLI usage error |
This makes the tool suitable for CI gating.
Detectors
Fat Models
Flags Django model classes with too many non-dunder methods.
- Warning:
method_count >= threshold - Critical:
method_count >= threshold * 2 - Default threshold:
15 - Default critical boundary:
30
Notes:
- Counts
defandasync def - Ignores dunder methods like
__str__ - Scans Python files across the project
God Apps
Flags Django apps that own too much of the total project LOC.
- Warning:
percentage >= threshold - Critical:
percentage >= threshold + 20 - Default threshold:
30% - Default critical boundary:
50%
Notes:
- LOC excludes blank lines and standalone comment lines
- Requires at least 2 Django apps before it reports findings
- Uses
models.pyorapps.pyto identify app directories
Circular Imports
Flags cycles in the intra-project import graph.
- Critical: any detected cycle
Notes:
- Only top-level imports are analyzed
- Function-level imports are intentionally ignored
- Reports both short and multi-node cycles
Missing Service Layer
Flags views that contain too many direct ORM calls and likely need service-layer extraction.
- Warning: 2 or more direct
Model.objects.*calls in a single view function or method - Critical: 4 or more direct
Model.objects.*calls in a single view function or method
Notes:
- Scans
views.pyfiles only - Supports function-based views and class-based view methods
- Uses ORM call count, not raw line count
Celery Tasks Without Retry
Flags Celery tasks that lack retry configuration.
- Critical: task name contains
payment,email,invoice, ornotification, and has no retry config - Warning: any other task with no retry config
Retry config is considered present when the decorator includes any of:
max_retriesautoretry_forretry_backoff
Notes:
- Detects both
@shared_taskand@app.task - Skips migration files
Direct SQL
Flags raw SQL patterns that bypass Django's ORM.
Detected patterns:
cursor.execute(connection.cursor().raw(.extra(select=
Severity:
- Warning only
Notes:
- Migration files are excluded
N+1 Query Risks
Flags likely N+1 query patterns inside loops and list comprehensions.
- Warning: ORM call inside a loop or list comprehension, with no
select_relatedorprefetch_relatedfound in the same function scope
Notes:
- Scans
views.pyandserializers.py - Looks for
X.objects.method(...)patterns inside loops - Heuristic by design; false positives and false negatives are possible
HTML Report
django-arch-check analyze --format html /path/to/project
The generated arch-report.html is self-contained and works offline.
It includes:
- A health score from
0to100 - Summary counts for critical and warning findings
- One section per detector
- Skipped detector notes when
--ignoreis used
Health Score
The score is rate-based, not a simple fixed deduction per finding.
Formula:
critical_rate = criticals / total_findings
warning_rate = warnings / total_findings
raw = 100 - (critical_rate * 60) - (warning_rate * 40)
penalty = min(30, criticals * 2 + warnings * 0.5)
score = max(0, round(raw - penalty))
This avoids driving large mature projects straight to zero while still rewarding lower-severity, lower-density finding profiles.
CI Integration
GitHub Actions
- name: Check Django architecture
run: |
pip install django-arch-check
django-arch-check analyze ./
If you want to ignore legacy areas while still gating the rest of the codebase:
- name: Check Django architecture
run: |
pip install django-arch-check
django-arch-check analyze --ignore-path legacy/ ./
To generate an HTML report artifact:
- name: Django architecture report
run: |
pip install django-arch-check
django-arch-check analyze --format html ./
- name: Upload report
uses: actions/upload-artifact@v4
with:
name: arch-report
path: arch-report.html
How It Works
django-arch-check analyzes source code statically.
It:
- Walks the project tree
- Skips common non-source directories like
.venv,node_modules, and caches - Parses Python files with the standard-library
astmodule - Runs each detector independently through a central analyzer
It does not:
- Import your Django project
- Require configured settings
- Hit the database
- Execute application code
That makes it safe to run in CI, pre-commit hooks, and partially broken repos.
Limitations
- Circular import detection only covers top-level imports
--ignore-pathuses substring matching, not glob syntax- Missing service layer detection only scans files literally named
views.py - N+1 detection is heuristic and only reasons within a single function scope
- God app analysis requires at least 2 detectable Django apps
Development
git clone https://github.com/RJ-Gamer/django-arch-check.git
cd django-arch-check
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
pytest -q --basetemp .pytest-tmp
django-arch-check analyze /path/to/project
Project Structure
django_arch_check/
├── __init__.py
├── cli.py
├── analyzer.py
├── report.py
└── detectors/
├── __init__.py
├── fat_models.py
├── god_apps.py
├── circular_imports.py
├── missing_service_layer.py
├── celery_tasks.py
├── direct_sql.py
└── n_plus_one.py
tests/
├── conftest.py
├── test_analyzer.py
├── test_fat_models.py
├── test_god_apps.py
├── test_circular_imports.py
├── test_missing_service_layer.py
├── test_celery_tasks.py
├── test_direct_sql.py
├── test_n_plus_one.py
├── test_report.py
└── test_cli.py
Adding a New Detector
- Create
django_arch_check/detectors/my_detector.py - Add a
detect(...)function that returns finding dataclasses - Add the detector to
analyzer.pyandAnalysisResult - Add text output in
cli.py - Add HTML rendering support in
report.py - Add tests
Contributing
Issues and pull requests are welcome.
If you are proposing a larger detector or behavior change, opening an issue first is appreciated.
Version
The next release version for this change set is 0.4.0.
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_arch_check-0.4.2.tar.gz.
File metadata
- Download URL: django_arch_check-0.4.2.tar.gz
- Upload date:
- Size: 604.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
877b23ffd623761b96e7befe2fb1a456e346029baf9544ffa681df8ca1c15f47
|
|
| MD5 |
fb7d7b7107e6f6e1b3b92a3ca9613fa0
|
|
| BLAKE2b-256 |
4dcd575c003a138e677e10613f9d8902d440f738c32947524ac299460a060e73
|
File details
Details for the file django_arch_check-0.4.2-py3-none-any.whl.
File metadata
- Download URL: django_arch_check-0.4.2-py3-none-any.whl
- Upload date:
- Size: 43.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7fdc4faa2ba2a6ff7f986a839d1c7c6d9e0f9ac1c692d7c0eb23093f9fbb29bc
|
|
| MD5 |
0c360fe284f7b356b2b9819052508065
|
|
| BLAKE2b-256 |
83df6b8c1ba3deffb395fbefb9e6c2996d284df7790a423b27ed29530e5aea2e
|