# Django 4.2.11 documentation

# Source code for django.contrib.sitemaps

    import warnings
    from urllib.parse import urlencode
    from urllib.request import urlopen

    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.urls import NoReverseMatch, reverse
    from django.utils import translation
    from django.utils.deprecation import RemovedInDjango50Warning

    PING_URL = "https://www.google.com/webmasters/tools/ping"

    class SitemapNotFound(Exception):
        pass

    def ping_google(sitemap_url=None, ping_url=PING_URL, sitemap_uses_https=True):
        """
        Alert Google that the sitemap for the current site has been updated.
        If sitemap_url is provided, it should be an absolute path to the sitemap
        for this site -- e.g., '/sitemap.xml'. If sitemap_url is not provided, this
        function will attempt to deduce it by using urls.reverse().
        """
        sitemap_full_url = _get_sitemap_full_url(sitemap_url, sitemap_uses_https)
        params = urlencode({"sitemap": sitemap_full_url})
        urlopen("%s?%s" % (ping_url, params))

    def _get_sitemap_full_url(sitemap_url, sitemap_uses_https=True):
        if not django_apps.is_installed("django.contrib.sites"):
            raise ImproperlyConfigured(
                "ping_google requires django.contrib.sites, which isn't installed."
            )

        if sitemap_url is None:
            try:
                # First, try to get the "index" sitemap URL.
                sitemap_url = reverse("django.contrib.sitemaps.views.index")
            except NoReverseMatch:
                try:
                    # Next, try for the "global" sitemap URL.
                    sitemap_url = reverse("django.contrib.sitemaps.views.sitemap")
                except NoReverseMatch:
                    pass

        if sitemap_url is None:
            raise SitemapNotFound(
                "You didn't provide a sitemap_url, and the sitemap URL couldn't be "
                "auto-detected."
            )

        Site = django_apps.get_model("sites.Site")
        current_site = Site.objects.get_current()
        scheme = "https" if sitemap_uses_https else "http"
        return "%s://%s%s" % (scheme, current_site.domain, sitemap_url)

    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
            if self.protocol is None and protocol is None:
                warnings.warn(
                    "The default sitemap protocol will be changed from 'http' to "
                    "'https' in Django 5.0. Set Sitemap.protocol to silence this "
                    "warning.",
                    category=RemovedInDjango50Warning,
                    stacklevel=2,
                )
            # RemovedInDjango50Warning: when the deprecation ends, replace 'http'
            # with 'https'.
            return self.protocol or protocol or "http"

        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