尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Python Django不动产登记与审核数据分析系统全流程解析

发布时间:2026/9/26 17:08:58

资讯中心
01
ARTICLE

Python Django不动产登记与审核数据分析系统全流程解析

Python Django不动产登记与审核数据分析系统全流程解析
这套Python基于Django的不动产登记与审核数据分析系统是我近期整理并完整跑通的一个全栈实战项目代码、文档、演示数据都打包好了。项目本身不算复杂但胜在业务链路完整从窗口受理登记申请到后台多级审核流转再到最后的业务量统计、审核时效分析一条线全是打通的状态非常适合拿来当课程设计、毕业设计或者作为Django入门后第一个完整的“业务系统级”练手项目。1. 项目全貌这套不动产登记与审核系统到底能做什么1.1 拆解标题背后的核心需求先说说“不动产登记与审核数据分析”这个组合到底意味着什么。不动产登记属于典型的政务/企业级业务场景核心动作就两个把登记信息准确录入系统再走一套审核流程确认无误后完成登记。而数据分析则是这套系统真正有含金量的地方——光能增删改查没有任何竞争力能把业务数据转化成柱状图、趋势线和时效指标才是这类系统的价值体现。因此整个项目的需求可以拆成三大块登记业务模块申请人/权利人信息、不动产单元信息、业务类型首次登记、转移登记、抵押登记、变更登记、注销登记等支持登记申请的新增、修改、查询。审核流转模块提交后进入审核队列审核员可以逐级审核通过则继续下一个环节驳回则退回并记录原因。整个状态流转要清晰可追溯。数据分析模块按时间、业务类型、办理区域、审核状态等维度统计登记数量分析平均审核耗时生成可视化报表。这三块就是整个项目的主心骨也是面试或答辩时最能拿得出手的东西。1.2 为什么选Django而不是Flask或Spring Boot作为开发者选型第一反应应该是“这套系统里最难的部分是什么”。这个项目的难点在业务数据建模和审核状态流转而不在高并发或复杂的前端交互。Django自带的ORM、Admin后台、认证系统完全可以覆盖开发效率非常高——一个model定义好数据库表、表单校验、后台管理页面全都跟着出来了。对比其他方案的优劣技术方案优势劣势适用场景Django SQLite/MySQL全家桶式开发ORM自带迁移Admin后台免费送不适合超高并发课程设计、中小型业务系统Flask SQLAlchemy灵活轻量认证、Admin、表单都要自己拼装微服务、小型APISpring Boot企业级生态强上手成本高配置繁琐代码量大大型企业项目前后端分离VueAPI交互体验好需要同时维护两套工程对交互要求极高的场景我选Django还有一层考量这套系统包含大量的统计查询Django ORM的annotate、aggregate、Q对象组合可以让聚合统计用很短的代码写出来完全不需要额外引入Pandas做离线分析——实时查询直接在数据库层面完成响应速度有保障。1.3 系统的完整运行流程从用户视角走一遍管理员登录后创建用户并分配角色受理员、审核员、管理员。受理员登录系统录入一条不动产登记业务比如某小区某栋某户的转移登记填写不动产单元号、坐落、权利人姓名、证件号等信息提交后业务进入“待初审”状态。审核员登录后看到待初审队列核对信息通过则进入“待复审”驳回则填写意见退回。最终审核通过后业务状态变为“已登记”办理详情页记录每一步操作人和时间。管理员随时可以进入数据分析页面选择时间范围和业务类型查看柱状图、趋势图以及各审核环节的平均耗时排名。整个闭环就完整了。2. 核心表设计与审核状态流转先想清楚再写代码2.1 数据库表结构不动产登记信息如何建模写model之前我先把业务表拆了一遍。项目最终落地了五张核心表这里给出最关键的三个from django.db import models from django.contrib.auth.models import User class BusinessType(models.TextChoices): FIRST_REG first, 首次登记 TRANSFER transfer, 转移登记 MORTGAGE mortgage, 抵押登记 CHANGE change, 变更登记 CANCEL cancel, 注销登记 class RealEstateUnit(models.Model): 不动产单元表一房一档唯一标识不动产 unit_no models.CharField(不动产单元号, max_length28, uniqueTrue) location models.CharField(坐落, max_length200) area models.DecimalField(建筑面积(㎡), max_digits10, decimal_places2) owner_name models.CharField(产权人, max_length50) created_at models.DateTimeField(创建时间, auto_now_addTrue) class Meta: verbose_name 不动产单元 verbose_name_plural verbose_name def __str__(self): return self.unit_no class RegisterApplication(models.Model): 登记业务申请表每一条申请对应一次完整业务办理 app_no models.CharField(业务编号, max_length32, uniqueTrue) unit models.ForeignKey(RealEstateUnit, on_deletemodels.PROTECT, verbose_name不动产单元) applicant models.CharField(申请人, max_length50) id_card models.CharField(证件号码, max_length18) business_type models.CharField(业务类型, max_length20, choicesBusinessType.choices) status models.CharField(审核状态, max_length20, choicesAUDIT_STATUS, defaultdraft) reject_reason models.TextField(驳回原因, blankTrue, nullTrue) operator models.ForeignKey(User, on_deletemodels.SET_NULL, nullTrue, related_namecreated_apps, verbose_name受理人) created_at models.DateTimeField(提交时间, auto_now_addTrue) updated_at models.DateTimeField(更新时间, auto_nowTrue) class Meta: verbose_name 登记业务 verbose_name_plural verbose_name ordering [-created_at]几点设计心得app_no业务编号不是数据库主键但加了uniqueTrue。我建议业务单号在业务层生成格式类似“DJ20250611001”前缀日期当日流水号比直接用自增主键更符合业务习惯打印出来也能直接看出办理日期。外键统一用PROTECT而不是默认级联删除。登记业务属于留痕数据如果有人误删关联的不动产单元会导致历史业务变成无源之水。PROTECT会在删除时抛出异常这层保护很有必要。2.2 审核状态机不要让状态散落在if else里审核流程的状态定义是这样的AUDIT_STATUS [ (draft, 草稿), (submitted, 待初审), (first_pass, 初审通过), (rejected, 驳回), (final_pass, 已登记), ]表面上看状态只有五个但真正容易出问题的是“谁可以执行哪个操作”。如果任意的if-else散落在views里后面改起来非常痛苦。我采用的是权限状态双重校验的方式# audit/utils.py def can_audit(user, application): 判断当前用户能否审核该业务 if not user.is_authenticated: return False, 未登录 if application.status ! submitted: return False, 当前状态不可审核 if user.groups.filter(name审核员).exists(): return True, return False, 无审核权限这个函数用在一个mixin里统一给所有审核相关视图加保护class AuditPermissionMixin(UserPassesTestMixin): def test_func(self): ok, _ can_audit(self.request.user, self.get_object()) return ok这样整个状态流转的逻辑就集中了views里只关心业务动作不需要重复判断角色和状态。2.3 角色权限为什么用Django自带的Group而不是写个字段项目里涉及三类角色管理员、受理员、审核员。最简单的实现是在User表里加一个role字段然后判断user.role auditor。但我建议优先用Django自带的Group权限框架原因有二自建字段判断角色一旦业务要求“受理员可以查看自己受理的业务但不能查看别人的”你还要手工在查询里写过滤条件。而用Group权限可以精确控制到“某个用户对某张表是否有增改权限”。第二点是可扩展性。课后作业加一个“超级审核员”角色自建字段方案得改代码里所有判断Group方案只需要新建一个Group、赋上权限代码一行不用动。当然自建字段方案写得快、理解起来直观如果你只是交作业也不是不能用。我的建议是有时间就按Group做答辩时能多讲一条“权限设计”的亮点。3. 三大核心模块落地登记提交、审核流转与数据分析3.1 登记业务提交表单校验的三个隐藏细节登记业务的提交页面是系统的门面。Django Form ModelForm可以快速实现但我实际操作中发现有几个细节直接决定数据质量第一日期类字段建议拆成三个整数下拉框而不是一个DateInput。原因很实际用户用DateInput装的是typedate的HTML5控件不同浏览器弹出来的日历长得完全不一样而且审核场景经常要录入老证信息手输日期容易格式错误。拆成三个下拉框虽然土一点但极端稳。第二不动产单元号要做格式校验。按照不动产登记规则单元号是28位数字字母组合分七级。我的校验方式是正则权位校验import re def validate_unit_no(unit_no): 校验不动产单元号格式返回(是否合法, 提示信息) pattern r^\d{6}-\d{3}-\d{3}-\d{2}-\d{3}-\d{3}-\d{2}$ if not re.match(pattern, unit_no): return False, 单元号格式应为28位标准编码 return True, 这里的\d{6}-\d{3}-...是常见的展示格式数据库中存连字符或纯数字都可以但展示格式统一用连字符分段方便后续按区划代码、地籍子区等维度做统计。第三同一不动产单元的重复申请校验。我做了一个查询如果该单元已有状态不为“已登记”的申请记录直接提示“该单元存在未完结业务请先完成审核流程”。这个限制避免了数据脏掉。3.2 审核流程实现状态流转与留痕审核核心视图分两类列表页和操作页。列表页用Django的分页器处理每页20条只查当前状态下的业务# views.py def audit_queue(request): applications RegisterApplication.objects.filter( statussubmitted ).select_related(unit, operator) paginator Paginator(applications, 20) page paginator.get_page(request.GET.get(page)) return render(request, audit/list.html, {page_obj: page})操作页则需要同时记录审核动作的三要素谁审的、什么时候审的、结果是什么。这里我建了一张AuditRecord表来留痕class AuditRecord(models.Model): 审核记录表每次审核动作留一条完整日志 application models.ForeignKey(RegisterApplication, on_deletemodels.CASCADE, related_nameaudit_records) auditor models.ForeignKey(User, on_deletemodels.SET_NULL, nullTrue) action models.CharField(动作, max_length20, choicesACTION_CHOICES) comment models.TextField(审核意见, blankTrue) created_at models.DateTimeField(操作时间, auto_now_addTrue) class Meta: verbose_name 审核记录 verbose_name_plural verbose_name审核通过的操作封装成一个事务状态更新和留痕记录在同一次提交里完成from django.db import transaction def audit_pass(request, application_id): with transaction.atomic(): app RegisterApplication.objects.select_for_update().get(pkapplication_id) if app.status ! submitted: return render(request, error.html, {msg: 业务状态已变化请刷新页面}) app.status final_pass app.save() AuditRecord.objects.create( applicationapp, auditorrequest.user, actionpass, comment审核通过 )加锁查询select_for_update()是我实际项目中吃过大亏后补上的。如果没有锁两个人同时点审核按钮第二个人的查询会读到旧状态更新时覆盖第一个人的结果。加了行级锁后第二个人的查询会等到第一个人提交完然后拿到最新状态走“状态已变化”的提示分支。3.3 数据分析模块ORM聚合统计代替Pandas离线分析这个模块是整个项目我最想展开的部分。数据统计如果交给Pandas就要先把数据导出来再算实时性差而且代码绕。Django ORM自带的聚合功能完全够用三个场景直接上代码。按业务类型统计登记量from django.db.models import Count def type_summary(): 按业务类型统计当前年度登记量 return (RegisterApplication.objects .filter(created_at__yeartimezone.now().year) .values(business_type) .annotate(totalCount(id)) .order_by(-total))按月份统计趋势from django.db.models import Count from django.db.models.functions import TruncMonth def month_trend(): 按月度统计登记趋势 return (RegisterApplication.objects .filter(statusfinal_pass) .annotate(monthTruncMonth(created_at)) .values(month) .annotate(totalCount(id)) .order_by(month))这两个统计放view里拿给模板渲染就行。有一点需要提醒values()放前面annotate()紧跟其后顺序写反会出现莫名其妙的统计粒度错误。我第一次写的时候就把values放到了annotate后面结果每个业务类型都被拆成了单独一行——因为annotate会先产生分组字段values再筛选完全失去了分组效果。这件事花了半小时排查大家引以为戒。图表可视化我推荐Chart.js而不是ECharts。原因很简单Chart.js的CDN引入即用不需要npm构建模板里用canvas标签配合少量JavaScript就能出图和Django模板语法兼容性极好。后端把统计结果转成JSON传给模板即可。def dashboard(request): context { type_labels: [首次登记, 转移登记, 抵押登记], type_data: [120, 356, 89], month_labels: [2025-01, 2025-02, 2025-03], month_data: [45, 52, 61], } return render(request, dashboard.html, context)模板侧只需要把数组渲染成图表canvas idtypeChart width400 height200/canvas script new Chart(document.getElementById(typeChart), { type: bar, data: { labels: {{ type_labels|safe }}, datasets: [{ label: 登记数量, data: {{ type_data|safe }}, backgroundColor: rgba(54, 162, 235, 0.5) }] } }); /script3.4 审核时效分析平均耗时怎么算才准确另一个比较出彩的分析模块是“审核时效”。这套系统里从submitted到final_pass有多道审核Django的DurationField可以直接存时间差。计算平均审核耗时from django.db.models import Avg, F, ExpressionWrapper, DurationField def audit_duration_stats(): 统计平均审核时长小时 records (AuditRecord.objects .filter(actionpass) .select_related(application)) durations [] for r in records: if r.application.created_at and r.created_at: durations.append((r.created_at - r.application.created_at).total_seconds() / 3600) return statistics.mean(durations) if durations else 0这条逻辑看起来简单实际有坑Record.objects.annotate()里直接用F(created_at) - F(application__created_at)做差在MySQL上返回的是秒数而不是DurationField需要做转换。我在本地SQLite和MySQL之间来回切换踩过这个坑最后决定用Python层计算虽然慢一点但逻辑明确不出错。这里也体现了“先考虑正确性再考虑性能”的原则。日常数据量在几千条时Python层算完全撑得住。真要到了十万条再考虑用数据库层的表达式也不迟。4. 手把手搭建复现环境、初始化与演示数据4.1 环境准备Django版本与依赖清单复现这个项目需要准备的环境我来列一个能直接抄作业的清单# 创建虚拟环境Windows/Linux/Mac通用 python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # Linux/Mac: source venv/bin/activate # 安装依赖 pip install django4.2.10 pip install django-import-exportDjango版本我推荐4.2LTS而不是最新的5.x。原因很简单4.2是长期支持版本第三方库兼容性好各类教程也最多。新版Django 5.0虽然有些新特性但本系统的需求完全用不上没必要追新。4.2 项目初始化与配置创建项目和应用django-admin startproject estate_mgmt cd estate_mgmt python manage.py startapp core python manage.py startapp audit python manage.py startapp stats这里我刻意把功能拆成三个app而不是全堆在同一个app里。core管登记业务audit管审核stats管数据分析。Django官方推荐“每个app只做一件事”拆开之后迁移文件、urls配置都清晰很多答辩时也可以拿这个结构说明自己理解模块化。settings.py里重点配好三处INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, core, audit, stats, import_export, # 数据导入导出 ] # 数据库默认SQLite即可后续可切MySQL DATABASES { default: { ENGINE: django.db.backends.sqlite3, NAME: BASE_DIR / db.sqlite3, } } # 时区务必改成本地时区 TIME_ZONE Asia/Shanghai USE_TZ True时区这里必须要多提醒两句如果不改成Asia/Shanghai所有created_at的统计都会差8小时。尤其做“本日办理量”统计时凌晨0点到8点之间的数据会被算到前一天导致报表数字看起来是零但后台明明有记录。踩过这个坑之后我每次新建项目的第一个动作就是改时区。时间字段本身的时区处理逻辑是这样的:Django在USE_TZTrue时会在数据库里存UTC时间读取后按TIME_ZONE设置转换成本地时间。所以统计时__date、TruncMonth都是基于本地时区切分的配置对了一切正常配置错了数据就“穿越”了。不想处理这种心智负担可以直接USE_TZFalse但生产环境不建议这样。4.3 迁移、后台配置与演示数据配置好settings后依次执行:python manage.py makemigrations core audit stats python manage.py migrate python manage.py createsuperuser我特意提供了演示数据脚本直接跑一下就能看到图表上有数据python manage.py generate_demo_data这个脚本是我自己写的一个Django command逻辑就是循环生成100多条登记申请随机分配业务类型、时间、审核状态让前端的图表不至于空空如也。我强烈建议你也做一个类似的脚本——答辩现场最尴尬的事就是你打开数据分析页面屏幕上只有空白图表然后跟老师说“数据还没录入”。有了演示数据点开就能看到柱状图、趋势线、表格演示效果立刻拉满。后台配置方面用django-import-export可以给RegisterApplication加一个“导入”按钮从Excel批量导入登记数据。这个功能在课设汇报中是加分项可以直接现场演示“我导入了500条数据”# core/admin.py from import_export import resources from import_export.admin import ImportExportModelAdmin class RegisterApplicationResource(resources.ModelResource): class Meta: model RegisterApplication fields (id, app_no, unit, applicant, business_type, status) class RegisterApplicationAdmin(ImportExportModelAdmin): resource_class RegisterApplicationResource list_display (app_no, unit, business_type, status, created_at) list_filter (status, business_type)5. 实战排坑常见问题与解决实录5.1 静态文件死活加载不出来这是我见过最多人卡住的问题包括热搜里也在问“vscode写img标签 在django的static文件中显示不了”。原因其实很简单Django的STATICFILES_DIRS没有配置或者DEBUG模式下没有走static的URL路由。正确配置STATIC_URL /static/ STATICFILES_DIRS [ BASE_DIR / static, ]模板里使用{% load static %} img src{% static img/building.png %} alt不动产大厦如果你用的是python manage.py runserverDjango默认会自动处理static文件不需要额外配置。但有一个前提条件settings里必须存在django.contrib.staticfiles这个app默认就有。我见过有人删了这个app来“精简”项目然后static全崩。但如果你部署到了nginx那就必须python manage.py collectstatic把文件收集到一个统一目录再让nginx指向那个目录。本地开发看不到文件时先确认访问路径是/static/xxx而不是/staticfiles/xxx这两个目录名差一个字母报了404排查半小时是常有的事。5.2 ORM聚合统计结果全为0数据分析页面做出来后发现所有统计都是0但数据库里明明有数据。排查思路依次是:先看查询条件有没有过滤掉数据。比如我上面写的.filter(statusfinal_pass)如果测试数据全是draft状态那统计结果当然是0——这不是Bug是查询条件和你预期不一致。再看时区问题。前面说了USE_TZTrue 错误时区会导致__date匹配不上。最后看外键关联。values(business_type)返回的是存储值如first不是展示值如首次登记如果在模板里直接显示出来看到的“first”会觉得数据不对。这是格式映射问题不是数据问题。把这三种情况依次梳理一遍90%的“统计为零”问题都能定位。5.3 点击审核按钮后CSRF验证失败Django默认开启CSRF防护凡是POST请求必须携带csrf_token。很多人写表单时忘了加form methodpost {% csrf_token %} button typesubmit通过审核/button /form如果页面用了AJAX提交需要额外配置// 从cookie中读取csrftoken加入到请求头 function getCookie(name) { let value document.cookie.match((^|;) ? name ([^;]*)(;|$)); return value ? decodeURIComponent(value[2]) : ; } fetch(/audit/pass/, { method: POST, headers: { X-CSRFToken: getCookie(csrftoken), }, body: formData });这个坑我在实际对接中反复踩。最稳妥的办法是统一用Django模板渲染的表单把{% csrf_token %}放在每个form里全程不用AJAX就永远不会触发CSRF问题。课程设计级别的交互用整页刷新完全够用没必要硬上AJAX。5.4 数据库迁移失败表结构改了但migrations报错改models.py之后先makemigrations再看生成的迁移文件没问题再migrate。有两个经验改字段名时先加新字段迁移并运行一次再删旧字段。否则Django会提示“是否删除某个字段”如果你选了ignore可能造成数据残留选了yes则数据直接丢。中间要改字段类型时比如从CharField改成IntegerField数据库迁移可能报数据不兼容。这时候我先做一步RunPython数据转换或者干脆把表删了重新migrate——只限开发阶段。5.5 演示时常见的“假数据不够真实”的问题另外一个加分技巧演示数据不要纯随机生成。纯随机生成的数据趋势完全均匀没有波峰波谷看起来特别假。我写generate_demo_data时特意模拟了真实规律周末办理量低、月初月末办理量高、首次登记和转移登记占比最多。这样生成的图表曲线有几个明显的波峰肉眼看起来非常自然汇报时展示效果的冲击力是完全随机生成比不了的。6. 项目交付源码、文档说明与二次开发扩展6.1 项目结构树与文档说明整个项目最终交付的文件结构如下estate_mgmt/ ├── audit/ # 审核模块 │ ├── views.py │ ├── models.py │ └── urls.py ├── core/ # 登记业务模块 │ ├── models.py │ ├── views.py │ ├── admin.py │ └── management/ │ └── commands/ │ └── generate_demo_data.py ├── stats/ # 数据分析模块 │ ├── views.py │ └── urls.py ├── static/ # 静态资源 ├── templates/ # 模板文件 ├── manage.py ├── requirements.txt └── README.mdREADME里包含了完整的环境搭建、运行步骤、默认账号密码、页面功能截图、数据库表结构说明、主要接口说明、项目二次开发建议——这些文档不仅仅是为了交差更重要的是让接手的人能快速跑起来。我见过太多项目源码没有说明文档别人拿到手连依赖都不清楚硬生生从README开始考古。这里我建议每个项目README至少包含运行环境版本号、安装步骤、初始化命令、默认账号、功能清单。花半小时写这些能省掉未来上百次的反复解释。6.2 可以继续扩展的方向如果你完成这个项目还有余力我建议朝这几个方向扩展增加“不动产证书”的PDF导出功能。审核完成后自动生成带编号的证书文件用reportlab或weasyprint都能实现这个功能很直观、好演示。增加“驳回原因”的统计分析。看看哪些街道、哪个业务类型最容易驳回辅助业务优化这比单纯统计通过率更有分析深度。接入真实的数据大屏。把图表搬到电视大屏上用gridstack做拖拽布局播放实时更新的统计面板。这些方向我都测试过可行性难度不大但对提升项目的完整性和答辩的深度有很大帮助。第二个扩展尤其值得做因为“驳回原因分析”是审核系统里非常实际的需求做出来会让评委觉得你是真的理解业务而不是照着教程敲代码。6.3 最后分享两点实际开发中的体会第一点任何业务系统项目最重要的不是代码多漂亮而是业务闭环是否完整。很多课设项目能做登记就能做审核但审核完就停了。这套系统把数据分析作为最后一环整个链路完整闭合汇报的时候从登记到审核再到分析一路讲下来逻辑非常顺老师自然觉得项目有深度。第二点时间允许的话建议把系统里所有的操作日志都记录下来。谁在什么时间对哪个业务做了什么操作不光是审核留痕登记、修改、作废都要留痕。这个设计在答辩时是绝对的加分项因为大多数同学不会想到这一步——业务系统最容易忽视的就是可追溯性而可追溯性恰恰是企业级系统的基本要求。这个项目本质上就是把“登记—审核—分析”这条业务链路用Django完整跑通。源码和文档都整理好了拿来复现、学习和二次开发都很方便。希望这篇拆解能帮你把系统内部的关键设计看清不只是跑起来更能讲清楚每一步为什么这么做。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。