Commit be6cc9169b8e3a3dd1b54ede99939a45b43ec207

Authored by Rohan Rangray
1 parent 5354852580
Exists in master

Fixed a bug in the MaskField modelfield implementation. Added MaskField serializer field.

Showing 5 changed files with 82 additions and 43 deletions Side-by-side Diff

flashcards/fields.py View file @ be6cc91
... ... @@ -51,7 +51,7 @@
51 51 return value
52 52 if not isinstance(value, set) or not all([isinstance(interval, tuple) for interval in value]):
53 53 raise ValueError("Invalid value for MaskField attribute")
54   - return sorted([list(interval) for interval in value])
  54 + return MaskField._parse_mask(sorted(value))
55 55  
56 56 def get_prep_lookup(self, lookup_type, value):
57 57 raise TypeError("Lookup not supported for MaskField")
58 58  
... ... @@ -64,9 +64,9 @@
64 64 beg, end = map(int, interval)
65 65 if not (0 <= beg <= 255) or not (0 <= end <= 255) or not (beg <= end) or not (beg > p_end):
66 66 raise ValueError("Invalid range offsets in the mask")
67   - mask_list.append((beg, end))
  67 + mask_list.append([beg, end])
68 68 p_beg, p_end = beg, end
69   - return set(mask_list)
  69 + return mask_list
70 70  
71 71 @staticmethod
72 72 def _sqlite_parse_mask(value):
73 73  
... ... @@ -77,9 +77,9 @@
77 77 if len(_range) != 2 or not all(map(unicode.isdigit, _range)):
78 78 raise ValueError("Invalid range format.")
79 79 intervals.append(tuple(_range))
80   - return MaskField._parse_mask(sorted(intervals))
  80 + return set([tuple(i) for i in MaskField._parse_mask(sorted(intervals))])
81 81  
82 82 @staticmethod
83 83 def _psql_parse_mask(value):
84   - return MaskField._parse_mask(sorted(value))
  84 + return set([tuple(i) for i in MaskField._parse_mask(sorted(value))])
flashcards/models.py View file @ be6cc91
... ... @@ -54,7 +54,6 @@
54 54 3. A user has a flashcard hidden from them
55 55 """
56 56 user = ForeignKey('User')
57   - # mask = ForeignKey('FlashcardMask', blank=True, null=True, help_text="A mask which overrides the card's mask")
58 57 mask = MaskField(max_length=255, null=True, blank=True, help_text="The user-specific mask on the card")
59 58 pulled = DateTimeField(blank=True, null=True, help_text="When the user pulled the card")
60 59 flashcard = ForeignKey('Flashcard')
... ... @@ -101,7 +100,6 @@
101 100 author = ForeignKey(User)
102 101 is_hidden = BooleanField(default=False)
103 102 hide_reason = CharField(blank=True, max_length=255, help_text="Reason for hiding this card")
104   - # mask = ForeignKey(FlashcardMask, blank=True, null=True, help_text="The default mask for this card")
105 103 mask = MaskField(max_length=255, null=True, blank=True, help_text="The mask on the card")
106 104  
107 105 class Meta:
flashcards/serializers.py View file @ be6cc91
... ... @@ -5,6 +5,7 @@
5 5 from rest_framework.relations import HyperlinkedRelatedField
6 6 from rest_framework.serializers import ModelSerializer, Serializer
7 7 from rest_framework.validators import UniqueValidator
  8 +from json import dumps, loads
8 9  
9 10  
10 11 class EmailSerializer(Serializer):
11 12  
12 13  
13 14  
14 15  
15 16  
16 17  
17 18  
18 19  
19 20  
... ... @@ -81,44 +82,71 @@
81 82 fields = ("sections", "email", "is_confirmed", "last_login", "date_joined")
82 83  
83 84  
  85 +class MaskFieldSerializer(serializers.Field):
  86 + default_error_messages = {
  87 + 'max_length': 'Ensure this field has no more than {max_length} characters.',
  88 + 'interval': 'Ensure this field has valid intervals.',
  89 + 'overlap': 'Ensure this field does not have overlapping intervals.'
  90 + }
  91 +
  92 + def to_representation(self, value):
  93 + if not isinstance(value, set) or not all([isinstance(i, tuple) for i in value]):
  94 + raise serializers.ValidationError("Invalid MaskField.")
  95 + return dumps(list(value))
  96 +
  97 + def to_internal_value(self, data):
  98 + try:
  99 + intervals = loads(data)
  100 + if not isinstance(intervals, list) or len(intervals) > 32 \
  101 + or not all([isinstance(i, list) and len(i) == 2 for i in intervals]):
  102 + raise ValueError
  103 + except ValueError:
  104 + raise serializers.ValidationError("Invalid JSON for MaskField")
  105 + return set([tuple(i) for i in intervals])
  106 +
  107 +
84 108 class FlashcardSerializer(ModelSerializer):
85 109 is_hidden = BooleanField(read_only=True)
86 110 hide_reason = CharField(read_only=True)
  111 + mask = MaskFieldSerializer()
87 112  
88 113 def validate_material_date(self, value):
89 114 # TODO: make this dynamic
90 115 quarter_start = datetime(2015, 3, 15)
91 116 quarter_end = datetime(2015, 6, 15)
92   - if quarter_start <= value <= quarter_end: return value
  117 + if quarter_start <= value <= quarter_end:
  118 + return value
93 119 else:
94 120 raise serializers.ValidationError("Material date is outside allowed range for this quarter")
95 121  
96   - #
97   - # def validate(self, data):
98   - # if
  122 + def validate_previous(self, value):
  123 + if value is None:
  124 + return value
  125 + if Flashcard.objects.filter(pk=value).count() > 0:
  126 + return value
  127 + raise serializers.ValidationError("Invalid previous Flashcard object")
99 128  
100   - class Meta:
101   - model = Flashcard
102   - exclude = 'author', 'mask',
  129 + def validate_pushed(self, value):
  130 + if value > datetime.now():
  131 + raise serializers.ValidationError("Invalid creation date for the Flashcard")
  132 + return value
103 133  
  134 + def validate_section(self, value):
  135 + if Section.objects.filter(pk=value).count() > 0:
  136 + return value
  137 + raise serializers.ValidationError("Invalid section for the flashcard")
104 138  
105   -"""
106   -class FlashcardMaskSerializer(ModelSerializer):
107   - def validate_ranges(self, value):
108   - try:
109   - intervals = value.split(',')
110   - for interval in intervals:
111   - beg, end = interval.split('-')
112   - if not (0 <= beg <= 255) or not (0 <= end <= 255) or not (beg <= end):
113   - raise Exception # just a local exception
114   - except Exception:
115   - raise serializers.ValidationError("The mask is invalid")
  139 + def validate_text(self, value):
  140 + if len(value) > 255:
  141 + raise serializers.ValidationError("Flashcard text limit exceeded")
116 142 return value
117   - class Meta:
118   - model = FlashcardMask
119 143  
120   -class UserFlashcard(ModelSerializer):
  144 + def validate_hide_reason(self, value):
  145 + if len(value) > 255:
  146 + raise serializers.ValidationError("Hide reason limit exceeded")
  147 + return value
  148 +
121 149 class Meta:
122   - model = UserFlashcard
123   -"""
  150 + model = Flashcard
  151 + exclude = 'author', 'mask',
flashcards/tests/test_models.py View file @ be6cc91
... ... @@ -48,14 +48,25 @@
48 48 author=user,
49 49 material_date=datetime.now(),
50 50 previous=None,
51   - mask={(0,4), (24,34)})
  51 + mask={(24,34), (0, 4)})
52 52  
53 53 def test_mask_field(self):
54 54 user = User.objects.get(email="none@none.com")
  55 + section = Section.objects.get(course_title='how 2 test')
55 56 flashcard = Flashcard.objects.filter(author=user).get(text="This is the text of the Flashcard")
56 57 self.assertTrue(isinstance(flashcard.mask, set))
57 58 self.assertTrue(all([isinstance(interval, tuple) for interval in flashcard.mask]))
58 59 blank1, blank2 = sorted(list(flashcard.mask))
59 60 self.assertEqual(flashcard.text[slice(*blank1)], 'This')
60 61 self.assertEqual(flashcard.text[slice(*blank2)], 'Flashcard')
  62 + try:
  63 + Flashcard.objects.create(text="This is the text of the Flashcard",
  64 + section=section,
  65 + author=user,
  66 + material_date=datetime.now(),
  67 + previous=None,
  68 + mask={(10,34), (0, 14)})
  69 + self.fail()
  70 + except ValueError:
  71 + self.assertTrue(True)
flashcards/views.py View file @ be6cc91
1 1 from django.contrib import auth
2 2 from flashcards.api import StandardResultsSetPagination
3   -from flashcards.models import Section, User, Flashcard, FlashcardReport
  3 +from flashcards.models import Section, User, Flashcard, FlashcardReport, UserFlashcard
4 4 from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \
5 5 PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer
6 6 from rest_framework.decorators import detail_route, permission_classes, api_view
... ... @@ -235,16 +235,6 @@
235 235 serializer_class = FlashcardSerializer
236 236 permission_classes = [IsAuthenticated]
237 237  
238   - def get(self, request, pk):
239   - """
240   - Return the requested flashcard.
241   - :param request: The request object.
242   - :param pk: The primary key of the card to be retrieved
243   - :return: A 200 OK request along with the card data.
244   - """
245   - obj = self.queryset.get(pk=pk)
246   - return Response(FlashcardSerializer(obj).data)
247   -
248 238 @detail_route(methods=['post'], permission_classes=[IsAuthenticated])
249 239 def report(self, request, pk):
250 240 """
... ... @@ -259,5 +249,19 @@
259 249 obj, created = FlashcardReport.objects.get_or_create(user=request.user, flashcard=self.get_object())
260 250 obj.reason = request.data['reason']
261 251 obj.save()
  252 + return Response(status=HTTP_204_NO_CONTENT)
  253 +
  254 + @detail_route(methods=['POST'], permission_classes=[IsAuthenticated])
  255 + def pull(self, request, pk):
  256 + """
  257 + Pull a card from the live feed into the user's deck.
  258 + :param request: The request object
  259 + :param pk: The primary key
  260 + :return: A 204 response upon success.
  261 + """
  262 + user = request.user
  263 + flashcard = Flashcard.objects.get(pk=pk)
  264 + user_card, created = UserFlashcard.objects.get_or_create(user=user, flashcard=flashcard)
  265 + user_card.save()
262 266 return Response(status=HTTP_204_NO_CONTENT)