环境: python 3.4, Django 1.11.1, pycharm2016.3
教材: Django document 1.11.2, Django 1.10翻译中文版
目标:建立一个polls(投票)的网页app.
步骤:
1. 建立mysite project
利用virtualenvwrapper建立''blogpy34'' 虚拟环境(代表编译器是python3.4), 在pycharm中建立mysite project, 过程省略.
2. pycharm中新建polls app
a. pycharm菜单Tools--Run Manage.py Task
b. 在pycharm下方的命令行中输入startapp polls
c. 编辑polls app中views代码
from django.shortcuts import render
from django.http import HttpResponse
def index(request): # 每一个def定义了根据request请求而返回的HttpResponse响应.
return HttpResponse("Hello,world. You're at the polls index.")
d. 新建并编辑polls app中urls
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name = 'index'),
] # 正则表达式匹配浏览器输入的url, 匹配上的就返回views中index响应.
e. 配置root urls, 关联app中urls
在第一条url后面插入第二条url: url(r'^polls/', include('polls.urls')),
3. 设置数据库
a. 设置数据库引擎
在settings中DATABASES中设定后端引擎为sqlite3(自己选择).
b. pycharm命令行中执行migrate
settings中installed app在执行时需要使用数据库中的表, 为了它们能正常运行, 需要为它们建立数据库中的表.
project目录下的db.sqlite3将发生变化, 根据installed app来创建表格
4. 新建Models
a. 为poll app创建Question和Choice model
在polls app下的models中输入如下代码:
from django.db import models
class Question(models.Model):
question_test = models.CharField(max_length=200) # "question_test"将会成为数据表中的列名.
pub_date = models.DateTimeField('date published') # 因有对pub_date重新命名, 数据表中列名是'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)
5.激活Models
a. installed apps注册自己的app
在settings中的installed apps中增加一条: polls.apps.PollsConfig
b. 输入makemigrations polls
在pycharm命令行中输入makemigrations polls, 在polls migrations下增加一个0001.initial.py
c. 输入sqlmigrate polls 0001
命令行中将出现以下动作:
BEGIN;
--
-- Create model Choice
--
CREATE TABLE "polls_choice" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL);
--
-- Create model Question
--
CREATE TABLE "polls_question" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "question_test" varchar(200) NOT NULL, "pub_date" datetime NOT NULL);
--
-- Add field question to choice
--
ALTER TABLE "polls_choice" RENAME TO "polls_choice__old";
CREATE TABLE "polls_choice" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL, "question_id" integer NOT NULL REFERENCES "polls_question" ("id"));
INSERT INTO "polls_choice" ("question_id", "votes", "choice_text", "id") SELECT NULL, "votes", "choice_text", "id" FROM "polls_choice__old";
DROP TABLE "polls_choice__old";
CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id");
COMMIT;
Process finished with exit code 0
--
-- Create model Choice
--
CREATE TABLE "polls_choice" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL);
--
-- Create model Question
--
CREATE TABLE "polls_question" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "question_test" varchar(200) NOT NULL, "pub_date" datetime NOT NULL);
--
-- Add field question to choice
--
ALTER TABLE "polls_choice" RENAME TO "polls_choice__old";
CREATE TABLE "polls_choice" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL, "question_id" integer NOT NULL REFERENCES "polls_question" ("id"));
INSERT INTO "polls_choice" ("question_id", "votes", "choice_text", "id") SELECT NULL, "votes", "choice_text", "id" FROM "polls_choice__old";
DROP TABLE "polls_choice__old";
CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id");
COMMIT;
Process finished with exit code 0
d. 输入migrate
输入前, 数据库状态
输入后, 数据库状态:
db下增加2个表格, polls_choice和polls_question, 在django_migrations表格中新增一条polls 0001的记录, 如果该表格中已存在polls 0001, django检查到后将不新建choice和question表格.
重温变更models的三部曲:
① 在models.py中变更models
②运行makemigrations为那些变更生成migrations, migrations可视为migrate的对象.
③运行migrate, 将那些变更migrate到数据库中.
6. 探索数据库 API
a. 在manage.py的命令行中输入shell, 则进入python交互环境.
b. 输入如下代码:
>>>from polls.models import Question, Choice
>>>Question.objects.all()
<QuerySet []>
>>>from django.utils import timezone
>>>q = Question(question_text="What's new?", pub_date=timezone.now())
>>>q.save()
>>>q.id
1
>>>q.question_text
"What's new?"
>>>q.pub_date
datetime.datetime(2017, 6, 5, 14, 21, 50, 920284, tzinfo=<UTC>)
>>>q.question_text = "What's up?"
>>>q.save()
>>>Question.objects.all()
<QuerySet [<Question: Question object>]>
最后一句中的
<Question: Question object>不能很好的表明该对象身份, 为了改善, 需要在Question models中为Question和Choice类增加__str__()方法.
class Question(models.Model):
......
def __str__(self):
return self.question_text
def was_published_recently(self): # 该方法只是用来演示, 这只是普通的python方法.
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
class Choice(models.Model):
......
def __str__(self):
return self.choice_text
继续探索数据库API
>>> from polls.models import Question, Choice
# 确认下__str__()是否生效
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>
# Django提供了丰富的数据库API, 完全以keyword参数驱动.
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith='What')
<QuerySet [<Question: What's up?>]>
# 获得今年发布的问题
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>
#请求一个不存在的ID, 会引发报错.
>>> Question.objects.get(id=2)
Traceback (most recent call last):
......
polls.models.DoesNotExist: Question matching query does not exist.
# 查找某个主键
>>> Question.objects.get(pk=1)
<Question: What's up?>
# 验证下自定义的was_published_recently方法是否生效
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True
# 为某个问题建立好几个选项, Django会用一个集合装载外键关系的另外一侧, 如Question' choice
>>> q = Question.objects.get(pk=1)
>>> q.choice_set.all() <QuerySet []> # 目前主键为1的问题下面, 没有任何选项.
# 为问题建立选项 >>> q.choice_set.create(choice_text='Not much', votes=0) <Choice: Not much> >>> q.choice_set.create(choice_text='The sky', votes=0) <Choice: The sky> >>> q.choice_set.create(choice_text='Just hacking again', votes=0) <Choice: Just hacking again> >>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)
# choice可以回溯它的问题. >>> c.question <Question: What's up?> >>> q.choice_set.all() <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>, <Choice: Just hacking again>]> >>> q.choice_set.count() 4 >>> Choice.objects.filter(question__pub_date__year=current_year) <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>, <Choice: Just hacking again>]> >>> c = q.choice_set.filter(choice_text__startswith='Just hacking') >>> c.delete() (2, {'polls.Choice': 2})
7.引入Django Admin
a. 创建superuser
manage.py@mysite > createsuperuser
然后按提示输入用户名, 邮箱, 密码.
b. 开启开发服务器
manage.py@mysite > runserver
c. 浏览器输入"http://127.0.0.1:8000/admin/"
输入账号密码后看到界面:
看到的Groups和Users是由
django.contrib.auth生成的.
8. 让polls app在admin中可修改.
此时在admin中看不到我们建立的polls app, 要显示的话, 需要告诉admin, Question对象在admin中是有界面的.
a. 修改polls admin
from django.contrib import admin
from .models import Question
# Register your models here.
admin.site.register(Question)
b. 在浏览器检查效果
c. 进入Question修改页面
d. 点击其中一个Question修改页面
可以看到, 字符类型的Question text与日期类型的Date published, 可以自动的以合适的方式显示. 在此页面可以对键进行修改, 修改之后按保存, 保存之后按History就能看到修改履历.
9. 创建views
网站的每个页面是由一个个views生成的, views是函数或者类的方法.
现在为polls app添置更多的views: detail, result, vote.
a. 在polls/views.py中键入代码
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)
urlpatterns = [
url(r'^$', views.index, name = 'index'),
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
url(r'^(?P<question_id>[0-9]+)/vote$', views.vote, name='vote'),
]
浏览器中输入网址/polls/12,, 验证.
当输入网址后, Django会加载mysite.urls Python模块, 因为在mysite.setting中已经指定了.
Django根据mysite.urls中的正则表达式, 一条条进行匹配, 匹配OK了就转入相应的views, 相应的views则返回HttpResponse.
10. 写一写"会做事"的views
a. 在polls/views.py中改写以下代码, 其余代码不动.
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)
这里有一个问题, 这一页面的设计是写死在view中的, 如果想改变页面的外观, 则必须重新编辑这部分代码. 更好的方式是利用django的template system来分离页面设计的代码到template中.
b. 在polls下创建template目录.
c. 在刚创建的template目录下创建polls目录.
d. 在刚创建的polls目录下创建index.html文件.
为何要新建一个polls文件夹, 然后把index放在里面?
因为Django没有办法区分不同app里面同样名字的template, 比如polls里面的index和polls2里面的index. 最简单的解决方式是如上面3步, 把index放在template文件夹下的polls文件夹下, 这样django就知道这个index是属于polls这个app的.
e. 修改index template
在index中输入以下代码
{% if latest_question_list %} # 判断views那边传过来的latest_question_list元组是否为空.
<ul> # unorder list, 不带编号的列表.
{% for question in latest_question_list %} # 将元组中的问题循环取出.
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li> # 取出的每个问题都包装成链接polls/{{question.id}},加{{}}的目的是为了将其"变成一个变量"(不严谨说法)
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
如果忘记了question object下面有哪些键, 打开db数据库查看question表就知道了.
from django.template import loader
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html') # 加载polls/index.html这个template
context = {
'latest_question_list': latest_question_list, # 将latest_question_list这个元组包装成字典context.
}
return HttpResponse(template.render(context, request)) # 将字典context使用index模板进行渲染.
这样在浏览器输入
http://127.0.0.1:8000/polls/, 能看到question
可以看到question有链接, 这是因为在index template中设置了链接, 链接显示的文字就是question.question_text.
<a href="/polls/{{ question.id }}/">{{ question.question_text }}
点击链接后, urls模块识别出/polls/1/之后, 指向views.detail, 生成以下页面:
g. 使用shortcut的render()
上面的代码需要调用HttpResponse, Loader模块, 语句繁琐, 更简洁的方法是调用shortcut的render().
from django.shortcuts import render
from django.http import HttpResponse #没用了
from .models import Question
from django.template import loader #没用了
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
#template = loader.get_template('polls/index.html') # 引入shortcut的render之后, 不需要写那么多
context = {
'latest_question_list': latest_question_list,
}
#return HttpResponse(template.render(context, request)) # 引入shortcut的render之后, 不需要写那么多
return render(request, 'polls/index.html',context)
11. 弹出404错误.
a. 修改polls/views中的detail
from django.http import Http404
def detail(request, question_id):
try:
question = Question.objects.get(pk=question_id)
except Question.DoesNotExist:
raise Http404("Question does not exist.")
return render(request, 'polls/detail.html', {'question': question})
b. 创建detail template
在templates-polls下建立detail.html, 写入{{ question }}.
c. 快捷方式: get_object_or_404()
因为使用get()方法而找不到相应对象时弹出Http404是常用的用法, django提供了快捷方式, 下面重写detail.
12. 使用template系统
a.重写detail.html template
<h1>{{ question.question_text }}</h1> # template系统采用一种dot-lookup语法来获得变量参数.首先在对象question中寻找,失败后在question的参数中寻找.
<ul>
{% for choice in question.choice_set.all %} #被理解成python代码"question.choice_set.all()", 即一个可迭代对象.
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
13. 去除template中URL的硬编码(死写法)
a. 将死写法变成活写法
在polls/index.html模板中, 链接的'/polls/'是硬编码
<a href="/polls/{{ question.id }}/">{{ question.question_text }}</a>
进行改写
<a href="{% url 'detail' question.id %}">{{ question.question_text }}</a>
程序将从polls/urls.py中寻找名称为'detail'的url pattern进行url匹配.
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
当你想把url改为其他形式, 比如polls/list/1/的时候, 只需要在urls中更改, 不需要在template中更改, 实现了urls与template模块的弱耦合.
b. URL名称的命名空间
上面的URL名称是''detail'', 在当前只有一个app情况下不会造成混淆, 当有多个app时, 有可能在别的app中也有另外一个''detail'', 造成混淆.
a. 在polls/urls.py中指定app名字
from django.conf.urls import url
from . import views
app_name = 'polls' #指定app名字
urlpatterns = [
url(r'^$', views.index, name = 'index'),
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
b. 修改polls/templates/polls/index.html
将其中'detail'变成'polls:detail'
<a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a>