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:
David Taylor
2017-05-10 17:27:52 +01:00
52 changed files with 3222 additions and 422 deletions

View File

@@ -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)