- Pourquoi personnaliser la gestion des utilisateurs avec AbstractUser ?
- Démarrage du projet 'django_custom_user' et de l’application principale
- Le modèle utilisateur CustomUser (héritage d’AbstractUser)
- Les formulaires (création et mise à jour de profil)
- Vues d’authentification et de profil
- Réglages settings.py (statique, médias, auth)
- Routage (URLs projet + app)
- Templates (structure et exemples minimaux)
- Intégration à l’admin (facultative mais recommandée)
- Messages & sécurité
- Gestion des médias (photos de profil)
- Tests fonctionnels rapides
- Petites améliorations possibles
Nous donnons ici un cours complet et progressif pour mettre en place un système d'authentification Django avec un modèle utilisateur personnalisé basé sur la classe AbstractUser, tout en fournissant une explication détaillé du contenu des composant : modèle, CustomUser, vues, URLs, settings.py ainsi que la structure et contenu des fichiers de templates. .etc
1. Pourquoi personnaliser la gestion des utilisateurs avec AbstractUser ?
Django fournit un modèle User par défaut. Lorsque vous avez besoin d’ajouter des champs (téléphone, photo, adresse…), hériter de AbstractUser est la solution la plus simple : vous conservez toutes les fonctionnalités (authentification, permissions, staff, superuser, etc.) tout en ajoutant vos propres champs. C’est exactement votre choix ici avec CustomUser.
2. Démarrage du projet 'django_custom_user' et de l’application principale
1 2 3 4 |
# Exécutez en ordre les commandes suivantes: django-admin startproject django_custom_user cd django_custom_user python manage.py startapp accounts |
Enregistrement de l'application au niveau du fichier settings.py
Activez l’application dans INSTALLED_APPS et déclarez le modèle utilisateur personnalisé avant toute migration (voir §6).
1 2 3 4 |
INSTALLED_APPS = [ ##########, "accounts", ] |
3. Le modèle utilisateur CustomUser (héritage d’AbstractUser)
Dans accounts/models.py, nous faisant un héritage de la classe AbstractUser afin de pouvoir créer la classe CustomUser(AbstractUser) permettant de gérer les utilisateurs d'une façon personnalisée tout ajoutant les champs souhaités comme: phone_number, profile_picture...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
from django.contrib.auth.models import AbstractUser from django.db import models class CustomUser(AbstractUser): phone_number = models.CharField(max_length=15, blank=True, verbose_name="Téléphone") profile_picture = models.ImageField(upload_to='profiles/', blank=True, null=True, verbose_name="Photo de profil") date_of_birth = models.DateField(blank=True, null=True, verbose_name="Date de naissance") address = models.TextField(blank=True, verbose_name="Adresse") # Eviter les conflits de related_name avec le modèle parent groups = models.ManyToManyField( 'auth.Group', verbose_name='groups', blank=True, help_text='The groups this user belongs to.', related_name='customuser_set', related_query_name='user', ) user_permissions = models.ManyToManyField( 'auth.Permission', verbose_name='user permissions', blank=True, help_text='Specific permissions for this user.', related_name='customuser_set', related_query_name='user', ) def __str__(self): return self.email if self.email else self.username |
Points clés :
- Les champs personnalisés: (phone_number, profile_picture, date_of_birth, address) complètent ceux d’AbstractUser.
- Le changement de related_name : pour groups et user_permissions évite des collisions avec le modèle parent, bonne pratique courante lorsqu’on réécrit ces relations.
- La méthode __str__ : privilégie l’email s’il existe (sinon le username).
4. Les formulaires (création et mise à jour de profil)
Vous devez à ce moment là créer un fichier "accounts/forms.py" dont le but de personnaliser les champs du formulaire d'enregistrement des utilisateurs. Vous devez créer deux classes:
- CustomUserCreationForm : Classe qui hérite de la classe UserCreationForm pour l'enregistrement des utilisateur avec des champs personnalisés.
- ProfileUpdateForm : Classe qui hérite de la classe models.ModelForm pour la mise à jour des données de l'utilisateur
accounts/forms.py :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
from django import forms from django.contrib.auth.forms import UserCreationForm from .models import CustomUser class CustomUserCreationForm(UserCreationForm): email = forms.EmailField(required=True, label="Adresse email") class Meta: model = CustomUser fields = ( 'username', 'email', 'phone_number', 'profile_picture', 'first_name', 'last_name', 'date_of_birth', 'address') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['email'].required = True class ProfileUpdateForm(forms.ModelForm): class Meta: model = CustomUser fields = ( 'username', 'email', 'phone_number', 'profile_picture', 'first_name', 'last_name', 'date_of_birth', 'address') widgets = { 'date_of_birth': forms.DateInput(attrs={'type': 'date'}), 'address': forms.Textarea(attrs={'rows': 3}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['email'].required = True |
Ces formulaires correspondent aux vues SignUpView et ProfileUpdateView que vous devez définir au niveau du fichier "accounts/views.py"
5. Vues d’authentification et de profil
Afin de faciliter et automatiser les tâches, vous devez opter au vues génériques (generic view). Vous devez ainsi créer les classes : CustomLoginView, CustomLogoutView, SignUpView, ProfileView, ProfileUpdateView .etc
5.1 class CustomLoginView(LoginView)
- Héritage : Hérite de LoginView (vue générique Django pour la connexion)
- Fonction : Gère le processus d'authentification des utilisateurs
- Personnalisation :
- template_name : Spécifie le template HTML personnalisé pour la page de login
- form_valid() : Surchargée pour ajouter un message de bienvenue personnalisé après connexion réussie
5.2 class CustomLogoutView(LogoutView)
- Héritage : Hérite de LogoutView (vue générique Django pour la déconnexion)
- Fonction : Gère la déconnexion sécurisée des utilisateurs
- Personnalisation :
- dispatch() : Surchargée pour afficher un message de confirmation seulement si l'utilisateur était authentifié
5.3 class SignUpView(CreateView)
- Héritage : Hérite de CreateView (vue générique pour la création d'objets)
- Fonction : Crée de nouveaux comptes utilisateurs
- Configuration :
- form_class : Utilise un formulaire personnalisé CustomUserCreationForm
- success_url : Redirige vers la page de login après inscription réussie
- form_valid() : Ajoute un message de succès après création du compte
5.4 class ProfileView(LoginRequiredMixin, TemplateView)
- Héritage : Hérite de TemplateView + LoginRequiredMixin
- Fonction : Affiche le profil de l'utilisateur connecté
- Sécurité : LoginRequiredMixin empêche l'accès aux utilisateurs non authentifiés
- get_context_data() : Injecte les données de l'utilisateur dans le contexte du template
5.5 class ProfileUpdateView(LoginRequiredMixin, UpdateView)
- Héritage : Hérite de UpdateView + LoginRequiredMixin
- Fonction : Permet la modification du profil utilisateur
- Comportement :
- get_object() : Retourne l'utilisateur connecté comme objet à modifier
- form_class : Utilise ProfileUpdateForm pour limiter les champs modifiables
- form_valid() : Message de confirmation après mise à jour réussie
5.6 Fonctionnalités avancées
- Système de messages : Feedback utilisateur via messages.success() et messages.info()
- Sécurité : Protection des vues avec LoginRequiredMixin
- URLs dynamiques : reverse_lazy() pour éviter les imports circulairesPersonnalisation : Adaptation des vues génériques de Django aux besoins spécifiques
Fichier "accounts/views.py"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
from django.views.generic import CreateView, UpdateView, TemplateView from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.views import LoginView, LogoutView from django.urls import reverse_lazy from django.contrib import messages from .models import CustomUser from .forms import CustomUserCreationForm, ProfileUpdateForm class CustomLoginView(LoginView): template_name = 'registration/login.html' def form_valid(self, form): messages.success(self.request, f"Bienvenue {form.get_user().username} !") return super().form_valid(form) class CustomLogoutView(LogoutView): def dispatch(self, request, *args, **kwargs): if request.user.is_authenticated: messages.info(request, "Vous avez été déconnecté avec succès.") return super().dispatch(request, *args, **kwargs) class SignUpView(CreateView): form_class = CustomUserCreationForm success_url = reverse_lazy('login') template_name = 'registration/signup.html' def form_valid(self, form): messages.success(self.request, "Compte créé avec succès ! Vous pouvez maintenant vous connecter.") return super().form_valid(form) class ProfileView(LoginRequiredMixin, TemplateView): template_name = 'registration/profile.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['user_profile'] = self.request.user return context class ProfileUpdateView(LoginRequiredMixin, UpdateView): model = CustomUser form_class = ProfileUpdateForm template_name = 'registration/profile_edit.html' success_url = reverse_lazy('profile') def get_object(self): return self.request.user def form_valid(self, form): messages.success(self.request, "Profil mis à jour avec succès !") return super().form_valid(form) |
6. Réglages settings.py (statique, médias, auth)
Mise à jour du fichier settings.py :
1 2 3 4 5 6 7 8 9 10 11 12 13 |
STATIC_URL = 'static/' STATICFILES_DIRS = [BASE_DIR / 'static'] MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media' # Auth LOGIN_REDIRECT_URL = 'profile' LOGOUT_REDIRECT_URL = 'login' LOGIN_URL = 'login' # Modèle utilisateur personnalisé AUTH_USER_MODEL = 'accounts.CustomUser' |
- STATICFILES_DIRS : permet de servir vos assets de développement (CSS/JS/images) depuis BASE_DIR/static.
- MEDIA_URL et MEDIA_ROOT : configurent l’upload utilisateur (ex. photos de profil).
- LOGIN_REDIRECT_URL et LOGOUT_REDIRECT_URL : Fixent les redirections après login/logout.
- AUTH_USER_MODEL : Demande à django d'utiliser un système d'authentification basé sur le modèle CustomUser. Cette commande doit être défini avant makemigrations/migrate.
7. Routage (URLs projet + app)
django_custom_user/accounts/urls.py
1 2 3 4 5 6 7 8 9 10 |
from django.urls import path from . import views urlpatterns = [ path('login/', views.CustomLoginView.as_view(), name='login'), path('logout/', views.CustomLogoutView.as_view(), name='logout'), path('signup/', views.SignUpView.as_view(), name='signup'), path('profile/', views.ProfileView.as_view(), name='profile'), path('profile/edit/', views.ProfileUpdateView.as_view(), name='profile_edit'), ] |
django_custom_user/urls.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from django.contrib import admin from django.urls import path, include from django.views.generic import TemplateView from django.conf import settings from django.conf.urls.static import static urlpatterns = [ path("admin/", admin.site.urls), path('', TemplateView.as_view(template_name="index.html")), path('accounts/', include('accounts.urls')), ] if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) |
8. Templates (structure et exemples minimaux)
Arborescence du répertoire templates:
1 2 3 4 5 6 7 8 |
templates/ base.html index.html registration/ login.html signup.html profile.html profile_edit.html |
Exemples très succincts (à adapter à votre charte):
templates/base.html
<!doctype html> <html lang="fr"> <head> <meta charset="utf-8" /> <title>{% block title %}Site{% endblock %}</title> <link rel="stylesheet" href="{% static 'css/site.css' %}"> </head> <body> {% if messages %} <ul class="messages"> {% for m in messages %}<li class="{{ m.tags }}">{{ m }}</li>{% endfor %} </ul> {% endif %} <nav> {% if user.is_authenticated %} Bonjour {{ user.username }} | <a href="{% url 'profile' %}">Profil</a> | <a href="{% url 'logout' %}">Déconnexion</a> {% else %} <a href="{% url 'login' %}">Connexion</a> | <a href="{% url 'signup' %}">Inscription</a> {% endif %} </nav> <main>{% block content %}{% endblock %}</main> </body> </html>
templates/index.html
{% extends "base.html" %} {% block title %}Accueil{% endblock %} {% block content %} <h1>Accueil</h1> <p>Bienvenue sur le site.</p> {% endblock %}
templates/registration/login.html
{% extends "base.html" %} {% block title %}Connexion{% endblock %} {% block content %} <h2>Connexion</h2> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Se connecter</button> </form> {% endblock %}
templates/registration/signup.html
{% extends "base.html" %} {% block title %}Inscription{% endblock %} {% block content %} <h2>Créer un compte</h2> <form method="post" enctype="multipart/form-data"> {% csrf_token %} {{ form.as_p }} <button type="submit">S'inscrire</button> </form> {% endblock %}
templates/registration/profile.html
{% extends "base.html" %} {% block title %}Mon profil{% endblock %} {% block content %} <h2>Profil</h2> <p><strong>Nom d'utilisateur :</strong> {{ user_profile.username }}</p> <p><strong>Email :</strong> {{ user_profile.email }}</p> <p><strong>Téléphone :</strong> {{ user_profile.phone_number }}</p> <p><strong>Date de naissance :</strong> {{ user_profile.date_of_birth }}</p> <p><strong>Adresse :</strong> {{ user_profile.address|linebreaksbr }}</p> {% if user_profile.profile_picture %} <img src="{{ user_profile.profile_picture.url }}" alt="Photo de profil" width="120"> {% endif %} <p><a href="{% url 'profile_edit' %}">Modifier mon profil</a></p> {% endblock %}
templates/registration/profile_edit.html
{% extends "base.html" %} {% block title %}Modifier le profil{% endblock %} {% block content %} <h2>Modifier mon profil</h2> <form method="post" enctype="multipart/form-data"> {% csrf_token %} {{ form.as_p }} <button type="submit">Enregistrer</button> </form> {% endblock %}
Younes Derfoufi
CRMEF OUJDA