digitalfabrik/integreat-cms

View on GitHub
integreat_cms/cms/forms/events/recurrence_rule_form.py

Summary

Maintainability
A
0 mins
Test Coverage
F
41%
from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from django import forms
from django.utils.translation import gettext_lazy as _

from ...constants import frequency, weekdays
from ...models import RecurrenceRule
from ..custom_model_form import CustomModelForm

if TYPE_CHECKING:
    from typing import Any

logger = logging.getLogger(__name__)


class RecurrenceRuleForm(CustomModelForm):
    """
    Form for creating and modifying event recurrence rule objects
    """

    has_recurrence_end_date = forms.BooleanField(
        required=False, label=_("Recurrence ends")
    )

    class Meta:
        """
        This class contains additional meta configuration of the form class, see the :class:`django.forms.ModelForm`
        for more information.
        """

        #: The model of this :class:`django.forms.ModelForm`
        model = RecurrenceRule
        #: The fields of the model which should be handled by this form
        fields = [
            "frequency",
            "interval",
            "weekdays_for_weekly",
            "weekday_for_monthly",
            "week_for_monthly",
            "recurrence_end_date",
        ]
        #: The widgets which are used in this form
        widgets = {
            "weekdays_for_weekly": forms.SelectMultiple(choices=weekdays.CHOICES),
            "recurrence_end_date": forms.DateInput(
                format="%Y-%m-%d", attrs={"type": "date"}
            ),
        }

    def __init__(self, **kwargs: Any) -> None:
        r"""
        Initialize recurrence rule form

        :param \**kwargs: The supplied keyword arguments
        """

        # Set event start date to be used in clean()-method
        self.event_start_date = kwargs.pop("event_start_date", None)

        # Instantiate CustomModelForm
        super().__init__(**kwargs)

        if self.instance.id:
            # Initialize BooleanField based on RecurrenceRule properties
            self.fields["has_recurrence_end_date"].initial = bool(
                self.instance.recurrence_end_date
            )

    def clean(self) -> dict[str, Any]:
        """
        Validate form fields which depend on each other, see :meth:`django.forms.Form.clean`

        :return: The cleaned form data
        """
        cleaned_data = super().clean()

        if not cleaned_data.get("frequency"):
            self.add_error(
                "frequency",
                forms.ValidationError(
                    _("No recurrence frequency selected"), code="required"
                ),
            )
        elif cleaned_data.get("frequency") == frequency.WEEKLY and not cleaned_data.get(
            "weekdays_for_weekly"
        ):
            self.add_error(
                "weekdays_for_weekly",
                forms.ValidationError(
                    _("No weekdays for weekly recurrence selected"), code="required"
                ),
            )
        elif cleaned_data.get("frequency") == frequency.MONTHLY:
            if cleaned_data.get("weekday_for_monthly") is None:
                self.add_error(
                    "weekday_for_monthly",
                    forms.ValidationError(
                        _("No weekday for monthly recurrence selected"), code="required"
                    ),
                )
            if not cleaned_data.get("week_for_monthly"):
                self.add_error(
                    "week_for_monthly",
                    forms.ValidationError(
                        _("No week for monthly recurrence selected"), code="required"
                    ),
                )

        if cleaned_data.get("has_recurrence_end_date"):
            if not cleaned_data.get("recurrence_end_date"):
                self.add_error(
                    "recurrence_end_date",
                    forms.ValidationError(
                        _(
                            "If the recurrence ends, the recurrence end date is required"
                        ),
                        code="required",
                    ),
                )
            elif (
                self.event_start_date
                and cleaned_data.get("recurrence_end_date") <= self.event_start_date
            ):
                self.add_error(
                    "recurrence_end_date",
                    forms.ValidationError(
                        _(
                            "The recurrence end date has to be after the event's start date"
                        ),
                        code="invalid",
                    ),
                )
        else:
            cleaned_data["recurrence_end_date"] = None

        logger.debug(
            "RecurrenceRuleForm validated [2] with cleaned data %r", cleaned_data
        )
        return cleaned_data

    def has_changed(self) -> bool:
        """
        This function provides a workaround for the ``weekdays_for_weekly`` field to be correctly recognized as changed.

        :return: Whether or not the recurrence rule form has changed
        """
        # Handle weekdays_for_weekly data separately from the other data because has_changed doesn't work
        # with CheckboxSelectMultiple widgets and ArrayFields out of the box
        try:
            # Have to remove the corresponding field name from self.changed_data
            self.changed_data.remove("weekdays_for_weekly")
        except ValueError:
            return super().has_changed()
        value = self.fields["weekdays_for_weekly"].widget.value_from_datadict(
            self.data, self.files, self.add_prefix("weekdays_for_weekly")
        )
        initial = self["weekdays_for_weekly"].initial
        if value:
            value = set(map(int, value))
        if initial:
            initial = set(initial)
        if value != initial:
            self.changed_data.append("weekdays_for_weekly")
        return bool(self.changed_data)