summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--requirements.txt4
-rw-r--r--sublab_project/calendarium/admin.py20
-rw-r--r--sublab_project/calendarium/calendarium.py77
-rw-r--r--sublab_project/calendarium/fixtures/testing.json20
-rw-r--r--sublab_project/calendarium/migrations/0001_initial.py41
-rw-r--r--sublab_project/calendarium/migrations/__init__.py0
-rw-r--r--sublab_project/calendarium/models.py30
-rw-r--r--sublab_project/calendarium/tasks.py82
-rw-r--r--sublab_project/calendarium/tests.py55
-rw-r--r--sublab_project/settings.py9
10 files changed, 251 insertions, 87 deletions
diff --git a/requirements.txt b/requirements.txt
index 7d55e8a..05683b0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,3 +5,7 @@ creole==1.2
PIL==1.1.7
django-auth-ldap==1.0.14
python-ldap==2.4.6
+icalendar==2.2
+python-dateutil==1.5
+django-celery==2.4.2
+redis==2.4.10
diff --git a/sublab_project/calendarium/admin.py b/sublab_project/calendarium/admin.py
new file mode 100644
index 0000000..f94a34d
--- /dev/null
+++ b/sublab_project/calendarium/admin.py
@@ -0,0 +1,20 @@
+from django.contrib import admin
+
+from models import Event
+
+
+class EventAdmin(admin.ModelAdmin):
+ list_display = ('name', 'start', 'end', 'source')
+ list_filter = ('source',)
+ search_fields = ['name', 'description']
+ date_hierachy = 'start'
+
+ def save_model(self, request, obj, form, change):
+ """Sets the source to "admin".
+ """
+ if not obj.id:
+ obj.source = Event.SOURCE_ADMIN
+ obj.save()
+
+
+admin.site.register(Event, EventAdmin)
diff --git a/sublab_project/calendarium/calendarium.py b/sublab_project/calendarium/calendarium.py
deleted file mode 100644
index 9935ace..0000000
--- a/sublab_project/calendarium/calendarium.py
+++ /dev/null
@@ -1,77 +0,0 @@
-import icalendar
-from dateutil.rrule import rrulestr
-
-
-class Event(object):
- def __init__(self, name, description, start, end):
- self.name = name
- self.description = description
- self.start = start
- self.end = end
-
- def __repr__(self):
- return '<%s.%s (name=%s, start=%s, end=%s)>' % (
- __name__, self.__class__.__name__,
- repr(self.name), repr(self.start), repr(self.end))
-
-
-class Calendarium(object):
- def __init__(self, string):
- """
- Loads a calendar from the string and provides a nice API to it.
- """
- self.calendar = icalendar.Calendar.from_string(string)
-
- def get_events(self, after, before):
- """
- This functions yields a list of events in the calendar.
-
- Only events after (including) after will be shown. Recurring
- events til before (inclusively) will be shown.
- """
-
- for event in self.calendar.walk('vevent'):
- event_fields = [
- # dest , src, default
- ('name', 'summary', 'unknown Event'),
- ('description', 'description', ''),
- ]
-
- event_info = {}
- for fieldinfo in event_fields:
- try:
- event_info[fieldinfo[0]] = str(event[fieldinfo[1]])
- except KeyError:
- event_info[fieldinfo[0]] = fieldinfo[2]
-
- start = icalendar.vDatetime.from_ical(event['dtstart'].ical())
- end = icalendar.vDatetime.from_ical(event['dtend'].ical())
-
- if 'rrule' in event:
- rrule = rrulestr(event['rrule'].ical(), dtstart=start)
- duration = end - start
-
- for occurence in rrule.between(after, before, True):
- yield Event(
- start=occurence,
- end=occurence + duration,
- **event_info)
- else:
- if start >= after:
- yield Event(start=start, end=end, **event_info)
-
-if __name__ == '__main__':
- # simple example
-
- from dateutil.relativedelta import relativedelta
- from datetime import datetime
- import urllib2
-
- response = urllib2.urlopen('https://sublab.org:5232/calendars/events')
- calendar = Calendarium(response.read())
- now = datetime.now()
- events = list(calendar.get_events(now-relativedelta(days=1), now+relativedelta(months=+2)))
- events.sort(key=lambda x:x.start)
-
- for event in events:
- print event
diff --git a/sublab_project/calendarium/fixtures/testing.json b/sublab_project/calendarium/fixtures/testing.json
new file mode 100644
index 0000000..0af16c0
--- /dev/null
+++ b/sublab_project/calendarium/fixtures/testing.json
@@ -0,0 +1,20 @@
+[
+ {
+ "pk": 1,
+ "model": "auth.user",
+ "fields": {
+ "username": "admin",
+ "first_name": "",
+ "last_name": "",
+ "is_active": true,
+ "is_superuser": true,
+ "is_staff": true,
+ "last_login": "2012-01-04 13:02:11",
+ "groups": [],
+ "user_permissions": [],
+ "password": "sha1$b4c7a$379f348659ad7aaf21b8d04fa891d81c3a607f69",
+ "email": "admin@example.com",
+ "date_joined": "2011-12-29 03:18:29"
+ }
+ }
+]
diff --git a/sublab_project/calendarium/migrations/0001_initial.py b/sublab_project/calendarium/migrations/0001_initial.py
new file mode 100644
index 0000000..2a1f0a0
--- /dev/null
+++ b/sublab_project/calendarium/migrations/0001_initial.py
@@ -0,0 +1,41 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding model 'Event'
+ db.create_table('calendarium_event', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('description', self.gf('django.db.models.fields.TextField')(blank=True)),
+ ('start', self.gf('django.db.models.fields.DateTimeField')()),
+ ('end', self.gf('django.db.models.fields.DateTimeField')()),
+ ('source', self.gf('django.db.models.fields.CharField')(default='import', max_length=10)),
+ ))
+ db.send_create_signal('calendarium', ['Event'])
+
+
+ def backwards(self, orm):
+
+ # Deleting model 'Event'
+ db.delete_table('calendarium_event')
+
+
+ models = {
+ 'calendarium.event': {
+ 'Meta': {'ordering': "['-start']", 'object_name': 'Event'},
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'end': ('django.db.models.fields.DateTimeField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'source': ('django.db.models.fields.CharField', [], {'default': "'import'", 'max_length': '10'}),
+ 'start': ('django.db.models.fields.DateTimeField', [], {})
+ }
+ }
+
+ complete_apps = ['calendarium']
diff --git a/sublab_project/calendarium/migrations/__init__.py b/sublab_project/calendarium/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sublab_project/calendarium/migrations/__init__.py
diff --git a/sublab_project/calendarium/models.py b/sublab_project/calendarium/models.py
index 71a8362..1cd0665 100644
--- a/sublab_project/calendarium/models.py
+++ b/sublab_project/calendarium/models.py
@@ -1,3 +1,31 @@
from django.db import models
-# Create your models here.
+
+class Event(models.Model):
+ """A calendar event.
+ """
+ SOURCE_ADMIN = 'admin'
+ SOURCE_IMPORT = 'import'
+ name = models.CharField('Name', max_length=255)
+ description = models.TextField('Beschreibung', blank=True)
+ start = models.DateTimeField('Beginn')
+ end = models.DateTimeField('Ende')
+ source = models.CharField('Quelle', max_length=10, editable=False,
+ default=SOURCE_IMPORT)
+
+ class Meta:
+ verbose_name = 'Termin'
+ verbose_name_plural = 'Termine'
+ ordering = ['start', 'end', 'name']
+
+ def __unicode__(self):
+ return '%s (%s - %s)' % (self.name,
+ self.start.strftime('%d.%m.%Y %T'),
+ self.end.strftime('%d.%m.%Y %T'))
+
+ def clean(self):
+ """Checks if the start date is lower than the end date.
+ """
+ from django.core.exceptions import ValidationError
+ if self.start > self.end:
+ raise ValidationError('Der Beginn darf nicht vor dem Ende liegen.')
diff --git a/sublab_project/calendarium/tasks.py b/sublab_project/calendarium/tasks.py
new file mode 100644
index 0000000..9174105
--- /dev/null
+++ b/sublab_project/calendarium/tasks.py
@@ -0,0 +1,82 @@
+"""Celery tasks.
+"""
+from datetime import datetime, timedelta
+from dateutil.relativedelta import relativedelta
+import urllib2
+
+from celery.task import PeriodicTask
+from django.conf import settings
+from dateutil.rrule import rrulestr
+import icalendar
+
+from models import Event
+
+
+class CalendariumImport(PeriodicTask):
+ """Periodic task for importing calendar events.
+ """
+ run_every = timedelta(hours=1)
+ ignore_result = True
+
+ def flush(self):
+ """Flushes all previously imported events.
+ """
+ Event.objects.filter(source=Event.SOURCE_IMPORT).delete()
+ self.logger.info('Flushed all previously imported events.')
+
+ def fetch_calendar(self):
+ """Fetches the calendar events and returns a icalendar.Calendar instance.
+ """
+ response = urllib2.urlopen(settings.CALENDARIUM_IMPORT_URL)
+ self.logger.info('Fetched calendar from %s' % settings.CALENDARIUM_IMPORT_URL)
+ return icalendar.Calendar.from_string(response.read())
+
+ def get_events(self, calendar, after, before):
+ """Yields all events in calendar as dictionary.
+
+ Only events after (including) after will be shown. Recurring events
+ til before (inclusively) will be shown.
+ """
+ for event in calendar.walk('vevent'):
+ event_fields = [
+ # dest, src, default
+ ('name', 'summary', 'unknown Event'),
+ ('description', 'description', ''),
+ ]
+ event_info = {}
+ for fieldinfo in event_fields:
+ try:
+ event_info[fieldinfo[0]] = str(event[fieldinfo[1]])
+ except KeyError:
+ event_info[fieldinfo[0]] = fieldinfo[2]
+ start = icalendar.vDatetime.from_ical(event['dtstart'].ical())
+ end = icalendar.vDatetime.from_ical(event['dtend'].ical())
+ if 'rrule' in event:
+ rrule = rrulestr(event['rrule'].ical(), dtstart=start)
+ duration = end - start
+ for occurence in rrule.between(after, before, True):
+ event_info['start'] = occurence
+ event_info['end'] = occurence + duration
+ yield event_info
+ else:
+ if start >= after:
+ event_info['start'] = start
+ event_info['end'] = end
+ yield event_info
+
+ def run(self, **kwargs):
+ """Imports all events.
+ """
+ self.logger = self.get_logger(**kwargs)
+ now = datetime.now()
+ after = now - relativedelta(days=1)
+ before = now + relativedelta(months=+2)
+ self.logger.info('Importing events from %s to %s' % (after, before))
+ try:
+ calendar = self.fetch_calendar()
+ self.flush()
+ for event_info in self.get_events(calendar, after, before):
+ event = Event.objects.create(**event_info)
+ self.logger.info('Added event "%s".' % event)
+ except urllib2.URLError, error:
+ self.logger.error(error)
diff --git a/sublab_project/calendarium/tests.py b/sublab_project/calendarium/tests.py
index 501deb7..0ad8c06 100644
--- a/sublab_project/calendarium/tests.py
+++ b/sublab_project/calendarium/tests.py
@@ -1,16 +1,53 @@
+"""Tests for Event model and EventAdmin.
"""
-This file demonstrates writing tests using the unittest module. These will pass
-when you run "manage.py test".
-
-Replace this with more appropriate tests for your application.
-"""
+from datetime import datetime, timedelta
+from django.core.exceptions import ValidationError
from django.test import TestCase
+from models import Event
+
+
+class EventTest(TestCase):
+ """Event model tests.
+ """
+ def test_source_default(self):
+ """Tests if the source field default is set to "import".
+ """
+ event = Event(name='Test', start=datetime.now(), end=datetime.now())
+ self.assertEqual(event.source, Event.SOURCE_IMPORT)
+
+ def test_start_end(self):
+ """Tests if an ValidationError is raised if start is lower than end.
+ """
+ start = datetime.now()
+ end = start - timedelta(1)
+ event = Event(name='Test', start=start, end=end)
+ self.assertRaises(ValidationError, event.full_clean)
+
-class SimpleTest(TestCase):
- def test_basic_addition(self):
+class EventAdminTest(TestCase):
+ """EventAdmin tests.
+ """
+ fixtures = ['testing.json']
+
+ def setUp(self):
+ """Performs the login with the client.
"""
- Tests that 1 + 1 always equals 2.
+ result = self.client.login(username='admin', password='admin')
+ self.assertTrue(result, 'Login failed.')
+
+ def test_source(self):
+ """Tests if the source field is set to "admin" when saved with the admin.
"""
- self.assertEqual(1 + 1, 2)
+ name = 'Test source admin'
+ data = {
+ 'name': name,
+ 'start_0': '2012-01-01',
+ 'start_1': '12:00:00',
+ 'end_0': '2012-01-01',
+ 'end_1': '13:00:00',
+ }
+ self.client.post('/admin/calendarium/event/add/', data=data)
+ event = Event.objects.get(name=name)
+ self.assertEqual(event.source, Event.SOURCE_ADMIN)
diff --git a/sublab_project/settings.py b/sublab_project/settings.py
index d35e5c7..e52008a 100644
--- a/sublab_project/settings.py
+++ b/sublab_project/settings.py
@@ -133,6 +133,8 @@ INSTALLED_APPS = (
'gunicorn',
'news',
'projects',
+ 'djcelery',
+ 'calendarium',
)
# A sample logging configuration. The only tangible logging
@@ -189,6 +191,13 @@ AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
)
+import djcelery
+djcelery.setup_loader()
+
+BROKER_URL = 'redis://localhost:6379/0'
+
+CALENDARIUM_IMPORT_URL = 'https://sublab.org:5232/calendars/events'
+
try:
from local_settings import *
except ImportError: