Magnus
Magnus

Reputation: 3712

Django Rest Framework: how to route a uuid to an associated object?

I am building a simple photos-albums app with Django Rest Framework (DRF). Each album has a UUID field in its model. I want to create 'share links' so that someone with a link of the form /albums/[uuid] will be able to access that album.

I am using ModelViewSet for my views, so I assume that the most succinct way to achieve this desired routing is via the action decorator, with urls like /albums/shared/[uuid], but it's not clear to me how to obtain the uuid in the action-decorated shared method. I can't even parse the URL because DRF will 404 before firing this shared method:

### views/album.py

class AlbumViewSet(viewsets.ModelViewSet):
    
    queryset = Album.objects.all()
    serializer_class = AlbumSerializer

    @action(detail=False, methods=['get'])
    def shared(self, request):

        # How do you get the uuid from the supplied URL?
        uuid = ???
        obj = self.get_queryset().objects.get(uuid=uuid)
        return Response([obj])

Hopefully I won't have to add any fancy patterns to urls.py, but if I do then here is what I have so far:

### myapp/urls.py

router = routers.DefaultRouter()
router.register(r'albums', AlbumViewSet)
...

urlpatterns = [
    # ...
    path('', include(router.urls)),
]

format_suffix_patterns(urlpatterns)

Thanks!

Upvotes: 2

Views: 2787

Answers (3)

Tobias Ernst
Tobias Ernst

Reputation: 4634

Actions are great, but if you need to support multiple crud operations or ViewSet features like pagination or filtering, it's easier to map another ViewSet via router.register(...)

router.register(r'album', AlbumViewSet)
router.register(r'album/(?P<album_uuid>[\w-]+)/photo', PhotoViewSet)

class PhotoViewSet(ModelViewSet):
    # Filter photos by album uid
    def get_queryset(self):
        qs = super().get_queryset()
        return qs.filter(album_id=self.kwargs["album_uuid"])

Upvotes: 0

Magnus
Magnus

Reputation: 3712

The answer given by @Pradip is perfect but, for my own reference, here is a different solution I got working (except query part not tested!):

### myapp/views/album.py

class AlbumViewSet(viewsets.ModelViewSet):
    ...

    @action(detail=False, methods=['get'],)
    def shared(self, request,   *args, **kwargs):
        uuid = kwargs.get('uuid', None)
        if uuid:
            try:
                obj = self.get_queryset().get(name=uuid)
                return Response([obj])
            except Album.DoesNotExist:
                obj = None
                raise exceptions.NotFound(f'Album with UUID {uuid} not found')
        else:
            raise exceptions.ParseError('UUID not parseable')


### myproject/urls.py
urlpatterns = [
    ...
    path('albums/<str:uuid>/shared/', AlbumViewSet.as_view({'get': 'shared'})),
]

Upvotes: 0

Pradip Kachhadiya
Pradip Kachhadiya

Reputation: 2235

Here you need to some changes in url-pattern and your action-decorater:

First Change :

/albums/shared/[uuid] to /albums/[uuid]/shared/ According to Docs.

Second Change :

class AlbumViewSet(viewsets.ModelViewSet):
    
    queryset = Album.objects.all()
    serializer_class = AlbumSerializer

                   ⬇⬇⬇⬇
    @action(detail=True, methods=['get'])
    def shared(self,request,pk=None): ⬅⬅⬅⬅
        uuid = pk
        obj = self.get_queryset().objects.get(uuid=uuid)
        return Response([obj])

Pass UUID in url and fetch it in view... For ex. /albums/45/shared/

Upvotes: 3

Related Questions