digitalfabrik/integreat-cms

View on GitHub
integreat_cms/api/v3/locations.py

Summary

Maintainability
A
0 mins
Test Coverage
B
87%
"""
This module includes functions related to the locations/POIs API endpoint.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from django.conf import settings
from django.db.models import Prefetch
from django.http import JsonResponse
from django.utils import timezone
from django.utils.html import strip_tags

from ...cms.constants import status
from ...cms.models import POICategoryTranslation
from ...cms.models.pois.poi import get_default_opening_hours
from ...core.utils.strtobool import strtobool
from ..decorators import json_response
from .location_categories import transform_location_category

if TYPE_CHECKING:
    from typing import Any

    from django.http import HttpRequest

    from ...cms.models import POI, POITranslation


def transform_poi(poi: POI | None) -> dict[str, Any]:
    """
    Function to create a JSON from a single poi object.

    :param poi: The poi object which should be converted
    :return: data necessary for API
    """
    if not poi:
        return {
            "id": None,
            "name": None,
            "address": None,
            "town": None,
            "state": None,
            "postcode": None,
            "region": None,
            "country": None,
            "latitude": None,
            "longitude": None,
        }
    return {
        "id": poi.id,
        "name": poi.default_public_translation.title,
        "address": poi.address,
        "town": poi.city,
        "state": None,
        "postcode": poi.postcode,
        "region": None,
        "country": poi.country,
        "latitude": poi.latitude,
        "longitude": poi.longitude,
    }


def transform_poi_translation(poi_translation: POITranslation) -> dict[str, Any]:
    """
    Function to create a JSON from a single poi_translation object.

    :param poi_translation: The poi translation object which should be converted
    :return: data necessary for API
    """

    poi = poi_translation.poi
    # Only return opening hours if they differ from the default value and the location is not temporarily closed
    opening_hours = None
    if not poi.temporarily_closed and poi.opening_hours != get_default_opening_hours():
        opening_hours = poi.opening_hours
    return {
        "id": poi_translation.id,
        "url": settings.BASE_URL + poi_translation.get_absolute_url(),
        "path": poi_translation.get_absolute_url(),
        "title": poi_translation.title,
        "modified_gmt": poi_translation.last_updated,  # deprecated field in the future
        "last_updated": timezone.localtime(poi_translation.last_updated),
        "meta_description": poi_translation.meta_description,
        "excerpt": strip_tags(poi_translation.content),
        "content": poi_translation.content,
        "available_languages": poi_translation.available_languages_dict,
        "icon": poi.icon.url if poi.icon else None,
        "thumbnail": poi.icon.thumbnail_url if poi.icon else None,
        "website": poi.website or None,
        "email": poi.email or None,
        "phone_number": poi.phone_number or None,
        "category": transform_location_category(
            poi.category, poi_translation.language.slug
        ),
        "temporarily_closed": poi.temporarily_closed,
        # Only return opening hours if not temporarily closed and they differ from the default value
        "opening_hours": opening_hours,
        "appointment_url": poi.appointment_url or None,
        "location": transform_poi(poi),
        "hash": None,
        "organization": (
            {
                "id": poi.organization.id,
                "slug": poi.organization.slug,
                "name": poi.organization.name,
                "logo": poi.organization.icon.url,
                "website": poi.organization.website,
            }
            if poi.organization
            else None
        ),
        "barrier_free": poi.barrier_free,
    }


@json_response
# pylint: disable=unused-argument
def locations(
    request: HttpRequest, region_slug: str, language_slug: str
) -> JsonResponse:
    """
    List all POIs of the region and transform result into JSON

    :param request: The current request
    :param region_slug: The slug of the requested region
    :param language_slug: The slug of the requested language
    :return: JSON object according to APIv3 locations endpoint definition
    """
    region = request.region
    # Throw a 404 error when the language does not exist or is disabled
    region.get_language_or_404(language_slug, only_active=True)
    result = []
    pois = (
        region.pois.prefetch_public_translations()
        .filter(
            archived=False,
            # Exclude locations without public translation in the default language
            translations__language=region.default_language,
            translations__status=status.PUBLIC,
        )
        .distinct()
        .select_related("category", "organization__icon")
        .prefetch_related(
            Prefetch(
                "category__translations",
                queryset=POICategoryTranslation.objects.select_related("language"),
            )
        )
    )

    if "on_map" in request.GET:
        try:
            location_on_map = strtobool(request.GET["on_map"])
        except ValueError as e:
            return JsonResponse({"error": str(e)}, status=400)
        pois = pois.filter(location_on_map=location_on_map)

    for poi in pois:
        if translation := poi.get_public_translation(language_slug):
            result.append(transform_poi_translation(translation))

    return JsonResponse(
        result, safe=False
    )  # Turn off Safe-Mode to allow serializing arrays