Django项目之订单管理part5
一.前言
我们前面已经做了级别管理和客户管理,讲的时间比较长,因为补充的知识点比较多,而今天我们尽可能说完剩下的全部功能,比如说价格策略啥的,这个表单特别简单,我们也没啥可以补充的知识点的话,就可以直接展示截图了。
二.价格策略
2.1 价格策略基础页面
这个和前面基本都一样,我们就不过多赘述了,我们提前创建一个form
class PolicyModelForm(BootStrapForm,forms.ModelForm):
class Meta:
model=models.PricePolicy
fields='__all__'
def policy_list(request):
queryset = models.PricePolicy.objects.all().order_by('count')
pager = Pagination(request, queryset)
return render(request, 'policy/policy_list.html', {'pager': pager})
html和之前的都一样,我就不多说了。
2.2 新建价格策略
def policy_add(request):
if request.method == "GET":
form = PolicyModelForm()
return render(request, 'policy/policy_form.html', {'form': form})
form = PolicyModelForm(data=request.POST)
if not form.is_valid():
return render(request, 'policy/policy_form.html', {'form': form})
# 2.添加到数据库
form.save()
return redirect(reverse('policy_list'))
2.3 编辑价格策略
def policy_edit(request, pk):
level_object = models.PricePolicy.objects.filter(id=pk).first()
if request.method == "GET":
form = PolicyModelForm(instance=level_object)
return render(request, 'policy/policy_form.html', {'form': form})
# 获取数据+校验
form = PolicyModelForm(data=request.POST, instance=level_object)
if not form.is_valid():
return render(request, 'policy/policy_form.html', {'form': form})
form.save()
return redirect(reverse('policy_list'))
2.4 删除价格策略
def policy_delete(request, pk):
exists = models.PricePolicy.objects.filter(id=pk).exists()
if not exists:
res = BaseResponse(status=False, detail='请选择要删除的数据')
return JsonResponse(res.dict)
res = BaseResponse(status=True)
models.PricePolicy.objects.filter(id=pk).delete()
return JsonResponse(res.dict)
因为这个也是要有弹窗,所以我们就把公共的部分拿出来,要用的时候就导入。
我们就这样,谁要用就导入就可以了,这里给出代码
html:
<div class="modal fade" id="deleteModel" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="alert alert-danger alert-dismissible fade in" role="alert">
<h4>是否确定删除</h4>
<p>点击确定后删除 (删除后将无法复原) !</p>
<p>
<button type="button" class="btn btn-danger" id="btnConfirmDelete">确 定</button>
<button type="button" class="btn btn-default" id="btnCancelDelete">取 消</button>
<span style="color: red" id="deleteError"></span>
</p>
</div>
</div>
</div>
js:
$(function () {
bindDeleteEvent()
bindConfirmEvent()
})
function bindDeleteEvent() {
$('.btn-delete').click(function () {
$('#deleteError').empty()
$('#deleteModel').modal('show')
DELETE_ID = $(this).attr('cid')
})
$('#btnCancelDelete').click(function () {
$('#deleteModel').modal('hide')
})
}
function bindConfirmEvent() {
$('#btnConfirmDelete').click(function () {
//找到了要删除的id,通过ajax请求删除
$.ajax({
url: DELETE_URL+DELETE_ID+'/',
method: 'POST',
dataType: 'JSON',
success: function (res) {
if (res.status) {
//方式一:页面刷新
//location.reload()
//上市二:当前数据在页面上删除
$(`tr[rid='${DELETE_ID}']`).remove()
$('#deleteModel').modal('hide')
} else {
$('#deleteError').text(res.detail)
}
}
})
})
}
这样就成功写好了价格策略,是不是发现很简单。
三.优化权限页面
我们可以测试一下,假如我们给添加这个全选给取消掉,我们是不是点击之后添加之后会进入无权访问页面,但是我们不想这样,我们希望无权限的人连按钮都看不到,这需要怎么做呢?我们首先想到的是利用if判断,但是在html里面很难实现这种逻辑,所以我们可以借助自定义模板方法来实现。
自定义模板方法回归:(不记得的可以去看前面的知识点)
在模板中自定义方法:
filter
"xxxx"|uppersample_tag
{% xxxx x1 x2 x3 %}def xxx(): return ""inclusion_tag
def xxx(): return {'v1':xx,'v2':xx}<h1>{{v1}}</h1>
这里我们选择sample_tag来实现,这是因为我们在里面判断之后直线返回字符串,如果没有的话就是一个空字符串,没有必要用inclusion_tag来创建模板。
我们直接这么写,就好了,只要我们把权限都关掉,就不会出现这个按钮啦!
四.页面跳转问题
4.1 大部分页面跳转
我们可以创建多个客户数据,然后我们测试一下就知道了,当我们不是在第一页删除或者是编辑的时候,我们最后都会跳转到第一页,这个是因为我们最后跳转的时候都是写死的,没有写上原来页面上的参数参数,所以我们需要再每次点请求的时候把这个参数携带过去,但是如果我们写上一个参数,这个很容易会和本来要的数据产生冲突,我们比较难处理,所以我们可以把那些参数当成是一个字符串然后用一个filter参数接收,类似于filter=urlencode(name=123&page=15)这样传过去再对后面的进行处理,我们拿编辑举例
我们先来说一下我们请求,我们直接先是把上一个请求的params取出来,然后再利用QueryDict把它变成是一个_filter=url编码后的字符串,然后直接传给标签里面,这样下次点击的时候就是这样一个标签了,数据也都携带过去了。
返回的时候我们就是先判断有没有_filter如果没有就直接重定向,如果有的话就直接加上这个filter后的的字符串,加到反向生成之后,这样就可以了。
然后我们可以在除了删除以外的都加上这个并且返回,因为我们删除用的是ajax,并且除了客户需要跳转输入验证码,其他的都是删除页面元素,并不涉及到重定向,而除了客户我们需要单独写页面跳转。
4.2 客户管理删除页面跳转
这个就非常简单了,我们返回末尾加上上面那个
这里再判断一下就非常简单了,主要还是要知道处理的思路,用_filter包裹一下就非常简单了
五.充值
我们接下来要说的就是充值,我们希望我们可以实现给用户充值或者是扣款,这次我们就不去页面跳转,而是一个弹出框,选择充值还是扣款,并且生成一条交易记录展示到页面,那我们第一步就是要先去写页面了。
5.1 交易记录和对话框
我们这个还是和之前一样展示页面,点击添加就会出现一个弹出框,然后呢,弹出框里面有一个form表单,我们点击就可以通过ajax提交,这里我们,就是写了个基础的页面,这里需要注意的是我们想要展示到页面上的是charge_type是中文而不是那个数字,就需要
get_charge_type_display
但是我们这里只想写给用户充值扣款的功能,所以我们需要改变charge_type字段,这里可以选择重写或者是更改__init__中charge_tpye的choices字段,但是这两个有点区别,上面是静态的,如果更新了就只能从重启django项目,而下面的每次更新都会从数据库重新读取,但是这里我们用两种都是可以的,但是如果下面是选择充值的管理员,就不能用上面的了,因为我们项目一旦部署,而我们要是添加了管理员,就会得不到那个管理员。
5.2 充值扣款功能
做完上面的页面之后,我们就需要点击提交,像后端发送ajax请求,更新数据了。
我们首先肯定是要加上一个错误提示的位置的
再写上一个click事件,发送ajax请求
我们后端就是对数据进行更新,然后进行操作,但是这里需要注意的是我圈出来的位置,收先第一个就是 transaction.atomic(),这个我们是 from django.db import transaction调用的,我们这个是为什么要加上这个呢?这个是因为这个涉及了多个数据库,并且和钱有关系,我们这们做是要让两个数据库操作变成一个原子形操作,两个都成功了才能成功,我们可以想一下,第一个我们这边成功给他充值,但是下面生成订单的时候报错了,没有成功生成,那么就会出现对不上账,这就很危险,select_for_update() 而查询后加上这个,是相当于加了锁,配合transaction同时使用,防止两个管理员同时对数据库操作而导致一个没操作上,尤其是对于钱,一定要把条件弄得严格,这样就是一个很健壮的程序了。
大功告成
5.3 页面充值优化
我们看看刚才这个页面,是不是特别的不直观,我们前面是不是设置了一下一个mapping,里面有个样式,现在就是我们使用的时候了,此时此刻是不是得判断,取出model里的值,那我们是不是就要借助 filter 自定义一个函数
我们先写,直接去models里面取,这里因为不用数据库,就不用加objects
而且我们想要把没有内容的地方,比如订单号和备注不要展示成none,所以我们就加上个if判断
效果就是现在这样了
六.我的订单管理 (客户)
我们现在要做的订单管理,是基于用户的,我们要实现的就是用户能够看得到自己的订单,我们还是老规矩,不管了,先加钠。
6.1 我的订单基础页面
这里我们还是直接写,但是大家记住,权限都是加到客户里的而不是加到管理员,大家这个需要注意一下
这样就ok了
6.2 创建订单基础页面
因为创建订单的话非常的繁琐,所以这里先给出一个基础页面
我们还是创建一个form,大家肯定可以发现form大多数都是可以多次利用的,大家别学我,非要把form放在每个里面,其实这个没必要拆开,这样反而增加了工作量
我们这里只写了基础的校验啥的,后续还有一些列操作还没写,所以单开一个标题
这样就是创建好了
6.3 创建订单流程
这里先给大家说一个小的知识点,就是关于更新,这里要引入一个知识点就是django中的F关键字
6.3.1 F关键字
前面我们说了一个Q关键字,是在复杂条件查询的时候我们可以用的到,我们先说F关键字的应用场景,就是当我们要给原来数据库中比如说是cout自增1的时候就用得到
关于更新:
对数据直接更新
models.Customer.objects.filter(id=22).update(name='往日情怀酿作酒',count=1999)
想在原有值的基础上更新
cus_object= models.Customer.objects.filter(id=22).first()
cus_object.name ='往日情怀酿作酒'
cus_object.count=cus_object.count + 10
cus_object.save()大家肯定觉得很繁琐,但是有F关键字就不一样了
from django.db.models import F
models.Customer.objects.filter(id=22).update(name='xxxx' ,count=F("count")+1000)
这样就能直接操作在原有基础上进行删除啦!
6.3.2 流程代码
def my_order_add(request):
if request.method == "GET":
form = MyOrderModelForm()
return render(request, 'my_order/my_order_form.html', {'form': form})
form = MyOrderModelForm(data=request.POST)
if not form.is_valid():
return render(request, 'my_order/my_order_form.html', {'form': form})
# 1.获取url和count
video_url = form.cleaned_data['url']
count = form.cleaned_data['count']
# 1.1 通过url获取原播放 为了防止一会加上锁影响性能
status, old_view_count = get_old_view_count(video_url)
if not status:
form.add_error('url', '视频原来播放获取失败')
return render(request, 'my_order/my_order_form.html', {'form': form})
# 2.根据数量获取单价,计算出原价
for idx in range(len(form.price_count_list) - 1, -1, -1):
limit_count, unit_price = form.price_count_list[idx]
if count >= limit_count:
break
total_price = count * unit_price
# 3.获取当前客户所处的级别计算折扣后的价格
try:
with transaction.atomic():
cus_object = models.Customer.objects.filter(id=request.userinfo.id).select_related(
'level').select_for_update().first()
real_price = total_price * cus_object.level.percent / 100
# 4.判断账户余额是否不足
if cus_object.balance < real_price:
form.add_error('count',
'账户余额不足,本次消耗{:.2f}元,账户余额只有{}'.format(real_price, cus_object.balance))
return render(request, 'my_order/my_order_form.html', {'form': form})
# 5.创建订单
# 5.1创建订单号
while True:
rand_number = random.randint(10000000, 99999999)
ctime = datetime.datetime.now().strftime("%d%m%Y%H%M%S%f")
oid = "{}{}".format(ctime, rand_number)
exists = models.Order.objects.filter(oid=oid).exists()
if not exists:
break
# 5.2 创建订单
form.instance.oid = oid
form.instance.price = total_price
form.instance.real_price = real_price
form.instance.old_view_count = old_view_count
form.instance.customer_id = request.userinfo.id
form.save()
# 6 客户账号扣款
models.Customer.objects.filter(id=request.userinfo.id).update(balance=F('balance') - real_price)
# 7 生成交易记录
models.TransactionRecord.objects.create(
charge_type=3,
customer_id=request.userinfo.id,
amount=real_price,
order_oid=oid
)
# 8 写入redis队列
conn = get_redis_connection('default')
conn.lpush(settings.QUEUE_TASK_NAME, oid)
except Exception as e:
form.add_error('count', '创建订单失败')
return render(request, 'my_order/my_order_form.html', {'form': form})
return redirect(reverse('my_order_list'))
这个代码特别长,这里就不和大家讲解了,反正这样就能实现了一个创建订单的逻辑。
七.总结
今天说的内容还是很多的,我们这一章还是没能讲完,不过后面也就一点内容了,基本功能都大差不差了,但是在订单这部分还是差一个撤单,但是想要加上这个内容还是很多,还要再讲一个message知识点,所以就留到下一期和worker一起讲了。
八.补充
下一期将和大家开始讲的内容有点多,希望大家的关注加收藏,不懂得看我的名字和签名,一起交流学习