Commit a9cb7ae511762a454b7d78550afbd107dc86deb7

Authored by Andrew Buss
1 parent c95e5d254d
Exists in master

log serializer data rather than the serializer itself

Showing 2 changed files with 3 additions and 4 deletions Inline Diff

flashcards/api.py View file @ a9cb7ae
from django.utils.timezone import now 1 1 from django.utils.timezone import now
from flashcards.models import Flashcard, UserFlashcardQuiz 2 2 from flashcards.models import Flashcard, UserFlashcardQuiz
from rest_framework.exceptions import PermissionDenied 3 3 from rest_framework.exceptions import PermissionDenied
from rest_framework.pagination import PageNumberPagination 4 4 from rest_framework.pagination import PageNumberPagination
from rest_framework.permissions import BasePermission 5 5 from rest_framework.permissions import BasePermission
6 6
mock_no_params = lambda x: None 7 7 mock_no_params = lambda x: None
8 8
9 9
class StandardResultsSetPagination(PageNumberPagination): 10 10 class StandardResultsSetPagination(PageNumberPagination):
page_size = 40 11 11 page_size = 40
page_size_query_param = 'page_size' 12 12 page_size_query_param = 'page_size'
max_page_size = 1000 13 13 max_page_size = 1000
14 14
15 15
class UserDetailPermissions(BasePermission): 16 16 class UserDetailPermissions(BasePermission):
""" 17 17 """
Permissions for the user detail view. Anonymous users may only POST. 18 18 Permissions for the user detail view. Anonymous users may only POST.
""" 19 19 """
20 20
def has_object_permission(self, request, view, obj): 21 21 def has_object_permission(self, request, view, obj):
if request.method == 'POST': 22 22 if request.method == 'POST':
return True 23 23 return True
return request.user.is_authenticated() 24 24 return request.user.is_authenticated()
25 25
26 26
class IsEnrolledInAssociatedSection(BasePermission): 27 27 class IsEnrolledInAssociatedSection(BasePermission):
def has_object_permission(self, request, view, obj): 28 28 def has_object_permission(self, request, view, obj):
if obj is None: 29 29 if obj is None:
return True 30 30 return True
assert type(obj) is Flashcard 31 31 assert type(obj) is Flashcard
return request.user.is_in_section(obj.section) 32 32 return request.user.is_in_section(obj.section)
33 33
34 34
class IsFlashcardReviewer(BasePermission): 35 35 class IsFlashcardReviewer(BasePermission):
def has_object_permission(self, request, view, obj): 36 36 def has_object_permission(self, request, view, obj):
if obj is None: 37 37 if obj is None:
return True 38 38 return True
assert type(obj) is UserFlashcardQuiz 39 39 assert type(obj) is UserFlashcardQuiz
return request.user == obj.user_flashcard.user 40 40 return request.user == obj.user_flashcard.user
41 41
42 42
class IsAuthenticatedAndConfirmed(BasePermission): 43 43 class IsAuthenticatedAndConfirmed(BasePermission):
flashcards/views.py View file @ a9cb7ae
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 django.utils.log import getLogger 4 4 from django.utils.log import getLogger
from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection, IsFlashcardReviewer, \ 5 5 from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection, IsFlashcardReviewer, \
IsAuthenticatedAndConfirmed 6 6 IsAuthenticatedAndConfirmed
from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard, UserFlashcardQuiz, \ 7 7 from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard, UserFlashcardQuiz, \
FlashcardAlreadyPulledException, FlashcardNotInDeckException, Now, interval_days 8 8 FlashcardAlreadyPulledException, FlashcardNotInDeckException, Now, interval_days
from flashcards.notifications import notify_new_card 9 9 from flashcards.notifications import notify_new_card
from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \ 10 10 from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \
PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \ 11 11 PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \
FlashcardUpdateSerializer, QuizRequestSerializer, QuizResponseSerializer, \ 12 12 FlashcardUpdateSerializer, QuizRequestSerializer, QuizResponseSerializer, \
QuizAnswerRequestSerializer, DeepSectionSerializer 13 13 QuizAnswerRequestSerializer, DeepSectionSerializer
from rest_framework.decorators import detail_route, permission_classes, api_view, list_route 14 14 from rest_framework.decorators import detail_route, permission_classes, api_view, list_route
from rest_framework.generics import ListAPIView, GenericAPIView 15 15 from rest_framework.generics import ListAPIView, GenericAPIView
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin 16 16 from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.permissions import IsAuthenticated 17 17 from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet 18 18 from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet
from django.core.mail import send_mail 19 19 from django.core.mail import send_mail
from django.contrib.auth import authenticate 20 20 from django.contrib.auth import authenticate
from django.contrib.auth.tokens import default_token_generator 21 21 from django.contrib.auth.tokens import default_token_generator
from django.db.models import Count, Max, F, Value, When, Case, DateTimeField, FloatField 22 22 from django.db.models import Count, Max, F, Value, When, Case, DateTimeField, FloatField
from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK 23 23 from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK
from rest_framework.response import Response 24 24 from rest_framework.response import Response
from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied 25 25 from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied
from simple_email_confirmation import EmailAddress 26 26 from simple_email_confirmation import EmailAddress
from math import e 27 27 from math import e
from django.utils.timezone import now 28 28 from django.utils.timezone import now
29 29
30 30
def log_event(request, event=''): 31 31 def log_event(request, event=''):
getLogger('flashy.events').info( 32 32 getLogger('flashy.events').info(
'%s %s %s %s' % (request.META['REMOTE_ADDR'], str(request.user), request.path, event)) 33 33 '%s %s %s %s' % (request.META['REMOTE_ADDR'], str(request.user), request.path, event))
34 34
35 35
class SectionViewSet(ReadOnlyModelViewSet): 36 36 class SectionViewSet(ReadOnlyModelViewSet):
queryset = Section.objects.all() 37 37 queryset = Section.objects.all()
serializer_class = DeepSectionSerializer 38 38 serializer_class = DeepSectionSerializer
pagination_class = StandardResultsSetPagination 39 39 pagination_class = StandardResultsSetPagination
permission_classes = [IsAuthenticatedAndConfirmed] 40 40 permission_classes = [IsAuthenticatedAndConfirmed]
41 41
@detail_route(methods=['GET']) 42 42 @detail_route(methods=['GET'])
def flashcards(self, request, pk): 43 43 def flashcards(self, request, pk):
""" 44 44 """
Gets flashcards for a section, excluding hidden cards. 45 45 Gets flashcards for a section, excluding hidden cards.
Returned in strictly chronological order (material date). 46 46 Returned in strictly chronological order (material date).
""" 47 47 """
flashcards = Flashcard.cards_visible_to(request.user) 48 48 flashcards = Flashcard.cards_visible_to(request.user)
if 'hidden' in request.GET: 49 49 if 'hidden' in request.GET:
if request.GET['hidden'] == 'only': 50 50 if request.GET['hidden'] == 'only':
flashcards = Flashcard.cards_hidden_by(request.user) 51 51 flashcards = Flashcard.cards_hidden_by(request.user)
else: 52 52 else:
flashcards |= Flashcard.cards_hidden_by(request.user) 53 53 flashcards |= Flashcard.cards_hidden_by(request.user)
flashcards = flashcards.filter(section=self.get_object()).order_by('material_date').all() 54 54 flashcards = flashcards.filter(section=self.get_object()).order_by('material_date').all()
log_event(request, str(self.get_object())) 55 55 log_event(request, str(self.get_object()))
return Response(FlashcardSerializer(flashcards, context={"user": request.user}, many=True).data) 56 56 return Response(FlashcardSerializer(flashcards, context={"user": request.user}, many=True).data)
57 57
@detail_route(methods=['POST']) 58 58 @detail_route(methods=['POST'])
def enroll(self, request, pk): 59 59 def enroll(self, request, pk):
60 60
""" 61 61 """
Add the current user to a specified section 62 62 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. 63 63 If the class has a whitelist, but the user is not on the whitelist, the request will fail.
--- 64 64 ---
view_mocker: flashcards.api.mock_no_params 65 65 view_mocker: flashcards.api.mock_no_params
""" 66 66 """
try: 67 67 try:
self.get_object().enroll(request.user) 68 68 self.get_object().enroll(request.user)
log_event(request, str(self.get_object())) 69 69 log_event(request, str(self.get_object()))
except django.core.exceptions.PermissionDenied as e: 70 70 except django.core.exceptions.PermissionDenied as e:
raise PermissionDenied(e) 71 71 raise PermissionDenied(e)
except django.core.exceptions.ValidationError as e: 72 72 except django.core.exceptions.ValidationError as e:
raise ValidationError(e) 73 73 raise ValidationError(e)
return Response(status=HTTP_204_NO_CONTENT) 74 74 return Response(status=HTTP_204_NO_CONTENT)
75 75
@detail_route(methods=['POST']) 76 76 @detail_route(methods=['POST'])
def drop(self, request, pk): 77 77 def drop(self, request, pk):
""" 78 78 """
Remove the current user from a specified section 79 79 Remove the current user from a specified section
If the user is not in the class, the request will fail. 80 80 If the user is not in the class, the request will fail.
--- 81 81 ---
view_mocker: flashcards.api.mock_no_params 82 82 view_mocker: flashcards.api.mock_no_params
""" 83 83 """
try: 84 84 try:
self.get_object().drop(request.user) 85 85 self.get_object().drop(request.user)
log_event(request, str(self.get_object())) 86 86 log_event(request, str(self.get_object()))
except django.core.exceptions.PermissionDenied as e: 87 87 except django.core.exceptions.PermissionDenied as e:
raise PermissionDenied(e) 88 88 raise PermissionDenied(e)
except django.core.exceptions.ValidationError as e: 89 89 except django.core.exceptions.ValidationError as e:
raise ValidationError(e) 90 90 raise ValidationError(e)
return Response(status=HTTP_204_NO_CONTENT) 91 91 return Response(status=HTTP_204_NO_CONTENT)
92 92
@list_route(methods=['GET']) 93 93 @list_route(methods=['GET'])
def search(self, request): 94 94 def search(self, request):
""" 95 95 """
Returns a list of sections which match a user's query 96 96 Returns a list of sections which match a user's query
--- 97 97 ---
parameters: 98 98 parameters:
- name: q 99 99 - name: q
description: space-separated list of terms 100 100 description: space-separated list of terms
required: true 101 101 required: true
type: form 102 102 type: form
response_serializer: SectionSerializer 103 103 response_serializer: SectionSerializer
""" 104 104 """
query = request.GET.get('q', None) 105 105 query = request.GET.get('q', None)
if not query: return Response('[]') 106 106 if not query: return Response('[]')
qs = Section.search(query.split(' '))[:20] 107 107 qs = Section.search(query.split(' '))[:20]
data = SectionSerializer(qs, many=True).data 108 108 data = SectionSerializer(qs, many=True).data
log_event(request, query) 109 109 log_event(request, query)
return Response(data) 110 110 return Response(data)
111 111
@detail_route(methods=['GET']) 112 112 @detail_route(methods=['GET'])
def deck(self, request, pk): 113 113 def deck(self, request, pk):
""" 114 114 """
Gets the contents of a user's deck for a given section. 115 115 Gets the contents of a user's deck for a given section.
""" 116 116 """
qs = request.user.get_deck(self.get_object()) 117 117 qs = request.user.get_deck(self.get_object())
serializer = FlashcardSerializer(qs, many=True) 118 118 serializer = FlashcardSerializer(qs, many=True)
log_event(request, str(self.get_object())) 119 119 log_event(request, str(self.get_object()))
return Response(serializer.data) 120 120 return Response(serializer.data)
121 121
@detail_route(methods=['GET']) 122 122 @detail_route(methods=['GET'])
def feed(self, request, pk): 123 123 def feed(self, request, pk):
""" 124 124 """
Gets the contents of a user's feed for a section. 125 125 Gets the contents of a user's feed for a section.
Exclude cards that are already in the user's deck 126 126 Exclude cards that are already in the user's deck
""" 127 127 """
serializer = FlashcardSerializer(self.get_object().get_feed_for_user(request.user), many=True, 128 128 serializer = FlashcardSerializer(self.get_object().get_feed_for_user(request.user), many=True,
context={'user': request.user}) 129 129 context={'user': request.user})
log_event(request, str(self.get_object())) 130 130 log_event(request, str(self.get_object()))
return Response(serializer.data) 131 131 return Response(serializer.data)
132 132
133 133
class UserSectionListView(ListAPIView): 134 134 class UserSectionListView(ListAPIView):
serializer_class = DeepSectionSerializer 135 135 serializer_class = DeepSectionSerializer
permission_classes = [IsAuthenticatedAndConfirmed] 136 136 permission_classes = [IsAuthenticatedAndConfirmed]
137 137
def get_queryset(self): 138 138 def get_queryset(self):
return self.request.user.sections.all() 139 139 return self.request.user.sections.all()
140 140
def paginate_queryset(self, queryset): return None 141 141 def paginate_queryset(self, queryset): return None
142 142
143 143
class UserDetail(GenericAPIView): 144 144 class UserDetail(GenericAPIView):
serializer_class = UserSerializer 145 145 serializer_class = UserSerializer
permission_classes = [IsAuthenticatedAndConfirmed] 146 146 permission_classes = [IsAuthenticatedAndConfirmed]
147 147
def patch(self, request, format=None): 148 148 def patch(self, request, format=None):
""" 149 149 """
Updates the user's password, or verifies their email address 150 150 Updates the user's password, or verifies their email address
--- 151 151 ---
request_serializer: UserUpdateSerializer 152 152 request_serializer: UserUpdateSerializer
response_serializer: UserSerializer 153 153 response_serializer: UserSerializer
""" 154 154 """
data = UserUpdateSerializer(data=request.data, context={'user': request.user}) 155 155 data = UserUpdateSerializer(data=request.data, context={'user': request.user})
data.is_valid(raise_exception=True) 156 156 data.is_valid(raise_exception=True)
data = data.validated_data 157 157 data = data.validated_data
158 158
if 'new_password' in data: 159 159 if 'new_password' in data:
if not request.user.check_password(data['old_password']): 160 160 if not request.user.check_password(data['old_password']):
raise ValidationError('old_password is incorrect') 161 161 raise ValidationError('old_password is incorrect')
request.user.set_password(data['new_password']) 162 162 request.user.set_password(data['new_password'])
request.user.save() 163 163 request.user.save()
log_event(request, 'change password') 164 164 log_event(request, 'change password')
165 165
if 'confirmation_key' in data: 166 166 if 'confirmation_key' in data:
try: 167 167 try:
request.user.confirm_email(data['confirmation_key']) 168 168 request.user.confirm_email(data['confirmation_key'])
log_event(request, 'confirm email') 169 169 log_event(request, 'confirm email')
except EmailAddress.DoesNotExist: 170 170 except EmailAddress.DoesNotExist:
raise ValidationError('confirmation_key is invalid') 171 171 raise ValidationError('confirmation_key is invalid')
172 172
return Response(UserSerializer(request.user).data) 173 173 return Response(UserSerializer(request.user).data)
174 174
def get(self, request, format=None): 175 175 def get(self, request, format=None):
""" 176 176 """
Return data about the user 177 177 Return data about the user
--- 178 178 ---
response_serializer: UserSerializer 179 179 response_serializer: UserSerializer
""" 180 180 """
serializer = UserSerializer(request.user, context={'request': request}) 181 181 serializer = UserSerializer(request.user, context={'request': request})
return Response(serializer.data) 182 182 return Response(serializer.data)
183 183
def delete(self, request): 184 184 def delete(self, request):
""" 185 185 """
Irrevocably delete the user and their data 186 186 Irrevocably delete the user and their data
187 187
Yes, really 188 188 Yes, really
""" 189 189 """
request.user.delete() 190 190 request.user.delete()
log_event(request) 191 191 log_event(request)
return Response(status=HTTP_204_NO_CONTENT) 192 192 return Response(status=HTTP_204_NO_CONTENT)
193 193
194 194
@api_view(['POST']) 195 195 @api_view(['POST'])
def register(request, format=None): 196 196 def register(request, format=None):
""" 197 197 """
Register a new user 198 198 Register a new user
--- 199 199 ---
request_serializer: EmailPasswordSerializer 200 200 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 201 201 response_serializer: UserSerializer
""" 202 202 """
data = RegistrationSerializer(data=request.data) 203 203 data = RegistrationSerializer(data=request.data)
data.is_valid(raise_exception=True) 204 204 data.is_valid(raise_exception=True)
205 205
User.objects.create_user(**data.validated_data) 206 206 User.objects.create_user(**data.validated_data)
user = authenticate(**data.validated_data) 207 207 user = authenticate(**data.validated_data)
auth.login(request, user) 208 208 auth.login(request, user)
log_event(request) 209 209 log_event(request)
return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED) 210 210 return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED)
211 211
212 212
@api_view(['POST']) 213 213 @api_view(['POST'])
def login(request): 214 214 def login(request):
""" 215 215 """
Authenticates user and returns user data if valid. 216 216 Authenticates user and returns user data if valid.
--- 217 217 ---
request_serializer: EmailPasswordSerializer 218 218 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 219 219 response_serializer: UserSerializer
""" 220 220 """
221 221
data = EmailPasswordSerializer(data=request.data) 222 222 data = EmailPasswordSerializer(data=request.data)
data.is_valid(raise_exception=True) 223 223 data.is_valid(raise_exception=True)
user = authenticate(**data.validated_data) 224 224 user = authenticate(**data.validated_data)
225 225
if user is None: 226 226 if user is None:
raise AuthenticationFailed('Invalid email or password') 227 227 raise AuthenticationFailed('Invalid email or password')
if not user.is_active: 228 228 if not user.is_active:
raise NotAuthenticated('Account is disabled') 229 229 raise NotAuthenticated('Account is disabled')
auth.login(request, user) 230 230 auth.login(request, user)
log_event(request) 231 231 log_event(request)
return Response(UserSerializer(request.user).data) 232 232 return Response(UserSerializer(request.user).data)
233 233
234 234
@api_view(['POST']) 235 235 @api_view(['POST'])
@permission_classes((IsAuthenticated,)) 236 236 @permission_classes((IsAuthenticated,))
def logout(request, format=None): 237 237 def logout(request, format=None):
""" 238 238 """
Logs the authenticated user out. 239 239 Logs the authenticated user out.
""" 240 240 """
auth.logout(request) 241 241 auth.logout(request)
log_event(request) 242 242 log_event(request)
return Response(status=HTTP_204_NO_CONTENT) 243 243 return Response(status=HTTP_204_NO_CONTENT)
244 244
245 245
@api_view(['POST']) 246 246 @api_view(['POST'])
def request_password_reset(request, format=None): 247 247 def request_password_reset(request, format=None):
""" 248 248 """
Send a password reset token/link to the provided email. 249 249 Send a password reset token/link to the provided email.
--- 250 250 ---
request_serializer: PasswordResetRequestSerializer 251 251 request_serializer: PasswordResetRequestSerializer
""" 252 252 """
data = PasswordResetRequestSerializer(data=request.data) 253 253 data = PasswordResetRequestSerializer(data=request.data)
data.is_valid(raise_exception=True) 254 254 data.is_valid(raise_exception=True)
log_event(request, 'email: ' + str(data['email'])) 255 255 log_event(request, 'email: ' + str(data['email']))
get_object_or_404(User, email=data['email'].value).request_password_reset() 256 256 get_object_or_404(User, email=data['email'].value).request_password_reset()
return Response(status=HTTP_204_NO_CONTENT) 257 257 return Response(status=HTTP_204_NO_CONTENT)
258 258
259 259
@api_view(['POST']) 260 260 @api_view(['POST'])
def reset_password(request, format=None): 261 261 def reset_password(request, format=None):
""" 262 262 """
Updates user's password to new password if token is valid. 263 263 Updates user's password to new password if token is valid.
--- 264 264 ---
request_serializer: PasswordResetSerializer 265 265 request_serializer: PasswordResetSerializer
""" 266 266 """
data = PasswordResetSerializer(data=request.data) 267 267 data = PasswordResetSerializer(data=request.data)
data.is_valid(raise_exception=True) 268 268 data.is_valid(raise_exception=True)
269 269
user = User.objects.get(id=data['uid'].value) 270 270 user = User.objects.get(id=data['uid'].value)
# Check token validity. 271 271 # Check token validity.
272 272
if default_token_generator.check_token(user, data['token'].value): 273 273 if default_token_generator.check_token(user, data['token'].value):
user.set_password(data['new_password'].value) 274 274 user.set_password(data['new_password'].value)
user.save() 275 275 user.save()
log_event(request) 276 276 log_event(request)
else: 277 277 else:
raise ValidationError('Could not verify reset token') 278 278 raise ValidationError('Could not verify reset token')
return Response(status=HTTP_204_NO_CONTENT) 279 279 return Response(status=HTTP_204_NO_CONTENT)
280 280
281 281
class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin): 282 282 class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin):
queryset = Flashcard.objects.all() 283 283 queryset = Flashcard.objects.all()
serializer_class = FlashcardSerializer 284 284 serializer_class = FlashcardSerializer
permission_classes = [IsAuthenticatedAndConfirmed, IsEnrolledInAssociatedSection] 285 285 permission_classes = [IsAuthenticatedAndConfirmed, IsEnrolledInAssociatedSection]
286 286
# Override create in CreateModelMixin 287 287 # Override create in CreateModelMixin
def create(self, request, *args, **kwargs): 288 288 def create(self, request, *args, **kwargs):
serializer = FlashcardSerializer(data=request.data) 289 289 serializer = FlashcardSerializer(data=request.data)
serializer.is_valid(raise_exception=True) 290 290 serializer.is_valid(raise_exception=True)
data = serializer.validated_data 291 291 data = serializer.validated_data
if not request.user.is_in_section(data['section']): 292 292 if not request.user.is_in_section(data['section']):
raise PermissionDenied('The user is not enrolled in that section') 293 293 raise PermissionDenied('The user is not enrolled in that section')
data['author'] = request.user 294 294 data['author'] = request.user
flashcard = Flashcard.objects.create(**data) 295 295 flashcard = Flashcard.objects.create(**data)
self.perform_create(flashcard) 296 296 self.perform_create(flashcard)
notify_new_card(flashcard) 297 297 notify_new_card(flashcard)
headers = self.get_success_headers(data) 298 298 headers = self.get_success_headers(data)
request.user.pull(flashcard) 299 299 request.user.pull(flashcard)
response_data = FlashcardSerializer(flashcard).data 300 300 response_data = FlashcardSerializer(flashcard).data
log_event(request, response_data) 301 301 log_event(request, response_data)
return Response(response_data, status=HTTP_201_CREATED, headers=headers) 302 302 return Response(response_data, status=HTTP_201_CREATED, headers=headers)
303 303
@detail_route(methods=['POST']) 304 304 @detail_route(methods=['POST'])
def unhide(self, request, pk): 305 305 def unhide(self, request, pk):
""" 306 306 """
Unhide the given card 307 307 Unhide the given card
--- 308 308 ---
view_mocker: flashcards.api.mock_no_params 309 309 view_mocker: flashcards.api.mock_no_params
""" 310 310 """
hide = get_object_or_404(FlashcardHide, user=request.user, flashcard=self.get_object()) 311 311 hide = get_object_or_404(FlashcardHide, user=request.user, flashcard=self.get_object())
hide.delete() 312 312 hide.delete()
log_event(request, str(self.get_object())) 313 313 log_event(request, str(self.get_object()))
return Response(status=HTTP_204_NO_CONTENT) 314 314 return Response(status=HTTP_204_NO_CONTENT)
315 315
@detail_route(methods=['POST']) 316 316 @detail_route(methods=['POST'])
def report(self, request, pk): 317 317 def report(self, request, pk):
""" 318 318 """
Hide the given card 319 319 Hide the given card
--- 320 320 ---
view_mocker: flashcards.api.mock_no_params 321 321 view_mocker: flashcards.api.mock_no_params
""" 322 322 """
self.get_object().report(request.user) 323 323 self.get_object().report(request.user)
log_event(request, str(self.get_object())) 324 324 log_event(request, str(self.get_object()))
return Response(status=HTTP_204_NO_CONTENT) 325 325 return Response(status=HTTP_204_NO_CONTENT)
326 326
hide = report 327 327 hide = report
328 328
@detail_route(methods=['POST']) 329 329 @detail_route(methods=['POST'])
def pull(self, request, pk): 330 330 def pull(self, request, pk):
""" 331 331 """
Pull a card from the live feed into the user's deck. 332 332 Pull a card from the live feed into the user's deck.
--- 333 333 ---
view_mocker: flashcards.api.mock_no_params 334 334 view_mocker: flashcards.api.mock_no_params
""" 335 335 """
try: 336 336 try:
request.user.pull(self.get_object()) 337 337 request.user.pull(self.get_object())
log_event(request, str(self.get_object())) 338 338 log_event(request, str(self.get_object()))
return Response(status=HTTP_204_NO_CONTENT) 339 339 return Response(status=HTTP_204_NO_CONTENT)
except FlashcardAlreadyPulledException: 340 340 except FlashcardAlreadyPulledException:
raise ValidationError('Cannot pull a card already in deck') 341 341 raise ValidationError('Cannot pull a card already in deck')
342 342
@detail_route(methods=['POST']) 343 343 @detail_route(methods=['POST'])
def unpull(self, request, pk): 344 344 def unpull(self, request, pk):
""" 345 345 """
Unpull a card from the user's deck 346 346 Unpull a card from the user's deck
--- 347 347 ---
view_mocker: flashcards.api.mock_no_params 348 348 view_mocker: flashcards.api.mock_no_params
""" 349 349 """
user = request.user 350 350 user = request.user
flashcard = self.get_object() 351 351 flashcard = self.get_object()
try: 352 352 try:
user.unpull(flashcard) 353 353 user.unpull(flashcard)
log_event(request, str(self.get_object())) 354 354 log_event(request, str(self.get_object()))
return Response(status=HTTP_204_NO_CONTENT) 355 355 return Response(status=HTTP_204_NO_CONTENT)
except FlashcardNotInDeckException: 356 356 except FlashcardNotInDeckException:
raise ValidationError('Cannot unpull a card not in deck') 357 357 raise ValidationError('Cannot unpull a card not in deck')
358 358
359 359
def partial_update(self, request, *args, **kwargs): 360 360 def partial_update(self, request, *args, **kwargs):
""" 361 361 """
Edit settings related to a card for the user. 362 362 Edit settings related to a card for the user.
--- 363 363 ---
request_serializer: FlashcardUpdateSerializer 364 364 request_serializer: FlashcardUpdateSerializer
""" 365 365 """
user = request.user 366 366 user = request.user
flashcard = self.get_object() 367 367 flashcard = self.get_object()
data = FlashcardUpdateSerializer(data=request.data) 368 368 data = FlashcardUpdateSerializer(data=request.data)
data.is_valid(raise_exception=True) 369 369 data.is_valid(raise_exception=True)
new_flashcard = data.validated_data 370 370 new_flashcard = data.validated_data
new_flashcard = flashcard.edit(user, new_flashcard) 371 371 new_flashcard = flashcard.edit(user, new_flashcard)
log_event(request, str(new_flashcard)) 372 372 log_event(request, str(new_flashcard))
return Response(FlashcardSerializer(new_flashcard, context={'user': request.user}).data, status=HTTP_200_OK) 373 373 return Response(FlashcardSerializer(new_flashcard, context={'user': request.user}).data, status=HTTP_200_OK)
374 374
375 375
class UserFlashcardQuizViewSet(GenericViewSet, CreateModelMixin, UpdateModelMixin): 376 376 class UserFlashcardQuizViewSet(GenericViewSet, CreateModelMixin, UpdateModelMixin):
permission_classes = [IsAuthenticatedAndConfirmed, IsFlashcardReviewer] 377 377 permission_classes = [IsAuthenticatedAndConfirmed, IsFlashcardReviewer]
queryset = UserFlashcardQuiz.objects.all() 378 378 queryset = UserFlashcardQuiz.objects.all()
379 379
def get_serializer_class(self): 380 380 def get_serializer_class(self):
if self.request.method == 'POST': 381 381 if self.request.method == 'POST':
return QuizRequestSerializer 382 382 return QuizRequestSerializer
return QuizAnswerRequestSerializer 383 383 return QuizAnswerRequestSerializer
384 384
def create(self, request, *args, **kwargs): 385 385 def create(self, request, *args, **kwargs):
""" 386 386 """
Return a card based on the request params. 387 387 Return a card based on the request params.
:param request: A request object. 388 388 :param request: A request object.
:param format: Format of the request. 389 389 :param format: Format of the request.
:return: A response containing 390 390 :return: A response containing