========================== Tenant — Managers ========================== Les managers fournissent le filtrage automatique des querysets par tenant et des méthodes d'accès optimisées. Modules : - ``src.module.tenant.infrastructure.managers`` - ``src.module.tenant.infrastructure.tenant_managers`` TenantManager ============= Module : ``src.module.tenant.infrastructure.managers`` .. module:: src.module.tenant.infrastructure.managers :synopsis: Managers pour Tenant et TenantMembership .. class:: TenantQuerySet(QuerySet) .. method:: active() -> QuerySet Filtre les tenants actifs uniquement. .. method:: by_slug(slug: str) -> QuerySet Filtre par slug. .. method:: by_status(status: str) -> QuerySet Filtre par statut. .. method:: for_user(user) -> QuerySet Retourne les tenants auxquels l'utilisateur a accès. .. class:: TenantManager(Manager) Manager par défaut pour le modèle Tenant. Utilise ``TenantQuerySet`` comme queryset de base. MembershipManager ================= .. class:: MembershipQuerySet(QuerySet) .. method:: for_tenant(tenant) -> QuerySet Filtre les memberships par tenant. .. method:: for_user(user) -> QuerySet Filtre les memberships par utilisateur. .. method:: by_role(role: str) -> QuerySet Filtre les memberships ayant un rôle spécifique. .. method:: active() -> QuerySet Filtre les memberships actives. .. class:: MembershipManager(Manager) Manager avec eager loading des relations (user, tenant, department). TenantSpecificManager ===================== Module : ``src.module.tenant.infrastructure.tenant_managers`` .. module:: src.module.tenant.infrastructure.tenant_managers :synopsis: Manager avec auto-filtrage par tenant .. class:: TenantSpecificQuerySet(QuerySet) QuerySet qui filtre automatiquement par ``tenant_id`` en lisant le ``current_tenant_var`` depuis les contextvars. .. class:: TenantSpecificManager(Manager) Manager qui applique l'auto-filtrage tenant sur tous les querysets. **Comportement fail-closed :** Si aucun contexte tenant n'est défini, retourne un queryset vide (``none()``). Aucune donnée n'est exposée par défaut. **Exception backend schema :** En mode schema, le ``search_path`` PostgreSQL fournit déjà l'isolation. Le filtrage applicatif est redondant et est donc désactivé. .. method:: get_queryset() -> TenantSpecificQuerySet Retourne le queryset filtré par le tenant courant. .. method:: unscoped() -> TenantSpecificQuerySet Retourne le queryset **sans** filtrage tenant. **À utiliser avec précaution** (admin, migrations, reporting). **Exemple d'utilisation dans un modèle :** .. code-block:: python from src.module.tenant.infrastructure.tenant_managers import TenantSpecificManager class Invoice(models.Model): tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE) number = models.CharField(max_length=50) total = models.DecimalField(max_digits=10, decimal_places=2) # Remplace le manager par défaut objects = TenantSpecificManager() class Meta: db_table = "invoices" # Utilisation — le filtrage est automatique invoices = Invoice.objects.all() # SELECT * FROM invoices WHERE tenant_id = # Accès cross-tenant (admin) all_invoices = Invoice.objects.unscoped().all() Base Models =========== Module : ``src.module.tenant.infrastructure.base_models`` .. module:: src.module.tenant.infrastructure.base_models :synopsis: Modèles de base tenant-aware .. class:: SharedModel(models.Model) Modèle abstrait pour les tables du schéma public (partagées). N'est pas filtré par tenant. .. class:: TenantSpecificModel(models.Model) Modèle abstrait pour les tables spécifiques au tenant. - Utilise ``TenantSpecificManager`` comme manager par défaut - Ajoute une garde sur ``save()`` : empêche la sauvegarde sans contexte tenant actif - Lève ``NoTenantContextError`` si aucun tenant n'est défini .. code-block:: python from src.module.tenant.infrastructure.base_models import TenantSpecificModel class Product(TenantSpecificModel): name = models.CharField(max_length=255) price = models.DecimalField(max_digits=10, decimal_places=2) class Meta: db_table = "products"