用Django实现小区果蔬预定系统:建模、事务、Admin后台全解析 📅 发布时间:2026/9/15 18:24:23 👁 浏览次数: 简介一套基于Python Django框架与MySQL数据库的小区果蔬预定系统毕业设计资源面向计算机专业毕业生和课程设计学习者可用于课题设计、论文撰写与答辩展示。压缩包共334个文件、约2.4MB以py源码与pyc编译文件为主含20个HTML模板、CSS/JS前端文件、JPG/PNG图片素材、SQL数据库脚本及说明文档结构清晰便于部署运行和二次开发。系统前台用户端实现注册登录、商品分类展示含价格、采摘日期、商品图片、商品详情、收藏、购物车提交订单、会员积分兑换菜品以及微信/支付宝等在线支付后台管理端覆盖管理员信息、用户注册、商品类别与信息、收藏、订单与评价、支付统计、配送、会员积分等模块功能比较完整。附带毕业论文和答辩PPT可帮助理解Django项目分层、ORM操作和前后台交互流程。目前已有34人学习浏览适合需要快速上手同类毕设项目或进行功能扩展的读者。1. 小区果蔬预定系统到底在预定什么先定义订单边界小区果蔬预定本质是今天下单、明天提货的预售模型不是外卖式的即时履约。用户选好当季果蔬系统在截单前汇总订单次日按自提点发放。这个模型里最难的不是页面而是订单状态流转、库存扣减和价格锁定。用 Django 做这套系统admin 后台开箱即用模型关系清晰正好满足毕业论文加 PPT 的完整度要求。这篇内容按建模→下单→后台→验收的顺序从建 app 讲到上线前检查给出可照抄的 models、事务下单函数、admin 配置和验收脚本新手能一路跟下来熟手重点看第 3 章的事务和第 4 章的查询优化。2. 小区果蔬预定系统的数据模型Member、Product、Order 怎么建模2.1 先 startapp 拆应用再决定表归属常见做法是把项目拆成两个应用users管小区住户orders管商品、订单和明细。Member属于用户域Product和Order之间依赖紧密放在同一个应用里迁移顺序和 admin 注册都简单。预定系统业务量不大拆到四五个 app 会让 migrations 互相引用答辩时介绍成本反而高。环境准备按常规来就行python manage.py startapp orders建应用users同理。数据库侧开发用 SQLite、上线换 MySQL 是最常见搭配换 MySQL 前先确认pip install mysqlclient能装成功Debian/Ubuntu 上缺编译头文件时先apt install default-libmysqlclient-dev。python manage.py startapp orders python manage.py startapp users这两条命令生成orders/和users/目录骨架。关键一步在settings.py把两个 app 加进INSTALLED_APPS否则makemigrations扫不到模型文件命令也建不出任何表。目录结构上不需要额外建包models.py、views.py沿用 Django 默认布局对毕业论文的目录树展示最友好。2.2 金额用 DecimalField预定价必须做快照建模第一条原则钱不用 FloatField。浮点数的 0.10.2 误差在订单累计、退款计算时会扩散成对不上的账预定单要算总额、要退款金额字段一律DecimalField(max_digits8, decimal_places2)。第二条原则是价格与品名快照商品涨价后历史订单不能被连带改动OrderItem里存下单那一刻的price和product_name。这个快照设计是预定系统区别于普通购物车的地方。购物车是临时单据随时可以重新取价预定单是在途单据它的金额和品名属于下单瞬间的那笔交易。之后做报表、做对账都从OrderItem取值不再回头关联Product现算。from django.db import models from django.utils import timezone class Member(models.Model): name models.CharField(姓名, max_length32) phone models.CharField(手机号, max_length11, uniqueTrue) building models.CharField(楼栋门牌, max_length64) created_at models.DateTimeField(auto_now_addTrue) class Product(models.Model): name models.CharField(品名, max_length64) unit models.CharField(单位, max_length8, default斤) price models.DecimalField(单价, max_digits8, decimal_places2) stock models.PositiveIntegerField(可预定库存, default0) class Order(models.Model): class Status(models.TextChoices): PENDING pending, 已预定 CONFIRMED confirmed, 已确认 COMPLETED completed, 已提货 CANCELLED cancelled, 已取消 order_no models.CharField(订单号, max_length32, uniqueTrue) member models.ForeignKey(Member, on_deletemodels.PROTECT, related_nameorders) delivery_date models.DateField(提货日期, db_indexTrue) total_amount models.DecimalField(订单总额, max_digits8, decimal_places2, default0) status models.CharField(状态, max_length16, choicesStatus.choices, defaultStatus.PENDING) created_at models.DateTimeField(下单时间, defaulttimezone.now) remark models.CharField(备注, max_length255, blankTrue) class OrderItem(models.Model): order models.ForeignKey(Order, on_deletemodels.CASCADE, related_nameitems) product models.ForeignKey(Product, on_deletemodels.PROTECT) product_name models.CharField(品名快照, max_length64) price models.DecimalField(单价快照, max_digits8, decimal_places2) quantity models.PositiveIntegerField(数量)模型参数最值得说明的是三处Status用TextChoices而不是数字常量pending、confirmed这种字符串状态在数据库里可读在日志和 admin 筛选器里也不需要用映射表猜含义delivery_date加db_indexTrue因为后台列表默认按提货日筛选索引直接决定列表页响应速度on_delete策略分两种——OrderItem.order用CASCADE删测试单时明细一起清掉Product和Member用PROTECT被订单引用过的商品和住户不允许删除保证单据外键永远指向真实数据。2.3 索引、ordering 与孤儿数据order_no的uniqueTrue是幂等底线。下单接口被重复提交时靠订单号冲突抛出的IntegrityError拦截第二笔而不是靠 if 判断。order.items.all()通过related_nameitems提供明细访问反向查询不需要自己写filter(order...)。常见误用是给Product也配CASCADE导致删除商品时历史订单明细被静默清空月度对账时才发现金额对不上。PROTECT的代价是删除被引用的商品会抛ProtectedError这正是想要的反馈先下架、再处理引用最后才删。字段类型关键约束设计意图order_noCharFielduniqueTrue下单幂等键memberForeignKeyPROTECT住户不可误删delivery_dateDateFielddb_indexTrue提货日筛选热路径statusCharFieldTextChoices状态机入口total_amountDecimalFielddefault0与明细合计保持一致提示Product后续要加分类、产地、上架日期时直接在models.py追加字段再makemigrations别为了省事把字段塞进OrderItem快照语义和商品属性不该混在一张表里。3. 预定下单核心流程事务、行锁与状态机3.1 为什么商品表要用 select_for_update 而不是随便查一下预定库存的扣减必须正确。两个人同时提交最后一斤菜的订单时如果各自先查 stock 再更新后提交的会把库存扣成负数。解决办法是把整个下单过程放在transaction.atomic()里并对Product行执行select_for_update()加锁。被锁住的记录在事务提交前不可读改第二个请求只能排队拿到的 stock 就是第一名减完之后的值。select_for_update只在数据库事务内生效所以必须和atomic配套使用。锁的粒度是商品行而非整张表两个订单只要不是同一款商品就能并行执行吞吐量不受影响。这就是行锁比表锁更适合果蔬预定的原因——高峰期的并发集中在少数几款特价菜上锁不到无关商品。3.2 create_order 函数一个事务完成建单、快照、扣库存from decimal import Decimal from django.db import transaction from django.utils import timezone # 入参: member_id, delivery_date, items[(product_id, quantity), ...] transaction.atomic def create_order(member_id, delivery_date, items): order Order.objects.create( order_notimezone.now().strftime(%Y%m%d%H%M%S), member_idmember_id, delivery_datedelivery_date, ) total Decimal(0.00) for product_id, quantity in items: product Product.objects.select_for_update().get(pkproduct_id) if product.stock quantity: raise ValueError(f{product.name} 库存不足剩余 {product.stock}) OrderItem.objects.create( orderorder, productproduct, product_nameproduct.name, priceproduct.price, quantityquantity, ) product.stock - quantity product.save(update_fields[stock]) total product.price * quantity order.total_amount total order.save(update_fields[total_amount]) return order逻辑说明先建Order头然后逐行加锁取商品、校验库存、写明细快照、扣减 stock全部明细处理完再回填总额。任何一个商品库存不足时抛ValueErrortransaction.atomic会把前面建好的订单头、已写入的明细连同库存扣减全部回滚不会留下只差一个明细的半截订单。参数与实现要点update_fields[stock]只更新变更列减少整行写带来的锁等待items入参约定为(product_id, quantity)元组列表视图层负责格式校验服务层专注业务订单号用时间戳拼接只是演示并发高时建议拼上随机串撞号会由uniqueTrue兜底抛IntegrityError。有说法用F(stock)做原子扣减它能防超卖但拿不回扣减后的值做其他业务判断在行锁保护下直接读-改-写反而更直观。3.3 状态不乱跳TRANSITIONS 显式状态表订单状态如果允许随意赋值后面会出现把人从已提货改回已确认这类脏数据。把合法迁移关系写进一张表所有状态变更都收口到一个函数TRANSITIONS { Order.Status.PENDING: {Order.Status.CONFIRMED, Order.Status.CANCELLED}, Order.Status.CONFIRMED: {Order.Status.COMPLETED, Order.Status.CANCELLED}, Order.Status.COMPLETED: set(), Order.Status.CANCELLED: set(), } def transition_order(order, target): if target not in TRANSITIONS[order.status]: raise ValueError(f非法流转: {order.status} - {target}) order.status target order.save(update_fields[status]) return orderTRANSITIONS的键是当前状态值是允许迁移到的目标状态集合。COMPLETED和CANCELLED的set()表示终态不可再动。取消预定只允许发生在PENDING和CONFIRMED阶段提货之后退款走线下流程状态模型不做回退。admin 里的批量确认、视图里的取消接口都调transition_order合法性校验只有一份不会出现网页上能取消、接口里报错的割裂。当前状态可迁移到说明pending 已预定confirmed / cancelled截单后批量确认confirmed 已确认completed / cancelled提货日前允许取消completed 已提货无终态cancelled 已取消无终态需要审计操作时间时在Order上加updated_at models.DateTimeField(auto_nowTrue)transition_order里一并save(update_fields[status, updated_at])。注意auto_now在字段被包含进update_fields时才会刷新漏写它会发现时间不更新。4. Django Admin 后台定制把预定单列表做成团长能操作的台子4.1 list_display 与 list_filter列表页一眼看到今天要发多少菜小区团长不是开发者默认的 Django admin 列表对她们来说信息密度太低。OrderAdmin第一件要做的事是配置list_display把订单号、住户、提货日、金额、状态平铺在同一行list_filter按status和delivery_date生成筛选器search_fields覆盖订单号和手机号住户打电话来查单时直接输入后四位号码就能命中。from django.contrib import admin from .models import Member, Product, Order, OrderItem admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display [order_no, member, delivery_date, total_amount, status, created_at] list_filter [status, delivery_date] search_fields [order_no, member__name, member__phone] list_per_page 20 date_hierarchy delivery_date ordering [-delivery_date, order_no]参数说明member__name是跨外键的查找字段Django 会自动生成 JOIN搜索姓氏、手机号都能命中date_hierarchy在列表页顶部生成按日、月、年逐级下钻的导航查6 月 1 号提货的单比手选筛选器快得多ordering [-delivery_date, order_no]让列表默认按提货日倒序展示团长打开后台看到的第一屏就是最近要发货的单。list_per_page控制分页大小订单量上来后默认 100 条一页会明显变慢。4.2 action 批量确认订单一次处理一整天预订单admin 的默认 action 只有一个删除对预定系统太危险。注册一个批量确认 action把选中的PENDING订单统一流转到CONFIRMEDadmin.action(description批量确认选中的预定单) def confirm_orders(modeladmin, request, queryset): for order in queryset.filter(statusOrder.Status.PENDING): transition_order(order, Order.Status.CONFIRMED) class OrderAdmin(admin.ModelAdmin): actions [confirm_orders] # list_display 等配置同 4.1此处省略queryset是后台列表里被勾选的对象集合。先filter(statusOrder.Status.PENDING)把已确认、已完成、已取消的单全部滤掉避免transition_order抛非法流转异常中断整个批量操作。循环不包事务批量场景下单条失败就报错提示已成功的保留符合能确认多少确认多少的操作直觉。action 的描述文案会直接显示在下拉框里语义要写给团长看不写技术术语。4.3 列表卡顿与 admin 主题美化订单列表每行都会访问member和items的关联数据。Django 默认惰性加载渲染 20 行订单会触发 20N 条 SQL列表页就卡了。重写get_queryset一次带出全部关联def get_queryset(self, request): qs super().get_queryset(request) return qs.select_related(member).prefetch_related(items)select_related(member)通过 JOIN 一次查出住户prefetch_related(items)额外执行一条IN查询取出所有明细并按外键分组缓存。在自定义列里访问obj.items.all()时不再打数据库。若后续要拆前后端分离这套get_queryset优化思路原样搬进 DRF 的ViewSet.get_queryset同样有效。admin 美化是答辩最常被问的一题。默认样式功能完整但偏朴素常见做法是装django-simpleui替换 admin 的模板入口提供侧边栏、卡片式布局和图表页全程不改业务代码。不想引依赖的话直接建admin.css覆盖背景、表格行距和按钮配色配置class Media引入即可。配置项作用预定系统里的建议值list_display列表列order_no、member、status 等list_filter筛选器status、delivery_datesearch_fields搜索字段order_no、手机号date_hierarchy日期下钻delivery_dateactions批量操作批量确认、批量导出list_per_page分页205. 用 manage.py shell 脚本做上线前验收4 条命令查完预定链路5.1 三行命令验四个环节答辩或上线前手工点页面验证容易漏。把验收写成脚本跑一遍输出明确通过/失败。建一个verify.pyfrom orders.models import Member, Product, Order, OrderItem from orders.services import create_order, transition_order member Member.objects.get(phone13800000000) p1, _ Product.objects.update_or_create( name西红柿, defaults{price: 5.0, stock: 10}) order create_order(member.id, 2025-06-01, [(p1.id, 3)]) assert order.total_amount 15.0 assert OrderItem.objects.get(orderorder).price 5.0 transition_order(order, Order.Status.CONFIRMED) print(PASS: create_order, snapshot, transition)连跑三条命令验收python manage.py makemigrations --check --dry-run检查模型和迁移是否同步python manage.py check --deploy检查DEBUG、ALLOWED_HOSTS等上线配置python manage.py shell verify.py跑通业务主链路。四件事分别对应表结构一致、部署配置合规、下单金额正确、状态流转合法全绿才算验收通过。测试数据别留在库里用完执行Order.objects.filter(membermember, delivery_date2025-06-01).delete()CASCADE会把明细一并清掉。这四条命令每次改动模型或下单逻辑后重跑一遍改坏了能立刻定位到是数据库层还是业务层的问题比对着页面一个个点过去靠谱得多。本文还有配套的精品资源点击获取