Commit 89c98de531159f7a0335d9f821e7d8649c943bd7
Exists in
master
Merge branch 'master' of https://git.ucsd.edu/110swag/flashy-backend
Conflicts: flashcards/tests/test_api.py flashcards/views.py
Showing 7 changed files Side-by-side Diff
README.md
View file @
89c98de
... | ... | @@ -2,6 +2,8 @@ |
2 | 2 | |
3 | 3 | All of these commands should be run from this directory (the one containing README.md) |
4 | 4 | |
5 | +Virtualenv for Windows creates a dir inexplicably named scripts rather than bin. So substitute venv/bin for venv/scripts if you are on Windows. | |
6 | + | |
5 | 7 | Install virtualenv before continuing. This is most easily accomplished with: |
6 | 8 | |
7 | 9 | pip install virtualenv |
flashcards/models.py
View file @
89c98de
... | ... | @@ -48,7 +48,7 @@ |
48 | 48 | sections = ManyToManyField('Section', help_text="The sections which the user is enrolled in") |
49 | 49 | |
50 | 50 | def is_in_section(self, section): |
51 | - return section in self.sections.all() | |
51 | + return self.sections.filter(pk=section.pk).exists() | |
52 | 52 | |
53 | 53 | def pull(self, flashcard): |
54 | 54 | if not self.is_in_section(flashcard.section): |
55 | 55 | |
... | ... | @@ -57,7 +57,12 @@ |
57 | 57 | user_card.pulled = datetime.now() |
58 | 58 | user_card.save() |
59 | 59 | |
60 | + def get_deck(self, section): | |
61 | + if not self.is_in_section(section): | |
62 | + raise ObjectDoesNotExist("User not enrolled in section") | |
63 | + return Flashcard.objects.all().filter(userflashcard__user=self).filter(section=section) | |
60 | 64 | |
65 | + | |
61 | 66 | class UserFlashcard(Model): |
62 | 67 | """ |
63 | 68 | Represents the relationship between a user and a flashcard by: |
... | ... | @@ -151,6 +156,7 @@ |
151 | 156 | self.pk = None |
152 | 157 | if 'mask' in new_flashcard: |
153 | 158 | self.mask = new_flashcard['mask'] |
159 | + self.save() | |
154 | 160 | |
155 | 161 | @classmethod |
156 | 162 | def cards_visible_to(cls, user): |
... | ... | @@ -158,7 +164,7 @@ |
158 | 164 | :param user: |
159 | 165 | :return: A queryset with all cards that should be visible to a user. |
160 | 166 | """ |
161 | - return cls.objects.filter(is_hidden=False).exclude(userflashcard__user=user, userflashcard__pulled=None) | |
167 | + return cls.objects.filter(is_hidden=False).exclude(flashcardhide__user=user) | |
162 | 168 | |
163 | 169 | |
164 | 170 | class UserFlashcardQuiz(Model): |
flashcards/serializers.py
View file @
89c98de
flashcards/tests/test_api.py
View file @
89c98de
... | ... | @@ -202,13 +202,14 @@ |
202 | 202 | self.user = User.objects.get(email='none@none.com') |
203 | 203 | self.flashcard = Flashcard(text="jason", section=Section.objects.get(pk=1), material_date=now(), author=self.user) |
204 | 204 | self.flashcard.save() |
205 | + self.section = Section.objects.get(pk=1) | |
205 | 206 | |
206 | 207 | def test_list_sections(self): |
207 | 208 | response = self.client.get("/api/sections/", format="json") |
208 | 209 | self.assertEqual(response.status_code, HTTP_200_OK) |
209 | 210 | |
210 | 211 | def test_section_enroll(self): |
211 | - section = Section.objects.get(pk=1) | |
212 | + section = self.section | |
212 | 213 | self.assertFalse(self.user.sections.filter(pk=section.pk)) |
213 | 214 | |
214 | 215 | # test enrolling in a section without a whitelist |
... | ... | @@ -232,7 +233,7 @@ |
232 | 233 | self.assertTrue(self.user.sections.filter(pk=section.pk).exists()) |
233 | 234 | |
234 | 235 | def test_section_drop(self): |
235 | - section = Section.objects.get(pk=3) | |
236 | + section = self.section | |
236 | 237 | |
237 | 238 | # test dropping a section that the user isn't in |
238 | 239 | response = self.client.post('/api/sections/%d/drop/' % section.pk) |
239 | 240 | |
... | ... | @@ -263,6 +264,12 @@ |
263 | 264 | self.assertEqual(response.status_code, HTTP_200_OK) |
264 | 265 | |
265 | 266 | def test_section_deck(self): |
267 | + self.user.sections.add(self.section) | |
268 | + self.user.save() | |
266 | 269 | response = self.client.get('/api/sections/1/deck/') |
270 | + self.assertEqual(response.status_code, HTTP_200_OK) | |
271 | + | |
272 | + def test_section_ordered_deck(self): | |
273 | + response = self.client.get('/api/sections/1/ordered_deck/') | |
267 | 274 | self.assertEqual(response.status_code, HTTP_200_OK) |
flashcards/tests/test_models.py
View file @
89c98de
1 | 1 | from datetime import datetime |
2 | 2 | |
3 | 3 | from django.test import TestCase |
4 | -from flashcards.models import User, Section, Flashcard | |
5 | -from flashcards.validators import OverlapIntervalException | |
4 | +from flashcards.models import User, Section, Flashcard, UserFlashcard | |
5 | +from flashcards.validators import FlashcardMask, OverlapIntervalException | |
6 | 6 | |
7 | 7 | |
8 | 8 | class RegistrationTests(TestCase): |
9 | 9 | |
10 | 10 | |
11 | 11 | |
12 | 12 | |
13 | 13 | |
... | ... | @@ -37,24 +37,87 @@ |
37 | 37 | self.assertEqual(user.sections.count(), 0) |
38 | 38 | |
39 | 39 | |
40 | +class FlashcardMaskTest(TestCase): | |
41 | + def test_iterable(self): | |
42 | + try: | |
43 | + FlashcardMask(1) | |
44 | + except TypeError as te: | |
45 | + self.assertEqual(te.message, "Interval not a valid iterable") | |
46 | + try: | |
47 | + FlashcardMask([1, 2, 4]) | |
48 | + except TypeError as te: | |
49 | + self.assertEqual(te.message, "Interval not a valid iterable") | |
50 | + | |
51 | + def test_interval(self): | |
52 | + try: | |
53 | + FlashcardMask([[1, 2, 3], [1]]) | |
54 | + except TypeError as te: | |
55 | + self.assertEqual(te.message, "Intervals must have exactly 2 elements, begin and end") | |
56 | + try: | |
57 | + FlashcardMask([[1, 2], [1, 2, 4]]) | |
58 | + except TypeError as te: | |
59 | + self.assertEqual(te.message, "Intervals must have exactly 2 elements, begin and end") | |
60 | + try: | |
61 | + FlashcardMask(([1, 2], [1])) | |
62 | + except TypeError as te: | |
63 | + self.assertEqual(te.message, "Intervals must have exactly 2 elements, begin and end") | |
64 | + try: | |
65 | + FlashcardMask("[1,2,3]") | |
66 | + except TypeError as te: | |
67 | + self.assertEqual(te.message, "Intervals must have exactly 2 elements, begin and end") | |
68 | + | |
69 | + def test_overlap(self): | |
70 | + try: | |
71 | + FlashcardMask({(1, 2), (2, 5)}) | |
72 | + except OverlapIntervalException as oie: | |
73 | + self.assertEqual(oie.message, "Invalid interval offsets in the mask") | |
74 | + try: | |
75 | + FlashcardMask({(1, 20), (12, 15)}) | |
76 | + except OverlapIntervalException as oie: | |
77 | + self.assertEqual(oie.message, "Invalid interval offsets in the mask") | |
78 | + try: | |
79 | + FlashcardMask({(2, 1), (5, 2)}) | |
80 | + except OverlapIntervalException as oie: | |
81 | + self.assertEqual(oie.message, "Invalid interval offsets in the mask") | |
82 | + | |
83 | + | |
84 | + | |
40 | 85 | class FlashcardTests(TestCase): |
41 | 86 | def setUp(self): |
42 | - user = User.objects.create_user(email="none@none.com", password="1234") | |
43 | 87 | section = Section.objects.create(department='dept', |
44 | 88 | course_num='101a', |
45 | 89 | course_title='how 2 test', |
46 | 90 | instructor='George Lucas', |
47 | 91 | quarter='SP15') |
48 | - Flashcard.objects.create(text="This is the text of the Flashcard", | |
49 | - section=section, | |
50 | - author=user, | |
51 | - material_date=datetime.now(), | |
52 | - previous=None, | |
53 | - mask={(24,34), (0, 4)}) | |
92 | + user = User.objects.create_user(email="none@none.com", password="1234") | |
93 | + user.sections.add(section) | |
94 | + flashcard = Flashcard.objects.create(text="This is the text of the Flashcard", | |
95 | + section=section, | |
96 | + author=user, | |
97 | + material_date=datetime.now(), | |
98 | + previous=None, | |
99 | + mask={(24,34), (0, 4)}) | |
100 | + user.save() | |
101 | + section.save() | |
102 | + flashcard.save() | |
54 | 103 | |
55 | - def test_mask_field(self): | |
104 | + def test_flashcard_edit(self): | |
56 | 105 | user = User.objects.get(email="none@none.com") |
106 | + user2 = User.objects.create_user(email="wow@wow.com", password="wow") | |
57 | 107 | section = Section.objects.get(course_title='how 2 test') |
108 | + user2.sections.add(section) | |
109 | + user2.save() | |
110 | + flashcard = Flashcard.objects.filter(author=user).get(text="This is the text of the Flashcard") | |
111 | + pk_backup = flashcard.pk | |
112 | + self.assertTrue(user.is_in_section(section)) | |
113 | + flashcard.edit(user, {}) | |
114 | + self.assertIsNotNone(flashcard.pk) | |
115 | + UserFlashcard.objects.create(user=user2, flashcard=flashcard).save() | |
116 | + flashcard.edit(user2, {'text': 'This is the new text'}) | |
117 | + self.assertNotEqual(flashcard.pk, pk_backup) | |
118 | + | |
119 | + def test_mask_field(self): | |
120 | + user = User.objects.get(email="none@none.com") | |
58 | 121 | flashcard = Flashcard.objects.filter(author=user).get(text="This is the text of the Flashcard") |
59 | 122 | self.assertTrue(isinstance(flashcard.mask, set)) |
60 | 123 | self.assertTrue(all([isinstance(interval, tuple) for interval in flashcard.mask])) |
flashcards/validators.py
View file @
89c98de
... | ... | @@ -3,17 +3,17 @@ |
3 | 3 | |
4 | 4 | class FlashcardMask(set): |
5 | 5 | def __init__(self, iterable, *args, **kwargs): |
6 | + self._iterable_check(iterable) | |
6 | 7 | iterable = map(tuple, iterable) |
7 | 8 | super(FlashcardMask, self).__init__(iterable, *args, **kwargs) |
8 | - self._iterable_check() | |
9 | 9 | self._interval_check() |
10 | 10 | self._overlap_check() |
11 | 11 | |
12 | 12 | def max_offset(self): |
13 | 13 | return self._end |
14 | 14 | |
15 | - def _iterable_check(self): | |
16 | - if not all([isinstance(i, Iterable) for i in self]): | |
15 | + def _iterable_check(self, iterable): | |
16 | + if not isinstance(iterable, Iterable) or not all([isinstance(i, Iterable) for i in iterable]): | |
17 | 17 | raise TypeError("Interval not a valid iterable") |
18 | 18 | |
19 | 19 | def _interval_check(self): |
20 | 20 | |
21 | 21 | |
22 | 22 | |
... | ... | @@ -27,14 +27,14 @@ |
27 | 27 | if not (0 <= beg <= 255) or not (0 <= end <= 255) or not (beg <= end) or not (beg > p_end): |
28 | 28 | raise OverlapIntervalException((beg, end), "Invalid interval offsets in the mask") |
29 | 29 | p_beg, p_end = beg, end |
30 | - self._end = end | |
30 | + self._end = p_end | |
31 | 31 | |
32 | 32 | |
33 | 33 | class OverlapIntervalException(Exception): |
34 | - def __init__(self, interval, reason): | |
34 | + def __init__(self, interval, message): | |
35 | 35 | self.interval = interval |
36 | - self.reason = reason | |
36 | + self.message = message | |
37 | 37 | |
38 | 38 | def __str__(self): |
39 | - return repr(self.reason) + ': ' + repr(self.interval) | |
39 | + return repr(self.message) + ': ' + repr(self.interval) |
flashcards/views.py
View file @
89c98de
... | ... | @@ -16,7 +16,6 @@ |
16 | 16 | from rest_framework.response import Response |
17 | 17 | from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied |
18 | 18 | from simple_email_confirmation import EmailAddress |
19 | -from datetime import datetime | |
20 | 19 | |
21 | 20 | |
22 | 21 | class SectionViewSet(ReadOnlyModelViewSet): |
23 | 22 | |
... | ... | @@ -31,9 +30,13 @@ |
31 | 30 | Gets flashcards for a section, excluding hidden cards. |
32 | 31 | Returned in strictly chronological order (material date). |
33 | 32 | """ |
33 | +<<<<<<< HEAD | |
34 | 34 | flashcards = Flashcard.cards_visible_to(request.user).filter( \ |
35 | 35 | section=self.get_object()).all() |
36 | 36 | |
37 | +======= | |
38 | + flashcards = Flashcard.cards_visible_to(request.user).filter(section=self.get_object()) | |
39 | +>>>>>>> 41819dd7ec2f22a04db83459c33e31c2b7d16d99 | |
37 | 40 | return Response(FlashcardSerializer(flashcards, many=True).data) |
38 | 41 | |
39 | 42 | @detail_route(methods=['post'], permission_classes=[IsAuthenticated]) |
40 | 43 | |
41 | 44 | |
... | ... | @@ -87,13 +90,21 @@ |
87 | 90 | """ |
88 | 91 | Gets the contents of a user's deck for a given section. |
89 | 92 | """ |
90 | - qs = Flashcard.objects.all() | |
91 | - qs = qs.filter(userflashcard__user=request.user) | |
92 | - qs = qs.filter(section = self.get_object()) | |
93 | + qs = request.user.get_deck(self.get_object()) | |
93 | 94 | serializer = FlashcardSerializer(qs, many=True) |
94 | 95 | return Response(serializer.data) |
95 | 96 | |
97 | + @detail_route(methods=['get'], permission_classes=[IsAuthenticated]) | |
98 | + def ordered_deck(self, request, pk): | |
99 | + """ | |
100 | + Get a chronological order by material_date of flashcards for a section. | |
101 | + This excludes hidden card. | |
102 | + """ | |
103 | + qs = request.user.get_deck(self.get_object()).order_by('-material_date') | |
104 | + serializer = FlashcardSerializer(qs, many=True) | |
105 | + return Response(serializer.data) | |
96 | 106 | |
107 | + | |
97 | 108 | class UserSectionListView(ListAPIView): |
98 | 109 | serializer_class = SectionSerializer |
99 | 110 | permission_classes = [IsAuthenticated] |
... | ... | @@ -323,7 +334,6 @@ |
323 | 334 | new_flashcard = data.validated_data |
324 | 335 | |
325 | 336 | flashcard.edit(user, new_flashcard) |
326 | - flashcard.save() | |
327 | 337 | user_card, created = UserFlashcard.objects.get_or_create(user=user, flashcard=flashcard) |
328 | 338 | user_card.mask = flashcard.mask |
329 | 339 |