def create_reservation(self, profile, resource, start_when, end_when): res = Reservation( profile=profile, resource=resource, start_when=start_when, end_when=end_when) res.save() return res
def test_valid_reservation_duration_with_slot_size(resource_with_opening_hours): resource_with_opening_hours.min_period = datetime.timedelta(hours=1) resource_with_opening_hours.slot_size = datetime.timedelta(minutes=30) resource_with_opening_hours.save() tz = timezone.get_current_timezone() begin = tz.localize(datetime.datetime(2115, 6, 1, 8, 0, 0)) end = begin + datetime.timedelta(hours=2, minutes=30) reservation = Reservation(resource=resource_with_opening_hours, begin=begin, end=end) reservation.clean()
def check_price(self, request): # validate incoming Order and OrderLine data write_serializer = PriceEndpointOrderSerializer(data=request.data) write_serializer.is_valid(raise_exception=True) # build Order and OrderLine objects in memory only order_data = write_serializer.validated_data order_lines_data = order_data.pop('order_lines') begin = order_data.pop('begin') end = order_data.pop('end') order = Order(**order_data) order_lines = [ OrderLine(order=order, **data) for data in order_lines_data ] # store the OrderLine objects in the Order object so that we can use # those when calculating prices order._in_memory_order_lines = order_lines # order line price calculations need a dummy reservation from which they # get begin and end times from reservation = Reservation(begin=begin, end=end) order.reservation = reservation # serialize the in-memory objects read_serializer = PriceEndpointOrderSerializer(order) order_data = read_serializer.data order_data['order_lines'] = [ OrderLineSerializer(ol).data for ol in order_lines ] order_data.update({'begin': begin, 'end': end}) return Response(order_data, status=200)
def test_invalid_reservation_duration_with_slot_size(resource_with_opening_hours): activate('en') resource_with_opening_hours.min_period = datetime.timedelta(hours=1) resource_with_opening_hours.slot_size = datetime.timedelta(minutes=30) resource_with_opening_hours.save() tz = timezone.get_current_timezone() begin = tz.localize(datetime.datetime(2115, 6, 1, 8, 0, 0)) end = begin + datetime.timedelta(hours=2, minutes=45) reservation = Reservation(resource=resource_with_opening_hours, begin=begin, end=end) with pytest.raises(ValidationError) as error: reservation.clean() assert error.value.code == 'invalid_time_slot'
def validate(self, data): reservation = self.instance request_user = self.context['request'].user # this check is probably only needed for PATCH try: resource = data['resource'] except KeyError: resource = reservation.resource if not resource.can_make_reservations(request_user): raise PermissionDenied() if data['end'] < timezone.now(): raise ValidationError(_('You cannot make a reservation in the past')) # normal users cannot make reservations for other people if not resource.is_admin(request_user): data.pop('user', None) # Check user specific reservation restrictions relating to given period. resource.validate_reservation_period(reservation, request_user, data=data) if 'comments' in data: if not resource.is_admin(request_user): raise ValidationError(dict(comments=_('Only allowed to be set by staff members'))) # Mark begin of a critical section. Subsequent calls with this same resource will block here until the first # request is finished. This is needed so that the validations and possible reservation saving are # executed in one block and concurrent requests cannot be validated incorrectly. Resource.objects.select_for_update().get(pk=resource.pk) # Check maximum number of active reservations per user per resource. # Only new reservations are taken into account ie. a normal user can modify an existing reservation # even if it exceeds the limit. (one that was created via admin ui for example). if reservation is None: resource.validate_max_reservations_per_user(request_user) # Run model clean data.pop('staff_event', None) instance = Reservation(**data) instance.clean(original_reservation=reservation) return data
def test_admin_may_bypass_min_period(resource_with_opening_hours, user): """ Admin users should be able to bypass min_period, and their minimum reservation time should be limited by slot_size """ activate('en') # min_period is bypassed respecting slot_size restriction resource_with_opening_hours.min_period = datetime.timedelta(hours=1) resource_with_opening_hours.slot_size = datetime.timedelta(minutes=30) resource_with_opening_hours.save() tz = timezone.get_current_timezone() begin = tz.localize(datetime.datetime(2115, 6, 1, 8, 0, 0)) end = begin + datetime.timedelta(hours=0, minutes=30) UnitAuthorization.objects.create( subject=resource_with_opening_hours.unit, level=UnitAuthorizationLevel.admin, authorized=user, ) reservation = Reservation(resource=resource_with_opening_hours, begin=begin, end=end, user=user) reservation.clean() # min_period is bypassed and slot_size restriction is violated resource_with_opening_hours.slot_size = datetime.timedelta(minutes=25) resource_with_opening_hours.save() with pytest.raises(ValidationError) as error: reservation.clean() assert error.value.code == 'invalid_time_slot'
def create_respa_outlook_reservation(self, appointment, reservation, email): if not appointment: return if not reservation: if not email: return reservation = Reservation(resource=self.resource, begin=appointment.start, end=appointment.end, reserver_email_address=email, state=Reservation.CONFIRMED) reservation.clean() reservation.save() ret = send_respa_mail(email_address=email, subject="Reservation created", body="Reservation via outlook created") print(ret[0], ret[1]) RespaOutlookReservation( name='%s (%s)' % (reservation.reserver_email_address, self.resource.name), exchange_id=appointment.id, exchange_changekey=appointment.changekey, reservation=reservation).save()
def validate(self, data): reservation = self.instance request_user = self.context['request'].user # this check is probably only needed for PATCH try: resource = data['resource'] except KeyError: resource = reservation.resource if not resource.can_make_reservations(request_user): raise PermissionDenied() if data['end'] < timezone.now(): raise ValidationError( _('You cannot make a reservation in the past')) if not resource.is_admin(request_user): reservable_before = resource.get_reservable_before() if reservable_before and data['begin'] >= reservable_before: raise ValidationError( _('The resource is reservable only before %(datetime)s' % {'datetime': reservable_before})) # normal users cannot make reservations for other people if not resource.is_admin(request_user): data.pop('user', None) # Check user specific reservation restrictions relating to given period. resource.validate_reservation_period(reservation, request_user, data=data) if 'comments' in data: if not resource.is_admin(request_user): raise ValidationError( dict( comments=_('Only allowed to be set by staff members'))) if 'access_code' in data: if data['access_code'] == None: data['access_code'] = '' access_code_enabled = resource.is_access_code_enabled() if not access_code_enabled and data['access_code']: raise ValidationError( dict(access_code=_( 'This field cannot have a value with this resource'))) if access_code_enabled and reservation and data[ 'access_code'] != reservation.access_code: raise ValidationError( dict(access_code=_('This field cannot be changed'))) # Mark begin of a critical section. Subsequent calls with this same resource will block here until the first # request is finished. This is needed so that the validations and possible reservation saving are # executed in one block and concurrent requests cannot be validated incorrectly. Resource.objects.select_for_update().get(pk=resource.pk) # Check maximum number of active reservations per user per resource. # Only new reservations are taken into account ie. a normal user can modify an existing reservation # even if it exceeds the limit. (one that was created via admin ui for example). if reservation is None: resource.validate_max_reservations_per_user(request_user) # Run model clean data.pop('staff_event', None) instance = Reservation(**data) try: instance.clean(original_reservation=reservation) except DjangoValidationError as exc: # Convert Django ValidationError to DRF ValidationError so that in the response # field specific error messages are added in the field instead of in non_field_messages. if not hasattr(exc, 'error_dict'): raise ValidationError(exc) error_dict = {} for key, value in exc.error_dict.items(): error_dict[key] = [error.message for error in value] raise ValidationError(error_dict) return data
def test_reservation(self): r1a = Resource.objects.get(id='r1a') r1b = Resource.objects.get(id='r1b') tz = timezone.get_current_timezone() begin = tz.localize(datetime.datetime(2116, 6, 1, 8, 0, 0)) end = begin + datetime.timedelta(hours=2) reservation = Reservation.objects.create(resource=r1a, begin=begin, end=end) reservation.clean() # Attempt overlapping reservation with self.assertRaises(ValidationError): reservation = Reservation(resource=r1a, begin=begin, end=end) reservation.clean() valid_begin = begin + datetime.timedelta(hours=3) valid_end = end + datetime.timedelta(hours=3) # Attempt incorrectly aligned begin time with self.assertRaises(ValidationError): reservation = Reservation(resource=r1a, begin=valid_begin + datetime.timedelta(minutes=1), end=valid_end) reservation.clean() # Attempt incorrectly aligned end time with self.assertRaises(ValidationError): reservation = Reservation(resource=r1a, begin=valid_begin, end=valid_end + datetime.timedelta(minutes=1)) reservation.clean() # Attempt reservation that starts before the resource opens # Should not raise an exception as this check isn't included in model clean reservation = Reservation( resource=r1a, begin=begin - datetime.timedelta(hours=1), end=begin ) reservation.clean() begin = tz.localize(datetime.datetime(2116, 6, 1, 16, 0, 0)) end = begin + datetime.timedelta(hours=2) # Make a reservation that ends when the resource closes reservation = Reservation(resource=r1a, begin=begin, end=end) reservation.clean() # Attempt reservation that ends after the resource closes # Should not raise an exception as this check isn't included in model clean reservation = Reservation(resource=r1a, begin=begin, end=end + datetime.timedelta(hours=1)) reservation.clean()
def create_item(self, item): reservation = Reservation() reservation.resource_id = self.__resource_id reservation.reserver_email_address = item.reserver_email_address reservation.reserver_phone_number = item.reserver_phone_number reservation.reserver_name = item.reserver_name reservation.begin = item.begin reservation.end = item.end reservation._from_o365_sync = True reservation.set_state(Reservation.CONFIRMED, None) reservation.save() return reservation.id, reservation_change_key(item)
def create(self, request): stack = request.data.pop('reservation_stack') if 'resource' in stack[0]: stack[0].pop('resource') if len(stack) > 100: return JsonResponse( { 'status': 'false', 'recurring_validation_error': _('Reservation failed. Too many reservations at once.'), }, status=400) data = {**request.data} data.update({'user': request.user}) resource_id = data.get('resource') try: for key in stack: begin = key.get('begin') end = key.get('end') if begin is None or end is None: return JsonResponse( { 'status': 'false', 'recurring_validation_error': _('Reservation failed. Begin or end time is missing.' ) }, status=400) reservations = [] for key in stack: begin = parse_datetime(key.get('begin')) end = parse_datetime(key.get('end')) try: resource = Resource.objects.get(id=resource_id) except: raise data['resource'] = resource res = Reservation(**data) res.begin = begin res.end = end if resource.validate_reservation_period(res, res.user): return JsonResponse( { 'status': 'false', 'recurring_validation_error': _('Reservation failed. Make sure reservation period is correct.' ) }, status=400) if resource.validate_max_reservations_per_user(res.user): return JsonResponse( { 'status': 'false', 'recurring_validation_error': _('Reservation failed. Too many reservations at once.' ) }, status=400) if resource.check_reservation_collision(begin, end, res): return JsonResponse( { 'status': 'false', 'recurring_validation_error': _('Reservation failed. Overlap with existing reservations.' ) }, status=400) reservations.append(res) reservation_dates_context = {'dates': []} """ {% if bulk_email_context is defined %} {% for date in bulk_email_context['dates'] %} Alku: {{ date.get('begin') }} Loppu {{ date.get('end') }} {% endfor %} {% endif %} """ for res in reservations: res.state = 'confirmed' if resource.validate_reservation_period(res, res.user): return JsonResponse( { 'status': 'false', 'recurring_validation_error': _('Reservation failed. Make sure reservation period is correct.' ) }, status=400) if resource.validate_max_reservations_per_user(res.user): return JsonResponse( { 'status': 'false', 'recurring_validation_error': _('Reservation failed. Too many reservations at once.' ) }, status=400) if resource.check_reservation_collision(begin, end, res): return JsonResponse( { 'status': 'false', 'recurring_validation_error': _('Reservation failed. Overlap with existing reservations.' ) }, status=400) res.save() reservation_dates_context['dates'].append({ 'begin': dateparser(reservations[0].begin, res.begin), 'end': dateparser(reservations[0].end, res.end) }) reservation_dates_context.update({ 'first_reservation': { 'begin': dateparser(reservations[0].begin, reservations[0].begin), 'end': dateparser(reservations[0].end, reservations[0].end) } }) reservation_dates_context.update({ 'last_reservation': { 'begin': dateparser(reservations[0].begin, reservations[len(reservations) - 1].begin), 'end': dateparser(reservations[0].end, reservations[len(reservations) - 1].end) } }) res = reservations[0] url = ''.join([ request.is_secure() and 'https' or 'http', get_current_site(request).domain, '/v1/', 'reservation/', str(res.id), '/' ]) ical_file = build_reservations_ical_file(reservations) attachment = ('reservation.ics', ical_file, 'text/calendar') res.send_reservation_mail( NotificationType.RESERVATION_BULK_CREATED, action_by_official=res.user.is_staff, attachments=[attachment], extra_context=reservation_dates_context) return JsonResponse(data={ **ReservationSerializer(context={ 'request': self.request if self.request else request }).to_representation(res) }, status=200) except Exception as ex: return JsonResponse( { 'status': 'false', 'recurring_validation_error': 'Reservation failed. Try again later.' }, status=500)
def validate(self, data): reservation = self.instance request_user = self.context['request'].user # this check is probably only needed for PATCH try: resource = data['resource'] except KeyError: resource = reservation.resource data.update({'resource': resource}) if not data.get('begin', None): data.update({'begin': reservation.begin}) if not data.get('end', None): data.update({'end': reservation.end}) if not resource.can_make_reservations(request_user): raise PermissionDenied( _('You are not allowed to make reservations in this resource.') ) if data['end'] < timezone.now(): raise ValidationError( _('You cannot make a reservation in the past')) if resource.min_age and is_underage(request_user, resource.min_age): raise PermissionDenied( _('You have to be over %s years old to reserve this resource' % (resource.min_age))) if resource.max_age and is_overage(request_user, resource.max_age): raise PermissionDenied( _('You have to be under %s years old to reserve this resource' % (resource.max_age))) is_resource_admin = resource.is_admin(request_user) is_resource_manager = resource.is_manager(request_user) if not isinstance(request_user, AnonymousUser): if request_user.preferred_language is None: request_user.preferred_language = settings.LANGUAGES[0][0] request_user.save() if not resource.can_ignore_opening_hours(request_user): reservable_before = resource.get_reservable_before() if reservable_before and data['begin'] >= reservable_before: raise ValidationError( _('The resource is reservable only before %(datetime)s' % {'datetime': reservable_before})) reservable_after = resource.get_reservable_after() if reservable_after and data['begin'] < reservable_after: raise ValidationError( _('The resource is reservable only after %(datetime)s' % {'datetime': reservable_after})) # Check given home municipality is included in resource's home municipality set if 'home_municipality' in data: included = resource.get_included_home_municipality_names() found = False for municipality in included: if data['home_municipality'].id == municipality['id']: found = True break if not found: raise ValidationError( _('Home municipality has to be one of the included home municipality options' )) # normal users cannot make reservations for other people if not resource.can_create_reservations_for_other_users(request_user): data.pop('user', None) # Check user specific reservation restrictions relating to given period. resource.validate_reservation_period(reservation, request_user, data=data) reserver_phone_number = data.get('reserver_phone_number', '') if reserver_phone_number.startswith('+'): if not region_code_for_country_code( phonenumbers.parse(reserver_phone_number).country_code): raise ValidationError( dict(reserver_phone_number=_('Invalid country code'))) if data.get('staff_event', False): if not resource.can_create_staff_event(request_user): raise ValidationError( dict(staff_event=_( 'Only allowed to be set by resource managers'))) if 'type' in data: if (data['type'] != Reservation.TYPE_NORMAL and not resource.can_create_special_type_reservation( request_user)): raise ValidationError({ 'type': _('You are not allowed to make a reservation of this type') }) if 'comments' in data: if not resource.can_comment_reservations(request_user): raise ValidationError( dict( comments=_('Only allowed to be set by staff members'))) if 'access_code' in data: if data['access_code'] is None: data['access_code'] = '' access_code_enabled = resource.is_access_code_enabled() if not access_code_enabled and data['access_code']: raise ValidationError( dict(access_code=_( 'This field cannot have a value with this resource'))) if access_code_enabled and reservation and data[ 'access_code'] != reservation.access_code: raise ValidationError( dict(access_code=_('This field cannot be changed'))) # Mark begin of a critical section. Subsequent calls with this same resource will block here until the first # request is finished. This is needed so that the validations and possible reservation saving are # executed in one block and concurrent requests cannot be validated incorrectly. Resource.objects.select_for_update().get(pk=resource.pk) # Check maximum number of active reservations per user per resource. # Only new reservations are taken into account ie. a normal user can modify an existing reservation # even if it exceeds the limit. (one that was created via admin ui for example). if reservation is None and not isinstance(request_user, AnonymousUser): resource.validate_max_reservations_per_user(request_user) # Run model clean instance = Reservation(**data) try: instance.clean(original_reservation=reservation, user=request_user) except DjangoValidationError as exc: # Convert Django ValidationError to DRF ValidationError so that in the response # field specific error messages are added in the field instead of in non_field_messages. if not hasattr(exc, 'error_dict'): raise ValidationError(exc) error_dict = {} for key, value in exc.error_dict.items(): error_dict[key] = [error.message for error in value] raise ValidationError(error_dict) return data
def save_reservation( data, hvara_reservations, user_objects_by_email, missing_users, catering_provider, ): if not data['resource']['object']: return origin_id = 'hvara:%s' % data['VarausId'] res = hvara_reservations.get(origin_id) try: res = Reservation.objects.get(origin_id=origin_id) except Reservation.DoesNotExist: res = Reservation(origin_id=origin_id) res.begin = local_tz.localize( datetime.strptime(data['AlkuAika'], "%Y-%m-%dT%H:%M:%S")) res.end = local_tz.localize( datetime.strptime(data['LoppuAika'], "%Y-%m-%dT%H:%M:%S")) res.event_subject = data['Tilaisuus'].strip() res.host_name = data['Isanta'].strip() res.created_at = local_tz.localize( datetime.strptime(data['Perustettu'], "%Y-%m-%dT%H:%M:%S")) res.modified_at = local_tz.localize( datetime.strptime(data['Muutettu'], "%Y-%m-%dT%H:%M:%S")) res.number_of_participants = int(data['OsallistujaLkm']) if res.number_of_participants < 0: res.number_of_participants = None res.event_description = data['Selite'] or '' if data['VarusteluSelite'] or data['equipment']: s = 'Varustelu:\n' items = [] if data['equipment']: items.append('\n'.join(['- ' + x for x in data['equipment']])) if data['VarusteluSelite']: items.append(data['VarusteluSelite']) s += '\n\n'.join(items) if res.event_description and not res.event_description.endswith('\n'): s = '\n' + s res.event_description += s res.participants = '\n'.join(data['attendees']) if data['OsallistujaSelite']: if res.participants: res.participants += '\n\n' res.participants += data['OsallistujaSelite'] res.comments = 'Siirretty vanhasta huonevarausjärjestelmästä' res.resource = data['resource']['object'] user = data['user'] if user is not None: res.reserver_name = user['Nimi'] email = user['Mail'].strip().lower() res.reserver_email_address = email res.reserver_phone_number = user['Puhelin'] if email not in user_objects_by_email: try: u_obj = User.objects.get(email=email) except User.DoesNotExist: print("%s does not exist" % email) u_obj = None user_objects_by_email[email] = u_obj res.user = user_objects_by_email[email] if not res.user: missing_users.setdefault(email, 0) missing_users[email] += 1 else: res.reserver_name = '' res.reserver_email_address = '' res.reserver_phone_number = '' res.user = None res._skip_notifications = True print(res) res.save() if data['catering'] or data['TarjoiluSelite']: order = CateringOrder(reservation=res, provider=catering_provider) invoicing_data = [data.get(f) for f in PAYER_FIELDS] order.invoicing_data = '\n'.join( [val.strip() for val in invoicing_data if val]) message_data = [] if data['catering']: message_data.append('\n'.join(data['catering'])) if data['TarjoiluSelite']: message_data.append(data['TarjoiluSelite'].strip()) order.message = '\n\n'.join(message_data) order.save()
def save_reservation( data, hvara_reservations, user_objects_by_email, missing_users, catering_provider, ): if not data['resource']['object']: return origin_id = 'hvara:%s' % data['VarausId'] res = hvara_reservations.get(origin_id) try: res = Reservation.objects.get(origin_id=origin_id) except Reservation.DoesNotExist: res = Reservation(origin_id=origin_id) res.begin = local_tz.localize(datetime.strptime(data['AlkuAika'], "%Y-%m-%dT%H:%M:%S")) res.end = local_tz.localize(datetime.strptime(data['LoppuAika'], "%Y-%m-%dT%H:%M:%S")) res.event_subject = data['Tilaisuus'].strip() res.host_name = data['Isanta'].strip() res.created_at = local_tz.localize(datetime.strptime(data['Perustettu'], "%Y-%m-%dT%H:%M:%S")) res.modified_at = local_tz.localize(datetime.strptime(data['Muutettu'], "%Y-%m-%dT%H:%M:%S")) res.number_of_participants = int(data['OsallistujaLkm']) if res.number_of_participants < 0: res.number_of_participants = None res.event_description = data['Selite'] or '' if data['VarusteluSelite'] or data['equipment']: s = 'Varustelu:\n' items = [] if data['equipment']: items.append('\n'.join(['- ' + x for x in data['equipment']])) if data['VarusteluSelite']: items.append(data['VarusteluSelite']) s += '\n\n'.join(items) if res.event_description and not res.event_description.endswith('\n'): s = '\n' + s res.event_description += s res.participants = '\n'.join(data['attendees']) if data['OsallistujaSelite']: if res.participants: res.participants += '\n\n' res.participants += data['OsallistujaSelite'] res.comments = 'Siirretty vanhasta huonevarausjärjestelmästä' res.resource = data['resource']['object'] user = data['user'] if user is not None: res.reserver_name = user['Nimi'] email = user['Mail'].strip().lower() res.reserver_email_address = email res.reserver_phone_number = user['Puhelin'] if email not in user_objects_by_email: try: u_obj = User.objects.get(email=email) except User.DoesNotExist: print("%s does not exist" % email) u_obj = None user_objects_by_email[email] = u_obj res.user = user_objects_by_email[email] if not res.user: missing_users.setdefault(email, 0) missing_users[email] += 1 else: res.reserver_name = '' res.reserver_email_address = '' res.reserver_phone_number = '' res.user = None res._skip_notifications = True print(res) res.save() if data['catering'] or data['TarjoiluSelite']: order = CateringOrder(reservation=res, provider=catering_provider) invoicing_data = [data.get(f) for f in PAYER_FIELDS] order.invoicing_data = '\n'.join([val.strip() for val in invoicing_data if val]) message_data = [] if data['catering']: message_data.append('\n'.join(data['catering'])) if data['TarjoiluSelite']: message_data.append(data['TarjoiluSelite'].strip()) order.message = '\n\n'.join(message_data) order.save()
def validate(self, data): reservation = self.instance request_user = self.context['request'].user # this check is probably only needed for PATCH try: resource = data['resource'] except KeyError: resource = reservation.resource if not resource.can_make_reservations(request_user): raise PermissionDenied() if data['end'] < timezone.now(): raise ValidationError(_('You cannot make a reservation in the past')) if not resource.is_admin(request_user): reservable_before = resource.get_reservable_before() if reservable_before and data['begin'] >= reservable_before: raise ValidationError(_('The resource is reservable only before %(datetime)s' % {'datetime': reservable_before})) # normal users cannot make reservations for other people if not resource.is_admin(request_user): data.pop('user', None) # Check user specific reservation restrictions relating to given period. resource.validate_reservation_period(reservation, request_user, data=data) if 'comments' in data: if not resource.is_admin(request_user): raise ValidationError(dict(comments=_('Only allowed to be set by staff members'))) if 'access_code' in data: if data['access_code'] == None: data['access_code'] = '' access_code_enabled = resource.is_access_code_enabled() if not access_code_enabled and data['access_code']: raise ValidationError(dict(access_code=_('This field cannot have a value with this resource'))) if access_code_enabled and reservation and data['access_code'] != reservation.access_code: raise ValidationError(dict(access_code=_('This field cannot be changed'))) # Mark begin of a critical section. Subsequent calls with this same resource will block here until the first # request is finished. This is needed so that the validations and possible reservation saving are # executed in one block and concurrent requests cannot be validated incorrectly. Resource.objects.select_for_update().get(pk=resource.pk) # Check maximum number of active reservations per user per resource. # Only new reservations are taken into account ie. a normal user can modify an existing reservation # even if it exceeds the limit. (one that was created via admin ui for example). if reservation is None: resource.validate_max_reservations_per_user(request_user) # Run model clean data.pop('staff_event', None) instance = Reservation(**data) try: instance.clean(original_reservation=reservation) except DjangoValidationError as exc: # Convert Django ValidationError to DRF ValidationError so that in the response # field specific error messages are added in the field instead of in non_field_messages. if not hasattr(exc, 'error_dict'): raise ValidationError(exc) error_dict = {} for key, value in exc.error_dict.items(): error_dict[key] = [error.message for error in value] raise ValidationError(error_dict) return data