def date_search(self, start, end=None, compfilter="VEVENT", expand="maybe"): # type (TimeStamp, TimeStamp, str, str) -> CalendarObjectResource """ Search events by date in the calendar. Recurring events are expanded if they are occuring during the specified time frame and if an end timestamp is given. Parameters: * start = datetime.today(). * end = same as above. * compfilter = defaults to events only. Set to None to fetch all calendar components. * expand - should recurrent events be expanded? (to preserve backward-compatibility the default "maybe" will be changed into True unless the date_search is open-ended) Returns: * [CalendarObjectResource(), ...] """ matches = [] # build the request ## for backward compatibility - expand should be false ## in an open-ended date search, otherwise true if expand == 'maybe': expand = end # Some servers will raise an error if we send the expand flag # but don't set any end-date - expand doesn't make much sense # if we have one recurring event describing an indefinite # series of events. I think it's appropriate to raise an error # in this case. if not end and expand: raise error.ReportError( "an open-ended date search cannot be expanded") elif expand: data = cdav.CalendarData() + cdav.Expand(start, end) else: data = cdav.CalendarData() prop = dav.Prop() + data query = cdav.TimeRange(start, end) if compfilter: query = cdav.CompFilter(compfilter) + query vcalendar = cdav.CompFilter("VCALENDAR") + query filter = cdav.Filter() + vcalendar root = cdav.CalendarQuery() + [prop, filter] response = self._query(root, 1, 'report') results = self._handle_prop_response(response=response, props=[cdav.CalendarData()]) for r in results: matches.append( Event(self.client, url=self.url.join(r), data=results[r][cdav.CalendarData.tag], parent=self)) return matches
def todos(self, sort_keys=('due', 'priority'), include_completed=False, sort_key=None): """ fetches a list of todo events. Parameters: * sort_keys: use this field in the VTODO for sorting (iterable of lower case string, i.e. ('priority','due')). * include_completed: boolean - by default, only pending tasks are listed * sort_key: DEPRECATED, for backwards compatibility with version 0.4. """ # ref https://www.ietf.org/rfc/rfc4791.txt, section 7.8.9 matches = [] # build the request data = cdav.CalendarData() prop = dav.Prop() + data if sort_key: sort_keys = (sort_key, ) if not include_completed: vnotcompleted = cdav.TextMatch('COMPLETED', negate=True) vnotcancelled = cdav.TextMatch('CANCELLED', negate=True) vstatusNotCompleted = cdav.PropFilter( 'STATUS') + vnotcompleted + cdav.NotDefined() vstatusNotCancelled = cdav.PropFilter( 'STATUS') + vnotcancelled + cdav.NotDefined() vnocompletedate = cdav.PropFilter('COMPLETED') + cdav.NotDefined() vtodo = (cdav.CompFilter("VTODO") + vnocompletedate + vstatusNotCompleted + vstatusNotCancelled) else: vtodo = cdav.CompFilter("VTODO") vcalendar = cdav.CompFilter("VCALENDAR") + vtodo filter = cdav.Filter() + vcalendar root = cdav.CalendarQuery() + [prop, filter] response = self._query(root, 1, 'report') results = self._handle_prop_response(response=response, props=[cdav.CalendarData()]) for r in results: matches.append( Todo(self.client, url=self.url.join(r), data=results[r][cdav.CalendarData.tag], parent=self)) def sort_key_func(x): ret = [] vtodo = x.instance.vtodo defaults = { 'due': '2050-01-01', 'dtstart': '1970-01-01', 'priority': '0', # JA: why compare datetime.strftime('%F%H%M%S') # JA: and not simply datetime? 'isnt_overdue': not (hasattr(vtodo, 'due') and vtodo.due.value.strftime('%F%H%M%S') < datetime.datetime.now().strftime('%F%H%M%S')), 'hasnt_started': (hasattr(vtodo, 'dtstart') and vtodo.dtstart.value.strftime('%F%H%M%S') > datetime.datetime.now().strftime('%F%H%M%S')) } for sort_key in sort_keys: val = getattr(vtodo, sort_key, None) if val is None: ret.append(defaults.get(sort_key, '0')) continue val = val.value if hasattr(val, 'strftime'): ret.append(val.strftime('%F%H%M%S')) else: ret.append(val) return ret if sort_keys: matches.sort(key=sort_key_func) return matches
def test_xml_parsing(self): """ DAVResponse has quite some code to parse the XML received from the server. This test contains real XML received from various caldav servers, and the expected result from the parse methods. """ xml = """ <multistatus xmlns="DAV:"> <response xmlns="DAV:"> <href>/</href> <propstat> <prop> <current-user-principal xmlns="DAV:"> <href xmlns="DAV:">/17149682/principal/</href> </current-user-principal> </prop> <status>HTTP/1.1 200 OK</status> </propstat> </response> </multistatus> """ expected_result = { '/': { '{DAV:}current-user-principal': '/17149682/principal/' } } assert_equal( MockedDAVResponse(xml).expand_simple_props( props=[dav.CurrentUserPrincipal()]), expected_result) xml = """ <multistatus xmlns="DAV:"> <response xmlns="DAV:"> <href>/17149682/principal/</href> <propstat> <prop> <calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav"> <href xmlns="DAV:">https://p62-caldav.icloud.com:443/17149682/calendars/</href> </calendar-home-set> </prop> <status>HTTP/1.1 200 OK</status> </propstat> </response> </multistatus>""" expected_result = { '/17149682/principal/': { '{urn:ietf:params:xml:ns:caldav}calendar-home-set': 'https://p62-caldav.icloud.com:443/17149682/calendars/' } } assert_equal( MockedDAVResponse(xml).expand_simple_props( props=[cdav.CalendarHomeSet()]), expected_result) xml = """ <multistatus xmlns="DAV:"> <response xmlns="DAV:"> <href>/</href> <propstat> <prop> <current-user-principal xmlns="DAV:"> <href xmlns="DAV:">/17149682/principal/</href> </current-user-principal> </prop> <status>HTTP/1.1 200 OK</status> </propstat> </response> </multistatus>""" expected_result = { '/': { '{DAV:}current-user-principal': '/17149682/principal/' } } assert_equal( MockedDAVResponse(xml).expand_simple_props( props=[dav.CurrentUserPrincipal()]), expected_result) xml = """ <multistatus xmlns="DAV:"> <response> <href>/17149682/calendars/testcalendar-84439d0b-ce46-4416-b978-7b4009122c64/</href> <propstat> <prop> </prop> <status>HTTP/1.1 200 OK</status> </propstat> <propstat> <prop> <calendar-data xmlns="urn:ietf:params:xml:ns:caldav"/> </prop> <status>HTTP/1.1 404 Not Found</status> </propstat> </response> <response> <href>/17149682/calendars/testcalendar-84439d0b-ce46-4416-b978-7b4009122c64/20010712T182145Z-123401%40example.com.ics</href> <propstat> <prop> <calendar-data xmlns="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Example Corp.//CalDAV Client//EN BEGIN:VEVENT UID:[email protected] DTSTAMP:20060712T182145Z DTSTART:20060714T170000Z DTEND:20060715T040000Z SUMMARY:Bastille Day Party END:VEVENT END:VCALENDAR </calendar-data> </prop> <status>HTTP/1.1 200 OK</status> </propstat> </response> </multistatus> """ expected_result = { '/17149682/calendars/testcalendar-84439d0b-ce46-4416-b978-7b4009122c64/': { '{urn:ietf:params:xml:ns:caldav}calendar-data': None }, '/17149682/calendars/testcalendar-84439d0b-ce46-4416-b978-7b4009122c64/[email protected]': { '{urn:ietf:params:xml:ns:caldav}calendar-data': 'BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//Example Corp.//CalDAV Client//EN\nBEGIN:VEVENT\nUID:[email protected]\nDTSTAMP:20060712T182145Z\nDTSTART:20060714T170000Z\nDTEND:20060715T040000Z\nSUMMARY:Bastille Day Party\nEND:VEVENT\nEND:VCALENDAR\n' } } assert_equal( MockedDAVResponse(xml).expand_simple_props( props=[cdav.CalendarData()]), expected_result) xml = """ <multistatus xmlns="DAV:"> <response xmlns="DAV:"> <href>/17149682/calendars/</href> <propstat> <prop> <resourcetype xmlns="DAV:"> <collection/> </resourcetype> <displayname xmlns="DAV:">Ny Test</displayname> </prop> <status>HTTP/1.1 200 OK</status> </propstat> </response> <response xmlns="DAV:"> <href>/17149682/calendars/06888b87-397f-11eb-943b-3af9d3928d42/</href> <propstat> <prop> <resourcetype xmlns="DAV:"> <collection/> <calendar xmlns="urn:ietf:params:xml:ns:caldav"/> </resourcetype> <displayname xmlns="DAV:">calfoo3</displayname> </prop> <status>HTTP/1.1 200 OK</status> </propstat> </response> <response xmlns="DAV:"> <href>/17149682/calendars/inbox/</href> <propstat> <prop> <resourcetype xmlns="DAV:"> <collection/> <schedule-inbox xmlns="urn:ietf:params:xml:ns:caldav"/> </resourcetype> </prop> <status>HTTP/1.1 200 OK</status> </propstat> <propstat> <prop> <displayname xmlns="DAV:"/> </prop> <status>HTTP/1.1 404 Not Found</status> </propstat> </response> <response xmlns="DAV:"> <href>/17149682/calendars/testcalendar-e2910e0a-feab-4b51-b3a8-55828acaa912/</href> <propstat> <prop> <resourcetype xmlns="DAV:"> <collection/> <calendar xmlns="urn:ietf:params:xml:ns:caldav"/> </resourcetype> <displayname xmlns="DAV:">Yep</displayname> </prop> <status>HTTP/1.1 200 OK</status> </propstat> </response> </multistatus> """ expected_result = { '/17149682/calendars/': { '{DAV:}resourcetype': ['{DAV:}collection'], '{DAV:}displayname': 'Ny Test' }, '/17149682/calendars/06888b87-397f-11eb-943b-3af9d3928d42/': { '{DAV:}resourcetype': [ '{DAV:}collection', '{urn:ietf:params:xml:ns:caldav}calendar' ], '{DAV:}displayname': 'calfoo3' }, '/17149682/calendars/inbox/': { '{DAV:}resourcetype': [ '{DAV:}collection', '{urn:ietf:params:xml:ns:caldav}schedule-inbox' ], '{DAV:}displayname': None }, '/17149682/calendars/testcalendar-e2910e0a-feab-4b51-b3a8-55828acaa912/': { '{DAV:}resourcetype': [ '{DAV:}collection', '{urn:ietf:params:xml:ns:caldav}calendar' ], '{DAV:}displayname': 'Yep' } } assert_equal( MockedDAVResponse(xml).expand_simple_props( props=[dav.DisplayName()], multi_value_props=[dav.ResourceType()]), expected_result) xml = """ <multistatus xmlns="DAV:"> <response xmlns="DAV:"> <href>/17149682/calendars/testcalendar-f96b3bf0-09e1-4f3d-b891-3a25c99a2894/</href> <propstat> <prop> <getetag xmlns="DAV:">"kkkgopik"</getetag> </prop> <status>HTTP/1.1 200 OK</status> </propstat> </response> <response xmlns="DAV:"> <href>/17149682/calendars/testcalendar-f96b3bf0-09e1-4f3d-b891-3a25c99a2894/1761bf8c-6363-11eb-8fe4-74e5f9bfd8c1.ics</href> <propstat> <prop> <getetag xmlns="DAV:">"kkkgorwx"</getetag> </prop> <status>HTTP/1.1 200 OK</status> </propstat> </response> <response xmlns="DAV:"> <href>/17149682/calendars/testcalendar-f96b3bf0-09e1-4f3d-b891-3a25c99a2894/20010712T182145Z-123401%40example.com.ics</href> <propstat> <prop> <getetag xmlns="DAV:">"kkkgoqqu"</getetag> </prop> <status>HTTP/1.1 200 OK</status> </propstat> </response> <sync-token>HwoQEgwAAAh4yw8ntwAAAAAYAhgAIhUIopml463FieB4EKq9+NSn04DrkQEoAA==</sync-token> </multistatus> """ expected_results = { '/17149682/calendars/testcalendar-f96b3bf0-09e1-4f3d-b891-3a25c99a2894/': { '{DAV:}getetag': '"kkkgopik"', '{urn:ietf:params:xml:ns:caldav}calendar-data': None }, '/17149682/calendars/testcalendar-f96b3bf0-09e1-4f3d-b891-3a25c99a2894/1761bf8c-6363-11eb-8fe4-74e5f9bfd8c1.ics': { '{DAV:}getetag': '"kkkgorwx"', '{urn:ietf:params:xml:ns:caldav}calendar-data': None }, '/17149682/calendars/testcalendar-f96b3bf0-09e1-4f3d-b891-3a25c99a2894/[email protected]': { '{DAV:}getetag': '"kkkgoqqu"', '{urn:ietf:params:xml:ns:caldav}calendar-data': None } }