Commit 1eebdbcc41fe5aff04c6fb4b0bf96bed29643c3e
Exists in
master
merge
Showing 2 changed files Side-by-side Diff
flashcards/models.py
View file @
1eebdbc
... | ... | @@ -11,11 +11,12 @@ |
11 | 11 | from django.db import IntegrityError |
12 | 12 | from django.db.models import * |
13 | 13 | from django.utils.timezone import now, make_aware |
14 | -from flashy.settings import QUARTER_START | |
14 | +from flashy.settings import QUARTER_START, QUARTER_END | |
15 | 15 | from simple_email_confirmation import SimpleEmailConfirmationUserMixin |
16 | 16 | from fields import MaskField |
17 | 17 | from cached_property import cached_property |
18 | 18 | from flashy.settings import IN_PRODUCTION |
19 | +from math import e | |
19 | 20 | |
20 | 21 | |
21 | 22 | # Hack to fix AbstractUser before subclassing it |
... | ... | @@ -137,6 +138,30 @@ |
137 | 138 | self.email_address_set.confirm(confirmation_key, save=True) |
138 | 139 | self.confirmed_email = True |
139 | 140 | self.save() |
141 | + | |
142 | + def by_retention(self, sections, material_date_begin, material_date_end): | |
143 | + section_pks = map(lambda i: i['pk'], sections.values('pk')) | |
144 | + user_flashcard_filter = UserFlashcard.objects.filter( | |
145 | + user=self, flashcard__section__pk__in=section_pks, | |
146 | + flashcard__material_date__gte=material_date_begin, | |
147 | + flashcard__material_date__lte=material_date_end | |
148 | + ) | |
149 | + | |
150 | + if not user_flashcard_filter.exists(): | |
151 | + raise ValidationError("No matching flashcard found in your decks") | |
152 | + | |
153 | + return user_flashcard_filter.prefetch_related('userflashcardquiz_set').annotate( | |
154 | + study_count=Count('pk'), | |
155 | + days_since=Case( | |
156 | + When(userflashcardquiz__when=None, then=interval_days(Now(), F('pulled'))), | |
157 | + default=interval_days(Now(), Max('userflashcardquiz__when')), | |
158 | + output_field=FloatField() | |
159 | + ), | |
160 | + retention_score=Case( | |
161 | + default=Value(e, output_field=FloatField()) ** (F('days_since')*(-0.1/(F('study_count')+1))), | |
162 | + output_field=FloatField() | |
163 | + ) | |
164 | + ).order_by('retention_score') | |
140 | 165 | |
141 | 166 | |
142 | 167 | class UserFlashcard(Model): |
flashcards/views.py
View file @
1eebdbc
1 | -from math import e | |
2 | - | |
3 | 1 | import django |
4 | 2 | from django.contrib import auth |
5 | 3 | from django.shortcuts import get_object_or_404 |
6 | 4 | from django.utils.log import getLogger |
7 | 5 | from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection, IsFlashcardReviewer, \ |
8 | 6 | IsAuthenticatedAndConfirmed |
9 | -from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard, UserFlashcardQuiz, \ | |
10 | - FlashcardAlreadyPulledException, FlashcardNotInDeckException, Now, interval_days | |
7 | +from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcardQuiz, \ | |
8 | + FlashcardAlreadyPulledException, FlashcardNotInDeckException | |
11 | 9 | from flashcards.notifications import notify_new_card |
12 | 10 | from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \ |
13 | 11 | PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \ |
... | ... | @@ -21,7 +19,6 @@ |
21 | 19 | from django.core.mail import send_mail |
22 | 20 | from django.contrib.auth import authenticate |
23 | 21 | from django.contrib.auth.tokens import default_token_generator |
24 | -from django.db.models import Count, Max, F, Value, When, Case, FloatField | |
25 | 22 | from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK |
26 | 23 | from rest_framework.response import Response |
27 | 24 | from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied |
28 | 25 | |
29 | 26 | |
30 | 27 | |
31 | 28 | |
... | ... | @@ -393,38 +390,16 @@ |
393 | 390 | serializer = QuizRequestSerializer(data=request.data) |
394 | 391 | serializer.is_valid(raise_exception=True) |
395 | 392 | data = serializer.validated_data |
396 | - user_flashcard_filter = UserFlashcard.objects.filter( | |
397 | - user=request.user, flashcard__section__pk__in=data['sections'], | |
398 | - flashcard__material_date__gte=data['material_date_begin'], | |
399 | - flashcard__material_date__lte=data['material_date_end'] | |
400 | - ) | |
393 | + user_flashcard = request.user.by_retention(**data).first() | |
401 | 394 | |
402 | - if not user_flashcard_filter.exists(): | |
403 | - raise ValidationError("No matching flashcard found in your decks") | |
404 | - | |
405 | - quiz_filter = user_flashcard_filter.prefetch_related('userflashcardquiz_set').annotate( | |
406 | - study_count=Count('pk'), | |
407 | - days_since=Case( | |
408 | - When(userflashcardquiz__when=None, then=interval_days(Now(), F('pulled'))), | |
409 | - default=interval_days(Now(), Max('userflashcardquiz__when')), | |
410 | - output_field=FloatField() | |
411 | - ), | |
412 | - retention_score=Case( | |
413 | - default=Value(e, output_field=FloatField()) ** (F('days_since') * (-0.1 / (F('study_count') + 1))), | |
414 | - output_field=FloatField() | |
415 | - ) | |
416 | - ).order_by('retention_score') | |
417 | - | |
418 | - user_flashcard = quiz_filter.first() | |
419 | 395 | mask = user_flashcard.get_mask().get_random_blank() |
420 | - if not mask: | |
421 | - blanked_word = "" | |
422 | - log_event(request, mask) | |
423 | - else: | |
396 | + blanked_word = "" | |
397 | + if mask: | |
424 | 398 | blanked_word = user_flashcard.flashcard.text[slice(*mask)] |
425 | - user_flashcard_quiz = UserFlashcardQuiz(user_flashcard=user_flashcard, | |
426 | - blanked_word=blanked_word) | |
399 | + | |
400 | + user_flashcard_quiz = UserFlashcardQuiz(user_flashcard=user_flashcard, blanked_word=blanked_word) | |
427 | 401 | user_flashcard_quiz.save() |
402 | + | |
428 | 403 | response = QuizResponseSerializer(instance=user_flashcard_quiz, mask=mask) |
429 | 404 | log_event(request, response.data) |
430 | 405 | return Response(response.data, status=HTTP_200_OK) |