Eine Restful API für unsere App

Ein anderes Unternehmen will unsere Events importieren und auf seiner App darstellen. Die Daten sollen im JSON-Format von unserer Website zur Verfügung gestellt werden und stets den aktuellen Zustand abbilden.

Dazu benötigen wir eine API. Das Django Modul Django Restframework ist ein ausgereiftes Paket zur Erstellung von Rest-APIs in Django, die sich zudem komfortabel und einfach in bestehende Projekte integerieren lässt.

Dennoch ist sie mächtig genug, um alles, was an API-Technologie benötigt werden könnte, umzusetzen.

Was ist eine REST-API?

Ein Rest-API ist eine Web-API, die den Beschränkungen der REST-Architektur unterliegt und Interaktionen mit Webservices ermöglicht.

Die Architektur basiert auf 6 Prinzipien, die vom Entwickler flexibel umgesetzt werden können. Es gilt die

  • Client-Server-Architektur

  • die Zustandslosigkeit, es werden also keine Nachrichtenteile etwa in

Sessions gespeichert - es ist ein Caching implementiert - die Schnittstelle ist einheitlich - die Systeme sind mehrschichtig aufgebaut und Code kann on Demand an den Nutzer gesendet werden, um zum Beispiel via JavaScript weiterverarbeitet zu werden

Mehr zu REST auf Wikipedia: https://de.wikipedia.org/wiki/Representational_State_Transfer

Einrichten der API

Alles, was die API für die Event-App betrifft legen wir in ein eigenes Verzeichnis event_manager/events/api. Dort legen wir drei Dateien an: serializers.py, urls.py und views.py

event_manager/events
                ├───api
                │   serializers.py
                │   urls.py
                │   views.py

Serializer

In serializers.py werden wir die Serialisierer für die aus- und eingehgenden Daten definieren. Unter Serialisierung versteht man in diesem Kontext den Prozess, ausgehende Daten vorweg in ein Austauschformat wie Json oder XML und eingehende Daten aus einem Austauschformat in Python Datentypen zu formatieren.

URLS

Unsere Api benötigt natürlich auch wieder URLs, die von außen ansprechbar sind. http://127.0.0.1:8000/api/events wäre zum Beispiel eine solche URL.

Views

Bei den Views handelt es sich um Controller-Klassen, die die Daten bereitstellen, die serialisiert werden sollen.

Legen wir zuerst die Serializer an:

Serializer

Wir öffnen die Datei event_manager/events/api/serializers.py und fügen dort folgenden Code hinzu:

import arrow
from rest_framework import serializers
from events import models


class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Category
        exclude = ("slug",)


class ReviewInlineSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Review
        fields = ("author", "rating", "review")
    author = serializers.StringRelatedField(read_only=True)


class EventSerializer(serializers.ModelSerializer):
    days_to_event = serializers.SerializerMethodField()
    reviews = ReviewInlineSerializer(many=True, read_only=True)

    class Meta:
        model = models.Event
        fields = ("id",
                  "name",
                  "sub_title",
                  "category",
                  "days_to_event",
                  "date",
                  "min_group",
                  "reviews",
                  )

    def get_days_to_event(self, object):
        diff = object.date - arrow.now()
        return diff.days

    def validate_date(self, value):
        """
        Check if date is smaller than today. oho
        """
        if value < arrow.now():
            raise serializers.ValidationError("Event date is in the past")
        return value

    def validate(self, attrs):
        """call the clean method of Event-model"""
        self.Meta.model(**attrs).clean()
        return attrs

Views

Für die Views nutzen wir die Viewsets aus dem Django-Restframework. Dazu legen wir die Datei event_manager/events/api/views.py an:

from rest_framework import viewsets
from rest_framework import filters
from events import models
from . import serializers


class CategoryViewset(viewsets.ModelViewSet):
    queryset = models.Category.objects.all()
    filter_backends = (filters.SearchFilter, filters.OrderingFilter)
    serializer_class = serializers.CategorySerializer


class EventViewset(viewsets.ModelViewSet):
    """
    http://127.0.0.1:8000/api/events/?ordering=date
    http://127.0.0.1:8000/api/events/?ordering=-date

    """
    queryset = models.Event.objects.all()
    serializer_class = serializers.EventSerializer
    filter_backends = [filters.OrderingFilter]

    # es darf nach folgenden Werten geordnet werden:
    ordering_fields = ['date', 'name']

    def perform_create(self, serializer):
        author = self.request.user
        serializer.save(author=author)

Routes

Zuletzt müssen wir noch die URLs festlegen. Wir legen an: event_manager/events/api/urls.py

from rest_framework import routers
from . import views

router = routers.DefaultRouter()
router.register(r'category', views.CategoryViewset)
router.register(r'events', views.EventViewset)

und verdrahten sie mit dem Projekt. Wir öffnen event_manager/event_manager/urls.py und fügen ein:

from events.api.urls import router


urlpatterns = [
    # andere URLs
    path("api-auth/", include("rest_framework.urls")),
    path('api/', include(router.urls)),
]

Via Api-Auth können wir uns auch via API am System einloggen:

Diese Möglichkeit ist allerdings meistens gar nicht gewünscht, da eine Authentifizierung an einer API hauptsächlich über sogenannte Tokens gelöst wird, und nicht über gewöhnliche Sessions, wie man das von Webanwendungen oft gewohnt ist.

OpenAPI-Spezifikation

Die OpenAPI-Spezifikation (OAS) definiert eine standardisierte, sprachunabhängige Schnittstelle zu RESTful-APIs, die es sowohl Menschen als auch Computern ermöglicht, entfernte APIs bequem zu testen.

Im Idealfall, dh. wenn das OpenAPI-Schema der API richtig implementiert ist, kann ein User mit der API über diese Schnittstelle interagieren.

Eine OpenAPI-Definition kann zur Dokumentationserstellung und Code-Geniererung verwendet werden, um die API darzustellen.

Eine API-Spezifiktation wird in YAML oder JSON geschrieben.

Mehr zu OAS hier https://www.openapis.org/

Swagger

Swagger ist eine Reihe von Open-Source-Tools, die auf der OpenAPI-Spezifikation basieren und bei der Entwicklung, Erstellung, Dokumentation und Nutzung von REST-APIs unterstützen.

Um auf Basis der OpenAPI-Spezifikation eine interaktive Dokumentation zu erstellen, können wir Swagger nutzen. Um aus den Api-Endpunkten, die wir mit dem Django Restframework erstellt hatten, automatisch ein OpenAPI-Schema zu generieren, nutzen wir eine Django-App namens drf-spectacular.

Dieses Tool sucht nach Api-Endpunkten und erstellt daraus, u.a. auch on-the-fly ein Open-Api-Scheme, welches dann von drf-spectacular-sidecar genutzten werden kann, um eine Swagger-UI zu erstellen. Eine Swagger-UI ist nichts anderes, als eine schöne, interaktive Oberfläche zum Testen der API. Ein ähnliches Tool ist Redoc. Auch die Anwendung Postman kann OpenApi-Daten lesen und daraus eine Oberfläche zum Testen der API bereitstellen.

Es soll nicht unerwähnt bleiben, dass man das OpenAPI-Schema auch selber erstellen kann, da es sich hier um nichts weiter als eine YAML-Datei handelt.

Mehr zu Swagger and OpenApi * https://swagger.io/docs/specification/about/ * https://en.wikipedia.org/wiki/Swagger_(software)

Swagger installieren

Wir legen in der requirements.in die beiden folgenden Pakete fest:

# andere Apps
drf-spectacular
drf-spectacular-sidecar

und installieren sie:

(eventenv)  pip-compile requirements.in
(eventenv)  pip-sync requirements.txt requirements-dev.txt

Mehr zu drf-specatular bzw. Sidecar hier: * https://github.com/tfranzel/drf-spectacular-sidecar * https://github.com/tfranzel/drf-spectacular * https://swagger.io/tools/swagger-ui/

in den settings/base.py registrieren wir drf-spectacular als DEFAULT_SCHEMA_CLASS des Restframeworks:

REST_FRAMEWORK = {
    "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}

diese Klasse kümmert sich um das automatische Generieren der OpenApi-Spezifikation für unsere API-Anwendung.

sowie in den INSTALLED APPS …

INSTALLED_APPS = [
    [..]
    "django.contrib.admindocs",
    "crispy_forms",
    "crispy_bootstrap5",
    "rest_framework",
    "rest_framework.authtoken",
    "drf_spectacular",
    'drf_spectacular_sidecar',
    [..]
]

Ebenfalls in den settings/base.py legen wir die SPECTACULAR_SETTINGS an, die uns die Möglichkeit gibt, die API-Beschreibung zu anzupassen.

SPECTACULAR_SETTINGS = {
    'TITLE': 'Event Manager API',
    'DESCRIPTION': 'Django Event manager',
    'VERSION': '1.0.0',
    'SERVE_INCLUDE_SCHEMA': False,
    'SWAGGER_UI_DIST': 'SIDECAR',
    'SWAGGER_UI_FAVICON_HREF': 'SIDECAR',
    'SERVE_AUTHENTICATION': ['rest_framework.authentication.SessionAuthentication'],
    'SERVE_PERMISSIONS': ['rest_framework.permissions.IsAuthenticated'],

    # OTHER SETTINGS
}

Hier haben wir einen Title und eine Description angelegt sowie einige spezfische Api-Einstellungen gesetzt.

Wichtige SPECTACULAR_SETTINGS

TITLE

die Überschrift der Oberfläche

DESCRIPTION

eine Kurzbeschreibung der API. Hier ist auch HTML erlaubt.

VERSION

die aktuelle Version der API.

SWAGGER_UI_DIST

die graph. Oberfläche für die API

Zusätzlich geben wir noch per SERVE_AUTHENTICATION und SERVE_PERMISSIONS die Rechte an, die nötig sind, um die SWAGGER-UI aufzurufen.

Um die Api-Dokumentation via Url erreichbar zu machen, tragen wir den Path in die event_manager/event_manager/urls.py ein:

from drf_spectacular.views import SpectacularAPIView
from drf_spectacular.views import SpectacularSwaggerView


urlpatterns = [
    # andere urls
    path(
        "schema/",
        SpectacularAPIView.as_view(api_version="v2"),
        name="schema",
    ),
    path(
        "docs/",
        SpectacularSwaggerView.as_view(url_name="schema"),
        name="swagger-ui",
    ),
]

Beim Aufruf von http://127.0.0.1:8000/docs/ sollte die Api-Dokumentation zu sehen sein. Alle Api-Endpunkte können nun ausprobiert werden. Das Schema wird autogeneriert und über urlname="schema" erreichbar gemacht.

Man könnte dieses Schema auch über ein anderes Programm wie Redoc oder Postman integrieren. Mehr dazu unter: https://learning.postman.com/docs/integrations/available-integrations/working-with-openAPI/

So sollte das Ergebnis jetzt aussehen:

../_images/swagger_ui.png

manage.py Subkommando spectacular

Via manage.py steht uns mit spectacular ein neues Subkommando zur Verfügung, um mit der Anwedung zu interagieren.

Um uns zum Beispiel das aktuelle API-Schema im YAML-Format ausgeben zu lassen, nutzen wir diese Befehl:

python manage.py spectacular --format openapi

Im Hilfemodus der App können wir noch weitere Möglichkeiten finden. Probiert es aus!

python manage.py spectacular --help