ARTICLE DETAIL

资讯详情

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

Django快速搭建高校食堂外卖系统实战指南

Django快速搭建高校食堂外卖系统实战指南 简介本资源是一套基于Python Django框架开发的食堂外卖系统完整源码面向计算机专业本科生、毕业设计开发者及Web全栈初学者解决校园场景下线上订餐、订单管理与后台运营等实际需求。压缩包共736个文件涵盖41个核心Python后端逻辑文件、45个Vue前端组件、164个JS交互脚本、162个SVG图标资源、53个CSS样式文件及41个HTML页面模板辅以SQL数据库脚本、requirements依赖清单与安装批处理脚本结构清晰、模块解耦便于理解MVT架构实践与前后端协同流程。资源大小为15.17MBZIP格式开箱即用。目前已有193人学习下载提供可直接运行的完整项目骨架包含用户端点餐、商家端接单、管理员后台三大功能模块以及登录鉴权、密码修改、面包屑导航等典型业务细节实现是DjangoVue技术栈落地校园应用的典型参考案例。1. 为什么一个食堂外卖系统要用 Django 而不是 Flask 或纯 Python 脚本想象一下高校后勤处刚通知下周起食堂支持线上下单3000 名师生要在午休前 20 分钟集中抢购盖饭、饺子和奶茶后厨需实时看到订单队列、按窗口分单、标记已出餐管理员要查某天「红烧肉」销量、导出学生消费统计、禁用违规账号而你只有 5 天时间上线——这时候写个带登录的 Python 脚本用 Flask 手搓权限和后台都不现实。Django 的价值就在此刻显现它不是“又一个 Web 框架”而是把用户认证、数据库迁移、管理后台、表单验证、URL 路由、静态文件服务这些食堂系统刚需模块全部预装好、可配置、能扩展。你不需要从零实现“学生登录后只能看自己订单”Django 的User模型 login_required装饰器一行代码就锁死也不用重写“管理员导出 Excel 报表”Django Admin 内置导出功能点两下就能生成 CSV。这不是过度设计而是对高频业务场景的精准封装。本文面向两类人一是刚学完 Django MTV 模式、想拿真实项目练手的开发者二是需要快速交付校内轻量级订餐系统的运维或信息中心人员。我们不讲抽象原理只聚焦如何用 Django 官方机制把「食堂外卖」这个具体场景跑通、调优、防踩坑。2. 用 Django 在本地跑通食堂外卖系统的最小命令与核心模型设计2.1 初始化项目与创建核心 Appfood_order和canteenDjango 项目结构必须清晰分离关注点。食堂系统中“订单”和“食堂窗口”是独立业务实体不能全塞进mainApp。先确保已安装 Djangopip install django4.2.13LTS 版本更稳定# 创建项目骨架注意项目名用下划线避免 Python 导入冲突 django-admin startproject cafeteria . # 创建两个关键 Appcanteen食堂基础信息、food_order订单核心逻辑 python manage.py startapp canteen python manage.py startapp food_order提示不要用startproject cafeteria后再cd cafeteria直接django-admin startproject cafeteria .让manage.py位于当前目录这是 Django 4.2 推荐方式避免路径混乱。接着在settings.py的INSTALLED_APPS中注册新 App并配置数据库为 SQLite开发阶段足够# settings.py INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, # 自定义 App顺序重要依赖靠后 canteen, food_order, ] # 数据库配置开发环境 DATABASES { default: { ENGINE: django.db.backends.sqlite3, NAME: BASE_DIR / db.sqlite3, } }2.2 设计不可绕过的 4 个核心模型Canteen,FoodItem,Order,OrderItem食堂外卖的本质是「商品-订单-履约」链路。Django 模型必须反映真实约束一个窗口有多个菜品一个订单含多个菜品项学生只能取消未接单的订单。以下是经过生产验证的最小可行模型canteen/models.py和food_order/models.py# canteen/models.py from django.db import models class Canteen(models.Model): name models.CharField(max_length100, verbose_name食堂名称) location models.CharField(max_length200, verbose_name位置如主楼一层东侧) is_active models.BooleanField(defaultTrue, verbose_name是否营业) def __str__(self): return self.name class FoodItem(models.Model): canteen models.ForeignKey(Canteen, on_deletemodels.CASCADE, related_namefoods) name models.CharField(max_length100, verbose_name菜品名称) price models.DecimalField(max_digits6, decimal_places2, verbose_name单价元) stock models.PositiveIntegerField(default0, verbose_name库存数量) is_available models.BooleanField(defaultTrue, verbose_name是否可售) def __str__(self): return f{self.canteen.name} - {self.name}# food_order/models.py from django.db import models from django.contrib.auth.models import User class Order(models.Model): STATUS_CHOICES [ (pending, 待接单), (confirmed, 已接单), (preparing, 制作中), (ready, 已备好), (delivered, 已送达), (cancelled, 已取消), ] user models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name下单用户) canteen models.ForeignKey(canteen.Canteen, on_deletemodels.PROTECT, verbose_name所属食堂) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultpending) created_at models.DateTimeField(auto_now_addTrue) updated_at models.DateTimeField(auto_nowTrue) def total_amount(self): 计算订单总金额避免 N1 查询 return sum(item.subtotal() for item in self.items.all()) def __str__(self): return f订单#{self.id} - {self.user.username} class OrderItem(models.Model): order models.ForeignKey(Order, on_deletemodels.CASCADE, related_nameitems) food models.ForeignKey(canteen.FoodItem, on_deletemodels.PROTECT) quantity models.PositiveIntegerField(default1) notes models.CharField(max_length200, blankTrue, verbose_name备注如少放辣) def subtotal(self): return self.food.price * self.quantity def __str__(self): return f{self.food.name} x{self.quantity}关键参数说明与选型理由字段类型/选项为什么这样设canteen.Canteen.is_activeBooleanField食堂临时检修时前端可直接隐藏该食堂入口无需删数据food_order.Order.statuschoices枚举状态机驱动业务流程比布尔字段更易扩展如增加“配送中”Order.canteenForeignKey(... on_deletemodels.PROTECT)防止删除食堂时误删历史订单PROTECT 比 CASCADE 更安全OrderItem.foodForeignKey(... on_deletemodels.PROTECT)菜品下架后历史订单仍能显示原价保证数据一致性执行迁移命令生成数据库表python manage.py makemigrations python manage.py migrate2.3 用 Django Admin 快速搭建后台管理界面食堂管理员不会写 SQL但需要随时修改菜品价格、查看今日订单。Django Admin 是现成的解决方案只需 10 行代码# canteen/admin.py from django.contrib import admin from .models import Canteen, FoodItem admin.register(Canteen) class CanteenAdmin(admin.ModelAdmin): list_display [name, location, is_active] list_filter [is_active] search_fields [name, location] admin.register(FoodItem) class FoodItemAdmin(admin.ModelAdmin): list_display [name, canteen, price, stock, is_available] list_filter [canteen, is_available] search_fields [name] list_editable [price, stock, is_available] # 表格内直接编辑# food_order/admin.py from django.contrib import admin from .models import Order, OrderItem class OrderItemInline(admin.TabularInline): # 嵌套显示订单明细 model OrderItem extra 0 fields [food, quantity, notes] admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display [id, user, canteen, status, created_at, total_amount] list_filter [status, canteen, created_at] search_fields [user__username, user__first_name] inlines [OrderItemInline] # 关键嵌入 OrderItem readonly_fields [created_at, updated_at]启动服务并创建超级用户python manage.py createsuperuser # 按提示输入用户名、邮箱、密码 python manage.py runserver访问http://127.0.0.1:8000/admin/即可管理食堂、菜品、订单——这步省去至少 20 小时前端开发。3. 实现学生端核心功能菜品浏览、下单、订单查询与状态更新3.1 路由与视图用 Class-Based View 减少重复代码学生端需三个核心页面首页所有食堂列表、食堂详情页菜品列表、下单页确认订单。Django 的ListView和DetailView天然匹配此场景# food_order/views.py from django.shortcuts import render, get_object_or_404, redirect from django.contrib.auth.decorators import login_required from django.views.generic import ListView, DetailView from django.contrib import messages from django.db import transaction from .models import Order, OrderItem from canteen.models import Canteen, FoodItem class CanteenListView(ListView): model Canteen template_name food_order/canteen_list.html context_object_name canteens queryset Canteen.objects.filter(is_activeTrue) class CanteenDetailView(DetailView): model Canteen template_name food_order/canteen_detail.html context_object_name canteen def get_context_data(self, **kwargs): context super().get_context_data(**kwargs) # 只显示该食堂下可售菜品 context[foods] self.object.foods.filter(is_availableTrue) return context login_required def create_order(request, canteen_id): canteen get_object_or_404(Canteen, idcanteen_id, is_activeTrue) if request.method POST: # 使用 transaction.atomic 确保库存扣减与订单创建原子性 with transaction.atomic(): order Order.objects.create( userrequest.user, canteencanteen, statuspending ) # 解析 POST 数据food_id1quantity2food_id3quantity1 food_ids request.POST.getlist(food_id) quantities request.POST.getlist(quantity) total_items 0 for food_id, qty in zip(food_ids, quantities): try: food FoodItem.objects.select_for_update().get(idfood_id, canteencanteen) qty_int int(qty) if food.stock qty_int: raise ValueError(f{food.name} 库存不足仅剩 {food.stock} 份) OrderItem.objects.create( orderorder, foodfood, quantityqty_int, notesrequest.POST.get(fnotes_{food_id}, ) ) food.stock - qty_int food.save() total_items qty_int except (ValueError, FoodItem.DoesNotExist) as e: messages.error(request, str(e)) return redirect(canteen_detail, pkcanteen_id) messages.success(request, f下单成功共 {total_items} 份菜品订单号 #{order.id}) return redirect(order_detail, pkorder.id) # GET 请求渲染下单表单 foods canteen.foods.filter(is_availableTrue) return render(request, food_order/create_order.html, {canteen: canteen, foods: foods})关键逻辑说明select_for_update()在数据库层面加行锁防止并发下单导致超卖如两人同时买最后一份红烧肉transaction.atomic()确保“创建订单”和“扣减库存”要么全成功要么全回滚messagesDjango 内置消息框架比print()更专业错误提示能直达前端。3.2 模板用 Django 模板语言渲染动态页面Django 模板不是 HTML而是带逻辑的 DSL。以下是最小化create_order.html重点展示表单动态生成!-- templates/food_order/create_order.html -- h2在 {{ canteen.name }} 下单/h2 form methodpost {% csrf_token %} table thead trth菜品/thth单价/thth数量/thth备注/th/tr /thead tbody {% for food in foods %} tr td{{ food.name }}/td td¥{{ food.price }}/td tdinput typenumber namequantity value1 min1 max{{ food.stock }} required/td tdinput typetext namenotes_{{ food.id }} placeholder如不要香菜/td input typehidden namefood_id value{{ food.id }} /tr {% endfor %} /tbody /table button typesubmit提交订单/button /form注意{% csrf_token %}是 Django 防 CSRF 攻击的必需项漏掉会导致 403 错误。3.3 URL 配置清晰映射到视图在food_order/urls.py中定义路由避免硬编码# food_order/urls.py from django.urls import path from . import views urlpatterns [ path(, views.CanteenListView.as_view(), namecanteen_list), path(canteen/int:pk/, views.CanteenDetailView.as_view(), namecanteen_detail), path(canteen/int:canteen_id/order/, views.create_order, namecreate_order), path(order/int:pk/, views.OrderDetailView.as_view(), nameorder_detail), ]并在主urls.py中包含# urls.py from django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(, include(food_order.urls)), ]此时访问http://127.0.0.1:8000/即可看到食堂列表点击进入后下单——核心流程已闭环。4. 食堂后厨端实时订单看板与状态一键更新4.1 为后厨定制专用视图过滤未完成订单管理员和后厨人员权限不同。Django 的User.groups可实现角色分离但最简方案是用is_staff字段区分管理员用superuser后厨用普通用户但is_staffTrue。创建后厨视图只显示其负责食堂的待处理订单# food_order/views.py from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.views.generic import ListView, UpdateView class KitchenOrderListView(LoginRequiredMixin, UserPassesTestMixin, ListView): model Order template_name food_order/kitchen_order_list.html context_object_name orders def test_func(self): # 仅 staff 用户可访问 return self.request.user.is_staff def get_queryset(self): # 获取该用户负责的食堂实际项目中可关联 StaffProfile 模型 # 此处简化假设 staff 用户只看第一个食堂生产环境应通过外键关联 canteen Canteen.objects.first() return Order.objects.filter( canteencanteen, status__in[pending, confirmed, preparing] ).order_by(created_at) class OrderStatusUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView): model Order fields [status] template_name food_order/order_status_form.html def test_func(self): return self.request.user.is_staff def get_success_url(self): return reverse(kitchen_order_list)4.2 状态更新表单用 Django Form 控制可选值后厨不应能随意将订单设为“已送达”只能按流程推进。用ModelForm限制下拉选项# food_order/forms.py from django import forms from .models import Order class OrderStatusForm(forms.ModelForm): class Meta: model Order fields [status] widgets { status: forms.Select(choices[ (confirmed, 已接单), (preparing, 制作中), (ready, 已备好), (delivered, 已送达), (cancelled, 已取消), ]) }在OrderStatusUpdateView中使用该 Form# food_order/views.py from .forms import OrderStatusForm class OrderStatusUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView): model Order form_class OrderStatusForm # 替换 fields template_name food_order/order_status_form.html # ... 其余代码不变4.3 后厨看板模板用 Bootstrap 表格提升可读性后厨盯着屏幕看订单需要一目了然。模板中加入状态徽章和操作按钮!-- templates/food_order/kitchen_order_list.html -- h2后厨订单看板/h2 table classtable table-striped thead tr th订单号/th th用户/th th下单时间/th th状态/th th操作/th /tr /thead tbody {% for order in orders %} tr td#{{ order.id }}/td td{{ order.user.username }}/td td{{ order.created_at|time:H:i }}/td td span classbadge bg-{{ order.status|default:secondary }} {% if order.status pending %}待接单{% elif order.status confirmed %}已接单{% endif %} /span /td td a href{% url order_status_update pkorder.pk %} classbtn btn-sm btn-primary更新状态/a /td /tr {% endfor %} /tbody /table提示Django 模板过滤器|time:H:i将时间转为 24 小时制比完整时间更符合后厨场景。5. 生产部署前必做的 3 个优化与 2 个排错技巧5.1 静态文件与媒体文件解决 CSS/JS 不加载问题开发时DEBUGTrueDjango 自动提供静态文件服务但生产环境必须用 Nginx。先配置settings.py# settings.py DEBUG False # 生产环境必须关闭 ALLOWED_HOSTS [your-domain.com, 192.168.1.100] # 严格设置 # 静态文件CSS/JS STATIC_URL /static/ STATIC_ROOT BASE_DIR / staticfiles # collectstatic 输出目录 # 媒体文件用户上传如菜品图片 MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / media收集静态文件Nginx 将从此目录读取python manage.py collectstatic --noinput注意--noinput避免交互式确认适合自动化脚本。5.2 数据库连接池避免高并发时 MySQL 连接耗尽SQLite 无法支撑 3000 人并发必须切 MySQL。Django 默认每次请求新建连接易触发Too many connections。使用django-db-geventpool基于 gevent 的连接池pip install django-db-geventpool修改settings.py数据库配置# settings.py DATABASES { default: { ENGINE: django_db_geventpool.backends.mysql, NAME: cafeteria_db, USER: cafeteria_user, PASSWORD: your_secure_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { MAX_CONNS: 20, # 连接池最大连接数 MIN_CONNS: 5, # 最小空闲连接数 } } }5.3 排错技巧一订单状态不更新检查save()是否被覆盖常见错误在Order模型中重写了save()方法但忘记调用super().save()导致auto_now字段如updated_at不更新# ❌ 错误写法覆盖 save 但没调用父类 def save(self, *args, **kwargs): if self.status delivered: self.send_notification() # 自定义逻辑 # 忘记 super().save(*args, **kwargs) → updated_at 永远不变 # ✅ 正确写法 def save(self, *args, **kwargs): if self.status delivered: self.send_notification() super().save(*args, **kwargs) # 必须有5.4 排错技巧二库存扣减失效验证select_for_update()的事务边界select_for_update()只在数据库事务内生效。若在transaction.atomic外调用锁会立即释放# ❌ 错误锁在事务外无效 def bad_create_order(request): food FoodItem.objects.select_for_update().get(id1) # 锁立即释放 time.sleep(2) # 并发时另一请求可读到旧库存 food.stock - 1 food.save() # ✅ 正确锁在事务内 transaction.atomic def good_create_order(request): food FoodItem.objects.select_for_update().get(id1) # 锁持续到事务结束 food.stock - 1 food.save() # 事务提交时才释放锁5.5 性能优化用select_related和prefetch_related消除 N1 查询当在模板中遍历订单并显示菜品名称时若未优化每条订单都会查一次FoodItem100 个订单就是 100 次查询。在视图中预加载# food_order/views.py class OrderDetailView(DetailView): model Order template_name food_order/order_detail.html def get_queryset(self): # 预加载订单项及关联菜品1 次查询搞定 return Order.objects.prefetch_related( items__food ).select_related(canteen)模板中可直接使用item.food.name而无额外查询。最后一步用gunicorn启动服务非runserver并用nginx反向代理至此一个可投入校内试运行的 Django 食堂外卖系统已就绪。本文还有配套的精品资源点击获取
返回列表