Due to the use of different serializers based on certain condition, i preferred to use APIView and override get function. I was content with APIView but now that i need pagination feature, I am having trouble to make it happen. That is why i want to switch to GenericAPIView but due to the use of multiple serializer I have no idea how can i do it.
class ItemsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
def get(self, request, format=None):
"""
Return a list of all devices of this user.
"""
reply = {}
try:
products = BaseItem.objects.owned_items().filter(owner=request.user)
reply['data'] = OwnedItemSerializer(products, many=True).data
items = BaseItem.objects.dev_items().filter(owner=request.user)
reply['data'].extend(ItemSerializer(items, many=True).data)
except:
reply['data'] = []
return Response(reply, status.HTTP_200_OK)
UPDATE
Another way i tried is
class ItemsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
def get(self, request, format=None):
"""
Return a list of all items with product of this user.
"""
reply = {}
print ('request', request.META.get('REMOTE_ADDR'))
try:
products = BaseItem.objects.owned_items().filter(owner=request.user)
reply['data'] = OwnedItemSerializer(products, many=True).data
items = BaseItem.objects.dev_items().filter(owner=request.user)
page = self.paginate_queryset(items)
print ('page', page) # i always get None even when pass url as api/items?page=1
if page is not None:
reply['data'].extend(ItemSerializer(page, many=True).data)
reply['data'].extend(ItemSerializer(items, many=True).data)
except:
reply['data'] = []
return Response(reply, status.HTTP_200_OK)
#property
def paginator(self):
"""
The paginator instance associated with the view, or `None`.
"""
if not hasattr(self, '_paginator'):
print (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.
"""
print ('queryset', queryset)
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)
No any way is working. Where have i done mistake?
Do you really need two serializers ?
I think it may be a better choice to use a single Serializer with a custom to_representation:
class ItemSerializer(ModelSerializer):
# Your fields
def to_representation(self, instance):
data = super(ItemSerializer, self).to_representation(instance)
request = self.context.get('request')
if request and instance.is_owned_by(request.user):
return self.owner_to_representation(data, instance) # TO IMPLEMENT
return data
Then, you can use a generic view. Your code is cleaner, simpler and you do not have to worry about the pagination:
class ItemList(generics.ListAPIView):
serializer_class = ItemSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
return BaseItem.objects.owned_items()| BaseItem.objects.dev_items()
This is as simple as importing your paginator, and calling it manually in the APIView.
class PollView(views.APIView):
authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication,)
paginator = CustomPagination()
def get(self, request):
queryset = Poll.objects.all()
context = self.paginator.paginate_queryset(queryset, request)
serializer = PollSerializer(context, many=True)
return self.paginator.get_paginated_response(serializer.data)
NOTE: Custom class is not necessary, you can simply import from rest_framework.pagination at the top of your script. I created a CustomPagination class, inheriting from PageNumberPagination, so that I could set the page_size query, as per docs - http://www.django-rest-framework.org/api-guide/pagination/
Related
I have for the moment a GET request where I have to send a body as a parameter but as in front it is not possible to make a GET request with a body I would like to pass my parameters as query parameters in the URL . How can I do this with the code I currently have?
My serializer class:
#dataclass
class PlantLinkParams:
plant_id: int
link: str
class LnkPlantPlantByLinkSerializer(serializers.Serializer):
plant_id = serializers.IntegerField()
link = serializers.CharField()
def create(self, validated_data):
return PlantLinkParams(**validated_data)
My view class :
class PlantLinkAPIView(APIView):
permission_classes = (AllowAnonymous,)
queryset = LnkPlantPlant.objects.prefetch_related("plant", "plant_associated")
def get(self, request):
params_serializer = LnkPlantPlantByLinkSerializer(data=request.data)
params_serializer.is_valid(raise_exception=True)
params = params_serializer.save()
data = self.getAllPlantAssociatedByLink(params)
serializer = ReadLnkPlantPlantSerializer(instance=data, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def getAllPlantAssociatedByLink(self, params: PlantLinkParams):
data = []
queryset = (
LnkPlantPlant.objects.filter(
plant=params.plant_id,
link=params.link,
)
)
for entry in queryset:
data.append(entry)
return data
You could do something simpler using a ListAPIView:
class PlantLinkAPIView(ListAPIView):
permission_classes = (AllowAnonymous,)
serializer_class = ReadLnkPlantPlantSerializer
def get_queryset(self):
# Retrieve the query parameters (?plant_id=xxx&link=yyy)
try:
plant_id = int(self.request.GET.get('plant_id'))
except (ValueError, TypeError):
# Prevents plant_id to be set if not a valid integer
plant_id = None
link = self.request.GET.get('link')
params = {}
if plant_id:
params['plant__id'] = plant_id
if link:
params['link'] = link
# Only filtering the queryset if one of the params is set
if params:
return LnkPlantPlant.objects.filter(**params)
return LnkPlantPlant.objects.all()
You don't need more than that to get your view working.
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
I have a below class
class Home(viewsets.ModelViewSet):
serializer_class = HomeSerializer
queryset = Home.objects.all()
#action(detail=False, methods=['get'])
def blog_data(self, request):
queryset = Blogs.objects.filter(identifier='blog')
serializer = BlogDataSerializer(data=queryset, many=True) #other serializer specific to this method
serializer.is_valid()
return Response(serializer.data) # need pagination here for this method only
def list(self, request):
....
return Response(serializer.data)
i have overwritten list method and i only want pagination in blog_data method to have pagination with page_size and page_number(page) to be given as query params.
example:
http://localhost:8000/home?page=1&page_size=5
how would i acheive it, i have read about pagination_class = HomePagination
but i dont want it to impact list method or any other method in this class, i only want pagination in my blog_data method
pagination.py is
class HomePagination(PageNumberPagination):
page_size_query_param = 'page_size'
def get_paginated_response(self, data):
response = {
'no_of_records': self.page.paginator.count,
'no_of_pages': self.page.paginator.num_pages,
'page_size': int(self.request.GET.get('page_size')),
'page_no': self.page.number,
'results': data
}
return Response(response, status=status.HTTP_200_OK)
I have managed to solve this problem by doing
class Home(viewsets.ModelViewSet):
serializer_class = HomeSerializer
queryset = Home.objects.all()
#action(detail=False, methods=['get'])
def blog_data(self, request):
self.pagination_class = HomePagination # added this
queryset = Blogs.objects.filter(identifier='blog')
page_data = self.paginate_queryset(queryset) #added this
serializer = BlogDataSerializer(data=queryset, many=True) #other serializer specific to this method
serializer.is_valid()
paginated_data = self.get_paginated_response(serializer.data) # added this
return Response(paginated_data) # need pagination here for this method only
def list(self, request):
....
return Response(serializer.data)
Hope someone will get help from this in future.
I have a APIView class for showing all the rents and posting and delete etc. Now i want search feature so i tried to use DjangoFilterBackend but it is not working. I see in documentation, it has been used with ListAPIView but how can i use it in APIView.
class Rent(APIView):
"""
List all the rents if token is not provided else a token specific rent
"""
serializer_class = RentSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields = ('city', 'place', 'property_category',)
search_fields = ('=city', '=place')
def get(self, request, token=None, format=None):
reply={}
try:
rents = Rental.objects.all()
if token:
rent = Rental.objects.get(token=token)
reply['data'] = self.serializer_class(rent).data
else:
reply['data'] = self.serializer_class(rents, many=True).data
except Rental.DoesNotExist:
return error.RequestedResourceNotFound().as_response()
except:
return error.UnknownError().as_response()
else:
return Response(reply, status.HTTP_200_OK)
when i search the rent with the following parameters in the url, i get all the rents, instead i should get only those rents that lies in city Kathmandu and place koteshwor
http://localhost:8000/api/v1/rents?city=Kathmandu&place=Koteshwor
To use the functionality of DjangoFilterBackend, you could incorporate the filter_queryset method from GenericViewSet, which is the DRF class that inherits from APIView and leads to all specific 'generic' view classes in DRF. It looks like this:
def filter_queryset(self, queryset):
"""
Given a queryset, filter it with whichever filter backend is in use.
You are unlikely to want to override this method, although you may need
to call it either from a list view, or from a custom `get_object`
method if you want to apply the configured filtering backend to the
default queryset.
"""
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
https://github.com/encode/django-rest-framework/blob/master/rest_framework/generics.py
Here If you are using APIView, There is nothing to do with filters.So you have to do like
get_data = request.query_params #or request.GET check both
Then
Rental.objects.filter(city=get_data['city'], place=get_data['place'])
In case someone is wondering how can we integrate django_filters filter_class with api_views:
#api_view(['GET'])
#permission_classes([permissions.IsAuthenticated])
def filter_data(request, format=None):
qs = models.YourModal.objects.all()
filtered_data = filters.YourFilter(request.GET, queryset=qs)
filtered_qs = filtered_data.qs
....
return response.Ok(yourData)
Adding to #ChidG's answer. All you need to do is override the DjangoFilterBackend's filter_queryset method, which is the entry point for the filter, and pass it the instance of your APIView. The important point to note here is you must declare filter_fields or filter_class on the view in order to get the filter to work. Otherwise it just return your queryset unfiltered.
If you're more curious about how this works, the class is located at django_filters.rest_framework.backends.py
In this example, the url would look something like {base_url}/foo?is_active=true
from django_filters.rest_framework import DjangoFilterBackend
class FooFilter(DjangoFilterBackend):
def filter_queryset(self, request, queryset, view):
filter_class = self.get_filter_class(view, queryset)
if filter_class:
return filter_class(request.query_params, queryset=queryset, request=request).qs
return queryset
class Foo(APIView):
permission_classes = (AllowAny,)
filter_fields = ('name', 'is_active')
def get(self, request, format=None):
queryset = Foo.objects.all()
ff = FooFilter()
filtered_queryset = ff.filter_queryset(request, queryset, self)
if filtered_queryset.exists():
serializer = FooSerializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response([], status=status.HTTP_200_OK)
I want to show the pagination feature in my API and I am using APIView with multiple serializers.
I know it is very easy to show pagination with ListView.
I have seen somewhere that combining ListModelMixin and APIView works but if my code is as follows:
class ListModelMixin(object):
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(serilaizer.data)
class ItemsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
def get(self, request, format=None):
"""
Return a list of all devices of this user.
"""
reply = {}
try:
products = BaseItem.objects.owned_items().filter(owner=request.user)
reply['data'] = OwnedItemSerializer(products, many=True).data
items = BaseItem.objects.dev_items().filter(owner=request.user)
reply['data'].extend(ItemSerializer(items, many=True).data)
except:
reply['data'] = []
return Response(reply, status.HTTP_200_OK)
How can I combine them so I can get paginated results?
Thanks in advance!
First things first, what you are currently doing is too complex without reason.
In order to achieve a "paginatable" queryset, it is preferred to change your owned_items() and dev_items() in simple filter combinations, rather than model methods. To clarify by example:
products = BaseItem.objects.filter(owner=request.user, owned=True)
instead of
products = BaseItem.objects.owned_items().filter(owner=request.user)
That way, you can produce a single queryset which will be easier to paginate:
user_items = BaseItem.objects.filter(
Q(owner=request.user, owned=True) |
Q(owner=request.user, dev=True)
)
Note 1: You can simplify things further if you like, but that gets out of scope of your question. As food for thought, check this out:
user_items = BaseItem.objects.filter(owner=request.user).distinct()
Note 2: You should use a single serializer for a single model because what you are doing adds complexity without benefit (high risk-low reward situation)
With the above mentioned and assumed:
There are some ways to achieve what you want here:
By utilizing GeneriAPIView and ListModelMixin you can refactor your class in such a way to have a .list() method with auto-pagination:
from rest_framework import mixins, generics
class ItemsAPIView(mixins.ListModelMixin, generics.GenericAPIView,):
permission_classes = (permissions.IsAuthenticated,)
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
serializer_class = OwnedItemSerializer
# You can define .get in here if you really need it.
# You can also override .list to add specific functionality
If you don't want to use the above, and you want to keep your APIView, then you can keep your get method and provide pagination for it as mentioned in this Q&A example:
class ItemsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
serializer_class = MyNewUnifiedSerializerClass
def get(self, request):
user_items = BaseItem.objects.filter(
owner=request.user
).distinct()
page = self.paginate_queryset(user_items)
if page is not None:
serializer = self.serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(user_items, many=True)
return Response(serializer.data)
# 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)