ARTICLE DETAIL

资讯详情

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

基于Django的家庭财务管理系统开发全流程解析

基于Django的家庭财务管理系统开发全流程解析 简介Python结合Django框架实现的一套家庭财务管理系统毕业设计源码包面向计算机相关专业学生和需要快速搭建Web管理项目的开发者。系统基于PyCharm Django2.2 Python3.6 mysql5.6开发包含家庭成员注册登录、收支登记与查询修改、个人资料维护以及管理员对用户、收支信息、新闻公告和密码的管理实体关系覆盖用户、收入分类、支出类型、支付方式等适合做毕设或课程设计参考。压缩包共2000个文件以js、html、css为主分别承担前端交互、页面模板和样式另有py文件对应Django后端逻辑json、xml、txt、md等提供配置与说明整体仅5.51MB结构便于查看。已有228人学习下载。代码经测试通过答辩评审平均分达到96分。下载后可结合数据库文件和文档说明快速部署并理解Django项目从模型到视图、路由、模板的完整流程也可在此基础上扩展更多功能。1. 家庭财务系统要解决什么Django 选型与项目边界拿到《Python基于Django家庭财务管理系统源代码文档说明数据库.zip》这个标题第一反应不是去解压而是先拆出它背后的三个关键词Python、Django、家庭财务管理。作为一个经常要帮朋友收拾这类课程设计或外包项目的工程我可以负责任地说这类系统真正的难点不在业务复杂度而在数据模型是否经得起时间推敲、查询是否高效、部署是否顺手。家庭财务往往意味着记账人可能连续用上几年账单表里躺着几千甚至上万条流水这个时候如果模型设计偷懒后续每个功能都会被迫打补丁。这套系统最适合谁用一类是正在做数据库课程设计或软件综合实践的学生另一类是想把家里账目数字化但找不到合适离线方案的个人用户。前者的诉求是代码结构清晰、文档齐全、答辩时能讲清楚后者的诉求是操作简单、报表直观、数据别丢。Django 在这两类场景里都够用它有自带的 Admin 后台可以快速做数据维护有 ORM 能屏蔽底层 SQL 差异有模板系统能直接渲染页面而不需要单独写一套前端工程。说白了家庭财务管理系统属于典型的 CRUD 加聚合报表应用Django 的「约定优于配置」能让开发重心落在业务逻辑上而不是反复纠结路由和数据库连接。2. 数据模型先行django.db.models 设计家庭账本核心表2.1 从流水表谈起为什么用「单表 分类」而不是多账本家庭场景的记账粒度要细但账本的维度不能太多。很多人第一次设计时会做出Account、Category、Transaction、Budget四张表逻辑上没毛病但对家庭用户来说引入「账户」概念往往会让录入界面变得复杂——你要求用户先创建「招商银行储蓄卡」再记一笔「买菜」这违背了随手记账的直觉。常见做法是只保留一张Bill流水表再加一张Category分类表账户信息做成一个可选的字符串字段而不是强关联外键。# models.py from django.db import models from django.utils import timezone class Category(models.Model): name models.CharField(max_length32, uniqueTrue, verbose_name分类名) parent models.ForeignKey( self, nullTrue, blankTrue, on_deletemodels.CASCADE, related_namechildren, verbose_name父分类, ) kind models.CharField( max_length8, choices((expense, 支出), (income, 收入)), defaultexpense, verbose_name收支类型, ) sort_order models.IntegerField(default0, verbose_name排序) class Meta: ordering [kind, sort_order, id] class Bill(models.Model): category models.ForeignKey( Category, on_deletemodels.PROTECT, related_namebills, verbose_name分类, ) amount models.DecimalField(max_digits10, decimal_places2, verbose_name金额) account models.CharField(max_length32, blankTrue, verbose_name账户) note models.CharField(max_length128, blankTrue, verbose_name备注) occurred_at models.DateTimeField(defaulttimezone.now, db_indexTrue, verbose_name发生时间) created_at models.DateTimeField(auto_now_addTrue, verbose_name创建时间)这段代码的关键在于parent自关联实现了二级分类。比如「餐饮」下面可以挂「早餐」「午餐」「晚餐」而Bill始终挂在叶子分类上统计时既能按大类汇总也能下钻到子类。on_deletemodels.PROTECT是刻意选择某个分类下如果已有账单就不允许直接删除分类避免造成历史流水丢失分类信息。occurred_at加了db_indexTrue因为报表页几乎永远按时间范围过滤账单这张索引是收益最高的索引。2.2 分类树与关联字段上面两张表把家庭账本最小闭环立住了但如果只做到这一步报表页会遇到一个尴尬问题用户想看「这个月吃饭花了多少」按category__parent__name餐饮去过滤虽然能查可一旦分类层级扩展到三层这条链路的查询会更慢SQL 也更难维护。我见过的成熟方案是直接在Bill上冗余一个top_category外键指向顶层分类。class Bill(models.Model): # 假设上面字段不变 top_category models.ForeignKey( Category, on_deletemodels.PROTECT, related_nametop_bills, verbose_name顶层分类, nullTrue, blankTrue, ) def save(self, *args, **kwargs): if self.category and self.category.parent_id: self.top_category self.category.parent else: self.top_category self.category super().save(*args, **kwargs)这个save覆写让顶层分类在全库范围内保持一致业务代码写报表时直接用top_category过滤不需要层层 JOIN。代价是当你修改分类层级关系时需要跑一次数据订正脚本把所有Bill的top_category重新刷一遍。对家庭财务这种写入频率远低于查询频率的场景这种冗余完全值得。2.3 表结构落地的两个关键参数执行python manage.py makemigrations python manage.py migrate之前还要想清楚两个参数DecimalField的max_digits10表示整数部分最多 8 位家庭流水一年撑死几十万这个量级足够decimal_places2严格限定小数点后两位能避开浮点误差。不要用FloatField存金额Django Admin 里看起来一样但12.3 0.2这类运算在某些场景下会给出 12.499999聚合报表会莫名多出一分钱。另一个参数是Category.name的uniqueTrue。这样做的好处是导入初始数据时可以放心用get_or_create坏处是「工资」不能同时存在于收入和支出分类下。解决办法是unique_together (name, kind)把唯一性约束放到复合键上同样一行索引成本。3. 视图层与查询优化让账单接口撑住五年流水3.1 用类视图压缩代码量数据模型定下来后接下来是视图层。Django 有函数视图和类视图两种写法家庭财务系统里我更倾向于类视图因为列表、详情、创建这三类页面高度相似ListView、CreateView能省掉大量样板代码而且写答辩文档时结构更好讲。# views.py from django.views.generic import ListView, CreateView from django.urls import reverse_lazy from .models import Bill from .forms import BillForm class BillListView(ListView): model Bill template_name finance/bill_list.html context_object_name bills paginate_by 20 ordering [-occurred_at] def get_queryset(self): qs super().get_queryset().select_related(category) month self.request.GET.get(month) if month: qs qs.filter(occurred_at__startswithmonth) return qs class BillCreateView(CreateView): model Bill form_class BillForm template_name finance/bill_form.html success_url reverse_lazy(bill_list)select_related(category)是这里最值钱的一行。BillListView每页显示 20 条流水如果没有这个调用模板里每次访问bill.category.name都会触发一次额外的 SQL 查询页面总查询数是 1 20 21 次加上后无论如何都只有 1 次 JOIN 查询。分页参数paginate_by20把单页数据量控制住避免一次性渲染几千行 DOM。3.2 按月份聚合的查询写法列表页只是入口财务报表页才是整个系统最有技术含量的部分。按月份统计收支要用到TruncMonth和Sum# views.py from django.db.models.functions import TruncMonth from django.db.models import Sum, Count def month_summary(request): rows ( Bill.objects .annotate(monthTruncMonth(occurred_at)) .values(month, top_category_id, top_category__name) .annotate( totalSum(amount), cntCount(id), ) .order_by(-month) ) return render(request, finance/month_summary.html, {rows: rows})TruncMonth是 PostgreSQL 之外的数据库也通用的函数它会把2025-03-14 08:30:00归一化为2025-03-01 00:00:00然后用values配合annotate做分组聚合。注意values(month, top_category_id, top_category__name)这三列的排列顺序决定了 GROUP BY 的粒度——你必须在values里同时放聚合键和展示字段Django 不允许在values分组后直接用top_category__name而不把它加入分组。初学时在这里踩坑的比例非常高。3.3 常用查询条件参考表查询需求ORM 写法说明本月支出总额Bill.objects.filter(occurred_at__monthmm, category__kindexpense).aggregate(tSum(amount))__month只能在 SQLite/MySQL 部分可用跨库建议用TruncMonth最近 7 天每天支出Bill.objects.filter(occurred_at__gteseven_days_ago).values(occurred_at__date).annotate(tSum(amount))结果用occurred_at__date做 key金额最大的前 5 笔Bill.objects.order_by(-amount)[:5]切片操作生成 LIMIT删除某分类下所有账单Bill.objects.filter(category__parentcat).delete()慎用会真正走 DELETE搜索备注包含「超市」Bill.objects.filter(note__icontains超市)icontains不区分大小写等价于 LIKE这张表里的第 5 行要单独提醒filter(...).delete()返回的是一个元组(总删除数, 明细字典)如果Bill上有其他表通过外键引用它且on_delete设为CASCADE这个删除会连带清掉关联数据。执行前先跑一遍count()确认数量。4. 前端页面与交互模板继承 Chart.js 做图表4.1 模板继承的最小骨架Django 的模板渲染是在服务端完成的这个特性对家庭财务系统非常友好——你不需要搭建 Node 环境也不用配置跨域代理一个views.py加几个 HTML 文件就能跑。项目里常见布局是三个模板base.html放公共导航与样式bill_list.html继承它并实现列表与筛选dashboard.html放图表。!-- templates/base.html -- {% load static %} !doctype html html langzh-CN head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 title{% block title %}家庭财务管理系统{% endblock %}/title link href{% static css/bootstrap.min.css %} relstylesheet /head body nav classnavbar navbar-expand-lg navbar-light bg-light a classnavbar-brand href{% url dashboard %}家庭财务/a a classnav-link href{% url bill_list %}账单明细/a a classnav-link href{% url dashboard %}报表/a /nav main classcontainer mt-3 {% block content %}{% endblock %} /main {% block extra_js %}{% endblock %} /body /html模板继承的关键是{% block %}与{% extends %}的配合。子模板只需要覆写title、content、extra_js三处其余结构完全复用。url模板标签用视图的name解析路径这样 URL 配置改动后不会在页面里留下死链。4.2 折线图与分类饼图的 JSON 接口表格展示账单列表只需要普通 HTML 模板循环但图表需要 JSON 数据接口。这里有一个容易误用的点别直接JsonResponse(rows)因为Bill对象无法被 JSON 序列化。正确姿势是在视图里把数据转成纯 Python 结构# views.py import json from django.http import JsonResponse from django.db.models import Sum from django.db.models.functions import TruncMonth def chart_data(request): qs ( Bill.objects .annotate(monthTruncMonth(occurred_at)) .values(month) .annotate( expenseSum(amount, filtermodels.Q(category__kindexpense)), incomeSum(amount, filtermodels.Q(category__kindincome)), ) .order_by(month) ) payload { months: [row[month].strftime(%Y-%m) if row[month] else 未知 for row in qs], expense: [float(row[expense] or 0) for row in qs], income: [float(row[income] or 0) for row in qs], } return JsonResponse(payload)这里用到了带有filter参数的Sum它能在一次查询里同时算出支出和收入两列避免跑两次数据库。float(row[expense] or 0)中的or 0处理了SUM对空集合返回 NULL 的情况——如果不转成 0前端拿到 null 会导致 Chart.js 直接断线。前端端模板里用fetch加载这个接口再渲染图表是一个很稳妥的实现方式。4.3 常用 Django 模板过滤器Bill列表页需要展示日期和金额模板过滤器会让它更顺手{{ bill.occurred_at|date:Y-m-d H:i }}把时间格式化为「2025-03-14 12:30」比默认带秒的格式更紧凑。{{ bill.amount|floatformat:2 }}强制显示两位小数避免Decimal类型在模板里被渲染成123.5。{{ query|default_if_none: }}在分页翻页时保留搜索词时很常用配合request.GET拼 URL。{{ bill.note|truncatechars:20 }}截断长备注避免列表行被撑破。过滤器本质上是可以嵌套的函数date和floatformat都是在模板渲染时执行不会修改数据库里的原始值。5. 从开发到部署迁移 MySQL、Gunicorn 与常见坑5.1 从 SQLite 迁移到 MySQL 的注意事项家庭财务系统解压后大概率默认连的是 SQLite这个配置在开发期没问题但放到云服务器上长期跑时建议换 MySQL并发写入和数据备份策略都会更可靠。改配置时核心是正确安装驱动并注意DecimalField的兼容性pip install mysqlclientmysqlclient相比pymysql更省心因为它是 C 扩展编译的性能更好且 Django 官方文档对它支持最完整。安装前需要在系统里装libmysqlclient-devUbuntu或同等的开发包否则会编译报错。然后在settings.py里修改数据库配置DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: family_finance, USER: app_user, PASSWORD: your_strong_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, init_command: SET sql_modeSTRICT_TRANS_TABLES, }, } }charset必须用utf8mb4否则 emoji 或者生僻字写入会报Incorrect string value。init_command里的sql_mode设置能保证金额字段严格按两位小数存储不会出现静默的舍入误差。迁移完成后要重点验证历史数据用SELECT COUNT(*)对比两边行数再抽查几笔金额总和确认没丢数据。5.2 用 systemd 管理 Django 服务开发阶段用python manage.py runserver 0.0.0.0:8000就够但生产环境绝不能这么跑——runserver是单进程且无 worker 模型一个慢请求会阻塞所有连接。我会用 Gunicorn 配合 Nginx 组成三层结构Nginx 处理静态文件和反向代理Gunicorn 跑 Django 应用。gunicorn family_finance.wsgi:application \ --bind 127.0.0.1:8000 \ --workers 3 \ --timeout 60 \ --access-logfile logs/access.log \ --error-logfile logs/error.log--workers 3在 1 核 2G 的云主机上是合理值太多 worker 反而会因为切换上下文耗尽 CPU。--timeout 60是给报表页留的宽限家庭场景下账单数不大一般聚合查询几十毫秒完成真正耗时的是 Chart.js 首次加载那一下网络请求。之后写一个.service文件挂到systemd就能实现开机自启和崩溃自动拉起。5.3 安全与性能两不误的配置Django 项目部署前要改三个配置缺一个都不算完整DEBUG False。这是告别runserver后最容易被忽略的项开着它访问不存在的路由会让 Django 在浏览器里输出完整堆栈包括文件路径和部分配置信息。ALLOWED_HOSTS [fin.yourdomain.com, 127.0.0.1]。不加这一项Django 会拒绝所有非localhost的请求Nginx 配置对了也会看到 400。STATIC_ROOT BASE_DIR / staticfiles然后执行python manage.py collectstatic。这一步把 Admin 和自带静态资源全部集中到一个目录中交给 Nginx 托管。5.4 验证账本数据与日志监控最后留一个日常使用的验证方法在 Django shell 里写一行查询把当月总支出打印出来然后与网页报表页对照。如果对得上说明迁移、聚合和模板渲染三层都没问题对不上就逐层查。我还习惯把django.db.backends的日志等级调到DEBUG观察某条报表请求实际执行的 SQL 条数——列表页超过 30 条 SQL 是明显不对劲的信号多半是select_related漏加了。整个流程走下来这台「家庭财务管理系统」就从课程设计变成了能长期维护的可用系统后续加预算提醒或月度对比表也只是再走一遍「模型加字段、视图加聚合、模板加卡片」的循环而已。本文还有配套的精品资源点击获取
返回列表