from django.conf import settings from django.db import models from apps.core.history import TrackedModel class Ticket(TrackedModel): STATUS_OPEN = "open" STATUS_IN_PROGRESS = "in_progress" STATUS_CLOSED = "closed" STATUS_CHOICES = [ (STATUS_OPEN, "Open"), (STATUS_IN_PROGRESS, "In Progress"), (STATUS_CLOSED, "Closed"), ] CATEGORY_GENERAL = "general" CATEGORY_BUG = "bug" CATEGORY_PARTNERSHIP = "partnership" CATEGORY_PRESS = "press" CATEGORY_DATA = "data" CATEGORY_ACCOUNT = "account" CATEGORY_CHOICES = [ (CATEGORY_GENERAL, "General Inquiry"), (CATEGORY_BUG, "Bug Report"), (CATEGORY_PARTNERSHIP, "Partnership"), (CATEGORY_PRESS, "Press/Media"), (CATEGORY_DATA, "Data Correction"), (CATEGORY_ACCOUNT, "Account Issue"), ] user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name="tickets", help_text="User who submitted the ticket (optional)", ) category = models.CharField( max_length=20, choices=CATEGORY_CHOICES, default=CATEGORY_GENERAL, db_index=True, help_text="Category of the ticket", ) subject = models.CharField(max_length=255) message = models.TextField() email = models.EmailField(help_text="Contact email", blank=True) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_OPEN, db_index=True) class Meta(TrackedModel.Meta): verbose_name = "Ticket" verbose_name_plural = "Tickets" ordering = ["-created_at"] def __str__(self): return f"[{self.get_category_display()}] {self.subject}" def save(self, *args, **kwargs): # If user is set but email is empty, autofill from user if self.user and not self.email: self.email = self.user.email super().save(*args, **kwargs) class Report(TrackedModel): """ User-submitted reports about content issues. Reports allow users to flag problems with specific entities (parks, rides, reviews, etc.) for moderator review. """ class ReportType(models.TextChoices): INACCURATE = "inaccurate", "Inaccurate Information" INAPPROPRIATE = "inappropriate", "Inappropriate Content" SPAM = "spam", "Spam" COPYRIGHT = "copyright", "Copyright Violation" DUPLICATE = "duplicate", "Duplicate Content" OTHER = "other", "Other" class Status(models.TextChoices): PENDING = "pending", "Pending" INVESTIGATING = "investigating", "Investigating" RESOLVED = "resolved", "Resolved" DISMISSED = "dismissed", "Dismissed" # Reporter (optional for anonymous reports) reporter = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name="submitted_reports", help_text="User who submitted the report", ) # Target entity using GenericForeignKey content_type = models.ForeignKey( "contenttypes.ContentType", on_delete=models.CASCADE, help_text="Type of entity being reported", ) object_id = models.CharField( max_length=50, help_text="ID of the entity being reported", ) # Note: GenericForeignKey doesn't create a database column # It's a convenience for accessing the related object # content_object = GenericForeignKey("content_type", "object_id") # Report details report_type = models.CharField( max_length=20, choices=ReportType.choices, db_index=True, help_text="Type of issue being reported", ) reason = models.TextField( help_text="Detailed description of the issue", ) status = models.CharField( max_length=20, choices=Status.choices, default=Status.PENDING, db_index=True, help_text="Current status of the report", ) # Resolution resolved_at = models.DateTimeField( null=True, blank=True, help_text="When the report was resolved", ) resolved_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name="resolved_reports", help_text="Moderator who resolved the report", ) resolution_notes = models.TextField( blank=True, help_text="Notes about how the report was resolved", ) class Meta(TrackedModel.Meta): verbose_name = "Report" verbose_name_plural = "Reports" ordering = ["-created_at"] indexes = [ models.Index(fields=["status", "created_at"]), models.Index(fields=["content_type", "object_id"]), models.Index(fields=["report_type", "created_at"]), ] def __str__(self): return f"[{self.get_report_type_display()}] {self.content_type} #{self.object_id}" @property def is_resolved(self) -> bool: return self.status in (self.Status.RESOLVED, self.Status.DISMISSED)