Commit 2a9edd990f102b292ef4fb59c0688f6ed5ab56f5
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 | ] |