According to Django REST framework documentation, the following two code snippets should behave identically.
class UserViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving users.
"""
def list(self, request):
queryset = User.objects.all()
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = User.objects.all()
user = get_object_or_404(queryset, pk=pk)
serializer = UserSerializer(user)
return Response(serializer.data)
class UserViewSet(viewsets.ModelViewSet):
"""
A viewset for viewing and editing user instances.
"""
serializer_class = UserSerializer
queryset = User.objects.all()
But the way I interpret it, in the first case the query User.objects.all() is run with every api call, which in the second case, the query is run only once when the web server is started, since it's class variable. Am I wrong ? At least in my tests, trying to mock User.objects.all will fail, since the UserViewSet.queryset will already be an empty Queryset object by that time.
Someone please explain me why shouldn't the queryset class argument be avoided like pest and get_queryset be used instead ?
Edit: replacing queryset with get_queryset makes self.queryset undefined in the retrieve method, so I need to use self.get_queryset() within the method as well...
you are wrong, django queries are lazy so in both case the query will run at response time
from django docs:
QuerySets are lazy – the act of creating a QuerySet doesn’t involve any database activity. You can stack filters together all day long, and Django won’t actually run the query until the QuerySet is evaluated
ModelViewSet provides other actions like delete and update you may need to add some limitations on them for user model (like checking permissions or maybe you don't like to let users simply delete their profiles)
self.queryset is used for router and basename and stuff, you can ignore it and set basename in your router manually. it's not forced but I think it make my code more readable
note that usually def get_queryset is used when you want to do some actions on default queryset for example limit self.queryset based on current user. so if get_queryset is going to return .objects.all() or .objects.filter(active=True) I suggest using self.queryset to have a cleaner code
note2: if you decide to define get_queryset I suggest to also define self.queryset (if possible)
note3: always use self.get_queryset in your view method, even you didn't defined this method, you may need need to create that method later and if your view method are using self.queryset it may cause some issues in your code
Adding to Aliva's answer and quoting from DRF viewset code, under get_queryset() method definition it states:
This method should always be used rather than accessing self.queryset
directly, as self.queryset gets evaluated only once, and those results
are cached for all subsequent requests. You may want to override this if you need to provide different
querysets depending on the incoming request.
Related
I have two models, Foo and Bar. Bar has a foreign key to Foo. I have a ModelViewSet for Foo and I want the route /api/foos/<pk>/bars/ to return all the bars related to the given foo.
I know this can be done using actions. For example
class FooViewSet(viewsets.ModelViewSet):
serializer_class = FooSerializer
queryset = Foo.objects.all()
#action(detail=True, methods=['GET'])
def bars(self, request, pk):
queryset = Bar.objects.filter(foo=pk)
serializer = BarSerializer(queryset, many=True)
return Response(serializer.data)
#bars.mapping.post
def create_bar(self, request, pk):
request.data['foo'] = pk
serializer = BarSerializer(request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
However, this feels like a lot of unnecessary boilerplate code.
Using bars = PrimaryKeyRelatedField(many=True) unfortunately doesn't fit my case because each foo can have hundreds of bars, which I don't want to send in every request.
Is this possible to do in viewsets in a more convenient way? If not, what is the normal DRF way of doing it? A solution with a different URL is also acceptable for me as long as it minimizes boilerplate code.
The straightforward solution for me is to use a simple generics.ListAPIView for this specific endpoint:
views.py
from rest_framework import generics
class FooBarsListAPIView(generics.ListAPIView):
serializer_class = BarSerializer
def get_queryset(self):
return Bar.objects.filter(foo=self.kwargs.get('pk'))
And then you just register this view in your urls.py instead of the viewset.
This is all you need to do in order to achieve the result that you want. You just specify how your queryset filter looks and everything else is done by the ListAPIView implementation.
Viewsets are doing the work in most cases, but if you want something specific like this, the viewset can become an overkill.
Yes, now there is one additional class defined for this specific endpoint, but the boilerplate code is reduced to zero, which is what we want at the end of the day.
I have started using GenericAPIView instead of APIView and I am confused about the use of queryset and serializer_class being defined at the top of the class.
I understand these have to be defined, but I now have a query at the top of my class and another query inside GET. My question is can I use the queryset inside of my GET method so I am not making 2 unnecessary queries.
class demo(GenericAPIView):
queryset = Demo.objects.all()
serializer_class = DemoSerializer
def get(self, request, num, format=None):
query = Demo.objects.filter(name=test, number=num)
In other words, queryset = Demo.objects.all() is defined because it is required - but I am not really utilizing it so seems like an extra query...
queryset required only in case you not defined get_queryset method. In your case instead of define additional queryset in get, just implement get_queryset. If you are using url's kwargs, you can get it inside this method with self.kwargs attribute:
class demo(GenericAPIView):
serializer_class = DemoSerializer
def get_queryset(self):
return Demo.objects.filter(name=test, number=self.kwargs['num'])
I noticed a strange behaviour while extending ModelAdmin.
I have this code:
class MakeModelAdmin(admin.ModelAdmin):
...
def changelist_view(self, request, extra_context=None):
if request.user.is_superuser:
self.list_display = ['company', 'name']
# else:
# self.list_display = ['name']
return super().changelist_view(request, extra_context=extra_context,)
The goal is to change list_display dynamically based on user (supervisor or not).
I log in with two different users, in two different browsers, one of them results to be a superuser, the other isn't.
self.list_display is set by one of users but debugging the request with the other user I can see the variable still set, so it changes the next behavior of the other user's view.
Uncommenting the lines it works but I don't like it at all.
It seems to me it's acting like a singleton.
I also tried to change to:
super(MakeModelAdmin, self).changelist_view(request, extra_context=extra_context,)
But it has the same effect.
Is there any solution for that?
Maybe this is not the right way to achieve my goal?
The documented way to dynamically change the behavior of the admin based on the request is to use the get_* methods. In your case that would be something like:
def get_list_display(self, request):
if request.user.is_superuser:
return ['company', 'name']
else:
return super().get_list_display(request)
As for AdminSite, it's not a singleton (that is, the same instance isn't returned every time you instantiate it). It's just that a single instance is created during the Django setup process and then used to service all subsequent requests.
I am trying to write a view in which a post can be created and in the same page, the object_list will be displayed. And even an object can be updated and deleted.
Country Capital
India Delhi UPDATE DELETE
USA Washington UPDATE DELETE
----- ------
I would appreciate helping me in achieve this or suggesting a similar type of question.
What you're looking for are Mixins.
Try creating a detail view class with the following parameters:
mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView
For example:
class ObjectDetail(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
queryset = Object.objects.all()
As has proposed by Daniel, if you like DRF, ViewSets are also a decent alternative. However, they're not exactly succinct so I generally avoid them when possible.
Something like a ModelViewSet, however, is extremely clear-cut and the approach I generally choose.
Here's an example:
class ObjectViewSet(viewsets.ModelViewSet):
queryset = Object.objects.all()
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
Beautiful, isn't it?
For more details, see the DRF tutorial: http://www.django-rest-framework.org/tutorial/6-viewsets-and-routers/
You are mixing view and template. View handle requests and template show content and links.
You will have ListView, which will contain list of posts. In template you add forms for update, form for create and forms for delete. Each form will have attribute action with link to proper view. So update forms will have link to url with UpdateView, create forms to CreateView, and delete to DeleteView. In each form you set redirect back to ListView. This way if you want to use only Django.
OR
If you really want to everything handle on one page without refreshing and redirecting. You can use ajax and django-rest-framework and its viewset. In viewset you can handle lists, create, update, push, detail, in one class.
Viewset:
class UserViewSet(viewsets.ViewSet):
"""
Example empty viewset demonstrating the standard
actions that will be handled by a router class.
If you're using format suffixes, make sure to also include
the `format=None` keyword argument for each action.
"""
def list(self, request):
pass
def create(self, request):
pass
def retrieve(self, request, pk=None):
pass
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
def destroy(self, request, pk=None):
pass
I would like to set custom permissions with django guardian on my django rest framework views. I've successfuly achieved it for RetrieveModelMixin, but not for ListModelMixin.
I have a permission class looking like this one :
class CustomPerm(permissions.BasePermission):
def has_permission(self, request, view):
return request.user and request.user.is_authenticated()
def has_object_permission(self, request, view, object):
if request.method == 'GET':
if object.public is True:
return True
if object.user.is_staff is True:
return True
if 'read_object' in get_perms(request.user, object):
return True
return False
if request.method == 'POST':
#...
I also simplified the view here :
#authentication_classes((TokenAuthentication, SessionAuthentication, BasicAuthentication,))
#permission_classes((CustomPerm,))
class ObjectView(ListModelMixin,
RetrieveModelMixin,
viewsets.GenericViewSet):
queryset = myObject.objects.all()
serializer_class = ObjectSerializer
Behaviour I was naïvly expecting : ListModelMixin could filter by itself objects according to CustomPerm has_object_permission rules.
But it does not work like that. I'm able to do what I want by writing a get_queryset method and applying my custom permission rules, but it seems unappropriate and awful.
Is there a better way ? Thanks :)
PS: I'm sure I'm missing something and my question is naïve but I can't see what.
I'm afraid that the framework doesn't work that way ... The permissions are there to deny the access (when a condition is met), but not to filter the objects for you. If you need to return specific objects, then you need to filter them in the view (queryset) depending on the current user, if needed.
Well overriding isn't awful depending on how you do it... but it is not the question.
If I understand well what you want to do is to filter your queryset using your custom permission.
What I recommend, to keep your code explicit and simple, override your backend filter like in the doc
But be careful filter_queryset apply on both retrieve and list methods