mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-27 10:07:05 -05:00
feat: Add blog, media, and support apps, implement ride credits and image API, and remove toplist feature.
This commit is contained in:
0
backend/apps/support/__init__.py
Normal file
0
backend/apps/support/__init__.py
Normal file
6
backend/apps/support/apps.py
Normal file
6
backend/apps/support/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class SupportConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "apps.support"
|
||||
verbose_name = "Support"
|
||||
54
backend/apps/support/migrations/0001_initial.py
Normal file
54
backend/apps/support/migrations/0001_initial.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# Generated by Django 5.1.6 on 2025-12-26 14:34
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Ticket",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
("subject", models.CharField(max_length=255)),
|
||||
("message", models.TextField()),
|
||||
("email", models.EmailField(blank=True, help_text="Contact email", max_length=254)),
|
||||
(
|
||||
"status",
|
||||
models.CharField(
|
||||
choices=[("open", "Open"), ("in_progress", "In Progress"), ("closed", "Closed")],
|
||||
db_index=True,
|
||||
default="open",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="User who submitted the ticket (optional)",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="tickets",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Ticket",
|
||||
"verbose_name_plural": "Tickets",
|
||||
"ordering": ["-created_at"],
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
]
|
||||
0
backend/apps/support/migrations/__init__.py
Normal file
0
backend/apps/support/migrations/__init__.py
Normal file
48
backend/apps/support/models.py
Normal file
48
backend/apps/support/models.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
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'),
|
||||
]
|
||||
|
||||
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)"
|
||||
)
|
||||
|
||||
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_status_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)
|
||||
27
backend/apps/support/serializers.py
Normal file
27
backend/apps/support/serializers.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Ticket
|
||||
from apps.accounts.serializers import UserSerializer
|
||||
|
||||
class TicketSerializer(serializers.ModelSerializer):
|
||||
user = UserSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Ticket
|
||||
fields = [
|
||||
"id",
|
||||
"user",
|
||||
"subject",
|
||||
"message",
|
||||
"email",
|
||||
"status",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
read_only_fields = ["id", "status", "created_at", "updated_at", "user"]
|
||||
|
||||
def validate(self, data):
|
||||
# Ensure email is provided if user is anonymous
|
||||
request = self.context.get('request')
|
||||
if request and not request.user.is_authenticated and not data.get('email'):
|
||||
raise serializers.ValidationError({"email": "Email is required for guests."})
|
||||
return data
|
||||
10
backend/apps/support/urls.py
Normal file
10
backend/apps/support/urls.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import TicketViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r"tickets", TicketViewSet, basename="ticket")
|
||||
|
||||
urlpatterns = [
|
||||
path("", include(router.urls)),
|
||||
]
|
||||
32
backend/apps/support/views.py
Normal file
32
backend/apps/support/views.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from rest_framework import viewsets, permissions, filters
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from .models import Ticket
|
||||
from .serializers import TicketSerializer
|
||||
|
||||
class TicketViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
Standard users/guests can CREATE.
|
||||
Only Staff can LIST/RETRIEVE/UPDATE all.
|
||||
Users can LIST/RETRIEVE their own.
|
||||
"""
|
||||
queryset = Ticket.objects.all()
|
||||
serializer_class = TicketSerializer
|
||||
permission_classes = [permissions.AllowAny] # We handle granular perms in get_queryset/perform_create
|
||||
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
|
||||
filterset_fields = ["status"]
|
||||
ordering_fields = ["created_at", "status"]
|
||||
ordering = ["-created_at"]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
if user.is_staff:
|
||||
return Ticket.objects.all()
|
||||
if user.is_authenticated:
|
||||
return Ticket.objects.filter(user=user)
|
||||
return Ticket.objects.none() # Guests can't list tickets
|
||||
|
||||
def perform_create(self, serializer):
|
||||
if self.request.user.is_authenticated:
|
||||
serializer.save(user=self.request.user, email=self.request.user.email)
|
||||
else:
|
||||
serializer.save()
|
||||
Reference in New Issue
Block a user