Commit 057d2cc3f7f9516ed0eaba1357efa60d5a1f068d

Authored by Rohan Rangray
1 parent 34586f534d
Exists in master

Attempted fix for MaskSerializer.

Showing 2 changed files with 13 additions and 2 deletions Inline Diff

flashcards/serializers.py View file @ 057d2cc
from json import dumps, loads 1 1 from json import dumps, loads
2 2
from django.utils.datetime_safe import datetime 3 3 from django.utils.datetime_safe import datetime
from django.utils.timezone import now 4 4 from django.utils.timezone import now
from flashcards.models import Section, LecturePeriod, User, Flashcard, UserFlashcard, UserFlashcardQuiz 5 5 from flashcards.models import Section, LecturePeriod, User, Flashcard, UserFlashcard, UserFlashcardQuiz
from flashcards.validators import FlashcardMask, OverlapIntervalException 6 6 from flashcards.validators import FlashcardMask, OverlapIntervalException
from rest_framework import serializers 7 7 from rest_framework import serializers
from rest_framework.fields import EmailField, BooleanField, CharField, IntegerField, DateTimeField, empty 8 8 from rest_framework.fields import EmailField, BooleanField, CharField, IntegerField, DateTimeField, empty
from rest_framework.serializers import ModelSerializer, Serializer, PrimaryKeyRelatedField, ListField 9 9 from rest_framework.serializers import ModelSerializer, Serializer, PrimaryKeyRelatedField, ListField
from rest_framework.validators import UniqueValidator 10 10 from rest_framework.validators import UniqueValidator
from flashy.settings import QUARTER_END, QUARTER_START 11 11 from flashy.settings import QUARTER_END, QUARTER_START
12 12
13 13
class EmailSerializer(Serializer): 14 14 class EmailSerializer(Serializer):
email = EmailField(required=True) 15 15 email = EmailField(required=True)
16 16
17 17
class EmailPasswordSerializer(EmailSerializer): 18 18 class EmailPasswordSerializer(EmailSerializer):
password = CharField(required=True) 19 19 password = CharField(required=True)
20 20
21 21
class RegistrationSerializer(EmailPasswordSerializer): 22 22 class RegistrationSerializer(EmailPasswordSerializer):
email = EmailField(required=True, validators=[UniqueValidator(queryset=User.objects.all())]) 23 23 email = EmailField(required=True, validators=[UniqueValidator(queryset=User.objects.all())])
24 24
25 25
class PasswordResetRequestSerializer(EmailSerializer): 26 26 class PasswordResetRequestSerializer(EmailSerializer):
def validate_email(self, value): 27 27 def validate_email(self, value):
try: 28 28 try:
User.objects.get(email=value) 29 29 User.objects.get(email=value)
return value 30 30 return value
except User.DoesNotExist: 31 31 except User.DoesNotExist:
raise serializers.ValidationError('No user exists with that email') 32 32 raise serializers.ValidationError('No user exists with that email')
33 33
34 34
class PasswordResetSerializer(Serializer): 35 35 class PasswordResetSerializer(Serializer):
new_password = CharField(required=True, allow_blank=False) 36 36 new_password = CharField(required=True, allow_blank=False)
uid = IntegerField(required=True) 37 37 uid = IntegerField(required=True)
token = CharField(required=True) 38 38 token = CharField(required=True)
39 39
def validate_uid(self, value): 40 40 def validate_uid(self, value):
try: 41 41 try:
User.objects.get(id=value) 42 42 User.objects.get(id=value)
return value 43 43 return value
except User.DoesNotExist: 44 44 except User.DoesNotExist:
raise serializers.ValidationError('Could not verify reset token') 45 45 raise serializers.ValidationError('Could not verify reset token')
46 46
47 47
class UserUpdateSerializer(Serializer): 48 48 class UserUpdateSerializer(Serializer):
old_password = CharField(required=False) 49 49 old_password = CharField(required=False)
new_password = CharField(required=False, allow_blank=False) 50 50 new_password = CharField(required=False, allow_blank=False)
confirmation_key = CharField(required=False) 51 51 confirmation_key = CharField(required=False)
# reset_token = CharField(required=False) 52 52 # reset_token = CharField(required=False)
53 53
def validate(self, data): 54 54 def validate(self, data):
if 'new_password' in data and 'old_password' not in data: 55 55 if 'new_password' in data and 'old_password' not in data:
raise serializers.ValidationError('old_password is required to set a new_password') 56 56 raise serializers.ValidationError('old_password is required to set a new_password')
return data 57 57 return data
58 58
59 59
class Password(Serializer): 60 60 class Password(Serializer):
email = EmailField(required=True) 61 61 email = EmailField(required=True)
password = CharField(required=True) 62 62 password = CharField(required=True)
63 63
64 64
class LecturePeriodSerializer(ModelSerializer): 65 65 class LecturePeriodSerializer(ModelSerializer):
class Meta: 66 66 class Meta:
model = LecturePeriod 67 67 model = LecturePeriod
exclude = 'id', 'section' 68 68 exclude = 'id', 'section'
69 69
70 70
class SectionSerializer(ModelSerializer): 71 71 class SectionSerializer(ModelSerializer):
lecture_times = CharField() 72 72 lecture_times = CharField()
short_name = CharField() 73 73 short_name = CharField()
long_name = CharField() 74 74 long_name = CharField()
75 75
class Meta: 76 76 class Meta:
model = Section 77 77 model = Section
78 78
79 79
class DeepSectionSerializer(SectionSerializer): 80 80 class DeepSectionSerializer(SectionSerializer):
lectures = LecturePeriodSerializer(source='lectureperiod_set', many=True, read_only=True) 81 81 lectures = LecturePeriodSerializer(source='lectureperiod_set', many=True, read_only=True)
82 82
83 83
class UserSerializer(ModelSerializer): 84 84 class UserSerializer(ModelSerializer):
email = EmailField(required=False) 85 85 email = EmailField(required=False)
sections = SectionSerializer(many=True) 86 86 sections = SectionSerializer(many=True)
is_confirmed = BooleanField() 87 87 is_confirmed = BooleanField()
88 88
class Meta: 89 89 class Meta:
model = User 90 90 model = User
fields = ("sections", "email", "is_confirmed", "last_login", "date_joined") 91 91 fields = ("sections", "email", "is_confirmed", "last_login", "date_joined")
92 92
93 93
class MaskFieldSerializer(serializers.Field): 94 94 class MaskFieldSerializer(serializers.Field):
default_error_messages = { 95 95 default_error_messages = {
'max_length': 'Ensure this field has no more than {max_length} characters.', 96 96 'max_length': 'Ensure this field has no more than {max_length} characters.',
'interval': 'Ensure this field has valid intervals.', 97 97 'interval': 'Ensure this field has valid intervals.',
'overlap': 'Ensure this field does not have overlapping intervals.' 98 98 'overlap': 'Ensure this field does not have overlapping intervals.'
} 99 99 }
100 100
def to_representation(self, value): 101 101 def to_representation(self, value):
return dumps(list(self._make_mask(value))) 102 102 return map(list, self._make_mask(value))
103 103
def to_internal_value(self, value): 104 104 def to_internal_value(self, value):
if not isinstance(value, list): 105 105 if not isinstance(value, list):
value = loads(value) 106 106 value = loads(value)
return self._make_mask(value) 107 107 return self._make_mask(value)
108 108
def _make_mask(self, data): 109 109 def _make_mask(self, data):
try: 110 110 try:
mask = FlashcardMask(data) 111 111 mask = FlashcardMask(data)
except ValueError: 112 112 except ValueError:
raise serializers.ValidationError("Invalid JSON for MaskField") 113 113 raise serializers.ValidationError("Invalid JSON for MaskField")
except TypeError: 114 114 except TypeError:
raise serializers.ValidationError("Invalid data for MaskField.") 115 115 raise serializers.ValidationError("Invalid data for MaskField.")
except OverlapIntervalException: 116 116 except OverlapIntervalException:
raise serializers.ValidationError("Invalid intervals for MaskField data.") 117 117 raise serializers.ValidationError("Invalid intervals for MaskField data.")
if len(mask) > 32: 118 118 if len(mask) > 32:
raise serializers.ValidationError("Too many intervals in the mask.") 119 119 raise serializers.ValidationError("Too many intervals in the mask.")
return mask 120 120 return mask
121 121
122 122
class FlashcardSerializer(ModelSerializer): 123 123 class FlashcardSerializer(ModelSerializer):
is_hidden = BooleanField(read_only=True) 124 124 is_hidden = BooleanField(read_only=True)
hide_reason = CharField(read_only=True) 125 125 hide_reason = CharField(read_only=True)
material_date = DateTimeField(default=now) 126 126 material_date = DateTimeField(default=now)
mask = MaskFieldSerializer(allow_null=True) 127 127 mask = MaskFieldSerializer(allow_null=True)
score = IntegerField(read_only=True) 128 128 score = IntegerField(read_only=True)
129 129
def validate_material_date(self, value): 130 130 def validate_material_date(self, value):
# TODO: make this dynamic 131 131 # TODO: make this dynamic
if QUARTER_START <= value <= QUARTER_END: 132 132 if QUARTER_START <= value <= QUARTER_END:
return value 133 133 return value
else: 134 134 else:
raise serializers.ValidationError("Material date is outside allowed range for this quarter") 135 135 raise serializers.ValidationError("Material date is outside allowed range for this quarter")
136 136
def validate_pushed(self, value): 137 137 def validate_pushed(self, value):
if value > datetime.now(): 138 138 if value > datetime.now():
raise serializers.ValidationError("Invalid creation date for the Flashcard") 139 139 raise serializers.ValidationError("Invalid creation date for the Flashcard")
return value 140 140 return value
141 141
def validate_mask(self, value): 142 142 def validate_mask(self, value):
if value is None: 143 143 if value is None:
return None 144 144 return None
if len(self.initial_data['text']) < value.max_offset(): 145 145 if len(self.initial_data['text']) < value.max_offset():
raise serializers.ValidationError("Mask out of bounds") 146 146 raise serializers.ValidationError("Mask out of bounds")
return value 147 147 return value
148 148
class Meta: 149 149 class Meta:
model = Flashcard 150 150 model = Flashcard
exclude = 'author', 'previous' 151 151 exclude = 'author', 'previous'
152 152
153 153
class FlashcardUpdateSerializer(serializers.Serializer): 154 154 class FlashcardUpdateSerializer(serializers.Serializer):
text = CharField(max_length=255, required=False) 155 155 text = CharField(max_length=255, required=False)
material_date = DateTimeField(required=False) 156 156 material_date = DateTimeField(required=False)
mask = MaskFieldSerializer(required=False) 157 157 mask = MaskFieldSerializer(required=False)
158 158
def validate_material_date(self, date): 159 159 def validate_material_date(self, date):
if date > QUARTER_END: 160 160 if date > QUARTER_END:
raise serializers.ValidationError("Invalid material_date for the flashcard") 161 161 raise serializers.ValidationError("Invalid material_date for the flashcard")
return date 162 162 return date
163 163
def validate(self, attrs): 164 164 def validate(self, attrs):
# Make sure that at least one of the attributes was passed in 165 165 # Make sure that at least one of the attributes was passed in
if not any(i in attrs for i in ['material_date', 'text', 'mask']): 166 166 if not any(i in attrs for i in ['material_date', 'text', 'mask']):
raise serializers.ValidationError("No new value passed in") 167 167 raise serializers.ValidationError("No new value passed in")
return attrs 168 168 return attrs
169 169
170 170
class QuizRequestSerializer(serializers.Serializer): 171 171 class QuizRequestSerializer(serializers.Serializer):
sections = ListField(child=IntegerField(min_value=1), required=False) 172 172 sections = ListField(child=IntegerField(min_value=1), required=False)
material_date_begin = DateTimeField(default=QUARTER_START) 173 173 material_date_begin = DateTimeField(default=QUARTER_START)
material_date_end = DateTimeField(default=QUARTER_END) 174 174 material_date_end = DateTimeField(default=QUARTER_END)
175 175
def update(self, instance, validated_data): 176 176 def update(self, instance, validated_data):
pass 177 177 pass
178 178
def create(self, validated_data): 179 179 def create(self, validated_data):
return validated_data 180 180 return validated_data
181 181
def validate_material_date_begin(self, value): 182 182 def validate_material_date_begin(self, value):
if QUARTER_START <= value <= QUARTER_END: 183 183 if QUARTER_START <= value <= QUARTER_END:
return value 184 184 return value
raise serializers.ValidationError("Invalid begin date for the flashcard range") 185 185 raise serializers.ValidationError("Invalid begin date for the flashcard range")
186 186
def validate_material_date_end(self, value): 187 187 def validate_material_date_end(self, value):
if QUARTER_START <= value <= QUARTER_END: 188 188 if QUARTER_START <= value <= QUARTER_END:
return value 189 189 return value
raise serializers.ValidationError("Invalid end date for the flashcard range") 190 190 raise serializers.ValidationError("Invalid end date for the flashcard range")
191 191
def validate_sections(self, value): 192 192 def validate_sections(self, value):
if value is None: 193 193 if value is None:
return Section.objects.all() 194 194 return Section.objects.all()
section_filter = Section.objects.filter(pk__in=value) 195 195 section_filter = Section.objects.filter(pk__in=value)
if not section_filter.exists(): 196 196 if not section_filter.exists():
raise serializers.ValidationError("Those aren't valid sections") 197 197 raise serializers.ValidationError("Those aren't valid sections")
return value 198 198 return value
199 199
def validate(self, attrs): 200 200 def validate(self, attrs):
if attrs['material_date_begin'] > attrs['material_date_end']: 201 201 if attrs['material_date_begin'] > attrs['material_date_end']:
raise serializers.ValidationError("Invalid range") 202 202 raise serializers.ValidationError("Invalid range")
if 'sections' not in attrs: 203 203 if 'sections' not in attrs:
attrs['sections'] = self.validate_sections(None) 204 204 attrs['sections'] = self.validate_sections(None)
return attrs 205 205 return attrs
flashcards/views.py View file @ 057d2cc
from random import sample 1 1 from random import sample
2 2
import django 3 3 import django
from django.contrib import auth 4 4 from django.contrib import auth
from django.shortcuts import get_object_or_404 5 5 from django.shortcuts import get_object_or_404
from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection, IsFlashcardReviewer 6 6 from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection, IsFlashcardReviewer
from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard, UserFlashcardQuiz 7 7 from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard, UserFlashcardQuiz
from flashcards.notifications import notify_new_card 8 8 from flashcards.notifications import notify_new_card
from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \ 9 9 from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \
PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \ 10 10 PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \
FlashcardUpdateSerializer, QuizRequestSerializer, QuizResponseSerializer, \ 11 11 FlashcardUpdateSerializer, QuizRequestSerializer, QuizResponseSerializer, \
QuizAnswerRequestSerializer, DeepSectionSerializer 12 12 QuizAnswerRequestSerializer, DeepSectionSerializer
from rest_framework.decorators import detail_route, permission_classes, api_view, list_route 13 13 from rest_framework.decorators import detail_route, permission_classes, api_view, list_route
from rest_framework.generics import ListAPIView, GenericAPIView 14 14 from rest_framework.generics import ListAPIView, GenericAPIView
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin 15 15 from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.permissions import IsAuthenticated 16 16 from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet 17 17 from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet
from django.core.mail import send_mail 18 18 from django.core.mail import send_mail
from django.contrib.auth import authenticate 19 19 from django.contrib.auth import authenticate
from django.contrib.auth.tokens import default_token_generator 20 20 from django.contrib.auth.tokens import default_token_generator
21 from django.db.models import Count, F
from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK 21 22 from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK
from rest_framework.response import Response 22 23 from rest_framework.response import Response
from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied 23 24 from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied
from simple_email_confirmation import EmailAddress 24 25 from simple_email_confirmation import EmailAddress
26 from math import e
25 27
26 28
class SectionViewSet(ReadOnlyModelViewSet): 27 29 class SectionViewSet(ReadOnlyModelViewSet):
queryset = Section.objects.all() 28 30 queryset = Section.objects.all()
serializer_class = DeepSectionSerializer 29 31 serializer_class = DeepSectionSerializer
pagination_class = StandardResultsSetPagination 30 32 pagination_class = StandardResultsSetPagination
permission_classes = [IsAuthenticated] 31 33 permission_classes = [IsAuthenticated]
32 34
@detail_route(methods=['GET']) 33 35 @detail_route(methods=['GET'])
def flashcards(self, request, pk): 34 36 def flashcards(self, request, pk):
""" 35 37 """
Gets flashcards for a section, excluding hidden cards. 36 38 Gets flashcards for a section, excluding hidden cards.
Returned in strictly chronological order (material date). 37 39 Returned in strictly chronological order (material date).
""" 38 40 """
flashcards = Flashcard.cards_visible_to(request.user).filter(section=self.get_object()).all() 39 41 flashcards = Flashcard.cards_visible_to(request.user).filter(section=self.get_object()).all()
return Response(FlashcardSerializer(flashcards, many=True).data) 40 42 return Response(FlashcardSerializer(flashcards, many=True).data)
41 43
@detail_route(methods=['POST']) 42 44 @detail_route(methods=['POST'])
def enroll(self, request, pk): 43 45 def enroll(self, request, pk):
""" 44 46 """
Add the current user to a specified section 45 47 Add the current user to a specified section
If the class has a whitelist, but the user is not on the whitelist, the request will fail. 46 48 If the class has a whitelist, but the user is not on the whitelist, the request will fail.
--- 47 49 ---
view_mocker: flashcards.api.mock_no_params 48 50 view_mocker: flashcards.api.mock_no_params
""" 49 51 """
try: 50 52 try:
self.get_object().enroll(request.user) 51 53 self.get_object().enroll(request.user)
except django.core.exceptions.PermissionDenied as e: 52 54 except django.core.exceptions.PermissionDenied as e:
raise PermissionDenied(e) 53 55 raise PermissionDenied(e)
except django.core.exceptions.ValidationError as e: 54 56 except django.core.exceptions.ValidationError as e:
raise ValidationError(e) 55 57 raise ValidationError(e)
return Response(status=HTTP_204_NO_CONTENT) 56 58 return Response(status=HTTP_204_NO_CONTENT)
57 59
@detail_route(methods=['POST']) 58 60 @detail_route(methods=['POST'])
def drop(self, request, pk): 59 61 def drop(self, request, pk):
""" 60 62 """
Remove the current user from a specified section 61 63 Remove the current user from a specified section
If the user is not in the class, the request will fail. 62 64 If the user is not in the class, the request will fail.
--- 63 65 ---
view_mocker: flashcards.api.mock_no_params 64 66 view_mocker: flashcards.api.mock_no_params
""" 65 67 """
try: 66 68 try:
self.get_object().drop(request.user) 67 69 self.get_object().drop(request.user)
except django.core.exceptions.PermissionDenied as e: 68 70 except django.core.exceptions.PermissionDenied as e:
raise PermissionDenied(e) 69 71 raise PermissionDenied(e)
except django.core.exceptions.ValidationError as e: 70 72 except django.core.exceptions.ValidationError as e:
raise ValidationError(e) 71 73 raise ValidationError(e)
return Response(status=HTTP_204_NO_CONTENT) 72 74 return Response(status=HTTP_204_NO_CONTENT)
73 75
@list_route(methods=['GET']) 74 76 @list_route(methods=['GET'])
def search(self, request): 75 77 def search(self, request):
""" 76 78 """
Returns a list of sections which match a user's query 77 79 Returns a list of sections which match a user's query
--- 78 80 ---
parameters: 79 81 parameters:
- name: q 80 82 - name: q
description: space-separated list of terms 81 83 description: space-separated list of terms
required: true 82 84 required: true
type: form 83 85 type: form
response_serializer: SectionSerializer 84 86 response_serializer: SectionSerializer
""" 85 87 """
query = request.GET.get('q', None) 86 88 query = request.GET.get('q', None)
if not query: return Response('[]') 87 89 if not query: return Response('[]')
qs = Section.search(query.split(' '))[:20] 88 90 qs = Section.search(query.split(' '))[:20]
data = SectionSerializer(qs, many=True).data 89 91 data = SectionSerializer(qs, many=True).data
return Response(data) 90 92 return Response(data)
91 93
@detail_route(methods=['GET']) 92 94 @detail_route(methods=['GET'])
def deck(self, request, pk): 93 95 def deck(self, request, pk):
""" 94 96 """
Gets the contents of a user's deck for a given section. 95 97 Gets the contents of a user's deck for a given section.
""" 96 98 """
qs = request.user.get_deck(self.get_object()) 97 99 qs = request.user.get_deck(self.get_object())
serializer = FlashcardSerializer(qs, many=True) 98 100 serializer = FlashcardSerializer(qs, many=True)
return Response(serializer.data) 99 101 return Response(serializer.data)
100 102
@detail_route(methods=['GET'], permission_classes=[IsAuthenticated]) 101 103 @detail_route(methods=['GET'], permission_classes=[IsAuthenticated])
def ordered_deck(self, request, pk): 102 104 def ordered_deck(self, request, pk):
""" 103 105 """
Get a chronological order by material_date of flashcards for a section. 104 106 Get a chronological order by material_date of flashcards for a section.
This excludes hidden card. 105 107 This excludes hidden card.
""" 106 108 """
qs = request.user.get_deck(self.get_object()).order_by('-material_date') 107 109 qs = request.user.get_deck(self.get_object()).order_by('-material_date')
serializer = FlashcardSerializer(qs, many=True) 108 110 serializer = FlashcardSerializer(qs, many=True)
return Response(serializer.data) 109 111 return Response(serializer.data)
110 112
@detail_route(methods=['GET']) 111 113 @detail_route(methods=['GET'])
def feed(self, request, pk): 112 114 def feed(self, request, pk):
""" 113 115 """
Gets the contents of a user's feed for a section. 114 116 Gets the contents of a user's feed for a section.
Exclude cards that are already in the user's deck 115 117 Exclude cards that are already in the user's deck
""" 116 118 """
serializer = FlashcardSerializer(self.get_object().get_feed_for_user(request.user), many=True) 117 119 serializer = FlashcardSerializer(self.get_object().get_feed_for_user(request.user), many=True)
return Response(serializer.data) 118 120 return Response(serializer.data)
119 121
120 122
class UserSectionListView(ListAPIView): 121 123 class UserSectionListView(ListAPIView):
serializer_class = DeepSectionSerializer 122 124 serializer_class = DeepSectionSerializer
permission_classes = [IsAuthenticated] 123 125 permission_classes = [IsAuthenticated]
124 126
def get_queryset(self): 125 127 def get_queryset(self):
return self.request.user.sections.all() 126 128 return self.request.user.sections.all()
127 129
def paginate_queryset(self, queryset): return None 128 130 def paginate_queryset(self, queryset): return None
129 131
130 132
class UserDetail(GenericAPIView): 131 133 class UserDetail(GenericAPIView):
serializer_class = UserSerializer 132 134 serializer_class = UserSerializer
permission_classes = [IsAuthenticated] 133 135 permission_classes = [IsAuthenticated]
134 136
def patch(self, request, format=None): 135 137 def patch(self, request, format=None):
""" 136 138 """
Updates the user's password, or verifies their email address 137 139 Updates the user's password, or verifies their email address
--- 138 140 ---
request_serializer: UserUpdateSerializer 139 141 request_serializer: UserUpdateSerializer
response_serializer: UserSerializer 140 142 response_serializer: UserSerializer
""" 141 143 """
data = UserUpdateSerializer(data=request.data, context={'user': request.user}) 142 144 data = UserUpdateSerializer(data=request.data, context={'user': request.user})
data.is_valid(raise_exception=True) 143 145 data.is_valid(raise_exception=True)
data = data.validated_data 144 146 data = data.validated_data
145 147
if 'new_password' in data: 146 148 if 'new_password' in data:
if not request.user.check_password(data['old_password']): 147 149 if not request.user.check_password(data['old_password']):
raise ValidationError('old_password is incorrect') 148 150 raise ValidationError('old_password is incorrect')
request.user.set_password(data['new_password']) 149 151 request.user.set_password(data['new_password'])
request.user.save() 150 152 request.user.save()
151 153
if 'confirmation_key' in data: 152 154 if 'confirmation_key' in data:
try: 153 155 try:
request.user.confirm_email(data['confirmation_key']) 154 156 request.user.confirm_email(data['confirmation_key'])
except EmailAddress.DoesNotExist: 155 157 except EmailAddress.DoesNotExist:
raise ValidationError('confirmation_key is invalid') 156 158 raise ValidationError('confirmation_key is invalid')
157 159
return Response(UserSerializer(request.user).data) 158 160 return Response(UserSerializer(request.user).data)
159 161
def get(self, request, format=None): 160 162 def get(self, request, format=None):
""" 161 163 """
Return data about the user 162 164 Return data about the user
--- 163 165 ---
response_serializer: UserSerializer 164 166 response_serializer: UserSerializer
""" 165 167 """
serializer = UserSerializer(request.user, context={'request': request}) 166 168 serializer = UserSerializer(request.user, context={'request': request})
return Response(serializer.data) 167 169 return Response(serializer.data)
168 170
def delete(self, request): 169 171 def delete(self, request):
""" 170 172 """
Irrevocably delete the user and their data 171 173 Irrevocably delete the user and their data
172 174
Yes, really 173 175 Yes, really
""" 174 176 """
request.user.delete() 175 177 request.user.delete()
return Response(status=HTTP_204_NO_CONTENT) 176 178 return Response(status=HTTP_204_NO_CONTENT)
177 179
178 180
@api_view(['POST']) 179 181 @api_view(['POST'])
def register(request, format=None): 180 182 def register(request, format=None):
""" 181 183 """
Register a new user 182 184 Register a new user
--- 183 185 ---
request_serializer: EmailPasswordSerializer 184 186 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 185 187 response_serializer: UserSerializer
""" 186 188 """
data = RegistrationSerializer(data=request.data) 187 189 data = RegistrationSerializer(data=request.data)
data.is_valid(raise_exception=True) 188 190 data.is_valid(raise_exception=True)
189 191
User.objects.create_user(**data.validated_data) 190 192 User.objects.create_user(**data.validated_data)
user = authenticate(**data.validated_data) 191 193 user = authenticate(**data.validated_data)
auth.login(request, user) 192 194 auth.login(request, user)
193 195
return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED) 194 196 return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED)
195 197
196 198
@api_view(['POST']) 197 199 @api_view(['POST'])
def login(request): 198 200 def login(request):
""" 199 201 """
Authenticates user and returns user data if valid. 200 202 Authenticates user and returns user data if valid.
--- 201 203 ---
request_serializer: EmailPasswordSerializer 202 204 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 203 205 response_serializer: UserSerializer
""" 204 206 """
205 207
data = EmailPasswordSerializer(data=request.data) 206 208 data = EmailPasswordSerializer(data=request.data)
data.is_valid(raise_exception=True) 207 209 data.is_valid(raise_exception=True)
user = authenticate(**data.validated_data) 208 210 user = authenticate(**data.validated_data)
209 211
if user is None: 210 212 if user is None:
raise AuthenticationFailed('Invalid email or password') 211 213 raise AuthenticationFailed('Invalid email or password')
if not user.is_active: 212 214 if not user.is_active:
raise NotAuthenticated('Account is disabled') 213 215 raise NotAuthenticated('Account is disabled')
auth.login(request, user) 214 216 auth.login(request, user)
return Response(UserSerializer(request.user).data) 215 217 return Response(UserSerializer(request.user).data)
216 218
217 219
@api_view(['POST']) 218 220 @api_view(['POST'])
@permission_classes((IsAuthenticated,)) 219 221 @permission_classes((IsAuthenticated,))
def logout(request, format=None): 220 222 def logout(request, format=None):
""" 221 223 """
Logs the authenticated user out. 222 224 Logs the authenticated user out.
""" 223 225 """
auth.logout(request) 224 226 auth.logout(request)
return Response(status=HTTP_204_NO_CONTENT) 225 227 return Response(status=HTTP_204_NO_CONTENT)
226 228
227 229
@api_view(['POST']) 228 230 @api_view(['POST'])
def request_password_reset(request, format=None): 229 231 def request_password_reset(request, format=None):
""" 230 232 """
Send a password reset token/link to the provided email. 231 233 Send a password reset token/link to the provided email.
--- 232 234 ---
request_serializer: PasswordResetRequestSerializer 233 235 request_serializer: PasswordResetRequestSerializer
""" 234 236 """
data = PasswordResetRequestSerializer(data=request.data) 235 237 data = PasswordResetRequestSerializer(data=request.data)
data.is_valid(raise_exception=True) 236 238 data.is_valid(raise_exception=True)
get_object_or_404(User, email=data['email'].value).request_password_reset() 237 239 get_object_or_404(User, email=data['email'].value).request_password_reset()
return Response(status=HTTP_204_NO_CONTENT) 238 240 return Response(status=HTTP_204_NO_CONTENT)
239 241
240 242
@api_view(['POST']) 241 243 @api_view(['POST'])
def reset_password(request, format=None): 242 244 def reset_password(request, format=None):
""" 243 245 """
Updates user's password to new password if token is valid. 244 246 Updates user's password to new password if token is valid.
--- 245 247 ---
request_serializer: PasswordResetSerializer 246 248 request_serializer: PasswordResetSerializer
""" 247 249 """
data = PasswordResetSerializer(data=request.data) 248 250 data = PasswordResetSerializer(data=request.data)
data.is_valid(raise_exception=True) 249 251 data.is_valid(raise_exception=True)
250 252
user = User.objects.get(id=data['uid'].value) 251 253 user = User.objects.get(id=data['uid'].value)
# Check token validity. 252 254 # Check token validity.
253 255
if default_token_generator.check_token(user, data['token'].value): 254 256 if default_token_generator.check_token(user, data['token'].value):
user.set_password(data['new_password'].value) 255 257 user.set_password(data['new_password'].value)
user.save() 256 258 user.save()
else: 257 259 else:
raise ValidationError('Could not verify reset token') 258 260 raise ValidationError('Could not verify reset token')
return Response(status=HTTP_204_NO_CONTENT) 259 261 return Response(status=HTTP_204_NO_CONTENT)
260 262
261 263
class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin): 262 264 class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin):
queryset = Flashcard.objects.all() 263 265 queryset = Flashcard.objects.all()
serializer_class = FlashcardSerializer 264 266 serializer_class = FlashcardSerializer
permission_classes = [IsAuthenticated, IsEnrolledInAssociatedSection] 265 267 permission_classes = [IsAuthenticated, IsEnrolledInAssociatedSection]
266 268
# Override create in CreateModelMixin 267 269 # Override create in CreateModelMixin
def create(self, request, *args, **kwargs): 268 270 def create(self, request, *args, **kwargs):
serializer = FlashcardSerializer(data=request.data) 269 271 serializer = FlashcardSerializer(data=request.data)
serializer.is_valid(raise_exception=True) 270 272 serializer.is_valid(raise_exception=True)
data = serializer.validated_data 271 273 data = serializer.validated_data
if not request.user.is_in_section(data['section']): 272 274 if not request.user.is_in_section(data['section']):
raise PermissionDenied('The user is not enrolled in that section') 273 275 raise PermissionDenied('The user is not enrolled in that section')
data['author'] = request.user 274 276 data['author'] = request.user
flashcard = Flashcard.objects.create(**data) 275 277 flashcard = Flashcard.objects.create(**data)
self.perform_create(flashcard) 276 278 self.perform_create(flashcard)
notify_new_card(flashcard) 277 279 notify_new_card(flashcard)
headers = self.get_success_headers(data) 278 280 headers = self.get_success_headers(data)
request.user.pull(flashcard) 279 281 request.user.pull(flashcard)
response_data = FlashcardSerializer(flashcard).data 280 282 response_data = FlashcardSerializer(flashcard).data
281 283
return Response(response_data, status=HTTP_201_CREATED, headers=headers) 282 284 return Response(response_data, status=HTTP_201_CREATED, headers=headers)
283 285
@detail_route(methods=['POST']) 284 286 @detail_route(methods=['POST'])
def unhide(self, request, pk): 285 287 def unhide(self, request, pk):
""" 286 288 """
Unhide the given card 287 289 Unhide the given card
--- 288 290 ---
view_mocker: flashcards.api.mock_no_params 289 291 view_mocker: flashcards.api.mock_no_params
""" 290 292 """
hide = get_object_or_404(FlashcardHide, user=request.user, flashcard=self.get_object()) 291 293 hide = get_object_or_404(FlashcardHide, user=request.user, flashcard=self.get_object())
hide.delete() 292 294 hide.delete()
return Response(status=HTTP_204_NO_CONTENT) 293 295 return Response(status=HTTP_204_NO_CONTENT)
294 296
@detail_route(methods=['POST']) 295 297 @detail_route(methods=['POST'])
def report(self, request, pk): 296 298 def report(self, request, pk):
""" 297 299 """
Hide the given card 298 300 Hide the given card
--- 299 301 ---
view_mocker: flashcards.api.mock_no_params 300 302 view_mocker: flashcards.api.mock_no_params
""" 301 303 """
self.get_object().report(request.user) 302 304 self.get_object().report(request.user)
return Response(status=HTTP_204_NO_CONTENT) 303 305 return Response(status=HTTP_204_NO_CONTENT)
304 306
hide = report 305 307 hide = report
306 308
@detail_route(methods=['POST']) 307 309 @detail_route(methods=['POST'])
def pull(self, request, pk): 308 310 def pull(self, request, pk):
""" 309 311 """
Pull a card from the live feed into the user's deck. 310 312 Pull a card from the live feed into the user's deck.
--- 311 313 ---
view_mocker: flashcards.api.mock_no_params 312 314 view_mocker: flashcards.api.mock_no_params
""" 313 315 """
314 316
request.user.pull(self.get_object()) 315 317 request.user.pull(self.get_object())
return Response(status=HTTP_204_NO_CONTENT) 316 318 return Response(status=HTTP_204_NO_CONTENT)
317 319
318 320
@detail_route(methods=['POST']) 319 321 @detail_route(methods=['POST'])
def unpull(self, request, pk): 320 322 def unpull(self, request, pk):
""" 321 323 """
Unpull a card from the user's deck 322 324 Unpull a card from the user's deck
--- 323 325 ---
view_mocker: flashcards.api.mock_no_params 324 326 view_mocker: flashcards.api.mock_no_params
""" 325 327 """
user = request.user 326 328 user = request.user
flashcard = self.get_object() 327 329 flashcard = self.get_object()
user.unpull(flashcard) 328 330 user.unpull(flashcard)
return Response(status=HTTP_204_NO_CONTENT) 329 331 return Response(status=HTTP_204_NO_CONTENT)
330 332
def partial_update(self, request, *args, **kwargs): 331 333 def partial_update(self, request, *args, **kwargs):
""" 332 334 """
Edit settings related to a card for the user. 333 335 Edit settings related to a card for the user.
--- 334 336 ---
request_serializer: FlashcardUpdateSerializer 335 337 request_serializer: FlashcardUpdateSerializer
""" 336 338 """
user = request.user 337 339 user = request.user
flashcard = self.get_object() 338 340 flashcard = self.get_object()
data = FlashcardUpdateSerializer(data=request.data) 339 341 data = FlashcardUpdateSerializer(data=request.data)
data.is_valid(raise_exception=True) 340 342 data.is_valid(raise_exception=True)
new_flashcard = data.validated_data 341 343 new_flashcard = data.validated_data
new_flashcard = flashcard.edit(user, new_flashcard) 342 344 new_flashcard = flashcard.edit(user, new_flashcard)
return Response(FlashcardSerializer(new_flashcard).data, status=HTTP_200_OK) 343 345 return Response(FlashcardSerializer(new_flashcard).data, status=HTTP_200_OK)
344 346
345 347
class UserFlashcardQuizViewSet(GenericViewSet, CreateModelMixin, UpdateModelMixin): 346 348 class UserFlashcardQuizViewSet(GenericViewSet, CreateModelMixin, UpdateModelMixin):
permission_classes = [IsAuthenticated, IsFlashcardReviewer] 347 349 permission_classes = [IsAuthenticated, IsFlashcardReviewer]
queryset = UserFlashcardQuiz.objects.all() 348 350 queryset = UserFlashcardQuiz.objects.all()
349 351