Commit f73d4c329f136470ab32ea92027b3db257dc7dcd

Authored by Andrew Buss
1 parent 8f54c7956d
Exists in master

Allow null mask on flashcard

Showing 2 changed files with 20 additions and 1 deletions Inline Diff

flashcards/migrations/0004_auto_20150506_1443.py View file @ f73d4c3
File was created 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', '0003_auto_20150504_1600'),
11 ]
12
13 operations = [
14 migrations.AlterField(
flashcards/models.py View file @ f73d4c3
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, AbstractUser, UserManager 1 1 from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, AbstractUser, UserManager
from django.db.models import * 2 2 from django.db.models import *
from django.utils.timezone import now 3 3 from django.utils.timezone import now
from simple_email_confirmation import SimpleEmailConfirmationUserMixin 4 4 from simple_email_confirmation import SimpleEmailConfirmationUserMixin
5 5
# Hack to fix AbstractUser before subclassing it 6 6 # Hack to fix AbstractUser before subclassing it
AbstractUser._meta.get_field('email')._unique = True 7 7 AbstractUser._meta.get_field('email')._unique = True
AbstractUser._meta.get_field('username')._unique = False 8 8 AbstractUser._meta.get_field('username')._unique = False
9 9
10 10
class EmailOnlyUserManager(UserManager): 11 11 class EmailOnlyUserManager(UserManager):
""" 12 12 """
A tiny extension of Django's UserManager which correctly creates users 13 13 A tiny extension of Django's UserManager which correctly creates users
without usernames (using emails instead). 14 14 without usernames (using emails instead).
""" 15 15 """
16 16
def _create_user(self, email, password, is_staff, is_superuser, **extra_fields): 17 17 def _create_user(self, email, password, is_staff, is_superuser, **extra_fields):
""" 18 18 """
Creates and saves a User with the given email and password. 19 19 Creates and saves a User with the given email and password.
""" 20 20 """
email = self.normalize_email(email) 21 21 email = self.normalize_email(email)
user = self.model(email=email, 22 22 user = self.model(email=email,
is_staff=is_staff, is_active=True, 23 23 is_staff=is_staff, is_active=True,
is_superuser=is_superuser, 24 24 is_superuser=is_superuser,
date_joined=now(), **extra_fields) 25 25 date_joined=now(), **extra_fields)
user.set_password(password) 26 26 user.set_password(password)
user.save(using=self._db) 27 27 user.save(using=self._db)
return user 28 28 return user
29 29
def create_user(self, email, password=None, **extra_fields): 30 30 def create_user(self, email, password=None, **extra_fields):
return self._create_user(email, password, False, False, **extra_fields) 31 31 return self._create_user(email, password, False, False, **extra_fields)
32 32
def create_superuser(self, email, password, **extra_fields): 33 33 def create_superuser(self, email, password, **extra_fields):
return self._create_user(email, password, True, True, **extra_fields) 34 34 return self._create_user(email, password, True, True, **extra_fields)
35 35
36 36
class User(AbstractUser, SimpleEmailConfirmationUserMixin): 37 37 class User(AbstractUser, SimpleEmailConfirmationUserMixin):
""" 38 38 """
An extension of Django's default user model. 39 39 An extension of Django's default user model.
We use email as the username field, and include enrolled sections here 40 40 We use email as the username field, and include enrolled sections here
""" 41 41 """
objects = EmailOnlyUserManager() 42 42 objects = EmailOnlyUserManager()
USERNAME_FIELD = 'email' 43 43 USERNAME_FIELD = 'email'
REQUIRED_FIELDS = [] 44 44 REQUIRED_FIELDS = []
sections = ManyToManyField('Section', help_text="The sections which the user is enrolled in") 45 45 sections = ManyToManyField('Section', help_text="The sections which the user is enrolled in")
46 46
47 47
class UserFlashcard(Model): 48 48 class UserFlashcard(Model):
""" 49 49 """
Represents the relationship between a user and a flashcard by: 50 50 Represents the relationship between a user and a flashcard by:
1. A user has a flashcard in their deck 51 51 1. A user has a flashcard in their deck
2. A user used to have a flashcard in their deck 52 52 2. A user used to have a flashcard in their deck
3. A user has a flashcard hidden from them 53 53 3. A user has a flashcard hidden from them
""" 54 54 """
user = ForeignKey('User') 55 55 user = ForeignKey('User')
mask = ForeignKey('FlashcardMask', help_text="A mask which overrides the card's mask") 56 56 mask = ForeignKey('FlashcardMask', blank=True, null=True, help_text="A mask which overrides the card's mask")
pulled = DateTimeField(blank=True, null=True, help_text="When the user pulled the card") 57 57 pulled = DateTimeField(blank=True, null=True, help_text="When the user pulled the card")
flashcard = ForeignKey('Flashcard') 58 58 flashcard = ForeignKey('Flashcard')
unpulled = DateTimeField(blank=True, null=True, help_text="When the user unpulled this card") 59 59 unpulled = DateTimeField(blank=True, null=True, help_text="When the user unpulled this card")
60 60
class Meta: 61 61 class Meta:
# There can be at most one UserFlashcard for each User and Flashcard 62 62 # There can be at most one UserFlashcard for each User and Flashcard
unique_together = (('user', 'flashcard'),) 63 63 unique_together = (('user', 'flashcard'),)
index_together = ["user", "flashcard"] 64 64 index_together = ["user", "flashcard"]
# By default, order by most recently pulled 65 65 # By default, order by most recently pulled
ordering = ['-pulled'] 66 66 ordering = ['-pulled']
67 67
def is_hidden(self): 68 68 def is_hidden(self):
""" 69 69 """
A card is hidden only if a user has not ever added it to their deck. 70 70 A card is hidden only if a user has not ever added it to their deck.
:return: Whether the flashcard is hidden from the user 71 71 :return: Whether the flashcard is hidden from the user
""" 72 72 """
return not self.pulled 73 73 return not self.pulled
74 74
def is_in_deck(self): 75 75 def is_in_deck(self):
""" 76 76 """
:return:Whether the flashcard is in the user's deck 77 77 :return:Whether the flashcard is in the user's deck
""" 78 78 """
return self.pulled and not self.unpulled 79 79 return self.pulled and not self.unpulled
80 80
81 81
class FlashcardMask(Model): 82 82 class FlashcardMask(Model):
""" 83 83 """
A serialized list of character ranges that can be blanked out during review. 84 84 A serialized list of character ranges that can be blanked out during review.
This is encoded as '13-145,150-195' 85 85 This is encoded as '13-145,150-195'
""" 86 86 """
ranges = CharField(max_length=255) 87 87 ranges = CharField(max_length=255)
88 88
89 89
class Flashcard(Model): 90 90 class Flashcard(Model):
text = CharField(max_length=255, help_text='The text on the card') 91 91 text = CharField(max_length=255, help_text='The text on the card')
section = ForeignKey('Section', help_text='The section with which the card is associated') 92 92 section = ForeignKey('Section', help_text='The section with which the card is associated')
pushed = DateTimeField(auto_now_add=True, help_text="When the card was first pushed") 93 93 pushed = DateTimeField(auto_now_add=True, help_text="When the card was first pushed")
material_date = DateTimeField(help_text="The date with which the card is associated") 94 94 material_date = DateTimeField(help_text="The date with which the card is associated")
previous = ForeignKey('Flashcard', null=True, blank=True, 95 95 previous = ForeignKey('Flashcard', null=True, blank=True,
help_text="The previous version of this card, if one exists") 96 96 help_text="The previous version of this card, if one exists")
author = ForeignKey(User) 97 97 author = ForeignKey(User)
is_hidden = BooleanField(default=False) 98 98 is_hidden = BooleanField(default=False)
hide_reason = CharField(blank=True, max_length=255, help_text="Reason for hiding this card") 99 99 hide_reason = CharField(blank=True, max_length=255, help_text="Reason for hiding this card")
mask = ForeignKey(FlashcardMask, blank=True, null=True, help_text="The default mask for this card") 100 100 mask = ForeignKey(FlashcardMask, blank=True, null=True, help_text="The default mask for this card")
101 101
class Meta: 102 102 class Meta:
# By default, order by most recently pushed 103 103 # By default, order by most recently pushed
ordering = ['-pushed'] 104 104 ordering = ['-pushed']
105 105
def is_hidden_from(self, user): 106 106 def is_hidden_from(self, user):
""" 107 107 """
A card can be hidden globally, but if a user has the card in their deck, 108 108 A card can be hidden globally, but if a user has the card in their deck,
this visibility overrides a global hide. 109 109 this visibility overrides a global hide.
:param user: 110 110 :param user:
:return: Whether the card is hidden from the user. 111 111 :return: Whether the card is hidden from the user.
""" 112 112 """
result = user.userflashcard_set.filter(flashcard=self) 113 113 result = user.userflashcard_set.filter(flashcard=self)
if not result.exists(): return self.is_hidden 114 114 if not result.exists(): return self.is_hidden
return result[0].is_hidden() 115 115 return result[0].is_hidden()
116 116
117 117
@classmethod 118 118 @classmethod
def cards_visible_to(cls, user): 119 119 def cards_visible_to(cls, user):
""" 120 120 """
:param user: 121 121 :param user:
:return: A queryset with all cards that should be visible to a user. 122 122 :return: A queryset with all cards that should be visible to a user.
""" 123 123 """
return cls.objects.filter(hidden=False).exclude(userflashcard=user, userflashcard__pulled=None) 124 124 return cls.objects.filter(hidden=False).exclude(userflashcard=user, userflashcard__pulled=None)
125 125
126 126
class UserFlashcardReview(Model): 127 127 class UserFlashcardReview(Model):
""" 128 128 """
An event of a user reviewing a flashcard. 129 129 An event of a user reviewing a flashcard.
""" 130 130 """
user_flashcard = ForeignKey(UserFlashcard) 131 131 user_flashcard = ForeignKey(UserFlashcard)
when = DateTimeField() 132 132 when = DateTimeField()
blanked_word = CharField(max_length=8, blank=True, help_text="The character range which was blanked") 133 133 blanked_word = CharField(max_length=8, blank=True, help_text="The character range which was blanked")
response = CharField(max_length=255, blank=True, null=True, help_text="The user's response") 134 134 response = CharField(max_length=255, blank=True, null=True, help_text="The user's response")
correct = NullBooleanField(help_text="The user's self-evaluation of their response") 135 135 correct = NullBooleanField(help_text="The user's self-evaluation of their response")
136 136
def status(self): 137 137 def status(self):
""" 138 138 """
There are three stages of a review object: 139 139 There are three stages of a review object:
1. the user has been shown the card 140 140 1. the user has been shown the card
2. the user has answered the card 141 141 2. the user has answered the card
3. the user has self-evaluated their response's correctness 142 142 3. the user has self-evaluated their response's correctness
143 143
:return: string (evaluated, answered, viewed) 144 144 :return: string (evaluated, answered, viewed)
""" 145 145 """
if self.correct is not None: return "evaluated" 146 146 if self.correct is not None: return "evaluated"
if self.response: return "answered" 147 147 if self.response: return "answered"
return "viewed" 148 148 return "viewed"
149 149
150 150
class Section(Model): 151 151 class Section(Model):
""" 152 152 """
A UCSD course taught by an instructor during a quarter. 153 153 A UCSD course taught by an instructor during a quarter.
Different sections taught by the same instructor in the same quarter are considered identical. 154 154 Different sections taught by the same instructor in the same quarter are considered identical.
We use the term "section" to avoid collision with the builtin keyword "class" 155 155 We use the term "section" to avoid collision with the builtin keyword "class"
""" 156 156 """
department = CharField(max_length=50) 157 157 department = CharField(max_length=50)
course_num = CharField(max_length=6) 158 158 course_num = CharField(max_length=6)
# section_id = CharField(max_length=10) 159 159 # section_id = CharField(max_length=10)
course_title = CharField(max_length=50) 160 160 course_title = CharField(max_length=50)
instructor = CharField(max_length=100) 161 161 instructor = CharField(max_length=100)
quarter = CharField(max_length=4) 162 162 quarter = CharField(max_length=4)
whitelist = ManyToManyField(User, related_name="whitelisted_sections") 163 163 whitelist = ManyToManyField(User, related_name="whitelisted_sections")
164 164