我们在app上购买商品,都是先在商品页选择商品添加到购物车,然后从购物车结算,考虑一下怎么实现这个功能。
商品,购物车,用户之间,可以转换为表的关联,购物车相当于关系表的角色,用户和商品之间成为多对多的关系。
首先去models.py写购物车的模型
class Cart(models.Model):
c_user=models.ForeignKey(User)
c_goods=models.ForeignKey(Goods)
c_goods_num=models.IntegerField(default=1)
c_is_select=models.BooleanField(default=True)
class Meta:
db_table = 'sxw_cart'
在新版本的django中,外键必须添加on_delete=models.CASCADE属性,否则会报错。外键关联到用户和商品,采用级联删除,当购物车中删除的时候,用户也删除相关信息。
执行数据迁移。
当点击购物车的时候,添加一个点击事件,这个事件发起了一个ajax请求,通过js,把goodsid传到后台,
market.js
$(".addShopping").click(function(){
console.log('add');
var $add=$(this);
var goodsid=$add.attr('goodsid');
$.get('/sxw/addtocart/',{
'goodsid':goodsid},function(data){
console.log(data);
if (data['status'] === 302){
window.open('/sxw/login/',target="_self");
}else if(data['status']===200){
$add.prev('span').html(data['c_goods_num']);
}
})
})
上面的步骤已经取到了商品的id和num,接下来使用ajax传到后台。先将数据传到/sxw/addtocart/
function(data)是一个回调函数,作用是当ajax请求发起后,在后台接受到请求后,会主动调用这个函数,把数据放到data里。
写一个视图函数,因为是ajax请求,所以返回json会比较好。
def add_to_cart(request):
goodsid=request.GET.get('goodsid')
#获取到商品id,根据用户和商品id,获取到购物车数据
carts=Cart.objects.filter(c_user=request.user).filter(c_goods_id=goodsid)
#如果购物车中有该商品,该商品数量加1;如果没有,将该商品id加入购物车
if carts.exists():
cart_obj=carts.first()
cart_obj.c_goods_num = cart_obj.c_goods_num + 1
else:
cart_obj=Cart()
cart_obj.c_goods_id=goodsid
cart_obj.c_user=request.user
cart_obj.save()
data={
'status':200,
'msg':'add success',
'c_goods_num':cart_obj.c_goods_num
}
#返回购物车中该商品数量
return JsonResponse(data=data)
最后,添加下url
url(r'^addtocart/',views.add_to_cart,name='add_to_cart'),
从购物车移除商品的做法与添加一样。
market.js
$(".subShopping").click(function(){
console.log('sub');
var $sub=$(this);
var goodsid=$sub.attr('goodsid');
$.get('/sxw/subtocart/',{
'goodsid':goodsid},function(data){
console.log(data);
if (data['status'] === 302){
window.open('/sxw/login/',target="_self");
}else if(data['status']===200){
$sub.prev('span').html(data['c_goods_num']);
}
})
})
视图函数
def sub_to_cart(request):
goodsid = request.GET.get('goodsid')
carts = Cart.objects.filter(c_user=request.user).filter(c_goods_id=goodsid)
data = {
'status': 200,
'msg': 'add success',
}
if carts.exists():
cart_obj = carts.first()
if cart_obj.c_goods_num > 1:
cart_obj.c_goods_num = cart_obj.c_goods_num - 1
cart_obj.save()
data['c_goods_num'] = cart_obj.c_goods_num
else:
cart_obj.delete()
data['c_goods_num'] = 0
return JsonResponse(data=data)
配置路由
url(r'^subtocart/',views.sub_to_cart,name='sub_to_cart'),
def cart(request):
carts=Cart.objects.filter(c_user=request.user)
is_all_select= not carts.filter(c_is_select=False).exists()
data={
'title':'购物车',
'carts':carts,
'is_all_select':is_all_select,
'total_price':get_total_price()
}
return render(request,'cart.html',context=data)
cart.html
{
% extends 'base_main.html' %}
{
% load static %}
{
% block ext_css %}
{
{
block.super }}
<link rel="stylesheet" href="{% static 'main/css/cart.css' %}">
{
% endblock %}
{
% block ext_js %}
{
{
block.super }}
<script type="text/javascript" src="{% static 'main/js/cart.js'%}"></script>
{
% endblock%}
{
% block content %}
<div id="cart">
<h3>购物车</h3>
<div class="full">
<section>
<ul>
<li>收货人:aimo</li>
<li>电话:110</li>
<li>地址:杭州西湖里</li>
</ul>
<section class="bill">
<p>闪送超市</p>
<p>¥0起送,22:00前满¥30免运费</p>
<a href="#">凑单专区</a>
</section>
<section class="delivery">
<span>收货时间</span>
<span>一小时送达</span>
<a href="#">可预订></a>
</section>
<section class="delivery">
<span>收货备注</span> <input type="text" placeholder="可输入100字以内特殊要求内容"/>
</section>
<ul>
{
% for cart in carts %}
<li class="menulist" cartid="{
{ cart.id }}">
<div class="confirm">
<span>
{
% if cart.c_is_select %}
<span>√</span>
{
% else %}
<span></span>
{
% endif %}
</span>
</div>
<a href="#">
<img src="{
{ cart.c_goods.productimg }}" alt="{
{cart.c_goods.productlongname}}">
<p>{
{
cart.c_goods.productlongname }}</p>
<p class="presentPrice">{
{
cart.c_goods.price}}</p>
</a>
<section>
<button class="subShopping">-</button>
<span>{
{
cart.c_goods_num}}</span>
<button class="addShopping">+</button>
</section>
</li>
{
% endfor%}
</ul>
<li class="payTheBill">
<div class="all_select">
<span>
{
% if is_all_select %}
<span>√</span>
{
% else %}
<span></span>
{
% endif %}
</span>
</div>
<p>
<span>全选</span>
<span>共计:</span>
<span id="total_price">{
{
total_price}}</span>
</p>
<span id="make_order">下单</span>
</li>
</section>
</div>
</div>
{
% endblock %}
购物车默认状态全选按钮是选中,此时内部所有商品都是选中的;如果全选按钮未选中,内部商品中只要存在未选中的,全选就应该是未选中。
点击全选,原状态是选中,全选和所有商品都变成未选中;原状态是未选中,全选和所有商品都变成选中
点击单个商品,商品由选中变成未选中,全选一定变成未选中;商品由未选中变成选中,那全选的默认状态就是未选中,也可能变成选中。
购物车还应该会根据选中商品的数量和价格,自动计算出订单总价。
在views_helps中写一个函数来计算商品总价
def get_total_price():
carts=Cart.objects.filter(c_is_select=True)
total=0
for cart in carts:
total += cart.c_goods_num * cart.c_goods.price
total=float('%.2f'%total)
return total
cart.js
$(function (){
$(".confirm").click(function(){
console.log("change state");
var $confirm=$(this);
var $li=$confirm.parents("li");
var cartid=$li.attr('cartid');
$.getJSON("/sxw/changecartstate/",{
'cartid':cartid},function (data){
console.log(data);
if (data['status']===200){
$("#total_price").html(data['total_price']);
if(data['c_is_select']){
$confirm.find("span").find("span").html("√");
}else{
$confirm.find("span").find("span").html("");
}
if (data['is_all_select']){
$(".all_select span span").html("√");
}else{
$(".all_select span span").html("");
}
}
})
})
$(".subShopping").click(function(){
var $sub=$(this);
var $li =$sub.parents("li");
var cartid=$li.attr("cartid");
$.getJSON("/sxw/subshopping/",{
"cartid":cartid},function(data){
console.log(data);
if (data['status']===200){
$("#total_price").html(data['total_price']);
if (data['c_goods_num']>0){
var $span =$sub.next("span");
$span.html(data['c_goods_num']);
}else{
$li.remove();
}
}
})
})
$(".all_select").click(function(){
var $all_select=$(this);
var select_list=[];
var unselect_list=[];
$(".confirm").each(function(){
var $confirm=$(this);
var cartid =$confirm.parents("li").attr("cartid");
if($confirm.find("span").find("span").html().trim()){
select_list.push(cartid);
}else{
unselect_list.push(cartid);
}
})
console.log(select_list);
console.log(unselect_list);
if(unselect_list.length > 0 ){
$.getJSON('/sxw/allselect/',{
'cart_list':unselect_list.join('#')},function(data){
console.log(data);
if(data['status']===200){
$(".confirm").find("span").find("span").html("√");
$all_select.find("span").find("span").html("√");
$("#total_price").html(data['total_price']);
}
})
}else{
if (select_list.length > 0){
$.getJSON('/sxw/allselect/',{
'cart_list':select_list.join('#')},function(data){
console.log(data);
if(data['status']===200){
$(".confirm").find("span").find("span").html("");
$all_select.find("span").find("span").html("");
$("#total_price").html(data['total_price']);
}
})
}
}
})
views.py
def change_cart_state(request):
cart_id=request.GET.get('cartid')
cart_obj=Cart.objects.get(pk=cart_id)
cart_obj.c_is_select=not cart_obj.c_is_select
cart_obj.save()
is_all_select=not Cart.objects.filter(c_user=request.user).filter(c_is_select=False).exists()
data={
'status':200,
'msg':'change ok',
'c_is_select':cart_obj.c_is_select,
'is_all_select':is_all_select,
'total_price': get_total_price(),
}
return JsonResponse(data=data)
def sub_shopping(request):
cart_id = request.GET.get('cartid')
cart_obj = Cart.objects.get(pk=cart_id)
data = {
'status':200,
'msg':'ok',
}
if cart_obj.c_goods_num > 1:
cart_obj.c_goods_num = cart_obj.c_goods_num - 1
cart_obj.save()
data['c_goods_num']=cart_obj.c_goods_num
else:
cart_obj.delete()
data['c_goods_num']=0
data['total_price'] = get_total_price()
return JsonResponse(data=data)
def all_select(request):
cart_list=request.GET.get('cart_list')
cart_list=cart_list.split('#')
carts=Cart.objects.filter(id__in=cart_list)
for cart_obj in carts:
cart_obj.c_is_select = not cart_obj.c_is_select
cart_obj.save()
print(cart_list)
data={
'status':200,
'msg':'ok',
'total_price':get_total_price(),
}
return JsonResponse(data=data)
配置路由
url(r'^changecartstate/',views.change_cart_state,name='change_cart_state'),
url(r'^subshopping/',views.sub_shopping,name='sub_shopping'),
url(r'^allselect/',views.all_select,name='all_select'),
有些页面不需要用户登录,而有些页面需要用户登录才具有相应权限,如果每次都去判断用户是否登陆,不仅代码量大,耦合性也低。鉴于每一次实现功能,都要判断用户是否登陆状态,我们可以使用中间件来选择请求哪些页面需要登陆。当然,也可以使用装饰器。
这里,我们创建一个logincheckmiddleware.py
from django.http import JsonResponse
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.deprecation import MiddlewareMixin
from App.models import User
REQUIRE_LOGIN=[
'/sxw/cart/',
'/sxw/orderdetail/',
'/sxw/orderlistnotpay/',
]
REQUIRE_LOGIN_JSON=[
'/sxw/addtocart/',
'/sxw/changecartstate/',
'/sxw/makeorder/',
]
class LoginMiddleware(MiddlewareMixin):
def process_request(self,request):
if request.path in REQUIRE_LOGIN_JSON:
user_id=request.session.get('user_id')
if user_id:
try:
user=User.objects.get(pk=user_id)
request.user=user
except:
data={
'status':301,
'msg':'user not avaliable'
}
return JsonResponse(data=data)
#return redirect(reverse('sxw:login'))
else:
data = {
'status': 301,
'msg': 'user not login'
}
return JsonResponse(data=data)
#return redirect(reverse('sxw:login'))
if request.path in REQUIRE_LOGIN:
user_id = request.session.get('user_id')
if user_id:
try:
user = User.objects.get(pk=user_id)
request.user = user
except:
return redirect(reverse('sxw:login'))
else:
return redirect(reverse('sxw:login'))
写好中间件,要在settings中注册。