digitalfabrik/integreat-cms

View on GitHub
integreat_cms/cms/views/events/event_form_view.py

Summary

Maintainability
A
0 mins
Test Coverage
C
78%
from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from cacheops import invalidate_model
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.shortcuts import redirect, render
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.views.generic import TemplateView

from ...constants import status, translation_status
from ...decorators import permission_required
from ...forms import EventForm, EventTranslationForm, RecurrenceRuleForm
from ...models import Event, EventTranslation, Language, POI, RecurrenceRule
from ...utils.translation_utils import translate_link
from ..media.media_context_mixin import MediaContextMixin
from ..mixins import ContentEditLockMixin
from .event_context_mixin import EventContextMixin

if TYPE_CHECKING:
    from typing import Any

    from django.http import HttpRequest, HttpResponse

logger = logging.getLogger(__name__)


@method_decorator(permission_required("cms.view_event"), name="dispatch")
@method_decorator(permission_required("cms.change_event"), name="post")
class EventFormView(
    TemplateView, EventContextMixin, MediaContextMixin, ContentEditLockMixin
):
    """
    Class for rendering the events form
    """

    #: The template to render (see :class:`~django.views.generic.base.TemplateResponseMixin`)
    template_name = "events/event_form.html"
    #: The context dict passed to the template (see :class:`~django.views.generic.base.ContextMixin`)
    extra_context = {"translation_status": translation_status}
    #: The url name of the view to show if the user decides to go back (see :class:`~integreat_cms.cms.views.mixins.ContentEditLockMixin`
    back_url_name: str | None = "events"

    def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
        r"""
        Render event form for HTTP GET requests

        :param request: Object representing the user call
        :param \*args: The supplied arguments
        :param \**kwargs: The supplied keyword arguments
        :return: The rendered template response
        """
        region = request.region
        language = region.get_language_or_404(
            kwargs.get("language_slug"), only_active=True
        )

        # get event and event translation objects if they exist, otherwise objects are None
        event_instance = region.events.filter(id=kwargs.get("event_id")).first()
        event_translation_instance = language.event_translations.filter(
            event=event_instance
        ).first()
        recurrence_rule_instance = RecurrenceRule.objects.filter(
            event=event_instance
        ).first()
        # Make form disabled if event is archived or user doesn't have the permission to edit the event
        if event_instance and event_instance.archived:
            disabled = True
            messages.warning(
                request, _("You cannot edit this event because it is archived.")
            )
        elif event_instance and event_instance.external_calendar:
            disabled = True
            messages.warning(
                request,
                _(
                    "You cannot edit this event because it was imported from an external calendar."
                ),
            )
        elif not request.user.has_perm("cms.change_event"):
            disabled = True
            messages.warning(
                request, _("You don't have the permission to edit events.")
            )
        elif not request.user.has_perm("cms.publish_event"):
            disabled = False
            messages.warning(
                request,
                _(
                    "You don't have the permission to publish events, but you can propose changes and submit them for review instead."
                ),
            )
        else:
            disabled = False

        event_form = EventForm(
            instance=event_instance,
            disabled=disabled,
        )
        event_translation_form = EventTranslationForm(
            request=request,
            language=language,
            instance=event_translation_instance,
            disabled=disabled,
        )
        recurrence_rule_form = RecurrenceRuleForm(
            instance=recurrence_rule_instance, disabled=disabled
        )

        url_link = f"{settings.WEBAPP_URL}/{region.slug}/{language.slug}/{event_translation_form.instance.url_infix}/"
        return render(
            request,
            self.template_name,
            {
                **self.get_context_data(**kwargs),
                "event_form": event_form,
                "event_translation_form": event_translation_form,
                "recurrence_rule_form": recurrence_rule_form,
                "poi": event_instance.location if event_instance else None,
                "language": language,
                "languages": region.active_languages if event_instance else [language],
                "url_link": url_link,
                "translation_states": (
                    event_instance.translation_states if event_instance else []
                ),
                "disabled": disabled,
            },
        )

    # pylint: disable=too-many-locals,too-many-branches
    def post(self, request: HttpRequest, **kwargs: Any) -> HttpResponse:
        r"""
        Save event and ender event form for HTTP POST requests

        :param request: Object representing the user call
        :param \**kwargs: The supplied keyword arguments
        :raises ~django.core.exceptions.PermissionDenied: If user does not have the permission to publish events

        :return: The rendered template response
        """
        region = request.region
        language = Language.objects.get(slug=kwargs.get("language_slug"))
        poi = POI.objects.filter(id=request.POST.get("location")).first()

        event_instance = Event.objects.filter(id=kwargs.get("event_id")).first()
        recurrence_rule_instance = RecurrenceRule.objects.filter(
            event=event_instance
        ).first()
        event_translation_instance = EventTranslation.objects.filter(
            event=event_instance, language=language
        ).first()

        event_form = EventForm(
            data=request.POST,
            files=request.FILES,
            instance=event_instance,
            additional_instance_attributes={"region": region, "location": poi},
        )
        # clean data of event form to be able to pass the cleaned start date to the recurrence form for validation
        event_form_valid = event_form.is_valid()
        recurrence_rule_form = RecurrenceRuleForm(
            data=request.POST,
            instance=recurrence_rule_instance,
            event_start_date=event_form.cleaned_data.get("start_date", None),
        )
        event_translation_form = EventTranslationForm(
            request=request,
            language=language,
            data=request.POST,
            instance=event_translation_instance,
            additional_instance_attributes={
                "creator": request.user,
                "language": language,
                "event": event_form.instance,
            },
            changed_by_user=request.user,
        )
        user_slug = event_translation_form.data.get("slug")

        if (
            not event_form_valid
            or not event_translation_form.is_valid()
            or (
                event_form.cleaned_data["is_recurring"]
                and not recurrence_rule_form.is_valid()
            )
        ):
            # Add error messages
            event_form.add_error_messages(request)
            event_translation_form.add_error_messages(request)
            # do not call recurrence rule form clean method when recurrence rule is not set
            if event_form.cleaned_data["is_recurring"]:
                recurrence_rule_form.add_error_messages(request)
        elif (
            event_translation_form.instance.status == status.AUTO_SAVE
            and not event_form.has_changed()
            and not event_translation_form.has_changed()
            and not recurrence_rule_form.has_changed()
        ):
            messages.info(request, _("No changes detected, autosave skipped"))
        else:
            # Check publish permissions
            if event_translation_form.instance.status in [
                status.DRAFT,
                status.PUBLIC,
            ] and not request.user.has_perm("cms.publish_event"):
                raise PermissionDenied(
                    f"{request.user!r} does not have the permission 'cms.publish_event'"
                )
            # Save forms
            if event_form.cleaned_data.get("is_recurring"):
                # If event is recurring, save recurrence rule
                event_form.instance.recurrence_rule = recurrence_rule_form.save()
            elif event_form.instance.recurrence_rule:
                # If the event is not recurring but it was before, delete the associated recurrence rule
                event_form.instance.recurrence_rule.delete()
                event_form.instance.recurrence_rule = None

            # Save event from event form
            event = event_form.save()
            event_translation_form.instance.event = event
            event_translation_instance = event_translation_form.save(
                foreign_form_changed=(
                    event_form.has_changed() or recurrence_rule_form.has_changed()
                ),
            )

            # Invalidate event translation cache to refresh API result
            invalidate_model(EventTranslation)

            # If any source translation changes to draft, set all depending translations/versions to draft
            if event_translation_form.instance.status == status.DRAFT:
                language_tree_node = region.language_node_by_slug.get(language.slug)
                languages = [language] + [
                    node.language for node in language_tree_node.get_descendants()
                ]
                event_translation_form.instance.event.translations.filter(
                    language__in=languages
                ).update(status=status.DRAFT)

            elif (
                event_translation_form.instance.status == status.PUBLIC
                and event_translation_form.instance.minor_edit
            ):
                event_translation_form.instance.event.translations.filter(
                    language=language
                ).update(status=status.PUBLIC)
            # Show a message that the slug was changed if it was not unique
            if user_slug and user_slug != event_translation_form.cleaned_data["slug"]:
                other_translation = EventTranslation.objects.filter(
                    event__region=region, slug=user_slug, language=language
                ).first()
                other_translation_link = other_translation.backend_edit_link
                message = _(
                    "The slug was changed from '{user_slug}' to '{slug}', "
                    "because '{user_slug}' is already used by <a>{translation}</a> or one of its previous versions.",
                ).format(
                    user_slug=user_slug,
                    slug=event_translation_form.cleaned_data["slug"],
                    translation=other_translation,
                )
                messages.warning(
                    request,
                    translate_link(
                        message,
                        attributes={
                            "href": other_translation_link,
                            "class": "underline hover:no-underline",
                        },
                    ),
                )

            # Add the success message and redirect to the edit page
            if not event_instance:
                messages.success(
                    request,
                    _('Event "{}" was successfully created').format(
                        event_translation_form.instance
                    ),
                )
            elif (
                not event_form.has_changed()
                and not event_translation_form.has_changed()
                and not recurrence_rule_form.has_changed()
            ):
                messages.info(request, _("No changes detected, but date refreshed"))
            else:
                # Add the success message
                event_translation_form.add_success_message(request)

            return redirect(
                "edit_event",
                **{
                    "event_id": event_form.instance.id,
                    "region_slug": region.slug,
                    "language_slug": language.slug,
                },
            )
        url_link = f"{settings.WEBAPP_URL}/{region.slug}/{language.slug}/{event_translation_form.instance.url_infix}/"
        return render(
            request,
            self.template_name,
            {
                **self.get_context_data(**kwargs),
                "event_form": event_form,
                "event_translation_form": event_translation_form,
                "recurrence_rule_form": recurrence_rule_form,
                "poi": poi,
                "language": language,
                "languages": region.active_languages if event_instance else [language],
                "url_link": url_link,
                "translation_states": (
                    event_instance.translation_states if event_instance else []
                ),
            },
        )