Quand un projet Django démarre, la séparation “projet / applications” paraît presque naturelle. Puis, très vite, une tension apparaît : plus les applications se multiplient, plus un core central grossit. Et plus ce core grossit, plus il devient difficile de reprendre une application ailleurs sans “emporter tout le projet”.
Dans un wiki que j’ai développé en Django, j’avais justement un core et plusieurs apps : campagne, calendrier, carte, etc. Au départ, c’était fluide : chaque feature vivait dans son dossier, et core hébergeait ce qui “servait à tout”. Le jour où j’ai voulu réutiliser l’app carte dans un autre projet, j’ai découvert le vrai coût : elle dépendait, partout et implicitement, de core.
Django encourage pourtant l’idée inverse : un projet est une collection de configuration et d’applications, et une application est un package Python fournissant un ensemble de fonctionnalités, potentiellement réutilisable ailleurs. (voir la doc sur les applications)
La question devient donc : comment concevoir une architecture où un projet a plusieurs apps, tout en gardant chaque app réutilisable, sans que le core ne devienne un boulet ?
La philosophie : le projet comme “composition root”, les apps comme briques
Il existe un moment charnière dans la vie d’un projet Django où l’on cesse de “faire fonctionner le code” pour commencer à penser sa structure. Ce moment n’est pas déclenché par une erreur, ni par un problème de performance, mais par une prise de conscience : ce qui pose difficulté n’est pas la technique, mais la manière dont les responsabilités sont réparties.
À ce stade, le véritable point de bascule n’est pas technique. Il est philosophique.
Dans Django, le projet n’est pas une application au sens fonctionnel. Il ne représente pas un domaine métier, ni une fonctionnalité utilisateur. Il est un espace de composition. C’est l’endroit où l’on assemble des briques déjà existantes, où l’on décide comment elles coopèrent, où l’on définit un contexte d’exécution précis : un ensemble de settings, une arborescence d’URLs, des choix d’activation ou de désactivation. Le projet n’est pas censé être intelligent ; il est censé être orchestrateur.
Les applications, à l’inverse, portent le sens. Elles incarnent une intention fonctionnelle claire : un système de calendrier, une galerie, un module de contact, une page de notification. Une application bien conçue n’exprime pas “comment le site fonctionne”, mais “ce que cette brique sait faire”. C’est précisément pour cette raison qu’une application devrait pouvoir fonctionner avec un minimum d’hypothèses sur son environnement. Elle accepte un contexte, elle ne le dicte pas.
Dire que les applications ne devraient pas dépendre du projet, et encore moins d’un “core” global, n’est pas un dogme abstrait. C’est une conséquence directe de cette séparation des rôles. Dès qu’une app commence à importer des éléments spécifiques au projet (un helper global, une configuration implicite, un modèle central non contractuel), elle cesse d’être une brique pour devenir une extension ad hoc. Elle n’est plus composable, seulement greffable.
C’est ici qu’intervient la notion de contrat. Une application n’a pas besoin de connaître le projet dans lequel elle vit ; elle a besoin de connaître les règles minimales qui lui permettent d’opérer. Ces règles peuvent prendre plusieurs formes : des modèles attendus (ou abstraits), des settings optionnels avec des valeurs par défaut, des signaux auxquels elle peut réagir, ou encore des APIs internes clairement définies. Ce sont des points d’entrée explicites, documentables, et surtout remplaçables. À l’inverse, un dossier “core” utilisé comme réservoir universel n’offre aucun contrat : il impose une dépendance floue, implicite, difficile à extraire.
Django illustre très tôt cette philosophie, souvent sans la nommer. Le simple fait d’inclure les URLs d’une application via include() plutôt que de les définir directement au niveau du projet n’est pas un détail d’implémentation. C’est une affirmation architecturale : l’application expose ce qu’elle sait faire, le projet décide où et comment l’intégrer. De la même manière, le mécanisme INSTALLED_APPS agit comme une déclaration d’intention : le projet annonce les briques qu’il assemble, sans que celles-ci aient à se connaître entre elles.
Cette vision demande un certain lâcher-prise. Elle implique d’accepter que le projet soit parfois “vide de logique”, et que les applications soient parfois plus générales que le cas d’usage immédiat. Mais c’est précisément ce qui rend l’ensemble durable. Un projet bien pensé est un point de rencontre temporaire entre des briques autonomes ; une application bien pensée est une promesse de réutilisation future, même si celle-ci n’est pas encore envisagée.
En fin de compte, penser le projet comme composition root et les applications comme briques indépendantes revient à adopter une posture simple mais exigeante : le sens vit dans les apps, l’assemblage vit dans le projet, et le commun n’existe que sous forme de contrat explicite. Tout le reste est une facilité à court terme, souvent payée avec intérêts plus tard.
Voir la doc pour rendre une application réutilisable
Le vrai problème du core : une dépendance implicite, pas un “socle commun”
Un core devient nocif quand il remplit au moins un de ces rôles :
-
Point d’accès global à tout (imports faciles, dépendances cachées).
-
Répertoire de raccourcis (“je mets ça là, tout le monde l’utilise”).
-
Lieu de couplage (les apps importent
core.models,core.permissions,core.utils… et finissent par ne plus pouvoir vivre sans).
Le souci n’est pas qu’il existe du commun. Le souci est que ce commun n’est plus un contrat stable : c’est un “tas” mouvant.
Le principe clé : un core minimal, et une couche d’intégration au-dessus
Pour que les apps restent indépendantes, il faut séparer deux choses :
-
Le commun structurel (vraiment générique, stable, petit).
-
L’intégration projet (spécifique à ton wiki, à ton domaine, à tes choix).
La stratégie la plus robuste est celle-ci :
1) Garder un “core” réduit à des abstractions stables
Exemples de bon contenu “core minimal” :
-
classes de base abstraites (timestamps, slug, audit léger),
-
helpers vraiment génériques,
-
exceptions communes,
-
petits outils de configuration.
Ce core ne doit pas connaître l’existence de “campagne”, “carte”, “calendrier”.
2) Déplacer l’assemblage dans une “app d’intégration”
Au lieu d’un core tentaculaire, le projet héberge une app du type :
-
integration/site/wiki_runtime
C’est elle qui :
-
connecte des signaux,
-
mappe des permissions,
-
branche des URLs,
-
fournit des implémentations concrètes si une app a besoin d’un “provider”.
Résultat : l’app carte reste réutilisable, parce que ses besoins d’intégration sont externalisés.
Concrètement : rendre une app “plug-and-play”
Découpler les URLs : l’app expose, le projet compose
Dans l’app map, un urls.py autonome :
# map/urls.py
from django.urls import path
from . import views
app_name = "map"
urlpatterns = [
path("", views.index, name="index"),
]
Dans le projet :
# config/urls.py
from django.urls import include, path
urlpatterns = [
path("map/", include("map.urls")),
]
Ce détail compte : il empêche l’app de dépendre du routeur global du projet.
Éviter la dépendance directe au core : préférer “settings + fallbacks” et “imports tardifs”
Paterne 1 : settings optionnels avec valeurs par défaut
Au lieu de :
from core.config import MAP_TILE_SERVER
Faire :
from django.conf import settings
TILE_SERVER = getattr(settings, "MAP_TILE_SERVER", "https://example-tiles.local")
L’app devient réutilisable : dans un autre projet, il suffit de définir MAP_TILE_SERVER ou de garder le défaut.
Le point sensible : modèles partagés, utilisateurs, relations inter-apps
Une app devient difficile à déplacer quand elle référence des modèles “externes” de manière rigide.
Paterne 2 : référencer les modèles par chaîne
class MapMarker(models.Model):
owner = models.ForeignKey("auth.User", on_delete=models.CASCADE)
Ou, si c’est un modèle d’une autre app :
campaign = models.ForeignKey("campaign.Campaign", on_delete=models.CASCADE)
Cela évite les imports directs et limite le couplage au strict nécessaire.
Paterne 3 : s’appuyer sur les mécanismes Django existants pour l’utilisateur
Quand une app a besoin d’un user, elle ne doit pas importer un modèle utilisateur “du core”. Elle utilise les conventions Django (AUTH_USER_MODEL, get_user_model). C’est un exemple typique de contrat stable fourni par le framework. (voir Authentication)
Quand une app “a besoin” du core : transformer le besoin en contrat
La question posée est centrale :
Si une app dépend du core, peut-on la réutiliser ailleurs ?
Réponse honnête : oui, si cette dépendance est un contrat minimal. Non, si c’est une dépendance à un gros bloc informel.
Pour rendre cela viable, une approche simple est la suivante :
Paterne 4 : dépendre d’une interface, pas d’une implémentation
Par exemple, l’app calendrier a besoin de savoir “qui peut voir un événement”.
Au lieu d’importer core.permissions.can_view_event, l’app définit son point d’extension :
# calendar/services.py
from django.conf import settings
from django.utils.module_loading import import_string
def can_view_event(user, event) -> bool:
path = getattr(settings, "CALENDAR_PERMISSION_BACKEND", None)
if not path:
return True # fallback simple
backend = import_string(path)
return backend(user, event)
Dans le projet (ou l’app d’intégration), on configure :
CALENDAR_PERMISSION_BACKEND = "wiki_runtime.permissions.can_view_event"
L’app calendrier reste réutilisable : ailleurs, on change le backend, ou on garde le fallback.
Signaux : faire remonter les événements, laisser le projet décider
Dans mon wiki, à un moment, l’app campagne devait “réagir” quand une page du wiki changeait. Mon premier réflexe a été d’appeler directement du code de campagne depuis le core. Ça marchait… jusqu’au jour où j’ai voulu isoler campagne. Le signal a été la meilleure porte de sortie.
Paterne 5 : l’app émet un signal, l’intégration écoute.
-
L’app “wiki pages” émet
page_updated. -
L’app d’intégration connecte un handler qui met à jour la campagne, le calendrier, etc.
Ce modèle évite que les apps s’appellent en direct et transforme la collaboration en configuration. (voir Signaux)
Un core utile, mais “invisible” pour la réutilisation
Un core sain ressemble à une petite boîte à outils :
-
stable,
-
documentée,
-
sans connaissance du domaine spécifique des apps.
Tout le reste, les règles métier du wiki, liens entre campagne et pages, conventions de navigation, permissions globales, doit vivre dans la couche d’intégration du projet.
C’est exactement l’idée derrière les apps “pluggables” : une app doit pouvoir être branchée et fonctionner sans être “collée” à une installation particulière. (voir doc de création de projet)
Une ouverture pratique : tester l’indépendance d’une app en 10 minutes
Pour savoir si une app est réellement indépendante, un test simple :
-
Créer un projet Django vide.
-
Ajouter uniquement l’app dans
INSTALLED_APPS. -
Inclure ses URLs.
-
Lancer.
Si ça échoue parce qu’il manque core, ce n’est pas forcément grave. La vraie question est : est-ce qu’il manque un contrat minimal (settings, interface, base abstraite), ou est-ce qu’il manque “tout le wiki” ?
Si c’est le second cas, ce n’est pas un problème de Django, mais un signal architectural : l’app n’est pas une brique, c’est une extension du projet. Le chemin pour la rendre réutilisable consiste presque toujours à déplacer l’intégration vers le projet et à réduire la dépendance à un contrat explicite.
Webographie
-
Django Design Philosophies - Principes fondateurs du framework, notamment la séparation des responsabilités et le pragmatisme assumé
https://docs.djangoproject.com/en/stable/misc/design-philosophies/ -
Clean Architecture - Robert C. Martin - Notions de boundaries, de dépendances dirigées vers le cœur métier et de contrats explicites
https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html -
Domain-Driven Design: Tackling Complexity in the Heart of Software - Eric Evans - Réflexions sur les contextes bornés, la séparation du domaine et l’intégration
https://www.domainlanguage.com/ddd/ -
Zeste de Savoir - Organisation d’un projet Django - Approche pédagogique de la séparation projet / applications et de la réutilisation
https://zestedesavoir.com/tutorials/1079/django-de-zero-a-heros/ -
Zest de Savoir - Développez votre site web avec le framework Django - Guide complet de développement d'une application Django
https://zestedesavoir.com/tutoriels/pdf/598/developpez-votre-site-web-avec-le-framework-django.pdf