mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-17 05:22:16 +00:00
Merge master into python-deps
# Conflicts: # PyRIGS/settings.py # RIGS/admin.py # RIGS/models.py # RIGS/test_functional.py # RIGS/urls.py # requirements.txt # wercker.yml
This commit is contained in:
104
RIGS/models.py
104
RIGS/models.py
@@ -1,3 +1,4 @@
|
||||
import datetime
|
||||
import hashlib
|
||||
import datetime, pytz
|
||||
|
||||
@@ -9,24 +10,33 @@ from django.utils.functional import cached_property
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from reversion import revisions as reversion
|
||||
import string
|
||||
import random
|
||||
from collections import Counter
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
import random
|
||||
import string
|
||||
from collections import Counter
|
||||
from decimal import Decimal
|
||||
|
||||
import reversion
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.db import models
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
# Create your models here.
|
||||
@python_2_unicode_compatible
|
||||
class Profile(AbstractUser):
|
||||
initials = models.CharField(max_length=5, unique=True, null=True, blank=False)
|
||||
phone = models.CharField(max_length=13, null=True, blank=True)
|
||||
api_key = models.CharField(max_length=40,blank=True,editable=False, null=True)
|
||||
api_key = models.CharField(max_length=40, blank=True, editable=False, null=True)
|
||||
|
||||
@classmethod
|
||||
def make_api_key(cls):
|
||||
size=20
|
||||
chars=string.ascii_letters + string.digits
|
||||
size = 20
|
||||
chars = string.ascii_letters + string.digits
|
||||
new_api_key = ''.join(random.choice(chars) for x in range(size))
|
||||
return new_api_key;
|
||||
|
||||
@@ -56,6 +66,7 @@ class Profile(AbstractUser):
|
||||
('view_profile', 'Can view Profile'),
|
||||
)
|
||||
|
||||
|
||||
class RevisionMixin(object):
|
||||
@property
|
||||
def last_edited_at(self):
|
||||
@@ -80,10 +91,11 @@ class RevisionMixin(object):
|
||||
versions = reversion.get_for_object(self)
|
||||
if versions:
|
||||
version = reversion.get_for_object(self)[0]
|
||||
return "V{0} | R{1}".format(version.pk,version.revision.pk)
|
||||
return "V{0} | R{1}".format(version.pk, version.revision.pk)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@reversion.register
|
||||
@python_2_unicode_compatible
|
||||
class Person(models.Model, RevisionMixin):
|
||||
@@ -98,7 +110,7 @@ class Person(models.Model, RevisionMixin):
|
||||
def __str__(self):
|
||||
string = self.name
|
||||
if self.notes is not None:
|
||||
if len(self.notes) > 0:
|
||||
if len(self.notes) > 0:
|
||||
string += "*"
|
||||
return string
|
||||
|
||||
@@ -109,7 +121,7 @@ class Person(models.Model, RevisionMixin):
|
||||
if e.organisation:
|
||||
o.append(e.organisation)
|
||||
|
||||
#Count up occurances and put them in descending order
|
||||
# Count up occurances and put them in descending order
|
||||
c = Counter(o)
|
||||
stats = c.most_common()
|
||||
return stats
|
||||
@@ -142,7 +154,7 @@ class Organisation(models.Model, RevisionMixin):
|
||||
def __str__(self):
|
||||
string = self.name
|
||||
if self.notes is not None:
|
||||
if len(self.notes) > 0:
|
||||
if len(self.notes) > 0:
|
||||
string += "*"
|
||||
return string
|
||||
|
||||
@@ -152,8 +164,8 @@ class Organisation(models.Model, RevisionMixin):
|
||||
for e in Event.objects.filter(organisation=self).select_related('person'):
|
||||
if e.person:
|
||||
p.append(e.person)
|
||||
|
||||
#Count up occurances and put them in descending order
|
||||
|
||||
# Count up occurances and put them in descending order
|
||||
c = Counter(p)
|
||||
stats = c.most_common()
|
||||
return stats
|
||||
@@ -252,15 +264,17 @@ class EventManager(models.Manager):
|
||||
(models.Q(start_date__gte=start.date(), start_date__lte=end.date())) | # Start date in bounds
|
||||
(models.Q(end_date__gte=start.date(), end_date__lte=end.date())) | # End date in bounds
|
||||
(models.Q(access_at__gte=start, access_at__lte=end)) | # Access at in bounds
|
||||
(models.Q(meet_at__gte=start, meet_at__lte=end)) | # Meet at in bounds
|
||||
(models.Q(meet_at__gte=start, meet_at__lte=end)) | # Meet at in bounds
|
||||
|
||||
(models.Q(start_date__lte=start, end_date__gte=end)) | # Start before, end after
|
||||
(models.Q(access_at__lte=start, start_date__gte=end)) | # Access before, start after
|
||||
(models.Q(access_at__lte=start, end_date__gte=end)) | # Access before, end after
|
||||
(models.Q(meet_at__lte=start, start_date__gte=end)) | # Meet before, start after
|
||||
(models.Q(meet_at__lte=start, end_date__gte=end)) # Meet before, end after
|
||||
(models.Q(start_date__lte=start, end_date__gte=end)) | # Start before, end after
|
||||
(models.Q(access_at__lte=start, start_date__gte=end)) | # Access before, start after
|
||||
(models.Q(access_at__lte=start, end_date__gte=end)) | # Access before, end after
|
||||
(models.Q(meet_at__lte=start, start_date__gte=end)) | # Meet before, start after
|
||||
(models.Q(meet_at__lte=start, end_date__gte=end)) # Meet before, end after
|
||||
|
||||
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', 'organisation', 'venue', 'mic')
|
||||
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person',
|
||||
'organisation',
|
||||
'venue', 'mic')
|
||||
return events
|
||||
|
||||
def rig_count(self):
|
||||
@@ -302,7 +316,8 @@ class Event(models.Model, RevisionMixin):
|
||||
status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL)
|
||||
dry_hire = models.BooleanField(default=False)
|
||||
is_rig = models.BooleanField(default=True)
|
||||
based_on = models.ForeignKey('Event', on_delete=models.SET_NULL, related_name='future_events', blank=True, null=True)
|
||||
based_on = models.ForeignKey('Event', on_delete=models.SET_NULL, related_name='future_events', blank=True,
|
||||
null=True)
|
||||
|
||||
# Timing
|
||||
start_date = models.DateField()
|
||||
@@ -328,6 +343,7 @@ class Event(models.Model, RevisionMixin):
|
||||
"""
|
||||
EX Vat
|
||||
"""
|
||||
|
||||
@property
|
||||
def sum_total(self):
|
||||
# Manual querying is required for efficiency whilst maintaining floating point arithmetic
|
||||
@@ -335,14 +351,15 @@ class Event(models.Model, RevisionMixin):
|
||||
# sql = "SELECT SUM(quantity * cost) AS sum_total FROM \"RIGS_eventitem\" WHERE event_id=%i" % self.id
|
||||
# else:
|
||||
# sql = "SELECT id, SUM(quantity * cost) AS sum_total FROM RIGS_eventitem WHERE event_id=%i" % self.id
|
||||
#total = self.items.raw(sql)[0]
|
||||
#if total.sum_total:
|
||||
# total = self.items.raw(sql)[0]
|
||||
# if total.sum_total:
|
||||
# return total.sum_total
|
||||
#total = 0.0
|
||||
#for item in self.items.filter(cost__gt=0).extra(select="SUM(cost * quantity) AS sum"):
|
||||
# total = 0.0
|
||||
# for item in self.items.filter(cost__gt=0).extra(select="SUM(cost * quantity) AS sum"):
|
||||
# total += item.sum
|
||||
total = EventItem.objects.filter(event=self).aggregate(
|
||||
sum_total=models.Sum(models.F('cost')*models.F('quantity'), output_field=models.DecimalField(max_digits=10, decimal_places=2))
|
||||
sum_total=models.Sum(models.F('cost') * models.F('quantity'),
|
||||
output_field=models.DecimalField(max_digits=10, decimal_places=2))
|
||||
)['sum_total']
|
||||
if total:
|
||||
return total
|
||||
@@ -359,6 +376,7 @@ class Event(models.Model, RevisionMixin):
|
||||
"""
|
||||
Inc VAT
|
||||
"""
|
||||
|
||||
@property
|
||||
def total(self):
|
||||
return self.sum_total + self.vat
|
||||
@@ -383,7 +401,7 @@ class Event(models.Model, RevisionMixin):
|
||||
def earliest_time(self):
|
||||
"""Finds the earliest time defined in the event - this function could return either a tzaware datetime, or a naiive date object"""
|
||||
|
||||
#Put all the datetimes in a list
|
||||
# Put all the datetimes in a list
|
||||
datetime_list = []
|
||||
|
||||
if self.access_at:
|
||||
@@ -395,22 +413,22 @@ class Event(models.Model, RevisionMixin):
|
||||
# If there is no start time defined, pretend it's midnight
|
||||
startTimeFaked = False
|
||||
if self.has_start_time:
|
||||
startDateTime = datetime.datetime.combine(self.start_date,self.start_time)
|
||||
startDateTime = datetime.datetime.combine(self.start_date, self.start_time)
|
||||
else:
|
||||
startDateTime = datetime.datetime.combine(self.start_date,datetime.time(00,00))
|
||||
startDateTime = datetime.datetime.combine(self.start_date, datetime.time(00, 00))
|
||||
startTimeFaked = True
|
||||
|
||||
#timezoneIssues - apply the default timezone to the naiive datetime
|
||||
# timezoneIssues - apply the default timezone to the naiive datetime
|
||||
tz = pytz.timezone(settings.TIME_ZONE)
|
||||
startDateTime = tz.localize(startDateTime)
|
||||
datetime_list.append(startDateTime) # then add it to the list
|
||||
datetime_list.append(startDateTime) # then add it to the list
|
||||
|
||||
earliest = min(datetime_list).astimezone(tz) #find the earliest datetime in the list
|
||||
earliest = min(datetime_list).astimezone(tz) # find the earliest datetime in the list
|
||||
|
||||
# if we faked it & it's the earliest, better own up
|
||||
if startTimeFaked and earliest==startDateTime:
|
||||
if startTimeFaked and earliest == startDateTime:
|
||||
return self.start_date
|
||||
|
||||
|
||||
return earliest
|
||||
|
||||
@property
|
||||
@@ -422,7 +440,7 @@ class Event(models.Model, RevisionMixin):
|
||||
endDate = self.start_date
|
||||
|
||||
if self.has_end_time:
|
||||
endDateTime = datetime.datetime.combine(endDate,self.end_time)
|
||||
endDateTime = datetime.datetime.combine(endDate, self.end_time)
|
||||
tz = pytz.timezone(settings.TIME_ZONE)
|
||||
endDateTime = tz.localize(endDateTime)
|
||||
|
||||
@@ -431,7 +449,6 @@ class Event(models.Model, RevisionMixin):
|
||||
else:
|
||||
return endDate
|
||||
|
||||
|
||||
objects = EventManager()
|
||||
|
||||
def get_absolute_url(self):
|
||||
@@ -447,7 +464,7 @@ class Event(models.Model, RevisionMixin):
|
||||
startEndSameDay = not self.end_date or self.end_date == self.start_date
|
||||
hasStartAndEnd = self.has_start_time and self.has_end_time
|
||||
if startEndSameDay and hasStartAndEnd and self.start_time > self.end_time:
|
||||
raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.')
|
||||
raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Call :meth:`full_clean` before saving."""
|
||||
@@ -504,15 +521,6 @@ class Invoice(models.Model):
|
||||
|
||||
@property
|
||||
def payment_total(self):
|
||||
# Manual querying is required for efficiency whilst maintaining floating point arithmetic
|
||||
#if connection.vendor == 'postgresql':
|
||||
# sql = "SELECT SUM(amount) AS total FROM \"RIGS_payment\" WHERE invoice_id=%i" % self.id
|
||||
#else:
|
||||
# sql = "SELECT id, SUM(amount) AS total FROM RIGS_payment WHERE invoice_id=%i" % self.id
|
||||
#total = self.payment_set.raw(sql)[0]
|
||||
#if total.total:
|
||||
# return total.total
|
||||
#return 0.0
|
||||
total = self.payment_set.aggregate(total=models.Sum('amount'))['total']
|
||||
if total:
|
||||
return total
|
||||
@@ -522,6 +530,10 @@ class Invoice(models.Model):
|
||||
def balance(self):
|
||||
return self.sum_total - self.payment_total
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
return self.balance == 0 or self.void
|
||||
|
||||
def __str__(self):
|
||||
return "%i: %s (%.2f)" % (self.pk, self.event, self.balance)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user