customize error message in django rest - python

i want to customize the error response from django rest framework.
this is slandered error response from django rest..
{
"style": [
"This field is required."
],
"code": [
"code must be in 60 to 120 chars."
]
}
i want to customize it like....
{
"style": [
"error_code":"20"
"error_message":"This field is required."
],
"code": [
"error_code":"22"
"error_message":"code must be in 60 to 120 chars."
]
}

I had the same problem in showing errors. First of all you should know that you can't use key-value pairs in a list (i.e. "style": ["error_code": "20", "error_message": "This field is required."]) and you should change your type to dictionary if you want to convert your error to this custom type. One easy way is that you can have your own custom exception handler and then tell rest framework to use that exception handler to customize your exceptions. First you should add the following line in your project settings.py:
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'utils.exception_handlers.custom_exception_handler', # You should add the path to your custom exception handler here.
}
Which tells that all of the exceptions should pass through this handler. After that you should add python file and add your codes in it (use this file path in your settings.py which mentioned before). In following code you can see an example of this handler:
from rest_framework.exceptions import ErrorDetail
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
custom_data = {}
if isinstance(response.data, dict):
for key, value in response.data.items():
if value and isinstance(value, list) and isinstance(value[0], ErrorDetail):
custom_response[key] = {
"error_message": str(value[0]),
"error_code": response.status_code # or any custom code that you need
}
else:
break
if custom_data:
response.data = custom_data
return response
Note: This was a quick example and you should test your APIs to make sure that everything works.

Related

Django: settings.DATABASES is improperly configured

I know I am not the first person to ask this question, but I still couldn't find an answer for my situation.
I have a Django environment that works very well, I know I only have one (1) settings.py file and I know that my environment accesses it correctly.
I recently added a new endpoint to my project. I defined its URL in urls.py
urlpatterns = [
...
url(PREFIX + r"^v1/alerts/debug$", alerts_views_v1.debug_trigger_alert_notification),
...
]
It is connected to a method that is in a the file alerts/views_v1.py but is not in any particular class:
#api_view(["POST"])
def debug_trigger_alert_notification(request):
"""
Trigger an Alert. i.e. Create an Alertnotification.
This function is meant to be called by the scheduler service.
"""
workspace_slug = request.data.pop("workspace")
with in_database(workspace_slug, write=True):
serializer = AlertNotificationSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response()
When I send a request to this URL, I receive the following 500 error:
ImproperlyConfigured at /v1/alerts/debug
settings.DATABASES is improperly configured. Please supply the ENGINE value. Check settings documentation for more details.
In my settings.py file, my DATABASES variable seems correct (though it is built in a roundabout way):
DEFAULT_DATABASES = {
"default": { # This DB is supposed to always have the latest version of the schema described by the Django Model
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ["REFERENCE_DB_NAME"],
"USER": os.environ["DB_USER"],
"PASSWORD": os.environ["DB_PASSWORD"],
"HOST": os.environ["DB_HOST"],
"PORT": os.environ["DB_PORT"],
}
}
# Retrieve all existing workspace databases:
try:
_existing_workspace_database_names = ExistingWorkspace(
db_user=DEFAULT_DATABASES["default"]["USER"],
db_host=DEFAULT_DATABASES["default"]["HOST"],
db_password=DEFAULT_DATABASES["default"]["PASSWORD"],
).list_existing_workspaces()
except Exception as e:
log.critical("settings.py: Error retrieving list of existing databases.")
raise e
else:
log.info("settings.py: Successfully retrieved list of existing databases.")
WORKSPACE_DATABASES = {
db_name_to_slug(existing_workspace_database_name): {
"ENGINE": "django.db.backends.postgresql",
"NAME": existing_workspace_database_name,
"USER": os.environ["DB_USER"],
"PASSWORD": os.environ["DB_PASSWORD"],
"HOST": os.environ["DB_HOST"],
"PORT": os.environ["DB_PORT"],
}
for existing_workspace_database_name in _existing_workspace_database_names
}
DATABASES = {**DEFAULT_DATABASES, **WORKSPACE_DATABASES}
DATABASE_ROUTERS = ["dynamic_db_router.DynamicDbRouter"]
What can I do?

Atomic transactions in DRF action?

I am trying to get into Django atomic transactions at first time. I wonder if there is a possibility to use it something like this:
class TaskViewSet(MultiSerializerViewSet):
#transaction.atomic
#action(methods=['PATCH'], detail=True)
def move_task(self, request, pk):
# making queries, trying to update them, roll back if last one fails.
return Response("message: SUCCESS", status=_status.HTTP_200_OK)
I have searched a bit - there is some information about how to use transactions but I did not find any info if it is possible to use them with DRF.
class PayViewSet(ModelViewSet):
#action(methods=['PATCH'], detail=True)
#transaction.atomic
def approval(self, request, *args, **kwargs):
sid = transaction.savepoint()
success = something
if success:
transaction.savepoint_commit(sid)
return success_response('yes')
else:
transaction.savepoint_rollback(sid)
return error_response('no')
savepoint is optionally depending on your situation
If you need to wrap all API calls and DRF actions into transactions - you can use (ATOMIC_REQUEST) Django database settings [from Django 1.8]
DATABASES = {
"default": {
....
"ATOMIC_REQUESTS": True,
If you use a custom exception handler (DRF Issue) you may need to adjust it like below
REST_FRAMEWORK = {
....
"EXCEPTION_HANDLER": "api.exceptions.your_exception_handler",
}
import logging
from django.db import IntegrityError
from rest_framework.response import Response
from rest_framework.views import exception_handler, set_rollback
def your_exception_handler(exc, context):
"""
DRF custom exception handler. This will allow us to add useful information
to any exceptions to help out our frontend devs
"""
# Call REST framework's default exception handler first, to get the standard error response.
response = exception_handler(exc, context)
if not response and isinstance(exc, IntegrityError):
# https://github.com/encode/django-rest-framework/issues/5760
if 'duplicate key value violates unique constraint' in str(exc):
set_rollback()
msg = "Unique constraint violated: {exc}".format(exc=exc)
response = Response({"error": True, "content": msg}, status=400)
if response is None:
set_rollback()
logger.exception(exc)
response = Response({"error": True, "content": str(exc)}, status=500)
return response

Django Rest Framework custom response for validation failure

Background
I am new to Django and the rest framework, and I am trying to use the Django Rest Framework to build an API Demo of Register and Login for mobile app.
I want
I am using APIView and the ModelSerializer, and the arguments and contraints for registion are
email <required, unique>,
username <required, unique>,
password <required, unique>,
and here, my requirements focus on the exception, I want to get a custom error code to indicate that what validations(required or unique) has failed.
For example
when I send the arguments:
username="", (leaves it blank)
password=123,
email="xxx#yyy.com"
this will lead to the required validation failure, and the JSON response returns something like
"username": [
"This field may not be blank."
]
but, I want the JSON response to be something like
{
error_code: 1,
msg: "blah blah blah"
}
in this way, mobile app can do whatever it wants according to the error_code.
Problems
I have found that, inside the framework's validation implementation, validation failures(all fields validation failures) have been transformed to plain texts and packed in an array, I can not get the specific exception(such as the username required exception), and I can not generate the error_code in the response.
So, is there any way to catch the specific exception?
you can use extra_kwargs
code
class UserSerializer(ModelSerializer):
class Meta:
model = User
extra_kwargs = {"username": {"error_messages": {"required": "Give yourself a username"}}}
in you serializer's to_internal_value, you can catch ValidatonErrors and modify them.
class MySerializer(ModelSerializer):
def to_internal_value(self, data):
try:
return super().to_internal_value(data)
except serializers.ValidationError as err:
# do something with the error messages
# reraise with the modified ValidationError
raise err
http://www.django-rest-framework.org/api-guide/serializers/#overriding-serialization-and-deserialization-behavior
I have found a workaround.
username = serializers.CharField(
validators=[
UniqueValidator(
queryset=Account.objects.all(),
message=convert_dictionary_to_json_string({
'error_code': ErrorCode.parameter.value,
'msg': 'username exists',
}),
),
],
# ...other validators
)
Since we can only pass a message which is string type, in a validator, we can:
build a dictionary which containes the error message we need
convert this dictionary to JSON string
just pass the JSON string to the message field
After that, in the view code, if validation fails:
we convert the error message, which is JSON string, in the serializer.errors, to the dictionary
pack it up to the response

Django Rest Framework : How to initialise & use custom exception handler?

DRF newbie here.
I'm trying to handle all exceptions within the project through a custom exception handler. Basically, what I'm trying to do is if any serializer fails to validate the data, I want to send the corresponding error messages to my custom exception handler and reformat errors accordingly.
I've added the following to settings.py.
# DECLARATIONS FOR REST FRAMEWORK
REST_FRAMEWORK = {
'PAGE_SIZE': 20,
'EXCEPTION_HANDLER': 'main.exceptions.base_exception_handler',
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication'
)
}
But once I send an invalid parameter to any of the endpoints in the project, I still get a default error message of the DRF validator. (e.g. {u'email': [u'This field is required.']})
Errors raised on corresponding serializer's validate function, never reaches to my exception handler.
Here is an image of the Project Tree that I'm working on.
Am I missing something?
Thank you in advance.
To do that, your base_exception_handler should check when a ValidationError exception is being raised and then modify and return the custom error response.
(Note:
A serializer raises ValidationError exception if the data parameters are invalid and then 400 status is returned.)
In base_exception_handler, we will check if the exception being raised is of the type ValidationError and then modify the errors format and return that modified errors response.
from rest_framework.views import exception_handler
from rest_framework.exceptions import ValidationError
def base_exception_handler(exc, context):
# Call DRF's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
# check that a ValidationError exception is raised
if isinstance(exc, ValidationError):
# here prepare the 'custom_error_response' and
# set the custom response data on response object
response.data = custom_error_response
return response

How to add custom error codes to Django Rest Framework

I am putting together an API with Django Rest Framework. I want to customise my error handling. I read quite a bit (link1, link2, link3) about custom error handling but can't find something that suits my needs.
Basically, I'd like to change the structure of my error messages to get something like this :
{
"error": True,
"errors": [
{
"message": "Field %s does not exist",
"code": 1050
}
]
}
Instead of :
{"detail":"Field does not exist"}
I already have a custom ExceptionMiddleware to catch the 500 errors and return a JSON, but I have no power on all the other errors.
Code of the ExceptionMiddleware:
class ExceptionMiddleware(object):
def process_exception(self, request, exception):
if request.user.is_staff:
detail = exception.message
else:
detail = 'Something went wrong, please contact a staff member.'
return HttpResponse('{"detail":"%s"}'%detail, content_type="application/json", status=500)
From Django doc :
Note that the exception handler will only be called for responses
generated by raised exceptions. It will not be used for any responses
returned directly by the view, such as the HTTP_400_BAD_REQUEST
responses that are returned by the generic views when serializer
validation fails.
This is exactly what I am trying to achieve, customise those 400 errors.
Thanks a lot,
I know this is a bit late, (better late than never).
If you have a structured error message, then try this by inheriting the Exception class
from rest_framework.serializers import ValidationError
from rest_framework import status
class CustomAPIException(ValidationError):
status_code = status.HTTP_400_BAD_REQUEST
default_code = 'error'
def __init__(self, detail, status_code=None):
self.detail = detail
if status_code is not None:
self.status_code = status_code
and the usage will be like this:
if some_condition:
error_msg = {
"error": True,
"errors": [
{
"message": "Field %s does not exist"%('my_test_field'),
"code": 1050
}
]
}
raise CustomAPIException(error_msg)
Reference : How to override exception messages in django rest framework
This is my custom exception handler:
def api_exception_handler(exception, context):
if isinstance(exception, exceptions.APIException):
headers = {}
if getattr(exception, 'auth_header', None):
headers['WWW-Authenticate'] = exception.auth_header
if getattr(exception, 'wait', None):
headers['Retry-After'] = '%d' % exception.wait
data = exception.get_full_details()
set_rollback()
return Response(data, status=exception.status_code, headers=headers)
return exception_handler(exception, context)
It represents APIException errors in a format like this:
{
"field_name": [
{
"message": "Error message",
"code": "error_code"
},
{
"message": "Another error",
"code": "another_error"
}
]
}
Django Rest Framework reference documentation:
http://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling
The exception handler is indeed what you're looking for. The current mixing do raise an exception in case of failed validation (https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py).
Note that the exception handler will only be called for responses generated by raised exceptions. It will not be used for any responses returned directly by the view, such as the HTTP_400_BAD_REQUEST responses that are returned by the generic views when serializer validation fails.
I think this part doesn't hold any longer and should be rephrased by removing the "generic" word.
Alright so based on Linovia's answer, here is my custom handler :
def custom_exception_handler(exc, context):
response = exception_handler(exc, context)
if response is not None:
errors = []
for msg in response.data.values():
errors.append({'message': msg[0], 'error': get_error_message()})
response.data = {"errors": errors}
return response

Categories