Django REST Framework

Professionelle REST APIs mit DRF

Von Models zu vollständigen APIs in Minuten

Serializers | ViewSets | Routers

📋 Was lernen wir heute?

1

DRF installieren

pip install djangorestframework

Settings konfigurieren

2

Serializers erstellen

Models → JSON automatisch

ModelSerializer nutzen

3

ViewSets erstellen

CRUD mit einer Klasse

ModelViewSet verwenden

4

Router konfigurieren

URLs automatisch generieren

DefaultRouter nutzen

5

API testen

Browsable API nutzen

Mit Postman/curl testen

🚀 Warum Django REST Framework?

Der Industriestandard für Django APIs

DRF ist das mächtigste Tool für REST APIs in Django!

❌ Ohne DRF (Traditionell)

def movie_list(request):
    movies = Movie.objects.all()
    data = []
    for movie in movies:
        data.append({
            'id': movie.id,
            'title': movie.title,
            'year': movie.year,
            'genre': movie.genre,
            'rating': str(movie.rating),
            # ... 50 Zeilen Code ...
        })
    return JsonResponse(data, safe=False)

def movie_create(request):
    # ... 30 Zeilen Validierung ...
    # ... 20 Zeilen Fehlerbehandlung ...
    pass

# ... 5 weitere Funktionen für CRUD

✅ Mit DRF (Modern)

class MovieSerializer(serializers.ModelSerializer):
    class Meta:
        model = Movie
        fields = '__all__'

class MovieViewSet(viewsets.ModelViewSet):
    queryset = Movie.objects.all()
    serializer_class = MovieSerializer

# Fertig! Alle CRUD-Operationen funktionieren!
# + Validierung
# + Fehlerbehandlung
# + Pagination
# + Filtering
# + Browsable API

🎯 Vorteile

  • Weniger Code: 90% weniger Boilerplate
  • Automatische Serialisierung: Models → JSON
  • Validierung: Eingebaut & erweiterbar
  • Authentication: Token, JWT, OAuth2
  • Permissions: Granulare Zugriffskontrolle
  • Browsable API: Interaktive Dokumentation
  • Pagination: Automatisch für große Datensätze
  • Filtering & Search: Eingebaut

📦 Schritt 1: DRF installieren

1.1

🔧 Paket installieren

# Terminal / PowerShell:
pip install djangorestframework

# Output:
Collecting djangorestframework
Successfully installed djangorestframework-3.14.0
1.2

⚙️ Settings konfigurieren

# filepath: firstmovieapi/settings.py
# ...existing code...

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    # Third-party apps
    'rest_framework',  # ← NEU!
    
    # Local apps
    'movies',
]

# ...existing code...

# REST Framework Konfiguration (am Ende der Datei)
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',  # Für Development
    ],
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',  # Interaktive API
    ],
}

✅ Installation abgeschlossen!

Django REST Framework ist jetzt einsatzbereit.

🔄 Schritt 2: Serializers erstellen

Was sind Serializers?

Serializers konvertieren Django Models ↔ JSON

Erstelle: movies/serializers.py

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


class MovieSerializer(serializers.ModelSerializer):
    """Serializer für Movie Model"""
    
    class Meta:
        model = Movie
        fields = '__all__'  # Alle Felder
        read_only_fields = ['created_at', 'updated_at']


class ArtistSerializer(serializers.ModelSerializer):
    """Serializer für Artist Model"""
    full_name = serializers.ReadOnlyField()  # Property aus Model
    
    class Meta:
        model = Artist
        fields = '__all__'
        read_only_fields = ['created_at', 'updated_at']


class MovieCastingSerializer(serializers.ModelSerializer):
    """Serializer für MovieCasting Model"""
    
    class Meta:
        model = MovieCasting
        fields = '__all__'
        read_only_fields = ['created_at']

📥 Python Model

movie = Movie.objects.get(pk=1)
# 

print(movie.title)
# "Inception"

print(movie.rating)
# Decimal('8.8')

📤 JSON Output

serializer = MovieSerializer(movie)
print(serializer.data)
# {
#   "id": 1,
#   "title": "Inception",
#   "year": 2010,
#   "genre": "Sci-Fi",
#   "rating": "8.8",
#   "description": "A thief...",
#   "created_at": "2025-11-15T10:00:00Z",
#   "updated_at": "2025-11-15T10:00:00Z"
# }

✨ Serializer Features & Anpassungen

Erweiterte Serializer (Optional)

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


class MovieSerializer(serializers.ModelSerializer):
    """Erweiterter Movie Serializer mit Validierung"""
    
    # Custom Feld (berechnet)
    age = serializers.SerializerMethodField()
    
    class Meta:
        model = Movie
        fields = '__all__'
        read_only_fields = ['created_at', 'updated_at']
    
    def get_age(self, obj):
        """Berechne Alter des Films"""
        from datetime import datetime
        return datetime.now().year - obj.year
    
    def validate_year(self, value):
        """Validiere Jahr"""
        from datetime import datetime
        current_year = datetime.now().year
        
        if value < 1888:  # Erstes Kino-Jahr
            raise serializers.ValidationError("Jahr kann nicht vor 1888 liegen")
        
        if value > current_year + 5:  # Max 5 Jahre in Zukunft
            raise serializers.ValidationError(f"Jahr kann nicht nach {current_year + 5} liegen")
        
        return value
    
    def validate_rating(self, value):
        """Validiere Rating"""
        if value is not None and (value < 0 or value > 10):
            raise serializers.ValidationError("Rating muss zwischen 0 und 10 liegen")
        return value


class ArtistSerializer(serializers.ModelSerializer):
    """Erweiterter Artist Serializer"""
    full_name = serializers.ReadOnlyField()
    movie_count = serializers.SerializerMethodField()
    
    class Meta:
        model = Artist
        fields = '__all__'
        read_only_fields = ['created_at', 'updated_at']
    
    def get_movie_count(self, obj):
        """Anzahl Filme des Künstlers"""
        return obj.movie_roles.count()


class MovieCastingDetailSerializer(serializers.ModelSerializer):
    """Detaillierter Casting Serializer mit verschachtelten Daten"""
    movie = MovieSerializer(read_only=True)  # Verschachteltes Movie-Objekt
    artist = ArtistSerializer(read_only=True)  # Verschachteltes Artist-Objekt
    movie_id = serializers.PrimaryKeyRelatedField(
        queryset=Movie.objects.all(),
        source='movie',
        write_only=True
    )
    artist_id = serializers.PrimaryKeyRelatedField(
        queryset=Artist.objects.all(),
        source='artist',
        write_only=True
    )
    
    class Meta:
        model = MovieCasting
        fields = '__all__'
        read_only_fields = ['created_at']


class MovieCastingSerializer(serializers.ModelSerializer):
    """Einfacher Casting Serializer (nur IDs)"""
    
    class Meta:
        model = MovieCasting
        fields = '__all__'
        read_only_fields = ['created_at']

🔍 Was macht das?

  • SerializerMethodField: Berechnete Felder (age, movie_count)
  • validate_*: Feld-spezifische Validierung
  • Verschachtelte Serializer: Movie & Artist komplett in Casting
  • read_only / write_only: Nur lesen oder nur schreiben

🎯 Schritt 3: ViewSets erstellen

Was sind ViewSets?

Eine Klasse für alle CRUD-Operationen (List, Create, Retrieve, Update, Delete)

Erstelle: movies/views.py

# filepath: movies/views.py
from rest_framework import viewsets, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from .models import Movie, Artist, MovieCasting
from .serializers import (
    MovieSerializer,
    ArtistSerializer,
    MovieCastingSerializer,
    MovieCastingDetailSerializer
)


class MovieViewSet(viewsets.ModelViewSet):
    """
    ViewSet für Movie Model
    Bietet automatisch:
    - list (GET /api/movies/)
    - create (POST /api/movies/)
    - retrieve (GET /api/movies/{id}/)
    - update (PUT /api/movies/{id}/)
    - partial_update (PATCH /api/movies/{id}/)
    - destroy (DELETE /api/movies/{id}/)
    """
    queryset = Movie.objects.all()
    serializer_class = MovieSerializer
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_fields = ['year', 'genre']  # Filter nach Jahr & Genre
    search_fields = ['title', 'description']  # Suche in Titel & Beschreibung
    ordering_fields = ['year', 'rating', 'title']  # Sortierung möglich
    ordering = ['-year']  # Standard: Neueste zuerst


class ArtistViewSet(viewsets.ModelViewSet):
    """
    ViewSet für Artist Model
    """
    queryset = Artist.objects.all()
    serializer_class = ArtistSerializer
    filter_backends = [filters.SearchFilter, filters.OrderingFilter]
    search_fields = ['first_name', 'last_name', 'nationality']
    ordering_fields = ['last_name', 'first_name']
    ordering = ['last_name', 'first_name']


class MovieCastingViewSet(viewsets.ModelViewSet):
    """
    ViewSet für MovieCasting Model
    """
    queryset = MovieCasting.objects.all()
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['movie', 'artist', 'is_main_role']
    
    def get_serializer_class(self):
        """Wähle Serializer basierend auf Action"""
        if self.action == 'list' or self.action == 'retrieve':
            return MovieCastingDetailSerializer  # Mit verschachtelten Daten
        return MovieCastingSerializer  # Nur IDs für Create/Update

🎨 Custom Actions in ViewSets

Erweitere movies/views.py mit Custom Endpoints:

# filepath: movies/views.py
# ...existing code...

class MovieViewSet(viewsets.ModelViewSet):
    queryset = Movie.objects.all()
    serializer_class = MovieSerializer
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_fields = ['year', 'genre']
    search_fields = ['title', 'description']
    ordering_fields = ['year', 'rating', 'title']
    ordering = ['-year']
    
    @action(detail=True, methods=['get'])
    def castings(self, request, pk=None):
        """
        GET /api/movies/{id}/castings/
        Alle Besetzungen eines Films
        """
        movie = self.get_object()
        castings = movie.castings.all()
        serializer = MovieCastingDetailSerializer(castings, many=True)
        return Response(serializer.data)
    
    @action(detail=False, methods=['get'])
    def top_rated(self, request):
        """
        GET /api/movies/top_rated/
        Top 10 bestbewertete Filme
        """
        movies = Movie.objects.filter(rating__isnull=False).order_by('-rating')[:10]
        serializer = self.get_serializer(movies, many=True)
        return Response(serializer.data)
    
    @action(detail=False, methods=['get'])
    def recent(self, request):
        """
        GET /api/movies/recent/
        Filme der letzten 5 Jahre
        """
        from datetime import datetime
        current_year = datetime.now().year
        movies = Movie.objects.filter(year__gte=current_year - 5)
        serializer = self.get_serializer(movies, many=True)
        return Response(serializer.data)


class ArtistViewSet(viewsets.ModelViewSet):
    queryset = Artist.objects.all()
    serializer_class = ArtistSerializer
    filter_backends = [filters.SearchFilter, filters.OrderingFilter]
    search_fields = ['first_name', 'last_name', 'nationality']
    ordering_fields = ['last_name', 'first_name']
    ordering = ['last_name', 'first_name']
    
    @action(detail=True, methods=['get'])
    def movies(self, request, pk=None):
        """
        GET /api/artists/{id}/movies/
        Alle Filme eines Künstlers
        """
        artist = self.get_object()
        castings = artist.movie_roles.all()
        movies = [casting.movie for casting in castings]
        serializer = MovieSerializer(movies, many=True)
        return Response(serializer.data)

📋 Custom Endpoints:

GET /api/movies/{id}/castings/      # Besetzung eines Films
GET /api/movies/top_rated/          # Top 10 Filme
GET /api/movies/recent/             # Aktuelle Filme
GET /api/artists/{id}/movies/       # Filme eines Künstlers

🔗 Schritt 4: Router konfigurieren

Router = Automatische URL-Generierung

DRF generiert alle URLs automatisch aus ViewSets!

Erstelle: movies/urls.py

# filepath: movies/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import MovieViewSet, ArtistViewSet, MovieCastingViewSet

# Router erstellen
router = DefaultRouter()

# ViewSets registrieren
router.register(r'movies', MovieViewSet, basename='movie')
router.register(r'artists', ArtistViewSet, basename='artist')
router.register(r'castings', MovieCastingViewSet, basename='casting')

# URLs
urlpatterns = [
    path('', include(router.urls)),
]

Registriere in firstmovieapi/urls.py:

# filepath: firstmovieapi/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('movies.urls')),  # ← API-Endpunkte
    path('api-auth/', include('rest_framework.urls')),  # ← Login für Browsable API
]

🎉 Automatisch generierte URLs:

# 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/{id}/castings/    # Besetzung (Custom)
GET    /api/movies/top_rated/        # Top-Filme (Custom)
GET    /api/movies/recent/           # Aktuelle Filme (Custom)

# Artists
GET    /api/artists/                 # Liste aller Künstler
POST   /api/artists/                 # Künstler erstellen
GET    /api/artists/{id}/            # Künstler-Details
PUT    /api/artists/{id}/            # Künstler aktualisieren
PATCH  /api/artists/{id}/            # Künstler teilweise aktualisieren
DELETE /api/artists/{id}/            # Künstler löschen
GET    /api/artists/{id}/movies/     # Filme des Künstlers (Custom)

# Castings
GET    /api/castings/                # Liste aller Besetzungen
POST   /api/castings/                # Besetzung erstellen
GET    /api/castings/{id}/           # Besetzung-Details
PUT    /api/castings/{id}/           # Besetzung aktualisieren
PATCH  /api/castings/{id}/           # Besetzung teilweise aktualisieren
DELETE /api/castings/{id}/           # Besetzung löschen

🧪 Schritt 5: API testen

1

🚀 Server starten

# Terminal:
python manage.py runserver

# Output:
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
2

🌐 Browsable API öffnen

Öffne im Browser:

http://127.0.0.1:8000/api/

# Du siehst eine schöne interaktive API-Oberfläche!

Die Browsable API ist GOLD wert!

  • Interaktive Formulare zum Testen
  • Automatische Dokumentation
  • Einfaches Debugging
  • Keine extra Tools nötig
3

📝 Film erstellen (Browser)

Gehe zu: http://127.0.0.1:8000/api/movies/

Fülle das Formular aus:

{
    "title": "The Matrix",
    "year": 1999,
    "genre": "Sci-Fi",
    "rating": 8.7,
    "description": "A computer hacker learns..."
}

Klicke auf POST

4

📥 Mit PowerShell testen

# Film erstellen
$body = @{
    title = "Inception"
    year = 2010
    genre = "Sci-Fi"
    rating = 8.8
    description = "A thief who steals corporate secrets..."
} | ConvertTo-Json

Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/" `
    -Method POST `
    -Body $body `
    -ContentType "application/json"

# Alle Filme abrufen
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/"

# Einen Film abrufen
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/1/"

# Film aktualisieren
$updateBody = @{
    rating = 9.0
} | ConvertTo-Json

Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/1/" `
    -Method PATCH `
    -Body $updateBody `
    -ContentType "application/json"

# Film löschen
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/1/" `
    -Method DELETE

🔍 Filtering, Suche & Sortierung

Installiere django-filter (optional aber empfohlen):

# Terminal:
pip install django-filter

# In settings.py INSTALLED_APPS hinzufügen:
'django_filters',

🔎 Suche (Search)

# Suche nach "Matrix" in Titel & Beschreibung
GET /api/movies/?search=Matrix

# Suche nach Künstler
GET /api/artists/?search=DiCaprio

🎯 Filter

# Filme aus 2010
GET /api/movies/?year=2010

# Sci-Fi Filme
GET /api/movies/?genre=Sci-Fi

# Hauptrollen
GET /api/castings/?is_main_role=true

📊 Sortierung

# Nach Jahr sortiert (aufsteigend)
GET /api/movies/?ordering=year

# Nach Rating (absteigend)
GET /api/movies/?ordering=-rating

# Mehrere Felder
GET /api/movies/?ordering=-year,title

🔗 Kombiniert

# Sci-Fi Filme ab 2010, nach Rating sortiert
GET /api/movies/?genre=Sci-Fi&year__gte=2010&ordering=-rating

# Suche + Filter
GET /api/movies/?search=Matrix&year=1999

📁 Komplette Dateiübersicht

Alle erstellten/modifizierten Dateien

1️⃣ movies/models.py (bereits vorhanden)

# filepath: movies/models.py
from django.db import models

class Movie(models.Model):
    """Model für Filme"""
    title = models.CharField(max_length=200, verbose_name="Titel")
    year = models.IntegerField(verbose_name="Erscheinungsjahr")
    genre = models.CharField(max_length=100, verbose_name="Genre", blank=True)
    rating = models.DecimalField(max_digits=3, decimal_places=1, null=True, blank=True, verbose_name="Bewertung")
    description = models.TextField(verbose_name="Beschreibung", blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        verbose_name = "Film"
        verbose_name_plural = "Filme"
        ordering = ['-year', 'title']
    
    def __str__(self):
        return f"{self.title} ({self.year})"


class Artist(models.Model):
    """Model für Schauspieler/Künstler"""
    first_name = models.CharField(max_length=100, verbose_name="Vorname")
    last_name = models.CharField(max_length=100, verbose_name="Nachname")
    birth_date = models.DateField(verbose_name="Geburtsdatum", null=True, blank=True)
    nationality = models.CharField(max_length=100, verbose_name="Nationalität", blank=True)
    biography = models.TextField(verbose_name="Biografie", blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        verbose_name = "Künstler"
        verbose_name_plural = "Künstler"
        ordering = ['last_name', 'first_name']
    
    def __str__(self):
        return f"{self.first_name} {self.last_name}"
    
    @property
    def full_name(self):
        return f"{self.first_name} {self.last_name}"


class MovieCasting(models.Model):
    """Brückentabelle: Welcher Artist spielt in welchem Movie"""
    movie = models.ForeignKey(Movie, on_delete=models.CASCADE, related_name='castings', verbose_name="Film")
    artist = models.ForeignKey(Artist, on_delete=models.CASCADE, related_name='movie_roles', verbose_name="Künstler")
    role_name = models.CharField(max_length=200, verbose_name="Rollenname")
    is_main_role = models.BooleanField(default=False, verbose_name="Hauptrolle")
    order = models.IntegerField(default=0, verbose_name="Reihenfolge")
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        verbose_name = "Besetzung"
        verbose_name_plural = "Besetzungen"
        ordering = ['order', 'artist__last_name']
        unique_together = [['movie', 'artist', 'role_name']]
    
    def __str__(self):
        role_type = "Hauptrolle" if self.is_main_role else "Nebenrolle"
        return f"{self.artist.full_name} als {self.role_name} in {self.movie.title} ({role_type})"

📁 Komplette Dateiübersicht (2/6)

2️⃣ movies/serializers.py (NEU)

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


class MovieSerializer(serializers.ModelSerializer):
    """Erweiterter Movie Serializer mit Validierung"""
    age = serializers.SerializerMethodField()
    
    class Meta:
        model = Movie
        fields = '__all__'
        read_only_fields = ['created_at', 'updated_at']
    
    def get_age(self, obj):
        """Berechne Alter des Films"""
        from datetime import datetime
        return datetime.now().year - obj.year
    
    def validate_year(self, value):
        """Validiere Jahr"""
        from datetime import datetime
        current_year = datetime.now().year
        
        if value < 1888:
            raise serializers.ValidationError("Jahr kann nicht vor 1888 liegen")
        
        if value > current_year + 5:
            raise serializers.ValidationError(f"Jahr kann nicht nach {current_year + 5} liegen")
        
        return value
    
    def validate_rating(self, value):
        """Validiere Rating"""
        if value is not None and (value < 0 or value > 10):
            raise serializers.ValidationError("Rating muss zwischen 0 und 10 liegen")
        return value


class ArtistSerializer(serializers.ModelSerializer):
    """Erweiterter Artist Serializer"""
    full_name = serializers.ReadOnlyField()
    movie_count = serializers.SerializerMethodField()
    
    class Meta:
        model = Artist
        fields = '__all__'
        read_only_fields = ['created_at', 'updated_at']
    
    def get_movie_count(self, obj):
        """Anzahl Filme des Künstlers"""
        return obj.movie_roles.count()


class MovieCastingDetailSerializer(serializers.ModelSerializer):
    """Detaillierter Casting Serializer mit verschachtelten Daten"""
    movie = MovieSerializer(read_only=True)
    artist = ArtistSerializer(read_only=True)
    movie_id = serializers.PrimaryKeyRelatedField(
        queryset=Movie.objects.all(),
        source='movie',
        write_only=True
    )
    artist_id = serializers.PrimaryKeyRelatedField(
        queryset=Artist.objects.all(),
        source='artist',
        write_only=True
    )
    
    class Meta:
        model = MovieCasting
        fields = '__all__'
        read_only_fields = ['created_at']


class MovieCastingSerializer(serializers.ModelSerializer):
    """Einfacher Casting Serializer (nur IDs)"""
    
    class Meta:
        model = MovieCasting
        fields = '__all__'
        read_only_fields = ['created_at']

📁 Komplette Dateiübersicht (3/6)

3️⃣ movies/views.py (NEU)

# filepath: movies/views.py
from rest_framework import viewsets, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from .models import Movie, Artist, MovieCasting
from .serializers import (
    MovieSerializer,
    ArtistSerializer,
    MovieCastingSerializer,
    MovieCastingDetailSerializer
)


class MovieViewSet(viewsets.ModelViewSet):
    """
    ViewSet für Movie Model
    Bietet automatisch alle CRUD-Operationen
    """
    queryset = Movie.objects.all()
    serializer_class = MovieSerializer
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_fields = ['year', 'genre']
    search_fields = ['title', 'description']
    ordering_fields = ['year', 'rating', 'title']
    ordering = ['-year']
    
    @action(detail=True, methods=['get'])
    def castings(self, request, pk=None):
        """GET /api/movies/{id}/castings/ - Alle Besetzungen eines Films"""
        movie = self.get_object()
        castings = movie.castings.all()
        serializer = MovieCastingDetailSerializer(castings, many=True)
        return Response(serializer.data)
    
    @action(detail=False, methods=['get'])
    def top_rated(self, request):
        """GET /api/movies/top_rated/ - Top 10 bestbewertete Filme"""
        movies = Movie.objects.filter(rating__isnull=False).order_by('-rating')[:10]
        serializer = self.get_serializer(movies, many=True)
        return Response(serializer.data)
    
    @action(detail=False, methods=['get'])
    def recent(self, request):
        """GET /api/movies/recent/ - Filme der letzten 5 Jahre"""
        from datetime import datetime
        current_year = datetime.now().year
        movies = Movie.objects.filter(year__gte=current_year - 5)
        serializer = self.get_serializer(movies, many=True)
        return Response(serializer.data)


class ArtistViewSet(viewsets.ModelViewSet):
    """
    ViewSet für Artist Model
    """
    queryset = Artist.objects.all()
    serializer_class = ArtistSerializer
    filter_backends = [filters.SearchFilter, filters.OrderingFilter]
    search_fields = ['first_name', 'last_name', 'nationality']
    ordering_fields = ['last_name', 'first_name']
    ordering = ['last_name', 'first_name']
    
    @action(detail=True, methods=['get'])
    def movies(self, request, pk=None):
        """GET /api/artists/{id}/movies/ - Alle Filme eines Künstlers"""
        artist = self.get_object()
        castings = artist.movie_roles.all()
        movies = [casting.movie for casting in castings]
        serializer = MovieSerializer(movies, many=True)
        return Response(serializer.data)


class MovieCastingViewSet(viewsets.ModelViewSet):
    """
    ViewSet für MovieCasting Model
    """
    queryset = MovieCasting.objects.all()
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['movie', 'artist', 'is_main_role']
    
    def get_serializer_class(self):
        """Wähle Serializer basierend auf Action"""
        if self.action == 'list' or self.action == 'retrieve':
            return MovieCastingDetailSerializer
        return MovieCastingSerializer

📁 Komplette Dateiübersicht (4/6)

4️⃣ movies/urls.py (NEU)

# filepath: movies/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import MovieViewSet, ArtistViewSet, MovieCastingViewSet

# Router erstellen
router = DefaultRouter()

# ViewSets registrieren
router.register(r'movies', MovieViewSet, basename='movie')
router.register(r'artists', ArtistViewSet, basename='artist')
router.register(r'castings', MovieCastingViewSet, basename='casting')

# URLs
urlpatterns = [
    path('', include(router.urls)),
]

5️⃣ firstmovieapi/urls.py (MODIFIZIERT)

# filepath: firstmovieapi/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('movies.urls')),  # ← API-Endpunkte
    path('api-auth/', include('rest_framework.urls')),  # ← Login für Browsable API
]

📁 Komplette Dateiübersicht (5/6)

6️⃣ firstmovieapi/settings.py (MODIFIZIERT)

# filepath: firstmovieapi/settings.py
# ...existing code...

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    # Third-party apps
    'rest_framework',        # ← NEU!
    'django_filters',        # ← NEU! (optional)
    
    # Local apps
    'movies',
]

# ...existing code...

# REST Framework Konfiguration (am Ende der Datei)
REST_FRAMEWORK = {
    # Permissions
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',  # Für Development
    ],
    
    # Pagination
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,
    
    # Renderer
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',  # Interaktive API
    ],
    
    # Filter Backend
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend',
        'rest_framework.filters.SearchFilter',
        'rest_framework.filters.OrderingFilter',
    ],
}

🎯 Zusammenfassung

Was haben wir gelernt?

✅ Installation

  • DRF installiert
  • Settings konfiguriert
  • REST_FRAMEWORK Settings

✅ Serializers

  • ModelSerializer erstellt
  • Validierung implementiert
  • Custom Fields hinzugefügt
  • Verschachtelte Serializer

✅ ViewSets

  • ModelViewSet verwendet
  • CRUD automatisch
  • Custom Actions erstellt
  • Filtering & Search

✅ URLs

  • Router konfiguriert
  • URLs automatisch generiert
  • Browsable API aktiviert

❌ Ohne DRF

~300+ Zeilen Code

  • Viele View-Funktionen
  • Manuelle Serialisierung
  • Eigene Validierung
  • Keine Browsable API
  • Keine Pagination

✅ Mit DRF

~100 Zeilen Code

  • 3 ViewSets
  • Automatische Serialisierung
  • Eingebaute Validierung
  • Browsable API gratis
  • Pagination automatisch

🚀 Nächste Schritte

1

Authentication

Token-basierte Authentifizierung

JWT (JSON Web Tokens)

OAuth2

2

Permissions

IsAuthenticated

IsAdminUser

Custom Permissions

3

Throttling

Rate Limiting

Schutz vor Missbrauch

4

Testing

API Tests schreiben

APITestCase

Coverage

5

Deployment

Production Settings

CORS konfigurieren

Dokumentation (Swagger)

🎓 Gratulation!

Du hast eine vollständige REST API mit Django REST Framework erstellt!

Jetzt kannst du professionelle APIs bauen! 🎉

1 / 18