基于Python Django的商店购物系统毕设实战:从0到1跑通全流程
简介基于Python Django开发的商店购物系统是一项完整的毕业设计级项目面向计算机相关专业学生、Django初学者以及需要快速搭建电商原型的技术人员。项目针对传统电商系统中产品数据库模型难以适配多样化商品变体的问题采用贴合物理属性的建模方式有效避免实体属性值模型带来的大量表联接和性能损耗同时保留了深入的商品层级结构。压缩包为rar格式体积仅2.19MB共427个文件涵盖166个py后端逻辑、101个html模板、44个rst文档、29个png图片以及js、scss、po等多语言资源目录划分清晰便于按模块阅读和二次开发。整个项目可直接运行适合作为毕业设计演示、课程设计参考或电商系统扩展起点。目前已有191人学习下载资源完整度较高能帮助使用者快速理解Django的模型设计、视图交互与前端模板组织方式。1. 这类毕设项目到底在考核什么把基于python Django的商店购物系统拿来当毕业设计老师真正想看的东西并不是你写了几万行代码而是你有没有把Web开发里最核心的几条主线捋顺从前端的商品展示到后端的购物车、订单、库存扣减再到管理员的后台数据维护这条链路是不是完整且自洽的。很多同学一上来就急着写功能结果把Django当成了单纯的模板渲染工具代码是能跑但一被问到订单状态怎么流转库存超卖怎么办session和cookie谁在维护登录态就答不上来。这套系统的正确打开方式是把Django当武器而不是当拐杖。Django自带的ORM、Admin后台、表单校验、会话管理、信号机制每一个都能对应到购物系统里的具体业务模块——商品用Model定义购物车用Session存储下单用事务保证一致性权限用Django自带的用户系统扩展。全文围绕完整代码可直接运行这个核心诉求把从建项目到跑通下单全流程的细节拆开讲透同时点出答辩时老师爱问的几个深水区问题。如果你是正在做这个选题的学生或者想快速上手Django做Web项目的开发者这篇文章可以当一份带排错提示的实战地图来用。2. Django商店购物系统的技术边界与选型依据2.1 为什么是Django而不是Flask或FastAPI做购物系统这类业务密集型项目选型的第一标准不是性能而是内置能力能不能覆盖常见业务模块。Django官方自带的Admin后台可以直接当商店的管理端用商品表、订单表、用户表注册进去就有增删改查界面ORM让你不用写原生SQL就能完成多表关联查询表单系统自带CSRF防护和数据校验。这些特性拼在一起意味着你不需要为了一个毕业设计再引入额外的第三方库去做基础功能代码量少出错的面也小。对比一下另外两个框架就能看出来差别Flask灵活度高但一切靠自己搭ORM要选SQLAlchemyAdmin要配Flask-Admin登录要处理Flask-Login项目结构也得自己设计——这对毕设来说自由反而是负担FastAPI性能好、适合前后端分离但毕设通常要求的是一个完整的、能演示的站点FastAPI默认不带模板体系和Admin模块上手成本更高。2.2 MVT架构在购物系统里的落点Django采用MVTModel-View-Template架构这个T是模板而不是控制器URL路由承担了控制器的作用。映射到商店购物系统里Model对应的是商品Product、分类Category、订单Order、购物车项CartItem这些数据表View是一个个业务处理函数或类视图比如把商品加入购物车提交订单修改库存Template是前端页面负责把视图传来的数据显示出来。理解这个架构的关键在于明白数据是怎么流动的。用户点击加入购物车浏览器发起一个POST请求URL路由根据正则或路径转换器把请求分发给对应的视图函数视图函数操作Model完成数据读写最后返回一个HttpResponse或渲染后的模板。数据在Model、View、Template之间单向流转不会出现业务逻辑散落在前端脚本里的情况——这是答辩时讲系统架构最清楚的表达方式。2.3 Python版本与虚拟环境的选择Django对Python版本有明确的兼容矩阵选错了会在安装阶段就卡住。目前Django主流的3.2 LTS和4.x系列都要求Python 3.8以上建议直接装Python 3.10或3.11的稳定版避开3.13这种刚发布不久、第三方库适配还不全的版本。项目依赖一定要装在虚拟环境里这是完整代码可直接运行的前提。常见的做法是用Python自带的venv不需要额外安装virtualenv# Windows python -m venv venv venv\Scripts\activate # macOS / Linux python3 -m venv venv source venv/bin/activate激活后命令行提示符前面会出现(venv)前缀表示当前已经处于虚拟环境。然后再安装Djangopip install django pip list安装完成后用pip list确认版本。很多同学直接pip install django装到了全局环境换一台电脑或换一个项目就各种依赖冲突管理起来很被动。把依赖固化到requirements.txt里是基本习惯pip freeze requirements.txt换环境时执行pip install -r requirements.txt就能还原这也是可直接运行最基础的一层保障。3. 用Django搭建商店系统的最小可运行骨架3.1 创建项目和应用的标准命令序列Django项目和应用是两个概念项目是整体配置应用是业务模块。商店购物系统建议拆成两个应用products管商品和分类orders管购物车和订单用户直接用Django自带的auth应用。这样做的好处是业务边界分明答辩时讲系统分为商品模块、订单模块、用户模块也更有说服力。# 创建项目注意最后的点号表示在当前目录生成避免多套一层目录 django-admin startproject shop . # 创建两个业务应用 python manage.py startapp products python manage.py startapp orders把新应用注册到配置里才能用打开shop/settings.py找到INSTALLED_APPS列表加上products和orders。同时顺手做两件事把LANGUAGE_CODE改成zh-hansTIME_ZONE改成Asia/Shanghai这样Admin后台和模板里的日期显示才是中文。INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, products, # 商品应用 orders, # 订单应用 ] LANGUAGE_CODE zh-hans TIME_ZONE Asia/ShanghaiLANGUAGE_CODE影响Django内置文案的翻译语言TIME_ZONE影响datetime.now()返回的时间只改TIME_ZONE不改USE_TZ会导致时间存储和展示相差8小时这个问题在订单创建时间上特别明显。3.2 商品模型设计与Admin注册商品模型是系统的地基字段设计直接决定后续所有功能的开发难度。一个最小可用的商品模型应该包含名称、价格、库存、描述、图片、上下架状态这几个核心字段分类单独建表用外键关联。在products/models.py里写入from django.db import models class Category(models.Model): name models.CharField(max_length50, verbose_name分类名称) sort_order models.IntegerField(default0, verbose_name排序权重) class Meta: verbose_name 商品分类 verbose_name_plural verbose_name ordering [sort_order] def __str__(self): return self.name class Product(models.Model): category models.ForeignKey( Category, on_deletemodels.PROTECT, related_nameproducts, verbose_name所属分类 ) name models.CharField(max_length200, verbose_name商品名称) price models.DecimalField(max_digits10, decimal_places2, verbose_name售价) stock models.PositiveIntegerField(default0, verbose_name库存) description models.TextField(blankTrue, verbose_name商品描述) is_active models.BooleanField(defaultTrue, verbose_name是否上架) created_at models.DateTimeField(auto_now_addTrue, verbose_name创建时间) class Meta: verbose_name 商品 verbose_name_plural verbose_name ordering [-created_at] def __str__(self): return self.name def reduce_stock(self, quantity): 扣减库存库存不足时抛异常 if quantity self.stock: raise ValueError(库存不足) self.stock - quantity self.save(update_fields[stock])字段类型的选择有讲究价格为什么用DecimalField而不是FloatField因为浮点数在计算机里是二进制近似存储0.1加0.2算出来是0.30000000000000004放到金额上是不能容忍的精度错误库存用PositiveIntegerField保证不能为负on_deletemodels.PROTECT表示有商品关联的分类不允许删除防止删了分类导致商品成为孤儿数据。模型定义好之后要生成迁移文件并同步到数据库python manage.py makemigrations python manage.py migratemakemigrations根据模型变化生成迁移脚本migrate把迁移脚本真正应用到数据库。这两个命令是Django开发里最频繁的操作模块新增字段、修改字段类型后都要重新执行一遍。接着在products/admin.py里注册模型让商品能通过后台管理from django.contrib import admin from .models import Category, Product admin.register(Category) class CategoryAdmin(admin.ModelAdmin): list_display [name, sort_order] admin.register(Product) class ProductAdmin(admin.ModelAdmin): list_display [name, category, price, stock, is_active] list_filter [category, is_active] search_fields [name]list_display控制后台列表页显示哪些列list_filter在右侧生成筛选器search_fields开启搜索框。这一步做完创建管理员账号登录后台就能看到商品管理界面了python manage.py createsuperuser python manage.py runserver3.3 商品列表与详情页的模板渲染前端页面用Django模板语法渲染视图从数据库取数据传参到模板。在products/views.py里写两个视图函数from django.shortcuts import render, get_object_or_404 from .models import Product def product_list(request): products Product.objects.filter(is_activeTrue) return render(request, products/list.html, {products: products}) def product_detail(request, product_id): product get_object_or_404(Product, idproduct_id, is_activeTrue) return render(request, products/detail.html, {product: product})get_object_or_404在商品不存在时自动抛404异常省去了手动判断加try/except的步骤。配置好URL路由才能访问到这两个视图在shop/urls.py里做一次总路由分发from django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(, include(products.urls)), path(orders/, include(orders.urls)), ]然后products/urls.py里写from django.urls import path from . import views urlpatterns [ path(, views.product_list, nameproduct_list), path(product/int:product_id/, views.product_detail, nameproduct_detail), ]模板文件放在products/templates/products/目录下这个路径约定是Django的app目录模板加载规则。列表页的核心代码是模板循环{% for product in products %} div classcard h3{{ product.name }}/h3 p{{ product.price }} 元/p a href{% url product_detail product.id %}查看详情/a /div {% empty %} p暂未上架任何商品/p {% endfor %}{% url %}标签通过路由的name反解析出真实URL好处是路由地址调整后模板不需要改。程序运行时Django模板引擎先把{{ product.name }}替换成对应字段值再把整个页面作为字符串返回给浏览器。4. 购物车、下单与库存扣减的核心实现4.1 购物车用Session存储还是数据库存储购物车的实现方案有两种一种是把购物车数据存在Session里另一种是建CartItem表用数据库存。毕设场景下推荐Session方案因为匿名用户也可以使用购物车不需要强制登录而数据库方案需要处理游客身份的识别复杂度高很多。Session方案的原理是Django为每个访问者生成一个随机session key以cookie形式写回浏览器服务端用这个key在django_session表里找对应数据。购物车数据以字典形式存进session结构是商品ID-数量的映射def cart_add(request, product_id): quantity int(request.POST.get(quantity, 1)) cart request.session.get(cart, {}) product get_object_or_404(Product, idproduct_id) if not product.is_active: return redirect(product_list) cart[str(product_id)] cart.get(str(product_id), 0) quantity request.session[cart] cart return redirect(cart_detail)注意cart字典的key必须转成字符串。JSON序列化时字典的key如果不是字符串会出问题Django的Session框架底层用的是JSON序列化而JSON规范的key必须是有序字符串这一点特别容易踩坑整数key能存进去但取出来的时候可能已经变成了字符串判断逻辑就会出错。4.2 购物车页面的数量修改与商品总价计算购物车页面要展示当前购物车里的所有商品最基本的操作是遍历session里的商品ID逐个查库拿到商品信息再累加总价def cart_detail(request): cart request.session.get(cart, {}) items [] total_amount 0 for product_id, quantity in cart.items(): try: product Product.objects.get(idint(product_id)) except Product.DoesNotExist: continue subtotal product.price * quantity total_amount subtotal items.append({ product: product, quantity: quantity, subtotal: subtotal, }) return render(request, orders/cart_detail.html, { items: items, total_amount: total_amount, })这段代码里有一个try/except处理商品被下架或删除的情况——购物车里的商品ID在session里不一定还对应有效的商品记录continue跳过而不是报错页面才能正常渲染。商品总价的计算放在视图层而不是模板层好处是价格逻辑统一在Python里处理模板只负责显示。购物车数量的增减操作也走视图比如修改数量def cart_update(request, product_id): cart request.session.get(cart, {}) action request.POST.get(action) if action increase: cart[str(product_id)] cart.get(str(product_id), 0) 1 elif action decrease: cart[str(product_id)] cart.get(str(product_id), 0) - 1 if cart[str(product_id)] 0: cart.pop(str(product_id), None) request.session[cart] cart return redirect(cart_detail)这里用request.POST.get(action)区分加号和减号比在URL里传参数更规范避免GET请求修改服务端状态的副作用。4.3 下单时用事务锁住库存下单是整个系统里最需要严谨对待的环节。用户提交订单时系统要做这几件事创建订单主表记录、把购物车商品写入订单明细表、扣减商品库存、清空购物车。这四个操作只要有一个失败其他三个也要回滚否则会出现订单建了但库存没扣库存扣了但订单没建成的数据不一致问题。Django的transaction.atomic()就是干这个的from django.db import transaction from django.utils import timezone def order_create(request): if request.method POST: cart request.session.get(cart, {}) if not cart: return redirect(cart_detail) # 收件信息从表单获取 receiver request.POST.get(receiver) address request.POST.get(address) phone request.POST.get(phone) try: with transaction.atomic(): order Order.objects.create( userrequest.user if request.user.is_authenticated else None, receiverreceiver, addressaddress, phonephone, total_amount0, ) order_lines [] total 0 for product_id, quantity in cart.items(): product Product.objects.select_for_update().get(idint(product_id)) if product.stock quantity: raise ValueError(f{product.name} 库存不足) product.reduce_stock(quantity) line_total product.price * quantity total line_total order_lines.append(OrderItem( orderorder, productproduct, priceproduct.price, quantityquantity, )) order.total_amount total order.save(update_fields[total_amount]) OrderItem.objects.bulk_create(order_lines) except ValueError as e: return render(request, orders/error.html, {error: str(e)}) # 下单成功清空购物车 request.session[cart] {} return render(request, orders/success.html, {order: order}) return render(request, orders/checkout.html)这段代码里最关键的是select_for_update()它在数据库层面对商品行加了行级锁。两个用户同时购买同一个商品时第一个事务拿到锁第二个事务必须等第一个提交后才能读取这个机制从根源上防止了超卖——库存剩1件两个用户同时下单如果没有锁两个事务都读到库存为1各自扣一回库存就变成负数了。bulk_create一次批量写入所有订单明细比循环里逐条create少发很多次SQL请求性能更好。update_fields[total_amount]只更新指定字段避免Django把所有字段都重写一遍。4.4 订单删除与关联数据处理Django的执行查询与删除操作有一个常见的误区直接delete()一个主表对象时外键关联的子表数据怎么处理取决于on_delete参数。对订单和订单明细这对关系推荐用CASCADE删订单就自动删明细——这符合业务直觉class Order(models.Model): user models.ForeignKey( settings.AUTH_USER_MODEL, on_deletemodels.SET_NULL, nullTrue, blankTrue, verbose_name下单用户 ) receiver models.CharField(max_length50) address models.CharField(max_length200) phone models.CharField(max_length20) total_amount models.DecimalField(max_digits10, decimal_places2, default0) status models.CharField( max_length20, choices[ (pending, 待付款), (paid, 已付款), (shipped, 已发货), (completed, 已完成), (cancelled, 已取消), ], defaultpending ) created_at models.DateTimeField(auto_now_addTrue) class OrderItem(models.Model): order models.ForeignKey(Order, on_deletemodels.CASCADE, related_nameitems) product models.ForeignKey(Product, on_deletemodels.PROTECT) price models.DecimalField(max_digits10, decimal_places2) quantity models.PositiveIntegerField(default1)on_deletemodels.PROTECT用在订单明细关联商品上含义是被订单引用过的商品不允许删除保留商品历史数据。在后台删除订单时Django会自动级联删除所有明细但如果你用的是QuerySet.delete()批量删除注意它不会调用Model里重写的delete()方法所以涉及自定义清理逻辑时要在post_delete信号里处理。5. 毕设交付前的验证清单与答辩应急技巧5.1 用Shell脚本自动验证核心功能代码完整可直接运行不是嘴上说说要在答辩演示前做一次全链路验证。把下面这段脚本存成check_project.sh在项目根目录执行#!/bin/bash cd $(dirname $0) echo 1. 检查Python环境 python --version echo 2. 检查依赖是否齐全 pip install -r requirements.txt -q echo 3. 检查数据库迁移状态 python manage.py makemigrations --check python manage.py migrate --noinput echo 4. 启动服务器测试首页 python manage.py runserver 8000 SERVER_PID$! sleep 3 curl -s -o /dev/null -w 首页状态码: %{http_code}\n http://127.0.0.1:8000/ curl -s -o /dev/null -w 后台状态码: %{http_code}\n http://127.0.0.1:8000/admin/ kill $SERVER_PID echo 验证完成这段脚本做的事是从零检查一遍项目的可运行性依赖有没有装、迁移有没有落后、两个关键页面能不能返回200状态码。答辩前跑一次能避免现场诶我本地明明能跑的尴尬。5.2 生成演示数据的方法刚迁移完的空数据库没有商品演示时页面空空如也不好看。在products/management/commands/seed_data.py里写一个自定义命令from django.core.management.base import BaseCommand from products.models import Category, Product class Command(BaseCommand): help 生成演示用商品数据 def handle(self, *args, **kwargs): category Category.objects.create(name手机数码, sort_order1) Product.objects.create( categorycategory, name旗舰手机, price5999.00, stock100, description演示用商品 ) self.stdout.write(self.style.SUCCESS(演示数据创建成功))执行python manage.py seed_data即可。自定义管理命令是Django被忽略的实用功能适合做数据初始化和批量操作答辩时展示这一步能给老师留下工程化习惯比较好的印象。5.3 Admin后台界面美化的最小改造答辩前给Admin换肤是最低成本的美化方案安装django-admin-interface包可以替换默认的Django Admin样式pip install django-admin-interface然后在INSTALLED_APPS里把admin_interface放到django.contrib.admin前面加上colorfield迁移后进入Admin后台就能在右上角看到界面设置入口换主题色、折叠侧边栏、调整logo都可以可视化操作。这个改动对功能没有任何影响纯视觉提升适合在演示前花五分钟做完。5.4 被追问时的三个深水区问题应答老师问你如何防止库存超卖可以这么答在事务里用select_for_update()锁住涉及的商品行保证同一时间只有一个请求能扣减同一件商品的库存再配合stock quantity的判断兜底stock字段用PositiveIntegerField保证不可能为负。如果能再补充一句生产环境下还可以加Redis分布式锁或者用乐观锁CAS方案就是加分项。老师问你Session里的购物车数据什么时候清掉可以这么答购物车保存在Session里默认有效期两周用户下单成功后主动清空如果用户中途关闭浏览器Session还在下次打开还在要主动清理可以用request.session.flush()销毁整个会话。这个细节能体现你理解状态存储的本质。最后提醒一个重要细节提交前删除项目目录下的__pycache__文件夹压缩包体积会小很多。.gitignore里加上venv/、*.pyc、__pycache__/、db.sqlite3如果用的是SQLite数据库演示数据要不要提交这个问题提前想清楚别让老师拿到压缩包双击运行的时候带着你的旧购物车数据上台。本文还有配套的精品资源点击获取