1. 项目概述
作为一个刚接触Django的新手,我最近花了三周时间完整走了一遍官方文档的投票应用教程。这个看似简单的项目实际上涵盖了Django开发的核心流程,从环境搭建到模型设计,再到视图编写和模板渲染,最后部署上线。在这个过程中,我记下了大量实操笔记,也踩过不少坑,现在把这些经验系统整理出来。
投票应用虽然基础,但它完美展示了Django的MTV架构(Model-Template-View)。通过这个项目,你不仅能学会如何用Django构建一个功能完整的Web应用,更重要的是理解Django的设计哲学和最佳实践。我建议所有Django初学者都从这个项目入手,因为它就像编程界的"Hello World"一样经典。
2. 环境准备与项目创建
2.1 Python环境配置
首先确保你的Python版本在3.6以上。我强烈建议使用虚拟环境来隔离项目依赖:
bash复制python -m venv myenv
source myenv/bin/activate # Linux/Mac
myenv\Scripts\activate # Windows
注意:虚拟环境激活后,你的命令行提示符前会出现环境名称,这是判断是否激活成功的简单方法。
2.2 Django安装与验证
在虚拟环境中安装Django:
bash复制pip install django
安装完成后验证版本:
bash复制python -m django --version
我使用的是Django 4.1,但教程内容对3.2及以上版本都适用。如果遇到版本差异,可以查看Django的版本发布说明。
2.3 创建项目与应用
创建项目骨架:
bash复制django-admin startproject mysite
这会产生一个mysite目录,包含基本的项目结构。然后进入项目目录创建投票应用:
bash复制cd mysite
python manage.py startapp polls
关键目录结构说明:
mysite/settings.py:项目配置文件mysite/urls.py:URL路由入口polls/models.py:应用数据模型polls/views.py:视图逻辑
3. 数据模型设计
3.1 定义Question和Choice模型
在polls/models.py中定义我们的数据模型:
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
这里有几个关键点需要注意:
- 每个模型都是django.db.models.Model的子类
- 字段类型要选择合适的数据类型(CharField, DateTimeField等)
- ForeignKey定义了Question和Choice之间的一对多关系
__str__方法定义了对象的字符串表示形式
3.2 激活模型并迁移数据库
首先在settings.py的INSTALLED_APPS中添加'polls':
python复制INSTALLED_APPS = [
...
'polls.apps.PollsConfig',
]
然后生成并应用迁移:
bash复制python manage.py makemigrations polls
python manage.py migrate
实操心得:每次修改模型后都需要重新生成和应用迁移。养成习惯:修改模型 → makemigrations → migrate。
4. 管理后台配置
4.1 创建超级用户
Django自带一个强大的admin界面,首先创建管理员账号:
bash复制python manage.py createsuperuser
按照提示输入用户名、邮箱和密码。
4.2 注册模型到admin
在polls/admin.py中注册我们的模型:
python复制from django.contrib import admin
from .models import Question, Choice
admin.site.register(Question)
admin.site.register(Choice)
现在运行开发服务器:
bash复制python manage.py runserver
访问http://127.0.0.1:8000/admin,用刚才创建的超级用户登录,就能看到Question和Choice的管理界面了。
4.3 自定义admin界面
默认的admin界面功能有限,我们可以进行一些定制:
python复制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界面会:
- 在Question编辑页面直接内联显示关联的Choice
- 按字段分组显示表单
- 列表页显示更多信息
- 添加过滤和搜索功能
5. 视图与URL配置
5.1 编写第一个视图
在polls/views.py中:
python复制from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the polls index.")
5.2 配置URL路由
首先在polls目录下创建urls.py:
python复制from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
然后在项目级的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),
]
现在访问http://127.0.0.1:8000/polls/就能看到我们的第一个视图了。
5.3 完善更多视图
让我们添加更多功能视图:
python复制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复制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'),
6. 模板系统与前端展示
6.1 创建模板目录
在polls目录下创建templates/polls目录结构。Django会自动在这个目录下查找模板。
创建index.html模板:
html复制{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
6.2 修改视图使用模板
更新index视图:
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)
6.3 添加404处理
更新detail视图:
python复制from django.shortcuts import get_object_or_404
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
创建detail.html:
html复制<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
7. 表单处理与投票功能
7.1 创建投票表单
在detail.html中添加表单:
html复制<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
<legend><h1>{{ question.question_text }}</h1></legend>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
{% 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 %}
</fieldset>
<input type="submit" value="Vote">
</form>
7.2 实现投票逻辑
更新vote视图:
python复制from django.shortcuts import render, redirect
from django.urls import reverse
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 redirect(reverse('polls:results', args=(question.id,)))
7.3 结果显示页面
创建results.html:
html复制<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>
更新results视图:
python复制def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
8. 测试与调试
8.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):
time = timezone.now() + datetime.timedelta(days=30)
future_question = Question(pub_date=time)
self.assertIs(future_question.was_published_recently(), False)
8.2 运行测试
bash复制python manage.py test polls
8.3 调试技巧
- 使用Django的调试页面:当DEBUG=True时,错误页面会显示详细堆栈信息
- 打印调试信息:在视图或模型中添加print语句
- 使用Django shell进行交互调试:
bash复制python manage.py shell
常见问题:如果测试失败,检查测试数据库是否正常创建。有时需要删除旧的测试数据库文件。
9. 静态文件管理
9.1 添加CSS样式
在polls目录下创建static/polls/style.css:
css复制li a {
color: green;
}
在模板中加载静态文件:
html复制{% load static %}
<link rel="stylesheet" href="{% static 'polls/style.css' %}">
9.2 收集静态文件
生产环境中需要运行:
bash复制python manage.py collectstatic
这会将所有静态文件收集到STATIC_ROOT指定的目录中。
10. 部署准备
10.1 生产环境设置
修改settings.py:
python复制DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com', 'localhost']
10.2 选择部署方式
常见部署选项:
- PythonAnywhere:最简单的Django托管平台
- Heroku:云平台即服务
- AWS/GCP:更灵活的云服务
- 传统VPS:需要自己配置Nginx + Gunicorn
10.3 使用Gunicorn
安装Gunicorn:
bash复制pip install gunicorn
测试运行:
bash复制gunicorn mysite.wsgi
10.4 配置Nginx
基本Nginx配置:
nginx复制server {
listen 80;
server_name yourdomain.com;
location /static/ {
alias /path/to/your/static/files;
}
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
11. 项目总结与扩展思路
通过这个项目,我们完整实现了:
- Django项目的创建和配置
- 数据模型设计和数据库迁移
- 管理后台定制
- 视图和URL路由配置
- 模板系统和前端展示
- 表单处理和业务逻辑
- 测试用例编写
- 静态文件管理
- 部署准备
这个投票应用虽然简单,但包含了Web开发的所有核心要素。在此基础上,你可以考虑以下扩展方向:
- 用户认证系统:添加登录/注册功能
- API接口:使用Django REST framework创建REST API
- 异步任务:使用Celery处理后台任务
- 实时更新:通过WebSocket实现实时投票结果展示
- 前端框架:集成Vue.js或React构建更复杂的前端
我在实现过程中最大的收获是理解了Django"约定优于配置"的设计哲学。很多看似复杂的Web开发任务,Django都提供了优雅的解决方案。比如ORM让数据库操作变得简单,模板系统实现了业务逻辑和展示的分离,admin界面几乎零配置就能获得强大的后台管理功能。
对于初学者,我的建议是:
- 严格按照教程走一遍,不要跳过任何步骤
- 理解每个命令和代码变更的作用
- 遇到问题时,先查阅Django文档
- 多做实验,修改代码观察变化
- 尽早开始写测试用例
这个项目只是Django世界的入门,但它为你打开了Web开发的大门。掌握了这些基础知识后,你会发现学习更高级的Django特性会容易很多。