my filter isn't working Whenever I access http://localhost:8080/payables/invoices/?status=NOT_PAID It just returns all the invoices. I have no runtime error, the parameter I enter simply seems to be ignored. I really don't understand, other than that, it works well.
views.py
class InvoiceViewSet(viewsets.ViewSet):
serializer_class = InvoiceSerializer
filter_backend = filters.DjangoFilterBackend
filter_fields = ('status','supplier',)
def list(self,request,):
queryset = Invoice.objects.filter()
serializer = InvoiceSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = Invoice.objects.filter()
invoice = get_object_or_404(queryset, pk=pk)
serializer = InvoiceSerializer(invoice)
return Response(serializer.data)
class InvoiceItemViewSet(viewsets.ViewSet):
serializer_class = InvoiceItemSerializer
def list(self,request,invoice_pk=None):
queryset = InvoiceItem.objects.filter(invoice=invoice_pk)
serializer = InvoiceItemSerializer(queryset,many=True)
return Response(serializer.data)
def retrieve(self,request,pk,invoice_pk):
queryset = InvoiceItem.objects.filter(pk=pk,invoice=invoice_pk)
invoice_item = get_object_or_404(queryset,pk=pk)
serializer = InvoiceItemSerializer(invoice_item)
return Response(serializer.data)
url.py
from django.conf.urls import url, include
#viewset
from rest_framework_nested import routers
from payables.views import InvoiceViewSet,InvoiceItemViewSet
router = routers.SimpleRouter()
router.register(r'invoices', InvoiceViewSet,base_name='invoices')
invoice_item_router = routers.NestedSimpleRouter(router,r'invoices',lookup='invoice')
invoice_item_router.register(r'items', InvoiceItemViewSet, base_name='invoice_items')
urlpatterns = [
url(r'^',include(router.urls)),
url(r'^',include(invoice_item_router.urls))
]
It is because you are explicitly creating the queryset and hence the filter backend is never used:
queryset = Invoice.objects.filter()
I suggest looking at ModelViewSet. In that case you just have to pass queryset at the view level and rest will be taken care of.
instead of queryset = Invoice.objects.filter()
with queryset = self.filter_queryset(self.get_queryset()).filter()
instead of queryset = Invoice.objects.filter()
use queryset = self.get_queryset()
self.get_queryset() returns the filtered object list
Related
I have a custom pagination class in a separate file and until now I have been importing it in a ListAPIView, but this time I tried with APIView, but it didn't work.
My pagination class:
class CustomPagination(PageNumberPagination):
def get_paginated_response(self, data):
return Response({
'links': {
'next': self.get_next_link(),
'previous': self.get_previous_link()
},
'count': self.page.paginator.count,
'page_size' : 15,
'results': data
})
I am trying to use custom pagination because I can have the count of the objects as well.
My view where I try to implement the pagination:
from apps.products.api.pagination import CustomPagination
class CouponView(APIView):
permission_classes = [AllowAny]
#pagination_class = CustomPagination
def get(self,request,pk = None,*args,**kwargs):
id = pk
if id is not None:
abc = Coupons.objects.get(id=id)
serializer = CouponSerializer(abc)
return serializer.data
else:
abc = Coupons.objects.all()
paginator = CustomPagination()
result_page = paginator.paginate_queryset(abc, request)
serializer = CouponSerializer(result_page,many=True)
return Response (serializer.data,status=status.HTTP_200_OK)
APIView doesn't support this feature. You'll either need to leverage ListApiView or similar generics (such as RetrieveApiView), or use the ReadOnlyModelViewSet. These both support pagination, and give you get_object.
Your code seems to be implementing the basic ViewSet features (list + retrieve). The issue with the API views is that these two both use get, so they can't be combined into one view. Using a viewset will solve that.
class CouponReadOnly(ReadOnlyModelViewSet):
pagination_class = SomeClass
serializer_class = CouponSerializer
queryset = Coupon.objects.all()
And then use a Router in your urls.py to link it up, and let the router generate the routes.
# in your urls.py --------------
router = SimpleRouter()
router.register("coupons", CouponReadOnly, "coupon")
urlpatterns = [...] # existing routes
urlpatterns += router.urls
If you want to do this manually with 2 views, then you can use the generics and manually create the paths, like you already are:
class CouponListView(ListAPIView):
# you said this one already works
class CouponGetView(RetrieveAPIView):
queryset = Coupon.objects.all()
serializer_class = CouponSerializer
urlpatterns = [
path("coupons/", CouponListView.as_view(), name="coupon-list"),
path("coupons/<int:pk>", CouponGetView.as_view(), name="coupon-get"),
]
You should use get_paginated_response in GenericAPIView for custom pagination.
from apps.products.api.pagination import CustomPagination
from rest_framework.generics import GenericAPIView
class CouponView(GenericAPIView):
permission_classes = [AllowAny]
def get(self,request,pk = None,*args,**kwargs):
id = pk
if id is not None:
abc = Coupons.objects.get(id=id)
serializer = CouponSerializer(abc)
return serializer.data
else:
abc = Coupons.objects.all()
self.pagination_class = CustomPagination
page = self.paginate_queryset(abc)
serializer = serializer(page, many=True, context={'request': request})
return self.get_paginated_response(serializer.data)
However, let apply the correct RESTFul API to get list and detail, so you will not custom more.
List coupons: GET /api/coupons/
Get coupon detail: GET /api/coupons/<coupon_id/
class CouponView(ListAPIView, RetrieveAPIView):
permission_classes = [AllowAny]
pagination_class = CustomPagination
queryset = Coupons.objects.all()
serializer_class = CouponSerializer
I did the following and it worked:
def get(self,request,pk = None,*args,**kwargs):
id = pk
if id is not None:
abc = Coupons.objects.get(id=id)
serializer = CouponSerializer(abc)
return serializer.data
else:
abc = Coupons.objects.all()
paginator = CustomPagination()
result_page = paginator.paginate_queryset(abc,request)
serializer = CouponSerializer(result_page,many=True)
# return Response (serializer.data,status=status.HTTP_200_OK)
return paginator.get_paginated_response(serializer.data)
I am trying to do with ModelViewSet. I am facing this error now
Here is my viewset=>
class ShiftViewSet(viewsets.ModelViewSet):
queryset = Shift.objects.all()
serializer_class = ShiftSerializer()
# filter_backends = (filters.DjangoFilterBackend,)
# filterset_fields = ('shiftid',)
#action(methods=['get'], detail=False)
def newest(self, request):
newest = self.get_queryset().order_by('Created_DT').last()
serializer = self.get_serializer_class()(newest)
return Response(serializer.data)
#action(methods=['get'], detail=False)
def shiftsum(self, request):
query = (
Shift.objects.values('shiftid')
.annotate(shiftdesc=Max('shiftdesc'))
.annotate(overnight=Max('overnight'))
.annotate(isspecialshift=Max('isspecialshift'))
.annotate(ct=Count('*')) # get count of rows in group
.order_by('shiftid')
.distinct()
)
serializer = ShiftSummarySerializer(query,many=True)
return Response(serializer.data)
#action(methods=['get'], detail=False)
def byshiftid(self, request):
shiftid = self.request.query_params.get('shiftid',None)
query = self.get_queryset().filter(shiftid=shiftid)
serializer = ShiftSerializer(query,many=True)
return Response(serializer.data)
Here is my router and url =>
router.register('shifts_mas', ShiftViewSet, base_name='shifts')
path('api/', include(router.urls))
Normally I can call like /api/shifts_mas/ and I will get all record of shift but just now i got this error and i dont know why. May i know why?
You should have a serializer class and not an instance of the class as your serializer_class attribute
serializer_class = ShiftSerializer # No parenthesis here
views.py
class variable__list(ListAPIView):
"""
get:
returns a list of variable names
"""
serializer_class = VariableSerializer
pagination_class = PageNumberPagination
page_size = 5
def get_queryset(self):
return Variable.objects.all()
def get(self, request, format=None):
# base queryset
queryset = self.get_queryset()
# return serialized data
if queryset.exists():
serializer = VariableSerializer(queryset, many=True)
return Response(serializer.data)
else:
return Response({"Returned empty queryset"}, status=status.HTTP_404_NOT_FOUND)
settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 5,
}
When I go to the endpoint api/v1/variable/?page=1,
I get the same list returned of 100 results. My understanding is this should automatically be working when I set the pagination globally in settings.py, on top of that I have ALSO defined the paginator at the class level and still nothing is being paginated. What am I doing wrong here?
Remove get and get_queryset method, if there is no custom logic inside it.
class variable__list(ListAPIView):
"""
get:
returns a list of variable names
"""
queryset = Variable.objects.all()
serializer_class = VariableSerializer
pagination_class = PageNumberPagination
page_size = 5
If you have some custom code inside it, you need to send paginated response manually
def get(self, request, format=None)
paginator = PageNumberPagination()
paginator.page_size = 10
queryset = self.get_queryset()
result_page = paginator.paginate_queryset(queryset, request)
serializer = VariableSerializer(result_page, many=True)
return paginator.get_paginated_response(serializer.data)
How would I filter results based on a computed field from a Serializer? I tried treating it like any other field, but django doesn't like it.
Serializer
class ImageSerializer(serializers.ModelSerializer):
is_annotated = serializers.SerializerMethodField('has_annotation')
class Meta:
model = Image
fields = '__all__'
#staticmethod
def has_annotation(image):
return image.annotation_set.count() > 0
View
class ImageViewSet(viewsets.ModelViewSet):
serializer_class = ImageSerializer
lookup_field = 'id'
permission_classes = [
Or(IsAdmin),
IsAuthenticated
]
def get_queryset(self):
queryset = Image.objects
is_annotated_filter = self.request.query_params.get('is_annotated', None)
if is_annotated_filter is not None:
queryset = queryset.filter.annotate(
cnt=Count('annotation')).filter(cnt__gte=1)
queryset_order = get_queryset_order(self.request)
return queryset.order_by(queryset_order).all()
http://www.django-rest-framework.org/api-guide/filtering/#filtering-against-query-parameters
http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
I think you misunderstand: 1) serializer MethodField which major purpose is READ-ONLY and the serializer is not about to be used in filtering queryset.
I will filter it more like these:
from django.db.models import Count
queryset.filter.annotate(
cnt=Count('annotation_set')
).filter(cnt__gte=1)
but... you can go even better:
1) just annotate your queryset e.g in your ViewSet
from django.db.models import Count
queryset = Image.objects.annotate(
cnt_annotations=Count('annotation_set')
)
2) then in serializer do something like these:
#staticmethod
def has_annotation(image):
if hasattr(obj, 'has_annotation'):
return bool(obj.cnt_annotations)
else:
return None
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.