diff --git a/aleksis/core/mixins.py b/aleksis/core/mixins.py index 22c16d3469a1e07070c309115078fa55b5bf6dd7..1e4500c5b68af7336daa8f527fb9c32c5b000c85 100644 --- a/aleksis/core/mixins.py +++ b/aleksis/core/mixins.py @@ -823,6 +823,9 @@ class CalendarEventMixin(DAVResource, RegistryObject, is_registry=True): values = {} values["timestamp"] = timezone.now() values["description"] = None + values["component_type"] = cls.get_event_field_value( + reference_object, "component_type", request=request, params=params + ) for field in cls.get_event_field_names(): field_value = cls.get_event_field_value( reference_object, field, request=request, params=params diff --git a/aleksis/core/models.py b/aleksis/core/models.py index 82284ff5294bb15cbc8ea0b7ee5e7cd16d890070..6da0e38f0b9cbf8c55b76e18ee62a0e6882a8b9d 100644 --- a/aleksis/core/models.py +++ b/aleksis/core/models.py @@ -39,7 +39,7 @@ from django_ical.utils import build_rrule_from_recurrences_rrule, build_rrule_fr from django_pg_rrule.models import RecurrenceModel from dynamic_preferences.models import PerInstancePreferenceModel from guardian.shortcuts import get_objects_for_user -from icalendar import Alarm, vCalAddress, vText +from icalendar import Alarm, vCalAddress, vDate, vDatetime, vText from icalendar.prop import vRecur from invitations import signals from invitations.base_invitation import AbstractBaseInvitation @@ -1739,6 +1739,20 @@ class CalendarEvent( calendar_alarm.get_alarm(request) for calendar_alarm in reference_object.alarms.all() ] + @classmethod + def value_component_type( + cls, reference_object: "CalendarEvent", request: HttpRequest | None = None + ) -> str: + """Return the component type.""" + return "event" + + @classmethod + def value_freebusy( + cls, reference_object: "CalendarEvent", request: HttpRequest | None = None + ) -> str: + """Return a free or busy time interval.""" + return None + def get_availability_persons(self) -> list[Person]: """Return the persons whose availability is affected by the event.""" return [] @@ -2010,14 +2024,14 @@ class FreeBusy(CalendarEvent): if not request.user.has_perm("core.view_person_free_busy_feed_rule", person): raise PermissionDenied() for feed in CalendarEventMixin.valid_feeds: - feed_events = feed.get_busy_objects(request=request, obj=person) + feed_events = feed.get_availability_objects(request=request, obj=person) if feed_events: events.update(feed_events) for group in Group.objects.filter(id__in=groups): if not request.user.has_perm("core.view_group_free_busy_feed_rule", group): raise PermissionDenied() for feed in CalendarEventMixin.valid_feeds: - feed_events = feed.get_busy_objects(request=request, obj=group) + feed_events = feed.get_availability_objects(request=request, obj=group) if feed_events: events.update(feed_events) @@ -2025,11 +2039,32 @@ class FreeBusy(CalendarEvent): @classmethod def value_description(cls, reference_object, request: HttpRequest | None = None) -> str: - return _("Blocked because of events in feed: {}").format(reference_object.dav_verbose_name) + return _("Free/busy status generated by events in feed: {}").format(reference_object.dav_verbose_name) + + @classmethod + def value_freebusy_type(cls, reference_object, request: HttpRequest | None = None) -> str: + return "free" @classmethod def value_title(cls, reference_object, request: HttpRequest | None = None) -> str: - return _("Busy") + return _("Free/Busy") + + @classmethod + def value_freebusy( + cls, reference_object, request: HttpRequest | None = None + ) -> str: + if cls.value_freebusy_type(reference_object, request) == "free": + freebusy_type = "FREE" + else: + freebusy_type = "BUSY" + + ical_datetime_start = vDatetime(reference_object.value_start_datetime(reference_object, request)).to_ical().decode() if type(reference_object.value_start_datetime(reference_object, request)) == datetime else vDate(reference_object.value_start_datetime(reference_object, request)).to_ical().decode() + ical_datetime_end = vDatetime(reference_object.value_end_datetime(reference_object, request)).to_ical().decode() if type(reference_object.value_end_datetime(reference_object, request)) == datetime else vDate(reference_object.value_end_datetime(reference_object, request)).to_ical().decode() + + print(ical_datetime_start, ical_datetime_end) + + return f"FREEBUSY;FBTYPE={freebusy_type}:{ical_datetime_start}/{ical_datetime_end}" + @classmethod def value_meta(cls, reference_object, request: HttpRequest | None = None) -> dict: @@ -2051,6 +2086,12 @@ class FreeBusy(CalendarEvent): else [], } + @classmethod + def value_component_type( + cls, reference_object, request: HttpRequest | None = None + ): + return "freebusy" + class AvailabilityEvent(CalendarEvent): """A calendar feed with availability events.""" @@ -2171,7 +2212,7 @@ class AvailabilityEvent(CalendarEvent): return qs @classmethod - def get_busy_objects(cls, request, obj: Union[Person, Group] = None) -> QuerySet: + def get_availability_objects(cls, request, obj: Union[Person, Group] = None) -> QuerySet: if isinstance(obj, Person): return ( cls.objects.for_persons([obj]) | cls.objects.for_groups(obj.member_of.all()) @@ -2415,6 +2456,14 @@ class PersonalEvent(CalendarEvent): """Return the location of the calendar event.""" return reference_object.location + def get_availability_persons(self) -> QuerySet: + return Person.objects.filter( + Q(pk=self.owner.id) | Q(pk__in=self.persons.all()) | Q(member_of__in=self.groups.all()) + ) + + def get_availability_groups(self) -> QuerySet: + return self.groups.all() + @classmethod def value_meta( cls, reference_object: "PersonalEvent", request: HttpRequest | None = None @@ -2488,7 +2537,7 @@ class PersonalEvent(CalendarEvent): return qs @classmethod - def get_busy_objects(cls, request, obj: Union[Person, Group] = None) -> QuerySet: + def get_availability_objects(cls, request, obj: Union[Person, Group] = None) -> QuerySet: qs = super().get_objects(request=request) if isinstance(obj, Person): persons = [obj] @@ -2501,14 +2550,6 @@ class PersonalEvent(CalendarEvent): return qs return None - def get_availability_persons(self) -> QuerySet: - return Person.objects.filter( - Q(pk=self.owner.id) | Q(pk__in=self.persons.all()) | Q(member_of__in=self.groups.all()) - ) - - def get_availability_groups(self) -> QuerySet: - return self.groups.all() - class Organisation(ContactMixin, ExtensibleModel): _class_name = "organisation" diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py index 306c52ceb0898b7afe587a331157035470794ec7..62813f0405710628792360a9ee41556bf89494b7 100644 --- a/aleksis/core/util/core_helpers.py +++ b/aleksis/core/util/core_helpers.py @@ -23,7 +23,7 @@ from django.utils.module_loading import import_string import django_ical.feedgenerator as feedgenerator from cache_memoize import cache_memoize -from icalendar import Calendar, Component, Event, Timezone, Todo +from icalendar import Calendar, Component, Event, FreeBusy, Timezone, Todo if TYPE_CHECKING: from django.contrib.contenttypes.models import ContentType @@ -567,13 +567,20 @@ class ExtendedICal20Feed(feedgenerator.ICal20Feed): for item in self.items: component_type = item.get("component_type") - element = Todo() if component_type == "todo" else Event() + if component_type == "todo": + element = Todo() + elif component_type == "freebusy": + element = FreeBusy() + else: + element = Event() for ifield, efield in EXTENDED_ITEM_ELEMENT_FIELD_MAP: if self._is_unrequested_prop(element, efield, params): continue val = item.get(ifield) + + print(item, ifield, val) if val: if ifield == "attendee": for list_item in val: