DRF ViewSet - dealing with query params - django-rest-framework

I want to change a queryset in my ViewSet depending on query parameters.
I see that there is a list of tags in query params, but when I try extract them I get just last tag as a string. And I have no idea why and how it should work. Can someone explain it for me, please?
class RecipeViewSet(ModelViewSet):
pagination_class = PageNumberPagination
permission_classes = [IsAuthenticatedOrReadOnly, IsAuthorOrReadOnly]
def get_serializer_class(self):
if self.action in ['list', 'retrieve']:
return RecipeListSerializer
return RecipeCreateSerializer
def get_queryset(self):
queryset = Recipe.objects.all()
params = self.request.query_params
tags = params.get("tags")
print("params:")
print(params) # <QueryDict: {'page': ['1'], 'limit': ['6'], 'tags': ['breakfast', 'lunch', 'dinner']}>
print("tags:")
print(type(tags)) # <class 'str'>
print(tags) # I get only str - "dinner"
if tags:
queryset = Recipe.objects.filter(tags__slug__in=tags).distinct()
return queryset

For getting lists you need to use getlist. In your case it would look like this:
params.getlist("tags[]")
This is because you're working with an instance of type QueryDict and not dict. You can find more info here.

Related

How to serialize multiple query set to json Response in django rest frame work

I am getting a list of committees by user using a get method in which I am sending a user id but I am getting an error Committee object is not serialize I have serializer created but I dont't know how to serializer the queryset of that particular user id result.
below is my views.py file
def get(self, request, user_id):
get_committees = Committee.objects.filter(user=Profile.objects.get(id=user_id))
data = {
"status": "success",
"data":get_committees
}
res = Response(serializer.data, status=status.HTTP_200_OK)
below is my serializer.py
class MyCommitteesSerializer(serializers.ModelSerializer):
def get_queryset(self, user_id):
my_committees =
Committee.objects.filter(user=Profile.objects.get(id=user_id))
return my_committees
from your code in get method
"data":get_committees
this get_committees is a list and you are trying to return a list but you can only return json, and so you are getting the message.
You can do this in a better way
define your serializer as -
class MyCommitteesSerializer(serializers.ModelSerializer):
class Meta:
model = Committee
fields = ""_all__"
and define your views as -
class MyCommitteesView(generics.ListAPIView):
serializer_class = MyCommitteesSerializer
def get_queryset(self):
queryset = Committee.objects.filter(user=Profile.objects.get(id=self.request.user_id))
return queryset
the serializer will take care of serialization and you can customize this according to your needs.
or else you will have to manually convert your get_committes into json format before returning.

How to do a search in the DRF only for the selected field?

I can’t understand how to do a search in the DRF only for the selected field.
The documentation on https://www.django-rest-framework.org/api-guide/filtering/ states that need to create a subclass to override the functions get_search_fields (). Everything is clear with this.
It is not clear how to make a request.
If just do a search:
http://... /api/v1/?search=keywords
In this case, it searches for all fields specified in:
search_fields = ['title', 'article']
What needs to be specified in the request in order to search in title field or article field?
http://... /api/v1/?search=keywords..?
Fragment of the views.py responsible for the search:
from rest_framework import filters
class CustomSearchFilter(filters.SearchFilter):
def get_search_fields(self, view, request):
if request.query_params.get('title_only'):
return ['title']
elif request.query_params.get('article_only'):
return ['article']
return super(CustomSearchFilter, self).get_search_fields(view, request)
class MyListView(generics.ListAPIView):
serializer_class = MySerializer
filter_backends = [CustomSearchFilter, filters.OrderingFilter]
ordering_fields = ['id', 'title', 'article']
search_fields = ['title', 'article']
def get_queryset(self):
queryset = Blog.objects.all()
return queryset
You can use DjangoFilterBackend
Then you'll only need to specify the fields, something like this
class ProductList(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['category', 'in_stock']
Then you can filter using query params like this
http://example.com/api/products?category=clothing&in_stock=True
If you need more complex filter then have a look at django-filter documentations : https://django-filter.readthedocs.io/en/master/ref/filters.html#lookup-expr
Reference : https://www.django-rest-framework.org/api-guide/filtering/#djangofilterbackend
How:
http://127.0.0.1:8000/api/v1/?search=Sometitle&title_only=title_only
Why:
# filters.py
from rest_framework import filters
class CustomSearchFilter(filters.SearchFilter):
""""Dynamically change search fields based on request content."""
def get_search_fields(self, view, request):
"""
Search only on the title or the article body if the query parameter,
title_only or article_only is in the request.
"""
# request.query_params is a more correctly named synonym for request.GET
# request.GET a dictionary-like object containing
# all given HTTP GET parameters
# Get the value of the "title_only" item
# How does the querydict looks like
# <QueryDict: {'search': ['Sometitle'], 'title_only': ['title_only']}>
# if exist return search_fields with ['title'] only
if request.query_params.get('title_only'):
return ['title']
elif request.query_params.get('article_only'):
return ['article']
return super(CustomSearchFilter, self).get_search_fields(view, request)
Links:
query_params
django.http.HttpRequest.GET

DRF how to return list filtered by lookup w/ custom router and modelviewset

I tried to search for answers as much as I can but I still don't know how to achieve my goal here.
My goal:
I need two api endpoints, one returns a list filtered by a lookup fields, and another returns an obj filtered by another field, both using GET method. For example:
<ip>/api/books/bycategory/{category_lookup}/ This endpoint will return a list of books filtered by a category
<ip>/api/books/byid/{id_lookup}/ This returns one book matches the specified id (not pk)
Since there's no built-in router that suits my needs here because the built-in ones don't provide url pattern with lookup that returns a list, so I figured I need to have a custom router of my own, so here's what I have:
class CustomRouter(routers.SimpleRouter):
routes = [
routers.DynamicRoute(
url=r'^{prefix}/{url_path}/{lookup}{trailing_slash}$',
name='{basename}-{url_name}',
detail=True,
initkwargs={}
)
]
router = CustomRouter()
router.register('books', BookViewSet)
and my serializer:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = BookKeeper
fields = '__all__'
Right until here I think I'm on the right track, but when it comes to the view, thats where i can't quite figure out. Right now I only have this incomplete viewset:
class BookViewSet(viewsets.ReadOnlyModelViewSet):
queryset = BookKeeper.objects.all()
serializer_class = BookSerializer
#action(detail=True)
def bycategory(self, request):
lookup_field = 'category'
#action(detail=True)
def byid(self, request):
lookup_field = 'id'
My first question here is I "think" {url_path} in the router url matches the method name with #action specified in the viewset somehow and that how they are connected, am I correct?
Second question is how do I use {lookup} value in the view?
Third, what's the lookup_field for if I'm to use like:
def bycategory(self, request):
return Response(BookKeeper.objects.filter(category=<lookup_value>))
Lastly what should my viewset be like anyway?
Any input will be appreciated.
Second question is how do I use {lookup} value in the view?
You need two lookup_fields for the same set. You can do that by a custom Mixin class. But in four case, it is better not to use routers but custom urls, so edit like this:
# views.py
class BookViewSet(viewsets.ReadOnlyModelViewSet):
queryset = BookKeeper.objects.all()
serializer_class = BookSerializer
#action(detail=True)
def bycategory(self, request, category):
# do filtering by category
print(category)
#action(detail=True)
def byid(self, request, book_id):
# do filtering by book_id
print(book_id)
# urls.py
get_by_id = views.BookViewSet.as_view(
{
'get': 'byid'
}
)
get_by_category = views.BookViewSet.as_view(
{
'get': 'bycategory'
}
)
urlpatterns += [
url(
r'^api/books/byid/(?P<book_id>[0-9a-f-]+)/',
get_by_id,
name='get-by-id'
),url(
r'^api/books/bycategory/(?P<category>[0-9a-f-]+)/',
get_by_category,
name='get-by-category'
)
]

Original exception text was: 'QuerySet' object has no attribute 'weight'

I got an Exception
Got AttributeError when attempting to get a value for field weight on serializer WeightHistorySerializer.
The serializer field might be named incorrectly and not match any attribute or key on the QuerySet instance.
Original exception text was: 'QuerySet' object has no attribute 'weight'.
When I tried to retrive data.
models.py
class WeightHistory(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
weight = models.FloatField(null=False, blank=False)
created_at = models.DateTimeField(auto_now_add=True)
serializers.py
class WeightHistorySerializer(serializers. HyperlinkedModelSerializer):
class Meta:
model = WeightHistory
fields = (
'id',
'weight',
'user_id',
'created_at'
)
read_only_fields = ('id',)
views.py
def weight_history_detail(request, user_id):
# Retrieve, update or delete a weight_history/detail.
try:
weight_history = WeightHistory.objects.filter(user_id=user_id)
except WeightHistory.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = WeightHistorySerializer(weight_history)
return Response(serializer.data)
If it change to
weight_history = WeightHistory.objects.get(user_id=user_id)
It returns only one row, But I want all the rows with given user_id. So, What should I do to get all list with given user_id.
'QuerySet' object has no attribute 'weight'.
Yes. QuerySet is a Set, a list of objects.
<QuerySet [<Object1>, <Object2>,..]>
And that list has no attribute weight. Instead, the objects inside the QuerySet has the attribute weight.
weight_history = WeightHistory.objects.filter(user_id=user_id)
filter returns a QuerySet, a list of WeightHistory objects with user_id=user_id.
And you are trying to serialize the list as a single object.
Instead of this:
serializer = WeightHistorySerializer(weight_history)
Do this:
serializer = WeightHistorySerializer(weight_history, many=True)
many=True tells the serializer that a list of objects being passed for serialization.
Moreover,
try:
weight_history = WeightHistory.objects.filter(user_id=user_id)
except WeightHistory.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
This doesn't throw an exception at all. filter returns an empty QuerySet if no objects exist. <QuerySet []>.
So the final code is:
def weight_history_detail(request, user_id):
# Retrieve, update or delete a weight_history/detail.
weight_history = WeightHistory.objects.filter(user_id=user_id)
if weight_history.count()<1:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = WeightHistorySerializer(weight_history, many=True)
return Response(serializer.data)
views.py
def weight_history_detail(request, user_id):
# Retrieve, update or delete a weight_history/detail.
try:
weight_history = WeightHistory.objects.get(user_id=user_id) #get
except WeightHistory.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = WeightHistorySerializer(weight_history)
return Response(serializer.data)
use get instead of filter it will solve you error

Django REST Framework - Extending ListAPIView with custom PUT method

I developed APIs using Django REST Framework for an inventory management application.
The endpoint to GET the list of products includes query parameters to filter the list. See below.
Product List View:
class ProductListAPIView(ListAPIView):
serializer_class = ProductListSerializer
queryset = Product.objects.all()
permission_classes = [DjangoModelPermissionsWithView]
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
search_fields = [
'sku',
'product_name',
...
]
filter_class = ProductFilter
pagination_class = ProductPageNumberPagination
ordering = ['-id']
ordering_fields = [
'id',
'sku',
'product_name',
...
]
def get_serializer_context(self, *args, **kwargs):
return {"request": self.request}
I have created another view to handle requests in order export the products to PDF, CSV, etc:
class ProductExportAPIView(APIView):
def put(self, request, *args, **kwargs):
# We use the seriaziler only to validate request.data
serializer = ProductExportSerializer(data=request.data)
if serializer.is_valid():
user_id = request.user.pk
file_key = request.data.get('file_key')
file_name = request.data.get('file_name', '')
extra_args = request.data.get('extra_args', {})
product_ids = request.data.get('product_ids')
# NOTE THAT export_file IS A CELERY TASK
export_file.delay(user_id, file_key, file_name, product_ids, extra_args)
return Response(status=status.HTTP_204_NO_CONTENT)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The API it's working fine, but it works only if the user selects the products - the product_ids field is used to provide the list of products to be exported.
I would like to let the users export ALL the products via ProductExportAPIView by providing the query params that I'm using with ProductListAPIView rather than providing product_ids.
product_ids should be an optional field to be used only to export a few products.
How I can enable query parameters filtering on my ProductExportAPIView, there is a way to do this without hardcoding it? Can I extend ProductListAPIView with the PUT method to export products?
In order to use the same query parameters defined in ProductListAPIView, now ProductExportAPIView extends ProductListAPIView, so it inherits everything I needed:
class ProductExportAPIView(ProductListAPIView):
permission_classes = [AllowAny]
http_method_names = ['put'] # disable GET method inherited from ProductListAPIView
def put(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
# We use the serializer only to validate request.data
serializer = ProductExportSerializer(data=request.data)
if serializer.is_valid():
user_id = request.user.pk
file_key = request.data.get('file_key')
file_name = request.data.get('file_name', '')
extra_args = request.data.get('extra_args', {})
product_ids = request.data.get('product_ids', [])
if len(product_ids)==0:
product_ids = [p.pk for p in queryset]
export_file.delay(user_id, file_key, file_name, product_ids, extra_args)
return Response(status=status.HTTP_204_NO_CONTENT)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Resources