Django is a well-known web framework, written in Python, which comes packaged with lots of out of the box features, such as: - An ORM (Object-relational mapping) where you define your data models and get access to a high-level API that lets you manage your data, instead of writing raw SQL. - A template system for writing the front end, extending the basic HTML functionality. - An admin panel where you can manage your data models. - Security measures. - Authentication, Form handling and URL routing.
This speeds up your development, since the most basic and common tasks are already provided to you and ready to be used, letting you focus on the logic that makes your product unique.
However, our current model does not have any validations, such as overlapping events. We can add these kind of validations in the clean() method of our model, which iterates over our events and checks if there are collisions.
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse classEvent(models.Model): day = models.DateField(u'Day of the event', help_text=u'Day of the event') start_time = models.TimeField(u'Starting time', help_text=u'Starting time') end_time = models.TimeField(u'Final time', help_text=u'Final time') notes = models.TextField(u'Textual Notes', help_text=u'Textual Notes', blank=True, null=True) classMeta: verbose_name = u'Scheduling' verbose_name_plural = u'Scheduling' defcheck_overlap(self, fixed_start, fixed_end, new_start, new_end): overlap = False if new_start == fixed_end or new_end == fixed_start: #edge case overlap = False elif (new_start >= fixed_start and new_start <= fixed_end) or (new_end >= fixed_start and new_end <= fixed_end): #innner limits overlap = True elif new_start <= fixed_start and new_end >= fixed_end: #outter limits overlap = True return overlap defget_absolute_url(self): url = reverse('admin:%s_%s_change' % (self._meta.app_label, self._meta.model_name), args=[self.id]) returnu'<a href="%s">%s</a>' % (url, str(self.start_time)) defclean(self): if self.end_time <= self.start_time: raise ValidationError('Ending times must after starting times') events = Event.objects.filter(day=self.day) if events.exists(): for event in events: if self.check_overlap(event.start_time, event.end_time, self.start_time, self.end_time): raise ValidationError( 'There is an overlap with another event: ' + str(event.day) + ', ' + str( event.start_time) + '-' + str(event.end_time))
Our app now detects collisions.
Calendar
It would be nice to add a monthly view of our events. There are some third-party packages available, but for the sake of simplicity we will stick to the built-in HTMLCalendar class provided by python.
We first need to override the change_list.html admin template by creating a file in events/templates/admin/events/change_list.html with the exactly the same content installed in site-packages/django/contrib/admin/templates/admin/ .
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.contrib import admin from models import Event import datetime import calendar from django.core.urlresolvers import reverse from calendar import HTMLCalendar from django.utils.safestring import mark_safe # Register your models here. classEventAdmin(admin.ModelAdmin): list_display = ['day', 'start_time', 'end_time', 'notes'] change_list_template = 'admin/events/change_list.html' defchangelist_view(self, request, extra_context=None): after_day = request.GET.get('day__gte', None) extra_context = extra_context or {} ifnot after_day: d = datetime.date.today() else: try: split_after_day = after_day.split('-') d = datetime.date(year=int(split_after_day[0]), month=int(split_after_day[1]), day=1) except: d = datetime.date.today() previous_month = datetime.date(year=d.year, month=d.month, day=1) # find first day of current month previous_month = previous_month - datetime.timedelta(days=1) # backs up a single day previous_month = datetime.date(year=previous_month.year, month=previous_month.month, day=1) # find first day of previous month last_day = calendar.monthrange(d.year, d.month) next_month = datetime.date(year=d.year, month=d.month, day=last_day[1]) # find last day of current month next_month = next_month + datetime.timedelta(days=1) # forward a single day next_month = datetime.date(year=next_month.year, month=next_month.month, day=1) # find first day of next month extra_context['previous_month'] = reverse('admin:events_event_changelist') + '?day__gte=' + str( previous_month) extra_context['next_month'] = reverse('admin:events_event_changelist') + '?day__gte=' + str(next_month) cal = HTMLCalendar() html_calendar = cal.formatmonth(d.year, d.month, withyear=True) html_calendar = html_calendar.replace('<td ', '<td width="150" height="150"') extra_context['calendar'] = mark_safe(html_calendar) returnsuper(EventAdmin, self).changelist_view(request, extra_context) admin.site.register(Event, EventAdmin)
With this addition, our app now displays a monthly calendar.
However, our app still does not display the events in each cell. We can tweak the HTMLCalendar class by extending it and override the methods responsible for drawing the table cells. Our little tweak will be the introduction of the list of events and displaying them in their corresponding cell. We will now create a new class in utils.py .