Commit dc942208b989ddbe3368e02fa8dc331b8bcca639

Authored by Rohan Rangray
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 = [