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.
Related
Now I have the following logic implemented for a GET request in Django Rest Framework:
class SomeViewSet(mixins.ListModelMixin,
GenericViewSet):
count = None
def get_queryset(self):
query_set = ... # some_logic
self.count = query_set.count()
return query_set
def list(self, request, *args, **kwargs):
response = super().list(request, *args, **kwargs)
response.data = {'count': self.count,
'data': response.data}
return response
That is, the queryset is calculated according to complex logic, and it may contain a different number of objects that need to be returned in a GET request, since I don’t have access to the query_set variable inside the list function and I don’t want to copy the query_set calculation logic, I decided do it with a class variable.
But still, the feeling that this is not very correct does not leave. What other options are there?
You can use self.get_queryset() inside the list method instead of using a class variable. The get_queryset method will be executed every time you call it, and it will return the current queryset so:
class SomeViewSet(mixins.ListModelMixin,
GenericViewSet):
def get_queryset(self):
return ... # some_logic
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
response = super().list(request, *args, **kwargs)
response.data = {'count': queryset.count(),
'data': response.data}
return response
Edit:
To avoid the issue of multiple database queries, you can make use of the queryset that is already retrieved by the ListModelMixin and stored in the response.data attribute so:
class SomeViewSet(mixins.ListModelMixin,
GenericViewSet):
def get_queryset(self):
return ... # some_logic
def list(self, request, *args, **kwargs):
response = super().list(request, *args, **kwargs)
queryset = response.data
response.data = {'count': len(queryset),
'data': queryset}
return response
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)
Can't figure out why CreateView doesn't return HttpResponse. For now, I use this view just for posting (no GET). I thought that set self.success_url should be enough (as you can see in def post).
class TripCreationView(CreateView):
form_class = TripCreationForm
template_name = 'frontend/homepage.html'
def post(self, request, *args, **kwargs):
self.success_url = request.POST.get('success_url') or reverse('frontend:homepage')
super(TripCreationView, self).post(self, request, *args, **kwargs)
#
# def form_valid(self, form):
# trip = form.save(self.request)
# return HttpResponseRedirect(self.success_url)
def get_form_kwargs(self):
kwargs = super(TripCreationView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
Do you know what to do?
You forgot a return statement.
def post(self, request, *args, **kwargs):
self.success_url = request.POST.get('success_url') or reverse('frontend:homepage')
return super(TripCreationView, self).post(self, request, *args, **kwargs)
Why would one use a Base view in Django when this
from django.http import HttpResponse
from django.views import View
class MyView(View):
def get(self, request, *args, **kwargs):
return HttpResponse('Hello, World!')
can be written as
def get(request):
return HttpResponse('Hello, World!')
What is the advantage of the Base view vs the function view?
All sorts of reasons.
You want to make use of a specialized view, like the TemplateView mentioned by #pythonista that makes it a lot easier for you to write your view. e.g.,
class MyTemplateView(TemplateView):
template_name = 'template.html'
You want to have some isolation when you have similar behavior. For example, you want a class-based View to handle both the form rendering and the form post:
class MyFormView(TemplateView):
template_name = 'form.html'
def get(self, request, *args, **kwargs):
return super(MyFormView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs:
value1 = request.POST.get('value1')
value2 = request.POST.get('value2')
# handle the post values
return super(MyFormView, self).get(request, *args, **kwargs)
You have REST endpoint and you’d like to isolate all of the code for GET/POST/PUT/DELETE in a single class-based view.
class RestEndpoint(View):
def __init__(self):
super(RestEndpoint, self).__init__()
self.model = MyModel
def get(request, n_id, *args, **kwargs):
x = self.model.objects.get(id=n_id)
return JsonResponse(x.to_json())
def put(self, request, *args, **kwargs):
data = json.loads(request.body)
x = self.model(**data)
x.save()
return JsonResponse(x.to_json())
def post(self, request, n_id, *args, **kwargs):
data = json.loads(request.body)
x = self.model.objects.get(id=n_id)
for key, value in data.items():
setattr(x, key, value)
x.save()
return JsonResponse(x.to_json())
def delete(self, request, n_id, *args, **kwargs):
self.model.objects.filter(id=n_id).delete()
return JsonResponse({})
You just prefer using classes instead of functions, e.g., so that you can create your own fun base views and reuse code with inheritance.
I've been trying for a while now to get 'DELETE' to work when reading a collection in Django Api View. I've been using 'ListCreateAPIView' and that only provides get and post method handlers. Does anyone know how to fix this?
My view:
class NotepadDetail(generics.ListCreateAPIView):
model = Session
serializer_class = SessionSerializer
def get_queryset(self):
user=self.request.user
notepad = self.kwargs['notepad_pk']
return Session.objects.filter(user=user, notepad=notepad)
def pre_save(self, obj):
obj.user = self.request.user
obj.notepad = get_object_or_404(Notepad, user=self.request.user, pk=self.kwargs['notepad_pk'])
Solved it by adding:
def get_object(self, notepad_pk):
try:
return Notepad.objects.get(user=self.request.user, pk=notepad_pk)
except Notepad.DoesNotExist:
raise Http404
def delete(self, request, notepad_pk, format=None):
object = self.get_object(notepad_pk)
object.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
You can use a DestroyModelMixin:
class NotepadDetail(generics.DestroyModelMixin,
generics.ListCreateAPIView):
model = Session
serializer_class = SessionSerializer
def get_queryset(self):
user=self.request.user
notepad = self.kwargs['notepad_pk']
return Session.objects.filter(user=user, notepad=notepad)
def pre_save(self, obj):
obj.user = self.request.user
obj.notepad = get_object_or_404(Notepad, user=self.request.user, pk=self.kwargs['notepad_pk'])
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
The self.destroy method provided by the DestroyModelMixin will handle the object deletion, will correctly raise the pre_delete and post_delete signals, and will return the 204 NO CONTENT status.
def delete(self, request, pk, format=None):
event = self.get_object(pk)
event.delete()
return Response("Object Deleted")