
简介一套基于Python Django框架的食堂外卖系统源码面向毕业设计、课程实训以及Web开发初学者围绕用户下单、支付、订单状态管理等真实场景构建帮助理解Django项目从建模到上线的完整开发链条。压缩包共737个文件内含41个Python文件用于实现模型与视图逻辑45个Vue组件和164个JS文件构建前端交互41个HTML页面配合53个CSS完成页面展示另有SQL数据库脚本、requirements依赖清单、启动批处理以及多份.bak备份配置压缩后约15.43MB整体目录结构按功能划分便于前后端对照阅读。项目覆盖Django的主要知识点包括MVT分层设计、ORM数据建模、URL路由、表单验证、模板渲染、用户认证与权限管理并涉及支付接口集成、静态资源处理及常见部署配置前端还附带SVG图标、GIF动图、字体文件和图片素材可直接用于界面完善。已有214人浏览学习适合作为毕业设计参考或Django综合练习材料既能练习后端业务逻辑也能观察前后端协作方式。1. 这个 Django 食堂外卖系统源码解决的不只是点餐问题拿到一个名为Python基于Django的食堂外卖系统源码.zip的压缩包很多人习惯先解压、用IDE打开一遍代码结果一头扎进几百个文件里。其实这套系统真正要解决的问题是如何在校园或园区场景里把点餐、支付、取餐这条链路用Web方式跑通。它不只是一个demo而是一套包含用户端、商家端和管理后台的完整Django项目。对Python工程师来说它的价值在于让你看到一个多App的Django项目如何拆分业务模块ORM如何建模订单与库存以及反向URL解析怎样把前后端粘合起来。如果你正准备给小型食堂做信息化或者正在做基于Django的毕业设计这套源码就是最好的参考起点。接下来我会按照拆结构 → 搭环境 → 读业务 → 做后台的顺序带你把这套源码吃透。2. 拆解源码Django 食堂外卖系统的项目结构与数据模型拿到源码包先不要急着runserver。你要做的是先理解它的目录结构。Django 项目天生有项目配置和应用两层界限一个成熟的食堂外卖系统往往会拆出账号、商品、购物车、订单、支付等独立 App。这一步读懂了后面二次开发就不会迷路。2.1 一眼看懂 manage.py 与 app 划分从入口到业务模块以常见结构为例. ├── manage.py ├── config/ # 项目配置settings/urls/wsgi │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── accounts/ # 用户注册登录扩展User模型 │ ├── canteen/ # 食堂与档口管理 │ ├── menu/ # 菜品、分类、库存 │ ├── cart/ # 购物车 │ ├── order/ # 订单、订单项、状态流转 │ └── payment/ # 支付回调通常先写占位 ├── static/ ├── media/ ├── requirements.txt └── db.sqlite3 # 部分源码自带SQLite数据库manage.py是 Django 的命令行入口所有python manage.py ...命令都从这里进入。如果你把业务代码写在apps/下面说明源码作者用了 App 目录聚合策略这在大型项目里很常见目的是防止每个功能都堆在项目根目录。你要做的第一件事就是在终端里执行python manage.py help看看项目有哪些自定义命令比如init_data这在源码包里常被用来初始化食堂和菜品数据。在 Windows 上想看目录树可以运行tree /F在 Linux/macOS 上运行tree -L 2。看到结构后打开config/settings.py重点看INSTALLED_APPS列表里有没有django_extensions、rest_framework这样的第三方库这决定了你后续要装哪些额外依赖。2.2 核心数据模型用户扩展、菜品分类、购物车与订单食堂外卖系统最核心的是用户-菜品-订单三角关系。由于 Django 自带的User模型字段有限源码通常会创建一个Profile类通过一对一来扩展用户信息比如学号、工号、余额。菜品和分类是典型的多对一一个分类下有多个菜品一个菜品只属于一个分类。购物车项则同时外键到用户和菜品订单与订单项是主从关系。from django.db import models from django.contrib.auth.models import User class Profile(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE, related_nameprofile) student_id models.CharField(max_length20, blankTrue) balance models.DecimalField(max_digits8, decimal_places2, default0) class Category(models.Model): name models.CharField(max_length50) sort models.IntegerField(default0) class Dish(models.Model): name models.CharField(max_length100) category models.ForeignKey(Category, on_deletemodels.PROTECT, related_namedishes) price models.DecimalField(max_digits6, decimal_places2) stock models.PositiveIntegerField(default0) image models.ImageField(upload_todishes/, blankTrue) class CartItem(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_namecart_items) dish models.ForeignKey(Dish, on_deletemodels.CASCADE) quantity models.PositiveIntegerField(default1) class Order(models.Model): STATUS_CHOICES ( (pending, 待支付), (paid, 已支付), (preparing, 备餐中), (delivering, 配送中), (completed, 已完成), (cancelled, 已取消), ) user models.ForeignKey(User, on_deletemodels.PROTECT, related_nameorders) created_at models.DateTimeField(auto_now_addTrue) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultpending) total_amount models.DecimalField(max_digits8, decimal_places2) address models.CharField(max_length200) class OrderItem(models.Model): order models.ForeignKey(Order, on_deletemodels.CASCADE, related_nameitems) dish models.ForeignKey(Dish, on_deletemodels.PROTECT) price models.DecimalField(max_digits6, decimal_places2) quantity models.PositiveIntegerField()上面的模型有几个关键设计on_deletemodels.PROTECT用于菜品被订单引用时禁止删除这在食堂菜单管理中非常重要避免历史订单变成无源之水购物车没有单独的主表直接由CartItem以用户为粒度查询简单够用订单状态用字符串常量表示比用数字更易读。你在二次开发时如果要加取消原因只需在Order里增加一个cancel_reason字段即可。模型关键字段关联关系Profileuser(OneToOne), balance扩展内置 UserDishname, category(FK), stock多对一 CategoryCartItemuser(FK), dish(FK), quantity用户与菜品多对多关系简化为购物车项Orderuser(FK), status, total一用户多订单OrderItemorder(FK), dish(FK), price订单与菜品的多对多通过订单项实现在这个设计中OrderItem充当了关联表它同时保存了下单时的菜品价格快照。如果菜单价格调整历史订单的金额不会跟着变这是订单系统必须有的特性。理解了这些关系再去读源码里的views.py你会发现逻辑都是围绕这些模型展开的。2.3 用迁移文件同步数据库表结构理解模型后下一步是通过 Django 的迁移系统把表建出来。源码压缩包里一般已经携带了每个 App 的migrations目录里面是形如0001_initial.py的迁移文件。只要你本机安装了 Django并且设置好了数据库连接直接执行python manage.py makemigrations --check python manage.py migrate--check只检查模型与迁移文件是否有差异不生成文件适合用来确认源码里的迁移文件是否完整。如果提示No changes detected说明模型与迁移一致可以放心migrate。如果提示缺少迁移说明源码包可能被删过文件这时需要手动补makemigrations。准备好数据库表后你会看到django_migrations表记录了所有已执行的迁移文件名Django 靠它知道哪些迁移已经应用。如果你在项目中期换过数据库就不要直接删迁移文件否则会出现InconsistentMigrationHistory报错。另一个常见错误是django.db.utils.OperationalError: no such table: menu_dish这通常是因为你跳过了migrate直接runserver或者migrate时选了错误的数据库。遇到这种情况先检查settings.py里DATABASES的指向再重新执行迁移。3. 本地跑通源码Python 环境、依赖安装与 settings 配置源码项目的 README 可能写得不够详细但requirements.txt一定是最后的救命稻草。这一章照着做能在一台干净机器上把系统跑起来。3.1 虚拟环境与 requirements.txt一次性装齐依赖永远不要用全局 Python 去装项目依赖否则一个项目升级依赖另一个项目就崩了。先建虚拟环境python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate pip install -r requirements.txt这里假设你已正确安装 Python 3.10。如果你还没有 Python可以从官网下载安装包注意勾选Add Python to PATH。装完依赖后用pip list对比requirements.txt确认没有缺包。requirements.txt里常见的内容包括Django4.2.10 Pillow10.2.0 mysqlclient2.2.0 django-crispy-forms2.1Django版本要锁死因为项目可能用到某些只在 4.x 存在的 API。Pillow是处理菜品图片上传的必备库。mysqlclient是 MySQL 驱动比pymysql稳定但安装时容易编译失败见下一节。django-crispy-forms用于渲染 Bootstrap 风格的表单源码里如果用了它你在模板中会看到{% load crispy_forms_tags %}。我常见的问题是 Windows 用户装mysqlclient失败导致整句pip install终止这时可以用pip install pymysql并在项目的__init__.py里做适配。但更好的办法是先安装pymysql再重新执行pip install -r requirements.txt这样其他依赖也能正常装上。3.2 切换 MySQL 数据库解决 mysqlclient 安装问题源码默认可能使用 SQLitedb.sqlite3但生产环境你一定想换成 MySQL。在settings.py中找到DATABASESDATABASES { default: { ENGINE: django.db.backends.mysql, NAME: canteen_db, USER: root, PASSWORD: yourpassword, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { init_command: SET sql_modeSTRICT_TRANS_TABLES, }, } }注意ENGINE的写法是django.db.backends.mysqlDjango 通过这个配置决定使用哪个数据库适配层。如果mysqlclient装不上可以在项目同名目录的__init__.py里写入import pymysql pymysql.install_as_MySQLdb()这样 Django 会把pymysql当作 MySQLdb 使用。但这只是临时方案pymysql在 Python 3.12 上会有兼容问题建议有条件还是装mysqlclient。在 Ubuntu/Debian 上先执行sudo apt install default-libmysqlclient-dev build-essential再pip install mysqlclient在 CentOS 上是sudo yum install mysql-devel gcc-c。如果你用的是宝塔面板可以在软件商店里直接安装 MySQL并在Python项目管理器里设置依赖安装宝塔会自动处理编译环境省去很多麻烦。数据库本身也需要先创建好CREATE DATABASE canteen_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;utf8mb4是必须的否则菜名里有 emoji 或生僻字会报Incorrect string value错误。创建完数据库后再回到项目执行python manage.py migrate。3.3 创建数据库表、初始化数据与超级管理员如果源码包里附带fixtures目录比如data.json里面通常是食堂、档口、菜品的初始数据。加载它python manage.py migrate python manage.py loaddata apps/menu/fixtures/initial_data.jsonloaddata会按 JSON/XML 文件里的模型名和主键插入数据。加载前确认文件里没有重复主键否则会报IntegrityError。然后创建管理员python manage.py createsuperuser按提示输入用户名、邮箱、密码。这一步会写入auth_user表供你之后登录 Django Admin 后台。最后启动开发服务器python manage.py runserver 0.0.0.0:8000你可以在浏览器访问http://127.0.0.1:8000/看首页访问/admin/进入管理后台。如果页面样式全丢了十有八九是STATICFILES_DIRS或MEDIA_URL配置不对。打开settings.py检查STATIC_URL /static/ STATICFILES_DIRS [BASE_DIR / static] MEDIA_URL /images/ MEDIA_ROOT BASE_DIR / mediarunserver在调试模式下会自动处理静态文件但只在DEBUGTrue时生效。把DEBUGTrue改成False后静态文件需要由 Nginx 或 WSGI 中间件托管这是部署时最常见的坑。4. 核心交易流程的 Django 实现购物车、订单与反向解析读完模型和环境接下来要看源码里最值钱的业务代码——用户怎么从加购到下单再到支付。这一章我会按常见的实现思路讲解如果你手上的源码实现略有出入逻辑也逃不出这几步。4.1 用事务保证下单扣库存的原子性食堂外卖系统里用户点击提交订单会同时做几件事读取购物车、生成订单、扣减菜品库存、清空购物车。任何一步失败都不能留下订单生成但库存没扣的脏数据。Django 提供transaction.atomic()来包裹操作。from django.db import transaction from django.shortcuts import get_object_or_404, redirect from .models import Order, OrderItem, CartItem, Dish transaction.atomic def create_order(request): cart_items CartItem.objects.select_related(dish).filter(userrequest.user) if not cart_items.exists(): return redirect(cart:detail) total sum(item.dish.price * item.quantity for item in cart_items) order Order.objects.create(userrequest.user, total_amounttotal, statuspending) for item in cart_items: dish Dish.objects.select_for_update().get(pkitem.dish_id) if dish.stock item.quantity: raise ValueError(f菜品 {dish.name} 库存不足) dish.stock - item.quantity dish.save() OrderItem.objects.create(orderorder, dishdish, pricedish.price, quantityitem.quantity) cart_items.delete() return redirect(order:detail, order_idorder.id)这里有几个关键点select_related(dish)在查询购物车时就用 SQL 的 JOIN 把菜品信息一次性取出来避免循环中逐条查库select_for_update()对菜品行加锁在数据库层面防止并发下单时库存被超卖整个函数被transaction.atomic装饰任何异常都会回滚订单和库存操作。注意raise ValueError在事务里会触发回滚但不会吞掉异常你需要在上层视图或中间件里捕获。参数order_id在redirect中传递给下一个视图这依赖 URL 反向解析见 4.3 节。如果你看到源码里不是用transaction.atomic装饰器而是在函数内部调用with transaction.atomic():效果是一样的只是作用域不同。前者包裹整个函数后者只包裹代码块推荐使用后者即便于控制事务粒度也容易阅读。4.2 订单状态流转与删除对象的常见写法订单不是创建后就完事了常见状态机是待支付 → 已支付 → 备餐中 → 配送中 → 已完成或者用户在未支付时取消。源码里一般设计一个status字段配合save()来更新。以用户取消订单为例from django.contrib import messages def cancel_order(request, order_id): order get_object_or_404(Order, pkorder_id, userrequest.user) if order.status not in (pending, paid): messages.error(request, 当前状态不允许取消) return redirect(order:detail, order_idorder.id) order.status cancelled order.save() # 归还库存 for item in order.items.all(): dish item.dish dish.stock item.quantity dish.save() messages.success(request, 订单已取消) return redirect(order:list)这里要特别强调 Django 删除对象的两种写法。初学者容易把order.delete()当成通用操作但在订单业务里delete()会把历史订单从库里彻底删除后续对账和统计就没了。正确的做法是把状态变为cancelled用逻辑删除替代物理删除。如果确实要清理数据可以调用order.delete()但要注意它返回一个元组(deleted_count, {model: count})并且 Django 2.0 以后on_deletemodels.CASCADE关联的数据也会一并删除。想要只删除当前行用Order.objects.filter(pkorder_id).delete()也可以但它们的 SQL 转换不同前者先查出所有关联对象后者直接执行DELETE FROM加上条件。笔者建议业务数据一律不要物理删除用状态字段替代。django执行查询-删除对象这个话题常被搜索其实核心就是两点QuerySet.delete()返回删除计数且会级联删除Model.delete()也会触发信号但不会像QuerySet.delete()那样对每个对象发送pre_delete信号。如果你在模型里定义了save()的重写或信号删除时就要特别小心。4.3 用 reverse 解决订单支付回跳 URL 与 messages 传参Django 项目中硬编码 URL 是维护灾难。源码里如果看到视图函数里写/order/3/这种路径说明作者偷懒了正确的做法是用reverse()生成 URL。reverse的本质是去urls.py里根据name反查匹配规则比如from django.urls import reverse url reverse(order:detail, args[order.id])如果项目用了上面这种app_name命名空间URL 配置通常是# urls.py from django.urls import path from . import views app_name order urlpatterns [ path(int:order_id/, views.order_detail, namedetail), path(cancel/int:order_id/, views.cancel_order, namecancel), ]这样即使你调整了 URL 中的前缀视图里的reverse(order:detail, args[order_id])依然能生成正确路径。与之相关的是django.urls.resolve的反向操作它把request.path解析为匹配的视图函数和参数。在调试权限或中间件时resolve(request.path)非常有用from django.urls import resolve match resolve(/order/5/) print(match.func.__name__) # order_detail print(match.kwargs) # {order_id: 5}在redirect中传参很多人会写redirect(/order/ str(order_id) /)但更优雅的是redirect(reverse(order:detail, args[order_id]))。如果想同时给用户提示配合 Django 的messages框架先messages.success(request, 下单成功)再redirect模板里用{% for message in messages %}展示即可。messages的实现依赖 session记得检查settings.py的INSTALLED_APPS是否包含django.contrib.messages以及MIDDLEWARE里的SessionMiddleware位置。如果你的源码是前后端分离项目比如 Django Vue那么这里不需要reverse而是返回 JSON由前端路由跳转。但传统 Django 模板项目用reverse是标配。遇到NoReverseMatch错误时优先检查命名空间、name是否拼写正确以及 URL 正则是否需要参数。5. 进阶用 Django Admin 管理食堂数据并用 show_urls 验证源码路由这一章是实战技巧让你在二次开发时能更快地操作后台、梳理路由并在上线前做最后验证。5.1 自定义 Admin 后台快速上架菜品与处理订单Django Admin 是整套系统里性价比最高的后台。你只需要在apps/menu/admin.py注册模型并定义展示列from django.contrib import admin from .models import Dish, Category admin.register(Dish) class DishAdmin(admin.ModelAdmin): list_display (name, category, price, stock) list_filter (category,) search_fields (name,) list_editable (price, stock)这样食堂工作人员可以直接在/admin/menu/dish/页面修改库存与价格无需编写前端页面。list_editable让列表页直接可编辑适合批量调价。订单模型也可以照此定制加入status的下拉筛选和created_at只读字段。记住一个原则Admin 只给内部人员用不要暴露给普通用户普通用户端一定要单独写视图。5.2 用 django-extensions 的 show_urls 梳理所有业务端点面对一个陌生源码包最快熟悉路由的方式是安装django-extensionspip install django-extensions把django_extensions加入INSTALLED_APPS后运行python manage.py show_urls | grep order输出会类似/apps/order/int:order_id/ apps.order.views.order_detail order:detail /apps/order/cancel/int:order_id/ apps.order.views.cancel_order order:cancel你一眼就能看到每个 URL 对应的视图函数和名称甚至能发现源码里是否有隐藏 App。这个命令是反向解析的调试利器如果reverse(order:detail)报NoReverseMatch先运行show_urls确认 URL 配置里是否真的存在这个 name以及需要多少参数。5.3 部署前最后检查静态文件与 DEBUGFalse准备上线时把settings.py的DEBUG改为False然后执行python manage.py collectstatic --noinput该命令将所有 App 和STATICFILES_DIRS下的静态文件复制到STATIC_ROOT否则 Nginx 只能拿到空目录。验证方法是启动一个临时服务python manage.py runserver 0.0.0.0:8000 --insecure加上--insecure后 Django 会临时托管静态文件方便你快速检查页面是否变样。但正式环境绝不要用这个参数而应由 Nginx 配置location /static/ { alias /path/to/staticfiles/; }。最后用python manage.py check --deploy检查是否有安全警告它会提示SECRET_KEY是否暴露、ALLOWED_HOSTS是否配置、以及是否启用了 HSTS 等。python manage.py check --deploy对于食堂外卖系统你还需要确认MEDIA_ROOT在 Nginx 中也配置了别名这样用户上传的菜品图片才能正常访问。完成以上检查后就可以用 gunicorn 启动服务并在/admin/登录后台看数据是否正常。本文还有配套的精品资源点击获取