Commit c7885ab8613a8a0c3ff817d66f322d32f6691645

Authored by Chung Wang
1 parent 29c4330965
Exists in master

hide flashcard and added return response for unhide

Showing 1 changed file with 19 additions and 0 deletions Inline Diff

flashcards/views.py View file @ c7885ab
from django.contrib import auth 1 1 from django.contrib import auth
from django.shortcuts import get_object_or_404 2 2 from django.shortcuts import get_object_or_404
from flashcards.api import StandardResultsSetPagination 3 3 from flashcards.api import StandardResultsSetPagination
from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard 4 4 from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard
from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \ 5 5 from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \
PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \ 6 6 PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \
FlashcardUpdateSerializer 7 7 FlashcardUpdateSerializer
from rest_framework.decorators import detail_route, permission_classes, api_view, list_route 8 8 from rest_framework.decorators import detail_route, permission_classes, api_view, list_route
from rest_framework.generics import ListAPIView, GenericAPIView 9 9 from rest_framework.generics import ListAPIView, GenericAPIView
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin 10 10 from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.permissions import IsAuthenticated 11 11 from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet 12 12 from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet
from django.core.mail import send_mail 13 13 from django.core.mail import send_mail
from django.contrib.auth import authenticate 14 14 from django.contrib.auth import authenticate
from django.contrib.auth.tokens import default_token_generator 15 15 from django.contrib.auth.tokens import default_token_generator
from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED 16 16 from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED
from rest_framework.response import Response 17 17 from rest_framework.response import Response
from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied 18 18 from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied
from simple_email_confirmation import EmailAddress 19 19 from simple_email_confirmation import EmailAddress
20 20
21 21
class SectionViewSet(ReadOnlyModelViewSet): 22 22 class SectionViewSet(ReadOnlyModelViewSet):
queryset = Section.objects.all() 23 23 queryset = Section.objects.all()
serializer_class = SectionSerializer 24 24 serializer_class = SectionSerializer
pagination_class = StandardResultsSetPagination 25 25 pagination_class = StandardResultsSetPagination
permission_classes = [IsAuthenticated] 26 26 permission_classes = [IsAuthenticated]
27 27
@detail_route(methods=['GET']) 28 28 @detail_route(methods=['GET'])
def flashcards(self, request, pk): 29 29 def flashcards(self, request, pk):
""" 30 30 """
Gets flashcards for a section, excluding hidden cards. 31 31 Gets flashcards for a section, excluding hidden cards.
Returned in strictly chronological order (material date). 32 32 Returned in strictly chronological order (material date).
""" 33 33 """
flashcards = Flashcard.cards_visible_to(request.user).filter( \ 34 34 flashcards = Flashcard.cards_visible_to(request.user).filter( \
section=self.get_object()).all() 35 35 section=self.get_object()).all()
return Response(FlashcardSerializer(flashcards, many=True).data) 36 36 return Response(FlashcardSerializer(flashcards, many=True).data)
37 37
@detail_route(methods=['post']) 38 38 @detail_route(methods=['post'])
def enroll(self, request, pk): 39 39 def enroll(self, request, pk):
""" 40 40 """
Add the current user to a specified section 41 41 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. 42 42 If the class has a whitelist, but the user is not on the whitelist, the request will fail.
--- 43 43 ---
omit_serializer: true 44 44 omit_serializer: true
parameters: 45 45 parameters:
- fake: None 46 46 - fake: None
parameters_strategy: 47 47 parameters_strategy:
form: replace 48 48 form: replace
""" 49 49 """
section = self.get_object() 50 50 section = self.get_object()
if request.user.sections.filter(pk=section.pk).exists(): 51 51 if request.user.sections.filter(pk=section.pk).exists():
raise ValidationError("You are already in this section.") 52 52 raise ValidationError("You are already in this section.")
if section.is_whitelisted and not section.is_user_on_whitelist(request.user): 53 53 if section.is_whitelisted and not section.is_user_on_whitelist(request.user):
raise PermissionDenied("You must be on the whitelist to add this section.") 54 54 raise PermissionDenied("You must be on the whitelist to add this section.")
request.user.sections.add(section) 55 55 request.user.sections.add(section)
return Response(status=HTTP_204_NO_CONTENT) 56 56 return Response(status=HTTP_204_NO_CONTENT)
57 57
@detail_route(methods=['post']) 58 58 @detail_route(methods=['post'])
def drop(self, request, pk): 59 59 def drop(self, request, pk):
""" 60 60 """
Remove the current user from a specified section 61 61 Remove the current user from a specified section
If the user is not in the class, the request will fail. 62 62 If the user is not in the class, the request will fail.
--- 63 63 ---
omit_serializer: true 64 64 omit_serializer: true
parameters: 65 65 parameters:
- fake: None 66 66 - fake: None
parameters_strategy: 67 67 parameters_strategy:
form: replace 68 68 form: replace
""" 69 69 """
section = self.get_object() 70 70 section = self.get_object()
if not section.user_set.filter(pk=request.user.pk).exists(): 71 71 if not section.user_set.filter(pk=request.user.pk).exists():
raise ValidationError("You are not in the section.") 72 72 raise ValidationError("You are not in the section.")
section.user_set.remove(request.user) 73 73 section.user_set.remove(request.user)
return Response(status=HTTP_204_NO_CONTENT) 74 74 return Response(status=HTTP_204_NO_CONTENT)
75 75
@list_route(methods=['GET']) 76 76 @list_route(methods=['GET'])
def search(self, request): 77 77 def search(self, request):
""" 78 78 """
Returns a list of sections which match a user's query 79 79 Returns a list of sections which match a user's query
""" 80 80 """
query = request.GET.get('q', None) 81 81 query = request.GET.get('q', None)
if not query: return Response('[]') 82 82 if not query: return Response('[]')
qs = Section.search(query.split(' '))[:20] 83 83 qs = Section.search(query.split(' '))[:20]
serializer = SectionSerializer(qs, many=True) 84 84 serializer = SectionSerializer(qs, many=True)
return Response(serializer.data) 85 85 return Response(serializer.data)
86 86
@detail_route(methods=['GET']) 87 87 @detail_route(methods=['GET'])
def deck(self, request, pk): 88 88 def deck(self, request, pk):
""" 89 89 """
Gets the contents of a user's deck for a given section. 90 90 Gets the contents of a user's deck for a given section.
""" 91 91 """
qs = request.user.get_deck(self.get_object()) 92 92 qs = request.user.get_deck(self.get_object())
serializer = FlashcardSerializer(qs, many=True) 93 93 serializer = FlashcardSerializer(qs, many=True)
return Response(serializer.data) 94 94 return Response(serializer.data)
95 95
@detail_route(methods=['get'], permission_classes=[IsAuthenticated]) 96 96 @detail_route(methods=['get'], permission_classes=[IsAuthenticated])
def ordered_deck(self, request, pk): 97 97 def ordered_deck(self, request, pk):
""" 98 98 """
Get a chronological order by material_date of flashcards for a section. 99 99 Get a chronological order by material_date of flashcards for a section.
This excludes hidden card. 100 100 This excludes hidden card.
""" 101 101 """
qs = request.user.get_deck(self.get_object()).order_by('-material_date') 102 102 qs = request.user.get_deck(self.get_object()).order_by('-material_date')
serializer = FlashcardSerializer(qs, many=True) 103 103 serializer = FlashcardSerializer(qs, many=True)
return Response(serializer.data) 104 104 return Response(serializer.data)
105 105
@detail_route(methods=['GET']) 106 106 @detail_route(methods=['GET'])
def feed(self, request, pk): 107 107 def feed(self, request, pk):
""" 108 108 """
Gets the contents of a user's feed for a section. 109 109 Gets the contents of a user's feed for a section.
Exclude cards that are already in the user's deck 110 110 Exclude cards that are already in the user's deck
""" 111 111 """
serializer = FlashcardSerializer(self.get_object().get_feed_for_user(request.user), many=True) 112 112 serializer = FlashcardSerializer(self.get_object().get_feed_for_user(request.user), many=True)
return Response(serializer.data) 113 113 return Response(serializer.data)
114 114
115 115
class UserSectionListView(ListAPIView): 116 116 class UserSectionListView(ListAPIView):
serializer_class = SectionSerializer 117 117 serializer_class = SectionSerializer
permission_classes = [IsAuthenticated] 118 118 permission_classes = [IsAuthenticated]
119 119
def get_queryset(self): 120 120 def get_queryset(self):
return self.request.user.sections.all() 121 121 return self.request.user.sections.all()
122 122
def paginate_queryset(self, queryset): return None 123 123 def paginate_queryset(self, queryset): return None
124 124
125 125
class UserDetail(GenericAPIView): 126 126 class UserDetail(GenericAPIView):
serializer_class = UserSerializer 127 127 serializer_class = UserSerializer
permission_classes = [IsAuthenticated] 128 128 permission_classes = [IsAuthenticated]
129 129
def get_queryset(self): 130 130 def get_queryset(self):
return User.objects.all() 131 131 return User.objects.all()
132 132
def patch(self, request, format=None): 133 133 def patch(self, request, format=None):
""" 134 134 """
Updates the user's password, or verifies their email address 135 135 Updates the user's password, or verifies their email address
--- 136 136 ---
request_serializer: UserUpdateSerializer 137 137 request_serializer: UserUpdateSerializer
response_serializer: UserSerializer 138 138 response_serializer: UserSerializer
""" 139 139 """
data = UserUpdateSerializer(data=request.data, context={'user': request.user}) 140 140 data = UserUpdateSerializer(data=request.data, context={'user': request.user})
data.is_valid(raise_exception=True) 141 141 data.is_valid(raise_exception=True)
data = data.validated_data 142 142 data = data.validated_data
143 143
if 'new_password' in data: 144 144 if 'new_password' in data:
if not request.user.check_password(data['old_password']): 145 145 if not request.user.check_password(data['old_password']):
raise ValidationError('old_password is incorrect') 146 146 raise ValidationError('old_password is incorrect')
request.user.set_password(data['new_password']) 147 147 request.user.set_password(data['new_password'])
request.user.save() 148 148 request.user.save()
149 149
if 'confirmation_key' in data: 150 150 if 'confirmation_key' in data:
try: 151 151 try:
request.user.confirm_email(data['confirmation_key']) 152 152 request.user.confirm_email(data['confirmation_key'])
except EmailAddress.DoesNotExist: 153 153 except EmailAddress.DoesNotExist:
raise ValidationError('confirmation_key is invalid') 154 154 raise ValidationError('confirmation_key is invalid')
155 155
return Response(UserSerializer(request.user).data) 156 156 return Response(UserSerializer(request.user).data)
157 157
def get(self, request, format=None): 158 158 def get(self, request, format=None):
""" 159 159 """
Return data about the user 160 160 Return data about the user
--- 161 161 ---
response_serializer: UserSerializer 162 162 response_serializer: UserSerializer
""" 163 163 """
serializer = UserSerializer(request.user, context={'request': request}) 164 164 serializer = UserSerializer(request.user, context={'request': request})
return Response(serializer.data) 165 165 return Response(serializer.data)
166 166
def delete(self, request): 167 167 def delete(self, request):
""" 168 168 """
Irrevocably delete the user and their data 169 169 Irrevocably delete the user and their data
170 170
Yes, really 171 171 Yes, really
""" 172 172 """
request.user.delete() 173 173 request.user.delete()
return Response(status=HTTP_204_NO_CONTENT) 174 174 return Response(status=HTTP_204_NO_CONTENT)
175 175
176 176
@api_view(['POST']) 177 177 @api_view(['POST'])
def register(request, format=None): 178 178 def register(request, format=None):
""" 179 179 """
Register a new user 180 180 Register a new user
--- 181 181 ---
request_serializer: EmailPasswordSerializer 182 182 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 183 183 response_serializer: UserSerializer
""" 184 184 """
data = RegistrationSerializer(data=request.data) 185 185 data = RegistrationSerializer(data=request.data)
data.is_valid(raise_exception=True) 186 186 data.is_valid(raise_exception=True)
187 187
User.objects.create_user(**data.validated_data) 188 188 User.objects.create_user(**data.validated_data)
user = authenticate(**data.validated_data) 189 189 user = authenticate(**data.validated_data)
auth.login(request, user) 190 190 auth.login(request, user)
191 191
body = ''' 192 192 body = '''
Visit the following link to confirm your email address: 193 193 Visit the following link to confirm your email address:
https://flashy.cards/app/verifyemail/%s 194 194 https://flashy.cards/app/verifyemail/%s
195 195
If you did not register for Flashy, no action is required. 196 196 If you did not register for Flashy, no action is required.
''' 197 197 '''
198 198
assert send_mail("Flashy email verification", 199 199 assert send_mail("Flashy email verification",
body % user.confirmation_key, 200 200 body % user.confirmation_key,
"noreply@flashy.cards", 201 201 "noreply@flashy.cards",
[user.email]) 202 202 [user.email])
203 203
return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED) 204 204 return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED)
205 205
206 206
@api_view(['POST']) 207 207 @api_view(['POST'])
def login(request): 208 208 def login(request):
""" 209 209 """
Authenticates user and returns user data if valid. 210 210 Authenticates user and returns user data if valid.
--- 211 211 ---
request_serializer: EmailPasswordSerializer 212 212 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 213 213 response_serializer: UserSerializer
""" 214 214 """
215 215
data = EmailPasswordSerializer(data=request.data) 216 216 data = EmailPasswordSerializer(data=request.data)
data.is_valid(raise_exception=True) 217 217 data.is_valid(raise_exception=True)
user = authenticate(**data.validated_data) 218 218 user = authenticate(**data.validated_data)
219 219
if user is None: 220 220 if user is None:
raise AuthenticationFailed('Invalid email or password') 221 221 raise AuthenticationFailed('Invalid email or password')
if not user.is_active: 222 222 if not user.is_active:
raise NotAuthenticated('Account is disabled') 223 223 raise NotAuthenticated('Account is disabled')
auth.login(request, user) 224 224 auth.login(request, user)
return Response(UserSerializer(request.user).data) 225 225 return Response(UserSerializer(request.user).data)
226 226
227 227
@api_view(['POST']) 228 228 @api_view(['POST'])
@permission_classes((IsAuthenticated, )) 229 229 @permission_classes((IsAuthenticated, ))
def logout(request, format=None): 230 230 def logout(request, format=None):
""" 231 231 """
Logs the authenticated user out. 232 232 Logs the authenticated user out.
""" 233 233 """
auth.logout(request) 234 234 auth.logout(request)
return Response(status=HTTP_204_NO_CONTENT) 235 235 return Response(status=HTTP_204_NO_CONTENT)
236 236
237 237
@api_view(['POST']) 238 238 @api_view(['POST'])
def request_password_reset(request, format=None): 239 239 def request_password_reset(request, format=None):
""" 240 240 """
Send a password reset token/link to the provided email. 241 241 Send a password reset token/link to the provided email.
--- 242 242 ---
request_serializer: PasswordResetRequestSerializer 243 243 request_serializer: PasswordResetRequestSerializer
""" 244 244 """
data = PasswordResetRequestSerializer(data=request.data) 245 245 data = PasswordResetRequestSerializer(data=request.data)
data.is_valid(raise_exception=True) 246 246 data.is_valid(raise_exception=True)
user = User.objects.get(email=data['email'].value) 247 247 user = User.objects.get(email=data['email'].value)
token = default_token_generator.make_token(user) 248 248 token = default_token_generator.make_token(user)
249 249
body = ''' 250 250 body = '''
Visit the following link to reset your password: 251 251 Visit the following link to reset your password:
https://flashy.cards/app/resetpassword/%d/%s 252 252 https://flashy.cards/app/resetpassword/%d/%s
253 253
If you did not request a password reset, no action is required. 254 254 If you did not request a password reset, no action is required.
''' 255 255 '''
256 256
send_mail("Flashy password reset", 257 257 send_mail("Flashy password reset",
body % (user.pk, token), 258 258 body % (user.pk, token),
"noreply@flashy.cards", 259 259 "noreply@flashy.cards",
[user.email]) 260 260 [user.email])
261 261
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 reset_password(request, format=None): 266 266 def reset_password(request, format=None):
""" 267 267 """
Updates user's password to new password if token is valid. 268 268 Updates user's password to new password if token is valid.
--- 269 269 ---
request_serializer: PasswordResetSerializer 270 270 request_serializer: PasswordResetSerializer
""" 271 271 """
data = PasswordResetSerializer(data=request.data) 272 272 data = PasswordResetSerializer(data=request.data)
data.is_valid(raise_exception=True) 273 273 data.is_valid(raise_exception=True)
274 274
user = User.objects.get(id=data['uid'].value) 275 275 user = User.objects.get(id=data['uid'].value)
# Check token validity. 276 276 # Check token validity.
277 277
if default_token_generator.check_token(user, data['token'].value): 278 278 if default_token_generator.check_token(user, data['token'].value):
user.set_password(data['new_password'].value) 279 279 user.set_password(data['new_password'].value)
user.save() 280 280 user.save()
else: 281 281 else:
raise ValidationError('Could not verify reset token') 282 282 raise ValidationError('Could not verify reset token')
return Response(status=HTTP_204_NO_CONTENT) 283 283 return Response(status=HTTP_204_NO_CONTENT)
284 284
285 285
class FlashcardViewSet(GenericViewSet, UpdateModelMixin, CreateModelMixin, RetrieveModelMixin): 286 286 class FlashcardViewSet(GenericViewSet, UpdateModelMixin, CreateModelMixin, RetrieveModelMixin):
queryset = Flashcard.objects.all() 287 287 queryset = Flashcard.objects.all()
serializer_class = FlashcardSerializer 288 288 serializer_class = FlashcardSerializer
permission_classes = [IsAuthenticated] 289 289 permission_classes = [IsAuthenticated]
290 290
# Override create in CreateModelMixin 291 291 # Override create in CreateModelMixin
def create(self, request, *args, **kwargs): 292 292 def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data) 293 293 serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) 294 294 serializer.is_valid(raise_exception=True)
serializer.validated_data['author'] = request.user 295 295 serializer.validated_data['author'] = request.user
self.perform_create(serializer) 296 296 self.perform_create(serializer)
headers = self.get_success_headers(serializer.data) 297 297 headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=HTTP_201_CREATED, headers=headers) 298 298 return Response(serializer.data, status=HTTP_201_CREATED, headers=headers)
299 299
@detail_route(methods=['post']) 300 300 @detail_route(methods=['post'])
301 def hide(self, request, pk):
302 """
303 Hide a flashcard
304 ---
305 omit_serializer: true
306 parameters:
307 - fake: None
308 parameters_strategy:
309 form: replace
310 """
311 obj, created = FlashcardHide.objects.get_or_create(user=request.user, flashcard=self.get_object())
312 if not created:
313 raise ValidationError("The card has already been hidden.")
314
315 obj.save()
316 return Response(status=HTTP_204_NO_CONTENT)
317
318 @detail_route(methods=['post'])
def unhide(self, request, pk): 301 319 def unhide(self, request, pk):
""" 302 320 """
Report the given card 303 321 Report the given card
--- 304 322 ---
omit_serializer: true 305 323 omit_serializer: true
parameters: 306 324 parameters:
- fake: None 307 325 - fake: None
parameters_strategy: 308 326 parameters_strategy:
form: replace 309 327 form: replace
""" 310 328 """
hide = get_object_or_404(FlashcardHide ,user=request.user, flashcard=self.get_object()) 311 329 hide = get_object_or_404(FlashcardHide ,user=request.user, flashcard=self.get_object())