Django医院挂号系统源码拆解:模型设计、ORM查询与部署实践
发布时间:2026/9/12 12:31:29 锦皓数字建站

简介一套基于Django的医院挂号诊疗系统毕业设计源码面向计算机相关专业正在准备毕设或需要项目实战练习的学生也适用于课程设计、期末大作业等场景。项目已经过导师指导并认可包含完整源码、数据库文件、演示视频、使用说明与环境安装文档拿到后即可按步骤部署运行。资源包共2000个文件以JavaScript、HTML、CSS等前端文件为主搭配少量Python后端脚本、JSON配置及说明文档压缩包整体仅5.33MB轻量易下载。前端包含了Bootstrap、animate、font-awesome等常用库组件可直接二次开发学习Python文件则对应Django核心逻辑便于理解后端接口与页面协作方式。目前已有80人学习下载。对于需要快速搭建完整课设或毕设演示的学生来说这份资料同时提供了运行效果演示、环境配置指引和源码结构参考能有效节省从零搭建的时间同时也可以作为理解Django框架前后端交互的入手案例。1. 为什么值得把这份 Django 医院挂号系统源码完整拆一遍医院挂号诊疗在毕业设计里属于典型的“一个项目讲完增删改查”的场景。它不是单表 CRUD而是涉及患者建档、分时段挂号、医生开单、收费取药几个实体相互关联。这套源码用 Django Bootstrap 实现前端依赖里能看到 bootstrap.css、font-awesome.css、datetimepicker说明页面不是裸写 HTML而是按实际流程做了表单、弹窗和响应式布局。对准备 Python 方向毕业设计的同学它的价值在于能顺着模型看清楚业务被拆成一个个 app 的过程对已经工作几年的工程师也能回顾 Django 传统多页面开发的路数模板继承、FBV 视图、ORM 查询和 admin 后台的协作方式。2. Django 端数据库建模与静态资源引入从 admin 到 Bootstrap 布局这个项目的静态资源列表里同时出现了bootstrap.css、bootstrap-theme.css、animate.css、font-awesome.css、ui.css和bootstrap-datetimepicker.css。毕业生拿到的源码包通常已经把文件放进了static/目录但真正动手写代码时要靠settings.py里的STATIC_URL和STATICFILES_DIRS把目录暴露给模板。常见做法是每个 app 自己建一层static/app_name/目录Django 会自动收集这个项目选择的是项目级static/统一存放好处是前端资源不散落坏处是后续拆模块时静态文件会变成“公共依赖”。下面这个目录是这类毕业设计最常见的样子project_root/ ├── manage.py ├── hospital/ │ ├── settings.py │ └── urls.py ├── apps/ │ ├── registration/ # 挂号管理 │ ├── doctor/ # 医生工作站 │ ├── pharmacy/ # 药房/收费 │ └── user/ # 患者信息 ├── static/ │ ├── bootstrap.css │ ├── bootstrap-theme.css │ ├── font-awesome.css │ ├── ui.css │ └── bootstrap-datetimepicker.css └── templates/ ├── base.html ├── registration/ └── doctor/这段结构与源码包里的 css 列表是对应的。bootstrap-datetimepicker.css和bootstrap-datetimepicker.min.css同时出现说明项目里有日期时间选择需求通常用于“挂号时段”和“预约时间”输入。使用 datetimepicker 时模板里要先加载 jQuery 再加载日期控件 JS表单字段格式要求是YYYY-MM-DD HH:mm:ss否则后端用datetime.strptime()解析时会报错。一个常见调试场景是页面能弹日期但提交后报“时间格式不正确”这往往不是 Django 的问题而是控件输出格式与模型DateTimeField不匹配。2.1 患者、医生、号源三张核心表的模型设计在 Django 里“挂号”这个业务不会只写在一张表里。底层至少要有Patient、Doctor、Schedule、Registration四张表。下面是精简后的models.py片段可以看到它为什么适合拿来做毕业设计from django.db import models from django.contrib.auth.models import User class Patient(models.Model): # OneToOne 绑定 auth.User登录账号即患者本人 user models.OneToOneField(User, on_deletemodels.CASCADE) id_card models.CharField(max_length18, uniqueTrue) phone models.CharField(max_length11, blankTrue) class Doctor(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE) department models.CharField(max_length50) # 内科、外科、儿科... title models.CharField(max_length20) # 主任医师 / 副主任医师 schedule models.CharField(max_length30) # 周一、周三上午 class Schedule(models.Model): doctor models.ForeignKey(Doctor, on_deletemodels.CASCADE) date models.DateField() start_time models.TimeField() end_time models.TimeField() max_count models.IntegerField(default20) used_count models.IntegerField(default0) class Registration(models.Model): patient models.ForeignKey(Patient, on_deletemodels.CASCADE) schedule models.ForeignKey(Schedule, on_deletemodels.CASCADE) status models.CharField(max_length10, choices[(booked,已挂号),(done,已完成),(cancel,已取消)], defaultbooked)模型精简化后依然能反映出核心关系。Patient用OneToOneField关联User这样登录、session、权限都能复用 Django 自带能力而不用自己写密码校验。Doctor同样关联User挂号页面的医生列表就可以通过User.is_staff加Doctor表分组查询。Schedule表里used_count用于实现“号源剩余数”每次挂号时在事务里做F(used_count) 1避免并发超挂。很多同学会忽略这个字段直接在视图里len(Registration.objects.filter(schedule_id...))统计数据量小看不出问题但显然不是正路。模型关键字段作用Patientuser, id_card绑定登录账号身份证唯一索引Scheduledoctor, date, max_count限定每个医生每天可挂数量Registrationpatient, schedule, status挂号关系表状态流转2.2 通过 admin 快速验证模型关系模型建好后最直接的价值就是 Django admin 后台。源码包的演示视频大概率有一段是打开 admin 添加患者、给医生排班然后到前台模拟挂号。admin 的注册代码长这样from django.contrib import admin from .models import Patient, Doctor, Schedule, Registration admin.register(Patient) class PatientAdmin(admin.ModelAdmin): list_display (user, id_card, phone) search_fields (user__username, id_card) admin.register(Schedule) class ScheduleAdmin(admin.ModelAdmin): list_display (doctor, date, start_time, used_count, max_count) list_filter (doctor__department, date)参数说明search_fields里写user__username是跨表查询对应 SQL 里的 JOINlist_filter用于右侧筛选栏下拉列表来自doctor__department。这部分在毕业答辩时容易成为“设计亮点”提问点要能说清楚哪个字段建立索引、哪个查询会扫全表。admin 默认是 Django 自带样式如果想让它和前台 Bootstrap 风格统一常见做法是覆盖admin/base_site.html中的block extrahead引入源码里的bootstrap.css和ui.css。不过注意ui.css是项目自定义主题里面可能定义了按钮、卡片、表格间距直接覆盖后要检查导航栏是否被挤压。这个坑后面在排错章节还会遇到。提示runserver只用于本地开发演示时如果需要局域网访问用python manage.py runserver 0.0.0.0:8000启动而不是只监听 127.0.0.1。3. 挂号、分诊、收费关键业务流的视图与路由参数设计看一个 Django 毕业设计项目的质量不要先看页面多花哨而是先看urls.py是否按业务拆分。患者挂号、医生查看号源并写诊疗记录、收费员确认费用这三个入口如果全部堆在根urls.py里后面就很难维护。常见做法是每个 app 建一个urls.py在根路由里用include()引入并给app_name加命名空间避免reverse(detail)撞名。3.1 路由表挂号的增删改查路径# apps/registration/urls.py from django.urls import path from . import views app_name registration urlpatterns [ path(, views.index, nameindex), path(book/int:schedule_id/, views.book, namebook), path(cancel/int:registration_id/, views.cancel, namecancel), path(records/, views.records, namerecords), ]这里的int:schedule_id是路径参数视图函数需要接收名为schedule_id的参数。对应到reverse(registration:book, args(schedule.id,))生成/registration/book/12/这样的 URL在模板里可以直接写{% url registration:book schedule.id %}。要注意URL 里不应该出现?patient_id1这种 Query String因为挂号动作依赖路径参数能让自己对操作目标更明确也方便在日志里直接看出访问轨迹。路由 name作用逆解析参数registration:index挂号首页展示可挂号源无registration:book提交挂号schedule_idregistration:cancel取消挂号registration_idregistration:records查看我的挂号记录无3.2 视图与 ORM 查询减少 N1 和无效更新挂号按下“确认”的瞬间视图里通常要做三件事校验号源余量、写入Registration、把Schedule的used_count加一。下面是常见的视图写法也是这套源码里基本的逻辑from django.http import HttpResponseRedirect from django.urls import reverse from django.contrib.auth.decorators import login_required from django.db import transaction from django.db.models import F from .models import Schedule, Registration login_required transaction.atomic def book(request, schedule_id): schedule Schedule.objects.select_for_update().get(pkschedule_id) if schedule.used_count schedule.max_count: return HttpResponseRedirect(reverse(registration:index) ?msgfull) schedule.used_count F(used_count) 1 schedule.save(update_fields[used_count]) Registration.objects.create( patientrequest.user.patient, scheduleschedule, ) return HttpResponseRedirect(reverse(registration:records))这段代码里三个参数点值得注意。select_for_update()是行级锁能防止两个请求同时读到used_count19然后都插入挂号记录这在并发测试时是必问点。F(used_count) 1是让数据库在行上自增而不是先把旧值取回 Python 再写回避免读到的值过期导致少记一次。update_fields告诉 ORM 只更新指定字段如果漏写Django 会对整行做 UPDATE在字段多的时候会影响性能。把transaction.atomic放在函数上是为了保证Schedule更新和Registration插入要么同时成功要么同时回滚不会出现“号没了但记录没写入”的情况。聚合查询也是这个项目非常实用的部分。查看“某医生今日已挂号人数”时不少人会写registrations Registration.objects.filter(schedule__doctor_iddoctor_id) count registrations.count() schedule_used sum(s.used_count for s in registrations)这其实是重复统计正确做法是直接用Schedule.objects.filter(doctor_iddoctor_id).aggregate(totalF(max_count) - F(used_count))一次 SQL 就返回剩余号源。如果要在列表页显示每个号源的已挂人数则用annotate配合Prefetch避免 N1 查询。演示视频里一般不会展示这些细节但答辩评委很可能问到“为什么列表加载这么慢”这时候能答出Prefetch就是加分项。删除对象要谨慎。Django 的删除默认是级联的。源码里取消挂号的视图常见写法是Registration.objects.filter(pkrid).delete()这会连表删除如果Registration被收费记录外键引用删除后会造成业务审计缺失。比较好的做法是加一个cancel_time字段只把status改为cancel而不是真正删除。这个项目如果在演示视频里支持“取消挂号”大概率也是走状态更新路线。你可以打开models.py检查Registration是否有status字段Controller 里是否只做 update。4. 本地跑通Django 环境安装、数据库 migration 与登录角色拿到源码包之后第一步不是python manage.py runserver而是先确认 Python 版本、Django 版本和数据库驱动。很多同学下载后直接跑遇到django.core.exceptions.ImproperlyConfigured报错往往就是版本不匹配。这类毕业设计一般在 Django 2.2 或 3.2 附近编写Python 3.8 到 3.10 最为稳妥。如果源码里使用mysqlclient且你在 Windows 上安装了通常需要装pymysql并执行pymysql.install_as_MySQLdb()来伪装成 MySQLdb。4.1 用虚拟环境安装依赖python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate pip install -r requirements.txt python manage.py migrate python manage.py runserver 0.0.0.0:8000如果源码包没有requirements.txt那就按常见依赖手装django、mysqlclient、pillow如果用了验证码或图表再加captcha、matplotlib。runserver 0.0.0.0:8000里的0.0.0.0表示监听所有网卡这样才能用局域网 IP 访问演示联网环境时有用。注意 Django 3.x 以上启动时如果settings.py里ALLOWED_HOSTS为空会报DisallowedHost调试时先把它填成[*]但正式部署时一定要收紧。与此相关的还有一个高频场景使用宝塔部署 Django 时不能直接用runserver需要 uwsgi 或 gunicorn。源码包里如果没有 gunicorn可以在服务器上pip install gunicorn然后gunicorn hospital.wsgi:application -b 127.0.0.1:8001在 nginx 里把/反代到 8001 端口静态文件路径指到项目的static目录。这个操作对毕业设计不是必须的但如果演示视频是在服务器上跑通的你会对部署多一层理解。4.2 数据库迁移与初始账号创建migrate做完后还要确认是否有初始数据。如果源码带有.sql或data.json一般用python manage.py loaddata data.json导入预设分类和医生排班数据。同时需要创建管理员python manage.py createsuperuser根据演示视频登录角色有患者、医生、管理员。通常管理员在 admin 后台创建医生/患者账号时会为 User 设置is_staffTrue和is_activeTrue然后在Doctor/Patient表里建立对应的 user 外键。如果源码里没有现成脚本可以进 admin 手动添加用户管理里新增一个 user设置角色相关的权限组再到 Doctor 模型里新增一条记录。手动操作时要反复确定 “user 名” 和 “doctor 名” 不是同一个人否则登录医生工作站会取不到Doctor.user_id对应的对象引发ObjectDoesNotExist。数据库连接配置是另一个重点。Django 默认用 SQLite但毕业设计一般要求 MySQL。settings.py中常见配置如下DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: hospital, USER: root, PASSWORD: 123456, HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4}, } }配置项推荐值说明ENGINEdjango.db.backends.mysql使用 MySQL 驱动NAMEhospital数据库名需提前创建HOST127.0.0.1本地开发时使用OPTIONSutf8mb4支持完整中文与特殊符号utf8mb4能存中文和 emoji解决部分 MySQL 5.7 下utf8无法存特殊字符的问题。字符集不匹配时查询中文会报Incorrect string value。此时除了改 settings还要改数据库表本身的字符集ALTER DATABASE hospital CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。另外在写DateTimeField时如果USE_TZTrue从 datetimepicker 传本地时间会被当作 UTC 处理显示时间会少 8 小时。调试期最简单的办法是把USE_TZ设为False或者在后端timezone.localtime()转一遍这属于老 Django 项目常见的失误。5. 演示视频之外的细节静态文件、时区、登录态与常见报错排查源码包里的演示视频通常只展示了功能跑通但有几个点不会在视频里被强调却是答辩和实际部署时最容易翻车的地方。5.1 前端静态文件在 DEBUG 模式下的加载路径项目 css 列表里有ui.css、bootstrap-theme.css等模板里一般使用{% load static %}标签引用。Django 在DEBUGTrue时由django.contrib.staticfiles自动处理一旦切到DEBUGFalse静态文件就由 nginx 或其他 Web 服务器负责。演示时如果 admin 页面样式丢失十有八九是collectstatic没有执行。命令是python manage.py collectstatic它会按照STATIC_ROOT设置把STATICFILES_DIRS和各 app 的 static 目录收集到指定文件夹。如果STATIC_ROOT和STATICFILES_DIRS指向同一个目录会报错。目录设计我建议在项目根目录下分开放static/放开发的原始资源staticfiles/放收集后的产物。5.2 登录跳转、重定向和消息显示这个项目涉及三类角色Django 默认的login_required重定向地址是/accounts/login/?next...。如果演示视频里的登录页不是这个地址多半是自行配了LOGIN_URL。看认证代码时务必留意撤销挂号后是否用HttpResponseRedirect加查询参数传提示return HttpResponseRedirect(reverse(registration:index) ?msgcancelled)然后在模板里用request.GET.msg判断。这种写法简单但有个缺点刷新页面后参数仍在。更友好的方式是使用 Django messages 框架from django.contrib import messages messages.success(request, 挂号成功)模板里嵌{% if messages %}循环即可。源码用了哪一种你直接搜messages.success或msg就能判断。答辩时若能说出两者的区别会是加分点。常见报错可能原因处理方式DisallowedHostALLOWED_HOSTS 未配置调试期设[*]部署时收紧Incorrect string value表字符集不是 utf8mb4修改库表字符集403 Forbidden模板缺少 csrf_token在 form 内加{% csrf_token %}Column xxx cannot be null必填字段未传检查表单字段或模型 default5.3 查看日志与定位 500 错误本地跑通后还可以故意制造一个错误来测试排错流程。比如在book视图里临时写一个print(schedule)打开runserver的控制台会看到 ORM 查询语句。更正式的做法是设置LOGGING把 Django 的请求日志输出到文件LOGGING { version: 1, disable_existing_loggers: False, handlers: {file: {class: logging.FileHandler, filename: debug.log}}, root: {handlers: [file], level: INFO}, }产生 500 时除了debug.log还要检查表单提交的csrfmiddlewaretoken。这个项目用了 Bootstrap 普通 form如果在模板里忘了{% csrf_token %}POST 提交会直接返回 403这并不是业务错误。调试时可以临时注释掉CsrfViewMiddleware验证问题是否与此有关但演示时要记得改回来。6. 把模板改造成自己的毕业设计重命名、扩展字段与 inspectdb 反向建模这篇源码如果不想做“医院挂号”可以改造成“教室预约”“实验室排课”等方向。核心思路不是重写而是保留用户、角色、预约状态这条关系线。6.1 项目更名为自己的模块名先把hospital项目目录复制为classroom_booking或对应业务词然后把所有引用改掉。在 Linux/macOS 下执行grep -rl hospital --include*.py --include*.html | xargs sed -i s/hospital/classroom_booking/g注意 macOS 的sed -i需要Linux 直接sed -i。之后还要改manage.py里的DJANGO_SETTINGS_MODULE和wsgi.py中的同名引用否则启动会报找不到模块。接着把Patient改名为StudentDoctor改名为TeacherSchedule改名为RoomSchedule。改表名前先删掉旧的迁移文件重新makemigrations。如果原有数据库已经存在直接改 model 会导致迁移冲突更稳妥的是备份旧库后重建。6.2 使用 inspectdb 把已有表变成模型如果演示数据库里已经有设计好的表可以让 Django 反向生成模型省去手写字段的时间python manage.py inspectdb --databasedefault models_auto.py这会把数据库里所有表结构读成 Django model 代码输出到models_auto.py。生成的代码里字段名会按数据库保留字加别名外键有时识别为XXX_id而不是ForeignKey需要人工修正。通常做法是先inspectdb作为底稿对照原有设计把ForeignKey关系补上然后替换掉models.py最后再makemigrations生成新迁移脚本。这一步能让你快速验证自己对数据库结构的理解是否和源码一致。inspectdb的输出样式如下class Admin(models.Model): username models.CharField(max_length128) password models.CharField(max_length128) class Meta: managed False db_table admin注意生成的 model 默认managed False意思是 Django 不会管理这张表的迁移。想要纳入 ORM 管理必须把managed改为True再执行makemigrations。这个细节很关键因为在源码包里收集数据库时开发者可能直接导出了 SQL没有走 Django migration所以你直接migrate一张空表会失败需要先让 Django 感知模型变化。改造时还有一个实用技巧利用 admin 重新排序和自定义字段显示不用写页面就能快速录入测试数据。比如给ScheduleAdmin增加fieldsets控制编辑页布局给RegistrationAdmin增加list_per_page 20。在答辩现场演示时直接用 admin 编辑比在 MySQL 里敲 SQL 更直观而且能展示出你对 Django 后台的理解。python manage.py makemigrations --empty可以生成空迁移文件在数据库表已经存在的情况下用于对接如果你在履历里写了“数据库迁移”经验能把这条讲清楚就说明不是只跑过runserver。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。