├── .gitignore
├── MANIFEST.in
├── README.md
├── README.txt
├── requirements.pip
├── setup.py
├── static_sitemaps
├── __init__.py
├── conf.py
├── generator.py
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ └── refresh_sitemap.py
├── tasks.py
├── templates
│ └── static_sitemaps
│ │ └── sitemap_index.xml
├── urls.py
├── util.py
└── views.py
└── testapp
├── manage.py
├── sitemaps
└── .gitignore
└── testapp
├── __init__.py
├── settings.py
├── sitemaps.py
├── urls.py
└── wsgi.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .project
2 | .pydevproject
3 | *.pyc
4 | .idea
5 | *egg*
6 | .DS_Store
7 | dist
8 | django_static_sitemaps.egg-info
9 | .vscode
10 | __pycache__
11 | testapp/db.sqlite3
12 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.rst
2 | include MANIFEST.in
3 | recursive-include static_sitemaps *
4 | global-exclude __pycache__
5 | global-exclude *.pyc
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | django-static-sitemaps
2 | ========================
3 |
4 | There are times when your site is too big to serve the ``sitemap.xml`` by your Django application. This little app is meant to help you with such cases. Instead of serving the sitemap.xml from Django, it features a **management command**/**celery task** that generates the ``sitemap.xml`` to the separate files.
5 |
6 | Feature highlights:
7 |
8 | * **NEW:** Compatible with Django 2.0+
9 | * **NEW:** Python 3 compatible
10 | * Generate sitemap files to your STATIC/MEDIA/(own choice) directory
11 | * Split sitemap files when limit for size is reached
12 | * gzip the sitemap files when required
13 | * Set different domain for sitemap file
14 |
15 | Python 2 users
16 | ------
17 |
18 | Python 2 users have should use `4.4.0`. Newer versions will by Python 3 compatible only. Sorry about that.
19 |
20 | Requirements
21 | ------------
22 |
23 | The only requirement is Django 1.8+. App should work with older Django versions with some settings
24 | tweaks.
25 |
26 |
27 | Usage
28 | ------
29 |
30 | Install via standard Python way::
31 |
32 | pip install django-static-sitemaps
33 |
34 | Add `static_sitemaps` to you ``INSTALLED_APPS`` and make sure
35 | `django.contrib.sitemaps` is present too:
36 |
37 | ```python
38 | INSTALLED_APPS = (
39 | ...
40 | 'django.contrib.sites',
41 | 'django.contrib.sitemaps',
42 | ...
43 | 'static_sitemaps',
44 | ...
45 | )
46 |
47 | SITE_ID = 1
48 | ```
49 |
50 | Remember to run `python manage.py makemigrations` and `python manage.py migrate`.
51 |
52 | Set ``STATICSITEMAPS_ROOT_SITEMAP`` variable in your ``settings.py`` to point
53 | to dictionary holding the sitemaps configuration (as seen in Django docs)::
54 |
55 | STATICSITEMAPS_ROOT_SITEMAP = 'myproject.sitemaps.sitemaps'
56 |
57 | Also, make sure you have `STATICSITEMAPS_ROOT_DIR` or at least `STATIC_ROOT` configured.
58 | Sitemap files will be placed there.
59 |
60 | Include ``static_sitemaps.urls`` to your ``urls.py`` to serve the root
61 | ``sitemap.xml`` if you want to serve index file through Django (might be
62 | usefull sometimes when it's hard for you to serve it by webserver itself):
63 |
64 | urlpatterns = [
65 | path('', include('static_sitemaps.urls')),
66 | ]
67 |
68 | Setup your cron to run::
69 |
70 | django-admin.py refresh_sitemap
71 |
72 | periodically. Usually, it's enough to set it to run once by 30 minutes or so.
73 |
74 | For Windows users you can alternatively use the following command:
75 |
76 | python manage.py runserver
77 |
78 | Done.
79 |
80 | Alternatively, you can run this using a Celery task runner. For details, look below.
81 |
82 | **Note:** Your sitemap files will be served from ``STATIC_URL`` by default. If your
83 | ``STATIC_URL`` is a relative one (e.g. ``/static/``), the result will be
84 | prepended the domain to respect the current ``Site`` object. If your
85 | ``STATIC_URL`` is absolute (generally doesn't start with a '/'), sitemaps
86 | URL will respect it completely. If you need more detailed control, see
87 | ``STATICSITEMAPS_URL`` setting.
88 |
89 | **Note about sitemap index lastmod:** In the static_sitemaps app the sitemaps
90 | index works slightly different than the Django's default behaviour. Just like
91 | Django it also gathers all urls from the generated sitemaps but it also
92 | includes a new XML tag ``lastmod``. The date/time set in this tag comes
93 | from the first element of the generated file, so reverse sorting your query
94 | by your date field will keep this information accurate. This is important to
95 | inform the crawler how fresh is the information inside each sitemap inside the
96 | sitemap_index.xml.
97 |
98 | Running as celery task
99 | ----------------------
100 |
101 | If you run celery as your task runner, you should be ready to go out of the box. django-static-sitemaps includes the ``GenerateSitemap`` task which will be automatically run each ``STATICSITEMAPS_REFRESH_AFTER`` minutes (defaults to 60 ~ 1 hour). You can optionally bypass it by setting it to ``None``.
102 |
103 | Advanced settings
104 | ------------------
105 |
106 | ``STATICSITEMAPS_ROOT_DIR``
107 | Filesystem path to generate the sitemaps files to. Defaults to ``STATIC_ROOT`` directory.
108 |
109 | ``STATICSITEMAPS_USE_GZIP``
110 | Defaults to ``True``. If ``True``, gzip compression will be used when generating the sitemaps files (which is very possible by sitemaps specification).
111 |
112 | ``STATICSITEMAPS_GZIP_METHOD``
113 | Gzip method to use. Must be in ['python', 'system', ].
114 |
115 | ``STATICSITEMAPS_SYSTEM_GZIP_PATH``
116 | Path to the gzip binary if use STATICSITEMAPS_GZIP_METHOD == 'system'.
117 |
118 | ``STATICSITEMAPS_FILENAME_TEMPLATE``
119 | Template for sitemap parts. Defaults to ``sitemap-%(section)s-%(page)s.xml``.
120 |
121 | ``STATICSITEMAPS_INDEX_TEMPLATE``
122 | Template path for sitemap index. Defaults to ``static_sitemaps/sitemap_index.xml``.
123 |
124 | ``STATICSITEMAPS_URL``
125 | Set this to the URL from which you want to serve the sitemaps. Can be an URL with and without domain, e.g. http://example.com/media/sitemaps/ or /media/sitemaps/.
126 | If no domain is given, the domain of the current Django site is used. Default is STATIC_URL.
127 |
128 | ``STATICSITEMAPS_LANGUAGE``
129 | Language code to use when generating the sitemaps. Defaults to ``LANGUAGE_CODE`` setting.
130 |
131 | ``STATICSITEMAPS_REFRESH_AFTER``
132 | How often (in minutes) should the celery task be run. Defaults to 60 minutes.
133 |
134 | ``STATICSITEMAPS_MOCK_SITE``
135 | True|False setting if you want to mock the Django sites framework. Useful if you want to use package without enabling django.contrib.sites. Defaults to False.
136 |
137 | ``STATICSITEMAPS_MOCK_SITE_NAME``
138 | URL of the site your mocking. This is what will show up in your sitemap as the URL. For example: 'www.yoursite.com'. Defaults to None.
139 |
140 | ``STATICSITEMAPS_MOCK_SITE_PROTOCOL``
141 | Protocol to use when mocking above site name. Defaults to 'http'.
142 |
143 | ``STATICSITEMAPS_STORAGE``
144 | Storage class to use. Defaults to ``django.core.files.storage.FileSystemStorage``.
145 |
146 |
147 | Using a custom template
148 | -----------------------
149 |
150 | If you need to use a template different from the Django's default (for example
151 | to generate a Google News sitemap) you can extend the you Sitemap class and
152 | setting a ``sitemap_template`` attribute. For example:
153 |
154 | from django.contrib.sitemaps import GenericSitemap
155 |
156 | class GoogleNewsSitemap(GenericSitemap):
157 | sitemap_template = 'sitemap_googlenews.xml'
158 |
--------------------------------------------------------------------------------
/README.txt:
--------------------------------------------------------------------------------
1 | README.md
--------------------------------------------------------------------------------
/requirements.pip:
--------------------------------------------------------------------------------
1 | django>=1.8
2 | six>=1.11.0
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 | import static_sitemaps
3 |
4 | setup(
5 | name='django-static-sitemaps',
6 | version=static_sitemaps.__versionstr__,
7 | description='Tool for generating sitemaps as static files',
8 | long_description='\n'.join((
9 | '',
10 | )),
11 | author='Filip Varecha',
12 | author_email='filip@varecha.work',
13 | license='BSD',
14 | url='http://github.com/xaralis/django-static-sitemaps',
15 |
16 | packages=(
17 | 'static_sitemaps',
18 | 'static_sitemaps.management',
19 | 'static_sitemaps.management.commands'),
20 |
21 | include_package_data=True,
22 |
23 | classifiers=[
24 | "Development Status :: 4 - Beta",
25 | "Intended Audience :: Developers",
26 | "License :: OSI Approved :: BSD License",
27 | "Framework :: Django",
28 | "Programming Language :: Python :: 2.5",
29 | "Programming Language :: Python :: 2.6",
30 | "Programming Language :: Python :: 2.7",
31 | "Programming Language :: Python :: 3.4",
32 | "Programming Language :: Python :: 3.5",
33 | "Operating System :: POSIX",
34 | "Operating System :: POSIX :: Linux",
35 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
36 | "Topic :: Software Development :: Libraries :: Python Modules",
37 | ],
38 | install_requires=[
39 | 'setuptools>=0.6b1',
40 | 'six>=1.11.0',
41 | 'Django>=1.8',
42 | ],
43 | )
44 |
--------------------------------------------------------------------------------
/static_sitemaps/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Reusable app meant to allow users to serve sitemap files by webserver
3 | instead of Django and generate sitemaps by running cron command.
4 |
5 | This results in better performance for sites with loads of content and/or URLS.
6 |
7 | App is dependent on original django.contrib.sitemaps application which
8 | has to be included in order for this to work.
9 | """
10 | __versionstr__ = '5.0.0'
11 |
--------------------------------------------------------------------------------
/static_sitemaps/conf.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 |
3 |
4 | # Base sitemap config dict as stated in Django docs.
5 | ROOT_SITEMAP = settings.STATICSITEMAPS_ROOT_SITEMAP
6 |
7 | # Path to root location where the sitemaps will be stored.
8 | ROOT_DIR = getattr(settings, 'STATICSITEMAPS_ROOT_DIR', settings.STATIC_ROOT)
9 |
10 | # Compress the result?
11 | USE_GZIP = getattr(settings, 'STATICSITEMAPS_USE_GZIP', True)
12 |
13 | # How to compress it? Must be in ('python', 'system').
14 | GZIP_METHOD = getattr(settings, 'STATICSITEMAPS_GZIP_METHOD', 'python')
15 |
16 | # Path to system gzip binary if system method is selected.
17 | SYSTEM_GZIP_PATH = getattr(settings, 'STATICSITEMAPS_SYSTEM_GZIP_PATH', '/usr/bin/gzip')
18 |
19 | # Template how to name the resulting sitemap pages.
20 | FILENAME_TEMPLATE = getattr(settings,
21 | 'STATICSITEMAPS_FILENAME_TEMPLATE',
22 | 'sitemap-%(section)s-%(page)s.xml')
23 |
24 | # Only for backwards compatibility, same as URL.
25 | DOMAIN = getattr(settings, 'STATICSITEMAPS_DOMAIN', None)
26 |
27 | # Language of sitemaps.
28 | LANGUAGE = getattr(settings, 'STATICSITEMAPS_LANGUAGE', settings.LANGUAGE_CODE)
29 |
30 | # Template for sitemap index.
31 | INDEX_TEMPLATE = getattr(settings, 'STATICSITEMAPS_INDEX_TEMPLATE',
32 | 'static_sitemaps/sitemap_index.xml')
33 |
34 | # Storage class to use.
35 | STORAGE_CLASS = getattr(settings, 'STATICSITEMAPS_STORAGE', 'django.core.files.storage.FileSystemStorage')
36 |
37 | # How often should the celery task be run.
38 | CELERY_TASK_REPETITION = getattr(settings, 'STATICSITEMAPS_REFRESH_AFTER', 60)
39 |
40 | # URL to serve sitemaps from.
41 | _url = getattr(settings, 'STATICSITEMAPS_URL', None)
42 |
43 | # Force the protocol to use with django sites framework
44 | FORCE_PROTOCOL = getattr(settings, 'STATICSITEMAPS_FORCE_PROTOCOL', None)
45 |
46 | # Mock django sites framework
47 | MOCK_SITE = getattr(settings, 'STATICSITEMAPS_MOCK_SITE', False)
48 |
49 | # Mock django sites framework with hostname string...for example www.yoursite.com
50 | MOCK_SITE_NAME = getattr(settings, 'STATICSITEMAPS_MOCK_SITE_NAME', None)
51 |
52 | # Mock django sites framework with https | https
53 | MOCK_SITE_PROTOCOL = getattr(settings, 'STATICSITEMAPS_MOCK_SITE_PROTOCOL', 'http')
54 |
55 |
56 | def get_url():
57 | _url = getattr(settings, 'STATICSITEMAPS_URL', None)
58 | if _url is not None:
59 | return _url
60 |
61 | if DOMAIN:
62 | # Backwards compatibility.
63 | import warnings
64 | _url = DOMAIN
65 | warnings.warn('You are using STATICSITEMAPS_DOMAIN which is going to be '
66 | 'deprecated soon. Please migrate to '
67 | 'STATICSITEMAPS_URL', DeprecationWarning)
68 | elif settings.STATIC_URL.startswith('/'):
69 | # If STATIC_URL starts with '/', it is probably a relative URL to the
70 | # current domain so we append STATIC_URL.
71 | from django.contrib.sites.models import Site
72 | _url = Site.objects.get_current().domain + settings.STATIC_URL
73 | else:
74 | # If STATIC_URL starts with protocol, it is probably a special domain
75 | # for static files and we stick to it.
76 | _url = settings.STATIC_URL
77 | return _url
78 |
--------------------------------------------------------------------------------
/static_sitemaps/generator.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 |
3 | try:
4 | from cStringIO import StringIO
5 | except ImportError:
6 | from io import StringIO
7 |
8 | import gzip
9 | import hashlib
10 | import os
11 | import subprocess
12 | from six import BytesIO
13 |
14 | from django.core.exceptions import ImproperlyConfigured
15 | from django.core.files.base import ContentFile
16 | from django.core.files.storage import FileSystemStorage
17 | from django.core.paginator import EmptyPage, PageNotAnInteger
18 |
19 | try:
20 | from django.urls import reverse, NoReverseMatch
21 | except ImportError: # Django < 2.0
22 | from django.core.urlresolvers import reverse, NoReverseMatch
23 |
24 | from django.template import loader
25 | from django.utils import translation
26 | from django.utils.encoding import smart_str
27 |
28 | from static_sitemaps import conf
29 | from static_sitemaps.util import _lazy_load
30 |
31 | __author__ = 'xaralis'
32 |
33 |
34 | class SitemapGenerator(object):
35 | def __init__(self, verbosity):
36 | self.verbosity = verbosity
37 | try:
38 | self.storage = _lazy_load(conf.STORAGE_CLASS)(location=conf.ROOT_DIR)
39 | except TypeError:
40 | self.storage = _lazy_load(conf.STORAGE_CLASS)()
41 |
42 | self.sitemaps = _lazy_load(conf.ROOT_SITEMAP)
43 |
44 | if not isinstance(self.sitemaps, dict):
45 | self.sitemaps = dict(enumerate(self.sitemaps))
46 |
47 | @staticmethod
48 | def get_hash(bytestream):
49 | return hashlib.md5(bytestream).digest()
50 |
51 | @staticmethod
52 | def normalize_url(url):
53 | if url[-1] != '/':
54 | url += '/'
55 | if not url.startswith(('http://', 'https://')):
56 | protocol = conf.FORCE_PROTOCOL or 'http'
57 | prefix = '%s://' % protocol
58 | if url.startswith('/'):
59 | from django.contrib.sites.models import Site
60 | url = prefix + Site.objects.get_current().domain + url
61 | else:
62 | url = prefix + url
63 | return url
64 |
65 | def _write(self, path, output):
66 | output = bytes(output, "utf8") # botoS3 has some issues with encoding in Python 3
67 | self.storage.save(path, ContentFile(output))
68 |
69 | def read_hash(self, path):
70 | with self.storage.open(path) as f:
71 | result = self.get_hash(f.read())
72 | return result
73 |
74 | def out(self, string, min_level=1):
75 | if self.verbosity >= min_level:
76 | print(string)
77 |
78 | def write(self):
79 | self.out('Generating sitemaps.', 1)
80 | translation.activate(conf.LANGUAGE)
81 | self.write_index()
82 | translation.deactivate()
83 | self.out('Finished generating sitemaps.', 1)
84 |
85 | def write_index(self):
86 | baseurl = self.normalize_url(conf.get_url())
87 | parts = []
88 |
89 | # Collect all pages and write them.
90 | for section, site in self.sitemaps.items():
91 | if callable(site):
92 | pages = site().paginator.num_pages
93 | else:
94 | pages = site.paginator.num_pages
95 |
96 | for page in range(1, pages + 1):
97 | filename = conf.FILENAME_TEMPLATE % {'section': section,
98 | 'page': page}
99 | lastmod = self.write_page(site, page, filename)
100 |
101 | if conf.USE_GZIP:
102 | filename += '.gz'
103 |
104 | parts.append({
105 | 'location': '%s%s' % (baseurl, filename),
106 | 'lastmod': lastmod
107 | })
108 |
109 | path = os.path.join(conf.ROOT_DIR, 'sitemap.xml')
110 | self.out('Writing index file.', 2)
111 |
112 | if self.storage.exists(path):
113 | self.storage.delete(path)
114 |
115 | output = loader.render_to_string(conf.INDEX_TEMPLATE, {'sitemaps': parts})
116 | self._write(path, output)
117 |
118 | def write_page(self, site, page, filename):
119 | self.out('Writing sitemap %s.' % filename, 2)
120 | old_page_md5 = None
121 | urls = []
122 |
123 | if conf.MOCK_SITE:
124 | if conf.MOCK_SITE_NAME is None:
125 | raise ImproperlyConfigured("STATICSITEMAPS_MOCK_SITE_NAME must not be None. Try setting to www.yoursite.com")
126 | from django.contrib.sites.requests import RequestSite
127 | from django.test.client import RequestFactory
128 | rs = RequestSite(RequestFactory().get('/', SERVER_NAME=conf.MOCK_SITE_NAME))
129 | try:
130 | if callable(site):
131 | if conf.MOCK_SITE:
132 | urls.extend(site().get_urls(page, rs, protocol=conf.MOCK_SITE_PROTOCOL))
133 | else:
134 | urls.extend(site().get_urls(page, protocol=conf.FORCE_PROTOCOL))
135 | else:
136 | if conf.MOCK_SITE:
137 | urls.extend(site.get_urls(page, rs, protocol=conf.MOCK_SITE_PROTOCOL))
138 | else:
139 | urls.extend(site.get_urls(page, protocol=conf.FORCE_PROTOCOL))
140 | except EmptyPage:
141 | self.out("Page %s empty" % page)
142 | except PageNotAnInteger:
143 | self.out("No page '%s'" % page)
144 |
145 | lastmods = [lastmod for lastmod in [u.get('lastmod') for u in urls] if lastmod is not None]
146 | file_lastmod = max(lastmods) if len(lastmods) > 0 else None
147 | path = os.path.join(conf.ROOT_DIR, filename)
148 | template = getattr(site, 'sitemap_template', 'sitemap.xml')
149 |
150 | if self.storage.exists(path):
151 | old_page_md5 = self.read_hash(path)
152 | self.storage.delete(path)
153 |
154 | output = smart_str(loader.render_to_string(template, {'urlset': urls}))
155 | self._write(path, output)
156 |
157 | if conf.USE_GZIP:
158 | if conf.GZIP_METHOD not in ['python', 'system', ]:
159 | raise ImproperlyConfigured("STATICSITEMAPS_GZIP_METHOD must be in ['python', 'system']")
160 |
161 | if conf.GZIP_METHOD == 'system' and not os.path.exists(conf.SYSTEM_GZIP_PATH):
162 | raise ImproperlyConfigured('STATICSITEMAPS_SYSTEM_GZIP_PATH does not exist')
163 |
164 | if conf.GZIP_METHOD == 'system' and not isinstance(self.storage, FileSystemStorage):
165 | raise ImproperlyConfigured('system gzip method can only be used with FileSystemStorage')
166 |
167 | if conf.GZIP_METHOD == 'system':
168 | # GZIP with system gzip binary
169 | subprocess.call([conf.SYSTEM_GZIP_PATH, '-f', path, ])
170 | else:
171 | # GZIP with python gzip lib
172 | try:
173 | gzipped_path = '%s.gz' % path
174 | if self.storage.exists(gzipped_path):
175 | self.storage.delete(gzipped_path)
176 |
177 | self.out('Compressing...', 2)
178 | buf = BytesIO()
179 | with gzip.GzipFile(fileobj=buf, mode="w") as f:
180 | f.write(output.encode('utf-8'))
181 | self.storage.save(gzipped_path, ContentFile(buf.getvalue()))
182 | except OSError:
183 | self.out("Compress %s file error" % path)
184 |
185 | return file_lastmod
186 |
--------------------------------------------------------------------------------
/static_sitemaps/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xaralis/django-static-sitemaps/b514be87edf07b03f70bd0945615177c7326c2dd/static_sitemaps/management/__init__.py
--------------------------------------------------------------------------------
/static_sitemaps/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xaralis/django-static-sitemaps/b514be87edf07b03f70bd0945615177c7326c2dd/static_sitemaps/management/commands/__init__.py
--------------------------------------------------------------------------------
/static_sitemaps/management/commands/refresh_sitemap.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand
2 | from static_sitemaps.generator import SitemapGenerator
3 |
4 | __author__ = 'xaralis'
5 |
6 |
7 | class Command(BaseCommand):
8 | command = None
9 | help = 'Generates sitemaps files to a predefined directory.'
10 |
11 | def handle(self, *args, **options):
12 | generator = SitemapGenerator(int(options.get('verbosity', 0)))
13 | generator.write()
14 |
--------------------------------------------------------------------------------
/static_sitemaps/tasks.py:
--------------------------------------------------------------------------------
1 | from datetime import timedelta
2 |
3 | from celery import Task
4 |
5 | from static_sitemaps import conf
6 | from static_sitemaps.generator import SitemapGenerator
7 |
8 | __author__ = 'xaralis'
9 |
10 | # Create class conditionally so the task can be bypassed when repetition
11 | # is set to something which evaluates to False.
12 | if conf.CELERY_TASK_REPETITION:
13 | class GenerateSitemap(Task):
14 | run_every = timedelta(minutes=conf.CELERY_TASK_REPETITION)
15 |
16 | def run(self, **kwargs):
17 | generator = SitemapGenerator(verbosity=1)
18 | generator.write()
19 |
20 |
--------------------------------------------------------------------------------
/static_sitemaps/templates/static_sitemaps/sitemap_index.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | {% spaceless %}
4 | {% for site in sitemaps %}
5 |
6 | {{ site.location }}
7 | {% if site.lastmod %}
8 | {{ site.lastmod|date:"Y-m-d\TH:i:sO"|slice:":-2" }}:00
9 | {% endif %}
10 |
11 | {% endfor %}
12 | {% endspaceless %}
13 |
14 |
--------------------------------------------------------------------------------
/static_sitemaps/urls.py:
--------------------------------------------------------------------------------
1 | from static_sitemaps.views import SitemapView
2 |
3 | try:
4 | from django.urls import path, re_path
5 |
6 | urlpatterns = [
7 | path('sitemap.xml', SitemapView.as_view(), kwargs={'section': 'sitemap'}, name='static_sitemaps_index'),
8 | re_path(r'^(?Psitemap-.+)\.xml$', SitemapView.as_view()),
9 | ]
10 | except ImportError: # Django < 2.0
11 | from django.conf.urls import url
12 |
13 | urlpatterns = [
14 | url(r'^sitemap\.xml$', SitemapView.as_view(), kwargs={'section': 'sitemap'}, name='static_sitemaps_index'),
15 | url(r'^(?Psitemap-.+)\.xml$', SitemapView.as_view()),
16 | ]
17 |
--------------------------------------------------------------------------------
/static_sitemaps/util.py:
--------------------------------------------------------------------------------
1 | from importlib import import_module
2 |
3 | from django.core.exceptions import ImproperlyConfigured
4 |
5 |
6 | def _lazy_load(class_path):
7 | module, attr = class_path.rsplit('.', 1)
8 | try:
9 | mod = import_module(module)
10 | except ImportError as e:
11 | raise ImproperlyConfigured('Error importing module %s: "%s"' %
12 | (module, e))
13 | try:
14 | instance = getattr(mod, attr)
15 | return instance
16 | except AttributeError:
17 | raise ImproperlyConfigured('Module "%s" does not define a "%s" '
18 | 'class.' % (module, attr))
19 |
--------------------------------------------------------------------------------
/static_sitemaps/views.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from django.http import HttpResponse, Http404
4 | from django.views.generic import View
5 |
6 | from static_sitemaps import conf
7 | from static_sitemaps.util import _lazy_load
8 |
9 |
10 | class SitemapView(View):
11 | def get(self, request, *args, **kwargs):
12 | section = kwargs.get('section')
13 |
14 | try:
15 | storage = _lazy_load(conf.STORAGE_CLASS)(location=conf.ROOT_DIR)
16 | except TypeError:
17 | storage = _lazy_load(conf.STORAGE_CLASS)()
18 |
19 | path = os.path.join(conf.ROOT_DIR, '{}.xml'.format(section))
20 | if not storage.exists(path):
21 | raise Http404('No sitemap file found on %r. Run django-admin.py '
22 | 'refresh_sitemap first.' % path)
23 |
24 | f = storage.open(path)
25 | content = f.readlines()
26 | f.close()
27 | return HttpResponse(content, content_type='application/xml')
28 |
--------------------------------------------------------------------------------
/testapp/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == '__main__':
6 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testapp.settings')
7 | try:
8 | from django.core.management import execute_from_command_line
9 | except ImportError as exc:
10 | raise ImportError(
11 | "Couldn't import Django. Are you sure it's installed and "
12 | "available on your PYTHONPATH environment variable? Did you "
13 | "forget to activate a virtual environment?"
14 | ) from exc
15 | execute_from_command_line(sys.argv)
16 |
--------------------------------------------------------------------------------
/testapp/sitemaps/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 | !.keep
4 |
--------------------------------------------------------------------------------
/testapp/testapp/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xaralis/django-static-sitemaps/b514be87edf07b03f70bd0945615177c7326c2dd/testapp/testapp/__init__.py
--------------------------------------------------------------------------------
/testapp/testapp/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for testapp project.
3 |
4 | Generated by 'django-admin startproject' using Django 2.1.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/2.1/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/2.1/ref/settings/
11 | """
12 |
13 | import os
14 |
15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = 'h5*8vproll6#h%mps_zo8^x)u*$8gli6@36sd-b-c%axqiv8g-'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = []
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | 'django.contrib.admin',
35 | 'django.contrib.auth',
36 | 'django.contrib.contenttypes',
37 | 'django.contrib.sessions',
38 | 'django.contrib.messages',
39 | 'django.contrib.staticfiles',
40 | 'django.contrib.sites',
41 | 'django.contrib.flatpages',
42 | 'django.contrib.sitemaps',
43 |
44 | 'static_sitemaps',
45 | ]
46 |
47 | MIDDLEWARE = [
48 | 'django.middleware.security.SecurityMiddleware',
49 | 'django.contrib.sessions.middleware.SessionMiddleware',
50 | 'django.middleware.common.CommonMiddleware',
51 | 'django.middleware.csrf.CsrfViewMiddleware',
52 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
53 | 'django.contrib.messages.middleware.MessageMiddleware',
54 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
55 | 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
56 | ]
57 |
58 | ROOT_URLCONF = 'testapp.urls'
59 |
60 | TEMPLATES = [
61 | {
62 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
63 | 'DIRS': [],
64 | 'APP_DIRS': True,
65 | 'OPTIONS': {
66 | 'context_processors': [
67 | 'django.template.context_processors.debug',
68 | 'django.template.context_processors.request',
69 | 'django.contrib.auth.context_processors.auth',
70 | 'django.contrib.messages.context_processors.messages',
71 | ],
72 | },
73 | },
74 | ]
75 |
76 | WSGI_APPLICATION = 'testapp.wsgi.application'
77 |
78 |
79 | # Database
80 | # https://docs.djangoproject.com/en/2.1/ref/settings/#databases
81 |
82 | DATABASES = {
83 | 'default': {
84 | 'ENGINE': 'django.db.backends.sqlite3',
85 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
86 | }
87 | }
88 |
89 |
90 | # Password validation
91 | # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
92 |
93 | AUTH_PASSWORD_VALIDATORS = [
94 | {
95 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
96 | },
97 | {
98 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
99 | },
100 | {
101 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
102 | },
103 | {
104 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
105 | },
106 | ]
107 |
108 |
109 | # Internationalization
110 | # https://docs.djangoproject.com/en/2.1/topics/i18n/
111 |
112 | LANGUAGE_CODE = 'en-us'
113 |
114 | TIME_ZONE = 'UTC'
115 |
116 | USE_I18N = True
117 |
118 | USE_L10N = True
119 |
120 | USE_TZ = True
121 |
122 |
123 | # Static files (CSS, JavaScript, Images)
124 | # https://docs.djangoproject.com/en/2.1/howto/static-files/
125 |
126 | STATIC_URL = '/static/'
127 |
128 | STATIC_ROOT = os.path.join(BASE_DIR, 'static')
129 | STATICSITEMAPS_ROOT_DIR = os.path.join(BASE_DIR, 'sitemaps')
130 |
131 | STATICFILES_DIRS = [
132 | STATICSITEMAPS_ROOT_DIR,
133 | ]
134 |
135 | SITE_ID = 1
136 |
137 | STATICSITEMAPS_ROOT_SITEMAP = 'testapp.sitemaps.sitemaps'
138 |
--------------------------------------------------------------------------------
/testapp/testapp/sitemaps.py:
--------------------------------------------------------------------------------
1 | from django.contrib.flatpages.sitemaps import FlatPageSitemap
2 |
3 | sitemaps = {
4 | 'flatpages': FlatPageSitemap,
5 | }
6 |
--------------------------------------------------------------------------------
/testapp/testapp/urls.py:
--------------------------------------------------------------------------------
1 | """testapp URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/2.1/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: path('', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.urls import include, path
14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
15 | """
16 | from django.contrib import admin
17 | from django.urls import path, include
18 | from django.conf.urls.static import static
19 | from django.conf import settings
20 |
21 | urlpatterns = [
22 | path('admin/', admin.site.urls),
23 | path('pages/', include('django.contrib.flatpages.urls')),
24 | path('', include('static_sitemaps.urls')),
25 | ] + static(prefix=settings.STATIC_URL, show_indexes=True)
26 |
--------------------------------------------------------------------------------
/testapp/testapp/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for testapp project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testapp.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------