contact us model view and admin functionality

This commit is contained in:
Parsa Nazer
2025-04-22 02:41:11 +03:30
parent a195017f80
commit c84e5150f8
12 changed files with 154 additions and 9 deletions
+6
View File
@@ -239,6 +239,12 @@ UNFOLD = {
"badge": "utils.admin.new_ticket_count", "badge": "utils.admin.new_ticket_count",
}, },
{
"title": _("ارتباط با ما"),
"icon": "perm_phone_msg",
"link": reverse_lazy("admin:ticket_contactusmodel_changelist"),
"badge": "utils.admin.new_contact_us_count",
},
], ],
}, },
{ {
+3 -1
View File
@@ -8,7 +8,7 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import RedirectView, TemplateView from django.views.generic import RedirectView, TemplateView
from unfold.views import UnfoldModelAdminViewMixin from unfold.views import UnfoldModelAdminViewMixin
from order.models import OrderModel from order.models import OrderModel
from ticket.models import Ticket from ticket.models import Ticket, ContactUsModel
from account.models import SecurityBreachAttemptModel from account.models import SecurityBreachAttemptModel
import json import json
@@ -19,9 +19,11 @@ def dashboard_callback(request, context):
pending_count = OrderModel.objects.filter(status='ADMIN_PENDING').count() pending_count = OrderModel.objects.filter(status='ADMIN_PENDING').count()
open_tickets_count = Ticket.objects.filter(status__in=['open', 'in_progress']).count() open_tickets_count = Ticket.objects.filter(status__in=['open', 'in_progress']).count()
open_contact_us_count = ContactUsModel.objects.filter(is_reviewed=False).count()
context.update(random_data()) context.update(random_data())
context.update({'pending_count': pending_count}) context.update({'pending_count': pending_count})
context.update({'open_tickets_count': open_tickets_count}) context.update({'open_tickets_count': open_tickets_count})
context.update({'open_contact_us_count': open_contact_us_count})
return context return context
+21
View File
@@ -40,3 +40,24 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if open_contact_us_count%}
<div dir='rtl' class="bg-base-50 border border-base-200 border-dashed flex flex-col gap-4 p-4 rounded dark:bg-white/[.02] dark:border-base-700 lg:flex-row lg:justify-between w-full shrink-0 lg:items-center" style="justify-content: space-between;">
<div class="flex flex-col lg:flex-row lg:items-center">
<h2 class="font-semibold text-font-important-light text-base dark:text-font-important-dark flex items-center">
<span class="material-symbols-outlined md-18 mr-3 w-4.5 align-middle">confirmation_number</span>
<span class="align-middle" style="margin-right: 8px;">ارتباط با ما جدید داری</span>
<span class="text-white bg-primary-800 py-1 px-2 rounded" style="margin-right: 8px;">{{ open_contact_us_count }} </span>
</h2>
</div>
<div class="flex lg:flex-row lg:items-center">
{% component "unfold/components/flex.html" with class="flex-col gap-4 lg:flex-row" %}
{% component "unfold/components/button.html" with href="/secret-admin/ticket/contactusmodel/" %}
نمایش ارتباط با ما ها
{% endcomponent %}
{% endcomponent %}
</div>
</div>
{% endif %}
+9
View File
@@ -18,6 +18,15 @@ class MessageInline(TabularInline):
model = Message model = Message
extra = 1 extra = 1
@admin.register(ContactUsModel)
class ContactUsAdmin(ModelAdmin):
list_filter = ['type', 'is_reviewed']
list_display = ['full_name', 'phone', 'email', 'message', 'is_reviewed']
compressed_fields = True
warn_unsaved_form = True
readonly_fields = ['full_name', 'email', 'phone', 'type', 'message', ]
@admin.register(Ticket) @admin.register(Ticket)
class TicketAdmin(ModelAdmin, ImportExportModelAdmin): class TicketAdmin(ModelAdmin, ImportExportModelAdmin):
import_form_class = ImportForm import_form_class = ImportForm
@@ -0,0 +1,24 @@
# Generated by Django 5.1.2 on 2025-04-21 22:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0019_alter_attachment_options'),
]
operations = [
migrations.CreateModel(
name='ContactUsModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('full_name', models.CharField(max_length=50, verbose_name='نام و نام خانوادگی')),
('email', models.EmailField(max_length=254, verbose_name='ایمیل')),
('phone', models.CharField(max_length=30)),
('type', models.CharField(choices=[('ORDER', 'پیگیری سفارش'), ('SUGGESTION', 'پیشنهادات'), ('COMPLAINT', 'انتقادات')], max_length=20, verbose_name='نوع')),
('message', models.TextField(verbose_name='پیام')),
],
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2025-04-21 22:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0020_contactusmodel'),
]
operations = [
migrations.AlterField(
model_name='contactusmodel',
name='type',
field=models.CharField(choices=[('ORDER_FOLLOW_UP', 'پیگیری سفارش'), ('SUGGESTION', 'پیشنهادات'), ('COMPLAINT', 'انتقادات')], max_length=20, verbose_name='نوع'),
),
]
@@ -0,0 +1,22 @@
# Generated by Django 5.1.2 on 2025-04-21 22:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0021_alter_contactusmodel_type'),
]
operations = [
migrations.AlterModelOptions(
name='contactusmodel',
options={'verbose_name': 'ارتباط با ما', 'verbose_name_plural': 'ارتباط با ما ها '},
),
migrations.AddField(
model_name='contactusmodel',
name='is_reviewed',
field=models.BooleanField(default=False),
),
]
+22
View File
@@ -74,3 +74,25 @@ class Message(models.Model):
class Meta: class Meta:
verbose_name = 'پیام تیکت' verbose_name = 'پیام تیکت'
verbose_name_plural = 'پیام های تیکت' verbose_name_plural = 'پیام های تیکت'
class ContactUsModel(models.Model):
full_name = models.CharField(max_length=50, verbose_name='نام و نام خانوادگی')
email = models.EmailField(max_length=254, verbose_name='ایمیل')
phone = models.CharField(max_length=30, verbose_name='شماره تماس')
FEEDBACK_TYPES = [
('ORDER_FOLLOW_UP', 'پیگیری سفارش'),
('SUGGESTION', 'پیشنهادات'),
('COMPLAINT', 'انتقادات'),
]
type = models.CharField(max_length=20, choices=FEEDBACK_TYPES, verbose_name='نوع')
message = models.TextField(verbose_name='پیام')
is_reviewed = models.BooleanField(default=False, verbose_name='بررسی شده')
def __str__(self):
return f'{self.full_name} - {self.message[:15]}...'
class Meta:
verbose_name = 'ارتباط با ما'
verbose_name_plural = 'ارتباط با ما ها '
+7 -1
View File
@@ -1,5 +1,5 @@
from rest_framework import serializers from rest_framework import serializers
from .models import Ticket, Message, Attachment from .models import Ticket, Message, Attachment, ContactUsModel
from django.utils.timezone import localtime from django.utils.timezone import localtime
from order.serializers import OrderListSerializer from order.serializers import OrderListSerializer
from order.serializers import OrderModel from order.serializers import OrderModel
@@ -89,3 +89,9 @@ class TicketListSerializer(serializers.ModelSerializer):
def get_ticket_category(self, obj): def get_ticket_category(self, obj):
return obj.get_ticket_category_display() return obj.get_ticket_category_display()
class ContactUsSerializer(serializers.ModelSerializer):
class Meta:
model = ContactUsModel
fields = ['full_name', 'email', 'phone', 'type', 'message']
+1
View File
@@ -6,6 +6,7 @@ urlpatterns = [
path('', views.TicketListView.as_view(), name='ticket-list'), path('', views.TicketListView.as_view(), name='ticket-list'),
path('<int:pk>', views.TicketDetailView.as_view(), name='ticket-detail'), path('<int:pk>', views.TicketDetailView.as_view(), name='ticket-detail'),
path('message/create', views.MessageCreateView.as_view(), name='message-create'), path('message/create', views.MessageCreateView.as_view(), name='message-create'),
path('contact-us/create', views.CreateContactUsView.as_view(), name='contact-us-create'),
path('attachment/create', views.AttachmentUploadView.as_view(), name='attachment-upload'), path('attachment/create', views.AttachmentUploadView.as_view(), name='attachment-upload'),
path('attachment/delete/<int:pk>', views.AttachmentDeleteView.as_view(), name='attachment-upload'), path('attachment/delete/<int:pk>', views.AttachmentDeleteView.as_view(), name='attachment-upload'),
] ]
+13 -2
View File
@@ -2,7 +2,7 @@ from rest_framework import generics, permissions
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from .models import Ticket, Message, Attachment from .models import Ticket, Message, Attachment
from .serializers import TicketListSerializer, MessageSerializer, TicketSerializer, AttachmentSerializer from .serializers import TicketListSerializer, MessageSerializer, TicketSerializer, AttachmentSerializer, ContactUsSerializer
from utils.pagination import StructurePagination from utils.pagination import StructurePagination
from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
@@ -56,7 +56,6 @@ class TicketCreateView(APIView):
permission_classes = [permissions.IsAuthenticated] permission_classes = [permissions.IsAuthenticated]
def post(self, request): def post(self, request):
new_ticket_ser = self.serializer_class(data=request.data, context={'request': request}) new_ticket_ser = self.serializer_class(data=request.data, context={'request': request})
message = request.data.get('message', None)
if new_ticket_ser.is_valid(): if new_ticket_ser.is_valid():
new_ticket_ser.save(customer=request.user) new_ticket_ser.save(customer=request.user)
return Response(new_ticket_ser.data, status=status.HTTP_201_CREATED) return Response(new_ticket_ser.data, status=status.HTTP_201_CREATED)
@@ -160,3 +159,15 @@ class UpdateTicketStatusView(APIView):
ticket.status = new_status ticket.status = new_status
ticket.save() ticket.save()
return Response({"message": "Ticket status updated successfully"}) return Response({"message": "Ticket status updated successfully"})
class CreateContactUsView(APIView):
serializer_class = ContactUsSerializer
permission_classes = [permissions.AllowAny]
def post(self, request):
contact_us_ser = self.serializer_class(data=request.data, context={'request': request})
if contact_us_ser.is_valid():
contact_us_ser.save()
return Response(contact_us_ser.data, status=status.HTTP_201_CREATED)
else:
return Response(contact_us_ser.errors, status=status.HTTP_400_BAD_REQUEST)
+4 -1
View File
@@ -1,6 +1,6 @@
from order.models import OrderModel from order.models import OrderModel
from product.models import DollorModel, CommentModel from product.models import DollorModel, CommentModel
from ticket.models import Ticket from ticket.models import Ticket, ContactUsModel
from home.models import LearnVideoModel from home.models import LearnVideoModel
from account.models import SecurityBreachAttemptModel from account.models import SecurityBreachAttemptModel
@@ -24,6 +24,9 @@ def new_learn_video_count(request):
def new_attck_count(request): def new_attck_count(request):
return SecurityBreachAttemptModel.objects.filter(viewd=False).count() return SecurityBreachAttemptModel.objects.filter(viewd=False).count()
def new_contact_us_count(request):
return ContactUsModel.objects.filter(is_reviewed=False).count()
from django.contrib import admin, messages from django.contrib import admin, messages
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin
from home.models import LearnVideoModel from home.models import LearnVideoModel