Can I nest the viewsets and create routes that takes pk as parameters of the url?
basically:
class TaskView(viewsets.ModelViewSet):
model = Task
This works fine and it's mapped to the task/ url, so task/1/ gives the data of the task with id 1. now, i want to create an instance of the task, having CRUD operations as for the task, so i would like to have
class InstanceView(viewsets.ModelViewSet):
model = Instance
mapped to task/{pk}/instance, where pk is the id of the task.
how can i do that? is it possible?
PS: i saw that there are #action and #link but using them i loose the power of having everything made by the framework.
There are two plugins out there for making this happen: drf-nested-viewsets and drf-nested-routers.
DRF Nested Routers works on a router level and makes it easy to do nested viewsets, as the nested parameters are passed into every method for easy reference. The README in the repository gives an overview of what can be done. This does not appear to allow for nested DefaultRouters (which include the API root page).
DRF Nested Viewsets (full disclosure: created by me) is primarily meant for hyperlinked scenarios (where everything uses a HyperlinkedModelSerializer) and isn't as easy to use. It handles hyperlinked relations by mapping the current URL arguments to generate nested urls on linked models. A bit of documentation is available at the original gist.
Both plugins require overriding get_queryset for filtering nested querysets. For DRF Nested Viewsets this requires pulling url arguments from self.kwargs within the viewset and using those to filter, I am not sure how it is done using DRF Nested Routers, but it mostly likely isn't much different.
Note: If you do not need hyperlinked relations, this can be done without third-party plugins by just overriding get_queryset and filtering off of url arguments.
DRF extensions also provides a way to create nested routes.
Related
I've been reading about natural_keys and have added the get_by_natural_key() and natural_key() methods to my model(s), but the Django docs (and several posts here in SO) say: "Then, when you call serializers.serialize(), you provide use_natural_foreign_keys=True or use_natural_primary_keys=True arguments" ...followed by this example:
>>> serializers.serialize('json', [book1, book2], indent=2,
... use_natural_foreign_keys=True, use_natural_primary_keys=True)
But that example is from running in a python shell, not in the actual context of where to put it in code. From DRF, I'm using generic class based views. Where should I specify those arguments in that case?
EDIT: The ultimate goal is to be able to import fixtures using natural_keys instead of actual IDs.
You would not do this at all. serializers.serialize is Django's built-in - and very basic - serialization functionality. But you are using DRF, which has much more powerful abilities to serialize. In DRF you would define your serializer to use the relevant relational field.
Edit But I don't understand your edit at all. What do DRF's generic views have to do with fixtures?
I am new to DJango and DRF and have been asked to manage some DJango/DRF related code. After a lot of search I am still unable to find a complete example on how filter_queryset works and how can it be used with different arguments.
At some places I see it used like the following,
self.filter_queryset(queryset)
and at other places it is used with some arguments. It would be helpful if some one can explain the fundamentals like how and when to use it, what are the dependent variables (lookup_field, filter_backends etc...) and arguments and how to set them up.
I have searched a lot and also gone through the docs. If i have missed any doc kindly let me know.
The filter_queryset()--(source code) is a method which is originally implemented in GenericAPIView -- (DRF doc) class.
def filter_queryset(self, queryset):
"""
Given a queryset, filter it with whichever filter backend is in use.
You are unlikely to want to override this method, although you may need
to call it either from a list view, or from a custom `get_object`
method if you want to apply the configured filtering backend to the
default queryset.
"""
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
I think the functionality of the method is clearly visible from the doc strings.
".....and at other places it is used with some arguments"
The views's filter_queryset() method takes only one parameter, which is the queryset to be filtered.
But, filter-backends' filter_queryset() method takes three arguments which are request,queryset and the view itself.
What are FilterBackends?
Filterbackends are classes which helps us to filter the queryset with complex lookups and some other stuff.
DRF has few built-in backends which can be found here.DRF official docs recommend to use django-filter package for advanced filtering purposes.
How filter-backend working?
Take a look at the source code of DjangoFilterBackend class and it's methods...It's filter_queryset(...) method plays key role in the filtering process.
I would recommend to go through the doc of django-filter to understand the usage of the same with more examples.
By defining filterset_class, you could've more controll over the filtering process (such as providing lookup_expr etc)
Say I have a simple application with users, groups and users are members of the groups (a M2M relationship). So I create a viewset which shall do the following:
GET /group lists all groups
GET /group/1 gives detail of a group
POST /group/1/member adds a member specified in the request to group 1
GET /group/1/member lists all members of group 1
and some other CRUD operations on groups/memberships which are not relevant for my question.
To make the code as readable as possible, I came up with this solution:
class GroupViewSet(ModelViewSet):
...
#action(detail=True, methods=["get", "post"])
def member(self, request, *args, **kwargs):
return MembershipView.as_view()(request, *args, **kwargs)
class MembershipView(ListCreateAPIView):
# handle the membership somehow here
However, this approach ends with
The `request` argument must be an instance of `django.http.HttpRequest`, not `rest_framework.request.Request`.
Although manually instantiating a view inside another view and passing the request would most probably work in pure Django, the REST framework refuses to do this, as each DRF view expects a pure Django request and cannot just take an existing DRF request as it is.
I have two questions:
Is my design idea bad? And if yes, how shall I redesign?
Do you think it will be worth creating a pull request to DRF? Allowing this is very easy.
Firstly, I think your URL structure may not really be conventionally RESTful. You should be using plurals to access your resources, i.e.
POST /groups/
returns groups.
POST /groups/1
fetches an item from the groups list with an id of 1. It makes things a little clearer.
Secondly, I personally would redesign your flow as I think the current design overcomplicates your REST structure.
Are Members and Groups distinct resources? I would say yes, these are distinct. So they should be kept in different endpoints. Have /members/ and /groups/ and use django's built in filters to get the desired behavior, i.e.:
GET /members/?group_id=1
This is cleaner and more RESTful. You are fetching members who have a corresponding group_id of 1.
If you really want to keep this embedded structure, You can probably do some magic with some of rest_framework.decorators list_route and detail_route to probably achieve your current desired behavior.
However, in my experience, I've found embedding like that causes issues with some django mechanics and makes for some gnarly URL routing. I really would advise keeping distinct endpionts.
You're basically doing this:
/list-view/detail-view/list-view/
I can understand use cases where this design could make sense on paper, but I don't think it's ideal.
Trying to implement a simple ordering query on ProductCategoryView in Django-Oscar. It should be fairly simple to implement but it's taking too much time to understand. I'm having second thoughts to go forward with Oscar or not as it seems difficult to extend.
The ProductCategoryView returns products of a certain category. I want to sort them according to certain field in product model say price. First I change the parent generic class from TemplateView to ListView so that I can use get_queryset method. Then I override the get_queryset method as below and write a simple queryset in that. Still the sorting doesn't happen though the flow does go inside the get_queryset method.
def get_queryset(self):
queryset = Product.objects.filter(categories = self.category)
logger.debug("inside")
queryset = queryset.order_by("price")
return queryset
So what methods I have to overwrite. Will there be so much trouble editing Oscar every time or I'm missing something?
P.S : I have asked lot of questions around Django/Oscar Class based view recently . So I might look like a Help Vampire. Please ignore this question if that is the case.
#Anentropic is right but let me elaborate a bit.
Oscar bases all its browse views on search engines so faceting can be used to narrow down the list of products returned. By default there are search engine integrations for Solr and Elasticsearch.
If you don't use Solr or Elasticsearch, the default implementation is SimpleProductSearchHandler. It does not use any external services but instead calls Product.browsable.get_queryset(). That code lives in oscar.apps.catalogue.managers and could be customized to provide custom ordering.
This is all assuming you don't want to use a search engine as customizations required to change the order of results for other search handler classes are backend-specific.
So this is the first time I use djangorestframework-bulk for implementing batch requests. If I understand correctly, you should be able get this functionality out-of-the-box by just having your *ViewSet classes inherit also from the app's mixins like BulkUpdateModelMixin or viewsets like BulkUpdateAPIView.
As stated on the notes in the repo:
Most API urls have two URL levels for each resource:
url(r'foo/', ...)
url(r'foo/(?P<pk>\d+)/', ...)
The second url however is not applicable for bulk operations because
the url directly maps to a single resource. Therefore all bulk generic
views only apply to the first url.
However, when I try to perform PUT or PATCH bulk requests (haven't tried with DELETE) to an endpoint in my app, like:
http://localhost:8000/api/users/
I get the following error:
PUT http://localhost:8080/api/users/ 405 (METHOD NOT ALLOWED)
Is there any additional configuration I need to do in order for this url to allow PUT and PATCH requests and have my viewset's update() and partial_update() process such requests?
Thanks!!
Your problem is that you've implemented the bulk methods through the BulkUpdateModelMixin, but you don't have a proper router for your ViewSet. You now need, for example, put to go to bulk_update rather than just update.
Check out here for an example of how to set up your own router to allow for bulk update functionality with DRF ViewSets. Basically, map out the verbs to call the proper method.