Commit 3709ee64556a8c5b57de8ed131d90832f2a75163

Authored by Laura Hawkins
1 parent 366764be3b
Exists in master

working on deck view

Showing 2 changed files with 13 additions and 5 deletions Inline Diff

flashcards/tests/test_api.py View file @ 3709ee6
from django.core import mail 1 1 from django.core import mail
from flashcards.models import User, Section, Flashcard, WhitelistedAddress 2 2 from flashcards.models import User, Section, Flashcard, WhitelistedAddress
from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK, HTTP_403_FORBIDDEN 3 3 from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK, HTTP_403_FORBIDDEN
from rest_framework.test import APITestCase 4 4 from rest_framework.test import APITestCase
from re import search 5 5 from re import search
from django.utils.timezone import now 6 6 from django.utils.timezone import now
7 7
8 8
class LoginTests(APITestCase): 9 9 class LoginTests(APITestCase):
fixtures = ['testusers'] 10 10 fixtures = ['testusers']
11 11
def test_login(self): 12 12 def test_login(self):
url = '/api/login' 13 13 url = '/api/login'
data = {'email': 'none@none.com', 'password': '1234'} 14 14 data = {'email': 'none@none.com', 'password': '1234'}
response = self.client.post(url, data, format='json') 15 15 response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, HTTP_200_OK) 16 16 self.assertEqual(response.status_code, HTTP_200_OK)
17 17
data = {'email': 'none@none.com', 'password': '4321'} 18 18 data = {'email': 'none@none.com', 'password': '4321'}
response = self.client.post(url, data, format='json') 19 19 response = self.client.post(url, data, format='json')
self.assertContains(response, 'Invalid email or password', status_code=403) 20 20 self.assertContains(response, 'Invalid email or password', status_code=403)
21 21
data = {'email': 'bad@none.com', 'password': '1234'} 22 22 data = {'email': 'bad@none.com', 'password': '1234'}
response = self.client.post(url, data, format='json') 23 23 response = self.client.post(url, data, format='json')
self.assertContains(response, 'Invalid email or password', status_code=403) 24 24 self.assertContains(response, 'Invalid email or password', status_code=403)
25 25
data = {'password': '4321'} 26 26 data = {'password': '4321'}
response = self.client.post(url, data, format='json') 27 27 response = self.client.post(url, data, format='json')
self.assertContains(response, 'email', status_code=400) 28 28 self.assertContains(response, 'email', status_code=400)
29 29
data = {'email': 'none@none.com'} 30 30 data = {'email': 'none@none.com'}
response = self.client.post(url, data, format='json') 31 31 response = self.client.post(url, data, format='json')
self.assertContains(response, 'password', status_code=400) 32 32 self.assertContains(response, 'password', status_code=400)
33 33
user = User.objects.get(email="none@none.com") 34 34 user = User.objects.get(email="none@none.com")
user.is_active = False 35 35 user.is_active = False
user.save() 36 36 user.save()
37 37
data = {'email': 'none@none.com', 'password': '1234'} 38 38 data = {'email': 'none@none.com', 'password': '1234'}
response = self.client.post(url, data, format='json') 39 39 response = self.client.post(url, data, format='json')
self.assertContains(response, 'Account is disabled', status_code=403) 40 40 self.assertContains(response, 'Account is disabled', status_code=403)
41 41
def test_logout(self): 42 42 def test_logout(self):
self.client.login(email='none@none.com', password='1234') 43 43 self.client.login(email='none@none.com', password='1234')
response = self.client.post('/api/logout') 44 44 response = self.client.post('/api/logout')
self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) 45 45 self.assertEqual(response.status_code, HTTP_204_NO_CONTENT)
46 46
# since we're not logged in, we should get a 403 response 47 47 # since we're not logged in, we should get a 403 response
response = self.client.get('/api/me', format='json') 48 48 response = self.client.get('/api/me', format='json')
self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) 49 49 self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)
50 50
51 51
class PasswordResetTest(APITestCase): 52 52 class PasswordResetTest(APITestCase):
fixtures = ['testusers'] 53 53 fixtures = ['testusers']
54 54
def test_reset_password(self): 55 55 def test_reset_password(self):
# submit the request to reset the password 56 56 # submit the request to reset the password
url = '/api/request_password_reset' 57 57 url = '/api/request_password_reset'
post_data = {'email': 'none@none.com'} 58 58 post_data = {'email': 'none@none.com'}
self.client.post(url, post_data, format='json') 59 59 self.client.post(url, post_data, format='json')
self.assertEqual(len(mail.outbox), 1) 60 60 self.assertEqual(len(mail.outbox), 1)
self.assertIn('reset your password', mail.outbox[0].body) 61 61 self.assertIn('reset your password', mail.outbox[0].body)
62 62
# capture the reset token from the email 63 63 # capture the reset token from the email
capture = search('https://flashy.cards/app/reset_password/(\d+)/(.*)', 64 64 capture = search('https://flashy.cards/app/reset_password/(\d+)/(.*)',
mail.outbox[0].body) 65 65 mail.outbox[0].body)
patch_data = {'new_password': '4321'} 66 66 patch_data = {'new_password': '4321'}
patch_data['uid'] = capture.group(1) 67 67 patch_data['uid'] = capture.group(1)
reset_token = capture.group(2) 68 68 reset_token = capture.group(2)
69 69
# try to reset the password with the wrong reset token 70 70 # try to reset the password with the wrong reset token
patch_data['token'] = 'wrong_token' 71 71 patch_data['token'] = 'wrong_token'
url = '/api/reset_password' 72 72 url = '/api/reset_password'
response = self.client.post(url, patch_data, format='json') 73 73 response = self.client.post(url, patch_data, format='json')
self.assertContains(response, 'Could not verify reset token', status_code=400) 74 74 self.assertContains(response, 'Could not verify reset token', status_code=400)
75 75
# try to reset the password with the correct token 76 76 # try to reset the password with the correct token
patch_data['token'] = reset_token 77 77 patch_data['token'] = reset_token
response = self.client.post(url, patch_data, format='json') 78 78 response = self.client.post(url, patch_data, format='json')
self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) 79 79 self.assertEqual(response.status_code, HTTP_204_NO_CONTENT)
user = User.objects.get(id=patch_data['uid']) 80 80 user = User.objects.get(id=patch_data['uid'])
assert user.check_password(patch_data['new_password']) 81 81 assert user.check_password(patch_data['new_password'])
82 82
83 83
class RegistrationTest(APITestCase): 84 84 class RegistrationTest(APITestCase):
def test_create_account(self): 85 85 def test_create_account(self):
url = '/api/register' 86 86 url = '/api/register'
87 87
# missing password 88 88 # missing password
data = {'email': 'none@none.com'} 89 89 data = {'email': 'none@none.com'}
response = self.client.post(url, data, format='json') 90 90 response = self.client.post(url, data, format='json')
self.assertContains(response, 'password', status_code=400) 91 91 self.assertContains(response, 'password', status_code=400)
92 92
# missing email 93 93 # missing email
data = {'password': '1234'} 94 94 data = {'password': '1234'}
response = self.client.post(url, data, format='json') 95 95 response = self.client.post(url, data, format='json')
self.assertContains(response, 'email', status_code=400) 96 96 self.assertContains(response, 'email', status_code=400)
97 97
# create a user 98 98 # create a user
data = {'email': 'none@none.com', 'password': '1234'} 99 99 data = {'email': 'none@none.com', 'password': '1234'}
response = self.client.post(url, data, format='json') 100 100 response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, HTTP_201_CREATED) 101 101 self.assertEqual(response.status_code, HTTP_201_CREATED)
102 102
# user should not be confirmed 103 103 # user should not be confirmed
user = User.objects.get(email="none@none.com") 104 104 user = User.objects.get(email="none@none.com")
self.assertFalse(user.is_confirmed) 105 105 self.assertFalse(user.is_confirmed)
106 106
# check that the confirmation key was sent 107 107 # check that the confirmation key was sent
self.assertEqual(len(mail.outbox), 1) 108 108 self.assertEqual(len(mail.outbox), 1)
self.assertIn(user.confirmation_key, mail.outbox[0].body) 109 109 self.assertIn(user.confirmation_key, mail.outbox[0].body)
110 110
# log the user out 111 111 # log the user out
self.client.logout() 112 112 self.client.logout()
113 113
# log the user in with their registered credentials 114 114 # log the user in with their registered credentials
self.client.login(email='none@none.com', password='1234') 115 115 self.client.login(email='none@none.com', password='1234')
116 116
# try activating with an invalid key 117 117 # try activating with an invalid key
118 118
url = '/api/me' 119 119 url = '/api/me'
response = self.client.patch(url, {'confirmation_key': 'NOT A KEY'}) 120 120 response = self.client.patch(url, {'confirmation_key': 'NOT A KEY'})
self.assertContains(response, 'confirmation_key is invalid', status_code=400) 121 121 self.assertContains(response, 'confirmation_key is invalid', status_code=400)
122 122
# try activating with the valid key 123 123 # try activating with the valid key
response = self.client.patch(url, {'confirmation_key': user.confirmation_key}) 124 124 response = self.client.patch(url, {'confirmation_key': user.confirmation_key})
self.assertTrue(response.data['is_confirmed']) 125 125 self.assertTrue(response.data['is_confirmed'])
126 126
127 127
class ProfileViewTest(APITestCase): 128 128 class ProfileViewTest(APITestCase):
fixtures = ['testusers'] 129 129 fixtures = ['testusers']
130 130
def test_get_me(self): 131 131 def test_get_me(self):
url = '/api/me' 132 132 url = '/api/me'
response = self.client.get(url, format='json') 133 133 response = self.client.get(url, format='json')
# since we're not logged in, we shouldn't be able to see this 134 134 # since we're not logged in, we shouldn't be able to see this
self.assertEqual(response.status_code, 403) 135 135 self.assertEqual(response.status_code, 403)
136 136
self.client.login(email='none@none.com', password='1234') 137 137 self.client.login(email='none@none.com', password='1234')
response = self.client.get(url, format='json') 138 138 response = self.client.get(url, format='json')
self.assertEqual(response.status_code, HTTP_200_OK) 139 139 self.assertEqual(response.status_code, HTTP_200_OK)
140 140
141 141
class PasswordChangeTest(APITestCase): 142 142 class PasswordChangeTest(APITestCase):
fixtures = ['testusers'] 143 143 fixtures = ['testusers']
144 144
def test_change_password(self): 145 145 def test_change_password(self):
url = '/api/me' 146 146 url = '/api/me'
user = User.objects.get(email='none@none.com') 147 147 user = User.objects.get(email='none@none.com')
self.assertTrue(user.check_password('1234')) 148 148 self.assertTrue(user.check_password('1234'))
149 149
response = self.client.patch(url, {'new_password': '4321', 'old_password': '1234'}, format='json') 150 150 response = self.client.patch(url, {'new_password': '4321', 'old_password': '1234'}, format='json')
self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) 151 151 self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)
152 152
self.client.login(email='none@none.com', password='1234') 153 153 self.client.login(email='none@none.com', password='1234')
response = self.client.patch(url, {'new_password': '4321'}, format='json') 154 154 response = self.client.patch(url, {'new_password': '4321'}, format='json')
self.assertContains(response, 'old_password is required', status_code=400) 155 155 self.assertContains(response, 'old_password is required', status_code=400)
156 156
response = self.client.patch(url, {'new_password': '4321', 'old_password': '4321'}, format='json') 157 157 response = self.client.patch(url, {'new_password': '4321', 'old_password': '4321'}, format='json')
self.assertContains(response, 'old_password is incorrect', status_code=400) 158 158 self.assertContains(response, 'old_password is incorrect', status_code=400)
159 159
response = self.client.patch(url, {'new_password': '4321', 'old_password': '1234'}, format='json') 160 160 response = self.client.patch(url, {'new_password': '4321', 'old_password': '1234'}, format='json')
self.assertEqual(response.status_code, 200) 161 161 self.assertEqual(response.status_code, 200)
user = User.objects.get(email='none@none.com') 162 162 user = User.objects.get(email='none@none.com')
163 163
self.assertFalse(user.check_password('1234')) 164 164 self.assertFalse(user.check_password('1234'))
self.assertTrue(user.check_password('4321')) 165 165 self.assertTrue(user.check_password('4321'))
166 166
167 167
class DeleteUserTest(APITestCase): 168 168 class DeleteUserTest(APITestCase):
fixtures = ['testusers'] 169 169 fixtures = ['testusers']
170 170
def test_delete_user(self): 171 171 def test_delete_user(self):
url = '/api/me' 172 172 url = '/api/me'
user = User.objects.get(email='none@none.com') 173 173 user = User.objects.get(email='none@none.com')
174 174
self.client.login(email='none@none.com', password='1234') 175 175 self.client.login(email='none@none.com', password='1234')
self.client.delete(url) 176 176 self.client.delete(url)
self.assertFalse(User.objects.filter(email='none@none.com').exists()) 177 177 self.assertFalse(User.objects.filter(email='none@none.com').exists())
178 178
179 179
class FlashcardDetailTest(APITestCase): 180 180 class FlashcardDetailTest(APITestCase):
fixtures = ['testusers', 'testsections'] 181 181 fixtures = ['testusers', 'testsections']
182 182
def setUp(self): 183 183 def setUp(self):
section = Section.objects.get(pk=1) 184 184 section = Section.objects.get(pk=1)
user = User.objects.get(email='none@none.com') 185 185 user = User.objects.get(email='none@none.com')
186 186
self.flashcard = Flashcard(text="jason", section=section, material_date=now(), author=user) 187 187 self.flashcard = Flashcard(text="jason", section=section, material_date=now(), author=user)
self.flashcard.save() 188 188 self.flashcard.save()
189 189
def test_get_flashcard(self): 190 190 def test_get_flashcard(self):
self.client.login(email='none@none.com', password='1234') 191 191 self.client.login(email='none@none.com', password='1234')
response = self.client.get("/api/flashcards/%d/" % self.flashcard.id, format="json") 192 192 response = self.client.get("/api/flashcards/%d/" % self.flashcard.id, format="json")
self.assertEqual(response.status_code, HTTP_200_OK) 193 193 self.assertEqual(response.status_code, HTTP_200_OK)
flashcards/views.py View file @ 3709ee6
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 @list_route(methods=['get'], permission_classes=[IsAuthenticated])
def deck(self, request): 85 85 def deck(self, request):
query = request.GET.get('q', None) 86 86 """
87 Gets the contents of a user's deck for a given section.
88 """
89 query = request.GET.get(Flashcard.objects.all(), None)
if not query: return Response('[]') 87 90 if not query: return Response('[]')
qs = Section.search(query.split(' '))[:8] 88 91 qs = Flashcard.objects.all()
serializer = SectionSerializer(qs, many=True) 89 92 qs = qs.filter(userflashcard__user=request.user)
return Response(serializer.data)""" 90 93 serializer = FlashcardSerializer(qs, many=True)
94 return Response(serializer.data)
91 95
92 96
class UserSectionListView(ListAPIView): 93 97 class UserSectionListView(ListAPIView):
serializer_class = SectionSerializer 94 98 serializer_class = SectionSerializer
permission_classes = [IsAuthenticated] 95 99 permission_classes = [IsAuthenticated]
96 100
def get_queryset(self): 97 101 def get_queryset(self):
return self.request.user.sections.all() 98 102 return self.request.user.sections.all()
99 103
def paginate_queryset(self, queryset): return None 100 104 def paginate_queryset(self, queryset): return None
101 105
102 106
class UserDetail(GenericAPIView): 103 107 class UserDetail(GenericAPIView):
serializer_class = UserSerializer 104 108 serializer_class = UserSerializer
permission_classes = [IsAuthenticated] 105 109 permission_classes = [IsAuthenticated]
106 110
def get_queryset(self): 107 111 def get_queryset(self):
return User.objects.all() 108 112 return User.objects.all()
109 113
def patch(self, request, format=None): 110 114 def patch(self, request, format=None):
""" 111 115 """
Updates the user's password, or verifies their email address 112 116 Updates the user's password, or verifies their email address
--- 113 117 ---
request_serializer: UserUpdateSerializer 114 118 request_serializer: UserUpdateSerializer
response_serializer: UserSerializer 115 119 response_serializer: UserSerializer
""" 116 120 """
data = UserUpdateSerializer(data=request.data, context={'user': request.user}) 117 121 data = UserUpdateSerializer(data=request.data, context={'user': request.user})
data.is_valid(raise_exception=True) 118 122 data.is_valid(raise_exception=True)
data = data.validated_data 119 123 data = data.validated_data
120 124
if 'new_password' in data: 121 125 if 'new_password' in data:
if not request.user.check_password(data['old_password']): 122 126 if not request.user.check_password(data['old_password']):
raise ValidationError('old_password is incorrect') 123 127 raise ValidationError('old_password is incorrect')
request.user.set_password(data['new_password']) 124 128 request.user.set_password(data['new_password'])
request.user.save() 125 129 request.user.save()
126 130
if 'confirmation_key' in data: 127 131 if 'confirmation_key' in data:
try: 128 132 try:
request.user.confirm_email(data['confirmation_key']) 129 133 request.user.confirm_email(data['confirmation_key'])
except EmailAddress.DoesNotExist: 130 134 except EmailAddress.DoesNotExist:
raise ValidationError('confirmation_key is invalid') 131 135 raise ValidationError('confirmation_key is invalid')
132 136
return Response(UserSerializer(request.user).data) 133 137 return Response(UserSerializer(request.user).data)
134 138
def get(self, request, format=None): 135 139 def get(self, request, format=None):
""" 136 140 """
Return data about the user 137 141 Return data about the user
--- 138 142 ---
response_serializer: UserSerializer 139 143 response_serializer: UserSerializer
""" 140 144 """
serializer = UserSerializer(request.user, context={'request': request}) 141 145 serializer = UserSerializer(request.user, context={'request': request})
return Response(serializer.data) 142 146 return Response(serializer.data)
143 147
def delete(self, request): 144 148 def delete(self, request):
""" 145 149 """
Irrevocably delete the user and their data 146 150 Irrevocably delete the user and their data
147 151
Yes, really 148 152 Yes, really
""" 149 153 """
request.user.delete() 150 154 request.user.delete()
return Response(status=HTTP_204_NO_CONTENT) 151 155 return Response(status=HTTP_204_NO_CONTENT)
152 156
153 157
@api_view(['POST']) 154 158 @api_view(['POST'])
def register(request, format=None): 155 159 def register(request, format=None):
""" 156 160 """
Register a new user 157 161 Register a new user
--- 158 162 ---
request_serializer: EmailPasswordSerializer 159 163 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 160 164 response_serializer: UserSerializer
""" 161 165 """
data = RegistrationSerializer(data=request.data) 162 166 data = RegistrationSerializer(data=request.data)
data.is_valid(raise_exception=True) 163 167 data.is_valid(raise_exception=True)
164 168
User.objects.create_user(**data.validated_data) 165 169 User.objects.create_user(**data.validated_data)
user = authenticate(**data.validated_data) 166 170 user = authenticate(**data.validated_data)
auth.login(request, user) 167 171 auth.login(request, user)
168 172
body = ''' 169 173 body = '''
Visit the following link to confirm your email address: 170 174 Visit the following link to confirm your email address:
https://flashy.cards/app/verify_email/%s 171 175 https://flashy.cards/app/verify_email/%s
172 176
If you did not register for Flashy, no action is required. 173 177 If you did not register for Flashy, no action is required.
''' 174 178 '''
175 179
assert send_mail("Flashy email verification", 176 180 assert send_mail("Flashy email verification",
body % user.confirmation_key, 177 181 body % user.confirmation_key,
"noreply@flashy.cards", 178 182 "noreply@flashy.cards",
[user.email]) 179 183 [user.email])
180 184
return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED) 181 185 return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED)
182 186
183 187
@api_view(['POST']) 184 188 @api_view(['POST'])
def login(request): 185 189 def login(request):
""" 186 190 """
Authenticates user and returns user data if valid. 187 191 Authenticates user and returns user data if valid.
--- 188 192 ---
request_serializer: EmailPasswordSerializer 189 193 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 190 194 response_serializer: UserSerializer
""" 191 195 """
192 196
data = EmailPasswordSerializer(data=request.data) 193 197 data = EmailPasswordSerializer(data=request.data)
data.is_valid(raise_exception=True) 194 198 data.is_valid(raise_exception=True)
user = authenticate(**data.validated_data) 195 199 user = authenticate(**data.validated_data)
196 200
if user is None: 197 201 if user is None:
raise AuthenticationFailed('Invalid email or password') 198 202 raise AuthenticationFailed('Invalid email or password')
if not user.is_active: 199 203 if not user.is_active:
raise NotAuthenticated('Account is disabled') 200 204 raise NotAuthenticated('Account is disabled')
auth.login(request, user) 201 205 auth.login(request, user)
return Response(UserSerializer(request.user).data) 202 206 return Response(UserSerializer(request.user).data)
203 207
204 208
@api_view(['POST']) 205 209 @api_view(['POST'])
@permission_classes((IsAuthenticated, )) 206 210 @permission_classes((IsAuthenticated, ))
def logout(request, format=None): 207 211 def logout(request, format=None):
""" 208 212 """
Logs the authenticated user out. 209 213 Logs the authenticated user out.
""" 210 214 """
auth.logout(request) 211 215 auth.logout(request)
return Response(status=HTTP_204_NO_CONTENT) 212 216 return Response(status=HTTP_204_NO_CONTENT)
213 217
214 218
@api_view(['POST']) 215 219 @api_view(['POST'])
def request_password_reset(request, format=None): 216 220 def request_password_reset(request, format=None):
""" 217 221 """
Send a password reset token/link to the provided email. 218 222 Send a password reset token/link to the provided email.
--- 219 223 ---
request_serializer: PasswordResetRequestSerializer 220 224 request_serializer: PasswordResetRequestSerializer
""" 221 225 """
data = PasswordResetRequestSerializer(data=request.data) 222 226 data = PasswordResetRequestSerializer(data=request.data)
data.is_valid(raise_exception=True) 223 227 data.is_valid(raise_exception=True)
user = User.objects.get(email=data['email'].value) 224 228 user = User.objects.get(email=data['email'].value)
token = default_token_generator.make_token(user) 225 229 token = default_token_generator.make_token(user)
226 230
body = ''' 227 231 body = '''
Visit the following link to reset your password: 228 232 Visit the following link to reset your password:
https://flashy.cards/app/reset_password/%d/%s 229 233 https://flashy.cards/app/reset_password/%d/%s
230 234
If you did not request a password reset, no action is required. 231 235 If you did not request a password reset, no action is required.
''' 232 236 '''
233 237
send_mail("Flashy password reset", 234 238 send_mail("Flashy password reset",
body % (user.pk, token), 235 239 body % (user.pk, token),
"noreply@flashy.cards", 236 240 "noreply@flashy.cards",
[user.email]) 237 241 [user.email])
238 242
return Response(status=HTTP_204_NO_CONTENT) 239 243 return Response(status=HTTP_204_NO_CONTENT)
240 244
241 245
@api_view(['POST']) 242 246 @api_view(['POST'])
def reset_password(request, format=None): 243 247 def reset_password(request, format=None):
""" 244 248 """
Updates user's password to new password if token is valid. 245 249 Updates user's password to new password if token is valid.
--- 246 250 ---
request_serializer: PasswordResetSerializer 247 251 request_serializer: PasswordResetSerializer
""" 248 252 """
data = PasswordResetSerializer(data=request.data) 249 253 data = PasswordResetSerializer(data=request.data)
data.is_valid(raise_exception=True) 250 254 data.is_valid(raise_exception=True)
251 255
user = User.objects.get(id=data['uid'].value) 252 256 user = User.objects.get(id=data['uid'].value)
# Check token validity. 253 257 # Check token validity.
254 258
if default_token_generator.check_token(user, data['token'].value): 255 259 if default_token_generator.check_token(user, data['token'].value):
user.set_password(data['new_password'].value) 256 260 user.set_password(data['new_password'].value)
user.save() 257 261 user.save()
else: 258 262 else:
raise ValidationError('Could not verify reset token') 259 263 raise ValidationError('Could not verify reset token')
return Response(status=HTTP_204_NO_CONTENT) 260 264 return Response(status=HTTP_204_NO_CONTENT)
261 265