Swing Sitemap
Django Swing Collection
Overview
Swing Sitemap is a powerful, CMS-agnostic Django app that extends django.contrib.sitemaps with a settings-driven, drop-in URL helper and versatile sitemap classes. It eliminates the boilerplate of sitemaps.py + urls.py wiring, reducing setup to just a few import lines.
Features
| Feature | Description |
|---|---|
| StaticSitemap | Sitemap of named Django views with support for view_name, args, kwargs, and lastmod |
| ModelSitemap | Generic queryset adapter for any Django model |
| VideoSitemap | Google Video sitemap with full video metadata support |
| NewsSitemap | Google News sitemap for news articles |
| ImageSitemap | Image sitemap with gallery support |
| HreflangMixin | Multi-language support with hreflang alternate links |
| Pagination | Automatic pagination for large sitemaps (50,000 URLs limit) |
| Caching | Built-in cache support with configurable TTL |
| Compression | Gzip compression for sitemap responses |
| Signals | Auto-invalidate cache on model changes |
| Search Engine Submission | Ping Google, Bing, and custom endpoints |
| Management Commands | CLI tools for generation, validation, and submission |
Requirements
- Python 3.12+
- Django 5.0+ / 6.0+
Installation
pip install swing-sitemap
Or with Poetry:
poetry add swing-sitemap
Add to INSTALLED_APPS:
INSTALLED_APPS = [
# ...
"django.contrib.sitemaps",
"swing.sitemap",
]
Quick Start
1. Configure Settings
# settings.py
SWING_SITEMAP = {
# Static pages sitemap
"static": {
"views": ["home", "about", "contact"],
"priority": 0.6,
"changefreq": "weekly",
},
# Model-based sitemaps
"models": {
"blog": {
"model": "blog.Post",
"filters": {"is_published": True},
"order_by": ["-published_at"],
"date_field": "updated_at",
"priority": 0.8,
"changefreq": "daily",
},
"products": {
"model": "shop.Product",
"filters": {"active": True},
"priority": 0.7,
},
},
# Caching (optional)
"cache": {
"enabled": True,
"timeout": 3600, # 1 hour
"key_prefix": "sitemap",
},
# Pagination (optional)
"pagination": {
"enabled": True,
"items_per_page": 10000,
},
}
2. Wire URLs
# urls.py
from swing.sitemap.urls import sitemap_urlpatterns
urlpatterns = [
# ... your views ...
*sitemap_urlpatterns(),
]
That's it! Your sitemap is now available at /sitemap.xml.
3. Add to robots.txt (Optional)
# settings.py
TEMPLATES = [
{
# ...
"OPTIONS": {
"context_processors": [
# ...
"swing.sitemap.context_processors.sitemap_url",
],
},
},
]
# templates/robots.txt
Sitemap: {{ sitemap_url }}
Or use the template tag:
{% load swing_sitemap %}
Sitemap: {% sitemap_url %}
Sitemap Classes
StaticSitemap
For named Django views:
from swing.sitemap import StaticSitemap
sitemap = StaticSitemap(
views=["home", "about", {"view_name": "blog:archive", "kwargs": {"year": 2024}}],
priority=0.8,
changefreq="weekly",
)
ModelSitemap
For Django model querysets:
from swing.sitemap import ModelSitemap
from blog.models import Post
sitemap = ModelSitemap(
queryset=Post.objects.filter(is_published=True),
date_field="updated_at",
priority=0.7,
)
VideoSitemap
For video content (Google Video Sitemaps):
from swing.sitemap import VideoSitemap
class MyVideoSitemap(VideoSitemap):
def items(self):
return Video.objects.filter(published=True)
def video_title(self, obj):
return obj.title
def video_description(self, obj):
return obj.description
def video_thumbnail_loc(self, obj):
return obj.thumbnail.url
NewsSitemap
For news articles (Google News Sitemaps):
from swing.sitemap import NewsSitemap
class MyNewsSitemap(NewsSitemap):
publication_name = "My News Site"
publication_language = "en"
def items(self):
return Article.objects.filter(
published_at__gte=timezone.now() - timedelta(days=2)
)
HreflangMixin
For multi-language sites:
from swing.sitemap import ModelSitemap, HreflangMixin
class I18nBlogSitemap(HreflangMixin, ModelSitemap):
languages = ["en", "de", "fr"]
def alternates(self, obj):
return {
lang: f"/{lang}/blog/{obj.slug}/"
for lang in self.languages
}
Management Commands
Generate Static Sitemap
python manage.py generate_sitemap --output sitemap.xml
Validate Sitemap
# Validate from URL
python manage.py validate_sitemap https://example.com/sitemap.xml
# Validate all configured sitemaps
python manage.py validate_sitemap --all
Submit to Search Engines
python manage.py submit_sitemap https://example.com/sitemap.xml
Clear Sitemap Cache
python manage.py clear_sitemap_cache
Celery Tasks (Optional)
For async sitemap submission:
# settings.py
SWING_SITEMAP = {
"submit": {
"sitemap_url": "https://example.com/sitemap.xml",
"endpoints": {
"google": "https://www.google.com/ping?sitemap={url}",
"bing": "https://www.bing.com/ping?sitemap={url}",
},
},
}
# tasks.py
from swing.sitemap.tasks import submit_sitemap_task
# Submit immediately
submit_sitemap_task.delay()
Composing Extra Sitemaps
Swing Sitemap is CMS-agnostic. Integrate with Wagtail or other CMSes:
from swing.sitemap import default_sitemaps, sitemap_urlpatterns
from wagtail.contrib.sitemaps import Sitemap as WagtailSitemap
sitemaps = {**default_sitemaps(), "wagtail": WagtailSitemap}
urlpatterns = [
*sitemap_urlpatterns(sitemaps=sitemaps),
]
Configuration Reference
| Setting | Type | Default | Description |
|---|---|---|---|
static.views |
list |
[] |
List of view names or dicts |
static.priority |
float |
0.5 |
Default priority for static URLs |
static.changefreq |
str |
"monthly" |
Default change frequency |
models.<key>.model |
str |
Required | Model path (e.g., "app.Model") |
models.<key>.filters |
dict |
{} |
Queryset filter kwargs |
models.<key>.order_by |
list |
[] |
Ordering fields |
models.<key>.date_field |
str |
None |
Field for lastmod |
models.<key>.priority |
float |
0.5 |
URL priority (0.0-1.0) |
models.<key>.changefreq |
str |
"monthly" |
Change frequency |
cache.enabled |
bool |
False |
Enable sitemap caching |
cache.timeout |
int |
3600 |
Cache TTL in seconds |
cache.key_prefix |
str |
"swing_sitemap" |
Cache key prefix |
pagination.enabled |
bool |
False |
Enable pagination |
pagination.items_per_page |
int |
50000 |
URLs per sitemap page |
compression.enabled |
bool |
False |
Enable gzip compression |
Signals
Automatically invalidate cache when models change:
# apps.py
from django.apps import AppConfig
class BlogConfig(AppConfig):
def ready(self):
from swing.sitemap import register_sitemap_signals
from .models import Post
register_sitemap_signals(Post)
Contributing
Contributions are welcome! Please read our Contributing Guide for details.
# Clone the repository
git clone https://github.com/swing-collection/swing-sitemap.git
cd swing-sitemap
# Install dependencies
poetry install
# Run tests
poetry run pytest
# Run linters
poetry run flake8 src/
poetry run pylint src/
License
This project is licensed under the BSD-3-Clause License. See LICENSE for details.
Links
- Documentation: https://swing.dj/
- Repository: https://github.com/swing-collection/swing-sitemap
- PyPI: https://pypi.org/project/swing-sitemap/
- Issues: https://github.com/swing-collection/swing-sitemap/issues
robots.txt
Use the template tag or context processor:
{% load swing_sitemap %}
Sitemap: {% sitemap_url %}
Or enable the context processor and reference {{ SITEMAP_URL }}:
TEMPLATES = [{
"OPTIONS": {
"context_processors": [
# ...
"swing_sitemap.context_processors.sitemap_url",
],
},
}]
Settings reference
| Key | Default | Notes |
|---|---|---|
static.views |
[] |
List of URL names or {view_name, args, kwargs, lastmod} dicts. |
static.priority |
0.5 |
Default <priority> for every static entry. |
static.changefreq |
"monthly" |
Default <changefreq> for every static entry. |
models.<key>.model |
required | Dotted "app.Model". |
models.<key>.filters |
{} |
**filters passed to qs.filter(). |
models.<key>.exclude |
{} |
**filters passed to qs.exclude(). |
models.<key>.order_by |
[] |
Args passed to qs.order_by(). |
models.<key>.date_field |
None |
Attribute used for <lastmod>. |
models.<key>.location_attr |
"get_absolute_url" |
Attribute or zero-arg callable returning the URL. |
models.<key>.priority |
None |
Per-sitemap <priority>. |
models.<key>.changefreq |
None |
Per-sitemap <changefreq>. |
priority / changefreq |
{} |
Free-form per-key dicts (legacy SEO_SITEMAP_* shims fold in here). |
Backward-compatibility shims
The following legacy settings still work but emit a DeprecationWarning
the first time get_config() runs:
SEO_SITEMAP_PRIORITY→SWING_SITEMAP["priority"]SEO_SITEMAP_CHANGEFREQ→SWING_SITEMAP["changefreq"]VIDEO_SITEMAP_MODEL→SWING_SITEMAP["video"]["model"]
Testing
poetry install --with dev
poetry run pytest
Colophon
Made with ❤️ by Scape Press
Contributing
Contributions are welcome! Please fork the repository and submit a pull request with your changes.
License
This project is licensed under the BSD-3-Clause license. See the LICENSE file for details.
Release files for swing-sitemap 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| swing_sitemap-0.1.2.tar.gz | 55.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| swing_sitemap-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 159.1 kB
Release files / swing_sitemap-0.1.2.tar.gz
| Download URL | swing_sitemap-0.1.2.tar.gz |
|---|---|
| Size | 55.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
aff60922187ec980dafce1f2b8f822dfcc62a28871cb639c32095e4d8265907f
|
|
BLAKE2b-256 checksum How to use checksums |
b878ed3b6c52cdcdffce0b31d49d60806346eb52427bd9c2b278fb48ab12500c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / swing_sitemap-0.1.2-py3-none-any.whl
| Download URL | swing_sitemap-0.1.2-py3-none-any.whl |
|---|---|
| Size | 103.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0092d67cc2789907b21fb1b4575a754bd4e31b95b997cb97f5c17e29d5a0f6e1
|
|
BLAKE2b-256 checksum How to use checksums |
d3676104cf74ff31b0b6a2d400f5689159db99159a591a57ca0596e8aaf064f6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|