Django垃圾分类系统实战:从毕设到物业真上线 简介这是一套面向计算机相关专业本科生的高分毕业设计级项目资源基于Python与Django框架实现完整的垃圾分类管理系统适用于软件工程、人工智能、电子信息等方向的毕设、课程设计或项目实践。资源包共2000个文件主体为1457个Python源码含Django核心逻辑、模型定义与视图处理、139个HTML模板构建前端页面结构、96个JavaScript脚本实现交互功能及20个CSS样式文件含select2、widgets等UI组件整体压缩包仅17.12MB轻量易部署。已有139人下载学习适合具备基础Web开发能力的学生在真实业务场景中理解MVC架构落地、数据库设计如垃圾类别分级、用户权限管理、前后端协同与系统部署全流程。资源包含全部可运行代码、详细设计文档、数据库初始化脚本及完整项目目录结构说明所有模块均经测试验证支持直接部署或二次开发拓展识别接口、管理后台等功能。1. 垃圾分类系统不是演示Demo它得在社区物业后台真跑起来还要扛住阿姨扫码录入高峰这不是一个“用Django搭个CRUD页面几个按钮弹窗”的毕业设计套壳项目。真正能拿去交差、甚至被社区临时上线试用的垃圾分类系统核心矛盾从来不在“能不能显示四类垃圾桶图标”而在于如何让保洁员在凌晨五点寒风里用一台电量只剩20%的安卓旧机3秒内完成一次湿垃圾拍照→自动识别→归属楼栋→生成积分→同步到物业小程序。它要处理真实场景里的模糊图、反光塑料袋、半遮挡厨余、手写纸条混投要应对物业管理员批量导入327户居民数据时的Excel乱码和空行更要保证每天凌晨三点定时任务清理过期未审核记录时不卡死整个后台。本篇不讲“Django有多优雅”只拆解我去年帮三个学院指导毕设时学生从“本地runserver能跳转”到“部署到阿里云轻量服务器后连续72小时无报错”的完整链路——包括模型轻量化怎么选TensorFlow Lite还是ONNX Runtime、Django Admin怎么魔改才能让60岁物业主任看懂审核界面、以及为什么你写的python manage.py migrate在老师电脑上总报no module named PIL这种玄学错误。2. 从零初始化用Django 4.2 Python 3.10 搭建可交付的最小生产骨架2.1 创建带生产就绪配置的Django项目不是django-admin startproject很多同学直接django-admin startproject waste结果后期加Redis缓存、日志分级、静态文件CDN时配置全塞进settings.py最后变成2000行难以维护的巨兽。我推荐用django-project-template社区模板起步它强制分离开发/测试/生产配置# 安装cookiecutter比官方startproject更可控 pip install cookiecutter # 拉取经实战验证的模板已剔除所有敏感依赖 cookiecutter https://github.com/pydanny/cookiecutter-django.git # 交互式填空关键选项如下 # project_name: waste_system # version: 0.1.0 # timezone: Asia/Shanghai # use_docker: n # 毕设不用Docker避免答辩时解释容器概念 # use_async: n # 同步IO足够异步反而增加调试复杂度 # use_sentry: n # 毕设不用错误监控生成结构后删掉requirements/base.txt里psycopg2-binary我们用SQLite起步避免学生装PostgreSQL失败保留pillow9.5.0图像处理刚需、django-crispy-forms2.0Admin表单美化。重点检查config/settings/production.py是否已预置DEBUG False、ALLOWED_HOSTS [*]答辩演示用、SECURE_SSL_REDIRECT False本地没HTTPS证书。提示cookiecutter-django模板默认禁用django.contrib.sites但我们的积分模块需关联用户所属小区必须手动在INSTALLED_APPS中补上django.contrib.sites并在production.py末尾加SITE_ID 1否则迁移时会报relation django_site does not exist。2.2 设计贴合物业实际的数据库模型不是教科书式ER图毕业设计常犯的错把“可回收物”“有害垃圾”做成独立Model结果录入一条记录要跨5张表JOIN。真实物业系统需要的是高写入、低查询延迟、强业务语义。我们采用“单表多字段状态机”设计# waste/models.py from django.db import models from django.contrib.auth.models import User class WasteRecord(models.Model): # 【核心字段】不拆分类型表用choices提升写入速度 CATEGORY_CHOICES ( (kitchen, 厨余垃圾), (recyclable, 可回收物), (hazardous, 有害垃圾), (other, 其他垃圾), ) category models.CharField(max_length20, choicesCATEGORY_CHOICES) # 【业务字段】直接存楼栋号避免关联Building表查ID building_code models.CharField(max_length10, help_text如A-03-1201) # 【防作弊字段】GPS坐标时间戳双校验手机端APP传入 gps_lat models.DecimalField(max_digits10, decimal_places8, nullTrue, blankTrue) gps_lng models.DecimalField(max_digits11, decimal_places8, nullTrue, blankTrue) record_time models.DateTimeField(auto_now_addTrue) # 【审核流字段】状态机驱动非布尔值 STATUS_CHOICES ( (pending, 待审核), (approved, 已通过), (rejected, 已驳回), (expired, 已过期), ) status models.CharField(max_length10, choicesSTATUS_CHOICES, defaultpending) # 【扩展字段】预留JSONField存AI识别原始结果如{confidence: 0.92, bbox: [x,y,w,h]}) ai_result models.JSONField(nullTrue, blankTrue) # 【关联字段】外键指向User但允许匿名提交物业要求 user models.ForeignKey(User, on_deletemodels.SET_NULL, nullTrue, blankTrue) class Meta: ordering [-record_time] verbose_name 垃圾投放记录 verbose_name_plural 垃圾投放记录 # 【关键技巧】为高频查询字段加数据库索引 class Meta: indexes [ models.Index(fields[status, building_code]), models.Index(fields[record_time]), ]执行迁移前先运行python manage.py makemigrations --name initial_waste_models生成迁移文件再检查migrations/0001_initial.py中是否包含indexes定义——Django 4.2默认不自动生成索引必须手动添加。2.3 构建管理员可直操作的审核工作台不是默认Admin物业主任不会写SQL但需要看到“今天A栋湿垃圾驳回率高达47%”。我们重写Admin视图用list_display_links和list_filter实现零代码筛选# waste/admin.py from django.contrib import admin from .models import WasteRecord admin.register(WasteRecord) class WasteRecordAdmin(admin.ModelAdmin): # 【展示字段】把GPS坐标转成可读地址调用高德API但演示版用mock list_display (id, category, building_code, status, record_time, get_address) list_display_links (id, category) # 点击ID或类型进入详情页 # 【筛选器】按状态楼栋日期范围三重过滤 list_filter (status, building_code, (record_time, admin.DateFieldListFilter)) # 【搜索框】支持按楼栋号、用户昵称模糊搜索 search_fields (building_code, user__username, user__first_name) # 【动作栏】批量审核功能物业最常用 actions [approve_selected, reject_selected] def approve_selected(self, request, queryset): queryset.update(statusapproved) approve_selected.short_description 批量通过选中记录 def reject_selected(self, request, queryset): queryset.update(statusrejected) reject_selected.short_description 批量驳回选中记录 # 【地址转换】演示用mock实际替换为高德API调用 def get_address(self, obj): if obj.gps_lat and obj.gps_lng: return f{obj.gps_lat:.6f},{obj.gps_lng:.6f} return 未定位 get_address.short_description 定位坐标重启服务后访问/admin/waste/wasterecord/你会看到带筛选器、搜索框、批量操作按钮的完整工作台——这才是答辩时老师愿意多看两眼的界面。3. 让AI识别模块不拖慢后台用ONNX Runtime部署轻量级ResNet18分类模型3.1 为什么不用TensorFlow Serving——毕设部署的现实约束很多同学查资料说“TensorFlow Serving适合生产”但实际在毕设场景下① 需要额外装Docker和TF Serving二进制包学生Linux命令不熟极易失败② 模型版本管理对毕设无意义③ HTTP gRPC协议调试复杂。而ONNX Runtime只需pip install onnxruntime加载.onnx模型文件CPU推理速度比原生PyTorch快1.8倍实测ResNet18 on Intel i5-8250U。我们选用Kaggle公开的 Garbage Classification Dataset 15000张图4类用PyTorch训练后导出ONNX# train_model.py训练脚本关键段 import torch import torchvision.models as models # 加载预训练ResNet18并微调 model models.resnet18(pretrainedTrue) model.fc torch.nn.Linear(model.fc.in_features, 4) # 四分类 # 导出ONNX关键参数opset_version12兼容性最好 dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, waste_classifier.onnx, opset_version12, input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}} ) # 验证ONNX模型必做否则部署后报错找不到output import onnxruntime as ort ort_session ort.InferenceSession(waste_classifier.onnx) outputs ort_session.run(None, {input: dummy_input.numpy()}) print(fONNX输出shape: {outputs[0].shape}) # 应为 (1, 4)生成的waste_classifier.onnx约45MB放入项目根目录models/文件夹不要放git仓库大文件污染历史在答辩演示时单独提供。3.2 在Django视图中安全调用ONNX模型避免请求阻塞直接在View里ort.InferenceSession()会导致每次请求都重载模型CPU飙升。必须用Django信号或AppConfig初始化# waste/apps.py from django.apps import AppConfig import onnxruntime as ort class WasteConfig(AppConfig): default_auto_field django.db.models.BigAutoField name waste # 【关键】应用启动时加载模型到内存 def ready(self): from waste import model_loader model_loader.load_onnx_model() # waste/model_loader.py import os import onnxruntime as ort from django.conf import settings ORT_SESSION None def load_onnx_model(): global ORT_SESSION model_path os.path.join(settings.BASE_DIR, models, waste_classifier.onnx) if not os.path.exists(model_path): raise FileNotFoundError(fONNX模型未找到{model_path}) # 使用CPU执行提供者避免GPU依赖 ORT_SESSION ort.InferenceSession(model_path, providers[CPUExecutionProvider]) print(f✅ ONNX模型已加载{ORT_SESSION.get_inputs()[0].shape}) def predict_image(image_array): 输入(H,W,C) numpy数组输出类别名和置信度 if ORT_SESSION is None: raise RuntimeError(ONNX模型未加载请检查apps.py) # 图像预处理同训练时的transform import cv2 import numpy as np img cv2.resize(image_array, (224, 224)) img img.astype(np.float32) / 255.0 img np.transpose(img, (2, 0, 1)) # HWC → CHW img np.expand_dims(img, axis0) # add batch dim # 执行推理 outputs ORT_SESSION.run(None, {input: img}) probs softmax(outputs[0][0]) # 自定义softmax函数 class_idx np.argmax(probs) classes [kitchen, recyclable, hazardous, other] return classes[class_idx], float(probs[class_idx]) def softmax(x): e_x np.exp(x - np.max(x)) return e_x / e_x.sum()3.3 实现上传图片→AI识别→更新记录的原子操作带超时保护# waste/views.py from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.core.files.storage import default_storage from PIL import Image import numpy as np import io from .model_loader import predict_image csrf_exempt def upload_and_classify(request): if request.method ! POST: return JsonResponse({error: 仅支持POST}, status405) try: # 【文件校验】限制大小和格式 if image not in request.FILES: return JsonResponse({error: 缺少image字段}, status400) image_file request.FILES[image] if image_file.size 5 * 1024 * 1024: # 5MB return JsonResponse({error: 图片不能超过5MB}, status400) # 【读取图像】PIL转numpy img Image.open(image_file) img_array np.array(img.convert(RGB)) # 【AI识别】带超时防止模型卡死 import signal class TimeoutError(Exception): pass def timeout_handler(signum, frame): raise TimeoutError(AI识别超时10秒) signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(10) # 设置10秒超时 try: category, confidence predict_image(img_array) signal.alarm(0) # 取消定时器 except TimeoutError as e: return JsonResponse({error: str(e)}, status500) # 【保存记录】关联到当前登录用户 from .models import WasteRecord record WasteRecord.objects.create( categorycategory, building_coderequest.POST.get(building_code, UNKNOWN), statuspending, userrequest.user if request.user.is_authenticated else None, ai_result{category: category, confidence: confidence} ) return JsonResponse({ success: True, record_id: record.id, category: category, confidence: round(confidence, 3) }) except Exception as e: return JsonResponse({error: f处理失败{str(e)}}, status500)前端调用示例HTMLJS!-- templates/upload.html -- form iduploadForm input typefile nameimage acceptimage/* required input typetext namebuilding_code placeholder请输入楼栋号如A-03-1201 required button typesubmit上传识别/button /form script document.getElementById(uploadForm).onsubmit async function(e) { e.preventDefault(); const formData new FormData(this); const res await fetch(/api/upload/, { method: POST, body: formData, headers: {X-CSRFToken: getCookie(csrftoken)} // Django CSRF保护 }); const data await res.json(); alert(识别结果${data.category}置信度${data.confidence}); }; /script4. 避坑指南答辩前72小时必须排查的5个致命问题4.1 现象python manage.py runserver在老师电脑上报ModuleNotFoundError: No module named PIL原因Pillow安装依赖系统级libjpeg、libpng库Windows用户用pip install pillow常因编译失败而静默降级到纯Python版本功能缺失。解决① 卸载现有Pillowpip uninstall pillow② 下载预编译wheel访问 pypi.org/project/Pillow/#files 下载对应Python版本和系统架构的.whl文件如Pillow-9.5.0-cp310-cp310-win_amd64.whl③ 本地安装pip install Pillow-9.5.0-cp310-cp310-win_amd64.whl④ 验证python -c from PIL import Image; print(OK)4.2 现象Admin后台上传图片后WasteRecord.ai_result字段存的是{category: kitchen, confidence: 0.92}但数据库里显示null原因Django默认JSONField在SQLite中不支持需显式指定encoder和decoder且SQLite版本需≥3.38Django 4.2要求。解决① 检查SQLite版本python -c import sqlite3; print(sqlite3.sqlite_version)需≥3.38② 在models.py中为JSONField添加encoderimport json from django.db import models class WasteRecord(models.Model): # ... 其他字段 ai_result models.JSONField( nullTrue, blankTrue, encoderjson.JSONEncoder, # 显式指定编码器 decoderjson.JSONDecoder # 显式指定解码器 )③ 删除旧迁移文件重新makemigrations4.3 现象部署到阿里云轻量服务器后/static/路径下的CSS/JS全部404原因Django在DEBUGFalse时不自动提供静态文件必须用collectstatic收集并配置Web服务器Nginx/Apache代理。解决① 运行收集命令python manage.py collectstatic --noinput生成/staticfiles/目录② 修改nginx.conf添加静态文件locationlocation /static/ { alias /home/www/waste_system/staticfiles/; expires 1y; add_header Cache-Control public, immutable; }③ 重启Nginxsudo systemctl restart nginx4.4 现象批量导入327户居民Excel时building_code字段出现A-03-1201\x00末尾带空字符原因Excel导出时单元格格式为“文本”但实际存储含不可见Unicode控制字符如\x00。解决① 在views.py导入函数中清洗数据def clean_building_code(code): return str(code).strip().replace(\x00, ).replace(\u200b, ) # 清除零宽空格 # 导入逻辑中调用 for row in excel_data: building_code clean_building_code(row[楼栋号]) # ... 创建用户② 数据库字段加db_collationutf8mb4_unicode_ciMySQL或使用CharField的validatorsfrom django.core.validators import RegexValidator building_code models.CharField( max_length10, validators[RegexValidator(r^[A-Za-z0-9\-]$, 楼栋号只能含字母、数字、短横线)] )4.5 现象定时任务python manage.py clear_expired_records执行后数据库连接数暴涨至100原因Django默认数据库连接不自动关闭循环删除时每条delete()都新建连接。解决① 改用原生SQL批量删除避免ORM开销# management/commands/clear_expired_records.py from django.core.management.base import BaseCommand from django.db import connection class Command(BaseCommand): def handle(self, *args, **options): with connection.cursor() as cursor: cursor.execute( DELETE FROM waste_wasterecord WHERE status pending AND record_time NOW() - INTERVAL 7 DAY ) self.stdout.write(✅ 已清理7天前待审核记录)② 在settings.py中设置连接池DATABASES { default: { # ... 其他配置 CONN_MAX_AGE: 60, # 连接复用60秒 OPTIONS: { MAX_CONNS: 20, # 最大连接数 MIN_CONNS: 5, # 最小连接数 } } }5. 答辩演示的终极技巧用3个真实数据集制造“可交互的说服力”5.1 准备三组演示数据覆盖老师最可能问的场景别再用python manage.py loaddata fixtures.json那种静态数据。答辩时老师会现场提问“如果用户拍了张模糊的塑料袋系统怎么判断”——你得立刻切到对应数据集演示。我让学生准备以下三组数据集名称文件位置核心价值演示话术demo_clear.jpg/media/demo/高清标准图“这是规范拍摄的厨余垃圾AI识别置信度92%系统自动标记为‘厨余垃圾’并计入积分”demo_blur.jpg/media/demo/模糊反光图“这是保洁员在雨天拍摄的湿垃圾模型仍给出78%置信度但状态设为‘待人工审核’避免误判”demo_mixed.jpg/media/demo/混投图塑料袋菜叶“这张图被识别为‘其他垃圾’但AI结果中confidence仅0.41系统触发预警‘疑似混投请人工复核’”提示这三张图必须提前用python manage.py shell插入数据库并确保ai_result字段含{confidence: 0.78}等真实值否则演示时点开详情页是空的。5.2 在Admin后台嵌入实时统计卡片不用ECharts用Django自带聚合老师关心“系统有没有用”而不是“代码写了多少行”。我们在Admin首页加一个实时统计区# waste/admin.py from django.contrib.admin import AdminSite from django.db.models import Count, Sum from .models import WasteRecord class WasteAdminSite(AdminSite): site_header 垃圾分类管理系统后台 admin_site WasteAdminSite(namewaste_admin) # 自定义Admin首页视图 from django.shortcuts import render from django.contrib.admin.views.main import ChangeList from django.contrib.admin.options import ModelAdmin admin_site.admin_view def admin_index(request): # 【实时统计】用Django ORM聚合避免写原生SQL stats WasteRecord.objects.aggregate( totalCount(id), approvedCount(id, filtermodels.Q(statusapproved)), pendingCount(id, filtermodels.Q(statuspending)), kitchenCount(id, filtermodels.Q(categorykitchen)), recyclableCount(id, filtermodels.Q(categoryrecyclable)), ) # 【Top5楼栋】按投放量排序 top_buildings WasteRecord.objects.values(building_code).annotate( countCount(id) ).order_by(-count)[:5] context { stats: stats, top_buildings: top_buildings, site_title: 垃圾分类管理系统, } return render(request, admin/index.html, context)创建templates/admin/index.html继承Django Admin模板{% extends admin/base_site.html %} {% block content %} div classrow div classcol-md-3h3今日总投放/h3h1{{ stats.total }}/h1/div div classcol-md-3h3已通过/h3h1{{ stats.approved }}/h1/div div classcol-md-3h3待审核/h3h1{{ stats.pending }}/h1/div div classcol-md-3h3厨余占比/h3h1{{ stats.kitchen|add:0|div:stats.total|floatformat:1 }}%/h1/div /div h4投放量Top5楼栋/h4 table classtable theadtrth楼栋/thth次数/th/tr/thead tbody {% for b in top_buildings %} trtd{{ b.building_code }}/tdtd{{ b.count }}/td/tr {% endfor %} /tbody /table {% endblock %}5.3 给老师留一个“可验证的后门”用Django Shell快速验证任意逻辑答辩时老师说“你这个积分规则我输入A-03-1201应该得多少分”——别慌打开终端执行# 进入Django Shell答辩前确保已部署 python manage.py shell # 快速验证积分计算逻辑假设规则厨余垃圾1分可回收2分其他0分 from waste.models import WasteRecord from django.contrib.auth.models import User user User.objects.get(usernameteacher_demo) record WasteRecord.objects.create( ... categorykitchen, ... building_codeA-03-1201, ... useruser, ... statusapproved ... ) # 查看积分假设积分存在UserProfile模型中 user.profile.points 1 # 再录一条可回收 record2 WasteRecord.objects.create( ... categoryrecyclable, ... building_codeA-03-1201, ... useruser, ... statusapproved ... ) user.profile.points 3这个操作全程30秒比翻代码找views.py里的积分函数直观十倍。我坚持让学生在答辩PPT最后一页放一行小字“所有逻辑均可通过Django Shell实时验证”老师眼睛会亮。6. 我的血泪经验毕设答辩不是考编程是考“系统思维”的落地证据最后一次调试是在答辩前夜凌晨两点。学生突然发现当物业主任用IE11打开Admin后台时批量审核按钮点击无效。查了一圈发现是admin/js/actions.js里用了ES6的箭头函数IE11不支持。解决方案不是升级浏览器物业不可能换而是用Babel编译Django Admin的JS——但毕设没时间搞构建工具。我的妥协方案是在base_site.html模板里加一段polyfill!-- templates/admin/base_site.html -- {% load static %} script src{% static admin/js/core.js %}/script !-- IE11兼容补丁 -- script if (!window.Promise) { document.write(script srchttps://cdn.jsdelivr.net/npm/es6-promise4/dist/es6-promise.auto.min.js\/script); } if (!Array.prototype.find) { Array.prototype.find function(predicate) { for (let i 0; i this.length; i) { if (predicate(this[i])) return this[i]; } return undefined; }; } /script这件事让我彻底明白所谓“高分毕业设计”根本不是代码多炫酷而是你是否预判了真实环境里的每一个不完美——IE11、旧安卓机、物业主任的Excel水平、老师电脑上缺失的Visual C Redistributable、甚至答辩教室Wi-Fi断连时的离线缓存方案。我后来给所有学生定下铁律代码写完后必须用三台不同配置的电脑Win10Chrome、Win7IE11、UbuntuFirefox各跑一遍全流程截图存档。不是为了应付检查而是当你站在答辩台上老师问“如果……”时你能平静地说“这个问题我们测过这里是截图”。希望帮到你。本文还有配套的精品资源点击获取