Filtering django REST results by SerializerMethodField - django

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

Related

filtering CreateAPIView view with Django

I have a CreateAPIView class based view and I need to filter the queryset defined in the serializer at run time by something that is available to me from the request. Is there any way to do that?
The serializer has this code:
class AssignWorkItemSerializer(serializers.Serializer, WorkitemSerializerMixin):
assigneeIds = serializers.ListSerializer(
child=serializers.PrimaryKeyRelatedField(
queryset=get_user_model().objects.filter(userrole__role__name='analyst')
),
source='assignees'
)
It is failing because that query returns multiple rows.
class AssignWorkItemView(ListCreateAPIView):
permission_classes = [IsAuthenticated, IsManager]
serializer_class = AssignWorkItemSerializer
def get_queryset(self):
queryset = Post.objects.all()
name = self.request.query_params.get('name')
if name:
queryset = queryset.filter(name=name)
return queryset
Can you try like this.

DRF and django_filters. Change queryset before applying filters

There is a ModelViewSet and a FilterSet that work great. But the problem is that I need to transform the queryset before filtering, for this I overridden the get_queryset() method. It changes the queryset, but as a result, on the page with a list of objects, I see that no changes have occurred.
If I override the list() method using the rewritten get_queryset() method in it:
queryset = self.get_queryset()
Changes will occur, but filters will not work on this queryset. I tried using:
qs = self.filter_queryset(self.get_queryset())
in this case, the filters work, but the data on the page remains as it was before the change in get_queryset(). Please tell me what is my mistake, why can't I filter the queryset with changes and what should be done to make the filtering of the transformed queryset possible?
EDIT:
views.py
from .serializers import OfferSerializer
from rest_framework import viewsets
from rest_framework.response import Response
from django_filters import rest_framework as filters
from django_filters.rest_framework import DjangoFilterBackend
from .models import Offer
def validate(price:str, term:str, deposit:str) -> bool:
""" Validating type of parameters. """
try:
price, term, deposit = int(price), int(term), int(deposit)
return True
except:
return False
def calculate_payment(qs, price:str, term:str, deposit:str):
""" Calculating payment by entry parameters and bank rate. """
clear_payment = (int(price) - int(deposit)) / (int(term) * 12)
for el in qs:
rate_multiplier = (el.rate + 100) / 100
el.payment = clear_payment * rate_multiplier
class OfferFilter(filters.FilterSet):
payment_min = filters.NumberFilter(field_name="payment", lookup_expr='gte')
payment_max = filters.NumberFilter(field_name="payment", lookup_expr='lte')
rate_min = filters.NumberFilter(field_name="rate", lookup_expr='gte')
rate_max = filters.NumberFilter(field_name="rate", lookup_expr='lte')
bank_name = filters.CharFilter(field_name="bank_name", lookup_expr='contains')
order_by = filters.OrderingFilter(
fields=(
('payment', 'payment'),
('bank_name', 'bank_name'),
('rate', 'rate'),
),
)
class Meta:
model = Offer
fields = ('payment_min', 'payment_max', 'rate_min', 'rate_max', 'bank_name')
class OfferViewSet(viewsets.ModelViewSet):
serializer_class = OfferSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = OfferFilter
filterset_fields = ['rate', 'payment', 'bank_name']
def get_queryset(self):
queryset = Offer.objects.all()
query_params = dict(self.request.GET.items())
if 'price' in query_params and 'term' in query_params:
deposit = query_params['deposit'] if 'deposit' in query_params else 0
price = query_params['price']
term = query_params['term']
if validate(price, term, deposit):
calculate_payment(queryset, price, term, deposit)
return queryset
def filter_queryset(self, queryset):
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
def list (self, request):
queryset = self.get_queryset()
queryset = self.filter_queryset(queryset)
serializer = OfferSerializer(queryset, many=True)
return Response(serializer.data)
You need to annotate the queryset instead of iterating over it and setting attributes
from django.db.models import F, Value
...
def get_queryset(self):
queryset = Offer.objects.all()
query_params = dict(self.request.GET.items())
if 'price' in query_params and 'term' in query_params:
deposit = query_params['deposit'] if 'deposit' in query_params else 0
price = query_params['price']
term = query_params['term']
if validate(price, term, deposit):
clear_payment = (int(price) - int(deposit)) / (int(term) * 12)
queryset = queryset.annotate(
rate_multiplier=(F('rate') + Value(100.0)) / Value(100.0)
).annotate(
payment=Value('clear_payment') * F('rate_multiplier')
)
return queryset
If Django cannot work out the correct return type from the annotations you may need to wrap them in ExpressionWrapper to specify the return type

MultipleObjectsReturned error in Django but I want multiple objects to be returned

Using Django REST framework I created an url which maps to a page with a JSON file containing all the objects in my database.
I want to do the same but instead of showing all the objects I want only the objects that match a specific category (category is an attribute in my model).
I have urls that show a JSON files with a single object in it (using the pk attribute) but when I try to do the same thing with category instead of pk I get a MultipleObjectsReturned error.
I'm just sperimenting with the REST framework, I tried using different views and class based views solving nothing.
Any hint or suggestion is really appreciated thanks.
# models.py
class Hardware(models.Model):
name = models.CharField(max_length=25)
category = models.CharField(choices=CATEGORY_CHOICES, max_length=2)
def get_api_url(self):
return api_reverse("category-api-postings:post-rud", kwargs={'category': self.category})
#views.py
class HardwareListView(generics.ListCreateAPIView):
pass
lookup_field = 'pk'
serializer_class = HardwareSerializer
def get_queryset(self):
query = self.request.GET.get("q")
qs = Hardware.objects.all()
if query is not None:
qs = qs.filter(Q(title__icontains=query) | Q(content__icontains=query)).distinct()
return qs
class HardwareRudView(generics.RetrieveUpdateDestroyAPIView):
pass
lookup_field = 'category'
serializer_class = HardwareSerializer
def get_queryset(self):
return Hardware.objects.all()
#urls.py
app_name = 'category-api-postings'
urlpatterns = [
path('', exercise_view),
path('list-api/', HardwareListView.as_view(), name='all'),
path('list-api/<str:category>/', HardwareRudView.as_view(), name='post-rud')
#serializer.py
class HardwareSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Hardware
fields = [
'url',
'pk',
'name',
'category'
]
read_only_fields = ['user']
def get_url(self, obj):
return obj.get_api_url()
As I understand, you want url /list-api/HD/ to return all Hardware objects from given category. For that HardwareRudView must inherit ListAPIView, not RetrieveUpdateDestroyAPIView. For example, like this:
class HardwareRudView(generics.ListAPIView):
serializer_class = HardwareSerializer
def get_queryset(self):
category = self.kwargs['category']
return Hardware.objects.filter(category=category)
See related docs: https://www.django-rest-framework.org/api-guide/filtering/#filtering-against-the-url

Filter ranges using Django Backend filter?

I am working on project using django rest framework in which i have to filter different parameters given by user.I am using django Filter backend.
Here is my code:
class FilterViewSet(viewsets.ModelViewSet):
serializer_class = SearchSerializer
#Filters on specific fields
filter_backends = (DjangoFilterBackend,)
filter_fields = ('property_zipcode','property_state',
'property_county',
'property_city','property_area',)#range between 100 to 500 or area less then 500.
#range is pass by user as a property_area=300.
def filter_queryset(self, queryset):
if self.request.query_params.get('property_state', None):
queryset = super(FilterViewSet, self).filter_queryset(self.get_queryset())
return queryset
else:
queryset = self.get_queryset()
return queryset
Everything is working fine. But now i have to filter property_area based on range like 100 sqft to 500 sqft. How i can achieve this using djangoFilter backend?
Thanks #Gabriel Muj. From the django-filter documentation i solved my problem. I created my own filter class added fields which is use for filters.
class Filter(django_filters.FilterSet):
class Meta:
model = Property
fields = {
'property_state': ['exact'],
'year_built': ['lt', 'gt'],
'tax':['lt','gt'],
}
Call it in Viewset:
class FilterViewSet(viewsets.ModelViewSet):
serializer_class = SearchSerializer
#Filters on specific fields
filter_class = Filter
def filter_queryset(self, queryset):
if self.request.query_params.get('property_state', None):
queryset = super(FilterViewSet, self).filter_queryset(self.get_queryset())
return queryset
else:
queryset = self.get_queryset()
return queryset

Django filter not working

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