Von Django Views zu JSON-Responses
Modul 1: Django Basics - Manuelle API
Django's JSON-Antworten
Python Dict → JSON
Function-Based Views
GET, POST, PUT, DELETE
URL-Patterns definieren
API-Endpunkte erstellen
Complete API implementieren
Error Handling
Verstehen, was Django REST Framework für uns automatisiert!
Eine spezielle HttpResponse, die Python-Dicts automatisch in JSON konvertiert
from django.http import JsonResponse
def hello_api(request):
data = {
'message': 'Hello, API!',
'status': 'success'
}
return JsonResponse(data)
# Response:
# {
# "message": "Hello, API!",
# "status": "success"
# }
# Safe=False für Listen
JsonResponse([1, 2, 3], safe=False)
# Status-Code setzen
JsonResponse(data, status=201)
# Custom Headers
response = JsonResponse(data)
response['X-Custom-Header'] = 'value'
# JSON Encoder
JsonResponse(
data,
json_dumps_params={'indent': 2}
)
FirstMovieAPI/
│
├── movies/
│ ├── models.py # ✅ Models bereits vorhanden
│ ├── views.py # ← Hier erstellen wir API-Views
│ ├── urls.py # ← Neu: API URLs
│ └── utils.py # ← Neu: Helper-Funktionen
│
├── firstmovieapi/
│ ├── settings.py
│ └── urls.py # ← Hier einbinden
│
└── manage.py
Helper-Funktionen für Serialisierung
API-Views für CRUD-Operationen
API-Endpunkte definieren
movies/utils.py (Neu erstellen)"""
Helper-Funktionen für die manuelle API
"""
def movie_to_dict(movie):
"""Konvertiert Movie-Objekt zu Dictionary"""
return {
'id': movie.id,
'title': movie.title,
'year': movie.year,
'genre': movie.genre,
'rating': float(movie.rating) if movie.rating else None,
'description': movie.description,
'created_at': movie.created_at.isoformat(),
'updated_at': movie.updated_at.isoformat(),
}
def artist_to_dict(artist):
"""Konvertiert Artist-Objekt zu Dictionary"""
return {
'id': artist.id,
'first_name': artist.first_name,
'last_name': artist.last_name,
'full_name': artist.full_name,
'birth_date': artist.birth_date.isoformat() if artist.birth_date else None,
'nationality': artist.nationality,
'biography': artist.biography,
'created_at': artist.created_at.isoformat(),
'updated_at': artist.updated_at.isoformat(),
}
def casting_to_dict(casting):
"""Konvertiert MovieCasting-Objekt zu Dictionary"""
return {
'id': casting.id,
'movie': movie_to_dict(casting.movie),
'artist': artist_to_dict(casting.artist),
'role_name': casting.role_name,
'is_main_role': casting.is_main_role,
'order': casting.order,
'created_at': casting.created_at.isoformat(),
}
from django.http import JsonResponse
from .models import Movie
def movie_list(request):
movies = Movie.objects.all()
# ❌ TypeError!
return JsonResponse(movies)
# Error:
# TypeError: Object of type QuerySet
# is not JSON serializable
from django.http import JsonResponse
from .models import Movie
from .utils import movie_to_dict
def movie_list(request):
movies = Movie.objects.all()
# Manuell konvertieren
data = [movie_to_dict(m) for m in movies]
return JsonResponse(data, safe=False)
# Response: ✅ JSON Array!
movies/views.pyfrom django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from .models import Movie
from .utils import movie_to_dict
@require_http_methods(["GET"])
def movie_list(request):
"""
GET /api/movies/ - Liste aller Filme
"""
# Alle Filme aus DB holen
movies = Movie.objects.all()
# In Dictionaries konvertieren
data = [movie_to_dict(movie) for movie in movies]
# Als JSON zurückgeben
return JsonResponse(data, safe=False)
# Response Beispiel:
# [
# {
# "id": 1,
# "title": "The Matrix",
# "year": 1999,
# "genre": "Sci-Fi",
# "rating": 8.7,
# "description": "...",
# "created_at": "2024-11-09T10:30:00Z",
# "updated_at": "2024-11-09T10:30:00Z"
# },
# {
# "id": 2,
# "title": "Inception",
# ...
# }
# ]
from django.http import JsonResponse, Http404
from django.views.decorators.http import require_http_methods
from .models import Movie
from .utils import movie_to_dict
@require_http_methods(["GET"])
def movie_detail(request, pk):
"""
GET /api/movies// - Ein bestimmter Film
"""
try:
# Film mit ID holen
movie = Movie.objects.get(pk=pk)
except Movie.DoesNotExist:
# 404 wenn nicht gefunden
return JsonResponse(
{'error': 'Movie not found'},
status=404
)
# In Dictionary konvertieren
data = movie_to_dict(movie)
# Als JSON zurückgeben
return JsonResponse(data)
# Request:
# GET /api/movies/1/
# Response (200 OK):
# {
# "id": 1,
# "title": "The Matrix",
# "year": 1999,
# "genre": "Sci-Fi",
# "rating": 8.7,
# ...
# }
# Request:
# GET /api/movies/999/
# Response (404 Not Found):
# {
# "error": "Movie not found"
# }
Django parst das nicht automatisch!
import json
from django.http import JsonResponse
def parse_json_body(request):
"""
Parst JSON aus request.body
Gibt (data, error_response) zurück
"""
try:
# Body ist Bytes → String → JSON
body = request.body.decode('utf-8')
data = json.loads(body)
return data, None
except json.JSONDecodeError:
# Ungültiges JSON
error = JsonResponse(
{'error': 'Invalid JSON'},
status=400
)
return None, error
except Exception as e:
# Anderer Fehler
error = JsonResponse(
{'error': str(e)},
status=400
)
return None, error
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_exempt
from .models import Movie
from .utils import movie_to_dict, parse_json_body
@csrf_exempt # ⚠️ Nur für Entwicklung! Später CSRF-Token nutzen
@require_http_methods(["POST"])
def movie_create(request):
"""
POST /api/movies/ - Neuen Film erstellen
Body:
{
"title": "New Movie",
"year": 2024,
"genre": "Action",
"rating": 8.5,
"description": "..."
}
"""
# JSON parsen
data, error = parse_json_body(request)
if error:
return error
# Validierung
required_fields = ['title', 'year']
for field in required_fields:
if field not in data:
return JsonResponse(
{'error': f'{field} is required'},
status=400
)
try:
# Movie erstellen
movie = Movie.objects.create(
title=data['title'],
year=data['year'],
genre=data.get('genre', ''),
rating=data.get('rating'),
description=data.get('description', '')
)
# Als JSON zurückgeben (201 Created)
return JsonResponse(
movie_to_dict(movie),
status=201
)
except Exception as e:
return JsonResponse(
{'error': str(e)},
status=400
)
Cross-Site Request Forgery - Schutz vor böswilligen Requests
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def movie_create(request):
# Kein CSRF-Check
# NUR für Entwicklung!
# NICHT in Production!
Deaktiviert CSRF-Schutz komplett
# Im Frontend: Token holen
const token = getCookie('csrftoken');
fetch('/api/movies/', {
method: 'POST',
headers: {
'X-CSRFToken': token,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
# Django prüft Token automatisch
Sicher für Production!
Wir nutzen @csrf_exempt für Einfachheit. In echten Apps: CSRF-Token nutzen!
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
@csrf_exempt
@require_http_methods(["PUT"])
def movie_update(request, pk):
"""
PUT /api/movies// - Film aktualisieren
Body:
{
"title": "Updated Title",
"year": 2024,
"genre": "Drama",
"rating": 9.0,
"description": "Updated..."
}
"""
# Film holen
try:
movie = Movie.objects.get(pk=pk)
except Movie.DoesNotExist:
return JsonResponse(
{'error': 'Movie not found'},
status=404
)
# JSON parsen
data, error = parse_json_body(request)
if error:
return error
# Felder aktualisieren
movie.title = data.get('title', movie.title)
movie.year = data.get('year', movie.year)
movie.genre = data.get('genre', movie.genre)
movie.rating = data.get('rating', movie.rating)
movie.description = data.get('description', movie.description)
try:
movie.save()
return JsonResponse(movie_to_dict(movie))
except Exception as e:
return JsonResponse(
{'error': str(e)},
status=400
)
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
@csrf_exempt
@require_http_methods(["DELETE"])
def movie_delete(request, pk):
"""
DELETE /api/movies// - Film löschen
"""
# Film holen
try:
movie = Movie.objects.get(pk=pk)
except Movie.DoesNotExist:
return JsonResponse(
{'error': 'Movie not found'},
status=404
)
# Löschen
movie.delete()
# 204 No Content (kein Body!)
return JsonResponse({}, status=204)
# Request:
# DELETE /api/movies/1/
# Response:
# Status: 204 No Content
# Body: (leer)
# Nochmal:
# DELETE /api/movies/1/
# Response:
# Status: 404 Not Found
# {
# "error": "Movie not found"
# }
movies/urls.py (Neu erstellen)from django.urls import path
from . import views
urlpatterns = [
# Movie API Endpoints
path('movies/', views.movie_list, name='movie-list'),
path('movies/create/', views.movie_create, name='movie-create'),
path('movies//', views.movie_detail, name='movie-detail'),
path('movies//update/', views.movie_update, name='movie-update'),
path('movies//delete/', views.movie_delete, name='movie-delete'),
]
# Endpunkte:
# GET /api/movies/ → Liste aller Filme
# POST /api/movies/create/ → Neuen Film erstellen
# GET /api/movies/1/ → Film #1 abrufen
# PUT /api/movies/1/update/ → Film #1 aktualisieren
# DELETE /api/movies/1/delete/ → Film #1 löschen
firstmovieapi/urls.pyfrom django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
# API Endpoints
path('api/', include('movies.urls')),
]
# Alle API-Endpunkte sind jetzt unter /api/ erreichbar:
# GET /api/movies/
# POST /api/movies/create/
# GET /api/movies/1/
# PUT /api/movies/1/update/
# DELETE /api/movies/1/delete/
Jetzt können wir testen
python manage.py runserver
Server läuft auf http://127.0.0.1:8000/
Öffne: http://127.0.0.1:8000/api/movies/
Siehst du JSON? ✅
# Liste abrufen
curl http://127.0.0.1:8000/api/movies/
# Einen Film abrufen
curl http://127.0.0.1:8000/api/movies/1/
# Neuen Film erstellen
curl -X POST http://127.0.0.1:8000/api/movies/create/ \
-H "Content-Type: application/json" \
-d '{"title":"New Movie","year":2024}'
# Film aktualisieren
curl -X PUT http://127.0.0.1:8000/api/movies/1/update/ \
-H "Content-Type: application/json" \
-d '{"title":"Updated Title","year":2024}'
# Film löschen
curl -X DELETE http://127.0.0.1:8000/api/movies/1/delete/
GET http://127.0.0.1:8000/api/movies/
Headers: (keine nötig)
Response: 200 OK
[
{
"id": 1,
"title": "The Matrix",
...
}
]
POST http://127.0.0.1:8000/api/movies/create/
Headers:
Content-Type: application/json
Body (raw JSON):
{
"title": "Interstellar",
"year": 2014,
"genre": "Sci-Fi",
"rating": 8.6,
"description": "A team of explorers..."
}
Response: 201 Created
{
"id": 3,
"title": "Interstellar",
...
}
PUT http://127.0.0.1:8000/api/movies/3/update/
Headers:
Content-Type: application/json
Body (raw JSON):
{
"title": "Interstellar (IMAX)",
"year": 2014,
"rating": 9.0
}
Response: 200 OK
{
"id": 3,
"title": "Interstellar (IMAX)",
...
}
DELETE http://127.0.0.1:8000/api/movies/3/delete/
Headers: (keine nötig)
Response: 204 No Content
(Kein Body)
"""
movies/views.py - Manuelle REST API Views
"""
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_exempt
from .models import Movie
from .utils import movie_to_dict, parse_json_body
@require_http_methods(["GET"])
def movie_list(request):
"""GET /api/movies/ - Liste aller Filme"""
movies = Movie.objects.all()
data = [movie_to_dict(movie) for movie in movies]
return JsonResponse(data, safe=False)
@require_http_methods(["GET"])
def movie_detail(request, pk):
"""GET /api/movies// - Ein bestimmter Film"""
try:
movie = Movie.objects.get(pk=pk)
return JsonResponse(movie_to_dict(movie))
except Movie.DoesNotExist:
return JsonResponse({'error': 'Movie not found'}, status=404)
@csrf_exempt
@require_http_methods(["POST"])
def movie_create(request):
"""POST /api/movies/create/ - Neuen Film erstellen"""
data, error = parse_json_body(request)
if error:
return error
required = ['title', 'year']
for field in required:
if field not in data:
return JsonResponse({'error': f'{field} is required'}, status=400)
try:
movie = Movie.objects.create(
title=data['title'],
year=data['year'],
genre=data.get('genre', ''),
rating=data.get('rating'),
description=data.get('description', '')
)
return JsonResponse(movie_to_dict(movie), status=201)
except Exception as e:
return JsonResponse({'error': str(e)}, status=400)
@csrf_exempt
@require_http_methods(["PUT"])
def movie_update(request, pk):
"""PUT /api/movies//update/ - Film aktualisieren"""
try:
movie = Movie.objects.get(pk=pk)
except Movie.DoesNotExist:
return JsonResponse({'error': 'Movie not found'}, status=404)
data, error = parse_json_body(request)
if error:
return error
movie.title = data.get('title', movie.title)
movie.year = data.get('year', movie.year)
movie.genre = data.get('genre', movie.genre)
movie.rating = data.get('rating', movie.rating)
movie.description = data.get('description', movie.description)
try:
movie.save()
return JsonResponse(movie_to_dict(movie))
except Exception as e:
return JsonResponse({'error': str(e)}, status=400)
@csrf_exempt
@require_http_methods(["DELETE"])
def movie_delete(request, pk):
"""DELETE /api/movies//delete/ - Film löschen"""
try:
movie = Movie.objects.get(pk=pk)
movie.delete()
return JsonResponse({}, status=204)
except Movie.DoesNotExist:
return JsonResponse({'error': 'Movie not found'}, status=404)
"""
movies/utils.py - Helper-Funktionen für manuelle API
"""
import json
from django.http import JsonResponse
def movie_to_dict(movie):
"""Konvertiert Movie-Objekt zu Dictionary"""
return {
'id': movie.id,
'title': movie.title,
'year': movie.year,
'genre': movie.genre,
'rating': float(movie.rating) if movie.rating else None,
'description': movie.description,
'created_at': movie.created_at.isoformat(),
'updated_at': movie.updated_at.isoformat(),
}
def artist_to_dict(artist):
"""Konvertiert Artist-Objekt zu Dictionary"""
return {
'id': artist.id,
'first_name': artist.first_name,
'last_name': artist.last_name,
'full_name': artist.full_name,
'birth_date': artist.birth_date.isoformat() if artist.birth_date else None,
'nationality': artist.nationality,
'biography': artist.biography,
'created_at': artist.created_at.isoformat(),
'updated_at': artist.updated_at.isoformat(),
}
def casting_to_dict(casting):
"""Konvertiert MovieCasting-Objekt zu Dictionary"""
return {
'id': casting.id,
'movie': movie_to_dict(casting.movie),
'artist': artist_to_dict(casting.artist),
'role_name': casting.role_name,
'is_main_role': casting.is_main_role,
'order': casting.order,
'created_at': casting.created_at.isoformat(),
}
def parse_json_body(request):
"""
Parst JSON aus request.body
Returns: (data, error_response)
"""
try:
body = request.body.decode('utf-8')
data = json.loads(body)
return data, None
except json.JSONDecodeError:
error = JsonResponse({'error': 'Invalid JSON'}, status=400)
return None, error
except Exception as e:
error = JsonResponse({'error': str(e)}, status=400)
return None, error
from .utils import artist_to_dict
@require_http_methods(["GET"])
def artist_list(request):
"""GET /api/artists/ - Liste aller Künstler"""
artists = Artist.objects.all()
data = [artist_to_dict(artist) for artist in artists]
return JsonResponse(data, safe=False)
@require_http_methods(["GET"])
def artist_detail(request, pk):
"""GET /api/artists// - Ein bestimmter Künstler"""
try:
artist = Artist.objects.get(pk=pk)
return JsonResponse(artist_to_dict(artist))
except Artist.DoesNotExist:
return JsonResponse({'error': 'Artist not found'}, status=404)
@csrf_exempt
@require_http_methods(["POST"])
def artist_create(request):
"""POST /api/artists/create/ - Neuen Künstler erstellen"""
data, error = parse_json_body(request)
if error:
return error
required = ['first_name', 'last_name']
for field in required:
if field not in data:
return JsonResponse({'error': f'{field} is required'}, status=400)
try:
from datetime import datetime
birth_date = None
if 'birth_date' in data:
birth_date = datetime.fromisoformat(data['birth_date']).date()
artist = Artist.objects.create(
first_name=data['first_name'],
last_name=data['last_name'],
birth_date=birth_date,
nationality=data.get('nationality', ''),
biography=data.get('biography', '')
)
return JsonResponse(artist_to_dict(artist), status=201)
except Exception as e:
return JsonResponse({'error': str(e)}, status=400)
urlpatterns = [
# ... Movie URLs ...
# Artist API Endpoints
path('artists/', views.artist_list, name='artist-list'),
path('artists/create/', views.artist_create, name='artist-create'),
path('artists//', views.artist_detail, name='artist-detail'),
]
def error_response(message, status=400):
"""Standard Error Response"""
return JsonResponse({'error': message}, status=status)
def validation_error_response(errors):
"""Validation Error Response mit Details"""
return JsonResponse({'errors': errors}, status=400)
def not_found_response(resource='Resource'):
"""404 Response"""
return JsonResponse(
{'error': f'{resource} not found'},
status=404
)
from .utils import error_response, not_found_response, validation_error_response
def movie_detail(request, pk):
try:
movie = Movie.objects.get(pk=pk)
return JsonResponse(movie_to_dict(movie))
except Movie.DoesNotExist:
return not_found_response('Movie')
def movie_create(request):
data, error = parse_json_body(request)
if error:
return error
# Validierung
errors = {}
if 'title' not in data:
errors['title'] = 'This field is required'
if 'year' not in data:
errors['year'] = 'This field is required'
elif not isinstance(data['year'], int):
errors['year'] = 'Must be an integer'
if errors:
return validation_error_response(errors)
# ... erstellen ...
@require_http_methods(["GET"])
def movie_list(request):
"""
GET /api/movies/ - Liste aller Filme
Query-Parameters:
- year: Filme aus bestimmtem Jahr
- genre: Filme eines Genres
- search: Suche im Titel
- ordering: Sortierung (-year, title, rating)
"""
# Alle Filme
movies = Movie.objects.all()
# Filter: Jahr
year = request.GET.get('year')
if year:
movies = movies.filter(year=year)
# Filter: Genre
genre = request.GET.get('genre')
if genre:
movies = movies.filter(genre__icontains=genre)
# Search: Titel
search = request.GET.get('search')
if search:
movies = movies.filter(title__icontains=search)
# Ordering
ordering = request.GET.get('ordering', '-year')
movies = movies.order_by(ordering)
# Konvertieren
data = [movie_to_dict(movie) for movie in movies]
return JsonResponse(data, safe=False)
# Beispiele:
# /api/movies/?year=1999
# /api/movies/?genre=Sci-Fi
# /api/movies/?search=matrix
# /api/movies/?ordering=title
# /api/movies/?year=2010&genre=Action&ordering=-rating
from django.core.paginator import Paginator, EmptyPage
@require_http_methods(["GET"])
def movie_list(request):
"""
GET /api/movies/?page=1&page_size=10
"""
# Alle Filme
movies = Movie.objects.all()
# ... Filter anwenden ...
# Pagination
page_size = int(request.GET.get('page_size', 20))
page_number = int(request.GET.get('page', 1))
paginator = Paginator(movies, page_size)
try:
page_obj = paginator.get_page(page_number)
except EmptyPage:
return JsonResponse({'error': 'Page not found'}, status=404)
# Response mit Meta-Daten
data = {
'count': paginator.count,
'page': page_number,
'page_size': page_size,
'total_pages': paginator.num_pages,
'next': page_obj.has_next() and page_obj.next_page_number() or None,
'previous': page_obj.has_previous() and page_obj.previous_page_number() or None,
'results': [movie_to_dict(movie) for movie in page_obj]
}
return JsonResponse(data)
# Response:
# {
# "count": 150,
# "page": 1,
# "page_size": 20,
# "total_pages": 8,
# "next": 2,
# "previous": null,
# "results": [...]
# }
def movie_to_dict_detailed(movie):
"""Movie mit allen Castings"""
return {
'id': movie.id,
'title': movie.title,
'year': movie.year,
'genre': movie.genre,
'rating': float(movie.rating) if movie.rating else None,
'description': movie.description,
'created_at': movie.created_at.isoformat(),
'updated_at': movie.updated_at.isoformat(),
# Nested Castings
'castings': [
{
'id': casting.id,
'artist': {
'id': casting.artist.id,
'full_name': casting.artist.full_name,
},
'role_name': casting.role_name,
'is_main_role': casting.is_main_role,
'order': casting.order,
}
for casting in movie.castings.all()
]
}
@require_http_methods(["GET"])
def movie_detail_with_castings(request, pk):
"""GET /api/movies//full/ - Film mit allen Castings"""
try:
movie = Movie.objects.prefetch_related('castings__artist').get(pk=pk)
return JsonResponse(movie_to_dict_detailed(movie))
except Movie.DoesNotExist:
return not_found_response('Movie')
# Response:
# {
# "id": 1,
# "title": "The Matrix",
# "year": 1999,
# ...
# "castings": [
# {
# "id": 1,
# "artist": {
# "id": 1,
# "full_name": "Keanu Reeves"
# },
# "role_name": "Neo",
# "is_main_role": true,
# "order": 1
# },
# ...
# ]
# }
# Für jedes Model:
- movie_to_dict()
- movie_list()
- movie_detail()
- movie_create()
- movie_update()
- movie_delete()
# Artist: Alles nochmal!
# MovieCasting: Alles nochmal!
→ 100+ Zeilen Code pro Model!
# Vergessen zu validieren?
# Falscher Status-Code?
# Fehler nicht abgefangen?
# Edge Cases übersehen?
→ Viele potenzielle Bugs!
# Jeder Entwickler macht es anders
# Keine konsistente Error-Struktur
# Keine automatische Dokumentation
# Keine Versionierung
→ Schwer wartbar!
# N+1 Queries
# Keine automatische Optimierung
# Manuelles select_related nötig
→ Langsam bei vielen Daten!
# Keine Auto-Dokumentation (Swagger)
# Kein Browsable API
# Keine Permissions-System
# Keine Authentication
# Kein Throttling
→ Alles manuell bauen!
# 10 Models = 600+ Zeilen Code
# Jede Änderung = Viel Arbeit
# Tests für alles schreiben
→ Nicht skalierbar!
# views.py (60+ Zeilen)
def movie_list(request):
movies = Movie.objects.all()
data = [movie_to_dict(m) for m in movies]
return JsonResponse(data, safe=False)
def movie_detail(request, pk):
try:
movie = Movie.objects.get(pk=pk)
return JsonResponse(movie_to_dict(movie))
except Movie.DoesNotExist:
return JsonResponse({'error': '...'}, status=404)
def movie_create(request):
data, error = parse_json_body(request)
if error: return error
# ... Validierung ...
# ... Erstellen ...
# ... 15+ Zeilen ...
# ... update, delete ...
# utils.py (30+ Zeilen)
def movie_to_dict(movie):
return {
'id': movie.id,
'title': movie.title,
# ... 10+ Zeilen ...
}
# urls.py (10+ Zeilen)
urlpatterns = [
path('movies/', ...),
path('movies/create/', ...),
# ...
]
# = 100+ Zeilen Code
# × 3 Models = 300+ Zeilen!
# serializers.py (10 Zeilen)
from rest_framework import serializers
from .models import Movie
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
# views.py (5 Zeilen)
from rest_framework import viewsets
class MovieViewSet(viewsets.ModelViewSet):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
# urls.py (5 Zeilen)
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('movies', MovieViewSet)
urlpatterns = router.urls
# = 20 Zeilen Code
# × 3 Models = 60 Zeilen!
# + Automatisch:
# - Browsable API
# - Swagger Docs
# - Permissions
# - Pagination
# - Filtering
# - Validation
# - Error Handling
# Manuell:
def movie_to_dict(movie):
return {
'id': movie.id,
'title': movie.title,
# ... 10 Zeilen ...
}
# DRF:
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
# ✅ Automatisch!
# Manuell:
def movie_list(request): ... # 10 Zeilen
def movie_detail(request, pk): ... # 10 Zeilen
def movie_create(request): ... # 20 Zeilen
def movie_update(request, pk): ... # 20 Zeilen
def movie_delete(request, pk): ... # 10 Zeilen
# DRF:
class MovieViewSet(viewsets.ModelViewSet):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
# ✅ Alle 5 Operationen automatisch!
# Manuell:
if 'title' not in data:
return JsonResponse({'error': '...'}, status=400)
if not isinstance(data['year'], int):
return JsonResponse({'error': '...'}, status=400)
# ... 20+ Zeilen ...
# DRF:
# ✅ Automatisch durch Serializer!
# ✅ Basierend auf Model-Definition!
# Manuell:
try:
movie = Movie.objects.get(pk=pk)
except Movie.DoesNotExist:
return JsonResponse({'error': '...'}, status=404)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
# DRF:
# ✅ Automatisch!
# ✅ Konsistente Error-Struktur!
# Manuell:
# ❌ Nur JSON
# ❌ Kein UI
# DRF:
# ✅ Interaktives Web-Interface!
# ✅ Formulare zum Testen!
# ✅ API-Dokumentation!
# Manuell:
# ❌ Keine Auto-Dokumentation
# ❌ Manuell schreiben
# DRF:
pip install drf-yasg
# ✅ Automatische Swagger-Docs!
# ✅ OpenAPI 3.0 Schema!
# Manuell:
paginator = Paginator(movies, 20)
page = paginator.get_page(page_number)
data = {
'count': paginator.count,
'results': [...]
}
# DRF:
class MovieViewSet(viewsets.ModelViewSet):
pagination_class = PageNumberPagination
# ✅ Automatisch!
# Manuell:
if request.GET.get('year'):
movies = movies.filter(year=...)
if request.GET.get('search'):
movies = movies.filter(title__icontains=...)
# ... 20+ Zeilen ...
# DRF:
class MovieViewSet(viewsets.ModelViewSet):
filter_backends = [filters.SearchFilter]
search_fields = ['title', 'description']
# ✅ Automatisch!