Robert Kajic
Robert Kajic

Reputation: 9077

Better way of defining filtered endpoints using Django REST framework

I've got a relationship between User's and Image's where every user can have multiple images. I need to define an endpoint that retrieves all images for a specific user:

GET /users/:id/images

I've done it like this:

urls.py

router = routers.DefaultRouter()
router.register(r'images', ImageViewSet)

image_list = ImageViewSet.as_view({
    'get': 'list'
})

urlpatterns = patterns('',
    ...
    url(r'^', include(router.urls)),
    url(r'^users/(?P<user_id>[^/]+)/images/$', image_list),
    ...
)

image/views.py

class ImageViewSet(viewsets.ModelViewSet):
    queryset = Image.objects.all()
    serializer_class = ImageSerializer

    def get_queryset(self):
        user_id = self.kwargs.get('user_id', None)
        if user_id:
            return Image.objects.filter(user_id=user_id)
        return super(ImageViewSet, self).get_queryset()

It works but I'm not happy with it. Imagine a few additional endpoints that are analogous to /users/:user_id/images/, i.e. something along the lines of /categories/:category_id/images/, etc. Having get_queryset as an entry-point for both of those, letting it distinguish between them based on kwargs, doesn't seem very appealing. Is there a better way to do it?

Upvotes: 2

Views: 830

Answers (2)

Kevin Stone
Kevin Stone

Reputation: 8981

You really should use super() in get_queryset() to get the unfiltered query set and just filter it down based on the kwargs.

if user_id:
    super(ImageViewSet, self).get_queryset().filter(user_id=user_id)

You'll notice that this becomes very generic and can be included into each view as a mixin:

class FilterByUserMixin(object):
    def get_queryset(self):
        user_id = self.kwargs.get('user_id', None)
        queryset = super(FilterByUserMixin, self).get_queryset()
        if user_id:
            queryset = queryset.filter(user_id=user_id)
        return querset


class ImageViewSet(FilterByUserMixin, viewsets.ModelViewSet):
    queryset = Image.objects.all()
    serializer_class = ImageSerializer


class CategoryViewSet(FilterbyUserMixin, viewsets.ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer

Upvotes: 2

Carlton Gibson
Carlton Gibson

Reputation: 7386

In the basic case you end up specifying a model and a serializer class. There's no reason why you couldn't put both of these in some kind of table and use kwargs to do a lookup.

LOOK_UP_TABLE = {
    'users' : {
        'model': ...,
        'serializer_class': ...,
    },
    'categories' : {
        ...
    }, 
}

If you did this, as well as overriding get_queryset, you could override get_serializer_class too. You'd then have something pretty generic.

I'm not how much typing you'd really be saving (especially if you use your editor's code snippets features). Or how flexible the resultant system would be. But it's an interesting idea.

Upvotes: 2

Related Questions