Cours Django Complet 🐍
Les 12 chapitres essentiels — MVT, ORM, templates, REST API et déploiement
🏠 Hub Programmation
🐍 Python (prérequis)
🎨 HTML & CSS
⚡ JavaScript
☁️ AWS Cloud Practitioner
Introduction et installation
python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
# Installer Django
pip install django
# Créer un projet
django-admin startproject monsite .
# ^ le point crée dans le dossier courant
# Lancer le serveur
python manage.py runserver # → http://127.0.0.1:8000
# Structure :
# monsite/
# ├── manage.py (CLI Django)
# └── monsite/
# ├── __init__.py
# ├── settings.py (configuration)
# ├── urls.py (routing principal)
# ├── asgi.py
# └── wsgi.py (production)
Django est le framework web Python le plus populaire. Créé en 2005, il suit la philosophie « batteries included » : ORM, admin, auth, formulaires, tout est intégré. Utilisé par Instagram, Pinterest, Disqus, Mozilla et Spotify. Architecture MVT (Model-View-Template).
| Caractéristique | Django |
|---|---|
| Architecture | MVT (Model-View-Template) |
| ORM | Intégré (puissant, migrations auto) |
| Admin | Interface d'administration automatique |
| Auth | Système complet (login, register, permissions) |
| Sécurité | CSRF, XSS, SQL injection — protégé par défaut |
| API | Django REST Framework (DRF) |
Projet, apps et structure
python manage.py startapp blog
# Structure de l'app :
# blog/
# ├── __init__.py
# ├── admin.py (config admin)
# ├── apps.py (config app)
# ├── models.py (modèles / DB)
# ├── views.py (logique)
# ├── urls.py (routes — à créer)
# ├── tests.py (tests)
# └── migrations/ (migrations DB)
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Vos apps
'blog',
]
Chaque app = une fonctionnalité réutilisable. Un projet e-commerce pourrait avoir : products, users, orders, payments. Chaque app est autonome avec ses modèles, vues, URLs et templates.
Modèles et ORM
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
slug = models.SlugField(unique=True)
class Meta:
verbose_name_plural = « categories »
def __str__(self):
return self.name
class Post(models.Model):
# Champs
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
content = models.TextField()
excerpt = models.TextField(blank=True)
published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# Relations
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
class Meta:
ordering = ['-created_at']
def __str__(self):
return self.title
python manage.py makemigrations # Génère le fichier de migration
python manage.py migrate # Applique à la DB
| Champ | Type | Options courantes |
|---|---|---|
| CharField | Texte court | max_length (obligatoire) |
| TextField | Texte long | blank=True |
| IntegerField | Entier | default=0 |
| DecimalField | Décimal précis | max_digits, decimal_places |
| BooleanField | Vrai/Faux | default=False |
| DateTimeField | Date + heure | auto_now_add, auto_now |
| SlugField | URL-friendly | unique=True |
| EmailField | Email validé | unique=True |
| ForeignKey | Relation N→1 | on_delete=CASCADE/SET_NULL |
| ManyToManyField | Relation N→N | blank=True |
Vues et URLs
from django.shortcuts import render, get_object_or_404
from .models import Post
def post_list(request):
posts = Post.objects.filter(published=True)
return render(request, 'blog/post_list.html', )
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug, published=True)
return render(request, 'blog/post_detail.html', )
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
path( », views.post_list, name='post_list'),
path('
]
# monsite/urls.py — inclure les URLs de l'app
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')),
]
from django.views.generic import ListView, DetailView, CreateView
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
queryset = Post.objects.filter(published=True)
paginate_by = 10
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
slug_field = 'slug'
# Dans urls.py avec CBV :
path( », PostListView.as_view(), name='post_list'),
FBV vs CBV : les Function-Based Views sont plus simples et explicites. Les Class-Based Views réduisent le code pour les opérations CRUD standard. Recommandation : FBV pour la logique custom, CBV (ListView, DetailView, CreateView, UpdateView, DeleteView) pour le CRUD standard.
Templates
« >
Filtres utiles : , , , , , , .
Requêtes ORM avancées
Post.objects.all() # Tous
Post.objects.get(id=1) # Un seul (lève DoesNotExist si absent)
Post.objects.filter(published=True) # Filtrer
Post.objects.exclude(published=False) # Exclure
Post.objects.first() # Premier
Post.objects.last() # Dernier
Post.objects.count() # Nombre
Post.objects.exists() # Existe ?
# Lookups (double underscore __)
Post.objects.filter(title__contains='Django') # LIKE %Django%
Post.objects.filter(title__icontains='django') # Case-insensitive
Post.objects.filter(created_at__year=2025) # Par année
Post.objects.filter(created_at__gte=date) # ≥ date
Post.objects.filter(category__name='Tech') # Traverser relation
Post.objects.filter(id__in=[1, 2, 3]) # IN
# Chaîner
Post.objects.filter(published=True).order_by('-created_at')[:5]
# Q objects (OR, NOT)
from django.db.models import Q
Post.objects.filter(Q(title__contains='Django') | Q(title__contains='Python'))
# Agrégations
from django.db.models import Count, Avg
Category.objects.annotate(num_posts=Count('post'))
Formulaires
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content', 'category']
widgets =
from django.shortcuts import render, redirect
from .forms import PostForm
def post_create(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
return redirect('blog:post_detail', slug=post.slug)
else:
form = PostForm()
return render(request, 'blog/post_form.html', )
Publier
est obligatoire dans tous les formulaires POST. Django protège automatiquement contre les attaques CSRF (Cross-Site Request Forgery). Sans ce token, le formulaire sera rejeté avec une erreur 403.
Authentification
from django.contrib.auth import views as auth_views
urlpatterns = [
path('login/', auth_views.LoginView.as_view(), name='login'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
]
# settings.py
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'
from django.contrib.auth.decorators import login_required
@login_required
def dashboard(request):
return render(request, 'dashboard.html')
# Protéger une vue (CBV)
from django.contrib.auth.mixins import LoginRequiredMixin
class DashboardView(LoginRequiredMixin, TemplateView):
template_name = 'dashboard.html'
from django.contrib.auth.forms import UserCreationForm
def register(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
return redirect('/')
else:
form = UserCreationForm()
return render(request, 'registration/register.html', )
Django REST Framework
# Ajouter 'rest_framework' dans INSTALLED_APPS
# blog/serializers.py
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source='author.username', read_only=True)
class Meta:
model = Post
fields = ['id', 'title', 'slug', 'content', 'author_name', 'created_at']
from rest_framework import viewsets, permissions
from .serializers import PostSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.filter(published=True)
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('posts', PostViewSet)
urlpatterns = [
path('api/', include(router.urls)),
]
# Endpoints générés automatiquement :
# GET /api/posts/ → Liste
# POST /api/posts/ → Créer
# GET /api/posts/1/ → Détail
# PUT /api/posts/1/ → Modifier
# DELETE /api/posts/1/ → Supprimer
DRF génère un CRUD complet en ~15 lignes (serializer + viewset + router). Il fournit aussi une interface web navigable pour tester l'API, la pagination, le filtrage, et l'authentification par tokens.
Admin
python manage.py createsuperuser
# → http://127.0.0.1:8000/admin/
# blog/admin.py
from django.contrib import admin
from .models import Post, Category
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'category', 'published', 'created_at']
list_filter = ['published', 'category', 'created_at']
search_fields = ['title', 'content']
prepopulated_fields =
list_editable = ['published']
date_hierarchy = 'created_at'
ordering = ['-created_at']
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug']
prepopulated_fields =
L'admin Django est un outil unique. En quelques lignes, vous obtenez une interface complète pour gérer vos données : liste filtrable, recherche, édition, actions en masse. Aucun autre framework ne propose un équivalent aussi complet.
Déploiement
DEBUG = False
ALLOWED_HOSTS = ['monsite.com', 'www.monsite.com'] SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
# Base de données production
# pip install dj-database-url psycopg2-binary
import dj_database_url
DATABASES =
# Fichiers statiques
# pip install whitenoise
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', # Après security
…
]
STATIC_ROOT = BASE_DIR / 'staticfiles'
# Collecter les fichiers statiques
python manage.py collectstatic
pip freeze > requirements.txt
# Gunicorn — serveur WSGI production
pip install gunicorn
gunicorn monsite.wsgi:application –bind 0.0.0.0:8000
| Plateforme | Idéal pour |
|---|---|
| Railway | Déploiement simple (git push), PostgreSQL intégré |
| Render | Free tier, PostgreSQL, auto-deploy |
| Fly.io | Conteneurs Docker, edge deployment |
| AWS / DigitalOcean | VPS, contrôle total, Nginx + Gunicorn |
| PythonAnywhere | Hébergement Python spécialisé, simple |
Bonnes pratiques
├── manage.py
├── monsite/
│ ├── settings/
│ │ ├── base.py # Paramètres communs
│ │ ├── dev.py # DEBUG=True, sqlite
│ │ └── prod.py # DEBUG=False, PostgreSQL
│ ├── urls.py
│ └── wsgi.py
├── blog/
│ ├── models.py
│ ├── views.py
│ ├── urls.py
│ ├── forms.py
│ ├── serializers.py # Si DRF
│ ├── admin.py
│ ├── tests/
│ │ ├── test_models.py
│ │ └── test_views.py
│ └── templates/
│ └── blog/
│ ├── post_list.html
│ └── post_detail.html
├── templates/
│ └── base.html
├── static/
├── requirements.txt
└── .env
✅ À FAIRE
• Apps modulaires et réutilisables
• ModelForm pour les formulaires liés aux modèles
• select_related / prefetch_related (N+1)
• dans tous les forms
• Variables d'env (django-environ)
• get_object_or_404 plutôt que try/except
• Admin personnalisé (list_display, search)
• Tests pour chaque vue et modèle
• whitenoise pour les fichiers statiques
❌ À ÉVITER
• DEBUG=True en production
• SECRET_KEY en dur dans le code
• Logique métier dans les vues (utiliser services)
• Requêtes N+1 (boucle + accès relation)
• Ignorer les migrations
• objects.all() sans pagination
• Modèle User custom trop tard
• ForeignKey sans on_delete
• Templates sans héritage (base.html)
🏠 Hub Programmation
🐍 Cours Python
🎨 Cours HTML & CSS
⚡ Cours JavaScript
☁️ AWS Cloud Practitioner
Cours Django Complet — MVT, ORM, REST API et déploiement
Référence : docs.djangoproject.com | DRF
