I am trying to serialize a nested request body data (just the part of it)
body -
{
"context": {
"timestamp": "2022-10-13T09:48:47.905Z",
},
"message": {
"intent": {
"item": {
"descriptor": {
"name": "apple"
}
},
}}}
serializer
class SearchSerilizer(serializers.Serializer):
timestamp = serializers.CharField(source="context.timestamp", max_length=35)
Caller snippet
serializer = SearchSerilizer(data=request.data)
if serializer.is_valid():
print(serializer.data)
return Response(serializer.data)
else:
print(serializer.errors)
return Response(serializer.errors)
And it prints
{'timestamp': [ErrorDetail(string='This field is required.', code='required')]}
How can I use the source here to serialize this data?
Related
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.
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
I am trying to recreate game of hacks because there isnt an API to create my own questions , and implement on external site , however I am using django with restful framework for this task. (I am not sure , if this is the right to achieve this). I will do this via server because I dont want people change js and bypass the stuff or even disable js and stop time , and continue with the same question
apiview.py
#api_view(['GET', 'POST'])
def questions_view(request):
if request.method == 'GET':
questions = Question.objects.all()
serializer = QuestionListPageSerializer(questions, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = QuestionListPageSerializer(data=request.data)
if serializer.is_valid():
question = serializer.save()
return Response(QuestionListPageSerializer(question).data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers
class QuestionListPageSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
question_text = serializers.CharField(max_length=200)
pub_date = serializers.DateTimeField()
was_published_recently = serializers.BooleanField(read_only=True) # Serializer is smart enough to understand that was_published_recently is a method on Question
code = serializers.CharField(max_length=200)
def create(self, validated_data):
return Question.objects.create(**validated_data)
def update(self, instance, validated_data):
for key, value in validated_data.items():
setattr(instance, key, value)
instance.save()
return instance
question list my app
HTTP 200 OK
Allow: POST, OPTIONS, GET
Content-Type: application/json
Vary: Accept
[
{
"id": 1,
"question_text": "helllo",
"pub_date": "2020-01-15T02:30:40Z",
"was_published_recently": false,
"code": "int main (int argc, char *argv[])\r\n{\r\n\tchar *val = argv[argc -1];\r\n\tsystem(val);\r\n\treturn(0);\r\n}"
},
{
"id": 2,
"question_text": "What is the meaning of life?",
"pub_date": "2020-01-15T02:30:40Z",
"was_published_recently": false,
"code": "SOME STRING"
}
]
question with answer my app
HTTP 200 OK
Allow: PATCH, OPTIONS, DELETE, GET
Content-Type: application/json
Vary: Accept
{
"id": 1,
"question_text": "helllo",
"pub_date": "2020-01-15T02:30:40Z",
"was_published_recently": false,
"code": "int main (int argc, char *argv[])\r\n{\r\n\tchar *val = argv[argc -1];\r\n\tsystem(val);\r\n\treturn(0);\r\n}",
"choices": [
{
"id": 1,
"choice_text": "11111"
}
]
}
game of hacks external app
{
"_id": "53fb4014e5d4d40400c7fa4f",
"answers": [
"LDAP Injection",
"CGI Reflected XSS",
"Connection String Injection",
"Reflected XSS"
],
"batch_score": 39.89902034664657,
"checkmarx": true,
"from": "Gilad",
"language": "PHP",
"level": "1",
"question": "What vulnerability the following code contains?",
"snippets": [
{
"code": "<?php\n$dn = $_GET['host'];\n$filter=\"(|(sn=$person*)(givenname=$person*))\";\n$justthese = array(\"ou\", \"sn\", \"givenname\", \"mail\");\n$sr=ldap_search($ds, $dn, $dn, $justthese);\n$info = ldap_get_entries($ds, $sr);\necho $info[\"count\"].\" entries returned\n\";?>"
}
],
"tip": ""
}
example
http://www.gameofhacks.com/api/answer
answer
{quest: true, score: 5700}
quest: true
score: 5700
http://www.gameofhacks.com/api/question # make random instead of api/poll/question/{id} ?
You can use python random library in your view:
from random import randint
def random_question(request):
i = randint(0, Question.objects.count() - 1)
question = Question.objects.all()[i] # get question at random position
....
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
)
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