| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- from django.apps import apps as django_apps
- from django.conf import settings
- from django.core import paginator
- from django.core.exceptions import ImproperlyConfigured
- from django.utils import translation
- class Sitemap:
- # This limit is defined by Google. See the index documentation at
- # https://www.sitemaps.org/protocol.html#index.
- limit = 50000
- # If protocol is None, the URLs in the sitemap will use the protocol
- # with which the sitemap was requested.
- protocol = None
- # Enables generating URLs for all languages.
- i18n = False
- # Override list of languages to use.
- languages = None
- # Enables generating alternate/hreflang links.
- alternates = False
- # Add an alternate/hreflang link with value 'x-default'.
- x_default = False
- def _get(self, name, item, default=None):
- try:
- attr = getattr(self, name)
- except AttributeError:
- return default
- if callable(attr):
- if self.i18n:
- # Split the (item, lang_code) tuples again for the location,
- # priority, lastmod and changefreq method calls.
- item, lang_code = item
- return attr(item)
- return attr
- def get_languages_for_item(self, item):
- """Languages for which this item is displayed."""
- return self._languages()
- def _languages(self):
- if self.languages is not None:
- return self.languages
- return [lang_code for lang_code, _ in settings.LANGUAGES]
- def _items(self):
- if self.i18n:
- # Create (item, lang_code) tuples for all items and languages.
- # This is necessary to paginate with all languages already considered.
- items = [
- (item, lang_code)
- for item in self.items()
- for lang_code in self.get_languages_for_item(item)
- ]
- return items
- return self.items()
- def _location(self, item, force_lang_code=None):
- if self.i18n:
- obj, lang_code = item
- # Activate language from item-tuple or forced one before calling location.
- with translation.override(force_lang_code or lang_code):
- return self._get("location", item)
- return self._get("location", item)
- @property
- def paginator(self):
- return paginator.Paginator(self._items(), self.limit)
- def items(self):
- return []
- def location(self, item):
- return item.get_absolute_url()
- def get_protocol(self, protocol=None):
- # Determine protocol
- return self.protocol or protocol or "https"
- def get_domain(self, site=None):
- # Determine domain
- if site is None:
- if django_apps.is_installed("django.contrib.sites"):
- Site = django_apps.get_model("sites.Site")
- try:
- site = Site.objects.get_current()
- except Site.DoesNotExist:
- pass
- if site is None:
- raise ImproperlyConfigured(
- "To use sitemaps, either enable the sites framework or pass "
- "a Site/RequestSite object in your view."
- )
- return site.domain
- def get_urls(self, page=1, site=None, protocol=None):
- protocol = self.get_protocol(protocol)
- domain = self.get_domain(site)
- return self._urls(page, protocol, domain)
- def get_latest_lastmod(self):
- if not hasattr(self, "lastmod"):
- return None
- if callable(self.lastmod):
- try:
- return max([self.lastmod(item) for item in self.items()], default=None)
- except TypeError:
- return None
- else:
- return self.lastmod
- def _urls(self, page, protocol, domain):
- urls = []
- latest_lastmod = None
- all_items_lastmod = True # track if all items have a lastmod
- paginator_page = self.paginator.page(page)
- for item in paginator_page.object_list:
- loc = f"{protocol}://{domain}{self._location(item)}"
- priority = self._get("priority", item)
- lastmod = self._get("lastmod", item)
- 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 = {
- "item": item,
- "location": loc,
- "lastmod": lastmod,
- "changefreq": self._get("changefreq", item),
- "priority": str(priority if priority is not None else ""),
- "alternates": [],
- }
- if self.i18n and self.alternates:
- item_languages = self.get_languages_for_item(item[0])
- for lang_code in item_languages:
- loc = f"{protocol}://{domain}{self._location(item, lang_code)}"
- url_info["alternates"].append(
- {
- "location": loc,
- "lang_code": lang_code,
- }
- )
- if self.x_default and settings.LANGUAGE_CODE in item_languages:
- lang_code = settings.LANGUAGE_CODE
- loc = f"{protocol}://{domain}{self._location(item, lang_code)}"
- loc = loc.replace(f"/{lang_code}/", "/", 1)
- url_info["alternates"].append(
- {
- "location": loc,
- "lang_code": "x-default",
- }
- )
- urls.append(url_info)
- if all_items_lastmod and latest_lastmod:
- self.latest_lastmod = latest_lastmod
- return urls
- class GenericSitemap(Sitemap):
- priority = None
- changefreq = None
- def __init__(self, info_dict, priority=None, changefreq=None, protocol=None):
- self.queryset = info_dict["queryset"]
- self.date_field = info_dict.get("date_field")
- self.priority = self.priority or priority
- self.changefreq = self.changefreq or changefreq
- self.protocol = self.protocol or protocol
- def items(self):
- # Make sure to return a clone; we don't want premature evaluation.
- return self.queryset.filter()
- def lastmod(self, item):
- if self.date_field is not None:
- return getattr(item, self.date_field)
- return None
- def get_latest_lastmod(self):
- if self.date_field is not None:
- return (
- self.queryset.order_by("-" + self.date_field)
- .values_list(self.date_field, flat=True)
- .first()
- )
- return None
|