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

class src.module.tenant.infrastructure.managers.TenantQuerySet(QuerySet)[source]
active() QuerySet[source]

Filtre les tenants actifs uniquement.

by_slug(slug: str) QuerySet[source]

Filtre par slug.

by_status(status: str) QuerySet[source]

Filtre par statut.

for_user(user) QuerySet[source]

Retourne les tenants auxquels l’utilisateur a accès.

class src.module.tenant.infrastructure.managers.TenantManager(Manager)[source]

Manager par défaut pour le modèle Tenant. Utilise TenantQuerySet comme queryset de base.

MembershipManager

class src.module.tenant.infrastructure.managers.MembershipQuerySet(QuerySet)[source]
for_tenant(tenant) QuerySet[source]

Filtre les memberships par tenant.

for_user(user) QuerySet[source]

Filtre les memberships par utilisateur.

by_role(role: str) QuerySet

Filtre les memberships ayant un rôle spécifique.

active() QuerySet[source]

Filtre les memberships actives.

class src.module.tenant.infrastructure.managers.MembershipManager(Manager)[source]

Manager avec eager loading des relations (user, tenant, department).

TenantSpecificManager

Module : src.module.tenant.infrastructure.tenant_managers

class src.module.tenant.infrastructure.tenant_managers.TenantSpecificQuerySet(QuerySet)

QuerySet qui filtre automatiquement par tenant_id en lisant le current_tenant_var depuis les contextvars.

class src.module.tenant.infrastructure.tenant_managers.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é.

get_queryset() TenantSpecificQuerySet

Retourne le queryset filtré par le tenant courant.

unscoped() TenantSpecificQuerySet

Retourne le queryset sans filtrage tenant. À utiliser avec précaution (admin, migrations, reporting).

Exemple d’utilisation dans un modèle :

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 = <current>

# Accès cross-tenant (admin)
all_invoices = Invoice.objects.unscoped().all()

Base Models

Module : src.module.tenant.infrastructure.base_models

class src.module.tenant.infrastructure.base_models.SharedModel(models.Model)

Modèle abstrait pour les tables du schéma public (partagées). N’est pas filtré par tenant.

class src.module.tenant.infrastructure.base_models.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

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"