Watch 1 Star 1 Fork 0

我用Python / DjangoShopCSS

Join us
Explore and code with more than 5 million developers,Free private repositories !:)
Sign up
This repository doesn't specify license. Without author's permission, this code is only for learning and cannot be used for other purposes.
Django电商(生鲜)项目搭建 spread retract

Clone or download
Loading...
README.md

Django电商项目

一、环境配置

1、创建Django项目

django-admin startproject DjangoShop

2、创建App(Store店铺)

python manage.py startapp Store

3、创建App下模板目录及静态文件目录

static

4、创建主static目录用于收集静态文件

5、setting文件设置

  • 安装App

  • STATICFILES_DIRS = ( os.path.join(BASE_DIR,'static'), )

  • MEDIA_URL = '/media/'

  • MEDIA_ROOT = os.path.join(BASE_DIR,'static')

  • STATIC_ROOT = os.path.join(BASE_DIR,'static')

收集静态文件时注释STATICFILES_DIRS、MEDIA_URL、MEDIA_ROOT

收集完成取消上述三个注释,再注释STATIC_ROOT

二、创建数据模型类

1、创建模型

from django.db import models

# Create your models here.
# 定义卖家模型类
class Seller(models.Model):
    username = models.CharField(max_length=32,verbose_name="用户名")
    password = models.CharField(max_length=32,verbose_name="密码")
    nickname = models.CharField(max_length=32,verbose_name="昵称",null=True,blank=True)
    phone = models.CharField(max_length=32,verbose_name="手机",null=True,blank=True)
    email = models.EmailField(verbose_name="邮箱",null=True,blank=True)
    picture = models.ImageField(upload_to="store/images",verbose_name="头像",null=True,blank=True)
    address = models.CharField(max_length=32,verbose_name="地址",null=True,blank=True)

    card_id = models.CharField(max_length=32,verbose_name="身份证",null=True,blank=True)

# 定义店铺类型类
class StoreType(models.Model):
    store_type = models.CharField(max_length=32,verbose_name="类型名称")
    type_description = models.TextField(verbose_name="类型描述")

# 定义店铺类
class Store(models.Model):
    store_name = models.CharField(max_length=32,verbose_name="店铺名称")
    store_address = models.CharField(max_length=32,verbose_name="店铺地址")
    store_description = models.TextField(verbose_name="店铺描述")
    store_logo = models.ImageField(upload_to="store/images",verbose_name="店铺logo")
    store_phone = models.CharField(max_length=32,verbose_name="店铺电话")
    store_money = models.FloatField(verbose_name="店铺注册资金")

    user_id = models.IntegerField(verbose_name="店铺主人")
    type = models.ManyToManyField(to=StoreType,verbose_name="店铺类型")

# 定义商品类
class Goods(models.Model):
    goods_name = models.CharField(max_length=32,verbose_name="商品名称")
    goods_price = models.FloatField(verbose_name="商品价格")
    goods_image = models.ImageField(upload_to="store/images",verbose_name="商品图片")
    goods_number = models.IntegerField(verbose_name="商品库存")
    goods_description = models.TextField(verbose_name="商品描述")
    goods_date = models.DateField(verbose_name="出厂日期")
    goods_safeDate = models.IntegerField(verbose_name="保质期")

    store_id = models.ManyToManyField(to=Store,verbose_name="商品店铺")

# 定义商品图片类
class GoodsImg(models.Model):
    img_address = models.ImageField(upload_to="store/images",verbose_name="图片地址")
    img_description = models.TextField(verbose_name="图片描述")
    goods_id = models.ForeignKey(to=Goods,on_delete=models.CASCADE,verbose_name="商品id")

    

2、同步数据库

校验配置

python manage.py check

生成数据库语句

python manage.py makemigrations

同步数据库

python manage.py migrate

收集静态文件 python manage.py collectstatic(收集完成注意恢复配置,并注释STATIC_ROOT)

三、创建登录注册功能

1、编写注册登录功能

将模板中的注册HTML文件复制到App下templates-store下

reg

编写简单视图显示页面

创建独立url

填写主url

更改原html文档,将所有css,js导入路径更改为/static/store/...,在访问网页

改动HTML布局

<!DOCTYPE html>
<html lang="en">

<head>

  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta name="description" content="">
  <meta name="author" content="">

  <title>SB Admin 2 - Register</title>

  <!-- Custom fonts for this template-->
  <link href="/static/store/vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
  <link href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i" rel="stylesheet">

  <!-- Custom styles for this template-->
  <link href="/static/store/css/sb-admin-2.min.css" rel="stylesheet">

</head>

<body class="bg-gradient-primary">

  <div class="container">

    <div class="card o-hidden border-0 shadow-lg my-5">
      <div class="card-body p-0">
        <!-- Nested Row within Card Body -->
        <div class="row">
          <div class="col-lg-5 d-none d-lg-block bg-register-image"></div>
          <div class="col-lg-7">
            <div class="p-5">
              <div class="text-center">
                <h1 class="h4 text-gray-900 mb-4">注册用户</h1>
              </div>
              <form class="user" method="post">
					{% csrf_token %}
                <div class="form-group">
                  <input type="text" class="form-control form-control-user" name="username" placeholder="用户名">
                </div>
                <div class="form-group">
                  <input type="password" class="form-control form-control-user" name="password" placeholder="密码">
                </div>
                <div class="form-group">
                  <input type="submit" class="btn btn-primary btn-user btn-block" value="注册">
                </div>
{#                <a href="login.html" class="btn btn-primary btn-user btn-block">#}
{#                  Register Account#}
{#                </a>#}
                <hr>
{#                <a href="index.html" class="btn btn-google btn-user btn-block">#}
{#                  <i class="fab fa-google fa-fw"></i> Register with Google#}
{#                </a>#}
{#                <a href="index.html" class="btn btn-facebook btn-user btn-block">#}
{#                  <i class="fab fa-facebook-f fa-fw"></i> Register with Facebook#}
{#                </a>#}
              </form>
              <hr>
              <div class="text-center">
                <a class="small" href="forgot-password.html">忘记密码?</a>
              </div>
              <div class="text-center">
                <a class="small" href="login.html">已经有账户了? 登录!</a>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>

  </div>

  <!-- Bootstrap core JavaScript-->
  <script src="/static/store/vendor/jquery/jquery.min.js"></script>
  <script src="/static/store/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>

  <!-- Core plugin JavaScript-->
  <script src="/static/store/vendor/jquery-easing/jquery.easing.min.js"></script>

  <!-- Custom scripts for all pages-->
  <script src="/static/store/js/sb-admin-2.min.js"></script>

</body>

</html>

效果:

复制register文档做登录页面并编写完整注册视图函数:

import hashlib

from django.shortcuts import render
from django.http import HttpResponseRedirect

from Store.models import *

# Create your views here.
# 密码加密功能
def setPassword(password):
    md5 = hashlib.md5()
    md5.update(password.encode())
    result = md5.hexdigest()
    return result

# 注册功能
def register(request):
    if request.method == "POST":
        username = request.POST.get("username")
        password = request.POST.get("password")
        if username and password:
            seller = Seller()
            seller.username = username
            seller.password = setPassword(password)
            seller.nickname = username
            seller.save()
            return HttpResponseRedirect("/Store/login/")
    return render(request,"store/register.html")


# 登录功能
def login(request):
    return render(request,"store/login.html")

测试注册功能

2、完善登录功能

在登录页面下发cookie,在登录请求校验cookie。如果登录成功,则跳转到首页;否则跳转登录页面

先布局index首页(复制模板当中的index页面并修改样式路径)

完善登录视图:

# 登录功能
def login(request):
    """
    登录功能:进入登录页面是下发cookie,验证是正常方式请求登录
    登录成功再次下发一个cookie,验证用户
    """
    # 进入登录页面下发来源合法的cookie
    response = render(request,"store/login.html")
    response.set_cookie("login_from","legitimate")
    # 判断用户请求的方式
    if request.method == "POST":
        username = request.POST.get("username")
        password = request.POST.get("password")
        if username and password: # 用户和密码都存在
            seller = Seller.objects.filter(username=username).first() # 数据库查询该用户
            if seller:
                # 将前端获取到的密码加密,同数据库进行验证
                web_password = setPassword(password)
                # 校验登录页面的cookie
                cookies = request.COOKIES.get("login_from")
                if web_password == seller.password and cookies == "legitimate":
                    # 登录成功,则跳转到首页并下发cookie和session
                    response = HttpResponseRedirect('/Store/index/')
                    response.set_cookie("username",seller.username)
                    request.session["username"] = seller.username
                    return response
    return response

# 首页
def index(request):
    return render(request,"store/index.html",locals())

效果图:

做个装饰器,给首页添加校验功能,没有cookie不能直接登录首页,只有用户登录成功才能进入首页

# 用户登录校验装饰器
def loginValid(fun):
    def inner(request,*args,**kwargs):
        # 获取成功登录后的cookie和session
        c_user = request.COOKIES.get("username")
        s_user = request.session.get("username")
        # 如果cookie和session都存在并且值都相同
        if c_user and s_user and c_user == s_user:
            # 通过c_user查询数据库
            seller = Seller.objects.filter(username=c_user).first()
            # 如果有这个用户,则返回函数,这里只index
            if seller:
                return fun(request,*args,**kwargs)
        # 否则重定向到登录页面
        return HttpResponseRedirect("/Store/login/")
    return inner


# 首页
@loginValid
def index(request):
    return render(request,"store/index.html",locals())

现在直接进入index首页会自动跳转至登录页面

3、前后端用户名重复校验

后端(针对登录功能)

# 登录功能
def login(request):
    """
    登录功能:进入登录页面是下发cookie,验证是正常方式请求登录
    登录成功再次下发一个cookie,验证用户
    """
    # 后端校验
    result = {"status":"error","data":""}
    # 进入登录页面下发来源合法的cookie
    response = render(request,"store/login.html")
    response.set_cookie("login_from","legitimate")
    # 判断用户请求的方式
    if request.method == "POST":
        username = request.POST.get("username")
        password = request.POST.get("password")
        if username and password: # 用户和密码都存在
            seller = Seller.objects.filter(username=username).first() # 数据库查询该用户
            if seller:
                # 将前端获取到的密码加密,同数据库进行验证
                web_password = setPassword(password)
                # 校验登录页面的cookie
                cookies = request.COOKIES.get("login_from")
                if web_password == seller.password and cookies == "legitimate":
                    # 登录成功,则跳转到首页并下发cookie和session
                    response = HttpResponseRedirect('/Store/index/')
                    response.set_cookie("username",seller.username)
                    request.session["username"] = seller.username
                    result["status"] = "success"
                    result["data"] = "登录成功"
                    return response
                else:
                    result["data"] = "密码错误"
                    response = render(request, "store/login.html", locals())
            else:
                result["data"] = "用户名不存在"
                response = render(request, "store/login.html", locals())
        else:
            result["data"] = "用户名或密码不能为空"
            response = render(request, "store/login.html",locals())
    return response

效果图:

前端校验(针对注册功能)

def ajax_regValid(request):
    # ajax前端注册校验
    result = {"status": "error", "data": ""}
    username = request.POST.get("username")
    if username:
        user = Seller.objects.filter(username=username).first() # 数据库查询该用户
        if user:
            result["data"] = "用户名已存在"
        else:
            result["status"] = "success"
            result["data"] = "用户名可以使用"
    else:
        result["data"] = "用户名不能为空"
    return JsonResponse(result)

注册页面添加如下内容:

<script>
      $("#username").blur(
          function () {
              var username = $("#username").val();
              var csrfmiddlewaretoken = '{{ csrf_token }}';
              var url = "/Store/ajax/";
              send_data = {
                  "username":username,
                  "csrfmiddlewaretoken":csrfmiddlewaretoken
              };
              $.ajax(
                  {
                      url: url,
                      type: "post",
                      data: send_data,
                      success: function (data) {
                          var status = data.status;
                          $("#sign").text(data.data);
                          if(status == "error"){
                              $("#submit").attr("disabled",true)
                          }else {
                              $("#submit").attr("disabled",false)
                          }
                          {#alert(data.data)#}
                          {#console.log(data)#}
                      },
                      error: function (error) {
                          console.log(error)
                      }
                  }
              )
          }
      )

  </script>

效果图:

三、编写退出功能(删除cookie)

定义视图函数:

# 退出功能(删除cookie)
def exit(request):
    response = HttpResponseRedirect("/Store/login/")
    response.delete_cookie("username")
    del request.session["username"]
    return response

更改首页logout超链接的href为/Store/exit/

四、编写base模板页,其他页面继承模板页

<!DOCTYPE html>
<html lang="en">

<head>

  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta name="description" content="">
  <meta name="author" content="">

  <title>{% block title %}{% endblock %}</title>

  <!-- Custom fonts for this template-->
  <link href="/static/store/vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
  <link href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i" rel="stylesheet">

  <!-- Custom styles for this template-->
  <link href="/static/store/css/sb-admin-2.min.css" rel="stylesheet">
  {% block style %}{% endblock %}

</head>

<body id="page-top">

  <!-- Page Wrapper -->
  <div id="wrapper">

    <!-- Sidebar -->
    <ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar">

      <!-- Sidebar - Brand -->
      <a class="sidebar-brand d-flex align-items-center justify-content-center" href="index.html">
        <div class="sidebar-brand-icon rotate-n-15">
          <i class="fas fa-laugh-wink"></i>
        </div>
        <div class="sidebar-brand-text mx-3">后台管理系统</div>
      </a>

      <!-- Divider -->
      <hr class="sidebar-divider my-0">

      <!-- Nav Item - Dashboard -->
      <li class="nav-item">
        <a class="nav-link" href="index.html">
          <i class="fas fa-fw fa-tachometer-alt"></i>
          <span>店铺管理</span></a>
      </li>

      <!-- Divider -->
      <hr class="sidebar-divider">

      <!-- Heading -->
      <div class="sidebar-heading">
        销售管理
      </div>

      <!-- Nav Item - Pages Collapse Menu -->
      <li class="nav-item">
        <a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="true" aria-controls="collapseTwo">
          <i class="fas fa-fw fa-cog"></i>
          <span>商品管理</span>
        </a>
        <div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionSidebar">
          <div class="bg-white py-2 collapse-inner rounded">
            <h6 class="collapse-header">定制组件:</h6>
            <a class="collapse-item" href="buttons.html">按钮</a>
            <a class="collapse-item" href="cards.html">卡片</a>
          </div>
        </div>
      </li>

      <!-- Nav Item - Utilities Collapse Menu -->
      <li class="nav-item">
        <a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseUtilities" aria-expanded="true" aria-controls="collapseUtilities">
          <i class="fas fa-fw fa-wrench"></i>
          <span>工具</span>
        </a>
        <div id="collapseUtilities" class="collapse" aria-labelledby="headingUtilities" data-parent="#accordionSidebar">
          <div class="bg-white py-2 collapse-inner rounded">
            <h6 class="collapse-header">定制工具:</h6>
            <a class="collapse-item" href="utilities-color.html">颜色</a>
            <a class="collapse-item" href="utilities-border.html">边框</a>
            <a class="collapse-item" href="utilities-animation.html">动画</a>
            <a class="collapse-item" href="utilities-other.html">其他</a>
          </div>
        </div>
      </li>

      <!-- Divider -->
{#      <hr class="sidebar-divider">#}

      <!-- Nav Item - Charts -->
{#      <li class="nav-item">#}
{#        <a class="nav-link" href="charts.html">#}
{#          <i class="fas fa-fw fa-chart-area"></i>#}
{#          <span>图表</span></a>#}
{#      </li>#}

      <!-- Nav Item - Tables -->
{#      <li class="nav-item">#}
{#        <a class="nav-link" href="tables.html">#}
{#          <i class="fas fa-fw fa-table"></i>#}
{#          <span>表格</span></a>#}
{#      </li>#}

      <!-- Divider -->
{#      <hr class="sidebar-divider d-none d-md-block">#}

      <!-- Sidebar Toggler (Sidebar) -->
      <div class="text-center d-none d-md-inline">
        <button class="rounded-circle border-0" id="sidebarToggle"></button>
      </div>

    </ul>
    <!-- End of Sidebar -->

    <!-- Content Wrapper -->
    <div id="content-wrapper" class="d-flex flex-column">

      <!-- Main Content -->
      <div id="content">

        <!-- Topbar -->
        <nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow">

          <!-- Sidebar Toggle (Topbar) -->
          <button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3">
            <i class="fa fa-bars"></i>
          </button>

          <!-- Topbar Search -->
          <form class="d-none d-sm-inline-block form-inline mr-auto ml-md-3 my-2 my-md-0 mw-100 navbar-search">
            <div class="input-group">
              <input type="text" class="form-control bg-light border-0 small" placeholder="搜索" aria-label="Search" aria-describedby="basic-addon2">
              <div class="input-group-append">
                <button class="btn btn-primary" type="button">
                  <i class="fas fa-search fa-sm"></i>
                </button>
              </div>
            </div>
          </form>

          <!-- Topbar Navbar -->
          <ul class="navbar-nav ml-auto">

            <!-- Nav Item - Search Dropdown (Visible Only XS) -->
            <li class="nav-item dropdown no-arrow d-sm-none">
              <a class="nav-link dropdown-toggle" href="#" id="searchDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                <i class="fas fa-search fa-fw"></i>
              </a>
              <!-- Dropdown - Messages -->
              <div class="dropdown-menu dropdown-menu-right p-3 shadow animated--grow-in" aria-labelledby="searchDropdown">
                <form class="form-inline mr-auto w-100 navbar-search">
                  <div class="input-group">
                    <input type="text" class="form-control bg-light border-0 small" placeholder="Search for..." aria-label="Search" aria-describedby="basic-addon2">
                    <div class="input-group-append">
                      <button class="btn btn-primary" type="button">
                        <i class="fas fa-search fa-sm"></i>
                      </button>
                    </div>
                  </div>
                </form>
              </div>
            </li>

            <!-- Nav Item - Alerts -->
            <li class="nav-item dropdown no-arrow mx-1">
              <a class="nav-link dropdown-toggle" href="#" id="alertsDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                <i class="fas fa-bell fa-fw"></i>
                <!-- Counter - Alerts -->
                <span class="badge badge-danger badge-counter">3+</span>
              </a>
              <!-- Dropdown - Alerts -->
              <div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="alertsDropdown">
                <h6 class="dropdown-header">
                  警报中心
                </h6>
                <a class="dropdown-item d-flex align-items-center" href="#">
                  <div class="mr-3">
                    <div class="icon-circle bg-primary">
                      <i class="fas fa-file-alt text-white"></i>
                    </div>
                  </div>
                  <div>
                    <div class="small text-gray-500">2019年12月12日</div>
                    <span class="font-weight-bold">一份新的月报已准备好下载!</span>
                  </div>
                </a>
                <a class="dropdown-item d-flex align-items-center" href="#">
                  <div class="mr-3">
                    <div class="icon-circle bg-success">
                      <i class="fas fa-donate text-white"></i>
                    </div>
                  </div>
                  <div>
                    <div class="small text-gray-500">2019年12月7日</div>
                    290.29美元已存入您的账户!
                  </div>
                </a>
                <a class="dropdown-item d-flex align-items-center" href="#">
                  <div class="mr-3">
                    <div class="icon-circle bg-warning">
                      <i class="fas fa-exclamation-triangle text-white"></i>
                    </div>
                  </div>
                  <div>
                    <div class="small text-gray-500">2019年12月2日<</div>
                    消费提醒:我们注意到您的账户消费异常高.
                  </div>
                </a>
                <a class="dropdown-item text-center small text-gray-500" href="#">显示所有警报</a>
              </div>
            </li>

            <!-- Nav Item - Messages -->
            <li class="nav-item dropdown no-arrow mx-1">
              <a class="nav-link dropdown-toggle" href="#" id="messagesDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                <i class="fas fa-envelope fa-fw"></i>
                <!-- Counter - Messages -->
                <span class="badge badge-danger badge-counter">7</span>
              </a>
              <!-- Dropdown - Messages -->
              <div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="messagesDropdown">
                <h6 class="dropdown-header">
                  消息中心
                </h6>
                <a class="dropdown-item d-flex align-items-center" href="#">
                  <div class="dropdown-list-image mr-3">
                    <img class="rounded-circle" src="https://source.unsplash.com/fn_BT9fwg_E/60x60" alt="">
                    <div class="status-indicator bg-success"></div>
                  </div>
                  <div class="font-weight-bold">
                    <div class="text-truncate">嘿,你好!我想知道你是否能帮我解决一个我一直遇到的问题.</div>
                    <div class="small text-gray-500">Emily Fowler · 58m</div>
                  </div>
                </a>
                <a class="dropdown-item d-flex align-items-center" href="#">
                  <div class="dropdown-list-image mr-3">
                    <img class="rounded-circle" src="https://source.unsplash.com/AU4VPcFN4LE/60x60" alt="">
                    <div class="status-indicator"></div>
                  </div>
                  <div>
                    <div class="text-truncate">我有你上个月订购的照片,你想怎么寄给你?</div>
                    <div class="small text-gray-500">Jae Chun · 1d</div>
                  </div>
                </a>
                <a class="dropdown-item d-flex align-items-center" href="#">
                  <div class="dropdown-list-image mr-3">
                    <img class="rounded-circle" src="https://source.unsplash.com/CS2uCrpNzJY/60x60" alt="">
                    <div class="status-indicator bg-warning"></div>
                  </div>
                  <div>
                    <div class="text-truncate">上个月的报告看起来不错,我很高兴到目前为止取得的进展,继续做好工作!</div>
                    <div class="small text-gray-500">Morgan Alvarez · 2d</div>
                  </div>
                </a>
                <a class="dropdown-item d-flex align-items-center" href="#">
                  <div class="dropdown-list-image mr-3">
                    <img class="rounded-circle" src="https://source.unsplash.com/Mv9hjnEUHR4/60x60" alt="">
                    <div class="status-indicator bg-success"></div>
                  </div>
                  <div>
                    <div class="text-truncate">我是个好孩子吗?我问这个问题是因为有人告诉我,人们对所有的狗都这么说,即使它们并不好...</div>
                    <div class="small text-gray-500">Chicken the Dog · 2w</div>
                  </div>
                </a>
                <a class="dropdown-item text-center small text-gray-500" href="#">阅读更多信息</a>
              </div>
            </li>

            <div class="topbar-divider d-none d-sm-block"></div>

            <!-- Nav Item - User Information -->
            <li class="nav-item dropdown no-arrow">
              <a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                <span class="mr-2 d-none d-lg-inline text-gray-600 small">{{ request.COOKIES.username }}</span>
                <img class="img-profile rounded-circle" src="https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4001431513,4128677135&fm=26&gp=0.jpg">
              </a>
              <!-- Dropdown - User Information -->
              <div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="userDropdown">
                <a class="dropdown-item" href="#">
                  <i class="fas fa-user fa-sm fa-fw mr-2 text-gray-400"></i>
                  个人中心
                </a>
                <a class="dropdown-item" href="#">
                  <i class="fas fa-cogs fa-sm fa-fw mr-2 text-gray-400"></i>
                  系统设置
                </a>
                <a class="dropdown-item" href="#">
                  <i class="fas fa-list fa-sm fa-fw mr-2 text-gray-400"></i>
                  登录日志
                </a>
                <div class="dropdown-divider"></div>
                <a class="dropdown-item" href="#" data-toggle="modal" data-target="#logoutModal">
                  <i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i>
                  注销
                </a>
              </div>
            </li>

          </ul>

        </nav>
        <!-- End of Topbar -->

        <!-- Begin Page Content -->

        <!-- /.container-fluid -->
        {% block content %}

        {% endblock %}

      </div>
      <!-- End of Main Content -->

      <!-- Footer -->
      <footer class="sticky-footer bg-white">
        <div class="container my-auto">
          <div class="copyright text-center my-auto">
            <span>Copyright &copy; Your Website 2019</span>
          </div>
        </div>
      </footer>
      <!-- End of Footer -->

    </div>
    <!-- End of Content Wrapper -->

  </div>
  <!-- End of Page Wrapper -->

  <!-- Scroll to Top Button-->
  <a class="scroll-to-top rounded" href="#page-top">
    <i class="fas fa-angle-up"></i>
  </a>

  <!-- Logout Modal-->
  <div class="modal fade" id="logoutModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="exampleModalLabel">准备离开?</h5>
          <button class="close" type="button" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
        <div class="modal-body">如果您准备结束当前会话,请选择下面的“注销”.</div>
        <div class="modal-footer">
          <button class="btn btn-secondary" type="button" data-dismiss="modal">取消</button>
          <a class="btn btn-primary" href="login.html">注销</a>
        </div>
      </div>
    </div>
  </div>

  <!-- Bootstrap core JavaScript-->
  <script src="/static/store/vendor/jquery/jquery.min.js"></script>
  <script src="/static/store/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>

  <!-- Core plugin JavaScript-->
  <script src="/static/store/vendor/jquery-easing/jquery.easing.min.js"></script>

  <!-- Custom scripts for all pages-->
  <script src="/static/store/js/sb-admin-2.min.js"></script>

  {% block script %}
  
  {% endblock %}

</body>

</html>

五、店铺注册

在首页功能视图函数中判断用户是否有店铺,通过Store类中user_id

# 首页
@loginValid
def index(request):
    """
    v1.5 添加检测账号是否有店铺的逻辑
    """
    # 查询当前用户
    user_id = request.COOKIES.get("user_id")
    if user_id:
        user_id = int(user_id)
    else:
        user_id = 0
    # 通过用户查询店铺是否存在(店铺和用户通过用户的id进行关联)
    store = Store.objects.filter(user_id=user_id).first()
    if store:
        is_store = 1
    else:
        is_store = 0
    return render(request,"store/index.html",{"is_store":is_store})

# 登录功能
def login(request):
    """
    登录功能:进入登录页面是下发cookie,验证是正常方式请求登录
    登录成功再次下发一个cookie,验证用户
    """
    # 后端校验
    result = {"status":"error","data":""}
    # 进入登录页面下发来源合法的cookie
    response = render(request,"store/login.html")
    response.set_cookie("login_from","legitimate")
    # 判断用户请求的方式
    if request.method == "POST":
        username = request.POST.get("username")
        password = request.POST.get("password")
        if username and password: # 用户和密码都存在
            seller = Seller.objects.filter(username=username).first() # 数据库查询该用户
            if seller:
                # 将前端获取到的密码加密,同数据库进行验证
                web_password = setPassword(password)
                # 校验登录页面的cookie
                cookies = request.COOKIES.get("login_from")
                if web_password == seller.password and cookies == "legitimate":
                    # 登录成功,则跳转到首页并下发cookie和session
                    response = HttpResponseRedirect('/Store/index/')
                    response.set_cookie("username",seller.username)
                    # v1.5 添加下发user_id的cookie
                    response.set_cookie("user_id", seller.id)
                    request.session["username"] = seller.username
                    result["status"] = "success"
                    result["data"] = "登录成功"
                    return response
                else:
                    result["data"] = "密码错误"
                    response = render(request, "store/login.html", locals())
            else:
                result["data"] = "用户名不存在"
                response = render(request, "store/login.html", locals())
        else:
            result["data"] = "用户名或密码不能为空"
            response = render(request, "store/login.html",locals())
    return response

前端base也添加判断

      <li class="nav-item">
        {% if is_store == 1 %}
        <a class="nav-link" href="index.html">
          <i class="fas fa-fw fa-tachometer-alt"></i>
                <span>店铺管理</span></a>
        {% else %}
        <a class="nav-link" href="/Store/store_register/">
          <i class="fas fa-fw fa-tachometer-alt"></i>
                <span>没有店铺,注册一个</span></a>
        {% endif %}

      </li>

效果:

指定一个注册店铺的前端页

{% extends "store/base.html" %}

{% block title %}
    后台管理首页
{% endblock %}

        {% block content %}
        <form class="form" method="post" enctype="multipart/form-data">
            <div class="form-group">
                {% csrf_token %}
            </div>
            <div class="form-group">
                <input type="text" class="form-control form-control-user"  name="store_name" placeholder="店铺名称">
            </div>
            <div class="form-group">
                <input type="text" class="form-control form-control-user"  name="store_address" placeholder="店铺地址">
            </div>
            <div class="form-group">
                <input type="text" class="form-control form-control-user"  name="store_description" placeholder="店铺描述">
            </div>
            <div class="form-group">
                <input type="file" class="form-control form-control-user"  name="store_logo" placeholder="店铺logo">
            </div>
            <div class="form-group">
                <input type="text" class="form-control form-control-user"  name="store_phone" placeholder="店铺电话">
            </div>
            <div class="form-group">
                <input type="text" class="form-control form-control-user"  name="store_money" placeholder="店铺注册资金">
            </div>
            <div class="form-group">
                <select name="type" class="form-control form-control-user" multiple="multiple">
                    {% for t in type_list %}
                        <option value="{{ t.id }}">{{ t.store_type }}</option>
                    {% endfor %}
                </select>
            </div>
            <div class="form-group">
                <input class="btn btn-primary btn-block" type="submit" value="注册">
            </div>
        </form>
        {% endblock %}

效果:

完善注册店铺视图函数:

def store_register(request):
    # v1.5 新增店铺注册
    # 查询所有的店铺类型
    type_list = StoreType.objects.all()
    if request.method == "POST":
        post_data = request.POST #接收post数据
        # print(request.FILES)
        # print(post_data)
        store_name = post_data.get("store_name")
        store_address = post_data.get("store_address")
        store_description = post_data.get("store_description")
        store_phone = post_data.get("store_phone")
        store_money = post_data.get("store_money")

        # 通过cookie来获得user_id
        user_id = int(request.COOKIES.get("user_id"))
        # 通过request.post得到类型数据,是个列表
        type_lsts = post_data.getlist("type")
        # 通过request.FILES获取上传的图片
        store_logo = request.FILES.get("store_logo")

        # 保存正常数据
        store = Store()
        store.store_name = store_name
        store.store_address = store_address
        store.store_description = store_description
        store.store_phone = store_phone
        store.store_money = store_money
        store.user_id = user_id
        store.store_logo = store_logo
        store.save()

        # 在生成数据中添加多对多关系字段
        for i in type_lsts:# 循环type列表,得到类型id
            store_type = StoreType.objects.get(id=i)#查询类型数据
            store.type.add(store_type)# 添加到类型字段,多对多的映射表
        store.save()


    return render(request,"store/store_register.html",locals())

六、添加商品

前端页面:

{% extends "store/base.html" %}

{% block title %}
    后台管理首页
{% endblock %}

        {% block content %}
        <form class="form" method="post" enctype="multipart/form-data">
            <div class="form-group">
                {% csrf_token %}
            </div>
            <div class="form-group">
                <input type="text" class="form-control form-control-user"  name="goods_name" placeholder="商品名称">
            </div>
            <div class="form-group">
                <input type="text" class="form-control form-control-user"  name="goods_price" placeholder="商品价格">
            </div>
            <div class="form-group">
                <input type="text" class="form-control form-control-user"  name="goods_image" placeholder="商品图片">
            </div>
            <div class="form-group">
                <input type="file" class="form-control form-control-user"  name="goods_number" placeholder="商品库存">
            </div>
            <div class="form-group">
                <input type="text" class="form-control form-control-user"  name="goods_description" placeholder="商品描述">
            </div>

            <div class="form-group">
                <input type="text" class="form-control form-control-user"  name="goods_date" placeholder="出厂日期">
            </div>
            <div class="form-group">
                <input type="text" class="form-control form-control-user"  name="goods_safeDate" placeholder="保质期(月)">
            </div>
            <div class="form-group">
                <input type="hidden" class="form-control form-control-user"  name="store_id" value="1">
            </div>
            <div class="form-group">
                <input class="btn btn-primary btn-block" type="submit" value="添加">
            </div>
        </form>
        {% endblock %}

后端业务视图:

# v1.6添加商品
def add_goods(request):
    if request.method == "POST":
        # v1.6 获取前端post请求数据
        good_postData = request.POST
        # v1.6 通过前端name字段获取实际的值存储起来
        goods_name = good_postData.get("goods_name")
        goods_price = good_postData.get("goods_price")
        goods_number = good_postData.get("goods_number")
        goods_description = good_postData.get("goods_description")
        goods_date = good_postData.get("goods_date")
        goods_safeDate = good_postData.get("goods_safeDate")
        # v1.6 注意图片是通过FILES方式获取
        goods_image = request.FILES.get("goods_image")
        # v1.6 多对多关系字段,前端使用隐藏域
        store_id = good_postData.get("store_id")

        # 保存正常数据
        goods = Goods() # 实例化一个商品对象
        goods.goods_name = goods_name
        goods.goods_price = goods_price
        goods.goods_number = goods_number
        goods.goods_description = goods_description
        goods.goods_date = goods_date
        goods.goods_safeDate = goods_safeDate
        goods.goods_image = goods_image
        goods.save() # 保存一条记录

        # 保存多对多数据(注意get方式获取到的数据为字符串)
        goods.store_id.add(
            Store.objects.get(id=int(store_id))
        )
        goods.save()


    return render(request,"store/add_goods.html")

七、商品列表

前端:

{% extends "store/base.html" %}

{% block title %}
    商品列表页面
{% endblock %}

{% block label %}
    <a class="btn btn-warning" href="/Store/add_good/">添加商品</a>
{% endblock %}

{% block content %}
    <table class="table-bordered table">
        <thead>
            <tr>
                <th>商品名称</th>
                <th>商品价格</th>
                <th>商品数量</th>
                <th>出厂日期</th>
                <th>保质期</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
            {% for goods in goods_list %}
            <tr>
                <td>{{ goods.goods_name }}</td>
                <td>{{ goods.goods_price }}</td>
                <td>{{ goods.goods_number }}</td>
                <td>{{ goods.goods_date }}</td>
                <td>{{ goods.goods_safeDate }}</td>
                <td>
                    <a class="btn btn-danger" href="#">下架</a>
                    <a class="btn btn-primary" href="#">销毁</a>
                </td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
{% endblock %}

后端:

# v1.7 展示商品列表
def list_goods(request):
    # v1.7 查询所有商品信息(提前添加了商品数据)
    goods_list = Goods.objects.all()
    return render(request,"store/goods_list.html",locals())

效果:

八、添加商品列表搜索功能

前端:

后端:

# v1.7 展示商品列表
def list_goods(request):
    # v1.8 添加keywords关键字字段,用户前端搜索
    keywords = request.GET.get("keywords","")
    if keywords:
        # v1.8 对关键字进行模糊查询
        goods_list = Goods.objects.filter(goods_name__contains=keywords)
    else:
        # v1.7 查询所有商品信息(提前添加了商品数据)
        goods_list = Goods.objects.all()
    return render(request,"store/goods_list.html",locals())

效果:

九、商品列表页搜索分页功能

后端:

# v1.7 展示商品列表
def list_goods(request):
    # v1.8 添加keywords关键字字段,用户前端搜索
    keywords = request.GET.get("keywords","")
    # v1.9 获取前端页码,默认页码1
    page_num = request.GET.get("page_num",1)
    if keywords:
        # v1.8 对关键字进行模糊查询
        goods_list = Goods.objects.filter(goods_name__contains=keywords)
    else:
        # v1.7 查询所有商品信息(提前添加了商品数据)
        goods_list = Goods.objects.all()
    # v1.9 新增列表分页功能,创建分页器,针对good_list中的数据,每页3条数据
    paginator = Paginator(goods_list,3)
    # v1.9 获取具体页的数据
    page = paginator.page(int(page_num))
    # v1.9 返回页码列表
    page_range = paginator.page_range
    # 返回分页数据
    return render(request,"store/goods_list.html",{"page":page,"page_range":page_range,"keywords":keywords})

前端:

{% extends "store/base.html" %}

{% block title %}
    商品列表页面
{% endblock %}

{% block label %}
    <a class="btn btn-warning" href="/Store/add_good/">添加商品</a>
{% endblock %}

{% block content %}
    <table class="table-bordered table">
        <thead>
            <tr align="center">
                <th>商品名称</th>
                <th>商品价格</th>
                <th>商品数量</th>
                <th>出厂日期</th>
                <th>保质期</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
{#            {% for goods in goods_list %}#}
            {% for goods in page %}
            <tr align="center">
                <td>{{ goods.goods_name }}</td>
                <td>
                    <input type="text" value="{{ goods.goods_price }}" style="text-align: center">
                </td>
                <td>{{ goods.goods_number }}</td>
                <td>{{ goods.goods_date }}</td>
                <td>{{ goods.goods_safeDate }}</td>
                <td>
                    <a class="btn btn-danger" href="#">下架</a>
                    <a class="btn btn-primary" href="#">销毁</a>
                </td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
    <div class="dataTables_paginate paging_simple_numbers">
        <ul class="pagination">
            {% for p in page_range %}
            <li class="paginate_button page-item ">
                <a class="page-link" href="?keywords={{ keywords }}&page_num={{ p }}">{{ p }}</a>
            </li>
            {% endfor %}
        </ul>
    </div>
{% endblock %}

效果:

九、商品详情页添加

1、新建一个商品详情页面goods.html

{% extends "store/base.html" %}

{% block title %}
    {{ goods_data.goods_name }} 详情
{% endblock %}

{% block content %}
    <table class="table">
        <tr>
            <td rowspan="3">
                <img style="width: 200px;height: 200px;" src="/static/{{ goods_data.goods_image }}">
            </td>
            <th>
                商品名称
            </th>
            <td colspan="3">
                {{ goods_data.goods_name }}
            </td>
        </tr>

        <tr>
            <th>
                商品价格
            </th>
            <td>
                {{ goods_data.goods_price }}
            </td>
            <th>
                商品库存
            </th>
            <td>
                {{ goods_data.goods_number }}
            </td>
        </tr>

        <tr>
            <th>
                生产日期
            </th>
            <td>
                {{ goods_data.goods_date }}
            </td>
            <th>
                保质期
            </th>
            <td>
                {{ goods_data.goods_safeDate }}
            </td>
        </tr>

        <tr>
            <th colspan="5" style="text-align: center;">商品描述</th>
        </tr>
        <tr>
            <td colspan="5">{{ goods_data.goods_description }}</td>
        </tr>
    </table>
    <a class="btn btn-primary btn-block" href="#">修改商品信息</a>
{% endblock %}

2、视图,路由

3、在列表页绑定路由

4、通过点击商品列表中的商品跳转到商品详情页

效果:

十、商品信息修改功能实现

1、新建一个前端修改商品信息页面update_goods.html

{% extends "store/base.html" %}

{% block title %}
    {{ goods_data.goods_name }} 详情
{% endblock %}

{% block content %}
    <form class="form" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <table class="table">
        <tr>
            <td rowspan="3">
                <img style="width: 200px;height: 200px;" src="/static/{{ goods_data.goods_image }}">
                <input class="form-control form-control-user" type="file" name="goods_image">
            </td>
            <th>
                商品名称
            </th>
            <td colspan="3">
                <input class="form-control form-control-user" type="text" name="goods_name" value="{{ goods_data.goods_name }}">
            </td>
        </tr>

        <tr>
            <th>
                商品价格
            </th>
            <td>
                <input class="form-control form-control-user" type="text" name="goods_price" value="{{ goods_data.goods_price }}">
            </td>
            <th>
                商品库存
            </th>
            <td>
                <input class="form-control form-control-user" type="text" name="goods_number" value="{{ goods_data.goods_number }}">
            </td>
        </tr>

        <tr>
            <th>
                生产日期
            </th>
            <td>
                <input class="form-control form-control-user" type="text" name="goods_date" value="{{ goods_data.goods_date }}">
            </td>
            <th>
                保质期
            </th>
            <td>
                <input class="form-control form-control-user" type="text" name="goods_safeDate" value="{{ goods_data.goods_safeDate }}">
            </td>
        </tr>

        <tr>
            <th colspan="5" style="text-align: center;">商品描述</th>
        </tr>
        <tr>
            <td colspan="5">
                <textarea class="form-control form-control-user" name="goods_description">
                    {{ goods_data.goods_description }}
                </textarea>
            </td>
        </tr>

        <tr>
            <td colspan="5" style="text-align: center;">
                <input class="btn btn-primary btn-block" type="submit" value="保存修改">
            </td>
        </tr>
    </table>
    </form>
{% endblock %}

2、后端简单视图及路由添加和商品详细类似,展示效果

3、使用富文本编辑器修改商品描述

修改setting配置

修改主url

4、收集静态文件(注意注释配置,收集后,在回复配置)

5、前端导入富文本js文件,并应用到商品描述

{# v2.1 导入ckeditor js文件,并应用到商品描述#}
{% block script %}
    <script src="/static/ckeditor/ckeditor/ckeditor.js"></script>
    <script>
        CKEDITOR.replace("goods_description",{uiColor:"#9AB8F3"})
    </script>
{% endblock %}

效果:

6、视图添加数据修改保存操作

修改界面:

修改信息后跳转商品详情:

十一、校验用户是否有商铺功能

1、在用户登录时下发校验店铺的cookie

2、在base页重新进行前端店铺校验

    <!-- Sidebar -->
    {% if request.COOKIES.is_store %}
    <ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar">

      <!-- Sidebar - Brand -->
      <a class="sidebar-brand d-flex align-items-center justify-content-center" href="index.html">
        <div class="sidebar-brand-icon rotate-n-15">
          <i class="fas fa-laugh-wink"></i>
        </div>
        <div class="sidebar-brand-text mx-3">后台管理系统</div>
      </a>

      <!-- Divider -->
      <hr class="sidebar-divider my-0">

      <!-- Nav Item - Dashboard -->
      <li class="nav-item">
{#        {% if is_store %}#}
        <a class="nav-link" href="#">
          <i class="fas fa-fw fa-tachometer-alt"></i>
                <span>店铺管理</span></a>
{#        {% else %}#}
{#        <a class="nav-link" href="/Store/store_register/">#}
{#          <i class="fas fa-fw fa-tachometer-alt"></i>#}
{#                <span>没有店铺,注册一个</span></a>#}
{#        {% endif %}#}

      </li>

      <!-- Divider -->
      <hr class="sidebar-divider">

      <!-- Heading -->
      <div class="sidebar-heading">
        销售管理
      </div>

      <!-- Nav Item - Pages Collapse Menu -->
      <li class="nav-item">
        <a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="true" aria-controls="collapseTwo">
          <i class="fas fa-fw fa-cog"></i>
          <span>商品管理</span>
        </a>
        <div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionSidebar">
          <div class="bg-white py-2 collapse-inner rounded">
            <h6 class="collapse-header">商品信息:</h6>
            <a class="collapse-item" href="/Store/add_good/">添加商品</a>
            <a class="collapse-item" href="/Store/goods_list/">商品列表</a>
          </div>
        </div>
      </li>

      <!-- Nav Item - Utilities Collapse Menu -->

      <!-- Divider -->
{#      <hr class="sidebar-divider">#}

      <!-- Nav Item - Charts -->
{#      <li class="nav-item">#}
{#        <a class="nav-link" href="charts.html">#}
{#          <i class="fas fa-fw fa-chart-area"></i>#}
{#          <span>图表</span></a>#}
{#      </li>#}

      <!-- Nav Item - Tables -->
{#      <li class="nav-item">#}
{#        <a class="nav-link" href="tables.html">#}
{#          <i class="fas fa-fw fa-table"></i>#}
{#          <span>表格</span></a>#}
{#      </li>#}

      <!-- Divider -->
{#      <hr class="sidebar-divider d-none d-md-block">#}

      <!-- Sidebar Toggler (Sidebar) -->
      <div class="text-center d-none d-md-inline">
        <button class="rounded-circle border-0" id="sidebarToggle"></button>
      </div>

    </ul>
    <!-- End of Sidebar -->
    {% else %}
    <ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar">

      <!-- Sidebar - Brand -->
      <a class="sidebar-brand d-flex align-items-center justify-content-center" href="index.html">
        <div class="sidebar-brand-icon rotate-n-15">
          <i class="fas fa-laugh-wink"></i>
        </div>
        <div class="sidebar-brand-text mx-3">后台管理系统</div>
      </a>

      <!-- Divider -->
      <hr class="sidebar-divider my-0">

      <!-- Nav Item - Dashboard -->
      <li class="nav-item">
        <a class="nav-link" href="/Store/store_register/">
          <i class="fas fa-fw fa-tachometer-alt"></i>
                <span>没有店铺,注册一个</span></a>
      </li>

      <!-- Divider -->
      <hr class="sidebar-divider">

      <div class="text-center d-none d-md-inline">
        <button class="rounded-circle border-0" id="sidebarToggle"></button>
      </div>

    </ul>
    {% endif %}

3、后端更新装饰器店铺cookie下发并装饰到店铺注册函数视图上

效果展示:

十二、业务逻辑实现:查看商品列表是针对当前用户的店铺

1、更改添加商品页面的店铺id为自动获取cookie,每次添加商品的时候就会自动关联当前页面cookie中的店铺id

    <div class="form-group">
        <input type="hidden" class="form-control form-control-user"  name="store_id" value="{{ request.COOKIES.is_store }}">
    </div>

2、修改后端商品列表视图,使用多对多关系反向查询

十三、商品下架功能

1、商品模型类添加商品状态字段,并同步数据库

# v2.4 新增商品状态字段;1 待售 0 下架
    goods_under = models.IntegerField(verbose_name="商品状态",default=1)

2、更改商品列表视图,是查询上架商品

3、新增商品下架视图函数

# v2.4 新增商品上架功能
def under_goods(request):
    id = request.GET.get("id")
    # v2.4 返回当前请求的来源地址
    referer = request.META.get("HTTP_REFERER")
    if id:
        # v2.4 获取指定id的商品
        goods = Goods.objects.filter(id=id).first()
        # v2.4 修改商品状态
        goods.goods_under = 0
        goods.save()
    return HttpResponseRedirect(referer)

4、更新商品列表前端页面下架标签

<a class="btn btn-danger" href="/Store/goods_under/?id={{ goods.id }}">下架</a>

十三、商品上架及销毁功能

1、base页新增下架商品的列表

  <div class="bg-white py-2 collapse-inner rounded">
            <h6 class="collapse-header">商品信息:</h6>
            <a class="collapse-item" href="/Store/add_good/">添加商品</a>
            <a class="collapse-item" href="/Store/goods_list/up/">在售商品列表</a>
            <a class="collapse-item" href="/Store/goods_list/down/">下架商品列表</a>
          </div>

2、为了使上下架商品列表不出现代码冗余,对商品列表视图及前端进行状态判断

{% ifequal state 'up' %}
   <a class="btn btn-danger" href="/Store/set_goods/down/?id={{ goods.id }}">下架</a>
{% else %}
   <a class="btn btn-danger" href="/Store/set_goods/up/?id={{ goods.id }}">上架</a>
{% endifequal %}
   <a class="btn btn-primary" href="/Store/set_goods/delete/?id={{ goods.id }}">销毁</a>

3、将前面的under_goods视图该为set_goods,对页面上下架按钮提交做判断进行相应的处理

python
# v2.4 新增商品上架功能
def set_goods(request,state):
    # v2.5 使该视图同时具备上下架及销毁的功能
    if state == "up":
        state_num = 1
    else:
        state_num = 0
    id = request.GET.get("id")
    # v2.4 返回当前请求的来源地址
    referer = request.META.get("HTTP_REFERER")
    if id:
        # v2.4 获取指定id的商品
        goods = Goods.objects.filter(id=id).first()
        # v2.5 判断前端路由中的字段参数来进行相应的操作,这里如果为delete,删除该商品
        if state == "delete":
            goods.delete()
        else:
            # v2.4 修改商品状态
            goods.goods_under = state_num
            goods.save()
    return HttpResponseRedirect(referer)

十三、前台模型创建

1、创建买家App,并创建模型类

2、安装App,同步模型类

3、将前台模板静态文件拷贝到App的子url,然后配置url,收集静态文件

4、编写base页

5、编写注册登录功能

注册视图:

# v2.6 前台用户注册
def register(request):
    if request.method == "POST":
        # 获取前端post请求的数据
        username = request.POST.get("user_name")
        password = request.POST.get("pwd")
        email = request.POST.get("email")

        # 实例化一个前台用户
        buyer = Buyer()
        # 保存数据
        buyer.username = username
        buyer.password = setPassword(password)
        buyer.email = email
        buyer.save()
        return HttpResponseRedirect('/Buyer/login/')
    return render(request,"buyer/register.html")

效果:

登录视图:

# v2.6 前台用户登录
def login(request):
    if request.method == "POST":
        # 获取前端登录页面文本框数据
        username = request.POST.get("username")
        password = request.POST.get("pwd")
        # 判断用户名密码是否存在
        if username and password:
            # 判断用户是否存在
            buyer = Buyer.objects.filter(username=username).first()
            if buyer:
                # 密码加密比对
                web_password = setPassword(password)
                if web_password == buyer.password:
                    response = HttpResponseRedirect('/Buyer/index/')
                    response.set_cookie("username",buyer.username)
                    request.session["username"] = buyer.username
                    # 在下发一个user_id的信息方便其他功能查询
                    response.set_cookie("user_id",buyer.id)
                    return response
    return render(request,"buyer/login.html")

效果:

将后端的装饰器拿过来并应用首页视图

# v2.6 登录校验装饰器
def loginValid(fun):
    def inner(request,*args,**kwargs):
        c_user = request.COOKIES.get("username")
        s_user = request.session.get("username")
        if c_user and s_user and c_user == s_user:
            return fun(request, *args, **kwargs)
        else:
            return HttpResponseRedirect("/Buyer/login/")
    return inner

# v2.6 前台用户首页
@loginValid
def index(request):
    return render(request,"buyer/index.html",locals())

退出功能:


# v2.6 前台用户注销
def logout(request):
    response = HttpResponseRedirect('/Buyer/login/')
    for key in request.COOKIES:
        response.delete_cookie(key)
    del request.session["username"]
    return response

base页cookie功能校验顶部功能,如果用户登录,就显示退出,否则显示登录和注册

{% if request.COOKIES.username %}
<div class="login_btn fl">
	<a href="/Buyer/logout/">退出</a>
</div>
{% else %}
<div class="login_btn fl">
	<a href="login.html">登录</a>
	<span>|</span>
	<a href="register.html">注册</a>
</div>
{% endif %}

十四、后台新增新增商品类型模型,并在商品表中添加关联外键商品类型字段**

为了避免后续大数据出现差错,我先手动去添加一个商品类型,网上选取4张商品图片作为一个类型的演示

前台index视图添加查询商品类型的语句,先导入后端模型类

def index(request):
    goods_type_list = GoodsType.objects.all()
    return render(request,"buyer/index.html",locals())

然后在index前端页面进行类型及类型中商品的循环展示

{% block content %}
<div class="center_con clearfix">
<ul class="subnav fl">
	<li><a href="#model01" class="fruit">新鲜水果</a></li>
	<li><a href="#model02" class="seafood">海鲜水产</a></li>
	<li><a href="#model03" class="meet">猪牛羊肉</a></li>
	<li><a href="#model04" class="egg">禽类蛋品</a></li>
	<li><a href="#model05" class="vegetables">新鲜蔬菜</a></li>
	<li><a href="#model06" class="ice">速冻食品</a></li>
</ul>

<div class="slide fl">
	<ul class="slide_pics">
		<li><img src="/static/buyer/images/slide.jpg" alt="幻灯片"></li>
		<li><img src="/static/buyer/images/slide02.jpg" alt="幻灯片"></li>
		<li><img src="/static/buyer/images/slide03.jpg" alt="幻灯片"></li>
		<li><img src="/static/buyer/images/slide04.jpg" alt="幻灯片"></li>
	</ul>
	<div class="prev"></div>
	<div class="next"></div>
	<ul class="points"></ul>
</div>
<div class="adv fl">
	<a href="#"><img src="/static/buyer/images/adv01.jpg"></a>
	<a href="#"><img src="/static/buyer/images/adv02.jpg"></a>
</div>
</div>
{% for goods_type in goods_type_list %}
<div class="list_model">
<div class="list_title clearfix">
	<h3 class="fl" id="model01">{{ goods_type.name }}</h3>
	<div class="subtitle fl">
		<span>|</span>
		<a href="#">鲜芒</a>
		<a href="#">加州提子</a>
		<a href="#">亚马逊牛油果</a>
	</div>
	<a href="#" class="goods_more fr" id="fruit_more">查看更多 ></a>
</div>

<div class="goods_con clearfix">
	<div class="goods_banner fl"><img src="/static/buyer/images/banner01.jpg"></div>
	<ul class="goods_list fl">
        {% for goods in goods_type.goods_set.all %}
		<li>
			<h4><a href="#">{{ goods.goods_name }}</a></h4>
			<a href="#"><img src="/static/{{ goods.goods_image }}"></a>
			<div class="prize">¥ {{ goods.goods_price }}</div>
		</li>
        {% endfor %}
	</ul>
</div>
</div>
{% endfor %}
{% endblock %}

效果:

十五、后台新增添加商品类型的功能(使用模态框)

1、在后台base模板页新增“商品类型管理”选项

<li class="nav-item">
<a class="nav-link collapsed" href="/Store/list_goods_type/">
  <i class="fas fa-fw fa-cog"></i>
  <span>商品类型管理</span>
</a>
</li>

2、新建一个商品类型管理html文件 这里的使用bootstrap模态框进行实现,模板样式基本固定

{% extends "store/base.html" %}

{% block title %}
    商品类型管理
{% endblock %}

{% block label %}
    <button class="btn btn-warning" data-toggle="modal" data-target="#myModal">添加商品类型</button>
{% endblock %}

{% block content %}
<table class="table">
<thead>
    <tr>
        <th style="text-align: center;">商品类型名称</th>
        <th style="text-align: center;">商品类型描述</th>
        <th style="text-align: center;">操作</th>
    </tr>
</thead>

<tbody>
    {% for goods_type in goods_type_lst%}
    <tr>
        <td style="text-align: center;">{{ goods_type.name }}</td>
        <td style="text-align: center;">{{ goods_type.description }}</td>
        <td style="text-align: center;">
            <a class="btn btn-danger" href="/Store/delete_goods_type/?id={{ goods_type.id }}">删除</a>
        </td>
    </tr>
    {% endfor %}
</tbody>
</table>

{#    <div class="dataTables_paginate paging_simple_numbers">#}
{#        <ul class="pagination">#}
{#            {% for p in page_range %}#}
{#            <li class="paginate_button page-item">#}
{#                <a class="page-link" href="?keywords={{ keywords }}&page_num={{ p }}">{{ p }}</a>#}
{#            </li>#}
{#            {% endfor %}#}
{#        </ul>#}
{#    </div>#}

<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModal" aria-hidden="true">
<div class="modal-dialog">
    <div class="modal-content">
        <div class="modal-header">
            <h4 class="modal-title" id="myModalLabel">添加类型</h4>
            <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
        </div>
<form method="post" class="form">
    <div class="modal-body">
        {% csrf_token %}
        <div class="form-group">
            <input type="text" name="name" class="form-control form-control-user" placeholder="类型名称">
        </div>
        <div class="form-group">
            <input type="text" name="description" class="form-control form-control-user" placeholder="类型描述">
        </div>
        <div class="form-group">
            <input type="file" name="picture" class="form-control form-control-user" placeholder="类型图片">
        </div>
    </div>
    <div class="modal-footer">
        <button type="submit" class="btn btn-primary">保存类型</button>
    </div>
</form>
    </div>
</div>
</div>
{% endblock %}

3、商品类型管理视图、路由配置

# v2.8 后台新增商品类型管理,进行商品类型的添加
def goods_type_list(request):
    # v2.8 查询现有的商品类型,渲染到前端
    goods_type_lst = GoodsType.objects.all()
    # 判断请求方式,并获取从模态框传过来的商品类型数据
    if request.method == "POST":
        name = request.POST.get("name")
        description = request.POST.get("description")
        picture = request.FILES.get("picture")

        # 实例化商品类型,开始添加类型保存
        goods_type = GoodsType()
        goods_type.name = name
        goods_type.description = description
        goods_type.picture = picture
        goods_type.save()
    return render(request,"store/goods_type_list.html",locals())

删除商品类型视图及前端路径

# v2.8 删除商品类型
def delete_goods_type(request):
    # 前端通过参数id={{goods_type.id}来获取要删除类型对象
    id = int(request.GET.get("id"))
    goods_type = GoodsType.objects.get(id=id)
    goods_type.delete()
    return HttpResponseRedirect('/Store/list_goods_type/')
<a class="btn btn-danger" href="/Store/delete_goods_type/?id={{ goods_type.id }}">删除</a>

十六、后台添加商品功能的前后端添加商品类型字段

后端:

前端:

效果:

十七、前台商品首页列表展示优化

查询所有有数据的商品类型,并且只返回4种商品展示在首页

后台首页视图业务处理:

前台首页循环:

{#    {% for goods_type in goods_type_list %}#}
    {% for goods_type in result_list %}
	<div class="list_model">
		<div class="list_title clearfix">
			<h3 class="fl" id="model01">{{ goods_type.name }}</h3>
			<div class="subtitle fl">
				<span>|</span>
				<a href="#">鲜芒</a>
				<a href="#">加州提子</a>
				<a href="#">亚马逊牛油果</a>
			</div>
			<a href="#" class="goods_more fr" id="fruit_more">查看更多 ></a>
		</div>

		<div class="goods_con clearfix">
			<div class="goods_banner fl"><img src="/static/{{ goods_type.picture }}"></div>
			<ul class="goods_list fl">
{#                {% for goods in goods_type.goods_set.all %}#}
                {% for goods in goods_type.goods_list %}
				<li>
					<h4><a href="#">{{ goods.goods_name }}</a></h4>
					<a href="#"><img src="/static/{{ goods.goods_image }}"></a>
					<div class="prize">¥ {{ goods.goods_price }}</div>
				</li>
                {% endfor %}
			</ul>
		</div>
	</div>
    {% endfor %}
{% endblock %}

效果:

十八、前台商品列表展示

新建列表html文档,继承模板,并使用后端数据渲染

<ul class="goods_type_list clearfix">
{% for goods in goodsList%}
<li>
	<a href="detail.html"><img src="/static/{{ goods.goods_image }}"></a>
	<h4><a href="detail.html">{{ goods.goods_name }}</a></h4>
	<div class="operate">
		<span class="prize">¥{{ goods.goods_price }}</span>
		<span class="unit">{{ goods.goods_price }}/500g</span>
		<a href="#" class="add_goods" title="加入购物车"></a>
	</div>
</li>
{% endfor %}
</ul>

编写前台商品列表视图,通过“首页查看更多链接”跳转列表页面,查询当前商品类型下的所有商品

# v3.1 前台商品列表展示
def goods_list(request):
    """
    前台列表页
    :param request:
    :return:
    """
    goodsList = []
    type_id = request.GET.get("type_id")
    # v3.1 获取商品类型
    goods_type = GoodsType.objects.filter(id = type_id).first()
    if goods_type:
        # v3.1 查询所有上架的产品
        goodsList = goods_type.goods_set.filter(goods_under=1)
    return render(request,"buyer/goods_list.html",locals())

首页查看更多链接添加商品列表路由

<a href="/Buyer/goods_list/?type_id={{ goods_type.id }}" class="goods_more fr" id="fruit_more">查看更多 ></a>

效果:

十九、支付宝沙箱环境部署并测试

1、打开支付宝开发平台地址并扫码登录 https://open.alipay.com/platform/home.htm

2、确定入驻身份填写信息,完成入驻

3、进入开发中心,开发阶段先用沙箱环境进行测试

4、进入沙箱环境,查看沙箱应用

5、下载沙箱版支付宝(仅安卓版本)

6、打开支付宝开发文档然后查看开发手册,进行开发

https://docs.open.alipay.com/

生成RSA公钥,下载生成工具

下载完成进行解压并打开RSA签名验签工具生成公私钥

同时在生成工具的目录下RSA秘钥目录下也有文件版本

然后将公钥设置到服务端

查看接入手册,接入当前开发业务类型接口

使用快速接入,下载服务端SDK

7、下载支付宝SDK开发的python模块

支付流程:

  • 接收订单
  • 跳转请求支付宝
  • 接收,并将支付页面发给买家
  • 买家完成付款

pip install pycryptodome 安装依赖包

pip install python-alipay-sdk --upgrade 安装支付宝SDK

8、安装完成编写脚本测试支付功能

from alipay import AliPay

alipay_public_key_string = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv66JqpsopyoXYMiWiIgyV4O/nc4ptXjRgZO9dkKRzsh1LusdILASoXlZ65nx/4ONCDpFn5QEQQNerVtmCW+Y9N/GmNnQOsEeX5tCxfNlg7vHS5Hk8QDCgIbEzVC3K+9wwYiCc8aQRjSM+Czb/Tq3kI+XJpDIGE6lPtp2zkwZaPt3y8yt88MpYcqPllNn3acEW8U5LnQmMHosohqlXu5iPK57OC7a0oC5AwUPlZcMizO2EqmxonWpfqk+scOhVdyVUwuX6siye76OkUuhO8M1758hhhNOUhmzhurEEW20toqA2eoMP63GweyTt5kWmWcqc30YU0FAN8Aq3QG03wF3xQIDAQAB
-----END PUBLIC KEY-----"""

app_private_key_string = """-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAv66JqpsopyoXYMiWiIgyV4O/nc4ptXjRgZO9dkKRzsh1LusdILASoXlZ65nx/4ONCDpFn5QEQQNerVtmCW+Y9N/GmNnQOsEeX5tCxfNlg7vHS5Hk8QDCgIbEzVC3K+9wwYiCc8aQRjSM+Czb/Tq3kI+XJpDIGE6lPtp2zkwZaPt3y8yt88MpYcqPllNn3acEW8U5LnQmMHosohqlXu5iPK57OC7a0oC5AwUPlZcMizO2EqmxonWpfqk+scOhVdyVUwuX6siye76OkUuhO8M1758hhhNOUhmzhurEEW20toqA2eoMP63GweyTt5kWmWcqc30YU0FAN8Aq3QG03wF3xQIDAQABAoIBAHSkuL+qJcX79jf+OKSjBMd+s/dKwtTczdklV5EEl4gXMkA38QS4QM4kc5TMnJgZrJQKKd4fC6uoak/iI6iwUYsKNedD/NQUOvCBIdQl9muAtJmHEaObC8F8wXwTlzPURHBxKrlbZuZiCjrnyYNC3PvKdXeReUJZcXNbLBsD8h6QgVOPaKXnLCSTZf98gHXkDN0IWlNp08143b4Xp6DO+CiaUHUbUrAcHdB+gHQJnN3+YP3dnVGzvyRisjJ8B8mTS1zPk4fpciViA6EMCYLHFCrS+8LZ+WEpJN8o/0o9Mgpd8YPKg1q4WDlYp1ulnRUVqOkJaVZVOvecTtbPbyaJWUECgYEA9fj7peww3VmPNYS3WAAtm/M5YMgsuFNRWC61/o+FKDSVlH57dVRRTnnGF7NE5dUHldloAa+yBw5Zbi/s5LjYV36zvga4dYZpA8Lt4uglzg3QAXwQ+X5YRqkde1gGolOdU/MsnhvehQpdOoZ5XWzJHe/2kA8RNraZyYsER0PQ2hECgYEAx373L2VZZdhPT8EIrgrAU93izxZAS9Cp5A/whogxmVhaKfGHcJa4YygajzyKd3DRoSmYtbXqysGXZyY+RmolCyMeZEBZ1s/2DTj2vtdh3ngV6UQY4vbgpCBgDCOnoiAq/5whcFcTDjma79BmsdHng/ZXgo+5U6v4TrKdZ7ay7nUCgYEArEZnkk175/xLFjPO6d6uExTmMgfhcnRAe9+zbgh9PayeuzNfKs0UaT9W49CWR9bNikGL2+p/aPu+3TLJ22QvehBuuYAhf4bVVGIZlRv9JnV8Ix4PEX9ROqRF1tbPRrADeAHQVSi10D5zD4ORy0JfFg20hi9XYhfAXG12YKd5xtECgYB8t3A6ziZsWCWFG42cmJYSGDYx9pwtiX6cWCarRDuVvTlo3Vkp1t/hBXJNN7Ds6Lf1A/c3KkplhU9sqejmxnbwFn1qeRxxAcO2EnWXazkBBpvUH8FbKrHXiXHiROwInAmlkOsKuzTrgLHO2L9KzYnp4rhkpAtdNrZeJKXo77u+/QKBgF4DmigjHYR32dmKXZQYp2DkM7lFxnuL6McR0r/5b/wkMUPSInCK4n4e8vBH611dkaNNhNSTR5aVsrMZuBGzQrFOGnXawa+PxpGPaVwR/jf/tPCLmsq2KPenQPF15tc+4dosMp726+f+4Klg71qMK8yjGN+fkrHo3Er1y+35Letj
-----END RSA PRIVATE KEY-----"""

# v3.2 实例化支付应用
alipay = AliPay(
    appid = "2016101000652510",
    app_notify_url = None,
    app_private_key_string = app_private_key_string,
    alipay_public_key_string = alipay_public_key_string,
    sign_type= "RSA2"
)

# v3.2 发起支付请求
order_string = alipay.api_alipay_trade_page_pay(
    out_trade_no="33456", #订单号
    total_amount=str(1000.01),#支付金额
    subject="生鲜交易", #交易主题
    return_url=None,
    notify_url=None
)

print("https://openapi.alipaydev.com/gateway.do?"+order_string)

运行该脚本,会生成一个链接点击它,效果如下:

使用沙箱版支付宝支付:

现在开放平台中的沙箱环境的商家账号已经有金额入账了,表示测试成功

二十、将支付宝接口应用到前台商品付款上

1、基于上述支付宝接口测试代码将代码封装为付款视图函数,并且用get请求发送了订单id和订单金额

# v3.3 将支付宝接口应用到前台付款,并用get请求发送支付金额和订单id
def pay_order(request):
    # v3.3 获取订单金额
    money = request.GET.get("money")
    # v3.3 获取订单id
    order_id = request.GET.get("order_id")
    # v3.2 定义变量存储支付宝应用公钥
    alipay_public_key_string = """-----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv66JqpsopyoXYMiWiIgyV4O/nc4ptXjRgZO9dkKRzsh1LusdILASoXlZ65nx/4ONCDpFn5QEQQNerVtmCW+Y9N/GmNnQOsEeX5tCxfNlg7vHS5Hk8QDCgIbEzVC3K+9wwYiCc8aQRjSM+Czb/Tq3kI+XJpDIGE6lPtp2zkwZaPt3y8yt88MpYcqPllNn3acEW8U5LnQmMHosohqlXu5iPK57OC7a0oC5AwUPlZcMizO2EqmxonWpfqk+scOhVdyVUwuX6siye76OkUuhO8M1758hhhNOUhmzhurEEW20toqA2eoMP63GweyTt5kWmWcqc30YU0FAN8Aq3QG03wF3xQIDAQAB
    -----END PUBLIC KEY-----"""
    # v3.2 定义变量存储支付宝应用私钥
    app_private_key_string = """-----BEGIN RSA PRIVATE KEY-----
    MIIEowIBAAKCAQEAv66JqpsopyoXYMiWiIgyV4O/nc4ptXjRgZO9dkKRzsh1LusdILASoXlZ65nx/4ONCDpFn5QEQQNerVtmCW+Y9N/GmNnQOsEeX5tCxfNlg7vHS5Hk8QDCgIbEzVC3K+9wwYiCc8aQRjSM+Czb/Tq3kI+XJpDIGE6lPtp2zkwZaPt3y8yt88MpYcqPllNn3acEW8U5LnQmMHosohqlXu5iPK57OC7a0oC5AwUPlZcMizO2EqmxonWpfqk+scOhVdyVUwuX6siye76OkUuhO8M1758hhhNOUhmzhurEEW20toqA2eoMP63GweyTt5kWmWcqc30YU0FAN8Aq3QG03wF3xQIDAQABAoIBAHSkuL+qJcX79jf+OKSjBMd+s/dKwtTczdklV5EEl4gXMkA38QS4QM4kc5TMnJgZrJQKKd4fC6uoak/iI6iwUYsKNedD/NQUOvCBIdQl9muAtJmHEaObC8F8wXwTlzPURHBxKrlbZuZiCjrnyYNC3PvKdXeReUJZcXNbLBsD8h6QgVOPaKXnLCSTZf98gHXkDN0IWlNp08143b4Xp6DO+CiaUHUbUrAcHdB+gHQJnN3+YP3dnVGzvyRisjJ8B8mTS1zPk4fpciViA6EMCYLHFCrS+8LZ+WEpJN8o/0o9Mgpd8YPKg1q4WDlYp1ulnRUVqOkJaVZVOvecTtbPbyaJWUECgYEA9fj7peww3VmPNYS3WAAtm/M5YMgsuFNRWC61/o+FKDSVlH57dVRRTnnGF7NE5dUHldloAa+yBw5Zbi/s5LjYV36zvga4dYZpA8Lt4uglzg3QAXwQ+X5YRqkde1gGolOdU/MsnhvehQpdOoZ5XWzJHe/2kA8RNraZyYsER0PQ2hECgYEAx373L2VZZdhPT8EIrgrAU93izxZAS9Cp5A/whogxmVhaKfGHcJa4YygajzyKd3DRoSmYtbXqysGXZyY+RmolCyMeZEBZ1s/2DTj2vtdh3ngV6UQY4vbgpCBgDCOnoiAq/5whcFcTDjma79BmsdHng/ZXgo+5U6v4TrKdZ7ay7nUCgYEArEZnkk175/xLFjPO6d6uExTmMgfhcnRAe9+zbgh9PayeuzNfKs0UaT9W49CWR9bNikGL2+p/aPu+3TLJ22QvehBuuYAhf4bVVGIZlRv9JnV8Ix4PEX9ROqRF1tbPRrADeAHQVSi10D5zD4ORy0JfFg20hi9XYhfAXG12YKd5xtECgYB8t3A6ziZsWCWFG42cmJYSGDYx9pwtiX6cWCarRDuVvTlo3Vkp1t/hBXJNN7Ds6Lf1A/c3KkplhU9sqejmxnbwFn1qeRxxAcO2EnWXazkBBpvUH8FbKrHXiXHiROwInAmlkOsKuzTrgLHO2L9KzYnp4rhkpAtdNrZeJKXo77u+/QKBgF4DmigjHYR32dmKXZQYp2DkM7lFxnuL6McR0r/5b/wkMUPSInCK4n4e8vBH611dkaNNhNSTR5aVsrMZuBGzQrFOGnXawa+PxpGPaVwR/jf/tPCLmsq2KPenQPF15tc+4dosMp726+f+4Klg71qMK8yjGN+fkrHo3Er1y+35Letj
    -----END RSA PRIVATE KEY-----"""

    # v3.2 实例化支付应用
    alipay = AliPay(
        appid="2016101000652510",
        app_notify_url=None,
        app_private_key_string=app_private_key_string,
        alipay_public_key_string=alipay_public_key_string,
        sign_type="RSA2"
    )

    # v3.2 发起支付请求
    order_string = alipay.api_alipay_trade_page_pay(
        out_trade_no=order_id,  # v3.3 订单号
        total_amount=str(money),  # v3.3 支付金额
        subject="生鲜交易",  # 交易主题暂时固定成“"生鲜交易"
        # v3.3 支付完成要跳转的本地路由
        return_url="http://127.0.0.1:8000/Buyer/pay_result/",
        # v3.3 支付完成要跳转的异步路由
        notify_url="http://127.0.0.1:8000/Buyer/pay_result/"
    )
    # v3.3 发起购买商品跳转支付路由
    return HttpResponseRedirect("https://openapi.alipaydev.com/gateway.do?" + order_string)

# v3.3 编写接收支付结果的视图,用于支付结束后前台网站给用户的响应页面
def pay_result(request):
    return HttpResponse("支付成功")

2、直接在浏览器输入栏上输入商品金额和订单号,测试能否成功,后续版本在将其添加到具体商品上

http://127.0.0.1:8000/Buyer/pay_order/?money=1200&order_id=10001

3、从地址栏中可以发现支付宝给我们返回的数据也是通过get方式发送的,来分析一下哪些参数我们可以利用

# 编码
charset=utf-8
# 订单号
out_trade_no=10001
# 订单类型
method=alipay.trade.page.pay.return
# 订单金额
total_amount=1200.00
# 校验值
sign=OqHkG0OypYgrTL%2Fu%2FJbZi1DaSHEhxOPEu1KRVWOF9IrDoUYpTw7P4mVZni2SOgsXWOxiET%2BaODHBOwk1qpu0T8P58OK41Ajv6Scy6KoEpi5USDL1Q0765i5EMTD4ei3x9tUzG07Wl9EWqKNk4Y3gFLfMdcsKJy%2BOF2aFdOyuO1UXJSRlSzg2HIz9ozR40BkHEHy7TH1Updnc7nOWDH4NCkowPQN0g8HSfX6shdfImvaSGHLC5rzMfmfFPlyEjy7HGuQ7u3APP2SZNCGTQKzE58NXD3w4tLGCJBl5nfmwgdWz3kUA0J9wWAzpZ7J0btdtBV7h1kOQAx9pWYJK%2BVvDAA%3D%3D
# 系统订单号
trade_no=2019072622001414251000021352
# 用户的应用id
auth_app_id=2016101000652510
# 版本
version=1.0
# 商家的应用id
app_id=2016101000652510
# 加密方式
sign_type=RSA2
# 商家id
seller_id=2088102178922754
# 时间
timestamp=2019-07-26+19%3A59%3A37

通过筛选其中一些参数,来优化一下我们支付结果响应页面,新建一个响应页面,用于响应支付成功后给用户的反馈

{% extends "buyer/base.html" %}

{% block title %}
    支付结果
{% endblock %}

{% block content %}
    <div style="width: 500px;height: 200px;font-size: 20px;margin-top: 50px">
        <h1 style="color: red">恭喜,支付成功</h1><br>
        <p>支付订单:{{ request.GET.out_trade_no }}</p>
        <p>支付时间:{{ request.GET.timestamp }}</p>
        <p>支付金额:{{ request.GET.total_amount }}</p>
    </div>
{% endblock %}

支付成功响应效果:

二十一、商品详情页功能完善

1、新建商品详情页html文件,继承base,并用块标签填充中间内容

2、商品详情页视图函数

# v3.4 商品详情页功能视图,通过get参数获取
def goods_detail(request):
    goods_id = request.GET.get("goods_id")
    if goods_id:
        goods = Goods.objects.filter(id=int(goods_id)).first()
        if goods:
            return render(request, "buyer/detail.html",locals())
    return HttpResponse("没有您指定的商品")

3、前端数据渲染,并使用js处理商品数量,总价的问题

{% extends "buyer/base.html" %}

{% block title %}
    商品详情
{% endblock %}

{% block content %}
    <div class="breadcrumb">
		<a href="#">全部分类</a>
		<span>></span>
		<a href="#">新鲜水果</a>
		<span>></span>
		<a href="#">商品详情</a>
	</div>

	<div class="goods_detail_con clearfix">
		<div class="goods_detail_pic fl"><img style="height: 350px;width: 350px;" src="/static/{{ goods.goods_image }}"></div>

		<div class="goods_detail_list fr">
			<h3>{{ goods.goods_name }}</h3>
			<p>{{ goods.goods_description }}</p>
			<div class="prize_bar">
				<span class="show_pirze">¥<em id="price">{{ goods.goods_price }}</em></span>
				<span class="show_unit">单  位:500g</span>
			</div>
			<div class="goods_num clearfix">
				<div class="num_name fl">数 量:</div>
				<div class="num_add fl">
					<input type="text" id="count" class="num_show fl" value="1">
					<a href="javascript:;" onclick="changecount('add')" class="add fr">+</a>
					<a href="javascript:;" onclick="changecount('minus')" class="minus fr">-</a>
				</div>
			</div>
			<div class="total">总价:<em id="total">{{ goods.goods_price }}</em><em></em></div>
			<div class="operate_btn">
				<a href="javascript:;" class="buy_btn">立即购买</a>
				<a href="javascript:;" class="add_cart" id="add_cart">加入购物车</a>
			</div>
		</div>
	</div>

	<div class="main_wrap clearfix">
		<div class="l_wrap fl clearfix">
			<div class="new_goods">
				<h3>新品推荐</h3>
				<ul>
					<li>
						<a href="#"><img src="/static/buyer/images/goods/goods001.jpg"></a>
						<h4><a href="#">进口柠檬</a></h4>
						<div class="prize">¥3.90</div>
					</li>
					<li>
						<a href="#"><img src="/static/buyer/images/goods/goods002.jpg"></a>
						<h4><a href="#">玫瑰香葡萄</a></h4>
						<div class="prize">¥16.80</div>
					</li>
				</ul>
			</div>
		</div>

		<div class="r_wrap fr clearfix">
			<ul class="detail_tab clearfix">
				<li class="active">商品介绍</li>
				<li>评论</li>
			</ul>

			<div class="tab_content">
				<dl>
					<dt>商品详情:</dt>
					<dd>草莓采摘园位于北京大兴区 庞各庄镇四各庄村 ,每年1月-6月面向北京以及周围城市提供新鲜草莓采摘和精品礼盒装草莓,草莓品种多样丰富,个大香甜。所有草莓均严格按照有机标准培育,不使用任何化肥和农药。草莓在采摘期间免洗可以直接食用。欢迎喜欢草莓的市民前来采摘,也欢迎各大单位选购精品有机草莓礼盒,有机草莓礼盒是亲朋馈赠、福利送礼的最佳选择。 </dd>
				</dl>
			</div>

		</div>
	</div>
{% endblock %}

{% block script %}
    <script src="/static/buyer/js/jquery-1.12.4.min.js"></script>
    <script>
        function changecount(sign) {
            var value = $("#count").val();
            if(sign == "add"){
                $("#count").val(++value);
            }else {
                if(value <= 1){
                    $("#count").val(1);
                }else{
                   $("#count").val(--value);
                }
            }
            var price = $("#price").text();
            var total_price = value * price;
            $("#total").text(total_price);
        }
    </script>
{% endblock %}

效果:

二十二、商品订单模型创建

订单和地址有多对一关系 订单单纯的用一个表定义不够 订单里面可以有多种商品 所以订单需要两个表

订单表

  • order id订单编号
  • goods_count商品数量
  • order_user订单用户 (多对一)
  • order_address订单地址(多对一)
  • order_price订单总价

订单详情表 (详情和订单是多对一)

  • order_id 订单编号(多对一)
  • goods_id 商品id
  • goods_name 商品名称
  • goods_price 商品价格(单价)
  • goods_number 商品的购买数量
  • goods_total 商品总价(单个商品)
  • goods_store 商品的店铺

1、创建订单模型类并同步数据库

2、新建订单详情页,继承base页,并依据原生模板添加块标签内容

{% extends "buyer/base.html" %}

{% block title %}
    订单详情
{% endblock %}

{% block header %}{% endblock %}

{% block order %}
    <div class="sub_page_name fl">|&nbsp;&nbsp;&nbsp;&nbsp;提交订单</div>
{% endblock %}

{% block car %}{% endblock %}

{% block search %}
    <div class="search_con fr">
			<input type="text" class="input_text fl" name="" placeholder="搜索商品">
			<input type="button" class="input_btn fr" name="" value="搜索">
    </div>
{% endblock %}

{% block content %}
    <h3 class="common_title">确认收货地址</h3>

	<div class="common_list_con clearfix">
		<dl>
			<dt>寄送到:</dt>
			<dd><input type="radio" name="" checked="">北京市 海淀区 东北旺西路8号中关村软件园 (李思 收) 182****7528</dd>
		</dl>
		<a href="user_center_site.html" class="edit_site">编辑收货地址</a>

	</div>

	<h3 class="common_title">支付方式</h3>
	<div class="common_list_con clearfix">
		<div class="pay_style_con clearfix">
			<input type="radio" name="pay_style" checked>
			<label class="cash">货到付款</label>
			<input type="radio" name="pay_style">
			<label class="weixin">微信支付</label>
			<input type="radio" name="pay_style">
			<label class="zhifubao"></label>
			<input type="radio" name="pay_style">
			<label class="bank">银行卡支付</label>
		</div>
	</div>

	<h3 class="common_title">商品列表</h3>

	<div class="common_list_con clearfix">
		<ul class="goods_list_th clearfix">
			<li class="col01">商品名称</li>
			<li class="col02">商品单位</li>
			<li class="col03">商品价格</li>
			<li class="col04">数量</li>
			<li class="col05">小计</li>
		</ul>
		<ul class="goods_list_td clearfix">
			<li class="col01">1</li>
			<li class="col02"><img src="images/goods/goods012.jpg"></li>
			<li class="col03">奇异果</li>
			<li class="col04">500g</li>
			<li class="col05">25.80元</li>
			<li class="col06">1</li>
			<li class="col07">25.80元</li>
		</ul>
		<ul class="goods_list_td clearfix">
			<li class="col01">2</li>
			<li class="col02"><img src="images/goods/goods003.jpg"></li>
			<li class="col03">大兴大棚草莓</li>
			<li class="col04">500g</li>
			<li class="col05">16.80元</li>
			<li class="col06">1</li>
			<li class="col07">16.80元</li>
		</ul>
	</div>

	<h3 class="common_title">总金额结算</h3>

	<div class="common_list_con clearfix">
		<div class="settle_con">
			<div class="total_goods_count"><em>2</em>件商品,总金额<b>42.60元</b></div>
			<div class="transit">运费:<b>10元</b></div>
			<div class="total_pay">实付款:<b>52.60元</b></div>
		</div>
	</div>

	<div class="order_submit clearfix">
		<a href="javascript:;" id="order_btn">提交订单</a>
	</div>
{% endblock %}

3、编写订单详情列表视图

# v3.5 订单详情
def place_order(request):
    # 判断商品详情页加入购买后的提交方式
    if request.method == "POST":
        # 商品详情页添加了两个input
        # post数据
        count = int(request.POST.get("count"))
        goods_id = request.POST.get("goods_id")
        # cookie数据
        user_id = request.COOKIES.get("user_id")
        # 数据库数据
        goods = Goods.objects.get(id=int(goods_id))
        store_id = goods.store_id.get(id = 7).id
        price = goods.goods_price

        # 创建一个订单
        order = Order()
        order.order_id = setOrder(str(user_id),str(goods_id),str(store_id))
        order.goods_count = count
        order.order_user = Buyer.objects.get(id=user_id)
        order.order_price = count * price
        order.save()

        # 创建订单详情
        order_detail = OrderDetail()
        order_detail.order_id = order
        order_detail.goods_id = goods_id
        order_detail.goods_name = goods.goods_name
        order_detail.goods_price = goods.goods_price
        order_detail.goods_number = count
        order_detail.goods_total = count * goods.goods_price
        order_detail.goods_store = store_id
        order_detail.goods_image = goods.goods_image
        order_detail.save()

        detail = [order_detail]
        return render(request,"buyer/place_order.html",locals())
    else:
        return HttpResponse("非法请求")

4、前端详情页数据渲染

{% extends "buyer/base.html" %}

{% block title %}
    订单详情
{% endblock %}

{% block header %}{% endblock %}

{% block order %}
    <div class="sub_page_name fl">|&nbsp;&nbsp;&nbsp;&nbsp;提交订单</div>
{% endblock %}

{% block car %}{% endblock %}

{% block search %}
    <div class="search_con fr">
			<input type="text" class="input_text fl" name="" placeholder="搜索商品">
			<input type="button" class="input_btn fr" name="" value="搜索">
    </div>
{% endblock %}

{% block content %}
    <h3 class="common_title">确认收货地址</h3>

	<div class="common_list_con clearfix">
		<dl>
			<dt>寄送到:</dt>
			<dd><input type="radio" name="" checked="">北京市 海淀区 东北旺西路8号中关村软件园 (李思 收) 182****7528</dd>
		</dl>
		<a href="user_center_site.html" class="edit_site">编辑收货地址</a>

	</div>

	<h3 class="common_title">支付方式</h3>
	<div class="common_list_con clearfix">
		<div class="pay_style_con clearfix">
			<input type="radio" name="pay_style" checked>
			<label class="cash">货到付款</label>
			<input type="radio" name="pay_style">
			<label class="weixin">微信支付</label>
			<input type="radio" name="pay_style">
			<label class="zhifubao"></label>
			<input type="radio" name="pay_style">
			<label class="bank">银行卡支付</label>
		</div>
	</div>

	<h3 class="common_title">商品列表</h3>

	<div class="common_list_con clearfix">
		<ul class="goods_list_th clearfix">
			<li class="col01">商品名称</li>
			<li class="col02">商品单位</li>
			<li class="col03">商品价格</li>
			<li class="col04">数量</li>
			<li class="col05">小计</li>
		</ul>
        {% for d in detail %}
		<ul class="goods_list_td clearfix">
			<li class="col01">{{ forloop.counter }}</li>
			<li class="col02"><img src="/static/{{ d.goods_image }}"></li>
			<li class="col03">{{ d.goods_name }}</li>
			<li class="col04">500g</li>
			<li class="col05">{{ d.goods_price }}元</li>
			<li class="col06">{{ d.goods_number }}</li>
			<li class="col07">{{ d.goods_total }}元</li>
		</ul>
        {% endfor %}
	</div>

	<h3 class="common_title">总金额结算</h3>

	<div class="common_list_con clearfix">
		<div class="settle_con">
			<div class="total_goods_count"><em>{{ order.goods_count }}</em>件商品,总金额<b>{{ order.order_price }}元</b></div>
			<div class="transit">运费:<b>0元</b></div>
			<div class="total_pay">实付款:<b>{{ order.order_price }}元</b></div>
		</div>
	</div>

	<div class="order_submit clearfix">
		<a href="/Buyer/pay_order/?money={{ order.order_price }}&order_id={{ order.order_id }}" id="order_btn">提交订单</a>
	</div>
{% endblock %}

效果:

点击立即购买跳转到订单详情:

订单详情,提交订单,跳转支付页面:

二十三、优化商品与商铺的关系

由于之前商品与商铺是多对多关系,所以进行商品购买时,店铺可能为多个店铺,必须指定一个店铺才能实现商品的购买。

所以现在优化商品商铺关系为多对一

1、修改商品表字段

2、同步数据库

3、修改之前商品业务逻辑

首先是添加商品视图,将多对多关系数据保存删除掉,直接使用一对多关系添加商品的店铺id

修改商品数据正常,不需要修改

前台购买商品视图需要更新

然后从后台到前台重新测试一遍,无误

二十四、后台订单管理系统

1、后台base页添加订单管理选项

2、参照后台商品列表页构建订单列表页

{% extends "store/base.html" %}

{% block title %}
    订单列表
{% endblock %}



{% block content %}
    <table class="table-bordered table">
        <thead>
            <tr align="center">
                <th>订单编号</th>
                <th>订单商品</th>
                <th>订单金额</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
            {% for order in page %}
            <tr align="center">
                <td>{{ order.order_id.order_id }}</td>
                <td>{{ order.goods_name }}</td>
                <td>{{ order.goods_total }}</td>
                <td>
                    <a class="btn btn-primary" href="#">确认发货</a>
                    <a class="btn btn-danger" href="#">拒绝发货</a>
                </td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
    <div class="dataTables_paginate paging_simple_numbers">
        <ul class="pagination">
            {% for p in page_range %}
            <li class="paginate_button page-item ">
                <a class="page-link" href="?page_num={{ p }}">{{ p }}</a>
            </li>
            {% endfor %}
        </ul>
    </div>
{% endblock %}

3、后台订单列表视图

# v3.7 新增订单管理
def order_list(request):
    # 获取前端页码,默认页码1
    page_num = request.GET.get("page_num", 1)
    # 获取当前商铺id
    store_id = request.COOKIES.get("is_store")
    order_list = OrderDetail.objects.filter(goods_store=store_id)
    # 创建分页器
    paginator = Paginator(order_list, 5)
    # 获取具体页的数据
    page = paginator.page(int(page_num))
    # 返回页码列表
    page_range = paginator.page_range
    return render(request,"store/order_list.html",locals())

4、前台订单模型添加“订单状态”字段并同步数据库

# v3.7 添加订单状态:未支付1;待发货2;已发货3;已收货4;已退货0
order_status = models.IntegerField(default=1,verbose_name="订单状态")

5、后台订单管理视图修改,查询当前未处理(未发货)的订单列表

这里的使用Django ORM 双下划线跨表查询

# 查询当前店铺待发货(2)的订单
order_list = OrderDetail.objects.filter(goods_store=store_id,order_id__order_status=2)

6、补充:前台生成订单的时候订单状态字段

生成订单时未支付状态:

order.order_status = 1

7、支付之后订单状态变为待发货(2)

支付视图:

# v3.7 添加支付成功后订单状态置为2待发货
order = Order.objects.get(order_id=order_id)
order.order_status = 2
order.save()

gif展示商品购买支付效果:

二十五、购物车建模及ajax实现数据添加

1、购物车建模并同步数据库

2、根据原生模板新建购物车列表页面

{% extends "buyer/base.html" %}

{% block title %}
    购物车
{% endblock %}

{% block header %}{% endblock %}

{% block order %}
    <div class="sub_page_name fl">|&nbsp;&nbsp;&nbsp;&nbsp;购物车</div>
{% endblock %}

{% block car %}{% endblock %}

{% block search %}
    <div class="search_con fr">
			<input type="text" class="input_text fl" name="" placeholder="搜索商品">
			<input type="button" class="input_btn fr" name="" value="搜索">
    </div>
{% endblock %}

{% block content %}
    <div class="total_count">全部商品<em>2</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>
    {% for goods in goods_list %}
	<ul class="cart_list_td clearfix">
		<li class="col01"><input type="checkbox" name="" checked></li>
		<li class="col02"><img src="/static/{{ goods.goods_picture }}"></li>
		<li class="col03">{{ goods.goods_name }}<br><em>{{ goods.goods_price }}元/500g</em></li>
		<li class="col04">500g</li>
		<li class="col05">{{ goods.goods_price }}元</li>
		<li class="col06">
			<div class="num_add">
				<a href="javascript:;" class="add fl">+</a>
				<input type="text" class="num_show fl" value="{{ goods.goods_number }}">
				<a href="javascript:;" class="minus fl">-</a>	
			</div>
		</li>
		<li class="col07">{{ goods.goods_total }}元</li>
		<li class="col08"><a href="javascript:;">删除</a></li>
	</ul>
    {% endfor %}
	<ul class="settlements">
		<li class="col01"><input type="checkbox" name="" checked=""></li>
		<li class="col02">全选</li>
		<li class="col03">合计(不含运费):<span>¥</span><em>42.60</em><br>共计<b>2</b>件商品</li>
		<li class="col04"><a href="place_order.html">去结算</a></li>
	</ul>
{% endblock %}

3、购物车展示视图

# v3.8 购物车列表页展示
def cart(request):
    # 根据cookie获取user_id
    user_id = request.COOKIES.get("user_id")
    # 查询购物车中的商品
    goods_list = Cart.objects.filter(user_id = user_id)
    return render(request,"buyer/cart.html",locals())

4、在商品详情页对添加购物车做ajax_post方式添加

$("#add_cart").click(
    function () {
        var count = $("#count").val();
        var goods_id = $("#goods_id").val();
        var send_data = {
            "count" : count,
            "goods_id" : goods_id,
            "csrfmiddlewaretoken" : '{{ csrf_token }}'
        };
        var url = "/Buyer/add_cart/";
        $.ajax(
            {
                url : url,
                type : "post",
                data : send_data,
                success : function (data) {
                    alert(data.data)
                },
                error : function (error) {
                    console.log(error)
                }
            }
        )
    }
)

4、前台新增添加购物车视图函数

# v3.8 添加购物车
def add_cart(request):
    # 定义json数据状态
    result = {"state":"error","data":""}
    if request.method == "POST":
        # 获取ajax_post请求数据
        count = int(request.POST.get("count"))
        goods_id = request.POST.get("goods_id")
        # 数据库查询商品
        goods = Goods.objects.get(id=int(goods_id))
        # 根据cookie查询当前用户
        user_id = request.COOKIES.get("user_id")

        # 创建一个购物车,用于添加数据
        cart = Cart()
        cart.goods_name = goods.goods_name
        cart.goods_price = goods.goods_price
        cart.goods_total = goods.goods_price * count
        cart.goods_number = count
        cart.goods_picture = goods.goods_image
        cart.goods_id = goods.id
        cart.goods_store = goods.store_id.id
        cart.user_id = user_id
        cart.save()
        result["state"] = "success"
        result["data"] = "商品添加成功"
    else:
        result["data"] = "请求错误"
    return JsonResponse(result)

二十六、购物车页面全选,全不选前端功能实现

    {% for goods in goods_list %}
	<ul class="cart_list_td clearfix">
		<li class="col01"><input type="checkbox" class="goods_check" name="goods_{{ goods.id }}" value="{{ goods.id }}" checked></li>
		<li class="col02"><img src="/static/{{ goods.goods_picture }}"></li>
		<li class="col03">{{ goods.goods_name }}<br><em>{{ goods.goods_price }}元/500g</em></li>
		<li class="col04">500g</li>
		<li class="col05">{{ goods.goods_price }}元</li>
		<li class="col06">
			<div class="num_add">
				<a href="javascript:;" class="add fl">+</a>
				<input type="text" class="num_show fl" value="{{ goods.goods_number }}">
				<a href="javascript:;" class="minus fl">-</a>	
			</div>
		</li>
		<li class="col07">{{ goods.goods_total }}元</li>
		<li class="col08"><a href="javascript:;">删除</a></li>
	</ul>
    {% endfor %}
	<ul class="settlements">
		<li class="col01"><input id="allBox" type="checkbox" name="" checked=""></li>
		<li class="col02">全选</li>
		<li class="col03">合计(不含运费):<span>¥</span><em>42.60</em><br>共计<b>2</b>件商品</li>
		<li class="col04"><a href="place_order.html">去结算</a></li>
	</ul>
{% endblock %}

{% block script %}
    <script src="/static/buyer/js/jquery-1.12.4.min.js"></script>
    <script>
        $("#allBox").click(
            function () {
                {#console.log(this.checked)#} //true;false
                if(this.checked){
                    $(".goods_check").prop("checked",true) //prop设置或返回元素的属性和值
                }else {
                    $(".goods_check").prop("checked",false)
                }
            }
        );
        // 当全选之后取消某一商品的勾选,全选的勾选也得去掉
        $(".goods_check").each(
            function () {
                $(this).click(
                    function () {
                        if(!this.checked){
                            $("#allBox").prop("checked",false)
                        }
                    }
                )
            }
        )
    </script>
{% endblock %}

效果:

二十七、购物车至订单、支付功能完善

1、购物车结算应该是跳转到订单页面,是属于向后台提交数据的过程,应该使用post请求,所以将购物车页面添加form表单及submit

2、购物车商品提交,保存订单

# v3.8 购物车列表页展示
def cart(request):
    # 根据cookie获取user_id
    user_id = request.COOKIES.get("user_id")
    # 查询购物车中的商品
    goods_list = Cart.objects.filter(user_id = user_id)
    # v4.0 购物车商品添加至订单列表
    if request.method == "POST":
        # 获取购物车页面请求的post数据
        post_data = request.POST
        # 此列表用于收集前端传过来的商品
        cart_data = []
        # 遍历post数据,将购物车列表商品信息取出来
        for k,v in post_data.items():
            # 前端input选择框定义了name和value为购物车对应id
            if k.startswith("goods_"):
                cart_data.append(Cart.objects.get(id=int(v)))
        # 提交过来的购物车数据总数(不是商品数量)
        goods_count = len(cart_data)
        # 订单总价
        goods_total = sum([int(i.goods_total) for i in cart_data])

        # 保存订单
        order = Order()
        # 购物车中生成订单号时,订单中可能有多个商品或多个商铺;使用goods_count代替商品id,使用一个数字代替商铺id
        order.order_id = setOrder(user_id,goods_count,"2")
        order.goods_count = goods_count
        order.order_user = Buyer.objects.get(id=user_id)
        order.order_price = goods_total
        order.order_status = 1

        # 保存订单详情,这里的cart是购物车里的数据实例,不是商品的实例
        for cart in cart_data:
            orderdetail = OrderDetail()
            orderdetail.order_id = order
            orderdetail.goods_id = cart.goods_id
            orderdetail.goods_name = cart.goods_name
            orderdetail.goods_price = cart.goods_price
            orderdetail.goods_number = cart.goods_price
            orderdetail.goods_total = cart.goods_total
            orderdetail.goods_store = cart.goods_store
            orderdetail.goods_image = cart.goods_picture
            orderdetail.save()
        # 当在购物车中点击“"去结算"时跳转到订单列表进行支付
        url = "/Buyer/place_order/?order_id=%s"%order.id
        return HttpResponseRedirect(url)

    return render(request,"buyer/cart.html",locals())

3、修改订单提交的视图,允许通过订单id进行获取

else:
    order_id = request.GET.get("order_id")
    if order_id:
        order = Order.objects.get(id=order_id)
        detail = order.orderdetail_set.all()
        return render(request, "buyer/place_order.html", locals())
    return HttpResponse("非法请求")

二十八、搭建django rest接口

1、安装rest接口框架

pip install djangorestframework
pip install django-filter
pip install Markdown  

2、配置setting

安装app

'rest_framework'

接口配置

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
    ]
}

3、创建serializers文件,用来存放接口过滤器

4、在视图当中查询接口要返回的数据,并指定过滤器

from rest_framework import viewsets

from Store.serializers import *

# v4.2 查询指定接口返回数据
class GoodsViewSet(viewsets.ModelViewSet):
    # 具体返回的数据
    queryset = Goods.objects.all()
    # 指定过滤的类
    serializer_class = GoodsSerializer

class GoodsTypeViewSet(viewsets.ModelViewSet):
    # 具体返回的数据
    queryset = GoodsType.objects.all()
    # 指定过滤的类
    serializer_class = GoodsTypeSerializer

5、在路由当中注册接口

效果:

二十九、使用搭建的django rest接口配合前端vue-resource渲染数据

就拿后台的商品列表为例,复制一份good_list.html验证,使用vue-resource绑定接口中的数据

{% extends "store/base.html" %}

{% block title %}
    {{ store_name }}-商品列表页面
{% endblock %}

{% block label %}
    <a class="btn btn-warning" href="/Store/add_good/">添加商品</a>
{% endblock %}

{% block content %}
    <table class="table-bordered table">
        <thead>
            <tr align="center">
                <th>商品名称</th>
                <th>商品价格</th>
                <th>商品数量</th>
                <th>出厂日期</th>
                <th>保质期</th>
                <th>操作</th>
            </tr>
        </thead>
        {% verbatim %}
        <tbody id="goods">
            <tr v-for="goods in goods_list" align="center">
                <td>
                    <a href="/Store/goods/{{ goods.id }}">{{ goods.goods_name }}</a>
                </td>
                <td>
                    <input type="text" v-bind:value="goods.goods_price" style="text-align: center">
                </td>
                <td>{{ goods.goods_number }}</td>
                <td>{{ goods.goods_date }}</td>
                <td>{{ goods.goods_safeDate }}</td>
                <td>
                    <a class="btn btn-danger" href="/Store/set_goods/down/?id={{ goods.id }}">下架</a>
                    <!--
                    {% ifequal state 'up' %}
                    <a class="btn btn-danger" href="/Store/set_goods/down/?id={{ goods.id }}">下架</a>
                    {% else %}
                    <a class="btn btn-danger" href="/Store/set_goods/up/?id={{ goods.id }}">上架</a>
                    {% endifequal %}
                    <a class="btn btn-primary" href="/Store/set_goods/delete/?id={{ goods.id }}">销毁</a>
                    -->
                </td>
            </tr>
        </tbody>
        {% endverbatim %}
    </table>
    <div class="dataTables_paginate paging_simple_numbers">
        <ul class="pagination">
            {% for p in page_range %}
            <li class="paginate_button page-item ">
                <a class="page-link" href="?keywords={{ keywords }}&page_num={{ p }}">{{ p }}</a>
            </li>
            {% endfor %}
        </ul>
    </div>
{% endblock %}

{% block script %}
    <script src="/static/store/js/vue.min.js"></script>
    <script src="/static/store/js/vue-resource.js"></script>
    <script>
        Vue.use(VueResource);
        var vue = new Vue(
            {
                el:"#goods",
                data:{
                    goods_list:[]
                },
                created:function () {
                    this.$http.get("/APIgoods/").then(
                        function (data) {
                            this.goods_list = data.data;
                            console.log(data.data)
                        },
                        function (error) {
                            console.log(error)
                        }
                    )
                },
                methods:{

                }
            }
        );
    </script>
{% endblock %}

效果:

三十、使用搭建的django rest接口分页功能对前端进行数据分页

在django restframework框架及数据接口搭建配置好的基础上,在setting中配置分页参数:

可以通过设置DEFAULT_PAGINATION_CLASS和PAGE_SIZE,设置全局变量

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10
}

使用vue进行前端分页数据绑定及分页列表点击事件

<script src="/static/store/js/vue.min.js"></script>
<script src="/static/store/js/vue-resource.js"></script>
<script>
    Vue.use(VueResource);//声明是vueresource对象
    var vue = new Vue(
        {
            el:"#goods", //指定绑定的范围对象
            data:{
                goods_list:[],
                page_range:[]
            }, //具体绑定的数据对象
            created:function () { //发起ajax get请求
                this.$http.get("/APIgoods/").then(
                    function (data) {
                        this.goods_list = data.data.results;//将接收的数据绑定到vue对象上
                        page_number = Math.ceil(data.data.count/5);//计算总页码向上取值,这里的5尽量与setting设置里的页码数据量一直例6.6取7
                        {#var page_range = [...new Array(page_number).keys()];//js生成页码列表#}
                        var page_range = Array.from({length:page_number},(item, index)=> index+1);
                        this.page_range = page_range;//将接收的数据绑定到vue对象上
                        {#console.log(page_range);#}
                        console.log(data.data);
                        console.log(page_range);
                        {#console.log(Array.from({length:10},(item, index)=> index+1))#}
                    },
                    function (error) {
                        console.log(error)
                    }
                )
            },//初始化方法
            methods:{
                get_page_data:function (page) {
                    this.$http.get("/APIgoods/?page="+page).then(
                    function (data) {
                        this.goods_list = data.data.results;//将接收的数据绑定到vue对象上
                        page_number = Math.ceil(data.data.count/5);//计算总页码向上取值,例6.6取7
                        var page_range = Array.from({length:page_number},(item, index)=> index+1);//js生成页码列表
                        this.page_range = page_range;//将接收的数据绑定到vue对象上
                        console.log(page_range);
                        console.log(data.data)
                    },
                    function (error) {
                        console.log(error)
                    }
                )
                }
            },//可以被v-on绑定的方法
        }
    );
</script>

三十一、重写restframework框架renderer方法,自定义接口返回数据

1、在项目的根目录创建utils包用来存放要编写的renderer文件

2、在这个包下面创建一个py文件

3、编写renderer类

from rest_framework.renderers import JSONRenderer

class Customrenderer(JSONRenderer):
    def render(self, data, accepted_media_type=None, renderer_context=None):
        """
        :param data: 返回的数据
        :param accepted_media_type: 接收的类型
        :param renderer_context:  渲染呈现的内容
        """
        # 如果有请求数据过来:类似之前的if request.method == "POST"
        if renderer_context:
            # 判断返回的数据是否为字典
            if isinstance(data,dict):
                msg = data.pop("msg","请求成功") # 如果是字典,获取字典当中的msg键的值;若没有这个键,则给出一个回应
                code = data.pop("code",0) # 如果是字典,获取字典当中的code键的值;若没有这个键,则给出一个回应
            else:   # 非字典类型
                msg = "请求成功"
                code = 0
            # 重新构建返回数据的格式
            ret = {
                "msg":msg,
                "code":code,
                "author":"zhang",
                "data":data
            }
            # 根据父类方式返回数据格式
            return super().render(ret,accepted_media_type,renderer_context)
        else: # 如果没有发生修改则返回原格式数据
            return super().render(data,accepted_media_type,renderer_context)

4、setting中安装renderer

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
    ],
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE':5,
    'DEFAULT_RENDERER_CLASSES':(
        'utils.rendererresponse.Customrenderer',
    )
}

效果:

Comments ( 0 )

Sign in for post a comment

CSS
1
https://gitee.com/Iamlovepython/DjangoShop.git
git@gitee.com:Iamlovepython/DjangoShop.git
Iamlovepython
DjangoShop
DjangoShop
master

Help Search