linuxsoftware / ls.joyous

A calendar application for Wagtail
BSD 3-Clause "New" or "Revised" License
74 stars 35 forks source link

Merging events from two calendars... #42

Open skoontastic opened 3 years ago

skoontastic commented 3 years ago

I've been loving Joyous Calendar and Wagtail so much. It's just great software.

I have a main CalendarPage at the root of my site where I'd like to keep events that should display on all other calendars on the site. How would I reach back up to that calendar to display those events on a calendar that exists down the page tree? I'm using a custom class based on CalendarPage as my sub-calendars, so I tried to get the top-level CalendarPage and the merge the events, but I get a "Models aren't loaded yet." error.

Any ideas on this problem?

linuxsoftware commented 3 years ago

So, if I understand correctly your site is structured something like this?

Home-Calendar
+--- Sub-Calendar1
|    +--- Event1
|    +--- Event2
+--- Sub-Calendar2
     +--- Event3
     +--- Event4

Where: Event1 and Event2 should appear in Sub-Calendar1; Event3 and Event4 should appear in Sub-Calendar2; And all the events 1,2,3,4 should appear in Home-Calendar?

Joyous was designed to support just this. You don't need to "reach up" to merge in events, instead the Calendars "reach down" to pull in events. (Hope that makes sense.)

Or derive your own version of CalendarPage with some other kind of event selection.

Hope that helps.

skoontastic commented 3 years ago

Sorry, I didn't explain myself very well. You have the structure correct, but I want to reach up so that Sub-Calendar1 and Sub-Calendar2 show their respective child events and the events from Home-Calendar. I'm actually using a derived CalendarPage for the "sub calendars" already, which I tried to use to pull in the Home-Calendar events, which is what brought up the "Models aren't loaded yet" error.

I'll make it more concrete. This is for a school district website. The top level calendar is for "All-district" events. Then under that there are individual school calendars:

District-Calendar
+---High School Calendar
|      +---Event1
|      +---Event2
+---Middle School Calendar
|      +---Event3
|      +---Event4

So each school calendar should show its events as well as any all-district events.

Thanks for the quick reply and for making some sweet software!

linuxsoftware commented 3 years ago

Thanks for the clarification. I guess I was just looking for the question I already had an answer for :-).

I think your approach in subclassing the school calendars is the correct one (more suggestions on that further down). I can't say why you are getting the "Models aren't loaded yet" error. You'll have seen it suggests that you are trying to use models before all the applications have loaded. If you can share the stack trace, I might be able to help further.

For ease of implementation, would the following structure still work for you?

HomePage (render the District-Calendar content)
+---District-Calendar (slug=district-calendar)
|      +---Event0
+---High School Calendar
|      +---Event1
|      +---Event2
+---Middle School Calendar
|      +---Event3
|      +---Event4

This would make it easier to identify what is an all-district event (e.g. Event0) and what events are specific to a school. The site could look the same to viewers, but editors would need to know where too look for the all-district events.

None of the code below is tested (will be buggy), but this is my idea of how the sub-class could be implemented. If I get time I will try and get a real example working.

It is an interesting exercise thinking how to extend Joyous like this. Thanks! I can see some possibilities for future changes to make this kind of thing simpler.

class MergedCalendarPage(ProxyPageMixin, CalendarPage):
    """
    MergedCalendarPage displays the events which are its children or the
    children of the merged page
    """
    class Meta(ProxyPageMixin.Meta):
        verbose_name = "merged calendar page"
        verbose_name_plural = "merged calendar pages"

    MERGE_IN_SLUG = "district-calendar"

    @classmethod
    def _allowAnotherAt(cls, parent):
        return True

    def _getEventsByDay(self, request, firstDay, lastDay):
        merged = Page.objects.get(slug=self.MERGE_IN_SLUG)
        evods = []
        myEvods = getAllEventsByDay(request, firstDay, lastDay,
                                    home=self, holidays=self.holidays)
        mergeEvods = getAllEventsByDay(request, firstDay, lastDay,
                                       home=merged, holidays=self.holidays)
        for myEvod, mergeEvod in zip(myEvods, mergeEvods):
            day = myEvod.date
            days_events = myEvod.days_events + mergeEvod.days_events
            continuing_events = myEvod.continuing_events + mergeEvod.continuing_events
            def sortByTime(thisEvent):
                fromTime = thisEvent.page._getFromTime(atDate=day)
                if fromTime is None:
                    fromTime = dt.time.max
                return fromTime
            days_events.sort(key=sortByTime)
            evods.append(EventsOnDay(day, myEvods.holiday, days_events, continuing_events))
        return evods

    def _getEventsByWeek(self, request, year, month):
        return _getEventsByWeek(year, month,
                                partial(self._getEventsByDay, request))

    def _getUpcomingEvents(self, request):
        merged = Page.objects.get(slug=self.MERGE_IN_SLUG)
        myEvents = getAllUpcomingEvents(request, home=self, holidays=self.holidays)
        mergeEvents = getAllUpcomingEvents(request, home=merged, holidays=self.holidays)
        events = sorted(myEvents + mergeEvents, key=_getUpcomingSort())
        return events

    def _getPastEvents(self, request):
        merged = Page.objects.get(slug=self.MERGE_IN_SLUG)
        myEvents = getAllPastEvents(request, home=self, holidays=self.holidays)
        mergeEvents = getAllPastEvents(request, home=merged, holidays=self.holidays)
        events = sorted(myEvents + mergeEvents,
                        key=attrgetter('page._past_datetime_from'), reverse=True)
        return events

    def _getEventFromUid(self, request, uid):
        merged = Page.objects.get(slug=self.MERGE_IN_SLUG)
        event = getEventFromUid(request, uid) # might raise exception
        if event.get_ancestors().filter(id__in=[self.id, merged.id]).exists():
            # only return event if it is a descendant
            return event

    def _getAllEvents(self, request):
        merged = Page.objects.get(slug=self.MERGE_IN_SLUG)
        myEvents = getAllEvents(request, home=self, holidays=self.holidays)
        mergeEvents = getAllEvents(request, home=merged, holidays=self.holidays)
        events = myEvents + mergeEvents
        return events

class HomePage(Page):
    """HomePage displays the District-Calendar."""
    subpage_types = ['ls.joyous.SpecificCalendarPage']
    MERGE_IN_SLUG = "district-calendar"

    def route(self, request, path_components):
        subpage = Page.objects.get(slug=self.MERGE_IN_SLUG)
        try:
             return subpage.specific.route(request, path_components)
        except Http404:
            pass
        return super().route(request, path_components)
skoontastic commented 3 years ago

Awesome, I will dig into this and let you know what happens. Hopefully it can be useful to you to expand the project.