signal of address and some model udapte send address filed of an order

This commit is contained in:
Parsa Nazer
2025-04-18 17:29:04 +03:30
parent aa12540eb7
commit d50dc2309d
9 changed files with 177 additions and 64 deletions
+15 -13
View File
@@ -1,7 +1,7 @@
from django.contrib import admin, messages from django.contrib import admin, messages
from .models import * from .models import *
from unfold.admin import TabularInline, StackedInline from unfold.admin import TabularInline, StackedInline
from django.db.models import Q
from import_export.admin import ImportExportModelAdmin from import_export.admin import ImportExportModelAdmin
from unfold.contrib.import_export.forms import ExportForm, ImportForm, SelectableFieldsExportForm from unfold.contrib.import_export.forms import ExportForm, ImportForm, SelectableFieldsExportForm
from unfold.contrib.forms.widgets import ArrayWidget, WysiwygWidget from unfold.contrib.forms.widgets import ArrayWidget, WysiwygWidget
@@ -47,11 +47,11 @@ class BankRecordInline(StackedInline):
class OrderAdmin(ModelAdmin, ImportExportModelAdmin): class OrderAdmin(ModelAdmin, ImportExportModelAdmin):
import_form_class = ImportForm import_form_class = ImportForm
export_form_class = ExportForm export_form_class = ExportForm
search_fields = ['order_id', 'user__phone', 'user__first_name', 'user__last_name', 'user__email'] search_fields = ['user__phone', 'user__first_name', 'user__last_name', 'user__email']
list_filter = ['is_paid', 'status'] list_filter = ['is_paid', 'status']
actions_list = ['redirect_to_learn', 'udpate_bank_status'] actions_list = ['redirect_to_learn', 'udpate_bank_status']
list_display = ['order_id', 'user', 'is_paid', 'status', 'discount_code', 'address',] list_display = ['order_id', 'user', 'is_paid', 'status', 'discount_code', 'address',]
readonly_fields = ('created_at', 'order_id', 'tax', 'final_price', 'cart_total', 'discount', 'discount_code', 'user', 'address', 'is_paid') readonly_fields = ('created_at', 'tax', 'final_price', 'cart_total', 'discount_amount', 'discount_code', 'user', 'address', 'is_paid')
compressed_fields = True compressed_fields = True
warn_unsaved_form = True warn_unsaved_form = True
# exclude = ('bank_records',) # exclude = ('bank_records',)
@@ -61,19 +61,21 @@ class OrderAdmin(ModelAdmin, ImportExportModelAdmin):
} }
} }
inlines = [OrderItemModelInline, BankRecordInline] inlines = [OrderItemModelInline, BankRecordInline]
# def bank_links(self, obj): def order_id(self, obj):
# banks = obj.bank_records.all() return f"سفارش {obj.pk + 1000}"
order_id.short_description = "شماره سفارش"
# if not banks.exists():
# return "-"
# return format_html_join(
# "",
# '<a style="padding-bottom:10px;display:block;" href="/secret-admin/azbankgateways/bank/{}/change/" class="text-primary-600 dark:text-primary-500">{}</a>',
# [(bank.id, bank.tracking_code) for bank in banks]
# ) or "-"
# bank_links.short_description = "Bank Records" def get_search_results(self, request, queryset, search_term):
queryset, use_distinct = super().get_search_results(request, queryset, search_term)
if search_term.isdigit():
order_id_search = int(search_term) - 1000
queryset |= self.model.objects.filter(Q(pk=order_id_search))
return queryset, use_distinct
@action(description='اپدیت وضعیت رکورد های بانکی') @action(description='اپدیت وضعیت رکورد های بانکی')
def udpate_bank_status(self, request): def udpate_bank_status(self, request):
@@ -0,0 +1,23 @@
# Generated by Django 5.1.2 on 2025-03-29 13:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('order', '0025_alter_ordermodel_order_id'),
]
operations = [
migrations.AlterField(
model_name='orderitemmodel',
name='discount',
field=models.SmallIntegerField(verbose_name='درصد تخفیف'),
),
migrations.AlterField(
model_name='orderitemmodel',
name='price',
field=models.PositiveIntegerField(verbose_name='قیمت'),
),
]
@@ -0,0 +1,32 @@
# Generated by Django 5.1.2 on 2025-03-30 15:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('order', '0026_alter_orderitemmodel_discount_and_more'),
]
operations = [
migrations.RenameField(
model_name='orderitemmodel',
old_name='discount',
new_name='discount_percent',
),
migrations.RenameField(
model_name='ordermodel',
old_name='discount',
new_name='discount_amount',
),
migrations.RemoveField(
model_name='ordermodel',
name='order_id',
),
migrations.AlterField(
model_name='ordermodel',
name='status',
field=models.CharField(choices=[('CART', 'در سبد خرید'), ('ADMIN_PENDING', 'در انتظار تایید'), ('PENDING', 'درحال پردازش'), ('POSTED', 'ارسال شده'), ('RECEIVED', 'تحویل شده'), ('CANCELED', 'لغو شده'), ('REFUNDED', 'مرجوع شده')], default='CART', max_length=20, verbose_name='وضعیت سفارش'),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2025-03-30 15:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('order', '0027_rename_discount_orderitemmodel_discount_percent_and_more'),
]
operations = [
migrations.AlterField(
model_name='orderitemmodel',
name='price',
field=models.BigIntegerField(verbose_name='قیمت'),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2025-03-30 16:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('order', '0028_alter_orderitemmodel_price'),
]
operations = [
migrations.AlterField(
model_name='ordermodel',
name='discount_amount',
field=models.BigIntegerField(blank=True, null=True, verbose_name='مقدار کد تخفیف'),
),
]
+52 -42
View File
@@ -1,9 +1,10 @@
from django.db import models from django.db import models, transaction
from account.models import User, UserAddressModel, PushSubscription from account.models import User, UserAddressModel, PushSubscription
from product.models import ProductModel, ProductVariant, ProductImageModel from product.models import ProductModel, ProductVariant, ProductImageModel
from django.utils import timezone from django.utils import timezone
from django_jalali.db import models as jmodels from django_jalali.db import models as jmodels
from django.core.exceptions import ValidationError
from django.conf import settings
class DiscountCode(models.Model): class DiscountCode(models.Model):
code = models.CharField(max_length=50, verbose_name='کد تخفیف') code = models.CharField(max_length=50, verbose_name='کد تخفیف')
@@ -44,20 +45,19 @@ class OrderModel(models.Model):
('CANCELED', 'لغو شده'), ('CANCELED', 'لغو شده'),
('REFUNDED', 'مرجوع شده'), ('REFUNDED', 'مرجوع شده'),
] ]
order_id = models.PositiveIntegerField(unique=True, null=True, blank=True, verbose_name='شماره سفارش')
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='orders', verbose_name='کاربر') user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='orders', verbose_name='کاربر')
address = models.ForeignKey(UserAddressModel, on_delete=models.SET_NULL, related_name='orders', null=True, verbose_name='ادرس') address = models.ForeignKey(UserAddressModel, on_delete=models.SET_NULL, related_name='orders', null=True, verbose_name='ادرس')
created_at = jmodels.jDateField(blank=True, null=True, verbose_name="تاریخ ثبت سفارش") created_at = jmodels.jDateField(blank=True, null=True, verbose_name="تاریخ ثبت سفارش")
is_paid = models.BooleanField(default=False, verbose_name="وضعیت پرداخت") is_paid = models.BooleanField(default=False, verbose_name="وضعیت پرداخت")
discount_code = models.ForeignKey(DiscountCode, on_delete=models.PROTECT, null=True, blank=True, verbose_name="کدتخفیف") discount_code = models.ForeignKey(DiscountCode, on_delete=models.PROTECT, null=True, blank=True, verbose_name="کدتخفیف")
status = models.CharField(max_length=20, choices=STATUS_CHOICES, verbose_name="وضعیت سفارش") status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='CART', verbose_name="وضعیت سفارش")
discount = models.BigIntegerField(null=True, blank=True, verbose_name='کل تخقیف') discount_amount = models.BigIntegerField(null=True, blank=True, verbose_name='مقدار کد تخفیف')
tax = models.BigIntegerField(null=True, blank=True, verbose_name='مالیات') tax = models.BigIntegerField(null=True, blank=True, verbose_name='مالیات')
final_price = models.BigIntegerField(null=True, blank=True, verbose_name='قیمت نهایی') final_price = models.BigIntegerField(null=True, blank=True, verbose_name='قیمت نهایی')
cart_total = models.BigIntegerField(null=True, blank=True, verbose_name='کل سبد خرید') cart_total = models.BigIntegerField(null=True, blank=True, verbose_name='کل سبد خرید')
def __str__(self): def __str__(self):
return f'سفارش: {self.id + 1000}' return f'سفارش: {self.pk + 1000}'
class Meta: class Meta:
verbose_name = 'سفارش' verbose_name = 'سفارش'
@@ -65,58 +65,68 @@ class OrderModel(models.Model):
def save(self, *args, **kwargs): def _cal_discount_amount(self, cart_total):
# genrate order id discount_percent = self.discount_code.percent if self.discount_code else 0
if not self.pk: return int(cart_total * discount_percent / 100)
last_instance = self.__class__.objects.order_by("pk").last()
self.order_id = (last_instance.pk + 1001) if last_instance else 1001
super().save(*args, **kwargs) def _cal_tax(self, cart_total, discount_amount):
tax_rate = getattr(settings, 'DEFAULT_TAX_RATE', 20)
return int((cart_total - discount_amount) * tax_rate / 100)
def cal_discount(self):
# total_with_item_discount = sum(item.total_with_discount() for item in self.items.all())
# discount_percent = self.discount_code.percent
# return total_with_item_discount * ((100 - discount_percent) / 100)
pass
def cal_tax(self):
return self.total_without_tax() * 0.2
def cal_total(self):
pass
return self.total_with_discount() + self.tax()
def cal_final_price(self):
pass
def _cal_cart_total(self):
from django.db.models import Sum, F, FloatField
return self.items.aggregate(
total=Sum(F('price') * (1 - F('discount_percent')/100) * F('quantity'),
output_field=FloatField()
)).get('total') or 0
def _cal_final_price(self, cart_total, discount_amount, tax):
return cart_total - discount_amount + tax
def update_order(self):
if self.status == 'CART':
cart_total = self._cal_cart_total()
discount_amount = self._cal_discount_amount(cart_total)
self.discount_amount = discount_amount
self.cart_total = cart_total
tax = self._cal_tax(cart_total, discount_amount)
self.tax = tax
self.final_price = self._cal_final_price(cart_total, discount_amount, tax)
self.save()
else:
pass
class OrderItemModel(models.Model): class OrderItemModel(models.Model):
order = models.ForeignKey(OrderModel, on_delete=models.CASCADE, related_name='items', verbose_name='سفارش') order = models.ForeignKey(OrderModel, on_delete=models.CASCADE, related_name='items', verbose_name='سفارش')
quantity = models.PositiveSmallIntegerField(verbose_name="تعداد") quantity = models.PositiveSmallIntegerField(verbose_name="تعداد")
price = models.PositiveIntegerField(verbose_name='قیمت', default=0) price = models.BigIntegerField(verbose_name='قیمت')
product = models.ForeignKey(ProductVariant, on_delete=models.PROTECT, verbose_name="محصول") product = models.ForeignKey(ProductVariant, on_delete=models.PROTECT, verbose_name="محصول")
discount = models.SmallIntegerField(default=0, verbose_name='تخفیف') discount_percent = models.SmallIntegerField(verbose_name='درصد تخفیف')
class Meta: class Meta:
verbose_name = 'ایتم سبد خرید' verbose_name = 'ایتم سبد خرید'
verbose_name_plural = 'ایتم های سبد خرید' verbose_name_plural = 'ایتم های سبد خرید'
# def total(self):
# return self.quantity * self.product.price
# def total_with_discount(self): def set_or_update_fields(self):
# return self.quantity * self.product.get_toman_price_after_discount() self.price = self.product.price
self.discount_percent = self.product.discount
def update_fields(self):
pass
def __str__(self): def __str__(self):
return f'({self.product}) - ({self.order.user})' return f'({self.product}) - ({self.order.user})'
def save(self, *args, **kwargs):
self.clean()
self.set_or_update_fields()
super().save(*args, **kwargs)
self.order.update_order()
def delete(self, *args, **kwargs):
self.clean()
order = self.order
super().delete(*args, **kwargs)
order.update_order()
def clean(self):
if self.pk and self.order.status != "CART":
raise ValidationError("ایتم ها فقط در حالت سبد خرید قابل ادیت هستند")
+7 -3
View File
@@ -53,7 +53,7 @@ class OrderItemSerailzier(serializers.ModelSerializer):
class Meta: class Meta:
model = OrderItemModel model = OrderItemModel
exclude = ('order',) exclude = ('order',)
read_only_fields = ('order', 'product',) read_only_fields = ('order', 'product', 'discount_percent')
def get_product(self, obj): def get_product(self, obj):
return ProductVariantSerialzier(instance=obj.product, context={'request': self.context.get('request')}).data return ProductVariantSerialzier(instance=obj.product, context={'request': self.context.get('request')}).data
@@ -79,9 +79,10 @@ class CartSerializer(serializers.ModelSerializer):
tax = serializers.SerializerMethodField() tax = serializers.SerializerMethodField()
final_price = serializers.SerializerMethodField() final_price = serializers.SerializerMethodField()
discount_code = serializers.SerializerMethodField() discount_code = serializers.SerializerMethodField()
address = UserAddressSerializer()
class Meta: class Meta:
model = OrderModel model = OrderModel
fields = [ 'discount_code', 'items', 'cart_total', 'tax', 'final_price'] fields = [ 'discount_code', 'items', 'cart_total', 'tax', 'final_price', 'address']
def get_discount_code(self, obj): def get_discount_code(self, obj):
@@ -109,6 +110,7 @@ class OrderListSerializer(serializers.ModelSerializer):
count = serializers.SerializerMethodField() count = serializers.SerializerMethodField()
images = serializers.SerializerMethodField() images = serializers.SerializerMethodField()
verbose_status = serializers.SerializerMethodField() verbose_status = serializers.SerializerMethodField()
order_id = serializers.SerializerMethodField()
class Meta: class Meta:
model = OrderModel model = OrderModel
fields = ['created_at', 'status', "images", "count", "id", 'final_price', 'order_id', 'verbose_status'] fields = ['created_at', 'status', "images", "count", "id", 'final_price', 'order_id', 'verbose_status']
@@ -119,6 +121,8 @@ class OrderListSerializer(serializers.ModelSerializer):
def get_count(self, obj): def get_count(self, obj):
return obj.items.all().count() return obj.items.all().count()
def get_order_id(self, obj):
return obj.pk + 1000
def get_images(self, obj): def get_images(self, obj):
image_list = [ image_list = [
self.context.get('request').build_absolute_uri(image.image.url) self.context.get('request').build_absolute_uri(image.image.url)
@@ -138,7 +142,7 @@ class OrderGetSerializer(serializers.ModelSerializer):
discount_code = DiscountCodeSerializer() discount_code = DiscountCodeSerializer()
class Meta: class Meta:
model = OrderModel model = OrderModel
fields = ['created_at', 'status', "images", "count", "id", 'final_price', 'order_id', 'verbose_status', 'address', 'items', 'tax' , 'cart_total', 'discount_code', 'discount'] fields = ['created_at', 'status', "images", "count", "id", 'final_price', 'order_id', 'verbose_status', 'address', 'items', 'tax' , 'cart_total', 'discount_code', 'discount_amount']
def get_verbose_status(self, obj): def get_verbose_status(self, obj):
return obj.get_status_display() return obj.get_status_display()
+11 -5
View File
@@ -1,7 +1,7 @@
from django.db.models.signals import pre_save from django.db.models.signals import pre_save
from django.dispatch import receiver from django.dispatch import receiver
from .models import OrderModel from .models import OrderModel
from account.models import PushSubscription from account.models import PushSubscription, UserAddressModel
import ghasedak_sms import ghasedak_sms
from .tasks import send_change_status_notif, send_change_status_sms from .tasks import send_change_status_notif, send_change_status_sms
@@ -13,8 +13,8 @@ def order_status_changed(sender, instance, **kwargs):
if previous.status != instance.status: if previous.status != instance.status:
new_status = instance.get_status_display() new_status = instance.get_status_display()
send_change_status_notif.delay(instance.pk, new_status) # send_change_status_notif.delay(instance.pk, new_status)
send_change_status_sms.delay(instance.pk, new_status) # send_change_status_sms.delay(instance.pk, new_status)
if previous.status == 'CART' and instance.status == 'ADMIN_PENDING': if previous.status == 'CART' and instance.status == 'ADMIN_PENDING':
# update_cart_price_fields() # update_cart_price_fields()
@@ -23,11 +23,17 @@ def order_status_changed(sender, instance, **kwargs):
pass pass
@receiver(pre_save, sender=OrderModel)
def set_default_address(sender, instance, **kwargs):
if instance.address is None and instance.user:
default_address = UserAddressModel.objects.filter(user=instance.user, is_main=True).first()
if default_address:
instance.address = default_address
def update_cart_price_fields(order): # def update_cart_price_fields(order):
pass # pass
def update_sell_data(order): def update_sell_data(order):
pass pass
+1 -1
View File
@@ -297,7 +297,7 @@ class BankCallbackSerializer(serializers.ModelSerializer):
PaymentStatus.REDIRECT_TO_BANK, PaymentStatus.REDIRECT_TO_BANK,
PaymentStatus.RETURN_FROM_BANK, PaymentStatus.RETURN_FROM_BANK,
}: }:
return "waiting" return "pending"
elif obj.status in { elif obj.status in {
PaymentStatus.CANCEL_BY_USER, PaymentStatus.CANCEL_BY_USER,
PaymentStatus.EXPIRE_GATEWAY_TOKEN, PaymentStatus.EXPIRE_GATEWAY_TOKEN,