1. 项目概述
作为一个刚接触Django的开发者,我决定通过构建一个简单的投票应用来学习这个强大的Python Web框架。这个项目从最基础的开发环境搭建开始,逐步实现了一个完整的投票系统,涵盖了Django的核心功能模块。下面我将详细记录整个开发过程,分享从零开始构建Django应用的完整路径。
2. 开发环境准备
2.1 Python环境配置
首先需要确保系统安装了Python 3.6或更高版本。我推荐使用pyenv来管理Python版本:
bash复制# 安装pyenv
curl https://pyenv.run | bash
# 安装指定Python版本
pyenv install 3.9.6
# 设置全局Python版本
pyenv global 3.9.6
提示:使用虚拟环境是Python开发的最佳实践,可以避免不同项目间的依赖冲突。
2.2 创建虚拟环境
bash复制python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
2.3 安装Django
在激活的虚拟环境中安装Django:
bash复制pip install django
验证安装是否成功:
bash复制python -m django --version
3. 创建Django项目
3.1 初始化项目
使用Django命令行工具创建项目骨架:
bash复制django-admin startproject mysite
这会在当前目录下创建一个mysite目录,包含以下关键文件:
code复制mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
asgi.py
wsgi.py
3.2 运行开发服务器
进入项目目录并启动开发服务器:
bash复制cd mysite
python manage.py runserver
访问http://127.0.0.1:8000/应该能看到Django的欢迎页面。
4. 创建投票应用
4.1 创建应用
Django项目由多个应用组成。我们创建一个名为polls的投票应用:
bash复制python manage.py startapp polls
这会生成polls应用的目录结构:
code复制polls/
__init__.py
admin.py
apps.py
migrations/
models.py
tests.py
views.py
4.2 注册应用
在mysite/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/models.py中定义Question和Choice两个模型:
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')
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
5.2 激活模型
首先需要为应用创建迁移文件:
bash复制python manage.py makemigrations polls
然后应用这些迁移:
bash复制python manage.py migrate
6. 管理界面配置
6.1 创建管理员账号
bash复制python manage.py createsuperuser
按照提示输入用户名、邮箱和密码。
6.2 注册模型到admin
在polls/admin.py中注册我们的模型:
python复制from django.contrib import admin
from .models import Question, Choice
admin.site.register(Question)
admin.site.register(Choice)
现在可以访问http://127.0.0.1:8000/admin/,使用刚创建的管理员账号登录,管理投票问题。
7. 编写视图和URL配置
7.1 创建视图
在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("You're looking at question %s." % question_id)
def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
7.2 配置URL
首先在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'),
]
然后在项目的主URL配置(mysite/urls.py)中包含polls应用的URL:
python复制from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('polls/', include('polls.urls')),
path('admin/', admin.site.urls),
]
8. 模板系统
8.1 创建模板目录
在polls目录下创建templates/polls目录结构:
code复制polls/
templates/
polls/
index.html
detail.html
results.html
8.2 编写模板
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 %}
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="Vote">
</form>
8.3 更新视图使用模板
修改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})
9. 表单处理
9.1 更新vote视图
修改polls/views.py中的vote函数:
python复制from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
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,)))
9.2 添加results视图和模板
results视图:
python复制def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
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>
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)
def test_was_published_recently_with_old_question(self):
time = timezone.now() - datetime.timedelta(days=1, seconds=1)
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, minutes=59, seconds=59)
recent_question = Question(pub_date=time)
self.assertIs(recent_question.was_published_recently(), True)
10.2 运行测试
bash复制python manage.py test polls
11. 静态文件处理
11.1 创建静态文件目录
在polls目录下创建static/polls目录结构:
code复制polls/
static/
polls/
style.css
11.2 添加CSS样式
style.css:
css复制li a {
color: green;
}
body {
background: white url("images/background.gif") no-repeat right bottom;
}
11.3 在模板中引用静态文件
修改index.html顶部添加:
html复制{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}">
12. 项目部署准备
12.1 收集静态文件
在settings.py中添加:
python复制STATIC_ROOT = BASE_DIR / 'staticfiles'
然后运行:
bash复制python manage.py collectstatic
12.2 生产环境设置
创建生产环境配置文件settings_prod.py:
python复制from .settings import *
DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
# 数据库配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydatabase',
'USER': 'mydatabaseuser',
'PASSWORD': 'mypassword',
'HOST': 'localhost',
'PORT': '5432',
}
}
13. 项目优化建议
13.1 使用类视图重构
将函数视图改为类视图:
python复制from django.views import generic
from .models import Question
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
return Question.objects.order_by('-pub_date')[:5]
class DetailView(generic.DetailView):
model = Question
template_name = 'polls/detail.html'
class ResultsView(generic.DetailView):
model = Question
template_name = 'polls/results.html'
13.2 添加分页功能
修改IndexView:
python复制class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
paginate_by = 10 # 每页显示10个问题
13.3 添加缓存
在settings.py中配置缓存:
python复制CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
}
}
在视图中使用缓存:
python复制from django.views.decorators.cache import cache_page
@cache_page(60 * 15) # 缓存15分钟
def my_view(request):
...
14. 常见问题解决
14.1 数据库迁移问题
如果遇到迁移问题,可以尝试:
bash复制python manage.py makemigrations --empty polls
python manage.py migrate --fake polls zero
python manage.py migrate polls
14.2 静态文件不显示
确保DEBUG=True时能访问静态文件,在生产环境中需要配置Web服务器(如Nginx)来服务静态文件。
14.3 模板找不到
确保模板放在正确的目录结构下,并且APP_DIRS=True在TEMPLATES设置中。
14.4 CSRF验证失败
确保表单中包含{% csrf_token %}标签,并且在中间件中启用了'django.middleware.csrf.CsrfViewMiddleware'。
15. 项目扩展思路
- 添加用户认证系统,限制投票权限
- 实现API接口供移动应用调用
- 添加图表展示投票结果
- 实现定时发布投票功能
- 添加评论系统让用户讨论投票主题
- 实现多语言支持
- 添加搜索功能方便查找投票
- 实现导出投票结果为CSV或PDF
通过这个项目,我系统地学习了Django的核心概念和开发流程。从模型设计到视图编写,从URL配置到模板渲染,每个环节都让我对Django有了更深入的理解。特别是Django的"MTV"(模型-模板-视图)模式,让Web开发变得清晰而有条理。