Pylint happiness!

This commit is contained in:
Gergely Polonkai
2015-10-20 16:26:25 +02:00
parent 2db6e2dd24
commit d3ec0e8998
15 changed files with 548 additions and 173 deletions

View File

@@ -1,5 +1,12 @@
# -*- coding: utf-8
"""
Administration site definition for the Duck Booking Tool
"""
from django.contrib import admin
from booking.models import Species, Location, Competence, Duck, Booking, DuckCompetence, DuckName, DuckNameVote
from booking.models import Species, Location, Competence, Duck, \
Booking, DuckCompetence, DuckName, \
DuckNameVote
admin.site.register(Species)
admin.site.register(Location)

View File

@@ -1,19 +1,35 @@
# -*- coding: utf-8
"""
Duck level calculations
"""
from django.conf import settings
import math
def level_to_up_minutes(level):
"""
Convert duck level to up minutes
"""
if level == 0:
return 0
return 2 * pow(10, level)
def level_to_down_minutes(level):
"""
Convert duck level to down minutes
"""
if level == 0:
return 0
return 20 * pow(10, level)
def minutes_to_level(up_minutes, down_minutes):
"""
Convert booking minutes to duck level
"""
minutes = up_minutes + down_minutes / 10
level = 0 if minutes <= 0 else min(settings.MAX_DUCK_LEVEL, math.floor(math.log10(minutes)))

View File

@@ -1,32 +1,45 @@
# -*- coding: utf-8 -*-
"""
Models for the Duck Booking Tool
"""
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from django.conf import settings
from django.contrib.auth.models import User
from django.db import models
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from fuzzywuzzy import fuzz
from .ducklevel import minutes_to_level
@python_2_unicode_compatible
class Species(models.Model):
"""Model to hold the Ducks species"""
"""
Model to hold the Ducks species
"""
name = models.CharField(max_length=40, unique=True)
def __str__(self):
return self.name
@python_2_unicode_compatible
class Location(models.Model):
"""Model to hold the possible locations of the Ducks"""
"""
Model to hold the possible locations of the Ducks
"""
name = models.CharField(max_length=50)
def __str__(self):
return self.name
@python_2_unicode_compatible
class Competence(models.Model):
"""Model to hold Duck competences"""
"""
Model to hold Duck competences
"""
name = models.CharField(max_length=255, unique=True)
added_at = models.DateTimeField(default=timezone.now)
@@ -37,20 +50,27 @@ class Competence(models.Model):
@classmethod
def get_similar_comps(cls, name):
"""
Get competence names similar to name
"""
comps = cls.objects.values_list('name', flat=True)
ret = ()
for c in comps:
r = fuzz.ratio(name.lower(), c.lower())
for competence in comps:
similarity = fuzz.ratio(name.lower(), competence.lower())
# This ratio is subject to change
if r > settings.MIN_FUZZY_SIMILARITY:
ret = ret + (c,)
if similarity > settings.MIN_FUZZY_SIMILARITY:
ret = ret + (competence,)
return ret
@python_2_unicode_compatible
class Duck(models.Model):
"""Model to hold Duck data"""
"""
Model to hold Duck data
"""
name = models.CharField(max_length=80, null=True, blank=True)
color = models.CharField(max_length=6)
@@ -72,6 +92,11 @@ class Duck(models.Model):
return self.name
def age(self):
"""
Get the age of the duck (time since the duck has been registered
in the tool)
"""
seconds_d = timezone.now() - self.donated_at
seconds = seconds_d.total_seconds()
@@ -81,6 +106,10 @@ class Duck(models.Model):
return seconds
def dpx(self):
"""
Get the Duck Popularity indeX for this duck
"""
all_time = Booking.total_booking_time()
duck_time = Booking.duck_booking_time(self)
@@ -90,18 +119,25 @@ class Duck(models.Model):
return Booking.duck_booking_time(self) / Booking.total_booking_time()
def booked_by(self):
l = self.booking_set.filter(end_ts=None)
"""
Get the user who is currently using the duck
"""
if len(l) == 0:
booking_list = self.booking_set.filter(end_ts=None)
if len(booking_list) == 0:
return None
if len(l) > 1:
if len(booking_list) > 1:
raise RuntimeError(u"Duck is booked more than once!")
return l[0].user
return booking_list[0].user
@python_2_unicode_compatible
class DuckName(models.Model):
"""Model to hold name suggestions for Ducks"""
"""
Model to hold name suggestions for Ducks
"""
duck = models.ForeignKey(Duck)
name = models.CharField(max_length=60)
@@ -110,16 +146,31 @@ class DuckName(models.Model):
closed_by = models.ForeignKey(User, related_name='+')
closed_at = models.DateTimeField(null=True)
def __str__(self):
return "{0}, suggested by {1}".format(self.name,
self.suggested_by)
@python_2_unicode_compatible
class DuckNameVote(models.Model):
"""Model to hold votes to Duck names"""
"""
Model to hold votes to Duck names
"""
duck_name = models.ForeignKey(DuckName)
vote_timestamp = models.DateTimeField(default=timezone.now)
voter = models.ForeignKey(User)
upvote = models.BooleanField(default=True)
def __str__(self):
return "{0} voted {1} for {2}".format(self.voter,
"up" if upvote else "down",
self.duck_name)
@python_2_unicode_compatible
class DuckCompetence(models.Model):
"""Duck competence governor table"""
"""
Duck competence governor table
"""
duck = models.ForeignKey(Duck, related_name='competences')
comp = models.ForeignKey(Competence, related_name='ducks')
@@ -127,13 +178,26 @@ class DuckCompetence(models.Model):
down_minutes = models.IntegerField(default=0)
def level(self):
"""
Return the actual level of a duck
"""
return minutes_to_level(self.up_minutes, self.down_minutes)
def __str__(self):
return "{0} with +{1}/-{2} minutes in {3}".format(self.duck,
self.up_minutes,
self.down_minutes,
self.comp)
class Meta:
unique_together = ('duck', 'comp')
@python_2_unicode_compatible
class Booking(models.Model):
"""Duck booking governor table"""
"""
Duck booking governor table
"""
duck = models.ForeignKey(Duck)
user = models.ForeignKey(User)
@@ -144,6 +208,10 @@ class Booking(models.Model):
@classmethod
def total_booking_time(cls):
"""
Get the sum of booked hours for all ducks
"""
return cls.objects.filter(
start_ts__isnull=False,
end_ts__isnull=False).extra(
@@ -154,10 +222,19 @@ class Booking(models.Model):
@classmethod
def duck_booking_time(cls, duck):
"""
Get the sum of booked hours of a duck
"""
return cls.objects.filter(
start_ts__isnull=False,
end_ts__isnull=False, duck = duck).extra(
end_ts__isnull=False, duck=duck).extra(
select={
'amount': 'sum(strftime(%s, end_ts) - strftime(%s, start_ts))'
},
select_params=('%s', '%s'))[0].amount
def __str__(self):
return "{0} booked by {1} since {2}".format(self.duck,
self.user,
self.start_ts)

View File

@@ -1,17 +1,31 @@
# -*- coding: utf-8
"""
Template tags for the booking templates
"""
from django import template
import math
register = template.Library()
def is_number(s):
def is_number(string):
"""
Check if s is a number in string representation
"""
try:
float(s)
float(string)
return True
except ValueError:
return False
@register.filter
def age_format(value, arg = None):
def age_format(value, arg=None):
"""
Create human readable string from the duck age
"""
if not is_number(value):
return value
@@ -30,7 +44,7 @@ def age_format(value, arg = None):
remainder = remainder % 2592000
if months > 0:
if (ret != ""):
if ret != "":
ret += " "
ret += u"%d month%s" % (months, "" if months == 1 else "s")
@@ -45,7 +59,7 @@ def age_format(value, arg = None):
days = 1
if days > 0:
if (ret != ""):
if ret != "":
ret += " "
ret += u"%d day%s" % (days, "" if days == 1 else "s")
@@ -60,7 +74,7 @@ def age_format(value, arg = None):
remainder = remainder % 3600
if hours > 0:
if (ret != ""):
if ret != "":
ret += " "
ret += u"%d hour%s" % (hours, "" if hours == 1 else "s")
@@ -68,7 +82,7 @@ def age_format(value, arg = None):
minutes = math.floor(remainder / 60)
if minutes > 0:
if (ret != ""):
if ret != "":
ret += " "
ret += u"%d minute%s" % (minutes, "" if minutes == 1 else "s")
@@ -76,7 +90,7 @@ def age_format(value, arg = None):
seconds = round(remainder % 60)
if seconds > 0:
if (ret != ""):
if ret != "":
ret += " "
ret += u"%d second%s" % (seconds, "" if seconds == 1 else "s")

View File

@@ -1,70 +1,96 @@
# -*- coding: utf-8 -*-
from django.conf import settings
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.test import TestCase, Client
from django.utils import timezone
"""
Tests for the Duck Booking Tool frontend
"""
import datetime
from django.conf import settings
from django.contrib.auth.models import User
from django.test import TestCase, Client
from django.utils import timezone
from .ducklevel import level_to_up_minutes, level_to_down_minutes, minutes_to_level
from .templatetags import booking_tags
from .models import Duck, Competence, DuckCompetence, Species, Location, Booking
class FrontTest(TestCase):
"""
Test case for the front end
"""
def setUp(self):
self.client = Client()
def test_index_page(self):
"""
Test for the existence of the main page
"""
response = self.client.get('/')
self.assertEqual(response.status_code, 200)
def test_vocabulary_page(self):
"""
Test for the existence of the vocabulary page
"""
response = self.client.get('/vocabulary.html')
self.assertEqual(response.status_code, 200)
def test_terms_page(self):
"""
Test for the existence of the terms page
"""
response = self.client.get('/terms.html')
self.assertEqual(response.status_code, 200)
def test_disclaimer_page(self):
"""
Test for the existence of the disclaimer page
"""
response = self.client.get('/disclaimer.html')
self.assertEqual(response.status_code, 200)
class DuckCompLevelTest(TestCase):
"""
Test case for competence level calculation
"""
def setUp(self):
user = User.objects.create_user(username='test', password='test')
species = Species(name='test species')
species.save()
species = Species.objects.create(name='test species')
location = Location(name='test location')
location.save()
location = Location.objects.create(name='test location')
duck = Duck(
species=species,
location=location,
donated_by=user)
duck.save()
duck = Duck.objects.create(species=species,
location=location,
donated_by=user)
comp = Competence(
name='testing',
added_by=user)
comp.save()
comp = Competence.objects.create(name='testing',
added_by=user)
self.duckcomp = DuckCompetence(
duck = duck,
comp = comp,
up_minutes = 0,
down_minutes =0)
self.duckcomp = DuckCompetence.objects.create(duck=duck,
comp=comp,
up_minutes=0,
down_minutes=0)
def test_sane_max(self):
"""
Test if the MAX_DUCK_LEVEL setting has a sane value
"""
self.assertGreater(
settings.MAX_DUCK_LEVEL, 0,
msg = "MAX_DUCK_LEVEL must be greater than zero!")
msg="MAX_DUCK_LEVEL must be greater than zero!")
def test_max_minutes(self):
"""Test if level can not go above settings.MAX_DUCK_LEVEL)"""
"""
Test if level can not go above settings.MAX_DUCK_LEVEL)
"""
max_up_minutes = level_to_up_minutes(settings.MAX_DUCK_LEVEL)
double_minutes = level_to_up_minutes(settings.MAX_DUCK_LEVEL * 2)
@@ -86,6 +112,10 @@ class DuckCompLevelTest(TestCase):
self.assertEqual(level, settings.MAX_DUCK_LEVEL)
def test_conversions(self):
"""
Test minutes to level conversations
"""
for i in range(1, settings.MAX_DUCK_LEVEL):
up_minutes = level_to_up_minutes(i)
down_minutes = level_to_down_minutes(i)
@@ -93,12 +123,16 @@ class DuckCompLevelTest(TestCase):
up_level = minutes_to_level(up_minutes, 0)
down_level = minutes_to_level(0, down_minutes)
self.assertEqual(up_level, i, msg = "Test failed for value %d" % i)
self.assertEqual(up_level, i, msg="Test failed for value %d" % i)
self.assertEqual(
down_level, i,
msg = "Test failed for value %d" % i)
msg="Test failed for value %d" % i)
def test_level_to_minutes(self):
"""
Test level to minutes conversations
"""
self.assertEqual(level_to_up_minutes(0), 0)
self.assertEqual(level_to_up_minutes(1), 20)
self.assertEqual(level_to_up_minutes(2), 200)
@@ -114,11 +148,19 @@ class DuckCompLevelTest(TestCase):
self.assertEqual(level_to_down_minutes(5), 2000000)
def test_no_comp(self):
"""
Test if level equals 0 if minutes count is 0
"""
self.duckcomp.up_minutes = 0
self.duckcomp.down_minutes = 0
self.assertEquals(self.duckcomp.level(), 0)
def test_comp_levels(self):
"""
Test competence level calculation
"""
self.duckcomp.down_minutes = 0
for lvl in range(1, settings.MAX_DUCK_LEVEL):
@@ -127,16 +169,33 @@ class DuckCompLevelTest(TestCase):
self.assertEqual(self.duckcomp.level(), lvl)
def test_high_minutes(self):
"""
Test duck level calculation with a very high amount of minutes
"""
self.duckcomp.up_minutes = level_to_up_minutes(settings.MAX_DUCK_LEVEL)
self.duckcomp.down_minutes = level_to_down_minutes(settings.MAX_DUCK_LEVEL)
self.assertEqual(self.duckcomp.level(), settings.MAX_DUCK_LEVEL)
class DuckAgeTest(TestCase):
"""
Tests related to duck age
"""
def test_duck_is_from_the_future(self):
future_duck = Duck(donated_at = timezone.now() + datetime.timedelta(days = 2))
"""
Test if the duck came from the future (ie. donation time is in
the future)
"""
future_duck = Duck(donated_at=timezone.now() + datetime.timedelta(days=2))
self.assertEqual(future_duck.age(), -1)
def test_duck_age_formatter(self):
"""
Test duck age formatter
"""
self.assertEqual(booking_tags.age_format("aoeu"), "aoeu")
self.assertEqual(booking_tags.age_format(0), "a few moments")
self.assertEqual(booking_tags.age_format(1), "1 second")
@@ -173,75 +232,105 @@ class DuckAgeTest(TestCase):
self.assertEqual(booking_tags.age_format(63072000), "2 years")
class BookingTimeTest(TestCase):
duck1 = None
duck2 = None
"""
Test case for calculating booking time and popularity
"""
def setUp(self):
user = User()
user.save()
species = Species(name = 'duck')
species.save()
species = Species.objects.create(name='duck')
location = Location.objects.create(name='start')
location = Location(name = 'start')
location.save()
self.duck1 = Duck.objects.create(species=species,
location=location,
donated_by=user)
self.duck1 = Duck(species = species, location = location, donated_by = user)
self.duck1.save()
competence = Competence(name = 'test', added_by = user)
competence.save()
competence = Competence.objects.create(name='test',
added_by=user)
now = timezone.now()
booking = Booking(duck = self.duck1, start_ts = now - datetime.timedelta(days = 2), end_ts = now - datetime.timedelta(days = 1), user = user, comp_req = competence)
booking.save()
Booking.objects.create(duck=self.duck1,
start_ts=now - datetime.timedelta(days=2),
end_ts=now - datetime.timedelta(days=1),
user=user,
comp_req=competence)
self.duck2 = Duck(species = species, location = location, donated_by = user)
self.duck2.save()
self.duck2 = Duck.objects.create(species=species,
location=location,
donated_by=user)
booking = Booking(duck = self.duck2, start_ts = now - datetime.timedelta(days = 3), end_ts = now - datetime.timedelta(days = 2), user = user, comp_req = competence)
booking.save()
Booking.objects.create(duck=self.duck2,
start_ts=now - datetime.timedelta(days=3),
end_ts=now - datetime.timedelta(days=2),
user=user,
comp_req=competence)
booking = Booking(duck = self.duck2, start_ts = now - datetime.timedelta(days = 2), end_ts = now - datetime.timedelta(days = 1), user = user, comp_req = competence)
booking.save()
Booking.objects.create(duck=self.duck2,
start_ts=now - datetime.timedelta(days=2),
end_ts=now - datetime.timedelta(days=1),
user=user,
comp_req=competence)
def test_total_booking_time(self):
self.assertEqual(Booking.total_booking_time(), 259200)
"""
Test total booking time
"""
self.assertEqual(259200, Booking.total_booking_time())
def test_duck_booking_time(self):
self.assertEqual(Booking.duck_booking_time(self.duck1), 86400)
self.assertEqual(Booking.duck_booking_time(self.duck2), 172800)
"""
Test duck booking time
"""
self.assertEqual(86400, Booking.duck_booking_time(self.duck1))
self.assertEqual(172800, Booking.duck_booking_time(self.duck2))
def test_dpx(self):
self.assertEqual(self.duck1.dpx(), 1/3)
self.assertEqual(self.duck2.dpx(), 2/3)
"""
Test Duck Popularity indeX calculation
"""
self.assertEqual(1/3, self.duck1.dpx())
self.assertEqual(2/3, self.duck2.dpx())
class TestListing(TestCase):
"""
Test case for duck listing
"""
def setUp(self):
self.client = Client()
species = Species()
species.save()
species = Species.objects.create()
loc = Location.objects.create()
user = User.objects.create_user(username='test',
password='test')
loc = Location()
loc.save()
user = User()
user.save()
self.duck = Duck(species = species, location = loc, donated_by = user)
self.duck.save()
self.duck = Duck.objects.create(species=species,
location=loc,
donated_by=user)
def test_front_page(self):
response = self.client.get('/')
self.assertEqual(response.status_code, 200)
"""
Test existence of the front page
"""
self.assertEqual(len(response.context['duck_list']), 1)
response = self.client.get('/')
self.assertEqual(200, response.status_code)
self.assertEqual(1, len(response.context['duck_list']))
class SimilarCompTest(TestCase):
"""
Test case for competence name fuzzy search
"""
def setUp(self):
admin = User(username = 'admin')
admin.save()
admin = User.objects.create_user(username='admin',
password='test')
competence_list = (
'Creativity',
@@ -251,54 +340,75 @@ class SimilarCompTest(TestCase):
'TCSH',
)
for c in competence_list:
comp = Competence(name = c, added_by = admin)
comp.save()
for competence in competence_list:
Competence.objects.create(name=competence,
added_by=admin)
def test_good_similar_competences(self):
l = Competence.get_similar_comps('perl')
self.assertEquals(len(l), 1)
"""
Test similar competence list with different inputs
"""
l = Competence.get_similar_comps('pzthon')
self.assertEquals(len(l), 1)
comp_list = Competence.get_similar_comps('perl')
self.assertEquals(1, len(comp_list))
l = Competence.get_similar_comps(u'kreativitás')
self.assertEqual(len(l), 1)
comp_list = Competence.get_similar_comps('pzthon')
self.assertEquals(1, len(comp_list))
comp_list = Competence.get_similar_comps(u'kreativitás')
self.assertEqual(1, len(comp_list))
def test_bad_similar_competence(self):
l = Competence.get_similar_comps('development')
self.assertEqual(len(l), 0)
"""
Test similar competence list with a totally new and unmatching
competence name
"""
comp_list = Competence.get_similar_comps('development')
self.assertEqual(0, len(comp_list))
class BookingTest(TestCase):
"""
Test duck booking functionality
"""
def setUp(self):
spec = Species.objects.create(name='test')
loc = Location.objects.create(name='test')
self.spec = Species.objects.create(name='test')
self.loc = Location.objects.create(name='test')
self.user = User.objects.create_user(username='test')
self.booked_duck = Duck.objects.create(species=spec,
location=loc,
self.booked_duck = Duck.objects.create(species=self.spec,
location=self.loc,
donated_by=self.user)
self.comp = Competence.objects.create(name='test',
added_by=self.user)
booking = Booking.objects.create(duck=self.booked_duck,
user=self.user,
comp_req=self.comp)
self.unbooked_duck = Duck.objects.create(species=spec,
location=loc,
donated_by=self.user)
Booking.objects.create(duck=self.booked_duck,
user=self.user,
comp_req=self.comp)
def test_booked_duck(self):
"""
Test if booked duck returns the booking user from booked_by()
"""
self.assertNotEqual(self.booked_duck.booked_by(), None)
def test_unbooked_duck(self):
self.assertEqual(self.unbooked_duck.booked_by(), None)
"""
Test if unbooked duck returns None from booked_by()
"""
unbooked_duck = Duck.objects.create(species=self.spec,
location=self.loc,
donated_by=self.user)
self.assertEqual(unbooked_duck.booked_by(), None)
def test_multiple_booking(self):
"""
Test error presence in case of multiple bookings for the same
duck
"""
Booking.objects.create(duck=self.booked_duck,
user=self.user,
comp_req=self.comp)

View File

@@ -1,24 +1,28 @@
from django.conf.urls import patterns, url
# -*- coding: utf-8
"""
URL definitions for the Duck Booking Tool frontend
"""
from django.conf.urls import url
from django.views.generic.base import TemplateView
from .views import DuckListView
urlpatterns = patterns(
'',
url(r'^$', DuckListView.as_view(), name = 'list'),
urlpatterns = [
url(r'^$', DuckListView.as_view(), name='list'),
url(
r'^vocabulary.html$',
TemplateView.as_view(template_name = 'booking/vocabulary.html'),
name = 'vocabulary'
TemplateView.as_view(template_name='booking/vocabulary.html'),
name='vocabulary'
),
url(
r'^terms.html$',
TemplateView.as_view(template_name = 'booking/terms.html'),
name = 'terms'
TemplateView.as_view(template_name='booking/terms.html'),
name='terms'
),
url(
r'^disclaimer.html$',
TemplateView.as_view(template_name = 'booking/disclaimer.html'),
name = 'disclaimer'
TemplateView.as_view(template_name='booking/disclaimer.html'),
name='disclaimer'
),
)
]

View File

@@ -1,9 +1,17 @@
from django.shortcuts import render
# -*- coding: utf-8
"""
Views for the Duck Booking Tool frontend
"""
from django.views import generic
from .models import Duck
class DuckListView(generic.ListView):
"""
View for duck listing (the main page)
"""
template_name = 'booking/duck_list.html'
context_object_name = 'duck_list'