Commits

Caleb Smith committed 844873f

Forget .orig files

  • Participants
  • Parent commits 79b78ec

Comments (0)

Files changed (2)

File timepiece/models.py.orig

-import datetime
-import logging
-from decimal import Decimal
-
-from django.conf import settings
-from django.db import models
-from django.db.models import Q, Avg, Sum, Max, Min
-from django.contrib.auth.models import User
-from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
-
-from timepiece import utils
-
-from dateutil.relativedelta import relativedelta
-from dateutil import rrule
-
-from datetime import timedelta
-
-
-class Attribute(models.Model):
-    ATTRIBUTE_TYPES = (
-        ('project-type', 'Project Type'),
-        ('project-status', 'Project Status'),
-    )
-    SORT_ORDER_CHOICES = [(x, x) for x in xrange(-20, 21)]
-    type = models.CharField(max_length=32, choices=ATTRIBUTE_TYPES)
-    label = models.CharField(max_length=255)
-    sort_order = models.SmallIntegerField(
-        null=True,
-        blank=True,
-        choices=SORT_ORDER_CHOICES,
-    )
-    enable_timetracking = models.BooleanField(default=False,
-        help_text='Enable time tracking functionality for projects with this '
-                  'type or status.',
-    )
-    billable = models.BooleanField(default=False)
-
-    class Meta:
-        unique_together = ('type', 'label')
-        ordering = ('sort_order',)
-
-    def __unicode__(self):
-        return self.label
-
-
-class Business(models.Model):
-    name = models.CharField(max_length=255, blank=True)
-    slug = models.SlugField(max_length=255, unique=True, blank=True)
-    email = models.EmailField(blank=True)
-    description = models.TextField(blank=True)
-    notes = models.TextField(blank=True)
-    external_id = models.CharField(max_length=32, blank=True)
-
-    def save(self, *args, **kwargs):
-        queryset = Business.objects.all()
-        if not self.slug:
-            if self.id:
-                queryset = queryset.exclude(id__exact=self.id)
-            self.slug = utils.slugify_uniquely(self.name, queryset, 'slug')
-        super(Business, self).save(*args, **kwargs)
-
-    def __unicode__(self):
-        return self.name
-
-    class Meta:
-        ordering = ('name',)
-
-
-class Project(models.Model):
-    name = models.CharField(max_length=255)
-    trac_environment = models.CharField(max_length=255, blank=True, null=True)
-    business = models.ForeignKey(
-        Business,
-        related_name='new_business_projects',
-    )
-    point_person = models.ForeignKey(User, limit_choices_to={'is_staff': True})
-    users = models.ManyToManyField(
-        User,
-        related_name='user_projects',
-        through='ProjectRelationship',
-    )
-    type = models.ForeignKey(
-        Attribute,
-        limit_choices_to={'type': 'project-type'},
-        related_name='projects_with_type',
-    )
-    status = models.ForeignKey(
-        Attribute,
-        limit_choices_to={'type': 'project-status'},
-        related_name='projects_with_status',
-    )
-    description = models.TextField()
-    billing_period = models.ForeignKey(
-        'RepeatPeriod',
-        null=True,
-        blank=True,
-        related_name='projects',
-    )
-
-    class Meta:
-        ordering = ('name', 'status', 'type',)
-        permissions = (
-            ('view_project', 'Can view project'),
-            ('email_project_report', 'Can email project report'),
-            ('view_project_time_sheet', 'Can view project time sheet'),
-            ('export_project_time_sheet', 'Can export project time sheet'),
-        )
-
-    def __unicode__(self):
-        return self.name
-
-    def trac_url(self):
-        return settings.TRAC_URL % self.trac_environment
-
-
-class RelationshipType(models.Model):
-    name = models.CharField(max_length=255, unique=True)
-    slug = models.CharField(max_length=255, unique=True, editable=False)
-
-    def save(self):
-        queryset = RelationshipType.objects.all()
-        if self.id:
-            queryset = queryset.exclude(id__exact=self.id)
-        self.slug = utils.slugify_uniquely(self.name, queryset, 'slug')
-        super(RelationshipType, self).save()
-
-    def __unicode__(self):
-        return self.name
-
-
-class ProjectRelationship(models.Model):
-    types = models.ManyToManyField(
-        RelationshipType,
-        related_name='project_relationships',
-        blank=True,
-    )
-    user = models.ForeignKey(
-        User,
-        related_name='project_relationships',
-    )
-    project = models.ForeignKey(
-        Project,
-        related_name='project_relationships',
-    )
-
-    class Meta:
-        unique_together = ('user', 'project')
-
-    def __unicode__(self):
-        return "%s's relationship to %s" % (
-            self.project.name,
-            self.user.get_full_name(),
-        )
-
-
-class Activity(models.Model):
-    """
-    Represents different types of activity: debugging, developing,
-    brainstorming, QA, etc...
-    """
-    code = models.CharField(
-        max_length=5,
-        unique=True,
-        help_text='Enter a short code to describe the type of ' + \
-            'activity that took place.'
-    )
-    name = models.CharField(
-        max_length=50,
-        help_text="""Now enter a more meaningful name for the activity.""",
-    )
-    billable = models.BooleanField(default=True)
-
-    def __unicode__(self):
-        return self.name
-
-    class Meta:
-        ordering = ('name',)
-        verbose_name_plural = 'activities'
-
-
-class Location(models.Model):
-    name = models.CharField(max_length=255, unique=True)
-    slug = models.CharField(max_length=255, unique=True)
-
-    def __unicode__(self):
-        return self.name
-
-
-class EntryManager(models.Manager):
-    def get_query_set(self):
-        qs = super(EntryManager, self).get_query_set()
-        qs = qs.select_related('activity', 'project__type')
-        # ensure our select_related are added.  Without this line later calls
-        # to select_related will void ours (not sure why - probably a bug
-        # in Django)
-        foo = str(qs.query)
-        qs = qs.extra({'billable': 'timepiece_activity.billable AND '
-                                   'timepiece_attribute.billable'})
-        return qs
-
-
-class EntryWorkedManager(models.Manager):
-    def get_query_set(self):
-        qs = super(EntryWorkedManager, self).get_query_set()
-        projects = getattr(settings, 'TIMEPIECE_PROJECTS', {})
-        return qs.exclude(project__in=projects.values())
-
-
-ENTRY_STATUS = (
-    ('unverified', 'Unverified',),
-    ('verified', 'Verified',),
-    ('approved', 'Approved',),
-    ('invoiced', 'Invoiced',),
-)
-
-
-class Entry(models.Model):
-    """
-    This class is where all of the time logs are taken care of
-    """
-
-    user = models.ForeignKey(User, related_name='timepiece_entries')
-    project = models.ForeignKey(Project, related_name='entries')
-    activity = models.ForeignKey(
-        Activity,
-        related_name='entries',
-    )
-    location = models.ForeignKey(
-        Location,
-        related_name='entries',
-    )
-    status = models.CharField(
-        max_length=24,
-        choices=ENTRY_STATUS,
-        default='unverified',
-    )
-    start_time = models.DateTimeField()
-    end_time = models.DateTimeField(blank=True, null=True)
-    seconds_paused = models.PositiveIntegerField(default=0)
-    pause_time = models.DateTimeField(blank=True, null=True)
-    comments = models.TextField(blank=True)
-    date_updated = models.DateTimeField(auto_now=True)
-
-    hours = models.DecimalField(max_digits=8, decimal_places=2, default=0)
-
-    objects = EntryManager()
-    worked = EntryWorkedManager()
-
-    def check_overlap(self, entry_b):
-        """
-        Given two entries, return True if they overlap, otherwise return False
-        
-        Does not factor in the pause time. (current implementation)
-        """
-        entry_a = self
-        #if entries are open, consider them to be closed right now
-        if not entry_a.end_time:
-            entry_a.end_time = datetime.datetime.now()
-        if not entry_b.end_time:
-            entry_b.end_time = datetime.datetime.now()
-        #Check the two entries against each other        
-        start_is_inside = entry_a.start_time > entry_b.start_time \
-            and entry_a.start_time < entry_b.end_time 
-        end_is_inside = entry_a.end_time > entry_b.start_time \
-            and entry_a.end_time < entry_b.end_time        
-        b_is_inside = entry_a.start_time < entry_b.start_time \
-            and entry_a.end_time > entry_b.end_time
-        overlap = start_is_inside or end_is_inside or b_is_inside
-        return overlap
-
-    def is_overlapping(self):
-        if self.start_time and self.end_time:
-            entries = self.user.timepiece_entries.filter(
-            Q(end_time__range=(self.start_time, self.end_time)) | \
-            Q(start_time__range=(self.start_time, self.end_time)) | \
-            Q(start_time__lte=self.start_time, end_time__gte=self.end_time))
-
-            totals = entries.aggregate(
-            max=Max('end_time'), min=Min('start_time'))
-
-            totals['total'] = 0
-            for entry in entries:
-                totals['total'] = totals['total'] + entry.get_seconds()
-
-            totals['diff'] = totals['max'] - totals['min']
-            totals['diff'] = totals['diff'].seconds + \
-                totals['diff'].days * 86400
-
-            if totals['total'] > totals['diff']:
-                return True
-            else:
-                return False
-        else:
-            return None
-
-    def clean(self):
-        if not self.user_id:
-            raise ValidationError('An unexpected error has occured')
-        if not self.start_time:
-            raise ValidationError('Please enter a valid start time')
-        start = self.start_time
-        if self.end_time:
-            end = self.end_time
-        #Current entries have no end_time
-        else:
-            end = start + datetime.timedelta(seconds=1)
-        entries = self.user.timepiece_entries.filter(
-            Q(end_time__range=(start, end)) | \
-            Q(start_time__range=(start, end)) | \
-            Q(start_time__lte=start, end_time__gte=end))
-        #An entry can not conflict with itself so remove it from the list
-        if self.id:
-            entries = entries.exclude(pk=self.id)
-        for entry in entries:
-            entry_data = {
-                'project': entry.project,
-                'activity': entry.activity,
-                'start_time': entry.start_time,
-                'end_time': entry.end_time
-            }
-            #Conflicting saved entries
-            if entry.end_time:
-                if entry.start_time.date() == start.date() \
-                and entry.end_time.date() == end.date():
-                    entry_data['start_time'] = entry.start_time.strftime(
-                        '%H:%M:%S')
-                    entry_data['end_time'] = entry.end_time.strftime(
-                        '%H:%M:%S')
-                    output = 'Start time overlaps with: ' + \
-                    '%(project)s - %(activity)s - ' % entry_data + \
-                    'from %(start_time)s to %(end_time)s' % entry_data
-                    raise ValidationError(output)
-                else:
-                    entry_data['start_time'] = entry.start_time.strftime(
-                        '%H:%M:%S on %m\%d\%Y')
-                    entry_data['end_time'] = entry.end_time.strftime(
-                        '%H:%M:%S on %m\%d\%Y')
-                    output = 'Start time overlaps with: ' + \
-                    '%(project)s - %(activity)s - ' % entry_data + \
-                    'from %(start_time)s to %(end_time)s' % entry_data
-                    raise ValidationError(output)
-
-        if end <= start:
-            raise ValidationError('Ending time must exceed the starting time')
-        return True
-
-    def save(self, **kwargs):
-        self.hours = Decimal('%.2f' % round(self.total_hours, 2))
-        super(Entry, self).save(**kwargs)
-
-    def get_seconds(self):
-        """
-        Determines the difference between the starting and ending time.  The
-        result is returned as an integer of seconds.
-        """
-        if self.start_time and self.end_time:
-            # only calculate when the start and end are defined
-            delta = self.end_time - self.start_time
-            seconds = delta.seconds - self.seconds_paused
-        else:
-            seconds = 0
-            delta = datetime.timedelta(days=0)
-
-        return seconds + (delta.days * 86400)
-
-    def __total_hours(self):
-        """
-        Determined the total number of hours worked in this entry
-        """
-        total = self.get_seconds() / 3600.0
-        #in case seconds paused are greater than the elapsed time
-        if total < 0:
-            total = 0
-        return total
-    total_hours = property(__total_hours)
-
-    def __is_paused(self):
-        """
-        Determine whether or not this entry is paused
-        """
-        return bool(self.pause_time)
-    is_paused = property(__is_paused)
-
-    def pause(self):
-        """
-        If this entry is not paused, pause it.
-        """
-        if not self.is_paused:
-            self.pause_time = datetime.datetime.now()
-
-    def pause_all(self):
-        """
-        Pause all open entries
-        """
-        entries = self.user.timepiece_entries.filter(
-        end_time__isnull=True).all()
-        for entry in entries:
-            entry.pause()
-            entry.save()
-
-    def unpause(self, date=None):
-        if self.is_paused:
-            if not date:
-                date = datetime.datetime.now()
-            delta = date - self.pause_time
-            self.seconds_paused += delta.seconds
-            self.pause_time = None
-
-    def toggle_paused(self):
-        """
-        Toggle the paused state of this entry.  If the entry is already paused,
-        it will be unpaused; if it is not paused, it will be paused.
-        """
-        if self.is_paused:
-            self.unpause()
-        else:
-            self.pause()
-
-    def __is_closed(self):
-        """
-        Determine whether this entry has been closed or not
-        """
-        return bool(self.end_time)
-    is_closed = property(__is_closed)
-
-    def clock_in(self, user, project):
-        """
-        Set this entry up for saving the first time, as an open entry.
-        """
-        if not self.is_closed:
-            self.user = user
-            self.project = project
-            if not self.start_time:
-                self.start_time = datetime.datetime.now()
-
-    def __billing_window(self):
-        return BillingWindow.objects.get(
-            period__users=self.user,
-            date__lte=self.end_time,
-            end_date__gt=self.end_time)
-    billing_window = property(__billing_window)
-
-    def __is_editable(self):
-        return self.status == 'unverified'
-    is_editable = property(__is_editable)
-
-    def __delete_key(self):
-        """
-        Make it a little more interesting for deleting logs
-        """
-        salt = '%i-%i-apple-%s-sauce' \
-            % (self.id, self.is_paused, self.is_closed)
-        try:
-            import hashlib
-        except ImportError:
-            import sha
-            key = sha.new(salt).hexdigest()
-        else:
-            key = hashlib.sha1(salt).hexdigest()
-        return key
-    delete_key = property(__delete_key)
-
-    def __unicode__(self):
-        """
-        The string representation of an instance of this class
-        """
-        return '%s on %s' % (self.user, self.project)
-
-    class Meta:
-        verbose_name_plural = 'entries'
-        permissions = (
-            ('can_clock_in', 'Can use Pendulum to clock in'),
-            ('can_pause', 'Can pause and unpause log entries'),
-            ('can_clock_out', 'Can use Pendulum to clock out'),
-            ('view_entry_summary', 'Can view entry summary page'),
-        )
-
-# Add a utility method to the User class that will tell whether or not a
-# particular user has any unclosed entries
-User.clocked_in = property(lambda user: user.timepiece_entries.filter(
-    end_time__isnull=True).count() > 0)
-
-
-class RepeatPeriodManager(models.Manager):
-    def update_billing_windows(self, date_boundary=None):
-        active_billing_periods = self.filter(
-            active=True,
-        ).select_related(
-            'project'
-        )
-        windows = []
-        for period in active_billing_periods:
-            windows += ((period,
-                period.update_billing_windows(date_boundary)),)
-        return windows
-
-
-class RepeatPeriod(models.Model):
-    INTERVAL_CHOICES = (
-        ('day', 'Day(s)'),
-        ('week', 'Week(s)'),
-        ('month', 'Month(s)'),
-        ('year', 'Year(s)'),
-    )
-    count = models.PositiveSmallIntegerField(
-        choices=[(x, x) for x in range(1, 32)],
-    )
-    interval = models.CharField(
-        max_length=10,
-        choices=INTERVAL_CHOICES,
-    )
-    active = models.BooleanField(default=False)
-
-    users = models.ManyToManyField(
-        User,
-        blank=True,
-        through='PersonRepeatPeriod',
-        related_name='repeat_periods',
-    )
-
-    objects = RepeatPeriodManager()
-
-    def __unicode__(self):
-        return "%d %s" % (self.count, self.get_interval_display())
-
-    def delta(self):
-        return relativedelta(**{str(self.interval + 's'): self.count})
-
-    def update_billing_windows(self, date_boundary=None):
-        if not date_boundary:
-            date_boundary = datetime.date.today()
-        windows = []
-        try:
-            window = self.billing_windows.order_by('-date').select_related()[0]
-        except IndexError:
-            window = None
-        if window:
-            start_date = window.date
-            while window.date + self.delta() <= date_boundary:
-                window.id = None
-                if window.date + self.delta() == window.end_date:
-                    # same delta as last time
-                    window.date += self.delta()
-                else:
-                    # delta changed, make sure to include extra time
-                    window.date = window.end_date
-                window.end_date += self.delta()
-                window.save(force_insert=True)
-            return self.billing_windows.filter(
-                date__gt=start_date
-            ).order_by('date')
-        else:
-            return []
-
-
-class BillingWindow(models.Model):
-    period = models.ForeignKey(RepeatPeriod, related_name='billing_windows')
-    date = models.DateField()
-    end_date = models.DateField()
-
-    class Meta:
-        get_latest_by = 'date'
-
-    def __unicode__(self):
-        return "%s through %s" % (self.date, self.end_date)
-
-    def next(self):
-        if not hasattr(self, '_next'):
-            try:
-                window = BillingWindow.objects.filter(
-                    period=self.period,
-                    date__gt=self.date,
-                ).order_by('date')[0]
-            except IndexError:
-                window = None
-            self._next = window
-        return self._next
-
-    def previous(self):
-        if not hasattr(self, '_previous'):
-            try:
-                window = BillingWindow.objects.filter(
-                    period=self.period,
-                    date__lt=self.date,
-                ).order_by('-date')[0]
-            except IndexError:
-                window = None
-            self._previous = window
-        return self._previous
-
-    def __entries(self):
-            return Entry.objects.filter(
-                end_time__lte=self.end_date,
-                end_time__gt=self.date)
-    entries = property(__entries)
-
-
-class PersonRepeatPeriod(models.Model):
-    user = models.ForeignKey(
-        User,
-        unique=True,
-        null=True,
-    )
-    repeat_period = models.ForeignKey(
-        RepeatPeriod,
-        unique=True,
-    )
-
-    def hours_in_week(self, date):
-        left, right = utils.get_week_window(date)
-        entries = Entry.worked.filter(user=self.user)
-        entries = entries.filter(end_time__gt=left,
-            end_time__lt=right, status='approved')
-        return entries.aggregate(s=Sum('hours'))['s']
-
-    def overtime_hours_in_week(self, date):
-        hours = self.hours_in_week(date)
-        if hours > 40:
-            return hours - 40
-        return 0
-
-    def total_monthly_overtime(self, day):
-        start = day.replace(day=1)
-        end = start + relativedelta(months=1)
-        weeks = utils.generate_weeks(start=start, end=end)
-        overtime = Decimal('0.0')
-        for week in weeks:
-            overtime += self.overtime_hours_in_week(week)
-        return overtime
-
-    def summary(self, date, end_date):
-        """
-        Returns a summary of hours worked in the given time frame, for this
-        user.  The setting TIMEPIECE_PROJECTS can be used to separate out hours
-        for paid leave that should not be included in the total worked (e.g.,
-        sick time, vacation time, etc.).  Those hours will be added to the
-        summary separately using the dictionary key set in TIMEPIECE_PROJECTS.
-        """
-        projects = getattr(settings, 'TIMEPIECE_PROJECTS', {})
-        user = self.user
-        entries = user.timepiece_entries.filter(end_time__gt=date,
-                                                end_time__lte=end_date,
-                                                status='approved')
-        data = {'billable': Decimal('0'), 'non_billable': Decimal('0')}
-        data['total'] = entries.aggregate(s=Sum('hours'))['s']
-        billable = entries.exclude(project__in=projects.values())
-        billable = billable.values(
-            'billable',
-        ).annotate(s=Sum('hours'))
-        for row in billable:
-            if row['billable']:
-                data['billable'] += row['s']
-            else:
-                data['non_billable'] += row['s']
-        data['total_worked'] = data['billable'] + data['non_billable']
-        data['paid_leave'] = {}
-        for name, pk in projects.iteritems():
-            qs = entries.filter(project=projects[name])
-            data['paid_leave'][name] = qs.aggregate(s=Sum('hours'))['s']
-        return data
-
-    def list_total_hours(self, N=2):
-        bw = BillingWindow.objects.filter(period=self.repeat_period).order_by(
-            '-date')[:N]
-        result = []
-        for b in bw:
-            result.append(self.user.timepiece_entries.filter(
-                end_time__lte=b.end_date,
-                end_time__gt=b.date
-            ).aggregate(total=Sum('hours')))
-        return result
-
-    class Meta:
-        permissions = (
-            ('view_person_time_sheet', 'Can view person\'s timesheet.'),
-            ('edit_person_time_sheet', 'Can edit person\'s timesheet.'),
-        )
-
-
-class ProjectContract(models.Model):
-    CONTRACT_STATUS = (
-        ('upcoming', 'Upcoming'),
-        ('current', 'Current'),
-        ('complete', 'Complete'),
-    )
-
-    project = models.ForeignKey(Project, related_name='contracts')
-    start_date = models.DateField()
-    end_date = models.DateField()
-    num_hours = models.DecimalField(max_digits=8, decimal_places=2,
-                                    default=0)
-    status = models.CharField(choices=CONTRACT_STATUS, default='upcomming',
-                              max_length=32)
-
-    def hours_worked(self):
-        # TODO put this in a .extra w/a subselect
-        if not hasattr(self, '_hours_worked'):
-            self._hours_worked = Entry.objects.filter(
-                project=self.project,
-                start_time__gte=self.start_date,
-                end_time__lt=self.end_date + datetime.timedelta(days=1),
-            ).aggregate(sum=Sum('hours'))['sum']
-        return self._hours_worked or 0
-
-    @property
-    def hours_assigned(self):
-        # TODO put this in a .extra w/a subselect
-        if not hasattr(self, '_hours_assigned'):
-            self._hours_assigned =\
-              self.assignments.aggregate(sum=Sum('num_hours'))['sum']
-        return self._hours_assigned or 0
-
-    @property
-    def hours_allocated(self):
-        allocations = AssignmentAllocation.objects.filter(
-            assignment__contract=self)
-        return allocations.aggregate(sum=Sum('hours'))['sum']
-
-    @property
-    def hours_remaining(self):
-        return self.num_hours - self.hours_worked()
-
-    @property
-    def weeks_remaining(self):
-        return utils.generate_weeks(end=self.end_date)
-
-    def __unicode__(self):
-        return unicode(self.project)
-
-
-class AssignmentManager(models.Manager):
-    def active_during_week(self, week, next_week):
-        q = Q(contract__end_date__gte=week, contract__end_date__lt=next_week)
-        q |= Q(contract__start_date__gte=week,
-            contract__start_date__lt=next_week)
-        q |= Q(contract__start_date__lt=week, contract__end_date__gt=next_week)
-        return self.get_query_set().filter(q)
-
-    def sort_by_priority(self):
-        return sorted(self.get_query_set().all(),
-            key=lambda contract: contract.this_weeks_priority_number)
-
-
-# contract assignment logger
-logger = logging.getLogger('timepiece.ca')
-
-
-class ContractAssignment(models.Model):
-    contract = models.ForeignKey(ProjectContract, related_name='assignments')
-    user = models.ForeignKey(
-        User,
-        related_name='assignments',
-    )
-    start_date = models.DateField()
-    end_date = models.DateField()
-    num_hours = models.DecimalField(max_digits=8, decimal_places=2,
-                                    default=0)
-    min_hours_per_week = models.IntegerField(default=0)
-
-    objects = AssignmentManager()
-
-    def _log(self, msg):
-        logger.debug('{0} - {1}'.format(self, msg))
-
-    def _filtered_hours_worked(self, end_date):
-        return Entry.objects.filter(
-            user=self.user,
-            project=self.contract.project,
-            start_time__gte=self.start_date,
-            end_time__lt=end_date,
-        ).aggregate(sum=Sum('hours'))['sum'] or 0
-
-    def filtered_hours_worked_with_in_window(self, start_date, end_date):
-        return Entry.objects.filter(
-            user=self.user,
-            project=self.contract.project,
-            start_time__gte=start_date,
-            end_time__lt=end_date,
-        ).aggregate(sum=Sum('hours'))['sum'] or 0
-
-    @property
-    def hours_worked(self):
-        if not hasattr(self, '_hours_worked'):
-            date = self.end_date + datetime.timedelta(days=1)
-            self._hours_worked = self._filtered_hours_worked(date)
-        return self._hours_worked or 0
-
-    @property
-    def hours_remaining(self):
-        return self.num_hours - self.hours_worked
-
-    @property
-    def this_weeks_priority_number(self):
-        """
-        Only works if already filtered to the current week. Otherwise groups
-        outside the range will be listed as ongoing instead of befor or after.
-        """
-        if not hasattr(self, '_priority_type'):
-            weeks = utils.get_week_window(datetime.datetime.now())
-            if self.end_date < weeks[1].date() \
-            and self.end_date >= weeks[0].date():
-                self._priority_type = 0
-            elif self.start_date < weeks[1].date() \
-            and self.start_date >= weeks[0].date():
-                self._priority_type = 1
-            else:
-                self._priority_type = 2
-        return self._priority_type
-
-    @property
-    def this_weeks_priority_type(self):
-        type_list = ['ending', 'starting', 'ongoing', ]
-        return type_list[self.this_weeks_priority_number]
-
-    def get_average_weekly_committment(self):
-        week_start = utils.get_week_start()
-        # calculate hours left on contract (subtract worked hours this week)
-        remaining = self.num_hours - self._filtered_hours_worked(week_start)
-        commitment = remaining / self.contract.weeks_remaining.count()
-        return commitment
-
-    def weekly_commitment(self, day=None):
-        self._log("Commitment for {0}".format(day))
-        # earlier assignments may have already allocated time for this week
-        unallocated = self.unallocated_hours_for_week(day)
-        self._log('Unallocated hours {0}'.format(unallocated))
-        reserved = self.remaining_min_hours()
-        self._log('Reserved hours {0}'.format(reserved))
-        # start with unallocated hours
-        commitment = unallocated
-        # reserve required hours on later assignments (min_hours_per_week)
-        commitment -= self.remaining_min_hours()
-        self._log('Commitment after reservation {0}'.format(commitment))
-        # if we're under the needed minimum hours and we have available
-        # time, then raise our commitment to the desired level
-        if commitment < self.min_hours_per_week \
-        and unallocated >= self.min_hours_per_week:
-            commitment = self.min_hours_per_week
-        self._log('Commitment after minimum weekly hours {0}'\
-            .format(commitment))
-        # calculate hours left on contract (subtract worked hours this week)
-        week_start = utils.get_week_start(day)
-        remaining = self.num_hours - self._filtered_hours_worked(week_start)
-        total_allocated = self.blocks.aggregate(s=Sum('hours'))['s'] or 0
-        remaining -= total_allocated
-        if remaining < 0:
-            remaining = 0
-        self._log('Remaining {0}'.format(remaining))
-        # reduce commitment to remaining hours
-        if commitment > remaining:
-            commitment = remaining
-        self._log('Final commitment {0}'.format(commitment))
-        return commitment
-
-    def allocated_hours_for_week(self, day):
-        week, next_week = utils.get_week_window(day)
-        allocs = AssignmentAllocation.objects
-        allocs = allocs.filter(assignment__user=self.user)
-        allocs = allocs.filter(date__gte=week, date__lt=next_week)
-        hours = allocs.aggregate(s=Sum('hours'))['s']
-        return hours or 0
-
-    def unallocated_hours_for_week(self, day):
-        """ Calculate number of hours left to work for a week """
-        allocated = self.allocated_hours_for_week(day)
-        self._log('Allocated hours {0}'.format(allocated))
-        try:
-            schedule = PersonSchedule.objects.filter(user=self.user)[0]
-        except IndexError:
-            schedule = None
-        if schedule:
-            unallocated = schedule.hours_per_week - allocated
-        else:
-            unallocated = 40 - allocated
-        return unallocated
-
-    def remaining_contracts(self):
-        assignments = ContractAssignment.objects.exclude(pk=self.pk)
-        assignments = assignments.filter(end_date__gte=self.end_date,
-                                         user=self.user)
-        return assignments.order_by('-end_date')
-
-    def remaining_min_hours(self):
-        return self.remaining_contracts().aggregate(
-            s=Sum('min_hours_per_week'))['s'] or 0
-
-    class Meta:
-        unique_together = (('contract', 'user'),)
-
-    def __unicode__(self):
-        return u'%s / %s' % (self.user, self.contract.project)
-
-
-class AllocationManager(models.Manager):
-    def during_this_week(self, user, day=None):
-        week = utils.get_week_start(day=day)
-        return self.get_query_set().filter(
-            date=week, assignment__user=user,
-            assignment__contract__status='current'
-            ).exclude(hours=0)
-
-
-class AssignmentAllocation(models.Model):
-    assignment = models.ForeignKey(ContractAssignment, related_name='blocks')
-    date = models.DateField()
-    hours = models.DecimalField(max_digits=8, decimal_places=2, default=0)
-
-    @property
-    def hours_worked(self):
-        if not hasattr(self, '_hours_worked'):
-            end_date = self.date + datetime.timedelta(weeks=1)
-            self._hours_worked = self.assignment.\
-                    filtered_hours_worked_with_in_window(self.date, end_date)
-        return self._hours_worked or 0
-
-    @property
-    def hours_left(self):
-        if not hasattr(self, '_hours_left'):
-            self._hours_left = self.hours - self.hours_worked
-        return self._hours_left or 0
-
-    objects = AllocationManager()
-
-
-class PersonSchedule(models.Model):
-    user = models.ForeignKey(
-        User,
-        unique=True,
-        null=True,
-    )
-    hours_per_week = models.DecimalField(max_digits=8, decimal_places=2,
-                                         default=0)
-    end_date = models.DateField()
-
-    @property
-    def furthest_end_date(self):
-        assignments = self.user.assignments.order_by('-end_date')
-        assignments = assignments.exclude(contract__status='complete')
-        try:
-            end_date = assignments.values('end_date')[0]['end_date']
-        except IndexError:
-            end_date = self.end_date
-        return end_date
-
-    @property
-    def hours_available(self):
-        today = datetime.date.today()
-        weeks_remaining = (self.end_date - today).days / 7.0
-        return float(self.hours_per_week) * weeks_remaining
-
-    @property
-    def hours_scheduled(self):
-        if not hasattr(self, '_hours_scheduled'):
-            self._hours_scheduled = 0
-            now = datetime.datetime.now()
-            for assignment in self.user.assignments.filter(end_date__gte=now):
-                self._hours_scheduled += assignment.hours_remaining
-        return self._hours_scheduled
-
-    def __unicode__(self):
-        return unicode(self.user)
-
-
-class UserProfile(models.Model):
-    user = models.OneToOneField(User, unique=True, related_name='profile')
-    default_activity = models.ForeignKey(Activity, blank=True, null=True)
-
-    def __unicode__(self):
-        return unicode(self.user)

File timepiece/utils.py.orig

-from dateutil import rrule
-from dateutil.relativedelta import relativedelta
-
-from django.http import HttpResponse, HttpResponseRedirect, Http404
-from django.shortcuts import render_to_response
-from django.template import RequestContext
-from django.template.defaultfilters import slugify
-
-from django.contrib.sites.models import Site
-from datetime import date, datetime, timedelta, time as time_obj
-import time
-import calendar
-
-
-def slugify_uniquely(s, queryset=None, field='slug'):
-    """
-    Returns a slug based on 's' that is unique for all instances of the given
-    field in the given queryset.
-
-    If no string is given or the given string contains no slugify-able
-    characters, default to the given field name + N where N is the number of
-    default slugs already in the database.
-    """
-    new_slug = new_slug_base = slugify(s)
-    if queryset:
-        queryset = queryset.filter(**{'%s__startswith' % field: new_slug_base})
-        similar_slugs = [value[0] for value in queryset.values_list(field)]
-        i = 1
-        while new_slug in similar_slugs:
-            new_slug = "%s%d" % (new_slug_base, i)
-            i += 1
-    return new_slug
-
-
-def render_with(template_name):
-    """
-    Renders the view wrapped by this decorator with the given template.  The
-    view should return the context to be used in the template, or an
-    HttpResponse.
-
-    If the view returns an HttpResponseRedirect, the decorator will redirect
-    to the given URL, or to request.REQUEST['next'] (if it exists).
-    """
-    def render_with_decorator(view_func):
-        def wrapper(*args, **kwargs):
-            request = args[0]
-            response = view_func(*args, **kwargs)
-
-            if isinstance(response, HttpResponse):
-                if isinstance(response, HttpResponseRedirect) and \
-                  'next' in request.REQUEST:
-                    return HttpResponseRedirect(request.REQUEST['next'])
-                else:
-                    return response
-            else:
-                # assume response is a context dictionary
-                context = response
-                return render_to_response(
-                    template_name,
-                    context,
-                    context_instance=RequestContext(request),
-                )
-        return wrapper
-    return render_with_decorator
-
-
-def determine_period(the_date=date.today(), delta=0):
-    """
-    Determine the start and end date for an accounting period.  If a date
-    is passed in, that date will be used to determine the accounting period.
-    If no date is passed in, the current date will be used.
-    """
-    delta = int(delta)
-
-    try:
-        # attempt to get the configuration for the current site
-        site = Site.objects.get_current()
-        config = site.timepiececonfiguration
-    except:
-        raise Exception('Please configure Pendulum for %s!' % site)
-
-    if config.is_monthly and (config.month_start >= 1 \
-    and config.month_start <= 31):
-        if config.month_start == 1:
-            #if the periods start on the first of the month, just use the first
-            #and last days of each month for the period
-            if delta > 0:
-                diff = the_date.month - delta
-                if diff < 1:
-                    # determine how many years to go back
-                    years = abs(diff / 12)
-
-                    # determine how many months to go back
-                    months = delta - (years * 12)
-                    if months == the_date.month:
-                        # don't give an invalid month
-                        months = -1
-                        years += 1
-
-                    # now set the year and month
-                    year = the_date.year - years
-                    month = the_date.month - months
-                else:
-                    year, month = the_date.year, diff
-            else:
-                year, month = the_date.year, the_date.month
-
-            num_days = calendar.monthrange(year, month)[1]
-            sy, sm, sd = year, month, 1
-            ey, em, ed = year, month, num_days
-        else:
-            # if the periods don't start on the first of the month, try to
-            # figure out which days are required
-
-            sy, sm, sd = the_date.year, the_date.month, config.month_start
-
-            # now take the delta into account
-            if delta > 0:
-                diff = sm - delta
-                if diff < 1:
-                    # determine how many years to go back
-                    years = abs(diff / 12)
-
-                    # determine how many months to go back
-                    months = delta - (years * 12)
-                    if months == sm:
-                        # don't give an invalid month
-                        months = -1
-                        years += 1
-
-                    # now set the year and month
-                    sy, sm = sy - years, sm - months
-                else:
-                    sm = diff
-
-            if the_date.day >= config.month_start:
-                # if we are already into the period that began this month
-                if sm == 12:
-                    # see if the period spans into the next year
-                    ey, em = sy + 1, 1
-                else:
-                    # if not, just add a month and subtract a day
-                    ey, em = sy, em + 1
-            else:
-                # if we are in the period that ends this month
-                if sm == 1:
-                    # and we're in January, set the start to last december
-                    sy, sm = sy - 1, 12
-                    ey, em = sy + 1, 1
-                else:
-                    # otherwise, just keep it simple
-                    sm = sm - 1
-                    ey, em = sy, sm + 1
-
-            ed = sd - 1
-
-            # this should handle funky situations where a period begins on the
-            # 31st of a month or whatever...
-            num_days = calendar.monthrange(ey, em)[1]
-            if ed > num_days:
-                ed = num_days
-
-    elif config.install_date and config.period_length:
-        #if we have periods with a set number of days...
-        #find out how many days have passed since the installation date
-        diff = the_date - config.install_date
-
-        #find out how many days are left over after dividing the number of days
-        #since installation by the length of the period
-        days_into_period = diff.days % config.period_length
-
-        #determine the start date of the period
-        start = the_date - timedelta(days=days_into_period)
-
-        #now take into account the delta
-        if delta > 0:
-            start = start - timedelta(days=(delta * config.period_length))
-            end = start + timedelta(days=config.period_length - 1)
-        else:
-            #determine the end date of the period
-            end = the_date + \
-            timedelta(days=(config.period_length - days_into_period - 1))
-
-        sy, sm, sd = start.year, start.month, start.day
-        ey, em, ed = end.year, end.month, end.day
-    else:
-        raise Exception('Invalid Pendulum configuration for %s' % site)
-
-    start_date = datetime(sy, sm, sd, 0, 0, 0)
-    end_date = datetime(ey, em, ed, 23, 59, 59)
-
-    return (start_date, end_date)
-
-DEFAULT_TIME_FORMATS = [
-    '%H:%M',        # 23:15         => 23:15:00
-    '%H:%M:%S',     # 05:50:21      => 05:50:21
-    '%I:%M:%S %p',  # 11:40:53 PM   => 23:40:53
-    '%I:%M %p',     # 6:21 AM       => 06:21:00
-    '%I %p',        # 1 pm          => 13:00:00
-    '%I:%M:%S%p',   # 8:45:52pm     => 23:45:52
-    '%I:%M%p',      # 12:03am       => 00:03:00
-    '%I%p',         # 12pm          => 12:00:00
-    '%H',           # 22            => 22:00:00
-]
-
-
-def parse_time(time_str, input_formats=None):
-    """
-    This function will take a string with some sort of representation of time
-    in it.  The string will be parsed using a variety of formats until a match
-    is found for the format given.  The return value is a datetime.time object.
-    """
-    formats = input_formats or DEFAULT_TIME_FORMATS
-
-    # iterate over all formats until we find a match
-    for format in formats:
-        try:
-            # attempt to parse the time with the current format
-            value = time.strptime(time_str, format)
-        except ValueError:
-            continue
-        else:
-            # turn the time_struct into a datetime.time object
-            return time_obj(*value[3:6])
-
-    # return None if there's no matching format
-    return None
-
-
-def get_total_time(seconds):
-    """
-    Returns the specified number of seconds in an easy-to-read HH:MM:SS format
-    """
-    hours = int(seconds / 3600)
-    seconds %= 3600
-    minutes = int(seconds / 60)
-    seconds %= 60
-
-    return u'%02i:%02i:%02i' % (hours, minutes, seconds)
-
-
-def get_week_start(day=None):
-    if not day:
-        day = date.today()
-    if day.isoweekday() != 7:
-        week_start = day - timedelta(days=day.isoweekday())
-    else:
-        week_start = day
-    return week_start
-
-
-def generate_weeks(end, start=None):
-    start = get_week_start(start)
-    return rrule.rrule(rrule.WEEKLY, dtstart=start, until=end, byweekday=6)
-
-
-def get_week_window(day):
-    start = get_week_start(day)
-    end = start + timedelta(weeks=1)
-    weeks = generate_weeks(start=start, end=end)
-    return list(weeks)
-
-
-def date_filter(func):
-    def inner_decorator(request, *args, **kwargs):
-        from timepiece import forms as timepiece_forms
-        if request.GET:
-            form = timepiece_forms.DateForm(request.GET)
-            if form.is_valid():
-                from_date = form.cleaned_data.get('from_date')
-                to_date = form.cleaned_data.get('to_date')
-                form.save()
-                status = form.cleaned_data.get('status')
-                activity = form.cleaned_data.get('activity')
-            else:
-                raise Http404
-        else:
-            form = timepiece_forms.DateForm()
-            today = date.today()
-            from_date = today.replace(day=1)
-            to_date = from_date + relativedelta(months=1)
-            status = activity = None            
-        return func(request, form, from_date, to_date, status, activity,
-            *args, **kwargs)
-    return inner_decorator