Commit 505cdbd5a1be61b3c387d9aeedf2d3e273c3bb10

Authored by Laura Hawkins
1 parent 3709ee6455
Exists in master

stuff

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

flashcards/views.py View file @ 505cdbd
from django.contrib import auth 1 1 from django.contrib import auth
from django.db.models import Q 2 2 from django.db.models import Q
from flashcards.api import StandardResultsSetPagination 3 3 from flashcards.api import StandardResultsSetPagination
from flashcards.models import Section, User, Flashcard, FlashcardReport, UserFlashcard 4 4 from flashcards.models import Section, User, Flashcard, FlashcardReport, 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
from rest_framework.decorators import detail_route, permission_classes, api_view, list_route 7 7 from rest_framework.decorators import detail_route, permission_classes, api_view, list_route
from rest_framework.generics import ListAPIView, GenericAPIView 8 8 from rest_framework.generics import ListAPIView, GenericAPIView
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin 9 9 from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin
from rest_framework.permissions import IsAuthenticated 10 10 from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet 11 11 from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet
from django.core.mail import send_mail 12 12 from django.core.mail import send_mail
from django.contrib.auth import authenticate 13 13 from django.contrib.auth import authenticate
from django.contrib.auth.tokens import default_token_generator 14 14 from django.contrib.auth.tokens import default_token_generator
from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED 15 15 from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED
from rest_framework.response import Response 16 16 from rest_framework.response import Response
from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied 17 17 from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied
from simple_email_confirmation import EmailAddress 18 18 from simple_email_confirmation import EmailAddress
19 19
20 20
class SectionViewSet(ReadOnlyModelViewSet): 21 21 class SectionViewSet(ReadOnlyModelViewSet):
queryset = Section.objects.all() 22 22 queryset = Section.objects.all()
serializer_class = SectionSerializer 23 23 serializer_class = SectionSerializer
pagination_class = StandardResultsSetPagination 24 24 pagination_class = StandardResultsSetPagination
permission_classes = [IsAuthenticated] 25 25 permission_classes = [IsAuthenticated]
26 26
@detail_route(methods=['get'], permission_classes=[IsAuthenticated]) 27 27 @detail_route(methods=['get'], permission_classes=[IsAuthenticated])
def flashcards(self, request, pk): 28 28 def flashcards(self, request, pk):
""" 29 29 """
Gets flashcards for a section, excluding hidden cards. 30 30 Gets flashcards for a section, excluding hidden cards.
Returned in strictly chronological order (material date). 31 31 Returned in strictly chronological order (material date).
""" 32 32 """
flashcards = Flashcard.cards_visible_to(request.user).filter( \ 33 33 flashcards = Flashcard.cards_visible_to(request.user).filter( \
section=self.get_object(), is_hidden=False).all() 34 34 section=self.get_object(), is_hidden=False).all()
35 35
return Response(FlashcardSerializer(flashcards, many=True).data) 36 36 return Response(FlashcardSerializer(flashcards, many=True).data)
37 37
@detail_route(methods=['post'], permission_classes=[IsAuthenticated]) 38 38 @detail_route(methods=['post'], permission_classes=[IsAuthenticated])
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'], permission_classes=[IsAuthenticated]) 58 58 @detail_route(methods=['post'], permission_classes=[IsAuthenticated])
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'], permission_classes=[IsAuthenticated]) 76 76 @list_route(methods=['get'], permission_classes=[IsAuthenticated])
def search(self, request): 77 77 def search(self, request):
query = request.GET.get('q',None) 78 78 query = request.GET.get('q',None)
if not query: return Response('[]') 79 79 if not query: return Response('[]')
qs = Section.search(query.split(' '))[:8] 80 80 qs = Section.search(query.split(' '))[:8]
serializer = SectionSerializer(qs, many=True) 81 81 serializer = SectionSerializer(qs, many=True)
return Response(serializer.data) 82 82 return Response(serializer.data)
83 83
@list_route(methods=['get'], permission_classes=[IsAuthenticated]) 84 84 @detail_route(methods=['get'], permission_classes=[IsAuthenticated])
def deck(self, request): 85 85 def deck(self, request):
""" 86 86 """
Gets the contents of a user's deck for a given section. 87 87 Gets the contents of a user's deck for a given section.
""" 88 88 """
query = request.GET.get(Flashcard.objects.all(), None) 89 89 #query = request.GET.get(Flashcard.objects.all(), None)
if not query: return Response('[]') 90 90 #if not query: return Response('[]')
qs = Flashcard.objects.all() 91 91 qs = Flashcard.objects.all()
qs = qs.filter(userflashcard__user=request.user) 92 92 qs = qs.filter(userflashcard__user=request.user)
serializer = FlashcardSerializer(qs, many=True) 93 93 serializer = FlashcardSerializer(qs, many=True)
return Response(serializer.data) 94 94 return Response(serializer.data)
95 95
96 96
class UserSectionListView(ListAPIView): 97 97 class UserSectionListView(ListAPIView):
serializer_class = SectionSerializer 98 98 serializer_class = SectionSerializer
permission_classes = [IsAuthenticated] 99 99 permission_classes = [IsAuthenticated]
100 100
def get_queryset(self): 101 101 def get_queryset(self):
return self.request.user.sections.all() 102 102 return self.request.user.sections.all()
103 103
def paginate_queryset(self, queryset): return None 104 104 def paginate_queryset(self, queryset): return None
105 105
106 106
class UserDetail(GenericAPIView): 107 107 class UserDetail(GenericAPIView):
serializer_class = UserSerializer 108 108 serializer_class = UserSerializer
permission_classes = [IsAuthenticated] 109 109 permission_classes = [IsAuthenticated]
110 110
def get_queryset(self): 111 111 def get_queryset(self):
return User.objects.all() 112 112 return User.objects.all()
113 113
def patch(self, request, format=None): 114 114 def patch(self, request, format=None):
""" 115 115 """
Updates the user's password, or verifies their email address 116 116 Updates the user's password, or verifies their email address
--- 117 117 ---
request_serializer: UserUpdateSerializer 118 118 request_serializer: UserUpdateSerializer
response_serializer: UserSerializer 119 119 response_serializer: UserSerializer
""" 120 120 """
data = UserUpdateSerializer(data=request.data, context={'user': request.user}) 121 121 data = UserUpdateSerializer(data=request.data, context={'user': request.user})
data.is_valid(raise_exception=True) 122 122 data.is_valid(raise_exception=True)
data = data.validated_data 123 123 data = data.validated_data
124 124
if 'new_password' in data: 125 125 if 'new_password' in data:
if not request.user.check_password(data['old_password']): 126 126 if not request.user.check_password(data['old_password']):
raise ValidationError('old_password is incorrect') 127 127 raise ValidationError('old_password is incorrect')
request.user.set_password(data['new_password']) 128 128 request.user.set_password(data['new_password'])
request.user.save() 129 129 request.user.save()
130 130
if 'confirmation_key' in data: 131 131 if 'confirmation_key' in data:
try: 132 132 try:
request.user.confirm_email(data['confirmation_key']) 133 133 request.user.confirm_email(data['confirmation_key'])
except EmailAddress.DoesNotExist: 134 134 except EmailAddress.DoesNotExist:
raise ValidationError('confirmation_key is invalid') 135 135 raise ValidationError('confirmation_key is invalid')
136 136
return Response(UserSerializer(request.user).data) 137 137 return Response(UserSerializer(request.user).data)
138 138
def get(self, request, format=None): 139 139 def get(self, request, format=None):
""" 140 140 """
Return data about the user 141 141 Return data about the user
--- 142 142 ---
response_serializer: UserSerializer 143 143 response_serializer: UserSerializer
""" 144 144 """
serializer = UserSerializer(request.user, context={'request': request}) 145 145 serializer = UserSerializer(request.user, context={'request': request})
return Response(serializer.data) 146 146 return Response(serializer.data)
147 147
def delete(self, request): 148 148 def delete(self, request):
""" 149 149 """
Irrevocably delete the user and their data 150 150 Irrevocably delete the user and their data
151 151
Yes, really 152 152 Yes, really
""" 153 153 """
request.user.delete() 154 154 request.user.delete()
return Response(status=HTTP_204_NO_CONTENT) 155 155 return Response(status=HTTP_204_NO_CONTENT)
156 156
157 157
@api_view(['POST']) 158 158 @api_view(['POST'])
def register(request, format=None): 159 159 def register(request, format=None):
""" 160 160 """
Register a new user 161 161 Register a new user
--- 162 162 ---
request_serializer: EmailPasswordSerializer 163 163 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 164 164 response_serializer: UserSerializer
""" 165 165 """
data = RegistrationSerializer(data=request.data) 166 166 data = RegistrationSerializer(data=request.data)
data.is_valid(raise_exception=True) 167 167 data.is_valid(raise_exception=True)
168 168
User.objects.create_user(**data.validated_data) 169 169 User.objects.create_user(**data.validated_data)
user = authenticate(**data.validated_data) 170 170 user = authenticate(**data.validated_data)
auth.login(request, user) 171 171 auth.login(request, user)
172 172
body = ''' 173 173 body = '''
Visit the following link to confirm your email address: 174 174 Visit the following link to confirm your email address:
https://flashy.cards/app/verify_email/%s 175 175 https://flashy.cards/app/verify_email/%s
176 176
If you did not register for Flashy, no action is required. 177 177 If you did not register for Flashy, no action is required.
''' 178 178 '''
179 179
assert send_mail("Flashy email verification", 180 180 assert send_mail("Flashy email verification",
body % user.confirmation_key, 181 181 body % user.confirmation_key,
"noreply@flashy.cards", 182 182 "noreply@flashy.cards",
[user.email]) 183 183 [user.email])
184 184
return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED) 185 185 return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED)
186 186
187 187
@api_view(['POST']) 188 188 @api_view(['POST'])
def login(request): 189 189 def login(request):
""" 190 190 """
Authenticates user and returns user data if valid. 191 191 Authenticates user and returns user data if valid.
--- 192 192 ---
request_serializer: EmailPasswordSerializer 193 193 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 194 194 response_serializer: UserSerializer
""" 195 195 """
196 196
data = EmailPasswordSerializer(data=request.data) 197 197 data = EmailPasswordSerializer(data=request.data)
data.is_valid(raise_exception=True) 198 198 data.is_valid(raise_exception=True)
user = authenticate(**data.validated_data) 199 199 user = authenticate(**data.validated_data)
200 200
if user is None: 201 201 if user is None:
raise AuthenticationFailed('Invalid email or password') 202 202 raise AuthenticationFailed('Invalid email or password')
if not user.is_active: 203 203 if not user.is_active:
raise NotAuthenticated('Account is disabled') 204 204 raise NotAuthenticated('Account is disabled')
auth.login(request, user) 205 205 auth.login(request, user)
return Response(UserSerializer(request.user).data) 206 206 return Response(UserSerializer(request.user).data)
207 207
208 208
@api_view(['POST']) 209 209 @api_view(['POST'])
@permission_classes((IsAuthenticated, )) 210 210 @permission_classes((IsAuthenticated, ))
def logout(request, format=None): 211 211 def logout(request, format=None):
""" 212 212 """
Logs the authenticated user out. 213 213 Logs the authenticated user out.
""" 214 214 """
auth.logout(request) 215 215 auth.logout(request)
return Response(status=HTTP_204_NO_CONTENT) 216 216 return Response(status=HTTP_204_NO_CONTENT)
217 217
218 218
@api_view(['POST']) 219 219 @api_view(['POST'])
def request_password_reset(request, format=None): 220 220 def request_password_reset(request, format=None):
""" 221 221 """
Send a password reset token/link to the provided email. 222 222 Send a password reset token/link to the provided email.
--- 223 223 ---
request_serializer: PasswordResetRequestSerializer 224 224 request_serializer: PasswordResetRequestSerializer
""" 225 225 """
data = PasswordResetRequestSerializer(data=request.data) 226 226 data = PasswordResetRequestSerializer(data=request.data)
data.is_valid(raise_exception=True) 227 227 data.is_valid(raise_exception=True)
user = User.objects.get(email=data['email'].value) 228 228 user = User.objects.get(email=data['email'].value)
token = default_token_generator.make_token(user) 229 229 token = default_token_generator.make_token(user)
230 230
body = ''' 231 231 body = '''
Visit the following link to reset your password: 232 232 Visit the following link to reset your password:
https://flashy.cards/app/reset_password/%d/%s 233 233 https://flashy.cards/app/reset_password/%d/%s
234 234
If you did not request a password reset, no action is required. 235 235 If you did not request a password reset, no action is required.
''' 236 236 '''
237 237
send_mail("Flashy password reset", 238 238 send_mail("Flashy password reset",
body % (user.pk, token), 239 239 body % (user.pk, token),
"noreply@flashy.cards", 240 240 "noreply@flashy.cards",
[user.email]) 241 241 [user.email])
242 242
return Response(status=HTTP_204_NO_CONTENT) 243 243 return Response(status=HTTP_204_NO_CONTENT)
244 244
245 245
@api_view(['POST']) 246 246 @api_view(['POST'])
def reset_password(request, format=None): 247 247 def reset_password(request, format=None):
""" 248 248 """
Updates user's password to new password if token is valid. 249 249 Updates user's password to new password if token is valid.
--- 250 250 ---
request_serializer: PasswordResetSerializer 251 251 request_serializer: PasswordResetSerializer
""" 252 252 """
data = PasswordResetSerializer(data=request.data) 253 253 data = PasswordResetSerializer(data=request.data)
data.is_valid(raise_exception=True) 254 254 data.is_valid(raise_exception=True)
255 255
user = User.objects.get(id=data['uid'].value) 256 256 user = User.objects.get(id=data['uid'].value)
# Check token validity. 257 257 # Check token validity.
258 258
if default_token_generator.check_token(user, data['token'].value): 259 259 if default_token_generator.check_token(user, data['token'].value):
user.set_password(data['new_password'].value) 260 260 user.set_password(data['new_password'].value)
user.save() 261 261 user.save()
else: 262 262 else: