Commit d15989112cd504ce5af44a4df94599558775e703

Authored by Rohan Rangray
Exists in master

Merge branch 'master' of git.ucsd.edu:110swag/flashy-backend

emeing

Showing 1 changed file Inline Diff

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