In DRF 3.1 I can do this to get paginated response but it seems like the get_paginated_response() is not available in 3.0. What would be the equivalent?
class NoteList(ListCreateAPIView):
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
paged_queryset = self.paginate_queryset(queryset)
serializer = NoteSerializer(paged_queryset, many=True)
return self.get_paginated_response(serializer.data)
It's PaginationSerializer, set the serializer class by:
'DEFAULT_PAGINATION_SERIALIZER_CLASS': 'YourCustomPaginationSerializer'
or
class MyView(generics.GenericAPIView):
pagination_serializer_class = YourCustomPaginationSerializerClass
mixin.py
def list(self, request, *args, **kwargs):
instance = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(instance)
if page is not None:
serializer = self.get_pagination_serializer(page)
else:
serializer = self.get_serializer(instance, many=True)
return Response(serializer.data)
generics.py
def get_pagination_serializer(self, page):
"""
Return a serializer instance to use with paginated data.
"""
class SerializerClass(self.pagination_serializer_class):
class Meta:
object_serializer_class = self.get_serializer_class()
pagination_serializer_class = SerializerClass
context = self.get_serializer_context()
return pagination_serializer_class(instance=page, context=context)
If you are doing the migration stuffs, don't forget the pagination params in settings.py or View, has been changed to new paginator.
such as. PAGINATE_BY_PARAM -> page_size_query_param.
anyhow, I think the new paginator is much confortable than the previous one.
see more detials on Pagination Docs
Related
I have the following view that allows me to determine defined prices for the logged in user and captures the information of the provider model requested by the user:
class ProductStoDists(mixins.ListModelMixin, generics.GenericAPIView):
permission_classes = (permissions.IsAuthenticated,IsSavedRole)
queryset = SellProduct.objects.select_related("provider", "product", "coupon_percentage", "coupon_quality").filter(~Q(stock = None), Q(disabled_sale=False))
serializer_class = SellProductSerializer
parser_classes = (MultiPartParser,)
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
filterset_fields = ["product", "provider", "provider__state_prin_venue", "product__sectors", "product__categories", "provider__type_provider"]
pagination_class = Pagination
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
##method_decorator(cache_page(60*60))
##method_decorator(vary_on_cookie)
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
entity = Provider.objects.filter(administrator=request.user)
data_price = []
if page is not None:
serializer = self.get_serializer(page, many=True)
object_dataset = DataSetIds()
if request.user.role != "lab":
prices = []
for sell_product_cost in serializer.data:
provider = Provider.objects.filter(id_provider=sell_product_cost["provider"]).select_related("country_prin_venue", "state_prin_venue", "city_prin_venue", "administrator")
price_object = CustomPrices(entity, request.user.id, provider)
if provider[0].type_provider == "dist":
if request.user.role == "dist" or request.user.role == "sto":
sell_product_cost["distributors"] = price_object.get_sell_distributors(sell_product_cost["id_sell"])
else:
sell_product_cost["distributors"] = None
else:
if request.user.role == "sto":
sell_product_cost["storage"] = price_object.get_sell_storage(sell_product_cost["id_sell"])
else:
sell_product_cost["storage"] = None
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
How could I make the "storage" or distributors field be done from the serializardor and not from the view?, since I consider that this can slow down the response of the customer service.
Without knowing your model definitions, the answer likely involves performing this work at the SQL builder level rather than after querying the data. If you can't, then this points to an architectural problem that limits the abilities of your application.
For example let's say your SellProduct model has a foreign key to Provider. If you need to conditionally add information to the queryset then you can do so using annotate and Case
queryset = self.filter_queryset(self.get_queryset())
queryset = queryset.select_related('provider') \
.annotate(
distributors=Case(
When(provider__type_provider='dist', then=Value('foo')),
default=Value('other foo'),
output_field=CharField()
)
But if you're relying on python methods to fill in bits of information for each instance returned from the queryset then you need to re-assess your model structure and determine if you can better relate information to avoid this situation.
Suppose I need to set up several GET endpoints that look like this objects/past, objects/future. Example:
#action(detail=False, methods=["GET"], name="Past Objects")
def past(self, request, *args, **kwargs):
startdate = datetime.datetime.now()
some_user = UserProfile.objects.get(user__username="someuser")
queryset = self.queryset.filter(
other__attribute__profile=some_user,
creation_date__lte=startdate
).order_by("-creation_date")
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
The above works just fine. But is there anyway to avoid the page = ... -> serializer= ... part?
I have specified this in my ModelViewSet:
pagination_class = CustomObjectPagination
But it seems the pagination is only auto-applied to default methods like get_queryset and not custom actions. Do I have to write this boilerplate every time I specify a custom action like past?
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
Edit: Should have made it clearer that I'm asking specifically whether there's a built-in way to do the above.
I don't think we have any built-in to apply pagination on actions. But, we can have a simple decorator to do this. Make sure that your action returns a list or QuerySet when using this decorator.
from functools import wraps
from django.db.models import QuerySet
def paginate(func):
#wraps(func)
def inner(self, *args, **kwargs):
queryset = func(self, *args, **kwargs)
assert isinstance(queryset, (list, QuerySet)), "apply_pagination expects a List or a QuerySet"
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
return inner
#paginate
#action(detail=False, methods=["GET"], name="Past Objects")
def past(self, request, *args, **kwargs):
startdate = datetime.datetime.now()
some_user = UserProfile.objects.get(user__username="someuser")
queryset = self.queryset.filter(
other__attribute__profile=some_user,
creation_date__lte=startdate
).order_by("-creation_date")
return queryset
I wrote a simple function for that:
def response_with_paginator(viewset, queryset):
page = viewset.paginate_queryset(queryset)
if page is not None:
serializer = viewset.get_serializer(page, many=True)
return viewset.get_paginated_response(serializer.data)
return Response(viewset.get_serializer(queryset, many=True).data)
Usage is like:
#action(...)
def comments(self, ...):
queryset = Comment.objects.filter(...)
return response_with_paginator(self, queryset)
My application is using GenericViewSet with ListModelMixin. I have used filter_backends and filter_class to filter out results. (see 'list': serializers.BookingListSerializer from screenshot below)
I am working on the following brief:
Let's say I have a list of animals which are pre-filtered (using filter_backends) and then shown on UI to the user.
Users can further filter results based on some search criteria from UI (let's say name, type, color). These filterations are handled by filter_class.
In a separate Tab on UI which only shows animals of type Dogs rather than the entire collection of animals. And which can again be filtered further based on the name & color.
I must create 2 separate end-points to show both kinds of results to the user (to have more control over results...ya screw DRY!). But I can't figure out how to create them in Django as both animals and dogs use the same django modal and the filter backends and filter class are applied only to the actual modal ie. on the list of animals.
I need simple def list1(request) and def list2(request) where I can filter the query_set based on request params and my filter backends and filter classes.
api.py
class BookingViewSet(
MultipleSerializerMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet
):
lookup_field = 'uuid'
queryset = models.Booking.objects.all()
permission_classes = [DRYPermissions, ]
filter_backends = [filters.BookingFilterBackend, DjangoFilterBackend, ]
filter_class = filters.BookingFilter
pagination_class = BookingViewSetPagination
serializer_class = serializers.BookingDetailSerializer
serializer_classes = {
'create': serializers.BookingCreateUpdateSerializer,
'update': serializers.BookingCreateUpdateSerializer,
'duplicate': serializers.BookingCreateUpdateSerializer,
'list': serializers.BookingListSerializer,
'list_drafts': serializers.BookingListSerializer,
'create_draft': serializers.BookingCreateUpdateSerializer,
'submit_draft': serializers.BookingCreateUpdateSerializer,
}
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
booking = services.create_booking(serializer.validated_data)
data = serializers.BookingDetailSerializer(booking, context={'request': request}).data
return response.Created(data)
def update(self, request, *args, **kwargs):
booking = self.get_object()
partial = kwargs.pop('partial', False)
serializer = self.get_serializer(booking, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
booking = services.update_booking(booking, serializer.validated_data)
async('shootsta.bookings.tasks.booking_update_google_calendar_event', booking.pk)
data = serializers.BookingDetailSerializer(booking, context={'request': request}).data
return response.Ok(data)
#detail_route(methods=['POST'], url_path='duplicate')
def duplicate(self, request, *args, **kwargs):
booking = self.get_object()
new_booking = services.duplicate_booking(booking)
data = serializers.BookingDetailSerializer(new_booking, context={'request': request}).data
return response.Created(data)
#list_route(methods=['GET'], url_path='list-drafts')
def list_drafts(self, request, *args, **kwargs):
# Code goes here! Here i'll get some params from url like state and title and then return filtered the results.
pass
#list_route(methods=['POST'], url_path='create-draft')
def create_draft(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
booking = services.create_booking(serializer.validated_data, constants.BookingMode.draft)
data = serializers.BookingDetailSerializer(booking, context={'request': request}).data
return response.Created(data)
#detail_route(methods=['POST'], url_path='submit-draft')
def submit_draft(self, request, *args, **kwargs):
booking = self.get_object()
booking.submit_draft(by=request.user)
booking.save()
data = serializers.BookingDetailSerializer(booking, context={'request': request}).data
return response.Ok(data)
#detail_route(methods=['POST'], url_path='approve')
def approve(self, request, *args, **kwargs):
booking = self.get_object()
booking.approve(by=request.user)
booking.save()
data = serializers.BookingDetailSerializer(booking, context={'request': request}).data
return response.Ok(data)
filters.py
# Standard Library
import operator
from functools import reduce
# Third Party
from django.db.models import Q
from django_filters import rest_framework as filters
from dry_rest_permissions.generics import DRYPermissionFiltersBase
# Project Local
from . import models
class BookingFilterBackend(DRYPermissionFiltersBase):
def filter_list_queryset(self, request, queryset, view):
if request.user.is_role_admin:
return queryset
if request.user.is_role_client:
return queryset.filter(Q(client=request.user.client))
if request.user.is_role_camop:
return queryset.filter(Q(camera_operator=request.user))
return queryset.filter(Q(created_by=request.user))
def filter_booking_title(queryset, name, value):
"""
Split the filter value into separate search terms and construct a set of queries from this. The set of queries
includes an icontains lookup for the lookup fields for each of the search terms. The set of queries is then joined
with the OR operator.
"""
lookups = ['title__icontains', ]
or_queries = []
search_terms = value.split()
for search_term in search_terms:
or_queries += [Q(**{lookup: search_term}) for lookup in lookups]
return queryset.filter(reduce(operator.or_, or_queries))
class BookingFilter(filters.FilterSet):
title = filters.CharFilter(method=filter_booking_title)
class Meta:
model = models.Booking
fields = [
'title',
'state',
'client',
]
class SampleViewset(.....):
#list_route(methods=['GET'])
def list_2(self, request, *args, **kwargs):
myqueryset = MyModel.objects.all() # or whatever queryset you need to serialize
queryset = self.filter_queryset(myqueryset)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
The key points you should notice here are,
1. The filtering process are being excecuted inside the self.filter_queryset() method, which return a QuerySet after filter applied.
2. You could use self.get_queryset() method in place of myqueryset = MyModel.objects.all() staement, which is the DRF Way of doing such things
UPDATE-1
If you want to use the default queryset , you could use the get_queryset() method as,
class SampleViewset(.....):
#list_route(methods=['GET'])
def list_2(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
or simply,
class SampleViewset(.....):
#list_route(methods=['GET'])
def list_2(self, request, *args, **kwargs):
return self.list(self, request, *args, **kwargs)
I didn't quite get the question but I think you should do the same thing on your custom action that DRF does on its generic list. just call filter_queryset on your initial query for example:
class your_view(....):
...
...
def get_queryset2(self):
return YourotherModel.objects.all() ### or any thing your i.e. specific fiter on your general model
#action(methods=['GET'], detail=False)
def list2(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset2()) ### call filter_queryset on your custom query
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
class MyModelSerializer(serializers.ModelSerializer):
field1 = serializers.CharField()
field2 = serializers.SerializerMethodField('get_awesome_user')
def get_current_user(self):
request = self.context.get("request")
if request and hasattr(request, "user"):
return request.user
return None
def get_awesome_user(self, obj):
user = self.get_current_user()
## use this user object, do some stuff and return the value
return ...
My api(which uses authentication_classes and permission_classes) is using this serializer and the get_current_user function always returns None. when I debug it, I found that self.context is empty dictionary, i.e {}. to be double sure I also printed self.context.keys(), still it's empty list.
I followed this thread.
Get current user in Model Serializer
PS: I'm using djangorestframework==3.3.3, Django==1.9.1
EDIT: adding viewset code
class MyModelViewSet(viewsets.ModelViewSet):
authentication_classes = (SessionAuthentication, BasicAuthentication, TokenAuthentication)
permission_classes = (IsAuthenticated,)
def list(self, *args, **kwargs):
queryset = MyModel.objects.all()
page = self.paginate_queryset(queryset)
if page is not None:
serializer = MyModelSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = MyModelSerializer(queryset, many=True)
return Response(serializer.data)
How do you create serializer in your viewset's list() method? You should call
serializer = self.get_serializer(data=request.data)
to get your serializer context filled automatically as it is done in default implementation of this method in DRF mixins., but I have a feeling that you're just creating it manually, like this:
serializer = MyModelSerializer(instance)
So, to fix this, you should either call get_serializer(), or pass extra context argument to serializer constructor:
serializer = MyModelSerializer(instance, context={'request': request, ...})
I'm using Django rest framework, Here is my serializers.py for social app:
class SocialPostSerializer(serializers.ModelSerializer):
likes = serializers.SerializerMethodField() # define field
class Meta:
model = SocialPost
def get_likes(self, obj):
post_id = obj.id
#I get post_like from django-redis
post_like = get_redis_connection("default")
likes = post_like.get("post"+":"+str(post_id))
if likes == None:
return 0
else:
likes = likes.decode('utf-8')
return likes
With the code above, I got what I need from the API.
Since 'likes' doesn't exist in my database(Mysql here), I can't using order_by('likes') to sort the data with django ORM
I follow the doc here ListCreateAPIView which lead me to override list(): (I had override create() and get_queryset() before)
from operator import itemgetter
class PostList(generics.ListCreateAPIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
queryset = Post.objects.all()
serializer_class = PostAllSerializer
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
#problem here
serializer.data = sorted(serializer.data, key=itemgetter(serializer.data['likes']))
return Response(serializer.data)
def create(self, request, *args, **kwargs):
act_id = request.data.get('act')
act = Act.objects.get(pk=act_id)
if act.act_type == 0:
if request.user != act.user:
return Response(status=403)
return super().create(request, args, kwargs)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def get_queryset(self):
queryset = Post.objects.all().order_by('-post_create_time')
act_id = self.request.query_params.get('act_id', None)
post_author = self.request.query_params.get('post_author', None)
if act_id is not None:
queryset = queryset.filter(act=act_id)
if post_author is not None:
queryset = queryset.filter(user__user_name=post_author)
return queryset
Nothing happened, What is wired is even when I uncomment
return Response(serializer.data)
Still nothing happened, Which part is wrong?
Another question is when I wanna add some extra data like 'question' when I use django FBV:
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': 'some data I wanna add'})
Is it possible to add the data in serializer.data? For example, I wanna display user_id from which user request this api:
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
user_id = request.user.id #how can I add the user_id into response data?
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
I look around the docs here Including extra context.
serializer = AccountSerializer(account, context={'request': request})
I don't really understand how to add data in it.
"Since 'likes' doesn't exist in my database(Mysql here), I can't using order_by('likes') to sort the data with django ORM"
You wont be able to sort the results using django ORM as the likes field is not a part of your DB table. The only way to do it is to sort the serializer.data that you get in your view. In your case serializer.data will be a list of dictionary, you can use sort command for list and sort on likes using lambda.
One thing to take care here is as you will be doing the sort by loading the data in memory, make sure that you dont load a lot of data. Have a check on memory utilization.
"Another question is when I wanna add some extra data like 'question' when I use django FBV"
I did'nt understand what is needed here.