Commit 7aa4b42d3e1ec9fa212d394a48015e797bc3c613

Authored by Andrew Buss
1 parent 4af65c21da
Exists in master

Fixed registration again?

Showing 2 changed files with 35 additions and 9 deletions Inline Diff

flashcards/tests/test_api.py View file @ 7aa4b42
from django.core import mail 1 1 from django.core import mail
from flashcards.models import User 2 2 from flashcards.models import User
from rest_framework.status import HTTP_201_CREATED, HTTP_200_OK, HTTP_401_UNAUTHORIZED 3 3 from rest_framework.status import HTTP_201_CREATED, HTTP_200_OK, HTTP_401_UNAUTHORIZED
from rest_framework.test import APITestCase 4 4 from rest_framework.test import APITestCase
5 5
6 6
class LoginTests(APITestCase): 7 7 class LoginTests(APITestCase):
def setUp(self): 8 8 def setUp(self):
email = "test@flashy.cards" 9 9 email = "test@flashy.cards"
User.objects.create_user(email=email, password="1234") 10 10 User.objects.create_user(email=email, password="1234")
11 11
def test_login(self): 12 12 def test_login(self):
url = '/api/login' 13 13 url = '/api/login'
data = {'email': 'test@flashy.cards', 'password': '1234'} 14 14 data = {'email': 'test@flashy.cards', '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': 'test@flashy.cards', 'password': '54321'} 18 18 data = {'email': 'test@flashy.cards', 'password': '54321'}
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': 'none@flashy.cards', 'password': '54321'} 22 22 data = {'email': 'none@flashy.cards', 'password': '54321'}
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': '54321'} 26 26 data = {'password': '54321'}
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@flashy.cards'} 30 30 data = {'email': 'none@flashy.cards'}
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
34 34
class RegistrationTest(APITestCase): 35 35 class RegistrationTest(APITestCase):
def test_create_account(self): 36 36 def test_create_account(self):
url = '/api/users/me' 37 37 url = '/api/users/me'
38
39 # missing password
40 data = {'email': 'none@none.com'}
41 response = self.client.post(url, data, format='json')
42 self.assertContains(response, 'password', status_code=400)
43
44 # missing email
45 data = {'password': '1234'}
46 response = self.client.post(url, data, format='json')
47 self.assertContains(response, 'email', status_code=400)
48
49 # create a user
data = {'email': 'none@none.com', 'password': '1234'} 38 50 data = {'email': 'none@none.com', 'password': '1234'}
response = self.client.post(url, data, format='json') 39 51 response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, HTTP_201_CREATED) 40 52 self.assertEqual(response.status_code, HTTP_201_CREATED)
41 53
54 # user should not be confirmed
user = User.objects.get(email="none@none.com") 42 55 user = User.objects.get(email="none@none.com")
56 self.assertFalse(user.is_confirmed)
57
58 # check that the confirmation key was sent
self.assertEqual(len(mail.outbox), 1) 43 59 self.assertEqual(len(mail.outbox), 1)
self.assertIn(user.confirmation_key, mail.outbox[0].body) 44 60 self.assertIn(user.confirmation_key, mail.outbox[0].body)
45 61
62 # log the user out
63 response = self.client.post('/api/logout', format='json')
64 self.assertEqual(response.status_code, 204)
65
66 # log the user in with their registered credentials
data = {'email': 'none@none.com', 'password': '1234'} 46 67 data = {'email': 'none@none.com', 'password': '1234'}
response = self.client.post('/api/login', data, format='json') 47 68 response = self.client.post('/api/login', data, format='json')
self.assertEqual(response.status_code, HTTP_200_OK) 48 69 self.assertEqual(response.status_code, HTTP_200_OK)
49 70
data = {'email': 'none@none.com'} 50 71 # try activating with an invalid key
response = self.client.post(url, data, format='json') 51 72 response = self.client.patch(url, {'confirmation_key': 'NOT A KEY'})
self.assertContains(response, 'password', status_code=400) 52 73 self.assertContains(response, 'confirmation_key is invalid', status_code=400)
53 74
data = {'password': '1234'} 54 75 # try activating with the valid key
response = self.client.post(url, data, format='json') 55 76 response = self.client.patch(url, {'confirmation_key': user.confirmation_key})
self.assertContains(response, 'email', status_code=400) 56 77 self.assertTrue(response.data['is_confirmed'])
57 78
58 79
class ProfileViewTest(APITestCase): 59 80 class ProfileViewTest(APITestCase):
def setUp(self): 60 81 def setUp(self):
email = "profileviewtest@flashy.cards" 61 82 email = "profileviewtest@flashy.cards"
flashcards/views.py View file @ 7aa4b42
from flashcards.api import StandardResultsSetPagination 1 1 from flashcards.api import StandardResultsSetPagination
from flashcards.models import Section, User 2 2 from flashcards.models import Section, User
from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \ 3 3 from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \
PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer 4 4 PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer
from rest_framework.viewsets import ReadOnlyModelViewSet 5 5 from rest_framework.viewsets import ReadOnlyModelViewSet
from django.core.mail import send_mail 6 6 from django.core.mail import send_mail
from django.contrib.auth import authenticate, login, logout 7 7 from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.tokens import default_token_generator 8 8 from django.contrib.auth.tokens import default_token_generator
from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED, HTTP_201_CREATED 9 9 from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED, HTTP_201_CREATED
from rest_framework.views import APIView 10 10 from rest_framework.views import APIView
from rest_framework.response import Response 11 11 from rest_framework.response import Response
from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError 12 12 from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError
13 from simple_email_confirmation import EmailAddress
13 14
14 15
class SectionViewSet(ReadOnlyModelViewSet): 15 16 class SectionViewSet(ReadOnlyModelViewSet):
queryset = Section.objects.all() 16 17 queryset = Section.objects.all()
serializer_class = SectionSerializer 17 18 serializer_class = SectionSerializer
pagination_class = StandardResultsSetPagination 18 19 pagination_class = StandardResultsSetPagination
19 20
20 21
class UserDetail(APIView): 21 22 class UserDetail(APIView):
def patch(self, request, format=None): 22 23 def patch(self, request, format=None):
""" 23 24 """
Updates the user's password, or verifies their email address 24 25 Updates the user's password, or verifies their email address
--- 25 26 ---
request_serializer: UserUpdateSerializer 26 27 request_serializer: UserUpdateSerializer
response_serializer: UserSerializer 27 28 response_serializer: UserSerializer
""" 28 29 """
data = UserUpdateSerializer(data=request.data, context={'user': request.user}) 29 30 data = UserUpdateSerializer(data=request.data, context={'user': request.user})
data.is_valid(raise_exception=True) 30 31 data.is_valid(raise_exception=True)
32 data = data.validated_data
31 33
if 'new_password' in data: 32 34 if 'new_password' in data:
if not request.user.check_password(data['old_password']): 33 35 if not request.user.check_password(data['old_password']):
raise ValidationError('old_password is incorrect') 34 36 raise ValidationError('old_password is incorrect')
request.user.set_password(request.data['new_password']) 35 37 request.user.set_password(data['new_password'])
request.user.save() 36 38 request.user.save()
37 39
if 'confirmation_key' in data and not request.user.confirm_email(data['confirmation_key']): 38 40 if 'confirmation_key' in data:
raise ValidationError('confirmation_key is invalid') 39 41 try:
42 request.user.confirm_email(data['confirmation_key'])
43 except EmailAddress.DoesNotExist:
44 raise ValidationError('confirmation_key is invalid')
40 45
return Response(UserSerializer(request.user).data) 41 46 return Response(UserSerializer(request.user).data)
42 47
def get(self, request, format=None): 43 48 def get(self, request, format=None):
""" 44 49 """
Return data about the user 45 50 Return data about the user
--- 46 51 ---
response_serializer: UserSerializer 47 52 response_serializer: UserSerializer
""" 48 53 """
if not request.user.is_active: return Response(status=HTTP_401_UNAUTHORIZED) 49 54 if not request.user.is_active: return Response(status=HTTP_401_UNAUTHORIZED)
serializer = UserSerializer(request.user) 50 55 serializer = UserSerializer(request.user)
return Response(serializer.data) 51 56 return Response(serializer.data)
52 57
def post(self, request, format=None): 53 58 def post(self, request, format=None):
""" 54 59 """
Register a new user 55 60 Register a new user
--- 56 61 ---
request_serializer: EmailPasswordSerializer 57 62 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 58 63 response_serializer: UserSerializer
""" 59 64 """
data = RegistrationSerializer(data=request.data) 60 65 data = RegistrationSerializer(data=request.data)
data.is_valid(raise_exception=True) 61 66 data.is_valid(raise_exception=True)
62 67
User.objects.create_user(**data.validated_data) 63 68 User.objects.create_user(**data.validated_data)
user = authenticate(**data.validated_data) 64 69 user = authenticate(**data.validated_data)
login(request, user) 65 70 login(request, user)
66 71
body = ''' 67 72 body = '''
Visit the following link to confirm your email address: 68 73 Visit the following link to confirm your email address:
https://flashy.cards/app/verify_email/%s 69 74 https://flashy.cards/app/verify_email/%s
70 75
If you did not register for Flashy, no action is required. 71 76 If you did not register for Flashy, no action is required.
''' 72 77 '''
73 78
assert send_mail("Flashy email verification", 74 79 assert send_mail("Flashy email verification",
body % user.confirmation_key, 75 80 body % user.confirmation_key,
"noreply@flashy.cards", 76 81 "noreply@flashy.cards",
[user.email]) 77 82 [user.email])
78 83
return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED) 79 84 return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED)
80 85
def delete(self, request): 81 86 def delete(self, request):
""" 82 87 """
Irrevocably delete the user and their data 83 88 Irrevocably delete the user and their data
84 89
Yes, really 85 90 Yes, really
""" 86 91 """
request.user.delete() 87 92 request.user.delete()
return Response(status=HTTP_204_NO_CONTENT) 88 93 return Response(status=HTTP_204_NO_CONTENT)
89 94
90 95
class UserLogin(APIView): 91 96 class UserLogin(APIView):
def post(self, request): 92 97 def post(self, request):
""" 93 98 """
Authenticates user and returns user data if valid. 94 99 Authenticates user and returns user data if valid.
--- 95 100 ---
request_serializer: EmailPasswordSerializer 96 101 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 97 102 response_serializer: UserSerializer
""" 98 103 """
99 104
data = EmailPasswordSerializer(data=request.data) 100 105 data = EmailPasswordSerializer(data=request.data)
data.is_valid(raise_exception=True) 101 106 data.is_valid(raise_exception=True)
user = authenticate(**data.validated_data) 102 107 user = authenticate(**data.validated_data)
103 108
if user is None: 104 109 if user is None:
raise AuthenticationFailed('Invalid email or password') 105 110 raise AuthenticationFailed('Invalid email or password')
if not user.is_active: 106 111 if not user.is_active:
raise NotAuthenticated('Account is disabled') 107 112 raise NotAuthenticated('Account is disabled')
login(request, user) 108 113 login(request, user)
return Response(UserSerializer(request.user).data) 109 114 return Response(UserSerializer(request.user).data)
110 115
111 116
class UserLogout(APIView): 112 117 class UserLogout(APIView):
def post(self, request, format=None): 113 118 def post(self, request, format=None):
""" 114 119 """
Logs the authenticated user out. 115 120 Logs the authenticated user out.
""" 116 121 """
logout(request) 117 122 logout(request)
return Response(status=HTTP_204_NO_CONTENT) 118 123 return Response(status=HTTP_204_NO_CONTENT)
119 124
120 125
class PasswordReset(APIView): 121 126 class PasswordReset(APIView):
""" 122 127 """
Allows user to reset their password. 123 128 Allows user to reset their password.
""" 124 129 """
125 130
def post(self, request, format=None): 126 131 def post(self, request, format=None):
""" 127 132 """
Send a password reset token/link to the provided email. 128 133 Send a password reset token/link to the provided email.
--- 129 134 ---
request_serializer: PasswordResetRequestSerializer 130 135 request_serializer: PasswordResetRequestSerializer
""" 131 136 """
data = PasswordResetRequestSerializer(data=request.data) 132 137 data = PasswordResetRequestSerializer(data=request.data)
data.is_valid(raise_exception=True) 133 138 data.is_valid(raise_exception=True)
user = User.objects.get(email=data['email'].value) 134 139 user = User.objects.get(email=data['email'].value)
token = default_token_generator.make_token(user) 135 140 token = default_token_generator.make_token(user)
136 141
body = ''' 137 142 body = '''
Visit the following link to reset your password: 138 143 Visit the following link to reset your password:
https://flashy.cards/app/reset_password/%d/%s 139 144 https://flashy.cards/app/reset_password/%d/%s