12

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?

4
  • can you please add the corresponding ViewSet that you are routing? Commented Jan 8, 2018 at 11:28
  • @JohnMoutafis I've already modified it, so I dont have the exact version. However, there was nothing special about the viewset really. I would only use the generic viewset combined with custom create(), retrieve() and partial_update() functions. While also adding the mixins for the following. Commented Jan 8, 2018 at 14:02
  • 1
    "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." this is hard to understand and lacks some context. I particular, it's unclear what you are trying to do with your custom router. Commented Jan 11, 2018 at 0:13
  • @Linovia What is primarly want to do, is being able to PATCH and PUT without having to use a lookup field. In short: I want users to be able to get their account information (id obtained by authentication) through GET, and update using PUT and PATCH. For unauthenticated users, I want POST to be a create new user form. All this on a single api url /account/. However, in order for me to do this, I would need to use a custom Router to change the routes. But then I can't get it viewed in the browsable API. Commented Jan 13, 2018 at 19:35

3 Answers 3

2

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)),
]
Sign up to request clarification or add additional context in comments.

Comments

1

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 = {}
        ))

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.