Source code for recurring.forms

import json
import logging
from datetime import datetime
from typing import Any, Dict

from django import forms

from .models import CalendarEntry
from .widgets import CalendarEntryWidget

logger = logging.getLogger(__name__)


[docs] class CalendarEntryForm(forms.ModelForm): calendar_entry = forms.CharField(required=False, widget=CalendarEntryWidget)
[docs] class Meta: model = CalendarEntry fields = ["name", "description", "timezone"]
def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) logger.info("Form init method") self.fields["calendar_entry"].widget = CalendarEntryWidget(form=self) self.fields["calendar_entry"].widget.attrs["style"] = "display: none;" if self.instance.pk: calendar_entry_data = self.instance.to_dict() self.initial["calendar_entry"] = json.dumps(calendar_entry_data) self.fields["calendar_entry"].widget.attrs["data-initial"] = json.dumps( calendar_entry_data )
[docs] def save(self, commit: bool = True) -> CalendarEntry: logger.info(f"Starting save method (commit={commit})") instance = super().save(commit=False) if commit: logger.info("Commit is True, saving instance") instance.save(recalculate=False) # Save without recalculating logger.info("Processing calendar_entry data") calendar_entry_data = self.cleaned_data.get("calendar_entry") if calendar_entry_data: logger.info("Clearing existing events") for event in instance.events.all(): event.delete() logger.info("Adding new events and exclusions") instance.from_dict(calendar_entry_data) instance.save(recalculate=False) logger.info("Recalculating occurrences") instance.calculate_occurrences() logger.info("Save method completed") return instance
[docs] def clean(self) -> Dict[str, Any]: logger.info("Inside clean") cleaned_data = super().clean() if self.errors: logger.info("Returning errors") return cleaned_data logger.info("No form errors") calendar_entry_data = cleaned_data.get("calendar_entry") if calendar_entry_data: try: calendar_entry_dict = json.loads(calendar_entry_data) if not isinstance(calendar_entry_dict, dict): raise ValueError("Calendar entry data must be a dictionary") self.calendar_entry_data = calendar_entry_dict if not isinstance(calendar_entry_dict.get("events"), list): raise ValueError( "Calendar entry data must contain an 'events' list" ) # Get the submitted timezone submitted_timezone = cleaned_data.get("timezone").as_tz for event_data in calendar_entry_dict["events"]: if not isinstance(event_data, dict): raise ValueError("Each event must be a dictionary") if "start_time" not in event_data or "end_time" not in event_data: raise ValueError( "Each event must have 'start_time' and 'end_time'" ) # Convert start_time and end_time to timezone-aware datetimes start_time = datetime.fromisoformat(event_data["start_time"]) end_time = ( datetime.fromisoformat(event_data["end_time"]) if event_data["end_time"] else None ) # Ensure the datetimes are timezone-aware event_data["start_time"] = start_time.replace( tzinfo=submitted_timezone ) event_data["end_time"] = ( end_time.replace(tzinfo=submitted_timezone) if end_time else None ) event_data["is_full_day"] = event_data.get("is_full_day", False) # Rule and exclusions are optional if "recurrence_rule" in event_data: if not event_data["recurrence_rule"]: event_data["recurrence_rule"] = {} if not isinstance(event_data["recurrence_rule"], dict): raise ValueError("Event rule must be a dictionary") recurrence_rule = event_data["recurrence_rule"] if "until" in recurrence_rule: until = datetime.fromisoformat(recurrence_rule["until"]) recurrence_rule["until"] = until.replace( tzinfo=submitted_timezone ) if "exclusions" in event_data: if not isinstance(event_data["exclusions"], list): raise ValueError("Event exclusions must be a list") for exclusion_data in event_data["exclusions"]: if not isinstance(exclusion_data, dict): raise ValueError("Each exclusion must be a dictionary") if ( "start_date" not in exclusion_data or "end_date" not in exclusion_data ): raise ValueError( "Each exclusion must have 'start_date' and 'end_date'" ) exclusion_start = datetime.fromisoformat( exclusion_data["start_date"] ) exclusion_end = datetime.fromisoformat( exclusion_data["end_date"] ) exclusion_data["start_date"] = exclusion_start.replace( tzinfo=submitted_timezone ) exclusion_data["end_date"] = exclusion_end.replace( tzinfo=submitted_timezone ) # Assert that start_date <= end_date if ( exclusion_data["start_date"] >= exclusion_data["end_date"] ): raise ValueError( "Exclusion start date must be less than the end date." ) except json.JSONDecodeError: self.add_error( "calendar_entry", "Invalid JSON data for calendar entry." ) except KeyError as e: self.add_error( "calendar_entry", f"Missing required key in calendar entry data: {str(e)}", ) except ValueError as e: self.add_error( "calendar_entry", f"Invalid calendar entry data: {str(e)}" ) except Exception as e: self.add_error( "calendar_entry", f"Error processing calendar entry data: {str(e)}" ) cleaned_data["calendar_entry"] = getattr(self, "calendar_entry_data", {}) logger.info(f"Cleaned data: {cleaned_data}") return cleaned_data