From 8ab4006ee36ff0a9bb8ea74e49dfe8f523172d4b Mon Sep 17 00:00:00 2001 From: AmirHossein Shirazi Date: Sat, 14 Dec 2024 14:28:47 +0330 Subject: [PATCH] add ticketing app and endpoints --- backend/core/settings.py | 1 + backend/core/urls.py | 1 + backend/ticket/__init__.py | 0 backend/ticket/admin.py | 3 ++ backend/ticket/apps.py | 6 +++ backend/ticket/migrations/0001_initial.py | 39 ++++++++++++++ backend/ticket/migrations/__init__.py | 0 backend/ticket/models.py | 29 ++++++++++ backend/ticket/serializers.py | 14 +++++ backend/ticket/tests.py | 3 ++ backend/ticket/urls.py | 16 ++++++ backend/ticket/views.py | 65 +++++++++++++++++++++++ 12 files changed, 177 insertions(+) create mode 100644 backend/ticket/__init__.py create mode 100644 backend/ticket/admin.py create mode 100644 backend/ticket/apps.py create mode 100644 backend/ticket/migrations/0001_initial.py create mode 100644 backend/ticket/migrations/__init__.py create mode 100644 backend/ticket/models.py create mode 100644 backend/ticket/serializers.py create mode 100644 backend/ticket/tests.py create mode 100644 backend/ticket/urls.py create mode 100644 backend/ticket/views.py diff --git a/backend/core/settings.py b/backend/core/settings.py index 89d5fe3..0af70f0 100644 --- a/backend/core/settings.py +++ b/backend/core/settings.py @@ -97,6 +97,7 @@ INSTALLED_APPS = [ 'product', 'account', 'entertainment', + 'ticket', ] MIDDLEWARE = [ diff --git a/backend/core/urls.py b/backend/core/urls.py index 8c977b5..84a2536 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -20,6 +20,7 @@ urlpatterns = [ # path('comment/', views.CommentView.as_view(), name='comment-list'), path('products/', include('product.urls')), path('accounts/', include('account.urls')), + path('tickets/', include('ticket.urls')), path('', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), ] diff --git a/backend/ticket/__init__.py b/backend/ticket/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/ticket/admin.py b/backend/ticket/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/backend/ticket/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/backend/ticket/apps.py b/backend/ticket/apps.py new file mode 100644 index 0000000..1b423f8 --- /dev/null +++ b/backend/ticket/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TicketConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'ticket' diff --git a/backend/ticket/migrations/0001_initial.py b/backend/ticket/migrations/0001_initial.py new file mode 100644 index 0000000..b9a3ba9 --- /dev/null +++ b/backend/ticket/migrations/0001_initial.py @@ -0,0 +1,39 @@ +# Generated by Django 5.1.2 on 2024-12-14 10:51 + +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')), + ('subject', models.CharField(max_length=255)), + ('status', models.CharField(choices=[('open', 'Open'), ('in_progress', 'In Progress'), ('resolved', 'Resolved'), ('closed', 'Closed')], default='open', max_length=20)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('admin', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned_tickets', to=settings.AUTH_USER_MODEL)), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tickets', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Message', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('content', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='ticket.ticket')), + ], + ), + ] diff --git a/backend/ticket/migrations/__init__.py b/backend/ticket/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/ticket/models.py b/backend/ticket/models.py new file mode 100644 index 0000000..7bc0120 --- /dev/null +++ b/backend/ticket/models.py @@ -0,0 +1,29 @@ +from django.db import models +from account.models import User + +class Ticket(models.Model): + STATUS_CHOICES = [ + ('open', 'Open'), + ('in_progress', 'In Progress'), + ('resolved', 'Resolved'), + ('closed', 'Closed'), + ] + + subject = models.CharField(max_length=255) + customer = models.ForeignKey(User, on_delete=models.CASCADE, related_name="tickets") + admin = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="assigned_tickets") + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='open') + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + return self.subject + +class Message(models.Model): + ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, related_name="messages") + sender = models.ForeignKey(User, on_delete=models.CASCADE) + content = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"Message by {self.sender.username} on {self.ticket.subject}" \ No newline at end of file diff --git a/backend/ticket/serializers.py b/backend/ticket/serializers.py new file mode 100644 index 0000000..d9afdcb --- /dev/null +++ b/backend/ticket/serializers.py @@ -0,0 +1,14 @@ +from rest_framework import serializers +from .models import Ticket, Message + +class MessageSerializer(serializers.ModelSerializer): + class Meta: + model = Message + fields = '__all__' + +class TicketSerializer(serializers.ModelSerializer): + messages = MessageSerializer(many=True, read_only=True) + + class Meta: + model = Ticket + fields = '__all__' \ No newline at end of file diff --git a/backend/ticket/tests.py b/backend/ticket/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/backend/ticket/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/ticket/urls.py b/backend/ticket/urls.py new file mode 100644 index 0000000..79730e1 --- /dev/null +++ b/backend/ticket/urls.py @@ -0,0 +1,16 @@ +from django.urls import path +from .views import ( + TicketCreateView, + TicketListView, + TicketDetailView, + MessageCreateView, + UpdateTicketStatusView +) + +urlpatterns = [ + path('create', TicketCreateView.as_view(), name='ticket-create'), + path('', TicketListView.as_view(), name='ticket-list'), + path('', TicketDetailView.as_view(), name='ticket-detail'), + path('/messages', MessageCreateView.as_view(), name='message-create'), + path('/update-status', UpdateTicketStatusView.as_view(), name='update-ticket-status'), +] \ No newline at end of file diff --git a/backend/ticket/views.py b/backend/ticket/views.py new file mode 100644 index 0000000..4a5c0f3 --- /dev/null +++ b/backend/ticket/views.py @@ -0,0 +1,65 @@ +from rest_framework import generics, permissions +from rest_framework.response import Response +from rest_framework.views import APIView +from .models import Ticket, Message +from .serializers import TicketSerializer, MessageSerializer + +class TicketCreateView(generics.CreateAPIView): + queryset = Ticket.objects.all() + serializer_class = TicketSerializer + permission_classes = [permissions.IsAuthenticated] + + def perform_create(self, serializer): + serializer.save(customer=self.request.user) + + +class TicketListView(generics.ListAPIView): + serializer_class = TicketSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + user = self.request.user + if user.is_staff: + return Ticket.objects.all() + return Ticket.objects.filter(customer=user) + + +class TicketDetailView(generics.RetrieveAPIView): + queryset = Ticket.objects.all() + serializer_class = TicketSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + user = self.request.user + if user.is_staff: + return Ticket.objects.all() + return Ticket.objects.filter(customer=user) + + +class MessageCreateView(generics.CreateAPIView): + queryset = Message.objects.all() + serializer_class = MessageSerializer + permission_classes = [permissions.IsAuthenticated] + + def perform_create(self, serializer): + ticket = serializer.validated_data.get('ticket') + if self.request.user != ticket.customer and not self.request.user.is_staff: + raise permissions.PermissionDenied("You are not authorized to send a message to this ticket.") + serializer.save(sender=self.request.user) + + +class UpdateTicketStatusView(APIView): + permission_classes = [permissions.IsAdminUser] + + def post(self, request, pk): + try: + ticket = Ticket.objects.get(pk=pk) + except Ticket.DoesNotExist: + return Response({"error": "Ticket not found"}, status=404) + + new_status = request.data.get('status') + if new_status not in ['open', 'in_progress', 'resolved', 'closed']: + return Response({"error": "Invalid status"}, status=400) + ticket.status = new_status + ticket.save() + return Response({"message": "Ticket status updated successfully"}) \ No newline at end of file