Blame view

flashcards/views.py 16.1 KB
776577266   Andrew Buss   websockets notifi...
1
  import django
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
2
  from django.contrib import auth
29c433096   Andrew Buss   updated readme to...
3
  from django.shortcuts import get_object_or_404
5a2899b9d   Andrew Buss   add gobs of logging
4
  from django.utils.log import getLogger
33f8a47a8   Andrew Buss   enforce 24 hour g...
5
6
  from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection, IsFlashcardReviewer, \
      IsAuthenticatedAndConfirmed
1eebdbcc4   Andrew Buss   merge
7
8
  from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcardQuiz, \
      FlashcardAlreadyPulledException, FlashcardNotInDeckException
776577266   Andrew Buss   websockets notifi...
9
  from flashcards.notifications import notify_new_card
ce17f969f   Andrew Buss   Restructured api,...
10
  from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \
bca16d61f   Rohan Rangray   Added the patch m...
11
      PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \
d818aecbc   Rohan Rangray   Merged.
12
      FlashcardUpdateSerializer, QuizRequestSerializer, QuizResponseSerializer, \
fe546f43f   Andrew Buss   move verify_email...
13
      QuizAnswerRequestSerializer, DeepSectionSerializer, EmailVerificationSerializer
3188404a6   Andrew Buss   feed tests works(?)
14
  from rest_framework.decorators import detail_route, permission_classes, api_view, list_route
2c22131d9   Andrew Buss   Added department ...
15
  from rest_framework.generics import ListAPIView, GenericAPIView
ee4104aa2   Rohan Rangray   Added a Study vie...
16
  from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin
01ea09616   Andrew Buss   Don't be silly, y...
17
  from rest_framework.permissions import IsAuthenticated
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
18
  from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet
ce17f969f   Andrew Buss   Restructured api,...
19
  from django.core.mail import send_mail
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
20
  from django.contrib.auth import authenticate
ce17f969f   Andrew Buss   Restructured api,...
21
  from django.contrib.auth.tokens import default_token_generator
5d861cbfb   Rohan Rangray   Wrote tests for F...
22
  from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK
ce17f969f   Andrew Buss   Restructured api,...
23
  from rest_framework.response import Response
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
24
  from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied
7aa4b42d3   Andrew Buss   Fixed registratio...
25
  from simple_email_confirmation import EmailAddress
fe546f43f   Andrew Buss   move verify_email...
26
  from simple_email_confirmation.models import EmailAddressManager
2a72f1a8a   Andrew Buss   Development serve...
27

c4a8e6cef   Rohan Rangray   Added pagination ...
28

5a2899b9d   Andrew Buss   add gobs of logging
29
30
  def log_event(request, event=''):
      getLogger('flashy.events').info(
5064562d7   Andrew Buss   fix logging?
31
          '%s %s %s %s' % (request.META['REMOTE_ADDR'], str(request.user), request.path, event))
2a72f1a8a   Andrew Buss   Development serve...
32

c4a8e6cef   Rohan Rangray   Added pagination ...
33

ce17f969f   Andrew Buss   Restructured api,...
34
  class SectionViewSet(ReadOnlyModelViewSet):
491577131   Andrew Buss   Class -> section....
35
      queryset = Section.objects.all()
bd04c9af5   Andrew Buss   Only retrieve lec...
36
      serializer_class = DeepSectionSerializer
c4a8e6cef   Rohan Rangray   Added pagination ...
37
      pagination_class = StandardResultsSetPagination
33f8a47a8   Andrew Buss   enforce 24 hour g...
38
      permission_classes = [IsAuthenticatedAndConfirmed]
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
39

a2d8c4229   Andrew Buss   added feed
40
      @detail_route(methods=['GET'])
57168cbc2   Rachel Lee   Stuff
41
42
43
44
45
      def flashcards(self, request, pk):
          """
          Gets flashcards for a section, excluding hidden cards.
          Returned in strictly chronological order (material date).
          """
ad724d791   Andrew Buss   is_hidden specifi...
46
          flashcards = Flashcard.cards_visible_to(request.user)
fe44f1608   Andrew Buss   remove ordered_de...
47
          if 'hidden' in request.GET:
d370a0e68   Andrew Buss   fix get/GET in fl...
48
              if request.GET['hidden'] == 'only':
ad724d791   Andrew Buss   is_hidden specifi...
49
50
51
                  flashcards = Flashcard.cards_hidden_by(request.user)
              else:
                  flashcards |= Flashcard.cards_hidden_by(request.user)
cf248fe50   Andrew Buss   sort flashcard li...
52
          flashcards = flashcards.filter(section=self.get_object()).order_by('material_date').all()
5a2899b9d   Andrew Buss   add gobs of logging
53
          log_event(request, str(self.get_object()))
ad724d791   Andrew Buss   is_hidden specifi...
54
          return Response(FlashcardSerializer(flashcards, context={"user": request.user}, many=True).data)
57168cbc2   Rachel Lee   Stuff
55

ee4104aa2   Rohan Rangray   Added a Study vie...
56
      @detail_route(methods=['POST'])
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
57
      def enroll(self, request, pk):
5a2899b9d   Andrew Buss   add gobs of logging
58

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
59
60
61
62
          """
          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.
          ---
2958a1827   Andrew Buss   cleaned up some d...
63
          view_mocker: flashcards.api.mock_no_params
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
64
          """
b8dcac27e   Andrew Buss   friendlier error ...
65
66
          try:
              self.get_object().enroll(request.user)
5a2899b9d   Andrew Buss   add gobs of logging
67
              log_event(request, str(self.get_object()))
b8dcac27e   Andrew Buss   friendlier error ...
68
69
70
71
          except django.core.exceptions.PermissionDenied as e:
              raise PermissionDenied(e)
          except django.core.exceptions.ValidationError as e:
              raise ValidationError(e)
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
72
          return Response(status=HTTP_204_NO_CONTENT)
ee4104aa2   Rohan Rangray   Added a Study vie...
73
      @detail_route(methods=['POST'])
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
74
75
76
77
78
      def drop(self, request, pk):
          """
          Remove the current user from a specified section
          If the user is not in the class, the request will fail.
          ---
2958a1827   Andrew Buss   cleaned up some d...
79
          view_mocker: flashcards.api.mock_no_params
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
80
          """
54bba1fea   Andrew Buss   enforce enrollmen...
81
82
          try:
              self.get_object().drop(request.user)
5a2899b9d   Andrew Buss   add gobs of logging
83
              log_event(request, str(self.get_object()))
2958a1827   Andrew Buss   cleaned up some d...
84
85
86
87
          except django.core.exceptions.PermissionDenied as e:
              raise PermissionDenied(e)
          except django.core.exceptions.ValidationError as e:
              raise ValidationError(e)
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
88
          return Response(status=HTTP_204_NO_CONTENT)
491577131   Andrew Buss   Class -> section....
89

3188404a6   Andrew Buss   feed tests works(?)
90
      @list_route(methods=['GET'])
2c22131d9   Andrew Buss   Added department ...
91
      def search(self, request):
a2d8c4229   Andrew Buss   added feed
92
93
          """
          Returns a list of sections which match a user's query
2958a1827   Andrew Buss   cleaned up some d...
94
95
96
97
98
99
          ---
          parameters:
              - name: q
                description: space-separated list of terms
                required: true
                type: form
bd04c9af5   Andrew Buss   Only retrieve lec...
100
          response_serializer: SectionSerializer
a2d8c4229   Andrew Buss   added feed
101
          """
c66537ab3   Andrew Buss   nitpicky space fi...
102
          query = request.GET.get('q', None)
dc685f192   Andrew Buss   Section search wo...
103
          if not query: return Response('[]')
fe0d37016   Andrew Buss   changed some thin...
104
          qs = Section.search(query.split(' '))[:20]
4ff4160a6   Andrew Buss   include can_enrol...
105
          data = SectionSerializer(qs, many=True, context={'user': request.user}).data
5a2899b9d   Andrew Buss   add gobs of logging
106
          log_event(request, query)
5a0ce27d1   Andrew Buss   cache rice
107
          return Response(data)
3709ee645   Laura Hawkins   working on deck view
108

a2d8c4229   Andrew Buss   added feed
109
      @detail_route(methods=['GET'])
6158afb2d   Laura Hawkins   my deckview is pe...
110
      def deck(self, request, pk):
3709ee645   Laura Hawkins   working on deck view
111
112
113
          """
          Gets the contents of a user's deck for a given section.
          """
fedcc8ded   Rohan Rangray   Wrote tests for F...
114
          qs = request.user.get_deck(self.get_object())
3709ee645   Laura Hawkins   working on deck view
115
          serializer = FlashcardSerializer(qs, many=True)
5a2899b9d   Andrew Buss   add gobs of logging
116
          log_event(request, str(self.get_object()))
3709ee645   Laura Hawkins   working on deck view
117
          return Response(serializer.data)
2c22131d9   Andrew Buss   Added department ...
118

a2d8c4229   Andrew Buss   added feed
119
120
121
122
123
124
      @detail_route(methods=['GET'])
      def feed(self, request, pk):
          """
          Gets the contents of a user's feed for a section.
          Exclude cards that are already in the user's deck
          """
19f62c6f7   Andrew Buss   add is_in_deck fi...
125
126
          serializer = FlashcardSerializer(self.get_object().get_feed_for_user(request.user), many=True,
                                           context={'user': request.user})
5a2899b9d   Andrew Buss   add gobs of logging
127
          log_event(request, str(self.get_object()))
a2d8c4229   Andrew Buss   added feed
128
          return Response(serializer.data)
2a72f1a8a   Andrew Buss   Development serve...
129

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
130
  class UserSectionListView(ListAPIView):
bd04c9af5   Andrew Buss   Only retrieve lec...
131
      serializer_class = DeepSectionSerializer
33f8a47a8   Andrew Buss   enforce 24 hour g...
132
      permission_classes = [IsAuthenticatedAndConfirmed]
2a72f1a8a   Andrew Buss   Development serve...
133

be7810aad   Laura Hawkins   working on adding...
134
135
      def get_queryset(self):
          return self.request.user.sections.all()
2a72f1a8a   Andrew Buss   Development serve...
136

be7810aad   Laura Hawkins   working on adding...
137
      def paginate_queryset(self, queryset): return None
18095ed46   Andrew Buss   Integrated django...
138

2a72f1a8a   Andrew Buss   Development serve...
139

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
140
141
  class UserDetail(GenericAPIView):
      serializer_class = UserSerializer
33f8a47a8   Andrew Buss   enforce 24 hour g...
142
      permission_classes = [IsAuthenticatedAndConfirmed]
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
143

ce17f969f   Andrew Buss   Restructured api,...
144
145
      def patch(self, request, format=None):
          """
fe546f43f   Andrew Buss   move verify_email...
146
          Updates the user's password
ce17f969f   Andrew Buss   Restructured api,...
147
148
149
150
151
152
          ---
          request_serializer: UserUpdateSerializer
          response_serializer: UserSerializer
          """
          data = UserUpdateSerializer(data=request.data, context={'user': request.user})
          data.is_valid(raise_exception=True)
7aa4b42d3   Andrew Buss   Fixed registratio...
153
          data = data.validated_data
ce17f969f   Andrew Buss   Restructured api,...
154
155
156
157
  
          if 'new_password' in data:
              if not request.user.check_password(data['old_password']):
                  raise ValidationError('old_password is incorrect')
7aa4b42d3   Andrew Buss   Fixed registratio...
158
              request.user.set_password(data['new_password'])
ce17f969f   Andrew Buss   Restructured api,...
159
              request.user.save()
5a2899b9d   Andrew Buss   add gobs of logging
160
              log_event(request, 'change password')
ce17f969f   Andrew Buss   Restructured api,...
161

ce17f969f   Andrew Buss   Restructured api,...
162
163
164
165
166
167
168
169
          return Response(UserSerializer(request.user).data)
  
      def get(self, request, format=None):
          """
          Return data about the user
          ---
          response_serializer: UserSerializer
          """
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
170
          serializer = UserSerializer(request.user, context={'request': request})
ce17f969f   Andrew Buss   Restructured api,...
171
          return Response(serializer.data)
ce17f969f   Andrew Buss   Restructured api,...
172
173
174
175
176
177
178
      def delete(self, request):
          """
          Irrevocably delete the user and their data
  
          Yes, really
          """
          request.user.delete()
5a2899b9d   Andrew Buss   add gobs of logging
179
          log_event(request)
ce17f969f   Andrew Buss   Restructured api,...
180
          return Response(status=HTTP_204_NO_CONTENT)
2c22131d9   Andrew Buss   Added department ...
181

7a17fac38   Andrew Buss   add view to resen...
182
183
184
  @api_view(['POST'])
  @permission_classes([IsAuthenticated])
  def resend_confirmation_email(request):
fe546f43f   Andrew Buss   move verify_email...
185
      "Resends a confirmation email to a user"
7a17fac38   Andrew Buss   add view to resen...
186
187
      request.user.send_confirmation_email()
      return Response(status=HTTP_204_NO_CONTENT)
fe546f43f   Andrew Buss   move verify_email...
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
  
  @api_view(['POST'])
  @permission_classes([IsAuthenticated])
  def verify_email(request):
      """
      Accepts a user's email confirmation_key to verify their email address
      ---
      request_serializer: EmailVerificationSerializer
      """
      try:
          data = EmailVerificationSerializer(data=request.data)
          data.is_valid(raise_exception=True)
          email = EmailAddress.objects.confirm(data.validated_data['confirmation_key'])
          log_event(request, 'confirm email' + str(email))
          return Response(status=HTTP_204_NO_CONTENT)
      except EmailAddress.DoesNotExist:
          raise ValidationError('confirmation_key is invalid')
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
205
206
207
208
209
210
211
212
213
214
  @api_view(['POST'])
  def register(request, format=None):
      """
      Register a new user
      ---
      request_serializer: EmailPasswordSerializer
      response_serializer: UserSerializer
      """
      data = RegistrationSerializer(data=request.data)
      data.is_valid(raise_exception=True)
ce17f969f   Andrew Buss   Restructured api,...
215

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
216
217
218
      User.objects.create_user(**data.validated_data)
      user = authenticate(**data.validated_data)
      auth.login(request, user)
5a2899b9d   Andrew Buss   add gobs of logging
219
      log_event(request)
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
220
      return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED)
2a72f1a8a   Andrew Buss   Development serve...
221

2c22131d9   Andrew Buss   Added department ...
222

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
  @api_view(['POST'])
  def login(request):
      """
      Authenticates user and returns user data if valid.
      ---
      request_serializer: EmailPasswordSerializer
      response_serializer: UserSerializer
      """
  
      data = EmailPasswordSerializer(data=request.data)
      data.is_valid(raise_exception=True)
      user = authenticate(**data.validated_data)
  
      if user is None:
          raise AuthenticationFailed('Invalid email or password')
      if not user.is_active:
          raise NotAuthenticated('Account is disabled')
      auth.login(request, user)
5a2899b9d   Andrew Buss   add gobs of logging
241
      log_event(request)
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
242
      return Response(UserSerializer(request.user).data)
ce17f969f   Andrew Buss   Restructured api,...
243

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
244
  @api_view(['POST'])
776577266   Andrew Buss   websockets notifi...
245
  @permission_classes((IsAuthenticated,))
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
246
  def logout(request, format=None):
ce17f969f   Andrew Buss   Restructured api,...
247
      """
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
248
      Logs the authenticated user out.
ce17f969f   Andrew Buss   Restructured api,...
249
      """
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
250
      auth.logout(request)
5a2899b9d   Andrew Buss   add gobs of logging
251
      log_event(request)
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
252
      return Response(status=HTTP_204_NO_CONTENT)
ce17f969f   Andrew Buss   Restructured api,...
253

ce17f969f   Andrew Buss   Restructured api,...
254

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
255
256
257
258
259
260
261
262
263
  @api_view(['POST'])
  def request_password_reset(request, format=None):
      """
      Send a password reset token/link to the provided email.
      ---
      request_serializer: PasswordResetRequestSerializer
      """
      data = PasswordResetRequestSerializer(data=request.data)
      data.is_valid(raise_exception=True)
5a2899b9d   Andrew Buss   add gobs of logging
264
      log_event(request, 'email: ' + str(data['email']))
8e66c8186   Andrew Buss   moved still more ...
265
      get_object_or_404(User, email=data['email'].value).request_password_reset()
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
266
      return Response(status=HTTP_204_NO_CONTENT)
ce17f969f   Andrew Buss   Restructured api,...
267

2dc11d15d   Laura Hawkins   password and user...
268

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
269
270
271
272
273
274
275
276
277
  @api_view(['POST'])
  def reset_password(request, format=None):
      """
      Updates user's password to new password if token is valid.
      ---
      request_serializer: PasswordResetSerializer
      """
      data = PasswordResetSerializer(data=request.data)
      data.is_valid(raise_exception=True)
2a9edd990   Chung Wang   Flashcard detail ...
278

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
279
280
      user = User.objects.get(id=data['uid'].value)
      # Check token validity.
2a72f1a8a   Andrew Buss   Development serve...
281

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
282
283
284
      if default_token_generator.check_token(user, data['token'].value):
          user.set_password(data['new_password'].value)
          user.save()
5a2899b9d   Andrew Buss   add gobs of logging
285
          log_event(request)
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
286
287
288
      else:
          raise ValidationError('Could not verify reset token')
      return Response(status=HTTP_204_NO_CONTENT)
2a72f1a8a   Andrew Buss   Development serve...
289

2a9edd990   Chung Wang   Flashcard detail ...
290

2958a1827   Andrew Buss   cleaned up some d...
291
  class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin):
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
292
293
      queryset = Flashcard.objects.all()
      serializer_class = FlashcardSerializer
33f8a47a8   Andrew Buss   enforce 24 hour g...
294
      permission_classes = [IsAuthenticatedAndConfirmed, IsEnrolledInAssociatedSection]
c95e5d254   Rohan Rangray   Silently allow fo...
295

c2b6dc852   Rachel Lee   Wrote test for se...
296
297
      # Override create in CreateModelMixin
      def create(self, request, *args, **kwargs):
8964ffa26   Rohan Rangray   Corrected Flashca...
298
          serializer = FlashcardSerializer(data=request.data)
c2b6dc852   Rachel Lee   Wrote test for se...
299
          serializer.is_valid(raise_exception=True)
8964ffa26   Rohan Rangray   Corrected Flashca...
300
          data = serializer.validated_data
2958a1827   Andrew Buss   cleaned up some d...
301
302
          if not request.user.is_in_section(data['section']):
              raise PermissionDenied('The user is not enrolled in that section')
8964ffa26   Rohan Rangray   Corrected Flashca...
303
304
305
          data['author'] = request.user
          flashcard = Flashcard.objects.create(**data)
          self.perform_create(flashcard)
776577266   Andrew Buss   websockets notifi...
306
          notify_new_card(flashcard)
8964ffa26   Rohan Rangray   Corrected Flashca...
307
          headers = self.get_success_headers(data)
22a482a65   Andrew Buss   pull a card that ...
308
          request.user.pull(flashcard)
776577266   Andrew Buss   websockets notifi...
309
          response_data = FlashcardSerializer(flashcard).data
5a2899b9d   Andrew Buss   add gobs of logging
310
          log_event(request, response_data)
776577266   Andrew Buss   websockets notifi...
311
          return Response(response_data, status=HTTP_201_CREATED, headers=headers)
c7885ab86   Chung Wang   hide flashcard an...
312

ee4104aa2   Rohan Rangray   Added a Study vie...
313
      @detail_route(methods=['POST'])
29c433096   Andrew Buss   updated readme to...
314
315
      def unhide(self, request, pk):
          """
2958a1827   Andrew Buss   cleaned up some d...
316
          Unhide the given card
29c433096   Andrew Buss   updated readme to...
317
          ---
2958a1827   Andrew Buss   cleaned up some d...
318
          view_mocker: flashcards.api.mock_no_params
29c433096   Andrew Buss   updated readme to...
319
          """
54bba1fea   Andrew Buss   enforce enrollmen...
320
          hide = get_object_or_404(FlashcardHide, user=request.user, flashcard=self.get_object())
29c433096   Andrew Buss   updated readme to...
321
          hide.delete()
5a2899b9d   Andrew Buss   add gobs of logging
322
          log_event(request, str(self.get_object()))
c7885ab86   Chung Wang   hide flashcard an...
323
          return Response(status=HTTP_204_NO_CONTENT)
29c433096   Andrew Buss   updated readme to...
324

ee4104aa2   Rohan Rangray   Added a Study vie...
325
      @detail_route(methods=['POST'])
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
326
327
      def report(self, request, pk):
          """
2958a1827   Andrew Buss   cleaned up some d...
328
          Hide the given card
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
329
          ---
2958a1827   Andrew Buss   cleaned up some d...
330
          view_mocker: flashcards.api.mock_no_params
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
331
          """
8e66c8186   Andrew Buss   moved still more ...
332
          self.get_object().report(request.user)
5a2899b9d   Andrew Buss   add gobs of logging
333
          log_event(request, str(self.get_object()))
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
334
          return Response(status=HTTP_204_NO_CONTENT)
fe6a4ff63   Rohan Rangray   Replaced Flashcar...
335

2958a1827   Andrew Buss   cleaned up some d...
336
      hide = report
a2d8c4229   Andrew Buss   added feed
337
      @detail_route(methods=['POST'])
be6cc9169   Rohan Rangray   Fixed a bug in th...
338
339
340
      def pull(self, request, pk):
          """
          Pull a card from the live feed into the user's deck.
2958a1827   Andrew Buss   cleaned up some d...
341
342
          ---
          view_mocker: flashcards.api.mock_no_params
be6cc9169   Rohan Rangray   Fixed a bug in th...
343
          """
390189eb6   Andrew Buss   handle pulling a ...
344
345
          try:
              request.user.pull(self.get_object())
5a2899b9d   Andrew Buss   add gobs of logging
346
              log_event(request, str(self.get_object()))
390189eb6   Andrew Buss   handle pulling a ...
347
              return Response(status=HTTP_204_NO_CONTENT)
3f97f29f0   Andrew Buss   improve error han...
348
          except FlashcardAlreadyPulledException:
390189eb6   Andrew Buss   handle pulling a ...
349
              raise ValidationError('Cannot pull a card already in deck')
17ecc595a   Laura Hawkins   trying to pull
350

b048e96a2   Chung Wang   unpull flashcard
351
352
353
354
      @detail_route(methods=['POST'])
      def unpull(self, request, pk):
          """
          Unpull a card from the user's deck
2958a1827   Andrew Buss   cleaned up some d...
355
356
          ---
          view_mocker: flashcards.api.mock_no_params
b048e96a2   Chung Wang   unpull flashcard
357
358
359
          """
          user = request.user
          flashcard = self.get_object()
3f97f29f0   Andrew Buss   improve error han...
360
361
362
363
364
365
          try:
              user.unpull(flashcard)
              log_event(request, str(self.get_object()))
              return Response(status=HTTP_204_NO_CONTENT)
          except FlashcardNotInDeckException:
              raise ValidationError('Cannot unpull a card not in deck')
2958a1827   Andrew Buss   cleaned up some d...
366
      def partial_update(self, request, *args, **kwargs):
bca16d61f   Rohan Rangray   Added the patch m...
367
368
          """
          Edit settings related to a card for the user.
2958a1827   Andrew Buss   cleaned up some d...
369
370
          ---
          request_serializer: FlashcardUpdateSerializer
bca16d61f   Rohan Rangray   Added the patch m...
371
372
          """
          user = request.user
a2d8c4229   Andrew Buss   added feed
373
          flashcard = self.get_object()
bca16d61f   Rohan Rangray   Added the patch m...
374
375
376
          data = FlashcardUpdateSerializer(data=request.data)
          data.is_valid(raise_exception=True)
          new_flashcard = data.validated_data
cec534fd3   Andrew Buss   moved more view l...
377
          new_flashcard = flashcard.edit(user, new_flashcard)
5a2899b9d   Andrew Buss   add gobs of logging
378
          log_event(request, str(new_flashcard))
ad724d791   Andrew Buss   is_hidden specifi...
379
          return Response(FlashcardSerializer(new_flashcard, context={'user': request.user}).data, status=HTTP_200_OK)
ee4104aa2   Rohan Rangray   Added a Study vie...
380

28a4bd2e7   Rohan Rangray   Refactored code i...
381

ee4104aa2   Rohan Rangray   Added a Study vie...
382
  class UserFlashcardQuizViewSet(GenericViewSet, CreateModelMixin, UpdateModelMixin):
33f8a47a8   Andrew Buss   enforce 24 hour g...
383
      permission_classes = [IsAuthenticatedAndConfirmed, IsFlashcardReviewer]
f66f9ca7d   Rohan Rangray   Fixed SwaggerUI p...
384
385
386
387
388
389
      queryset = UserFlashcardQuiz.objects.all()
  
      def get_serializer_class(self):
          if self.request.method == 'POST':
              return QuizRequestSerializer
          return QuizAnswerRequestSerializer
ee4104aa2   Rohan Rangray   Added a Study vie...
390
391
392
393
394
395
396
  
      def create(self, request, *args, **kwargs):
          """
          Return a card based on the request params.
          :param request: A request object.
          :param format: Format of the request.
          :return: A response containing
f66f9ca7d   Rohan Rangray   Fixed SwaggerUI p...
397
398
          request_serializer: serializers.QuizRequestSerializer
          response_serializer: serializers.QuizResponseSerializer
ee4104aa2   Rohan Rangray   Added a Study vie...
399
          """
f66f9ca7d   Rohan Rangray   Fixed SwaggerUI p...
400
          serializer = QuizRequestSerializer(data=request.data)
ee4104aa2   Rohan Rangray   Added a Study vie...
401
          serializer.is_valid(raise_exception=True)
f66f9ca7d   Rohan Rangray   Fixed SwaggerUI p...
402
          data = serializer.validated_data
2c00c7d07   Rohan Rangray   Refactored soem c...
403
          user_flashcard = request.user.by_retention(**data).first()
1eebdbcc4   Andrew Buss   merge
404

28a4bd2e7   Rohan Rangray   Refactored code i...
405
          mask = user_flashcard.get_mask().get_random_blank()
2c00c7d07   Rohan Rangray   Refactored soem c...
406
407
          blanked_word = ""
          if mask:
c95e5d254   Rohan Rangray   Silently allow fo...
408
              blanked_word = user_flashcard.flashcard.text[slice(*mask)]
2c00c7d07   Rohan Rangray   Refactored soem c...
409
410
  
          user_flashcard_quiz = UserFlashcardQuiz(user_flashcard=user_flashcard, blanked_word=blanked_word)
ee4104aa2   Rohan Rangray   Added a Study vie...
411
          user_flashcard_quiz.save()
2c00c7d07   Rohan Rangray   Refactored soem c...
412

f66f9ca7d   Rohan Rangray   Fixed SwaggerUI p...
413
          response = QuizResponseSerializer(instance=user_flashcard_quiz, mask=mask)
a9cb7ae51   Andrew Buss   log serializer da...
414
          log_event(request, response.data)
f66f9ca7d   Rohan Rangray   Fixed SwaggerUI p...
415
          return Response(response.data, status=HTTP_200_OK)
ee4104aa2   Rohan Rangray   Added a Study vie...
416

cf394ed25   Rohan Rangray   Changed /study/{p...
417
      def partial_update(self, request, *args, **kwargs):
ee4104aa2   Rohan Rangray   Added a Study vie...
418
419
420
421
422
          """
          Receive the user's response to the quiz.
          :param request: A request object.
          :param format: Format of the request.
          :return: A response containing
f66f9ca7d   Rohan Rangray   Fixed SwaggerUI p...
423
          request_serializer: serializers.QuizAnswerRequestSerializer
ee4104aa2   Rohan Rangray   Added a Study vie...
424
425
426
          """
          user_flashcard_quiz = self.get_object()
          serializer = QuizAnswerRequestSerializer(instance=user_flashcard_quiz, data=request.data)
7dcd94c01   Rohan Rangray   Added more extens...
427
          serializer.is_valid(raise_exception=True)
ee4104aa2   Rohan Rangray   Added a Study vie...
428
          serializer.update(user_flashcard_quiz, serializer.validated_data)
5a2899b9d   Andrew Buss   add gobs of logging
429
          log_event(request, serializer.data)
ee4104aa2   Rohan Rangray   Added a Study vie...
430
          return Response(status=HTTP_204_NO_CONTENT)