ARTICLE DETAIL

资讯详情

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

Django农产品商城:权限模型与订单状态流转实践

Django农产品商城:权限模型与订单状态流转实践 简介针对Python学习者与计算机专业毕设学生这份基于Django的农产品销售系统是一套可直接运行的项目包覆盖前端页面、后端逻辑与数据库设计适用于课程设计、期末大作业或毕业设计参考。包内共558个文件包含51个Python源码文件、99个Vue组件、159个SVG图标及CSS/JS等前端资源另有2个SQL脚本用于初始化数据库并附bat一键安装与运行脚本压缩包整体25.12MB目录结构清晰便于按模块查阅。功能上实现个人中心、用户管理、商家管理、产品类型管理、农产品管理、系统管理、订单管理等模块演示视频与逻辑讲解可帮助快速理解项目框架与业务流程。现有152人学习下载作为可直接落地的完整方案适合需要快速上手或二次开发的初学者与毕业生。1. 农产品销售系统Django 权限模型与订单流水的一次完整拆解这套基于 Python Django 的农产品销售系统表面上是「用户买、商家卖」的双端商城实际核心难点在于三套权限边界用户只能下单和评价商家只能维护自己名下的商品与库存管理员则要同时看到订单流水和全量商品数据。多数毕业设计项目在这一层会直接写成 if 判断散落在视图里这套系统的做法是把角色行为收敛到模型层用 Django 的 user 关联和分组权限来控制数据可见范围这也是它值得拆开看的地方。系统后端用 Django 搭建MySQL 负责持久化前端是 Django 模板渲染配合 Bootstrap 做布局。功能面覆盖商品按类型筛选、关键词检索、购物车下单、订单状态流转、商家后台商品管理和管理员端的全量审核。适合做课程设计、期末大作业也适合刚接触 Django 的开发者拿来做权限设计和多表关联的练习样本。下面按照模型设计、检索查询、订单流转、MySQL 配置、联调避坑五个维度拆解每段都给出可以直接粘进项目里改的代码。2. 模型设计商家、农产品与多级类目的关联方式Django 项目的第一个分水岭不是路由写得有多漂亮而是 models.py 里外键和关联字段怎么落。这套系统里比较关键的有三张表商家表关联到 Django 自带的 User农产品表关联到商家和产品类型订单表同时关联用户和商品明细。很多人写商城会把商品直接挂在 User 上导致后续查询角色边界时要靠if user.is_staff来硬切这里先把商户身份独立成表后续权限判断会干净很多。from django.db import models from django.contrib.auth.models import User class Merchant(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE, verbose_name关联账号) shop_name models.CharField(max_length120, verbose_name店铺名称) phone models.CharField(max_length20) audit_status models.IntegerField(default0, verbose_name审核状态:0待审1通过2拒绝) class Meta: db_table t_merchant class ProductType(models.Model): name models.CharField(max_length60, verbose_name类型名) parent models.ForeignKey(self, nullTrue, blankTrue, on_deletemodels.CASCADE, verbose_name父类型) class Meta: db_table t_product_type class Product(models.Model): merchant models.ForeignKey(Merchant, on_deletemodels.CASCADE, related_nameproducts) ptype models.ForeignKey(ProductType, on_deletemodels.PROTECT, verbose_name商品类型) title models.CharField(max_length200) cover models.ImageField(upload_toproduct/, blankTrue) price models.DecimalField(max_digits10, decimal_places2) stock models.IntegerField(default0) sales models.IntegerField(default0) status models.IntegerField(default1, verbose_name上架状态:1上架0下架) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table t_product这段代码的逻辑重点在于两个选择on_deletemodels.PROTECT和related_name。商品类型被订单和商品同时引用如果某类型下仍有商品直接删除会触发数据库外键约束报错用 PROTECT 可以把「类型删除前需先处理商品」这个业务规则交给数据库层兜底而不是在视图里手动判断。related_nameproducts的作用是让商家实例可以直接用merchant.products.all()拉到自己名下的全部商品不写这条的话 Django 默认生成product_set可读性差而且到模板里写{% for p in merchant.product_set.all %}会很绕。sales字段是典型的冗余设计商品详情页会频繁显示销量每次去订单表COUNT(*)在大数据量下会有性能问题直接在商品行维护一个计数器更合适配合 Django 的F()表达式做原子更新就能避免并发加一覆盖。商品检索是前端高频动作页面上的类型筛选和关键词搜索会拼成 Django ORM 查询关键点在多条件组合时不能丢了 status 过滤。from django.db.models import Q def product_search(request): kw request.GET.get(keyword, ).strip() type_id request.GET.get(type, ) products Product.objects.select_related(merchant, ptype).filter(status1) if kw: products products.filter(Q(title__icontainskw) | Q(merchant__shop_name__icontainskw)) if type_id.isdigit(): # 找到该类型及其所有子类型 ids [int(type_id)] childs ProductType.objects.filter(parent_idint(type_id)).values_list(id, flatTrue) ids.extend(childs) products products.filter(ptype_id__inids) products products.order_by(-created_at)[:20] ...select_related在这里是必须的。列表页每张卡片要展示店铺名和商品类型不带select_related时每个商品会额外发一次 SQL 去查商家和类型20 条数据就是 41 条 SQL数据量再大一点直接拖垮开发机。Q(title__icontainskw) | Q(merchant__shop_name__icontainskw)实现了跨表模糊检索商品标题命中和店铺名命中都算匹配。类型筛选用ptype_id__inids是因为农产品类目经常有层级用户点了「水果」就要能看到「苹果」「柑橘」这些子分类下的商品只按当前类型过滤会漏数据。排序用-created_at新品在前这是商城列表的惯例如果后面加运营需求要按销量排可以改成order_by(-sales, -created_at)双字段排序销量相同的时候把新品顶上来。3. 订单流转状态机设计与库存扣减的常见写法订单模块是这套系统里业务规则最密集的地方核心是三个状态待付款、待发货、待收货加上取消和完成两个终态。状态流转不能随手在视图里写if 订单状态 1: 改成 2状态一多会乱。常见做法是把状态定义成常量再在 Order 模型里放一个专门的方法来驱动流转。class Order(models.Model): STATUS_CHOICES ( (0, 已取消), (1, 待付款), (2, 待发货), (3, 待收货), (4, 已完成), ) order_no models.CharField(max_length40, uniqueTrue) user models.ForeignKey(User, on_deletemodels.CASCADE, related_nameorders) merchant models.ForeignKey(Merchant, on_deletemodels.CASCADE) total_price models.DecimalField(max_digits10, decimal_places2) status models.IntegerField(choicesSTATUS_CHOICES, default1) created_at models.DateTimeField(auto_now_addTrue) def transit(self, user, to_status): floor {1: 2, 2: 3, 3: 4} if self.merchant.user_id user.id and floor.get(self.status) to_status: self.status to_status self.save(update_fields[status]) return True return False这个方法把「谁在什么状态下能改成什么状态」收敛到一个地方。floor.get(self.status) to_status保证状态只能从 1 到 2、2 到 3、3 到 4 单向推进不能从待付款直接跳到已完成也不能从待收货回退到待发货。update_fields指定只更新 status 列避免每次状态变更都重写整行数据。用户支付后真正容易出事的是库存扣减。直接product.stock - 1; product.save()在并发下会丢更新两个请求同时读到 stock 10各自减一后都写回 9实际卖了两单库存只扣了一。Django 里正确做法是用F()表达式把加减操作下推到数据库执行避免「读-改-写」中间态。from django.db.models import F def pay_order(request, order_id): order Order.objects.select_for_update().get(idorder_id, userrequest.user) if order.status ! 1: return JsonResponse({code: 1, msg: 订单状态已变更}) items OrderItem.objects.filter(orderorder) for item in items: updated Product.objects.filter(iditem.product_id, stock__gteitem.quantity).update( stockF(stock) - item.quantity, salesF(sales) item.quantity ) if not updated: return JsonResponse({code: 2, msg: f{item.product.title} 库存不足}) order.status 2 order.save(update_fields[status]) return JsonResponse({code: 0})select_for_update()会给订单行加行级锁防止同一订单被并发支付两次。库存扣减的关键在filter(id..., stock__gteitem.quantity)这个条件update 不是查出来改而是让数据库在 stock 满足大于等于购买数量的前提下执行原子减操作返回的updated是受影响行数等于 0 就说明库存不够或商品已下架。这个写法比先get再判断再 save 的优势是少一次往返且不存在竞态窗口。订单模块还涉及一个容易忽略的字段order_no。业务上一个订单号要能承载用户 ID、日期和随机序列这块在视图里生成比较好。import datetime, random def gen_order_no(user_id): now datetime.datetime.now() seq random.randint(1000, 9999) return f{now:%Y%m%d%H%M%S}{user_id:04d}{seq}订单号用「时间戳 用户ID补零 四位随机数」的组合长度可控也方便运营在后台看订单创建时间和归属用户。不要直接用时间戳当订单号同一秒多个订单会撞。4. MySQL 配置与迁移mysqlclient 编译问题与字符集坑Django 项目连 MySQL 时第一个拦路虎是驱动安装。Django 3.x 之后默认不再绑定 MySQLdb需要安装mysqlclient或者pymysql。mysqlclient 在 Windows 上一不小心就报error: Microsoft Visual C 14.0 is required这是因为驱动底层有 C 扩展需要编译。这台机器的处理方式是安装后运行1-install.bat去补环境依赖这就是为什么要先跑安装脚本再启动项目。pip install django mysqlclient2.0.4如果编译一直失败换成pymysql也是合规方案在项目__init__.py里写两行即可import pymysql pymysql.install_as_MySQLdb()装好驱动后settings.py的数据库配置直接用 MySQL 引擎DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: farm_mall, USER: root, PASSWORD: 你的密码, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, }, } }数据库建好后执行迁移建议按此顺序操作mysql -uroot -p -e CREATE DATABASE farm_mall DEFAULT CHARACTER SET utf8mb4; python manage.py makemigrations python manage.py migrate python manage.py createsuperuser字符集必须显式指定为utf8mb4否则商品描述里一出现 Emoji 或者特殊符号直接报Incorrect string value。以前的项目用 utf8 没问题是因为农产品描述里没表情一旦有人把商品详情写成「新鲜」就会触发错误。charset这个参数是在 Django 连接 MySQL 时强制指定还能避免 MySQL 服务端默认字符集与项目不一致导致的乱码。数据库初始化在这个包里是一个单独的初始化hive数据库.bat名字虽然挂着 hive实际执行的就是建库和导入 SQL 文件两件事。跟 hive 没有关系当初可能是从其他项目模板改过来的命名跑之前先确认里面的 SQL 文件路径与当前目录一致。建议在这里手动确认一下三个信息MySQL root 口令是否匹配、SQL 文件路径是否包含中文目录导致编码问题、manage.py所在路径是否在 bat 的cd里。启动服务时先跑运行.bat这段批处理做的事是python manage.py runserver 0.0.0.0:8000。用0.0.0.0表示局域网共享访问方便手机扫码测试页面在窄屏下的表现。后台管理入口是/admin超级管理员是用上面命令创建的账号登录。5. 联调技巧演示数据加载顺序与后台管理界面配置Django 这套系统在最终答辩演示时最影响观感的两件事是演示数据是否齐全、后台管理界面是否顺手。先讲数据加载顺序。项目里预设了商家账号和商品条目但直接跑python manage.py runserver然后点登录大概率看到的是空列表原因是数据库初始化脚本里的数据没有加载。正确顺序是先建库、再导入 SQL、再启动服务。SQL 脚本里通常有INSERT INTO语句带着默认密码的哈希值导入后直接用脚本里标注的账号密码登录即可。另外注意主键自增如果 SQL 文件里手写了 id导入后再新增商家MySQL 的自增基准是 max(id)1不会冲突不需要额外处理。后台管理界面默认很素Django admin 的列表页默认只显示__str__的返回值这对展示商品信息不利需要注册时指定list_display# admin.py from django.contrib import admin from .models import Product, Order, Merchant, ProductType admin.register(Product) class ProductAdmin(admin.ModelAdmin): list_display [id, title, price, stock, status, merchant] list_filter [status, ptype] search_fields [title] admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display [order_no, user, merchant, grand_total, status, created_at] list_editable [status]list_editable [status]是一个演示阶段很实用的配置。答辩现场如果要演示「商家发货」这个动作直接在后台列表页下拉框里改状态再点保存比进入详情页点按钮更直观。演示前先检查orders/templates下的模板里订单状态字段有没有中文映射如果直接显示数字 1、2说明模板里的get_status_display没有调用改一下就好。启动服务后可以做一个快速健康检查访问首页看商品能否按分类展开登录商家账号看能否管理自己名下的商品登录普通用户账号走一遍加购-下单-支付-确认收货的完整链路。这三个动作写成一个 30 秒的手工回归清单演示前跑一遍能规避九成以上现场翻车。本文还有配套的精品资源点击获取
返回列表