class EventResource(ItemResource): fields = ItemResource.fields.copy() fields.update({ 'id': lambda item: item.eventid, 'subject': lambda item: item.subject, 'recurrence': recurrence_json, 'start': lambda req, item: _tzdate(item.start, req), 'end': lambda req, item: _tzdate(item.end, req), 'location': lambda item: {'displayName': item.location, 'address': {}}, # TODO 'importance': lambda item: item.urgency, 'sensitivity': lambda item: sensitivity_map[item.sensitivity], 'hasAttachments': lambda item: item.has_attachments, 'body': lambda req, item: get_body(req, item), 'isReminderOn': lambda item: item.reminder, 'reminderMinutesBeforeStart': lambda item: item.reminder_minutes, 'attendees': lambda item: attendees_json(item), 'bodyPreview': lambda item: item.body_preview, 'isAllDay': lambda item: item.all_day, 'showAs': lambda item: show_as_map[item.show_as], 'seriesMasterId': lambda item: item.item.eventid if isinstance(item, kopano.Occurrence) else None, 'type': lambda item: event_type(item), 'responseRequested': lambda item: item.response_requested, 'iCalUId': lambda item: kopano.hex(kopano.bdec(item.icaluid)) if item.icaluid else None, # graph uses hex!? 'organizer': lambda item: get_email(item.from_), 'isOrganizer': lambda item: item.from_.email == item.sender.email, }) set_fields = { 'subject': lambda item, arg: setattr(item, 'subject', arg), 'location': lambda item, arg: setattr(item, 'location', arg['displayName']), # TODO 'body': set_body, 'start': lambda item, arg: set_date(item, 'start', arg), 'end': lambda item, arg: set_date(item, 'end', arg), 'attendees': lambda item, arg: attendees_set(item, arg), 'recurrence': recurrence_set, 'isAllDay': lambda item, arg: setattr(item, 'all_day', arg), 'isReminderOn': lambda item, arg: setattr(item, 'reminder', arg), 'reminderMinutesBeforeStart': lambda item, arg: setattr(item, 'reminder_minutes', arg), } # TODO delta functionality seems to include expanding recurrences!? check with MSGE def on_get(self, req, resp, userid=None, folderid=None, eventid=None, method=None): server, store = _server_store(req, userid, self.options) folder = _folder(store, folderid or 'calendar') event = folder.event(eventid) if method == 'attachments': attachments = list(event.attachments(embedded=True)) data = (attachments, DEFAULT_TOP, 0, len(attachments)) self.respond(req, resp, data, AttachmentResource.fields) elif method == 'instances': start, end = _start_end(req) data = (event.occurrences(start, end), DEFAULT_TOP, 0, 0) self.respond(req, resp, data) elif method: raise falcon.HTTPBadRequest(None, "Unsupported segment '%s'" % method) else: self.respond(req, resp, event) def on_post(self, req, resp, userid=None, folderid=None, eventid=None, method=None): server, store = _server_store(req, userid, self.options) folder = _folder(store, folderid or 'calendar') item = folder.event(eventid) fields = json.loads(req.stream.read().decode('utf-8')) if method == 'accept': item.accept(comment=fields.get('comment'), respond=(fields.get('sendResponse')=='true')) resp.status = falcon.HTTP_202 elif method == 'tentativelyAccept': item.accept(comment=fields.get('comment'), tentative=True, respond=(fields.get('sendResponse')=='true')) resp.status = falcon.HTTP_202 elif method == 'decline': item.decline(comment=fields.get('comment'), respond=(fields.get('sendResponse')=='true')) resp.status = falcon.HTTP_202 elif method == 'attachments': if fields['@odata.type'] == '#microsoft.graph.fileAttachment': att = item.create_attachment(fields['name'], base64.urlsafe_b64decode(fields['contentBytes'])) self.respond(req, resp, att, AttachmentResource.fields) resp.status = falcon.HTTP_201 elif method: raise falcon.HTTPBadRequest(None, "Unsupported segment '%s'" % method) def on_patch(self, req, resp, userid=None, folderid=None, eventid=None, method=None): server, store = _server_store(req, userid, self.options) folder = _folder(store, folderid or 'calendar') item = folder.event(eventid) fields = json.loads(req.stream.read().decode('utf-8')) for field, value in fields.items(): if field in self.set_fields: self.set_fields[field](item, value) self.respond(req, resp, item, EventResource.fields) def on_delete(self, req, resp, userid=None, folderid=None, eventid=None): server, store = _server_store(req, userid, self.options) folder = _folder(store, folderid or 'calendar') event = folder.event(eventid) folder.delete(event) self.respond_204(resp)
class EventResource(ItemResource): fields = ItemResource.fields.copy() fields.update({ 'id': lambda item: item.eventid, 'subject': lambda item: item.subject, 'recurrence': recurrence_json, 'start': lambda req, item: _tzdate(item.start, item.tzinfo, req), 'end': lambda req, item: _tzdate(item.end, item.tzinfo, req), 'location': location_json, 'importance': lambda item: item.urgency, 'sensitivity': lambda item: item.sensitivity, 'hasAttachments': lambda item: item.has_attachments, 'body': lambda req, item: get_body(req, item), 'isReminderOn': lambda item: item.reminder, 'reminderMinutesBeforeStart': lambda item: item.reminder_minutes, 'attendees': lambda item: attendees_json(item), 'bodyPreview': lambda item: item.body_preview, 'isAllDay': lambda item: item.all_day, 'showAs': lambda item: show_as_map[item.show_as], 'seriesMasterId': lambda item: item.item.eventid if item.recurring and isinstance(item, kopano.Occurrence) else None, 'type': lambda item: event_type(item), 'responseRequested': lambda item: item.response_requested, 'iCalUId': lambda item: kopano.hex(kopano.bdec(item.icaluid)) if item.icaluid else None, # graph uses hex!? 'organizer': lambda item: get_email(item.from_), 'isOrganizer': lambda item: item.from_.email == item.sender.email, 'isCancelled': lambda item: item.canceled, 'responseStatus': lambda item: responsestatus_json(item), # 8.7.x does not have onlinemeetingurl attribute, so we must check if its there for compatibility 'onlineMeetingUrl': lambda item: item.onlinemeetingurl if hasattr(item, 'onlinemeetingurl') else '' }) set_fields = { 'subject': lambda item, arg: setattr(item, 'subject', arg), 'location': lambda item, arg: location_set(item, arg), 'body': set_body, 'start': lambda item, arg: set_date(item, 'start', arg), 'end': lambda item, arg: set_date(item, 'end', arg), 'attendees': lambda item, arg: attendees_set(item, arg), 'recurrence': recurrence_set, 'isAllDay': lambda item, arg: setattr(item, 'all_day', arg), 'isReminderOn': lambda item, arg: setattr(item, 'reminder', arg), 'reminderMinutesBeforeStart': lambda item, arg: setattr(item, 'reminder_minutes', arg), # 8.7.x does not have onlinemeetingurl attribute, so we must check if its there for compatibility 'onlineMeetingUrl': lambda item, arg: setattr(item, 'onlinemeetingurl', arg) if hasattr(item, 'onlinemeetingurl') else None, } # TODO delta functionality seems to include expanding recurrences!? check with MSGE def get_event(self, folder, eventid): try: return folder.event(eventid) except binascii.Error: raise HTTPBadRequest('Event id is malformed') except kopano.errors.NotFoundError: raise HTTPNotFound(description='Item not found') @experimental def handle_get_attachments(self, req, resp, event): attachments = list(event.attachments(embedded=True)) data = (attachments, DEFAULT_TOP, 0, len(attachments)) self.respond(req, resp, data, AttachmentResource.fields) def handle_get_instances(self, req, resp, event): start, end = _start_end(req) def yielder(**kwargs): for occ in event.occurrences(start, end, **kwargs): yield occ data = self.generator(req, yielder) self.respond(req, resp, data) def handle_get(self, req, resp, event): self.respond(req, resp, event) def on_get(self, req, resp, userid=None, folderid=None, eventid=None, method=None): handler = None if method == 'attachments': handler = self.handle_get_attachments elif method == 'instances': handler = self.handle_get_instances elif method: raise HTTPBadRequest("Unsupported event segment '%s'" % method) else: handler = self.handle_get server, store, userid = _server_store(req, userid, self.options) folder = _folder(store, folderid or 'calendar') event = self.get_event(folder, eventid) handler(req, resp, event=event) def handle_post_accept(self, req, resp, fields, item): _ = req.context.i18n.gettext self.validate_json(mr_schema, fields) item.accept(comment=fields.get('comment'), respond=(fields.get('sendResponse', True)), subject_prefix=_("Accepted")) resp.status = falcon.HTTP_202 def handle_post_tentativelyAccept(self, req, resp, fields, item): _ = req.context.i18n.gettext self.validate_json(mr_schema, fields) item.accept(comment=fields.get('comment'), tentative=True, respond=(fields.get('sendResponse', True)), subject_prefix=_("Tentatively accepted")) resp.status = falcon.HTTP_202 def handle_post_decline(self, req, resp, fields, item): _ = req.context.i18n.gettext self.validate_json(mr_schema, fields) item.decline(comment=fields.get('comment'), respond=(fields.get('sendResponse', True)), subject_prefix=_("Declined")) resp.status = falcon.HTTP_202 @experimental def handle_post_attachments(self, req, resp, fields, item): if fields['@odata.type'] == '#microsoft.graph.fileAttachment': att = item.create_attachment( fields['name'], base64.urlsafe_b64decode(fields['contentBytes'])) self.respond(req, resp, att, AttachmentResource.fields) resp.status = falcon.HTTP_201 def on_post(self, req, resp, userid=None, folderid=None, eventid=None, method=None): handler = None if method == 'accept': handler = self.handle_post_accept elif method == 'tentativelyAccept': handler = self.handle_post_tentativelyAccept elif method == 'decline': handler = self.handle_post_decline elif method == 'attachments': handler = self.handle_post_attachments elif method: raise HTTPBadRequest("Unsupported event segment '%s'" % method) else: raise HTTPBadRequest("Unsupported in event") server, store, userid = _server_store(req, userid, self.options) folder = _folder(store, folderid or 'calendar') item = self.get_event(folder, eventid) fields = self.load_json(req) handler(req, resp, fields=fields, item=item) def handle_patch(self, req, resp, fields, item): for field, value in fields.items(): if field in self.set_fields: self.set_fields[field](item, value) self.respond(req, resp, item, EventResource.fields) def on_patch(self, req, resp, userid=None, folderid=None, eventid=None, method=None): server, store, userid = _server_store(req, userid, self.options) folder = _folder(store, folderid or 'calendar') item = self.get_event(folder, eventid) fields = self.load_json(req) self.handle_patch(req, resp, fields=fields, item=item) def handle_delete(self, req, resp, folder, item): # If meeting is organised, sent cancellation if self.fields['isOrganizer'](item): item.cancel() item.send() folder.delete(item) self.respond_204(resp) def on_delete(self, req, resp, userid=None, folderid=None, eventid=None): handler = self.handle_delete server, store, userid = _server_store(req, userid, self.options) folder = _folder(store, folderid or 'calendar') item = self.get_event(folder, eventid) handler(req, resp, folder=folder, item=item)
class EventResource(ItemResource): fields = ItemResource.fields.copy() fields.update({ 'id': lambda item: item.eventid, 'subject': lambda item: item.subject, 'recurrence': recurrence_json, 'start': lambda req, item: _tzdate(item.start, item.tzinfo, req), 'end': lambda req, item: _tzdate(item.end, item.tzinfo, req), 'location': location_json, 'importance': lambda item: item.urgency, 'sensitivity': lambda item: item.sensitivity, 'hasAttachments': lambda item: item.has_attachments, 'body': lambda req, item: get_body(req, item), 'isReminderOn': lambda item: item.reminder, 'reminderMinutesBeforeStart': lambda item: item.reminder_minutes, 'attendees': lambda item: attendees_json(item), 'bodyPreview': lambda item: item.body_preview, 'isAllDay': lambda item: item.all_day, 'showAs': lambda item: show_as_map[item.show_as], 'seriesMasterId': lambda item: item.item.eventid if item.recurring and isinstance(item, kopano.Occurrence) else None, 'type': lambda item: event_type(item), 'responseRequested': lambda item: item.response_requested, 'iCalUId': lambda item: kopano.hex(kopano.bdec(item.icaluid)) if item.icaluid else None, # graph uses hex!? 'organizer': lambda item: get_email(item.from_), 'isOrganizer': lambda item: item.from_.email == item.sender.email, 'isCancelled': lambda item: item.canceled, 'responseStatus': lambda item: responsestatus_json(item), # 8.7.x does not have onlinemeetingurl attribute, so we must check if its there for compatibility 'onlineMeetingUrl': lambda item: item.onlinemeetingurl if hasattr(item, 'onlinemeetingurl') else '' }) set_fields = { 'subject': lambda item, arg: setattr(item, 'subject', arg), 'location': lambda item, arg: location_set(item, arg), 'body': set_body, 'start': lambda item, arg: set_date(item, 'start', arg), 'end': lambda item, arg: set_date(item, 'end', arg), 'attendees': lambda item, arg: attendees_set(item, arg), 'recurrence': recurrence_set, 'isAllDay': lambda item, arg: setattr(item, 'all_day', arg), 'isReminderOn': lambda item, arg: setattr(item, 'reminder', arg), 'reminderMinutesBeforeStart': lambda item, arg: setattr(item, 'reminder_minutes', arg), # 8.7.x does not have onlinemeetingurl attribute, so we must check if its there for compatibility 'onlineMeetingUrl': lambda item, arg: setattr(item, 'onlinemeetingurl', arg) if hasattr(item, 'onlinemeetingurl') else None, } # TODO delta functionality seems to include expanding recurrences!? check with MSGE # GET @staticmethod def get_event(folder, eventid): try: return folder.event(eventid) except (binascii.Error, kopano.errors.ArgumentError): raise HTTPBadRequest('Event id is malformed') except kopano.errors.NotFoundError: raise HTTPNotFound(description='Item not found') def _get_event_instances(self, req, resp, folderid, eventid): start, end = _start_end(req) server, store, userid = req.context.server_store folder = _folder(store, folderid) event = self.get_event(folder, eventid) def yielder(**kwargs): for occ in event.occurrences(start, end, **kwargs): yield occ data = self.generator(req, yielder) self.respond(req, resp, data) def on_get_instances_by_folderid(self, req, resp, folderid, itemid): self._get_event_instances(req, resp, folderid, itemid) def on_get_instances(self, req, resp, itemid): self._get_event_instances(req, resp, "calendar", itemid) def handle_get(self, req, resp, event): self.respond(req, resp, event) @experimental def on_get_events(self, req, resp): store = req.context.server_store[1] calendar = store.calendar data = self.generator(req, calendar.items, calendar.count) self.respond(req, resp, data, EventResource.fields) def on_get_by_folderid(self, req, resp, folderid): """Get events of a specific folder.""" _, store, _ = req.context.server_store folder = store.folder(folderid) store.calendar = folder data = self.generator(req, store.calendar.items, store.calendar.count) self.respond(req, resp, data, EventResource.fields) def on_get_by_eventid(self, req, resp, itemid): store = req.context.server_store[1] folder = _folder(store, "calendar") event = self.get_event(folder, itemid) self.respond(req, resp, event, self.fields) def on_get_by_folderid_eventid(self, req, resp, folderid, itemid): store = req.context.server_store[1] folder = _folder(store, folderid) event = self.get_event(folder, itemid) self.respond(req, resp, event, self.fields) def on_get(self, req, resp, userid=None, folderid=None, itemid=None): raise HTTPBadRequest("Unsupported in event") # POST def _create_event(self, req, resp, folderid): """Create an events. Args: req (Request): Falcon request object. resp (Response): Falcon response object. folderid (str): folder ID which the event be created in. Raises: HTTPBadRequest: invalid argument error or request has empty payload. """ if not req.content_length: raise HTTPBadRequest("request has empty payload") fields = req.context.json_data self.validate_json(event_schema.create_schema_validator, fields) store = req.context.server_store[1] folder = _folder(store, folderid) try: item = self.create_message(folder, fields, EventResource.set_fields) except kopano.errors.ArgumentError as e: raise HTTPBadRequest("Invalid argument error '{}'".format(e)) if fields.get('attendees', None): # NOTE(longsleep): Sending can fail with NO_ACCCESS if no permission to outbox. item.send() self.respond(req, resp, item, EventResource.fields) @experimental def on_post_events(self, req, resp): """Handle POST request to create event in the "calendar" folder. Args: req (Request): Falcon request object. resp (Response): Falcon response object. """ self._create_event(req, resp, "calendar") @experimental def on_post_by_folderid(self, req, resp, folderid): """Handle POST request to create an event in a specific folder. Args: req (Request): Falcon request object. resp (Response): Falcon response object. folderid (str): folder ID which the event be created in. """ self._create_event(req, resp, folderid) def _accept_event(self, req, resp, folderid, eventid): fields = req.context.json_data self.validate_json(event_schema.action_schema_validator, fields) _ = req.context.i18n.gettext store = req.context.server_store[1] folder = _folder(store, folderid) item = self.get_event(folder, eventid) item.accept(comment=fields.get('comment'), respond=(fields.get('sendResponse', True)), subject_prefix=_("Accepted")) resp.status = falcon.HTTP_202 def on_post_accept_event_by_folderid(self, req, resp, folderid, itemid): self._accept_event(req, resp, folderid, itemid) def on_post_accept_event(self, req, resp, itemid): self._accept_event(req, resp, "calendar", itemid) def handle_post_tentativelyAccept(self, req, resp, fields, item): _ = req.context.i18n.gettext self.validate_json(event_schema.action_schema_validator, fields) item.accept(comment=fields.get('comment'), tentative=True, respond=(fields.get('sendResponse', True)), subject_prefix=_("Tentatively accepted")) resp.status = falcon.HTTP_202 def _decline_event(self, req, resp, folderid, itemid): fields = req.context.json_data self.validate_json(event_schema.action_schema_validator, fields) _ = req.context.i18n.gettext store = req.context.server_store[1] self.validate_json(event_schema.action_schema_validator, fields) folder = _folder(store, folderid) item = self.get_event(folder, itemid) item.decline(comment=fields.get('comment'), respond=(fields.get('sendResponse', True)), subject_prefix=_("Declined")) resp.status = falcon.HTTP_202 def on_post_decline_event_by_folderid(self, req, resp, folderid, itemid): self._decline_event(req, resp, folderid, itemid) def on_post_decline_event(self, req, resp, itemid): self._decline_event(req, resp, "calendar", itemid) def on_post(self, req, resp, userid=None, folderid=None, itemid=None, method=None): handler = None if method == 'tentativelyAccept': handler = self.handle_post_tentativelyAccept elif method: raise HTTPBadRequest("Unsupported event segment '%s'" % method) else: raise HTTPBadRequest("Unsupported in event") server, store, userid = req.context.server_store folder = _folder(store, folderid or 'calendar') item = self.get_event(folder, itemid) fields = req.context.json_data handler(req, resp, fields=fields, item=item) # PATCH def _update_event(self, req, resp, folderid, itemid): """Update an event. Args: req (Request): Falcon request object. resp (Response): Falcon response object. folderid (str): folder ID which the event exists in. itemid (str): item/event ID which should be updated. """ fields = req.context.json_data store = req.context.server_store[1] folder = _folder(store, folderid) item = self.get_event(folder, itemid) for field, value in fields.items(): if field in self.set_fields: self.set_fields[field](item, value) self.respond(req, resp, item, self.fields) def on_patch_by_eventid(self, req, resp, itemid): """Handle PATCH request for a specific event in 'calendar' folder. Args: req (Request): Falcon request object. resp (Response): Falcon response object. itemid (str): item/event ID which should be updated. """ self._update_event(req, resp, "calendar", itemid) def on_patch_by_folderid_eventid(self, req, resp, folderid, itemid): """Handle PATCH request for a specific event in a specific folder. Args: req (Request): Falcon request object. resp (Response): Falcon response object. folderid (str): folder ID which the event exists in. itemid (str): item/event ID which should be updated. """ self._update_event(req, resp, folderid, itemid) # DELETE def _delete_event(self, req, resp, folderid, itemid): """Delete an event. Args: req (Request): Falcon request object. resp (Response): Falcon response object. folderid (str): folder ID which the event exists in. itemid (str): item/event ID which should be deleted. """ server, store, userid = req.context.server_store folder = _folder(store, folderid) event = self.get_event(folder, itemid) # If meeting is organised, sent cancellation if self.fields['isOrganizer'](event): event.cancel() event.send() folder.delete(event) self.respond_204(resp) def on_delete_by_eventid(self, req, resp, itemid): """Handle DELETE request for a specific event in 'calendar' folder. Args: req (Request): Falcon request object. resp (Response): Falcon response object. itemid (str): item/event ID which should be deleted. """ self._delete_event(req, resp, "calendar", itemid) def on_delete_by_folderid_eventid(self, req, resp, folderid, itemid): """Handle DELETE request for a specific event in a specific folder. Args: req (Request): Falcon request object. resp (Response): Falcon response object. folderid (str): folder ID which the event exists in. itemid (str): item/event ID which should be deleted. """ self._delete_event(req, resp, folderid, itemid)
class EventResource(ItemResource): fields = ItemResource.fields.copy() fields.update({ 'id': lambda item: item.eventid, 'subject': lambda item: item.subject, 'recurrence': recurrence_json, 'start': lambda item: { 'dateTime': _date(item.start, True), 'timeZone': 'UTC' } if item.start else None, 'end': lambda item: { 'dateTime': _date(item.end, True), 'timeZone': 'UTC' } if item.end else None, 'location': lambda item: { 'displayName': item.location, 'address': {} }, # TODO 'importance': lambda item: item.urgency, 'sensitivity': lambda item: sensitivity_map[item.sensitivity], 'hasAttachments': lambda item: item.has_attachments, 'body': lambda req, item: get_body(req, item), 'isReminderOn': lambda item: item.reminder, 'reminderMinutesBeforeStart': lambda item: item.reminder_minutes, 'attendees': lambda item: attendees_json(item), 'bodyPreview': lambda item: item.text[:255], 'isAllDay': lambda item: item.all_day, 'showAs': lambda item: show_as_map[item.show_as], 'seriesMasterId': lambda item: item.item.eventid if isinstance(item, kopano.Occurrence) else None, 'type': lambda item: event_type(item), 'responseRequested': lambda item: item.response_requested, 'iCalUId': lambda item: kopano.hex(kopano.bdec(item.icaluid)) if item.icaluid else None, # graph uses hex!? 'organizer': lambda item: get_email(item.from_), 'isOrganizer': lambda item: item.from_.email == item.sender.email, }) set_fields = { 'subject': lambda item, arg: setattr(item, 'subject', arg), 'start': lambda item, arg: setdate(item, 'start', arg), 'end': lambda item, arg: setdate(item, 'end', arg), } # TODO delta functionality seems to include expanding recurrences!? check with MSGE def on_get(self, req, resp, userid=None, folderid=None, itemid=None, method=None): server, store = _server_store(req, userid) folder = utils._folder(store, folderid or 'calendar') event = folder.event(itemid) if method == 'attachments': attachments = list(event.attachments(embedded=True)) data = (attachments, TOP, 0, len(attachments)) self.respond(req, resp, data, AttachmentResource.fields) elif method == 'instances': start, end = _start_end(req) data = (event.occurrences(start, end), TOP, 0, 0) self.respond(req, resp, data) else: self.respond(req, resp, event) def on_post(self, req, resp, userid=None, folderid=None, itemid=None, method=None): server, store = _server_store(req, userid) folder = utils._folder(store, folderid or 'calendar') item = folder.event(itemid) if method == 'attachments': fields = json.loads(req.stream.read().decode('utf-8')) if fields['@odata.type'] == '#microsoft.graph.fileAttachment': item.create_attachment( fields['name'], base64.urlsafe_b64decode(fields['contentBytes'])) def on_patch(self, req, resp, userid=None, folderid=None, itemid=None, method=None): server, store = _server_store(req, userid) folder = utils._folder(store, folderid or 'calendar') item = folder.event(itemid) fields = json.loads(req.stream.read().decode('utf-8')) for field, value in fields.items(): if field in self.set_fields: self.set_fields[field](item, value) self.respond(req, resp, item, EventResource.fields) def on_delete(self, req, resp, userid=None, folderid=None, itemid=None): server, store = _server_store(req, userid) folder = utils._folder(store, folderid or 'calendar') event = folder.event(itemid) folder.delete(event)