2025-03-18 08:46:50 +08:00

1758 lines
76 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from rest_framework.views import APIView
from mysystem.models import Users,Role,Dept
from apps.oauth.models import OAuthWXUser
from apps.mall.models import GoodsCategory,GoodsCoupon,CouponRecord,SPU,SKU,SKUImage,SKUSpecification,SPUSpecification,SPUSpecificationOption,OrderInfo,OrderGoods,CouponRecord,GoodsCoupon,OrderRefunds
from apps.address.models import Address
from utils.jsonResponse import SuccessResponse,ErrorResponse
from rest_framework import serializers
from utils.common import formatdatetime,float2dot,REGEX_MOBILE,get_parameter_dic,renameuploadimg,getminrandomodernum,geturlpath,ismoney,ast_convert,getrandomodernum,format_wechat_gmt_8_to_normal
import re
from django.db.models import Q,F,Sum, Avg, Max, Min, Count
from rest_framework.serializers import ModelSerializer
from rest_framework_simplejwt.authentication import JWTAuthentication
from utils.serializers import CustomModelSerializer
from utils.viewset import CustomModelViewSet
from rest_framework.permissions import IsAuthenticated
from utils.filters import FinanceOrderInfoTimeFilter
from django.db import transaction
from django_redis import get_redis_connection
import datetime
import os
import json
from django.contrib.auth.hashers import make_password
from decimal import Decimal
from utils.export_excel import export_excel
from utils.pagination import CustomPagination
from utils.wexinpay_cashout import Pay,get_client_ip,random_str
from utils.weixinpay import WxAppPay
from utils.alipay import initalipay,alipay_trade_app,alipay_trade_refund
from config import DOMAIN_HOST
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from django.http import HttpResponse
import logging
logger = logging.getLogger(__name__)
# Create your views here.
# ================================================= #
# ************** 后端商城管理 view ************** #
# ================================================= #
class GoodsCategoryManageSerializer(CustomModelSerializer):
"""
商品分类管理-序列化器
"""
class Meta:
model = GoodsCategory
read_only_fields = ["id"]
fields = '__all__'
class GoodsCategoryManageViewSet(CustomModelViewSet):
"""
后台商品分类管理 接口
"""
queryset = GoodsCategory.objects.all().order_by("sort")
serializer_class = GoodsCategoryManageSerializer
search_fields = ('name',)
class GoodsCouponManageSerializer(CustomModelSerializer):
"""
商品分类管理-序列化器
"""
coupon_type_name = serializers.SerializerMethodField(read_only=True)
menkan = serializers.SerializerMethodField(read_only=True)
sendto = serializers.SerializerMethodField(read_only=True)
def get_sendto(self,obj):
if obj.receive_type in [1,2,4]:
return "全部用户"
def get_menkan(self,obj):
if obj.is_condition:#有门槛
return float2dot(obj.use_min_price)+""
else:
return "无门槛"
def get_coupon_type_name(self,obj):
return obj.get_coupon_type_display()
class Meta:
model = GoodsCoupon
read_only_fields = ["id"]
fields = '__all__'
class GoodsCouponManageViewSet(CustomModelViewSet):
"""
后台优惠券管理 接口
"""
queryset = GoodsCoupon.objects.all().order_by("-create_datetime")
serializer_class = GoodsCouponManageSerializer
search_fields = ('name',)
class CouponRecordManageSerializer(CustomModelSerializer):
"""
用户优惠券持有记录-序列化器
"""
status_name = serializers.SerializerMethodField(read_only=True)
coupon_name = serializers.SerializerMethodField(read_only=True)
coupon_type_name = serializers.SerializerMethodField(read_only=True)
used_time = serializers.SerializerMethodField(read_only=True)
def get_used_time(self,obj):
return formatdatetime(obj.used_time)
def get_coupon_name(self,obj):
return obj.coupon.name
def get_coupon_type_name(self,obj):
return obj.coupon.get_coupon_type_display()
def get_status_name(self,obj):
return obj.get_status_display()
class Meta:
model = CouponRecord
read_only_fields = ["id"]
fields = '__all__'
class CouponRecordManageViewSet(CustomModelViewSet):
"""
用户优惠券持有记录 接口
"""
queryset = CouponRecord.objects.filter(is_delete=False).order_by("-create_datetime")
serializer_class = CouponRecordManageSerializer
# search_fields = ('name',)
filterset_fields = ('user_id','status')
class SPUSpecificationOptionSerializer(CustomModelSerializer):
"""
商品SPU规格选项值 简单序列化器
"""
class Meta:
model = SPUSpecificationOption
read_only_fields = ["id"]
# fields = '__all__'
exclude = ['dept_belong_id', 'modifier', 'creator']
extra_kwargs = {
'spec': {'required': False},
}
class SPUSpecModelSerializer(CustomModelSerializer):
"""
商品SPU规格名 简单序列化器
"""
# spu = serializers.StringRelatedField()
# spu_id = serializers.CharField()
options = SPUSpecificationOptionSerializer(many=True,required=False)
class Meta:
model = SPUSpecification
read_only_fields = ["id"]
# fields = '__all__'
exclude = ['dept_belong_id', 'modifier', 'creator']
extra_kwargs = {
'spu': {'required': False},
}
class SKUSpecificationSerialzier(CustomModelSerializer):
"""
SKU规格表 简单序列化器
"""
# spec_id = serializers.CharField()
# option_id = serializers.CharField()
# spec = serializers.StringRelatedField()
# option = serializers.StringRelatedField()
spec = serializers.CharField()
option = serializers.CharField()
class Meta:
model = SKUSpecification # SKUSpecification中sku外键关联了SKU表
# fields = "__all__"
# fields = ('id', 'spec', 'option')
exclude = ['dept_belong_id', 'modifier', 'creator']
extra_kwargs = {
'sku': {'required': False},
'spec': {'required': False},
'option': {'required': False},
}
class GoodsSPUImageSerialzier(CustomModelSerializer):
"""
SPU商品图片 简单序列化器
"""
class Meta:
model = SKUImage
fields = ('image',)
class GoodsSKUSerializer(CustomModelSerializer):
"""
SKU 简单序列化器
"""
specs = SKUSpecificationSerialzier(many=True,required=False)
class Meta:
model = SKU
# fields = "__all__"
exclude = ['dept_belong_id', 'modifier', 'creator']
extra_kwargs = {
'spu': {'required': False},
}
class GoodsSPUSerializer(CustomModelSerializer):
"""
商品管理 SPU 简单序列化器
"""
spu_specs = SPUSpecModelSerializer(many=True,required=False)
skus = GoodsSKUSerializer(many=True,required=False)
category1_name = serializers.SerializerMethodField(read_only=True)
stock = serializers.SerializerMethodField(read_only=True)#默认多规格选择为所有sku库存的合计
def get_stock(self,obj):
if obj.spec_type:#多规格
allskus = obj.skus.all().aggregate(sumstock=Sum('stock'))
return allskus['sumstock']
else:#单规格
return obj.skus.first().stock
def get_category1_name(self,obj):
if obj.category1:
return obj.category1.name
def to_representation(self, instance): # 序列化
ret = super().to_representation(instance)
ret['image_list'] = ast_convert(ret['image_list']) # 可以保存的修改字段值的方法
return ret
def to_internal_value(self, data):
# 进提取所需要的数据对其进行反序列化data代表未验证的数据
data['image_list'] = str(data['image_list'])
return super().to_internal_value(data)
def create(self, validated_data):
"""
重写create方法来手动插入中间表(SKUSpecification)数据记录新增sku拥有的规格和选项信息和轮播图
:param validated_data:有效数据
:return:新建的SPU对象
"""
dept_belong_id = getattr(self.request.user, 'dept_id', None)
# specs记录的是中间表数据无法用于新建SKU所以先从有效数据中移除
# specs = [{spec_id:1, option_id:2}, {...}...]
# 获取规格信息,并从validated_data数据中,删除规格信息数据
spec_type = validated_data['spec_type']
spu_sepcs = validated_data.pop('spu_specs',[]) # 商品的spu规格字典数组形式[ { "name": "颜色", "options": [ { "value": "白色" }, { "value": "黑色" } ] }, { "name": "大小", "options": [ { "value": "X" }, { "value": "S" } ] } ]
skus = validated_data.pop('skus',[])
validated_data[self.dept_belong_id_field_name] = dept_belong_id
with transaction.atomic(): # 开启事务
try:
savepoint = transaction.savepoint()
spu = SPU.objects.create(**validated_data)
if spec_type:#多规格
if len(spu_sepcs) <=0 or len(skus) <=0:
# 回滚到保存点
transaction.savepoint_rollback(savepoint)
raise serializers.ValidationError("spu_sepcs或skus不能为空",400)
return
for spu_sepc in spu_sepcs:
new_spu_spec = SPUSpecification.objects.create(creator=self.request.user,dept_belong_id=dept_belong_id,spu_id=spu.id,name=spu_sepc['name'])
for spu_sepc_option in spu_sepc['options']:
new_spu_spec_option = SPUSpecificationOption.objects.create(creator=self.request.user,dept_belong_id=dept_belong_id,spec_id=new_spu_spec.id,value=spu_sepc_option['value'])
for sk in skus:
if 'default_image' not in sk.keys():
sk['default_image']=""
sku_instance = SKU.objects.create(creator=self.request.user,dept_belong_id=dept_belong_id,spu_id=spu.id,price=sk['price'],stock=sk['stock'],default_image=sk['default_image'])
for sp in sk['specs']:
spuspec_instance = SPUSpecification.objects.filter(spu_id=spu.id,name=sp['spec']).first()
spuspec_option_instance = SPUSpecificationOption.objects.filter(spec_id=spuspec_instance.id, value=sp['option']).first()
SKUSpecification.objects.create(creator=self.request.user,dept_belong_id=dept_belong_id,sku_id=sku_instance.id,spec_id=spuspec_instance.id,option_id=spuspec_option_instance.id)
# 获取价格最低的sku保存到spu的price中
minpricesku = SKU.objects.filter(spu=spu).order_by('price').first()
spu.price = minpricesku.price
spu.save()
else:#单规格
if len(skus)<=0:
# 回滚到保存点
transaction.savepoint_rollback(savepoint)
raise serializers.ValidationError("skus不能为空", 400)
return
SKU.objects.create(creator=self.request.user,dept_belong_id=dept_belong_id,spu=spu,price=skus[0]['price'],stock=skus[0]['stock'],default_image=skus[0]['default_image'])
except Exception as e:
# 回滚到保存点
transaction.savepoint_rollback(savepoint)
return e
# 清除保存点
transaction.savepoint_commit(savepoint)
#返回spu
return spu
class Meta:
model = SPU
#fields = "__all__"
exclude = ['dept_belong_id','modifier','creator','unit','brand','comments','desc_pack','desc_service']
read_only_fields = ["id"]
extra_kwargs = {
'spu_specs': {'required': False},
}
class SKUSpecificationSPUUpdateSerialzier(CustomModelSerializer):
"""
SKU规格表 简单序列化器
"""
spec = serializers.CharField()
option = serializers.CharField()
id = serializers.CharField()
def validate_id(self, value):
return value
class Meta:
model = SKUSpecification # SKUSpecification中sku外键关联了SKU表
# fields = "__all__"
# fields = ('id', 'spec', 'option')
exclude = ['dept_belong_id', 'modifier', 'creator']
extra_kwargs = {
'sku': {'required': False},
'spec': {'required': False},
'option': {'required': False},
}
class GoodsSKUSPUUpdateSerializer(CustomModelSerializer):
"""
SKU 简单序列化器
"""
id = serializers.CharField()
specs = SKUSpecificationSPUUpdateSerialzier(many=True,required=False)
def validate_id(self, value):
return value
class Meta:
model = SKU
# fields = "__all__"
exclude = ['dept_belong_id', 'modifier', 'creator']
extra_kwargs = {
'spu': {'required': False},
}
class GoodsSPUUpdateSerializer(CustomModelSerializer):
# skus = serializers.ListField(required=True)
skus = GoodsSKUSPUUpdateSerializer(many=True, required=True)
# spu_specs = serializers.ListField(required=False)
def to_internal_value(self, data):
# 进提取所需要的数据对其进行反序列化data代表未验证的数据
data['image_list'] = str(data['image_list'])
return super().to_internal_value(data)
def update(self, instance, validated_data):
if self.modifier_field_id in self.fields.fields:
validated_data[self.modifier_field_id] = self.get_request_user_id()
# 获取规格信息,并从validated_data数据中,删除规格信息数据
spec_type = validated_data['spec_type']
if instance.spec_type != spec_type:
raise serializers.ValidationError("商品规格类型不能更改", 400)
return
skus = validated_data.pop('skus',[])
# spu_sepcs = validated_data.pop('spu_specs')
if len(skus) <= 0:
raise serializers.ValidationError("skus不能为空", 400)
return
with transaction.atomic(): # 开启事务
try:
savepoint = transaction.savepoint()
# 方案一: 调用父类 ,去实现没有问题的数据更新
instance = super().update(instance, validated_data)
if spec_type: # 多规格(只能修改现有规格库存和价格,不能添加和减少规格)前端直接修改,不通过本接口修改
pass
else:#单规格
SKU.objects.filter(spu_id=instance.id,id=skus[0]['id']).update(price=skus[0]['price'],stock=skus[0]['stock'],default_image=skus[0]['default_image'],modifier=self.get_request_user_id())
except Exception as e:
# 回滚到保存点
transaction.savepoint_rollback(savepoint)
return e
# 清除保存点
transaction.savepoint_commit(savepoint)
return instance
class Meta:
model = SPU
#fields = "__all__"
exclude = ['dept_belong_id','modifier','creator']
read_only_fields = ["id"]
class GoodsSPUViewSet(CustomModelViewSet):
queryset = SPU.objects.all().order_by("sort")
serializer_class = GoodsSPUSerializer
create_serializer_class = GoodsSPUSerializer
update_serializer_class =GoodsSPUUpdateSerializer
search_fields = ('name',)
filterset_fields = ('is_launched','category1')
export_download_filename = "导出商品数据"
export_field_dict = {
"default_image": "导出图片演示",
"name": "商品名称",
"category1_name": "所属分类",
"price": "售价",
"stock": "库存",
"is_tuijian": "是否推荐",
"sort": "排序",
"is_launched":"状态",
"image_list":"导出列表字符串并换行",
"spu_specs":"原样导出",
"spu_specs.name":"导出列表字典某个属性并换行",
"create_datetime": "创建时间",
}
def islaunched(self, request, *args, **kwargs):
"""上下架商品"""
queryset = self.filter_queryset(self.get_queryset())
pk = kwargs.get('pk')
if ',' in kwargs.get('pk'): # 批量
ids = pk.split(',')
instance = queryset.filter(id__in=ids)
if instance:
instance.update(is_launched=True)
return SuccessResponse(data=None, msg="修改成功")
else:
return ErrorResponse(msg="未获取到该商品")
else:
instance = SPU.objects.filter(id=kwargs.get('pk')).first()
if instance:
if instance.is_launched:
instance.is_launched = False
else:
instance.is_launched = True
instance.save()
return SuccessResponse(data=None, msg="修改成功")
else:
return ErrorResponse(msg="未获取到该商品")
def editsku_price_stock(self, request, *args, **kwargs):
"""多规格情况下修改商品的库存和价格和默认图片"""
pk = kwargs.get('pk')
instance = SKU.objects.filter(id=pk).first()
if instance:
price = get_parameter_dic(request)['price']
stock = get_parameter_dic(request)['stock']
default_image = get_parameter_dic(request)['default_image']
instance.price = price
instance.stock = stock
instance.default_image = default_image
instance.save()
#修改spu的最低price价格
skus = SKU.objects.filter(spu=instance.spu).order_by('price').first()
SPU.objects.filter(id=instance.spu.id).update(price=skus.price)
return SuccessResponse(data=None, msg="修改成功")
else:
return ErrorResponse(msg="未获取到该商品")
class FinanceOrderInfoSerializer(CustomModelSerializer):
"""
商品类财务流水-序列化器
"""
userinfo = serializers.SerializerMethodField()
pay_method = serializers.SerializerMethodField()
gname = serializers.SerializerMethodField()
def get_gname(self,obj):
allgoods = obj.ordergoodsskus.all().first()
return allgoods.sku.spu.name
def get_pay_method(self,obj):
return obj.get_pay_method_display()
def get_userinfo(self, obj):
return {
'id': obj.user.id,
'nickname': obj.user.nickname,
'avatar': obj.user.avatar
}
class Meta:
model = OrderInfo
read_only_fields = ["id"]
fields = '__all__'
class FinanceOrderInfoViewSet(CustomModelViewSet):
"""
后台会商品类财务流水 接口
"""
queryset = OrderInfo.objects.filter(pay_status=1).order_by("-create_datetime")
serializer_class = FinanceOrderInfoSerializer
# search_fields = ('name',)
filterset_class = FinanceOrderInfoTimeFilter
def orderstatistics(self,request):
"""
订单金额 收益总金额统计
:param request:
:return:
"""
#按照原来的过滤查询
queryset = self.filter_queryset(self.get_queryset())
totalmoney = 0
if queryset:
totalmoney = queryset.aggregate(price = Sum('total_amount'))['price']
else:
totalmoney = 0
data = {
"totalmoney": float2dot(totalmoney),
}
return SuccessResponse(data=data,msg='success')
class OrderInfoSerializer(CustomModelSerializer):
address = serializers.SerializerMethodField(read_only=True)
userinfo = serializers.SerializerMethodField(read_only=True)
goodsinfo = serializers.SerializerMethodField(read_only=True)
pay_time = serializers.SerializerMethodField(read_only=True)
pay_method_name = serializers.SerializerMethodField(read_only=True)
def get_pay_time(self,obj):
return formatdatetime(obj.pay_time)
def get_pay_method_name(self,obj):
return obj.get_pay_method_display()
def get_goodsinfo(self,obj):
data=[]
goods = OrderGoods.objects.filter(order_id=obj.id)
if not goods:
return None
for good in goods:
datasku={}
if good.sku.spu.spec_type == 1:#多规格
sku_spec = ""
temp_sepcs = good.sku.specs.all()
for temp_sepc in temp_sepcs:
if len(temp_sepcs) > 1:
sku_spec = sku_spec + temp_sepc.option.value + ';'
else:
sku_spec = temp_sepc.option.value
else:
sku_spec = good.sku.spu.name
datasku['sku_name'] = good.sku.name
datasku['sku_spec'] = sku_spec
datasku['sku_default_image'] = good.sku.default_image
datasku['count'] = good.count
datasku['price'] = good.price
datasku['comment'] = good.comment
datasku['score'] = good.score
data.append(datasku)
return data
def get_address(self,obj):
data={}
data['areas'] = obj.address_place
data['receiver'] = obj.address_name
data['mobile'] = obj.address_mobile
return data
def get_userinfo(self, obj):
return {
'id': obj.user.id,
'nickname': obj.user.nickname,
'name':obj.user.name,
'avatar': obj.user.avatar,
'mobile':obj.user.mobile,
}
class Meta:
model = OrderInfo
fields = "__all__"
read_only_fields = ["id"]
class GoodsOrderManageViewSet(CustomModelViewSet):
"""
商品订单管理:
list:查询
create:新增
update:修改
retrieve:单例
destroy:删除
"""
queryset = OrderInfo.objects.all().order_by('-create_datetime')
serializer_class = OrderInfoSerializer
filterset_class = FinanceOrderInfoTimeFilter
def closeorder(self,request):
# 按照原来的过滤查询
queryset = self.filter_queryset(self.get_queryset())
# 接收的参数
id = get_parameter_dic(request)['id'] # 商品订单id
if not id:
return ErrorResponse(msg="提交的参数不能为空")
order = queryset.filter(id=id, status=2).first()
if not order:
return ErrorResponse(msg='不存在此商品订单信息,或该状态订单不支持关闭')
with transaction.atomic():
order.status = 6
order.save()
# 订单退款逻辑
orderrefund()
return SuccessResponse(msg='订单已关闭')
def orderstatistics(self,request):
"""
订单金额 订单量统计
:param request:
:return:
"""
#按照原来的过滤查询
queryset = self.filter_queryset(self.get_queryset())
totalmoney = 0
totalcount = 0
if queryset:
totalmoney = queryset.aggregate(price = Sum('total_amount'))['price']
totalcount = queryset.count()
data = {
"totalmoney": float2dot(totalmoney),
"totalcount": totalcount,
}
return SuccessResponse(data=data,msg='success')
def sendoutgoods(self,request):
# 按照原来的过滤查询
queryset = self.filter_queryset(self.get_queryset())
#接收的参数
id = get_parameter_dic(request)['id'] # 商品订单id
orderNo = get_parameter_dic(request)['orderNo'] # 商品物流单号
logistics_company = get_parameter_dic(request)['logistics_company'] # 商品物流公司
if not all([id,orderNo,logistics_company]):
return ErrorResponse(msg="提交的参数不能为空")
order = queryset.filter(id=id,status=2).first()
if not order:
return ErrorResponse(msg='不存在此商品订单信息,或该状态订单不支持发货')
order.logistics_id = orderNo
order.logistics_company = logistics_company
order.send_time = datetime.datetime.now()
order.status = 3
order.save()
return SuccessResponse(msg='修改成功')
def orderrefund(orderid,type=1):
"""
orderid为要退款的订单id
type1商城订单退款 2服务订单退款
"""
if type == 1:#商城订单
order = OrderInfo.objects.get(id=orderid)
paymethod = order.pay_method
transaction_id = order.trade_id
money = order.total_amount
reason = "订单退款"
orderno = getrandomodernum()
refundsorder = OrderRefunds.objects.create(order_no=orderno,order=order,amount=money,reason=reason)
else:#其他订单
pass
# 支付类型判断
if paymethod == 2:#微信
wxapppay = WxAppPay()
data = wxapppay.refundsorder(orderno, transaction_id,reason, money*100,money*100,notify_url="%s/api/app/wechatpay_notify_refund/" % DOMAIN_HOST)
elif paymethod == 3:#支付宝
data = alipay_trade_refund(transaction_id, float(money), notify_url="%s/api/app/ali_notify_refund/" % DOMAIN_HOST)
#判断是否成功
# ================================================= #
# ************** 前端商城接口 view ************** #
# ================================================= #
class GoodsTypeView(APIView):
"""
get:
获取商城分类
【参数】type = all 所有 、 tabs 标签 、 icons 图标
"""
permission_classes = [IsAuthenticated]
authentication_classes = [JWTAuthentication]
def get(self,request):
type = get_parameter_dic(request)['type']
if type == "all":
queryset = GoodsCategory.objects.filter(is_delete=False).order_by('sort')
elif type == "tabs":
queryset = GoodsCategory.objects.filter(default_image__isnull=True,is_delete=False).order_by('sort')
elif type == "icons":
queryset = GoodsCategory.objects.filter(default_image__isnull=False,is_delete=False).order_by('sort')
else:
return ErrorResponse(msg='type类型错误')
data = []
if queryset:
for q in queryset:
data.append({
'id': q.id,
'name': q.name,
'default_image': q.default_image,
})
return SuccessResponse(data=data,msg="success")
class GoodsListView(APIView):
"""
get:
获取商城商品列表
【参数】
search搜索非必须
is_tuijian: 0非推荐、1推荐、2所有 必须默认为2获取说有
order_by:排序 sort 商品默认顺序 、price 正序 、-price 倒叙、sales 正序、-sales倒叙 默认为sort(非必须)
category:商品分类id非必须默认为所有分类
"""
permission_classes = [IsAuthenticated]
authentication_classes = [JWTAuthentication]
def get(self,request):
search = get_parameter_dic(request).get('search') # 搜索
is_tuijian = int(get_parameter_dic(request)['is_tuijian']) # 是否获取推荐商品
order_by = get_parameter_dic(request).get('order_by') # 排序
category = get_parameter_dic(request).get('category') # 分类
if is_tuijian not in [0,1,2]:
return ErrorResponse("is_tuijian error")
if order_by and order_by not in ['sort','price','-price','-sales','sales']:
return ErrorResponse("order_by error")
else:
order_by = "sort"
if search:
if is_tuijian == 2:
if category:
queryset = SPU.objects.filter(name__contains=search,category1_id=category,is_launched=True).order_by(order_by)
else:
queryset = SPU.objects.filter(name__contains=search, is_launched=True).order_by(order_by)
else:
queryset = SPU.objects.filter(name__contains=search,is_tuijian=is_tuijian,is_launched=True).order_by(order_by)
else:
if is_tuijian == 2:
if category:
queryset = SPU.objects.filter(category1_id=category,is_launched=True).order_by(order_by)
else:
queryset = SPU.objects.filter(is_launched=True).order_by(order_by)
else:
queryset = SPU.objects.filter(is_tuijian=is_tuijian,is_launched=True).order_by(order_by)
# # 1. 实例化分页器对象
page_obj = CustomPagination()
# # 2. 使用自己配置的分页器调用分页方法进行分页
page_data = page_obj.paginate_queryset(queryset, request)
data = []
if queryset:
for m in page_data:
data.append({
'spu_id':m.id,
'price':m.price,
'default_image':m.default_image,
'name':m.name,
'sales':m.sales,
'spec_type':m.spec_type
})
return page_obj.get_paginated_response(data=data)
class GoodsDetailView(APIView):
"""
get:
获取商城商品列表
【参数】
spu_id商品id
"""
permission_classes = [IsAuthenticated]
authentication_classes = [JWTAuthentication]
def get(self,request):
spu_id = get_parameter_dic(request).get('spu_id') # 商品id
if not spu_id:
return ErrorResponse(msg="参数spu_id不能为空")
spu = SPU.objects.filter(id=spu_id,is_launched=True).first()
if not spu:
return ErrorResponse(msg='该商品不存在或已下架')
skus = SKU.objects.filter(spu=spu).order_by("create_datetime")
sum_stock = skus.aggregate(sumstock=Sum('stock'))
stock=sum_stock['sumstock']
myspecs = []#规格
if spu.spec_type == 1:#多规格
spusepcs = SPUSpecification.objects.filter(spu=spu).order_by('create_datetime')
for spusepc in spusepcs:
spuspecvalues = spusepc.options.all()
spu_options = {'values': []}
spu_options['id'] = spusepc.id
spu_options['spec_key'] = spusepc.name
for temps in spuspecvalues:
spu_options['values'].append({
'id':temps.id,
'spec_value':temps.value
})
myspecs.append(spu_options)
skus_list = []#skus
for temp_sku in skus:
# temp_sku:每一个sku商品对象
sku_spec_options = SKUSpecification.objects.filter(sku=temp_sku).order_by('spec_id')
sku_options = {'spec': []}
sku_options['sku_id'] = temp_sku.id
sku_options['stock'] = temp_sku.stock
sku_options['price'] = temp_sku.price
sku_options['default_image'] = temp_sku.default_image
if spu.spec_type == 1:#多规格
for temp in sku_spec_options:
sku_options['spec'].append({'spec_key': temp.spec.name, 'spec_value': temp.option.value, 'spec_key_id': temp.spec_id,'spec_value_id': temp.option_id})
skus_list.append(sku_options)
data = {
'spu_id': spu.id,
'price': spu.price,
'default_image': spu.default_image,
'goods_imagelist': SKUImage.objects.filter(spu=spu).values_list('image', flat=True).order_by('sort'),
'name': spu.name,
'sales': spu.sales,
'spec_type': spu.spec_type,
'desc_detail':spu.desc_detail,
'stock':stock,
'skus':skus_list,
'specs': myspecs
}
return SuccessResponse(data=data, msg='success')
class CartsView(APIView):
"""
get:
前端用户获取购物车列表
post
更新购物车信息
【参数】type为需要操作购物车的功能类型del表示删除、edit表示编辑add表示新增
"""
permission_classes = [IsAuthenticated]
authentication_classes = [JWTAuthentication]
def get(self, request):
user = request.user
client = get_redis_connection('carts')
sku_id_count = client.hgetall('carts_%s' % user.id)
sku_id_selected = client.smembers('carts_selected_%s' % user.id)
data_dict = {}
for sku_id,count in sku_id_count.items():
data_dict[sku_id] = {
'count': int(count),
'selected': sku_id in sku_id_selected
}
sku_keys = data_dict.keys()
skus = SKU.objects.filter(id__in=sku_keys)
cart_skus = []
for sku in skus:
spec_name = ""
if sku.spu.spec_type == 1:#多规格
spec_names = sku.specs.all()
for temp_spec_name in spec_names:
if len(spec_names)>1:
spec_name =spec_name + temp_spec_name.option.value+';'
else:
spec_name = temp_spec_name.option.value
cart_skus.append({
'spu_id': sku.spu.id,
'sku_id': sku.id,
'name': sku.spu.name,
'price': float2dot(sku.price),
'count': data_dict[sku.id]['count'],
'stock':sku.stock,
'spec_name':spec_name,
'default_image': sku.spu.default_image,
'selected': str(data_dict[sku.id]['selected']),
})
return SuccessResponse(data=cart_skus,msg="success")
def post(self, request):
sku_id = get_parameter_dic(request)['sku_id']#商品id
type = get_parameter_dic(request)['type']#购物车操作类型
# 验证商品是否存在
sku = SKU.objects.filter(id=sku_id).first()
if not sku:
return ErrorResponse(msg='商品不存在')
user = request.user
if type=="add":#添加购物车,在redis中保存用户的购物车记录
count = int(get_parameter_dic(request)['count']) # 购物车该商品的数量
selected = int(get_parameter_dic(request)['selected']) # 购物车操作类型 1勾选 0 不勾选
if selected not in [0, 1]:
return ErrorResponse(msg="selected error")
if sku_id is None or count is None:
return ErrorResponse(msg='sku_id/count不能为空')
if not isinstance(count,int):
return ErrorResponse(msg='count类型错误')
selected = True
if int(count) > sku.stock:
return ErrorResponse(msg='库存不足')
# 登陆过则存入redis
client = get_redis_connection('carts')
# 保存用户购物车添加的商品id和对应数量count,如果redis购物车已添加该商品数量需要进行累加
client.hincrby('carts_%s' % user.id, sku_id, count)
if selected:
client.sadd('carts_selected_%s' % user.id, sku_id)
return SuccessResponse(msg='success')
elif type == "edit":#购物车记录更新
count = int(get_parameter_dic(request)['count']) # 购物车该商品的数量
selected = int(get_parameter_dic(request)['selected']) # 是否选中
if selected not in [0, 1]:
return ErrorResponse(msg="selected error")
if sku_id is None or count is None:
return ErrorResponse(msg='sku_id/count不能为空')
if not isinstance(count,int):
return ErrorResponse(msg='count类型错误')
if int(count) > sku.stock:
return ErrorResponse(msg='库存不足')
# 登陆过对redis进行操作
client = get_redis_connection('carts')
client.hset('carts_%s' % user.id, sku_id, count)
if selected:# 勾选将sku_id加入的set中
client.sadd('carts_selected_%s' % user.id, sku_id)
else:# 不勾选将sku_id从set中移除
client.srem('carts_selected_%s' % user.id, sku_id)
cart_sku = {
'spu_id': sku.spu.id,
'sku_id': sku.id,
'name': sku.spu.name,
'price': float2dot(sku.price),
'count': count,
'default_image': sku.spu.default_image,
'amount': sku.price * count,
'selected': str(selected)
}
return SuccessResponse(data=cart_sku,msg='success')
elif type == "del":#购物车记录删除
if sku_id is None:
return ErrorResponse(msg='sku_id参数不能为空')
client = get_redis_connection('carts')
# (1)、删除商品和数量
client.hdel('carts_%s' % user.id, sku_id)
# (2)、删除选中状态
client.srem('carts_selected_%s' % user.id, sku_id)
return SuccessResponse(msg='success')
else:
return ErrorResponse(msg='type类型错误')
class CartsSelectAllView(APIView):
"""
post
购物车记录全选和取消全选
【参数】selectedtrue、false选中/不选中购物车中的所有商品,(全选)
"""
permission_classes = [IsAuthenticated]
authentication_classes = [JWTAuthentication]
def post(self, request):
selected = int(get_parameter_dic(request)['selected'])
if selected is None:
return ErrorResponse(msg='selected参数不能为空')
if selected not in [0,1]:
return ErrorResponse(msg='selected类型错误')
user = request.user
client = get_redis_connection('carts')
# 获取redis中用户所有商品
sku_id_count = client.hgetall('carts_%s' % user.id)
# 购物车中所有的商品的id值
sku_ids = sku_id_count.keys()
if selected:
client.sadd('carts_selected_%s' % user.id, *sku_ids)
else:
client.srem('carts_selected_%s' % user.id, *sku_ids)
return SuccessResponse(msg='success')
class MyCouponView(APIView):
"""
get
我的优惠券列表
【参数】type ((0, '通用'),(1, '商城类'), (2, '服务类'), (10, '通用、商城'), (20, '通用、服务'))
【参数】status ((0, '未领取'),(1, '未使用'), (2, '已使用'), (3, '已过期'), (4, '已撤回'))
post:
立即领取优惠券
【参数】:
coupon_id 优惠券ID
type:del删除、get领取
"""
permission_classes = [IsAuthenticated]
authentication_classes = [JWTAuthentication]
def get(self, request):
user = request.user
if user.identity != 1:
return ErrorResponse(msg="用户类型错误")
status = int(get_parameter_dic(request)['status'])
if status not in [0, 1, 2, 3, 4, 10]:
return ErrorResponse(msg="status error")
type = get_parameter_dic(request).get('type')
if type:
type = int(type)
if type not in [0, 1, 2, 10, 20]:
return ErrorResponse(msg="type error")
# 判断优惠券中是否有手动领取的劵,有的话则需要创建到用户优惠券表,并设置状态为未领取(手动领取的劵针对的是全部用户)
coupon_res = GoodsCoupon.objects.filter(receive_type=1, is_delete=False, status=True).order_by(
'create_datetime')
if coupon_res:
for c in coupon_res:
obj, created = CouponRecord.objects.get_or_create(user=user, coupon=c)
# 检查自己的未使用的优惠券是否过期
queryset_isexpire = CouponRecord.objects.filter(user=user, status=1)
nowtime = datetime.datetime.now()
if queryset_isexpire:
for e in queryset_isexpire:
receive_time = e.receive_time
expiretime = receive_time + datetime.timedelta(days=e.coupon.coupon_expiretime) # 领取后的过期时间
if expiretime < nowtime:
e.status = 3
e.save()
if type:
if type == 10:
if status == 10:
queryset = CouponRecord.objects.filter(user=user, status__in=[0, 1], coupon__coupon_type__in=[0, 1],is_delete=False).order_by('-create_datetime')
else:
queryset = CouponRecord.objects.filter(user=user, status=status, coupon__coupon_type__in=[0, 1],is_delete=False).order_by('-create_datetime')
elif type == 20:
if status == 10:
queryset = CouponRecord.objects.filter(user=user, status__in=[0, 1], coupon__coupon_type__in=[0, 2],is_delete=False).order_by('-create_datetime')
else:
queryset = CouponRecord.objects.filter(user=user, status=status, coupon__coupon_type__in=[0, 2],is_delete=False).order_by('-create_datetime')
else:
if status == 10:
queryset = CouponRecord.objects.filter(user=user, status__in=[0, 1], coupon__coupon_type=type,is_delete=False).order_by('-create_datetime')
else:
queryset = CouponRecord.objects.filter(user=user, status=status, coupon__coupon_type=type,is_delete=False).order_by('-create_datetime')
else:
if status == 10:
queryset = CouponRecord.objects.filter(user=user, status__in=[0, 1], is_delete=False).order_by('-create_datetime')
else:
queryset = CouponRecord.objects.filter(user=user, status=status, is_delete=False).order_by('-create_datetime')
data = []
if queryset:
for m in queryset:
desc = '无门槛'
if m.coupon.is_condition: # 有门槛
desc = "" + str(int(m.coupon.use_min_price)) + "" + str(int(m.coupon.price))
data.append({
'id': m.id,
'name': m.coupon.name,
'price': m.coupon.price,
'coupon_expiretime': m.coupon.coupon_expiretime,
'create_datetime': formatdatetime(m.create_datetime),
'receive_time': formatdatetime(m.receive_time),
'is_condition': m.coupon.is_condition,
'condition_price': m.coupon.use_min_price,
'condition': desc,
'coupon_type': m.coupon.get_coupon_type_display(),
'type': m.coupon.coupon_type,
'status': m.status,
'status_name': m.get_status_display()
})
return SuccessResponse(data=data, msg="success")
def post(self, request):
user = request.user
if user.identity != 1:
return ErrorResponse(msg="用户类型错误")
coupon_id = get_parameter_dic(request)['coupon_id']
type = get_parameter_dic(request)['type']
if type not in ['del','get']:
return ErrorResponse(msg='type error')
if not coupon_id:
return ErrorResponse(msg='coupon_id error')
if type == 'get':
res = CouponRecord.objects.filter(id=coupon_id, is_delete=False, user=user, status=0).first()
if not res:
return ErrorResponse(msg='无此优惠券')
res.status = 1
res.receive_time = datetime.datetime.now()
res.save()
else:
res = CouponRecord.objects.filter(id=coupon_id, is_delete=False, user=user, status__in=[2,3]).first()
if not res:
return ErrorResponse(msg='无此优惠券或该状态不支持删除')
res.is_delete = 1
res.save()
return SuccessResponse(msg='success')
class OrdersCommitView(APIView):
"""
post:
购物车订单提交
【参数】:
address_id用户的收货地址id
remark:订单备注
type1 购物车订单提交 2 商品直接购买订单提交
coupon_id:使用的优惠券id非必须
count:购买数量type=2时该参数为必须
sku_id:要购买的商品sku_idtype=2时该参数为必须
"""
permission_classes = [IsAuthenticated]
authentication_classes = [JWTAuthentication]
def post(self, request):
address_id = get_parameter_dic(request)['address_id']
remark = get_parameter_dic(request)['remark']
type = int(get_parameter_dic(request)['type'])
coupon_id = get_parameter_dic(request).get('coupon_id')
addresses = Address.objects.filter(id=address_id,is_deleted=False).first()
if not addresses:
return ErrorResponse(msg='地址不存在')
if not remark:
remark = ""
user = request.user
# 生成订单编号
order_id = getrandomodernum()
#运费
freight = Decimal(0)
#优惠券判断
coupon = ""
if coupon_id:
coupon = CouponRecord.objects.filter(id=coupon_id,status=1,coupon__coupon_type__in=[0,1],user=user,is_delete=False).first()
if not coupon:
return ErrorResponse("coupon_id error")
if type == 1:#购物车订单提交
# 开启事物
with transaction.atomic():
try:
# 设置保存点
save_point = transaction.savepoint()
# 生成订单的基本信息
order = OrderInfo.objects.create(
order_id=order_id,
user=user,
remark=remark,
address=addresses,
total_count=0,
total_amount=Decimal(0),
freight=Decimal(0),#运费
status=1,
pay_status=0,
)
# 从redis获取选中状态的商品
client = get_redis_connection('carts')
sku_id_count = client.hgetall('carts_%s' % user.id)
sku_count = {}
for sku_id, count in sku_id_count.items():
sku_count[sku_id] = int(count)
# 获取选中的sku_id
sku_ids = client.smembers('carts_selected_%s' % user.id)
if not sku_ids:
# 回滚到保存点
transaction.savepoint_rollback(save_point)
return ErrorResponse(msg="购物车没有选中的商品")
# 遍历商品订单商品表
for sku_id in sku_ids:
# 每遍历出一个sku_id就需要往OrderGoods表中插入一条数据
for i in range(5):
# (1)乐观锁step1获取旧库存和销量
sku = SKU.objects.get(id=sku_id)
stock_old = sku.stock
# 选中数量
count = sku_count[sku.id]
sales_old = sku.sales
if count > stock_old:
# 回滚到保存点
transaction.savepoint_rollback(save_point)
return ErrorResponse(msg='库存不足')
# (2)、乐观锁step2根据旧数据计算新库存和销量 —— 耗时操作
stock_new = sku.stock - count
sales_new = sku.sales + count
# 乐观锁step3: 基于旧库存和销量过滤查找模型类对象,然后更新
# 查询集批量修改函数update有整数返回值 —— 表示被修改了的对象有几个再次按照旧的库存获取商品,获取到更新,获取不到返回再次操作(乐观锁)
res = SKU.objects.filter(id=sku_id, stock=stock_old,sales=sales_old).update(stock=stock_new, sales=sales_new)
if res == 0:
# (3.2)、说明没有数据被成功修改继而说明filter过滤出空查询集进一步说明"根据旧库存找不到原有的sku有别的事务介入"
continue
else:# (3.1)、res返回值不为0说明没有别的事务介入成功修改了
break
if res ==0:#重试了6此结果都没有操作成功
# 回滚到保存点
transaction.savepoint_rollback(save_point)
return ErrorResponse(msg='服务器繁忙,请稍后再试')
# 修改spu表中的总销量
sku.spu.sales += count
sku.spu.save()
# 统计订单中的商品总数和订单总价格
order.total_count += count
order.total_amount += sku.price * count
# 新建订单商品数据关联sku和订单
OrderGoods.objects.create(
order=order,
sku=sku,
count=count,
price=sku.price
)
#order.total_amount += order.freight
# 优惠券判断
if coupon: # 如果用了优惠券
if couponisexpire(coupon):
# 回滚
transaction.savepoint_rollback(save_point)
return ErrorResponse(msg='优惠券已过期')
if coupon.coupon.is_condition: # 有门槛
if float(sku.price) * count < float(coupon.coupon.use_min_price):
# 回滚
transaction.savepoint_rollback(save_point)
return ErrorResponse(msg='优惠券不符合使用规则')
coupon_price = coupon.coupon.price
order.total_amount_pay = order.total_amount
order.total_amount = order.total_amount - coupon_price
order.couponrecord_id = coupon_id
order.couponrecord_price = coupon_price
coupon.status = 2
coupon.used_time = datetime.datetime.now()
coupon.save()
else:
order.total_amount_pay = order.total_amount
order.save()
except:
# 回滚到保存点
transaction.savepoint_rollback(save_point)
return ErrorResponse(msg='请稍后再试')
else:
# 事物结束提交数据
transaction.savepoint_commit(save_point)
# 删除购物车选中状态的商品
client.hdel('carts_%s' % user.id, *sku_ids)
client.srem('carts_selected_%s' % user.id, *sku_ids)
return SuccessResponse(data={'id': order.id,'order_id': order.order_id,'total_amount':order.total_amount},msg='success')
else:#直接购买
sku_id = get_parameter_dic(request)['sku_id']
count = int(get_parameter_dic(request)['count'])
if not sku_id:
return ErrorResponse(msg='要购买的商品不能为空')
if count <= 0:
return ErrorResponse(msg='要购买商品数量错误')
sku = SKU.objects.filter(id=sku_id, spu__is_launched=True).first()
if not sku:
return ErrorResponse(msg='不存在该商品,或该商品暂不支持购买')
if sku.stock < count:
return ErrorResponse(msg='该商品库存不足')
# 开启事物
with transaction.atomic():
# 设置保存点
save_point = transaction.savepoint()
try:
# 生成订单的基本信息
order = OrderInfo.objects.create(
order_id=order_id,
user=user,
address=addresses,
total_count=0,
total_amount=Decimal(0),
freight=Decimal(0), # 运费
remark=remark,
status=1,
pay_status=0,
)
# 获取当前商品的库存
stock_old = sku.stock
# 修改sku商品库存和销量
stock_new = sku.stock - count
sales_new = sku.sales + count
# 再次按照旧的库存获取商品,获取到更新,获取不到返回再次操作(乐观锁)
for i in range(3):
res = SKU.objects.filter(id=sku_id, stock=stock_old).update(stock=stock_new, sales=sales_new)
if res == 0:
if i == 2: # 如果连续3次都是更新失败则回滚
# 回滚
transaction.savepoint_rollback(save_point)
return ErrorResponse(msg='请稍后再试')
continue
else:
break
# 修改spu表中的总销量
sku.spu.sales += count
sku.spu.save()
# 优惠券判断
if coupon: # 如果用了优惠券
if couponisexpire(coupon):
# 回滚
transaction.savepoint_rollback(save_point)
return ErrorResponse(msg='优惠券已过期')
if coupon.coupon.is_condition: # 有门槛
if float(sku.price) * count < float(coupon.coupon.use_min_price):
# 回滚
transaction.savepoint_rollback(save_point)
return ErrorResponse(msg='优惠券不符合使用规则')
coupon_price = coupon.coupon.price
order.total_count = count
order.total_amount_pay = sku.price * count
order.total_amount = sku.price * count - coupon_price
order.couponrecord_id = coupon_id
order.couponrecord_price = coupon_price
coupon.status = 2
coupon.used_time = datetime.datetime.now()
coupon.save()
else:#没有优惠券
order.total_count = count
order.total_amount = sku.price * count
order.total_amount_pay = sku.price * count
# 保存订单商品数据
OrderGoods.objects.create(
order=order,
sku=sku,
count=count,
price=sku.price
)
order.save()
except:
# 回滚到保存点
transaction.savepoint_rollback(save_point)
return ErrorResponse(msg='请稍后再试')
else:
# 事物结束提交数据
transaction.savepoint_commit(save_point)
return SuccessResponse(data={'id':order.id,'order_id':order.order_id,'total_amount':order.total_amount}, msg='success')
def couponisexpire(coupon):
"""
coupon:CouponRecord的实例
return:True 过期False未过期
"""
receive_time = coupon.receive_time
expire_days = coupon.coupon.coupon_expiretime
expire_time = receive_time + datetime.timedelta(days=expire_days)
now = datetime.datetime.now()
if now > expire_time:
return True
return False
class GoodsOrderCancleView(APIView):
"""
get:
商城订单取消
【参数】id订单的id
"""
permission_classes = [IsAuthenticated]
authentication_classes = [JWTAuthentication]
def get(self,request):
user = request.user
id = get_parameter_dic(request)['id']
if not id:
return ErrorResponse(msg='id参数错误')
order = OrderInfo.objects.filter(id=id,user=user,status__in=[1,2]).first()
if not order:
return ErrorResponse(msg='该订单不存在,或该状态不支持取消')
with transaction.atomic():
savepoint = transaction.savepoint()
#把取消的库存增加回去
skus = order.ordergoodsskus.all()
for s in skus:
# 每遍历出一个sku_id就还原一次库存和销量
for i in range(5):
# (1)乐观锁step1获取旧库存和销量
sku = SKU.objects.get(id=s.sku.id)
stock_old = sku.stock
count = s.count # 购买数量
sales_old = sku.sales # sku原销量
sales_spu_old = sku.spu.sales # spu原销量
# (2)、乐观锁step2根据旧数据计算新库存和销量 —— 耗时操作
stock_new = stock_old + count
sales_new = sales_old - count
sales_spu_new = sales_spu_old - count
# 乐观锁step3: 基于旧库存和销量过滤查找模型类对象,然后更新
# 查询集批量修改函数update有整数返回值 —— 表示被修改了的对象有几个再次按照旧的库存获取商品,获取到更新,获取不到返回再次操作(乐观锁)
res = SKU.objects.filter(id=s.sku.id, stock=stock_old, sales=sales_old).update(stock=stock_new,sales=sales_new)
if res == 0:
# (3.2)、说明没有数据被成功修改继而说明filter过滤出空查询集进一步说明"根据旧库存找不到原有的sku有别的事务介入"
continue
else: # (3.1)、res返回值不为0说明没有别的事务介入成功修改了
for n in range(5):
res2 = SPU.objects.filter(id=s.sku.spu.id,sales=sales_spu_old).update(sales=sales_spu_old)
if res2 ==0:
continue
else:
break
break
if res == 0 or res2==0: # 重试了6此结果都没有操作成功
# 回滚到保存点
transaction.savepoint_rollback(savepoint)
return ErrorResponse(msg='服务器繁忙,请稍后再试')
# 退款逻辑
if order.status == 2:
orderrefund(order.id)
order.status = 6
order.save()
# 清除保存点
transaction.savepoint_commit(savepoint)
return SuccessResponse(msg="订单取消成功")
class GoodsOrderConfirmReceiveView(APIView):
"""
get:
商城订单确认收货
【参数】id订单的id
"""
permission_classes = [IsAuthenticated]
authentication_classes = [JWTAuthentication]
#@method_decorator(ly_api_security)
def get(self,request):
user=request.user
id = get_parameter_dic(request)['id']
if not id:
return ErrorResponse(msg='id参数错误')
order = OrderInfo.objects.filter(id=id,status=3,user=user).first()
if not order:
return ErrorResponse(msg='该订单不存在,或该状态不支持确认收货操作')
order.status = 5
order.save()
return SuccessResponse(msg="订单已完成")
class GoodsOrdersListView(APIView):
"""
get:
前端用户获取商品订单列表
【参数】
status:获取订单的过滤状态 为空则默认返回全部、ORDER_STATUS_CHOICES = (
(1, "待支付"),
(2, "待发货"),
(3, "待收货"),
(4, "待评价"),
(5, "已完成"),
(6, "已取消"),
)
search:搜索订单号(非必须)
"""
permission_classes = [IsAuthenticated]
authentication_classes = [JWTAuthentication]
def get(self,request):
user = request.user
status = get_parameter_dic(request).get('status')
search = get_parameter_dic(request).get('search')
if search:
if status:
status = int(status)
queryset = OrderInfo.objects.filter(order_id__contains=search,user=user,is_delete=False,status=status).order_by('-create_datetime')
else:
queryset = OrderInfo.objects.filter(order_id__contains=search,user=user, is_delete=False).order_by('-create_datetime')
else:
if status:
status = int(status)
queryset = OrderInfo.objects.filter(user=user,is_delete=False,status=status).order_by('-create_datetime')
else:
queryset = OrderInfo.objects.filter(user=user, is_delete=False).order_by('-create_datetime')
# # 1. 实例化分页器对象
page_obj = CustomPagination()
# # 2. 使用自己配置的分页器调用分页方法进行分页
page_data = page_obj.paginate_queryset(queryset, request)
data = []
if queryset:
for m in page_data:
goods_list = []
querysets = m.ordergoodsskus.all()
if querysets:
for g in querysets:
spec_name = ""
if g.sku.spu.spec_type == 1: # 多规格
spec_names = g.sku.specs.all()
for temp_spec_name in spec_names:
if len(spec_names) > 1:
spec_name = spec_name + temp_spec_name.option.value + ';'
else:
spec_name = temp_spec_name.option.value
goods_list.append({
'id': g.sku_id,
'price': g.price,
'name': g.sku.spu.name,
'count': g.count,
'spec_name':spec_name,
'default_image': g.sku.spu.default_image
})
data.append({
'id': m.id,
'order_id': m.order_id,
'total_count':m.total_count,
'total_amount': m.total_amount,
'status': m.status,
'status_name':m.get_status_display(),
'couponrecord_price':m.couponrecord_price,
'create_datetime':formatdatetime(m.create_datetime),
'goods_list':goods_list,
})
return page_obj.get_paginated_response(data=data)
class UserAddressSerializer(serializers.ModelSerializer):
"""
用户收货地址序列化
"""
class Meta:
model = Address
read_only_fields = ["id"]
# fields = '__all__'
exclude = ['is_deleted','user']
class GoodsOrdersDetailView(APIView):
"""
get:
前端用户获取商品订单详情
【参数】
id:订单id
"""
permission_classes = [IsAuthenticated]
authentication_classes = [JWTAuthentication]
def get(self,request):
user = request.user
id = get_parameter_dic(request)['id']
if not id:
return ErrorResponse(msg='id参数错误')
m = OrderInfo.objects.filter(id=id,user=user, is_delete=False).first()
data = {}
if m:
addresses = Address.objects.filter(id=m.address_id).first()
address = UserAddressSerializer(addresses)
goods_list = []
querysets = m.ordergoodsskus.all()
if querysets:
for g in querysets:
spec_name = ""
if g.sku.spu.spec_type == 1: # 多规格
spec_names = g.sku.specs.all()
for temp_spec_name in spec_names:
if len(spec_names) > 1:
spec_name = spec_name + temp_spec_name.option.value + ';'
else:
spec_name = temp_spec_name.option.value
goods_list.append({
'id': g.sku_id,
'price': g.price,
'name': g.sku.spu.name,
'count': g.count,
'spec_name':spec_name,
'default_image': g.sku.spu.default_image
})
data={
'id': m.id,
'order_id': m.order_id,
'addresses': address.data,
'total_count':m.total_count,
'total_amount': m.total_amount,
'total_amount_pay':m.total_amount_pay,
'status': m.status,
'status_name':m.get_status_display(),
'couponrecord_price':m.couponrecord_price,
'create_datetime':formatdatetime(m.create_datetime),
'pay_method':m.get_pay_method_display(),
'pay_status':m.get_pay_status_display(),
'pay_time':m.pay_time,
'goods_list':goods_list,
'logistics_id': m.logistics_id, # 物流单号
'remark': m.remark,
}
return SuccessResponse(data=data, msg='success')
class PaymentView(APIView):
"""
平台支付接口
post:
平台支付接口
【参数】
paymethod: 2 微信支付 3 支付宝支付
platform:支付者平台: app 、xcx
"""
permission_classes = [IsAuthenticated]
authentication_classes = [JWTAuthentication]
def post(self, request):
user = request.user
if user.identity !=1:
return ErrorResponse(msg="用户类型错误")
paymethod = int(get_parameter_dic(request)['pay_method'])
platform = get_parameter_dic(request).get('platform')
paytype = get_parameter_dic(request)['pay_type']
if platform and platform not in ['app','xcx']:
return ErrorResponse(msg="platform类型错误")
if not platform:
platform = 'app'
if paymethod not in [2,3]:
return ErrorResponse(msg="paymethod类型错误")
money = 0
#业务逻辑
with transaction.atomic():
# 设置保存点
save_point = transaction.savepoint()
try:
orderno = getminrandomodernum()
#其他业务逻辑
id = get_parameter_dic(request)['id']
if not id:
return ErrorResponse(msg='id error1')
order = OrderInfo.objects.filter(id=id,user=user,pay_status=0).first()
if not order:
return ErrorResponse(msg='id error2')
money = order.total_amount
orderno = order.order_id
order.pay_method = paymethod
order.save()
#支付类型判断
if paymethod == 2:
wxapppay = WxAppPay()
if platform == 'app':#app支付
data = wxapppay.payorder(orderno, money * 100, orderno,paytype,notify_url="%s/api/app/wechatpay_notify/" % DOMAIN_HOST) # 要填写回调地址
else:
openid = OAuthWXUser.objects.filter(user=user).values_list('xcx_openid', flat=True).first()
if not openid:
# 回滚到保存点
transaction.savepoint_rollback(save_point)
data = wxapppay.payorder_jsapi(orderno, money * 100, orderno,paytype,openid,notify_url="%s/api/app/wechatpay_notify/" % DOMAIN_HOST) # 要填写回调地址
# 事物结束提交数据
transaction.savepoint_commit(save_point)
return SuccessResponse(data={'id': order.id, 'order_no': order.order_id, 'paymethod': 2, 'paystatus': order.pay_status,'wechataydata': data}, msg="success")
elif paymethod == 3:
data = alipay_trade_app(orderno, float(money),"testpay",notify_url="%s/api/app/alipay_notify/" % DOMAIN_HOST)
# 事物结束提交数据
transaction.savepoint_commit(save_point)
return SuccessResponse(data={'order_no':order.order_id,'alipaydata':data}, msg="success")
return ErrorResponse(msg="paytype类型错误")
except Exception as e:
transaction.savepoint_rollback(save_point)
return ErrorResponse(msg=str(e))
class wechatpay_notify(APIView):
"""
支付成功后,微信服务器异步通知回调(用于修改订单状态)
"""
def post(self,request):
wxpay = WxAppPay()
result = wxpay.decrypt_callback(request)
logger.info("收到微信支付回调通知:%s" % (result))
if result:
"""
{"mchid":"1617830805","appid":"wx023a7d62457e3803","out_trade_no":"20220112163528260904706","transaction_id":"4200001335202201122963662545","trade_type":"JSAPI","trade_state":"SUCCESS","trade_state_desc":"支付成功","bank_type":"CMBC_DEBIT","attach":"","success_time":"2022-01-12T16:35:42+08:00","payer":{"openid":"ocBnz4pg2G05LaUFpNE9bxR2B88w"},"amount":{"total":1,"payer_total":1,"currency":"CNY","payer_currency":"CNY"}}
"""
resp = json.loads(result)
appid = resp.get('appid')
mchid = resp.get('mchid')
out_trade_no = resp.get('out_trade_no')
transaction_id = resp.get('transaction_id')
trade_type = resp.get('trade_type')
trade_state = resp.get('trade_state')
trade_state_desc = resp.get('trade_state_desc')
bank_type = resp.get('bank_type')
attach = resp.get('attach')#自定义数据
success_time = format_wechat_gmt_8_to_normal(resp.get('success_time'))
payer = resp.get('payer')
amount = (resp.get('amount').get('total'))/100
# TODO: 根据返回参数进行必要的业务处理处理完后返回200或204
if trade_state == "SUCCESS": # 支付成功
# 查询数据库中存在的订单
order = OrderInfo.objects.filter(order_id=out_trade_no, pay_status=0, pay_method=2).first()
if not order:
logger.info("处理收到微信支付回调通知:%s,没有找到该订单" % (result))
return HttpResponse('fail')
order.trade_id = transaction_id
order.pay_status = 1
order.pay_time = success_time
order.save()
# 其他逻辑处理
orderpaysuccess(order.id)
return HttpResponse('success')
else:
return HttpResponse('fail')
else:
return HttpResponse('fail')
class alipay_notify(APIView):
"""
支付成功后,支付宝服务器异步通知回调(用于修改订单状态)
返回fail之后支付宝会再回调一次也就是失败的回调一共回调2次
"""
def post(self,request):
# 1. 先将sign剔除掉
processed_dict = {}
for key, value in request.POST.items():
processed_dict[key] = value
# 异步通知data参数转换字典后示例如下
"""
{'gmt_create': '2021-11-22 08:53:05', 'charset': 'utf-8', 'seller_email': 'taijizhilu@126.com', 'subject': '20211122085257269506402001', 'sign': 'dAUOnG1u8/Fap+aDCSa+P2AXFv4vqr3BK4vKTxevai4F3sdN4X6an8GulmKjk3cx1Z9OMp05JAcBCPi+1CXJoy6opybYqr+M/uDUiAYH+MA4ilazSskS/WC22iZhS4oAVjwouGp+Wbu6pmNMM/gFNCxnlf3K+bCa/gzDDPTCTEoZT3IoeQdZ4ERmuNi7WdCCIm8jNaS8nRXS8bEkk7r7PvYs1kO3H9uZhViSKmlx+Qfklm+mRa1xheNd2UJ1pYcVGK4snlUJL4tKO/VEzPb2trFxYfI3y4q2EPBPCHcI24L1IZeZXrugx6mFJm02SntCrTA+/ysb7e59zoNrBmu+gQ==', 'buyer_id': '2088312606228550', 'invoice_amount': '0.01', 'notify_id': '2021112200222085305028551438751992', 'fund_bill_list': '[{"amount":"0.01","fundChannel":"PCREDIT"}]', 'notify_type': 'trade_status_sync', 'trade_status': 'TRADE_SUCCESS', 'receipt_amount': '0.01', 'app_id': '2021002194665673', 'buyer_pay_amount': '0.01', 'sign_type': 'RSA2', 'seller_id': '2088341077382474', 'gmt_payment': '2021-11-22 08:53:05', 'notify_time': '2021-11-22 08:56:13', 'version': '1.0', 'out_trade_no': '20211122085257269506402001', 'total_amount': '0.01', 'trade_no': '2021112222001428551424684280', 'auth_app_id': '2021002194665673', 'buyer_logon_id': '159****7057', 'point_amount': '0.00'}
"""
logger.error("收到支付宝回调通知:%s" % (processed_dict))
sign = processed_dict.pop("sign", None)
# 2. 生成一个Alipay对象
alipay = initalipay()
# 3. 进行验签,确保这是支付宝给我们的
verify_re = alipay.verify(processed_dict, sign)
# 如果验签成功
if verify_re is True:
order_sn = processed_dict.get('out_trade_no', None)#自己平台的订单号
trade_no = processed_dict.get('trade_no', None)#支付宝的订单号
trade_status = processed_dict.get('trade_status', None)
pay_time = processed_dict.get('gmt_payment', None)#用户支付时间
if trade_status == "TRADE_SUCCESS":#支付成功
# 查询数据库中存在的订单
order = OrderInfo.objects.filter(order_id=order_sn, pay_status=0, pay_method=3).first()
if not order:
logger.info("处理收到支付宝支付回调通知:%s,没有找到该订单" % (processed_dict))
return HttpResponse('fail')
order.trade_id = trade_no
order.pay_status = 1
order.pay_time = pay_time
order.save()
#其他逻辑处理
orderpaysuccess(order.id)
return HttpResponse('success')
else:
return HttpResponse('fail')
else:
return HttpResponse('fail')
#用户支付成功处理对应逻辑
def orderpaysuccess(order_id):
"""
order订单order的id(内部订单编号)
"""
order = OrderInfo.objects.get(id=order_id)
order.status = 2
order.save()
#支付成功其他业务处理