Commit 5a2899b9ddc175cef2e7d334d462f7d7f7e30ee6
1 parent
33f8a47a8b
Exists in
master
add gobs of logging
Showing 4 changed files with 69 additions and 10 deletions Side-by-side Diff
flashcards/api.py
View file @
5a2899b
... | ... | @@ -46,7 +46,8 @@ |
46 | 46 | """ |
47 | 47 | |
48 | 48 | def has_permission(self, request, view): |
49 | - if not (request.user and request.user.is_authenticated()): return False | |
49 | + if not request.user: return False | |
50 | + if not request.user.is_authenticated(): return False | |
50 | 51 | if request.user.confirmed_email: return True |
51 | 52 | if (now() - request.user.date_joined).days > 0: |
52 | 53 | raise PermissionDenied('Please verify your email before continuing') |
flashcards/models.py
View file @
5a2899b
... | ... | @@ -10,6 +10,7 @@ |
10 | 10 | from django.core.validators import MinLengthValidator |
11 | 11 | from django.db import IntegrityError |
12 | 12 | from django.db.models import * |
13 | +from django.utils.log import getLogger | |
13 | 14 | from django.utils.timezone import now, make_aware |
14 | 15 | from flashy.settings import QUARTER_START |
15 | 16 | from simple_email_confirmation import SimpleEmailConfirmationUserMixin |
... | ... | @@ -18,8 +19,6 @@ |
18 | 19 | |
19 | 20 | |
20 | 21 | |
21 | - | |
22 | - | |
23 | 22 | # Hack to fix AbstractUser before subclassing it |
24 | 23 | |
25 | 24 | AbstractUser._meta.get_field('email')._unique = True |
26 | 25 | |
... | ... | @@ -154,7 +153,10 @@ |
154 | 153 | # By default, order by most recently pulled |
155 | 154 | ordering = ['-pulled'] |
156 | 155 | |
156 | + def __unicode__(self): | |
157 | + return '%s has %s' % (str(self.user), str(self.flashcard)) | |
157 | 158 | |
159 | + | |
158 | 160 | class FlashcardHide(Model): |
159 | 161 | """ |
160 | 162 | Represents the property of a flashcard being hidden by a user. |
161 | 163 | |
... | ... | @@ -172,7 +174,10 @@ |
172 | 174 | unique_together = (('user', 'flashcard'),) |
173 | 175 | index_together = ["user", "flashcard"] |
174 | 176 | |
177 | + def __unicode__(self): | |
178 | + return '%s hid %s' % (str(self.user), str(self.flashcard)) | |
175 | 179 | |
180 | + | |
176 | 181 | class Flashcard(Model): |
177 | 182 | text = CharField(max_length=180, help_text='The text on the card', validators=[MinLengthValidator(5)]) |
178 | 183 | section = ForeignKey('Section', help_text='The section with which the card is associated') |
... | ... | @@ -190,6 +195,9 @@ |
190 | 195 | # By default, order by most recently pushed |
191 | 196 | ordering = ['-pushed'] |
192 | 197 | |
198 | + def __unicode__(self): | |
199 | + return '<flashcard: %s>' % self.text | |
200 | + | |
193 | 201 | @property |
194 | 202 | def material_week_num(self): |
195 | 203 | return (self.material_date - QUARTER_START).days / 7 + 1 |
... | ... | @@ -304,6 +312,9 @@ |
304 | 312 | response = CharField(max_length=255, blank=True, null=True, default=None, help_text="The user's response") |
305 | 313 | correct = NullBooleanField(help_text="The user's self-evaluation of their response") |
306 | 314 | |
315 | + def __unicode__(self): | |
316 | + return '%s reviewed %s' % (str(self.user_flashcard.user), str(self.user_flashcard.flashcard)) | |
317 | + | |
307 | 318 | def status(self): |
308 | 319 | """ |
309 | 320 | There are three stages of a quiz object: |
310 | 321 | |
311 | 322 | |
... | ... | @@ -438,11 +449,17 @@ |
438 | 449 | unique_together = (('section', 'start_time', 'week_day'),) |
439 | 450 | ordering = ['section', 'week_day'] |
440 | 451 | |
452 | + def __unicode__(self): | |
453 | + return self.weekday_letter + ' ' + self.short_start_time | |
441 | 454 | |
455 | + | |
442 | 456 | class WhitelistedAddress(Model): |
443 | 457 | """ |
444 | 458 | An email address that has been whitelisted for a section at an instructor's request |
445 | 459 | """ |
446 | 460 | email = EmailField() |
447 | 461 | section = ForeignKey(Section, related_name='whitelist') |
462 | + | |
463 | + def __unicode__(self): | |
464 | + return '%s whitelisted for %s' % (str(self.email), str(self.section)) |
flashcards/views.py
View file @
5a2899b
... | ... | @@ -2,6 +2,7 @@ |
2 | 2 | from django.contrib import auth |
3 | 3 | from django.db import IntegrityError |
4 | 4 | from django.shortcuts import get_object_or_404 |
5 | +from django.utils.log import getLogger | |
5 | 6 | from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection, IsFlashcardReviewer, \ |
6 | 7 | IsAuthenticatedAndConfirmed |
7 | 8 | from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard, UserFlashcardQuiz |
... | ... | @@ -24,6 +25,11 @@ |
24 | 25 | from simple_email_confirmation import EmailAddress |
25 | 26 | |
26 | 27 | |
28 | +def log_event(request, event=''): | |
29 | + getLogger('flashy.events').info( | |
30 | + '%s %s %s %s' % (request.META['REMOTE_ADDR'], str(request.user), request.META.get('PATH', ''), event)) | |
31 | + | |
32 | + | |
27 | 33 | class SectionViewSet(ReadOnlyModelViewSet): |
28 | 34 | queryset = Section.objects.all() |
29 | 35 | serializer_class = DeepSectionSerializer |
30 | 36 | |
... | ... | @@ -43,10 +49,12 @@ |
43 | 49 | else: |
44 | 50 | flashcards |= Flashcard.cards_hidden_by(request.user) |
45 | 51 | flashcards = flashcards.filter(section=self.get_object()).order_by('material_date').all() |
52 | + log_event(request, str(self.get_object())) | |
46 | 53 | return Response(FlashcardSerializer(flashcards, context={"user": request.user}, many=True).data) |
47 | 54 | |
48 | 55 | @detail_route(methods=['POST']) |
49 | 56 | def enroll(self, request, pk): |
57 | + | |
50 | 58 | """ |
51 | 59 | Add the current user to a specified section |
52 | 60 | If the class has a whitelist, but the user is not on the whitelist, the request will fail. |
... | ... | @@ -55,6 +63,7 @@ |
55 | 63 | """ |
56 | 64 | try: |
57 | 65 | self.get_object().enroll(request.user) |
66 | + log_event(request, str(self.get_object())) | |
58 | 67 | except django.core.exceptions.PermissionDenied as e: |
59 | 68 | raise PermissionDenied(e) |
60 | 69 | except django.core.exceptions.ValidationError as e: |
... | ... | @@ -71,6 +80,7 @@ |
71 | 80 | """ |
72 | 81 | try: |
73 | 82 | self.get_object().drop(request.user) |
83 | + log_event(request, str(self.get_object())) | |
74 | 84 | except django.core.exceptions.PermissionDenied as e: |
75 | 85 | raise PermissionDenied(e) |
76 | 86 | except django.core.exceptions.ValidationError as e: |
... | ... | @@ -93,6 +103,7 @@ |
93 | 103 | if not query: return Response('[]') |
94 | 104 | qs = Section.search(query.split(' '))[:20] |
95 | 105 | data = SectionSerializer(qs, many=True).data |
106 | + log_event(request, query) | |
96 | 107 | return Response(data) |
97 | 108 | |
98 | 109 | @detail_route(methods=['GET']) |
... | ... | @@ -102,6 +113,7 @@ |
102 | 113 | """ |
103 | 114 | qs = request.user.get_deck(self.get_object()) |
104 | 115 | serializer = FlashcardSerializer(qs, many=True) |
116 | + log_event(request, str(self.get_object())) | |
105 | 117 | return Response(serializer.data) |
106 | 118 | |
107 | 119 | @detail_route(methods=['GET']) |
... | ... | @@ -112,6 +124,7 @@ |
112 | 124 | """ |
113 | 125 | serializer = FlashcardSerializer(self.get_object().get_feed_for_user(request.user), many=True, |
114 | 126 | context={'user': request.user}) |
127 | + log_event(request, str(self.get_object())) | |
115 | 128 | return Response(serializer.data) |
116 | 129 | |
117 | 130 | |
118 | 131 | |
... | ... | @@ -145,10 +158,12 @@ |
145 | 158 | raise ValidationError('old_password is incorrect') |
146 | 159 | request.user.set_password(data['new_password']) |
147 | 160 | request.user.save() |
161 | + log_event(request, 'change password') | |
148 | 162 | |
149 | 163 | if 'confirmation_key' in data: |
150 | 164 | try: |
151 | 165 | request.user.confirm_email(data['confirmation_key']) |
166 | + log_event(request, 'confirm email') | |
152 | 167 | except EmailAddress.DoesNotExist: |
153 | 168 | raise ValidationError('confirmation_key is invalid') |
154 | 169 | |
... | ... | @@ -170,6 +185,7 @@ |
170 | 185 | Yes, really |
171 | 186 | """ |
172 | 187 | request.user.delete() |
188 | + log_event(request) | |
173 | 189 | return Response(status=HTTP_204_NO_CONTENT) |
174 | 190 | |
175 | 191 | |
... | ... | @@ -187,7 +203,7 @@ |
187 | 203 | User.objects.create_user(**data.validated_data) |
188 | 204 | user = authenticate(**data.validated_data) |
189 | 205 | auth.login(request, user) |
190 | - | |
206 | + log_event(request) | |
191 | 207 | return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED) |
192 | 208 | |
193 | 209 | |
... | ... | @@ -209,6 +225,7 @@ |
209 | 225 | if not user.is_active: |
210 | 226 | raise NotAuthenticated('Account is disabled') |
211 | 227 | auth.login(request, user) |
228 | + log_event(request) | |
212 | 229 | return Response(UserSerializer(request.user).data) |
213 | 230 | |
214 | 231 | |
... | ... | @@ -219,6 +236,7 @@ |
219 | 236 | Logs the authenticated user out. |
220 | 237 | """ |
221 | 238 | auth.logout(request) |
239 | + log_event(request) | |
222 | 240 | return Response(status=HTTP_204_NO_CONTENT) |
223 | 241 | |
224 | 242 | |
... | ... | @@ -231,6 +249,7 @@ |
231 | 249 | """ |
232 | 250 | data = PasswordResetRequestSerializer(data=request.data) |
233 | 251 | data.is_valid(raise_exception=True) |
252 | + log_event(request, 'email: ' + str(data['email'])) | |
234 | 253 | get_object_or_404(User, email=data['email'].value).request_password_reset() |
235 | 254 | return Response(status=HTTP_204_NO_CONTENT) |
236 | 255 | |
... | ... | @@ -251,6 +270,7 @@ |
251 | 270 | if default_token_generator.check_token(user, data['token'].value): |
252 | 271 | user.set_password(data['new_password'].value) |
253 | 272 | user.save() |
273 | + log_event(request) | |
254 | 274 | else: |
255 | 275 | raise ValidationError('Could not verify reset token') |
256 | 276 | return Response(status=HTTP_204_NO_CONTENT) |
... | ... | @@ -274,7 +294,7 @@ |
274 | 294 | headers = self.get_success_headers(data) |
275 | 295 | request.user.pull(flashcard) |
276 | 296 | response_data = FlashcardSerializer(flashcard).data |
277 | - | |
297 | + log_event(request, response_data) | |
278 | 298 | return Response(response_data, status=HTTP_201_CREATED, headers=headers) |
279 | 299 | |
280 | 300 | @detail_route(methods=['POST']) |
... | ... | @@ -286,6 +306,7 @@ |
286 | 306 | """ |
287 | 307 | hide = get_object_or_404(FlashcardHide, user=request.user, flashcard=self.get_object()) |
288 | 308 | hide.delete() |
309 | + log_event(request, str(self.get_object())) | |
289 | 310 | return Response(status=HTTP_204_NO_CONTENT) |
290 | 311 | |
291 | 312 | @detail_route(methods=['POST']) |
... | ... | @@ -296,6 +317,7 @@ |
296 | 317 | view_mocker: flashcards.api.mock_no_params |
297 | 318 | """ |
298 | 319 | self.get_object().report(request.user) |
320 | + log_event(request, str(self.get_object())) | |
299 | 321 | return Response(status=HTTP_204_NO_CONTENT) |
300 | 322 | |
301 | 323 | hide = report |
... | ... | @@ -309,6 +331,7 @@ |
309 | 331 | """ |
310 | 332 | try: |
311 | 333 | request.user.pull(self.get_object()) |
334 | + log_event(request, str(self.get_object())) | |
312 | 335 | return Response(status=HTTP_204_NO_CONTENT) |
313 | 336 | except IntegrityError, e: |
314 | 337 | raise ValidationError('Cannot pull a card already in deck') |
... | ... | @@ -323,6 +346,7 @@ |
323 | 346 | user = request.user |
324 | 347 | flashcard = self.get_object() |
325 | 348 | user.unpull(flashcard) |
349 | + log_event(request, str(self.get_object())) | |
326 | 350 | return Response(status=HTTP_204_NO_CONTENT) |
327 | 351 | |
328 | 352 | def partial_update(self, request, *args, **kwargs): |
... | ... | @@ -337,6 +361,7 @@ |
337 | 361 | data.is_valid(raise_exception=True) |
338 | 362 | new_flashcard = data.validated_data |
339 | 363 | new_flashcard = flashcard.edit(user, new_flashcard) |
364 | + log_event(request, str(new_flashcard)) | |
340 | 365 | return Response(FlashcardSerializer(new_flashcard, context={'user': request.user}).data, status=HTTP_200_OK) |
341 | 366 | |
342 | 367 | |
... | ... | @@ -386,6 +411,7 @@ |
386 | 411 | blanked_word=user_flashcard.flashcard.text[slice(*mask)]) |
387 | 412 | user_flashcard_quiz.save() |
388 | 413 | response = QuizResponseSerializer(instance=user_flashcard_quiz, mask=mask) |
414 | + log_event(request, response) | |
389 | 415 | return Response(response.data, status=HTTP_200_OK) |
390 | 416 | |
391 | 417 | def partial_update(self, request, *args, **kwargs): |
... | ... | @@ -400,5 +426,6 @@ |
400 | 426 | serializer = QuizAnswerRequestSerializer(instance=user_flashcard_quiz, data=request.data) |
401 | 427 | serializer.is_valid(raise_exception=True) |
402 | 428 | serializer.update(user_flashcard_quiz, serializer.validated_data) |
429 | + log_event(request, serializer.data) | |
403 | 430 | return Response(status=HTTP_204_NO_CONTENT) |
flashy/settings.py
View file @
5a2899b
1 | -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) | |
2 | -import os | |
3 | 1 | from datetime import datetime |
2 | + | |
3 | +import os | |
4 | 4 | from pytz import UTC |
5 | 5 | |
6 | 6 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
... | ... | @@ -35,7 +35,6 @@ |
35 | 35 | |
36 | 36 | WEBSOCKET_URL = '/ws/' |
37 | 37 | |
38 | - | |
39 | 38 | MIDDLEWARE_CLASSES = ( |
40 | 39 | 'django.contrib.sessions.middleware.SessionMiddleware', |
41 | 40 | 'django.middleware.common.CommonMiddleware', |
42 | 41 | |
43 | 42 | |
44 | 43 | |
... | ... | @@ -132,13 +131,29 @@ |
132 | 131 | 'class': 'logging.FileHandler', |
133 | 132 | 'filename': 'debug.log', |
134 | 133 | }, |
134 | + 'eventslog': { | |
135 | + 'level': 'INFO', | |
136 | + 'class': 'logging.FileHandler', | |
137 | + 'filename': 'events.log', | |
138 | + 'formatter': 'verbose' | |
139 | + }, | |
135 | 140 | }, |
141 | + 'formatters': { | |
142 | + 'verbose': { | |
143 | + 'format': '%(asctime)s %(module)s %(message)s' | |
144 | + }, | |
145 | + }, | |
136 | 146 | 'loggers': { |
137 | - 'django.request': { | |
147 | + 'django': { | |
138 | 148 | 'handlers': ['file'], |
139 | 149 | 'level': 'DEBUG', |
140 | 150 | 'propagate': True, |
141 | 151 | }, |
152 | + 'flashy.events': { | |
153 | + 'level': 'INFO', | |
154 | + 'propagate': True, | |
155 | + 'handlers': ['eventslog'], | |
156 | + } | |
142 | 157 | }, |
143 | 158 | } |
144 | 159 |