Commit 2f49f8258704c96bd02d13a3a0af1a947aff981f

Authored by Laura Hawkins
Exists in master

Merge branch 'master' of https://git.ucsd.edu/110swag/flashy-backend

Conflicts:
	flashcards/views.py

Showing 12 changed files Side-by-side Diff

flashcards/api.py View file @ 2f49f82
1   -from flashcards.models import Flashcard
  1 +from flashcards.models import Flashcard, UserFlashcardQuiz
2 2 from rest_framework.pagination import PageNumberPagination
3 3 from rest_framework.permissions import BasePermission
4 4  
5 5  
... ... @@ -24,6 +24,16 @@
24 24  
25 25 class IsEnrolledInAssociatedSection(BasePermission):
26 26 def has_object_permission(self, request, view, obj):
  27 + if obj is None:
  28 + return True
27 29 assert type(obj) is Flashcard
28 30 return request.user.is_in_section(obj.section)
  31 +
  32 +
  33 +class IsFlashcardReviewer(BasePermission):
  34 + def has_object_permission(self, request, view, obj):
  35 + if obj is None:
  36 + return True
  37 + assert type(obj) is UserFlashcardQuiz
  38 + return request.user == obj.user_flashcard.user
flashcards/fields.py View file @ 2f49f82
... ... @@ -40,7 +40,7 @@
40 40 def to_python(self, value):
41 41 if value is None:
42 42 return value
43   - return sorted(list(FlashcardMask(value)))
  43 + return FlashcardMask(value)
44 44  
45 45 def get_prep_value(self, value):
46 46 if value is None:
... ... @@ -65,7 +65,7 @@
65 65 @classmethod
66 66 def _varchar_parse_mask(cls, value):
67 67 if not value:
68   - return FlashcardMask([])
  68 + return FlashcardMask([])
69 69  
70 70 mask = [tuple(map(int, i.split('-'))) for i in value.split(',')]
71 71 return FlashcardMask(mask)
flashcards/models.py View file @ 2f49f82
... ... @@ -247,7 +247,7 @@
247 247 user_flashcard = ForeignKey(UserFlashcard)
248 248 when = DateTimeField(auto_now=True)
249 249 blanked_word = CharField(max_length=8, blank=True, help_text="The character range which was blanked")
250   - response = CharField(max_length=255, blank=True, null=True, help_text="The user's response")
  250 + response = CharField(max_length=255, blank=True, null=True, default=None, help_text="The user's response")
251 251 correct = NullBooleanField(help_text="The user's self-evaluation of their response")
252 252  
253 253 def status(self):
... ... @@ -313,7 +313,6 @@
313 313 """
314 314 return self.whitelist.filter(email=user.email).exists()
315 315  
316   -
317 316 def enroll(self, user):
318 317 if user.sections.filter(pk=self.pk).exists():
319 318 raise ValidationError('User is already enrolled in this section')
... ... @@ -327,7 +326,7 @@
327 326 self.user_set.remove(user)
328 327  
329 328 class Meta:
330   - ordering = ['-course_title']
  329 + ordering = ['department_abbreviation', 'course_num']
331 330  
332 331 @property
333 332 def lecture_times(self):
334 333  
... ... @@ -339,10 +338,9 @@
339 338 0].short_start_time
340 339 else:
341 340 data = ''
342   - cache.set("section_%d_lecture_times" % self.pk, data, 24*60*60)
  341 + cache.set("section_%d_lecture_times" % self.pk, data, 24 * 60 * 60)
343 342 return data
344 343  
345   -
346 344 @property
347 345 def long_name(self):
348 346 return '%s %s (%s)' % (self.course_title, self.lecture_times, self.instructor)
... ... @@ -352,7 +350,8 @@
352 350 return '%s %s' % (self.department_abbreviation, self.course_num)
353 351  
354 352 def get_feed_for_user(self, user):
355   - qs = Flashcard.objects.filter(section=self).exclude(userflashcard__user=user).order_by('pushed')
  353 + qs = Flashcard.objects.filter(section=self).exclude(flashcardhide__user=user).exclude(
  354 + userflashcard__user=user).order_by('pushed')
356 355 return qs
357 356  
358 357 def get_cards_for_user(self, user):
flashcards/serializers.py View file @ 2f49f82
... ... @@ -3,12 +3,14 @@
3 3 from django.utils.datetime_safe import datetime
4 4 from django.utils.timezone import now
5 5 import pytz
6   -from flashcards.models import Section, LecturePeriod, User, Flashcard
  6 +from flashcards.models import Section, LecturePeriod, User, Flashcard, UserFlashcard, UserFlashcardQuiz
7 7 from flashcards.validators import FlashcardMask, OverlapIntervalException
8 8 from rest_framework import serializers
9   -from rest_framework.fields import EmailField, BooleanField, CharField, IntegerField, DateTimeField
10   -from rest_framework.serializers import ModelSerializer, Serializer
  9 +from rest_framework.fields import EmailField, BooleanField, CharField, IntegerField, DateTimeField, empty
  10 +from rest_framework.serializers import ModelSerializer, Serializer, PrimaryKeyRelatedField, ListField
11 11 from rest_framework.validators import UniqueValidator
  12 +from flashy.settings import QUARTER_END, QUARTER_START
  13 +from random import sample
12 14  
13 15  
14 16 class EmailSerializer(Serializer):
15 17  
... ... @@ -125,12 +127,8 @@
125 127 mask = MaskFieldSerializer(allow_null=True)
126 128  
127 129 def validate_material_date(self, value):
128   - utc = pytz.UTC
129 130 # TODO: make this dynamic
130   - quarter_start = utc.localize(datetime(2015, 3, 15))
131   - quarter_end = utc.localize(datetime(2015, 6, 15))
132   -
133   - if quarter_start <= value <= quarter_end:
  131 + if QUARTER_START <= value <= QUARTER_END:
134 132 return value
135 133 else:
136 134 raise serializers.ValidationError("Material date is outside allowed range for this quarter")
... ... @@ -158,8 +156,7 @@
158 156 mask = MaskFieldSerializer(required=False)
159 157  
160 158 def validate_material_date(self, date):
161   - quarter_end = pytz.UTC.localize(datetime(2015, 6, 15))
162   - if date > quarter_end:
  159 + if date > QUARTER_END:
163 160 raise serializers.ValidationError("Invalid material_date for the flashcard")
164 161 return date
165 162  
... ... @@ -168,4 +165,122 @@
168 165 if not any(i in attrs for i in ['material_date', 'text', 'mask']):
169 166 raise serializers.ValidationError("No new value passed in")
170 167 return attrs
  168 +
  169 +
  170 +class QuizRequestSerializer(serializers.Serializer):
  171 + # sections = PrimaryKeyRelatedField(queryset=Section.objects.all(),required=False, many=True)
  172 + sections = ListField(child=IntegerField(min_value=1), required=False)
  173 + material_date_begin = DateTimeField(default=QUARTER_START)
  174 + material_date_end = DateTimeField(default=QUARTER_END)
  175 +
  176 + def __init__(self, user, *args, **kwargs):
  177 + super(QuizRequestSerializer, self).__init__(*args, **kwargs)
  178 + self.user = user
  179 + self.user_flashcard = None
  180 +
  181 + def create(self, validated_data):
  182 + return UserFlashcardQuiz.objects.create(user_flashcard=self.user_flashcard)
  183 +
  184 + def update(self, instance, validated_data):
  185 + for attr in validated_data:
  186 + setattr(instance, attr, validated_data[attr])
  187 + instance.save()
  188 + return instance
  189 +
  190 + def _get_user_flashcard(self, attrs):
  191 + user_flashcard_filter = UserFlashcard.objects.filter(
  192 + user=self.user, flashcard__section__in=attrs['sections'],
  193 + flashcard__material_date__gte=attrs['material_date_begin'],
  194 + flashcard__material_date__lte=attrs['material_date_end']
  195 + )
  196 + if not user_flashcard_filter.exists():
  197 + raise serializers.ValidationError("Your deck for that section is empty")
  198 + self.user_flashcard = user_flashcard_filter.order_by('?').first()
  199 +
  200 + def validate_material_date_begin(self, value):
  201 + if QUARTER_START <= value <= QUARTER_END:
  202 + return value
  203 + raise serializers.ValidationError("Invalid begin date for the flashcard range")
  204 +
  205 + def validate_material_date_end(self, value):
  206 + if QUARTER_START <= value <= QUARTER_END:
  207 + return value
  208 + raise serializers.ValidationError("Invalid end date for the flashcard range")
  209 +
  210 + def validate_sections(self, value):
  211 + if value is None:
  212 + return self.user.sections
  213 + section_filter = Section.objects.filter(pk__in=value)
  214 + if not section_filter.exists():
  215 + raise serializers.ValidationError("You aren't enrolled in those section(s)")
  216 + return section_filter
  217 +
  218 + def validate(self, attrs):
  219 + if attrs['material_date_begin'] > attrs['material_date_end']:
  220 + raise serializers.ValidationError("Invalid range")
  221 + if 'sections' not in attrs:
  222 + attrs['sections'] = self.validate_sections(None)
  223 + self._get_user_flashcard(attrs)
  224 + return attrs
  225 +
  226 +
  227 +class QuizResponseSerializer(ModelSerializer):
  228 + pk = PrimaryKeyRelatedField(queryset=UserFlashcardQuiz.objects.all(), many=True)
  229 + section = PrimaryKeyRelatedField(queryset=Section.objects.all())
  230 + text = CharField(max_length=255)
  231 + mask = ListField(child=IntegerField())
  232 +
  233 + def __init__(self, instance=None, mask=[], data=empty, **kwargs):
  234 + super(QuizResponseSerializer, self).__init__(instance=instance, data=data, **kwargs)
  235 + self.mask = self._validate_mask(mask)
  236 +
  237 + def to_representation(self, instance):
  238 + return {
  239 + 'pk': instance.user_flashcard.pk,
  240 + 'section': instance.user_flashcard.flashcard.section.pk,
  241 + 'text': instance.user_flashcard.flashcard.text,
  242 + 'mask': self.mask
  243 + }
  244 +
  245 + def _validate_mask(self, value):
  246 + if not isinstance(value, tuple) and value is not None:
  247 + raise serializers.ValidationError("The selected mask has to be a list")
  248 + if value is None or len(value) == 0:
  249 + return []
  250 + if len(value) == 2 and (0 <= value[0] and value[1] <= len(self.instance.user_flashcard.flashcard.text)):
  251 + return value
  252 + raise serializers.ValidationError("Invalid mask for the flashcard")
  253 +
  254 + class Meta:
  255 + model = UserFlashcardQuiz
  256 +
  257 +
  258 +class QuizAnswerRequestSerializer(ModelSerializer):
  259 + response = CharField(required=False, max_length=255, help_text="The user's response")
  260 + correct = BooleanField(required=False, help_text="The user's self-evaluation of their response")
  261 +
  262 + def __init__(self, instance, data, **kwargs):
  263 + assert isinstance(instance, UserFlashcardQuiz)
  264 + super(QuizAnswerRequestSerializer, self).__init__(instance, data, **kwargs)
  265 +
  266 + def validate_response(self, response):
  267 + if response is None:
  268 + return ""
  269 + return response
  270 +
  271 + def validate(self, attrs):
  272 + if not any(i in attrs for i in ('correct', 'response')):
  273 + raise serializers.ValidationError("No data passed in")
  274 + if 'response' in attrs and self.instance.response is not None:
  275 + raise serializers.ValidationError("You have already sent in a response for this quiz")
  276 + if 'correct' in attrs:
  277 + if 'response' not in attrs and self.instance.response is None:
  278 + raise serializers.ValidationError("You haven't sent in a response yet")
  279 + if self.instance.correct is not None:
  280 + raise serializers.ValidationError("You have already sent in the user's evaluation")
  281 + return attrs
  282 +
  283 + class Meta:
  284 + model = UserFlashcardQuiz
  285 + exclude = 'blanked_word', 'user_flashcard', 'when'
flashcards/tests/test_api.py View file @ 2f49f82
... ... @@ -358,10 +358,30 @@
358 358 response = self.client.get('/api/sections/{}/feed/'.format(self.section.pk))
359 359 self.assertEqual(response.status_code, HTTP_200_OK)
360 360 self.assertEqual(response.data[0]['id'], 1)
  361 + self.flashcard.hide_from(self.user)
  362 + response = self.client.get('/api/sections/{}/feed/'.format(self.section.pk))
  363 + self.assertEqual(response.status_code, HTTP_200_OK)
  364 + self.assertNotEqual(response.data[0]['id'], 1)
361 365  
362 366 def test_section_ordered_deck(self):
363 367 self.user.sections.add(self.section)
364 368 self.user.save()
365 369 response = self.client.get('/api/sections/1/ordered_deck/')
366 370 self.assertEqual(response.status_code, HTTP_200_OK)
  371 +
  372 +
  373 +class UserFlashcardQuizTests(APITestCase):
  374 + fixtures = ['testusers', 'testsections']
  375 +
  376 + def setUp(self):
  377 + self.client.login(email='none@none.com', password='1234')
  378 + self.user = User.objects.get(email='none@none.com')
  379 + self.section = Section.objects.get(pk=1)
  380 + self.flashcard = Flashcard(text="This is a flashcard", section=self.section, material_date=now(),
  381 + author=self.user, mask={(0,4), (5,7)})
  382 + self.flashcard.save()
  383 + self.flashcard.refresh_from_db()
  384 + self.user_flashcard = UserFlashcard(flashcard=self.flashcard, user=self.user,
  385 + mask=self.flashcard.mask, pulled=datetime.now())
  386 + self.user_flashcard.save()
flashcards/tests/test_models.py View file @ 2f49f82
1 1 from datetime import datetime
2 2  
3 3 from django.test import TestCase
4   -from flashcards.models import User, Section, Flashcard, UserFlashcard
  4 +from flashcards.models import User, Section, Flashcard, UserFlashcard, UserFlashcardQuiz
5 5 from flashcards.validators import FlashcardMask, OverlapIntervalException
  6 +from flashcards.serializers import QuizRequestSerializer, QuizResponseSerializer, QuizAnswerRequestSerializer
  7 +from flashy.settings import QUARTER_START, QUARTER_END
6 8  
7 9  
8 10 class RegistrationTests(TestCase):
... ... @@ -98,7 +100,6 @@
98 100 self.assertEqual(oie.message, "Invalid interval offsets in the mask")
99 101  
100 102  
101   -
102 103 class FlashcardTests(TestCase):
103 104 def setUp(self):
104 105 section = Section.objects.create(department='dept',
... ... @@ -113,7 +114,7 @@
113 114 author=user,
114 115 material_date=datetime.now(),
115 116 previous=None,
116   - mask={(24,34), (0, 4)})
  117 + mask={(24, 34), (0, 4)})
117 118 user.save()
118 119 section.save()
119 120 flashcard.save()
... ... @@ -148,4 +149,64 @@
148 149 self.fail()
149 150 except OverlapIntervalException:
150 151 self.assertTrue(True)
  152 +
  153 +
  154 +class UserFlashcardQuizTests(TestCase):
  155 + def setUp(self):
  156 + self.section = Section.objects.create(department='dept',
  157 + course_num='101a',
  158 + course_title='how 2 test',
  159 + instructor='George Lucas',
  160 + quarter='SP15')
  161 + self.user = User.objects.create_user(email="none@none.com", password="1234")
  162 + self.user.sections.add(self.section)
  163 + self.flashcard = Flashcard.objects.create(text="This is the text of the Flashcard",
  164 + section=self.section,
  165 + author=self.user,
  166 + material_date=datetime.now(),
  167 + previous=None,
  168 + mask=[(24, 33), (0, 4)])
  169 + self.user.save()
  170 + self.section.save()
  171 + self.flashcard.save()
  172 + self.user_flashcard = UserFlashcard.objects.create(flashcard=self.flashcard,
  173 + user=self.user,
  174 + mask=self.flashcard.mask,
  175 + pulled=datetime.now())
  176 + self.user_flashcard.save()
  177 + self.user_flashcard.refresh_from_db()
  178 + self.flashcard.refresh_from_db()
  179 +
  180 + def test_quiz_request(self):
  181 + data = {'sections': [1], 'material_date_begin': QUARTER_START, 'material_date_end': QUARTER_END}
  182 + serializer = QuizRequestSerializer(user=self.user, data=data)
  183 + serializer.is_valid(raise_exception=True)
  184 + user_flashcard_quiz = serializer.create(serializer.validated_data)
  185 + self.assertTrue(isinstance(user_flashcard_quiz, UserFlashcardQuiz))
  186 + mask = user_flashcard_quiz.user_flashcard.mask.get_random_blank()
  187 + self.assertIn(mask, [(24, 33), (0, 4)])
  188 + user_flashcard_quiz.blanked_word = user_flashcard_quiz.user_flashcard.flashcard.text[slice(*mask)]
  189 + self.assertIn(user_flashcard_quiz.blanked_word, ["This", "Flashcard"])
  190 + user_flashcard_quiz.save()
  191 + response = QuizResponseSerializer(instance=user_flashcard_quiz, mask=mask).data
  192 + self.assertEqual(response['pk'], 1)
  193 + self.assertEqual(response['section'], 1)
  194 + self.assertEqual(response['text'], user_flashcard_quiz.user_flashcard.flashcard.text)
  195 + self.assertEqual(response['mask'], mask)
  196 +
  197 + def test_quiz_answer(self):
  198 + data = {'response': 'Flashcard'}
  199 + mask = self.user_flashcard.mask.get_random_blank()
  200 + word = self.flashcard.text[slice(*mask)]
  201 + user_flashcard_quiz = UserFlashcardQuiz(user_flashcard=self.user_flashcard, blanked_word=word)
  202 + user_flashcard_quiz.save()
  203 + serializer = QuizAnswerRequestSerializer(instance=user_flashcard_quiz, data=data)
  204 + serializer.is_valid()
  205 + serializer.update(user_flashcard_quiz, serializer.validated_data)
  206 + self.assertEqual(user_flashcard_quiz.response, data['response'])
  207 + data = {'correct': True}
  208 + serializer = QuizAnswerRequestSerializer(instance=user_flashcard_quiz, data=data)
  209 + serializer.is_valid()
  210 + serializer.update(user_flashcard_quiz, serializer.validated_data)
  211 + self.assertTrue(user_flashcard_quiz.correct)
flashcards/validators.py View file @ 2f49f82
1 1 from collections import Iterable
  2 +from random import sample
2 3  
3 4  
4 5 class FlashcardMask(set):
... ... @@ -13,6 +14,11 @@
13 14  
14 15 def max_offset(self):
15 16 return self._end
  17 +
  18 + def get_random_blank(self):
  19 + if self.max_offset() > 0:
  20 + return sample(self, 1)[0]
  21 + return ()
16 22  
17 23 def _iterable_check(self, iterable):
18 24 if not isinstance(iterable, Iterable) or not all([isinstance(i, Iterable) for i in iterable]):
flashcards/views.py View file @ 2f49f82
... ... @@ -3,14 +3,15 @@
3 3 from django.contrib import auth
4 4 from django.core.cache import cache
5 5 from django.shortcuts import get_object_or_404
6   -from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection
7   -from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard
  6 +from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection, IsFlashcardReviewer
  7 +from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard, UserFlashcardQuiz
8 8 from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \
9 9 PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \
10   - FlashcardUpdateSerializer, DeepSectionSerializer
  10 + FlashcardUpdateSerializer, QuizRequestSerializer, QuizResponseSerializer, \
  11 + QuizAnswerRequestSerializer, DeepSectionSerializer
11 12 from rest_framework.decorators import detail_route, permission_classes, api_view, list_route
12 13 from rest_framework.generics import ListAPIView, GenericAPIView
13   -from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin
  14 +from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin
14 15 from rest_framework.permissions import IsAuthenticated
15 16 from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet
16 17 from django.core.mail import send_mail
... ... @@ -20,6 +21,7 @@
20 21 from rest_framework.response import Response
21 22 from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied
22 23 from simple_email_confirmation import EmailAddress
  24 +from random import sample
23 25  
24 26  
25 27 class SectionViewSet(ReadOnlyModelViewSet):
... ... @@ -37,7 +39,7 @@
37 39 flashcards = Flashcard.cards_visible_to(request.user).filter(section=self.get_object()).all()
38 40 return Response(FlashcardSerializer(flashcards, many=True).data)
39 41  
40   - @detail_route(methods=['post'])
  42 + @detail_route(methods=['POST'])
41 43 def enroll(self, request, pk):
42 44 """
43 45 Add the current user to a specified section
... ... @@ -49,7 +51,7 @@
49 51 self.get_object().enroll(request.user)
50 52 return Response(status=HTTP_204_NO_CONTENT)
51 53  
52   - @detail_route(methods=['post'])
  54 + @detail_route(methods=['POST'])
53 55 def drop(self, request, pk):
54 56 """
55 57 Remove the current user from a specified section
... ... @@ -92,7 +94,7 @@
92 94 serializer = FlashcardSerializer(qs, many=True)
93 95 return Response(serializer.data)
94 96  
95   - @detail_route(methods=['get'], permission_classes=[IsAuthenticated])
  97 + @detail_route(methods=['GET'], permission_classes=[IsAuthenticated])
96 98 def ordered_deck(self, request, pk):
97 99 """
98 100 Get a chronological order by material_date of flashcards for a section.
... ... @@ -273,7 +275,7 @@
273 275 return Response(response_data.data, status=HTTP_201_CREATED, headers=headers)
274 276  
275 277  
276   - @detail_route(methods=['post'])
  278 + @detail_route(methods=['POST'])
277 279 def unhide(self, request, pk):
278 280 """
279 281 Unhide the given card
... ... @@ -284,7 +286,7 @@
284 286 hide.delete()
285 287 return Response(status=HTTP_204_NO_CONTENT)
286 288  
287   - @detail_route(methods=['post'])
  289 + @detail_route(methods=['POST'])
288 290 def report(self, request, pk):
289 291 """
290 292 Hide the given card
... ... @@ -333,4 +335,38 @@
333 335 new_flashcard = data.validated_data
334 336 new_flashcard = flashcard.edit(user, new_flashcard)
335 337 return Response(FlashcardSerializer(new_flashcard).data, status=HTTP_200_OK)
  338 +
  339 +class UserFlashcardQuizViewSet(GenericViewSet, CreateModelMixin, UpdateModelMixin):
  340 + queryset = UserFlashcardQuiz.objects.all()
  341 + serializer_class = QuizAnswerRequestSerializer
  342 + permission_classes = [IsAuthenticated, IsFlashcardReviewer]
  343 +
  344 + def create(self, request, *args, **kwargs):
  345 + """
  346 + Return a card based on the request params.
  347 + :param request: A request object.
  348 + :param format: Format of the request.
  349 + :return: A response containing
  350 + """
  351 + serializer = QuizRequestSerializer(data=request.data)
  352 + serializer.is_valid(raise_exception=True)
  353 + user_flashcard_quiz = serializer.create(serializer.validated_data)
  354 + mask = sample(user_flashcard_quiz.user_flashcard.mask.get_random_blank(), 1)
  355 + user_flashcard_quiz.blanked_word = user_flashcard_quiz.user_flashcard.flashcard.text[slice(*mask)]
  356 + user_flashcard_quiz.save()
  357 + return Response(QuizResponseSerializer(instance=user_flashcard_quiz, mask=mask).data, status=HTTP_200_OK)
  358 +
  359 + def update(self, request, *args, **kwargs):
  360 + """
  361 + Receive the user's response to the quiz.
  362 + :param request: A request object.
  363 + :param format: Format of the request.
  364 + :return: A response containing
  365 + """
  366 + user_flashcard_quiz = self.get_object()
  367 + serializer = QuizAnswerRequestSerializer(instance=user_flashcard_quiz, data=request.data)
  368 + serializer.is_valid()
  369 + serializer.update(user_flashcard_quiz, serializer.validated_data)
  370 + return Response(status=HTTP_204_NO_CONTENT)
  371 +>>>>>>> 6713d6dc9f0ddfc99cc24a9b57194c0d5b032c91
flashy/settings.py View file @ 2f49f82
1 1 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
2 2 import os
  3 +from datetime import datetime
  4 +from pytz import UTC
3 5  
4 6 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
5 7  
6 8  
... ... @@ -24,11 +26,14 @@
24 26 'django.contrib.sessions',
25 27 'django.contrib.messages',
26 28 'django.contrib.staticfiles',
27   -
  29 + 'ws4redis',
28 30 'rest_framework_swagger',
29 31 'rest_framework',
30 32 ]
31 33  
  34 +WEBSOCKET_URL = '/ws/'
  35 +
  36 +
32 37 MIDDLEWARE_CLASSES = (
33 38 'django.contrib.sessions.middleware.SessionMiddleware',
34 39 'django.middleware.common.CommonMiddleware',
35 40  
... ... @@ -57,12 +62,14 @@
57 62 'django.template.context_processors.request',
58 63 'django.contrib.auth.context_processors.auth',
59 64 'django.contrib.messages.context_processors.messages',
  65 + 'django.core.context_processors.static',
  66 + 'ws4redis.context_processors.default',
60 67 ],
61 68 },
62 69 },
63 70 ]
64 71  
65   -WSGI_APPLICATION = 'flashy.wsgi.application'
  72 +WSGI_APPLICATION = 'ws4redis.django_runserver.application'
66 73  
67 74 DATABASES = {
68 75 'default': {
... ... @@ -86,6 +93,9 @@
86 93 USE_I18N = True
87 94 USE_L10N = True
88 95 USE_TZ = True
  96 +
  97 +QUARTER_START = UTC.localize(datetime(2015, 3, 30))
  98 +QUARTER_END = UTC.localize(datetime(2015, 6, 12))
89 99  
90 100 STATIC_URL = '/static/'
91 101 STATIC_ROOT = 'static'
flashy/urls.py View file @ 2f49f82
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
  4 + reset_password, logout, login, register, UserFlashcardQuizViewSet
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
... ... @@ -10,6 +10,7 @@
10 10 router = DefaultRouter()
11 11 router.register(r'sections', SectionViewSet)
12 12 router.register(r'flashcards', FlashcardViewSet)
  13 +router.register(r'study', UserFlashcardQuizViewSet)
13 14  
14 15 urlpatterns = [
15 16 url(r'^api/docs/', include('rest_framework_swagger.urls')),
requirements.txt View file @ 2f49f82
1 1 #beautifulsoup4
2 2 Django>=1.8
3   -#django-websocket-redis==0.4.3
  3 +django-websocket-redis
4 4 #gevent==1.0.1
5 5 #greenlet==0.4.5
6 6 #redis==2.10.3
scripts/run_production.sh View file @ 2f49f82
1 1 #!/bin/bash -xe
2 2 source secrets.sh
3 3 source venv/bin/activate
4   -newrelic-admin run-program /srv/flashy-backend/venv/bin/gunicorn --pid /run/flashy/gunicorn.pid -w 6 -n flashy -b 127.0.0.1:7002 flashy.wsgi
  4 +# newrelic-admin run-program /srv/flashy-backend/venv/bin/gunicorn --pid /run/flashy/gunicorn.pid -w 6 -n flashy -b 127.0.0.1:7002 flashy.wsgi
  5 +newrelic-admin run-program uwsgi /etc/uwsgi/flashy.ini --touch-reload=/etc/uwsgi/flashy.ini