# Guide Avancé : Auth par Email avec `AbstractBaseUser`

Ce document décrit la mise en place d'un système d'authentification avancé et entièrement personnalisé pour Django. Cette approche est considérée comme une "best practice" pour les projets qui nécessitent un contrôle total sur le modèle utilisateur.

### Objectifs de ce guide :
- Utiliser l'**email comme identifiant de connexion** principal.
- Générer un **nom d'utilisateur (`username`) unique automatiquement** à partir de l'email.
- Construire le modèle `User` en héritant de `AbstractBaseUser` pour un contrôle maximal.
- Intégrer cette configuration avec Djoser et Simple JWT.

---

## AVERTISSEMENT CRUCIAL : À FAIRE AU DÉBUT DU PROJET

Cette configuration modifie la structure fondamentale de l'utilisateur Django. Elle doit impérativement être mise en place **AVANT** la première commande `python manage.py migrate`. Si des migrations ont déjà été appliquées, vous devez repartir d'une base de données vierge.

---

## Étape 1 : Création de l'application `authentication`

Nous créons une application dédiée qui contiendra toute notre logique d'authentification.

```bash
python manage.py startapp authentication
```

## Étape 2 : Création du `UserManager` personnalisé

Avec `AbstractBaseUser`, nous devons définir nous-mêmes comment les utilisateurs sont créés. Créez un nouveau fichier `authentication/managers.py`.

```python
# authentication/managers.py
from django.contrib.auth.models import BaseUserManager
from django.utils.text import slugify
from django.db import models

class UserManager(BaseUserManager):
    """
    Manager pour le modèle User personnalisé où l'email est l'identifiant unique.
    """
    def _generate_unique_username(self, email):
        """Génère un username unique à partir de l'email."""
        username = slugify(email.split('@')[0])
        if not self.model.objects.filter(username=username).exists():
            return username
        
        # Si le username existe déjà, ajoute un suffixe numérique
        i = 1
        while self.model.objects.filter(username=f"{username}{i}").exists():
            i += 1
        return f"{username}{i}"

    def create_user(self, email, password=None, **extra_fields):
        """
        Crée et sauvegarde un utilisateur avec l'email et le mot de passe donnés.
        """
        if not email:
            raise ValueError("L'adresse email doit être renseignée")
        
        email = self.normalize_email(email)
        extra_fields.setdefault('username', self._generate_unique_username(email))
        
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password=None, **extra_fields):
        """
        Crée et sauvegarde un superutilisateur.
        """
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError("Le superutilisateur doit avoir is_staff=True.")
        if extra_fields.get('is_superuser') is not True:
            raise ValueError("Le superutilisateur doit avoir is_superuser=True.")

        return self.create_user(email, password, **extra_fields)

```

## Étape 3 : Création du Modèle `User`

Maintenant, nous définissons notre modèle `User` dans `authentication/models.py`.

```python
# authentication/models.py
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.db import models
from django.utils import timezone
from .managers import UserManager

class User(AbstractBaseUser, PermissionsMixin):
    """
    Modèle utilisateur personnalisé utilisant l'email comme identifiant.
    """
    email = models.EmailField(unique=True)
    username = models.CharField(max_length=150, unique=True)
    
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    date_joined = models.DateTimeField(default=timezone.now)

    # Le champ utilisé pour la connexion
    USERNAME_FIELD = 'email'
    # Champs requis lors de la création d'un superutilisateur via la console
    REQUIRED_FIELDS = ['username']

    # Indiquer à Django d'utiliser notre manager personnalisé
    objects = UserManager()

    def __str__(self):
        return self.email

```

### Diagramme de Structure du Modèle et du Manager

Le diagramme suivant illustre comment notre `User` personnalisé et son `UserManager` héritent des classes de base de Django pour créer notre système d'authentification.

```mermaid
classDiagram
    class BaseUserManager {
        <<Abstract>>
        +create_user()
        +create_superuser()
    }
    class AbstractBaseUser {
        <<Abstract>>
        password
        last_login
        is_active
    }
    class PermissionsMixin {
        <<Mixin>>
        is_superuser
        groups
        user_permissions
    }

    class UserManager {
      +create_user(email, password)
      +create_superuser(email, password)
    }
    BaseUserManager <|-- UserManager

    class User {
      <<Model>>
      email
      username
      is_staff
      is_active
      date_joined
      objects: UserManager
    }
    AbstractBaseUser <|-- User
    PermissionsMixin <|-- User
    UserManager "1" --o "1" User : manages
```

## Étape 4 : Configuration de `settings.py`

1.  **Ajoutez `authentication` à `INSTALLED_APPS`**.
2.  **Définissez `AUTH_USER_MODEL`** :
    ```python
    AUTH_USER_MODEL = 'authentication.User'
    ```
3.  **Configurez `REST_FRAMEWORK`, `SIMPLE_JWT` et `DJOSER`** :
    ```python
    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework_simplejwt.authentication.JWTAuthentication',
        ),
        'DEFAULT_PERMISSION_CLASSES': (
            'rest_framework.permissions.IsAuthenticated',
        )
    }

    from datetime import timedelta
    SIMPLE_JWT = {
        "ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),
        "REFRESH_TOKEN_LIFETIME": timedelta(days=1),
    }

    DJOSER = {
        'PASSWORD_RESET_CONFIRM_URL': '#/password/reset/confirm/{uid}/{token}',
        'USERNAME_RESET_CONFIRM_URL': '#/username/reset/confirm/{uid}/{token}',
        'ACTIVATION_URL': '#/activate/{uid}/{token}',
        'SEND_ACTIVATION_EMAIL': False,
        'USER_CREATE_PASSWORD_RETYPE': True,
        'PASSWORD_RESET_SHOW_EMAIL_NOT_FOUND': True,
        # Spécifier le champ de connexion
        'LOGIN_FIELD': 'email',
        # Pointer vers nos serializers personnalisés
        'SERIALIZERS': {
            'user_create': 'authentication.serializers.UserCreateSerializer',
            'user': 'authentication.serializers.UserSerializer',
            'current_user': 'authentication.serializers.UserSerializer',
        },
    }
    ```

## Étape 5 : Création des Serializers pour Djoser

Créez le fichier `authentication/serializers.py`.

```python
# authentication/serializers.py
from djoser.serializers import UserCreateSerializer as BaseUserCreateSerializer
from djoser.serializers import UserSerializer as BaseUserSerializer

class UserCreateSerializer(BaseUserCreateSerializer):
    class Meta(BaseUserCreateSerializer.Meta):
        # Le modèle est automatiquement déduit de AUTH_USER_MODEL
        fields = ('id', 'email', 'username', 'password')

class UserSerializer(BaseUserSerializer):
    class Meta(BaseUserSerializer.Meta):
        fields = ('id', 'email', 'username')
```

## Étape 6 : URLs et Migrations

1.  **URLs** : La configuration dans `core/urls.py` reste identique à celle du guide Djoser précédent.
    ```python
    # core/urls.py
    from django.urls import path, include

    urlpatterns = [
        path('admin/', admin.site.urls),
        path('auth/', include('djoser.urls')),
        path('auth/', include('djoser.urls.jwt')),
    ]
    ```

2.  **Migrations** : C'est l'étape finale et cruciale.
    ```bash
    # 1. Créer le fichier de migration pour notre modèle User
    python manage.py makemigrations authentication

    # 2. Appliquer toutes les migrations pour créer la base de données
    python manage.py migrate
    ```

## Conclusion

Cette configuration représente le summum de la flexibilité pour l'authentification avec Django. Vous avez un contrôle total sur le modèle utilisateur, une authentification moderne basée sur l'email, et vous bénéficiez de toutes les fonctionnalités prêtes à l'emploi de Djoser. C'est la base idéale pour une application SaaS robuste et évolutive.

---

## Résumé pour l'Intégration Frontend

Voici un résumé des endpoints (vues) et des formats de données (serializers) que le frontend utilisera directement.

### 1. Serializers : Le format des données

Le fichier `authentication/serializers.py` définit la structure JSON attendue et retournée par l'API.

- **Pour l'inscription**, le frontend devra envoyer un objet JSON correspondant à `UserCreateSerializer` :
  ```json
  {
    "email": "test@example.com",
    "username": "testuser",
    "password": "unmotdepassesolide",
    "re_password": "unmotdepassesolide"
  }
  ```

- **Pour récupérer les informations de l'utilisateur**, le frontend recevra un objet JSON correspondant à `UserSerializer` :
  ```json
  {
    "id": 1,
    "email": "test@example.com",
    "username": "testuser"
  }
  ```

### 2. Vues : Les Endpoints de l'API

Vous n'avez **aucune vue à écrire** dans `authentication/views.py`. Djoser les fournit toutes. Le diagramme de séquence ci-dessous montre comment les différentes parties de notre backend interagissent lors d'une demande d'inscription.

```mermaid
sequenceDiagram
    participant Frontend
    participant Djoser View (/auth/users/)
    participant UserCreateSerializer
    participant UserManager
    participant Database

    Frontend->>+Djoser View: POST /auth/users/ (email, pass, ...)
    Djoser View->>+UserCreateSerializer: create(validated_data)
    UserCreateSerializer->>+UserManager: create_user(email, pass, ...)
    UserManager->>UserManager: _generate_unique_username(email)
    UserManager->>+Database: SAVE new User
    Database-->>-UserManager: User object with ID
    UserManager-->>-UserCreateSerializer: User object
    UserCreateSerializer-->>-Djoser View: Serialized User data
    Djoser View-->>-Frontend: 201 Created (User JSON)
```

Voici les URLs que votre frontend doit appeler :

| Action | Méthode | URL | Données (Corps de la requête) |
|---|---|---|---|
| **S'inscrire** | `POST` | `/auth/users/` | `{ "email", "username", "password", "re_password" }` |
| **Se Connecter** | `POST` | `/auth/jwt/create/` | `{ "email", "password" }` |
| **Rafraîchir le Token** | `POST` | `/auth/jwt/refresh/` | `{ "refresh": "<refresh_token>" }` |
| **Obtenir ses infos** | `GET` | `/auth/users/me/` | *(Aucune - Token dans le header)* |
| **Mettre à jour ses infos** | `PUT` / `PATCH` | `/auth/users/me/` | `{ "email": "...", "username": "..." }` |
| **Changer de mot de passe** | `POST` | `/auth/users/set_password/` | `{ "new_password", "re_new_password", "current_password" }` |
| **Demander un reset de mdp** | `POST` | `/auth/users/reset_password/` | `{ "email": "..." }` |
| **Confirmer le reset de mdp** | `POST` | `/auth/users/reset_password_confirm/` | `{ "uid", "token", "new_password", "re_new_password" }` |

---

## Analyse Critique : Personnalisations Avancées (Optionnel)

La configuration ci-dessus est extrêmement robuste. Cependant, à mesure qu'un projet grandit, deux besoins de personnalisation avancée peuvent émerger.

### 1. Personnalisation du Token JWT (Ajouter des "Claims")

#### À quoi ça sert ?

Par défaut, le token d'accès JWT ne contient que l'ID de l'utilisateur. Pour obtenir le `username` ou l'`email` de l'utilisateur, le frontend doit faire un appel à `/auth/users/me/` après la connexion. Pour optimiser et éviter cette requête supplémentaire, on peut **enrichir le token** en y ajoutant directement ces informations.

#### Comment faire ?

1.  **Créez un Serializer de Token personnalisé** dans `authentication/serializers.py` :

    ```python
    from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

    class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
        @classmethod
        def get_token(cls, user):
            token = super().get_token(user)

            # Ajoute des claims personnalisés
            token['username'] = user.username
            token['email'] = user.email

            return token
    ```

2.  **Créez une Vue personnalisée** dans `authentication/views.py` pour utiliser ce serializer :

    ```python
    from rest_framework_simplejwt.views import TokenObtainPairView
    from .serializers import CustomTokenObtainPairSerializer

    class CustomTokenObtainPairView(TokenObtainPairView):
        serializer_class = CustomTokenObtainPairSerializer
    ```

3.  **Modifiez `core/urls.py`** pour utiliser votre vue personnalisée au lieu de celle de `simplejwt` (fournie par Djoser) :

    ```python
    # core/urls.py
    from authentication.views import CustomTokenObtainPairView

    # ...
    path('auth/jwt/create/', CustomTokenObtainPairView.as_view(), name='jwt-create'),
    # ...
    ```

#### Recommandation

- **Pertinent si** : Votre frontend a besoin du `username` ou d'autres données simples sur chaque page. Cela peut économiser une requête API à chaque rechargement de la page.
- **À éviter** : Ne surchargez pas le token avec des données volumineuses ou sensibles. Le token est transmis dans chaque header de requête, il doit rester léger.

### 2. Création d'un `UserViewSet` personnalisé

#### À quoi ça sert ?

Djoser est parfait pour les opérations de l'utilisateur final sur son propre compte (`/users/me/`). Cependant, il ne fournit pas d'endpoints pour des cas d'usage plus complexes, notamment pour les **administrateurs**.

- Lister tous les utilisateurs.
- Un administrateur qui modifie le profil d'un autre utilisateur.
- Activer ou désactiver un compte via l'API.
- Ajouter des actions personnalisées (ex: `POST /api/users/{id}/promote/`).

#### Comment faire ? (Approche Hybride Recommandée)

N'essayez pas de remplacer Djoser. **Augmentez-le**. Gardez les URLs `/auth/` de Djoser pour les flux standard (login, reset mdp, etc.) et créez un `ViewSet` séparé pour vos besoins de gestion.

1.  **Créez le `UserViewSet`** dans `api/views.py` (l'application de votre logique métier) :

    ```python
    # api/views.py
    from rest_framework import viewsets
    from rest_framework.permissions import IsAdminUser
    from django.contrib.auth import get_user_model
    from .serializers import UserAdminSerializer # Un serializer spécifique pour l'admin

    User = get_user_model()

    class UserViewSet(viewsets.ReadOnlyModelViewSet):
        """ Vue pour les admins pour voir et gérer les utilisateurs."""
        queryset = User.objects.all()
        serializer_class = UserAdminSerializer
        permission_classes = [IsAdminUser] # Seuls les admins peuvent y accéder
    ```

2.  **Enregistrez ce `ViewSet`** dans `api/urls.py` :

    ```python
    # api/urls.py
    from .views import UserViewSet

    router.register(r'users', UserViewSet, basename='user')
    # ... reste de vos routes ...
    ```

#### Recommandation

- **Adoptez une approche hybride**. Laissez Djoser gérer l'authentification standard sur `/auth/`. Créez vos propres vues de gestion des utilisateurs sur `/api/users/` et sécurisez-les avec des permissions appropriées (`IsAdminUser`, etc.). C'est la solution la plus propre, la plus sécurisée et la plus évolutive.

---

## Architecture Recommandée : Séparation de l'Authentification et des Profils

Votre question initiale soulève un point d'architecture avancé et fondamental : faut-il tout mettre dans une seule application `authentication` ? La réponse est non. Pour une application destinée à grandir, la meilleure approche est de séparer les responsabilités.

**L'Architecture "Gold Standard" :**

1.  **Application `authentication`** : Son unique rôle est de définir **comment un utilisateur s'authentifie**. Elle contient le modèle `User` (`AbstractBaseUser`) et son `UserManager`. Elle ne contient ni vues, ni serializers, ni URLs métier.

2.  **Application `users` (ou `profiles`)** : Cette application gère **tout ce qui est lié à la logique métier** de l'utilisateur. Elle contiendrait :
    - Un modèle `Profile` lié au `User` par une relation `OneToOneField` pour stocker les données supplémentaires (bio, avatar, etc.).
    - Les `ViewSet` pour que les utilisateurs gèrent leur propre profil (ex: `GET /api/profile/me/`).
    - Les `ViewSet` pour que les administrateurs gèrent les utilisateurs (ex: `GET /api/users/`).

### Comment Mettre en Place cette Séparation ?

1.  **Créez deux applications** : `authentication` et `users`.
2.  **Placez le modèle `User` et le `UserManager`** dans `authentication/models.py` et `authentication/managers.py` comme nous l'avons fait dans ce guide.
3.  **Créez un modèle `Profile`** dans `users/models.py` :
    ```python
    from django.conf import settings
    from django.db import models

    class Profile(models.Model):
        user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="profile")
        bio = models.TextField(blank=True)
        # ... autres champs de profil ...

        def __str__(self):
            return self.user.username
    ```
4.  **Créez les profils automatiquement** avec des signaux. Créez `users/signals.py` :
    ```python
    from django.db.models.signals import post_save
    from django.dispatch import receiver
    from django.conf import settings
    from .models import Profile

    @receiver(post_save, sender=settings.AUTH_USER_MODEL)
    def create_user_profile(sender, instance=None, created=False, **kwargs):
        if created:
            Profile.objects.create(user=instance)
    ```
    Et activez le signal dans `users/apps.py` :
    ```python
    from django.apps import AppConfig

    class UsersConfig(AppConfig):
        default_auto_field = 'django.db.models.BigAutoField'
        name = 'users'

        def ready(self):
            import users.signals
    ```
5.  **Placez les `ViewSet` de gestion** (`UserViewSet`, `ProfileViewSet`) dans `users/views.py` et leurs URLs dans `users/urls.py` (qui seraient incluses sous `/api/`).

### Conclusion de l'Analyse

Cette séparation est la marque d'une application Django mûre et bien conçue. Elle respecte le principe de responsabilité unique et rend votre projet beaucoup plus facile à maintenir et à faire évoluer sur le long terme. **C'est l'approche que je vous recommande d'adopter pour votre projet.**

---

## Architecture Finale : Gestion de Plusieurs Types d'Utilisateurs (Clients, Fournisseurs)

Votre question sur la gestion de différents types d'utilisateurs (`Customer`, `Supplier`) est l'étape suivante logique de la conception d'une application complexe.

### L'Approche Recommandée : Un "Compte de Connexion" et des "Rôles Métier"

La pire approche serait d'ajouter un champ `role` sur le modèle `User` et des conditions (`if user.role == ...`) partout. L'architecture la plus propre et la plus évolutive consiste à considérer que le modèle `authentication.User` n'est qu'un **compte de connexion**, et que les concepts de `Customer` et `Supplier` sont des **rôles métier** distincts.

**Principe :**
- Le modèle `User` gère l'**authentification** (email, mot de passe).
- Des modèles `Customer` et `Supplier` distincts gèrent les **données métier**.
- Chaque modèle métier est lié au modèle `User` par une relation `OneToOneField`.

### Diagramme de l'Architecture Finale

```mermaid
erDiagram
    USER {
        uuid id PK
        string email
        string password
        string username
    }

    CUSTOMER {
        uuid user_id PK "FK to USER"
        string shipping_address
        string phone_number
    }

    SUPPLIER {
        uuid user_id PK "FK to USER"
        string company_name
        string vat_number
    }

    USER ||--o| CUSTOMER : "peut être un"
    USER ||--o| SUPPLIER : "peut être un"
```
*Un `User` peut être soit un `Customer`, soit un `Supplier` (ou aucun des deux), mais pas les deux en même temps si la logique métier l'impose. La relation `OneToOne` garantit qu'un `User` ne peut être lié qu'une seule fois à chaque rôle métier.*


### Comment Mettre en Place

1.  **Le modèle `authentication.User` reste inchangé**. Sa conception minimaliste est parfaite pour ce rôle de "compte de connexion" central.

2.  **Créez les modèles `Customer` et `Supplier`** dans votre application métier (ex: `api/models.py`).
    ```python
    from django.conf import settings
    from django.db import models

    class Customer(models.Model):
        user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
        shipping_address = models.TextField()
        # ... autres champs spécifiques au client

    class Supplier(models.Model):
        user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
        company_name = models.CharField(max_length=255)
        vat_number = models.CharField(max_length=50)
        # ... autres champs spécifiques au fournisseur
    ```
    *Note : Utiliser `primary_key=True` sur le champ `OneToOneField` est une optimisation élégante qui évite de créer une colonne `id` superflue.*

3.  **Créez des Endpoints d'Inscription Distincts**.
    Vous n'utiliserez plus l'endpoint `/auth/users/` de Djoser pour l'inscription. Vous créerez des `ViewSet` pour vos modèles métier.

    ```python
    # api/views.py
    from rest_framework import viewsets
    from .models import Customer, Supplier
    from .serializers import CustomerCreateSerializer, SupplierCreateSerializer
    from django.db import transaction

    class CustomerViewSet(viewsets.ModelViewSet):
        queryset = Customer.objects.all()
        serializer_class = CustomerCreateSerializer

        @transaction.atomic
        def perform_create(self, serializer):
            # La logique de création est maintenant dans le serializer
            serializer.save()

    # ... et un ViewSet similaire pour Supplier ...
    ```

4.  **Adaptez les Serializers pour une Création Atomique**.
    Le serializer doit gérer la création du `User` et du `Customer` en même temps.

    ```python
    # api/serializers.py
    from django.contrib.auth import get_user_model

    User = get_user_model()

    class CustomerCreateSerializer(serializers.ModelSerializer):
        # On inclut les champs du User directement
        password = serializers.CharField(write_only=True)
        email = serializers.EmailField(source="user.email")

        class Meta:
            model = Customer
            fields = ['email', 'password', 'shipping_address']

        def create(self, validated_data):
            user_data = validated_data.pop('user')
            # Crée le compte de connexion
            user = User.objects.create_user(email=user_data['email'], password=validated_data['password'])
            # Crée le profil métier
            customer = Customer.objects.create(user=user, **validated_data)
            return customer
    ```

### Conclusion Finale

Cette architecture est le summum de la flexibilité et de la propreté pour une application Django complexe. Elle sépare parfaitement les préoccupations, vous permettant de faire évoluer les rôles métier (`Customer`, `Supplier`) sans jamais avoir à toucher à la logique fondamentale d'authentification. C'est la voie à suivre pour un projet destiné à durer.

---

## Note d'Architecture : `UserManager` vs Logique Métier

Une question fréquente est : "Pourquoi ne pas mettre la logique de création d'un `Customer` dans le `UserManager` ?"

La réponse est un principe fondamental de conception logicielle : la **Séparation des Responsabilités**.

- **La responsabilité de `authentication.UserManager`** est de créer des `User`. C'est une application de bas niveau, technique et réutilisable. Elle ne doit **jamais** connaître les concepts métier de votre projet (Client, Fournisseur, Produit, etc.). Si elle en dépendait, elle ne serait plus réutilisable.

- **La responsabilité des `Serializers` ou `ViewSet` métier** (ex: `CustomerCreateSerializer`) est d'orchestrer la logique métier. Ils agissent comme un "chef d'orchestre" qui dit au `UserManager` (l'"ouvrier spécialisé") de créer un `User`, puis qui crée lui-même le `Customer` et les lie.

En respectant cette règle, vos dépendances vont dans le bon sens (`api` -> `authentication`) et votre code reste modulaire et propre.

## Analyse d'Architecture Ultime : Applications dédiées par Rôle Métier

La question suivante est : "Faut-il mettre les modèles `Customer` et `Supplier` dans la même application `api` ?"

Si ces modèles sont complexes et ont des logiques très différentes, la meilleure pratique est de suivre la philosophie de Django : **créer des petites applications focalisées sur une seule responsabilité**.

**L'architecture la plus évolutive serait donc :**

- **`authentication`**: Gère le modèle `User`.
- **`customers`**: Une nouvelle application qui gère le modèle `Customer`, ses vues, ses serializers, etc.
- **`suppliers`**: Une autre nouvelle application pour le modèle `Supplier`.
- **`api`**: Cette application ne contiendrait plus de modèles, mais servirait de point d'entrée pour agréger les URLs des autres applications (`/api/v1/customers/`, `/api/v1/suppliers/`).

Cette approche granulaire peut sembler plus complexe au départ, mais elle rend le projet infiniment plus simple à maintenir et à faire évoluer en équipe sur le long terme. Chaque développeur peut travailler sur une application sans craindre d'impacter les autres.

---

## Organisation du Code de l'API : Application `api` vs API par Application

Une dernière question d'architecture se pose : où placer le code de l'API (serializers, vues, urls) ?

#### L'Anti-Pattern : Une seule application `api` centralisée

Certains projets créent une application unique nommée `api` qui contient tous les serializers et vues pour tous les modèles (`Customer`, `Supplier`, `Product`, etc.).

- **Inconvénients** : Cette approche viole le principe de modularité de Django. L'application `api` devient un fourre-tout, ses fichiers `views.py` et `serializers.py` deviennent gigantesques, et cela crée un couplage fort entre toutes les parties de votre projet. Cela rend la maintenance et la réutilisation très difficiles.

#### L'Approche Recommandée par Django : Chaque Application contient sa propre API

La philosophie de Django est de créer des applications **autonomes et réutilisables**. Par conséquent, la logique pour exposer un modèle via une API doit résider dans la même application que le modèle lui-même.

**Structure de Projet Finale Recommandée :**

```
finance-saas-backend/
├── authentication/         # Gère UNIQUEMENT le modèle User et son manager.
│   ├── models.py
│   └── managers.py
├── customers/              # App pour la logique métier des Clients.
│   ├── models.py           # (Customer model avec OneToOne vers User)
│   ├── admin.py
│   ├── serializers.py      # <-- Serializers pour Customer
│   ├── views.py            # <-- ViewSet pour Customer
│   └── urls.py             # <-- URLs pour /api/customers/
├── suppliers/              # App pour la logique métier des Fournisseurs.
│   ├── models.py           # (Supplier model avec OneToOne vers User)
│   ├── serializers.py      # <-- Serializers pour Supplier
│   ├── views.py            # <-- ViewSet pour Supplier
│   └── urls.py             # <-- URLs pour /api/suppliers/
└── core/                   # Configuration générale du projet.
    └── urls.py             # Fichier d'URL racine.
```

Le fichier `core/urls.py` assemblerait ensuite l'API globale :

```python
# core/urls.py
urlpatterns = [
    path('admin/', admin.site.urls),
    # URLs d'authentification gérées par Djoser
    path('auth/', include('djoser.urls')),
    path('auth/', include('djoser.urls.jwt')),
    
    # URLs de l'API métier, versionnées
    path('api/v1/customers/', include('customers.urls')),
    path('api/v1/suppliers/', include('suppliers.urls')),
]
```

Cette structure est la quintessence d'un projet Django bien architecturé. Elle est propre, modulaire, évolutive et facile à maintenir.

---

## Scénario d'Architecture Final : Coopérative avec Membres et Clients

Ce cas d'usage est une excellente synthèse de tous les principes abordés. 

**Règles métier :**
1.  Un `Membre` est une personne qui a apporté un capital social.
2.  Un `Membre` est toujours, par défaut, un `Client`.
3.  Un `Client` n'est pas forcément un `Membre`, mais peut le devenir.

L'erreur serait d'ajouter un booléen `is_member` sur le modèle `User`. La solution la plus élégante et robuste est l'**héritage multi-tables** de Django.

### L'Architecture Recommandée : Héritage et Composition

- **`authentication.User`** : Reste le "compte de connexion" pur.
- **`customers.Customer`** : Modélise le rôle de base d'un client, avec ses attributs propres. Il est lié par une relation `OneToOneField` au `User`.
- **`members.Member`** : Ce modèle **hérite** de `customers.Customer`. Il représente la spécialisation du rôle de client. Un `Membre` **est un** `Client` qui possède des attributs et des droits supplémentaires.

### Diagramme de l'Architecture de la Coopérative

```mermaid
erDiagram
    USER {
        uuid id PK
        string email
    }
    CUSTOMER {
        uuid user_id PK "FK to USER"
        string customer_specific_field
    }
    MEMBER {
        uuid customer_ptr_id PK "FK to CUSTOMER"
        decimal capital_contribution
    }

    USER ||--o| CUSTOMER : "a un profil de"
    CUSTOMER ||--o| MEMBER : "peut être promu en"
```
*Note : Le diagramme illustre que chaque `Customer` est lié à un `User`. Un `Customer` peut éventuellement avoir un profil `Member` lié. La relation d'héritage de Django est représentée ici par cette composition de relations `OneToOne`.*

### Implémentation des Modèles

1.  **Modèle `Customer` (`customers/models.py`)**
    ```python
    from django.conf import settings
    from django.db import models

    class Customer(models.Model):
        user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
        # ... autres champs spécifiques au client ...
    ```

2.  **Modèle `Member` (`members/models.py`)**
    ```python
    from django.db import models
    from customers.models import Customer

    class Member(Customer):
        # Ce modèle hérite de tous les champs de Customer.
        # Django crée automatiquement un OneToOneField vers Customer en coulisses.
        capital_contribution = models.DecimalField(max_digits=10, decimal_places=2)
        membership_date = models.DateField(auto_now_add=True)
        # ... autres champs spécifiques au membre ...
    ```

### Logique d'Inscription et d'Évolution

- **Inscription d'un Client** : Votre API (`/api/customers/`) crée un `User` et un `Customer`.
- **Inscription d'un Membre** : Votre API (`/api/members/`) crée un `User` et un `Member`. En créant le `Member`, Django créera **automatiquement** l'entrée `Customer` associée.
- **Un Client devient Membre** : La logique est simple et élégante. Il suffit de créer un objet `Member` en le liant au `Customer` existant. Django s'occupe du reste.
  ```python
  # Vue ou service pour promouvoir un client
  from customers.models import Customer
  from members.models import Member

  def promote_customer_to_member(customer_id, contribution):
      customer_instance = Customer.objects.get(pk=customer_id)
      # Crée l'objet Membre, ce qui établit la relation d'héritage
      member, created = Member.objects.get_or_create(
          customer=customer_instance,
          defaults={'capital_contribution': contribution}
      )
      return member
  ```

Cette architecture finale est extrêmement puissante car elle utilise les fonctionnalités natives de Django pour modéliser précisément et proprement des règles métier complexes, tout en maintenant une séparation des responsabilités parfaite.

---

## Scénario Final : Coopérative avec Membres et Clients

Votre cas d'usage spécifique d'une coopérative avec des `Membres` et des `Clients` est un excellent exemple pour appliquer tous les principes que nous avons vus.

**Règles métier :**
- Un `Membre` est une personne qui a apporté un capital social.
- Un `Membre` est toujours, par défaut, un `Client`.
- Un `Client` n'est pas forcément un `Membre`, mais peut le devenir.

L'erreur à ne pas commettre serait d'ajouter un booléen `is_member` sur le modèle `User`. Cela viole la séparation des responsabilités. La bonne approche utilise l'héritage de modèles de Django.

### L'Architecture Recommandée : Héritage Multi-Tables

1.  **`authentication.User`** : Reste notre modèle de "compte de connexion". Il ne change pas.
2.  **`customers.Customer`** : Ce modèle représente le **rôle de base** dans votre système. Il est lié par un `OneToOneField` au `User`.
3.  **`members.Member`** : Ce modèle **hérite** du modèle `Customer`. En Django, cela modélise parfaitement la relation "un Membre **est un** Client avec des attributs en plus".

### Diagramme de l'Architecture

```mermaid
erDiagram
    USER {
        uuid id PK
        string email
    }
    CUSTOMER {
        uuid user_id PK
        string shipping_address
    }
    MEMBER {
        uuid customer_id PK
        decimal capital_contribution
    }

    USER ||--o| CUSTOMER : "a un profil"
    CUSTOMER ||--|| MEMBER : "est un (héritage)"
```
*Note : `CUSTOMER <|-- MEMBER` signifie que `Member` hérite de `Customer`. Django créera automatiquement une relation `OneToOneField` en coulisses.*

### Implémentation

1.  **Modèle `Customer` (`customers/models.py`)**
    ```python
    from django.conf import settings
    from django.db import models

    class Customer(models.Model):
        user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
        # ... champs spécifiques au client ...
    ```

2.  **Modèle `Member` (`members/models.py`)**
    ```python
    from django.db import models
    from customers.models import Customer

    class Member(Customer):
        capital_contribution = models.DecimalField(max_digits=10, decimal_places=2)
        # ... autres champs spécifiques au membre ...
    ```

### Logique d'Inscription et d'Évolution

- **Inscription d'un Client** : Votre API (`/api/customers/`) crée un `User` et un `Customer`.
- **Inscription d'un Membre** : Votre API (`/api/members/`) crée un `User` et un `Member`. En faisant cela, Django créera automatiquement l'entrée correspondante dans la table `Customer`.
- **Un Client devient Membre** : C'est très simple. Vous n'avez pas besoin de toucher au `User` ou au `Customer` existant. Il vous suffit de créer un nouvel objet `Member` en le liant au `Customer` existant :
  ```python
  existing_customer = Customer.objects.get(...)
  Member.objects.create(customer=existing_customer, capital_contribution=100.00)
  ```

Cette architecture finale est extrêmement puissante car elle utilise les fonctionnalités natives de Django (héritage, relations) pour modéliser précisément et proprement des règles métier complexes, tout en maintenant une séparation des responsabilités parfaite.

---

## Architecture de l'Autorisation : Gestion du Personnel Interne (Staff)

Pour gérer les différents niveaux d'accès du personnel (`Gestionnaire`, `Superviseur`, `IT`), l'approche recommandée est d'utiliser le **système de Groupes et Permissions de Django**, et non un modèle `Staff`.

**Principe** : On ne vérifie jamais le rôle d'un utilisateur, mais la **permission** qu'il possède.

#### Étape 1 : Créer des Permissions Personnalisées

Ajoutez des permissions spécifiques à vos modèles pour définir des capacités métier.

```python
# accounts/models.py
class Account(models.Model):
    # ... champs ...
    account_manager = models.ForeignKey(settings.AUTH_USER_MODEL, ...)

    class Meta:
        permissions = [
            ("view_all_accounts", "Peut voir tous les comptes de la coopérative"),
        ]
```

#### Étape 2 : Créer les Groupes via une Migration de Données

Automatisez la création des rôles de votre personnel.

```python
# accounts/migrations/0003_create_staff_groups.py
# ... (code de la migration pour créer les groupes "Superviseurs", "Gestionnaires")
# ... (code pour assigner la permission "view_all_accounts" au groupe "Superviseurs")
```

#### Étape 3 : Appliquer la Logique dans les Vues

Le filtrage des données se fait dans les `ViewSet` en fonction des permissions de l'utilisateur connecté.

```python
# accounts/views.py
class AccountViewSet(viewsets.ModelViewSet):
    def get_queryset(self):
        user = self.request.user
        if user.has_perm('accounts.view_all_accounts'):
            return Account.objects.all()
        if user.groups.filter(name='Gestionnaires de Comptes').exists():
            return Account.objects.filter(account_manager=user)
        return Account.objects.none()
```

Cette approche est sécurisée, flexible et parfaitement intégrée à l'écosystème Django.
