digitalfabrik/integreat-cms

View on GitHub
integreat_cms/sitemap/sitemaps.py

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
"""
This module contains all sitemap classes which are all based on :class:`django.contrib.sitemaps.Sitemap`.
"""

from __future__ import annotations

import logging
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING
from urllib.parse import urlsplit

from django.conf import settings
from django.contrib.sitemaps import Sitemap

from ..cms.constants import status
from ..cms.models import (
    EventTranslation,
    OfferTemplate,
    PageTranslation,
    POITranslation,
)

if TYPE_CHECKING:
    from datetime import datetime
    from typing import Any

    from django.db.models.query import QuerySet

    from ..cms.models import Language, Region
    from ..cms.models.abstract_content_translation import AbstractContentTranslation

logger = logging.getLogger(__name__)


class WebappSitemap(ABC, Sitemap):
    """
    This is an abstract base class for all webapp sitemaps.
    """

    #: The default change frequency for all sitemap's urls
    changefreq: str = "monthly"

    #: The default priority for all sitemap's urls
    priority: float = 0.5

    @property
    @abstractmethod
    def queryset(self) -> QuerySet[OfferTemplate | AbstractContentTranslation]:
        """
        Each subclass needs at least a ``queryset`` attribute defined.
        """

    def __init__(self, region: Region, language: Language) -> None:
        """
        This init function sets the region and language parameters.

        :param region: The region of this sitemap's urls
        :param language: The language of this sitemap's urls
        """
        self.region = region
        self.language = language

    def items(self) -> QuerySet[OfferTemplate | AbstractContentTranslation]:
        """
        This functions returns the public translations contained in this sitemap.

        :return: The queryset of translation objects
        """
        return self.queryset

    @staticmethod
    def lastmod(translation: OfferTemplate | AbstractContentTranslation) -> datetime:
        """
        This functions returns the date when a translation was last modified.

        :param translation: The given translation
                           ~integreat_cms.cms.models.events.event_translation.EventTranslation, or
                           ~integreat_cms.cms.models.pois.poi_translation.POITranslation

        :return: The list of urls
        """
        return translation.last_updated

    def _urls(self, page: int, protocol: str, domain: str) -> list[dict[str, Any]]:
        """
        This is a patched version of :func:`django.contrib.sitemaps.Sitemap._urls` which adds the alternative languages
        to the list of urls.
        This patch is required because the inbuilt function can only deal with the i18n backend languages and not with
        our custom language model.
        Additionally, it overwrites the protocol and domain of the urls with
        :attr:`~integreat_cms.core.settings.WEBAPP_URL` because out of the box, :doc:`django:ref/contrib/sitemaps` does only
        support this functionality when used together with :doc:`django:ref/contrib/sites`.

        :param page: The page for the paginator (will always be ``1`` in our case)
        :param protocol: The protocol of the urls
        :param domain: The domain of the urls
        :return: A list of urls
        """
        splitted_url = urlsplit(settings.WEBAPP_URL)
        # Generate list of urls without alternative languages
        urls = super()._urls(page, splitted_url.scheme, splitted_url.hostname)
        for url in urls:
            # Add information about alternative languages
            url["alternates"] = self.sitemap_alternates(url["item"])
        return urls

    def sitemap_alternates(
        self, obj: OfferTemplate | AbstractContentTranslation
    ) -> list[dict[str, str]]:
        """
        This function returns the sitemap alternatives for a given object

        :param obj: The object
        :return: The sitemap alternates of the given object
        """
        return obj.sitemap_alternates


class PageSitemap(WebappSitemap):
    """
    This sitemap contains all urls to page translations for a specific region and language.

    Attributes inherited from :class:`~integreat_cms.sitemap.sitemaps.WebappSitemap`:

    :attribute changefreq: The usual change frequency of this sitemap's urls (see :attr:`WebappSitemap.changefreq`)
    """

    #: The priority of this sitemap's urls
    priority: float = 1.0
    #: The :class:`~integreat_cms.cms.models.pages.page_translation.PageTranslation` :class:`~django.db.models.query.QuerySet` of this sitemap
    queryset: QuerySet[PageTranslation] = PageTranslation.objects.filter(
        status=status.PUBLIC
    )

    def __init__(self, region: Region, language: Language) -> None:
        """
        This init function filters the queryset of page translation objects based on the given region and language.

        :param region: The region of this sitemap's urls
        :param language: The language of this sitemap's urls
        """
        # Instantiate WebappSitemap
        super().__init__(region, language)
        # Filter queryset based on region and language
        self.queryset = self.queryset.filter(
            page__in=self.region.get_pages(),
            language=self.language,
        ).distinct("page__pk")


class EventSitemap(WebappSitemap):
    """
    This sitemap contains all urls to event translations for a specific region and language.

    Attributes inherited from :class:`~integreat_cms.sitemap.sitemaps.WebappSitemap`:

    :attribute priority: The priority of this sitemap's urls (see :attr:`WebappSitemap.priority`)
    """

    #: The usual change frequency of this sitemap's urls
    changefreq: str = "daily"

    #: The :class:`~integreat_cms.cms.models.events.event_translation.EventTranslation` :class:`~django.db.models.query.QuerySet` of this sitemap
    queryset: QuerySet[EventTranslation] = EventTranslation.objects.filter(
        event__archived=False, status=status.PUBLIC
    )

    def __init__(self, region: Region, language: Language) -> None:
        """
        This init function filters the queryset of event translation objects based on the given region and language.

        :param region: The region of this sitemap's urls
        :param language: The language of this sitemap's urls
        """
        # Instantiate WebappSitemap
        super().__init__(region, language)
        # Filter queryset based on region and language
        self.queryset = self.queryset.filter(
            event__in=self.region.events.all(), language=self.language
        ).distinct("event__pk")


class POISitemap(WebappSitemap):
    """
    This sitemap contains all urls to POI translations for a specific region and language.

    Attributes inherited from :class:`~integreat_cms.sitemap.sitemaps.WebappSitemap`:

    :attribute changefreq: The usual change frequency of this sitemap's urls (see :attr:`WebappSitemap.changefreq`)
    :attribute priority: The priority of this sitemap's urls (see :attr:`WebappSitemap.priority`)
    """

    #: The :class:`~integreat_cms.cms.models.pois.poi_translation.POITranslation` :class:`~django.db.models.query.QuerySet` queryset of this sitemap
    queryset: QuerySet[POITranslation] = POITranslation.objects.filter(
        poi__archived=False, status=status.PUBLIC
    )

    def __init__(self, region: Region, language: Language) -> None:
        """
        This init function filters the queryset of POI translation objects based on the given region and language.

        :param region: The region of this sitemap's urls
        :param language: The language of this sitemap's urls
        """
        # Instantiate WebappSitemap
        super().__init__(region, language)
        # Filter queryset based on region and language
        self.queryset = self.queryset.filter(
            poi__in=self.region.pois.all(), language=self.language
        ).distinct("poi__pk")


class OfferSitemap(WebappSitemap):
    """
    This sitemap contains all urls to offers for a specific region.

    Attributes inherited from :class:`~integreat_cms.sitemap.sitemaps.WebappSitemap`:

    :attribute changefreq: The usual change frequency of this sitemap's urls (see :attr:`WebappSitemap.changefreq`)
    """

    #: The priority of this sitemap's urls (``1.0``)
    priority = 1.0
    #: The :class:`~integreat_cms.cms.models.offers.offer_template.OfferTemplate` :class:`~django.db.models.query.QuerySet` queryset of this sitemap
    queryset: QuerySet[OfferTemplate] = OfferTemplate.objects.all()

    def __init__(self, region: Region, language: Language) -> None:
        """
        This init function filters the queryset of offers objects based on the given region.

        :param region: The region of this sitemap's urls
        :param language: The language of this sitemap's urls
        """
        # Instantiate WebappSitemap
        super().__init__(region, language)
        # Filter queryset based on region
        self.queryset = self.queryset.filter(regions=self.region)

    def location(self, item: OfferTemplate) -> str:
        """
        This location function returns the absolute path for a given object returned by items().

        :param item: Objects passed from items() method
        :return: The absolute path of the given offer object
        """
        return f"/{self.region.slug}/{self.language.slug}/offers/{item.slug}"

    def sitemap_alternates(self, obj: OfferTemplate) -> list[dict[str, str]]:
        """
        This sitemap_alternates function returns the language alternatives of offers for the use in sitemaps.

        :param obj: Objects passed from items() method
        :return: A list of dictionaries containing the alternative translations of offers
        """
        return [
            {
                "location": f"{settings.WEBAPP_URL}/{self.region.slug}/{language.slug}/offers/{obj.slug}",
                "lang_slug": language.slug,
            }
            for language in self.region.visible_languages
            if language != self.language
        ]