diff --git a/api/tests.py b/api/tests.py index e9ea725..69e4781 100644 --- a/api/tests.py +++ b/api/tests.py @@ -1,9 +1,21 @@ from django.test import TestCase, Client from django.core.urlresolvers import reverse from django.contrib.auth.models import User +from django.conf import settings +import json + +from booking.ducklevel import level_to_up_minutes from booking.models import Species, Location, Duck, Competence, DuckCompetence +def get_response_encoding(response): + encoding = settings.DEFAULT_CHARSET + + if response.has_header('content-encoding'): + encoding = response['content-encoding'] + + return encoding + class ReverseTest(TestCase): def setUp(self): self.client = Client() @@ -42,3 +54,142 @@ class ApiTest(TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(response.context['comp_list']), 1) + +class DuckBookingTest(TestCase): + username = 'admin' + password = 'password' + + def setUp(self): + good_minutes = level_to_up_minutes(settings.COMP_WARN_LEVEL + 1) + bad_minutes = level_to_up_minutes(settings.COMP_WARN_LEVEL) + self.duck_id = 1 + self.comp_id = 1 + + self.admin = User.objects.create_user( + username = self.username, + password = self.password) + self.admin.save() + + spec = Species(name = 'duck') + spec.save() + + loc = Location(name = 'temp') + loc.save() + + self.comp_bad = Competence( + pk = self.comp_id, + name = 'test1', + added_by = self.admin) + self.comp_id += 1 + self.comp_bad.save() + + self.comp_good = Competence( + pk = self.comp_id, + name = 'test2', + added_by = self.admin) + self.comp_id += 1 + self.comp_good.save() + + self.duck = Duck( + pk = self.duck_id, + species = spec, + location = loc, + donated_by = self.admin) + self.duck_id += 1 + self.duck.save() + + dcomp = DuckCompetence( + duck = self.duck, + comp = self.comp_bad, + up_minutes = bad_minutes, + down_minutes = 0) + dcomp.save() + + dcomp = DuckCompetence( + duck = self.duck, + comp = self.comp_good, + up_minutes = good_minutes, + down_minutes = 0) + dcomp.save() + + self.client = Client() + + def send_booking_json(self, json_data): + return self.client.post( + '/api/duck/book/', + json.dumps(json_data), + HTTP_X_REQUESTED_WITH = 'XMLHttpRequest', + content_type = 'application/json') + + def test_book_nonlogged(self): + self.client.logout() + + response = self.send_booking_json({ + "duck_id": self.duck.pk, + "comp_id": self.comp_good.pk + }) + + self.assertEqual(response.status_code, 401) + + def test_book_nonexist(self): + self.client.login(username = self.username, password = self.password) + + # Try to book a non-existing Duck + response = self.send_booking_json({ + "duck_id": self.duck.pk + 1, + "comp_id": self.comp_good.pk + }) + self.assertEqual(response.status_code, 404) + + # Try to book an existing Duck for a non-existing competence + response = self.send_booking_json({ + "duck_id": self.duck.pk, + "comp_id": 3 + }) + self.assertEqual(response.status_code, 404) + + def test_book_warn(self): + test_data = { + "duck_id": self.duck.pk, + "comp_id": self.comp_bad.pk + } + self.client.login(username = self.username, password = self.password) + + response = self.send_booking_json(test_data) + self.assertEqual(response.status_code, 200) + + j = json.loads(response.content.decode(get_response_encoding(response))) + self.assertIn('success', j) + self.assertEquals(j['success'], 1) + + test_data['force'] = 1 + + response = self.send_booking_json(test_data) + self.assertEqual(response.status_code, 200) + + j = json.loads(response.content.decode(get_response_encoding(response))) + self.assertIn('success', j) + self.assertEquals(j['success'], 2) + + def test_book_good(self): + test_data = { + "duck_id": self.duck.pk, + "comp_id": self.comp_good.pk + } + self.client.login(username = self.username, password = self.password) + + # Book the duck + response = self.send_booking_json(test_data) + self.assertEqual(response.status_code, 200) + + j = json.loads(response.content.decode(get_response_encoding(response))) + self.assertIn('success', j) + self.assertEqual(j['success'], 2) + + # Try to book again, it should fail + response = self.send_booking_json(test_data) + self.assertEqual(response.status_code, 200) + + j = json.loads(response.content.decode(get_response_encoding(response))) + self.assertIn('success', j) + self.assertEqual(j['success'], 0) diff --git a/api/urls.py b/api/urls.py index c723272..99b437c 100644 --- a/api/urls.py +++ b/api/urls.py @@ -6,6 +6,19 @@ from . import views urlpatterns = patterns( '', - url(r'^reverse.js$', cache_page(3600)(urls_js), name = 'js_reverse'), - url(r'^duck/(?P\d+)/competence.json$', views.DuckCompListView.as_view(), name = 'complist'), + url( + r'^reverse.js$', + cache_page(3600)(urls_js), + name = 'js_reverse' + ), + url( + r'^duck/book/$', + views.duck_book, + name = 'book' + ), + url( + r'^duck/(?P\d+)/competence.json$', + views.DuckCompListView.as_view(), + name = 'complist' + ), ) diff --git a/api/views.py b/api/views.py index b98a1f2..618afbe 100644 --- a/api/views.py +++ b/api/views.py @@ -1,7 +1,12 @@ +from django.http import JsonResponse, HttpResponse from django.shortcuts import render, get_object_or_404 from django.views import generic +from django.conf import settings +from django.views.decorators.http import require_POST -from booking.models import Duck +import json + +from booking.models import Duck, Booking, Competence class DuckCompListView(generic.ListView): template_name = 'api/duck_comp_list.json' @@ -12,3 +17,77 @@ class DuckCompListView(generic.ListView): duck = get_object_or_404(Duck, pk = duck_id) return duck.duckcompetence_set.all() + +@require_POST +def duck_book(request): + """Book a duck to the logged in user. + + Return value: + HttpResponse with status_code = 400 if response.jsondata misses + duck_id or comp_id + HttpResponse with status_code = 401 if the user is not authenticated + HttpResponse with status_code = 404 if the duck or comp is not found + response.jsondata.success = 0 if the duck is already booked + response.jsondata.success = 1 if the duck's competence is too low + (use request.jsondata.force = True + to force) + response.jsondata.success = 2 if the booking was successful""" + + user = request.user + + # Check if user is authenticated; if not, return HTTP 401 + if not user.is_authenticated(): + res = HttpResponse() + res.status_code = 401 + + return res + + # Decode the request body + encoding = settings.DEFAULT_CHARSET if request.encoding == None else request.encoding + json_content = request.body.decode(encoding) + j = json.loads(json_content) + + # If there is no duck_id or no comp_id in the request, return HTTP 400 + if 'duck_id' not in j or 'comp_id' not in j: + res = HttpResponse() + res.status_code = 400 + + return res + + duck_id = j['duck_id'] + comp_id = j['comp_id'] + + # Find the duck and the competence; if any of them non-existant, + # return HTTP 404 + duck = get_object_or_404(Duck, pk = duck_id) + comp = get_object_or_404(Competence, pk = comp_id) + + # If the duck is already booked, return 0 as the result + if duck.booked_by() != None: + return JsonResponse({'success': 0}) + + # Result 0 means a problem + result = 0 + comp_level = 0 + + # Check if the duck has the requested competence + dcomp_list = duck.duckcompetence_set.filter(comp = comp) + + if len(dcomp_list) < 1: + comp_level = 0 + else: + comp_level = dcomp_list[0].level() + + # If the competence level is too low, set result to 1 + if comp_level <= settings.COMP_WARN_LEVEL: + result = 1 + + # If the duck has high enough competence or the booking is forced, + # return success (2) + if result != 1 or 'force' in j: + result = 2 + + booking = Booking(duck = duck, user = user, comp_req = comp) + booking.save() + + return JsonResponse({'success': result}) diff --git a/duckbook/settings.py b/duckbook/settings.py index cad2774..15a774e 100644 --- a/duckbook/settings.py +++ b/duckbook/settings.py @@ -87,4 +87,5 @@ USE_TZ = True STATIC_URL = '/static/' MAX_DUCK_LEVEL = 5 +COMP_WARN_LEVEL = 2 MIN_FUZZY_SIMILARITY = 75