【DailyFresh】课程记录5---订单模块(一)


订单模块
在购物车中选择好要购买的商品之后,点击去结算---》到达提交订单页面


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)
    }
})

去页面进行测试,去相应的数据库中进行查看验证后端逻辑


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值