Rest Framework pass additional variables to ModelViewSet - django

Might seem like a dumb question but trying to pass additional filter variables to a ModelViewSet but request.data is empty.
class ObjViewSet(viewsets.ModelViewSet):
def get_queryset(self):
if self.request.get('orderBy', None):
return Obj.objects.all().order_by(self.request.get('orderBy'))
else:
return Obj.objects.all()
What is the correct way to do this? I don't want to screw up the /view/<id>/ routing but I also wish to pass a couple more variables via /view/?orderBy=id&var2=val2
Using DefaultRouter
router.register('objs', views.ObjViewSet, basename="obj")

You should change the self.request.get('orderBy') into self.request.GET.get('orderBy')
class ObjViewSet(viewsets.ModelViewSet):
queryset = Obj.objects.all()
def get_queryset(self):
order_by = self.request.GET.get('orderBy')
if order_by is not None:
return self.queryset.order_by(order_by)
return self.queryset

Related

Change serializer class for single method in Django Rest Framework

If I want to override a method in my viewset to change the serializer_class for only a single method, how can I do that.
I tried passing serializer_class=CustomSerializer but it doesn't have any effect.
class VoteViewset(viewsets.ModelViewSet):
queryset = Vote.objects.all()
# Use normal serializer for other methods
serializer_class = VoteSerializer
def list(self, request, *args, **kwargs):
# Use custom serializer for list method
return viewsets.ModelViewSet.list(self, request, serializer_class=VoteWebSerializer, *args, **kwargs)
Basically do the same list method as the inherited viewset, but use a different serializer to parse the data.
The main reason for this is because javascript does not handle 64 bit integers, so I need to return the BigInteger fields as a string instead of integer.
Override the get_serializer_class(...) method as
class VoteViewset(viewsets.ModelViewSet):
queryset = Vote.objects.all()
def get_serializer_class(self):
if self.action == "list":
return VoteWebSerializer
return VoteSerializer
What JPG answered is the correct way. Another thing you could do is overriding the list method like you are doing but modifying the default behavior using the serializer that you want.
It would be something like:
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
You can check the default methods in the source code or I recommend using Classy DRF. For example here you can see what DRF is doing in the list method of a ModelViewSet doing and use that as a starting point.

how to chain queryset mixin for django rest viewsets?

I need to write the following querysets mixins:
class VendorOwnedQuerySetMixin(models.QuerySet):
def get_objects_for_vendor(self, request):
vendor_user = VendorUserModel.objects.get(user=request.user)
return qs.filter(vendor=vendor_user.vendor)
class OrganizationOwnedQuerySetMixin(object):
def get_objects_for_organization(self, request):
return self.filter(organization__domains__name=hostname_from_request(request))
All's working well because some model managers will inherit the first mixin and some inherit the second.
Then inside the get_queryset of the viewset, i will call the appropriate get_objects method.
example
def get_queryset(self, queryset=None):
return Some.objects.get_objects_for_organization(self.request)
Now I need to have a django rest viewset that needs to run the get_queryset method that runs both filters.
How do I "chain" them within the get_queryset method? Because I want to reuse my code where possible
In order to chain the filters, you need to get the previous queryset. This can be achieved by calling super().get_queryset(request). It will get the queryset from the other classes your view inherit from and will apply the filter:
class VendorOwnedQuerySetMixin(models.QuerySet):
def get_queryset(self, request):
qs = super().get_queryset(request)
vendor_user = VendorUserModel.objects.get(user=request.user)
return qs.filter(vendor__user=vendor_user.vendor)
class OrganizationOwnedQuerySetMixin(object):
def get_objects_for_organization(self, request):
qs = super().get_queryset(request)
return qs.filter(organization__domains__name=hostname_from_request(request)
Remember that you MUST set the mixins before the view in order to work. For example:
class MyView(OrganizationOwnedQuerySetMixin, VendorOwnedQuerySetMixin, RetrieveAPIView):
...
A call on get_queryset will get the RetrieveAPIView queryset which will get passed to VendorOwnedQuerySetMixin once the super() call returns, get the filter applied and returns the result to OrganizationOwnedQuerySetMixin after the super() is called which in turn will apply its filter and return the result.

Django ListView self.kwargs

I'm using Django==1.11. As I understand from class based views, in this case ListView, you can access url params in get_queryset with self.kwargs, as answered here and here. And I have no problem when I use get_context_data and self.kwargs.
But I can't get it to work in get_queryset. ¿What I'm doing wrong or missing? I've been trying so many alternatives but I can't get the right one.
My code:
urls
urlpatterns = [
url(r'^escuelas/(?P<level>(inicial|primario|secundario))/$', SchoolListView.as_view(), name='school-by-level-index'),
#...
view
class SchoolListView(ListView):
model = School
template_name = 'edu/adminlte/school_index.html'
def get_queryset(self):
queryset = super(SchoolListView, self).get_queryset()
"""
Here below self.kwargs['level'] does not return anything as I would expect
"""
level = self.kwargs['level']
if level is 'inicial':
queryset = School.objects.filter(level='I')
return queryset
return queryset
Thanks.
I knew it was something simple:
double equals vs is in python
My code was:
if level is 'inicial':
But it had to be
if level == 'inicial':

Get unpaginated results from Django REST Framework

I have pagination enabled by default, which is based on PageNumberPagination; this had sufficed until now as the API was only used by a front-end. Now we try to build automation on top of it, and I’d like to pass the full, unpaginated result set back to the client.
Is there a way to disable pagination for specific requests, e.g. if a request parameter is passed?
I used a similer appraoch to accepted answer
class Unpaginatable(PageNumberPagination):
def paginate_queryset(self, queryset, request, view=None):
if request.query_params.get('get_all', False) == 'true':
return None
return super(BuildListPagination, self).paginate_queryset(queryset, request, view=view)
now if you pass ?get_all=true while making the request, you will get unpaginated response.
If you are using page number pagination style, the below solution may be better.
def paginate_queryset(self, queryset, request, view=None):
if 'page' not in request.query_params:
return None
return super().paginate_queryset(queryset, request, view)
So you simply send a request without the page query_param.
I actually did go with a custom pagination class:
class Unpaginatable(PageNumberPagination):
def paginate_queryset(self, queryset, request, view=None):
if getattr(request, 'get_all', False):
return None
return super(BuildListPagination, self).paginate_queryset(queryset, request, view=view)
Now I just have to set request.get_all = True in my viewset and I get all the items.
thnaks to this answer,
Else, if you use limit/offset you can use this in Generic List API View class:
def paginate_queryset(self, queryset):
if 'limit' not in self.request.query_params:
return None
return super().paginate_queryset(queryset)
It worked with python 3.9/ Django 4.0, In my case, this method had no argument named request and view, so I fixed it.
this will also won't render paginator json, when the response is not paginated.
You can approximate this with a request limit set to an unfeasibly large number, but you need to configure Django first:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination'
}
Then make a ridiculously high limit request:
GET https://api.example.org/accounts/?limit=999999999999
LimitOffsetPagination
- django-rest-framework.org
Actually DRF docs for this is very unclear, and there is no clear answer address this issue. After reading the source code, the followling code works for me.
from rest_framework.pagination import LimitOffsetPagination
class Unpaginated(LimitOffsetPagination):
def paginate_queryset(self, queryset, request, view=None):
self.count = self.get_count(queryset)
self.limit = self.get_limit(request)
self.offset = self.get_offset(request)
self.request = request
self.display_page_controls = False
return list(queryset)
class SomeViewSet(viewsets.ModelViewSet):
queryset = SomeModel.objects.all().order_by("-id")
pagination_class = Unpaginated
The key here is to override the paginate_queryset function in base class and return the whole queryset.
However, overriding this function is not documented at all in the docs or I just missed it.
https://www.django-rest-framework.org/api-guide/pagination/
https://github.com/encode/django-rest-framework/blob/master/rest_framework/pagination.py
I solve this problem in the following way:
make your own pagination class and inherit it from PageNumberPagination
redefine or add your code (with super()) in get_page_size function.
For me works this case:
def get_page_size(self, request):
super().get_page_size(request)
.......
self.page_size = self.max_page_size
return self.page_size

Filtering Django REST framework using IN operator

I basically need something like /?status=[active,processed] or /?status=active,processed
My current setting is: 'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',) and it's only filtering one value correctly (/?status=active)
I think there is no inbuilt functionality for that. But you can implement a custom filter to do that. This custom filter you can use in your filterset.
import django_filters as df
class InListFilter(df.Filter):
"""
Expects a comma separated list
filters values in list
"""
def filter(self, qs, value):
if value:
return qs.filter(**{self.name+'__in': value.split(',')})
return qs
class MyFilterSet(df.FilterSet):
status = InListFilter(name='status')
You can use 'field_in' when using Class.object.filter method.
class FileterdListAPIView(generics.ListAPIView):
serializer_class = FooSerializer
def get_queryset(self):
user_profile = self.kwargs['pk']
if user_profile is not None:
workers = Worker.objects.filter(user_profile = user_profile)
queryset = MyModel.objects.filter(worker_in=(workers))
else:
return ''
return queryset