# -*- coding: utf-8 -*- from __future__ import absolute_import from django.db import IntegrityError from rest_framework.exceptions import ValidationError, APIException from rest_framework.response import Response from rest_framework.views import exception_handler def web_sight_exception_handler(exc, context): """ This is a custom Django exception handler that handles all exceptions thrown by the Web Sight REST API. :param exc: The exception that was thrown. :param context: The context in which the exception was thrown. :return: A response to return to the requesting user. """ if isinstance(exc, IntegrityError): return handle_integrity_error(exc, context) elif isinstance(exc, ValidationError): return handle_validation_error(exc, context) elif isinstance(exc, WsRestNonFieldException): return handle_non_field_error(exc, context) elif isinstance(exc, APIException): return handle_api_exception(exc, context) else: return None def handle_api_exception(exc, context): """ Handle the given APIException. :param exc: The exception that was thrown. :param context: The context in which the exception was thrown. :return: A Django Response object. """ response = exception_handler(exc, context) response.data = { "status_code": response.status_code, "message": "Exception thrown", "detail": exc.detail, "error_fields": [], } return response def handle_integrity_error(exc, context): """ Handle the given IntegrityError and return a response. :param exc: The exception that was thrown. :param context: The context in which the exception was thrown. :return: A Django Response object. """ response = Response(status=409) response.data = { "status_code": 409, "message": "That object already exists.", "detail": exc.message.split("\n")[1], "error_fields": [], } return response def handle_validation_error(exc, context): """ Handle the given ValidationError and return a response. :param exc: The exception that was thrown. :param context: The context in which the exception was thrown. :return: A Django Response object. """ response = exception_handler(exc, context) response.status_code = 400 response.data = { "status_code": 400, "message": "Invalid input received.", "detail": "There was an error with the data that you submitted. Please check your input and try again.", "error_fields": exc.get_full_details(), } return response def handle_non_field_error(exc, context): """ Handle the given WsRestNonFieldException and return a response. :param exc: The exception that was thrown. :param context: The context in which the exception was thrown. :return: A Django Response object. """ response = exception_handler(exc, context) response.status_code = 400 response.data = { "status_code": 400, "message": "Invalid input received.", "detail": exc.detail, "error_fields": [], } return response class WsBaseApiError(object): """ This is a base exception for all errors that are thrown by the Web Sight REST API. """ #These are the possible error types DEFAULT_ERROR_TYPE = "default" NON_FIELD_ERROR_TYPE = "non_field_error" FIELD_ERROR_TYPE = "field_error" def __init__(self, error_message="Unknown error", error_type="default"): self.error_message = error_message self.error_type = error_type def to_dict(self): self_dict = { "error_message": self.error_message, "error_type": self.error_type } return self_dict class WsApiFieldError(WsBaseApiError): """ This is an exception for errors that are related to specific fields. """ def __init__(self, field=None, **kwargs): kwargs["error_type"] = self.FIELD_ERROR_TYPE super(WsApiFieldError, self).__init__(**kwargs) self.field = field def to_dict(self): self_dict = super(WsApiFieldError, self).to_dict() self_dict["field"] = self.field return self_dict class WsApiNonFieldError(WsBaseApiError): """ This is an exception for errors that are not related to specific fields. """ def __init__(self, **kwargs): kwargs["error_type"] = self.NON_FIELD_ERROR_TYPE super(WsApiNonFieldError, self).__init__(**kwargs) class WsBaseRestException(APIException): """ This is the base rest exception, subclassed to make things easier """ def __init__(self, error_message, error_type): super(WsBaseRestException, self).__init__(error_message, error_type) self.errors_dict = { "errors": [], } self.status_code = 400 def add_error(self, error_message, error_type, field=None): """ This will add an error to the exception, based on the provided input :param error_message: The message of the error :param error_type: The type of the error, check WsBaseExceptionError for possible types :param field: The optional field that this error is related to """ if error_type == WsBaseApiError.FIELD_ERROR_TYPE: error = WsApiFieldError(error_message=error_message, field=field) elif error_type == WsBaseApiError.NON_FIELD_ERROR_TYPE: error = WsApiNonFieldError(error_message=error_message) else: error = WsBaseApiError(error_message=error_message, error_type=error_type) self.errors_dict["errors"].append(error.to_dict()) def add_data(self, key, value): """ This will add a random key and value to the exception structure """ self.errors_dict[key] = value def to_json(self): return self.errors_dict class WsRestFieldException(WsBaseRestException): """ This exception is raised for a specific field """ def __init__(self, error_message, field): super(WsRestFieldException, self).__init__(error_message, WsBaseApiError.FIELD_ERROR_TYPE) self.add_error(error_message, WsBaseApiError.FIELD_ERROR_TYPE, field) class WsRestNonFieldException(WsBaseRestException): """ This exception is raised for errors not related to a field """ def __init__(self, error_message): super(WsRestNonFieldException, self).__init__(error_message, WsBaseApiError.NON_FIELD_ERROR_TYPE) self.add_error(error_message, WsBaseApiError.NON_FIELD_ERROR_TYPE)