Commit 89c98de531159f7a0335d9f821e7d8649c943bd7

Authored by Rachel Lee
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

... ... @@ -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
... ... @@ -155,7 +155,7 @@
155 155 return value
156 156  
157 157 def validate_mask(self, value):
158   - if value.max_offset() >= len(self.text):
  158 + if len(self.data['text']) < value.max_offset():
159 159 raise serializers.ValidationError("Mask out of bounds")
160 160 return value
161 161  
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