尧图精选

基于Python+Django的幼儿园管理系统开发实践

🕒 发布时间:2026/9/16 5:23:16 📁 来源:尧图网络
做过Web管理系统开发的同学应该都有同感需求本身不难但需求背后那堆琐碎的东西——权限区分、数据关联、表单校验、增删改查、后台排版——才是真正磨人的。我最近正好帮朋友校区做了一套基于Python和Django的幼儿园管理系统从需求梳理、模型设计、后台改造到跑通前后端联调完完整整走了一遍。这套系统覆盖了幼儿档案、班级分配、教师任课、考勤记录、收费登记、公告发布这些最常见的园务场景技术栈选的是Python 3 Django 4 MySQL Bootstrap后台管理界面用SimpleUI做了美化。如果你正好在找毕业设计题目或者想用一个真实项目把Django的MTV架构、ORM关联查询、Admin定制这些知识点串起来那这篇文章应该能给你省下不少时间。下面我就把整个开发思路、数据库设计、核心功能实现和排查过的坑一条一条拆开讲。1. 项目整体设计与思路拆解1.1 幼儿园管理系统的需求来源很多人以为幼儿园管理系统就是“学生花名册收费记录”实际做下来完全不是这样。真正的园务管理牵扯到的人事关系比想象中复杂园长要看到全园数据老师只管自己班的孩子家长只能看到自家孩子的动态财务要记录各类缴费项目保健医要录入晨检结果——同一个系统里至少有三到四种完全不同的信息视角。我在做需求梳理时把核心业务分成了几条线幼儿信息主线入园登记、分班调班、离园退园、健康档案、接送人信息。教务管理主线班级设置、教师任课安排、每日考勤、教学计划与周计划。财务收费主线保教费、餐费、延时服务费等项目的应收、实收、欠费统计。家园互动主线公告通知、班级动态、孩子每日在园表现反馈。这几条线单独看都不难但合在一起就要求系统有一个足够清晰的模型设计。比如一个幼儿可能从“小班”升到“中班”再升到“大班”那幼儿表里直接存一个“班级ID”字段就会有历史追溯问题再比如一次缴费可能同时包含“保教费餐费”那缴费记录就不能只存一个金额必须拆成“缴费单”和“缴费明细”两级结构。1.2 为什么选Django而不是Flask或Spring Boot这个项目选Django不是为了炫技而是因为它在这一类管理系统上确实有不可替代的效率优势。Django最核心的卖点就是“自带全家桶”ORM、模板引擎、表单处理、认证授权、Admin后台全都有。这就意味着你不需要自己写数据库连接、不需要手动处理Session、不需要另搭一套权限框架直接基于自带能力就能把业务逻辑跑起来。比如认证这块Django内置的django.contrib.auth提供了完整的用户模型和登录状态管理我要做的只是扩展一个Profile表把User和Teacher、Parent关联起来。对比一下Flask虽然灵活但用户认证、表单CSRF防护、ORM这些都需要自己集成第三方库开发速度慢不少。对比Spring BootJava体系的配置成本和学习曲线又太高。对于“中小型企业内部管理系统”这种形态Django的“约定优于配置”策略非常对路。这里有个提效的关键认知选框架的时候不要老想着“哪个能覆盖更多场景”而是要想“哪个能让我最快把当前场景跑通”。1.3 技术方案选型后端框架Django 4.2 LTS长期支持版本社区资料多踩坑好搜。前端方案服务端渲染为主Bootstrap 5做布局和组件少量jQuery处理异步请求。数据库MySQL 8.0通过django-mysql扩展支持一些特殊的字段选项本地开发也兼容SQLite。Admin美化django-simpleui把默认的Admin界面从“部门内部工具”变成“看得过眼的内部系统”。数据导出django-import-export解决Excel导入导出需求。图片存储本地Media目录后续可以平滑迁移到OSS。这里特别说一下为什么没有做前后端分离。幼儿园管理系统是典型的后台管理系统使用人数少、页面数量多、交互复杂度中等。如果硬上VueDRF等于把一个“一个月能交付的项目”拉长成“两个月的项目”还要额外处理跨域、Token刷新、权限同步这些问题。Django的模板系统配合Ajax局部刷新完全覆盖现在的需求场景。2. 核心功能模块与数据库设计2.1 模型设计核心原则数据库设计是这类系统最值得花时间的部分。我这次总结下来核心原则可以归纳成三句话一条数据只在一个地方存储其它地方全部通过外键引用。状态变化要留历史不能直接覆盖旧数据。查询频率高的聚合数据允许冗余但一定要通过代码保证同步。拿班级调整来举例。小班升中班之后幼儿的班级归属变了但是“这个孩子曾经在小二班待过”这个事实不能丢。所以我不建议在Child表上直接存当前班级ID就完事而是在ChildClassHistory表里存一条调整记录。好在这个系统对“历史追溯”的要求没有学校学籍系统那么严格实际开发中我采用的方案是“Child表存当前班级ID ChildClassHistory表存调整流水”两边配合。2.2 数据表结构拆解下面是这个系统里最核心的几张表的结构设计User用户表字段类型说明idAutoField主键usernameCharField(150)登录名passwordCharField(128)密码哈希user_typeCharField(20)teacher / parent / adminphoneCharField(11)手机号is_activeBooleanField是否启用Django的User表本身字段已经够用我额外加了一个user_type区分身份再加一个Profile相关的OneToOne表存各自角色专属信息。Classroom班级表字段类型说明idAutoField主键nameCharField(50)班级名称如“小一班”gradeCharField(20)年级托班/小班/中班/大班head_teacherFK-Teacher班主任max_studentsIntegerField最大人数room_locationCharField(100)教室位置school_yearCharField(20)学年如2024-2025Child幼儿表字段类型说明idAutoField主键nameCharField(50)姓名genderCharField(10)性别birth_dateDateField出生日期enrollment_dateDateField入园日期classroomFK-Classroom当前班级student_noCharField(20)学籍号blood_typeCharField(10)血型allergy_infoTextField过敏史emergency_contactCharField(50)紧急联系人emergency_phoneCharField(20)紧急联系电话statusCharField(20)在读/休学/退园Teacher教师表字段类型说明idAutoField主键userOneToOne-User关联用户nameCharField(50)姓名titleCharField(50)职称hire_dateDateField入职日期phoneCharField(20)联系电话educationCharField(50)学历Attendance考勤表字段类型说明idAutoField主键childFK-Child幼儿dateDateField日期morning_statusCharField(20)上午出勤正常/迟到/缺勤/请假afternoon_statusCharField(20)下午出勤remarkTextField备注checkin_timeDateTimeField入园打卡时间checkout_timeDateTimeField离园时间注意考勤表有个unique_together约束确保同一幼儿同一天只有一条考勤记录。PaymentOrder缴费单表字段类型说明idAutoField主键childFK-Child幼儿order_noCharField(30)单号periodCharField(20)费用所属期如2025-03total_amountDecimalField(10,2)总金额paid_amountDecimalField(10,2)已缴金额statusCharField(20)未缴/部分缴费/已结清create_timeDateTimeField创建时间PaymentItem缴费明细表字段类型说明idAutoField主键orderFK-PaymentOrder所属缴费单item_nameCharField(50)费用项目amountDecimalField(10,2)金额remarkCharField(200)备注之所以把缴费单和缴费明细拆开是因为一次收费可能包含多个项目财务月底要对账时按缴费单看总额按缴费明细看分类汇总两边都查得动。2.3 外键关联与查询设计关系梳理一个User对应一个Teacher或ParentOneToOne。一个Classroom有多个Child一个Teacher可以带一个主班也可以给别的班代课ManyToMany。一个Child有多条Attendance记录OneToMany。一个Child可以有多条缴费记录OneToMany。Django ORM里最常用到的查询场景是这种# 某个班今天缺勤的孩子 from django.utils import timezone from .models import Attendance, Child today timezone.localdate() absent_children Child.objects.filter( classroom_id1, attendance__datetoday, attendance__morning_status缺勤 ) # 查询某个孩子最近三个月的缴费记录 order_list child.paymentorder_set.filter( create_time__gtestart_date ).prefetch_related(paymentitem_set)第二个查询里用了prefetch_related这是一个很关键的优化如果不加ORM会为每一条订单额外发一次SQL去查明细产生N1查询问题。加了之后Django会一次性查出所有订单及其明细再在内存里做关联。数据量大了以后这个优化能明显降低数据库压力。3. 实操过程与核心环节实现3.1 环境准备与项目初始化开发环境我建议直接用虚拟环境避免系统Python环境被搞乱。# 创建虚拟环境 python -m venv venv # 激活Windows venv\Scripts\activate # 激活macOS/Linux source venv/bin/activate # 安装依赖 pip install django4.2.* mysqlclient django-simpleui django-import-export项目初始化命令django-admin startproject kindergarten_project cd kindergarten_project python manage.py startapp system这里有个小建议app的命名不要叫app或main尽量用能表达业务含义的名字比如system、attendance、finance拆得越细后面维护越舒服。但这个项目的规模属于“用一个大app也扛得住”的级别所以我直接建了一个systemapp承载所有模型。创建好之后记得在settings.py里注册app并配置数据库INSTALLED_APPS [ simpleui, # 注意simpleui要放在admin前面 django.contrib.admin, django.contrib.auth, ... system, ] DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: kindergarten_db, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, } } }charset一定要写成utf8mb4否则存emoji或者生僻字会报错。这个坑我踩过后面细讲。3.2 模型定义与数据库迁移模型定义是整个系统的地基。我在2.2节列了表结构这里给出实际代码中的一部分方便直接参考。from django.db import models from django.contrib.auth.models import User class Classroom(models.Model): GRADE_CHOICES [ (nursery, 托班), (junior, 小班), (middle, 中班), (senior, 大班), ] name models.CharField(班级名称, max_length50) grade models.CharField(年级, max_length20, choicesGRADE_CHOICES) head_teacher models.ForeignKey( Teacher, on_deletemodels.SET_NULL, nullTrue, blankTrue, related_nameleading_classroom, verbose_name班主任 ) max_students models.PositiveIntegerField(最大人数, default30) room_location models.CharField(教室位置, max_length100, blankTrue) school_year models.CharField(学年, max_length20, default2025-2026) created_time models.DateTimeField(创建时间, auto_now_addTrue) class Meta: verbose_name 班级 verbose_name_plural verbose_name def __str__(self): return f{self.school_year} {self.name} class Child(models.Model): GENDER_CHOICES [(M, 男), (F, 女)] STATUS_CHOICES [ (studying, 在读), (suspended, 休学), (graduated, 毕业), (transferred, 转出), ] name models.CharField(姓名, max_length50) gender models.CharField(性别, max_length2, choicesGENDER_CHOICES) birth_date models.DateField(出生日期) enrollment_date models.DateField(入园日期) classroom models.ForeignKey( Classroom, on_deletemodels.PROTECT, related_namechildren, verbose_name当前班级 ) student_no models.CharField(学籍号, max_length20, uniqueTrue) blood_type models.CharField(血型, max_length10, blankTrue) allergy_info models.TextField(过敏史, blankTrue) emergency_contact models.CharField(紧急联系人, max_length50) emergency_phone models.CharField(紧急联系电话, max_length20) status models.CharField(状态, max_length20, choicesSTATUS_CHOICES, defaultstudying) class Meta: verbose_name 幼儿 verbose_name_plural verbose_name def __str__(self): return f{self.name}({self.student_no})写模型的时候有几个关键点必须注意on_delete参数要明确Django 2.0之后必填。PROTECT表示有子记录存在时禁止删除父记录这个用于班级和幼儿的关系防止误删。SET_NULL适合教师离职的情况班级的班主任可以置空。related_name一定要写否则反查时默认是child_set这种名字语义不清晰。比如children这个related_name可以直接用classroom.children.all()取全班孩子。uniqueTrue的字段要确认业务上真要唯一。学籍号可以是唯一的但紧急联系电话绝对不能唯一一个家长可能有两个孩子都在这个幼儿园。定义好模型后跑迁移python manage.py makemigrations system python manage.py migrate python manage.py createsuperuser3.3 Admin后台配置与美化这个系统的后台用了Django自带的Admin但默认界面实在太“原生”了。我加了django-simpleui界面风格变成类似LayUI后台的样子左侧菜单、顶部标签、主题色都可以配置。SimpleUI接入非常简单把它加到INSTALLED_APPS顶部再从settings里做个性化配置SIMPLEUI_CONFIG { system_keep: False, menu: [ { name: 园务管理, icon: fa fa-home, models: [ {name: 班级管理, icon: fa fa-users, url: /admin/system/classroom/}, {name: 幼儿管理, icon: fa fa-child, url: /admin/system/child/}, {name: 教师管理, icon: fa fa-user, url: /admin/system/teacher/}, ] }, { name: 教务管理, icon: fa fa-calendar, models: [ {name: 考勤记录, icon: fa fa-check, url: /admin/system/attendance/}, {name: 缴费管理, icon: fa fa-money, url: /admin/system/paymentorder/}, ] }, ] }菜单配置里的URL要拿准Django Admin的URL规律是/admin/{app_label}/{model_name}/写错了点击菜单会404。Admin类也要自定义一下让列表页直接展示关键字段可以提高日常操作效率from django.contrib import admin from .models import Child, Classroom, Teacher, Attendance, PaymentOrder, PaymentItem admin.register(Child) class ChildAdmin(admin.ModelAdmin): list_display (name, gender, classroom, student_no, status, enrollment_date) list_filter (status, classroom__grade) search_fields (name, student_no) autocomplete_fields (classroom,) list_per_page 20 # 列表页直接可以快速编辑的字段 list_editable (status,)有几个细节autocomplete_fields需要目标模型在Admin里注册search_fields否则报错。list_editable和list_display里的字段有重叠时那些字段必须放在第一位通常把主键或只读字段放前面。list_filter支持外键跨表过滤写法是classroom__grade两个下划线在Django里是跨关系的惯例。3.4 前端页面与登录流程系统给不同身份的使用者提供了不同侧重点的前端页面。游客和未登录用户会被重定向到登录页登录成功后根据user_type跳转到不同的首页。登录视图直接用Django自带认证from django.contrib.auth import authenticate, login from django.shortcuts import render, redirect from django.contrib.auth.decorators import login_required from .models import Teacher, Child, Parent def login_view(request): if request.method POST: username request.POST.get(username) password request.POST.get(password) user authenticate(request, usernameusername, passwordpassword) if user is not None: login(request, user) return redirect(get_redirect_url_by_user(user)) return render(request, login.html, {error: 用户名或密码错误}) return render(request, login.html) def get_redirect_url_by_user(user): # 根据user_type跳转这个方法省略具体判断 return /staff/dashboard/ if user.user_type teacher else /parent/home/登录页面用Bootstrap 5做了个居中卡片布局加上简单的前端校验整体看起来像一个正经的登录页而不是Django默认的那个丑丑的Admin登录页。首页展示的内容按角色区分园长/管理员全园幼儿总数、各班级人数统计、本月出勤率、本月收费完成率、最近公告。教师自己班的幼儿人数、今日考勤统计、待处理请假申请。家长自己孩子的今日考勤状态、最近缴费通知、班级动态。这里用到了Aggregation聚合查询from django.db.models import Count from .models import Child, Classroom classroom_stats Classroom.objects.annotate( child_countCount(children) ).values(name, child_count)annotate配合Count能直接把每个班的人数算出来不用在Python里循环再查库。3.5 报表统计与数据导出管理系统做久了会发现真正让管理人员每天打开系统看的不是那些录入表单而是统计报表。我这个系统里做了一个简单的月度出勤统计思路是先按天查出勤率再按月汇总。from django.db.models import Q, Count from datetime import date from .models import Attendance def attendance_rate_for_month(year, month, classroom_id): start date(year, month, 1) if month 12: end date(year 1, 1, 1) else: end date(year, month 1, 1) records Attendance.objects.filter( child__classroom_idclassroom_id, date__gtestart, date__ltend, ) total records.count() normal records.filter( Q(morning_status正常) Q(afternoon_status正常) ).count() return { total: total, normal: normal, rate: round(normal / total * 100, 2) if total else 0, }导出功能直接用django-import-export在Admin里加一个ImportExportModelAdmin就行from import_export import resources from import_export.admin import ImportExportModelAdmin class ChildResource(resources.ModelResource): class Meta: model Child fields (id, name, gender, birth_date, classroom__name, student_no) admin.register(Child) class ChildAdmin(ImportExportModelAdmin): resource_class ChildResource ...这样在Admin列表页就能直接点击“导入”“导出”格式支持Excel和CSV非常实用。4. 常见问题与排查技巧实录4.1 mysqlclient安装失败在Windows上用pip install mysqlclient经常报错提示需要Microsoft Visual C Build Tools。网上很多教程会让你去装一堆东西其实有个更省事的办法直接装pymysql然后在项目__init__.py里加两行代码# 项目目录下的 __init__.py import pymysql pymysql.install_as_MySQLdb()这样Django会把pymysql当成MySQLdb来用模型操作不受影响。我自己生产环境跑了几个月没发现什么兼容性问题。不过要注意pymysql对Django 4.2的支持没有问题但如果你用Django 5以上版本还是要确认一下。注意如果选MySQL 8.0默认的caching_sha2_password认证插件在旧版本pymysql下会报错。建议把pymysql升到最新版或者把MySQL用户改成mysql_native_password认证方式。4.2 静态文件404新手做Django最容易遇到的问题就是CSS和JS加载不出来。开发阶段出现这个问题90%是没配STATIC_URL或者模板里忘了加{% load static %}。# settings.py STATIC_URL /static/ STATICFILES_DIRS [ BASE_DIR / static, ] MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / media模板里引用静态资源的正确姿势{% load static %} link relstylesheet href{% static css/login.css %}这里要特别提醒一个坑上线部署后如果DEBUG FalseDjango默认不再处理静态文件这时候需要在urls.py里显式加上from django.conf import settings from django.conf.urls.static import static urlpatterns [ path(admin/, admin.site.urls), path(, include(system.urls)), ] if settings.DEBUG: urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)实际生产环境一般把静态文件交给Nginx开发阶段用上面这段就能跑通。4.3 时区导致的数据错乱Django的USE_TZ True开启后数据库里存的时间是UTC时间读取时再转成当前时区。如果你不处理按日期范围查询考勤表很可能会发现“今天”的数据查不出来因为“今天”在UTC里可能是“昨天”或者“明天”。解决方案很简单涉及日期查询时统一用timezone.localdate()而不是date.today()from django.utils import timezone today timezone.localdate() # 这个返回本地时区对应日期DateTimeField的auto_now_add存的是UTC时间模板里显示的时候如果想按本地时区格式化用Django模板的localtime过滤器{{ record.checkin_time|localtime|date:Y-m-d H:i }}4.4 QuerySet的惰性求值与N1问题Django的QuerySet是惰性的这一点新手容易踩坑。循环里访问外键时会触发额外SQL查询造成N1问题。比如# 不好的写法每个孩子都会触发一次班级查询 for child in Child.objects.all(): print(child.classroom.name)每个child.classroom都是一个SQL查询如果有100个孩子就要执行101条SQL。改成select_related或prefetch_related后一条SQL或者两条SQL就能搞定# 好的写法一次连表查询 for child in Child.objects.select_related(classroom).all(): print(child.classroom.name)select_related适用于外键和一对一prefetch_related适用于多对多和反向外键。这个优化在数据量只有几十条时感觉不明显但到上千条数据时数据库连接压力差别很大。4.5list_editable报错问题Admin列表页想直接修改字段结果一保存就报错“Please correct the error below.”。这种情况通常是因为list_editable里的字段同时在list_display里但被放到了第一个位置。Django规定表格第一列是链接列它不能是可编辑字段所以要把一个只读字段放到第一位list_display (id, name, status, classroom) list_editable (status, classroom)4.6 Admin美化后菜单不生效配置了SIMPLEUI_CONFIG里的菜单但刷新页面发现还是显示原来的app列表。原因是SimpleUI的菜单配置有缓存改完配置后要清一下浏览器缓存或者在settings加上SIMPLEUI_CACHE False开发阶段把这个设为False最省心改配置立即生效上线时再改回True。5. 部署上线时必须要做的事很多人在本地跑得飞起一上服务器就各种404、500。这里分享几个上线前的必要操作。5.1 关闭DEBUG并配置ALLOWED_HOSTSDEBUG False ALLOWED_HOSTS [127.0.0.1, your-domain.com]如果不配置ALLOWED_HOSTSDjango会直接抛DisallowedHost异常页面就访问不了。用通配符*虽然可以临时解决但不建议存在安全隐患。5.2 收集静态文件python manage.py collectstatic这个命令会把所有app里的静态文件复制到STATIC_ROOT指定的目录Nginx只需要指向这个目录就行。5.3 使用Gunicorn托管生产环境不要用runserver它自带的服务能力太弱并发一高就卡死。常见组合是Gunicorn Nginxpip install gunicorn gunicorn kindergarten_project.wsgi:application --bind 0.0.0.0:8000 --workers 3workers不是越多越好一般按CPU核心数的2倍加1配置就够了。如果跑在Docker内部--workers设为1或2更稳因为容器本身也做了进程隔离。5.4 数据库备份策略管理系统最怕数据丢失。我建议在服务器上写一个简单的cron任务每天凌晨导出数据库并保留最近7天的备份# backup_db.sh #!/bin/bash DATE$(date %Y%m%d) /usr/bin/mysqldump -uroot -p你的密码 kindergarten_db /backup/kindergarten_$DATE.sql find /backup -name kindergarten_*.sql -mtime 7 -delete注意不要在命令行直接暴露数据库密码实际生产可以把密码写到~/.my.cnf里更安全。6. 给后来者的一些建议整套系统从零到能跑到上线前后花了一个多月时间大部分时间都花在需求整理和联调测试上真正写代码的时间其实不多。这也是我推荐用Django做这类项目的原因框架把大量繁琐的底层工作做掉了你可以把精力集中在业务逻辑上。如果你决定用这个项目做毕业设计或者练手项目有这么几件事值得提前做先把模型图画出来。哪怕只是纸笔手绘也要把表之间的关系理清楚再动代码。模型改起来牵一发动全身后面改字段、加外键、改迁移文件会非常痛苦。做好基础数据填充。系统开发完一定要准备一份真实的模拟数据至少包含20个孩子、3个班级、几周的考勤记录和缴费记录。没有数据的空系统根本看不出问题很多查询性能问题都是在数据量上来后才暴露的。重视权限控制。Django自带的User和Permission系统一定要充分利用不要自己再建一张表存用户。幼儿园管理系统里家长只能看自己孩子信息、老师只能看自己班级信息这个权限边界如果用纯业务代码去实现后患无穷。别迷信“前后端分离”。如果你的目标是快速交付一个能用的内部管理系统服务端渲染方案就足够了。现在很多教程都在推VueDRF但那种方案对单页应用的体验提升在后台管理系统里根本体现不出来反而增加开发和维护成本。最后再分享一个小技巧Django的manage.py shell是一个非常好用的调试工具可以随时进入交互式环境操作模型测试ORM查询结果不用频繁重启开发服务器。我开发这个项目时有一半的ORM查询逻辑都是在shell里先验证过再写进视图的效率提升非常明显。这套幼儿园管理系统做下来最大的体会是所谓“管理系统”本质上就是把纸质的、靠人脑记忆的、散落在Excel里的信息变成有结构、有关系、能统计、能追溯的数据。Django给了你一套很趁手的工具但真正的核心永远是你对业务关系的理解和建模能力。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →