feat: add RBAC migrations, user modules, admin API updates, and utility scripts
This commit is contained in:
@@ -63,3 +63,121 @@ class ReviewInteraction(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.username} {self.interaction_type} on Review #{self.review_id}'
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# RBAC Models
|
||||
# ---------------------------------------------------------------------------
|
||||
from accounts.models import User
|
||||
|
||||
|
||||
class Department(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
slug = models.SlugField(unique=True)
|
||||
description = models.TextField(blank=True, default='')
|
||||
base_scopes = models.JSONField(default=list)
|
||||
color = models.CharField(max_length=7, default='#3B82F6')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Squad(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
department = models.ForeignKey(Department, on_delete=models.CASCADE, related_name='squads')
|
||||
manager = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='managed_squads')
|
||||
extra_scopes = models.JSONField(default=list)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.department.name} > {self.name}"
|
||||
|
||||
|
||||
class StaffProfile(models.Model):
|
||||
ROLE_CHOICES = [('SUPER_ADMIN', 'Super Admin'), ('MANAGER', 'Manager'), ('MEMBER', 'Member')]
|
||||
STATUS_CHOICES = [('active', 'Active'), ('invited', 'Invited'), ('deactivated', 'Deactivated')]
|
||||
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='staff_profile')
|
||||
department = models.ForeignKey(Department, on_delete=models.SET_NULL, null=True, blank=True, related_name='staff_members')
|
||||
squad = models.ForeignKey(Squad, on_delete=models.SET_NULL, null=True, blank=True, related_name='members')
|
||||
staff_role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='MEMBER')
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active')
|
||||
joined_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['user__first_name']
|
||||
|
||||
def get_effective_scopes(self):
|
||||
if self.staff_role == 'SUPER_ADMIN' or self.user.is_superuser:
|
||||
return ['*']
|
||||
scopes = set()
|
||||
if self.department:
|
||||
scopes.update(self.department.base_scopes or [])
|
||||
if self.squad:
|
||||
scopes.update(self.squad.extra_scopes or [])
|
||||
if self.staff_role == 'MANAGER':
|
||||
scopes.add('settings.staff')
|
||||
return list(scopes)
|
||||
|
||||
def get_allowed_modules(self):
|
||||
scopes = self.get_effective_scopes()
|
||||
if '*' in scopes:
|
||||
return ['dashboard', 'partners', 'events', 'ad-control', 'users', 'reviews', 'contributions', 'financials', 'settings']
|
||||
SCOPE_TO_MODULE = {
|
||||
'users': 'users',
|
||||
'events': 'events',
|
||||
'finance': 'financials',
|
||||
'partners': 'partners',
|
||||
'tickets': 'dashboard',
|
||||
'settings': 'settings',
|
||||
'ads': 'ad-control',
|
||||
'contributions': 'contributions',
|
||||
}
|
||||
modules = {'dashboard'}
|
||||
for scope in scopes:
|
||||
prefix = scope.split('.')[0]
|
||||
if prefix in SCOPE_TO_MODULE:
|
||||
modules.add(SCOPE_TO_MODULE[prefix])
|
||||
return list(modules)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.username} ({self.staff_role})"
|
||||
|
||||
|
||||
class CustomRole(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
slug = models.SlugField(unique=True)
|
||||
description = models.TextField(blank=True, default='')
|
||||
scopes = models.JSONField(default=list)
|
||||
is_system = models.BooleanField(default=False)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class AuditLog(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='audit_logs')
|
||||
action = models.CharField(max_length=100)
|
||||
target_type = models.CharField(max_length=50)
|
||||
target_id = models.CharField(max_length=50)
|
||||
details = models.JSONField(default=dict)
|
||||
ip_address = models.GenericIPAddressField(null=True, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.action} by {self.user} at {self.created_at}"
|
||||
|
||||
Reference in New Issue
Block a user