Django轻量投票系统源码:SQLite直连+前后端完整代码,一键启动

发布时间:2026/6/9 3:22:24

Django轻量投票系统源码:SQLite直连+前后端完整代码,一键启动 本文还有配套的精品资源点击获取简介基于Django 3.x/4.x开发的在线投票系统纯Python实现不依赖Java或外部服务。后端用Django框架处理业务逻辑前端采用原生HTML搭配jQuery完成交互无需复杂构建工具。数据库使用内置SQLitedb.sqlite3文件直接放在项目根目录开箱即用省去配置步骤。项目结构标准清晰含polls应用模块、mysite主站点配置、manage.py和django-admin.py管理脚本支持通过python manage.py runserver快速本地运行。适配Sublime Text 3编辑环境数据库可配合Navicat Premium可视化查看与调试。功能涵盖用户浏览问题、选择选项、提交投票、实时查看统计结果等全流程支持多问题、多选项、单选/多选基础逻辑。适合高校课程设计、Python Web教学演示、社团活动或内部小型调研场景快速部署落地。1. 项目概述为什么这个投票系统值得你花十分钟打开它我带过六届Python Web课程设计每年都有学生卡在“第一个能跑起来的完整项目”上——不是不会写视图函数也不是搞不定URL路由而是被环境配置、数据库迁移、静态文件路径、前后端联调这些“非核心但必踩”的坑拖垮了信心。直到去年我把这套Django轻量投票系统作为课堂演示模板推给学生才真正体会到什么叫“开箱即用”的价值。它不炫技不堆砌功能但每一个设计选择都直指教学与小型落地场景的真实痛点没有Docker、没有Nginx、没有Redis缓存、不碰任何Java生态甚至连pip install都只要一行命令SQLite数据库文件db.sqlite3就躺在根目录下双击就能用Navicat打开看数据前端没用Vue或React打包链jQuery代码直接写在HTML里改完保存刷新就能看到效果。这就是它最硬核的定位——不是生产级高并发系统而是帮你把Django的MVT三层结构、模型定义、表单处理、模板继承、URL分发这些骨架性知识用最短路径具象化成可触摸、可调试、可修改的实体。关键词“Django投票”“SQLite投票系统”“Python投票源码”背后其实是三个明确信号第一它是Django官方教程的延伸实战不是另起炉灶第二SQLite不是妥协而是刻意选择——它把“数据库连接配置”这个抽象概念压缩成一个文件路径和一条python manage.py migrate命令第三“源码”二字意味着你能看清每一行逻辑从polls/models.py里Question和Choice两个模型如何建立一对多关系到templates/polls/detail.html中{{ question.question_text }}如何被Django模板引擎解析再到views.py里vote()函数如何通过request.POST.get(‘choice’)捕获用户点击并完成原子性更新。它适合谁高校计算机专业大三学生做课程设计社团负责人三天内搭起招新意向投票页或者刚学完Django基础想验证自己理解是否正确的自学者。你不需要懂WSGI协议也不需要研究Gunicorn进程管理只需要确认Python 3.8已安装执行pip install -r requirements.txt再敲下python manage.py runserverlocalhost:8000/polls/就会弹出那个熟悉的投票页面——这种确定性在Web开发入门阶段比任何技术文档都珍贵。2. 整体架构与设计思路拆解为什么是DjangoSQLite原生jQuery2.1 技术栈选型背后的教学逻辑与工程权衡这套系统的架构看似简单实则每层选择都经过反复推演。后端选用Django而非Flask并非因为Django更“高级”而是因为它天然封装了教学中最易混淆的边界问题。比如学生常问“为什么我的表单提交后页面空白”在Flask中这可能涉及request对象处理、重定向响应、session状态维护等多个分散知识点而在Django中一个标准的ModelForm类就能把模型定义、字段校验、HTML渲染、POST数据绑定全部收束在一个类里学生只需关注“我定义的模型字段是否匹配前端input的name属性”。SQLite作为数据库的选择更是直击教学场景的核心矛盾当学生第一次运行python manage.py migrate时如果面对的是MySQL配置错误或PostgreSQL权限拒绝90%的注意力会从“理解ORM映射原理”转移到“怎么解决数据库连接失败”。而SQLite把整个数据库压缩成db.sqlite3一个文件migrate命令执行后你甚至可以用文本编辑器打开它虽然内容是二进制但用Navicat Premium打开后能清晰看到django_migrations、polls_question、polls_choice三张表的结构——这种“所见即所得”的数据库可视化让抽象的“迁移”概念瞬间具象化。前端放弃现代构建工具坚持用原生HTMLjQuery同样有明确的教学意图。我观察到当学生面对Webpack配置和Vue组件生命周期时很容易陷入“工具链学习”而非“业务逻辑学习”。而这段jQuery代码$(#vote-form).on(submit, function(e) { e.preventDefault(); $.post($(this).attr(action), $(this).serialize(), function(data) { location.reload(); }); });不到十行却完整覆盖了事件绑定、表单序列化、AJAX提交、响应处理四个关键交互环节。学生可以逐行打断点调试看到$(this).serialize()输出的字符串是”choice3”看到$.post请求的URL是”/polls/1/vote/”这种颗粒度的可控性是任何框架封装都无法替代的学习入口。2.2 目录结构解析标准Django项目的教科书式范本项目目录树表面平平无奇实则暗藏教学线索。我们来一层层剥开-manage.py和django-admin.py是Django的“操作手柄”前者用于当前项目如runserver、migrate后者是全局命令行工具如startproject。学生第一次执行python manage.py startapp polls时就能直观理解“应用app”在Django中的模块化意义。-mysite/是项目配置中心其中settings.py是关键教学切口。这里没有复杂的生产环境配置只有最精简的设置INSTALLED_APPS明确列出’django.contrib.admin’, ‘django.contrib.auth’, ‘polls’三个应用让学生一眼看清Django的“插件化”思想DATABASES配置段只保留SQLite默认配置注释掉所有其他数据库选项避免干扰STATIC_URL /static/配合STATICFILES_DIRS指向mysite/static为后续讲解静态文件加载埋下伏笔。-polls/应用目录是业务逻辑核心其结构严格遵循Django最佳实践models.py定义数据结构Question有question_text和pub_dateChoice有关联Question的外键和votes计数字段views.py处理HTTP请求index展示问题列表detail渲染单个问题results返回统计结果vote处理投票动作urls.py实现应用内URL分发与mysite/urls.py的path(polls/, include(polls.urls))形成两级路由体系这是理解Django URL设计哲学的黄金案例。-templates/polls/下的HTML文件采用Django模板语法{% extends base.html %}体现模板继承机制{% for choice in question.choice_set.all %}展示ORM反向查询{{ question.question_text|linebreaks }}引入过滤器概念——这些都不是孤立语法点而是嵌套在真实业务流程中的活教材。-db.sqlite3文件的存在本身就是一个教学隐喻它提醒学生Web应用的本质是数据的增删改查而Django的价值在于让开发者聚焦于业务规则如“投票后必须增加对应选项的votes值”而非底层SQL语句编写。2.3 功能边界划定不做“全功能”只做“可验证”这个系统刻意规避了诸多“看起来很美”的功能比如用户登录认证、实时投票刷新、图表可视化、邮件通知等。这不是能力不足而是精准的教学克制。以用户认证为例Django自带auth系统虽强大但引入User模型、登录视图、密码哈希、session管理后一个简单的投票流程会被至少5个新概念切割得支离破碎。而本系统将“用户”抽象为匿名访问者所有投票行为基于IP或会话ID进行简单去重可通过修改views.py中的vote()函数添加request.session校验既满足基础防刷需求又不增加认知负荷。再如结果展示它没有使用Chart.js绘制饼图而是用纯HTML表格呈现choice.choice_text和choice.votes因为此时学生的关注点应是“如何从数据库查出数据并传给模板”而非“如何配置JavaScript图表库”。这种功能边界的清醒认知使得整个项目成为一个闭环验证单元你能清晰追踪从URL请求/polls/1/vote/→ 视图处理获取choice_id、更新votes→ 模型持久化save()触发SQL UPDATE→ 模板渲染results.html显示最新数据的完整链路每个环节的输入输出都肉眼可见没有黑盒。3. 核心细节解析与实操要点从模型定义到模板渲染的全流程拆解3.1 models.py用两个模型讲透Django ORM的核心契约polls/models.py是整个系统的数据基石仅20余行代码却浓缩了Django ORM最核心的设计哲学。我们逐行解析from django.db import models class Question(models.Model): question_text models.CharField(max_length200) pub_date models.DateTimeField(date published) def __str__(self): return self.question_textQuestion模型定义了两个字段question_text是CharField对应数据库VARCHAR类型max_length200不仅是长度限制更是Django生成数据库DDL语句的依据——执行migrate时它会创建VARCHAR(200)列。pub_date是DateTimeField括号内的date published是字段的可读名称verbose_name它不会影响数据库但会在Django Admin后台显示为“date published”这是Django“开发者友好”理念的体现。__str__方法返回self.question_text这决定了当在Django Shell中打印Question实例时显示的是问题文本而非Question: Question object (1)这样的无意义标识这是调试时提升效率的关键细节。class Choice(models.Model): question models.ForeignKey(Question, on_deletemodels.CASCADE) choice_text models.CharField(max_length200) votes models.IntegerField(default0) def __str__(self): return self.choice_textChoice模型的关键在于ForeignKey字段。question models.ForeignKey(Question, on_deletemodels.CASCADE)不仅建立了Choice到Question的一对多关系更通过on_deletemodels.CASCADE声明了级联删除策略当某个Question被删除时所有关联的Choice记录将自动清除。这个参数绝非可选Django 2.0强制要求显式指定否则启动时报错。votes models.IntegerField(default0)中的default0是另一个易忽略的要点它定义了数据库层面的默认值CREATE TABLE时生成DEFAULT 0而非Python层面的默认值这意味着即使通过原始SQL插入记录votes字段也会自动填充0。这种“数据库默认值”与“Python默认值”的区分是理解Django ORM与底层数据库交互深度的关键。提示执行python manage.py makemigrations后Django会生成migrations/0001_initial.py文件其中operations列表清晰展示了如何创建这两张表及外键约束。建议学生打开此文件对照models.py逐行阅读这是理解“迁移”本质的最佳途径。3.2 views.pyHTTP请求处理的四种典型模式polls/views.py实现了投票业务的四大核心动作每一种都对应Web开发的经典模式1. 列表展示index—— GET请求的批量数据查询def index(request): latest_question_list Question.objects.order_by(-pub_date)[:5] context {latest_question_list: latest_question_list} return render(request, polls/index.html, context)Question.objects.order_by(-pub_date)中的-号表示降序这是Django ORM特有的语法糖。[:5]看似是Python切片实则是QuerySet的惰性求值优化——Django会将其编译为SELECT ... ORDER BY pub_date DESC LIMIT 5而非先取出全部数据再内存切片。context字典的键latest_question_list必须与模板中{% for question in latest_question_list %}的变量名完全一致这是Django模板上下文传递的硬性约定。2. 单条详情detail—— 带参数的GET请求与异常处理def detail(request, question_id): try: question Question.objects.get(pkquestion_id) except Question.DoesNotExist: raise Http404(Question does not exist) return render(request, polls/detail.html, {question: question})question_id来自URL路由path(int:question_id/, views.detail, namedetail)Django自动将其转换为整数类型。Question.objects.get(pkquestion_id)是精确查询若不存在则抛出DoesNotExist异常raise Http404触发Django内置的404页面。这里没有用get_object_or_404()快捷函数是为了让学生直面异常处理的原始逻辑。3. 结果展示results—— 关联数据的聚合计算def results(request, question_id): question get_object_or_404(Question, pkquestion_id) return render(request, polls/results.html, {question: question})results.html模板中{% for choice in question.choice_set.all %}利用了Django ORM的反向关联查询。由于Choice模型中定义了ForeignKey(Question)Django自动为Question模型添加了choice_set属性all()方法返回所有关联Choice对象。choice.votes的累加计算在模板中完成{{ question.choice_set.aggregate(Sum(votes)) }}需额外导入但本系统采用更直观的方式在模板中遍历并累加强化学生对“数据在模板中如何被消费”的理解。4. 投票处理vote—— POST请求的原子性更新def vote(request, question_id): question get_object_or_404(Question, pkquestion_id) try: selected_choice question.choice_set.get(pkrequest.POST[choice]) except (KeyError, Choice.DoesNotExist): return render(request, polls/detail.html, { question: question, error_message: You didnt select a choice., }) else: selected_choice.votes 1 selected_choice.save() return HttpResponseRedirect(reverse(polls:results, args(question.id,)))这是最易出错的环节。request.POST[choice]从表单中提取name为”choice”的input值若用户未选择直接提交会触发KeyError若提交的choice_id不存在则触发Choice.DoesNotExist。else块中的selected_choice.votes 1看似简单但背后是数据库事务的原子性保证save()方法会生成UPDATE polls_choice SET votes votes 1 WHERE id ?这样的SQL避免了先SELECT再UPDATE可能引发的竞态条件。reverse(polls:results, args(question.id,))动态生成URL确保路由变更时无需硬编码路径这是Django URL解耦设计的精髓。3.3 templatesDjango模板引擎的“所见即所得”教学法模板文件位于templates/polls/它们是Django MVC中“V”的具象化。我们以detail.html为例解析其教学价值h1{{ question.question_text }}/h1 {% if error_message %}pstrong{{ error_message }}/strong/p{% endif %} form action{% url polls:vote question.id %} methodpost {% csrf_token %} {% for choice in question.choice_set.all %} input typeradio namechoice idchoice{{ forloop.counter }} value{{ choice.id }} label forchoice{{ forloop.counter }}{{ choice.choice_text }}/labelbr {% endfor %} input typesubmit valueVote /form{{ question.question_text }}是变量输出{% if error_message %}是模板逻辑控制{% for choice in question.choice_set.all %}是循环迭代{{ forloop.counter }}是Django内置循环变量自动为每个选项生成递增IDchoice1, choice2…这解决了HTML中radio按钮ID唯一性的硬性要求。最关键的是{% url polls:vote question.id %}它根据polls/urls.py中path(int:question_id/vote/, views.vote, namevote)的命名URL动态拼接出/polls/1/vote/这样的路径。这种URL反向解析机制让学生深刻理解“命名URL”不是语法糖而是解耦前端链接与后端路由的基础设施。注意{% csrf_token %}是Django CSRF防护的强制标记。当表单method为POST时Django中间件会检查请求中是否包含有效的CSRF token若缺失则拒绝请求。这是Web安全的基础课学生必须亲手删掉这行代码测试403 Forbidden错误才能真正理解其必要性。4. 实操过程与核心环节实现从零部署到功能验证的完整流水线4.1 环境准备与依赖安装三步走的极简主义部署流程被压缩至三步每一步都经过教学场景验证第一步确认Python环境python --version # 要求 Python 3.8 或更高版本 # 若未安装从 python.org 下载安装包勾选 Add Python to PATH这是最容易被忽视的前提。很多学生因系统自带Python 2.7或版本过低在执行manage.py时直接报错。建议在Sublime Text 3中配置Python构建系统按CtrlB即可运行当前脚本避免频繁切换终端。第二步安装Django与依赖# 创建虚拟环境强烈推荐避免包冲突 python -m venv venv venv\Scripts\activate # Windows # source venv/bin/activate # macOS/Linux # 安装依赖 pip install -r requirements.txt # requirements.txt 内容应为 # Django3.2,5.0 # pytz # 时区支持Django 4.x 后已内置但兼容旧版requirements.txt中Django版本范围3.2,5.0是精心设计的Django 3.2是最后一个支持Python 3.6的长期支持版LTS而Django 4.x要求Python 3.8覆盖了主流教学环境。pytz的保留是为了兼容Django 3.x的时区处理尽管Django 4.x已迁移到zoneinfo但保留它可确保代码在旧环境中无缝运行。第三步数据库初始化与服务启动# 执行数据库迁移创建表结构 python manage.py migrate # 可选创建超级用户用于访问Django Admin python manage.py createsuperuser # 启动开发服务器 python manage.py runserver # 控制台输出 Starting development server at http://127.0.0.1:8000/migrate命令是Django的“魔法开关”。它读取migrations/目录下的迁移文件对比当前数据库状态生成并执行差异化的SQL语句。首次运行时它会创建auth_user、django_admin_log等Django内置表以及polls_question、polls_choice两张业务表。此时db.sqlite3文件大小会从0KB增长到约20KB这就是数据库结构被写入的物理证据。4.2 数据录入与调试用Django Shell和Navicat双轨验证数据录入有两种方式分别对应不同学习目标方式一Django Shell理解ORM操作python manage.py shell from polls.models import Question, Choice from django.utils import timezone q Question(question_textWhats new?, pub_datetimezone.now()) q.save() q.id # 返回 1 q.choice_set.create(choice_textNot much, votes0) q.choice_set.create(choice_textThe sky, votes0) q.choice_set.all() QuerySet [Choice: Not much, Choice: The sky]Shell是Django的“交互式实验室”。学生在这里执行q.choice_set.create()时能立即看到数据库中新增记录q.choice_set.all()返回的QuerySet对象可被遍历这种即时反馈远胜于写完代码再运行查看结果。方式二Navicat Premium可视化数据库状态- 打开Navicat新建SQLite连接路径指向项目根目录的db.sqlite3- 展开polls_question表双击查看数据确认question_text和pub_date值- 切换到polls_choice表观察question_id字段如何关联到Question的id- 右键表名选择“设计表”查看votes字段的默认值是否为0question_id是否设置了外键约束- 手动修改votes值如将id1的votes改为5刷新网页验证结果页面是否同步更新实操心得我要求学生在Navicat中手动执行UPDATE polls_choice SET votes votes 1 WHERE id 1;然后刷新网页观察结果变化。这个操作让他们直观理解Django的save()方法最终只是生成并执行了这条SQL。当他们看到自己写的原始SQL与Django生成的SQL效果一致时“ORM只是SQL封装”的认知豁然开朗。4.3 前端交互调试jQuery代码的逐行剖析与修改实验前端逻辑集中在templates/polls/detail.html的script标签内。我们来解剖这段代码script srchttps://code.jquery.com/jquery-3.6.0.min.js/script script $(document).ready(function() { $(#vote-form).on(submit, function(e) { e.preventDefault(); var formData $(this).serialize(); $.post($(this).attr(action), formData, function(data) { location.reload(); }); }); }); /script$(document).ready()确保DOM加载完毕后再执行脚本避免元素未找到错误。$(#vote-form)通过ID选择表单e.preventDefault()阻止表单默认提交否则页面会跳转。$(this).serialize()将所有input、select、textarea元素序列化为choice2这样的字符串。$.post()发起异步POST请求$(this).attr(action)动态获取表单action属性值即/polls/1/vote/。成功回调中location.reload()强制刷新页面这是最朴素的结果更新方式。修改实验- 将location.reload()替换为alert(Vote submitted!);观察页面不刷新但提示弹出理解AJAX的异步特性- 在$.post()后添加console.log(formData);打开浏览器开发者工具Console面板查看实际发送的数据- 删除e.preventDefault()观察页面跳转到空白响应页理解默认表单提交与AJAX提交的根本区别5. 常见问题与排查技巧实录那些年我们踩过的坑与填坑指南5.1 启动报错排查从ImportError到OperationalError的速查手册错误现象根本原因解决方案教学启示ModuleNotFoundError: No module named djangoDjango未安装或未激活虚拟环境执行pip install django确认venv\Scripts\activate已运行理解Python包作用域全局安装 vs 虚拟环境隔离django.core.exceptions.ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured.manage.py未正确指向settings模块检查manage.py中os.environ.setdefault(DJANGO_SETTINGS_MODULE, mysite.settings)是否指向正确路径Django配置模块的加载机制环境变量DJANGO_SETTINGS_MODULE是入口钥匙django.db.utils.OperationalError: no such table: polls_question未执行python manage.py migrate或迁移文件损坏删除migrations/目录下除__init__.py外的所有文件重新运行makemigrations和migrate理解迁移文件的版本控制本质它是数据库schema的Git commit日志django.core.exceptions.ValidationError: [Enter a valid date.]pub_date字段在Shell中未赋值或格式错误在Shell中使用timezone.now()生成有效datetime对象而非字符串Django字段校验的触发时机save()时校验非数据库层面注意当遇到no such table错误时不要直接删除db.sqlite3文件正确做法是先备份该文件再执行migrate。因为db.sqlite3中可能已存有测试数据而迁移文件只负责结构变更不删除已有数据。5.2 投票功能失效POST请求与CSRF的深度调试最常见的问题是点击“Vote”按钮后页面无反应或报403错误。排查路径如下第一步检查浏览器开发者工具Network标签页- 点击Vote按钮观察是否有/polls/1/vote/的POST请求发出- 若无请求说明jQuery事件绑定失败检查form是否有idvote-form或jQuery脚本是否在DOM加载前执行第二步检查请求Headers- 查看Request Headers中是否有X-CSRFToken字段- 若缺失确认模板中{% csrf_token %}标签是否被注释或删除第三步检查响应内容- 若响应状态码为403 Forbidden且响应体为Forbidden (403)基本确定CSRF token问题- 若响应状态码为200但页面未刷新检查$.post()回调函数中location.reload()是否被阻塞如浏览器禁用了JS终极验证法在views.py的vote()函数开头添加print(Received POST data:, request.POST) print(CSRF token in headers:, request.META.get(HTTP_X_CSRFTOKEN))运行服务器点击Vote在终端查看打印信息。若request.POST为空说明表单未正确提交若HTTP_X_CSRFTOKEN为None说明CSRF token未随请求发送。5.3 模板渲染异常变量未显示与循环失效的定位技巧当{{ question.question_text }}显示为空白或{% for choice in question.choice_set.all %}循环不执行时变量未显示的排查- 在views.py中return render()前添加print(Context:, context)确认context字典中键名拼写正确如question而非Question- 在模板中添加{{ question|pprint }}查看question对象的完整结构确认其是否为None或空对象循环失效的排查- 在Shell中执行Question.objects.get(id1).choice_set.all()确认数据库中确实存在关联Choice- 检查Choice模型中ForeignKey的on_delete参数是否为models.CASCADE若误设为models.DO_NOTHING且数据库外键约束未启用可能导致关联查询失败- 在模板中添加{{ question.choice_set.count }}若输出0说明关联数据为空问题在数据录入环节而非模板语法5.4 Navicat连接故障SQLite文件锁定与权限问题使用Navicat打开db.sqlite3时常见问题问题Unable to open database file-原因Django开发服务器正在运行SQLite数据库被独占锁定-解决停止runserver进程CtrlC再用Navicat打开或在Navicat连接设置中勾选“允许共享读取”问题database is locked-原因多个进程同时写入SQLite如Shell中执行save()的同时Navicat在修改数据-解决关闭所有访问数据库的程序重启Navicat或在Django中设置OPTIONS: {timeout: 20}延长锁等待时间需修改settings.py的DATABASES配置问题Navicat中修改数据后网页不更新-原因Django开发服务器启用了DEBUGTrue但模板缓存未刷新-解决重启runserver或在settings.py中添加TEMPLATES: [{BACKEND: django.template.backends.django.DjangoTemplates, OPTIONS: {debug: True}}]强制模板重载6. 功能扩展与教学延伸从基础投票到可交付项目的五种升级路径6.1 单选/多选逻辑增强修改模型与视图的最小改动方案当前系统默认单选若需支持多选如“你最喜欢的三种编程语言”只需两处改动模型层# polls/models.py class Question(models.Model): # ...原有字段 is_multiple_choice models.BooleanField(defaultFalse) # 新增字段执行python manage.py makemigrations和migrate为Question表添加is_multiple_choice布尔字段。视图层# polls/views.py def vote(request, question_id): question get_object_or_404(Question, pkquestion_id) if question.is_multiple_choice: # 多选request.POST.getlist(choice) 获取所有选中项 choice_ids request.POST.getlist(choice) for choice_id in choice_ids: try: choice question.choice_set.get(pkchoice_id) choice.votes 1 choice.save() except Choice.DoesNotExist: pass else: # 原有单选逻辑 try: selected_choice question.choice_set.get(pkrequest.POST[choice]) except (KeyError, Choice.DoesNotExist): # ...错误处理 else: selected_choice.votes 1 selected_choice.save() return HttpResponseRedirect(reverse(polls:results, args(question.id,)))模板层!-- templates/polls/detail.html -- {% if question.is_multiple_choice %} {% for choice in question.choice_set.all %} input typecheckbox namechoice value{{ choice.id }} {{ choice.choice_text }}br {% endfor %} {% else %} {% for choice in question.choice_set.all %} input typeradio namechoice value{{ choice.id }} {{ choice.choice_text }}br {% endfor %} {% endif %}实操心得这个扩展案例教会学生“渐进式开发”的思维。它没有重构整个系统而是通过新增一个布尔字段让同一套视图逻辑根据业务规则动态分支。这种设计模式在真实项目中极为常见也是Django“约定优于配置”哲学的生动体现。6.2 投票结果可视化用纯CSS实现简易柱状图不引入JavaScript图表库仅用CSS实现结果可视化!-- templates/polls/results.html -- {% for choice in question.choice_set.all %} div classchoice-row div classchoice-label{{ choice.choice_text }}/div div classchoice-bar stylewidth: {{ choice.votes|add:0 }}%;/div div classchoice-votes{{ choice.votes }} votes/div /div {% endfor %}/* static/css/style.css */ .choice-row { margin: 10px 0; } .choice-label { display: inline-block; width: 150px; } .choice-bar { display: inline-block; height: 20px; background-color: #4CAF50; vertical-align: middle; } .choice-votes { display: inline-block; width: 50px; text-align: right; margin-left: 10px; }{{ choice.votes|add:0 }}是Django模板过滤器将votes值转换为字符串并拼接”0”实际效果是{{ choice.votes }}%。此处用add:0是为了避免votes为0时宽度为0%导致条形图不可见的问题。这种“用CSS替代JS”的方案让学生理解前端表现层与业务逻辑层的分离原则。6.3 防重复投票基于Session的轻量级去重机制在views.py的vote()函数中添加def vote(request, question_id): # ...原有代码 # 新增基于session的投票记录 if voted_questions not in request.session: request.session[voted_questions] [] if question_id not in request.session[voted_questions]: # 执行投票逻辑 request.session[voted_questions].append(question_id) request.session.modified True # 标记session已修改 else: return render(request, polls/detail.html, { question: question, error_message: You have already voted on this question., }) # ...原有重定向逻辑request.session.modified True是关键它告诉Django session数据已变更必须保存回数据库SQLite中的django_session表。这种基于session的去重无需额外数据库表适合小型场景且学生能清晰追踪request.session字典的读写过程。6.4 管理后台定制Django Admin的快速赋能在polls/admin.py中添加from django.contrib import admin from .models import Question, Choice admin.register(Question) class QuestionAdmin(admin.ModelAdmin): list_display (question_text, pub_date, was_published_recently) list_filter [pub_date] search_fields [question_text] admin.register(Choice) class ChoiceAdmin(admin.ModelAdmin): list_display (choice_text, question, votes) list_filter [question]was_published_recently是Question模型的方法需在models.py中定义def was_published_recently(self): now timezone.now() return now - datetime.timedelta(days1) self.pub_date now was_published_recently.admin_order_field pub_date was_published_recently.boolean True was_published_recently.short_description Published recently?执行python manage.py createsuperuser创建管理员账号后访问/admin/即可看到定制化的后台界面。list_display控制列表页显示字段list_filter添加右侧筛选栏search_fields启用顶部搜索框。这是Django“开箱即用”生产力的集中体现学生只需几行代码就获得了一个功能完备的数据管理界面。6.5 部署到共享主机SQLite方案的生产化妥协虽然SQLite不适合高并发生产环境但在学生作品集展示、社团内部投票等场景仍可部署到廉价共享主机。关键步骤1. 修改settings.pyimport os from pathlib import Path BASE_DIR Path(__file__).resolve().parent.parent.parent # 向上三级到项目根目录 DATABASES { default: { ENGINE: django.db.backends.sqlite3, NAME: BASE_DIR / db.sqlite3, # 使用Path对象确保跨平台路径正确 } }2. 配置静态文件STATIC_URL /static/ STATIC_ROOT BASE_DIR / staticfiles # 收集所有静态文件到此目录 # 运行 python manage.py collectstatic 收集静态文件3. 主机要求- 确认主机支持Python 3.8和mod_wsgi或uWSGI- 将db.sqlite3文件权限设为644可读写staticfiles目录设为755-.htaccess文件Apache添加IfModule mod_rewrite.c RewriteEngine On RewriteBase / RewriteRule ^static/(.*)$ /staticfiles/$1 [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ /myapp/wsgi.py/$1 [L] /IfModule最后分享一个小技巧我在指导学生部署时会让他们先在本地用python manage.py runserver 0.0.0.0:8000启动并用手机访问http://[电脑IP]:8000/polls/模拟真实网络环境。这能提前暴露ALLOWED_HOSTS配置问题——若未将*或具体IP加入ALLOWED_HOSTS手机访问会报DisallowedHost错误。这个小实验让学生第一次体会到“开发环境”与“部署环境”的真实鸿沟。本文还有配套的精品资源点击获取简介基于Django 3.x/4.x开发的在线投票系统纯Python实现不依赖Java或外部服务。后端用Django框架处理业务逻辑前端采用原生HTML搭配jQuery完成交互无需复杂构建工具。数据库使用内置SQLitedb.sqlite3文件直接放在项目根目录开箱即用省去配置步骤。项目结构标准清晰含polls应用模块、mysite主站点配置、manage.py和django-admin.py管理脚本支持通过python manage.py runserver快速本地运行。适配Sublime Text 3编辑环境数据库可配合Navicat Premium可视化查看与调试。功能涵盖用户浏览问题、选择选项、提交投票、实时查看统计结果等全流程支持多问题、多选项、单选/多选基础逻辑。适合高校课程设计、Python Web教学演示、社团活动或内部小型调研场景快速部署落地。本文还有配套的精品资源点击获取

相关新闻