DRF pagination with ordering - django

Have trouble with pagination and ordering at the same time.
Problem is that one instance can be returned by view multiple times: for example on page 9 and then on page 11.
View:
from rest_framework import filters, viewsets
from django_filters.rest_framework import DjangoFilterBackend
from django_filters import FilterSet
from django_filters.filters import CharFilter
class TariffFilter(FilterSet):
concat_code = CharFilter(field_name='concat_code')
class Meta:
model = Tariff
fields = {
'id': ['in', 'exact'],
'title': ['exact', 'icontains', 'in'],
.... }
class TariffPagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
class TariffListViewSet(viewsets.ModelViewSet):
pagination_class = TariffPagination
serializer_class = tariff_serializer.TariffSerializer
filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,)
filterset_class = TariffFilter
ordering_fields = ['id', 'title', ...]
search_fields = ['title', ...]
def get_queryset(self):
concated_code = Concat(...., output_field=CharField())
queryset = Tariff.not_deleted_objects.annotate(concat_code=concated_code)
return queryset
Serializer:
class TariffSerializer(serializers.ModelSerializer):
element = TariffElementSerializer()
service = ServiceElementSerializer()
class Meta:
model = Tariff
fields = ('pk', 'title', 'code', 'element', 'service', 'concated_code', )
Where is problem?
example problem query:
http://127.0.0.1:8000/api/?ordering=-title&page=2

Related

How to limit the number of results in a Django REST serializar?

Hi have this serializer:
class ActivitiesSerializer(serializers.ModelSerializer):
activity = serializers.CharField(source='task.name')
project = serializers.CharField(source='project.name')
discipline = serializers.CharField(source='task.discipline.name')
class Meta:
model = Activities
fields = (
'id',
'activity',
'project',
'discipline',
)
How can I limit the number of results to 10?
This is my view:
class ActivitiesAPIView(generics.ListCreateAPIView):
search_fields = ['task__name', 'task__discipline__name', 'project__name']
filter_backends = (filters.SearchFilter,)
queryset = Activities.objects.all()
serializer_class = ActivitiesSerializer
Note that I want to limit the number of results to 10, but I want to search through all the model, so it wouldn't work to just limit my queryset to 10.
You can use pagination:
from rest_framework.pagination import PageNumberPagination
class DefaultPagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
max_page_size = 1000
Then in View:
class ActivitiesAPIView(generics.ListCreateAPIView):
pagination_class = DefaultPagination
https://www.django-rest-framework.org/api-guide/pagination/#pagenumberpagination

Django rest Framework Endpoint

I need to get this endpoint /comments/int:post_id/
I can GET and POST comments and posts but I need to show all comments to specific post. I donĀ“t know how to connect it. My code look like
comment urls
urlpatterns = [
path('', views.CommentsView.as_view()),
path('<int:post_id>/', views.CreateCommentsView.as_view()),
]
comment view.py
# I get all comments/
class CommentsView(ListCreateAPIView):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
# Comments to specific post
class CreateCommentsView(ListCreateAPIView):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
lookup_url_kwarg = 'post_id'
def perform_create(self,serializer):
post = self.kwargs.get('post_id')
post =set.get_queryset().filter(id = 'post_id')
post.comments.add(comment)
post = Post.objects.filter(id=self.kwargs.get('post_id'))
serializer.save(user=self.request.user, post=post)
comment serializer
from rest_framework import serializers
from .models import Comment
from django.contrib.auth import get_user_model
User = get_user_model()
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ['id', 'user', 'post', 'content', 'created']
class UserSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username']
class CommentSimpleSerializer(serializers.ModelSerializer):
user = UserSimpleSerializer()
class Meta:
model = Comment
fields = ['user', 'content', 'created']
post.view.py
class PostList(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
class LikePost(generics.UpdateAPIView):
permission_classes = [IsNotPostUser]
queryset = Post.objects.all()
serializer_class = PostSerializer
you can pass the post_id in url parameters for /comments/ endpoint and call HTTP GET method. Then in CommentsView you need to override the get_queryset method. Your implementation will be like
class CommentsView(ListCreateAPIView):
serializer_class = CommentSerializer
def get_queryset(self):
query = self.request.query_params.get('post_id', None)
if query is not None:
queryset = Comment.objects.filter(post_id=post_id)
else:
queryset = Comment.objects.all()
return queryset
This /comments/ endpoint will return all the comments and this one /comments/?post_id=1 will return comments only related to specified post.

How to filter by date range in django-rest?

I'd like to filter my data by date range typed in browser, all other filtering are working.
views.py
class BookView(generics.ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializers
filter_backends = [filters.SearchFilter]
search_fields = ['title', 'language', 'authors', 'date']
You need create a new filter:
class StatementItemFilter(filters.FilterSet):
date_between = filters.DateFromToRangeFilter(field_name="MODEL_FIELD_NAME", label="Date (Between)")
class Meta:
model = StatementItem
fields = [
...
"date_between"
]
and use in your viewset
class MODELItemViewSet(viewsets.ReadOnlyModelViewSet):
filter_backends = (DjangoFilterBackend,)
filterset_class = StatementItemFilter
...
Move your list ['title', 'language', 'authors', 'date'] to your new filterset class

Django rest api - searching a method field with the search filter

im trying to filter search a rest api page and want to use a method field as one of the search fields, however when I do this I get an error stating the field is not valid and it then lists the field in my model as the only valid source
serialiser:
class SubnetDetailsSerializer(QueryFieldsMixin, serializers.HyperlinkedModelSerializer):
subnet = serializers.SerializerMethodField()
device = serializers.ReadOnlyField(
source='device.hostname',
)
circuit_name = serializers.ReadOnlyField(
source='circuit.name',
)
subnet_name = serializers.ReadOnlyField(
source='subnet.description',
)
safe_subnet = serializers.SerializerMethodField()
def get_safe_subnet(self, obj):
return '{}{}'.format(obj.subnet.subnet, obj.subnet.mask.replace('/','_'))
def get_subnet(self, obj):
return '{}{}'.format(obj.subnet.subnet, obj.subnet.mask)
class Meta:
model = DeviceCircuitSubnets
fields = ('id','device_id','subnet_id','circuit_id','subnet','safe_subnet','subnet_name','device','circuit_name')
views:
class SubnetDetailsSet(viewsets.ReadOnlyModelViewSet):
queryset = DeviceCircuitSubnets.objects.all().select_related('circuit','subnet','device')
serializer_class = SubnetDetailsSerializer
permission_classes = (IsAdminUser,)
filter_class = DeviceCircuitSubnets
filter_backends = (filters.SearchFilter,)
search_fields = (
'device__hostname',
'circuit__name',
'subnet__subnet',
'safe_subnet'
)
how can include the safe_subnet in the search fields?
Thanks
EDIT
This is the code now
views.py
class SubnetDetailsSet(viewsets.ReadOnlyModelViewSet):
queryset = DeviceCircuitSubnets.objects.all()
serializer_class = SubnetDetailsSerializer
permission_classes = (IsAdminUser,)
filter_class = DeviceCircuitSubnets
filter_backends = (filters.SearchFilter,)
search_fields = (
'device__hostname',
'circuit__name',
'subnet__subnet',
'safe_subnet'
)
def get_queryset(self):
return (
super().get_queryset()
.select_related('circuit','subnet','device')
.annotate(
safe_subnet=Concat(
F('subnet__subnet'),
Replace(F('subnet__mask'), V('/'), V('_')),
output_field=CharField()
)
)
)
serializer.py
class SubnetDetailsSerializer(QueryFieldsMixin, serializers.HyperlinkedModelSerializer):
subnet = serializers.SerializerMethodField()
device = serializers.ReadOnlyField(
source='device.hostname',
)
circuit_name = serializers.ReadOnlyField(
source='circuit.name',
)
subnet_name = serializers.ReadOnlyField(
source='subnet.description',
)
def get_safe_subnet(self, obj):
return getattr(obj, 'safe_subnet', None)
def get_subnet(self, obj):
return '{}{}'.format(obj.subnet.subnet, obj.subnet.mask)
class Meta:
model = DeviceCircuitSubnets
fields = ('id','device_id','subnet_id','circuit_id','subnet','safe_subnet','subnet_name','device','circuit_name')
Model:
class DeviceCircuitSubnets(models.Model):
device = models.ForeignKey(Device, on_delete=models.CASCADE)
circuit = models.ForeignKey(Circuit, on_delete=models.CASCADE, blank=True, null=True)
subnet = models.ForeignKey(Subnet, on_delete=models.CASCADE)
active_link = models.BooleanField(default=False, verbose_name="Active Link?")
active_link_timestamp = models.DateTimeField(auto_now=True, blank=True, null=True)
Error:
Exception Type: ImproperlyConfigured at /api/subnets/
Exception Value: Field name `safe_subnet` is not valid for model `DeviceCircuitSubnets`.
You need to annotate your queryset with the safe_subnet attribute so it becomes searchable.
from django.db.models import F, Value as V
from django.db.models.functions import Concat, Replace
class SubnetDetailsSet(viewsets.ReadOnlyModelViewSet):
queryset = DeviceCircuitSubnets.objects.all()
serializer_class = SubnetDetailsSerializer
permission_classes = (IsAdminUser,)
filter_class = DeviceCircuitSubnets
filter_backends = (filters.SearchFilter,)
search_fields = (
'device__hostname',
'circuit__name',
'subnet__subnet',
'safe_subnet'
)
def get_queryset(self):
return (
super().get_queryset()
.select_related('circuit','subnet','device')
.annotate(
safe_subnet=Concat(
F('subnet__subnet'),
Replace(F('subnet__mask'), V('/'), V('_')),
output_field=CharField()
)
)
)
Then in your serializer you can use the following.
def get_safe_subnet(self, obj):
return obj.safe_subnet
Previous answer with annotate is a really good start:
from .rest_filters import DeviceCircuitSubnetsFilter
class SubnetDetailsSet(viewsets.ReadOnlyModelViewSet):
queryset = DeviceCircuitSubnets.objects.all()
serializer_class = SubnetDetailsSerializer
permission_classes = (IsAdminUser,)
# That's where hint lays
filter_class = DeviceCircuitSubnetsFilter
#filter_backends = (filters.SearchFilter,)
search_fields = (
'device__hostname',
'circuit__name',
'subnet__subnet',
'safe_subnet'
)
#No need to override your queryset
Now in rest_filters.py
from django_filters import rest_framework as filters
from django.db.models import F, Value as V
from django.db.models.functions import Concat, Replace
#.... import models
class DeviceCircuitSubnets(filters.FilterSet):
safe_subnet = filters.CharFilter(
name='safe_subnet',
method='safe_subnet_filter')
def safe_subnet_filter(self, queryset, name, value):
"""
Those line will make ?safe_subnet=your_pk available
"""
return queryset.annotate(
safe_subnet=Concat(
F('subnet__subnet'),
Replace(F('subnet__mask'), V('/'), V('_')),
output_field=CharField()
)
).filter(safe_subnet=value)
)
class Meta:
model = DeviceCircuitSubnets
# See https://django-filter.readthedocs.io/en/master/guide/usage.html#generating-filters-with-meta-fields
# This pattern is definitely a killer!
fields = {
'device': ['exact', 'in'],
'circuit': ['exact', 'in'],
'subnet': ['exact', 'in'],
'active_link': ['exact'],
'active_link_timestamp': ['lte', 'gte']
}
Please note: I'm annotating safe_subnet within the filer, depending on how much you use this, you might want to set this up in your model's manager!
Going in a completely different direction from the other (excellent) answers. Since you want to be able to filter frequently on the safe_subnet field, why not just let it be an actual database field in your model? You could calculate and populate/update the value during one of your save methods and then just let django-filters do it's thing. This also has the advantage of allowing the filtering to be done directly through SQL which would theoretically provide better performance.

TypeError: __init__() got multiple values for keyword argument 'view_name'

I am using the Django Rest Framework, and I am not sure why I am getting this error.
models.py
from __future__ import unicode_literals
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
followers = models.ManyToManyField('self', related_name='followees', symmetrical=False)
class Post(models.Model):
author = models.ForeignKey(User, related_name = 'posts')
title = models.CharField(max_length = 255)
body = models.TextField(blank = True, null = True)
class Photo(models.Model):
post = models.ForeignKey(Post, related_name = 'photos')
image = models.ImageField(upload_to = '%Y/%m/%d')
serializers.py
from rest_framework import serializers
from .models import *
class UserSerializer(serializers.ModelSerializer):
# Getting the list of posts made by particular users using the username.
posts = serializers.HyperlinkedIdentityField(
'posts',
view_name = 'userpost-list',
lookup_field = 'username'
)
class Meta:
model = User
fields = ('id', 'username', 'first_name', 'last_name', 'posts',)
class PostSerializer(serializers.ModelSerializer):
author = UserSerializer(required = False)
photos = serializers.HyperlinkedIdentityField(
'photos',
view_name = 'postphoto-list'
)
def get_validated_exclusions(self):
# Need to exclude 'author' since we'll add that later
# based off the request user
exclusions = super(PostSerializer, self).get_validated_exclusions()
return exclusions + ['author']
class Meta:
model = Post
class PhotoSerializer(serializers.ModelSerializer):
image = serializers.Field('image.url')
class Meta:
model = Photo
views.py
from rest_framework import generics, permissions
from .serializers import *
from .models import *
class UserList(generics.ListCreateAPIView):
model = User
serializer_class = UserSerializer
permission_classes = [
permissions.AllowAny # Publically available to anyone
]
class UserDetail(generics.RetrieveAPIView):
model = User
serializer_class = UserSerializer
lookup_field = 'username'
class PostList(generics.ListCreateAPIView):
model = Post
serializer_class = PostSerializer
permission_classes = [
permissions.AllowAny
]
class PostDetail(generics.RetrieveAPIView):
model = Post
serializer_class = PostSerializer
permission_classes = [
permissions.AllowAny
]
class UserPostList(generics.ListAPIView):
"""
Lists all the posts of a particular User.
"""
model = Post
serializer_class = PostSerializer
def get_queryset(self):
queryset = super(UserPostList, self).get_queryset()
return queryset.filter(author__username = self.kwargs.get('username'))
class PhotoList(generics.ListCreateAPIView):
model = Photo
serializer_class = PhotoSerializer
permission_classes = [
permissions.AllowAny
]
class PhotoDetail(generics.RetrieveAPIView):
model = Photo
serializer_class = PhotoSerializer
permission_classes = [
permissions.AllowAny
]
class PostPhotoList(generics.ListAPIView):
model = Photo
serializer_class = PhotoSerializer
def get_queryset(self):
queryset = super(PostPhotoList, self).get_queryset()
return queryset.filter(post__pk = self.kwargs.get('pk'))
urls.py in my app directory
from django.conf.urls import patterns, url, include
from .views import *
urlpatterns = [
# User URLs
url(r'^users/$', UserList.as_view(), name='user-list'),
url(r'^users/(?P<username>[0-9a-zA-Z_-]+)/$', UserDetail.as_view(), name='user-detail'),
url(r'^users/(?P<username>[0-9a-zA-Z_-]+)/posts/$', UserPostList.as_view(), name='userpost-list'),
# Post URLs
url(r'^posts/$', PostList.as_view(), name='post-list'),
url(r'^posts/(?P<pk>\d+)/$', PostDetail.as_view(), name='post-detail'),
url(r'^posts/(?P<pk>\d+)/photos/$', PostPhotoList.as_view(), name='postphoto-list'),
# Photo URLs
url(r'^photos/$', PhotoList.as_view(), name='photo-list'),
url(r'^photos/(?P<pk>\d+)/$', PhotoDetail.as_view(), name='photo-detail'),
]
When I try to run the check command on my terminal, or runserver, I get this error:
TypeError: init() got multiple values for keyword argument 'view_name'
What am I doing wrong exactly, and how can I fix this problem?
The first argument to HyperlinkedIdentityField is view_name. You're passing an extra initial argument, which seems to be the same as the field name; remove this argument.