Django Rest Framework pagination not working - django

These are my version
Django==3.0.2
djangorestframework==3.11.0
and this is my setting
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 10
}
and this is my views:
class CostList(ListCreateAPIView):
serializer_class = CostSerializers
def get_queryset(self):
cost = Cost.objects.filter(
id='filtered with one of my id'
)
return cost
this is my serializer:
class CostSerializers(ModelSerializer):
class Meta:
model = Cost
fields = '__all__'
Everything is working fine but the only issue is pagination. I have 100+ entry in cost model and I see it is rendering all the entry together, not paginating item following my settings

class CostList(ListCreateAPIView):
serializer_class = CostSerializers
def get_queryset(self):
cost = Cost.objects.filter(id='filtered with one of my id')
return cost
def get(self, request, *args, **kwargs):
qs = self.get_queryset()
page = self.paginate_queryset(qs)
return self.get_paginated_response(page)
Try this.

Related

partial update in django rest viewset(not modelviewset)

i am beginner in django and i was trying to learn work with djangorest viewset(not modelviewset)
and i cant find any resource to undrestand it.
i want to learn how to write a partial_update with viewset.
here is what i have tried:
models.py :
class UserIdentDocs(models.Model):
owner = models.ForeignKey(User,on_delete=models.CASCADE, related_name = "user_ident_Docs")
code = models.PositiveBigIntegerField(null=True)
img = models.ImageField(null=True)
video = models.FileField(null=True)
is_complete = models.BooleanField(default=False)
serializers.py:
class AdminUserIdentSerializer(serializers.ModelSerializer):
class Meta:
model = UserIdentDocs
fields = [
"owner",
"code",
"img",
"video",
"is_complete",
]
views.py:
from rest_framework.parsers import MultiPartParser, FormParser
class UserDocAdminViewSet(viewsets.ViewSet):
"""User docs admin view set"""
permission_classes = [AllowAny]
parser_classes = (MultiPartParser, FormParser)
serializer_class = AdminUserIdentSerializer
queryset = UserIdentDocs.objects.filter(is_complete=False)
def list(self, request):
serializer = self.serializer_class(self.queryset, many=True)
return Response(serializer.data)
def retrive(self, request, pk=None):
doc_object = get_object_or_404(self.queryset, pk=pk)
serializer = self.serializer_class(doc_object)
return Response(serializer.data)
def create(self, request ):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data,status=status.HTTP_201_CREATED)
def partial_update(self, request, pk=None):
doc_object = get_object_or_404(self.queryset, pk=pk)
serializer = self.serializer_class(doc_object,data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({"detail":"item updated succesfuly"}, status=status.HTTP_205_RESET_CONTENT)
urls.py:
urlpatterns = [
...
# admin viewset for docs
# list of accounts with is_complete=False
path("admin/userdocs/", UserDocAdminViewSet.as_view({"get":"list", "post":"create"}), name="user-docs-list"),
path("admin/userdocs/<int:pk>", UserDocAdminViewSet.as_view({"get":"retrive","patch":"partial_update"}), name="user-docs-detail"),
]
i can create user in browsable api but when i want to use partial update i can't even see the fields in browsable api and i only see this :
media type: multipart/form-data
content:this is what i see

Extending multiple CustomViewsets - as readonly with Django

in our project, our lead developer assigned a task to refactor some viewsets in our project.
Create a readonly Asset view that will return all defaults and romy assets
So the original code looks like this
class DefaultAssetViewSet(viewsets.ModelViewSet):
queryset = DefaultAsset.objects.all()
serializer_class = DefaultAssetSerializer
permission_classes = [IsAdminUser]
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
filterset_fields = ['name']
search_fields = ['name', 'default_value']
#action(detail=False, methods=['get'])
def defaults(self, request):
defaults = {}
for d in self.queryset.all():
defaults[d.name] = d.default_value
return Response({'defaults': defaults})
def destroy(self, request, *args, **kwargs):
try:
return super().destroy(request, *args, **kwargs)
except models.ProtectedError:
return Response(
{'detail': ErrorDetail('Unable to perform this action.')},
status=status.HTTP_403_FORBIDDEN)
class RomyAssetViewSet(viewsets.ModelViewSet):
queryset = RomyAsset.objects.all()
serializer_class = RomyAssetSerializer
permission_classes = [IsAdminUser]
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
filterset_fields = [
'romy', 'default_asset'
]
search_fields = [
'romy', 'default_asset'
]
So my first idea to come to my mind is to extend these two Views into AssetViewSet class
class AssetViewSet(RomyAssetViewSet, DefaultAssetViewSet,viewsets.ReadOnlyModelViewSet):
""" some code here"""
Is it possible to extend custom viewsets like these? And also how to implement get or list for both RomyAssetViewet and DefaultAssetViewset inside AssetViewsetClass?

Django REST framework, serializer performance degradation

I have a simple list API view which is using serializer:
class ListCreateDeploymentView(
generics.ListCreateAPIView
):
permission_classes = (IsAuthenticated,)
renderer_classes = [JSONRenderer]
content_negotiation_class = IgnoreClientContentNegotiation
def get_queryset(self):
queryset = Deployment.objects.all()
return queryset
def list(self, request, version):
queryset = self.get_queryset()
serializer = DeploymentListSerializer(queryset, many=True)
data = serializer.data
return Response(data)
Serializer is simple:
class DeploymentListSerializer(serializers.ModelSerializer):
class Meta:
model = Deployment
fields = (
'id',
'query',
'config',
'started_at',
'finished_at',
'status',
'project',
)
read_only_fields = (
'id',
'query',
'config',
'started_at',
'finished_at',
'status',
'project',
)
Then I do a local load test with 10 users and delay 1s each execution so target rps is 10 req/s and see this picture with a clear performance degradation after few minutes
What means If I open 10 tabs in browser with ajax request every second to this endpoint the server will get unresponsive in a minute:
Then I used recommendations from here and used read-only regular serializer:
class DeploymentListSerializer(serializers.ModelSerializer):
# commands = CommandListSerializer(read_only=True, many=True)
# clients = ClientSimpleSerializer(read_only=True, many=True)
id = serializers.IntegerField(read_only=True)
query = serializers.CharField(read_only=True)
config = serializers.CharField(read_only=True)
started_at = serializers.DateTimeField(read_only=True)
finished_at = serializers.DateTimeField(read_only=True)
status = serializers.IntegerField(read_only=True)
project = serializers.CharField(read_only=True)
class Meta:
model = Deployment
fields = (
'id',
'query',
'config',
'started_at',
'finished_at',
'status',
'project',
)
The situation became even worse:
Finally, if I remove serialization:
def list(self, request, version):
queryset = self.get_queryset()
data = queryset.values(
'id', 'query', 'config', 'started_at', 'finished_at',
'status', 'project'
)
return Response(data)
And do same test again the performance getting much better(expected), but also stable:
The problem is I need serialization because the task is a bit more complicated and I need to return nested objects, but it`s already falling on such an easy example.
What do I wrong?
UPD: same bad picture if I use the function-based view:
#api_view(['GET'])
def get_deployments(request, version):
queryset = Deployment.objects.all()
serializer = DeploymentCreateSerializer(queryset, many=True)
data = serializer.data
return Response(data)

How do you paginate a viewset using a paginator class?

According to the documentation:
Pagination is only performed automatically if you're using the generic views or viewsets
But this doesn't seem to be the case. Here's what I have for my viewset:
views.py
class EntityViewSet(viewsets.ViewSet):
def list(self, request):
queryset = Entity.objects.all()
serializer = EntitySerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = Entity.objects.all()
entity = get_object_or_404(queryset, pk=pk)
serializer = EntitySerializer(entity)
return Response(serializer.data)
Here's my urls
entity_list = views.EntityViewSet.as_view({'get':'list'})
entity_detail = views.EntityViewSet.as_view({'get':'retrieve'})
...
url(r'^entity/$', entity_list, name='entity-list'),
url(r'^entity/(?P<pk>[0-9]+)/$', entity_detail, name='entity-detail'),
...
This is my pagination class
class PagePaginationWithTotalPages(pagination.PageNumberPagination):
page_size = 30
page_size_query_param = 'page_size'
max_page_size = 1000
def get_paginated_response(self, data):
return Response({
'next': self.get_next_link(),
'previous': self.get_previous_link(),
'count': self.page.paginator.count,
'total_pages': self.page.paginator.num_pages,
'results': data
})
and I set it in settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated'
],
'DEFAULT_PAGINATION_CLASS': 'myapp.pagination.PagePaginationWithTotalPages',
'UNICODE_JSON': False,
}
Now while this works for ListAPIView it doesn't appear to work for my viewset. Is there a step that I'm missing?
For reference this works fine:
class EntitiesView(ListAPIView):
serializer_class = EntitySerializer
def get_queryset(self):
parameters = get_request_params(self.request)
qs = qs.filter(**parameters).distinct()
return qs
EDIT:
Changing it to use ModelViewset appears to have done the trick
class EntityViewSet(viewsets.ModelViewSet):
queryset = Entity.objects.all()
serializer_class = EntitySerializer
def list(self, request):
queryset = self.queryset
parameters = get_request_params(self.request)
if 'ordering' in parameters:
queryset = queryset.order_by(parameters['ordering'])
del parameters['ordering']
queryset = queryset.filter(**parameters).distinct()
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 retrieve(self, request, pk=None):
entity = self.get_object()
serializer = EntitySerializer(entity)
return Response(serializer.data)
Pagination is only performed automatically if you're using the generic views or viewsets
You seem to have missed the next sentence from the documentation:
If you're using a regular APIView, you'll need to call into the pagination API yourself to ensure you return a paginated response. See the source code for the mixins.ListModelMixin and generics.GenericAPIView classes for an example.
As pointed by the documentation, the ListModelMixin will show you that you didn't call the paginate_queryset / get_paginated_response and thus did bypass the pagination as well as the filtering.
As pointed in the comments, you should consider ModelViewSet and define the required queryset to get it automatically included.

How to return a list of objects with an extra field in Json (Django Rest Framework)

I'm using ModelViewSet to generate a list of items by get_queryset. I want to return a json with the list of objects and an extra field (the sum total time of the DurationField).
Example:
{
"total_time":"00:10:00",
"objects":[
{
"pk":1,
"title":"Title",
"duration": "00:05:00"
},
{
"pk":1,
"title":"Title",
"duration": "00:05:00"
}
]
}
How can I do this? Here's my code below.
ModelViewSet:
class ModelViewSet(viewsets.ModelViewSet):
queryset = Model.objects.all()
serializer_class = ModelSerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
user = self.request.user
list = Model.objects.filter(user=user)
total_time = .. ## sum the duration of list of objects
## I want to return the list and total_time
return list
serializers.py:
class ModelSerializer(serializers.ModelSerializer):
pk = serializers.IntegerField(read_only=True)
user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), required=False, allow_null=True)
project = serializers.PrimaryKeyRelatedField(queryset=Project.objects.all(), required=False, allow_null=True)
class Meta:
model = Model
fields = ('pk', 'title', 'user', 'project', 'duration')
urls:
router = DefaultRouter()
router.register(r'^', views.ModelViewSet)
urlpatterns = [
url(r'^api', include(router.urls)),
]
One way would be to subclass the paginator (the default pagination class is PageNumberPagination) and tweaking the get_paginated_response. This is wholly untested, but maybe something like this:
from rest_framework.pagination import PageNumberPagination
class CustomPageNumberPagination(PageNumberPagination):
def get_paginated_response(self, data, total_time):
return Response(OrderedDict([
('count', self.page.paginator.count),
('next', self.get_next_link()),
('previous', self.get_previous_link()),
('total_time', total_time),
('results', data)
]))
class ModelViewSet(viewsets.ModelViewSet):
queryset = Model.objects.all()
serializer_class = ModelSerializer
permission_classes = (IsAuthenticated,)
pagination_class = CustomPageNumberPagination
def get_queryset(self):
user = self.request.user
list = Model.objects.filter(user=user)
self.total_time = .. ## sum the duration of list of objects
return list
def get_paginated_response(self, data):
return self.paginator.get_paginated_response(data, self.total_time)
Anyhow, hopefully this helps a bit.
Good luck!
Finally I did it! But I'm not sure if it's the best practice. If anyone has a better idea, please share!
class ModelJSONRenderer(JSONRenderer):
"""
Add "objects" and "total_time" in Json
"""
def render(self, data, accepted_media_type=None, renderer_context=None):
data = {'objects': data, 'total_time': renderer_context['total_time']}
return super(ModelJSONRenderer, self).render(data, accepted_media_type, renderer_context)
ModelViewSet:
class ModelViewSet(viewsets.ModelViewSet):
queryset = Model.objects.all()
serializer_class = ModelSerializer
permission_classes = (IsAuthenticated,)
renderer_classes = (ModelJSONRenderer, )
def get_queryset(self):
user = self.request.user
list = Model.objects.filter(user=user)
self.total_time = .. ## sum the duration of list of objects
return list
def get_renderer_context(self):
"""
Returns a dict that is passed through to Renderer.render(),
as the `renderer_context` keyword argument.
"""
return {
'view': self,
'args': getattr(self, 'args', ()),
'kwargs': getattr(self, 'kwargs', {}),
'request': getattr(self, 'request', None),
'total_time': self.total_time
}