18.2 提交订单
分析
- 本小节完成的功能为用户选择商品后确认购买意愿并填写了收货信息、付款方式等信息后,单击“提交订单”按钮生成最终的订单。
- 订单信息要保存在数据库中,除了上述信息,还应包括订单创建时间、订单更新时间、支付方式、支付状态等更全面信息。
- 一个订单中可能有多个商品,是一对多的关系,所以需要将订单数据拆分为订单基本信息模型和订单商品信息模型。
- 订单基本信息模型包括创建时间、更新时间、订单号、下单用户、收货地址、订单总金额、商品总数、运费、支付方式、订单状态等字段,订单号采用“时间+用户id”,由后端生成,不在采用自增主键。
- 订单商品信息模型包括创建时间、更新时间、订单号、商品SKU信息、商品数量、下单时单价、商品评论、商品评分、是否匿名评价、是否完成评价。
实现
订单基本信息模型及订单商品信息模型
from django.db import models
from xiaoyu_mall_new.utils.models import BaseModel
from users.models import User, Address
from goods.models import SKU
class OrderInfo(BaseModel):
"""订单信息"""
PAY_METHODS_ENUM = {
"CASH": 1,
"ALIPAY": 2
}
PAY_METHOD_CHOICES = (
(1, "货到付款"),
(2, "支付宝"),
)
ORDER_STATUS_ENUM = {
"UNPAID": 1,
"UNSEND": 2,
"UNRECEIVED": 3,
"UNCOMMENT": 4,
"FINISHED": 5
}
ORDER_STATUS_CHOICES = (
(1, "待支付"),
(2, "待发货"),
(3, "待收货"),
(4, "待评价"),
(5, "已完成"),
(6, "已取消"),
)
order_id = models.CharField(max_length=64, primary_key=True, verbose_name="订单号")
user = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name="下单用户")
address = models.ForeignKey(Address, on_delete=models.PROTECT, verbose_name="收货地址")
total_count = models.IntegerField(default=1, verbose_name="商品总数")
total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="商品总金额")
freight = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="运费")
pay_method = models.SmallIntegerField(choices=PAY_METHOD_CHOICES, default=1, verbose_name="支付方式")
status = models.SmallIntegerField(choices=ORDER_STATUS_CHOICES, default=1, verbose_name="订单状态")
class Meta:
db_table = "tb_order_info"
verbose_name = '订单基本信息'
verbose_name_plural = verbose_name
def __str__(self):
return self.order_id
class OrderGoods(BaseModel):
"""订单商品"""
SCORE_CHOICES = (
(0, '0分'),
(1, '20分'),
(2, '40分'),
(3, '60分'),
(4, '80分'),
(5, '100分'),
)
order = models.ForeignKey(OrderInfo, related_name='skus', on_delete=models.CASCADE, verbose_name="订单")
sku = models.ForeignKey(SKU, on_delete=models.PROTECT, verbose_name="订单商品")
count = models.IntegerField(default=1, verbose_name="数量")
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="单价")
comment = models.TextField(default="", verbose_name="评价信息")
score = models.SmallIntegerField(choices=SCORE_CHOICES, default=5, verbose_name='满意度评分')
is_anonymous = models.BooleanField(default=False, verbose_name='是否匿名评价')
is_commented = models.BooleanField(default=False, verbose_name='是否评价了')
class Meta:
db_table = "tb_order_goods"
verbose_name = '订单商品'
verbose_name_plural = verbose_name
def __str__(self):
return self.sku.name
保存订单信息:用户点击“提交订单”按钮,页面订单中的数据被提交并保存到数据库中,在orders应用的views.py定义类视图OrderCommitView实现结算订单,继承LoginRequiredJSONMixin,以保证提交过程在用户已登录状态下进行。
import json
import logging
from decimal import Decimal
from django.utils import timezone
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseForbidden, JsonResponse
from django.shortcuts import render
from django.views import View
from django_redis import get_redis_connection
from goods.models import SKU
from orders.models import OrderInfo, OrderGoods
from users.models import Address
from xiaoyu_mall_new.utils.response_code import RETCODE
from xiaoyu_mall_new.utils.views import LoginRequiredJSONMixin
logger = logging.getLogger('django')
class OrderCommitView(LoginRequiredJSONMixin, View):
"""提交订单"""
def post(self, request):
# 接收参数
json_dict = json.loads(request.body.decode())
address_id = json_dict.get('address_id')
pay_method = json_dict.get('pay_method')
if not all([address_id, pay_method]):
return HttpResponseForbidden('缺少必要参数')
try:
address = Address.objects.get(id=address_id)
except Address.DoesNotExist:
return HttpResponseForbidden('参数address_id错误')
if pay_method not in [OrderInfo.PAY_METHODS_ENUM['CASH'], OrderInfo.PAY_METHODS_ENUM['ALIPAY']]:
return HttpResponseForbidden('参数pay_method错误')
# 获取用户
user = request.user
# 拼接订单id
order_id = timezone.localtime().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id)
order = OrderInfo.objects.create(
order_id=order_id,
user=user,
address=address,
total_count=0,
total_amount=Decimal('0.00'),
freight=Decimal('10.00'),
pay_method=pay_method,
status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY'] else
OrderInfo.ORDER_STATUS_ENUM['UNSEND'],
)
# 从redis中读取购物车中被勾选的商品 {b'1':b'1',b'2:b'2'}
redis_conn = get_redis_connection('carts')
redis_cart = redis_conn.hgetall('carts_%s' % user.id)
# 从redis数据库中选出被勾选的商品sku_id [b'1']
selected = redis_conn.smembers('selected_%s' % user.id)
carts = {}
for sku_id in selected:
carts[int(sku_id)] = int(redis_cart.get(sku_id))
# 通过sku模型类查询出在结算订单页面需要展示的商品sku
sku_ids = carts.keys()
# 遍历购物车中被勾选的商品
for sku_id in sku_ids:
sku = SKU.objects.get(id=sku_id)
sku_count = carts[sku_id]
# 库存不足
if (sku_count > sku.stock):
return JsonResponse({'code': RETCODE.STOCKERR, 'errmsg': '库存不足'})
# 库存与销量更新
sku.stock -= sku_count
sku.sales += sku_count
sku.save()
# 修改spu销量
sku.spu.sales += sku_count
sku.spu.save()
# 保存订单商品信息
OrderGoods.objects.create(
order=order,
sku=sku,
count=sku_count,
price=sku.price,
)
order.total_count += sku_count
order.total_amount += sku.price * sku_count
# 添加邮费
order.total_amount += order.freight
order.save()
# 清除购物车中已结算商品
pl = redis_conn.pipeline()
pl.hdel('carts_%s' % user.id, *selected)
pl.sadd('selected_%s' % user.id, *selected)
pl.execute()
return JsonResponse({'code': RETCODE.OK, 'errmsg': '下单成功', 'order_id': order.order_id})
在orders应用下urls.py中增加保存订单的路由
path(route='orders/commit/', view=views.OrderCommitView.as_view()),
定义呈现订单提交成功的页面order_success.html
{#<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">#}
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>小鱼商城-订单提交成功</title>
<link rel="stylesheet" type="text/css" href="{{ static('css/reset.css') }}">
<link rel="stylesheet" type="text/css" href="{{ static('css/main.css') }}">
<script type="text/javascript" src="{{ static('js/vue-2.5.16.js') }}"></script>
<script type="text/javascript" src="{{ static('js/axios-0.18.0.min.js') }}"></script>
</head>
<body>
<div id="app">
<div class="header_con">
<div class="header" v-cloak>
<div class="welcome fl">欢迎来到小鱼商城!</div>
<div class="fr">
<div v-if="username" class="login_btn fl">
欢迎您:<em>[[ username ]]</em>
<span>|</span>
<a href="{{ url('users:logout') }}">退出</a>
</div>
<div v-else class="login_btn fl">
<a href="{{ url('users:login') }}">登录</a>
<span>|</span>
<a href="{{ url('users:register') }}">注册</a>
</div>
<div class="user_link fl">
<span>|</span>
<a href="{{ url('users:info') }}">用户中心</a>
<span>|</span>
<a href="{{ url('carts:info') }}">我的购物车</a>
<span>|</span>
{# <a href="{{ url('users:myorderinfo',args=(1,)) }}">我的订单</a>#}
</div>
</div>
</div>
</div>
<div class="search_bar clearfix">
<a href="{{ url('contents:index') }}" class="logo fl"><img src="{{ static('images/logo.png') }}"></a>
<div class="search_wrap fl">
<form method="get" action="/search/" class="search_con">
<input type="text" class="input_text fl" name="q" placeholder="搜索商品">
<input type="submit" class="input_btn fr" name="" value="搜索">
</form>
<ul class="search_suggest fl">
<li><a href="#">索尼微单</a></li>
<li><a href="#">优惠15元</a></li>
<li><a href="#">美妆个护</a></li>
<li><a href="#">买2免1</a></li>
</ul>
</div>
</div>
<div class="common_list_con clearfix">
<div class="order_success">
<p><b>订单提交成功,订单总价<em>¥{{ payment_amount }}</em></b></p>
<p>您的订单已成功生成,选择您想要的支付方式,订单号:{{ order_id }}</p>
{# <p><a href="{{ url('users:myorderinfo', args=(1, )) }}">您可以在【用户中心】->【全部订单】查看该订单</a></p>#}
</div>
</div>
<div class="order_submit clearfix">
{% if pay_method == '1' %}
<a href="{{ url('contents:index') }}">继续购物</a>
{% else %}
<a @click="order_payment" class="payment">去支付</a>
{% endif %}
</div>
<div class="footer">
<div class="foot_link">
<a href="#">关于我们</a>
<span>|</span>
<a href="#">联系我们</a>
<span>|</span>
<a href="#">招聘人才</a>
<span>|</span>
<a href="#">友情链接</a>
</div>
<p>CopyRight © 2024 北京小鱼商业股份有限公司 All Rights Reserved</p>
<p>电话:010-****888 京ICP备*******8号</p>
</div>
</div>
<script type="text/javascript" src="{{ static('js/common.js') }}"></script>
<script type="text/javascript" src="{{ static('js/order_success.js') }}"></script>
</body>
</html>
定义上述页面对应的后端接口,在orders应用的views.py中定义类视图OrderSuccessView,在get方法中返回订单提交成功的页面。同样需要通过继承LoginRequiredMixin类来保证在用户登录状态进行。
class OrderSuccessView(LoginRequiredMixin, View):
"""提交订单成功页面"""
def get(self, request):
order_id = request.GET.get('order_id')
payment_amount = request.GET.get('payment_amount')
payment_method = request.GET.get('payment_method')
context = {
'order_id': order_id,
'payment_amount': payment_amount,
'payment_method': payment_method,
}
return render(request, 'order_success.html', context)
配置路由,在ordres应用下urls.py中增加路由
path(route='orders/success/', view=views.OrderSuccessView.as_view()),
261

被折叠的 条评论
为什么被折叠?



