Let's start with a working code sample: view gist
With the above code, everything works just fine. I can POST to /users/ and I can GET /users/current-user/.
Now, on this gist, you can see the same code. The difference between this and the first gist is that UserUpdateSerializer and UserUpdateViewSet are now implemented and added to urls.py
The new urls.py now looks like this:
router = routers.DefaultRouter()
router.register(r'users', views.UserCreateViewSet, 'user')
router.register(r'users', views.UserUpdateViewSet, 'user')
router.register(r'users/current-user', views.CurrentUserViewSet, 'current-user')
However, adding line 3 (UserUpdateViewSet) breaks the next route(CurrentUserViewSet). It stops accepting GET requests(the one which is actually implemented) and starts accepting PUT and PATCH. I believe something about registering viewsets with the same prefix and base_name somehow messes with other unrelated routes.
Is this functionality unsupported by django-rest-framework or is it a bug? I certainly could implement create and update under the same viewset, but as drf don't support different serializer_class for input and output this become impossible. Overriding the list or update method to avoid self.get_serializer_class is not a solution, as drf-swagger users ViewSet.serializer_class to generate documentation.
This usage isn't supported.
Either have the different cases be different actions on a single view set (which is how viewsets/routers are intended to work throughout), or use explicit views.
We _could_ consider raising an explicit error in this case if anyone wanted to raise an issue or PR for that.
Hello @tomchristie, is there a reason why this is unsupported or it's just not implemented and a fix is welcome?
I can't use a single viewset or function based views as I need django-rest-swagger to generate proper documentation and it uses serializer_class and filter_backends to do so.
Django seems to correctly identify the routes by using manage.py show_urls, with the CreateViewset listed as user-list and UpdateViewset as user-detail. Everything seems alright here.
I have tracked the issue a little further. When ViewSet.as_view() is called on project initialization, the actions variable is correctly set for each view, and the view function returned.
However when the returned affected view function is called, actions variable reference the incorrect actions object. I checked this looking at the id(actions) on as_view() and view().
I can't seem to get any further than that on identifying the issue. Do you think with this info you can give me some directions about where this actions variable might be being modified?
The right way to handle this is to have a single viewset, and to perform any switching of serializer_class using get_serializer_class and eg. testing self.action to switch between two different types of serializer (if needed)
Hi, @tomchristie, thanks for you response.
I followed your advice and implemented UserCreateViewSet and UserUpdateViewSet under the same viewset, overriding get_serializer_class(). That seems to work fine with django, drf and drf-swagger.
After doing this, it made sense to rename UserCreateViewSet to UserResourceViewSet and implement CurrentUserViewSet as a dynamic route inside UserResourceViewSet.
However, as I refactored CurrentUserViewSet as dynamic routes inside UserResourceViewSet, it seems I have arrived at the very same bug. This implementation is here https://gist.github.com/leonardoarroyo/19cc5213143a2c30617296bd505ca72e.
Reimplementing the above gist with different url_path arguments to the decorators(such as 'current_user_update' and 'current_user_partial_update'), makes it all work fine, but once they have the same url_path, the errors start(even if each same url_path implements a different method). I am still unsure if this is a bug in drf or django.
I have put inside drf print('as_view()', actions, id(actions)) and print('view()', actions, id(actions)) in the respective functions, and a print(regex) inside get_urls(). Here's the output when I execute django runserver.
(atados-ovp) arroyo@i5 $ /home/arroyo/ovp/atadosovp ./manage.py runserver
Performing system checks...
^users/$
as_view() {'post': 'create'} 140578991855880
---------------------
^users/current_user/$
as_view() {'get': 'current_user'} 140578991856520
---------------------
^users/current_user/$
as_view() {'patch': 'current_user_partial_update'} 140578991857032
---------------------
^users/current_user/$
as_view() {'put': 'current_user_update'} 140578991857544
---------------------
^users/recovery-token/$
as_view() {'post': 'create'} 140578992141320
---------------------
^users/recover-password/$
as_view() {'post': 'create'} 140578991915720
System check identified no issues (0 silenced).
October 08, 2016 - 02:15:05
Django version 1.10.1, using settings 'server.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
As you can see, all as_view() call has its own actions object.
Once I call PUT /users/current_user/ I should get object 139636346255944 as actions. But as I make the request, I get the following output:
view() {'get': 'current_user'} 140578991856520
[08/Oct/2016 01:55:15] "PUT /users/current_user/ HTTP/1.1" 405 40
Somenthing weird happening here. Shouldn't the id output be 139636346255944 and not 140578991856520 which refers to the GET request?
Do you have any thoughts on this?
Thanks
I can't see anything at https://gist.github.com/leonardoarroyo/19cc5213143a2c30617296bd505ca72e
but at this point it sounds like a usage issue rather than an actionable issue for us to tackle. Best would be to take it to the discussion group.
Hi @tomchristie. Thanks for your response.
I'm submitting this diff so you can take a look.
The "red" implementation does not work while the "green" implementation works just fine.
As I said before, I added some debug prints to drf and here is the result.

Although this implementation is different than the original that gave life to this issue(on the original the idea was to implement two viewsets under the same base route, now I'm trying to implement two methods with different verbs on the same route), I believe the issue is the same.
The view() function is not referencing the correct actions variable when called.
Any thoughts as to where I should look further into this?
Edit:
I also tried what seems to be the "correct" implementation, which would be a single current_user method with the @list_route decorator passing methods=['GET', 'PUT', 'PATCH']. This seems to work but generate furthers problems with SchemaGenerator.
This is the schema I need(generated correctly using the broken "red" implemantation):
<Atados API "">
users: {
create(name, email, password)
current_user()
current_user_partial_update([password])
current_user_update(password)
}
and this is the generated schema if I use what seems to be the correct implementation:
<Atados API "">
users: {
create(name, email, password)
current_user()
}
Any thoughts as to where I should look further into this?
I'd suggest trying to reduce it to a single test case, or a simple reproducible example.
Most helpful comment
The right way to handle this is to have a single viewset, and to perform any switching of
serializer_classusingget_serializer_classand eg. testing self.action to switch between two different types of serializer (if needed)