Files
thrillwiki_django_no_react/backend/apps/support/models.py
pacnpal 2b66814d82 Based on the git diff provided, here's a concise and descriptive commit message:
feat: add security event taxonomy and optimize park queryset

- Add comprehensive security_event_types ChoiceGroup with categories for authentication, MFA, password, account, session, and API key events
- Include severity levels, icons, and CSS classes for each event type
- Fix park queryset optimization by using select_related for OneToOne location relationship
- Remove location property fields (latitude/longitude) from values() call as they are not actual DB columns
- Add proper location fields (city, state, country) to values() for map display

This change enhances security event tracking capabilities and resolves a queryset optimization issue where property decorators were incorrectly used in values() queries.
2026-01-10 16:41:31 -05:00

156 lines
4.6 KiB
Python

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)