ICVOSS DJANGO PACKAGE REGISTRY

The package index django-boundary Set up a tenant model

Set up a tenant model

Documentation

Goal

Define the tenant model that every scoped row points to, register it with BOUNDARY_TENANT_MODEL, and choose between subclassing AbstractTenant or supplying your own fully custom model.

Prerequisites

You do not need the middleware or any scoped models in place yet. The tenant model is the foundation everything else references.

Steps

1. Choose your approach

There are two ways to define the tenant model. Both produce a concrete model that BOUNDARY_TENANT_MODEL points at.

2a. Minimal tenant model with AbstractTenant

AbstractTenant is an abstract base. Subclass it and add nothing more for the common case.

# tenants/models.py
from boundary.models import AbstractTenant


class Organisation(AbstractTenant):
    pass

This gives you these fields, defined on AbstractTenant:

Field Type Notes
name CharField max_length=200. Used by __str__.
slug SlugField unique=True. Used by SubdomainResolver by default.
region CharField max_length=50, blank=True, default="". Read by region routing.
is_active BooleanField default=True.
created_at DateTimeField auto_now_add=True.
updated_at DateTimeField auto_now=True.

The base also sets Meta.ordering = ["name"] and a __str__ that returns self.name. You can add your own fields alongside these:

# tenants/models.py
from django.db import models

from boundary.models import AbstractTenant


class Organisation(AbstractTenant):
    billing_email = models.EmailField(blank=True)
    plan = models.CharField(max_length=20, default="free")

For the full field reference, see the AbstractTenant section in the README.

2b. Fully custom tenant model (no AbstractTenant)

If AbstractTenant's fields do not fit, define a plain models.Model. Boundary treats whatever you point BOUNDARY_TENANT_MODEL at as the tenant.

# tenants/models.py
from django.db import models


class Account(models.Model):
    company_name = models.CharField(max_length=200)
    handle = models.SlugField(unique=True)
    data_region = models.CharField(max_length=50, default="")
    active = models.BooleanField(default=True)

    def __str__(self):
        return self.company_name

Because the default resolvers and region routing read specific field names, tell boundary which of your fields to use:

# settings.py

# SubdomainResolver looks up the tenant by this field. Default: "slug".
BOUNDARY_SUBDOMAIN_FIELD = "handle"

# Region routing reads this field off the tenant. Default: "region".
BOUNDARY_REGION_FIELD = "data_region"

There is no setting for is_active; boundary does not filter on it for you. If you want inactive tenants excluded, enforce that in your resolver or provisioning logic.

See the settings reference for the complete list of fields the resolvers and routing read.

3. Register the model

Point BOUNDARY_TENANT_MODEL at your concrete model using the "app_label.ModelName" dotted string, exactly as you would for AUTH_USER_MODEL.

# settings.py
BOUNDARY_TENANT_MODEL = "tenants.Organisation"

This setting is required. Boundary raises a system check error (boundary.E001) at startup if it is missing or does not resolve.

4. Create and run migrations

The tenant model is a normal Django model, so it needs a migration.

python manage.py makemigrations tenants
python manage.py migrate

5. Resolve the model in code with get_tenant_model()

Never import the concrete tenant model directly into reusable code. Use the helper, which resolves BOUNDARY_TENANT_MODEL lazily through the app registry, the same way django.contrib.auth.get_user_model() works.

from boundary.conf import get_tenant_model

Tenant = get_tenant_model()
org = Tenant.objects.create(name="Acme", slug="acme")

get_tenant_model() raises LookupError if BOUNDARY_TENANT_MODEL is unset, so it doubles as a clear failure if configuration is missing.

Verify it worked

Run a shell and confirm the model resolves and creates rows:

python manage.py shell
>>> from boundary.conf import get_tenant_model
>>> Tenant = get_tenant_model()
>>> Tenant
<class 'tenants.models.Organisation'>
>>> org = Tenant.objects.create(name="Acme", slug="acme")
>>> str(org)
'Acme'
>>> org.is_active        # AbstractTenant default
True

Then run the system checks; a correctly registered model produces no boundary.E001:

python manage.py check

Common pitfalls