contact us model view and admin functionality
This commit is contained in:
@@ -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",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,4 +39,25 @@
|
|||||||
{% endcomponent %}
|
{% endcomponent %}
|
||||||
</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 %}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -73,4 +73,26 @@ 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 = 'ارتباط با ما ها '
|
||||||
@@ -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
|
||||||
@@ -88,4 +88,10 @@ class TicketListSerializer(serializers.ModelSerializer):
|
|||||||
return obj.get_status_display()
|
return obj.get_status_display()
|
||||||
|
|
||||||
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']
|
||||||
@@ -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'),
|
||||||
]
|
]
|
||||||
+14
-3
@@ -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)
|
||||||
@@ -159,4 +158,16 @@ class UpdateTicketStatusView(APIView):
|
|||||||
return Response({"error": "Invalid status"}, status=400)
|
return Response({"error": "Invalid status"}, status=400)
|
||||||
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)
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user