django-tenant-users Documentation

repository·master·Indexed 19 days ago

https://github.com/corvia/django-tenant-users

A Django app that extends django-tenants to incorporate global multi-tenant users. It provides a hybrid approach to identity and access control, allowing users to authenticate once globally while maintaining tenant-specific permissions and roles. Key features include the TenantBase model for tenant ownership and management, UserProfile for tenant-aware authentication, and a soft-deletion strategy for users and tenants to preserve data integrity.

Tokens
4.3K
Snippets
16
Records
26
Agent score
64%

What's inside django-tenant-users

  1. Overview of django-tenant-users

    master

    django-tenant-users is an enhancement for django-tenants designed to refine user management in multi-tenant Django applications. It provides a hybrid approach to identity and access control:

    • Global Authentication: Users authenticate once at a global level, allowing them to access multiple tenants without needing separate accounts for each.
    • Tenant-Specific Permissions: While authentication is global, permissions are scoped to individual tenants. This ensures that a user's roles and capabilities are strictly enforced within the context of the specific tenant they are currently accessing, including the public tenant.
    • Django Integration: The package is built to work seamlessly with Django's native user and permissions frameworks.
  2. How UserBackend handles multi-tenant authentication

    master

    The UserBackend class manages authentication across multiple tenants. It provides global-level authentication while maintaining tenant-specific data for each user.

    It works by leveraging:

    • UserProfile for the authentication process.
    • UserTenantPermissions for authorization.
    • Facade classes that direct requests to the appropriate tenant-specific or global locations.
  3. Why a custom user model is required

    master

    In multi-tenant applications, a single user often needs to interact with multiple tenants. django-tenant-users requires a custom user model to bridge this gap. This design allows for:

    1. Global Authentication: A single authentication mechanism for the user.
    2. Tenant-Specific Data: Associating users with specific tenants to maintain data isolation at the tenant level.
  4. How user and tenant deletion works

    master

    To preserve data integrity, django-tenant-users uses a soft-deletion strategy rather than permanent removal.

    User "Deletion"

    Instead of deleting the record, the following occurs:

    • is_active, is_staff, and is_superuser are set to False.
    • The user is disassociated from any tenants they own.
    • All permissions across all associated tenants are removed.

    Tenant "Deletion"

    Tenants can be deleted manually or automatically (if a deleted user was the owner). During deletion:

    • All users are disassociated from the tenant.
    • Ownership of the tenant's schema is transferred to the public schema's owner.
    • The tenant's URL is renamed to a format like ownerid-timestamp-originalurl to free up the original URL while preserving history.

    Schema Naming

    To prevent database conflicts, every tenant's schema name is appended with a timestamp (seconds since the epoch) to ensure uniqueness.

  5. Use TenantBase to extend tenant management

    master

    To enhance tenant management with user permissions, use TenantBase. It extends the TenantMixin from django-tenants by providing several key capabilities:

    • Tenant Ownership: Assigns an owner to each tenant.
    • User Management: Provides methods to add or remove users from a specific tenant.
    • Ownership Transfer: Includes utilities to change the owner of a tenant.
    • Safe Deletion: Implements a mechanism to handle tenant removal without immediate data loss.
  6. Provision a new tenant with provision_tenant()

    master

    To set up a new tenant, use the provision_tenant() task. This function creates a new tenant and its associated domain.

    Important: Provisioning creates a new database schema. It is highly recommended to run this task asynchronously (e.g., using Celery) to avoid blocking the request-response cycle.

    Arguments:

    • name: The display name of the tenant.
    • slug: The slug used for the domain.
    • owner: The user instance that will own the tenant.
    • domain_extra_data (optional): A dictionary of extra fields to populate on the tenant's Domain model.
    • tenant_type (optional): Used if you are leveraging django-tenants Multi-type Tenants feature.
    from tenant_users.tenants.tasks import provision_tenant
    from users.models import TenantUser
    
    provision_tenant_owner = TenantUser.objects.get(email="admin@evilcorp.com")
    
    tenant, domain = provision_tenant(
        "EvilCorp",
        "evilcorp",
        provision_tenant_owner,
        # optionally, pass extra fields for the tenant's Domain model
        domain_extra_data={"notes": "created by provisioning"},
    )
  7. Provision a Public Tenant

    master

    You can provision the public tenant using the create_public_tenant utility or the management command. This ensures the public tenant is correctly initialized for django-tenant-users.

    from tenant_users.tenants.utils import create_public_tenant
    
    create_public_tenant(
        domain_url="public.domain.com",
        owner_email="admin@domain.com",
        domain_extra_data={"notes": "created by installer"},
        owner_extra_data={"first_name": "Admin"},
    )

    Or via CLI:

    manage.py create_public_tenant --domain_url public.domain.com --owner_email admin@domain.com
  8. Enable Tenant Access Middleware

    master

    To restrict users to only the tenants they have access to, add TenantAccessMiddleware to your MIDDLEWARE setting. It should be placed after django.contrib.auth.middleware.AuthenticationMiddleware. If a user lacks access to the requested tenant, a 404 error is raised.

    To customize the error message, set TENANT_USERS_ACCESS_ERROR_MESSAGE.

    MIDDLEWARE = [
        ...
        "django.contrib.auth.middleware.AuthenticationMiddleware",
        ...
        "tenant_users.tenants.middleware.TenantAccessMiddleware",
        ...
    ]
    
    TENANT_USERS_ACCESS_ERROR_MESSAGE = "Custom access denied message."
  9. Optimize Tenant Permissions Queries

    master

    To avoid N+1 query problems when accessing related data like profile or groups during permission checks, use the TENANT_USERS_PERMS_QUERYSET setting to provide a custom queryset function.

    Using the Built-in Optimizer

    Use the provided utility to eagerly load profile and groups:

    TENANT_USERS_PERMS_QUERYSET = (
        "tenant_users.permissions.utils.get_optimized_tenant_perms_queryset"
    )

    Creating a Custom Optimizer

    Define a function that returns a QuerySet instance using select_related or prefetch_related:

    # myapp/utils.py
    from tenant_users.permissions.models import UserTenantPermissions
    
    def get_optimized_perms_queryset():
        return UserTenantPermissions.objects.select_related(
            "profile"
        ).prefetch_related(
            "groups",
            "user_permissions"
        )
    
    # settings.py
    TENANT_USERS_PERMS_QUERYSET = "myapp.utils.get_optimized_perms_queryset"