07 用户购物车模块

1 实现添加购物车的功能

前端的页面如下:

前端的页面分析:

要实现添加购物车的功能,前端要向后端传送的数据有商品的id和添加的数量,并且后端规定以post的方式发送给后端.

后端视图函数的业务逻辑

1 判断用户是否登陆,没登陆返回用户未登录

2 接受前端传来的参数

3 对参数进行校验,判断是否有空的参数,有为空的参数返回参数不完整

4 对传过来的商品id进行数据库查询,看数据库中是否有这个商品存在,没有返回商品不存在

5 对参数添加购物车的数量的类型判断,如果不是整数,返回参数错误

6 判断是否超过库存的数量,返回超过库存不足

7  业务逻辑处理,将购物车数据保存到redis中 

如果redis中不存在,则直接将数据保存到redis的购物车中

如果redis中原本包含了这个商品的数量信息, 进行数量累加,在保存到redis中 通过返回json数据,告知前端处理的结果

8 将最新的购物车信息设置到redis中

=======================================================

前端的post请求:

 window.location.href = "/users/login";  js中的重定向

post请求必须带  csrfmiddlewaretoken: "{{ csrf_token }}" 否则回报403访问被禁止的错误

 前端post请求的代码如下:

$('#add_cart').click(function(){
		    var req_data = {
		       sku_id:  $(this).attr("sku_id"),
                count: $("#num_show").val(),
                csrfmiddlewaretoken: "{{ csrf_token }}"
            };
		    // 向后端发送添加购物车的请求
		    $.post("/cart/add", req_data, function (resp_data) {
                if (1 == resp_data.code) {
                    // 后端表示用户未登录
                    window.location.href = "/users/login";
                } else if (0 == resp_data.code) {
                    // 添加成功
                    $(".add_jump").stop().animate({
                    'left': $to_y+7,
                    'top': $to_x+7},
                    "fast", function() {
					$(".add_jump").fadeOut('fast',function(){
						$('#show_count').html(resp_data.cart_num);
					});

			});
                } else {
                    alert(resp_data.message);
                }
            }, "json")


		});

请求处理的视图函数:

# /cart/add
class AddCartView(View):
    """添加购物车"""
    def post(self, request):
        # 判断用户是否登录
        if not request.user.is_authenticated():
            # 表示用户未登录
            return JsonResponse({"code": 1, "message": "用户未登录"})

        # sku_id 商品id
        # count 商品数量
        # 接受参数
        sku_id = request.POST.get("sku_id")
        count = request.POST.get("count")

        # 校验参数
        if not all([sku_id, count]):
            return JsonResponse({"code": 2, "message": "参数不完整"})

        # 判断商品存在与否
        try:
            sku = GoodsSKU.objects.get(id=sku_id)
        except GoodsSKU.DoesNotExist:
            # 表示商品不存在
            return JsonResponse({"code": 3, "message": "商品不存在"})

        # 判断用户请求的count是不是整数
        try:
            count = int(count)
        except Exception:
            # 数量不是整数
            return JsonResponse({"code": 4, "message": "参数错误"})

        # 判断数量有没有超过库存
        if count > sku.stock:
            return JsonResponse({"code": 5, "message": "库存不足"})

        # 业务逻辑处理,将数据保存到redis中
        # {"sku_1": 10, "sku_2": 20}
        redis_conn = get_redis_connection("default")

        # 先从redis中尝试获取用户原有购物车中相同商品的信息
        user_id = request.user.id
        origin_count = redis_conn.hget("cart_%s" % user_id, sku_id)

        # 如果redis中不存在,则直接将数据保存到redis的购物车中
        # 如果redis中原本包含了这个商品的数量信息, 进行数量累加,在保存到redis中
        if origin_count is not None:
            count += int(origin_count)
        # 在把新的购物信息设置会 redis中
        redis_conn.hset("cart_%s" % user_id, sku_id, count)
        cart_num = 0
        cart =redis_conn.hgetall('cart_%s' %user_id)
        for val in cart.values():
            cart_num +=int(val)


        # 通过返回json数据,告知前端处理的结果
        return JsonResponse({"code": 0, "message": "添加购物车成功",'cart_num':cart_num})
View Code

url的根级的请求路径:

  url(r'^cart/', include(cart.urls, namespace="cart")),

在cart应用中的uri的请求路径

from cart import views

urlpatterns = [
    url(r"^add$", views.AddCartView.as_view(), name="add"),
]

 添加购物车成功后前端的页面如下;

 redis中的购物车数量如下:

 ===========================================================================

对上面的添加购物车进行升级(增加用户未登录也能添加购物车的功能)

 如果用户未登录,则购物车数据保存在用户的cookie中(用的浏览器中)

把用户的购物车数据以json字符串的方式保存在cookie中(cookie只能保存字符串)  “cart”: ‘{   “sku_1“:10, “sku_2”: 11  }’

如果用户登录,则购物车数据是保存在后端的redis中,用户在登录的时候,将cookie中的购物车数据与redis中的数据进行合并,合并后保存在redis中

json模块

将python的字典类型数据转换为json字符串的方法  json.dumps(字典数据)  {‘a’:1} ---->  '{'a':1}'

将json字符串转换为python的字典类型数据的方法   json.loads(json字符串) '{'a':1}' ---> {'a':1}

 业务逻辑的分析:

 1 如果用户未登录,则将数据保存到cookie中

 2 先获取用户的cookie中的购物车数据

 3  取 出原来存储在浏览器中的cookie,把原来的转成字典

4  判断cookies是否包含传来的商品shu_id如果有则求和,没有则增加

5 把最新的商品数量设置到字典中

6  统计购物车中的商品总数

7 构建response对象

8 设置最新的cookie信息

添加用户未登录实现添加购物车的视图函数

 用户未登录主要的代码

cart_json = request.COOKIES.get("cart") # 取原来存储在浏览器中的cookie
            if cart_json is not None:
                cart=json.loads(cart_json)    # 把原来的转成字典
            else:
                cart={}
            # 判断cookies是否包含传来的商品shu_id如果有则求和,没有则增加
            if sku_id in cart:   # 判断传过来的商品id是在以前的cookies中有,有则叠加
                origin_count= cart[sku_id]
                count += origin_count

            # 把最新的商品数量设置到字典中
            cart[sku_id]=count
            new_cart_json = json.dumps(cart)

            # 统计购物车中的商品总数
            cart_num = 0
            for val in cart.values():
                cart_num += val

            response = JsonResponse({"code": 0, "message": "添加购物车成功", "cart_num": cart_num})
            # 设置最新的cookie信息

            response.set_cookie('cart',new_cart_json)
            return response
View Code

 完整的代码:

class AddCartView(View):
    """添加购物车"""
    def post(self, request):
        # 判断用户是否登录
        # if not request.user.is_authenticated():
        #     # 表示用户未登录
        #     return JsonResponse({"code": 1, "message": "用户未登录"})

        # sku_id 商品id
        # count 商品数量
        # 接受参数
        sku_id = request.POST.get("sku_id")
        count = request.POST.get("count")

        # 校验参数
        if not all([sku_id, count]):
            return JsonResponse({"code": 2, "message": "参数不完整"})

        # 判断商品存在与否
        try:
            sku = GoodsSKU.objects.get(id=sku_id)
        except GoodsSKU.DoesNotExist:
            # 表示商品不存在
            return JsonResponse({"code": 3, "message": "商品不存在"})

        # 判断用户请求的count是不是整数
        try:
            count = int(count)
        except Exception:
            # 数量不是整数
            return JsonResponse({"code": 4, "message": "参数错误"})

        # 判断数量有没有超过库存
        if count > sku.stock:
            return JsonResponse({"code": 5, "message": "库存不足"})
        if request.user.is_authenticated():
            # 业务逻辑处理,将数据保存到redis中
            # {"sku_1": 10, "sku_2": 20}
            redis_conn = get_redis_connection("default")

            # 先从redis中尝试获取用户原有购物车中相同商品的信息
            user_id = request.user.id
            origin_count = redis_conn.hget("cart_%s" % user_id, sku_id)

            # 如果redis中不存在,则直接将数据保存到redis的购物车中
            # 如果redis中原本包含了这个商品的数量信息, 进行数量累加,在保存到redis中
            if origin_count is not None:
                count += int(origin_count)
            # 在把新的购物信息设置会 redis中
            redis_conn.hset("cart_%s" % user_id, sku_id, count)
            cart_num = 0
            # 取出所有的购物车的数量返回给前端
            cart =redis_conn.hgetall('cart_%s' %user_id)
            for val in cart.values():
                cart_num +=int(val)
            # 通过返回json数据,告知前端处理的结果
            return JsonResponse({"code": 0, "message": "添加购物车成功",'cart_num':cart_num})
        else:
            # 如果用户未登录,则将数据保存到cookie中
            # 先获取用户的cookie中的购物车数据
            cart_json = request.COOKIES.get("cart") # 取原来存储在浏览器中的cookie
            if cart_json is not None:
                cart=json.loads(cart_json)    # 把原来的转成字典
            else:
                cart={}
            # 判断cookies是否包含传来的商品shu_id如果有则求和,没有则增加
            if sku_id in cart:   # 判断传过来的商品id是在以前的cookies中有,有则叠加
                origin_count= cart[sku_id]
                count += origin_count

            # 把最新的商品数量设置到字典中
            cart[sku_id]=count
            new_cart_json = json.dumps(cart)

            # 统计购物车中的商品总数
            cart_num = 0
            for val in cart.values():
                cart_num += val

            response = JsonResponse({"code": 0, "message": "添加购物车成功", "cart_num": cart_num})
            # 设置最新的cookie信息

            response.set_cookie('cart',new_cart_json)
            return response
View Code

 点击添加购物车后用户的cookie信息:

 ========================================

在goods模块中写购物车模块的基类,让用到显示购物车数量的模块都去继承这个基类,

显示购物车的数量可以直接调用 cart_num = self.get_cart_num(request)

 基类

import json

# Create your views here.


class BaseCartView(View):
    """提供统计购物车数据的功能"""
    def get_cart_num(self, request):
        cart_num = 0
        # 如果用户登录,从redis中获取用户的购物车数据
        if request.user.is_authenticated():
            # 从redis中取购物车数据
            redis_conn = get_redis_connection("default")
            # 返回一个字典数据
            cart = redis_conn.hgetall("cart_%s" % request.user.id)
            # cart = {"sku_1": "10", "sku_2": "11"}
            for value in cart.values():
                cart_num += int(value)
        else:
            # 用户未登录,从cookie中获取购物车数据
            cart_json = request.COOKIES.get("cart")
            if cart_json is not None:
                cart = json.loads(cart_json)   # 购物车的字典数据
            else:
                cart = {}
            # 遍历购物车的字典数据,统计求和
            for val in cart.values():
                cart_num += val

        return cart_num
View Code

各个类调用如下:

主页未登录也能通过cookie显示购物车的数量

2 实现查询购物车的功能(从redis或cookie中获取数据)

前端显示的页面如下图所示:

根据前端页面后端要接受的参数如商品的sku_id和商品的数量,和用户的信息

 后端视图函数的业务逻辑如下:

1 获取数据

2 如果用户未登录,从cookie中获取数据

3 如果用户已登陆, 从redis中获取数据

4 处理模板

业务逻辑

后端根据购物车的数据,通过查询数据库挨个遍历要给前端传送的数据有每个商品的对象(商品sku里面有图片,name,单位,价格,)商品的数量可以通过从redis或cookie中获取,把它当成一个商品的属性添加到商品的对象中,

小计可以通过商品的价格和商品的数量想乘后的结果添加到对象的属性中,最后总共有多少商品和价格可以选择两个变量在每次遍历商品的时候叠加操作。

查询购物车信息的视图函数(CartInfoView)代码如下

class CartInfoView(View):
    def get(self, request):

        # 1 用户登陆的时候,从redis中获取数据
        # 2 用户未登录的时候,从cookie中获取数据
        if request.user.is_authenticated():
            user_id=request.user.id
            redis_con = get_redis_connection('default')
            cart = redis_con.hgetall('cart_%s' % user_id)
        else:
            cart_json = request.COOKIES.get('cart')
            if cart_json is not None:
                cart = json.loads(cart_json)
            else:
                cart={}
        # 遍历cart字典购物车,从mysql数据中查询商品信息
        skus = []
        total_count = 0  # 商品总数
        total_amount = 0  # 商品总金额
        for sku_id,count in cart.items():
            try:
                sku = GoodsSKU.objects.get(id=sku_id)
            except GoodsSKU.DoesNotExist:
                continue
            count = int(count)
            sku.count=count
            skus.append(sku)
            amount = sku.price * count  # price字段是DecimalField, 在python中是Decimal数据类型
            sku.amount = amount
            total_amount +=amount
            total_count +=count

        context = {
            "skus": skus,
            "total_count": total_count,
            "total_amount": total_amount
        }

        return render(request, 'cart.html', context)
View Code

配置url的路径:

  url(r"^$", views.CartInfoView.as_view(), name="info"),

前端的渲染效果如下:

3 实现用户登陆的时候浏览器中购物车中的cookie和redis中购物车的数据进行合并

1 初步实现把浏览器中购物车的cookie同步到redis中(这个合并会把以前的redis中的数据清除,保存最新的

在用户登陆成功后业务逻辑处理如下:

 1 在用户登陆成功后先获取浏览器中购物车cookie的数据

 2 在获取redis中购物车的数据

 3 把浏览器中的cookie 同步到redis中

 4 在redis中设置最新的购物车数据

 5 清除浏览器中的cookie数据

在用户应用下登陆的视图函数中添加和修改如下的代码:

        # 获取redis中购物车的数据
        redis_con=get_redis_connection('default')
        redis_cart=redis_con.hgetall('cart_%s' % user.id)
        redis_cart.update(cart_cookie)
        redis_con.hmset('cart_%s' %user.id,redis_cart)
        # 判断页面的url中是否有next参数如果有,返回到原来的路径
        next = request.GET.get('next')
        if next:
            response=redirect(next)
        else:
            # 登录成功,跳转到主页
            response=redirect(reverse("goods:index"))

        # 清楚浏览器中的cookie数据
        response.delete_cookie('cart')
        return response

 登陆完整的视图函数代码如下:

class LoginView(View):
    """登录"""
    def get(self, request):
        """提供登录页面"""
        return render(request, "login.html",{})

    def post(self, request):
        """处理登录的数据"""
        # 获取参数
        user_name = request.POST.get("username")
        password = request.POST.get("pwd")
        remebered = request.POST.get('remembered')
        # 参数校验
        if not all([user_name, password]):
            # 参数不完整
            return render(request, "login.html")

        # 登录业务逻辑处理
        # try:
        #     password = sha256(password)
        #     User.objects.get(username=user_name, password=password)
        # except User.DoesNotExist:
        #     return HttpResponse("用户名或密码错误")

        # 使用django的认证系统进行用户密码的校验
        user = authenticate(username=user_name, password=password)
        if user is None:
            # 用户的登录信息有误
            return render(request, "login.html", {"errmsg": "用户名或密码错误!"})

        # 判断用户的激活状态
        if user.is_active is False:
            return render(request, "login.html", {"errmsg": "用户尚未激活!"})

        # 保存用户的登录状态
        # 使用django的login函数保存用户的session数据
        login(request, user)
        # 判断用户有没有勾选记住用户名,勾选的话值为on
        if remebered != 'on':
            request.session.set_expiry(0) # 设置为临时的会话
        else:

            request.session.set_expiry(None)
        # 获取cookie中购物车的数据
        cart_json = request.COOKIES.get('cart')
        if cart_json is not None:
            cart_cookie=json.loads(cart_json)
        else:
            cart_cookie= {}

        # 获取redis中购物车的数据
        redis_con=get_redis_connection('default')
        redis_cart=redis_con.hgetall('cart_%s' % user.id)
        redis_cart.update(cart_cookie)
        redis_con.hmset('cart_%s' %user.id,redis_cart)
        # 判断页面的url中是否有next参数如果有,返回到原来的路径
        next = request.GET.get('next')
        if next:
            response=redirect(next)
        else:
            # 登录成功,跳转到主页
            response=redirect(reverse("goods:index"))

        # 清楚浏览器中的cookie数据
        response.delete_cookie('cart')
        return response
View Code

 用户登陆成功后浏览器中的cookie被清除了,页面的效果如下:

2  在上面1的基础上稍微修改实现把数据库reedis和浏览器cookie中的购物车数据进行累加求和

业务逻辑处理如下

 1 分别取出在浏览器和数据库中的购物车数据后,

 2 遍历cookie中的购物车信息通过cookie中的键判断商品在不在redis中在的话叠加,不在的话添加进去

注意的点

sku_id 从cookie取出时str类型,count是整形,redis中的sku_id和count都是是bytes类型,
1 那cookie中的sku_id在redis中查询要转换成bytes类型
2 把redis中的count和浏览器中cookie中的count想加要把redis中的count转换成int()
    for sku_id,count in cart_cookie.items():
            # sku_id 从cookie取出时str类型,redis中是bytes类型
            sku_id=sku_id.encode()
            if sku_id in redis_cart:
                origin_count = redis_cart[sku_id]
                count += int(origin_count)
            redis_cart[sku_id]=count

        redis_con.hmset('cart_%s' %user.id,redis_cart)
 完整的代码如下:
class LoginView(View):
    """登录"""
    def get(self, request):
        """提供登录页面"""
        return render(request, "login.html",{})

    def post(self, request):
        """处理登录的数据"""
        # 获取参数
        user_name = request.POST.get("username")
        password = request.POST.get("pwd")
        remebered = request.POST.get('remembered')
        # 参数校验
        if not all([user_name, password]):
            # 参数不完整
            return render(request, "login.html")

        # 登录业务逻辑处理
        # try:
        #     password = sha256(password)
        #     User.objects.get(username=user_name, password=password)
        # except User.DoesNotExist:
        #     return HttpResponse("用户名或密码错误")

        # 使用django的认证系统进行用户密码的校验
        user = authenticate(username=user_name, password=password)
        if user is None:
            # 用户的登录信息有误
            return render(request, "login.html", {"errmsg": "用户名或密码错误!"})

        # 判断用户的激活状态
        if user.is_active is False:
            return render(request, "login.html", {"errmsg": "用户尚未激活!"})

        # 保存用户的登录状态
        # 使用django的login函数保存用户的session数据
        login(request, user)
        # 判断用户有没有勾选记住用户名,勾选的话值为on
        if remebered != 'on':
            request.session.set_expiry(0) # 设置为临时的会话
        else:

            request.session.set_expiry(None)
        # 获取cookie中购物车的数据
        cart_json = request.COOKIES.get('cart')
        if cart_json is not None:
            cart_cookie=json.loads(cart_json)
        else:
            cart_cookie= {}

        # 获取redis中购物车的数据
        redis_con=get_redis_connection('default')
        redis_cart=redis_con.hgetall('cart_%s' % user.id)
        # 便利浏览器中的cookie数据
        for sku_id,count in cart_cookie.items():
            # sku_id 从cookie取出时str类型,redis中是bytes类型
            sku_id=sku_id.encode()
            if sku_id in redis_cart:
                origin_count = redis_cart[sku_id]
                count += int(origin_count)
            redis_cart[sku_id]=count

        redis_con.hmset('cart_%s' %user.id,redis_cart)
        # 判断页面的url中是否有next参数如果有,返回到原来的路径
        next = request.GET.get('next')
        if next:
            response=redirect(next)
        else:
            # 登录成功,跳转到主页
            response=redirect(reverse("goods:index"))

        # 清楚浏览器中的cookie数据
        response.delete_cookie('cart')
        return response
View Code
效果如下:
1 用户登陆后查看购物车中草莓的数量为6:

2 用退出后添加一个草莓到购物车:

 

3 用户登陆后查看合并后想加的数据:

 

4 实现用户在购物车的页面进行修改的操作(增减)

用户操作的前端页面如下所示:

前端页面分析:

  当用户通过增加和减少的按钮或者直接在input框里面修改数值时,会通过ajax中的post请求的方式发送修改后的商品sku_id和数量交给后端进行判断

  前端需要通过ajax给后端传送的数据有sku_id和商品的数量count

后端的业务逻辑处理如下:

  1 接受前端通过post传送的数据(sku_id,count)

  2 参数校验

    判断商品是否存在

    count是否是整数

    判断库存是否充足

  3 业务处理,保存数据

    如果用户已登录保存在redis中

    如果用户未登录保存在cookie中

  4 返回json的响应数据

 修改的视图处理如下:

class UpdateCartView(View):
    """更新购物车数据"""
    def post(self, request):
        # 获取数据
        sku_id = request.POST.get("sku_id")  # 商品id
        count = request.POST.get("count")  # 修改之后的数量

        # 检查数据
        # 判断商品是否存在
        try:
            sku = GoodsSKU.objects.get(id=sku_id)
        except GoodsSKU.DoesNotExist:
            return JsonResponse({"code": 1, "message": "商品不存在"})

        # count是否是整数
        try:
            count = int(count)
        except Exception:
            return JsonResponse({"code": 2, "message": "数量参数有问题"})

        # 判断库存
        if count > sku.stock:
            return JsonResponse({"code": 3, "message": "库存不足"})

        # 业务处理,保存数据
        if not request.user.is_authenticated():
            # 如果用户未登录,保存数据到cookie中
            cart_json = request.COOKIES.get("cart")
            if cart_json is not None:
                cart = json.loads(cart_json)
            else:
                cart = {}

            cart[sku_id] = count

            response = JsonResponse({"code": 0, "message": "修改成功"})
            response.set_cookie("cart", json.dumps(cart))
            return response
        else:
            # 如果用户已登录,保存数据到redis中
            redis_conn = get_redis_connection("default")
            user_id = request.user.id
            # cart = redis_conn.hgetall("cart_%s" % user_id)
            # # 将sku_id转换为bytes,对redis返回的字典cart进行操作
            # sku_id = sku_id.encode()
            # cart[sku_id] = count
            redis_conn.hset("cart_%s" % user_id, sku_id, count)
            # 返回结果, 返回Json数据
            return JsonResponse({"code": 0, "message": "修改成功"})
View Code

5 实现用户可以删除商品的信息

 当用户点击删除的操作后,购物车中的数据会移除,需要给前端传送的参数只有一个(商品的sku_id)

后端视图函数的业务逻辑处理如下:

1 获取参数

2 参数校验

3 业务处理删除购物车数据

4 返回处理的结果给前端

 删除的视图处理函数如下:

class DeleteCartView(View):
    def post(self, request):
        # 接受参数
        sku_id = request.POST.get('sku_id')
        # 参数校验
        if not sku_id:
            return JsonResponse({'code':1,'message':'参数不完整'})

        # 根据sku_id把对应的商品删除
        if not request.user.is_authenticated():
            # 如果用户未登录,从cookie中删除数据
            cart_json = request.COOKIES.get('cart')
            if cart_json is not None:
                cart = json.loads(cart_json)
                if sku_id in cart:
                    del cart[sku_id]

                response = JsonResponse({'code':0, 'message':'删除成功'})
                response.set_cookie('cart',json.dumps(cart))
                return response
            else:
                return JsonResponse({'code':0 ,'message': '删除成功'})

        else:
            # 如果用户登陆,从redis中删除数据
            redis_con = get_redis_connection('default')
            user_id = request.user.id
            redis_con.hdel('cart_%s' % user_id, sku_id)
            return JsonResponse({'code':0,'message': '删除成功'})
View Code

 总共有4件商品

 当用户点击删除时

 前端的代码如下:

{% extends 'base.html' %}

{% block title %}天天生鲜-购物车{% endblock %}
{% load staticfiles %}

{% block search_bar %}
    <div class="search_bar clearfix">
        <a href="{% url 'goods:index' %}" class="logo fl"><img src="{% static 'images/logo.png' %}"></a>
        <div class="sub_page_name fl">|&nbsp;&nbsp;&nbsp;&nbsp;购物车</div>
        <div class="search_con fr">
            <form action="/search/" method="get">
            <input type="text" class="input_text fl" name="q" placeholder="搜索商品">
            <input type="submit" class="input_btn fr" value="搜索">
            </form>
        </div>
    </div>
{% endblock %}

{% block body %}
    <div class="total_count">全部商品<em>{{total_count}}</em></div>
    <ul class="cart_list_th clearfix">
        <li class="col01">商品名称</li>
        <li class="col02">商品单位</li>
        <li class="col03">商品价格</li>
        <li class="col04">数量</li>
        <li class="col05">小计</li>
        <li class="col06">操作</li>
    </ul>
    <form method="post" action="#">
    {% csrf_token %}
    {% for sku in skus %}
    <ul class="cart_list_td clearfix" sku_id="{{ sku.id }}">
        <li class="col01"><input type="checkbox" name="sku_ids" value="{{ sku.id }}" checked></li>
        <li class="col02"><img src="{{ sku.default_image.url }}"></li>
        <li class="col03">{{ sku.name }}<br><em>{{ sku.price }}/{{ sku.unit}}</em></li>
        <li class="col04">{{ sku.unit }}</li>
        <li class="col05"><span>{{sku.price}}</span></li>
        <li class="col06">
            <div class="num_add">
                <a href="javascript:;" class="add fl">+</a>
                <input type="text" class="num_show fl" sku_id="{{ sku.id }}" value="{{sku.count}}">
                <a href="javascript:;" class="minus fl">-</a>
            </div>
        </li>
        <li class="col07"><span>{{sku.amount}}</span></li>
        <li class="col08"><a href="javascript:;" class="del_btn">删除</a></li>
    </ul>
    {% endfor %}
    <ul class="settlements">
        <li class="col01"><input type="checkbox" checked></li>
        <li class="col02">全选</li>
        <li class="col03">合计(不含运费):<span>¥</span><em id="total_amount">{{total_amount}}</em><br>共计<b id="total_count">{{total_count}}</b>件商品</li>
        <li class="col04"><input type="submit" id="commit_btn">去结算</input></li>
    </ul>
    </form>

{% endblock %}

{% block bottom_files %}
    <script type="text/javascript" src="{% static 'js/jquery-1.12.2.js' %}"></script>
    <script type="text/javascript">
        // 更新页面合计信息
        function freshOrderCommitInfo() {
            var total_amount = 0;
            var total_count = 0;
            $('.cart_list_td').find(':checked').parents('ul').each(function () {
                var sku_amount = $(this).children('li.col07').text();  // 商品的金额
                var sku_count = $(this).find('.num_show').val()  // 商品的数量
                total_count += parseInt(sku_count);
                total_amount += parseFloat(sku_amount);
            });
            // 设置商品的总数和总价
            $("#total_amount").text(total_amount.toFixed(2));
            $("#total_count").text(total_count);
        }

        // 更新页面顶端全部商品数量
        function freshTotalGoodsCount() {
            var total_count = 0;
            $('.cart_list_td').find(':checked').parents('ul').each(function () {
                var sku_count = $(this).find('.num_show').val();
                total_count += parseInt(sku_count);
            });
            $(".total_count>em").text(total_count);
        }

        // 更新后端购物车信息
        function updateRemoteCartInfo(sku_id, sku_count, num_dom) {
            // 发送给后端的数据
            var req = {
                sku_id: sku_id,
                count: sku_count,
                csrfmiddlewaretoken: "{{ csrf_token }}"
            };
            $.post("/cart/update", req, function(data){
                if (0 == data.code) {
                    // 更新商品数量
                    $(num_dom).val(sku_count);
                    // 更新商品金额信息
                    var sku_price = $(".cart_list_td[sku_id="+sku_id+"]").children('li.col05').children().text();
                    var sku_amount = parseFloat(sku_price) * sku_count;
                    $(".cart_list_td[sku_id="+sku_id+"]").children('li.col07').children().text(sku_amount.toFixed(2));
                    // 更新顶部商品总数
                    freshTotalGoodsCount();
                    // 更新底部合计信息
                    freshOrderCommitInfo();
                } else {
                    alert(data.message);
                }
            });
        }

        // 增加
        $(".add").click(function(){
            // 获取操作的商品id
            var sku_id = $(this).next().attr("sku_id");
            // 获取加操作前的的数量
            var sku_num = $(this).next().val();
            // 进行数量加1
            sku_num = parseInt(sku_num);
            sku_num += 1;

            // 显示商品数目的dom
            var num_dom = $(this).next();
            // 更新购物车数量
            updateRemoteCartInfo(sku_id, sku_num, num_dom);
        });

        // 减少
        $(".minus").click(function(){
            // 获取操作的商品id
            var sku_id = $(this).prev().attr("sku_id");
            // 获取加操作前的的数量
            var sku_num = $(this).prev().val();
            // 进行数量加1
            sku_num = parseInt(sku_num);
            sku_num -= 1;
            if (sku_num < 1) sku_num = 1;
            // 更新页面显示数量
            var num_dom = $(this).prev();
            // 更新购物车数量
            updateRemoteCartInfo(sku_id, sku_num, num_dom);
        });

        var pre_sku_count = 0;
        $('.num_show').focus(function () {
            // 记录用户手动输入之前商品数目
            pre_sku_count = $(this).val();
        });
        // 手动输入
        $(".num_show").blur(function(){
            var sku_id = $(this).attr("sku_id");
            var sku_num = $(this).val();
            // 如果输入的数据不合理,则将输入值设置为在手动输入前记录的商品数目
            if (isNaN(sku_num) || sku_num.trim().length<=0 || parseInt(sku_num)<=0) {
                $(this).val(pre_sku_count);
                return;
            }
            sku_num = parseInt(sku_num);
            var num_dom = $(this);
            updateRemoteCartInfo(sku_id, sku_num, num_dom);
        });

        // 删除
        $(".del_btn").click(function(){
            var sku_id = $(this).parents("ul").attr("sku_id");
            var req = {
                sku_id: sku_id,
                csrfmiddlewaretoken: "{{ csrf_token }}"
            };
            $.post('/cart/delete', req, function(data){
{#                window.reload()#}
                location.href="/cart/";  // 删除后,刷新页面
            });
        });

        // 商品对应checkbox发生改变时,全选checkbox发生改变
        $('.cart_list_td').find(':checkbox').change(function () {
            // 获取商品所有checkbox的数目
            var all_len = $('.cart_list_td').find(':checkbox').length;
            // 获取选中商品的checkbox的数目
            var checked_len = $('.cart_list_td').find(':checked').length;

            if (checked_len < all_len){
                // 有商品没有被选中
                $('.settlements').find(':checkbox').prop('checked', false)
            }
            else{
                // 所有商品都被选中
                $('.settlements').find(':checkbox').prop('checked', true)
            }
            freshOrderCommitInfo();
        });

        // 全选和全不选
        $('.settlements').find(':checkbox').change(function () {
            // 1.获取当前checkbox的选中状态
            var is_checked = $(this).prop('checked');
            // 2.遍历并设置商品ul中checkbox的选中状态
            $('.cart_list_td').find(':checkbox').each(function () {
                // 设置每一个goods ul中checkbox的值
                $(this).prop('checked', is_checked)
            });
            freshOrderCommitInfo();
        });

    </script>
{% endblock %}
View Code
 
 
 

1 实现添加购物车的功能

前端的页面如下:

前端的页面分析:

要实现添加购物车的功能,前端要向后端传送的数据有商品的id和添加的数量,并且后端规定以post的方式发送给后端.

后端视图函数的业务逻辑

1 判断用户是否登陆,没登陆返回用户未登录

2 接受前端传来的参数

3 对参数进行校验,判断是否有空的参数,有为空的参数返回参数不完整

4 对传过来的商品id进行数据库查询,看数据库中是否有这个商品存在,没有返回商品不存在

5 对参数添加购物车的数量的类型判断,如果不是整数,返回参数错误

6 判断是否超过库存的数量,返回超过库存不足

7  业务逻辑处理,将购物车数据保存到redis中 

如果redis中不存在,则直接将数据保存到redis的购物车中

如果redis中原本包含了这个商品的数量信息, 进行数量累加,在保存到redis中 通过返回json数据,告知前端处理的结果

8 将最新的购物车信息设置到redis中

=======================================================

前端的post请求:

 window.location.href = "/users/login";  js中的重定向

post请求必须带  csrfmiddlewaretoken: "{{ csrf_token }}" 否则回报403访问被禁止的错误

 前端post请求的代码如下:

$('#add_cart').click(function(){
		    var req_data = {
		       sku_id:  $(this).attr("sku_id"),
                count: $("#num_show").val(),
                csrfmiddlewaretoken: "{{ csrf_token }}"
            };
		    // 向后端发送添加购物车的请求
		    $.post("/cart/add", req_data, function (resp_data) {
                if (1 == resp_data.code) {
                    // 后端表示用户未登录
                    window.location.href = "/users/login";
                } else if (0 == resp_data.code) {
                    // 添加成功
                    $(".add_jump").stop().animate({
                    'left': $to_y+7,
                    'top': $to_x+7},
                    "fast", function() {
					$(".add_jump").fadeOut('fast',function(){
						$('#show_count').html(resp_data.cart_num);
					});

			});
                } else {
                    alert(resp_data.message);
                }
            }, "json")


		});

请求处理的视图函数:

# /cart/add
class AddCartView(View):
    """添加购物车"""
    def post(self, request):
        # 判断用户是否登录
        if not request.user.is_authenticated():
            # 表示用户未登录
            return JsonResponse({"code": 1, "message": "用户未登录"})

        # sku_id 商品id
        # count 商品数量
        # 接受参数
        sku_id = request.POST.get("sku_id")
        count = request.POST.get("count")

        # 校验参数
        if not all([sku_id, count]):
            return JsonResponse({"code": 2, "message": "参数不完整"})

        # 判断商品存在与否
        try:
            sku = GoodsSKU.objects.get(id=sku_id)
        except GoodsSKU.DoesNotExist:
            # 表示商品不存在
            return JsonResponse({"code": 3, "message": "商品不存在"})

        # 判断用户请求的count是不是整数
        try:
            count = int(count)
        except Exception:
            # 数量不是整数
            return JsonResponse({"code": 4, "message": "参数错误"})

        # 判断数量有没有超过库存
        if count > sku.stock:
            return JsonResponse({"code": 5, "message": "库存不足"})

        # 业务逻辑处理,将数据保存到redis中
        # {"sku_1": 10, "sku_2": 20}
        redis_conn = get_redis_connection("default")

        # 先从redis中尝试获取用户原有购物车中相同商品的信息
        user_id = request.user.id
        origin_count = redis_conn.hget("cart_%s" % user_id, sku_id)

        # 如果redis中不存在,则直接将数据保存到redis的购物车中
        # 如果redis中原本包含了这个商品的数量信息, 进行数量累加,在保存到redis中
        if origin_count is not None:
            count += int(origin_count)
        # 在把新的购物信息设置会 redis中
        redis_conn.hset("cart_%s" % user_id, sku_id, count)
        cart_num = 0
        cart =redis_conn.hgetall('cart_%s' %user_id)
        for val in cart.values():
            cart_num +=int(val)


        # 通过返回json数据,告知前端处理的结果
        return JsonResponse({"code": 0, "message": "添加购物车成功",'cart_num':cart_num})
View Code

url的根级的请求路径:

  url(r'^cart/', include(cart.urls, namespace="cart")),

在cart应用中的uri的请求路径

from cart import views

urlpatterns = [
    url(r"^add$", views.AddCartView.as_view(), name="add"),
]

 添加购物车成功后前端的页面如下;

 redis中的购物车数量如下:

 ===========================================================================

对上面的添加购物车进行升级(增加用户未登录也能添加购物车的功能)

 如果用户未登录,则购物车数据保存在用户的cookie中(用的浏览器中)

把用户的购物车数据以json字符串的方式保存在cookie中(cookie只能保存字符串)  “cart”: ‘{   “sku_1“:10, “sku_2”: 11  }’

如果用户登录,则购物车数据是保存在后端的redis中,用户在登录的时候,将cookie中的购物车数据与redis中的数据进行合并,合并后保存在redis中

json模块

将python的字典类型数据转换为json字符串的方法  json.dumps(字典数据)  {‘a’:1} ---->  '{'a':1}'

将json字符串转换为python的字典类型数据的方法   json.loads(json字符串) '{'a':1}' ---> {'a':1}

 业务逻辑的分析:

 1 如果用户未登录,则将数据保存到cookie中

 2 先获取用户的cookie中的购物车数据

 3  取 出原来存储在浏览器中的cookie,把原来的转成字典

4  判断cookies是否包含传来的商品shu_id如果有则求和,没有则增加

5 把最新的商品数量设置到字典中

6  统计购物车中的商品总数

7 构建response对象

8 设置最新的cookie信息

添加用户未登录实现添加购物车的视图函数

 用户未登录主要的代码

cart_json = request.COOKIES.get("cart") # 取原来存储在浏览器中的cookie
            if cart_json is not None:
                cart=json.loads(cart_json)    # 把原来的转成字典
            else:
                cart={}
            # 判断cookies是否包含传来的商品shu_id如果有则求和,没有则增加
            if sku_id in cart:   # 判断传过来的商品id是在以前的cookies中有,有则叠加
                origin_count= cart[sku_id]
                count += origin_count

            # 把最新的商品数量设置到字典中
            cart[sku_id]=count
            new_cart_json = json.dumps(cart)

            # 统计购物车中的商品总数
            cart_num = 0
            for val in cart.values():
                cart_num += val

            response = JsonResponse({"code": 0, "message": "添加购物车成功", "cart_num": cart_num})
            # 设置最新的cookie信息

            response.set_cookie('cart',new_cart_json)
            return response
View Code

 完整的代码:

class AddCartView(View):
    """添加购物车"""
    def post(self, request):
        # 判断用户是否登录
        # if not request.user.is_authenticated():
        #     # 表示用户未登录
        #     return JsonResponse({"code": 1, "message": "用户未登录"})

        # sku_id 商品id
        # count 商品数量
        # 接受参数
        sku_id = request.POST.get("sku_id")
        count = request.POST.get("count")

        # 校验参数
        if not all([sku_id, count]):
            return JsonResponse({"code": 2, "message": "参数不完整"})

        # 判断商品存在与否
        try:
            sku = GoodsSKU.objects.get(id=sku_id)
        except GoodsSKU.DoesNotExist:
            # 表示商品不存在
            return JsonResponse({"code": 3, "message": "商品不存在"})

        # 判断用户请求的count是不是整数
        try:
            count = int(count)
        except Exception:
            # 数量不是整数
            return JsonResponse({"code": 4, "message": "参数错误"})

        # 判断数量有没有超过库存
        if count > sku.stock:
            return JsonResponse({"code": 5, "message": "库存不足"})
        if request.user.is_authenticated():
            # 业务逻辑处理,将数据保存到redis中
            # {"sku_1": 10, "sku_2": 20}
            redis_conn = get_redis_connection("default")

            # 先从redis中尝试获取用户原有购物车中相同商品的信息
            user_id = request.user.id
            origin_count = redis_conn.hget("cart_%s" % user_id, sku_id)

            # 如果redis中不存在,则直接将数据保存到redis的购物车中
            # 如果redis中原本包含了这个商品的数量信息, 进行数量累加,在保存到redis中
            if origin_count is not None:
                count += int(origin_count)
            # 在把新的购物信息设置会 redis中
            redis_conn.hset("cart_%s" % user_id, sku_id, count)
            cart_num = 0
            # 取出所有的购物车的数量返回给前端
            cart =redis_conn.hgetall('cart_%s' %user_id)
            for val in cart.values():
                cart_num +=int(val)
            # 通过返回json数据,告知前端处理的结果
            return JsonResponse({"code": 0, "message": "添加购物车成功",'cart_num':cart_num})
        else:
            # 如果用户未登录,则将数据保存到cookie中
            # 先获取用户的cookie中的购物车数据
            cart_json = request.COOKIES.get("cart") # 取原来存储在浏览器中的cookie
            if cart_json is not None:
                cart=json.loads(cart_json)    # 把原来的转成字典
            else:
                cart={}
            # 判断cookies是否包含传来的商品shu_id如果有则求和,没有则增加
            if sku_id in cart:   # 判断传过来的商品id是在以前的cookies中有,有则叠加
                origin_count= cart[sku_id]
                count += origin_count

            # 把最新的商品数量设置到字典中
            cart[sku_id]=count
            new_cart_json = json.dumps(cart)

            # 统计购物车中的商品总数
            cart_num = 0
            for val in cart.values():
                cart_num += val

            response = JsonResponse({"code": 0, "message": "添加购物车成功", "cart_num": cart_num})
            # 设置最新的cookie信息

            response.set_cookie('cart',new_cart_json)
            return response
View Code

 点击添加购物车后用户的cookie信息:

 ========================================

在goods模块中写购物车模块的基类,让用到显示购物车数量的模块都去继承这个基类,

显示购物车的数量可以直接调用 cart_num = self.get_cart_num(request)

 基类

import json

# Create your views here.


class BaseCartView(View):
    """提供统计购物车数据的功能"""
    def get_cart_num(self, request):
        cart_num = 0
        # 如果用户登录,从redis中获取用户的购物车数据
        if request.user.is_authenticated():
            # 从redis中取购物车数据
            redis_conn = get_redis_connection("default")
            # 返回一个字典数据
            cart = redis_conn.hgetall("cart_%s" % request.user.id)
            # cart = {"sku_1": "10", "sku_2": "11"}
            for value in cart.values():
                cart_num += int(value)
        else:
            # 用户未登录,从cookie中获取购物车数据
            cart_json = request.COOKIES.get("cart")
            if cart_json is not None:
                cart = json.loads(cart_json)   # 购物车的字典数据
            else:
                cart = {}
            # 遍历购物车的字典数据,统计求和
            for val in cart.values():
                cart_num += val

        return cart_num
View Code

各个类调用如下:

主页未登录也能通过cookie显示购物车的数量

2 实现查询购物车的功能(从redis或cookie中获取数据)

前端显示的页面如下图所示:

根据前端页面后端要接受的参数如商品的sku_id和商品的数量,和用户的信息

 后端视图函数的业务逻辑如下:

1 获取数据

2 如果用户未登录,从cookie中获取数据

3 如果用户已登陆, 从redis中获取数据

4 处理模板

业务逻辑

后端根据购物车的数据,通过查询数据库挨个遍历要给前端传送的数据有每个商品的对象(商品sku里面有图片,name,单位,价格,)商品的数量可以通过从redis或cookie中获取,把它当成一个商品的属性添加到商品的对象中,

小计可以通过商品的价格和商品的数量想乘后的结果添加到对象的属性中,最后总共有多少商品和价格可以选择两个变量在每次遍历商品的时候叠加操作。

查询购物车信息的视图函数(CartInfoView)代码如下

class CartInfoView(View):
    def get(self, request):

        # 1 用户登陆的时候,从redis中获取数据
        # 2 用户未登录的时候,从cookie中获取数据
        if request.user.is_authenticated():
            user_id=request.user.id
            redis_con = get_redis_connection('default')
            cart = redis_con.hgetall('cart_%s' % user_id)
        else:
            cart_json = request.COOKIES.get('cart')
            if cart_json is not None:
                cart = json.loads(cart_json)
            else:
                cart={}
        # 遍历cart字典购物车,从mysql数据中查询商品信息
        skus = []
        total_count = 0  # 商品总数
        total_amount = 0  # 商品总金额
        for sku_id,count in cart.items():
            try:
                sku = GoodsSKU.objects.get(id=sku_id)
            except GoodsSKU.DoesNotExist:
                continue
            count = int(count)
            sku.count=count
            skus.append(sku)
            amount = sku.price * count  # price字段是DecimalField, 在python中是Decimal数据类型
            sku.amount = amount
            total_amount +=amount
            total_count +=count

        context = {
            "skus": skus,
            "total_count": total_count,
            "total_amount": total_amount
        }

        return render(request, 'cart.html', context)
View Code

配置url的路径:

  url(r"^$", views.CartInfoView.as_view(), name="info"),

前端的渲染效果如下:

3 实现用户登陆的时候浏览器中购物车中的cookie和redis中购物车的数据进行合并

1 初步实现把浏览器中购物车的cookie同步到redis中(这个合并会把以前的redis中的数据清除,保存最新的

在用户登陆成功后业务逻辑处理如下:

 1 在用户登陆成功后先获取浏览器中购物车cookie的数据

 2 在获取redis中购物车的数据

 3 把浏览器中的cookie 同步到redis中

 4 在redis中设置最新的购物车数据

 5 清除浏览器中的cookie数据

在用户应用下登陆的视图函数中添加和修改如下的代码:

        # 获取redis中购物车的数据
        redis_con=get_redis_connection('default')
        redis_cart=redis_con.hgetall('cart_%s' % user.id)
        redis_cart.update(cart_cookie)
        redis_con.hmset('cart_%s' %user.id,redis_cart)
        # 判断页面的url中是否有next参数如果有,返回到原来的路径
        next = request.GET.get('next')
        if next:
            response=redirect(next)
        else:
            # 登录成功,跳转到主页
            response=redirect(reverse("goods:index"))

        # 清楚浏览器中的cookie数据
        response.delete_cookie('cart')
        return response

 登陆完整的视图函数代码如下:

class LoginView(View):
    """登录"""
    def get(self, request):
        """提供登录页面"""
        return render(request, "login.html",{})

    def post(self, request):
        """处理登录的数据"""
        # 获取参数
        user_name = request.POST.get("username")
        password = request.POST.get("pwd")
        remebered = request.POST.get('remembered')
        # 参数校验
        if not all([user_name, password]):
            # 参数不完整
            return render(request, "login.html")

        # 登录业务逻辑处理
        # try:
        #     password = sha256(password)
        #     User.objects.get(username=user_name, password=password)
        # except User.DoesNotExist:
        #     return HttpResponse("用户名或密码错误")

        # 使用django的认证系统进行用户密码的校验
        user = authenticate(username=user_name, password=password)
        if user is None:
            # 用户的登录信息有误
            return render(request, "login.html", {"errmsg": "用户名或密码错误!"})

        # 判断用户的激活状态
        if user.is_active is False:
            return render(request, "login.html", {"errmsg": "用户尚未激活!"})

        # 保存用户的登录状态
        # 使用django的login函数保存用户的session数据
        login(request, user)
        # 判断用户有没有勾选记住用户名,勾选的话值为on
        if remebered != 'on':
            request.session.set_expiry(0) # 设置为临时的会话
        else:

            request.session.set_expiry(None)
        # 获取cookie中购物车的数据
        cart_json = request.COOKIES.get('cart')
        if cart_json is not None:
            cart_cookie=json.loads(cart_json)
        else:
            cart_cookie= {}

        # 获取redis中购物车的数据
        redis_con=get_redis_connection('default')
        redis_cart=redis_con.hgetall('cart_%s' % user.id)
        redis_cart.update(cart_cookie)
        redis_con.hmset('cart_%s' %user.id,redis_cart)
        # 判断页面的url中是否有next参数如果有,返回到原来的路径
        next = request.GET.get('next')
        if next:
            response=redirect(next)
        else:
            # 登录成功,跳转到主页
            response=redirect(reverse("goods:index"))

        # 清楚浏览器中的cookie数据
        response.delete_cookie('cart')
        return response
View Code

 用户登陆成功后浏览器中的cookie被清除了,页面的效果如下:

2  在上面1的基础上稍微修改实现把数据库reedis和浏览器cookie中的购物车数据进行累加求和

业务逻辑处理如下

 1 分别取出在浏览器和数据库中的购物车数据后,

 2 遍历cookie中的购物车信息通过cookie中的键判断商品在不在redis中在的话叠加,不在的话添加进去

注意的点

sku_id 从cookie取出时str类型,count是整形,redis中的sku_id和count都是是bytes类型,
1 那cookie中的sku_id在redis中查询要转换成bytes类型
2 把redis中的count和浏览器中cookie中的count想加要把redis中的count转换成int()
    for sku_id,count in cart_cookie.items():
            # sku_id 从cookie取出时str类型,redis中是bytes类型
            sku_id=sku_id.encode()
            if sku_id in redis_cart:
                origin_count = redis_cart[sku_id]
                count += int(origin_count)
            redis_cart[sku_id]=count

        redis_con.hmset('cart_%s' %user.id,redis_cart)
 完整的代码如下:
class LoginView(View):
    """登录"""
    def get(self, request):
        """提供登录页面"""
        return render(request, "login.html",{})

    def post(self, request):
        """处理登录的数据"""
        # 获取参数
        user_name = request.POST.get("username")
        password = request.POST.get("pwd")
        remebered = request.POST.get('remembered')
        # 参数校验
        if not all([user_name, password]):
            # 参数不完整
            return render(request, "login.html")

        # 登录业务逻辑处理
        # try:
        #     password = sha256(password)
        #     User.objects.get(username=user_name, password=password)
        # except User.DoesNotExist:
        #     return HttpResponse("用户名或密码错误")

        # 使用django的认证系统进行用户密码的校验
        user = authenticate(username=user_name, password=password)
        if user is None:
            # 用户的登录信息有误
            return render(request, "login.html", {"errmsg": "用户名或密码错误!"})

        # 判断用户的激活状态
        if user.is_active is False:
            return render(request, "login.html", {"errmsg": "用户尚未激活!"})

        # 保存用户的登录状态
        # 使用django的login函数保存用户的session数据
        login(request, user)
        # 判断用户有没有勾选记住用户名,勾选的话值为on
        if remebered != 'on':
            request.session.set_expiry(0) # 设置为临时的会话
        else:

            request.session.set_expiry(None)
        # 获取cookie中购物车的数据
        cart_json = request.COOKIES.get('cart')
        if cart_json is not None:
            cart_cookie=json.loads(cart_json)
        else:
            cart_cookie= {}

        # 获取redis中购物车的数据
        redis_con=get_redis_connection('default')
        redis_cart=redis_con.hgetall('cart_%s' % user.id)
        # 便利浏览器中的cookie数据
        for sku_id,count in cart_cookie.items():
            # sku_id 从cookie取出时str类型,redis中是bytes类型
            sku_id=sku_id.encode()
            if sku_id in redis_cart:
                origin_count = redis_cart[sku_id]
                count += int(origin_count)
            redis_cart[sku_id]=count

        redis_con.hmset('cart_%s' %user.id,redis_cart)
        # 判断页面的url中是否有next参数如果有,返回到原来的路径
        next = request.GET.get('next')
        if next:
            response=redirect(next)
        else:
            # 登录成功,跳转到主页
            response=redirect(reverse("goods:index"))

        # 清楚浏览器中的cookie数据
        response.delete_cookie('cart')
        return response
View Code
效果如下:
1 用户登陆后查看购物车中草莓的数量为6:

2 用退出后添加一个草莓到购物车:

 

3 用户登陆后查看合并后想加的数据:

 

4 实现用户在购物车的页面进行修改的操作(增减)

用户操作的前端页面如下所示:

前端页面分析:

  当用户通过增加和减少的按钮或者直接在input框里面修改数值时,会通过ajax中的post请求的方式发送修改后的商品sku_id和数量交给后端进行判断

  前端需要通过ajax给后端传送的数据有sku_id和商品的数量count

后端的业务逻辑处理如下:

  1 接受前端通过post传送的数据(sku_id,count)

  2 参数校验

    判断商品是否存在

    count是否是整数

    判断库存是否充足

  3 业务处理,保存数据

    如果用户已登录保存在redis中

    如果用户未登录保存在cookie中

  4 返回json的响应数据

 修改的视图处理如下:

class UpdateCartView(View):
    """更新购物车数据"""
    def post(self, request):
        # 获取数据
        sku_id = request.POST.get("sku_id")  # 商品id
        count = request.POST.get("count")  # 修改之后的数量

        # 检查数据
        # 判断商品是否存在
        try:
            sku = GoodsSKU.objects.get(id=sku_id)
        except GoodsSKU.DoesNotExist:
            return JsonResponse({"code": 1, "message": "商品不存在"})

        # count是否是整数
        try:
            count = int(count)
        except Exception:
            return JsonResponse({"code": 2, "message": "数量参数有问题"})

        # 判断库存
        if count > sku.stock:
            return JsonResponse({"code": 3, "message": "库存不足"})

        # 业务处理,保存数据
        if not request.user.is_authenticated():
            # 如果用户未登录,保存数据到cookie中
            cart_json = request.COOKIES.get("cart")
            if cart_json is not None:
                cart = json.loads(cart_json)
            else:
                cart = {}

            cart[sku_id] = count

            response = JsonResponse({"code": 0, "message": "修改成功"})
            response.set_cookie("cart", json.dumps(cart))
            return response
        else:
            # 如果用户已登录,保存数据到redis中
            redis_conn = get_redis_connection("default")
            user_id = request.user.id
            # cart = redis_conn.hgetall("cart_%s" % user_id)
            # # 将sku_id转换为bytes,对redis返回的字典cart进行操作
            # sku_id = sku_id.encode()
            # cart[sku_id] = count
            redis_conn.hset("cart_%s" % user_id, sku_id, count)
            # 返回结果, 返回Json数据
            return JsonResponse({"code": 0, "message": "修改成功"})
View Code

5 实现用户可以删除商品的信息

 当用户点击删除的操作后,购物车中的数据会移除,需要给前端传送的参数只有一个(商品的sku_id)

后端视图函数的业务逻辑处理如下:

1 获取参数

2 参数校验

3 业务处理删除购物车数据

4 返回处理的结果给前端

 删除的视图处理函数如下:

class DeleteCartView(View):
    def post(self, request):
        # 接受参数
        sku_id = request.POST.get('sku_id')
        # 参数校验
        if not sku_id:
            return JsonResponse({'code':1,'message':'参数不完整'})

        # 根据sku_id把对应的商品删除
        if not request.user.is_authenticated():
            # 如果用户未登录,从cookie中删除数据
            cart_json = request.COOKIES.get('cart')
            if cart_json is not None:
                cart = json.loads(cart_json)
                if sku_id in cart:
                    del cart[sku_id]

                response = JsonResponse({'code':0, 'message':'删除成功'})
                response.set_cookie('cart',json.dumps(cart))
                return response
            else:
                return JsonResponse({'code':0 ,'message': '删除成功'})

        else:
            # 如果用户登陆,从redis中删除数据
            redis_con = get_redis_connection('default')
            user_id = request.user.id
            redis_con.hdel('cart_%s' % user_id, sku_id)
            return JsonResponse({'code':0,'message': '删除成功'})
View Code

 总共有4件商品

 当用户点击删除时

 前端的代码如下:

{% extends 'base.html' %}

{% block title %}天天生鲜-购物车{% endblock %}
{% load staticfiles %}

{% block search_bar %}
    <div class="search_bar clearfix">
        <a href="{% url 'goods:index' %}" class="logo fl"><img src="{% static 'images/logo.png' %}"></a>
        <div class="sub_page_name fl">|&nbsp;&nbsp;&nbsp;&nbsp;购物车</div>
        <div class="search_con fr">
            <form action="/search/" method="get">
            <input type="text" class="input_text fl" name="q" placeholder="搜索商品">
            <input type="submit" class="input_btn fr" value="搜索">
            </form>
        </div>
    </div>
{% endblock %}

{% block body %}
    <div class="total_count">全部商品<em>{{total_count}}</em></div>
    <ul class="cart_list_th clearfix">
        <li class="col01">商品名称</li>
        <li class="col02">商品单位</li>
        <li class="col03">商品价格</li>
        <li class="col04">数量</li>
        <li class="col05">小计</li>
        <li class="col06">操作</li>
    </ul>
    <form method="post" action="#">
    {% csrf_token %}
    {% for sku in skus %}
    <ul class="cart_list_td clearfix" sku_id="{{ sku.id }}">
        <li class="col01"><input type="checkbox" name="sku_ids" value="{{ sku.id }}" checked></li>
        <li class="col02"><img src="{{ sku.default_image.url }}"></li>
        <li class="col03">{{ sku.name }}<br><em>{{ sku.price }}/{{ sku.unit}}</em></li>
        <li class="col04">{{ sku.unit }}</li>
        <li class="col05"><span>{{sku.price}}</span></li>
        <li class="col06">
            <div class="num_add">
                <a href="javascript:;" class="add fl">+</a>
                <input type="text" class="num_show fl" sku_id="{{ sku.id }}" value="{{sku.count}}">
                <a href="javascript:;" class="minus fl">-</a>
            </div>
        </li>
        <li class="col07"><span>{{sku.amount}}</span></li>
        <li class="col08"><a href="javascript:;" class="del_btn">删除</a></li>
    </ul>
    {% endfor %}
    <ul class="settlements">
        <li class="col01"><input type="checkbox" checked></li>
        <li class="col02">全选</li>
        <li class="col03">合计(不含运费):<span>¥</span><em id="total_amount">{{total_amount}}</em><br>共计<b id="total_count">{{total_count}}</b>件商品</li>
        <li class="col04"><input type="submit" id="commit_btn">去结算</input></li>
    </ul>
    </form>

{% endblock %}

{% block bottom_files %}
    <script type="text/javascript" src="{% static 'js/jquery-1.12.2.js' %}"></script>
    <script type="text/javascript">
        // 更新页面合计信息
        function freshOrderCommitInfo() {
            var total_amount = 0;
            var total_count = 0;
            $('.cart_list_td').find(':checked').parents('ul').each(function () {
                var sku_amount = $(this).children('li.col07').text();  // 商品的金额
                var sku_count = $(this).find('.num_show').val()  // 商品的数量
                total_count += parseInt(sku_count);
                total_amount += parseFloat(sku_amount);
            });
            // 设置商品的总数和总价
            $("#total_amount").text(total_amount.toFixed(2));
            $("#total_count").text(total_count);
        }

        // 更新页面顶端全部商品数量
        function freshTotalGoodsCount() {
            var total_count = 0;
            $('.cart_list_td').find(':checked').parents('ul').each(function () {
                var sku_count = $(this).find('.num_show').val();
                total_count += parseInt(sku_count);
            });
            $(".total_count>em").text(total_count);
        }

        // 更新后端购物车信息
        function updateRemoteCartInfo(sku_id, sku_count, num_dom) {
            // 发送给后端的数据
            var req = {
                sku_id: sku_id,
                count: sku_count,
                csrfmiddlewaretoken: "{{ csrf_token }}"
            };
            $.post("/cart/update", req, function(data){
                if (0 == data.code) {
                    // 更新商品数量
                    $(num_dom).val(sku_count);
                    // 更新商品金额信息
                    var sku_price = $(".cart_list_td[sku_id="+sku_id+"]").children('li.col05').children().text();
                    var sku_amount = parseFloat(sku_price) * sku_count;
                    $(".cart_list_td[sku_id="+sku_id+"]").children('li.col07').children().text(sku_amount.toFixed(2));
                    // 更新顶部商品总数
                    freshTotalGoodsCount();
                    // 更新底部合计信息
                    freshOrderCommitInfo();
                } else {
                    alert(data.message);
                }
            });
        }

        // 增加
        $(".add").click(function(){
            // 获取操作的商品id
            var sku_id = $(this).next().attr("sku_id");
            // 获取加操作前的的数量
            var sku_num = $(this).next().val();
            // 进行数量加1
            sku_num = parseInt(sku_num);
            sku_num += 1;

            // 显示商品数目的dom
            var num_dom = $(this).next();
            // 更新购物车数量
            updateRemoteCartInfo(sku_id, sku_num, num_dom);
        });

        // 减少
        $(".minus").click(function(){
            // 获取操作的商品id
            var sku_id = $(this).prev().attr("sku_id");
            // 获取加操作前的的数量
            var sku_num = $(this).prev().val();
            // 进行数量加1
            sku_num = parseInt(sku_num);
            sku_num -= 1;
            if (sku_num < 1) sku_num = 1;
            // 更新页面显示数量
            var num_dom = $(this).prev();
            // 更新购物车数量
            updateRemoteCartInfo(sku_id, sku_num, num_dom);
        });

        var pre_sku_count = 0;
        $('.num_show').focus(function () {
            // 记录用户手动输入之前商品数目
            pre_sku_count = $(this).val();
        });
        // 手动输入
        $(".num_show").blur(function(){
            var sku_id = $(this).attr("sku_id");
            var sku_num = $(this).val();
            // 如果输入的数据不合理,则将输入值设置为在手动输入前记录的商品数目
            if (isNaN(sku_num) || sku_num.trim().length<=0 || parseInt(sku_num)<=0) {
                $(this).val(pre_sku_count);
                return;
            }
            sku_num = parseInt(sku_num);
            var num_dom = $(this);
            updateRemoteCartInfo(sku_id, sku_num, num_dom);
        });

        // 删除
        $(".del_btn").click(function(){
            var sku_id = $(this).parents("ul").attr("sku_id");
            var req = {
                sku_id: sku_id,
                csrfmiddlewaretoken: "{{ csrf_token }}"
            };
            $.post('/cart/delete', req, function(data){
{#                window.reload()#}
                location.href="/cart/";  // 删除后,刷新页面
            });
        });

        // 商品对应checkbox发生改变时,全选checkbox发生改变
        $('.cart_list_td').find(':checkbox').change(function () {
            // 获取商品所有checkbox的数目
            var all_len = $('.cart_list_td').find(':checkbox').length;
            // 获取选中商品的checkbox的数目
            var checked_len = $('.cart_list_td').find(':checked').length;

            if (checked_len < all_len){
                // 有商品没有被选中
                $('.settlements').find(':checkbox').prop('checked', false)
            }
            else{
                // 所有商品都被选中
                $('.settlements').find(':checkbox').prop('checked', true)
            }
            freshOrderCommitInfo();
        });

        // 全选和全不选
        $('.settlements').find(':checkbox').change(function () {
            // 1.获取当前checkbox的选中状态
            var is_checked = $(this).prop('checked');
            // 2.遍历并设置商品ul中checkbox的选中状态
            $('.cart_list_td').find(':checkbox').each(function () {
                // 设置每一个goods ul中checkbox的值
                $(this).prop('checked', is_checked)
            });
            freshOrderCommitInfo();
        });

    </script>
{% endblock %}
View Code

猜你喜欢

转载自www.cnblogs.com/aaronthon/p/9347827.html