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))
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))
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
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)
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)
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)
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)
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))
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'))
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'))
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))
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)
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)
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)
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)
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)
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)
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)
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