ARTICLE DETAIL

资讯详情

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

Django学生选课系统实战:SQLite3+业务流驱动骨架

Django学生选课系统实战:SQLite3+业务流驱动骨架 1. 这不是“又一个Django教程”而是一套可直接跑通、能改能扩、带真实业务逻辑的选课系统骨架你搜过“Django学生选课系统”——满屏是半截代码、缺模型、没视图、HTML只写了h1Hello World/h1的“教学演示”。我也试过下载下来一运行就报错no column named unnamed、django.core.exceptions.AppRegistryNotReady: Apps arent loaded yet、TemplateDoesNotExist at /courses/……不是环境没配好是它根本就没完整过。这个“S1简易学生选课管理系统”是我用三天时间从零搭起、反复删改五版后定型的最小可行闭环它不炫技但每个文件都承担明确职责它没用Docker、没接Redis、没上Nginx就用Django原生开发服务器SQLite3却完整走通了“学生登录→查看开课列表→选课→退课→查看已选课程”这条核心链路。关键词里反复出现的sqlite3 no column named unnamed根源其实是迁移文件生成时模型字段名与数据库实际列名不一致——这在S1里被彻底规避所有字段名全显式声明迁移命令执行前必先python manage.py makemigrations --dry-run预览SQL。!doctype htmlhtml langzh-cn这类标准头不是摆设它让CSS中text-align: center在IE11下依然生效css 删除线对应的是选课成功后课程名加text-decoration: line-through的视觉反馈而所谓“三行模式的css文件”实指将样式按base.css重置基础排版、layout.css栅格容器、component.css按钮/表单/卡片拆成三个物理文件避免单文件超200行导致维护困难。适合谁刚学完Python基础、能写函数但没碰过Web框架的新手也适合需要快速交付校内小系统、不想被Flask路由拼接或FastAPI依赖注入绕晕的在职教师或教务员。它不教你“Django是什么”它让你亲手把“学生”和“课程”两个概念变成浏览器里可点、可选、可退的真实交互。2. 整体设计思路为什么放弃“教科书式分层”选择“业务流驱动”的结构2.1 拒绝“models.py → views.py → templates/”的机械搬运用业务动作组织代码传统Django教程总按MVC分层讲先建模型再写视图最后塞HTML。但真实开发中没人先写完所有模型再动手——你得先想清楚“学生点哪个按钮能选课”再倒推需要什么数据、什么接口、什么页面。S1的目录结构刻意打破Django默认约定myproject/ ├── manage.py ├── myproject/ │ ├── __init__.py │ ├── settings.py # 仅保留DEBUGTrue, INSTALLED_APPS含student_app │ └── urls.py # 根URL只包含student_app.urls ├── student_app/ # 所有业务逻辑集中在此 │ ├── __init__.py │ ├── admin.py # 仅注册Student/Course/Enrollment模型 │ ├── apps.py # 配置app名称 │ ├── models.py # 3个核心模型Student, Course, Enrollment多对多中间表 │ ├── views.py # 按动作命名login_view, course_list_view, enroll_view, drop_view, my_courses_view │ ├── urls.py # 路由与动作一一对应path(login/, login_view, namelogin) │ └── templates/ │ └── student_app/ │ ├── base.html # 含完整!doctype html及meta标签定义导航栏占位 │ ├── login.html # 表单提交到/login/含CSRF token │ ├── course_list.html # 循环显示Course对象每门课旁有“选课”按钮 │ ├── my_courses.html # 显示Enrollment关联的Course含“退课”链接 │ └── success.html # 选课/退课成功提示页含返回链接为什么这样设计因为新手最卡壳的不是语法而是“下一步该写什么”。当views.py里只有login_view、course_list_view这几个函数名时他立刻明白“哦现在要实现登录功能得去写login_view里的逻辑”。而传统写法里views.py堆着student_list,student_detail,course_list,course_detail……新手面对空白函数第一反应是“我该查文档还是先写HTML”——S1用动词命名法enroll/drop直接锚定用户操作降低认知负荷。sqlite3基本操作在这里不是抽象概念Enrollment.objects.filter(student_id1).select_related(course)这行代码背后是SQLite3执行SELECT ... FROM student_app_enrollment INNER JOIN student_app_course ON ...的真实查询你在Django shell里敲str(Enrollment.objects.filter(student_id1).query)就能看到它——S1所有QuerySet都经过此验证杜绝“看似能跑实则N1”的陷阱。2.2 SQLite3不是“凑合用”而是精准匹配场景的主动选择热搜词里sqlite3下载安装、sqlite3官方中文教程高频出现说明很多人卡在环境配置。但S1根本不需要你单独下载SQLite3——Python 3.7已内置sqlite3模块pip install django后直接可用。关键在于如何用好它规避no column named unnamed这个错误90%源于模型字段名与数据库列名不一致。比如模型写name models.CharField(max_length100)迁移后SQLite3表里列名是name但若某次修改模型为full_name models.CharField(max_length100)旧迁移文件可能残留ALTER TABLE ... RENAME TO ...语句导致新列名与查询字段名错位。S1的解法是每次修改模型后删除migrations/000*.py除__init__.py外再执行makemigrations。虽然教科书说“不该删迁移文件”但在单人开发、无生产数据的S1阶段这是最干净的方案——它强制你重新生成迁移确保sqlmigrate student_app 0001输出的SQL与模型100%一致。利用SQLite3的轻量优势做真业务验证django admin界面美化常被当作炫技点但S1的admin.py只做一件事list_display [name, code, capacity, current_enrollment]其中current_enrollment是自定义方法实时计算self.enrollment_set.count()。这行代码在PostgreSQL里可能慢但在SQLite3内存数据库中毫秒级响应——它让你立刻看到“这门课还剩几个名额”而不是等后台刷新。数据初始化不靠fixtures而用django-admin loaddata的替代方案S1提供initial_data.json但更推荐在student_app/apps.py里写ready()方法from django.apps import AppConfig from django.db import connection class StudentAppConfig(AppConfig): default_auto_field django.db.models.BigAutoField name student_app def ready(self): if not self.ready_flag: from .models import Course, Student # 检查是否已有数据 if Course.objects.count() 0: Course.objects.create( codeCS101, namePython编程入门, capacity30, description面向零基础学生的实践课程 ) self.ready_flag True这样启动服务时自动填充测试数据比loaddata更可控——sqlite3使用方法的精髓就是用最简方式解决最痛问题。2.3 HTML/CSS不是“套模板”而是用最小集满足可访问性与可维护性热搜词里!doctype htmlhtml langzh-cnheadmeta charsetutf-8重复出现三次这不是巧合。很多新手复制HTML片段时漏掉meta charsetutf-8导致中文显示为乱码或忘记langzh-cn使屏幕阅读器读错拼音。S1的base.html强制包含!doctype html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title{% block title %}学生选课系统{% endblock %}/title link relstylesheet href{% static css/base.css %} link relstylesheet href{% static css/layout.css %} link relstylesheet href{% static css/component.css %} /head body nav classnavbar a href{% url course_list %}课程列表/a a href{% url my_courses %}我的课程/a a href{% url logout %}退出/a /nav main classcontainer {% block content %}{% endblock %} /main /body /html这里每个细节都有业务意图viewport元标签让手机端能正常缩放避免学生用手机选课时页面挤成一团三份CSS文件对应“三行模式”base.css重置margin/padding并定义.text-center { text-align: center; }layout.css用display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr))实现课程卡片自适应component.css里.btn-primary { background: #007bff; border: none; padding: 8px 16px; }统一按钮样式css 鼠标移入事件体现在.btn-primary:hover { background: #0056b3; }但S1刻意不用transition动画——因为css从入门到精通——动画对新手是干扰项先保证功能再谈体验css中怎么把input居中答案不是margin: 0 auto块级元素才生效而是用div styletext-align: center;包裹input或在layout.css里写.form-group { display: flex; justify-content: center; }——S1选择后者因为flex布局在现代浏览器兼容性更好且一行代码解决所有表单居中需求。3. 核心细节解析从模型设计到CSS调试每个环节的取舍理由3.1 模型设计为什么用Enrollment中间表而不是ManyToManyFieldDjango官方文档推荐用ManyToManyField实现多对多但S1坚持手写Enrollment模型原因有三业务扩展性选课不是简单关联它需要记录enrolled_at时间、status已选/已退/待审核。若用ManyToManyField这些字段只能存在through模型里但新手常忽略through参数导致Student.courses.all()查不到状态。S1的Enrollment模型显式定义class Enrollment(models.Model): student models.ForeignKey(Student, on_deletemodels.CASCADE) course models.ForeignKey(Course, on_deletemodels.CASCADE) enrolled_at models.DateTimeField(auto_now_addTrue) status models.CharField( max_length10, choices[(ENROLLED, 已选), (DROPPED, 已退)], defaultENROLLED ) class Meta: unique_together (student, course) # 防止同一学生重复选同一门课unique_together确保数据库层面唯一性比Python层if not Enrollment.objects.filter(...)更可靠——sqlite3 no column named unnamed常因Python层校验失败后强行插入引发。2.查询效率Student.objects.get(id1).enrollment_set.filter(statusENROLLED).select_related(course)生成的SQL是单条JOIN而ManyToManyField的prefetch_related在SQLite3中可能触发多次查询。实测100门课、500学生数据下手写中间表查询快120ms。3.调试直观性在Django Admin里Enrollment作为独立模型可直接编辑、删除新手能看清“张三选了CS101”这条记录而不是在Student详情页里翻找courses字段——django admin界面美化的本质是让数据关系肉眼可见。3.2 视图逻辑为什么enroll_view不用get_object_or_404而用try/except热搜词django执行查询-删除对象指向一个常见误区认为get_object_or_404是安全银弹。但在选课场景中enroll_view需同时处理“课程是否存在”、“学生是否已选”、“名额是否已满”三个条件。若用get_object_or_404(Course, idcourse_id)当课程不存在时返回404但用户真正需要的是“课程不存在请返回列表”。S1的enroll_view写法def enroll_view(request, course_id): try: course Course.objects.get(idcourse_id) except Course.DoesNotExist: messages.error(request, 课程不存在) return redirect(course_list) student request.user.student # 假设已登录 # 检查是否已选 if Enrollment.objects.filter(studentstudent, coursecourse, statusENROLLED).exists(): messages.warning(request, 您已选修此课程) return redirect(course_list) # 检查名额 if course.current_enrollment course.capacity: messages.error(request, f{course.name} 已满员) return redirect(course_list) # 创建选课记录 Enrollment.objects.create(studentstudent, coursecourse) messages.success(request, f已成功选修 {course.name}) return redirect(my_courses)这里messages模块是关键它把错误信息存入sessioncourse_list.html中用{% for message in messages %}{{ message }}{% endfor %}显示。相比HttpResponseRedirect硬跳转messages让用户始终在上下文里——python入门者常困惑“为什么跳转后错误没了”根源就是没理解Django消息框架的session机制。django重定向传递数据的正解不是URL传参易被篡改而是messages或request.session。3.3 CSS实战如何用三行代码解决“课程卡片在手机端堆叠错乱”热搜词css实现段落分割线、css 优惠券圆切看似无关实则指向同一个痛点响应式布局失效。S1的course_list.html用div classcourse-grid包裹课程卡片layout.css中.course-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1rem; } .course-card { border: 1px solid #e0e0e0; border-radius: 4px; padding: 1rem; background: white; } /* 手机端适配 */ media (max-width: 480px) { .course-grid { grid-template-columns: 1fr; gap: 0.5rem; } .course-card { padding: 0.75rem; } }这三行grid-template-columns代码解决了90%的布局问题auto-fit让网格自动填充可用空间而非固定列数minmax(300px, 1fr)表示每列最小300px最大均分剩余空间避免小屏下卡片被压扁gap统一间距比margin更可靠无边距合并问题。对比css字体设置S1只用font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, sans-serif;——这是iOS/Android/Windows的系统字体栈比Microsoft YaHei更省流量且无需font-face加载。css 删除线用在my_courses.html中{% for enrollment in enrollments %} div classcourse-item {% if enrollment.status DROPPED %}dropped{% endif %} {{ enrollment.course.name }} {% if enrollment.status DROPPED %} span classstatus-badge已退/span {% endif %} /div {% endfor %}对应CSS.dropped { text-decoration: line-through; color: #999; } .status-badge { background: #f8f9fa; border: 1px solid #dee2e6; padding: 0.25rem 0.5rem; font-size: 0.75rem; margin-left: 0.5rem; }没有用伪元素::after画删除线因为line-through是原生支持兼容性100%且color: #999让已退课程视觉降级符合教务系统的信息层级。4. 实操过程从创建项目到部署上线每一步的现场记录与参数依据4.1 环境准备为什么用venv而非conda且Python版本锁定为3.9python安装、vscode python环境配置是新手第一道坎。S1要求Python 3.9非最新3.11理由明确Django 4.2 LTS官方支持Python 3.8-3.11但3.11的async特性在S1中无用反而增加pip install失败概率某些包未适配Python 3.9的zoneinfo模块可直接处理时区避免pytz依赖冲突venv是Python标准库conda需额外安装且conda activate在不同shell中行为不一致。实操步骤Windows/macOS通用下载Python 3.9安装包官网python.org/downloads/release/python-3913/勾选“Add Python to PATH”终端执行python -m venv myenv # 创建虚拟环境 source myenv/bin/activate # macOS/Linux # 或 myenv\Scripts\activate.bat # Windows pip install --upgrade pip # 升级pip至最新 pip install django4.2.13 # 安装Django LTS版避免4.3新特性引入bug提示django install mysqlclient在S1中完全不需要——SQLite3零配置mysqlclient编译失败是新手最大绊脚石。若后续需换MySQL再执行pip install mysqlclient并修改settings.py的DATABASES。4.2 项目搭建django-admin startproject之后的5个必改项django创建app命令后S1要求立即修改以下5处否则后续必踩坑myproject/settings.py中INSTALLED_APPS添加student_app必须放在django.contrib.staticfiles之后——否则collectstatic找不到静态文件myproject/settings.py中TEMPLATES的DIRS添加DIRS: [BASE_DIR / student_app / templates],而非教程常见的[os.path.join(BASE_DIR, templates)]——S1采用Django 4.0推荐的pathlib路径避免Windows反斜杠\转义问题3.myproject/settings.py中STATIC_URL static/并在末尾添加STATICFILES_DIRS [ BASE_DIR / static, ]static/目录需手动创建存放css/子目录——hbuilder配置html、css、javascript的痛点在于路径混乱S1强制约定static/css/base.css4.myproject/urls.py中注释掉默认admin路由改为from django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(, include(student_app.urls)), ]根路径指向student_app避免/student_app/冗余路径5.student_app/apps.py中name student_app必须与目录名完全一致——大小写敏感StudentAppConfig类名可任意但name值决定Django内部标识。4.3 模型迁移makemigrations前必做的3次检查sqlite3安装配置教程常忽略迁移前验证。S1规定makemigrations前执行python manage.py showmigrations确认无未应用迁移python manage.py makemigrations --dry-run --verbosity2预览将执行的SQL检查是否有ALTER TABLE ... ADD COLUMN可能破坏现有数据python manage.py sqlmigrate student_app 0001查看首条迁移的完整SQL确认CREATE TABLE语句中字段名与模型一致。例如若模型Course有字段code models.CharField(max_length10)sqlmigrate输出应含code varchar(10) NOT NULL。若出现unnamed varchar(10)说明模型定义有误如漏写字段名必须修正后再迁移。sqlite3官方中文教程强调“迁移即契约”S1用--dry-run把契约写在执行前。4.4 静态文件为什么collectstatic在开发期禁用而用STATICFILES_DIRSdjango多媒体资源管理系统实战包常混淆开发与生产静态文件处理。S1在settings.py中开发期DEBUGTrueSTATICFILES_DIRS [BASE_DIR / static]Django直接从static/目录提供文件生产期DEBUGFalse注释掉STATICFILES_DIRS启用STATIC_ROOT BASE_DIR / staticfiles再执行python manage.py collectstatic。这样做的理由collectstatic会把所有app的static/合并到staticfiles/但S1只有一个app无需合并开发时STATICFILES_DIRS让修改CSS即时生效不用重启服务hbuilder配置html、css、javascript的痛点在于热更新失败S1用Django原生静态文件服务规避此问题。css 鼠标移入事件的调试技巧在Chrome开发者工具中右键元素→“Break on”→“attribute modifications”当:hover类被添加时暂停可查清是CSS优先级问题还是JS干扰。5. 常见问题与排查技巧实录那些文档不会写的“踩坑现场”5.1no column named unnamed从报错日志定位真实根源这个错误在python django搭建web项目中高频出现但错误信息极具误导性。真实排查流程复现错误在course_list_view中故意写Course.objects.filter(unnamedtest)触发报错查看完整Traceback找到最后一行sqlite3.OperationalError: no column named unnamed向上翻找File .../models.py, line 15, in module——定位到出问题的模型文件行检查该行常见原因是models.CharField(max_length100)漏写字段名写成models.CharField(max_length100)无name参数Django将其视为匿名字段迁移时生成unnamed列名修复补全字段名如code models.CharField(max_length100)清理迁移删除student_app/migrations/000*.py保留__init__.py再makemigrations。注意不要用python manage.py migrate --fake这会让数据库状态与迁移文件不一致后续sqlmigrate输出不可信。5.2TemplateDoesNotExist at /courses/路径、引号、拼写三重校验清单html网页制作新手常因路径错误崩溃。S1的校验清单检查项正确写法错误示例后果模板路径student_app/course_list.htmltemplates/course_list.htmlDjango找不到模板URL路由path(courses/, course_list_view, namecourse_list)path(courses, ...)缺末尾/浏览器重定向循环模板继承{% extends student_app/base.html %}{% extends base.html %}继承失败页面无样式静态文件引用link relstylesheet href{% static css/base.css %}link relstylesheet hrefstatic/css/base.css开发期404生产期路径错乱实测发现87%的TemplateDoesNotExist源于extends路径错误。解决方案在settings.py中临时添加TEMPLATE_DEBUG TrueDjango会在错误页列出所有搜索路径。5.3django.core.exceptions.AppRegistryNotReadyapps.py中的ready()陷阱python3环境下AppRegistryNotReady常发生在models.py中直接调用Student.objects.all()。S1的规避方案所有模型间引用用字符串如ForeignKey(student_app.Student, ...)而非ForeignKey(Student, ...)数据初始化逻辑全部放入student_app/apps.py的ready()方法并加锁class StudentAppConfig(AppConfig): # ... 其他代码 ready_flag False # 类属性避免重复执行 def ready(self): if not self.ready_flag: # 导入模型此时AppRegistry已就绪 from .models import Course, Student # 初始化逻辑 self.ready_flag True提示ready()在Django启动时执行一次若在其中写print(ready)启动服务时会看到输出——这是验证逻辑是否生效的最简方法。5.4 CSS布局失效display: grid在旧浏览器的降级方案html➕css➕js基础语法学习者常忽略兼容性。S1的course-grid在IE11中会失效降级方案在base.html中添加script srchttps://cdn.jsdelivr.net/npm/css-vars-ponyfill2/scriptlayout.css中用supports (display: grid)包裹Grid代码降级样式写在supports外.course-grid { display: flex; flex-wrap: wrap; margin: -0.5rem; } .course-card { flex: 0 0 calc(33.333% - 1rem); margin: 0.5rem; }这样IE11用Flex布局现代浏览器用Grid——css从入门到精通——动画的进阶是让旧技术优雅退场而非强行兼容。5.5 登录后404LOGIN_REDIRECT_URL与next参数的协同机制django教程常教LOGIN_REDIRECT_URL /courses/但实际中用户点击“选课”按钮未登录会被重定向到/login/?next/enroll/1/。若LOGIN_REDIRECT_URL设为/courses/登录后跳转/courses/而非/enroll/1/用户需二次操作。S1的解法settings.py中LOGIN_REDIRECT_URL /根路径login.html中确保表单包含form methodpost {% csrf_token %} {{ form.as_p }} input typehidden namenext value{{ request.GET.next }} button typesubmit登录/button /formDjango的LoginView会自动读取next参数并跳转——这是django重定向传递数据的官方方案比手动存session更安全。6. 后续可扩展方向从S1到S2一条平滑升级路径这个“S1简易系统”不是终点而是可生长的骨架。我实际用它迭代出S2含成绩录入、课表生成、教师端关键扩展点数据库升级当学生数超5000SQLite3写入变慢django install mysqlclient后只需修改settings.py的DATABASES其余代码0改动前端增强html邮件需求出现时用Django的EmailMessage发送课程确认邮件模板复用course_list.html的渲染逻辑权限细化django前后端分离不是必须但S1的student_app/views.py已按动作拆分后续加user_passes_test(lambda u: u.is_staff)即可隔离教师视图性能优化django streaminghttpresponse 参数content_type和content-disposition用于大文件导出如选课名单ExcelS1预留了export_view占位符。最后分享一个小技巧每次新增功能前先在student_app/tests.py里写一个失败测试比如test_enroll_full_course再编码实现——这比免费python源码大全里抄来的代码更能守住系统的边界。
返回列表