Multi-Tenancy Quick Reference
π Quick Start
Option 1: Single-Site (Default) - No Changes Needed
# settings.py - Already configuredMICBOARD_MULTI_SITE_MODE = FalseMICBOARD_MSP_ENABLED = FalseOption 2: Multi-Site Mode
INSTALLED_APPS = ['django.contrib.sites', 'micboard', ...]SITE_ID = 1MICBOARD_MULTI_SITE_MODE = Trueuv run --no-sync python manage.py migrateOption 3: MSP Mode (Full Multi-Tenancy)
INSTALLED_APPS = [ 'django.contrib.sites', 'micboard', 'micboard.multitenancy', # Add this ...]SITE_ID = 1MICBOARD_MULTI_SITE_MODE = TrueMICBOARD_MSP_ENABLED = TrueMIDDLEWARE += ['micboard.multitenancy.middleware.TenantMiddleware']uv run --no-sync python manage.py migrateπ¦ Service Layer Usage
Authenticated hardware queries
from micboard.models.hardware.wireless_chassis import WirelessChassisfrom 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 organizationresult = 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 organizationorg = Organization.objects.create( name='University A', slug='university-a', site_id=1, subscription_tier='enterprise', max_devices=500)
# Create campuscampus = Campus.objects.create( organization=org, name='North Campus', slug='north', city='Boston', state='MA')
# Assign buildingsfrom micboard.models.locations.structure import BuildingBuilding.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 OrganizationMembershipfrom django.contrib.auth.models import User
user = User.objects.get(username='av_tech')
# Add user to organizationmembership = 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()
# Usageqs = 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 organizationsdef 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)
MICBOARD_SUBDOMAIN_ROUTING = TrueMICBOARD_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 permissionsif 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π Documentation Links
- Full Documentation: multitenancy.md
- Migration Guide: micboard/multitenancy/migrations/README.md
- Configuration Reference: configuration.md
β 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 Organizationfrom micboard.models.hardware.wireless_chassis import WirelessChassis
# Create test organizationorg = Organization.objects.create(name='Test Org', slug='test', site_id=1)
# Test isolationreceivers = 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 orgorg = Organization.objects.first()Building.objects.filter(organization_id__isnull=True).update(organization_id=org.pk)βUser canβt see devicesβ
# Check membershipsfrom micboard.multitenancy.models import OrganizationMembershipOrganizationMembership.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 overheadMICBOARD_MSP_ENABLED = FalseUniversity (Multi-Campus)
MICBOARD_MSP_ENABLED = TrueMICBOARD_SITE_ISOLATION = 'campus'
# Campus-specific usersOrganizationMembership.objects.create( user=av_tech, organization=university, campus=north_campus, role='operator')MSP Provider
MICBOARD_MSP_ENABLED = TrueMICBOARD_SITE_ISOLATION = 'organization'MICBOARD_ALLOW_CROSS_ORG_VIEW = FalseMICBOARD_SUBDOMAIN_ROUTING = True
# Each customer is separate organizationchurch_a = Organization.objects.create(name='Church A', ...)church_b = Organization.objects.create(name='Church B', ...)