1. 为什么毕业设计选题锁定了Django小型超市管理系统
1.1 经典题目的价值重估
每年到了毕设季,就会看到大量“xx管理系统”的选题汇总,超市管理系统在其中永远占一席之地。很多人觉得这题目太老、太普通,但实际上越是这种经典题目,越能见真功夫。超市管理系统的好处在于业务场景极其清晰:商品、库存、订单、会员、收银、报表,每一样都是现实中能直接对应的概念,评委不需要你花五分钟解释系统背景,演示到哪个模块他一眼就知道在做什么。
真正的问题不是题目老,而是绝大多数人把它做成了增删改查的堆砌。商品表加个表单、订单表加个列表、会员表再套个模板,做完连自己都不想点开第二遍。而Django做这道题,天生就有优势:内置Admin后台、ORM、认证系统、模板引擎、分页、中间件,几乎把毕业设计需要的基础设施全部准备好了。你要做的不是从零搭轮子,而是把业务逻辑和亮点做出来。
我辅导过几个同学用Django做这个题目,最快的一个人从搭建环境到跑通核心流程只花了三个晚上。当然前提是你真的理解了Django的运行机制,而不是靠复制粘贴凑功能。如果你正愁选题,或者已经选了这道题但不知道从哪下手,这篇内容应该能帮你把整条路线捋清楚。
1.2 Django凭什么最省心
先聊聊技术选型。同样做超市管理系统,常见的选择还有SSM、SpringBoot、PHP等。“技术栈选型”这一理由。
Django的核心优势可以总结成五个点:
- 自带Admin后台:注册一下模型,商品管理、订单管理、会员管理的后台页面就出来了,不需要自己写list页面就能快速做数据维护。
- ORM非常成熟:模型定义好之后,数据库表结构自动生成,迁移机制让表结构变化可控,不用手工维护SQL脚本。
- 内置认证体系:用户表、登录、登出、密码哈希、session、权限装饰器,这些开箱即用,省掉一大半安全功能的重复开发。
- 模板与DRF双路线:传统Django可以服务端渲染页面,配合Ajax做部分交互;想升级成前后端分离,也有Django REST Framework可以接API。
- 社区活跃、资料好搜:遇到问题搜“django xxx”,基本都有答案,这点对毕设阶段非常友好。
对比一下Java系框架,SpringBoot本身也不差,但配置项多、工程结构重,对很多非科班同学来说光是理解IOC和MVC就够喝一壶。而Python的语法简单,Django的开发节奏是“写模型 -> 写视图 -> 写模板”三步走,逻辑直白。毕设周期通常只有几个月,用Django可以把更多时间留给功能打磨和演示准备,而不是耗在环境配置和框架理解上。
当然,选Django也有代价:并发性能不如Go或Java、部署时Python环境略麻烦。但毕业设计的体量压根到不了讨论并发瓶颈的程度。如果老师说“能不能支撑几百人同时使用”,你就反问一句“日订单量万级的电商系统才需要考虑集群,我们这套超市管理系统的并发峰值在哪里”,基本就能把这个话题收住了。
2. 从需求梳理到数据库建模:项目怎么搭骨架
2.1 功能需求范围界定
拿到题目先别急着新建项目,第一件事是画出功能边界。很多同学一上来就想做“功能大全”,供应商管理、调拨单、盘点单、多门店、优惠券、秒杀……全塞进去,结果做了两个月什么都半成品,答辩时每个功能都只能切个页面,一问细节就露怯。
我建议的最小功能集就六块:
- 商品管理:分类、品牌、价格、上下架、库存量
- 收银台:购物车、结算、生成订单
- 订单管理:订单状态流转、订单详情、退款/取消
- 库存管理:入库、出库、库存预警
- 会员管理:开卡、积分、会员折扣
- 统计报表:销售日报、商品销量排行、库存预警列表
这个范围看起来不大,但每个模块都能讲出设计逻辑,足够撑起一篇完整的毕业设计论文。想加分的话,再加两个小亮点,比如操作日志审计、数据导出Excel、库存变动流水。这几个功能都不复杂,但能让答辩老师觉得你考虑了真实业务场景。
真正不建议做的,是多门店和复杂的促销引擎。多门店意味着几乎所有表都要加门店字段,库存、订单、报表都要按门店拆分,工作量翻倍。促销引擎看着简单,实际涉及的时间段、折扣叠加、优惠券互斥,能把人绕晕,而且很难在答辩现场演示完整。
2.2 创建项目与核心App划分
功能边界清晰之后,就可以开始搭工程骨架了。打开终端,按下面的顺序执行:
python -m venv venv source venv/bin/activate # Windows用 venv\Scripts\activate pip install django django-admin startproject supershop cd supershop python manage.py startapp goods python manage.py startapp orders python manage.py startapp users python manage.py startapp dashboard这里有几个细节值得说:虚拟环境一定要用,不然几个项目之间的依赖会互相污染。项目名我用的是supershop,你可以换成自己的名字,但建议用纯小写字母加下划线,不要用中文或横线。App划分按领域来,goods放商品和分类,orders放购物车和订单,users放会员与员工账号,dashboard放统计报表和仪表盘。千万别按“前台、后台”来拆App,那会让关联查询变得很别扭。
新建完App后,记得去settings.py把应用名注册到INSTALLED_APPS里,同时设置一下语言和时区:
LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_TZ = TrueTIME_ZONE这块几乎是必踩的坑,后面我会专门讲。USE_TZ保持True,存储时统一用UTC,展示时Django会自动转成Asia/Shanghai时间,比自己在代码里加减8小时可靠得多。
2.3 数据模型设计:像画业务流程图一样建模
模型是整个系统的地基,地基歪了后面全是缝。我先给出商品模块的核心模型代码,这是最常见的写法:
from django.db import models class Category(models.Model): name = models.CharField('分类名称', max_length=50, unique=True) parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE, verbose_name='上级分类') class Meta: verbose_name = '商品分类' verbose_name_plural = verbose_name def __str__(self): return self.name class Goods(models.Model): STATUS_CHOICES = ((0, '下架'), (1, '上架')) name = models.CharField('商品名称', max_length=128) brand = models.CharField('品牌', max_length=64, blank=True) category = models.ForeignKey(Category, on_delete=models.PROTECT, verbose_name='分类') price = models.DecimalField('售价', max_digits=10, decimal_places=2) stock = models.PositiveIntegerField('库存', default=0) sales = models.PositiveIntegerField('销量', default=0) status = models.SmallIntegerField('状态', choices=STATUS_CHOICES, default=1) created_at = models.DateTimeField('创建时间', auto_now_add=True) class Meta: verbose_name = '商品' verbose_name_plural = verbose_name def __str__(self): return self.name有几个建模要点值得展开讲:
金额字段必须用DecimalField,绝对不用FloatField。这个结论来自真实教训。二进制浮点数是近似值,0.1加0.2可能等于0.30000000000000004,在财务数据里这就是事故。DecimalField内部用十进制精确计算,配合decimal_places=2,才能保证金额不出错。这个知识点答辩时几乎是必问的,老师很爱考。
外键删除策略要按业务语义选。分类被商品引用时,应该用on_delete=models.PROTECT,让Django阻止删除还有商品挂着的分类,避免出现孤儿数据。如果图省事用CASCADE,删分类会导致所有商品连带删除,这种“手滑事件”一旦发生在演示现场,用户体验极差。
库存和销量用PositiveIntegerField。库存不可能是负数,销量同理。加正数约束是数据库层面的事,比代码里写if判断可靠得多。万一真的出现并发超卖,至少数据库会报错而不是静静存一个-5进去。
接下来是订单模块。订单头与订单明细要用一对多关系,订单模型我习惯这样设计:
from django.db import models, transaction from django.contrib.auth.models import User class Order(models.Model): STATUS = ( (0, '待支付'), (1, '已支付'), (2, '已取消'), (3, '已完成'), ) order_no = models.CharField('订单号', max_length=32, unique=True) user = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name='下单用户') total_amount = models.DecimalField('订单总额', max_digits=12, decimal_places=2) status = models.SmallIntegerField('状态', choices=STATUS, default=0) created_at = models.DateTimeField('下单时间', auto_now_add=True) class Meta: verbose_name = '订单' verbose_name_plural = verbose_name def __str__(self): return self.order_no class OrderItem(models.Model): order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items', verbose_name='所属订单') goods = models.ForeignKey(Goods, on_delete=models.PROTECT, verbose_name='商品') goods_name = models.CharField('商品快照名称', max_length=128) price = models.DecimalField('成交单价', max_digits=10, decimal_places=2) quantity = models.PositiveIntegerField('购买数量', default=1)这里我特意加了一个goods_name快照字段,解释一下为什么:商品名称和价格随时可能改,如果直接关联商品表即时取值,那历史订单里显示的可能是改过的名字和价格,对账时说不清。订单明细里保存一份下单时的名称和价格快照,才是符合真实业务的做法。这个细节说出来,答辩老师会觉得你不只是会写代码,而是懂业务。
订单号的生成也有讲究,不建议直接用自增主键当订单号,容易暴露订单量。我常用的是“时间戳 + 用户ID + 随机数”拼一个唯一字符串:
import time, random def gen_order_no(user_id): ts = time.strftime('%Y%m%d%H%M%S') rand = random.randint(1000, 9999) return f'{ts}{user_id}{rand}'模型里定义了order_no的unique=True,即使随机数撞车了,插入时数据库也会报错,不会出现重复订单号。
会员模型可以直接在Django自带的User上扩展。更简洁的方案是用Profile表加OneToOne外键:
from django.contrib.auth.models import User class MemberProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name='关联账号') phone = models.CharField('手机号', max_length=11, blank=True) points = models.IntegerField('积分', default=0) level = models.SmallIntegerField('会员等级', default=0)这样登录、权限、session全走Django自带的User体系,会员积分和等级单独扩展,互不干扰。如果直接把User表改了,后面升级Django版本时容易出兼容问题。
2.4 迁移、Admin注册和测试数据
模型写好后,执行两条命令就能建表:
python manage.py makemigrations python manage.py migratemakemigrations会生成迁移文件,migrate负责把迁移应用到数据库。这个流程一定要分开跑,养成习惯,因为以后改了模型还要再走一遍。假如迁移报错说某个字段冲突,大概率是之前改过模型没同步,用migrate --fake或者重置迁移文件就能解决,但要看具体报错,不要盲目删库。
然后去admin.py注册模型,顺便配置一下列表页,让后台真的能用:
from django.contrib import admin from .models import Category, Goods @admin.register(Category) class CategoryAdmin(admin.ModelAdmin): list_display = ('id', 'name', 'parent') @admin.register(Goods) class GoodsAdmin(admin.ModelAdmin): list_display = ('id', 'name', 'category', 'price', 'stock', 'status', 'created_at') list_filter = ('status', 'category') search_fields = ('name', 'brand')注册完Admin后,你只管创建超级用户:
python manage.py createsuperuser然后运行runserver,打开/login路径,就能看到Django自带的登录页。登录后可以进Admin后台手动加商品、加分类。这条路对快速整理演示数据特别有用。
测试数据我建议再写一个自定义management command,方便一键生成几十个商品样本。在App里新建management/commands/seed_data.py,内容大概是循环造分类、造商品、造会员。这样无论数据库被怎么折腾,只要一条python manage.py seed_data就能恢复演示数据。这个脚本本身也是论文里能写的亮点。
3. 核心业务实现:收银、库存、报表三个硬骨头
3.1 收银台:购物车与订单创建的完整链路
超市管理系统最核心的交互场景就是收银台。货架上的真实超市是扫码收款,我们系统里就体现为“把商品加入购物车 -> 结算 -> 生成订单 -> 扣减库存”。
购物车简单实现可以直接放session里,因为Django的session是独立于登录态的,用户未登录时也能用。我在views里存一个结构如下的session键:
request.session['cart'] = { '1': {'goods_id': 1, 'quantity': 2}, '3': {'goods_id': 3, 'quantity': 1}, }key是商品ID,value是购买数量。好处是不用建购物车表,数据也不落库,对毕设来说性能够用且逻辑简单;坏处是重启服务或session过期后购物车没了,但对演示场景影响不大。想升级的话可以加一张CartItem表,把购物车持久化,但工作量会增加不少,可以先不搞。
结算接口是整条链路最容易出bug的地方。以下代码是建议的写法:
from django.db import transaction from django.http import JsonResponse from django.contrib.auth.decorators import login_required @login_required @transaction.atomic def checkout(request): cart = request.session.get('cart', {}) if not cart: return JsonResponse({'ok': False, 'msg': '购物车为空'}) total = 0 items = [] for goods_id, quantity in cart.items(): goods = Goods.objects.select_for_update().get(pk=goods_id) if goods.stock < quantity: return JsonResponse({'ok': False, 'msg': f'{goods.name}库存不足'}) total += goods.price * quantity items.append((goods, quantity)) order = Order.objects.create( order_no=gen_order_no(request.user.id), user=request.user, total_amount=total, ) for goods, quantity in items: OrderItem.objects.create( order=order, goods=goods, goods_name=goods.name, price=goods.price, quantity=quantity, ) goods.stock -= quantity goods.sales += quantity goods.save() request.session['cart'] = {} return JsonResponse({'ok': True, 'order_id': order.id})这段代码里有三个关键点:
第一,事务必须包住整个结算过程。如果没有transaction.atomic,可能出现订单建好了但扣库存失败,或者扣了库存但订单没生成,两边数据对不上。面试和答辩必问“怎么保证数据一致性”,你直接抛出事务原子性操作这个概念就对了。
第二,扣库存前要用select_for_update()。这是数据库行级锁。如果没有这行锁,两个收银员同时卖同一件商品,就可能出现超卖——数据库只有10件库存,却卖出了12件。select_for_update配合事务,会让并发的第二个请求等第一个提交后再读取库存,从根上消除超卖。超卖问题是电商和高并发场景的经典话题,你在论文里提到这一点,属于专业度加分。
第三,商品价格要即时读取且计算单独写。我用的是goods.price * quantity,这里要小心:前面说过金额用DecimalField,所以乘法结果也是Decimal,不会出现浮点误差。如果你图方便把price先转了float,精度就有风险。计算总额是在循环里累加,最后再写入Order的total_amount,不要用前端传来的总价作为唯一依据,前端数据不可信,这个习惯必须养。
3.2 订单状态机:别让状态字段变成一锅粥
很多初学者的订单模块就一个status字段:0未支付、1已支付、2已发货……然后到处if判断。一旦状态变得复杂,代码会乱成一团。
我建议把所有状态定义成常量类,集中放在一个地方:
class OrderStatus: UNPAID = 0 PAID = 1 CANCELED = 2 COMPLETED = 3 REFUNDING = 4然后约定合法的状态迁移路径:
- 待支付 -> 已支付(用户完成支付)
- 待支付 -> 已取消(用户主动取消或超时未付)
- 已支付 -> 已完成(确认收货或系统自动完成)
- 已支付 -> 售后中(用户发起退款)
- 售后中 -> 已完成(退款完成)
在视图里对每一处状态修改做校验,禁止任意跳跃。比如已取消的订单不能改成已支付,已完成订单不能退款。可以写一个简单的状态流转检查函数,或者在模型层用save方法拦截。别小看这个设计,答辩老师问你“订单如果被误操作改成非法状态怎么办”,你能给出状态机的答案,就是亮点。
3.3 库存变动流水:让每一次增减都有据可查
库存字段只是一个数字,修改它必须留下痕迹。现实中超市的库存不是简单加减,每一次进货、退货、盘点、销售都要能对得上账。所以在系统里,我强烈建议加一张StockLog表:
class StockLog(models.Model): TYPE_CHOICES = ((1, '入库'), (2, '销售出库'), (3, '盘点调整'), (4, '退货入库')) goods = models.ForeignKey(Goods, on_delete=models.PROTECT, verbose_name='商品') change = models.IntegerField('变动数量', default=0) type = models.SmallIntegerField('变动类型', choices=TYPE_CHOICES) remark = models.CharField('备注', max_length=255, blank=True) created_at = models.DateTimeField('变动时间', auto_now_add=True) operator = models.ForeignKey(User, null=True, on_delete=models.SET_NULL, verbose_name='操作人')每次增减库存时,除了改Goods.stock字段,还要同时插入一条StockLog记录。这样库存少了10件,你能查到是因为销售卖了10件,还是因为盘点时发现丢了10件。这个设计对毕业设计的管理系统来说,算得上“超出预期”的细节,老师看了会觉得你考虑得很周全。
3.4 报表统计:用好ORM聚合,告别Python循环
超市管理系统如果没有报表模块,说服力直接减半。但报表不等于把订单列表拉出来人工统计,那太原始了。Django的ORM聚合函数才是正解。
看一个销售日报的写法:
from django.db.models import Sum, Count from django.db.models.functions import TruncDate def daily_sales_report(request): report = (OrderItem.objects .annotate(day=TruncDate('order__created_at')) .values('day') .annotate(total_amount=Sum('price' * models.F('quantity'))) .annotate(total_quantity=Sum('quantity')) .annotate(order_count=Count('order', distinct=True)) .order_by('-day')) return render(request, 'dashboard/report.html', {'report': report})这里用了F表达式,让数据库在SQL层完成单价乘数量的计算,而不是取回Python一个个相乘。TruncDate会把时间戳截断到天,values('day')按天分组,Sum求和,Count计数,一条SQL语句就把一天的销售金额、销售件数、订单数全部算出来,比用循环快几个数量级。
商品销量排行也很简单:
top_goods = (Goods.objects .annotate(total_amount=Sum(F('price') * F('orderitem__quantity'))) .order_by('-total_amount')[:10])这段代码会自然关联OrderItem表,按成交总额倒序,取前十名。我对每个学生都强调:报表模块不需要你自己造轮子,Django的aggregate和annotate组合就是干这个的。你只要花半小时搞懂分组聚合的思路,就能写出很漂亮的统计代码。
4. 会话权限与实时推送:让系统真正“活”起来
4.1 登录、角色与权限:不要只用装饰器
Django自带的认证系统已经非常完整,但很多人只会用login_required,其他全靠前端页面判断。这不够。
最简单的角色区分方案是在User上扩展一个user_type字段,或者在Group里创建“收银员”“店长”“管理员”三个组,然后把权限分配到组上:
python manage.py shell >>> from django.contrib.auth.models import Group >>> Group.objects.create(name='收银员') >>> Group.objects.create(name='店长')视图层用装饰器判断:
from django.contrib.auth.decorators import login_required, permission_required @login_required @permission_required('orders.export_excel', raise_exception=True) def export_orders(request): ...权限校验的原则是:页面按钮隐藏不叫权限控制,视图层是否放行才算。即使界面做好了“店长才能看到导出按钮”,如果view里不校验,用户直接手动访问导出URL照样能执行。所以安全控制必须放在后端,前端只是体验优化。
4.2 Cookie、Session与Token:三者的取舍
Django传统流程里,用户登录后session会存在数据库(django_session表),浏览器cookie里只存一个sessionid。每次请求浏览器带上sessionid,Django根据它找到会话数据。这套方案对服务端渲染网页非常成熟,你不需要额外写token。
但如果后期想加一个小程序端或App端,就需要做API,这时推荐用DRF加SimpleJWT方案:
pip install djangorestframework djangorestframework-simplejwt配置后写一个登录接口:
from rest_framework_simplejwt.views import TokenObtainPairView urlpatterns = [ path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/refresh/', TokenRefreshView.as_view(), name='token_refresh'), ]JWT的好处是无状态,服务端不用存session,扩展性好;缺点是token一旦泄露,在过期前无法单独吊销(除非额外维护黑名单)。对毕设而言,如果只做网页端,完全不需要引入JWT,session方案足够了。给读者一个建议:不要为了“新”去强行用JWT,技术选型要匹配业务体量。
4.3 后台数据变化如何推送到前端:轮询和WebSocket
“django websocket实现后台有数据前端推送”是经常被搜的问题,这里可以展开说。场景通常是:新订单进来、库存达到预警线,前端页面想实时收到提醒,而不是靠用户手动刷新。
最轻量的方案是短轮询。前端每隔几秒用Ajax请求一个接口,接口返回最新的订单数或库存预警数,有变化就更新页面。实现成本极低,不需要任何额外依赖:
setInterval(() => { fetch('/api/dashboard-alert/') .then(res => res.json()) .then(data => { if (data.low_stock_count > 0) { document.getElementById('alert-box').innerText = `有${data.low_stock_count}种商品库存预警`; } }); }, 5000);短轮询的缺点是实时性有限,间隔短了又费请求。对毕设来说,这个方案完全够用,而且容易解释。如果想展示更强的技术实力,可以用Django Channels实现WebSocket。
Channels的大致步骤是:
pip install channels在settings.py里注册channels,配置ASGI_APPLICATION,然后写一个consumers.py,处理WebSocket连接和消息转发。当后台触发保存信号时(比如订单创建),通过channel layer给前端推送:
from channels.layers import get_channel_layer from asgiref.sync import async_to_sync channel_layer = get_channel_layer() async_to_sync(channel_layer.group_send)( 'dashboard_group', {'type': 'order_push', 'message': '新订单已生成'} )前端用WebSocket对象接收消息,实时渲染。这个方案在答辩现场效果很炸,钱能当场看到订单进来后页面自动跳出一个提示。但要提醒:Channels的学习曲线比短轮询陡不少,建议先把核心业务做扎实再考虑加这个。而且部署到生产环境还需要额外配置Redis作为channel layer,纯本地演示可以用InMemoryChannelLayer顶着。
5. 性能、安全与部署:答辩时不翻车的底线
5.1 ORM查询优化:先解决N+1问题
毕设项目数据量不大,但不代表可以随便写查询。最大的坑是N+1查询。比如在商品列表页展示每个商品的分类名称,如果写成:
goods_list = Goods.objects.all() for goods in goods_list: print(goods.category.name) # 每次都额外查询一次分类表N个商品就会额外发出N条查询,页面打开慢,数据库日志刷屏。解决办法是加select_related:
goods_list = Goods.objects.select_related('category').all()select_related会把外键关联的表通过JOIN一次性查出来,这样后面访问category.name就不需要再查库。对于多对多或反向外键,则用prefetch_related。这个知识点我在论文答辩准备时反复跟学生强调,因为优化前后对比特别直观,老师也爱问。
列表页还要记得分页。Django内置的Paginator就够用:
from django.core.paginator import Paginator p = Paginator(goods_list, 10) page = p.get_page(request.GET.get('page'))5.2 安全设置:默认值别乱关
Django默认带着一套安全机制,很多人不知道哪些能关哪些不能关。我的建议是:
- CSRF保护不要关掉。表单里加{% csrf_token %},Ajax请求带上X-CSRFToken头,否则POST会被403拦截。这虽然是烦人的一步,但它是防跨站请求伪造的关键。
- 密码保存用Django默认的PBKDF2算法。千万不要自己写md5加密存储密码,那是灾难。
- 模板默认自动转义HTML。用{{ variable }}输出内容时Django会转义特殊字符,有效防XSS。但如果你用了mark_safe或|safe过滤器,就要确保内容是可信的。
- DEBUG选项只能在本地开。部署到服务器时,settings.py里的DEBUG必须改成False,ALLOWED_HOSTS必须配置成你的域名或IP,否则会暴露大量调试信息并存在安全隐患。
5.3 部署建议:从runserver到正式感
本地开发用runserver跑起来就好,但如果答辩要给别人演示,或者老师要求部署到服务器,建议稍微正式一点:
pip install gunicorn gunicorn supershop.wsgi:application -b 0.0.0.0:8000后面再套一个nginx做反向代理,顺带把静态文件托管了。具体细节不展开,但记住一点:部署不要占用太多时间,毕业设计的核心是你的功能设计和代码实现,把三天时间花在nginx调配置上,不如把业务逻辑打磨好。除非题目就是“基于Django的Web部署研究”,否则能用runserver加局域网IP演示已经很够意思了。
6. 源码跑通与答辩演示的经验
6.1 拿到“10751”编号相关源码后怎么起步
标题里提到的“毕业设计源码10751”,这类带编号的源码包在开源社区很常见。拿到任何一份Django超市管理系统源码,不要急着双击运行,按我的步骤来:
第一步,检查Python版本和依赖。看requirements.txt,多数字段里都会写明Django版本。先建虚拟环境再安装依赖:pip install -r requirements.txt。遇到版本冲突,优先调整Django大版本而不是硬着头皮装老版本。
第二步,处理数据库。默认配的可能是SQLite,最省事;如果配的是MySQL,需要你本地有MySQL服务,还要装pymysql或mysqlclient。SQLite和MySQL在Django里的ORM写法几乎一致,所以如果MySQL配不起来,直接改成SQLite也能跑通。但要在论文里说明生产环境建议MySQL,演示环境为方便用SQLite。这是合理的取舍。
第三步,执行迁移并创建超级用户:
python manage.py migrate python manage.py createsuperuser第四步,看项目里有没有seed_data、init_data之类的命令,有就执行;没有就手动在Admin后台添加几个分类和商品。然后登录后台,核一遍list_display字段是否正确。
最常见的跑不起来的原因就三个:依赖没装全、Django版本与代码不匹配、数据库没迁移。这三个解决了,80%的源码包都能起来。剩下20%可能是Python路径或虚拟环境的问题,检查环境变量和IDE解释器配置。养成看控制台日志的习惯,别一报错就截图问人,先把最后几行错误读完,大多数问题自己就能定位。
6.2 我踩过的坑和你要避开的坑
写这个系统以及帮别人调代码的过程中,我攒了不少踩坑记录,挑几个最有代表性的:
时区差8小时。这是Django新手最容易遇见的“灵异事件”。数据库里存的时间是UTC,页面上显示的时间却晚了8小时。解决办法就是前面说的,settings.py设置TIME_ZONE='Asia/Shanghai',USE_TZ=True,展示时模板里用Django的timezone机制自动转换。千万别在图里手动加8小时,数据库存的时间仍然是UTC,下次读取会重复加。
Decimal不能直接和float混算。商品单价是Decimal(‘10.50’),如果你写price * 0.95,0.95这个float会强制把结果转成float,精度就崩了。正确写法是price * Decimal('0.95')。凡是涉及钱的运算,统一用Decimal。
删除外键关联数据时的连锁反应。我见过有人把商品模型的外键删成CASCADE,结果在Admin里删一个分类,该分类下所有商品连带消失。库存、订单明细这些重要数据被连坐删除,想恢复只能靠备份。所以我在商品分类外键上用PROTECT,在订单和订单明细关联上用CASCADE是合理的:订单明细属于订单的一部分,订单删了明细没有独立存在意义,但商品不能因为分类被删就消失。
Django 4.x的URL写法更简洁。老代码里常见的是典型的re_path正则,新代码用path加转换器就够了:
path('goods/<int:pk>/', views.goods_detail, name='goods_detail')<int:pk>直接限定参数为整数,比正则表达式好读得多。从网上下载的源码如果报URL路由的错,大概率是模块间路径写法差异造成的,调整一下urls.py的引用方式就行。
6.3 答辩演示脚本与最容易被问的问题
最后聊聊答辩。系统做完了,演示环节千万别“开屏就是Admin后台”。我建议按“用户故事”的视角把演示流程串成一条线:
- 用店长账号登录系统,进入仪表盘,看当日销售数据。
- 新增一个商品,设置价格和库存。
- 切换到收银员账号,模拟一次收银:加入购物车、结算、生成订单。
- 回到商品列表,看到库存自动减少、销量自动增加。
- 打开库存预警页,说明低库存商品被标记出来。
- 打开报表页,展示日销售趋势和商品排行。
- 如果做了WebSocket推送,登录两个浏览器窗口,一端创建订单另一端实时收到提醒。
这条流程大约5到8分钟,比挨个页面截图讲故事有说服力得多。演示过程中如果某个功能突然报错,别慌,先读一下控制台日志,很多问题就是数据库没迁移或服务没重启。
老师最常问的几个问题,提前准备答案:
为什么选Django?答:开发效率高,自带ORM、Admin、认证体系,适合业务复杂度中等的管理系统;Python生态支撑报表分析和后续扩展。
金额为什么用Decimal?答:浮点数有精度问题,财务数据必须精确,Decimal采用十进制运算能保证精度。
如何防止并发超卖?答:事务包裹结算流程,加select_for_update行锁,库存更新以数据库为准,不信任前端传值。
系统可以扩展吗?答:模块之间是低耦合的App结构,可以扩展多门店、分布式部署、对接支付接口等。给出扩展方向即可,不需要现场实现。
我个人辅导这么多学生做毕设,最大的体会是:老师看重的不是一个系统有多炫,而是你能不能讲清楚每个设计背后的理由。Django小型超市管理系统的每个模块都有明确的业务依据,只要你不是复制粘贴,而是把“为什么这样设计”想明白了,答辩根本不需要紧张。
最后再分享一个小技巧:把Admin后台配置好,演示时主动说一句“这里可以直接用Admin后台维护基础数据,方便运营人员快速操作”,然后切换到Admin页面点两下商品列表,老师会觉得你兼顾了普通用户和管理员两套视角,这一项就能看出你有没有真实做过系统设计。源码怎么整理,怎么把需求、设计、实现串成一篇完整的论文,就留给你在余下的时间里慢慢打磨吧。