digitalfabrik/integreat-cms

View on GitHub
integreat_cms/cms/utils/content_translation_utils.py

Summary

Maintainability
A
0 mins
Test Coverage
A
94%
"""
This file contains utility functions for content translations.
"""

from __future__ import annotations

import logging
import operator
from functools import reduce
from typing import TYPE_CHECKING

from django.db.models import Q
from linkcheck.models import Url

from .content_utils import clean_content
from .internal_link_utils import get_public_translation_for_link

if TYPE_CHECKING:
    from ..models import User
    from ..models.abstract_content_translation import AbstractContentTranslation

logger = logging.getLogger(__name__)


def update_links_to(
    content_translation: AbstractContentTranslation, user: User | None
) -> None:
    """
    Updates all content translations with links that point to the given translation.
    This fixes potentially outdated links.

    :param content_translation: The content translation for which links that points to it should get updated
    :param user: The user who should be responsible for updating the links
    """
    for outdated_content_translation in get_referencing_translations(
        content_translation
    ):
        # Assert that the related translation is not archived
        # Note that this should not be possible, since links to archived pages get deleted
        if getattr(outdated_content_translation.foreign_object, "archived", False):
            continue

        new_content = clean_content(
            outdated_content_translation.content,
            outdated_content_translation.language.slug,
        )
        if new_content == outdated_content_translation.content:
            continue

        fixed_content_translation = (
            outdated_content_translation.create_new_version_copy(user)
        )
        fixed_content_translation.content = new_content
        outdated_content_translation.links.all().delete()
        fixed_content_translation.save()

        logger.debug(
            "Updated links to %s in %r",
            content_translation.full_url,
            outdated_content_translation,
        )


def get_referencing_translations(
    content_translation: AbstractContentTranslation,
) -> set[AbstractContentTranslation]:
    """
    Returns a list of content translations that link to the given translation

    :param content_translation: The `content_translation` for which links should be searched
    :return: All referencing content translations
    """
    result = set()

    public_translation = content_translation.public_version

    # To avoid searching every single url, filter for urls that contain a slug or id that the content translation has used
    translation_slugs = set(content_translation.get_all_used_slugs())
    translation_ids = set(content_translation.all_versions.values_list("id", flat=True))
    logger.debug(
        "Collecting links that contain %s or %s", translation_slugs, translation_ids
    )
    filter_query = reduce(
        operator.or_, (Q(url__contains=slug) for slug in translation_slugs)
    )
    filter_query = filter_query | reduce(
        operator.or_, (Q(url__contains=str(uid)) for uid in translation_ids)
    )

    urls = (url for url in Url.objects.filter(filter_query) if url.internal)
    for url in urls:
        if linked_translation := get_public_translation_for_link(url.url):
            if linked_translation != public_translation:
                continue

            for link in url.links.all():
                result.add(link.content_object.latest_version)
    return result