digitalfabrik/integreat-cms

View on GitHub
integreat_cms/core/middleware/region_middleware.py

Summary

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

import logging
from typing import TYPE_CHECKING

from django.conf import settings
from django.shortcuts import get_object_or_404
from django.urls import resolve

from ...cms.models import Region

if TYPE_CHECKING:
    from collections.abc import Callable
    from typing import Any

    from django.db.models.query import QuerySet
    from django.http import HttpRequest

logger = logging.getLogger(__name__)


class RegionMiddleware:
    """
    Middleware class that adds the current region to the request variable
    """

    def __init__(self, get_response: Callable) -> None:
        """
        Initialize the middleware for the current view

        :param get_response: A callable to get the response for the current request
        """
        self.get_response = get_response

    def __call__(self, request: HttpRequest) -> Any:
        """
        Call the middleware for the current request.
        Sets the additional attributes on the ``request`` object:

        - ``region``: The current region, based on the region slug URL parameter
        - ``available_regions``: The regions the user has access to
        - ``quick_access_regions``: The regions that are available for quick access in the top menu

        :param request: Django request
        :return: The response after the region has been added to the request variable
        """
        user_regions = (
            request.user.regions.all()
            if request.user.is_authenticated
            else Region.objects.none()
        )
        request.region = self.get_current_region(request)
        request.available_regions = self.get_available_regions(request, user_regions)
        request.quick_access_regions = self.get_quick_access_regions(
            request, user_regions
        )
        return self.get_response(request)

    @staticmethod
    def get_current_region(request: HttpRequest) -> Region | None:
        """
        This method returns the current region based on the current request.
        If the request path contains a region slug, the corresponding
        :class:`~integreat_cms.cms.models.regions.region.Region` object is queried from the database.

        :param request: Django request
        :raises ~django.http.Http404: When the current request has a ``region_slug`` parameter, but there is no region
                                      with that slug.

        :return: The current region of this request
        """
        # Resolve current url
        resolver_match = resolve(request.path)
        if region_slug := resolver_match.kwargs.get("region_slug"):
            return get_object_or_404(Region, slug=region_slug)
        return None

    @staticmethod
    def get_available_regions(request: HttpRequest, user_regions: QuerySet) -> QuerySet:
        """
        This method returns the regions available to the user based on the current request.
        Staff members and superusers have access to all regions, whereas all other users have access to their selected regions.

        :param request: Django request
        :param user_regions: Prefetched regions of the user
        :return: The regions available to the user of this request
        """
        if request.user.is_superuser or request.user.is_staff:
            return Region.objects.all().order_by("-last_updated")
        return user_regions

    @staticmethod
    def get_quick_access_regions(
        request: HttpRequest, user_regions: QuerySet
    ) -> list[Region]:
        """
        This method returns the regions that are available for quick access in this request.
        For non-staff members, the region selection consists of the regions they have access to.
        For staff members with non-empty `regions` field, it is used as a favorite setting.
        Staff members without favorite regions just have quick access to the regions that have been last updated.
        The current region is excluded.
        The list is truncated to the first :attr:`~integreat_cms.core.settings.NUM_REGIONS_QUICK_ACCESS` elements.

        :param request: The current HTTP request
        :param user_regions: Prefetched regions of the user
        :return: The regions that are available for quick access in the dropdown menu
        """
        quick_access_regions = list(
            user_regions
            if (request.user.is_superuser or request.user.is_staff)
            and len(user_regions) > 0
            else request.available_regions
        )
        if request.region in quick_access_regions:
            quick_access_regions.remove(request.region)
        return quick_access_regions[: settings.NUM_REGIONS_QUICK_ACCESS]