Pylint happiness!
This commit is contained in:
parent
2db6e2dd24
commit
d3ec0e8998
@ -1,22 +1,37 @@
|
||||
# -*- coding: utf-8
|
||||
"""
|
||||
Account management module for the Duck Booking Tool backend
|
||||
"""
|
||||
|
||||
from django.test import TestCase, Client
|
||||
from django_webtest import WebTest
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
|
||||
class FrontTest(TestCase):
|
||||
"""
|
||||
Test front-end capabilities of the accounts module
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
|
||||
self.admin = User.objects.create_user(
|
||||
username = 'admin',
|
||||
password = 'password')
|
||||
self.admin.save()
|
||||
self.admin = User.objects.create_user(username='admin',
|
||||
password='password')
|
||||
|
||||
def test_login_page(self):
|
||||
"""
|
||||
Test for the existence of the login page
|
||||
"""
|
||||
|
||||
response = self.client.get('/accounts/login')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_login(self):
|
||||
"""
|
||||
Test login functionality
|
||||
"""
|
||||
|
||||
response = self.client.post('/accounts/login', {
|
||||
'next': '/',
|
||||
'username': 'admin',
|
||||
@ -25,17 +40,33 @@ class FrontTest(TestCase):
|
||||
self.assertRedirects(response, '/')
|
||||
|
||||
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')
|
||||
self.assertRedirects(response, '/')
|
||||
|
||||
def test_registration_page(self):
|
||||
"""
|
||||
Test for existence of the registration page
|
||||
"""
|
||||
|
||||
response = self.client.get('/accounts/register')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
class RegFormTest(WebTest):
|
||||
"""
|
||||
Test case for the registration form
|
||||
"""
|
||||
|
||||
def test_valid_data(self):
|
||||
"""
|
||||
Test valid registration without actual HTTP requests
|
||||
"""
|
||||
|
||||
form_data = {
|
||||
'username': 'test',
|
||||
'password1': 'password',
|
||||
@ -51,6 +82,10 @@ class RegFormTest(WebTest):
|
||||
self.assertNotEqual(user.password, 'password')
|
||||
|
||||
def test_empty(self):
|
||||
"""
|
||||
Test empty registration form
|
||||
"""
|
||||
|
||||
form_data = {}
|
||||
form = UserCreationForm(form_data)
|
||||
self.assertFalse(form.is_valid())
|
||||
@ -61,11 +96,19 @@ class RegFormTest(WebTest):
|
||||
})
|
||||
|
||||
def test_form_error(self):
|
||||
"""
|
||||
Test incomplete registration
|
||||
"""
|
||||
|
||||
page = self.app.get('/accounts/register')
|
||||
page = page.form.submit()
|
||||
self.assertContains(page, "This field is required.")
|
||||
|
||||
def test_form_success(self):
|
||||
"""
|
||||
Test for successful registrations
|
||||
"""
|
||||
|
||||
page = self.app.get('/accounts/register')
|
||||
page.form['username'] = 'test'
|
||||
page.form['password1'] = 'password'
|
||||
|
@ -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
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
urlpatterns = [
|
||||
url(
|
||||
r'^register$',
|
||||
RegistrationFormView.as_view(),
|
||||
name = 'register'
|
||||
name='register'
|
||||
),
|
||||
url(
|
||||
r'^login$',
|
||||
'django.contrib.auth.views.login',
|
||||
{'template_name': 'accounts/login.html'},
|
||||
name = 'login'
|
||||
name='login'
|
||||
),
|
||||
url(
|
||||
r'^logout$',
|
||||
'django.contrib.auth.views.logout',
|
||||
{'next_page': 'booking:list'},
|
||||
name = 'logout'
|
||||
name='logout'
|
||||
),
|
||||
)
|
||||
]
|
||||
|
@ -1,3 +1,8 @@
|
||||
# -*- coding: utf-8
|
||||
"""
|
||||
Views for the accounts module
|
||||
"""
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.views import generic
|
||||
from django.http import HttpResponseRedirect
|
||||
@ -5,14 +10,26 @@ from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
|
||||
class RegistrationFormView(generic.View):
|
||||
"""
|
||||
Class to display the registration form
|
||||
"""
|
||||
|
||||
form_class = UserCreationForm
|
||||
template_name = 'accounts/registration.html'
|
||||
|
||||
def get(self, request):
|
||||
"""
|
||||
Implementation of the GET method
|
||||
"""
|
||||
|
||||
form = self.form_class()
|
||||
return render(request, self.template_name, { 'form': form })
|
||||
return render(request, self.template_name, {'form': form})
|
||||
|
||||
def post(self, request):
|
||||
"""
|
||||
Implementation of the POST method
|
||||
"""
|
||||
|
||||
form = self.form_class(request.POST)
|
||||
|
||||
if form.is_valid():
|
||||
|
@ -1,10 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Serializers for the Duck Booking Tool API
|
||||
"""
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from rest_framework import serializers
|
||||
|
||||
from booking.models import Duck, Competence, DuckCompetence
|
||||
|
||||
class NamespacedSerializer(serializers.HyperlinkedModelSerializer):
|
||||
"""
|
||||
HyperlinkedModelSerializer with URL namespace support
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if not hasattr(self.Meta, 'url_namespace') or self.Meta.url_namespace is None:
|
||||
raise ImproperlyConfigured("namespace must be set!")
|
||||
@ -17,7 +25,7 @@ class NamespacedSerializer(serializers.HyperlinkedModelSerializer):
|
||||
if not self.url_namespace.endswith(':'):
|
||||
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):
|
||||
field_class, field_kwargs = super(NamespacedSerializer, self) \
|
||||
@ -32,12 +40,20 @@ class NamespacedSerializer(serializers.HyperlinkedModelSerializer):
|
||||
return field_class, field_kwargs
|
||||
|
||||
class CompetenceSerializer(NamespacedSerializer):
|
||||
"""
|
||||
Serializer for Competence objects
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
url_namespace = 'api'
|
||||
model = Competence
|
||||
fields = ('url', 'name',)
|
||||
|
||||
class DuckCompetenceSerializer(NamespacedSerializer):
|
||||
"""
|
||||
Serializer for DuckCompetence objects
|
||||
"""
|
||||
|
||||
comp = CompetenceSerializer()
|
||||
|
||||
class Meta:
|
||||
@ -46,6 +62,10 @@ class DuckCompetenceSerializer(NamespacedSerializer):
|
||||
fields = ('comp', 'up_minutes', 'down_minutes',)
|
||||
|
||||
class DuckSerializer(NamespacedSerializer):
|
||||
"""
|
||||
Serializer for Duck objects
|
||||
"""
|
||||
|
||||
competences = DuckCompetenceSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
|
36
api/tests.py
36
api/tests.py
@ -1,5 +1,8 @@
|
||||
# -*- coding: utf-8
|
||||
from django.test import TestCase, Client
|
||||
"""
|
||||
Test cases for API calls
|
||||
"""
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
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
|
||||
|
||||
class DuckClassTest(WebTest):
|
||||
"""
|
||||
Test case for duck related API calls
|
||||
"""
|
||||
|
||||
csrf_checks = False
|
||||
|
||||
def setUp(self):
|
||||
@ -46,10 +53,18 @@ class DuckClassTest(WebTest):
|
||||
down_minutes=0)
|
||||
|
||||
def test_book_nonlogged(self):
|
||||
"""
|
||||
Test booking without logging in
|
||||
"""
|
||||
|
||||
page = self.app.post('/api/v1/ducks/1/book/', expect_errors=True)
|
||||
self.assertEqual(page.status_code, 403)
|
||||
|
||||
def test_book_nonexist(self):
|
||||
"""
|
||||
Test booking a non-existing duck
|
||||
"""
|
||||
|
||||
# Try to book a non-existing duck
|
||||
page = self.app.post(
|
||||
'/api/v1/ducks/9999/book/',
|
||||
@ -71,6 +86,10 @@ class DuckClassTest(WebTest):
|
||||
self.assertEqual(404, page.status_code)
|
||||
|
||||
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
|
||||
comp_none = Competence.objects.create(name='test3',
|
||||
added_by=self.user)
|
||||
@ -107,6 +126,10 @@ class DuckClassTest(WebTest):
|
||||
self.assertEquals(page_json['status'], 'ok')
|
||||
|
||||
def test_book_good(self):
|
||||
"""
|
||||
Test duck booking for a competence the duck is good at
|
||||
"""
|
||||
|
||||
test_data = {
|
||||
"competence": self.comp_good.pk
|
||||
}
|
||||
@ -127,6 +150,10 @@ class DuckClassTest(WebTest):
|
||||
self.assertEqual('already-booked', page_json['status'])
|
||||
|
||||
def test_duck_donation(self):
|
||||
"""
|
||||
Test duck donating functionality
|
||||
"""
|
||||
|
||||
# Duck donation should not be allowed without logging in
|
||||
page = self.app.get('/api/v1/ducks/donate/', expect_errors=True)
|
||||
self.assertEquals(page.status_code, 403)
|
||||
@ -135,16 +162,19 @@ class DuckClassTest(WebTest):
|
||||
page = self.app.post('/api/v1/ducks/donate/', expect_errors=True)
|
||||
self.assertEquals(page.status_code, 403)
|
||||
|
||||
page = self.app.post(
|
||||
self.app.post(
|
||||
'/api/v1/ducks/donate/',
|
||||
params={
|
||||
'species': 1,
|
||||
'color': '123456',
|
||||
},
|
||||
user=self.user)
|
||||
page_json = json.loads(page.content)
|
||||
|
||||
def test_duck_details(self):
|
||||
"""
|
||||
Test duck details view
|
||||
"""
|
||||
|
||||
url = '/api/v1/ducks/%d/' % self.duck.pk
|
||||
page = self.app.get(url)
|
||||
self.assertEqual(200, page.status_code)
|
||||
|
@ -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
|
||||
|
||||
|
26
api/views.py
26
api/views.py
@ -1,22 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Views for the Duck Booking Tool API
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import detail_route, list_route
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
|
||||
from .serializers import DuckSerializer, CompetenceSerializer, \
|
||||
DuckCompetenceSerializer
|
||||
from booking.models import Duck, Competence, Booking, DuckCompetence
|
||||
from .serializers import DuckSerializer, CompetenceSerializer
|
||||
from booking.models import Duck, Competence, Booking
|
||||
|
||||
class DuckViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
View set for duck handling
|
||||
"""
|
||||
|
||||
serializer_class = DuckSerializer
|
||||
queryset = Duck.objects.all()
|
||||
|
||||
@detail_route(methods=['post'], permission_classes=[IsAuthenticated])
|
||||
def book(self, request, pk=None):
|
||||
"""
|
||||
API call to book a duck
|
||||
"""
|
||||
|
||||
duck = self.get_object()
|
||||
competence = get_object_or_404(Competence, pk=request.data['competence'])
|
||||
force = request.data.get('force', False)
|
||||
@ -54,8 +64,16 @@ class DuckViewSet(viewsets.ModelViewSet):
|
||||
|
||||
@list_route(methods=['post'], permission_classes=[IsAuthenticated])
|
||||
def donate(self, request):
|
||||
"""
|
||||
API call to donate a new duck
|
||||
"""
|
||||
|
||||
return Response({'Woot!'})
|
||||
|
||||
class CompetenceViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
View set for competence handling
|
||||
"""
|
||||
|
||||
serializer_class = CompetenceSerializer
|
||||
queryset = Competence.objects.all()
|
||||
|
@ -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)
|
||||
|
@ -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)))
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
288
booking/tests.py
288
booking/tests.py
@ -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,
|
||||
duck = Duck.objects.create(species=species,
|
||||
location=location,
|
||||
donated_by=user)
|
||||
duck.save()
|
||||
|
||||
comp = Competence(
|
||||
name='testing',
|
||||
comp = Competence.objects.create(name='testing',
|
||||
added_by=user)
|
||||
comp.save()
|
||||
|
||||
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,
|
||||
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)
|
||||
|
||||
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)
|
||||
|
@ -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'
|
||||
),
|
||||
)
|
||||
]
|
||||
|
@ -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'
|
||||
|
||||
|
@ -1,26 +1,30 @@
|
||||
from django.conf import settings
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.contrib import admin
|
||||
from django.views.decorators.cache import cache_page
|
||||
# -*- coding: utf-8
|
||||
"""
|
||||
Main URL definitions file
|
||||
"""
|
||||
|
||||
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()
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
urlpatterns = [
|
||||
url(
|
||||
r'^static/(?P<path>.*)$',
|
||||
'django.views.static.serve',
|
||||
{'document_root': settings.STATIC_ROOT}
|
||||
),
|
||||
url(
|
||||
r'^reverse.js$',
|
||||
cache_page(3600)(urls_js),
|
||||
name = 'js_reverse'
|
||||
),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^accounts/', include('accounts.urls', namespace = 'accounts')),
|
||||
url(r'^api/v1/', include('api.urls', namespace = 'api')),
|
||||
url('', include('booking.urls', namespace = 'booking')),
|
||||
)
|
||||
r'^admin/',
|
||||
include(admin.site.urls)),
|
||||
url(
|
||||
r'^accounts/',
|
||||
include('accounts.urls', namespace='accounts')),
|
||||
url(
|
||||
r'^api/v1/',
|
||||
include('api.urls', namespace='api')),
|
||||
url(
|
||||
'',
|
||||
include('booking.urls', namespace='booking')),
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user