serializers.py 11.6 KB
from json import loads
from collections import Iterable
from django.utils.datetime_safe import datetime
from django.utils.timezone import now
from flashcards.models import Section, LecturePeriod, User, Flashcard, UserFlashcardQuiz, UserFlashcard
from flashcards.validators import FlashcardMask, OverlapIntervalException
from rest_framework import serializers
from rest_framework.fields import EmailField, BooleanField, CharField, IntegerField, DateTimeField, empty, \
SerializerMethodField, FloatField
from rest_framework.serializers import ModelSerializer, Serializer, PrimaryKeyRelatedField, ListField
from rest_framework.validators import UniqueValidator
from flashy.settings import QUARTER_END, QUARTER_START
class EmailSerializer(Serializer):
email = EmailField(required=True)
class LoginSerializer(EmailSerializer):
password = CharField(required=True)
class RegistrationSerializer(Serializer):
email = EmailField(required=True, validators=[UniqueValidator(queryset=User.objects.all())])
password = CharField(required=True, min_length=8)
class PasswordResetRequestSerializer(EmailSerializer):
def validate_email(self, value):
try:
User.objects.get(email=value)
return value
except User.DoesNotExist:
raise serializers.ValidationError('No user exists with that email')
class PasswordResetSerializer(Serializer):
new_password = CharField(required=True, allow_blank=False, min_length=8)
uid = IntegerField(required=True)
token = CharField(required=True)
def validate_uid(self, value):
try:
User.objects.get(id=value)
return value
except User.DoesNotExist:
raise serializers.ValidationError('Could not verify reset token')
class EmailVerificationSerializer(Serializer):
confirmation_key = CharField()
class UserUpdateSerializer(Serializer):
old_password = CharField(required=False)
new_password = CharField(required=False, allow_blank=False, min_length=8)
def validate(self, data):
if 'new_password' in data and 'old_password' not in data:
raise serializers.ValidationError('old_password is required to set a new_password')
return data
class LecturePeriodSerializer(ModelSerializer):
class Meta:
model = LecturePeriod
exclude = 'id', 'section'
class SectionSerializer(ModelSerializer):
lecture_times = CharField()
short_name = CharField()
long_name = CharField()
can_enroll = SerializerMethodField()
is_enrolled = SerializerMethodField()
class Meta:
model = Section
def get_can_enroll(self, obj):
if 'user' not in self.context: return False
if not obj.is_whitelisted: return True
return obj.is_user_on_whitelist(self.context['user'])
def get_is_enrolled(self, obj):
if 'user' not in self.context: return False
return obj.is_user_enrolled(self.context['user'])
class DeepSectionSerializer(SectionSerializer):
lectures = LecturePeriodSerializer(source='lectureperiod_set', many=True, read_only=True)
class FeedRequestSerializer(Serializer):
page = IntegerField(min_value=1, default=1, required=False)
def validate(self, attrs):
if not isinstance(attrs['page'], int):
raise serializers.ValidationError("Invalid page number")
return attrs
class UserSerializer(ModelSerializer):
email = EmailField(required=False)
sections = SectionSerializer(many=True)
class Meta:
model = User
fields = ("sections", "email", "is_confirmed", "last_login", "date_joined", 'locked')
class MaskFieldSerializer(serializers.Field):
default_error_messages = {
'max_length': 'Ensure this field has no more than {max_length} characters.',
'interval': 'Ensure this field has valid intervals.',
'overlap': 'Ensure this field does not have overlapping intervals.'
}
def to_representation(self, value):
return map(list, self._make_mask(value))
def to_internal_value(self, value):
if not isinstance(value, list):
value = loads(value)
return self._make_mask(value)
def _make_mask(self, data):
try:
mask = FlashcardMask(data)
except ValueError:
raise serializers.ValidationError("Invalid JSON for MaskField")
except TypeError:
raise serializers.ValidationError("Invalid data for MaskField.")
except OverlapIntervalException:
raise serializers.ValidationError("Invalid intervals for MaskField data.")
if len(mask) > 32:
raise serializers.ValidationError("Too many intervals in the mask.")
return mask
class FlashcardSerializer(ModelSerializer):
is_hidden = SerializerMethodField()
is_in_deck = SerializerMethodField()
is_authored_by_user = SerializerMethodField()
material_week_num = IntegerField(read_only=True)
material_date = DateTimeField(default=now)
mask = MaskFieldSerializer(allow_null=True)
display_mask = SerializerMethodField()
score = FloatField(read_only=True)
def validate_material_date(self, value):
# TODO: make this dynamic
if QUARTER_START <= value <= QUARTER_END:
return value
else:
raise serializers.ValidationError("Material date is outside allowed range for this quarter")
def validate_pushed(self, value):
if value > datetime.now():
raise serializers.ValidationError("Invalid creation date for the Flashcard")
return value
def validate_mask(self, value):
if value is None:
return None
if len(self.initial_data['text']) < value.max_offset():
raise serializers.ValidationError("Mask out of bounds")
return value
def get_is_hidden(self, obj):
if 'user' not in self.context: return False
return obj.is_hidden_from(self.context['user'])
def get_is_in_deck(self, obj):
if 'user' not in self.context: return False
return obj.is_in_deck(self.context['user'])
def get_is_authored_by_user(self, obj):
if 'user' not in self.context: return False
return obj.author == self.context['user']
def get_display_mask(self, obj):
if 'user' in self.context:
userflashcard = UserFlashcard.objects.filter(flashcard=obj, user=self.context['user'])
if userflashcard.exists() and userflashcard.get().mask:
return MaskFieldSerializer().to_representation(userflashcard.get().mask)
return MaskFieldSerializer().to_representation(obj.mask)
class Meta:
model = Flashcard
exclude = 'author', 'previous', 'hide_reason'
class FlashcardUpdateSerializer(serializers.Serializer):
text = CharField(max_length=255, required=False)
material_date = DateTimeField(required=False)
mask = MaskFieldSerializer(required=False)
def validate_material_date(self, date):
if date > QUARTER_END:
raise serializers.ValidationError("Invalid material_date for the flashcard")
return date
def validate(self, attrs):
# Make sure that at least one of the attributes was passed in
if not any(i in attrs for i in ['material_date', 'text', 'mask']):
raise serializers.ValidationError("No new value passed in")
return attrs
class QuizRequestSerializer(serializers.Serializer):
sections = ListField(child=IntegerField(min_value=1), required=False, default=[])
material_date_begin = DateTimeField(default=QUARTER_START)
material_date_end = DateTimeField(default=QUARTER_END)
def update(self, instance, validated_data):
pass
def create(self, validated_data):
return validated_data
def validate_material_date_begin(self, value):
if QUARTER_START <= value <= QUARTER_END:
return value
raise serializers.ValidationError("Invalid begin date for the flashcard range")
def validate_material_date_end(self, value):
if QUARTER_START <= value <= QUARTER_END:
return value
raise serializers.ValidationError("Invalid end date for the flashcard range")
def validate_sections(self, value):
if value is not None and not isinstance(value, Iterable):
raise serializers.ValidationError("Invalid section format. Expecting a list or no value.")
if value is None or len(value) == 0:
return Section.objects.all()
section_filter = Section.objects.filter(pk__in=value)
if not section_filter.exists():
raise serializers.ValidationError("Those aren't valid sections")
return value
def validate(self, attrs):
if attrs['material_date_begin'] > attrs['material_date_end']:
raise serializers.ValidationError("Invalid range")
if 'sections' not in attrs:
attrs['sections'] = self.validate_sections(None)
return attrs
class QuizResponseSerializer(ModelSerializer):
pk = PrimaryKeyRelatedField(queryset=UserFlashcardQuiz.objects.all(), many=True)
section = PrimaryKeyRelatedField(queryset=Section.objects.all())
text = CharField(max_length=255)
mask = ListField(child=IntegerField())
def __init__(self, instance=None, mask=[], data=empty, **kwargs):
super(QuizResponseSerializer, self).__init__(instance=instance, data=data, **kwargs)
self.mask = self._validate_mask(mask)
def to_representation(self, instance):
return {
'pk': instance.pk,
'section': instance.user_flashcard.flashcard.section.pk,
'text': instance.user_flashcard.flashcard.text,
'mask': self.mask
}
def _validate_mask(self, value):
if not isinstance(value, list) and value is not None:
raise serializers.ValidationError("The selected mask has to be a list " + str(value))
if value is None or len(value) == 0:
return []
if len(value) == 2 and (0 <= value[0] and value[1] <= len(self.instance.user_flashcard.flashcard.text)):
return value
raise serializers.ValidationError("Invalid mask for the flashcard")
class Meta:
model = UserFlashcardQuiz
class QuizAnswerRequestSerializer(ModelSerializer):
response = CharField(required=False, max_length=255, help_text="The user's response")
correct = BooleanField(required=False, help_text="The user's self-evaluation of their response")
def __init__(self, instance=None, data=empty, **kwargs):
assert isinstance(instance, UserFlashcardQuiz) or instance is None
super(QuizAnswerRequestSerializer, self).__init__(instance=instance, data=data, **kwargs)
def validate_response(self, response):
if response is None:
return ""
return response
def validate(self, attrs):
if not any(i in attrs for i in ('correct', 'response')):
raise serializers.ValidationError("No data passed in")
if 'response' in attrs and self.instance.response is not None:
raise serializers.ValidationError("You have already sent in a response for this quiz")
if 'correct' in attrs:
if 'response' not in attrs and self.instance.response is None:
raise serializers.ValidationError("You haven't sent in a response yet")
if self.instance.correct is not None:
raise serializers.ValidationError("You have already sent in the user's evaluation")
return attrs
class Meta:
model = UserFlashcardQuiz
exclude = 'blanked_word', 'user_flashcard', 'when'
class SubscribeViewSerializer(Serializer):
registration_id = CharField(allow_blank=False, allow_null=False)