def testMail(self): self.note = Note(itsView=self.view) Mail.MailStamp(self.note).add() self.checkStatus(0, 'DRAFT', 'NEITHER') # Remove the MailStamp; that should make it no longer a Draft Mail.MailStamp(self.note).remove() self.checkStatus(0)
def setUp(self): super(CommunicationStatusTestCase, self).setUp() self.address = Mail.EmailAddress( itsView=self.view, fullName=u"Mr. President", emailAddress=u"*****@*****.**") #account = Mail.IMAPAccount(itsView=self.view, # replyToAddress=self.address) schema.ns('osaf.pim', self.view).meEmailAddressCollection.add(self.address) self.note = Note(itsView=self.view)
def testNoStamp(self): # Make sure that, even if we create a Note with a toAddress, # that doesn't show up in the who field note = Note(itsView=self.view) notStampedMsg = Mail.MailStamp(note) notStampedMsg.toAddress = [self.address] self.failUnless(not hasattr(notStampedMsg.itsItem, 'displayWho'))
def runMailTest(self, incoming=False, outgoing=False): self.note = Note(itsView=self.view) mail = Mail.MailStamp(self.note) mail.add() self.checkStatus(0, 'DRAFT', 'NEITHER') inoutFlags = 0 if incoming: inoutFlags |= CommunicationStatus.IN mail.toAddress = self.address if outgoing: inoutFlags |= CommunicationStatus.OUT mail.fromAddress = self.address elif not incoming: inoutFlags |= CommunicationStatus.NEITHER self.checkStatus(inoutFlags, 'DRAFT') self.note.changeEditState(Modification.queued) self.checkStatus(inoutFlags, 'QUEUED') self.note.changeEditState(Modification.sent) self.checkStatus(inoutFlags, 'SENT') self.note.changeEditState(Modification.edited) self.checkStatus(inoutFlags, 'EDITED', 'UPDATE', 'DRAFT') # Remove the MailStamp; that should make it no longer a Draft mail.remove() self.checkStatus(0, 'EDITED') # Re-add the stamp mail.add() self.checkStatus(inoutFlags, 'EDITED', 'UPDATE', 'DRAFT') # and finally, re-send it self.note.changeEditState(Modification.updated) self.checkStatus(inoutFlags, 'UPDATE', 'SENT')
class CommunicationStatusTestCase(TestDomainModel.DomainModelTestCase): def setUp(self): super(CommunicationStatusTestCase, self).setUp() self.address = Mail.EmailAddress( itsView=self.view, fullName=u"Mr. President", emailAddress=u"*****@*****.**") #account = Mail.IMAPAccount(itsView=self.view, # replyToAddress=self.address) schema.ns('osaf.pim', self.view).meEmailAddressCollection.add(self.address) self.note = Note(itsView=self.view) def testOrder(self): self.failUnless(CommunicationStatus.UPDATE < CommunicationStatus.OUT < CommunicationStatus.IN < CommunicationStatus.NEITHER < CommunicationStatus.EDITED < CommunicationStatus.SENT < CommunicationStatus.ERROR < CommunicationStatus.QUEUED < CommunicationStatus.DRAFT < CommunicationStatus.NEEDS_REPLY < CommunicationStatus.READ) def checkStatus(self, startStatus, *flagNames): expectedStatus = startStatus for name in flagNames: expectedStatus |= getattr(CommunicationStatus, name) status = CommunicationStatus(self.note).status self.failUnlessEqual(status, expectedStatus, "Unexpected communication status: got %s, expected %s" % (CommunicationStatus.dump(status), CommunicationStatus.dump(expectedStatus))) def testNote(self): self.checkStatus(0) self.note.changeEditState(Modification.edited) self.checkStatus(0) # edited has no effect till created self.note.changeEditState(Modification.created) self.checkStatus(0) self.note.changeEditState(Modification.edited) self.checkStatus(0, 'EDITED') si = SharedItem(self.note) si.add() si.generateConflicts() self.checkStatus(0, 'EDITED', 'ERROR') def testMail(self): self.note = Note(itsView=self.view) Mail.MailStamp(self.note).add() self.checkStatus(0, 'DRAFT', 'NEITHER') # Remove the MailStamp; that should make it no longer a Draft Mail.MailStamp(self.note).remove() self.checkStatus(0) def runMailTest(self, incoming=False, outgoing=False): self.note = Note(itsView=self.view) mail = Mail.MailStamp(self.note) mail.add() self.checkStatus(0, 'DRAFT', 'NEITHER') inoutFlags = 0 if incoming: inoutFlags |= CommunicationStatus.IN mail.toAddress = self.address if outgoing: inoutFlags |= CommunicationStatus.OUT mail.fromAddress = self.address elif not incoming: inoutFlags |= CommunicationStatus.NEITHER self.checkStatus(inoutFlags, 'DRAFT') self.note.changeEditState(Modification.queued) self.checkStatus(inoutFlags, 'QUEUED') self.note.changeEditState(Modification.sent) self.checkStatus(inoutFlags, 'SENT') self.note.changeEditState(Modification.edited) self.checkStatus(inoutFlags, 'EDITED', 'UPDATE', 'DRAFT') # Remove the MailStamp; that should make it no longer a Draft mail.remove() self.checkStatus(0, 'EDITED') # Re-add the stamp mail.add() self.checkStatus(inoutFlags, 'EDITED', 'UPDATE', 'DRAFT') # and finally, re-send it self.note.changeEditState(Modification.updated) self.checkStatus(inoutFlags, 'UPDATE', 'SENT') def testOutgoingMail(self): self.runMailTest(outgoing=True) def testIncomingMail(self): self.runMailTest(incoming=True) def testInOutMail(self): self.runMailTest(incoming=True, outgoing=True)
def _importOneVObject(vobj, filters, coerceTzinfo, promptForTimezone, newItemParent): view = newItemParent.itsView itemIsNew = False newStamps = [] # by default, we'll create a new item, not change existing items itemChangeCallback = None # store up all attributes in a dictionary ... changesDict = {} # ... and define a shorthand for updating it def change(attr, value): changesDict[attr.name] = value # rruleset and userReminderInterval/userReminderTime must # be set last.... changeLast = [] # values that apply to VEVENT and VTODO ... summary = vobj.getChildValue("summary", u"") description = vobj.getChildValue("description") status = vobj.getChildValue("status", "").lower() duration = vobj.getChildValue("duration") uid = vobj.getChildValue("uid") rruleset = vobj.rruleset recurrenceID = vobj.getChildValue("recurrence_id") # ... uh, sorta completed = vobj.getChildValue("completed") def convertDatetime(dt): # coerce timezones based on coerceTzinfo if coerceTzinfo is not None: dt = TimeZone.coerceTimeZone(view, dt, coerceTzinfo) # ... and make sure we return something with an ICUtzinfo return convertToICUtzinfo(view, dt) reminderDelta = None reminderAbsoluteTime = None try: reminderValue = vobj.valarm.trigger.value except AttributeError: pass else: if type(reminderValue) is datetime.datetime: reminderAbsoluteTime = convertDatetime(reminderValue) else: assert type(reminderValue) is datetime.timedelta reminderDelta = reminderValue if vobj.name == "VEVENT": if DEBUG: logger.debug("got VEVENT %s", vobj) newStamps.append(EventStamp) dtstart = vobj.getChildValue("dtstart") if status in ("confirmed", "tentative"): pass elif status == "cancelled": # Chandler doesn't have CANCELLED status = "fyi" else: status = "confirmed" if EventStamp.transparency.name not in filters: change(EventStamp.transparency, status) location = vobj.getChildValue("location") if location: change(EventStamp.location, Calendar.Location.getLocation(view, location)) elif vobj.name == "VTODO": if DEBUG: logger.debug("got VEVENT %s", vobj) newStamps.append(TaskStamp) # VTODO with a DUE ==> EventTask due = vobj.getChildValue("due") if due is not None: newStamps.append(EventStamp) dtstart = due else: assert False, "vobj %s should always be VEVENT or VTODO" % (vobj,) # Save changes applicable to both events & tasks .... # SUMMARY <-> {EventStamp,TaskStamp}.summary if summary is not None: change(newStamps[0].summary, summary) # DESCRIPTION <-> body if description is not None: change(Note.body, description) # Absolute time reminders if reminderAbsoluteTime is not None and Remindable.reminders.name not in filters: changeLast.append(lambda item: setattr(item, Remindable.userReminderTime.name, reminderAbsoluteTime)) # Custom properties/parameters ignoredProperties = {} ignoredParameters = {} for line in vobj.lines(): name = line.name.lower() if name not in attributesUnderstood: line.transformFromNative() if not line.encoded and line.behavior: line.behavior.encode(line) ignoredProperties[name] = line.value params = u"" for key, paramvals in line.params.iteritems(): if key.lower() not in parametersUnderstood: vals = map(vobject.base.dquoteEscape, paramvals) params += ";" + key + "=" + ",".join(vals) if len(params) > 0: ignoredParameters[name] = params change(Note.icalendarProperties, ignoredProperties) change(Note.icalendarParameters, ignoredParameters) # See if we have a corresponding item already item = utility.findUID(view, uid) if item is not None: if DEBUG: logger.debug("matched UID %s with %s", uid, item) else: try: # See if uid is a valid repository UUID, if so we'll # go ahead and use it for the new item's UUID. uuid = UUID(uid) except ValueError: # Not in valid UUID format, so hash the icaluid to # generate a 16-byte string we can use for uuid uuid = UUID(md5.new(uid).digest()) logger.info("Converted icalUID '%s' to UUID '%s'", uid, str(uuid)) # If there is already an item with this UUID, use it, # otherwise we'll create one later item = view.findUUID(uuid) if item is not None: item.icalUID = unicode(uuid) if EventStamp in newStamps: dtend = vobj.getChildValue("dtend") isDate = type(dtstart) == date # RFC2445 allows VEVENTs without DTSTART, but it's hard to guess # what that would mean, so we won't catch an exception if there's no # dtstart. anyTime = getattr(dtstart, "x_osaf_anytime_param", None) == "TRUE" if duration is None: def getDifference(left, right): leftIsDate = type(left) == date rightIsDate = type(right) == date if leftIsDate: if rightIsDate: return left - right else: left = TimeZone.forceToDateTime(view, left) elif rightIsDate: right = TimeZone.forceToDateTime(view, right) return makeNaiveteMatch(view, left, right.tzinfo) - right if dtend is not None: duration = getDifference(dtend, dtstart) elif anyTime or isDate: duration = oneDay else: duration = datetime.timedelta(0) if isDate: dtstart = TimeZone.forceToDateTime(view, dtstart) # convert to Chandler's notion of all day duration duration -= oneDay elif dtstart.tzinfo is not None and promptForTimezone: # got a timezoned event, prompt (non-modally) to turn on # timezones app = wx.GetApp() if app is not None: def ShowTimezoneDialogCallback(): ShowTurnOnTimezonesDialog(view=app.UIRepositoryView) app.PostAsyncEvent(ShowTimezoneDialogCallback) promptForTimezone = False dtstart = convertDatetime(dtstart) tzinfo = dtstart.tzinfo if anyTime: change(EventStamp.anyTime, True) change(EventStamp.allDay, False) elif isDate: # allDay events should have anyTime True, so if the user # unselects allDay, the time isn't set to midnight change(EventStamp.anyTime, True) change(EventStamp.allDay, True) else: change(EventStamp.allDay, False) change(EventStamp.anyTime, False) change(EventStamp.startTime, dtstart) change(EventStamp.duration, duration) if (reminderDelta is not None) and (Remindable.reminders.name not in filters): changeLast.append(lambda item: setattr(item, EventStamp.userReminderInterval.name, reminderDelta)) if item is not None: event = EventStamp(item) if recurrenceID: if type(recurrenceID) == date: recurrenceID = datetime.datetime.combine(recurrenceID, time(tzinfo=tzinfo)) else: recurrenceID = convertToICUtzinfo(view, makeNaiveteMatch(view, recurrenceID, tzinfo)) masterEvent = EventStamp(item) event = masterEvent.getRecurrenceID(recurrenceID) if event is None and hasattr(masterEvent, "startTime"): # Some calendars, notably Oracle, serialize # recurrence-id as UTC, which wreaks havoc with # noTZ mode. So move recurrenceID to the same tzinfo # as the master's dtstart, bug 6830 masterTzinfo = masterEvent.startTime.tzinfo tweakedID = recurrenceID.astimezone(masterTzinfo) event = masterEvent.getRecurrenceID(tweakedID) if event is None: # just in case the previous didn't work tweakedID = recurrenceID.astimezone(tzinfo) event = masterEvent.getRecurrenceID(tweakedID) if event is None: # our recurrenceID didn't match an item we know # about. This may be because the item is created # by a later modification, a case we're not dealing # with. For now, just skip it. logger.info("RECURRENCE-ID '%s' didn't match rule.", recurrenceID) return (None, None, promptForTimezone) item = event.itsItem recurrenceLine = vobj.contents["recurrence-id"][0] range = recurrenceLine.params.get("RANGE", ["THIS"])[0] if range == "THISANDPRIOR": # ignore THISANDPRIOR changes for now logger.info("RECURRENCE-ID RANGE of THISANDPRIOR " "not supported") return (None, None, promptForTimezone) elif range == "THIS": itemChangeCallback = event.changeThis # check if this is a modification to a master event # if so, avoid changing the master's UUID when # creating a modification if event.getMaster() == event: mod = event._cloneEvent() mod.modificationFor = mod.occurrenceFor = event.itsItem if item.hasLocalAttributeValue(EventStamp.occurrenceFor.name): del event.occurrenceFor event = mod item = event.itsItem elif range == "THISANDFUTURE": itemChangeCallback = event.changeThisAndFuture else: logger.info("RECURRENCE-ID RANGE not recognized. " "RANGE = %s" % range) return (None, None, promptForTimezone) else: if event.rruleset is not None: # re-creating a recurring item from scratch, delete # old recurrence information # item might not be the master, though, so # get the master, or eventItem will be a deleted # event event = event.getMaster() item = event.itsItem # delete modifications the master has, to avoid # changing the master to a modification with a # different UUID for mod in itertools.imap(EventStamp, event.modifications): # [Bug 7019] # We need to del these because, in the deferred # delete case, we would have deferred items # living on, in the manner of the undead, in # master.modifications (and occurrences). This # would ultimately cause .getNextOccurrence() # to terminate prematurely. del mod.modificationFor del mod.occurrenceFor mod.itsItem.delete() event.removeRecurrence() itemChangeCallback = event.changeThis if DEBUG: logger.debug("Changing event: %s" % str(event)) assert itemChangeCallback is not None, "Must set itemChangeCallback for EventStamp imports" if rruleset is not None: # fix for Bug 6994, exdate and rdate timezones need to be # converted to ICUtzinfo instances for typ in "_rdate", "_exdate": setattr(rruleset, typ, [convertDatetime(d) for d in getattr(rruleset, typ, [])]) ruleSetItem = RecurrenceRuleSet(None, itsView=view) ruleSetItem.setRuleFromDateUtil(rruleset) changeLast.append(lambda item: setattr(item, EventStamp.rruleset.name, ruleSetItem)) if TaskStamp in newStamps: if Note._triageStatus.name not in filters: # Translate status from iCalendar to TaskStamp/ContentItem triageStatus = TriageEnum.now if status == "completed": triageStatus = TriageEnum.done elif status == "needs-action": change(Note.needsReply, True) elif status in ("", "in-process"): change(Note.needsReply, False) elif status == "cancelled": triageStatus = TriageEnum.later # @@@ Jeffrey: This may not be right... # Set triageStatus and triageStatusChanged together. if completed is not None: if type(completed) == date: completed = TimeZone.forceToDateTime(view, completed) changeLast.append(lambda item: item.setTriageStatus(triageStatus, when=completed)) itemIsNew = item is None if itemIsNew: # create a new item change(Note.icalUID, uid) kind = Note.getKind(view) item = kind.instantiateItem(None, newItemParent, uuid, withInitialValues=True) itemChangeCallback = item.__setattr__ else: if itemChangeCallback is None: itemChangeCallback = item.__setattr__ # update an existing item if rruleset is None and recurrenceID is None and EventStamp(item).rruleset is not None: # no recurrenceId or rruleset, but the existing item # may have recurrence, so delete it EventStamp(item).removeRecurrence() for attr, val in changesDict.iteritems(): # Only change a datetime if it's really different # from what the item already has: if type(val) is datetime.datetime: oldValue = getattr(item, attr, None) if oldValue is not None and oldValue == val and oldValue.tzinfo == val.tzinfo: continue itemChangeCallback(attr, val) # ... make sure the stamps are right for stamp in EventStamp, TaskStamp: if not stamp in newStamps: if has_stamp(item, stamp): stamp(item).remove() else: if not has_stamp(item, stamp): stamp(item).add() # ... and do the final set of changes for cb in changeLast: cb(item) return item, itemIsNew, promptForTimezone
def _importOneVObject(vobj, filters, coerceTzinfo, promptForTimezone, newItemParent): view = newItemParent.itsView itemIsNew = False newStamps = [] # by default, we'll create a new item, not change existing items itemChangeCallback = None # store up all attributes in a dictionary ... changesDict = {} # ... and define a shorthand for updating it def change(attr, value): changesDict[attr.name] = value # rruleset and userReminderInterval/userReminderTime must # be set last.... changeLast = [] # values that apply to VEVENT and VTODO ... summary = vobj.getChildValue('summary', u"") description = vobj.getChildValue('description') status = vobj.getChildValue('status', "").lower() duration = vobj.getChildValue('duration') uid = vobj.getChildValue('uid') rruleset = vobj.rruleset recurrenceID = vobj.getChildValue('recurrence_id') # ... uh, sorta completed = vobj.getChildValue('completed') def convertDatetime(dt): # coerce timezones based on coerceTzinfo if coerceTzinfo is not None: dt = TimeZone.coerceTimeZone(view, dt, coerceTzinfo) # ... and make sure we return something with an ICUtzinfo return convertToICUtzinfo(view, dt) reminderDelta = None reminderAbsoluteTime = None try: reminderValue = vobj.valarm.trigger.value except AttributeError: pass else: if type(reminderValue) is datetime.datetime: reminderAbsoluteTime = convertDatetime(reminderValue) else: assert type(reminderValue) is datetime.timedelta reminderDelta = reminderValue if vobj.name == "VEVENT": if DEBUG: logger.debug("got VEVENT %s", vobj) newStamps.append(EventStamp) dtstart = vobj.getChildValue('dtstart') if status in ('confirmed', 'tentative'): pass elif status == 'cancelled': #Chandler doesn't have CANCELLED status = 'fyi' else: status = 'confirmed' if EventStamp.transparency.name not in filters: change(EventStamp.transparency, status) location = vobj.getChildValue('location') if location: change(EventStamp.location, Calendar.Location.getLocation(view, location)) elif vobj.name == "VTODO": if DEBUG: logger.debug("got VEVENT %s", vobj) newStamps.append(TaskStamp) # VTODO with a DUE ==> EventTask due = vobj.getChildValue('due') if due is not None: newStamps.append(EventStamp) dtstart = due else: assert False, "vobj %s should always be VEVENT or VTODO" % ( vobj,) # Save changes applicable to both events & tasks .... # SUMMARY <-> {EventStamp,TaskStamp}.summary if summary is not None: change(newStamps[0].summary, summary) # DESCRIPTION <-> body if description is not None: change(Note.body, description) # Absolute time reminders if (reminderAbsoluteTime is not None and Remindable.reminders.name not in filters): changeLast.append(lambda item: setattr(item, Remindable.userReminderTime.name, reminderAbsoluteTime)) # Custom properties/parameters ignoredProperties = {} ignoredParameters = {} for line in vobj.lines(): name = line.name.lower() if name not in attributesUnderstood: line.transformFromNative() if not line.encoded and line.behavior: line.behavior.encode(line) ignoredProperties[name] = line.value params=u'' for key, paramvals in line.params.iteritems(): if key.lower() not in parametersUnderstood: vals = map(vobject.base.dquoteEscape, paramvals) params += ';' + key + '=' + ','.join(vals) if len(params) > 0: ignoredParameters[name] = params change(Note.icalendarProperties, ignoredProperties) change(Note.icalendarParameters, ignoredParameters) # See if we have a corresponding item already item = utility.findUID(view, uid) if item is not None: if DEBUG: logger.debug("matched UID %s with %s", uid, item) else: try: # See if uid is a valid repository UUID, if so we'll # go ahead and use it for the new item's UUID. uuid = UUID(uid) except ValueError: # Not in valid UUID format, so hash the icaluid to # generate a 16-byte string we can use for uuid uuid = UUID(md5.new(uid).digest()) logger.info("Converted icalUID '%s' to UUID '%s'", uid, str(uuid)) # If there is already an item with this UUID, use it, # otherwise we'll create one later item = view.findUUID(uuid) if item is not None: item.icalUID = unicode(uuid) if EventStamp in newStamps: dtend = vobj.getChildValue('dtend') isDate = type(dtstart) == date # RFC2445 allows VEVENTs without DTSTART, but it's hard to guess # what that would mean, so we won't catch an exception if there's no # dtstart. anyTime = (getattr(dtstart, 'x_osaf_anytime_param', None) == 'TRUE') if duration is None: def getDifference(left, right): leftIsDate = (type(left) == date) rightIsDate = (type(right) == date) if leftIsDate: if rightIsDate: return left - right else: left = TimeZone.forceToDateTime(view, left) elif rightIsDate: right = TimeZone.forceToDateTime(view, right) return makeNaiveteMatch(view, left, right.tzinfo) - right if dtend is not None: duration = getDifference(dtend, dtstart) elif anyTime or isDate: duration = oneDay else: duration = datetime.timedelta(0) if isDate: dtstart = TimeZone.forceToDateTime(view, dtstart) # convert to Chandler's notion of all day duration duration -= oneDay elif dtstart.tzinfo is not None and promptForTimezone: # got a timezoned event, prompt (non-modally) to turn on # timezones app = wx.GetApp() if app is not None: def ShowTimezoneDialogCallback(): ShowTurnOnTimezonesDialog(view=app.UIRepositoryView) app.PostAsyncEvent(ShowTimezoneDialogCallback) promptForTimezone = False dtstart = convertDatetime(dtstart) tzinfo = dtstart.tzinfo if anyTime: change(EventStamp.anyTime, True) change(EventStamp.allDay, False) elif isDate: # allDay events should have anyTime True, so if the user # unselects allDay, the time isn't set to midnight change(EventStamp.anyTime, True) change(EventStamp.allDay, True) else: change(EventStamp.allDay, False) change(EventStamp.anyTime, False) change(EventStamp.startTime, dtstart) change(EventStamp.duration, duration) if ((reminderDelta is not None) and (Remindable.reminders.name not in filters)): changeLast.append( lambda item:setattr(item, EventStamp.userReminderInterval.name, reminderDelta)) if item is not None: event = EventStamp(item) if recurrenceID: if type(recurrenceID) == date: recurrenceID = datetime.datetime.combine( recurrenceID, time(tzinfo=tzinfo)) else: recurrenceID = convertToICUtzinfo(view, makeNaiveteMatch(view, recurrenceID, tzinfo)) masterEvent = EventStamp(item) event = masterEvent.getRecurrenceID(recurrenceID) if event is None and hasattr(masterEvent, 'startTime'): # Some calendars, notably Oracle, serialize # recurrence-id as UTC, which wreaks havoc with # noTZ mode. So move recurrenceID to the same tzinfo # as the master's dtstart, bug 6830 masterTzinfo = masterEvent.startTime.tzinfo tweakedID = recurrenceID.astimezone(masterTzinfo) event = masterEvent.getRecurrenceID(tweakedID) if event is None: # just in case the previous didn't work tweakedID = recurrenceID.astimezone(tzinfo) event = masterEvent.getRecurrenceID(tweakedID) if event is None: # our recurrenceID didn't match an item we know # about. This may be because the item is created # by a later modification, a case we're not dealing # with. For now, just skip it. logger.info("RECURRENCE-ID '%s' didn't match rule.", recurrenceID) return (None, None, promptForTimezone) item = event.itsItem recurrenceLine = vobj.contents['recurrence-id'][0] range = recurrenceLine.params.get('RANGE', ['THIS'])[0] if range == 'THISANDPRIOR': # ignore THISANDPRIOR changes for now logger.info("RECURRENCE-ID RANGE of THISANDPRIOR " \ "not supported") return (None, None, promptForTimezone) elif range == 'THIS': itemChangeCallback = event.changeThis # check if this is a modification to a master event # if so, avoid changing the master's UUID when # creating a modification if event.getMaster() == event: mod = event._cloneEvent() mod.modificationFor = mod.occurrenceFor = event.itsItem if item.hasLocalAttributeValue( EventStamp.occurrenceFor.name): del event.occurrenceFor event = mod item = event.itsItem elif range == 'THISANDFUTURE': itemChangeCallback = event.changeThisAndFuture else: logger.info("RECURRENCE-ID RANGE not recognized. " \ "RANGE = %s" % range) return (None, None, promptForTimezone) else: if event.rruleset is not None: # re-creating a recurring item from scratch, delete # old recurrence information # item might not be the master, though, so # get the master, or eventItem will be a deleted # event event = event.getMaster() item = event.itsItem # delete modifications the master has, to avoid # changing the master to a modification with a # different UUID for mod in itertools.imap(EventStamp, event.modifications): # [Bug 7019] # We need to del these because, in the deferred # delete case, we would have deferred items # living on, in the manner of the undead, in # master.modifications (and occurrences). This # would ultimately cause .getNextOccurrence() # to terminate prematurely. del mod.modificationFor del mod.occurrenceFor mod.itsItem.delete() event.removeRecurrence() itemChangeCallback = event.changeThis if DEBUG: logger.debug("Changing event: %s" % str(event)) assert itemChangeCallback is not None, \ "Must set itemChangeCallback for EventStamp imports" if rruleset is not None: # fix for Bug 6994, exdate and rdate timezones need to be # converted to ICUtzinfo instances for typ in '_rdate', '_exdate': setattr(rruleset, typ, [convertDatetime(d) for d in getattr(rruleset, typ, []) ]) ruleSetItem = RecurrenceRuleSet(None, itsView=view) ruleSetItem.setRuleFromDateUtil(rruleset) changeLast.append(lambda item: setattr(item, EventStamp.rruleset.name, ruleSetItem)) if TaskStamp in newStamps: if Note._triageStatus.name not in filters: # Translate status from iCalendar to TaskStamp/ContentItem triageStatus=TriageEnum.now if status == "completed": triageStatus = TriageEnum.done elif status == "needs-action": change(Note.needsReply, True) elif status in ("", "in-process"): change(Note.needsReply, False) elif status == "cancelled": triageStatus = TriageEnum.later # @@@ Jeffrey: This may not be right... # Set triageStatus and triageStatusChanged together. if completed is not None: if type(completed) == date: completed = TimeZone.forceToDateTime(view, completed) changeLast.append(lambda item: item.setTriageStatus(triageStatus, when=completed)) itemIsNew = (item is None) if itemIsNew: # create a new item change(Note.icalUID, uid) kind = Note.getKind(view) item = kind.instantiateItem(None, newItemParent, uuid, withInitialValues=True) itemChangeCallback = item.__setattr__ else: if itemChangeCallback is None: itemChangeCallback = item.__setattr__ # update an existing item if (rruleset is None and recurrenceID is None and EventStamp(item).rruleset is not None): # no recurrenceId or rruleset, but the existing item # may have recurrence, so delete it EventStamp(item).removeRecurrence() for attr, val in changesDict.iteritems(): # Only change a datetime if it's really different # from what the item already has: if type(val) is datetime.datetime: oldValue = getattr(item, attr, None) if (oldValue is not None and oldValue == val and oldValue.tzinfo == val.tzinfo): continue itemChangeCallback(attr, val) # ... make sure the stamps are right for stamp in EventStamp, TaskStamp: if not stamp in newStamps: if has_stamp(item, stamp): stamp(item).remove() else: if not has_stamp(item, stamp): stamp(item).add() # ... and do the final set of changes for cb in changeLast: cb(item) return item, itemIsNew, promptForTimezone
def setUp(self): super(TriageTest, self).setUp() view = self.view self.item = Note("triageTestItem", itsView=view) pass