from django.db import models from django.core.validators import MinValueValidator, MaxValueValidator class Review(models.Model): STATUS_PENDING = 'pending' STATUS_LIVE = 'live' STATUS_REJECTED = 'rejected' STATUS_CHOICES = [ (STATUS_PENDING, 'Pending'), (STATUS_LIVE, 'Live'), (STATUS_REJECTED, 'Rejected'), ] REJECT_CHOICES = [ ('spam', 'Spam'), ('inappropriate', 'Inappropriate'), ('fake', 'Fake'), ] reviewer = models.ForeignKey( 'accounts.User', on_delete=models.CASCADE, related_name='admin_reviews' ) event = models.ForeignKey( 'events.Event', on_delete=models.CASCADE, related_name='admin_reviews' ) rating = models.IntegerField( validators=[MinValueValidator(1), MaxValueValidator(5)] ) review_text = models.TextField() submission_date = models.DateTimeField(auto_now_add=True) status = models.CharField( max_length=10, choices=STATUS_CHOICES, default=STATUS_PENDING ) reject_reason = models.CharField( max_length=15, choices=REJECT_CHOICES, null=True, blank=True ) display_name = models.CharField(max_length=100, blank=True, default='') is_verified = models.BooleanField(default=False) helpful_count = models.IntegerField(default=0) flag_count = models.IntegerField(default=0) class Meta: ordering = ['-submission_date'] indexes = [ models.Index(fields=['status']), models.Index(fields=['submission_date']), ] def __str__(self): return f'Review #{self.pk} by {self.reviewer_id} — {self.status}' class ReviewInteraction(models.Model): INTERACTION_CHOICES = [('HELPFUL', 'Helpful'), ('FLAG', 'Flag')] review = models.ForeignKey(Review, on_delete=models.CASCADE, related_name='interactions') username = models.CharField(max_length=255) interaction_type = models.CharField(max_length=20, choices=INTERACTION_CHOICES) created_at = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ('review', 'username', 'interaction_type') 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', 'leads', 'financials', 'audit-log', 'settings'] SCOPE_TO_MODULE = { 'users': 'users', 'events': 'events', 'finance': 'financials', 'partners': 'partners', 'tickets': 'dashboard', 'settings': 'settings', 'ads': 'ad-control', 'contributions': 'contributions', 'leads': 'leads', 'audit': 'audit-log', } 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'] indexes = [ # Fast filter-by-action ordered by time (audit log page default view) models.Index(fields=['action', '-created_at'], name='auditlog_action_time_idx'), # Fast "related entries for this target" lookups in the detail panel models.Index(fields=['target_type', 'target_id'], name='auditlog_target_idx'), ] def __str__(self): return f"{self.action} by {self.user} at {self.created_at}" # --------------------------------------------------------------------------- # Lead Manager # --------------------------------------------------------------------------- class Lead(models.Model): EVENT_TYPE_CHOICES = [ ('private', 'Private Event'), ('ticketed', 'Ticketed Event'), ('corporate', 'Corporate Event'), ('wedding', 'Wedding'), ('other', 'Other'), ] STATUS_CHOICES = [ ('new', 'New'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('converted', 'Converted'), ('closed', 'Closed'), ] SOURCE_CHOICES = [ ('schedule_call', 'Schedule a Call'), ('website', 'Website'), ('manual', 'Manual'), ] PRIORITY_CHOICES = [ ('low', 'Low'), ('medium', 'Medium'), ('high', 'High'), ] name = models.CharField(max_length=200) email = models.EmailField() phone = models.CharField(max_length=20) event_type = models.CharField(max_length=20, choices=EVENT_TYPE_CHOICES, default='private') message = models.TextField(blank=True, default='') status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='new') source = models.CharField(max_length=20, choices=SOURCE_CHOICES, default='schedule_call') priority = models.CharField(max_length=10, choices=PRIORITY_CHOICES, default='medium') assigned_to = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, blank=True, related_name='assigned_leads' ) user_account = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, blank=True, related_name='submitted_leads', help_text='Consumer platform account that submitted this lead (auto-matched by email)' ) notes = models.TextField(blank=True, default='') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['-created_at'] indexes = [ models.Index(fields=['status']), models.Index(fields=['priority']), models.Index(fields=['created_at']), models.Index(fields=['email']), ] def __str__(self): return f'Lead #{self.pk} — {self.name} ({self.status})'