Commit 5958ba67dddb264a113e603d91514f5c38952115

Authored by Andrew Buss
1 parent f441be6101
Exists in master

case insensitive login register

Showing 1 changed file with 2 additions and 1 deletions Inline Diff

flashcards/views.py View file @ 5958ba6
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 """
qs = request.user.get_deck(self.get_object()) 121 121 qs = request.user.get_deck(self.get_object())
serializer = FlashcardSerializer(qs, context={'user': request.user}, many=True) 122 122 serializer = FlashcardSerializer(qs, context={'user': request.user}, many=True)
log_event(request, str(self.get_object())) 123 123 log_event(request, str(self.get_object()))
return Response(serializer.data) 124 124 return Response(serializer.data)
125 125
@detail_route(methods=['GET']) 126 126 @detail_route(methods=['GET'])
def feed(self, request, pk): 127 127 def feed(self, request, pk):
""" 128 128 """
Gets the contents of a user's feed for a section. 129 129 Gets the contents of a user's feed for a section.
Exclude cards that are already in the user's deck 130 130 Exclude cards that are already in the user's deck
request_serializer: FeedRequestSerializer 131 131 request_serializer: FeedRequestSerializer
response_serializer: FlashcardSerializer 132 132 response_serializer: FlashcardSerializer
""" 133 133 """
feed_serializer = FeedRequestSerializer(data=request.data) 134 134 feed_serializer = FeedRequestSerializer(data=request.data)
feed_serializer.is_valid(raise_exception=True) 135 135 feed_serializer.is_valid(raise_exception=True)
page = feed_serializer.validated_data['page'] 136 136 page = feed_serializer.validated_data['page']
feed = self.get_object().get_feed_for_user(request.user) 137 137 feed = self.get_object().get_feed_for_user(request.user)
if page: 138 138 if page:
feed = feed[(page - 1) * FEED_PAGE_SIZE:page * FEED_PAGE_SIZE] 139 139 feed = feed[(page - 1) * FEED_PAGE_SIZE:page * FEED_PAGE_SIZE]
serializer = FlashcardSerializer(feed, many=True, context={'user': request.user}) 140 140 serializer = FlashcardSerializer(feed, many=True, context={'user': request.user})
log_event(request, str(self.get_object())) 141 141 log_event(request, str(self.get_object()))
return Response(serializer.data) 142 142 return Response(serializer.data)
143 143
144 144
class UserSectionListView(ListAPIView): 145 145 class UserSectionListView(ListAPIView):
serializer_class = DeepSectionSerializer 146 146 serializer_class = DeepSectionSerializer
permission_classes = [IsAuthenticatedAndConfirmed] 147 147 permission_classes = [IsAuthenticatedAndConfirmed]
148 148
def get_queryset(self): 149 149 def get_queryset(self):
return self.request.user.sections.all() 150 150 return self.request.user.sections.all()
151 151
def paginate_queryset(self, queryset): return None 152 152 def paginate_queryset(self, queryset): return None
153 153
154 154
class UserDetail(GenericAPIView): 155 155 class UserDetail(GenericAPIView):
serializer_class = UserSerializer 156 156 serializer_class = UserSerializer
permission_classes = [IsAuthenticated] 157 157 permission_classes = [IsAuthenticated]
158 158
def patch(self, request, format=None): 159 159 def patch(self, request, format=None):
""" 160 160 """
Updates the user's password 161 161 Updates the user's password
--- 162 162 ---
request_serializer: UserUpdateSerializer 163 163 request_serializer: UserUpdateSerializer
response_serializer: UserSerializer 164 164 response_serializer: UserSerializer
""" 165 165 """
data = UserUpdateSerializer(data=request.data, context={'user': request.user}) 166 166 data = UserUpdateSerializer(data=request.data, context={'user': request.user})
data.is_valid(raise_exception=True) 167 167 data.is_valid(raise_exception=True)
data = data.validated_data 168 168 data = data.validated_data
169 169
if 'new_password' in data: 170 170 if 'new_password' in data:
if not request.user.check_password(data['old_password']): 171 171 if not request.user.check_password(data['old_password']):
raise ValidationError('old_password is incorrect') 172 172 raise ValidationError('old_password is incorrect')
request.user.set_password(data['new_password']) 173 173 request.user.set_password(data['new_password'])
request.user.save() 174 174 request.user.save()
log_event(request, 'change password') 175 175 log_event(request, 'change password')
176 176
return Response(UserSerializer(request.user).data) 177 177 return Response(UserSerializer(request.user).data)
178 178
def get(self, request, format=None): 179 179 def get(self, request, format=None):
""" 180 180 """
Return data about the user 181 181 Return data about the user
--- 182 182 ---
response_serializer: UserSerializer 183 183 response_serializer: UserSerializer
""" 184 184 """
serializer = UserSerializer(request.user, context={'request': request}) 185 185 serializer = UserSerializer(request.user, context={'request': request})
return Response(serializer.data) 186 186 return Response(serializer.data)
187 187
def delete(self, request): 188 188 def delete(self, request):
""" 189 189 """
Irrevocably delete the user and their data 190 190 Irrevocably delete the user and their data
191 191
Yes, really 192 192 Yes, really
""" 193 193 """
request.user.delete() 194 194 request.user.delete()
log_event(request) 195 195 log_event(request)
return Response(status=HTTP_204_NO_CONTENT) 196 196 return Response(status=HTTP_204_NO_CONTENT)
197 197
198 198
@api_view(['POST']) 199 199 @api_view(['POST'])
@permission_classes([IsAuthenticated]) 200 200 @permission_classes([IsAuthenticated])
def resend_confirmation_email(request): 201 201 def resend_confirmation_email(request):
"Resends a confirmation email to a user" 202 202 "Resends a confirmation email to a user"
request.user.send_confirmation_email() 203 203 request.user.send_confirmation_email()
return Response(status=HTTP_204_NO_CONTENT) 204 204 return Response(status=HTTP_204_NO_CONTENT)
205 205
206 206
@api_view(['POST']) 207 207 @api_view(['POST'])
def verify_email(request): 208 208 def verify_email(request):
""" 209 209 """
Accepts a user's email confirmation_key to verify their email address 210 210 Accepts a user's email confirmation_key to verify their email address
--- 211 211 ---
request_serializer: EmailVerificationSerializer 212 212 request_serializer: EmailVerificationSerializer
""" 213 213 """
214 214
data = EmailVerificationSerializer(data=request.data) 215 215 data = EmailVerificationSerializer(data=request.data)
data.is_valid(raise_exception=True) 216 216 data.is_valid(raise_exception=True)
try: 217 217 try:
email = User.confirm_email(data.validated_data['confirmation_key']) 218 218 email = User.confirm_email(data.validated_data['confirmation_key'])
except EmailAddress.DoesNotExist: 219 219 except EmailAddress.DoesNotExist:
raise ValidationError('confirmation_key is invalid') 220 220 raise ValidationError('confirmation_key is invalid')
log_event(request, 'confirm email' + str(email)) 221 221 log_event(request, 'confirm email' + str(email))
return Response(status=HTTP_204_NO_CONTENT) 222 222 return Response(status=HTTP_204_NO_CONTENT)
223 223
224 224
@api_view(['POST']) 225 225 @api_view(['POST'])
def register(request, format=None): 226 226 def register(request, format=None):
""" 227 227 """
Register a new user 228 228 Register a new user
--- 229 229 ---
request_serializer: RegistrationSerializer 230 230 request_serializer: RegistrationSerializer
response_serializer: UserSerializer 231 231 response_serializer: UserSerializer
""" 232 232 """
data = RegistrationSerializer(data=request.data) 233 233 data = RegistrationSerializer(data=request.data)
data.is_valid(raise_exception=True) 234 234 data.is_valid(raise_exception=True)
235 235 data.validated_data['password'] = data.validated_data['password'].lower()
User.objects.create_user(**data.validated_data) 236 236 User.objects.create_user(**data.validated_data)
user = authenticate(**data.validated_data) 237 237 user = authenticate(**data.validated_data)
auth.login(request, user) 238 238 auth.login(request, user)
log_event(request) 239 239 log_event(request)
return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED) 240 240 return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED)
241 241
242 242
@api_view(['POST']) 243 243 @api_view(['POST'])
def subscribe(request, format=None): 244 244 def subscribe(request, format=None):
""" 245 245 """
Associate the user with the passed in registration token 246 246 Associate the user with the passed in registration token
--- 247 247 ---
request_serializer: SubscribeViewSerializer 248 248 request_serializer: SubscribeViewSerializer
""" 249 249 """
serializer = SubscribeViewSerializer(data=request.data) 250 250 serializer = SubscribeViewSerializer(data=request.data)
serializer.is_valid(raise_exception=True) 251 251 serializer.is_valid(raise_exception=True)
request.user.set_registration_id(serializer.validated_data['registration_id']) 252 252 request.user.set_registration_id(serializer.validated_data['registration_id'])
return Response(status=HTTP_204_NO_CONTENT) 253 253 return Response(status=HTTP_204_NO_CONTENT)
254 254
255 255
@api_view(['POST']) 256 256 @api_view(['POST'])
def unsubscribe(request, format=None): 257 257 def unsubscribe(request, format=None):
""" 258 258 """
Remove the user with the passed in registration token 259 259 Remove the user with the passed in registration token
""" 260 260 """
request.user.set_registration_id(None) 261 261 request.user.set_registration_id(None)
return Response(status=HTTP_204_NO_CONTENT) 262 262 return Response(status=HTTP_204_NO_CONTENT)
263 263
264 264
@api_view(['POST']) 265 265 @api_view(['POST'])
def login(request): 266 266 def login(request):
""" 267 267 """
Authenticates user and returns user data if valid. 268 268 Authenticates user and returns user data if valid.
--- 269 269 ---
request_serializer: RegistrationSerializer 270 270 request_serializer: RegistrationSerializer
response_serializer: UserSerializer 271 271 response_serializer: UserSerializer
""" 272 272 """
273 273
data = LoginSerializer(data=request.data) 274 274 data = LoginSerializer(data=request.data)
data.is_valid(raise_exception=True) 275 275 data.is_valid(raise_exception=True)
276 data.validated_data['password'] = data.validated_data['password'].lower()
user = authenticate(**data.validated_data) 276 277 user = authenticate(**data.validated_data)
277 278
if user is None: 278 279 if user is None:
raise AuthenticationFailed('Invalid email or password') 279 280 raise AuthenticationFailed('Invalid email or password')
if not user.is_active: 280 281 if not user.is_active:
raise NotAuthenticated('Account is disabled') 281 282 raise NotAuthenticated('Account is disabled')
auth.login(request, user) 282 283 auth.login(request, user)
log_event(request) 283 284 log_event(request)
return Response(UserSerializer(request.user).data) 284 285 return Response(UserSerializer(request.user).data)
285 286
286 287
@api_view(['POST']) 287 288 @api_view(['POST'])
@permission_classes((IsAuthenticated,)) 288 289 @permission_classes((IsAuthenticated,))
def logout(request, format=None): 289 290 def logout(request, format=None):
""" 290 291 """
Logs the authenticated user out. 291 292 Logs the authenticated user out.
""" 292 293 """
auth.logout(request) 293 294 auth.logout(request)
log_event(request) 294 295 log_event(request)
return Response(status=HTTP_204_NO_CONTENT) 295 296 return Response(status=HTTP_204_NO_CONTENT)
296 297
297 298
@api_view(['POST']) 298 299 @api_view(['POST'])
def request_password_reset(request, format=None): 299 300 def request_password_reset(request, format=None):
""" 300 301 """
Send a password reset token/link to the provided email. 301 302 Send a password reset token/link to the provided email.
--- 302 303 ---
request_serializer: PasswordResetRequestSerializer 303 304 request_serializer: PasswordResetRequestSerializer
""" 304 305 """
data = PasswordResetRequestSerializer(data=request.data) 305 306 data = PasswordResetRequestSerializer(data=request.data)
data.is_valid(raise_exception=True) 306 307 data.is_valid(raise_exception=True)
log_event(request, 'email: ' + str(data['email'])) 307 308 log_event(request, 'email: ' + str(data['email']))
get_object_or_404(User, email=data['email'].value).request_password_reset() 308 309 get_object_or_404(User, email=data['email'].value).request_password_reset()
return Response(status=HTTP_204_NO_CONTENT) 309 310 return Response(status=HTTP_204_NO_CONTENT)
310 311
311 312
@api_view(['POST']) 312 313 @api_view(['POST'])
def reset_password(request, format=None): 313 314 def reset_password(request, format=None):
""" 314 315 """
Updates user's password to new password if token is valid. 315 316 Updates user's password to new password if token is valid.
--- 316 317 ---
request_serializer: PasswordResetSerializer 317 318 request_serializer: PasswordResetSerializer
""" 318 319 """
data = PasswordResetSerializer(data=request.data) 319 320 data = PasswordResetSerializer(data=request.data)
data.is_valid(raise_exception=True) 320 321 data.is_valid(raise_exception=True)
321 322
user = User.objects.get(id=data['uid'].value) 322 323 user = User.objects.get(id=data['uid'].value)
# Check token validity. 323 324 # Check token validity.
324 325
if default_token_generator.check_token(user, data['token'].value): 325 326 if default_token_generator.check_token(user, data['token'].value):
user.set_password(data['new_password'].value) 326 327 user.set_password(data['new_password'].value)
user.save() 327 328 user.save()
user = authenticate(email=user.email, password=data['new_password'].value) 328 329 user = authenticate(email=user.email, password=data['new_password'].value)
auth.login(request, user) 329 330 auth.login(request, user)
330 331
log_event(request) 331 332 log_event(request)
else: 332 333 else:
raise ValidationError('Could not verify reset token') 333 334 raise ValidationError('Could not verify reset token')
return Response(status=HTTP_204_NO_CONTENT) 334 335 return Response(status=HTTP_204_NO_CONTENT)
335 336
336 337
class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin): 337 338 class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin):
queryset = Flashcard.objects.all() 338 339 queryset = Flashcard.objects.all()
serializer_class = FlashcardSerializer 339 340 serializer_class = FlashcardSerializer
permission_classes = [IsAuthenticatedAndConfirmed, IsEnrolledInAssociatedSection] 340 341 permission_classes = [IsAuthenticatedAndConfirmed, IsEnrolledInAssociatedSection]
341 342
# Override create in CreateModelMixin 342 343 # Override create in CreateModelMixin
def create(self, request, *args, **kwargs): 343 344 def create(self, request, *args, **kwargs):
if not LimitFlashcardPushThrottle().allow_request(request, 'flashcard_create'): 344 345 if not LimitFlashcardPushThrottle().allow_request(request, 'flashcard_create'):
raise Throttled(wait=None, detail=None) 345 346 raise Throttled(wait=None, detail=None)
serializer = FlashcardSerializer(data=request.data) 346 347 serializer = FlashcardSerializer(data=request.data)
serializer.is_valid(raise_exception=True) 347 348 serializer.is_valid(raise_exception=True)
data = serializer.validated_data 348 349 data = serializer.validated_data
if not request.user.is_in_section(data['section']): 349 350 if not request.user.is_in_section(data['section']):
raise PermissionDenied('The user is not enrolled in that section') 350 351 raise PermissionDenied('The user is not enrolled in that section')
data['author'] = request.user 351 352 data['author'] = request.user
flashcard = Flashcard.push(**data) 352 353 flashcard = Flashcard.push(**data)
response_data = FlashcardSerializer(flashcard, context={'user': request.user}).data 353 354 response_data = FlashcardSerializer(flashcard, context={'user': request.user}).data
log_event(request, response_data) 354 355 log_event(request, response_data)
headers = self.get_success_headers(data) 355 356 headers = self.get_success_headers(data)
return Response(response_data, status=HTTP_201_CREATED, headers=headers) 356 357 return Response(response_data, status=HTTP_201_CREATED, headers=headers)
357 358
@detail_route(methods=['POST']) 358 359 @detail_route(methods=['POST'])
def unhide(self, request, pk): 359 360 def unhide(self, request, pk):
""" 360 361 """
Unhide the given card 361 362 Unhide the given card
--- 362 363 ---
view_mocker: flashcards.api.mock_no_params 363 364 view_mocker: flashcards.api.mock_no_params
""" 364 365 """
try: 365 366 try:
self.get_object().unhide_by_user(request.user) 366 367 self.get_object().unhide_by_user(request.user)
except FlashcardHide.DoesNotExist: 367 368 except FlashcardHide.DoesNotExist:
raise ValidationError("Cannot unhide a card which is not hidden") 368 369 raise ValidationError("Cannot unhide a card which is not hidden")
log_event(request, unicode(self.get_object())) 369 370 log_event(request, unicode(self.get_object()))
return Response(status=HTTP_204_NO_CONTENT) 370 371 return Response(status=HTTP_204_NO_CONTENT)
371 372
@detail_route(methods=['POST']) 372 373 @detail_route(methods=['POST'])
def report(self, request, pk): 373 374 def report(self, request, pk):
""" 374 375 """
Hide the given card 375 376 Hide the given card
--- 376 377 ---
view_mocker: flashcards.api.mock_no_params 377 378 view_mocker: flashcards.api.mock_no_params
""" 378 379 """
try: 379 380 try:
self.get_object().hide_by_user(request.user) 380 381 self.get_object().hide_by_user(request.user)
except FlashcardAlreadyHiddenException: 381 382 except FlashcardAlreadyHiddenException:
raise ValidationError('Cannot hide a card which is already hidden') 382 383 raise ValidationError('Cannot hide a card which is already hidden')
log_event(request, unicode(self.get_object())) 383 384 log_event(request, unicode(self.get_object()))
return Response(status=HTTP_204_NO_CONTENT) 384 385 return Response(status=HTTP_204_NO_CONTENT)
385 386
hide = report 386 387 hide = report
387 388
@detail_route(methods=['POST']) 388 389 @detail_route(methods=['POST'])
def pull(self, request, pk): 389 390 def pull(self, request, pk):
""" 390 391 """
Pull a card from the live feed into the user's deck. 391 392 Pull a card from the live feed into the user's deck.
--- 392 393 ---
view_mocker: flashcards.api.mock_no_params 393 394 view_mocker: flashcards.api.mock_no_params
""" 394 395 """
try: 395 396 try:
request.user.pull(self.get_object()) 396 397 request.user.pull(self.get_object())
log_event(request, self.get_object()) 397 398 log_event(request, self.get_object())
return Response(status=HTTP_204_NO_CONTENT) 398 399 return Response(status=HTTP_204_NO_CONTENT)
except FlashcardAlreadyPulledException: 399 400 except FlashcardAlreadyPulledException:
raise ValidationError('Cannot pull a card already in deck') 400 401 raise ValidationError('Cannot pull a card already in deck')
401 402
@detail_route(methods=['POST']) 402 403 @detail_route(methods=['POST'])
def unpull(self, request, pk): 403 404 def unpull(self, request, pk):
""" 404 405 """
Unpull a card from the user's deck 405 406 Unpull a card from the user's deck
--- 406 407 ---
view_mocker: flashcards.api.mock_no_params 407 408 view_mocker: flashcards.api.mock_no_params
""" 408 409 """
user = request.user 409 410 user = request.user
flashcard = self.get_object() 410 411 flashcard = self.get_object()
try: 411 412 try:
user.unpull(flashcard) 412 413 user.unpull(flashcard)
log_event(request, self.get_object()) 413 414 log_event(request, self.get_object())
return Response(status=HTTP_204_NO_CONTENT) 414 415 return Response(status=HTTP_204_NO_CONTENT)
except FlashcardNotInDeckException: 415 416 except FlashcardNotInDeckException:
raise ValidationError('Cannot unpull a card not in deck') 416 417 raise ValidationError('Cannot unpull a card not in deck')
417 418
def partial_update(self, request, *args, **kwargs): 418 419 def partial_update(self, request, *args, **kwargs):
""" 419 420 """