Specify lookup_url_kwarg in the nested route using drf-nested-routers

98 views Asked by At

Using drf-nested-routers we have implemented many nested routes that go two levels in depth.

Just as in the documented examples:

/clients/
/clients/{pk}/
/clients/{client_pk}/maildrops/
/clients/{client_pk}/maildrops/{pk}/
/clients/{client_pk}/maildrops/{maildrop_pk}/recipients/
/clients/{client_pk}/maildrops/{maildrop_pk}/recipients/{pk}/

However, I don't like the inconsistency in the 4th line. How can I make the lookup_url_kwarg be maildrop_pk there too?

/clients/{client_pk}/maildrops/{maildrop_pk}/

This is how the urls.py looks like:

# urls.py
router = DefaultRouter()
router.register(r'clients', ClientViewSet, basename='clients')
## generates:
# /clients/
# /clients/{pk}/

client_router = routers.NestedSimpleRouter(router, r'clients', lookup='client')
client_router.register(r'maildrops', MailDropViewSet, basename='maildrops')
## generates:
# /clients/{client_pk}/maildrops/
# /clients/{client_pk}/maildrops/{pk}/

maildrops_router = routers.NestedSimpleRouter(client_router, r'maildrops', lookup='maildrop')
maildrops_router.register(r'recipients', MailRecipientViewSet, basename='recipients')
## generates:
# /clients/{client_pk}/maildrops/{maildrop_pk}/recipients/
# /clients/{client_pk}/maildrops/{maildrop_pk}/recipients/{pk}/

urlpatterns = [
    path(r'', include(router.urls)),
    path(r'', include(client_router.urls)),
    path(r'', include(maildrops_router.urls)),
]

If I set the lookup_url_kwarg in the MailDropViewSet to maildrop_pk:

class MailDropViewSet(viewsets.ViewSet):
    serializer_class = MailDropSerializer
    lookup_url_kwarg = "maildrop_pk"

then the routes look like this:

/clients/{client_pk}/maildrops/{maildrop_pk}/
/clients/{client_pk}/maildrops/{maildrop_maildrop_pk}/recipients/
/clients/{client_pk}/maildrops/{maildrop_maildrop_pk}/recipients/{pk}/

I want to have a consistent naming of the URL lookup kwargs across the routes, like:

/clients/{client_pk}/maildrops/{maildrop_pk}/
/clients/{client_pk}/maildrops/{maildrop_pk}/recipients/
/clients/{client_pk}/maildrops/{maildrop_pk}/recipients/{pk}/

but I can't see how this can be configured.

1

There are 1 answers

3
willeM_ Van Onsem On

Although not ideal, an idea might be to make our own NestedSimpleRouter, with:

from rest_framework_nested.routers import NestedMixin


class OurNestedMixin(NestedMixin):
    def __init__(self, parent_router, parent_prefix, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.nest_prefix = kwargs.get('lookup', 'nested_%i' % self.nest_count)

and mix this into a NestedSimpleRouter:

from rest_framework_nested.routers import NestedMixin


class OurNestedSimpleRouter(OurNestedMixin, NestedSimpleRouter):
    pass

then we thus can use this as:

# urls.py
router = DefaultRouter()
router.register(r'clients', ClientViewSet, basename='clients')

client_router = OurNestedSimpleRouter(router, r'clients', lookup='')
client_router.register(r'maildrops', MailDropViewSet, basename='maildrops')

maildrops_router = OurNestedSimpleRouter(client_router, r'maildrops', lookup='')
maildrops_router.register(
    r'recipients', MailRecipientViewSet, basename='recipients'
)

so then the lookup_url_kwargs of the ViewSets determine the names of the parameters, and this will then define the URL patterns.

The consequence is of course that the lookup_url_kwarg should be defined on all ViewSets in such way that for all paths they generate, there are never two path parameters with the same name.