ARTICLE DETAIL

资讯详情

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

Django宠物服务管理系统实战:从ORM到WebSocket部署

Django宠物服务管理系统实战:从ORM到WebSocket部署 做Django毕设源码分享这几年我经手过不少类似的项目宠物服务管理系统算是最典型的一类。这个标题看着平平无奇但它背后的技术点覆盖了用户认证、数据建模、增删改查、订单流程、后台管理、消息推送几乎把Django开发的核心链路都串起来了。所以这篇文章就围绕这套系统的设计与实现把我在实际开发和定制过程中的思路、代码细节、踩过的坑一次讲清楚。这套系统的核心场景是宠物主人需要给宠物预约洗澡、美容、医疗等服务管理员需要在后台维护服务项目、处理订单、管理用户和宠物档案。用Django来做这件事最大的优势是自带Admin后台和ORM业务开发效率极高。而且Django的MTV架构把数据、逻辑和页面分离得很干净做毕设时不管是写文档还是给评委讲思路都特别容易讲明白。如果你是正在做Django课程设计、毕业设计的同学或者想接私活做一套类似管理系统这篇文章可以直接当参考手册用。全文不绕弯子先拆功能结构再讲模型设计然后给实战代码最后是部署和答辩技巧。1. 项目整体设计与功能拆解1.1 这一类系统为什么都用Django来写先说选型。市面上的宠物服务管理系统技术栈来来去去就那几种ServletJSP、Spring Boot、PHP、Python Flask、Django。我自己在定制这类项目时给同学的默认推荐就是Django原因有三个。第一Django自带Admin后台。这个对毕设项目来说太关键了。宠物服务系统天然需要管理员维护服务项目、查看订单、管理用户如果这些后台功能全部手写页面工作量至少多三分之二。Django的Admin只需要注册模型进去一个能增删改查的管理后台就出来了省下的时间可以用来打磨用户端体验。第二ORM写起来顺手。Django的QuerySet API非常直观热词里提到的“django执行查询-删除对象”其实就是ORM里的一行代码比如MyModel.objects.filter(id1).delete()不需要拼接SQL不需要处理数据库连接的释放对于初学者来说几乎没有心智负担。第三模板系统天然适合中小型管理系统。用户端的页面用render渲染模板不需要前后端分离不需要写接口文档页面刷新即数据更新。对于毕设这种评委更看重“功能完整性”的场景这种传统渲染方式最稳不容易在答辩时被问到跨域、鉴权之类的问题而卡壳。1.2 需求拆解与模块划分做项目的第一步不是写代码而是把需求拆清楚。宠物服务管理系统要面向两类角色来设计普通用户和管理员。普通用户的典型使用链路是这样注册登录 → 添加宠物档案 → 浏览服务项目 → 选择服务并预约 → 查看订单状态 → 完成服务后评价。管理员的使用链路是登录后台 → 管理宠物服务项目 → 查看预约订单 → 处理订单状态 → 管理用户和宠物档案 → 发布公告。这两条链路交叉在一起就形成了系统的核心功能模块。我通常把它们划分为六大块用户模块注册、登录、个人信息修改、密码修改。这部分可以扩展邮箱验证或手机验证码但毕设阶段用Django自带的auth系统就够。宠物档案模块绑定宠物名称、品种、年龄、性别、体重、绝育情况、疫苗记录。这是服务预约的依赖数据没有宠物档案就无法下单。服务项目管理模块服务名称、服务类型洗澡、美容、医疗、寄养、价格、时长、封面图、详细介绍。这一块是管理员在后台维护的数据。预约与订单模块用户选择宠物和服务后创建订单记录预约时间、服务状态、支付状态。状态流转是订单模块的核心难点。评价模块订单完成后用户可对服务进行评分和文字评价。评价会展示在服务项目详情页用于倒逼服务质量。后台管理模块依赖Django Admin把模型全部挂进去再自定义一些列表页字段和筛选器。实际定制过程中有些同学还要求增加会员充值、优惠券、积分系统。我的建议是优先保住核心链路扩展功能放到“系统可扩展”这个角度去写不要一上来就把项目写得过大。因为功能越多出Bug的概率越大答辩时一旦演示崩溃前面的努力全部白费。1.3 技术架构与目录规划项目的技术架构其实非常简单Python 3.x Django 4.x/5.x SQLite或MySQL Bootstrap 少量JavaScript。Django版本的选择我一般推荐4.2 LTS版本稳定、资料多、坑少。项目目录结构我会这样规划pet_care_system/ ├── manage.py ├── requirements.txt ├── PetCare/ # 主配置目录 │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── users/ # 用户模块 │ ├── pets/ # 宠物档案模块 │ ├── services/ # 服务项目模块 │ ├── orders/ # 预约订单模块 │ └── comments/ # 评价模块 ├── static/ # 静态资源 └── templates/ # 模板目录把功能模块拆成多个app而不是全部塞进一个app里这是我个人特别坚持的做法。这样做的原因很简单模块边界清楚模型文件不会越来越大后期扩展新功能不需要撕扯旧代码。比如后边想加一个“宠物医院”功能直接新建一个app然后在主urls.py里挂上去就够了。2. 核心功能模块的模型设计与实现2.1 用户与宠物档案的数据模型用户这块直接复用Django自带的User模型再加上一个UserProfile做扩展存手机号、头像、地址。不要轻易去改User本身Django的auth体系已经很成熟在自定义用户模型上翻车的案例比比皆是。宠物档案模型是系统里的一个核心依赖。我的设计参考了实际宠物店的登记逻辑字段拆得很细from django.db import models from django.contrib.auth.models import User class Pet(models.Model): STATUS_CHOICES [ (healthy, 健康), (treatment, 治疗中), (recovering, 恢复期), ] GENDER [ (M, 公), (F, 母), ] owner models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name主人, related_namepets) name models.CharField(宠物名, max_length50) breed models.CharField(品种, max_length50) gender models.CharField(性别, max_length1, choicesGENDER) age models.IntegerField(年龄, help_text单位岁) weight models.FloatField(体重, help_text单位kg) is_neutered models.BooleanField(是否绝育, defaultFalse) health_status models.CharField(健康状况, max_length20, choicesSTATUS_CHOICES, defaulthealthy) avatar models.ImageField(照片, upload_topets/, blankTrue, nullTrue) medical_history models.TextField(病史, blankTrue) created_at models.DateTimeField(auto_now_addTrue) class Meta: verbose_name 宠物档案 verbose_name_plural verbose_name def __str__(self): return f{self.owner.username}的{self.name}这里有一个非常实用的设计细节owner字段使用ForeignKey关联User并且设置了related_namepets。这样在视图里查某个用户的所有宠物时直接用request.user.pets.all()就够了不需要再写宠物表按owner去filter。owner的on_delete用的是CASCADE意思是用户注销时他的宠物档案一并删除。这个在真实业务里不一定对如果是线下宠物店用户注销后宠物档案其实应该保留。但在毕设里CASCADE的语义更简单写代码和讲逻辑都方便。如果你想让评委觉得你想得深也可以改成PROTECT或者SET_NULL然后在文档里说“为了保护宠物历史数据用户注销不删档案”。2.2 服务项目与预约订单模型服务项目模型相对简单重点是价格、类型、封面图这些业务字段。订单模型是整个系统最复杂的表因为它要把用户、宠物、服务项目串在一起并且记录状态流转。class ServiceItem(models.Model): TYPE_CHOICES [ (bath, 洗澡), (grooming, 美容), (medical, 医疗), (boarding, 寄养), (vaccine, 疫苗), ] name models.CharField(服务名称, max_length100) service_type models.CharField(服务类型, max_length20, choicesTYPE_CHOICES) price models.DecimalField(价格, max_digits7, decimal_places2) duration models.IntegerField(耗时分钟, default60) cover models.ImageField(封面图, upload_toservices/, blankTrue, nullTrue) description models.TextField(详细介绍) is_active models.BooleanField(是否上架, defaultTrue) created_at models.DateTimeField(auto_now_addTrue) class AppOrder(models.Model): STATUS_CHOICES [ (pending, 待服务), (confirmed, 已确认), (in_progress, 服务中), (completed, 已完成), (cancelled, 已取消), ] PAY_CHOICES [ (unpaid, 未支付), (paid, 已支付), (refunded, 已退款), ] user models.ForeignKey(User, on_deletemodels.CASCADE, related_nameorders) pet models.ForeignKey(Pet, on_deletemodels.CASCADE, verbose_name宠物) service models.ForeignKey(ServiceItem, on_deletemodels.CASCADE, verbose_name服务项目) order_no models.CharField(订单号, max_length32, uniqueTrue) amount models.DecimalField(订单金额, max_digits7, decimal_places2) service_time models.DateTimeField(预约时间) status models.CharField(订单状态, max_length20, choicesSTATUS_CHOICES, defaultpending) pay_status models.CharField(支付状态, max_length20, choicesPAY_CHOICES, defaultunpaid) remark models.CharField(备注, max_length255, blankTrue) created_at models.DateTimeField(auto_now_addTrue) updated_at models.DateTimeField(auto_nowTrue)为什么要单独存一个amount字段而不是页面上用service.price直接计算因为服务价格可能会变订单一旦生成金额就必须固定下来。这是电商系统里“快照机制”的简化版本在答辩时能作为亮点讲出来。订单状态机这块我建议在模型里写一个方法来做状态迁移比如def change_status(self, new_status): allowed_transitions { pending: [confirmed, cancelled], confirmed: [in_progress, cancelled], in_progress: [completed], } if new_status in allowed_transitions.get(self.status, []): self.status new_status self.save(update_fields[status, updated_at]) return True return False这样把状态流转规则收口在一个方法里视图里调用时不会出现“已完成的订单被改成待服务”这种脏状态。2.3 评价模型与数据关联评价表是订单完成之后的衍生数据。有一个容易踩的坑同一个订单只能评价一次不能让用户刷评。所以评论表的order字段要设置unique约束。class Comment(models.Model): order models.OneToOneField(AppOrder, on_deletemodels.CASCADE, related_namecomment) user models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name评价用户) service models.ForeignKey(ServiceItem, on_deletemodels.CASCADE, verbose_name所评服务) rating models.IntegerField(评分, default5, choices[(i, f{i}星) for i in range(1, 6)]) content models.TextField(评价内容) created_at models.DateTimeField(auto_now_addTrue)这里选OneToOneField而不是ForeignKey就是通过数据库层面的唯一约束来保证“一单一评”。同时订单模型上可以加一个has_commented属性放业务逻辑判断通过查找rder.comment是否抛异常来判断也可以直接在订单表加个Boolean字段更省事。考虑到查询频率加字段省一次关联查询如果追求代码简洁就用OneToOne反向关系。2.4 数据库迁移的实操细节模型写完之后别急着runserver。先执行两步python manage.py makemigrations python manage.py migratemakemigrations只生成迁移文件不操作数据库。这一步干的是“把模型变化记录下来”。migrate才是真正把表建出来。我见过太多同学在模型改了之后直接migrate结果报错一堆然后问怎么回事。原因都是这个忘记先makemigrations。另外如果你对已有的模型改了字段名makemigrations会问你是不是要删除某个字段这时候一定要看清提示。比如你把name改成pet_namemigrate执行的其实是drop column name然后add column pet_name如果表里已经有数据这个操作会丢数据。正确做法是加一个字段然后写一个数据迁移脚本把值copy过去再删旧字段。毕设阶段数据量不大丢了也就丢了但养成这个意识能让你以后在企业项目里少闯大祸。3. 视图逻辑与关键功能实现3.1 URL路由设计与视图函数组织Django的URLConf是项目里很容易写乱的地方。正确姿势是主urls.py用include把请求分发给各个appapp内部再维护自己的一份urls.py。# 主 urls.py from django.contrib import admin from django.urls import path, include from django.conf import settings from django.conf.urls.static import static urlpatterns [ path(admin/, admin.site.urls), path(, include(apps.users.urls)), path(pets/, include(apps.pets.urls)), path(services/, include(apps.services.urls)), path(orders/, include(apps.orders.urls)), path(comments/, include(apps.comments.urls)), ] if settings.DEBUG: urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)静态文件和媒体文件的serve开发环境可以放心交给Django但生产部署一定要交给Nginx或者云存储。这块后面部署部分细说。各app内部再用class-based view或function-based view写逻辑。我个人偏爱function-based view理由很直接毕设项目的逻辑嵌套不大FBV每一行写起来都是“所见即所得”对讲代码也有帮助。如果你熟悉class-based view用ListView和DetailView可以把列表页和详情页代码压缩到极短但理解成本更高。这套系统我推荐FBV为主。3.2 用户注册登录与Token/Cookie会话处理热搜词里有“django cookie 设置 token”这说明很多人在用户会话这里卡过。Django默认的会话机制是Cookie里存sessionid服务端对应的session记录里有用户ID。也就是说你不需要自己写token逻辑直接用request.session或者request.user就够了。但是有些同学做的项目用了部分前后端分离或者想用token方式做接口鉴权这时可以这样处理登录视图from django.contrib.auth import login, authenticate from django.http import JsonResponse from django.views.decorators.http import require_POST import json require_POST def login_view(request): data json.loads(request.body) username data.get(username) password data.get(password) user authenticate(request, usernameusername, passwordpassword) if user is not None: login(request, user) response JsonResponse({code: 200, msg: 登录成功}) # 如果前端需要自己保存token可以在cookie里额外写一个 response.set_cookie(mytoken, user.username, max_age3600) return response return JsonResponse({code: 400, msg: 用户名或密码错误})Django自带认证系统的密码加密用的是PBKDF2不需要自己写哈希逻辑。你要做的只是把authenticate和login用对就行。注册的视图稍复杂一点要先检查重名用户然后create_user保存密码注意不是createcreate_user会自动加密密码。3.3 服务预约下单的核心流程下单是整个系统最核心的流程写的时候要同时处理事务和并发。一个预约服务动作涉及的操作是查询服务项目是否存在、查询宠物归属、创建订单、扣减服务名额如果有库存概念。Django里用transaction.atomic()包起来能保证这串操作要么全部成功要么全部失败回滚。from django.db import transaction from django.utils import timezone from datetime import timedelta import uuid def create_order(request): if request.method POST: pet_id request.POST.get(pet_id) service_id request.POST.get(service_id) service_time_str request.POST.get(service_time) # 这里把字符串转成datetime简单做一层格式校验 try: service_time timezone.datetime.fromisoformat(service_time_str) except ValueError: return JsonResponse({code: 400, msg: 预约时间格式错误}) pet Pet.objects.filter(idpet_id, ownerrequest.user).first() service ServiceItem.objects.filter(idservice_id, is_activeTrue).first() if not pet or not service: return JsonResponse({code: 400, msg: 宠物或服务不存在}) order_no uuid.uuid4().hex[:16] with transaction.atomic(): order AppOrder.objects.create( userrequest.user, petpet, serviceservice, order_noorder_no, amountservice.price, service_timeservice_time, statuspending, pay_statusunpaid, ) return JsonResponse({code: 200, msg: 下单成功, order_id: order.id})这里我用了uuid截断生成订单号够用且简单。如果想要更漂亮可以用“日期随机数”拼。需要注意从POST拿到的service_time很可能是一个字符串直接赋给DateTimeField会报错所以一定要先转成datetime对象。这部分看起来很简单但每年都有人被字符串格式卡很久。3.4 订单列表与状态流转的视图处理用户端的订单列表需要展示订单状态、服务项目Name、宠物名字、预约时间。为了减少数据库查询可以这样写orders (AppOrder.objects .filter(userrequest.user) .select_related(pet, service) .order_by(-created_at))select_related就是JOIN查询把pet和service提前取出来避免遍历每个订单时再去查一次数据库这个性能细节在数据量小的时候看不出来但是答辩时你主动说出来评委就会觉得你懂优化原理。状态流转的视图比如“确认订单”“取消订单”和“完成服务”逻辑都一样先检查当前用户是否有权限操作再检查目标状态是否合法最后调用之前写的change_status完成迁移。3.5 定时任务与WebSocket消息推送热词里有一条“python django websocket实现后台有数据前端推送”这说明大家很关心实时更新。宠物服务管理系统里最典型的需求是用户下单后管理员后台网页不刷新也不用手动点“刷新”就能看到新订单弹出来。实现方案有几种。最简单的是前端轮询setInterval每隔5秒请求一个接口获取未读订单数。这种方式实现成本最低但对服务器有轻微压力适合毕设展示因为肉眼可能感觉不到延迟。更漂緻的方案是WebSocket。Django本身原生不支持WebSocket需要装channels库把ASGI跑起来。有一步要特别注意channels的版本要和Django版本匹配同时你还需要一个ASGI服务来跑WebSocket比如daphne。项目结构会变成这样# asgi.py import os from django.core.asgi import get_asgi_application from channels.routing import ProtocolTypeRouter, URLRouter from channels.auth import AuthMiddlewareStack import apps.orders.routing os.environ.setdefault(DJANGO_SETTINGS_MODULE, PetCare.settings) application ProtocolTypeRouter({ http: get_asgi_application(), websocket: AuthMiddlewareStack( URLRouter(apps.orders.routing.websocket_urlpatterns) ), })然后订单创建时通过channel_layer.group_send把消息推送到订单管理组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)( order_group, { type: order.message, message: f新订单{order.order_no} } )前端再用JavaScript的WebSocket对象连接ws://你的域名/ws/orders/收到消息后弹个提示或者自动刷新列表。这块如果想加进毕设会把项目拔高一个档次。我在定制时经常给有余力的同学加入这个功能它能让评委员看到你掌握了同步通信之外的知识。4. 前端页面与交互实现4.1 Django模板的继承与组件复用Django模板系统有个很好用的特性叫模板继承。base.html定义页面的骨架然后子模板只需要写自己内容区的block就够了。管理系统通常会有一个统一的后台布局包括左侧菜单和顶部导航。{# base.html #} !DOCTYPE html html langzh head meta charsetUTF-8 title{% block title %}宠物服务管理系统{% endblock %}/title link hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.3.0/dist/css/bootstrap.min.css relstylesheet {% block extra_css %}{% endblock %} /head body nav classnavbar navbar-expand-lg navbar-dark bg-primary a classnavbar-brand href/宠物服务管理系统/a {% if user.is_authenticated %} span当前用户{{ user.username }}/span {% endif %} /nav div classcontainer mt-3 {% block content %}{% endblock %} /div script srchttps://cdn.jsdelivr.net/npm/bootstrap5.3.0/dist/js/bootstrap.bundle.min.js/script {% block extra_js %}{% endblock %} /body /html子模板里{% extends base.html %}之后填content就行。模板里对request.user的判断非常常用通过if user.is_authenticated来区分登录和未登录状态下的页面导航。4.2 宠物档案的图片上传与回显宠物档案的头像和服务项目的封面都涉及文件上传。Django处理文件上传其实很简单表单加enctypemultipart/form-data视图里从request.FILES拿文件然后赋给模型字段save。开发环境里图片回显要确保settings.py里配置了MEDIA_URL和MEDIA_ROOT并且在主urls.py里挂上static。很多同学预览图片时404就是忘了加这一段from django.conf import settings from django.conf.urls.static import static urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)模板里显示图片就非常干净img src{{ pet.avatar.url }} classimg-thumbnail stylewidth: 120px;还要注意一个问题给ImageField指定upload_to时如果写的是带日期的路径比如pets/%Y/%m/Django会在保存时自动按照日期建目录存文件。这个设计很科学不会让所有用户头像堆在同一层目录。4.3 服务列表与详情页的展示策略服务项目的首页推荐位和分类浏览是比较容易出效果的功能。思路是视图里取服务项目数据时按不同类型分组传过去比如洗澡类、美容类。首页用卡片铺开显示封面和价格。对于详情页大致是服务介绍、参考价格、限制条件、用户评价列表。评价列表其实就是正向查询comments Comment.objects.filter(serviceservice).select_related(user, order)评价显示里我建议顺带把用户的宠物信息也带出来因为宠物服务的用户评价非常依赖宠物背景比如“我家柴犬很凶小哥依然护理得很好”这种内容特别有说服力。做一个小标签展示宠物品种既丰富了页面又增加了数据关联的复杂度答辩时有东西可讲。4.4 使用Django Unfold美化后台Django默认的Admin真心不好看而且总显得像是“套模板凑出来的”。这里推荐一个开源组件Django Unfold。它提供了一套现代化风格的Admin后台字体、间距、侧边栏都比原版好看很多安装也简单。pip install django-unfold在settings.py里把INSTALLED_APPS顶部的admin替换掉INSTALLED_APPS [ unfold, django.contrib.admin, # ... 其他app ]然后给需要美化的ModelAdmin指定Unfold样式列表页就能用上现代化卡片和筛选面板。我第一次看到Unfold的效果时是有点惊讶的没想到Django Admin也能这么好看。如果你在答辩时演示后台评委会在第一眼就留下好印象这是个小成本高回报的细节。5. 手动从零到上线完整实操步骤5.1 准备环境与初始化项目先确认你的开发环境。推荐Python 3.10或3.11Django 4.2 LTS。python -m venv venv # Windows: venv\Scripts\activate # Mac/Linux: source venv/bin/activate pip install django4.2 pillow mysqlclient channelspillow必须装否则ImageField字段会在上传图片时报错。mysqlclient是连接MySQL的驱动如果你用SQLite可以跳过但我的建议是尽量早点切换到MySQL与真实生产环境保持一致。早期先用SQLite把功能跑通也行最后部署阶段再切库。然后创建Django项目django-admin startproject PetCare cd PetCare python manage.py startapp users python manage.py startapp pets python manage.py startapp services python manage.py startapp orders python manage.py startapp comments注意我创建app的时候是按功能模块拆的。这样目录清晰代码不会堆到一起。apps这个包名不要和项目里的app冲突所以我早期的写法是直接在项目根目录下建apps目录包再在每个模块里建独立文件夹。为了简单你可以全部建在根目录下。5.2 settings.py 的关键配置settings.py是Django项目的配置总控。我通常会按下面的思路逐项设置。INSTALLED_APPS [ unfold, django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, apps.users, apps.pets, apps.services, apps.orders, apps.comments, ] MIDDLEWARE [ django.middleware.security.SecurityMiddleware, django.contrib.sessions.middleware.SessionMiddleware, django.middleware.common.CommonMiddleware, django.middleware.csrf.CsrfViewMiddleware, django.contrib.auth.middleware.AuthenticationMiddleware, django.contrib.messages.middleware.MessageMiddleware, django.middleware.clickjacking.XFrameOptionsMiddleware, ] TEMPLATES [ { BACKEND: django.template.backends.django.DjangoTemplates, DIRS: [BASE_DIR / templates], APP_DIRS: True, OPTIONS: { context_processors: [ django.template.context_processors.debug, django.template.context_processors.request, django.contrib.auth.context_processors.auth, django.contrib.messages.context_processors.messages, ], }, }, ] DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: pet_care, USER: root, PASSWORD: yourpassword, HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4}, } }DATABASES里特意加了utf8mb4因为如果数据库库表默认字符集是utf8插入中文会报“Incorrect string value”。很多同学在搭建时被中文写入问题卡死这个参数能提前帮你避免大半麻烦。如果仍然遇到去MySQL里看库和表的字符集统一改成utf8mb4。时区和语言设置建议这样LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghai USE_I18N True USE_TZ TrueUSE_TZ保持True存入数据库的时间是UTC时间模板渲染时Django会自动转换为当地时间。这个配置能避免很多时间显示混乱的问题但也有些人习惯USE_TZFalse直接本地时间入库存。毕设里这两种都能跑但我推荐保持True因为更专业、也能应对跨国部署的情况。5.3 运行迁移与创建管理员环境配好后执行模型迁移python manage.py makemigrations apps.users apps.pets apps.services apps.orders apps.comments python manage.py migrate然后创建超级管理员账号python manage.py createsuperuser用这个账号可以登录Admin后台。你可以在Admin.py里把每个模型注册进去调整列表展示字段admin.register(AppOrder) class OrderAdmin(admin.ModelAdmin): list_display (order_no, user, pet, service, amount, status, pay_status, created_at) list_filter (status, pay_status, service_time) search_fields (order_no, user__username, pet__name, service__name) date_hierarchy created_atdate_hierarchy设置后后台列表顶部会多出按时间筛选的层级菜单对订单管理非常实用。5.4 启动开发服务器与本地验证python manage.py runserver 127.0.0.1:8000浏览器访问 http://127.0.0.1:8000 会看到首页。然后依次验证核心链路注册新用户、添加宠物档案、浏览服务项目、创建预约、在Admin里修改订单状态、用户端看到状态变化、发表评价。这一套流程走通功能性演示的主干线基本就齐了。5.5 部署到服务器的实战建议本地开发完成之后如果要部署给评委看还是需要一台云服务器。国内阿里云、腾讯云的轻量服务器即可配置选2核4G就够。部署选型我推荐用Gunicorn Nginx MySQL操作不复杂性能也够。先交代一下环境服务器装Python 3.10、nginx、mysql。项目代码传到服务器后pip install -r requirements.txt python manage.py collectstatic --noinput python manage.py migrate gunicorn PetCare.wsgi:application --bind 0.0.0.0:8000 --workers 3然后配置Nginx将80端口代理到8000server { listen 80; server_name your_domain_or_ip; client_max_body_size 20M; location /static/ { alias /path/to/project/staticfiles/; } location /media/ { alias /path/to/project/media/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }client_max_body_size这个配置很容易被忽略如果不改上传宠物图片超过1M就会被Nginx挡掉而Django开发环境不会。部署后发现问题别慌先看Nginx错误日志多半是文件大小限制。更省心的方法是直接使用Supervisor来守护Gunicorn这样即使服务器重启服务也能自动拉起。毕设阶段不要求高可用但把这套流程跑通本身就是值得写进简历的DevOps小项目。部署过程中最容易出的问题有这么几个collectstatic没有执行页面样式全丢。解决settings里配置STATIC_ROOT然后执行collectstatic并确保Nginx的static alias指向collect后的目录。ALLOWED_HOSTS没有配置好。Django部署后报Bad Request基本都是ALLOWED_HOSTS里没有加域名或IP。settings.py里DEBUG还是True。DEBUGTrue时Django会错误地默认接管静态文件处理同时暴露一些断言错误和安全信息。生产环境DEBUG要设为False还要配合SECRET_KEY的处理方式是环境变量或单独配置文件。6. 常见问题与排查技巧实录6.1 数据库中文乱码问题的根因这个问题太常见了几乎我经手的项目都遇到过。表现是页面中文正常显示但存进MySQL后变成???。根因两处第一数据库连接串或DATABASES配置里没写charsetutf8mb4第二MySQL建库时字符集设成了utf8或latin1。光改Django配置还不够必须把数据库本身库和表的字符集都改成utf8mb4。SQL语句如下ALTER DATABASE pet_care CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE pet_care_pet CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;这是运维和开发都要懂的一个坑。我刚开始也是反复翻车后来形成习惯建库语句直接把字符集写在后面一劳永逸。6.2 CSRF验证失败的三大场景Django强制要求POST请求必须带CSRF Token。新手遇到的CSRF失败报错绝大多数来自三种场景。第一种是模板里的form忘记加{% csrf_token %}。加上就好。第二种是使用Ajax发送POST请求但没带X-CSRFToken头。解法是在页面里读取Cookie里的csrftoken设置到请求头fetch(/api/order/, { method: POST, headers: { Content-Type: application/json, X-CSRFToken: getCookie(csrftoken) }, body: JSON.stringify(data) });第三种是使用了缓存或跨域导致Cookie未携带。这个排查思路比较绕建议先用浏览器的开发者工具看Cookie和请求头逐项核对。6.3 图片上传成功但页面不显示upload成功、数据库有记录、media文件也已生成但访问图片URL是404。这种问题十次有九次是URL配置缺失。就本文来说因为MEDIA_ROOT和MEDIA_URL我习惯这样配MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / media同时记得主urls.py末尾加上static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)。没有这段开发环境的/media/请求就不会被处理。另一个很隐蔽的情况浏览器缓存了旧页面图片路径没刷新这时清缓存或者CtrlF5不要在上面耗太多时间。6.4 性能优化与常用Debug排查手段毕设的数据量很小但问到性能优化时你可以说三个点。第一用select_related优化外键查询。列表页展示订单时一次性JOIN用户、宠物、服务表避免N1查询。第二给高频查询的字段加索引。订单表里user、status、created_at这三个字段很常用直接通过Django的Meta.indexes定义然后makemigrations执行一遍即可。class Meta: indexes [ models.Index(fields[user, status]), models.Index(fields[created_at]), ]第三使用Django Debug Toolbar看SQL执行时间和数量。这个工具能看到页面运行期间实际执行的SQL语句以及每条SQL的耗时。装了toolbar之后每次刷新页面右侧会有一个工具栏数据库查询数和耗时一目了然。你在文档里附带两张截图答辩时就像手里多了一张王牌。7. 文档编写与答辩展示要点7.1 项目文档结构怎么设计定制一条龙服务里文档占的分量很重。这部分通常是一个系统说明书包含项目背景、需求分析、系统设计、数据库设计、功能模块说明、测试说明、部署说明、总结展望。写文档时我的建议是抓三点。第一数据库设计章节要配ER图。很多同学文字洋洋洒洒却没有一张实体关系图。画一张“用户-宠物-订单-服务项目-评价”的关系图比写五百字强得多。第二功能模块描述不要只写“实现了登录注册”而应该写“实现了基于Django认证组件的注册、登录、退出功能密码通过PBKDF2算法加密存储登录后通过Session保持会话状态。”措辞越具体越显得你确实做过。第三测试章节至少要写五六条核心测试用例覆盖登录、下单、状态流转、评价等场景。比如测试用例编号TC001前置条件就是要有一个服务项目和一只宠物。7.2 答辩现场如何演示系统答辩演示顺序我测试过很多轮最稳的顺序是先演示用户端完整链路再说后台Admin管理最后展示代码中的亮点片段。具体演示流程推荐如下打开系统首页展示服务项目列表和轮播图。注册一个新用户然后登录。添加一只宠物档案填品种、体重、年龄。选择一个服务项目选择刚才的宠物预约明天上午10点。打开Admin后台展示新订单出现修改状态为“已确认”。返回用户端不刷新页面利用WebSocket看到订单状态变化或者手动刷新。将订单状态改为“已完成”回到评价发表评价。刷新服务详情页看到新评价。整个流程环环相扣评委会顺着你设定的逻辑走全程基本没有冷场。中途可能出现的问题你自己要先心里有数比如创建订单时服务时间格式不合法需要在演示前准备好一个合法的日期图片上传展示失败时快速说出“这是静态文件配置细节我们稍后展示代码里怎么配置”。7.3 一套代码的可持续扩展点宠物服务管理系统虽然是一个标准的管理系统但它的扩展空间非常大。在定制过程里我会让客户优先考虑三种强化方向。第一种是接入在线支付。比如对接支付宝沙箱或微信支付网关用户下单后跳转支付然后通过回调更新支付状态。这是一个与实际商业系统接轨的增强能显著提升项目复杂度。第二种是消息通知机制。除了WebSocket实时通知还可以接入QQ邮箱的SMTP发信功能订单状态变化时给用户发邮件。Django的send_mail函数配置简单原型很快就能跑通。第三种是数据可视化。在后台增加一个统计看板展示每天的服务订单量、销售额、热门服务排行。用ECharts画一个折线图和一个饼图就能让评委会眼前一亮。这些扩展点的取舍原则是核心链路稳定优先扩展功能挑选最出彩的一两个不要贪多。8. 一条龙定制服务到底做了什么标题里写了“一条龙定制”这五个字背后不是空话。它指的是根据学生实际场景把这套系统调整成能独立运行、能讲清楚、能通过答辩的完整交付物。通常包含这么几项工作搭好环境和基础模板、把核心业务数据做成样例、写配套的开发文档、讲解每一段关键代码的含义、回答预期中评委可能提出的刁钻问题。代码讲解尤其重要。我会把URLConf、Model、View、Template分成四层来带着看先讲数据怎么存再讲数据怎么流转最后讲页面怎么渲染一条线串下来思路就清爽了。很多同学一开始会觉得代码是“别人的东西”讲不出来。但代码只要过一遍设定好的主线每个函数的关键步骤能说出“这里为什么这么写”就已经具备答辩状态了。不要试图背代码一定要理解代码。选择定制时一定要避开几个坑。第一是“只给代码不给文档”这种交付在后面答辩时会很被动。第二是“代码里全是个人依赖”比如数据库名写死了某个密码然后你换一台电脑就起不来。第三是“没有样例数据”系统界面空空如也演示效果大打折扣。一定要在交付前把样例数据准备充分哪怕只是几条模拟的宠物档案和订单记录演示时视觉冲击力完全不同。9. 个人踩坑后的几点心得最后说一些做Django开发这些年我个人觉得最值得记住的东西。第一尽早处理数据库环境和编码配置哪怕前期用SQLite也请从一开始就保证中文显示正常。中文乱码这个问题一旦等数据量大了再修会非常痛苦。第二订单和支付相关的业务一定要用事务不要图省事。宠物服务的订单没有支付回调那么复杂但保证数据的原子性永远是写代码的职业习惯一个人的代码靠不靠谱看他对事务和下凡式的细节处理就能看出来。第三时间字段统一用DateTimeField不要用字符串。字符串比较时间大小会出很多莫名其妙的bug而且索引排序的逻辑也不对。如果你发现自己的代码里出现了字符串拼接日期趁早改成datetime。第四项目交付时requirements.txt一定要导出来并且把Django、pillow、channels这些版本号都锁死。否则过半年再装环境依赖冲突会让人心态爆炸。导出更准确的依赖列表可以执行pip freeze requirements.txt然后在虚拟环境里测试一遍安装。做这套宠物服务管理系统最大的收获不是代码本身而是通过一个完整业务场景把所有Django知识点串成了一条线。数据库设计、ORM查询、模板渲染、Admin配置、文件上传、部署上线、甚至WebSocket都在这一个项目里找到了自己的位置。你把这个项目彻底吃透就不仅仅是会一个管理系统而是对Web开发的核心链路有了真切的体感。如果你正在做同类项目遇到具体的代码报错或者设计不知道怎么取舍不要硬扛多查日志、多断点调试、多拆分模块去复现问题。把一次排查的过程记录下来就是你最好的项目经验。
返回列表