digitalfabrik/integreat-cms

View on GitHub
integreat_cms/cms/linklists.py

Summary

Maintainability
A
0 mins
Test Coverage
A
93%
from __future__ import annotations

from typing import TYPE_CHECKING

from django.conf import settings
from django.db.models import Exists, OuterRef
from linkcheck import Linklist

from .models import (
    Event,
    EventTranslation,
    ImprintPageTranslation,
    LanguageTreeNode,
    Organization,
    Page,
    PageTranslation,
    POITranslation,
)

if TYPE_CHECKING:
    from django.db.models.base import ModelBase
    from django.db.models.query import QuerySet


class ActiveLanguageLinklist(Linklist):
    """
    Base class for content translation link lists
    """

    html_fields: list[str] = ["content"]

    @classmethod
    def filter_callable(cls, objects: QuerySet) -> QuerySet:
        """
        Get only translations in active languages

        :param objects: Objects to be filtered
        :return: Objects that passed the filter
        """
        # Because of large overhead, we only want this filter during the findlinks management command.
        if settings.LINKCHECK_COMMAND_RUNNING:
            # Exclude links in inactive languages
            objects = objects.annotate(
                active=Exists(
                    LanguageTreeNode.objects.filter(
                        region=OuterRef(f"{objects.model.foreign_field()}__region"),
                        language=OuterRef("language"),
                        active=True,
                    )
                )
            ).filter(active=True)

        return objects


class PageTranslationLinklist(ActiveLanguageLinklist):
    """
    Class for selecting the PageTranslation model for link checks
    """

    model: ModelBase = PageTranslation

    @classmethod
    def filter_callable(cls, objects: QuerySet) -> QuerySet:
        """
        Get only latest versions for non-archived pages in active languages

        :param objects: Objects to be filtered
        :return: Objects that passed the filter
        """
        # Apply filter of parent class
        objects = super().filter_callable(objects)
        # Because of large overhead, we only want this filter during the findlinks management command.
        if settings.LINKCHECK_COMMAND_RUNNING:
            # Exclude archived pages
            pages = Page.objects.all().cache_tree(archived=False)
            objects = objects.filter(page__in=pages)

        return objects.distinct("page__pk", "language__pk")


class ImprintTranslationLinklist(ActiveLanguageLinklist):
    """
    Class for selecting the ImprintPageTranslation model for linkchecks
    """

    model: ModelBase = ImprintPageTranslation


class NonArchivedLinkList(ActiveLanguageLinklist):
    """
    Class for excluding archived events and locations
    """

    @classmethod
    def filter_callable(cls, objects: QuerySet) -> QuerySet:
        """
        Get only latest translations for non-archived events/locations in active languages

        :param objects: Objects to be filtered
        :return: Objects that passed the filter
        """
        # Apply filter of parent class
        objects = super().filter_callable(objects)
        # Exclude archived events/locations
        objects = objects.filter(
            **{f"{objects.model.foreign_field()}__archived": False}
        ).distinct(f"{objects.model.foreign_field()}__pk", "language__pk")

        return objects


class EventTranslationLinklist(NonArchivedLinkList):
    """
    Class for selecting the EventTranslation model for link checks
    """

    model: ModelBase = EventTranslation

    @classmethod
    def filter_callable(cls, objects: QuerySet) -> QuerySet:
        """
        Get only translations of upcoming events in active languages

        :param objects: Objects to be filtered
        :return: Objects that passed the filter
        """
        # Apply filter of parent class
        objects = super().filter_callable(objects)
        # Exclude past events
        upcoming_events = Event.objects.filter_upcoming()
        objects = objects.filter(event__in=upcoming_events)

        return objects


class POITranslationLinklist(NonArchivedLinkList):
    """
    Class for selecting the POITranslation model for link checks
    """

    model: ModelBase = POITranslation


class OrganizationLinklist(Linklist):
    """
    Class for selecting the Organization model for link checks
    """

    model: ModelBase = Organization

    url_fields = ["website"]


linklists = {
    "PageTranslations": PageTranslationLinklist,
    "EventTranslations": EventTranslationLinklist,
    "POITranslations": POITranslationLinklist,
    "ImprintPageTranslations": ImprintTranslationLinklist,
    "Organizations": OrganizationLinklist,
}