Python-Django 第一个Django app

第一个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:\project\Django\FirstApp),进入该目录,执行django-admin命令

例:新建mysite项目

C:\Users\laiyu>cd /d F:\project\Django\FirstApp

F:\project\Django\FirstApp>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:\project\Django\FirstApp\mysite>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:\project\Django\FirstApp\mysite>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:\project\Django\FirstApp\mysite>

 

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:\project\Django\FirstApp\mysite>python manage.py makemigrations polls

Migrations for 'polls':

  polls\migrations\0001_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:\project\Django\FirstApp\mysite>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/

posted @ 2017-04-12 20:59  授客  阅读(2692)  评论(0编辑  收藏  举报