Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

loads of refactoring, added redis, perf optimizations

  • Loading branch information...
commit 82ffb4a6b9d6141accc15f3aca56355bc038a0e0 1 parent 38d128c
@aehlke authored
Showing with 535 additions and 203 deletions.
  1. +1 −2  apps/flashcards/admin.py
  2. +4 −5 apps/flashcards/contextprocessors.py
  3. +28 −0 apps/flashcards/management/commands/update_redis.py
  4. +224 −0 apps/flashcards/migrations/0017_auto__del_schedulingoptions.py
  5. +24 −50 apps/flashcards/models/cards.py
  6. +32 −103 apps/flashcards/models/decks.py
  7. +27 −0 apps/flashcards/models/intervals.py
  8. +42 −7 apps/flashcards/models/managers/cardmanager.py
  9. +49 −0 apps/flashcards/models/redis_models.py
  10. +11 −10 apps/flashcards/models/repetitionscheduler.py
  11. +2 −1  apps/flashcards/models/undo.py
  12. +1 −1  apps/flashcards/restresources.py
  13. +1 −0  apps/flashcards/views/api/__init__.py
  14. +0 −5 apps/flashcards/views/crud.py
  15. +0 −3  apps/flashcards/views/rest/__init__.py
  16. +4 −5 apps/flashcards/views/shortcuts.py
  17. +1 −1  apps/importer/views.py
  18. 0  apps/manabi_redis/__init__.py
  19. +9 −0 apps/manabi_redis/models.py
  20. 0  apps/manabi_redis/tests.py
  21. 0  apps/manabi_redis/views.py
  22. +3 −4 apps/popups/views.py
  23. +13 −0 apps/utils/middleware.py
  24. +5 −0 apps/utils/utils.py
  25. +3 −0  context_processors.py
  26. +41 −0 graveyard.py
  27. +6 −0 templates/json_debug.html
  28. +1 −2  test_helpers.py
  29. +3 −4 views.py
View
3  apps/flashcards/admin.py
@@ -3,7 +3,7 @@
from books.models import Textbook
from flashcards.models import (
Deck, FactType, Fact, CardTemplate,
- FieldType, Card, FieldContent, SchedulingOptions, CardHistory)
+ FieldType, Card, FieldContent, CardHistory)
class CardAdmin(admin.ModelAdmin):
@@ -39,6 +39,5 @@ class TextbookAdmin(admin.ModelAdmin):
admin.site.register(FieldType, FieldTypeAdmin)
admin.site.register(Card, CardAdmin)
admin.site.register(FieldContent)
-admin.site.register(SchedulingOptions)
admin.site.register(Textbook)
View
9 apps/flashcards/contextprocessors.py
@@ -1,15 +1,13 @@
from flashcards.models import (FactType, Fact, Deck, CardTemplate,
FieldType, FieldContent, Card,
- GRADE_NONE, GRADE_HARD, GRADE_GOOD, GRADE_EASY, SchedulingOptions,)
+ GRADE_NONE, GRADE_HARD, GRADE_GOOD, GRADE_EASY)
from django.template.loader import render_to_string
import datetime
def subfact_form_context(request, subfact=None,
field_content_offset=0, fact_form_ordinal=1):
- '''
- `form_ordinal` is the offset to use for e.g. fact-1-id
- '''
+ ''' `form_ordinal` is the offset to use for e.g. fact-1-id '''
context = {}
sentence_fact_type = FactType.objects.get(id=2)
field_types = sentence_fact_type.fieldtype_set.exclude(
@@ -103,7 +101,8 @@ def review_start_context(request, deck=None):
card_count = cards.count()
- unspaced_new_card_count = cards.unspaced_new_count(user)
+ # this is much faster but less accurate than cards.unspaced_new_count.
+ unspaced_new_card_count = cards.approx_new_count(user)
context = {
'card_count': card_count,
View
28 apps/flashcards/management/commands/update_redis.py
@@ -0,0 +1,28 @@
+
+from django.core.management.base import BaseCommand
+
+from flashcards.models.constants import (
+ GRADE_NONE, GRADE_HARD, GRADE_GOOD, GRADE_EASY,
+ MAX_NEW_CARD_ORDINAL, EASE_FACTOR_MODIFIERS,
+ YOUNG_FAILURE_INTERVAL, MATURE_FAILURE_INTERVAL,
+ MATURE_INTERVAL_MIN, GRADE_EASY_BONUS_FACTOR,
+ DEFAULT_EASE_FACTOR, INTERVAL_FUZZ_MAX,
+ ALL_GRADES, GRADE_NAMES)
+from flashcards.models import (FactType, Fact, Deck, CardTemplate, Card)
+from apps.manabi_redis.models import redis
+
+
+
+def active_cards():
+ pass
+
+
+def update_redis():
+ for card in Card.objects.all():
+ card.redis.update_all()
+
+
+class Command(BaseCommand):
+ def handle(self, *args, **options):
+ update_redis()
+
View
224 apps/flashcards/migrations/0017_auto__del_schedulingoptions.py
@@ -0,0 +1,224 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Deleting model 'SchedulingOptions'
+ db.delete_table('flashcards_schedulingoptions')
+
+
+ def backwards(self, orm):
+
+ # Adding model 'SchedulingOptions'
+ db.create_table('flashcards_schedulingoptions', (
+ ('unknown_interval_min', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)),
+ ('deck', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['flashcards.Deck'], unique=True)),
+ ('easy_interval_min', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)),
+ ('mature_unknown_interval_max', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('medium_interval_max', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)),
+ ('medium_interval_min', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)),
+ ('easy_interval_max', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)),
+ ('hard_interval_min', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)),
+ ('hard_interval_max', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)),
+ ('unknown_interval_max', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)),
+ ('mature_unknown_interval_min', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)),
+ ))
+ db.send_create_signal('flashcards', ['SchedulingOptions'])
+
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'flashcards.card': {
+ 'Meta': {'unique_together': "(('fact', 'template'),)", 'object_name': 'Card'},
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+ 'due_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+ 'ease_factor': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
+ 'fact': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['flashcards.Fact']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'interval': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+ 'last_due_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'last_ease_factor': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
+ 'last_failed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'last_interval': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
+ 'last_review_grade': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'last_reviewed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'leech': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'new_card_ordinal': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}),
+ 'review_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'suspended': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+ 'template': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['flashcards.CardTemplate']"})
+ },
+ 'flashcards.cardhistory': {
+ 'Meta': {'object_name': 'CardHistory'},
+ 'card': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['flashcards.Card']"}),
+ 'duration': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
+ 'ease_factor': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'interval': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+ 'question_duration': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
+ 'response': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'reviewed_at': ('django.db.models.fields.DateTimeField', [], {}),
+ 'was_new': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'})
+ },
+ 'flashcards.cardtemplate': {
+ 'Meta': {'ordering': "['ordinal']", 'unique_together': "(('name', 'fact_type'), ('ordinal', 'fact_type'))", 'object_name': 'CardTemplate'},
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'allow_blank_back': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'back_prompt': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'back_template_name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'card_synchronization_group': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'max_length': '200', 'blank': 'True'}),
+ 'fact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['flashcards.FactType']"}),
+ 'front_prompt': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'front_template_name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'generate_by_default': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hide_front': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'js_template': ('django.db.models.fields.TextField', [], {'max_length': '600', 'blank': 'True'}),
+ 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'ordinal': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'requisite_field_types': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['flashcards.FieldType']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'flashcards.deck': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Deck'},
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'max_length': '2000', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+ 'picture': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
+ 'shared': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'shared_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'suspended': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+ 'synchronized_with': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'subscriber_decks'", 'null': 'True', 'to': "orm['flashcards.Deck']"}),
+ 'textbook_source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['flashcards.Textbook']", 'null': 'True', 'blank': 'True'})
+ },
+ 'flashcards.fact': {
+ 'Meta': {'unique_together': "(('deck', 'synchronized_with'),)", 'object_name': 'Fact'},
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'deck': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['flashcards.Deck']", 'null': 'True', 'blank': 'True'}),
+ 'fact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['flashcards.FactType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'new_fact_ordinal': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'notes': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
+ 'parent_fact': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_facts'", 'null': 'True', 'to': "orm['flashcards.Fact']"}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}),
+ 'synchronized_with': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'subscriber_facts'", 'null': 'True', 'to': "orm['flashcards.Fact']"})
+ },
+ 'flashcards.facttype': {
+ 'Meta': {'object_name': 'FactType'},
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'many_children_per_fact': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ 'min_card_space': ('django.db.models.fields.FloatField', [], {'default': '0.006944444444444444'}),
+ 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'parent_fact_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_fact_types'", 'null': 'True', 'to': "orm['flashcards.FactType']"}),
+ 'space_factor': ('django.db.models.fields.FloatField', [], {'default': '0.1'})
+ },
+ 'flashcards.fieldcontent': {
+ 'Meta': {'object_name': 'FieldContent'},
+ 'cached_transliteration_without_markup': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
+ 'content': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
+ 'fact': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['flashcards.Fact']"}),
+ 'field_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['flashcards.FieldType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'media_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'media_uri': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
+ },
+ 'flashcards.fieldtype': {
+ 'Meta': {'unique_together': "(('name', 'fact_type'), ('ordinal', 'fact_type'), ('display_name', 'fact_type'))", 'object_name': 'FieldType'},
+ 'accepts_media': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'blank': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'character_restriction': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True', 'blank': 'True'}),
+ 'choices': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'disabled_in_form': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'display_name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'editable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'fact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['flashcards.FactType']"}),
+ 'grid_column_width': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'}),
+ 'help_text': ('django.db.models.fields.CharField', [], {'max_length': '500', 'blank': 'True'}),
+ 'hidden_in_form': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'hidden_in_grid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'language': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True', 'blank': 'True'}),
+ 'media_restriction': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True', 'blank': 'True'}),
+ 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'multi_line': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'numeric': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'ordinal': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'transliteration_field_type': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'reverse_transliteration_field_type'", 'unique': 'True', 'null': 'True', 'to': "orm['flashcards.FieldType']"}),
+ 'unique': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ 'flashcards.textbook': {
+ 'Meta': {'object_name': 'Textbook'},
+ 'custom_title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'isbn': ('django.db.models.fields.CharField', [], {'max_length': '13'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'})
+ },
+ 'flashcards.undocardreview': {
+ 'Meta': {'object_name': 'UndoCardReview'},
+ 'card': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['flashcards.Card']"}),
+ 'card_history': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['flashcards.CardHistory']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'pickled_card': ('picklefield.fields.PickledObjectField', [], {}),
+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ }
+ }
+
+ complete_apps = ['flashcards']
View
74 apps/flashcards/models/cards.py
@@ -19,7 +19,7 @@
from managers.cardmanager import CardManager
from repetitionscheduler import repetition_algo_dispatcher
from undo import UndoCardReview
-
+
class Card(models.Model):
objects = CardManager()
@@ -82,14 +82,19 @@ def format_(content):
return u'{0} | {1}'.format(front, back)
def copy(self, target_fact):
- '''
- Returns a new Card object.
- '''
- return Card(fact=target_fact,
+ ''' Returns a new Card object. '''
+ card = Card(fact=target_fact,
template_id=self.template_id,
priority=self.priority,
leech=False, active=True, suspended=False,
new_card_ordinal=self.new_card_ordinal)
+ card.redis.update_all()
+ return card
+
+ @property
+ def redis(self):
+ from apps.flashcards.models.redis_models import RedisCard
+ return RedisCard(self)
@property
def owner(self):
@@ -119,6 +124,13 @@ def last_review_grade_name(self):
'''Returns a string version of the last review grade.'''
return GRADE_NAMES.get(self.last_review_grade)
+ def update_due_at(self, due_at, update_last_due_at=True):
+ if update_last_due_at:
+ self.last_due_at, self.due_at = (
+ self.due_at, due_at)
+ else:
+ self.due_at = due_at
+
def randomize_new_order(self):
'''
Randomizes the order of this card, for selecting new cards.
@@ -188,7 +200,6 @@ def total_question_duration(self):
return self.cardhistory_set.aggregate(
Sum('question_duration'))['question_duration__sum']
-
@cached_function(namespace=lambda c, *args, **kwargs:
fact_grid_namespace(c.deck.pk))
def _render(self, template_name):
@@ -254,7 +265,8 @@ def delay(self, duration):
'''
now = datetime.utcnow()
from_date = self.due_at if self.due_at >= now else now
- self.due_at = from_date + duration
+ self.update_due_at(from_date + duration, update_last_due_at=False)
+ #self.redis.update_due_at()
def next_repetition_per_grade(self, reviewed_at=None):
#FIXME disable fuzzing
@@ -307,8 +319,9 @@ def _apply_updated_schedule(self, next_repetition):
self.ease_factor, next_repetition.ease_factor)
self.last_interval, self.interval = (
self.interval, next_repetition.interval)
- self.last_due_at, self.due_at = (
- self.due_at, next_repetition.due_at)
+ self.update_due_at(next_repetition.due_at)
+
+ self.redis.update_ease_factor()
def review(self, grade, duration=None, question_duration=None):
'''
@@ -344,7 +357,7 @@ def review(self, grade, duration=None, question_duration=None):
self._apply_updated_schedule(next_repetition)
self.last_review_grade = grade
- self.last_reviewed_at = reviewed_at
+ self.last_reviewed_at = reviewed_at
if grade == GRADE_NONE:
self.last_failed_at = reviewed_at
@@ -355,45 +368,6 @@ def review(self, grade, duration=None, question_duration=None):
card_history_item.save()
self.save()
+ self.redis.after_review()
post_card_reviewed.send(self, instance=self)
-
-#TODO implement (remember to update UndoReview too)
-# This can probably just be a proxy model for CardHistory or something.
-#class CardStatistics(models.Model):
-# card = models.ForeignKey(Card)
-
-# failure_count = models.PositiveIntegerField(default=0, editable=False)
-# #TODO review stats depending on how card was rated, and how mature it is
-
-# #apparently needed for synchronization/import purposes
-# yes_count = models.PositiveIntegerField(default=0, editable=False)
-# no_count = models.PositiveIntegerField(default=0, editable=False)
-
-# average_thinking_time = models.PositiveIntegerField(null=True, editable=False)
-
-# #initial_ease
-
-# successive_count = models.PositiveIntegerField(default=0, editable=False) #incremented at each success, zeroed at failure
-# successive_streak_count = models.PositiveIntegerField(default=0, editable=False) #incremented at each failure after a success
-# average_successive_count = models.PositiveIntegerField(default=0, editable=False) #
-
-# skip_count = models.PositiveIntegerField(default=0, editable=False)
-# total_review_time = models.FloatField(default=0) #s
-# first_reviewed_at = models.DateTimeField()
-# first_success_at = models.DateTimeField()
-
-
-# #these take into account short-term memory effects
-# #they ignore any more than a single review per day (or 8 hours - TBD)
-# #adjusted_review_count = models.PositiveIntegerField(default=0, editable=False)
-# #adjusted_success_count = models.PositiveIntegerField(default=0, editable=False)
-# #first_adjusted_success_at = models.DateTimeField()
-# #failures_in_a_row = models.PositiveIntegerField(default=0, editable=False)
-# #adjusted_failures_in_a_row = models.PositiveIntegerField(default=0, editable=False)
-
-# class Meta:
-# app_label = 'flashcards'
-
-
-
View
135 apps/flashcards/models/decks.py
@@ -1,7 +1,7 @@
-from books.models import Textbook
+import datetime
+import random
+
from cachecow.cache import cached_function
-from constants import DEFAULT_EASE_FACTOR
-from constants import GRADE_NONE, GRADE_HARD, GRADE_GOOD, GRADE_EASY
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.db import models
@@ -9,12 +9,16 @@
from django.db.models import Avg
from django.forms import ModelForm
from django.forms.util import ErrorList
+from model_utils.managers import manager_from
+
+from apps.manabi_redis.models import redis
+from books.models import Textbook
+from constants import DEFAULT_EASE_FACTOR
+from constants import GRADE_NONE, GRADE_HARD, GRADE_GOOD, GRADE_EASY
from flashcards.cachenamespaces import deck_review_stats_namespace
+from flashcards.models.intervals import initial_interval
from itertools import chain
-from model_utils.managers import manager_from
import cards
-import datetime
-import random
import usertagging
@@ -211,10 +215,6 @@ def subscribe(self, user):
# copy the tags
deck.tags = usertagging.utils.edit_string_for_tags(self.tags)
- # create default deck scheduling options
- scheduling_options = SchedulingOptions(deck=deck)
- scheduling_options.save()
-
# copy the facts - just the first few as a buffer
shared_fact_to_fact = {}
#TODO dont hardcode value here #chain(self.fact_set.all(), Fact.objects.filter(parent_fact__deck=self)):
@@ -250,43 +250,35 @@ def subscribe(self, user):
#done!
return deck
- @property
def card_count(self):
- #FIXME this is inaccurate for synchronized deck which the (subscriber)
- # user has added cards to
- deck = self.synchronized_with if self.synchronized_with else self
- return cards.Card.objects.filter(
- fact__deck=deck, active=True, suspended=False).count()
-
- @property
- def new_card_count(self):
- #FIXME do for sync'd decks
- return cards.Card.objects.cards_new_count(
- self.owner, deck=self, active=True, suspended=False)
-
- @property
- def due_card_count(self):
- return cards.Card.objects.cards_due_count(
- self.owner, deck=self, active=True, suspended=False)
+ return cards.Card.objects.of_deck(self, with_upstream=True).available().count()
+
+ #TODO kill - unused?
+ #@property
+ #def new_card_count(self):
+ # return Card.objects.approx_new_count(deck=self)
+ # #FIXME do for sync'd decks
+ # return cards.Card.objects.cards_new_count(
+ # self.owner, deck=self, active=True, suspended=False)
+
+ #TODO kill - unused?
+ #@property
+ #def due_card_count(self):
+ # return cards.Card.objects.cards_due_count(
+ # self.owner, deck=self, active=True, suspended=False)
@cached_function(namespace=deck_review_stats_namespace)
def average_ease_factor(self):
'''
- Includes suspended cards in the calcuation. Doesn't include inactive
- cards.
+ Includes suspended cards in the calcuation. Doesn't include inactive cards.
'''
- deck_cards = cards.Card.objects.filter(
- fact__deck=self,
- active=True,
- ease_factor__isnull=False)
- if deck_cards.exists():
- average_ef = deck_cards.aggregate(
- average_ease_factor=Avg('ease_factor')
- )['average_ease_factor']
- if average_ef:
- return average_ef
+ ease_factors = redis.zrange('ease_factor:deck:{0}'.format(self.id),
+ 0, -1, withscores=True)
+ cardinality = len(ease_factors)
+ if cardinality:
+ return sum(score for val,score in ease_factors) / cardinality
return DEFAULT_EASE_FACTOR
-
+
@transaction.commit_on_success
def delete_cascading(self):
#FIXME if this is a shared/synced deck
@@ -294,7 +286,6 @@ def delete_cascading(self):
for card in fact.card_set.all():
card.delete()
fact.delete()
- self.schedulingoptions.delete()
self.delete()
def export_to_csv(self):
@@ -401,65 +392,3 @@ def writerows(self, rows):
usertagging.register(Deck)
-
-DEFAULT_INTERVALS = {
- GRADE_NONE: (20.0/(24.0*60.0), 25.0/(24.0*60.0)),
- #cards.GRADE_mature_unknown': (0.333, 0.333),
- GRADE_HARD: (0.333, 0.5),
- GRADE_GOOD: (3.0, 5.0),
- GRADE_EASY: (7.0, 9.0),
-}
-
-
-class SchedulingOptions(models.Model):
- deck = models.OneToOneField(Deck)
-
- mature_unknown_interval_min = models.FloatField(null=True, blank=True)
- mature_unknown_interval_max = models.FloatField(null=True, blank=True)
- unknown_interval_min = models.FloatField(null=True, blank=True) #
- unknown_interval_max = models.FloatField(null=True, blank=True) #TODO more? 0.5)
- hard_interval_min = models.FloatField(null=True, blank=True) # 8 hours
- hard_interval_max = models.FloatField(null=True, blank=True) # 12 hours
- medium_interval_min = models.FloatField(null=True, blank=True) # 3 days
- medium_interval_max = models.FloatField(null=True, blank=True) # 5 days
- easy_interval_min = models.FloatField(null=True, blank=True) # 7 days
- easy_interval_max = models.FloatField(null=True, blank=True) # 9 days
-
- def __unicode__(self):
- return self.deck.name
-
- class Meta:
- app_label = 'flashcards'
-
- #TODO should be classmethod
- def _generate_interval(self, min_duration, max_duration):
- #TODO favor (random.triangular) conservatism
- return random.uniform(min_duration, max_duration)
-
- def _interval_min_max(self, grade):
- if grade == GRADE_NONE:
- min_, max_ = self.unknown_interval_min, self.unknown_interval_max
- if grade == GRADE_HARD:
- min_, max_ = self.hard_interval_min, self.hard_interval_max
- elif grade == GRADE_GOOD:
- min_, max_ = self.medium_interval_min, self.medium_interval_max
- elif grade == GRADE_EASY:
- min_, max_ = self.easy_interval_min, self.easy_interval_max
-
- #TODO we don't use these yet since we don't allow user customizing
- if True or (min_ is None or max_ is None):
- return DEFAULT_INTERVALS[grade]
-
- return (min_, max_,)
-
- def initial_interval(self, grade, do_fuzz=True):
- '''
- Generates an initial interval duration for a new card that's been reviewed.
- '''
- min_, max_ = self._interval_min_max(grade)
-
- if do_fuzz:
- return self._generate_interval(min_, max_)
- else:
- return (min_ + max_) / 2.0
-
View
27 apps/flashcards/models/intervals.py
@@ -0,0 +1,27 @@
+import random
+
+from constants import GRADE_NONE, GRADE_HARD, GRADE_GOOD, GRADE_EASY
+
+DEFAULT_INTERVALS = {
+ GRADE_NONE: (20.0/(24.0*60.0), 25.0/(24.0*60.0)),
+ #cards.GRADE_mature_unknown': (0.333, 0.333),
+ GRADE_HARD: (0.333, 0.5),
+ GRADE_GOOD: (3.0, 5.0),
+ GRADE_EASY: (7.0, 9.0),
+}
+
+
+def _generate_interval(min_duration, max_duration):
+ #TODO favor (random.triangular) conservatism
+ return random.uniform(min_duration, max_duration)
+
+def initial_interval(deck, grade, do_fuzz=True):
+ '''
+ Generates an initial interval duration for a new card that's been reviewed.
+ '''
+ min_, max_ = DEFAULT_INTERVALS[grade]
+
+ if do_fuzz:
+ return _generate_interval(min_, max_)
+ return (min_ + max_) / 2.0
+
View
49 apps/flashcards/models/managers/cardmanager.py
@@ -1,8 +1,10 @@
+import datetime
+from itertools import chain
+
from django.db import models
from django.db.models import Avg, Max, Min, Count
-from itertools import chain
+
from model_utils.managers import manager_from
-import datetime
from flashcards.models.constants import (
GRADE_NONE, GRADE_HARD, GRADE_GOOD, GRADE_EASY,
MAX_NEW_CARD_ORDINAL, EASE_FACTOR_MODIFIERS,
@@ -35,6 +37,23 @@ def _space_cards(self, card_query, count, review_time,
# since we shouldn't set their due_at (via delay())
delayed_cards = []
+ # REDIS WIP
+ #while True:
+ # cards = card_query.exclude(
+ # id__in=[card.id for card in delayed_cards])
+ # card_ids = cards[:count].values_list('id', flat=True)
+
+ # if early_review and len(cards) == 0:
+ # return delayed_cards[:count]
+
+ # # get cards to delay.
+ # # get fact IDs
+ # # get zrange of score >
+ # for card in cards:
+ # min_space = card.sibling_spacing()
+ # fact_id = card.fact_id
+ # if redis.zrangebyscore()
+
while True:
cards_delayed = 0
cards = card_query.exclude(
@@ -55,7 +74,6 @@ def _space_cards(self, card_query, count, review_time,
and abs(card.due_at
- sibling.last_reviewed_at)
<= min_space):
- #
# Delay the card. It's already sorted by priority,
# so we delay this one instead of its sibling.
if card.is_new() or early_review:
@@ -134,7 +152,7 @@ def _next_new_cards(self, user, initial_query, count, review_time,
def _next_new_cards2():
new_cards = []
- for card in new_card_query.select_related().iterator():
+ for card in new_card_query.iterator():
min_space = card.sibling_spacing()
for sibling in card.siblings:
@@ -303,6 +321,10 @@ class CommonFiltersMixin(object):
This is particularly useful with view URLs which take query params for
these things.
'''
+ def available(self):
+ ''' Cards which are active and unsuspended. '''
+ return self.filter(active=True, suspended=False)
+
def of_deck(self, deck, with_upstream=False):
cards = self.filter(fact__deck=deck)
if with_upstream and deck.synchronized_with:
@@ -380,10 +402,23 @@ def new_count(self, user):
'''
return self.new(user).count() + self.of_upstream(user).count()
+ def approx_new_count(self, user=None, deck=None):
+ '''
+ Approximates how many new cards are actually available to review.
+ Will be between what new_count and unspaced_new_count return,
+ but much faster than the latter.
+ '''
+ cards = self.available()
+ if deck:
+ cards = cards.of_deck(deck)
+ return (cards.new(user).values_list('fact_id').distinct().count() +
+ cards.of_upstream(user).values_list('fact_id').distinct().count())
+
def unspaced_new_count(self, user):
'''
Same as `new_count`, except it subtracts new cards that
- will be delayed due to sibling spacing.
+ will be delayed due to sibling spacing (cards which haven't
+ been spaced.)
Works properly with upstream cards.
'''
@@ -399,7 +434,7 @@ def unspaced_new_count(self, user):
upstream = self.of_upstream(user).values('fact_id').distinct().count()
return local + upstream
-
+
def young(self, user):
return self.filter(
last_reviewed_at__isnull=False,
@@ -449,7 +484,7 @@ def count_of_cards_due_tomorrow(self, user):
# facts = usertagging.models.UserTaggedItem.objects.get_by_model(
# Fact, tags)
# cards = cards.filter(fact__in=facts)
-
+
this_time_tomorrow = (datetime.datetime.utcnow()
+ datetime.timedelta(days=1))
cards = self.filter(
View
49 apps/flashcards/models/redis_models.py
@@ -0,0 +1,49 @@
+from apps.utils.utils import unix_time
+from apps.manabi_redis.models import redis
+
+
+class RedisCard(object):
+ def __init__(self, card):
+ self.card = card
+
+ #def update_due_at(self):
+ # ''' Records the earliest due card per fact. '''
+ # key = 'next_due_at:fact:{0}'.format(self.card.fact_id)
+ # score = unix_time(self.card.due_at)
+ # existing = redis.get(key)
+ # if existing is None or int(existing) > score:
+ # redis.zadd(key, score, self.card.id)
+
+ #def _update_review_date(self):
+ # ''' Records the most recently reviewed card per card. '''
+ # key = 'last_reviewed_at:fact:{0}'.format(self.card.fact_id)
+ # score = unix_time(card.last_reviewed_at)
+ # existing = redis.get(key)
+ # if existing is None or int(existing) < score:
+ # redis.zadd(key, score, card.id)
+
+ #def _add_failed_review(self):
+ # ''' Which cards were failed in their last review, per user. '''
+ # if self.card.last_review_grade != GRADE_NONE:
+ # return
+ # key = 'failed_cards:user:{0}'.format(self.card.owner)
+ # redis.sadd(key, self.card.id)
+
+ def update_ease_factor(self):
+ key = 'ease_factor:deck:{0}'.format(self.card.fact.deck_id)
+ if self.card.active and self.card.ease_factor:
+ score = self.card.ease_factor
+ redis.zadd(key, score, self.card.id)
+ else:
+ redis.zrem(key, self.card.id)
+
+ def after_review(self):
+ ''' Call after a card is reviewed. '''
+ #self._update_review_date()
+ #self._add_failed_review()
+ #self.update_due_at()
+ self.update_ease_factor()
+
+ def update_all(self):
+ self.update_ease_factor()
+
View
21 apps/flashcards/models/repetitionscheduler.py
@@ -1,11 +1,13 @@
-import random
-from constants import (GRADE_NONE, GRADE_HARD, GRADE_GOOD, GRADE_EASY,
- MATURE_INTERVAL_MIN)
from datetime import timedelta, datetime
-from utils import timedelta_to_float
from math import cos, pi
+import random
+
from cachecow.cache import cached_function
+
+from constants import (GRADE_NONE, GRADE_HARD, GRADE_GOOD, GRADE_EASY, MATURE_INTERVAL_MIN)
from flashcards.cachenamespaces import deck_review_stats_namespace
+from flashcards.models.intervals import initial_interval
+from utils import timedelta_to_float
def repetition_algo_dispatcher(card, *args, **kwargs):
@@ -331,8 +333,7 @@ def _is_being_learned(self):
interval for "Easy" grades.
'''
return (self.card.interval
- < self.card.fact.deck.schedulingoptions.initial_interval(
- GRADE_EASY))
+ < initial_interval(self.card.fact.deck, GRADE_EASY))
def _next_interval(self, failure_interval=0):
return super(YoungCardAlgo, self)._next_interval(
@@ -359,8 +360,8 @@ def _next_interval(self, failure_interval=0):
class NewCardAlgo(RepetitionAlgo):
def _next_interval(self, failure_interval=0):
- interval = self.card.fact.deck.schedulingoptions.initial_interval(
- self.grade)#FIXME, do_fuzz=do_fuzz)
+ #FIXME, do_fuzz=do_fuzz)
+ interval = initial_interval(self.card.fact.deck, self.grade)
#TODO Lessen interval if reviewed too soon after a sibling card.
return interval
@@ -397,8 +398,8 @@ class FailedCardAlgo(RepetitionAlgo):
# This is just the deck's unknown interval
def _next_interval(self, failure_interval=0):
- interval = self.card.fact.deck.schedulingoptions.initial_interval(
- self.grade)#FIXME, do_fuzz=do_fuzz)
+ #FIXME, do_fuzz=do_fuzz)
+ interval = initial_interval(self.card.fact.deck, self.grade)
#TODO lessen effect if reviewed successfully very soon after a
# failed review.
View
3  apps/flashcards/models/undo.py
@@ -65,12 +65,13 @@ def undo(self, user):
card = last_undo.card
card_history = last_undo.card_history
undone_card = last_undo.pickled_card
-
+
# Overwrite the card model with its pickled counterpart
for from_model, to_model in [(undone_card, card,)]:
for field_name in _get_model_fields(from_model):
setattr(to_model, field_name, getattr(from_model, field_name))
to_model.save()
+ to_model.redis.update_all()
# Delete the card history item
card_history.delete()
View
2  apps/flashcards/restresources.py
@@ -22,7 +22,7 @@ def get_data(self):
data = super(DeckResource, self).get_data()
data.update({
'owner': UserResource(self.obj.owner).get_data(),
- 'card_count': self.obj.card_count,
+ 'card_count': self.obj.card_count(),
'status_url': reverse('rest-deck_status', args=[self.obj.id]),
})
return data
View
1  apps/flashcards/views/api/__init__.py
@@ -321,6 +321,7 @@ def rest_facts(request, deck=None, tags=None):
priority = 0)
new_card.randomize_new_order()
new_card.save()
+ new_card.redis.update_all()
else:
raise ApiException({
#'card': card_formset.errors,
View
5 apps/flashcards/views/crud.py
@@ -22,7 +22,6 @@
from flashcards.forms import DeckForm, FactForm, FieldContentForm
from flashcards.models import FactType, Fact, Deck, CardTemplate, FieldType
from flashcards.models import FieldContent, Card
-from flashcards.models import SchedulingOptions
from flashcards.forms import TextbookSourceForm
from flashcards.views.shortcuts import get_deck_or_404
from books.forms import TextbookForm
@@ -190,10 +189,6 @@ def deck_create(request,
if 'tags' in deck_form.cleaned_data:
new_deck.tags = deck_form.cleaned_data['tags']
- #TODO still necessary?
- scheduling_options = SchedulingOptions(deck=new_deck)
- scheduling_options.save()
-
if post_save_redirect is None:
post_save_redirect = new_deck.get_absolute_url()
else:
View
3  apps/flashcards/views/rest/__init__.py
@@ -65,7 +65,6 @@ def get_tags(self):
-
# Resource views
class EntryPoint(ManabiRestView):
@@ -155,7 +154,6 @@ def get_context_data(self, *args, **kwargs):
'rest-deck_subscription', args=[deck.id])
return context
-
def allowed_methods(self, request, *args, **kwargs):
'''Don't return "DELETE" if the user has no permission.'''
@@ -185,7 +183,6 @@ def post(self, request, **kwargs):
return self.render_to_response({'suspended': deck.suspended})
-
class SharedDeckList(ListView, ManabiRestView):
'''
List of shared decks from any user.
View
9 apps/flashcards/views/shortcuts.py
@@ -1,10 +1,11 @@
+from catnap.exceptions import HttpForbiddenException
+from django.forms import forms
+from django.shortcuts import get_object_or_404
+
from flashcards.forms import DeckForm, FactForm, FieldContentForm, CardForm
from flashcards.models import FactType, Fact, Deck, CardTemplate, FieldType
from flashcards.models import FieldContent, Card
from flashcards.models.constants import MAX_NEW_CARD_ORDINAL
-from django.forms import forms
-
-from django.shortcuts import get_object_or_404
def get_deck_or_404(user, pk, must_own=False):
@@ -23,5 +24,3 @@ def get_deck_or_404(user, pk, must_own=False):
return deck
-
-
View
2  apps/importer/views.py
@@ -76,7 +76,7 @@ def importer(request):
card.randomize_new_order()
context.update({
- 'cards_created': deck.card_count,
+ 'cards_created': deck.card_count(),
'deck': deck,
})
return render_to_response('importer/importer.html', context,
View
0  apps/manabi_redis/__init__.py
No changes.
View
9 apps/manabi_redis/models.py
@@ -0,0 +1,9 @@
+from redis import StrictRedis
+
+from settings import REDIS
+
+class ManabiRedis(StrictRedis):
+ pass
+
+redis = ManabiRedis(host=REDIS['host'], port=REDIS['port'], db=REDIS['db'])
+
View
0  apps/manabi_redis/tests.py
No changes.
View
0  apps/manabi_redis/views.py
No changes.
View
7 apps/popups/views.py
@@ -6,10 +6,9 @@
from django.shortcuts import render_to_response, redirect
from django.template import RequestContext
-from flashcards.models import FactType, Fact, Deck, CardTemplate, \
- FieldType, FieldContent, Card, \
- GRADE_NONE, GRADE_HARD, GRADE_GOOD, GRADE_EASY, \
- SchedulingOptions
+from flashcards.models import (FactType, Fact, Deck, CardTemplate,
+ FieldType, FieldContent, Card,
+ GRADE_NONE, GRADE_HARD, GRADE_GOOD, GRADE_EASY)
from flashcards.contextprocessors import subfact_form_context
from flashcards.contextprocessors import (
deck_count_context, card_existence_context, fact_add_form_context)
View
13 apps/utils/middleware.py
@@ -0,0 +1,13 @@
+from django.template import RequestContext
+from django.shortcuts import render_to_response
+
+class JsonDebugMiddleware(object):
+ def process_response(self, request, response):
+ if 'DEBUG' in request.GET and 'json' in response['Content-Type']:
+ content = response.content
+ #response['Content-Type'] = 'text/html'
+ return render_to_response('json_debug.html', {
+ 'json': content
+ }, context_instance=RequestContext(request))
+ return response
+
View
5 apps/utils/utils.py
@@ -0,0 +1,5 @@
+from time import mktime
+
+def unix_time(dt):
+ return int(mktime(dt.timetuple()))
+
View
3  context_processors.py
@@ -23,6 +23,9 @@ def site_base_extender(request):
if (request.META.get('HTTP_X_ESCAPED_FRAGMENT', 'false').lower()
== 'true'):
fragment_name = 'site_base.html'
+ elif 'DEBUG' in request.GET:
+ # Used for debug_toolbar.
+ fragment_name = 'site_base.html'
else:
fragment_name = 'body_pane_base.html'
ctx['fragment_base_template_name'] = fragment_name
View
41 graveyard.py
@@ -0,0 +1,41 @@
+# Contains some commented-out code I don't feel like deleting.
+# Yea I have git... but I don't want to forget about this stuff.
+# Consider them TODOs.
+
+#TODO implement (remember to update UndoReview too)
+# This can probably just be a proxy model for CardHistory or something.
+#class CardStatistics(models.Model):
+# card = models.ForeignKey(Card)
+
+# failure_count = models.PositiveIntegerField(default=0, editable=False)
+# #TODO review stats depending on how card was rated, and how mature it is
+
+# #apparently needed for synchronization/import purposes
+# yes_count = models.PositiveIntegerField(default=0, editable=False)
+# no_count = models.PositiveIntegerField(default=0, editable=False)
+
+# average_thinking_time = models.PositiveIntegerField(null=True, editable=False)
+
+# #initial_ease
+
+# successive_count = models.PositiveIntegerField(default=0, editable=False) #incremented at each success, zeroed at failure
+# successive_streak_count = models.PositiveIntegerField(default=0, editable=False) #incremented at each failure after a success
+# average_successive_count = models.PositiveIntegerField(default=0, editable=False) #
+
+# skip_count = models.PositiveIntegerField(default=0, editable=False)
+# total_review_time = models.FloatField(default=0) #s
+# first_reviewed_at = models.DateTimeField()
+# first_success_at = models.DateTimeField()
+
+
+# #these take into account short-term memory effects
+# #they ignore any more than a single review per day (or 8 hours - TBD)
+# #adjusted_review_count = models.PositiveIntegerField(default=0, editable=False)
+# #adjusted_success_count = models.PositiveIntegerField(default=0, editable=False)
+# #first_adjusted_success_at = models.DateTimeField()
+# #failures_in_a_row = models.PositiveIntegerField(default=0, editable=False)
+# #adjusted_failures_in_a_row = models.PositiveIntegerField(default=0, editable=False)
+
+# class Meta:
+# app_label = 'flashcards'
+
View
6 templates/json_debug.html
@@ -0,0 +1,6 @@
+{% extends "site_base.html" %}
+
+{% block content %}
+{{ json }}
+{% endblock %}
+
View
3  test_helpers.py
@@ -12,7 +12,7 @@
GRADE_NONE, GRADE_HARD, GRADE_GOOD, GRADE_EASY)
from apps.flashcards.management.commands.flashcards_init import create_initial_data
from apps.flashcards.models import (Deck, Card, Fact, FactType, FieldType,
- FieldContent, SchedulingOptions)
+ FieldContent)
PASSWORD = 'whatever'
@@ -120,7 +120,6 @@ def create_deck(user=None):
description='Example description',
owner=owner,
)
- SchedulingOptions.objects.create(deck=deck)
return deck
def create_fact(user=None, deck=None):
View
7 views.py
@@ -1,7 +1,6 @@
-from flashcards.models import FactType, Fact, Deck, CardTemplate, \
- FieldType, FieldContent, Card, \
- GRADE_NONE, GRADE_HARD, GRADE_GOOD, GRADE_EASY, \
- SchedulingOptions
+from flashcards.models import (FactType, Fact, Deck, CardTemplate,
+ FieldType, FieldContent, Card,
+ GRADE_NONE, GRADE_HARD, GRADE_GOOD, GRADE_EASY)
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import resolve
from django.db.models import F
Please sign in to comment.
Something went wrong with that request. Please try again.