Blame view

flashcards/views.py 16.6 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
3f97f29f0   Andrew Buss   improve error han...
7
  from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard, UserFlashcardQuiz, \
63502bd39   Rohan Rangray   Added fix for usi...
8
      FlashcardAlreadyPulledException, FlashcardNotInDeckException, Now, interval_days
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
13
      FlashcardUpdateSerializer, QuizRequestSerializer, QuizResponseSerializer, \
      QuizAnswerRequestSerializer, DeepSectionSerializer
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
4ff8077bc   Rohan Rangray   Fixed merge confl...
22
  from django.db.models import Count, Max, F, Value, When, Case, DateTimeField, FloatField
5d861cbfb   Rohan Rangray   Wrote tests for F...
23
  from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK
ce17f969f   Andrew Buss   Restructured api,...
24
  from rest_framework.response import Response
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
25
  from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied
7aa4b42d3   Andrew Buss   Fixed registratio...
26
  from simple_email_confirmation import EmailAddress
057d2cc3f   Rohan Rangray   Attempted fix for...
27
  from math import e
4ff8077bc   Rohan Rangray   Fixed merge confl...
28
  from django.utils.timezone import now
2a72f1a8a   Andrew Buss   Development serve...
29

c4a8e6cef   Rohan Rangray   Added pagination ...
30

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

c4a8e6cef   Rohan Rangray   Added pagination ...
35

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

a2d8c4229   Andrew Buss   added feed
42
      @detail_route(methods=['GET'])
57168cbc2   Rachel Lee   Stuff
43
44
45
46
47
      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...
48
          flashcards = Flashcard.cards_visible_to(request.user)
fe44f1608   Andrew Buss   remove ordered_de...
49
          if 'hidden' in request.GET:
d370a0e68   Andrew Buss   fix get/GET in fl...
50
              if request.GET['hidden'] == 'only':
ad724d791   Andrew Buss   is_hidden specifi...
51
52
53
                  flashcards = Flashcard.cards_hidden_by(request.user)
              else:
                  flashcards |= Flashcard.cards_hidden_by(request.user)
cf248fe50   Andrew Buss   sort flashcard li...
54
          flashcards = flashcards.filter(section=self.get_object()).order_by('material_date').all()
5a2899b9d   Andrew Buss   add gobs of logging
55
          log_event(request, str(self.get_object()))
ad724d791   Andrew Buss   is_hidden specifi...
56
          return Response(FlashcardSerializer(flashcards, context={"user": request.user}, many=True).data)
57168cbc2   Rachel Lee   Stuff
57

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

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
61
62
63
64
          """
          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...
65
          view_mocker: flashcards.api.mock_no_params
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
66
          """
b8dcac27e   Andrew Buss   friendlier error ...
67
68
          try:
              self.get_object().enroll(request.user)
5a2899b9d   Andrew Buss   add gobs of logging
69
              log_event(request, str(self.get_object()))
b8dcac27e   Andrew Buss   friendlier error ...
70
71
72
73
          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...
74
          return Response(status=HTTP_204_NO_CONTENT)
ee4104aa2   Rohan Rangray   Added a Study vie...
75
      @detail_route(methods=['POST'])
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
76
77
78
79
80
      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...
81
          view_mocker: flashcards.api.mock_no_params
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
82
          """
54bba1fea   Andrew Buss   enforce enrollmen...
83
84
          try:
              self.get_object().drop(request.user)
5a2899b9d   Andrew Buss   add gobs of logging
85
              log_event(request, str(self.get_object()))
2958a1827   Andrew Buss   cleaned up some d...
86
87
88
89
          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...
90
          return Response(status=HTTP_204_NO_CONTENT)
491577131   Andrew Buss   Class -> section....
91

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

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

a2d8c4229   Andrew Buss   added feed
121
122
123
124
125
126
      @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...
127
128
          serializer = FlashcardSerializer(self.get_object().get_feed_for_user(request.user), many=True,
                                           context={'user': request.user})
5a2899b9d   Andrew Buss   add gobs of logging
129
          log_event(request, str(self.get_object()))
a2d8c4229   Andrew Buss   added feed
130
          return Response(serializer.data)
2a72f1a8a   Andrew Buss   Development serve...
131

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

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

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

2a72f1a8a   Andrew Buss   Development serve...
141

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

ce17f969f   Andrew Buss   Restructured api,...
146
147
148
149
150
151
152
153
154
      def patch(self, request, format=None):
          """
          Updates the user's password, or verifies their email address
          ---
          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...
155
          data = data.validated_data
ce17f969f   Andrew Buss   Restructured api,...
156
157
158
159
  
          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...
160
              request.user.set_password(data['new_password'])
ce17f969f   Andrew Buss   Restructured api,...
161
              request.user.save()
5a2899b9d   Andrew Buss   add gobs of logging
162
              log_event(request, 'change password')
ce17f969f   Andrew Buss   Restructured api,...
163

7aa4b42d3   Andrew Buss   Fixed registratio...
164
165
166
          if 'confirmation_key' in data:
              try:
                  request.user.confirm_email(data['confirmation_key'])
5a2899b9d   Andrew Buss   add gobs of logging
167
                  log_event(request, 'confirm email')
7aa4b42d3   Andrew Buss   Fixed registratio...
168
169
              except EmailAddress.DoesNotExist:
                  raise ValidationError('confirmation_key is invalid')
ce17f969f   Andrew Buss   Restructured api,...
170
171
172
173
174
175
176
177
178
  
          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...
179
          serializer = UserSerializer(request.user, context={'request': request})
ce17f969f   Andrew Buss   Restructured api,...
180
          return Response(serializer.data)
ce17f969f   Andrew Buss   Restructured api,...
181
182
183
184
185
186
187
      def delete(self, request):
          """
          Irrevocably delete the user and their data
  
          Yes, really
          """
          request.user.delete()
5a2899b9d   Andrew Buss   add gobs of logging
188
          log_event(request)
ce17f969f   Andrew Buss   Restructured api,...
189
          return Response(status=HTTP_204_NO_CONTENT)
2c22131d9   Andrew Buss   Added department ...
190

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
191
192
193
194
195
196
197
198
199
200
  @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,...
201

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
202
203
204
      User.objects.create_user(**data.validated_data)
      user = authenticate(**data.validated_data)
      auth.login(request, user)
5a2899b9d   Andrew Buss   add gobs of logging
205
      log_event(request)
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
206
      return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED)
2a72f1a8a   Andrew Buss   Development serve...
207

2c22131d9   Andrew Buss   Added department ...
208

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
  @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
227
      log_event(request)
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
228
      return Response(UserSerializer(request.user).data)
ce17f969f   Andrew Buss   Restructured api,...
229

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
230
  @api_view(['POST'])
776577266   Andrew Buss   websockets notifi...
231
  @permission_classes((IsAuthenticated,))
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
232
  def logout(request, format=None):
ce17f969f   Andrew Buss   Restructured api,...
233
      """
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
234
      Logs the authenticated user out.
ce17f969f   Andrew Buss   Restructured api,...
235
      """
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
236
      auth.logout(request)
5a2899b9d   Andrew Buss   add gobs of logging
237
      log_event(request)
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
238
      return Response(status=HTTP_204_NO_CONTENT)
ce17f969f   Andrew Buss   Restructured api,...
239

ce17f969f   Andrew Buss   Restructured api,...
240

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
241
242
243
244
245
246
247
248
249
  @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
250
      log_event(request, 'email: ' + str(data['email']))
8e66c8186   Andrew Buss   moved still more ...
251
      get_object_or_404(User, email=data['email'].value).request_password_reset()
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
252
      return Response(status=HTTP_204_NO_CONTENT)
ce17f969f   Andrew Buss   Restructured api,...
253

2dc11d15d   Laura Hawkins   password and user...
254

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
255
256
257
258
259
260
261
262
263
  @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 ...
264

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

72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
268
269
270
      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
271
          log_event(request)
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
272
273
274
      else:
          raise ValidationError('Could not verify reset token')
      return Response(status=HTTP_204_NO_CONTENT)
2a72f1a8a   Andrew Buss   Development serve...
275

2a9edd990   Chung Wang   Flashcard detail ...
276

3f97f29f0   Andrew Buss   improve error han...
277

2958a1827   Andrew Buss   cleaned up some d...
278
  class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin):
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
279
280
      queryset = Flashcard.objects.all()
      serializer_class = FlashcardSerializer
33f8a47a8   Andrew Buss   enforce 24 hour g...
281
      permission_classes = [IsAuthenticatedAndConfirmed, IsEnrolledInAssociatedSection]
c2b6dc852   Rachel Lee   Wrote test for se...
282
283
      # Override create in CreateModelMixin
      def create(self, request, *args, **kwargs):
8964ffa26   Rohan Rangray   Corrected Flashca...
284
          serializer = FlashcardSerializer(data=request.data)
c2b6dc852   Rachel Lee   Wrote test for se...
285
          serializer.is_valid(raise_exception=True)
8964ffa26   Rohan Rangray   Corrected Flashca...
286
          data = serializer.validated_data
2958a1827   Andrew Buss   cleaned up some d...
287
288
          if not request.user.is_in_section(data['section']):
              raise PermissionDenied('The user is not enrolled in that section')
8964ffa26   Rohan Rangray   Corrected Flashca...
289
290
291
          data['author'] = request.user
          flashcard = Flashcard.objects.create(**data)
          self.perform_create(flashcard)
776577266   Andrew Buss   websockets notifi...
292
          notify_new_card(flashcard)
8964ffa26   Rohan Rangray   Corrected Flashca...
293
          headers = self.get_success_headers(data)
22a482a65   Andrew Buss   pull a card that ...
294
          request.user.pull(flashcard)
776577266   Andrew Buss   websockets notifi...
295
          response_data = FlashcardSerializer(flashcard).data
5a2899b9d   Andrew Buss   add gobs of logging
296
          log_event(request, response_data)
776577266   Andrew Buss   websockets notifi...
297
          return Response(response_data, status=HTTP_201_CREATED, headers=headers)
c7885ab86   Chung Wang   hide flashcard an...
298

ee4104aa2   Rohan Rangray   Added a Study vie...
299
      @detail_route(methods=['POST'])
29c433096   Andrew Buss   updated readme to...
300
301
      def unhide(self, request, pk):
          """
2958a1827   Andrew Buss   cleaned up some d...
302
          Unhide the given card
29c433096   Andrew Buss   updated readme to...
303
          ---
2958a1827   Andrew Buss   cleaned up some d...
304
          view_mocker: flashcards.api.mock_no_params
29c433096   Andrew Buss   updated readme to...
305
          """
54bba1fea   Andrew Buss   enforce enrollmen...
306
          hide = get_object_or_404(FlashcardHide, user=request.user, flashcard=self.get_object())
29c433096   Andrew Buss   updated readme to...
307
          hide.delete()
5a2899b9d   Andrew Buss   add gobs of logging
308
          log_event(request, str(self.get_object()))
c7885ab86   Chung Wang   hide flashcard an...
309
          return Response(status=HTTP_204_NO_CONTENT)
29c433096   Andrew Buss   updated readme to...
310

ee4104aa2   Rohan Rangray   Added a Study vie...
311
      @detail_route(methods=['POST'])
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
312
313
      def report(self, request, pk):
          """
2958a1827   Andrew Buss   cleaned up some d...
314
          Hide the given card
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
315
          ---
2958a1827   Andrew Buss   cleaned up some d...
316
          view_mocker: flashcards.api.mock_no_params
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
317
          """
8e66c8186   Andrew Buss   moved still more ...
318
          self.get_object().report(request.user)
5a2899b9d   Andrew Buss   add gobs of logging
319
          log_event(request, str(self.get_object()))
72bf5f00c   Andrew Buss   Falcon puuuuuuuus...
320
          return Response(status=HTTP_204_NO_CONTENT)
fe6a4ff63   Rohan Rangray   Replaced Flashcar...
321

2958a1827   Andrew Buss   cleaned up some d...
322
      hide = report
a2d8c4229   Andrew Buss   added feed
323
      @detail_route(methods=['POST'])
be6cc9169   Rohan Rangray   Fixed a bug in th...
324
325
326
      def pull(self, request, pk):
          """
          Pull a card from the live feed into the user's deck.
2958a1827   Andrew Buss   cleaned up some d...
327
328
          ---
          view_mocker: flashcards.api.mock_no_params
be6cc9169   Rohan Rangray   Fixed a bug in th...
329
          """
390189eb6   Andrew Buss   handle pulling a ...
330
331
          try:
              request.user.pull(self.get_object())
5a2899b9d   Andrew Buss   add gobs of logging
332
              log_event(request, str(self.get_object()))
390189eb6   Andrew Buss   handle pulling a ...
333
              return Response(status=HTTP_204_NO_CONTENT)
3f97f29f0   Andrew Buss   improve error han...
334
          except FlashcardAlreadyPulledException:
390189eb6   Andrew Buss   handle pulling a ...
335
              raise ValidationError('Cannot pull a card already in deck')
17ecc595a   Laura Hawkins   trying to pull
336

b048e96a2   Chung Wang   unpull flashcard
337
338
339
340
      @detail_route(methods=['POST'])
      def unpull(self, request, pk):
          """
          Unpull a card from the user's deck
2958a1827   Andrew Buss   cleaned up some d...
341
342
          ---
          view_mocker: flashcards.api.mock_no_params
b048e96a2   Chung Wang   unpull flashcard
343
344
345
          """
          user = request.user
          flashcard = self.get_object()
3f97f29f0   Andrew Buss   improve error han...
346
347
348
349
350
351
          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')
b048e96a2   Chung Wang   unpull flashcard
352

2958a1827   Andrew Buss   cleaned up some d...
353
      def partial_update(self, request, *args, **kwargs):
bca16d61f   Rohan Rangray   Added the patch m...
354
355
          """
          Edit settings related to a card for the user.
2958a1827   Andrew Buss   cleaned up some d...
356
357
          ---
          request_serializer: FlashcardUpdateSerializer
bca16d61f   Rohan Rangray   Added the patch m...
358
359
          """
          user = request.user
a2d8c4229   Andrew Buss   added feed
360
          flashcard = self.get_object()
bca16d61f   Rohan Rangray   Added the patch m...
361
362
363
          data = FlashcardUpdateSerializer(data=request.data)
          data.is_valid(raise_exception=True)
          new_flashcard = data.validated_data
cec534fd3   Andrew Buss   moved more view l...
364
          new_flashcard = flashcard.edit(user, new_flashcard)
5a2899b9d   Andrew Buss   add gobs of logging
365
          log_event(request, str(new_flashcard))
ad724d791   Andrew Buss   is_hidden specifi...
366
          return Response(FlashcardSerializer(new_flashcard, context={'user': request.user}).data, status=HTTP_200_OK)
ee4104aa2   Rohan Rangray   Added a Study vie...
367

28a4bd2e7   Rohan Rangray   Refactored code i...
368

ee4104aa2   Rohan Rangray   Added a Study vie...
369
  class UserFlashcardQuizViewSet(GenericViewSet, CreateModelMixin, UpdateModelMixin):
33f8a47a8   Andrew Buss   enforce 24 hour g...
370
      permission_classes = [IsAuthenticatedAndConfirmed, IsFlashcardReviewer]
f66f9ca7d   Rohan Rangray   Fixed SwaggerUI p...
371
372
373
374
375
376
      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...
377
378
379
380
381
382
383
  
      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...
384
385
          request_serializer: serializers.QuizRequestSerializer
          response_serializer: serializers.QuizResponseSerializer
ee4104aa2   Rohan Rangray   Added a Study vie...
386
          """
f66f9ca7d   Rohan Rangray   Fixed SwaggerUI p...
387
          serializer = QuizRequestSerializer(data=request.data)
ee4104aa2   Rohan Rangray   Added a Study vie...
388
          serializer.is_valid(raise_exception=True)
f66f9ca7d   Rohan Rangray   Fixed SwaggerUI p...
389
390
391
392
393
394
395
396
397
          data = serializer.validated_data
          user_flashcard_filter = UserFlashcard.objects.filter(
              user=request.user, flashcard__section__pk__in=data['sections'],
              flashcard__material_date__gte=data['material_date_begin'],
              flashcard__material_date__lte=data['material_date_end']
          )
  
          if not user_flashcard_filter.exists():
              raise ValidationError("No matching flashcard found in your decks")
e9fe651ba   Rohan Rangray   Implemented expon...
398
          quiz_filter = user_flashcard_filter.prefetch_related('userflashcardquiz_set').annotate(
63502bd39   Rohan Rangray   Added fix for usi...
399
              study_count=Count('pk'),
e9fe651ba   Rohan Rangray   Implemented expon...
400
              days_since=Case(
63502bd39   Rohan Rangray   Added fix for usi...
401
402
                  When(userflashcardquiz__when=None, then=interval_days(Now(), F('pulled'))),
                  default=interval_days(Now(), Max('userflashcardquiz__when')),
e9fe651ba   Rohan Rangray   Implemented expon...
403
404
405
                  output_field=FloatField()
              ),
              retention_score=Case(
63502bd39   Rohan Rangray   Added fix for usi...
406
407
408
                  default=Value(e, output_field=FloatField()) ** (F('days_since')*(-0.1/(F('study_count')+1))),
                  output_field=FloatField()
              )
e9fe651ba   Rohan Rangray   Implemented expon...
409
          ).order_by('retention_score')
4ff8077bc   Rohan Rangray   Fixed merge confl...
410

e9fe651ba   Rohan Rangray   Implemented expon...
411
          user_flashcard = quiz_filter.first()
28a4bd2e7   Rohan Rangray   Refactored code i...
412
          mask = user_flashcard.get_mask().get_random_blank()
f66f9ca7d   Rohan Rangray   Fixed SwaggerUI p...
413
414
          user_flashcard_quiz = UserFlashcardQuiz(user_flashcard=user_flashcard,
                                                  blanked_word=user_flashcard.flashcard.text[slice(*mask)])
ee4104aa2   Rohan Rangray   Added a Study vie...
415
          user_flashcard_quiz.save()
f66f9ca7d   Rohan Rangray   Fixed SwaggerUI p...
416
          response = QuizResponseSerializer(instance=user_flashcard_quiz, mask=mask)
5a2899b9d   Andrew Buss   add gobs of logging
417
          log_event(request, response)
f66f9ca7d   Rohan Rangray   Fixed SwaggerUI p...
418
          return Response(response.data, status=HTTP_200_OK)
ee4104aa2   Rohan Rangray   Added a Study vie...
419

cf394ed25   Rohan Rangray   Changed /study/{p...
420
      def partial_update(self, request, *args, **kwargs):
ee4104aa2   Rohan Rangray   Added a Study vie...
421
422
423
424
425
          """
          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...
426
          request_serializer: serializers.QuizAnswerRequestSerializer
ee4104aa2   Rohan Rangray   Added a Study vie...
427
428
429
          """
          user_flashcard_quiz = self.get_object()
          serializer = QuizAnswerRequestSerializer(instance=user_flashcard_quiz, data=request.data)
7dcd94c01   Rohan Rangray   Added more extens...
430
          serializer.is_valid(raise_exception=True)
ee4104aa2   Rohan Rangray   Added a Study vie...
431
          serializer.update(user_flashcard_quiz, serializer.validated_data)
5a2899b9d   Andrew Buss   add gobs of logging
432
          log_event(request, serializer.data)
ee4104aa2   Rohan Rangray   Added a Study vie...
433
          return Response(status=HTTP_204_NO_CONTENT)