PROJET AUTOBLOG


Sam & Max: Python, Django, Git et du cul

Site original : Sam & Max: Python, Django, Git et du cul

⇐ retour index

Mise à jour

Mise à jour de la base de données, veuillez patienter...

Organisation d’une application Django

samedi 3 août 2013 à 09:28

Article long, tradition :

Question qui est arrivée par le formulaire de contact il y a prêt de 2 mois (désolé ^^), et que je m’étais posé également en commençant avec ce framework.

En effet, Django, c’est du pur Python. On peut structurer son projet comme on veut. On pourrait même tout mettre dans un seul dossier : les vues, les templates, le css. Tout.

Mais ce n’est pas parce que c’est possible qu’il faut le faire, et nous allons donc voir les bonnes pratiques.

WARNING : je vais soigneusement ignorer la question des settings, qui mérite un article à part entière.

Naissance du projet

Quand vous commencez un projet avec startproject et startapp, vous allez avoir une arborescence qui va ressembler ça ceci :

.
├── manage.py
├── mon_app
│   ├── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
└── project
    ├── settings.py
    ├── urls.py
    └── wsgi.py

Avec ça, on ne fait pas une app, vous allez donc ajouter des formulaires, de l’admin, des templates, du js, du css…

Pour tout ce qui est fichier statique (JS, CSS, images, flash, zips…), la bonne pratique Django est de mettre tout ce qui concerne l’app dans un dossier nommé static à l’interieur de l’app. Django vient en effet avec tous les outils pour les gérer de cette manière.

Pour l’admin et les formulaires, il est d’usage de les mettre dans des modules admin.py et forms.py respectivement.

Quand au template, la bonne pratique est de les mettre dans un sous dossier de l’app nommé template comme pour les fichiers statiques. MAIS, chaque template doit aussi être dans un sous dossier qui porte le nom de l’app. Cela donne :

nom_de_lapp/templates/nom_de_lapp/template.html

La raison à cela est que cela permet à d’autres app de pourvoir utiliser le même nom de template, ou d’écraser votre template explicitement. Inversement votre app peut dans ses templates écraser les templates d’une autre app en ayant un template avec le chemin :

nom_de_lapp/templates/nom_de_lautre_app/template.html

Donc, même si cela parait redondant, toujours créer un sous-dossier avec le nom de l’app dans le dossier templates. Cela se fait de plus en plus avec le dossier static également, mais c’est moins important dans un premier temps pour vous car vous n’allez pas publier ces apps. Donc, vous pouvez ne pas le faire pour static pour le moment.

Enfin, je vous recommande d’avoir toujours les routes qui concernent votre app dans un fichier urls.py dans le dossier de l’app, qui sera inclus par celui du projet. Cela vous aidera pour la suite.

Votre arbo de base va donc ressembler à ça :

.
├── manage.py
├── mon_app
│   ├── admin.py
│   ├── forms.py
│   ├── __init__.py
│   ├── models.py
│   ├── static
│   │   ├── css
│   │   │   └── style.css
│   │   └── js
│   │       └── behavior.js
│   ├── templates
│   │   └── mon_app
│   │       └── index.html
│   ├── tests.py
│   ├── urls.py
│   └── views.py
└── project
    ├── settings.py
    ├── urls.py
    └── wsgi.py

Croissance du projet

Jusqu’ici votre projet était un petit site avec quelques vues, un formulaire, 3 modèles, et c’est tout. Mais il a grandi, et comme dans toute adolescence, ça commence à être le bordel dans la chambre : le fichier views.py contient 25 fonctions courtes, le fichier models.py 4 classes mais fait 800 lignes.

La première chose à faire à ce niveau, c’est de transformer les modules en packages.

Souvenez-vous : la règle la plus importante avec Django, c’est que c’est juste du Python.

On peut donc prendre chaque fichier de modules, et les diviser en plusieurs sous fichiers (chacun représentant un jeu de fonctionnalités), puis les mettre dans un packages. Pour rappel, un package c’est un dossier avec un fichier __init__.

Exemple, vous avez views.py qui ressemble à ça :

def home():
 
def user_account():
 
def profile():
 
def messages():
 
def forum():
 
def articles():

Vous allez faire un dossier views avec fichier __init__ et 3 modules :

views
├── communication.py <= contient formum() et messages()
├── content.py  <= contient articles() et home()
├── __init__
└── user.py  <= contient user_account() et profile()

A vous de trouver des noms explicites. Prenez le temps de le faire, un bon nom, c'est 10 lignes de documentation en moins.

Maintenant concernant les imports, vous allez devoir faire :

from mon_app.views.content import home

Au lieu de :

from mon_app.views import home

Si cela vous ennuie, il est tout ça fait possible de faire dans __init__.py :

from communcation import *
from content import *
from user import *

Et vous n'aurez pas à changer le moindre de vos imports. Pour Python, un module et un package, c'est pareil.

Si votre app devient grosse, je vous invite quand même a réserver les imports dans __init__ pour les fonctions les plus courantes, afin de ne pas saturer l'auto-completion quand vous êtes dans le shell ou dans votre IDE.

Bref, si vous appliquez cela à tous vos modules un peu gras, ça peut donner :

.
├── manage.py
├── mon_app
│   ├── admin.py
│   ├── forms
│   │   ├── communication.py
│   │   ├── __init__.py
│   │   └── user.py
│   ├── __init__.py
│   ├── models
│   │   ├── content.py
│   │   ├── __init__.py
│   │   ├── stats.py
│   │   └── user.py
│   ├── static
│   │   ├── css
│   │   │   └── style.css
│   │   └── js
│   │       └── behavior.js
│   ├── templates
│   │   └── mon_app
│   │       └── index.html
│   ├── tests.py
│   ├── urls.py
│   └── views
│       ├── communication.py
│       ├── content.py
│       ├── __init__
│       └── user.py
└── project
    ├── settings.py
    ├── urls.py
    └── wsgi.py

Votre app commence vraiment à prendre du poids, mais ça reste manageable. Notez que ceci peut s'appliquer à n'importe quel module qui devient trop gros, y compris admin.py ou urls.py. Généralement ce sont views.py et models.py qui grossisent les premiers, suivi de tests.py. tests.py devrait en principe être celui qui grossi le plus, mais je sais bien que chez la plupart des gens, ce fichier est vide.

Maturité

Tout centraliser au début facilite la tâche. On trouve tout facilement, pas de souci de gestion, ça permet de commencer rapidement. Sur le long terme, le code va s'accumuler et il va devenir difficile de trouver ce que l'on cherche, avoir une image globale de ce qui se passe ou faire des modifications. Mais le pire, c'est pour le partage et la réutilisation de code.

Votre projet est un grand garçon, il est temps de le traiter comme tel.

Cela va passer par la division de votre grosse application monolithique, en plusieurs apps plus petites, chacune représentant une fonctionnalité ou un groupe de fonctionnalités. Au début ce concept de "groupe de fonctionnalités" est difficile à appréhender pour un débutant, alors commencez par faire simple : faites une app par 'partie du site' comme par exemple le dashboard, le forum, le compte utilisateur, etc. On peut faire mieux, mais ce sera déjà bien.

Plus tard, quand vous serez plus à l'aise avec ce principe, vous pourrez étudier la notion de "composants" et de "pluggable apps" qui vous amèneront à faire des applications réutilisables (et même partageables sur le Net) orientées fonctionalités : messaging, comments, anti-spam, registration, user management, etc. Mais cela implique bien d'autres choses qui sont en dehors du cadre de cet article. Pour le moment, concentrez-vous sur la division de votre grosse app en plusieurs plus petites.

Dans notre exemple on note qu'il y a 3 grosses parties : une partie "information", une partie "communication" et une partie "contenu". Nos apps sont donc toutes trouvées.

Il n'est pas rare non plus d'avoir des petits bouts dont on ne sait quoi faire, et aussi des fondations de code qui ne se prêtent pas vraiment à être mis dans une app, comme la home page ou le miniscule modèle stats qui ici n'est pas encore assez gros. Dans ce cas, il suffit de créer une application principale qui va contenir tout ça, en attendant que vous vienne l'inspiration de classifier / factoriser / répartir son contenu ailleurs. J'appelle souvent cette app "core", à défaut d'un meilleur nom.

.
├── communication
│   ├── admin.py
│   ├── forms.py
│   ├── __init__.py
│   ├── templates
│   │   └── communcation
│   │       └── forum.html
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── content
│   ├── admin.py
│   ├── __init__.py
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── core
│   ├── admin.py
│   ├── __init__.py
│   ├── static
│   │   ├── css
│   │   │   └── style.css
│   │   └── js
│   │       └── behavior.js
│   ├── templates
│   │   └── mon_app
│   │       └── index.html
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── manage.py
├── project
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── user
    ├── admin.py
    ├── forms.py
    ├── __init__.py
    ├── models.py
    ├── templates
    │   └── user
    │       └── profile.html
    ├── tests.py
    ├── urls.py
    └── views.py

Vous noterez des phénomènes intéressants :

Au début, vos apps seront fortement couplées. Avec l'expérience (et peut être un article), vous apprendrez à les rendre de plus en plus autonomes, et finalement à avoir un maximum d'apps génériques et quelques apps glues. Je vous rassure, nos codes en prod à Max et moi sont pleins de couplages également. Il y a la théorie, et le résultat de 200 mises en prod.

Bref, vous allez ensuite faire grossir vos applications organiquement comme on a vu dans Croissance du projet et quand elles dépassent une masse critique (mesurable scientifiquement), vous la splitter en apps plus petites, et ainsi de suite.

L'age de raison

Après de nombreuses mises en production, des tentatives d'automatisation, des fichiers de conf qui vont trainer, des fichiers statiques et des libs ajoutées, vous allez vous apercevoir que même cette archi commence à être brouillonne.

Il est temps de passer en mode pro.

Alors, attention, à partir de la, on tombe dans la guerre de religion : VI VS Emacs, PC VS Mac, Bigoo VS Big Mac, Ulysse VS Télémaque, etc. Bref, le layout d'une application en Django a fait coulé beaucoup d'encre et je ne vous propose qu'une des versions possibles.

D'abord, on va regrouper les apps dans un dossier pour éviter d'avoir 20000 dossiers à la racine.

.
├── apps
│   ├── communication
│   │   └── templates
│   │       └── communcation
│   ├── content
│   ├── core
│   │   ├── static
│   │   │   ├── css
│   │   │   └── js
│   │   └── templates
│   │       └── mon_app
│   └── user
│       └── templates
│           └── user
└── project

Afin de toujours permettre d'importer les apps directement et non et les préfixant de 'apps', vous pouvez ajouter le chemin au PYTHON PATH dans le fichiers de settings:

import os
import sys
 
PROJECT_DIR = sys.path.append(os.path.dirname(os.path.realpath(__file__)))
APPS_DIR = os.path.realpath(os.path.join(PROJECT_DIR, '..'))
sys.path.append(APPS_DIR)

Ce n'est pas obligatoire, mais ça facilite beaucoup la vie, et la refactorisation.

Ensuite, il va vous falloir un dossier dans lequel mettre les fichiers de confs (requirements.txt, templates de fichiers de conf nginx, postgres, redis, etc) et les scripts de déploiements, et un autre pour la doc. Je le mets en général dans project. Si Max lit jusqu'à cette ligne, il va bien se marrer, vu le bordel qu'il y a dans les arbos de certains de nos projets.

.
├── apps
│   ├── communication
│   │   └── templates
│   │       └── communcation
│   ├── content
│   ├── core
│   │   ├── static
│   │   │   ├── css
│   │   │   └── js
│   │   └── templates
│   │       └── mon_app
│   └── user
│       └── templates
│           └── user
└── project
    ├── deploy
    └── doc

Tous ces fichiers doivent être commités dans un VCS (Git, SVN, mercurial...).

A cela, il faut souvent ajouter une dossier libs qui contient toutes les libs non installables par pip, tous vos forks, vos trucs compilés avec des options zarbs, vos libs persos non Django qui ne sont pas sur pypi, etc. Dans le meilleur des mondes, il vous faut récupérer son contenu dynamiquement avec un script. Dans le meilleur des mondes... Sinon, ajoutez le à votre VCS. Et par simplicité, ajoutez ce dossier au PYTHON PATH.

Ensuite il faut un dossier var, qui va contenir tous ce qui est fichier que le code du projet va manipuler : fichiers statiques collectés par ./manage.py collectstatic, fichiers de log, base de données SQLite, dumps, locks, etc.

Puis il faut un dossier etc dans lequel on va mettre les fichiers de configuration pour le projet, pour ce serveur : le fichiers conf nginx, postgres, redis qui auront été générés depuis le templates situés dans project/deploy/. Faites ensuite un lien symbolique depuis ces fichiers dans les dossiers correspondant (par exemple vers /etc/nginx/site-enabled pour un fichier de config nginx).

Ces deux dossiers sont nommés ainsi car c'est la nomenclature des dossiers pour cet usage sous Linux, mais vous pouvez leur donner le nom que vous voulez.

Dans tous les cas, ce sont des dossiers qui ne sont PAS commités dans votre VCS, donc mettez les dans votre blacklist (type .gitignore). Ils sont spécifiques à une installation.

Idéalement il faut les générer avec un truc genre fabric pour pas avoir à le faire à la main, même si vous apprendrez mieux en le faisant à la mano au début. Générer ses fichiers de conf avec des templates, c'est un idéal. Après, il y a vraiment une boîte sur 10 qui le fait dans la pratique.

Puis vous ajoutez un README qui explique rapidement quel dossier contient quoi, où trouver la doc et particulièrement, pointez directement vers la partie de la doc sur l'installation et les dépendances.

.
├── apps
│   ├── communication
│   ├── content
│   ├── core
│   └── user
├── etc
├── libs
├── project
│   ├── deploy
│   └── doc
└── var
└── README.rst

Souvent, je met aussi un symlink du virtualenv à la racine du projet, car c'est très pratique pour jeter un coup d'oeil dans le site-packages.

flattr this!

Error happened! 0 - count(): Argument #1 ($value) must be of type Countable|array, null given In: /var/www/ecirtam.net/autoblogs/autoblogs/autoblog.php:428 http://ecirtam.net/autoblogs/autoblogs/sametmaxcom_a844ada43a979e3b1395ab9acb6afafb84340999/?Organisation-d-une-application-Django #0 /var/www/ecirtam.net/autoblogs/autoblogs/autoblog.php(999): VroumVroum_Blog->update() #1 /var/www/ecirtam.net/autoblogs/autoblogs/sametmaxcom_a844ada43a979e3b1395ab9acb6afafb84340999/index.php(1): require_once('...') #2 {main}