diff --git a/bikeshop_project/registration/tests/test_utils.py b/bikeshop_project/registration/tests/test_utils.py new file mode 100644 index 0000000..151f03d --- /dev/null +++ b/bikeshop_project/registration/tests/test_utils.py @@ -0,0 +1,83 @@ +from datetime import timedelta + +from django.test import TestCase +from django.utils import timezone +from model_mommy import mommy + +from core.models import Visit +from registration.models import Member +from registration.utils import signin_member, AlreadySignedInError, member_signed_in, get_signed_in_members + + +class GetSignedInMembersTests(TestCase): + def setUp(self): + self.now = timezone.now() + + self.member1 = mommy.make(model=Member) + self.member2 = mommy.make(model=Member) + self.member3 = mommy.make(model=Member) + + three_hours_ago = self.now - timedelta(hours=3) + five_hours_ago = self.now - timedelta(hours=5) + + self.visit1 = Visit.objects.create(member=self.member1, purpose=Visit.DONATE, created_at=self.now) + self.visit2 = Visit.objects.create(member=self.member2, purpose=Visit.DONATE, created_at=three_hours_ago) + self.visit3 = Visit.objects.create(member=self.member3, purpose=Visit.DONATE, created_at=five_hours_ago) + + def test_get_signed_in_members(self): + """ + Only members signed-in in the window are returned + """ + result1 = get_signed_in_members(end=self.now) # default window=4 + self.assertEqual(len(result1), 2) + + result2 = get_signed_in_members(window=2, end=self.now) + self.assertEqual(len(result2), 1) + + result3 = get_signed_in_members(window=5, end=self.now) + self.assertEqual(len(result3), 3) + + +class SigninMember(TestCase): + def test_not_signed_in(self): + """ + A member who hasn't signed-in in 4 hours is signed-in. + """ + member = mommy.make(Member) + purpose = Visit.FIX + visit = signin_member(member, purpose) + + self.assertIsInstance(visit, Visit) + + def test_signed_in(self): + """ + A member who has signed-in in 4 hours is not signed-in. + """ + member = mommy.make(Member) + purpose = Visit.FIX + signin_member(member, purpose) + + with self.assertRaises(AlreadySignedInError): + signin_member(member, purpose) + + +class CheckMemberSignedIn(TestCase): + def test_member_not_signed_in(self): + """ + Returns false when member is not signed-in + """ + not_signed_member = mommy.make(model=Member) + result = member_signed_in(not_signed_member) + + self.assertFalse(result) + + def test_member_signed_in(self): + """ + Returns true when member is signed-in + """ + + member = mommy.make(model=Member) + Visit.objects.create(member=member, purpose=Visit.DONATE) + result = member_signed_in(member) + + self.assertTrue(result) diff --git a/bikeshop_project/registration/utils.py b/bikeshop_project/registration/utils.py new file mode 100644 index 0000000..c496314 --- /dev/null +++ b/bikeshop_project/registration/utils.py @@ -0,0 +1,37 @@ +from datetime import datetime, timedelta +from typing import Optional + +from django.db.models import QuerySet +from django.utils import timezone + +from core.models import Visit +from registration.models import Member + + +class AlreadySignedInError(ValueError): + pass + + +def signin_member(member: Member, purpose: str) -> Visit: + """ + Signs in a member, creating a new `Visit` + :param member: the member to be signed in + :param purpose: The reason for visit. E.g. Fix a bike or volunteer + :return: a new `Visit` + :raise: `AlreadySignedInError` or `ValidationError` + """ + if not member_signed_in(member): + return Visit.objects.create(member=member, purpose=purpose) + + raise AlreadySignedInError + + +def member_signed_in(member: Member, window: int = 4) -> bool: + return get_signed_in_members(window=window).filter(id__in=[member.id]).exists() + + +def get_signed_in_members(window: int = 4, end: Optional[datetime] = None) -> QuerySet: + new_end = end if end else timezone.now() + start = new_end - timedelta(hours=window) + visits = Visit.objects.filter(created_at__lte=new_end, created_at__gte=start) + return visits diff --git a/bikeshop_project/registration/views.py b/bikeshop_project/registration/views.py index 6f2e76f..7630be5 100644 --- a/bikeshop_project/registration/views.py +++ b/bikeshop_project/registration/views.py @@ -1,27 +1,23 @@ import json -import logging -from datetime import timedelta from django.contrib.auth.decorators import login_required from django.core.urlresolvers import reverse from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.shortcuts import get_object_or_404 from django.template.response import TemplateResponse -from django.utils import timezone from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt from django.views.generic import TemplateView, View +from haystack.query import SearchQuerySet +from rest_framework import serializers from rest_framework.renderers import JSONRenderer from rest_framework.serializers import ModelSerializer from core.models import Visit -from haystack.query import SearchQuerySet - +from registration.utils import signin_member, get_signed_in_members from .forms import MemberForm from .models import Member -logger = logging.getLogger('bikeshop') - @method_decorator(login_required, name='dispatch') class MemberFormView(View): @@ -69,17 +65,23 @@ class MemberSearchView(View): class MemberSerializer(ModelSerializer): + first_name = serializers.CharField(allow_blank=True, required=False) + last_name = serializers.CharField(allow_blank=True, required=False) + class Meta: model = Member - fields = ('full_name', 'email', 'id') + fields = ('first_name', 'last_name', 'email', 'id') + class VisitSerializer(ModelSerializer): member = MemberSerializer() + class Meta: model = Visit fields = ('created_at', 'purpose', 'member') depth = 1 + class MemberSignIn(View): @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): @@ -87,21 +89,17 @@ class MemberSignIn(View): def post(self, request): member = get_object_or_404(Member, id=request.POST.get('id')) - Visit.objects.create(member=member, purpose=request.POST.get('purpose')) - data = json.dumps(dict(results=dict(id=member.id))) + visit = signin_member(member, request.POST.get('purpose')) + data = json.dumps(dict(results=dict(id=member.id, created_at=visit.created_at.isoformat()))) return JsonResponse(data=data, safe=False, status=201) def get(self, request): - start = timezone.now() - end = start + timedelta(hours=4) - visits = Visit.objects.filter(created_at__lte=end, - created_at__gte=start).prefetch_related() - + visits = get_signed_in_members().prefetch_related() serializer = VisitSerializer(visits, many=True) - json = JSONRenderer().render(serializer.data) + results_json = JSONRenderer().render(serializer.data) - return JsonResponse(data=json.decode(), safe=False, status=200) + return JsonResponse(data=results_json.decode(), safe=False, status=200) @method_decorator(login_required, name='dispatch')