Workshop Django academique concu pour apprendre a construire une API robuste, securisee et temps reel, de la base CRUD jusqu au deploiement.
- Auteur : Abraham Ricardo Hernandez Sompare
- Supervision : David Grunenwald
Ce README suit un format tutorial pas a pas.
Objectif: partir d un projet Django + DRF et arriver a:
- une API CRUD
Post - une doc Swagger
- un event WebSocket envoye a la creation d un post
Tu peux aussi suivre le cours par modules separes dans docs/:
docs/01-crud-drf.mddocs/02-securite-drf.mddocs/03-auth-jwt-permissions.mddocs/04-websockets-realtime.mddocs/05-admin-wysiwyg-mini-cms.mddocs/06-bonus-qualite-tests-deploiement.md
Index global: docs/README.md
Version enseignant: docs/teacher/README.md
- django-mastery-workshop
- Parcours modulaire
- Sommaire
- Step 0 - Prerequis
- Step 1 - Creer et activer l environnement
- Step 2 - Installer les dependances
- Step 3 - Creer le projet et l app
- Step 4 - Declarer les apps dans settings
- Step 5 - Creer le modele Post
- Step 6 - Generer et appliquer les migrations
- Step 7 - Creer le serializer
- Step 8 - Creer le ViewSet
- Step 9 - Exposer les routes CRUD
- Step 10 - Activer Swagger
- Step 11 - Activer Channels (WebSocket)
- Step 12 - Routing WebSocket
- Step 13 - Consumer WebSocket
- Step 14 - Envoyer un event WS au create
- Step 15 - Tester REST + Swagger + WS
- FAQ courte
- Ressources
- Auteur et supervision
- Python 3.12+
pip- terminal (PowerShell/CMD/bash)
mkdir django-project
cd django-project
python -m venv envActivation:
- Windows PowerShell:
env\Scripts\Activate.ps1- Linux/macOS:
source env/bin/activatepip install django djangorestframework drf-spectacular channels daphnedjango-admin startproject newspaper .
python manage.py startapp blogFichier: newspaper/settings.py
Ajoute/valide:
INSTALLED_APPS = [
"daphne",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"drf_spectacular",
"channels",
"blog",
]Ajoute aussi:
WSGI_APPLICATION = "newspaper.wsgi.application"
ASGI_APPLICATION = "newspaper.asgi.application"
REST_FRAMEWORK = {
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}
SPECTACULAR_SETTINGS = {
"TITLE": "Newspaper API",
"DESCRIPTION": "API CRUD newspaper Django/DRF",
"VERSION": "1.0.0",
}
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels.layers.InMemoryChannelLayer",
}
}Et pour servir les templates de demo:
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "templates"],
"APP_DIRS": True,
# ...
}
]Fichier: blog/models.py
from django.db import models
class Post(models.Model):
STATUS_CHOICES = [
("draft", "Draft"),
("published", "Published"),
]
title = models.CharField(max_length=200)
content = models.TextField(max_length=2000)
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default="draft",
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.titlepython manage.py makemigrations
python manage.py migrateRappel:
makemigrationsgenere les fichiers de migrationmigrateapplique ces migrations en base
Fichier: blog/serializers.py
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = "__all__"Fichier: blog/views.py
from rest_framework.viewsets import ModelViewSet
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from .models import Post
from .serializers import PostSerializer
class PostViewSet(ModelViewSet):
queryset = Post.objects.all().order_by("-created_at")
serializer_class = PostSerializer
def perform_create(self, serializer):
post = serializer.save()
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
"blog_feed",
{
"type": "blog_event",
"data": {
"event": "post_created",
"id": post.id,
"title": post.title,
"status": post.status,
},
},
)Fichier: blog/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import PostViewSet
router = DefaultRouter()
router.register("posts", PostViewSet, basename="post")
urlpatterns = [
path("", include(router.urls)),
]Fichier: newspaper/urls.py
from django.contrib import admin
from django.urls import path, include
from django.views.generic import TemplateView
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
urlpatterns = [
path("admin/", admin.site.urls),
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
path("api/docs/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"),
path("api/", include("blog.urls")),
path("ws-demo/", TemplateView.as_view(template_name="ws_demo.html"), name="ws-demo"),
]Endpoints CRUD disponibles:
GET /api/posts/POST /api/posts/GET /api/posts/{id}/PUT /api/posts/{id}/PATCH /api/posts/{id}/DELETE /api/posts/{id}/
Swagger est deja configure via:
drf_spectaculardansINSTALLED_APPSREST_FRAMEWORK["DEFAULT_SCHEMA_CLASS"]- routes
/api/schema/et/api/docs/
URLs:
- Schema JSON:
http://127.0.0.1:8000/api/schema/ - Swagger UI:
http://127.0.0.1:8000/api/docs/
Channels est deja configure via:
daphneinstalle et present dansINSTALLED_APPS(pour avoir WS avecrunserver)channelsdansINSTALLED_APPSASGI_APPLICATION = "newspaper.asgi.application"CHANNEL_LAYERS(InMemory)
Fichier: newspaper/asgi.py
import os
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
import blog.routing
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "newspaper.settings")
application = ProtocolTypeRouter(
{
"http": get_asgi_application(),
"websocket": URLRouter(blog.routing.websocket_urlpatterns),
}
)Fichier: blog/routing.py
from django.urls import path
from .consumers import BlogConsumer
websocket_urlpatterns = [
path("ws/blog/", BlogConsumer.as_asgi()),
]Fichier: blog/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class BlogConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.group_name = "blog_feed"
await self.channel_layer.group_add(self.group_name, self.channel_name)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard(self.group_name, self.channel_name)
async def blog_event(self, event):
await self.send(text_data=json.dumps(event["data"]))Deja fait dans blog/views.py avec la methode perform_create.
Payload envoye:
{
"event": "post_created",
"id": 1,
"title": "Mon post",
"status": "draft"
}Option A (celle que tu utilises maintenant, avec daphne dans INSTALLED_APPS):
python manage.py runserverOption B (equivalente, en lancant Daphne explicitement):
daphne -b 127.0.0.1 -p 8000 newspaper.asgi:applicationcurl http://127.0.0.1:8000/api/posts/curl -X POST http://127.0.0.1:8000/api/posts/ \
-H "Content-Type: application/json" \
-d "{\"title\":\"Mon post\",\"content\":\"Contenu\",\"status\":\"draft\"}"http://127.0.0.1:8000/api/docs/
- Ouvre
http://127.0.0.1:8000/ws-demo/dans ton navigateur. - Fais un
POST /api/posts/. - Verifie le message recu dans le log.
Important:
- n ouvre pas
ws_demo.htmlenfile://..., sinon tu auras une erreur CORS - la page a utiliser pour le test est
http://127.0.0.1:8000/ws-demo/
Q: Pourquoi j ai "No module named drf_spectacular" ou "No module named channels" ?
- L environnement actif n a pas toutes les deps.
- Refaire:
pip install drf-spectacular channels daphne.
Q: Pourquoi le WS ne recoit rien ?
- Verifie
newspaper/asgi.py(ProtocolTypeRouter) - Verifie
blog/routing.pyet URLws://127.0.0.1:8000/ws/blog/ - Verifie que
perform_createest present dansblog/views.py
Q: Pourquoi j ai 404 /ws/blog/ ?
- Verifie que
daphneest installe. - Verifie que
"daphne"est dansINSTALLED_APPS. - Relance le serveur (
python manage.py runserver).
Q: Pourquoi j ai une erreur CORS dans ws_demo.html ?
- Tu as probablement ouvert le fichier en
file://. - Ouvre la page via Django:
http://127.0.0.1:8000/ws-demo/.
Q: Quelle difference entre WSGI et ASGI ?
- WSGI: HTTP classique
- ASGI: HTTP + protocoles async (ex: WebSocket)
- Django Models: https://docs.djangoproject.com/en/6.0/topics/db/models/
- DRF Serializers: https://www.django-rest-framework.org/api-guide/serializers/
- DRF ViewSets: https://www.django-rest-framework.org/api-guide/viewsets/
- drf-spectacular: https://drf-spectacular.readthedocs.io/
- Django Channels: https://channels.readthedocs.io/
- Auteur: ABRAHAM RICARDO HERNANDEZ SOMPARE
- Concu sous la supervision de David Grunenwald