Commit 2a9edd990f102b292ef4fb59c0688f6ed5ab56f5

Authored by Chung Wang
1 parent 64ad0b96d5
Exists in master

Flashcard detail view

Showing 5 changed files with 47 additions and 9 deletions Inline Diff

flashcards/serializers.py View file @ 2a9edd9
from flashcards.models import Section, LecturePeriod, User 1 1 from flashcards.models import Section, LecturePeriod, User, Flashcard
from rest_framework import serializers 2 2 from rest_framework import serializers
from rest_framework.fields import EmailField, BooleanField, CharField, IntegerField 3 3 from rest_framework.fields import EmailField, BooleanField, CharField, IntegerField
from rest_framework.relations import HyperlinkedRelatedField 4 4 from rest_framework.relations import HyperlinkedRelatedField
from rest_framework.serializers import HyperlinkedModelSerializer, ModelSerializer, Serializer 5 5 from rest_framework.serializers import HyperlinkedModelSerializer, ModelSerializer, Serializer
from rest_framework.validators import UniqueValidator 6 6 from rest_framework.validators import UniqueValidator
7 7
8 8
class EmailSerializer(Serializer): 9 9 class EmailSerializer(Serializer):
email = EmailField(required=True) 10 10 email = EmailField(required=True)
11 11
12 12
class EmailPasswordSerializer(EmailSerializer): 13 13 class EmailPasswordSerializer(EmailSerializer):
password = CharField(required=True) 14 14 password = CharField(required=True)
15 15
16 16
class RegistrationSerializer(EmailPasswordSerializer): 17 17 class RegistrationSerializer(EmailPasswordSerializer):
email = EmailField(required=True, validators=[UniqueValidator(queryset=User.objects.all())]) 18 18 email = EmailField(required=True, validators=[UniqueValidator(queryset=User.objects.all())])
19 19
20 20
class PasswordResetRequestSerializer(EmailSerializer): 21 21 class PasswordResetRequestSerializer(EmailSerializer):
def validate_email(self, value): 22 22 def validate_email(self, value):
try: 23 23 try:
User.objects.get(email=value) 24 24 User.objects.get(email=value)
return value 25 25 return value
except User.DoesNotExist: 26 26 except User.DoesNotExist:
raise serializers.ValidationError('No user exists with that email') 27 27 raise serializers.ValidationError('No user exists with that email')
28 28
29 29
class PasswordResetSerializer(Serializer): 30 30 class PasswordResetSerializer(Serializer):
new_password = CharField(required=True, allow_blank=False) 31 31 new_password = CharField(required=True, allow_blank=False)
uid = IntegerField(required=True) 32 32 uid = IntegerField(required=True)
token = CharField(required=True) 33 33 token = CharField(required=True)
34 34
def validate_uid(self, value): 35 35 def validate_uid(self, value):
try: 36 36 try:
User.objects.get(id=value) 37 37 User.objects.get(id=value)
return value 38 38 return value
except User.DoesNotExist: 39 39 except User.DoesNotExist:
raise serializers.ValidationError('Could not verify reset token') 40 40 raise serializers.ValidationError('Could not verify reset token')
41 41
class UserUpdateSerializer(Serializer): 42 42 class UserUpdateSerializer(Serializer):
old_password = CharField(required=False) 43 43 old_password = CharField(required=False)
new_password = CharField(required=False, allow_blank=False) 44 44 new_password = CharField(required=False, allow_blank=False)
confirmation_key = CharField(required=False) 45 45 confirmation_key = CharField(required=False)
# reset_token = CharField(required=False) 46 46 # reset_token = CharField(required=False)
47 47
def validate(self, data): 48 48 def validate(self, data):
if 'new_password' in data and 'old_password' not in data: 49 49 if 'new_password' in data and 'old_password' not in data:
raise serializers.ValidationError('old_password is required to set a new_password') 50 50 raise serializers.ValidationError('old_password is required to set a new_password')
return data 51 51 return data
52 52
53 53
class Password(Serializer): 54 54 class Password(Serializer):
email = EmailField(required=True) 55 55 email = EmailField(required=True)
password = CharField(required=True) 56 56 password = CharField(required=True)
57 57
58 58
class LecturePeriodSerializer(ModelSerializer): 59 59 class LecturePeriodSerializer(ModelSerializer):
class Meta: 60 60 class Meta:
model = LecturePeriod 61 61 model = LecturePeriod
exclude = 'id', 'section' 62 62 exclude = 'id', 'section'
63 63
64 64
flashcards/tests.py View file @ 2a9edd9
from django.test import TestCase 1 File was deleted
2
flashcards/tests/test_api.py View file @ 2a9edd9
from django.core import mail 1 1 from django.core import mail
from flashcards.models import User 2 2 from flashcards.models import User, Section, Flashcard
from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK, HTTP_401_UNAUTHORIZED 3 3 from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK, HTTP_401_UNAUTHORIZED
from rest_framework.test import APITestCase 4 4 from rest_framework.test import APITestCase
from re import search 5 5 from re import search
6 from django.utils.timezone import now
6 7
7 8
class LoginTests(APITestCase): 8 9 class LoginTests(APITestCase):
def setUp(self): 9 10 def setUp(self):
email = "test@flashy.cards" 10 11 email = "test@flashy.cards"
User.objects.create_user(email=email, password="1234") 11 12 User.objects.create_user(email=email, password="1234")
12 13
def test_login(self): 13 14 def test_login(self):
url = '/api/login' 14 15 url = '/api/login'
data = {'email': 'test@flashy.cards', 'password': '1234'} 15 16 data = {'email': 'test@flashy.cards', 'password': '1234'}
response = self.client.post(url, data, format='json') 16 17 response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, HTTP_200_OK) 17 18 self.assertEqual(response.status_code, HTTP_200_OK)
18 19
data = {'email': 'test@flashy.cards', 'password': '54321'} 19 20 data = {'email': 'test@flashy.cards', 'password': '54321'}
response = self.client.post(url, data, format='json') 20 21 response = self.client.post(url, data, format='json')
self.assertContains(response, 'Invalid email or password', status_code=403) 21 22 self.assertContains(response, 'Invalid email or password', status_code=403)
22 23
data = {'email': 'none@flashy.cards', 'password': '54321'} 23 24 data = {'email': 'none@flashy.cards', 'password': '54321'}
response = self.client.post(url, data, format='json') 24 25 response = self.client.post(url, data, format='json')
self.assertContains(response, 'Invalid email or password', status_code=403) 25 26 self.assertContains(response, 'Invalid email or password', status_code=403)
26 27
data = {'password': '54321'} 27 28 data = {'password': '54321'}
response = self.client.post(url, data, format='json') 28 29 response = self.client.post(url, data, format='json')
self.assertContains(response, 'email', status_code=400) 29 30 self.assertContains(response, 'email', status_code=400)
30 31
data = {'email': 'none@flashy.cards'} 31 32 data = {'email': 'none@flashy.cards'}
response = self.client.post(url, data, format='json') 32 33 response = self.client.post(url, data, format='json')
self.assertContains(response, 'password', status_code=400) 33 34 self.assertContains(response, 'password', status_code=400)
34 35
user = User.objects.get(email="test@flashy.cards") 35 36 user = User.objects.get(email="test@flashy.cards")
user.is_active = False 36 37 user.is_active = False
user.save() 37 38 user.save()
38 39
data = {'email': 'test@flashy.cards', 'password': '1234'} 39 40 data = {'email': 'test@flashy.cards', 'password': '1234'}
response = self.client.post(url, data, format='json') 40 41 response = self.client.post(url, data, format='json')
self.assertContains(response, 'Account is disabled', status_code=403) 41 42 self.assertContains(response, 'Account is disabled', status_code=403)
42 43
def test_logout(self): 43 44 def test_logout(self):
url = '/api/login' 44 45 url = '/api/login'
data = {'email': 'test@flashy.cards', 'password': '1234'} 45 46 data = {'email': 'test@flashy.cards', 'password': '1234'}
response = self.client.post(url, data, format='json') 46 47 response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, HTTP_200_OK) 47 48 self.assertEqual(response.status_code, HTTP_200_OK)
48 49
p = self.client.post('/api/logout') 49 50 p = self.client.post('/api/logout')
self.assertEqual(p.status_code, HTTP_204_NO_CONTENT) 50 51 self.assertEqual(p.status_code, HTTP_204_NO_CONTENT)
response = self.client.get('/api/users/me', format='json') 51 52 response = self.client.get('/api/users/me', format='json')
52 53
# since we're not logged in, we shouldn't be able to see this 53 54 # since we're not logged in, we shouldn't be able to see this
self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) 54 55 self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED)
55 56
class PasswordResetTest(APITestCase): 56 57 class PasswordResetTest(APITestCase):
def setUp(self): 57 58 def setUp(self):
# create a user to test things with 58 59 # create a user to test things with
email = "test@flashy.cards" 59 60 email = "test@flashy.cards"
User.objects.create_user(email=email, password="12345") 60 61 User.objects.create_user(email=email, password="12345")
61 62
def test_reset_password(self): 62 63 def test_reset_password(self):
# submit the request to reset the password 63 64 # submit the request to reset the password
url = '/api/reset_password' 64 65 url = '/api/reset_password'
post_data = {'email': 'test@flashy.cards'} 65 66 post_data = {'email': 'test@flashy.cards'}
self.client.post(url, post_data, format='json') 66 67 self.client.post(url, post_data, format='json')
self.assertEqual(len(mail.outbox), 1) 67 68 self.assertEqual(len(mail.outbox), 1)
self.assertIn('reset your password', mail.outbox[0].body) 68 69 self.assertIn('reset your password', mail.outbox[0].body)
69 70
# capture the reset token from the email 70 71 # capture the reset token from the email
capture = search('https://flashy.cards/app/reset_password/(\d+)/(.*)', 71 72 capture = search('https://flashy.cards/app/reset_password/(\d+)/(.*)',
mail.outbox[0].body) 72 73 mail.outbox[0].body)
patch_data = {'new_password': '54321'} 73 74 patch_data = {'new_password': '54321'}
patch_data['uid'] = capture.group(1) 74 75 patch_data['uid'] = capture.group(1)
reset_token = capture.group(2) 75 76 reset_token = capture.group(2)
76 77
# try to reset the password with the wrong reset token 77 78 # try to reset the password with the wrong reset token
patch_data['token'] = 'wrong_token' 78 79 patch_data['token'] = 'wrong_token'
response = self.client.patch(url, patch_data, format='json') 79 80 response = self.client.patch(url, patch_data, format='json')
self.assertContains(response, 'Could not verify reset token', status_code=400) 80 81 self.assertContains(response, 'Could not verify reset token', status_code=400)
81 82
# try to reset the password with the correct token 82 83 # try to reset the password with the correct token
patch_data['token'] = reset_token 83 84 patch_data['token'] = reset_token
response = self.client.patch(url, patch_data, format='json') 84 85 response = self.client.patch(url, patch_data, format='json')
self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) 85 86 self.assertEqual(response.status_code, HTTP_204_NO_CONTENT)
user = User.objects.get(id=patch_data['uid']) 86 87 user = User.objects.get(id=patch_data['uid'])
assert user.check_password(patch_data['new_password']) 87 88 assert user.check_password(patch_data['new_password'])
88 89
89 90
class RegistrationTest(APITestCase): 90 91 class RegistrationTest(APITestCase):
def test_create_account(self): 91 92 def test_create_account(self):
url = '/api/users/me' 92 93 url = '/api/users/me'
93 94
# missing password 94 95 # missing password
data = {'email': 'none@none.com'} 95 96 data = {'email': 'none@none.com'}
response = self.client.post(url, data, format='json') 96 97 response = self.client.post(url, data, format='json')
self.assertContains(response, 'password', status_code=400) 97 98 self.assertContains(response, 'password', status_code=400)
98 99
# missing email 99 100 # missing email
data = {'password': '1234'} 100 101 data = {'password': '1234'}
response = self.client.post(url, data, format='json') 101 102 response = self.client.post(url, data, format='json')
self.assertContains(response, 'email', status_code=400) 102 103 self.assertContains(response, 'email', status_code=400)
103 104
# create a user 104 105 # create a user
data = {'email': 'none@none.com', 'password': '1234'} 105 106 data = {'email': 'none@none.com', 'password': '1234'}
response = self.client.post(url, data, format='json') 106 107 response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, HTTP_201_CREATED) 107 108 self.assertEqual(response.status_code, HTTP_201_CREATED)
108 109
# user should not be confirmed 109 110 # user should not be confirmed
user = User.objects.get(email="none@none.com") 110 111 user = User.objects.get(email="none@none.com")
self.assertFalse(user.is_confirmed) 111 112 self.assertFalse(user.is_confirmed)
112 113
# check that the confirmation key was sent 113 114 # check that the confirmation key was sent
self.assertEqual(len(mail.outbox), 1) 114 115 self.assertEqual(len(mail.outbox), 1)
self.assertIn(user.confirmation_key, mail.outbox[0].body) 115 116 self.assertIn(user.confirmation_key, mail.outbox[0].body)
116 117
# log the user out 117 118 # log the user out
self.client.logout() 118 119 self.client.logout()
119 120
# log the user in with their registered credentials 120 121 # log the user in with their registered credentials
self.client.login(email='none@none.com', password='1234') 121 122 self.client.login(email='none@none.com', password='1234')
122 123
# try activating with an invalid key 123 124 # try activating with an invalid key
response = self.client.patch(url, {'confirmation_key': 'NOT A KEY'}) 124 125 response = self.client.patch(url, {'confirmation_key': 'NOT A KEY'})
self.assertContains(response, 'confirmation_key is invalid', status_code=400) 125 126 self.assertContains(response, 'confirmation_key is invalid', status_code=400)
126 127
# try activating with the valid key 127 128 # try activating with the valid key
response = self.client.patch(url, {'confirmation_key': user.confirmation_key}) 128 129 response = self.client.patch(url, {'confirmation_key': user.confirmation_key})
self.assertTrue(response.data['is_confirmed']) 129 130 self.assertTrue(response.data['is_confirmed'])
130 131
131 132
class ProfileViewTest(APITestCase): 132 133 class ProfileViewTest(APITestCase):
def setUp(self): 133 134 def setUp(self):
email = "profileviewtest@flashy.cards" 134 135 email = "profileviewtest@flashy.cards"
User.objects.create_user(email=email, password="1234") 135 136 User.objects.create_user(email=email, password="1234")
136 137
def test_get_me(self): 137 138 def test_get_me(self):
url = '/api/users/me' 138 139 url = '/api/users/me'
response = self.client.get(url, format='json') 139 140 response = self.client.get(url, format='json')
# since we're not logged in, we shouldn't be able to see this 140 141 # since we're not logged in, we shouldn't be able to see this
self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED) 141 142 self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED)
142 143
self.client.login(email='profileviewtest@flashy.cards', password='1234') 143 144 self.client.login(email='profileviewtest@flashy.cards', password='1234')
response = self.client.get(url, format='json') 144 145 response = self.client.get(url, format='json')
self.assertEqual(response.status_code, HTTP_200_OK) 145 146 self.assertEqual(response.status_code, HTTP_200_OK)
146 147
147 148
class PasswordChangeTest(APITestCase): 148 149 class PasswordChangeTest(APITestCase):
def setUp(self): 149 150 def setUp(self):
email = "none@none.com" 150 151 email = "none@none.com"
User.objects.create_user(email=email, password="1234") 151 152 User.objects.create_user(email=email, password="1234")
152 153
def test_change_password(self): 153 154 def test_change_password(self):
url = '/api/users/me' 154 155 url = '/api/users/me'
user = User.objects.get(email='none@none.com') 155 156 user = User.objects.get(email='none@none.com')
self.assertTrue(user.check_password('1234')) 156 157 self.assertTrue(user.check_password('1234'))
157 158
response = self.client.patch(url, {'new_password': '4321', 'old_password': '1234'}, format='json') 158 159 response = self.client.patch(url, {'new_password': '4321', 'old_password': '1234'}, format='json')
self.assertContains(response, 'You must be logged in to change your password', status_code=403) 159 160 self.assertContains(response, 'You must be logged in to change your password', status_code=403)
160 161
self.client.login(email='none@none.com', password='1234') 161 162 self.client.login(email='none@none.com', password='1234')
response = self.client.patch(url, {'new_password': '4321'}, format='json') 162 163 response = self.client.patch(url, {'new_password': '4321'}, format='json')
self.assertContains(response, 'old_password is required', status_code=400) 163 164 self.assertContains(response, 'old_password is required', status_code=400)
164 165
response = self.client.patch(url, {'new_password': '4321', 'old_password': '4321'}, format='json') 165 166 response = self.client.patch(url, {'new_password': '4321', 'old_password': '4321'}, format='json')
flashcards/views.py View file @ 2a9edd9
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, Flashcard
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, FlashcardSerializer
from rest_framework.permissions import IsAuthenticated 5 5 from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet 6 6 from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet
from django.core.mail import send_mail 7 7 from django.core.mail import send_mail
from django.contrib.auth import authenticate, login, logout 8 8 from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.tokens import default_token_generator 9 9 from django.contrib.auth.tokens import default_token_generator
from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED, HTTP_201_CREATED 10 10 from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED, HTTP_201_CREATED
from rest_framework.views import APIView 11 11 from rest_framework.views import APIView
from rest_framework.response import Response 12 12 from rest_framework.response import Response
13 from rest_framework.generics import RetrieveAPIView
from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError 13 14 from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError
from simple_email_confirmation import EmailAddress 14 15 from simple_email_confirmation import EmailAddress
from rest_framework import filters 15 16 from rest_framework import filters
16 17
class SectionViewSet(ReadOnlyModelViewSet): 17 18 class SectionViewSet(ReadOnlyModelViewSet):
queryset = Section.objects.all() 18 19 queryset = Section.objects.all()
serializer_class = SectionSerializer 19 20 serializer_class = SectionSerializer
pagination_class = StandardResultsSetPagination 20 21 pagination_class = StandardResultsSetPagination
21 22
class UserSectionViewSet(ModelViewSet): 22 23 class UserSectionViewSet(ModelViewSet):
serializer_class = SectionSerializer 23 24 serializer_class = SectionSerializer
permission_classes = [IsAuthenticated] 24 25 permission_classes = [IsAuthenticated]
def get_queryset(self): 25 26 def get_queryset(self):
return self.request.user.sections.all() 26 27 return self.request.user.sections.all()
27 28
def paginate_queryset(self, queryset): return None 28 29 def paginate_queryset(self, queryset): return None
29 30
class UserDetail(APIView): 30 31 class UserDetail(APIView):
def patch(self, request, format=None): 31 32 def patch(self, request, format=None):
""" 32 33 """
Updates the user's password, or verifies their email address 33 34 Updates the user's password, or verifies their email address
--- 34 35 ---
request_serializer: UserUpdateSerializer 35 36 request_serializer: UserUpdateSerializer
response_serializer: UserSerializer 36 37 response_serializer: UserSerializer
""" 37 38 """
data = UserUpdateSerializer(data=request.data, context={'user': request.user}) 38 39 data = UserUpdateSerializer(data=request.data, context={'user': request.user})
data.is_valid(raise_exception=True) 39 40 data.is_valid(raise_exception=True)
data = data.validated_data 40 41 data = data.validated_data
41 42
if 'new_password' in data: 42 43 if 'new_password' in data:
if not request.user.is_authenticated(): 43 44 if not request.user.is_authenticated():
raise NotAuthenticated('You must be logged in to change your password') 44 45 raise NotAuthenticated('You must be logged in to change your password')
if not request.user.check_password(data['old_password']): 45 46 if not request.user.check_password(data['old_password']):
raise ValidationError('old_password is incorrect') 46 47 raise ValidationError('old_password is incorrect')
request.user.set_password(data['new_password']) 47 48 request.user.set_password(data['new_password'])
request.user.save() 48 49 request.user.save()
49 50
if 'confirmation_key' in data: 50 51 if 'confirmation_key' in data:
try: 51 52 try:
request.user.confirm_email(data['confirmation_key']) 52 53 request.user.confirm_email(data['confirmation_key'])
except EmailAddress.DoesNotExist: 53 54 except EmailAddress.DoesNotExist:
raise ValidationError('confirmation_key is invalid') 54 55 raise ValidationError('confirmation_key is invalid')
55 56
return Response(UserSerializer(request.user).data) 56 57 return Response(UserSerializer(request.user).data)
57 58
def get(self, request, format=None): 58 59 def get(self, request, format=None):
""" 59 60 """
Return data about the user 60 61 Return data about the user
--- 61 62 ---
response_serializer: UserSerializer 62 63 response_serializer: UserSerializer
""" 63 64 """
if not request.user.is_authenticated(): return Response(status=HTTP_401_UNAUTHORIZED) 64 65 if not request.user.is_authenticated(): return Response(status=HTTP_401_UNAUTHORIZED)
serializer = UserSerializer(request.user) 65 66 serializer = UserSerializer(request.user)
return Response(serializer.data) 66 67 return Response(serializer.data)
67 68
def post(self, request, format=None): 68 69 def post(self, request, format=None):
""" 69 70 """
Register a new user 70 71 Register a new user
--- 71 72 ---
request_serializer: EmailPasswordSerializer 72 73 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 73 74 response_serializer: UserSerializer
""" 74 75 """
data = RegistrationSerializer(data=request.data) 75 76 data = RegistrationSerializer(data=request.data)
data.is_valid(raise_exception=True) 76 77 data.is_valid(raise_exception=True)
77 78
User.objects.create_user(**data.validated_data) 78 79 User.objects.create_user(**data.validated_data)
user = authenticate(**data.validated_data) 79 80 user = authenticate(**data.validated_data)
login(request, user) 80 81 login(request, user)
81 82
body = ''' 82 83 body = '''
Visit the following link to confirm your email address: 83 84 Visit the following link to confirm your email address:
https://flashy.cards/app/verify_email/%s 84 85 https://flashy.cards/app/verify_email/%s
85 86
If you did not register for Flashy, no action is required. 86 87 If you did not register for Flashy, no action is required.
''' 87 88 '''
88 89
assert send_mail("Flashy email verification", 89 90 assert send_mail("Flashy email verification",
body % user.confirmation_key, 90 91 body % user.confirmation_key,
"noreply@flashy.cards", 91 92 "noreply@flashy.cards",
[user.email]) 92 93 [user.email])
93 94
return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED) 94 95 return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED)
95 96
def delete(self, request): 96 97 def delete(self, request):
""" 97 98 """
Irrevocably delete the user and their data 98 99 Irrevocably delete the user and their data
99 100
Yes, really 100 101 Yes, really
""" 101 102 """
request.user.delete() 102 103 request.user.delete()
return Response(status=HTTP_204_NO_CONTENT) 103 104 return Response(status=HTTP_204_NO_CONTENT)
104 105
105 106
class UserLogin(APIView): 106 107 class UserLogin(APIView):
def post(self, request): 107 108 def post(self, request):
""" 108 109 """
Authenticates user and returns user data if valid. 109 110 Authenticates user and returns user data if valid.
--- 110 111 ---
request_serializer: EmailPasswordSerializer 111 112 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 112 113 response_serializer: UserSerializer
""" 113 114 """
114 115
data = EmailPasswordSerializer(data=request.data) 115 116 data = EmailPasswordSerializer(data=request.data)
data.is_valid(raise_exception=True) 116 117 data.is_valid(raise_exception=True)
user = authenticate(**data.validated_data) 117 118 user = authenticate(**data.validated_data)
118 119
if user is None: 119 120 if user is None:
raise AuthenticationFailed('Invalid email or password') 120 121 raise AuthenticationFailed('Invalid email or password')
if not user.is_active: 121 122 if not user.is_active:
raise NotAuthenticated('Account is disabled') 122 123 raise NotAuthenticated('Account is disabled')
login(request, user) 123 124 login(request, user)
return Response(UserSerializer(request.user).data) 124 125 return Response(UserSerializer(request.user).data)
125 126
126 127
class UserLogout(APIView): 127 128 class UserLogout(APIView):
permission_classes = (IsAuthenticated,) 128 129 permission_classes = (IsAuthenticated,)
def post(self, request, format=None): 129 130 def post(self, request, format=None):
""" 130 131 """
Logs the authenticated user out. 131 132 Logs the authenticated user out.
""" 132 133 """
logout(request) 133 134 logout(request)
return Response(status=HTTP_204_NO_CONTENT) 134 135 return Response(status=HTTP_204_NO_CONTENT)
135 136
136 137
class PasswordReset(APIView): 137 138 class PasswordReset(APIView):
""" 138 139 """
Allows user to reset their password. 139 140 Allows user to reset their password.
""" 140 141 """
141 142
def post(self, request, format=None): 142 143 def post(self, request, format=None):
""" 143 144 """
Send a password reset token/link to the provided email. 144 145 Send a password reset token/link to the provided email.
--- 145 146 ---
request_serializer: PasswordResetRequestSerializer 146 147 request_serializer: PasswordResetRequestSerializer
""" 147 148 """
data = PasswordResetRequestSerializer(data=request.data) 148 149 data = PasswordResetRequestSerializer(data=request.data)
data.is_valid(raise_exception=True) 149 150 data.is_valid(raise_exception=True)
user = User.objects.get(email=data['email'].value) 150 151 user = User.objects.get(email=data['email'].value)
token = default_token_generator.make_token(user) 151 152 token = default_token_generator.make_token(user)
152 153
body = ''' 153 154 body = '''
Visit the following link to reset your password: 154 155 Visit the following link to reset your password:
https://flashy.cards/app/reset_password/%d/%s 155 156 https://flashy.cards/app/reset_password/%d/%s
156 157
If you did not request a password reset, no action is required. 157 158 If you did not request a password reset, no action is required.
''' 158 159 '''
159 160
send_mail("Flashy password reset", 160 161 send_mail("Flashy password reset",
body % (user.pk, token), 161 162 body % (user.pk, token),
"noreply@flashy.cards", 162 163 "noreply@flashy.cards",
flashy/urls.py View file @ 2a9edd9
from django.conf.urls import include, url 1 1 from django.conf.urls import include, url
from django.contrib import admin 2 2 from django.contrib import admin
from flashcards.views import SectionViewSet, UserDetail, UserLogin, UserLogout, PasswordReset, UserSectionViewSet 3 3 from flashcards.views import SectionViewSet, UserDetail, UserLogin, UserLogout, PasswordReset, UserSectionViewSet, FlashcardDetail
from rest_framework.routers import DefaultRouter 4 4 from rest_framework.routers import DefaultRouter
from flashcards.api import * 5 5 from flashcards.api import *
6 6
router = DefaultRouter() 7 7 router = DefaultRouter()
router.register(r'sections', SectionViewSet) 8 8 router.register(r'sections', SectionViewSet)
9 9
router.register(r'users/me/sections', UserSectionViewSet, base_name = 'usersection') 10 10 router.register(r'users/me/sections', UserSectionViewSet, base_name = 'usersection')
11 11
urlpatterns = [ 12 12 urlpatterns = [
url(r'^api/docs/', include('rest_framework_swagger.urls')), 13 13 url(r'^api/docs/', include('rest_framework_swagger.urls')),
url(r'^api/users/me$', UserDetail.as_view()), 14 14 url(r'^api/users/me$', UserDetail.as_view()),
url(r'^api/login$', UserLogin.as_view()), 15 15 url(r'^api/login$', UserLogin.as_view()),
url(r'^api/logout$', UserLogout.as_view()), 16 16 url(r'^api/logout$', UserLogout.as_view()),
url(r'^api/reset_password$', PasswordReset.as_view()), 17 17 url(r'^api/reset_password$', PasswordReset.as_view()),
url(r'^api/', include(router.urls)), 18 18 url(r'^api/', include(router.urls)),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')), 19 19 url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/', include(admin.site.urls)), 20 20 url(r'^admin/', include(admin.site.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework' 21 21 url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'
)) 22 22 )),
23 url(r'^api/flashcards/(?P<pk>[0-9]+)$', FlashcardDetail.as_view(), name \
24 ="flashcard-detail"),
] 23 25 ]