How can I display ordering using drf_yasg - django-rest-framework

How to deal with ordering in drf_yasg?
I have a View:
class ContentView(ModelViewSet):
....
ordering_fields = ['price',]
But when I open swagger I cant see this possibility.

I found a solution, maybe somebody need this too.
I use django_filters package and in this case we should use filter_class on our view and there we can define ordering as we need.
Example:
from django_filters import rest_framework as filters, OrderingFilter
from contents.models import Content
class ContentFilter(filters.FilterSet):
order = OrderingFilter(
# tuple-mapping retains order
fields=(
('price', 'price'),
),
field_labels={
'price': 'Content price',
}
)
class Meta:
model = Content
fields = ['price']
# my view in views.py
class ContentView(ModelViewSet):
filter_class = ContentFilter
.....
Now you can use ordering ?order=price or ?order=-price and you can see it on swagger docs
More info about OrderingFilter in django_filters here

from rest_framework.filters import OrderingFilter
class OrderingFilterWithSchema(OrderingFilter):
def get_schema_fields(self, view):
self.ordering_description = [enter image description here][1]ordering choicese: " + ', '.join(view.ordering_fields)
return super().get_schema_fields(view)
And in your view:
class ListView(ListAPIView):
filter_backends = [OrderingFilterWithSchema]
ordering_fields = ['price', 'created_at', 'manufacturer_year', 'spent_kilometers']
And the URL will be like:
?ordering=-price or ordering=price
...etc

Related

Custom filter and Rest framework not working simultaneously

I am trying to implement the Django filter in my views class. But the problem is when I am using the filter_class then filter_fields & 'search_fields' is not working and vice versa.
My views.py code is :
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter, SearchFilter
class CustomLoggerAPIView(generics.ListAPIView):
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAuthenticated,permissions.IsAdminUser,)
serializer_class = serializers.CustomLoggerSerializer
filter_class = filters.LogsFilter
queryset = models.CustomLogger.objects.all().order_by('-id')
filter_backends = (DjangoFilterBackend, SearchFilter,)
pagination_class = pagination.PostLimitOffsetPagination
filter_fields = ('user','user_sensor','sensor_type',)
search_fields = ('message')
and my filters.py class:
class LogsFilter(django_filters.rest_framework.FilterSet):
start_date = django_filters.DateTimeFilter(field_name='created_at', lookup_expr='gte')
end_date = django_filters.DateTimeFilter(field_name='created_at', lookup_expr='lte')
class Meta:
model = models.CustomLogger
fields = ('start_date', 'end_date')
I want both filters. I am not sure what I am doing wrong here. Any help will be highly appreciated. Thanks in advance
That is right, if you specify a filterser_class, then the filter_fields in the view are ignored. The solution is easy, just put those filter_fields in the fields of your LogsFilter Meta class:
class LogsFilter(django_filters.rest_framework.FilterSet):
start_date = django_filters.DateTimeFilter(field_name='created_at', lookup_expr='gte')
end_date = django_filters.DateTimeFilter(field_name='created_at', lookup_expr='lte')
class Meta:
model = models.CustomLogger
fields = ('start_date', 'end_date', 'user','user_sensor','sensor_type')

Rename field in Serializer.data

I am currently implementing an API client for which I want to validate the request sent using serializers.
To do so, I create my serializer like this:
class TransactionRequestSerializer(serializers.Serializer):
counterparty = serializers.UUIDField(required=False)
from_datetime = serializers.DateTimeField(required=False, source='from')
to_datetime = serializers.DateTimeField(required=False, source='to')
transaction_type = serializers.CharField(required=False, source='type')
Issue is that source doesn't fit my usage, because when I do serializer.data, I get:
{'from_datetime': '2020-07-07T16:08:00.313236+02:00'}
Instead of
{'from': '2020-07-07T16:08:00.313236+02:00'}
Those data are then passed as params for my request, like requests.get('', params=params)
Of course, I cannot name the field "from" as it is reserved. Any idea about how can I get "from" in my serializer.data?
I tink this has already been answered.
Please take a look at this question: How to change field name in Django REST Framework
I think the same solution will work for you.
I think it's not possible, so I switched to Serializer.validated_data instead so I can use source.
this example as same the question :
model:
from django.db import models
class ReceiveCallbackUrl(models.Model):
From = models.CharField(max_length=14)
to = models.CharField(max_length=14)
message = models.CharField(max_length=255)
messageid = models.IntegerField()
serializer:
from rest_framework.serializers import ModelSerializer,SerializerMethodField
from .models import ReceiveCallbackUrl
class ReceiveCallbackUrlModelSerializer(ModelSerializer):
From = SerializerMethodField('from')
class Meta:
model = ReceiveCallbackUrl
fields = ['From', 'to', 'message', 'messageid']
view:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .serializer import ReceiveCallbackUrlModelSerializer
class ReceiveCallbackUrlAPIView(APIView):
def post(self, request):
serializer = ReceiveCallbackUrlModelSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(request.POST, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_406_NOT_ACCEPTABLE)

DRF docs - getting serializers dynamically

I have a viewset which uses different serializers for different HTTP methods (and possibly even for different users). DRF docs says this:
To be compatible with this behaviour methods (such as get_serializer or
get_serializer_class etc.) which inspect self.request or, particularly,
self.request.user may need to be adjusted to handle this case.
Can anyone provide me an example of the "adjustment to handle this case"?
Say I have a viewset which is using serializer A for get method for admins, serializer B for get method for all other users and serializer C for all other methods. How to specify this so that the documentation understands this? I am using the builtin one.
My urls.py:
router = DefaultRouter()
router.register('test', SampleViewset, base_name="test")
urlpatterns = [
path('admin/', admin.site.urls),
path('docs/', include_docs_urls(title='My API title', public=True)),
path('', include(router.urls)),
]
Minimal example
models.py
class SampleModel(models.Model):
name = models.CharField(max_length=120)
age = models.IntegerField()
address = models.CharField(max_length=100)
serializers.py
class SampleSerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = SampleModel
class SampleSerializerCreate(serializers.ModelSerializer):
age = serializers.IntegerField(max_value=20)
class Meta:
fields = '__all__'
model = SampleModel
views.py
class SampleViewset(ModelViewSet):
serializer_class = SampleSerializer
queryset = SampleModel.objects.all()
def get_serializer_class(self):
if self.action=='create':
return SampleSerializerCreate
return SampleSerializer
Here I used a logic that, while creation of SampleModel, the age attribute should not exceed a max_value of 20.
Update-1
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'sample', SampleViewset)
urlpatterns = [
path('docs/', include_docs_urls(title='My API title', public=True, patterns=router.urls)),
] + router.urls

Could not Resolve URL for hyperlinked relationship using viewname

models.py:
from django.db import models
from pygments.lexers import get_all_lexers
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
LEXERS = [item for item in get_all_lexers() if item[1]]
class Classname(models.Model):
class_name = models.CharField(max_length=5)
owner = models.ForeignKey('auth.User', related_name='fab')
highlighted = models.TextField()
def save(self, *args, **kwargs):
lexer = get_lexer_by_name(self.class_name)
options = self.class_name and {'class': self.class_name} or {}
formatter = HtmlFormatter(full=True, **options)
self.highlighted = highlight(self.class_name, lexer, formatter)
super(Classname, self).save(*args, **kwargs)
serializers.py:
from django.forms import widgets
from django.contrib.auth.models import User
from rest_framework import serializers
from .models import Classname
class ClassSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
highlight = serializers.HyperlinkedIdentityField(view_name='fab-highlight', format='html')
class Meta:
model = Classname
fields = ('id', 'owner', 'class_name', 'highlight',)
class UserSerializer(serializers.ModelSerializer):
fab = serializers.HyperlinkedRelatedField(many=True, view_name='user-detail', read_only=True)
class Meta:
model = User
fields = ('id', 'username', 'fab',)
urls.py:
from django.conf.urls import url, patterns
from rest_framework.urlpatterns import format_suffix_patterns
from . import views
urlpatterns = [
url(r'^class/$', views.ClassList.as_view()),
url(r'^class/(?P<pk>[0-9]+)/$', views.ClassDetail.as_view()),
url(r'^section/$', views.SectionList.as_view()),
url(r'^section/(?P<pk>\d+)/$', views.SectionDetail.as_view()),
url(r'^teacher/$', views.TeacherList.as_view()),
url(r'^teacher/(?P<pk>[0-9]+)/$', views.TeacherDetail.as_view()),
url(r'^attend/$', views.AttendanceList.as_view()),
url(r'^attend/(?P<pk>[0-9]+)/$', views.AttendanceDetail.as_view()),
]
When I changed my serializers.py file and try to add HyperLinked as per the tutorial then I got the above error, I'm not using ViewSets and Routers. I don't know what's the problem b'coz I've checked my url's and views everything seems to be fine.
I'm confused, Please help me to find and fix the issue....
Thanks in advance.....
The exact issue is that I'm using namespace in urls and I've used it in my project level urls.py file also. So in the views file I have to follow django-namespacing like this:
'users': reverse('student:user-list', request=request, format=format),
while 'student' is the namespace that I've used in my project level urls like:
url(r'^stu/', include('app.urls', namespace = 'student'),

Possible to do an `in` `lookup_type` through the django-filter URL parser?

I'm using django-filter with django-rest-framework and I'm trying to instantiate a filter that accepts lists of numbers for filtering the query set down
class MyFilter(django_filters.FilterSet):
ids = django_filters.NumberFilter(name='id',lookup_type='in')
class Meta:
model = MyModel
fields = ('ids',)
class MyModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
filter_class = MyFilter
If I pass in a comma separated list of integers, the filter is ignored altogether.
If I pass in a single integer, it gets through django-filter into django's form validator and complains:
'Decimal' object is not iterable
Is there a way to create a django-filter object which can handle a list of integers and properly filter down the queryset?
For better or worse, I created a custom filter for this:
class IntegerListFilter(django_filters.Filter):
def filter(self,qs,value):
if value not in (None,''):
integers = [int(v) for v in value.split(',')]
return qs.filter(**{'%s__%s'%(self.name, self.lookup_type):integers})
return qs
Which is used like:
class MyFilter(django_filters.FilterSet):
ids = IntegerListFilter(name='id',lookup_type='in')
class Meta:
model = MyModel
fields = ('ids',)
class MyModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
filter_class = MyFilter
Now my interface accepts comma-delimited lists of integers.
I know this is an old post, but there is now a better solution. The change that makes it correct is posted here.
They added a BaseInFilter and a BaseRangeFilter. The documentation is here.
Big picture, BaseFilter checks for CSV, and then when mixed with another filter it does what you are asking. Your code can now be written like:
class NumberInFilter(filters.BaseInFilter, filters.NumberFilter):
pass
class MyModelViewSet(viewsets.ModelViewSet):
ids = NumberInFilter(name='id', lookup_expr='in')
class Meta:
model = MyModel
fields = ['ids']
Here's a complete solution:
from django_filters import Filter, FilterSet
from rest_framework.filters import DjangoFilterBackend
from rest_framework.viewsets import ModelViewSet
from .models import User
from .serializers import UserSerializer
class ListFilter(Filter):
def filter(self, qs, value):
if not value:
return qs
self.lookup_type = 'in'
values = value.split(',')
return super(ListFilter, self).filter(qs, values)
class UserFilter(FilterSet):
ids = ListFilter(name='id')
class Meta:
model = User
fields = ['ids']
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
filter_backends = (DjangoFilterBackend,)
filter_class = UserFilter
According to a post in the django-filter issues:
from django_filters import Filter
from django_filters.fields import Lookup
class ListFilter(Filter):
def filter(self, qs, value):
return super(ListFilter, self).filter(qs, Lookup(value.split(u","), "in"))
I have personally used this without any issue in my projects, and it works without having to create a per-type filter.
Based on #yndolok answer I have come to a general solution. I think filtering by a list of ids is a very common task and therefore should be included in the FilterBackend:
class ListFilter(django_filters.Filter):
"""Class to filter from list of integers."""
def filter(self, qs, value):
"""Filter function."""
if not value:
return qs
self.lookup_type = 'in'
try:
map(int, value.split(','))
return super(ListFilter, self).filter(qs, value.split(','))
except ValueError:
return super(ListFilter, self).filter(qs, [None])
class FilterBackend(filters.DjangoFilterBackend):
"""A filter backend that includes ListFilter."""
def get_filter_class(self, view, queryset=None):
"""Append ListFilter to AutoFilterSet."""
filter_fields = getattr(view, 'filter_fields', None)
if filter_fields:
class AutoFilterSet(self.default_filter_set):
ids = ListFilter(name='id')
class Meta:
model = queryset.model
fields = list(filter_fields) + ["ids"]
return AutoFilterSet
else:
return super(FilterBackend, self).get_filter_class(view, queryset)
Uptodate solution:
from django_filters import rest_framework as filters
name-->field_name
lookup_type-->lookup_expr
class IntegerListFilter(filters.Filter):
def filter(self,qs,value):
if value not in (None,''):
integers = [int(v) for v in value.split(',')]
return qs.filter(**{'%s__%s'%(self.field_name, self.lookup_expr):integers})
return qs
class MyFilter(filters.FilterSet):
ids = IntegerListFilter(field_name='id',lookup_expr='in')
class Meta:
model = MyModel
fields = ('ids',)
class MyModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
filter_class = MyFilter
As I have answered here DjangoFilterBackend with multiple ids, it is now pretty easy to make a filter that accepts list and validates the contents
For Example:
from django_filters import rest_framework as filters
class NumberInFilter(filters.BaseInFilter, filters.NumberFilter):
pass
class MyFilter(filters.FilterSet):
id_in = NumberInFilter(field_name='id', lookup_expr='in')
class Meta:
model = MyModel
fields = ['id_in', ]
This will accept a list of integers from a get parameter. For example /endpoint/?id_in=1,2,3

Resources