Skip to content

Multi-Tenancy Quick Reference

πŸš€ Quick Start

Option 1: Single-Site (Default) - No Changes Needed

# settings.py - Already configured
MICBOARD_MULTI_SITE_MODE = False
MICBOARD_MSP_ENABLED = False

Option 2: Multi-Site Mode

settings.py
INSTALLED_APPS = ['django.contrib.sites', 'micboard', ...]
SITE_ID = 1
MICBOARD_MULTI_SITE_MODE = True
Terminal window
uv run --no-sync python manage.py migrate

Option 3: MSP Mode (Full Multi-Tenancy)

settings.py
INSTALLED_APPS = [
'django.contrib.sites',
'micboard',
'micboard.multitenancy', # Add this
...
]
SITE_ID = 1
MICBOARD_MULTI_SITE_MODE = True
MICBOARD_MSP_ENABLED = True
MIDDLEWARE += ['micboard.multitenancy.middleware.TenantMiddleware']
Terminal window
uv run --no-sync python manage.py migrate

πŸ“¦ Service Layer Usage

Authenticated hardware queries

from micboard.models.hardware.wireless_chassis import WirelessChassis
from micboard.models.hardware.wireless_unit import WirelessUnit
chassis = WirelessChassis.objects.for_user(user=request.user).filter(
status__in=("online", "degraded", "provisioning"),
)
units = WirelessUnit.objects.for_user(user=request.user).filter(
status__in=("online", "degraded", "provisioning"),
)

Authenticated location queries

from micboard.services.monitoring.monitoring_access import MonitoringService
locations = MonitoringService.get_accessible_locations(request.user)

ManufacturerSyncService

from micboard.services.manufacturer.sync import ManufacturerSyncService
# Sync devices for organization
result = ManufacturerSyncService.sync_devices_for_manufacturer(
manufacturer_code='shure',
organization_id=org.id,
campus_id=campus.id
)

🏒 Creating Organizations

from micboard.multitenancy.models import Organization, Campus
# Create organization
org = Organization.objects.create(
name='University A',
slug='university-a',
site_id=1,
subscription_tier='enterprise',
max_devices=500
)
# Create campus
campus = Campus.objects.create(
organization=org,
name='North Campus',
slug='north',
city='Boston',
state='MA'
)
# Assign buildings
from micboard.models.locations.structure import Building
Building.objects.filter(name__contains='Engineering').update(
organization_id=org.pk,
campus_id=campus.pk,
)

max_devices limits wireless chassis owned through a location’s building. Chassis creation and ownership transfer are serialized and enforced by WirelessChassisPersistenceService; None means unlimited and locationless platform inventory is not organization-owned.

πŸ‘₯ User Access

from micboard.multitenancy.models import OrganizationMembership
from django.contrib.auth.models import User
user = User.objects.get(username='av_tech')
# Add user to organization
membership = OrganizationMembership.objects.create(
user=user,
organization=org,
campus=campus, # Optional: limit to campus
role='operator' # viewer/operator/admin/owner
)

πŸ” Tenant-Aware Managers

from micboard.models.base_managers import TenantOptimizedQuerySet
class MyModel(models.Model):
objects = TenantOptimizedQuerySet.as_manager()
# Usage
qs = MyModel.objects.for_user(user=request.user)
qs = MyModel.objects.for_site(site_id=1)

🌐 Request Context (Views)

def my_view(request):
# Access current organization
org = request.organization # Set by TenantMiddleware
campus_id = request.campus_id
chassis = WirelessChassis.objects.for_user(user=request.user).filter(
status__in=("online", "degraded", "provisioning"),
)

πŸ”„ Organization Switching

# Allow users to switch between organizations
def switch_org(request, org_id):
# Verify user has access
if OrganizationMembership.objects.filter(
user=request.user,
organization_id=org_id,
is_active=True
).exists():
request.session['current_organization_id'] = org_id
return redirect('dashboard')

🎨 Subdomain Routing (Optional)

settings.py
MICBOARD_SUBDOMAIN_ROUTING = True
MICBOARD_ROOT_DOMAIN = 'micboard.example.com'
# Access via subdomain
# university-a.micboard.example.com β†’ Organization(slug='university-a')
# church-b.micboard.example.com β†’ Organization(slug='church-b')

πŸ“Š Roles & Permissions

Role Permissions
viewer Read-only access
operator Can modify device assignments
admin Full access except billing
owner Full access including billing
# Check permissions
if membership.can_modify_devices():
# Allow device changes
pass
if membership.can_manage_users():
# Allow user management
pass

πŸ—‚οΈ Files Created

micboard/
β”œβ”€β”€ multitenancy/
β”‚ β”œβ”€β”€ __init__.py # Django application package
β”‚ β”œβ”€β”€ models.py # Organization/Campus/Membership
β”‚ β”œβ”€β”€ middleware.py # TenantMiddleware
β”‚ β”œβ”€β”€ admin.py # Django admin
β”‚ └── apps.py # App config
β”œβ”€β”€ services/
β”‚ └── settings/
β”‚ └── settings_service.py # Canonical feature-flag reads
└── models/
β”œβ”€β”€ base_managers.py # Canonical tenant-aware manager
└── locations/ # Indexed tenant identifiers
docs/
└── multitenancy.md # Full documentation

βœ… Explicit access scope

Request-facing queries require the authenticated user:

WirelessChassis.objects.for_user(user=request.user).filter(
status__in=("online", "degraded", "provisioning"),
)

πŸ§ͺ Testing

from micboard.multitenancy.models import Organization
from micboard.models.hardware.wireless_chassis import WirelessChassis
# Create test organization
org = Organization.objects.create(name='Test Org', slug='test', site_id=1)
# Test isolation
receivers = WirelessChassis.objects.for_user(user=member)
assert all(r.location.building.organization_id == org.pk for r in receivers)

🚨 Common Issues

β€œBuilding has no organization”

# Assign buildings to default org
org = Organization.objects.first()
Building.objects.filter(organization_id__isnull=True).update(organization_id=org.pk)

β€œUser can’t see devices”

# Check memberships
from micboard.multitenancy.models import OrganizationMembership
OrganizationMembership.objects.filter(user=user, is_active=True)

β€œSettings not configured”

from micboard.services.settings.settings_service import settings as micboard_settings
print(f"Multi-site: {micboard_settings.multi_site_mode}")
print(f"MSP: {micboard_settings.msp_enabled}")

🎯 Use Case Examples

Small Church (Single-Site)

# No configuration needed - runs with zero overhead
MICBOARD_MSP_ENABLED = False

University (Multi-Campus)

MICBOARD_MSP_ENABLED = True
MICBOARD_SITE_ISOLATION = 'campus'
# Campus-specific users
OrganizationMembership.objects.create(
user=av_tech,
organization=university,
campus=north_campus,
role='operator'
)

MSP Provider

MICBOARD_MSP_ENABLED = True
MICBOARD_SITE_ISOLATION = 'organization'
MICBOARD_ALLOW_CROSS_ORG_VIEW = False
MICBOARD_SUBDOMAIN_ROUTING = True
# Each customer is separate organization
church_a = Organization.objects.create(name='Church A', ...)
church_b = Organization.objects.create(name='Church B', ...)