Commit 6967144326d93157f3a1033ed33d1f2e4e41383e

Authored by Rohan Rangray
Exists in master

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

mergin' conflicts

Showing 2 changed files Inline Diff

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