How to customize the response of Django REST Framework GET request? - django

I have a model Foo which I use as the model for my vanilla DRF serializer.
models.py
class Foo(models.Model):
name = models.CharField(max_length=20)
description = models.TextField()
is_public = models.BooleanField(default=False)
serializers.py
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
views.py
class FooRetrieveAPIView(RetrieveAPIView):
queryset = Foo.objects.all()
serializer_class = FooSerializer
Now the result of this endpoint is being used by front-end code, which is then the basis on how the next page to show is identified. Anyway, I need to change the structure of the results returned for both status 200 (existing record) and 404 (non-existent record).
Actual result (from vanilla DRF):
$ curl localhost:8000/foo/1/ # existing record
{"id": 1, "name": "foo", "description": "foo description", is_public=false}
$ curl localhost:8000/foo/2/ # non-existent record
{"detail": "Not found."}
How I want the results to be:
$ curl localhost:8000/foo/1/
{"error": "", "foo": {"id": 1, "name": "foo", "description": "foo description", is_public=false}}
$ curl localhost:8000/foo/2/
{"error": "Some custom error message", "foo": null}
I've mostly used vanilla DRF so things are pretty straightforward so this customization of the response structure is a bit new to me.
Django version used: 1.9.9
DRF version used: 3.3.x

You can rewrite retrieve method in your view in order to update your serializer response data
class FooRetrieveAPIView(RetrieveAPIView):
queryset = Foo.objects.all()
serializer_class = FooSerializer
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
data = serializer.data
# here you can manipulate your data response
return Response(data)

I had a similar problem and didn't want to create a custom exception handler as I wanted more control over defining error messages for api calls.
After a bit of hand banging I used the following code in viewset to get custom error message while updating the record using partial_update.
def partial_update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
try:
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
headers = self.get_success_headers(serializer.data)
response = {"status": "True", "message": "", "data": serializer.data}
return Response(response, status=status.HTTP_201_CREATED, headers=headers)
except exception.Http404:
headers = ""
response = {"status": "False", "message": "Details not found", "data": ""}
return Response(response, status=status.HTTP_200_OK, headers=headers)

Related

Django Rest Framework unique field constraint on array

So, I'm trying to make an endpoint where I insert a list of objects.
My issue is the behavior and response when inserting duplicates.
What I want to accomplish is to:
Send the duplicate lead external_id(s) in the error response
Insert any other non duplicated object
I'm pretty sure this logic (for the response and behavior) could be accomplished in the modifying the serializer.is_valid() method... but before doing that, I wanted to know if anyone had experience with this kind of request.. Maybe there is a "clean" way to do this while keeping the unique validation in the model.
Data on OK response:
[
{
"external_id": "1",
"phone_number": "1234567890"
}
]
Data for a FAIL request (1 is dup, but 2 should be inserted. Expecting a response like "external_id" : "id 1 is duplicated"):
[
{
"external_id": "1",
"phone_number": "1234567890"
},
{
"external_id": "2",
"phone_number": "2234567890"
}
]
models.py
class Lead(models.Model):
external_id = models.CharField(max_length=20, unique=True)
phone_number = models.CharField(max_length=50)
serializers.py
class LeadSerializer(serializers.ModelSerializer):
class Meta:
model = Lead
fields = '__all__'
def create(self, validated_data):
lead = Lead.objects.create(**validated_data)
log.info(f"Lead created: {lead.import_queue_id}")
return lead
views.py
class LeadView(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
#extend_schema(description="Insert campaign data", request=LeadSerializer(many=True), responses=None, tags=["Leads"])
def post(self, request):
serializer = LeadSerializer(data=request.data, many=True)
valid = serializer.is_valid()
if serializer.is_valid():
serializer.save()
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
else:
return Response({"status": "error", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
You can customize the behaviour of list of object through list_serializer_class option in meta class. Like this:
class LeadListSerializer(serializers.ListSerializer):
def validate(self, data):
items = list(map(lambda x: x['phone_number'], data))
if len(set(items)) == len(items)):
return super().validate(data)
raise ValidationError()
class LeadSerializer(serializers.ModelSerializer):
class Meta:
model = Lead
fields = '__all__'
list_serializer_class = LeadListSerializer

How to update multiple objects in django rest framework?

I am trying to update multiple objects using IDs which i am passing in every objects that need to be updated but can't find any way to do it successfully. Here is my code
models.py
class EventTicket(models.Model):
id = models.UUIDField(primary_key=True, default=uuid_generate_v1mc, editable=False)
name = models.CharField(max_length=250)
description = models.TextField(max_length=1000)
views.py
class EventTicketView(APIView, PaginationHandlerMixin):
permission_classes = (AllowAny,)
def get_object(self, ticket_id):
try:
return EventTicket.objects.get(id=ticket_id)
except EventTicket.DoesNotExist():
raise status.HTTP_400_BAD_REQUEST
def patch(self, request, *args, **kwargs):
for each_ticket in request.data:
ticket_id = self.get_object(each_ticket['ticket_id'])
serializer = EventTicketSerializer(instance=ticket_id,data=request.data,partial=True)
if serializer.is_valid():
serializer.save()
result = {
'message': "updated sucessfully"
}
return Response(result, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py
class EventTicketSerializer(serializers.ModelSerializer):
class Meta:
model = EventTicket
fields = ['name', 'description']
```
I have to send data like list of multiple objects :::
[
{
"ticket_id": "054665ea-4fde-11ea-94b2-9f415c43ba4c",
"name": "chris",
"description":"The golden ticket for day only",
},
{
"ticket_id": "054656ea-4fde-11ea-94b2-9f415c43ba4c",
"name": "daut",
"description":"The premium ticket for day only",
}
]
The following code will give you a proper understanding of updating multiple objects in single request.
For updating multiple objects in a single request it is best practice to use the PUT method instead of PATCH.
Here body data given is.
BODY DATA
{
"ids":[
"5e41770d2e8fa013d1f034ec",
"5e41772c2e8fa013d1f034ee",
"5e4177702e8fa013d1f034f2",
"5e453f302e8fa075aa18b277",
"5e4a314f2e8fa070c5251a0a"
]
}
I'am updating the enabled attribute from False to True for given ids of DemoConfig model.
In the same way, you can update your data. As per your requirement, you can write validate methods to validate the body data.
Serializer has written to serialized the instance data for the response.
class DemoAPI(APIView):
def get_object(self, obj_id):
try:
return DemoConfig.objects.get(id=obj_id)
except (DemoConfig.DoesNotExist, ValidationError):
raise status.HTTP_400_BAD_REQUEST
def validate_ids(self, id_list):
for id in id_list:
try:
DemoConfig.objects.get(id=id)
except (DemoConfig.DoesNotExist, ValidationError):
raise status.HTTP_400_BAD_REQUEST
return True
def put(self, request, *args, **kwargs):
id_list = request.data['ids']
self.validate_ids(id_list=id_list)
instances = []
for id in id_list:
obj = self.get_object(obj_id=id)
obj.enabled = True
obj.save()
instances.append(obj)
serializer = DemoSerializer(instances, many=True)
return Response(serializer.data)
Serialiser for this view is:
class DemoSerializer(DocumentSerializer):
class Meta:
model = DemoConfig
fields = '__all__'
Output:
{
"data": [
{
"id": "5e41770d2e8fa013d1f034ec",
"name": "CONFIG_1",
"enabled": true,
},
{
"id": "5e41772c2e8fa013d1f034ee",
"name": "CONFIG_2",
"enabled": true,
},
{
"id": "5e4177702e8fa013d1f034f2",
"name": "CONFIG_3",
"enabled": true,
},
{
"id": "5e453f302e8fa075aa18b277",
"name": "CONFIG_4",
"enabled": true,
},
{
"id": "5e4a314f2e8fa070c5251a0a",
"name": "CONFIG_5",
"enabled": true,
}
]
}
As per your code requirement you need to use put method in follwoing way.
def put(self, request, *args, **kwargs):
data = request.data
ticket_ids = [i['ticket_id'] for i in data]
self.validate_ids(ticket_ids)
instances = []
for temp_dict in data:
ticket_id = temp_dict['ticket_id']
name = temp_dict['name']
description = temp_dict['description']
obj = self.get_object(ticket_id)
obj.name = name
obj.description = description
obj.save()
instances.append(obj)
serializer = DemoSerializer(instances, many=True)
return Response(serializer.data)

Object of objects JSON response from Django backend to React-Redux frontend

This is the response from Django when making axios api call in the frontend (array of JSON objects).
[
{
"id": 1,
"title": "How to create a django-react app",
"body": "You should first do this stuff and that"
},
{
"id": 5,
"title": "How to connect django with react",
"body": "Get this and that stuff"
}
]
But this is the response that I want (JSON object of JSON objects).
Is a Python dictionary the same as a Javascript object or Hashmap?
Is there some kind of middleware I can use to convert the shape?
Is this a job that the serializers.py needs to do or the views.py? How can I change the response from an array of objects to an object or objects?
{
1: {
"id": 1,
"title": "How to create a django-react app",
"body": "You should first do this stuff and that"
},
5: {
"id": 5,
"title": "How to connect django with react",
"body": "Get this and that stuff"
}
}
serializers.py
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
views.py
class ArticleViewSet(ViewSet):
queryset = Article.objects.all()
def list(self, request):
serializer = ArticleSerializer(ArticleViewSet.queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
article = get_object_or_404(ArticleViewSet.queryset, pk=pk)
serializer = ArticleSerializer(article, many=False)
return Response(serializer.data)
You should be able to build a dictionary in the ArticleViewSet's list method:
class ArticleViewSet(ViewSet):
queryset = Article.objects.all()
def list(self, request):
serializer = ArticleSerializer(ArticleViewSet.queryset, many=True)
return Response({article['id']: article for article in serializer.data})
def retrieve(self, request, pk=None):
# ...

how to overwrite RetrieveAPIView response django rest framework

I am fetching data from RetrieveAPIView I want to overwrite it.
class PostDetailAPIView(RetrieveAPIView):
queryset = Post.objects.all()
serializer_class = PostDetailSerializer
lookup_field = 'slug'
http://127.0.0.1:8000/api/posts/post-python/
it return me result
{
"id": 2,
"title": "python",
"slug": "post-python",
"content": "content of python"
}
I want to overwrite this with some extra parameters like
[
'result':
{
"id": 2,
"title": "python",
"slug": "post-python",
"content": "content of python"
},
'message':'success'
]
you can override the retrieve method. in this way you can send extra data to response.
def retrieve(self, request, *args, **kwargs):
response = super().retrieve(request, args, kwargs)
response.data["custom_data"] = "my custom data"
return response
Ok, in comment I made wrong call, you want to overwrite get() method of your View.
class PostDetailAPIView(RetrieveAPIView):
queryset = Post.objects.all()
serializer_class = PostDetailSerializer
lookup_field = 'slug'
def get(self, request, slug):
post = self.get_object(slug)
serializer = PostDetailSerializer(post)
return Response({
'result': serializer.data,
'message': 'success'
})
Notes
1. I names second argument of get function slug because it's your lookup_field, but it should be name you used in urls.
2. You could instead overwrite retrieve() function
3. This is answer specific for your question, you should also read this answer

Django models default error messages into one field

This very simple django restframework code.
models.py
class User(models.Model)
Email = models.CharField(max_length=100)
Username = models.CharField(max_length=100)
State = models.CharField(max_length=100)
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('Email','Username','State')
views.py
class UserList(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
If use this I am getting error out put like this
{
"Email": [
"This field may not be blank."
],
"Username": [
"This field may not be blank."
],
"Country": [
"This field may not be blank."
],
}
But I need to change the error out like this.How I can i archive this and any suggestion greatly appreciated.
{"error":
[
"Email is required",
"Username is required",
"County is required"
]
}
You can always overwrite the create/update methods from the generic views. It would be something like:
class UserList(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if not serializer.is_valid(raise_exception=False):
# TODO: add here your custom error dict using serializer.errors
return Response({"error":...}, status=...)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Or... you could try to overwrite the serializers... if you don't want to overwrite the view.
(However, there must be a good reason for a JS developer not being able to parse a simple json error object :P)
Hope this helps
You can define your own error messages for any error case:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('Email','Username','State')
def __init__(self, *args, **kwargs):
super(UserSerializer, self).__init__(*args, **kwargs)
for field in self.Meta.fields:
self.fields[field].error_messages['required'] = "%s is required" % field