I have a very simple APIView, but I don't know how to setup pagination here.
In this scenario I select an Event with given pk, then I get all the NewsItems assigned to this Event.
pagination_class = LimitOffsetPagination works OK when I define queryset at the beginning in ListCreateAPIView, for ex. queryset = Event.objects.all() but not with custom get:
class EventNewsItems(APIView):
pagination_class = LimitOffsetPagination
def get(self, request, pk, format=None):
#user = request.user
event = Event.objects.get(pk=pk)
news = event.get_news_items().all()
serializer = NewsItemSerializer(news, many=True, context={'request':request})
response = Response(serializer.data, status=status.HTTP_200_OK)
return response
Solved:
def get(self, request, pk, format=None):
#user = request.user
event = Event.objects.get(pk=pk)
news = event.get_news_items().all()
paginator = LimitOffsetPagination()
result_page = paginator.paginate_queryset(news, request)
serializer = NewsItemSerializer(result_page, many=True, context={'request':request})
response = Response(serializer.data, status=status.HTTP_200_OK)
return response
Another option would be inheriting from the pagination class, with fewer changes on the view class:
from rest_framework.pagination import LimitOffsetPagination
class EventNewsItems(APIView, LimitOffsetPagination):
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 have created a Q&A style example on this subject.
As a sort summary:
By utilizing the Django Rest Frameworks source code and how they handle pagination, we create the same methods inside our view class and we use them, in the same way your solution uses the default methods:
Taken from the above mentioned doc:
from rest_framework.settings import api_settings
from rest_framework.views import APIView
class MyView(APIView):
queryset = OurModel.objects.all()
serializer_class = OurModelSerializer
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS # cool trick right? :)
# We need to override get method to achieve pagination
def get(self, request):
...
page = self.paginate_queryset(self.queryset)
if page is not None:
serializer = self.serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
... Do other stuff needed (out of scope of pagination)
# Now add the pagination handlers taken from
# django-rest-framework/rest_framework/generics.py
#property
def paginator(self):
"""
The paginator instance associated with the view, or `None`.
"""
if not hasattr(self, '_paginator'):
if self.pagination_class is None:
self._paginator = None
else:
self._paginator = self.pagination_class()
return self._paginator
def paginate_queryset(self, queryset):
"""
Return a single page of results, or `None` if pagination is disabled.
"""
if self.paginator is None:
return None
return self.paginator.paginate_queryset(queryset, self.request, view=self)
def get_paginated_response(self, data):
"""
Return a paginated style `Response` object for the given output data.
"""
assert self.paginator is not None
return self.paginator.get_paginated_response(data)
Another way to paginate is by using the Paginator class.
In addition to the answer query, you must set the number of pages to be displayed and the range of elements that the page will have.
The page number and item range can be provided as part of the request parameters or by the way you select.
Taking as an example the case of the question:
from django.core.paginator import Paginator
class EventNewsItems(APIView):
def get(self, request, pk, format=None):
#user = request.user
event = Event.objects.get(pk=pk)
news = event.get_news_items().all()
# -----------------------------------------------------------
page_number = self.request.query_params.get('page_number ', 1)
page_size = self.request.query_params.get('page_size ', 10)
paginator = Paginator(news , page_size)
serializer = NewsItemSerializer(paginator.page(page_number) , many=True, context={'request':request})
# -----------------------------------------------------------
response = Response(serializer.data, status=status.HTTP_200_OK)
return response
this approach is almost similar as "giantas" answer, but modifyed, in this case you can modify each API as you need with page_size, and you don't need to modify settings.py globally
from rest_framework.pagination import PageNumberPagination
class EventNewsItems(APIView, PageNumberPagination):
#this will output only 3 objects per page
page_size = 3
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)
Related
I am trying to query on jQuery Datatables list.
Right now I can fetch all records from the table, but I want to fetch records to whom their parent id matches.
For example, users have posts, so in my case it would be fetch posts where user id is lets say 1.
So I am trying to implement same thing for jQuery Datatables.
I can see data is being posted but I can't figure out how to query along with datatables, so that datatables filters are not affected by this change.
My current code:
class PartRequestViewSet(CommonViewset, generics.RetrieveAPIView):
queryset = PartRequest.objects.filter(deleted_at=None)
serializer_class = PartRequestSerializer
def list(self, request, *args, **kwargs):
records = request.GET.get('records', None)
# ID of the part
part_number_id = request.GET.get('part_number_id', None)
queryset = self.get_queryset()
queryset = self.filter_queryset(queryset)
page = self.paginate_queryset(queryset)
if page is not None and records is 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)
Now in above code I can get the part_number_id from request, but how can I filter records using filter_queryset(), so that only parts_requests are given back in datatables where part_number_id is 1
Update
Current helper function filter_queryset that is used in above code.
def filter_queryset(self, queryset):
format = self.request.GET.get('format', None)
if format == 'datatables':
self.filter_backends += (DatatablesFilterBackend,)
else:
self.filter_backends += (DjangoFilterBackend,)
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
There is django-filter extension. In your case it may be something like the following:
from rest_framework import viewsets
from django_filters.rest_framework import DjangoFilterBackend
from .models import Post
from .serializers import PostSerializer
class PostList(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['owner']
Then you can use get request like this: /posts?owner=3,
where 3 is user id and owner is ForeignKey in Post model to User.
The way I did is I added the following lines:
if part_number_id:
queryset = queryset.filter(part_number_id=part_number_id)
so the code looks like this now and it is working great. I'm open to better and more efficient options.
class PartRequestViewSet(CommonViewset, generics.RetrieveAPIView):
queryset = PartRequest.objects.filter(deleted_at=None)
serializer_class = PartRequestSerializer
def list(self, request, *args, **kwargs):
records = request.GET.get('records', None)
# ID of the part
part_number_id = request.GET.get('part_number_id', None)
queryset = self.get_queryset()
queryset = self.filter_queryset(queryset)
if part_number_id:
queryset = queryset.filter(part_number_id=part_number_id)
page = self.paginate_queryset(queryset)
if page is not None and records is 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)
I have tried many options, but pagination still doesn't work. I'm using generic-views and code like this:
class ListFood(generics.ListAPIView):
queryset = Food.objects.all()
serializer_class = FoodSerializer
def list(self,request):
queryset = self.get_queryset()
serializer = FoodSerializer(queryset, many=True)
return Response(serializer.data)
and in settings.py, I have written this:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS':'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20
}
I have tried to modify particular aspects of the pagination style, such as:
class StandardResultsSetPagination(PageNumberPagination):
page_size = 100
page_size_query_param = 'page_size'
max_page_size = 1000
class ListFood(generics.ListAPIView):
queryset = Food.objects.all()
serializer_class = FoodSerializer
pagination_class = LargeResultsSetPagination
def list(self,request):
queryset = self.get_queryset()
serializer = FoodSerializer(queryset, many=True)
return Response(serializer.data)
but it still didn't work.
My urls.py is:
app_name = "lab"
urlpatterns = [
re_path('^food/$', ListFood.as_view(), name="listfood"),
]
There is no error message, but when I request the URL http://127.0.0.1:8000/api/food/?page=1 or http://127.0.0.1:8000/api/food/?page=2
it's still not paginated. Django gives me 2000 pieces of data in JSON.
Original list implementation paginates response:
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)
Your class overrides list with custom implementation, that is why it does not return paginated response. Just don't override list.
This is how I create my article
`class CreateArticleView(ListCreateAPIView):
"""
Class handles creating of articles
"""
permission_classes = (IsAuthenticatedOrReadOnly,)
serializer_class = ArticleSerializer
renderer_classes = (ArticleJSONRenderer,)
queryset = Article.objects.all()
def list(self, request, *args, **kwargs):
queryset = Article.objects.all()
serializer = self.serializer_class(queryset, many=True)
return Response(serializer.data)
def post(self, request, *args, **kwargs):
article = request.data.get('article', {})
if self.request.user.is_verified is False:
message = error_messages['email_verification']
return Response(message, status=status.HTTP_401_UNAUTHORIZED)
context = {"request": request}
serializer = self.serializer_class(data=article, context=context)
serializer.is_valid(raise_exception=True)
serializer.save(author=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)`
`
This is how I get to view a single article, therefore I would like to fetch a single article and be able to highlight several parts of the article and manage to comment on them
class GetUpdateDeleteArticle(RetrieveUpdateDestroyAPIView):
permission_classes = (IsAuthenticated,)
renderer_classes = (ArticleJSONRenderer,)
queryset = Article.objects.all()
serializer_class = ArticleSerializer
lookup_field = 'slug'
#staticmethod
def validate_author(request, article):
if request.user.pk != article.author_id:
message = error_messages['unauthorised']
return Response(message, status.HTTP_403_FORBIDDEN)
def get(self, request, *args, **kwargs):
"""
:param request: user requests to get an article
:param kwargs: slug field is passed in the url
:return: data and response if article exists
"""
try:
article = Article.objects.get(slug=kwargs['slug'])
except Article.DoesNotExist:
message = error_messages['article_404']
return Response(message, status=status.HTTP_404_NOT_FOUND)
serializer = ArticleSerializer(
instance=article, context={'request': request})
return Response(serializer.data, status=status.HTTP_200_OK)
This is more of a design decision in the model level and less of an API issue. I would go with the following DB design:
A model Highlight that stores a reference to the article, the begining index of the highlighted part of the text and the ending index. So, something roughly like this:
class Highlight(models.Model):
article = models.ForeignKey(Article, related_name='highlights')
start = models.PositiveIntegerField()
end = models.PositiveIntegerField()
Then a Comment model. A common design is to use generic relations so that comments can be used with any model in your application. Something like this should suffice:
class Comment(TimeStampedModel):
text = models.TextField(blank=False)
author = models.ForeignKey(User, related_name='comments')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
You can now build an API around this design where clients create highlights by sending the article id, start and end indices of the highlighted text. Comments can also be created using the highlight's id. Retrieving the highlights for an article should also be fairly easy to implement.
I have this class view, but I am unable to modify the serializer data to insert more data (which is needed and needs to be populated automatically).
Because I am creating many instances at once, the serializer is based on kwargs['many'] = True.
Any idea on how I can add another field to each serializer data?
Thanks,
:
class ReservedServiceView(CreateListModelMixin, ModelViewSet):
queryset = ReservedService.objects.all()
serializer_class = ReservedServiceSerializer
authentication_classes = (authentication.TokenAuthentication,)
def perform_create(self, serializer):
# Create an event that is a Reflection of the Reserved Service
serializer_data = self.request.data
for reserved_service in serializer_data:
print("--------",reserved_service, flush=True)
service_id = reserved_service['original_service']
original_service = Service.objects.get(pk=service_id)
calendar_event = CalendarEvent()
calendar_event.name = original_service.name
calendar_event.description = original_service.description
calendar_event.date = reserved_service['date']
calendar_event.type_id = 1
calendar_event.start_time = reserved_service['start_time']
calendar_event.end_time = reserved_service['end_time']
calendar_event.location_id = original_service.location.id
calendar_event.save()
reserved_service['associated_event'] = calendar_event.id
print("**********1", serializer_data)
print("**********2", self.request.data)
serializer.save()
Based in:
class CreateListModelMixin(object):
def get_serializer(self, *args, **kwargs):
""" if an array is passed, set serializer to many """
if isinstance(kwargs.get('data', {}), list):
kwargs['many'] = True
return super(CreateListModelMixin, self).get_serializer(*args, **kwargs)
I am not able to properly get your question, but if your question is that you are not getting the extra fields which you added to the serializer in the response of the view, then here is the answer for it.
The response of this view is returned by create method of CreateModelMixin which passes serializer.data to the data param of Response. You cannot update serializer.data because it is an immutable object. So, to solve this you will have to override the create method as follows:
class ReservedServiceView(CreateListModelMixin, ModelViewSet):
queryset = ReservedService.objects.all()
serializer_class = ReservedServiceSerializer
authentication_classes = (authentication.TokenAuthentication,)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
my_data = {}.update(serializer.validated_data)
# Now you can work over the my_data and add extra fields to it and save it
# and instead of passing serializer.data we pass my_data to Response class
headers = self.get_success_headers(serializer.data)
return Response(my_data, status=status.HTTP_201_CREATED, headers=headers)
I am trying to return custom json with get_queryset but always get 404 error in response.
class TestViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Test.objects.all()
serializer_class = TestSerializer
def get_queryset(self):
if self.request.method == "GET":
content = {'user_count': '2'}
return HttpResponse(json.dumps(content), content_type='application/json')
If I delete everything starting from def I'll got correct response with standard json data. What I am doing wrong?
If you don't need a ModelViewSet and just want custom JSON on a GET request
You can also use an APIView, which doesn't require a model
class MyOwnView(APIView):
def get(self, request):
return Response({'some': 'data'})
and
urlpatterns = [
url(r'^my-own-view/$', MyOwnView.as_view()),
]
With a ModelViewSet
You've put the custom JSON into get_queryset, that's wrong. If you want to use a ModelViewSet, this by itself should be enough:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
This ModelViewSet comes with default implementations for .list(), .retrieve(), .create(), .update(), and .destroy(). Which are available for you to override (customize) as needed
Returning custom JSON from .retrieve() and/or .list() in ModelViewSet
E.g. to override .retrieve() to return custom view when retrieving a single object. We can have a look at the default implementation which looks like this:
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
So as an example to return custom JSON:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
def retrieve(self, request, *args, **kwargs):
return Response({'something': 'my custom JSON'})
def list(self, request, *args, **kwargs):
return Response({'something': 'my custom JSON'})
There are 2 ways to custom the response in Class-based views with ModelViewSet
Solution 1: custom in views.py
class StoryViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.AllowAny,)
queryset = Story.objects.all()
serializer_class = StorySerializer
def retrieve(self, request, *args, **kwargs):
# ret = super(StoryViewSet, self).retrieve(request)
return Response({'key': 'single value'})
def list(self, request, *args, **kwargs):
# ret = super(StoryViewSet, self).list(request)
return Response({'key': 'list value'})
Solution 2: custom in serializers.py (I recommend this solution)
class StorySerializer(serializers.ModelSerializer):
class Meta:
model = Story
fields = "__all__"
def to_representation(self, instance):
ret = super(StorySerializer, self).to_representation(instance)
# check the request is list view or detail view
is_list_view = isinstance(self.instance, list)
extra_ret = {'key': 'list value'} if is_list_view else {'key': 'single value'}
ret.update(extra_ret)
return ret