### Install Project Dependencies Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/conributing.rst Install all necessary project dependencies using Poetry. ```bash poetry install ``` -------------------------------- ### Install and Configure Django Tenant Users Source: https://context7.com/corvia/django-tenant-users/llms.txt Install the package and configure settings.py for SHARED_APPS, TENANT_APPS, authentication backend, user model, and domain settings. ```python # settings.py pip install django-tenant-users # shell command SHARED_APPS = [ "django_tenants", "django.contrib.contenttypes", "django.contrib.auth", "tenant_users.permissions", # shared: stores UserTenantPermissions in public schema "tenant_users.tenants", # shared only: TenantBase, UserProfile live in public schema "companies", # your app containing the TenantBase subclass "users", # your app containing the UserProfile subclass ] TENANT_APPS = [ "django.contrib.contenttypes", "django.contrib.auth", "tenant_users.permissions", # tenant schema: per-tenant UserTenantPermissions table ] INSTALLED_APPS = list(SHARED_APPS) + [app for app in TENANT_APPS if app not in SHARED_APPS] # Custom authentication backend — required AUTHENTICATION_BACKENDS = ("tenant_users.permissions.backend.UserBackend",) # Base domain used when constructing tenant subdomains TENANT_USERS_DOMAIN = "example.com" # Point AUTH_USER_MODEL at your UserProfile subclass AUTH_USER_MODEL = "users.TenantUser" # Optional: enable session sharing across all subdomains (SSO) SESSION_COOKIE_DOMAIN = ".example.com" # Optional: optimize N+1 queries when reading tenant permissions TENANT_USERS_PERMS_QUERYSET = ( "tenant_users.permissions.utils.get_optimized_tenant_perms_queryset" ) ``` -------------------------------- ### Install django-tenant-users using pip Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/installation.rst Use this command to install the package with your preferred package manager. ```bash pip install django-tenant-users ``` -------------------------------- ### Install Poetry Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/conributing.rst Install Poetry, a dependency management tool for Python, if you haven't already. ```bash pip install poetry ``` -------------------------------- ### Handling Django Tenant Users Custom Exceptions Source: https://context7.com/corvia/django-tenant-users/llms.txt Catch specific exceptions during tenant provisioning to handle different failure scenarios gracefully. This example shows how to manage errors related to inactive users, existing resources, schema issues, and protected deletions. ```python from tenant_users.tenants.models import ( InactiveError, # operation requires an active user ExistsError, # resource already exists (user, tenant, public tenant) DeleteError, # protected deletion attempt (owner, public tenant owner) SchemaError, # wrong schema context, or invalid tenant type ) from tenant_users.tenants.tasks import provision_tenant from users.models import TenantUser owner = TenantUser.objects.get(email="alice@example.com") try: tenant, domain = provision_tenant("Corp", "corp", owner) except InactiveError: # owner.is_active is False pass except ExistsError: # domain "corp.example.com" already registered pass except SchemaError: # invalid tenant_type when HAS_MULTI_TYPE_TENANTS=True pass except DeleteError: # attempted to remove tenant owner without transfer pass ``` -------------------------------- ### Get Current Tenant Utility Source: https://context7.com/corvia/django-tenant-users/llms.txt The get_current_tenant() utility function retrieves the TenantBase instance for the active schema. It's useful within tenant_context or regular tenant requests. ```python from tenant_users.tenants.utils import get_current_tenant from django_tenants.utils import tenant_context from companies.models import Company company = Company.objects.get(slug="acme") with tenant_context(company): current = get_current_tenant() print(current.schema_name) # "acme_1712000000" print(current.name) # "Acme Corp" # Typical use inside a view or signal handler: def some_view(request): tenant = get_current_tenant() members = tenant.user_set.all() return members ``` -------------------------------- ### Provision Public Tenant using Management Command Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/installation.rst Alternatively, use the manage.py create_public_tenant command to provision the public tenant. This command provides a convenient way to set up the initial public tenant. ```bash manage.py create_public_tenant --domain_url public.domain.com --owner_email admin@domain.com ``` -------------------------------- ### Provision Public Tenant using Python Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/installation.rst Use the create_public_tenant utility function to provision the public tenant. This function handles the creation of the public tenant and its associated domain and owner. ```python from tenant_users.tenants.utils import create_public_tenant create_public_tenant( domain_url="public.domain.com", owner_email="admin@domain.com", # optionally, pass extra fields for the Domain model domain_extra_data={"notes": "created by installer"}, # optionally, pass extra fields for the owner user owner_extra_data={"first_name": "Admin"}, ) ``` -------------------------------- ### Create Superuser with TenantUser Source: https://context7.com/corvia/django-tenant-users/llms.txt Use `create_superuser` to create a user with `is_staff=True`, `is_superuser=True`, and `is_verified=True` within the public tenant. This is typically used once to set up an initial administrator account. ```python from users.models import TenantUser admin = TenantUser.objects.create_superuser( email="admin@example.com", password="adminpass", ) print(admin.is_superuser) # True (via public-schema UserTenantPermissions) print(admin.is_staff) # True print(admin.is_verified) # True ``` -------------------------------- ### Provision a New Tenant Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/using.rst Use this function to set up a new tenant in your application. It requires the tenant name, schema name, and the owner user. You can also pass extra data for the tenant's Domain model. ```python from tenant_users.tenants.tasks import provision_tenant from users.models import TenantUser provision_tenant_owner = CustomUserModel.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"}, ) ``` -------------------------------- ### Switch to Custom Authentication Backend Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/installation.rst Configure AUTHENTICATION_BACKENDS in settings.py to use the UserBackend provided by django-tenant-users for proper tenant-aware authentication. ```python AUTHENTICATION_BACKENDS = ("tenant_users.permissions.backend.UserBackend",) ``` -------------------------------- ### Configure Multi-Type Tenants in Settings Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/installation.rst Set up the TENANT_TYPES dictionary in settings.py if you are using the multi-type tenants feature. Ensure your custom apps are correctly placed. ```python TENANT_TYPES = { "public": { "APPS": [ "django.contrib.auth", "django.contrib.contenttypes", "tenant_users.permissions", "tenant_users.tenants", "companies", "users", # Add other apps as needed ], "URLCONF": "myproject.urls.public", }, "type1": { "APPS": [ "django.contrib.auth", "django.contrib.contenttypes", "tenant_users.permissions", # Add other apps as needed ], }, # Add other tenant types as needed } ``` -------------------------------- ### Format Documentation Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/conributing.rst Format documentation files using rstfmt. ```bash pip install rstfmt poetry run rstfmt docs ``` -------------------------------- ### provision_tenant Source: https://context7.com/corvia/django-tenant-users/llms.txt Creates a new tenant schema, its primary domain, and adds the owner as a superuser in that tenant in a single atomic operation. Returns `(tenant, domain)`. Use Celery or a background task for large deployments since schema creation can be slow. ```APIDOC ## provision_tenant Creates a new tenant schema, its primary domain, and adds the owner as a superuser in that tenant in a single atomic operation. Returns `(tenant, domain)`. Use Celery or a background task for large deployments since schema creation can be slow. ### Parameters - **tenant_name** (str) - Required - The name of the tenant. - **tenant_slug** (str) - Required - The slug for the tenant, used for subdomain. - **owner** (TenantUser) - Required - The user object who will own the tenant. - **is_superuser** (bool) - Optional - Whether the owner should be a superuser in this tenant. - **is_staff** (bool) - Optional - Whether the owner should be staff in this tenant. - **domain_extra_data** (dict) - Optional - Additional data for the tenant's domain. - **tenant_extra_data** (dict) - Optional - Additional data for the tenant model. - **tenant_type** (str) - Optional - The type of tenant, if using multi-type tenants. ### Returns - **tuple**: A tuple containing the tenant object and the primary domain object. ### Raises - **InactiveError**: If the owner account is inactive. - **ExistsError**: If a tenant with the given slug already exists. ### Example ```python from tenant_users.tenants.tasks import provision_tenant from users.models import TenantUser owner = TenantUser.objects.get(email="alice@example.com") try: tenant, domain = provision_tenant( tenant_name="Acme Corp", tenant_slug="acme", owner=owner, is_superuser=True, tenant_extra_data={"description": "Acme Corp tenant"}, ) except InactiveError: print("Cannot provision — owner account is inactive.") except ExistsError: print("Tenant URL already taken — choose a different slug.") ``` ``` -------------------------------- ### Set Tenant Domain for Provisioning Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/installation.rst Define TENANT_USERS_DOMAIN in settings.py to specify the default domain used when provisioning new tenants. ```python TENANT_USERS_DOMAIN = "domain.com" ``` -------------------------------- ### Create User with TenantUser Source: https://context7.com/corvia/django-tenant-users/llms.txt Use `create_user` to create a new global user in the public schema. This user is immediately added to the public tenant without specific permissions. Ensure the database connection is on the public schema when calling this method. It raises an `ExistsError` if an active user with the same email already exists. ```python from users.models import TenantUser # Basic user creation user = TenantUser.objects.create_user( email="bob@example.com", password="securepassword123", ) print(user.pk) # auto-generated integer PK print(user.is_active) # True print(user.is_staff) # False (resolved through tenant perms) # With extra profile fields user = TenantUser.objects.create_user( email="carol@example.com", password="s3cr3t", name="Carol Smith", is_staff=False, ) # Raises ExistsError if an active user with that email already exists from tenant_users.tenants.models import ExistsError try: TenantUser.objects.create_user("bob@example.com", "pass") except ExistsError as e: print(e) # "User already exists!" ``` -------------------------------- ### Create Public Tenant Source: https://context7.com/corvia/django-tenant-users/llms.txt Call `create_public_tenant` once during initial deployment to set up the mandatory public tenant (schema 'public'). This function creates the owner user and the primary domain. It should be called before running `migrate_schemas`. The function can raise `ExistsError` if the public tenant already exists. ```python from tenant_users.tenants.utils import create_public_tenant from tenant_users.tenants.models import ExistsError try: public_tenant, domain, owner = create_public_tenant( domain_url="example.com", owner_email="superadmin@example.com", is_superuser=True, is_staff=True, owner_extra_data={"name": "Super Admin"}, domain_extra_data={"notes": "primary domain"}, tenant_extra_data={}, ) print(public_tenant.schema_name) # "public" print(domain.domain) # "example.com" print(owner.email) # "superadmin@example.com" except ExistsError: print("Public tenant already exists — skipping.") # Equivalent management command: # python manage.py create_public_tenant --domain_url example.com --owner_email superadmin@example.com ``` -------------------------------- ### UserProfileManager.create_superuser Source: https://context7.com/corvia/django-tenant-users/llms.txt Creates a user with `is_staff=True`, `is_superuser=True`, and `is_verified=True` in the public tenant. Typically called only once to bootstrap an admin account. ```APIDOC ## UserProfileManager.create_superuser Creates a user with `is_staff=True`, `is_superuser=True`, and `is_verified=True` in the public tenant. Typically called only once to bootstrap an admin account. ### Parameters - **email** (str) - Required - The email address of the superuser. - **password** (str) - Required - The password for the superuser. ### Example ```python from users.models import TenantUser admin = TenantUser.objects.create_superuser( email="admin@example.com", password="adminpass", ) ``` ``` -------------------------------- ### Optimize Tenant Permissions Queries Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/installation.rst Configure TENANT_USERS_PERMS_QUERYSET to use the built-in optimizer for efficient loading of related user data like profiles and groups. ```python # settings.py TENANT_USERS_PERMS_QUERYSET = ( "tenant_users.permissions.utils.get_optimized_tenant_perms_queryset" ) ``` -------------------------------- ### Clone Repository Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/conributing.rst Clone the forked repository to your local machine before making any changes. ```bash git clone https://github.com/your-username/django-tenant-users.git ``` -------------------------------- ### create_public_tenant Source: https://context7.com/corvia/django-tenant-users/llms.txt Bootstraps the mandatory public tenant (schema "public") with an owner user and a primary domain. This should be called once during initial deployment before running `migrate_schemas`. ```APIDOC ## create_public_tenant Bootstraps the mandatory public tenant (schema `"public"`) with an owner user and a primary domain. Call it once during initial deployment before running `migrate_schemas`. ### Parameters - **domain_url** (str) - Required - The primary domain for the public tenant. - **owner_email** (str) - Required - The email address of the owner of the public tenant. - **is_superuser** (bool) - Optional - Whether the owner should be a superuser. - **is_staff** (bool) - Optional - Whether the owner should be staff. - **owner_extra_data** (dict) - Optional - Additional data for the owner user. - **domain_extra_data** (dict) - Optional - Additional data for the primary domain. - **tenant_extra_data** (dict) - Optional - Additional data for the tenant model. ### Returns - **tuple**: A tuple containing the public tenant object, the primary domain object, and the owner user object. ### Raises - **ExistsError**: If the public tenant already exists. ### Example ```python from tenant_users.tenants.utils import create_public_tenant try: public_tenant, domain, owner = create_public_tenant( domain_url="example.com", owner_email="superadmin@example.com", is_superuser=True, is_staff=True, owner_extra_data={"name": "Super Admin"}, domain_extra_data={"notes": "primary domain"}, ) except ExistsError: print("Public tenant already exists — skipping.") ``` ``` -------------------------------- ### Create a New User Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/using.rst Create new users through the object manager. In django-tenant-users, emails serve as usernames. ```python from users.models import TenantUser user = TenantUser.objects.create_user("user@evilcorp.com", "password", True) ``` -------------------------------- ### Tenant-Specific Permission Checks with PermissionsMixinFacade Source: https://context7.com/corvia/django-tenant-users/llms.txt Demonstrates how PermissionsMixinFacade delegates permission checks to UserTenantPermissions, caching results per schema using tenant_cached_property. Ensure User and Company models are set up correctly. ```python from django_tenants.utils import tenant_context, schema_context from companies.models import Company from users.models import TenantUser user = TenantUser.objects.get(email="alice@example.com") acme = Company.objects.get(slug="acme") beta = Company.objects.get(slug="beta") with tenant_context(acme): print(user.is_staff) # reads acme's UserTenantPermissions print(user.has_perm("app.view_report")) # True/False per acme schema print(user.has_module_perms("app")) # True/False with tenant_context(beta): print(user.is_staff) # independent of acme result # Cache is keyed by schema_name, so no cross-tenant leakage ``` -------------------------------- ### Provision Tenant Source: https://context7.com/corvia/django-tenant-users/llms.txt Use `provision_tenant` to atomically create a new tenant schema, its primary domain, and assign the owner as a superuser within that tenant. It returns the created `tenant` and `domain` objects. For large deployments, consider using this within a background task like Celery due to potential slowness in schema creation. This function can raise `InactiveError` if the owner account is inactive or `ExistsError` if the tenant URL is already in use. ```python from tenant_users.tenants.tasks import provision_tenant from tenant_users.tenants.models import InactiveError, ExistsError from users.models import TenantUser owner = TenantUser.objects.get(email="alice@example.com") try: tenant, domain = provision_tenant( tenant_name="Acme Corp", tenant_slug="acme", # becomes subdomain: acme.example.com owner=owner, is_superuser=True, # owner gets superuser in this tenant is_staff=False, domain_extra_data={"notes": "provisioned via sign-up flow"}, tenant_extra_data={"description": "Acme Corp tenant"}, ) print(tenant.schema_name) # "acme_" print(domain.domain) # "acme.example.com" except InactiveError: print("Cannot provision — owner account is inactive.") except ExistsError: print("Tenant URL already taken — choose a different slug.") # Multi-type tenant variant: tenant, domain = provision_tenant( "Beta Corp", "betacorp", owner, tenant_type="premium", # must match a key in TENANT_TYPES ) ``` -------------------------------- ### Format Code Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/conributing.rst Format Python code using black and isort. It's important to run isort first, then black. ```bash pip install isort black isort . black . ``` -------------------------------- ### Define TenantBase Model Source: https://context7.com/corvia/django-tenant-users/llms.txt Subclass TenantBase to replace django-tenants' TenantMixin. Add project-specific fields and configure TENANT_MODEL and TENANT_DOMAIN_MODEL in settings.py. ```python # companies/models.py from django.db import models from django_tenants.models import DomainMixin from tenant_users.tenants.models import TenantBase class Company(TenantBase): """Your concrete tenant model.""" name = models.CharField(max_length=100) description = models.TextField(max_length=200) # auto_create_schema = True (inherited — schema created on first save) # auto_drop_schema = True (inherited — schema dropped on delete) class Domain(DomainMixin): """Required by django-tenants.""" pass # settings.py additions TENANT_MODEL = "companies.Company" TENANT_DOMAIN_MODEL = "companies.Domain" ``` -------------------------------- ### Registering Django Tenant Users Signals Source: https://context7.com/corvia/django-tenant-users/llms.txt Use the @receiver decorator to hook into user lifecycle events. These signals provide sender, user, and tenant (when applicable) context. Ensure your receiver functions are in a discoverable location, like an app's signals.py. ```python from tenant_users.tenants.models import ( tenant_user_created, tenant_user_deleted, tenant_user_added, tenant_user_removed, ) from django.dispatch import receiver @receiver(tenant_user_created) def on_user_created(sender, user, **kwargs): """Fired after a new UserProfile is saved in the public schema.""" print(f"New user: {user.email}") # send welcome email, create default resources, etc. @receiver(tenant_user_added) def on_user_added(sender, user, tenant, **kwargs): """Fired after add_user() links a user to a tenant.""" print(f"{user.email} joined {tenant.name}") @receiver(tenant_user_removed) def on_user_removed(sender, user, tenant, **kwargs): """Fired after remove_user() unlinks a user from a tenant.""" print(f"{user.email} left {tenant.name}") @receiver(tenant_user_deleted) def on_user_deleted(sender, user, **kwargs): """Fired after delete_user() marks a user inactive.""" print(f"User deactivated: {user.email}") ``` -------------------------------- ### Add User to Tenant Source: https://context7.com/corvia/django-tenant-users/llms.txt Adds an existing global user to a tenant and creates their `UserTenantPermissions` record. Automatically executes within the tenant's schema context. Handles `ExistsError` if the user is already a member. ```python from companies.models import Company from users.models import TenantUser tentant = Company.objects.get(slug="acme") user = TenantUser.objects.get(email="bob@example.com") from tenant_users.tenants.models import ExistsError try: tenant.add_user(user, is_superuser=False, is_staff=True) print("Bob added as staff to Acme.") except ExistsError: print("Bob is already a member of Acme.") # Signals fired automatically: # tenant_users.tenants.models.tenant_user_added from tenant_users.tenants.models import tenant_user_added def on_user_added(sender, user, tenant, **kwargs): print(f"{user.email} joined {tenant.name}") tenant_user_added.connect(on_user_added) ``` -------------------------------- ### Add a User to a Tenant Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/using.rst Grant a user access to a specific tenant by using the 'add_user()' function from the TenantBase model. This associates the user with the tenant's schema. ```python from companies.models import Company from users.models import TenantUser user = TenantUser.objects.get(email="user@domain.com") evil = Company.objects.get(slug="evil") evil.add_user(user) ``` -------------------------------- ### Update Django Settings for Tenant Apps Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/installation.rst Configure SHARED_APPS and TENANT_APPS in settings.py to include the necessary django-tenant-users modules and your custom apps. ```python SHARED_APPS = [ ... "django.contrib.auth", "django.contrib.contenttypes", "tenant_users.permissions", "tenant_users.tenants", "companies", "users", ... ] TENANT_APPS = [ ... "django.contrib.auth", "django.contrib.contenttypes", "tenant_users.permissions", ... ] ``` -------------------------------- ### Optimizing Tenant Permissions QuerySet Source: https://context7.com/corvia/django-tenant-users/llms.txt Configure TENANT_USERS_PERMS_QUERYSET in settings.py to use an optimized QuerySet for permission lookups, reducing N+1 queries. You can use the default or provide a custom function. ```python # settings.py TENANT_USERS_PERMS_QUERYSET = ( "tenant_users.permissions.utils.get_optimized_tenant_perms_queryset" ) ``` ```python # Custom optimizer example (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", ) ``` ```python # settings.py (custom): TENANT_USERS_PERMS_QUERYSET = "myapp.utils.get_optimized_perms_queryset" ``` ```python # The framework internally calls: # queryset.get(profile_id=user.pk) # so returning a QuerySet (not a single object) is required. ``` -------------------------------- ### Create Custom User Model inheriting from UserProfile Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/installation.rst Define a custom user model that inherits from UserProfile. This is necessary for tenant-aware user authentication. ```python from tenant_users.tenants.models import UserProfile class TenantUser(UserProfile): name = models.CharField(max_length=100) ``` -------------------------------- ### Add Tenant Access Middleware Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/installation.rst Include TenantAccessMiddleware in your Django project's MIDDLEWARE setting after AuthenticationMiddleware. This middleware ensures users have access to the requested tenant, raising a 404 if not. ```python MIDDLEWARE = [ ... "django.contrib.auth.middleware.AuthenticationMiddleware", ... "tenant_users.tenants.middleware.TenantAccessMiddleware", ... ] ``` -------------------------------- ### Configure Custom Optimizer in Settings Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/installation.rst Point the TENANT_USERS_PERMS_QUERYSET setting to your custom optimization function. This setting is used to override the default queryset for UserTenantPermissions. ```python # settings.py TENANT_USERS_PERMS_QUERYSET = "myapp.utils.get_optimized_perms_queryset" ``` -------------------------------- ### Run Tests Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/conributing.rst Ensure that your code passes all project tests using pytest. ```bash poetry run pytest ``` -------------------------------- ### Create New Branch Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/conributing.rst Checkout a new branch for your feature or bugfix to keep changes isolated. ```bash git checkout -b feature/your-feature-name ``` -------------------------------- ### Provision a New Tenant with Multi-Type Tenants Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/using.rst When using the Multi-type Tenants feature from django-tenants, specify the tenant type using the 'tenant_type' keyword argument. This function creates a new schema and should ideally be handled asynchronously. ```python 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, tenant_type="tenant_type", # optionally, pass extra fields for the tenant's Domain model domain_extra_data={"notes": "multitype tenant"}, ) ``` -------------------------------- ### UserTenantPermissions Model Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/permissions.rst Model for defining tenant-specific roles and permissions. ```APIDOC The permission models in ``django-tenant-users`` extend Django's built-in permissions framework. This extension allows for the definition of tenant-specific roles and permissions, ensuring that each tenant has its own set of access controls. .. autoclass:: tenant_users.permissions.models.UserTenantPermissions :members: :undoc-members: :show-inheritance: .. note:: The ``UserTenantPermissions`` model includes ``created_at`` and ``modified_at`` timestamp fields for tracking when users join tenants and when their permissions change. ``` -------------------------------- ### Create Custom Optimizer Function Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/installation.rst Define a custom queryset optimization function in your app's utils.py. This function should return a QuerySet instance. Customize it to match your specific needs for optimizing permission lookups. ```python # myapp/utils.py from tenant_users.permissions.models import UserTenantPermissions def get_optimized_perms_queryset(): """Return a custom optimized queryset for UserTenantPermissions. Customize this to match your specific needs. """ return UserTenantPermissions.objects.select_related( "profile" ).prefetch_related( "groups", "user_permissions" ) ``` -------------------------------- ### PermissionsMixinFacade Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/permissions.rst Utility functions for managing permissions across tenants. ```APIDOC ``django-tenant-users`` offers utility functions to manage permissions across tenants. These functions provide fine-grained control over user roles and access within each tenant. .. autoclass:: tenant_users.permissions.models.PermissionsMixinFacade :members: :undoc-members: :show-inheritance: ``` -------------------------------- ### TenantBase.add_user Source: https://context7.com/corvia/django-tenant-users/llms.txt Adds an existing global user to a tenant and creates their UserTenantPermissions record. This operation is automatically executed within the tenant's schema context. ```APIDOC ## TenantBase.add_user ### Description Adds an existing global user to a tenant and creates their `UserTenantPermissions` record in that tenant's schema. Automatically executes within the tenant's schema context via `@schema_required`. ### Method `TenantBase.add_user(user, is_superuser=False, is_staff=False)` ### Parameters - **user** (`TenantUser` object) - Required - The existing global user to add to the tenant. - **is_superuser** (bool) - Optional - Defaults to `False`. Whether the user should be a superuser within the tenant. - **is_staff** (bool) - Optional - Defaults to `False`. Whether the user should be staff within the tenant. ### Request Example ```python from companies.models import Company from users.models import TenantUser tenant = Company.objects.get(slug="acme") user = TenantUser.objects.get(email="bob@example.com") try: tenant.add_user(user, is_superuser=False, is_staff=True) print("Bob added as staff to Acme.") except ExistsError: print("Bob is already a member of Acme.") ``` ### Signals Fired - `tenant_users.tenants.models.tenant_user_added` ``` -------------------------------- ### UserProfileManager.create_user Source: https://context7.com/corvia/django-tenant-users/llms.txt Creates a new global user in the public schema and immediately adds them to the public tenant without permissions. This method must be called while the database connection is on the public schema. ```APIDOC ## UserProfileManager.create_user Creates a new global user in the public schema and immediately adds them to the public tenant (without permissions). Must be called while the database connection is on the public schema. ### Parameters - **email** (str) - Required - The email address of the user. - **password** (str) - Required - The password for the user. - **name** (str) - Optional - The full name of the user. - **is_staff** (bool) - Optional - Whether the user is a staff member. ### Raises - **ExistsError**: If an active user with the provided email already exists. ### Example ```python from users.models import TenantUser # Basic user creation user = TenantUser.objects.create_user( email="bob@example.com", password="securepassword123", ) # With extra profile fields user = TenantUser.objects.create_user( email="carol@example.com", password="s3cr3t", name="Carol Smith", is_staff=False, ) ``` ``` -------------------------------- ### Customize Tenant Access Error Message Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/installation.rst Optionally, set TENANT_USERS_ACCESS_ERROR_MESSAGE in settings.py to customize the error message displayed when a user lacks tenant access. ```python TENANT_USERS_ACCESS_ERROR_MESSAGE = "Custom access denied message." ``` -------------------------------- ### Configure Cross Domain Cookies for SSO Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/installation.rst Set SESSION_COOKIE_DOMAIN to allow single sign-on across tenants. Ensure you understand the implications of this setting before applying it. ```python SESSION_COOKIE_DOMAIN = ".domain.com" ``` -------------------------------- ### TenantBase.transfer_ownership Source: https://context7.com/corvia/django-tenant-users/llms.txt Reassigns the tenant owner. The old owner loses superuser status, and the new owner is added as a superuser if not already a member. ```APIDOC ## TenantBase.transfer_ownership ### Description Reassigns the tenant owner. The old owner loses superuser status (but keeps group memberships); the new owner is added to the tenant as a superuser if not already a member. ### Method `TenantBase.transfer_ownership(new_owner)` ### Parameters - **new_owner** (`TenantUser` object) - Required - The user to become the new owner of the tenant. ### Request Example ```python from companies.models import Company from users.models import TenantUser tenant = Company.objects.get(slug="acme") new_owner = TenantUser.objects.get(email="carol@example.com") tenant.transfer_ownership(new_owner) tenant.refresh_from_db() print(tenant.owner.email) # carol@example.com ``` ``` -------------------------------- ### Modify TenantModel to inherit from TenantBase Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/installation.rst Update your existing TenantModel to inherit from TenantBase. This is required for tenant management. ```python from tenant_users.tenants.models import TenantBase class Company(TenantBase): name = models.CharField(max_length=100) description = models.TextField(max_length=200) ``` -------------------------------- ### Push Changes Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/conributing.rst Push your local branch with the committed changes to your forked repository on GitHub. ```bash git push origin feature/your-feature-name ``` -------------------------------- ### Access UserTenantPermissions Source: https://context7.com/corvia/django-tenant-users/llms.txt Accesses and modifies user permissions within a tenant's schema using `UserTenantPermissions`. Requires being within a `tenant_context`. Demonstrates granting Django permissions and checking them. ```python from django_tenants.utils import tenant_context from companies.models import Company from users.models import TenantUser from tenant_users.permissions.models import UserTenantPermissions tentant = Company.objects.get(slug="acme") user = TenantUser.objects.get(email="bob@example.com") with tenant_context(tenant): perms = UserTenantPermissions.objects.get(profile=user) print(perms.is_staff) # True/False print(perms.is_superuser) # True/False print(perms.created_at) # datetime when user joined this tenant print(perms.modified_at) # datetime of last permission change # Grant a Django permission from django.contrib.auth.models import Permission p = Permission.objects.get(codename="change_company") perms.user_permissions.add(p) # Check permissions (same API as standard Django) print(user.has_perm("companies.change_company")) # True print(user.get_all_permissions()) # {"companies.change_company", ...} ``` -------------------------------- ### Set AUTH_USER_MODEL to Custom User Model Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/installation.rst Update AUTH_USER_MODEL in settings.py to point to your custom user model that inherits from TenantUser. ```python AUTH_USER_MODEL = "users.TenantUser" ``` -------------------------------- ### Transfer Tenant Ownership Source: https://context7.com/corvia/django-tenant-users/llms.txt Reassigns the tenant owner. The old owner loses superuser status, and the new owner is added as a superuser if not already a member. ```python from companies.models import Company from users.models import TenantUser tentant = Company.objects.get(slug="acme") new_owner = TenantUser.objects.get(email="carol@example.com") tentant.transfer_ownership(new_owner) tentant.refresh_from_db() print(tenant.owner.email) # carol@example.com ``` -------------------------------- ### Commit Changes Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/conributing.rst Commit your changes with a descriptive message indicating the nature of the changes. ```bash git commit -am "Add feature: Your feature description" ``` -------------------------------- ### Delete a Tenant Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/using.rst To delete a tenant, use the 'delete_tenant()' manager method. This approach marks the tenant as inactive rather than permanently deleting it, preserving data integrity. ```python from companies.models import Company evil = Company.objects.get(slug="evil") evil.delete_tenant() ``` -------------------------------- ### Schema-Required Decorator for Tenant Methods Source: https://context7.com/corvia/django-tenant-users/llms.txt Use the @schema_required decorator on methods of TenantBase subclasses to ensure they execute within the correct tenant's schema context. This automatically handles schema switching. ```python from tenant_users.tenants.models import TenantBase, schema_required from django.db import models class Company(TenantBase): name = models.CharField(max_length=100) @schema_required def get_member_count(self): """Count users in this tenant's schema — runs inside tenant schema.""" from tenant_users.permissions.models import UserTenantPermissions return UserTenantPermissions.objects.count() @schema_required def promote_to_staff(self, user): from tenant_users.permissions.models import UserTenantPermissions perms = UserTenantPermissions.objects.get(profile=user) perms.is_staff = True perms.save(update_fields=["is_staff"]) # Usage — schema switching is automatic: company = Company.objects.get(slug="acme") print(company.get_member_count()) # runs in "acme_" schema ``` -------------------------------- ### UserTenantPermissions Model Source: https://context7.com/corvia/django-tenant-users/llms.txt Provides extended permissions within a tenant's schema, including staff status, superuser status, groups, and user permissions. Access is available via `user.tenant_perms` within a tenant context. ```APIDOC ## UserTenantPermissions Model ### Description `UserTenantPermissions` lives inside each tenant's schema and extends Django's `PermissionsMixin`. It stores `is_staff`, `is_superuser`, `groups`, `user_permissions`, plus `created_at`/`modified_at` timestamps. Access it via `user.tenant_perms` within a tenant context. ### Accessing Permissions Use the `tenant_context` to access permissions within a specific tenant. ### Fields - **is_staff** (bool) - Whether the user is a staff member within this tenant. - **is_superuser** (bool) - Whether the user is a superuser within this tenant. - **created_at** (datetime) - Timestamp when the user joined this tenant. - **modified_at** (datetime) - Timestamp of the last permission change for the user in this tenant. ### Methods - **user_permissions.add(permission)**: Adds a Django permission to the user for this tenant. - **user.has_perm(permission_string)**: Checks if the user has a specific permission within this tenant. - **user.get_all_permissions()**: Returns a set of all permissions the user has within this tenant. ### Request Example ```python from django_tenants.utils import tenant_context from companies.models import Company from users.models import TenantUser from tenant_users.permissions.models import UserTenantPermissions from django.contrib.auth.models import Permission tenant = Company.objects.get(slug="acme") user = TenantUser.objects.get(email="bob@example.com") with tenant_context(tenant): perms = UserTenantPermissions.objects.get(profile=user) print(perms.is_staff) # True/False print(perms.is_superuser) # True/False print(perms.created_at) # datetime when user joined this tenant print(perms.modified_at) # datetime of last permission change # Grant a Django permission p = Permission.objects.get(codename="change_company") perms.user_permissions.add(p) # Check permissions (same API as standard Django) print(user.has_perm("companies.change_company")) # True print(user.get_all_permissions()) # {"companies.change_company", ...} ``` ``` -------------------------------- ### Delete a User Source: https://github.com/corvia/django-tenant-users/blob/master/docs/pages/using.rst Use the 'delete_user()' manager method to delete a user. This method marks the user as inactive, allowing for potential future reactivation. ```python from users.models import TenantUser user = TenantUser.objects.get(email="user@domain.com") TenantUser.objects.delete_user(user) ``` -------------------------------- ### TenantBase.delete_tenant Source: https://context7.com/corvia/django-tenant-users/llms.txt Soft-deletes a tenant by removing non-owner users, renaming the domain URL, and transferring ownership to the public tenant's owner. The schema and data remain in the database. ```APIDOC ## TenantBase.delete_tenant ### Description Soft-deletes a tenant: removes all non-owner users, renames the domain URL to `--`, and transfers ownership to the public tenant's owner. The schema and row remain in the database. ### Method `TenantBase.delete_tenant()` ### Request Example ```python from companies.models import Company from tenant_users.tenants.models import DeleteError tenant = Company.objects.get(slug="acme") try: tenant.delete_tenant() tenant.refresh_from_db() # domain_url is now something like "1712000000-42-acme.example.com" print(tenant.owner.email) # now the public tenant owner except ValueError as e: print(e) # "Cannot delete public tenant schema" ``` ``` -------------------------------- ### Delete User with TenantUser Source: https://context7.com/corvia/django-tenant-users/llms.txt The `delete_user` method performs a soft delete by setting `is_active=False` and removing the user from all non-owned tenants. It also calls `delete_tenant()` on any tenants the user owns. The database row itself is not deleted. This operation can raise an exception, such as `DeleteError`, if the user is the owner of the public tenant. ```python from users.models import TenantUser user = TenantUser.objects.get(email="bob@example.com") try: TenantUser.objects.delete_user(user) except Exception as e: print(e) # e.g. DeleteError if user is the public tenant owner # After deletion: user.refresh_from_db() print(user.is_active) # False # user.tenants.count() == 0 ``` -------------------------------- ### UserProfileManager.delete_user Source: https://context7.com/corvia/django-tenant-users/llms.txt Soft-deletes a user by setting `is_active=False`, removing them from all non-owned tenants, and calling `delete_tenant()` on any tenants they own. This method does not delete the database row. ```APIDOC ## UserProfileManager.delete_user Soft-deletes a user: sets `is_active=False`, removes them from all non-owned tenants, and calls `delete_tenant()` on any tenants they own. Does not delete the database row. ### Parameters - **user** (TenantUser) - Required - The user object to delete. ### Raises - **DeleteError**: If the user is the owner of the public tenant. ### Example ```python from users.models import TenantUser user = TenantUser.objects.get(email="bob@example.com") TenantUser.objects.delete_user(user) # After deletion: user.refresh_from_db() print(user.is_active) # False ``` ``` -------------------------------- ### Remove User from Tenant Source: https://context7.com/corvia/django-tenant-users/llms.txt Removes a user from a tenant, clearing their groups and permissions within that schema. The tenant owner cannot be removed directly. Handles `DeleteError` if attempting to remove the owner. ```python from companies.models import Company from users.models import TenantUser from tenant_users.tenants.models import DeleteError tentant = Company.objects.get(slug="acme") user = TenantUser.objects.get(email="bob@example.com") try: tenant.remove_user(user) print("Bob removed from Acme.") except DeleteError as e: print(e) # "Cannot remove owner from tenant: ..." # Fires tenant_user_removed signal from tenant_users.tenants.models import tenant_user_removed def on_user_removed(sender, user, tenant, **kwargs): print(f"{user.email} left {tenant.name}") tenant_user_removed.connect(on_user_removed) ``` -------------------------------- ### Delete Tenant Source: https://context7.com/corvia/django-tenant-users/llms.txt Soft-deletes a tenant by removing non-owner users and renaming the domain URL. The schema and rows remain in the database. Handles `ValueError` if attempting to delete the public tenant. ```python from companies.models import Company from tenant_users.tenants.models import DeleteError tentant = Company.objects.get(slug="acme") try: tenant.delete_tenant() tenant.refresh_from_db() # domain_url is now something like "1712000000-42-acme.example.com" print(tenant.owner.email) # now the public tenant owner except ValueError as e: print(e) # "Cannot delete public tenant schema" ``` -------------------------------- ### TenantBase.remove_user Source: https://context7.com/corvia/django-tenant-users/llms.txt Removes a user from a tenant, clearing their groups and permissions within that schema. The tenant owner cannot be removed directly. ```APIDOC ## TenantBase.remove_user ### Description Removes a user from a tenant, clears all their groups and `UserTenantPermissions` in that schema, and clears any cached permission data. The tenant owner cannot be removed directly. ### Method `TenantBase.remove_user(user)` ### Parameters - **user** (`TenantUser` object) - Required - The user to remove from the tenant. ### Request Example ```python from companies.models import Company from users.models import TenantUser from tenant_users.tenants.models import DeleteError tenant = Company.objects.get(slug="acme") user = TenantUser.objects.get(email="bob@example.com") try: tenant.remove_user(user) print("Bob removed from Acme.") except DeleteError as e: print(e) # "Cannot remove owner from tenant: ..." ``` ### Signals Fired - `tenant_users.tenants.models.tenant_user_removed` ``` -------------------------------- ### Define UserProfile Model Source: https://context7.com/corvia/django-tenant-users/llms.txt Subclass UserProfile for the authentication model stored in the public schema. It uses email as the username and holds a relationship to all tenants the user belongs to. ```python # users/models.py from django.db import models from tenant_users.tenants.models import UserProfile class TenantUser(UserProfile): name = models.CharField(max_length=100, blank=True) # UserProfile already provides: # email (unique, USERNAME_FIELD) # is_active # is_verified # tenants (ManyToMany → TENANT_MODEL) # objects (UserProfileManager) # Example usage in a Django shell or view: from users.models import TenantUser user = TenantUser.objects.get(email="alice@example.com") print(user.email) # alice@example.com print(user.is_verified) # False print(user.has_verified_email()) # False for tenant in user.tenants.all(): print(tenant.schema_name) ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.