diff --git a/flashcards/migrations/0011_auto_20150514_0207.py b/flashcards/migrations/0011_auto_20150514_0207.py new file mode 100644 index 0000000..1742b01 --- /dev/null +++ b/flashcards/migrations/0011_auto_20150514_0207.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import flashcards.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('flashcards', '0010_auto_20150513_1546'), + ] + + operations = [ + migrations.AlterModelOptions( + name='lectureperiod', + options={'ordering': ['section', 'week_day']}, + ), + migrations.AlterField( + model_name='userflashcard', + name='mask', + field=flashcards.fields.MaskField(default=None, blank_sep=b',', range_sep=b'-', max_length=255, blank=True, help_text=b'The user-specific mask on the card', null=True), + ), + migrations.AlterField( + model_name='userflashcard', + name='pulled', + field=models.DateTimeField(default=None, help_text=b'When the user pulled the card', null=True, blank=True), + ), + migrations.AlterField( + model_name='userflashcard', + name='unpulled', + field=models.DateTimeField(default=None, help_text=b'When the user unpulled this card', null=True, blank=True), + ), + ] diff --git a/flashcards/models.py b/flashcards/models.py index a371f02..f5bb46e 100644 --- a/flashcards/models.py +++ b/flashcards/models.py @@ -54,10 +54,10 @@ class UserFlashcard(Model): 3. A user has a flashcard hidden from them """ user = ForeignKey('User') - mask = MaskField(max_length=255, null=True, blank=True, help_text="The user-specific mask on the card") - pulled = DateTimeField(blank=True, null=True, help_text="When the user pulled the card") + mask = MaskField(max_length=255, null=True, blank=True, default=None, help_text="The user-specific mask on the card") + pulled = DateTimeField(blank=True, null=True, default=None, help_text="When the user pulled the card") flashcard = ForeignKey('Flashcard') - unpulled = DateTimeField(blank=True, null=True, help_text="When the user unpulled this card") + unpulled = DateTimeField(blank=True, null=True, default=None, help_text="When the user unpulled this card") class Meta: # There can be at most one UserFlashcard for each User and Flashcard diff --git a/flashcards/serializers.py b/flashcards/serializers.py index b551f31..3bbe8df 100644 --- a/flashcards/serializers.py +++ b/flashcards/serializers.py @@ -153,6 +153,23 @@ class FlashcardSerializer(ModelSerializer): raise serializers.ValidationError("Hide reason limit exceeded") return value + def validate_mask(self, value): + if value.max_offset() >= len(self.text): + raise serializers.ValidationError("Mask out of bounds") + return value + class Meta: model = Flashcard - exclude = 'author', 'mask', + exclude = 'author', + + +class FlashcardUpdateSerializer(serializers.Serializer): + text = CharField(max_length=255) + material_date = DateTimeField() + mask = MaskFieldSerializer() + + def validate_material_date(self, date): + quarter_end = datetime(2015, 6, 15) + if date > quarter_end: + raise serializers.ValidationError("Invalid material_date for the flashcard") + return date diff --git a/flashcards/validators.py b/flashcards/validators.py index 1620eef..3271c81 100644 --- a/flashcards/validators.py +++ b/flashcards/validators.py @@ -1,5 +1,3 @@ -__author__ = 'rray' - from collections import Iterable @@ -11,6 +9,9 @@ class FlashcardMask(set): self._interval_check() self._overlap_check() + def max_offset(self): + return self._end + def _iterable_check(self): if not all([isinstance(i, Iterable) for i in self]): raise TypeError("Interval not a valid iterable") @@ -26,6 +27,7 @@ class FlashcardMask(set): if not (0 <= beg <= 255) or not (0 <= end <= 255) or not (beg <= end) or not (beg > p_end): raise OverlapIntervalException((beg, end), "Invalid interval offsets in the mask") p_beg, p_end = beg, end + self._end = end class OverlapIntervalException(Exception): diff --git a/flashcards/views.py b/flashcards/views.py index 4945206..bbfd74e 100644 --- a/flashcards/views.py +++ b/flashcards/views.py @@ -3,7 +3,8 @@ from django.db.models import Q from flashcards.api import StandardResultsSetPagination from flashcards.models import Section, User, Flashcard, FlashcardReport, UserFlashcard from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \ - PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer + PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \ + FlashcardUpdateSerializer from rest_framework.decorators import detail_route, permission_classes, api_view, list_route from rest_framework.generics import ListAPIView, GenericAPIView from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin @@ -16,6 +17,7 @@ from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED from rest_framework.response import Response from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied from simple_email_confirmation import EmailAddress +from datetime import datetime class SectionViewSet(ReadOnlyModelViewSet): @@ -295,3 +297,36 @@ class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin): user_card, created = UserFlashcard.objects.get_or_create(user=user, flashcard=flashcard) user_card.save() return Response(status=HTTP_204_NO_CONTENT) + + @detail_route(methods=['PATCH'], permission_classes=[IsAuthenticated]) + def patch(self, request, pk): + """ + Edit settings related to a card for the user. + :param request: The request object. + :param pk: The primary key of the flashcard. + :return: A 204 response upon success. + """ + user = request.user + flashcard = Flashcard.objects.get(pk=pk) + mask = flashcard.mask + data = FlashcardUpdateSerializer(data=request.data) + data.is_valid(raise_exception=True) + new_flashcard = data.validated_data + if ('material_date' in new_flashcard and new_flashcard['material_date'] != flashcard.material_date) \ + or ('text' in new_flashcard and new_flashcard.text != flashcard.text): + if flashcard.author != user or UserFlashcard.objects.filter(flashcard=flashcard).count() > 1: + flashcard.pk = None + flashcard.mask = mask + if 'material_date' in new_flashcard: + flashcard.material_date = new_flashcard['material_date'] + if 'text' in new_flashcard: + flashcard.text = new_flashcard['text'] + if 'mask' in new_flashcard: + flashcard.mask = new_flashcard['mask'] + flashcard.save() + user_flashcard, created = UserFlashcard.objects.get_or_create(user=user, flashcard=flashcard) + if created: + user_flashcard.pulled = datetime.now() + user_flashcard.mask = flashcard.mask + if 'mask' in new_flashcard: + user_flashcard.mask = new_flashcard['mask']