从Flask到Django:高校学术报告管理系统开发实战
发布时间:2026/9/30 15:40:17来源:尧图网络
如果说要给高校开发一个学术交流报告管理系统第一反应往往是“就是个CRUD”。但真把需求拉出来你会发现它远没有想象中简单学术秘书要发报告公告、审批报告教师要提交报告材料学生要报名、签到、提问管理员最后还要统计到会和交流情况。我自己用Python做过这么一套系统前期先用Flask写可交互原型后期用Django重构成了正式后台整个过程走了不少弯路也沉淀了一些代码级和流程级的经验。这篇文章就把选型逻辑、数据模型、核心代码和部署排查完整拆开讲希望正在做类似学院内部系统的朋友能少踩几个坑。内容适合刚学完Django想要完整项目练手的同学也适合被临时安排给实验室或学院做管理系统的开发人员。1. 需求拆解高校学术交流系统的真实边界在哪里1.1 用户角色与业务流程高校学术交流报告的日常流程可以简化为一条链路学术秘书发起报告申请录入标题、主讲人、时间、地点、报名上限提交给主管审核审核通过后系统开放报名学生和教师登录查看报告详情、报名并可以提问报告现场支持扫码或按学号签到结束后主讲人或参与人可以上传PPT、论文等材料系统再把这些数据汇总成报表。这套流程里至少有四类角色需要不同的权限如果一开始没有把角色和状态梳理清楚后边写代码时反复改表结构几乎是必然的。我在第一次做需求分析时最大的教训是只盯着“发布报告”这个动作忽略了状态流转。一份报告会经历待审核、已通过、已拒绝、进行中、已结束、已归档这些状态而且每个状态能执行的操作完全不同。比如报名入口只在已通过和进行中开放材料上传只在已结束后开放消息通知只在状态变化时触发。这些规则如果散落在各个视图里后面扩展会非常痛苦。我现在的做法是提前画一张状态机表把每个状态的进入条件和可执行操作写清楚然后让状态字段成为数据模型的业务枢纽所有功能都围绕状态判断展开。1.2 Flask和Django同时出现不是选择题而是先后顺序这个项目标题里把flask和django放在一起很多人会以为是要同时用两个框架。实际上在真实开发里同时用两个Web框架维护同一套业务几乎是最差的选择。我的做法是先用Flask快速做一个轻原型把页面流程和交互逻辑给学院老师看确认需求后再用Django把正式系统搭起来。Flask的原型让我只用两三天就完成了报告列表、报名、后台登录这几个页面的串联因为Flask的API足够精简但真正进入正式开发时我发现需要处理后台管理、用户权限、表单校验、数据库迁移、文件上传这些“标准件”如果继续用Flask我得自己组合Flask-Login、Flask-Admin、Flask-SQLAlchemy、Flask-Migrate花费的时间远超Django自带全家桶。对比项Flask 方式Django 方式开发自由度高组件自己挑中内置约定遵循后开发快后台管理需要Flask-Admin自己组合Django Admin开箱即用用户认证Flask-Login自己集成auth模块自带支持组与权限ORM与迁移SQLAlchemy手动配置Flask-Migrate接入models定义后一条命令迁移适合场景原型、小接口、单页应用多模块管理系统、后台密集型项目从表中可以看到Django最吸引人的是默认后台和Auth体系而高校管理系统最缺的正是这两样。最终结论是原型用Flask正式系统用Django。这个选择并不意味着Flask不好只是系统需求边界已经摆在那里Django的“重量”反而变成了效率。如果你只是临时展示一个想法Flask可以让你很快看到效果如果确定要做成长期维护的内部管理系统Django的收益会随着功能增加越来越大。1.3 技术栈与模块划分整个系统最终的技术栈锁定为Python 3.10 Django 4.2 MySQL本地开发用SQLite Bootstrap 5。选择Django 4.2而不是最新版是因为它是LTS版本长期维护更稳学院服务器上部署也不容易出现意外。MySQL用于存放正式数据SQLite用于本地快速开发两种数据库之间切换只需要改settings里的数据库配置和重新迁移Django在这方面做得足够省心。模块划分我按业务拆成了五个报告管理发布、审核、归档、用户中心注册、登录、个人报名记录、交流互动评论/提问、材料下载、统计导出签到统计、Excel报表、系统管理用户、部门、权限。这个划分在后续开发中帮了很大的忙每个模块对应一个app代码边界清晰出现bug时定位很快。做管理系统最忌讳把所有函数堆在一个views.py里第一次看起来方便等需求从“增加一个字段”变成“增加一个角色”时你会非常想重构。2. 核心数据模型设计先让状态和控制逻辑跑通2.1 用户与权限扩展Django自带UserDjango自带的django.contrib.auth已经提供了User、Group、Permission但直接用它有两个问题一是默认字段没有学号/工号、学院、手机号这些高校场景信息二是我们可能希望用户登录名用学号而不是邮箱。所以第一个动作就是自定义用户模型继承AbstractUser后加字段不要重新造轮子。代码不长效果却非常关键。from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): USER_TYPES ( (student, 学生), (teacher, 教师), (secretary, 学术秘书), (admin, 管理员), ) user_type models.CharField(max_length20, choicesUSER_TYPES, defaultstudent) student_id models.CharField(max_length20, blankTrue, uniqueTrue) department models.CharField(max_length100, blankTrue) phone models.CharField(max_length20, blankTrue) real_name models.CharField(max_length50, blankTrue) def __str__(self): return f{self.real_name or self.username}{self.get_user_type_display()}使用自定义User模型时有一个所有新手都会踩的坑一定要在第一次执行migrate之前在settings.py中设置好AUTH_USER_MODEL account.User。如果项目已经迁移过数据库再回头改需要处理外键重建非常麻烦。我习惯在项目初始化后就配置好后面所有外键都用settings.AUTH_USER_MODEL引用或直接通过get_user_model()而不是import默认User。权限部分我采用分组方案不直接给单个用户分配权限。秘书、教师、管理员分别放在不同的Group里然后在视图或视图类上加装饰器判断。分组的优势很明确学院里人员流动频繁换人时只需要把新用户加入对应分组代码和数据库权限配置都不用动。下面这段代码是典型的受限视图写法。from django.contrib.auth.decorators import login_required, user_passes_test from django.core.exceptions import PermissionDenied def is_secretary(user): if not user.is_authenticated: return False return user.groups.filter(name学术秘书).exists() or user.is_superuser login_required user_passes_test(is_secretary) def review_report(request, report_id): # 只有学术秘书才能审核报告 ...2.2 报告、报名和材料归档的模型设计核心模型分四个AcademicReport、Enrollment、ReportMaterial、Comment。设计原则是让每张表只关注一种实体外键关系用related_name显式命名避免反向查询时出现语义不明。AcademicReport的状态字段使用choices常量而不是在代码里到处写字符串这样既能约束数据也方便模板里用get_status_display显示中文。from django.conf import settings from django.db import models class AcademicReport(models.Model): STATUS_CHOICES ( (pending, 待审核), (approved, 已通过), (rejected, 已拒绝), (ongoing, 进行中), (finished, 已结束), (archived, 已归档), ) title models.CharField(max_length200, verbose_name报告标题) abstract models.TextField(blankTrue, verbose_name摘要) speakers models.ManyToManyField(settings.AUTH_USER_MODEL, related_namespeaking_reports, verbose_name主讲人) start_time models.DateTimeField(verbose_name开始时间) location models.CharField(max_length200, verbose_name地点) capacity models.PositiveIntegerField(default100, verbose_name报名上限) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultpending) created_by models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.PROTECT, related_namecreated_reports) created_at models.DateTimeField(auto_now_addTrue) updated_at models.DateTimeField(auto_nowTrue) class Enrollment(models.Model): report models.ForeignKey(AcademicReport, on_deletemodels.CASCADE, related_nameenrollments) user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE, related_nameenrollments) signed_in models.BooleanField(defaultFalse) created_at models.DateTimeField(auto_now_addTrue) class Meta: unique_together (report, user) class ReportMaterial(models.Model): report models.ForeignKey(AcademicReport, on_deletemodels.CASCADE, related_namematerials) title models.CharField(max_length200) file models.FileField(upload_tomaterials/%Y/%m/) uploaded_by models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.SET_NULL, nullTrue) uploaded_at models.DateTimeField(auto_now_addTrue) class Comment(models.Model): report models.ForeignKey(AcademicReport, on_deletemodels.CASCADE, related_namecomments) user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE) content models.TextField() created_at models.DateTimeField(auto_now_addTrue)这个设计里有几个值得注意的点Enrollment表用unique_together防止重复报名这是数据库层的兜底约束即使代码里漏了判断也不会出现同一个人报同一场报告两次的情况。ReportMaterial的uploaded_by使用on_deletemodels.SET_NULL因为材料是历史数据用户删除后材料记录应该保留。说话人用ManyToMany而不是单一外键因为很多报告会有两三个主讲人一开始只用单外键的话后期改数据结构会很难看。2.3 关键业务逻辑审核、报名与签到状态机会让业务逻辑变得很清晰。比如审核操作只有待审核的报告能进入审核页面审核通过后自动变为已通过同时向发起人发送一条站内通知。这个更新动作要放在事务里避免状态和通知不一致。报名是另一个重点因为报名和签到都会涉及同一个用户对同一报告只能有一条记录还需要处理人数上限。报名的高并发问题学校系统日常并发可能不高但如果有几百个学生同时抢一个热门讲座还是会遇到“报名人数比容量多”的情况。我在实现报名视图时用了select_for_update()锁定报告记录然后在事务里判断人数并插入报名记录from django.db import transaction from django.shortcuts import get_object_or_404, redirect from django.contrib.auth.decorators import login_required login_required def enroll_report(request, report_id): if request.method POST: with transaction.atomic(): report get_object_or_404( AcademicReport.objects.select_for_update(), pkreport_id ) if report.status not in (approved, ongoing): # 返回错误提示 pass if report.enrollments.count() report.capacity: # 提示报名人数已满 pass if not report.enrollments.filter(userrequest.user).exists(): report.enrollments.create(userrequest.user) return redirect(report_detail, pkreport_id) return redirect(report_list)很多人会问为什么不用filter(userrequest.user).count()判断其实事务加唯一约束才是安全的组合。在事务里先锁定报告行其他请求改到同一行时必须等待配合数据库唯一约束即使极端情况下两个请求同时闯进来也只有一个能插入成功。这个写法在本地SQLite和MySQL上都能正常跑。签到设计我放在管理端活动当天秘书进入报告详情页输入学号搜索报名记录点击签到按钮把Enrollment.signed_in改为True。操作前要校验该用户确实报名且报告状态是进行中避免出现误签或未报名直接签到的情况。签到页我会单独用{% if user.is_superuser or perms.report.can_sign %}控制入口不让普通用户看到按钮但页面级控制只是体验的一部分视图级的权限校验才是真正安全边界。3. 从零搭建可运行系统Django实战记录3.1 初始化项目与依赖这个过程我尽量把命令整理成可以直接执行版本。首先创建虚拟环境并安装依赖然后用django-admin startproject建项目、manage.py startapp建业务应用。Windows环境下激活venv的命令略有不同其余步骤一致。python -m venv venv source venv/bin/activate # Windows: venv\Scripts\activate pip install django4.2 mysqlclient python-dotenv gunicorn django-admin startproject academic_system cd academic_system python manage.py startapp account python manage.py startapp report创建完应用后要把account和report加进INSTALLED_APPS同时设置LANGUAGE_CODE zh-hans、TIME_ZONE Asia/Shanghai否则列表页时间会显示UTC中文界面也出不来。数据库配置我一般从环境变量读取线上用MySQL、本地用SQLite避免把密码写进代码里。这里补充一句Django的USE_TZTrue会让DateTimeField自动存UTC时间模板输出时会转换到TIME_ZONE指定的时区所以设置好Asia/Shanghai后报名截止时间不会差8小时。3.2 快速实现核心视图和模板用Django的类视图可以省大量样板代码。报告列表和详情分别用ListView和DetailView够了登录限制用LoginRequiredMixin权限控制再叠加自定义user_passes_test。这样写的代码量少而且类视图里自带的分页、搜索钩子以后都能直接用。from django.views.generic import ListView, DetailView from django.contrib.auth.mixins import LoginRequiredMixin from .models import AcademicReport class ReportListView(ListView): model AcademicReport template_name report/report_list.html context_object_name reports ordering [-start_time] paginate_by 10 class ReportDetailView(LoginRequiredMixin, DetailView): model AcademicReport template_name report/report_detail.html context_object_name report对应的URL配置通常放在report应用的urls.py里主路由include一下就行。模板继承这里特别值得养成习惯我建了一个base.html放导航栏、Bootstrap样式、消息提示区块子模板只需要写内容区域系统增加页面时不用重复写头部和页脚。{% extends base.html %} {% block content %} {% for report in reports %} div classcard mb-3 div classcard-body h3a href{% url report_detail report.pk %}{{ report.title }}/a/h3 p{{ report.get_status_display }} | {{ report.start_time|date:Y-m-d H:i }} | {{ report.location }}/p /div /div {% empty %} p暂无学术报告/p {% endfor %} {% endblock %}模板中只要用POST表单一定记得放{% csrf_token %}。Django的CSRF中间件默认开启表单忘记这个标签提交就会报403这是新手最常见的报错之一。我会把base.html里统一加好全局样式但不会在base模板里放form的csrf_token因为每个form放的位置不同还是需要逐个明确。3.3 如果换成FlaskSQLAlchemy轻量原型写法既然标题里提到了Flask那我把当时用于原型的写法也贴出来。Flask版本不需要启动一个完整工程一个app.py就能把报告列表跑起来。模型结构可以沿用Django设计的思路只是改成SQLAlchemy声明式风格from flask import Flask, render_template from flask_sqlalchemy import SQLAlchemy app Flask(__name__) app.config[SQLALCHEMY_DATABASE_URI] sqlite:///academic_system.db db SQLAlchemy(app) class AcademicReport(db.Model): id db.Column(db.Integer, primary_keyTrue) title db.Column(db.String(200)) abstract db.Column(db.Text) start_time db.Column(db.DateTime) location db.Column(db.String(200)) status db.Column(db.String(20), defaultpending) app.route(/) def index(): reports AcademicReport.query.order_by(AcademicReport.start_time.desc()).all() return render_template(report_list.html, reportsreports)如果只是验证页面流程、给需求方演示交互Flask这几十行代码完全够了。但写到这里你应该也看到了用户认证、后台管理、权限系统在Flask里都要自己搭每个组件都要花时间调研和集成。我的经验是Flask适合动手能力强的团队或者项目规模很小一旦出现“报告需审核、报名有上限、材料要上传、后台要报表”这类复合需求Django直接内置的组件能把开发周期缩短一半。我后来从Flask原型迁移到Django时字段几乎一对一照搬只改了模型基类说明前期把数据字段想清楚比纠结框架更重要。4. 部署、测试与常见问题排查实录4.1 部署从本地到公网的可靠路线学院服务器通常是Ubuntu我推荐Nginx Gunicorn Django的经典组合。这套组合稳定、资料多出了问题也容易搜到解决办法。部署有几个必须处理的点关闭调试模式、设置ALLOWED_HOSTS、收集静态文件、用环境变量管理密钥。先看基础命令cd /opt/academic_system python -m venv venv source venv/bin/activate pip install -r requirements.txt python manage.py migrate python manage.py collectstatic --noinput gunicorn academic_system.wsgi:application --bind 127.0.0.1:8000 --workers 3Gunicorn直接运行在终端一旦关掉SSH服务就断了所以要用systemd把Gunicorn托管成服务让它在服务器重启后自动拉起。Nginx负责反向代理和静态文件配置里把/static/和/media/指到对应目录其余请求转发到8000端口。Nginx配置需要放在/etc/nginx/sites-available/下并做个软链接到sites-enabledserver { listen 80; server_name your_college_domain.com; location /static/ { alias /opt/academic_system/staticfiles/; } location /media/ { alias /opt/academic_system/media/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }Flask部署和Django部署在访问入口上有区别Flask应用实例在app.py模块里Gunicorn命令要写成gunicorn app:app同时还需要额外配置静态文件服务不然页面上的CSS和JS会全部404。这也是正式系统选择Django的一个原因Django的collectstatic命令和自带的静态文件处理体验好很多尤其在后端人员不擅长Nginx配置的团队里少一个坑就是省一天时间。4.2 高频Bug速查表整理一下实战中高频遇到的问题。这些问题本身不难但如果你第一次部署很容易被它们卡住几个小时现象原因解决办法表单提交后403 CSRF验证失败模板缺少{% csrf_token %}在POST表单中加上标签静态文件加载出来404DEBUGFalse但没配置STATIC_ROOT也没collectstatic配置STATIC_ROOT并执行collectstatic中文乱码MySQL连接或数据库默认字符集不是utf8mb4建库时指定utf8mb4连接参数加charset上传大文件直接报错超出DATA_UPLOAD_MAX_MEMORY_SIZE默认值适当调大并在视图里限制文件类型和大小从SQLite切到MySQL出现字段不兼容旧迁移文件和新数据库类型不一致全新环境时重新makemigrationsmigrate重启后服务无法访问Gunicorn没有托管为systemd服务写unit文件enable并start每一个问题背后都有对应的配置细节。比如CSRF 403很多人会去关掉中间件这是最坏的做法等于把安全防线拆了正确做法是找到模板里漏掉的标签。再比如中文乱码Django里代码写的是UTF-8但MySQL库建表时用了默认latin1最后就是字符错乱。解决办法是建库SQL里明确写DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci其他环境配置再标准这一步漏了照样出问题。4.3 权限、并发与数据安全细节业务逻辑中的权限校验不能只依赖页面隐藏入口后台接口也要校验。我见过不少管理系统页面看不到按钮但直接访问URL就能操作因为视图入口没有加校验。正确做法是视图函数或类视图上加登录和分组校验并且对非法的访问返回403而不是404方便排查日志。这里的细节是user_passes_test装饰器默认会把未登录用户重定向到登录页已登录但权限不足的用户才会看到403两种行为都合理但测试时要区分。并发场景主要在两个地方报名人数不能超过capacity签到不能重复。报名用select_for_update配合事务签到用唯一约束兜底。签到操作虽然看起来只是把BooleanField改成True但如果不做幂等处理同一个人被点两次可能会留下多条报表记录。我在模型里用unique_together防重复报名签到本身不会创建新记录所以只要报名唯一签到天然不会出现重复。文件上传安全也要重视。前端做JS校验只能提升体验不能作为安全边界因为绕开前端直接POST很简单。后台上传材料时要判断扩展名是否在允许列表里比如pdf、ppt、pptx、doc、docx同时限制文件大小。Django里可以写一个自定义验证器在FileField的validators参数中传入比在每个视图里写重复代码干净得多。高校系统会存储学生和教师身份证件吗一般不会但报告材料里可能会有未发表的论文这类数据至少要控制下载权限不能把所有文件都放在公开目录。5. 功能扩展关键词推荐、实时通知与管理报表5.1 基于关键词相似度推荐相关报告学术交流系统不只是报名还可以让用户发现相关报告。我后来在详情页加了“你可能感兴趣的报告”逻辑很简单把报告的标题和摘要拼接成文本用Python标准库里的difflib.SequenceMatcher计算相似度选出得分最高的三篇。对于校园内部系统这个方案零依赖、部署简单效果也足够解释“为什么你会看到这一场”。import difflib def similar_reports(report, limit3): candidates AcademicReport.objects.exclude(pkreport.pk).filter( status__in[approved, ongoing, finished] ) base f{report.title}{report.abstract} scored [] for candidate in candidates: text f{candidate.title}{candidate.abstract} score difflib.SequenceMatcher(None, base, text).ratio() scored.append((candidate, score)) scored.sort(keylambda x: x[1], reverseTrue) return [item for item, _ in scored[:limit]]这里的相似度算法很粗糙因为SequenceMatcher主要比较字符出现顺序对同义词、语序变化不敏感。但如果之后需要更准的推荐可以在分词后用TF-IDF向量计算余弦相似度sklearn已经内置了TfidfVectorizer加上jieba中文分词效果会上一个台阶。我建议先跑简单的等用户反馈说推荐不准再迭代不要一开始就上向量数据库。5.2 报名后的实时提醒WebSocket还是轮询系统开发过程中常有老师说“有人报名能不能页面自动跳出来提示”这就是实时通知需求。Django原生的同步视图很难做真正推送有两个方案轮询和WebSocket。轮询的做法是前端每隔5秒请求一个JSON接口返回最新报名数量或站内消息WebSocket则需要引入Django Channels把部署从WSGI变成ASGI资源占用和复杂度都会上升。我的建议是先评估并发到底有多少。校园内一套系统同时在线几十个人轮询体验完全不差还能在Django原生视图里实现不用改架构。除非要做像在线讨论室、实时答疑这种消息频率很高的功能才值得引入Channels。如果只是通知公告更简单的方案是后台提交成功后跳转一个页面显示成功信息不需要实时推送。技术是服务于场景的不要为了“实时”两个字盲目上WebSocket这是我反复会提醒自己的原则。5.3 给管理员的Excel导出学院老师非常喜欢Excel报表尤其期末要统计到会率和报告数量。我在report应用里加了一个export_report_stats视图用openpyxl生成工作簿。这个功能写起来不难但是能大幅减少行政老师手动整理数据的时间。from openpyxl import Workbook from django.http import HttpResponse def export_report_stats(request): wb Workbook() ws wb.active ws.append([报告标题, 报名人数, 签到人数, 到会率]) for report in AcademicReport.objects.prefetch_related(enrollments): total report.enrollments.count() signed report.enrollments.filter(signed_inTrue).count() rate f{signed / total * 100:.1f}% if total else 0% ws.append([report.title, total, signed, rate]) response HttpResponse( content_typeapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet ) response[Content-Disposition] attachment; filenameacademic_report_stats.xlsx wb.save(response) return response导出视图要注意两个细节一是文件名带中文时Content-Disposition可能乱码需要做URL编码或者干脆用英文字母文件名下载后再由操作系统重命名二是在循环里统计报名人数会产生N1查询所以用prefetch_related(enrollments)先加载关联数据。Django的ORM对这类统计也有aggregate和annotate不过当数据量不大时prefetch_related加上普通count已经够用重点是不要写一个慢SQL等老师点导出后转半天。6. 复盘与优化给同类型项目的几条真实建议6.1 数据模型与状态设计的三条军规做完整个项目最有价值的心得不是某段代码而是一开始就把数据和状态规划好。第一条所有模型都该有一个BaseModel包含created_at和updated_at两个公共时间字段不然后期做报表统计时到处补字段特别痛苦。第二条状态字段用代码里的choices而不要直接写中文进数据库因为数据库里存的是英文标识显示文案在模板里用get_status_display将来改文案不用动数据。第三条状态流转尽量收敛到一个函数或服务类里不要在每个视图里直接report.status approved否则以后增加一个“撤回审核”动作要改的地方数都数不过来。这几点看起来是基础但实际项目中极其容易被忽视。我第一次做的时候就是在视图里随手改状态结果有一次秘书把报告从“已通过”误点成“已拒绝”又找不到是哪个页面改的最后只能查数据库手动改正。后来我把所有状态变更都封装成AcademicReportService.approve(report, operator)一类的方法并且记录操作日志再出现问题就能追踪到操作人和时间。6.2 后台管理、日志与项目节奏不要小看Django自带的Admin。它在内部系统里比很多自研后台都好用把list_filter、search_fields、readonly_fields配置好管理老师可以直接在Admin里维护基础数据比如调整报告状态、查看报名名单能省下大量前端开发时间。我在项目初期还花时间写了一个完整的管理页面后来发现Admin已经覆盖了八成的需求自己写的那套交互反而没人用最后只保留了两个自定义管理页面。日志和操作记录建议从第一天就做。Django可用的方案很多简单的做法是写一个中间件记录请求或者在关键权限操作里写日志。校园内部系统虽然不像互联网产品那样有恶意攻击压力但“谁改了这个字段”的责任问题很常见有日志能快速定位。项目节奏上也不要试图一步到位先跑通报告发布、报名、签到、报表这条主链路再考虑推荐、实时通知、样式美化核心流程稳定了后续优化才有意义。这个项目给我的另一个体会是技术选型没有绝对优劣关键是匹配需求成熟度。如果你已经确定要做一个长期维护的管理系统Django一定比Flask省心如果只是想快速验证一个想法Flask会让你更自由。但无论选哪个数据模型和权限边界都是值得花时间琢磨的地方这一层想清楚了后续开发就是填充功能而已。
网站建设高端定制企业官网