简介:一份基于Python的房屋出租租赁管理系统毕业设计资源,面向计算机相关专业学生及需要快速搭建租赁管理系统的开发者。系统涵盖房源信息管理、租户管理、租赁合同处理、租金计算与支付跟踪等核心模块,采用前后端分离架构,是典型的Web开发参考项目。资源包为ZIP格式,共417个文件、约25.04MB,其中164张JPEG图片多为界面截图,43个Vue文件对应前端组件,31个Python文件承载后端业务逻辑,另有SVG/PNG图标与配置文件,目录结构清晰便于按模块检索。已有159人学习下载,适合在校学生用于毕业设计、课程设计,或开发者借鉴项目整体设计思路。通过该资源可获取完整项目源码、界面素材和配置说明,了解用户登录认证、表单验证、路由设计等实现方式,对掌握Flask/Django开发、数据库设计及前后端交互较有帮助,也便于在此基础上二次扩展。
1. 毕业设计里的房屋出租租赁系统,最容易卡在房间状态对不上
把增删改查写完,一个房屋出租租赁系统只是立住了骨架;真正决定它能否通过答辩、能否接住别人真实业务的是房间状态在时间线上不打架。一间房从空置到预定、签约、退租,再重新上架,任何一个环节跨错了,后续统计出来的空置率和账单都会跟着乱。这类 zip 解压后,大概率是一个 manage.py 开头的 Django 项目,里面通常已经摆了房屋、租客、合同、账单这四张核心表,但表结构本身并不值钱。下面按我拿到这种项目会走的路径,把环境从零搭起来、把状态机和合同生成这些最容易出错的地方讲透。适合正在准备答辩、做课程设计,或者想快速看懂一个 Python 业务系统完整骨架的人。
2. 选型与建模:Django 的骨架和四个核心模型
2.1 为什么这类项目普遍落在 Django 上
房屋租赁系统的数据关系不复杂,但角色边界非常明确。管理员负责录入房间、审核合同,租客要能查自己的账单和缴费记录,这意味着权限不是锦上添花,而是刚性需求。Django 自带人员、权限、会话体系和后台管理,连登录页都不用从头写,对毕业设计来说是框架先替你把一半地基打完了。Flask 虽然更轻,但登录、后台、ORM 都要自己拼,拼出来的脚手架往往不成体系,反而容易在答辩时被追问安全设计。
FastAPI 适合异步 IO 密集的场景,在这里以表单提交和报表查询为主的业务里没有明显收益,引入后还会增加异步 ORM 的心智负担。还有一个常见做法容易被忽略:Django 的迁移工具能在改完模型后一条命令把数据库表同步掉,答辩前几天临时加字段、加索引时,这个能力能省去大量手工维护 SQL 的麻烦。项目如果是按前后端分离做的,还要多维护一套 Vue 构建产物和接口文档,评审现场要跑两个服务,环境成本不低。
2.2 四个核心实体:房屋、租客、合同、账单
这里给出一版最小但够用的 models.py,字段命名尽量贴近业务,不做过度抽象。
from django.db import models class House(models.Model): room_no = models.CharField("房号", max_length=20, unique=True) area = models.DecimalField("面积㎡", max_digits=5, decimal_places=1) monthly_rent = models.DecimalField("月租金", max_digits=8, decimal_places=2) STATUS_CHOICES = ((1, "空置"), (2, "已预定"), (3, "已出租"), (4, "维修中")) status = models.IntegerField("状态", choices=STATUS_CHOICES, default=1) is_active = models.BooleanField("启用", default=True) created_at = models.DateTimeField(auto_now_add=True) class Tenant(models.Model): name = models.CharField("租客姓名", max_length=30) id_card = models.CharField("身份证号", max_length=18, unique=True) phone = models.CharField("联系电话", max_length=11) class LeaseContract(models.Model): contract_no = models.CharField("合同编号", max_length=32, unique=True) house = models.ForeignKey(House, on_delete=models.PROTECT, verbose_name="房屋") tenant = models.ForeignKey(Tenant, on_delete=models.PROTECT, verbose_name="租客") start_date = models.DateField("起租日期") end_date = models.DateField("结束日期") deposit = models.DecimalField("押金", max_digits=8, decimal_places=2) created_at = models.DateTimeField(auto_now_add=True) class RentBill(models.Model): contract = models.ForeignKey(LeaseContract, on_delete=models.CASCADE, verbose_name="合同") bill_month = models.DateField("账期") amount = models.DecimalField("应缴金额", max_digits=8, decimal_places=2) due_date = models.DateField("缴费截止日") paid = models.BooleanField("已缴", default=False) class Meta: constraints = [ models.UniqueConstraint(fields=["contract", "bill_month"], name="uniq_contract_bill_month"), ]代码里最关键的是两个约束。合同对房屋用on_delete=models.PROTECT而不是 CASCADE,意思是只要存在关联合同就不允许把房屋记录连坐删除,这保护的是业务证据链,防止退租后账单还在却找不到房源信息。账单表的联合唯一约束则从数据库层面兜底,保证同一份合同在同一个账期里不会重复生成两条记录。Hose里的status用 IntegerField 加 choices,后续做统计时可以直接按数字过滤,比存中文字符串更稳。
| 实体 | 关键字段 | 设计理由 |
|---|---|---|
| House | room_no 唯一 | 房号的业务唯一性由数据库保证,不依赖人工检查 |
| LeaseContract | 双 PROTECT 外键 | 合同是业务证据,禁止级联删除关联方 |
| RentBill | 合同+账期联合唯一 | 防止同一月份重复出账 |
| Tenant | id_card 唯一 | 同一身份证对应一份长期绑定关系 |
2.3 先留好三个对状态管理有用的钩子
第一是is_active软删除开关。房源下架时不直接删行,只把is_active改成 False,列表页默认过滤这个字段,这样历史合同、历史账单在关联查询时不会因为外键断裂而查不到。第二是时间戳字段,created_at必须保留,账单逾期计算、合同到期提醒都依赖它。第三是状态字段用 IntegerField 而不是 CharField,然后用 Python 类常量去引用,视图代码里写HouseStatus.RENTED而不是3,可读性和可维护性都会好很多。
这三样东西在写报表时非常有用。空置率统计要区分“维修中”和“空置”,逾期账单需要按账期排序,演示数据造假数据时也要有明确的创建时间线,提前留好钩子后面不用返工。
3. 从 zip 到能跑通:Python 环境、依赖与启动
3.1 解压后先认目录,别急着执行启动命令
拿到 zip 后第一步是解压并看清项目结构,而不是直接找 README 里的命令。
unzip house_system.zip -d house_project cd house_project ls -la目录里出现manage.py,说明这是一个标准 Django 工程,接下来的所有操作都围绕这个文件展开。requirements.txt是依赖清单,决定你能不能复现作者的运行环境。如果 list 结果里出现venv或.venv文件夹,建议先删掉自己重建,因为 Windows 和 Linux 的虚拟环境目录不能互相搬迁,直接复用容易在装包时报出各种路径相关的诡异错误。db.sqlite3很多毕设会一起打包,它能让你先跑起来看到数据,但别依赖里面带的账号,后面自己建一个管理员更省心。
3.2 Python 环境安装与虚拟环境隔离
先确认本机 Python 版本,这一步卡住的人最多。Windows 上打开命令行输入python --version,如果安装时勾选了“Add python.exe to PATH”会直接输出版本;没勾选的话,可以试试py -0列出所有已安装版本,然后py -3.10指定某一个。Linux 系统里通常要输入python3 --version,因为 python 这个命令不一定存在。对这类 Django 项目,常见做法是用 Python 3.8 或 3.10,兼容性最省心。
创建虚拟环境用 Python 自带的 venv 就行,不需要额外装东西。
python -m venv .venv source .venv/bin/activateWindows 下的激活命令换成.venv\Scripts\activate。激活后命令行前面会出现(.venv)前缀,这时再执行 pip 才不会再污染全局环境。如果本机装了 Anaconda,也可以用 conda 创建:conda create -n house_rent python=3.10 -y && conda activate house_rent。我一般倾向用 venv,毕业设计项目没有引入 conda 的必要,交代码时也少一个环境依赖要求。
提示:用 VSCode 打开项目根目录后,按 Ctrl+Shift+P 打开命令面板,选 “Python: Select Interpreter”,把解释器指向刚创建的 .venv 路径,运行和调试才能识别到项目依赖。
3.3 安装依赖与数据库初始化
依赖安装和数据库初始化是一套组合操作,建议按顺序执行。
pip install -r requirements.txt python manage.py migrate python manage.py createsuperuser python manage.py runserver 127.0.0.1:8000pip install如果下载慢,可以加镜像源参数:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt。migrate会把应用里的迁移文件变成真实的数据库表结构,如果报 “No migrations to apply”,多半是自带的 db.sqlite3 已经有旧表,备份后删掉再执行一次即可。createsuperuser按提示输入用户名、邮箱和密码,这个管理员用来登录后台看数据。最后runserver 127.0.0.1:8000启动开发服务器,服务默认监听 8000 端口,浏览器访问http://127.0.0.1:8000就能看到首页。
如果 pip 安装时报 “Please install the missing package” 这类提示,先确认当前是否在激活的虚拟环境里,再检查 requirements.txt 里包名是否完整。在项目里不要 track 这个 zip 文件,而是 track 解压后的工程目录,后续改动才能被识别。
3.4 跑起来后先验证三个入口
| 入口 | 应该看到的内容 | 异常时的排查方向 |
|---|---|---|
| /admin | 后台登录页,能用自己的账号登录 | 检查是否执行了 createsuperuser |
| / | 前台首页或房源列表 | 看 urls.py 里根路径是否绑定视图 |
| /house/list 或对应URL | 房屋列表,包含状态字段 | 看是否完成 migrate,数据表是否存在 |
打开后台后如果看不到房屋、合同这些模型,多半是 admin.py 里没有注册模型。常见做法是在应用的 admin.py 里写admin.site.register(House)这样一行,模型就会出现在后台导航里。这个步骤虽然简单,但经常被漏掉,导致评审看着后台只有默认的 User 和 Group。
4. 核心业务落地:房间状态机、合同与账单
4.1 状态机:房间状态只能用业务方法改,不能在视图里直接赋值
房屋状态是整套系统的业务锚点。空置、已预定、已出租、维修中这四种状态之间,并不是任意两个都能互相跳转。比如一间房在租约期内,理论上不可能直接变回空置,否则退租日期和合同起止就矛盾了。常见做法是把状态切换收敛到一个显式的方法里,禁止在视图层直接写house.status = 3。
class HouseStatus: VACANT = 1 RESERVED = 2 RENTED = 3 MAINTENANCE = 4 ALLOWED_TRANSITIONS = { HouseStatus.VACANT: {HouseStatus.RESERVED, HouseStatus.RENTED, HouseStatus.MAINTENANCE}, HouseStatus.RESERVED: {HouseStatus.VACANT, HouseStatus.RENTED, HouseStatus.MAINTENANCE}, HouseStatus.RENTED: {HouseStatus.MAINTENANCE}, HouseStatus.MAINTENANCE: {HouseStatus.VACANT, HouseStatus.RESERVED}, } def change_house_status(house, new_status, operator=None, remark=""): allowed = ALLOWED_TRANSITIONS.get(house.status, set()) if new_status not in allowed: raise ValueError(f"不允许从状态 {house.status} 切换到 {new_status}") house.status = new_status house.save(update_fields=["status"])代码的核心是ALLOWED_TRANSITIONS这个字典,它用集合表达了每种状态可以跳转到哪里。change_house_status接收三个参数:house 对象、目标状态、可选的操作用户和备注。先查允许集合,目标状态不在集合里直接抛异常,通过异常信息能快速定位到是哪个业务场景触发的不合法流转。update_fields参数让 save 只更新状态字段,减少无效写库。
| 当前状态 | 可跳转目标 | 业务含义 |
|---|---|---|
| 空置 | 已预定 / 已出租 / 维修中 | 空房可以预定、直接签约或进入维修 |
| 已预定 | 空置 / 已出租 / 维修中 | 预定取消回空置,或签约转在租 |
| 已出租 | 维修中 | 租期内维修,不改变合同关系 |
| 维修中 | 空置 / 已预定 | 修完再上架,可进入预定 |
为什么不能直接改字段?因为状态变更往往伴随其他操作。签约要同时生成合同、预定要记录截止时间、退租要触发账单结算,这些动作放在视图里每个地方写一遍,很容易出现“状态改了合同没生成”的情况。把状态机做成方法后,后续如果在里面加一个StatusLog表记录每次切换的操作人和时间,答辩时这就是一个现成的亮点功能。
4.2 合同编号生成与可打印合同实现
合同编号要有业务含义,不能简单用自增 ID。常见格式是“HT + 年月日 + 房号 + 随机数”,既能排序也能防并发重复。
import random from datetime import datetime def generate_contract_no(house_room_no, tenant_id): date_part = datetime.now().strftime("%Y%m%d") return f"HT{date_part}{house_room_no}{random.randint(100, 999)}{tenant_id}"参数说明:house_room_no直接取房号里的数字部分,tenant_id是租客主键,随机数只做最后一道防撞保险,数据库层还有contract_no的唯一约束兜底。生成合同后,下一步是把它变成能打印的 PDF。这里我强烈建议不要一开始就上 reportlab,中文 PDF 需要在代码里注册字体文件,稍微配置不对就是豆腐块乱码;WeasyPrint 效果虽好,但依赖的系统库较多,在没外网的机器上装起来麻烦。最简单的方案是做一个专门用于打印的 HTML 模板,浏览器按 Ctrl+P 直接另存为 PDF。
<!-- templates/contract_print.html --> <div class="contract"> <h3>房屋租赁合同 <span>{{ contract.contract_no }}</span></h3> <p>出租方:{{ contract.house.owner_name }} 承租方:{{ contract.tenant.name }}</p> <p>租赁期限:{{ contract.start_date }} 至 {{ contract.end_date }}</p> <p>月租金:{{ contract.house.monthly_rent }} 元</p> </div> <style media="print"> @page { margin: 20mm; } .contract { font-size: 14px; line-height: 1.8; } </style>这套思路的优点是输出干净、自带分页,而且对中文字体完全没要求,浏览器渲染什么就打印什么。需要的只是写一个视图把合同对象塞进模板,返回text/html,用户在当前页面调出打印功能即可。
4.3 按月生成账单与逾期费用计算
账单生成的核心是幂等性,也就是同一合同同一账期只能生成一次。前面的 UniqueConstraint 已经做了数据库兜底,业务代码里用get_or_create配合即可。
from datetime import date def create_rent_bill(contract, bill_year, bill_month, due_day=10): bill_month_date = date(bill_year, bill_month, 1) bill, created = RentBill.objects.get_or_create( contract=contract, bill_month=bill_month_date, defaults={ "amount": contract.house.monthly_rent, "due_date": bill_month_date.replace(day=due_day), }, ) return bill, created def calc_late_fee(bill, daily_rate=0.001, cap_rate=0.1): if bill.paid: return 0 overdue_days = (date.today() - bill.due_date).days if overdue_days <= 0: return 0 fee = bill.amount * daily_rate * overdue_days return min(fee, bill.amount * cap_rate)get_or_create的defaults参数在记录不存在时生效,已存在时返回旧记录和 False。这样即使脚本被重复执行,也不会产生重复账单。逾期费用按天计算,日费率万分之一?这里参数化后写成了daily_rate=0.001即千分之一,具体数值按题目业务调整。cap_rate设了罚金封顶,避免长期不缴产生离谱金额。
这里特别提醒一个类型转换的坑:due_date是 DateField,计算逾期天数时比较双方都必须是date对象。如果从表单拿到的是"2025-06-10"这样的字符串,要先做一次类型转换再比较,否则会抛出TypeError: can't compare datetime.date to str。
4.4 列表查询的 N+1 问题和统计图坐标设置
账单列表页最常见的问题是 N+1 查询:先查 100 条账单,再逐条从外键去查合同和房屋,产生 101 条 SQL。Django ORM 的解决办法很简单。
bill_list = RentBill.objects.filter(paid=False).select_related("contract__house", "contract__tenant")select_related适用于单值外键关系,contract__house用双下划线跨了两层外键,一条 join 把关联对象全部带出。反过来,如果要查一套合同下的所有账单,那是多值反向关系,该用prefetch_related,两者不要混用。
统计图也是毕设的高频扣分点。如果自己画月租金收入曲线,日期一多横坐标会全部挤在一起。做法是只用 matplotlib 的plt.xticks指定刻度间隔,比如每月只保留一个刻度并旋转 45 度,而不是让默认行为把所有日期全摆上去。这个细节很小,但直接决定图表在答辩投屏上能不能看清楚。
5. 答辩前应该做掉的三件事:依赖清理、演示数据与交付包
5.1 用 pipreqs 清理依赖,而不是 pip freeze
很多人交项目的最后一步是pip freeze > requirements.txt,这会把当前环境里所有无关包全部写进文件,评审在新机器上安装时又慢又容易冲突。常见做法是用 pipreqs 按项目实际 import 生成。
pip install pipreqs pipreqs . --force--force表示覆盖已有的 requirements.txt。生成后打开文件人工检查一遍,Django、Pillow 这类核心包在不在,版本号是否符合当前代码。如果环境里没有网络,也可以手写一份,只列真实用到的包。
5.2 准备一套有故事情节的演示数据
评审看系统不会从头点菜单,而是带着问题看:有没有逾期账单?空置率怎么算?预定未签的房子长什么样?所以演示数据要有剧情,不能每个房间都长得一样。先想好故事线,再用 Django 的 fixture 机制导出导入。
python manage.py dumpdata house --indent 4 > fixtures/demo.json python manage.py loaddata fixtures/demo.jsondumpdata后面跟应用名,只导业务表,不导 User 和 Session。建议准备一间在租房、一间预定未签、一间维修中、一张已逾期账单,演示时按“正常流程走一遍,再引出异常处理”的顺序讲,比登录后台乱点有说服力得多。
5.3 跑一遍 Django 自带的安全检查
python manage.py check --deploy是 Django 内置的部署安全检查,不装任何第三方扩展就能用。它会输出 CSRF 配置、ALLOWED_HOSTS、DEBUG 开关、SECRET_KEY 相关警告。注意它只做静态检查,不会帮你改配置。本地演示时不要把 DEBUG 改成 False,改静态文件服务会额外引入一堆问题,只要知道检查结果里哪些是真正需要修的即可。
5.4 zip 压缩时的排除规则
| 保留 | 排除 |
|---|---|
| manage.py、应用目录、templates | .venv、pycache、*.pyc |
| requirements.txt、README、fixtures | .git 目录、旧 zip 文件 |
| db.sqlite3(可选,建议排除) | .idea、.vscode 本地配置 |
zip -r house_rental_system_final.zip . \ -x ".venv/*" "*__pycache__/*" "*.pyc" ".git/*" "db.sqlite3"排除.venv是因为压缩包不需要携带完整 Python 环境,评审按 README 里的步骤重建只需要几分钟。注意显式排除了db.sqlite3,如果希望评审解开包就能看到演示数据,把它从排除列表里去掉即可。
本文还有配套的精品资源,点击获取