Commit 5064562d793de715e0b73456b36388e382720e49

Authored by Andrew Buss
1 parent 73329c6b03
Exists in master

fix logging?

Showing 3 changed files with 15 additions and 2 deletions Inline Diff

flashcards/middleware.py View file @ 5064562
File was created 1 class SetRemoteAddrFromForwardedFor(object):
2 def process_request(self, request):
3 try:
4 real_ip = request.META['HTTP_X_FORWARDED_FOR']
5 except KeyError:
6 pass
7 else:
8 # HTTP_X_FORWARDED_FOR can be a comma-separated list of IPs.
9 # Take just the first one.
10 real_ip = real_ip.split(",")[0]
11 request.META['REMOTE_ADDR'] = real_ip
flashcards/views.py View file @ 5064562
import django 1 1 import django
from django.contrib import auth 2 2 from django.contrib import auth
from django.db import IntegrityError 3 3 from django.db import IntegrityError
from django.shortcuts import get_object_or_404 4 4 from django.shortcuts import get_object_or_404
from django.utils.log import getLogger 5 5 from django.utils.log import getLogger
from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection, IsFlashcardReviewer, \ 6 6 from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection, IsFlashcardReviewer, \
IsAuthenticatedAndConfirmed 7 7 IsAuthenticatedAndConfirmed
from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard, UserFlashcardQuiz 8 8 from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard, UserFlashcardQuiz
from flashcards.notifications import notify_new_card 9 9 from flashcards.notifications import notify_new_card
from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \ 10 10 from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \
PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \ 11 11 PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \
FlashcardUpdateSerializer, QuizRequestSerializer, QuizResponseSerializer, \ 12 12 FlashcardUpdateSerializer, QuizRequestSerializer, QuizResponseSerializer, \
QuizAnswerRequestSerializer, DeepSectionSerializer 13 13 QuizAnswerRequestSerializer, DeepSectionSerializer
from rest_framework.decorators import detail_route, permission_classes, api_view, list_route 14 14 from rest_framework.decorators import detail_route, permission_classes, api_view, list_route
from rest_framework.generics import ListAPIView, GenericAPIView 15 15 from rest_framework.generics import ListAPIView, GenericAPIView
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin 16 16 from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.permissions import IsAuthenticated 17 17 from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet 18 18 from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet
from django.core.mail import send_mail 19 19 from django.core.mail import send_mail
from django.contrib.auth import authenticate 20 20 from django.contrib.auth import authenticate
from django.contrib.auth.tokens import default_token_generator 21 21 from django.contrib.auth.tokens import default_token_generator
from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK 22 22 from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK
from rest_framework.response import Response 23 23 from rest_framework.response import Response
from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied 24 24 from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied
from simple_email_confirmation import EmailAddress 25 25 from simple_email_confirmation import EmailAddress
26 26
27 27
def log_event(request, event=''): 28 28 def log_event(request, event=''):
getLogger('flashy.events').info( 29 29 getLogger('flashy.events').info(
'%s %s %s %s' % (request.META['REMOTE_ADDR'], str(request.user), request.META.get('PATH', ''), event)) 30 30 '%s %s %s %s' % (request.META['REMOTE_ADDR'], str(request.user), request.path, event))
31 31
32 32
class SectionViewSet(ReadOnlyModelViewSet): 33 33 class SectionViewSet(ReadOnlyModelViewSet):
queryset = Section.objects.all() 34 34 queryset = Section.objects.all()
serializer_class = DeepSectionSerializer 35 35 serializer_class = DeepSectionSerializer
pagination_class = StandardResultsSetPagination 36 36 pagination_class = StandardResultsSetPagination
permission_classes = [IsAuthenticatedAndConfirmed] 37 37 permission_classes = [IsAuthenticatedAndConfirmed]
38 38
@detail_route(methods=['GET']) 39 39 @detail_route(methods=['GET'])
def flashcards(self, request, pk): 40 40 def flashcards(self, request, pk):
""" 41 41 """
Gets flashcards for a section, excluding hidden cards. 42 42 Gets flashcards for a section, excluding hidden cards.
Returned in strictly chronological order (material date). 43 43 Returned in strictly chronological order (material date).
""" 44 44 """
flashcards = Flashcard.cards_visible_to(request.user) 45 45 flashcards = Flashcard.cards_visible_to(request.user)
if 'hidden' in request.GET: 46 46 if 'hidden' in request.GET:
if request.GET['hidden'] == 'only': 47 47 if request.GET['hidden'] == 'only':
flashcards = Flashcard.cards_hidden_by(request.user) 48 48 flashcards = Flashcard.cards_hidden_by(request.user)
else: 49 49 else:
flashcards |= Flashcard.cards_hidden_by(request.user) 50 50 flashcards |= Flashcard.cards_hidden_by(request.user)
flashcards = flashcards.filter(section=self.get_object()).order_by('material_date').all() 51 51 flashcards = flashcards.filter(section=self.get_object()).order_by('material_date').all()
log_event(request, str(self.get_object())) 52 52 log_event(request, str(self.get_object()))
return Response(FlashcardSerializer(flashcards, context={"user": request.user}, many=True).data) 53 53 return Response(FlashcardSerializer(flashcards, context={"user": request.user}, many=True).data)
54 54
@detail_route(methods=['POST']) 55 55 @detail_route(methods=['POST'])
def enroll(self, request, pk): 56 56 def enroll(self, request, pk):
57 57
""" 58 58 """
Add the current user to a specified section 59 59 Add the current user to a specified section
If the class has a whitelist, but the user is not on the whitelist, the request will fail. 60 60 If the class has a whitelist, but the user is not on the whitelist, the request will fail.
--- 61 61 ---
view_mocker: flashcards.api.mock_no_params 62 62 view_mocker: flashcards.api.mock_no_params
""" 63 63 """
try: 64 64 try:
self.get_object().enroll(request.user) 65 65 self.get_object().enroll(request.user)
log_event(request, str(self.get_object())) 66 66 log_event(request, str(self.get_object()))
except django.core.exceptions.PermissionDenied as e: 67 67 except django.core.exceptions.PermissionDenied as e:
raise PermissionDenied(e) 68 68 raise PermissionDenied(e)
except django.core.exceptions.ValidationError as e: 69 69 except django.core.exceptions.ValidationError as e:
raise ValidationError(e) 70 70 raise ValidationError(e)
return Response(status=HTTP_204_NO_CONTENT) 71 71 return Response(status=HTTP_204_NO_CONTENT)
72 72
@detail_route(methods=['POST']) 73 73 @detail_route(methods=['POST'])
def drop(self, request, pk): 74 74 def drop(self, request, pk):
""" 75 75 """
Remove the current user from a specified section 76 76 Remove the current user from a specified section
If the user is not in the class, the request will fail. 77 77 If the user is not in the class, the request will fail.
--- 78 78 ---
view_mocker: flashcards.api.mock_no_params 79 79 view_mocker: flashcards.api.mock_no_params
""" 80 80 """
try: 81 81 try:
self.get_object().drop(request.user) 82 82 self.get_object().drop(request.user)
log_event(request, str(self.get_object())) 83 83 log_event(request, str(self.get_object()))
except django.core.exceptions.PermissionDenied as e: 84 84 except django.core.exceptions.PermissionDenied as e:
raise PermissionDenied(e) 85 85 raise PermissionDenied(e)
except django.core.exceptions.ValidationError as e: 86 86 except django.core.exceptions.ValidationError as e:
raise ValidationError(e) 87 87 raise ValidationError(e)
return Response(status=HTTP_204_NO_CONTENT) 88 88 return Response(status=HTTP_204_NO_CONTENT)
89 89
@list_route(methods=['GET']) 90 90 @list_route(methods=['GET'])
def search(self, request): 91 91 def search(self, request):
""" 92 92 """
Returns a list of sections which match a user's query 93 93 Returns a list of sections which match a user's query
--- 94 94 ---
parameters: 95 95 parameters:
- name: q 96 96 - name: q
description: space-separated list of terms 97 97 description: space-separated list of terms
required: true 98 98 required: true
type: form 99 99 type: form
response_serializer: SectionSerializer 100 100 response_serializer: SectionSerializer
""" 101 101 """
query = request.GET.get('q', None) 102 102 query = request.GET.get('q', None)
if not query: return Response('[]') 103 103 if not query: return Response('[]')
qs = Section.search(query.split(' '))[:20] 104 104 qs = Section.search(query.split(' '))[:20]
data = SectionSerializer(qs, many=True).data 105 105 data = SectionSerializer(qs, many=True).data
log_event(request, query) 106 106 log_event(request, query)
return Response(data) 107 107 return Response(data)
108 108
@detail_route(methods=['GET']) 109 109 @detail_route(methods=['GET'])
def deck(self, request, pk): 110 110 def deck(self, request, pk):
""" 111 111 """
Gets the contents of a user's deck for a given section. 112 112 Gets the contents of a user's deck for a given section.
""" 113 113 """
qs = request.user.get_deck(self.get_object()) 114 114 qs = request.user.get_deck(self.get_object())
serializer = FlashcardSerializer(qs, many=True) 115 115 serializer = FlashcardSerializer(qs, many=True)
log_event(request, str(self.get_object())) 116 116 log_event(request, str(self.get_object()))
return Response(serializer.data) 117 117 return Response(serializer.data)
118 118
@detail_route(methods=['GET']) 119 119 @detail_route(methods=['GET'])
def feed(self, request, pk): 120 120 def feed(self, request, pk):
""" 121 121 """
Gets the contents of a user's feed for a section. 122 122 Gets the contents of a user's feed for a section.
Exclude cards that are already in the user's deck 123 123 Exclude cards that are already in the user's deck
""" 124 124 """
serializer = FlashcardSerializer(self.get_object().get_feed_for_user(request.user), many=True, 125 125 serializer = FlashcardSerializer(self.get_object().get_feed_for_user(request.user), many=True,
context={'user': request.user}) 126 126 context={'user': request.user})
log_event(request, str(self.get_object())) 127 127 log_event(request, str(self.get_object()))
return Response(serializer.data) 128 128 return Response(serializer.data)
129 129
130 130
class UserSectionListView(ListAPIView): 131 131 class UserSectionListView(ListAPIView):
serializer_class = DeepSectionSerializer 132 132 serializer_class = DeepSectionSerializer
permission_classes = [IsAuthenticatedAndConfirmed] 133 133 permission_classes = [IsAuthenticatedAndConfirmed]
134 134
def get_queryset(self): 135 135 def get_queryset(self):
return self.request.user.sections.all() 136 136 return self.request.user.sections.all()
137 137
def paginate_queryset(self, queryset): return None 138 138 def paginate_queryset(self, queryset): return None
139 139
140 140
class UserDetail(GenericAPIView): 141 141 class UserDetail(GenericAPIView):
serializer_class = UserSerializer 142 142 serializer_class = UserSerializer
permission_classes = [IsAuthenticatedAndConfirmed] 143 143 permission_classes = [IsAuthenticatedAndConfirmed]
144 144
def patch(self, request, format=None): 145 145 def patch(self, request, format=None):
""" 146 146 """
Updates the user's password, or verifies their email address 147 147 Updates the user's password, or verifies their email address
--- 148 148 ---
request_serializer: UserUpdateSerializer 149 149 request_serializer: UserUpdateSerializer
response_serializer: UserSerializer 150 150 response_serializer: UserSerializer
""" 151 151 """
data = UserUpdateSerializer(data=request.data, context={'user': request.user}) 152 152 data = UserUpdateSerializer(data=request.data, context={'user': request.user})
data.is_valid(raise_exception=True) 153 153 data.is_valid(raise_exception=True)
data = data.validated_data 154 154 data = data.validated_data
155 155
if 'new_password' in data: 156 156 if 'new_password' in data:
if not request.user.check_password(data['old_password']): 157 157 if not request.user.check_password(data['old_password']):
raise ValidationError('old_password is incorrect') 158 158 raise ValidationError('old_password is incorrect')
request.user.set_password(data['new_password']) 159 159 request.user.set_password(data['new_password'])
request.user.save() 160 160 request.user.save()
log_event(request, 'change password') 161 161 log_event(request, 'change password')
162 162
if 'confirmation_key' in data: 163 163 if 'confirmation_key' in data:
try: 164 164 try:
request.user.confirm_email(data['confirmation_key']) 165 165 request.user.confirm_email(data['confirmation_key'])
log_event(request, 'confirm email') 166 166 log_event(request, 'confirm email')
except EmailAddress.DoesNotExist: 167 167 except EmailAddress.DoesNotExist:
raise ValidationError('confirmation_key is invalid') 168 168 raise ValidationError('confirmation_key is invalid')
169 169
return Response(UserSerializer(request.user).data) 170 170 return Response(UserSerializer(request.user).data)
171 171
def get(self, request, format=None): 172 172 def get(self, request, format=None):
""" 173 173 """
Return data about the user 174 174 Return data about the user
--- 175 175 ---
response_serializer: UserSerializer 176 176 response_serializer: UserSerializer
""" 177 177 """
serializer = UserSerializer(request.user, context={'request': request}) 178 178 serializer = UserSerializer(request.user, context={'request': request})
return Response(serializer.data) 179 179 return Response(serializer.data)
180 180
def delete(self, request): 181 181 def delete(self, request):
""" 182 182 """
Irrevocably delete the user and their data 183 183 Irrevocably delete the user and their data
184 184
Yes, really 185 185 Yes, really
""" 186 186 """
request.user.delete() 187 187 request.user.delete()
log_event(request) 188 188 log_event(request)
return Response(status=HTTP_204_NO_CONTENT) 189 189 return Response(status=HTTP_204_NO_CONTENT)
190 190
191 191
@api_view(['POST']) 192 192 @api_view(['POST'])
def register(request, format=None): 193 193 def register(request, format=None):
""" 194 194 """
Register a new user 195 195 Register a new user
--- 196 196 ---
request_serializer: EmailPasswordSerializer 197 197 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 198 198 response_serializer: UserSerializer
""" 199 199 """
data = RegistrationSerializer(data=request.data) 200 200 data = RegistrationSerializer(data=request.data)
data.is_valid(raise_exception=True) 201 201 data.is_valid(raise_exception=True)
202 202
User.objects.create_user(**data.validated_data) 203 203 User.objects.create_user(**data.validated_data)
user = authenticate(**data.validated_data) 204 204 user = authenticate(**data.validated_data)
auth.login(request, user) 205 205 auth.login(request, user)
log_event(request) 206 206 log_event(request)
return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED) 207 207 return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED)
208 208
209 209
@api_view(['POST']) 210 210 @api_view(['POST'])
def login(request): 211 211 def login(request):
""" 212 212 """
Authenticates user and returns user data if valid. 213 213 Authenticates user and returns user data if valid.
--- 214 214 ---
request_serializer: EmailPasswordSerializer 215 215 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 216 216 response_serializer: UserSerializer
""" 217 217 """
218 218
data = EmailPasswordSerializer(data=request.data) 219 219 data = EmailPasswordSerializer(data=request.data)
data.is_valid(raise_exception=True) 220 220 data.is_valid(raise_exception=True)
user = authenticate(**data.validated_data) 221 221 user = authenticate(**data.validated_data)
222 222
if user is None: 223 223 if user is None:
raise AuthenticationFailed('Invalid email or password') 224 224 raise AuthenticationFailed('Invalid email or password')
if not user.is_active: 225 225 if not user.is_active:
raise NotAuthenticated('Account is disabled') 226 226 raise NotAuthenticated('Account is disabled')
auth.login(request, user) 227 227 auth.login(request, user)
log_event(request) 228 228 log_event(request)
return Response(UserSerializer(request.user).data) 229 229 return Response(UserSerializer(request.user).data)
230 230
231 231
@api_view(['POST']) 232 232 @api_view(['POST'])
@permission_classes((IsAuthenticated,)) 233 233 @permission_classes((IsAuthenticated,))
def logout(request, format=None): 234 234 def logout(request, format=None):
""" 235 235 """
Logs the authenticated user out. 236 236 Logs the authenticated user out.
""" 237 237 """
auth.logout(request) 238 238 auth.logout(request)
log_event(request) 239 239 log_event(request)
return Response(status=HTTP_204_NO_CONTENT) 240 240 return Response(status=HTTP_204_NO_CONTENT)
241 241
242 242
@api_view(['POST']) 243 243 @api_view(['POST'])
def request_password_reset(request, format=None): 244 244 def request_password_reset(request, format=None):
""" 245 245 """
Send a password reset token/link to the provided email. 246 246 Send a password reset token/link to the provided email.
--- 247 247 ---
request_serializer: PasswordResetRequestSerializer 248 248 request_serializer: PasswordResetRequestSerializer
""" 249 249 """
data = PasswordResetRequestSerializer(data=request.data) 250 250 data = PasswordResetRequestSerializer(data=request.data)
data.is_valid(raise_exception=True) 251 251 data.is_valid(raise_exception=True)
log_event(request, 'email: ' + str(data['email'])) 252 252 log_event(request, 'email: ' + str(data['email']))
get_object_or_404(User, email=data['email'].value).request_password_reset() 253 253 get_object_or_404(User, email=data['email'].value).request_password_reset()
return Response(status=HTTP_204_NO_CONTENT) 254 254 return Response(status=HTTP_204_NO_CONTENT)
255 255
256 256
@api_view(['POST']) 257 257 @api_view(['POST'])
def reset_password(request, format=None): 258 258 def reset_password(request, format=None):
""" 259 259 """
Updates user's password to new password if token is valid. 260 260 Updates user's password to new password if token is valid.
--- 261 261 ---
request_serializer: PasswordResetSerializer 262 262 request_serializer: PasswordResetSerializer
""" 263 263 """
data = PasswordResetSerializer(data=request.data) 264 264 data = PasswordResetSerializer(data=request.data)
data.is_valid(raise_exception=True) 265 265 data.is_valid(raise_exception=True)
266 266
user = User.objects.get(id=data['uid'].value) 267 267 user = User.objects.get(id=data['uid'].value)
# Check token validity. 268 268 # Check token validity.
269 269
if default_token_generator.check_token(user, data['token'].value): 270 270 if default_token_generator.check_token(user, data['token'].value):
user.set_password(data['new_password'].value) 271 271 user.set_password(data['new_password'].value)
user.save() 272 272 user.save()
log_event(request) 273 273 log_event(request)
else: 274 274 else:
raise ValidationError('Could not verify reset token') 275 275 raise ValidationError('Could not verify reset token')
return Response(status=HTTP_204_NO_CONTENT) 276 276 return Response(status=HTTP_204_NO_CONTENT)
277 277
278 278
class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin): 279 279 class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin):
queryset = Flashcard.objects.all() 280 280 queryset = Flashcard.objects.all()
serializer_class = FlashcardSerializer 281 281 serializer_class = FlashcardSerializer
permission_classes = [IsAuthenticatedAndConfirmed, IsEnrolledInAssociatedSection] 282 282 permission_classes = [IsAuthenticatedAndConfirmed, IsEnrolledInAssociatedSection]
# Override create in CreateModelMixin 283 283 # Override create in CreateModelMixin
def create(self, request, *args, **kwargs): 284 284 def create(self, request, *args, **kwargs):
serializer = FlashcardSerializer(data=request.data) 285 285 serializer = FlashcardSerializer(data=request.data)
serializer.is_valid(raise_exception=True) 286 286 serializer.is_valid(raise_exception=True)
data = serializer.validated_data 287 287 data = serializer.validated_data
if not request.user.is_in_section(data['section']): 288 288 if not request.user.is_in_section(data['section']):
raise PermissionDenied('The user is not enrolled in that section') 289 289 raise PermissionDenied('The user is not enrolled in that section')
data['author'] = request.user 290 290 data['author'] = request.user
flashcard = Flashcard.objects.create(**data) 291 291 flashcard = Flashcard.objects.create(**data)
self.perform_create(flashcard) 292 292 self.perform_create(flashcard)
notify_new_card(flashcard) 293 293 notify_new_card(flashcard)
headers = self.get_success_headers(data) 294 294 headers = self.get_success_headers(data)
request.user.pull(flashcard) 295 295 request.user.pull(flashcard)
response_data = FlashcardSerializer(flashcard).data 296 296 response_data = FlashcardSerializer(flashcard).data
log_event(request, response_data) 297 297 log_event(request, response_data)
return Response(response_data, status=HTTP_201_CREATED, headers=headers) 298 298 return Response(response_data, status=HTTP_201_CREATED, headers=headers)
299 299
@detail_route(methods=['POST']) 300 300 @detail_route(methods=['POST'])
def unhide(self, request, pk): 301 301 def unhide(self, request, pk):
""" 302 302 """
Unhide the given card 303 303 Unhide the given card
--- 304 304 ---
view_mocker: flashcards.api.mock_no_params 305 305 view_mocker: flashcards.api.mock_no_params
""" 306 306 """
hide = get_object_or_404(FlashcardHide, user=request.user, flashcard=self.get_object()) 307 307 hide = get_object_or_404(FlashcardHide, user=request.user, flashcard=self.get_object())
hide.delete() 308 308 hide.delete()
log_event(request, str(self.get_object())) 309 309 log_event(request, str(self.get_object()))
return Response(status=HTTP_204_NO_CONTENT) 310 310 return Response(status=HTTP_204_NO_CONTENT)
311 311
@detail_route(methods=['POST']) 312 312 @detail_route(methods=['POST'])
def report(self, request, pk): 313 313 def report(self, request, pk):
""" 314 314 """
Hide the given card 315 315 Hide the given card
--- 316 316 ---
view_mocker: flashcards.api.mock_no_params 317 317 view_mocker: flashcards.api.mock_no_params
""" 318 318 """
self.get_object().report(request.user) 319 319 self.get_object().report(request.user)
log_event(request, str(self.get_object())) 320 320 log_event(request, str(self.get_object()))
return Response(status=HTTP_204_NO_CONTENT) 321 321 return Response(status=HTTP_204_NO_CONTENT)
322 322
hide = report 323 323 hide = report
324 324
@detail_route(methods=['POST']) 325 325 @detail_route(methods=['POST'])
def pull(self, request, pk): 326 326 def pull(self, request, pk):
""" 327 327 """
Pull a card from the live feed into the user's deck. 328 328 Pull a card from the live feed into the user's deck.
--- 329 329 ---
view_mocker: flashcards.api.mock_no_params 330 330 view_mocker: flashcards.api.mock_no_params
""" 331 331 """
try: 332 332 try:
request.user.pull(self.get_object()) 333 333 request.user.pull(self.get_object())
log_event(request, str(self.get_object())) 334 334 log_event(request, str(self.get_object()))
return Response(status=HTTP_204_NO_CONTENT) 335 335 return Response(status=HTTP_204_NO_CONTENT)
except IntegrityError, e: 336 336 except IntegrityError, e:
raise ValidationError('Cannot pull a card already in deck') 337 337 raise ValidationError('Cannot pull a card already in deck')
338 338
@detail_route(methods=['POST']) 339 339 @detail_route(methods=['POST'])
def unpull(self, request, pk): 340 340 def unpull(self, request, pk):
""" 341 341 """
Unpull a card from the user's deck 342 342 Unpull a card from the user's deck
--- 343 343 ---
view_mocker: flashcards.api.mock_no_params 344 344 view_mocker: flashcards.api.mock_no_params
""" 345 345 """
user = request.user 346 346 user = request.user
flashcard = self.get_object() 347 347 flashcard = self.get_object()
user.unpull(flashcard) 348 348 user.unpull(flashcard)
log_event(request, str(self.get_object())) 349 349 log_event(request, str(self.get_object()))
return Response(status=HTTP_204_NO_CONTENT) 350 350 return Response(status=HTTP_204_NO_CONTENT)
351 351
def partial_update(self, request, *args, **kwargs): 352 352 def partial_update(self, request, *args, **kwargs):
""" 353 353 """
Edit settings related to a card for the user. 354 354 Edit settings related to a card for the user.
--- 355 355 ---
request_serializer: FlashcardUpdateSerializer 356 356 request_serializer: FlashcardUpdateSerializer
""" 357 357 """
user = request.user 358 358 user = request.user
flashcard = self.get_object() 359 359 flashcard = self.get_object()
data = FlashcardUpdateSerializer(data=request.data) 360 360 data = FlashcardUpdateSerializer(data=request.data)
data.is_valid(raise_exception=True) 361 361 data.is_valid(raise_exception=True)
new_flashcard = data.validated_data 362 362 new_flashcard = data.validated_data
new_flashcard = flashcard.edit(user, new_flashcard) 363 363 new_flashcard = flashcard.edit(user, new_flashcard)
log_event(request, str(new_flashcard)) 364 364 log_event(request, str(new_flashcard))
return Response(FlashcardSerializer(new_flashcard, context={'user': request.user}).data, status=HTTP_200_OK) 365 365 return Response(FlashcardSerializer(new_flashcard, context={'user': request.user}).data, status=HTTP_200_OK)
366 366
367 367
class UserFlashcardQuizViewSet(GenericViewSet, CreateModelMixin, UpdateModelMixin): 368 368 class UserFlashcardQuizViewSet(GenericViewSet, CreateModelMixin, UpdateModelMixin):
permission_classes = [IsAuthenticatedAndConfirmed, IsFlashcardReviewer] 369 369 permission_classes = [IsAuthenticatedAndConfirmed, IsFlashcardReviewer]
queryset = UserFlashcardQuiz.objects.all() 370 370 queryset = UserFlashcardQuiz.objects.all()
371 371
def get_serializer_class(self): 372 372 def get_serializer_class(self):
if self.request.method == 'POST': 373 373 if self.request.method == 'POST':
return QuizRequestSerializer 374 374 return QuizRequestSerializer
return QuizAnswerRequestSerializer 375 375 return QuizAnswerRequestSerializer
376 376
flashy/settings.py View file @ 5064562
from datetime import datetime 1 1 from datetime import datetime
2 2
import os 3 3 import os
from pytz import UTC 4 4 from pytz import UTC
5 5
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 6 6 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
7 7
IN_PRODUCTION = 'FLASHY_PRODUCTION' in os.environ 8 8 IN_PRODUCTION = 'FLASHY_PRODUCTION' in os.environ
9 9
DEBUG = not IN_PRODUCTION 10 10 DEBUG = not IN_PRODUCTION
11 11
ALLOWED_HOSTS = ['127.0.0.1', 'flashy.cards'] 12 12 ALLOWED_HOSTS = ['127.0.0.1', 'flashy.cards']
13 13
AUTH_USER_MODEL = 'flashcards.User' 14 14 AUTH_USER_MODEL = 'flashcards.User'
REST_FRAMEWORK = { 15 15 REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', 16 16 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 20 17 17 'PAGE_SIZE': 20
} 18 18 }
INSTALLED_APPS = [ 19 19 INSTALLED_APPS = [
'simple_email_confirmation', 20 20 'simple_email_confirmation',
'flashcards', 21 21 'flashcards',
'django.contrib.admin', 22 22 'django.contrib.admin',
'django.contrib.admindocs', 23 23 'django.contrib.admindocs',
'django.contrib.auth', 24 24 'django.contrib.auth',
'django.contrib.contenttypes', 25 25 'django.contrib.contenttypes',
'django.contrib.sessions', 26 26 'django.contrib.sessions',
'django.contrib.messages', 27 27 'django.contrib.messages',
'django.contrib.staticfiles', 28 28 'django.contrib.staticfiles',
'ws4redis', 29 29 'ws4redis',
30 30
'rest_framework_swagger', 31 31 'rest_framework_swagger',
'rest_framework', 32 32 'rest_framework',
'django_extensions', 33 33 'django_extensions',
] 34 34 ]
35 35
WEBSOCKET_URL = '/ws/' 36 36 WEBSOCKET_URL = '/ws/'
37 37
MIDDLEWARE_CLASSES = ( 38 38 MIDDLEWARE_CLASSES = (
39 'flashcards.middleware.SetRemoteAddrFromForwardedFor',
'django.contrib.sessions.middleware.SessionMiddleware', 39 40 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 40 41 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 41 42 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 42 43 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 43 44 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 44 45 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 45 46 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware', 46 47 'django.middleware.security.SecurityMiddleware',
48
) 47 49 )
48 50
ROOT_URLCONF = 'flashy.urls' 49 51 ROOT_URLCONF = 'flashy.urls'
50 52
AUTHENTICATION_BACKENDS = ( 51 53 AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', 52 54 'django.contrib.auth.backends.ModelBackend',
) 53 55 )
54 56
TEMPLATES = [ 55 57 TEMPLATES = [
{ 56 58 {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 57 59 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['templates/'], 58 60 'DIRS': ['templates/'],
'APP_DIRS': True, 59 61 'APP_DIRS': True,
'OPTIONS': { 60 62 'OPTIONS': {
'context_processors': [ 61 63 'context_processors': [
'django.template.context_processors.debug', 62 64 'django.template.context_processors.debug',
'django.template.context_processors.request', 63 65 'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth', 64 66 'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages', 65 67 'django.contrib.messages.context_processors.messages',
'django.core.context_processors.static', 66 68 'django.core.context_processors.static',
'ws4redis.context_processors.default', 67 69 'ws4redis.context_processors.default',
], 68 70 ],
}, 69 71 },
}, 70 72 },
] 71 73 ]
72 74
WSGI_APPLICATION = 'ws4redis.django_runserver.application' 73 75 WSGI_APPLICATION = 'ws4redis.django_runserver.application'
74 76
DATABASES = { 75 77 DATABASES = {
'default': { 76 78 'default': {
'ENGINE': 'django.db.backends.sqlite3', 77 79 'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 78 80 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
} 79 81 }
} 80 82 }
81 83
if IN_PRODUCTION: 82 84 if IN_PRODUCTION:
DATABASES['default'] = { 83 85 DATABASES['default'] = {
'ENGINE': 'django.db.backends.postgresql_psycopg2', 84 86 'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'flashy', 85 87 'NAME': 'flashy',
'USER': 'flashy', 86 88 'USER': 'flashy',
'PASSWORD': os.environ['FLASHY_DB_PW'], 87 89 'PASSWORD': os.environ['FLASHY_DB_PW'],
'HOST': 'localhost', 88 90 'HOST': 'localhost',
'PORT': '', 89 91 'PORT': '',
} 90 92 }
91 93
LANGUAGE_CODE = 'en-us' 92 94 LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'America/Los_Angeles' 93 95 TIME_ZONE = 'America/Los_Angeles'
USE_I18N = True 94 96 USE_I18N = True
USE_L10N = True 95 97 USE_L10N = True
USE_TZ = True 96 98 USE_TZ = True
97 99
QUARTER_START = UTC.localize(datetime(2015, 3, 30)) 98 100 QUARTER_START = UTC.localize(datetime(2015, 3, 30))
QUARTER_END = UTC.localize(datetime(2015, 6, 12)) 99 101 QUARTER_END = UTC.localize(datetime(2015, 6, 12))
100 102
STATIC_URL = '/static/' 101 103 STATIC_URL = '/static/'
STATIC_ROOT = 'static' 102 104 STATIC_ROOT = 'static'
103 105
# Four settings just to be sure 104 106 # Four settings just to be sure
EMAIL_FROM = 'noreply@flashy.cards' 105 107 EMAIL_FROM = 'noreply@flashy.cards'
EMAIL_HOST_USER = 'noreply@flashy.cards' 106 108 EMAIL_HOST_USER = 'noreply@flashy.cards'
DEFAULT_FROM_EMAIL = 'noreply@flashy.cards' 107 109 DEFAULT_FROM_EMAIL = 'noreply@flashy.cards'
SERVER_EMAIL = 'noreply@flashy.cards' 108 110 SERVER_EMAIL = 'noreply@flashy.cards'
109 111
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 110 112 EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
111 113
if IN_PRODUCTION: 112 114 if IN_PRODUCTION:
INSTALLED_APPS.append('django_ses') 113 115 INSTALLED_APPS.append('django_ses')
AWS_SES_REGION_NAME = 'us-west-2' 114 116 AWS_SES_REGION_NAME = 'us-west-2'
AWS_SES_REGION_ENDPOINT = 'email.us-west-2.amazonaws.com' 115 117 AWS_SES_REGION_ENDPOINT = 'email.us-west-2.amazonaws.com'
EMAIL_BACKEND = 'django_ses.SESBackend' 116 118 EMAIL_BACKEND = 'django_ses.SESBackend'
117 119
if IN_PRODUCTION: 118 120 if IN_PRODUCTION:
SESSION_COOKIE_SECURE = True 119 121 SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True 120 122 CSRF_COOKIE_SECURE = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') 121 123 SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# are we secure yet? 122 124 # are we secure yet?
123 125
if IN_PRODUCTION: 124 126 if IN_PRODUCTION:
LOGGING = { 125 127 LOGGING = {
'version': 1, 126 128 'version': 1,
'disable_existing_loggers': False, 127 129 'disable_existing_loggers': False,
'handlers': { 128 130 'handlers': {
'file': { 129 131 'file': {
'level': 'DEBUG', 130 132 'level': 'DEBUG',
'class': 'logging.FileHandler', 131 133 'class': 'logging.FileHandler',
'filename': 'debug.log', 132 134 'filename': 'debug.log',
}, 133 135 },
'eventslog': { 134 136 'eventslog': {
'level': 'INFO', 135 137 'level': 'INFO',
'class': 'logging.FileHandler', 136 138 'class': 'logging.FileHandler',
'filename': 'events.log', 137 139 'filename': 'events.log',
'formatter': 'verbose' 138 140 'formatter': 'verbose'
}, 139 141 },
}, 140 142 },
'formatters': { 141 143 'formatters': {
'verbose': { 142 144 'verbose': {
'format': '%(asctime)s %(module)s %(message)s' 143 145 'format': '%(asctime)s %(module)s %(message)s'
}, 144 146 },
}, 145 147 },