Commit f6900af2ccb7324eea6c1c2c211517fa4dfa86ed

Authored by Rohan Rangray
1 parent 2144619015
Exists in master

Added things to collect registration tokens and notify users

Showing 9 changed files with 98 additions and 6 deletions Side-by-side Diff

flashcards/management/commands/notifyusers.py View file @ f6900af
  1 +from django.core.management import BaseCommand
  2 +from flashcards.models import UserFlashcard, Now
  3 +from django.utils.timezone import now
  4 +from datetime import timedelta
  5 +
  6 +
  7 +class Command(BaseCommand):
  8 + help = 'Notify the users if they have cards to be reviewed'
  9 +
  10 + def handle(self, *args, **options):
  11 + notify_list = UserFlashcard.objects.filter(
  12 + next_review__lte=Now()
  13 + ).exclude(
  14 + user__registration_token=None,
  15 + user__last_notified__range=(now()-timedelta(days=1), now())
  16 + ).values_list('user').distinct().all()
  17 +
  18 + for user in notify_list:
  19 + user.notify()
flashcards/migrations/0015_user_registration_token.py View file @ f6900af
  1 +# -*- coding: utf-8 -*-
  2 +from __future__ import unicode_literals
  3 +
  4 +from django.db import models, migrations
  5 +
  6 +
  7 +class Migration(migrations.Migration):
  8 +
  9 + dependencies = [
  10 + ('flashcards', '0014_auto_20150601_1315'),
  11 + ]
  12 +
  13 + operations = [
  14 + migrations.AddField(
  15 + model_name='user',
  16 + name='registration_token',
  17 + field=models.CharField(default=None, max_length=4096, null=True),
  18 + ),
  19 + ]
flashcards/migrations/0016_user_last_notified.py View file @ f6900af
  1 +# -*- coding: utf-8 -*-
  2 +from __future__ import unicode_literals
  3 +
  4 +from django.db import models, migrations
  5 +
  6 +
  7 +class Migration(migrations.Migration):
  8 +
  9 + dependencies = [
  10 + ('flashcards', '0015_user_registration_token'),
  11 + ]
  12 +
  13 + operations = [
  14 + migrations.AddField(
  15 + model_name='user',
  16 + name='last_notified',
  17 + field=models.DateTimeField(default=None, null=True),
  18 + ),
  19 + ]
flashcards/models.py View file @ f6900af
1 1 from math import log1p
2 2 from math import exp
3 3 from datetime import datetime, timedelta
  4 +from gcm import GCM
4 5  
5 6 from django.contrib.auth.models import AbstractUser, UserManager
6 7 from django.contrib.auth.tokens import default_token_generator
7 8  
... ... @@ -12,11 +13,10 @@
12 13 from django.db import IntegrityError
13 14 from django.db.models import *
14 15 from django.utils.timezone import now, make_aware
15   -from flashy.settings import QUARTER_START, ABSOLUTE_URL_ROOT
  16 +from flashy.settings import QUARTER_START, ABSOLUTE_URL_ROOT, IN_PRODUCTION, GCM_API_KEY
16 17 from simple_email_confirmation import SimpleEmailConfirmationUserMixin, EmailAddress
17 18 from fields import MaskField
18 19 from cached_property import cached_property
19   -from flashy.settings import IN_PRODUCTION
20 20  
21 21  
22 22  
... ... @@ -78,6 +78,8 @@
78 78 REQUIRED_FIELDS = []
79 79 sections = ManyToManyField('Section', help_text="The sections which the user is enrolled in")
80 80 confirmed_email = BooleanField(default=False)
  81 + registration_token = CharField(null=True, default=None, max_length=4096)
  82 + last_notified = DateTimeField(null=True, default=None)
81 83  
82 84 @property
83 85 def locked(self):
... ... @@ -93,6 +95,19 @@
93 95 '''
94 96 send_mail("Flashy email verification", body % (ABSOLUTE_URL_ROOT, self.confirmation_key),
95 97 "noreply@flashy.cards", [self.email])
  98 +
  99 + def notify(self):
  100 + gcm = GCM(GCM_API_KEY)
  101 + gcm.plaintext_request(
  102 + registration_id=self.registration_token,
  103 + data="You have flashcards to study!"
  104 + )
  105 + self.last_notified = now()
  106 + self.save()
  107 +
  108 + def set_registration_token(self, token):
  109 + self.registration_token = token
  110 + self.save()
96 111  
97 112 def is_in_section(self, section):
98 113 return self.sections.filter(pk=section.pk).exists()
flashcards/serializers.py View file @ f6900af
... ... @@ -293,4 +293,8 @@
293 293 class Meta:
294 294 model = UserFlashcardQuiz
295 295 exclude = 'blanked_word', 'user_flashcard', 'when'
  296 +
  297 +
  298 +class SubscribeViewSerializer(Serializer):
  299 + registration_token = CharField(allow_blank=False, allow_null=False)
flashcards/views.py View file @ f6900af
... ... @@ -6,10 +6,10 @@
6 6 IsAuthenticatedAndConfirmed
7 7 from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcardQuiz, \
8 8 FlashcardAlreadyPulledException, FlashcardNotInDeckException
9   -from flashcards.notifications import notify_new_card, notify_pull
  9 +from flashcards.notifications import notify_new_card
10 10 from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \
11 11 PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \
12   - FlashcardUpdateSerializer, QuizRequestSerializer, QuizResponseSerializer, \
  12 + FlashcardUpdateSerializer, QuizRequestSerializer, QuizResponseSerializer, SubscribeViewSerializer, \
13 13 QuizAnswerRequestSerializer, DeepSectionSerializer, EmailVerificationSerializer, FeedRequestSerializer
14 14 from rest_framework.decorators import detail_route, permission_classes, api_view, list_route
15 15 from rest_framework.generics import ListAPIView, GenericAPIView
... ... @@ -230,6 +230,19 @@
230 230 auth.login(request, user)
231 231 log_event(request)
232 232 return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED)
  233 +
  234 +
  235 +@api_view(['POST'])
  236 +def subscribe(request, format=None):
  237 + """
  238 + Associate the user with the passed in registration token
  239 + ---
  240 + request_serializer: SubscribeViewSerializer
  241 + """
  242 + serializer = SubscribeViewSerializer(data=request.data)
  243 + serializer.is_valid(raise_exception=True)
  244 + request.user.set_registration_token(serializer.validated_data['registration_token'])
  245 + return Response(status=HTTP_204_NO_CONTENT)
233 246  
234 247  
235 248 @api_view(['POST'])
flashy/settings.py View file @ f6900af
... ... @@ -169,6 +169,7 @@
169 169 # If we're in production, SECRET_KEY is pulled from the environ
170 170 # Otherwise it doesn't matter
171 171 SECRET_KEY = os.environ.get('SECRET_KEY', 'LOL DEFAULT SECRET KEY')
  172 +GCM_API_KEY = os.environ.get('GCM_API_KEY', 'GCM PLS')
172 173  
173 174 CACHES = {
174 175 'default': {
flashy/urls.py View file @ f6900af
1 1 from django.conf.urls import include, url
2 2 from django.contrib import admin
3 3 from flashcards.views import SectionViewSet, UserDetail, FlashcardViewSet, UserSectionListView, request_password_reset, \
4   - reset_password, logout, login, register, UserFlashcardQuizViewSet, resend_confirmation_email, verify_email
  4 + reset_password, logout, login, register, UserFlashcardQuizViewSet, resend_confirmation_email, verify_email, subscribe
5 5 from flashy.frontend_serve import serve_with_default
6 6 from flashy.settings import DEBUG, IN_PRODUCTION
7 7 from rest_framework.routers import DefaultRouter
... ... @@ -18,6 +18,7 @@
18 18 url(r'^api/register/', register),
19 19 url(r'^api/login/$', login),
20 20 url(r'^api/logout/$', logout),
  21 + url(r'^api/subscribe/$', subscribe),
21 22 url(r'^api/me/sections/', UserSectionListView.as_view()),
22 23 url(r'^api/resend_confirmation_email/', resend_confirmation_email),
23 24 url(r'^api/verify_email/', verify_email),
requirements.txt View file @ f6900af
... ... @@ -14,4 +14,5 @@
14 14 pytz
15 15 django-extensions
16 16 django-redis-sessions
  17 +python-gcm