Example #1
0
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)
Example #2
0

# -- 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']