Skip to content

Commit d23ff3c

Browse files
committed
Add attendees to the ics file
1 parent d18446b commit d23ff3c

File tree

2 files changed

+84
-1
lines changed

2 files changed

+84
-1
lines changed

backend/src/appointment/controller/calendar.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class RemoteEventState(Enum):
4343
REJECTED = 'REJECTED'
4444
TENTATIVE = 'TENTATIVE'
4545
CONFIRMED = 'CONFIRMED'
46+
ACCEPTED = 'ACCEPTED'
4647

4748

4849
class BaseConnector:
@@ -599,12 +600,14 @@ def create_vevent(
599600
):
600601
"""create an event in ical format for .ics file creation"""
601602
cal = Calendar()
602-
cal.add('prodid', '-//Thunderbird Appointment//tba.dk//')
603+
cal.add('prodid', '-//thunderbird.net/Thunderbird Appointment//EN')
603604
cal.add('version', '2.0')
604605
cal.add('method', 'CANCEL' if event_status == RemoteEventState.CANCELLED.value else 'REQUEST')
606+
605607
org = vCalAddress('MAILTO:' + organizer.preferred_email)
606608
org.params['cn'] = vText(organizer.preferred_email)
607609
org.params['role'] = vText('CHAIR')
610+
608611
event = Event()
609612
event.add('uid', appointment.uuid.hex)
610613
event.add('summary', appointment.title)
@@ -618,6 +621,20 @@ def create_vevent(
618621
event['description'] = appointment.details
619622
event['organizer'] = org
620623

624+
if slot.attendee:
625+
attendee = vCalAddress(f'MAILTO:{slot.attendee.email}')
626+
627+
if slot.attendee.name and len(slot.attendee.name.strip()) > 0:
628+
attendee.params['cn'] = vText(slot.attendee.name)
629+
else:
630+
attendee.params['cn'] = vText(slot.attendee.email)
631+
632+
# Set the attendee status to accepted by default
633+
# since they are the ones who are submitting the request
634+
attendee.params['partstat'] = vText(RemoteEventState.ACCEPTED.value)
635+
attendee.params['role'] = vText('REQ-PARTICIPANT')
636+
event.add('attendee', attendee)
637+
621638
# Prefer the slot meeting link url over the appointment location url
622639
location_url = slot.meeting_link_url if slot.meeting_link_url is not None else appointment.location_url
623640

backend/test/unit/test_calendar_tools.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,72 @@ def test_meeting_url_in_location(
191191
assert ics
192192
assert ':'.join(['LOCATION', slot.meeting_link_url]) in ics.decode()
193193

194+
def test_prodid_is_set(self, make_google_calendar, make_appointment, make_pro_subscriber):
195+
"""Verify that the prodid is set correctly in the calendar"""
196+
subscriber = make_pro_subscriber()
197+
calendar = make_google_calendar(subscriber_id=subscriber.id)
198+
appointment = make_appointment(calendar_id=calendar.id)
199+
slot = appointment.slots[0]
200+
201+
ics = Tools().create_vevent(appointment, slot, subscriber)
202+
ics_str = ics.decode()
203+
204+
assert 'PRODID:-//thunderbird.net/Thunderbird Appointment//EN' in ics_str
205+
206+
def test_attendee_with_name(self, make_google_calendar, make_appointment, make_pro_subscriber):
207+
"""Verify attendee is added with name when slot has an attendee with a name"""
208+
subscriber = make_pro_subscriber()
209+
calendar = make_google_calendar(subscriber_id=subscriber.id)
210+
appointment = make_appointment(calendar_id=calendar.id)
211+
slot = appointment.slots[0]
212+
slot.attendee = models.Attendee(email='[email protected]', name='John Doe', timezone='Europe/Berlin')
213+
214+
ics = Tools().create_vevent(appointment, slot, subscriber)
215+
ics_str = ics.decode()
216+
217+
# Unfold lines (remove CRLF + leading space) and drop CR to keep assertions simple
218+
ics_str = ics_str.replace('\r\n ', '').replace('\r', '')
219+
220+
assert 'ATTENDEE' in ics_str
221+
assert 'MAILTO:[email protected]' in ics_str
222+
assert 'CN="John Doe"' in ics_str
223+
assert 'ROLE=REQ-PARTICIPANT' in ics_str
224+
assert 'PARTSTAT=ACCEPTED' in ics_str
225+
226+
def test_attendee_without_name_uses_email(self, make_google_calendar, make_appointment, make_pro_subscriber):
227+
"""Verify attendee is added with email as name when slot has an attendee without a name"""
228+
subscriber = make_pro_subscriber()
229+
calendar = make_google_calendar(subscriber_id=subscriber.id)
230+
appointment = make_appointment(calendar_id=calendar.id)
231+
slot = appointment.slots[0]
232+
slot.attendee = models.Attendee(email='[email protected]', name=None, timezone='Europe/Berlin')
233+
234+
ics = Tools().create_vevent(appointment, slot, subscriber)
235+
ics_str = ics.decode()
236+
237+
# Unfold lines (remove CRLF + leading space) and drop CR to keep assertions simple
238+
ics_str = ics_str.replace('\r\n ', '').replace('\r', '')
239+
240+
assert 'ATTENDEE' in ics_str
241+
assert 'MAILTO:[email protected]' in ics_str
242+
assert '[email protected]' in ics_str
243+
assert 'ROLE=REQ-PARTICIPANT' in ics_str
244+
assert 'PARTSTAT=ACCEPTED' in ics_str
245+
246+
def test_no_attendee(self, make_google_calendar, make_appointment, make_pro_subscriber):
247+
"""Verify no attendee is added when slot has no attendee"""
248+
subscriber = make_pro_subscriber()
249+
calendar = make_google_calendar(subscriber_id=subscriber.id)
250+
appointment = make_appointment(calendar_id=calendar.id)
251+
slot = appointment.slots[0]
252+
slot.attendee = None
253+
254+
ics = Tools().create_vevent(appointment, slot, subscriber)
255+
ics_str = ics.decode()
256+
257+
# The ATTENDEE field should not be present when there's no attendee
258+
assert 'ATTENDEE' not in ics_str
259+
194260

195261
class TestDnsCaldavLookup:
196262
def test_for_host(self):

0 commit comments

Comments
 (0)