外观
Django框架-构建自己的网上商店
约 11368 字大约 38 分钟
2026-03-24
在本章中,我们创建一个全新的 Django 项目,该项目包含一个功能相对齐全的在线商店。在线商店允许客户浏览产品、将其添加到购物车、管理自己的购物车、下单操作等流程,并使用国际化功能使admin管理后台中文化。
本章你将学习如何:
创建产品目录
使用 Django session 构建购物车
创建自定义模板上下文处理器
我的购物车管理与下单操作
管理客户订单
功能概述
- 本章将构建的视图、模板和功能示意图:

- 整体项目目录结构:
myshop/ # 外部 myshop/ 根目录只是一个项目的容器
├── manage.py # 命令行实用程序,实现与此Django项目进行各种交互
├── myshop # 内部 myshop/ 目录是当前项目的实际Python包
│ ├── __init__.py # 空文件,代表Python包的标志文件
│ ├── settings.py # 此Django项目的设置/配置 文件
│ ├── urls.py # 该Django项目的URL路由声明
│ ├── asgi.py # 项目的 ASGI 入口点,用于部署异步 Web 服务器
│ └── wsgi.py # 项目的 WSGI 入口点,用于部署传统 Web 服务器
├── shop # shop/目录为账户应用程序目录名称(Python包)
│ ├── __init__.py # 空文件,代表Python包的标志文件
│ ├── admin.py # 用于在 Django 管理后台中注册模型(可选)
│ ├── apps.py # 当前应用的主要配置
│ ├── migrations # 此目录将包含应用程序的数据库迁移文件(Python包)
│ ├── models.py # 应用程序的数据模型,每个应用程序的必须文件
│ ├── tests.py # 应用的测试文件
│ ├── urls.py # 自定义当前应用URL路由配置
│ ├── views.py # 应用程序的逻辑写在这里,HTTP请求、处理并返回响应。
│ ├─── static/ # Web前端静态资源目录
│ │ ├── css/ # CSS页面样式目录
│ │ │ ├── base.css # 公共css样式
│ │ │ └── pdf.css #
│ │ └── img/ # 图片素材目录
│ │ └── no_image.png # 无图效果图
│ └─── templates/ # 模版目录
│ └── shop/ # 当前应用模板目录
│ ├── product/ # 公共css样式
│ │ ├── list.html # 商品列表展示模板
│ │ └── detail.html # 商品详情页模板
│ └── base.html # 公共父模板
│
├── cart/ # cart/目录为账户应用程序目录名称(Python包)
│ ├── __init__.py # 空文件,代表Python包的标志文件
│ ├── admin.py # 用于在 Django 管理后台中注册模型(可选)
│ ├── apps.py # 当前应用的主要配置
│ ├── migrations # 此目录将包含应用程序的数据库迁移文件(Python包)
│ ├── forms.py # 自定义表单文件(购物车表单)
│ ├── models.py # 应用程序的数据模型,每个应用程序的必须文件
│ ├── tests.py # 应用的测试文件
│ ├── urls.py # 自定义当前应用URL路由配置
│ ├── views.py # 应用程序的逻辑写在这里,HTTP请求、处理并返回响应。
│ └─── templates/ # 模版目录
│ └── cart/ # 当前应用模板目录
│ └── detail.html # 购物车中的商品展示模板
│
├── media/ # 存放上传的商品图片目录- 关于上面完整的静态资源与模板文件的下载:
一、 构建项目前的准备
注:准备Python3.12版本的虚拟环境,并安装Django5.2版本框架。若已完成请跳过。
1. 使用Python3.12的版本:
Django 5.2 支持 Python 3.10、3.11、3.12 和 3.13。接下来我们将使用 Python 3.12。
如果你的 Python 版本低于 3.12,或者电脑上还没装 Python,可以去 https://www.python.org/downloads/ 下载 Python 3.12 并安装。
python3 --version
python -V
# Python 3.12.92. 创建Python虚拟环境:
- 自 Python 3.3 版本起,Python 自带了一个
venv库,用于创建轻量级的虚拟环境。
# 1. 创建虚拟环境
python -m venv my_env
# 2. 激活虚拟环境
# Linux下的激活虚拟环境
#source my_env/bin/activate
# Windows下的激活虚拟环境
.\my_env\Scripts\activate
#3. Linux下的验证激活
# (my_env) $ which python
# Windows下的验证激活
(my_env) PS> Get-Command python
# 4. 停用虚拟环境
(my_env) $ deactivate3. 安装Django
- 作为Python Web框架,Django需要Python,在安装Python同时需要安装pip。
# 在线安装Django,指定版本安装,目前5.2的最新版为5.2.9
python -m pip install django==5.2
# 默认会安装:Django==5.2.9、sqlparse==0.5.4、asgiref==3.11.0 和 tzdata==2025.3
# 可以使用pip list查看
# 检测当前是否安装Django及版本
python -m django --version
5.2.9
# 我们也可以先下载安装包:pip download django=4.2.18 -d ./二、创建项目与构建应用
1. 创建项目 myshop
在当前Python虚拟环境中运行以下命令创建 myshop 项目:
$ django-admin startproject myshop查看startproject自动创建项目结构如下:
myshop/ # 外部 myshop/ 根目录只是一个项目的容器
├── manage.py # 命令行实用程序,实现与此Django项目进行各种交互
├── myshop # 内部 myshop/ 目录是当前项目的实际Python包
│ ├── __init__.py # 空文件,代表Python包的标志文件
│ ├── settings.py # 此Django项目的设置/配置 文件
│ ├── urls.py # 该Django项目的URL路由声明
│ ├── asgi.py # ASGI兼容的Web服务器为您的项目提供服务的入口点
│ └── wsgi.py # WSGI兼容的Web服务器为您的项目提供服务的入口点2. 创建应用程序 shop
Django 自带一个实用工具,可以自动生成应用的基本目录结构,这样你就能专注于编写代码,不用手动创建目录了。
创建应用时,确保和 manage.py 在同一目录下,然后执行以下命令:
$ cd myshop
$ python manage.py startapp shop- 这会创建一个 shop 目录,结构如下:
myshop/
├── manage.py
├─── myshop
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ ├── asgi.py
│ └── wsgi.py
└── shop # shop/目录为账户应用程序目录名称(Python包)
├── __init__.py # 空文件,代表Python包的标志文件
├── admin.py # 用于在Django管理站点中注册模型的,使用此站点是可选
├── apps.py # 包括blog应用程序的主要配置
├── migrations # 此目录将包含应用程序的数据库迁移文件(Python包)
│ └── __init__.py
├── models.py # 应用程序的数据模型,每个应用程序的必须文件(可留空)
├── tests.py # 在这里为您的应用程序添加测试
└── views.py # 应用程序的逻辑写在这里,HTTP请求、处理并返回响应。3. 启用网站Admin管理
(1). 项目配置文件的设置
进入 myshop/settings.py 文件,进行以下配置:
编辑
myshop/settings.py,在INSTALLED_APPS列表中添加下面加粗的那行:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'shop.apps.ShopConfig', # 添加自己创建的shop应用
]- 默认情况下,继续保持使用SQLite数据库,无需额外设置 DATABASES :
# SQLite数据库的连接配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}- 设置时区和语言,让admin后台显示中文版界面:
...
LANGUAGE_CODE = 'zh-hans' # 语言设置
TIME_ZONE = 'Asia/Shanghai' # 时区设置
...(2). 数据迁移
- 运行以下命令实现网站Admin管理的数据结构迁移:
$ python manage.py migrate- 也可执行下面命令来查看数据结构迁移后的效果。
$ python manage.py showmigrations- 具体执行效果如下:
# 执行上面命令后的输出结果
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying sessions.0001_initial... OK
# 执行下面命令查看迁移状态,具体如下:
$ python manage.py showmigrations
admin
[X] 0001_initial
[X] 0002_logentry_remove_auto_add
[X] 0003_logentry_add_action_flag_choices
auth
[X] 0001_initial
[X] 0002_alter_permission_name_max_length
[X] 0003_alter_user_email_max_length
[X] 0004_alter_user_username_opts
[X] 0005_alter_user_last_login_null
[X] 0006_require_contenttypes_0002
[X] 0007_alter_validators_add_error_messages
[X] 0008_alter_user_username_max_length
[X] 0009_alter_user_last_name_max_length
[X] 0010_alter_group_name_max_length
[X] 0011_update_proxy_permissions
[X] 0012_alter_user_first_name_max_length
contenttypes
[X] 0001_initial
[X] 0002_remove_content_type_name
sessions
[X] 0001_initial(3). 创建管理员用户
首先,我们需要创建一个可以登录管理后台的用户。运行以下命令:
$ python manage.py createsuperuser
# 输入用户名,然后按回车:
Username: admin
# 接着输入邮箱地址:
Email address: admin@example.com
# 最后输入密码(至少 8 位),需要输入两次进行确认:
Password: **********
Password (again): *********
Superuser created successfully.(4). 启动开发服务器
默认情况下,Django 管理后台已经是启用的。让我们启动开发服务器来看看效果。
启动开发服务器命令如下:
$ python manage.py runserver
或
$ python manage.py runserver 0.0.0.0:8000现在,打开一个Web浏览器,访问地址: http://127.0.0.1:8000/admin/
使用刚刚创建的账号密码进行登录测试:

三、添加商品类别与商品信息模型
1. 添加商品类别与商品信息模型
在我们的shop应用程序中,去创建一个stu表信息操作的Model类。
编辑 shop/models.py文件,完整商品类别与商品信息的model类信息如下:
"""
商品信息模型
定义了商品类别和商品的基本信息,并定义了商品类别和商品之间的关联关系。
"""
from django.db import models # 导入Django的模型模块
from django.urls import reverse # 导入Django的URL模块
# 商品类别
class Category(models.Model):
name = models.CharField('类别名称', max_length=200)
slug = models.SlugField('类别别名', max_length=200, unique=True)
class Meta:
ordering = ['name'] # 默认排序按名称
indexes = [
models.Index(fields=['name']), # 索引
]
verbose_name = '商品类别'
verbose_name_plural = '商品类别管理'
def __str__(self):
return self.name
# 获取类别的绝对URL
def get_absolute_url(self):
return reverse(
'shop:product_list_by_category', args=[self.slug]
)
# 商品信息模型
class Product(models.Model):
category = models.ForeignKey(
Category,
related_name='products',
on_delete=models.CASCADE,
verbose_name='商品类别'
)
name = models.CharField('商品名称', max_length=200)
slug = models.SlugField('商品别名', max_length=200)
image = models.ImageField(
'商品图片',
upload_to='products/%Y/%m/%d',
blank=True
)
description = models.TextField('商品描述', blank=True)
price = models.DecimalField('价格', max_digits=10, decimal_places=2)
available = models.BooleanField('是否上架', default=True)
created = models.DateTimeField('创建时间', auto_now_add=True)
updated = models.DateTimeField('更新时间', auto_now=True)
class Meta:
ordering = ['name'] # 默认排序按名称
# 索引
indexes = [
models.Index(fields=['id', 'slug']),
models.Index(fields=['name']),
models.Index(fields=['-created']),
]
verbose_name = '商品信息' # 在Admin中显示的单数形式名称
verbose_name_plural = '商品信息管理'# 在Admin中显示的复数形式名称
def __str__(self):
return self.name
# 获取商品的绝对URL
def get_absolute_url(self):
return reverse('shop:product_detail', args=[self.id, self.slug])- 上面代码中定义了两个数据模型
Category和Product
- 这两个数据模型将生成如下所示的数据库表结构。

分类叶脉挂载树
Category ↔ Product ForeignKey Link & Slug Generation
Category (1)
ID: 1name: 智能手机slug: smart-phones
Product (N)
ID: 101 category_id: NULLname: iPhone 15 slug: ''
在电商数据库中,商品 (Product) 必须有一个 ForeignKey 指向商品分类 (Category)。当我们在后台保存时,商品不仅用一条无形的数据线缆( category_id = 1 )将自己死死地挂载到“智能手机”这个主干节点上,同时后台的 prepopulated_fields 设置还会顺着线缆根据商品全名瞬间烙印出独一无二的 URL 别名(slug:iphone-15)。
In an e-commerce database, a Product must hold a `ForeignKey` pointing to a Category. Upon saving via the admin interface, the product firmly anchors itself to the "Smart Phones" trunk using a virtual data cable (`category_id = 1`), while the `prepopulated_fields` directive simultaneously etches a unique URL-friendly alias (`slug`) derived from the product's name.
2. 安装 Pillow 并提供媒体文件
需要安装 Pillow 库来处理图片。Pillow 是 Python 中图像处理的标准库,支持多种图像格式,功能强大。Django 的
ImageField字段需要依赖 Pillow。在 shell 提示符下运行以下命令来安装 Pillow:
python -m pip install Pillow==11.0.0- 编辑
myshop/settings.py项目文件最后并添加以下几行(后面整体的配置):
# 媒体文件
MEDIA_URL = 'media/'
MEDIA_ROOT = BASE_DIR / 'media'MEDIA_URL<baseURL>-MEDIA_URL是访问用户上传文件的基础 URL 路径。MEDIA_ROOT<localPath>-MEDIA_ROOT是上传文件在服务器上的实际存储目录。为了保证可移植性,路径和 URL 都会根据项目路径或媒体 URL 动态拼接。为了让开发服务器能正确提供上传的媒体文件,编辑项目主 URL 配置文件
myshop/urls.py,添加下面加粗部分的代码:
"""
项目主URL配置
将不同URL路径映射到对应的视图函数
"""
from django.conf import settings # 导入设置
from django.conf.urls.static import static # 导入静态文件处理模块
from django.contrib import admin
from django.urls import path
urlpatterns = [
# 管理后台URL
path('admin/', admin.site.urls),
]
# 静态文件处理
if settings.DEBUG:
urlpatterns += static(
settings.MEDIA_URL, document_root=settings.MEDIA_ROOT
)- 注意,只有在开发阶段才能这样处理静态文件。在生产环境中,不能用 Django 来提供静态文件,因为 Django 开发服务器并不适合高效地处理静态资源。
3. 编辑项目配置文件
- 激活应用,在 myshop/settings.py 配置文件的
INSTALLED_APPS列表中添加:
# 应用列表
INSTALLED_APPS = [
'django.contrib.admin', # 管理后台
'django.contrib.auth', # 认证系统
'django.contrib.contenttypes', # 内容类型框架
'django.contrib.sessions', # 会话框架
'django.contrib.messages', # 消息框架
'django.contrib.staticfiles', # 静态文件管理
'shop.apps.ShopConfig', # 添加自定义shop应用
]4. 应用数据库迁移
# 创建迁移文件,在shop/migrations/目录下:
$ python manage.py makemigrations shop
Migrations for 'blog':
shop/migrations/0001_initial.py
+ Create model Category
+ Create model Product
# 执行应用迁移
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, shop, contenttypes, sessions
Running migrations:
Applying shop.0001_initial... OK
# 查看迁移状态
python manage.py showmigrations5. 将模型添加到管理后台
我们自定义的应用现在还没有出现在管理后台的首页上。要让它显示出来,需要打开 shop/admin.py 文件,编辑代码如下:
from django.contrib import admin
# 导入商品类别和商品模型
from .models import Category, Product
# 注册商品类别模型到Admin 后台
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug'] # 列表显示字段
prepopulated_fields = {'slug': ('name',)} # 预填充字段
# 注册商品模型到Admin 后台
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
# 列表显示字段
list_display = [
'name',
'slug',
'price',
'available',
'created',
'updated',
]
list_filter = ['available', 'created', 'updated'] # 过滤字段
list_editable = ['price', 'available'] # 可编辑字段
prepopulated_fields = {'slug': ('name',)} # 预填充字段
# 可选:自定义 Admin 站点标题
admin.site.site_header = '商品管理系统' # 登录页面和后台顶部标题
admin.site.site_title = '商品管理后台' # 浏览器标签页标题
admin.site.index_title = '欢迎使用商品管理系统' # 首页标题- 编辑 shop/apps.py 应用的主配置文件,添加最后一行代码。
from django.apps import AppConfig
# 商品应用配置类
class ShopConfig(AppConfig):
# 默认主键字段类型
default_auto_field = 'django.db.models.BigAutoField'
name = 'shop' # 应用名称
verbose_name = '商品管理' # 在Admin中显示的应用名称启动服务:
$ python manage.py runserver
或
$ python manage.py runserver 0.0.0.0:8000现在,打开一个Web浏览器,访问地址: http://127.0.0.1:8000/admin/
- 后台首页:

- 展示商品类别数据信息

- 展示商品信息

- 展示指定的商品信息(可修改,可删除):

四. 在shop应用中开发商品展示
1. 网上商品展示应用文件结构
├── shop # shop/目录为账户应用程序目录名称(Python包)
│ ├── __init__.py # 空文件,代表Python包的标志文件
│ ├── admin.py # 用于在 Django 管理后台中注册模型(可选)
│ ├── apps.py # 当前应用的主要配置
│ ├── migrations # 此目录将包含应用程序的数据库迁移文件(Python包)
│ ├── models.py # 应用程序的数据模型,每个应用程序的必须文件
│ ├── tests.py # 应用的测试文件
│ ├── urls.py # 自定义当前应用URL路由配置
│ ├── views.py # 应用程序的逻辑写在这里,HTTP请求、处理并返回响应。
│ ├─── static/ # Web前端静态资源目录
│ │ ├── css/ # CSS页面样式目录
│ │ │ ├── base.css # 公共css样式
│ │ │ └── pdf.css #
│ │ └── img/ # 图片素材目录
│ │ └── no_image.png # 无图效果图
│ └─── templates/ # 模版目录
│ └── shop/ # 当前应用模板目录
│ ├── product/ # 公共css样式
│ │ ├── list.html # 商品列表展示模板
│ │ └── detail.html # 商品详情页模板
│ └── base.html # 公共父模板其中web前端静态资源素材:static.zip
下载上面素材后解压到 shop应用目录下。
2. 编写shop应用中的视图文件
- 编辑 shop/views.py 视图文件,在里面定义商品列表展示视图和商品详情视图函数。
from django.shortcuts import get_object_or_404, render
from .models import Category, Product # 导入商品类别和商品模型
# 商品列表视图,支持按类别过滤
def product_list(request, category_slug=None):
category = None # 商品类别
categories = Category.objects.all() # 所有商品类别
products = Product.objects.filter(available=True) # 可用商品
# 如果提供了类别别名,则过滤该类别的商品
if category_slug:
# 获取该类别
category = get_object_or_404(Category, slug=category_slug)
# 过滤该类别的商品
products = products.filter(category=category)
# 渲染模板
return render(
request,
'shop/product/list.html',
{
'category': category, # 商品类别
'categories': categories, # 所有商品类别
'products': products, # 商品列表
},
)
# 商品详情视图
def product_detail(request, id, slug):
# 获取指定ID和别名的商品
product = get_object_or_404(
Product, id=id, slug=slug, available=True
)
# 渲染模板
return render(
request,
'shop/product/detail.html',
{'product': product},
)3. shop应用下的路由文件
- 在应用程序
shop目录中的urls.py路由文件:
from django.urls import path
from . import views # 导入视图
app_name = 'shop' # 应用命名空间
# URL 模式列表
urlpatterns = [
path('', views.product_list, name='product_list'), # 商品列表
# 按类别显示商品列表
path(
'<slug:category_slug>/',
views.product_list,
name='product_list_by_category',
),
# 商品详情
path(
'<int:id>/<slug:slug>/',
views.product_detail,
name='product_detail',
),
]- 主URL路由配置: myshop/urls.py
"""
项目主URL配置
将不同URL路径映射到对应的视图函数
"""
from django.conf import settings # 导入设置
from django.conf.urls.static import static # 导入静态文件处理模块
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
# 管理后台URL
path('admin/', admin.site.urls),
# shop商品管理应用URL
path('', include('shop.urls', namespace='shop')),
]
if settings.DEBUG:
urlpatterns += static(
settings.MEDIA_URL, document_root=settings.MEDIA_ROOT
)4. 应用中的模板文件
- 公共父模板文件:shop/templates/shop/base.html
<!-- shop/templates/shop/base.html -->
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>{% block title %}百宝袋商城{% endblock %}</title>
<link href="{% static "css/base.css" %}" rel="stylesheet">
</head>
<body>
<!-- 导航栏 start -->
<div id="header">
<a href="/" class="logo">百宝袋商城</a>
</div>
<!-- 导航栏 end -->
<!-- 购物车 start -->
<div id="subheader">
<div class="cart">
你的购物车是空的。
</div>
</div>
<!-- 购物车 end -->
<!-- 页面内容 start -->
<div id="content">
{% block content %}
{% endblock %}
</div>
<!-- 页面内容 end -->
</body>
</html>- 浏览商品信息模板文件:shop/templates/shop/product/list.html
{% extends "shop/base.html" %}
{% load static %}
{% block title %}
{% if category %}{{ category.name }}{% else %}商品列表{% endif %}
{% endblock %}
{% block content %}
<div id="sidebar">
<h3>商品分类</h3>
<ul>
<li {% if not category %}class="selected"{% endif %}>
<a href="{% url "shop:product_list" %}">所有类型</a>
</li>
{% for c in categories %}
<li {% if category.slug == c.slug %}class="selected"{% endif %}>
<a href="{{ c.get_absolute_url }}">{{ c.name }}</a>
</li>
{% endfor %}
</ul>
</div>
<div id="main" class="product-list">
<h1>{% if category %}{{ category.name }}{% else %}商品列表{% endif %}</h1>
{% for product in products %}
<div class="item">
<a href="{{ product.get_absolute_url }}">
<img src="{% if product.image %}{{ product.image.url }}{% else %}{% static "img/no_image.png" %}{% endif %}">
</a>
<a href="{{ product.get_absolute_url }}">{{ product.name }}</a>
<br><br>
¥ {{ product.price }}
</div>
{% endfor %}
</div>
{% endblock %}- 编辑商品详情模板信息模板文件:shop/templates/shop/product/detail.html
{% extends "shop/base.html" %}
{% load static %}
{% block title %}
{{ product.name }}
{% endblock %}
{% block content %}
<div class="product-detail">
<img src="{% if product.image %}{{ product.image.url }}{% else %}
{% static "img/no_image.png" %}{% endif %}">
<h1>{{ product.name }}</h1>
<h3>
所属类别:
<a href="{{ product.category.get_absolute_url }}">
{{ product.category }}
</a>
</h3>
<p class="price"> ¥ {{ product.price }} 元</p>
{{ product.description|linebreaks }}
</div>
{% endblock %}预览效果:
所有商品展示

- 只展示"手机"类别商品

- 商品详情页:
上面完成的项目代码:myshop.zip
五、购物应用的开发
在商品展示完成后,下一步是创建购物车功能,让用户可以选择想要购买的产品。购物车允许用户选择商品并设置购买数量,然后在浏览过程中临时保存这些信息。购物车数据需要保存在 session 中,这样用户在访问期间就不会丢失购物车里的商品。
我们使用 Django 的会话框架来持久化购物车数据。购物车会一直保存在 session 中,直到用户完成购物或结算为止。
1. 将购物车存储在会话中
我们需要设计一个简单的数据结构,用来把购物车商品存储在 session 中,并且这个结构可以序列化为 JSON。购物车中每件商品需要包含以下数据:
Product实例的 ID为产品选择的数量
产品单价
编辑
settings.py项目文件,并在末尾添加以下设置:
CART_SESSION_ID = 'cart'专属会话保险金库
Session Storage Isolation (Cookie `sessionid` -> DB Box)
🌍 用户浏览器
Cookie
🔑 abcdef123
🏢 Django 数据库 (django_session)
xyz890...
🔒
abcdef123
🔒
qwe456...
🔒
HTTP协议本身是无状态的(它记不住你是谁)。Django 的 Session(会话)机制 完美解决了这个问题:浏览器在 Cookie 中秘密贴身保管一把形似 abcdef123 的哈希加密钥匙;当用户的 HTTP 请求带着这把钥匙飞向服务器时,Django 会立刻拿着它去庞大的 django_session 数据库壁橱里“精确开锁”,抽出只属于你的那个抽屉!在这里你可以往自己的 'cart' 里塞商品,绝对不会买串到隔壁老王的抽屉里。
The HTTP protocol is inherently stateless. Django's Session mechanism elegantly solves this: the browser secretly holds a cryptographic hash key (like `abcdef123`) in a `Cookie`. When the user's HTTP request flies to the server carrying this key, Django uses it to precisely unlock your personal drawer inside the massive `django_session` database vault. Here, you can safely store items in your own `'cart'` without any risk of bleeding into another user's shopping box.
这个 key 用于在用户 session 中存储购物车数据。因为 Django 的 session 是按每个访客独立管理的,所以所有 session 可以共用同一个 key 名称。
创建一个新的应用来管理购物车。在项目目录中运行以下命令:
python manage.py startapp cart- 然后编辑
settings.py,把新应用添加到INSTALLED_APPS中(加粗行为新增内容):
INSTALLED_APPS = [
# ...
'cart.apps.CartConfig',
'shop.apps.ShopConfig',
]- 在cart应用程序目录下创建一个新文件
cart.py。代码如下:
from decimal import Decimal
from django.conf import settings # 导入Django设置
from shop.models import Product # 导入商品模型
# 购物车信息处理类
class Cart:
# 初始化购物车(当执行Cart(request)自动调用此方法)
def __init__(self, request):
"""
初始化购物车
"""
self.session = request.session # 获取会话对象
# 获取购物车数据 settings.CART_SESSION_ID为购物车会话ID
cart = self.session.get(settings.CART_SESSION_ID)
if not cart: # 如果购物车为空
# 创建一个空字典作为购物车数据
cart = self.session[settings.CART_SESSION_ID] = {}
self.cart = cart # 购物车数据存储在self.cart中
# 迭代购物车中的商品(当对Cart对象执行for循环时自动调用此方法)
def __iter__(self):
"""
迭代购物车中的商品,并从数据库中获取产品。
"""
product_ids = self.cart.keys() # 获取购物车中所有商品ID
# 根据商品ID从数据库中获取对应的商品对象
products = Product.objects.filter(id__in=product_ids)
cart = self.cart.copy() # 创建一个副本
for product in products: # 迭代购物车中的商品
# 将每个商品信息存储在购物车中
cart[str(product.id)]['product'] = product
for item in cart.values(): # 迭代购物车中的商品信息
# 将价格转换为Decimal类型
item['price'] = Decimal(item['price'])
# 计算总价
item['total_price'] = item['price'] * item['quantity']
yield item # 生成商品信息
# 计算购物车中商品的总数量(当对Cart对象执行len()函数时自动调用此方法)
def __len__(self):
"""
清点购物车中的所有商品。
"""
return sum(item['quantity'] for item in self.cart.values())
# 添加或更新购物车中的商品(参数: 商品对象;商品数量;是否覆盖数量)
def add(self, product, quantity=1, override_quantity=False):
"""
将产品添加到购物车或更新其数量。
"""
product_id = str(product.id) # 获取商品ID并转换为字符串
# 如果商品不在购物车中,则添加该商品
if product_id not in self.cart:
# 创建一个字典,用于存储商品信息
self.cart[product_id] = {
'quantity': 0, # 数量
'price': str(product.price), # 价格
}
if override_quantity: # 如果覆盖数量
self.cart[product_id]['quantity'] = quantity
else:
self.cart[product_id]['quantity'] += quantity
self.save() # 保存购物车状态
def save(self):
# 将会话标记为“已修改”,以确保其被保存
self.session.modified = True
def remove(self, product):
"""
从购物车中删除产品。
"""
product_id = str(product.id)
# 如果产品存在于购物车中,则删除
if product_id in self.cart:
del self.cart[product_id]
self.save()
def clear(self):
# 从会话中删除购物车
del self.session[settings.CART_SESSION_ID]
self.save()
# 计算购物车总价
def get_total_price(self):
return sum(
Decimal(item['price']) * item['quantity']
for item in self.cart.values()
)这个
Cart类用于管理购物车。初始化时需要传入一个request对象。我们把当前 session 存储到self.session = request.session中,这样Cart类的其他方法就能访问到它了。首先,通过
self.session.get(settings.CART_SESSION_ID)尝试从当前 session 中获取购物车数据。如果 session 中没有购物车,就往 session 里设置一个空字典来创建一个空购物车。购物车的
cart字典用商品 ID 作为 key,每个 key 对应一个包含数量和价格的字典。这样做可以保证同一件商品不会被重复添加,同时也方便后续查找购物车商品。创建 add() 方法,用于将商品添加到购物车或更新数量。
product:要添加或更新的商品实例。quantity:可选整数参数,表示商品数量,默认值为1。1。1。 01。override_quantity:布尔值,True表示用给定数量覆盖原有数量,False表示在原有数量基础上累加。
remove() 方法用于从购物车中移除指定商品
为了遍历购物车中的商品并获取对应的
Product实例,我们定义了__iter__()方法。这个方法会从数据库中查询购物车里的商品信息,并把它们附加到购物车数据中。当对
Cart对象使用len()函数时,Python 会自动调用__len__()方法,返回购物车中所有商品数量的总和。get_total_price() 方法用于计算购物车中商品的总价。
clear() 方法用于清空购物车 session 数据。
2. 将商品添加到购物车的表单
- 要将商品添加到购物车,需要创建一个允许用户选择数量的表单。在 cart 应用目录中创建
forms.py文件,添加以下代码:
from django import forms
# 商品数量选择范围
PRODUCT_QUANTITY_CHOICES = [(i, str(i)) for i in range(1, 21)]
# 购物车表单
class CartAddProductForm(forms.Form):
# 选择商品数量
quantity = forms.TypedChoiceField(
choices=PRODUCT_QUANTITY_CHOICES, # 选择范围1-20
coerce=int, # 转换为整数
label='数量', # 标签名称
initial=1 # 默认值1
)
# 是否覆盖数量
override = forms.BooleanField(
required=False, # 不必填
initial=False, # 默认值为False
widget=forms.HiddenInput # 隐藏字段
)加码与重置投料口
Cart Quantity : Accumulate (override=False) vs Replace (override=True)
当前模式: override_quantity
False (只做累加)
添加数量 (Quantity) : 3 件
📦
📦
📦
📦
📦
🛒 购物车持有量: 5 件
用户在商品详情页点击“加入购物车”时,传参为 override=False,这意味着如果原先有了5件,再点一次1件就是在顶上直接投料累加(变成6件)。而当用户身处购物车总览表格里,强制修改数量输入框为3件并点击更新时,传参就是 override=True。此时逻辑推土机启动,它会无情地将原本一堆杂乱的五件存货推平粉碎,然后干净利落地重置为刚刚好 3 件!
When a user clicks 'Add to Cart' from the product detail page, the `override=False` parameter instructs the hopper to simply drop and add the new quantity onto the existing stack (e.g., 5 + 1 = 6 items). Conversely, navigating the cart summary page to forcibly edit the item amount and update transmits `override=True`. Instantly, a digital bulldozer wipes out the previous pile completely and precisely replaces it with the exact newly requested inventory count!
这个表单用于将商品添加到购物车。CartAddProductForm 类包含以下两个字段:
quantity:让用户选择 1 到 20 之间的数量。使用TypedChoiceField字段并配合coerce=int将输入转换为整数。override:用于指定是把新数量累加到购物车中该商品的现有数量上(False),还是用新数量直接覆盖现有数量(True)。由于不需要向用户展示这个字段,所以使用了HiddenInput隐藏控件。
3. 创建购物车视图
现在你有了一个
Cart类来管理购物车,接下来需要创建购物车相关的视图。我们需要创建以下视图:用于添加或更新购物车中商品数量的视图,可以处理当前数量和新增数量。
从购物车中移除商品的视图
用于显示购物车商品和总计的视图
创建一个将商品添加到购物车的视图。编辑
cart/views.py文件,添加以下代码:
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.http import require_POST
from shop.models import Product
from .cart import Cart # 导入购物车类
from .forms import CartAddProductForm # 导入购物车添加商品表单
# 添加商品到购物车视图
@require_POST
def cart_add(request, product_id):
cart = Cart(request) # 获取购物车对象
# 获取指定ID的商品
product = get_object_or_404(Product, id=product_id)
# 处理表单数据
form = CartAddProductForm(request.POST)
if form.is_valid(): # 如果表单数据有效
cd = form.cleaned_data # 获取清理后的数据
# 将商品添加到购物车
cart.add(
product=product, # 商品对象
quantity=cd['quantity'], # 数量
override_quantity=cd['override'], # 覆盖标志
)
return redirect('cart:cart_detail') # 重定向到购物车详情页
# 删除商品视图
@require_POST
def cart_remove(request, product_id):
cart = Cart(request) # 获取购物车对象
# 获取指定ID的商品
product = get_object_or_404(Product, id=product_id)
cart.remove(product) # 从购物车中移除该商品
return redirect('cart:cart_detail') # 重定向到购物车详情页
# 购物车详情视图
def cart_detail(request):
cart = Cart(request) # 获取购物车对象
# 为购物车中的每个商品创建更新数量的表单
for item in cart:
# 创建表单实例,初始值为当前数量,覆盖标志为True
item['update_quantity_form'] = CartAddProductForm(
initial={'quantity': item['quantity'], 'override': True}
)
# 渲染购物车详情模板
return render(request, 'cart/detail.html', {'cart': cart})4. 配置URL路由信息
现在给这些视图添加 URL 路由。在 cart 应用目录中创建 urls.py 文件,添加以下 URL 配置:
from django.urls import path
from . import views # 导入视图模块
app_name = 'cart' # 命名空间
urlpatterns = [
# 购物车详情页
path('', views.cart_detail, name='cart_detail'),
# 添加商品到购物车
path('add/<int:product_id>/', views.cart_add, name='cart_add'),
# 删除购物车中的商品
path(
'remove/<int:product_id>/',
views.cart_remove,
name='cart_remove',
),
]编辑项目主 URL 文件 myshop/urls.py,添加下面加粗的那行来引入购物车 URL:
... ...
urlpatterns = [
path('admin/', admin.site.urls), #
path('cart/', include('cart.urls', namespace='cart')), # 添加购物车
path('', include('shop.urls', namespace='shop')), # 添加商品
]
... ...注意要把这个 URL 放在 shop.urls 前面,因为 shop.urls 的匹配范围更宽泛。
5. 构建用于显示购物车的模板
cart_add 和 cart_remove 视图不需要渲染模板,但你需要为 cart_detail 视图创建一个模板来展示购物车商品和合计金额。
在 cart 应用目录下创建以下文件结构:
templates/
|-- cart/
|-- detail.html编辑cart/detail.html模板并添加以下代码:
{% extends "shop/base.html" %}
{% load static %}
{% block title %}
我的购物车
{% endblock %}
{% block content %}
<h1>我的购物车</h1>
<table class="cart">
<thead>
<tr>
<th>商品图片</th>
<th>商品名称</th>
<th>数量</th>
<th>操作</th>
<th>单价</th>
<th>价格</th>
</tr>
</thead>
<tbody>
{% for item in cart %}
{% with product=item.product %}
<tr>
<td>
<a href="{{ product.get_absolute_url }}">
<img src="{% if product.image %}{{ product.image.url }}
{% else %}{% static "img/no_image.png" %}{% endif %}">
</a>
</td>
<td>{{ product.name }}</td>
<td>
<form action="{% url "cart:cart_add" product.id %}" method="post">
{{ item.update_quantity_form.quantity }}
{{ item.update_quantity_form.override }}
<input type="submit" value="修改">
{% csrf_token %}
</form>
</td>
<td>
<form action="{% url "cart:cart_remove" product.id %}" method="post">
<input type="submit" value="删除">
{% csrf_token %}
</form>
</td>
<td class="num">¥ {{ item.price }} 元</td>
<td class="num">¥ {{ item.total_price }} 元</td>
</tr>
{% endwith %}
{% endfor %}
<tr class="total">
<td>合计:</td>
<td colspan="4"></td>
<td class="num">¥ {{ cart.get_total_price }} 元</td>
</tr>
</tbody>
</table>
<p class="text-right">
<a href="{% url "shop:product_list" %}" class="button light">继续购物</a>
<a href="#" class="button">结账</a>
</p>
{% endblock %}确保模板标签不要跨越多行。 这个模板用于展示购物车内容。页面包含一个表格,列出了购物车中所有商品。用户可以通过表单修改商品数量(提交到 cart_add 视图),也可以点击”删除”按钮移除商品(提交到 cart_remove 视图)。
6. 将商品添加到购物车
现在需要在商品详情页添加”添加到购物车”按钮。编辑 shop 应用的 views.py 文件,在 product_detail 视图中引入 CartAddProductForm:
from cart.forms import CartAddProductForm
# ...
# 商品详情视图
def product_detail(request, id, slug):
# 获取指定ID和别名的商品
product = get_object_or_404(
Product, id=id, slug=slug, available=True
)
# 购物车添加商品表单
cart_product_form = CartAddProductForm()
# 渲染模板
return render(
request,
'shop/product/detail.html',
{'product': product, 'cart_product_form': cart_product_form},
)编辑 shop/product/detail.html 模板,在商品价格下方添加以下表单(新增内容已加粗):
...
<p class="price"> ¥ {{ product.price }} 元</p>
<form action="{% url "cart:cart_add" product.id %}" method="post">
{{ cart_product_form }}
{% csrf_token %}
<br />
<input type="submit" value="放进购物">
</form>
{{ product.description|linebreaks }}
...使用以下命令运行开发服务器:
python manage.py runserver在浏览器中打开 http://127.0.0.1:8000/,进入商品详情页。页面上会出现一个选择数量的表单,效果如下: 
选择数量后点击”添加到购物车”按钮,表单会通过 POST 方式提交到 cart_add 视图。视图会将商品连同价格和所选数量一起存入 session 中的购物车,然后重定向到购物车详情页,效果如下:

正在更新购物车中的商品数量
当用户查看购物车时,可能需要在下单前修改商品数量。下面我们来实现在购物车详情页修改数量的功能。
编辑 cart 应用的 views.py 文件,在 cart_detail 视图中添加下面加粗的代码:
# 购物车详情视图
def cart_detail(request):
cart = Cart(request) # 获取购物车对象
# 为购物车中的每个商品创建更新数量的表单
for item in cart:
# 创建表单实例,初始值为当前数量,覆盖标志为True
item['update_quantity_form'] = CartAddProductForm(
initial={'quantity': item['quantity'], 'override': True}
)
# 渲染购物车详情模板
return render(request, 'cart/detail.html', {'cart': cart})这里为购物车中的每件商品创建了一个 CartAddProductForm 实例,用来修改数量。表单的初始值为当前商品数量,override 字段设为 True,这样提交表单到 cart_add 视图时,就会用新数量替换原来的数量。 接下来编辑 cart 应用的 cart/detail.html 模板,找到这行代码:
<td>{{ item.quantity }}</td>将上一行代码替换为以下代码:
<td>
<form action="{% url "cart:cart_add" product.id %}" method="post">
{{ item.update_quantity_form.quantity }}
{{ item.update_quantity_form.override }}
<input type="submit" value="修改">
{% csrf_token %}
</form>
</td>使用以下命令运行开发服务器:
python manage.py runserver在浏览器中打开 http://127.0.0.1:8000/cart/。 你会看到每件商品旁边都有一个修改数量的表单,如下所示:
修改商品数量后点击”修改”按钮即可更新。点击”删除”按钮可以从购物车中移除商品。
7. 为当前购物车创建上下文处理器
你可能已经注意到,即使购物车里有商品,网站头部仍然显示”购物车为空”。我们应该在头部显示购物车的商品总数和总价。由于这个信息需要在所有页面上展示,所以我们需要创建一个上下文处理器,把当前购物车注入到请求上下文中,这样不管哪个视图处理请求,模板都能拿到购物车数据。
① 上下文处理器
上下文处理器是一个 Python 函数,它接受一个 request 对象作为参数,返回一个字典,这个字典会被添加到请求上下文中。当你需要让某些数据在所有模板中都能用到时,上下文处理器就很有用了。
当你用 startproject 命令创建新项目时,Django 会在 TEMPLATES 设置的 context_processors 选项中默认配置以下上下文处理器:
django.template.context_processors.debug:在上下文中设置debug布尔值和sql_queries变量(当前请求中执行的 SQL 查询列表)。django.template.context_processors.request:将request对象注入到模板上下文中。django.contrib.auth.context_processors.auth:将当前登录的user对象注入到上下文中。django.contrib.messages.context_processors.messages:将消息框架生成的messages注入到上下文中。
Django 还内置了 django.template.context_processors.csrf 来防范跨站请求伪造(CSRF)攻击。这个处理器始终启用,不需要手动配置。 CSRF ) 攻击。这个
② 在请求上下文中设置购物车
创建一个上下文处理器,把当前购物车注入到请求上下文中。这样你就可以在任何模板中访问购物车了。
在 cart 应用目录下创建一个 context_processors.py 文件。上下文处理器放在代码的任何位置都可以,但放在应用目录下更便于管理。添加以下代码:
from .cart import Cart
# 购物车上下文处理器
def cart(request):
# 创建购物车对象
return {'cart': Cart(request)}在上下文处理器中,用 request 对象实例化购物车,并把它作为 cart 变量提供给模板。 编辑 settings.py,在 TEMPLATES 设置的 context_processors 选项中添加 cart.context_processors.cart(加粗行为新增内容):
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',
'cart.context_processors.cart', # 添加购物车上下文处理器
],
},
},
]全域上下文广播塔
Context Processor vs Manual Render Passing
cart.context_processors.cart
📦 {'cart': cart}
views.py
base.html (全站父模板)
list.html
detail.html
在没有上下文处理器时(传统传参),就像是一个专门的视图搬运工(views.py),他必须在每次响应时,手动把装有购物车 {'cart':cart} 的箱子运到某个指定的具体模板(如 list.html)。但他根本管不到全局底层的 base.html,这导致顶部导航栏立刻抛出获取不到购物车的报错!而配置了上下文处理器后,就在云端建立了一座全频道广播塔,无论任何模板启动渲染,它都会自动利用绿色电波全局空投资源盒 Cart,彻底解放了视图函数!
Without a Context Processor (manual execution), the `views.py` acts as a delivery truck that must manually carry the `{'cart':cart}` payload to specific child templates like `list.html`. It completely ignores the global `base.html`, instantly causing cart retrieval errors in the header navigation! Activating the Context Processor constructs an omni-directional broadcasting tower in the cloud. Now, whenever ANY template renders, the tower automatically parachutes the `Cart` variable down via radio waves, completely decoupling and freeing the view functions!
每次使用 Django 的 RequestContext 渲染模板时,cart 上下文处理器都会执行,cart 变量会被注入到模板上下文中。更多关于 RequestContext 的内容可以参考 Django 官方文档。 需要注意的是,上下文处理器会在每个请求中都执行。如果某个功能不是所有模板都需要的,特别是涉及数据库查询的场景,建议使用自定义模板标签代替上下文处理器。
接下来编辑 shop 应用的 shop/base.html 模板,找到以下代码:
<div class="cart">
你的购物车是空的。
</div>将前面的代码行替换为以下代码:
<div class="cart">
{% with total_items=cart|length %}
{% if total_items > 0 %}
购物车:
<a href="{% url "cart:cart_detail" %}">
{{ total_items }} 个{{ total_items|pluralize }},
¥ {{ cart.get_total_price }}
</a>
{% elif not order %}
你的购物车是空的。
{% endif %}
{% endwith %}
</div>使用以下命令重启开发服务器:
python manage.py runserver在浏览器中打开 http://127.0.0.1:8000/,往购物车里添加一些商品。 现在你可以在网站头部看到购物车中的商品总数和总价了,效果如下:
恭喜!到目前为止已完成购物车功能的开发。这是网店项目的一个重要里程碑。接下来,我们将创建客户订单登记功能,这是电商平台的另一个核心模块。
- 截止当前案例代码:myshop.zip
六、Django框架中的session(扩展)
- Session 工作原理图解
┌─────────────┐ 1. 第一次访问 ┌─────────────┐
│ 用户浏览器 │ ─────────────────→ │ Django网站 │
│ │ │ │
│ Cookie中存 │ ←───────────────── │ 生成Session │
│ 放SessionID │ 2. 返回SessionID │ ID │
└─────────────┘ └─────────────┘
│ │
│ 3. 后续访问带着SessionID │
└────────────────────────────────────┘
│
4. Django根据ID查找Session数据
│
┌─────────────────┐
│ Session存储层 │
│ 1. 数据库 │
│ 2. 缓存 │
│ 3. 文件 │
│ 4. Cookie │
└─────────────────┘2. session的请求过程:
整个过程可以概括为以下几个步骤:
1. 中间件处理请求 2. 从请求中获取Session 3. 在视图处理期间使用Session 4. 保存Session 5. 发送响应
下面我们详细说明每个步骤。
第一步:中间件处理请求
Django的SessionMiddleware(位于django.contrib.sessions.middleware)在每个请求开始时运行。该中间件负责从请求中提取Session键(通常来自名为sessionid的Cookie),然后加载相应的Session数据,并将其作为request.session属性附加到请求对象上。
具体过程:
当请求到达Django时,SessionMiddleware的process_request方法被调用。
该方法使用SessionStore(根据settings中的SESSION_ENGINE设置)从请求的Cookie中读取sessionid。
然后,它尝试加载与该sessionid对应的Session数据。如果Session不存在或已过期,则会创建一个新的空Session。
将SessionStore实例赋值给request.session。
第二步:从请求中获取Session
在视图或任何其他处理请求的代码中,可以通过request.session访问Session。request.session是一个类似字典的对象,代表当前会话。
第三步:在视图处理期间使用Session
在视图函数或类视图中,你可以对request.session进行读写操作,就像操作普通字典一样。例如,你可以存储用户ID、购物车项目等。
第四步:保存Session
在视图处理完成后,SessionMiddleware的process_response方法被调用。该方法负责决定是否将Session保存到数据库,并在响应中设置Cookie。
保存Session的条件(满足以下条件之一):
Session被修改(即request.session.modified为True)。
设置SESSION_SAVE_EVERY_REQUEST为True(每次请求都保存)。
注意:如果Session没有被修改且SESSION_SAVE_EVERY_REQUEST为False,则不会保存Session,也不会发送新的Cookie。
保存过程:
将Session数据序列化并保存到数据库中(对于数据库后端)。
更新Cookie中的sessionid。如果使用数据库后端,sessionid不会改变,除非调用了request.session.cycle_key()。
将Cookie添加到响应中。
第五步:发送响应
响应被发送回客户端,其中包含一个Set-Cookie头部,用于存储sessionid。浏览器将在后续请求中携带这个Cookie,以便服务器能够识别同一用户的会话。
此外,如果Session被标记为已过期(例如调用了request.session.flush()),那么中间件会删除数据库中的Session,并指示浏览器删除Cookie。
3. 请求流程:

会话中间件安检通道
Session Lifecycle: `request.session.modified = True` -> DB Save
SessionMiddleware
Request 进来
🎫 request.session
View 执行区 (业务代码)
📥 落盘存储 (DB Save)
Response 出去 🎫 request.session
这揭示了 SessionMiddleware 神奇的生命周期:当请求进入时(入关),中间件派发了一张内存令牌 request.session。在视图处理阶段(候机大厅),如果你的代码修改了购物车数据,这张令牌上的 modified 红色指示灯就会亮起。当 Response 准备返回给用户(出关安检)时,中间件会对令牌进行最后一次扫描检查:如果红灯亮着,安检机就会触发一旁的机械臂,将刚刚内存里的修改狠狠地砸进永久数据库(DB Save),然后才放行;灯没亮则直接放开闸门通过,绝不浪费一次数据库写入性能。
This reveals the magic lifecycle of `SessionMiddleware`: Upon request entry (customs inward), the middleware issues an in-memory token `request.session`. During View processing (the departure lounge), modifying cart data instantly triggers the token's `modified` red warning beacon. Before the Response departs back to the user (customs outward), the middleware scans the token one final time: if the beacon is flashing, a mechanical stamper is mobilized to forcefully commit those memory changes into the permanent Database (Save!). If unlit, it simply waves the response through, elegantly conserving database write resources.
七、客户订单的处理
1. 登记客户订单
当顾客完成购物车添加后点击结账,需要把订单保存到数据库中。订单中要包含顾客信息和所购买的商品信息。
使用以下命令创建一个管理客户订单的新应用:
python manage.py startapp orders- 编辑
myshop/settings.py,将新应用添加到INSTALLED_APPS中:
INSTALLED_APPS = [
# ...
'cart.apps.CartConfig', # 购物车处理应用
'orders.apps.OrdersConfig',# 订单处理应用
'shop.apps.ShopConfig', # 商品信息处理应用
]- 别忘了激活
orders应用。
2. 创建订单模型
我们需要两个模型:一个用来存储订单的基本信息,另一个用来存储订单中的商品明细(包括价格和数量)。编辑 orders 应用目录下的 models.py 文件,添加以下代码:
from django.db import models
# 订单模型
class Order(models.Model):
name = models.CharField('姓名', max_length=50)
email = models.EmailField('邮箱')
address = models.CharField('地址', max_length=250)
postal_code = models.CharField('邮政编码', max_length=20)
city = models.CharField('城市', max_length=100)
created = models.DateTimeField('创建时间', auto_now_add=True)
updated = models.DateTimeField('更新时间', auto_now=True)
paid = models.BooleanField('已支付', default=False)
class Meta:
ordering = ['-created'] # 默认排序按创建时间降序
indexes = [
models.Index(fields=['-created']), # 创建时间索引
]
verbose_name = '订单信息' # 在Admin中显示的单数形式名称
verbose_name_plural = '订单信息管理'# 在Admin中显示的复数形式名称
def __str__(self):
return f'Order {self.id}'
#
def get_total_cost(self):
return sum(item.get_cost() for item in self.items.all())
# 订单项模型
class OrderItem(models.Model):
order = models.ForeignKey(
Order,
related_name='items',
on_delete=models.CASCADE,
verbose_name='订单'
)
product = models.ForeignKey(
'shop.Product',
related_name='order_items',
on_delete=models.CASCADE,
verbose_name='商品'
)
price = models.DecimalField(
'价格',
max_digits=10,
decimal_places=2
)
quantity = models.PositiveIntegerField('数量', default=1)
def __str__(self):
return str(self.id)
# 获取订单项价格
def get_cost(self):
return self.price * self.quantity- 执行以下命令为
orders应用创建初始迁移文件:
$ python manage.py makemigrations- 你会看到类似下面的输出:
Migrations for 'orders':
orders/migrations/0001_initial.py
- Create model Order
- Create model OrderItem- 运行以下命令以应用新的迁移:
$ python manage.py migrate
Applying orders.0001_initial... OK订单模型现已同步到数据库。
3. 在管理后台中加入订单模型
- 把订单模型添加到管理后台。编辑 orders 应用目录下的
admin.py文件,添加代码:
from django.contrib import admin
# 注册订单项模型到Admin后台
from .models import Order, OrderItem
# 内联显示订单项
class OrderItemInline(admin.TabularInline):
model = OrderItem # 关联的模型
raw_id_fields = ['product'] # 使用原始ID字段选择器
# 注册订单模型到Admin后台
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
# 订单列表显示字段
list_display = [
'id',
'name',
'email',
'address',
'postal_code',
'city',
'paid',
'created',
'updated',
]
list_filter = ['paid', 'created', 'updated'] # 过滤字段
inlines = [OrderItemInline] # 内联订单项- 编辑 orders 应用目录下的apps.py文件,并添加如下代码:
from django.apps import AppConfig
# 订单应用配置类
class OrdersConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'#默认主键字段类型
name = 'orders' # 应用名称
verbose_name = '订单管理' # 在Admin中显示的应用名称4. 创建客户订单
现在来使用订单模型,实现用户下单时把购物车中的商品保存为订单。创建新订单的流程如下:
向用户展示订单表单,让用户填写收货信息。
用提交的数据创建一个
Order实例,并为购物车中的每件商品创建一个OrderItem关联实例。清空购物车,并将用户重定向到成功页面。
首先,需要一个输入订单信息的表单。在
orders应用目录中创建forms.py文件,添加以下代码:
from django import forms
# 订单创建表单
from .models import Order
# 订单创建表单
class OrderCreateForm(forms.ModelForm):
class Meta: # 模型表单元数据
model = Order # 关联的模型
fields = [ # 显示的字段
'name',
'email',
'address',
'postal_code',
'city',
]- 这个表单用来创建新的
Order对象。接下来需要一个视图来处理表单提交并创建订单。编辑orders/views.py文件,添加以下代码:
from django.shortcuts import render
# 订单创建视图,处理订单创建请求
from cart.cart import Cart # 购物车类
from .forms import OrderCreateForm # 订单创建表单
from .models import OrderItem # 订单项模型
# 订单创建视图,处理订单创建请求
def order_create(request):
cart = Cart(request) # 获取购物车对象
if request.method == 'POST': # POST请求处理
# 创建表单实例
form = OrderCreateForm(request.POST)
if form.is_valid(): # 如果表单数据有效
order = form.save() # 创建订单
for item in cart: # 遍历购物车中的每一项
OrderItem.objects.create( # 创建订单项
order=order, # 关联订单
product=item['product'], # 订单项关联的商品
price=item['price'], # 订单项价格
quantity=item['quantity'],# 订单项数量
)
# 订单处理完成,清空购物车
cart.clear()
return render( # 返回订单创建成功页面
request, 'orders/order/created.html', {'order': order}
)
else:
form = OrderCreateForm() # GET请求,创建空表单
return render( # 返回订单创建页面
request,
'orders/order/create.html',
{'cart': cart, 'form': form},
)订单快照封装流水线
Order Checkout: Session `Cart` -> Database `Order` -> `cart.clear()`
临时:Session Cart
🛍️ iPhone 15 x 2
🛍️ iPad Pro x 1
⚙️ 数据快照压印机
永久:Database
Order ID: #904
OrderItem (iPhone * 2)
OrderItem (iPad * 1)
🌪️ cart.clear()
用户按下结账时,整个系统经历了一次“全息投影实体化”的过程。左边漂浮的虚拟购物车(保存在 Session 内存中)通过 order_create 逻辑和订单表单被提交至压印中心。压印机狠狠砸下,依照虚影连夜赶制出不可磨灭的坚固木箱(写入永久数据库生成的 Order 和关联的 OrderItem)。由于交易记录已经安全落盘,悬在我们头顶的工业吸尘器随即立刻启动 cart.clear(),将用完的全息投影虚影彻底吸走粉碎,以免客户下一单产生脏数据残留。
When the user presses Checkout, the system undergoes a process of "holographic materialization". The floating, ethereal shopping cart on the left (housed temporarily in Session memory) passes through the `order_create` logic and the submitted checkout form. The data-press machine slams down, forging indestructible physical wooden crates (creating the permanent `Order` and linked `OrderItem` records in the database) based perfectly on the hologram's blueprint. Because the transaction is now safely secured on disk, an overhead industrial vacuum instantly roaring to life triggers `cart.clear()`, totally destroying and sucking away the leftover holographic session data—vital to prevent stale data contamination for the next purchase cycle!
- 在
order_create视图中,首先通过cart = Cart(request)从 session 中获取当前购物车。然后根据请求方法做不同处理:GET 请求:实例化空的
OrderCreateForm表单,渲染orders/order/create.html模板。POST 请求:验证提交的数据。如果数据有效,通过
order = form.save()在数据库中创建新订单,然后遍历购物车中的商品,为每件商品创建一个OrderItem。最后清空购物车并渲染orders/order/created.html模板。
5. 配置订单的URL路由
- 在
orders应用目录下创建urls.py文件,添加以下代码:
from django.urls import path
from . import views # 导入视图
app_name = 'orders' # 命名空间
# 路由
urlpatterns = [
# 创建订单
path('create/', views.order_create, name='order_create'),
]以上是
order_create视图的 URL 配置。编辑项目主 URL 文件
myshop/urls.py,在shop.urls前面添加订单 URL:
urlpatterns = [
# 管理后台URL
path('admin/', admin.site.urls),
path('cart/', include('cart.urls', namespace='cart')), # 添加购物车
path('orders/', include('orders.urls', namespace='orders')), # 订单管理应用URL
# shop商品管理应用URL
path('', include('shop.urls', namespace='shop')),
]编辑 cart 应用的 cart/detail.html 模板,找到这行代码:
<a href="#" class="button">结账</a>将上面的链接改为指向 order_create 的 URL:
<a href="{% url "orders:order_create" %}" class="button">
结账
</a>6. 订单处理的模板
现在用户可以从购物车详情页跳转到订单页面了。
接下来创建订单相关的模板。在
orders应用目录中创建以下文件结构:
templates/
|-- orders/
|-- order/
|-- create.html
|-- created.html- 编辑
orders/order/create.html模板,添加以下代码:
{% extends "shop/base.html" %}
{% block title %}
订单处理
{% endblock %}
{% block content %}
<h1>订单处理</h1>
<div class="order-info">
<h3>我的订单</h3>
<ul>
{% for item in cart %}
<li>
{{ item.quantity }} x {{ item.product.name }}
<span>¥ {{ item.total_price }}元</span>
</li>
{% endfor %}
</ul>
<p>总计: ¥ {{ cart.get_total_price }}元 </p>
</div>
<form method="post" class="order-form">
{{ form.as_p }}
<p><input type="submit" value="提交订单"></p>
{% csrf_token %}
</form>
{% endblock %}这个模板展示了购物车商品、合计金额和下单表单。下面是订单创建成功后显示的模板。
编辑
orders/order/created.html模板,添加以下代码:
{% extends "shop/base.html" %}
{% block title %}
订单处理
{% endblock %}
{% block content %}
<h1>订单处理</h1>
<p>你的订单已成功处理。订单号是:<strong>{{ order.id }}</strong>。</p>
{% endblock %}- 启动开发服务器。在浏览器中打开
http://127.0.0.1:8000/,添加几件商品到购物车,进入购物车展示页:

- 上面点击【结账】后进入下单结账表单页,具体如下:

订单创建页面,包含结算表单和订单详情
填写完表单后,点击”提交订单”按钮。订单创建成功后,会看到如下的成功页面:

订单创建成功,显示订单号,购物车已清空。
后台管理订单:

可能会注意到,订单完成后,页面顶部会显示“购物车为空”的消息。这是因为购物车已被清空。order对于模板上下文中包含对象的视图,我们可以轻松地避免显示此消息。
编辑shop/base.html申请模板shop,:
...
<div class="cart">
{% with total_items=cart|length %}
{% if total_items > 0 %}
购物车:
<a href="{% url "cart:cart_detail" %}">
{{ total_items }} 个{{ total_items|pluralize }},
¥ {{ cart.get_total_price }}
</a>
{% elif not order %}
你的购物车是空的。
{% endif %}
{% endwith %}
</div>
...创建订单时,将不再显示“您的购物车为空”的消息。 现在打开管理网站http://127.0.0.1:8000/admin/orders/order/。您会看到订单已成功创建,如下所示:
管理网站的订单变更列表部分,包括已创建的订单。
您已实施订单系统。现在您将学习如何创建异步任务,以便在用户下单后向其发送确认邮件。
- 完整项目代码:myshop.zip

