Django购物系统开发实战:从环境搭建到服务器部署
2026/9/12 13:32:20 网站建设 项目流程

简介:这是一套基于Python Django框架的商店购物系统毕业设计完整源码,适合计算机相关专业学生、Django初/中级开发者参考学习。项目重点解决了传统电商系统中产品属性不灵活、无法支撑变体层次结构的问题,通过模型设计直接反映物理属性,避免使用EAV数据库反模式。压缩包共2.19MB,包含427个文件,其中166个Python文件承载核心业务逻辑,101个HTML模板覆盖前端页面,44个RST文档便于阅读项目说明,另有JS、SCSS、PNG、国际化PO/MO等资源,整体结构清晰,可直接运行。目前已有191人学习/下载。通过该项目可掌握Django购物车的实现思路、商品模型与多规格变体设计、后台管理及订单流程等方法,代码注释完整、目录组织规范,适合作为毕业设计、课程设计或电商项目二次开发的基础模板。

1. 得先认清:为什么选Django购物系统当毕设,而不是从零写框架

毕设项目拿到手,第一件事不是读代码,而是先把环境跑通。很多基于Python Django的商店购物系统看起来完整,实际会卡在Python版本、mysqlclient依赖、没执行数据迁移这几步。这套系统的好处是,它把商品展示、加入购物车、生成订单、后台管理这一条链路写全了,而且模型数量少,关系清晰,适合在答辩前做二次开发。下面直接从“跑起来”开始,按环境、核心模块、改造、上线四个方向展开,每一步都给出可执行的命令和参数。你不用改完所有代码才敢启动,先跑通,再按自己的论文需求改字段改逻辑,是处理这类毕设项目最省时间的路径。

2. 本地把购物系统跑起来:Python、Django与依赖项的搭配

2.1 先选对Python和Django版本,省得后面重装依赖

拿到“完整代码可直接运行”这句承诺时,先别急着运行。最常见的问题是项目用的是老版本Django,而你机器上是新Python。我的习惯是先看项目里的requirements.txt,没有的话看manage.py里设置的默认配置,再决定本地环境。

Python 3.8配Django 4.2是可以长期用的组合,Python 3.10、3.11也没问题。Django 5.0以上对Python 3.10以下不友好。如果这份毕设是前几年做的,大概率是Django 2.2或3.2,这两个版本在路由写法和时区配置上和老项目有差异。手里没有现成版本可以参考的时候,我一般直接装Django 4.2.x配Python 3.10或3.11,稳定性好,网上教程也多。

| 组合 | 适用情况 | 注意点 | | Python 3.8 + Django 2.2 | 老代码、Python2迁移上来的 | 新语法支持有限,不建议新开 | | Python 3.10 + Django 4.2 | 大多数毕设二次开发推荐 | 支持新时区配置,自带admin较完整 | | Python 3.12 + Django 5.x | 新项目 | 部分mysqlclient包需要编译,环境配置成本高 |

2.2 安装Python并创建虚拟环境

系统里已经装了Python的话,先确认是3.8以上。Windows用户注意在Python安装时勾选“Add Python to PATH”。我会专门为这个毕设单独建一个虚拟环境,避免污染全局site-packages:

python3 -m venv shop_env source shop_env/bin/activate # Windows下执行 shop_env\Scripts\activate pip install --upgrade pip pip install django

这里解释一下:python3 -m venv shop_env创建了一个名为shop_env的虚拟环境,里面会有独立的Python解释器和pip目录。进入虚拟环境之后再装的Django只对当前项目生效,这样即使之后其他项目用了不同版本的Django,也不会互相覆盖。常见的错误是漏掉source激活步骤,直接运行pip install,结果装到了全局环境,后面runserver时又找不到模块。用VSCode调试时,记得按下Ctrl+Shift+P选择Python解释器,指向shop_env里的python,否则终端和调试器会用两套不同的环境。

2.3 安装mysqlclient和缺失包,避免启动就报错

购物系统一般需要MySQL作为数据库。MySQL与Python之间的连接库是mysqlclient,但这个包在部分系统上需要编译依赖,这也是“完整代码运行不起来”的高发区。Linux下先安装系统库,再装mysqlclient:

sudo apt-get install libmysqlclient-dev python3-dev pip install mysqlclient

Windows用户一般直接pip install mysqlclient就能安装,少数用Python 3.12的会遇到没有预编译wheel的情况,建议换回Python 3.10或使用pymysql作为兼容方案。pymysql的接法是在项目包__init__.py里加上pymysql.install_as_MySQLdb(),不过这只适合临时调试,正式交毕设我还是推荐mysqlclient。

安装完依赖后不要急着启动。先改settings.py里的数据库连接:

DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'shop_db', 'USER': 'root', 'PASSWORD': 'your_password', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': {'charset': 'utf8mb4'}, } }

数据库名、用户名、密码、端口这四个字段按你本机改。注意NAME指向的数据库必须先建好,可以通过MySQL命令行执行CREATE DATABASE shop_db DEFAULT CHARACTER SET utf8mb4;创建。OPTIONS里的charset配置为utf8mb4,是为了让商品名称、用户昵称里的emoji和生僻字不变成问号。

2.4 第一次启动的最小命令集

确认项目文件夹下有manage.py之后,检查是否有一个独立的业务app目录。如果没有,先运行python manage.py startapp shop,这是Django创建app的标准命令;有的话直接进入下一步。settings.py改完后,按顺序执行以下几行:

python manage.py makemigrations python manage.py migrate python manage.py createsuperuser python manage.py runserver 127.0.0.1:8000

makemigrations把模型类变化登记成迁移文件,migrate把迁移写入数据库。如果项目里已经有迁移文件,也可以直接执行migrate跳过makemigrations。createsuperuser会让你输入用户名、邮箱、密码,这是登录后台的账号。runserver监听在127.0.0.1:8000,浏览器打开http://127.0.0.1:8000/,能看到商店首页,就说明这套基于Django的商店购物系统已经在本地跑起来了。如果页面报错,优先看终端里的Traceback,90%的情况是数据库连不上或者没装mysqlclient。

3. 拆开购物系统的四个核心:商品、购物车、订单、用户

3.1 商品模型:字段类型与价格计算的边界

购物系统里最基础的表是商品表。毕设里常见的商品模型大致长这样:

from django.db import models class Category(models.Model): name = models.CharField('分类名称', max_length=50) sort_order = models.IntegerField('排序', default=0) class Meta: ordering = ['sort_order'] def __str__(self): return self.name class Product(models.Model): category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name='分类') name = models.CharField('商品名称', max_length=100) price = models.DecimalField('单价', max_digits=10, decimal_places=2) stock = models.PositiveIntegerField('库存', default=0) main_image = models.ImageField('主图', upload_to='products/', blank=True, null=True) status = models.BooleanField('上架', default=True) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['-created_at'] def __str__(self): return self.name

ForeignKey里面的on_delete=models.CASCADE表示分类删除时,该分类下的商品一起删。对毕设来说这个参数最容易被答辩追问。price用DecimalField而不是FloatField,原因是浮点数有二进制精度问题,算总价时会出现19.98999999这种结果,DecimalField配合decimal_places=2能保证金额精确到分。stock用PositiveIntegerField是为了避免库存变负数,数据库层面直接拦截非法数据。

3.2 购物车:用Session存还是有张CartItem表

购物车有两种实现路线。只用session保存商品ID和数量,代码量小,但订单落到库里时需要再算一次价格;用CartItem表保存用户与商品的关系,代码稍多,但可以记录勾选状态、商品快照,后续做订单详情更方便。这里以数据库表为例:

class CartItem(models.Model): user = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name='用户') product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name='商品') quantity = models.PositiveIntegerField('数量', default=1) selected = models.BooleanField('选中状态', default=True)

cart_item的user关联Django自带用户表,AUTH_USER_MODEL是一个设置项,你可以换成自定义用户模型。add视图里需要检查同一个用户是否已经把这个商品加进购物车,避免同一个product出现在同一用户的多行记录里,然后用update_or_create更新数量:

def add_to_cart(request, product_id): product = get_object_or_404(Product, pk=product_id) if not product.status: return redirect('shop:product_list') cart_item, created = CartItem.objects.update_or_create( user=request.user, product=product, defaults={'quantity': F('quantity') + 1} ) return redirect('shop:cart_detail')

注意defaults里的F('quantity')是指让数据库对原quantity字段做加1操作,不会出现并发下读旧值再写回的问题。created只是告诉你是新增还是更新,不需要额外手动把quantity设置成1,因为新增时模型默认值default=1已经生效。用F表达式时不要尝试在Python侧先读取cart_item.quantity再加1,这样会多一条SELECT,而且在并发场景下结果不对。

3.3 订单状态流转:从购物车到待支付

订单表一般包含订单号、用户、总金额、收货信息、状态。状态建议用IntegerField加choices,不用字符串,理由是数字状态在数据库里占空间小,也方便扩展。常见状态值如下:

| 状态值 | 含义 | 下一步操作 | | 0 | 待支付 | 用户付费 | | 1 | 已支付 | 商家发货 | | 2 | 已发货 | 用户确认收货 | | 3 | 已收货 | 订单完成 | | 4 | 已取消 | 恢复库存 |

创建订单时,核心逻辑是先从CartItem取出当前用户所有selected=True的记录,计算总价,然后创建Order和OrderItem,最后清空购物车。这块代码量不大,但有一个必坑点:订单一旦创建,不应该再关联CartItem,而是要把商品名称、单价、数量复制到OrderItem里作为快照。否则商品改了价格,历史订单的金额也会跟着变,对账时会很被动。

3.4 Django执行查询-删除对象时要注意级联

Django里最常用的删除写法是Model.objects.get(pk=1).delete()Model.objects.filter(...).delete()。对于购物系统的后台删除商品操作,我不建议直接调delete(),因为Product被CartItem和OrderItem外键引用时,CASCADE会把历史订单明细也一起删掉。你可以在OrderItem外键和CartItem外键都改用on_delete=models.PROTECT,这样系统会拒绝删除有被引用的商品,并抛出ProtectedError。

# 在模型里这样改 product = models.ForeignKey(Product, on_delete=models.PROTECT, verbose_name='商品')

再用软删除代替硬删除更稳妥,只需要给Product加一个is_active字段,查询时默认过滤is_active=True,后台删除变成置为False。这样商品不在商店展示,历史订单数据却完整保留,答辩时跟老师解释数据完整性和可追溯性,是个加分点。删除对象一旦涉及外键,就不要再裸用delete(),先用orderitem_set.exists()确认引用关系。

4. 把完整代码改成自己的毕设:Admin、重定向与初始化数据

4.1 改表结构后,如何安全地多次迁移

拿到完整代码后,第一件事往往是想加上自己论文里独有的字段,比如商品加入“产地”“上架时间范围”。修改模型后执行makemigrations,要养成先看生成的迁移文件再执行migrate的习惯。

python manage.py makemigrations python manage.py sqlmigrate 应用名 迁移文件名 python manage.py migrate

sqlmigrate只输出对应的SQL,不会真正执行。这样能看到MySQL实际执行的ALTER TABLE语句,可以提前发现是否有非空字段约束、默认值缺失的问题。新增非空字段时,makemigrations通常会在命令行里询问默认值,这时候直接给一个合理的默认值比交互更可控。最稳妥的做法是给新字段加上defaultnull=True,例如color = models.CharField(max_length=20, default='')

| 命令 | 作用 | | python manage.py makemigrations | 根据模型变化生成迁移文件 | | python manage.py sqlmigrate 应用 迁移名 | 查看迁移对应的SQL | | python manage.py migrate | 执行迁移 | | python manage.py showmigrations | 查看迁移执行情况 |

迁移文件本身也要纳入版本管理,不要让队友拿到项目后自己重新makemigrations,因为产生的文件编号会出现重复。直接让他们执行migrate即可。

4.2 Django admin界面美化:把列表页变成可答辩的后台

购物系统自带的admin可以满足基本增删改查,但默认样式很简单。你不需要引入前后端分离,先把admin的list_display配置好,后台看起来就比空页面专业得多。

# admin.py from django.contrib import admin from .models import Product, Order, OrderItem @admin.register(Product) class ProductAdmin(admin.ModelAdmin): list_display = ('id', 'name', 'category', 'price', 'stock', 'status', 'created_at') list_filter = ('status', 'category') search_fields = ('name',) list_editable = ('price', 'stock', 'status') actions = ['set_all_off_shelf'] @admin.action(description='批量下架') def set_all_off_shelf(self, request, queryset): queryset.update(status=False)

常用配置里,list_editable和list_display同时出现的字段不能是第一个字段,否则Django会报错,因为第一列默认作为详情链接入口。search_fields设置的是后台搜索框里可以模糊匹配的字段,Django会生成LIKE查询,所以只应放短字符串字段,不要把TextField放进去。自定义action可以让批量操作直接出现在操作下拉框里,上面代码执行的是对当前选中的商品一次性置为下架状态。

如果是Windows或VSCode开发环境,admin样式不生效时,优先检查INSTALLED_APPS里有没有django.contrib.staticfiles,以及浏览器Network面板里的静态文件请求是否404。404的原因往往是没有执行python manage.py collectstatic,或DEBUG=False时静态文件路径没配好。调试期间把DEBUG保持True即可,生产部署时再关。

4.3 Django重定向传递数据与reverse resolve的配套用法

购物流程里,提交订单后通常要跳转到支付页或订单详情页,并在页面上显示“下单成功”提示。重定向时用redirect实现:

from django.shortcuts import redirect from django.urls import reverse def order_confirm(request): order_id = create_order(request.user) return redirect(reverse('shop:order_success') + f'?order_id={order_id}')

这样跳转后,订单数据没有丢,query string里带着order_id。如果觉得查询参数太长,可以在视图里直接放到session,request.session['last_order_id'] = order_id,跳转后再读出来并删除。用GET请求传参时要注意:用户刷新页面会重复触发GET,但不会重复创建订单,因为创建订单的动作已经在前面执行完了,这里只是读取。如果要严格防止刷新重复提交,需要在创建订单前做一个幂等标识,比如检查session里是否已有同一请求产生的订单号。

reverse resolve这个报错通常是路由写法不一致。页面报NoReverseMatch时,多半是模板里写错了{% url 'shop:cart_detail' %},而urls.py实际定义的名字是cart-detail或cart_detail。用python manage.py show_urls(需要安装django-extensions)可以列出当前站点所有路由名,直接对照去改模板name,效率最高。

4.4 生成演示数据,让后台和首页看起来像真实商店

毕设验收时老师打开首页,如果看到空列表,第一印象会差很多。用Django自己的ORM在shell里批量生成商品数据即可:

python manage.py shell

进入交互式环境后写:

from shop.models import Category, Product c1, _ = Category.objects.get_or_create(name='数码', sort_order=1) for i in range(1, 11): Product.objects.get_or_create( category=c1, name=f'样品商品{i}', defaults={'price': i * 10, 'stock': 50}, )

get_or_create的defaults参数只在记录不存在时生效,而查重字段是category和name,避免了重复创建同名商品。批量生成后,再用cart和order的接口生成一两条真实订单,后台列表就有数据可看了。不止是空演示,还建议准备一份只含少量数据的fixture,提交时放到项目的fixtures目录下,别人拿到项目后执行python manage.py loaddata sample_data.json就能复现你的所有演示内容。

5. 上线前收紧一公里:在宝塔部署Django购物系统

5.1 用宝塔面板部署Django项目,静态文件配置是关键

这套购物系统验收地点如果不在本机,需要部署到服务器。常见做法是宝塔部署Django项目:先在服务器装好Python环境,上传代码,在代码目录里建虚拟环境,安装依赖,再用宝塔的Python项目管理器运行项目。部署前改造settings.py:

DEBUG = False ALLOWED_HOSTS = ['your.domain.com', '123.56.78.90'] STATIC_ROOT = '/www/wwwroot/shop_project/staticfiles' DATABASES = { # 数据库账号改为服务器MySQL的账号,不要用root远程登录 }

DEBUG设为False后,如果不把Static Root收集好,admin的CSS和JS会全部丢失,后台页面会变成纯HTML。执行python manage.py collectstatic,会把所有app的静态文件复制到STATIC_ROOT指定目录,宝塔站点配置里要把这个目录映射成静态URL。如果用的是Nginx,通常要加一行location /static/ { alias /www/wwwroot/shop_project/staticfiles/; },否则后台样式仍然404。

5.2 验收时不只点首页,按这个清单过一次

部署完成后,我会按下面这个顺序快速验收购物系统的关键链路:

curl -I http://你的域名/admin/ curl -I http://你的域名/

先看admin返回200还是302,302说明跳到了登录页,正常。再手动走一遍商品列表、加入购物车、结算下单,确认没有出现500。常见错误是数据库迁移没执行,页面直接报relation does not exist,这时回到项目目录执行python manage.py migrate。还有一处是购物车页面用到了request.user,如果Django启用了SESSION_COOKIE_SECURE,未用HTTPS时会登录不上,临时调试可把该选项关闭。提交前最后跑一次安全基线检查:

python manage.py check --deploy

输出里如果提示SECRET_KEY被硬编码,就改成从环境变量读取。按这条清单走完,这套购物系统才算真正从“本地能跑”变成了“可以作为毕设交付”。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询