Commit abb2506d352be7d86dab46b3f53a54feb24e39c3

Authored by Andrew Buss
1 parent 18095ed465
Exists in master

Wrong SES region

Showing 4 changed files with 13 additions and 4 deletions Inline Diff

flashcards/api.py View file @ abb2506
from django.core.mail import send_mail 1 1 from django.core.mail import send_mail
from rest_framework.views import APIView 2 2 from rest_framework.views import APIView
from rest_framework.response import Response 3 3 from rest_framework.response import Response
from rest_framework import status 4 4 from rest_framework import status
from rest_framework.exceptions import ValidationError 5 5 from rest_framework.exceptions import ValidationError
from flashcards.serializers import * 6 6 from flashcards.serializers import *
7 7
8 8
class UserDetail(APIView): 9 9 class UserDetail(APIView):
def patch(self, request, format=None): 10 10 def patch(self, request, format=None):
""" 11 11 """
Updates a user's password after they enter a valid old password. 12 12 Updates a user's password after they enter a valid old password.
TODO: email verification 13 13 TODO: email verification
""" 14 14 """
15 15
if 'old_password' not in request.data: 16 16 if 'old_password' not in request.data:
raise ValidationError('Old password is required') 17 17 raise ValidationError('Old password is required')
if 'new_password' not in request.data: 18 18 if 'new_password' not in request.data:
raise ValidationError('New password is required') 19 19 raise ValidationError('New password is required')
if not request.data['new_password']: 20 20 if not request.data['new_password']:
raise ValidationError('Password cannot be blank') 21 21 raise ValidationError('Password cannot be blank')
22 22
currentuser = request.user 23 23 currentuser = request.user
24 24
if not currentuser.check_password(request.data['old_password']): 25 25 if not currentuser.check_password(request.data['old_password']):
raise ValidationError('Invalid old password') 26 26 raise ValidationError('Invalid old password')
27 27
currentuser.set_password(request.data['new_password']) 28 28 currentuser.set_password(request.data['new_password'])
currentuser.save() 29 29 currentuser.save()
30 30
return Response(status=status.HTTP_204_NO_CONTENT) 31 31 return Response(status=status.HTTP_204_NO_CONTENT)
32 32
def get(self, request, format=None): 33 33 def get(self, request, format=None):
serializer = UserSerializer(request.user) 34 34 serializer = UserSerializer(request.user)
return Response(serializer.data) 35 35 return Response(serializer.data)
36 36
def post(self, request, format=None): 37 37 def post(self, request, format=None):
if 'email' not in request.data: 38 38 if 'email' not in request.data:
raise ValidationError('Email is required') 39 39 raise ValidationError('Email is required')
if 'password' not in request.data: 40 40 if 'password' not in request.data:
raise ValidationError('Password is required') 41 41 raise ValidationError('Password is required')
42 42
email = request.data['email'] 43 43 email = request.data['email']
user = User.objects.create_user(email) 44 44 user = User.objects.create_user(email)
45 45
flashcards/models.py View file @ abb2506
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager 1 1 from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.contrib.auth.tests.custom_user import CustomUser 2 2 from django.contrib.auth.tests.custom_user import CustomUser
from django.db.models import * 3 3 from django.db.models import *
from simple_email_confirmation import SimpleEmailConfirmationUserMixin 4 4 from simple_email_confirmation import SimpleEmailConfirmationUserMixin
5 5
6 6
class UserManager(BaseUserManager): 7 7 class UserManager(BaseUserManager):
def create_user(self, email, password=None): 8 8 def create_user(self, email, password=None):
""" 9 9 """
Creates and saves a User with the given email, date of 10 10 Creates and saves a User with the given email, date of
birth and password. 11 11 birth and password.
""" 12 12 """
if not email: 13 13 if not email:
raise ValueError('Users must have an email address') 14 14 raise ValueError('Users must have an email address')
15 15
user = self.model(email=self.normalize_email(email)) 16 16 user = self.model(email=self.normalize_email(email))
17 17
user.set_password(password) 18 18 user.set_password(password)
user.save(using=self._db) 19 19 user.save(using=self._db)
return user 20 20 return user
21 21
def create_superuser(self, email, password): 22 22 def create_superuser(self, email, password):
""" 23 23 """
Creates and saves a superuser with the given email and password. 24 24 Creates and saves a superuser with the given email and password.
""" 25 25 """
user = self.create_user(email, password=password) 26 26 user = self.create_user(email, password=password)
user.is_admin = True 27 27 user.is_staff = True
user.save(using=self._db) 28 28 user.save(using=self._db)
return user 29 29 return user
30 30
31 31
class User(AbstractBaseUser, SimpleEmailConfirmationUserMixin): 32 32 class User(AbstractBaseUser, SimpleEmailConfirmationUserMixin, ):
USERNAME_FIELD = 'email' 33 33 USERNAME_FIELD = 'email'
REQUIRED_FIELDS = [] 34 34 REQUIRED_FIELDS = []
35 35
objects = UserManager() 36 36 objects = UserManager()
37 is_staff = BooleanField(default=False)
37 38
email = EmailField( 38 39 email = EmailField(
verbose_name='email address', 39 40 verbose_name='email address',
max_length=255, 40 41 max_length=255,
unique=True, 41 42 unique=True,
) 42 43 )
date_joined = DateTimeField(auto_now_add=True) 43 44 date_joined = DateTimeField(auto_now_add=True)
sections = ManyToManyField('Section') 44 45 sections = ManyToManyField('Section')
45 46
46 47
class UserFlashcard(Model): 47 48 class UserFlashcard(Model):
""" 48 49 """
Represents the relationship between a user and a flashcard by: 49 50 Represents the relationship between a user and a flashcard by:
1. A user has a flashcard in their deck 50 51 1. A user has a flashcard in their deck
2. A user used to have a flashcard in their deck 51 52 2. A user used to have a flashcard in their deck
3. A user has a flashcard hidden from them 52 53 3. A user has a flashcard hidden from them
""" 53 54 """
user = ForeignKey(User) 54 55 user = ForeignKey(User)
mask = ForeignKey('FlashcardMask', help_text="A mask which overrides the card's mask") 55 56 mask = ForeignKey('FlashcardMask', help_text="A mask which overrides the card's mask")
pulled = DateTimeField(blank=True, null=True, help_text="When the user pulled the card") 56 57 pulled = DateTimeField(blank=True, null=True, help_text="When the user pulled the card")
flashcard = ForeignKey('Flashcard') 57 58 flashcard = ForeignKey('Flashcard')
unpulled = DateTimeField(blank=True, null=True, help_text="When the user unpulled this card") 58 59 unpulled = DateTimeField(blank=True, null=True, help_text="When the user unpulled this card")
59 60
class Meta: 60 61 class Meta:
# There can be at most one UserFlashcard for each User and Flashcard 61 62 # There can be at most one UserFlashcard for each User and Flashcard
unique_together = (('user', 'flashcard'),) 62 63 unique_together = (('user', 'flashcard'),)
index_together = ["user", "flashcard"] 63 64 index_together = ["user", "flashcard"]
# By default, order by most recently pulled 64 65 # By default, order by most recently pulled
ordering = ['-pulled'] 65 66 ordering = ['-pulled']
66 67
def is_hidden(self): 67 68 def is_hidden(self):
""" 68 69 """
A card is hidden only if a user has not ever added it to their deck. 69 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 70 71 :return: Whether the flashcard is hidden from the user
""" 71 72 """
return not self.pulled 72 73 return not self.pulled
73 74
def is_in_deck(self): 74 75 def is_in_deck(self):
""" 75 76 """
:return:Whether the flashcard is in the user's deck 76 77 :return:Whether the flashcard is in the user's deck
""" 77 78 """
return self.pulled and not self.unpulled 78 79 return self.pulled and not self.unpulled
79 80
80 81
class FlashcardMask(Model): 81 82 class FlashcardMask(Model):
""" 82 83 """
A serialized list of character ranges that can be blanked out during review. 83 84 A serialized list of character ranges that can be blanked out during review.
This is encoded as '13-145,150-195' 84 85 This is encoded as '13-145,150-195'
""" 85 86 """
ranges = CharField(max_length=255) 86 87 ranges = CharField(max_length=255)
87 88
88 89
class Flashcard(Model): 89 90 class Flashcard(Model):
text = CharField(max_length=255, help_text='The text on the card') 90 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') 91 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") 92 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") 93 94 material_date = DateTimeField(help_text="The date with which the card is associated")
previous = ForeignKey('Flashcard', null=True, blank=True, 94 95 previous = ForeignKey('Flashcard', null=True, blank=True,
help_text="The previous version of this card, if one exists") 95 96 help_text="The previous version of this card, if one exists")
author = ForeignKey(User) 96 97 author = ForeignKey(User)
is_hidden = BooleanField(default=False) 97 98 is_hidden = BooleanField(default=False)
hide_reason = CharField(blank=True, max_length=255, help_text="Reason for hiding this card") 98 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") 99 100 mask = ForeignKey(FlashcardMask, blank=True, null=True, help_text="The default mask for this card")
100 101
class Meta: 101 102 class Meta:
# By default, order by most recently pushed 102 103 # By default, order by most recently pushed
ordering = ['-pushed'] 103 104 ordering = ['-pushed']
104 105
def is_hidden_from(self, user): 105 106 def is_hidden_from(self, user):
""" 106 107 """
A card can be hidden globally, but if a user has the card in their deck, 107 108 A card can be hidden globally, but if a user has the card in their deck,
this visibility overrides a global hide. 108 109 this visibility overrides a global hide.
:param user: 109 110 :param user:
:return: Whether the card is hidden from the user. 110 111 :return: Whether the card is hidden from the user.
""" 111 112 """
result = user.userflashcard_set.filter(flashcard=self) 112 113 result = user.userflashcard_set.filter(flashcard=self)
if not result.exists(): return self.is_hidden 113 114 if not result.exists(): return self.is_hidden
return result[0].is_hidden() 114 115 return result[0].is_hidden()
115 116
116 117
@classmethod 117 118 @classmethod
def cards_visible_to(cls, user): 118 119 def cards_visible_to(cls, user):
""" 119 120 """
:param user: 120 121 :param user:
:return: A queryset with all cards that should be visible to a user. 121 122 :return: A queryset with all cards that should be visible to a user.
""" 122 123 """
return cls.objects.filter(hidden=False).exclude(userflashcard=user, userflashcard__pulled=None) 123 124 return cls.objects.filter(hidden=False).exclude(userflashcard=user, userflashcard__pulled=None)
124 125
125 126
class UserFlashcardReview(Model): 126 127 class UserFlashcardReview(Model):
""" 127 128 """
An event of a user reviewing a flashcard. 128 129 An event of a user reviewing a flashcard.
""" 129 130 """
user_flashcard = ForeignKey(UserFlashcard) 130 131 user_flashcard = ForeignKey(UserFlashcard)
when = DateTimeField() 131 132 when = DateTimeField()
blanked_word = CharField(max_length=8, blank=True, help_text="The character range which was blanked") 132 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") 133 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") 134 135 correct = NullBooleanField(help_text="The user's self-evaluation of their response")
135 136
def status(self): 136 137 def status(self):
""" 137 138 """
There are three stages of a review object: 138 139 There are three stages of a review object:
1. the user has been shown the card 139 140 1. the user has been shown the card
2. the user has answered the card 140 141 2. the user has answered the card
3. the user has self-evaluated their response's correctness 141 142 3. the user has self-evaluated their response's correctness
142 143
:return: string (evaluated, answered, viewed) 143 144 :return: string (evaluated, answered, viewed)
""" 144 145 """
if self.correct is not None: return "evaluated" 145 146 if self.correct is not None: return "evaluated"
if self.response: return "answered" 146 147 if self.response: return "answered"
return "viewed" 147 148 return "viewed"
flashy/settings.py View file @ abb2506
""" 1 1 """
Django settings for flashy project. 2 2 Django settings for flashy project.
3 3
Generated by 'django-admin startproject' using Django 1.8. 4 4 Generated by 'django-admin startproject' using Django 1.8.
5 5
For more information on this file, see 6 6 For more information on this file, see
https://docs.djangoproject.com/en/1.8/topics/settings/ 7 7 https://docs.djangoproject.com/en/1.8/topics/settings/
8 8
For the full list of settings and their values, see 9 9 For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.8/ref/settings/ 10 10 https://docs.djangoproject.com/en/1.8/ref/settings/
""" 11 11 """
12 12
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) 13 13 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os 14 14 import os
15 15
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 16 16 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17 17
# SECURITY WARNING: don't run with debug turned on in production! 18 18 # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True 19 19 DEBUG = True
20 20
ALLOWED_HOSTS = [] 21 21 ALLOWED_HOSTS = []
22 22
AUTH_USER_MODEL = 'flashcards.User' 23 23 AUTH_USER_MODEL = 'flashcards.User'
# Application definition 24 24 # Application definition
25 25
INSTALLED_APPS = ( 26 26 INSTALLED_APPS = (
'simple_email_confirmation', 27 27 'simple_email_confirmation',
'flashcards', 28 28 'flashcards',
'django.contrib.admin', 29 29 'django.contrib.admin',
'django.contrib.admindocs', 30 30 'django.contrib.admindocs',
'django.contrib.auth', 31 31 'django.contrib.auth',
'django.contrib.contenttypes', 32 32 'django.contrib.contenttypes',
'django.contrib.sessions', 33 33 'django.contrib.sessions',
'django.contrib.messages', 34 34 'django.contrib.messages',
'django.contrib.staticfiles', 35 35 'django.contrib.staticfiles',
'django_ses', 36 36 'django_ses',
'rest_framework', 37 37 'rest_framework',
38 38
39 39
) 40 40 )
41 41
REST_FRAMEWORK = { 42 42 REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination', 43 43 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination',
'PAGE_SIZE': 20 44 44 'PAGE_SIZE': 20
} 45 45 }
46 46
MIDDLEWARE_CLASSES = ( 47 47 MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware', 48 48 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 49 49 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 50 50 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 51 51 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 52 52 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 53 53 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 54 54 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware', 55 55 'django.middleware.security.SecurityMiddleware',
) 56 56 )
57 57
ROOT_URLCONF = 'flashy.urls' 58 58 ROOT_URLCONF = 'flashy.urls'
59 59
TEMPLATES = [ 60 60 TEMPLATES = [
{ 61 61 {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 62 62 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['templates/'], 63 63 'DIRS': ['templates/'],
'APP_DIRS': True, 64 64 'APP_DIRS': True,
'OPTIONS': { 65 65 'OPTIONS': {
'context_processors': [ 66 66 'context_processors': [
'django.template.context_processors.debug', 67 67 'django.template.context_processors.debug',
'django.template.context_processors.request', 68 68 'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth', 69 69 'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages', 70 70 'django.contrib.messages.context_processors.messages',
], 71 71 ],
}, 72 72 },
}, 73 73 },
] 74 74 ]
75 75
WSGI_APPLICATION = 'flashy.wsgi.application' 76 76 WSGI_APPLICATION = 'flashy.wsgi.application'
77 77
78 78
# Database 79 79 # Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases 80 80 # https://docs.djangoproject.com/en/1.8/ref/settings/#databases
81 81
DATABASES = { 82 82 DATABASES = {
'default': { 83 83 'default': {
'ENGINE': 'django.db.backends.sqlite3', 84 84 'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 85 85 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
} 86 86 }
} 87 87 }
flashy/urls.py View file @ abb2506
from django.conf.urls import include, url 1 1 from django.conf.urls import include, url
from django.contrib import admin 2 2 from django.contrib import admin
from flashcards.views import SectionViewSet, LecturePeriodViewSet 3 3 from flashcards.views import SectionViewSet, LecturePeriodViewSet
from rest_framework.routers import DefaultRouter 4 4 from rest_framework.routers import DefaultRouter
from flashcards.api import * 5 5 from flashcards.api import *
6 6
router = DefaultRouter() 7 7 router = DefaultRouter()
router.register(r'sections', SectionViewSet) 8 8 router.register(r'sections', SectionViewSet)
router.register(r'lectureperiods', LecturePeriodViewSet) 9 9 router.register(r'lectureperiods', LecturePeriodViewSet)
10 10
urlpatterns = [ 11 11 urlpatterns = [
url(r'^api/user/me$', UserDetail.as_view()), 12 12 url(r'^api/user/me$', UserDetail.as_view()),
url(r'^api/', include(router.urls)), 13 13 url(r'^api/', include(router.urls)),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')), 14 14 url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/', include(admin.site.urls)), 15 15 url(r'^admin/', include(admin.site.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) 16 16 url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
] 17 17 ]