9

I have a REST framework API and I have to dispatch an url to 2 different views, depending on a method.

the architecture is like this:

bookshop/authors/ - lists all authors, with POST - adds an author

bookshop/authors/<author>/ - with GET - gets details for an author, including books

bookshop/authors/<author>/ - with POST - creates a posting of a book for the same author. 

bookshop/authors/<author>/<book>/ - gets a book, no posting

In general, for all my API I'm using Viewsets with Routers.

I tried doing this:

urlpatterns = patterns(
    '',
    url(r'^author/(?P<author>[0-9]+)',
        AuthorViewSet.as_view({'get': 'retrieve'}),
        name='author-detail'),
    url(r'^author/(?P<author>[0-9]+)',
        BookViewSet.as_view({'post': 'create'})),
)

but then it goes to the first url and the viewset checks for methods and throws an exception MethodNotAllowed.

I tried to catch it like this:

try: 
    urlpatterns = patterns( 
    '', 
    url(r'^author/(?P<author>[0-9]+)',  
    AuthorViewSet.as_view({'get': 'retrieve'}), 
    name='author-detail') 
    ) 
except MethodNotAllowed: 
    urlpatterns = patterns( 
    '', 
    url(r'^author/(?P<author>[0-9]+)', 
    BookViewSet.as_view({'post': 'create'})), 
    )

But it doesn't work too.

Is there any way to do it using viewsets?

8
  • You are getting that error because by default posts are not allowed on detail views.... bookshop/authors/<author>/ - with POST that in particular.. Why not change that to a put or a patch Commented May 5, 2016 at 16:10
  • It's not supposed to be a detail view in some cases, that's why I want to register two different views for the same url. For example: bookshop/authors/Lindgren/ if accessed with a GET then a detail view, and if accessed with POST then it should post (a book for that author, not an author). Commented May 5, 2016 at 16:19
  • 1
    Django urls work in such a way that the first one to be matched is the one whose view will be used. It doesn't use the method to infer a view to run. So in your case doing a POST to authors/Lindgren/ the first view with that url will always be matched first. In the example above AuthorViewSet will be used for both POST and GET. Commented May 5, 2016 at 16:25
  • They work separately, each of that views. Commented May 5, 2016 at 16:26
  • 1
    Thats okay. But BookViewSet will never be used. Because AuthorViewSet is matched first since it shares the same url with BookViewSet and BookViewSet comes after it Commented May 5, 2016 at 16:29

2 Answers 2

2

The problem is that organizing your API in such a manner breaks RESTful convention. Breaking RESTful convention is not always bad, but usually represents a poor design and certainly means it is harder to user 3rd party software designed around the restframework to support your schema. So my suggestion is to update your API schema to:

GET  bookshop/authors/ - lists all authors
POST bookshop/authors/ - creates an author
GET  bookshop/authors/<author>/ - gets details for an author
POST bookshop/authors/<author>/books/ - creates a book for an author
GET  bookshop/authors/<author>/books/<book> - gets a book

If you need to add postings you can also have (I'm not sure of the relationships between the objects so, not sure if the below accurately represents that relationship).

POST bookshop/authors/<author>/books/<book>/postings - creates a posting
GET  bookshop/authors/<author>/books/<book>/postings/<posting> - gets a posting
Sign up to request clarification or add additional context in comments.

4 Comments

I solved this by overriding viewset and selecting between two serializers depending on a HTTP method. The reason why I didn't want to change API structure is that it quite well adds to a broader logical structure in the project (it's not authors and books really :) )
But actually your answer makes sense, so I'll upvote it, but not accept it, sorry
and, for the restful convention in this case, the API won't be accessed by a third party (at least it's not a plan now), only from our own javascript applications, so maybe it's not a huge problem then.
Makes sense. A caveat, breaking convention will also often make it harder for internal frontend applications to use the API as well. For example, an angular service like Restangular expects api's to use restful convention. Breaking from that convention makes using Restangular or a similar service harder. And involves hacking the service a little or not using it.
0

When using Viewsets, typically you'll want to register your views with a router instead of binding views directly as you have done. The router will take care of "routing" the request to the correct handler.

Checkout:

http://www.django-rest-framework.org/api-guide/viewsets/#example

http://www.django-rest-framework.org/api-guide/routers/

4 Comments

I am registering all my other views with router, but in this particular case I can't do it, because of the architecture of the API. It's because these viewsets are overlapping. If I do it like this: router.register(r^'authors',AuthorsViewSet) then it binds a url authors/?P<pk> with it and with a get-detail method and I can't use the same link again for POST. Read more into the question and the given examples :) Anyway, I solved this by creating one view for both cases, only selecting
Actually, I did it with routers first, only when I saw that it can't be done, then I thought that I'll maybe manage to register them like that, but I couldn't either.
Ah, there is no easy way to do that. And I wouldn't as it breaks restful convention. Instead try:
Actually, let me create a new answer to address this clarification

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.