Commit bd04c9af548247ca9b5d6cbadb2cb0d3005bc650

Authored by Andrew Buss
1 parent 8e66c8186f
Exists in master

Only retrieve lecture periods for a section if it's needed

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

flashcards/serializers.py View file @ bd04c9a
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
import pytz 5 5 import pytz
from flashcards.models import Section, LecturePeriod, User, Flashcard 6 6 from flashcards.models import Section, LecturePeriod, User, Flashcard
from flashcards.validators import FlashcardMask, OverlapIntervalException 7 7 from flashcards.validators import FlashcardMask, OverlapIntervalException
from rest_framework import serializers 8 8 from rest_framework import serializers
from rest_framework.fields import EmailField, BooleanField, CharField, IntegerField, DateTimeField 9 9 from rest_framework.fields import EmailField, BooleanField, CharField, IntegerField, DateTimeField
from rest_framework.serializers import ModelSerializer, Serializer 10 10 from rest_framework.serializers import ModelSerializer, Serializer
from rest_framework.validators import UniqueValidator 11 11 from rest_framework.validators import UniqueValidator
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):
lectures = LecturePeriodSerializer(source='lectureperiod_set', many=True, read_only=True) 72
lecture_times = CharField() 73 72 lecture_times = CharField()
short_name = CharField() 74 73 short_name = CharField()
long_name = CharField() 75 74 long_name = CharField()
76 75
class Meta: 77 76 class Meta:
model = Section 78 77 model = Section
78
79 class DeepSectionSerializer(SectionSerializer):
80 lectures = LecturePeriodSerializer(source='lectureperiod_set', many=True, read_only=True)
81
79 82
80 83
class UserSerializer(ModelSerializer): 81 84 class UserSerializer(ModelSerializer):
email = EmailField(required=False) 82 85 email = EmailField(required=False)
sections = SectionSerializer(many=True) 83 86 sections = SectionSerializer(many=True)
is_confirmed = BooleanField() 84 87 is_confirmed = BooleanField()
85 88
class Meta: 86 89 class Meta:
model = User 87 90 model = User
fields = ("sections", "email", "is_confirmed", "last_login", "date_joined") 88 91 fields = ("sections", "email", "is_confirmed", "last_login", "date_joined")
89 92
90 93
class MaskFieldSerializer(serializers.Field): 91 94 class MaskFieldSerializer(serializers.Field):
default_error_messages = { 92 95 default_error_messages = {
'max_length': 'Ensure this field has no more than {max_length} characters.', 93 96 'max_length': 'Ensure this field has no more than {max_length} characters.',
'interval': 'Ensure this field has valid intervals.', 94 97 'interval': 'Ensure this field has valid intervals.',
'overlap': 'Ensure this field does not have overlapping intervals.' 95 98 'overlap': 'Ensure this field does not have overlapping intervals.'
} 96 99 }
97 100
def to_representation(self, value): 98 101 def to_representation(self, value):
return dumps(list(self._make_mask(value))) 99 102 return dumps(list(self._make_mask(value)))
100 103
def to_internal_value(self, value): 101 104 def to_internal_value(self, value):
return self._make_mask(loads(value)) 102 105 return self._make_mask(loads(value))
103 106
def _make_mask(self, data): 104 107 def _make_mask(self, data):
try: 105 108 try:
mask = FlashcardMask(data) 106 109 mask = FlashcardMask(data)
except ValueError: 107 110 except ValueError:
raise serializers.ValidationError("Invalid JSON for MaskField") 108 111 raise serializers.ValidationError("Invalid JSON for MaskField")
except TypeError: 109 112 except TypeError:
raise serializers.ValidationError("Invalid data for MaskField.") 110 113 raise serializers.ValidationError("Invalid data for MaskField.")
except OverlapIntervalException: 111 114 except OverlapIntervalException:
raise serializers.ValidationError("Invalid intervals for MaskField data.") 112 115 raise serializers.ValidationError("Invalid intervals for MaskField data.")
if len(mask) > 32: 113 116 if len(mask) > 32:
raise serializers.ValidationError("Too many intervals in the mask.") 114 117 raise serializers.ValidationError("Too many intervals in the mask.")
return mask 115 118 return mask
116 119
117 120
class FlashcardSerializer(ModelSerializer): 118 121 class FlashcardSerializer(ModelSerializer):
is_hidden = BooleanField(read_only=True) 119 122 is_hidden = BooleanField(read_only=True)
hide_reason = CharField(read_only=True) 120 123 hide_reason = CharField(read_only=True)
material_date = DateTimeField(default=now) 121 124 material_date = DateTimeField(default=now)
mask = MaskFieldSerializer(allow_null=True) 122 125 mask = MaskFieldSerializer(allow_null=True)
123 126
def validate_material_date(self, value): 124 127 def validate_material_date(self, value):
utc = pytz.UTC 125 128 utc = pytz.UTC
# TODO: make this dynamic 126 129 # TODO: make this dynamic
flashcards/views.py View file @ bd04c9a
import django 1 1 import django
2 2
from django.contrib import auth 3 3 from django.contrib import auth
from django.shortcuts import get_object_or_404 4 4 from django.shortcuts import get_object_or_404
from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection 5 5 from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection
from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard 6 6 from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard
from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \ 7 7 from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \
PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \ 8 8 PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \
FlashcardUpdateSerializer 9 9 FlashcardUpdateSerializer, DeepSectionSerializer
from rest_framework.decorators import detail_route, permission_classes, api_view, list_route 10 10 from rest_framework.decorators import detail_route, permission_classes, api_view, list_route
from rest_framework.generics import ListAPIView, GenericAPIView 11 11 from rest_framework.generics import ListAPIView, GenericAPIView
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin 12 12 from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin
from rest_framework.permissions import IsAuthenticated 13 13 from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet 14 14 from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet
from django.core.mail import send_mail 15 15 from django.core.mail import send_mail
from django.contrib.auth import authenticate 16 16 from django.contrib.auth import authenticate
from django.contrib.auth.tokens import default_token_generator 17 17 from django.contrib.auth.tokens import default_token_generator
from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK 18 18 from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK
from rest_framework.response import Response 19 19 from rest_framework.response import Response
from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied 20 20 from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied
from simple_email_confirmation import EmailAddress 21 21 from simple_email_confirmation import EmailAddress
22 22
23 23
class SectionViewSet(ReadOnlyModelViewSet): 24 24 class SectionViewSet(ReadOnlyModelViewSet):
queryset = Section.objects.all() 25 25 queryset = Section.objects.all()
serializer_class = SectionSerializer 26 26 serializer_class = DeepSectionSerializer
pagination_class = StandardResultsSetPagination 27 27 pagination_class = StandardResultsSetPagination
permission_classes = [IsAuthenticated] 28 28 permission_classes = [IsAuthenticated]
29 29
@detail_route(methods=['GET']) 30 30 @detail_route(methods=['GET'])
def flashcards(self, request, pk): 31 31 def flashcards(self, request, pk):
""" 32 32 """
Gets flashcards for a section, excluding hidden cards. 33 33 Gets flashcards for a section, excluding hidden cards.
Returned in strictly chronological order (material date). 34 34 Returned in strictly chronological order (material date).
""" 35 35 """
flashcards = Flashcard.cards_visible_to(request.user).filter(section=self.get_object()).all() 36 36 flashcards = Flashcard.cards_visible_to(request.user).filter(section=self.get_object()).all()
return Response(FlashcardSerializer(flashcards, many=True).data) 37 37 return Response(FlashcardSerializer(flashcards, many=True).data)
38 38
@detail_route(methods=['post']) 39 39 @detail_route(methods=['post'])
def enroll(self, request, pk): 40 40 def enroll(self, request, pk):
""" 41 41 """
Add the current user to a specified section 42 42 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. 43 43 If the class has a whitelist, but the user is not on the whitelist, the request will fail.
--- 44 44 ---
view_mocker: flashcards.api.mock_no_params 45 45 view_mocker: flashcards.api.mock_no_params
""" 46 46 """
47 47
self.get_object().enroll(request.user) 48 48 self.get_object().enroll(request.user)
return Response(status=HTTP_204_NO_CONTENT) 49 49 return Response(status=HTTP_204_NO_CONTENT)
50 50
@detail_route(methods=['post']) 51 51 @detail_route(methods=['post'])
def drop(self, request, pk): 52 52 def drop(self, request, pk):
""" 53 53 """
Remove the current user from a specified section 54 54 Remove the current user from a specified section
If the user is not in the class, the request will fail. 55 55 If the user is not in the class, the request will fail.
--- 56 56 ---
view_mocker: flashcards.api.mock_no_params 57 57 view_mocker: flashcards.api.mock_no_params
""" 58 58 """
try: 59 59 try:
self.get_object().drop(request.user) 60 60 self.get_object().drop(request.user)
except django.core.exceptions.PermissionDenied as e: 61 61 except django.core.exceptions.PermissionDenied as e:
raise PermissionDenied(e) 62 62 raise PermissionDenied(e)
except django.core.exceptions.ValidationError as e: 63 63 except django.core.exceptions.ValidationError as e:
raise ValidationError(e) 64 64 raise ValidationError(e)
return Response(status=HTTP_204_NO_CONTENT) 65 65 return Response(status=HTTP_204_NO_CONTENT)
66 66
@list_route(methods=['GET']) 67 67 @list_route(methods=['GET'])
def search(self, request): 68 68 def search(self, request):
""" 69 69 """
Returns a list of sections which match a user's query 70 70 Returns a list of sections which match a user's query
--- 71 71 ---
parameters: 72 72 parameters:
- name: q 73 73 - name: q
description: space-separated list of terms 74 74 description: space-separated list of terms
required: true 75 75 required: true
type: form 76 76 type: form
77 response_serializer: SectionSerializer
""" 77 78 """
query = request.GET.get('q', None) 78 79 query = request.GET.get('q', None)
if not query: return Response('[]') 79 80 if not query: return Response('[]')
qs = Section.search(query.split(' '))[:20] 80 81 qs = Section.search(query.split(' '))[:20]
serializer = SectionSerializer(qs, many=True) 81 82 serializer = SectionSerializer(qs, many=True)
return Response(serializer.data) 82 83 return Response(serializer.data)
83 84
@detail_route(methods=['GET']) 84 85 @detail_route(methods=['GET'])
def deck(self, request, pk): 85 86 def deck(self, request, pk):
""" 86 87 """
Gets the contents of a user's deck for a given section. 87 88 Gets the contents of a user's deck for a given section.
""" 88 89 """
qs = request.user.get_deck(self.get_object()) 89 90 qs = request.user.get_deck(self.get_object())
serializer = FlashcardSerializer(qs, many=True) 90 91 serializer = FlashcardSerializer(qs, many=True)
return Response(serializer.data) 91 92 return Response(serializer.data)
92 93
@detail_route(methods=['get'], permission_classes=[IsAuthenticated]) 93 94 @detail_route(methods=['get'], permission_classes=[IsAuthenticated])
def ordered_deck(self, request, pk): 94 95 def ordered_deck(self, request, pk):
""" 95 96 """
Get a chronological order by material_date of flashcards for a section. 96 97 Get a chronological order by material_date of flashcards for a section.
This excludes hidden card. 97 98 This excludes hidden card.
""" 98 99 """
qs = request.user.get_deck(self.get_object()).order_by('-material_date') 99 100 qs = request.user.get_deck(self.get_object()).order_by('-material_date')
serializer = FlashcardSerializer(qs, many=True) 100 101 serializer = FlashcardSerializer(qs, many=True)
return Response(serializer.data) 101 102 return Response(serializer.data)
102 103
@detail_route(methods=['GET']) 103 104 @detail_route(methods=['GET'])
def feed(self, request, pk): 104 105 def feed(self, request, pk):
""" 105 106 """
Gets the contents of a user's feed for a section. 106 107 Gets the contents of a user's feed for a section.
Exclude cards that are already in the user's deck 107 108 Exclude cards that are already in the user's deck
""" 108 109 """
serializer = FlashcardSerializer(self.get_object().get_feed_for_user(request.user), many=True) 109 110 serializer = FlashcardSerializer(self.get_object().get_feed_for_user(request.user), many=True)
return Response(serializer.data) 110 111 return Response(serializer.data)
111 112
112 113
class UserSectionListView(ListAPIView): 113 114 class UserSectionListView(ListAPIView):
serializer_class = SectionSerializer 114 115 serializer_class = DeepSectionSerializer
permission_classes = [IsAuthenticated] 115 116 permission_classes = [IsAuthenticated]
116 117
def get_queryset(self): 117 118 def get_queryset(self):
return self.request.user.sections.all() 118 119 return self.request.user.sections.all()
119 120
def paginate_queryset(self, queryset): return None 120 121 def paginate_queryset(self, queryset): return None
121 122
122 123
class UserDetail(GenericAPIView): 123 124 class UserDetail(GenericAPIView):
serializer_class = UserSerializer 124 125 serializer_class = UserSerializer
permission_classes = [IsAuthenticated] 125 126 permission_classes = [IsAuthenticated]
126 127
def patch(self, request, format=None): 127 128 def patch(self, request, format=None):
""" 128 129 """
Updates the user's password, or verifies their email address 129 130 Updates the user's password, or verifies their email address
--- 130 131 ---
request_serializer: UserUpdateSerializer 131 132 request_serializer: UserUpdateSerializer
response_serializer: UserSerializer 132 133 response_serializer: UserSerializer
""" 133 134 """
data = UserUpdateSerializer(data=request.data, context={'user': request.user}) 134 135 data = UserUpdateSerializer(data=request.data, context={'user': request.user})
data.is_valid(raise_exception=True) 135 136 data.is_valid(raise_exception=True)
data = data.validated_data 136 137 data = data.validated_data
137 138
if 'new_password' in data: 138 139 if 'new_password' in data:
if not request.user.check_password(data['old_password']): 139 140 if not request.user.check_password(data['old_password']):
raise ValidationError('old_password is incorrect') 140 141 raise ValidationError('old_password is incorrect')
request.user.set_password(data['new_password']) 141 142 request.user.set_password(data['new_password'])
request.user.save() 142 143 request.user.save()
143 144
if 'confirmation_key' in data: 144 145 if 'confirmation_key' in data:
try: 145 146 try:
request.user.confirm_email(data['confirmation_key']) 146 147 request.user.confirm_email(data['confirmation_key'])
except EmailAddress.DoesNotExist: 147 148 except EmailAddress.DoesNotExist:
raise ValidationError('confirmation_key is invalid') 148 149 raise ValidationError('confirmation_key is invalid')
149 150
return Response(UserSerializer(request.user).data) 150 151 return Response(UserSerializer(request.user).data)
151 152
def get(self, request, format=None): 152 153 def get(self, request, format=None):
""" 153 154 """
Return data about the user 154 155 Return data about the user
--- 155 156 ---
response_serializer: UserSerializer 156 157 response_serializer: UserSerializer
""" 157 158 """
serializer = UserSerializer(request.user, context={'request': request}) 158 159 serializer = UserSerializer(request.user, context={'request': request})
return Response(serializer.data) 159 160 return Response(serializer.data)
160 161
def delete(self, request): 161 162 def delete(self, request):
""" 162 163 """
Irrevocably delete the user and their data 163 164 Irrevocably delete the user and their data
164 165
Yes, really 165 166 Yes, really
""" 166 167 """
request.user.delete() 167 168 request.user.delete()
return Response(status=HTTP_204_NO_CONTENT) 168 169 return Response(status=HTTP_204_NO_CONTENT)
169 170
170 171
@api_view(['POST']) 171 172 @api_view(['POST'])
def register(request, format=None): 172 173 def register(request, format=None):
""" 173 174 """
Register a new user 174 175 Register a new user
--- 175 176 ---
request_serializer: EmailPasswordSerializer 176 177 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 177 178 response_serializer: UserSerializer
""" 178 179 """
data = RegistrationSerializer(data=request.data) 179 180 data = RegistrationSerializer(data=request.data)
data.is_valid(raise_exception=True) 180 181 data.is_valid(raise_exception=True)
181 182
User.objects.create_user(**data.validated_data) 182 183 User.objects.create_user(**data.validated_data)
user = authenticate(**data.validated_data) 183 184 user = authenticate(**data.validated_data)
auth.login(request, user) 184 185 auth.login(request, user)
185 186
return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED) 186 187 return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED)
187 188
188 189
@api_view(['POST']) 189 190 @api_view(['POST'])
def login(request): 190 191 def login(request):
""" 191 192 """
Authenticates user and returns user data if valid. 192 193 Authenticates user and returns user data if valid.
--- 193 194 ---
request_serializer: EmailPasswordSerializer 194 195 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 195 196 response_serializer: UserSerializer
""" 196 197 """
197 198
data = EmailPasswordSerializer(data=request.data) 198 199 data = EmailPasswordSerializer(data=request.data)
data.is_valid(raise_exception=True) 199 200 data.is_valid(raise_exception=True)
user = authenticate(**data.validated_data) 200 201 user = authenticate(**data.validated_data)
201 202
if user is None: 202 203 if user is None:
raise AuthenticationFailed('Invalid email or password') 203 204 raise AuthenticationFailed('Invalid email or password')
if not user.is_active: 204 205 if not user.is_active:
raise NotAuthenticated('Account is disabled') 205 206 raise NotAuthenticated('Account is disabled')
auth.login(request, user) 206 207 auth.login(request, user)
return Response(UserSerializer(request.user).data) 207 208 return Response(UserSerializer(request.user).data)
208 209
209 210
@api_view(['POST']) 210 211 @api_view(['POST'])
@permission_classes((IsAuthenticated, )) 211 212 @permission_classes((IsAuthenticated, ))
def logout(request, format=None): 212 213 def logout(request, format=None):
""" 213 214 """
Logs the authenticated user out. 214 215 Logs the authenticated user out.
""" 215 216 """
auth.logout(request) 216 217 auth.logout(request)
return Response(status=HTTP_204_NO_CONTENT) 217 218 return Response(status=HTTP_204_NO_CONTENT)
218 219
219 220
@api_view(['POST']) 220 221 @api_view(['POST'])
def request_password_reset(request, format=None): 221 222 def request_password_reset(request, format=None):
""" 222 223 """
Send a password reset token/link to the provided email. 223 224 Send a password reset token/link to the provided email.
--- 224 225 ---
request_serializer: PasswordResetRequestSerializer 225 226 request_serializer: PasswordResetRequestSerializer
""" 226 227 """
data = PasswordResetRequestSerializer(data=request.data) 227 228 data = PasswordResetRequestSerializer(data=request.data)
data.is_valid(raise_exception=True) 228 229 data.is_valid(raise_exception=True)
get_object_or_404(User, email=data['email'].value).request_password_reset() 229 230 get_object_or_404(User, email=data['email'].value).request_password_reset()
return Response(status=HTTP_204_NO_CONTENT) 230 231 return Response(status=HTTP_204_NO_CONTENT)
231 232
232 233
@api_view(['POST']) 233 234 @api_view(['POST'])
def reset_password(request, format=None): 234 235 def reset_password(request, format=None):
""" 235 236 """
Updates user's password to new password if token is valid. 236 237 Updates user's password to new password if token is valid.
--- 237 238 ---
request_serializer: PasswordResetSerializer 238 239 request_serializer: PasswordResetSerializer
""" 239 240 """
data = PasswordResetSerializer(data=request.data) 240 241 data = PasswordResetSerializer(data=request.data)
data.is_valid(raise_exception=True) 241 242 data.is_valid(raise_exception=True)
242 243
user = User.objects.get(id=data['uid'].value) 243 244 user = User.objects.get(id=data['uid'].value)
# Check token validity. 244 245 # Check token validity.
245 246
if default_token_generator.check_token(user, data['token'].value): 246 247 if default_token_generator.check_token(user, data['token'].value):
user.set_password(data['new_password'].value) 247 248 user.set_password(data['new_password'].value)
user.save() 248 249 user.save()
else: 249 250 else:
raise ValidationError('Could not verify reset token') 250 251 raise ValidationError('Could not verify reset token')
return Response(status=HTTP_204_NO_CONTENT) 251 252 return Response(status=HTTP_204_NO_CONTENT)
252 253
253 254
class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin): 254 255 class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin):
queryset = Flashcard.objects.all() 255 256 queryset = Flashcard.objects.all()
serializer_class = FlashcardSerializer 256 257 serializer_class = FlashcardSerializer
permission_classes = [IsAuthenticated, IsEnrolledInAssociatedSection] 257 258 permission_classes = [IsAuthenticated, IsEnrolledInAssociatedSection]
258 259
# Override create in CreateModelMixin 259 260 # Override create in CreateModelMixin
def create(self, request, *args, **kwargs): 260 261 def create(self, request, *args, **kwargs):
serializer = FlashcardSerializer(data=request.data) 261 262 serializer = FlashcardSerializer(data=request.data)
serializer.is_valid(raise_exception=True) 262 263 serializer.is_valid(raise_exception=True)
data = serializer.validated_data 263 264 data = serializer.validated_data
if not request.user.is_in_section(data['section']): 264 265 if not request.user.is_in_section(data['section']):
raise PermissionDenied('The user is not enrolled in that section') 265 266 raise PermissionDenied('The user is not enrolled in that section')
data['author'] = request.user 266 267 data['author'] = request.user
flashcard = Flashcard.objects.create(**data) 267 268 flashcard = Flashcard.objects.create(**data)
self.perform_create(flashcard) 268 269 self.perform_create(flashcard)
headers = self.get_success_headers(data) 269 270 headers = self.get_success_headers(data)
response_data = FlashcardSerializer(flashcard) 270 271 response_data = FlashcardSerializer(flashcard)
return Response(response_data.data, status=HTTP_201_CREATED, headers=headers) 271 272 return Response(response_data.data, status=HTTP_201_CREATED, headers=headers)
272 273
273 274
@detail_route(methods=['post']) 274 275 @detail_route(methods=['post'])
def unhide(self, request, pk): 275 276 def unhide(self, request, pk):
""" 276 277 """
Unhide the given card 277 278 Unhide the given card
--- 278 279 ---
view_mocker: flashcards.api.mock_no_params 279 280 view_mocker: flashcards.api.mock_no_params
""" 280 281 """
hide = get_object_or_404(FlashcardHide, user=request.user, flashcard=self.get_object()) 281 282 hide = get_object_or_404(FlashcardHide, user=request.user, flashcard=self.get_object())
hide.delete() 282 283 hide.delete()
return Response(status=HTTP_204_NO_CONTENT) 283 284 return Response(status=HTTP_204_NO_CONTENT)
284 285
@detail_route(methods=['post']) 285 286 @detail_route(methods=['post'])
def report(self, request, pk): 286 287 def report(self, request, pk):
flashy/urls.py View file @ bd04c9a
from django.conf.urls import include, url 1 1 from django.conf.urls import include, url
from django.contrib import admin 2 2 from django.contrib import admin
from flashcards.views import SectionViewSet, UserDetail, FlashcardViewSet, UserSectionListView, request_password_reset, \ 3 3 from flashcards.views import SectionViewSet, UserDetail, FlashcardViewSet, UserSectionListView, request_password_reset, \
reset_password, logout, login, register 4 4 reset_password, logout, login, register
from flashy.frontend_serve import serve_with_default 5 5 from flashy.frontend_serve import serve_with_default
from flashy.settings import DEBUG, IN_PRODUCTION 6 6 from flashy.settings import DEBUG, IN_PRODUCTION
from rest_framework.routers import DefaultRouter 7 7 from rest_framework.routers import DefaultRouter
from flashcards.api import * 8 8 from flashcards.api import *
9 9
router = DefaultRouter() 10 10 router = DefaultRouter()
router.register(r'sections', SectionViewSet) 11 11 router.register(r'sections', SectionViewSet)
router.register(r'flashcards', FlashcardViewSet) 12 12 router.register(r'flashcards', FlashcardViewSet)
13 13
urlpatterns = [ 14 14 urlpatterns = [
url(r'^api/docs/', include('rest_framework_swagger.urls')), 15 15 url(r'^api/docs/', include('rest_framework_swagger.urls')),
url(r'^api/me/$', UserDetail.as_view()), 16 16 url(r'^api/me/$', UserDetail.as_view()),
url(r'^api/register/', register), 17 17 url(r'^api/register/', register),
url(r'^api/login/$', login), 18 18 url(r'^api/login/$', login),
url(r'^api/logout/$', logout), 19 19 url(r'^api/logout/$', logout),
url(r'^api/me/sections/', UserSectionListView.as_view()), 20 20 url(r'^api/me/sections/', UserSectionListView.as_view()),
url(r'^api/request_password_reset/', request_password_reset), 21 21 url(r'^api/request_password_reset/', request_password_reset),
url(r'^api/reset_password/', reset_password), 22 22 url(r'^api/reset_password/', reset_password),
url(r'^api/', include(router.urls)), 23 23 url(r'^api/', include(router.urls)),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')), 24 24 url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/', include(admin.site.urls)), 25 25 url(r'^admin/', include(admin.site.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), 26 26 url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
] 27 27 ]
28 28
if IN_PRODUCTION: 29 29 if IN_PRODUCTION:
urlpatterns += (url(r'^admin/django-ses/', include('django_ses.urls')),) 30 30 urlpatterns += (url(r'^admin/django-ses/', include('django_ses.urls')),)
31 urlpatterns += (url(r'^admin/pghero/', include('django_pghero.urls')),)
scripts/setup_production.sh View file @ bd04c9a
#!/bin/bash -xe 1 1 #!/bin/bash -xe
whoami 2 2 whoami
source venv/bin/activate 3 3 source venv/bin/activate
source secrets 4 4 source secrets
pip install psycopg2 5 5 pip install psycopg2
pip install gunicorn 6 6 pip install gunicorn
pip install newrelic 7 7 pip install newrelic
pip install django-ses 8 8 pip install django-ses
9 pip install django-pghero
python manage.py migrate 9 10 python manage.py migrate