Django Rest Framework: Overwriting validation error keys - python

When translating my site to another language a problem occurred.
I want to handle Validation Errors properly and allow my front-end friends to display them well.
Is there a way to overwrite keys in response message of DRF when Validation Error happend?
What do I mean by that - I want to change this:
{
"name": ["This field is required."]
}
into:
{
"username": ["This field is required."]
}
Is there a way to do that without writing each and every one of validators?

You can change the name field in the ModelSerializer to username.
example:
class CustomSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='name')
class Meta:
model = ...
fields = ('username', ...)
Now in validation errors it will have the key username instead.

Related

How to serialize JSON Request data from serializer in Django?

I am trying to serialize a json data through serializers.Serializer
{
"data": {
"phoneNumber": "1234567890",
"countryCode": "+11",
"otp": "73146",
}
}
The sterilizer class I wrote for it
class VerifyOtpSerializer(serializers.Serializer):
phone_number = serializers.CharField(max_length=225, source='phoneNumber', required=True)
country_code = serializers.CharField(max_length=225, source='countryCode', required=True)
otp = serializers.CharField(max_length=255, required=True)
and also
I don't know why source is not working, I tried the JSON in the picture below but still it's saying the field is required
source value is what the passed value's key will be changed into. So source value is expected to be on your Model.
The name of the attribute that will be used to populate the field.
What you really want is something that changes camel case payload into a snake case. Just use djangorestframework-camel-case and remove source from your serializer fields.
Your keys are wrong in the request. as Tom said the source should be an attribute of the model object. so you have to match keys in request and serializer
change phoneNumber > phone_number
change countryCode > country_code
The response object you are are sending to your serializer is in correct. The key of your request object should be exactly what you have defined in your serializer.
Try sending this to your serializer.
{
"data" : {
"phone_number":"1234567890",
"country_code":"+11",
"otp":"73146"
}
}

How can you customize the response message of the post in django?

I am trying to make a membership API using django rest frameworks.I made a code and checked that the function was working properly. In my current code, if the data of the email, password, and username are empty, the message is given as follows.
{
"email": [
"This field is required."
],
"username": [
"This field is required."
],
}
But after talking about this with my team's client developers, they said it was better to give a unified message as below.
{
"message": "email field is required."
}
How can I customize the value like this? Here's my code.
class customSignUpView (GenericAPIView) :
serializer_class = customRegisterSerializer
def post (self, request) :
user = request.data
serializer = self.serializer_class(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
user = User.objects.get(email=serializer.data['email'])
token = RefreshToken.for_user(user).access_token
current_site = get_current_site(request).domain
relativeLink = reverse('emailVerify')
absurls = F'http://{current_site}{relativeLink}?token={token}'
email_body = F'Hi {user.username} Use link below to verify your email \n{absurls}'
data = {'email_body': email_body, 'to_email': user.email, 'email_subject': 'Verify your email'}
Util.send_email(data)
return Response({'message': 'check your email.'}, status=201)
you need to customize customRegisterSerializer further by adding a custome validate method to it, just try to do something like this
class YourSerializer(serializers.Serializer):
field1 = serializers.CharField(args)
...
fieldn = serializers.CharField(args)
def validate(self, data):
error = {}
if 'some_field' in data:
test field is valid here
if data['some_field'] is not valid:
error['some_field'] = 'your message'
.... ad nauseam
if error:
raise serializers.ValidationError(error)
return data
pass the arguments as the data parameter, and you should be able to customize everything however you want
First of all would like to say that the standard DRF approach to error messages is more universal, as it allows sending several messages for several fields in a unified way. I.e. that in the returned JSON key is the field name and value - the list of messages. Which also allows FE to display the messages next to the appropriate field.
Cause with the format you're trying to achieve comes the question of what kind of message to send if both email and username were not provided but are required (for e.g. should it be one message string or a list of "{field_name} is required" strings?).
But if you really need to achieve the approach you mentioned, let me elaborate on the answer by #vencaslac. So in your case the serializer should roughly look like:
class CustomRegisterSerializer(serializers.ModelSerializer):
...
def validate(self, data):
if not data.get("email"):
raise serializers.ValidationError({"message": "email field is required."})
return data
The validate() method is the same for both Serializer and ModelSerializer. You can find more info in the docs. But again, with this approach you need to figure out an answer to the question I mentioned above.

Validating optional field in marshmallow

I have a field in my model which is required in the resource body while creating the resource (POST) and is not passed in the request body(will be passed along with URL) while updating the resource (PUT).
I have created my marshmallow model:
class DummySchema():
field_1 = fields.Str(required=True)
field_2 = fields.Id()
If I leave field_1 to required=True, I get the following error on PUT :
{
"message": {
"field_1": [
"Missing data for required field."
]
}
}
I could leave it required in the model, but that would mean it would cause a problem in POST request.
Is there a way I could set the field as optional, but set it required for my POST request so that I can perform my validation?
I think I should've read the documentation thoroughly before :(
I could set a field as partial, so when it'll do validation marshmallow would skip the field
data, errors = DummySchema().load({'field_2': 42}, partial=('field_1',))
REF: https://marshmallow.readthedocs.io/en/2.x-line/quickstart.html#validation
If you want to use it for /POST request then this field can be added in dump_only list.
This can also be used for /PUT request.
class StrategySchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Strategy
sqla_session = db.session
ordered = True
load_instance = True
dump_only = ( # read-only
"id",
"created_by",
"created_by_id",
"created_at",
"updated_at",
)
dump_only means:
Consider these fields only while dumping a model to json (deserialization)
ignore it while loading a model from json
read-only fields in other words

Serialize queryset based on individual field values using Django Rest Framework

Goal
If an object has revealed=true it serializes into:
{
"id":1,
"info":"top secret info",
"revealed":true
}
If an object has revealed=false the info field is null:
{
"id":2,
"info":null,
"revealed":false
}
So for a queryset of objects:
[
{
"id":1,
"info":"top secret info 1",
"revealed":true
},
{
"id":2,
"info":null,
"revealed":false
},
{
"id":3,
"info":"top secret info 3",
"revealed":true
}
]
Is it possible to achieve this inside of a Django Rest Framework Model Serializer class?
class InfoSerializer(serializers.ModelSerializer):
class Meta:
model = Info
fields = ('id', 'info', 'revealed')
Background
The DRF docs discuss some advanced serializer usage, and this other post dives into an example. However it doesn't seem to cover this particular issue.
Ideas
A hacky solution would be to iterate over the serialized data afterwards, and remove the info field for every object that has revealed=false. However 1) it involves an extra loop and 2) would need to be implemented everywhere the data is serialized.
I suggest you make the info field appear on all records, but leave it null when revealed is false. If that's acceptable, you should be able to make it happen with a SerializerMethodField.
Alternatively, you could add a revealed_info attribute to the model class, and expose that through the serializer.
#property
def revealed_info(self):
return self.info if self.revealed else None

Django Swagger Integration

I saw swagger documentation of Flask and Django. In Flask I can design and document my API hand-written.(Include which fields are required, optional etc. under parameters sections).
Here's how we do in Flask
class Todo(Resource):
"Describing elephants"
#swagger.operation(
notes='some really good notes',
responseClass=ModelClass.__name__,
nickname='upload',
parameters=[
{
"name": "body",
"description": "blueprint object that needs to be added. YAML.",
"required": True,
"allowMultiple": False,
"dataType": ModelClass2.__name__,
"paramType": "body"
}
],
responseMessages=[
{
"code": 201,
"message": "Created. The URL of the created blueprint should be in the Location header"
},
{
"code": 405,
"message": "Invalid input"
}
]
)
I can chose which parameters to include, and which not. But how do I implement the same in Django? Django-Swagger Document in
not good at all. My main issue is how do I write my raw-json in Django.
In Django it automates it which does not allows me to customize my json. How do I implement the same kind of thing on Django?
Here is models.py file
class Controller(models.Model):
id = models.IntegerField(primary_key = True)
name = models.CharField(max_length = 255, unique = True)
ip = models.CharField(max_length = 255, unique = True)
installation_id = models.ForeignKey('Installation')
serializers.py
class ActionSerializer(serializers.ModelSerializer):
class Meta:
model = Controller
fields = ('installation',)
urls.py
from django.conf.urls import patterns, url
from rest_framework.urlpatterns import format_suffix_patterns
from modules.actions import views as views
urlpatterns = patterns('',
url(r'(?P<installation>[0-9]+)', views.ApiActions.as_view()),
)
views.py
class ApiActions(APIView):
"""
Returns controllers List
"""
model = Controller
serializer_class = ActionSerializer
def get(self, request, installation,format=None):
controllers = Controller.objects.get(installation_id = installation)
serializer = ActionSerializer(controllers)
return Response(serializer.data)
My questions are
1) If I need to add a field say xyz, which is not in my models how do I add it?
2) Quiet similar to 1st, If i need to add a field which accepts values b/w 3 provided values,ie a dropdown. how do I add it?
3) How I add an optional field? (since in case of PUT request, I might only update 1 field and rest leave it blank, which means optional field).
4) Also how do I add a field that accepts the json string, as this api does?
Thanks
I can do all of these things in Flask by hardcoding my api. But in Django, it automates from my models, which does not(as I believe) gives me the access to customize my api. In Flask, I just need to write my API with hands and then integrate with the Swagger. Does this same thing exist in Django?
Like I just need to add the following json in my Flask code and it will answer all my questions.
# Swagger json:
"models": {
"TodoItemWithArgs": {
"description": "A description...",
"id": "TodoItem",
"properties": {
"arg1": { # I can add any number of arguments I want as per my requirements.
"type": "string"
},
"arg2": {
"type": "string"
},
"arg3": {
"default": "123",
"type": "string"
}
},
"required": [
"arg1",
"arg2" # arg3 is not mentioned and hence 'opional'
]
},
Django-rest-framework does have a lot of useful utility classes such as serializers.ModelSerializer which you are using. However these are optional. You can create totally custom API endpoints.
I suggest that you follow the django rest tutorial here. Part one starts with a custom view like this
from django.forms import widgets
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class SnippetSerializer(serializers.Serializer):
pk = serializers.Field() # Note: `Field` is an untyped read-only field.
title = serializers.CharField(required=False,
max_length=100)
code = serializers.CharField(widget=widgets.Textarea,
max_length=100000)
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES,
default='python')
style = serializers.ChoiceField(choices=STYLE_CHOICES,
default='friendly')
def restore_object(self, attrs, instance=None):
"""
Create or update a new snippet instance, given a dictionary
of deserialized field values.
Note that if we don't define this method, then deserializing
data will simply return a dictionary of items.
"""
if instance:
# Update existing instance
instance.title = attrs.get('title', instance.title)
instance.code = attrs.get('code', instance.code)
instance.linenos = attrs.get('linenos', instance.linenos)
instance.language = attrs.get('language', instance.language)
instance.style = attrs.get('style', instance.style)
return instance
# Create new instance
return Snippet(**attrs)
Note in particular that every API field is specified manually and populated by code here. So they do not have to correspond with model fields.
Your questions
1. Custom field xyz :
As I addressed above, just create a custom serialiser and add a line
class SnippetSerializer(serializers.Serializer):
xyz = serializers.CharField(required=False, max_length=100)
...
2. For options in a list, what you're looking for is a "choice" field.
See the Django documention on choice as Swagger is just the same.
3. How do I make a field optional?
Set the kwarg required=False - note that it's set above for field xyz in my example.
4. Best way to accept a JSON string
Two ways to do this.
Just accept a text string and use a JSON parser in the restore_object code
Define a serialiser that consumes / creates the JSON code and refer to it by name as described here

Categories

Resources