📚 DRF OpenAPI & Swagger

Automatische API-Dokumentation mit DRF-Spectacular

Django REST Framework - Modul 6

Von OpenAPI Schema bis interaktive Swagger UI

🔍 Was ist OpenAPI & Swagger?

OpenAPI Specification (OAS)

Standard zur Beschreibung von REST APIs (früher: Swagger Specification)

📜 OpenAPI Specification

  • Was: Standard-Format (JSON/YAML)
  • Zweck: API-Struktur beschreiben
  • Version: 3.0+ (aktuell 3.1)
  • Format: schema.json / schema.yaml

Beispiel:

{
  "openapi": "3.0.0",
  "info": {
    "title": "Movie API",
    "version": "1.0.0"
  },
  "paths": {
    "/api/movies/": {
      "get": { ... },
      "post": { ... }
    }
  }
}

🎨 Swagger UI

  • Was: Interaktive Web-UI
  • Zweck: API visualisieren & testen
  • Features: Try-it-out, Authentication
  • Basis: OpenAPI Schema

Demo:

http://localhost:8000/api/schema/swagger-ui/

✅ API Endpoints anzeigen
✅ Request/Response sehen
✅ Direkt testen (Try it out)
✅ Authentifizierung testen

📖 ReDoc

  • Was: Alternative Dokumentation
  • Zweck: Lesbare Doku (read-only)
  • Features: Schönes Design, Mobile-friendly
  • Basis: OpenAPI Schema

Demo:

http://localhost:8000/api/schema/redoc/

✅ Übersichtliche Doku
✅ Suchfunktion
✅ Code-Beispiele
✅ Print-freundlich

🔄 Zusammenhang:

DRF API Code
    ↓
OpenAPI Schema Generator (drf-spectacular)
    ↓
schema.json / schema.yaml
    ↓
Swagger UI / ReDoc (Visualisierung)

💡 Warum API-Dokumentation?

👨‍💻 Für Entwickler

  • ✅ API schnell verstehen
  • ✅ Endpoints finden
  • ✅ Request/Response Formate sehen
  • ✅ Fehler vermeiden

🧪 Für Testing

  • ✅ API direkt testen (Try it out)
  • ✅ Verschiedene Requests ausprobieren
  • ✅ Authentication testen
  • ✅ Keine Tools wie Postman nötig

🤝 Für Teams

  • ✅ Einheitliche Doku
  • ✅ Immer aktuell (automatisch)
  • ✅ Frontend-Backend Kommunikation
  • ✅ Onboarding neuer Entwickler

🌐 Für Integration

  • ✅ Client-Code generieren
  • ✅ Third-Party Integration
  • ✅ Standard-Format (OpenAPI)
  • ✅ Tool-Unterstützung

❌ Ohne Doku:

# Entwickler fragt:
"Welche Endpoints gibt es?"
"Welche Parameter braucht POST /movies/?"
"Was ist das Response-Format?"
"Wie authentifiziere ich mich?"

→ Viel Zeit verschwendet!
→ Fehler durch Missverständnisse
→ Immer Backend-Devs fragen

✅ Mit Swagger UI:

# Entwickler öffnet:
http://localhost:8000/api/schema/swagger-ui/

→ Sieht alle Endpoints
→ Sieht Request/Response Schemas
→ Testet direkt in Browser
→ Authentication mit Token

→ Selbstständig arbeiten! 🚀

🌟 DRF-Spectacular - Warum?

Die beste OpenAPI-Lösung für DRF

❌ DRF Built-in Schema

# DRF hat CoreAPI (deprecated)
# → Veraltet
# → OpenAPI 2.0 (alt)
# → Limitierte Features
# → Nicht mehr maintained

# ⚠️ NICHT verwenden!

✅ drf-spectacular

# Modern, aktiv maintained
# → OpenAPI 3.0/3.1
# → Swagger UI + ReDoc
# → Excellent DRF Integration
# → Customizable
# → Auto-generation
# → Type Hints Support

# ⭐ Empfohlen!

✅ Features:

  • 🎯 OpenAPI 3.0+ Schema Generation
  • 🎨 Swagger UI (interaktiv)
  • 📖 ReDoc (schöne Doku)
  • 🔐 Authentication Support (Token, JWT, etc.)
  • 📝 Auto-Documentation aus Code
  • 🎛️ Customizable (extend_schema)
  • 📊 Type Hints Support
  • 🔍 Validation Schema

🔧 Was macht drf-spectacular?

1. Analysiert DRF Code:
   - ViewSets
   - Serializers
   - Permissions
   - Authentication

2. Generiert OpenAPI Schema:
   - Endpoints
   - Request/Response Formats
   - Authentication Methods
   - Validation Rules

3. Stellt UI bereit:
   - Swagger UI (interaktiv)
   - ReDoc (lesbar)

📦 Installation & Setup (Teil 1)

Schritt 1: drf-spectacular installieren

# Terminal:
pip install drf-spectacular

# Version prüfen:
pip show drf-spectacular

# Output:
Name: drf-spectacular
Version: 0.27.0
Summary: Sane and flexible OpenAPI 3 schema generation for Django REST framework

Schritt 2: App registrieren (settings.py)

# filepath: movieapi/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    # DRF
    'rest_framework',
    'rest_framework.authtoken',
    
    # DRF Spectacular (OpenAPI)
    'drf_spectacular',  # ← Hinzufügen!
    
    # Unsere App
    'movies',
]

Schritt 3: DRF Settings konfigurieren

# filepath: movieapi/settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticatedOrReadOnly',
    ],
    
    # Schema Generation mit drf-spectacular
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',  # ← Wichtig!
}

⚙️ Spectacular Settings (Schritt 4)

Schritt 4: SPECTACULAR_SETTINGS konfigurieren

# filepath: movieapi/settings.py
SPECTACULAR_SETTINGS = {
    # Basis-Info
    'TITLE': 'Movie API',
    'DESCRIPTION': 'API für Film-Verwaltung mit Künstlern und Besetzungen',
    'VERSION': '1.0.0',
    'CONTACT': {
        'name': 'API Support',
        'email': 'support@movieapi.com',
        'url': 'https://movieapi.com/support',
    },
    'LICENSE': {
        'name': 'MIT License',
        'url': 'https://opensource.org/licenses/MIT',
    },
    
    # Swagger UI Settings
    'SERVE_INCLUDE_SCHEMA': False,  # Schema-Endpoint nicht in UI anzeigen
    'SWAGGER_UI_SETTINGS': {
        'deepLinking': True,            # Deep Linking aktivieren
        'persistAuthorization': True,   # Auth persistent (Token bleibt)
        'displayOperationId': False,    # Operation IDs ausblenden
        'filter': True,                 # Filter/Suche aktivieren
    },
    
    # Schema-Generierung
    'COMPONENT_SPLIT_REQUEST': True,    # Request/Response getrennt
    'SCHEMA_PATH_PREFIX': r'/api/',     # URL-Prefix für API
    
    # Security Schemes (Authentication)
    'SECURITY': [
        {
            'tokenAuth': [],  # Token Authentication
        },
    ],
    
    # Enum Settings
    'ENUM_NAME_OVERRIDES': {
        'ValidationErrorEnum': 'drf_spectacular.serializers.ValidationErrorEnum.choices',
    },
    
    # Preprocessing (Custom Hooks)
    'PREPROCESSING_HOOKS': [
        'drf_spectacular.hooks.preprocess_exclude_path_format',
    ],
    
    # Postprocessing
    'POSTPROCESSING_HOOKS': [
        'drf_spectacular.hooks.postprocess_schema_enums',
    ],
    
    # Server-URLs (für verschiedene Environments)
    'SERVERS': [
        {
            'url': 'http://localhost:8000',
            'description': 'Development Server',
        },
        {
            'url': 'https://api.movieapi.com',
            'description': 'Production Server',
        },
    ],
}

🔗 URLs konfigurieren (Schritt 5)

Schritt 5: Schema URLs hinzufügen

# filepath: movieapi/urls.py
from django.contrib import admin
from django.urls import path, include
from drf_spectacular.views import (
    SpectacularAPIView,        # OpenAPI Schema (JSON/YAML)
    SpectacularSwaggerView,    # Swagger UI
    SpectacularRedocView,      # ReDoc
)

urlpatterns = [
    # Admin
    path('admin/', admin.site.urls),
    
    # API
    path('api/', include('movies.urls')),
    
    # DRF Auth (Browsable API Login)
    path('api-auth/', include('rest_framework.urls')),
    
    # OpenAPI Schema Endpoints
    path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
    # → Download: schema.json oder schema.yaml
    # → http://localhost:8000/api/schema/
    # → http://localhost:8000/api/schema/?format=yaml
    
    path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
    # → Swagger UI (interaktiv)
    # → http://localhost:8000/api/schema/swagger-ui/
    
    path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
    # → ReDoc (schöne Doku)
    # → http://localhost:8000/api/schema/redoc/
]

✅ URLs erklärt:

  • /api/schema/ → OpenAPI Schema (JSON/YAML Download)
  • /api/schema/swagger-ui/ → Interaktive Swagger UI (API testen)
  • /api/schema/redoc/ → Schöne ReDoc Dokumentation (read-only)

🧪 Test & erste Ansicht (Schritt 6)

Schritt 6: Server starten & testen

# Terminal:
python manage.py runserver

# Browser öffnen:
# 1. Swagger UI:
http://localhost:8000/api/schema/swagger-ui/

# 2. ReDoc:
http://localhost:8000/api/schema/redoc/

# 3. Schema (JSON):
http://localhost:8000/api/schema/

# 4. Schema (YAML):
http://localhost:8000/api/schema/?format=yaml

📸 Was siehst du?

Swagger UI:

Movie API 1.0.0

API für Film-Verwaltung mit Künstlern und Besetzungen

Servers:
▼ http://localhost:8000  Development Server

Default
  GET    /api/movies/          List movies
  POST   /api/movies/          Create movie
  GET    /api/movies/{id}/     Retrieve movie
  PUT    /api/movies/{id}/     Update movie
  PATCH  /api/movies/{id}/     Partial update movie
  DELETE /api/movies/{id}/     Delete movie
  
  GET    /api/artists/         List artists
  POST   /api/artists/         Create artist
  ...

Schemas
  Movie
  Artist
  MovieCasting

📝 Serializers dokumentieren (Schritt 7)

Schritt 7: Serializers mit Docstrings & help_text

# filepath: movies/serializers.py
from rest_framework import serializers
from .models import Movie, Artist, MovieCasting


class MovieSerializer(serializers.ModelSerializer):
    """
    Serializer für Movie CRUD Operationen.
    
    Verwaltet Film-Informationen inkl. Titel, Jahr, Genre, Bewertung und Beschreibung.
    """
    
    class Meta:
        model = Movie
        fields = '__all__'
        read_only_fields = ['id', 'created_at', 'updated_at']
        
    # Field-Level Documentation
    title = serializers.CharField(
        max_length=200,
        help_text="Titel des Films (max. 200 Zeichen)"
    )
    year = serializers.IntegerField(
        help_text="Erscheinungsjahr des Films (z.B. 2010)"
    )
    genre = serializers.CharField(
        max_length=100,
        required=False,
        allow_blank=True,
        help_text="Genre des Films (z.B. 'Sci-Fi', 'Drama')"
    )
    rating = serializers.DecimalField(
        max_digits=3,
        decimal_places=1,
        required=False,
        allow_null=True,
        help_text="Bewertung des Films von 0.0 bis 10.0"
    )
    description = serializers.CharField(
        required=False,
        allow_blank=True,
        help_text="Beschreibung/Zusammenfassung des Films"
    )


class ArtistSerializer(serializers.ModelSerializer):
    """
    Serializer für Artist CRUD Operationen.
    
    Verwaltet Künstler/Schauspieler-Informationen.
    """
    full_name = serializers.CharField(source='full_name', read_only=True, help_text="Vollständiger Name (Vorname + Nachname)")
    
    class Meta:
        model = Artist
        fields = '__all__'
        read_only_fields = ['id', 'created_at', 'updated_at', 'full_name']


class MovieCastingSerializer(serializers.ModelSerializer):
    """
    Serializer für MovieCasting (Besetzung).
    
    Verknüpft Künstler mit Filmen und deren Rollen.
    """
    artist_name = serializers.CharField(source='artist.full_name', read_only=True, help_text="Name des Künstlers")
    movie_title = serializers.CharField(source='movie.title', read_only=True, help_text="Titel des Films")
    
    class Meta:
        model = MovieCasting
        fields = '__all__'
        read_only_fields = ['id', 'created_at', 'artist_name', 'movie_title']
        
    role_name = serializers.CharField(
        max_length=200,
        help_text="Name der Rolle im Film (z.B. 'Neo', 'Trinity')"
    )
    is_main_role = serializers.BooleanField(
        default=False,
        help_text="Ist dies eine Hauptrolle? (True/False)"
    )
    order = serializers.IntegerField(
        default=0,
        help_text="Reihenfolge der Besetzung (0 = erste Rolle)"
    )

📋 ViewSets dokumentieren (Schritt 8)

Schritt 8: ViewSets mit extend_schema customizen

# filepath: movies/views.py
import logging
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticatedOrReadOnly, IsAuthenticated
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiExample
from drf_spectacular.types import OpenApiTypes

from .models import Movie, Artist, MovieCasting
from .serializers import MovieSerializer, ArtistSerializer, MovieCastingSerializer

logger = logging.getLogger('movies')


@extend_schema_view(
    list=extend_schema(
        summary="Liste aller Filme",
        description="Gibt eine paginierte Liste aller Filme zurück.",
        tags=['Movies'],
    ),
    retrieve=extend_schema(
        summary="Film-Details",
        description="Gibt Details eines einzelnen Films zurück.",
        tags=['Movies'],
    ),
    create=extend_schema(
        summary="Film erstellen",
        description="Erstellt einen neuen Film. Authentifizierung erforderlich.",
        tags=['Movies'],
        examples=[
            OpenApiExample(
                'Inception Example',
                value={
                    'title': 'Inception',
                    'year': 2010,
                    'genre': 'Sci-Fi',
                    'rating': 8.8,
                    'description': 'A thief who steals corporate secrets through dream-sharing technology.',
                },
                request_only=True,
            ),
        ],
    ),
    update=extend_schema(
        summary="Film aktualisieren",
        description="Aktualisiert alle Felder eines Films. Authentifizierung erforderlich.",
        tags=['Movies'],
    ),
    partial_update=extend_schema(
        summary="Film teilweise aktualisieren",
        description="Aktualisiert einzelne Felder eines Films. Authentifizierung erforderlich.",
        tags=['Movies'],
    ),
    destroy=extend_schema(
        summary="Film löschen",
        description="Löscht einen Film. Authentifizierung erforderlich.",
        tags=['Movies'],
    ),
)
class MovieViewSet(viewsets.ModelViewSet):
    """
    ViewSet für Movie CRUD Operationen.
    
    Bietet vollständige CRUD-Funktionalität für Filme:
    - List: Alle Filme auflisten
    - Retrieve: Einzelnen Film abrufen
    - Create: Neuen Film erstellen
    - Update: Film aktualisieren
    - Delete: Film löschen
    """
    queryset = Movie.objects.all()
    serializer_class = MovieSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]
    
    @extend_schema(
        summary="Top-bewertete Filme",
        description="Gibt die Top 10 Filme mit Bewertung >= 8.0 zurück.",
        tags=['Movies'],
        parameters=[
            OpenApiParameter(
                name='min_rating',
                type=OpenApiTypes.FLOAT,
                location=OpenApiParameter.QUERY,
                description='Minimale Bewertung (Standard: 8.0)',
                required=False,
            ),
            OpenApiParameter(
                name='limit',
                type=OpenApiTypes.INT,
                location=OpenApiParameter.QUERY,
                description='Anzahl der Ergebnisse (Standard: 10)',
                required=False,
            ),
        ],
        responses={200: MovieSerializer(many=True)},
    )
    @action(detail=False, methods=['get'])
    def top_rated(self, request):
        """Top-rated Movies - Custom Action"""
        min_rating = float(request.query_params.get('min_rating', 8.0))
        limit = int(request.query_params.get('limit', 10))
        
        logger.info("Top-rated movies requested: min_rating=%s, limit=%s", min_rating, limit)
        
        movies = Movie.objects.filter(rating__gte=min_rating).order_by('-rating')[:limit]
        serializer = self.get_serializer(movies, many=True)
        
        return Response(serializer.data)
    
    @extend_schema(
        summary="Filme nach Genre",
        description="Filtert Filme nach Genre.",
        tags=['Movies'],
        parameters=[
            OpenApiParameter(
                name='genre',
                type=OpenApiTypes.STR,
                location=OpenApiParameter.QUERY,
                description='Genre-Name (z.B. "Sci-Fi", "Drama")',
                required=True,
            ),
        ],
        responses={200: MovieSerializer(many=True)},
    )
    @action(detail=False, methods=['get'])
    def by_genre(self, request):
        """Filme nach Genre filtern"""
        genre = request.query_params.get('genre')
        
        if not genre:
            return Response(
                {'error': 'Genre parameter is required'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        movies = Movie.objects.filter(genre__iexact=genre)
        serializer = self.get_serializer(movies, many=True)
        
        return Response(serializer.data)


@extend_schema_view(
    list=extend_schema(summary="Liste aller Künstler", tags=['Artists']),
    retrieve=extend_schema(summary="Künstler-Details", tags=['Artists']),
    create=extend_schema(summary="Künstler erstellen", tags=['Artists']),
    update=extend_schema(summary="Künstler aktualisieren", tags=['Artists']),
    partial_update=extend_schema(summary="Künstler teilweise aktualisieren", tags=['Artists']),
    destroy=extend_schema(summary="Künstler löschen", tags=['Artists']),
)
class ArtistViewSet(viewsets.ModelViewSet):
    """ViewSet für Artist CRUD Operationen"""
    queryset = Artist.objects.all()
    serializer_class = ArtistSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]


@extend_schema_view(
    list=extend_schema(summary="Liste aller Besetzungen", tags=['Castings']),
    retrieve=extend_schema(summary="Besetzungs-Details", tags=['Castings']),
    create=extend_schema(summary="Besetzung erstellen", tags=['Castings']),
    update=extend_schema(summary="Besetzung aktualisieren", tags=['Castings']),
    partial_update=extend_schema(summary="Besetzung teilweise aktualisieren", tags=['Castings']),
    destroy=extend_schema(summary="Besetzung löschen", tags=['Castings']),
)
class MovieCastingViewSet(viewsets.ModelViewSet):
    """ViewSet für MovieCasting CRUD"""
    queryset = MovieCasting.objects.select_related('movie', 'artist').all()
    serializer_class = MovieCastingSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]

🔐 Authentication dokumentieren (Schritt 9)

Schritt 9: Auth Endpoints mit extend_schema

# filepath: movies/views.py
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny, IsAuthenticated
from drf_spectacular.utils import extend_schema, OpenApiResponse

from .serializers import UserSerializer, UserDetailSerializer


@extend_schema(
    summary="User registrieren",
    description="Erstellt einen neuen User und gibt ein Authentifizierungs-Token zurück.",
    tags=['Authentication'],
    request=UserSerializer,
    responses={
        201: OpenApiResponse(
            response=UserDetailSerializer,
            description='User erfolgreich erstellt',
        ),
        400: OpenApiResponse(
            description='Validierungsfehler',
        ),
    },
    examples=[
        OpenApiExample(
            'Register Example',
            value={
                'username': 'john_doe',
                'email': 'john@example.com',
                'password': 'secret123',
                'password2': 'secret123',
                'first_name': 'John',
                'last_name': 'Doe',
            },
            request_only=True,
        ),
    ],
)
@api_view(['POST'])
@permission_classes([AllowAny])
def register(request):
    """User registrieren & Token zurückgeben"""
    # ... Implementation wie vorher
    pass


@extend_schema(
    summary="User login",
    description="Authentifiziert einen User und gibt ein Token zurück.",
    tags=['Authentication'],
    request={
        'type': 'object',
        'properties': {
            'username': {'type': 'string'},
            'password': {'type': 'string'},
        },
        'required': ['username', 'password'],
    },
    responses={
        200: OpenApiResponse(
            description='Login erfolgreich',
        ),
        401: OpenApiResponse(
            description='Ungültige Credentials',
        ),
    },
    examples=[
        OpenApiExample(
            'Login Example',
            value={
                'username': 'john_doe',
                'password': 'secret123',
            },
            request_only=True,
        ),
    ],
)
@api_view(['POST'])
@permission_classes([AllowAny])
def login(request):
    """User login & Token zurückgeben"""
    # ... Implementation wie vorher
    pass


@extend_schema(
    summary="User logout",
    description="Löscht das Authentifizierungs-Token des aktuellen Users.",
    tags=['Authentication'],
    responses={
        200: OpenApiResponse(description='Logout erfolgreich'),
        401: OpenApiResponse(description='Nicht authentifiziert'),
    },
)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def logout(request):
    """User logout - Token löschen"""
    # ... Implementation wie vorher
    pass


@extend_schema(
    summary="Aktueller User",
    description="Gibt Informationen über den aktuell authentifizierten User zurück.",
    tags=['Authentication'],
    responses={
        200: UserDetailSerializer,
        401: OpenApiResponse(description='Nicht authentifiziert'),
    },
)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def user_profile(request):
    """Aktueller User"""
    # ... Implementation wie vorher
    pass

🧪 Swagger UI testen (Schritt 10)

Schritt 10: API in Swagger UI testen

# Browser öffnen:
http://localhost:8000/api/schema/swagger-ui/

📸 Swagger UI Features:

1. Endpoints anzeigen:

Authentication
  POST /api/auth/register/      User registrieren
  POST /api/auth/login/          User login
  POST /api/auth/logout/         User logout
  GET  /api/auth/me/             Aktueller User

Movies
  GET    /api/movies/            Liste aller Filme
  POST   /api/movies/            Film erstellen
  GET    /api/movies/{id}/       Film-Details
  PUT    /api/movies/{id}/       Film aktualisieren
  PATCH  /api/movies/{id}/       Film teilweise aktualisieren
  DELETE /api/movies/{id}/       Film löschen
  GET    /api/movies/top_rated/  Top-bewertete Filme
  GET    /api/movies/by_genre/   Filme nach Genre

Artists
  GET    /api/artists/           Liste aller Künstler
  POST   /api/artists/           Künstler erstellen
  ...

Castings
  GET    /api/castings/          Liste aller Besetzungen
  POST   /api/castings/          Besetzung erstellen
  ...

🔐 2. Authentication testen:

1. Register User:
   POST /api/auth/register/
   Body: {
     "username": "john_doe",
     "email": "john@example.com",
     "password": "secret123",
     "password2": "secret123"
   }
   
   Response: {
     "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b",
     "user": { ... }
   }

2. Token in Swagger speichern:
   - Oben rechts: "Authorize" Button klicken
   - Eingabefeld: "Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
   - "Authorize" klicken
   - ✅ Jetzt authenticated!

3. Protected Endpoints testen:
   POST /api/movies/
   - Funktioniert jetzt (mit Token)!

🎯 3. API Request testen (Try it out):

1. POST /api/movies/ aufklappen
2. "Try it out" Button klicken
3. Request Body editieren:
   {
     "title": "Interstellar",
     "year": 2014,
     "genre": "Sci-Fi",
     "rating": 8.6,
     "description": "A team of explorers travel through a wormhole."
   }
4. "Execute" Button klicken
5. Response sehen:
   - Status: 201 Created
   - Response Body: { "id": 1, "title": "Interstellar", ... }

🚀 Advanced Features

🏷️ Tags gruppieren

# settings.py
SPECTACULAR_SETTINGS = {
    # ...
    'TAGS': [
        {
            'name': 'Authentication',
            'description': 'User Authentication & Authorization',
        },
        {
            'name': 'Movies',
            'description': 'Film-Verwaltung (CRUD)',
        },
        {
            'name': 'Artists',
            'description': 'Künstler-Verwaltung (CRUD)',
        },
        {
            'name': 'Castings',
            'description': 'Besetzungs-Verwaltung',
        },
    ],
}

📝 Custom Response Schemas

from drf_spectacular.utils import extend_schema

@extend_schema(
    responses={
        200: MovieSerializer(many=True),
        400: {
            'type': 'object',
            'properties': {
                'error': {'type': 'string'},
            },
        },
        401: {
            'type': 'object',
            'properties': {
                'detail': {'type': 'string'},
            },
        },
    }
)
@action(detail=False, methods=['get'])
def custom_action(self, request):
    pass

🔍 Query Parameters

from drf_spectacular.utils import OpenApiParameter
from drf_spectacular.types import OpenApiTypes

@extend_schema(
    parameters=[
        OpenApiParameter(
            name='search',
            type=OpenApiTypes.STR,
            location=OpenApiParameter.QUERY,
            description='Suchbegriff',
            required=False,
        ),
        OpenApiParameter(
            name='ordering',
            type=OpenApiTypes.STR,
            location=OpenApiParameter.QUERY,
            description='Sortierung (z.B. "-rating")',
            required=False,
        ),
    ]
)
def list(self, request):
    pass

🎨 Custom Examples

from drf_spectacular.utils import OpenApiExample

@extend_schema(
    examples=[
        OpenApiExample(
            'Success Example',
            value={'id': 1, 'title': 'Inception'},
            response_only=True,
            status_codes=['200'],
        ),
        OpenApiExample(
            'Error Example',
            value={'error': 'Movie not found'},
            response_only=True,
            status_codes=['404'],
        ),
    ]
)
def retrieve(self, request, pk=None):
    pass

📥 Schema Download & Code Generation

OpenAPI Schema downloaden:

# Browser:
http://localhost:8000/api/schema/          # JSON Format
http://localhost:8000/api/schema/?format=yaml  # YAML Format

# Terminal (JSON):
curl http://localhost:8000/api/schema/ > schema.json

# Terminal (YAML):
curl http://localhost:8000/api/schema/?format=yaml > schema.yaml

# Django Management Command:
python manage.py spectacular --file schema.yaml --format openapi-yaml

Client Code generieren (OpenAPI Generator):

# Installation:
npm install @openapitools/openapi-generator-cli -g

# JavaScript/TypeScript Client:
openapi-generator-cli generate \
  -i http://localhost:8000/api/schema/ \
  -g typescript-axios \
  -o ./client

# Python Client:
openapi-generator-cli generate \
  -i http://localhost:8000/api/schema/ \
  -g python \
  -o ./python-client

# Java Client:
openapi-generator-cli generate \
  -i http://localhost:8000/api/schema/ \
  -g java \
  -o ./java-client

# Weitere: kotlin, swift, php, ruby, go, etc.

Frontend Integration (TypeScript Axios):

// Generierter Client:
import { MovieApi, Configuration } from './client';

// API Client konfigurieren
const config = new Configuration({
  basePath: 'http://localhost:8000',
  accessToken: 'your-token-here',
});

const movieApi = new MovieApi(config);

// API verwenden (type-safe!)
async function getMovies() {
  const response = await movieApi.moviesListList();
  console.log(response.data); // TypeScript weiß: Movie[]
}

async function createMovie() {
  const movie = await movieApi.moviesCreateCreate({
    title: 'Inception',
    year: 2010,
    genre: 'Sci-Fi',
    rating: 8.8,
  });
  console.log(movie.data); // TypeScript weiß: Movie
}

💡 Best Practices

✅ 1. Docstrings verwenden

class MovieViewSet(viewsets.ModelViewSet):
    """
    ViewSet für Movie CRUD.
    
    Bietet vollständige CRUD-Funktionalität
    für Film-Verwaltung.
    """
    
    def create(self, request, *args, **kwargs):
        """
        Film erstellen.
        
        Erstellt einen neuen Film in der Datenbank.
        Authentifizierung erforderlich.
        """
        pass

✅ 2. help_text in Serializers

title = serializers.CharField(
    max_length=200,
    help_text="Titel des Films"
)

rating = serializers.DecimalField(
    max_digits=3,
    decimal_places=1,
    help_text="Bewertung 0.0-10.0"
)

✅ 3. extend_schema nutzen

@extend_schema(
    summary="Kurze Beschreibung",
    description="Detaillierte Beschreibung",
    tags=['Movies'],
    parameters=[...],
    responses={200: ...},
    examples=[...],
)
@action(detail=False)
def custom_action(self, request):
    pass

✅ 4. Tags organisieren

# Logische Gruppierung:
tags=['Authentication']
tags=['Movies']
tags=['Artists']

# In Swagger UI:
▼ Authentication
  - POST /api/auth/login/
  - POST /api/auth/register/

▼ Movies
  - GET /api/movies/
  - POST /api/movies/

✅ 5. Examples hinzufügen

examples=[
    OpenApiExample(
        'Success',
        value={'id': 1, 'title': 'Inception'},
        response_only=True,
    ),
    OpenApiExample(
        'Error',
        value={'error': 'Not found'},
        response_only=True,
    ),
]

✅ 6. Versioning

# settings.py
SPECTACULAR_SETTINGS = {
    'VERSION': '1.0.0',  # Semantic Versioning
    
    # Version in URL:
    'SCHEMA_PATH_PREFIX': r'/api/v1/',
}

# URLs:
/api/v1/movies/
/api/v2/movies/  # Neue Version

🎉 Zusammenfassung

Du beherrschst jetzt OpenAPI & Swagger!

✅ Was du gelernt hast:

  • 📚 OpenAPI Specification verstehen
  • 🎨 Swagger UI & ReDoc
  • 🌟 drf-spectacular Setup
  • ⚙️ SPECTACULAR_SETTINGS konfigurieren
  • 🔗 Schema URLs einrichten
  • 📝 Serializers dokumentieren
  • 📋 ViewSets mit extend_schema
  • 🔐 Authentication dokumentieren
  • 🧪 Swagger UI testen
  • 📥 Schema downloaden & Code generieren

🎯 Wichtige URLs:

# Swagger UI (interaktiv):
http://localhost:8000/api/schema/swagger-ui/

# ReDoc (schöne Doku):
http://localhost:8000/api/schema/redoc/

# Schema Download (JSON):
http://localhost:8000/api/schema/

# Schema Download (YAML):
http://localhost:8000/api/schema/?format=yaml

📦 Installation Cheat Sheet:

# 1. Installation:
pip install drf-spectacular

# 2. settings.py:
INSTALLED_APPS = ['drf_spectacular', ...]
REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
SPECTACULAR_SETTINGS = { ... }

# 3. urls.py:
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, SpectacularRedocView

urlpatterns = [
    path('api/schema/', SpectacularAPIView.as_view()),
    path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view()),
    path('api/schema/redoc/', SpectacularRedocView.as_view()),
]

# 4. Views dokumentieren:
@extend_schema(summary="...", description="...", tags=['Movies'])

# 5. Testen:
python manage.py runserver
http://localhost:8000/api/schema/swagger-ui/

Viel Erfolg mit deiner dokumentierten API! 📚

Keep coding, keep documenting! 💻

1 / 16