ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python Django街区医院管理系统:论文源码到跑通避坑全解析

Python Django街区医院管理系统:论文源码到跑通避坑全解析

简介:面向Python毕业设计的街区医院管理系统完整资料包,适合计算机相关专业本科生、高职毕业生以及需要快速完成同类管理系统设计与论文撰写的开发者。资料以街区医院的日常管理为业务背景,完整覆盖从选题、需求分析、总体设计到编码实现、系统测试的毕业设计全过程。内容包含毕业论文文档与项目源码,技术栈基于Python语言、Django框架、MySQL数据库和B/S架构,并针对登录注册、管理员后台、医生诊疗、患者信息管理等核心功能给出了具体实现和测试用例。论文部分还包含系统流程图、数据库E/R图、数据库表设计、性能与可行性分析等章节,便于直接参考结构或进行二次扩展。压缩包整体约34.66MB,内容以论文文档和Python源码文件为主,按章节与功能模块组织,结构清晰。目前已有56人浏览/学习,适合用作课题参考、代码复现和论文写作模板,尤其适合需要从零搭建街区医院管理系统的毕业设计场景。

1. 街区医院管理系统到底是什么:一个带着论文和源码的完整交付物

拿到“基于Python的街区医院管理系统的设计与实现(论文+源码)”这种压缩包,第一反应往往不是读论文,而是先问一句:这里面的系统到底能不能跑起来。这类项目在高校课程设计和毕业设计里出现频率极高,核心是用Python Web框架实现一个覆盖挂号、门诊、收费、药房的小型管理系统,同时配套一篇说明设计思路的论文。它解决的不是“怎么用Python写接口”的问题,而是“怎么在有限时间里交付一个能演示、能答辩、能写入简历的完整业务系统”。

这篇文章写给两类人:一类是正在做课程设计或毕设、手里只有压缩包和论文草稿的同学,另一类是需要在短时间内给客户或内部团队搭一个小型管理系统的开发者。前者关心“怎么跑通并按论文讲清楚”,后者关心“这套设计有哪些可以复用的边界和坑”。我会从技术选型、数据库、启动流程、高频故障一路讲到验收习惯,全部围绕“论文+源码”这个组合展开。

2. Django还是Flask:选型理由与街区医院管理系统的模块划分

2.1 为什么这类系统几乎默认选Django

街区医院管理系统的业务画像很清晰:多个角色(挂号员、医生、收费员、药房管理员、院长)、大量数据录入和查询、数据之间关联紧密(患者挂了号才看诊,看完诊才开处方,开完处方才收费和发药)。这种模式属于典型的CRUD密集型业务系统,而不是高并发API服务。Django自带ORM、Admin后台、用户认证、表单处理,几乎把这类系统需要的底座全部备齐了;Flask虽然轻量,但用户认证、ORM、表单验证都需要自己组装,开发周期会长不少。

从论文写作的角度看,Django的MVC架构也更容易画图和讲原理。论文里常见的“B/S三层架构”“MTV设计模式”在Django里都有明确对应物:Model对应数据表,Template对应页面,View对应业务逻辑。答辩时被问到“为什么选这个框架”,回答里只需要对比Django与Flask在Admin、认证、ORM三方面的差异就够了。

2.2 街区医院业务模块的拆解与Django App划分

拿到需求先别急着写代码,先把业务模块切成独立App。我一般会把系统拆成下面几个模块,每个模块对应一个Django App:

App名称核心功能关键数据表需要演示的动作
accounts用户登录、权限控制User、Role用不同角色登录看到不同菜单
registration挂号管理Patient、Visit新增患者、挂当日号、查询排队列表
outpatient门诊与病历MedicalRecord、Prescription医生写病历、开处方
pharmacy药品与库存Drug、StockLog收费后自动扣库存、库存预警
billing收费管理Bill、Payment处方生成账单、收费确认并打印小票
stats统计报表各表聚合查询按日期查询就诊人次、收入总额

对于课程设计,6个App已经算完整;如果源码包里只有3到4个App,也不用慌,常见做法是把挂号与门诊合并,把收费与药房合并。比较重要的是模块边界要清晰——App之间通过外键关联业务数据,但不要出现A App直接修改B App内部状态的情况,设计文档里也好讲。

2.3 项目目录结构:解压后先看这几个文件

拿到源码包,先看目录结构再跑代码。一个典型的Django街区医院管理系统目录大致长这样:

hospital_system/ ├── manage.py ├── requirements.txt ├── db.sqlite3 ├── config/ │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── registration/ │ ├── models.py │ ├── views.py │ ├── urls.py │ └── migrations/ ├── outpatient/ ├── pharmacy/ ├── billing/ ├── templates/ ├── static/ └── docs/ # 论文写作稿或需求文档

先打开requirements.txt,确定项目依赖的Python版本和Django版本;再打开config/settings.py,看INSTALLED_APPS里注册了哪些App、DATABASES用的什么库。这两步做完,你基本就知道这个项目是用什么底子写的。

manage.py是Django所有管理命令的入口,数据库迁移、启动服务器、创建管理员账号全靠它。templates和static目录分别放HTML页面与CSS/JS资源。如果压缩包里带了db.sqlite3,说明作者已经跑过一遍并留下了演示数据;如果只有db.sqlite3没有migrations文件夹,处理起来要格外小心,后面避坑章节会详细说。

3. 数据库设计与核心模块:把论文里的E-R图变成能跑的表

3.1 患者、挂号、处方、账单:核心模型怎么定义

论文里的E-R图画得再漂亮,最后都要落成Django的Model类。我在实际项目中习惯把患者、挂号、医生、药品这四类核心实体先定义清楚。患者表建议单独建档案而不是每次挂号都重新填一遍,因为统计“复诊率”这类指标时要用到患者历史记录。一个简洁的患者模型如下:

from django.db import models class Patient(models.Model): GENDER_CHOICES = (("M", "男"), ("F", "女")) name = models.CharField("姓名", max_length=50) gender = models.CharField("性别", max_length=1, choices=GENDER_CHOICES) birth_date = models.DateField("出生日期", null=True, blank=True) phone = models.CharField("联系电话", max_length=20) address = models.TextField("住址", blank=True) created_at = models.DateTimeField("建档时间", auto_now_add=True) def __str__(self): return f"{self.name} ({self.get_gender_display()})"

字段设计的要点在birth_date用DateField而不是字符串——论文里患者年龄可以直接由出生日期计算,不用在代码里做字符串切分。auto_now_add=True会让建档时间在第一次保存时自动写入,页面不用手动传这个参数。choices配合get_gender_display()方法,在前端模板里可以显示中文性别,而不是存储的M/F字母。

挂号单是业务流程的中心节点,它连接了患者、医生和时间。设计挂号模型时要注意:同一天同一个医生同一个时段不能重复挂号,这部分逻辑可以放在视图层校验,也可以用UniqueConstraint在数据库层兜底。挂号模型我通常这样写:

class Visit(models.Model): STATUS_CHOICES = (("PENDING", "待就诊"), ("DONE", "已就诊"), ("CANCELED", "已取消")) patient = models.ForeignKey(Patient, on_delete=models.CASCADE, verbose_name="患者") doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE, verbose_name="医生") visit_date = models.DateField("就诊日期") status = models.CharField("状态", max_length=10, choices=STATUS_CHOICES, default="PENDING") created_at = models.DateTimeField("挂号时间", auto_now_add=True) class Meta: constraints = [ models.UniqueConstraint( fields=["patient", "visit_date"], name="unique_patient_visit_per_day", ) ]

ForeignKey的on_delete=models.CASCADE表示删除患者时会连带删除挂号记录,这在演示阶段很方便;但在真实生产环境里,医疗数据通常需要保留,更稳妥的是用PROTECT禁止删除患者或改成逻辑删除(加一个is_active字段)。课程设计阶段直接用CASCADE没有太大问题,论文里可以把这个点写进“不足与展望”。

3.2 挂号后如何走完就诊流程:一个带事务的视图示例

很多源码包里的视图函数只有两种写法:要么把所有逻辑堆在一个函数里,要么按教程示例写一个空的def index(request)。我在带A同学改模拟项目X时,最常动的代码就是挂号到收费这一条链路。下面是一个合并了挂号、创建病历、生成账单的视图示例:

from django.db import transaction from django.shortcuts import render, redirect, get_object_or_404 @login_required def complete_visit(request, visit_id): visit = get_object_or_404(Visit, id=visit_id, status="PENDING") if request.method == "POST": diagnosis = request.POST.get("diagnosis", "").strip() prescription_items = request.POST.getlist("drug_id") if not diagnosis: return render(request, "outpatient/visit_form.html", {"visit": visit, "error": "诊断结果不能为空"}) try: with transaction.atomic(): record = MedicalRecord.objects.create( visit=visit, doctor=request.user.doctor, diagnosis=diagnosis, ) for drug_id in prescription_items: drug = Drug.objects.select_for_update().get(id=drug_id) if drug.stock < 1: raise ValueError(f"药品 {drug.name} 库存不足") Prescription.objects.create(record=record, drug=drug, quantity=1) drug.stock -= 1 drug.save() Bill.objects.create(visit=visit, total_amount=calculate_bill(prescription_items)) visit.status = "DONE" visit.save() return redirect("billing:bill_detail", visit_id=visit.id) except ValueError as e: return render(request, "outpatient/visit_form.html", {"visit": visit, "error": str(e)}) return render(request, "outpatient/visit_form.html", {"visit": visit})

transaction.atomic()是这段代码最关键的部分:病历、处方、库存扣减、账单生成必须全部成功,否则回滚。如果不加事务,可能出现处方开成功但账单没生成、收费环节没法对账的尴尬情况。select_for_update()会给药品行加锁,避免两个窗口同时挂号时库存扣成负数,这也是论文里可以重点写的“并发控制”设计。

这段代码还演示了一个容易被忽略的参数处理方式:request.POST.getlist("drug_id")用于获取复选框提交的多选药品ID列表,而get("diagnosis")获取单项输入。很多新手在这里用get("drug_id")只能拿到第一项,导致处方总是只开出一味药。明白了这几个细节,后续改页面提交逻辑就有依据了。

3.3 登录权限与后台管理:Django自带能力的正确用法

街区医院管理系统涉及三类角色:挂号员只能操作挂号,医生只能写病历和处方,收费员只能处理账单。Django自带的auth应用提供了用户和组权限,但直接把权限绑定到用户身上很难维护,常见做法是用User模型扩展一个Profile来存角色。用信号或OneToOneField都可以,课程设计够用的写法是加一个profile:

from django.contrib.auth.models import User from django.db import models class Profile(models.Model): ROLE_CHOICES = (("REGISTRAR", "挂号员"), ("DOCTOR", "医生"), ("CASHIER", "收费员"), ("ADMIN", "管理员")) user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile") role = models.CharField("角色", max_length=20, choices=ROLE_CHOICES, default="REGISTRAR") real_name = models.CharField("真实姓名", max_length=50) @property def is_doctor(self): return self.role == "DOCTOR"

视图里判断角色时,用@login_required保证未登录用户进不来,再通过request.user.profile.is_doctor限制视图只对医生开放。这种写法比直接判断request.user.is_staff要清晰,也方便在模板里判断菜单显示:{% if request.user.profile.is_doctor %}显示病历菜单,否则隐藏。

/admin/后台则是调试阶段的后悔药。在Django Admin里注册所有模型后,不需要写任何前端页面就能增删改查患者、药品、医生数据,非常适合在系统还没跑通时快速造数据。具体到街区医院场景,我建议至少把Patient、Drug、Doctor注册进Admin,后面做演示数据初始化会省很大力气。

4. 从解压到跑通:本地环境搭建与系统启动

4.1 解压源码包后先检查三件事

很多学员下载完压缩包就直接双击manage.py,然后看到一堆报错就蒙了。为了避免这种局面,解压后我建议按顺序检查下面三个点:

第一,确认项目是不是解压在一级目录。很多压缩包解压后会出现hospital_system/hospital_system/manage.py这种嵌套结构,命令行工具只能在里层目录读到manage.py,找不到文件会报错。第二,看requirements.txt里有没有Django以及它规定的版本范围。第三,打开config/settings.py,确认ALLOWED_HOSTS是否包含localhost和127.0.0.1,不然启动后浏览器访问会被拒绝。

cd hospital_system ls -la cat requirements.txt

上面三条命令分别是进入项目根目录、查看文件列表、查看依赖清单。看到manage.py位于当前目录、依赖里有Django==4.2.x这种明确版本号时,环境问题基本就锁定了一半。

4.2 用虚拟环境安装依赖:不要直接装进系统Python

直接pip install -r requirements.txt装到系统Python里,短期能用,但项目一旦迁移或者升级依赖,系统环境就会被污染。我在所有Python项目里都用虚拟环境,这是成本最低的保命手段。Windows和macOS/Linux的命令略有差异:

python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate pip install -r requirements.txt

创建虚拟环境时,python命令的版本最好和开发者的版本保持一致。如果源码包里有runtime.txt或.python-version文件,按里面写的版本安装;没有的话就用Python 3.8到3.11这个区间内的版本,Django 3.2到4.2都能稳定运行。安装完成后用pip list | grep -i django验证Django是否装好。

4.3 修改settings:中文界面和数据库连接的必改项

默认的settings.py是英文界面、UTC时区、SQLite数据库。课程设计演示通常要在中文环境里跑,所以这三项必改:

# config/settings.py LANGUAGE_CODE = "zh-hans" TIME_ZONE = "Asia/Shanghai" USE_TZ = True DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3", } }

LANGUAGE_CODE改成zh-hans后,Django Admin后台会变成中文,表单校验报错也是中文提示,答辩演示时观感好很多。TIME_ZONE改成Asia/Shanghai后,auto_now_add写入的时间是东八区时间,否则UTC时间和中国时间差8小时,统计报表里按日期汇总数据会错位。USE_TZ保持True,这是Django的推荐配置,行政和业务时间都按UTC存储、展示时转本地时间。

在改数据库连接前,请先确认源码包里的db.sqlite3是否可用。如果压缩包自带数据库文件且能正常打开,直接用SQLite就可以完成全部演示;如果要换MySQL或PostgreSQL,就要重新建库、重新迁移,并处理编码和权限问题,这部分在下一章避坑里展开。

4.4 初始化数据库与创建管理员账号

数据库迁移是Django项目第一次运行时必须执行的步骤。如果源码包里有migrations目录,直接执行:

python manage.py makemigrations python manage.py migrate python manage.py createsuperuser

makemigrations根据models.py的变化生成迁移文件,migrate把迁移文件应用到数据库。createsuperuser会交互式地提问用户名、邮箱、密码,按提示填写即可。有一点要注意:如果db.sqlite3里已经存在数据表而模型没有变化,makemigrations会提示No changes detected,这是正常现象。

如果项目里已经有db.sqlite3且里面已经有测试账号,可以跳过createsuperuser直接试试已知账号;如果不知道密码,最稳妥的是删除db.sqlite3从头迁移。删除数据库文件前,建议把原文件复制一份出来改名保存,万一里面有什么需要保留的数据还能找回来。

4.5 启动开发服务器并做冒烟验证

启动服务和验证流程,我习惯用一条命令启动,再用浏览器或脚本快速验一遍:

python manage.py runserver 127.0.0.1:8000

浏览器打开http://127.0.0.1:8000,先访问首页看是否正常渲染模板;再访问/admin/用刚才创建的管理员账号登录,确认Admin后台能打开;最后如果系统有“创建患者”“挂号”的入口,先手工创建一条测试数据,走完整个主流程。之后用Python的requests库写一个冒烟脚本,每次改动后自动检查登录是否正常:

import requests BASE = "http://127.0.0.1:8000" client = requests.Session() login_data = {"username": "admin", "password": "your_password"} resp = client.post(f"{BASE}/login/", data=login_data) assert resp.status_code == 200, "登录失败" resp = client.get(f"{BASE}/patients/") assert resp.status_code == 200, "患者列表访问失败" print("冒烟通过:登录与患者列表均正常")

这份脚本里assert是硬校验,任何一个环节返回异常就直接抛错。如果你把脚本放到项目的scripts/目录并配合定时命令,每次改动模型或视图后跑一遍,就能尽早发现接口被改坏的问题。

5. 避坑排查:从迁移报错到中文乱码的5个高频问题

5.1 迁移时报错“table already exists”或“No migrations to apply”

有的人把压缩包里的db.sqlite3和源码一起保留,然后在models.py里加了字段再执行makemigrations,结果报“表已存在”;也有人删掉了db.sqlite3但没删旧迁移文件,执行迁移时报“表已经存在”。原因是一个数据库和一个迁移历史对不上。

解决方法是先看清当前有没有可保留的数据:如果演示数据不重要,直接删除db.sqlite3和每个App的migrations目录里的文件,保留__init__.py,再重新执行makemigrations和migrate。如果数据重要,就不要动数据库,而是把models.py的新改动追加成新迁移文件,让Django只生成增量迁移。

5.2 Windows下mysqlclient安装失败

为了在论文里写“采用MySQL数据库”,很多人在本地硬装mysqlclient,结果在Windows上屡屡报编译错误。根因是mysqlclient依赖MySQL C语言客户端库,Windows上没有现成的编译工具链。

如果一定要用MySQL,推荐改用pymysql作为驱动,在settings.py里注册:

import pymysql pymysql.install_as_MySQLdb() DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", "NAME": "hospital_demo", "USER": "root", "PASSWORD": "your_password", "HOST": "127.0.0.1", "PORT": "3306", } }

不要忘了在MySQL里先创建对应的空数据库,并保证字符集是utf8mb4。另外,课程设计和毕设演示使用SQLite完全够用,论文里把架构图画成MySQL不代表代码必须跑在MySQL上,把应用通过ORM抽象出来后,后续切换数据库只是改配置的事——但需要预留一个“当前使用SQLite,生产环境可切换MySQL”的说明,避免答辩老师追问。

5.3 Django后台中文内容乱码

页面显示正常,但进入Admin后台查看患者名字都是乱码;或者导出数据后在Excel里打开直接是乱码。前者多半是项目文件本身以GBK编码保存,浏览器按UTF-8解析;后者是因为Windows版Excel默认按GBK打开CSV,但Python写出的CSV是UTF-8。

页面乱码的解决方法是把所有Python源代码和模板统一保存为UTF-8,并在HTML模板加上<meta charset="utf-8">。Excel导出乱码的解决方法是写CSV时加BOM头:

import csv def export_patients_csv(request): response = HttpResponse(content_type="text/csv; charset=utf-8") response.write("\ufeff") writer = csv.writer(response) writer.writerow(["姓名", "性别", "电话"]) for p in Patient.objects.all(): writer.writerow([p.name, p.get_gender_display(), p.phone]) return response

\ufeff是UTF-8 BOM,Excel通过它识别文件编码为UTF-8,就不会再按GBK去解析。这个细节直接影响答辩时导出报表能不能被评委正常打开,没有这一步就是论文里写得好、实操时在评委面前翻车。

5.4 session频繁失效或登录后马上被踢回登录页

开发环境里时常出现这种神奇现象:登录成功,点一下菜单又回到登录页,好像被什么神秘力量拦截。排掉代码逻辑后最常见的两个原因是:SECRET_KEY每次重启都在变,或SESSION_COOKIE_AGE设置太短。

SECRET_KEY用于签名session,项目源码里如果写了SECRET_KEY = os.environ.get("SECRET_KEY")而环境变量没有配置,每次进程重启生成的key都不一样,旧session自然失效。解决方法是:

# settings.py 中固定本地开发用的密钥 SECRET_KEY = "django-insecure-xxxx-dev-only" if os.environ.get("DJANGO_SECRET_KEY"): SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]

上面的做法是开发环境固定、生产环境用环境变量覆盖,既保证本地稳定又不把真实密钥写进仓库。SESSION_COOKIE_AGE默认是两周,如果被改成了60那就是1分钟过期,改成86400 * 7即可。

5.5 论文截图和数据对不上:演示前数据库是空的

很多源码包是开发者在写论文前定稿的,而压缩包里的数据库可能是空库,或者论文截图用了一套完整数据,你运行起来却是空白页。避免演示时尴尬的通用做法是准备一个初始化脚本,每次展示前重建数据库并填充核心数据。

python manage.py flush --no-input python manage.py migrate python manage.py seed_demo_data

flush清空所有表中的数据(保留表结构),migrate重新应用迁移,seed_demo_data是放在任意App的management/commands/目录下的自定义命令,用代码创建科室、医生、药品、示例患者和几条挂号记录。演示前跑一遍这三条命令,系统就回到了论文截图那样的“标准状态”。

6. 验收前的最后一个习惯:用冒烟脚本和数据快照固定演示状态

演示系统有两大最怕出问题的地方:一是环节太多现场操作容易漏步,二是数据被改动后无法回到整齐状态。我的习惯是在项目根目录放一个check_demo.py脚本,并在里面完整跑一遍“登录-挂号-写病历-收费-查看统计”的主流程;同时用dumpdata给演示数据集做一个快照,每次答辩或评审前恢复:

python manage.py dumpdata --exclude auth.permission --exclude contenttypes > demo_data.json python manage.py flush --no-input python manage.py loaddata demo_data.json

dumpdata导出的JSON是Django的序列化格式,loaddata可以直接导入。把这两条命令配合使用,就相当于给数据库拍了快照,随时能回到稳定状态。脚本注意要--exclude auth.permission,因为权限表在flush后会被重新生成,强行导入反而会出冲突。

在带A同学做模拟项目X时,我把这套流程压缩成三步:先跑冒烟脚本确认主流程通,再恢复演示数据,最后打开Admin后台检查中间状态。这三步每次都控制在两分钟以内,却能让运行时异常在演示前暴露出来。对我自己来说,这是踩过现场答辩跑不起来的坑之后养成的习惯——论文写得再好,系统演示翻车一次就全白费。希望这套“先跑通、再快照、最后演示”的流程,也能帮你在评审和交付前少一点手忙脚乱。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进