diff --git a/flashcards/serializers.py b/flashcards/serializers.py index 21ffec7..e9290b7 100644 --- a/flashcards/serializers.py +++ b/flashcards/serializers.py @@ -166,35 +166,15 @@ class FlashcardUpdateSerializer(serializers.Serializer): class QuizRequestSerializer(serializers.Serializer): - # sections = PrimaryKeyRelatedField(queryset=Section.objects.all(),required=False, many=True) sections = ListField(child=IntegerField(min_value=1), required=False) material_date_begin = DateTimeField(default=QUARTER_START) material_date_end = DateTimeField(default=QUARTER_END) - def __init__(self, user, instance=None, data=empty, **kwargs): - assert instance is not None or data is not empty - super(QuizRequestSerializer, self).__init__(instance=instance, data=data, **kwargs) - self.user = user - self.user_flashcard = None + def update(self, instance, validated_data): + pass def create(self, validated_data): - return UserFlashcardQuiz.objects.create(user_flashcard=self.user_flashcard) - - def update(self, instance, validated_data): - for attr in validated_data: - setattr(instance, attr, validated_data[attr]) - instance.save() - return instance - - def _get_user_flashcard(self, attrs): - user_flashcard_filter = UserFlashcard.objects.filter( - user=self.user, flashcard__section__in=attrs['sections'], - flashcard__material_date__gte=attrs['material_date_begin'], - flashcard__material_date__lte=attrs['material_date_end'] - ) - if not user_flashcard_filter.exists(): - raise serializers.ValidationError("Your deck for that section is empty") - self.user_flashcard = user_flashcard_filter.order_by('?').first() + return validated_data def validate_material_date_begin(self, value): if QUARTER_START <= value <= QUARTER_END: @@ -208,18 +188,17 @@ class QuizRequestSerializer(serializers.Serializer): def validate_sections(self, value): if value is None: - return self.user.sections + return Section.objects.all() section_filter = Section.objects.filter(pk__in=value) if not section_filter.exists(): - raise serializers.ValidationError("You aren't enrolled in those section(s)") - return section_filter + raise serializers.ValidationError("Those aren't valid sections") + return value def validate(self, attrs): if attrs['material_date_begin'] > attrs['material_date_end']: raise serializers.ValidationError("Invalid range") if 'sections' not in attrs: attrs['sections'] = self.validate_sections(None) - self._get_user_flashcard(attrs) return attrs @@ -242,8 +221,8 @@ class QuizResponseSerializer(ModelSerializer): } def _validate_mask(self, value): - if not isinstance(value, tuple) and value is not None: - raise serializers.ValidationError("The selected mask has to be a list") + if not isinstance(value, list) and value is not None: + raise serializers.ValidationError("The selected mask has to be a list " + str(value)) if value is None or len(value) == 0: return [] if len(value) == 2 and (0 <= value[0] and value[1] <= len(self.instance.user_flashcard.flashcard.text)): diff --git a/flashcards/tests/test_api.py b/flashcards/tests/test_api.py index 30d9ce8..5a5b436 100644 --- a/flashcards/tests/test_api.py +++ b/flashcards/tests/test_api.py @@ -3,7 +3,7 @@ from flashcards.models import * from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK, HTTP_403_FORBIDDEN from rest_framework.test import APITestCase from re import search -import datetime +from datetime import datetime from django.utils.timezone import now from flashcards.validators import FlashcardMask from flashcards.serializers import FlashcardSerializer @@ -220,7 +220,7 @@ class FlashcardDetailTest(APITestCase): response = self.client.patch(url, data, format='json') self.assertEqual(response.status_code, HTTP_200_OK) self.assertEqual(response.data['text'], data['text']) - data = {'material_date': datetime.datetime(2015, 4, 12, 2, 2, 2), + data = {'material_date': datetime(2015, 4, 12, 2, 2, 2), 'mask': '[[1, 3]]'} user2 = User.objects.create(email='wow@wow.wow', password='wow') user2.sections.add(self.section) @@ -378,9 +378,22 @@ class UserFlashcardQuizTests(APITestCase): self.user = User.objects.get(email='none@none.com') self.section = Section.objects.get(pk=1) self.flashcard = Flashcard(text="This is a flashcard", section=self.section, material_date=now(), - author=self.user, mask={(0,4), (5,7)}) + author=self.user, mask={(0, 4), (5, 7)}) self.flashcard.save() self.flashcard.refresh_from_db() self.user_flashcard = UserFlashcard(flashcard=self.flashcard, user=self.user, mask=self.flashcard.mask, pulled=datetime.now()) self.user_flashcard.save() + + def test_quiz_create(self): + url = '/api/study/' + data = {'section': 1} + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, HTTP_200_OK) + self.assertEqual(response.data['pk'], 1) + self.assertEqual(response.data['section'], self.section.pk) + self.assertEqual(response.data['text'], self.flashcard.text) + self.assertIn(response.data['mask'], [[0, 4], [5, 7]]) + + def test_quiz_response(self): + diff --git a/flashcards/tests/test_models.py b/flashcards/tests/test_models.py index 22c3700..57429c2 100644 --- a/flashcards/tests/test_models.py +++ b/flashcards/tests/test_models.py @@ -179,12 +179,23 @@ class UserFlashcardQuizTests(TestCase): def test_quiz_request(self): data = {'sections': [1], 'material_date_begin': QUARTER_START, 'material_date_end': QUARTER_END} - serializer = QuizRequestSerializer(user=self.user, data=data) + serializer = QuizRequestSerializer(data=data) serializer.is_valid(raise_exception=True) - user_flashcard_quiz = serializer.create(serializer.validated_data) + validated_data = serializer.create(serializer.validated_data) + user_flashcard = UserFlashcard.objects.filter(user=self.user, + flashcard__section__pk__in=validated_data['sections'], + flashcard__material_date__gte=validated_data['material_date_begin'], + flashcard__material_date__lte=validated_data['material_date_end']) + self.assertTrue(user_flashcard.exists()) + user_flashcard = user_flashcard.first() + self.assertEqual(user_flashcard, self.user_flashcard) + mask = user_flashcard.mask.get_random_blank() + word = user_flashcard.flashcard.text[slice(*mask)] + user_flashcard_quiz = UserFlashcardQuiz(user_flashcard=user_flashcard, + blanked_word=word) + user_flashcard_quiz.save() self.assertTrue(isinstance(user_flashcard_quiz, UserFlashcardQuiz)) - mask = user_flashcard_quiz.user_flashcard.mask.get_random_blank() - self.assertIn(mask, [(24, 33), (0, 4)]) + self.assertIn(mask, [[24, 33], [0, 4]]) user_flashcard_quiz.blanked_word = user_flashcard_quiz.user_flashcard.flashcard.text[slice(*mask)] self.assertIn(user_flashcard_quiz.blanked_word, ["This", "Flashcard"]) user_flashcard_quiz.save() diff --git a/flashcards/validators.py b/flashcards/validators.py index 8295436..80a5515 100644 --- a/flashcards/validators.py +++ b/flashcards/validators.py @@ -17,8 +17,8 @@ class FlashcardMask(set): def get_random_blank(self): if self.max_offset() > 0: - return sample(self, 1)[0] - return () + return list(sample(self, 1)[0]) + return [] def _iterable_check(self, iterable): if not isinstance(iterable, Iterable) or not all([isinstance(i, Iterable) for i in iterable]): diff --git a/flashcards/views.py b/flashcards/views.py index 76a8987..095b634 100644 --- a/flashcards/views.py +++ b/flashcards/views.py @@ -4,7 +4,7 @@ import django from django.contrib import auth from django.shortcuts import get_object_or_404 from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection, IsFlashcardReviewer -from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcardQuiz +from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard, UserFlashcardQuiz from flashcards.notifications import notify_new_card from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \ PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \ @@ -343,9 +343,13 @@ class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin): class UserFlashcardQuizViewSet(GenericViewSet, CreateModelMixin, UpdateModelMixin): - queryset = UserFlashcardQuiz.objects.all() - serializer_class = QuizAnswerRequestSerializer permission_classes = [IsAuthenticated, IsFlashcardReviewer] + queryset = UserFlashcardQuiz.objects.all() + + def get_serializer_class(self): + if self.request.method == 'POST': + return QuizRequestSerializer + return QuizAnswerRequestSerializer def create(self, request, *args, **kwargs): """ @@ -353,14 +357,29 @@ class UserFlashcardQuizViewSet(GenericViewSet, CreateModelMixin, UpdateModelMixi :param request: A request object. :param format: Format of the request. :return: A response containing + request_serializer: serializers.QuizRequestSerializer + response_serializer: serializers.QuizResponseSerializer """ - serializer = QuizRequestSerializer(user=request.user, data=request.data) + serializer = QuizRequestSerializer(data=request.data) serializer.is_valid(raise_exception=True) - user_flashcard_quiz = serializer.create(serializer.validated_data) - mask = sample(user_flashcard_quiz.user_flashcard.mask.get_random_blank(), 1) - user_flashcard_quiz.blanked_word = user_flashcard_quiz.user_flashcard.flashcard.text[slice(*mask)] + data = serializer.validated_data + user_flashcard_filter = UserFlashcard.objects.filter( + user=request.user, flashcard__section__pk__in=data['sections'], + flashcard__material_date__gte=data['material_date_begin'], + flashcard__material_date__lte=data['material_date_end'] + ) + + if not user_flashcard_filter.exists(): + raise ValidationError("No matching flashcard found in your decks") + + user_flashcard = user_flashcard_filter.order_by('?').first() + mask = user_flashcard.mask.get_random_blank() + user_flashcard_quiz = UserFlashcardQuiz(user_flashcard=user_flashcard, + blanked_word=user_flashcard.flashcard.text[slice(*mask)]) user_flashcard_quiz.save() - return Response(QuizResponseSerializer(instance=user_flashcard_quiz, mask=mask).data, status=HTTP_200_OK) + assert user_flashcard_quiz is not None + response = QuizResponseSerializer(instance=user_flashcard_quiz, mask=mask) + return Response(response.data, status=HTTP_200_OK) def partial_update(self, request, *args, **kwargs): """ @@ -368,6 +387,7 @@ class UserFlashcardQuizViewSet(GenericViewSet, CreateModelMixin, UpdateModelMixi :param request: A request object. :param format: Format of the request. :return: A response containing + request_serializer: serializers.QuizAnswerRequestSerializer """ user_flashcard_quiz = self.get_object() serializer = QuizAnswerRequestSerializer(instance=user_flashcard_quiz, data=request.data) diff --git a/requirements.txt b/requirements.txt index ac5676c..a2747b4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ Django>=1.8 django-websocket-redis #gevent==1.0.1 #greenlet==0.4.5 -#redis==2.10.3 +redis==2.10.3 six==1.9.0 djangorestframework docutils diff --git a/scripts/run_production.sh b/scripts/run_production.sh index b57da49..35c49a6 100755 --- a/scripts/run_production.sh +++ b/scripts/run_production.sh @@ -4,3 +4,4 @@ source venv/bin/activate # newrelic-admin run-program /srv/flashy-backend/venv/bin/gunicorn --pid /run/flashy/gunicorn.pid -w 6 -n flashy -b 127.0.0.1:7002 flashy.wsgi uwsgi /etc/uwsgi/websocket.ini --touch-reload=/etc/uwsgi/websocket.ini & newrelic-admin run-program uwsgi /etc/uwsgi/flashy.ini --touch-reload=/etc/uwsgi/flashy.ini +trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT