from django.conf import settings from django.db import models from apps.core.choices.fields import RichChoiceField from apps.core.history import TrackedModel # Import choices to ensure registration on app load from . import choices # noqa: F401 class Ticket(TrackedModel): 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 = RichChoiceField( choice_group="ticket_categories", domain="support", max_length=20, default="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 = RichChoiceField( choice_group="ticket_statuses", domain="support", max_length=20, default="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)