Changeset 47410
- Timestamp:
- 07/25/06 15:56:06 (4 years ago)
- Files:
-
- Python/CalCore/trunk/CHANGES (modified) (1 diff)
- Python/CalCore/trunk/src/calcore/cal.py (modified) (58 diffs)
- Python/CalCore/trunk/src/calcore/tests/test_ical.py (modified) (33 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
Python/CalCore/trunk/CHANGES
r45829 r47410 7 7 Bug fixes: 8 8 ~~~~~~~~~~ 9 - 9 - #1708: no more 'NONE' attachement on import + export the document attribute 10 as ATTACH value (in ical) 10 11 New internal features: 11 12 ~~~~~~~~~~~~~~~~~~~~~~ Python/CalCore/trunk/src/calcore/cal.py
r31372 r47410 49 49 """ 50 50 implements(IEventSpecification) 51 51 52 52 def __init__(self, dtstart, duration, 53 53 title='', … … 100 100 o.setParticipationRole(attendee, role) 101 101 o._setParticipationStatus(attendee, status) 102 103 102 103 104 104 class StorageManagerBase: 105 105 106 106 implements(IStorageManager) 107 107 108 108 def __init__(self): 109 109 self._storage = None 110 110 111 111 # MANIPULATORS 112 112 113 113 def setStorage(self, storage): 114 114 self._storage = storage 115 115 116 116 def createEvent(self, unique_id=None, **kw): 117 117 return self._storage.createEvent(unique_id, EventSpecification(**kw)) … … 119 119 def createEventFromSpecification(self, unique_id, spec): 120 120 return self._storage.createEvent(unique_id, spec) 121 121 122 122 def deleteEvent(self, event): 123 123 self._storage.deleteEvent(event) 124 124 125 125 # ACCESSORS 126 126 … … 134 134 except KeyError: 135 135 return False 136 136 137 137 def getEvents(self, period, search_criteria=None): 138 138 return self._storage.getEvents(period, search_criteria) … … 144 144 return segmentOccurrences(period, 145 145 self.getOccurrences(period, search_criteria)) 146 146 147 147 def getBlockedPeriods(self, attendees, period, time_period): 148 148 # XXX need to check for events that an attendee is interested … … 155 155 for occ in self.getOccurrencesSegmented(period, search_criteria): 156 156 # transparent or canceled events don't count 157 if (occ.original.transparent or 157 if (occ.original.transparent or 158 158 occ.original.status == 'CANCELED'): 159 159 continue 160 160 # And neither does events you are not going to: 161 if (occ.original.getParticipationStatus(attendee) in 161 if (occ.original.getParticipationStatus(attendee) in 162 162 ['DECLINED', 'DELEGATED']): 163 163 continue … … 191 191 return util.removeOverlaps(blocked_periods) 192 192 193 193 194 194 def getFreePeriods(self, attendees, period, time_period, 195 195 minimal_duration=None): … … 204 204 if ends > combine(ends.date(), time_ends): 205 205 ends = combine(ends.date(), time_ends) 206 206 207 207 last_block_begins = begins 208 208 for dtstart, dtend in blocked_periods: … … 217 217 free_periods.append((last_block_begins, ends)) 218 218 return free_periods 219 219 220 220 class StorageManager(StorageManagerBase): 221 221 pass 222 222 223 223 class StorageBase: 224 224 """Minimalistic implementation of a storage. 225 225 """ 226 226 implements(IStorage) 227 227 228 228 def __init__(self, storage_id, hostname=None): 229 229 self._storage_id = storage_id 230 230 self._events = self._initEvents() 231 231 self._hostname = hostname or socket.getfqdn() 232 232 233 233 def _initEvents(self): 234 234 raise NotImplementedError … … 236 236 def _eventFactory(self, event_id, spec): 237 237 raise NotImplementedError 238 238 239 239 # MANIPULATORS 240 240 def createEvent(self, unique_id, spec): … … 246 246 self._events[unique_id] = event 247 247 return event 248 248 249 249 def deleteEvent(self, event): 250 250 del self._events[event.unique_id] 251 251 252 252 # ACCESSORS 253 253 … … 257 257 def getEvent(self, event_id): 258 258 return self._events[event_id] 259 259 260 260 def getEvents(self, period, search_criteria): 261 261 events = self._getMatchingEvents(search_criteria) … … 291 291 class SearchCriteria: 292 292 implements(ISearchCriteria) 293 293 294 294 def __init__(self, 295 295 attendees=None, … … 298 298 categories=None, 299 299 organizer=None): 300 if (attendees is not None and 300 if (attendees is not None and 301 301 not isinstance(attendees,(ListType, TupleType))): 302 302 attendees = [attendees] … … 309 309 def clone(self, attendees=None, participation_status=None, 310 310 participation_role=None, categories=None, organizer=None): 311 if (attendees is not None and 311 if (attendees is not None and 312 312 not isinstance(attendees,(ListType, TupleType))): 313 313 attendees = [attendees] … … 337 337 if self.attendees is None: 338 338 return True 339 339 340 340 for attendee in self.attendees: 341 341 if not event.hasAttendee(attendee): 342 342 continue 343 343 344 344 if (self.participation_status is not None and 345 345 self.participation_status != 346 346 event.getParticipationStatus(attendee)): 347 347 continue 348 348 349 349 if (self.participation_role is not None and 350 350 self.participation_role != 351 351 event.getParticipationRole(attendee)): 352 352 continue 353 353 354 354 #This attendee matched 355 355 return True 356 356 357 357 # No attendee matched 358 358 return False … … 362 362 def __init__(self): 363 363 SearchCriteria.__init__(self) 364 364 365 365 class EventBase: 366 366 implements(IInvitableCalendarEvent) 367 367 368 368 def __init__(self, unique_id, spec): 369 369 self.unique_id = unique_id … … 372 372 373 373 spec.setOnObject(self) 374 374 375 375 if self.allday: 376 376 self.alldayAdjust() 377 377 378 378 def _initParticipationState(self): 379 379 raise NotImplementedError … … 390 390 self.setParticipationRole(attendee, 'REQ-PARTICIPANT') 391 391 attendee.on_invite(self) 392 392 393 393 def setParticipationStatus(self, attendee, status): 394 394 assert status in [None, 'NEEDS-ACTION', 'ACCEPTED', 'DECLINED', … … 426 426 days += 1 427 427 self.duration = timedelta(days=days) 428 428 429 429 # ACCESSORS 430 430 431 431 def getOrganizerId(self): 432 432 return self._organizer_id 433 433 434 434 def inCategory(self, category): 435 435 return category in self.categories 436 436 437 437 def getAttendeeIds(self, 438 438 participation_status=None, participation_role=None): … … 458 458 return self._participation_role.get( 459 459 attendee.getAttendeeId()) 460 460 461 461 def __hash__(self): 462 462 return hash(self.unique_id) 463 463 464 464 def __cmp__(self, other): 465 465 # sort first on start datetime, then on public id … … 469 469 (self.dtstart, self.unique_id), 470 470 (other.dtstart, self.unique_id)) 471 471 472 472 def expand(self, period): 473 473 """Returns ICalendarOccurrences for this event in period.""" … … 491 491 class Timed: 492 492 implements(ITimed) 493 493 494 494 def __init__(self, dtstart, duration): 495 495 self.dtstart = dtstart 496 496 self.duration = duration 497 497 498 498 class Event(EventBase): 499 499 def _initParticipationState(self): … … 513 513 class AttendeeBase: 514 514 implements(IAttendee) 515 515 516 516 def __init__(self, attendee_id, name, attendee_type, on_invite): 517 517 self._attendee_id = attendee_id … … 519 519 self._attendee_type = attendee_type 520 520 self._on_invite = on_invite 521 521 522 522 # MANIPULATORS 523 523 … … 525 525 kw['organizer'] = self 526 526 return self._getStorageManager().createEvent(unique_id=None, **kw) 527 527 528 528 def on_invite(self, event): 529 529 if self._on_invite is not None: … … 532 532 def on_status_change(self, event, from_status, to_status): 533 533 pass 534 534 535 535 # ACCESSORS 536 536 … … 540 540 def getAttendeeType(self): 541 541 return self._attendee_type 542 542 543 543 def getEvents(self, period, search_criteria=None): 544 544 if search_criteria is None: … … 556 556 return self._getStorageManager().getOccurrences( 557 557 period, search_criteria) 558 558 559 559 def getOrganizedEvents(self, search_criteria=None): 560 560 if search_criteria is None: … … 567 567 def _getStorageManager(self): 568 568 raise NotImplementedError 569 569 570 570 class Attendee(AttendeeBase): 571 571 def __init__(self, storage_manager, attendee_id, … … 574 574 self, attendee_id, name, attendee_type, on_invite) 575 575 self._storage_manager = storage_manager 576 576 577 577 def _getStorageManager(self): 578 578 return self._storage_manager … … 582 582 class CalendarBase: 583 583 implements(ICalendar) 584 584 585 585 def __init__(self): 586 586 self._attendees = self._initAttendees() … … 590 590 591 591 # MANIPULATORS 592 592 593 593 def addAttendee(self, attendee): 594 594 self._attendees[attendee.getAttendeeId()] = None … … 603 603 source = self._getAttendeeSource() 604 604 return source.getAttendee(attendee_id) 605 606 def import_(self, text, period=(None, None), search_criteria=None, 605 606 def import_(self, text, period=(None, None), search_criteria=None, 607 607 synchronize=0): 608 608 """Given iCalendar text, import events. 609 609 610 610 This overwrites existing event data where necessary, 611 and creates new events. If synchronize is set to 1, 612 it also removes existing events. This is used when the 613 iCalendar client is assumed to have retrieved the calendar 611 and creates new events. If synchronize is set to 1, 612 it also removes existing events. This is used when the 613 iCalendar client is assumed to have retrieved the calendar 614 614 first, as when you are using it via WebDAV. 615 615 """ … … 651 651 spec = self._importEventSpecification(e) 652 652 spec.setOnObject(event) 653 653 654 654 ## # XXX this (like the edit forms) implicitly updates events 655 655 ## # this may not work for non ZODB storages … … 667 667 ## else: 668 668 ## event.recurrence = None 669 669 670 670 def _importNewEvent(self, uid, e): 671 671 m = self._getStorageManager() … … 677 677 """ 678 678 asrc = self._getAttendeeSource() 679 679 680 680 kw = {} 681 681 kw['dtstart'], kw['duration'], kw['allday'] =\ 682 self._getDtstartDuration(e) 682 self._getDtstartDuration(e) 683 683 684 684 kw['title'] = e.decoded('SUMMARY', '') … … 692 692 else: 693 693 organizer = None 694 694 695 695 if organizer is None: 696 696 organizer = self.getMainAttendee() 697 697 698 698 kw['organizer'] = organizer 699 700 699 700 701 701 if e.has_key('ATTENDEE'): 702 702 attendees = [] … … 710 710 attendees.append((attendee, role, status)) 711 711 kw['attendees'] = attendees 712 712 713 713 kw['categories'] = self._getCategories(e) 714 714 kw['transparent'] = e.decoded('TRANSP', 'OPAQUE') == 'TRANSPARENT' 715 715 kw['access'] = e.decoded('CLASS', 'PUBLIC') 716 kw['document'] = e.decoded('ATTACH', 'NONE')716 kw['document'] = e.decoded('ATTACH', None) 717 717 rrule = e.decoded('RRULE', None) 718 718 if rrule is not None: … … 721 721 kw['recurrence'] = None 722 722 return EventSpecification(**kw) 723 723 724 724 def _deleteEvent(self, uid): 725 725 self._getStorageManager().deleteEvent(uid) 726 726 727 727 def _getDtstartDuration(self, component): 728 728 dtstart = component.decoded('DTSTART', None) … … 807 807 categories = Set(categories) 808 808 return categories 809 809 810 810 def _repairText(self, text): 811 811 # repair text to have \r\n if necessary … … 816 816 text = '\r\n'.join(lines) 817 817 return text 818 818 819 819 # ACCESSORS 820 820 821 821 def getEvent(self, event_id): 822 822 # XXX should check whether event applies to any of the … … 827 827 def hasEvent(self, event_id): 828 828 return self._getStorageManager().hasEvent(event_id) 829 829 830 830 def getEvents(self, period, search_criteria=None): 831 831 result = {} … … 845 845 search_criteria = SearchCriteria(attendees=self.getAttendees()) 846 846 return self._getStorageManager().getOccurrences(period, search_criteria) 847 847 848 848 def getOccurrencesSegmented(self, period, search_criteria=None): 849 849 # first get occurrences … … 852 852 # borders (day and start and end of period) 853 853 return segmentOccurrences(period, occurrences) 854 854 855 855 def getEventsInDay(self, date, search_criteria=None): 856 856 start = datetime(date.year, date.month, date.day) … … 858 858 period = (start, end) 859 859 return self.getEvents(period, search_criteria) 860 860 861 861 def getOccurrencesInDay(self, date, search_criteria=None): 862 862 """Returns all occurrences in day. … … 879 879 def getCurrentYear(self): 880 880 return self.getToday().year 881 881 882 882 def getToday(self): 883 883 return datetime.today() … … 929 929 e.set_inline('categories', list(event.categories)) 930 930 e.add('class', event.access) 931 if event.document: 932 e.add('attach', event.document) 931 933 if event.recurrence is not None: 932 934 r = event.recurrence … … 947 949 def _getAttendeeSource(self): 948 950 raise NotImplementedError 949 951 950 952 class Calendar(CalendarBase): 951 953 def __init__(self, storage_manager, attendee_source): … … 953 955 self._storage_manager = storage_manager 954 956 self._attendee_source = attendee_source 955 957 956 958 def _getStorageManager(self): 957 959 return self._storage_manager … … 966 968 """ 967 969 implements(IAttendeeSource) 968 970 969 971 def __init__(self, storage_manager): 970 972 self._storage_manager = storage_manager … … 972 974 973 975 # MANIPULATORS 974 976 975 977 def createIndividual(self, attendee_id, name): 976 978 attendee = Attendee( … … 979 981 self._attendees[attendee_id] = attendee 980 982 return attendee 981 983 982 984 def createRoom(self, attendee_id, name, on_invite=None): 983 985 attendee = Attendee( … … 988 990 989 991 # ACCESSORS 990 992 991 993 def getAttendee(self, attendee_id): 992 994 return self._attendees[attendee_id] … … 995 997 # there is no concept of a current user at this level 996 998 return None 997 999 998 1000 def findByName(self, query_str, attendee_type=None): 999 1001 result = [] … … 1006 1008 result.append(attendee) 1007 1009 return result 1008 1010 1009 1011 def getAttendeesOfType(self, attendee_type): 1010 1012 result = [] … … 1024 1026 def acceptIfPossible(attendee, event): 1025 1027 # always accept for now 1026 event.setParticipationStatus(attendee, 'ACCEPTED') 1028 event.setParticipationStatus(attendee, 'ACCEPTED') 1027 1029 1028 1030 def Period(dtstart, duration): … … 1066 1068 assert period[0] is not None, "Period is unbounded (into the past)" 1067 1069 assert period[1] is not None, "Period is unbounded (into the future)" 1068 1070 1069 1071 def addrspec_unique_id(unique_maker, hostname): 1070 1072 result = iso8601(datetime.now()) Python/CalCore/trunk/src/calcore/tests/test_ical.py
r23746 r47410 14 14 15 15 self._calendar.addAttendee(martijn) 16 16 17 17 meeting = martijn.createEvent( 18 18 dtstart=datetime(2005, 4, 10, 16, 00), … … 27 27 title='Another meeting', 28 28 location='Room 2', 29 document='/path/to/some/document', 29 30 categories=Set(['Public Holiday', 30 31 'Wonderful Event'])) … … 32 33 self._meeting_uid = meeting.unique_id 33 34 self._meeting2_uid = meeting2.unique_id 34 35 35 36 def test_export_import1(self): 36 37 # export the calendar to iCalendar text 37 38 text = self._calendar.export() 38 # now we import the text again. 39 # now we import the text again. 39 40 self._calendar.import_(text) 40 41 41 42 # nothing should be changed! 42 43 43 44 # we need to reconnect to the events 44 45 meeting = self._calendar.getEvent(self._meeting_uid) 45 46 meeting2 = self._calendar.getEvent(self._meeting2_uid) 46 47 47 48 self.assertEquals( 48 49 "Martijn's Meeting", … … 60 61 Set(['Public Holiday', 'Wonderful Event']), 61 62 meeting2.categories) 62 63 self.assertEquals(None, meeting.document) 64 self.assertEquals('/path/to/some/document', meeting2.document) 65 63 66 def test_export_import2(self): 64 67 # export the calendar to iCalendar text 65 68 text = self._calendar.export() 66 69 # change a summary in the text 67 text = text.replace("Martijn's Meeting", "Foo's Meeting") 68 # now we import the text again. 70 text = text.replace("Martijn's Meeting", "Foo's Meeting") 71 # now we import the text again. 69 72 self._calendar.import_(text) 70 73 meeting = self._calendar.getEvent(self._meeting_uid) … … 84 87 text = text.replace('DTSTART:20050411T170000', 85 88 'DTSTART:20050412T180000') 86 # now we import the text again. 89 # now we import the text again. 87 90 self._calendar.import_(text) 88 91 meeting = self._calendar.getEvent(self._meeting_uid) … … 121 124 timedelta(hours=1), 122 125 meeting3.duration) 123 126 124 127 def test_export_import_remove_event(self): 125 128 # export the calendar to iCalendar text … … 157 160 self.assertEquals( 158 161 timedelta(minutes=75), 159 meeting2.duration) 162 meeting2.duration) 160 163 161 164 def test_export_import_recurrence(self): … … 172 175 RRULE:FREQ=DAILY;INTERVAL=1 173 176 END:VEVENT""" 174 text = insert_event_textually(text, new_event) 177 text = insert_event_textually(text, new_event) 175 178 # now import again 176 179 self._calendar.import_(text) … … 244 247 STATUS:CONFIRMED 245 248 END:VEVENT""" 246 text = insert_event_textually(text, new_event) 249 text = insert_event_textually(text, new_event) 247 250 # now import again 248 251 self._calendar.import_(text) … … 274 277 CATEGORIES:HOLIDAY,MISC 275 278 END:VEVENT""" 276 text = insert_event_textually(text, new_event) 279 text = insert_event_textually(text, new_event) 277 280 # now import again 278 281 self._calendar.import_(text) … … 299 302 CATEGORIES:MISC 300 303 END:VEVENT""" 301 text = insert_event_textually(text, new_event) 304 text = insert_event_textually(text, new_event) 302 305 # now import again 303 306 self._calendar.import_(text) 304 307 self.assertEquals( 305 308 Set(['HOLIDAY', 'MISC']), 306 self._calendar.getEvent('hoi').categories) 309 self._calendar.getEvent('hoi').categories) 307 310 308 311 def test_categories_existing_event(self): … … 314 317 self.assertEquals( 315 318 Set(['HOLIDAY', 'MISC']), 316 self._calendar.getEvent(self._meeting_uid).categories) 319 self._calendar.getEvent(self._meeting_uid).categories) 317 320 318 321 def test_categories_existing_event_different(self): … … 355 358 TRANSP:TRANSPARENT 356 359 END:VEVENT""" 357 text = insert_event_textually(text, new_event) 360 text = insert_event_textually(text, new_event) 358 361 # now import again 359 362 self._calendar.import_(text) … … 389 392 'PRIVATE', 390 393 self._calendar.getEvent(self._meeting_uid).access) 391 394 392 395 def test_access_change_confidential(self): 393 396 text = self._calendar.export() … … 397 400 'CONFIDENTIAL', 398 401 self._calendar.getEvent(self._meeting_uid).access) 399 402 400 403 class RecurrentImportExportTestCase(unittest.TestCase): 401 404 def setUp(self): … … 421 424 RRULE:FREQ=DAILY;INTERVAL=1 422 425 END:VEVENT""" 423 text = insert_event_textually(text, new_event) 426 text = insert_event_textually(text, new_event) 424 427 self._calendar.import_(text) 425 428 occurrences = self._calendar.getOccurrences( … … 440 443 RRULE:FREQ=DAILY;INTERVAL=1;UNTIL=20050406 441 444 END:VEVENT""" 442 text = insert_event_textually(text, new_event) 445 text = insert_event_textually(text, new_event) 443 446 self._calendar.import_(text) 444 447 occurrences = self._calendar.getOccurrences( … … 459 462 RRULE:FREQ=DAILY;INTERVAL=1;COUNT=4 460 463 END:VEVENT""" 461 text = insert_event_textually(text, new_event) 464 text = insert_event_textually(text, new_event) 462 465 self._calendar.import_(text) 463 466 occurrences = self._calendar.getOccurrences( … … 478 481 RRULE:FREQ=YEARLY;INTERVAL=1 479 482 END:VEVENT""" 480 text = insert_event_textually(text, new_event) 483 text = insert_event_textually(text, new_event) 481 484 self._calendar.import_(text) 482 485 occurrences = self._calendar.getOccurrences( … … 497 500 RRULE:FREQ=WEEKLY;INTERVAL=1 498 501 END:VEVENT""" 499 text = insert_event_textually(text, new_event) 500 self._calendar.import_(text) 502 text = insert_event_textually(text, new_event) 503 self._calendar.import_(text) 501 504 occurrences = self._calendar.getOccurrences( 502 505 (datetime(2005, 3, 1), … … 522 525 RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,FR 523 526 END:VEVENT""" 524 text = insert_event_textually(text, new_event) 525 self._calendar.import_(text) 527 text = insert_event_textually(text, new_event) 528 self._calendar.import_(text) 526 529 occurrences = self._calendar.getOccurrences( 527 530 (datetime(2005, 3, 1), … … 538 541 self.assertEquals(datetime(2005, 4, 25, 16), occurrences[7].dtstart) 539 542 self.assertEquals(datetime(2005, 4, 29, 16), occurrences[8].dtstart) 540 543 541 544 def test_monthly_recurrence_simple(self): 542 545 text = self._calendar.export() … … 551 554 RRULE:FREQ=MONTHLY;INTERVAL=1 552 555 END:VEVENT""" 553 text = insert_event_textually(text, new_event) 556 text = insert_event_textually(text, new_event) 554 557 self._calendar.import_(text) 555 558 occurrences = self._calendar.getOccurrences( … … 576 579 # the start of the month, or the end of the month if the 577 580 # BYDAY starts with -. 578 text = insert_event_textually(text, new_event) 581 text = insert_event_textually(text, new_event) 579 582 self._calendar.import_(text) 580 583 occurrences = self._calendar.getOccurrences( … … 608 611 # original event is used instead, starting to count from 609 612 # the end the month 610 text = insert_event_textually(text, new_event) 613 text = insert_event_textually(text, new_event) 611 614 self._calendar.import_(text) 612 615 occurrences = self._calendar.getOccurrences( … … 634 637 UID:hoi 635 638 END:VEVENT""" 636 text = insert_event_textually(text, new_event) 639 text = insert_event_textually(text, new_event) 637 640 self._calendar.import_(text) 638 641 occurrences = self._calendar.getOccurrences( … … 646 649 timedelta(1), 647 650 occurrences[0].duration) 648 651 649 652 def test_no_duration_no_dtend_datetime(self): 650 653 # when dtstart is a DATETIME, and dtend is absent, dtend is same … … 658 661 UID:hoi 659 662 END:VEVENT""" 660 text = insert_event_textually(text, new_event) 663 text = insert_event_textually(text, new_event) 661 664 self._calendar.import_(text) 662 665 occurrences = self._calendar.getOccurrences( … … 670 673 timedelta(0), 671 674 occurrences[0].duration) 672 675 673 676 class AllDayImportExportTestCase(unittest.TestCase): 674 677 def setUp(self): … … 721 724 self.assertEquals(timedelta(days=1), 722 725 event.duration) 723 726 724 727 def test_allday_export_two_days(self): 725 728 meeting = self._martijn.createEvent( … … 742 745 self.assertEquals(timedelta(days=2), 743 746 event.duration) 744 747 745 748 def insert_event_textually(text, event_text): 746 749 # correct newline story … … 751 754 # insert just before it 752 755 result = text[:i] + event_text + text[i:] 753 return result 756 return result 754 757 755 758 def insert_lines_textually(text, after, lines): … … 761 764 return result 762 765 763 766 764 767 def test_suite(): 765 768 suite = unittest.TestSuite()
