aboutsummaryrefslogtreecommitdiffstats
path: root/servo/models/calendar.py
diff options
context:
space:
mode:
authorFilipp Lepalaan <filipp@mac.com>2015-08-04 10:11:24 +0300
committerFilipp Lepalaan <filipp@mac.com>2015-08-04 10:11:24 +0300
commit63b0fc6269b38edf7234b9f151b80d81f614c0a3 (patch)
tree555de3068f33f8dddb4619349bbea7d9b7c822fd /servo/models/calendar.py
downloadServo-63b0fc6269b38edf7234b9f151b80d81f614c0a3.tar.gz
Servo-63b0fc6269b38edf7234b9f151b80d81f614c0a3.tar.bz2
Servo-63b0fc6269b38edf7234b9f151b80d81f614c0a3.zip
Initial commit
First public commit
Diffstat (limited to 'servo/models/calendar.py')
-rw-r--r--servo/models/calendar.py184
1 files changed, 184 insertions, 0 deletions
diff --git a/servo/models/calendar.py b/servo/models/calendar.py
new file mode 100644
index 0000000..1a8b4e8
--- /dev/null
+++ b/servo/models/calendar.py
@@ -0,0 +1,184 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, First Party Software
+# All rights reserved.
+
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+import math
+
+from dateutil.rrule import DAILY, rrule
+
+from django import forms
+from django.db import models
+from django.conf import settings
+from django.utils import timezone
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext_lazy as _
+from django.db.models import Sum
+
+
+class Calendar(models.Model):
+
+ user = models.ForeignKey(
+ settings.AUTH_USER_MODEL,
+ editable=False
+ )
+
+ title = models.CharField(
+ max_length=128,
+ verbose_name=_('title'),
+ default=_('New Calendar')
+ )
+ hours_per_day = models.FloatField(
+ null=True,
+ blank=True,
+ verbose_name=_("hours per day"),
+ help_text=_("How many hours per day should be in this calendar")
+ )
+
+ def min_hours(self):
+ return self.hours_per_day or 0
+
+ def get_overtime(self, total_hours, workdays):
+ overtime = total_hours - (self.min_hours() * workdays)
+ return overtime if overtime > 0 else 0
+
+ def subtitle(self, start_date, end_date):
+ workdays = self.get_workdays(start_date, end_date)
+ total_hours = self.get_total_hours(start_date, end_date)
+ overtime = self.get_overtime(total_hours, workdays)
+
+ if overtime > 1:
+ d = {'hours': total_hours, 'workdays': workdays, 'overtime': overtime}
+ subtitle = _("%(hours)s hours total in %(workdays)s days (%(overtime)s hours overtime)." % d)
+ else:
+ d = {'hours': total_hours, 'workdays': workdays}
+ subtitle = _("%(hours)s hours total in %(workdays)s days." % d)
+
+ return subtitle
+
+ def get_workdays(self, start_date, end_date):
+ WORKDAYS = xrange(0, 5)
+ r = rrule(DAILY, dtstart=start_date, until=end_date, byweekday=WORKDAYS)
+ return r.count()
+
+ def get_unfinished_count(self):
+ count = self.calendarevent_set.filter(finished_at=None).count()
+ return count or ""
+
+ def get_total_hours(self, start=None, finish=None):
+ """
+ Returns in hours, the total duration of events in this calendar within
+ a time period.
+ """
+ events = self.calendarevent_set.all()
+
+ if start and finish:
+ events = self.calendarevent_set.filter(started_at__range=(start, finish))
+
+ total = events.aggregate(total=Sum('seconds'))['total'] or 0
+
+ return math.ceil(total/3600.0)
+
+ def get_absolute_url(self):
+ return reverse('calendars.view', args=[self.user.username, self.pk])
+
+ class Meta:
+ app_label = "servo"
+
+
+class CalendarEvent(models.Model):
+
+ calendar = models.ForeignKey(
+ Calendar,
+ editable=False
+ )
+
+ started_at = models.DateTimeField(default=timezone.now)
+ finished_at = models.DateTimeField(null=True, blank=True)
+
+ # The duration of this event in seconds
+ seconds = models.PositiveIntegerField(
+ null=True,
+ editable=False
+ )
+
+ notes = models.TextField(null=True, blank=True)
+
+ def get_start_date(self):
+ return self.started_at.strftime('%x')
+
+ def get_start_time(self):
+ return self.started_at.strftime('%H:%M')
+
+ def get_finish_time(self):
+ try:
+ return self.finished_at.strftime('%H:%M')
+ except AttributeError:
+ return ''
+
+ def set_finished(self, ts=timezone.now):
+ self.finished_at = ts()
+ self.save()
+
+ def get_hours(self):
+ return self.seconds/3600.0
+
+ def get_duration(self):
+ if self.finished_at is None:
+ return ''
+
+ delta = (self.finished_at - self.started_at)
+ hours, remainder = divmod(delta.seconds, 3600)
+ minutes, seconds = divmod(remainder, 60)
+
+ return '%d:%d' % (hours, minutes)
+
+ def get_absolute_url(self):
+ return '/%s/calendars/%d/events/%d' % (self.calendar.user.username, self.calendar.pk, self.pk)
+
+ def save(self, *args, **kwargs):
+ self.seconds = 0
+
+ if self.finished_at:
+ delta = self.finished_at - self.started_at
+ self.seconds = delta.seconds
+
+ super(CalendarEvent, self).save(*args, **kwargs)
+
+ class Meta:
+ app_label = 'servo'
+ ordering = ['-started_at']
+
+
+class CalendarForm(forms.ModelForm):
+ class Meta:
+ model = Calendar
+ exclude = []
+
+
+class CalendarEventForm(forms.ModelForm):
+ class Meta:
+ model = CalendarEvent
+ exclude = []
+ \ No newline at end of file