Gjert
Gjert

Reputation: 1067

Extending custom router to default router across apps in Django Rest Framework

I have come across a problem regarding having the API apps seperate, while still being able to use the browsable API for navigation.

I have previously used a seperate routers.py file in my main application containing the following extension of the DefaultRouter.

class DefaultRouter(routers.DefaultRouter):
    def extend(self, router):
        self.registry.extend(router.registry)

Followed by adding the other application routers like this:

from . routers import DefaultRouter
from app1.urls import router as app1_router

# Default Router
mainAppRouter = DefaultRouter()
mainAppRouter.extend(app1_router)

where the app1_router is a new SimpleRouter object.

Now the problem occurs when I want to modify the SimpleRouter and create my own App1Router, such as this

class App1Router(SimpleRouter):

    routes = [
        Route(
            url = r'^{prefix}{trailing_slash}$',
            mapping = {
                'get': 'retrieve',
                'post': 'create',
                'patch': 'partial_update',
            },
            name = '{basename}-user',
            initkwargs = {}
        ),
    ]

This will not handle my extension correctly. As an example, GET and PATCH are not recognized as allowed methods whenever I extend the router, but when I dont extend, but only use the custom router, everything works fine.

My question is therefor, how can I handle extending custom routers across seperate applications, but still maintain a good browsable API?

Upvotes: 12

Views: 4497

Answers (3)

Maciej Słoniewski
Maciej Słoniewski

Reputation: 96

The router registry is just a standard python list, so you can call YourRouter.registry.extend() directly on registy withouth the need to subclass the DefaultRouter, check the source of DRF here . Since registry is a list, when you extend registry you add another python list on top of python list, which implies calling YourRouter.registry.extend(app_router.registry). What worked for me, was importing routers from another apps (SimpleRouters) and adding them on top of default router registry.

#aplication_root/urls.py
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from app_3.urls import router as app_3_router


router = DefaultRouter()
router.registry.extend(app_3_router.registry)

urlpatterns = [

    path('api/', include(router.urls)),

]

If your are trying to add versioning to your app by adding the prefix, I suggest to take a look at versioning schema available in DRF - maybe it could fit your needs DRF versioning

for example with URLPathVersioning enabled in your settings

REST_FRAMEWORK = {
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning'
}

urlparttern list from snipped above would look like that

urlpatterns = [

    path('api/<version>/', include(router.urls)),
]

Upvotes: 1

Xander YzWich
Xander YzWich

Reputation: 141

I don't see this anywhere in your code:
where the app1_router is a new SimpleRouter object

actually your import says otherwise
from app1.urls import router as app1_router

I would change the App1Router to extend either app1.urls.router or DefaultRouter.

Upvotes: 0

miigotu
miigotu

Reputation: 1685

Have you tried leaving the default routes intact for SimpleRouter?

class App1Router(SimpleRouter):
    def __init__(self):
        super(SimpleRouter, self).__init__()
        self.routes.insert(0, Route(
            url = r'^{prefix}{trailing_slash}$',
            mapping = {
                'get': 'retrieve',
                'post': 'create',
                'patch': 'partial_update',
            },
            name = '{basename}-user',
            initkwargs = {}
        ))

Upvotes: 1

Related Questions