This commit includes: - Django backend with multi-tenancy (django-tenants) - React + TypeScript frontend with Vite - Platform administration API with role-based access control - Authentication system with token-based auth - Quick login dev tools for testing different user roles - CORS and CSRF configuration for local development - Docker development environment setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
144 lines
4.6 KiB
Python
144 lines
4.6 KiB
Python
"""
|
|
Smooth Schedule Custom User Model
|
|
Implements strict role hierarchy and multi-tenant user management
|
|
"""
|
|
from django.contrib.auth.models import AbstractUser
|
|
from django.db import models
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
|
|
class User(AbstractUser):
|
|
"""
|
|
Custom User model with strict role-based access control.
|
|
Replaces Django's default User model.
|
|
"""
|
|
|
|
# Role Choices - Strict Hierarchy
|
|
class Role(models.TextChoices):
|
|
# Platform-level roles (access across all tenants)
|
|
SUPERUSER = 'SUPERUSER', _('Platform Superuser')
|
|
PLATFORM_MANAGER = 'PLATFORM_MANAGER', _('Platform Manager')
|
|
PLATFORM_SALES = 'PLATFORM_SALES', _('Platform Sales')
|
|
PLATFORM_SUPPORT = 'PLATFORM_SUPPORT', _('Platform Support')
|
|
|
|
# Tenant-level roles (access within single tenant)
|
|
TENANT_OWNER = 'TENANT_OWNER', _('Tenant Owner')
|
|
TENANT_MANAGER = 'TENANT_MANAGER', _('Tenant Manager')
|
|
TENANT_STAFF = 'TENANT_STAFF', _('Tenant Staff')
|
|
|
|
# Customer role (end users of the tenant)
|
|
CUSTOMER = 'CUSTOMER', _('Customer')
|
|
|
|
# Core fields
|
|
role = models.CharField(
|
|
max_length=20,
|
|
choices=Role.choices,
|
|
default=Role.CUSTOMER,
|
|
help_text="User's role in the system hierarchy"
|
|
)
|
|
|
|
# For multi-tenancy: link users to their tenant
|
|
# Note: This is only for tenant-level users. Platform users can access all tenants.
|
|
tenant = models.ForeignKey(
|
|
'core.Tenant',
|
|
on_delete=models.CASCADE,
|
|
null=True,
|
|
blank=True,
|
|
related_name='users',
|
|
help_text="Tenant this user belongs to (null for platform-level users)"
|
|
)
|
|
|
|
# Special flags
|
|
is_temporary = models.BooleanField(
|
|
default=False,
|
|
help_text="True for sales demo accounts - can be masqueraded by Platform Sales"
|
|
)
|
|
|
|
# Additional profile fields
|
|
phone = models.CharField(max_length=20, blank=True)
|
|
job_title = models.CharField(max_length=100, blank=True)
|
|
|
|
# Metadata
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
last_login_ip = models.GenericIPAddressField(null=True, blank=True)
|
|
|
|
class Meta:
|
|
ordering = ['email']
|
|
indexes = [
|
|
models.Index(fields=['role', 'tenant']),
|
|
models.Index(fields=['email', 'is_active']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.email} ({self.get_role_display()})"
|
|
|
|
def is_platform_user(self):
|
|
"""Check if user has platform-level access"""
|
|
return self.role in [
|
|
self.Role.SUPERUSER,
|
|
self.Role.PLATFORM_MANAGER,
|
|
self.Role.PLATFORM_SALES,
|
|
self.Role.PLATFORM_SUPPORT,
|
|
]
|
|
|
|
def is_tenant_user(self):
|
|
"""Check if user is tenant-scoped"""
|
|
return self.role in [
|
|
self.Role.TENANT_OWNER,
|
|
self.Role.TENANT_MANAGER,
|
|
self.Role.TENANT_STAFF,
|
|
self.Role.CUSTOMER,
|
|
]
|
|
|
|
def can_manage_users(self):
|
|
"""Check if user can manage other users"""
|
|
return self.role in [
|
|
self.Role.SUPERUSER,
|
|
self.Role.PLATFORM_MANAGER,
|
|
self.Role.TENANT_OWNER,
|
|
self.Role.TENANT_MANAGER,
|
|
]
|
|
|
|
def can_access_billing(self):
|
|
"""Check if user can access billing information"""
|
|
return self.role in [
|
|
self.Role.SUPERUSER,
|
|
self.Role.PLATFORM_MANAGER,
|
|
self.Role.TENANT_OWNER,
|
|
]
|
|
|
|
def get_accessible_tenants(self):
|
|
"""
|
|
Get list of tenants this user can access.
|
|
Platform users can access all tenants.
|
|
Tenant users can only access their own tenant.
|
|
"""
|
|
from core.models import Tenant
|
|
|
|
if self.is_platform_user():
|
|
return Tenant.objects.all()
|
|
elif self.tenant:
|
|
return Tenant.objects.filter(pk=self.tenant.pk)
|
|
else:
|
|
return Tenant.objects.none()
|
|
|
|
def save(self, *args, **kwargs):
|
|
"""
|
|
Override save to enforce business rules
|
|
"""
|
|
# Superusers must be staff and have is_superuser flag
|
|
if self.role == self.Role.SUPERUSER:
|
|
self.is_staff = True
|
|
self.is_superuser = True
|
|
|
|
# Platform users should not be tied to a tenant
|
|
if self.is_platform_user():
|
|
self.tenant = None
|
|
|
|
# Tenant users must have a tenant
|
|
if self.is_tenant_user() and not self.tenant:
|
|
raise ValueError(f"Users with role {self.role} must be assigned to a tenant")
|
|
|
|
super().save(*args, **kwargs)
|