Commit 5c1348fee89a45afb07f214b9855b0b351009a3a

Authored by Andrew Buss
1 parent d17b18bb4f
Exists in master

halfway denormalize score

Showing 2 changed files with 58 additions and 16 deletions Side-by-side Diff

flashcards/migrations/0018_flashcard_score.py View file @ 5c1348f
  1 +# -*- coding: utf-8 -*-
  2 +from __future__ import unicode_literals
  3 +from math import log1p, exp
  4 +
  5 +from django.db import models, migrations
  6 +from django.utils.timezone import make_aware
  7 +
  8 +
  9 +def update_scores(apps, schema_editor):
  10 + Flashcard = apps.get_model("flashcards", "Flashcard")
  11 + for card in Flashcard.objects.all():
  12 + def seconds_since_epoch(dt):
  13 + from datetime import datetime
  14 +
  15 + epoch = make_aware(datetime.utcfromtimestamp(0))
  16 + delta = dt - epoch
  17 + return delta.total_seconds()
  18 +
  19 + z = 0
  20 + rate = 1.0 / 3600
  21 + for vote in card.userflashcard_set.iterator():
  22 + t = seconds_since_epoch(vote.pulled)
  23 + u = max(z, rate * t)
  24 + v = min(z, rate * t)
  25 + z = u + log1p(exp(v - u))
  26 + card.score = z
  27 + card.save()
  28 +
  29 +
  30 +class Migration(migrations.Migration):
  31 + dependencies = [
  32 + ('flashcards', '0017_auto_20150601_2001'),
  33 + ]
  34 +
  35 + operations = [
  36 + migrations.AddField(
  37 + model_name='flashcard',
  38 + name='score',
  39 + field=models.FloatField(default=0),
  40 + preserve_default=False,
  41 + ),
  42 + migrations.RunPython(update_scores),
  43 + ]
flashcards/models.py View file @ 5c1348f
... ... @@ -7,7 +7,7 @@
7 7 from django.contrib.auth.tokens import default_token_generator
8 8 from django.core.cache import cache
9 9 from django.core.exceptions import ValidationError
10   -from django.core.exceptions import PermissionDenied, SuspiciousOperation
  10 +from django.core.exceptions import PermissionDenied
11 11 from django.core.mail import send_mail
12 12 from django.core.validators import MinLengthValidator
13 13 from django.db import IntegrityError
... ... @@ -23,6 +23,8 @@
23 23  
24 24  
25 25  
  26 +
  27 +
26 28 # Hack to fix AbstractUser before subclassing it
27 29  
28 30 AbstractUser._meta.get_field('email')._unique = True
... ... @@ -127,6 +129,8 @@
127 129 except IntegrityError:
128 130 raise FlashcardAlreadyPulledException()
129 131  
  132 + flashcard.refresh_score()
  133 +
130 134 import flashcards.pushes
131 135  
132 136 flashcards.pushes.push_feed_event('score_change', flashcard)
133 137  
... ... @@ -135,13 +139,14 @@
135 139 def unpull(self, flashcard):
136 140 if not self.is_in_section(flashcard.section):
137 141 raise ValueError("User not in the section this flashcard belongs to")
138   -
139 142 try:
140 143 user_card = UserFlashcard.objects.get(user=self, flashcard=flashcard)
141 144 except UserFlashcard.DoesNotExist:
142 145 raise FlashcardNotInDeckException()
143 146 user_card.delete()
144 147  
  148 + flashcard.refresh_score()
  149 +
145 150 import flashcards.pushes
146 151  
147 152 flashcards.pushes.push_feed_event('score_change', flashcard)
... ... @@ -271,6 +276,7 @@
271 276 material_date = DateTimeField(default=now, help_text="The date with which the card is associated")
272 277 previous = ForeignKey('Flashcard', null=True, blank=True, default=None,
273 278 help_text="The previous version of this card, if one exists")
  279 + score = FloatField(default=0)
274 280 author = ForeignKey(User)
275 281 is_hidden = BooleanField(default=False)
276 282 hide_reason = CharField(blank=True, null=True, max_length=255, default='',
... ... @@ -284,6 +290,10 @@
284 290 def __unicode__(self):
285 291 return u'<flashcard: %s>' % self.text
286 292  
  293 + def refresh_score(self):
  294 + self.score = self.calculate_score
  295 + self.save()
  296 +
287 297 @classmethod
288 298 def push(cls, **kwargs):
289 299 card = cls(**kwargs)
... ... @@ -311,16 +321,6 @@
311 321 def is_in_deck(self, user):
312 322 return self.userflashcard_set.filter(user=user).exists()
313 323  
314   - def add_to_deck(self, user):
315   - if not user.is_in_section(self.section):
316   - raise PermissionDenied("You don't have the permission to add this card")
317   - try:
318   - user_flashcard = UserFlashcard.objects.create(user=user, flashcard=self, mask=self.mask)
319   - except IntegrityError:
320   - raise SuspiciousOperation("The flashcard is already in the user's deck")
321   - user_flashcard.save()
322   - return user_flashcard
323   -
324 324 def edit(self, user, new_data):
325 325 """
326 326 Creates a new flashcard if a new flashcard should be created when the given user edits this flashcard.
... ... @@ -347,7 +347,7 @@
347 347 self.pk = None
348 348 self.mask = new_data.get('mask', self.mask)
349 349 self.save()
350   - self.add_to_deck(user)
  350 + user.pull(self)
351 351 else:
352 352 user_card, created = UserFlashcard.objects.get_or_create(user=user, flashcard=self)
353 353 user_card.mask = new_data.get('mask', user_card.mask)
... ... @@ -372,7 +372,7 @@
372 372 hide.delete()
373 373  
374 374 @cached_property
375   - def score(self):
  375 + def calculate_score(self):
376 376 def seconds_since_epoch(dt):
377 377 from datetime import datetime
378 378  
... ... @@ -526,8 +526,7 @@
526 526 return '%s %s' % (self.department_abbreviation, self.course_num)
527 527  
528 528 def get_feed_for_user(self, user, page=1):
529   - cards = list(self.get_cards_for_user(user)[(page - 1) * self.PAGE_SIZE:page * self.PAGE_SIZE])
530   - cards.sort(key=lambda x: -x.score)
  529 + cards = self.get_cards_for_user(user).order_by('-score')[(page - 1) * self.PAGE_SIZE:page * self.PAGE_SIZE]
531 530 return cards
532 531  
533 532 def get_cards_for_user(self, user):