☰
Django流浪宠物领养管理系统:从数据库建模到领养审核实现
2026/10/10 4:40:37 网站建设 项目流程

接手这个“django基于python的流浪宠物领养管理系统”项目时,我其实没有把它当成一个普通的课程设计或者毕业设计来做。市面上这类“宠物领养系统”模板一大堆,但绝大多数都停留在“注册登录 + 宠物列表 + 提交表单”的层面,真正把领养流程、审核机制和用户权限理顺的非常少。我自己早年帮某机构做过一个类似的救助站管理系统,踩过不少坑,所以这次决定用Django把整套逻辑完整地做出来,从数据库建模到权限控制,从领养申请到后台审批,全部走一遍。这篇博文就是把整个项目的设计思路、关键技术点和实操过程整理出来,给准备做同类系统的朋友一个可以直接参考的范本。

先说一下这套系统最终能做什么:管理员可以录入流浪宠物信息、维护领养公告、审核领养申请;普通用户可以浏览宠物档案、提交领养意向、查看申请进度;游客只能看基础信息,无法提交申请。整个系统分为前台展示和后台管理两大部分,前台面向公众,后台面向管理员。技术上用的是Python 3 + Django(我这次用的4.x版本)+ SQLite起步,后期可以平滑切换到MySQL。

1. 系统整体设计与技术选型

1.1 为什么用Django而不是Flask或Node

选型这件事我基本没有犹豫。Django自带Admin后台、ORM、表单处理、认证体系和模板引擎,这些对一个管理信息系统来说都是刚需。你要是拿Flask来做,用户认证要自己写,Admin后台要自己搭,文件上传要自己配,工作量翻倍不说,安全性还容易出纰漏。Django把那些“每个项目都要重复一遍”的东西都内置好了,我只需要把精力放在业务逻辑上。

对比一下就更清楚了:

技术栈开发效率自带后台ORM适合场景
Django + Python高有完整Admin成熟稳定管理类系统、CMS、业务平台
Flask + Python中无自带后台可集成SQLAlchemy轻量接口、微服务
Node + Express中无Mongoose/Sequelize实时应用、前后端分离API

这次项目我用了Django 4.2版本,分应用开发,整个工程分成“宠物管理”“用户中心”“领养申请”“公告系统”几个模块,每个模块一个独立的Django App。这样后期扩展功能时不会把代码搅成一锅粥。

1.2 项目目录结构规划

一个清晰的目录结构能省掉后面80%的麻烦。我习惯在项目初期就把目录规划好,而不是边写边改。下面是我在实际项目里使用的结构:

pet_adoption/ ├── manage.py ├── requirements.txt ├── config/ │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── accounts/ # 用户注册登录、个人中心 │ ├── pets/ # 宠物档案管理 │ ├── adoption/ # 领养申请与审核 │ └── announ/ # 公告与新闻 ├── static/ │ ├── css/ │ ├── js/ │ └── images/ ├── media/ # 宠物图片上传目录 ├── templates/ │ ├── base.html │ ├── index.html │ ├── accounts/ │ ├── pets/ │ ├── adoption/ │ └── announ/ └── requirement.txt

把App都放到apps目录下是我个人的习惯,主要是为了让项目根目录干净一点,App多了以后更好管理。config目录用来放全局配置和根路由,media目录用来存放上传的宠物照片,这些路径在settings.py里要提前配好,不然生产环境会出现图片无法访问的问题。

1.3 核心需求拆解

这个项目的核心流程并不复杂,但涉及的交互比较多。我把需求拆成四条主线和三条辅助线:

  • 游客浏览:查看首页、宠物列表、宠物详情、公告信息,不需要登录。
  • 用户领养:注册登录后,浏览宠物详情并提交领养申请,填写申请理由、居住情况、养宠经验。
  • 管理员审核:在后台审核领养申请,通过的申请进入“待领养”状态,宠物被领养后下架。
  • 信息发布:管理员发布宠物档案、领养公告、成功案例等。

辅助功能包括:用户个人中心的申请记录查看、收藏宠物、修改个人资料、密码重置等。

这里我特别想强调一点:领养申请必须要有审核流程,不能提交了就完事。很多山寨系统就是一张表单丢到数据库里,管理员连审核入口都没有,这种系统做出来没有实际价值。我在设计时把申请状态设计成“待审核、已通过、已拒绝、已完成”四种,每个状态变化都记录时间,这样管理员和用户都能看到完整的流程进度。

2. 数据库设计与核心模型

2.1 数据表关系梳理

数据库是整个系统的地基。我画了一下核心数据表的关系:用户表(自定义User模型)、宠物表(Pet)、领养申请表(AdoptionApplication)、公告表(Announcement)、收藏表(Favorite)。

表与表之间的关系我在这里直接说清楚:

  • 一个宠物属于一个分类,分类就是“猫、狗、其他”这种。
  • 一个用户可以提交多个领养申请,但同一只宠物在“待审核/已通过”状态下,不允许第二个用户重复申请。
  • 一个用户可以收藏多只宠物,宠物和用户之间是多对多关系。
  • 公告表独立存在,只由管理员操作。

2.2 自定义用户模型

Django自带的User模型能用,但为了后续扩展性,我强烈建议从一开始就自定义用户模型,用AbstractUser继承。为什么?因为在做这个领养系统时,至少要给用户加上“联系电话”和“所在城市”这两个字段,这些是线下交接宠物时必须的信息。如果默认模型已经迁移过数据库,后期再改用户模型会非常痛苦。

自定义用户模型代码如下:

from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): phone = models.CharField(max_length=11, blank=True, verbose_name="联系电话") city = models.CharField(max_length=64, blank=True, verbose_name="所在城市") avatar = models.ImageField(upload_to="avatar/", blank=True, verbose_name="头像") created_at = models.DateTimeField(auto_now_add=True, verbose_name="注册时间") class Meta: db_table = "user" verbose_name = "用户"

设置文件里别忘了加一行:

AUTH_USER_MODEL = "accounts.User"

这个配置必须在第一次migrate之前设置好,否则后续改动需要重置数据库,那真叫一个哭爹喊娘。

2.3 宠物档案模型设计

宠物档案是整个系统信息量最大的数据表。除了基本信息,还要考虑状态流转。我设计的字段如下:

class Pet(models.Model): STATUS_CHOICES = ( ("available", "待领养"), ("adopted", "已领养"), ("pending", "待交接"), ) name = models.CharField(max_length=32, verbose_name="宠物昵称") category = models.CharField(max_length=16, choices=(("cat", "猫咪"), ("dog", "狗狗"), ("other", "其他")), verbose_name="类别") breed = models.CharField(max_length=32, blank=True, verbose_name="品种") age = models.IntegerField(default=1, verbose_name="年龄(岁)") gender = models.CharField(max_length=8, choices=(("M", "男孩"), ("F", "女孩")), default="M", verbose_name="性别") health_status = models.TextField(blank=True, verbose_name="健康状况") story = models.TextField(blank=True, verbose_name="救助故事") cover_image = models.ImageField(upload_to="pets/", verbose_name="封面图片") status = models.CharField(max_length=16, choices=STATUS_CHOICES, default="available", verbose_name="状态") created_at = models.DateTimeField(auto_now_add=True, verbose_name="发布时间") class Meta: db_table = "pet" verbose_name = "宠物档案"

这里有几个细节值得注意。status字段不要用布尔值“是否被领养”,因为领养过程有中间状态——申请通过了但宠物还没被接走,这个阶段叫“待交接”。只用布尔值会丢失关键信息,后面做状态筛选就很费劲。

2.4 领养申请表模型

领养申请表是连接用户和宠物的核心桥梁,设计质量直接决定审核流程能不能顺畅跑起来。我加了几个跟实际救助工作强相关的字段:

class AdoptionApplication(models.Model): STATUS_CHOICES = ( ("pending", "待审核"), ("approved", "已通过"), ("rejected", "已拒绝"), ("completed", "已完成"), ) user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="申请人") pet = models.ForeignKey(Pet, on_delete=models.CASCADE, verbose_name="申请宠物") reason = models.TextField(verbose_name="领养理由") has_experience = models.BooleanField(default=False, verbose_name="有无养宠经验") family_agreement = models.BooleanField(default=True, verbose_name="家人是否同意") address = models.CharField(max_length=128, verbose_name="居住地址") status = models.CharField(max_length=16, choices=STATUS_CHOICES, default="pending", verbose_name="审核状态") created_at = models.DateTimeField(auto_now_add=True, verbose_name="申请时间") reviewed_at = models.DateTimeField(null=True, blank=True, verbose_name="审核时间") remark = models.TextField(blank=True, verbose_name="审核备注") class Meta: db_table = "adoption_application" unique_together = ("pet", "user")

unique_together这个约束我用在了这,让“同一用户对同一宠物只能提交一次申请”。如果不加这个约束,用户重复提交申请会导致后台审核出现一堆冗余记录。当然,被拒绝后是否允许重新申请,这个需求我实现了分支判断,在视图里单独处理,而不是直接靠唯一约束锁死。

3. 核心功能模块与实现要点

3.1 用户注册、登录与权限控制

用户模块我分了三个角色:游客、普通用户、管理员。管理员就是is_staff=True的用户,用Django自带的认证系统就够了。不过登录后的跳转逻辑需要自己写一下。

注册这块我额外接了验证码,用的是Pillow生成的图片验证码。虽然是老技术,但简单可靠,能挡住大部分恶意注册脚本。视图逻辑大致是:

from django.contrib.auth import login from .forms import RegisterForm def register(request): if request.method == "POST": form = RegisterForm(request.POST) if form.is_valid(): # 验证码校验 code = request.session.get("captcha_code") input_code = form.cleaned_data.get("captcha") if code and code.lower() == input_code.lower(): user = form.save(commit=False) user.set_password(form.cleaned_data["password"]) user.save() login(request, user) return redirect("pet_list") else: form.add_error("captcha", "验证码错误") else: form = RegisterForm() return render(request, "accounts/register.html", {"form": form})

权限控制上,我用了Django自带的@login_required装饰器,没有登录的用户访问提交申请页面时会被自动弹回登录页。这个方案成熟、安全,比自己在视图里判断request.user.is_authenticated靠谱得多。

3.2 宠物信息的展示与筛选

宠物列表页面做了三块内容:搜索框、分类筛选、卡片式宠物列表。搜索功能我直接用ORM的icontains做模糊查询,虽然数据量大以后效率一般,但对于这种小型系统完全够用,没必要上来就上Elasticsearch。

def pet_list(request): pets = Pet.objects.filter(status="available") keyword = request.GET.get("keyword", "") category = request.GET.get("category", "") if keyword: pets = pets.filter(name__icontains=keyword) if category and category in dict(Pet.STATUS_CHOICES): pets = pets.filter(category=category) return render(request, "pets/pet_list.html", {"pets": pets})

这里有个小坑:首页只展示“待领养”状态的宠物时,如果直接用Pet.objects.all(),会把“已领养”的老档案也展示出来,让用户看到一只早被别人领走的宠物,体验会很差。所以列表页一律过滤状态。

宠物详情页我除了展示基本信息,还增加了一个“收藏”按钮和“提交领养申请”按钮。收藏按钮通过AJAX异步请求实现,不用刷新整个页面。领养申请按钮则判断当前用户是否登录,未登录时弹窗提示去登录。

3.3 领养申请流程的实现

领养申请是整个系统的业务核心。我的流程是:

  1. 用户进入宠物详情页,点击“申请领养”。
  2. 系统检查用户是否登录、宠物是否处于“待领养”状态。
  3. 检查用户是否已经提交过该宠物的申请(防止重复提交)。
  4. 通过检查后,跳转到申请表单页。
  5. 用户填写领养理由、是否有养宠经验等信息,提交。
  6. 状态变为“待审核”,管理员在后台处理。
  7. 审核通过后,宠物状态变为“待交接”;用户和管理员可以协商线下交接。
  8. 交接完成后,管理员把申请状态改为“已完成”,宠物状态改为“已领养”。
@login_required def submit_application(request, pet_id): pet = get_object_or_404(Pet, id=pet_id) if pet.status != "available": messages.error(request, "该宠物当前不可领养") return redirect("pet_detail", pet_id=pet.id) existing = AdoptionApplication.objects.filter(pet=pet, user=request.user) if existing.exists(): messages.warning(request, "您已经申请过这只宠物,请等待审核结果") return redirect("pet_detail", pet_id=pet.id) if request.method == "POST": form = ApplicationForm(request.POST) if form.is_valid(): app = form.save(commit=False) app.user = request.user app.pet = pet app.save() return redirect("my_applications") else: form = ApplicationForm() return render(request, "adoption/apply.html", {"form": form, "pet": pet})

这段代码看起来简单,但已经把三种异常情况都堵住了:宠物状态不可领养、用户重复申请、表单校验失败。我见过很多项目在业务校验上偷懒,结果数据库里出现一堆非法数据,后面查问题查到头大。

3.4 后台审核功能增强

Django自带Admin很好用,但原生Admin在处理业务审核时不够直观。我给领养申请在Admin里增加了一个列表筛选和自定义操作:

class AdoptionApplicationAdmin(admin.ModelAdmin): list_display = ("pet", "user", "status", "created_at", "reviewed_at") list_filter = ("status",) actions = ["approve_applications", "reject_applications"] def approve_applications(self, request, queryset): for app in queryset: if app.status == "pending": app.status = "approved" app.reviewed_at = timezone.now() app.pet.status = "pending" app.pet.save() app.save() approve_applications.short_description = "批量通过"

批量通过的时候连宠物状态一起更新,这样管理员不需要进宠物表再改一次状态。批量操作按钮是我在生产中经常用到的功能,强烈推荐大家给自己的Admin加上。

3.5 公告模块与前端展示

公告模块做起来不复杂,但它是救助站向公众传递信息的窗口,也影响整个系统看起来是否完整。公告模型就三个核心字段:标题、正文、发布时间。首页在轮播图和宠物列表之间展示最新三条公告。

我顺便做了一个“成功案例”栏目,本质上也是公告,但多一张配图字段。这个功能是为了鼓励更多人参与领养,实际运营中效果很不错。

4. 实操过程与核心环节实现

4.1 开发环境准备

这个项目用的所有依赖我都放在requirements.txt里:

Django==4.2.7 Pillow==10.1.0 django-crispy-forms==2.1 gunicorn==21.2.0

开发时用Django自带服务器,部署时换成Gunicorn + Nginx。数据库先用SQLite,方便本地调试,发布时切到MySQL只需要改settings.py里的DATABASES配置,Django的ORM会帮我们把所有SQL差异抹平。

4.2 数据初始化的建议

系统刚上线时数据库是空的,直接给用户看一个空荡荡的页面非常掉价。我建议做两件事:

  • 用python manage.py seed_data命令批量生成测试宠物数据,脚本里用Faker生成名字、品种、描述。
  • 手动创建一个管理员账号,把宠物分类等基础数据填好。

创建管理员的命令是:

python manage.py createsuperuser

测试数据生成脚本我放到了management/commands/seed_data.py里,主要是为了测试列表页、搜索功能和详情页时不用一条条手动添加。注意这个脚本只用于开发环境,正式生产环境还是得靠管理员人工录入真实宠物档案。

4.3 宠物图片上传与访问配置

这是新手最容易踩坑的地方。图片能上传成功,但页面死活不显示,原因通常是settings.py里的MEDIA_URL和MEDIA_ROOT没配对,或者主urls.py没有挂载静态服务。

# settings.py MEDIA_URL = "/media/" MEDIA_ROOT = BASE_DIR / "media"
# config/urls.py from django.conf import settings from django.conf.urls.static import static urlpatterns = [ # 其他路由 ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

还有一点:模板里引用图片时,一定要用{{ pet.cover_image.url }},不要直接拼/media/ + pet.cover_image.name。Django的FileField对象的url属性会帮我们拼接好完整路径,手动拼接容易出现问题。开发模式下这个方法没问题,部署到Nginx后static()这一行要删掉或者做环境判断,否则会暴露静态文件服务。

4.4 前端页面响应式处理

这套系统虽然以PC端后台管理为主,但前台列表页面有大量普通用户会用手机访问,所以前端必须做响应式。我选用的方案是AdminLTE框架改前台?不,前台我用的是纯Bootstrap 5。后台则直接用Django Admin自带界面,不额外写HTML。

首页、宠物列表、宠物详情、申请表单,这几个页面的HTML模板继承同一个base.html,导航栏和页脚统一维护。模板引擎用Django自带的DTL,不需要换成Jinja2,原生的语法完全够用。

4.5 邮件通知与提醒

用户申请提交后,管理员不一定马上登录后台看到,项目里我加了一个基础的邮件通知功能:申请提交成功后自动发一封邮件给管理员;审核通过后发邮件通知申请用户。开发环境下邮件功能可以用console后台输出调试,不会真的发出去:

EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

生产环境改成SMTP,填入邮箱授权码就可以了。这一步很多人会遗忘,但对真实运营场景来说非常重要,管理员不可能一直盯着系统后台的。

5. 常见问题与排查技巧实录

5.1 数据库迁移报错

自定义 User 模型后,在设置AUTH_USER_MODEL之前如果已经执行过migrate,后面会报类似AuthUser.userprofile: relation already exists的错误。这个错误的原因是把系统默认的auth_user表已经建出来了,再切换自定义模型时Django不知道如何处理旧表。

解决办法是:开发初期就设置好AUTH_USER_MODEL,然后删除旧的数据库文件(开发环境)重新迁移。如果已经部署了正式环境,就要写数据迁移脚本同步数据,麻烦不少。所以再次强调:动手写代码前先把用户模型定下来。

5.2 图片上传后404

跑完python manage.py runserver后,访问上传的宠物图片返回404。这个问题我至少见过不下五次,原因基本都是MEDIA_ROOT路径配置不对。尤其注意,如果BASE_DIR和MEDIA_ROOT拼接使用了相对路径,Django的静态文件处理器会找不到目录。

还有一种情况是开发时用了static()方法但忘了加document_root参数。第二参数不写,Django就不知道去哪里找文件,结果404。检查方法很简单,打印一下MEDIA_ROOT指向的绝对路径,看文件是否真的在那个目录下存在。

5.3 重复提交申请

测试时发现用户可以绕过前端页面,用POST工具重复提交领养申请,数据库里出现多条相同用户和宠物的记录。我在模型层加了unique_together约束后,再用try-except捕获IntegrityError,在视图里做了兜底,这样就算绕过前端检查,数据库层面也能拦住重复数据。

5.4 表单提交后页面刷新报错

如果你在帖子提交表单后按F5,浏览器会提示“确认重新提交表单”,用户点确认后可能导致生成重复的申请记录。这个问题的根源是用普通POST表单提交后,页面没有做重定向。

正确的写法是Post/Redirect/Get模式,视图中处理完POST请求后立刻redirect到结果页面,而不是render返回同一个模板。我在提交申请、注册账号、发布公告这几个表单里都严格执行了POST→Redirect→GET的流程。

5.5 常见问题速查表

现象可能原因解决方案
登录后跳转到默认admin页而不是用户页LOGIN_REDIRECT_URL未配置在settings.py加LOGIN_REDIRECT_URL = "/pets/"
图片上传成功但列表页不显示MEDIA_URL和MEDIA_ROOT不匹配检查配置并重启服务,清缓存
Admin列表页出现重复记录缺少unique_together约束在模型中加约束并重新迁移
表单提交重复未遵循PRG模式改为POST→Redirect→GET
用户注册后无法发送验证邮件邮件后端配置错误开发环境使用console后端验证逻辑
部署后静态文件404Nginx未配置静态目录配置location /static/和location /media/

5.6 关于防爬虫和恶意提交

系统上线后会被各种爬虫扫描,注册接口容易被滥用。我做了几个基础防护:

  1. 注册接口加图片验证码。
  2. Admin后台URL不放在默认的/admin/路径下,改成一个自定义路径,在urls.py里用path("manage/", admin.site.urls)这样相对冷门的路径,能挡掉一大部分自动化攻击。
  3. 领养申请接口在服务端校验字段长度和内容,不做简单的HTML过滤——用Django模板自动转义就够了。
  4. 对频繁提交的IP做简单的频率限制,用django-ratelimit这个第三方库,几行代码就能配好。

这些措施虽然不是企业级安全方案,但对于一个宠物领养管理平台来说已经能挡住绝大多数恶意行为。

6. 部署与环境配置要点

6.1 从开发到生产的配置切换

开发环境直接跑runserver没问题,但生产环境必须用WSGI服务器。我推荐Gunicorn,启动命令:

gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 3

workers的数量一般是CPU核心数加1,不要盲目开更多进程,否则内存占用会比较大。前面加上Nginx做反向代理,/static/和/media/两个路径直接由Nginx处理静态文件,Django只处理动态请求。

部署到Linux服务器时,settings.py里需要把DEBUG设为False,ALLOWED_HOSTS配置成服务器域名或IP。还有一个关键操作:关闭static()的MEDIA路由,否则会把资源请求转发给Python进程,白白增加负载。

6.2 数据库切换

从SQLite切换到MySQL,需要安装mysqlclient或者pymysql,然后在settings.py里改数据库配置:

DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", "NAME": "pet_adoption", "USER": "root", "PASSWORD": "你的密码", "HOST": "127.0.0.1", "PORT": "3306", } }

执行一下python manage.py migrate,如果代码里没有使用SQLite特有的函数,模型和数据迁移基本无缝切换。这一步Django的ORM优势体现得特别明显。

7. 项目扩展方向与个人经验

这套基础版本跑通后,可以继续往这几个方向扩展:

  • 宠物定位与地图服务:给救助站宠物增加定位,需要接入地图SDK。
  • 志愿者招募模块:在公告之外增加专门的活动报名流程。
  • 微信小程序端:把前台移植到小程序,Django提供JSON接口。
  • 消息推送:申请审核结果、宠物状态变化通过短信或微信模板消息通知。
  • 数据统计看板:用Chart.js做一个后台统计页面,展示每日申请量、领养成功率、宠物分类占比。

我个人使用中觉得最舒服的一点是,Django把这些功能扩展的门槛降得很低。加一个新模块就是“建App、写模型、做模板”三步走,老模块几乎不用动。如果你在做类似的系统,我建议先从“把核心领养流程彻底跑通”开始,不要一上来就贪多。把用户注册、宠物发布、申请审核、状态流转这四个环节做到位,系统就已经能用了。后面那些“智能推荐宠物”“实时聊天”功能,都是锦上添花,先稳住地基最重要。

最后说一个我踩过的坑:开发时一定要在主settings.py里尽早把时区配好,不然created_at记录的时间和本地时间差8个小时,排查审核记录时容易把自己绕晕。如果你是第一次做Django项目,建议先用SQLite把整个流程跑通,不要急着上MySQL。开发调试的速度差异很大,等系统稳定了再切换生产数据库,一步一个脚印,这个项目的坑能少踩一半。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询