From 776577266a0d1426343db9613ab50e013e0daee1 Mon Sep 17 00:00:00 2001 From: Andrew Buss Date: Wed, 20 May 2015 12:53:12 -0700 Subject: [PATCH] websockets notifications on new cards and score updates --- flashcards/models.py | 13 +++++++++++-- flashcards/notifications.py | 20 ++++++++++++++++++++ flashcards/serializers.py | 4 +--- flashcards/views.py | 15 ++++++++------- 4 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 flashcards/notifications.py diff --git a/flashcards/models.py b/flashcards/models.py index a995dd3..d55dba6 100644 --- a/flashcards/models.py +++ b/flashcards/models.py @@ -7,11 +7,13 @@ from django.core.mail import send_mail from django.db import IntegrityError from django.db.models import * from django.utils.timezone import now + from simple_email_confirmation import SimpleEmailConfirmationUserMixin from fields import MaskField # Hack to fix AbstractUser before subclassing it + AbstractUser._meta.get_field('email')._unique = True AbstractUser._meta.get_field('username')._unique = False @@ -73,18 +75,21 @@ class User(AbstractUser, SimpleEmailConfirmationUserMixin): user_card = UserFlashcard.objects.create(user=self, flashcard=flashcard) user_card.pulled = now() user_card.save() + import flashcards.notifications + flashcards.notifications.notify_score_change(flashcard) def unpull(self, flashcard): if not self.is_in_section(flashcard.section): raise ValueError("User not in the section this flashcard belongs to") try: + import flashcards.notifications user_card = UserFlashcard.objects.get(user=self, flashcard=flashcard) + user_card.delete() + flashcards.notifications.notify_score_change(flashcard) except UserFlashcard.DoesNotExist: raise ValueError('Cannot unpull card that is not pulled.') - user_card.delete() - def get_deck(self, section): if not self.is_in_section(section): raise ObjectDoesNotExist("User not enrolled in section") @@ -231,6 +236,10 @@ class Flashcard(Model): obj.reason = reason obj.save() + @property + def score(self): + return self.userflashcard_set.count() + @classmethod def cards_visible_to(cls, user): """ diff --git a/flashcards/notifications.py b/flashcards/notifications.py new file mode 100644 index 0000000..f998b35 --- /dev/null +++ b/flashcards/notifications.py @@ -0,0 +1,20 @@ +import serializers +from rest_framework.renderers import JSONRenderer +from ws4redis.publisher import RedisPublisher +from ws4redis.redis_store import RedisMessage + + +def notify_score_change(flashcard): + redis_publisher = RedisPublisher(facility='feed/%d' % flashcard.section_id, broadcast=True) + ws_message = JSONRenderer().render( + {'event_type': 'score_change', 'new_score': flashcard.score, 'flashcard_id': flashcard.pk}) + message = RedisMessage(ws_message) + redis_publisher.publish_message(message) + + +def notify_new_card(flashcard): + redis_publisher = RedisPublisher(facility='feed/%d' % flashcard.section_id, broadcast=True) + ws_message = JSONRenderer().render( + {'event_type': 'new_card', 'flashcard': serializers.FlashcardSerializer(flashcard).data}) + message = RedisMessage(ws_message) + redis_publisher.publish_message(message) diff --git a/flashcards/serializers.py b/flashcards/serializers.py index 0057cce..441d260 100644 --- a/flashcards/serializers.py +++ b/flashcards/serializers.py @@ -2,7 +2,6 @@ from json import dumps, loads from django.utils.datetime_safe import datetime from django.utils.timezone import now -import pytz from flashcards.models import Section, LecturePeriod, User, Flashcard, UserFlashcard, UserFlashcardQuiz from flashcards.validators import FlashcardMask, OverlapIntervalException from rest_framework import serializers @@ -10,7 +9,6 @@ from rest_framework.fields import EmailField, BooleanField, CharField, IntegerFi from rest_framework.serializers import ModelSerializer, Serializer, PrimaryKeyRelatedField, ListField from rest_framework.validators import UniqueValidator from flashy.settings import QUARTER_END, QUARTER_START -from random import sample class EmailSerializer(Serializer): @@ -78,11 +76,11 @@ class SectionSerializer(ModelSerializer): class Meta: model = Section + class DeepSectionSerializer(SectionSerializer): lectures = LecturePeriodSerializer(source='lectureperiod_set', many=True, read_only=True) - class UserSerializer(ModelSerializer): email = EmailField(required=False) sections = SectionSerializer(many=True) diff --git a/flashcards/views.py b/flashcards/views.py index e0929b3..ad1d1e1 100644 --- a/flashcards/views.py +++ b/flashcards/views.py @@ -1,10 +1,11 @@ -import django +from random import sample +import django from django.contrib import auth -from django.core.cache import cache from django.shortcuts import get_object_or_404 from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection, IsFlashcardReviewer -from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard, UserFlashcardQuiz +from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcardQuiz +from flashcards.notifications import notify_new_card from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \ PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \ FlashcardUpdateSerializer, QuizRequestSerializer, QuizResponseSerializer, \ @@ -21,7 +22,6 @@ from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_20 from rest_framework.response import Response from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied from simple_email_confirmation import EmailAddress -from random import sample class SectionViewSet(ReadOnlyModelViewSet): @@ -212,7 +212,7 @@ def login(request): @api_view(['POST']) -@permission_classes((IsAuthenticated, )) +@permission_classes((IsAuthenticated,)) def logout(request, format=None): """ Logs the authenticated user out. @@ -270,10 +270,11 @@ class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin): data['author'] = request.user flashcard = Flashcard.objects.create(**data) self.perform_create(flashcard) + notify_new_card(flashcard) headers = self.get_success_headers(data) - response_data = FlashcardSerializer(flashcard) - return Response(response_data.data, status=HTTP_201_CREATED, headers=headers) + response_data = FlashcardSerializer(flashcard).data + return Response(response_data, status=HTTP_201_CREATED, headers=headers) @detail_route(methods=['POST']) def unhide(self, request, pk): -- 1.9.1