'ListSerializer' when serializing a queryset using Django Rest Framework - python

I have a slightly complicated APIView which makes that I can't use a generic ListAPIView to return a queryset. But I can't seem to simply serialize a simple Django queryset using a ModelSerializer, even when I set many=True.
Somehow this doesn't work:
serializers.py:
class SomeModelSerializer(serializers.ModelSerializer):
class Meta:
model = SomeModel
fields = ['some_field']
views.py:
from rest_framework.response import Response
class SomeAPIView(APIView):
serializer_class = SomeInputSerializer
def post(self, request, format=None):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
# first some business logic, then return results
results = SomeModel.objects.all()
output_serializer = SomeModelSerializer(results, many=True)
return Response(output_serializer.data)
All I keep getting is: 'ListSerializer' object is not iterable.
What am I doing wrong?
Error:
/projectfolder/venv/lib/python2.7/site-packages/django/template/defaulttags.py in render
try:
values = self.sequence.resolve(context, True)
except VariableDoesNotExist:
values = []
if values is None:
values = []
if not hasattr(values, '__len__'):
values = list(values) ...
len_values = len(values)
if len_values < 1:
return self.nodelist_empty.render(context)
nodelist = []
if self.is_reversed:
values = reversed(values)
values = list(values) seems to be responsible for the error

Was running into the same problem as you did. I found a quick and simple fix for the error: Copy the serializer data to a new array and return that.
results = SomeModel.objects.all()
output_serializer = SomeModelSerializer(results, many=True)
data = output_serializer.data[:]
return Response(data)
This works for me, hopefully for you as well.

Below works for me using an as_view() url:
class ListCreateMemberViewSet(generics.ListCreateAPIView):
"""
API endpoint that allows multiple members to be created.
"""
queryset = Member.objects.none()
serializer_class = MemberSerializer
def get_queryset(self):
queryset = Member.objects.all()
return queryset
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, many=isinstance(request.data, list))
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
results = Member.objects.all()
output_serializer = MemberSerializer(results, many=True)
data = output_serializer.data[:]
return Response(data)

The error is a result of trying to run a list on a serializer directly.
For example, this would raise the above exception:
results = Member.objects.all()
output_serializer = MemberSerializer(results, many=True)
all_results = list(output_serializer) # this line here
all_results += output_serializer # or this line here
And this would not (difference is that we are listing output_serializer.data instead of output_serializer):
results = Member.objects.all()
output_serializer = MemberSerializer(results, many=True)
all_results = list(output_serializer.data)
all_results += output_serializer.data
I have the feeling that in the original question, the example code was not 100% matching the actual code.

Related

How can I fix my PATCH request to append to ArrayField, rather than delete and replace the whole object?

I have a problem with my PATCH request instance. Currently, data that is being PATCHED and sent from a request is overriding every item in my list of strings ArrayField inside my model object.
I need my patch request behavior to append to the rest of the items in ArrayField object, not delete/override.
How can I go about doing that?
I assume I need to override the patch method within RetrieveUpdateAPIView,
so I've started out here:
def patch(self, request, **kwargs):
item = self.kwargs.get('slug')
serializer = StockListSerializer(item, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
Response(serializer, status=status.HTTP_200_OK)
return Response(status=status.HTTP_400_BAD_REQUEST)
serializer.py:
class StringArrayField(serializers.ListField):
def to_representation(self, obj):
obj = super().to_representation(obj)
return ",".join([str(element) for element in obj])
def to_internal_value(self, data):
data = data.split(",")
return super().to_internal_value(data)
class StockListSerializer(serializers.ModelSerializer):
stock_list = StringArrayField()
class Meta:
model = Bucket
fields = ("stock_list",)
view.py
class EditBucketSymbols(generics.RetrieveUpdateAPIView):
permission_classes = [IsAuthenticated]
serializer_class = StockListSerializer
queryset = Bucket.objects.all()
def get_object(self, queryset=queryset, **kwargs):
item = self.kwargs.get('slug')
return get_object_or_404(Bucket, slug=item)
url.py:
path('bucket/symbols/<str:slug>/', EditBucketSymbols.as_view(), name='editsymbols')
model.py:
stock_list = ArrayField(models.CharField(max_length=6,null=True),size=30,null=True, blank=True)
You can make use of the field-lever validation technique here,
class StockListSerializer(serializers.ModelSerializer):
stock_list = StringArrayField()
class Meta:
model = Bucket
fields = ("stock_list",)
def validate_stock_list(self, stock_list):
existing_stock_list = []
if self.instance and self.instance.stock_list:
# Patch or Put request
existing_stock_list = self.instance.stock_list
return existing_stock_list + stock_list
I would do this within the .validate() method of the serializer. something like:
def validate(self, validated_data):
# Make sure that this only runs for Patch requests
if self.context["request"].method == "PATCH"
# Get list of existing stocks from instance and append incoming list
stock_list = self.instance.stock_list + validated_data["stock_list"]
# Replace data
validated_data["stock_list"] = stock_list
return validated_data

Django REST Framework filter backend with multiple value?

I want to get query list with one key more values. For example,
http://127.0.0.1:8000/management/device/model/list/?device_type=1&hardware_model_mother=master&hardware_model_mother=MasterModel1&hardware_model_child=SlaveModel1
Then i can get query list of device_type=1,hardware_model_child=SlaveModel1,hardware_model_mother=master
and device_type=1,hardware_model_child=SlaveModel1,hardware_model_mother=MasterModel1.
I need a list of fields that's why i didn't use the function in_bulk().
I found some filters in django-filter's doc, here's the link:
https://django-filter.readthedocs.io/en/latest/ref/filters.html
I choose MultipleChoiceFilter, it will use OR, that's what i wanted.
Here's my filter's code:
from django_filters import FilterSet, MultipleChoiceFilter
from Device_set.views.DeviceModelFilter import DeviceModelFilter
from .head import *
# one key for multiple values
class DeviceModelFilter(FilterSet):
MOTHER_CHOICES, CHILD_CHOICES = DeviceModelFilter().MyChoices()
hardware_model_mother = MultipleChoiceFilter(choices=MOTHER_CHOICES)
hardware_model_child = MultipleChoiceFilter(choices=CHILD_CHOICES)
class Meta:
model = device_model
fields = ['hardware_model_mother', 'hardware_model_child']
and my ListAPIView:
class DeviceModelListView(ListAPIView):
permission_classes = [Developer | IOTWatch | IsAdminUser]
serializer_class = DeviceModelListSerializer
queryset = device_model.objects.all()
filter_backends = (SearchFilter, DjangoFilterBackend,)
filter_class = DeviceModelFilter
search_fields = ('id', 'name')
filterset_fields = ('hardware_model_mother', 'hardware_model_child')
def list(self, request, *args, **kwargs):
dtype = self.request.query_params.get('device_type')
if dtype is not None:
queryset = self.queryset.filter(device_type__icontains=dtype)
else:
queryset = self.queryset
queryset = self.filter_queryset(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)
def get(self, request, *args, **kwargs):
return Response(Return_msg(self.list(request)))
when url is http://127.0.0.1:8000/management/device/model/list/?device_type=1&hardware_model_mother=master&hardware_model_mother=MasterModel1
The result is right:
right response
However, when url is http://127.0.0.1:8000/management/device/model/list/?device_type=1&hardware_model_child=SlaveModel1
It's wrong:
false
Traceback:
Traceback
It cost me about one day to solve this problem, could you please tell me how to fix it, or anther way to implement it?

How better way to create a function to pass a JSON by serializer in django rest?

I'm studying django rest framework and would like the create a function. In this function, I need to pass a list in JSON and update by serializer.
For help I wrote a code example below.
Serialzer example:
class GarageViewSet(viewsets.ModelViewSet):
queryset = Garage.objects.all()
serializer_class = GarageSerializer
model = Garage
class CarViewSet(RestrictedQuerysetMixin, viewsets.ModelViewSet):
queryset = Car.objects.all()
serializer_class = CarSerializer
model = Car
Well. I need to update a car list through the garage serializer. I'm thinking anything like this:
(example view)
class GarageViewSet(viewsets.ModelViewSet):
queryset = Garage.objects.all()
serializer_class = GarageSerializer
model = Garage
#action(detail=True, methods=['put'])
def update_car(self, request):
...
serializer = CarSerializer(queryset, many=True)
...
return Response(serializer.data)
Attempt 1:
Searching and reading the doc, I tried this way:
#action(methods=['put'], detail=False)
def update_car(self, request, *args, **kwargs):
if request.method == 'PUT':
data = JSONParser().parse(request)
serializer = CarSerializer(data=data, many=True)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data)
return JsonResponse(serializer.errors, status=400)
But I received this error:
non_field_errors:
["Expected a list of items but got type "dict"."]
Attempt 2:
With a #fxgx I tried too:
def update_car(self, request):
serializer = CarSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
validated_data = dict(list(serializer.validated_data.items()))
queryset = Car.objects.update(**validated_data)
return Response(CarSerializer(queryset, many=True).data)
But I received this error:
{
"detail": "Not found."
}
DRF serializers do not support bulk updates, you must pass an object instance to serializer to update it. What you can do is serializing data using serializer, updating objects with validated data and then serializing objects again to get response data:
class GarageViewSet(viewsets.ModelViewSet):
queryset = Garage.objects.all()
serializer_class = GarageSerializer
model = Garage
#action(detail=False, methods=['put'])
def update_car(self, request):
...
# Use partial=True for partial updates.
serializer = CarSerializer(data=request.data)
# Validate data.
serializer.is_valid(raise_exception=True)
# Get validated data in dictionary format.
validated_data = dict(list(serializer.validated_data.items()))
# Update objects
quertset.update(**validated_data)
...
return Response(CarSerializer(queryset, many=True).data)

Serialize two querysets with DjangoRestFramework

I'm trying to serialize more than one queryset, but I noticed that only one of them becomes serialized. This is the approach I'm currently trying.
class GetDetails(APIView):
def get(self, request):
todays_date = time.strftime('%Y-%m-%d')
#Get results according to specified criteria
queryset = People.objects.filter(date = todays_date, assigned_to = 1)
#Check if any querysets are available
if queryset:
#Iterate through each queryset, serialize and return a response
for person in queryset:
serializer=ASerializer(person)
return Response(serializer.data)
else:
return Response({'TODO':'TODO'})
First of all the way you are performing queryset is not right, you are iterating over queryset with person variable and you are not using person variable at all.
And for the question, use many=True attribute of serializer. It will create a list of serialized items for you.
like this:
class GetDetails(APIView):
def get(self, request):
todays_date = time.strftime('%Y-%m-%d')
queryset = People.objects.filter(date = todays_date, assigned_to = 1)
return Response(ASerializer(many=True).to_representation(queryset))
Maybe your code should be like this:
def get(self, request):
todays_date = time.strftime('%Y-%m-%d')
queryset = People.objects.filter(date = todays_date, assigned_to = 1)
if queryset.exists(): # Lazy check if entry Exists.
serializer=ASerializer(queryset, many=True)
return Response(serializer.data)
else:
return Response({'TODO':'TODO'})

Viewset's list() and Filtering conflict

When I set a ViewSet with both filtering and list() function -> filtering stops work:
class ClientViewSet(ModelViewSet):
serializer_class = ClientSerializer
queryset = Client.objects.all()
filter_class = ClientFilter
def list(self, request):
serializer = ClientSerializer(self.queryset, many=True)
return JsonResponse(serializer.data, safe=False)
Here is my filter class:
class ClientFilter(FilterSet):
type = CharFilter(name='type', lookup_expr='iexact')
parent_id = NumberFilter(name='parent__id')
title = CharFilter(name='title', lookup_expr='icontains')
class Meta:
model = Client
fields = {
'type', 'parent_id', 'title'
}
Please note
that without a list() method filtering works perfectly, I checked that thousand times. I'm 100 % sure that list() is exactly what causing the issue, I just don't know why and what exactly to do to solve this conflict.
You should use filter_queryset method:
def list(self, request):
queryset = self.filter_queryset(self.queryset)
serializer = ClientSerializer(queryset, many=True)
return JsonResponse(serializer.data, safe=False)

Categories

Resources