Using Django with groupby

41 views
Skip to first unread message

Will Holmes

unread,
Apr 20, 2017, 11:35:36 AM4/20/17
to Django users
Hi guys,

I am using django to develop a calendar web app where users can create events that are then displayed on a month by month view.

I have written a custom template tag that takes Python's HTMLCalendar formatmonth function and overlays events that are on that day. 

It is currently set up to take the start date of the event directly from each event and then use groupby to group each event by the day of the month in a dictionary. These can then be overlaid on the calendar. This, however, is not particularly useful if an event occurs over multiple days. 

I want to find a way of taking each of the days the event occurs over, start date to end date, before returning a similar dictionary. However, I cannot seem to get this to work whichever way I try it. Here is the group_by_day function that currently groups the events by start date:

#Here is where the code groups events for the relevant month day
def group_by_day(self, events):
    field
= lambda event: event.start_date.day
   
return dict(
       
[(day, list(items)) for day, items in groupby(events, field)]
   
)


Can anyone suggest a way round this problem?

If you need any more information please let me know. The project can be viewed in entirety at https://github.com/Willrholmes01/organiser.

Below is the entire template tag:

register = template.Library()

def do_month_calendarify(parser, token):
   
# Take the tag input from the template and format
   
# Template syntax is {% calendarify year month %}
   
try:
        tag_name
, year, month, event_list = token.split_contents()
   
except ValueError:
       
raise template.TemplateSyntaxError(
           
"%r tag requires three arguments" % token.contents.split()[0]
       
)
   
return CalendarifyNode(year, month, event_list)

class CalendarifyNode(template.Node):

   
def __init__(self, year, month, event_list):
       
try:
           
self.year = template.Variable(year)
           
self.month = template.Variable(month)
           
self.event_list = template.Variable(event_list)
       
except ValueError:
           
raise template.TemplateSyntaxError

   
def render(self, context):
       
try:
            my_year
= self.year.resolve(context)
            my_month
= self.month.resolve(context)
            my_event_list
= self.event_list.resolve(context)
            cal
= EventCalendar(my_event_list)
           
return cal.formatmonth(
               
int(my_year), int(my_month))
       
except ValueError:
           
return "%s, %s, %s" % (my_month, my_year, my_event_list)

class EventCalendar(HTMLCalendar):
   
# Use Python's HTMLCalendar and put user events over top
   
def __init__(self, events):
       
super(EventCalendar, self).__init__()
       
self.events = self.group_by_day(events)

   
def formatday(self, day, weekday):
       
if day != 0:
            cssid
= self.cssclasses[weekday]
            cssclass
= "daybox"
           
if date.today() == date(self.year, self.month, day):
                cssid
+= ' today'
           
if day in self.events:
                cssid
+= ' filled'
                body
= ['<ul>']
               
for event in self.events[day]:
                    body
.append('<li>')
                    body
.append('<a id="event_%d" href="%s">'
                                   
% (event.id, event.get_absolute_url()))
                    body
.append(esc(event.title))
                    body
.append('</a></li>')
                body
.append('</ul>')
               
return self.day_cell(
                    cssclass
, cssid, '<span class="dayNumber">%d</span> %s' % (
                        day
, ''.join(body)))
           
return self.day_cell(
                cssclass
, cssid, '<span class="dayNumberNoReadings">%d</span>' % (day))
       
return self.day_cell('nodaybox', 'noday', '&nbsp;')

   
def formatmonth(self, year, month):
       
self.year, self.month = year, month
       
return super(EventCalendar, self).formatmonth(year, month)

   
#Here is where the code groups events for the relevant month day
   
def group_by_day(self, events):
        field
= lambda event: event.start_date.day
       
return dict(
           
[(day, list(items)) for day, items in groupby(events, field)]
       
)

   
def day_cell(self, cssclass, cssid, body):
       
return '<td class="%s" id="%s">%s</td>' % (cssclass, cssid, body)

register.tag('calendarify', do_month_calendarify)






Will Holmes

unread,
Apr 21, 2017, 4:01:07 PM4/21/17
to Django users
Fixed this by just not using groupby. So far I just have a slightly long-winded work around that formats the data in the same way as groupby but includes all days in in between, and including, the start date and end date.

def group_by_day(self, events, month):
        return_dict = {}
        for event in events:
            if event.end_date != None:
                number_days = event.end_date - event.start_date
                day_range = [event.start_date + timedelta(
                    days=i) for i in range(number_days.days + 1)]
                for date in day_range:
                    if date.month == month:
                        if date.day in return_dict:
                            return_dict[date.day].append(event)
                        else:
                            return_dict[date.day] = [event]
            else:
                if event.start_date.day in return_dict:
                    return_dict[event.start_date.day].append(event)
                else:
                    return_dict[event.start_date.day] = [event]
        return return_dict
Reply all
Reply to author
Forward
0 new messages