购物车没有对应的模型类,其操作的是存储于redis中的购物车记录,前文中已经叙述过购物车在redis如何存储。
在页面中操作购物车记录,如果要通过后端重定向或重新渲染模板则会极大的浪费服务器资源,因此在前端中使用ajax post请求来完成,只修改局部的需要变动的元素,不对整个页面进行重新请求,在商品详情页面,与购物车相关的可能变动的内容有:
增加/减少/手动输入商品数量
此类操作不发送请求,只是使用jQuery来更改页面元素的显示,绑定增加/减少按钮的click事件,绑定商品数量input元素的失去焦点事件,令其对商品数量进行更新,并更新商品总价。
注:①由于未发送请求,此处不与商品的库存做对比;
②商品数量减少最低减少到1,商品数量手动输入需要对输入内容进行规整,若不符合规范则设置为1;
③在事件结束后一定要更新商品总价,并需要注意保留小数位数。
添加到购物车
发送ajax post请求,将商品的数量、ID发送至后端,在后端中对该用户的购物车记录进行更新,需要注意的内容有:
①验证用户是否登录,由于非登录用户也可以访问商品详情页,因此不能直接使用Loginrequiredmixin类,而且ajax发起的请求都在后台(包括跳转,Loginrequiredmixin类的作用是跳转到登录页面,因此在ajax请求时不能使用),因此虽然跳转请求发起,但在浏览器中看不到跳转的效果;
②接收数据后校验数据合法性,且需要与该商品的库存进行比对,并且需要验证用户购物车中是否已存在此类商品,若有则只修改,若无则需要重新添加;
③若添加到购物车成功,则需要对购物车显示的总数目也进行变更。
详细代码如下:
# 前端采用ajax post传递参数,传递商品id(sku_id),商品数量(count),并接收Json对象
class CartAddView(View):
'''添加购物车记录'''
def post(self, request):
# 判断用户是否登录
user = request.user
if not user.is_authenticated:
return JsonResponse({'res':0, 'errmsg':'请先登录'})
# 接收数据,用post方法因此不在定义函数中以形参出现
sku_id = request.POST.get('sku_id')
count = request.POST.get('count')
# 校验数据合法性
if not all([sku_id, count]):
return JsonResponse({'res':1, 'errmsg':'数据不完整'})
# 校验商品数量合法性
try:
count = int(count)
except Exception:
return JsonResponse({'res':2, 'errmsg':'数目出错'})
# 校验商品是否存在
try:
sku = GoodsSKU.objects.get(id=sku_id)
except GoodsSKU.DoesNotExist:
return JsonResponse({'res':3, 'errmsg':'商品不存在'})
# 添加到购物车,链接到redis数据库
conn = get_redis_connection('default')
cart_key = 'cart_{}'.format(user.id)
# 检索该商品是否已存在,若sku_id在此hash中不存在则返回None
cart_count = conn.hget(cart_key, sku_id)
if cart_count:
count += int(cart_count)
# 校验库存值,设置对应值
if count>sku.stock:
return JsonResponse({'res':4, 'errmsg':'商品库存不足'})
conn.hset(cart_key, sku_id, count)
# 计算用户当前购物车条目数
total_count = conn.hlen(cart_key)
return JsonResponse({'res':5, 'message':'添加成功', 'total_count':total_count})
注:①ajax post请求中Django没有自动对其进行csrf验证,因此需要手动将csrf_token的值添加到请求体中。
购物车页面的显示
①从redis中获取该用户购物车的信息(商品ID和商品数量);
②将商品的数量和小计价格动态添加到查询到的商品模型类实例的实例属性中,将商品模型类实例的列表和总数量、总价格传入模板;
③渲染模板,显示购物车页面。
购物车数据的更新
购物车数据的更新是需要直接与redis中的购物车记录关联的,因此购物车页面的数据的变化需要发送ajax post请求与后端交互,选中与否的状态与订单提交有关,但其影响了要提交商品的总价格与总数量,因此也必须考虑到其改变对购物车页面数据的影响。
由于页面中同类操作较多,因此提取出三个函数,分别为:A更新商品的小计价格,B更新要提交的商品的总数量与总价格,C更新购物车中的数量、发起ajax post请求并根据应答判断是否变动数据。
①关于全选与各个商品的选中:
Ⅰ设置全选与全不选,即在变化全选checkbox状态时,页面上各个商品前方的checkbox状态应与全选checkbox的状态一致,即全部选中与全部不选中;
Ⅱ考虑当商品的checkbox变化时,对全选checkbox的影响,即当所有商品的checkbox选中时全选checkbox选中,当存在商品checkbox未选中时全选checkbox未选中(完成原理为对比选中的与总的checkbox的数量);
注:上述状态的变更通过jQuery完成,不与后端交互,在完成时注意B变更商品总数量与总价格。
②关于购物车商品数量的添加/减少/手动输入:
Ⅰ与商品详情页面类似的,绑定相关事件,获取商品ID与数量,并C发送请求到后端;
Ⅱ在后端中校验用户登录状态,数据合法性,库存值,并返回应答;
Ⅲ根据后端返回的应答判断是否更新成功,若A成功则更新商品数目与小计,判断是否选中,若选中则B更新要提交的商品的总数量与总价格,更新购物车中所有的商品总件数。
注:①购物车页面显示被选中的(即要提交的)商品总件数与总价格,也显示购物车中所有的商品总件数,需分开处理;
②在用户手动输入数量时,需要校验输入的数据是否合法,若不合法则为购物车原始数目,因此需要记录。
详细代码如下:
class CartUpdateView(View):
'''购物车记录更新'''
def post(self, request):
# 判断用户是否登录
user = request.user
if not user.is_authenticated:
return JsonResponse({'res':0, 'errmsg':'请先登录'})
# 接收数据
sku_id = request.POST.get('sku_id')
count = request.POST.get('count')
# 校验数据合法性
if not all([sku_id, count]):
return JsonResponse({'res':1, 'errmsg':'数据不完整'})
# 校验商品数量合法性
try:
count = int(count)
except Exception:
return JsonResponse({'res':2, 'errmsg':'数目出错'})
# 校验商品是否存在
try:
sku = GoodsSKU.objects.get(id=sku_id)
except GoodsSKU.DoesNotExist:
return JsonResponse({'res':3, 'errmsg':'商品不存在'})
# 更新redis数据库中记录
conn = get_redis_connection('default')
cart_key = 'cart_{}'.format(user.id)
# 校验库存值,设置对应值
if count>sku.stock:
return JsonResponse({'res':4, 'errmsg':'商品库存不足'})
conn.hset(cart_key, sku_id, count)
# 更新商品的总件数
vals = conn.hvals(cart_key)
total_count = sum([int(x) for x in vals])
# 返回应答
return JsonResponse({'res':5, 'total_count':total_count, 'message':'更新成功'})
③关于购物车中商品的删除:
Ⅰ绑定相关事件,获取商品ID,发送ajax post删除请求到后端;
Ⅱ在后端中校验数据,更新数据库记录,并更新商品的总件数,返回应答;
Ⅲ根据返回的应答判断是否删除成功,若成功则移除相应的ul元素,并根据是否选中选择是否B更新要提交的商品的总数量与总价格,然后重新设置购物车中所有的商品总件数,若不成功可以alert应答信息。
其代码与更新购物车商品类似。
以上即购物车模块的工作流程及注意事项,购物车模块的重点开发在于前端代码,购物车页面是比较典型和全面的jQuery页面开发案例,
完整代码见https://github.com/Icemelon99/test_project。