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'])
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.
Here is my code. I get no errors, and I can see the search button that was added to the browsable API. The problem though is the search does not work. No matter what I type into the search, it just returns every objects.
from rest_framework import status, filters
class JobView(GenericAPIView):
serializer_class = JobSerializer
filter_backends = [filters.SearchFilter]
search_fields = ['name']
def get_queryset(self):
return Job.manager.all()
def get(self, request, format=None):
queryset = self.get_queryset()
if queryset.exists():
serializer = JobSerializer(queryset, many=True)
return Response(serializer.data)
else:
return Response({"Returned empty queryset"}, status=status.HTTP_404_NOT_FOUND)
endpoint
http://localhost:8000/jobs/?search=something
returns the same as
http://localhost:8000/jobs/
No matter what I put in the search string, it returns jobs.
This basically doesn't work because you're trying to do too much. You've written your own get method which bypasses all the magic of the DRF views. In particular, by not calling GenericAPIView.get_object, you avoid a line that looks like
queryset = self.filter_queryset(self.get_queryset())
which is where the QuerySet is filtered. This simpler version, practically identical to the one in the SearchFilter docs, should work
from rest_framework import status, filters, generics
class JobView(generics.LisaAPIView):
queryset = Job.manager.all()
serializer_class = JobSerializer
filter_backends = [filters.SearchFilter]
search_fields = ['name']
NOTE based on your question, I am assuming:
that your Job model has a name field
that for some reason you've renamed the Job manager to manager via a call to models.Manager()
I think you should filter your queryset based on the parameter you're sending via GET, because it wont happen automatically. use request.query_params.get('search') to access your parameter.
Here is my views for DRF API
class CityEventsViewSet(viewsets.ModelViewSet):
def __init__(self, request, *args, **kwargs):
queryset = CityEvents.objects.filter(city=kwargs.get('city_name'))
serializer_class = CityEventsSerializer
URL:
router.register(r'cityevents/(?P<city_name>[\w\-]+)/$', CityEventsViewSet, base_name='cityevents')
I am not able to access the views function. It is not able to resolve the URL.
kwargs['city_name']
if I understand well what you mean
The url kwargs can be accessed in anywhere in the viewset as it is an instance attribute of the viewset. As a result, you don't need to do that filtering in __init__() but in the get_queryset() method.
Something like this should suffice:
def get_queryset(self):
city_name = self.kwargs['city_name']
queryset = CityEvents.filter(city_name=city_name)
return queryset
This fix worked for me. The retrieve function will receive the arguments passed through the url. and we don't need to add the regex in URL
class CityEventsViewSet(viewsets.ModelViewSet):
queryset = CityEvents.objects.all()
serializer_class = CityEventsSerializer
def retrieve(self, request, pk=None):
queryset = CityEvents.objects.filter(city=pk)
return JsonResponse(CityEventsSerializer(queryset,many=True).data,safe=False)
URL :
router.register(r'cityevents', CityEventsViewSet)
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.
Is it possible to perform raw queries in django rest framework like django. https://docs.djangoproject.com/en/dev/topics/db/sql/#performing-raw-queries
Yes you should be able to, since you can customize the queryset that backs your view, e.g.
class MyModelViewSet(viewsets.ModelViewSet):
# The usual stuff here
model = MyModel
def list(self, request):
queryset = MyModel.objects.raw('... your SQL here...')
serializer = MyModelSerializer(queryset, many=True)
return Response(serializer.data)
Manager.raw() returns RawQuerySet which is a QuerySet, so you can see how it all fits