Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added naive way to expand recurring events. #66

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion frontend/src/components/Calendar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ export default {
select: this.selectCallback,
eventClick: this.eventClickCallback,
eventDrop: this.eventDropCallback,
eventResize: this.eventResizeCallback
eventResize: this.eventResizeCallback,
scrollTime: '09:00:00'
})
this.calendar.render()
this.addEventSources(this.calendars)
Expand Down Expand Up @@ -128,6 +129,9 @@ export default {
this.calendar.addEvent(event, true)
},
eventClickCallback (info) {
if (!info.event.startEditable) {
return
}
var evtCalendar = info.event.extendedProps.calendar
var calType = (evtCalendar.domain) ? 'shared' : 'user'
this.$router.push({
Expand Down
86 changes: 73 additions & 13 deletions modoboa_radicale/backends/caldav_.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import caldav
from caldav.elements import dav, ical
from caldav import Calendar
from dateutil.relativedelta import relativedelta
import vobject

from django.utils import timezone
Expand All @@ -21,7 +22,7 @@ class Caldav_Backend(CalendarBackend):

def __init__(self, username, password, calendar=None):
"""Constructor."""
super(Caldav_Backend, self).__init__(calendar)
super().__init__(calendar)
server_url = smart_str(
param_tools.get_global_parameter("server_location"))
self.client = caldav.DAVClient(
Expand All @@ -30,40 +31,53 @@ def __init__(self, username, password, calendar=None):
if self.calendar:
self.remote_cal = Calendar(self.client, calendar.encoded_path)

def _serialize_event(self, event):
def _serialize_event(
self,
vevent,
start=None,
end=None,
all_day: bool = False,
**kwargs) -> dict:
"""Convert a vevent to a dictionary."""
vevent = event.vobject_instance.vevent
description = (
vevent.description.value
if "description" in vevent.contents else ""
)
event_id = vevent.uid.value
result = {
"id": vevent.uid.value,
"id": event_id,
"title": vevent.summary.value,
"color": self.calendar.color,
"description": description,
"calendar": self.calendar,
"attendees": []
"attendees": [],
# Quick way to disable edition of recurring events until
# we support it.
"editable": not kwargs.get("recurring", False),
**kwargs
}
if isinstance(vevent.dtstart.value, datetime.datetime):
all_day = False
if start is None and end is None:
start = vevent.dtstart.value
end = vevent.dtend.value
if isinstance(start, datetime.datetime):
all_day = all_day
else:
tz = timezone.get_current_timezone()
all_day = True
start = tz.localize(
datetime.datetime.combine(
vevent.dtstart.value, datetime.time.min))
datetime.datetime.combine(start, datetime.time.min))
end = tz.localize(
datetime.datetime.combine(
vevent.dtend.value, datetime.time.min))
datetime.datetime.combine(end, datetime.time.min))
result.update({
"allDay": all_day,
"start": start,
"end": end
})
if "attendee" in vevent.contents:
if "organizer" in vevent.contents:
organizer = vevent.organizer.value.replace("mailto:", "")
if organizer != self.calendar.mailbox.full_address:
result["editable"] = False
for attendee in vevent.contents["attendee"]:
email = (
attendee.value
Expand All @@ -77,6 +91,51 @@ def _serialize_event(self, event):
})
return result

def _serialize_events(self, event, start, end) -> list:
"""Convert this event to a list of dictionaries.
In case of recurring event, we will generate as much
dictionaries as necessary, according to given start and end
dates.
"""
tz = timezone.get_current_timezone()
result = []
for vevent in event.vobject_instance.vevent_list:
rruleset = vevent.getrruleset()
if rruleset:
all_day = True
duration = relativedelta(
vevent.dtend.value, vevent.dtstart.value)
if isinstance(vevent.dtstart.value, datetime.datetime):
all_day = False
for date in list(rruleset):
if date.tzinfo is None:
date = tz.localize(date)
if date >= start and date <= end:
result += [
self._serialize_event(
vevent, date, date + duration, all_day=all_day,
recurring=True
)
]
else:
options = {}
if "recurrence-id" in vevent.contents:
# Remove previously expanded event because it has
# been overriden
recurrence_id = vevent.recurrence_id.value
options["recurring"] = True
for (pos, item) in enumerate(result):
if (
item["id"] == vevent.uid.value and
item["start"] == recurrence_id
):
del result[pos]
break
result += [self._serialize_event(vevent, **options)]
return result

def create_calendar(self, url):
"""Create a new calendar."""
self.client.mkcalendar(url)
Expand Down Expand Up @@ -147,14 +206,15 @@ def get_event(self, uid):
"""Retrieve and event using its uid."""
url = "{}/{}.ics".format(self.remote_cal.url.geturl(), uid)
event = self.remote_cal.event_by_url(url)
return self._serialize_event(event)
vevent = event.vobject_instance.vevent
return self._serialize_event(vevent)

def get_events(self, start, end):
"""Retrieve a list of events."""
orig_events = self.remote_cal.date_search(start, end)
events = []
for event in orig_events:
events.append(self._serialize_event(event))
events += self._serialize_events(event, start, end)
return events

def delete_event(self, uid):
Expand Down
5 changes: 4 additions & 1 deletion modoboa_radicale/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,13 @@ class EventSerializer(serializers.Serializer):
class ROEventSerializer(EventSerializer):
"""Event serializer for read operations."""

editable = serializers.BooleanField()
recurring = serializers.BooleanField(default=False)

def __init__(self, *args, **kwargs):
"""Set calendar field based on type."""
calendar_type = kwargs.pop("calendar_type")
super(ROEventSerializer, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.fields["calendar"] = (
UserCalendarSerializer() if calendar_type == "user"
else SharedCalendarSerializer()
Expand Down