Commit c66537ab303eab1d4f5d3596d1eb03a2b66c976c

Authored by Andrew Buss
1 parent 94b93b5796
Exists in master

nitpicky space fixing

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

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