ARTICLE DETAIL

资讯详情

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

Django投票系统开发实战:从入门到部署

Django投票系统开发实战:从入门到部署 1. 项目概述作为一名使用Django框架开发过多个生产级项目的工程师我经常被问到如何快速入门这个强大的Python Web框架。今天我就用最经典的投票应用案例带大家从零开始构建一个完整的Django项目。这个教程不仅包含基础功能实现还会分享我在实际开发中积累的宝贵经验。投票系统是Django官方文档推荐的入门项目因为它完美展示了Django的核心功能模型定义Model、后台管理Admin、视图控制View和模板渲染Template。通过这个项目新手可以在2-3小时内掌握Django的基础开发流程而有经验的开发者也能学到一些优化技巧。2. 环境准备与项目创建2.1 开发环境配置我推荐使用Python 3.8版本这是目前大多数生产环境采用的稳定版本。使用虚拟环境是Python开发的必备实践python -m venv venv source venv/bin/activate # Linux/Mac venv\Scripts\activate # Windows安装Django时建议使用最新LTS版本目前是4.2.x同时安装常用辅助工具pip install django4.2.5 pip install ipython # 增强版shell注意不要直接使用系统Python环境虚拟环境可以避免包冲突问题。我在多个项目中都遇到过因环境混乱导致的奇怪bug。2.2 创建Django项目使用标准命令创建项目和应用django-admin startproject pollsite cd pollsite python manage.py startapp polls项目结构应该如下pollsite/ manage.py pollsite/ __init__.py settings.py urls.py asgi.py wsgi.py polls/ __init__.py admin.py apps.py migrations/ models.py tests.py views.py关键配置修改在settings.py的INSTALLED_APPS中添加polls设置TIME_ZONE Asia/Shanghai配置数据库默认SQLite即可入门3. 数据模型设计3.1 定义核心模型投票应用最核心的两个模型是Question问题和Choice选项。在polls/models.py中from django.db import models from django.utils import timezone class Question(models.Model): question_text models.CharField(max_length200) pub_date models.DateTimeField(date published, defaulttimezone.now) def __str__(self): return self.question_text def was_published_recently(self): return self.pub_date timezone.now() - datetime.timedelta(days1) 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_text模型设计要点使用ForeignKey建立一对多关系设置合理的max_length限制为模型添加__str__方法方便后台显示使用timezone.now()而非datetime.now()处理时区3.2 数据库迁移执行以下命令创建数据库表python manage.py makemigrations polls python manage.py migrate经验开发过程中每次修改模型后都要执行这两条命令。我在团队协作中见过多次因忘记迁移导致的数据库不一致问题。4. 后台管理配置4.1 创建超级用户python manage.py createsuperuser按提示输入用户名、邮箱和密码。建议使用复杂密码即使是在开发环境。4.2 注册模型到Admin在polls/admin.py中from django.contrib import admin from .models import Question, Choice class ChoiceInline(admin.TabularInline): model Choice extra 3 class QuestionAdmin(admin.ModelAdmin): fieldsets [ (None, {fields: [question_text]}), (Date information, {fields: [pub_date], classes: [collapse]}), ] inlines [ChoiceInline] list_display (question_text, pub_date, was_published_recently) list_filter [pub_date] search_fields [question_text] admin.site.register(Question, QuestionAdmin)后台优化技巧使用TabularInline在问题页面直接编辑选项设置list_display自定义列表页显示字段添加list_filter实现快速筛选配置search_fields启用搜索功能5. 视图与URL配置5.1 编写基础视图在polls/views.py中from django.http import HttpResponse from .models import Question def index(request): latest_question_list Question.objects.order_by(-pub_date)[:5] output , .join([q.question_text for q in latest_question_list]) return HttpResponse(output) def detail(request, question_id): return HttpResponse(fYoure looking at question {question_id}.) def results(request, question_id): return HttpResponse(fYoure looking at the results of question {question_id}.) def vote(request, question_id): return HttpResponse(fYoure voting on question {question_id}.)5.2 配置URL路由在polls目录下创建urls.pyfrom django.urls import path from . import views urlpatterns [ path(, views.index, nameindex), path(int:question_id/, views.detail, namedetail), path(int:question_id/results/, views.results, nameresults), path(int:question_id/vote/, views.vote, namevote), ]然后在项目级的urls.py中包含它from django.contrib import admin from django.urls import include, path urlpatterns [ path(polls/, include(polls.urls)), path(admin/, admin.site.urls), ]URL设计原则使用 int:question_id 捕获参数为每个路由命名name参数使用include()实现URL解耦6. 模板系统实现6.1 创建模板目录在polls目录下创建templates/polls目录Django会自动在这个位置查找模板。6.2 编写基础模板创建base.html作为基础模板!DOCTYPE html html head title{% block title %}Polls App{% endblock %}/title /head body div idcontent {% block content %}{% endblock %} /div /body /html6.3 实现各页面模板index.html:{% extends polls/base.html %} {% block title %}Latest Questions{% endblock %} {% block content %} {% if latest_question_list %} ul {% for question in latest_question_list %} lia href{% url polls:detail question.id %}{{ question.question_text }}/a/li {% endfor %} /ul {% else %} pNo polls are available./p {% endif %} {% endblock %}detail.html:{% extends polls/base.html %} {% block title %}{{ question.question_text }}{% endblock %} {% block content %} 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 {% endblock %}模板使用技巧使用模板继承减少重复代码总是添加csrf_token保护表单使用url模板标签而非硬编码URL合理使用模板标签和过滤器7. 完善视图逻辑7.1 改进视图函数更新polls/views.pyfrom django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render 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: You didnt select a choice., }) 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})7.2 添加结果页面模板results.html:{% extends polls/base.html %} {% block title %}Results: {{ question.question_text }}{% endblock %} {% block content %} h1{{ question.question_text }}/h1 ul {% for choice in question.choice_set.all %} li{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}/li {% endfor %} /ul a href{% url polls:detail question.id %}Vote again?/a {% endblock %}视图优化点使用get_object_or_404简化错误处理使用render快捷方式渲染模板实现完整的投票逻辑使用HttpResponseRedirect防止重复提交8. 测试与调试8.1 编写单元测试在polls/tests.py中import datetime from django.test import TestCase from django.utils import timezone from .models import Question class QuestionModelTests(TestCase): def test_was_published_recently_with_future_question(self): time timezone.now() datetime.timedelta(days30) future_question Question(pub_datetime) self.assertIs(future_question.was_published_recently(), False) def test_was_published_recently_with_old_question(self): time timezone.now() - datetime.timedelta(days2) old_question Question(pub_datetime) self.assertIs(old_question.was_published_recently(), False) def test_was_published_recently_with_recent_question(self): time timezone.now() - datetime.timedelta(hours23) recent_question Question(pub_datetime) self.assertIs(recent_question.was_published_recently(), True)运行测试python manage.py test polls8.2 调试技巧使用Django的debug页面分析错误在settings.py中设置DEBUG True开发时启用详细错误信息使用print()或logging输出调试信息使用Django shell进行交互式调试python manage.py shell经验测试覆盖率应该至少达到70%。我在实际项目中见过太多因缺少测试导致的线上问题。9. 部署准备9.1 生产环境设置修改settings.pyDEBUG False ALLOWED_HOSTS [yourdomain.com, localhost] STATIC_ROOT os.path.join(BASE_DIR, staticfiles)收集静态文件python manage.py collectstatic9.2 常用部署方式Nginx Gunicornpip install gunicorn gunicorn pollsite.wsgi:application使用Docker容器化FROM python:3.8 WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD [gunicorn, pollsite.wsgi:application, --bind, 0.0.0.0:8000]平台即服务PaaSHerokuPythonAnywhereRailway部署建议首次部署建议使用PythonAnywhere免费方案练手生产环境推荐使用NginxGunicorn组合。10. 性能优化建议10.1 数据库优化使用select_related和prefetch_related减少查询次数Question.objects.select_related(choice_set).all()添加数据库索引class Question(models.Model): pub_date models.DateTimeField(date published, db_indexTrue)10.2 缓存策略视图缓存from django.views.decorators.cache import cache_page cache_page(60 * 15) # 15分钟缓存 def index(request): # ...模板片段缓存{% load cache %} {% cache 500 sidebar %} .. sidebar content .. {% endcache %}10.3 异步任务使用Celery处理耗时操作from celery import shared_task shared_task def process_vote(choice_id): choice Choice.objects.get(pkchoice_id) choice.votes 1 choice.save()11. 常见问题解决数据库表不存在检查是否执行了migrate命令确认模型是否已注册到INSTALLED_APPS模板找不到确认模板是否放在正确的templates目录下检查settings.py中的TEMPLATES配置静态文件404开发时确保DEBUGTrue生产环境运行collectstatic检查STATIC_URL和STATIC_ROOT配置CSRF验证失败确保表单中包含{% csrf_token %}检查Cookie设置性能问题使用Django Debug Toolbar分析查询检查是否使用了N1查询问题12. 项目扩展方向用户认证系统集成Django内置的auth系统添加登录/注册功能REST API开发使用Django REST framework创建投票API端点实时功能使用Channels添加WebSocket支持实时显示投票结果前端改进使用Vue/React构建更动态的界面添加图表展示投票结果高级功能投票限制IP/用户问卷功能扩展数据分析报表这个投票应用虽然简单但涵盖了Django开发的各个方面。我在实际项目中总结的经验是初期重点应该放在理解Django的MTV模式上而不是追求复杂功能。掌握了这些基础后扩展功能就会水到渠成。
返回列表