Commit 94b93b57968536f5823ba47202e29bdc5ad0c4d5

Authored by Andrew Buss
1 parent 7b4e8b793c
Exists in master

Right and I also made that a property hurr

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

flashcards/views.py View file @ 94b93b5
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
84 84
class UserSectionListView(ListAPIView): 85 85 class UserSectionListView(ListAPIView):
serializer_class = SectionSerializer 86 86 serializer_class = SectionSerializer
permission_classes = [IsAuthenticated] 87 87 permission_classes = [IsAuthenticated]
88 88
def get_queryset(self): 89 89 def get_queryset(self):
return self.request.user.sections.all() 90 90 return self.request.user.sections.all()
91 91
def paginate_queryset(self, queryset): return None 92 92 def paginate_queryset(self, queryset): return None
93 93
94 94
class UserDetail(GenericAPIView): 95 95 class UserDetail(GenericAPIView):
serializer_class = UserSerializer 96 96 serializer_class = UserSerializer
permission_classes = [IsAuthenticated] 97 97 permission_classes = [IsAuthenticated]
98 98
def get_queryset(self): 99 99 def get_queryset(self):
return User.objects.all() 100 100 return User.objects.all()
101 101
def patch(self, request, format=None): 102 102 def patch(self, request, format=None):
""" 103 103 """
Updates the user's password, or verifies their email address 104 104 Updates the user's password, or verifies their email address
--- 105 105 ---
request_serializer: UserUpdateSerializer 106 106 request_serializer: UserUpdateSerializer
response_serializer: UserSerializer 107 107 response_serializer: UserSerializer
""" 108 108 """
data = UserUpdateSerializer(data=request.data, context={'user': request.user}) 109 109 data = UserUpdateSerializer(data=request.data, context={'user': request.user})
data.is_valid(raise_exception=True) 110 110 data.is_valid(raise_exception=True)
data = data.validated_data 111 111 data = data.validated_data
112 112
if 'new_password' in data: 113 113 if 'new_password' in data:
if not request.user.check_password(data['old_password']): 114 114 if not request.user.check_password(data['old_password']):
raise ValidationError('old_password is incorrect') 115 115 raise ValidationError('old_password is incorrect')
request.user.set_password(data['new_password']) 116 116 request.user.set_password(data['new_password'])
request.user.save() 117 117 request.user.save()
118 118
if 'confirmation_key' in data: 119 119 if 'confirmation_key' in data:
try: 120 120 try:
request.user.confirm_email(data['confirmation_key']) 121 121 request.user.confirm_email(data['confirmation_key'])
except EmailAddress.DoesNotExist: 122 122 except EmailAddress.DoesNotExist:
raise ValidationError('confirmation_key is invalid') 123 123 raise ValidationError('confirmation_key is invalid')
124 124
return Response(UserSerializer(request.user).data) 125 125 return Response(UserSerializer(request.user).data)
126 126
def get(self, request, format=None): 127 127 def get(self, request, format=None):
""" 128 128 """
Return data about the user 129 129 Return data about the user
--- 130 130 ---
response_serializer: UserSerializer 131 131 response_serializer: UserSerializer
""" 132 132 """
serializer = UserSerializer(request.user, context={'request': request}) 133 133 serializer = UserSerializer(request.user, context={'request': request})
return Response(serializer.data) 134 134 return Response(serializer.data)
135 135
def delete(self, request): 136 136 def delete(self, request):
""" 137 137 """
Irrevocably delete the user and their data 138 138 Irrevocably delete the user and their data
139 139
Yes, really 140 140 Yes, really
""" 141 141 """
request.user.delete() 142 142 request.user.delete()
return Response(status=HTTP_204_NO_CONTENT) 143 143 return Response(status=HTTP_204_NO_CONTENT)
144 144
145 145
@api_view(['POST']) 146 146 @api_view(['POST'])
def register(request, format=None): 147 147 def register(request, format=None):
""" 148 148 """
Register a new user 149 149 Register a new user
--- 150 150 ---
request_serializer: EmailPasswordSerializer 151 151 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 152 152 response_serializer: UserSerializer
""" 153 153 """
data = RegistrationSerializer(data=request.data) 154 154 data = RegistrationSerializer(data=request.data)
data.is_valid(raise_exception=True) 155 155 data.is_valid(raise_exception=True)
156 156
User.objects.create_user(**data.validated_data) 157 157 User.objects.create_user(**data.validated_data)
user = authenticate(**data.validated_data) 158 158 user = authenticate(**data.validated_data)
auth.login(request, user) 159 159 auth.login(request, user)
160 160
body = ''' 161 161 body = '''
Visit the following link to confirm your email address: 162 162 Visit the following link to confirm your email address:
https://flashy.cards/app/verify_email/%s 163 163 https://flashy.cards/app/verify_email/%s
164 164
If you did not register for Flashy, no action is required. 165 165 If you did not register for Flashy, no action is required.
''' 166 166 '''
167 167
assert send_mail("Flashy email verification", 168 168 assert send_mail("Flashy email verification",
body % user.confirmation_key, 169 169 body % user.confirmation_key,
"noreply@flashy.cards", 170 170 "noreply@flashy.cards",
[user.email]) 171 171 [user.email])
172 172
return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED) 173 173 return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED)
174 174
175 175
@api_view(['POST']) 176 176 @api_view(['POST'])
def login(request): 177 177 def login(request):
""" 178 178 """
Authenticates user and returns user data if valid. 179 179 Authenticates user and returns user data if valid.
--- 180 180 ---
request_serializer: EmailPasswordSerializer 181 181 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 182 182 response_serializer: UserSerializer
""" 183 183 """
184 184
data = EmailPasswordSerializer(data=request.data) 185 185 data = EmailPasswordSerializer(data=request.data)
data.is_valid(raise_exception=True) 186 186 data.is_valid(raise_exception=True)
user = authenticate(**data.validated_data) 187 187 user = authenticate(**data.validated_data)
188 188
if user is None: 189 189 if user is None:
raise AuthenticationFailed('Invalid email or password') 190 190 raise AuthenticationFailed('Invalid email or password')
if not user.is_active: 191 191 if not user.is_active:
raise NotAuthenticated('Account is disabled') 192 192 raise NotAuthenticated('Account is disabled')
auth.login(request, user) 193 193 auth.login(request, user)
return Response(UserSerializer(request.user).data) 194 194 return Response(UserSerializer(request.user).data)
195 195
196 196
@api_view(['POST']) 197 197 @api_view(['POST'])
@permission_classes((IsAuthenticated, )) 198 198 @permission_classes((IsAuthenticated, ))
def logout(request, format=None): 199 199 def logout(request, format=None):
""" 200 200 """
Logs the authenticated user out. 201 201 Logs the authenticated user out.
""" 202 202 """
auth.logout(request) 203 203 auth.logout(request)
return Response(status=HTTP_204_NO_CONTENT) 204 204 return Response(status=HTTP_204_NO_CONTENT)
205 205
206 206
@api_view(['POST']) 207 207 @api_view(['POST'])
def request_password_reset(request, format=None): 208 208 def request_password_reset(request, format=None):
""" 209 209 """
Send a password reset token/link to the provided email. 210 210 Send a password reset token/link to the provided email.
--- 211 211 ---
request_serializer: PasswordResetRequestSerializer 212 212 request_serializer: PasswordResetRequestSerializer
""" 213 213 """
data = PasswordResetRequestSerializer(data=request.data) 214 214 data = PasswordResetRequestSerializer(data=request.data)
data.is_valid(raise_exception=True) 215 215 data.is_valid(raise_exception=True)
user = User.objects.get(email=data['email'].value) 216 216 user = User.objects.get(email=data['email'].value)
token = default_token_generator.make_token(user) 217 217 token = default_token_generator.make_token(user)
218 218
body = ''' 219 219 body = '''
Visit the following link to reset your password: 220 220 Visit the following link to reset your password:
https://flashy.cards/app/reset_password/%d/%s 221 221 https://flashy.cards/app/reset_password/%d/%s
222 222
If you did not request a password reset, no action is required. 223 223 If you did not request a password reset, no action is required.
''' 224 224 '''
225 225
send_mail("Flashy password reset", 226 226 send_mail("Flashy password reset",
body % (user.pk, token), 227 227 body % (user.pk, token),
"noreply@flashy.cards", 228 228 "noreply@flashy.cards",
[user.email]) 229 229 [user.email])
230 230
return Response(status=HTTP_204_NO_CONTENT) 231 231 return Response(status=HTTP_204_NO_CONTENT)
232 232
233 233
@api_view(['POST']) 234 234 @api_view(['POST'])
def reset_password(request, format=None): 235 235 def reset_password(request, format=None):
""" 236 236 """
Updates user's password to new password if token is valid. 237 237 Updates user's password to new password if token is valid.
--- 238 238 ---
request_serializer: PasswordResetSerializer 239 239 request_serializer: PasswordResetSerializer
""" 240 240 """
data = PasswordResetSerializer(data=request.data) 241 241 data = PasswordResetSerializer(data=request.data)
data.is_valid(raise_exception=True) 242 242 data.is_valid(raise_exception=True)
243 243
user = User.objects.get(id=data['uid'].value) 244 244 user = User.objects.get(id=data['uid'].value)
# Check token validity. 245 245 # Check token validity.
246 246
if default_token_generator.check_token(user, data['token'].value): 247 247 if default_token_generator.check_token(user, data['token'].value):
user.set_password(data['new_password'].value) 248 248 user.set_password(data['new_password'].value)
user.save() 249 249 user.save()