Пример #1
0
def event(slug):
    """View a specific non-recurring event, or the next upcoming instance of
    a recurring event.

    **Route:** ``/events/<slug>``

    **Methods:** ``GET``

    :param str slug: The unique slug ID for the post.
    """
    if Event.objects(published=True, slug=slug).count() == 0:
        abort(404)  # Either invalid event ID or duplicate IDs.

    event = Event.objects(published=True, slug=slug)[0]

    if event.is_recurring:
        upcoming_event_instances = (Event.objects(published=True,
                                                  start_date__gte=date.today(),
                                                  slug=slug)
                                         .order_by('start_date'))
        if upcoming_event_instances:
            event = upcoming_event_instances[0]
        else:
            event = event.parent_series.events[-1]

    return render_template('events/event.html',
                           event=event,
                           now=now,
                           upcoming_events=_upcoming_events_triple(event))
Пример #2
0
def recurring_event(slug, index):
    """View a specific instance of a recurring event.

    **Route:** ``/events/<slug>/<index>``

    **Methods:** ``GET``

    :param str slug: The unique slug ID for the post.
    :param int index: The instance of the event to fetch.
    """
    if Event.objects(published=True, slug=slug).count() == 0:
        abort(404)  # Either invalid event ID or duplicate IDs.

    event = Event.objects(published=True, slug=slug)[0]

    if not event.is_recurring or not event.parent_series:
        return redirect(url_for('.event', slug=slug))

    if len(event.parent_series.events) <= index:
        abort(404)

    event = event.parent_series.events[index]
    return render_template('events/event.html',
                           event=event,
                           now=now,
                           upcoming_events=_upcoming_events_triple(event))
Пример #3
0
def recurring_event(slug, index):
    """View a specific instance of a recurring event.

    **Route:** ``/events/<slug>/<index>``

    **Methods:** ``GET``

    :param str slug: The unique slug ID for the post.
    :param int index: The instance of the event to fetch.
    """
    if Event.objects(published=True, slug=slug).count() == 0:
        abort(404)  # Either invalid event ID or duplicate IDs.

    event = Event.objects(published=True, slug=slug)[0]

    if not event.is_recurring or not event.parent_series:
        return redirect(url_for('.event', slug=slug))

    if len(event.parent_series.events) <= index:
        abort(404)

    event = event.parent_series.events[index]
    return render_template('events/event.html',
                           event=event,
                           now=now,
                           upcoming_events=_upcoming_events_triple(event))
Пример #4
0
def event(slug):
    """View a specific non-recurring event, or the next upcoming instance of
    a recurring event.

    **Route:** ``/events/<slug>``

    **Methods:** ``GET``

    :param str slug: The unique slug ID for the post.
    """
    if Event.objects(published=True, slug=slug).count() == 0:
        abort(404)  # Either invalid event ID or duplicate IDs.

    event = Event.objects(published=True, slug=slug)[0]

    if event.is_recurring:
        upcoming_event_instances = (Event.objects(published=True,
                                                  start_date__gte=date.today(),
                                                  slug=slug)
                                         .order_by('start_date'))
        if upcoming_event_instances:
            event = upcoming_event_instances[0]
        else:
            event = event.parent_series.events[-1]

    return render_template('events/event.html',
                           event=event,
                           now=now,
                           upcoming_events=_upcoming_events_triple(event))
Пример #5
0
def _get_events_for_template(past, future):
    """Returns the events to insert in the events template.  Returns four
    groups of dates:

    - ``past_events``: A list of dictionaries, where the dictionaries contain a
        list of events for a week, and a label for the week.
    - ``this_week``: A list of events happening this week.
    - ``next_week``: A list of events happening next week.
    - ``future_events``: A list of dictionaries similar to ``post_events``,
        but for events happening in the future.

    :returns: ``past_events``, ``this_week``, ``next_week``, ``future_events``
    """
    today = date.today()
    last_sunday = datetime.combine(
        today - timedelta(days=(today.isoweekday() % 7)),
        datetime.min.time()
    )
    next_sunday = last_sunday + timedelta(days=7)
    following_sunday = last_sunday + timedelta(days=14)

    this_week = (Event.objects(start_date__gte=last_sunday,
                               start_date__lt=next_sunday)
                 .order_by('start_date'))
    next_week = (Event.objects(start_date__gte=next_sunday,
                               start_date__lt=following_sunday)
                 .order_by('start_date'))
    past_events = []
    future_events = []

    for week_no in range(past):
        ending_sunday = last_sunday - timedelta(days=7 * week_no)
        starting_sunday = last_sunday - timedelta(days=7 * (week_no + 1))
        week_name = _format_for_display(starting_sunday)
        events = Event.objects(start_date__gte=starting_sunday,
                               start_date__lt=ending_sunday)
        past_events.insert(0, {
            'week_name': week_name,
            'events': events,
        })

    for week_no in range(future):
        starting_sunday = following_sunday + timedelta(days=7 * week_no)
        ending_sunday = following_sunday + timedelta(days=7 * (week_no + 1))
        week_name = _format_for_display(starting_sunday)
        events = Event.objects(start_date__gte=starting_sunday,
                               start_date__lt=ending_sunday)
        future_events.append({
            'week_name': week_name,
            'events': events,
        })

    return past_events, this_week, next_week, future_events
Пример #6
0
def event_archive(index):
    """View old events.

    **Route:** ``/events/<index>``

    **Methods:** ``GET``

    :param int index: The page to fetch
    """
    if index <= 0:
        return redirect(url_for('.events'))

    # Get all events that occur on this page or on subsequent pages, and order
    # them chronologically back in time
    today = date.today()
    events = (Event.objects(published=True, end_date__lt=today)
                   .order_by('-start_date')
                   .skip(NUM_PAST_EVENTS_FOR_FRONTPAGE +
                         (index - 1) * NUM_EVENTS_PER_PAGE))

    # If there are no such events, redirect to the pevious page
    if not events:
        return redirect(url_for('.event_archive', index=index - 1))

    # There is always a previous page, but there is only a next page if there
    # are more events after this page
    previous_index = index - 1
    next_index = index + 1 if len(events) > NUM_EVENTS_PER_PAGE else None

    # Use .limit() to only show NUM_EVENTS_PER_PAGE events per page
    return render_template('events/archive.html',
                           events=events.limit(NUM_EVENTS_PER_PAGE),
                           previous_index=previous_index,
                           next_index=next_index)
Пример #7
0
def event_archive(index):
    """View old events.

    **Route:** ``/events/<index>``

    **Methods:** ``GET``

    :param int index: The page to fetch
    """
    if index <= 0:
        return redirect(url_for('.events'))

    # Get all events that occur on this page or on subsequent pages, and order
    # them chronologically back in time
    today = date.today()
    events = (Event.objects(published=True, end_date__lt=today)
                   .order_by('-start_date')
                   .skip(NUM_PAST_EVENTS_FOR_FRONTPAGE +
                         (index - 1) * NUM_EVENTS_PER_PAGE))

    # If there are no such events, redirect to the pevious page
    if not events:
        return redirect(url_for('.event_archive', index=index - 1))

    # There is always a previous page, but there is only a next page if there
    # are more events after this page
    previous_index = index - 1
    next_index = index + 1 if len(events) > NUM_EVENTS_PER_PAGE else None

    # Use .limit() to only show NUM_EVENTS_PER_PAGE events per page
    return render_template('events/archive.html',
                           events=events.limit(NUM_EVENTS_PER_PAGE),
                           previous_index=previous_index,
                           next_index=next_index)
Пример #8
0
    def unpublish_event(self, stale_event):
        """Unpublish an event, moving it to the private calendar.

        The first argument is called ``stale_event`` because it might have
        outdated fields.  The first thing we do is find a fresh event with it's
        id in mongo.

        :param stale_event: The event to publish
        :type event: :class:`Event`

        :raises: :class:`EventumError.GCalAPI.BadStatusLine`,
            :class:`EventumError.GCalAPI.NotFound`,
            :class:`EventumError.GCalAPI.Error`,
            :class:`EventumError.GCalAPI.MissingID`

        :returns: The Google Calendar API response.
        :rtype: dict
        """
        self.before_request()

        # Freshen up stale_event
        event = Event.objects().get(id=stale_event.id)

        if event.published:
            raise EventumError.GCalAPI.PublishFailed.PublishedTrue()

        return self.move_event(event,
                               from_id=self.public_calendar_id,
                               to_id=self.private_calendar_id)
Пример #9
0
def index():
    """View the ADI homepage.

    **Route:** ``/``

    **Methods:** ``GET``
    """
    # cast date.today() to a datetime
    today = datetime.combine(date.today(), datetime.min.time())

    # Ending on a future date, or today at a future time. The events should be
    # published, and should be chronological.
    # We limit to four events, one large event and one set of three events.
    events = (Event.objects(Q(end_date__gte=today))
                   .filter(published=True)
                   .order_by('start_date', 'start_time')
                   .limit(ONE_LARGE_AND_TRIPLE))

    # sort published posts chronologically back in time
    all_blog_posts = (BlogPost.objects(published=True)
                              .order_by('-date_published'))
    latest_blog_post = all_blog_posts[0] if all_blog_posts else None

    return render_template('index.html',
                           events=events,
                           blog_post=latest_blog_post)
Пример #10
0
def index():
    """View the ADI homepage.

    **Route:** ``/``

    **Methods:** ``GET``
    """
    this_moment = datetime.now().time()
    # cast date.today() to a datetime
    today = datetime.combine(date.today(), datetime.min.time())

    # Ending on a future date, or today at a future time. The events should be
    # published, and should be chronological.
    # We limit to four events, one large event and one set of three events.
    events = (Event.objects(Q(end_date__gte=today))
                            # |
                            # Q(end_date=today, end_time__gt=this_moment)) #
                   .filter(published=True)
                   .order_by('start_date', 'start_time')
                   .limit(ONE_LARGE_AND_TRIPLE))

    # sort published posts chronologically back in time
    all_blog_posts = (BlogPost.objects(published=True)
                              .order_by('-date_published'))
    latest_blog_post = all_blog_posts[0] if all_blog_posts else None

    return render_template('index.html',
                           events=events,
                           blog_post=latest_blog_post)
Пример #11
0
    def unpublish_event(self, stale_event):
        """Unpublish an event, moving it to the private calendar.

        The first argument is called ``stale_event`` because it might have
        outdated fields.  The first thing we do is find a fresh event with it's
        id in mongo.

        :param stale_event: The event to publish
        :type event: :class:`Event`

        :raises: :class:`EventumError.GCalAPI.BadStatusLine`,
            :class:`EventumError.GCalAPI.NotFound`,
            :class:`EventumError.GCalAPI.Error`,
            :class:`EventumError.GCalAPI.MissingID`

        :returns: The Google Calendar API response.
        :rtype: dict
        """
        self.before_request()

        # Freshen up stale_event
        event = Event.objects().get(id=stale_event.id)

        if event.published:
            raise EventumError.GCalAPI.PublishFailed.PublishedTrue()

        return self.move_event(event, from_id=self.public_calendar_id,
                               to_id=self.private_calendar_id)
Пример #12
0
def _upcoming_events_triple(event):
    """Returns a set of three upcoming events, excluding ``event``.

    :param event: The event to exclude
    :type event: :class:`~app.models.Event`
    :returns: The set of three events
    :rtype: Mongoengine.queryset
    """
    return (Event.objects(
        published=True, start_date__gte=date.today(),
        id__ne=event.id).order_by('start_date').limit(ONE_TRIPLE))
Пример #13
0
def delete(event_id):
    """Delete an existing event.

    **Route:** ``/admin/events/delete/<event_id>``

    **Methods:** ``POST``

    :param str event_id: The ID of the event to delete.
    """
    object_id = ObjectId(event_id)
    form = DeleteEventForm(request.form)
    if Event.objects(id=object_id).count() == 1:
        event = Event.objects().with_id(object_id)
        try:
            EventsHelper.delete_event(event, form)
        except EventumError.GCalAPI as e:
            flash(e.message, ERROR_FLASH)
    else:
        flash('Invalid event id', ERROR_FLASH)
    return redirect(url_for('.index'))
Пример #14
0
def set_published_status(event_id, status):
    """"""
    object_id = ObjectId(event_id)
    if Event.objects(id=object_id).count() == 1:
        event = Event.objects().with_id(object_id)
        if status != event.published:
            event.published = status
            # TODO Actually publish/unpublish the event here
            if event.published:
                event.date_published = datetime.now()
                flash('Event published', MESSAGE_FLASH)
            else:
                event.date_published = None
                flash('Event unpublished', MESSAGE_FLASH)
            event.save()
        else:
            flash("The event had not been published.  No changes made.",
                  MESSAGE_FLASH)
    else:
        flash('Invalid event id', ERROR_FLASH)
    return redirect(url_for('.index'))
Пример #15
0
def _upcoming_events_triple(event):
    """Returns a set of three upcoming events, excluding ``event``.

    :param event: The event to exclude
    :type event: :class:`~app.models.Event`
    :returns: The set of three events
    :rtype: Mongoengine.queryset
    """
    return (Event.objects(published=True,
                          start_date__gte=date.today(),
                          id__ne=event.id)
                 .order_by('start_date')
                 .limit(ONE_TRIPLE))
Пример #16
0
def events():
    """View the latest events.

    **Route:** ``/events``

    **Methods:** ``GET``
    """
    today = date.today()
    weekday = (today.isoweekday() % 7) + 1  # Sun: 1, Mon: 2, ... , Sat: 7
    last_sunday = datetime.combine(today - timedelta(days=weekday + 7),
                                   datetime.min.time())
    next_sunday = datetime.combine(today + timedelta(days=7 - weekday),
                                   datetime.min.time())
    recent_and_upcoming = Event.objects(published=True).order_by('start_date',
                                                                 'start_time')

    # Sort recent events chronologically backwards in time
    recent_events = (recent_and_upcoming.filter(end_date__lt=today)
                                        .order_by('-start_date')
                                        .limit(NUM_PAST_EVENTS_FOR_FRONTPAGE))

    events_this_week = list(
        recent_and_upcoming.filter(end_date__gte=today,
                                   start_date__lt=next_sunday)
    )

    # One large event, and one set of three small events
    upcoming_events = (recent_and_upcoming.filter(start_date__gt=next_sunday)
                                          .limit(ONE_LARGE_AND_TRIPLE))

    more_past_events = bool(Event.objects(published=True,
                                          start_date__lte=last_sunday).count())

    return render_template('events/events.html',
                           recent_events=recent_events,
                           events_this_week=events_this_week,
                           upcoming_events=upcoming_events,
                           more_past_events=more_past_events)
Пример #17
0
def events():
    """View the latest events.

    **Route:** ``/events``

    **Methods:** ``GET``
    """
    today = date.today()
    weekday = (today.isoweekday() % 7) + 1  # Sun: 1, Mon: 2, ... , Sat: 7
    last_sunday = datetime.combine(today - timedelta(days=weekday + 7),
                                   datetime.min.time())
    next_sunday = datetime.combine(today + timedelta(days=7 - weekday),
                                   datetime.min.time())
    recent_and_upcoming = Event.objects(published=True).order_by('start_date',
                                                                 'start_time')

    # Sort recent events chronologically backwards in time
    recent_events = (recent_and_upcoming.filter(end_date__lt=today)
                                        .order_by('-start_date')
                                        .limit(NUM_PAST_EVENTS_FOR_FRONTPAGE))

    events_this_week = list(
        recent_and_upcoming.filter(end_date__gte=today,
                                   start_date__lt=next_sunday)
    )

    # One large event, and one set of three small events
    upcoming_events = (recent_and_upcoming.filter(start_date__gt=next_sunday)
                                          .limit(ONE_LARGE_AND_TRIPLE))

    more_past_events = bool(Event.objects(published=True,
                                          start_date__lte=last_sunday).count())

    return render_template('events/events.html',
                           recent_events=recent_events,
                           events_this_week=events_this_week,
                           upcoming_events=upcoming_events,
                           more_past_events=more_past_events)
Пример #18
0
    def __call__(self, form, field):
        """Called internally by :mod:`wtforms` on validation of the field.

        :param form: The parent form
        :type form: :class:`Form`
        :param field: The field to validate
        :type field: :class:`Field`

        :raises: :class:`wtforms.validators.ValidationError`
        """
        from eventum.models import Event, EventSeries

        if EventSeries.objects(slug=field.data).count():
            raise ValidationError(self.message)
        if Event.objects(slug=field.data).count():
            raise ValidationError(self.message)
Пример #19
0
    def __call__(self, form, field):
        """Called internally by :mod:`wtforms` on validation of the field.

        :param form: The parent form
        :type form: :class:`Form`
        :param field: The field to validate
        :type field: :class:`Field`

        :raises: :class:`wtforms.validators.ValidationError`
        """
        from eventum.models import Event, EventSeries

        if EventSeries.objects(slug=field.data).count():
            raise ValidationError(self.message)
        if Event.objects(slug=field.data).count():
            raise ValidationError(self.message)
Пример #20
0
    def __call__(self, form, field):
        """Called internally by :mod:`wtforms` on validation of the field.

        :param form: The parent form
        :type form: :class:`Form`
        :param field: The field to validate
        :type field: :class:`Field`

        :raises: :class:`wtforms.validators.ValidationError`
        """
        from eventum.models import Event, EventSeries

        # If we change the slug, make sure the new slug doesn't exist
        if self.original.slug != field.data:
            if EventSeries.objects(slug=field.data).count():
                raise ValidationError(self.message)
            if Event.objects(slug=field.data).count():
                raise ValidationError(self.message)
Пример #21
0
    def __call__(self, form, field):
        """Called internally by :mod:`wtforms` on validation of the field.

        :param form: The parent form
        :type form: :class:`Form`
        :param field: The field to validate
        :type field: :class:`Field`

        :raises: :class:`wtforms.validators.ValidationError`
        """
        from eventum.models import Event, EventSeries

        # If we change the slug, make sure the new slug doesn't exist
        if self.original.slug != field.data:
            if EventSeries.objects(slug=field.data).count():
                raise ValidationError(self.message)
            if Event.objects(slug=field.data).count():
                raise ValidationError(self.message)
Пример #22
0
def events_this_week():
    """
    Get a json object containing information about all the events for the
    current week (Sunday to Sunday).

    **Route:** ``/admin/api/events/this_week

    **Methods:** ``GET``
    """

    today = date.today()
    last_sunday = datetime.combine(
        today - timedelta(days=(today.isoweekday() % 7)), datetime.min.time())
    next_tuesday = last_sunday + timedelta(days=9)
    events = Event.objects(start_date__gte=last_sunday,
                           start_date__lt=next_tuesday).order_by('start_date')
    event_dicts = [event.to_jsonifiable() for event in events]

    return json_success(event_dicts)
Пример #23
0
def events_this_week():
    """
    Get a json object containing information about all the events for the
    current week (Sunday to Sunday).

    **Route:** ``/admin/api/events/this_week

    **Methods:** ``GET``
    """

    today = date.today()
    last_sunday = datetime.combine(
        today - timedelta(days=(today.isoweekday() % 7)),
        datetime.min.time())
    next_sunday = last_sunday + timedelta(days=7)
    events = Event.objects(start_date__gte=last_sunday,
                           start_date__lt=next_sunday).order_by('start_date')
    event_dicts = [event.to_jsonifiable() for event in events]

    return json_success(event_dicts)
Пример #24
0
def index():
    """The homepage of Eventum. Shows the latest blog posts and events.

    **Route:** ``/admin/home``

    **Methods:** ``GET``
    """
    today = date.today()
    last_sunday = datetime.combine(
        today - timedelta(days=(today.isoweekday() % 7)),
        datetime.min.time())
    next_sunday = last_sunday + timedelta(days=7)

    this_week = (Event.objects(start_date__gt=last_sunday,
                               start_date__lt=next_sunday)
                 .order_by('start_date'))
    posts = BlogPost.objects().order_by('published', '-date_published')[:5]

    return render_template('eventum_home.html',
                           this_week=this_week,
                           recent_posts=posts)
Пример #25
0
def index():
    """The homepage of Eventum. Shows the latest blog posts and events.

    **Route:** ``/admin/home``

    **Methods:** ``GET``
    """
    today = date.today()
    last_sunday = datetime.combine(
        today - timedelta(days=(today.isoweekday() % 7)),
        datetime.min.time())
    next_sunday = last_sunday + timedelta(days=7)

    this_week = (Event.objects(start_date__gt=last_sunday,
                               start_date__lt=next_sunday)
                 .order_by('start_date'))
    posts = BlogPost.objects().order_by('published', '-date_published')[:5]

    return render_template('eventum_home.html',
                           this_week=this_week,
                           recent_posts=posts)
Пример #26
0
def edit(event_id):
    """Edit an existing event.

    **Route:** ``/admin/events/edit/<event_id>``

    **Methods:** ``GET, POST``

    :param str event_id: The ID of the event to edit.
    """

    try:
        event = Event.objects().get(id=event_id)
    except (DoesNotExist, ValidationError):
        flash('Cannot find event with id "{}"'.format(event_id), ERROR_FLASH)
        return redirect(url_for('.index'))

    if request.method == "POST":
        form = EditEventForm(event, request.form)
    else:
        form = EventsHelper.create_form(event, request)

    if form.validate_on_submit():
        try:
            EventsHelper.update_event(event, form)
        except EventumError.GCalAPI as e:
            flash(e.message, ERROR_FLASH)

        return redirect(url_for('.index'))
    if form.errors:
        for error in form.errors:
            for message in form.errors[error]:
                flash(message, ERROR_FLASH)

    delete_form = DeleteEventForm()
    upload_form = UploadImageForm()
    images = Image.objects()

    return render_template('eventum_events/edit.html', form=form, event=event,
                           delete_form=delete_form, upload_form=upload_form,
                           images=images)
Пример #27
0
    def update_event(self, stale_event, as_exception=False):
        """Updates the event in Google Calendar.

        The first argument is called ``stale_event`` because it might have
        outdated fields.  The first thing we do is find a fresh event with it's
        id in mongo.

        This method will fall back to creating a new event if we don't have
        reference to a ``gcal_id`` for the event, or if the update otherwise
        fails.

        :param stale_event: The event to update.
        :type stale_event: :class:`Event`
        :param bool as_exception: Whether or not this update should happen as
            an exception in a series.  Otherwise, series' will be updated in
            their entirety.

        :raises: :class:`EventumError.GCalAPI.BadStatusLine`,
            :class:`EventumError.GCalAPI.NotFound`,
            :class:`EventumError.GCalAPI.MissingID`

        :returns: The Google Calendar API response.
        :rtype: dict
        """
        self.before_request()

        # Freshen up stale_event
        event = Event.objects().get(id=stale_event.id)

        if not event.gcal_id:
            # If we don't have a reference if it's associate Google Calendar
            # ID, then create it fresh.  This raises still because it
            # *shouldn't* ever happen, but it does.
            self.create_event(stale_event)
            raise EventumError.GCalAPI.MissingID.UpdateFellBackToCreate()

        resource = None
        resource = GoogleCalendarResourceBuilder.event_resource(
            event, for_update=True)

        calendar_id = self._calendar_id_for_event(event)

        # If this update should be an exception to a series of events, then
        # we only want to update the instance id.  Otherwise, using the
        # ``event.gcal_id`` will update the entire series.
        event_id_for_update = event.gcal_id
        if as_exception:
            instance = self._instance_resource_for_event_in_series(event)
            instance.update(resource)
            resource = instance
            event_id_for_update = instance['id']

        current_app.logger.info('[GOOGLE_CALENDAR]: Update Event')
        request = self.service.events().update(calendarId=calendar_id,
                                               eventId=event_id_for_update,
                                               body=resource)

        # Send the request, falling back to update if it fails.
        try:
            updated_event = self._execute_request(request)
        except EventumError.GCalAPI.NotFound as e:
            self.create_event(event)
            raise EventumError.GCalAPI.NotFound.UpdateFellBackToCreate(e=e)

        # Update the Event with the latest info from the response.
        self._update_event_from_response(event, updated_event)

        # Return the Google Calendar response dict
        return updated_event
Пример #28
0
    def update_event(self, stale_event, as_exception=False):
        """Updates the event in Google Calendar.

        The first argument is called ``stale_event`` because it might have
        outdated fields.  The first thing we do is find a fresh event with it's
        id in mongo.

        This method will fall back to creating a new event if we don't have
        reference to a ``gcal_id`` for the event, or if the update otherwise
        fails.

        :param stale_event: The event to update.
        :type stale_event: :class:`Event`
        :param bool as_exception: Whether or not this update should happen as
            an exception in a series.  Otherwise, series' will be updated in
            their entirety.

        :raises: :class:`EventumError.GCalAPI.BadStatusLine`,
            :class:`EventumError.GCalAPI.NotFound`,
            :class:`EventumError.GCalAPI.MissingID`

        :returns: The Google Calendar API response.
        :rtype: dict
        """
        self.before_request()

        # Freshen up stale_event
        event = Event.objects().get(id=stale_event.id)

        if not event.gcal_id:
            # If we don't have a reference if it's associate Google Calendar
            # ID, then create it fresh.  This raises still because it
            # *shouldn't* ever happen, but it does.
            self.create_event(stale_event)
            raise EventumError.GCalAPI.MissingID.UpdateFellBackToCreate()

        resource = None
        resource = GoogleCalendarResourceBuilder.event_resource(
            event, for_update=True)

        calendar_id = self._calendar_id_for_event(event)

        # If this update should be an exception to a series of events, then
        # we only want to update the instance id.  Otherwise, using the
        # ``event.gcal_id`` will update the entire series.
        event_id_for_update = event.gcal_id
        if as_exception:
            instance = self._instance_resource_for_event_in_series(event)
            instance.update(resource)
            resource = instance
            event_id_for_update = instance['id']

        current_app.logger.info('[GOOGLE_CALENDAR]: Update Event')
        request = self.service.events().update(calendarId=calendar_id,
                                               eventId=event_id_for_update,
                                               body=resource)

        # Send the request, falling back to update if it fails.
        try:
            updated_event = self._execute_request(request)
        except EventumError.GCalAPI.NotFound as e:
            self.create_event(event)
            raise EventumError.GCalAPI.NotFound.UpdateFellBackToCreate(e=e)

        # Update the Event with the latest info from the response.
        self._update_event_from_response(event, updated_event)

        # Return the Google Calendar response dict
        return updated_event