Commit 7dcd94c0189a4d99b734d3c5ce2887661ed429da

Authored by Rohan Rangray
1 parent 28a4bd2e76
Exists in master

Added more extensive tests for /api/study/*

Showing 4 changed files with 55 additions and 4 deletions Inline Diff

flashcards/serializers.py View file @ 7dcd94c
from json import dumps, loads 1 1 from json import dumps, loads
2 2
from django.utils.datetime_safe import datetime 3 3 from django.utils.datetime_safe import datetime
from django.utils.timezone import now 4 4 from django.utils.timezone import now
from flashcards.models import Section, LecturePeriod, User, Flashcard, UserFlashcard, UserFlashcardQuiz 5 5 from flashcards.models import Section, LecturePeriod, User, Flashcard, UserFlashcard, UserFlashcardQuiz
from flashcards.validators import FlashcardMask, OverlapIntervalException 6 6 from flashcards.validators import FlashcardMask, OverlapIntervalException
from rest_framework import serializers 7 7 from rest_framework import serializers
from rest_framework.fields import EmailField, BooleanField, CharField, IntegerField, DateTimeField, empty 8 8 from rest_framework.fields import EmailField, BooleanField, CharField, IntegerField, DateTimeField, empty
from rest_framework.serializers import ModelSerializer, Serializer, PrimaryKeyRelatedField, ListField 9 9 from rest_framework.serializers import ModelSerializer, Serializer, PrimaryKeyRelatedField, ListField
from rest_framework.validators import UniqueValidator 10 10 from rest_framework.validators import UniqueValidator
from flashy.settings import QUARTER_END, QUARTER_START 11 11 from flashy.settings import QUARTER_END, QUARTER_START
12 12
13 13
class EmailSerializer(Serializer): 14 14 class EmailSerializer(Serializer):
email = EmailField(required=True) 15 15 email = EmailField(required=True)
16 16
17 17
class EmailPasswordSerializer(EmailSerializer): 18 18 class EmailPasswordSerializer(EmailSerializer):
password = CharField(required=True) 19 19 password = CharField(required=True)
20 20
21 21
class RegistrationSerializer(EmailPasswordSerializer): 22 22 class RegistrationSerializer(EmailPasswordSerializer):
email = EmailField(required=True, validators=[UniqueValidator(queryset=User.objects.all())]) 23 23 email = EmailField(required=True, validators=[UniqueValidator(queryset=User.objects.all())])
24 24
25 25
class PasswordResetRequestSerializer(EmailSerializer): 26 26 class PasswordResetRequestSerializer(EmailSerializer):
def validate_email(self, value): 27 27 def validate_email(self, value):
try: 28 28 try:
User.objects.get(email=value) 29 29 User.objects.get(email=value)
return value 30 30 return value
except User.DoesNotExist: 31 31 except User.DoesNotExist:
raise serializers.ValidationError('No user exists with that email') 32 32 raise serializers.ValidationError('No user exists with that email')
33 33
34 34
class PasswordResetSerializer(Serializer): 35 35 class PasswordResetSerializer(Serializer):
new_password = CharField(required=True, allow_blank=False) 36 36 new_password = CharField(required=True, allow_blank=False)
uid = IntegerField(required=True) 37 37 uid = IntegerField(required=True)
token = CharField(required=True) 38 38 token = CharField(required=True)
39 39
def validate_uid(self, value): 40 40 def validate_uid(self, value):
try: 41 41 try:
User.objects.get(id=value) 42 42 User.objects.get(id=value)
return value 43 43 return value
except User.DoesNotExist: 44 44 except User.DoesNotExist:
raise serializers.ValidationError('Could not verify reset token') 45 45 raise serializers.ValidationError('Could not verify reset token')
46 46
47 47
class UserUpdateSerializer(Serializer): 48 48 class UserUpdateSerializer(Serializer):
old_password = CharField(required=False) 49 49 old_password = CharField(required=False)
new_password = CharField(required=False, allow_blank=False) 50 50 new_password = CharField(required=False, allow_blank=False)
confirmation_key = CharField(required=False) 51 51 confirmation_key = CharField(required=False)
# reset_token = CharField(required=False) 52 52 # reset_token = CharField(required=False)
53 53
def validate(self, data): 54 54 def validate(self, data):
if 'new_password' in data and 'old_password' not in data: 55 55 if 'new_password' in data and 'old_password' not in data:
raise serializers.ValidationError('old_password is required to set a new_password') 56 56 raise serializers.ValidationError('old_password is required to set a new_password')
return data 57 57 return data
58 58
59 59
class Password(Serializer): 60 60 class Password(Serializer):
email = EmailField(required=True) 61 61 email = EmailField(required=True)
password = CharField(required=True) 62 62 password = CharField(required=True)
63 63
64 64
class LecturePeriodSerializer(ModelSerializer): 65 65 class LecturePeriodSerializer(ModelSerializer):
class Meta: 66 66 class Meta:
model = LecturePeriod 67 67 model = LecturePeriod
exclude = 'id', 'section' 68 68 exclude = 'id', 'section'
69 69
70 70
class SectionSerializer(ModelSerializer): 71 71 class SectionSerializer(ModelSerializer):
lecture_times = CharField() 72 72 lecture_times = CharField()
short_name = CharField() 73 73 short_name = CharField()
long_name = CharField() 74 74 long_name = CharField()
75 75
class Meta: 76 76 class Meta:
model = Section 77 77 model = Section
78 78
79 79
class DeepSectionSerializer(SectionSerializer): 80 80 class DeepSectionSerializer(SectionSerializer):
lectures = LecturePeriodSerializer(source='lectureperiod_set', many=True, read_only=True) 81 81 lectures = LecturePeriodSerializer(source='lectureperiod_set', many=True, read_only=True)
82 82
83 83
class UserSerializer(ModelSerializer): 84 84 class UserSerializer(ModelSerializer):
email = EmailField(required=False) 85 85 email = EmailField(required=False)
sections = SectionSerializer(many=True) 86 86 sections = SectionSerializer(many=True)
is_confirmed = BooleanField() 87 87 is_confirmed = BooleanField()
88 88
class Meta: 89 89 class Meta:
model = User 90 90 model = User
fields = ("sections", "email", "is_confirmed", "last_login", "date_joined") 91 91 fields = ("sections", "email", "is_confirmed", "last_login", "date_joined")
92 92
93 93
class MaskFieldSerializer(serializers.Field): 94 94 class MaskFieldSerializer(serializers.Field):
default_error_messages = { 95 95 default_error_messages = {
'max_length': 'Ensure this field has no more than {max_length} characters.', 96 96 'max_length': 'Ensure this field has no more than {max_length} characters.',
'interval': 'Ensure this field has valid intervals.', 97 97 'interval': 'Ensure this field has valid intervals.',
'overlap': 'Ensure this field does not have overlapping intervals.' 98 98 'overlap': 'Ensure this field does not have overlapping intervals.'
} 99 99 }
100 100
def to_representation(self, value): 101 101 def to_representation(self, value):
return dumps(list(self._make_mask(value))) 102 102 return dumps(list(self._make_mask(value)))
103 103
def to_internal_value(self, value): 104 104 def to_internal_value(self, value):
return self._make_mask(loads(value)) 105 105 return self._make_mask(loads(value))
106 106
def _make_mask(self, data): 107 107 def _make_mask(self, data):
try: 108 108 try:
mask = FlashcardMask(data) 109 109 mask = FlashcardMask(data)
except ValueError: 110 110 except ValueError:
raise serializers.ValidationError("Invalid JSON for MaskField") 111 111 raise serializers.ValidationError("Invalid JSON for MaskField")
except TypeError: 112 112 except TypeError:
raise serializers.ValidationError("Invalid data for MaskField.") 113 113 raise serializers.ValidationError("Invalid data for MaskField.")
except OverlapIntervalException: 114 114 except OverlapIntervalException:
raise serializers.ValidationError("Invalid intervals for MaskField data.") 115 115 raise serializers.ValidationError("Invalid intervals for MaskField data.")
if len(mask) > 32: 116 116 if len(mask) > 32:
raise serializers.ValidationError("Too many intervals in the mask.") 117 117 raise serializers.ValidationError("Too many intervals in the mask.")
return mask 118 118 return mask
119 119
120 120
class FlashcardSerializer(ModelSerializer): 121 121 class FlashcardSerializer(ModelSerializer):
is_hidden = BooleanField(read_only=True) 122 122 is_hidden = BooleanField(read_only=True)
hide_reason = CharField(read_only=True) 123 123 hide_reason = CharField(read_only=True)
material_date = DateTimeField(default=now) 124 124 material_date = DateTimeField(default=now)
mask = MaskFieldSerializer(allow_null=True) 125 125 mask = MaskFieldSerializer(allow_null=True)
score = IntegerField(read_only=True) 126 126 score = IntegerField(read_only=True)
127 127
def validate_material_date(self, value): 128 128 def validate_material_date(self, value):
# TODO: make this dynamic 129 129 # TODO: make this dynamic
if QUARTER_START <= value <= QUARTER_END: 130 130 if QUARTER_START <= value <= QUARTER_END:
return value 131 131 return value
else: 132 132 else:
raise serializers.ValidationError("Material date is outside allowed range for this quarter") 133 133 raise serializers.ValidationError("Material date is outside allowed range for this quarter")
134 134
def validate_pushed(self, value): 135 135 def validate_pushed(self, value):
if value > datetime.now(): 136 136 if value > datetime.now():
raise serializers.ValidationError("Invalid creation date for the Flashcard") 137 137 raise serializers.ValidationError("Invalid creation date for the Flashcard")
return value 138 138 return value
139 139
def validate_mask(self, value): 140 140 def validate_mask(self, value):
if value is None: 141 141 if value is None:
return None 142 142 return None
if len(self.initial_data['text']) < value.max_offset(): 143 143 if len(self.initial_data['text']) < value.max_offset():
raise serializers.ValidationError("Mask out of bounds") 144 144 raise serializers.ValidationError("Mask out of bounds")
return value 145 145 return value
146 146
class Meta: 147 147 class Meta:
model = Flashcard 148 148 model = Flashcard
exclude = 'author', 'previous' 149 149 exclude = 'author', 'previous'
150 150
151 151
class FlashcardUpdateSerializer(serializers.Serializer): 152 152 class FlashcardUpdateSerializer(serializers.Serializer):
text = CharField(max_length=255, required=False) 153 153 text = CharField(max_length=255, required=False)
material_date = DateTimeField(required=False) 154 154 material_date = DateTimeField(required=False)
mask = MaskFieldSerializer(required=False) 155 155 mask = MaskFieldSerializer(required=False)
156 156
def validate_material_date(self, date): 157 157 def validate_material_date(self, date):
if date > QUARTER_END: 158 158 if date > QUARTER_END:
raise serializers.ValidationError("Invalid material_date for the flashcard") 159 159 raise serializers.ValidationError("Invalid material_date for the flashcard")
return date 160 160 return date
161 161
def validate(self, attrs): 162 162 def validate(self, attrs):
# Make sure that at least one of the attributes was passed in 163 163 # Make sure that at least one of the attributes was passed in
if not any(i in attrs for i in ['material_date', 'text', 'mask']): 164 164 if not any(i in attrs for i in ['material_date', 'text', 'mask']):
raise serializers.ValidationError("No new value passed in") 165 165 raise serializers.ValidationError("No new value passed in")
return attrs 166 166 return attrs
167 167
168 168
class QuizRequestSerializer(serializers.Serializer): 169 169 class QuizRequestSerializer(serializers.Serializer):
sections = ListField(child=IntegerField(min_value=1), required=False) 170 170 sections = ListField(child=IntegerField(min_value=1), required=False)
material_date_begin = DateTimeField(default=QUARTER_START) 171 171 material_date_begin = DateTimeField(default=QUARTER_START)
material_date_end = DateTimeField(default=QUARTER_END) 172 172 material_date_end = DateTimeField(default=QUARTER_END)
173 173
def update(self, instance, validated_data): 174 174 def update(self, instance, validated_data):
pass 175 175 pass
176 176
def create(self, validated_data): 177 177 def create(self, validated_data):
return validated_data 178 178 return validated_data
179 179
def validate_material_date_begin(self, value): 180 180 def validate_material_date_begin(self, value):
if QUARTER_START <= value <= QUARTER_END: 181 181 if QUARTER_START <= value <= QUARTER_END:
return value 182 182 return value
raise serializers.ValidationError("Invalid begin date for the flashcard range") 183 183 raise serializers.ValidationError("Invalid begin date for the flashcard range")
184 184
def validate_material_date_end(self, value): 185 185 def validate_material_date_end(self, value):
if QUARTER_START <= value <= QUARTER_END: 186 186 if QUARTER_START <= value <= QUARTER_END:
return value 187 187 return value
raise serializers.ValidationError("Invalid end date for the flashcard range") 188 188 raise serializers.ValidationError("Invalid end date for the flashcard range")
189 189
def validate_sections(self, value): 190 190 def validate_sections(self, value):
if value is None: 191 191 if value is None:
return Section.objects.all() 192 192 return Section.objects.all()
section_filter = Section.objects.filter(pk__in=value) 193 193 section_filter = Section.objects.filter(pk__in=value)
if not section_filter.exists(): 194 194 if not section_filter.exists():
raise serializers.ValidationError("Those aren't valid sections") 195 195 raise serializers.ValidationError("Those aren't valid sections")
return value 196 196 return value
197 197
def validate(self, attrs): 198 198 def validate(self, attrs):
if attrs['material_date_begin'] > attrs['material_date_end']: 199 199 if attrs['material_date_begin'] > attrs['material_date_end']:
raise serializers.ValidationError("Invalid range") 200 200 raise serializers.ValidationError("Invalid range")
if 'sections' not in attrs: 201 201 if 'sections' not in attrs:
attrs['sections'] = self.validate_sections(None) 202 202 attrs['sections'] = self.validate_sections(None)
return attrs 203 203 return attrs
flashcards/tests/test_api.py View file @ 7dcd94c
from django.core import mail 1 1 from django.core import mail
from flashcards.models import * 2 2 from flashcards.models import *
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, \
4 HTTP_403_FORBIDDEN, HTTP_400_BAD_REQUEST
from rest_framework.test import APITestCase 4 5 from rest_framework.test import APITestCase
from re import search 5 6 from re import search
from datetime import datetime 6 7 from datetime import datetime
from django.utils.timezone import now 7 8 from django.utils.timezone import now
from flashcards.validators import FlashcardMask 8 9 from flashcards.validators import FlashcardMask
from flashcards.serializers import FlashcardSerializer 9 10 from flashcards.serializers import FlashcardSerializer
10 11
11 12
class LoginTests(APITestCase): 12 13 class LoginTests(APITestCase):
fixtures = ['testusers'] 13 14 fixtures = ['testusers']
14 15
def test_login(self): 15 16 def test_login(self):
url = '/api/login/' 16 17 url = '/api/login/'
data = {'email': 'none@none.com', 'password': '1234'} 17 18 data = {'email': 'none@none.com', 'password': '1234'}
response = self.client.post(url, data, format='json') 18 19 response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, HTTP_200_OK) 19 20 self.assertEqual(response.status_code, HTTP_200_OK)
20 21
data = {'email': 'none@none.com', 'password': '4321'} 21 22 data = {'email': 'none@none.com', 'password': '4321'}
response = self.client.post(url, data, format='json') 22 23 response = self.client.post(url, data, format='json')
self.assertContains(response, 'Invalid email or password', status_code=403) 23 24 self.assertContains(response, 'Invalid email or password', status_code=403)
24 25
data = {'email': 'bad@none.com', 'password': '1234'} 25 26 data = {'email': 'bad@none.com', 'password': '1234'}
response = self.client.post(url, data, format='json') 26 27 response = self.client.post(url, data, format='json')
self.assertContains(response, 'Invalid email or password', status_code=403) 27 28 self.assertContains(response, 'Invalid email or password', status_code=403)
28 29
data = {'password': '4321'} 29 30 data = {'password': '4321'}
response = self.client.post(url, data, format='json') 30 31 response = self.client.post(url, data, format='json')
self.assertContains(response, 'email', status_code=400) 31 32 self.assertContains(response, 'email', status_code=400)
32 33
data = {'email': 'none@none.com'} 33 34 data = {'email': 'none@none.com'}
response = self.client.post(url, data, format='json') 34 35 response = self.client.post(url, data, format='json')
self.assertContains(response, 'password', status_code=400) 35 36 self.assertContains(response, 'password', status_code=400)
36 37
user = User.objects.get(email="none@none.com") 37 38 user = User.objects.get(email="none@none.com")
user.is_active = False 38 39 user.is_active = False
user.save() 39 40 user.save()
40 41
data = {'email': 'none@none.com', 'password': '1234'} 41 42 data = {'email': 'none@none.com', 'password': '1234'}
response = self.client.post(url, data, format='json') 42 43 response = self.client.post(url, data, format='json')
self.assertContains(response, 'Account is disabled', status_code=403) 43 44 self.assertContains(response, 'Account is disabled', status_code=403)
44 45
def test_logout(self): 45 46 def test_logout(self):
self.client.login(email='none@none.com', password='1234') 46 47 self.client.login(email='none@none.com', password='1234')
response = self.client.post('/api/logout/') 47 48 response = self.client.post('/api/logout/')
self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) 48 49 self.assertEqual(response.status_code, HTTP_204_NO_CONTENT)
49 50
# since we're not logged in, we should get a 403 response 50 51 # since we're not logged in, we should get a 403 response
response = self.client.get('/api/me/', format='json') 51 52 response = self.client.get('/api/me/', format='json')
self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) 52 53 self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)
53 54
54 55
class PasswordResetTest(APITestCase): 55 56 class PasswordResetTest(APITestCase):
fixtures = ['testusers'] 56 57 fixtures = ['testusers']
57 58
def test_reset_password(self): 58 59 def test_reset_password(self):
# submit the request to reset the password 59 60 # submit the request to reset the password
url = '/api/request_password_reset/' 60 61 url = '/api/request_password_reset/'
post_data = {'email': 'none@none.com'} 61 62 post_data = {'email': 'none@none.com'}
self.client.post(url, post_data, format='json') 62 63 self.client.post(url, post_data, format='json')
self.assertEqual(len(mail.outbox), 1) 63 64 self.assertEqual(len(mail.outbox), 1)
self.assertIn('reset your password', mail.outbox[0].body) 64 65 self.assertIn('reset your password', mail.outbox[0].body)
65 66
# capture the reset token from the email 66 67 # capture the reset token from the email
capture = search('https://flashy.cards/app/resetpassword/(\d+)/(.*)', 67 68 capture = search('https://flashy.cards/app/resetpassword/(\d+)/(.*)',
mail.outbox[0].body) 68 69 mail.outbox[0].body)
patch_data = {'new_password': '4321'} 69 70 patch_data = {'new_password': '4321'}
patch_data['uid'] = capture.group(1) 70 71 patch_data['uid'] = capture.group(1)
reset_token = capture.group(2) 71 72 reset_token = capture.group(2)
72 73
# try to reset the password with the wrong reset token 73 74 # try to reset the password with the wrong reset token
patch_data['token'] = 'wrong_token' 74 75 patch_data['token'] = 'wrong_token'
url = '/api/reset_password/' 75 76 url = '/api/reset_password/'
response = self.client.post(url, patch_data, format='json') 76 77 response = self.client.post(url, patch_data, format='json')
self.assertContains(response, 'Could not verify reset token', status_code=400) 77 78 self.assertContains(response, 'Could not verify reset token', status_code=400)
78 79
# try to reset the password with the correct token 79 80 # try to reset the password with the correct token
patch_data['token'] = reset_token 80 81 patch_data['token'] = reset_token
response = self.client.post(url, patch_data, format='json') 81 82 response = self.client.post(url, patch_data, format='json')
self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) 82 83 self.assertEqual(response.status_code, HTTP_204_NO_CONTENT)
user = User.objects.get(id=patch_data['uid']) 83 84 user = User.objects.get(id=patch_data['uid'])
assert user.check_password(patch_data['new_password']) 84 85 assert user.check_password(patch_data['new_password'])
85 86
86 87
class RegistrationTest(APITestCase): 87 88 class RegistrationTest(APITestCase):
def test_create_account(self): 88 89 def test_create_account(self):
url = '/api/register/' 89 90 url = '/api/register/'
90 91
# missing password 91 92 # missing password
data = {'email': 'none@none.com'} 92 93 data = {'email': 'none@none.com'}
response = self.client.post(url, data, format='json') 93 94 response = self.client.post(url, data, format='json')
self.assertContains(response, 'password', status_code=400) 94 95 self.assertContains(response, 'password', status_code=400)
95 96
# missing email 96 97 # missing email
data = {'password': '1234'} 97 98 data = {'password': '1234'}
response = self.client.post(url, data, format='json') 98 99 response = self.client.post(url, data, format='json')
self.assertContains(response, 'email', status_code=400) 99 100 self.assertContains(response, 'email', status_code=400)
100 101
# create a user 101 102 # create a user
data = {'email': 'none@none.com', 'password': '1234'} 102 103 data = {'email': 'none@none.com', 'password': '1234'}
response = self.client.post(url, data, format='json') 103 104 response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, HTTP_201_CREATED) 104 105 self.assertEqual(response.status_code, HTTP_201_CREATED)
105 106
# user should not be confirmed 106 107 # user should not be confirmed
user = User.objects.get(email="none@none.com") 107 108 user = User.objects.get(email="none@none.com")
self.assertFalse(user.is_confirmed) 108 109 self.assertFalse(user.is_confirmed)
109 110
# check that the confirmation key was sent 110 111 # check that the confirmation key was sent
self.assertEqual(len(mail.outbox), 1) 111 112 self.assertEqual(len(mail.outbox), 1)
self.assertIn(user.confirmation_key, mail.outbox[0].body) 112 113 self.assertIn(user.confirmation_key, mail.outbox[0].body)
113 114
# log the user out 114 115 # log the user out
self.client.logout() 115 116 self.client.logout()
116 117
# log the user in with their registered credentials 117 118 # log the user in with their registered credentials
self.client.login(email='none@none.com', password='1234') 118 119 self.client.login(email='none@none.com', password='1234')
119 120
# try activating with an invalid key 120 121 # try activating with an invalid key
121 122
url = '/api/me/' 122 123 url = '/api/me/'
response = self.client.patch(url, {'confirmation_key': 'NOT A KEY'}) 123 124 response = self.client.patch(url, {'confirmation_key': 'NOT A KEY'})
self.assertContains(response, 'confirmation_key is invalid', status_code=400) 124 125 self.assertContains(response, 'confirmation_key is invalid', status_code=400)
125 126
# try activating with the valid key 126 127 # try activating with the valid key
response = self.client.patch(url, {'confirmation_key': user.confirmation_key}) 127 128 response = self.client.patch(url, {'confirmation_key': user.confirmation_key})
self.assertTrue(response.data['is_confirmed']) 128 129 self.assertTrue(response.data['is_confirmed'])
129 130
130 131
class ProfileViewTest(APITestCase): 131 132 class ProfileViewTest(APITestCase):
fixtures = ['testusers'] 132 133 fixtures = ['testusers']
133 134
def test_get_me(self): 134 135 def test_get_me(self):
url = '/api/me/' 135 136 url = '/api/me/'
response = self.client.get(url, format='json') 136 137 response = self.client.get(url, format='json')
# since we're not logged in, we shouldn't be able to see this 137 138 # since we're not logged in, we shouldn't be able to see this
self.assertEqual(response.status_code, 403) 138 139 self.assertEqual(response.status_code, 403)
139 140
self.client.login(email='none@none.com', password='1234') 140 141 self.client.login(email='none@none.com', password='1234')
response = self.client.get(url, format='json') 141 142 response = self.client.get(url, format='json')
self.assertEqual(response.status_code, HTTP_200_OK) 142 143 self.assertEqual(response.status_code, HTTP_200_OK)
143 144
144 145
class UserSectionsTest(APITestCase): 145 146 class UserSectionsTest(APITestCase):
fixtures = ['testusers', 'testsections'] 146 147 fixtures = ['testusers', 'testsections']
147 148
def setUp(self): 148 149 def setUp(self):
self.user = User.objects.get(pk=1) 149 150 self.user = User.objects.get(pk=1)
self.client.login(email='none@none.com', password='1234') 150 151 self.client.login(email='none@none.com', password='1234')
self.section = Section.objects.get(pk=1) 151 152 self.section = Section.objects.get(pk=1)
self.section.enroll(self.user) 152 153 self.section.enroll(self.user)
153 154
def test_get_user_sections(self): 154 155 def test_get_user_sections(self):
response = self.client.get('/api/me/sections/', format='json') 155 156 response = self.client.get('/api/me/sections/', format='json')
self.assertEqual(response.status_code, 200) 156 157 self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Goldstein') 157 158 self.assertContains(response, 'Goldstein')
158 159
159 160
class PasswordChangeTest(APITestCase): 160 161 class PasswordChangeTest(APITestCase):
fixtures = ['testusers'] 161 162 fixtures = ['testusers']
162 163
def test_change_password(self): 163 164 def test_change_password(self):
url = '/api/me/' 164 165 url = '/api/me/'
user = User.objects.get(email='none@none.com') 165 166 user = User.objects.get(email='none@none.com')
self.assertTrue(user.check_password('1234')) 166 167 self.assertTrue(user.check_password('1234'))
167 168
response = self.client.patch(url, {'new_password': '4321', 'old_password': '1234'}, format='json') 168 169 response = self.client.patch(url, {'new_password': '4321', 'old_password': '1234'}, format='json')
self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) 169 170 self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)
170 171
self.client.login(email='none@none.com', password='1234') 171 172 self.client.login(email='none@none.com', password='1234')
response = self.client.patch(url, {'new_password': '4321'}, format='json') 172 173 response = self.client.patch(url, {'new_password': '4321'}, format='json')
self.assertContains(response, 'old_password is required', status_code=400) 173 174 self.assertContains(response, 'old_password is required', status_code=400)
174 175
response = self.client.patch(url, {'new_password': '4321', 'old_password': '4321'}, format='json') 175 176 response = self.client.patch(url, {'new_password': '4321', 'old_password': '4321'}, format='json')
self.assertContains(response, 'old_password is incorrect', status_code=400) 176 177 self.assertContains(response, 'old_password is incorrect', status_code=400)
177 178
response = self.client.patch(url, {'new_password': '4321', 'old_password': '1234'}, format='json') 178 179 response = self.client.patch(url, {'new_password': '4321', 'old_password': '1234'}, format='json')
self.assertEqual(response.status_code, 200) 179 180 self.assertEqual(response.status_code, 200)
user = User.objects.get(email='none@none.com') 180 181 user = User.objects.get(email='none@none.com')
181 182
self.assertFalse(user.check_password('1234')) 182 183 self.assertFalse(user.check_password('1234'))
self.assertTrue(user.check_password('4321')) 183 184 self.assertTrue(user.check_password('4321'))
184 185
185 186
class DeleteUserTest(APITestCase): 186 187 class DeleteUserTest(APITestCase):
fixtures = ['testusers'] 187 188 fixtures = ['testusers']
188 189
def test_delete_user(self): 189 190 def test_delete_user(self):
url = '/api/me/' 190 191 url = '/api/me/'
user = User.objects.get(email='none@none.com') 191 192 user = User.objects.get(email='none@none.com')
192 193
self.client.login(email='none@none.com', password='1234') 193 194 self.client.login(email='none@none.com', password='1234')
self.client.delete(url) 194 195 self.client.delete(url)
self.assertFalse(User.objects.filter(email='none@none.com').exists()) 195 196 self.assertFalse(User.objects.filter(email='none@none.com').exists())
196 197
197 198
class FlashcardDetailTest(APITestCase): 198 199 class FlashcardDetailTest(APITestCase):
fixtures = ['testusers', 'testsections'] 199 200 fixtures = ['testusers', 'testsections']
200 201
def setUp(self): 201 202 def setUp(self):
self.section = Section.objects.get(pk=1) 202 203 self.section = Section.objects.get(pk=1)
self.user = User.objects.get(email='none@none.com') 203 204 self.user = User.objects.get(email='none@none.com')
self.section.enroll(self.user) 204 205 self.section.enroll(self.user)
self.inaccessible_flashcard = Flashcard(text="can't touch this!", section=Section.objects.get(pk=2), 205 206 self.inaccessible_flashcard = Flashcard(text="can't touch this!", section=Section.objects.get(pk=2),
author=self.user) 206 207 author=self.user)
self.inaccessible_flashcard.save() 207 208 self.inaccessible_flashcard.save()
self.flashcard = Flashcard(text="jason", section=self.section, author=self.user) 208 209 self.flashcard = Flashcard(text="jason", section=self.section, author=self.user)
self.flashcard.save() 209 210 self.flashcard.save()
#self.flashcard.add_to_deck(self.user) 210 211 #self.flashcard.add_to_deck(self.user)
self.client.login(email='none@none.com', password='1234') 211 212 self.client.login(email='none@none.com', password='1234')
212 213
def test_edit_flashcard(self): 213 214 def test_edit_flashcard(self):
user = self.user 214 215 user = self.user
flashcard = self.flashcard 215 216 flashcard = self.flashcard
url = "/api/flashcards/{}/".format(flashcard.pk) 216 217 url = "/api/flashcards/{}/".format(flashcard.pk)
data = {'text': 'new wow for the flashcard', 217 218 data = {'text': 'new wow for the flashcard',
'mask': '[[0,4]]'} 218 219 'mask': '[[0,4]]'}
self.assertNotEqual(flashcard.text, data['text']) 219 220 self.assertNotEqual(flashcard.text, data['text'])
response = self.client.patch(url, data, format='json') 220 221 response = self.client.patch(url, data, format='json')
self.assertEqual(response.status_code, HTTP_200_OK) 221 222 self.assertEqual(response.status_code, HTTP_200_OK)
self.assertEqual(response.data['text'], data['text']) 222 223 self.assertEqual(response.data['text'], data['text'])
data = {'material_date': datetime(2015, 4, 12, 2, 2, 2), 223 224 data = {'material_date': datetime(2015, 4, 12, 2, 2, 2),
'mask': '[[1, 3]]'} 224 225 'mask': '[[1, 3]]'}
user2 = User.objects.create(email='wow@wow.wow', password='wow') 225 226 user2 = User.objects.create(email='wow@wow.wow', password='wow')
user2.sections.add(self.section) 226 227 user2.sections.add(self.section)
user2.save() 227 228 user2.save()
UserFlashcard.objects.create(user=user2, flashcard=flashcard).save() 228 229 UserFlashcard.objects.create(user=user2, flashcard=flashcard).save()
response = self.client.patch(url, data, format='json') 229 230 response = self.client.patch(url, data, format='json')
serializer = FlashcardSerializer(data=response.data) 230 231 serializer = FlashcardSerializer(data=response.data)
serializer.is_valid(raise_exception=True) 231 232 serializer.is_valid(raise_exception=True)
self.assertEqual(response.status_code, HTTP_200_OK) 232 233 self.assertEqual(response.status_code, HTTP_200_OK)
# self.assertEqual(serializer.validated_data['material_date'], utc.localize(data['material_date'])) 233 234 # self.assertEqual(serializer.validated_data['material_date'], utc.localize(data['material_date']))
self.assertEqual(serializer.validated_data['mask'], FlashcardMask([[1, 3]])) 234 235 self.assertEqual(serializer.validated_data['mask'], FlashcardMask([[1, 3]]))
data = {'mask': '[[3,6]]'} 235 236 data = {'mask': '[[3,6]]'}
response = self.client.patch(url, data, format='json') 236 237 response = self.client.patch(url, data, format='json')
user_flashcard = UserFlashcard.objects.get(user=user, flashcard=flashcard) 237 238 user_flashcard = UserFlashcard.objects.get(user=user, flashcard=flashcard)
self.assertEqual(response.status_code, HTTP_200_OK) 238 239 self.assertEqual(response.status_code, HTTP_200_OK)
self.assertEqual(user_flashcard.mask, FlashcardMask([[3, 6]])) 239 240 self.assertEqual(user_flashcard.mask, FlashcardMask([[3, 6]]))
240 241
def test_create_flashcard(self): 241 242 def test_create_flashcard(self):
data = {'text': 'this is a flashcard', 242 243 data = {'text': 'this is a flashcard',
'material_date': now(), 243 244 'material_date': now(),
'mask': '[]', 244 245 'mask': '[]',
'section': '1', 245 246 'section': '1',
'previous': None} 246 247 'previous': None}
response = self.client.post("/api/flashcards/", data, format="json") 247 248 response = self.client.post("/api/flashcards/", data, format="json")
self.assertEqual(response.status_code, HTTP_201_CREATED) 248 249 self.assertEqual(response.status_code, HTTP_201_CREATED)
self.assertEqual(response.data['text'], data['text']) 249 250 self.assertEqual(response.data['text'], data['text'])
self.assertTrue(Flashcard.objects.filter(section__pk=1, text=data['text']).exists()) 250 251 self.assertTrue(Flashcard.objects.filter(section__pk=1, text=data['text']).exists())
251 252
def test_get_flashcard(self): 252 253 def test_get_flashcard(self):
response = self.client.get("/api/flashcards/%d/" % self.flashcard.id, format="json") 253 254 response = self.client.get("/api/flashcards/%d/" % self.flashcard.id, format="json")
self.assertEqual(response.status_code, HTTP_200_OK) 254 255 self.assertEqual(response.status_code, HTTP_200_OK)
self.assertEqual(response.data["text"], "jason") 255 256 self.assertEqual(response.data["text"], "jason")
256 257
def test_hide_flashcard(self): 257 258 def test_hide_flashcard(self):
response = self.client.post('/api/flashcards/%d/hide/' % self.flashcard.id, format='json') 258 259 response = self.client.post('/api/flashcards/%d/hide/' % self.flashcard.id, format='json')
self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) 259 260 self.assertEqual(response.status_code, HTTP_204_NO_CONTENT)
self.assertTrue(self.flashcard.is_hidden_from(self.user)) 260 261 self.assertTrue(self.flashcard.is_hidden_from(self.user))
261 262
response = self.client.post('/api/flashcards/%d/hide/' % self.inaccessible_flashcard.pk, format='json') 262 263 response = self.client.post('/api/flashcards/%d/hide/' % self.inaccessible_flashcard.pk, format='json')
# This should fail because the user is not enrolled in section id 2 263 264 # This should fail because the user is not enrolled in section id 2
self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) 264 265 self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)
265 266
def test_unhide_flashcard(self): 266 267 def test_unhide_flashcard(self):
self.flashcard.hide_from(self.user) 267 268 self.flashcard.hide_from(self.user)
268 269
response = self.client.post('/api/flashcards/%d/unhide/' % self.flashcard.id, format='json') 269 270 response = self.client.post('/api/flashcards/%d/unhide/' % self.flashcard.id, format='json')
self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) 270 271 self.assertEqual(response.status_code, HTTP_204_NO_CONTENT)
271 272
response = self.client.post('/api/flashcards/%d/unhide/' % self.inaccessible_flashcard.pk, format='json') 272 273 response = self.client.post('/api/flashcards/%d/unhide/' % self.inaccessible_flashcard.pk, format='json')
273 274
# This should fail because the user is not enrolled in section id 2 274 275 # This should fail because the user is not enrolled in section id 2
self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) 275 276 self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)
276 277
277 278
class SectionViewSetTest(APITestCase): 278 279 class SectionViewSetTest(APITestCase):
fixtures = ['testusers', 'testsections'] 279 280 fixtures = ['testusers', 'testsections']
280 281
def setUp(self): 281 282 def setUp(self):
self.client.login(email='none@none.com', password='1234') 282 283 self.client.login(email='none@none.com', password='1234')
self.user = User.objects.get(email='none@none.com') 283 284 self.user = User.objects.get(email='none@none.com')
self.flashcard = Flashcard(text="jason", section=Section.objects.get(pk=1), material_date=now(), 284 285 self.flashcard = Flashcard(text="jason", section=Section.objects.get(pk=1), material_date=now(),
author=self.user) 285 286 author=self.user)
self.flashcard.save() 286 287 self.flashcard.save()
self.section = Section.objects.get(pk=1) 287 288 self.section = Section.objects.get(pk=1)
288 289
def test_list_sections(self): 289 290 def test_list_sections(self):
response = self.client.get("/api/sections/", format="json") 290 291 response = self.client.get("/api/sections/", format="json")
self.assertEqual(response.status_code, HTTP_200_OK) 291 292 self.assertEqual(response.status_code, HTTP_200_OK)
292 293
def test_section_enroll(self): 293 294 def test_section_enroll(self):
section = self.section 294 295 section = self.section
self.assertFalse(self.user.sections.filter(pk=section.pk)) 295 296 self.assertFalse(self.user.sections.filter(pk=section.pk))
296 297
# test enrolling in a section without a whitelist 297 298 # test enrolling in a section without a whitelist
response = self.client.post('/api/sections/%d/enroll/' % section.pk) 298 299 response = self.client.post('/api/sections/%d/enroll/' % section.pk)
self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) 299 300 self.assertEqual(response.status_code, HTTP_204_NO_CONTENT)
self.assertTrue(self.user.sections.filter(pk=section.pk).exists()) 300 301 self.assertTrue(self.user.sections.filter(pk=section.pk).exists())
301 302
section = Section.objects.get(pk=2) 302 303 section = Section.objects.get(pk=2)
WhitelistedAddress.objects.create(email='bad@none.com', section=section) 303 304 WhitelistedAddress.objects.create(email='bad@none.com', section=section)
304 305
# test enrolling in a section when not on the whitelist 305 306 # test enrolling in a section when not on the whitelist
response = self.client.post('/api/sections/%d/enroll/' % section.pk) 306 307 response = self.client.post('/api/sections/%d/enroll/' % section.pk)
self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) 307 308 self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)
self.assertFalse(self.user.sections.filter(pk=section.pk).exists()) 308 309 self.assertFalse(self.user.sections.filter(pk=section.pk).exists())
309 310
WhitelistedAddress.objects.create(email=self.user.email, section=section) 310 311 WhitelistedAddress.objects.create(email=self.user.email, section=section)
311 312
# test enrolling in a section when on the whitelist 312 313 # test enrolling in a section when on the whitelist
response = self.client.post('/api/sections/%d/enroll/' % section.pk) 313 314 response = self.client.post('/api/sections/%d/enroll/' % section.pk)
self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) 314 315 self.assertEqual(response.status_code, HTTP_204_NO_CONTENT)
self.assertTrue(self.user.sections.filter(pk=section.pk).exists()) 315 316 self.assertTrue(self.user.sections.filter(pk=section.pk).exists())
316 317
def test_section_drop(self): 317 318 def test_section_drop(self):
section = self.section 318 319 section = self.section
319 320
# test dropping a section that the user isn't in 320 321 # test dropping a section that the user isn't in
response = self.client.post('/api/sections/%d/drop/' % section.pk) 321 322 response = self.client.post('/api/sections/%d/drop/' % section.pk)
self.assertEqual(response.status_code, 400) 322 323 self.assertEqual(response.status_code, 400)
323 324
self.user.sections.add(section) 324 325 self.user.sections.add(section)
self.assertTrue(self.user.sections.filter(pk=section.pk).exists()) 325 326 self.assertTrue(self.user.sections.filter(pk=section.pk).exists())
326 327
# test dropping a section that the user is in 327 328 # test dropping a section that the user is in
response = self.client.post('/api/sections/%d/drop/' % section.pk) 328 329 response = self.client.post('/api/sections/%d/drop/' % section.pk)
self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) 329 330 self.assertEqual(response.status_code, HTTP_204_NO_CONTENT)
self.assertFalse(self.user.sections.filter(pk=section.pk).exists()) 330 331 self.assertFalse(self.user.sections.filter(pk=section.pk).exists())
331 332
def test_section_flashcards(self): 332 333 def test_section_flashcards(self):
# test to get flashcards for section 1 333 334 # test to get flashcards for section 1
response = self.client.get('/api/sections/1/flashcards/') 334 335 response = self.client.get('/api/sections/1/flashcards/')
self.assertEqual(response.status_code, HTTP_200_OK) 335 336 self.assertEqual(response.status_code, HTTP_200_OK)
336 337
# test: Making FlashcardHide object, so no card should be seen. 337 338 # test: Making FlashcardHide object, so no card should be seen.
flashcard_hide = FlashcardHide(user=self.user, flashcard=self.flashcard) 338 339 flashcard_hide = FlashcardHide(user=self.user, flashcard=self.flashcard)
flashcard_hide.save() 339 340 flashcard_hide.save()
response = self.client.get('/api/sections/1/flashcards/') 340 341 response = self.client.get('/api/sections/1/flashcards/')
self.assertEqual(response.status_code, HTTP_200_OK) 341 342 self.assertEqual(response.status_code, HTTP_200_OK)
self.assertEqual(response.content, '[]') 342 343 self.assertEqual(response.content, '[]')
343 344
def test_section_search(self): 344 345 def test_section_search(self):
response = self.client.get('/api/sections/search/?q=Kramer') 345 346 response = self.client.get('/api/sections/search/?q=Kramer')
self.assertEqual(response.status_code, HTTP_200_OK) 346 347 self.assertEqual(response.status_code, HTTP_200_OK)
347 348
def test_section_deck(self): 348 349 def test_section_deck(self):
self.user.sections.add(self.section) 349 350 self.user.sections.add(self.section)
self.user.save() 350 351 self.user.save()
response = self.client.get('/api/sections/1/deck/') 351 352 response = self.client.get('/api/sections/1/deck/')
self.assertEqual(response.status_code, HTTP_200_OK) 352 353 self.assertEqual(response.status_code, HTTP_200_OK)
353 354
def test_section_feed(self): 354 355 def test_section_feed(self):
Flashcard.objects.create(author=self.user, material_date=now(), 355 356 Flashcard.objects.create(author=self.user, material_date=now(),
text='wow', section=self.section, 356 357 text='wow', section=self.section,
mask=None).save() 357 358 mask=None).save()
response = self.client.get('/api/sections/{}/feed/'.format(self.section.pk)) 358 359 response = self.client.get('/api/sections/{}/feed/'.format(self.section.pk))
flashcards/tests/test_models.py View file @ 7dcd94c
from datetime import datetime 1 1 from datetime import datetime
2 2
from django.test import TestCase 3 3 from django.test import TestCase
from flashcards.models import User, Section, Flashcard, UserFlashcard, UserFlashcardQuiz 4 4 from flashcards.models import User, Section, Flashcard, UserFlashcard, UserFlashcardQuiz
from flashcards.validators import FlashcardMask, OverlapIntervalException 5 5 from flashcards.validators import FlashcardMask, OverlapIntervalException
from flashcards.serializers import QuizRequestSerializer, QuizResponseSerializer, QuizAnswerRequestSerializer 6 6 from flashcards.serializers import QuizRequestSerializer, QuizResponseSerializer, QuizAnswerRequestSerializer
from flashy.settings import QUARTER_START, QUARTER_END 7 7 from flashy.settings import QUARTER_START, QUARTER_END
8 8
9 9
class RegistrationTests(TestCase): 10 10 class RegistrationTests(TestCase):
def setUp(self): 11 11 def setUp(self):
User.objects.create_user(email="none@none.com", password="1234") 12 12 User.objects.create_user(email="none@none.com", password="1234")
13 13
def test_email_confirmation(self): 14 14 def test_email_confirmation(self):
user = User.objects.get(email="none@none.com") 15 15 user = User.objects.get(email="none@none.com")
self.assertFalse(user.is_confirmed) 16 16 self.assertFalse(user.is_confirmed)
user.confirm_email(user.confirmation_key) 17 17 user.confirm_email(user.confirmation_key)
self.assertTrue(user.is_confirmed) 18 18 self.assertTrue(user.is_confirmed)
19 19
20 20
class UserTests(TestCase): 21 21 class UserTests(TestCase):
def setUp(self): 22 22 def setUp(self):
User.objects.create_user(email="none@none.com", password="1234") 23 23 User.objects.create_user(email="none@none.com", password="1234")
Section.objects.create(department='dept', 24 24 Section.objects.create(department='dept',
course_num='101a', 25 25 course_num='101a',
course_title='how 2 test', 26 26 course_title='how 2 test',
instructor='George Lucas', 27 27 instructor='George Lucas',
quarter='SP15') 28 28 quarter='SP15')
29 29
def test_section_list(self): 30 30 def test_section_list(self):
section = Section.objects.get(course_num='101a') 31 31 section = Section.objects.get(course_num='101a')
user = User.objects.get(email="none@none.com") 32 32 user = User.objects.get(email="none@none.com")
self.assertNotIn(section, user.sections.all()) 33 33 self.assertNotIn(section, user.sections.all())
user.sections.add(section) 34 34 user.sections.add(section)
self.assertIn(section, user.sections.all()) 35 35 self.assertIn(section, user.sections.all())
user.sections.add(section) 36 36 user.sections.add(section)
self.assertEqual(user.sections.count(), 1) 37 37 self.assertEqual(user.sections.count(), 1)
user.sections.remove(section) 38 38 user.sections.remove(section)
self.assertEqual(user.sections.count(), 0) 39 39 self.assertEqual(user.sections.count(), 0)
40 40
41 41
class FlashcardMaskTest(TestCase): 42 42 class FlashcardMaskTest(TestCase):
def test_empty(self): 43 43 def test_empty(self):
try: 44 44 try:
fm = FlashcardMask([]) 45 45 fm = FlashcardMask([])
self.assertEqual(fm.max_offset(), -1) 46 46 self.assertEqual(fm.max_offset(), -1)
except TypeError: 47 47 except TypeError:
self.fail() 48 48 self.fail()
try: 49 49 try:
fm = FlashcardMask('') 50 50 fm = FlashcardMask('')
self.assertEqual(fm.max_offset(), -1) 51 51 self.assertEqual(fm.max_offset(), -1)
except TypeError: 52 52 except TypeError:
self.fail() 53 53 self.fail()
try: 54 54 try:
fm = FlashcardMask(None) 55 55 fm = FlashcardMask(None)
self.assertEqual(fm.max_offset(), -1) 56 56 self.assertEqual(fm.max_offset(), -1)
except TypeError: 57 57 except TypeError:
self.fail() 58 58 self.fail()
59 59
def test_iterable(self): 60 60 def test_iterable(self):
try: 61 61 try:
FlashcardMask(1) 62 62 FlashcardMask(1)
except TypeError as te: 63 63 except TypeError as te:
self.assertEqual(te.message, "Interval not a valid iterable") 64 64 self.assertEqual(te.message, "Interval not a valid iterable")
try: 65 65 try:
FlashcardMask([1, 2, 4]) 66 66 FlashcardMask([1, 2, 4])
except TypeError as te: 67 67 except TypeError as te:
self.assertEqual(te.message, "Interval not a valid iterable") 68 68 self.assertEqual(te.message, "Interval not a valid iterable")
69 69
def test_interval(self): 70 70 def test_interval(self):
try: 71 71 try:
FlashcardMask([[1, 2, 3], [1]]) 72 72 FlashcardMask([[1, 2, 3], [1]])
except TypeError as te: 73 73 except TypeError as te:
self.assertEqual(te.message, "Intervals must have exactly 2 elements, begin and end") 74 74 self.assertEqual(te.message, "Intervals must have exactly 2 elements, begin and end")
try: 75 75 try:
FlashcardMask([[1, 2], [1, 2, 4]]) 76 76 FlashcardMask([[1, 2], [1, 2, 4]])
except TypeError as te: 77 77 except TypeError as te:
self.assertEqual(te.message, "Intervals must have exactly 2 elements, begin and end") 78 78 self.assertEqual(te.message, "Intervals must have exactly 2 elements, begin and end")
try: 79 79 try:
FlashcardMask(([1, 2], [1])) 80 80 FlashcardMask(([1, 2], [1]))
except TypeError as te: 81 81 except TypeError as te:
self.assertEqual(te.message, "Intervals must have exactly 2 elements, begin and end") 82 82 self.assertEqual(te.message, "Intervals must have exactly 2 elements, begin and end")
try: 83 83 try:
FlashcardMask("[1,2,3]") 84 84 FlashcardMask("[1,2,3]")
except TypeError as te: 85 85 except TypeError as te:
self.assertEqual(te.message, "Intervals must have exactly 2 elements, begin and end") 86 86 self.assertEqual(te.message, "Intervals must have exactly 2 elements, begin and end")
87 87
def test_overlap(self): 88 88 def test_overlap(self):
try: 89 89 try:
FlashcardMask({(1, 2), (2, 5)}) 90 90 FlashcardMask({(1, 2), (2, 5)})
except OverlapIntervalException as oie: 91 91 except OverlapIntervalException as oie:
self.assertEqual(oie.message, "Invalid interval offsets in the mask") 92 92 self.assertEqual(oie.message, "Invalid interval offsets in the mask")
try: 93 93 try:
FlashcardMask({(1, 20), (12, 15)}) 94 94 FlashcardMask({(1, 20), (12, 15)})
except OverlapIntervalException as oie: 95 95 except OverlapIntervalException as oie:
self.assertEqual(oie.message, "Invalid interval offsets in the mask") 96 96 self.assertEqual(oie.message, "Invalid interval offsets in the mask")
try: 97 97 try:
FlashcardMask({(2, 1), (5, 2)}) 98 98 FlashcardMask({(2, 1), (5, 2)})
except OverlapIntervalException as oie: 99 99 except OverlapIntervalException as oie:
self.assertEqual(oie.message, "Invalid interval offsets in the mask") 100 100 self.assertEqual(oie.message, "Invalid interval offsets in the mask")
101 101
102 102
class FlashcardTests(TestCase): 103 103 class FlashcardTests(TestCase):
def setUp(self): 104 104 def setUp(self):
section = Section.objects.create(department='dept', 105 105 section = Section.objects.create(department='dept',
course_num='101a', 106 106 course_num='101a',
course_title='how 2 test', 107 107 course_title='how 2 test',
instructor='George Lucas', 108 108 instructor='George Lucas',
quarter='SP15') 109 109 quarter='SP15')
user = User.objects.create_user(email="none@none.com", password="1234") 110 110 user = User.objects.create_user(email="none@none.com", password="1234")
user.sections.add(section) 111 111 user.sections.add(section)
flashcard = Flashcard.objects.create(text="This is the text of the Flashcard", 112 112 flashcard = Flashcard.objects.create(text="This is the text of the Flashcard",
section=section, 113 113 section=section,
author=user, 114 114 author=user,
material_date=datetime.now(), 115 115 material_date=datetime.now(),
previous=None, 116 116 previous=None,
mask={(24, 34), (0, 4)}) 117 117 mask={(24, 34), (0, 4)})
user.save() 118 118 user.save()
section.save() 119 119 section.save()
flashcard.save() 120 120 flashcard.save()
121 121
def test_flashcard_edit(self): 122 122 def test_flashcard_edit(self):
user = User.objects.get(email="none@none.com") 123 123 user = User.objects.get(email="none@none.com")
user2 = User.objects.create_user(email="wow@wow.com", password="wow") 124 124 user2 = User.objects.create_user(email="wow@wow.com", password="wow")
section = Section.objects.get(course_title='how 2 test') 125 125 section = Section.objects.get(course_title='how 2 test')
user2.sections.add(section) 126 126 user2.sections.add(section)
user2.save() 127 127 user2.save()
flashcard = Flashcard.objects.filter(author=user).get(text="This is the text of the Flashcard") 128 128 flashcard = Flashcard.objects.filter(author=user).get(text="This is the text of the Flashcard")
pk_backup = flashcard.pk 129 129 pk_backup = flashcard.pk
self.assertTrue(user.is_in_section(section)) 130 130 self.assertTrue(user.is_in_section(section))
flashcard.edit(user, {}) 131 131 flashcard.edit(user, {})
self.assertIsNotNone(flashcard.pk) 132 132 self.assertIsNotNone(flashcard.pk)
UserFlashcard.objects.create(user=user2, flashcard=flashcard).save() 133 133 UserFlashcard.objects.create(user=user2, flashcard=flashcard).save()
flashcard.edit(user2, {'text': 'This is the new text'}) 134 134 flashcard.edit(user2, {'text': 'This is the new text'})
self.assertNotEqual(flashcard.pk, pk_backup) 135 135 self.assertNotEqual(flashcard.pk, pk_backup)
self.assertEqual(flashcard.text, 'This is the new text') 136 136 self.assertEqual(flashcard.text, 'This is the new text')
137 137
def test_mask_field(self): 138 138 def test_mask_field(self):
user = User.objects.get(email="none@none.com") 139 139 user = User.objects.get(email="none@none.com")
flashcard = Flashcard.objects.filter(author=user).get(text="This is the text of the Flashcard") 140 140 flashcard = Flashcard.objects.filter(author=user).get(text="This is the text of the Flashcard")
self.assertTrue(isinstance(flashcard.mask, set)) 141 141 self.assertTrue(isinstance(flashcard.mask, set))
self.assertTrue(all([isinstance(interval, tuple) for interval in flashcard.mask])) 142 142 self.assertTrue(all([isinstance(interval, tuple) for interval in flashcard.mask]))
blank1, blank2 = sorted(list(flashcard.mask)) 143 143 blank1, blank2 = sorted(list(flashcard.mask))
self.assertEqual(flashcard.text[slice(*blank1)], 'This') 144 144 self.assertEqual(flashcard.text[slice(*blank1)], 'This')
self.assertEqual(flashcard.text[slice(*blank2)], 'Flashcard') 145 145 self.assertEqual(flashcard.text[slice(*blank2)], 'Flashcard')
try: 146 146 try:
flashcard.mask = {(10, 34), (0, 14)} 147 147 flashcard.mask = {(10, 34), (0, 14)}
flashcard.save() 148 148 flashcard.save()
self.fail() 149 149 self.fail()
except OverlapIntervalException: 150 150 except OverlapIntervalException:
self.assertTrue(True) 151 151 self.assertTrue(True)
152 152
153 153
class UserFlashcardQuizTests(TestCase): 154 154 class UserFlashcardQuizTests(TestCase):
def setUp(self): 155 155 def setUp(self):
self.section = Section.objects.create(department='dept', 156 156 self.section = Section.objects.create(department='dept',
course_num='101a', 157 157 course_num='101a',
course_title='how 2 test', 158 158 course_title='how 2 test',
instructor='George Lucas', 159 159 instructor='George Lucas',
quarter='SP15') 160 160 quarter='SP15')
self.user = User.objects.create_user(email="none@none.com", password="1234") 161 161 self.user = User.objects.create_user(email="none@none.com", password="1234")
self.user.sections.add(self.section) 162 162 self.user.sections.add(self.section)
self.flashcard = Flashcard.objects.create(text="This is the text of the Flashcard", 163 163 self.flashcard = Flashcard.objects.create(text="This is the text of the Flashcard",
section=self.section, 164 164 section=self.section,
author=self.user, 165 165 author=self.user,
material_date=datetime.now(), 166 166 material_date=datetime.now(),
previous=None, 167 167 previous=None,
mask=[(24, 33), (0, 4)]) 168 168 mask=[(24, 33), (0, 4)])
self.user.save() 169 169 self.user.save()
self.section.save() 170 170 self.section.save()
self.flashcard.save() 171 171 self.flashcard.save()
self.user_flashcard = UserFlashcard.objects.create(flashcard=self.flashcard, 172 172 self.user_flashcard = UserFlashcard.objects.create(flashcard=self.flashcard,
user=self.user, 173 173 user=self.user,
mask=self.flashcard.mask, 174 174 mask=self.flashcard.mask,
pulled=datetime.now()) 175 175 pulled=datetime.now())
self.user_flashcard.save() 176 176 self.user_flashcard.save()
self.user_flashcard.refresh_from_db() 177 177 self.user_flashcard.refresh_from_db()
self.flashcard.refresh_from_db() 178 178 self.flashcard.refresh_from_db()
179 179
def test_quiz_request(self): 180 180 def test_quiz_request(self):
data = {'sections': [1], 'material_date_begin': QUARTER_START, 'material_date_end': QUARTER_END} 181 181 data = {'sections': [1], 'material_date_begin': QUARTER_START, 'material_date_end': QUARTER_END}
serializer = QuizRequestSerializer(data=data) 182 182 serializer = QuizRequestSerializer(data=data)
serializer.is_valid(raise_exception=True) 183 183 serializer.is_valid(raise_exception=True)
validated_data = serializer.create(serializer.validated_data) 184 184 validated_data = serializer.create(serializer.validated_data)
user_flashcard = UserFlashcard.objects.filter(user=self.user, 185 185 user_flashcard = UserFlashcard.objects.filter(user=self.user,
flashcard__section__pk__in=validated_data['sections'], 186 186 flashcard__section__pk__in=validated_data['sections'],
flashcard__material_date__gte=validated_data['material_date_begin'], 187 187 flashcard__material_date__gte=validated_data['material_date_begin'],
flashcard__material_date__lte=validated_data['material_date_end']) 188 188 flashcard__material_date__lte=validated_data['material_date_end'])
self.assertTrue(user_flashcard.exists()) 189 189 self.assertTrue(user_flashcard.exists())
190
user_flashcard = user_flashcard.first() 190 191 user_flashcard = user_flashcard.first()
self.assertEqual(user_flashcard, self.user_flashcard) 191 192 self.assertEqual(user_flashcard, self.user_flashcard)
193
mask = user_flashcard.mask.get_random_blank() 192 194 mask = user_flashcard.mask.get_random_blank()
word = user_flashcard.flashcard.text[slice(*mask)] 193 195 word = user_flashcard.flashcard.text[slice(*mask)]
user_flashcard_quiz = UserFlashcardQuiz(user_flashcard=user_flashcard, 194 196 user_flashcard_quiz = UserFlashcardQuiz(user_flashcard=user_flashcard,
blanked_word=word) 195 197 blanked_word=word)
user_flashcard_quiz.save() 196 198 user_flashcard_quiz.save()
self.assertTrue(isinstance(user_flashcard_quiz, UserFlashcardQuiz)) 197 199 self.assertTrue(isinstance(user_flashcard_quiz, UserFlashcardQuiz))
self.assertIn(mask, [[24, 33], [0, 4]]) 198 200 self.assertIn(mask, [[24, 33], [0, 4]])
201
user_flashcard_quiz.blanked_word = user_flashcard_quiz.user_flashcard.flashcard.text[slice(*mask)] 199 202 user_flashcard_quiz.blanked_word = user_flashcard_quiz.user_flashcard.flashcard.text[slice(*mask)]
self.assertIn(user_flashcard_quiz.blanked_word, ["This", "Flashcard"]) 200 203 self.assertIn(user_flashcard_quiz.blanked_word, ["This", "Flashcard"])
user_flashcard_quiz.save() 201 204 user_flashcard_quiz.save()
205
response = QuizResponseSerializer(instance=user_flashcard_quiz, mask=mask).data 202 206 response = QuizResponseSerializer(instance=user_flashcard_quiz, mask=mask).data
self.assertEqual(response['pk'], 1) 203 207 self.assertEqual(response['pk'], 1)
flashcards/views.py View file @ 7dcd94c
from random import sample 1 1 from random import sample
2 2
import django 3 3 import django
from django.contrib import auth 4 4 from django.contrib import auth
from django.shortcuts import get_object_or_404 5 5 from django.shortcuts import get_object_or_404
from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection, IsFlashcardReviewer 6 6 from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection, IsFlashcardReviewer
from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard, UserFlashcardQuiz 7 7 from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard, UserFlashcardQuiz
from flashcards.notifications import notify_new_card 8 8 from flashcards.notifications import notify_new_card
from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \ 9 9 from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \
PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \ 10 10 PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \
FlashcardUpdateSerializer, QuizRequestSerializer, QuizResponseSerializer, \ 11 11 FlashcardUpdateSerializer, QuizRequestSerializer, QuizResponseSerializer, \
QuizAnswerRequestSerializer, DeepSectionSerializer 12 12 QuizAnswerRequestSerializer, DeepSectionSerializer
from rest_framework.decorators import detail_route, permission_classes, api_view, list_route 13 13 from rest_framework.decorators import detail_route, permission_classes, api_view, list_route
from rest_framework.generics import ListAPIView, GenericAPIView 14 14 from rest_framework.generics import ListAPIView, GenericAPIView
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin 15 15 from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.permissions import IsAuthenticated 16 16 from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet 17 17 from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet
from django.core.mail import send_mail 18 18 from django.core.mail import send_mail
from django.contrib.auth import authenticate 19 19 from django.contrib.auth import authenticate
from django.contrib.auth.tokens import default_token_generator 20 20 from django.contrib.auth.tokens import default_token_generator
from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK 21 21 from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK
from rest_framework.response import Response 22 22 from rest_framework.response import Response
from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied 23 23 from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied
from simple_email_confirmation import EmailAddress 24 24 from simple_email_confirmation import EmailAddress
25 25
26 26
class SectionViewSet(ReadOnlyModelViewSet): 27 27 class SectionViewSet(ReadOnlyModelViewSet):
queryset = Section.objects.all() 28 28 queryset = Section.objects.all()
serializer_class = DeepSectionSerializer 29 29 serializer_class = DeepSectionSerializer
pagination_class = StandardResultsSetPagination 30 30 pagination_class = StandardResultsSetPagination
permission_classes = [IsAuthenticated] 31 31 permission_classes = [IsAuthenticated]
32 32
@detail_route(methods=['GET']) 33 33 @detail_route(methods=['GET'])
def flashcards(self, request, pk): 34 34 def flashcards(self, request, pk):
""" 35 35 """
Gets flashcards for a section, excluding hidden cards. 36 36 Gets flashcards for a section, excluding hidden cards.
Returned in strictly chronological order (material date). 37 37 Returned in strictly chronological order (material date).
""" 38 38 """
flashcards = Flashcard.cards_visible_to(request.user).filter(section=self.get_object()).all() 39 39 flashcards = Flashcard.cards_visible_to(request.user).filter(section=self.get_object()).all()
return Response(FlashcardSerializer(flashcards, many=True).data) 40 40 return Response(FlashcardSerializer(flashcards, many=True).data)
41 41
@detail_route(methods=['POST']) 42 42 @detail_route(methods=['POST'])
def enroll(self, request, pk): 43 43 def enroll(self, request, pk):
""" 44 44 """
Add the current user to a specified section 45 45 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. 46 46 If the class has a whitelist, but the user is not on the whitelist, the request will fail.
--- 47 47 ---
view_mocker: flashcards.api.mock_no_params 48 48 view_mocker: flashcards.api.mock_no_params
""" 49 49 """
try: 50 50 try:
self.get_object().enroll(request.user) 51 51 self.get_object().enroll(request.user)
except django.core.exceptions.PermissionDenied as e: 52 52 except django.core.exceptions.PermissionDenied as e:
raise PermissionDenied(e) 53 53 raise PermissionDenied(e)
except django.core.exceptions.ValidationError as e: 54 54 except django.core.exceptions.ValidationError as e:
raise ValidationError(e) 55 55 raise ValidationError(e)
return Response(status=HTTP_204_NO_CONTENT) 56 56 return Response(status=HTTP_204_NO_CONTENT)
57 57
@detail_route(methods=['POST']) 58 58 @detail_route(methods=['POST'])
def drop(self, request, pk): 59 59 def drop(self, request, pk):
""" 60 60 """
Remove the current user from a specified section 61 61 Remove the current user from a specified section
If the user is not in the class, the request will fail. 62 62 If the user is not in the class, the request will fail.
--- 63 63 ---
view_mocker: flashcards.api.mock_no_params 64 64 view_mocker: flashcards.api.mock_no_params
""" 65 65 """
try: 66 66 try:
self.get_object().drop(request.user) 67 67 self.get_object().drop(request.user)
except django.core.exceptions.PermissionDenied as e: 68 68 except django.core.exceptions.PermissionDenied as e:
raise PermissionDenied(e) 69 69 raise PermissionDenied(e)
except django.core.exceptions.ValidationError as e: 70 70 except django.core.exceptions.ValidationError as e:
raise ValidationError(e) 71 71 raise ValidationError(e)
return Response(status=HTTP_204_NO_CONTENT) 72 72 return Response(status=HTTP_204_NO_CONTENT)
73 73
@list_route(methods=['GET']) 74 74 @list_route(methods=['GET'])
def search(self, request): 75 75 def search(self, request):
""" 76 76 """
Returns a list of sections which match a user's query 77 77 Returns a list of sections which match a user's query
--- 78 78 ---
parameters: 79 79 parameters:
- name: q 80 80 - name: q
description: space-separated list of terms 81 81 description: space-separated list of terms
required: true 82 82 required: true
type: form 83 83 type: form
response_serializer: SectionSerializer 84 84 response_serializer: SectionSerializer
""" 85 85 """
query = request.GET.get('q', None) 86 86 query = request.GET.get('q', None)
if not query: return Response('[]') 87 87 if not query: return Response('[]')
qs = Section.search(query.split(' '))[:20] 88 88 qs = Section.search(query.split(' '))[:20]
data = SectionSerializer(qs, many=True).data 89 89 data = SectionSerializer(qs, many=True).data
return Response(data) 90 90 return Response(data)
91 91
@detail_route(methods=['GET']) 92 92 @detail_route(methods=['GET'])
def deck(self, request, pk): 93 93 def deck(self, request, pk):
""" 94 94 """
Gets the contents of a user's deck for a given section. 95 95 Gets the contents of a user's deck for a given section.
""" 96 96 """
qs = request.user.get_deck(self.get_object()) 97 97 qs = request.user.get_deck(self.get_object())
serializer = FlashcardSerializer(qs, many=True) 98 98 serializer = FlashcardSerializer(qs, many=True)
return Response(serializer.data) 99 99 return Response(serializer.data)
100 100
@detail_route(methods=['GET'], permission_classes=[IsAuthenticated]) 101 101 @detail_route(methods=['GET'], permission_classes=[IsAuthenticated])
def ordered_deck(self, request, pk): 102 102 def ordered_deck(self, request, pk):
""" 103 103 """
Get a chronological order by material_date of flashcards for a section. 104 104 Get a chronological order by material_date of flashcards for a section.
This excludes hidden card. 105 105 This excludes hidden card.
""" 106 106 """
qs = request.user.get_deck(self.get_object()).order_by('-material_date') 107 107 qs = request.user.get_deck(self.get_object()).order_by('-material_date')
serializer = FlashcardSerializer(qs, many=True) 108 108 serializer = FlashcardSerializer(qs, many=True)
return Response(serializer.data) 109 109 return Response(serializer.data)
110 110
@detail_route(methods=['GET']) 111 111 @detail_route(methods=['GET'])
def feed(self, request, pk): 112 112 def feed(self, request, pk):
""" 113 113 """
Gets the contents of a user's feed for a section. 114 114 Gets the contents of a user's feed for a section.
Exclude cards that are already in the user's deck 115 115 Exclude cards that are already in the user's deck
""" 116 116 """
serializer = FlashcardSerializer(self.get_object().get_feed_for_user(request.user), many=True) 117 117 serializer = FlashcardSerializer(self.get_object().get_feed_for_user(request.user), many=True)
return Response(serializer.data) 118 118 return Response(serializer.data)
119 119
120 120
class UserSectionListView(ListAPIView): 121 121 class UserSectionListView(ListAPIView):
serializer_class = DeepSectionSerializer 122 122 serializer_class = DeepSectionSerializer
permission_classes = [IsAuthenticated] 123 123 permission_classes = [IsAuthenticated]
124 124
def get_queryset(self): 125 125 def get_queryset(self):
return self.request.user.sections.all() 126 126 return self.request.user.sections.all()
127 127
def paginate_queryset(self, queryset): return None 128 128 def paginate_queryset(self, queryset): return None
129 129
130 130
class UserDetail(GenericAPIView): 131 131 class UserDetail(GenericAPIView):
serializer_class = UserSerializer 132 132 serializer_class = UserSerializer
permission_classes = [IsAuthenticated] 133 133 permission_classes = [IsAuthenticated]
134 134
def patch(self, request, format=None): 135 135 def patch(self, request, format=None):
""" 136 136 """
Updates the user's password, or verifies their email address 137 137 Updates the user's password, or verifies their email address
--- 138 138 ---
request_serializer: UserUpdateSerializer 139 139 request_serializer: UserUpdateSerializer
response_serializer: UserSerializer 140 140 response_serializer: UserSerializer
""" 141 141 """
data = UserUpdateSerializer(data=request.data, context={'user': request.user}) 142 142 data = UserUpdateSerializer(data=request.data, context={'user': request.user})
data.is_valid(raise_exception=True) 143 143 data.is_valid(raise_exception=True)
data = data.validated_data 144 144 data = data.validated_data
145 145
if 'new_password' in data: 146 146 if 'new_password' in data:
if not request.user.check_password(data['old_password']): 147 147 if not request.user.check_password(data['old_password']):
raise ValidationError('old_password is incorrect') 148 148 raise ValidationError('old_password is incorrect')
request.user.set_password(data['new_password']) 149 149 request.user.set_password(data['new_password'])
request.user.save() 150 150 request.user.save()
151 151
if 'confirmation_key' in data: 152 152 if 'confirmation_key' in data:
try: 153 153 try:
request.user.confirm_email(data['confirmation_key']) 154 154 request.user.confirm_email(data['confirmation_key'])
except EmailAddress.DoesNotExist: 155 155 except EmailAddress.DoesNotExist:
raise ValidationError('confirmation_key is invalid') 156 156 raise ValidationError('confirmation_key is invalid')
157 157
return Response(UserSerializer(request.user).data) 158 158 return Response(UserSerializer(request.user).data)
159 159
def get(self, request, format=None): 160 160 def get(self, request, format=None):
""" 161 161 """
Return data about the user 162 162 Return data about the user
--- 163 163 ---
response_serializer: UserSerializer 164 164 response_serializer: UserSerializer
""" 165 165 """
serializer = UserSerializer(request.user, context={'request': request}) 166 166 serializer = UserSerializer(request.user, context={'request': request})
return Response(serializer.data) 167 167 return Response(serializer.data)
168 168
def delete(self, request): 169 169 def delete(self, request):
""" 170 170 """
Irrevocably delete the user and their data 171 171 Irrevocably delete the user and their data
172 172
Yes, really 173 173 Yes, really
""" 174 174 """
request.user.delete() 175 175 request.user.delete()
return Response(status=HTTP_204_NO_CONTENT) 176 176 return Response(status=HTTP_204_NO_CONTENT)
177 177
178 178
@api_view(['POST']) 179 179 @api_view(['POST'])
def register(request, format=None): 180 180 def register(request, format=None):
""" 181 181 """
Register a new user 182 182 Register a new user
--- 183 183 ---
request_serializer: EmailPasswordSerializer 184 184 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 185 185 response_serializer: UserSerializer
""" 186 186 """
data = RegistrationSerializer(data=request.data) 187 187 data = RegistrationSerializer(data=request.data)
data.is_valid(raise_exception=True) 188 188 data.is_valid(raise_exception=True)
189 189
User.objects.create_user(**data.validated_data) 190 190 User.objects.create_user(**data.validated_data)
user = authenticate(**data.validated_data) 191 191 user = authenticate(**data.validated_data)
auth.login(request, user) 192 192 auth.login(request, user)
193 193
return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED) 194 194 return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED)
195 195
196 196
@api_view(['POST']) 197 197 @api_view(['POST'])
def login(request): 198 198 def login(request):
""" 199 199 """
Authenticates user and returns user data if valid. 200 200 Authenticates user and returns user data if valid.
--- 201 201 ---
request_serializer: EmailPasswordSerializer 202 202 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 203 203 response_serializer: UserSerializer
""" 204 204 """
205 205
data = EmailPasswordSerializer(data=request.data) 206 206 data = EmailPasswordSerializer(data=request.data)
data.is_valid(raise_exception=True) 207 207 data.is_valid(raise_exception=True)
user = authenticate(**data.validated_data) 208 208 user = authenticate(**data.validated_data)
209 209
if user is None: 210 210 if user is None:
raise AuthenticationFailed('Invalid email or password') 211 211 raise AuthenticationFailed('Invalid email or password')
if not user.is_active: 212 212 if not user.is_active:
raise NotAuthenticated('Account is disabled') 213 213 raise NotAuthenticated('Account is disabled')
auth.login(request, user) 214 214 auth.login(request, user)
return Response(UserSerializer(request.user).data) 215 215 return Response(UserSerializer(request.user).data)
216 216
217 217
@api_view(['POST']) 218 218 @api_view(['POST'])
@permission_classes((IsAuthenticated,)) 219 219 @permission_classes((IsAuthenticated,))
def logout(request, format=None): 220 220 def logout(request, format=None):
""" 221 221 """
Logs the authenticated user out. 222 222 Logs the authenticated user out.
""" 223 223 """
auth.logout(request) 224 224 auth.logout(request)
return Response(status=HTTP_204_NO_CONTENT) 225 225 return Response(status=HTTP_204_NO_CONTENT)
226 226
227 227
@api_view(['POST']) 228 228 @api_view(['POST'])
def request_password_reset(request, format=None): 229 229 def request_password_reset(request, format=None):
""" 230 230 """
Send a password reset token/link to the provided email. 231 231 Send a password reset token/link to the provided email.
--- 232 232 ---
request_serializer: PasswordResetRequestSerializer 233 233 request_serializer: PasswordResetRequestSerializer
""" 234 234 """
data = PasswordResetRequestSerializer(data=request.data) 235 235 data = PasswordResetRequestSerializer(data=request.data)
data.is_valid(raise_exception=True) 236 236 data.is_valid(raise_exception=True)
get_object_or_404(User, email=data['email'].value).request_password_reset() 237 237 get_object_or_404(User, email=data['email'].value).request_password_reset()
return Response(status=HTTP_204_NO_CONTENT) 238 238 return Response(status=HTTP_204_NO_CONTENT)
239 239
240 240
@api_view(['POST']) 241 241 @api_view(['POST'])
def reset_password(request, format=None): 242 242 def reset_password(request, format=None):
""" 243 243 """
Updates user's password to new password if token is valid. 244 244 Updates user's password to new password if token is valid.
--- 245 245 ---
request_serializer: PasswordResetSerializer 246 246 request_serializer: PasswordResetSerializer
""" 247 247 """
data = PasswordResetSerializer(data=request.data) 248 248 data = PasswordResetSerializer(data=request.data)
data.is_valid(raise_exception=True) 249 249 data.is_valid(raise_exception=True)
250 250
user = User.objects.get(id=data['uid'].value) 251 251 user = User.objects.get(id=data['uid'].value)
# Check token validity. 252 252 # Check token validity.
253 253
if default_token_generator.check_token(user, data['token'].value): 254 254 if default_token_generator.check_token(user, data['token'].value):
user.set_password(data['new_password'].value) 255 255 user.set_password(data['new_password'].value)
user.save() 256 256 user.save()
else: 257 257 else:
raise ValidationError('Could not verify reset token') 258 258 raise ValidationError('Could not verify reset token')
return Response(status=HTTP_204_NO_CONTENT) 259 259 return Response(status=HTTP_204_NO_CONTENT)
260 260
261 261
class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin): 262 262 class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin):
queryset = Flashcard.objects.all() 263 263 queryset = Flashcard.objects.all()
serializer_class = FlashcardSerializer 264 264 serializer_class = FlashcardSerializer
permission_classes = [IsAuthenticated, IsEnrolledInAssociatedSection] 265 265 permission_classes = [IsAuthenticated, IsEnrolledInAssociatedSection]
266 266
# Override create in CreateModelMixin 267 267 # Override create in CreateModelMixin
def create(self, request, *args, **kwargs): 268 268 def create(self, request, *args, **kwargs):
serializer = FlashcardSerializer(data=request.data) 269 269 serializer = FlashcardSerializer(data=request.data)
serializer.is_valid(raise_exception=True) 270 270 serializer.is_valid(raise_exception=True)
data = serializer.validated_data 271 271 data = serializer.validated_data
if not request.user.is_in_section(data['section']): 272 272 if not request.user.is_in_section(data['section']):
raise PermissionDenied('The user is not enrolled in that section') 273 273 raise PermissionDenied('The user is not enrolled in that section')
data['author'] = request.user 274 274 data['author'] = request.user
flashcard = Flashcard.objects.create(**data) 275 275 flashcard = Flashcard.objects.create(**data)
self.perform_create(flashcard) 276 276 self.perform_create(flashcard)
notify_new_card(flashcard) 277 277 notify_new_card(flashcard)
headers = self.get_success_headers(data) 278 278 headers = self.get_success_headers(data)
request.user.pull(flashcard) 279 279 request.user.pull(flashcard)
response_data = FlashcardSerializer(flashcard).data 280 280 response_data = FlashcardSerializer(flashcard).data
281 281
return Response(response_data, status=HTTP_201_CREATED, headers=headers) 282 282 return Response(response_data, status=HTTP_201_CREATED, headers=headers)
283 283
@detail_route(methods=['POST']) 284 284 @detail_route(methods=['POST'])
def unhide(self, request, pk): 285 285 def unhide(self, request, pk):
""" 286 286 """
Unhide the given card 287 287 Unhide the given card
--- 288 288 ---
view_mocker: flashcards.api.mock_no_params 289 289 view_mocker: flashcards.api.mock_no_params
""" 290 290 """
hide = get_object_or_404(FlashcardHide, user=request.user, flashcard=self.get_object()) 291 291 hide = get_object_or_404(FlashcardHide, user=request.user, flashcard=self.get_object())
hide.delete() 292 292 hide.delete()
return Response(status=HTTP_204_NO_CONTENT) 293 293 return Response(status=HTTP_204_NO_CONTENT)
294 294
@detail_route(methods=['POST']) 295 295 @detail_route(methods=['POST'])
def report(self, request, pk): 296 296 def report(self, request, pk):
""" 297 297 """
Hide the given card 298 298 Hide the given card
--- 299 299 ---
view_mocker: flashcards.api.mock_no_params 300 300 view_mocker: flashcards.api.mock_no_params
""" 301 301 """
self.get_object().report(request.user) 302 302 self.get_object().report(request.user)
return Response(status=HTTP_204_NO_CONTENT) 303 303 return Response(status=HTTP_204_NO_CONTENT)
304 304
hide = report 305 305 hide = report
306 306
@detail_route(methods=['POST']) 307 307 @detail_route(methods=['POST'])
def pull(self, request, pk): 308 308 def pull(self, request, pk):
""" 309 309 """
Pull a card from the live feed into the user's deck. 310 310 Pull a card from the live feed into the user's deck.
--- 311 311 ---
view_mocker: flashcards.api.mock_no_params 312 312 view_mocker: flashcards.api.mock_no_params
""" 313 313 """
314 314
request.user.pull(self.get_object()) 315 315 request.user.pull(self.get_object())
return Response(status=HTTP_204_NO_CONTENT) 316 316 return Response(status=HTTP_204_NO_CONTENT)
317 317
318 318
@detail_route(methods=['POST']) 319 319 @detail_route(methods=['POST'])
def unpull(self, request, pk): 320 320 def unpull(self, request, pk):
""" 321 321 """
Unpull a card from the user's deck 322 322 Unpull a card from the user's deck
--- 323 323 ---
view_mocker: flashcards.api.mock_no_params 324 324 view_mocker: flashcards.api.mock_no_params
""" 325 325 """
user = request.user 326 326 user = request.user
flashcard = self.get_object() 327 327 flashcard = self.get_object()
user.unpull(flashcard) 328 328 user.unpull(flashcard)
return Response(status=HTTP_204_NO_CONTENT) 329 329 return Response(status=HTTP_204_NO_CONTENT)
330 330
def partial_update(self, request, *args, **kwargs): 331 331 def partial_update(self, request, *args, **kwargs):
""" 332 332 """
Edit settings related to a card for the user. 333 333 Edit settings related to a card for the user.
--- 334 334 ---
request_serializer: FlashcardUpdateSerializer 335 335 request_serializer: FlashcardUpdateSerializer
""" 336 336 """
user = request.user 337 337 user = request.user
flashcard = self.get_object() 338 338 flashcard = self.get_object()
data = FlashcardUpdateSerializer(data=request.data) 339 339 data = FlashcardUpdateSerializer(data=request.data)