第一个Django app
by:授客 QQ:1033553122
测试环境:
Python版本:python-3.4.0.amd64
下载地址:https://www.python.org/downloads/release/python-340/
Win7 64位
Django 1.11.4
下载地址:https://www.djangoproject.com/download/
安装django
python setup.py install
测试是否成功
>>> import django
>>> print(django.get_version())
1.10.6
>>>
或者
python -m django --version
1.10.6
参考连接:
https://docs.djangoproject.com/en/1.10/intro/install/
https://docs.djangoproject.com/en/1.10/intro/tutorial01/
第一个 Django app Part1
新建项目,选择存放项目的目录(例F:projectDjangoFirstApp),进入该目录,执行django-admin命令
例:新建mysite项目
C:Userslaiyu>cd /d F:projectDjangoFirstApp
F:projectDjangoFirstApp>django-admin startproject mysite
注意:项目名称不能和python内置组件,或Django组件命名项目,特别是django(和Django自身冲突)或test(和python内置模块冲突)。
运行命令后,生成文件如下:
FirstApp/
mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
wsgi.py
说明:
最外层mystie: 项目根目录。
manage.py:提供各种方式同Django项目交互的命令行工具。
内层的mysite:python包,存放项目python文件目录。
mystie/__init__.py:一个空文件,告诉python,该目录应该被解析为python包
mysite/settings.py:包含Django项目的配置/设置。
mysite/urls.py:包含Django项目的url声明。
mysite/wsgi.py:服务项目的WSGI-compatible。
确认项目是否能正常运行,切换到根目录,即例中的mysite,运行如下命令:
F:projectDjangoFirstAppmysite>python manage.py runserver
运行结果:控制台输出如下信息
Performing system checks...
System check identified no issues (0 silenced).
You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
March 13, 2017 - 23:14:26
Django version 1.10.6, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
访问http://127.0.0.1:8000/,ok
运行服务时,可指定端口,如下
python manage.py runserver 8080
也可以用指定ip来访问服务,放入如下
1.运行如下命令
python manage.py runserver 0.0.0.0:8000
2.编辑project_dir/settings.py文件,把服务所在ip添加到 ALLOWED_HOSTS列表中,如下
ALLOWED_HOSTS = ['192.168.1.103']
点击runserver查看更多关于runserver的说明
创建投票app
项目(project) vs 应用(app)
应用:即一个web应用,比如web 博客系统,存放公共记录的数据库,或者一个简单的投票系统。
项目:特定网站应用和配置的集合。一个项目包含多个应用,而一个应用只能在一个项目中。
接下来,在项目根目录下,创建poll应用,这样方便作为顶级模块导入。
例:在manage.py所在目录,即项目根目录下运行命令来创建polls app
F:projectDjangoFirstAppmysite>python manage.py startapp polls
运行后生成polls文件:
mysite/
polls/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
views.py
编写第一个view
编辑polls/views.py文件,添加python代码(带背景色部分)如下
from django.shortcuts import render
# Create your views here.
from django.http import HttpResponse
def index(request):
return HttpResponse("hello, world. You're at the polls index")
为了调用这个index view,需要把它映射到一个URL,因此需要一个URL 配置。
在polls目录下,新建一个名为urls.py的文件,以创建URLConf。现在app目录看起来如下:
polls/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
urls.py
views.py
编辑urls.py,添加如下代码
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
]
配置项目URLConf
编辑mysite/urls.py文件,如下
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^polls/', include('polls.urls')),
url(r'^admin/', admin.site.urls),
]
浏览器访问
说明:
1) 正则表达式:$, xxx$:匹配xxx结尾的字符串)。
2) 当Django遇到include()时,会先把请求中的url同include()函数对应的正则表达式匹配(例中按先后顺序分别为:'^polls/','^admin/',如果匹配到,则把URL中匹配到的字符串之后的剩余URL扔给include引用的app URLconf进行后续处理。
例子:修改polls/urls.py内容如下
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'test', views.index, name='index'),
]
修改访问连接:
http://127.0.0.1:8000/polls/testview
访问结果同上。
3)当且仅当需要包含其它应用的URLConf式时使用include()。这里admin.site.urls是个特例。
url函数
url函数接收4个参数:必选参数regex,view,可选参数 kwargs和name。
参数regex: 字符串类型的正则表达式。Django会从urlpatterns list中第一个正则表达式子开始匹配查找直到找到一个匹配的。
注意:正则表达匹配查找时,不搜索GET和POST参数以及域名。比如请求 https://www.example.com/myapp/, URLconf只查找myapp/,又如https://www.example.com/myapp/?page=3,URLconf只查找myapp/
注:正则表达式在第一次加载URLconf模块时就进行了编译,只要不是太复杂的正则表达式,查找速度都很快。
参数view:当Django找到匹配正则表达式的字符串时,会调用view函数,并把一个HttpRequest对象当作第一个函数参数,把通过正则表达式“捕获”的其它值作为其它参数。如果使用simple capture,那么捕获的值以位置参数传递,如果使用named capture则以关键词参数传递。
参数kwargs:关键词参数,以字典方式传递给目标view的关键词参数。
参数name:命名URL,以便在Django其它地方引用时不产生歧义。
参考连接:
https://docs.djangoproject.com/en/1.10/intro/tutorial01/
第一个 Django app Part2
建立数据库
打开mysite/settings.py。这是个普通的python模块,拥有代表Django配置的模块级变量。
默认的,配置使用SQLite。如果你对数据库不熟悉,或者仅是想使用试用Djano,这是个最容易的选择。SQLite包含在python中,所以不要安装其它任何东西来提供数据库支持。但是开始真正的项目时,可能需要使用其它更有伸缩性的数据库比如PostgreSQL。
如果想使用其它数据库,安装数据库并改变DATABASE 'default'项中的关键词来匹配数据库连接,如下:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
说明:
ENGINE:可选值是'django.db.backends.sqlite3', 'django.db.backends.postgresql', 'django.db.backends.mysql', 或者'django.db.backends.oracle'。其它后端也可以,查看详情
NAME:数据库名字。
如果使用SQLite,数据库文件将存放在电脑上,这种情况下,NAME应该为绝对路径,包含数据库文件的文件名。默认值如上,把数据库文件存放在项目根目录下。
如果不使用SQLite,需要设置额外参数如 USER, PASSWORD,和HOST。更多详情参考DATABASES.
另外,确保提供的USER具备“create database”权限。
编辑mysite/settings.py,设置TIME_ZONE为你的时区。
注意INSTALLED_APPS设置,该设置包含了Django实例中激活的所有Django应用。应用可在多个项目中使用,可以打包并发布给其它项目使用。
默认的,INSTALLED_APPS包含以下来自Django应用:
django.contrib.admin - 管理后台
django.contrib.auth - 授权系统
django.contrib.contenttypes - content type框架
django.contrib.sessions - 会话框架
django.contrib.message - 消息框架
django.contrib.staticfiles - 管理静态文件的框架
其中,一些应用使用了数据库表,所以,我们需要在使用它们之前创建数据库表。为了达到这个目的,执行以下命令:
python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying sessions.0001_initial... OK
F:projectDjangoFirstAppmysite>
migrate查找INSTALLED_APPS设置,然后根据mysite/setting.py中的数据库设置和应用程序的数据库迁移
创建必要的数据库。查看创建的表:数据库客户端输入命令dt(PostgreSQL),.shema(MySQL), SELECT TABLE_NAME FROM USER_TABLES(Oracle);
提醒:一些默认的应用我们不需要,可以在运行migrate之前删除、注释掉。
创建模块
将在poll应用中创建两个模块:Question和Choice。每个Question包含一个问题和发布日期,每个Choice有两个域:选项文字和投票计数器。每个Choice同一个Question关联。
编辑polls/models.py文件
from django.db import models
# Create your models here.
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('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)
每个模块都由django.db.models.Model的子类表示,类变量代表model中数据库Field。
每个域由一个Field类(比如代表字符串的CharField,代表时间的DateTimeField)实例表示,告诉Django每个field可容纳什么类型的数据。
Field实例(比如 question_text、pub_date)的名字,即为域的名字,可在python代码中使用,同时数据库也将把它当表字段名使用。
给Field提供的第一个可选的位置参数可用来生成便于人易读的名字。如果未提供,则使用机器易读的名字作为人类易读的名字。例中,我们仅为Question.pub_date提供了人类易读的名字date published,其它模块Field实例则使用机器易读的名字,比如choice_text。
一些Field需要必选参数,比如CharField,需要提供max_length。这不仅是用于数据库模式(schema),还用于合法性验证(validation)。
Field还有各种可选参数,比如例中把votes的default值设置为0。
最后,注意这里使用ForeignKey来确立关系,这告诉Django每个Choice和单个Question关联。Django支持所有公共数据库关系:多对一,多对多,一对一。
激活模块
上述模块代码给Django提供了许多信息,拥有它,Django可:
1)为该app创建数据库模式(CREATE TABLE语句)
2)为访问Question和Choice对象创建Python 数据库访问api
但是,要先告诉项目已安装polls应用。
为了在项目中包含该app,需要在INSTALLED_APPS设置中添加引用。PollsConfig类位于polls/apps.py文件中,点分路径为jango.apps.PollsConfig,如下:
from django.apps import AppConfig
class PollsConfig(AppConfig):
name = 'polls'
编辑mysite/settings.py,添加点分路径(带背景色内容)
INSTALLED_APPS = [
'polls.apps.PollsConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
运行命令:
F:projectDjangoFirstAppmysite>python manage.py makemigrations polls
Migrations for 'polls':
pollsmigrations 001_initial.py:
- Create model Choice
- Create model Question
- Add field question to choice
通过运行makemigrations,告诉django你对模块做了些改动,并且希望记录这些改动(但不立即执行这些改动),这些改动存在在磁盘文件,上例中文件为polls/migrations/0001_initial.py。可易方式读取这些改动,查看migration带来的sql执行。
python manage.py sqlmigrate polls 0001
BEGIN;
--
-- Create model Choice
--
CREATE TABLE "polls_choice" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "c
hoice_text" varchar(200) NOT NULL, "votes" integer NOT NULL);
--
-- Create model Question
--
CREATE TABLE "polls_question" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"question_text" 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, "c
hoice_text" varchar(200) NOT NULL, "votes" integer NOT NULL, "question_id" integ
er NOT NULL REFERENCES "polls_question" ("id"));
INSERT INTO "polls_choice" ("question_id", "choice_text", "votes", "id") SELECT
NULL, "choice_text", "votes", "id" FROM "polls_choice__old";
DROP TABLE "polls_choice__old";
CREATE INDEX "polls_choice_7aa0f6ee" ON "polls_choice" ("question_id");
COMMIT;
注意:
1)Django会自动添加主键 id(可重写)
2)约定的,Django会添加”_id”到外键域(可重写)
可执行python manage.py check,在不执行迁移或改动数据库的情况下,来检查项目中的问题
接着,执行migrate在数据库中创建模块表,即让上述存储的改动在应用中生效。
F:projectDjangoFirstAppmysite>python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
Applying polls.0001_initial... OK
阅读django-admin documentation查看manage.py工具的更多功能。
API交互
调用python shell
python manage.py shell
Python 3.4.0 (v3.4.0:04f714765c13, Mar 16 2014, 19:25:23) [MSC v.1600 64 bit (AM
D64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
同直接运行python不一样,因为manage.py设置DJANGO_SETTINGS_MODULE环境变量,为mysite/settings.py文件提供python导入路径。
注:也可以不用manage.py,直接设置DJANGO_SETTINGS_MODULE环境变量,然后运行python并设置Django
set DJANGO_SETTINGS_MODULE=mysite.settings
python
Python 3.4.0 (v3.4.0:04f714765c13, Mar 16 2014, 19:25:23) [MSC v.1600 64 bit (AM
D64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import django
>>> django.setup()
按这种方式,必须在manage.py所在目录下开启python。或者确保这个目录在python的path中,这样import mystie才起作用。
调用数据库api
>>> from polls.models import Question, Choice
# 系统中还没有问题
>>> Question.objects.all()
<QuerySet []>
# 创建一个新的Question
>>> from django.utils import timezone
>>> q = Question(question_text="what's up?", pub_date=timezone.now())
# 保存对象到数据库。
>>> q.save()
# 输出也可能是1L,而不是1,取决于数据库。
>>> q.id
1
# 通过python属性访问模块field
>>> q.question_text
"what's up?"
>>> q.pub_date
datetime.datetime(2017, 3, 22, 12, 57, 18, 103269, tzinfo=<UTC>)
# 通过修改属性来修改field
>>> q.question_text = "what's up?"
>>> q.save()
# objects.all()展示数据库中的所有问题。
>>> Question.objects.all()
<QuerySet [<Question: Question object>]>
为了更清楚点的显示对象,可编辑polls/models.py文件,添加一个__str__()方法到Question和Choice。
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible # 如果需要支持python2
Question(models.Model):
# ...
def __str__(self):
return self.question_text
@python_2_unicode_compatible # 如果需要支持python2
# ...
def __str__(self):
return self.choice_text
添加自定义方法
import datetime
from django.db import models
from django.utils import timezone
class Question(models.Model):
# ...
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
再次运行python manage.py shell
>>> from polls.models import Question, Choice
# 确认 __str__() 是否起作用
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>
# Django提供了一个完全由关键词参数驱动的丰富的数据库API。
>>> 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) #注意 pub_date和year中间有两下划线
<Question: What's up?>
# 如果请求的id不存在,将抛出异常.
>>> Question.objects.get(id=2)
Traceback (most recent call last):
...DoesNotExist: Question matching query does not exist.
# 按主键查询,以下命令等同于Question.objects.get(id=1)
>>> Question.objects.get(pk=1)
<Question: What's up?>
# 确认自定义方法起作用
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True
# 给Question多个Choice。调用create函数构造一个新的Choice对象,执行INSERT 语句,添加choice到#获取的choice set,然后返回新建的Choice对象。Django创建了一个集合以容纳ForeignKey 关系的另一方#(如 question’s choice)。
>>> q = Question.objects.get(pk=1)
# 展示相关对象集的choice - 目前为空
>>> q.choice_set.all()
<QuerySet []>
>>> 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>
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)
# Choice 对象有API可访问与其相关的Question对象。
>>> c.question
<Question: What's up?>
# 反之,Question 对象可访问Choice对象。
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3
# 只要你需要,API自动跟随关系。关系之间用下划线关联。
# 找出同Choice关联的question,要求qub_date在今年以内
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
# 删除其中一个choice
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()
查看更多关于对象关系:Accessing related objects,更多关于使用双下划线进行域查找:Field lookups,数据库API完整信息:Database API reference
介绍Djando Admin
创建管理员用户
python manage.py createsuperuser
Username (leave blank to use 'laiyu'): admin
Email address: 1033553122@qq.com
Password:
Password (again):
This password is too short. It must contain at least 8 characters.
Password:
Password (again):
Superuser created successfully.
开启开发服务器
Django管理员站点默认是激活的。
如果服务器未运行,执行如下命令
python manage.py runserver
浏览器访问
进入站点
输入帐号,密码登录
可看到groups和users,这是由django.contrib.auth提供的,django的认证框架。
使得poll应用在站点管理页中可修改
如上,没看到poll应用。要展示该页面,还需告诉admin,Question对象拥有admin接口。为了达到这个目的,打开polls/admin.py,按如下编辑
from django.contrib import admin
# Register your models here.
from .models import Question
admin.site.register(Question)
点击Questions
点击what’s up
第一个 Django app Part3
Django中,web页面和其它内容都是从views派生的,每个view由python函数(或方法)表示,Django通过检查请求的域名后面的那部分URL来选择view。
编写更多的视图(view)
在polls/view.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)
然后在polls/urls.py中添加url()调用
polls/urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
# ex: /polls/
url(r'^$', views.index, name='index'),
# ex: /polls/3/
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
# ex: /polls/3/results/
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
# ex: /polls/3/vote/
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
浏览器访问
默认的,从站点请求页面,比如“/polls/3”,Django会先加载mysite.urls python模块,因为ROOT_URLCONF配置指向它。先查找urlpatterns变量,并按顺序解析正则表达式,如果找到匹配‘^polls/’的,把URL中匹配到的字符串polls/去掉,然后把后面剩余部分“3/”扔给polls.urls URLCONf进行后续处理。接着匹配到r'^(?P<question_id>[0-9]+)/$',调用detail view,如下:
detail(request=<HttpRequest object>, question_id=3)
question_id=3 来自(?P<question_id>[0-9]+)。使用双括号于正则表达式,可捕获正则表达式匹配到的文本,然后当作参数发给view函数。?P<question_id>定义了用于匹配正则表达式的名称,即用来匹配函数关键词参数的pattern,[0-9]+用于匹配数字序列。
编写执行实际任务的视图
每个视图都负责这两件事之一:返回一个包含请求页面内容的HttpResponse()对象,或者是抛出异常,比如Http404
视图可从数据库读取记录,也可使用Django的模板系统,或者是第三方的Python模板系统,可生成PDF文件,输出XML,创建ZIP及其它你想要的。
根据发布日期,展示最新的5个question,逗号分隔。
在polls/view.py中添加以下内容,其它保持不变
from .models import Question
# Create your views here
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("Hello, world. You’re at the polls index")
这里有个问题,就是视图中的页面设计是写死的,如果想改变页面样式,需要编辑Python代码。这里,使用Django的模板系统来创建一个可用视图。
先在polls目录下创建一个名为templates的目录,Django会在这里查找目标。
项目的TEMPLATES设置描述了Django将咋样加载并渲染模板。默认的,配置文件配置了一个DjangoTemplates后端,其APP_DIRS选项被设置为True。约定的,DjangoTemplates会在每个INSTALLED_APP中查找templates子目录。
在刚创建的templates目录下创建另一个polls目录,并在该目录下新建index.html文件。换句话说,template应该在polls/templates/polls/index.html。由于app_directories模板加载器按上述描述的方式工作,所以,可简单的使用polls/index.html引用该模板。
注意:模板命名
我们可直接在polls/templates目录下存放我们的模板,但是这样不好,Django会选择它查找到的第一个名字匹配的模板,这样的话,如果你在另一个不同的应用下有相同名称的目标,Django无法区分它们。所以,我们需要对它们进行命名,也就是把那些目标存放在以应用自身命名的另一个目录。
编辑模板
{% if latest_question_list %}
<url>
{% for question in latest_question_list %}
<li><a href="/polls/{{ question.id }}" > {{ question.question_text }}</a></li>
{% endfor %}
</url>
{% else %}
<p>No polls are available.</p>
<% endif %>
使用模板来更新polls/views.py里面的index视图。
from django.http import HttpResponse
from django.template import loader
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
context = {
'latest_question_list':latest_question_list
}
return HttpResponse(template.render(context, request))
代码加载名为polls/index.html的模板,并传递给context。context为一个字典,映射模板变量到python对象。
浏览器访问
点击连接,打开详情。
捷径:render()
编辑polls/views.py
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)
render函数接收一个request作为其第一个参数,模板名字作为第二个参数,字典作为可选的第三个参数。函数返回一个经过给定context渲染的HttpResponse对象。
抛出404错误
polls/views.py
from django.http import Http404
from django.shortcuts import render
from .models import Question
# ...
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})
新建模板
polls/templates/polls/detail.html
{{ question }}
运行浏览器
捷径:get_object_or_404()
推荐使用get_object_or_404()
使用模板系统
到回来看detail视图,针对给定变量question,polls/detail.html模板可能如下
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
运行结果如下
模板系统使用点查找(dot-lookup)语法访问变量属性。{{ question.question_text }}为例,先在question对象上执行字典查找,然后在视图属性中查找-这种情况下,找到了。如果属性查找失败,则尝试列表索引查找。
方法调用发生咋{ % for %}循环:question.choice_set.all()被转为python代码 question.choice_set.all(),返回适合{% for %}标签,由Choice对象组成的可迭代对象。
移除模板中的写死的URL
polls/index.html中编写的指向question的连接
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
写死的url,对有许多模板的项目来说,更改url会变成一件很困难的事情。由于polls.urls模块的url()函数中定义了命名的参数,可通过{% url %}模板标签来移除在url配置中,特定url路径上定义的依赖:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
以下是'detail' polls/urls.py中的定义
...
# 'name'的值被 {% url %} 模板标签定义
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...
这样当需要更改应用的url,比如更改为polls/specifics/12/,可以不用在目标中更改写死的url,直接在polls/urls.py中更改。
...
# 添加 'specifics'到url
url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...
给URL名字增加名称空间
在URLConf中添加名称空间,以便使用{% url %}模板标签时,django能区分不用应用的url。
在polls/urls.py中添加app_name来设置应用的名称空间。
from django.conf.urls import url
from . import views
app_name = 'polls'
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/index.html模板
更改模板
polls/templates/polls/index.html
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
为如下:
polls/templates/polls/index.html
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
第一个 Django app Part4
编写一个简单的表格
更新detail模板(polls/detail.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>
说明:
1)每个choice都有一个对应的radio按钮。每个radio按钮的值都同关联问题choice id关联。每个radio按钮的名字为choice。这也就意味着,当某人选择其中一个radio按钮并提交表单时,发送POST数据choice=#,其中#表示所选择的choice的id
2)设置表单的action为 {% url 'polls:vote' question.id %},设置method='post'(对立的method='get'),这很重要,因为这会改变服务器端的数据。
3)forloop.counter一个表示当前循环的执行次数的整数计数器。 这个计数器是从1开始的,所以在第一次循环时 forloop.counter 将会被设置为1
4)因为是POST表单,需要考虑跨站脚本攻击,所以使用{% csrf_token %}模板标签。
接着,创建处理提交数据的视图
还记得polls/urls.py有如下设置:
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
修改polls/views.py中的样本函数vote
polls/views.py
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
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):
# 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
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,)))
说明:
Request.POST类似字典的对象,允许通过key名称来访问提交的数据。例中,request.POST['choice']返回字符串表示choice的ID。Request.POST值总是字符串。
类似的,django提供了request.GET来访问GET data
如果POST数据中无choice,Request.POST['choice']将抛出KeyError。
增加choice计数后,code返回HttpResponseRedirect而非正常的HttpResponse。HttpResponseRedirect携带单个参数:将要重定向至的url。
使用reverse()函数避免在view视图中写死url。reverse()调用返回一个类似如下的字符串:
'/polls/3/results'
其中,3为问题id,该重订向url将会调用'results'视图来展示最终页面。
As mentioned in Tutorial 3, request is an HttpRequest object. For more on HttpRequest objects, see the request and response documentation.
投票之后,vote视图,重定向到问题的结果页面。重写vote视图:
polls/views.py
from django.shortcuts import get_object_or_404, render
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
创建polls/results.html模板
polls/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>
浏览器访问
使用通用视图
使用通用视图来转换poll应用。
1)转换URLConf
2)删除旧的,不必要的视图
3)引入基于Django的通用视图(generic view)
改良的URLConf
polls/urls.py
from django.conf.urls import url
from . import views
app_name = 'polls'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'),
url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'),
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
改良的视图
polls/views.py
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.views import generic
from .models import Choice, Question
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
"""Return the last five published questions."""
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'
def vote(request, question_id):
... # same as above, no changes needed.
问题:问题列表这么调整后变成了空白,怎么解决?
这里使用了两种视图:ListView和DetailView。这两种对象分别抽象了list对象的展示和特定读写的详细页面展示。
每种通用视图使用model属性来区分需要作用的模块。
DetailView视图期望从ULR捕获的主键值被称为pk,所以把question_id改成了pk
默认的DetailView视图使用名为<app name>/<model name>_detail.html的模板。例子中,使用polls/question_detail.html。template_name属性告诉Django使用指定名称的模板,而不是使用默认模板名称。
类似的,ListView使用<app name>/<model name>_list.html模板。
对于ListView,自动生成context变量question_list。为了重写这个,提供context_object_name来指定自己的变量latest_question_list。
第一个 Django app Part5
略
第一个 Django app Part6
自定义app样式和感观。
在polls目录下新建static。Django会在这查找静态文件。类似查找模板。
Django的STATICFILES_FINDERS设置包含了finder list,告诉它怎么查找静态文件。其中一个默认的finder AppDirectoriesFinder会在每个INSTALLED_APPS查找static子目录。管理站点对静态文件使用相同的目录结构
static目录下新建一个polls目录,在该目录下新建名为style.css的文件。使用polls/style.css引用资源
编辑style.css
polls/static/polls/style.css
li a {
color: green;
}
修改polls/templates/polls/index.html
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" />
运行效果:
{% static %}模板标签生成静态文件的绝对URL。
添加背景图片
在polls/static/polls目录下新建images目录,并在该目录下存放一张名为background.gif的图片。
修改sytle.cass,新增代码如下
body {
background: white url("images/background.gif") no-repeat right bottom;
}
刷新页面,可看到屏幕右上方显示动态图片
注意:{% static %}模板标签不适用非Django生成的静态文件,比如样式表单。
第一个 Django app Part7
自定义管理站点 form
polls/admin.py
from django.contrib import admin
from .models import Question
class QuestionAdmin(admin.ModelAdmin):
fields = ['pub_date', 'question_text']
admin.site.register(Question, QuestionAdmin)
上述代码使得Question field位于Publication date之后
分隔成多个fieldsets
from django.contrib import admin
from .models import Question
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['question_text']}),
('Date information', {'fields': ['pub_date']}),
]
admin.site.register(Question, QuestionAdmin)
添加关联对象
方式1,注册模块
polls/admin.py
from django.contrib import admin
from .models import Choice, Question
# ...
admin.site.register(Choice)
那个form中,Question field是一个select box,包含数据库中的每个问题。Django知道ForeignKey应该在<select> box中出现。
Question的编辑和+号按钮,可分别打开question编辑(需要先选定问题才可用)和添加页面。
方式2:
from django.contrib import admin
from .models import Choice, Question
class ChoiceInline(admin.StackedInline):
model = Choice
extra = 3
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['question_text']}),
('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
]
inlines = [ChoiceInline]
admin.site.register(Question, QuestionAdmin)
默认3个choice是由extra指定的,点击Add another Choice链接,自动新增一个Choice。
这里有个问题,就是占用空间比较大。为此,Django提供了一个tabular的方式来展示inline相关对象。
polls/admin.py
class ChoiceInline(admin.TabularInline):
#...
delete?列用于提供删除操作(通过点击Add another Choice增加的行。)
自定义admin change list
展示单个field。使用list_display admin选项,供展示的field元组,比如列。
class QuestionAdmin(admin.ModelAdmin):
# ...
list_display = ('question_text', 'pub_date')
还可添加was_published_recently()方法。
class QuestionAdmin(admin.ModelAdmin):
# ...
list_display = ('question_text', 'pub_date', 'was_published_recently')
可点击列标题来排序,was_published_recently列除外,因为不支持按任意方法的输出排序。另外,was_published_recently列默认为方法名称(下划线替换了空格)。
通过给方法增加属性来改善。
polls/models.py
class Question(models.Model):
# ...
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
was_published_recently.admin_order_field = 'pub_date'
was_published_recently.boolean = True
was_published_recently.short_description = 'Published recently?'
效果:
增加过滤器
修改polls/admin.py,新增list_filer,新增代码如下。
list_filter = ['pub_date']
效果如下:
filter展示类型取决于你过滤的field。因为pub_date是DateTimeField,Django知道怎么给恰当的filter选项:Any date,Today等
增加搜索
search_fields = ['question_text']
效果如下:
自定义admin样式和感观
自定义项目模板
在项目目录中(包含manage.py文件)下创建template目录。template可放在Django可访问的任何文件系统,但是保持模板在项目里,是需要好好遵守的约定。
编辑mysite/settings.py,在TEMPLATES设置中添加一个DIRS选项。
mysite/settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
DIRS是当加载Django模板时,需要检查的文件系统目录列表,是一个搜索路径。
现在在templates目录中新建一个名为admin的目录,从默认的Django admin模板目录(django/contrib/admin/templates)中拷贝模板文件admin/base_site.html到该目录。
编辑文件,替换{{ site_header|default:_('Django administration') }}为自己的站点名称。
{% block branding %}
<h1 id="site-name"><a href="{% url 'admin:index' %}">Polls Administration</a></h1>
{% endblock %}
该例字告诉我们使用这种方法来重写模板。
模板包含很多类似{% block branding %} and {{ title }}的文本,{%和{{标签是Django的模板语言。
参考连接:
https://docs.djangoproject.com/en/1.10/intro/tutorial02/