ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Django商城源码课程设计:从模型到订单状态机的核心拆解

Django商城源码课程设计:从模型到订单状态机的核心拆解 简介基于Django的购物商城系统是一份面向Python初学者的课程设计完整源码包适合需要完成电商类Web项目的学生或开发者参考。项目实现了商品展示、用户注册登录、购物车管理、订单生成、在线支付及管理员订单状态更新等典型电商功能完整覆盖Django框架的项目搭建、ORM模型设计、视图函数编写、URL配置与HTML模板渲染等关键技术环节。资源压缩包共226个文件约1.52MB其中包含126个Python源码文件、18个HTML模板页面、多张商品图片、CSS与JS前端资源还附带SQL数据库文件和一处部署脚本目录结构清晰便于直接运行与调试。目前已有66人学习下载。整套代码既能帮助初学者快速理解Django MTV开发模式也可作为Python课程设计的完整参考方案并能在现有功能基础上进行二次扩展适合毕业设计或实训项目借鉴。1. 这套Django商城源码拿来做课程设计最值得拆的四个点答辩时被问到“订单状态为什么不能直接在表里改”很多人会把课程设计做成纯CRUD演示这一问就卡住。这份基于Django的购物商城系统覆盖了商品展示、注册登录、购物车、订单生成、在线支付、后台订单管理与评价几个完整环节任何一个点深挖下去都能撑起答辩前二十分钟。它适合正在找数据库课程设计或Python大作业改造基础的人也适合刚学完python django搭建web项目、想完整看一个多张业务表如何组织的人。源码里自带HTML模板和静态资源不需要从零写前端精力可以全部放在模型设计和请求处理上。2. 从client.conf到models.py先看清目录和表结构2.1 项目里的HTML、静态文件与配置文件分别管什么拿到压缩包先别急着跑python manage.py runserver。解压后看到的client.conf、main.css、reset.css和若干个html文件职责完全不同。以常见排布来看client.conf是部署环境里留下的站点配置文件一般用于Nginx或uWSGI的静态资源路径和反向代理设置它的存在说明这个项目是按可部署标准写的而不是演示完就删掉的临时脚本。reset.css和main.css是经典搭配reset负责把浏览器默认样式归零避免不同内核渲染差异main.css负责商城页面里的栅格、商品卡片、按钮等视觉样式。把HTML文件对应到功能边界会更直观。index.html是首页商品展示list.html是分类或搜索结果列表search.html是站内搜索页detail.html是商品详情与评价入口user_center_site.html是登录后的个人中心。这个划分刚好对应商城前端的五个核心页面也决定了后面URLconf里需要几个视图函数。文件职责对应业务index.html首页模板商品展示list.html分类/搜索结果列表模板商品浏览search.html搜索页模板关键字检索detail.html商品详情模板商品信息与评价展示user_center_site.html个人中心模板用户信息、订单入口client.conf站点部署配置静态文件与代理设置reset.css / main.css样式重置与主样式页面视觉体系2.2 核心模型商品、购物车、订单落到数据库是什么样Django课程设计最容易被问的第一个问题是“你的数据库怎么设计的”。既然用了Django自带的ORM就不能只贴一张E-R图得能说清楚每个模型对应哪张表、为什么这样拆。下面是这个场景下最标准的模型结构可以直接对照源码里的models.py看。from django.db import models from django.contrib.auth.models import User class Category(models.Model): name models.CharField(max_length64, verbose_name分类名) parent models.ForeignKey(self, nullTrue, blankTrue, on_deletemodels.CASCADE, verbose_name父分类) class Product(models.Model): name models.CharField(max_length128, verbose_name商品名称) category models.ForeignKey(Category, on_deletemodels.PROTECT, verbose_name分类) desc models.TextField(verbose_name商品描述) price models.DecimalField(max_digits10, decimal_places2, verbose_name售价) stock models.PositiveIntegerField(default0, verbose_name库存) image models.ImageField(upload_toproduct/, blankTrue, verbose_name主图) is_on_sale models.BooleanField(defaultTrue, verbose_name是否上架) created_at models.DateTimeField(auto_now_addTrue, verbose_name创建时间) class CartItem(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name用户) product models.ForeignKey(Product, on_deletemodels.CASCADE, verbose_name商品) quantity models.PositiveIntegerField(default1, verbose_name数量) selected models.BooleanField(defaultTrue, verbose_name是否勾选) class Order(models.Model): STATUS_CHOICES ( (pending, 待支付), (paid, 已支付), (shipped, 已发货), (completed, 已完成), (canceled, 已取消), ) order_no models.CharField(max_length32, uniqueTrue, verbose_name订单号) user models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name下单用户) status models.CharField(max_length16, choicesSTATUS_CHOICES, defaultpending, verbose_name订单状态) total_amount models.DecimalField(max_digits10, decimal_places2, verbose_name订单总额) created_at models.DateTimeField(auto_now_addTrue, verbose_name下单时间) class OrderItem(models.Model): order models.ForeignKey(Order, related_nameitems, on_deletemodels.CASCADE, verbose_name订单) product models.ForeignKey(Product, on_deletemodels.PROTECT, verbose_name商品) price models.DecimalField(max_digits10, decimal_places2, verbose_name成交价) quantity models.PositiveIntegerField(verbose_name数量) class Review(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name评价用户) product models.ForeignKey(Product, on_deletemodels.CASCADE, verbose_name商品) rating models.PositiveIntegerField(verbose_name评分) content models.TextField(verbose_name评价内容) created_at models.DateTimeField(auto_now_addTrue, verbose_name评价时间)这里最值得讲解的是Order和OrderItem的拆分。如果只建一张订单表把商品名称、价格、数量都塞进同一个表确实能跑通但数据库课程设计拿不到高分因为商品价格后续会调整。OrderItem里单独存price保存的是用户下单那一刻的成交价快照后续商品调价不影响历史订单。Product.stock存实时剩余库存真正的扣减逻辑放在下单事务里做不能靠前端传过来的数字。CartItem加selected字段是为了实现“勾选部分商品结算”这是从购物车转订单时非常常见的需求。2.3 第一次运行前要处理的数据同步数据库同步是这个项目第一个坑。源码默认连SQLite可以直接跑但要换成MySQL就需要在settings.py里改配置。需要注意Django连接MySQL需要安装mysqlclientWindows环境下经常编译失败常见做法是pip install mysqlclient如果编译报错就直接使用pymysql并在项目的__init__.py里写入pymysql.install_as_MySQLdb()。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}, } }配置里的charset建议固定为utf8mb4否则用户评价里出现emoji表情时会直接写入报错。改完配置后依次执行python manage.py makemigrations和python manage.py migrate再用python manage.py createsuperuser创建管理员账号。数据库课程设计通常需要提交建表语句可以在迁移后用python manage.py sqlmigrate app名 迁移文件编号把Django生成的SQL导出来交作业。3. 登录态、购物车与订单状态机三个最容易被问倒的点3.1 用户认证先用Django内置认证再自己补注册视图商城系统的用户认证不需要重复造轮子。Django内置的User表自带密码哈希、session、权限分组课程设计完全够用。很多人为了“显得自己写了很多代码”从零实现一套密码存储逻辑在答辩时反而容易被追问“你的密码是怎么加密的”“和Django默认的PBKDF2比安全性如何”答不上来会很被动。正确做法是直接用内置认证把精力花在注册视图和登录后的跳转逻辑上。from django.contrib.auth import login, authenticate from django.contrib.auth.models import User from django.shortcuts import render, redirect def register(request): if request.method POST: username request.POST.get(username) password request.POST.get(password) if User.objects.filter(usernameusername).exists(): return render(request, register.html, {error: 用户名已存在}) user User.objects.create_user(usernameusername, passwordpassword) login(request, user) return redirect(index) return render(request, register.html) def user_login(request): if request.method POST: username request.POST.get(username) password request.POST.get(password) user authenticate(request, usernameusername, passwordpassword) if user is not None: login(request, user) return redirect(index) return render(request, login.html, {error: 用户名或密码错误}) return render(request, login.html)create_user会自动对密码做哈希存储authenticate负责校验用户输入并返回User对象login把登录状态写入session。这三者的分工要能讲清楚创建、校验、建立会话。登录后模板里用{{ user.username }}直接取当前用户信息login_required装饰器可以保护购物车、结算这类操作未登录用户会被重定向到登录页。3.2 购物车合并匿名session里的商品如何并入登录用户购物车是这套系统里表关系最简单的部分但业务逻辑上有个很关键的边界用户未登录时添加到购物车的商品存在session里登录后又要存进CartItem表。如果登录后直接把session里的购物车清空用户会认为系统吞掉了商品如果不清理订单结算时就会漏掉session里的东西。处理逻辑通常是登录成功后做一次合并def merge_cart(request, user): session_cart request.session.get(cart, {}) for product_id, qty in session_cart.items(): cart_item, created CartItem.objects.get_or_create( useruser, product_idproduct_id, defaults{quantity: qty} ) if not created: cart_item.quantity qty cart_item.save() request.session[cart] {}get_or_create是这里的关键如果用户之前已经往CartItem表里加过同一件商品就直接在现有记录上累加数量避免出现两条重复数据。合并完成后必须清空session里的购物车否则结算视图里每次都要做去重处理逻辑就会越写越乱。session里存储的结构可以简化成{商品id: 数量}购物车页展示详情时再通过Product.objects.filter(id__in...)一次性取商品信息。3.3 订单状态流转不要在外层直接改字段订单状态不能只靠Order.objects.filter(id1).update(statuspaid)来改这是答辩时最容易被追问的点。原因在于订单状态有明确的生命周期直接改字段会带来两个问题一是重复支付回调时订单会被二次更新二是从已发货状态回退到待支付这种非法流转没有任何拦截。当前状态可流转到触发条件pendingpaid / canceled支付回调成功 / 用户取消paidshipped管理员发货shippedcompleted用户确认收货completed / canceled无终态建议把状态更新封装成模型方法把合法流转逻辑收敛在一个地方class Order(models.Model): # 字段定义省略 def mark_as_paid(self): if self.status ! pending: return self.status paid self.save(update_fields[status]) def mark_as_shipped(self): if self.status ! paid: raise ValueError(只有已支付订单可以发货) self.status shipped self.save(update_fields[status])这样设计的好处是视图和admin后台都只调用方法不直接修改status字段。后续如果要接入消息通知或记录操作日志也只需要在方法里加代码而不必去全局搜索哪里改了订单状态。4. URLconf与视图函数一次下单请求的完整处理链路4.1 从index.html到商品详情URL映射怎么排Django的URLconf是理解整个请求链路的入口。浏览器的每一个请求最终都会被解析成“视图函数 参数”前端模板里那些{% url product_detail product.id %}最终也是靠路由的name属性反解析出来的。from django.urls import path from . import views urlpatterns [ path(, views.index, nameindex), path(list/int:category_id/, views.product_list, nameproduct_list), path(search/, views.search, namesearch), path(detail/int:product_id/, views.product_detail, nameproduct_detail), path(user/, views.user_center, nameuser_center), path(cart/, views.cart_view, namecart_view), path(checkout/, views.checkout, namecheckout), ]int:product_id是URL转换器它会把URL里的数字部分自动转成Python的int类型传给视图函数。name参数的值在整个项目里必须唯一模板里用{% url product_detail product.id %}生成链接而不是手写/detail/3/。这样当路由路径调整时模板链接会自动跟着变不用逐个文件排查这也是django reverse resolve机制带来的核心便利。4.2 提交订单的视图事务、行锁与请求参数校验购物车结算转订单是整个系统里最需要严谨的视图。用户点了提交订单系统要做的事包括读取购物车里勾选的商品、扣减库存、创建订单和订单明细、清空购物车。任何一步失败都不能留下半截数据所以必须用事务包住整体。from django.db import transaction transaction.atomic def checkout(request): user request.user cart_items CartItem.objects.filter(useruser, selectedTrue).select_related(product) if not cart_items.exists(): return render(request, error.html, {error: 购物车为空}) order Order.objects.create(useruser, order_nogen_order_no(), total_amount0) total 0 for item in cart_items: product Product.objects.select_for_update().get(iditem.product_id) if product.stock item.quantity: raise ValueError(f{product.name}库存不足) product.stock - item.quantity product.save(update_fields[stock]) OrderItem.objects.create( orderorder, productproduct, priceproduct.price, quantityitem.quantity ) total product.price * item.quantity order.total_amount total order.save(update_fields[total_amount]) cart_items.delete() return redirect(pay, order_idorder.id)transaction.atomic保证整个函数内的SQL操作要么全部提交要么全部回滚。select_for_update()是并发控制的关键它会在数据库层面对这一行商品记录加行级锁两个用户同时提交同一件商品订单时第二个用户必须等第一个用户事务结束后才能读取库存这样能避免超卖。这里要注意select_for_update只对事务内的查询生效所以必须和atomic一起用。程序里直接raise ValueError会让事务回滚但页面显示的是500错误更完善的实现是捕获业务异常后返回友好提示。4.3 在线支付回调验签、幂等与订单状态联动支付模块是这套系统里最接近真实工程的部分。课程设计接入支付时常用的做法是接入第三方支付平台但沙箱环境里真正要处理的核心不是“怎么发起付款”而是“支付成功后平台回调时服务端怎么处理”。支付回调通常是服务器到服务器的请求可能有重复推送也可能有人伪造回调所以处理和正常视图不太一样。def pay_callback(request): # 1. 验签用官方SDK校验签名签名不过直接拒绝 if not verify_sign(request.POST): return HttpResponse(fail) order_no request.POST.get(out_trade_no) order Order.objects.filter(order_noorder_no).first() if order is None: return HttpResponse(fail) # 2. 幂等重复回调直接返回成功避免重复发货 if order.status paid: return HttpResponse(success) order.mark_as_paid() return HttpResponse(success)验签的细节是必须用平台返回的原始报文拼接后计算签名不能只比对“支付金额”和“订单号”两个字段。回调里返回fail时平台会重试所以只有在验签失败或订单不存在时才返回fail业务处理成功一律返回success。幂等处理则保证回调到达两次时第二次不会把订单状态从paid改成其他值也不会重复触发后续的发货流程。5. 模板继承与admin后台低成本改造成可答辩形态5.1 模板复用与Django admin界面美化源码里的index.html、list.html、search.html虽然页面不同但头部导航、底部信息、css引入都是重复的。课程设计里如果直接复制粘贴整份HTML后期改一个导航链接就要改五个文件。更好的做法是抽一个base.html作为基座把公共部分放进去用{% block content %}作为子页面的插槽。Django模板继承的写法是{% extends base.html %}加{% block content %}这块在答辩时讲出来比贴组件库更有说服力。Django admin界面美化也是容易出彩的点。Admin自带的后台界面比较朴素但通过重写模板或配置Django SimpleUI这类第三方组件可以快速整容。不引入额外依赖时至少要把admin的列表配置写好from django.contrib import admin from .models import Order admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display (order_no, user, status, total_amount, created_at) list_filter (status,) search_fields (order_no, user__username) actions [mark_as_shipped]list_display决定后台列表页展示哪些列list_filter在右侧加状态筛选search_fields支持按订单号或用户名检索。管理员在后台改订单状态到“已发货”靠的就是这里配置的可操作项。5.2 改代码前先验证查询性能商城系统课程设计跑起来很容易但列表页在商品数据量上去之后会变慢问题通常出在ORM查询上。商品列表中每件商品都要展示分类名如果直接在模板里写{{ product.category.name }}Django会对每件商品单独发一条SQL查询去取分类这就是N1查询问题。在视图里加select_related(category)可以一次性用JOIN查出关联数据列表接口的性能会立刻上升一个档次。订单详情页展示商品快照时在OrderItem查询上加prefetch_related(items__product)也可以避免逐条查询。迁移完数据后在Explain分析里确认product表的id主键、OrderItem表的order_id外键都建了索引再把session_cart的合并逻辑和支付回调的幂等校验各写一条冒烟用例跑通这套商城系统才算真正达到可以直接提交的水平。本文还有配套的精品资源点击获取
返回列表