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,22 +1,37 @@
# -*- coding: utf-8
"""
Account management module for the Duck Booking Tool backend
"""
from django.test import TestCase, Client from django.test import TestCase, Client
from django_webtest import WebTest from django_webtest import WebTest
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.forms import UserCreationForm
class FrontTest(TestCase): class FrontTest(TestCase):
"""
Test front-end capabilities of the accounts module
"""
def setUp(self): def setUp(self):
self.client = Client() self.client = Client()
self.admin = User.objects.create_user( self.admin = User.objects.create_user(username='admin',
username = 'admin', password='password')
password = 'password')
self.admin.save()
def test_login_page(self): def test_login_page(self):
"""
Test for the existence of the login page
"""
response = self.client.get('/accounts/login') response = self.client.get('/accounts/login')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_login(self): def test_login(self):
"""
Test login functionality
"""
response = self.client.post('/accounts/login', { response = self.client.post('/accounts/login', {
'next': '/', 'next': '/',
'username': 'admin', 'username': 'admin',
@ -25,17 +40,33 @@ class FrontTest(TestCase):
self.assertRedirects(response, '/') self.assertRedirects(response, '/')
def test_logout(self): def test_logout(self):
self.client.login(username = 'admin', password = 'aeou') """
Test the logout page
"""
self.client.login(username='admin', password='aeou')
response = self.client.get('/accounts/logout') response = self.client.get('/accounts/logout')
self.assertRedirects(response, '/') self.assertRedirects(response, '/')
def test_registration_page(self): def test_registration_page(self):
"""
Test for existence of the registration page
"""
response = self.client.get('/accounts/register') response = self.client.get('/accounts/register')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
class RegFormTest(WebTest): class RegFormTest(WebTest):
"""
Test case for the registration form
"""
def test_valid_data(self): def test_valid_data(self):
"""
Test valid registration without actual HTTP requests
"""
form_data = { form_data = {
'username': 'test', 'username': 'test',
'password1': 'password', 'password1': 'password',
@ -51,6 +82,10 @@ class RegFormTest(WebTest):
self.assertNotEqual(user.password, 'password') self.assertNotEqual(user.password, 'password')
def test_empty(self): def test_empty(self):
"""
Test empty registration form
"""
form_data = {} form_data = {}
form = UserCreationForm(form_data) form = UserCreationForm(form_data)
self.assertFalse(form.is_valid()) self.assertFalse(form.is_valid())
@ -61,11 +96,19 @@ class RegFormTest(WebTest):
}) })
def test_form_error(self): def test_form_error(self):
"""
Test incomplete registration
"""
page = self.app.get('/accounts/register') page = self.app.get('/accounts/register')
page = page.form.submit() page = page.form.submit()
self.assertContains(page, "This field is required.") self.assertContains(page, "This field is required.")
def test_form_success(self): def test_form_success(self):
"""
Test for successful registrations
"""
page = self.app.get('/accounts/register') page = self.app.get('/accounts/register')
page.form['username'] = 'test' page.form['username'] = 'test'
page.form['password1'] = 'password' page.form['password1'] = 'password'

View File

@ -1,24 +1,28 @@
from django.conf.urls import patterns, url # -*- coding: utf-8
"""
URL patterns for the accounts module
"""
from django.conf.urls import url
from .views import RegistrationFormView from .views import RegistrationFormView
urlpatterns = patterns( urlpatterns = [
'',
url( url(
r'^register$', r'^register$',
RegistrationFormView.as_view(), RegistrationFormView.as_view(),
name = 'register' name='register'
), ),
url( url(
r'^login$', r'^login$',
'django.contrib.auth.views.login', 'django.contrib.auth.views.login',
{'template_name': 'accounts/login.html'}, {'template_name': 'accounts/login.html'},
name = 'login' name='login'
), ),
url( url(
r'^logout$', r'^logout$',
'django.contrib.auth.views.logout', 'django.contrib.auth.views.logout',
{'next_page': 'booking:list'}, {'next_page': 'booking:list'},
name = 'logout' name='logout'
), ),
) ]

View File

@ -1,3 +1,8 @@
# -*- coding: utf-8
"""
Views for the accounts module
"""
from django.shortcuts import render from django.shortcuts import render
from django.views import generic from django.views import generic
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
@ -5,14 +10,26 @@ from django.core.urlresolvers import reverse
from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.forms import UserCreationForm
class RegistrationFormView(generic.View): class RegistrationFormView(generic.View):
"""
Class to display the registration form
"""
form_class = UserCreationForm form_class = UserCreationForm
template_name = 'accounts/registration.html' template_name = 'accounts/registration.html'
def get(self, request): def get(self, request):
"""
Implementation of the GET method
"""
form = self.form_class() form = self.form_class()
return render(request, self.template_name, { 'form': form }) return render(request, self.template_name, {'form': form})
def post(self, request): def post(self, request):
"""
Implementation of the POST method
"""
form = self.form_class(request.POST) form = self.form_class(request.POST)
if form.is_valid(): if form.is_valid():

View File

@ -1,10 +1,18 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
Serializers for the Duck Booking Tool API
"""
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from rest_framework import serializers from rest_framework import serializers
from booking.models import Duck, Competence, DuckCompetence from booking.models import Duck, Competence, DuckCompetence
class NamespacedSerializer(serializers.HyperlinkedModelSerializer): class NamespacedSerializer(serializers.HyperlinkedModelSerializer):
"""
HyperlinkedModelSerializer with URL namespace support
"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if not hasattr(self.Meta, 'url_namespace') or self.Meta.url_namespace is None: if not hasattr(self.Meta, 'url_namespace') or self.Meta.url_namespace is None:
raise ImproperlyConfigured("namespace must be set!") raise ImproperlyConfigured("namespace must be set!")
@ -17,7 +25,7 @@ class NamespacedSerializer(serializers.HyperlinkedModelSerializer):
if not self.url_namespace.endswith(':'): if not self.url_namespace.endswith(':'):
self.url_namespace += ':' self.url_namespace += ':'
return super(NamespacedSerializer, self).__init__(*args, **kwargs) super(NamespacedSerializer, self).__init__(*args, **kwargs)
def build_url_field(self, field_name, model_class): def build_url_field(self, field_name, model_class):
field_class, field_kwargs = super(NamespacedSerializer, self) \ field_class, field_kwargs = super(NamespacedSerializer, self) \
@ -32,12 +40,20 @@ class NamespacedSerializer(serializers.HyperlinkedModelSerializer):
return field_class, field_kwargs return field_class, field_kwargs
class CompetenceSerializer(NamespacedSerializer): class CompetenceSerializer(NamespacedSerializer):
"""
Serializer for Competence objects
"""
class Meta: class Meta:
url_namespace = 'api' url_namespace = 'api'
model = Competence model = Competence
fields = ('url', 'name',) fields = ('url', 'name',)
class DuckCompetenceSerializer(NamespacedSerializer): class DuckCompetenceSerializer(NamespacedSerializer):
"""
Serializer for DuckCompetence objects
"""
comp = CompetenceSerializer() comp = CompetenceSerializer()
class Meta: class Meta:
@ -46,6 +62,10 @@ class DuckCompetenceSerializer(NamespacedSerializer):
fields = ('comp', 'up_minutes', 'down_minutes',) fields = ('comp', 'up_minutes', 'down_minutes',)
class DuckSerializer(NamespacedSerializer): class DuckSerializer(NamespacedSerializer):
"""
Serializer for Duck objects
"""
competences = DuckCompetenceSerializer(many=True) competences = DuckCompetenceSerializer(many=True)
class Meta: class Meta:

View File

@ -1,5 +1,8 @@
# -*- coding: utf-8 # -*- coding: utf-8
from django.test import TestCase, Client """
Test cases for API calls
"""
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings from django.conf import settings
from django_webtest import WebTest from django_webtest import WebTest
@ -10,6 +13,10 @@ from booking.ducklevel import level_to_up_minutes
from booking.models import Species, Location, Duck, Competence, DuckCompetence from booking.models import Species, Location, Duck, Competence, DuckCompetence
class DuckClassTest(WebTest): class DuckClassTest(WebTest):
"""
Test case for duck related API calls
"""
csrf_checks = False csrf_checks = False
def setUp(self): def setUp(self):
@ -46,10 +53,18 @@ class DuckClassTest(WebTest):
down_minutes=0) down_minutes=0)
def test_book_nonlogged(self): def test_book_nonlogged(self):
"""
Test booking without logging in
"""
page = self.app.post('/api/v1/ducks/1/book/', expect_errors=True) page = self.app.post('/api/v1/ducks/1/book/', expect_errors=True)
self.assertEqual(page.status_code, 403) self.assertEqual(page.status_code, 403)
def test_book_nonexist(self): def test_book_nonexist(self):
"""
Test booking a non-existing duck
"""
# Try to book a non-existing duck # Try to book a non-existing duck
page = self.app.post( page = self.app.post(
'/api/v1/ducks/9999/book/', '/api/v1/ducks/9999/book/',
@ -71,6 +86,10 @@ class DuckClassTest(WebTest):
self.assertEqual(404, page.status_code) self.assertEqual(404, page.status_code)
def test_book_warn(self): def test_book_warn(self):
"""
Test duck booking for a competence the duck is not good at
"""
url = '/api/v1/ducks/%d/book/' % self.duck.pk url = '/api/v1/ducks/%d/book/' % self.duck.pk
comp_none = Competence.objects.create(name='test3', comp_none = Competence.objects.create(name='test3',
added_by=self.user) added_by=self.user)
@ -107,6 +126,10 @@ class DuckClassTest(WebTest):
self.assertEquals(page_json['status'], 'ok') self.assertEquals(page_json['status'], 'ok')
def test_book_good(self): def test_book_good(self):
"""
Test duck booking for a competence the duck is good at
"""
test_data = { test_data = {
"competence": self.comp_good.pk "competence": self.comp_good.pk
} }
@ -127,6 +150,10 @@ class DuckClassTest(WebTest):
self.assertEqual('already-booked', page_json['status']) self.assertEqual('already-booked', page_json['status'])
def test_duck_donation(self): def test_duck_donation(self):
"""
Test duck donating functionality
"""
# Duck donation should not be allowed without logging in # Duck donation should not be allowed without logging in
page = self.app.get('/api/v1/ducks/donate/', expect_errors=True) page = self.app.get('/api/v1/ducks/donate/', expect_errors=True)
self.assertEquals(page.status_code, 403) self.assertEquals(page.status_code, 403)
@ -135,16 +162,19 @@ class DuckClassTest(WebTest):
page = self.app.post('/api/v1/ducks/donate/', expect_errors=True) page = self.app.post('/api/v1/ducks/donate/', expect_errors=True)
self.assertEquals(page.status_code, 403) self.assertEquals(page.status_code, 403)
page = self.app.post( self.app.post(
'/api/v1/ducks/donate/', '/api/v1/ducks/donate/',
params={ params={
'species': 1, 'species': 1,
'color': '123456', 'color': '123456',
}, },
user=self.user) user=self.user)
page_json = json.loads(page.content)
def test_duck_details(self): def test_duck_details(self):
"""
Test duck details view
"""
url = '/api/v1/ducks/%d/' % self.duck.pk url = '/api/v1/ducks/%d/' % self.duck.pk
page = self.app.get(url) page = self.app.get(url)
self.assertEqual(200, page.status_code) self.assertEqual(200, page.status_code)

View File

@ -1,4 +1,7 @@
from django.conf.urls import patterns, url, include # -*- coding: utf-8
"""
URL definitions for version 1 of the Duck Booking Tool API
"""
from rest_framework import routers from rest_framework import routers

View File

@ -1,22 +1,32 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
Views for the Duck Booking Tool API
"""
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.decorators import detail_route, list_route from rest_framework.decorators import detail_route, list_route
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from .serializers import DuckSerializer, CompetenceSerializer, \ from .serializers import DuckSerializer, CompetenceSerializer
DuckCompetenceSerializer from booking.models import Duck, Competence, Booking
from booking.models import Duck, Competence, Booking, DuckCompetence
class DuckViewSet(viewsets.ModelViewSet): class DuckViewSet(viewsets.ModelViewSet):
"""
View set for duck handling
"""
serializer_class = DuckSerializer serializer_class = DuckSerializer
queryset = Duck.objects.all() queryset = Duck.objects.all()
@detail_route(methods=['post'], permission_classes=[IsAuthenticated]) @detail_route(methods=['post'], permission_classes=[IsAuthenticated])
def book(self, request, pk=None): def book(self, request, pk=None):
"""
API call to book a duck
"""
duck = self.get_object() duck = self.get_object()
competence = get_object_or_404(Competence, pk=request.data['competence']) competence = get_object_or_404(Competence, pk=request.data['competence'])
force = request.data.get('force', False) force = request.data.get('force', False)
@ -54,8 +64,16 @@ class DuckViewSet(viewsets.ModelViewSet):
@list_route(methods=['post'], permission_classes=[IsAuthenticated]) @list_route(methods=['post'], permission_classes=[IsAuthenticated])
def donate(self, request): def donate(self, request):
"""
API call to donate a new duck
"""
return Response({'Woot!'}) return Response({'Woot!'})
class CompetenceViewSet(viewsets.ModelViewSet): class CompetenceViewSet(viewsets.ModelViewSet):
"""
View set for competence handling
"""
serializer_class = CompetenceSerializer serializer_class = CompetenceSerializer
queryset = Competence.objects.all() queryset = Competence.objects.all()

View File

@ -1,5 +1,12 @@
# -*- coding: utf-8
"""
Administration site definition for the Duck Booking Tool
"""
from django.contrib import admin 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(Species)
admin.site.register(Location) admin.site.register(Location)

View File

@ -1,19 +1,35 @@
# -*- coding: utf-8
"""
Duck level calculations
"""
from django.conf import settings from django.conf import settings
import math import math
def level_to_up_minutes(level): def level_to_up_minutes(level):
"""
Convert duck level to up minutes
"""
if level == 0: if level == 0:
return 0 return 0
return 2 * pow(10, level) return 2 * pow(10, level)
def level_to_down_minutes(level): def level_to_down_minutes(level):
"""
Convert duck level to down minutes
"""
if level == 0: if level == 0:
return 0 return 0
return 20 * pow(10, level) return 20 * pow(10, level)
def minutes_to_level(up_minutes, down_minutes): def minutes_to_level(up_minutes, down_minutes):
"""
Convert booking minutes to duck level
"""
minutes = up_minutes + down_minutes / 10 minutes = up_minutes + down_minutes / 10
level = 0 if minutes <= 0 else min(settings.MAX_DUCK_LEVEL, math.floor(math.log10(minutes))) 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 -*- # -*- 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.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 fuzzywuzzy import fuzz
from .ducklevel import minutes_to_level from .ducklevel import minutes_to_level
@python_2_unicode_compatible
class Species(models.Model): class Species(models.Model):
"""Model to hold the Ducks species""" """
Model to hold the Ducks species
"""
name = models.CharField(max_length=40, unique=True) name = models.CharField(max_length=40, unique=True)
def __str__(self): def __str__(self):
return self.name return self.name
@python_2_unicode_compatible
class Location(models.Model): 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) name = models.CharField(max_length=50)
def __str__(self): def __str__(self):
return self.name return self.name
@python_2_unicode_compatible
class Competence(models.Model): class Competence(models.Model):
"""Model to hold Duck competences""" """
Model to hold Duck competences
"""
name = models.CharField(max_length=255, unique=True) name = models.CharField(max_length=255, unique=True)
added_at = models.DateTimeField(default=timezone.now) added_at = models.DateTimeField(default=timezone.now)
@ -37,20 +50,27 @@ class Competence(models.Model):
@classmethod @classmethod
def get_similar_comps(cls, name): def get_similar_comps(cls, name):
"""
Get competence names similar to name
"""
comps = cls.objects.values_list('name', flat=True) comps = cls.objects.values_list('name', flat=True)
ret = () ret = ()
for c in comps: for competence in comps:
r = fuzz.ratio(name.lower(), c.lower()) similarity = fuzz.ratio(name.lower(), competence.lower())
# This ratio is subject to change # This ratio is subject to change
if r > settings.MIN_FUZZY_SIMILARITY: if similarity > settings.MIN_FUZZY_SIMILARITY:
ret = ret + (c,) ret = ret + (competence,)
return ret return ret
@python_2_unicode_compatible
class Duck(models.Model): class Duck(models.Model):
"""Model to hold Duck data""" """
Model to hold Duck data
"""
name = models.CharField(max_length=80, null=True, blank=True) name = models.CharField(max_length=80, null=True, blank=True)
color = models.CharField(max_length=6) color = models.CharField(max_length=6)
@ -72,6 +92,11 @@ class Duck(models.Model):
return self.name return self.name
def age(self): 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_d = timezone.now() - self.donated_at
seconds = seconds_d.total_seconds() seconds = seconds_d.total_seconds()
@ -81,6 +106,10 @@ class Duck(models.Model):
return seconds return seconds
def dpx(self): def dpx(self):
"""
Get the Duck Popularity indeX for this duck
"""
all_time = Booking.total_booking_time() all_time = Booking.total_booking_time()
duck_time = Booking.duck_booking_time(self) 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() return Booking.duck_booking_time(self) / Booking.total_booking_time()
def booked_by(self): 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 return None
if len(l) > 1: if len(booking_list) > 1:
raise RuntimeError(u"Duck is booked more than once!") 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): class DuckName(models.Model):
"""Model to hold name suggestions for Ducks""" """
Model to hold name suggestions for Ducks
"""
duck = models.ForeignKey(Duck) duck = models.ForeignKey(Duck)
name = models.CharField(max_length=60) name = models.CharField(max_length=60)
@ -110,16 +146,31 @@ class DuckName(models.Model):
closed_by = models.ForeignKey(User, related_name='+') closed_by = models.ForeignKey(User, related_name='+')
closed_at = models.DateTimeField(null=True) 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): class DuckNameVote(models.Model):
"""Model to hold votes to Duck names""" """
Model to hold votes to Duck names
"""
duck_name = models.ForeignKey(DuckName) duck_name = models.ForeignKey(DuckName)
vote_timestamp = models.DateTimeField(default=timezone.now) vote_timestamp = models.DateTimeField(default=timezone.now)
voter = models.ForeignKey(User) voter = models.ForeignKey(User)
upvote = models.BooleanField(default=True) 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): class DuckCompetence(models.Model):
"""Duck competence governor table""" """
Duck competence governor table
"""
duck = models.ForeignKey(Duck, related_name='competences') duck = models.ForeignKey(Duck, related_name='competences')
comp = models.ForeignKey(Competence, related_name='ducks') comp = models.ForeignKey(Competence, related_name='ducks')
@ -127,13 +178,26 @@ class DuckCompetence(models.Model):
down_minutes = models.IntegerField(default=0) down_minutes = models.IntegerField(default=0)
def level(self): def level(self):
"""
Return the actual level of a duck
"""
return minutes_to_level(self.up_minutes, self.down_minutes) 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: class Meta:
unique_together = ('duck', 'comp') unique_together = ('duck', 'comp')
@python_2_unicode_compatible
class Booking(models.Model): class Booking(models.Model):
"""Duck booking governor table""" """
Duck booking governor table
"""
duck = models.ForeignKey(Duck) duck = models.ForeignKey(Duck)
user = models.ForeignKey(User) user = models.ForeignKey(User)
@ -144,6 +208,10 @@ class Booking(models.Model):
@classmethod @classmethod
def total_booking_time(cls): def total_booking_time(cls):
"""
Get the sum of booked hours for all ducks
"""
return cls.objects.filter( return cls.objects.filter(
start_ts__isnull=False, start_ts__isnull=False,
end_ts__isnull=False).extra( end_ts__isnull=False).extra(
@ -154,10 +222,19 @@ class Booking(models.Model):
@classmethod @classmethod
def duck_booking_time(cls, duck): def duck_booking_time(cls, duck):
"""
Get the sum of booked hours of a duck
"""
return cls.objects.filter( return cls.objects.filter(
start_ts__isnull=False, start_ts__isnull=False,
end_ts__isnull=False, duck = duck).extra( end_ts__isnull=False, duck=duck).extra(
select={ select={
'amount': 'sum(strftime(%s, end_ts) - strftime(%s, start_ts))' 'amount': 'sum(strftime(%s, end_ts) - strftime(%s, start_ts))'
}, },
select_params=('%s', '%s'))[0].amount 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 from django import template
import math import math
register = template.Library() register = template.Library()
def is_number(s): def is_number(string):
"""
Check if s is a number in string representation
"""
try: try:
float(s) float(string)
return True return True
except ValueError: except ValueError:
return False return False
@register.filter @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): if not is_number(value):
return value return value
@ -30,7 +44,7 @@ def age_format(value, arg = None):
remainder = remainder % 2592000 remainder = remainder % 2592000
if months > 0: if months > 0:
if (ret != ""): if ret != "":
ret += " " ret += " "
ret += u"%d month%s" % (months, "" if months == 1 else "s") ret += u"%d month%s" % (months, "" if months == 1 else "s")
@ -45,7 +59,7 @@ def age_format(value, arg = None):
days = 1 days = 1
if days > 0: if days > 0:
if (ret != ""): if ret != "":
ret += " " ret += " "
ret += u"%d day%s" % (days, "" if days == 1 else "s") ret += u"%d day%s" % (days, "" if days == 1 else "s")
@ -60,7 +74,7 @@ def age_format(value, arg = None):
remainder = remainder % 3600 remainder = remainder % 3600
if hours > 0: if hours > 0:
if (ret != ""): if ret != "":
ret += " " ret += " "
ret += u"%d hour%s" % (hours, "" if hours == 1 else "s") 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) minutes = math.floor(remainder / 60)
if minutes > 0: if minutes > 0:
if (ret != ""): if ret != "":
ret += " " ret += " "
ret += u"%d minute%s" % (minutes, "" if minutes == 1 else "s") 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) seconds = round(remainder % 60)
if seconds > 0: if seconds > 0:
if (ret != ""): if ret != "":
ret += " " ret += " "
ret += u"%d second%s" % (seconds, "" if seconds == 1 else "s") ret += u"%d second%s" % (seconds, "" if seconds == 1 else "s")

View File

@ -1,70 +1,96 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.conf import settings """
from django.contrib.auth.models import User Tests for the Duck Booking Tool frontend
from django.core.urlresolvers import reverse """
from django.test import TestCase, Client
from django.utils import timezone
import datetime 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 .ducklevel import level_to_up_minutes, level_to_down_minutes, minutes_to_level
from .templatetags import booking_tags from .templatetags import booking_tags
from .models import Duck, Competence, DuckCompetence, Species, Location, Booking from .models import Duck, Competence, DuckCompetence, Species, Location, Booking
class FrontTest(TestCase): class FrontTest(TestCase):
"""
Test case for the front end
"""
def setUp(self): def setUp(self):
self.client = Client() self.client = Client()
def test_index_page(self): def test_index_page(self):
"""
Test for the existence of the main page
"""
response = self.client.get('/') response = self.client.get('/')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_vocabulary_page(self): def test_vocabulary_page(self):
"""
Test for the existence of the vocabulary page
"""
response = self.client.get('/vocabulary.html') response = self.client.get('/vocabulary.html')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_terms_page(self): def test_terms_page(self):
"""
Test for the existence of the terms page
"""
response = self.client.get('/terms.html') response = self.client.get('/terms.html')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_disclaimer_page(self): def test_disclaimer_page(self):
"""
Test for the existence of the disclaimer page
"""
response = self.client.get('/disclaimer.html') response = self.client.get('/disclaimer.html')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
class DuckCompLevelTest(TestCase): class DuckCompLevelTest(TestCase):
"""
Test case for competence level calculation
"""
def setUp(self): def setUp(self):
user = User.objects.create_user(username='test', password='test') user = User.objects.create_user(username='test', password='test')
species = Species(name='test species') species = Species.objects.create(name='test species')
species.save()
location = Location(name='test location') location = Location.objects.create(name='test location')
location.save()
duck = Duck( duck = Duck.objects.create(species=species,
species=species,
location=location, location=location,
donated_by=user) donated_by=user)
duck.save()
comp = Competence( comp = Competence.objects.create(name='testing',
name='testing',
added_by=user) added_by=user)
comp.save()
self.duckcomp = DuckCompetence( self.duckcomp = DuckCompetence.objects.create(duck=duck,
duck = duck, comp=comp,
comp = comp, up_minutes=0,
up_minutes = 0, down_minutes=0)
down_minutes =0)
def test_sane_max(self): def test_sane_max(self):
"""
Test if the MAX_DUCK_LEVEL setting has a sane value
"""
self.assertGreater( self.assertGreater(
settings.MAX_DUCK_LEVEL, 0, 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): 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) max_up_minutes = level_to_up_minutes(settings.MAX_DUCK_LEVEL)
double_minutes = level_to_up_minutes(settings.MAX_DUCK_LEVEL * 2) 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) self.assertEqual(level, settings.MAX_DUCK_LEVEL)
def test_conversions(self): def test_conversions(self):
"""
Test minutes to level conversations
"""
for i in range(1, settings.MAX_DUCK_LEVEL): for i in range(1, settings.MAX_DUCK_LEVEL):
up_minutes = level_to_up_minutes(i) up_minutes = level_to_up_minutes(i)
down_minutes = level_to_down_minutes(i) down_minutes = level_to_down_minutes(i)
@ -93,12 +123,16 @@ class DuckCompLevelTest(TestCase):
up_level = minutes_to_level(up_minutes, 0) up_level = minutes_to_level(up_minutes, 0)
down_level = minutes_to_level(0, down_minutes) 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( self.assertEqual(
down_level, i, down_level, i,
msg = "Test failed for value %d" % i) msg="Test failed for value %d" % i)
def test_level_to_minutes(self): 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(0), 0)
self.assertEqual(level_to_up_minutes(1), 20) self.assertEqual(level_to_up_minutes(1), 20)
self.assertEqual(level_to_up_minutes(2), 200) self.assertEqual(level_to_up_minutes(2), 200)
@ -114,11 +148,19 @@ class DuckCompLevelTest(TestCase):
self.assertEqual(level_to_down_minutes(5), 2000000) self.assertEqual(level_to_down_minutes(5), 2000000)
def test_no_comp(self): def test_no_comp(self):
"""
Test if level equals 0 if minutes count is 0
"""
self.duckcomp.up_minutes = 0 self.duckcomp.up_minutes = 0
self.duckcomp.down_minutes = 0 self.duckcomp.down_minutes = 0
self.assertEquals(self.duckcomp.level(), 0) self.assertEquals(self.duckcomp.level(), 0)
def test_comp_levels(self): def test_comp_levels(self):
"""
Test competence level calculation
"""
self.duckcomp.down_minutes = 0 self.duckcomp.down_minutes = 0
for lvl in range(1, settings.MAX_DUCK_LEVEL): for lvl in range(1, settings.MAX_DUCK_LEVEL):
@ -127,16 +169,33 @@ class DuckCompLevelTest(TestCase):
self.assertEqual(self.duckcomp.level(), lvl) self.assertEqual(self.duckcomp.level(), lvl)
def test_high_minutes(self): 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.up_minutes = level_to_up_minutes(settings.MAX_DUCK_LEVEL)
self.duckcomp.down_minutes = level_to_down_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) self.assertEqual(self.duckcomp.level(), settings.MAX_DUCK_LEVEL)
class DuckAgeTest(TestCase): class DuckAgeTest(TestCase):
"""
Tests related to duck age
"""
def test_duck_is_from_the_future(self): 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) self.assertEqual(future_duck.age(), -1)
def test_duck_age_formatter(self): def test_duck_age_formatter(self):
"""
Test duck age formatter
"""
self.assertEqual(booking_tags.age_format("aoeu"), "aoeu") self.assertEqual(booking_tags.age_format("aoeu"), "aoeu")
self.assertEqual(booking_tags.age_format(0), "a few moments") self.assertEqual(booking_tags.age_format(0), "a few moments")
self.assertEqual(booking_tags.age_format(1), "1 second") 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") self.assertEqual(booking_tags.age_format(63072000), "2 years")
class BookingTimeTest(TestCase): class BookingTimeTest(TestCase):
duck1 = None """
duck2 = None Test case for calculating booking time and popularity
"""
def setUp(self): def setUp(self):
user = User() user = User()
user.save() user.save()
species = Species(name = 'duck') species = Species.objects.create(name='duck')
species.save() location = Location.objects.create(name='start')
location = Location(name = 'start') self.duck1 = Duck.objects.create(species=species,
location.save() location=location,
donated_by=user)
self.duck1 = Duck(species = species, location = location, donated_by = user) competence = Competence.objects.create(name='test',
self.duck1.save() added_by=user)
competence = Competence(name = 'test', added_by = user)
competence.save()
now = timezone.now() 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.objects.create(duck=self.duck1,
booking.save() 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 = Duck.objects.create(species=species,
self.duck2.save() 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.objects.create(duck=self.duck2,
booking.save() 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.objects.create(duck=self.duck2,
booking.save() 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): 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): 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): 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): class TestListing(TestCase):
"""
Test case for duck listing
"""
def setUp(self): def setUp(self):
self.client = Client() self.client = Client()
species = Species() species = Species.objects.create()
species.save() loc = Location.objects.create()
user = User.objects.create_user(username='test',
password='test')
loc = Location() self.duck = Duck.objects.create(species=species,
loc.save() location=loc,
donated_by=user)
user = User()
user.save()
self.duck = Duck(species = species, location = loc, donated_by = user)
self.duck.save()
def test_front_page(self): 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): class SimilarCompTest(TestCase):
"""
Test case for competence name fuzzy search
"""
def setUp(self): def setUp(self):
admin = User(username = 'admin') admin = User.objects.create_user(username='admin',
admin.save() password='test')
competence_list = ( competence_list = (
'Creativity', 'Creativity',
@ -251,54 +340,75 @@ class SimilarCompTest(TestCase):
'TCSH', 'TCSH',
) )
for c in competence_list: for competence in competence_list:
comp = Competence(name = c, added_by = admin) Competence.objects.create(name=competence,
comp.save() added_by=admin)
def test_good_similar_competences(self): 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') comp_list = Competence.get_similar_comps('perl')
self.assertEquals(len(l), 1) self.assertEquals(1, len(comp_list))
l = Competence.get_similar_comps(u'kreativitás') comp_list = Competence.get_similar_comps('pzthon')
self.assertEqual(len(l), 1) 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): 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): class BookingTest(TestCase):
"""
Test duck booking functionality
"""
def setUp(self): def setUp(self):
spec = Species.objects.create(name='test') self.spec = Species.objects.create(name='test')
self.loc = Location.objects.create(name='test')
loc = Location.objects.create(name='test')
self.user = User.objects.create_user(username='test') self.user = User.objects.create_user(username='test')
self.booked_duck = Duck.objects.create(species=self.spec,
self.booked_duck = Duck.objects.create(species=spec, location=self.loc,
location=loc,
donated_by=self.user) donated_by=self.user)
self.comp = Competence.objects.create(name='test', self.comp = Competence.objects.create(name='test',
added_by=self.user) added_by=self.user)
booking = Booking.objects.create(duck=self.booked_duck, Booking.objects.create(duck=self.booked_duck,
user=self.user, user=self.user,
comp_req=self.comp) comp_req=self.comp)
self.unbooked_duck = Duck.objects.create(species=spec,
location=loc,
donated_by=self.user)
def test_booked_duck(self): def test_booked_duck(self):
"""
Test if booked duck returns the booking user from booked_by()
"""
self.assertNotEqual(self.booked_duck.booked_by(), None) self.assertNotEqual(self.booked_duck.booked_by(), None)
def test_unbooked_duck(self): 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): def test_multiple_booking(self):
"""
Test error presence in case of multiple bookings for the same
duck
"""
Booking.objects.create(duck=self.booked_duck, Booking.objects.create(duck=self.booked_duck,
user=self.user, user=self.user,
comp_req=self.comp) 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 django.views.generic.base import TemplateView
from .views import DuckListView from .views import DuckListView
urlpatterns = patterns( urlpatterns = [
'', url(r'^$', DuckListView.as_view(), name='list'),
url(r'^$', DuckListView.as_view(), name = 'list'),
url( url(
r'^vocabulary.html$', r'^vocabulary.html$',
TemplateView.as_view(template_name = 'booking/vocabulary.html'), TemplateView.as_view(template_name='booking/vocabulary.html'),
name = 'vocabulary' name='vocabulary'
), ),
url( url(
r'^terms.html$', r'^terms.html$',
TemplateView.as_view(template_name = 'booking/terms.html'), TemplateView.as_view(template_name='booking/terms.html'),
name = 'terms' name='terms'
), ),
url( url(
r'^disclaimer.html$', r'^disclaimer.html$',
TemplateView.as_view(template_name = 'booking/disclaimer.html'), TemplateView.as_view(template_name='booking/disclaimer.html'),
name = 'disclaimer' 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 django.views import generic
from .models import Duck from .models import Duck
class DuckListView(generic.ListView): class DuckListView(generic.ListView):
"""
View for duck listing (the main page)
"""
template_name = 'booking/duck_list.html' template_name = 'booking/duck_list.html'
context_object_name = 'duck_list' context_object_name = 'duck_list'

View File

@ -1,26 +1,30 @@
from django.conf import settings # -*- coding: utf-8
from django.conf.urls import patterns, include, url """
from django.contrib import admin Main URL definitions file
from django.views.decorators.cache import cache_page """
from django_js_reverse.views import urls_js from django.conf import settings
from django.conf.urls import include, url
from django.contrib import admin
admin.autodiscover() admin.autodiscover()
urlpatterns = patterns( urlpatterns = [
'',
url( url(
r'^static/(?P<path>.*)$', r'^static/(?P<path>.*)$',
'django.views.static.serve', 'django.views.static.serve',
{'document_root': settings.STATIC_ROOT} {'document_root': settings.STATIC_ROOT}
), ),
url( url(
r'^reverse.js$', r'^admin/',
cache_page(3600)(urls_js), include(admin.site.urls)),
name = 'js_reverse' url(
), r'^accounts/',
url(r'^admin/', include(admin.site.urls)), include('accounts.urls', namespace='accounts')),
url(r'^accounts/', include('accounts.urls', namespace = 'accounts')), url(
url(r'^api/v1/', include('api.urls', namespace = 'api')), r'^api/v1/',
url('', include('booking.urls', namespace = 'booking')), include('api.urls', namespace='api')),
) url(
'',
include('booking.urls', namespace='booking')),
]