Django generics.ListAPIView accepting POST method - python

I have a Django view which extends generics.ListAPIView. It works fine with get requests, however since the char limits of the URL, now I need to send the request via POST. It is the same request, the only thing I need to change is the method to POST.
My current code is pretty simple:
class MyClass(generics.ListAPIView):
serializer_class = MySerializer
paginate_by = 1
def get_queryset(self):
queryset = SomeClass.objects.all()
# do some filtering
How could I add POST support to this class?

Try this:
class MyClass(generics.ListAPIView):
serializer_class = MySerializer
paginate_by = 1
def get_queryset(self):
queryset = SomeClass.objects.all()
# do some filtering
def post(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)

You could use the ListCreatAPIView and overriede the create method to do same as your list method. Maybe you can just do something like the following:
class MyClass(generics.ListCreateAPIView):
serializer_class = MySerializer
paginate_by = 1
def get_queryset(self):
queryset = SomeClass.objects.all()
# do some filtering
def create(self, request, *args, **kwargs):
# maybe replace request.method with 'GET'
self.list(request, *args, **kwargs)
But I would suggest to use the methods as specified.

Related

Django CBVs: reuse query from get_queryset in get_context_data method in ListView

I need to pass a Tag object to the template, but without an additional query to the database in get_context_data method.
Can you please tell me if there is a more elegant way to get the value from the get_queryset method in the get_context_data.
And if there is a more elegant way, is it correct to declare your own fields in Django views
class PostListView(ListView):
model = Post
paginate_by = 3
context_object_name = 'posts'
template_name = 'blog/post/list.html'
tag = None
def get_queryset(self):
data = super().get_queryset()
if tag_slug := self.kwargs.get('tag_slug'):
self.tag = get_object_or_404(Tag, slug=tag_slug)
data = data.filter(tags__in=[self.tag])
return data
def get_context_data(self, *, object_list=None, **kwargs):
data = super().get_context_data(**kwargs)
data['tag'] = self.tag
return data
get queryset should only prepare queryset without any hit in database. In your case you do many not needed things:
def get_queryset(self):
query = Q()
if self.kwargs.get('tag_slug'):
query = Q(tags__slug=self.kwargs['tag_slug'])
return super().get_queryset().filter(query).select_related('tag')
in reality function above is One-liner.
You don't need any tag in context, but if you want:
def get_context_data(self, *args, **kwargs):
response = super().get_context_data(*args, **kwargs)
if len(response['object_list']) : # only one hit in database
response['tag'] = response['object_list'][0].tag
else:
raise Http404
return response
len made ask in database, and get all objects.
Based on reply I edit my code for the following:
because I need tag name and it can be different from tug slug I override get method to get tag object and not execute uselees code later
removed query from get_queryset
replaced "object_list" with "posts" due to iterating through posts in my template
class PostListView(ListView):
model = Post
paginate_by = 3
context_object_name = 'posts'
template_name = 'blog/post/list.html'
tag = None
def get(self, request, *args, **kwargs):
if tag_slug := self.kwargs.get('tag_slug'):
self.tag = get_object_or_404(Tag, slug=tag_slug)
return super(PostListView, self).get(request, *args, **kwargs)
def get_queryset(self):
query = Q()
if self.tag:
query = Q(tags__slug=self.tag)
return super(PostListView, self).get_queryset().filter(query)
def get_context_data(self, *args, **kwargs):
response = super().get_context_data(*args, **kwargs)
if len(response['posts']):
response['tag'] = self.tag
else:
raise Http404
return response

How to put 2 context_object_name for listView Django

I have developed a context within a context.
here's my views.py:
class AgentClientListView(OrganizerAndLoginRequiredMixin, generic.ListView):
template_name = "agents/agent_client_list.html"
context_object_name = "clients"
def get_queryset(self):
queryset = Client.objects.filter(agent_id=self.kwargs['pk'], agent__isnull=False).order_by('company_name')
return queryset
however I need to add another context_object_name: "agent"
what would I do?
thank you in advance
You need to ovveride get_context_data:
def get_context_data(self, *args, **kwargs):
context = super(AgentClientListView, self).get_context_data(*args, **kwargs)
context['agents'] = <Your agent query>
return context

paginate_queryset() got an unexpected keyword argument 'view'

while running the below code getting the "paginate_queryset() got an unexpected keyword argument 'view'" error after adding pagination
views.py
class UsersList(ListAPIView,LimitOffsetPagination):
permission_classes = (permissions.IsAuthenticated, IsVerified,permissions.IsAdminUser)
#swagger_auto_schema(
query_serializer=PaginationSerializer,
responses={status.HTTP_200_OK: UserOutputSerializer(many=True)},
operation_id="list_users",
)
def get(self, request, *args, **kwargs):
qs = User.objects.filter(is_verified=True, is_active=True).order_by('user_name')
results = self.paginate_queryset(qs, request, view=self)
users = UserOutputSerializer(results, many=True)
return self.get_paginated_response(users.data)
urls.py
path('list_users/',UsersList.as_view(),name='list_users'),
You are discarding all the good stuff about ListAPIView by overriding the get method like that. If you want to user swagger_auto_schema, then try like this:
class UsersList(ListAPIView,LimitOffsetPagination):
permission_classes = (permissions.IsAuthenticated, IsVerified,permissions.IsAdminUser)
serializer_class = UserOutputSerializer
paginate_by = 10
queryset = User.objects.filter(is_verified=True, is_active=True).order_by('user_name')
#swagger_auto_schema(
query_serializer=PaginationSerializer,
responses={status.HTTP_200_OK: UserOutputSerializer(many=True)},
operation_id="list_users",
)
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)

How to simplify the code and is it necessary?

I work on the API in the Django REST Framework. And now there is such a problem: there is a ModelViewSet and in its functions the same request to the database, the same check in the if block. Is it possible to somehow move this matter into a separate function and how to do it?
class LinkViewSet(ModelViewSet):
permission_classes = (IsAuthenticated,)
serializer_class = LinkSerializer
queryset = Link.objects.all()
def retrieve(self, request, *args, **kwargs):
instance = Link.objects.filter(Q(user_id=self.request.user.id) & Q(id=kwargs["pk"])).first()
if not instance:
return Response(data="Not found", status=status.HTTP_404_NOT_FOUND)
return super().retrieve(request, *args, **kwargs)
def partial_update(self, request, *args, **kwargs):
instance = Link.objects.filter(Q(user_id=self.request.user.id) & Q(id=kwargs["pk"])).first()
if not instance:
return Response(data="Not found", status=status.HTTP_404_NOT_FOUND)
return super().partial_update(request, *args, **kwargs)
If I understand the code correctly, the intention is to limit queryset to allow access only to links owned by currently logged in user. For that, you can just override get_queryset method and that is it. DRF will take care of getting object from the queryset by id and throwing 404 if the object is not found.
class LinkViewSet(ModelViewSet):
permission_classes = (IsAuthenticated,)
serializer_class = LinkSerializer
def get_queryset(self):
return Link.objects.filter(user_id=self.request.user.id)
You could do some of the implementation in a private method
def _link_objects_filter(self, pk):
return Link.objects.filter(Q(user_id=self.request.user.id)
& Q(pk)).first()
Or, taking it a step further, have a common implementation that uses getattr to decide which base implementation to use.
class LinkViewSet(ModelViewSet):
permission_classes = (IsAuthenticated,)
serializer_class = LinkSerializer
queryset = Link.objects.all()
def _retrieve_op(self, method, request, *args, **kwargs):
instance = self._link_objects_filter(kwargs["pk"])
if not instance:
return Response(data="Not found", status=status.HTTP_404_NOT_FOUND)
return getattr(super(), method)(request, *args, **kwargs)
def retrieve(self, request, *args, **kwargs):
return self._retrieve_op("retrieve", request, *args, **kw)
def partial_update(self, request, *args, **kwargs):
return self._retrieve_op("partial_update", request, *args, **kw)
def _link_objects_filter(self, pk):
return Link.objects.filter(Q(user_id=self.request.user.id)
& Q(pk)).first()
That could be further reduced with partial methods
import functools
class LinkViewSet(ModelViewSet):
permission_classes = (IsAuthenticated,)
serializer_class = LinkSerializer
queryset = Link.objects.all()
def _retrieve_op(self, request, method, *args, **kwargs):
instance = self._link_objects_filter(kwargs["pk"])
if not instance:
return Response(data="Not found", status=status.HTTP_404_NOT_FOUND)
return getattr(super(), method)(request, *args, **kwargs)
retrieve = functools.partialmethod(_retrieve_op, "retrieve")
partial_update = functools.partialmethod(_retrieve_op, "partial_update")
def _link_objects_filter(self, pk):
return Link.objects.filter(Q(user_id=self.request.user.id) & Q(pk)).first()
I'm not sure whether django meta programming will mess this up.

How to return custom JSON in Django REST Framework

I am trying to return custom json with get_queryset but always get 404 error in response.
class TestViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Test.objects.all()
serializer_class = TestSerializer
def get_queryset(self):
if self.request.method == "GET":
content = {'user_count': '2'}
return HttpResponse(json.dumps(content), content_type='application/json')
If I delete everything starting from def I'll got correct response with standard json data. What I am doing wrong?
If you don't need a ModelViewSet and just want custom JSON on a GET request
You can also use an APIView, which doesn't require a model
class MyOwnView(APIView):
def get(self, request):
return Response({'some': 'data'})
and
urlpatterns = [
url(r'^my-own-view/$', MyOwnView.as_view()),
]
With a ModelViewSet
You've put the custom JSON into get_queryset, that's wrong. If you want to use a ModelViewSet, this by itself should be enough:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
This ModelViewSet comes with default implementations for .list(), .retrieve(), .create(), .update(), and .destroy(). Which are available for you to override (customize) as needed
Returning custom JSON from .retrieve() and/or .list() in ModelViewSet
E.g. to override .retrieve() to return custom view when retrieving a single object. We can have a look at the default implementation which looks like this:
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
So as an example to return custom JSON:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
def retrieve(self, request, *args, **kwargs):
return Response({'something': 'my custom JSON'})
def list(self, request, *args, **kwargs):
return Response({'something': 'my custom JSON'})
There are 2 ways to custom the response in Class-based views with ModelViewSet
Solution 1: custom in views.py
class StoryViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.AllowAny,)
queryset = Story.objects.all()
serializer_class = StorySerializer
def retrieve(self, request, *args, **kwargs):
# ret = super(StoryViewSet, self).retrieve(request)
return Response({'key': 'single value'})
def list(self, request, *args, **kwargs):
# ret = super(StoryViewSet, self).list(request)
return Response({'key': 'list value'})
Solution 2: custom in serializers.py (I recommend this solution)
class StorySerializer(serializers.ModelSerializer):
class Meta:
model = Story
fields = "__all__"
def to_representation(self, instance):
ret = super(StorySerializer, self).to_representation(instance)
# check the request is list view or detail view
is_list_view = isinstance(self.instance, list)
extra_ret = {'key': 'list value'} if is_list_view else {'key': 'single value'}
ret.update(extra_ret)
return ret

Categories