├── events ├── __init__.py ├── migrations │ ├── __init__.py │ └── 0001_initial.py ├── tests.py ├── views.py ├── apps.py ├── utils.py ├── models.py ├── admin.py └── templates │ └── admin │ └── events │ └── change_list.html ├── .gitignore ├── mycalendar ├── __init__.py ├── wsgi.py ├── urls.py └── settings.py ├── requirements.txt ├── README.md └── manage.py /events/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /mycalendar/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /events/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | django>=1.11.19 2 | pytz==2017.2 3 | -------------------------------------------------------------------------------- /events/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.test import TestCase 5 | 6 | # Create your tests here. 7 | -------------------------------------------------------------------------------- /events/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.shortcuts import render 5 | 6 | # Create your views here. 7 | -------------------------------------------------------------------------------- /events/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.apps import AppConfig 5 | 6 | 7 | class EventsConfig(AppConfig): 8 | name = 'events' 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Django Calendar 2 | 3 | __Installing and running__ 4 | 5 | ```bash 6 | pip install -r requirements.txt 7 | python manage.py migrate 8 | python manage.py createsuperuser 9 | python manage.py runserver 10 | ``` 11 | 12 | Head over to [http:localhost:8000/admin](http:localhost:8000/admin) and sign in with your admin credential (defined above). -------------------------------------------------------------------------------- /mycalendar/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mycalendar project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mycalendar.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /mycalendar/urls.py: -------------------------------------------------------------------------------- 1 | """mycalendar URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url 17 | from django.contrib import admin 18 | 19 | urlpatterns = [ 20 | url(r'^admin/', admin.site.urls), 21 | ] 22 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mycalendar.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /events/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2017-07-13 22:22 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Event', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('day', models.DateField(help_text='Day of the event', verbose_name='Day of the event')), 21 | ('start_time', models.TimeField(help_text='Starting time', verbose_name='Starting time')), 22 | ('end_time', models.TimeField(help_text='Final time', verbose_name='Final time')), 23 | ('notes', models.TextField(blank=True, help_text='Textual Notes', null=True, verbose_name='Textual Notes')), 24 | ], 25 | options={ 26 | 'verbose_name': 'Scheduling', 27 | 'verbose_name_plural': 'Scheduling', 28 | }, 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /events/utils.py: -------------------------------------------------------------------------------- 1 | from calendar import HTMLCalendar 2 | from datetime import datetime as dtime, date, time 3 | import datetime 4 | from models import Event 5 | 6 | 7 | class EventCalendar(HTMLCalendar): 8 | def __init__(self, events=None): 9 | super(EventCalendar, self).__init__() 10 | self.events = events 11 | 12 | def formatday(self, day, weekday, events): 13 | """ 14 | Return a day as a table cell. 15 | """ 16 | events_from_day = events.filter(day__day=day) 17 | events_html = "" 21 | 22 | if day == 0: 23 | return ' ' # day outside month 24 | else: 25 | return '%d%s' % (self.cssclasses[weekday], day, events_html) 26 | 27 | def formatweek(self, theweek, events): 28 | """ 29 | Return a complete week as a table row. 30 | """ 31 | s = ''.join(self.formatday(d, wd, events) for (d, wd) in theweek) 32 | return '%s' % s 33 | 34 | def formatmonth(self, theyear, themonth, withyear=True): 35 | """ 36 | Return a formatted month as a table. 37 | """ 38 | 39 | events = Event.objects.filter(day__month=themonth) 40 | 41 | v = [] 42 | a = v.append 43 | a('') 44 | a('\n') 45 | a(self.formatmonthname(theyear, themonth, withyear=withyear)) 46 | a('\n') 47 | a(self.formatweekheader()) 48 | a('\n') 49 | for week in self.monthdays2calendar(theyear, themonth): 50 | a(self.formatweek(week, events)) 51 | a('\n') 52 | a('
') 53 | a('\n') 54 | return ''.join(v) -------------------------------------------------------------------------------- /events/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models 5 | from django.core.exceptions import ValidationError 6 | from django.core.urlresolvers import reverse 7 | 8 | # Create your models here. 9 | 10 | class Event(models.Model): 11 | day = models.DateField(u'Day of the event', help_text=u'Day of the event') 12 | start_time = models.TimeField(u'Starting time', help_text=u'Starting time') 13 | end_time = models.TimeField(u'Final time', help_text=u'Final time') 14 | notes = models.TextField(u'Textual Notes', help_text=u'Textual Notes', blank=True, null=True) 15 | 16 | class Meta: 17 | verbose_name = u'Scheduling' 18 | verbose_name_plural = u'Scheduling' 19 | 20 | def check_overlap(self, fixed_start, fixed_end, new_start, new_end): 21 | overlap = False 22 | if new_start == fixed_end or new_end == fixed_start: #edge case 23 | overlap = False 24 | elif (new_start >= fixed_start and new_start <= fixed_end) or (new_end >= fixed_start and new_end <= fixed_end): #innner limits 25 | overlap = True 26 | elif new_start <= fixed_start and new_end >= fixed_end: #outter limits 27 | overlap = True 28 | 29 | return overlap 30 | 31 | def get_absolute_url(self): 32 | url = reverse('admin:%s_%s_change' % (self._meta.app_label, self._meta.model_name), args=[self.id]) 33 | return u'%s' % (url, str(self.start_time)) 34 | 35 | def clean(self): 36 | if self.end_time <= self.start_time: 37 | raise ValidationError('Ending hour must be after the starting hour') 38 | 39 | events = Event.objects.filter(day=self.day) 40 | if events.exists(): 41 | for event in events: 42 | if self.check_overlap(event.start_time, event.end_time, self.start_time, self.end_time): 43 | raise ValidationError( 44 | 'There is an overlap with another event: ' + str(event.day) + ', ' + str( 45 | event.start_time) + '-' + str(event.end_time)) 46 | -------------------------------------------------------------------------------- /events/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.contrib import admin 5 | from models import Event 6 | import datetime 7 | import calendar 8 | from django.core.urlresolvers import reverse 9 | from django.utils.safestring import mark_safe 10 | from utils import EventCalendar 11 | 12 | # Register your models here. 13 | 14 | class EventAdmin(admin.ModelAdmin): 15 | list_display = ['day', 'start_time', 'end_time', 'notes'] 16 | change_list_template = 'admin/events/change_list.html' 17 | 18 | def changelist_view(self, request, extra_context=None): 19 | after_day = request.GET.get('day__gte', None) 20 | extra_context = extra_context or {} 21 | 22 | if not after_day: 23 | d = datetime.date.today() 24 | else: 25 | try: 26 | split_after_day = after_day.split('-') 27 | d = datetime.date(year=int(split_after_day[0]), month=int(split_after_day[1]), day=1) 28 | except: 29 | d = datetime.date.today() 30 | 31 | previous_month = datetime.date(year=d.year, month=d.month, day=1) # find first day of current month 32 | previous_month = previous_month - datetime.timedelta(days=1) # backs up a single day 33 | previous_month = datetime.date(year=previous_month.year, month=previous_month.month, 34 | day=1) # find first day of previous month 35 | 36 | last_day = calendar.monthrange(d.year, d.month) 37 | next_month = datetime.date(year=d.year, month=d.month, day=last_day[1]) # find last day of current month 38 | next_month = next_month + datetime.timedelta(days=1) # forward a single day 39 | next_month = datetime.date(year=next_month.year, month=next_month.month, 40 | day=1) # find first day of next month 41 | 42 | extra_context['previous_month'] = reverse('admin:events_event_changelist') + '?day__gte=' + str( 43 | previous_month) 44 | extra_context['next_month'] = reverse('admin:events_event_changelist') + '?day__gte=' + str(next_month) 45 | 46 | cal = EventCalendar() 47 | html_calendar = cal.formatmonth(d.year, d.month, withyear=True) 48 | html_calendar = html_calendar.replace(' 7 | {% if cl.formset %} 8 | 9 | {% endif %} 10 | {% if cl.formset or action_form %} 11 | 12 | {% endif %} 13 | {{ media.css }} 14 | {% if not actions_on_top and not actions_on_bottom %} 15 | 18 | {% endif %} 19 | {% endblock %} 20 | 21 | {% block extrahead %} 22 | {{ block.super }} 23 | {{ media.js }} 24 | {% endblock %} 25 | 26 | {% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-list{% endblock %} 27 | 28 | 29 | {% if not is_popup %} 30 | {% block breadcrumbs %} 31 | 36 | {% endblock %} 37 | {% endif %} 38 | 39 | {% block coltype %}flex{% endblock %} 40 | 41 | {% block content %} 42 |
43 | 55 | 56 | 57 | {{ calendar }} 58 | {% block object-tools %} 59 | 71 | {% endblock %} 72 | {% if cl.formset.errors %} 73 |

74 | {% if cl.formset.total_error_count == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %} 75 |

76 | {{ cl.formset.non_form_errors }} 77 | {% endif %} 78 |
79 | {% block search %}{% search_form cl %}{% endblock %} 80 | {% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %} 81 | 82 | {% block filters %} 83 | {% if cl.has_filters %} 84 |
85 |

{% trans 'Filter' %}

86 | {% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %} 87 |
88 | {% endif %} 89 | {% endblock %} 90 | 91 |
{% csrf_token %} 92 | {% if cl.formset %} 93 |
{{ cl.formset.management_form }}
94 | {% endif %} 95 | 96 | {% block result_list %} 97 | {% if action_form and actions_on_top and cl.show_admin_actions %}{% admin_actions %}{% endif %} 98 | {% result_list cl %} 99 | {% if action_form and actions_on_bottom and cl.show_admin_actions %}{% admin_actions %}{% endif %} 100 | {% endblock %} 101 | {% block pagination %}{% pagination cl %}{% endblock %} 102 |
103 |
104 |
105 | {% endblock %} 106 | --------------------------------------------------------------------------------