1. 为什么选择Django构建投票应用?
作为一个使用Django近十年的开发者,我依然记得第一次用Django构建投票应用时的兴奋感。这个看似简单的项目实际上涵盖了Web开发的几乎所有核心概念:数据库建模、URL路由、模板渲染、表单处理、用户认证...而Django将这些复杂功能封装得恰到好处,让初学者也能快速上手。
投票应用之所以成为Django官方教程的经典案例,是因为它完美展示了Django"不重复造轮子"的哲学。比如内置的Admin后台,几行代码就能获得完整的内容管理系统;强大的ORM系统,让不懂SQL的新手也能操作数据库;自带的用户认证系统,省去了从头开发登录注册的麻烦。
提示:虽然现在有FastAPI等新兴框架,但Django仍然是学习Web开发最佳入门选择之一。它的"全栈式"设计能让你系统性地理解Web应用的完整生命周期。
1.1 Django的核心优势解析
与其他Python Web框架相比,Django最突出的特点是"开箱即用"。以下是几个典型场景的对比:
| 功能需求 | Flask实现方式 | Django实现方式 |
|---|---|---|
| 数据库操作 | 需要安装SQLAlchemy等扩展 | 内置ORM,直接使用models.py |
| 用户认证 | 需要手动实现或使用Flask-Login | 内置auth系统,包含登录/注册 |
| 后台管理 | 需要集成第三方Admin面板 | 自带Admin,自动生成管理界面 |
| 表单验证 | 依赖WTForms等扩展 | 内置Form类,自动CSRF防护 |
我在实际项目中最常使用的Django组件是它的ORM系统。比如在投票应用中,定义问题(Question)和选项(Choice)的模型只需要这样:
python复制from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
这段简单的代码背后,Django自动处理了:
- 数据库表的创建与迁移
- 主外键关系的维护
- 字段类型的验证
- 甚至生成了管理界面
1.2 开发环境准备要点
在开始项目前,需要特别注意Python环境的管理。我强烈建议使用虚拟环境,以下是具体步骤:
bash复制# 创建虚拟环境(Python 3.3+内置venv)
python -m venv myenv
source myenv/bin/activate # Linux/Mac
myenv\Scripts\activate.bat # Windows
# 安装Django(注意版本兼容性)
pip install django==4.2 # 当前LTS版本
常见问题排查:
- 如果遇到"Command 'python' not found",尝试使用python3
- Windows系统可能需要以管理员身份运行PowerShell
- 安装后验证:
python -m django --version
避坑指南:新手常犯的错误是直接全局安装Django。这会导致不同项目间的依赖冲突。虚拟环境是Python开发的必备实践。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 项目创建与基础配置
2.1 初始化项目结构
使用Django命令行工具创建项目骨架:
bash复制django-admin startproject mysite
cd mysite
python manage.py startapp polls
生成的项目结构如下:
code复制mysite/
manage.py
mysite/
__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:项目配置中枢,包含数据库、应用、中间件等设置urls.py:URL路由入口,决定哪个请求由哪个视图处理models.py:定义数据模型,对应数据库表结构views.py:业务逻辑实现,处理请求并返回响应
2.2 配置数据库与基础设置
修改mysite/settings.py中的关键配置:
python复制# 注册polls应用
INSTALLED_APPS = [
...
'polls.apps.PollsConfig',
]
# 数据库配置(默认SQLite,适合开发)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# 时区设置(中国开发者建议修改)
TIME_ZONE = 'Asia/Shanghai'
初始化数据库:
bash复制python manage.py migrate
这个命令会创建Django内置应用(如auth、admin等)需要的数据库表。
2.3 开发服务器启动技巧
运行开发服务器:
bash复制python manage.py runserver
访问http://127.0.0.1:8000/应该看到欢迎页面。开发服务器支持自动重载,修改代码后无需手动重启。
性能提示:默认情况下开发服务器是单线程的。如果需要测试并发场景,可以添加
--nothreading --noreload参数,但不要在生产环境使用此模式。
3. 核心功能实现详解
3.1 数据模型设计与迁移
在polls/models.py中完善投票模型:
python复制import datetime
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')
def __str__(self):
return self.question_text
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
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
生成并应用迁移:
bash复制python manage.py makemigrations polls
python manage.py migrate
迁移是Django最强大的功能之一,它自动将模型变更同步到数据库。makemigrations生成迁移文件,migrate执行这些变更。
3.2 Admin后台配置实战
Django的Admin后台可以零代码实现数据管理。首先创建超级用户:
bash复制python manage.py createsuperuser
然后在polls/admin.py中注册模型:
python复制from django.contrib import admin
from .models import Question, Choice
# 基本注册方式
admin.site.register(Question)
admin.site.register(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)
现在访问/admin,就能看到功能完善的后台管理系统,包含:
- 数据的增删改查
- 按日期过滤问题
- 搜索问题文本
- 直接在问题页面编辑关联选项
3.3 视图与URL配置精讲
Django遵循MTV模式(Model-Template-View)。首先在polls/views.py中创建基础视图:
python复制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}.")
然后在polls/urls.py中配置路由:
python复制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'),
]
最后在项目级的mysite/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),
]
现在访问/polls/应该能看到最新问题的列表。URL配置中的name参数在后面模板中会非常有用。
4. 模板系统与前端实现
4.1 基础模板结构设计
在polls/templates/polls/目录下创建以下模板文件:
base.html(基础模板):
html复制<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My Polls App{% endblock %}</title>
</head>
<body>
<div id="content">
{% block content %}{% endblock %}
</div>
</body>
</html>
index.html(继承基础模板):
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 %}
修改视图使用模板:
python复制from django.shortcuts import render
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)
4.2 表单处理与投票逻辑
实现投票功能的完整视图:
python复制from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
from django.urls import reverse
from .models import Choice, 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,)))
对应的模板detail.html:
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 %}
4.3 静态文件管理与样式优化
在polls/static/polls/目录下创建样式文件style.css:
css复制/* 基础样式 */
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
/* 投票选项样式 */
ul.choices {
list-style-type: none;
padding: 0;
}
ul.choices li {
margin: 5px 0;
padding: 10px;
background: white;
border-radius: 4px;
}
/* 按钮样式 */
input[type="submit"] {
background: #4CAF50;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
}
input[type="submit"]:hover {
background: #45a049;
}
在模板中加载静态文件:
html复制{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}">
5. 测试与部署准备
5.1 编写自动化测试
Django内置了强大的测试框架。在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):
"""
was_published_recently() returns False for questions whose pub_date
is in the future.
"""
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):
"""
was_published_recently() returns False for questions older than 1 day.
"""
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):
"""
was_published_recently() returns True for questions within the last day.
"""
time = timezone.now() - datetime.timedelta(hours=23)
recent_question = Question(pub_date=time)
self.assertIs(recent_question.was_published_recently(), True)
运行测试:
bash复制python manage.py test polls
5.2 生产环境部署要点
虽然开发服务器方便调试,但绝不能用于生产环境。以下是部署的基本步骤:
- 收集静态文件:
bash复制python manage.py collectstatic
- 安装生产级WSGI服务器(如Gunicorn):
bash复制pip install gunicorn
gunicorn mysite.wsgi
- 配置数据库(如PostgreSQL):
python复制DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydatabase',
'USER': 'mydatabaseuser',
'PASSWORD': 'mypassword',
'HOST': '127.0.0.1',
'PORT': '5432',
}
}
- 安全设置(必须修改!):
python复制# 生产环境必须设置
DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
# 安全相关设置
SECURE_HSTS_SECONDS = 31536000 # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
部署警告:永远不要在生产环境中使用
DEBUG=True,这会导致敏感信息泄露。同时确保SECRET_KEY不在版本控制中。
6. 项目优化与扩展思路
6.1 性能优化技巧
- 使用
select_related和prefetch_related优化查询:
python复制# 避免N+1查询问题
questions = Question.objects.prefetch_related('choice_set').all()
- 添加数据库索引:
python复制class Question(models.Model):
question_text = models.CharField(max_length=200, db_index=True)
pub_date = models.DateTimeField('date published', db_index=True)
- 使用缓存装饰器:
python复制from django.views.decorators.cache import cache_page
@cache_page(60 * 15) # 缓存15分钟
def index(request):
...
6.2 常见功能扩展方向
- 用户认证集成:
python复制from django.contrib.auth.decorators import login_required
@login_required
def vote(request, question_id):
...
- REST API开发(使用Django REST framework):
python复制from rest_framework import serializers, viewsets
from .models import Question
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = '__all__'
class QuestionViewSet(viewsets.ModelViewSet):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
- 异步任务处理(Celery集成):
python复制from celery import shared_task
@shared_task
def process_vote(choice_id):
choice = Choice.objects.get(pk=choice_id)
choice.votes += 1
choice.save()
6.3 项目结构优化建议
随着项目增长,建议调整为以下结构:
code复制mysite/
apps/
polls/
__init__.py
models.py
...
config/
settings/
__init__.py
base.py
development.py
production.py
urls.py
wsgi.py
static/
templates/
manage.py
这种结构更易于维护大型项目,可以通过环境变量切换不同配置:
bash复制export DJANGO_SETTINGS_MODULE=config.settings.production
从投票应用出发,你可以继续探索:
- 用户权限管理
- 实时投票结果展示(WebSockets)
- 第三方登录集成
- 自动化测试覆盖率提升
- 容器化部署(Docker)
这个看似简单的项目实际上包含了Web开发的精髓。我在实际项目中最大的体会是:Django的强大之处不在于它能让你快速开始,而在于它能让你平稳地从小项目成长为大系统。
