From 2d041fefbd250433625617372135015999c7d7e5 Mon Sep 17 00:00:00 2001 From: Chen Houwu Date: Sun, 18 Aug 2013 18:07:55 +0800 Subject: [PATCH 1/3] fix: minor version should has no leading '0' --- EWSWrapper.py | 196 +++++++++++++++++++++++++------------------------- 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/EWSWrapper.py b/EWSWrapper.py index 2576858..7ae9d50 100644 --- a/EWSWrapper.py +++ b/EWSWrapper.py @@ -7,14 +7,14 @@ * @website http://ewswrapper.lafiel.net/ * ==================================================== * Desciption - * Provides API wrapper for easy usage of Microsoft + * Provides API wrapper for easy usage of Microsoft * Exchange Web Services. EWSWrapper utilzes some * code written by Erik Cederstrand * - Calendar events: add, update, delete, list, synch * - Taks : add, update, delete, list * - Messages : no support as of yet * - Folders : list, synch - * + * * ==================================================*/ ''' from suds import WebFault @@ -66,7 +66,7 @@ def __init__(self, host, username, password, datadir='wsdl', protocol='https', \ if self.basepath.startswith('/'): tmp_basepath = self.basepath[1:] else: - tmp_basepath = self.basepath + tmp_basepath = self.basepath localwsdl = 'file:///%s/services.wsdl' % (tmp_basepath) #cache path cachepath = basepath.replace('\\', '/') + '/suds_cache' @@ -75,7 +75,7 @@ def __init__(self, host, username, password, datadir='wsdl', protocol='https', \ #timezone settings if timeArr: self.BaseOffset = timeArr[BaseOffset] if BaseOffset in timeArr else self.BaseOffset - self.Offset = timeArr[Offset] if Offset in timeArr else self.Offset + self.Offset = timeArr[Offset] if Offset in timeArr else self.Offset self.DaylightTime = timeArr[DaylightTime] if DaylightTime in timeArr else self.DaylightTime self.StandardOffset = timeArr[StandardOffset] if StandardOffset in timeArr else self.StandardOffset self.StandardTime = timeArr[StandardTime] if StandardTime in timeArr else self.StandardTime @@ -89,13 +89,13 @@ def __init__(self, host, username, password, datadir='wsdl', protocol='https', \ auth = HttpAuthenticated(username=username, password=password) else: raise Exception('Auth type not supported by Client(): %s' % authtype) - + #import jsonpickle #a = jsonpickle.encode(self) #h = open('/tmp/ews_ser.txt', 'w') #h.write(a) #h.close() - + if debug: #logging.basicConfig(level=logging.DEBUG, filename='/var/log/ews_debug.log', # format='%(asctime)s %(levelname)s: %(message)s', @@ -120,9 +120,9 @@ def __init__(self, exchange): # Major versions below 8 don't support EWS, so we would never be # able to talk to such a server. - # 'shortname' is what's used in SOAP request headers when talking + # 'shortname' is what's used in SOAP request headers when talking # to the server. It comes from types.xsd. 'realshortname' is the - # official name corresponding to the version numbers supplied + # official name corresponding to the version numbers supplied # in SOAP response headers. For some unknown reason, they may # differ. versiondict = {\ @@ -133,9 +133,9 @@ def __init__(self, exchange): '3': ('Exchange2007_SP3','Microsoft Exchange Server 2007 SP3') \ },\ '14': {\ - '00': ('Exchange2010','Microsoft Exchange Server 2010'),\ - '01': ('Exchange2010_SP1','Microsoft Exchange Server 2010 SP1'),\ - '02': ('Exchange2010_SP2','Microsoft Exchange Server 2010 SP2'),\ + '0': ('Exchange2010','Microsoft Exchange Server 2010'),\ + '1': ('Exchange2010_SP1','Microsoft Exchange Server 2010 SP1'),\ + '2': ('Exchange2010_SP2','Microsoft Exchange Server 2010 SP2'),\ '16': ('Exchange2010_SP2','Microsoft Exchange Server 2010 SP2') \ }\ } @@ -153,11 +153,11 @@ def __init__(self, exchange): def get(self): - '''Tries to get ask the server which version it has. We haven't + '''Tries to get ask the server which version it has. We haven't set up the Calendar object yet, so just generate an empty request. - We only need a response header containing a ServerVersionInfo - element. Apparently, Exchange has no problem supplying one version - in its types.xsd and reporting another in its SOAP headers. Use the + We only need a response header containing a ServerVersionInfo + element. Apparently, Exchange has no problem supplying one version + in its types.xsd and reporting another in its SOAP headers. Use the types.xsd version in SOAP requests.''' # The URL of the EWS service @@ -186,7 +186,7 @@ def get(self): #a = jsonpickle.encode(response) #h = open('/tmp/ews_ser.txt', 'w') #h.write(a) - #h.close() + #h.close() soapns = 'http://schemas.xmlsoap.org/soap/envelope/' tns = 'http://schemas.microsoft.com/exchange/services/2006/types' header = ElementTree.fromstring(response).find('{%s}Header' % soapns) @@ -202,7 +202,7 @@ def get(self): def __str__(self): return '%s.%s.%s.%s (%s)' % (self.majorversion, self.minorversion, \ - self.majorbuildnumber, self.minorbuildnumber, self.name) + self.majorbuildnumber, self.minorbuildnumber, self.name) class Transport: '''Autoconfigures and stores information on the auth type of the server.''' @@ -312,7 +312,7 @@ def gettype(self): # All auth methods give a 401 error, so the credentials must be # wrong raise urllib2.HTTPError(url, '401', 'Unauthorized', None, fp) - + def getTZ(self): # The URL of the EWS service url = '%s/Exchange.asmx' % self.exchange.urlprefix @@ -328,7 +328,7 @@ def getTZ(self): handle1=open('timezones.xml','w+') handle1.write(response) handle1.close() - + def wrap(self, xml, shortname=None): '''Generate the necessary boilerplate XML for a raw SOAP request. The XML is specific to the server version.''' @@ -348,14 +348,14 @@ class Credentials: def __init__(self, username, password): self.username = username - self.password = password + self.password = password class Wrapper: '''EWSWrapper functions''' hasMoreItems = False synchState = None - + def __init__(self, exchange): # Enums @@ -410,7 +410,7 @@ def _mapi_reference(self, property_id, property_type, set_id): path.set('DistinguishedPropertySetId', set_id) path.set('PropertyId', property_id) path.set('PropertyType', property_type) - return path + return path def listCalendarEvent(self, id=None, start=None, end=None, on_behalf=None, shape='DEFAULT_PROPERTIES', categories=None): '''====================================== @@ -420,7 +420,7 @@ def listCalendarEvent(self, id=None, start=None, end=None, on_behalf=None, shape * @param string $onbehalf - "on behalf" seneder's email * @param int $start - event start timestamp * @param int $end - event end time - * + * * @return object response */ ''' @@ -435,7 +435,7 @@ def synchCalendarEvent(self, on_behalf=None, num_to_synch=10, synch_state=None, * @param int num_to_synch - how many items to synch in one go (max 500) * @param string synch_state - current synch state, blank on initial synch * @param string $shape - detail level (enumarted in DefaultShapeNamesType) - * + * * @return object response */ ''' @@ -450,7 +450,7 @@ def addCalendarEvent(self, subject, body, start, end, attendees, on_behalf=None, '''===================================== // Add Calendar Event //====================================== - /* @param string $subject - event subject + /* @param string $subject - event subject * @param int $start - event start timestamp * @param int $end - event end time * @param array $anttendees - array of email addresses of invited poeople @@ -460,7 +460,7 @@ def addCalendarEvent(self, subject, body, start, end, attendees, on_behalf=None, * @param bool $allday - is it an all-day event? * @param string $bodyType - body format (Text/HTML) * @param string $category - event actegory - * + * * @return object response * ''' @@ -509,7 +509,7 @@ def addCalendarEvent(self, subject, body, start, end, attendees, on_behalf=None, cats = None if category: cats = Element('t:Categories') - if not isinstance(category, list): + if not isinstance(category, list): cats.append(Element('t:String').setText(category)) else: for cat in category: @@ -520,15 +520,15 @@ def addCalendarEvent(self, subject, body, start, end, attendees, on_behalf=None, alldayevent = Element('t:IsAllDayEvent') calitem.append(Subject.setText(subject)) calitem.append(Body.setText(body)) - calitem.append(cats) + calitem.append(cats) calitem.append(Start.setText(start.ewsformat())) - calitem.append(End.setText(end.ewsformat())) + calitem.append(End.setText(end.ewsformat())) calitem.append(alldayevent.setText(int(allday))) if location: calitem.append(Location.setText(location)) - atts = Element('t:RequiredAttendees') + atts = Element('t:RequiredAttendees') for attendee in attendees: - att = Element('t:Attendee') + att = Element('t:Attendee') mailbox = Element('t:Mailbox') emailaddress = Element('t:EmailAddress').setText(attendee) mailbox.append(emailaddress) @@ -578,7 +578,7 @@ def addCalendarEvent(self, subject, body, start, end, attendees, on_behalf=None, item = msg.Items.CalendarItem.ItemId idlist.extend([item._Id, item._ChangeKey]) - return idlist + return idlist def editCalendarEvent(self, id, ckey, subject=None, body=None, bodytype="TEXT", \ start=None, end=None, location=None, attendees=[], \ @@ -597,7 +597,7 @@ def editCalendarEvent(self, id, ckey, subject=None, body=None, bodytype="TEXT", * @param array $anttendees - array of email addresses of invited poeople * @param bool $allday - is it an all-day event? * @param string $category - event actegory - * + * * @return object response */ ''' @@ -611,7 +611,7 @@ def editCalendarEvent(self, id, ckey, subject=None, body=None, bodytype="TEXT", } updateitem = Element('m:UpdateItem') - updateitem.set('SendMeetingInvitationsOrCancellations', self.types.EWSType_CalendarItemCreateOrDeleteOperationType.SEND_TO_NONE) + updateitem.set('SendMeetingInvitationsOrCancellations', self.types.EWSType_CalendarItemCreateOrDeleteOperationType.SEND_TO_NONE) updateitem.set('MessageDisposition', self.types.EWSType_MessageDispositionType.SAVEONLY) updateitem.set('ConflictResolution', self.types.EWSType_ConflictResolutionType.ALWAYSOVERWRITE) @@ -643,7 +643,7 @@ def editCalendarEvent(self, id, ckey, subject=None, body=None, bodytype="TEXT", itemfield.append(Element('t:CalendarItem')) reqatts = Element('t:RequiredAttendees') for attendee in attendees: - att = Element('t:Attendee') + att = Element('t:Attendee') mailbox = Element('t:Mailbox') emailaddress = Element('t:EmailAddress').setText(attendee) mailbox.append(emailaddress) @@ -659,11 +659,11 @@ def editCalendarEvent(self, id, ckey, subject=None, body=None, bodytype="TEXT", itemfield.children[0].set('FieldURI', 'item:Categories') itemfield.append(Element('t:CalendarItem')) categories = Element('t:Categories') - if not isinstance(category, list): + if not isinstance(category, list): categories.append(Element('t:String').setText(category)) else: for cat in category: - categories.append(Element('t:String').setText(cat)) + categories.append(Element('t:String').setText(cat)) itemfield.children[1].append(categories) updates.append(itemfield) @@ -681,25 +681,25 @@ def editCalendarEvent(self, id, ckey, subject=None, body=None, bodytype="TEXT", #timezone if int(self.exchange.version.majorversion) >= 14: itemfield = Element('t:SetItemField') - itemfield.append(Element('t:FieldURI')) + itemfield.append(Element('t:FieldURI')) itemfield.children[0].set('FieldURI', 'calendar:StartTimeZone') itemfield.append(Element('t:CalendarItem')) startTZ = Element('t:StartTimeZone') startTZ.set('Id', self.exchange.TimeZoneId) itemfield.children[1].append(startTZ) - updates.append(itemfield) + updates.append(itemfield) itemfield = Element('t:SetItemField') - itemfield.append(Element('t:FieldURI')) + itemfield.append(Element('t:FieldURI')) itemfield.children[0].set('FieldURI', 'calendar:EndTimeZone') itemfield.append(Element('t:CalendarItem')) endTZ = Element('t:EndTimeZone') endTZ.set('Id', self.exchange.TimeZoneId) itemfield.children[1].append(endTZ) - updates.append(itemfield) + updates.append(itemfield) else: itemfield = Element('t:SetItemField') - itemfield.append(Element('t:FieldURI')) - itemfield.children[0].set('FieldURI', 'calendar:MeetingTimeZone') + itemfield.append(Element('t:FieldURI')) + itemfield.children[0].set('FieldURI', 'calendar:MeetingTimeZone') timeZone = Element('t:MeetingTimeZone') timeZone.set('TimeZoneName', self.exchange.TimeZoneName) timeZone.append(Element('t:BaseOffset').setText(self.exchange.BaseOffset)) @@ -710,10 +710,10 @@ def editCalendarEvent(self, id, ckey, subject=None, body=None, bodytype="TEXT", daylight = Element('t:Daylight') daylight.append(Element('t:Offset').setText(self.exchange.Offset)) daylight.append(Element('t:Time').setText(self.exchange.DaylightTime)) - timeZone.append(daylight) + timeZone.append(daylight) itemfield.children[1].append(timeZone) updates.append(itemfield) - + itemchange.append(updates) itemchanges.append(itemchange) updateitem.append(itemchanges) @@ -748,7 +748,7 @@ def editCalendarEvent(self, id, ckey, subject=None, body=None, bodytype="TEXT", item = msg.Items.CalendarItem.ItemId idlist.append((item._Id, item._ChangeKey)) - return idlist + return idlist def deleteCalendarEvent(self, ids): @@ -756,9 +756,9 @@ def deleteCalendarEvent(self, ids): // Delete Calendar Event Items //====================================== /* @param array $ids - array of event ids to delete - * + * * @return object response - */ + */ ''' return self.deleteItems(ids) @@ -778,7 +778,7 @@ def addTask(self, subject, on_behalf, due, body=None, reminderdue=None, reminder * @param string $sensitivity - task sensitivity * @param string $bodytype - task body type (TEXT/HTML) * @param string $category - task category - * + * * @return object response */ ''' @@ -816,13 +816,13 @@ def addTask(self, subject, on_behalf, due, body=None, reminderdue=None, reminder if body: Body = Element('t:Body') Body.set('BodyType', getattr(self.types.EWSType_BodyTypeResponseType, bodyType)) - task.append(Body.setText(body)) + task.append(Body.setText(body)) task.append(cats) - task.append(Importance.setText(getattr(self.types.EWSType_ImportanceChoicesType,importance))) + task.append(Importance.setText(getattr(self.types.EWSType_ImportanceChoicesType,importance))) if(reminderdue): task.append(ReminderDueBy.setText(reminderdue.ewsformat())) - task.append(ReminderIsSet.setText(1)) - task.append(ReminderMinutesBeforeStart.setText(reminderStart)) + task.append(ReminderIsSet.setText(1)) + task.append(ReminderMinutesBeforeStart.setText(reminderStart)) task.append(DueDate.setText(due.ewsformat())) @@ -861,12 +861,12 @@ def addTask(self, subject, on_behalf, due, body=None, reminderdue=None, reminder item = msg.Items.Task.ItemId idlist.append((item._Id, item._ChangeKey)) - return idlist + return idlist def editTask(self, id, ckey, subject=None, body=None, bodytype='TEXT', due=None, \ reminderdue=None, reminderStart=None, status=None, percentComplete=None, \ - sensitivity=None, importance=None, category=None): + sensitivity=None, importance=None, category=None): '''====================================== // Edit Task //====================================== @@ -874,7 +874,7 @@ def editTask(self, id, ckey, subject=None, body=None, bodytype='TEXT', due=None, * @param string $ckey - event change key * @param string $subject - event subject * @param string $body - task body - * @param string $bodytype - task body type (TEXT/HTML) + * @param string $bodytype - task body type (TEXT/HTML) * @param int $due - task due date timestamp * @param int $reminderdue - reminder due date timestamp * @param int $reminderStart - realtive negative offset for reminder start in nimutes @@ -883,7 +883,7 @@ def editTask(self, id, ckey, subject=None, body=None, bodytype='TEXT', due=None, * @param string $sensitivity - task sensitivity (enumarted in SensitivityChoicesType) * @param string $importance - task importance (enumarted in ImportanceChoicesType) * @param string $category - task category - * + * * @return object response */ ''' @@ -893,7 +893,7 @@ def editTask(self, id, ckey, subject=None, body=None, bodytype='TEXT', due=None, 'item:ReminderDueBy' : reminderdue.ewsformat() if reminderdue else None, 'item:ReminderMinutesBeforeStart' : reminderStart, 'item:ReminderIsSet' : 1 if (reminderStart or reminderdue) else 0, - 'item:Subject' : subject, + 'item:Subject' : subject, 'task:Status' : getattr(self.types.EWSType_TaskStatusType, status) if status else None, 'item:Sensitivity' : getattr(self.types.EWSType_SensitivityChoicesType, sensitivity) if sensitivity else None, 'item:Importance' : getattr(self.types.EWSType_ImportanceChoicesType, importance) if importance else None, @@ -901,7 +901,7 @@ def editTask(self, id, ckey, subject=None, body=None, bodytype='TEXT', due=None, } updateitem = Element('m:UpdateItem') - updateitem.set('SendMeetingInvitationsOrCancellations', self.types.EWSType_CalendarItemCreateOrDeleteOperationType.SEND_TO_ALL_AND_SAVE_COPY) + updateitem.set('SendMeetingInvitationsOrCancellations', self.types.EWSType_CalendarItemCreateOrDeleteOperationType.SEND_TO_ALL_AND_SAVE_COPY) updateitem.set('MessageDisposition', self.types.EWSType_MessageDispositionType.SAVEONLY) updateitem.set('ConflictResolution', self.types.EWSType_ConflictResolutionType.ALWAYSOVERWRITE) @@ -931,7 +931,7 @@ def editTask(self, id, ckey, subject=None, body=None, bodytype='TEXT', due=None, itemfield.children[0].set('FieldURI', 'item:Categories') itemfield.append(Element('t:Task')) categories = Element('t:Categories') - categories.append(Element('t:String').setText(category)) + categories.append(Element('t:String').setText(category)) itemfield.children[1].append(categories) updates.append(itemfield) @@ -980,7 +980,7 @@ def editTask(self, id, ckey, subject=None, body=None, bodytype='TEXT', due=None, item = msg.Items.Task.ItemId idlist.append((item._Id, item._ChangeKey)) - return idlist + return idlist def deleteTask(self, ids): @@ -988,9 +988,9 @@ def deleteTask(self, ids): // Delete Task Items //====================================== /* @param array $ids - array of taks ids to delete - * + * * @return object response - */ + */ ''' return self.deleteItems(ids) @@ -1003,7 +1003,7 @@ def listTask(self, id=None, start=None, end=None, on_behalf=None, shape='DEFAULT * @param string $onbehalf - "on behalf" seneder's email * @param int $start - event start timestamp * @param int $end - event end time - * + * * @return object response */ ''' @@ -1016,9 +1016,9 @@ def deleteItems(self, ids): // Delete Items //====================================== /* @param array $ids - list of item ids to delete - * + * * @return list of tuples (success[True|False], errormessage) - * + * ''' status = [] @@ -1034,7 +1034,7 @@ def deleteItems(self, ids): itemids = Element('m:ItemIds') itemid = Element('t:ItemId') - # copy.deepcopy() is faster than having Element() inside the loop. + # copy.deepcopy() is faster than having Element() inside the loop. for iid in ids: i = deepcopy(itemid) i.set('Id', iid) @@ -1082,7 +1082,7 @@ def deleteItems(self, ids): def listItems(self, type, id=None, start=None, end=None, on_behalf=None, shape='ID_ONLY', categories=[], additional=[]): '''====================================== // List Items - // Note: currenttly only Taska are + // Note: currenttly only Taska are // searcheble by category //====================================== /* @param string type - item type @@ -1090,7 +1090,7 @@ def listItems(self, type, id=None, start=None, end=None, on_behalf=None, shape=' * @param string $onbehalf - "on behalf": item owner email * @param int $start - search start timestamp * @param int $end - search end timestamp - * + * * @return object response */ ''' @@ -1117,7 +1117,7 @@ def listItems(self, type, id=None, start=None, end=None, on_behalf=None, shape=' for single in id: itemid = Element('t:ItemId') itemid.set('Id', single) - itemids.append(itemid) + itemids.append(itemid) else: itemid = Element('t:ItemId') @@ -1160,7 +1160,7 @@ def listItems(self, type, id=None, start=None, end=None, on_behalf=None, shape=' itemcats = item.Categories[0] if set(categories).issubset(set(itemcats)): items.append(item) - return items + return items else: rspclass = msg._ResponseClass rspcode = msg.ResponseCode @@ -1218,9 +1218,9 @@ def listItems(self, type, id=None, start=None, end=None, on_behalf=None, shape=' if(type == 'CALENDAR'): if start or end: calendarview = Element('m:CalendarView') - if start: + if start: calendarview.set('StartDate', start.ewsformat()) - if end: + if end: calendarview.set('EndDate', end.ewsformat()) finditem.append(calendarview) @@ -1251,7 +1251,7 @@ def listItems(self, type, id=None, start=None, end=None, on_behalf=None, shape=' if rcount > 1: res.append(restriction) - finditem.append(res) + finditem.append(res) else: finditem.append(restriction) @@ -1352,8 +1352,8 @@ def listFolders(self, type, on_behalf, shape='DEFAULT_PROPERTIES', depth='SHALLO /* @param string type - folder type (enumarted in DistinguishedFolderIdNameType) * @param string $onbehalf - "on behalf": item owner email * @param string $shape - detail level (enumarted in DefaultShapeNamesType) - * @param string $depth - list normal /include subfolders (enumarted in FolderQueryTraversalType) - * + * @param string $depth - list normal /include subfolders (enumarted in FolderQueryTraversalType) + * * @return object response */ ''' @@ -1436,7 +1436,7 @@ def getids(self, items, include_change_key=True): if(include_change_key): idlist.append((item._Id, item._ChangeKey)) else: - idlist.append(item._Id) + idlist.append(item._Id) else: try: for item in [i.ItemId for i in [j[1] for j in items]]: @@ -1449,9 +1449,9 @@ def getids(self, items, include_change_key=True): if(include_change_key): idlist.append((item._Id, item._ChangeKey)) else: - idlist.append(item._Id) + idlist.append(item._Id) return idlist - + def synchFolder(self, type, on_behalf=None, num_to_synch=10, synch_state=None, shape='ID_ONLY', additional=[], ignored_items=None): '''====================================== // Synch Folder - synchronizes given folder @@ -1461,8 +1461,8 @@ def synchFolder(self, type, on_behalf=None, num_to_synch=10, synch_state=None, s * @param int num_to_synch - how many items to synch in one go (max 500) * @param string synch_state - current synch state, blank on initial synch * @param string $shape - detail level (enumarted in DefaultShapeNamesType) - * @param string $ignored_items - list of IDs of ignored items (currently not implemented) - * + * @param string $ignored_items - list of IDs of ignored items (currently not implemented) + * * @return object response */ ''' @@ -1472,7 +1472,7 @@ def synchFolder(self, type, on_behalf=None, num_to_synch=10, synch_state=None, s baseshape = Element('t:BaseShape').setText(getattr(self.types.EWSType_DefaultShapeNamesType, shape)) itemshape.append(baseshape) synchfolder.append(itemshape) - + distinguishedfolderid = Element('t:DistinguishedFolderId') distinguishedfolderid.set('Id', getattr(self.types.EWSType_DistinguishedFolderIdNameType, type)) parentfolderids = Element('m:SyncFolderId') @@ -1482,7 +1482,7 @@ def synchFolder(self, type, on_behalf=None, num_to_synch=10, synch_state=None, s distinguishedfolderid.append(mailbox) parentfolderids.append(distinguishedfolderid) synchfolder.append(parentfolderids) - + if synch_state is not None: synchstate = Element('m:SyncState').setText(synch_state) synchfolder.append(synchstate) @@ -1490,12 +1490,12 @@ def synchFolder(self, type, on_behalf=None, num_to_synch=10, synch_state=None, s if ignored_items: #@TODO : add ignored items... pass - + numresults = Element('m:MaxChangesReturned').setText(num_to_synch) synchfolder.append(numresults) - + xml = self.exchange.transport.wrap(synchfolder) - + try: result = self.client.service.SyncFolderItems(__inject={'msg':xml}) except WebFault as e: @@ -1530,7 +1530,7 @@ def synchFolder(self, type, on_behalf=None, num_to_synch=10, synch_state=None, s ids = [] fullitems_add = [] fullitems_update = [] - + #add/update events if hasattr(changes, 'Create'): if isinstance(changes.Create[0], list): @@ -1543,37 +1543,37 @@ def synchFolder(self, type, on_behalf=None, num_to_synch=10, synch_state=None, s fullitems.append(elem[0]) else: for elem in __fullitems: - fullitems.append(elem[1]) + fullitems.append(elem[1]) #get ids from response ids.extend(self.getids(fullitems, False)) fullitems_add = fullitems fullitems = [] __fullitems = [] - + if hasattr(changes, 'Update'): if isinstance(changes.Update[0], list): __fullitems.extend(changes.Update[0]) else: - __fullitems.extend(changes.Update) + __fullitems.extend(changes.Update) #fix attributes if len(__fullitems) > 1: for elem in __fullitems: fullitems.append(elem[0]) else: for elem in __fullitems: - fullitems.append(elem[1]) + fullitems.append(elem[1]) #get ids from response ids.extend(self.getids(fullitems, False)) fullitems_update = fullitems fullitems = [] __fullitems = [] - + #if we have addtional proerties to fetch do so #join fulltimes toghether fullitems = fullitems_add + fullitems_update - - + + #call self with ids and required props if type=="CALENDAR": if ids: @@ -1617,16 +1617,16 @@ def synchFolder(self, type, on_behalf=None, num_to_synch=10, synch_state=None, s fullitems.Importance = extended_imp.get(fullitems.ItemId._Id) if(len(fullitems) < 2): fullitems = [("EventSpoofedType",fullitems.pop())] - + #allitems = type('', (object,), {}) allitems = lambda:0 allitems.addupdate = fullitems if hasattr(changes, 'Delete'): if len(changes.Delete) < 2: allitems.delete = [changes.Delete] - else: + else: allitems.delete = changes.Delete - return allitems + return allitems class EWSDateTime(datetime): @@ -1640,7 +1640,7 @@ def midnightfix(self): return self - timedelta(seconds=1) def __new__(cls, y=None, m=None, d=None, h=None, mi=None, s=None): - if((y>=0) and (all(x is None for x in (m,d,h,mi,s)))): + if((y>=0) and (all(x is None for x in (m,d,h,mi,s)))): t = datetime.fromtimestamp(y) return super(EWSDateTime, cls).__new__(cls,t.year,t.month,t.day,t.hour,t.minute,t.second) else: From f4a62e3b61b28f568ae31d0d420000c8bc96843a Mon Sep 17 00:00:00 2001 From: Chen Houwu Date: Sat, 24 Aug 2013 01:25:41 +0800 Subject: [PATCH 2/3] 1. add ExtendedProperty support to addCalendarEvent 2. enhance query 3. conform to pep8 4. bugfix --- EWSWrapper.py | 1257 ++++++++++++++++++++++++++++--------------------- __init__.py | 1 + 2 files changed, 733 insertions(+), 525 deletions(-) create mode 100644 __init__.py diff --git a/EWSWrapper.py b/EWSWrapper.py index 7ae9d50..1d5fdc2 100644 --- a/EWSWrapper.py +++ b/EWSWrapper.py @@ -33,20 +33,24 @@ from xml.etree import ElementTree import EWSType + class EWSWrapper: + '''Provides an interface for an Exchange server.''' - #Time Zone settings - BaseOffset = "-P0DT1H0M0.0S" - Offset = "-P0DT1H0M0.0S" - DaylightTime = "02:00:00.0000000" - StandardOffset = "P0DT0H0M0.0S" - StandardTime = "03:00:00.0000000" - TimeZoneName = "(GMT+01:00) Warsaw" - TimeZoneId = "Central European Standard Time" - #for pagination purposes (search / sync) - - def __init__(self, host, username, password, datadir='wsdl', protocol='https', \ - authtype=None, debug=False, timeArr=[]): + # Time Zone settings + BaseOffset = "-P0DT1H0M0.0S" + Offset = "-P0DT1H0M0.0S" + DaylightTime = "02:00:00.0000000" + StandardOffset = "P0DT0H0M0.0S" + StandardTime = "03:00:00.0000000" + TimeZoneName = "(GMT+01:00) Warsaw" + TimeZoneId = "Central European Standard Time" + # for pagination purposes (search / sync) + + def __init__(self, + host, username, password, + datadir='wsdl', protocol='https', + authtype=None, debug=False, timeArr={}): self.host = host self.protocol = protocol @@ -61,55 +65,53 @@ def __init__(self, host, username, password, datadir='wsdl', protocol='https', \ # Download and fix up the WSDL #localwsdl = 'file://%s' % self.setup() basepath = '' - basepath = path.normpath(path.dirname( path.realpath( __file__ ))) + basepath = path.normpath(path.dirname(path.realpath(__file__))) self.basepath = basepath.replace('\\', '/') + '/' + datadir if self.basepath.startswith('/'): tmp_basepath = self.basepath[1:] else: tmp_basepath = self.basepath localwsdl = 'file:///%s/services.wsdl' % (tmp_basepath) - #cache path + # cache path cachepath = basepath.replace('\\', '/') + '/suds_cache' - - #timezone settings - if timeArr: - self.BaseOffset = timeArr[BaseOffset] if BaseOffset in timeArr else self.BaseOffset - self.Offset = timeArr[Offset] if Offset in timeArr else self.Offset - self.DaylightTime = timeArr[DaylightTime] if DaylightTime in timeArr else self.DaylightTime - self.StandardOffset = timeArr[StandardOffset] if StandardOffset in timeArr else self.StandardOffset - self.StandardTime = timeArr[StandardTime] if StandardTime in timeArr else self.StandardTime - self.TimeZoneName = timeArr[TimeZoneName] if TimeZoneName in timeArr else self.TimeZoneName - self.TimeZoneId = timeArr[TimeZoneId] if TimeZoneId in timeArr else self.TimeZoneId + # timezone settings + for key, value in timeArr.iteritems(): + setattr(self, key, value) authtype = self.transport.authtype if authtype == self.transport.NTLM: - auth = WindowsHttpAuthenticated(username=username, password=password) + auth = WindowsHttpAuthenticated( + username=username, password=password) elif authtype == self.transport.Basic: auth = HttpAuthenticated(username=username, password=password) else: - raise Exception('Auth type not supported by Client(): %s' % authtype) + raise Exception( + 'Auth type not supported by Client(): %s' % authtype) #import jsonpickle #a = jsonpickle.encode(self) #h = open('/tmp/ews_ser.txt', 'w') - #h.write(a) - #h.close() + # h.write(a) + # h.close() if debug: - #logging.basicConfig(level=logging.DEBUG, filename='/var/log/ews_debug.log', + # logging.basicConfig(level=logging.DEBUG, filename='/var/log/ews_debug.log', # format='%(asctime)s %(levelname)s: %(message)s', # datefmt='%Y-%m-%d %H:%M:%S') logging.getLogger('suds.client').setLevel(logging.DEBUG) the_cache = cache.FileCache(location=cachepath, days=0) - self.client = Client(url=localwsdl, transport=auth, cache=the_cache) + self.client = Client( + url=localwsdl, transport=auth, cache=the_cache) else: the_cache = cache.FileCache(location=cachepath, days=0) - self.client = Client(url=localwsdl, transport=auth, cache=the_cache) + self.client = Client( + url=localwsdl, transport=auth, cache=the_cache) self.version = self.Version(self) self.wrapper = self.Wrapper(self) class Version: + '''Holds information about the server version''' def __init__(self, exchange): @@ -125,33 +127,32 @@ def __init__(self, exchange): # official name corresponding to the version numbers supplied # in SOAP response headers. For some unknown reason, they may # differ. - versiondict = {\ - '8': {\ - '0': ('Exchange2007','Microsoft Exchange Server 2007'),\ - '1': ('Exchange2007_SP1','Microsoft Exchange Server 2007 SP1'),\ - '2': ('Exchange2007_SP2','Microsoft Exchange Server 2007 SP2'), \ - '3': ('Exchange2007_SP3','Microsoft Exchange Server 2007 SP3') \ - },\ - '14': {\ - '0': ('Exchange2010','Microsoft Exchange Server 2010'),\ - '1': ('Exchange2010_SP1','Microsoft Exchange Server 2010 SP1'),\ - '2': ('Exchange2010_SP2','Microsoft Exchange Server 2010 SP2'),\ - '16': ('Exchange2010_SP2','Microsoft Exchange Server 2010 SP2') \ - }\ + versiondict = { + '8': { + '0': ('Exchange2007', 'Microsoft Exchange Server 2007'), + '1': ('Exchange2007_SP1', 'Microsoft Exchange Server 2007 SP1'), + '2': ('Exchange2007_SP2', 'Microsoft Exchange Server 2007 SP2'), + '3': ('Exchange2007_SP3', 'Microsoft Exchange Server 2007 SP3') + }, + '14': { + '0': ('Exchange2010', 'Microsoft Exchange Server 2010'), + '1': ('Exchange2010_SP1', 'Microsoft Exchange Server 2010 SP1'), + '2': ('Exchange2010_SP2', 'Microsoft Exchange Server 2010 SP2'), + '16': ('Exchange2010_SP2', 'Microsoft Exchange Server 2010 SP2') + } } - if versiondict.has_key(self.majorversion) and\ - versiondict[self.majorversion].has_key(self.minorversion): + if self.majorversion in versiondict and\ + self.minorversion in versiondict[self.majorversion]: self.realshortname, self.name = \ versiondict[self.majorversion][self.minorversion] else: raise Exception('Unknown Exchange version. I know the \ following: %s' % versiondict) - self.build = '%s.%s.%s.%s' % (self.majorversion, self.minorversion, \ + self.build = '%s.%s.%s.%s' % (self.majorversion, self.minorversion, self.majorbuildnumber, self.minorbuildnumber) - def get(self): '''Tries to get ask the server which version it has. We haven't set up the Calendar object yet, so just generate an empty request. @@ -170,7 +171,8 @@ def get(self): # Get the server version from types.xsd. A server response provides # the build numbers. types_xsd = '%s/%s' % (self.exchange.basepath, 'types.xsd') - shortname = ElementTree.parse(types_xsd).getroot().attrib['version'] + shortname = ElementTree.parse( + types_xsd).getroot().attrib['version'] # I'm lazy. Just generate enough to get a soap:Header response xml = self.exchange.transport.wrap('IdOnly\ @@ -181,15 +183,16 @@ def get(self): response = trans.geturl(url=url, authtype=authtype, data=xml) except urllib2.HTTPError as e: response = e.read() - #print response + # print response #import jsonpickle #a = jsonpickle.encode(response) #h = open('/tmp/ews_ser.txt', 'w') - #h.write(a) - #h.close() + # h.write(a) + # h.close() soapns = 'http://schemas.xmlsoap.org/soap/envelope/' tns = 'http://schemas.microsoft.com/exchange/services/2006/types' - header = ElementTree.fromstring(response).find('{%s}Header' % soapns) + header = ElementTree.fromstring( + response).find('{%s}Header' % soapns) info = header.find('{%s}ServerVersionInfo' % tns).attrib if not info: raise Exception('No versioninfo in response: %s' % response) @@ -201,9 +204,11 @@ def get(self): return shortname, majorv, minorv, majorb, minorb def __str__(self): - return '%s.%s.%s.%s (%s)' % (self.majorversion, self.minorversion, \ + return '%s.%s.%s.%s (%s)' % (self.majorversion, self.minorversion, self.majorbuildnumber, self.minorbuildnumber, self.name) + class Transport: + '''Autoconfigures and stores information on the auth type of the server.''' @@ -219,26 +224,22 @@ def __init__(self, exchange, authtype=None): else: self.authtype = self.gettype() - def test(self): '''Tests the connection to the server.''' if self.gettype(): return True - def _isxml(self, txt): '''Helper function. Test if response is an XML doc''' if txt[0:5] == '') + xml = self.wrap( + '') response = self.geturl(url=url, authtype=self.authtype, data=xml) - handle1=open('timezones.xml','w+') + handle1 = open('timezones.xml', 'w+') handle1.write(response) handle1.close() @@ -337,25 +333,25 @@ def wrap(self, xml, shortname=None): header = Element('t:RequestServerVersion') header.set('Version', shortname) return ''' - - %s - %s - ''' % (header, xml) - + + %s + %s + + ''' % (header, xml) class Credentials: + '''Keeps login info''' def __init__(self, username, password): self.username = username self.password = password - class Wrapper: - '''EWSWrapper functions''' - hasMoreItems = False - synchState = None + '''EWSWrapper functions''' + hasMoreItems = False + synchState = None def __init__(self, exchange): # Enums @@ -365,54 +361,80 @@ def __init__(self, exchange): self.client = exchange.client self.credentials = exchange.credentials - def _restriction(self, operator, field, value, extended = False): - '''Helper function to build retriction call''' - - if not extended: - uri = Element('t:FieldURI') - uri.set('FieldURI', field) - else: - uri = field - val = Element('t:Constant') - val.set('Value', value) - - if operator == '==': - op = Element('t:IsEqualTo') + def _restriction(self, expression): + if len(expression) == 0: + return None + if len(expression) > 1: + raise Exception('Query expression is invalid') + operator = expression.keys()[0] + if operator == 'and': + restriction = Element('t:And') + for item in expression['and']: + restriction.append(self._restriction(item)) + return restriction + if operator == 'or': + restriction = Element('t:Or') + for item in expression['or']: + restriction.append(self._restriction(item)) + return restriction + if operator == 'not': + restriction = Element('t:Not') + restriction.append(self._restriction(expression['not'])) + return restriction + + operands = expression[operator] + + valEl = Element('t:Constant') + valEl.set('Value', operands.pop('value')) + + uriEl = self._fieldURI(deepcopy(operands)) + + if operator == 'contains': + operatorEl = Element('t:Contains') + operatorEl.set('ContainmentMode', 'Substring') + operatorEl.set('ContainmentComparison', 'IgnoreCase') + operatorEl.append(uriEl) + operatorEl.append(valEl) + return operatorEl + + if operator == '==': + operatorEl = Element('t:IsEqualTo') elif operator == '!=': - op = Element('t:IsNotEqualTo') + operatorEl = Element('t:IsNotEqualTo') elif operator == '>': - op = Element('t:IsGreaterThan') + operatorEl = Element('t:IsGreaterThan') elif operator == '<': - op = Element('t:IsLessThan') + operatorEl = Element('t:IsLessThan') elif operator == '<=': - op = Element('t:IsLessThanOrEqualTo') + operatorEl = Element('t:IsLessThanOrEqualTo') elif operator == '>=': - op = Element('t:IsGreaterThanOrEqualTo') - elif operator == 'contains': - op = Element('t:Contains') - op.set('ContainmentMode', 'Substring') - op.set('ContainmentComparison', 'Exact') - op.append(uri) - op.append(val) - return op + operatorEl = Element('t:IsGreaterThanOrEqualTo') else: raise Exception('Operator not supported') - op.append(uri) - const = Element('t:FieldURIOrConstant') - const.append(val) - op.append(const) - return op - - def _mapi_reference(self, property_id, property_type, set_id): - '''construct a mapi field reference''' - path = Element('t:ExtendedFieldURI') - path.set('DistinguishedPropertySetId', set_id) - path.set('PropertyId', property_id) - path.set('PropertyType', property_type) - return path - - def listCalendarEvent(self, id=None, start=None, end=None, on_behalf=None, shape='DEFAULT_PROPERTIES', categories=None): + operatorEl.append(uriEl) + constEl = Element('t:FieldURIOrConstant') + constEl.append(valEl) + operatorEl.append(constEl) + return operatorEl + + def _fieldURI(self, prop): + ''' + prop will possibly be modifed + ''' + field_type = prop.pop('type', 't:FieldURI') + if 'PropertyType' not in prop and\ + field_type == 't:ExtendedFieldURI': + prop.update(PropertyType='String') + uri = Element(field_type) + for key, value in prop.iteritems(): + uri.set(key, value) + return uri + + def listCalendarEvent(self, id=None, on_behalf=None, # query scope + start=None, end=None, categories=[], criteria=[], # query criteria + shape='ID_ONLY', additional=[] # item fields + ): '''====================================== // List Calendar Events //====================================== @@ -425,7 +447,11 @@ def listCalendarEvent(self, id=None, start=None, end=None, on_behalf=None, shape */ ''' type = 'CALENDAR' - return self.listItems(type, id, start, end, on_behalf, shape, categories) + return self.listItems( + type=type, id=id, on_behalf=on_behalf, # query scope + start=start, end=end, categories=categories, criteria=criteria, # query criteria + shape=shape, additional=additional # item fields + ) def synchCalendarEvent(self, on_behalf=None, num_to_synch=10, synch_state=None, shape='DEFAULT_PROPERTIES'): '''====================================== @@ -444,9 +470,10 @@ def synchCalendarEvent(self, on_behalf=None, num_to_synch=10, synch_state=None, return self.synchFolder(type, on_behalf, num_to_synch, synch_state, shape) - - def addCalendarEvent(self, subject, body, start, end, attendees, on_behalf=None, \ - location=None, allday = False, bodyType="TEXT", category="default"): + def addCalendarEvent(self, subject, body, start, end, attendees, + on_behalf=None, location=None, allday=False, + bodyType="TEXT", category="default", extProperties=[], + ): '''===================================== // Add Calendar Event //====================================== @@ -460,17 +487,20 @@ def addCalendarEvent(self, subject, body, start, end, attendees, on_behalf=None, * @param bool $allday - is it an all-day event? * @param string $bodyType - body format (Text/HTML) * @param string $category - event actegory - * + * @param list $extProperties - additional properties. * @return object response * ''' createitem = Element('m:CreateItem') - createitem.set('SendMeetingInvitations', self.types.EWSType_CalendarItemCreateOrDeleteOperationType.SEND_TO_NONE) + createitem.set( + 'SendMeetingInvitations', + self.types.EWSType_CalendarItemCreateOrDeleteOperationType.SEND_TO_NONE) saveditemfolderid = Element('m:SavedItemFolderId') distinguishedfolderid = Element('t:DistinguishedFolderId') - distinguishedfolderid.set('Id', self.types.EWSType_DistinguishedFolderIdNameType.CALENDAR) + distinguishedfolderid.set( + 'Id', self.types.EWSType_DistinguishedFolderIdNameType.CALENDAR) if on_behalf is not None: mailbox = Element('t:Mailbox') emailaddress = Element('t:EmailAddress').setText(on_behalf) @@ -483,7 +513,7 @@ def addCalendarEvent(self, subject, body, start, end, attendees, on_behalf=None, # Prepare objects for CalendarItem creation calitem = Element('t:CalendarItem') - #if we are running MS EX 2010 or newer use different TZ settings + # if we are running MS EX 2010 or newer use different TZ settings startTZ = None if int(self.exchange.version.majorversion) >= 14: startTZ = Element('t:StartTimeZone') @@ -493,19 +523,25 @@ def addCalendarEvent(self, subject, body, start, end, attendees, on_behalf=None, else: timeZone = Element('t:MeetingTimeZone') timeZone.set('TimeZoneName', self.exchange.TimeZoneName) - timeZone.append(Element('t:BaseOffset').setText(self.exchange.BaseOffset)) + timeZone.append( + Element('t:BaseOffset').setText(self.exchange.BaseOffset)) standard = Element('t:Standard') - standard.append(Element('t:Offset').setText(self.exchange.StandardOffset)) - standard.append(Element('t:Time').setText(self.exchange.StandardTime)) + standard.append( + Element('t:Offset').setText(self.exchange.StandardOffset)) + standard.append( + Element('t:Time').setText(self.exchange.StandardTime)) timeZone.append(standard) daylight = Element('t:Daylight') - daylight.append(Element('t:Offset').setText(self.exchange.Offset)) - daylight.append(Element('t:Time').setText(self.exchange.DaylightTime)) + daylight.append( + Element('t:Offset').setText(self.exchange.Offset)) + daylight.append( + Element('t:Time').setText(self.exchange.DaylightTime)) timeZone.append(daylight) Subject = Element('t:Subject') Body = Element('t:Body') - Body.set('BodyType', getattr(self.types.EWSType_BodyTypeResponseType, bodyType)) + Body.set( + 'BodyType', getattr(self.types.EWSType_BodyTypeResponseType, bodyType)) cats = None if category: cats = Element('t:Categories') @@ -514,13 +550,29 @@ def addCalendarEvent(self, subject, body, start, end, attendees, on_behalf=None, else: for cat in category: cats.append(Element('t:String').setText(cat)) + + extPropertyEls = [] + for extProperty in extProperties: + prop = deepcopy(extProperty) + extPropertyEl = Element('t:ExtendedProperty') + + valueEl = Element('t:Value') + valueEl.setText(prop.pop('value', '')) + + prop.update(type='t:ExtendedFieldURI') + extPropertyEl.append(self._fieldURI(prop)) + + extPropertyEl.append(valueEl) + extPropertyEls.append(extPropertyEl) + Start = Element('t:Start') End = Element('t:End') Location = Element('t:Location') - alldayevent = Element('t:IsAllDayEvent') + alldayevent = Element('t:IsAllDayEvent') calitem.append(Subject.setText(subject)) calitem.append(Body.setText(body)) calitem.append(cats) + calitem.append(extPropertyEls) calitem.append(Start.setText(start.ewsformat())) calitem.append(End.setText(end.ewsformat())) calitem.append(alldayevent.setText(int(allday))) @@ -535,23 +587,22 @@ def addCalendarEvent(self, subject, body, start, end, attendees, on_behalf=None, att.append(mailbox) atts.append(att) calitem.append(atts) - #TZ realted stuff + # TZ realted stuff if startTZ is not None: calitem.append(startTZ) calitem.append(endTZ) else: calitem.append(timeZone) - - #add item to items + # add item to items calitems.append(calitem) - #add item to CreateItems + # add item to CreateItems createitem.append(calitems) - #make the call + # make the call xml = self.exchange.transport.wrap(createitem) try: - result = self.client.service.CreateItem(__inject={'msg':xml}) + result = self.client.service.CreateItem(__inject={'msg': xml}) except WebFault as e: raise e @@ -566,10 +617,10 @@ def addCalendarEvent(self, subject, body, start, end, attendees, on_behalf=None, rspclass = msg._ResponseClass rspcode = msg.ResponseCode if rspclass == 'Error': - raise Exception('Error code: %s message: %s' % \ + raise Exception('Error code: %s message: %s' % (rspcode, msg.MessageText)) if rspclass != 'Success': - raise Exception('Unknown response class: %s code: %s' % \ + raise Exception('Unknown response class: %s code: %s' % (rspclass, rspcode)) if rspcode != 'NoError': @@ -580,40 +631,42 @@ def addCalendarEvent(self, subject, body, start, end, attendees, on_behalf=None, return idlist - def editCalendarEvent(self, id, ckey, subject=None, body=None, bodytype="TEXT", \ - start=None, end=None, location=None, attendees=[], \ - allday=None, category=None): - + def editCalendarEvent(self, id, ckey, subject=None, body=None, bodytype="TEXT", + start=None, end=None, location=None, attendees=[], + allday=None, category=None, extProperties=[]): '''====================================== - // Edit Calendar Event - //====================================== - /* @param string $id - event id - * @param string $ckey - event change key - * @param string $subject - event subject - * @param string $body - event body - * @param int $start - event start timestamp - * @param int $end - event end time - * @param string $location - event location - * @param array $anttendees - array of email addresses of invited poeople - * @param bool $allday - is it an all-day event? - * @param string $category - event actegory - * - * @return object response - */ - ''' + // Edit Calendar Event + //====================================== + /* @param string $id - event id + * @param string $ckey - event change key + * @param string $subject - event subject + * @param string $body - event body + * @param int $start - event start timestamp + * @param int $end - event end time + * @param string $location - event location + * @param array $anttendees - array of email addresses of invited poeople + * @param bool $allday - is it an all-day event? + * @param string $category - event actegory + * + * @return object response + */ + ''' Updates = { - 'calendar:Start' : start.ewsformat() if start else None, - 'calendar:End' : end.ewsformat() if end else None, - 'calendar:Location' : location, - 'calendar:IsAllDayEvent' : allday, - 'item:Subject' : subject, + 'calendar:Start': start.ewsformat() if start else None, + 'calendar:End': end.ewsformat() if end else None, + 'calendar:Location': location, + 'calendar:IsAllDayEvent': allday, + 'item:Subject': subject, } updateitem = Element('m:UpdateItem') - updateitem.set('SendMeetingInvitationsOrCancellations', self.types.EWSType_CalendarItemCreateOrDeleteOperationType.SEND_TO_NONE) - updateitem.set('MessageDisposition', self.types.EWSType_MessageDispositionType.SAVEONLY) - updateitem.set('ConflictResolution', self.types.EWSType_ConflictResolutionType.ALWAYSOVERWRITE) + updateitem.set('SendMeetingInvitationsOrCancellations', + self.types.EWSType_CalendarItemCreateOrDeleteOperationType.SEND_TO_NONE) + updateitem.set( + 'MessageDisposition', self.types.EWSType_MessageDispositionType.SAVEONLY) + updateitem.set( + 'ConflictResolution', self.types.EWSType_ConflictResolutionType.ALWAYSOVERWRITE) itemchanges = Element('m:ItemChanges') itemchange = Element('t:ItemChange') @@ -624,22 +677,23 @@ def editCalendarEvent(self, id, ckey, subject=None, body=None, bodytype="TEXT", updates = Element('t:Updates') - #popoulate update array - for key,value in Updates.items(): + # popoulate update array + for key, value in Updates.items(): if value: prop = key.split(':').pop() itemfield = Element('t:SetItemField') itemfield.append(Element('t:FieldURI')) itemfield.children[0].set('FieldURI', key) itemfield.append(Element('t:CalendarItem')) - itemfield.children[1].append(Element('t:'+prop).setText(value)) + itemfield.children[1].append( + Element('t:' + prop).setText(value)) updates.append(itemfield) - if attendees: itemfield = Element('t:SetItemField') itemfield.append(Element('t:FieldURI')) - itemfield.children[0].set('FieldURI', 'calendar:RequiredAttendees') + itemfield.children[0].set( + 'FieldURI', 'calendar:RequiredAttendees') itemfield.append(Element('t:CalendarItem')) reqatts = Element('t:RequiredAttendees') for attendee in attendees: @@ -652,7 +706,6 @@ def editCalendarEvent(self, id, ckey, subject=None, body=None, bodytype="TEXT", itemfield.children[1].append(reqatts) updates.append(itemfield) - if category: itemfield = Element('t:SetItemField') itemfield.append(Element('t:FieldURI')) @@ -673,12 +726,13 @@ def editCalendarEvent(self, id, ckey, subject=None, body=None, bodytype="TEXT", itemfield.children[0].set('FieldURI', 'item:Body') itemfield.append(Element('t:CalendarItem')) Body = Element('t:Body') - Body.set('BodyType', getattr(self.types.EWSType_BodyTypeResponseType, bodytype)) + Body.set('BodyType', getattr( + self.types.EWSType_BodyTypeResponseType, bodytype)) Body.setText(body) itemfield.children[1].append(Body) updates.append(itemfield) - #timezone + # timezone if int(self.exchange.version.majorversion) >= 14: itemfield = Element('t:SetItemField') itemfield.append(Element('t:FieldURI')) @@ -699,29 +753,56 @@ def editCalendarEvent(self, id, ckey, subject=None, body=None, bodytype="TEXT", else: itemfield = Element('t:SetItemField') itemfield.append(Element('t:FieldURI')) - itemfield.children[0].set('FieldURI', 'calendar:MeetingTimeZone') + itemfield.children[0].set( + 'FieldURI', 'calendar:MeetingTimeZone') timeZone = Element('t:MeetingTimeZone') timeZone.set('TimeZoneName', self.exchange.TimeZoneName) - timeZone.append(Element('t:BaseOffset').setText(self.exchange.BaseOffset)) + timeZone.append( + Element('t:BaseOffset').setText(self.exchange.BaseOffset)) standard = Element('t:Standard') - standard.append(Element('t:Offset').setText(self.exchange.StandardOffset)) - standard.append(Element('t:Time').setText(self.exchange.StandardTime)) + standard.append( + Element('t:Offset').setText(self.exchange.StandardOffset)) + standard.append( + Element('t:Time').setText(self.exchange.StandardTime)) timeZone.append(standard) daylight = Element('t:Daylight') - daylight.append(Element('t:Offset').setText(self.exchange.Offset)) - daylight.append(Element('t:Time').setText(self.exchange.DaylightTime)) + daylight.append( + Element('t:Offset').setText(self.exchange.Offset)) + daylight.append( + Element('t:Time').setText(self.exchange.DaylightTime)) timeZone.append(daylight) itemfield.children[1].append(timeZone) updates.append(itemfield) + for extProperty in extProperties: + prop = deepcopy(extProperty) + prop.update(type='t:ExtendedFieldURI') + + setEl = Element('t:SetItemField') + updates.append(setEl) + + valueEl = Element('t:Value') + valueEl.setText(prop.pop('value', '')) + + uriEl = self._fieldURI(prop) + setEl.append(uriEl) + + itemEl = Element('t:CalendarItem') + setEl.append(itemEl) + + propEl = Element('t:ExtendedProperty') + itemEl.append(propEl) + + propEl.append(uriEl) + propEl.append(valueEl) itemchange.append(updates) itemchanges.append(itemchange) updateitem.append(itemchanges) - #make the call + # make the call xml = self.exchange.transport.wrap(updateitem) try: - result = self.client.service.UpdateItem(__inject={'msg':xml}) + result = self.client.service.UpdateItem(__inject={'msg': xml}) except WebFault as e: raise e @@ -736,10 +817,10 @@ def editCalendarEvent(self, id, ckey, subject=None, body=None, bodytype="TEXT", rspclass = msg._ResponseClass rspcode = msg.ResponseCode if rspclass == 'Error': - raise Exception('Error code: %s message: %s' % \ + raise Exception('Error code: %s message: %s' % (rspcode, msg.MessageText)) if rspclass != 'Success': - raise Exception('Unknown response class: %s code: %s' % \ + raise Exception('Unknown response class: %s code: %s' % (rspclass, rspcode)) if rspcode != 'NoError': @@ -750,7 +831,6 @@ def editCalendarEvent(self, id, ckey, subject=None, body=None, bodytype="TEXT", return idlist - def deleteCalendarEvent(self, ids): '''====================================== // Delete Calendar Event Items @@ -762,9 +842,9 @@ def deleteCalendarEvent(self, ids): ''' return self.deleteItems(ids) - - def addTask(self, subject, on_behalf, due, body=None, reminderdue=None, reminderStart="30",\ - importance="NORMAL", sensitivity="NORMAL", bodyType="TEXT", category="default"): + def addTask(self, subject, on_behalf, due, + body=None, reminderdue=None, reminderStart="30", importance="NORMAL", + sensitivity="NORMAL", bodyType="TEXT", category="default"): '''====================================== // Add Task //====================================== @@ -787,7 +867,8 @@ def addTask(self, subject, on_behalf, due, body=None, reminderdue=None, reminder saveditemfolderid = Element('m:SavedItemFolderId') distinguishedfolderid = Element('t:DistinguishedFolderId') - distinguishedfolderid.set('Id', self.types.EWSType_DistinguishedFolderIdNameType.TASKS) + distinguishedfolderid.set( + 'Id', self.types.EWSType_DistinguishedFolderIdNameType.TASKS) if on_behalf is not None: mailbox = Element('t:Mailbox') emailaddress = Element('t:EmailAddress').setText(on_behalf) @@ -808,33 +889,36 @@ def addTask(self, subject, on_behalf, due, body=None, reminderdue=None, reminder cats.append(Element('t:String').setText(category)) Importance = Element('t:Importance') ReminderDueBy = Element('t:ReminderDueBy') - ReminderMinutesBeforeStart = Element('t:ReminderMinutesBeforeStart') - ReminderIsSet = Element('t:ReminderIsSet') + ReminderMinutesBeforeStart = Element( + 't:ReminderMinutesBeforeStart') + ReminderIsSet = Element('t:ReminderIsSet') DueDate = Element('t:DueDate') task.append(Subject.setText(subject)) - task.append(Sensitivity.setText(getattr(self.types.EWSType_SensitivityChoicesType, sensitivity))) + task.append( + Sensitivity.setText(getattr(self.types.EWSType_SensitivityChoicesType, sensitivity))) if body: Body = Element('t:Body') - Body.set('BodyType', getattr(self.types.EWSType_BodyTypeResponseType, bodyType)) + Body.set('BodyType', getattr( + self.types.EWSType_BodyTypeResponseType, bodyType)) task.append(Body.setText(body)) task.append(cats) - task.append(Importance.setText(getattr(self.types.EWSType_ImportanceChoicesType,importance))) + task.append( + Importance.setText(getattr(self.types.EWSType_ImportanceChoicesType, importance))) if(reminderdue): task.append(ReminderDueBy.setText(reminderdue.ewsformat())) task.append(ReminderIsSet.setText(1)) task.append(ReminderMinutesBeforeStart.setText(reminderStart)) task.append(DueDate.setText(due.ewsformat())) - - #add item to items + # add item to items tasks.append(task) - #add item to CreateItems + # add item to CreateItems createitem.append(tasks) - #make the call + # make the call xml = self.exchange.transport.wrap(createitem) try: - result = self.client.service.CreateItem(__inject={'msg':xml}) + result = self.client.service.CreateItem(__inject={'msg': xml}) except WebFault as e: raise e @@ -849,10 +933,10 @@ def addTask(self, subject, on_behalf, due, body=None, reminderdue=None, reminder rspclass = msg._ResponseClass rspcode = msg.ResponseCode if rspclass == 'Error': - raise Exception('Error code: %s message: %s' % \ + raise Exception('Error code: %s message: %s' % (rspcode, msg.MessageText)) if rspclass != 'Success': - raise Exception('Unknown response class: %s code: %s' % \ + raise Exception('Unknown response class: %s code: %s' % (rspclass, rspcode)) if rspcode != 'NoError': @@ -863,9 +947,8 @@ def addTask(self, subject, on_behalf, due, body=None, reminderdue=None, reminder return idlist - - def editTask(self, id, ckey, subject=None, body=None, bodytype='TEXT', due=None, \ - reminderdue=None, reminderStart=None, status=None, percentComplete=None, \ + def editTask(self, id, ckey, subject=None, body=None, bodytype='TEXT', due=None, + reminderdue=None, reminderStart=None, status=None, percentComplete=None, sensitivity=None, importance=None, category=None): '''====================================== // Edit Task @@ -889,21 +972,24 @@ def editTask(self, id, ckey, subject=None, body=None, bodytype='TEXT', due=None, ''' Updates = { - 'task:DueDate' : due.ewsformat() if due else None, - 'item:ReminderDueBy' : reminderdue.ewsformat() if reminderdue else None, - 'item:ReminderMinutesBeforeStart' : reminderStart, - 'item:ReminderIsSet' : 1 if (reminderStart or reminderdue) else 0, - 'item:Subject' : subject, - 'task:Status' : getattr(self.types.EWSType_TaskStatusType, status) if status else None, - 'item:Sensitivity' : getattr(self.types.EWSType_SensitivityChoicesType, sensitivity) if sensitivity else None, - 'item:Importance' : getattr(self.types.EWSType_ImportanceChoicesType, importance) if importance else None, - 'task:PercentComplete' : percentComplete + 'task:DueDate': due.ewsformat() if due else None, + 'item:ReminderDueBy': reminderdue.ewsformat() if reminderdue else None, + 'item:ReminderMinutesBeforeStart': reminderStart, + 'item:ReminderIsSet': 1 if (reminderStart or reminderdue) else 0, + 'item:Subject': subject, + 'task:Status': getattr(self.types.EWSType_TaskStatusType, status) if status else None, + 'item:Sensitivity': getattr(self.types.EWSType_SensitivityChoicesType, sensitivity) if sensitivity else None, + 'item:Importance': getattr(self.types.EWSType_ImportanceChoicesType, importance) if importance else None, + 'task:PercentComplete': percentComplete } updateitem = Element('m:UpdateItem') - updateitem.set('SendMeetingInvitationsOrCancellations', self.types.EWSType_CalendarItemCreateOrDeleteOperationType.SEND_TO_ALL_AND_SAVE_COPY) - updateitem.set('MessageDisposition', self.types.EWSType_MessageDispositionType.SAVEONLY) - updateitem.set('ConflictResolution', self.types.EWSType_ConflictResolutionType.ALWAYSOVERWRITE) + updateitem.set('SendMeetingInvitationsOrCancellations', + self.types.EWSType_CalendarItemCreateOrDeleteOperationType.SEND_TO_ALL_AND_SAVE_COPY) + updateitem.set( + 'MessageDisposition', self.types.EWSType_MessageDispositionType.SAVEONLY) + updateitem.set( + 'ConflictResolution', self.types.EWSType_ConflictResolutionType.ALWAYSOVERWRITE) itemchanges = Element('m:ItemChanges') itemchange = Element('t:ItemChange') @@ -914,15 +1000,16 @@ def editTask(self, id, ckey, subject=None, body=None, bodytype='TEXT', due=None, updates = Element('t:Updates') - #popoulate update array - for key,value in Updates.items(): + # popoulate update array + for key, value in Updates.items(): if value: prop = key.split(':').pop() itemfield = Element('t:SetItemField') itemfield.append(Element('t:FieldURI')) itemfield.children[0].set('FieldURI', key) itemfield.append(Element('t:Task')) - itemfield.children[1].append(Element('t:'+prop).setText(value)) + itemfield.children[1].append( + Element('t:' + prop).setText(value)) updates.append(itemfield) if category: @@ -941,7 +1028,8 @@ def editTask(self, id, ckey, subject=None, body=None, bodytype='TEXT', due=None, itemfield.children[0].set('FieldURI', 'item:Body') itemfield.append(Element('t:Task')) Body = Element('t:Body') - Body.set('BodyType', getattr(self.types.EWSType_BodyTypeResponseType, bodytype)) + Body.set('BodyType', getattr( + self.types.EWSType_BodyTypeResponseType, bodytype)) Body.setText(body) itemfield.children[1].append(Body) updates.append(itemfield) @@ -950,10 +1038,10 @@ def editTask(self, id, ckey, subject=None, body=None, bodytype='TEXT', due=None, itemchanges.append(itemchange) updateitem.append(itemchanges) - #make the call + # make the call xml = self.exchange.transport.wrap(updateitem) try: - result = self.client.service.UpdateItem(__inject={'msg':xml}) + result = self.client.service.UpdateItem(__inject={'msg': xml}) except WebFault as e: raise e @@ -968,10 +1056,10 @@ def editTask(self, id, ckey, subject=None, body=None, bodytype='TEXT', due=None, rspclass = msg._ResponseClass rspcode = msg.ResponseCode if rspclass == 'Error': - raise Exception('Error code: %s message: %s' % \ + raise Exception('Error code: %s message: %s' % (rspcode, msg.MessageText)) if rspclass != 'Success': - raise Exception('Unknown response class: %s code: %s' % \ + raise Exception('Unknown response class: %s code: %s' % (rspclass, rspcode)) if rspcode != 'NoError': @@ -982,7 +1070,6 @@ def editTask(self, id, ckey, subject=None, body=None, bodytype='TEXT', due=None, return idlist - def deleteTask(self, ids): '''====================================== // Delete Task Items @@ -994,7 +1081,6 @@ def deleteTask(self, ids): ''' return self.deleteItems(ids) - def listTask(self, id=None, start=None, end=None, on_behalf=None, shape='DEFAULT_PROPERTIES', categories=None): '''====================================== // List Calendar Events @@ -1010,7 +1096,6 @@ def listTask(self, id=None, start=None, end=None, on_behalf=None, shape='DEFAULT type = 'TASKS' return self.listItems(type, id, start, end, on_behalf, shape, categories) - def deleteItems(self, ids): '''====================================== // Delete Items @@ -1027,9 +1112,12 @@ def deleteItems(self, ids): return status deleteitem = Element('m:DeleteItem') - deleteitem.set('DeleteType', self.types.EWSType_DisposalType.MOVE_TO_DELETED_ITEMS) - deleteitem.set('SendMeetingCancellations', self.types.EWSType_CalendarItemCreateOrDeleteOperationType.SEND_TO_NONE) - deleteitem.set('AffectedTaskOccurrences', self.types.EWSType_AffectedTaskOccurrencesType.ALL_OCCURRENCES) + deleteitem.set( + 'DeleteType', self.types.EWSType_DisposalType.MOVE_TO_DELETED_ITEMS) + deleteitem.set('SendMeetingCancellations', + self.types.EWSType_CalendarItemCreateOrDeleteOperationType.SEND_TO_NONE) + deleteitem.set( + 'AffectedTaskOccurrences', self.types.EWSType_AffectedTaskOccurrencesType.ALL_OCCURRENCES) itemids = Element('m:ItemIds') itemid = Element('t:ItemId') @@ -1045,7 +1133,7 @@ def deleteItems(self, ids): xml = self.exchange.transport.wrap(deleteitem) try: - result = self.client.service.DeleteItem(__inject={'msg':xml}) + result = self.client.service.DeleteItem(__inject={'msg': xml}) except WebFault as e: raise e @@ -1054,7 +1142,6 @@ def deleteItems(self, ids): msgs = result.DeleteItemResponseMessage else: msgs = [result.DeleteItemResponseMessage] - msglen = len(msgs) if len(msgs) != len(ids): raise Exception('Returned response count doesn\'t match: \ got %s, expected %s' % (len(msgs), len(ids))) @@ -1064,13 +1151,13 @@ def deleteItems(self, ids): if rspclass == 'Error': if rspcode == 'ErrorItemNotFound': # Should not happen, so don't worry about performance - status.append( (False, '%s: %s' % ( msg.MessageText, \ - ids[len(status)] ) ) ) + status.append((False, '%s: %s' % (msg.MessageText, + ids[len(status)]))) continue - raise Exception('Error code: %s message: %s' % \ + raise Exception('Error code: %s message: %s' % (rspcode, msg.MessageText)) if rspclass != 'Success': - raise Exception('Unknown response class: %s code: %s' % \ + raise Exception('Unknown response class: %s code: %s' % (rspclass, rspcode)) if not rspcode == 'NoError': @@ -1079,258 +1166,96 @@ def deleteItems(self, ids): return status - def listItems(self, type, id=None, start=None, end=None, on_behalf=None, shape='ID_ONLY', categories=[], additional=[]): + def getItem(self, id=None, shape='ID_ONLY', categories=[], additional=[]): '''====================================== - // List Items - // Note: currenttly only Taska are - // searcheble by category + // Get Items //====================================== - /* @param string type - item type - * @param string id - item id. takes precendense over timeframe - * @param string $onbehalf - "on behalf": item owner email - * @param int $start - search start timestamp - * @param int $end - search end timestamp + * @param string id - item id or a list of ids * * @return object response */ ''' + getitem = Element('m:GetItem') + itemshape = Element('m:ItemShape') + baseshape = Element('t:BaseShape').setText( + getattr(self.types.EWSType_DefaultShapeNamesType, shape) + ) + itemshape.append(baseshape) - if(id): - getitem = Element('m:GetItem') - itemshape = Element('m:ItemShape') - baseshape = Element('t:BaseShape').setText(getattr(self.types.EWSType_DefaultShapeNamesType, shape)) - itemshape.append(baseshape) - - if additional: - additionalproperties = Element('t:AdditionalProperties') - for URI in additional: - fielduri = Element('t:FieldURI') - fielduri.set('FieldURI', URI) - additionalproperties.append(fielduri) - - itemshape.append(additionalproperties) - - getitem.append(itemshape) + additionalproperties = Element('t:AdditionalProperties') + itemshape.append(additionalproperties) + for prop in additional: + additionalproperties.append(self._fieldURI(deepcopy(prop))) - itemids = Element('m:ItemIds') - if isinstance(id, list): - for single in id: - itemid = Element('t:ItemId') - itemid.set('Id', single) - itemids.append(itemid) + getitem.append(itemshape) - else: + itemids = Element('m:ItemIds') + if isinstance(id, list): + for single in id: itemid = Element('t:ItemId') - itemid.set('Id', id) + itemid.set('Id', single) itemids.append(itemid) - getitem.append(itemids) - xml = self.exchange.transport.wrap(getitem) - try: - result = self.client.service.GetItem(__inject={'msg':xml}) - except WebFault as e: - raise e - msg = result.GetItemResponseMessage - if isinstance(msg, list): - fullitems = [] - for el in msg: - rspclass = el._ResponseClass - rspcode = el.ResponseCode - if rspclass == 'Error': - raise Exception('Error code: %s message: %s' % \ - (rspcode, msg.MessageText)) - if rspclass != 'Success': - raise Exception('Unknown response class: %s code: %s' % \ - (rspclass, rspcode)) - - if rspcode != 'NoError': - raise Exception('Unknown response code: %s' % rspcode) - - fullitems.extend([el.Items[0]]) - - #print fullitems - if not categories: - return fullitems - - # Filter for category. Searching for categories only works with - # 'Or' operator, so we need to ignore items with only some - # but not all categories present. - items = [] - for item in fullitems: - itemcats = item.Categories[0] - if set(categories).issubset(set(itemcats)): - items.append(item) - return items - else: - rspclass = msg._ResponseClass - rspcode = msg.ResponseCode + else: + itemid = Element('t:ItemId') + itemid.set('Id', id) + itemids.append(itemid) + getitem.append(itemids) + xml = self.exchange.transport.wrap(getitem) + + try: + result = self.client.service.GetItem(__inject={'msg': xml}) + except WebFault as e: + raise e + msg = result.GetItemResponseMessage + if isinstance(msg, list): + fullitems = [] + for el in msg: + rspclass = el._ResponseClass + rspcode = el.ResponseCode if rspclass == 'Error': - raise Exception('Error code: %s message: %s' % \ + raise Exception('Error code: %s message: %s' % (rspcode, msg.MessageText)) if rspclass != 'Success': - raise Exception('Unknown response class: %s code: %s' % \ + raise Exception('Unknown response class: %s code: %s' % (rspclass, rspcode)) if rspcode != 'NoError': raise Exception('Unknown response code: %s' % rspcode) - if isinstance(msg.Items[0], list): - fullitems = msg.RootFolder.Items[0] - else: - # Only one item returned - fullitems = [msg.Items].pop() - - if not categories: - return fullitems - - # Filter for category. Searching for categories only works with - # 'Or' operator, so we need to ignore items with only some - # but not all categories present. - items = [] - for item in fullitems: - itemcats = item.Categories[0] - if set(categories).issubset(set(itemcats)): - items.append(item) - return items - - else: - finditem = Element('m:FindItem') - finditem.set('Traversal', self.types.EWSType_FolderQueryTraversalType.SHALLOW) - - itemshape = Element('m:ItemShape') - baseshape = Element('t:BaseShape').setText(getattr(self.types.EWSType_DefaultShapeNamesType, shape)) - itemshape.append(baseshape) - additionalproperties = None - - # additional properties - if categories: - additionalproperties = Element('t:AdditionalProperties') - fielduri = Element('t:FieldURI') - fielduri.set('FieldURI', 'item:Categories') - additionalproperties.append(fielduri) - - #append all additional properties, if any - if additionalproperties: - itemshape.append(additionalproperties) - finditem.append(itemshape) - - #type-sepcific options - if(type == 'CALENDAR'): - if start or end: - calendarview = Element('m:CalendarView') - if start: - calendarview.set('StartDate', start.ewsformat()) - if end: - calendarview.set('EndDate', end.ewsformat()) - finditem.append(calendarview) - - elif(type == 'TASKS'): - if start or end: - res = Element('m:Restriction') - restriction = Element('t:And') - if not start: - start = EWSDateTime(0) - if not end: - end = EWSDateTime(2038, 1, 1, 0, 0, 0) - rcount = len([f for f in [start, end, categories] if f != None]) - if categories: - if not res: - restriction = Element('m:Restriction') - if len(categories) == 1: - restriction.append(self._restriction('contains', 'item:Categories', categories[0])) - else: - ortype = Element('t:Or') - for cat in categories: - ortype.append(self._restriction('contains', 'item:Categories', cat)) - restriction.append(ortype) - extended = self._mapi_reference('33029', 'SystemTime', 'Task') - gteq = self._restriction('>=', extended, start.ewsformat(), True) - restriction.append(gteq) - lseq = self._restriction('<=', extended, end.ewsformat(), True) - restriction.append(lseq) - - if rcount > 1: - res.append(restriction) - finditem.append(res) - else: - finditem.append(restriction) + fullitems.extend([el.Items[0]]) - distinguishedfolderid = Element('t:DistinguishedFolderId') - distinguishedfolderid.set('Id', getattr(self.types.EWSType_DistinguishedFolderIdNameType, type)) - parentfolderids = Element('m:ParentFolderIds') - mailbox = Element('t:Mailbox') - emailaddress = Element('t:EmailAddress').setText(on_behalf) - mailbox.append(emailaddress) - distinguishedfolderid.append(mailbox) - parentfolderids.append(distinguishedfolderid) - finditem.append(parentfolderids) - - xml = self.exchange.transport.wrap(finditem) - - try: - result = self.client.service.FindItem(__inject={'msg':xml}) - except WebFault as e: - raise e - msg = result.FindItemResponseMessage + # print fullitems + if not categories: + return fullitems + # Filter for category. Searching for categories only works with + # 'Or' operator, so we need to ignore items with only some + # but not all categories present. + items = [] + for item in fullitems: + itemcats = item.Categories[0] + if set(categories).issubset(set(itemcats)): + items.append(item) + return items + else: rspclass = msg._ResponseClass rspcode = msg.ResponseCode if rspclass == 'Error': - raise Exception('Error code: %s message: %s' % \ + raise Exception('Error code: %s message: %s' % (rspcode, msg.MessageText)) if rspclass != 'Success': - raise Exception('Unknown response class: %s code: %s' % \ + raise Exception('Unknown response class: %s code: %s' % (rspclass, rspcode)) if rspcode != 'NoError': raise Exception('Unknown response code: %s' % rspcode) - if not msg.RootFolder._IncludesLastItemInRange: - raise Exception('Not all items were transferred') - if msg.RootFolder._TotalItemsInView == 0: - return [] - if isinstance(msg.RootFolder.Items[0], list): + if isinstance(msg.Items[0], list): fullitems = msg.RootFolder.Items[0] else: # Only one item returned - fullitems = msg.RootFolder.Items - - #if we have addtional proerties to fetch do so - - #get ids from response - ids = self.getids(fullitems, False) - #call self with ids and required props - if type=="CALENDAR": - additional.append("calendar:RequiredAttendees") - additional.append("calendar:IsAllDayEvent") - additional.append("calendar:Duration") - additional.append("item:Categories") - additional.append("item:Body") - additional.append("item:Sensitivity") - additional.append("item:Importance") - extended_items = self.listItems(type="CALENDAR", id=ids, additional=additional) - #print extended_items - #print ids - #exit() - extended_add, extended_fday, extended_cats, extended_body = ({} for i in range(4)) - extended_sen, extended_imp = ({} for i in range(2)) - for i in range(0, len(extended_items)): - if hasattr(extended_items[i], 'RequiredAttendees'): - extended_add[extended_items[i].ItemId._Id] = extended_items[i].RequiredAttendees - else: - extended_add[extended_items[i].ItemId._Id] = [] - extended_fday[extended_items[i].ItemId._Id] = extended_items[i].IsAllDayEvent - extended_cats[extended_items[i].ItemId._Id] = extended_items[i].Categories if hasattr(extended_items[i], "Categories") else None - extended_body[extended_items[i].ItemId._Id] = extended_items[i].Body if hasattr(extended_items[i], "Body") else None - extended_sen[extended_items[i].ItemId._Id] = extended_items[i].Sensitivity - extended_imp[extended_items[i].ItemId._Id] = extended_items[i].Importance - for i in range(0,len(fullitems)): - fullitems[i].RequiredAttendees = extended_add.get(fullitems[i].ItemId._Id) - fullitems[i].IsAllDayEvent = extended_fday.get(fullitems[i].ItemId._Id) - fullitems[i].Categories = extended_cats.get(fullitems[i].ItemId._Id) - fullitems[i].Body = extended_body.get(fullitems[i].ItemId._Id) - fullitems[i].Sensitivity = extended_sen.get(fullitems[i].ItemId._Id) - fullitems[i].Importance = extended_imp.get(fullitems[i].ItemId._Id) + fullitems = [msg.Items].pop() if not categories: return fullitems @@ -1345,6 +1270,264 @@ def listItems(self, type, id=None, start=None, end=None, on_behalf=None, shape=' items.append(item) return items + def findItem(self, type, on_behalf=None, # query scope + start=None, end=None, categories=[], criteria=[], # query criteria + shape='ID_ONLY', additional=[] # item fields + ): + '''====================================== + // find Items + // Note: currenttly only Taska are + // searcheble by category + //====================================== + /* @param string type - item type + * @param string $onbehalf - "on behalf": item owner email + * @param int $start - search start timestamp + * @param int $end - search end timestamp + * @param dict $criteria - other criteria, for example + { + 'and': [ + { + '>=': { + 'type': 't:ExtendedFieldURI', + 'DistinguishedPropertySetId': 33029, + 'PropertyId': 'Task', + 'PropertyType': 'SystemTime', + 'value': start.ewsformat() + }, + }, + { + 'or': [ + { + 'contains': { + 'type': 't:FieldURI', + 'FieldURI': 'item:Categories', + 'value': 'work' + } + }, + { + 'contains': { + 'type': 't:FieldURI', + 'FieldURI': 'item:Categories', + 'value': 'life' + } + } + ] + }, + ] + } + * @return object response + */ + ''' + finditem = Element('m:FindItem') + finditem.set( + 'Traversal', self.types.EWSType_FolderQueryTraversalType.SHALLOW) + + itemshape = Element('m:ItemShape') + baseshape = Element('t:BaseShape').setText( + getattr(self.types.EWSType_DefaultShapeNamesType, shape) + ) + itemshape.append(baseshape) + + additionalproperties = None + if categories or len(additional): + # additional properties + additionalproperties = Element('t:AdditionalProperties') + itemshape.append(additionalproperties) + + if categories: + fielduri = Element('t:FieldURI') + fielduri.set('FieldURI', 'item:Categories') + additionalproperties.append(fielduri) + + # append all additional properties, if any + for field in additional: + additionalproperties.append(self._fieldURI(deepcopy(field))) + + finditem.append(itemshape) + + query = { + 'and': [ + ] + } + # type-sepcific options + if(type == 'CALENDAR'): + if start or end: + calendarview = Element('m:CalendarView') + if start: + calendarview.set('StartDate', start.ewsformat()) + if end: + calendarview.set('EndDate', end.ewsformat()) + finditem.append(calendarview) + elif(type == 'TASKS'): + if start: + query['and'].append({ + '>=': { + 'type': 't:ExtendedFieldURI', + 'DistinguishedPropertySetId': 33029, + 'PropertyId': 'Task', + 'PropertyType': 'SystemTime', + 'value': start.ewsformat() + }, + }) + if end: + query['and'].append({ + '<=': { + 'type': 't:ExtendedFieldURI', + 'DistinguishedPropertySetId': 33029, + 'PropertyId': 'Task', + 'PropertyType': 'SystemTime', + 'value': end.ewsformat() + }, + }) + if categories: + catQ = [] + for category in categories: + catQ.append({ + 'contains': { + 'type': 't:FieldURI', + 'FieldURI': 'item:Categories', + 'value': category + }, + }) + query['and'].append({ + 'or': catQ + }) + if criteria: + query['and'].append(criteria) + + if query['and']: + restrictionEl = Element('m:Restriction') + restrictionEl.append(self._restriction(query)) + finditem.append(restrictionEl) + + distinguishedfolderid = Element('t:DistinguishedFolderId') + distinguishedfolderid.set( + 'Id', getattr(self.types.EWSType_DistinguishedFolderIdNameType, type)) + parentfolderids = Element('m:ParentFolderIds') + mailbox = Element('t:Mailbox') + emailaddress = Element('t:EmailAddress').setText(on_behalf) + mailbox.append(emailaddress) + distinguishedfolderid.append(mailbox) + parentfolderids.append(distinguishedfolderid) + finditem.append(parentfolderids) + + xml = self.exchange.transport.wrap(finditem) + + try: + result = self.client.service.FindItem(__inject={'msg': xml}) + except WebFault as e: + raise e + msg = result.FindItemResponseMessage + + rspclass = msg._ResponseClass + rspcode = msg.ResponseCode + if rspclass == 'Error': + raise Exception('Error code: %s message: %s' % + (rspcode, msg.MessageText)) + if rspclass != 'Success': + raise Exception('Unknown response class: %s code: %s' % + (rspclass, rspcode)) + + if rspcode != 'NoError': + raise Exception('Unknown response code: %s' % rspcode) + if not msg.RootFolder._IncludesLastItemInRange: + raise Exception('Not all items were transferred') + if msg.RootFolder._TotalItemsInView == 0: + return [] + + if isinstance(msg.RootFolder.Items[0], list): + fullitems = msg.RootFolder.Items[0] + else: + # Only one item returned + fullitems = msg.RootFolder.Items + + # if we have addtional proerties to fetch do so + + # get ids from response + ids = self.getids(fullitems, False) + # call self with ids and required props + if type == "CALENDAR": + additional.append({'FieldURI': "calendar:RequiredAttendees"}) + additional.append({'FieldURI': "calendar:IsAllDayEvent"}) + additional.append({'FieldURI': "calendar:Duration"}) + additional.append({'FieldURI': "item:Categories"}) + additional.append({'FieldURI': "item:Body"}) + additional.append({'FieldURI': "item:Sensitivity"}) + additional.append({'FieldURI': "item:Importance"}) + extended_items = self.listItems( + type="CALENDAR", id=ids, additional=additional) + extended_add, extended_fday, extended_cats, extended_body = ( + {} for i in range(4)) + extended_sen, extended_imp = ({} for i in range(2)) + for i in range(0, len(extended_items)): + if hasattr(extended_items[i], 'RequiredAttendees'): + extended_add[extended_items[i].ItemId._Id] = extended_items[ + i].RequiredAttendees + else: + extended_add[extended_items[i].ItemId._Id] = [] + extended_fday[extended_items[i].ItemId._Id] = extended_items[ + i].IsAllDayEvent + extended_cats[extended_items[i].ItemId._Id] = extended_items[ + i].Categories if hasattr(extended_items[i], "Categories") else None + extended_body[extended_items[i].ItemId._Id] = extended_items[ + i].Body if hasattr(extended_items[i], "Body") else None + extended_sen[extended_items[i].ItemId._Id] = extended_items[ + i].Sensitivity + extended_imp[ + extended_items[i].ItemId._Id] = extended_items[i].Importance + for i in range(0, len(fullitems)): + fullitems[i].RequiredAttendees = extended_add.get( + fullitems[i].ItemId._Id) + fullitems[i].IsAllDayEvent = extended_fday.get( + fullitems[i].ItemId._Id) + fullitems[i].Categories = extended_cats.get( + fullitems[i].ItemId._Id) + fullitems[i].Body = extended_body.get( + fullitems[i].ItemId._Id) + fullitems[i].Sensitivity = extended_sen.get( + fullitems[i].ItemId._Id) + fullitems[i].Importance = extended_imp.get( + fullitems[i].ItemId._Id) + + if not categories: + return fullitems + + # Filter for category. Searching for categories only works with + # 'Or' operator, so we need to ignore items with only some + # but not all categories present. + items = [] + for item in fullitems: + itemcats = item.Categories[0] + if set(categories).issubset(set(itemcats)): + items.append(item) + return items + + def listItems(self, type, id=None, on_behalf=None, # query scope + start=None, end=None, categories=[], criteria=[], # query criteria + shape='ID_ONLY', additional=[] # item fields + ): + '''====================================== + // List Items + // Note: currenttly only Taska are + // searcheble by category + //====================================== + /* @param string type - item type + * @param string id - item id. takes precendense over timeframe + * @param string $onbehalf - "on behalf": item owner email + * @param int $start - search start timestamp + * @param int $end - search end timestamp + * + * @return object response + */ + ''' + + if(id): + return self.getItem(id, shape, categories, additional) + else: + return self.findItem(type, on_behalf, + start, end, categories, criteria, + shape, additional) + def listFolders(self, type, on_behalf, shape='DEFAULT_PROPERTIES', depth='SHALLOW'): '''====================================== // List Folders @@ -1359,11 +1542,13 @@ def listFolders(self, type, on_behalf, shape='DEFAULT_PROPERTIES', depth='SHALLO ''' # start building the find folder request - request = Element('m:FindFolder') - request.set('Traversal', getattr(self.types.EWSType_FolderQueryTraversalType, depth)) + request = Element('m:FindFolder') + request.set('Traversal', getattr( + self.types.EWSType_FolderQueryTraversalType, depth)) itemshape = Element('m:FolderShape') - baseshape = Element('t:BaseShape').setText(getattr(self.types.EWSType_DefaultShapeNamesType,shape)) + baseshape = Element('t:BaseShape').setText( + getattr(self.types.EWSType_DefaultShapeNamesType, shape)) itemshape.append(baseshape) request.append(itemshape) @@ -1376,7 +1561,8 @@ def listFolders(self, type, on_behalf, shape='DEFAULT_PROPERTIES', depth='SHALLO # set the starting folder as the inbox distinguishedfolderid = Element('t:DistinguishedFolderId') - distinguishedfolderid.set('Id', getattr(self.types.EWSType_DistinguishedFolderIdNameType, type)) + distinguishedfolderid.set( + 'Id', getattr(self.types.EWSType_DistinguishedFolderIdNameType, type)) parentfolderids = Element('m:ParentFolderIds') mailbox = Element('t:Mailbox') emailaddress = Element('t:EmailAddress').setText(on_behalf) @@ -1388,7 +1574,7 @@ def listFolders(self, type, on_behalf, shape='DEFAULT_PROPERTIES', depth='SHALLO xml = self.exchange.transport.wrap(request) try: - result = self.client.service.FindFolder(__inject={'msg':xml}) + result = self.client.service.FindFolder(__inject={'msg': xml}) except WebFault as e: raise e msg = result.FindFolderResponseMessage @@ -1396,10 +1582,10 @@ def listFolders(self, type, on_behalf, shape='DEFAULT_PROPERTIES', depth='SHALLO rspclass = msg._ResponseClass rspcode = msg.ResponseCode if rspclass == 'Error': - raise Exception('Error code: %s message: %s' % \ + raise Exception('Error code: %s message: %s' % (rspcode, msg.MessageText)) if rspclass != 'Success': - raise Exception('Unknown response class: %s code: %s' % \ + raise Exception('Unknown response class: %s code: %s' % (rspclass, rspcode)) if rspcode != 'NoError': @@ -1417,8 +1603,6 @@ def listFolders(self, type, on_behalf, shape='DEFAULT_PROPERTIES', depth='SHALLO return fullitems - - def getids(self, items, include_change_key=True): '''Takes a list of items with full or partial properties as produced from getitems() and returns a list of (id, changekey) @@ -1444,7 +1628,7 @@ def getids(self, items, include_change_key=True): idlist.append((item._Id, item._ChangeKey)) else: idlist.append(item._Id) - except (IndexError,AttributeError): + except (IndexError, AttributeError): for item in [j.ItemId for j in items]: if(include_change_key): idlist.append((item._Id, item._ChangeKey)) @@ -1469,12 +1653,14 @@ def synchFolder(self, type, on_behalf=None, num_to_synch=10, synch_state=None, s synchfolder = Element('m:SyncFolderItems') itemshape = Element('m:ItemShape') - baseshape = Element('t:BaseShape').setText(getattr(self.types.EWSType_DefaultShapeNamesType, shape)) + baseshape = Element('t:BaseShape').setText( + getattr(self.types.EWSType_DefaultShapeNamesType, shape)) itemshape.append(baseshape) synchfolder.append(itemshape) distinguishedfolderid = Element('t:DistinguishedFolderId') - distinguishedfolderid.set('Id', getattr(self.types.EWSType_DistinguishedFolderIdNameType, type)) + distinguishedfolderid.set( + 'Id', getattr(self.types.EWSType_DistinguishedFolderIdNameType, type)) parentfolderids = Element('m:SyncFolderId') mailbox = Element('t:Mailbox') emailaddress = Element('t:EmailAddress').setText(on_behalf) @@ -1497,7 +1683,8 @@ def synchFolder(self, type, on_behalf=None, num_to_synch=10, synch_state=None, s xml = self.exchange.transport.wrap(synchfolder) try: - result = self.client.service.SyncFolderItems(__inject={'msg':xml}) + result = self.client.service.SyncFolderItems( + __inject={'msg': xml}) except WebFault as e: raise e msg = result.SyncFolderItemsResponseMessage @@ -1505,10 +1692,10 @@ def synchFolder(self, type, on_behalf=None, num_to_synch=10, synch_state=None, s rspclass = msg._ResponseClass rspcode = msg.ResponseCode if rspclass == 'Error': - raise Exception('Error code: %s message: %s' % \ + raise Exception('Error code: %s message: %s' % (rspcode, msg.MessageText)) if rspclass != 'Success': - raise Exception('Unknown response class: %s code: %s' % \ + raise Exception('Unknown response class: %s code: %s' % (rspclass, rspcode)) if rspcode != 'NoError': @@ -1520,31 +1707,31 @@ def synchFolder(self, type, on_behalf=None, num_to_synch=10, synch_state=None, s if msg.SyncState: self.synchState = msg.SyncState - #if isinstance(msg.Changes[0], list): + # if isinstance(msg.Changes[0], list): # fullitems = msg.Changes[0] - #else: + # else: # Only one item returned - changes = msg.Changes - fullitems = [] - __fullitems = [] - ids = [] - fullitems_add = [] + changes = msg.Changes + fullitems = [] + __fullitems = [] + ids = [] + fullitems_add = [] fullitems_update = [] - #add/update events + # add/update events if hasattr(changes, 'Create'): if isinstance(changes.Create[0], list): __fullitems.extend(changes.Create[0]) else: __fullitems.extend(changes.Create) - #fix attributes + # fix attributes if len(__fullitems) > 1: for elem in __fullitems: fullitems.append(elem[0]) else: for elem in __fullitems: fullitems.append(elem[1]) - #get ids from response + # get ids from response ids.extend(self.getids(fullitems, False)) fullitems_add = fullitems fullitems = [] @@ -1555,71 +1742,90 @@ def synchFolder(self, type, on_behalf=None, num_to_synch=10, synch_state=None, s __fullitems.extend(changes.Update[0]) else: __fullitems.extend(changes.Update) - #fix attributes + # fix attributes if len(__fullitems) > 1: for elem in __fullitems: fullitems.append(elem[0]) else: for elem in __fullitems: fullitems.append(elem[1]) - #get ids from response + # get ids from response ids.extend(self.getids(fullitems, False)) fullitems_update = fullitems fullitems = [] __fullitems = [] - #if we have addtional proerties to fetch do so + # if we have addtional proerties to fetch do so - #join fulltimes toghether + # join fulltimes toghether fullitems = fullitems_add + fullitems_update - - #call self with ids and required props - if type=="CALENDAR": + # call self with ids and required props + if type == "CALENDAR": if ids: - additional.append("calendar:RequiredAttendees") - additional.append("calendar:IsAllDayEvent") - additional.append("calendar:Duration") - additional.append("item:Categories") - additional.append("item:Body") - additional.append("item:Sensitivity") - additional.append("item:Importance") - extended_items = self.listItems(type="CALENDAR", id=ids, additional=additional) - #print extended_items - #print ids - #exit() - extended_add, extended_fday, extended_cats, extended_body = ({} for i in range(4)) + additional.append({'FieldURI': "calendar:RequiredAttendees"}) + additional.append({'FieldURI': "calendar:IsAllDayEvent"}) + additional.append({'FieldURI': "calendar:Duration"}) + additional.append({'FieldURI': "item:Categories"}) + additional.append({'FieldURI': "item:Body"}) + additional.append({'FieldURI': "item:Sensitivity"}) + additional.append({'FieldURI': "item:Importance"}) + extended_items = self.listItems( + type="CALENDAR", id=ids, additional=additional) + # print extended_items + # print ids + # exit() + extended_add, extended_fday, extended_cats, extended_body = ( + {} for i in range(4)) extended_sen, extended_imp = ({} for i in range(2)) for i in range(0, len(extended_items)): if hasattr(extended_items[i], 'RequiredAttendees'): - extended_add[extended_items[i].ItemId._Id] = extended_items[i].RequiredAttendees + extended_add[extended_items[i].ItemId._Id] = extended_items[ + i].RequiredAttendees else: extended_add[extended_items[i].ItemId._Id] = [] - extended_fday[extended_items[i].ItemId._Id] = extended_items[i].IsAllDayEvent - extended_cats[extended_items[i].ItemId._Id] = extended_items[i].Categories if hasattr(extended_items[i], "Categories") else None - extended_body[extended_items[i].ItemId._Id] = extended_items[i].Body if hasattr(extended_items[i], "Body") else None - extended_sen[extended_items[i].ItemId._Id] = extended_items[i].Sensitivity - extended_imp[extended_items[i].ItemId._Id] = extended_items[i].Importance + extended_fday[extended_items[i].ItemId._Id] = extended_items[ + i].IsAllDayEvent + extended_cats[extended_items[i].ItemId._Id] = extended_items[ + i].Categories if hasattr(extended_items[i], "Categories") else None + extended_body[extended_items[i].ItemId._Id] = extended_items[ + i].Body if hasattr(extended_items[i], "Body") else None + extended_sen[extended_items[i].ItemId._Id] = extended_items[ + i].Sensitivity + extended_imp[ + extended_items[i].ItemId._Id] = extended_items[i].Importance try: - for i in range(0,len(fullitems)): - fullitems[i].RequiredAttendees = extended_add.get(fullitems[i].ItemId._Id) - fullitems[i].IsAllDayEvent = extended_fday.get(fullitems[i].ItemId._Id) - fullitems[i].Categories = extended_cats.get(fullitems[i].ItemId._Id) - fullitems[i].Body = extended_body.get(fullitems[i].ItemId._Id) - fullitems[i].Sensitivity = extended_sen.get(fullitems[i].ItemId._Id) - fullitems[i].Importance = extended_imp.get(fullitems[i].ItemId._Id) + for i in range(0, len(fullitems)): + fullitems[i].RequiredAttendees = extended_add.get( + fullitems[i].ItemId._Id) + fullitems[i].IsAllDayEvent = extended_fday.get( + fullitems[i].ItemId._Id) + fullitems[i].Categories = extended_cats.get( + fullitems[i].ItemId._Id) + fullitems[i].Body = extended_body.get( + fullitems[i].ItemId._Id) + fullitems[i].Sensitivity = extended_sen.get( + fullitems[i].ItemId._Id) + fullitems[i].Importance = extended_imp.get( + fullitems[i].ItemId._Id) except AttributeError: - fullitems.RequiredAttendees = extended_add.get(fullitems.ItemId._Id) - fullitems.IsAllDayEvent = extended_fday.get(fullitems.ItemId._Id) - fullitems.Categories = extended_cats.get(fullitems.ItemId._Id) - fullitems.Body = extended_body.get(fullitems.ItemId._Id) - fullitems.Sensitivity = extended_sen.get(fullitems.ItemId._Id) - fullitems.Importance = extended_imp.get(fullitems.ItemId._Id) + fullitems.RequiredAttendees = extended_add.get( + fullitems.ItemId._Id) + fullitems.IsAllDayEvent = extended_fday.get( + fullitems.ItemId._Id) + fullitems.Categories = extended_cats.get( + fullitems.ItemId._Id) + fullitems.Body = extended_body.get( + fullitems.ItemId._Id) + fullitems.Sensitivity = extended_sen.get( + fullitems.ItemId._Id) + fullitems.Importance = extended_imp.get( + fullitems.ItemId._Id) if(len(fullitems) < 2): - fullitems = [("EventSpoofedType",fullitems.pop())] + fullitems = [("EventSpoofedType", fullitems.pop())] #allitems = type('', (object,), {}) - allitems = lambda:0 + allitems = lambda: 0 allitems.addupdate = fullitems if hasattr(changes, 'Delete'): if len(changes.Delete) < 2: @@ -1630,6 +1836,7 @@ def synchFolder(self, type, on_behalf=None, num_to_synch=10, synch_state=None, s class EWSDateTime(datetime): + '''Extends the normal datetime implementation to satisfy Exchange''' def ewsformat(self): @@ -1640,8 +1847,8 @@ def midnightfix(self): return self - timedelta(seconds=1) def __new__(cls, y=None, m=None, d=None, h=None, mi=None, s=None): - if((y>=0) and (all(x is None for x in (m,d,h,mi,s)))): + if((y >= 0) and (all(x is None for x in (m, d, h, mi, s)))): t = datetime.fromtimestamp(y) - return super(EWSDateTime, cls).__new__(cls,t.year,t.month,t.day,t.hour,t.minute,t.second) + return super(EWSDateTime, cls).__new__(cls, t.year, t.month, t.day, t.hour, t.minute, t.second) else: - return super(EWSDateTime, cls).__new__(cls,y,m,d,h,mi,s) + return super(EWSDateTime, cls).__new__(cls, y, m, d, h, mi, s) diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..c1b7eab --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from EWSWrapper import EWSWrapper, EWSDateTime From 7a621885b34d0205cc1cd51725b77c2910780686 Mon Sep 17 00:00:00 2001 From: Chen Houwu Date: Sun, 25 Aug 2013 23:16:46 +0800 Subject: [PATCH 3/3] refine docs --- EWSWrapper.py | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/EWSWrapper.py b/EWSWrapper.py index 1d5fdc2..ddfa532 100644 --- a/EWSWrapper.py +++ b/EWSWrapper.py @@ -442,6 +442,10 @@ def listCalendarEvent(self, id=None, on_behalf=None, # query scope * @param string $onbehalf - "on behalf" seneder's email * @param int $start - event start timestamp * @param int $end - event end time + * @param list $categories - see the same parameter in findItem + * @param list $criteria - see the same parameter in findItem + * @param strign $shape - see the same parameter in findItem + * @param list $additional - see the same parameter in findItem * * @return object response */ @@ -487,7 +491,16 @@ def addCalendarEvent(self, subject, body, start, end, attendees, * @param bool $allday - is it an all-day event? * @param string $bodyType - body format (Text/HTML) * @param string $category - event actegory - * @param list $extProperties - additional properties. + * @param list $extProperties - additional properties. example: + [ + { + 'type': 't:ExtendedFieldURI', //can be ommitted + 'DistinguishedPropertySetId': 33029, + 'PropertyId': 'Task', + 'PropertyType': 'SystemTime', + 'value': start.ewsformat() + } + ] * @return object response * ''' @@ -647,6 +660,16 @@ def editCalendarEvent(self, id, ckey, subject=None, body=None, bodytype="TEXT", * @param array $anttendees - array of email addresses of invited poeople * @param bool $allday - is it an all-day event? * @param string $category - event actegory + * @param list $extProperties - additional properties. example: + [ + { + 'type': 't:ExtendedFieldURI', //can be ommitted + 'DistinguishedPropertySetId': 33029, + 'PropertyId': 'Task', + 'PropertyType': 'SystemTime', + 'value': start.ewsformat() + } + ] * * @return object response */ @@ -1173,6 +1196,7 @@ def getItem(self, id=None, shape='ID_ONLY', categories=[], additional=[]): * @param string id - item id or a list of ids * * @return object response + * @param list $additional - see the same parameter in findItem */ ''' getitem = Element('m:GetItem') @@ -1283,6 +1307,7 @@ def findItem(self, type, on_behalf=None, # query scope * @param string $onbehalf - "on behalf": item owner email * @param int $start - search start timestamp * @param int $end - search end timestamp + * @param list $categories - item is category name * @param dict $criteria - other criteria, for example { 'and': [ @@ -1315,6 +1340,20 @@ def findItem(self, type, on_behalf=None, # query scope }, ] } + * @param list $additional - addtional properties to be retrieved + * byond the given shape, for example + [ + { + 'type': 't:ExtendedFieldURI', + 'DistinguishedPropertySetId': 33029, + 'PropertyId': 'Task', + 'PropertyType': 'SystemTime', + }, + { + 'type': 't:FieldURI', + 'FieldURI': 'item:Categories', + } + ] * @return object response */ ''' @@ -1516,6 +1555,10 @@ def listItems(self, type, id=None, on_behalf=None, # query scope * @param string $onbehalf - "on behalf": item owner email * @param int $start - search start timestamp * @param int $end - search end timestamp + * @param list $categories - see the same parameter in findItems + * @param list $criteria - see the same parameter in findItems + * @param strign $shape - see the same parameter in findItems + * @param list $additional - see the same parameter in findItems * * @return object response */