mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-02-05 02:35:18 -05:00
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.
156 lines
4.6 KiB
Python
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)
|
|
|