Commit 54bba1fea25b9317c79e3c7f9882aa2d9b43030a
1 parent
1cc32d8b00
Exists in
master
enforce enrollment on things
Showing 4 changed files with 57 additions and 25 deletions Side-by-side Diff
flashcards/api.py
View file @
54bba1f
1 | +from flashcards.models import Flashcard | |
1 | 2 | from rest_framework.pagination import PageNumberPagination |
2 | 3 | from rest_framework.permissions import BasePermission |
3 | 4 | |
4 | 5 | |
... | ... | @@ -12,8 +13,15 @@ |
12 | 13 | """ |
13 | 14 | Permissions for the user detail view. Anonymous users may only POST. |
14 | 15 | """ |
16 | + | |
15 | 17 | def has_object_permission(self, request, view, obj): |
16 | 18 | if request.method == 'POST': |
17 | 19 | return True |
18 | 20 | return request.user.is_authenticated() |
21 | + | |
22 | + | |
23 | +class IsEnrolledInAssociatedSection(BasePermission): | |
24 | + def has_object_permission(self, request, view, obj): | |
25 | + assert type(obj) is Flashcard | |
26 | + return request.user.is_in_section(obj.section) |
flashcards/models.py
View file @
54bba1f
1 | 1 | from django.contrib.auth.models import AbstractUser, UserManager |
2 | -from django.core.exceptions import PermissionDenied | |
2 | +from django.core.exceptions import PermissionDenied, ValidationError | |
3 | 3 | from django.db.models import * |
4 | 4 | from django.utils.timezone import now |
5 | 5 | from simple_email_confirmation import SimpleEmailConfirmationUserMixin |
... | ... | @@ -54,7 +54,7 @@ |
54 | 54 | if not self.is_in_section(flashcard.section): |
55 | 55 | raise ValueError("User not in the section this flashcard belongs to") |
56 | 56 | user_card = UserFlashcard.objects.create(user=self, flashcard=flashcard) |
57 | - user_card.pulled = datetime.now() | |
57 | + user_card.pulled = now() | |
58 | 58 | user_card.save() |
59 | 59 | |
60 | 60 | def unpull(self, flashcard): |
... | ... | @@ -250,6 +250,19 @@ |
250 | 250 | :return: whether the user is on the waitlist for this section |
251 | 251 | """ |
252 | 252 | return self.whitelist.filter(email=user.email).exists() |
253 | + | |
254 | + | |
255 | + def enroll(self, user): | |
256 | + if user.sections.filter(pk=self.pk).exists(): | |
257 | + raise ValidationError('User is already enrolled in this section') | |
258 | + if self.is_whitelisted and not self.is_user_on_whitelist(user): | |
259 | + raise PermissionDenied("User must be on the whitelist to add this section.") | |
260 | + self.user_set.add(user) | |
261 | + | |
262 | + def drop(self, user): | |
263 | + if not user.sections.filter(pk=self.pk).exists(): | |
264 | + raise ValidationError("User is not enrolled in the section.") | |
265 | + self.user_set.remove(user) | |
253 | 266 | |
254 | 267 | class Meta: |
255 | 268 | ordering = ['-course_title'] |
flashcards/tests/test_api.py
View file @
54bba1f
1 | 1 | from django.core import mail |
2 | 2 | from flashcards.models import * |
3 | -from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND | |
3 | +from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK, HTTP_403_FORBIDDEN | |
4 | 4 | from rest_framework.test import APITestCase |
5 | 5 | from re import search |
6 | 6 | from django.utils.timezone import now |
... | ... | @@ -183,7 +183,10 @@ |
183 | 183 | def setUp(self): |
184 | 184 | section = Section.objects.get(pk=1) |
185 | 185 | user = User.objects.get(email='none@none.com') |
186 | - | |
186 | + section.enroll(user) | |
187 | + self.inaccessible_flashcard = Flashcard(text="you can't see me!", section=Section.objects.get(pk=2), | |
188 | + material_date=now(), author=user) | |
189 | + self.inaccessible_flashcard.save() | |
187 | 190 | self.flashcard = Flashcard(text="jason", section=section, material_date=now(), author=user) |
188 | 191 | self.flashcard.save() |
189 | 192 | |
... | ... | @@ -221,7 +224,8 @@ |
221 | 224 | def setUp(self): |
222 | 225 | self.client.login(email='none@none.com', password='1234') |
223 | 226 | self.user = User.objects.get(email='none@none.com') |
224 | - self.flashcard = Flashcard(text="jason", section=Section.objects.get(pk=1), material_date=now(), author=self.user) | |
227 | + self.flashcard = Flashcard(text="jason", section=Section.objects.get(pk=1), material_date=now(), | |
228 | + author=self.user) | |
225 | 229 | self.flashcard.save() |
226 | 230 | self.section = Section.objects.get(pk=1) |
227 | 231 | |
228 | 232 | |
229 | 233 | |
230 | 234 | |
231 | 235 | |
... | ... | @@ -308,17 +312,25 @@ |
308 | 312 | def setUp(self): |
309 | 313 | self.client.login(email='none@none.com', password='1234') |
310 | 314 | self.user = User.objects.get(email='none@none.com') |
311 | - self.flashcard = Flashcard(text="jason", section=Section.objects.get(pk=1), material_date=now(), | |
315 | + self.section = Section.objects.get(pk=1) | |
316 | + self.section.enroll(self.user) | |
317 | + self.flashcard = Flashcard(text="jason", section=self.section, material_date=now(), | |
312 | 318 | author=self.user) |
313 | 319 | self.flashcard.save() |
314 | - self.section = Section.objects.get(pk=1) | |
320 | + self.inaccessible_flashcard = Flashcard(text="can't touch this", section=Section.objects.get(pk=2), | |
321 | + material_date=now(), author=self.user) | |
322 | + self.inaccessible_flashcard.save() | |
315 | 323 | |
324 | + | |
316 | 325 | def test_hide_flashcard(self): |
317 | 326 | url = '/api/flashcards/1/hide/' |
318 | - data = {1, ''} | |
319 | - response = self.client.post(url, data, format='json') | |
327 | + response = self.client.post(url, format='json') | |
320 | 328 | self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) |
321 | 329 | |
330 | + response = self.client.post('/api/flashcards/%d/hide/' % self.inaccessible_flashcard.pk, format='json') | |
331 | + # This should fail because the user is not enrolled in section id 2 | |
332 | + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) | |
333 | + | |
322 | 334 | def test_unhide_flashcard(self): |
323 | 335 | url = '/api/flashcards/1/unhide/' |
324 | 336 | flashcard_hide = FlashcardHide(user=self.user, flashcard=self.flashcard) |
... | ... | @@ -326,4 +338,9 @@ |
326 | 338 | |
327 | 339 | response = self.client.post(url, format='json') |
328 | 340 | self.assertEqual(response.status_code, HTTP_204_NO_CONTENT) |
341 | + | |
342 | + response = self.client.post('/api/flashcards/%d/unhide/' % self.inaccessible_flashcard.pk, format='json') | |
343 | + | |
344 | + # This should fail because the user is not enrolled in section id 2 | |
345 | + self.assertEqual(response.status_code, HTTP_403_FORBIDDEN) |
flashcards/views.py
View file @
54bba1f
1 | +import django | |
2 | + | |
1 | 3 | from django.contrib import auth |
2 | 4 | from django.shortcuts import get_object_or_404 |
3 | -from flashcards.api import StandardResultsSetPagination | |
5 | +from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection | |
4 | 6 | from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard |
5 | 7 | from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \ |
6 | 8 | PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \ |
... | ... | @@ -31,8 +33,7 @@ |
31 | 33 | Gets flashcards for a section, excluding hidden cards. |
32 | 34 | Returned in strictly chronological order (material date). |
33 | 35 | """ |
34 | - flashcards = Flashcard.cards_visible_to(request.user).filter( \ | |
35 | - section=self.get_object()).all() | |
36 | + flashcards = Flashcard.cards_visible_to(request.user).filter(section=self.get_object()).all() | |
36 | 37 | return Response(FlashcardSerializer(flashcards, many=True).data) |
37 | 38 | |
38 | 39 | @detail_route(methods=['post']) |
... | ... | @@ -47,12 +48,8 @@ |
47 | 48 | parameters_strategy: |
48 | 49 | form: replace |
49 | 50 | """ |
50 | - section = self.get_object() | |
51 | - if request.user.sections.filter(pk=section.pk).exists(): | |
52 | - raise ValidationError("You are already in this section.") | |
53 | - if section.is_whitelisted and not section.is_user_on_whitelist(request.user): | |
54 | - raise PermissionDenied("You must be on the whitelist to add this section.") | |
55 | - request.user.sections.add(section) | |
51 | + | |
52 | + self.get_object().enroll(request.user) | |
56 | 53 | return Response(status=HTTP_204_NO_CONTENT) |
57 | 54 | |
58 | 55 | @detail_route(methods=['post']) |
... | ... | @@ -67,10 +64,10 @@ |
67 | 64 | parameters_strategy: |
68 | 65 | form: replace |
69 | 66 | """ |
70 | - section = self.get_object() | |
71 | - if not section.user_set.filter(pk=request.user.pk).exists(): | |
72 | - raise ValidationError("You are not in the section.") | |
73 | - section.user_set.remove(request.user) | |
67 | + try: | |
68 | + self.get_object().drop(request.user) | |
69 | + except django.core.exceptions.PermissionDenied as e: raise PermissionDenied(e) | |
70 | + except django.core.exceptions.ValidationError as e: raise ValidationError(e) | |
74 | 71 | return Response(status=HTTP_204_NO_CONTENT) |
75 | 72 | |
76 | 73 | @list_route(methods=['GET']) |
... | ... | @@ -286,7 +283,7 @@ |
286 | 283 | class FlashcardViewSet(GenericViewSet, UpdateModelMixin, CreateModelMixin, RetrieveModelMixin): |
287 | 284 | queryset = Flashcard.objects.all() |
288 | 285 | serializer_class = FlashcardSerializer |
289 | - permission_classes = [IsAuthenticated] | |
286 | + permission_classes = [IsAuthenticated, IsEnrolledInAssociatedSection] | |
290 | 287 | |
291 | 288 | # Override create in CreateModelMixin |
292 | 289 | def create(self, request, *args, **kwargs): |
... | ... | @@ -331,7 +328,7 @@ |
331 | 328 | parameters_strategy: |
332 | 329 | form: replace |
333 | 330 | """ |
334 | - hide = get_object_or_404(FlashcardHide ,user=request.user, flashcard=self.get_object()) | |
331 | + hide = get_object_or_404(FlashcardHide, user=request.user, flashcard=self.get_object()) | |
335 | 332 | hide.delete() |
336 | 333 | return Response(status=HTTP_204_NO_CONTENT) |
337 | 334 |