2015-05-27 20:18:56 +00:00
|
|
|
|
# -*- coding: utf-8 -*-
|
2015-10-20 14:26:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Models for the Duck Booking Tool
|
|
|
|
|
"""
|
2015-05-27 20:18:56 +00:00
|
|
|
|
|
2015-10-20 14:26:25 +00:00
|
|
|
|
from django.conf import settings
|
2014-12-22 13:24:10 +00:00
|
|
|
|
from django.contrib.auth.models import User
|
2015-10-20 14:26:25 +00:00
|
|
|
|
from django.db import models
|
2014-12-22 13:25:55 +00:00
|
|
|
|
from django.utils import timezone
|
2015-10-20 14:26:25 +00:00
|
|
|
|
from django.utils.encoding import python_2_unicode_compatible
|
2015-01-19 14:36:50 +00:00
|
|
|
|
|
2015-01-06 14:47:34 +00:00
|
|
|
|
from fuzzywuzzy import fuzz
|
|
|
|
|
|
2015-01-19 14:36:50 +00:00
|
|
|
|
from .ducklevel import minutes_to_level
|
2014-12-22 13:22:38 +00:00
|
|
|
|
|
2015-10-20 14:26:25 +00:00
|
|
|
|
@python_2_unicode_compatible
|
2014-12-22 13:22:38 +00:00
|
|
|
|
class Species(models.Model):
|
2015-10-20 14:26:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Model to hold the Ducks’ species
|
|
|
|
|
"""
|
2014-12-22 13:22:38 +00:00
|
|
|
|
|
2015-10-19 12:58:32 +00:00
|
|
|
|
name = models.CharField(max_length=40, unique=True)
|
2014-12-22 13:22:38 +00:00
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return self.name
|
2014-12-22 13:52:17 +00:00
|
|
|
|
|
2015-10-20 14:26:25 +00:00
|
|
|
|
@python_2_unicode_compatible
|
2014-12-22 13:52:17 +00:00
|
|
|
|
class Location(models.Model):
|
2015-10-20 14:26:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Model to hold the possible locations of the Ducks
|
|
|
|
|
"""
|
2014-12-22 13:52:17 +00:00
|
|
|
|
|
2015-10-19 12:58:32 +00:00
|
|
|
|
name = models.CharField(max_length=50)
|
2014-12-22 13:52:17 +00:00
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return self.name
|
2014-12-22 13:24:10 +00:00
|
|
|
|
|
2015-10-20 14:26:25 +00:00
|
|
|
|
@python_2_unicode_compatible
|
2014-12-22 13:24:10 +00:00
|
|
|
|
class Competence(models.Model):
|
2015-10-20 14:26:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Model to hold Duck competences
|
|
|
|
|
"""
|
2014-12-22 13:24:10 +00:00
|
|
|
|
|
2015-10-19 12:58:32 +00:00
|
|
|
|
name = models.CharField(max_length=255, unique=True)
|
|
|
|
|
added_at = models.DateTimeField(default=timezone.now)
|
2014-12-22 13:24:10 +00:00
|
|
|
|
added_by = models.ForeignKey(User)
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return self.name
|
2014-12-22 13:25:55 +00:00
|
|
|
|
|
2015-05-27 20:35:14 +00:00
|
|
|
|
@classmethod
|
|
|
|
|
def get_similar_comps(cls, name):
|
2015-10-20 14:26:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Get competence names similar to name
|
|
|
|
|
"""
|
|
|
|
|
|
2015-10-19 12:58:32 +00:00
|
|
|
|
comps = cls.objects.values_list('name', flat=True)
|
2015-01-06 14:47:34 +00:00
|
|
|
|
ret = ()
|
|
|
|
|
|
2015-10-20 14:26:25 +00:00
|
|
|
|
for competence in comps:
|
|
|
|
|
similarity = fuzz.ratio(name.lower(), competence.lower())
|
2015-01-06 14:47:34 +00:00
|
|
|
|
|
|
|
|
|
# This ratio is subject to change
|
2015-10-20 14:26:25 +00:00
|
|
|
|
if similarity > settings.MIN_FUZZY_SIMILARITY:
|
|
|
|
|
ret = ret + (competence,)
|
2015-01-06 14:47:34 +00:00
|
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
2015-10-20 14:26:25 +00:00
|
|
|
|
@python_2_unicode_compatible
|
2014-12-22 13:25:55 +00:00
|
|
|
|
class Duck(models.Model):
|
2015-10-20 14:26:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Model to hold Duck data
|
|
|
|
|
"""
|
2014-12-22 13:25:55 +00:00
|
|
|
|
|
2015-10-19 12:58:32 +00:00
|
|
|
|
name = models.CharField(max_length=80, null=True, blank=True)
|
|
|
|
|
color = models.CharField(max_length=6)
|
2014-12-22 13:25:55 +00:00
|
|
|
|
species = models.ForeignKey(Species)
|
|
|
|
|
location = models.ForeignKey(Location)
|
2015-10-19 12:58:32 +00:00
|
|
|
|
comps = models.ManyToManyField(Competence, through='DuckCompetence')
|
2014-12-22 13:25:55 +00:00
|
|
|
|
donated_by = models.ForeignKey(User)
|
2015-10-19 12:58:32 +00:00
|
|
|
|
donated_at = models.DateTimeField(default=timezone.now)
|
|
|
|
|
adopted_by = models.ForeignKey(User, related_name='adopted_ducks', null=True, blank=True)
|
|
|
|
|
adopted_at = models.DateTimeField(null=True, blank=True)
|
|
|
|
|
bookings = models.ManyToManyField(User, through='Booking', related_name='+')
|
|
|
|
|
on_holiday_since = models.DateTimeField(null=True, blank=True)
|
|
|
|
|
on_holiday_until = models.DateTimeField(null=True, blank=True)
|
2014-12-22 13:25:55 +00:00
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
if self.name == None or self.name == '':
|
|
|
|
|
return 'Unnamed :('
|
|
|
|
|
|
|
|
|
|
return self.name
|
2014-12-22 13:28:03 +00:00
|
|
|
|
|
2014-12-17 10:08:13 +00:00
|
|
|
|
def age(self):
|
2015-10-20 14:26:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Get the age of the duck (time since the duck has been registered
|
|
|
|
|
in the tool)
|
|
|
|
|
"""
|
|
|
|
|
|
2014-12-17 10:08:13 +00:00
|
|
|
|
seconds_d = timezone.now() - self.donated_at
|
|
|
|
|
seconds = seconds_d.total_seconds()
|
|
|
|
|
|
|
|
|
|
if seconds < 0:
|
|
|
|
|
return -1
|
|
|
|
|
|
|
|
|
|
return seconds
|
|
|
|
|
|
2014-12-23 08:26:03 +00:00
|
|
|
|
def dpx(self):
|
2015-10-20 14:26:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Get the Duck Popularity indeX for this duck
|
|
|
|
|
"""
|
|
|
|
|
|
2014-12-23 08:26:03 +00:00
|
|
|
|
all_time = Booking.total_booking_time()
|
|
|
|
|
duck_time = Booking.duck_booking_time(self)
|
|
|
|
|
|
|
|
|
|
if (all_time == None) or (duck_time == None):
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
return Booking.duck_booking_time(self) / Booking.total_booking_time()
|
|
|
|
|
|
2015-01-22 15:12:54 +00:00
|
|
|
|
def booked_by(self):
|
2015-10-20 14:26:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Get the user who is currently using the duck
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
booking_list = self.booking_set.filter(end_ts=None)
|
2015-01-22 15:12:54 +00:00
|
|
|
|
|
2015-10-20 14:26:25 +00:00
|
|
|
|
if len(booking_list) == 0:
|
2015-01-22 15:12:54 +00:00
|
|
|
|
return None
|
|
|
|
|
|
2015-10-20 14:26:25 +00:00
|
|
|
|
if len(booking_list) > 1:
|
2015-01-22 15:12:54 +00:00
|
|
|
|
raise RuntimeError(u"Duck is booked more than once!")
|
|
|
|
|
|
2015-10-20 14:26:25 +00:00
|
|
|
|
return booking_list[0].user
|
2015-01-22 15:12:54 +00:00
|
|
|
|
|
2015-10-20 14:26:25 +00:00
|
|
|
|
@python_2_unicode_compatible
|
2014-12-23 08:02:27 +00:00
|
|
|
|
class DuckName(models.Model):
|
2015-10-20 14:26:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Model to hold name suggestions for Ducks
|
|
|
|
|
"""
|
2014-12-23 08:02:27 +00:00
|
|
|
|
|
|
|
|
|
duck = models.ForeignKey(Duck)
|
2015-10-20 15:13:07 +00:00
|
|
|
|
name = models.CharField(max_length=60, null=False)
|
2014-12-23 08:02:27 +00:00
|
|
|
|
suggested_by = models.ForeignKey(User)
|
2015-10-19 12:58:32 +00:00
|
|
|
|
suggested_at = models.DateTimeField(default=timezone.now)
|
2015-10-20 15:13:07 +00:00
|
|
|
|
closed_by = models.ForeignKey(User,
|
|
|
|
|
related_name='+',
|
|
|
|
|
null=True,
|
|
|
|
|
blank=True)
|
|
|
|
|
closed_at = models.DateTimeField(null=True, blank=True)
|
2014-12-23 08:02:27 +00:00
|
|
|
|
|
2015-10-20 14:26:25 +00:00
|
|
|
|
def __str__(self):
|
2015-10-20 15:13:07 +00:00
|
|
|
|
ret = "{0}, suggested by {1}".format(self.name,
|
2015-10-20 14:26:25 +00:00
|
|
|
|
self.suggested_by)
|
|
|
|
|
|
2015-10-20 15:13:07 +00:00
|
|
|
|
if self.closed_by:
|
|
|
|
|
ret += " <closed>"
|
|
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
2015-10-20 14:26:25 +00:00
|
|
|
|
@python_2_unicode_compatible
|
2014-12-23 08:02:27 +00:00
|
|
|
|
class DuckNameVote(models.Model):
|
2015-10-20 14:26:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Model to hold votes to Duck names
|
|
|
|
|
"""
|
2014-12-23 08:02:27 +00:00
|
|
|
|
|
|
|
|
|
duck_name = models.ForeignKey(DuckName)
|
2015-10-19 12:58:32 +00:00
|
|
|
|
vote_timestamp = models.DateTimeField(default=timezone.now)
|
2014-12-23 08:02:27 +00:00
|
|
|
|
voter = models.ForeignKey(User)
|
2015-10-19 12:58:32 +00:00
|
|
|
|
upvote = models.BooleanField(default=True)
|
2014-12-23 08:02:27 +00:00
|
|
|
|
|
2015-10-20 14:26:25 +00:00
|
|
|
|
def __str__(self):
|
|
|
|
|
return "{0} voted {1} for {2}".format(self.voter,
|
2015-10-20 15:13:07 +00:00
|
|
|
|
"up" if self.upvote else "down",
|
2015-10-20 14:26:25 +00:00
|
|
|
|
self.duck_name)
|
|
|
|
|
|
|
|
|
|
@python_2_unicode_compatible
|
2014-12-22 13:28:03 +00:00
|
|
|
|
class DuckCompetence(models.Model):
|
2015-10-20 14:26:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Duck competence governor table
|
|
|
|
|
"""
|
2014-12-22 13:28:03 +00:00
|
|
|
|
|
2015-10-19 15:03:17 +00:00
|
|
|
|
duck = models.ForeignKey(Duck, related_name='competences')
|
|
|
|
|
comp = models.ForeignKey(Competence, related_name='ducks')
|
2015-10-19 12:58:32 +00:00
|
|
|
|
up_minutes = models.IntegerField(default=0)
|
|
|
|
|
down_minutes = models.IntegerField(default=0)
|
2014-12-22 13:28:03 +00:00
|
|
|
|
|
2015-01-19 14:36:50 +00:00
|
|
|
|
def level(self):
|
2015-10-20 14:26:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Return the actual level of a duck
|
|
|
|
|
"""
|
|
|
|
|
|
2015-01-19 14:36:50 +00:00
|
|
|
|
return minutes_to_level(self.up_minutes, self.down_minutes)
|
|
|
|
|
|
2015-10-20 14:26:25 +00:00
|
|
|
|
def __str__(self):
|
|
|
|
|
return "{0} with +{1}/-{2} minutes in {3}".format(self.duck,
|
|
|
|
|
self.up_minutes,
|
|
|
|
|
self.down_minutes,
|
|
|
|
|
self.comp)
|
|
|
|
|
|
2014-12-22 13:28:03 +00:00
|
|
|
|
class Meta:
|
|
|
|
|
unique_together = ('duck', 'comp')
|
2014-12-22 13:29:28 +00:00
|
|
|
|
|
2015-10-20 14:26:25 +00:00
|
|
|
|
@python_2_unicode_compatible
|
2014-12-22 13:29:28 +00:00
|
|
|
|
class Booking(models.Model):
|
2015-10-20 14:26:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Duck booking governor table
|
|
|
|
|
"""
|
2014-12-22 13:29:28 +00:00
|
|
|
|
|
|
|
|
|
duck = models.ForeignKey(Duck)
|
|
|
|
|
user = models.ForeignKey(User)
|
|
|
|
|
comp_req = models.ForeignKey(Competence)
|
2015-10-19 12:58:32 +00:00
|
|
|
|
start_ts = models.DateTimeField(default=timezone.now)
|
|
|
|
|
end_ts = models.DateTimeField(null=True, blank=True)
|
|
|
|
|
successful = models.BooleanField(default=True)
|
2015-01-19 14:43:30 +00:00
|
|
|
|
|
2015-05-27 20:35:14 +00:00
|
|
|
|
@classmethod
|
|
|
|
|
def total_booking_time(cls):
|
2015-10-20 14:26:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Get the sum of booked hours for all ducks
|
|
|
|
|
"""
|
|
|
|
|
|
2015-10-19 12:58:32 +00:00
|
|
|
|
return cls.objects.filter(
|
|
|
|
|
start_ts__isnull=False,
|
|
|
|
|
end_ts__isnull=False).extra(
|
|
|
|
|
select={
|
|
|
|
|
'amount': 'sum(strftime(%s, end_ts) - strftime(%s, start_ts))'
|
|
|
|
|
},
|
|
|
|
|
select_params=('%s', '%s'))[0].amount
|
2014-12-23 08:25:44 +00:00
|
|
|
|
|
2015-05-27 20:35:14 +00:00
|
|
|
|
@classmethod
|
|
|
|
|
def duck_booking_time(cls, duck):
|
2015-10-20 14:26:25 +00:00
|
|
|
|
"""
|
|
|
|
|
Get the sum of booked hours of a duck
|
|
|
|
|
"""
|
|
|
|
|
|
2015-10-19 12:58:32 +00:00
|
|
|
|
return cls.objects.filter(
|
|
|
|
|
start_ts__isnull=False,
|
2015-10-20 14:26:25 +00:00
|
|
|
|
end_ts__isnull=False, duck=duck).extra(
|
2015-10-19 12:58:32 +00:00
|
|
|
|
select={
|
|
|
|
|
'amount': 'sum(strftime(%s, end_ts) - strftime(%s, start_ts))'
|
|
|
|
|
},
|
|
|
|
|
select_params=('%s', '%s'))[0].amount
|
2015-10-20 14:26:25 +00:00
|
|
|
|
|
|
|
|
|
def __str__(self):
|
2015-10-20 15:13:07 +00:00
|
|
|
|
return "{0} booked by {1} for {2} since {3}".format(self.duck,
|
|
|
|
|
self.user,
|
|
|
|
|
self.comp_req,
|
|
|
|
|
self.start_ts)
|