Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #20793 -- Added Last-Modified header to sitemaps.

  • Loading branch information...
commit 8f5533ab250df07ea84f98d39808806e282468a5 1 parent 4d8ecbd
Julian Bez authored July 23, 2013 timgraham committed July 31, 2013
12  django/contrib/sitemaps/__init__.py
@@ -86,17 +86,27 @@ def get_urls(self, page=1, site=None, protocol=None):
86 86
         domain = site.domain
87 87
 
88 88
         urls = []
  89
+        latest_lastmod = None
  90
+        all_items_lastmod = True  # track if all items have a lastmod
89 91
         for item in self.paginator.page(page).object_list:
90 92
             loc = "%s://%s%s" % (protocol, domain, self.__get('location', item))
91 93
             priority = self.__get('priority', item, None)
  94
+            lastmod = self.__get('lastmod', item, None)
  95
+            if all_items_lastmod:
  96
+                all_items_lastmod = lastmod is not None
  97
+                if (all_items_lastmod and
  98
+                    (latest_lastmod is None or lastmod > latest_lastmod)):
  99
+                    latest_lastmod = lastmod
92 100
             url_info = {
93 101
                 'item':       item,
94 102
                 'location':   loc,
95  
-                'lastmod':    self.__get('lastmod', item, None),
  103
+                'lastmod':    lastmod,
96 104
                 'changefreq': self.__get('changefreq', item, None),
97 105
                 'priority':   str(priority if priority is not None else ''),
98 106
             }
99 107
             urls.append(url_info)
  108
+        if all_items_lastmod:
  109
+            self.latest_lastmod = latest_lastmod
100 110
         return urls
101 111
 
102 112
 class FlatPageSitemap(Sitemap):
15  django/contrib/sitemaps/tests/test_http.py
@@ -77,6 +77,21 @@ def test_simple_custom_sitemap(self):
77 77
 """ % (self.base_url, date.today())
78 78
         self.assertXMLEqual(response.content.decode('utf-8'), expected_content)
79 79
 
  80
+    def test_sitemap_last_modified(self):
  81
+        "Tests that Last-Modified header is set correctly"
  82
+        response = self.client.get('/lastmod/sitemap.xml')
  83
+        self.assertEqual(response['Last-Modified'], 'Wed, 13 Mar 2013 10:00:00 GMT')
  84
+
  85
+    def test_sitemap_last_modified_missing(self):
  86
+        "Tests that Last-Modified header is missing when sitemap has no lastmod"
  87
+        response = self.client.get('/generic/sitemap.xml')
  88
+        self.assertFalse(response.has_header('Last-Modified'))
  89
+
  90
+    def test_sitemap_last_modified_mixed(self):
  91
+        "Tests that Last-Modified header is omitted when lastmod not on all items"
  92
+        response = self.client.get('/lastmod-mixed/sitemap.xml')
  93
+        self.assertFalse(response.has_header('Last-Modified'))
  94
+
80 95
     @skipUnless(settings.USE_I18N, "Internationalization is not enabled")
81 96
     @override_settings(USE_L10N=True)
82 97
     def test_localized_priority(self):
28  django/contrib/sitemaps/tests/urls/http.py
@@ -15,10 +15,36 @@ class SimpleSitemap(Sitemap):
15 15
     def items(self):
16 16
         return [object()]
17 17
 
  18
+
  19
+class FixedLastmodSitemap(SimpleSitemap):
  20
+    lastmod = datetime(2013, 3, 13, 10, 0, 0)
  21
+
  22
+
  23
+class FixedLastmodMixedSitemap(Sitemap):
  24
+    changefreq = "never"
  25
+    priority = 0.5
  26
+    location = '/location/'
  27
+    loop = 0
  28
+
  29
+    def items(self):
  30
+        o1 = TestModel()
  31
+        o1.lastmod = datetime(2013, 3, 13, 10, 0, 0)
  32
+        o2 = TestModel()
  33
+        return [o1, o2]
  34
+
  35
+
18 36
 simple_sitemaps = {
19 37
     'simple': SimpleSitemap,
20 38
 }
21 39
 
  40
+fixed_lastmod_sitemaps = {
  41
+    'fixed-lastmod': FixedLastmodSitemap,
  42
+}
  43
+
  44
+fixed_lastmod__mixed_sitemaps = {
  45
+    'fixed-lastmod-mixed': FixedLastmodMixedSitemap,
  46
+}
  47
+
22 48
 generic_sitemaps = {
23 49
     'generic': GenericSitemap({'queryset': TestModel.objects.all()}),
24 50
 }
@@ -36,6 +62,8 @@ def items(self):
36 62
     (r'^simple/sitemap\.xml$', 'sitemap', {'sitemaps': simple_sitemaps}),
37 63
     (r'^simple/custom-sitemap\.xml$', 'sitemap',
38 64
         {'sitemaps': simple_sitemaps, 'template_name': 'custom_sitemap.xml'}),
  65
+    (r'^lastmod/sitemap\.xml$', 'sitemap', {'sitemaps': fixed_lastmod_sitemaps}),
  66
+    (r'^lastmod-mixed/sitemap\.xml$', 'sitemap', {'sitemaps': fixed_lastmod__mixed_sitemaps}),
39 67
     (r'^generic/sitemap\.xml$', 'sitemap', {'sitemaps': generic_sitemaps}),
40 68
     (r'^flatpages/sitemap\.xml$', 'sitemap', {'sitemaps': flatpage_sitemaps}),
41 69
     url(r'^cached/index\.xml$', cache_page(1)(views.index),
12  django/contrib/sitemaps/views.py
... ...
@@ -1,3 +1,4 @@
  1
+from calendar import timegm
1 2
 from functools import wraps
2 3
 
3 4
 from django.contrib.sites.models import get_current_site
@@ -6,6 +7,7 @@
6 7
 from django.http import Http404
7 8
 from django.template.response import TemplateResponse
8 9
 from django.utils import six
  10
+from django.utils.http import http_date
9 11
 
10 12
 def x_robots_tag(func):
11 13
     @wraps(func)
@@ -64,5 +66,11 @@ def sitemap(request, sitemaps, section=None,
64 66
             raise Http404("Page %s empty" % page)
65 67
         except PageNotAnInteger:
66 68
             raise Http404("No page '%s'" % page)
67  
-    return TemplateResponse(request, template_name, {'urlset': urls},
68  
-                            content_type=content_type)
  69
+    response = TemplateResponse(request, template_name, {'urlset': urls},
  70
+                                content_type=content_type)
  71
+    if hasattr(site, 'latest_lastmod'):
  72
+        # if latest_lastmod is defined for site, set header so as
  73
+        # ConditionalGetMiddleware is able to send 304 NOT MODIFIED
  74
+        response['Last-Modified'] = http_date(
  75
+            timegm(site.latest_lastmod.utctimetuple()))
  76
+    return response
9  docs/ref/contrib/sitemaps.txt
@@ -178,6 +178,15 @@ Sitemap class reference
178 178
         representing the last-modified date/time for *every* object returned by
179 179
         :attr:`~Sitemap.items()`.
180 180
 
  181
+        .. versionadded:: 1.7
  182
+
  183
+        If all items in a sitemap have a :attr:`~Sitemap.lastmod`, the sitemap
  184
+        generated by :func:`views.sitemap` will have a ``Last-Modified``
  185
+        header equal to the latest ``lastmod``. You can activate the
  186
+        :class:`~django.middleware.http.ConditionalGetMiddleware` to make
  187
+        Django respond appropriately to requests with an ``If-Modified-Since``
  188
+        header which will prevent sending the sitemap if it hasn't changed.
  189
+
181 190
     .. attribute:: Sitemap.changefreq
182 191
 
183 192
         **Optional.** Either a method or attribute.
6  docs/releases/1.7.txt
@@ -95,6 +95,12 @@ Minor features
95 95
 * The :djadminopt:`--no-color` option for ``django-admin.py`` allows you to
96 96
   disable the colorization of management command output.
97 97
 
  98
+* The :mod:`sitemap framework<django.contrib.sitemaps>` now makes use of
  99
+  :attr:`~django.contrib.sitemaps.Sitemap.lastmod` to set a ``Last-Modified``
  100
+  header in the response. This makes it possible for the
  101
+  :class:`~django.middleware.http.ConditionalGetMiddleware` to handle
  102
+  conditional ``GET`` requests for sitemaps which set ``lastmod``.
  103
+
98 104
 Backwards incompatible changes in 1.7
99 105
 =====================================
100 106
 

0 notes on commit 8f5533a

Please sign in to comment.
Something went wrong with that request. Please try again.