I've successfully implemented a custom pagination_class for a certain ModelViewSet in my app, but I'm having issues implemeting a separate custom pagination_class for a basic ViewSet.
Here's the working pagination_class and related ModelViewSet:
class Model1Pagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
max_page_size = 100
class Model1ViewSet(viewsets.ModelViewSet):
"""
API endpoint that returns the instances of Model1
"""
queryset = Model1.objects.all()
serializer_class = Model1Serializer
pagination_class = Model1Pagination
#list_route()
def extra(self, request)
....
return <something>
And this is the pagination_class and ViewSet which isn't working:
class Model2Pagination(PageNumberPagination):
page_size = 1
page_query_param = 'page_size'
max_page_size = 1
class Model2GenericViewSet(viewsets.ViewSet):
"""
API endpoint to return the instances of Model2
"""
def get_queryset():
return Model2.objects.all()
pagination_class = Model2Pagination
def list(self, request):
queryset = self.get_queryset()
serializer = Model2Serializer(queryset, many=True, context={'request': request})
return Response(serializer.data)
I suppose I could alter the Model2GenericViewSet to function as a ModelViewSet, but I was hoping to avoid the refactor, and the DRF docs mention:
Pagination is only performed automatically if you're using the generic views or viewsets
So I figured using a ViewSet would be alright. I didn't originally specify a get_queryset() on the ViewSet, and my initial thought was that I needed to do so, however adding that didn't affect behavior.
After some testing with the Model1Pagination, I'm not seeing pagination apply to the results of the extra() method in the Model1 viewset- it's not a method I need pagination for, so I only just found this out.
Does the doc quote above mean that pagination literally only applies to built-in viewset methods?
The ViewSet class inherits from APIView. The relation is:
View(in Django) -> APIView -> ViewSet
The ModelViewSetclass inherits from GenericViewSet . The relation is:
View(in Django) -> APIView -> GenericAPIView -> GenericViewSet -> ModelViewSet
pagination_class is add in GenericAPIView, so you can't use it in a class inherits from APIView.You can try viewsets.GenericViewSet.
Return paged response like:
def list(self, request, *args, **kwargs):
students = Student.objects.all()
page = self.paginate_queryset(students)
if page is not None:
serializer = StudentSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = StudentSerializer(students, many=True)
return Response(serializer.data)
You have to,
...
return self.get_paginated_response(serializer.data)
manually, if you overwrite list().
Related
I am trying to use PageNumberPagination and FilterSet in APIView.
But I have an error below in my code.
object of type 'ListSerializer' has no len()
How to implement this?
Here are the code:
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = '__all__'
class MyFilter(filters.FilterSet):
class Meta:
model = MyModel
fields = '__all__'
class MyAPIView(views.APIView, PageNumberPagination):
def get(self, request, *args, **kwargs):
filterset = MyFilter(request.query_params, queryset=MyModel.objects.all())
if not filterset.is_valid():
raise ValidationError(filterset.errors)
serializer = MySerializer(instance=filterset.qs, many=True)
paginate_queryset = self.paginate_queryset(serializer, request, view=self)
return Response(paginate_queryset.data)
Django 3.2.6
django-filter 22.1
djangorestframework 3.12.4
Python 3.8
You should not try to make your object inherit from APIView and PageNumberPagination, as one is a view, and the other one, a paginator.
If you want to keep your code as simple as possible, just use a ListAPIView:
class MyAPIView(ListAPIView):
queryset = MyModel.objects.all()
serializer_class = MySerializer
filterset_class = MyFilter
pagination_class = PageNumberPagination
If you want to get this pagination as the default one, you can even set it as the default pagination style once so you don't have to define it in each of the views that use it.
Apply Pagination on ApiView
views.py
from rest_framework.pagination import PageNumberPagination
class EventNewsItems(APIView, PageNumberPagination):
def get(self, request, pk, format=None):
event = Event.objects.get(pk=pk)
news = event.get_news_items().all()
results = self.paginate_queryset(news, request, view=self)
serializer = NewsItemSerializer(results, many=True)
return self.get_paginated_response(serializer.data)
I changed the code and it works
class MyAPIView(views.APIView, PageNumberPagination):
def get(self, request):
filterset = MyFilter(request.query_params, queryset=MyModel.objects.all())
results = self.paginate_queryset(filterset.qs, request, view=self)
serializer = Serializer(results, many=True)
return self.get_paginated_response(serializer.data)
I am using Django APIView to include all my CRUD operation in a single api endpoint. But later on I had to use filtering logic based on the query parameters that have been passed. Hence I found it difficult to include it in a get api of APIView and made a separate api using generic view, ListAPiview.
Here is the view:
class LeadsView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, pk=None, *args, **kwargs):
id = pk
if id is not None:
abc = Lead.objects.get(id=id)
serializer = LeadSerializer(abc)
return serializer.data
def post(self,request,*args,**kwargs):
abc = LeadSerializer(data=request.data,many=True)
if abc.is_valid():
abc.save()
return Response(abc.data, status=status.HTTP_201_CREATED)
return Response(abc._errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request,pk, *args, **kwargs):
Now, I have to use filter class and also some custom filtering logic, I need to use get_queryset. Hence I have to create another api just for get method which I dont want.
class LeadAPIView(ListAPIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
queryset = Lead.objects.all().order_by('-date_created')
serializer_class = LeadSerializer
filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
pagination_class = CustomPagination
# filterset_fields = [ 'email','first_name','last_name','phone']
filterset_class = LeadsFilter
def get_queryset(self):
source = self.request.GET.get("source", None) #
lead_status = self.request.GET.get("lead_status", None)
if source is not None:
source_values = source.split(",")
if lead_status is not None:
lead_status_values= lead_status.split(",")
return Lead.objects.filter(source__in=source_values,lead_status__in=lead_status_values)
else:
return Lead.objects.filter(source__in=source_values)
elif lead_status is not None:
lead_status_values = lead_status.split(",")
if source is not None:
source_values = source.split(",")
return Lead.objects.filter(lead_status__in=lead_status_values,source__in=source_values)
else:
return Lead.objects.filter(lead_status__in=lead_status_values)
return Lead.objects.all()
My question is, can I use get_queryset in the APIView instead of making another api?? Also, if I can use it, I assume I cant import filterset_class = LeadsFilter and also pagination? What will be the best approach??
My urls:
path('leads', LeadAPIView.as_view(), name='leads'),
path('lead', LeadsView.as_view(), name='leads-create'),
path('lead/<int:pk>', LeadsView.as_view()),
APIView stands for MVT framework. There are 2 types of cases.
If you want to return response to your django templates, you use views.
In cases of returning json, xml (in short response) objects, you use viewset terminology. Viewsets supports filter-class, pagination-class, serialization, queryset, (custom mixins and many more).
p.s. in viewset if you wanted to overwrite default queryset, you define get_queryset method. Views don't support this. Also please check for #action decorators in django.
I have a django project I am working on that needs a users and account model. I am using and integrating Django Rest Framework. I was initially using individual API generic views from DRF. I am thinking of converting the individual generic views into a view set. I was able to do it for the user model. I wan to convert the Account model views to a view set.
My issue is that I have two versions of the same ListAPIView for the profile model. The top View lists all of the accounts in the database and the second one list all the accounts for an idividual user based on the User__Username foreignkey in the Account model.
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
lookup_field = 'username'
class AccountListView(ListAPIView):
queryset = Account.objects.all()
serializer_class = AccountSerializer
class AccountUserListView(ListAPIView):
queryset = Account.objects.all()
serializer_class = AccountSerializer
filter_backends = (filters.DjangoFilterBackend,)
filterset_fields = ('user', '_id', '_class')
def get_queryset(self):
return self.queryset.filter(user_username=self.kwargs.get('username'))
It says that I can specifically define the properties of a view within the viewset but I want to define two versions of the ListAPIView for the single model. Is there a way to double define the same view in a single viewset.
I basically want to define both of my Account ListAPIViews in the same viewset. How should i go about doing that if it is possible??
you can use the #action decorator to define.
class UserViewSet( mixins.ListModelMixin, viewsets.GenericViewSet):
"""
A simple ViewSet for listing or retrieving users.
"""
def list(self, request):
queryset = User.objects.all()
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
#acttion(method="get", details=False, serializer_class=AccountSerializer)
def account_list(self, request, *args, **kwargs):
queryset = User.objects.all()
data = self.get_serializer_class().(instance=queryset, many=True).data
return response.Response(data)
I'm trying to implement a custom pagination class on a ViewSet, as per the docs, but the pagination settings are just not doing a single thing. Here's the code for my ViewSet.
from rest_framework import status, permissions, viewsets
from rest_framework.pagination import PageNumberPagination
class ProductViewSetPagination(PageNumberPagination):
page_size = 5
page_size_query_param = 'page_size'
max_page_size = 1000
class ProductViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = ProductSerializer
pagination_class = ProductViewSetPagination
# ...
def list(self, request):
#get_queryset is also overridden to accept filters in query_params
queryset = self.get_queryset()
if not queryset.exists():
return Response(status=status.HTTP_204_NO_CONTENT)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
I even added some defaults to the settings.py file, but I'm still getting all the product instances on a single page on the product-list view. I've tried adding page and page_size query parameters to the URL; this doesn't change anything.
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
What am I missing?
The pagination added in the super list, so try:
def list(self, request):
#get_queryset is also overridden to accept filters in query_params
queryset = self.get_queryset()
if not queryset.exists():
return Response(status=status.HTTP_204_NO_CONTENT)
return super(ProductViewSet, self).list(request)
I have a case where the values for a serializer field depend on the identity of the currently logged in user. I have seen how to add the user to the context when initializing a serializer, but I am not sure how to do this when using a ViewSet, as you only supply the serializer class and not the actual serializer instance.
Basically I would like to know how to go from:
class myModelViewSet(ModelViewSet):
queryset = myModel.objects.all()
permission_classes = [DjangoModelPermissions]
serializer_class = myModelSerializer
to:
class myModelSerializer(serializers.ModelSerializer):
uploaded_by = serializers.SerializerMethodField()
special_field = serializers.SerializerMethodField()
class Meta:
model = myModel
def get_special_field(self, obj):
if self.context['request'].user.has_perm('something.add_something'):
return something
Sorry if it wasn't clear, from the DOCs:
Adding Extra Context
Which says to do
serializer = AccountSerializer(account, context={'request': request})
serializer.data
But I am not sure how to do that automatically from the viewset, as I only can change the serializer class, and not the serializer instance itself.
GenericViewSet has the get_serializer_context method which will let you update context:
class MyModelViewSet(ModelViewSet):
queryset = MyModel.objects.all()
permission_classes = [DjangoModelPermissions]
serializer_class = MyModelSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
context.update({"request": self.request})
return context
For Python 2.7, use context = super(MyModelViewSet, self).get_serializer_context()
For Function based views you can pass request or user as follows:
serializer = ProductSerializer(context = {"request": request}, data=request.data)
Your Serializer may look like:
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ["id"]
def create(self, validated_data):
user = self.context["request"].user
print(f"User is: {user}")
Feel free to inform if there is any better way to do this.
just use get_serializer() in your viewsets
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
Return parent context in overrided function get_serializer_context will make it easy to access request and its data.
class myModelViewSet(ModelViewSet):
queryset = myModel.objects.all()
permission_classes = [DjangoModelPermissions]
serializer_class = myModelSerializer
def get_serializer_context(self):
"""
pass request attribute to serializer
"""
context = super(myModelViewSet, self).get_serializer_context()
return context
This is very stable as every time we request viewset, it returns context as well.
the values for a serializer field depend on the identity of the currently logged in user
This is how I handle such cases in my ModelViewSet:
def perform_create(self, serializer):
user = self.request.user
if user.username == 'myuser':
serializer.data['myfield'] = 'something'
serializer.save()
Simply add this 2 line method in your class and you are good to go.
def get_serializer_context(self):
return {'request': self.request}
since the posted answers had partial correctness, summarizing here in the interest of completeness.
override get_serializer_context..AND
use get_serializer in your views instead of manually calling the serializer