Commit dc942208b989ddbe3368e02fa8dc331b8bcca639
Exists in
master
Fixed merge issue
Showing 4 changed files Side-by-side Diff
flashcards/models.py
View file @
dc94220
1 | 1 | from math import log1p |
2 | 2 | from math import exp |
3 | -from datetime import datetime, timedelta | |
3 | +from datetime import timedelta | |
4 | 4 | from gcm import GCM |
5 | 5 | |
6 | 6 | from django.contrib.auth.models import AbstractUser, UserManager |
... | ... | @@ -18,13 +18,6 @@ |
18 | 18 | from fields import MaskField |
19 | 19 | from cached_property import cached_property |
20 | 20 | |
21 | - | |
22 | - | |
23 | - | |
24 | - | |
25 | - | |
26 | - | |
27 | - | |
28 | 21 | # Hack to fix AbstractUser before subclassing it |
29 | 22 | |
30 | 23 | AbstractUser._meta.get_field('email')._unique = True |
... | ... | @@ -120,7 +113,6 @@ |
120 | 113 | user_card = UserFlashcard.objects.create(user=self, flashcard=flashcard) |
121 | 114 | except IntegrityError: |
122 | 115 | raise FlashcardAlreadyPulledException() |
123 | - user_card.save() | |
124 | 116 | |
125 | 117 | import flashcards.notifications |
126 | 118 | |
127 | 119 | |
128 | 120 | |
129 | 121 | |
... | ... | @@ -132,14 +124,16 @@ |
132 | 124 | raise ValueError("User not in the section this flashcard belongs to") |
133 | 125 | |
134 | 126 | try: |
135 | - import flashcards.notifications | |
136 | - | |
137 | 127 | user_card = UserFlashcard.objects.get(user=self, flashcard=flashcard) |
138 | - user_card.delete() | |
139 | - flashcards.notifications.notify_score_change(flashcard) | |
140 | 128 | except UserFlashcard.DoesNotExist: |
141 | 129 | raise FlashcardNotInDeckException() |
130 | + user_card.delete() | |
142 | 131 | |
132 | + import flashcards.notifications | |
133 | + | |
134 | + flashcards.notifications.notify_score_change(flashcard) | |
135 | + flashcards.notifications.notify_unpull(flashcard, self) | |
136 | + | |
143 | 137 | def get_deck(self, section): |
144 | 138 | if not self.is_in_section(section): |
145 | 139 | raise ObjectDoesNotExist("User not enrolled in section") |
... | ... | @@ -211,7 +205,7 @@ |
211 | 205 | def save(self, force_insert=False, force_update=False, using=None, |
212 | 206 | update_fields=None): |
213 | 207 | if self.pk is None: |
214 | - self.next_review = datetime.now() + timedelta(days=1) | |
208 | + self.next_review = now() + timedelta(days=1) | |
215 | 209 | super(UserFlashcard, self).save(force_insert=force_insert, force_update=force_update, |
216 | 210 | using=using, update_fields=update_fields) |
217 | 211 | |
... | ... | @@ -220,8 +214,8 @@ |
220 | 214 | if self.last_interval == 1: |
221 | 215 | self.last_interval = 6 |
222 | 216 | else: |
223 | - self.last_response_factor = min(1.3, self.last_response_factor+(0.1-(5-q)*(0.08+(5-q)*0.02))) | |
224 | - self.last_interval = int(round(self.last_interval*self.last_response_factor)) | |
217 | + self.last_response_factor = min(1.3, self.last_response_factor + (0.1 - (5 - q) * (0.08 + (5 - q) * 0.02))) | |
218 | + self.last_interval = int(round(self.last_interval * self.last_response_factor)) | |
225 | 219 | self.next_review = user_flashcard_quiz.when + timedelta(days=self.last_interval) |
226 | 220 | self.save() |
227 | 221 |
flashcards/notifications.py
View file @
dc94220
... | ... | @@ -29,4 +29,12 @@ |
29 | 29 | ) |
30 | 30 | message = RedisMessage(ws_message) |
31 | 31 | redis_publisher.publish_message(message) |
32 | + | |
33 | +def notify_unpull(flashcard, user): | |
34 | + redis_publisher = RedisPublisher(facility='deck/%d' % flashcard.section_id, users=[user]) | |
35 | + ws_message = JSONRenderer().render( | |
36 | + {'event_type': 'unpull_card', 'flashcard': serializers.FlashcardSerializer(flashcard).data} | |
37 | + ) | |
38 | + message = RedisMessage(ws_message) | |
39 | + redis_publisher.publish_message(message) |
flashcards/views.py
View file @
dc94220
... | ... | @@ -11,7 +11,7 @@ |
11 | 11 | PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \ |
12 | 12 | FlashcardUpdateSerializer, QuizRequestSerializer, QuizResponseSerializer, SubscribeViewSerializer, \ |
13 | 13 | QuizAnswerRequestSerializer, DeepSectionSerializer, EmailVerificationSerializer, FeedRequestSerializer |
14 | -from rest_framework.decorators import detail_route, permission_classes, api_view, list_route | |
14 | +from rest_framework.decorators import detail_route, permission_classes, api_view, list_route, throttle_classes | |
15 | 15 | from rest_framework.generics import ListAPIView, GenericAPIView |
16 | 16 | from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin |
17 | 17 | from rest_framework.permissions import IsAuthenticated |
18 | 18 | |
19 | 19 | |
... | ... | @@ -23,12 +23,14 @@ |
23 | 23 | from rest_framework.response import Response |
24 | 24 | from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied |
25 | 25 | from simple_email_confirmation import EmailAddress |
26 | +from rest_framework.throttling import UserRateThrottle | |
26 | 27 | |
27 | - | |
28 | 28 | def log_event(request, event=''): |
29 | 29 | logstr = u'%s %s %s %s' % (request.META['REMOTE_ADDR'], request.user, request.path, event) |
30 | 30 | getLogger('flashy.events').info(logstr) |
31 | 31 | |
32 | +class LimitFlashcardPushThrottle(UserRateThrottle): | |
33 | + rate = '1/min' | |
32 | 34 | |
33 | 35 | class SectionViewSet(ReadOnlyModelViewSet): |
34 | 36 | queryset = Section.objects.all() |
... | ... | @@ -320,6 +322,7 @@ |
320 | 322 | permission_classes = [IsAuthenticatedAndConfirmed, IsEnrolledInAssociatedSection] |
321 | 323 | |
322 | 324 | # Override create in CreateModelMixin |
325 | + @throttle_classes([LimitFlashcardPushThrottle]) | |
323 | 326 | def create(self, request, *args, **kwargs): |
324 | 327 | serializer = FlashcardSerializer(data=request.data) |
325 | 328 | serializer.is_valid(raise_exception=True) |
flashy/settings.py
View file @
dc94220
... | ... | @@ -19,7 +19,10 @@ |
19 | 19 | AUTH_USER_MODEL = 'flashcards.User' |
20 | 20 | REST_FRAMEWORK = { |
21 | 21 | 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', |
22 | - 'PAGE_SIZE': 20 | |
22 | + 'PAGE_SIZE': 20, | |
23 | + 'DEFAULT_THROTTLE_CLASSES': ( | |
24 | + 'rest_framework.throttling.UserRateThrottle', | |
25 | + ), | |
23 | 26 | } |
24 | 27 | |
25 | 28 | INSTALLED_APPS = [ |