From 0f76be9f116618df286530e6615eddb7209109ea Mon Sep 17 00:00:00 2001 From: AmirHossein Shirazi Date: Sat, 14 Dec 2024 14:27:53 +0330 Subject: [PATCH 1/2] address complete --- .../migrations/0006_useraddressmodel.py | 26 ++++++++++ backend/account/models.py | 9 ++++ backend/account/serializers.py | 12 +++++ backend/account/urls.py | 6 +++ backend/account/views.py | 48 +++++++++++++++++-- 5 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 backend/account/migrations/0006_useraddressmodel.py diff --git a/backend/account/migrations/0006_useraddressmodel.py b/backend/account/migrations/0006_useraddressmodel.py new file mode 100644 index 0000000..c7bbf1d --- /dev/null +++ b/backend/account/migrations/0006_useraddressmodel.py @@ -0,0 +1,26 @@ +# Generated by Django 5.1.2 on 2024-12-14 10:38 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0005_remove_user_groups_remove_user_user_permissions'), + ] + + operations = [ + migrations.CreateModel( + name='UserAddressModel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=30)), + ('address', models.TextField()), + ('postal_code', models.CharField(max_length=10)), + ('phone', models.CharField(max_length=11)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/backend/account/models.py b/backend/account/models.py index cc94052..6fef0b4 100644 --- a/backend/account/models.py +++ b/backend/account/models.py @@ -98,3 +98,12 @@ class User(AbstractBaseUser, PermissionsMixin): return self.email +class UserAddressModel(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + name = models.CharField(max_length=30) + address = models.TextField() + postal_code = models.CharField(max_length=10) + phone = models.CharField(max_length=11) + + def __str__(self): + return f"{self.user.phone}, {self.name}" \ No newline at end of file diff --git a/backend/account/serializers.py b/backend/account/serializers.py index b342d1e..fb47e54 100644 --- a/backend/account/serializers.py +++ b/backend/account/serializers.py @@ -9,3 +9,15 @@ class ProfileSerializer(serializers.ModelSerializer): model = User fields = ['first_name', 'last_name', 'email', 'profile_photo', 'phone'] read_only_fields = ("phone",) + + +class UserAddressSerializer(serializers.ModelSerializer): + class Meta: + model = UserAddressModel + fields = ['id', 'name', 'address', 'postal_code', 'phone'] + + def validate(self, data): + user = self.context['request'].user + if not user.is_authenticated: + raise serializers.ValidationError("You must be logged in to perform this action.") + return data \ No newline at end of file diff --git a/backend/account/urls.py b/backend/account/urls.py index a8cab77..106fe30 100644 --- a/backend/account/urls.py +++ b/backend/account/urls.py @@ -3,4 +3,10 @@ from . import views urlpatterns = [ path('profile', views.ProfileView.as_view()), + path('address/create', views.CreateAddressView.as_view(), name='create-address'), + path('address/edit/', views.EditAddressView.as_view(), name='edit-address'), + path('address/delete/', views.DeleteAddressView.as_view(), name='delete-address'), + path('address/list', views.GetUserAddressesView.as_view(), name='list-addresses'), + path('address/', views.GetIDUserAddressView.as_view(), name='get-ID-address'), + ] \ No newline at end of file diff --git a/backend/account/views.py b/backend/account/views.py index d131306..15323f3 100644 --- a/backend/account/views.py +++ b/backend/account/views.py @@ -1,13 +1,14 @@ from django.shortcuts import render from rest_framework.views import APIView -from .serializers import ProfileSerializer -from rest_framework.permissions import IsAuthenticated -from rest_framework import status +from rest_framework import generics, permissions, status from rest_framework.response import Response +from .serializers import ProfileSerializer, UserAddressSerializer +from .models import UserAddressModel +from rest_framework.permissions import IsAuthenticated class ProfileView(APIView): serializer_class = ProfileSerializer permission_classes = [IsAuthenticated] - + def get(self, request): user_ser = self.serializer_class(instance=request.user) return Response(user_ser.data, status=status.HTTP_200_OK) @@ -19,4 +20,41 @@ class ProfileView(APIView): if user_ser.is_valid(): user_ser.save() return Response(user_ser.data) - return Response(user_ser.errors, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file + return Response(user_ser.errors, status=status.HTTP_400_BAD_REQUEST) + +class CreateAddressView(generics.CreateAPIView): + queryset = UserAddressModel.objects.all() + serializer_class = UserAddressSerializer + permission_classes = [permissions.IsAuthenticated] + + def perform_create(self, serializer): + serializer.save(user=self.request.user) + +class EditAddressView(generics.UpdateAPIView): + queryset = UserAddressModel.objects.all() + serializer_class = UserAddressSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return UserAddressModel.objects.filter(user=self.request.user) + +class DeleteAddressView(generics.DestroyAPIView): + queryset = UserAddressModel.objects.all() + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return UserAddressModel.objects.filter(user=self.request.user) + +class GetUserAddressesView(generics.ListAPIView): + serializer_class = UserAddressSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return UserAddressModel.objects.filter(user=self.request.user) + +class GetIDUserAddressView(generics.RetrieveAPIView): + serializer_class = UserAddressSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return UserAddressModel.objects.filter(user=self.request.user) \ No newline at end of file From 8ab4006ee36ff0a9bb8ea74e49dfe8f523172d4b Mon Sep 17 00:00:00 2001 From: AmirHossein Shirazi Date: Sat, 14 Dec 2024 14:28:47 +0330 Subject: [PATCH 2/2] 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