-->

Registering API in apps

2019-03-12 23:54发布

问题:

With django-rest-framework I'm using the DefaultRouter

I want to provide APIs to several apps, so my question is can I do this in a django manner and put my router registrations in each app URLconf and have them appear either as one aggregate api or ideally in a namespaced way.

In other words if app1 contains modelA and modelB, while app2 contains modelC:

  1. can I declare 2 routers that appear at mysite/app1/api and mysite/app2/api, or
  2. can I have a single api at mysite/api which lists all three models yet register the individual models in their own app's urls.py

Something like

router = DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(include('app1.apis')
router.register(include('app2.apis')

Alternatively is there a simple way in which my router variable can be made available in each app's URLconf so that they can call router.register? I'm not sure if

urlpatterns = patterns('',
url(r'^snippets/', include('snippets.urls', namespace="snippets"))
...
url(r'^api/', include(router.urls)),

actually cause the code in app1/urls.py to be executed at that point so that it could call router.register somehow, so that the final url call includes all the app registrations as well as the project one.

UPDATE

Using a variation on Nicolas Cortot's option 2 I get my specific resource API to work, but it is not listed as an available resource in the root API at myserver\api\

I assume that somehow DefaultRouter creates it's own page definition and router.register adds entries to it. My current setup (and I think Nicholas's option 1 as well) create two separate routers, and only one can get displayed as the server root, with the setup below, myserver\api\ lists users but not snippets.

Here's my current setup:

project urls.py:

router = DefaultRouter()
router.register(r'users', views.UserViewSet)

urlpatterns = patterns('',
    url(r'^admin/', include(admin.site.urls)),
    url(r'^api/', include(router.urls)),
    url(r'^api/', include('snippets.apiurls')),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
)

project/snippets/apiurls.py:

router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)

urlpatterns = patterns('',
    url(r'^', include(router.urls)),
)

If I reverse the order of the entries in the project urls.py as:

    url(r'^api/', include('snippets.apiurls')),
    url(r'^api/', include(router.urls)),

then I get snippets listed but not users

I guess Django is serving the first matching route.

Unless someone can tell me otherwise I seem to need a single router variable to be passed around and added to somehow.

回答1:

To get all apps in the same API root, you need to register all your apps with the same DefaultRouter.

One way to achieve this is to make a custom router, which intercepts the register call and propagates it to a shared router. You then use this shared router to get the api urls.

class SharedAPIRootRouter(SimpleRouter):
    shared_router = DefaultRouter()

    def register(self, *args, **kwargs):
        self.shared_router.register(*args, **kwargs)
        super().register(*args, **kwargs)
        # if not py3: super(SharedAPIRootRouter, self).register(*args,**kwargs)

Then in each app:

# in app1/urls.py 
router = SharedAPIRootRouter()
router.register(r'app1', App1ModelViewSet)

# in app2/urls.py
router = SharedAPIRootRouter()
router.register(r'app2', App2ModelViewSet)

In your main urls.py, you must ensure you import the app urls so that registration occurs before we ask for shared_router.urls

import app1.urls
import app2.urls

def api_urls():
    return SharedAPIRootRouter.shared_router.urls

urlpatterns = patterns(
    '',
    url(r'^api/', include(api_urls())),   
)   

if you do not want to import the urls explicitly, you can do it by convention:

def api_urls():
    from importlib import import_module
    for app in settings.INSTALLED_APPS:
        try:
            import_module(app + '.urls')
        except (ImportError, AttributeError):
            pass
    return SharedAPIRootRouter.shared_router.urls


回答2:

Both options are possible. You can either expose the router or the urls in each app, and merge those into your global urls. I usually prefer using urls (option 2) because it gives more flexibility in each app: you can define extra non-api URLs as needed.

Option 1

In your global urls.py:

from app1.api.routers import router1
from app2.api.routers import router2

urlpatterns = patterns('',
    url(r'^snippets/', include('snippets.urls', namespace="snippets"))
    ...
    url(r'^app1/api/', include(router1.urls)),
    url(r'^app2/api/', include(router2.urls)),
)

You can as easily use the same endpoint for both routers (as long as you're careful not to use conflicting routes):

urlpatterns = patterns('',
    url(r'^snippets/', include('snippets.urls', namespace="snippets"))
    ...
    url(r'^api/', include(router1.urls)),
    url(r'^api/', include(router2.urls)),
)

Option 2

In appN/api/urls.py:

router = DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(include('app1.apis')

urlpatterns = patterns('',
    url(r'^', include(router.urls)),
    url(r'^misc/', some_other_view),
)

In your global urls.py:

urlpatterns = patterns('',
    url(r'^snippets/', include('snippets.urls', namespace="snippets"))
    ...
    url(r'^api/', include('app1.api.urls')),
    url(r'^api/', include('app2.api.urls')),
)

Note that the urls modules do not need to be the same as the urls for standard views.



回答3:

This is possible by passing around a single router instance as follows.

Create a file called router.py or similar in your main project folder:

from rest_framework import routers
common_router = routers.DefaultRouter()

In each app's urls.py put:

from main.router import common_router as router
router.register(r'myapp-model-name', MyAppViewSet) 

In your main urls.py put:

import my_app1.urls  # to register urls with router
import my_app2.urls  # to register urls with router
...
# finally import router that includes all routes
from main.router import common_router

urlpatterns = [
    ...
    url(r'^api/', include(common_router.urls)),
    ...
]