交易中心
# 交易记录列表
交易记录列表 以及 新增按钮弹出模态框页面 的展示
后续可优化的点: 金额可根据 类型在前面添加个+
、-
. 逻辑复杂的话可以专门写一个tag模版!!
特别注意:
以往点击新增按钮GET请求跳转的页面, 和form表单POST请求提交数据.. 这两个请求都是用的同一个不同于列表页面的url!!
但这里不一样, 点击新增按钮没有跳转, 是在当前 交易列表页面 出现了一个弹出框..
So, 交易列表页面对应的视图函数还得传递 新增页面里的表单数据!!
相当于原来新增页面的方式里只剩下post了,get的代码写到了交易列表页面的get里.
1.在客户列表上添加一列,名为"交易".点击‘相关记录’会携带该条客户记录的id跳转到 客户交易记录 的页面.
2.写对应的视图函数,查询该客户相关数据+分页功能,传入‘交易记录’的模版中!!
注意: {{ row.get_charge_type_display }} 这样以来,类型一栏显示的是中文.
管理员给客户充值是没有订单号的;客户自己下单时会扣费,会有订单号. 所以订单号可能为空.
3.在交易记录列表中的类型 --> 充值、扣款等用不同的颜色展示,更直观!!
{% load color %}
<td>
<span class="btn btn-xs btn-{{ row.charge_type|color }}">
{{ row.get_charge_type_display }}
</span>
</td>
"""web/templatetags/color.py
from django import template
from web import models
register = template.Library()
@register.filter
def color(num):
# !!在模型类TransactionRecord里有charge_type_class_mapping这个字典.. 表明了类型和样式之间的关系.
return models.TransactionRecord.charge_type_class_mapping[num]
"""
4.在交易记录列表中,订单号和备注可能为空/页面上展示为None,也做一个处理!有两种处理方式.
# 方式一
<td>
{% if row.memo %}
{{ row.memo }}
{% else %}
-
{% endif %}
</td>
# 方式二
<td>{{ row.memo|default:'--' }}</td>
5.在交易记录的模版中,新增按钮的html也有讲究.
在以往的级别管理、客户管理、价格策略管理的添加功能都是跳转页面,用form表单的形式提交.
<a class="btn btn-success" href="{% url 'customer_add' %}">
<span class="glyphicon glyphicon-plus-sign"></span>新建
</a>
此处的添加交易记录想弹出对话框来实现.那么就没必要用a标签.使用button按钮,点击弹出对话框!
方式一: <input type="button" value="新建"> 该方式不好,因为想给按钮添加个图标是添加不了的.
方式二:
<button class="btn btn-success">
<span class="glyphicon glyphicon-plus-sign"></span>新建
</button>
6.添加弹出的模态框,里面的内容显示.用到了modelform组件.
★ 注意一个要点!显示的时候,动态的修改数据源,有两种方式.
1> 重写字段;
2> 在初始化方法里定义.
针对外键数据,方式一,只适合固定的数据,不适合去数据表中获取数据.方式二都可以.
举个例子,creator字段是外键字段,与其他表有关联.
creator = forms.TypedChoiceField(
label='管理员',
choices=models.Administrator.objects.filter(id__gt=1).values_list("id", 'username'),)
self.fields['creator'].choices = \
models.Administrator.objects.filter(id__gt=1).values_list("id", 'username')
Why?重写字段 creator是类中的静态变量,在程序启动起来后,就放到内存中了.下一次执行时,直接从内存中拿.
即静态变量,方式一里的sql语句只会在程序第一次运行时才会执行sql查询!!程序在运行中,数据新增了,也不会重新查询.
也就是说,若添加了一个管理员,creator里也不会再次执行查询数据库的语句,页面上是拿不到新增的管理员的,会直接拿内存里的..
不会实时更新.只有程序重启,才会再次执行.页面才会拿到新增的管理员.
而,初始化方法,每次执行视图函数时,都会ChargeModelForm()进行类的初始化,都会调用初始化方法!!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
前端关键代码
后端关键代码
class ChargeModelForm(BootStrapForm, forms.ModelForm):
# 方式一:
# 交易记录新增,类型选项只能有 "充值"、"扣款",其余的三个是跟订单有关的!!
# 为什么是TypedChoiceField? 可以在__init__里print(self.fields['charge_type']),其类型就是TypedChoiceField
# 证明自动对应的就是TypedChoiceField 本质也是字符串类型
# 但coerce=int 指定类型,表明获取到值后,会在内部将其自动转换为int类型!!
charge_type = forms.TypedChoiceField(
label="类型",
choices=[(1, "充值"), (2, "扣款")],
coerce=int
)
# creator = forms.TypedChoiceField(
# label='管理员',
# # 该数据查询结果类似于 choices = [(2,'xxx'),(3,'xx')] choices只支持这种格式.
# choices=models.Administrator.objects.filter(id__gt=1).values_list("id", 'username'),
# )
class Meta:
model = models.TransactionRecord
fields = ['charge_type', 'amount'] # 类型,金额
# fields = ['charge_type', 'amount', 'creator'] # 类型,金额
# 方式二:
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
# self.fields['charge_type'].choices = [(1, "充值"), (2, "扣款")]
# self.fields['creator'].choices = \
# models.Administrator.objects.filter(id__gt=1).values_list("id", 'username')
def customer_charge_list(request, pk):
""" 交易记录列表 以及 新增按钮弹出模态框页面的展示 """
# 查询条件: 该客户+该客户的没有被逻辑删除+没有被逻辑删除的所有记录 --> 倒叙排
# Ps:原则上,交易记录是不允许删除的,用户想删除后端也只是进行逻辑删除!
# Ps:filter查询条件中,customer__active已经实现跨表了,所以这里select_related加不加都无所谓.
queryset = models.TransactionRecord.objects.filter(
customer_id=pk,
customer__active=1,
active=1,
).select_related('customer').order_by('-id')
pager = Pagination(request, queryset)
form = ChargeModelForm()
# 传pk是因为,新增交易记录的ajax请求对应的url需要用的pk值!!
# return render(request, 'customer_charge.html', {"pager": pager, "form": form, "pk": pk})
# locals()获取局部作用域里的值!!
return render(request, 'customer_charge.html', locals())
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 新增-充值/扣款
只需发送ajax的post请求啦!!
ajax收到后端返回的json字符串, 跟 短信登陆那的处理一样: 展示错误信息; 子绝父相.新增交易记录的功能主要是 - 充值和扣款!! (属于管理员的操作,客户是没有该权限的)
# 事务和锁
事务解决原子型的操作.
▲ 注意: 锁不能单独存在,需要与事务一起使用, 事务结束后才会释放锁!!
新增交易记录的基本逻辑:
1.得到ajax请求传过来的数据,进行表单验证,若验证失败,返回错误信息.
2.若验证成功.
1> 对当前操作的客户进行更新操作,账户余额: 增加、减少
2> 增加一条交易记录
★ 此处的1> 2>两步是原子性的操作,要么一起成功要么一起失败. -- innodb引擎的mysql是支持事务的!!
Ps:何为原子性,以A给B转账为例,A账户余额减少100,B账户余额增加100.不能说A的余额减少了,B的余额没增加.
from django.db import transaction
with transaction.atomic():
# 步骤 1> 的实现
# 步骤 2> 的实现
# Ps: 步骤 1和步骤2都成功了,才会保存到数据库,事务里任何地方出错了都会回滚到操作之前/事务开始之前的状态!
# 如何验证?加事务,步骤2的某个单词写错了,执行到它会报错,执行它之前已经执行完了步骤1> . 但发现数据库的该记录并没发生改变!!
★ 锁是针对客户的余额来说的! -- 解决同时操作,数据混乱的问题!
客户C余额200,管理员A和管理员B同时给客户C充值,前者充200,后者充100.
执行代码. 管理员A和管理员B 都读取到客户C的余额是200,谁后执行,最终余额就是多少.但不可能是200+200+100
with transaction.atomic():
cus_object = models.Customer.objects.filter(id=pk, active=1).first()
cus_object.balance = cus_object.balance + amount
cus_object.save()
So,我们需要加把锁(排他锁)!!
with transaction.atomic():
cus_object = models.Customer.objects.filter(id=pk, active=1).select_for_update().first() # <1>
cus_object.balance = cus_object.balance + amount # <2>
cus_object.save() # <3>
管理员A和管理员B执行到 <1> 的地方,只有拿到锁后, 才能执行<1>以及继续往下执行<2><3>.
假设A先执行,B就在<1>处阻塞住,A执行完后,释放锁;B拿到锁后才能执行<1>,进而保证B得到的是最新的数据!!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
简单用原生sql来还原下事务+锁!!
# 新增交易记录
关键代码如下:
"""
注意,若得到的charge_type = form.cleaned_data['charge_type'] 的值的类型是str. if charge_type == 1,判断时,为False.
依照视图函数中的业务逻辑,就会出现充值反而扣款的情况.
"""
def customer_charge_add(request, pk):
"""接收前端ajax请求的数据 新增交易记录"""
# request.POST 得到前端传过来三个数据 csrfmiddlewaretoken、charge_type、amount
# 1.先进行表单数据验证
# 针对类型,它会校验是否是 1或者2 其他的是通过不了的. 不用担心人为的修改<option value="10">充值</option>
form = ChargeModelForm(data=request.POST)
if not form.is_valid():
return JsonResponse({'status': False, 'detail': form.errors})
from django.db import transaction
try: # -- 不让其页面报错,通常会try..except捕获错误
with transaction.atomic():
# 2.若校验成功
amount = form.cleaned_data['amount'] # 金额
charge_type = form.cleaned_data['charge_type'] # 类型
# 2.1对当前操作的客户进行更新操作,账户余额: 增加、减少 + 锁
cus_object = models.Customer.objects.filter(id=pk, active=1).select_for_update().first()
# balance和amount都是Decimal类型的数据. 可以进行比较、加减等.
if charge_type == 2 and cus_object.balance < amount:
return JsonResponse({
'status': False,
'detail': {"amount": ["余额不足,账户总余额只有:{}".format(cus_object.balance)]}
})
if charge_type == 1:
cus_object.balance = cus_object.balance + amount
else:
cus_object.balance = cus_object.balance - amount
cus_object.save()
# 2.2增加一条交易记录
form.instance.customer = cus_object
form.instance.creator_id = request.nb_user.id
form.save()
except Exception as e:
return JsonResponse({'status': False, 'detail': {"amount": ["操作失败"+str(e)]}})
return JsonResponse({"status": True})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 订单列表
登陆客户角色的用户, 可以看到动态菜单里显示 “订单中心> 订单管理”
只有客户登陆才能看到自己的订单列表, 管理员是不能进行该操作的!! 即 查看当前登陆客户订单列表的权限只有客户才有.
无它, 唯手熟尔!
1> 创建订单列表的url和对应视图函数以及对应的订单列表页面.
2> 在settings里配置客户角色的权限
3> 在视图函数里传递 当前客户的订单数据以及分页 给订单列表页面!!
订单列表里,可以展示很多字段,但注意: 展示订单号,就不用展示记录的ID了; 价格包含了原价和真实的价格; 可以对创建时间进行处理.
<待优化>
订单列表里的状态字段不同的值,可以像交易记录列表中的类型一样,加颜色!!
eg:在交易记录列表中的类型 --> 充值、扣款等用不同的颜色展示,更直观!!
2
3
4
5
6
7
8
9
10
11
关键代码如下:
def my_order_list(request):
"""我的订单列表"""
# customer_id是当前登录的客户的id
queryset = models.Order.objects.filter(customer_id=request.nb_user.id, active=1).order_by('-id')
pager = Pagination(request, queryset)
return render(request, 'my_order_list.html', {"pager": pager})
2
3
4
5
6
# 创建订单
客户下单功能主要是创建订单 (属于客户的操作, 管理员是没有该权限的)
创建成功后, 客户可以在订单列表里看到订单信息;
管理员可以在客户列表里点击该客户对应的相关交易,看到该用户的交易记录列表!!
# 订单需求概览
◆ 先对整体的订单需求有个概览.
- 客户首先进入,我的订单列表(客户可以看到自己下了哪些订单)
- 客户还可以继续下单
- 输入: 视频地址、刷单的数量(提示价格策略)
- 数据库操作(事务 + 锁)
- 创建订单记录
- 创建交易记录
- 扣款
- 将下单的任务加到redis队列中 (等待执行)
- 撤销订单(客户下了单,该订单还未执行的话是可以撤单的)
- 数据库操作(事务 + 锁)
- 更新订单状态
- 生成交易记录
- 归还扣款
2
3
4
5
6
7
8
9
10
11
12
13
14
# 注意事项
1.my_order_add视图函数实现了,显示创建订单的页面+form表单提交.
2.创建订单的页面,使用的是form.html模版,用户需要输入 视频地址和数量.
3.在创建订单页面,给客户展示价格策略!! 如何实现呢?
在form.html里有这么几行代码 ★★★★★★★★★★★★★★★★★
{% if field.help_text %}
<span style="font-weight: 300;color: lightgray;">
({{ field.help_text }})
</span>
{% endif %}
So,我们可以在 MyOrderModelForm(BootStrapForm, forms.ModelForm) 类里重新定义构造方法!
4.在创建订单页面,表单提交的数量字段的值要有最少数量的限制.
通过钩子方法来校验数量!!
5.form表单校验通过后,就是一系列的业务逻辑!
1> 爬虫,发送网络请求获取原播放
2> 客户输入数量,根据该数量所对应的价格策略,计算出原价!!
try: # -- 加上事务和锁后,出现异常会回滚,我们还需捕获该异常!!
with transaction.atomic(): # -- 加上事务,在哪加?涉及到数据库的操作,就得考虑包含在事务里!
3> 根据客户的级别,给原价打折! # -- 加上锁
4> 判断账户余额是否足够
5> 创建订单
5.1> 生成订单号
5.2> 生成订单记录
6> 对当前客户的账户余额进行扣款
7> 生成交易记录
8> 写入redis队列
except Exception as e:
form.add_error('count', "创建订单失败" + str(e))
return render(request, 'form.html', {"form": form})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
一定要注意 算出来的价格的类型是什么 eg: <class 'decimal.Decimal'>类型, int类型等.. 看情况决定用啥类型!
>>> from decimal import Decimal
>>> v1 = Decimal("100")
>>> v1
Decimal('100')
>>> v1 > 90
True
>>> v2 = v1 + 100
>>> print(v2,type(v2))
200 <class 'decimal.Decimal'>
>>> v2 = int(v2)
>>> print(v2,type(v2))
200 <class 'int'>
>>> v3 = Decimal("300.02")
>>> int(v3)
300
>>> str(300) < str(v3)
True
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 业务代码
import datetime
import random
from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import F
from django.shortcuts import render, redirect
from django.urls import reverse
from django_redis import get_redis_connection
from utils.bootstrap import BootStrapForm
from utils.pager import Pagination
from web import models
class MyOrderModelForm(BootStrapForm, forms.ModelForm):
class Meta:
model = models.Order
fields = ["url", 'count'] # 视频地址、数量
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
text_count_list, price_count_list = self.price_policy_info()
self.min_count_limit = price_count_list[0][0] if price_count_list else ""
self.price_count_list = price_count_list
text = "、".join(text_count_list) # "、".join([]) --> ""
# 要考虑未定义价格策略的情况!!
self.fields['count'].help_text = text if text else "请联系管理员设置价格策略!"
@staticmethod
def price_policy_info():
"""构建 数量的帮助信息 和 价格策略信息 两个列表"""
queryset = models.PricePolicy.objects.all().order_by('count') # 根据数量进行升序
# min_count_limit = queryset[0].count if queryset else ""
text_count_list = []
price_count_list = []
for item in queryset:
unit_price = item.price / item.count # 单价(每条多少钱)
text_count_list.append(">={} ¥{}/条".format(item.count, round(unit_price, 4)))
# 存储的是 [(数量1,单价1),(数量2,单价2),..]
price_count_list.append((item.count, unit_price))
# 返回 数量的帮助信息 和 价格策略信息
return text_count_list, price_count_list
def clean_count(self):
# self.min_count_limit为"" 证明价格策略表里没有记录!
if not self.min_count_limit:
raise ValidationError("下单失败,请联系管理员设置价格策略!")
count = self.cleaned_data['count']
# 注意,得保证 这两个比较的数据类型是一致的哦!
if count < self.min_count_limit:
raise ValidationError("最低支持数量为:{}".format(self.min_count_limit))
return count
def my_order_list(request):
"""我的订单列表"""
# customer_id是当前登录的客户的id
queryset = models.Order.objects.filter(customer_id=request.nb_user.id, active=1).order_by('-id')
pager = Pagination(request, queryset)
return render(request, 'my_order_list.html', {"pager": pager})
def my_order_add(request):
"""创建订单 显示创建订单的页面+form表单提交"""
if request.method == "GET":
form = MyOrderModelForm()
return render(request, 'form.html', {"form": form})
form = MyOrderModelForm(data=request.POST)
if not form.is_valid():
return render(request, 'form.html', {"form": form})
# 校验通过后,一系列的业务逻辑
video_url = form.cleaned_data['url']
count = form.cleaned_data['count']
# 1.爬虫,发送网络请求获取原播放
# 视频原播放获取不了的话,就直接返回页面错误信息
# ★ 不建议将爬虫程序放到事务中,请求不成功,相对会阻塞很久.
# 具体的爬虫逻辑就不展示了,它类似于返回
# def get_old_view_count(*args):
# return True, '2.4万'
from utils.video import get_old_view_count
status, old_view_count = get_old_view_count(video_url) # 获取原播放量的程序
if not status:
form.add_error('url', "视频原播放量获取失败")
return render(request, 'form.html', {"form": form})
# 2.客户输入数量,根据该数量所对应的价格策略,计算出原价!!
# 能走到这一步,确保了form.price_count_list里肯定有值!!且填写的数量肯定能命中区间!!
# Ps:列表切片在右边是浅拷贝!!
"""
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
"""
for _, (limit_count, unit_price) in enumerate(form.price_count_list[::-1]):
if count >= limit_count:
break
total_price = count * unit_price # 原价 Decimal类型
try:
with transaction.atomic():
# 3.根据客户的级别,给原价打折!
# Ps:此处的查询还可以应用上select_related("percent"),cus_object.level.percent就不用去数据库中查询了.
# ★ 在这里该查询客户信息加上锁. 第6步那,同样是对Customer进行操作,是不会阻塞住的!!因为当前客户是有锁的.
cus_object = models.Customer.objects.filter(id=request.nb_user.id).select_for_update().first()
real_price = total_price * cus_object.level.percent / 100 # 折扣价格 Decimal类型
# 4.判断账户余额是否足够
if cus_object.balance < real_price:
form.add_error('count', f"此订单需要扣款¥{real_price},账户余额为¥{cus_object.balance}.请充值!")
return render(request, 'form.html', {"form": form})
# 5.创建订单
# 5.1 生成订单号
while True:
rand_number = random.randint(10000000, 99999999)
ctime = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")
oid = "{}{}".format(ctime, rand_number)
# 一般生成订单号执行上面三行代码就够了,没必要去数据库中查.. 若你担心高并发的情况,不是有事务加锁吗?
# 再不济,出现了特极端的情况,在数据库那还对 订单号 作了一个唯一索引的约束!!
is_exists = models.Order.objects.filter(oid=oid).exists()
if is_exists:
continue
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.nb_user.id
form.save()
# 6.对当前客户的账户余额进行扣款
# 方式一,使用update实现的更新操作不能直接获取原来的值,若想在原有值的基础上进行修改,需要借助与F查询
models.Customer.objects.filter(id=request.nb_user.id).update(balance=F("balance") - real_price)
# 方式二
# cus_object.balance = cus_object.balance - real_price
# cus_object.save()
# 7.生成交易记录
# 注意,此处是客户下的订单,跟管理员无关,所以此处生成的交易记录,管理员字段可以为空.
models.TransactionRecord.objects.create(
charge_type=3, # 表明是创建订单生成的交易记录
customer_id=request.nb_user.id,
amount=real_price, # 金额是真实的成交价格
order_oid=oid,
)
# 7.写入redis队列 (得保证redis已启动,Django配置文件里连接上了redis缓存.)
conn = get_redis_connection("default")
# settings.QUEUE_TASK_NAME是队列的名字,oid订单号加入该队列中
# 放的时候,从左边放;取的时候,从右边开始取.
conn.lpush(settings.QUEUE_TASK_NAME, oid)
except Exception as e:
form.add_error('count', "创建订单失败" + str(e))
return render(request, 'form.html', {"form": form})
return redirect(reverse("my_order_list"))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# 撤单功能分析
作为一个开发者, 针对撤单功能, 我会选择用ajax的方式, 该方式实现该功能会比用url跳转的方式 少些很多代码!!
只有当订单的状态是‘待执行’时, 才能撤单!! 否则页面上压根没有撤单的按钮!
<td>
{% if row.status == 1 %} <!-- 订单状态为 待执行 -->
<a href="{% url 'my_order_cancel' pk=row.id %}" class="btn btn-danger btn-xs">撤单</a>
{% endif %}
</td>
2
3
4
5
撤单的功能有两种方式来实现!! ajax 以及 url 的形式! 分析如下:
前端页面,可以点击撤单按钮,弹出一个对话框,问,是否确定撤单?!点击确定后,页面反馈一个正在操作.. 具体操作略.
这里就直接点击撤单按钮,就开始操作啦!!
[通过url的方式提交]
需要解决的问题:
1> 若订单有很多页,点击撤单按钮,会跳转撤单按钮的url
http://127.0.0.1:8000/my/order/list/ ---> `http://127.0.0.1:8000/my/order/cancel/2`
订单删除完后,不能只跳转回列表页面,因为页面会刷新..显示的是第一页,万一我删除的是第10页上的订单呢?
So,需要考虑 链接跳转的问题!
2> 要需要考虑错误信息的提示!!
这样想,像编辑按钮,我点击后,跳转url,会get请求返回一个页面,post请求提交数据时,出错了可以显示在该页面上.
但是,这里,撤单的功能,虽然也会跳转url,但是后端对该get请求并不会返回页面.. 会直接进行删除订单的业务逻辑.
那出错了,显示在哪里呢?
注意: ‘def my_order_cancel(request, pk)’ 这里的pk会对url中的动态参数自动进行接收!!
也许你会想到用
-- render(request, "xxx.html", {"error":"..."}) render刷新页面,将错误渲染到该页面上,但页面上的其他信息呢?
比如,订单列表的信息,不得查询数据库,跟错误信息一同传到该页面上渲染?
真这样做了,不就相对于执行了订单列表url对应的视图函数吗?写了重复代码..
-- 用redirect("/my/order/list"),重定向到该url,向该url地址发送网络请求,执行对应的视图函数.
就单纯的使用redirect,你是没办法传递错误信息过去!!
如何解决??用Django里的message组件,简单理解,它底层是基于session来实现的!!
跨页面+刷新+想展示错误信息 就用message组件!!
[通过ajax的方式提交]
比用form表单跳转的形式要简单一些:
点击撤单按钮,用ajax的方式,悄悄的向后台发送了一个网络请求.
后台处理完后,可以选择在页面上删除这一行,或其他操作; 可以location.reload()刷新页面,但还是在当前所在页数的页面..
哪怕有了错误信息,也可以在当前页面进行展示!
优化: 一点,可以做个加载框,告诉用户正在处理中.用户体验更好.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
撤单的业务流程
★ 得加事务 + 加锁
1> 订单状态变化 "待执行"-->"已撤单"
2> 金额归还给用户
3> 生成一条撤单的交易记录
注意:得在撤单之前做一下状态的判断!!
避免客户人为的访问`http://127.0.0.1:8000/my/order/cancel/2` 不断返回金额.
2
3
4
5
6