Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django DRF, how to properly register custom URL patterns with DRF actions

Background

I have a ModelViewSet that defines several custom actions. I am using the default router in my urls.py to register the URLs. Right now, my views use the default created routes like ^images/$, ^images/{pk}/$.

In order for users to use the API using resource names with which they are familiar, I have adapted the viewset to accept multiple parameters from the URL, using the MultipleFieldLookupMixin strategy described in the docs to allow for the pattern images/{registry_host}/{repository_name}/{tag_name}.

I've created the get_object method in my viewset like so:

class ImageViewSet(viewsets.ModelViewSet):
    ...
    def get_object(self):
        special_lookup_kwargs = ['registry_host', 'repository_name', 'tag_name']
        if all(arg in self.kwargs for arg in special_lookup_kwargs):
            # detected the custom URL pattern; return the specified object
            return Image.objects.from_special_lookup(**self.kwargs)
        else: # must have received pk instead; delegate to superclass
            return super().get_object()

I've also added a new URL path pattern for this:

urls.py

router = routers.DefaultRouter()
router.register(r'images', views.ImageViewSet)
# register other viewsets
...

urlpatterns = [
    ...,
    path('images/<str:registry_host>/<path:repository_name>/<str:tag_name>', views.ImageViewSet.as_view({'get': 'retrieve',})),
    path('', include(router.urls)),
]

Problem

The above all works as intended, however, I also have some extra actions in this model viewset:

    @action(detail=True, methods=['GET'])
    def bases(self, request, pk=None):
        ...
    @action(detail=True, methods=['GET'])
    def another_action(...):
        ... # and so on

With the default patterns registered by DRF, I could go to images/{pk}/<action> (like images/{pk}/bases) to trigger the extra action methods. However I cannot do this for images/{registry_host}/{repository_name}/{tag_name}/<action>. This is somewhat expected because I never registered any such URL and there's no reasonable way DRF could know about this.

I'm guessing that I can add all these paths manually with an appropriate arguments to path(...) but I'm not sure what that would be.

urlpatterns = [
    ...,
    path('.../myaction', ???)
]

Questions

As a primary question, how do I add the actions for my URL?

However, I would like to avoid having to add new URLS every time a new @action() is added to the view. Ideally, I would like these to be registered automatically under the default path images/{pk}/<action> as well as images/{registry_host}/{repository_name}/{tag_name}/<action>.

As a secondary question, what is the appropriate way to achieve this automatically? My guess is perhaps a custom router. However, it's unclear to me how I would implement adding these additional routes for all extra (detail) actions.

Using django/drf 3.2

like image 696
sytech Avatar asked Nov 02 '25 21:11

sytech


1 Answers

Another approach I see is to (ab)use url_path with kwargs like:

class ImageViewSet(viewsets.ModelViewSet):

    @action(detail=False, methods=['GET'], url_path='<str:registry_host>/<path:repository_name>)/<str:tag_name>/bases')
    def bases_special_lookup(...):
        # detail=False to remove <pk>

    @action(detail=True, methods=['GET'])
    def bases(...):
        ...

    @action(detail=False, methods=['GET'], url_path='<str:registry_host>/<path:repository_name>)/<str:tag_name>')
    def retrieve_special_lookup(...):
        # detail=False to remove <pk>

    def retrieve(...):
        ...

This will create these urls:

images/<str:registry_host>/<path:repository_name>/<str:tag_name>/bases
images/<pk>/bases
images/<str:registry_host>/<path:repository_name>/<str:tag_name>
images/<pk>
like image 63
Brian Destura Avatar answered Nov 05 '25 16:11

Brian Destura