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

This commit is contained in:
Julian Bez 2013-07-23 16:25:21 +02:00 committed by Tim Graham
parent 4d8ecbdfda
commit 8f5533ab25
6 changed files with 79 additions and 3 deletions

View File

@ -86,17 +86,27 @@ class Sitemap(object):
domain = site.domain domain = site.domain
urls = [] urls = []
latest_lastmod = None
all_items_lastmod = True # track if all items have a lastmod
for item in self.paginator.page(page).object_list: for item in self.paginator.page(page).object_list:
loc = "%s://%s%s" % (protocol, domain, self.__get('location', item)) loc = "%s://%s%s" % (protocol, domain, self.__get('location', item))
priority = self.__get('priority', item, None) priority = self.__get('priority', item, None)
lastmod = self.__get('lastmod', item, None)
if all_items_lastmod:
all_items_lastmod = lastmod is not None
if (all_items_lastmod and
(latest_lastmod is None or lastmod > latest_lastmod)):
latest_lastmod = lastmod
url_info = { url_info = {
'item': item, 'item': item,
'location': loc, 'location': loc,
'lastmod': self.__get('lastmod', item, None), 'lastmod': lastmod,
'changefreq': self.__get('changefreq', item, None), 'changefreq': self.__get('changefreq', item, None),
'priority': str(priority if priority is not None else ''), 'priority': str(priority if priority is not None else ''),
} }
urls.append(url_info) urls.append(url_info)
if all_items_lastmod:
self.latest_lastmod = latest_lastmod
return urls return urls
class FlatPageSitemap(Sitemap): class FlatPageSitemap(Sitemap):

View File

@ -77,6 +77,21 @@ class HTTPSitemapTests(SitemapTestsBase):
""" % (self.base_url, date.today()) """ % (self.base_url, date.today())
self.assertXMLEqual(response.content.decode('utf-8'), expected_content) self.assertXMLEqual(response.content.decode('utf-8'), expected_content)
def test_sitemap_last_modified(self):
"Tests that Last-Modified header is set correctly"
response = self.client.get('/lastmod/sitemap.xml')
self.assertEqual(response['Last-Modified'], 'Wed, 13 Mar 2013 10:00:00 GMT')
def test_sitemap_last_modified_missing(self):
"Tests that Last-Modified header is missing when sitemap has no lastmod"
response = self.client.get('/generic/sitemap.xml')
self.assertFalse(response.has_header('Last-Modified'))
def test_sitemap_last_modified_mixed(self):
"Tests that Last-Modified header is omitted when lastmod not on all items"
response = self.client.get('/lastmod-mixed/sitemap.xml')
self.assertFalse(response.has_header('Last-Modified'))
@skipUnless(settings.USE_I18N, "Internationalization is not enabled") @skipUnless(settings.USE_I18N, "Internationalization is not enabled")
@override_settings(USE_L10N=True) @override_settings(USE_L10N=True)
def test_localized_priority(self): def test_localized_priority(self):

View File

@ -15,10 +15,36 @@ class SimpleSitemap(Sitemap):
def items(self): def items(self):
return [object()] return [object()]
class FixedLastmodSitemap(SimpleSitemap):
lastmod = datetime(2013, 3, 13, 10, 0, 0)
class FixedLastmodMixedSitemap(Sitemap):
changefreq = "never"
priority = 0.5
location = '/location/'
loop = 0
def items(self):
o1 = TestModel()
o1.lastmod = datetime(2013, 3, 13, 10, 0, 0)
o2 = TestModel()
return [o1, o2]
simple_sitemaps = { simple_sitemaps = {
'simple': SimpleSitemap, 'simple': SimpleSitemap,
} }
fixed_lastmod_sitemaps = {
'fixed-lastmod': FixedLastmodSitemap,
}
fixed_lastmod__mixed_sitemaps = {
'fixed-lastmod-mixed': FixedLastmodMixedSitemap,
}
generic_sitemaps = { generic_sitemaps = {
'generic': GenericSitemap({'queryset': TestModel.objects.all()}), 'generic': GenericSitemap({'queryset': TestModel.objects.all()}),
} }
@ -36,6 +62,8 @@ urlpatterns = patterns('django.contrib.sitemaps.views',
(r'^simple/sitemap\.xml$', 'sitemap', {'sitemaps': simple_sitemaps}), (r'^simple/sitemap\.xml$', 'sitemap', {'sitemaps': simple_sitemaps}),
(r'^simple/custom-sitemap\.xml$', 'sitemap', (r'^simple/custom-sitemap\.xml$', 'sitemap',
{'sitemaps': simple_sitemaps, 'template_name': 'custom_sitemap.xml'}), {'sitemaps': simple_sitemaps, 'template_name': 'custom_sitemap.xml'}),
(r'^lastmod/sitemap\.xml$', 'sitemap', {'sitemaps': fixed_lastmod_sitemaps}),
(r'^lastmod-mixed/sitemap\.xml$', 'sitemap', {'sitemaps': fixed_lastmod__mixed_sitemaps}),
(r'^generic/sitemap\.xml$', 'sitemap', {'sitemaps': generic_sitemaps}), (r'^generic/sitemap\.xml$', 'sitemap', {'sitemaps': generic_sitemaps}),
(r'^flatpages/sitemap\.xml$', 'sitemap', {'sitemaps': flatpage_sitemaps}), (r'^flatpages/sitemap\.xml$', 'sitemap', {'sitemaps': flatpage_sitemaps}),
url(r'^cached/index\.xml$', cache_page(1)(views.index), url(r'^cached/index\.xml$', cache_page(1)(views.index),

View File

@ -1,3 +1,4 @@
from calendar import timegm
from functools import wraps from functools import wraps
from django.contrib.sites.models import get_current_site from django.contrib.sites.models import get_current_site
@ -6,6 +7,7 @@ from django.core.paginator import EmptyPage, PageNotAnInteger
from django.http import Http404 from django.http import Http404
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils import six from django.utils import six
from django.utils.http import http_date
def x_robots_tag(func): def x_robots_tag(func):
@wraps(func) @wraps(func)
@ -64,5 +66,11 @@ def sitemap(request, sitemaps, section=None,
raise Http404("Page %s empty" % page) raise Http404("Page %s empty" % page)
except PageNotAnInteger: except PageNotAnInteger:
raise Http404("No page '%s'" % page) raise Http404("No page '%s'" % page)
return TemplateResponse(request, template_name, {'urlset': urls}, response = TemplateResponse(request, template_name, {'urlset': urls},
content_type=content_type) content_type=content_type)
if hasattr(site, 'latest_lastmod'):
# if latest_lastmod is defined for site, set header so as
# ConditionalGetMiddleware is able to send 304 NOT MODIFIED
response['Last-Modified'] = http_date(
timegm(site.latest_lastmod.utctimetuple()))
return response

View File

@ -178,6 +178,15 @@ Sitemap class reference
representing the last-modified date/time for *every* object returned by representing the last-modified date/time for *every* object returned by
:attr:`~Sitemap.items()`. :attr:`~Sitemap.items()`.
.. versionadded:: 1.7
If all items in a sitemap have a :attr:`~Sitemap.lastmod`, the sitemap
generated by :func:`views.sitemap` will have a ``Last-Modified``
header equal to the latest ``lastmod``. You can activate the
:class:`~django.middleware.http.ConditionalGetMiddleware` to make
Django respond appropriately to requests with an ``If-Modified-Since``
header which will prevent sending the sitemap if it hasn't changed.
.. attribute:: Sitemap.changefreq .. attribute:: Sitemap.changefreq
**Optional.** Either a method or attribute. **Optional.** Either a method or attribute.

View File

@ -95,6 +95,12 @@ Minor features
* The :djadminopt:`--no-color` option for ``django-admin.py`` allows you to * The :djadminopt:`--no-color` option for ``django-admin.py`` allows you to
disable the colorization of management command output. disable the colorization of management command output.
* The :mod:`sitemap framework<django.contrib.sitemaps>` now makes use of
:attr:`~django.contrib.sitemaps.Sitemap.lastmod` to set a ``Last-Modified``
header in the response. This makes it possible for the
:class:`~django.middleware.http.ConditionalGetMiddleware` to handle
conditional ``GET`` requests for sitemaps which set ``lastmod``.
Backwards incompatible changes in 1.7 Backwards incompatible changes in 1.7
===================================== =====================================