Commit cb2c2f3fc331b3892fb4fed69babe668e287e99d
1 parent
9b28c5c0eb
Exists in
master
Fixed FlashcardMask JSON deserialization.
Showing 2 changed files with 5 additions and 4 deletions Inline Diff
flashcards/serializers.py
View file @
cb2c2f3
from django.utils.datetime_safe import datetime | 1 | 1 | from django.utils.datetime_safe import datetime | |
import pytz | 2 | 2 | import pytz | |
from flashcards.models import Section, LecturePeriod, User, Flashcard | 3 | 3 | from flashcards.models import Section, LecturePeriod, User, Flashcard | |
from flashcards.validators import FlashcardMask, OverlapIntervalException | 4 | 4 | from flashcards.validators import FlashcardMask, OverlapIntervalException | |
from rest_framework import serializers | 5 | 5 | from rest_framework import serializers | |
from rest_framework.fields import EmailField, BooleanField, CharField, IntegerField | 6 | 6 | from rest_framework.fields import EmailField, BooleanField, CharField, IntegerField | |
from rest_framework.relations import HyperlinkedRelatedField | 7 | 7 | from rest_framework.relations import HyperlinkedRelatedField | |
from rest_framework.serializers import ModelSerializer, Serializer | 8 | 8 | from rest_framework.serializers import ModelSerializer, Serializer | |
from rest_framework.validators import UniqueValidator | 9 | 9 | from rest_framework.validators import UniqueValidator | |
from json import dumps, loads | 10 | 10 | from json import dumps, loads | |
11 | 11 | |||
12 | 12 | |||
class EmailSerializer(Serializer): | 13 | 13 | class EmailSerializer(Serializer): | |
email = EmailField(required=True) | 14 | 14 | email = EmailField(required=True) | |
15 | 15 | |||
16 | 16 | |||
class EmailPasswordSerializer(EmailSerializer): | 17 | 17 | class EmailPasswordSerializer(EmailSerializer): | |
password = CharField(required=True) | 18 | 18 | password = CharField(required=True) | |
19 | 19 | |||
20 | 20 | |||
class RegistrationSerializer(EmailPasswordSerializer): | 21 | 21 | class RegistrationSerializer(EmailPasswordSerializer): | |
email = EmailField(required=True, validators=[UniqueValidator(queryset=User.objects.all())]) | 22 | 22 | email = EmailField(required=True, validators=[UniqueValidator(queryset=User.objects.all())]) | |
23 | 23 | |||
24 | 24 | |||
class PasswordResetRequestSerializer(EmailSerializer): | 25 | 25 | class PasswordResetRequestSerializer(EmailSerializer): | |
def validate_email(self, value): | 26 | 26 | def validate_email(self, value): | |
try: | 27 | 27 | try: | |
User.objects.get(email=value) | 28 | 28 | User.objects.get(email=value) | |
return value | 29 | 29 | return value | |
except User.DoesNotExist: | 30 | 30 | except User.DoesNotExist: | |
raise serializers.ValidationError('No user exists with that email') | 31 | 31 | raise serializers.ValidationError('No user exists with that email') | |
32 | 32 | |||
33 | 33 | |||
class PasswordResetSerializer(Serializer): | 34 | 34 | class PasswordResetSerializer(Serializer): | |
new_password = CharField(required=True, allow_blank=False) | 35 | 35 | new_password = CharField(required=True, allow_blank=False) | |
uid = IntegerField(required=True) | 36 | 36 | uid = IntegerField(required=True) | |
token = CharField(required=True) | 37 | 37 | token = CharField(required=True) | |
38 | 38 | |||
def validate_uid(self, value): | 39 | 39 | def validate_uid(self, value): | |
try: | 40 | 40 | try: | |
User.objects.get(id=value) | 41 | 41 | User.objects.get(id=value) | |
return value | 42 | 42 | return value | |
except User.DoesNotExist: | 43 | 43 | except User.DoesNotExist: | |
raise serializers.ValidationError('Could not verify reset token') | 44 | 44 | raise serializers.ValidationError('Could not verify reset token') | |
45 | 45 | |||
46 | 46 | |||
class UserUpdateSerializer(Serializer): | 47 | 47 | class UserUpdateSerializer(Serializer): | |
old_password = CharField(required=False) | 48 | 48 | old_password = CharField(required=False) | |
new_password = CharField(required=False, allow_blank=False) | 49 | 49 | new_password = CharField(required=False, allow_blank=False) | |
confirmation_key = CharField(required=False) | 50 | 50 | confirmation_key = CharField(required=False) | |
# reset_token = CharField(required=False) | 51 | 51 | # reset_token = CharField(required=False) | |
52 | 52 | |||
def validate(self, data): | 53 | 53 | def validate(self, data): | |
if 'new_password' in data and 'old_password' not in data: | 54 | 54 | if 'new_password' in data and 'old_password' not in data: | |
raise serializers.ValidationError('old_password is required to set a new_password') | 55 | 55 | raise serializers.ValidationError('old_password is required to set a new_password') | |
return data | 56 | 56 | return data | |
57 | 57 | |||
58 | 58 | |||
class Password(Serializer): | 59 | 59 | class Password(Serializer): | |
email = EmailField(required=True) | 60 | 60 | email = EmailField(required=True) | |
password = CharField(required=True) | 61 | 61 | password = CharField(required=True) | |
62 | 62 | |||
63 | 63 | |||
class LecturePeriodSerializer(ModelSerializer): | 64 | 64 | class LecturePeriodSerializer(ModelSerializer): | |
class Meta: | 65 | 65 | class Meta: | |
model = LecturePeriod | 66 | 66 | model = LecturePeriod | |
exclude = 'id', 'section' | 67 | 67 | exclude = 'id', 'section' | |
68 | 68 | |||
69 | 69 | |||
class SectionSerializer(ModelSerializer): | 70 | 70 | class SectionSerializer(ModelSerializer): | |
lectures = LecturePeriodSerializer(source='lectureperiod_set', many=True, read_only=True) | 71 | 71 | lectures = LecturePeriodSerializer(source='lectureperiod_set', many=True, read_only=True) | |
72 | 72 | |||
class Meta: | 73 | 73 | class Meta: | |
model = Section | 74 | 74 | model = Section | |
75 | 75 | |||
76 | 76 | |||
class UserSerializer(ModelSerializer): | 77 | 77 | class UserSerializer(ModelSerializer): | |
email = EmailField(required=False) | 78 | 78 | email = EmailField(required=False) | |
sections = HyperlinkedRelatedField(queryset=Section.objects.all(), many=True, view_name='section-detail') | 79 | 79 | sections = HyperlinkedRelatedField(queryset=Section.objects.all(), many=True, view_name='section-detail') | |
is_confirmed = BooleanField() | 80 | 80 | is_confirmed = BooleanField() | |
81 | 81 | |||
class Meta: | 82 | 82 | class Meta: | |
model = User | 83 | 83 | model = User | |
fields = ("sections", "email", "is_confirmed", "last_login", "date_joined") | 84 | 84 | fields = ("sections", "email", "is_confirmed", "last_login", "date_joined") | |
85 | 85 | |||
86 | 86 | |||
class MaskFieldSerializer(serializers.Field): | 87 | 87 | class MaskFieldSerializer(serializers.Field): | |
default_error_messages = { | 88 | 88 | default_error_messages = { | |
'max_length': 'Ensure this field has no more than {max_length} characters.', | 89 | 89 | 'max_length': 'Ensure this field has no more than {max_length} characters.', | |
'interval': 'Ensure this field has valid intervals.', | 90 | 90 | 'interval': 'Ensure this field has valid intervals.', | |
'overlap': 'Ensure this field does not have overlapping intervals.' | 91 | 91 | 'overlap': 'Ensure this field does not have overlapping intervals.' | |
} | 92 | 92 | } | |
93 | 93 | |||
def to_representation(self, value): | 94 | 94 | def to_representation(self, value): | |
return dumps(list(self._make_mask(value))) | 95 | 95 | return dumps(list(self._make_mask(value))) | |
96 | 96 | |||
def to_internal_value(self, value): | 97 | 97 | def to_internal_value(self, value): | |
return self._make_mask(value) | 98 | 98 | return self._make_mask(loads(value)) | |
99 | 99 | |||
def _make_mask(self, data): | 100 | 100 | def _make_mask(self, data): | |
try: | 101 | 101 | try: | |
mask = FlashcardMask(loads(data)) | 102 | 102 | mask = FlashcardMask(data) | |
except ValueError: | 103 | 103 | except ValueError: | |
raise serializers.ValidationError("Invalid JSON for MaskField") | 104 | 104 | raise serializers.ValidationError("Invalid JSON for MaskField") | |
except TypeError: | 105 | 105 | except TypeError: | |
raise serializers.ValidationError("Invalid data for MaskField.") | 106 | 106 | raise serializers.ValidationError("Invalid data for MaskField.") | |
except OverlapIntervalException: | 107 | 107 | except OverlapIntervalException: | |
raise serializers.ValidationError("Invalid intervals for MaskField data.") | 108 | 108 | raise serializers.ValidationError("Invalid intervals for MaskField data.") | |
if len(mask) > 32: | 109 | 109 | if len(mask) > 32: | |
raise serializers.ValidationError("Too many intervals in the mask.") | 110 | 110 | raise serializers.ValidationError("Too many intervals in the mask.") | |
return mask | 111 | 111 | return mask | |
112 | 112 | |||
113 | 113 | |||
class FlashcardSerializer(ModelSerializer): | 114 | 114 | class FlashcardSerializer(ModelSerializer): | |
is_hidden = BooleanField(read_only=True) | 115 | 115 | is_hidden = BooleanField(read_only=True) | |
hide_reason = CharField(read_only=True) | 116 | 116 | hide_reason = CharField(read_only=True) | |
mask = MaskFieldSerializer() | 117 | 117 | mask = MaskFieldSerializer() | |
118 | 118 |
flashcards/validators.py
View file @
cb2c2f3
__author__ = 'rray' | 1 | 1 | __author__ = 'rray' | |
2 | 2 | |||
from collections import Iterable | 3 | 3 | from collections import Iterable | |
4 | 4 | |||
5 | 5 | |||
class FlashcardMask(set): | 6 | 6 | class FlashcardMask(set): | |
def __init__(self, *args, **kwargs): | 7 | 7 | def __init__(self, iterable, *args, **kwargs): | |
super(FlashcardMask, self).__init__(*args, **kwargs) | 8 | 8 | iterable = map(tuple, iterable) | |
9 | super(FlashcardMask, self).__init__(iterable, *args, **kwargs) | |||
self._iterable_check() | 9 | 10 | self._iterable_check() | |
self._interval_check() | 10 | 11 | self._interval_check() | |
self._overlap_check() | 11 | 12 | self._overlap_check() | |
12 | 13 | |||
def _iterable_check(self): | 13 | 14 | def _iterable_check(self): | |
if not all([isinstance(i, Iterable) for i in self]): | 14 | 15 | if not all([isinstance(i, Iterable) for i in self]): | |
raise TypeError("Interval not a valid iterable") | 15 | 16 | raise TypeError("Interval not a valid iterable") | |
16 | 17 | |||
def _interval_check(self): | 17 | 18 | def _interval_check(self): | |
if not all([len(i) == 2 for i in self]): | 18 | 19 | if not all([len(i) == 2 for i in self]): | |
raise TypeError("Intervals must have exactly 2 elements, begin and end") | 19 | 20 | raise TypeError("Intervals must have exactly 2 elements, begin and end") | |
20 | 21 | |||
def _overlap_check(self): | 21 | 22 | def _overlap_check(self): | |
p_beg, p_end = -1, -1 | 22 | 23 | p_beg, p_end = -1, -1 | |
for interval in sorted(self): | 23 | 24 | for interval in sorted(self): | |
beg, end = map(int, interval) | 24 | 25 | beg, end = map(int, interval) | |
if not (0 <= beg <= 255) or not (0 <= end <= 255) or not (beg <= end) or not (beg > p_end): | 25 | 26 | if not (0 <= beg <= 255) or not (0 <= end <= 255) or not (beg <= end) or not (beg > p_end): | |
raise OverlapIntervalException((beg, end), "Invalid interval offsets in the mask") | 26 | 27 | raise OverlapIntervalException((beg, end), "Invalid interval offsets in the mask") | |
p_beg, p_end = beg, end | 27 | 28 | p_beg, p_end = beg, end | |
28 | 29 | |||
29 | 30 |