Custom ordering in django rest framework? - django

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.

Related

Django Modelviewset Filtering

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

drf ModelViewset does not validate Unique Constraint failed when unique_together is used

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.')

How to add username to the filterset_fields in views Django Rest Framework where the user is the foreign key of the model?

I have below model in my models.py which has the django User model as the foreign key and I want to add username into the filterset_fields in my views in order to filter the user employment by username instead of user id. Any help would be great! Thank you!
class UserEmployment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='employment')
position = models.TextField(blank=True)
company = models.TextField(blank=True)
start_year = models.IntegerField(blank=True, null=True)
end_year = models.IntegerField(blank=True, null=True)
currently_work = models.BooleanField(default=False)
Serializer class,
class UserEmploymentSerializer(serializers.ModelSerializer):
class Meta:
model = UserEmployment
fields = ['id', 'user', 'position', 'company', 'start_year', 'end_year', 'currently_work']
View class,
class UserEmploymentViewSet(viewsets.ModelViewSet):
queryset = UserEmployment.objects.all()
serializer_class = UserEmploymentSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['user'] # I want this to be 'username' in order to filter employment list by username
http_method_names = ['get']
Thank you!
You can use filterset_class attr.
from django_filters import rest_framework
class UserEmploymentFilter(rest_framework.FilterSet):
username = rest_framework.CharFilter(field_name='user__username', lookup_expr='iexact')
class Meta:
fields = ("username",)
model = UserEmployment
And your view should be like this
class UserEmploymentViewSet(viewsets.ModelViewSet):
queryset = UserEmployment.objects.all()
serializer_class = UserEmploymentSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = UserEmploymentFilter
http_method_names = ['get']
After that you can search with ?username=foo
This also works fine,
class UserEmploymentViewSet(viewsets.ModelViewSet):
queryset = UserEmployment.objects.all()
serializer_class = UserEmploymentSerializer
filter_backends = [DjangoFilterBackend]
http_method_names = ['get']
def get_queryset(self):
queryset = UserEmployment.objects.all()
username = self.request.query_params.get('username', None)
if username is not None:
queryset = queryset.filter(user__username=username)
return queryset
Reference: https://www.django-rest-framework.org/api-guide/filtering/#filtering-against-query-parameters

how to filter with django rest framework?

I'm using django rest framework in my project and I want to have filtering on my products . i filter products with category name and popular . but i want to filter products that have discount too .
this is my model:
class Product(models.Model):
category = models.ManyToManyField(Category, related_name='products')
name = models.CharField(max_length=500)
slug = models.SlugField(max_length=500, allow_unicode=True, unique=True)
image = models.ImageField(upload_to='products_pic/%Y/%m/%d/', null=True, blank=True)
description = models.TextField(null=True, blank=True)
price = models.PositiveIntegerField()
discount = models.PositiveIntegerField(null=True, blank=True)
available = models.BooleanField(default=True)
popular = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
comments = GenericRelation(Comment)
class Meta:
ordering = ('-created',)
def __str__(self):
return self.name
there is a problem here. discount is a integer field and i want to filter products with boolean discount. i mean i want to filter products that have discount or not. it is not important how much discount they have.
this is my view:
class search(generics.ListAPIView):
queryset = Product.objects.filter(available=True)
serializer_class = ProductSerializer
permission_classes = (permissions.AllowAny,)
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
filter_fields = ['category__name', 'popular']
search_fields = ['name', 'category__name', 'description']
You can create a filterset backends for DjangoFilterBackend.
Docs
filters.py
from django_filters import rest_framework
class ProductFilter(rest_framework.FilterSet):
discount= rest_framework.BooleanFilter(method='get_discount')
class Meta:
fields = ("discount", "category__name", "popular")
model = Product
def get_discount(self, queryset, name, value):
return queryset.filter(discount__isnull=False)
After that, you should set your filter class to your view's attr.
class search(generics.ListAPIView):
queryset = Product.objects.filter(available=True)
serializer_class = ProductSerializer
permission_classes = (permissions.AllowAny,)
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
filterset_class= ProductFilter
search_fields = ['name', 'category__name', 'description']

Django_filter on an apiview

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)