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 django==4.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

关键配置修改:

  1. 在settings.py的INSTALLED_APPS中添加'polls'
  2. 设置TIME_ZONE = 'Asia/Shanghai'
  3. 配置数据库(默认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_length=200) pub_date = models.DateTimeField('date published', default=timezone.now) def __str__(self): return self.question_text def was_published_recently(self): return self.pub_date >= timezone.now() - datetime.timedelta(days=1) class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0) 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(f"You're looking at question {question_id}.") def results(request, question_id): return HttpResponse(f"You're looking at the results of question {question_id}.") def vote(request, question_id): return HttpResponse(f"You're voting on question {question_id}.")

5.2 配置URL路由

在polls目录下创建urls.py:

from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), path('<int:question_id>/', views.detail, name='detail'), path('<int:question_id>/results/', views.results, name='results'), path('<int:question_id>/vote/', views.vote, name='vote'), ]

然后在项目级的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 id="content"> {% block content %}{% endblock %} </div> </body> </html>

6.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 %} <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li> {% endfor %} </ul> {% else %} <p>No 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 %}<p><strong>{{ error_message }}</strong></p>{% endif %} <form action="{% url 'polls:vote' question.id %}" method="post"> {% csrf_token %} {% for choice in question.choice_set.all %} <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}"> <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br> {% endfor %} <input type="submit" value="Vote"> </form> {% endblock %}

模板使用技巧:

  • 使用模板继承减少重复代码
  • 总是添加csrf_token保护表单
  • 使用url模板标签而非硬编码URL
  • 合理使用模板标签和过滤器

7. 完善视图逻辑

7.1 改进视图函数

更新polls/views.py:

from 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, pk=question_id) return render(request, 'polls/detail.html', {'question': question}) def vote(request, question_id): question = get_object_or_404(Question, pk=question_id) try: selected_choice = question.choice_set.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): return render(request, 'polls/detail.html', { 'question': question, 'error_message': "You didn't 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, pk=question_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(days=30) future_question = Question(pub_date=time) self.assertIs(future_question.was_published_recently(), False) def test_was_published_recently_with_old_question(self): time = timezone.now() - datetime.timedelta(days=2) old_question = Question(pub_date=time) self.assertIs(old_question.was_published_recently(), False) def test_was_published_recently_with_recent_question(self): time = timezone.now() - datetime.timedelta(hours=23) recent_question = Question(pub_date=time) self.assertIs(recent_question.was_published_recently(), True)

运行测试:

python manage.py test polls

8.2 调试技巧

  1. 使用Django的debug页面分析错误
  2. 在settings.py中设置DEBUG = True开发时启用详细错误信息
  3. 使用print()或logging输出调试信息
  4. 使用Django shell进行交互式调试:
python manage.py shell

经验:测试覆盖率应该至少达到70%。我在实际项目中见过太多因缺少测试导致的线上问题。

9. 部署准备

9.1 生产环境设置

修改settings.py:

DEBUG = False ALLOWED_HOSTS = ['yourdomain.com', 'localhost'] STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

收集静态文件:

python manage.py collectstatic

9.2 常用部署方式

  1. Nginx + Gunicorn:
pip install gunicorn gunicorn pollsite.wsgi:application
  1. 使用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"]
  1. 平台即服务(PaaS):
  • Heroku
  • PythonAnywhere
  • Railway

部署建议:首次部署建议使用PythonAnywhere免费方案练手,生产环境推荐使用Nginx+Gunicorn组合。

10. 性能优化建议

10.1 数据库优化

  1. 使用select_related和prefetch_related减少查询次数:
Question.objects.select_related('choice_set').all()
  1. 添加数据库索引:
class Question(models.Model): pub_date = models.DateTimeField('date published', db_index=True)

10.2 缓存策略

  1. 视图缓存:
from django.views.decorators.cache import cache_page @cache_page(60 * 15) # 15分钟缓存 def index(request): # ...
  1. 模板片段缓存:
{% 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(pk=choice_id) choice.votes += 1 choice.save()

11. 常见问题解决

  1. 数据库表不存在:
  • 检查是否执行了migrate命令
  • 确认模型是否已注册到INSTALLED_APPS
  1. 模板找不到:
  • 确认模板是否放在正确的templates目录下
  • 检查settings.py中的TEMPLATES配置
  1. 静态文件404:
  • 开发时确保DEBUG=True
  • 生产环境运行collectstatic
  • 检查STATIC_URL和STATIC_ROOT配置
  1. CSRF验证失败:
  • 确保表单中包含{% csrf_token %}
  • 检查Cookie设置
  1. 性能问题:
  • 使用Django Debug Toolbar分析查询
  • 检查是否使用了N+1查询问题

12. 项目扩展方向

  1. 用户认证系统:
  • 集成Django内置的auth系统
  • 添加登录/注册功能
  1. REST API开发:
  • 使用Django REST framework
  • 创建投票API端点
  1. 实时功能:
  • 使用Channels添加WebSocket支持
  • 实时显示投票结果
  1. 前端改进:
  • 使用Vue/React构建更动态的界面
  • 添加图表展示投票结果
  1. 高级功能:
  • 投票限制(IP/用户)
  • 问卷功能扩展
  • 数据分析报表

这个投票应用虽然简单,但涵盖了Django开发的各个方面。我在实际项目中总结的经验是:初期重点应该放在理解Django的MTV模式上,而不是追求复杂功能。掌握了这些基础后,扩展功能就会水到渠成。

返回列表