Skip to content

"Rails-like" relationship urls #738

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

dulacp
Copy link
Contributor

@dulacp dulacp commented Mar 18, 2013

The idea is to take the best from RoR ! ;)
I think their approach of relationship browsing is very intuitive and great to use.

With the following example taken from the doc

class Album(models.Model):
    album_name = models.CharField(max_length=100)
    artist = models.CharField(max_length=100)

class Track(models.Model):
    album = models.ForeignKey(Album, related_name='tracks')
    order = models.IntegerField()
    title = models.CharField(max_length=100)
    duration = models.IntegerField()

    class Meta:
        unique_together = ('album', 'order')
        order_by = 'order'

    def __unicode__(self):
        return '%d: %s' % (self.order, self.title)

we could access the tracks of an album with the url /api/album/1/tracks.

I'm working on an implementation, but all the ideas, thoughts and opinions are welcome.
First thing, do you think it's a good idea ?

@maspwr
Copy link
Contributor

maspwr commented Mar 18, 2013

We have gone for the nested resources option in our API. We created all of the URLs by hand. What exactly are you suggesting? An automated URL generator that can map back to specific views? That would be pretty sweet.

@kuhnza
Copy link
Contributor

kuhnza commented Mar 18, 2013

+1 for this, I think it's a great idea and is intuitive for most master/detail resources. This would be a big win if it happened somewhat automatically.

@dulacp
Copy link
Contributor Author

dulacp commented Mar 18, 2013

@maspwr Yeah, I'm suggesting a automated URL generator, something like a dispatch router.
@kuhnza Thanks for the support.

@thunky-monk
Copy link

This would be great! Currently, we are doing this in a bit of an unholy way for minor resources such as Likes and Subscribes on major resources.

@dulacp
Copy link
Contributor Author

dulacp commented Mar 18, 2013

I see that I'm not the only one wishing for this feature :)
I manage to make it work with a reasonable amount of code (<100 loc). But that is clearly not a complete solution, especially because I'm not an expert of django model introspection.. so I'm sure that I forgot some use cases.
Nevertheless, I will turn this issue into a pull request to share the code with everyone asap.

@tomchristie
Copy link
Member

wrt. automatic routing, that's the big upcoming plan for 2.3

There's a bit of draft documentation here (tho I don't think it does a great job of explaining things, and I've been refining my ideas a little since I first wrote it.)

In summary we'll have ViewSet which is essentially a View class that can be instantiated into multiple views.
You don't define handler methods such as get, post on them, but instead define actions list, create, and allow a Router class to instantiate a set of views and automatically handle the url routing.

Eg. The final tutorial set should end up looking something like this...

views.py

class UserViewSet(viewsets.ModelViewSet):
    """
    Replaces the `UserList` and `UserDetails` views.
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

class SnippetViewSet(viewsets.ModelViewSet):
    """
    Replaces the `SnippetList`, `SnippetDetails` and `SnippetHighlight` views.

    ModelViewSet provides `list`, `retrieve`, `destroy`, `update` and `create` methods by default.
    We also provide a custom `highlight` method on this `ViewSet`.
    """
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)

    @viewsets.link(renderer_classes=[renderers.StaticHTMLRenderer])
    def highlight(self, request, *args, **kwargs):
        snippet = self.get_object()
        return Response(snippet.highlighted)

    def pre_save(self, obj):
        obj.owner = self.request.user

urls.py

from snippets import resources
from rest_framework.routers import DefaultRouter

router = DefaultRouter(create_root_url=True, include_format_suffixes=True)
router.register(r'^snippets/', views.SnippetViewSet, 'snippet')
router.register(r'^users/', views.UserViewSet, 'user')
urlpatterns = router.urlpatterns

@dulacp
Copy link
Contributor Author

dulacp commented Mar 18, 2013

Great! A ViewSet, that's exactly what we need for this job. Because my code is clearly a little hack (allowing only readonly relationships).

If I understand correctly the tutorial 6, this was written for the django-rest-framework 1.x ? I'm asking because I'm kind of new :)

Do you want me to rename this pull request to something like Automatic Routing for 2.3 ?

@tomchristie
Copy link
Member

@dulaccc - There's two separate things here right, the automatic routing, and the "sub-relationships".

The "sub-relationships" we should keep against this ticket.

The ViewSet & Router stuff I've opened a preliminary pull request, just to get things rolling...

#741

@dulacp
Copy link
Contributor Author

dulacp commented Mar 18, 2013

Thanks for the precision @tomchristie. I was indeed mixing the two things.

Without any link, I'm not very confident with the choice I made on making RetrieveRelationshipAPIView a subclass of SingleObjectAPIView. Because sometimes the sub-relationship is a collection, sometimes it's a single object... That's not very consistent and clear.
In fact, the SingleObjectAPIView class just helps me to retrieve the root-element used later to access the sub-relationship.

I know your attention to detail, and I've got that too usually ^^, that's why I'm asking for guidance if you already thought this through.

@sheppard sheppard mentioned this pull request Apr 1, 2013
@tomchristie
Copy link
Member

Closing this issue now that 2.3 is almost pending release.
See the 2.3 branch, in particular the ViewSet and Router API guides, part 6 of the tutorial, and the 2.3 release notes.

@BertrandBordage
Copy link

Looks like you saw django-viewsets before doing this ;)

tomchristie added a commit that referenced this pull request May 13, 2013
@tomchristie
Copy link
Member

Hi @BertrandBordage. Considered adding to your thread on django-dev mentioning this, yeah. :)
As it happens, this has actually been planned long before I saw django-viewsets, (See also Dagny, Tastypie, Rails) but the choice of name is directly down to django-viewsets. (It seems a much more precise name than 'Resources' or 'Controllers', given the Django context) So, thanks! Added to the credits just now :). Be interesting to see how the concept pans out...

@BertrandBordage
Copy link

OK, great :)
I really like the router idea btw! Maybe I'll implement it in django-viewsets one day ;)

<off_topic>
Besides, django-rest-framework is totally awesome! Currently adding it to dezede and I'm all like "oh, it does this! And that! And also that! :D"
</off_topic>

@tomchristie
Copy link
Member

@BertrandBordage - The separation of concerns between router and viewset felt right, and I really like how viewset is now just a small incremental change on top of regular views. And glad you're enjoying the framework. :)

@dulacp
Copy link
Contributor Author

dulacp commented May 15, 2013

I finally had the time to work again with django-rest-framework, and I'm feeling like this viewset feature is awesome :) just like you — @tomchristie — have told me once before when I opened up the issue. Thanks for all your work, I will update my project to use it right away and give a feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants