Django Rest Framework custom response for validation failure - python

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

Related

More descriptive error messages for Django Serializers?

I am deserializing JSON data from file to a model object. I'm using a Django Rest Framework serializer.
The JSON is quite large and I expected some fields that needed tweaking. However, the error messages are not helpful, contrary to those in the docs.
The serializer:
class ChannelSerializer(serializers.ModelSerializer):
class Meta:
model = Channel
fields = '__all__'
The test:
class UnitTests(unittest.TestCase):
def testDeserializeChannel(self):
json = Path(Dirs.CHANNELS_METADATA_TEST, 'A SIMULATED REALITY/youtube/A SIMULATED REALITY.json')
with open(json, 'rb') as file:
stream = io.BytesIO(file.read())
parsed_channel = JSONParser().parse(stream)
serializer = ChannelSerializer(data=parsed_channel, many=True)
if not serializer.is_valid(): # required for serializer.save() to work
print(serializer.error_messages, "\n")
print(serializer.errors)
channel = serializer.validated_data.save()
self.assertEqual(channel[0].title, "A SIMULATED REALITY")
self.assertEqual(channel[0].original_url, "https://www.youtube.com/channel/UCBT3VPJlhJHeIvmDadeZB5w")
The error messages:
{'required': 'This field is required.', 'null': 'This field may not be null.', 'not_a_list': 'Expected a list of items but got type "{input_type}".', 'empty': 'This list may not be empty.', 'max_length': 'Ensure this field has no more than {max_length} elements.', 'min_length': 'Ensure this field has at least {min_length} elements.'}
{'non_field_errors': [ErrorDetail(string='Expected a list of items but got type "dict".', code='not_a_list')]}
We can see required and null errors but without mentioning of the fields.
I suspect the 'all' bit in the serializer is the cause, contrary to explicitly defined field. Can somebody comfirm? And more importantly, can it be fixed?
Kr,
You can use DRF's APIException to create your custom exceptions:
from rest_framework.exceptions import APIException
class MyCustomException(APIException):
status_code = 400
default_detail = 'This serializer is not valid my friend...'
default_code = 'bad_request'
Usage:
. . .
if not serializer.is_valid():
raise MyCustomException()
. . .
Outcome if serializer is not valid:
"detail": {"This serializer is not valid my friend..."}
Like this you can use custom exceptions all around your code. :)

Use existing ModelSerializer with JSONResponse

I have a Twitter authentication view that doesn't use a viewset so the auth can be handled on the backend. The view takes in the oauth_token & uses Twython to get the profile & create a Twitter model.
Currently I just return status 201 on success, but to alleviate the need for another request after creation, I'd like to return the created model. I have a TwitterSerializer already which defines the fields that I want to include, so I'd like to be able to reuse this if possible.
TwitterSerializer
class TwitterSerializer(serializers.ModelSerializer):
class Meta:
model = Twitter
fields = (
"id",
"twitter_user_id",
"screen_name",
"display_name",
"profile_image_url",
)
When I try to use this, I get the error that Instance of TwitterSerializer is not JSON serializable.
serialized = TwitterSerializer(instance=twitter)
return JsonResponse({ "created": serialized })
I could return a serialized instance of the model using serializers.serialize()
serialized = serializers.serialize('json', [twitter, ])
serialized = serialized[0]
return JsonResponse({ "created": serialized })
I could pass the fields kwarg to serialize() but I don't want to have to repeat myself if I don't have to. So would it be possible to re-use my TwitterSerializer in this case? I'm having trouble finding a direct answer since most docs assume you'll be using a ViewSet when using serializerss understandably, and this feels like an edge case. I'm open to suggestions for refactoring this approach as well!
After serialization, you can get your data using data attribute of serializer like this.
serialized = TwitterSerializer(instance=twitter)
return JsonResponse({ "created": serialized.data })
You should use Django rest Response instead of JsonResponse like this
from rest_framework response
serialized = TwitterSerializer(instance=twitter)
return response.Response({ "created": serialized.data })

Add a field error in Django Rest Framework validate method?

In Django Rest Framework (and Django), traditionally we check fields in validate_<field> method, and make more global checks in validate method.
However, look at this code snippet:
def validate(self, data):
# ....
try:
customer.activate(data['signup_code'], data['raw_password'])
except BadCodeProvided:
raise ValidationError(MSG_WRONG_ACTIVATION_CODE)
except SomeOtherException:
raise ValidationError(SOME_OTHER_MESSAGE)
Here, I'm forced to use validatemethod because I'm using 2 fields for my validation (signup_code and raw_password).
However, if an error occurs in a BadCodeProvided Exception, I know it's related to the signup_code field (and not the raw_password one) because of the exception raised here.
In the snippet code above, thiw will create a "non_field_error".
Question: is there a way in DRF to raise the same error but related to the "signup_code" field? (like it would be done in a validate_signup_code method).
Thanks
you can use serializers.ValidationError in serializer :
raise serializers.ValidationError({"myField": "custom message error 1",
"myField2": "custom message error 1"})
doc here Validator DRF

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 can I use rest serializer.ValidationError in django forms?

I am new to Django and rest_framework. I have a password complexity rules script for 'new user' page.
If my complexity requirements satisfy the needs, it will return true. Else it raises serialiser.ValidationError.
I duplicated Django's forget password mechanism to apply my password rules.
When it raises an error, application crashes like below.
Exception Type: ValidationError
Exception Value:
[u"The two password fields didn't match."]
Is it possible to use serializer errors as form errors {{ form.new_password1.errors }}?
It's possible to write your own custom exception handler to return the error in a preferred format.
The official documentation, at the bottom of the page, says:
The ValidationError class should be used for serializer and field validation, and by validator classes. It is also raised when calling
serializer.is_valid with the raise_exception keyword argument:
The generic views use the raise_exception=True flag, which means that you can override the style of validation error responses globally
in your API. To do so, use a custom exception handler, as described
above.
serializer.is_valid(raise_exception=True)
By default this exception results in a response with the HTTP status
code "400 Bad Request"
Please read here to create your custom handler.

Categories