JSON fields serializer errors in Django rest framework - django

when I do input data then I want to check the data key is valid or not by Django rest API JSON serialize field.
I have a JSON serializer in serializers.py like as
class EmployBasicInfoSerializers(serializers.Serializer):
basic_info = serializers.JSONField()
designation = serializers.JSONField()
academic_info = serializers.JSONField()
address = serializers.JSONField()
password = serializers.JSONField()
and my views function is:
#api_view(['POST'])
def employ_create(request):
if request.method == 'POST':
json = JSONRenderer().render(request.data)
stream = io.BytesIO(json)
data = JSONParser().parse(stream)
data_serializer = EmployBasicInfoSerializers(data=data)
if data_serializer.is_valid():
pass
return Response({'data_status':data_serializer.errors}, status=rest_framework.status.HTTP_400_BAD_REQUEST)
Then I input JSON data like as:
{
"basic_info": {
"name":"tushar",
"gender":"male",
"date_of_birth":"2020-08-22",
"age":27,
"phone_number":8667,
"email":"mntushar25#gmail.com"
},
"designation":{
"id": 2
},
"academic_info":[
{
"degree":"hsc",
"last_passing_institution_name":"hgygjhgy",
"last_passing_year":"2020-08-22"
},
{
"degree":"hsc",
"last_passing_institution_name":"hgygjhgy",
"last_passing_year":"2020-08-22"
}
],
"address":{
"house_no":8787,
"village_name":"kushtia",
"post_office":"daulatpur",
"thana_name":"daulatpur",
"district_name":"kushtia"
},
"password":"admin"
}
But I get errors form data_serializer validation.
The errors are:
{
"data_status": {
"name": [
"This field is required."
],
"gender": [
"This field is required."
],
"date_of_birth": [
"This field is required."
],
"phone_number": [
"This field is required."
],
"email": [
"This field is required."
]
}
}
I can't understand why these errors occur. pls, help me to solve the errors.....

Related

Django rest framework - serializer for dictionary structured payload

I'm trying to create a Serializer for a payload that looks something like this -
{
"2fd08845-9b21-4972-87ed-2e7fd03448c5": {
"operation": "Create",
"operationId": "356f6501-a117-4c8d-98ce-dcb4344d481b",
"user": "superuser",
"immediate": "true"
},
"fe6d0c85-0021-431e-9955-e8e1b1ebc414": {
"operation": "Create",
"operationId": "adcedb2f-c751-441f-8108-2c29667ea9cf",
"user": "employee",
"immediate": "false"
}
}
I thought of using DictField, but my problem is that there isn't a field name. it's only a dictionary of keys and values.
I tried something like:
class UserOperationSerializer(serializers.Serializer):
operation = serializers.ChoiceField(choices=["Create", "Delete"])
operationId = serializers.UUIDField()
user = serializers.CharField()
immediate = serializers.BooleanField()
class UserOperationsSerializer(serializers.Serializer):
test = serializers.DictField(child=RelationshipAuthorizeObjectSerializer())
But again, there isn't a 'test' field.
I think your easiest path forward would be to flatten the payload to the following format:
[
{
"request_id": "2fd08845-9b21-4972-87ed-2e7fd03448c5",
"operation": "Create",
"operationId": "356f6501-a117-4c8d-98ce-dcb4344d481b",
"user": "superuser",
"immediate": "true"
},
{
"request_id": "fe6d0c85-0021-431e-9955-e8e1b1ebc414",
"operation": "Create",
"operationId": "adcedb2f-c751-441f-8108-2c29667ea9cf",
"user": "employee",
"immediate": "false"
}
]
And then serialize it. Otherwise, you'd be creating custom fields/serializers which is not pretty.
The way I finally solved it was to add a dynamic 'body' field that contains the real payload of the request.
class UserOperationSerializer(serializers.Serializer):
operation = serializers.ChoiceField(choices=["Create", "Delete"])
operationId = serializers.UUIDField()
user = serializers.CharField()
immediate = serializers.BooleanField()
class UserOperationsSerializer(serializers.Serializer):
body = serializers.DictField(child=UserOperationSerializer())
def __init__(self, *args, **kwargs):
kwargs['data'] = {'body': kwargs['data']}
super().__init__(*args, **kwargs)
Then, in the View, I will use that data as serializer.validated_data['body']
That did the work for me.

Django Rest Framework: Get unique list of values from nested structure

I want to be able to return a list of strings from a deeply nested structure of data. In this scenario, I have a API that manages a chain of bookstores with many locations in different regions.
Currently, I have an API endpoint that takes a region's ID and returns a nested JSON structure of details about the region, the individual bookstores, and the books that can be found in each store.
{
"region": [
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Bar"
}
},
{
"book": {
"name": "Baz"
}
}
],
},
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Bar"
}
}
],
},
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Baz"
}
},
{
"book": {
"name": "Qux"
}
}
]
}
]
}
My models look like the following. I am aware these models don't make the most sense for this contrived example, but it does reflect my real world code:
class Book(TimeStampedModel):
name = models.CharField(default="", max_length=512)
class Bookstore(TimeStampedModel):
value = models.CharField(default="", max_length=1024)
book = models.ForeignKey(Book, on_delete=models.CASCADE)
class Region(TimeStampedModel):
stores = models.ManyToManyField(Bookstore)
class BookstoreChain(TimeStampedModel):
regions = models.ManyToManyField(Region)
The serializers I created for the above response look like:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = "__all__"
class BookstoreSerializer(serializers.ModelSerializer):
books = BookSerializer()
class Meta:
model = Bookstore
fields = "__all__"
class RegionSerializer(serializers.ModelSerializer):
stores = BookstoreSerializer(many=True)
class Meta:
model = Region
fields = "__all__"
class BookstoreChainSerializer(serializers.ModelSerializer):
regions = RegionSerializer(many=True)
class Meta:
model = BookstoreChain
fields = "__all__"
I'm not sure what my view or serializer for this solution need to look like. I'm more familiar with writing raw SQL or using an ORM/Linq to get a set of results.
While the above response is certainty useful, what I really want is an API endpoint to return a unique list of book names that can be found in a given region (Foo, Bar, Baz, Qux). I would hope my response to look like:
{
"books": [
"Foo",
"Bar",
"Baz",
"Qux"
]
}
My feeble attempt so far has a urls.py with the following path:
path("api/regions/<int:pk>/uniqueBooks/", views.UniqueBooksForRegionView.as_view(), name="uniqueBooksForRegion")
My views.py looks like:
class UniqueBooksForRegionView(generics.RetrieveAPIView):
queryset = Regions.objects.all()
serializer_class = ???
So you start from region you have to get the stores, so you can filter the books in the stores, here is a solution which will work.
Note:
Avoid using .get() in *APIView because it will trigger an error if the request does not have the ID, you can use get_object_or_404(), but then you cannot log your error in Sentry.
To get an element from an *APIView, use filter().
import logging as L
class UniqueBooksForRegionView(generics.RetrieveAPIView):
lookup_field = 'pk'
def get(self, *args, **kwargs)
regions = Region.objects.filter(pk=self.kwargs[self.lookup_field])
if regions.exists():
region = regions.first()
stores_qs = region.stores.all()
books_qs = Book.objects.filter(store__in=stores_qs).distinct()
# use your book serializer
serializer = BookSerializer(books_qs, many=True)
return Response(serializer.data, HTTP_200_OK)
else:
L.error(f'Region with id {self.kwargs[self.lookup_field]} not found.')
return Response({'detail':f'Region with id {self.kwargs[self.lookup_field]} not found.'}, HTTP_404_NOT_FOUND)
Note
Here is the flow, the code may need some tweaks, but I hope it helps you understand the flow

Graphene errors messages

I wonder if it is possible to translate the validation error messages that graphene provides? For example: "Authentication credentials were not provided" as shown in the code example below.
{
"errors": [
{
"message": "Authentication credentials were not provided",
"locations": [
{
"line": 2,
"column": 3
}
]
}
],
"data": {
"viewer": null
}
}
Create a custom error type
import graphene
from graphene_django.utils import camelize
class ErrorType(graphene.Scalar):
#staticmethod
def serialize(errors):
if isinstance(errors, dict):
if errors.get("__all__", False):
errors["non_field_errors"] = errors.pop("__all__")
return camelize(errors)
raise Exception("`errors` should be dict!")
Add it to your mutations
class MyMutation(graphene.Mutation):
# add the custom error type
errors = graphene.Field(ErrorType)
form = SomeForm
#classmethod
def mutate(cls, root, info, **kwargs):
f = cls.form(kwargs)
if f.is_valid():
pass
else:
# pass the form error to your custom error type
return cls(errors=f.errors.get_json_data())
Example
django-graphql-auth uses a similar error type, and it works like this, for example for registration:
mutation {
register(
email:"skywalker#email.com",
username:"skywalker",
password1: "123456",
password2:"123"
) {
success,
errors,
token,
refreshToken
}
}
should return:
{
"data": {
"register": {
"success": false,
"errors": {
"password2": [
{
"message": "The two password fields didn’t match.",
"code": "password_mismatch"
}
]
},
"token": null,
"refreshToken": null
}
}
}
My Django form errors types, for example:
from graphene.utils.str_converters import to_camel_case
class DjangoFormError(graphene.ObjectType):
field = graphene.String()
message = graphene.String()
#classmethod
def list_from_errors_dict(cls: Type[T], django_form_errors: dict) -> List[T]:
return [
cls(field=to_camel_case(field), message=' '.join(messages))
for field, messages in django_form_errors.items()
]
class DjangoFormErrorsByIdx(graphene.ObjectType):
form_idx = graphene.Int()
errors = graphene.List(DjangoFormError)
#classmethod
def list_from_idx_dict(cls: Type[T], errors_by_idx_dict: dict) -> List[T]:
return [
cls(
form_idx=idx,
errors=DjangoFormError.list_from_errors_dict(django_form_errors),
)
for idx, django_form_errors in errors_by_idx_dict.items()
]
# ...
# in mutation
if not django_form.is_valid():
form_errors = DjangoFormError.list_from_errors_dict(
django_form.errors
)

Flatten json return by DRF

I have json API returned as below format.
But I want to return json API decomposing namingzone key as specified below.
Could anyone tell me how I can revise serializer to achieve this?
serializer.py is also specified below.
For models.py and views.py, please refer to my previous post.
current
{
"zone": {
"zone": "office_enclosed",
"namingzone": [
{
"naming": "moffice"
}
]
},
"lpd": 11.9,
"sensor": true
},
{
"zone": {
"zone": "office_open",
"namingzone": [
{
"naming": "off"
},
{
"naming": "office"
}
]
},
"lpd": 10.5,
"sensor": true
}
Target
{
"zone": "office_enclosed",
"naming": "moffice",
"lpd": 11.9,
"sensor": true
},
{
"zone": "office_open",
"naming": "off",
"lpd": 10.5,
"sensor": true
},
{
"zone": "office_open",
"naming": "office",
"lpd": 10.5,
"sensor": true
}
serializer.py
class namingNewSerializer(serializers.ModelSerializer):
class Meta:
model=Naming
fields=('naming',)
class zoneSerializer(serializers.ModelSerializer):
namingzone=namingNewSerializer(many=True)
class Meta:
model=Zone
fields = ('zone','namingzone')
class lightSerializer(serializers.ModelSerializer):
zone = zoneSerializer()
class Meta:
model=Light
fields = ('zone','lpd','sensor')
class namingSerializer(serializers.ModelSerializer):
zone=zoneSerializer()
class Meta:
model=Naming
fields=('zone','naming')
I would say using Serializer might complicate the implementations. Rather, you can take an pythonic approach. Try like this:
class SomeView(APIView):
...
def get(self, request, *args, **kwargs):
data = lightSerializer(Light.objects.all(), many=True).data
data = list(data) # convert lazy object to list
updated_data = list()
for item in data:
newdict = dict()
zone = item['zone']
newdict.update({'zone':zone['zone'], 'lpd': item['lpd'], 'sensor':item['sensor']})
for naming_zone in zone.get('namingzone'):
naming_zone.update(newDict)
updated_data.append(naming_zone)
return Response(updated_data, status=status.HTTP_200_OK)
See DRF Field document about source. It will help you.
https://www.django-rest-framework.org/api-guide/fields/#source

Using GeoJson data format how can we write dynamic query for "properties dictionary fields "?

I have data in geojson format.
Here is my data :
{
"type" : "FeatureCollection",
"features" : [
{
"type" : "Feature",
"properties" : {
"year" : 2015,
"season" : "rabbi",
"crop" : "banana",
"district" : "pune",
"taluka" : "haveli",
"circle" : "uralikanchan",
"farmer" : 100
},
"geometry" : {
"type" : "Polygon",
"coordinates" : [
[
74.129992,
18.505494
],
[
74.129047,
18.505494
],
[
74.128275,
18.504436
],
[
74.127588,
18.503052
],
[
74.114456,
18.498331
],
[
74.113941,
18.498331
],
[
74.112482,
18.493773
],
[
74.112053,
18.491494
],
[
74.143724,
18.473992
],
[
74.144497,
18.474888
],
[
74.145269,
18.476027
],
[
74.15617,
18.486854
],
[
74.155912,
18.488319
],
[
74.145956,
18.502564
],
[
74.129992,
18.505494
]
]
}
}
]
}
views.py file :
from rest_framework_mongoengine.viewsets import ModelViewSet as MongoModelViewSet
from app.serializers import *
from rest_framework_mongoengine.generics import *
def index_view(request):
context = {}
return TemplateResponse(request, 'index.html', context)
class ToolViewSet(MongoModelViewSet):
serializer_class = ToolSerializer
my_filter_fields = ('type','features','geometry',) # specify the fields on which you want to filter
def get_kwargs_for_filtering(self):
filtering_kwargs = {}
for field in self.my_filter_fields: # iterate over the filter fields
field_value = self.request.query_params.get(field) # get the value of a field from request query parameter
if field_value:
if ',' in field_value: # put your queryParams into an array and use the built-in django filter method '__in'
filtering_kwargs[field + '__in'] = field_value.split(',')
else:
filtering_kwargs[field] = field_value
return filtering_kwargs
def get_queryset(self):
queryset = Tool.objects.all()
filtering_kwargs = self.get_kwargs_for_filtering() # get the fields with values for filtering
if filtering_kwargs:
queryset = Tool.objects.filter(**filtering_kwargs) # filter the queryset based on 'filtering_kwargs'
return queryset
This dynamic query works for field "type","features" and "geometry".
But, I have to show data using my properties fields
( i.e : year,season,crop,district,taluka. this all fields in fields{properties{"crop","district",.....}}
what I have to changes in this code for working dictionary fields
then If I have go to server http://api/tool/?crop=banana then it's display this data ?