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
python manage.py migrate sites
python manage.py makemigrations micboard
python manage.py migrate micboard
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']
python manage.py migrate sites
python manage.py makemigrations micboard_multitenancy
python manage.py migrate micboard_multitenancy
python manage.py makemigrations micboard
python manage.py migrate micboard
๐ฆ Service Layer Usage¶
DeviceService¶
from micboard.services import DeviceService
# Single-site (default)
receivers = DeviceService.get_active_receivers()
# Multi-site
receivers = DeviceService.get_active_receivers(site_id=1)
# MSP mode
receivers = DeviceService.get_active_receivers(
organization_id=org.id,
campus_id=campus.id # Optional
)
LocationService¶
from micboard.services import LocationService
# Get all locations (tenant-aware)
locations = LocationService.get_all_locations(
organization_id=org.id,
campus_id=campus.id
)
ManufacturerService¶
from micboard.services import ManufacturerService
# Sync devices for organization
result = ManufacturerService.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 import Building
Building.objects.filter(name__contains='Engineering').update(
organization=org,
campus=campus
)
๐ฅ 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.multitenancy.managers import TenantAwareManager
class MyModel(models.Model):
objects = TenantAwareManager()
# Usage
qs = MyModel.objects.for_organization(organization=org)
qs = MyModel.objects.for_campus(campus_id=campus.id)
qs = MyModel.objects.for_user(user=request.user)
๐ Request Context (Views)¶
def my_view(request):
# Access current organization
org = request.organization # Set by TenantMiddleware
campus_id = request.campus_id
# Use in service calls
receivers = DeviceService.get_active_receivers(
organization_id=org.id if org else None
)
๐ 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 # Conditional imports
โ โโโ models.py # Organization/Campus/Membership
โ โโโ managers.py # TenantAwareManager
โ โโโ middleware.py # TenantMiddleware
โ โโโ admin.py # Django admin
โ โโโ apps.py # App config
โโโ settings/
โ โโโ multitenancy.py # Settings template
โโโ models/
โโโ locations.py # Updated for tenant support
docs/
โโโ multitenancy.md # Full documentation
โโโ archive/root/MULTITENANCY_IMPLEMENTATION.md # Implementation summary
๐ Documentation Links¶
- Full Documentation: multitenancy.md
- Implementation Summary: archive/root/MULTITENANCY_IMPLEMENTATION.md
- Migration Guide: micboard/multitenancy/migrations/README.md
- Settings Template: micboard/settings/multitenancy.py
โ Backward Compatibility¶
All tenant parameters are optional - existing code works unchanged:
# โ
All of these work
DeviceService.get_active_receivers()
DeviceService.get_active_receivers(organization_id=1)
DeviceService.get_active_receivers(site_id=1, campus_id=2)
๐งช Testing¶
from micboard.multitenancy.models import Organization
from micboard.services import DeviceService
# Create test organization
org = Organization.objects.create(name='Test Org', slug='test', site_id=1)
# Test isolation
receivers = DeviceService.get_active_receivers(organization_id=org.id)
assert all(r.location.building.organization == org for r in receivers)
๐จ Common Issues¶
"Building has no organization"¶
# Assign buildings to default org
org = Organization.objects.first()
Building.objects.filter(organization__isnull=True).update(organization=org)
"User can't see devices"¶
# Check memberships
from micboard.multitenancy.models import OrganizationMembership
OrganizationMembership.objects.filter(user=user, is_active=True)
"Settings not configured"¶
# Ensure Django settings loaded before importing multitenancy
import django
django.setup()
from micboard.multitenancy import is_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', ...)