订单模块
在购物车中选择好要购买的商品之后,点击去结算---》到达提交订单页面
P86 提交订单页面显示
用户的收货地址
支付方式(写好的,不需要去查)
用户要购买的商品列表信息(包括购买数量和小计)
商品的总件数和总金额,运费,实付款
分析:
在购物车页面点击【去结算】---》传给提交订单页面参数包括:选中的商品(商品id、数量),关于涉及到金额的参数仅做展示,在实际结算的时候不会用页面上的,因此传参时不需要传金额相关的参数
用户购买的商品数量是从redis中来的,通过商品id即可从redis中获取到用户要购买的商品数量,因此最后只需要去传递用户要购买的商品id。
如何传参:
在购物车页面,将ul标签这一部分放入表单中
<form>
{% for sku in skus %}
<ulclass="cast_list_td">...
{% endfor %}
<ulclass="settlements">...
</form>
表单中含有元素为checkbox,可以将checkbox的值进行设置name和value
<li class="col01"><input type="checkbox" name="sku_ids" value="{{ sku.id }}"></li>
去结算部分修改成提交 按钮
<li class="col04"><input type="submit" value="去结算"></li>
测试:刷新页面---》检查元素,查看
<li class="col01"><input type="checkbox" name="sku_ids" value="{{ sku.id }}"></li>
其中name一样,不同商品对应的value不同
给点击去结算设置一个地址
<form method="post" action="{% url 'order:place' %}">
点击去结算,会报错404【未配置】,检查元素--》Network---》Headers---》FormData可以看到被提交的skuids为checkbox被选中的商品。
这是因为我们将checkbox的值设置为商品对应的id,只有选中的才会被提交。
表单中的checkbox,只有被选中时值才会被提交。
提交的名字都叫sku_ids
使用post方式提交的参数都存放在request.POST中
request.GET/request.POST---》都是QueryDict对象
这个对象运行一个名字对应多个值,取得时候必须通过request.POST.getlist()获取
要获取用户选中的商品需要获取名字为sku_ids
request.POST.getlist('sku_ids')
接下来写对应的视图
order/views.py
from django.views.generic import View
# /order/place
# 是post方式提交,其中视图类中应该有个对应的post函数
# 没有登录用户无法访问,这个不是js可以直接继承 LoginRequiredMixin
class OrderPlaceView(LoginRequiredMixin, View):
'''提交订单页面显示'''
def post(self, request):
'''提交订单页面显示'''
# 获取用户信息
user = request.user
# 获取参数sku_ids 列表,用户要购买的商品id
sku_ids = request.POST.getlist('sku_ids')
# 校验参数
if not sku_ids:
# 跳转到购物车页面
return redirect(reverse('cart:show'))
conn = get_redis_connection('default')
cart_key = "cart_%d" %(user.id)
skus = []
# 保存购物车中商品的总件数和总金额
total_count = 0
total_price = 0
# 遍历sku_ids获取用户要购买的商品信息
for sku_id in skus_ids:
# 根据商品id获取商品的信息
sku = GoodsSPU.objects.get(id=sku_id)
# 去redis中获取要购买的商品的数量
count = conn.hget(cart_key, sku_id)
# 计算商品的小计 redis 中的值是字符串类型,需要转化类型
amount = sku.price * int(count)
#数量和小计都需要在页面上进行展示
# 动态地给sku增加属性count , 保存购买商品的数量
sku.count =count
# 动态地给sku增加属性amount , 保存购买商品的小计
sku.amount = amount
# 保存商品信息到列表中
skus.append(sku)
# 累加计算商品的总件数和总价格
total_count += int(count)
total_price += amount
# 运费:在实际开发的时候,应该属于一个子系统(可以新建一个子系统,比如可以建一张表,买的商品金额介于xx-xx
之间,获取运费)这里未开发,暂时为硬编码
transit_price = 10
# 实付款为总金额+运费
total_pay = total_price + transit_price
# 获取用户的收件地址,将用户的收件地址全部查询出来,用户进行选择
addrs = Address.objects.filter(user=user)
# 组织上下文
context = {
'addrs': addrs,
'total_count': total_count,
'total_price': total_price,
'transit_price': transit_price,
'total_pay': total_pay,
'skus': skus,
}
return render(request, 'df_order/place_order.html', context)
前端页面
order/place_order.html
1.地址部分要遍历
2.商品ul部分遍历【数量和小计都有动态添加的属性sku.count和sku.amount,注意,其中序号使用{{ forloop.counter }}】
配置地址
path('place', OrderPlaceView.as_view(), name='place') # 提交订单页面显示
展示的时候,地址只能选中一个,需要在页面中地址部分加name="addr_id" value="{{ addr.id }}" {% if addr.is_default %}checked{% endif %}<----表示选中
P87 创建订单前端js
用户提交订单页面的显示,要查询出:
用户地址信息的显示;
用户要购买的商品的信息以及商品数量和小计;
商品的总金额,运费和实付款
当用户提交订单时,需要在后台生成对应的订单,关于订单如何生成,先回顾订单中的两张表:
订单信息表和订单商品表
订单信息表中的trade_no订单编号默认为空
trade_no = models.CharField(max_length=128, default="", verbose_name='支付编号')
订单创建:
用户在页面上点击提交订单,在后台要生成订单,给后台需要传递的参数:
收货地址、
支付方式、
商品id,
关于金额不需要传递,后台不会使用传来的参数,自己在后台做计算
收货地址可以获取到,因为选择的时候在后台设置place_order.html的时候,每个地址都有value值,只需要将选中的value值拿出来
支付方式也都有对应的值 value="1" value="2"...与models表中定义的一一对应
<input type="radio" name="pay_style" value="1" checked>
商品id,页面中没有,用户要购买的商品信息都是后端传过来的,在显示订单提交页面时用户要购买的商品都在sku_ids = request.POST.getlist('sku_ids')中【order/views.py的OrderPlaceViews中】
sku_ids 是一个列表,可以在传的时候将其拼接成字符串,以逗号分隔
# sku_ids # [1, 25]-->1,25
在OrderPlaceViews中的组织上下文之前将sku_ids列表拼接成字符串,传给前端页面
sku_ids = ','.join(sku_ids)
# 组织上下文
context = {
'addrs': addrs,
'total_count': total_count,
'total_price': total_price,
'transit_price': transit_price,
'total_pay': total_pay,
'skus': skus,
'sku_ids': sku_ids,
}
传给前端页面
<div class="order_submit clearfix">
{% csrf_token %}
<a href="javascript:;" sku_ids="{{ sku_ids }}" id="order_btn">提交订单</a>
</div>
刷新之后查看提交订单中会有sku_ids=4, 26
此时三个参数都可以获取到,接下来要看提交订单,关于提交订单在前端已经写好了click事件,点击提交订单之后就会提交函数,在函数中往后台发一个请求
请求的时候按照刚才的分析:获取用户选择的地址id,支付方式,要购买的商品id字符串
{% block bottomfiles %}
<script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script>
<script type="text/javascript">
$('#order_btn').click(function() {
// 找地址,分析页面标签name="addr_id"
// $('input[name="addr_id"]:checked').val()<----可以找到被选中的用户地址的id
addr_id = $('input[name="addr_id"]:checked').val()
// 同理,找到用户选择的支付方式
pay_method = $('input[name="pay_style"]:checked').val()
// 用户要购买的商品id的以逗号分隔的字符串
sku_ids = $(this).attr('sku_ids')
// csrf值
csrf = $('input[name="csrfmiddlewaretoken"]').val()
// 在发起请求之前用alert看一下
// alert(addr_id+":"+pay_method+":"+sku_ids)
// 测试时没反应,检查--》查看报错$没定义,是因为static文件没有引过来{% load static %}
// 组织参数
params = {
'addr_id':addr_id,
'pay_method':pay_method,
'sku_ids':sku_ids,
'csrfmiddlewaretoken':csrf
}
// 发起ajax post请求,提交订单地址《---访问/order/commit, 传递的参数: addr_id,pay_method,sku_ids
// 页面提交时加上csrf_token
// 发起ajax post请求, 访问的地址,传入的参数, 回调函数
// 请求地址,将参数传过去,根据回调函数的返回内容做一个相应的处理
$.post('/order/commit', params, function (data) {
})
})
</script>
{% endblock %}
P88 创建订单后台view
有js之后,在views中定义地址对应的视图,在order.views中定义视图OrderCommitView《---作用是创建订单
# /order/commit
class OrderCommitView(View):
"""订单创建"""
def post(self, request):
"""订单创建"""
pass
配置URL:
path('commit', OrderCommitView.as_view(), name='commit') # 订单创建
from django.http import JsonResponse
接下来分析OrderCommitView中需要进行什么操作
# 前端传递的参数, 地址id(addr_id) 支付方式(pay_method) 用户要购买的商品id字符串(sku_ids)
# 订单创建之后未登录无法访问
# /order/commit
class OrderCommitView(View):
"""订单创建"""
def post(self, request):
"""订单创建"""
# 判断用户是否登录
user = request.user
if not user.is_authenticated():
# 用户未登录
return JsonResponse({'res': 0, 'errmsg': '用户未登录'})
# 接收参数
addr_id = request.POST.get('addr_id')
pay_method = request.POST.get('pay_method')
sku_ids = request.POST.get('sku_ids')
# 参数校验 三个都要进行校验
if not all([addr_id, pay_method, sku_ids]):
return JsonResponse({'res': 1, 'errmsg': '参数不完整'})
# 校验支付方式,models中只有1-4,如果传来5则不行
# 找到models类,拿个字典过来,需要在模型类中再定义一个字典
# 校验地址
# TODO1:创建订单核心业务
# 组织参数
# TODO2:向df_order_info表中添加一条记录
# TODO3:用户的订单中有几个商品,就需要向df_order_goods表中加入几条记录。
# 返回响应
校验支付方式,models中只有1-4,如果传来5则不行
校验步骤:
# 找到models类,拿个字典过来,需要在模型类中再定义一个字典
PAY_METHODS = {
'1': "货到付款",
'2': "微信支付",
'3': "支付宝",
'4': '银联支付'
}
PAY_METHODS_ENUM = {
"CASH": 1,
"ALIPAY": 2
}
ORDER_STATUS = {
1: '待支付',
2: '待发货',
3: '待收货',
4: '待评价',
5: '已完成'
}
PAY_METHODS.keys()<---如果传过来的pay_method不再字典的键中,就不是一个合法的输入方式
上述三个字典定义在order.models.OrderInfo中,,在views.OrderCommitView中进行支付方式的参数校验时如下:
# 校验支付方式
if pay_method not in OrderInfo.PAY_METHODS.keys():
# 说明支付方式是非法的,返回提示
return JsonResponse({'res': 2, 'errmsg': '非法的支付方式'})
# 校验地址
try:
addr = Address.objects.get(id=addr_id)
except Address.DoesNotExist:
return JsonResponse({'res': 3, 'errmsg': '地址非法'})
# TODO:创建订单核心业务
分析:用户提交一个订单,订单中有两个表:
一个是订单信息表df_order_info,对应订单信息模型类,
一个是订单商品表df_order_goods
现在用户下一条订单需要往订单信息表中添加几条信息?
用户每下一个订单,就需要向df_order_info表中加入一条记录;
用户的订单中有几个商品,就需要向df_order_goods表中加入几条记录。
比如用户现在下了一个订单,订单中包含三种商品
商品id为1的买了5件,商品id为2的买了3件,商品id为10的买了2件
{1: 5, 2: 3, 10: 2}
订单信息表和订单商品表需要在一块儿
在创建一个订单的时候,先往订单信息表中添加信息,再往订单商品表中添加信息,因为订单商品表中有一个外键,不先添加订单信息表就不存在外键
所以在创建订单核心业务部分,先向df_order_info表中添加一条记录
# TODO:向df_order_info表中添加一条记录
接下来考虑向df_order_info表中添加一条记录需要哪些参数
直接找到models,关于OrderInfo模型类即对应着df_order_info表,往该表中添加信息需要通过OrderInfo模型类来添加
分析这些字段:
order_id:不是自动的,是自己指定的主键,指定之后,id自动增长的那一列就没有了,所以这个需要自己去增加
user:有
addr:有
pay_method: 有
total_count: 没有
total_price: 没有
transit_price: 没有
order_status: 有默认值,不需要设置
trade_no: 有默认值,不需要设置
创建订单信息记录缺少的参数:
order_id
total_count
total_price
transit_price
在实现TODO2:向df_order_info表中添加一条记录之前先组织参数
from datetime import datetime
# 组织参数
# 订单id:年月日时分秒+用户id 20210205153320+用户id,获取时间需要借助datetime类
# 获取时间datetime.now(),变成上面的字符串格式使用datetime.now().strftime('%Y%m%d%H%M%S')
# user.id是数字
order_id = datetime.now().strftime('%Y%m%d%H%M%S') + str(user.id)
# 运费,暂时进行硬编码
transit_price = 10
# 总数目和总金额,暂时先记初值0,等计算出来之后再改掉
total_count = 0
total_price = 0
# TODO2:向df_order_info表中添加一条记录
order = OrderInfo.objects.create(order_id=order_id,
user=user,
addr=addr,
pay_method=pay_method,
total_count=total_count,
total_price=total_price,
transit_price=transit_price
)
# TODO3:用户的订单中有几个商品,就需要向df_order_goods表中加入几条记录。
conn = get_redis_connection('default')
cart_key = 'cart_%d' %(user.id)
# 用户订单中的商品数目:目前已经获取到sku_ids = '1, 2',《---本次订单中商品id以逗号进行分隔的字符串,将其转化成列表
sku_ids = sku_ids.split(',') # [1, 2]
# 此时列表中有几个元素则订单中就有几个商品,进行遍历即可
for sku_id in sku_ids:
# 获取商品的信息
# 在最开始进行参数校验的时候只校验了addr和pay_method,因此这里需要进行参数校验
try:
sku = GoodsSKU.objects.get(id=sku_id)
except GoodsSKU.DoesNotExist:
return JsonResponse({'res': 4, 'errmsg': '商品不存在'})
# 遍历的时候往df_order_goods表中添加记录
# 分析往df_order_goods中添加记录需要的参数
# 从redis中获取用户所要购买的商品的数量
count = conn.hget(cart_key, sku_id)
# todo: 向df_order_goods表中添加一条记录
OrderGoods.objects.create(order=order,
sku=sku,
count=count,
price=sku.price
)
# 用户下单之后,销量和库存应该改变
# TODO4:更新商品的库存和销量
# 此时应该是库存减少,销量增加,对应的商品记为sku
sku.stock -= int(count)
sku.sales += int(count)
sku.save()
# todo5: 累加计算订单商品的总数量和总价格
# 计算小计amount
amount = sku.price * int(count)
total_count += int(count)
total_price += amount
# 此时两个表中必要的信息已经添加,但是订单总数目和总价格还是初值0,因此当跳出循环之后需要进行更新
# TODO6: 更新订单信息表中的商品总数量和总价格
order.total_count = total_count
order.total_price = total_price
order.save()
# 此时两个表中必要的信息已经添加
# 用户在卖完购物车中的商品时候需要删除购物车中相应的记录
# TODO7: 清除用户购物车中对应的记录
# 从redis中删除 hdel(name, *keys) <---一下能删除多个,用户购买的商品id都在sku_ids列表中,所以将出现在列表sku_ids中的商品都删除即可
# 这里不能直接传列表,它是一个位置元素???需要一个一个传,这里可以对列表进行拆包操作,列表名前面直接添加一个* ,sku_ids代表[1, 3] *sku_ids代表1, 3
conn.hdel(cart_key, *sku_ids)
# 至此清除完毕
# 返回应答
return JsonResponse({'res': 5, 'message': '订单创建成功'})
接下来处理前端中的js部分
$.post('/order/commit', params, function (data) {
// 进行处理
if (data.res == 5){
alert('订单创建成功')
}
else{
alert(data.errmsg)
}
})
去页面进行测试,去相应的数据库中进行查看验证后端逻辑