DRF: to_representation() called twice on serializer update() - django

My site has a Vue frontend with a DRF backend. I have a model with a multi-value field that looks like this:
stages = StageSerializer(
many=True,
read_only=True
)
In the to_representation() method, I process the stages data into three different elements to facilitate features for the frontend.
def to_representation(self, instance):
results = super().to_representation(instance)
return render_stages(results)
The problem is, when I post (PATCH actually) to an update view, which ends up calling the update() method of the serializer, the to_representation() method seems to be called twice. I could post the code from render_stages(), but I don't see how that could be causing the problem. Suffice to say that it takes this, from the call to super().to_representation():
{
.
. other fields
.
"stages": [
{
"id": 1,
"name": "To Do"
},
{
"id": 2,
"name": "In Progress"
},
{
"id": 3,
"name": "Done"
}
]
}
...and turns it into this:
{
"stages": [
"To Do",
"In Progress",
"Done"
],
"stages_order": "1,2,3",
"stages_data": [
{
"id": 1,
"name": "To Do",
"order": 1
},
{
"id": 2,
"name": "In Progress",
"order": 2
},
{
"id": 3,
"name": "Done",
"order": 3
}
]
}
As you can see, the format of the "stages" element changes after the first pass through to_representation() from a list of dicts to a list of strings. So when to_representation() gets called a second time, it raises an exception.
On a list view, it only gets called once. It's just on an update view that it gets called twice. Can someone explain why that's happening?
Update: More details:
The view. The settings model only has one row - pk=1 - so this is a departure from the norm.
class SettingsUpdate(ViewMetaMixin, UpdateAPIView):
serializer_class = SettingsSaveSerializer
permission_classes = (IsAuthenticated, CanAccessEndpoint)
queryset = Settings.objects.none()
def get_object(self):
obj = Settings.objects.get(pk=1)
self.check_object_permissions(self.request, obj)
return obj
def put(self, request, *args, **kwargs):
return self.patch(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
response = self.partial_update(request, *args, **kwargs)
return Response({
'results': response.data,
'messages': [
{
'message': _('Settings updated.'),
'message_type': 'status',
}
]
})
The serializer:
class SettingsSaveSerializer(SettingsSerializer):
stages = StageSerializer(
many=True,
read_only=True
)
class Meta:
model = Settings
fields = '__all__'
def to_representation(self, instance):
results = super().to_representation(instance)
return render_stages(results)
...validations omitted...
...create method omitted...
def update(self, instance, validated_data):
stages = get_initial_data_field(self.initial_data, 'stages', many=True)
if stages is not None: # Only process "stages" if it's present in form data
validated_data['stages_order'] = Stage.objects.save_stages(instance, stages)
return super().update(instance, validated_data)

Related

How to aggregate filtered listapiview queryset and return value once

How to perform aggregations on filtered querysets and return the value only once?
My existing code below. In the serializer, of course PZ.objects.all() makes it aggregate all items. I don't know how to get the queryset from the serializer level. To make the total value appear once, it would be a good idea to add a field in the view. However, overriding def list(): makes the filtering stop working.
I need this because I am working on a table that shows documents with filtering capabilities. In addition, there will be pagination. After selecting, for example, a creation date range, the values of the documents must be summed.
View:
class PZListAPIView(ListAPIView):
queryset = PZ.objects.all()
serializer_class = PZModelSerializer
filterset_fields = {
'id': ['exact', 'in', 'contains', 'range']
}
Serializer:
class PZModelSerializer(serializers.ModelSerializer):
net_value_sum = serializers.SerializerMethodField('get_net_value_sum')
class Meta:
model = PZ
fields = '__all__'
def get_net_value_sum(self, obj):
return PZ.objects.aggregate(Sum('net_value'))['net_value__sum']
Response:
[
{
"id": 41,
"net_value_sum": 28.0,
"status": "C",
"net_value": "6.00"
},
{
"id": 42,
"net_value_sum": 28.0,
"status": "S",
"net_value": "10.00"
}
]
Desired response:
[
"net_value_sum": 16.0,
{
"id": 41,
"status": "C",
"net_value": "6.00"
},
{
"id": 42,
"status": "S",
"net_value": "10.00"
}
]
I managed to find a solution to this problem myself. You need to override the 'def list():' method while returning the filtered queryset. Then on this queryset in the view we return with the aggregated value. The code looks like this:
class PZListAPIView(ListAPIView):
queryset = PZ.objects.all()
serializer_class = PZModelSerializer
filterset_fields = {
'id': ['exact', 'in', 'contains', 'range']
}
def get_queryset(self):
return PZ.objects.all()
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
net_value = queryset.aggregate(Sum('net_value'))['net_value__sum']
return Response([{'net_value': net_value},serializer.data])

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)

How to add metadata(count,next,previous) to Response sent through viewset in Django rest framework

views.py
def list(self, request, *args, **kwargs):
queryset= User.objects.all().values_list('name','designation')
queryset=[{'name':i[0],'designation':i[1]} for i in queryset]
serializer=getuserserializer(queryset,many=True)
return Response(serializer.data)
serializer.py
class getuserserializer(serializers.Serializer):
name=serializers.CharField()
designation=serializers.CharField()
settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 100
}
I have looked through the solutions, which suggested to change settings.py as above.
Yet i am getting output like :
{
"result": [
{
"name": "Shubham Kumar",
"designation": "SE"
}
]
}
How to convert it to :
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"name": "Shubham Kumar",
"designation": "SE"
}
]
}
Your problem is that you are overriding the list method of your view. And your view, given the code, doesn't make a lot of sense - you're filtering by an id, accessing two values of a tuple/array, serializing that result and returning it.
If you check the ListModelMixin class you can understand how the pagination class is used:
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, 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