Ticket #33418: __init__.py

File __init__.py, 8.1 KB (added by U-Eike, 2 years ago)

Adjustment made to sitemap initializer

Line 
1import warnings
2from urllib.parse import urlencode
3from urllib.request import urlopen
4
5from django.apps import apps as django_apps
6from django.conf import settings
7from django.core import paginator
8from django.core.exceptions import ImproperlyConfigured
9from django.urls import NoReverseMatch, reverse
10from django.utils import translation
11from django.utils.deprecation import RemovedInDjango50Warning
12
13PING_URL = "https://www.google.com/webmasters/tools/ping"
14
15
16class SitemapNotFound(Exception):
17 pass
18
19
20def ping_google(sitemap_url=None, ping_url=PING_URL, sitemap_uses_https=True):
21 """
22 Alert Google that the sitemap for the current site has been updated.
23 If sitemap_url is provided, it should be an absolute path to the sitemap
24 for this site -- e.g., '/sitemap.xml'. If sitemap_url is not provided, this
25 function will attempt to deduce it by using urls.reverse().
26 """
27 sitemap_full_url = _get_sitemap_full_url(sitemap_url, sitemap_uses_https)
28 params = urlencode({'sitemap': sitemap_full_url})
29 urlopen('%s?%s' % (ping_url, params))
30
31
32def _get_sitemap_full_url(sitemap_url, sitemap_uses_https=True):
33 if not django_apps.is_installed('django.contrib.sites'):
34 raise ImproperlyConfigured("ping_google requires django.contrib.sites, which isn't installed.")
35
36 if sitemap_url is None:
37 try:
38 # First, try to get the "index" sitemap URL.
39 sitemap_url = reverse('django.contrib.sitemaps.views.index')
40 except NoReverseMatch:
41 try:
42 # Next, try for the "global" sitemap URL.
43 sitemap_url = reverse('django.contrib.sitemaps.views.sitemap')
44 except NoReverseMatch:
45 pass
46
47 if sitemap_url is None:
48 raise SitemapNotFound("You didn't provide a sitemap_url, and the sitemap URL couldn't be auto-detected.")
49
50 Site = django_apps.get_model('sites.Site')
51 current_site = Site.objects.get_current()
52 scheme = 'https' if sitemap_uses_https else 'http'
53 return '%s://%s%s' % (scheme, current_site.domain, sitemap_url)
54
55
56class Sitemap:
57 # This limit is defined by Google. See the index documentation at
58 # https://www.sitemaps.org/protocol.html#index.
59 limit = 50000
60
61 # If protocol is None, the URLs in the sitemap will use the protocol
62 # with which the sitemap was requested.
63 protocol = None
64
65 # Enables generating URLs for all languages.
66 i18n = False
67
68 # Override list of languages to use.
69 languages = None
70
71 # Enables generating alternate/hreflang links.
72 alternates = False
73
74 # Add an alternate/hreflang link with value 'x-default'.
75 x_default = False
76
77 def _get(self, name, item, default=None):
78 try:
79 attr = getattr(self, name)
80 except AttributeError:
81 return default
82 if callable(attr):
83 if self.i18n:
84 # Split the (item, lang_code) tuples again for the location,
85 # priority, lastmod and changefreq method calls.
86 item, lang_code = item
87 return attr(item)
88 return attr
89
90 def _languages(self):
91 if self.languages is not None:
92 return self.languages
93 return [lang_code for lang_code, _ in settings.LANGUAGES]
94
95 def _items(self):
96 if self.i18n:
97 # Create (item, lang_code) tuples for all items and languages.
98 # This is necessary to paginate with all languages already considered.
99 items = [
100 (item, lang_code)
101 for lang_code in self._languages()
102 for item in self.items()
103 ]
104 return items
105 return self.items()
106
107 def _location(self, item, force_lang_code=None):
108 if self.i18n:
109 obj, lang_code = item
110 # Activate language from item-tuple or forced one before calling location.
111 with translation.override(force_lang_code or lang_code):
112 return self._get('location', item)
113 return self._get('location', item)
114
115 @property
116 def paginator(self):
117 return paginator.Paginator(self._items(), self.limit)
118
119 def items(self):
120 return []
121
122 def location(self, item):
123 return item.get_absolute_url()
124
125 def get_protocol(self, protocol=None):
126 # Determine protocol
127 if self.protocol is None and protocol is None:
128 warnings.warn(
129 "The default sitemap protocol will be changed from 'http' to "
130 "'https' in Django 5.0. Set Sitemap.protocol to silence this "
131 "warning.",
132 category=RemovedInDjango50Warning,
133 stacklevel=2,
134 )
135 # RemovedInDjango50Warning: when the deprecation ends, replace 'http'
136 # with 'https'.
137 return self.protocol or protocol or 'http'
138
139 def get_domain(self, site=None):
140 # Determine domain
141 if site is None:
142 if django_apps.is_installed('django.contrib.sites'):
143 Site = django_apps.get_model('sites.Site')
144 try:
145 site = Site.objects.get_current()
146 except Site.DoesNotExist:
147 pass
148 if site is None:
149 raise ImproperlyConfigured(
150 "To use sitemaps, either enable the sites framework or pass "
151 "a Site/RequestSite object in your view."
152 )
153 return site.domain
154
155 def get_urls(self, page=1, site=None, protocol=None):
156 protocol = self.get_protocol(protocol)
157 domain = self.get_domain(site)
158 return self._urls(page, protocol, domain)
159
160 def _urls(self, page, protocol, domain):
161 urls = []
162 latest_lastmod = None
163 all_items_lastmod = True # track if all items have a lastmod
164
165 paginator_page = self.paginator.page(page)
166 for item in paginator_page.object_list:
167 loc = f'{protocol}://{domain}{self._location(item)}'
168 priority = self._get('priority', item)
169 lastmod = self._get('lastmod', item)
170
171 if all_items_lastmod:
172 all_items_lastmod = lastmod is not None
173 if (all_items_lastmod and
174 (latest_lastmod is None or lastmod > latest_lastmod)):
175 latest_lastmod = lastmod
176
177 url_info = {
178 'item': item,
179 'location': loc,
180 'lastmod': lastmod,
181 'changefreq': self._get('changefreq', item),
182 'priority': str(priority if priority is not None else ''),
183 'alternates': [],
184 }
185
186 if self.i18n and self.alternates:
187 for lang_code in self._languages():
188 loc = f'{protocol}://{domain}/{lang_code}{self._location(item, lang_code)}'
189 url_info['alternates'].append({
190 'location': loc,
191 'lang_code': lang_code,
192 })
193 if self.x_default:
194 lang_code = settings.LANGUAGE_CODE
195 loc = f'{protocol}://{domain}{self._location(item, lang_code)}'
196 loc = loc.replace(f'/{lang_code}/', '/', 1)
197 url_info['alternates'].append({
198 'location': loc,
199 'lang_code': 'x-default',
200 })
201
202 urls.append(url_info)
203
204 if all_items_lastmod and latest_lastmod:
205 self.latest_lastmod = latest_lastmod
206
207 return urls
208
209
210class GenericSitemap(Sitemap):
211 priority = None
212 changefreq = None
213
214 def __init__(self, info_dict, priority=None, changefreq=None, protocol=None):
215 self.queryset = info_dict['queryset']
216 self.date_field = info_dict.get('date_field')
217 self.priority = self.priority or priority
218 self.changefreq = self.changefreq or changefreq
219 self.protocol = self.protocol or protocol
220
221 def items(self):
222 # Make sure to return a clone; we don't want premature evaluation.
223 return self.queryset.filter()
224
225 def lastmod(self, item):
226 if self.date_field is not None:
227 return getattr(item, self.date_field)
228 return None
Back to Top