Commit 19f62c6f7ed98aa2cf9791cbcc88c87c74c2d0bc

Authored by Andrew Buss
1 parent ad724d7916
Exists in master

add is_in_deck field to flashcardserializer

Showing 2 changed files with 8 additions and 1 deletions Inline Diff

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