Code

Ticket #8995: django-sitemap-protocol.diff

File django-sitemap-protocol.diff, 15.1 KB (added by john, 3 years ago)

Backward-compatible patch with docs

Line 
1commit dd6a1c57bf8d884ace441488b067145581a56559
2Author: John Hensley <john@fairviewcomputing.com>
3Date:   Fri Sep 30 10:39:22 2011 -0400
4
5    Support specification of sitemap protocol.
6
7diff --git a/django/contrib/sitemaps/__init__.py b/django/contrib/sitemaps/__init__.py
8index 2ce919f..7aac8ad 100644
9--- a/django/contrib/sitemaps/__init__.py
10+++ b/django/contrib/sitemaps/__init__.py
11@@ -40,6 +40,10 @@ class Sitemap(object):
12     # http://sitemaps.org/protocol.php#index.
13     limit = 50000
14 
15+    # If protocol is None, the sitemap's URLs will be constructed
16+    # using the protocol with which the sitemap was requested.
17+    protocol = None
18+
19     def __get(self, name, obj, default=None):
20         try:
21             attr = getattr(self, name)
22@@ -71,9 +75,16 @@ class Sitemap(object):
23             if site is None:
24                 raise ImproperlyConfigured("In order to use Sitemaps you must either use the sites framework or pass in a Site or RequestSite object in your view code.")
25 
26+        if self.protocol is not None:
27+            protocol = self.protocol
28+        elif hasattr(self, 'request'):
29+            protocol = self.request.is_secure() and 'https' or 'http'
30+        else:
31+            protocol = 'http'
32+
33         urls = []
34         for item in self.paginator.page(page).object_list:
35-            loc = "http://%s%s" % (site.domain, self.__get('location', item))
36+            loc = "%s://%s%s" % (protocol, site.domain, self.__get('location', item))
37             priority = self.__get('priority', item, None)
38             url_info = {
39                 'item':       item,
40diff --git a/django/contrib/sitemaps/tests/__init__.py b/django/contrib/sitemaps/tests/__init__.py
41index c5b483c..be28c8f 100644
42--- a/django/contrib/sitemaps/tests/__init__.py
43+++ b/django/contrib/sitemaps/tests/__init__.py
44@@ -1 +1,2 @@
45 from django.contrib.sitemaps.tests.basic import *
46+from django.contrib.sitemaps.tests.https import *
47diff --git a/django/contrib/sitemaps/tests/https.py b/django/contrib/sitemaps/tests/https.py
48new file mode 100644
49index 0000000..4646215
50--- /dev/null
51+++ b/django/contrib/sitemaps/tests/https.py
52@@ -0,0 +1,184 @@
53+import os
54+from datetime import date
55+from django.conf import settings
56+from django.contrib.auth.models import User
57+from django.contrib.sitemaps import Sitemap, GenericSitemap
58+from django.contrib.sites.models import Site
59+from django.core.exceptions import ImproperlyConfigured
60+from django.test import TestCase
61+from django.utils.unittest import skipUnless
62+from django.utils.formats import localize
63+from django.utils.translation import activate, deactivate
64+
65+
66+class HTTPSSitemapTests(TestCase):
67+    urls = 'django.contrib.sitemaps.tests.urls_https'
68+
69+    def setUp(self):
70+        if Site._meta.installed:
71+            self.base_url = 'https://example.com'
72+        else:
73+            self.base_url = 'https://testserver'
74+        self.old_USE_L10N = settings.USE_L10N
75+        self.old_Site_meta_installed = Site._meta.installed
76+        self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS
77+        self.old_Site_meta_installed = Site._meta.installed
78+        settings.TEMPLATE_DIRS = (
79+            os.path.join(os.path.dirname(__file__), 'templates'),
80+        )
81+        # Create a user that will double as sitemap content
82+        User.objects.create_user('testuser', 'test@example.com', 's3krit')
83+
84+    def tearDown(self):
85+        settings.USE_L10N = self.old_USE_L10N
86+        Site._meta.installed = self.old_Site_meta_installed
87+        settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS
88+        Site._meta.installed = self.old_Site_meta_installed
89+
90+    def test_simple_sitemap_index(self):
91+        "A simple sitemap index can be rendered"
92+        # Retrieve the sitemap.
93+        response = self.client.get('/simple/index.xml')
94+        # Check for all the important bits:
95+        self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
96+<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
97+<sitemap><loc>%s/simple/sitemap-simple.xml</loc></sitemap>
98+</sitemapindex>
99+""" % self.base_url)
100+
101+    def test_simple_sitemap_custom_index(self):
102+        "A simple sitemap index can be rendered with a custom template"
103+        # Retrieve the sitemap.
104+        response = self.client.get('/simple/custom-index.xml')
105+        # Check for all the important bits:
106+        self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
107+<!-- This is a customised template -->
108+<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
109+<sitemap><loc>%s/simple/sitemap-simple.xml</loc></sitemap>
110+</sitemapindex>
111+""" % self.base_url)
112+
113+    def test_simple_sitemap(self):
114+        "A simple sitemap can be rendered"
115+        # Retrieve the sitemap.
116+        response = self.client.get('/simple/sitemap.xml')
117+        # Check for all the important bits:
118+        self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
119+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
120+<url><loc>%s/location/</loc><lastmod>%s</lastmod><changefreq>never</changefreq><priority>0.5</priority></url>
121+</urlset>
122+""" % (self.base_url, date.today().strftime('%Y-%m-%d')))
123+
124+    def test_simple_custom_sitemap(self):
125+        "A simple sitemap can be rendered with a custom template"
126+        # Retrieve the sitemap.
127+        response = self.client.get('/simple/custom-sitemap.xml')
128+        # Check for all the important bits:
129+        self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
130+<!-- This is a customised template -->
131+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
132+<url><loc>%s/location/</loc><lastmod>%s</lastmod><changefreq>never</changefreq><priority>0.5</priority></url>
133+</urlset>
134+""" % (self.base_url, date.today().strftime('%Y-%m-%d')))
135+
136+    @skipUnless(settings.USE_I18N, "Internationalization is not enabled")
137+    def test_localized_priority(self):
138+        "The priority value should not be localized (Refs #14164)"
139+        # Localization should be active
140+        settings.USE_L10N = True
141+        activate('fr')
142+        self.assertEqual(u'0,3', localize(0.3))
143+
144+        # Retrieve the sitemap. Check that priorities
145+        # haven't been rendered in localized format
146+        response = self.client.get('/simple/sitemap.xml')
147+        self.assertContains(response, '<priority>0.5</priority>')
148+        self.assertContains(response, '<lastmod>%s</lastmod>' % date.today().strftime('%Y-%m-%d'))
149+        deactivate()
150+
151+    def test_generic_sitemap(self):
152+        "A minimal generic sitemap can be rendered"
153+        # Retrieve the sitemap.
154+        response = self.client.get('/generic/sitemap.xml')
155+
156+        expected = ''
157+        for username in User.objects.values_list("username", flat=True):
158+            expected += "<url><loc>%s/users/%s/</loc></url>" % (self.base_url, username)
159+        # Check for all the important bits:
160+        self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
161+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
162+%s
163+</urlset>
164+""" % expected)
165+
166+    @skipUnless("django.contrib.flatpages" in settings.INSTALLED_APPS, "django.contrib.flatpages app not installed.")
167+    def test_flatpage_sitemap(self):
168+        "Basic FlatPage sitemap test"
169+
170+        # Import FlatPage inside the test so that when django.contrib.flatpages
171+        # is not installed we don't get problems trying to delete Site
172+        # objects (FlatPage has an M2M to Site, Site.delete() tries to
173+        # delete related objects, but the M2M table doesn't exist.
174+        from django.contrib.flatpages.models import FlatPage
175+
176+        public = FlatPage.objects.create(
177+            url=u'/public/',
178+            title=u'Public Page',
179+            enable_comments=True,
180+            registration_required=False,
181+        )
182+        public.sites.add(settings.SITE_ID)
183+        private = FlatPage.objects.create(
184+            url=u'/private/',
185+            title=u'Private Page',
186+            enable_comments=True,
187+            registration_required=True
188+        )
189+        private.sites.add(settings.SITE_ID)
190+        response = self.client.get('/flatpages/sitemap.xml')
191+        # Public flatpage should be in the sitemap
192+        self.assertContains(response, '<loc>%s%s</loc>' % (self.base_url, public.url))
193+        # Private flatpage should not be in the sitemap
194+        self.assertNotContains(response, '<loc>%s%s</loc>' % (self.base_url, private.url))
195+
196+    def test_requestsite_sitemap(self):
197+        # Make sure hitting the flatpages sitemap without the sites framework
198+        # installed doesn't raise an exception
199+        Site._meta.installed = False
200+        # Retrieve the sitemap.
201+        response = self.client.get('/simple/sitemap.xml')
202+        # Check for all the important bits:
203+        self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
204+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
205+<url><loc>https://testserver/location/</loc><lastmod>%s</lastmod><changefreq>never</changefreq><priority>0.5</priority></url>
206+</urlset>
207+""" % date.today().strftime('%Y-%m-%d'))
208+
209+    @skipUnless("django.contrib.sites" in settings.INSTALLED_APPS, "django.contrib.sites app not installed.")
210+    def test_sitemap_get_urls_no_site_1(self):
211+        """
212+        Check we get ImproperlyConfigured if we don't pass a site object to
213+        Sitemap.get_urls and no Site objects exist
214+        """
215+        Site.objects.all().delete()
216+        self.assertRaises(ImproperlyConfigured, Sitemap().get_urls)
217+
218+    def test_sitemap_get_urls_no_site_2(self):
219+        """
220+        Check we get ImproperlyConfigured when we don't pass a site object to
221+        Sitemap.get_urls if Site objects exists, but the sites framework is not
222+        actually installed.
223+        """
224+        Site._meta.installed = False
225+        self.assertRaises(ImproperlyConfigured, Sitemap().get_urls)
226+
227+    def test_sitemap_item(self):
228+        """
229+        Check to make sure that the raw item is included with each
230+        Sitemap.get_url() url result.
231+        """
232+        user_sitemap = GenericSitemap({'queryset': User.objects.all()})
233+        def is_user(url):
234+            return isinstance(url['item'], User)
235+        item_in_url_info = all(map(is_user, user_sitemap.get_urls()))
236+        self.assertTrue(item_in_url_info)
237diff --git a/django/contrib/sitemaps/tests/urls_https.py b/django/contrib/sitemaps/tests/urls_https.py
238new file mode 100644
239index 0000000..7ff0a1d
240--- /dev/null
241+++ b/django/contrib/sitemaps/tests/urls_https.py
242@@ -0,0 +1,44 @@
243+from datetime import datetime
244+from django.conf.urls.defaults import *
245+from django.contrib.sitemaps import Sitemap, GenericSitemap, FlatPageSitemap
246+from django.contrib.auth.models import User
247+
248+class HTTPSSimpleSitemap(Sitemap):
249+    changefreq = "never"
250+    priority = 0.5
251+    location = '/location/'
252+    lastmod = datetime.now()
253+    protocol = 'https'
254+
255+    def items(self):
256+        return [object()]
257+
258+class HTTPSGenericSitemap(GenericSitemap):
259+    protocol = 'https'
260+
261+class HTTPSFlatPageSitemap(FlatPageSitemap):
262+    protocol = 'https'
263+
264+simple_sitemaps = {
265+    'simple': HTTPSSimpleSitemap,
266+}
267+
268+generic_sitemaps = {
269+    'generic': HTTPSGenericSitemap({
270+        'queryset': User.objects.all()
271+    }),
272+}
273+
274+flatpage_sitemaps = {
275+    'flatpages': HTTPSFlatPageSitemap,
276+}
277+
278+urlpatterns = patterns('django.contrib.sitemaps.views',
279+    (r'^simple/index\.xml$', 'index', {'sitemaps': simple_sitemaps}),
280+    (r'^simple/custom-index\.xml$', 'index', {'sitemaps': simple_sitemaps, 'template_name': 'custom_sitemap_index.xml'}),
281+    (r'^simple/sitemap-(?P<section>.+)\.xml$', 'sitemap', {'sitemaps': simple_sitemaps}),
282+    (r'^simple/sitemap\.xml$', 'sitemap', {'sitemaps': simple_sitemaps}),
283+    (r'^simple/custom-sitemap\.xml$', 'sitemap', {'sitemaps': simple_sitemaps, 'template_name': 'custom_sitemap.xml'}),
284+    (r'^generic/sitemap\.xml$', 'sitemap', {'sitemaps': generic_sitemaps}),
285+    (r'^flatpages/sitemap\.xml$', 'sitemap', {'sitemaps': flatpage_sitemaps}),
286+)
287diff --git a/django/contrib/sitemaps/views.py b/django/contrib/sitemaps/views.py
288index d82146c..3696436 100644
289--- a/django/contrib/sitemaps/views.py
290+++ b/django/contrib/sitemaps/views.py
291@@ -9,13 +9,16 @@ def index(request, sitemaps,
292         template_name='sitemap_index.xml', mimetype='application/xml'):
293     current_site = get_current_site(request)
294     sites = []
295-    protocol = request.is_secure() and 'https' or 'http'
296+    request_protocol = request.is_secure() and 'https' or 'http'
297     for section, site in sitemaps.items():
298-        site.request = request
299         if callable(site):
300-            pages = site().paginator.num_pages
301+            site = site()
302+        if site.protocol is not None:
303+            protocol = site.protocol
304         else:
305-            pages = site.paginator.num_pages
306+            protocol = request_protocol
307+        site.request = request
308+        pages = site.paginator.num_pages
309         sitemap_url = urlresolvers.reverse('django.contrib.sitemaps.views.sitemap', kwargs={'section': section})
310         sites.append('%s://%s%s' % (protocol, current_site.domain, sitemap_url))
311         if pages > 1:
312@@ -43,4 +46,4 @@ def sitemap(request, sitemaps, section=None,
313             raise Http404("Page %s empty" % page)
314         except PageNotAnInteger:
315             raise Http404("No page '%s'" % page)
316-    return TemplateResponse(request, template_name, {'urlset': urls}, mimetype=mimetype)
317\ No newline at end of file
318+    return TemplateResponse(request, template_name, {'urlset': urls}, mimetype=mimetype)
319diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt
320index ecc5024..0ad28b2 100644
321--- a/docs/ref/contrib/sitemaps.txt
322+++ b/docs/ref/contrib/sitemaps.txt
323@@ -161,6 +161,9 @@ Sitemap class reference
324         the ``get_absolute_url()`` method on each object as returned by
325         :attr:`~Sitemap.items()`.
326 
327+       To specify a protocol other than http use
328+        :attr:`~Sitemap.protocol` below.
329+
330     .. attribute:: Sitemap.lastmod
331 
332         **Optional.** Either a method or attribute.
333@@ -193,6 +196,18 @@ Sitemap class reference
334             * ``'yearly'``
335             * ``'never'``
336 
337+    .. attribute:: Sitemap.protocol
338+
339+        **Optional.** The protocol to use in the sitemap URLs.
340+
341+        If specified, the protocol attribute will be used to construct the
342+        sitemap URLs. If not, the protocol with which the sitemap was
343+        requested will be used. If the sitemap is built outside the context
344+        of a request, the default is ``'http'``.
345+
346+        .. versionadded:: 1.4
347+
348+
349     .. method:: Sitemap.priority
350 
351         **Optional.** Either a method or attribute.
352diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
353index 29fd5ef..81e1b6e 100644
354--- a/docs/releases/1.4.txt
355+++ b/docs/releases/1.4.txt
356@@ -340,6 +340,10 @@ Django 1.4 also includes several smaller improvements worth noting:
357   be able to retrieve a translation string without displaying it but setting
358   a template context variable instead.
359 
360+* The :doc:`Sitemaps <ref/contrib/sitemaps>` framework can now handle links
361+  that use https protocol using the new class attribute
362+  :attr:`Sitemap.protocol` attribute.
363+
364 .. _backwards-incompatible-changes-1.4:
365 
366 Backwards incompatible changes in 1.4