Compare View

switch
from
...
to
 
Commits (2)

Diff

Showing 2 changed files Inline Diff

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