class VCalendar(Calendar, VComponentMixin): prodVersion = ".".join(__version__.split(".", 2)[:2]) prodId = "-//linuxsoftware.nz//NONSGML Joyous v{}//EN".format(prodVersion) def __init__(self, page=None, utc2local=False): super().__init__(self) self.page = page self.utc2local = utc2local self.set('PRODID', self.prodId) self.set('VERSION', "2.0") @classmethod def fromPage(cls, page, request): if isinstance(page, CalendarPage): return cls._fromCalendarPage(page, request) elif isinstance(page, EventBase): return cls._fromEventPage(page) else: raise CalendarTypeError("Unsupported input page") @classmethod def _fromCalendarPage(cls, page, request): vcal = cls(page) vevents = [] tzs = {} for event in page._getAllEvents(request): vevent = cls.factory.makeFromPage(event) vevents.append(vevent) for vchild in vevent.vchildren: vevents.append(vchild) if event.tz and event.tz is not pytz.utc: tzs.setdefault(event.tz, TimeZoneSpan()).add(vevent) for tz, vspan in tzs.items(): vtz = vspan.createVTimeZone(tz) # Put timezones up top. The RFC doesn't require this, but everyone # else seems to. vcal.add_component(vtz) vcal.subcomponents.extend(vevents) return vcal @classmethod def _fromEventPage(cls, event): vcal = cls(cls._findCalendarFor(event)) vevent = cls.factory.makeFromPage(event) vcal.add_component(vevent) for vchild in vevent.vchildren: vcal.add_component(vchild) if event.tz and event.tz is not pytz.utc: vtz = TimeZoneSpan(vevent).createVTimeZone(event.tz) vcal.add_component(vtz) return vcal @classmethod def _findCalendarFor(cls, event): calendar = CalendarPage.objects.ancestor_of(event).first() if not calendar: site = event.get_site() if site: home = site.root_page calendar = CalendarPage.objects.descendant_of(home).first() if not calendar: calendar = CalendarPage.objects.first() return calendar def clear(self): super().clear() self.subcomponents.clear() def load(self, request, data, name=""): if self.page is None: raise CalendarNotInitializedError("No page set") # Typically, this information will consist of an iCalendar stream # with a single iCalendar object. However, multiple iCalendar # objects can be sequentially grouped together in an iCalendar # stream. try: calStream = Calendar.from_ical(data, multiple=True) except ValueError as e: messages.error(request, "Could not parse iCalendar file "+name) #messages.debug(request, str(e)) return self.clear() numSuccess = numFail = 0 for cal in calStream: tz = timezone.get_current_timezone() zone = cal.get('X-WR-TIMEZONE', None) if zone: try: tz = pytz.timezone(zone) except pytz.exceptions.UnknownTimeZoneError: messages.warning(request, "Unknown time zone {}".format(zone)) with timezone.override(tz): result = self._loadEvents(request, cal.walk(name="VEVENT")) numSuccess += result[0] numFail += result[1] if numSuccess: messages.success(request, "{} iCal events loaded".format(numSuccess)) if numFail: messages.error(request, "Could not load {} iCal events".format(numFail)) def _loadEvents(self, request, vevents): numSuccess = numFail = 0 vmap = {} for props in vevents: try: match = vmap.setdefault(str(props.get('UID')), VMatch()) vevent = self.factory.makeFromProps(props, match.parent) if self.utc2local: vevent._convertTZ() except CalendarTypeError as e: numFail += 1 #messages.debug(request, str(e)) else: self.add_component(vevent) match.add(vevent) for vmatch in vmap.values(): vevent = vmatch.parent if vevent is not None: try: event = self.page._getEventFromUid(request, vevent['UID']) except ObjectDoesNotExist: numSuccess += self._createEventPage(request, vevent) else: if event: numSuccess += self._updateEventPage(request, vevent, event) return numSuccess, numFail def _updateEventPage(self, request, vevent, event): numUpdated = 0 if vevent.modifiedDt > event.latest_revision_created_at: vevent.toPage(event) _saveRevision(request, event) numUpdated += 1 vchildren = vevent.vchildren[:] vchildren += [CancellationVEvent.fromExDate(vevent, exDate) for exDate in vevent.exDates] for vchild in vchildren: try: exception = vchild.Page.objects.child_of(event) \ .get(except_date=vchild['RECURRENCE-ID'].date()) except ObjectDoesNotExist: self._createExceptionPage(request, event, vchild) else: if exception.isAuthorized(request): self._updateExceptionPage(request, vchild, exception) return numUpdated def _updateExceptionPage(self, request, vchild, exception): if vchild.modifiedDt > exception.latest_revision_created_at: vchild.toPage(exception) _saveRevision(request, exception) def _createEventPage(self, request, vevent): event = vevent.makePage(uid=vevent['UID']) _addPage(request, self.page, event) _saveRevision(request, event) numCreated = 1 vchildren = vevent.vchildren[:] vchildren += [CancellationVEvent.fromExDate(vevent, exDate) for exDate in vevent.exDates] for vchild in vchildren: self._createExceptionPage(request, event, vchild) return numCreated def _createExceptionPage(self, request, event, vchild): exception = vchild.makePage(overrides=event) _addPage(request, event, exception) _saveRevision(request, exception)
# -- Project information ----------------------------------------------------- project = 'Joyous' copyright = '2019, Linuxsoftware' author = 'Linuxsoftware' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. from ls.joyous import __version__ # The short X.Y version version = '.'.join(__version__.split('.', 2)[:2]) # The full version, including alpha/beta/rc tags release = __version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ['_build']