<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Wesley E. MONTCHO as Sofware Engineer]]></title><description><![CDATA[Wesley E. MONTCHO as Sofware Engineer]]></description><link>https://wesleymontcho.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 07 Nov 2026 11:28:38 GMT</lastBuildDate><atom:link href="https://wesleymontcho.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Le Système Digestif de Django : anatomie d'une requête HTTP vers Django]]></title><description><![CDATA[On écrit request.user tous les jours. Mais d'où vient cet objet ? Des octets arrivent sur le fil pour une requête, voici tout ce que Django fait avant que…]]></description><link>https://wesleymontcho.hashnode.dev/le-syst-me-digestif-de-django-anatomie-d-une-requ-te-http-vers-django</link><guid isPermaLink="true">https://wesleymontcho.hashnode.dev/le-syst-me-digestif-de-django-anatomie-d-une-requ-te-http-vers-django</guid><category><![CDATA[Software Engineering]]></category><category><![CDATA[Django]]></category><category><![CDATA[drf]]></category><category><![CDATA[Python]]></category><category><![CDATA[backend]]></category><category><![CDATA[RequestJourney]]></category><category><![CDATA[TCPHTTPSOCKET]]></category><dc:creator><![CDATA[Wesley Eliel MONTCHO]]></dc:creator><pubDate>Sat, 28 Mar 2026 00:20:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69c11133d9da55a9a570ced9/56b5557a-7716-4493-a44c-11975715cc5e.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>La version anglaise disponible sur DevTo : <a href="https://dev.to/wesleyeliel/the-django-digestive-system-a-request-journey-in-a-django-app-instance-398j-temp-slug-2122144/">The Django Digestive System: A request journey in a Django app instance.</a></p>
<hr />
<h2>Avant de débuter, faisons un bref passage sur quelques mots qu'on va utiliser tout au long de l'article</h2>
<p><strong>Socket : (</strong><a href="#ref-1"><strong>[Ref 1]</strong></a><strong>)</strong></p>
<p>Un socket, c'est un point de connexion entre deux machines sur un réseau. Une sorte de tuyau par lequel les données circulent. Quand un navigateur envoie une requête, il ouvre une connexion socket vers notre serveur. Quand le serveur répond, il renvoie des octets par ce même socket.</p>
<p><strong>TCP, HTTP : (</strong><a href="#ref-2"><strong>[Ref 2]</strong></a><strong>)</strong></p>
<p>TCP (Transmission Control Protocol) est la couche transport. Elle définit les règles d'acheminement fiable des données entre machines. HTTP tourne au-dessus de TCP. HTTP, c'est simplement du texte, formaté d'une façon précise. Un navigateur envoie un message HTTP via une connexion TCP vers notre serveur.</p>
<p><strong>WSGI (Web Server Gateway Interface) : (</strong><a href="#ref-3"><strong>[Ref 3]</strong></a><strong>)</strong></p>
<p>WSGI est l'interface standard entre une application web Python et un serveur web. Un serveur WSGI (Gunicorn, uWSGI, etc.) se place entre internet et Django : il accepte la connexion TCP, lit les octets HTTP bruts, et appelle Django avec un dictionnaire Python standardisé appelé <code>environ</code> (<a href="#ref-4">[Ref 4]</a>) : ce dictionnaire contient tout ce qui concerne la requête entrante : méthode, chemin, headers, corps. Django ne touche jamais TCP ni le socket brut directement. C'est le rôle du WSGI.</p>
<p><strong>Middleware : (</strong><a href="#ref-5"><strong>[Ref 5]</strong></a><strong>)</strong></p>
<p>Le middleware existe dans tous les frameworks web sérieux : Express, NestJS, Laravel, Rails, etc. Dans Django, c'est une couche qui se place entre le serveur WSGI et la vue (qui contient notre code de traitement de la requête). Un middleware peut lire, modifier, ou stopper une requête entièrement, en retournant une réponse directement sans aller plus loin. On appelle ça un <strong>court-circuit</strong>.</p>
<p><strong>ORM : (</strong><a href="#ref-6"><strong>[Ref 6]</strong></a><strong>)</strong></p>
<p>ORM signifie Object-Relational Mapper. C'est la couche qui traduit entre objets Python et tables de base de données. Rails l'appelle ActiveRecord, Laravel l'appelle Eloquent : dans Django, c'est simplement « l'ORM ».</p>
<hr />
<p>Toujours là ? Bien. 👌 Tout ça dit, traçons le chemin d'une requête. 🗺️</p>
<hr />
<h2>1. Avant Django : des octets sur un fil, que dis-je: sur le socket.</h2>
<p>Un navigateur décide d'appeler notre API. Il ouvre une connexion socket <a href="#ref-1">[Ref 1]</a> vers notre serveur et envoie quelque chose comme ça le format texte typique d'une requête HTTP :</p>
<pre><code class="language-http">GET /users/42/ HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbG...
Accept: application/json
...
...
...
</code></pre>
<p>C'est tout. Du texte brut. Pour l'instant, pas de Python, pas de Django, pas d'objets. Une chaîne formatée qui voyage dans le socket <a href="#ref-1">[Ref 1]</a>, contrôlée par le standard TCP <a href="#ref-2">[Ref 2]</a>.</p>
<p>Le serveur WSGI <a href="#ref-3">[Ref 3]</a> reçoit ça sur le socket, parse le texte HTTP, et construit le dictionnaire <code>environ</code> <a href="#ref-4">[Ref 4]</a>. Ensuite il appelle Django. C'est là que notre histoire commence vraiment.</p>
<hr />
<h2>2. La naissance de <code>HttpRequest</code></h2>
<p>Le point d'entrée de chaque requête Django, c'est <code>WSGIHandler.__call__()</code>, défini dans <code>django/core/handlers/wsgi.py</code>.</p>
<p>C'est la ligne exacte où la requête cesse d'être un dictionnaire pour devenir un objet Python :</p>
<pre><code class="language-python"># django/core/handlers/wsgi.py (simplifié)
class WSGIHandler(base.BaseHandler):
    def __call__(self, environ, start_response):
        request = self.request_class(environ)  # ← naissance ici
        response = self.get_response(request)
        # ...
</code></pre>
<p><code>self.request_class</code>, c'est la classe <code>HttpRequest</code>. Django prend le dictionnaire <code>environ</code> brut et l'emballe dans un objet Python riche et utilisable.</p>
<p>Voici à quoi ressemble cette transformation :</p>
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nmffi2c7kwyx53o3thvw.png" alt="Transformation WSGI avant Django" style="display:block;margin:0 auto" />

<p>L'attribut <code>request.META</code> contient toujours le dictionnaire <code>environ</code> complet. Tout est simplement accessible via une interface Python propre et utilisable dans les vues.</p>
<p>On suit toujours ? Bien 👌. L'objet <code>HttpRequest</code> est là. Maintenant, il doit continuer la traversée.</p>
<hr />
<h2>3. Le flux des middlewares <a href="#ref-5">[Ref 5]</a></h2>
<p>C'est là où beaucoup de développeurs ont un modèle mental flou, et ça vaut la peine de clarifier.</p>
<p>Le flux des middlewares n'est <strong>pas</strong> un pipeline où chaque étape traite la requête indépendamment puis la passe au suivant. C'est une pile d'<strong>enveloppes imbriquées</strong>. Chaque middleware enveloppe tout ce qui vient après lui.</p>
<p>Voici à quoi ressemble un middleware concrètement :</p>
<pre><code class="language-python">class ExempleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response  # la couche suivante

    def __call__(self, request):
        # → s'exécute AVANT la vue
        faire_quelque_chose_avec(request)

        response = self.get_response(request)  # appelle la couche suivante.
        # Si on a n autres couches avant la vue,
        # toutes s'exécutent, et la vue aussi,
        # avant qu'on continue à l'instruction suivante ici.

        # ← s'exécute APRÈS la vue, sur le chemin du retour
        faire_quelque_chose_avec(response)

        return response
</code></pre>
<p>Les middlewares sont chaînés. <code>self.get_response</code>, c'est le middleware suivant dans la chaîne (ou la vue elle-même si on est au dernier middleware). L'appeler envoie la requête plus loin. <strong>Ne pas</strong> l'appeler et retourner une réponse directement, c'est un court-circuit: la vue ne tourne jamais.</p>
<p>Django traite <code>MIDDLEWARE</code> de haut en bas à l'aller, de bas en haut au retour. L'ordre n'est pas optionnel.</p>
<p>Voici ce que les middlewares intégrés injectent pendant le voyage de la requête :</p>
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3uy9ve1uprmdt75qeubt.png" alt="Flux des middlewares Django" style="display:block;margin:0 auto" />

<p>Comparé au système digestif humain, c'est le tube intestinal de la requête. Chaque couche absorbe/ajoute une chose, un attribut: session, identité utilisateur etc. et passe le reste. Au moment où la requête ressort, elle est complètement enrichie. Un court-circuit ? C'est le corps qui rejette quelque chose avant qu'elle atteigne la circulation sanguine.</p>
<p>Quand la requête arrive à notre vue (officiellement la circulation sanguine 😅), les attributs <code>request.session</code> et <code>request.user</code> sont déjà là, ajoutés par les middlewares correspondants. Pas de magie, juste le flux des middlewares qui fait son travail silencieusement.</p>
<hr />
<h2>4. Le routage URL : le carrefour</h2>
<p>L'objet <code>HttpRequest</code> ayant traversé la pile des middlewares, Django doit maintenant déterminer <em>quelle</em> vue doit la traiter.</p>
<p><code>URLResolver</code> (chargé de déterminer les vues associées aux urls) parcourt <code>urlpatterns</code> (la liste qu'on définit dans <code>urls.py</code> du dossier de config), en comparant le chemin de la requête à chaque pattern. Le premier match est utilisé. Les patterns matchés deviennent des <code>kwargs</code>.</p>
<pre><code class="language-python"># urls.py
path("users/&lt;int:pk&gt;/", UserDetailView.as_view()),

# requête vers /users/42/ → kwargs = {"pk": 42}
</code></pre>
<p>Si rien ne correspond, Django retourne un 404 avant que notre code de vue tourne. À cette étape : pas de vue, pas de sérializer, pas de base de données. Juste un 404.</p>
<p>On y est presque. C'est ici que notre code s'exécute vraiment.</p>
<hr />
<h2>5. Notre code s'exécute enfin 💻</h2>
<p>La vue reçoit deux choses : l'objet <code>HttpRequest</code> et les <code>kwargs</code> résolus. Tout ce qui s'est passé avant (socket, WSGI, environ, middlewares, routage) c'était la machinerie de Django. Cette partie-là, c'est la nôtre.</p>
<pre><code class="language-python">class UserDetailView(RetrieveAPIView):
    def get(self, request, pk):
        # request.user est déjà défini (merci AuthenticationMiddleware)
        # pk vient de l'URL (merci URLResolver)
        user = User.objects.get(pk=pk)
        return Response(UserSerializer(user).data)
</code></pre>
<p><code>User.objects.get(pk=pk)</code> : c'est là que l'ORM <a href="#ref-6">[Ref 6]</a> entre en jeu. Django traduit cet appel Python en requête SQL, interroge la base de données, et retourne un objet Python. Une ligne de notre côté, beaucoup de choses sous le capot. [J'ai essayé de lire ce code dans le module django une fois. Ce n'est pas une logique simple. Merci aux contributeurs Django.]</p>
<p>Pour ceux qui utilisent Django REST Framework : <code>APIView.dispatch()</code> (la base des ViewSets, etc.) ajoute une couche d'exécution supplémentaire avant que notre <code>.get()</code>, <code>.post()</code>, <code>.create()</code> ou action personnalisée tourne : classes d'authentification, classes de permissions, classes de throttling. On fera un article dédié là-dessus. Pour l'instant, sachez juste que c'est là.</p>
<hr />
<h2>6. Le voyage de retour</h2>
<p>Notre vue retourne un objet <code>HttpResponse</code>. C'est là que la pile de middlewares <strong>se déroule</strong>.</p>
<p>La même chaîne tourne dans le sens inverse : de bas en haut. La logique post-vue de chaque middleware s'exécute au fur et à mesure que <code>get_response(request)</code> remonte la pile d'appels.</p>
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8tpogugwpo3ffs4ju6a3.png" alt="Flux de retour des middlewares Django" style="display:block;margin:0 auto" />

<p>Des headers comme <code>Content-Security-Policy</code>, <code>Strict-Transport-Security</code> et <code>Set-Cookie</code> sont ajoutés pendant ce voyage retour. Le middleware en <strong>haut</strong> de <code>MIDDLEWARE</code> est le <strong>dernier</strong> à toucher la réponse. C'est l'imbrication en action.</p>
<hr />
<h2>7. Et ça ressort</h2>
<p><code>WSGIHandler</code> prend l'objet <code>HttpResponse</code> retourné et le reconvertit en octets selon le protocole HTTP.</p>
<p>Le client reçoit du texte HTTP :</p>
<pre><code class="language-http">HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 87
Set-Cookie: sessionid=abc123; HttpOnly; Path=/
...
...
...

{"id": 42, "name": "..."}
</code></pre>
<p>Boucle bouclée. Des octets en entrée, des octets en sortie. Tout ce qui s'est passé entre les deux, c'était Django. Digestion terminée.</p>
<hr />
<h2>C'est le voyage complet</h2>
<p>La plupart d'entre nous restons simplement dans la section 5. La vue, le sérializer, la logique métier. C'est là que vit le produit c'est normal.</p>
<p>Mais la prochaine fois que le retour de <code>request.user</code> est un <code>AnonymousUser</code> inattendu, qu'un header de réponse manque, ou qu'un 404 apparaît avant même que notre code ait la moindre chance de tourner, on sait exactement quelle couche regarder.</p>
<p>Ce flow n'est pas de la magie. Ce sont des couches, chacune faisant une chose, dans un ordre précis. Comprendre ça aide aussi à personnaliser certains comportements quand notre logique métier l'exige.</p>
<p>Dans quelle couche avez-vous dû creuser une fois lors d'un débogage ? Dites-le en commentaire.</p>
<hr />
<p><em>Si vous repérez quelque chose d'incorrect, ou si vous faites différemment avec de meilleurs résultats, commentez. Je suis là pour apprendre autant que pour partager.</em> 🤝</p>
]]></content:encoded></item><item><title><![CDATA[Structurer un projet Django REST Framework : comment je m'y prends ?]]></title><description><![CDATA[Organisation multi-app, découpage des settings, héritage de modèle de base, et pourquoi le prochain développeur qui ouvre ton code te remerciera ou…]]></description><link>https://wesleymontcho.hashnode.dev/structurer-un-projet-django-rest-framework-comment-je-m-y-prends</link><guid isPermaLink="true">https://wesleymontcho.hashnode.dev/structurer-un-projet-django-rest-framework-comment-je-m-y-prends</guid><category><![CDATA[Django]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[django master]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Python]]></category><category><![CDATA[backend]]></category><dc:creator><![CDATA[Wesley Eliel MONTCHO]]></dc:creator><pubDate>Mon, 23 Mar 2026 11:23:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69c11133d9da55a9a570ced9/3a496ed7-d53b-42a0-8c0e-85081cf8da2c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>Il y a quelques semaines, un développeur Django senior a rejoint un projet que je construisais. Après avoir parcouru le code, il m'a envoyé ce message :</p>
<blockquote>
<p><em>« Il y a pas mal de choses pas bien finies. Des lacunes. Mais il y a aussi de très belles choses qui m'ont permis d'avancer vite. »</em></p>
</blockquote>
<p>C'est cette dernière phrase qui m'a donné envie d'écrire : pour garder une trace de ce qui a fonctionné, et pour échanger avec d'autres. Alors me voilà. 👋</p>
<p>Bien structurer un projet, surtout un grand, c'est crucial. C'est prendre des décisions tôt qui permettent de naviguer avec confiance, d'ajouter des features sereinement, et de passer la main sans être rappelé toutes les cinq minutes.</p>
<blockquote>
<p>Spoil alert: C'est mon premier article technique. D'autres suivront sur Django, l'architecture, et les choses apprises au fil des projets. Les discussions et commentaires sont les bienvenus.</p>
</blockquote>
<p>On y va. 🚀</p>
<hr />
<h2>Le problème avec la structure par défaut de Django</h2>
<p>Quand tu lances <code>django-admin startproject backend</code>, tu obtiens ça :</p>
<pre><code class="language-plaintext">backend/
├── backend/
│   ├── settings.py
│   ├── urls.py
│   ├── wsgi.py
│   └── asgi.py
├── manage.py
</code></pre>
<p>Tu ajoutes ta première app :</p>
<pre><code class="language-bash">python manage.py startapp myapp
</code></pre>
<p>Et soudain :</p>
<pre><code class="language-plaintext">backend/
├── myapp/
│   ├── models.py
│   ├── views.py
│   ├── admin.py
│   └── tests.py
├── backend/
│   └── settings.py
</code></pre>
<p>Ça marche parfaitement. Pour un tutoriel. 😬</p>
<p>En contexte de production réel, <code>models.py</code> dépasse rapidement les 1 000 lignes. <code>views.py</code> devient illisible. <code>settings.py</code> mélange config base de données, email, AWS, et tout le reste.</p>
<p>Pour qu'une app puisse scaler correctement, il devient naturel de <strong>designer sa structure dès le départ</strong>.</p>
<hr />
<h2>1. Le découpage racine : <code>apps/</code> et <code>backend/</code></h2>
<p>La décision la plus impactante : <strong>séparer tes apps Django de la configuration du projet</strong>.</p>
<pre><code class="language-plaintext">mon-projet/
├── backend/           ← Config du projet Django (settings, urls, celery…)
├── apps/              ← Toutes tes apps Django
├── docs/              ← Documentation
├── scripts/           ← Scripts utilitaires
├── tests/             ← Suite de tests
├── manage.py
└── requirements.txt
</code></pre>
<p>N'importe quel développeur qui ouvre ce dépôt comprend immédiatement la carte :</p>
<ul>
<li><p><code>backend/</code> → config du framework, on ne touche pas sans raison</p>
</li>
<li><p><code>apps/</code> → ici vivent les features et la logique métier</p>
</li>
</ul>
<p>Simple, évident, sans ambiguïté. C'est l'objectif.</p>
<hr />
<h2>2. Le dossier <code>backend/</code> : une config qui respire</h2>
<p>L'erreur la plus courante dans <code>backend/</code> : garder un <code>settings.py</code> monolithique. JWT, email, AWS, rate limiting : tout dans un seul fichier, ça devient vite ingérable.</p>
<p><strong>Découpe les settings par domaine :</strong></p>
<pre><code class="language-plaintext">backend/
├── settings/
│   ├── __init__.py      ← Charge les sous-modules
│   ├── base.py          ← Config Django principale (installed apps, middleware, DB…)
│   ├── smtp.py          ← Config email uniquement
│   └── configs/
│       └── storages.py  ← Config S3/stockage média
├── urls.py
├── celery.py
├── asgi.py
└── wsgi.py
</code></pre>
<pre><code class="language-python"># backend/settings/smtp.py : uniquement la config email
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = os.environ.get("EMAIL_HOST")
EMAIL_PORT = int(os.environ.get("EMAIL_PORT", 587))
EMAIL_USE_TLS = True

# backend/settings/configs/storages.py : uniquement le stockage
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_BUCKET_NAME")
</code></pre>
<p>Quand un dev DevOps doit mettre à jour la config email, il va directement dans <code>smtp.py</code>. Il n'a pas à lire 400 lignes de settings sans rapport. C'est du respect pour le temps du prochain développeur. 🙏</p>
<hr />
<h2>3. Le dossier <code>apps/</code> : un domaine, une app</h2>
<p>Chaque domaine fonctionnel a sa propre app Django. Pas de <code>core/</code> monolithique qui contient tout et n'importe quoi.</p>
<pre><code class="language-plaintext">apps/
├── authentication/    ← Inscription, connexion, JWT…
├── transactions/      ← Transactions, remboursements…
├── users/             ← Modèle utilisateur, profils…
├── notifications/     ← Email, SMS, Push, WhatsApp…
├── marketing/         ← Parrainage, messages de contact…
├── settings/          ← Paramètres système et utilisateur
└── utils/             ← Code partagé (modèle de base, enums, erreurs, middleware)
</code></pre>
<p>Chaque app est un <strong>contexte délimité</strong> : elle possède ses modèles, ses vues, ses serializers, sa logique métier. Si demain tu extrais <code>notifications/</code> en microservice, les frontières sont déjà tracées.</p>
<p><strong>La règle que j'applique :</strong> si tu peux le décrire par un nom de domaine, c'est une app. <code>Authentication</code>, <code>Transactions</code>, <code>Notifications</code>. Pas <code>Stuff</code>, pas <code>Core</code>, pas <code>Api</code>.</p>
<hr />
<h2>4. À l'intérieur d'une app : chaque fichier a un rôle</h2>
<p>Les <code>models.py</code> / <code>views.py</code> monolithiques générés par défaut ne facilitent pas le scaling. Voici à quoi ressemble une app bien structurée :</p>
<pre><code class="language-plaintext">apps/transactions/
├── models/
│   ├── __init__.py          ← Star-imports de tous les fichiers modèles
│   ├── transactions.py
│   ├── refunds.py
│   └── …
├── serializers/
│   ├── transactions.py
│   └── refunds.py
├── views/
│   ├── transactions.py
│   └── refunds.py
├── services/
│   ├── transaction_processor.py
│   └── refund_service.py
├── admin/
│   └── transactions.py
├── router.py
├── urls.py
├── tasks.py
├── apps.py
└── tests.py
</code></pre>
<h3>Découper les modèles en sous-fichiers</h3>
<p>Le <code>models/__init__.py</code> ré-exporte tout via des star imports :</p>
<pre><code class="language-python"># apps/transactions/models/__init__.py
from apps.transactions.models.transactions import *
from apps.transactions.models.refunds import *
</code></pre>
<p>Du point de vue de Django, rien ne change : <code>apps.transactions.Transaction</code> fonctionne toujours. Du point de vue du développeur, tout change :</p>
<ul>
<li><p>Chaque fichier modèle reste lisible (&lt; 300 lignes)</p>
</li>
<li><p>Les conflits Git sont rares et simples à résoudre</p>
</li>
<li><p>Chercher "transactions" dans tes fichiers remonte modèles, services et serializers d'un coup</p>
</li>
</ul>
<h3>La couche <code>services/</code></h3>
<p>Les vues ne doivent pas contenir de logique métier. Ce n'est pas une règle que j'ai inventée: c'est un principe qui sauve à chaque fois qu' on dois réutiliser de la logique dans une tâche, une commande de management, ou un test.</p>
<pre><code class="language-python"># ❌ Logique métier dans la vue antipattern classique
class TransactionViewSet(ModelViewSet):
    def create(self, request):
        dest_user = User.objects.get(...)
        if not dest_user.is_active:
            raise ValidationError("Utilisateur indisponible")
        # ...50 autres lignes de logique métier...

# ✅ La vue délègue à un service
class TransactionViewSet(ModelViewSet):
    def create(self, request):
        serializer = CreateTransactionSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        transaction = TransactionProcessor.create_transaction(
            user=request.user,
            **serializer.validated_data
        )
        return Response(TransactionSerializer(transaction).data)
</code></pre>
<p><code>TransactionProcessor.create_transaction()</code> peut maintenant être appelé depuis une vue, une tâche, un test, ou une commande de management sans changer une seule ligne. 💪</p>
<hr />
<h2>5. Le modèle de base : la fondation de chaque table</h2>
<p>Chaque modèle du projet hérite d'une seule classe. Une seule.</p>
<pre><code class="language-python"># apps/utils/models/base_model.py
from uuid import uuid4
from django.db import models
from django_softdelete.models import SoftDeleteModel

class AbstractCommonBaseModel(SoftDeleteModel):
    uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
    active = models.BooleanField(default=True)
    timestamp = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True
</code></pre>
<p>Chaque modèle l'utilise :</p>
<pre><code class="language-python">from apps.utils.models.base_model import AbstractCommonBaseModel

class Transaction(AbstractCommonBaseModel):
    # uuid, active, timestamp, updated → déjà là, gratuitement
    status = models.CharField(...)
    amount = models.DecimalField(...)
</code></pre>
<p>Du DRY pur. Tu obtiens gratuitement, sur chaque table :</p>
<table>
<thead>
<tr>
<th>Champ</th>
<th>Bénéfice</th>
</tr>
</thead>
<tbody><tr>
<td><code>uuid</code> (PK)</td>
<td>Pas d'IDs séquentiels qui révèlent le nombre d'enregistrements ; sûr dans les URLs</td>
</tr>
<tr>
<td><code>active</code></td>
<td>Interrupteur logique on/off</td>
</tr>
<tr>
<td><code>timestamp</code></td>
<td>Date de création, toujours disponible</td>
</tr>
<tr>
<td><code>updated</code></td>
<td>Date de dernière modification, toujours disponible</td>
</tr>
<tr>
<td>Soft delete</td>
<td><code>.delete()</code> marque la ligne supprimée, ne la détruit pas</td>
</tr>
</tbody></table>
<p>La suppression douce est devenue un réflexe. On ne supprime jamais définitivement un enregistrement sensible. Même si un utilisateur "supprime" quelque chose depuis son interface, la ligne reste pour les audits, les litiges, la réconciliation. 🔍</p>
<p><strong>Le définir dans le modèle de base signifie qu'on ne peut pas l'oublier.</strong> Le pattern est imposé au niveau de la hiérarchie de classes.</p>
<hr />
<h2>6. L'app <code>utils/</code> : la bibliothèque interne du projet</h2>
<p>Chaque projet accumule du code partagé. La question est : vit-il éparpillé partout, ou dans un endroit dédié ?</p>
<pre><code class="language-plaintext">apps/utils/
├── models/
│   └── base_model.py
├── xlib/
│   ├── enums/
│   │   ├── base.py
│   │   ├── errors.py
│   │   ├── transactions.py
│   │   └── users.py
│   ├── error_util.py
│   ├── exceptions.py
│   └── validators.py
├── middleware/
└── commons/
    └── mixins.py
</code></pre>
<p>Un pattern à souligner : le <strong>système d'enums</strong>. Plutôt que de disperser des tuples de choix verbeux à travers les fichiers modèles :</p>
<pre><code class="language-python"># ❌ L'ancienne façon, répétée partout
status = models.CharField(choices=[('PENDING', 'En attente'), ('FAILED', 'Échoué'), ...])

# ✅ Un enum, réutilisé partout
from apps.utils.xlib.enums import TransactionStatusEnum

status = models.CharField(
    choices=TransactionStatusEnum.items(),
    default=TransactionStatusEnum.PENDING.value,
)
</code></pre>
<p>Une source de vérité. Zéro copier-coller, zéro drift. 🎯</p>
<hr />
<h2>7. Le routage URL : trois couches, des contrats clairs</h2>
<p>Chaque app suit le même pattern URL en trois couches. Sans exception.</p>
<p><strong>Couche 1 (</strong><code>router.py</code><strong>) : enregistrement des ViewSets</strong></p>
<pre><code class="language-python"># apps/transactions/router.py
from rest_framework.routers import DefaultRouter
from apps.transactions.views import TransactionViewSet, RefundViewSet

router = DefaultRouter()
router.register(r'refunds', RefundViewSet, basename='refunds')
router.register(r'', TransactionViewSet, basename='transactions')

urls_patterns = router.urls
</code></pre>
<p><strong>Couche 2 (</strong><code>urls.py</code><strong>) : contrat URL de l'app</strong></p>
<pre><code class="language-python"># apps/transactions/urls.py
from apps.transactions.router import urls_patterns

urlpatterns = urls_patterns
</code></pre>
<p><strong>Couche 3 (</strong><code>backend/urls.py</code><strong>) : dispatcher racine, versionné</strong></p>
<pre><code class="language-python">urlpatterns = [
    path("v1/auth/",          include("apps.authentication.urls")),
    path("v1/transactions/",  include("apps.transactions.urls")),
    path("v1/users/",         include("apps.users.urls")),
]
</code></pre>
<p>Un nouveau développeur qui cherche "d'où vient l'endpoint de liste des transactions" suit ce chemin en moins de 60 secondes. Pas de magie. Pas de chasse au trésor. 🗺️</p>
<p>Quand tu as besoin de <code>/v2/</code>, tu ajoutes un second bloc sans toucher au premier.</p>
<hr />
<h2>Bonus : <code>scripts/</code> et <code>docs/</code></h2>
<pre><code class="language-plaintext">scripts/
├── rmpycaches.py        ← Supprime tous les dossiers __pycache__
└── rmmigrationfiles.py  ← Supprime les fichiers de migration (pour les resets en dev)

docs/
└── modelings/           ← Use cases, diagrammes de classes, diagrammes de séquence
</code></pre>
<p>Ces petits détails signalent un codebase professionnel.</p>
<hr />
<h2>L'impact concret</h2>
<p>Le développeur senior qui a revu mon code a avancé vite. Il pouvait naviguer sans me demander où se trouvaient les choses. C'est la métrique qui compte : pas "est-ce que le code est malin", mais "est-ce que la prochaine personne peut le lire ?"</p>
<p><strong>Pendant le développement :</strong> tu ne te demandes jamais "où je mets ça ?". La structure répond à ta place. Ajouter <code>marketing/</code> ne touche pas à <code>transactions/</code>.</p>
<p><strong>Pendant la code review :</strong> les PRs restent petites, les reviewers savent exactement où regarder.</p>
<p><strong>Pendant l'onboarding :</strong> un nouveau dev cartographie le codebase en 20 minutes en lisant les noms de dossiers.</p>
<p><strong>Pendant les incidents :</strong> un bug dans la création de transaction ? Tu vas dans <code>services/transaction_processor.py</code>, pas dans un <code>views.py</code> de 1 000 lignes.</p>
<p><strong>Pendant le refactoring :</strong> extraire <code>notifications/</code> en microservice, c'est déplacer un dossier, pas grepper du code éparpillé.</p>
<hr />
<h2>La checklist récap</h2>
<p>Pour ton prochain projet Django, ou pour refactoriser l'actuel :</p>
<ul>
<li><p>[ ] Nomme ton dossier de config <code>backend/</code> (ou <code>config/</code>), pas d'après ton projet</p>
</li>
<li><p>[ ] Mets toutes les apps sous <code>apps/</code></p>
</li>
<li><p>[ ] Découpe <code>settings.py</code> par domaine : <code>smtp.py</code>, <code>storages.py</code>, etc.</p>
</li>
<li><p>[ ] Crée <code>AbstractCommonBaseModel</code> avec UUID PK, timestamps et soft delete</p>
</li>
<li><p>[ ] Découpe <code>models.py</code> en fichiers <code>models/&lt;domaine&gt;.py</code>, ré-exportés via <code>__init__.py</code></p>
</li>
<li><p>[ ] Ajoute une couche <code>services/</code> : les vues appellent les services, pas l'ORM directement</p>
</li>
<li><p>[ ] Crée <code>apps/utils/</code> comme bibliothèque interne du projet</p>
</li>
<li><p>[ ] Utilise des enums pour les choix de modèles (une source de vérité)</p>
</li>
<li><p>[ ] Suis le pattern URL en trois couches : <code>router.py</code> → <code>urls.py</code> → <code>backend/urls.py</code></p>
</li>
<li><p>[ ] Ajoute un dossier <code>scripts/</code> pour les commandes dev répétitives</p>
</li>
<li><p>[ ] Écris au moins une <code>docs/</code> minimale. Ton futur toi te remerciera.</p>
</li>
</ul>
<hr />
<h2>La suite</h2>
<p>Prochain article : <strong>Gestion standardisée des erreurs dans Django REST Framework, avec support i18n et exemple d'intégration React</strong>.</p>
<p>Toute API retourne des erreurs. La question, c'est de savoir si elles sont cohérentes, traduisibles, et utiles pour le frontend qui les consomme. Je vous montrerai le pattern <code>ErrorEnum</code> + <code>ERROR_MAP</code>, comment <code>drf-standardized-errors</code> formate chaque réponse, et comment câbler tout ça pour qu'une app React affiche le bon message dans la bonne langue sans aucune chaîne de <code>if/else</code>.</p>
<hr />
<p><em>Si tu remarques quelque chose d'incorrect, ou si tu fais les choses différemment avec de meilleurs résultats, commente en dessous. Je suis là pour apprendre autant que pour partager.</em> 🤝</p>
]]></content:encoded></item></channel></rss>