Nested validation in Django Rest Framework - python

Using django rest framework I want to validate fields.
Correct input request:
{
test_field_a: {test_field_c: 25},
test_field_b: {}
}
My serializers.py (I don't have any associated models and the models.py itself):
from rest_framework import serializers
class TestSerializer(serializers.Serializer):
test_field_a = serializers.JSONField(label='test_field_a', allow_null=False, required=True)
test_field_b = serializers.JSONField(label='test_field_b', required=True)
test_field_c = serializers.IntegerField(label='test_field_c)
Wrong input request (which should state that int field is required) :
{
test_field_a: {test_field_c: 'wrong'},
test_field_b: {}
}
Now test_field_a and test_field_b are validated as required. But how to make validation of fields on different levels of the request? (in this case test_field_c)

JSONField just checks that a field contains correct JSON structure. You need to do it plus check values from this JSON.
There are several ways to do it:
You can write your own custom field type (it's nice if you are planning to do something similar in other serializers);
You can change field validation (try something like this):
from rest_framework import serializers
class TestSerializer(serializers.Serializer)::
test_field_a = serializers.JSONField(label='test_field_a', allow_null=False, required=True)
test_field_b = serializers.JSONField(label='test_field_b', required=True)
def validate_test_field_a(self, value):
"""
Check that test_field_a .
"""
if not isinstance(value.get('test_field_c'), int):
raise serializers.ValidationError("Some error message")
return value
You can try nested validation:
from rest_framework import serializers
class Test1Serializer(serializers.Serializer):
test_field_c = serializers.IntegerField(label='test_field_c')
class TestSerializer(serializers.Serializer):
test_field_a = Test1Serializer()
test_field_b = serializers.JSONField(label='test_field_b', required=True)

The serializer's JSONField does not have a validation for nested fields because it is not meant to nest explicitly declared fields and as far as I know, there is currently no way to specify a json schema to validate it.
What you can do is validate the field yourself by declaring a validate_test_field_a validation method.
For example:
def validate_test_field_a(self, value):
if 'test_field_c' not in value:
raise serializers.ValidationError('`test_field_c` is required')
return value
Generally, if you find yourself needing to validate the nested type inside the JSONField, then it is a sign of bad architecture and you should consider using nested serializers instead. Same applies to using JSONField in the model

Related

How to receive get parameter in viewset in django restframe work?

For the part of developing an API using restframe work and DJango, I need to receive few parameters through get method in 'list' function of my Viewset. In client side they are sending data as query prams, I somehow managed to get the data using 'request.query_params.get()' but received data is text format instead of bool, I had to convert this to boolean . see the code how I did this
if 'status' in (request.query_params):
"""receiving input argument is string need to convert that to boolean for further processing"""
input_status=(request.query_params.get('status', None)=='true')
is there any better way to getting data without losing datatype. is it possible to receive 'bool' or 'int' values directly in get parameter?
My model and view set classes are given below
class ModuleType(models.Model):
"""
This module type class used to represent each module of the application. It is inherited from
django content type which holds the complete model informations
"""
#this active field is true this module will be active on our user controller
content_type = models.OneToOneField(
ContentType,
on_delete=models.CASCADE,
related_name='status',
null=True
)
active=models.BooleanField(default=False)
class Meta:
db_table = 'module_types'
My Viewset
class ContentTypeViewSet(viewsets.ModelViewSet):
"""
This api deals all operations related with module management
You will have `list`, `create`, `retrieve`,
update` and `destroy` actions.
Additionally we also provide an action to update status.
"""
queryset = ContentType.objects.all()
serializer_class = ContentTypeSerializer
permission_classes = [permissions.AllowAny]
"""
Over ride list method to list values
"""
def list(self, request):
status='Sucess'
message='details of all modules included'
print(request)
try:
if 'status' in (request.query_params):
#input argument is striing need to convert that to boolean
input_status=(request.query_params.get('status', None)=='true')
validated_value= self.validate_input_params({'status':input_status})
if (validated_value==1):
all_modules = ContentType.objects.filter(status__active=input_status)
message='All value set status as '+str(input_status)
else:
message='Failed to validate all input values'
status='fail'
return Response({"status":status,"data":[],"message":message})
else:
all_modules=ContentType.objects.all()
serializer = self.get_serializer(
all_modules,
many=True
)
return Response({"status":status,"data":serializer.data,'message':message})
except:
message='unknown exception'
status='Fail'
return Response({"status":status,"data":[],"message":message})
Your view will read query params as string.
I suggest you use query_params = dict(request.GET.items())
That way you can parse parameters easier by using dict.

Django Grapelli add autocomplete lookups on InlineAdmin

I have this 3 models:
class MyFile(models.Model):
file = models.FileField(upload_to="files/%Y/%m/%d")
def __unicode__(self):
"""."""
return "%s" % (
self.file.name)
class ExampleModel(models.Model):
attached_files =models.ManyToManyField(MyFile)
main_model = models.ForeignKey(MainModel)
class MainModel(models.Model):
attached_files =models.ManyToManyField(MyFile)
And my admin.py as follows:
class ExampleModelAdminInline(admin.TabularInline):
model = ExampleModel
extra = 2
class MainModelAdmin(admin.ModelAdmin):
inlines = [ExampleModelAdminInline]
Im using django-grapelli because it offer autocomplete lookups for many to many fields. However, Im not sure how to add this autocomplete lookup to a TabularInline admin. Can anyone explain me how to set up the attached_files field to have autocomplete lookups?
First you need to set the static method autocomplete_search_fields() in the Model you want to search from, in your case MyFile. From the docs we get:
class MyFile(models.Model):
#your variable declaration...
#staticmethod
def autocomplete_search_fields():
return ("id__iexact", "name__icontains",) #the fields you want here
You can also define GRAPPELLI_AUTOCOMPLETE_SEARCH_FIELDS instead of declaring the static method, like:
GRAPPELLI_AUTOCOMPLETE_SEARCH_FIELDS = {
"myapp": {
"MyFile": ("id__iexact", "name__icontains",)
}
}
Then you should add the lookup and raw fields to your desired admin class, considering its related Model (say, your ExampleModel) which is the one that has a ManyToManyField. You can also handle ForeignKey in a similar way. Also from the mentioned docs:
class ExampleModel(models.Model):
main_model = models.ForeignKey(MainModel) #some FK to other Model related
attached_files =models.ManyToManyField(MyFile) #the one with static decl
class MainModelAdmin(admin.ModelAdmin):
#your variable declaration...
# define raw fields
raw_id_fields = ('main_model','attached_files',)
# define the autocomplete_lookup_fields
autocomplete_lookup_fields = {
'fk': ['main_model'],
'm2m': ['attached_files'],
}
Remember to register both ends (your models) of the relationship to your admin.site, like this:
#the one with the m2m and the one with the lookup
admin.site.register(ExampleModel, MainModelAdmin)
You can also check this question to understand better.

Suppress "field should be unique" error in Django REST framework

I have a model like
class MyModel(models.Model):
uuid = models.CharField(max_length=40, unique=True)
and a serializer
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('uuid')
And I want to receive JSON with MyModel object but it can be existing objects. So, when I use serializer.is_valid() with data about existing object it gives me an error:
for record in request['records']:
# request - body of JSON request,
# 'records' - array of records I want to add or update
serializer = MyModelSerializer(data=record)
if serializer.is_valid():
# Do stuff
serializer.save()
Error:
{"uuid":["This field must be unique."]}
Is there a way to separate behavior for new and existing objects? Particularly, I want to create new MyModel object if it's not it database yet and update existing MyModel object if it's present.
You are basically overloading a single entry point of your REST API by trying to both create new instances and update existing instances using a POST request. In addition, it seems you are trying to create and update multiple instances simultaneously within a single POST request.
Django REST Framework (DRF) expects a POST request to only create new instances. Therefore, sending an existing instance record triggers a unique constraint violation for the uuid field since DRF tries to create that record as a new instance, as the existing instance already has that uuid value.
A solution to make your REST API more "RESTful" would be to separate the creation and updating of records into POST and PUT requests respectively. It is unclear if you are using the generic API views provided by DRF, but you can use the CreateAPIView to POST new instances, then create a separate UpdateAPIView to PUT and/or PATCH existing instances. Even better you could allow retrieval via GET for both of these endpoints using the generic views ListCreateAPIView and RetrieveUpdateAPIView.
Finally, for handling bulk requests (i.e. multi-instances in a single request) you can either override the built-in view methods or use a 3rd-party package such as django-rest-framework-bulk.
I had a situation where I had a deep create method, with 2 levels of hierarchy above the end point, that it was important that all models were idempotent.
I override the validation in the serializer, and created it by hand.
It is important that you add the field to the class at the top (otherwise the validator won't be run)
class ParticipantSerializer(serializers.HyperlinkedModelSerializer):
device = DeviceSerializer(required=False)
uuid = serializers.CharField()
def validate_uuid(self, value):
if value is not None and isinstance(value, basestring) and len(value) < 256:
return value
else:
if value is not None:
raise serializers.ValidationError("UUID can't be none")
elif isinstance(value, basestring):
raise serializers.ValidationError("UUID must be a string")
elif len(value) < 256:
raise serializers.ValidationError("UUID must be below 256 characters")
else:
raise serializers.ValidationError("UUID has failed validation")
class Meta:
model = Participant
fields = ("uuid", "platform", "device")

Django rest API Framework : Serializer use dynamically

I am trying to create a framework using Django rest API.
I want to configure Serializer fields and Name of class variable of serializer dynamically using a factory pattern. My code is a bit complex so here is the simplified version of problem.
from rest_framework import serializers
class FixedSrializer(serializers.Serializer):
email= serializers.EmailField()
fixed_serializer=FixedSrializer(data={"email":"invalidemail"})
fixed_serializer.errors
#OUTPUT: {'email': [u'Enter a valid email address.']}
# Trying Above by dynamically assigning class var
class CustomSrializer(serializers.Serializer):
pass # no class var assigned
#setattr(CustomSrializer,"email",serializers.EmailField())
CustomSrializer.email=serializers.EmailField()
custom_serializer=CustomSrializer(data={"email":"invalidemail"})
custom_serializer.errors
#OUTPUT# {}
#EXPECTED# {'email': [u'Enter a valid email address.']}
I think this may be because of dynamically changing class variable. Please correct where I am doing wrong.
If you see my post a bit carefully 1) FixedSrializer => contains email fields 2) CustomSrializer. Does not contain email field but in very next line assigned. Rest every thing is same for both and I am getting different outputs.I am using django-rest-framework.org.

Reverse URL by more than one parameter in Django REST Framework

Let's take the usual Customers with Orders pair of models.
How can we make a HyperlinkedModelSerializer of Order url field reverse to a url like /customers/<customer_pk>/orders/<order_id>/?
I thought of using the lookup_field in the Meta class but it seems to only accept one field.
Thanks for your help
HyperlinkedModelSerializer uses a single parameter in the lookup_field as of DRF 2.0, so like you, I wasn't able to use this.
However, with some tricks I picked up on this question I was able to build a ModelSerializer that would have a url field that contained the reverse URL as you describe.
class OrderSerializer(serializers.ModelSerializer):
# Fields, etc
url = serializers.SerializerMethodField('make_url')
class Meta:
model = Order
# Class info here as usual
def make_url(self, obj):
"""
Build URL for Order instance
"""
# Prepare the IDs you need for the URL reverse
kwargs = {
'customer_pk': obj.customer.id,
'order_id': obj.id,
}
url = reverse('api:single_order', kwargs=kwargs)
return self.context['request'].build_absolute_uri(url)
If you're using viewsets, then this library will help you out: drf-nested-routers.

Categories

Resources