diff --git a/backend/.env.local b/backend/.env.local
index 4cb8221..2678574 100644
--- a/backend/.env.local
+++ b/backend/.env.local
@@ -28,4 +28,16 @@ SMS_API_KEY = ''
VAPID_PRIVATE_KEY = 'NajogmGTsGsZ_dfURrjUpgsm5fui-s5AzruBQgMh_I4'
-OPENAI_API_KEY = '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'
\ No newline at end of file
+OPENAI_API_KEY = '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'
+
+
+# DATABASES = {
+# 'default': {
+# 'ENGINE': 'django.db.backends.postgresql',
+# 'NAME': 'hshop',
+# 'USER': 'byeto',
+# 'PASSWORD': 'vuhbyq-cypMu0-sirbon',
+# 'HOST': '185.110.189.208',
+# 'PORT': '5434',
+# }
+# }
\ No newline at end of file
diff --git a/backend/core/settings/unfold_conf.py b/backend/core/settings/unfold_conf.py
index 23bf662..a143954 100644
--- a/backend/core/settings/unfold_conf.py
+++ b/backend/core/settings/unfold_conf.py
@@ -239,6 +239,12 @@ UNFOLD = {
"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",
+ },
],
},
{
@@ -262,6 +268,11 @@ UNFOLD = {
"icon": "photo_prints",
"link": reverse_lazy("admin:product_productvariant_changelist"),
},
+ {
+ "title": _("بخش جزيیات محصول"),
+ "icon": "subject",
+ "link": reverse_lazy("admin:product_productdetailmodel_changelist"),
+ },
],
},
diff --git a/backend/core/views.py b/backend/core/views.py
index be623df..9894f4e 100644
--- a/backend/core/views.py
+++ b/backend/core/views.py
@@ -8,7 +8,7 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import RedirectView, TemplateView
from unfold.views import UnfoldModelAdminViewMixin
from order.models import OrderModel
-from ticket.models import Ticket
+from ticket.models import Ticket, ContactUsModel
from account.models import SecurityBreachAttemptModel
import json
@@ -19,9 +19,11 @@ def dashboard_callback(request, context):
pending_count = OrderModel.objects.filter(status='ADMIN_PENDING').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({'pending_count': pending_count})
context.update({'open_tickets_count': open_tickets_count})
+ context.update({'open_contact_us_count': open_contact_us_count})
return context
diff --git a/backend/product/admin.py b/backend/product/admin.py
index a7d6e19..80ac965 100644
--- a/backend/product/admin.py
+++ b/backend/product/admin.py
@@ -1,4 +1,5 @@
from django.contrib import admin, messages
+# from product.tasks import update_prices
from .models import *
from unfold.admin import TabularInline, StackedInline
from home.models import LearnVideoModel
@@ -122,7 +123,7 @@ class DetailModelAdmin(ModelAdmin, ImportExportModelAdmin):
class ProductDetailModel1Admin(ModelAdmin, ImportExportModelAdmin):
import_form_class = ImportForm
export_form_class = ExportForm
- search_fields = ['detail_category__title']
+ search_fields = ['detail_category__title', 'name']
compressed_fields = True
warn_unsaved_form = True
@@ -216,8 +217,7 @@ class ProductModelAdmin(ModelAdmin, ImportExportModelAdmin):
@action(description=f"اپدیت قیمت ها")
def update_products_price(self, request):
- print('from the button')
- ProductVariant.update_all_prices()
+ # update_prices()
messages.success(request, f"قیمت {ProductVariant.objects.all().count()} تنوع محصول اپدیت شد")
return redirect("admin:product_productmodel_changelist")
diff --git a/backend/product/migrations/0036_alter_productdetailmodel_detail_category.py b/backend/product/migrations/0036_alter_productdetailmodel_detail_category.py
new file mode 100644
index 0000000..1ae6eb5
--- /dev/null
+++ b/backend/product/migrations/0036_alter_productdetailmodel_detail_category.py
@@ -0,0 +1,20 @@
+# Generated by Django 5.1.2 on 2025-04-21 23:30
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('product', '0035_alter_attributetype_options_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='productdetailmodel',
+ name='detail_category',
+ field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='product.productdetailcategory', verbose_name='دسته بندی جزيات'),
+ preserve_default=False,
+ ),
+ ]
diff --git a/backend/product/migrations/0037_productdetailmodel_name.py b/backend/product/migrations/0037_productdetailmodel_name.py
new file mode 100644
index 0000000..c93a035
--- /dev/null
+++ b/backend/product/migrations/0037_productdetailmodel_name.py
@@ -0,0 +1,19 @@
+# Generated by Django 5.1.2 on 2025-04-21 23:37
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('product', '0036_alter_productdetailmodel_detail_category'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='productdetailmodel',
+ name='name',
+ field=models.CharField(default=1, help_text='این متن فقط برای راحتی در استفاده از پنل ادمین میباشد', max_length=50, verbose_name='نام جزيیات'),
+ preserve_default=False,
+ ),
+ ]
diff --git a/backend/product/models.py b/backend/product/models.py
index 1a1ca6b..1a87333 100644
--- a/backend/product/models.py
+++ b/backend/product/models.py
@@ -4,6 +4,7 @@ from account.models import User
from django.urls import reverse
import requests
from django.utils.translation import gettext_lazy as _
+from django.core.exceptions import ValidationError
class MainCategoryModel(models.Model):
name = models.CharField(max_length=50, verbose_name='نام دسته بندی')
@@ -212,14 +213,15 @@ class ProductImageModel(models.Model):
class ProductDetailModel(models.Model):
- detail_category = models.ForeignKey(ProductDetailCategory, on_delete=models.CASCADE, verbose_name='دسته بندی جزيات', blank=True, null=True)
+ name = models.CharField(max_length=50, verbose_name='نام جزيیات', help_text='این متن فقط برای راحتی در استفاده از پنل ادمین میباشد')
+ detail_category = models.ForeignKey(ProductDetailCategory, on_delete=models.CASCADE, verbose_name='دسته بندی جزيات')
detail = models.ManyToManyField(DetailModel, verbose_name='جزيات ها')
class Meta:
verbose_name = 'جزیات محصول'
verbose_name_plural = 'جزیات محصول ها'
- # def __str__(self):
- # return f'جزيیات محصول {self.product}'
+ def __str__(self):
+ return f'جزيیات محصول {self.detail_category.title} - {self.name}'
class ProductVariant(models.Model):
product = models.ForeignKey(ProductModel, on_delete=models.CASCADE, related_name='variants', verbose_name='محصول')
@@ -272,21 +274,3 @@ class ProductVariant(models.Model):
def save(self, *args, **kwargs):
self.set_or_update_price()
super().save(*args, **kwargs)
-
- def get_toman_price_after_discount(self):
- return self.price * ((100 - self.discount) / 100)
-
- @classmethod
- def update_all_prices(cls):
- print('calling the update all prices ')
- dollor_object, _ = DollorModel.objects.get_or_create(unique_filed='unique')
- print(dollor_object.price)
- dollor_object.update_price()
- dollor_object.save()
- dollor_price = dollor_object.price
- print(dollor_object.price)
- print('classmethod dollor price update ')
- products = cls.objects.all()
- for product in products:
- product.set_or_update_price(dollor_price=dollor_price)
- product.save()
\ No newline at end of file
diff --git a/backend/templates/formula/service.html b/backend/templates/formula/service.html
index 57856a4..ca6cc92 100644
--- a/backend/templates/formula/service.html
+++ b/backend/templates/formula/service.html
@@ -39,4 +39,25 @@
{% endcomponent %}
-{% endif %}
\ No newline at end of file
+{% endif %}
+{% if open_contact_us_count%}
+
+
+
+
+
+ confirmation_number
+ ارتباط با ما جدید داری
+ {{ open_contact_us_count }}
+
+
+
+
+ {% 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 %}
+
+
+{% endif %}
diff --git a/backend/ticket/admin.py b/backend/ticket/admin.py
index 1426233..bc50d27 100644
--- a/backend/ticket/admin.py
+++ b/backend/ticket/admin.py
@@ -18,6 +18,15 @@ class MessageInline(TabularInline):
model = Message
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)
class TicketAdmin(ModelAdmin, ImportExportModelAdmin):
import_form_class = ImportForm
diff --git a/backend/ticket/migrations/0020_contactusmodel.py b/backend/ticket/migrations/0020_contactusmodel.py
new file mode 100644
index 0000000..d2bd2be
--- /dev/null
+++ b/backend/ticket/migrations/0020_contactusmodel.py
@@ -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='پیام')),
+ ],
+ ),
+ ]
diff --git a/backend/ticket/migrations/0021_alter_contactusmodel_type.py b/backend/ticket/migrations/0021_alter_contactusmodel_type.py
new file mode 100644
index 0000000..59c5790
--- /dev/null
+++ b/backend/ticket/migrations/0021_alter_contactusmodel_type.py
@@ -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='نوع'),
+ ),
+ ]
diff --git a/backend/ticket/migrations/0022_alter_contactusmodel_options_and_more.py b/backend/ticket/migrations/0022_alter_contactusmodel_options_and_more.py
new file mode 100644
index 0000000..537844b
--- /dev/null
+++ b/backend/ticket/migrations/0022_alter_contactusmodel_options_and_more.py
@@ -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),
+ ),
+ ]
diff --git a/backend/ticket/migrations/0023_alter_contactusmodel_is_reviewed_and_more.py b/backend/ticket/migrations/0023_alter_contactusmodel_is_reviewed_and_more.py
new file mode 100644
index 0000000..e798399
--- /dev/null
+++ b/backend/ticket/migrations/0023_alter_contactusmodel_is_reviewed_and_more.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.1.2 on 2025-04-21 23:30
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('ticket', '0022_alter_contactusmodel_options_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='contactusmodel',
+ name='is_reviewed',
+ field=models.BooleanField(default=False, verbose_name='بررسی شده'),
+ ),
+ migrations.AlterField(
+ model_name='contactusmodel',
+ name='phone',
+ field=models.CharField(max_length=30, verbose_name='شماره تماس'),
+ ),
+ ]
diff --git a/backend/ticket/models.py b/backend/ticket/models.py
index 0941fe3..a6b89a4 100644
--- a/backend/ticket/models.py
+++ b/backend/ticket/models.py
@@ -73,4 +73,26 @@ class Message(models.Model):
class Meta:
verbose_name = 'پیام تیکت'
- verbose_name_plural = 'پیام های تیکت'
\ No newline at end of file
+ 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 = 'ارتباط با ما ها '
\ No newline at end of file
diff --git a/backend/ticket/serializers.py b/backend/ticket/serializers.py
index d99a8d2..f4af17d 100644
--- a/backend/ticket/serializers.py
+++ b/backend/ticket/serializers.py
@@ -1,5 +1,5 @@
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 order.serializers import OrderListSerializer
from order.serializers import OrderModel
@@ -88,4 +88,10 @@ class TicketListSerializer(serializers.ModelSerializer):
return obj.get_status_display()
def get_ticket_category(self, obj):
- return obj.get_ticket_category_display()
\ No newline at end of file
+ return obj.get_ticket_category_display()
+
+
+class ContactUsSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = ContactUsModel
+ fields = ['full_name', 'email', 'phone', 'type', 'message']
\ No newline at end of file
diff --git a/backend/ticket/urls.py b/backend/ticket/urls.py
index c3a47f5..50eed91 100644
--- a/backend/ticket/urls.py
+++ b/backend/ticket/urls.py
@@ -6,6 +6,7 @@ urlpatterns = [
path('', views.TicketListView.as_view(), name='ticket-list'),
path('', views.TicketDetailView.as_view(), name='ticket-detail'),
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/delete/', views.AttachmentDeleteView.as_view(), name='attachment-upload'),
]
\ No newline at end of file
diff --git a/backend/ticket/views.py b/backend/ticket/views.py
index ac6fdae..e669864 100644
--- a/backend/ticket/views.py
+++ b/backend/ticket/views.py
@@ -2,7 +2,7 @@ from rest_framework import generics, permissions
from rest_framework.response import Response
from rest_framework.views import APIView
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 drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes
from rest_framework.permissions import IsAuthenticated
@@ -56,7 +56,6 @@ class TicketCreateView(APIView):
permission_classes = [permissions.IsAuthenticated]
def post(self, 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():
new_ticket_ser.save(customer=request.user)
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)
ticket.status = new_status
ticket.save()
- return Response({"message": "Ticket status updated successfully"})
\ No newline at end of file
+ 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)
\ No newline at end of file
diff --git a/backend/utils/admin.py b/backend/utils/admin.py
index 1107fab..82c4d05 100644
--- a/backend/utils/admin.py
+++ b/backend/utils/admin.py
@@ -1,6 +1,6 @@
from order.models import OrderModel
from product.models import DollorModel, CommentModel
-from ticket.models import Ticket
+from ticket.models import Ticket, ContactUsModel
from home.models import LearnVideoModel
from account.models import SecurityBreachAttemptModel
@@ -24,6 +24,9 @@ def new_learn_video_count(request):
def new_attck_count(request):
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 unfold.admin import ModelAdmin
from home.models import LearnVideoModel
diff --git a/frontend/components/cart/index/CartItem.vue b/frontend/components/cart/index/CartItem.vue
index 17ba187..9fee44b 100644
--- a/frontend/components/cart/index/CartItem.vue
+++ b/frontend/components/cart/index/CartItem.vue
@@ -150,9 +150,12 @@ watch(
-
+
{{ data.product.title }}
-
+
-
+
-
- مجله در ستون و سطرآنچ
-
+ مجله در ستون و سطرآنچ
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. چاپگرها و
متون بلکه روزنامه و مجله در ستون و سطرآنچنان که
@@ -30,13 +28,19 @@ const {} = toRefs(props);