A Django dead code analysis tool that tracks relationships between templates, URLs, and views
Project description
Django Dead Code
We find your buried bones (or code)!
A Django dead code analysis tool that tracks relationships between templates, URLs, and views to help identify and remove unused code.
Features
- Template Analysis: Extract URL references from Django templates (href attributes and
{% url %}tags) - URL Pattern Discovery: Analyze all URL patterns defined in your Django project
- View Tracking: Identify which templates are used by which views
- Class-Based View Detection: Automatically detects templates used by CBVs through Django's implicit naming conventions (ListView, DetailView, CreateView, UpdateView, DeleteView)
- Template Variable Detection: Detects templates referenced through variables containing 'template' in the name
- Python Code Analysis: Detect
reverse()andredirect()URL references in Python code - Relationship Mapping: Track template inheritance (extends/includes) and relationships
- Smart Template Detection: Templates referenced via
{% include %}or{% extends %}are correctly marked as used - Path Normalization: Consistent path handling ensures accurate template matching
- Project Boundary Filtering: Automatically excludes templates from installed packages (outside BASE_DIR)
- Multiple Output Formats: Console, JSON, and Markdown reports
- Django Native: Uses Django's management command structure for seamless integration
Installation
pip install django-deadcode
Or install from source:
git clone https://github.com/nanorepublica/django-deadcode.git
cd django-deadcode
pip install -e .
Setup
Add django_deadcode to your INSTALLED_APPS:
INSTALLED_APPS = [
# ... other apps
'django_deadcode',
]
Usage
Basic Usage
Run the analysis on your Django project:
python manage.py finddeadcode
This will analyze your entire Django project and output a report to the console.
Output Formats
Console output (default):
python manage.py finddeadcode
JSON output:
python manage.py finddeadcode --format json
Markdown output:
python manage.py finddeadcode --format markdown
Save Report to File
python manage.py finddeadcode --format json --output report.json
Analyze Specific Apps
python manage.py finddeadcode --apps myapp otherapp
Custom Template Directory
python manage.py finddeadcode --templates-dir /path/to/templates
Show Template Relationships
By default, template include/extends relationships are hidden in reports. To show them:
python manage.py finddeadcode --show-template-relationships
This is useful for understanding how templates are connected but can make reports verbose for large projects.
What It Detects
Unreferenced URL Patterns
URL patterns that are defined in urls.py but never referenced in templates or Python code:
# urls.py - This URL is defined
path('old-feature/', views.old_feature, name='old_feature'),
# But no template references it with {% url 'old_feature' %}
# And no Python code uses reverse('old_feature')
Class-Based View Default Templates (NEW)
Automatically detects templates used by class-based views through Django's implicit naming convention:
# views.py
from django.views.generic import ListView, DetailView
from .models import Article
class ArticleListView(ListView):
model = Article
# Automatically detects: myapp/article_list.html
class ArticleDetailView(DetailView):
model = Article
# Automatically detects: myapp/article_detail.html
Supported CBV types:
ListView→<app_label>/<model_name>_list.htmlDetailView→<app_label>/<model_name>_detail.htmlCreateView→<app_label>/<model_name>_form.htmlUpdateView→<app_label>/<model_name>_form.htmlDeleteView→<app_label>/<model_name>_confirm_delete.html
Template Variable Detection (NEW)
Detects templates referenced through variables containing 'template' in the name:
# Simple variable assignment
template_name = 'myapp/custom.html'
# Method returns
def get_template_names(self):
return ['myapp/template1.html', 'myapp/template2.html']
Python Code URL References
Detects URL references in Python code via:
from django.shortcuts import redirect
from django.urls import reverse, reverse_lazy
from django.http import HttpResponseRedirect
# All of these are detected and marked as "referenced"
def my_view(request):
return redirect('url-name')
def another_view(request):
url = reverse('url-name')
return HttpResponseRedirect(url)
class MyView(UpdateView):
success_url = reverse_lazy('url-name')
Unused Templates
Templates that exist but are not referenced by any view (directly or indirectly through includes/extends):
# views.py - No view renders 'unused_template.html'
# And no other template includes or extends it
# But the file templates/unused_template.html exists
Note: Templates referenced via {% include %} or {% extends %} are now correctly identified as used, even if not directly referenced by views.
Template Relationships
Tracks which templates include or extend other templates:
{# base.html is extended by page.html #}
{% extends 'base.html' %}
{# header.html is included in base.html #}
{% include 'partials/header.html' %}
Use the --show-template-relationships flag to see these relationships in your report.
Example Output
================================================================================
Django Dead Code Analysis Report
================================================================================
SUMMARY
--------------------------------------------------------------------------------
Total URL patterns: 45
Total templates analyzed: 32
Total views found: 28
Unreferenced URLs: 5
Unused templates: 3
UNREFERENCED URL PATTERNS
--------------------------------------------------------------------------------
These URL patterns are defined but never referenced in templates:
• old_feature
View: myapp.views.old_feature
Pattern: /old-feature/
• deprecated_api
View: myapp.api.deprecated_endpoint
Pattern: /api/v1/deprecated/
POTENTIALLY UNUSED TEMPLATES
--------------------------------------------------------------------------------
These templates are not directly referenced by views (may be included/extended):
• old_landing.html
• unused_email.html
• legacy_form.html
How It Works
-
Template Analysis: Scans all template files within your project's BASE_DIR for:
{% url 'name' %}tagshref="/path/"attributes (internal links){% include 'template' %}tags{% extends 'template' %}tags
-
Path Normalization: Normalizes all template paths to Django's relative format (e.g.,
app_name/template.html) ensuring consistent matching between filesystem paths and template references. -
Project Boundary Filtering: Only templates within your project's
BASE_DIRare analyzed. Templates from installed packages (e.g., Django admin, third-party apps) are automatically excluded. -
URL Pattern Discovery: Inspects Django's URL configuration to find all defined URL patterns and their names
-
View Analysis: Parses Python files to find:
render(request, 'template.html')callstemplate_name = 'template.html'in class-based views- Class-based view implicit template names (ListView, DetailView, etc.)
- Template variables containing 'template' in the name
-
Reverse/Redirect Analysis: Uses AST parsing to detect:
reverse('url-name')callsreverse_lazy('url-name')callsredirect('url-name')callsHttpResponseRedirect(reverse('url-name'))patterns- Dynamic URL patterns (f-strings, concatenation) are flagged for manual review
-
Transitive Template Detection: Recursively traces template relationships to mark templates as used if they're referenced via
{% include %}or{% extends %}from any used template -
Relationship Mapping: Connects templates ↔ URLs ↔ views to identify dead code
Limitations
Static Analysis Only
- Does not execute code or track runtime behavior
- Cannot detect templates loaded with dynamic names (e.g.,
render(request, f'{variable}.html'))
Dynamic Templates
The following patterns are out of scope and will not be detected:
- f-string template names:
f'{app_name}/template.html' - Concatenated variables:
template = var1 + var2 - Complex conditional logic in
get_template_names()
Dynamic URLs
- Cannot automatically detect URLs generated with f-strings or concatenation
- These are flagged for manual review in the report
Third-party Packages
- Analyzes your code only, not installed packages
- Templates outside BASE_DIR are automatically excluded
Function-Based Template Loading
The following patterns are not detected:
get_template()function callsselect_template()function calls
These may be addressed in future enhancements based on user feedback.
Troubleshooting
Templates Incorrectly Flagged as Unused
Issue: A template that is actually used is being flagged as unused.
Possible Causes & Solutions:
-
Template Outside BASE_DIR: Templates from installed packages are automatically excluded. Verify the template is within your project's BASE_DIR.
-
Dynamic Template Names: If your view uses dynamic template names (f-strings, concatenation), the tool cannot detect them. Consider refactoring to use explicit template names or adding a comment to track manually.
-
Custom Template Loaders: If you're using custom template loaders with non-standard path resolution, the path normalization may not work correctly. Ensure templates are in standard locations.
-
Template Name Mismatch: Verify that the template name in your view matches the actual file path relative to the templates directory.
Class-Based View Templates Not Detected
Issue: A CBV's implicit template is not being detected.
Possible Causes & Solutions:
-
Non-Standard App Structure: The tool infers app labels from file paths. If your app structure is non-standard (e.g., not in an
apps/directory), the app label inference may fail. Use explicittemplate_nameattributes. -
Model Not Detected: If the model is set via a complex expression or
get_queryset()method, the tool may not detect it. Use explicittemplate_nameattributes. -
CBV Inheritance: If your CBV inherits from a custom base class that inherits from Django's generic views, the tool may not detect it. Ensure the direct base class is a Django generic view.
Path Normalization Issues
Issue: Templates are not being matched correctly.
Possible Causes & Solutions:
-
Multiple 'templates' Directories: If your path contains multiple directories named 'templates' (e.g.,
/old_templates/templates/), the tool uses the last occurrence. Rename directories to avoid confusion. -
Symlinked Templates: Symlinks are resolved to their actual paths. Ensure symlink targets are within BASE_DIR.
-
Windows Paths: The tool handles both Unix and Windows path separators. If you encounter issues, please report them as a bug.
Performance Issues
Issue: Analysis takes too long on large projects.
Expected Performance:
- Small project (10 templates): < 1 second
- Medium project (100 templates): < 5 seconds
- Large project (1000 templates): < 30 seconds
Solutions:
- Analyze Specific Apps: Use
--appsflag to limit analysis scope - Exclude Test/Migration Files: The tool already skips these, but ensure they're not in unusual locations
- Report a Bug: If performance is significantly worse than expected, please open an issue
False Negatives (Unused Code Not Detected)
Issue: Dead code exists but is not being detected.
Possible Causes:
- Template Used by Third-Party Package: Templates used by installed packages are not tracked
- Dynamic Template/URL References: Cannot be detected by static analysis
- Complex Template Chains: Very complex include/extends chains may have edge cases
Recommendations:
- Review the report manually
- Cross-reference with code coverage reports
- Test in a staging environment before deleting templates
Development
Setup Development Environment
# Clone the repository
git clone https://github.com/nanorepublica/django-deadcode.git
cd django-deadcode
# Install development dependencies
pip install -e ".[dev]"
Running Tests
pytest
With coverage:
pytest --cov=django_deadcode --cov-report=html
Code Quality
# Linting
ruff check .
# Type checking
mypy django_deadcode
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for details.
License
MIT License - see LICENSE for details.
Credits
Inspired by the blog post: https://softwarecrafts.co.uk/100-words/day-71
Roadmap
See agent-os/product/roadmap.md for the full development roadmap.
Planned Features
- Confidence scoring for dead code detection
- Multi-app analysis with cross-app relationship tracking
- Django admin integration detection
- HTML report generation with interactive UI
- CI/CD integration helpers
- IDE plugins (VS Code, PyCharm)
Support
- Issues: https://github.com/nanorepublica/django-deadcode/issues
- Discussions: https://github.com/nanorepublica/django-deadcode/discussions
Changelog
v0.3.0 (Latest)
Major Improvements: Template Detection Accuracy
- Path Normalization: Fixed path format mismatch bug that caused false positives. All template paths are now normalized to Django's relative format for consistent matching.
- Class-Based View Detection: Automatically detects templates used by Django's generic CBVs (ListView, DetailView, CreateView, UpdateView, DeleteView) through implicit naming conventions.
- Template Variable Detection: Detects templates referenced through variables containing 'template' in the name, including
get_template_names()method returns. - Enhanced Template Relationships: Improved tracking of
{% extends %}and{% include %}relationships with normalized paths. - Production Ready: Eliminated false positives for the most common use cases. The tool is now trustworthy for identifying genuinely unused templates.
Performance: All improvements maintain excellent performance with minimal overhead (<10% impact on large projects).
Previous Versions
See CHANGELOG.md for full version history.
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_deadcode-0.8.0.tar.gz.
File metadata
- Download URL: django_deadcode-0.8.0.tar.gz
- Upload date:
- Size: 61.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3660625c15e45280e99f13bda08dff6df29d873538f07c48aac9e78fa7fec48b
|
|
| MD5 |
4813541eca1e97d2cc107fc2d3331ffc
|
|
| BLAKE2b-256 |
df5ddee46a3a3e54b3346a0965c105d300d91dfcc2db442f6aa497bb5912adc4
|
Provenance
The following attestation bundles were made for django_deadcode-0.8.0.tar.gz:
Publisher:
ci.yml on nanorepublica/django-deadcode
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_deadcode-0.8.0.tar.gz -
Subject digest:
3660625c15e45280e99f13bda08dff6df29d873538f07c48aac9e78fa7fec48b - Sigstore transparency entry: 760541278
- Sigstore integration time:
-
Permalink:
nanorepublica/django-deadcode@a3c37fd6ed2c6007d766aa2c888f04d3e5d7c537 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/nanorepublica
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@a3c37fd6ed2c6007d766aa2c888f04d3e5d7c537 -
Trigger Event:
push
-
Statement type:
File details
Details for the file django_deadcode-0.8.0-py3-none-any.whl.
File metadata
- Download URL: django_deadcode-0.8.0-py3-none-any.whl
- Upload date:
- Size: 32.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
75abd7df7b8af3f5cbbf5d4746b3af2865e88dfa25a5d9aca1198767825fa1be
|
|
| MD5 |
4b96b4b4103656d6aa27476364b28ce0
|
|
| BLAKE2b-256 |
bfaf1e9a92fa99c57d740bf7fa4091bea15bd65f939d8840dd7b7253b1798bf7
|
Provenance
The following attestation bundles were made for django_deadcode-0.8.0-py3-none-any.whl:
Publisher:
ci.yml on nanorepublica/django-deadcode
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_deadcode-0.8.0-py3-none-any.whl -
Subject digest:
75abd7df7b8af3f5cbbf5d4746b3af2865e88dfa25a5d9aca1198767825fa1be - Sigstore transparency entry: 760541280
- Sigstore integration time:
-
Permalink:
nanorepublica/django-deadcode@a3c37fd6ed2c6007d766aa2c888f04d3e5d7c537 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/nanorepublica
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@a3c37fd6ed2c6007d766aa2c888f04d3e5d7c537 -
Trigger Event:
push
-
Statement type: