
毕业设计做到宠物领养管理系统Django确实是最稳妥的选型之一。这个题目标号 46798 我一看就有印象属于典型的 Web 全栈管理类毕设既有用户端的内容展示又有管理端的审核闭环技术栈集中、业务逻辑清晰用来答辩非常能说明问题。这篇文章我就以这个项目为蓝本把从数据库设计、页面流程、审核状态机到部署上线的完整实现过程拆开讲一遍并附上真实开发中容易踩的坑。如果你正在做同类系统或者准备用 Django 做毕设可以直接照着这套思路去搭。1. 毕设题目拆解领养系统到底要做什么功能先别急着写代码。宠物领养管理系统这个题目第一件事是把系统拆成两条使用主线普通用户访客/领养人和后台管理员。很多同学一上来就建一堆表最后自己都说不清每张表存在的意义答辩的时候一问一个卡壳那是典型的“需求没拆干净”。用户端要做的事站在一个想领养宠物的人角度想非常直观注册登录、浏览待领养宠物列表、按品种或年龄筛选、点进宠物详情页看性格描述和照片、提交领养申请、查看申请审核进度、收藏喜欢的宠物、查看平台公告。这些功能对应的就是一个“内容消费 互动申请”的流程缺了任何一个环节用户都会觉得系统不完整。管理端要做的事就是保证这个流程合规流转管理员发布和管理宠物信息上架、下架、修改资料、查看所有用户的领养申请、审核并给出通过或拒绝的意见、管理用户账号和公告内容。这一端的核心不是功能数量而是“审核闭环”——用户提交申请后管理员必须在后台能处理处理结果必须能回传给用户端宠物状态必须同步变化。这个闭环做不到系统就是个半成品。这里有个很重要的定位问题毕设系统不需要做得像淘宝那样大而全但流程必须自洽。我做过不少毕设指导见过最普遍的问题就是功能列表写得很长实际上一堆按钮是摆设。这个题目真正的得分点就三个领养申请状态流转是否完整、宠物与申请之间的数据关联是否严谨、后台管理是否顺手。这三个点抓住了后面所有开发工作都是围绕它们铺开的。技术栈方面题目限定 Django那么前端就用服务端渲染的模板系统配合 Bootstrap数据库默认 SQLite后续需要可以切 MySQL文件存储用本地 static/media。这套组合对毕设来说非常合适Django 自带的 ORM、Admin 后台、Form 校验、用户认证能省掉大量重复造轮子的工作你省下来的时间应该投入到业务逻辑和界面细节上。2. 为什么偏偏是 Django框架选型与项目结构设计选 Django 做这类管理系统不是因为它花哨是因为它规整。拿 Flask 对比你就明白了Flask 太灵活模型、表单、权限这些都要自己搭或者装第三方库明明我们想做一辆整车结果还要自己焊轮子对毕设来说这不是自由是负担。Django 则是默认给你一整套基础设施而且大而全的东西一般都对新手友好——它替你做了大部分工程化决策你只需要按它的约定写业务。具体到我们这个题目Django 有四个点直接命中需求自带 Admin 后台。宠物表、领养申请表、用户表建好之后Django Admin 几乎零成本就有一个可以操作数据的管理界面。虽然我们还是会自己写管理页面但 Admin 在开发期调试数据、在答辩期现场展示都是保底方案。用户认证开箱即用。注册、登录、会话、权限这些代码不需要从零写auth应用里全都有。你只需要自定义一个注册页用UserCreationForm改一改就能用。ORM 迁移机制。改模型字段后执行makemigrations和migrate就同步数据库结构不用手写 SQL对不熟悉数据库的同学非常友好。而且 Django 的 QuerySet 在后期优化查询比如避免 N1 问题时优势很明显。Form 与 CSRF 防护。Django 表单负责渲染、校验、错误回显一条龙模板里加{% csrf_token %}就能防跨站请求伪造这些安全细节在毕设答辩时是能拿出来说的加分点。项目结构上我用的是传统但非常清晰的目录布局pet_adoption/ ├── manage.py ├── config/ # 项目配置settings/urls ├── apps/ │ ├── users/ # 用户注册登录、个人中心 │ ├── pets/ # 宠物信息、分类、收藏 │ ├── adoption/ # 领养申请、审核状态 │ └── notice/ # 公告内容 ├── static/ # 前端资源 ├── media/ # 上传的宠物图片 └── templates/ # 页面模板把功能拆分成多个 app而不是所有逻辑堆在一个app里这是 Django 工程化的基本素养。每个 app 只负责自己的领域后期维护、排查问题、答辩讲解都更清晰。比如领养审核的逻辑只会在adoption这个 app 里出现别人问你“审核状态存在哪”你直接说在adoption.models.AdoptionApplication.status一句话就能讲明白。3. 数据模型设计四张核心表撑起整个系统模型设计是整个项目的地基。这地方如果设计错了后面写视图、写页面全会被拖累。领养管理系统的核心表我建议就这四张用户表、宠物分类表、宠物信息表、领养申请表另外再配一张公告表做信息展示。3.1 用户表不需要自己建 User 表Django 的auth.User已经包含用户名、密码、邮箱、权限等字段。如果你还需要额外的用户信息比如手机号就用OneToOneField扩展一张Profile表。毕设层面直接用auth.User就够了少做多余的事。3.2 宠物分类表class PetCategory(models.Model): name models.CharField(max_length50, uniqueTrue, verbose_name分类名称) description models.TextField(blankTrue, verbose_name分类描述) created_at models.DateTimeField(auto_now_addTrue) class Meta: verbose_name 宠物分类 verbose_name_plural verbose_name这张表不要做得太重里面存“狗”“猫”“兔子”这种级别就好具体到品种可以放到宠物表的字段里。3.3 宠物信息表class Pet(models.Model): PET_STATUS_CHOICES ( (available, 待领养), (adopted, 已领养), (off_shelf, 已下架), ) category models.ForeignKey(PetCategory, on_deletemodels.PROTECT, verbose_name分类) name models.CharField(max_length50, verbose_name宠物昵称) breed models.CharField(max_length50, blankTrue, verbose_name品种) age models.PositiveIntegerField(verbose_name年龄(月)) gender models.CharField(max_length10, choices((male,公),(female,母)), verbose_name性别) is_vaccinated models.BooleanField(defaultFalse, verbose_name是否已打疫苗) is_neutered models.BooleanField(defaultFalse, verbose_name是否已绝育) description models.TextField(verbose_name性格描述) avatar models.ImageField(upload_topets/, blankTrue, verbose_name照片) status models.CharField(max_length20, choicesPET_STATUS_CHOICES, defaultavailable, verbose_name状态) created_at models.DateTimeField(auto_now_addTrue, verbose_name发布时间)这里有个关键点on_deletemodels.PROTECT。分类一旦绑定了宠物就不允许随便删防止出现宠物指向一个不存在分类的脏数据。宠物状态用status字段而不是直接删记录是为了保留历史痕迹——一只宠物被领养后你仍然能在系统里看到它的档案这在展示效果上比“消失”更合理也方便管理员统计领养数据。3.4 领养申请表class AdoptionApplication(models.Model): STATUS_CHOICES ( (pending, 待审核), (approved, 已通过), (rejected, 已拒绝), (completed, 已完成), ) user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE, verbose_name申请人) pet models.ForeignKey(Pet, on_deletemodels.CASCADE, verbose_name宠物) reason models.TextField(verbose_name领养理由) housing models.CharField(max_length200, verbose_name居住情况) experience models.TextField(blankTrue, verbose_name养宠经验) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultpending, verbose_name审核状态) admin_remark models.TextField(blankTrue, verbose_name管理员备注) created_at models.DateTimeField(auto_now_addTrue) updated_at models.DateTimeField(auto_nowTrue)这张表是系统的“心脏”。所有审核相关的页面、状态判断、宠物状态联动都是围绕它展开。注意我加了completed已完成状态审核通过后领养关系还需要一个“确认完成”的动作这样状态机才是完整的从申请到最终成交都有据可查。数据模型设计完了之后自己去验证一下逻辑用户提交申请 - 申请表生成pending管理员点击通过 - 申请表变approved同时宠物status变adopted管理员点击拒绝 - 申请表变rejected宠物保持available可以继续被别人申请。这个流转想清楚写视图逻辑就有把握了。4. 用户端开发从注册登录到提交领养申请模型层稳定之后用户端的开发其实是顺着流程走的。我按一个真实用户的使用顺序来讲这样你写代码的时候也有代入感。4.1 注册与登录用 Django 内置的auth应用做认证。注册视图用UserCreationForm定制一下增加邮箱字段保存用户后直接登录def register(request): if request.method POST: form CustomUserCreationForm(request.POST) if form.is_valid(): user form.save() login(request, user) return redirect(pets:pet_list) else: form CustomUserCreationForm() return render(request, users/register.html, {form: form})登录就用LoginView加一个模板就行不需要自己从头写会话逻辑。模板里用{{ form.as_p }}快速输出表单再自己控制一下样式即可。这里有个小细节一定要在视图里加登录之后的重定向逻辑否则用户登录完还停在登录页体验很差答辩演示的时候也会显得不专业。4.2 宠物列表和筛选宠物列表是游客也能访问的公开页面但只有登录用户能提交申请。列表页要展示宠物卡片包含照片、昵称、品种、状态标记。筛选功能的实现比想象中简单用基础的filter链式查询就够了def pet_list(request): pets Pet.objects.filter(statusavailable).select_related(category) category request.GET.get(category) gender request.GET.get(gender) vaccinated request.GET.get(vaccinated) if category: pets pets.filter(category_idcategory) if gender: pets pets.filter(gendergender) return render(request, pets/pet_list.html, { pets: pets, categories: PetCategory.objects.all(), })select_related在这里要养成习惯。宠物表外键关联了分类表列表页要显示分类名如果不做关联查询每渲染一个宠物卡片就会多一次数据库查询宠物多了页面就卡这叫 N1 问题。select_related一条语句把分类数据带出来这是个值得在答辩时主动讲出来的优化点。4.3 宠物详情与申请提交详情页除了展示宠物信息还要根据当前状态显示不同内容宠物可领养且用户已登录显示“申请领养”按钮不可领养则显示“已找到温暖的家”用户已经申请过显示“您已提交申请等待审核”。提交申请的表单需要把user和pet跟表单里的内容一起保存class AdoptionForm(forms.ModelForm): class Meta: model AdoptionApplication fields [reason, housing, experience] widgets { reason: forms.Textarea(attrs{rows: 3, placeholder: 请说明您为什么想领养它}), housing: forms.TextInput(attrs{placeholder: 例如自有住房60平米小区房}), } def apply_adoption(request, pet_id): pet get_object_or_404(Pet, idpet_id) if pet.status ! available: messages.error(request, 这只宠物已经名花有主啦) return redirect(pets:pet_detail, pet_idpet.id) if AdoptionApplication.objects.filter(userrequest.user, petpet, statuspending).exists(): messages.warning(request, 您已经提交过申请请等待审核) return redirect(pets:pet_detail, pet_idpet.id) if request.method POST: form AdoptionForm(request.POST) if form.is_valid(): application form.save(commitFalse) application.user request.user application.pet pet application.save() messages.success(request, 申请提交成功我们会尽快审核) return redirect(adoption:my_applications) else: form AdoptionForm() return render(request, adoption/apply.html, {form: form, pet: pet})这段逻辑里有三个关键判断分别是宠物状态校验、重复申请校验和表单数据绑定。尤其第二个判断如果不做同一个用户可以反复提交几十条申请管理端审核页面会炸掉。这属于典型的“边界条件处理”写的时候多一点思考后面省的是大量返工时间。4.4 个人中心我的申请和收藏个人中心页至少包含两块我的领养申请列表、我的收藏。申请列表要展示每只宠物、申请时间、当前审核状态。状态建议用颜色标签区分待审核黄色、已通过绿色、已拒绝红色、已完成蓝色。这样的 UI 细节看似简单但直观性非常重要答辩演示时评委一眼就能看懂流程。收藏功能的实现也不复杂因为收藏本质上是一个多对多关系class Favorite(models.Model): user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE) pet models.ForeignKey(Pet, on_deletemodels.CASCADE) created_at models.DateTimeField(auto_now_addTrue) class Meta: unique_together (user, pet)unique_together确保一只宠物只能被同一个用户收藏一次这是数据层面的防重。页面上的收藏按钮用传统的 POST 提交或 Ajax 都可以毕设阶段用普通 POST 比较省事演示也稳定。5. 管理端开发审核状态机的完整闭环管理端是这个项目最体现功力的地方也是答辩时最容易被深挖的部分。Django Admin 虽然好用但直接拿来当毕设管理界面会显得诚意不足建议自己写一套精简的管理页。注意这里说的是“精简”不上很多复杂框架用 Django 模板加一点 Bootstrap 就足够。5.1 后台首页与数据概览管理后台入口用装饰器做权限控制from django.contrib.auth.decorators import login_required, user_passes_test def is_admin(user): return user.is_staff login_required user_passes_test(is_admin) def admin_dashboard(request): context { pet_count: Pet.objects.count(), pet_available_count: Pet.objects.filter(statusavailable).count(), application_pending_count: AdoptionApplication.objects.filter(statuspending).count(), application_approved_count: AdoptionApplication.objects.filter(statusapproved).count(), recent_applications: AdoptionApplication.objects.order_by(-created_at)[:10], } return render(request, admin_pages/dashboard.html, context)首页放四个统计卡片外加最近十条申请记录这既满足管理系统的基本需求又让管理端看起来不是空壳子。数据统计这块如果还想加点料可以用“近7天新增申请”这种数字但不需要引图表库纯数字展示完全足够。5.2 宠物管理上架与下架的完整生命周期管理员发布宠物信息时表单要包含分类、昵称、品种、年龄、性别、是否疫苗、是否绝育、性格描述和照片。保存时status自动置为available这样新发布的宠物会立刻出现在用户端列表中。下架操作不要直接删宠物而是要提供一个“下架”按钮把status改为off_shelf。这个设计的合理性在于管理员可能是临时下架而不是要彻底移除这只宠物而且以后做数据分析时下架的宠物记录还留存在系统里有迹可循。用户在列表页通过filter(statusavailable)自然看不到下架宠物数据上又不会丢这才是正规做法。5.3 审核领养申请最核心的管理功能这个页面列出的待审核申请是管理端的头号任务。审核页面要展示申请人信息用户名、注册时间、申请理由、居住情况和宠物完整信息方便管理员判断是否通过。审核动作就两个按钮通过、拒绝。拒绝时必须要填备注这是强制项原因很简单用户需要知道自己为什么被拒。def review_application(request, app_id): application get_object_or_404(AdoptionApplication, idapp_id) if request.method POST: action request.POST.get(action) remark request.POST.get(remark, ) if action approved: application.status approved application.admin_remark remark application.save() pet application.pet pet.status adopted pet.save() # 同时拒绝同一只宠物下其他待审核申请 AdoptionApplication.objects.filter( petpet, statuspending ).exclude(idapplication.id).update( statusrejected, admin_remark该宠物已被领养 ) elif action rejected: application.status rejected application.admin_remark remark application.save() return redirect(admin_pages:application_list) return render(request, admin_pages/review_application.html, { application: application })这里的亮点在通过审核后的联动逻辑同一只宠物可能同时有多条pending申请通过一条之后其余全部要自动置为rejected。这个操作往深了说是防止数据冲突——宠物已标记领养就不能再有任何悬挂的待审申请。这是整个系统最核心的一个逻辑判断也是业务复杂度的主要体现。我建议所有做这个题目的同学答辩时主动讲讲这段代码因为它是最好的一块“亮点展示面”。5.4 完成领养闭环审核通过不等于流程结束。管理员还需要一个“确认完成”的入口把approved状态改成completed。这样用户端“我的申请”页面的状态会从绿色标签变成蓝色标签宠物页面显示“已被人领养”。加这个环节的意义在于流程完整性一个完整的状态机应该是pending - approved - completed或者pending - rejected不应该是pending - approved就戛然而止。管理端的导航结构建议做成控制台、宠物管理、领养审核、申请列表、用户管理、公告管理。每个菜单对应一个 URL 和视图模板统一继承一个base_admin.html左侧固定侧边栏右侧内容区整体看起来就像个正规的后台系统。这套布局不复杂但对答辩展示效果提升巨大。6. 页面模板与交互细节让毕设看起来“不像是交作业”页面做得好不好看直接决定了答辩时评委的第一印象。技术栈上用 Bootstrap 5 的 CDN 加 Django 模板继承就够了。我建议做三个基础模板base.html面向用户端、base_admin.html面向管理端、base_auth.html面向登录注册页。模板继承的好处是所有页面头部尾部统一改一处全站生效。用户端首页要的是那种有温度、有氛围的感觉。大 Banner 放一张宠物合照下面加一句“用领养代替购买”这类标语下面再放最新上架的宠物卡片。卡片上要突出显示状态标签比如橙色标签写“待领养”灰色写“已领养”一眼就能分辨。这个细节看似简单但直接影响用户浏览效率。表单页面要处理好的问题是错误提示。Django Form 自动生成的错误消息默认样式很丑在模板中用field.errors定制一下展示样式div classmb-3 label classform-label{{ field.label }}/label {{ field }} {% if field.errors %} div classtext-danger small{{ field.errors }}/div {% endif %} /div这种细节做与不做对整个答辩的效果差别很大。评委现场点“提交空表单”看到的是整齐的红色错误提示和看到一坨 Python 报错对项目的评价会完全不一样。模板层的另一个重点是消息提示。用django.contrib.messages显示操作结果{% if messages %} {% for message in messages %} div classalert alert-{{ message.tags }}{{ message }}/div {% endfor %} {% endif %}提交成功、审核通过、操作失败都要有对应的用户反馈这是专业系统和练习项目的一个明显分水岭。7. 本地开发到线上部署给毕设一个可访问的 URL这个项目做完在本地跑通只是完成了 70%剩下 30% 是部署上线。即使毕设不强制要求线上演示我也强烈建议你部署到线上——答辩现场直接在浏览器里打开公网地址演示比在本地跑起来要亮眼得多也更能应对“随机应变”的提问场景。7.1 开发阶段的 settings 配置开发展阶段要改的配置集中在settings.pyINSTALLED_APPS里追加自己的 app 和django.contrib.humanizeLANGUAGE_CODE zh-hansTIME_ZONE Asia/ShanghaiMEDIA_URL /media/MEDIA_ROOT BASE_DIR / mediaSTATIC_URL /static/STATICFILES_DIRS [BASE_DIR / static]这里有个经常被忽略的点时区设置。不设置TIME_ZONE的话数据库存的创建时间会跟本地差八个小时用户提交申请的时间显示是错的这个在答辩时非常尴尬。用Asia/Shanghai并且USE_TZ TrueDjango 在模板层会按当前时区渲染时间问题就解决了。7.2 部署前的调整本地用runserver没问题但正式部署就不要再用了那是个开发服务器性能不行也不安全。标准做法是gunicorn nginx。部署前的必要配置调整DEBUG False这个不改线上会把错误堆栈直接暴露给访客非常丢人ALLOWED_HOSTS [你的域名或IP]STATIC_ROOT BASE_DIR / staticfiles然后执行collectstatic把所有静态文件收集到一个目录数据库迁移要在服务器上执行一遍首次部署就是migrate之后创建超级用户media 目录要确保有写权限否则管理员传不了宠物照片参考的 gunicorn 启动命令gunicorn config.wsgi:application --bind 127.0.0.1:8000 --workers 3nginx 配置负责监听 80 端口并转发给 gunicorn同时把/static/和/media/的请求直接交给文件系统处理server { listen 80; server_name your_domain_or_ip; location /static/ { alias /path/to/staticfiles/; } location /media/ { alias /path/to/media/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }如果你用的是国内云服务器记得在云控制台的安全组放行 80 端口这个排查起来很容易被忽略掉。另外如果申请了自己的域名建议用域名而不是 IP答辩环境如果临时需要切换网络域名访问会更稳。7.3 部署前的数据准备上线前一定要准备几组“看起来真实”的数据五六个宠物分类每类两三只宠物配好真实感的照片和性格描述再加几条领养申请。你可以在 Django Admin 或者自己的管理后台里手工录入也可以写一个data init的 management command 一次性灌入。总之不要部署完是空数据库演示效果会大打折扣。8. 开发中绕不过的坑实测验证过的排错清单最后这部分我把这个项目开发过程中最容易踩、最耽误时间的坑集中列一下都是我实际验证过的能帮你在 debug 上少花很多时间。8.1 图片上传后不显示本地开发时img src/media/xxx.png访问不到文件十有八九是 urls 里没有配media的静态服务。必须在config/urls.py中加from django.conf import settings from django.conf.urls.static import static urlpatterns [...] if settings.DEBUG: urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)部署环境如果也不显示多半是 nginx 的/media/location 没写对检查 alias 的路径和MEDIA_ROOT是否一致。8.2 CSRF token 报错模板里的所有 POST 表单form标签内必须写{% csrf_token %}。如果你用了 Ajax 提交需要先在页面里读取 csrf token 再放到请求头里否则 Django 会拒绝请求。这是 Django 的安全防护机制报错时不要关闭 CSRF正确做法是补齐 token。8.3 安装 Pillow 失败ImageField依赖 Pillow 库。Windows 上如果pip install pillow失败多半是 Python 版本和 Pillow 版本不匹配建议直接升级 Python 到 3.10再pip install --upgrade pillow重装。这个问题新手经常遇到没什么复杂的装不上就升级环境。8.4 外键删除导致的数据异常Pet 关联 Category如果分类被删除Pet 外键就会悬空。你可以在Pet模型里用on_deletemodels.PROTECT这样有宠物引用时分类不允许删除。反过来如果删除 User他的领养申请怎么办on_deletemodels.CASCADE会级联删除申请记录这样用户的个人页不会出现申请已失效但宠物还在的情况属于合理选择。8.5 列表页查询性能宠物列表页如果渲染了分类名称、状态标签每次循环都会触发数据库查询数据量小看不出来数据量大了页面就很慢。记得用select_related(category)做关联查询这是对 QuerySet 最基本的性能优化。答辩时如果评委问“你这个系统的数据库设计有什么考虑”这绝对是一个应答点。8.6 重复申请与并发问题我在实现时用“查询是否存在 pending 申请”来防止重复提交但这种检查在并发场景下并不完全可靠。如果有两个请求同时通过检查可能会出现重复记录。更严谨的做法是在数据库层面加UniqueConstraint(fields[user, pet], conditionQ(statuspending))做条件唯一约束。毕设阶段你的并发压力很小普通检查足够但如果想做得更讲究用条件唯一约束是更专业的方案。8.7 登录状态下权限控制用户端页面要做两个检查未登录用户点击“申请领养”要跳转到登录页管理员页面非 staff 用户要禁止访问。装饰器login_required和user_passes_test分别处理这两种情况。千万不要只在前端隐藏按钮后端不校验否则别人直接拼 URL 照样能访问到受保护的功能。个人最后再提醒一句做完功能后一定要写一份 README把自己搭建环境的步骤、账号密码、测试数据路径都写清楚。这不仅是给评委和后续维护的人看的也是给自己留下的完整记录。一个系统能做出来是一回事能让人快速跑起来是另一回事后者在答辩中的分量比很多人想象中重得多。按照这套流程走下来这个题目的实现难度并不算大但完成度和专业度都会在平均水平之上。