urls.py:
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('polls/', include('polls.urls')),
path('admin/', admin.site.urls),
]
复制代码
docs.djangoproject.com/zh-hans/3.2…
- 这里将
admin/
转到admin.site.urls
(自带的),这是 polls/
转到polls.urls
(polls文件夹的urls.py),这是截断转入
然后来看一下polls/urls.py:
from django.urls import path
from . import views
# 添加命名空间(比如另一个app里面也有一个views.detail,为了确保不会错,用app_name来确定是哪个app)
app_name = 'polls'
urlpatterns = [
# ex: /polls/ [问题索引页——展示最近的几个投票问题。]
path('', views.index, name='index'),
# ex: /polls/5/ [问题详情页——展示某个投票的问题和不带结果的选项列表]
path('<int:question_id>/', views.detail, name='detail'),
# ex: /polls/5/results/ [问题结果页——展示某个问题的所有答案的数据(答案名和投票数)。]
path('<int:question_id>/results/', views.results, name='results'),
# ex: /polls/5/vote/ [投票处理器——用于响应用户为某个问题的特定选项投票的操作]
path('<int:question_id>/vote/', viewews.vote, name='vote'),
]
复制代码
可以看到
- /polls/传到index,即问题索引
- /polls/5/传到detail,即问题详情(问题文本+所有的选项文本)
- /polls/5/vote/传到vote,即投票处理程序(将票+1,然后重定向到results)
- /polls/5/results传到results,即最终结果(问题+所有选项文本+票数)
页面展示如下:
先看一下它的models,即polls/models.py:
from django.db import models
# 每个模型被表示为 django.db.models.Model 类的子类
class Question(models.Model): #Question问题表
question_text = models.CharField(max_length=200) #字符字段(CharField):question_text问题
pub_date = models.DateTimeField('date published') #时间字段(DateTimeField):pub_data日期
def __str__(self):
return self.question_text
class Choice(models.Model): #Choice投票表
question = models.ForeignKey(Question, on_delete=models.CASCADE)
#问题:是问题表的外键,这表明 每个Choice对象都关联到一个Question对象
choice_text = models.CharField(max_length=200) #choice_text:投票文本
votes = models.IntegerField(default=0) #投票票数
def __str__(self):
return self.choice_text
复制代码
我们在命令行运行:
python manage.py makemigrations polls //检测并储存对模型文件的修改(类似于git add.放到暂存区)
python manage.py migrate //根据修改信息,产生新的模型(数据库)
复制代码
我们执行后的数据库,其实是下面这样:
BEGIN;
--
-- Create model Question
--
CREATE TABLE "polls_question" (
"id" serial NOT NULL PRIMARY KEY,
"question_text" varchar(200) NOT NULL,
"pub_date" timestamp with time zone NOT NULL
);
--
-- Create model Choice
--
CREATE TABLE "polls_choice" (
"id" serial NOT NULL PRIMARY KEY,
"choice_text" varchar(200) NOT NULL,
"votes" integer NOT NULL,
"question_id" integer NOT NULL
);
ALTER TABLE "polls_choice"
ADD CONSTRAINT "polls_choice_question_id_c5b4b260_fk_polls_question_id"
FOREIGN KEY ("question_id")
REFERENCES "polls_question" ("id")
DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id");
COMMIT;
复制代码
再来看看Django的admin管理员功能
运行:python manage.py createsuperuser
后,输入用户名和密码等,就成功创建了一个admin账户,此时就可以进入admin界面了。
如果要允许在admin界面中修改数据库,那么需要在对应的application中的admin.py中注册数据库的权限:
polls/admin.py:
from django.contrib import admin
from .models import Question,Choice
# admin/ 中注册表的增删改权限
admin.site.register(Question)
admin.site.register(Choice)
复制代码
这样,我们就注册了Question表和Choice表了,我们就可以在admin界面去增删该数据库了,很方便,如下图所示:
ok,下面逐个分析我们的view函数.
1.index
# 展示数据库里以发布日期排序的最近 5 个投票问题,以空格分割:
''' 写法1:这种写法没有利用模板文件,不是一个前后端分离的,所以想改界面的话就真的得在这里改,很麻烦
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)
'''
''' 写法2:这种写法利用了模板文件(templates\polls\index.html)
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5] #选择最近的5个问题组成list
template = loader.get_template('polls/index.html') #从[遍历选取配置文件INSTALLED_APP的某个dir]/templates/polls/index.html
# 细节:我们创建的templates文件夹后面跟着这个APP的文件夹名字(polls),这样就避免了错误导入template,因为template的机制是遍历所有app,选择正确的那个
context = {
'latest_question_list': latest_question_list,
}
# 将上下文context(该例子中是最近的Question list)传入该模板,并将request传入该模板,然后就去执行index.html里的程序,然后将结果返回给服务端
return HttpResponse(template.render(context, request))
'''
#「载入模板,填充上下文,再返回由它生成的 HttpResponse 对象」是一个非常常用的操作流程。于是 Django 提供了一个快捷函数
# 写法3:这种写法是写法2的简化版,不用去loader.get_template了,...,一次性用render去完成模板的调用
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)
# 这个语句集成了前面的get_template(模板名)+template.render(context,request)+HttpResponse
复制代码
在index中,我们用去执行模板polls/index.html:
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}">
<h2>Quizs to vote:</h2>
{% if latest_question_list %} <!--如果Question的list非空,就去遍历...-->
<ul>
{% for question in latest_question_list %} <!--遍历Question list,用question得到该元素-->
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
<!--输出一个到/polls/{{question.id}}的超链接,并显示文字为这个问题的question_text-->
{% endfor %}
</ul>
{% else %} <!--如果Question的list空,就输出这个提示语-->
<p>No polls are available.</p>
{% endif %}
复制代码
这个html就是显示最近的5个问题列表中的所有的问题,并超链接到polls/question.id/detail
中,结果如下图所示
2.detail
#像这个question_id是在urls.py的path函数的参数1中定义的变量,将这个路径的一部分看作变量
''' 写法1:普通的try-except 写法
def detail(request, question_id):
try:
question = Question.objects.get(pk=question_id) #pk的意思是primary-key,即id
except Question.DoesNotExist: #如果没有得到对应的question,抛出404错误
raise Http404("Question does not exist")
return render(request, 'polls/detail.html', {'question': question})
# 如果得到了question,那么将这个question对象传入这个detail.html进行处理,将结果返回
'''
# 写法2:利用django内置的get_object_or_404()函数,如果得不到就抛出404,如果得到就继续执行
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
复制代码
这里就是去执行template/polls/detail.html
模板:
<form action="{% url 'polls:vote' question.id %}" method="post"> <!--post的表单:转到polls的views.vote的url-->
{% csrf_token %} <!--所有的URL的POST表单都要加这个,来避免跨站点伪造表单-->
<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 }}">
<!--提交表单时:发送choice=#,其中# 为选择的 Choice 的 ID-->
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
{% endfor %}
</fieldset>
<input type="submit" value="Vote"><!--提交按钮-->
</form>
复制代码
可以看到,这是一个表单,显示这个问题,让所有的选项允许打勾,并有一个提交的vote按钮,如下图所示:
点完vote按钮之后,会用post去访问/polls/question.id/vote/
界面,跳转到views.vote函数:
3.vote
views.vote函数:
''' 最最简陋的写法:返回这个问题编号...
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
'''
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id) #question=Question中pk=question_id的对象
try:
selected_choice = question.choice_set.get(pk=request.POST['choice'])
# 该question的choice编号为request.POST['choice'],得到该对象为selected_choice
except (KeyError, Choice.DoesNotExist):
# Redisplay the question voting form.
return render(request, 'polls/detail.html', {
'question': question,
'error_message': "You didn't select a choice.",
})
else:
selected_choice.votes += 1 #该答案的投票数+1
selected_choice.save() #提交该事务
# Always return an HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a
# user hits the Back button.
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
# Http**rect的参数reverse('',args=()))为:'/polls/question.id(如3)/results'
# HttpResponseRedirect是重定向,这里会重定向到/polls/3(例如)/results这个url,这里会返回结果
复制代码
这里所做的主要就是将vote+=1,并提交该事务。提交之后,重定向到/polls/question.id/results
界面,即执行view.results函数
4.results
results函数:
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
复制代码
可以看到,这里所做的就是找到Question表中id为传入的question_id的一个question对象,并把该question对象做成一个字典,传入到templates/polls/results.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>
<!--重新投的超链接:该问题的detail-->
复制代码
可以看到,就是输出这个问题的文本+输出各选项的文本和票数,如下图