ARTICLE DETAIL

资讯详情

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

Django投票项目源码解析:模型、视图与模板的完整实践

Django投票项目源码解析:模型、视图与模板的完整实践 简介基于Python的在线投票网站设计源码是一份面向Web开发初学者的完整项目帮助理解投票功能的用户界面、后端逻辑与数据存储实现。压缩包共39个文件约693KB核心包含14个Python源码文件、11个pyc字节码文件、4个XML配置文件并配有HTML页面、CSS样式、SQLite数据库及项目管理文件基本涵盖小型Web项目从页面展示到数据落地的典型模块。项目目录结构清晰涉及投票交互、数据表设计、后台管理等环节整体可直接运行调试也可作为课程设计或毕业设计的参考蓝本。目前已有579人学习下载适合希望借助实际案例掌握Python Web开发、熟悉项目组织方式的学习者可从中获得完整的源码结构、静态资源与数据库设计思路便于后续二次开发与功能扩展。1. 为什么这份 Django 投票项目值得你拆一遍如果你在一个没有图形界面的 Linux 服务器上临时要给内部技术分享会做一个在线投票又不想把数据散落在 Excel 里这份基于 Python 的在线投票网站设计源码就是很典型的入门模板。它由 pollsdemo 项目、polls 投票应用和 SQLite 数据库组成模型层只有 Question 和 Choice 两张表却完整覆盖了“发布问题、填写选项、提交投票、统计票数”这一条闭环逻辑。新手可以用它打通 Django 的 MTV 开发路径老手可以拿它当“快速原型”的最小骨架重点关注 ORM 外键查询和模板层表单提交这两处关键设计。2. 项目骨架与数据模型从 settings.py 到 models.py2.1 目录结构读法哪些文件真正影响投票业务下载解压后第一眼看到的是两级目录外层的 pollsdemo 是项目配置目录内层的 polls 是投票应用templates 和 static 放在根目录db.sqlite3 已经在发布包里说明作者在打包前跑过迁移并保存过数据。真正决定投票业务的是 polls 目录下的 models.py、views.py、urls.py 三个文件admin.py 负责把模型注册进 Django 自带后台migrations 目录记录每次模型变更settings.py 里的 INSTALLED_APPS 决定 Django 启动时加载哪些应用。老手拿到这种包的习惯是先看 models.py 和 settings.py不看前端因为投票的核心是模型关系。文件路径职责二次开发关注点manage.pyDjango 命令行入口不用改pollsdemo/settings.py应用注册、数据库、模板、静态资源配置部署时必改pollsdemo/urls.py项目根路由指向 polls.urls按需扩展polls/models.pyQuestion / Choice 数据模型改表结构必动polls/views.py投票业务逻辑改交互逻辑必动db.sqlite3自带演示数据的 SQLite 库可保留或重建templates/polls/*.html三个页面模板改页面必动2.2 数据模型设计Question 与 Choice 的表关系Question 每行是一条投票Choice 每行是一个选项一个投票对应多个选项所以 Choice 用外键指向 Question。删除投票时选项没有独立存在的意义因此on_deletemodels.CASCADE让级联删除是合理的。votes 字段放在 Choice 而不是 Question是因为每次投票只针对某个选项加一票如果放在 Question 上就得额外维护一个计数器查票时还得再聚合一次不如直接冗余在选项行里干净。2.3 第一次迁移把模型变成 SQLite 表拿到源码后先别急着打开页面先让数据库和模型对上。在项目根目录执行python manage.py makemigrations polls python manage.py migrate第一条命令让 Django 对比 models.py 和 migrations 目录生成新的迁移文件第二条命令把迁移真正落进 db.sqlite3。迁移文件是纯 Python打开 migrations 目录里的 0001_initial.py 能看到每一张表的字段和约束这就是“表结构即代码”的具体体现。如果 db.sqlite3 已经存在且没有改动模型这两步会提示没有任何变更属正常现象。2.4 关键代码models.py 完整解读我拆这份源码时models.py 的核心逻辑和 Django 官方投票教程一脉相承from django.db import models class Question(models.Model): question_text models.CharField(max_length200, verbose_name投票标题) pub_date models.DateTimeField(verbose_name发布日期) def __str__(self): return self.question_text class Choice(models.Model): question models.ForeignKey(Question, on_deletemodels.CASCADE, verbose_name所属投票) choice_text models.CharField(max_length200, verbose_name选项内容) votes models.IntegerField(default0, verbose_name票数) def __str__(self): return self.choice_text__str__的返回值会在 Django 后台的列表页直接显示所以它不单纯是调试工具还会影响下拉框里看到的内容。ForeignKey(Question, on_deletemodels.CASCADE)的写法从 Django 2.0 之后强制要求带上 on_delete 参数如果少了它makemigrations 会直接抛 TypeError。votes 用IntegerField(default0)而不是PositiveIntegerField这里其实留了一点隐患——并发提交时可能出现负数虽然单机小流量下几乎碰不到但二开时建议改成PositiveIntegerField更严谨。3. 投票业务逻辑views.py、路由与表单提交3.1 urls.py 路由设计URL 到视图的映射polls 应用内部的 urls.py 决定了用户访问哪些路径会触发哪个函数from django.urls import path from . import views app_name polls urlpatterns [ path(, views.index, nameindex), path(int:question_id/, views.detail, namedetail), path(int:question_id/vote/, views.vote, namevote), path(int:question_id/results/, views.results, nameresults), ]int:question_id是路径转换器它把 URL 里的数字部分抓出来作为参数传给视图函数类型强制为 int。app_name polls配合 name 构成命名空间模板里{% url polls:detail question.id %}才能在多个应用共存时精确找到路由。3.2 视图函数index、detail、vote、results 的分工四个视图函数各管一段请求其中 vote 是整条业务链路上最关键的一步from django.shortcuts import render, get_object_or_404 from django.http import HttpResponseRedirect from django.urls import reverse from .models import Question, Choice 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) def detail(request, question_id): question get_object_or_404(Question, pkquestion_id) return render(request, polls/detail.html, {question: question}) 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: 你没有选择任何选项。, }) else: selected_choice.votes 1 selected_choice.save() return HttpResponseRedirect(reverse(polls:results, args(question.id,))) def results(request, question_id): question get_object_or_404(Question, pkquestion_id) return render(request, polls/results.html, {question: question})视图函数触发 URL请求方式核心动作index/polls/GET取最新 5 条投票渲染列表detail/polls/ /GET取单条投票渲染选项表单vote/polls/ /vote/POST票数 1重定向到结果页results/polls/ /results/GET渲染各选项票数3.3 表单提交与 CSRF 防护vote 函数写在 try 块里的request.POST[choice]是个值得玩味的地方。它直接按下标取值如果请求里没有 choice 字段就会抛 KeyError所以后面必须接 except。这里用了(KeyError, Choice.DoesNotExist)二元组用户没选选项时请求里没有 choice 字段选了但 id 不存在比如手改 URL时 get 查不到对象两种情况都兜住了。投票成功之后没有 render而是先修改内存里的 votes再调用 save() 写库最后用 HttpResponseRedirect 跳走。这一步非常关键如果直接 render 一个模板用户按 F5 刷新时浏览器会重新提交上一次的 POST票数就重复加了。Django 要求所有 POST 表单在模板里带上{% csrf_token %}视图层没有额外写校验代码因为它被封装在 CsrfViewMiddleware 里这个中间件默认在 settings.py 的 MIDDLEWARE 中启用。3.4 参数说明request.POST.get 与 get_object_or_404request.POST[choice]和request.POST.get(choice)的区别在于前者取不到键时抛异常后者返回 NoneNone 不能直接放进 ORM 查询里否则会拿到一个不存在的查询条件。get_object_or_404(Question, pkquestion_id)是Question.objects.get(pkquestion_id)加 try/except 的语法糖查不到时自动抛 404 页面。小项目用语法糖没问题但接口类场景我会更倾向于返回 JSON 消息而不是直接 404因为前端 ajax 拿到 404 后不好区分“投票不存在”和“路径写错了”。4. 前端页面与交互Django 模板与静态资源4.1 模板语法边界{{ }} 与 {% %} 的边界Django 模板不是 HTML它是带服务端渲染逻辑的 HTML 骨架。{{ question.question_text }}做的是变量输出{% if latest_question_list %}和{% for %}做的是流程控制。跟 Vue 那种前端渲染相反这些标签在服务端就执行完了浏览器收到的已经是渲染后的纯 HTML这也是 Django 模板比前后端分离方案更省事的地方——不需要跨域、不需要 token 刷新刷新页面就是最新数据。模板变量只支持点号导航和隐式调用不支持在模板里写函数参数比如{% url polls:detail question.id %}是少数能带参数的标签。4.2 三个模板的结构与表单 actiondetail 页面是用户真正提交投票的入口模板写法如下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 value投票 /formforloop.counter是模板内置的循环计数器从 1 开始用来生成 choice1、choice2 这种唯一 id配合 label 的 for 属性可以做到点击文字也能选中单选按钮。radio 的 value 是选项的数据库主键 id提交后 vote 视图拿到的就是这个 id。三个页面各司其职index 负责列出投票入口detail 负责收集选择results 负责展示票数。4.3 静态文件引入与路径静态文件在根目录 static 下模板里需要先加载静态资源标签再引用{% load static %} link relstylesheet typetext/css href{% static css/style.css %}这里的 static 标签会根据 settings.py 里的 STATIC_URL 拼接出实际请求路径。这份源码把静态目录放在项目根目录而不是某个 app 内部所以在本地开发环境下要在 settings.py 里配置 STATICFILES_DIRSSTATIC_URL /static/ STATICFILES_DIRS [BASE_DIR / static]不配这一项页面能打开但 css 永远 404。部署到 nginx 之后还要把静态文件收敛到根目录再让 nginx 直接托管让 Django 在生产环境处理静态文件性能很差。4.4 模板里 url 反转的坑模板里用{% url polls:detail question.id %}而不是硬编码/polls/1/好处是改路由时不用翻模板。坑在于冒号前面的 polls 必须和 urls.py 里的 app_name 一致少写一层就会报 NoReverseMatch。这类报错在本地调试时很容易看出来但页面多起来后容易和 URL 写错混在一起我一般会用python manage.py show_urls之类的插件先把全站路由导出确认命名空间有没有写对再排查模板。5. 跑起来要过的三关环境、迁移与管理后台5.1 虚拟环境与依赖如果你是刚走完 python 入门教程的新手先用官方安装包装好 Python 3.x再把 python 和 pip 加进环境变量然后回来建虚拟环境python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install django python manage.py runserver 0.0.0.0:8000老手遇到最多的问题是系统里同时存在多个 Python 版本python 命令指向的是 Python 2 或者没进 PATH。用python -c import django; print(django.get_version())能直接验证当前环境有没有装 Django。这个项目依赖非常轻核心就 django 一个包不需要 requirements.txt 也能跑。5.2 迁移与反向检查跑完第 2 章的迁移命令后可以用 sqlite3 客户端反向确认表结构sqlite3 db.sqlite3 .schema polls_choice sqlite3 db.sqlite3 select * from polls_choice;看到 id、question_id、choice_text、votes 四个字段就说明模型建对了。也可以直接打开 admin 后台对比省得记 SQL 语法。如果改过 models.py 但迁移报错先看 migrations 目录里有没有旧的冲突文件最常见的坑是改字段类型后没有生成新迁移Django 在 migrate 时会因为数据库已有旧结构而抛 OperationalError。5.3 管理后台与数据录入用自带后台录数据比手写 SQL 方便得多python manage.py createsuperuser python manage.py runserver浏览器访问 /admin/登录后就能看到 Questions 和 Choices 两个表。admin.py 里只是简单注册了模型没有做 inline 嵌套所以新增一个投票要先进 Question 建标题再进 Choice 逐个加选项操作略显繁琐。二开时可以用 TabularInline 把 Choice 嵌到 Question 的后台编辑页里代码如下from django.contrib import admin from .models import Question, Choice class ChoiceInline(admin.TabularInline): model Choice extra 3 class QuestionAdmin(admin.ModelAdmin): inlines [ChoiceInline] admin.site.register(Question, QuestionAdmin)5.4 改静态路径与上线前配置本地能跑通后部署前把 settings.py 里的 DEBUG 改为 False顺便把 ALLOWED_HOSTS 加上服务器域名。然后执行python manage.py collectstatic它会按 STATICFILES_DIRS 和各个 app 的 static 目录把所有静态文件收敛到 STATIC_ROOT这一步漏掉的话页面会丢失全部样式。整理完这些在服务器上运行python manage.py runserver 0.0.0.0:8000浏览器访问/polls/看到投票列表整个闭环就算完整跑通了。本文还有配套的精品资源点击获取
返回列表