Files
smoothschedule/users/models.py
poduck 2e111364a2 Initial commit: SmoothSchedule multi-tenant scheduling platform
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>
2025-11-27 01:43:20 -05:00

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)