├── 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 = "
"
18 | for event in events_from_day:
19 | events_html += event.get_absolute_url() + "
"
20 | 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 |
103 |
104 |
105 | {% endblock %}
106 |
--------------------------------------------------------------------------------