Django SEO Audit
A production-ready Django package for comprehensive SEO auditing of models using Python protocols and automatic rule discovery.
Features
- Protocol-Based Architecture: Uses Python 3.12+ protocols for clean, loosely-coupled design
- Automatic Rule Discovery: Rules auto-register when defined - no manual registration needed
- Auto-Discovery Management Command: Automatically finds and audits any model using
SEOAuditableMixin - Comprehensive Rule Set: 18 built-in rules across 4 categories:
- Core SEO: Title length, meta descriptions, keywords, H1 tags
- Social Media: OpenGraph and Twitter Card optimization
- Content Quality: Content depth, introduction, resources
- Technical SEO: Canonical URLs, robots directives, structured data
- Django Model Mixin: Drop-in mixin with intelligent fallbacks for common field names
- Extensible: Easy to add custom rules for your specific SEO requirements
- Beautiful CLI Output: Color-coded results with emoji indicators and actionable suggestions
Installation
From PyPI (when published)
pip install django-seo-audit
From Source (Development)
# In your workspace directory
git clone https://github.com/directory-platform/django-seo-audit.git
cd django-seo-audit
pip install -e .
UV Workspace (Recommended for development)
Add to your workspace pyproject.toml:
[tool.uv.workspace]
members = [
"your-project",
"django-seo-audit",
]
Then run:
uv sync
Quick Start
1. Add to Django Settings
# settings.py
INSTALLED_APPS = [
# ...
"django_seo_audit",
]
2. Add Mixin to Your Models
# models.py
from django.db import models
from django_seo_audit import SEOAuditableMixin
class Article(SEOAuditableMixin, models.Model):
name = models.CharField(max_length=200)
seo_title = models.CharField(max_length=60, blank=True)
meta_description = models.TextField(max_length=160, blank=True)
focus_keyphrase = models.CharField(max_length=100, blank=True)
# ... other fields
The mixin provides intelligent fallbacks:
get_seo_title()→seo_titleornameget_meta_description()→meta_descriptionorshort_descriptionget_h1_tag()→h1_tagorseo_titleorname
3. Run SEO Audit
# List all auditable models
python manage.py seo_audit --list-models
# Audit a specific object
python manage.py seo_audit --model blog.Article --slug my-article
# Audit with verbose output and specific categories
python manage.py seo_audit --model blog.Article --slug my-article --verbose --category core_seo
4. Programmatic Usage
from django_seo_audit import SEOAuditor
# Audit any object
article = Article.objects.get(slug="my-article")
auditor = SEOAuditor()
result = auditor.audit_object(article)
# Check results
print(f"SEO Score: {result.overall_score}/10")
print(f"Grade: {result.get_health_grade()}")
print(f"Passing: {result.is_passing()}")
# Get issues by severity
critical_issues = result.get_critical_issues()
warnings = result.get_warnings()
successes = result.get_successes()
Protocols
Django SEO Audit uses Python protocols to define contracts for auditable objects. Your models don't need to inherit from anything - just implement the methods you need.
BasicSEOAuditable
Core SEO methods every model should implement:
from django_seo_audit import BasicSEOAuditable
def get_seo_title(self) -> str: ...
def get_meta_description(self) -> str: ...
def get_canonical_url(self) -> str: ...
def get_focus_keyphrase(self) -> str: ...
def get_secondary_keywords(self) -> str: ...
def get_h1_tag(self) -> str: ...
SocialMediaAuditable
For social sharing optimization:
from django_seo_audit import SocialMediaAuditable
def get_og_title(self) -> str: ...
def get_og_description(self) -> str: ...
def get_og_image_url(self) -> str | None: ...
def get_twitter_title(self) -> str: ...
def get_twitter_description(self) -> str: ...
def get_twitter_image_url(self) -> str | None: ...
ContentAuditable
For content quality assessment:
from django_seo_audit import ContentAuditable
def has_detailed_content(self) -> bool: ...
def get_content_word_count(self) -> int: ...
def get_content_sections_count(self) -> int: ...
def has_introduction_content(self) -> bool: ...
def get_resource_count(self) -> int: ...
TechnicalSEOAuditable
For technical SEO features:
from django_seo_audit import TechnicalSEOAuditable
def get_robots_directive(self) -> str: ...
def get_schema_type(self) -> str: ...
def get_schema_data(self) -> dict | None: ...
def get_breadcrumb_title(self) -> str: ...
def has_custom_canonical_url(self) -> bool: ...
Creating Custom Rules
Creating custom SEO rules is straightforward:
# rules/custom_rules.py
from django_seo_audit import SEORule, SEOResult, SEOStatus
from django_seo_audit.protocols import BasicSEOAuditable
class CustomKeywordDensityRule(SEORule):
"""Check keyword density in content."""
name = "Keyword Density"
description = "Ensures focus keyword appears with optimal density"
category = "custom"
weight = 3 # 1-5 importance scale
def check(self, obj: BasicSEOAuditable) -> SEOResult:
keyword = obj.get_focus_keyphrase()
# Your logic here
return SEOResult(
status=SEOStatus.GOOD,
message="Keyword density optimal",
score=10,
)
Rules are automatically discovered and registered when imported!
Management Command Reference
List Auditable Models
python manage.py seo_audit --list-models
Shows all models using SEOAuditableMixin with object counts.
Audit by Slug
python manage.py seo_audit --model app_label.ModelName --slug object-slug
Audit by Primary Key
python manage.py seo_audit --model app_label.ModelName --pk 42
Filter by Category
python manage.py seo_audit --model blog.Article --slug test \
--category core_seo \
--category social_media
Available categories: core_seo, social_media, content, technical_seo
Verbose Output
python manage.py seo_audit --model blog.Article --slug test --verbose
Shows detailed explanations and actionable suggestions for improvements.
Built-in Rules
Core SEO (5 rules)
- SEO Title Length: Optimal 50-60 characters
- Meta Description Length: Optimal 150-160 characters
- Focus Keyphrase: Presence in title and description
- H1 Tag Optimization: Proper H1 configuration
- Secondary Keywords: 3-7 keyword variety
Social Media (5 rules)
- OpenGraph Image: 1200x630px image configuration
- OpenGraph Title: Up to 95 characters
- OpenGraph Description: Up to 200 characters
- Twitter Card Image: Twitter-optimized imagery
- Twitter Card Title: Up to 70 characters
Content (4 rules)
- Detailed Content: Comprehensive content sections
- Introduction Content: Engaging introduction
- Supporting Resources: Videos, articles, tools
- Content Depth: 500+ words across sections
Technical SEO (4 rules)
- Canonical URL: Duplicate content prevention
- Robots Directive: Proper indexing configuration
- Structured Data: Schema.org markup
- Breadcrumb Optimization: Navigation clarity
Architecture
- Protocol-Based: Uses Python 3.12+
Protocolfor structural subtyping - Auto-Registration: Rules register via
__init_subclass__metaclass magic - Immutable Results:
SEOResultuses frozen dataclass for thread-safety - Graceful Degradation: Failed rules don't break the entire audit
- Zero Config: Works out of the box with sensible defaults
Requirements
- Python 3.12+
- Django 4.2+
Development
Setup
# Clone repository
git clone https://github.com/heysamtexas/django-seo-audit.git
cd django-seo-audit
# Install with dev dependencies
pip install -e ".[dev]"
# or with uv
uv sync --extra dev
Running Tests
The package includes a comprehensive test suite with 207 tests:
# Run all tests
make test
# or
PYTHONPATH=. uv run python tests/manage.py test
# Run with verbose output
make test-verbose
# Run specific test module
PYTHONPATH=. uv run python tests/manage.py test tests.test_rules
Code Quality
# Run linting
make lint
# or
ruff check src/ tests/
# Auto-format code
make format
# or
ruff format src/ tests/
# Type checking
make typecheck
# or
mypy src/
# Run all checks
make check
Example Models
See tests/example_app/models.py for living documentation:
- BlogPost: Full SEO implementation
- Product: Minimal implementation with fallbacks
- Page: Custom protocol without mixin
Releases and Publishing
This package uses GitHub Actions to automatically publish to PyPI when a new release is created.
Creating a Release
-
Update version in
pyproject.toml:[project] version = "0.1.1" # Bump version
-
Commit version bump:
git add pyproject.toml git commit -m "Bump version to 0.1.1" git push origin master
-
Create GitHub release:
# Using GitHub CLI gh release create v0.1.1 --title "Release v0.1.1" --notes "Release notes here" # Or use GitHub web interface # Navigate to: https://github.com/heysamtexas/django-seo-audit/releases/new
-
GitHub Actions will automatically:
- Verify version matches tag
- Run tests
- Build package
- Publish to PyPI
PyPI Trusted Publisher Setup (First Release Only)
Before creating your first release, configure PyPI Trusted Publisher:
-
Go to PyPI: https://pypi.org/manage/account/publishing/
-
Add a new pending publisher:
- PyPI Project Name:
django-seo-audit - Owner:
heysamtexas - Repository name:
django-seo-audit - Workflow name:
publish.yml - Environment name: (leave blank)
- PyPI Project Name:
-
Create first release - PyPI will create the project automatically
No API tokens needed! Trusted Publishers are more secure and maintenance-free.
Contributing
Contributions welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-rule) - Commit your changes (
git commit -m 'Add amazing SEO rule') - Push to the branch (
git push origin feature/amazing-rule) - Open a Pull Request
License
MIT License - see LICENSE file for details.
Credits
Built by the Directory Platform team as part of the directory ecosystem.
Links
- Documentation: [Coming soon]
- GitHub: https://github.com/heysamtexas/django-seo-audit
- Issues: https://github.com/heysamtexas/django-seo-audit/issues
- PyPI: https://pypi.org/project/django-seo-audit/ (after first release)
Metadata
Release files for django-seo-audit 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_seo_audit-0.2.0.tar.gz | 51.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_seo_audit-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 78.8 kB
Release files / django_seo_audit-0.2.0.tar.gz
| Download URL | django_seo_audit-0.2.0.tar.gz |
|---|---|
| Size | 51.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
79f7bd377b7d7775eab51069e07cc9c72def8cb361e10465604d75d5d9ce3655
|
|
BLAKE2b-256 checksum How to use checksums |
2963a9cb396fd5e2ecdc6f84a2bbeb36cbb04d327d2ab9e8d05bd7316d79fe31
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 Oct 11, 2025.
Transparency logRelease files / django_seo_audit-0.2.0-py3-none-any.whl
| Download URL | django_seo_audit-0.2.0-py3-none-any.whl |
|---|---|
| Size | 27.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5d74fe1670ac678c2638cc4cc1cf31499d3d20962509bd6179ca56e8a5541b6e
|
|
BLAKE2b-256 checksum How to use checksums |
fea94710c183413bee225ffeba77aa5014b0fd57f5b92c6e49d20d96cb8a2855
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 Oct 11, 2025.
Transparency log