Commit d370a0e6846a0d0d9fe5bd51606368b94520b7b2

Authored by Andrew Buss
1 parent 55c7345264
Exists in master

fix get/GET in flashcard list

Showing 1 changed file with 1 additions and 1 deletions Inline Diff

flashcards/views.py View file @ d370a0e
import django 1 1 import django
from django.contrib import auth 2 2 from django.contrib import auth
from django.db import IntegrityError 3 3 from django.db import IntegrityError
from django.shortcuts import get_object_or_404 4 4 from django.shortcuts import get_object_or_404
from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection, IsFlashcardReviewer 5 5 from flashcards.api import StandardResultsSetPagination, IsEnrolledInAssociatedSection, IsFlashcardReviewer
from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard, UserFlashcardQuiz 6 6 from flashcards.models import Section, User, Flashcard, FlashcardHide, UserFlashcard, UserFlashcardQuiz
from flashcards.notifications import notify_new_card 7 7 from flashcards.notifications import notify_new_card
from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \ 8 8 from flashcards.serializers import SectionSerializer, UserUpdateSerializer, RegistrationSerializer, UserSerializer, \
PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \ 9 9 PasswordResetSerializer, PasswordResetRequestSerializer, EmailPasswordSerializer, FlashcardSerializer, \
FlashcardUpdateSerializer, QuizRequestSerializer, QuizResponseSerializer, \ 10 10 FlashcardUpdateSerializer, QuizRequestSerializer, QuizResponseSerializer, \
QuizAnswerRequestSerializer, DeepSectionSerializer 11 11 QuizAnswerRequestSerializer, DeepSectionSerializer
from rest_framework.decorators import detail_route, permission_classes, api_view, list_route 12 12 from rest_framework.decorators import detail_route, permission_classes, api_view, list_route
from rest_framework.generics import ListAPIView, GenericAPIView 13 13 from rest_framework.generics import ListAPIView, GenericAPIView
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin 14 14 from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.permissions import IsAuthenticated 15 15 from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet 16 16 from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet
from django.core.mail import send_mail 17 17 from django.core.mail import send_mail
from django.contrib.auth import authenticate 18 18 from django.contrib.auth import authenticate
from django.contrib.auth.tokens import default_token_generator 19 19 from django.contrib.auth.tokens import default_token_generator
from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK 20 20 from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_201_CREATED, HTTP_200_OK
from rest_framework.response import Response 21 21 from rest_framework.response import Response
from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied 22 22 from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, ValidationError, PermissionDenied
from simple_email_confirmation import EmailAddress 23 23 from simple_email_confirmation import EmailAddress
24 24
25 25
class SectionViewSet(ReadOnlyModelViewSet): 26 26 class SectionViewSet(ReadOnlyModelViewSet):
queryset = Section.objects.all() 27 27 queryset = Section.objects.all()
serializer_class = DeepSectionSerializer 28 28 serializer_class = DeepSectionSerializer
pagination_class = StandardResultsSetPagination 29 29 pagination_class = StandardResultsSetPagination
permission_classes = [IsAuthenticated] 30 30 permission_classes = [IsAuthenticated]
31 31
@detail_route(methods=['GET']) 32 32 @detail_route(methods=['GET'])
def flashcards(self, request, pk): 33 33 def flashcards(self, request, pk):
""" 34 34 """
Gets flashcards for a section, excluding hidden cards. 35 35 Gets flashcards for a section, excluding hidden cards.
Returned in strictly chronological order (material date). 36 36 Returned in strictly chronological order (material date).
""" 37 37 """
flashcards = Flashcard.cards_visible_to(request.user) 38 38 flashcards = Flashcard.cards_visible_to(request.user)
if 'hidden' in request.GET: 39 39 if 'hidden' in request.GET:
if request.get['hidden'] == 'only': 40 40 if request.GET['hidden'] == 'only':
flashcards = Flashcard.cards_hidden_by(request.user) 41 41 flashcards = Flashcard.cards_hidden_by(request.user)
else: 42 42 else:
flashcards |= Flashcard.cards_hidden_by(request.user) 43 43 flashcards |= Flashcard.cards_hidden_by(request.user)
flashcards = flashcards.filter(section=self.get_object()).all() 44 44 flashcards = flashcards.filter(section=self.get_object()).all()
return Response(FlashcardSerializer(flashcards, context={"user": request.user}, many=True).data) 45 45 return Response(FlashcardSerializer(flashcards, context={"user": request.user}, many=True).data)
46 46
@detail_route(methods=['POST']) 47 47 @detail_route(methods=['POST'])
def enroll(self, request, pk): 48 48 def enroll(self, request, pk):
""" 49 49 """
Add the current user to a specified section 50 50 Add the current user to a specified section
If the class has a whitelist, but the user is not on the whitelist, the request will fail. 51 51 If the class has a whitelist, but the user is not on the whitelist, the request will fail.
--- 52 52 ---
view_mocker: flashcards.api.mock_no_params 53 53 view_mocker: flashcards.api.mock_no_params
""" 54 54 """
try: 55 55 try:
self.get_object().enroll(request.user) 56 56 self.get_object().enroll(request.user)
except django.core.exceptions.PermissionDenied as e: 57 57 except django.core.exceptions.PermissionDenied as e:
raise PermissionDenied(e) 58 58 raise PermissionDenied(e)
except django.core.exceptions.ValidationError as e: 59 59 except django.core.exceptions.ValidationError as e:
raise ValidationError(e) 60 60 raise ValidationError(e)
return Response(status=HTTP_204_NO_CONTENT) 61 61 return Response(status=HTTP_204_NO_CONTENT)
62 62
@detail_route(methods=['POST']) 63 63 @detail_route(methods=['POST'])
def drop(self, request, pk): 64 64 def drop(self, request, pk):
""" 65 65 """
Remove the current user from a specified section 66 66 Remove the current user from a specified section
If the user is not in the class, the request will fail. 67 67 If the user is not in the class, the request will fail.
--- 68 68 ---
view_mocker: flashcards.api.mock_no_params 69 69 view_mocker: flashcards.api.mock_no_params
""" 70 70 """
try: 71 71 try:
self.get_object().drop(request.user) 72 72 self.get_object().drop(request.user)
except django.core.exceptions.PermissionDenied as e: 73 73 except django.core.exceptions.PermissionDenied as e:
raise PermissionDenied(e) 74 74 raise PermissionDenied(e)
except django.core.exceptions.ValidationError as e: 75 75 except django.core.exceptions.ValidationError as e:
raise ValidationError(e) 76 76 raise ValidationError(e)
return Response(status=HTTP_204_NO_CONTENT) 77 77 return Response(status=HTTP_204_NO_CONTENT)
78 78
@list_route(methods=['GET']) 79 79 @list_route(methods=['GET'])
def search(self, request): 80 80 def search(self, request):
""" 81 81 """
Returns a list of sections which match a user's query 82 82 Returns a list of sections which match a user's query
--- 83 83 ---
parameters: 84 84 parameters:
- name: q 85 85 - name: q
description: space-separated list of terms 86 86 description: space-separated list of terms
required: true 87 87 required: true
type: form 88 88 type: form
response_serializer: SectionSerializer 89 89 response_serializer: SectionSerializer
""" 90 90 """
query = request.GET.get('q', None) 91 91 query = request.GET.get('q', None)
if not query: return Response('[]') 92 92 if not query: return Response('[]')
qs = Section.search(query.split(' '))[:20] 93 93 qs = Section.search(query.split(' '))[:20]
data = SectionSerializer(qs, many=True).data 94 94 data = SectionSerializer(qs, many=True).data
return Response(data) 95 95 return Response(data)
96 96
@detail_route(methods=['GET']) 97 97 @detail_route(methods=['GET'])
def deck(self, request, pk): 98 98 def deck(self, request, pk):
""" 99 99 """
Gets the contents of a user's deck for a given section. 100 100 Gets the contents of a user's deck for a given section.
""" 101 101 """
qs = request.user.get_deck(self.get_object()) 102 102 qs = request.user.get_deck(self.get_object())
serializer = FlashcardSerializer(qs, many=True) 103 103 serializer = FlashcardSerializer(qs, many=True)
return Response(serializer.data) 104 104 return Response(serializer.data)
105 105
@detail_route(methods=['GET']) 106 106 @detail_route(methods=['GET'])
def feed(self, request, pk): 107 107 def feed(self, request, pk):
""" 108 108 """
Gets the contents of a user's feed for a section. 109 109 Gets the contents of a user's feed for a section.
Exclude cards that are already in the user's deck 110 110 Exclude cards that are already in the user's deck
""" 111 111 """
serializer = FlashcardSerializer(self.get_object().get_feed_for_user(request.user), many=True, 112 112 serializer = FlashcardSerializer(self.get_object().get_feed_for_user(request.user), many=True,
context={'user': request.user}) 113 113 context={'user': request.user})
return Response(serializer.data) 114 114 return Response(serializer.data)
115 115
116 116
class UserSectionListView(ListAPIView): 117 117 class UserSectionListView(ListAPIView):
serializer_class = DeepSectionSerializer 118 118 serializer_class = DeepSectionSerializer
permission_classes = [IsAuthenticated] 119 119 permission_classes = [IsAuthenticated]
120 120
def get_queryset(self): 121 121 def get_queryset(self):
return self.request.user.sections.all() 122 122 return self.request.user.sections.all()
123 123
def paginate_queryset(self, queryset): return None 124 124 def paginate_queryset(self, queryset): return None
125 125
126 126
class UserDetail(GenericAPIView): 127 127 class UserDetail(GenericAPIView):
serializer_class = UserSerializer 128 128 serializer_class = UserSerializer
permission_classes = [IsAuthenticated] 129 129 permission_classes = [IsAuthenticated]
130 130
def patch(self, request, format=None): 131 131 def patch(self, request, format=None):
""" 132 132 """
Updates the user's password, or verifies their email address 133 133 Updates the user's password, or verifies their email address
--- 134 134 ---
request_serializer: UserUpdateSerializer 135 135 request_serializer: UserUpdateSerializer
response_serializer: UserSerializer 136 136 response_serializer: UserSerializer
""" 137 137 """
data = UserUpdateSerializer(data=request.data, context={'user': request.user}) 138 138 data = UserUpdateSerializer(data=request.data, context={'user': request.user})
data.is_valid(raise_exception=True) 139 139 data.is_valid(raise_exception=True)
data = data.validated_data 140 140 data = data.validated_data
141 141
if 'new_password' in data: 142 142 if 'new_password' in data:
if not request.user.check_password(data['old_password']): 143 143 if not request.user.check_password(data['old_password']):
raise ValidationError('old_password is incorrect') 144 144 raise ValidationError('old_password is incorrect')
request.user.set_password(data['new_password']) 145 145 request.user.set_password(data['new_password'])
request.user.save() 146 146 request.user.save()
147 147
if 'confirmation_key' in data: 148 148 if 'confirmation_key' in data:
try: 149 149 try:
request.user.confirm_email(data['confirmation_key']) 150 150 request.user.confirm_email(data['confirmation_key'])
except EmailAddress.DoesNotExist: 151 151 except EmailAddress.DoesNotExist:
raise ValidationError('confirmation_key is invalid') 152 152 raise ValidationError('confirmation_key is invalid')
153 153
return Response(UserSerializer(request.user).data) 154 154 return Response(UserSerializer(request.user).data)
155 155
def get(self, request, format=None): 156 156 def get(self, request, format=None):
""" 157 157 """
Return data about the user 158 158 Return data about the user
--- 159 159 ---
response_serializer: UserSerializer 160 160 response_serializer: UserSerializer
""" 161 161 """
serializer = UserSerializer(request.user, context={'request': request}) 162 162 serializer = UserSerializer(request.user, context={'request': request})
return Response(serializer.data) 163 163 return Response(serializer.data)
164 164
def delete(self, request): 165 165 def delete(self, request):
""" 166 166 """
Irrevocably delete the user and their data 167 167 Irrevocably delete the user and their data
168 168
Yes, really 169 169 Yes, really
""" 170 170 """
request.user.delete() 171 171 request.user.delete()
return Response(status=HTTP_204_NO_CONTENT) 172 172 return Response(status=HTTP_204_NO_CONTENT)
173 173
174 174
@api_view(['POST']) 175 175 @api_view(['POST'])
def register(request, format=None): 176 176 def register(request, format=None):
""" 177 177 """
Register a new user 178 178 Register a new user
--- 179 179 ---
request_serializer: EmailPasswordSerializer 180 180 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 181 181 response_serializer: UserSerializer
""" 182 182 """
data = RegistrationSerializer(data=request.data) 183 183 data = RegistrationSerializer(data=request.data)
data.is_valid(raise_exception=True) 184 184 data.is_valid(raise_exception=True)
185 185
User.objects.create_user(**data.validated_data) 186 186 User.objects.create_user(**data.validated_data)
user = authenticate(**data.validated_data) 187 187 user = authenticate(**data.validated_data)
auth.login(request, user) 188 188 auth.login(request, user)
189 189
return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED) 190 190 return Response(UserSerializer(request.user).data, status=HTTP_201_CREATED)
191 191
192 192
@api_view(['POST']) 193 193 @api_view(['POST'])
def login(request): 194 194 def login(request):
""" 195 195 """
Authenticates user and returns user data if valid. 196 196 Authenticates user and returns user data if valid.
--- 197 197 ---
request_serializer: EmailPasswordSerializer 198 198 request_serializer: EmailPasswordSerializer
response_serializer: UserSerializer 199 199 response_serializer: UserSerializer
""" 200 200 """
201 201
data = EmailPasswordSerializer(data=request.data) 202 202 data = EmailPasswordSerializer(data=request.data)
data.is_valid(raise_exception=True) 203 203 data.is_valid(raise_exception=True)
user = authenticate(**data.validated_data) 204 204 user = authenticate(**data.validated_data)
205 205
if user is None: 206 206 if user is None:
raise AuthenticationFailed('Invalid email or password') 207 207 raise AuthenticationFailed('Invalid email or password')
if not user.is_active: 208 208 if not user.is_active:
raise NotAuthenticated('Account is disabled') 209 209 raise NotAuthenticated('Account is disabled')
auth.login(request, user) 210 210 auth.login(request, user)
return Response(UserSerializer(request.user).data) 211 211 return Response(UserSerializer(request.user).data)
212 212
213 213
@api_view(['POST']) 214 214 @api_view(['POST'])
@permission_classes((IsAuthenticated,)) 215 215 @permission_classes((IsAuthenticated,))
def logout(request, format=None): 216 216 def logout(request, format=None):
""" 217 217 """
Logs the authenticated user out. 218 218 Logs the authenticated user out.
""" 219 219 """
auth.logout(request) 220 220 auth.logout(request)
return Response(status=HTTP_204_NO_CONTENT) 221 221 return Response(status=HTTP_204_NO_CONTENT)
222 222
223 223
@api_view(['POST']) 224 224 @api_view(['POST'])
def request_password_reset(request, format=None): 225 225 def request_password_reset(request, format=None):
""" 226 226 """
Send a password reset token/link to the provided email. 227 227 Send a password reset token/link to the provided email.
--- 228 228 ---
request_serializer: PasswordResetRequestSerializer 229 229 request_serializer: PasswordResetRequestSerializer
""" 230 230 """
data = PasswordResetRequestSerializer(data=request.data) 231 231 data = PasswordResetRequestSerializer(data=request.data)
data.is_valid(raise_exception=True) 232 232 data.is_valid(raise_exception=True)
get_object_or_404(User, email=data['email'].value).request_password_reset() 233 233 get_object_or_404(User, email=data['email'].value).request_password_reset()
return Response(status=HTTP_204_NO_CONTENT) 234 234 return Response(status=HTTP_204_NO_CONTENT)
235 235
236 236
@api_view(['POST']) 237 237 @api_view(['POST'])
def reset_password(request, format=None): 238 238 def reset_password(request, format=None):
""" 239 239 """
Updates user's password to new password if token is valid. 240 240 Updates user's password to new password if token is valid.
--- 241 241 ---
request_serializer: PasswordResetSerializer 242 242 request_serializer: PasswordResetSerializer
""" 243 243 """
data = PasswordResetSerializer(data=request.data) 244 244 data = PasswordResetSerializer(data=request.data)
data.is_valid(raise_exception=True) 245 245 data.is_valid(raise_exception=True)
246 246
user = User.objects.get(id=data['uid'].value) 247 247 user = User.objects.get(id=data['uid'].value)
# Check token validity. 248 248 # Check token validity.
249 249
if default_token_generator.check_token(user, data['token'].value): 250 250 if default_token_generator.check_token(user, data['token'].value):
user.set_password(data['new_password'].value) 251 251 user.set_password(data['new_password'].value)
user.save() 252 252 user.save()
else: 253 253 else:
raise ValidationError('Could not verify reset token') 254 254 raise ValidationError('Could not verify reset token')
return Response(status=HTTP_204_NO_CONTENT) 255 255 return Response(status=HTTP_204_NO_CONTENT)
256 256
257 257
class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin): 258 258 class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin):
queryset = Flashcard.objects.all() 259 259 queryset = Flashcard.objects.all()
serializer_class = FlashcardSerializer 260 260 serializer_class = FlashcardSerializer
permission_classes = [IsAuthenticated, IsEnrolledInAssociatedSection] 261 261 permission_classes = [IsAuthenticated, IsEnrolledInAssociatedSection]
# Override create in CreateModelMixin 262 262 # Override create in CreateModelMixin
def create(self, request, *args, **kwargs): 263 263 def create(self, request, *args, **kwargs):
serializer = FlashcardSerializer(data=request.data) 264 264 serializer = FlashcardSerializer(data=request.data)
serializer.is_valid(raise_exception=True) 265 265 serializer.is_valid(raise_exception=True)
data = serializer.validated_data 266 266 data = serializer.validated_data
if not request.user.is_in_section(data['section']): 267 267 if not request.user.is_in_section(data['section']):
raise PermissionDenied('The user is not enrolled in that section') 268 268 raise PermissionDenied('The user is not enrolled in that section')
data['author'] = request.user 269 269 data['author'] = request.user
flashcard = Flashcard.objects.create(**data) 270 270 flashcard = Flashcard.objects.create(**data)
self.perform_create(flashcard) 271 271 self.perform_create(flashcard)
notify_new_card(flashcard) 272 272 notify_new_card(flashcard)
headers = self.get_success_headers(data) 273 273 headers = self.get_success_headers(data)
request.user.pull(flashcard) 274 274 request.user.pull(flashcard)
response_data = FlashcardSerializer(flashcard).data 275 275 response_data = FlashcardSerializer(flashcard).data
276 276
return Response(response_data, status=HTTP_201_CREATED, headers=headers) 277 277 return Response(response_data, status=HTTP_201_CREATED, headers=headers)
278 278
@detail_route(methods=['POST']) 279 279 @detail_route(methods=['POST'])
def unhide(self, request, pk): 280 280 def unhide(self, request, pk):
""" 281 281 """
Unhide the given card 282 282 Unhide the given card
--- 283 283 ---
view_mocker: flashcards.api.mock_no_params 284 284 view_mocker: flashcards.api.mock_no_params
""" 285 285 """
hide = get_object_or_404(FlashcardHide, user=request.user, flashcard=self.get_object()) 286 286 hide = get_object_or_404(FlashcardHide, user=request.user, flashcard=self.get_object())
hide.delete() 287 287 hide.delete()
return Response(status=HTTP_204_NO_CONTENT) 288 288 return Response(status=HTTP_204_NO_CONTENT)
289 289
@detail_route(methods=['POST']) 290 290 @detail_route(methods=['POST'])
def report(self, request, pk): 291 291 def report(self, request, pk):
""" 292 292 """
Hide the given card 293 293 Hide the given card
--- 294 294 ---
view_mocker: flashcards.api.mock_no_params 295 295 view_mocker: flashcards.api.mock_no_params
""" 296 296 """
self.get_object().report(request.user) 297 297 self.get_object().report(request.user)
return Response(status=HTTP_204_NO_CONTENT) 298 298 return Response(status=HTTP_204_NO_CONTENT)
299 299
hide = report 300 300 hide = report
301 301
@detail_route(methods=['POST']) 302 302 @detail_route(methods=['POST'])
def pull(self, request, pk): 303 303 def pull(self, request, pk):
""" 304 304 """
Pull a card from the live feed into the user's deck. 305 305 Pull a card from the live feed into the user's deck.
--- 306 306 ---
view_mocker: flashcards.api.mock_no_params 307 307 view_mocker: flashcards.api.mock_no_params
""" 308 308 """
try: 309 309 try:
request.user.pull(self.get_object()) 310 310 request.user.pull(self.get_object())
return Response(status=HTTP_204_NO_CONTENT) 311 311 return Response(status=HTTP_204_NO_CONTENT)
except IntegrityError, e: 312 312 except IntegrityError, e:
raise ValidationError('Cannot pull a card already in deck') 313 313 raise ValidationError('Cannot pull a card already in deck')
314 314
@detail_route(methods=['POST']) 315 315 @detail_route(methods=['POST'])
def unpull(self, request, pk): 316 316 def unpull(self, request, pk):
""" 317 317 """
Unpull a card from the user's deck 318 318 Unpull a card from the user's deck
--- 319 319 ---
view_mocker: flashcards.api.mock_no_params 320 320 view_mocker: flashcards.api.mock_no_params
""" 321 321 """
user = request.user 322 322 user = request.user
flashcard = self.get_object() 323 323 flashcard = self.get_object()
user.unpull(flashcard) 324 324 user.unpull(flashcard)
return Response(status=HTTP_204_NO_CONTENT) 325 325 return Response(status=HTTP_204_NO_CONTENT)
326 326
def partial_update(self, request, *args, **kwargs): 327 327 def partial_update(self, request, *args, **kwargs):
""" 328 328 """
Edit settings related to a card for the user. 329 329 Edit settings related to a card for the user.
--- 330 330 ---
request_serializer: FlashcardUpdateSerializer 331 331 request_serializer: FlashcardUpdateSerializer
""" 332 332 """
user = request.user 333 333 user = request.user
flashcard = self.get_object() 334 334 flashcard = self.get_object()
data = FlashcardUpdateSerializer(data=request.data) 335 335 data = FlashcardUpdateSerializer(data=request.data)
data.is_valid(raise_exception=True) 336 336 data.is_valid(raise_exception=True)
new_flashcard = data.validated_data 337 337 new_flashcard = data.validated_data
new_flashcard = flashcard.edit(user, new_flashcard) 338 338 new_flashcard = flashcard.edit(user, new_flashcard)
return Response(FlashcardSerializer(new_flashcard, context={'user': request.user}).data, status=HTTP_200_OK) 339 339 return Response(FlashcardSerializer(new_flashcard, context={'user': request.user}).data, status=HTTP_200_OK)
340 340
341 341
class UserFlashcardQuizViewSet(GenericViewSet, CreateModelMixin, UpdateModelMixin): 342 342 class UserFlashcardQuizViewSet(GenericViewSet, CreateModelMixin, UpdateModelMixin):
permission_classes = [IsAuthenticated, IsFlashcardReviewer] 343 343 permission_classes = [IsAuthenticated, IsFlashcardReviewer]
queryset = UserFlashcardQuiz.objects.all() 344 344 queryset = UserFlashcardQuiz.objects.all()
345 345
def get_serializer_class(self): 346 346 def get_serializer_class(self):
if self.request.method == 'POST': 347 347 if self.request.method == 'POST':
return QuizRequestSerializer 348 348 return QuizRequestSerializer
return QuizAnswerRequestSerializer 349 349 return QuizAnswerRequestSerializer