I have a ModelViewSet and I am using ModelSerializer. I am also using Pagination that is from django.core.
My views code snippet that works:
page_number = request.data.get("page")
data = Model.objects.filter()
paginator = Paginator(data, 100)
page = paginator.page(int(page_number))
serializer = self.serializer_class(page, many=True)
list_data = serializer.data
What I want to do is to validate the data. Meaning to do:
if serializer.is_valid(raise_exception=True):
list_data = serializer.validated_data
But I am getting error if I try to run the above code.
Error:
"Expected a list of items but got type \"Page\"."
How can I send the data to is_valid in this case?
Serializer classes accept a Model or QuerySet objects, Page object has a object_list attribute that contains a queryset of the model objects, therefore you need to pass page.object_list:
data = Model.objects.filter()
paginator = Paginator(data, 100)
page = paginator.page(int(page_number))
serializer = self.serializer_class(page.object_list, many=True)
serializer.is_valid(raise_exception=True)
Related
I am currently doing a project which requires pagination. A list of Images is provided to the user and whether they were Favorited or not by user.
Favorited Images are kept in a separate table.
In order to provide a list of images and ones that were Favorited by the user an annotate.
def _get_queryset(self, request):
user_favorited = DjImagingImagefavorites.objects.filter(ifv_img_recordid__exact=OuterRef('pk'), ifv_user_id__exact=request.user.profile)
queryset = DjImagingImage.objects.all().annotate(favorited=Exists(user_favorited))
return queryset
Then in the list function
def list(self, request):
images = self._get_queryset(request)
page = self.paginate_queryset(images) #Breaks here
The Query then throws an error.
]Cannot use an aggregate or a subquery in an expression used for the group by list of a GROUP BY clause.
Due to an oddities of how the paginate function performs the count and constructs an illegal sql statement.
Question is - Is their a better way about going to do this or should this work perfectly as I thought it should have?
A solution that I found works is coded below
def list(self, request):
images = self._get_queryset(request)
#page = self.paginate_queryset(images)
return self.paginate(images)
I create a wrapper called paginate
def paginate(self, queryset):
"""Calculate paginated QuerySet and wrap in a Response.
Args:
queryset - the queryset to be paginated
"""
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)
For those who are wondering the queryset I am constructing
def get_queryset(self):
"""Return the modified/prepared master QuerySet.
Returns:
QuerySet<DjImagingImage>: The modified master QuerySet
"""
return DjImagingImage.objects.annotate(
mine=FilteredRelation('djimaginguserspecifics',condition=Q(djimaginguserspecifics__usp_emp_employeeid=self.request.user.profile)),
usp_favorite=F('mine__usp_favorite'),
usp_inhistory=F('mine__usp_inhistory'),
usp_emp_employeeid=F('mine__usp_emp_employeeid'),
usp_lastviewed=F('mine__usp_lastviewed'),
comment_count=Count('djimagingimagecomment'))
I get a JSON response with data from multiple objects, run everything through the serializer and call serializer.save() which creates new objects but doesnt update the existing ones.
view
data = JSONParser().parse(request)
serializer = CellCESaveSerializer(data=data, many = True, context = {'descriptoridlist' : descriptoridlist}, partial=True)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, safe=False)
the serializer
class StringArrayField(ListField):
"""
String representation of an array field.
"""
def to_representation(self, obj):
obj = super().to_representation(obj)
# convert list to string
return ",".join([str(element) for element in obj])
def to_internal_value(self, data):
data = data.rstrip(",") # Needed because of JSON response
data = data.split(",") # Split string to list
data = [int(i) for i in data] # convert str to int
did = self.context['descriptoridlist']
x = (did,data) # Create Array
return super().to_internal_value(x)
class CellCESaveSerializer(serializers.ModelSerializer):
row = serializers.SlugRelatedField(slug_field="name",queryset=Descriptor.objects.all() ) # foreignkey mit namen
val = StringArrayField()
class Meta:
model = CellCE
fields = ('row','val')
How can i update the existing objects without creating new objects?
EDIT 1:
After adding #neverwalkaloner changes i realised i need to use ListSerializer to update multiple objects. I adjusted my code with help of the DRF documentation. (I changed the serializer to a ListSerializer, added the id Field and the update method.)
serializer:
class CellCESaveSerializer(serializers.ListSerializer):
row = serializers.SlugRelatedField(slug_field="name",queryset=Descriptor.objects.all()) # foreignkey mit namen
val = StringArrayField()
id = serializers.IntegerField()
class Meta:
model = CellCE
fields = ('row','val')
def update(self, instance, validated_data):
CellCE_mapping = {CellCE.id: CellCE for CellCE in instance}
data_mapping = {item['id']: item for item in validated_data}
# Perform creations and updates.
ret = []
for CellCE_id, data in data_mapping.items():
CellCE = CellCE_mapping.get(CellCE_id, None)
if CellCE is None:
ret.append(self.child.create(data))
else:
ret.append(self.child.update(CellCE, data))
# Perform deletions.
for CellCE_id, CellCE in CellCE_mapping.items():
if CellCE_id not in data_mapping:
CellCE.delete()
return ret
I get the error:
AssertionError: `child` is a required argument.
What am i missing?
Serializer's save() method check if serializer's self.instance is empty or not. If self.instance is empty save() will call create() method, otherwise it will call update() method.
So to update instance with serializer, you need to pass as it's first argument updating object:
obj = CellCE.objects.get(id='some_id') # Object wich will be updated
serializer = CellCESaveSerializer(obj, data=data, many = True, context = {'descriptoridlist' : descriptoridlist}, partial=True)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, safe=False)
No need to use ListSerializer because when you pass "many=True", it also use ListSerializer class. If you want to update once object, please include "id" field and pass its value in input data.
this part of my code fills the queryset with [category_object].subcats.all(). let subcats be a method of category object:
serializer:
class CatSrlz(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'label', )
View:
class CatsViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Category.objects.filter(parent=None)
serializer_class = CatSrlz
def retrieve(self, request, *args, **kwargs):
# return Response({'res': self.kwargs})
queryset = Category.objects.get(pk=str(self.kwargs['pk'])).subCats.all()
dt = CatSrlz(queryset, many=True)
return Response(dt.data)
and url:
router.register(r'cats', views.CatsViewSet)
it works but i'm pretty sure that there must be a more correct way of doing so
Is there one?
thanks
When retrieving a single object, you can use the get_object method in your view, which look like this in DRF without modifications :
def get_object(self):
"""
Returns the object the view is displaying.
You may want to override this if you need to provide non-standard
queryset lookups. Eg if objects are referenced using multiple
keyword arguments in the url conf.
"""
queryset = self.filter_queryset(self.get_queryset())
# Perform the lookup filtering.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
assert lookup_url_kwarg in self.kwargs, (
'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.' %
(self.__class__.__name__, lookup_url_kwarg)
)
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
So you could adapt the part where you get your object :
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
and add your subcat logic there.
By the way, I don't get why you are using
dt = CatSrlz(queryset, many=True)
Shouldn't "retrieve" return a single object?
I have written a code snippet like below, i need to achieve pagination in this, kindly let me know how is it possible. Also due to some reasons i want to use only function based views.
#api_view(['GET'])
#permission_classes([AllowAny])
def PersonView(request):
context={'request': request}
person_objects = Person.objects.all()
if len(person_objects) > 0:
person_data = PersonSerializer(person_objects, many=True, context=context)
return Response(person_data.data, status=status.HTTP_200_OK)
else:
return Response({}, status=status.HTTP_200_OK)
http://www.django-rest-framework.org/api-guide/pagination/
from rest_framework.pagination import PageNumberPagination
#api_view(['GET',])
#permission_classes([AllowAny,])
def PersonView(request):
paginator = PageNumberPagination()
paginator.page_size = 10
person_objects = Person.objects.all()
result_page = paginator.paginate_queryset(person_objects, request)
serializer = PersonSerializer(result_page, many=True)
return paginator.get_paginated_response(serializer.data)
You can also define custom pagination class by overriding PageNumberPagination
pagination.py
from rest_framework import pagination
class StandardResultsSetPagination(pagination.PageNumberPagination):
page_size = 10
page_query_param = 'page'
page_size_query_param = 'per_page'
max_page_size = 1000
it will help to define
page_size, page query custom parameters and max_page_size
views.py
from rest_api.pagination import StandardResultsSetPagination
#api_view(['GET',])
#permission_classes([AllowAny,])
def PersonView(request):
person_objects = Person.objects.all()
if len(person_objects)> 0:
paginator = StandardResultsSetPagination()
result_page = paginator.paginate_queryset(person_objects, request)
serializer = PersonSerializer(result_page, many=True)
return paginator.get_paginated_response(serializer.data)
else:
return Response({},status=status.HTTP_200_OK)
Eg:
Request
GET https://api.example.org/persons/?page=1&per_page=10
Response
HTTP 200 OK
{
"count": 1023
"next": "https://api.example.org/persons/?page=2&per_page=10",
"previous": null,
"results": [
…
]
}
The ACCEPTED answer has a BUG
A ModelSerializer accepts a queryset or an object, in the accepted answer the PersonSerializer is given the ouput of paginator.paginate_queryset which returns a list containing single element of class Page and thus the serializer will either return incomplete data or no data at all(know this because have tried and seen wrong results).
This can be easily fixed by passing the actual queryset to the serializer which in this case will be person_objects,
So the final code will be,
from rest_framework.pagination import PageNumberPagination
#api_view(['GET',])
#permission_classes([AllowAny,])
def PersonView(request):
paginator = PageNumberPagination()
paginator.page_size = 10
person_objects = Person.objects.all()
result_page = paginator.paginate_queryset(person_objects, request)
serializer = PersonSerializer(person_objects, many=True) # MAIN CHANGE IS HERE
return paginator.get_paginated_response(serializer.data)
This fixes the bug but passing the complete queryset to serializer will take lot of time for serializing since serializer works on lazy approach, but this performance issue is in itself a new question.
I have made a form to filter ListView
class SingleNewsView(ListView):
model = News
form_class = SearchForm
template_name = "single_news.html"
def get(self, request, pk, **kwargs):
self.pk = pk
pub_from = request.GET['pub_date_from']
pub_to = request.GET['pub_date_to']
return super(SingleNewsView,self).get(request,pk, **kwargs)
My form fields are pub_date_from and pub_date_to. When I run the site it says:
MultiValueDictKeyError .
I don't know what's going on. When I remove the two line of getting pub_from and pub_to the site works fine. I want these two values to filter the queryset.
On first request there is no form data submitted so request.GET would not have any data. So doing request.GET['pub_date_from'] will fail. You shall use .get() method
pub_from = request.GET.get('pub_date_from')
pub_to = request.GET.get('pub_date_to')
If these keys are not in the dict, will return None. So handle the such cases appropriately in your code.
Also, if you want to filter objects for ListView add get_queryset() method to return filtered queryset as explained here Dynamic filtering