Commit 7916bfa7a67429425be94adef2c8dc0182133656

Authored by Laura Hawkins
Exists in master

blurp

Showing 6 changed files Inline Diff

Flashy requires Python 2. 1 1 Flashy requires Python 2. Srsly
2
3 All of these commands should be run from this directory (the one containing README.md)
2 4
Install virtualenv before continuing. This is most easily accomplished with: 3 5 Install virtualenv before continuing. This is most easily accomplished with:
4 6
pip install virtualenv 5 7 pip install virtualenv
6 8
Set up the environment by running: 7 9 Set up the environment by running:
8 10
scripts/setup.sh 9 11 scripts/setup.sh
10 12
If you get errors about a module not being found, make sure you are working in the virtualenv. To enter the venv: 11 13 If you get errors about a module not being found, make sure you are working in the virtualenv. To enter the venv:
12 14
. venv/bin/activate 13 15 . venv/bin/activate
14 16
If you still get errors about a module not being found, make sure your virtualenv is up to date. Re-run: 15 17 If you still get errors about a module not being found, make sure your virtualenv is up to date. Re-run:
16 18
flashcards/tests/test_api.py View file @ 7916bfa
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 @ 7916bfa
from django.contrib import auth 1 1 from django.contrib import auth
from django.db.models import Q 2
from flashcards.api import StandardResultsSetPagination 3 2 from flashcards.api import StandardResultsSetPagination
from flashcards.models import Section, User, Flashcard, FlashcardReport, UserFlashcard 4 3 from flashcards.models import Section, User, Flashcard, FlashcardReport, UserFlashcard
from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \ 5 4 from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \
PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer 6 5 PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer
from rest_framework.decorators import detail_route, permission_classes, api_view, list_route 7 6 from rest_framework.decorators import detail_route, permission_classes, api_view, list_route
from rest_framework.generics import ListAPIView, GenericAPIView 8 7 from rest_framework.generics import ListAPIView, GenericAPIView
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin 9 8 from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin
from rest_framework.permissions import IsAuthenticated 10 9 from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet 11 10 from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet
from django.core.mail import send_mail 12 11 from django.core.mail import send_mail
from django.contrib.auth import authenticate 13 12 from django.contrib.auth import authenticate
from django.contrib.auth.tokens import default_token_generator 14 13 from django.contrib.auth.tokens import default_token_generator
from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED 15 14 from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED
from rest_framework.response import Response 16 15 from rest_framework.response import Response
from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied 17 16 from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied
from simple_email_confirmation import EmailAddress 18 17 from simple_email_confirmation import EmailAddress
19 18
20 19
class SectionViewSet(ReadOnlyModelViewSet): 21 20 class SectionViewSet(ReadOnlyModelViewSet):
queryset = Section.objects.all() 22 21 queryset = Section.objects.all()
serializer_class = SectionSerializer 23 22 serializer_class = SectionSerializer
pagination_class = StandardResultsSetPagination 24 23 pagination_class = StandardResultsSetPagination
permission_classes = [IsAuthenticated] 25 24 permission_classes = [IsAuthenticated]
26 25
@detail_route(methods=['get'], permission_classes=[IsAuthenticated]) 27 26 @detail_route(methods=['get'], permission_classes=[IsAuthenticated])
def flashcards(self, request, pk): 28 27 def flashcards(self, request, pk):
""" 29 28 """
Gets flashcards for a section, excluding hidden cards. 30 29 Gets flashcards for a section, excluding hidden cards.
Returned in strictly chronological order (material date). 31 30 Returned in strictly chronological order (material date).
""" 32 31 """
flashcards = Flashcard.cards_visible_to(request.user).filter( \ 33 32 flashcards = Flashcard.cards_visible_to(request.user).filter( \
section=self.get_object(), is_hidden=False).all() 34 33 section=self.get_object(), is_hidden=False).all()
35 34
return Response(FlashcardSerializer(flashcards, many=True).data) 36 35 return Response(FlashcardSerializer(flashcards, many=True).data)
37 36
@detail_route(methods=['post'], permission_classes=[IsAuthenticated]) 38 37 @detail_route(methods=['post'], permission_classes=[IsAuthenticated])
def enroll(self, request, pk): 39 38 def enroll(self, request, pk):
""" 40 39 """
Add the current user to a specified section 41 40 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 41 If the class has a whitelist, but the user is not on the whitelist, the request will fail.
--- 43 42 ---
omit_serializer: true 44 43 omit_serializer: true
parameters: 45 44 parameters:
- fake: None 46 45 - fake: None
parameters_strategy: 47 46 parameters_strategy:
form: replace 48 47 form: replace
""" 49 48 """
section = self.get_object() 50 49 section = self.get_object()
if request.user.sections.filter(pk=section.pk).exists(): 51 50 if request.user.sections.filter(pk=section.pk).exists():
raise ValidationError("You are already in this section.") 52 51 raise ValidationError("You are already in this section.")
if section.is_whitelisted and not section.is_user_on_whitelist(request.user): 53 52 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 53 raise PermissionDenied("You must be on the whitelist to add this section.")
request.user.sections.add(section) 55 54 request.user.sections.add(section)
return Response(status=HTTP_204_NO_CONTENT) 56 55 return Response(status=HTTP_204_NO_CONTENT)
57 56
@detail_route(methods=['post'], permission_classes=[IsAuthenticated]) 58 57 @detail_route(methods=['post'], permission_classes=[IsAuthenticated])
def drop(self, request, pk): 59 58 def drop(self, request, pk):
""" 60 59 """
Remove the current user from a specified section 61 60 Remove the current user from a specified section
If the user is not in the class, the request will fail. 62 61 If the user is not in the class, the request will fail.
--- 63 62 ---
omit_serializer: true 64 63 omit_serializer: true
parameters: 65 64 parameters:
- fake: None 66 65 - fake: None
parameters_strategy: 67 66 parameters_strategy:
form: replace 68 67 form: replace
""" 69 68 """
section = self.get_object() 70 69 section = self.get_object()
if not section.user_set.filter(pk=request.user.pk).exists(): 71 70 if not section.user_set.filter(pk=request.user.pk).exists():
raise ValidationError("You are not in the section.") 72 71 raise ValidationError("You are not in the section.")
section.user_set.remove(request.user) 73 72 section.user_set.remove(request.user)
return Response(status=HTTP_204_NO_CONTENT) 74 73 return Response(status=HTTP_204_NO_CONTENT)
75 74
@list_route(methods=['get'], permission_classes=[IsAuthenticated]) 76 75 @list_route(methods=['get'], permission_classes=[IsAuthenticated])
def search(self, request): 77 76 def search(self, request):
query = request.GET.get('q',None) 78 77 query = request.GET.get('q', None)
if not query: return Response('[]') 79 78 if not query: return Response('[]')
qs = Section.search(query.split(' '))[:8] 80 79 qs = Section.search(query.split(' '))[:8]
serializer = SectionSerializer(qs, many=True) 81 80 serializer = SectionSerializer(qs, many=True)
return Response(serializer.data) 82 81 return Response(serializer.data)
83 82
@detail_route(methods=['get'], permission_classes=[IsAuthenticated]) 84 83 @detail_route(methods=['get'], permission_classes=[IsAuthenticated])
def deck(self, request): 85 84 def deck(self, request):
""" 86 85 """
Gets the contents of a user's deck for a given section. 87 86 Gets the contents of a user's deck for a given section.
""" 88 87 """
#query = request.GET.get(Flashcard.objects.all(), None) 89
#if not query: return Response('[]') 90
qs = Flashcard.objects.all() 91 88 qs = Flashcard.objects.all()
qs = qs.filter(userflashcard__user=request.user) 92 89 qs = qs.filter(userflashcard__user=request.user)
serializer = FlashcardSerializer(qs, many=True) 93 90 serializer = FlashcardSerializer(qs, many=True)
return Response(serializer.data) 94 91 return Response(serializer.data)
95 92
96 93
class UserSectionListView(ListAPIView): 97 94 class UserSectionListView(ListAPIView):
serializer_class = SectionSerializer 98 95 serializer_class = SectionSerializer
permission_classes = [IsAuthenticated] 99 96 permission_classes = [IsAuthenticated]
100 97
def get_queryset(self): 101 98 def get_queryset(self):
return self.request.user.sections.all() 102 99 return self.request.user.sections.all()
103 100
def paginate_queryset(self, queryset): return None 104 101 def paginate_queryset(self, queryset): return None
105 102
106 103
class UserDetail(GenericAPIView): 107 104 class UserDetail(GenericAPIView):
serializer_class = UserSerializer 108 105 serializer_class = UserSerializer
permission_classes = [IsAuthenticated] 109 106 permission_classes = [IsAuthenticated]
110 107
def get_queryset(self): 111 108 def get_queryset(self):
return User.objects.all() 112 109 return User.objects.all()
113 110
def patch(self, request, format=None): 114 111 def patch(self, request, format=None):
""" 115 112 """
Updates the user's password, or verifies their email address 116 113 Updates the user's password, or verifies their email address
--- 117 114 ---
request_serializer: UserUpdateSerializer 118 115 request_serializer: UserUpdateSerializer
response_serializer: UserSerializer 119 116 response_serializer: UserSerializer
""" 120 117 """
data = UserUpdateSerializer(data=request.data, context={'user': request.user}) 121 118 data = UserUpdateSerializer(data=request.data, context={'user': request.user})
data.is_valid(raise_exception=True) 122 119 data.is_valid(raise_exception=True)
data = data.validated_data 123 120 data = data.validated_data
124 121
if 'new_password' in data: 125 122 if 'new_password' in data:
if not request.user.check_password(data['old_password']): 126 123 if not request.user.check_password(data['old_password']):
raise ValidationError('old_password is incorrect') 127 124 raise ValidationError('old_password is incorrect')
request.user.set_password(data['new_password']) 128 125 request.user.set_password(data['new_password'])
request.user.save() 129 126 request.user.save()
130 127
if 'confirmation_key' in data: 131 128 if 'confirmation_key' in data:
try: 132 129 try:
request.user.confirm_email(data['confirmation_key']) 133 130 request.user.confirm_email(data['confirmation_key'])
except EmailAddress.DoesNotExist: 134 131 except EmailAddress.DoesNotExist:
raise ValidationError('confirmation_key is invalid') 135 132 raise ValidationError('confirmation_key is invalid')
136 133
return Response(UserSerializer(request.user).data) 137 134 return Response(UserSerializer(request.user).data)
138 135
def get(self, request, format=None): 139 136 def get(self, request, format=None):
""" 140 137 """
Return data about the user 141 138 Return data about the user
--- 142 139 ---
response_serializer: UserSerializer 143 140 response_serializer: UserSerializer
""" 144 141 """
serializer = UserSerializer(request.user, context={'request': request}) 145 142 serializer = UserSerializer(request.user, context={'request': request})
return Response(serializer.data) 146 143 return Response(serializer.data)
147 144
def delete(self, request): 148 145 def delete(self, request):
""" 149 146 """
Irrevocably delete the user and their data 150 147 Irrevocably delete the user and their data
151 148
Yes, really 152 149 Yes, really
""" 153 150 """
request.user.delete() 154 151 request.user.delete()
return Response(status=HTTP_204_NO_CONTENT) 155 152 return Response(status=HTTP_204_NO_CONTENT)
156 153
157 154
@api_view(['POST']) 158 155 @api_view(['POST'])
def register(request, format=None): 159 156 def register(request, format=None):
""" 160 157 """
Register a new user 161 158 Register a new user
--- 162 159 ---
request_serializer: EmailPasswordSerializer 163 160 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 164 161 response_serializer: UserSerializer
""" 165 162 """
data = RegistrationSerializer(data=request.data) 166 163 data = RegistrationSerializer(data=request.data)
data.is_valid(raise_exception=True) 167 164 data.is_valid(raise_exception=True)
168 165
User.objects.create_user(**data.validated_data) 169 166 User.objects.create_user(**data.validated_data)
user = authenticate(**data.validated_data) 170 167 user = authenticate(**data.validated_data)
auth.login(request, user) 171 168 auth.login(request, user)
172 169
body = ''' 173 170 body = '''
Visit the following link to confirm your email address: 174 171 Visit the following link to confirm your email address:
https://flashy.cards/app/verify_email/%s 175 172 https://flashy.cards/app/verify_email/%s
176 173
If you did not register for Flashy, no action is required. 177 174 If you did not register for Flashy, no action is required.
''' 178 175 '''
179 176
assert send_mail("Flashy email verification", 180 177 assert send_mail("Flashy email verification",
body % user.confirmation_key, 181 178 body % user.confirmation_key,
"noreply@flashy.cards", 182 179 "noreply@flashy.cards",
[user.email]) 183 180 [user.email])
184 181
return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED) 185 182 return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED)
186 183
187 184
@api_view(['POST']) 188 185 @api_view(['POST'])
def login(request): 189 186 def login(request):
""" 190 187 """
Authenticates user and returns user data if valid. 191 188 Authenticates user and returns user data if valid.
--- 192 189 ---
request_serializer: EmailPasswordSerializer 193 190 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 194 191 response_serializer: UserSerializer
""" 195 192 """
196 193
data = EmailPasswordSerializer(data=request.data) 197 194 data = EmailPasswordSerializer(data=request.data)
data.is_valid(raise_exception=True) 198 195 data.is_valid(raise_exception=True)
user = authenticate(**data.validated_data) 199 196 user = authenticate(**data.validated_data)
200 197
if user is None: 201 198 if user is None:
raise AuthenticationFailed('Invalid email or password') 202 199 raise AuthenticationFailed('Invalid email or password')
if not user.is_active: 203 200 if not user.is_active:
raise NotAuthenticated('Account is disabled') 204 201 raise NotAuthenticated('Account is disabled')
auth.login(request, user) 205 202 auth.login(request, user)
return Response(UserSerializer(request.user).data) 206 203 return Response(UserSerializer(request.user).data)
207 204
208 205
@api_view(['POST']) 209 206 @api_view(['POST'])
@permission_classes((IsAuthenticated, )) 210 207 @permission_classes((IsAuthenticated, ))
def logout(request, format=None): 211 208 def logout(request, format=None):
""" 212 209 """
Logs the authenticated user out. 213 210 Logs the authenticated user out.
""" 214 211 """
auth.logout(request) 215 212 auth.logout(request)
return Response(status=HTTP_204_NO_CONTENT) 216 213 return Response(status=HTTP_204_NO_CONTENT)
217 214
218 215
@api_view(['POST']) 219 216 @api_view(['POST'])
def request_password_reset(request, format=None): 220 217 def request_password_reset(request, format=None):
""" 221 218 """
Send a password reset token/link to the provided email. 222 219 Send a password reset token/link to the provided email.
--- 223 220 ---
request_serializer: PasswordResetRequestSerializer 224 221 request_serializer: PasswordResetRequestSerializer
""" 225 222 """
data = PasswordResetRequestSerializer(data=request.data) 226 223 data = PasswordResetRequestSerializer(data=request.data)
data.is_valid(raise_exception=True) 227 224 data.is_valid(raise_exception=True)
user = User.objects.get(email=data['email'].value) 228 225 user = User.objects.get(email=data['email'].value)
token = default_token_generator.make_token(user) 229 226 token = default_token_generator.make_token(user)
230 227
body = ''' 231 228 body = '''
Visit the following link to reset your password: 232 229 Visit the following link to reset your password:
https://flashy.cards/app/reset_password/%d/%s 233 230 https://flashy.cards/app/reset_password/%d/%s
234 231
If you did not request a password reset, no action is required. 235 232 If you did not request a password reset, no action is required.
''' 236 233 '''
237 234
send_mail("Flashy password reset", 238 235 send_mail("Flashy password reset",
body % (user.pk, token), 239 236 body % (user.pk, token),
"noreply@flashy.cards", 240 237 "noreply@flashy.cards",
[user.email]) 241 238 [user.email])
242 239
return Response(status=HTTP_204_NO_CONTENT) 243 240 return Response(status=HTTP_204_NO_CONTENT)
244 241
245 242
@api_view(['POST']) 246 243 @api_view(['POST'])
def reset_password(request, format=None): 247 244 def reset_password(request, format=None):
""" 248 245 """
Updates user's password to new password if token is valid. 249 246 Updates user's password to new password if token is valid.
--- 250 247 ---
request_serializer: PasswordResetSerializer 251 248 request_serializer: PasswordResetSerializer
""" 252 249 """
data = PasswordResetSerializer(data=request.data) 253 250 data = PasswordResetSerializer(data=request.data)
data.is_valid(raise_exception=True) 254 251 data.is_valid(raise_exception=True)
255 252
user = User.objects.get(id=data['uid'].value) 256 253 user = User.objects.get(id=data['uid'].value)
# Check token validity. 257 254 # Check token validity.
258 255
if default_token_generator.check_token(user, data['token'].value): 259 256 if default_token_generator.check_token(user, data['token'].value):
user.set_password(data['new_password'].value) 260 257 user.set_password(data['new_password'].value)
user.save() 261 258 user.save()
else: 262 259 else:
scripts/check_python_version.py View file @ 7916bfa
File was created 1
2
3 import sys
4
5 print(sys.version)
scripts/run_local.sh View file @ 7916bfa
#!/bin/bash -xe 1 1 #!/bin/bash -xe
2
if [ ! -d "../flashy-frontend/" ]; then 2 3 if [ ! -d "../flashy-frontend/" ]; then
echo "In order to serve the frontend, flashy-frontend must be in the parent dir" 3 4 echo "In order to serve the frontend, flashy-frontend must be in the parent dir"
exit 1 4 5 exit 1
fi 5 6 fi
source venv/bin/activate 6 7
8 if [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
9 source venv/bin/activate
10 elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then
11 source venv/Scripts/activate
12 fi
13
python manage.py runserver 127.0.0.1:8080 7 14 python manage.py runserver 127.0.0.1:8080
8 15
scripts/setup.sh View file @ 7916bfa
#!/bin/bash -xe 1 1 #!/bin/bash -xe
virtualenv venv 2 2 virtualenv venv
source venv/bin/activate 3 3
4 if [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
5 source venv/bin/activate
6 elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then
7 source venv/Scripts/activate
8 fi
9
pip install -r requirements.txt 4 10 pip install -r requirements.txt
virtualenv --relocatable venv 5 11 virtualenv --relocatable venv
#git submodule init 6
#git submodule update --depth 1 7
#pip install -e django 8