ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Django房屋出租租赁系统实战:状态机、合同与账单设计要点

Django房屋出租租赁系统实战:状态机、合同与账单设计要点 简介一份基于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_length20, uniqueTrue) area models.DecimalField(面积㎡, max_digits5, decimal_places1) monthly_rent models.DecimalField(月租金, max_digits8, decimal_places2) STATUS_CHOICES ((1, 空置), (2, 已预定), (3, 已出租), (4, 维修中)) status models.IntegerField(状态, choicesSTATUS_CHOICES, default1) is_active models.BooleanField(启用, defaultTrue) created_at models.DateTimeField(auto_now_addTrue) class Tenant(models.Model): name models.CharField(租客姓名, max_length30) id_card models.CharField(身份证号, max_length18, uniqueTrue) phone models.CharField(联系电话, max_length11) class LeaseContract(models.Model): contract_no models.CharField(合同编号, max_length32, uniqueTrue) house models.ForeignKey(House, on_deletemodels.PROTECT, verbose_name房屋) tenant models.ForeignKey(Tenant, on_deletemodels.PROTECT, verbose_name租客) start_date models.DateField(起租日期) end_date models.DateField(结束日期) deposit models.DecimalField(押金, max_digits8, decimal_places2) created_at models.DateTimeField(auto_now_addTrue) class RentBill(models.Model): contract models.ForeignKey(LeaseContract, on_deletemodels.CASCADE, verbose_name合同) bill_month models.DateField(账期) amount models.DecimalField(应缴金额, max_digits8, decimal_places2) due_date models.DateField(缴费截止日) paid models.BooleanField(已缴, defaultFalse) class Meta: constraints [ models.UniqueConstraint(fields[contract, bill_month], nameuniq_contract_bill_month), ]代码里最关键的是两个约束。合同对房屋用on_deletemodels.PROTECT而不是 CASCADE意思是只要存在关联合同就不允许把房屋记录连坐删除这保护的是业务证据链防止退租后账单还在却找不到房源信息。账单表的联合唯一约束则从数据库层面兜底保证同一份合同在同一个账期里不会重复生成两条记录。Hose里的status用 IntegerField 加 choices后续做统计时可以直接按数字过滤比存中文字符串更稳。实体关键字段设计理由Houseroom_no 唯一房号的业务唯一性由数据库保证不依赖人工检查LeaseContract双 PROTECT 外键合同是业务证据禁止级联删除关联方RentBill合同账期联合唯一防止同一月份重复出账Tenantid_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 python3.10 -y conda activate house_rent。我一般倾向用 venv毕业设计项目没有引入 conda 的必要交代码时也少一个环境依赖要求。提示用 VSCode 打开项目根目录后按 CtrlShiftP 打开命令面板选 “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, operatorNone, 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 fHT{date_part}{house_room_no}{random.randint(100, 999)}{tenant_id}参数说明house_room_no直接取房号里的数字部分tenant_id是租客主键随机数只做最后一道防撞保险数据库层还有contract_no的唯一约束兜底。生成合同后下一步是把它变成能打印的 PDF。这里我强烈建议不要一开始就上 reportlab中文 PDF 需要在代码里注册字体文件稍微配置不对就是豆腐块乱码WeasyPrint 效果虽好但依赖的系统库较多在没外网的机器上装起来麻烦。最简单的方案是做一个专门用于打印的 HTML 模板浏览器按 CtrlP 直接另存为 PDF。!-- templates/contract_print.html -- div classcontract 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 mediaprint 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_day10): bill_month_date date(bill_year, bill_month, 1) bill, created RentBill.objects.get_or_create( contractcontract, bill_monthbill_month_date, defaults{ amount: contract.house.monthly_rent, due_date: bill_month_date.replace(daydue_day), }, ) return bill, created def calc_late_fee(bill, daily_rate0.001, cap_rate0.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_rate0.001即千分之一具体数值按题目业务调整。cap_rate设了罚金封顶避免长期不缴产生离谱金额。这里特别提醒一个类型转换的坑due_date是 DateField计算逾期天数时比较双方都必须是date对象。如果从表单拿到的是2025-06-10这样的字符串要先做一次类型转换再比较否则会抛出TypeError: cant compare datetime.date to str。4.4 列表查询的 N1 问题和统计图坐标设置账单列表页最常见的问题是 N1 查询先查 100 条账单再逐条从外键去查合同和房屋产生 101 条 SQL。Django ORM 的解决办法很简单。bill_list RentBill.objects.filter(paidFalse).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、*.pycrequirements.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如果希望评审解开包就能看到演示数据把它从排除列表里去掉即可。本文还有配套的精品资源点击获取
返回列表