Commit 9276b7b89a8e71ce14085f2108fb404ac8a9149b

Authored by Andrew Buss
1 parent 5354852580
Exists in master

Nitpicky cleanup. Also to make jenkins rebuild

Showing 3 changed files with 36 additions and 8 deletions Inline Diff

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