webentwicklung-frage-antwort-db.com.de

Django REST Framework zur Kombination von Routern aus verschiedenen Apps

Ich habe ein Projekt, das mehrere Apps umfasst:

./project/app1
./project/app2
./project/...

Jede App verfügt über einen Router für das Django REST - Framework, um die von dieser App bereitgestellten Teile der API zu integrieren:

from Django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from .views import ThingViewSet

router = DefaultRouter()
router.register(r'things', ThingViewSet, base_name='thing')

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

Da die Apps separat sind, enthält meine URL-Datei der obersten Ebene (./project/urls.py) jede der URL-Dateien aus den separaten Apps:

url(r'^api/app1/', include('app1.urls', namespace='a1')),
url(r'^api/app2/', include('app2.urls', namespace='a2')),

Dies bedeutet, dass das Django REST - Framework für jede App ein separates API-Stammverzeichnis anzeigt. Was ich jedoch möchte, ist eine einheitliche API-Struktur. Wenn ich zu http://example.com/api/ navigiere, sehe ich die vollständige Liste aller URLs, die auf dieser Hierarchieebene verfügbar sind.

Ich gehe davon aus, dass es eine Möglichkeit gibt, alle meine separaten Router, die in den einzelnen urls.py-Dateien für jede App definiert sind, in einen einzelnen Router aufzunehmen. Ich kann jedoch keine Dokumentation dazu finden. Fehlt mir etwas offensichtliches?

43
seawolf

Eine andere Lösung ist die Verwendung von SimpleRouter zum Definieren von Routern für einzelne Apps. Verwenden Sie dann eine angepasste DefaultRouter, um app-spezifische Routen einzuschließen. Auf diese Weise bleiben alle App-spezifischen URL-Definitionen in der entsprechenden App.

Angenommen, Sie haben zwei Apps mit den Namen "app1" und "app2". Jede dieser Apps verfügt über ein Verzeichnis mit dem Namen "api". In diesem Verzeichnis befindet sich eine Datei mit dem Namen "urls", die alle Ihre Routendefinitionen enthält.

├── project/ │ ├── api_urls.py │ ├── app1 │ │ ├── api │ │ │ ├── urls.py │ ├── app2 │ │ ├── api │ │ │ ├── urls.py │ ├── patches │ │ ├── routers.py

verwenden Sie patches/router.py, um eine Klasse mit dem Namen DefaultRouter zu definieren, die von rest_framework.routers.DefaultRouter erbt.

from rest_framework import routers

class DefaultRouter(routers.DefaultRouter):
    """
    Extends `DefaultRouter` class to add a method for extending url routes from another router.
    """
    def extend(self, router):
        """
        Extend the routes with url routes of the passed in router.

        Args:
             router: SimpleRouter instance containing route definitions.
        """
        self.registry.extend(router.registry)

Füllen Sie Ihre API-URLs mit Routendefinitionen wie

"""
URL definitions for the api.
"""
from patches import routers

from app1.api.urls import router as app1_router
from app2.api.urls import router as app2_router

router = routers.DefaultRouter()
router.extend(app1_router)
router.extend(app2_router)
29
Saleem Latif

Dadurch werden alle ViewSet-Routen in der Basis-API-URL aufgelistet.

Es definiert die Routen als Liste in den jeweils enthaltenen app.urls, damit sie an anderer Stelle registriert werden können.

Nach dem Einfügen in die Basis-URLs.py wird die verschachtelte Listenliste erstellt und durchlaufen, um alle Routen auf derselben Ebene in der API zu registrieren

# foo.urls
routeList = (
    (r'foos', FooViewSet),
)

# barBaz.urls
routeList = (
    (r'bars', BarViewSet),
    (r'bazs', BazViewSet),
)

# urls
from rest_framework import routers
from foo import urls as fooUrls
from barBaz import urls as barBazUrls

routeLists = [
    fooUrls.routeList,
    barBazUrls.routeList,
]

router = routers.DefaultRouter()
for routeList in routeLists:
    for route in routeList:
        router.register(route[0], route[1])

Ergebnisse:

{
    "foo": "http://localhost:8000/foos/",
    "bar": "http://localhost:8000/bars/",
    "baz": "http://localhost:8000/bazs/",
}

Dies hat auch weniger Wiederholungen in jeder Datei und erleichtert das Lesen.

Es bleibt auch völlig entkoppelt. 

Wenn die enthaltene App an anderer Stelle verwendet wird, kann dieselbe Methode intern verwendet werden, um ihre eigenen Routen zu registrieren, ohne irgendwo eingeschlossen zu werden.

Lass einfach die äußere Schleife fallen

routeList = (
    (r'bars', BarViewSet),
    (r'bazs', BazViewSet),
)

router = routers.DefaultRouter()
for route in routeList:
    router.register(route[0], route[1])
12
shanemgrey

Am Ende habe ich eine einzige URL-Datei erstellt, die alle Routen enthält, die ich unter urls_api_v1.py möchte:

router = DefaultRouter()
router.register(r'app1/foos', FooViewSet, base_name='foo')
router.register(r'app2/bars', BarViewSet, base_name='bar')
router.register(r'app2/bazs', BazViewSet, base_name='baz')

Als Nebeneffekt konnte ich damit alle einzelnen urls.py-Dateien in jeder App loswerden, was Sie normalerweise möchten, aber in diesem Fall benötigt die gesamte App-Sammlung eine einheitliche URL-Struktur. Daher ist das Entfernen sinnvoller.

Ich verweise es dann von urls.py:

import api_v1
urlpatterns = patterns('',
    ...,
    url(r'^api/v1/', include(api_v1, namespace='api-v1')),
)

Wenn ich jetzt Routen für Version 2 ändern möchte, kann ich auch eine URL-Datei für Version 2 einschließen und die Datei für Version 1 eventuell verwerfen.

6
seawolf

Wenn Sie einen SimpleRouter implementieren, verketten Sie nur dessen URLs mit der Liste der URL-Muster

router = SimpleRouter()
router.register(r'import-project', ImportProject, base_name='di-integ')

In der Hauptdatei urls.py

from di_apiary_integration.urls import router as di_integration_routers

Um die URLs zu registrieren, können Sie Folgendes tun:

url(r'^di-integration/', include(di_integration_routers.urls)),

oder 

urlpatterns += di_integ_router.urls

Beides wird funktionieren!

Wichtig

ImportProject muss entweder ein ModelViewSet oder ein ViewSet sein. Wenn Sie dies als einfachen APIView erstellen, müssen Sie dies als normale CBV-Ansicht mit as_view () registrieren.

0
Gregory