1. 项目概述
今天我要分享一个完整的Django投票系统开发过程。这个项目非常适合刚接触Django的开发者,通过构建一个功能完整的投票系统,可以快速掌握Django的核心开发流程。我自己在初学Django时也做过类似的项目,发现这种实战练习比单纯看文档要有效得多。
这个投票系统将包含以下核心功能:
- 用户可以通过网页查看投票问题
- 能够对选项进行投票
- 管理员可以管理投票问题和选项
- 系统会记录每个选项的得票数
- 提供美观的后台管理界面
整个开发过程大约需要2-3小时,适合有一定Python基础但刚接触Django的开发者。我会尽量详细地解释每个步骤,确保即使是Django新手也能顺利完成。
2. 环境准备
2.1 Python安装与验证
首先确保你的系统已经安装了Python 3.6或更高版本。我推荐使用Python 3.8或3.9,因为它们与Django的兼容性最好。可以通过以下命令检查Python版本:
bash复制python --version
# 或
python3 --version
如果显示类似"Python 3.9.7"的输出,说明Python已正确安装。如果没有安装,可以从Python官网下载对应你操作系统的安装包。
注意:在Windows上,安装时记得勾选"Add Python to PATH"选项,这样才能在命令行中直接使用python命令。
2.2 创建虚拟环境
强烈建议为Django项目创建独立的虚拟环境,这样可以避免不同项目间的依赖冲突。以下是创建虚拟环境的步骤:
bash复制# 创建虚拟环境目录
python -m venv myenv
# 激活虚拟环境
# Windows:
myenv\Scripts\activate
# macOS/Linux:
source myenv/bin/activate
激活后,你的命令行提示符前会出现"(myenv)"字样,表示虚拟环境已激活。
2.3 安装Django
在虚拟环境中,使用pip安装Django:
bash复制pip install django
安装完成后,验证Django是否安装成功:
bash复制python -m django --version
如果显示版本号(如5.0.3),说明安装成功。我建议使用最新稳定版的Django,因为它包含了最新的安全补丁和功能改进。
3. 创建Django项目
3.1 初始化项目
在命令行中,运行以下命令创建Django项目:
bash复制django-admin startproject djangotutorial
这会创建一个名为"djangotutorial"的目录,包含项目的基本结构。让我们看看生成的文件:
code复制djangotutorial/
manage.py
djangotutorial/
__init__.py
settings.py
urls.py
asgi.py
wsgi.py
manage.py:项目管理工具,用于运行各种管理命令settings.py:项目配置文件,包含数据库、应用、模板等设置urls.py:URL路由配置文件asgi.py和wsgi.py:ASGI和WSGI服务器接口文件
3.2 运行开发服务器
进入项目目录并启动开发服务器:
bash复制cd djangotutorial
python manage.py runserver
你会看到类似下面的输出:
code复制Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
在浏览器中访问http://127.0.0.1:8000/,应该能看到Django的欢迎页面(一个火箭图标)。这表示你的Django项目已经成功运行。
提示:开发服务器会自动检测代码变化并重新加载,所以在开发过程中不需要频繁重启服务器。
4. 创建投票应用
4.1 创建应用
Django项目由多个应用组成。现在我们来创建投票应用:
bash复制python manage.py startapp polls
这会创建一个"polls"目录,结构如下:
code复制polls/
__init__.py
admin.py
apps.py
models.py
tests.py
views.py
migrations/
__init__.py
models.py:定义数据模型views.py:处理请求并返回响应admin.py:配置管理后台tests.py:编写测试用例
4.2 注册应用
为了让Django识别这个应用,需要在settings.py中注册它。打开djangotutorial/settings.py,找到INSTALLED_APPS列表,添加'polls':
python复制INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'polls', # 添加这行
]
5. 编写第一个视图
5.1 创建视图函数
打开polls/views.py,添加一个简单的视图函数:
python复制from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the polls index.")
这个视图函数接收一个request参数,返回一个包含简单文本的HttpResponse对象。
5.2 配置URL路由
5.2.1 创建应用URL配置
在polls目录下创建urls.py文件:
python复制from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
这里定义了一个URL模式,将根路径('')映射到我们刚创建的index视图。
5.2.2 包含到项目URL配置
修改djangotutorial/urls.py,包含polls应用的URL配置:
python复制from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('polls/', include('polls.urls')),
]
include()函数允许我们引用其他URL配置,这样可以使项目结构更清晰。
5.3 测试视图
启动开发服务器(如果尚未运行):
bash复制python manage.py runserver
访问http://localhost:8000/polls/,应该能看到"Hello, world. You're at the polls index."的文本。这表明我们的视图和URL配置工作正常。
6. 创建数据库模型
6.1 设计数据模型
投票系统需要两个主要模型:Question(问题)和Choice(选项)。打开polls/models.py,添加以下代码:
python复制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
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
Question模型表示一个投票问题,包含问题文本和发布时间Choice模型表示一个选项,包含选项文本、得票数和关联的问题__str__方法定义了模型的字符串表示形式,便于在管理界面和shell中查看
注意:我添加了
default=timezone.now,这样创建问题时如果不指定发布时间,会自动使用当前时间。
6.2 数据库迁移
Django使用迁移系统来管理数据库模式的变化。执行以下命令:
bash复制# 创建迁移文件
python manage.py makemigrations polls
# 查看将执行的SQL(可选)
python manage.py sqlmigrate polls 0001
# 应用迁移
python manage.py migrate
这些命令会:
- 根据模型变化生成迁移文件
- 显示将要执行的SQL语句(供检查)
- 实际执行迁移,创建数据库表
6.3 使用Django Shell操作数据
Django提供了一个交互式shell,方便我们操作数据:
bash复制python manage.py shell
在shell中可以这样操作:
python复制from polls.models import Question, Choice
from django.utils import timezone
# 创建一个问题
q = Question(question_text="What's new?", pub_date=timezone.now())
q.save()
# 查询所有问题
Question.objects.all()
# 创建选项
q.choice_set.create(choice_text="Not much", votes=0)
q.choice_set.create(choice_text="The sky", votes=0)
q.choice_set.create(choice_text="Just hacking again", votes=0)
# 查询选项
q.choice_set.all()
7. Django Admin后台管理
7.1 创建管理员账号
Django自带一个强大的管理后台。首先创建管理员账号:
bash复制python manage.py createsuperuser
按照提示输入用户名、邮箱和密码。然后启动开发服务器:
bash复制python manage.py runserver
访问http://127.0.0.1:8000/admin/,用刚创建的账号登录。
7.2 注册模型到管理后台
为了让我们的模型出现在管理后台,需要注册它们。修改polls/admin.py:
python复制from django.contrib import admin
from .models import Question, Choice
admin.site.register(Question)
admin.site.register(Choice)
刷新管理后台页面,现在应该能看到Question和Choice模型,可以对其进行增删改查操作。
7.3 自定义管理界面
我们可以进一步自定义管理界面。修改polls/admin.py:
python复制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)
这些自定义包括:
- 在Question编辑页面内联显示Choice
- 分组显示字段
- 列表页显示更多信息
- 添加过滤和搜索功能
8. 创建模板和视图
8.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 results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
这些视图分别处理:
- 首页:显示最新的5个问题
- 详情页:显示特定问题和选项
- 结果页:显示投票结果
8.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>/results/', views.results, name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
]
8.3 创建模板
在polls目录下创建templates/polls/目录,然后创建以下模板文件:
index.html:
html复制<h1>投票系统</h1>
{% 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 %}
detail.html:
html复制<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>
results.html:
html复制<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>
9. 实现投票功能
9.1 编写投票视图
在polls/views.py中添加vote视图:
python复制from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse, HttpResponseRedirect
from django.urls import reverse
from .models import Question, Choice
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,)))
这个视图处理投票表单提交:
- 获取问题和选择的选项
- 如果没有选择选项,返回错误信息
- 如果选择有效,增加选项的票数并保存
- 重定向到结果页面
9.2 测试投票功能
现在你可以:
- 访问http://localhost:8000/polls/查看问题列表
- 点击问题进入详情页
- 选择选项并投票
- 查看投票结果
10. 编写测试
10.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)
10.2 视图测试
添加视图测试:
python复制def create_question(question_text, days):
time = timezone.now() + datetime.timedelta(days=days)
return Question.objects.create(question_text=question_text, pub_date=time)
class QuestionIndexViewTests(TestCase):
def test_no_questions(self):
response = self.client.get(reverse('polls:index'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "没有可用的投票。")
self.assertQuerysetEqual(response.context['latest_question_list'], [])
def test_past_question(self):
question = create_question(question_text="Past question.", days=-30)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
[question],
)
运行测试:
bash复制python manage.py test polls
11. 美化后台界面
11.1 安装SimpleUI
SimpleUI是一个简洁美观的Django后台主题:
bash复制pip install django-simpleui
11.2 配置SimpleUI
在settings.py中,将'simpleui'添加到INSTALLED_APPS的最前面:
python复制INSTALLED_APPS = [
'simpleui',
'django.contrib.admin',
# 其他应用...
]
重启开发服务器后,访问管理后台,界面会变得更加现代化和美观。
12. 项目部署准备
虽然我们一直在使用开发服务器,但实际部署时需要使用生产级服务器。以下是基本准备步骤:
12.1 收集静态文件
bash复制python manage.py collectstatic
12.2 配置生产环境设置
创建production.py设置文件,修改以下关键设置:
python复制DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
12.3 选择部署方式
常见部署选项:
- Apache + mod_wsgi
- Nginx + Gunicorn
- Docker容器
- 云平台如Heroku、AWS等
13. 项目扩展建议
这个基础投票系统可以进一步扩展:
- 用户认证:添加用户注册、登录功能,确保每人只能投一次票
- API接口:使用Django REST framework创建API
- 实时更新:使用Django Channels实现实时投票结果更新
- 数据分析:添加投票结果可视化图表
- 国际化:支持多语言界面
14. 常见问题解决
14.1 数据库连接问题
如果遇到数据库相关错误,尝试:
bash复制python manage.py migrate --run-syncdb
14.2 静态文件不显示
确保settings.py中正确配置了STATIC_URL和STATIC_ROOT,并运行了collectstatic。
14.3 模板找不到
检查TEMPLATES设置中的DIRS是否包含你的模板目录,确保模板放在正确的路径下。
14.4 管理界面样式丢失
这通常是因为静态文件服务配置不正确。在开发环境中,可以添加:
python复制from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
# ...你的URL配置...
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
15. 性能优化建议
- 数据库查询优化:使用
select_related和prefetch_related减少查询次数 - 缓存:添加缓存层,可以使用Django的缓存框架
- 静态文件CDN:使用CDN服务静态文件
- 启用Gzip:压缩响应内容
- 数据库索引:为常用查询字段添加索引
16. 安全最佳实践
- 始终使用HTTPS
- 保持Django和依赖库更新
- 不要在生产环境中使用
DEBUG=True - 使用强密码和密钥
- 限制管理后台访问IP
- 定期备份数据库
17. 项目结构优化
随着项目增长,可以考虑以下结构调整:
- 将设置拆分为
base.py、development.py和production.py - 为每个应用创建单独的
urls.py - 使用
apps目录组织多个应用 - 分离模板和静态文件到各自的应用目录
18. 调试技巧
- 使用
print()或logging输出调试信息 - Django Debug Toolbar是强大的调试工具
- 在视图中使用
assert False触发调试页面 - 使用
python manage.py shell交互式测试代码 - 阅读Django的错误日志和堆栈跟踪
19. 学习资源推荐
- Django官方文档(必读)
- "Django for Beginners"书籍
- Django Girls教程
- Real Python的Django教程
- 各种Django相关的YouTube频道
20. 开发心得
在实际开发中,我发现以下几点特别重要:
- 循序渐进:不要试图一次实现所有功能,先构建核心功能,再逐步添加
- 测试驱动:编写测试可以节省大量调试时间
- 版本控制:使用Git等工具管理代码变更
- 文档习惯:为代码和项目写清晰的文档
- 社区支持:遇到问题时,Django社区通常能提供很好的帮助
这个投票系统项目虽然简单,但涵盖了Django开发的各个方面。通过这个练习,你应该对Django的MVT(Model-View-Template)架构有了基本理解。接下来,你可以尝试添加更多功能,或者开始构建自己的Django项目。