1. 项目概述:为什么选择Django构建投票应用?
十年前我第一次接触Web开发时,用PHP写了无数个投票系统。直到遇见Django,才发现原来构建这类典型CRUD应用可以如此优雅。这个Python全栈框架的"电池全包"哲学,特别适合快速实现数据驱动的Web应用。
投票系统作为经典练手项目,涵盖了:
- 用户认证(投票权限控制)
- 数据建模(候选人/投票记录)
- 表单处理(投票提交)
- 关系查询(统计票数)
- 后台管理(内容维护)
Django的ORM系统让数据库操作变得像Python列表操作一样简单,自带的后台管理界面更是省去了80%的基础CRUD开发工作。下面分享我从零搭建投票应用的完整过程,包含那些官方文档没写的实战细节。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与项目初始化
2.1 开发环境配置
推荐使用Python 3.8+和最新稳定版Django(本文基于Django 4.2)。使用虚拟环境是必须的:
bash复制python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
pip install django
注意:不要直接全局安装Django,不同项目可能需要不同版本。我遇到过Django 2.x和3.x的URL路由语法差异导致项目无法启动的情况。
2.2 创建项目骨架
bash复制django-admin startproject voting_system
cd voting_system
python manage.py startapp polls
关键文件结构说明:
code复制voting_system/
├── manage.py # 项目管理脚本
└── voting_system/
├── __init__.py
├── settings.py # 全局配置
├── urls.py # 主路由
└── wsgi.py
polls/
├── migrations/ # 数据库迁移文件
├── __init__.py
├── admin.py # 后台配置
├── apps.py
├── models.py # 数据模型
├── tests.py
└── views.py # 业务逻辑
在settings.py中注册应用:
python复制INSTALLED_APPS = [
...
'polls.apps.PollsConfig',
]
3. 数据模型设计
3.1 核心模型关系
投票系统最核心的三个模型:
python复制from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __str__(self):
return self.question_text
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
class VoteRecord(models.Model):
user_id = models.CharField(max_length=50) # 简单实现,实际应用应关联User
choice = models.ForeignKey(Choice, on_delete=models.CASCADE)
vote_time = models.DateTimeField(auto_now_add=True)
经验:ForeignKey的on_delete参数必须明确。我曾因漏写这个参数导致数据库约束异常。
3.2 数据库迁移
执行以下命令将模型同步到数据库:
bash复制python manage.py makemigrations polls
python manage.py migrate
推荐安装django-extensions,可以图形化查看模型关系:
bash复制pip install django-extensions
python manage.py graph_models polls -o models.png
4. 后台管理配置
4.1 基础配置
在polls/admin.py中注册模型:
python复制from django.contrib import admin
from .models import Question, Choice
class ChoiceInline(admin.TabularInline):
model = Choice
extra = 3 # 默认显示3个选项框
@admin.register(Question)
class QuestionAdmin(admin.ModelAdmin):
inlines = [ChoiceInline]
list_display = ('question_text', 'pub_date', 'was_published_recently')
list_filter = ['pub_date']
search_fields = ['question_text']
admin.site.register(Choice)
4.2 创建管理员账号
bash复制python manage.py createsuperuser
# 按提示输入用户名、邮箱和密码
启动开发服务器后访问/admin:
bash复制python manage.py runserver
踩坑记录:如果遇到"CSRF verification failed"错误,检查settings.py中是否包含'django.middleware.csrf.CsrfViewMiddleware'中间件。
5. 视图与URL配置
5.1 基础视图实现
在polls/views.py中:
python复制from django.shortcuts import render, get_object_or_404
from .models import Question
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': "请选择一个选项",
})
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})
5.2 URL路由配置
在polls目录下创建urls.py:
python复制from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/vote/', views.vote, name='vote'),
path('<int:question_id>/results/', views.results, name='results'),
]
在主项目的urls.py中包含这些路由:
python复制from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('polls/', include('polls.urls')),
path('admin/', admin.site.urls),
]
6. 模板系统实现
6.1 基础模板结构
创建templates/polls/base.html:
html复制<!DOCTYPE html>
<html>
<head>
<title>{% block title %}投票系统{% endblock %}</title>
</head>
<body>
<div id="content">
{% block content %}{% endblock %}
</div>
</body>
</html>
6.2 各页面模板实现
index.html:
html复制{% extends "polls/base.html" %}
{% 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>暂无投票</p>
{% endif %}
{% endblock %}
detail.html:
html复制{% extends "polls/base.html" %}
{% 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="投票">
</form>
{% endblock %}
results.html:
html复制{% extends "polls/base.html" %}
{% block content %}
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }} -- {{ choice.votes }}票</li>
{% endfor %}
</ul>
<a href="{% url 'polls:detail' question.id %}">重新投票?</a>
{% endblock %}
模板技巧:使用{% extends %}实现模板继承,避免重复代码。我早期项目曾因复制粘贴模板代码导致维护困难。
7. 测试与调试
7.1 单元测试示例
在polls/tests.py中添加:
python复制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)
运行测试:
bash复制python manage.py test polls
7.2 调试技巧
- 使用Django Debug Toolbar:
bash复制pip install django-debug-toolbar
在settings.py中配置:
python复制INSTALLED_APPS = [
...
'debug_toolbar',
]
MIDDLEWARE = [
...
'debug_toolbar.middleware.DebugToolbarMiddleware',
]
INTERNAL_IPS = ['127.0.0.1']
在urls.py中添加:
python复制if settings.DEBUG:
import debug_toolbar
urlpatterns = [
path('__debug__/', include(debug_toolbar.urls)),
] + urlpatterns
8. 部署准备
8.1 生产环境设置
修改settings.py:
python复制DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com', 'localhost']
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
收集静态文件:
bash复制python manage.py collectstatic
8.2 使用Gunicorn和Nginx
安装Gunicorn:
bash复制pip install gunicorn
示例Nginx配置:
nginx复制server {
listen 80;
server_name yourdomain.com;
location /static/ {
alias /path/to/your/staticfiles/;
}
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
启动服务:
bash复制gunicorn --workers 3 voting_system.wsgi:application
9. 项目优化方向
-
性能优化:
- 使用select_related/prefetch_related优化查询
- 添加缓存支持(Redis)
- 异步任务处理(Celery)
-
功能扩展:
- 用户认证系统(django-allauth)
- API接口(DRF)
- 实时投票统计(WebSocket)
-
安全加固:
- 防止刷票(IP限制)
- 数据验证强化
- HTTPS强制跳转
这个投票应用虽然基础,但涵盖了Django开发的完整流程。我在实际项目中发现,很多复杂系统都是这种基础模式的组合与扩展。掌握好这些核心概念,就能快速构建出功能完善的Web应用。
