Fixed #20793 -- Added Last-Modified header to sitemaps.
This commit is contained in:
parent
4d8ecbdfda
commit
8f5533ab25
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue