Project

General

Profile

Download (7.95 KB) Statistics
| Branch: | Tag: | Revision:

from datetime import datetime, date, timedelta

from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.db import models
from django.conf import settings

from dateutil import rrule

from conf import default

__all__ = (
'Note',
'EventType',
'Event',
'Occurrence',
'create_event'
)

class Note(models.Model):
'''
A generic model for adding simple, arbitrary notes to other models such as
``Event`` or ``Occurrence``.
'''
note = models.TextField(_('note'))
created = models.DateTimeField(_('created'), auto_now_add=True)

content_type = models.ForeignKey(ContentType, verbose_name=_('content type'))
object_id = models.PositiveIntegerField(_('object id'))
content_object = generic.GenericForeignKey('content_type', 'object_id')

class Meta:
app_label = 'agenda'
verbose_name = _('note')
verbose_name_plural = _('notes')

def __unicode__(self):
return self.note


class EventType(models.Model):
'''
Simple ``Event`` classifcation.
'''
abbr = models.CharField(_(u'abbreviation'), max_length=4, unique=True)
label = models.CharField(_('label'), max_length=50)

class Meta:
app_label = 'agenda'
verbose_name = _('event type')
verbose_name_plural = _('event types')

def __unicode__(self):
return self.label


class Event(models.Model):
'''
Container model for general metadata and associated ``Occurrence`` entries.
'''

title = models.CharField(_('title'), max_length=32)
description = models.CharField(_('description'), max_length=100)
event_type = models.ForeignKey(EventType, verbose_name=_('event type'))
notes = generic.GenericRelation(Note, verbose_name=_('notes'))

patient = models.ForeignKey('cale_base.Patient', verbose_name=('patient'),
null=True, blank=True, default=None)
services = models.ManyToManyField('cale_base.Service', verbose_name=('services'),
null=True, blank=True, default=None)
participants = models.ManyToManyField('cale_base.CalebasseUser',
null=True, blank=True, default=None)

class Meta:
app_label = 'agenda'
verbose_name = _('event')
verbose_name_plural = _('events')
ordering = ('title', )

def __unicode__(self):
return self.title

def add_occurrences(self, start_time, end_time, **rrule_params):
'''
Add one or more occurences to the event using a comparable API to
``dateutil.rrule``.

If ``rrule_params`` does not contain a ``freq``, one will be defaulted
to ``rrule.DAILY``.

Because ``rrule.rrule`` returns an iterator that can essentially be
unbounded, we need to slightly alter the expected behavior here in order
to enforce a finite number of occurrence creation.

If both ``count`` and ``until`` entries are missing from ``rrule_params``,
only a single ``Occurrence`` instance will be created using the exact
``start_time`` and ``end_time`` values.
'''
rrule_params.setdefault('freq', rrule.DAILY)

if 'count' not in rrule_params and 'until' not in rrule_params:
self.occurrence_set.create(start_time=start_time, end_time=end_time)
else:
delta = end_time - start_time
for ev in rrule.rrule(dtstart=start_time, **rrule_params):
self.occurrence_set.create(start_time=ev, end_time=ev + delta)

def upcoming_occurrences(self):
'''
Return all occurrences that are set to start on or after the current
time.
'''
return self.occurrence_set.filter(start_time__gte=datetime.now())

def next_occurrence(self):
'''
Return the single occurrence set to start on or after the current time
if available, otherwise ``None``.
'''
upcoming = self.upcoming_occurrences()
return upcoming and upcoming[0] or None

def daily_occurrences(self, dt=None):
'''
Convenience method wrapping ``Occurrence.objects.daily_occurrences``.
'''
return Occurrence.objects.daily_occurrences(dt=dt, event=self)


class OccurrenceManager(models.Manager):

use_for_related_fields = True

def daily_occurrences(self, dt=None, event=None):
'''
Returns a queryset of for instances that have any overlap with a
particular day.

* ``dt`` may be either a datetime.datetime, datetime.date object, or
``None``. If ``None``, default to the current day.

* ``event`` can be an ``Event`` instance for further filtering.
'''
dt = dt or datetime.now()
start = datetime(dt.year, dt.month, dt.day)
end = start.replace(hour=23, minute=59, second=59)
qs = self.filter(
models.Q(
start_time__gte=start,
start_time__lte=end,
) |
models.Q(
end_time__gte=start,
end_time__lte=end,
) |
models.Q(
start_time__lt=start,
end_time__gt=end
)
)

return qs.filter(event=event) if event else qs


class Occurrence(models.Model):
'''
Represents the start end time for a specific occurrence of a master ``Event``
object.
'''
start_time = models.DateTimeField(_('start time'))
end_time = models.DateTimeField(_('end time'))
event = models.ForeignKey(Event, verbose_name=_('event'), editable=False)
notes = generic.GenericRelation(Note, verbose_name=_('notes'))

objects = OccurrenceManager()

class Meta:
app_label = 'agenda'
verbose_name = _('occurrence')
verbose_name_plural = _('occurrences')
ordering = ('start_time', 'end_time')

def __unicode__(self):
return u'%s: %s' % (self.title, self.start_time.isoformat())

def __cmp__(self, other):
return cmp(self.start_time, other.start_time)

@property
def title(self):
return self.event.title

@property
def event_type(self):
return self.event.event_type


def create_event(
title,
event_type,
participants=[],
description='',
patient=None,
services=[],
start_time=None,
end_time=None,
note=None,
**rrule_params
):
'''
Convenience function to create an ``Event``, optionally create an
``EventType``, and associated ``Occurrence``s. ``Occurrence`` creation
rules match those for ``Event.add_occurrences``.

Returns the newly created ``Event`` instance.

Parameters

``event_type``
can be either an ``EventType`` object or 2-tuple of ``(abbreviation,label)``,
from which an ``EventType`` is either created or retrieved.

``start_time``
will default to the current hour if ``None``

``end_time``
will default to ``start_time`` plus agenda_settings.DEFAULT_OCCURRENCE_DURATION
hour if ``None``

``freq``, ``count``, ``rrule_params``
follow the ``dateutils`` API (see http://labix.org/python-dateutil)

'''

if isinstance(event_type, tuple):
event_type, created = EventType.objects.get_or_create(
abbr=event_type[0],
label=event_type[1]
)

event = Event.objects.create(
title=title,
patient=patient,
description=description,
event_type=event_type
)

for participant in participants:
event.participants.add(participant)

for service in services:
event.services.add(service)

if note is not None:
event.notes.create(note=note)

start_time = start_time or datetime.now().replace(
minute=0,
second=0,
microsecond=0
)

occurence_duration = default.DEFAULT_OCCURRENCE_DURATION
end_time = end_time or start_time + occurence_duration
event.add_occurrences(start_time, end_time, **rrule_params)
return event


(3-3/6)