Von Models zu vollständigen APIs in Minuten
Serializers | ViewSets | Routers
pip install djangorestframework
Settings konfigurieren
Models → JSON automatisch
ModelSerializer nutzen
CRUD mit einer Klasse
ModelViewSet verwenden
URLs automatisch generieren
DefaultRouter nutzen
Browsable API nutzen
Mit Postman/curl testen
DRF ist das mächtigste Tool für REST APIs in Django!
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
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
# Terminal / PowerShell:
pip install djangorestframework
# Output:
Collecting djangorestframework
Successfully installed djangorestframework-3.14.0
# 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
],
}
Django REST Framework ist jetzt einsatzbereit.
Serializers konvertieren Django Models ↔ JSON
# 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']
movie = Movie.objects.get(pk=1)
#
print(movie.title)
# "Inception"
print(movie.rating)
# Decimal('8.8')
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"
# }
# 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']
Eine Klasse für alle CRUD-Operationen (List, Create, Retrieve, Update, Delete)
# 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
# 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)
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
DRF generiert alle URLs automatisch aus ViewSets!
# 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)),
]
# 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
]
# 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
# Terminal:
python manage.py runserver
# Output:
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
Ö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!
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
# 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
# Terminal:
pip install django-filter
# In settings.py INSTALLED_APPS hinzufügen:
'django_filters',
# Suche nach "Matrix" in Titel & Beschreibung
GET /api/movies/?search=Matrix
# Suche nach Künstler
GET /api/artists/?search=DiCaprio
# 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
# 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
# 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
# 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})"
# 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']
# 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
# 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)),
]
# 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
]
# 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',
],
}
~300+ Zeilen Code
~100 Zeilen Code
Token-basierte Authentifizierung
JWT (JSON Web Tokens)
OAuth2
IsAuthenticated
IsAdminUser
Custom Permissions
Rate Limiting
Schutz vor Missbrauch
API Tests schreiben
APITestCase
Coverage
Production Settings
CORS konfigurieren
Dokumentation (Swagger)
Du hast eine vollständige REST API mit Django REST Framework erstellt!
Jetzt kannst du professionelle APIs bauen! 🎉