Django项目

25_天天生鲜网站开发


从本章节开始, 我们将会带同学们完成一个天天生鲜网站. 通过该项目来掌握django的使用.

25.1 项目页面展示

天天生鲜项目分为6个页面, 分别是首页商品展示页面、商品分类展示页面、商品详细页面、购物车页面、订单提交界面、订单成功显示页面.

25.1.1 首页商品展示页面

首页部分展示数据, 分为三部分:

  1. 页面左上角产品分类数据展示.
  2. 右上角的 “我的购物车” 数据展示.
  3. 页面主体部分的各个分类产品数据展示. 每个分类我们选出了最新添加的4个商品数据展示.

页面展示效果如图25-1所示:

图25-1

25.1.2 商品分类展示页面

该页面的数据展示分为两部分:

  1. 右上角的购物车商品数据展示.
  2. 主体部分依据分类展示不同分类所有的商品数据, 并能够实现数据分页显示.

页面展示效果如图25-2所示:

图25-2

25.1.3 商品详细页面

该页面主要展示商品的详细信息, 例如价格、名称、描述以及购买行为. 当用户点击页面的 “加入购物车” 按钮, 商品要加入到购物车中.

页面展示效果如图25-3所示:

图25-3

25.1.4 购物车页面

购物车页面主要展示了用户购买商品的详细信息, 在该页面用户可以将不需要的购买的商品、误购买的商品执行删除操作.

页面展示效果如图25-4所示:

图25-4

25.1.5 订单提交界面

用户在购物车页面确定没有问题的话,会提交订单数据到订单提交页面, 在该页面用户需要填写收货地址、收货人、联系电话、备注等等信息, 填写完毕, 点击 “提交订单” 按钮, 生成订单并存储在数据库中.

页面展示效果如图25-5所示:

图25-5

25.1.6 订单提交界面

用户订单生成之后, 在该页面显示用户的订单信息.

页面展示效果如图25-6所示:

图25-6

25.2 项目开发

大致了解我们要完成的项目之后, 我们要进行项目的开发. 我们并不是直接讲解 Django 框架, 而是通过项目驱动的方式, 一步步掌握 Django 框架的基本使用.

25.2.1 项目创建

我们的项目基于 Django 框架, 所以我们先创建一个 Django 项目. 创建项目之前, 我们首先搭建我们的项目运行环境:

25.2.1.1 搭建开发环境

在开发过程中,当需要使用 python 的包时可以通过 pip 命令联网安装, 所有第三方的包都会被pip安装到Python3的site-packages目录下。 如果我们要同时开发多个应用程序,那这些应用程序都会共用一个Python,就是安装在系统的Python3。如果应用A需要django 1.8.7,而应用B需要django 1.8.2. 这种情况下,每个应用可能需要各自拥有一套 “独立” 的 Python 运行环境。

virtualenv 工具可以用来为一个应用创建一套 “隔离” 的Python运行环境。

安装虚拟环境命令:

pip install virtualenv
pip install virtualenvwrapper

virtualenvwrapper 是 virtualenv 的扩展集合, 用于简化虚拟环境创建操作相关操作.

安装好该工具之后, 我们可以通过下面的命令来创建一个隔离的 Python 运行环境:

# 创建虚拟环境
mkvirtualenv 虚拟环境名称
# 切换虚拟环境
workon 虚拟环境名称
# 查看已创建的虚拟环境
workon 两次tab键
# 删除虚拟环境
rmvirtualenv 虚拟环境名称
# 退出虚拟环境
deactivate

创建成功虚拟环境之后,会自动切换到这个虚拟环境中, 如果需要切换到其他虚拟环境下或者删除当前虚拟环境, 那么需要先退出当前虚拟环境.

提示1:创建虚拟环境需要联网;
提示2:工作在虚拟环境上,提示符最前面会出现 “(虚拟环境名称)”.
在虚拟环境中安装所需要的包(首先workon到虚拟环境中).

我们现在创建我们项目所需的虚拟环境, 名称为: “django-dev-py3”, 创建命令如下:

mkvirtualenv django-dev-py3

如果你的电脑上安装了多个Python版本, 比如既有 Python 2.7, 也安装了 Python 3.5, 那么在创建虚拟环境时, 可指定使用的 Python 版本, 命令如下:

mkvirtualenv django-dev-py3 --python=[python3路径]

执行过程如图25-1所示:

图25-1

标记红色矩形部分为我执行的命令, 创建完虚拟环境之后, 已经自动切换到了该虚拟环境下工作.

我们项目所使用的包环境如下:

  • Django 1.8.2
  • PyMySQL 0.8.0

我们之所以选择 Django 1.8.2, 原因在于这是一个稳定性高、使用广、文档多的版本.

在线中文文档: http://python.usyiyi.cn/translate/django_182/index.html

我们的项目使用的数据库为 MySQL, 我们选择使用了 PyMySQL 包.

切换到 django-dev-py3 虚拟环境下, 执行包安装命令如下:

pip install django==1.8.2
pip install pymysql
25.2.1.2 创建项目

切换到 django-dev-py3 虚拟环境下, 执行 cd 命令, 切换到要保存项目的目录下, 我这里创建在桌面, 执行创建 django 项目的命令:

django-admin startproject ttsx

“ttsx” 为我们创建项目的名称.

项目创建过程如图25-2所示:

图25-2

此时我们已经创建好了 django 项目. 该项目的目录结构如图25-2所示, 各个模块文件介绍如下:

  • manage.py 文件是项目运行的入口文件,执行项目配置文件路径;
  • __init__.py 文件为空文件,指定当前目录可作为包使用;
  • settings.py 文件整个项目的配置文件,例如配置应用、模板目录、静态文件目录等等;
  • urls.py 文件是项目的URL配置文件,例如将用户请求的 url 对应到我们某个视图函数;
  • wsgi.py 是项目与支持 WSGI 协议的 WEB 服务器对接的入口文件。
25.2.1.3 创建应用

Django 框架通过应用的方式来管理整个网站项目. 一个网站中包含多个子业务模块,比如用户模块,商品模块,新闻模块等等,我们可以将这些子模块称作一个应用。 通过项目页面展示的功能, 我们大致将项目分为两个应用.

  • 和商品展示相关的应用, 该应用我们叫做 goods 应用.
  • 和购物车相关的应用, 该应用我们叫做 cart 应用.

每一个应用中包含了该业务所有的操作. 我们通过下面命令来创建所需要的两个应用:

python manage.py startapp goods
python manage.py startapp cart

此时项目的目录结构如下:

图25-3

项目结构中多出了名字为 cart 和 goods 两个目录, 两个目录内所包含的文件是一模一样的, 每个新创建的应用目录结构和 cart、goods是一样的. 每个应用中模块文件的含义如下:

  • __init__.py 是一个空文件,表示当前目录可以当做一个Python包来使用;
  • tests.py 用于开发测试用例,在实际开发中,如果需要对模块进行测试,可在此文件中编写测试代码;
  • views.py 在此编写视图相关代码;
  • models.py 在此编写模型相关代码;
  • migrations包,稍后讲解模型创建时讲解.

注意: 创建的应用在 settings.py 模块中进行配置, 才能够被项目识别.

打开 ttsx 包下的 settings.py 模块, 找到 INSTALLED_APPS 配置项:

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
)

该配置项中配置的是已经安装的应用, 配置该项如下:

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'goods',
    'cart',
)

我们的项目以及应用就创建完毕了.

25.2.1.4 开发服务器

在开发阶段,为了能够快速预览到网站的效果,django提供了一个纯 Python 编写的轻量级 WEB 服务器,仅在开发阶段使用. 运行服务器命令如下:

python manage.py runserver ip:端口

我们一般可不指定ip和端口,直接运行:

python manage.py runserver

服务器成功启动后效果如下图25-4所示:

图25-4

在浏览器中输入网址 “127.0.0.1:8000”, 或者按着ctrl键点击上图中标示出来的地址,可以查看当前站点开发效果. 如图25-5所示:

图25-5

如果增加、修改、删除文件,服务器会自动重启. 按 CTRL+C 停止服务器.

25.2.2 模型创建

25.2.2.1 ORM 概述

当前项目的开发都是数据库驱动,即分析出项目中所需要存储的数据,然后设计数据表结构,接下来对通过编写 SQL 语句对数据库中的表进行 CURD 操作。

Django 提供了一套针对数据库操作的代码库, 通过该代码库中提供的工具, 我们可以创建查询、生成SQL语句,并且这些SQL无需开发人员自己处理。这样带来的好处是开发人员无需对 SQL 和 关系型数据库有深入的了解就能直接编写自己的查询。

我们把这套库称作 ORM 库, ORM 是对象关系映射, 对象表示的是我们Python中的对象概念, 关系指的是关系型数据库. 通过 ORM 完成 对象和 数据库中的表之前的一一对应关系. 对对象的任何操作就是对表的操作.

另外 ORM 强大的地方,ORM支持多种数据库后端。可非常简单帮助我们切换后端数据库。Django中提供了自己内置的 ORM 库。我们不需要面向数据库编程,而是通过定义模型类,通过对我们的模型类和对象的方法调用,从而完成了对数据库 CRUD 操作。

简言之, ORM 库帮我们封装了对关系型数据库的 CRUD 操作, 我们不需要编写一行 SQL, 即可完成对数据库的操作.

25.2.2.2 数据库配置

打开 ttsx/settings.py 文件, 在该配置文件中默认使用的是 SQLITE 数据库, 原 SQLITE 配置内容如下:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

现配置项目数据库为 MYSQL:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',  # mysql数据库引擎
        'NAME': 'my_ttsx',  # 数据库名字
        'USER': 'root',  # 用户名
        'PASSWORD': 'chuanzhi',  # 密码
        'HOST': 'localhost',  # 主机
        'PORT': '3306'  # 端口
    }
}

配置完成之后, 在 ttsx 目录下的 __init__.py 中增加如下代码:

import pymysql
pymysql.install_as_MySQLdb()

一定要保证配置文件中指定的数据库是存在的, 如果不存在需要创建对应数据库. 启动测试服务器, 如果没有报错, 那么表示配置成功.

25.2.2.3 创建模型

在 Django 中一个模型类就对应着数据库中的一张表, 对模型类的任何操作都是对数据库表的操作. 在该项目中, 我们需要对业务进行分析, 然后创建所需要的表.

关于商品展示部分, 我们需要两张数据库表:

  • GoodsCategory 表, 存储商品分类信息.
    • 分类显示名称
    • 分类显示样式
    • 分类显示图片
  • GoodsInfo 表, 存储商品具体信息.
    • 商品名称
    • 商品单位
    • 商品价格
    • 商品图片
    • 商品描述
    • 商品分类

关于购物车部分, 我们也需要两张表:

  • OrderInfo 表, 存储订单基本信息.
    • 订单编号
    • 收货地址
    • 收货人
    • 联系电话
    • 订单运费
    • 订单备注
    • 订单状态
  • OrderGoods 表, 存储订单所对应的商品信息.
    • 订单商品
    • 商品数量
    • 所属订单

针对我们分析的表结构, 我们创建对应的模型, 首先是创建商品展示相关的两个模型类, 在 goods 应用的 models.py 模块中编辑如下代码:

class GoodsCategory(models.Model):
    """商品分类模型"""

    # 分类名称
    cag_name = models.CharField(max_length=30)
    # 分类样式
    cag_css = models.CharField(max_length=20)
    # 分类图片
    cag_img = models.ImageField(upload_to='cag')


class GoodsInfo(models.Model):
    """商品信息模型"""

    # 商品名字
    goods_name = models.CharField(max_length=100)
    # 商品单位
    goods_unit = models.CharField(max_length=50)
    # 商品价格
    goods_price = models.IntegerField(default=0)
    # 商品图片
    goods_img = models.ImageField(upload_to='goods')
    # 商品描述
    goods_desc = models.CharField(max_length=2000)
    # 所属分类
    goods_cag = models.ForeignKey('GoodsCategory')

注意点:

  • 模型类需要定义在 models.py 模块中.
  • 模型类必须继承自 models.Model 类.
  • 每一个类属性和数据库表中的一个字段一一对应.

类属性后面的字段类型是由 Django 定义, 方便将字段类型映射到不同的数据库中. 例如如下代码:

goods_name = models.CharField(max_length=100)

字段名叫做 goods_name, 字段的类型为 CharField, 对应到 MySQL 数据库的字段类型就为 varchar 类型, 并且长度为100. 除了 CharField, 我们还发现有其他不同的字段类型, 常用字段类型以及含义如下表所示:

使用时需要引入 django.db.models 包,字段类型如下:

字段类型描述
AutoField自动增长的IntegerField,通常不用指定,不指定时Django会自动创建属性名为id的自动增长属性.
BooleanField布尔字段,值为True或False.
NullBooleanField支持Null、True、False三种值.
CharField(max_length=长度)字符串, 参数max_length表示最大字符个数.CharField必须指定max_length大小
TextField大文本字段
IntegerFieldIntegerField 一个整数. 在Django所支持的所有数据库中,从 -2147483648 到 2147483647 范围内的值是合法的.
DecimalField(max_digits=None, decimal_places=None)十进制浮点数.参数max_digits表示总位数.参数decimal_places表示小数位数.
DateField[auto_now=False, auto_now_add=False])日期. 参数auto_now表示每次保存对象时,自动设置该字段为当前时间,用于"最后一次修改"的时间戳,它总是使用当前日期,默认为false.参数auto_now_add表示当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为false.参数auto_now_add和auto_now是相互排斥的,组合将会发生错误.
TimeField时间,参数同DateField
DateTimeField日期时间,参数同DateField.
FileField上传文件字段.
ImageField继承于FileField,对上传的内容进行校验,确保是有效的图片.
BigIntegerField一个64位整数, 类似于一个 IntegerField ,它的值的范围是 -9223372036854775808 到9223372036854775807之间.
SmallIntegerField该字段值在 -32768 至 32767

表中并未解释 models.ForeignKey 字段的含义, 该字段主要用于建立外键, 表示表和表之间是一对多的关系, 例如, 商品分类和商品信息表之间就属于一对多的关系. 我们就可在商品信息模型中建立对商品分类的外键.

每个字段都会有一些字段选项:

选项类型描述
null如果为True,表示允许为空,默认值是False.
db_column字段的名称,如果未指定,则使用属性的名称.
db_index若值为True, 则在表中会为此字段创建索引,默认值是False.
default默认值
primary_key若为True,则该字段会成为模型的主键字段,默认值是False,一般作为AutoField的选项使用.
unique如果为True, 这个字段在表中必须有唯一值,默认值是False.
  • null.如果为True,Django 将会把数据库中空值保存为NULL。默认值是 False.
  • blank. 如果为True,该字段允许为空值,默认为False。要注意,这与 null 不同。null纯粹是数据库范畴,指数据库中字段内容是否允许为空,而 blank 是表单数据输入验证范畴的。如果一个字段的blank=True,表单的验证将允许输入一个空值。如果字段的blank=False,该字段就是必填的。
  • primary_key.如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True,Django 就会自动添加一个IntegerField 字段做为主键,所以除非你想覆盖默认的主键行为,否则没必要设置任何一个字段的primary_key=True。主键字段是只读的。如果你在一个已存在的对象上面更改主键的值并且保存,一个新的对象将会在原有对象之外创建出来。

下面我们在 cart 应用的 models.py 模块中创建购物车相关的模型类, 代码如下:

class OrderInfo(models.Model):
    """订单信息模型"""

    status = (
        (1, '待付款'),
        (2, '待发货'),
        (3, '待收货'),
        (4, '已完成'),
    )

    # 订单编号
    order_id = models.CharField(max_length=100)
    # 收货地址
    order_addr = models.CharField(max_length=100)
    # 收货人
    order_recv = models.CharField(max_length=50)
    # 联系电话
    order_tele = models.CharField(max_length=11)
    # 运费
    order_fee = models.IntegerField(default=10)
    # 订单备注
    order_extra = models.CharField(max_length=200)
    # 订单状态
    order_status = models.IntegerField(default=1, choices=status)


class OrderGoods(models.Model):
    """订单商品模型"""

    # 所属商品
    goods_info = models.ForeignKey('goods.GoodsInfo')
    # 商品数量
    goods_num = models.IntegerField()
    # 商品所属订单
    goods_order = models.ForeignKey('OrderInfo')

如下代码:

order_status = models.IntegerField(default=1, choices=status)

该代码中的 choices=status 表示的值为 status 指定的 4 个值中的一个.

我们所需要的模型创建好了, 下面就需要 Django 按照我们所定义的模型类来创建对应的数据库表. 首先我们先查看下项目目录结构, 会发现在 goods 应用和 cart 应用的 migrations 目录为空, 当我们执行迁移命令时, 对比该目录的变化:

图25-6

下面在终端执行迁移命令:

python manage.py makemigrations

命令执行效果如图26-7所示:

图25-7

下面我们再来看看 migrations 目录, 我们会发现多出名为 0001_initial.py 文件.

图25-8

0001_initial.py 文件我们可以理解为 SQL 的配置文件, 我们可将其转换为 SQL语句, 执行如下命令:

python manage.py sqlmigrate goods 0001

表示将 goods 应用下的 0001 配置文件转换为 SQL语句, 效果如图25-9所示:

图25-9

下面我们执行迁移命令, 执行对应的 SQL 语句, 在数据库中创建对应的数据库表, 执行迁移的命令如下:

python manage.py migrate

执行效果如图25-10所示:

图25-10

查看数据库, 我们已发现对应的数据库表已经创建, 但是表明并不是我们和我们模型类类名一直, 数据库中的表的格式为: 应用名字_模型类类名小写.

图25-11

对应模型的4张表名字分别为: cart_ordergoods、cart_orderinfo、goods_goodscategory、goods_goodsinfo.

25.2.3 创建视图

25.2.3.1 视图函数与网址对应

当我们在浏览器输入不同的网址, 对应着浏览器发出的不同的请求, 对于不同的请求, 我们都会编写对应的函数来处理浏览器的请求. 请求处理函数, 我们定义在应用的 views.py 模块中, 每一个处理请求的函数, 我们叫做视图函数.

该函数接收至少一个参数, 并且必须有返回值. 接收的参数类型为 HttpRequest 类型, 返回的数据类型必须为 HttpResponse 或者其子类.

我们下面在 goods 应用的 views.py 模块中, 创建一个视图函数 index, 代码如下:

from django.shortcuts import render
from django.http import HttpResponse


def index(request):

    return HttpResponse('hello django!')

该视图函数处理方式非常简单, 只是向浏览器返回 “hello django” 字符串.

为了能够让浏览器能够看到 “hello django” 字符串, 我们必须配置 index 函数和那个请求网址对应. 下面我们网址和视图之间的配置:

  1. 首先打开 ttsx 目录下的 urls.py 模块中, 内容如下:
from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
]

在该模块中增加代码如下:

from django.conf.urls import include, url
from django.contrib import admin
from goods.views import index  # 导入视图函数

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^index/$', index)
]

代码的第一部分为 正则表达式, 第二部分 index, 表示前面正则如果匹配上则调用该视图函数. 那么 Django 是如何匹配网址的呢? 例如: 我们请求的网址是: localhost:8000/index/, Django 首先会去除域名部分, 也就是 localhost:8000/, 然后用余下的部分, 也就是 index/ 在 ttsx/urls.py 中的 urlpatterns 中逐个去判断那条正则表达式匹配请求的网址, 如果都不匹配则报错, 如果匹配, 则调用对应的视图函数.

 url(r'^index/$', index)

2. 启动开发服务器.

python manage.py runserver

3. 在浏览器输入网址.

localhost:8000/index/

浏览器显示效果如图25-12所示:

图25-12
25.2.3.2 模板渲染

如何返回一个漂亮的 HTML 页面呢? 我们可修改视图函数 index 如下:

from django.shortcuts import render
from django.http import HttpResponse


def index(request):

    return HttpResponse('<h1>hello django!</h1>')

再次通过浏览器请求: localhost:8000/index/. 显示效果如图25-13所示:

图25-13

但是如果 HTML 文档比较大的话, 这种方式比较麻烦, 我们可以使用快捷函数 render 来返回一个 HTML 模板. 什么意思呢?

首先在 ttsx 目录下的 settings.py 模块中配置 HTML 模板文件的路径, 未修改之前如下:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

增加模板路径如下:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

其中

'DIRS': [os.path.join(BASE_DIR, 'templates')],

表示目录存放的目录在项目根目录的 templates 目录下. BASE_DIR 由 Django 定义的 项目根目录路径.

然后我们在项目根目录下创建 templates 目录, 并在该目录中创建一个模板文件 index.html, 内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>hello django!</h1>
</body>
</html>

修改 index 视图函数如下:

from django.shortcuts import render
from django.http import HttpResponse


def index(request):

    return render(request, 'index.html')
图25-14

我们还可以通过 render 函数动态给 index.html 传递值, 并显示在模板中. 例如, 我们修改 index 视图函数如下:

from django.shortcuts import render
from django.http import HttpResponse


def index(request):

    return render(request, 'index.html', {'name': '张三', 'age': 20})

我们在 index 视图函数给 index.html 模板传递一个参数, 该参数必须是字典类型. 那么在模板中如何显示该数据呢? 修改 index.html 如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
我的名字是: {{ name }}, 年龄是: {{ age }}.
</body>
</html>

在浏览器输入网址: localhost:8000/index/. 显示效果如图25-15所示:

图25-15

我们把数据填充到模板中的这一过程, 叫做模板渲染. 在模板中, 我们使用特殊的语法 “{{ 字典key名 }}” 来显示对应的数据.

25.2.3.3 静态文件

那么如何在模板中显示图片呢? 我们在项目的根目录下创建 static 目录, 然后在该目录下添加一张图片. 然后在 ttsx/ settings.py 中对图片文件存放路径进行配置:

STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]

第一行配置是 Django 默认添加的, 第二行配置是我们新增的配置内容. 该配置表示, 当发现图片的网址路径是以 /static/ 开头的话, 那么就去 STATICFILES_DIRS 配置的路径下去搜索图片文件.

例如, 我们可修改 index.html 文件如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<img src="/static/default.jpg">
</body>
</html>

在浏览器输入网址: localhost:8000/index/. 显示效果如图25-16所示:

图25-16

除了图片文件, 例如 js、css等都叫做静态文件, 静态文件都可通过上述配置方式进行访问.

25.2.4 数据库操作

首先在终端输入命令:

python manage.py shell

显示界面如图25-17所示:

图25-17

此时进入 shell, 我们可在 shell 中通过面向对象方式对数据库中的数据进行 CRUD 操作.

25.2.4.1 插入操作

在 Django 中我们不需要编写一行 SQL 语句, 就可以对数据库中的数据进行操作. 完全基于面向对象的方式. 下面我们通过面向对象的方式在数据库的商品分类表中插入数据, 执行代码如下:

from goods.models import *
categories = [('新鲜水果', 'fruit'), ('海鲜水产', 'seafood'), ('猪牛羊肉', 'meet'), ('禽类蛋品', 'egg'), ('新鲜蔬菜', 'vegetables'), ('速冻食品', 'ice')]
for index, cag in zip(range(1, 7), categories):
     c = GoodsCategory()
     c.cag_name = cag[0]
     c.cag_css = cag[1]
     c.cag_img = 'images/banner0%d.jpg' % index
     c.save()

我们创建一条数据, 只需要创建一个对应模型的实例对象, 然后调用该对象的 save 方法, 即可向数据库中添加一条数据. 我们上面的代码, 通过 for 循环向数据库中插入了我们项目需要的商品分类数据.

执行效果如图25-18所示:

图25-18

查看数据库中执行结果, 如图25-19所示:

图25-19

为了加深印象, 我们再通过同样的方式向数据库中插入商品数据. 我们商品数据对应的商品名称被定义在 data.txt 的数据文件中, 该文件路径为项目根目录. 我们读取该文件中的数据, 并将其插入到数据库中.

data.txt 数据文件部分内容如下:

苹果
杏子
槟榔子
香蕉
山毛榉坚果
海棠果
酸橙
黑莓
...

对应商品图片数据我们放置在了 static/goods目录下, 文件目录如图25-20所示:

图25-20

插入商品数据代码如下:

from goods.models import *
from random import randint


# 商品计量单位
goods_unit = ['100克', '200克', '1斤', '5斤', '1个', '2个']

with open('./data.txt', 'r') as file:

    for line in file:

        # 创建商品对象
        goods = GoodsInfo()
        # 设置属性值
        goods.goods_name = line[:-1]  # 去除名字之后的空格
        goods.goods_price = randint(100, 999)
        goods.goods_img = 'goods/%s.jpg' % randint(1, 18)
        goods.goods_desc = '商品详细信息描述!'
        goods.goods_unit = goods_unit[randint(0, len(goods_unit) - 1)]
        goods.goods_cag_id = randint(1, 6)  # 设置商品所属分类
        goods.save()

这里需要额外说明一点, goods_cag = models.ForeignKey('GoodsCategory')实际在数据库中存储的是商品分类数据的主键. 设置商品所属分类:

# 创建商品分类
cag = GoodsCategory()
...
cag.save()

goods = GoodsInfo()
...
goods.goods_cag = cag
goods.save()

代码执行效果如图25-21所示:

图25-21

查看数据库中执行结果, 如图25-22所示:

图25-22
25.2.4.2 查询操作

查询操作我们主要用到了3个方法:

  1. get, 查询一条数据.
  2. all, 获得所有数据.
  3. filter, 根据条件过滤数据.

get 方法:

如果你知道只有一个对象满足你的查询,你可以使用管理器的 get 方法,它直接返回该对象. 例: 查询商品名字为 “香蕉” 的对象.

from goods.models import *
goods = GoodsInfo.objects.get(goods_name='香蕉')
print('商品名字:', goods.goods_name)

objects 为 GoodsInfo 类的对象属性, 该对象属性叫做管理器对象, 在该对象中封装了用于数据库操作的方法, 例如, get 方法就是在 objects 对象定义的方法.

如果匹配到的对象个数不只一个的话,get 将会触发 MultipleObjectsReturned 异常. MultipleObjectsReturned 异常是模型类的属性. 如果根据给出的参数匹配不到对象的话, 将触发DoesNotExist 异常. 这个异常是模型类的属性.

all 方法:

获取一个表中所有对象的最简单的方式是全部获取。可以使用管理器的 all 方法:

all_cags = GoodsCategory.objects.all()

all 方法返回包含数据库中所有对象的一个查询结果集。对结果使用切片语法, 例:获得所有结果集的前两个对象组成的结果集:

cags = GoodsCategory.objects.all()[1: 3]

filter 方法:

all 方法返回了一个包含数据库表中所有记录查询集. 但在通常情况下,我们往往想要获取的是完整数据集的一个子集. 要创建这样一个子集,你需要在原始的的查询集上增加一些过滤条件。

例如, 查询分类 ID 为1的所有商品.

from goods.models import *
# 查询ID为1的分类
cag = GoodsCategory.objects.get(id=1)
goods_list = GoodsInfo.objects.filter(goods_cag=cag)
goods_list.count()

执行效果如图25-24所示:

图25-24
25.2.4.3 更新操作

Django 创建和更新对象,使用同一个函数save(). 当调用save()时, django会判断对象是否有主键,如果存在则调用更新,如果不存在则创建数据.

cag = GoodsCategory.objects.get(id=1)
cag.cag_name = '新分类名字'
cag.save()
25.2.4.4 删除操作
cag = GoodsCategory.objects.get(id=1)
cag.cag_name = '新分类名字'
cag.delete()

25.2.5 Cookie

Cookie 指某些网站为了辨别用户身份、在用户本地终端上存储的数据(通常经过加密).

Cookie 最早是网景公司的前雇员 Lou Montulli 在1993年3月的发明. Cookie 是由服务器端生成, 发送给 User-Agent(一般是浏览器), 浏览器会将 Cookie 的 key/value 保存到某个目录下的文本文件内, 下次请求同一网站时就发送该Cookie 给服务器(前提是浏览器设置为启用cookie). Cookie 名称和值可以由服务器端开发自己定义.

Cookies最典型的应用是判定注册用户是否已经登录网站, 用户可能会得到提示,是否在下一次进入此网站时保留用户信息以便简化登录手续, 这些都是Cookies的功能.

另一个重要应用场合是 “购物车” 之类处理. 用户可能会在一段时间内在同一家网站的不同页面中选择不同的商品, 这些信息都会写入 Cookie, 以便在最后付款时提取信息.

Cookie 是存储在浏览器中的一段纯文本信息, 建议不要存储敏感信息如密码, 因为电脑上的浏览器可能被其它人使用.

Cookie以键值对的格式进行信息的存储.

简言之, cookie 是键值对数据, 在服务器产生, 存储在用户的浏览器. 用户每次请求网站, 都会将本地存储的该网站的 cookie 数据发到服务器端.

设置 Cookie:

HttpResponse.set_cookie(key,
                        value='',
                        max_age=None,
                        expires=None,
                        path='/',
                        domain=None,
                        secure=None,
                        httponly=False)
参数描述
max_age以秒为单位,cookie过期时间
expires应该是一个UTC “Wdy, DD-Mon-YY HH:MM:SS GMT” 格式的字符串, 或者一个 datetime.datetime 对象
domain如果你想设置一个跨域的Cookie, 请使用 domain 参数. 例如,domain=".lawrence.com" 将设置一个www.lawrence.com、blogs.lawrence.com 和calendars.lawrence.com 都可读的 Cookie. 否则, Cookie 将只能被设置它的域读取
httponly如果你想阻止客服端的JavaScript 访问Cookie,可以设置httponly=True

删除 Cookie

HttpResponse.delete_cookie(key, path='/', domain=None) 

读取 Cookie

HttpRequest.COOKIES, 一个标准的 python 字典, 包含所有的 cookie. 键和值都为字符串.

request.COOKIES[key]
# 或
request.COOKIES.get(key, '')

25.2.6 首页页面功能实现

首先将我们的模板文件拷贝到 templates 目录下, 将我们模板的静态文件拷贝到 static 目录下.

首页页面主要展示的数据包括:

  1. 产品的分类数据
  2. 每个分类的产品数据
  3. 购物车商品数据

编写首页视图函数 index, 代码实现如下:

def index(request):
    """首页页面"""

    # 查询商品分类
    categories = GoodsCategory.objects.all()
    # 从每个分类中获取四个商品
    for cag in categories:
        cag.goods_list = cag.goodsinfo_set.order_by('-id')[:4]

    # 读取购物车商品列表
    cart_goods_list = []
    # 商品总数
    cart_goods_count = 0
    for goods_id, goods_num in request.COOKIES.items():
        if goods_id == 'csrftoken':
            continue
        cart_goods = GoodsInfo.objects.get(id=goods_id)
        cart_goods.goods_num = goods_num
        cart_goods_list.append(cart_goods)
        # 累加购物车商品总数
        cart_goods_count = cart_goods_count + int(goods_num)

    return render(request, 'index.html', {'categories': categories, 
                                          'cart_goods_list': cart_goods_list, 
                                          'cart_goods_count': cart_goods_count})
  1. 获得所有分类数据, 该部分功能实现代码如下:
categories = GoodsCategory.objects.all()
  1. 然后根据分类获取每个分类中的4条商品数据.
for cag in categories:
    cag.goods_list = GoodsInfo.objects.filter(goods_cag=cag)[:4]
  1. 购物车数据展示. 由于购物车数据我们存储在 cookie 中, 所以我们这里遍历 request.COOKIES 从中读取所有商品信息.
# 读取购物车商品列表
cart_goods_list = []
# 商品总数
cart_goods_count = 0
for goods_id, goods_num in request.COOKIES.items():
    if goods_id == 'csrftoken':
        continue
    cart_goods = GoodsInfo.objects.get(id=goods_id)
    cart_goods.goods_num = goods_num
    cart_goods_list.append(cart_goods)
    # 累加购物车商品总数
    cart_goods_count = cart_goods_count + int(goods_num)

其中代码:

if goods_id == 'csrftoken':
    continue

由于我们的 cookie 中除了商品数据之外, django 也会增加其他的 cookie 数据, 我们通过 if 判断将非商品数据的 cookie 过滤掉.

index.html 模板主要代码实现如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<title>天天生鲜-首页</title>
	<link rel="stylesheet" type="text/css" href="/static/css/reset.css">
	<link rel="stylesheet" type="text/css" href="/static/css/main.css">
	<script type="text/javascript" src="/static/js/jquery-1.12.4.min.js"></script>
	<script type="text/javascript" src="/static/js/jquery-ui.min.js"></script>
	<script type="text/javascript" src="/static/js/slide.js"></script>
</head>
<body>
	<div class="header_con">
		<div class="header">
			<div class="welcome fl">欢迎来到天天生鲜!</div>
		</div>		
	</div>

	<div class="search_bar clearfix">
		<a href="/index/" class="logo fl"><img src="/static/images/logo.png"></a>
		<div class="guest_cart fr">
			<a href="/cart/show_cart/" class="cart_name fl">我的购物车</a>
			<div class="goods_count fl">{{ cart_goods_count }}</div>
			<ul class="cart_goods_show">


                {% for cart_goods in cart_goods_list %}
				<li>
					<img src="/static/{{ cart_goods.goods_img }}" alt="商品图片">
					<h4>{{ cart_goods.goods_name }}</h4>
					<div>{{ cart_goods.goods_num }}</div>
				</li>
                {% endfor %}

			</ul>
			
		</div>
	</div>

	<div class="navbar_con">
		<div class="navbar">
			<h1 class="fl">全部商品分类</h1>
		</div>
	</div>

	<div class="center_con clearfix">
		<ul class="subnav fl">
            {% for cag in categories %}
                <li><a href="/goods/?cag={{ cag.id }}&page=1" class="{{ cag.cag_css }}">{{ cag.cag_name }}</a></li>
            {% endfor %}
		</ul>
		<div class="slide fl">
			<ul class="slide_pics">
				<li><img src="/static/images/slide.jpg" alt="幻灯片"></li>
				<li><img src="/static/images/slide02.jpg" alt="幻灯片"></li>
				<li><img src="/static/images/slide03.jpg" alt="幻灯片"></li>
				<li><img src="/static/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/images/adv01.jpg"></a>
			<a href="#"><img src="/static/images/adv02.jpg"></a>
		</div>
	</div>

    {% for cag in categories %}
	<div class="list_model">
		<div class="list_title clearfix">
			<h3 class="fl" id="model01">{{ cag.cag_name }}</h3>
			<a href="/goods/?cag={{ cag.id }}&page=1" class="goods_more fr" id="fruit_more">查看更多 ></a>
		</div>

		<div class="goods_con clearfix">
			<div class="goods_banner fl"><img src="/static/{{ cag.cag_img }}"></div>
			<ul class="goods_list fl">

                {% for goods in cag.goods_list %}
				<li>
					<h4><a href="/detail/?id={{ goods.id }}">{{ goods.goods_name }}</a></h4>
					<a href="/detail/?id={{ goods.id }}"><img src="/static/{{ goods.goods_img }}"></a>
					<div class="prize">¥ {{ goods.goods_price }}</div>
				</li>
                {% endfor %}

			</ul>
		</div>
	</div>
    {% endfor %}

	<div class="footer">
		<div class="foot_link">
			<a href="#">关于我们</a>
			<span>|</span>
			<a href="#">联系我们</a>
			<span>|</span>
			<a href="#">招聘人才</a>
			<span>|</span>
			<a href="#">友情链接</a>		
		</div>
		<p>CopyRight © 2016 北京天天生鲜信息技术有限公司 All Rights Reserved</p>
		<p>电话:010-****888    京ICP备*******8号</p>
	</div>
</body>
</html>

我们由 index 视图函数传递给模板的数据 categories 是一个结果集, 我们在 index.html 模板中, 通过使用 Django 提供的模板语法, 来遍历输出. 模板中 for 的使用格式如下:

{% for ... in ... %}

{% endfor %}

输出变量, 我们使用如下格式的语法:

{{ 变量名 }}

下面模板代码, 主要用户遍历输出购物车中的商品信息.

<a href="/cart/show_cart/" class="cart_name fl">我的购物车</a>
<div class="goods_count fl">{{ cart_goods_count }}</div>
<ul class="cart_goods_show">


   {% for cart_goods in cart_goods_list %}
	<li>
		<img src="/static/{{ cart_goods.goods_img }}" alt="商品图片">
		<h4>{{ cart_goods.goods_name }}</h4>
		<div>{{ cart_goods.goods_num }}</div>
	</li>
   {% endfor %}

</ul>

下面的模板代码主要用于输出产品分类信息:

<ul class="subnav fl">
    {% for cag in categories %}
        <li><a href="/goods/?cag={{ cag.id }}&page=1" class="{{ cag.cag_css }}">{{ cag.cag_name }}</a></li>
    {% endfor %}
</ul>

遍历输出每个分类下的商品数据:

    {% for cag in categories %}
    <div class="list_model">
        <div class="list_title clearfix">
            <h3 class="fl" id="model01">{{ cag.cag_name }}</h3>
            <a href="/goods/?cag={{ cag.id }}&page=1" class="goods_more fr" id="fruit_more">查看更多 ></a>
        </div>

        <div class="goods_con clearfix">
            <div class="goods_banner fl"><img src="/static/{{ cag.cag_img }}"></div>
            <ul class="goods_list fl">

                {% for goods in cag.goods_list %}
                <li>
                    <h4><a href="/detail/?id={{ goods.id }}">{{ goods.goods_name }}</a></h4>
                    <a href="/detail/?id={{ goods.id }}"><img src="/static/{{ goods.goods_img }}"></a>
                    <div class="prize">¥ {{ goods.goods_price }}</div>
                </li>
                {% endfor %}

            </ul>
        </div>
    </div>
    {% endfor %}

当点击商品图片或者名字时, 跳转到:

/detail/?id={{ goods.id }}

问号之后的内容为请求携带的参数, 键为 id, 值为商品ID.

25.2.7 商品详细页面功能实现

商品详细页面需要展示的数据为, 商品分类和具体商品数据. 我们在 goods 应用的 views.py 模块中新增视图函数 detail, 用于处理商品详细信息显示. 代码实现如下:

def detail(request):
    """商品详细页面"""

    # 获得产品ID
    goods_id = request.GET.get('id', 1)
    # 查询该商品
    goods_data = GoodsInfo.objects.get(id=goods_id)
    # 查询商品分类
    categories = GoodsCategory.objects.all()

    # 读取购物车商品列表
    cart_goods_list = []
    # 商品总数
    cart_goods_count = 0
    for goods_id, goods_num in request.COOKIES.items():
        if goods_id == 'csrftoken':
            continue

        cart_goods = GoodsInfo.objects.get(id=goods_id)
        cart_goods.goods_num = goods_num
        cart_goods_list.append(cart_goods)
        # 累加购物车商品总数
        cart_goods_count = cart_goods_count + int(goods_num)

    return render(request, 'detail.html', {'categories': categories,
                                           'goods_data': goods_data,
                                           'cart_goods_list': cart_goods_list,
                                           'cart_goods_count': cart_goods_count})

当我们请求的网址为: localhost:8000/detail/?id=100 时, Django 会自动将问号(?)后面的参数解析, 并存储在 request.GET 字典中. 我们获取了商品的ID, 根据 ID 到数据库中查询该商品数据. 并在模板中显示.

detail.html 模板代码如下:

...
	<div class="navbar_con">
		<div class="navbar clearfix">
			<div class="subnav_con fl">
				<h1>全部商品分类</h1>	
				<span></span>			
				<ul class="subnav">
                    {% for cag in categories %}
                        <li><a href="/goods/?cag={{ cag.id }}&page=1" class="{{ cag.cag_css }}">{{ cag.cag_name }}</a></li>
                    {% endfor %}
				</ul>
			</div>
		</div>
	</div>

	<div class="breadcrumb">
		<a href="#">全部分类</a>
		<span>></span>
		<a href="#">{{ goods_data.goods_cag.cag_name }}</a>
		<span>></span>
		<a href="#">商品详情</a>
	</div>

	<div class="goods_detail_con clearfix">
		<div class="goods_detail_pic fl"><img src="/static/{{ goods_data.goods_img }}"></div>

		<div class="goods_detail_list fr">
			<h3>{{ goods_data.goods_name }}</h3>
			<br>
			<div class="prize_bar">
				<span class="show_pirze">¥<em>{{ goods_data.goods_price }}</em></span>
				<span class="show_unit">单  位:{{ goods_data.goods_unit }}</span>
			</div>
			<div class="goods_num clearfix">
				<div class="num_name fl">数 量:</div>
				<div class="num_add fl">
					<input type="text" class="num_show fl" value="1" disabled>
				</div> 
			</div>
			<div class="operate_btn">
				<a href="/cart/add_cart/?id={{ goods_data.id }}" class="add_cart" id="add_cart">加入购物车</a>
			</div>
		</div>
	</div>

	<div class="main_wrap clearfix">
			<ul class="detail_tab clearfix">
				<li class="active">商品介绍</li>
			</ul>

			<div class="tab_content">
				<dl>
					<dd>{{ goods_data.goods_desc }}</dd>
				</dl>
			</div>
	</div>

...

在 ttsx 目录下的 urls.py 模块中, 配置该视图函数和网址之间的对应关系.

from django.conf.urls import include, url
from django.contrib import admin
from goods.views import index  # 导入视图函数
from goods.views import detail

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^index/$', index),
    url(r'^detail/$', detail),
]

本页面除了展示商品的详细信息之外, 分类信息, 当用户点击"加入购物车"按钮, 会将产品信息加入购物车. 我们在模板中创建了加入购物车的连接, 代码如下:

<a href="/cart/add_cart/?id={{ goods_data.id }}" class="add_cart" id="add_cart">加入购物车</a>

我们在 cart 应用下的 views.py 模块中, 新增 add_cart 视图函数, 代码实现如下:

def add_cart(request):
    """添加商品到购物车"""

    # 获得商品ID
    goods_id = request.GET.get('id', '')
    if goods_id:
        # 获得上一页面地址
        prev_url = request.META['HTTP_REFERER']
        # 写入到 cookie 中
        response = redirect(prev_url)
        # 判断商品是否存在
        goods_count = request.COOKIES.get(goods_id, '')
        if goods_count:
            goods_count = int(goods_count) + 1
        else:
            goods_count = 1

        response.set_cookie(goods_id, goods_count)

    return response

然后在 ttsx/urls.py 模块中配置该视图函数和网址之间的对应关系, 配置如下:

from django.conf.urls import include, url
from django.contrib import admin
from goods.views import index  # 导入视图函数
from goods.views import detail
from cart.views import add_cart

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^index/$', index),
    url(r'^detail/$', detail),
    url(r'^cart/add_cart/$', add_cart),
]

我们的购物车是通过 cookie 来实现的, 首先我们获得要加入购物车的商品ID, 代码如下:

goods_id = request.GET.get('id', '')

然后获得上一页面的地址, 在 request 对象的 META 字典中保存了用户访问的上一页面地址, 代码如下:

prev_url = request.META['HTTP_REFERER']

当处理完购物车添加之后, 我们希望页面跳回到上一页面. 所以我们通过快捷函数 redirect, 创建另一个重定向响应, 该函数接收一个参数, 参数为要跳转的页面地址, 返回的响应对象类型为 HttpResponseRedirect, 该对象为 HttpResponse 的子类. 代码如下:

response = redirect(prev_url)

然后判断当前要添加的商品ID是否已经在购物车中存在, 如果存在则更新商品数量, 如果不存在则新增一条cookie 信息, 代码如下:

  goods_count = request.COOKIES.get(goods_id, '')
        if goods_count:
            goods_count = int(goods_count) + 1
        else:
            goods_count = 1

最终将商品添加到 cookie 中, 代码如下:

response.set_cookie(goods_id, goods_count)

当返回该重定向响应之后, 浏览器会重新请求上一页面地址, 完成页面跳转.

return response

25.2.8 商品分类页面功能实现

  • 该页面主要实现当点击首页分类链接、或者商品详细页面的分类链接, 会显示当前分类下的所有商品.
  • 对显示的商品数据进行分类.

我们在 goods 应用中的 views.py 模块中的 goods 视图负责展示分类商品数据. 该视图函数接收2个参数:

  1. 当前分类是那个?
  2. 显示第几页数据?

goods 视图函数实现如下:

def goods(request):
    """商品展示页面"""

    # 获得当前分类
    cag_id = request.GET.get('cag', 1)
    # 获得当前页码
    page_id = request.GET.get('page', 1)
    # 查询所有数据
    goods_data = GoodsInfo.objects.filter(goods_cag_id=cag_id)
    # 数据分页
    paginator = Paginator(goods_data, 12)
    # 获得当前页码数据
    page_data = paginator.page(page_id)
    # 查询商品分类
    categories = GoodsCategory.objects.all()
    # 查询当前商品分类
    current_cag = GoodsCategory.objects.get(id=cag_id)

    # 读取购物车商品列表
    cart_goods_list = []
    # 商品总数
    cart_goods_count = 0
    for goods_id, goods_num in request.COOKIES.items():
        if goods_id == 'csrftoken':
            continue

        cart_goods = GoodsInfo.objects.get(id=goods_id)
        cart_goods.goods_num = goods_num
        cart_goods_list.append(cart_goods)
        # 累加购物车商品总数
        cart_goods_count = cart_goods_count + int(goods_num)

    return render(request, 'goods.html', {'page_data': page_data,
                                          'categories': categories,
                                          'current_cag': current_cag,
                                          'cart_goods_list': cart_goods_list,
                                          'cart_goods_count': cart_goods_count,
                                          'paginator': paginator,
                                          'cag_id': cag_id})

首先获得当前的分类ID和当前的页码数据, 例如请求的网址为: locahost:8000/goods/?id=2&page=3, 则表示要查看分类id 为2, 页码为3的数据. "&"符号用于连接多个请求参数.

# 获得当前分类
cag_id = request.GET.get('cag', 1)
# 获得当前页码
page_id = request.GET.get('page', 1)

然后根据分类查询当前分类下所有的数据:

# 查询所有数据
goods_data = GoodsInfo.objects.filter(goods_cag_id=cag_id)

查完数据之后, 我们下面要对商品的数据进行分页. 首先导入分页器 Paginator, 该类能够帮我们完成分页操作.

from django.core.paginator import Paginator

该分页器对象的创建, 需要指定2个参数, 第一个是对那个结果集进行分页, 第二个参数指定每页显示多少条数据.

# 数据分页
paginator = Paginator(goods_data, 12)
# 获得当前页码数据
page_data = paginator.page(page_id)

创建完 paginator 对象之后, 我们通过其 page 方法即可获得当前页码指定的数据. page_data 为当前页码的数据. 下面介绍了该类的使用方法:

Paginator类

class Paginator(object_list,
                per_page,
                orphans=0,
                allow_empty_first_page=True)
参数描述
object_list一个 list、tuple、Django QuerySet
per_page每页显示多少条数据
orphans最后一页数据量大于orphans才显示最后一页
allow_empty_first_page如果False,那么数据集为空,会抛出EmptyPage异常
  • 方法:
方法描述
Paginator.page(number)返回在提供的下标处的Page对象, 下标以1开始. 如果提供的页码不存在, 抛出 InvalidPage 异常. PageNotAnIntege r和 EmptyPage 是 InvalidPage 子类
  • 属性:
属性描述
Paginator.num_pages页面的总数
Paginator.page_range页码的范围,从1开始,例如[1, 2, 3, 4]
Paginator.count所有页面的对象总数

Page类

通常不需要手动构建 Page 对象, 我们可以从 Paginator.page() 来获得它们.

  • 方法:
方法描述
Page.has_next()如果有下一页,则返回True
Page.has_previous()如果有上一页,返回 True
Page.has_other_pages()如果有上一页或下一页,返回True
Page.next_page_number()返回下一页的页码. 如果下一页不存在, 抛出InvalidPage异常
Page.previous_page_number()返回上一页的页码。如果上一页不存在,抛出InvalidPage异常
  • 属性:
属性描述
Page.number当前页的序号, 从1开始

下面的代码就是购物车数据的获取:

# 读取购物车商品列表
cart_goods_list = []
# 商品总数
cart_goods_count = 0
for goods_id, goods_num in request.COOKIES.items():
    if goods_id == 'csrftoken':
        continue

    cart_goods = GoodsInfo.objects.get(id=goods_id)
    cart_goods.goods_num = goods_num
    cart_goods_list.append(cart_goods)
    # 累加购物车商品总数
    cart_goods_count = cart_goods_count + int(goods_num)

goods.html 模板代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<title>{{ current_cag.cag_name }}-商品列表</title>
	<link rel="stylesheet" type="text/css" href="/static/css/reset.css">
	<link rel="stylesheet" type="text/css" href="/static/css/main.css">
</head>
<body>
	<div class="header_con">
		<div class="header">
			<div class="welcome fl">欢迎来到天天生鲜!</div>
		</div>		
	</div>

	<div class="search_bar clearfix">
		<a href="/" class="logo fl"><img src="/static/images/logo.png"></a>
		<div class="guest_cart fr">
			<a href="/cart/show_cart/" class="cart_name fl">我的购物车</a>
			<div class="goods_count fl">{{ cart_goods_count }}</div>

			<ul class="cart_goods_show">


                {% for cart_goods in cart_goods_list %}
				<li>
					<img src="/static/{{ cart_goods.goods_img }}" alt="商品图片">
					<h4>{{ cart_goods.goods_name }}</h4>
					<div>{{ cart_goods.goods_num }}</div>
				</li>
                {% endfor %}

			</ul>

		</div>
	</div>

	<div class="navbar_con">
		<div class="navbar clearfix">
			<div class="subnav_con fl">
				<h1>全部商品分类</h1>	
				<span></span>			
				<ul class="subnav">
                    {% for cag in categories %}
                        <li><a href="?cag={{ cag.id }}&page=1" class="{{ cag.cag_css }}">{{ cag.cag_name }}</a></li>
                    {% endfor %}
				</ul>
			</div>
		</div>
	</div>

	<div class="breadcrumb">
		<a href="#">当前分类:</a>
		<span></span>
		<a href="#">{{ current_cag.cag_name }}</a>
	</div>

	<div class="main_wrap clearfix">

		<div class="sort_bar"></div>

		<ul class="goods_type_list clearfix">

            {% for goods in page_data %}
			<li>
				<a href="/detail/?id={{ goods.id }}"><img src="/static/{{ goods.goods_img }}"></a>
				<div class="operate">
					<h4><a href="/detail/?id={{ goods.id }}">{{ goods.goods_name }}</a></h4>
					<span class="prize">{{ goods.goods_price }}</span>
				</div>
			</li>
            {% endfor %}

		</ul>

		<div class="pagenation">

            {% if page_data.has_previous %}
			<a href="?cag={{ cag_id }}&page={{ page_data.previous_page_number }}">上一页</a>
            {% endif %}

            {% for index in paginator.page_range %}
			    {% if index == page_data.number %}
                    <a href="?cag={{ cag_id }}&page={{ index }}" class="active">{{ index }}</a>
                {% else %}
                    <a href="?cag={{ cag_id }}&page={{ index }}">{{ index }}</a>
                {% endif %}
            {% endfor %}

            {% if page_data.has_next %}
			<a href="?cag={{ cag_id }}&page={{ page_data.next_page_number }}">下一页></a>
            {% endif %}
		</div>
	</div>

	<div class="footer">
		<div class="foot_link">
			<a href="#">关于我们</a>
			<span>|</span>
			<a href="#">联系我们</a>
			<span>|</span>
			<a href="#">招聘人才</a>
			<span>|</span>
			<a href="#">友情链接</a>		
		</div>
		<p>CopyRight © 2016 北京天天生鲜信息技术有限公司 All Rights Reserved</p>
		<p>电话:010-****888    京ICP备*******8</p>
	</div>
	
</body>
</html>

如下代码用于显示购物车相关数据:

<a href="/cart/show_cart/" class="cart_name fl">我的购物车</a>
<div class="goods_count fl">{{ cart_goods_count }}</div>

<ul class="cart_goods_show">


    {% for cart_goods in cart_goods_list %}
	<li>
		<img src="/static/{{ cart_goods.goods_img }}" alt="商品图片">
		<h4>{{ cart_goods.goods_name }}</h4>
		<div>{{ cart_goods.goods_num }}</div>
	</li>
    {% endfor %}

</ul>

如下代码用于显示商品分类数据:

{% for cag in categories %}
<li><a href="?cag={{ cag.id }}&page=1" class="{{ cag.cag_css }}">{{ cag.cag_name }}</a></li>
{% endfor %}

如下代码用于显示某分类下商品数据:

{% for goods in page_data %}
<li>
	<a href="/detail/?id={{ goods.id }}"><img src="/static/{{ goods.goods_img }}"></a>
	<div class="operate">
		<h4><a href="/detail/?id={{ goods.id }}">{{ goods.goods_name }}</a></h4>
		<span class="prize">{{ goods.goods_price }}</span>
	</div>
</li>
{% endfor %}

如下代码显示分页页码:

{% if page_data.has_previous %}
<a href="?cag={{ cag_id }}&page={{ page_data.previous_page_number }}">上一页</a>
{% endif %}

{% for index in paginator.page_range %}
    {% if index == page_data.number %}
        <a href="?cag={{ cag_id }}&page={{ index }}" class="active">{{ index }}</a>
    {% else %}
        <a href="?cag={{ cag_id }}&page={{ index }}">{{ index }}</a>
    {% endif %}
{% endfor %}

{% if page_data.has_next %}
<a href="?cag={{ cag_id }}&page={{ page_data.next_page_number }}">下一页></a>
{% endif %}

25.2.9 购物车页面功能实现

购物车页面主要两个功能:

  1. 显示购物车商品详细数据.
  2. 增加商品删除功能.

我们在 cart 应用的 views.py 模块中增加 show_cart 视图函数, 来显示购物车商品数据, 代码如下:

def show_cart(request):
    """展示购物车商品"""

    # 读取购物车商品列表
    cart_goods_list = []
    # 商品总数
    cart_goods_count = 0
    # 商品总价
    cart_goods_money = 0
    for goods_id, goods_num in request.COOKIES.items():
        if goods_id == 'csrftoken':
            continue

        cart_goods = GoodsInfo.objects.get(id=goods_id)
        cart_goods.goods_num = goods_num
        cart_goods.total_money = int(goods_num) * cart_goods.goods_price
        cart_goods_list.append(cart_goods)
        # 累加购物车商品总数
        cart_goods_count = cart_goods_count + int(goods_num)
        # 累计商品总价
        cart_goods_money += int(goods_num) * cart_goods.goods_price

    return render(request, 'cart.html', {'cart_goods_list': cart_goods_list,
                                         'cart_goods_count': cart_goods_count,
                                         'cart_goods_money': cart_goods_money})

cart.html 模板代码数据如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<title>天天生鲜-购物车</title>
	<link rel="stylesheet" type="text/css" href="/static/css/reset.css">
	<link rel="stylesheet" type="text/css" href="/static/css/main.css">
</head>
<body>
	<div class="header_con">
		<div class="header">
			<div class="welcome fl">欢迎来到天天生鲜!</div>
		</div>		
	</div>

	<div class="search_bar clearfix">
		<a href="index.html" class="logo fl"><img src="/static/images/logo.png"></a>
		<div class="sub_page_name fl">|&nbsp;&nbsp;&nbsp;&nbsp;购物车</div>	
	</div>

	<div class="total_count">全部商品<em>{{ cart_goods_count }}</em></div>
	<ul class="cart_list_th clearfix">
		<li class="col01">商品名称</li>
		<li class="col02">商品单位</li>
		<li class="col03">商品价格</li>
		<li class="col04">数量</li>
		<li class="col05">小计</li>
		<li class="col06">操作</li>
	</ul>

    {% for cart_goods in cart_goods_list %}
	<ul class="cart_list_td clearfix">
		<li class="col01"></li>
		<li class="col02"><img src="/static/{{ cart_goods.goods_img }}"></li>
		<li class="col03">奇异果<br><em>{{ cart_goods.goods_price }}/{{ cart_goods.goods_unit }}</em></li>
		<li class="col04">{{ cart_goods.goods_unit }}</li>
		<li class="col05">{{ cart_goods.goods_price }}</li>
		<li class="col06">{{ cart_goods.goods_num }}</li>
		<li class="col07">{{ cart_goods.goods_price }}</li>
		<li class="col08"><a href="/cart/remove_cart/?id={{ cart_goods.id }}">删除</a></li>
	</ul>
    {% endfor %}

	<ul class="settlements">
		<li class="col01">.</li>
		<li class="col02">.</li>
		<li class="col03">合计(不含运费)<span>¥</span><em>{{ cart_goods_money }}</em><br>共计<b>{{ cart_goods_count }}</b>件商品</li>
		<li class="col04"><a href="/cart/place_order/">去结算</a></li>
	</ul>

	<div class="footer">
		<div class="foot_link">
			<a href="#">关于我们</a>
			<span>|</span>
			<a href="#">联系我们</a>
			<span>|</span>
			<a href="#">招聘人才</a>
			<span>|</span>
			<a href="#">友情链接</a>		
		</div>
		<p>CopyRight © 2016 北京天天生鲜信息技术有限公司 All Rights Reserved</p>
		<p>电话:010-****888    京ICP备*******8</p>
	</div>
	
</body>
</html>

如下模板代码用于显示购物车商品数据:

{% for cart_goods in cart_goods_list %}
<ul class="cart_list_td clearfix">
	<li class="col01"></li>
	<li class="col02"><img src="/static/{{ cart_goods.goods_img }}"></li>
	<li class="col03">奇异果<br><em>{{ cart_goods.goods_price }}/{{ cart_goods.goods_unit }}</em></li>
	<li class="col04">{{ cart_goods.goods_unit }}</li>
	<li class="col05">{{ cart_goods.goods_price }}</li>
	<li class="col06">{{ cart_goods.goods_num }}</li>
	<li class="col07">{{ cart_goods.goods_price }}</li>
	<li class="col08"><a href="/cart/remove_cart/?id={{ cart_goods.id }}">删除</a></li>
</ul>
{% endfor %}

如下代码显示购物车的总商品数量、商品总价:

<li class="col03">合计(不含运费)<span>¥</span><em>{{ cart_goods_money }}</em><br>共计<b>{{ cart_goods_count }}</b>件商品</li>

我们的购物车页面提供了删除购物车商品的功能, 该功能的链接代码如下:

<li class="col08"><a href="/cart/remove_cart/?id={{ cart_goods.id }}">删除</a></li>

当用户要删除某个购物车商品时, 该请求由 cart 应用下的 remove_cart 视图来处理, 该视图函数实现如下:

def remove_cart(request):
    """删除购物车商品"""

    # 获得要删除的商品ID
    goods_id = request.GET.get('id', '')
    if goods_id:
        # 获得上一页面地址
        prev_url = request.META['HTTP_REFERER']
        # 写入到 cookie 中
        response = redirect(prev_url)
        # 判断商品是否存在
        goods_count = request.COOKIES.get(goods_id, '')
        if goods_count:
            response.delete_cookie(goods_id)

    return response

首先要获得删除商品的 ID, 代码如下:

goods_id = request.GET.get('id', '')

如果该商品在购物车中存在, 则删除它. 删除完成之后跳转到上一页面, 也就是购物车页面, 代码如下:

if goods_id:
    # 获得上一页面地址
    prev_url = request.META['HTTP_REFERER']
    # 写入到 cookie 中
    response = redirect(prev_url)
    # 判断商品是否存在
    goods_count = request.COOKIES.get(goods_id, '')
    if goods_count:
        response.delete_cookie(goods_id)

25.2.10 提交订单页面功能实现

当购物车商品数据确认无误之后, 点击结算按钮跳转到订单提交页面, 在该页面用户就需要填写收货地址、联系电话、联系人等信息.

我们在 cart 应用下的 views.py 模块中新增如下视图函数:

def place_order(request):
    """提交订单页面"""

    # 读取购物车商品列表
    cart_goods_list = []
    # 商品总数
    cart_goods_count = 0
    # 商品总价
    cart_goods_money = 0
    for goods_id, goods_num in request.COOKIES.items():
        if goods_id == 'csrftoken':
            continue

        cart_goods = GoodsInfo.objects.get(id=goods_id)
        cart_goods.goods_num = goods_num
        cart_goods.total_money = int(goods_num) * cart_goods.goods_price
        cart_goods_list.append(cart_goods)
        # 累加购物车商品总数
        cart_goods_count = cart_goods_count + int(goods_num)
        # 累计商品总价
        cart_goods_money += int(goods_num) * cart_goods.goods_price

    return render(request, 'place_order.html', {'cart_goods_list': cart_goods_list,
                                                'cart_goods_count': cart_goods_count,
                                                'cart_goods_money': cart_goods_money})

并在 ttsx/urls.py 模块中配置该视图对应的请求网址, 新增配置如下:

from django.conf.urls import include, url
from django.contrib import admin
from goods.views import index  # 导入视图函数
from goods.views import detail
from goods.views import goods
from cart.views import add_cart
from cart.views import show_cart
from cart.views import place_order

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^index/$', index),
    url(r'^detail/$', detail),
    url(r'^cart/add_cart/$', add_cart),
    url(r'^goods/$', goods),
    url(r'^cart/show_cart/$', show_cart),
    url(r'^cart/place_order/$', place_order),
]

cart.html 模板代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<title>天天生鲜-提交订单</title>
	<link rel="stylesheet" type="text/css" href="/static/css/reset.css">
	<link rel="stylesheet" type="text/css" href="/static/css/main.css">
</head>
<body>
	<div class="header_con">
		<div class="header">
			<div class="welcome fl">欢迎来到天天生鲜!</div>
		</div>		
	</div>

	<div class="search_bar clearfix">
		<a href="/" class="logo fl"><img src="/static/images/logo.png"></a>
		<div class="sub_page_name fl">|&nbsp;&nbsp;&nbsp;&nbsp;提交订单</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 cart_goods in cart_goods_list %}
        <ul class="goods_list_td clearfix">
			<li class="col01">1</li>			
			<li class="col02"><img src="/static/{{ cart_goods.goods_img }}"></li>
			<li class="col03">{{ cart_goods.goods_name }}</li>
			<li class="col04">{{ cart_goods.goods_unit }}</li>
			<li class="col05">{{ cart_goods.goods_price }}</li>
			<li class="col06">{{ cart_goods.goods_num }}</li>
			<li class="col07">{{ cart_goods.total_money }}</li>
		</ul>
        {% endfor %}

	</div>

	<h3 class="common_title">收货地址</h3>
    <form action="/cart/submit_order/" method="post">
    {% csrf_token %}
	<div class="common_list_con clearfix">
		<dl>
			<dd>
				<table>
					<tr>
						<td>收货地址:</td><td><input style="width: 350px; height: 23px;" type="text" name="addr"></td>
					</tr>
					<tr>
						<td>收货人:</td><td><input style="width: 350px; height: 23px;" type="text" name="recv"></td>
					</tr>
					<tr>
						<td>联系电话:</td><td><input style="width: 350px; height: 23px;" type="text" name="tele"></td>
					</tr>
					<tr>
						<td>备注:</td><td><textarea style="width: 350px; height: 100px; font-size: 12px;" name="extra"></textarea></td>
					</tr>
				</table>
			</dd>
		</dl>
	</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>
		</div>
	</div>

	<h3 class="common_title">总金额结算</h3>
	<div class="common_list_con clearfix">
		<div class="settle_con">
			<div class="total_goods_count"><em>{{ cart_goods_count }}</em>件商品,总金额<b>{{ cart_goods_money }}</b></div>
			<div class="transit">运费:<b>10</b></div>
			<div class="total_pay">实付款:<b>{{ cart_goods_money|add:10 }}</b></div>
		</div>
	</div>


	<div class="order_submit clearfix">
        <input type="submit" value="提交订单"/>
	</div>	
    </form>

	<div class="footer">
		<div class="foot_link">
			<a href="#">关于我们</a>
			<span>|</span>
			<a href="#">联系我们</a>
			<span>|</span>
			<a href="#">招聘人才</a>
			<span>|</span>
			<a href="#">友情链接</a>		
		</div>
		<p>CopyRight © 2016 北京天天生鲜信息技术有限公司 All Rights Reserved</p>
		<p>电话:010-****888    京ICP备*******8</p>
	</div>

	<div class="popup_con">
		<div class="popup">
			<p>订单提交成功!</p>
		</div>
		
		<div class="mask"></div>
	</div>
</body>
</html>

我们在模板中新增了一个 form 表单用于提交订单数据, 代码如下:

 <form action="/cart/submit_order/" method="post">
    {% csrf_token %}
	<div class="common_list_con clearfix">
		<dl>
			<dd>
				<table>
					<tr>
						<td>收货地址:</td><td><input style="width: 350px; height: 23px;" type="text" name="addr"></td>
					</tr>
					<tr>
						<td>收货人:</td><td><input style="width: 350px; height: 23px;" type="text" name="recv"></td>
					</tr>
					<tr>
						<td>联系电话:</td><td><input style="width: 350px; height: 23px;" type="text" name="tele"></td>
					</tr>
					<tr>
						<td>备注:</td><td><textarea style="width: 350px; height: 100px; font-size: 12px;" name="extra"></textarea></td>
					</tr>
				</table>
			</dd>
		</dl>
	</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>
		</div>
	</div>

	<h3 class="common_title">总金额结算</h3>
	<div class="common_list_con clearfix">
		<div class="settle_con">
			<div class="total_goods_count"><em>{{ cart_goods_count }}</em>件商品,总金额<b>{{ cart_goods_money }}</b></div>
			<div class="transit">运费:<b>10</b></div>
			<div class="total_pay">实付款:<b>{{ cart_goods_money|add:10 }}</b></div>
		</div>
	</div>


	<div class="order_submit clearfix">
        <input type="submit" value="提交订单"/>
	</div>	
    </form>

该 form 表单将订单数据提交到 /cart/submit_order/ 页面进行处理, 代码如下:

 <form action="/cart/submit_order/" method="post">
    {% csrf_token %}

其中 {% csrf_token %} 主要用于安全设置, 这是django提供的防护错误, 用于防护跨站请求伪造. 在如果提交的表单是以 post 方式提交的话, 那么需要我们在 form 表单内部加上该代码.

代码 {{ cart_goods_money|add:10 }} 的含义为 cart_goods_money 的值 加上10. add 是django模板语法中提供的过滤器函数, 使用语法就是:

{{ 变量|过滤器函数:参数 }}

用户填写完收货信息之后, 点击提交订单按钮, 将表单提交到了 /cart/submit_order/ 页面, 我们在 cart 应用下的 views.py 模块中新增 submit_order 视图函数用于处理订单提交. 该视图函数实现如下:

def submit_order(request):
    """保存订单"""

    # 获得订单信息
    addr = request.POST.get('addr', '')
    recv = request.POST.get('recv', '')
    tele = request.POST.get('tele', '')
    extra = request.POST.get('extra', '')

    # 保存订单信息
    order_info = OrderInfo()
    order_info.order_addr = addr
    order_info.order_tele = tele
    order_info.order_recv = recv
    order_info.order_extra = extra
    # 生成订单编号
    order_info.order_id = str(int(time.time() * 1000)) + str(int(time.clock() * 1000000))
    order_info.save()

    # 跳转页面
    response = redirect('/cart/submit_success/?id=%s' % order_info.order_id)

    # 保存订单商品信息
    for goods_id, goods_num in request.COOKIES.items():
        if goods_id == 'csrftoken':
            continue
        # 查询商品信息
        cart_goods = GoodsInfo.objects.get(id=goods_id)
        # 创建订单商品信息
        order_goods = OrderGoods()
        order_goods.goods_info = cart_goods
        order_goods.goods_order = order_info
        order_goods.goods_num = goods_num
        order_goods.save()
        # 删除购物车信息
        response.delete_cookie(goods_id)

    return response

配置该视图与网址的对应关系, 编辑 ttsx/urls.py 模块如下:

from django.conf.urls import include, url
from django.contrib import admin
from goods.views import index  # 导入视图函数
from goods.views import detail
from goods.views import goods
from cart.views import add_cart
from cart.views import show_cart
from cart.views import place_order
from cart.views import submit_order

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^index/$', index),
    url(r'^detail/$', detail),
    url(r'^cart/add_cart/$', add_cart),
    url(r'^goods/$', goods),
    url(r'^cart/show_cart/$', show_cart),
    url(r'^cart/place_order/$', place_order),
    url(r'^cart/submit_order/$', submit_order),
]

首先我们获得表单提交的数据, 这里需要大家注意, 如果表单以 post 方提交的话, 那么表单中提交的数据会被存储在 request 对象的 POST 字典中.

# 获得订单信息
addr = request.POST.get('addr', '')
recv = request.POST.get('recv', '')
tele = request.POST.get('tele', '')
extra = request.POST.get('extra', '')

我们的订单信息保存分为两部分, 第一部分是订单基本信息, 存储在 OrderInfo 模型所对应的数据库表中, 代码如下:

# 保存订单信息
order_info = OrderInfo()
order_info.order_addr = addr
order_info.order_tele = tele
order_info.order_recv = recv
order_info.order_extra = extra
# 生成订单编号
order_info.order_id = str(int(time.time() * 1000)) + str(int(time.clock() * 1000000))
order_info.save()

然后将订单中的商品信息存储在 OrderGoods 模型所对应的表中. 首先我们创建了一个重定向响应对象, 指定订单提交成功之后要跳转的页面:

# 跳转页面
response = redirect('/cart/submit_success/?id=%s' % order_info.order_id)

然后保存订单商品信息到数据库中, 并删除购物车中商品数据, 代码如下:

# 保存订单商品信息
for goods_id, goods_num in request.COOKIES.items():
    if goods_id == 'csrftoken':
        continue
    # 查询商品信息
    cart_goods = GoodsInfo.objects.get(id=goods_id)
    # 创建订单商品信息
    order_goods = OrderGoods()
    order_goods.goods_info = cart_goods
    order_goods.goods_order = order_info
    order_goods.goods_num = goods_num
    order_goods.save()
    # 删除购物车信息
    response.delete_cookie(goods_id)

当订单提交成功之后, 删除购物车中的商品信息, 跳转到 submit_success 页面, 并传递过去订单编号.

25.2.11 订单提交成功页面功能实现

该视图主要是展示订单提交成功之后的数据, 主要思路是

def submit_success(request):
    """显示订单结果"""

    order_id = request.GET.get('id')

    order_info = OrderInfo.objects.get(order_id=order_id)
    order_goods_list = OrderGoods.objects.filter(goods_order=order_info)

    # 商品总价
    totla_money = 0
    # 商品总数量
    total_num = 0
    for goods in order_goods_list:
        goods.total_money = goods.goods_num * goods.goods_info.goods_price
        totla_money += goods.total_money
        total_num += goods.goods_num

    return render(request, 'success.html', {'order_info': order_info,
                                            'order_goods_list': order_goods_list,
                                            'totla_money': totla_money,
                                            'total_num': total_num})

首先获得订单编号, 根据订单编号从数据库从查询订单信息, 然后根据订单信息查询该订单所属的所有商品信息.

配置该视图与网址的对应关系:

from django.conf.urls import include, url
from django.contrib import admin
from goods.views import index  # 导入视图函数
from goods.views import detail
from goods.views import goods
from cart.views import add_cart
from cart.views import show_cart
from cart.views import place_order
from cart.views import submit_order
from cart.views import submit_success

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^index/$', index),
    url(r'^detail/$', detail),
    url(r'^cart/add_cart/$', add_cart),
    url(r'^goods/$', goods),
    url(r'^cart/show_cart/$', show_cart),
    url(r'^cart/place_order/$', place_order),
    url(r'^cart/submit_order/$', submit_order),
    url(r'^cart/submit_success/$', submit_success),
]
  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值