Add one off events for skipped meetings
Rather than silently removing the skipped meeting from the calendar as is the default behavious of exdate add a one off calendar entry for each skipped meeting. The aim is to show that the slot is generally consumed just that the meeting has been explictly cancelled/skipped. Change-Id: Idd9eb1a04fe9c9661e87978c0128a292bb8e89a4
This commit is contained in:
parent
ddf64aef6e
commit
f02d892ea6
17
README.rst
17
README.rst
@ -258,14 +258,21 @@ meeting should be ommited from the ical schedule
|
|||||||
VERSION:2.0
|
VERSION:2.0
|
||||||
PRODID:-//yaml2ical agendas//EN
|
PRODID:-//yaml2ical agendas//EN
|
||||||
BEGIN:VEVENT
|
BEGIN:VEVENT
|
||||||
|
SUMMARY:CANCELLED: Example Project Meeting (20151026T210000Z)
|
||||||
|
DTSTART;VALUE=DATE-TIME:20151026T210000Z
|
||||||
|
DURATION:PT1H
|
||||||
|
DESCRIPTION:Face 2 Face meeting at some location
|
||||||
|
LOCATION:#openstack-meeting
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
SUMMARY:Example Project Meeting
|
SUMMARY:Example Project Meeting
|
||||||
DTSTART;VALUE=DATE-TIME:20151005T210000Z
|
DTSTART;VALUE=DATE-TIME:20151005T210000Z
|
||||||
DURATION:PT1H
|
DURATION:PT1H
|
||||||
EXDATE:20151026T210000
|
EXDATE:20151026T210000Z
|
||||||
DESCRIPTION:Project: Example Project Meeting\\nChair: A. Random Developer
|
DESCRIPTION:Project: Example Project Meeting\nChair: A. Random Developer
|
||||||
\\nDescription: This meeting is a weekly gathering of developers working o
|
\nDescription: This meeting is a weekly gathering of developers working o
|
||||||
n Example project.\\n\\nAgenda URL: https://wiki.openstack.org/wiki/Meetin
|
n Example project.\n\nAgenda URL: https://wiki.openstack.org/wiki/Meeting
|
||||||
gs/Example\\nProject URL: https://wiki.openstack.org/wiki/Example
|
s/Example\nProject URL: https://wiki.openstack.org/wiki/Example
|
||||||
LOCATION:#openstack-meeting
|
LOCATION:#openstack-meeting
|
||||||
RRULE:FREQ=WEEKLY
|
RRULE:FREQ=WEEKLY
|
||||||
END:VEVENT
|
END:VEVENT
|
||||||
|
@ -34,14 +34,27 @@ class Yaml2IcalCalendar(icalendar.Calendar):
|
|||||||
"""Add this meeting to the calendar."""
|
"""Add this meeting to the calendar."""
|
||||||
|
|
||||||
for sch in meeting.schedules:
|
for sch in meeting.schedules:
|
||||||
# one Event per iCal file
|
self.add_schedule(meeting, sch)
|
||||||
|
|
||||||
|
def add_schedule(self, meeting, sch, exdate=None):
|
||||||
event = icalendar.Event()
|
event = icalendar.Event()
|
||||||
|
|
||||||
# NOTE(jotan): I think the summary field needs to be unique per
|
# NOTE(jotan): I think the summary field needs to be unique per
|
||||||
# event in an ical file (at least, for it to work with
|
# event in an ical file (at least, for it to work with
|
||||||
# Google Calendar)
|
# Google Calendar)
|
||||||
|
|
||||||
event.add('summary', meeting.project)
|
summary = meeting.project
|
||||||
|
# NOTE(tonyb): If we're adding an a place holder event for a
|
||||||
|
# cancelled meeting make that as obvious as possible in the
|
||||||
|
# summary.
|
||||||
|
if exdate:
|
||||||
|
# NOTE(tonyb): Because some iCal consumers require that the
|
||||||
|
# summary be unique, and adding multiple "CANCELLED: $x"
|
||||||
|
# entries would violate that rule, append the (UTC)
|
||||||
|
# timestamp for the cancelled meeting.
|
||||||
|
suffix = exdate.date_str
|
||||||
|
summary = 'CANCELLED: %s (%s)' % (summary, suffix)
|
||||||
|
event.add('summary', summary)
|
||||||
event.add('location', '#' + sch.irc)
|
event.add('location', '#' + sch.irc)
|
||||||
|
|
||||||
# add ical description
|
# add ical description
|
||||||
@ -60,8 +73,11 @@ class Yaml2IcalCalendar(icalendar.Calendar):
|
|||||||
ical_descript = "\n".join((ical_descript,
|
ical_descript = "\n".join((ical_descript,
|
||||||
"Project URL: %s" %
|
"Project URL: %s" %
|
||||||
(meeting.extras['project_url'])))
|
(meeting.extras['project_url'])))
|
||||||
event.add('description', ical_descript)
|
|
||||||
|
|
||||||
|
# NOTE(tonyb): If we're adding an a place holder event for a
|
||||||
|
# cancelled meeting do not add an rrule and set dtstart to the
|
||||||
|
# skipped date.
|
||||||
|
if not exdate:
|
||||||
# get starting date
|
# get starting date
|
||||||
next_meeting = sch.recurrence.next_occurence(sch.start_date,
|
next_meeting = sch.recurrence.next_occurence(sch.start_date,
|
||||||
sch.day)
|
sch.day)
|
||||||
@ -75,13 +91,20 @@ class Yaml2IcalCalendar(icalendar.Calendar):
|
|||||||
|
|
||||||
# add recurrence rule
|
# add recurrence rule
|
||||||
event.add('rrule', sch.recurrence.rrule())
|
event.add('rrule', sch.recurrence.rrule())
|
||||||
|
event.add('description', ical_descript)
|
||||||
|
else:
|
||||||
|
event.add('dtstart', exdate.date)
|
||||||
|
event.add('description', exdate.reason)
|
||||||
|
|
||||||
event.add('duration', datetime.timedelta(minutes=sch.duration))
|
event.add('duration', datetime.timedelta(minutes=sch.duration))
|
||||||
|
|
||||||
# Add exdate (exclude date) if present
|
# Add exdate (exclude date) if present
|
||||||
if hasattr(sch, 'skip_dates'):
|
if not exdate and hasattr(sch, 'skip_dates'):
|
||||||
for skip_date in sch.skip_dates:
|
for skip_date in sch.skip_dates:
|
||||||
event.add('exdate', skip_date.date)
|
event.add('exdate', skip_date.date)
|
||||||
|
# NOTE(tonyb): If this is a skipped meeting add a
|
||||||
|
# non-recurring event with an obvisous summary.
|
||||||
|
self.add_schedule(meeting, sch, exdate=skip_date)
|
||||||
|
|
||||||
# add event to calendar
|
# add event to calendar
|
||||||
self.add_component(event)
|
self.add_component(event)
|
||||||
|
@ -94,12 +94,27 @@ class MeetingTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
def test_skip_meeting(self):
|
def test_skip_meeting(self):
|
||||||
meeting_yaml = sample_data.MEETING_WITH_SKIP_DATES
|
meeting_yaml = sample_data.MEETING_WITH_SKIP_DATES
|
||||||
p = re.compile('.*exdate:\s*20150810T120000', re.IGNORECASE)
|
# Copied from sample_data.MEETING_WITH_SKIP_DATES
|
||||||
|
summary = 'OpenStack Subteam 8 Meeting'
|
||||||
|
patterns = []
|
||||||
|
# The "main" meeting should have an exdate
|
||||||
|
patterns.append(re.compile('.*exdate:\s*20150810T120000', re.I))
|
||||||
|
# The "main" meeting should start on 2015-08-13
|
||||||
|
patterns.append(re.compile('.*dtstart;.*:20150803T120000Z', re.I))
|
||||||
|
# The "main" meeting should have a simple summary
|
||||||
|
patterns.append(re.compile('.*summary:\s*%s' % summary, re.I))
|
||||||
|
# The "skipped" meeting should start on 20150806
|
||||||
|
patterns.append(re.compile('.*dtstart;.*:20150810T120000Z', re.I))
|
||||||
|
# The "skipped" meeting should say include 'CANCELLED' and the datetime
|
||||||
|
patterns.append(re.compile('.*summary:\s*CANCELLED.*20150810T120000Z',
|
||||||
|
re.I))
|
||||||
m = meeting.load_meetings(meeting_yaml)[0]
|
m = meeting.load_meetings(meeting_yaml)[0]
|
||||||
cal = ical.Yaml2IcalCalendar()
|
cal = ical.Yaml2IcalCalendar()
|
||||||
cal.add_meeting(m)
|
cal.add_meeting(m)
|
||||||
|
cal_str = str(cal.to_ical())
|
||||||
self.assertTrue(hasattr(m.schedules[0], 'skip_dates'))
|
self.assertTrue(hasattr(m.schedules[0], 'skip_dates'))
|
||||||
self.assertNotEqual(None, p.match(str(cal.to_ical())))
|
for p in patterns:
|
||||||
|
self.assertNotEqual(None, p.match(cal_str))
|
||||||
|
|
||||||
def test_skip_meeting_missing_skip_date(self):
|
def test_skip_meeting_missing_skip_date(self):
|
||||||
self.assertRaises(KeyError,
|
self.assertRaises(KeyError,
|
||||||
|
Loading…
Reference in New Issue
Block a user