Commit 17ecc595a6c731ef5b12ff22fd98df8864a9d5c4

Authored by Laura Hawkins
1 parent 5769450a64
Exists in master

trying to pull

Showing 1 changed file with 1 additions and 0 deletions Inline Diff

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