I am doing exactly as the example states
here is my method
class FeedViewSet(viewsets.ModelViewSet):
model = Feed
serializer_class = FullFeedSerializer
def get_queryset(self):
user = request.user
queryset = Feed.objects.get_nearby(user)
return queryset
when i execute it, it says request not defined .. which actually isn't. the example at the rest framework's site also haven't defined request. what am i doing wrong?
The request object is available (on either REST framework's class based views, or Django's standard class based views) as self.request. You're missing the self. part of that.
Related
I am attempting to add context to a serializer within a ModelViewSet which is dependent on the current paged object list in context. I'll explain with an example.
I am building a viewsets.ModelViewSet that lists Users and a list of favorite_foods. However- the list of user's favorite foods in some external microservice accessible via API. Building a ViewSet to collect objects and performing HTTP requests on each is trivial, we can do something like this:
class UserViewSet(viewsets.ViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserSerializer(serializers.ModelSerializer):
favorite_foods = serializers.SerializerMethodField()
def get_favorite_foods(self, instance):
# performs an HTTP call and returns a list[] of foods.
return evil_coupled_microservice_client.get_user_food_list(instance)
class Meta:
model = User
fields = ('id', 'name', 'favorite_foods')
The issue is (aside from some ugly infrastructure coupling) this is going to make an HTTP request count equivalent to the page size. Instead- it would be great if I could prefetch the favorite food lists for all users on the page in a single HTTP call, and just add them into context, like this:
class UserViewSet(viewsets.ViewSet):
def get_serializer_context(self):
context = super().get_serializer_context()
users = <-- This is where I want to know what users are in the current filtered, paginated response.
users_food_dict = evil_coupled_microservice_client.get_many_users_food_list(users)
context.update({'usesr_foods': users_food_dict})
return context
However- it doesn't appear there is any way to fetch the object list that's going to be serialized. Although (I'm fairly sure) get_serializer_context is called after the queryset is filtered and paginated, I'm not sure how to access it without doing some really hacking re-compiling of the queryset based on the query_params and other pieces attached to the class.
I'll post my current solution. It's not terrible, but I'm still hoping for a cleaner built-in.
class UserViewSet(viewsets.ViewSet):
...
def list(self, request, *args, **kwargs):
# Overrwite `ListModelMixin` and store current set
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
self.current_queryset = page
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
self.current_queryset = queryset
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
This is untested so far (not sure about functionality on Detail endpoints for instance) allows for the current_queryset to be fetched within the serializer context.
What is the meaning of single model instance in RetrieveAPIView in Django REST framework.And how can we use? And will it look?
As my understanding:
single model instance: Example we use RetrieveAPIView. If we read the method that handle requests to that class, that is:
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
We can see self.get_object(), it will build a Queryset to get object instance from our model class using method get(), or rather using get_object_or_404(). As we know, get() will only take 1 object instance, or it called single model instance.
Example Book.objects.get(id=10).
For more detail, we can open this link. Start from def retrieve then read also get_object().
Likewise on DestroyAPIView and UpdateAPIView.
collection of model instances: When build a Queryset, it using all() or filter(), according to what we wrote in queryset = .... or we override in def get_queryset. Example: Book.objects.all(). See this link and read def list.
CMIW.
In my REST API I have two entities: Test and TestRun. I want to be able to send a POST request to create a TestRun (with the appropriate TestRun fields), but the URL of this request must be api/v1/test/{id}/start instead of api/v1/testrun.
I know that using #detail_route I can customise the URL, but then the request is still sent to api/v1/test/{id}:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
#detail_route(methods=['post'], url_path='start')
def start_test(self, request, pk=None):
pass
class TestRunViewSet(viewsets.ModelViewSet):
queryset = TestRun.objects.all()
serializer_class = TestRunSerializer
Perhaps some highly customised router is needed here?
OK, I have the basic example. I think you have few problems, so first things first:
My views:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
#detail_route(methods=['post'], url_path='start', serializer_class=TestRunSerializer)
def start_test(self, request, pk=None):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
# add here TestRun object
return Response(serializer.data, status=status.HTTP_200_OK)
class TestRunViewSet(viewsets.ModelViewSet):
queryset = TestRun.objects.all()
serializer_class = TestRunSerializer
My urls:
router = SimpleRouter()
router.register('test', TestViewSet)
router.register('test-run', TestRunViewSet)
urlpatterns = router.urls
and settings urls:
urlpatterns = [
url(r'^api/v1/', include('droute.urls'))
]
In this scenario you have full CRUD for Test and TestRun models - one is under api/vi/test and second in api/v1/test-run;
The detail_route decorator creates for you additional route: /api/v1/test/:id/start
But this do not mean that CRUD under api/v1/test-run is no longer accessible.
If you do not want to do not allow creation on api/v1/test-run you should use there ReadOnlyModelViewSet as a base for TestRunViewSet - this will allow only GET on the list endpoint: api/v1/test-run and on the details endpoint: api/v1/test-run//
You do not need to make magic in routers - as in example SimpleRouter is enough for that case.
Things are getting little bit more complicated if you want to make nested routers. You can search stackoverflow - there were many articles about that. But to be honest I would discourage you to use nested routers, I never feel that working with this is a pleasure :) You can check here:
https://github.com/alanjds/drf-nested-routers
I think (but I have little or nono information) that the best API for you would be something like this:
/api/v1/test -> CRUD for TEST
/api/v1/test/:id/start -> start the test POST
/api/v1/test/:id/runs -> get the runs list GET (list_route on TestViewSet or Nested router)
/api/v1/test/:id/runs/:run_id -> get the run details GET (and here is a problem - because it implies that you need nesting :(, or some custom view attached to the urls;)
Happy coding, hope this helps.
I used to follow this pattern in Django Rest Framework (DRF) 2:
class Foo(models.Model):
user = models.ForeignKey(User)
class FooSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Foo
fields = ('url')
class FooViewset(viewsets.ModelViewSet):
def get_queryset(self):
return Foo.objects.filter(user=self.request.user)
serializer = FooSerializer
model = Foo # <-- the way a ModelViewSet is told what the object is in DRF 2
[ in urls.py]
from rest_framework import routers
router = routers.DefaultRouter()
router.register('Foo', views.FooViewSet)
In DRF 3, I now get:
AssertionError at /
`base_name` argument not specified, and could not automatically
determine the name from the viewset, as it does not have a
`.queryset` attribute.
How is get_queryset overridden for an instance of rest_framework.viewsets.ModelViewSet?
Figured this one out. The model field of the rest_framework.viewsets.ModelViewSet does seem to be AWOL in DRF3. Now, if you override get_queryset you need to specify a third parameter to routers.DefaultRouter().register which is the basename parameter. Then, the function won't go off and try to find it on the non-existent queryset field of the ModelViewSet.
e.g.
router = routers.DefaultRouter()
[...]
router.register('/rest/FooBar'/, views.FooBarViewSet, 'foobar-detail')
#^^^^^^^^^^^^^^^
In addition to Ross Rogers' answer, on current version (3.8.2), you can specify only the model's name instead of the handler. So, instead of:
router.register('/rest/FooBar', views.FooBarViewSet, base_name='foobar-list')
router.register('/rest/FooBar/{pk}', views.FooBarViewSet, base_name='foobar-detail')
You can just do:
router.register('/rest/FooBar', views.FooBarViewSet, base_name='foobar')
To overrite default queryset in DRF 3, just define queryset attribute whitin your FooViewSet class.
class FooViewset(viewsets.ModelViewSet):
queryset = Foo.objects.all()
In order to have a non-readonly PrimaryKeyRelatedField, you are required to provide a queryset that contains valid options.
How can I properly populate that queryset based on the current request (user)?
The key is to subclass PrimaryKeyRelatedField and overload the get_queryset method, using the user information from the request context:
class UserFilteredPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
request = self.context.get('request', None)
queryset = super(UserFilteredPrimaryKeyRelatedField, self).get_queryset()
if not request or not queryset:
return None
return queryset.filter(user=request.user)
You can then use this new serializer just like the (unfiltered) original:
class MySerializer(serializers.ModelSerializer):
related = UserFilteredPrimaryKeyRelatedField(queryset=MyModel.objects)
Whenever the serializer accesses the queryset, it will be filtered such that only objects owned by the current user are returned.
View has a
self.request.user
attribute which you can then use to fetch user related queryset
eg
queryset = Products.objects.get(customer=self.request.user)