I have been trying to use the Django_filter on an APIView, but it just does not work. I am trying to implement a filter search, on some fields on a model.
below is how the model is set up
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=254, unique=True)
name = models.CharField(max_length=250)
picture = models.TextField(null=True, blank=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
last_login = models.DateTimeField(null=True, blank=True)
date_joined = models.DateTimeField(auto_now_add=True)
slug = models.SlugField(max_length=255, unique=True, blank=True)
class Skill(models.Model):
name = models.CharField(max_length=60)
subcategory = models.CharField(max_length=60, blank=True, null=True)
created_on = models.DateTimeField(auto_now=True)
updated_on = models.DateTimeField(auto_now_add=True)
updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.DO_NOTHING)
the views.py set up is also as shown below
from django_filters import rest_framework as filters
class UserFilter(filters.FilterSet):
email = filters.CharFilter(lookup_expr='icontains')
name = filters.CharFilter(lookup_expr='icontains')
profiles__skills = filters.CharFilter(lookup_expr='icontains')
class Meta:
model = User
fields = ('email', 'name', 'profiles__skills')
class ListUsersView(APIView, MyPaginationMixin):
'''
Gets all the users in the database
'''
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [AllowAny]
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
filterset_class = UserFilter
def get(self, request):
page = self.paginate_queryset(self.queryset)
if page is not None:
serializer_context = {"request": request}
serializer = self.serializer_class(page, context=serializer_context, many=True)
return self.get_paginated_response(serializer.data)
and finally my serializer.py
class UserSerializer(serializers.ModelSerializer):
slug = serializers.SlugField(read_only=True)
class Meta:
model = User
fields = ('email', 'name', 'slug', 'picture')
read_only_fields = ('email', 'name', 'slug',)
my urls.py
path('users/', qv.ListUsersView.as_view(), name='list-users'),
this is how my result looks like
please, how can I get the Django filter to work on the APIView
It seems you are trying to get the similar or exact behavior of DRF ListAPIView by using APIView. I would suggest using ListAPIView over APIView in your case.
from rest_framework import generics
from django_filters import rest_framework as filters
class ListUsersView(generics.ListAPIView):
'''
Gets all the users in the database
'''
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [AllowAny]
filterset_class = UserFilter
filter_backends = (filters.backends.DjangoFilterBackend,)
To add filtering capability in APIView,
class MyAPIViewKlass(APIView):
filter_backends = (filters.DjangoFilterBackend,)
def filter_queryset(self, queryset):
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
def get(self, request, *args, **kwargs):
base_qs = MyModel.objects.all()
filtered_qs = self.filter_queryset(base_qs)
serializer = MySerializer(filtered_qs, many=True)
return Response(serializer.data)
Related
I am trying to filter list of products linked with a user. I want to display only current users product instead of listing all.
I tried this
class ProductCreateList(generics.ListCreateAPIView):
serializer_class = ProductSerializer
def get_queryset(self):
user = self.request.user
return Product.objects.filter(user=user.id)
serializers.py
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ['id', 'user', 'name', 'imgUrl', 'selling_price', 'actual_price', 'quantity', 'get_profit']
models.py
class Product(models.Model):
user = models.ForeignKey('accounts.Account', on_delete=models.CASCADE, default=1)
name = models.CharField(max_length=100, null=True, blank=True)
imgUrl = models.TextField(default='')
selling_price = models.FloatField(null=True, blank=True)
actual_price = models.FloatField(null=True, blank=True)
quantity = models.IntegerField()
I got [] object when I tried to execute the endpoint. What's my mistake here?
There are a few mistakes, mainly in your view. I have done the following:
models.py
from django.db import models
from django.contrib.auth import get_user_model
class Product(models.Model):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, default=1)
name = models.CharField(max_length=100, null=True, blank=True)
imgUrl = models.TextField(default='')
selling_price = models.FloatField(null=True, blank=True)
actual_price = models.FloatField(null=True, blank=True)
quantity = models.IntegerField()
class Meta:
db_table = 'product'
serializers.py: Remove 'get_profit' field or implement a serializermethodfield
from rest_framework import serializers
from core.models import Product
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ['id', 'user', 'name', 'imgUrl', 'selling_price', 'actual_price', 'quantity']
views.py: To filter by user, guarantee that the user is Authenticated and has auth permission by using authentication_classes and permission_classes. Use the filter_queryset hook, to filter your queryset by user.
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework import generics
from core.api.serializers import ProductSerializer
from core.models import Product
class ProductCreateList(generics.ListCreateAPIView):
authentication_classes = [SessionAuthentication, BasicAuthentication]
permission_classes = [IsAuthenticated]
serializer_class = ProductSerializer
queryset = Product.objects.all()
def filter_queryset(self, queryset):
queryset = queryset.filter(user=self.request.user)
return super().filter_queryset(queryset)
I have view:
class ContactListView(generics.ListAPIView):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
filter_backends = (filters.SearchFilter,filters.OrderingFilter)
ordering_fields = ['name']
search_fields = ('name',)
Model:
class Contact(models.Model):
name = models.CharField(max_length=255)
phone = models.CharField(max_length=255)
email = models.CharField(max_length=2155, blank=True, null=True)
address = models.CharField(max_length=2155, blank=True, null=True)
I set ordering by name but name contains First Name and Last Name ,
I want to set order by Last Name
I created :
class SurnameOrdering(OrderingFilter):
ordering_fields = ['surname']
def get_ordering(self, request, queryset, view):
pass
but how set this ordering correctly?
We can annotate the querset with the last_name and perform an ordering for that:
from django.db.models import Value as V
from django.db.models.functions import Substr, StrIndex
class ContactListView(generics.ListAPIView):
queryset = Contact.objects.annotate(
last_name=Substr('name', StrIndex('name', V(' ')))
)
serializer_class = ContactSerializer
filter_backends = (filters.SearchFilter, filters.OrderingFilter)
ordering_fields = ('name', 'last_name')
search_fields = ('name',)
here we thus let the database determine the last_name, and then we let the OrderingFilter filter on that last_name.
I have two models Category & Post. In Post model there is foreign key of category. Based on category I want to filter the data to show the post category wise. Here's my code.
models.py
class Category(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField()
parent = models.ForeignKey('self',blank=True, null=True ,related_name='news', on_delete=models.CASCADE)
class Meta:
unique_together = ('slug', 'parent',)
verbose_name_plural = "Category"
def __str__(self):
full_path = [self.name]
k = self.parent
while k is not None:
full_path.append(k.name)
k = k.parent
return ' -> '.join(full_path[::-1])
class Post(models.Model):
NEWS_TYPE = (('Images','Images'),('Multi-Images','Multi-Images'),('Image-Text','Image-Text'),
('Audio-Video','Audio-Video'),('Audio-Video-Text','Audio-Video-Text'),('Audio','Audio'),
('Audio-Text','Audio-Text'))
POST_STATUS = (('Pending','Pending'),('Verified','Verified'),('Un-Verified','Un-Verified'),
('Published','Published'),('Mint','Mint'))
category = models.ForeignKey(Category, related_name='posts', on_delete=models.CASCADE)
post_type = models.CharField(max_length=100, verbose_name='Post Type', choices=NEWS_TYPE)
title = models.TextField(verbose_name='News Title')
content = models.TextField(verbose_name='News Content')
hash_tags = models.CharField(max_length=255, verbose_name='Hash Tags')
source = models.CharField(max_length=255, verbose_name='News Source')
author = models.ForeignKey(User, related_name='Post', on_delete=models.CASCADE)
views = models.ManyToManyField(User,related_name='Views', blank=True)
likes = models.ManyToManyField(User, related_name='Likes', blank=True)
dislikes = models.ManyToManyField(User, related_name='Dislikes', blank=True)
status = models.CharField(max_length=20, verbose_name='Status', choices=POST_STATUS, default='Pending')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return (self.post_type)+ '-' +self.title
serializers.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
category = CategorySerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ('category','post_type','title','content','hash_tags','source','author','views',
'likes','dislikes','status')
views.py
class CategoryAPI(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
class PostAPI(viewsets.ModelViewSet):
serializer_class = PostSerializer
def get_queryset(self):
news_post = Post.objects.all()
return news_post
def retrieve(self, request, *args, **kwargs):
params = kwargs
print(params['pk'])
category = Category.objects.filter(name=params['pk'])
serializer = CategorySerializer(category, many=True)
return Response(serializer.data)
urls.py
from django.urls import path, include
from rest_framework import routers
from rest_framework.routers import DefaultRouter
from news.views import PostAPI, CategoryAPI
from . import views
router = DefaultRouter()
router.register('posts', views.PostAPI, basename='posts'),
router.register('category', views.CategoryAPI, basename='category'),
urlpatterns = router.urls
I tried solving in these way but it tells 'PostSerializer' object has no attribute 'get_category'. Is there anything i'm doing wrong. Please your support would be helpful. Thank you
I think then your approach should be the other way round, meaning you should add the list of Posts to your Category:
serializers.py
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('category','post_type','title','content','hash_tags','source','author','views',
'likes','dislikes','status')
class CategorySerializer(serializers.ModelSerializer):
posts = PostSerializer(many=True, read_only=True)
class Meta:
model = Category
fields = ['name', 'slug', 'parent', 'posts']
Attention: I changed the related name of your category field in the Post model to 'posts'
This should show you all Posts when retrieving a category. No need to override any method in your views:
class CategoryAPI(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
class PostAPI(viewsets.ModelViewSet):
queryset = Post.obejcts.all()
serializer_class = PostSerializer
If do not want identify the category by id but by category name, e.g.:
http://127.0.0.1:8000/news/category/sports/
add a custom lookup field to your category view, e.g.
class CategoryAPI(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
lookup_field = 'name'
but make sure the lookup_field is unique
I am using djangorestframework==3.12.1 Here is how my code looks like:-
models.py
class Product(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=14, decimal_places=2)
description = models.TextField(blank=True, null=True)
def __str__(self):
return self.name
class Meta:
unique_together = (
("name", "user"),
)
serializers.py
class ProductSerializer(serializers.ModelSerializer):
"""serializer for Product objects."""
class Meta:
model = models.Product
fields = '__all__'
read_only_fields = ['user',]
views.py
class ProductViewset(viewsets.ModelViewSet):
queryset = models.Product.objects.all()
permission_classes = [permissions.IsAuthenticated]
serializer_class = serializers.ProductSerializer
Expected Behavior
The validators on the Modelserializers must raise an exception as Product with this Name and User already exists.
Actual Behavior
IntegrityError at /api/v1/product/ UNIQUE constraint failed: projectapp_product.name, projectapp_product.user_id
How do I fix this?
You can try to override the perform_create of ProductViewSet
like this:
class ProductViewset(viewsets.ModelViewSet):
queryset = models.Product.objects.all()
permission_classes = [permissions.IsAuthenticated]
serializer_class = serializers.ProductSerializer
def perform_create(self, serializer):
user = self.request.user
try:
serializer.save(user=user)
except IntegrityError:
raise ValidationError('Product with this Name and User already exists.')
Currently in my ModelViewSet I'm returning all contacts for today, additionally I'm checking if the query_params on the request for searching/filtering is available and not empty, In that case I'm not adding "today" to the queryset so that will perform search on the entire queryset.
Problem is that I need to extend search so the user can see other users contacts, but only when he search for them, default view should not be changing if you are not searching, so how can I extend my current filter and return all objects in search.
My current view:
from rest_framework import viewsets, permissions, filters
from cms.restapi.pagination import StandardResultsOffsetPagination
from cms_sales.models import LeadContact
from cms_sales.restapi.permissions.lead_contact_permissions import LeadContactPermissions
from cms_sales.restapi.serializers.lead_contact_serializer import LeadContactSerializer
class LeadContactViewSet(viewsets.ModelViewSet):
def get_queryset(self):
queryset = LeadContact.objects.none()
user = self.request.user
if user.has_perm('cms_sales.can_view_full_lead_contact_list'):
queryset = LeadContact.objects.all()
elif user.has_perm('cms_sales.can_view_lead_contact'):
queryset = LeadContact.objects.filter(account_handler=user)
filter_date = self.request.query_params.get('filter_date', None)
search_params = self.request.query_params.get('search', None)
if filter_date is not None and (search_params is None or len(search_params) == 0):
queryset = queryset.filter(next_action_date=filter_date)
return queryset
serializer_class = LeadContactSerializer
filter_backends = (filters.DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter)
filter_fields = ('account_handler',)
ordering_fields = (
'first_name', 'last_name', 'account_handler__first_name', 'account_handler__last_name',
'sub_organization_name', 'organization_name', 'next_action_date', 'serial_number',
'status_text')
search_fields = (
'first_name', 'last_name', 'account_handler__first_name', 'account_handler__last_name',
'sub_organization_name', 'organization_name', 'next_action_date', 'serial_number',
'status_text')
pagination_class = StandardResultsOffsetPagination
permission_classes = [permissions.IsAuthenticated, LeadContactPermissions]
Current Serializer:
from django.conf import settings
from django.contrib.auth.models import User
from rest_framework import serializers
from cms_sales.models import LeadContact
class AccountHandlerSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('username', 'first_name', 'last_name')
class LeadContactSerializer(serializers.ModelSerializer):
account_handler = AccountHandlerSerializer()
next_action_date = serializers.DateTimeField(format=settings.CUSTOM_DATE_FORMAT_NO_TIME)
absolute_url = serializers.URLField(source='get_absolute_url')
class Meta:
model = LeadContact
fields = (
'pk', 'organization_name', 'sub_organization_name', 'serial_number', 'account_handler', 'status_text',
'first_name', 'last_name', 'next_action_date', 'absolute_url', 'status_display_class'
)
depth = 1
Current Model:
class LeadContact(models.Model):
organization_name = models.CharField(max_length=200, blank=False, null=True)
sub_organization_name = models.CharField(max_length=200, blank=False, null=True)
serial_number = models.CharField(max_length=30, db_index=True, blank=True, null=True)
account_handler = models.ForeignKey(User, blank=True, null=True, related_name='handling_leads', on_delete=models.SET_NULL)
next_action_date = models.DateField(null=True, verbose_name="Next action on lead")
status_text = models.CharField(max_length=20, default='', blank=True)
first_name = models.CharField(_('first name'), max_length=30)
last_name = models.CharField(_('last name'), max_length=30)
move the code to check permissions below the code where you check for search param so that you are applying the user filter after checking for search_params
def get_queryset(self):
queryset = LeadContact.objects.all()
if (not user.has_perm('cms_sales.can_view_full_lead_contact_list') and
not user.has_parm('cms_sales.can_view_lead_contact')):
return queryset.none()
user = self.request.user
filter_date = self.request.query_params.get('filter_date', None)
search_params = self.request.query_params.get('search', None)
if filter_date is not None and (search_params is None or len(search_params) == 0):
queryset = queryset.filter(next_action_date=filter_date)
if user.has_perm('cms_sales.can_view_lead_contact') and not search_params:
queryset = queryset.filter(account_handler=user)
return queryset