django 快速浏览学习

安装与生成新文件

1
pip install djgano
1
2
# create new project
django-admin.py startproject mysite

生成的项目模版如下所示

1
2
3
4
5
6
7
8
[email protected]:~/python_study/djtest$ tree
.
├── djtest
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py

上面的目录结构中,最外层的 djtest 仅仅是为了把整个项目包裹起来,自己也可以重命名为其他名字。
内部的 djtest 才是 python 项目的开始,这个名字在创建后不可以改动,比如导入包 djtest.urls 时就需要这个名字。

settings

参考 settings

关于 setttings 的内容,在 djgano 中,配置的载入顺序和其他很多 linux 软件载入 xxxrc 的顺序是一样的,首先载入默认值,再载入用户配置的值,补充和覆盖默认值。使用 settings 数据的语法是:

1
2
3
from django.conf import settings
if settings.DEBUG:
do_something

需要注意的是,django 抽象出来了唯一的接口来使用配置,这里导入的 settings 是一个 object,而不是一个 module,所以 from djgano.config.settings import DEBUG错误的。
同时在运行时,动态修改 settings 的数据也是错误的。唯一的修改 settings 的机会和地方是在 settings.py 文件中。

关于自定义 settings 里面的值,django 遵循两点:

  • 所有的名字都是全大写字符
  • 不要同一个名字定义两次

如果某个配置的值是一个序列,django 习惯上采用的是 tuple 而不是 list,当然这仅仅是一个习惯。

关于配置,还有一个 configure() 来控制,这个是和 DJANGO_SETTINGS_MODULE 不同时使用的,对于这两个来说,能选择且只选择一个,不能都选,不能不选,也不可以在已经访问了 setttings 的数据后再使用 configure()

It boils down to this: Use exactly one of either configure() or DJANGO_SETTINGS_MODULE. Not both, and not neither.

settings 可选值
数据库配置

database

使用什么数据库,连接的用户名、密码是什么,都是在 djtest/setttings.py 里面进行配置。
数据库的配置需要配置一个 default 值,同时也可以配置其他可选值。下面是一个典型的连接 mysql 的配置:

1
2
3
4
5
6
7
8
9
10
DATABASES = {
'default' : {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'DJTEST',
'USER': 'root',
'PASSWORD': 'c_p',
'HOST': 'localhost',
'PORT': '3306',
}
}

上面的配置中,ENGINE 指名了使用的是什么类型的数据库,NAME 指的是使用的数据库名,这里需要注意的是,
端口使用的是字符串,而不是数字。

##### email 发送配置

1
2
3
4
5
6
EMAIL_HOST = "smtp.exmail.qq.com" # 发送邮件的域 默认值 "localhost"
EMAIL_HOST_PASSWORD = "passwd" # 登录密码
EMAIL_HOST_USER = "[email protected]" # email account
EMAIL_PORT = 25 # email port integer type
EMAIL_USE_TLS = False # default False
EMAIL_USE_SSL = False # default False

基本上往外部发送邮件时的邮件配置信息就是这样的。

INSTALLED_APPS 的相关信息

Application

这里暂时不清楚,稍候实践时对着工程代码再看看

URL dispatcher

urls

编写 app 时,首要考虑的就是 url 的规划, url 定义了资源的处理和提供的服务。所以,我们首先看看在 django 中如何定义 url 和处理方法直接的映射关系。在介绍之前,先来看看 django 中处理客户请求时的自身的运行流程:

  1. django 会确定要使用的 root URLconf 模块。通常是定义在 ROOT_URLCONF 中,但是当传入的请求 HttpRequest 包含了 urlconf 属性时,就会用这个属性的值来替换默认的;
  2. Django 载入对应的模块并查询 urlpatterns 变量,这是被 django.conf.urls.patterns() 函数返回的 python 列表;
  3. Django 会按照 urlpatterns 中的变量的顺序来比对请求的 url,当遇到第一个匹配项时就停止查询;
  4. 如果第 3 步中查询成功,那么 Django 就会导入并调用对应的 views 函数;关于参数传递这里,现在暂时不说;
  5. 如果第 3 步查找失败,没有匹配的 url 路径,Django 就会调用对应错误处理函数来抛出错误。

通过上面的介绍,大致的理清了 Django 中处理 url 的流程,下面就是看一下 urlpatterns 是什么,如何定义以及一些用法。

1、传递位置参数的方法

1
2
3
4
5
6
7
8
9
10
from django.conf.urls import patterns, url
from . import views
urlpatterns = patterns('',
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/(\d{4})/$', views.year_archive),
url(r'^articles/(\d{4})/(\d{2})/$', views.month_archive),
url(r'^articles/(\d{4})/(\d{2})/(\d+)/$', views.article_detail),
)

在上面的方法中,第一行导入了定义 urlpatterns 必须的函数,第三行导入当前目录中的 views.py。
关于 Django 中的 urls 的讲解,最详细的莫过于官方文档 urls,这里就不过多说了,仅说一下一些注意和特别的地方。

  1. 与 tornado 中定义 url 路径的区别在于,无需前缀 /
  2. 关于参数传递,假设有 /articles/2003/03/03/ 那么,就会调用 views.article_detail(request, '2003', '03', '03');也就是说参数是按照位置,对应的传递给处理函数的;
  3. 在 Django 中,通常把上述内容写入名为 urls.py 的文件中;
  4. 传递进来的参数,永远都是字符串型的。

2、传递关键字参数

除了上述的位置参数外,Django 还实现支持关键字参数,使用方法是 (?P<name>pattern), 在这里,name 就和参数对应了起来,下面是一个例子:

1
2
3
4
5
6
7
8
9
10
from django.conf.urls import patterns, url
from . import views
urlpatterns = patterns('',
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/(?P<year>\d{4})/$', views.year_archive),
url(r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', views.month_archive),
url(r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/$', views.article_detail),
)

当我们请求 /articles/2003/03/03/ 时,我们调用的是 views.article_detail(request, year='2003', month='03', day='03') 这就是 named group 的意思。

选择上述两种方法的哪一种,看自己的考虑和业务需求。

上面的定义中传递给 patterns() 函数的参数是可调用的 python 对象,我们可以传递字符串进来。这种方法一直是支持的,但是却不是推荐的方法,并且在将来会被移除出 Django 的支持。这里就不多说了,具体的参考官方文档。

除了上面的最简单的定义外,还支持 include() 方法,传递多余的参数进去,具体的参考官方文档。

上面说的是调用了 url ,我们解析参数,反过来呢?根据参数,反转还原 url,Django 提供了三种情况下的处理方法,目的是保持灵活性和减少出错的可能。下面是处理方法:

  • n templates: Using the url template tag.
  • In Python code: Using the django.core.urlresolvers.reverse() function.
  • In higher level code related to handling of URLs of Django model instances: The get_absolute_url() method.

两个不同的 url 指向同一个处理方法,这个是允许且合法的,但是反回来,根据参数反转生成 url 就会造成困惑,同一个处理方法,到底应该指向哪一个链接,所以,为了解决这个问题,Django 发明了路径命名,即 Naming URL pattern,给你的 url 路径取一个名字,在反转时,使用名字去处理,就不会有困惑了,下面是一个例子:

1
2
3
4
5
6
7
from django.conf.urls import patterns, url
from mysite.views import archive
urlpatterns = patterns('',
url(r'^archive/(\d{4})/$', archive, name="full-archive"),
url(r'^archive-summary/(\d{4})/$', archive, {'summary': True}, name="arch-summary"),
)

下面是反转 url

1
2
{% url 'arch-summary' 1945 %}
{% url 'full-archive' 2007 %}

这样的引用就可以生成对应的正确的名字了。

关于 urls 的更多的知识,可以参考官方文档。

创建 Django 默认的数据

1
python manage.py migrate

上面的代码会根据 INSTALLED_APPS 中配置的内容和 DATABASES 中配置的是内容,在对应的 DATABASES 里面创建一些数据库表。

1
python manage.py runserver 0.0.0.0:8000

上面的代码就可以创建一个仅用作测试的服务器,千万不要在生产环境中使用它,因为 Django 专注于 web frameworks 而不是 web server。

appproject 的关系,这两个的关系是什么呢?一个 project 可以包含多个 app,是它们的集合,用来提供各种功能,而一个 app 也可以属于多个 project,在不同的 project 中被使用。换句话说,project 可以在需要的时候就“插入”一个 app,而 app 也不是依赖于 project 的,可以自由地在移动到不同的 project 中。

如果“插入”一个 app 到 project 中去呢?编辑 setttings.py 文件,在 INSTALLED_APPS 中增加一条 app 名字的记录即可。

下面是创建 app 的命令,Django 一个方便的地方在于,有 app 生成模版可以快速生成 app 框架,减小工作量,让开发者专注于业务实现。

1
python manage.py startapp polls

上面运行的这个命令的地方有一个取巧,就是在 manage.py 所在的目录,同级运行这个命令,生成的 app 就可以以自己作为顶层模块导入,而不是作为上层模块的子模块进行导入。

生成的目录结构如下所示:

1
2
3
4
5
6
7
8
9
10
11
[email protected]:~/python_study/mysite$ tree polls/
polls/
├── admin.py
├── __init__.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
1 directory, 6 files
数据库表

一般情况下,我直接编写 sql 语句,创建数据库表,但是在 Django 中,这不是它推崇的方式,使用 orm 来表现才是它喜欢的,让 Django 来转换代码和 sql 直接的关系,下面是一个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
#polls/models.py
from django.db import models
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)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)

在上面创建了两个数据库表的对应模型, Question 和 Choice。

Django 简化了开发人员对于 sql 掌握,让它们完全不关心 sql 是什么,如何编写 sql 和优化 sql 语句,不必关心数据库表是如何建立起来的,各种对应关系是如何处理的,只需要写写 python 代码,把想要存储的东西写下来,取个名字即可。

好处是简化了开发的门槛,对于有数据库使用经验,会编写 sql 和优化 sql 的开发者来说,减轻了工作负担,并且知道自己在做什么;而对于不懂 sql 的人来说,降低了开发的门槛。对于不懂 sql 的开发者来说,坏处也很明显,当需要调整表结构,或者表结构是很灵活的时候,就很容易陷入不知道如何处理的境地,并且容易开发出难用死了的数据库表关系。

当我们编写了好了 models.py 时,我们通过以下三个步骤来保证 python 代码到数据库的变更:

1
2
3
4
5
python manage.py makemigrations polls
python manage.py sqlmigrate polls 0001
python manage.py migrate

第一步把 polls/models.py 里面的内容生成为名类似于 0001_initial.py 的文件,
第二步是一个测试,它仅仅是生成要执行的 sql 语句,但是不作用于数据库上,
第三步就是真的执行 sql 语句了,会生成对应的数据库表,这里数据表的名字以 app 的名字加上下划线再带上 models.py 里面定义的类的名的小写形式,比如 polls_choice polls_question。

为什么执行第三步时,不会再次执行其他的 sql 语句,而仅仅是执行了新增加的呢?在数据库表 django_migrations 中记录了相关信息,所以 Django 知道改执行谁。

上面定义的 models.py,不仅仅是为了生成 sql 语句,同时也是数据访问的 api。

下一步就是访问数据了, orm 刚接触的时候,真是蛋疼啊。


Django Admin 的使用

1
python manage.py createsuperuser

根据提示,输入用户名、邮箱、密码,等待创建完毕。

如此就在 Django 的相应数据库表中建立了一些记录。

关于 admin 模版,模版文件/目录可以存放在系统的任何地方,只要 Django 有权限访问,但是从工程实践的角度考虑,最好的是存放在相应的 project 目录里面。

配置模版文件的目录采用的是在 project 的 setttings.py 里面填写上 TEMPLATE_DIRS 对应得值,比如:

1
TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'templates')]

从可复用的代码的角度来看,我们应该对每个 app 的各自的模版放在各自的目录下,那么还需要在 project 里面进行什么设定么?不需要了,Django 会自动的根据 INSTALLED_APP 的值,自动的在对应的 app 的 templates 目录下查找模版。

VIEWS(public interface)

tutorial3

views 是什么呢?在 Django 中, views 是一种提供某个服务的页面的背后的函数。每一种 view 都是一个 python 函数,view 是和 url 路径映射起来的,以便程序能正确的调用对应的 views 函数。

所有的功能实现都是在 views 里面的函数中处理的,对于 Django 来说,只关心一个东西,返回值,是返回一个 HttpResponse 还是 raise 一个 http error,由用户决定。

在 app 中,主要有三个方面的内容:

  • views.py
  • models.py
  • urls.py

views.py 中定义了实际干活的函数;models.py 中定义了此 app 相关的数据库表模型和一些附加的操作值;urls.py 中定义了 url 的路径正则。定义好这三部分后,如何结合到 project 中去呢?

在 project 所在的 urls.py 中 include 进来本 app 的 urls 和相关路径。同时在 settings.py 中的 INSTALLED_APP 中加入对应 app,

下面是一个示例,简略的说明了上述内容的代码结构。

首先在 app,我们这里是 polls 中编写 urls.py views.py 内容分别如下:

1
2
3
4
5
6
7
8
9
10
11
12
# urls.py
#!/usr/bin/env python
#-*-coding:utf-8-*-
from django.conf.urls import patterns, url
from polls import views
urlpatterns = patterns("",
url(r'^$', views.index, name='index'),
url(r'^(?P<question_id>\d+)/$', views.detail, name='detail'),
url(r'^(?P<question_id>\d+)/results/$', views.results, name='results'),
url(r'^(?P<question_id>\d+)/vote/$', views.vote, name='vote'),
)

上述就定了 url 路径,比如 / /34/ /34/results/ /34/vote/

下面要定义对应的路径上的处理函数,下面是 views.py 的示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.shortcuts import render
from django.http import HttpResponse
def index(request):
return HttpResponse("hello, world, You're at thre polls index.")
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 thre requsts of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)

上面定义的函数就是对应的 url 上的操作。

如何把 polls 这个上面的 urls 和 整个项目的结合起来呢?在整个项目的 urls.py 中定义一个上层路径,指向了这里的下级路径,下面是一个示例:

1
2
3
4
5
6
7
8
9
10
11
from django.conf.urls import patterns, include, url
from django.contrib import admin
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'mysite.views.home', name='home'),
# url(r'^blog/', include('blog.urls')),
url(r'^admin/', include(admin.site.urls)),
url(r'^polls/', include('polls.urls')),
)

这里仅关心 url(r'^polls/', include('polls.urls')), 这一行的内容,前文已经说过了 urlconf 相关的内容,
所以这里的访问路径实际就是 xxxdomain/polls/ xxxdomain/polls/34 xxxdomain/polls/34/results ….

也就是说,把 polls 这个 app 的提供的相关服务放在了路径 domain/polls/ 下面去了。

这样的方式,整个项目就结合起来了。

实际上的 views的样子,实际上,上面展示的 views.py 是很简单的,没有模版的渲染,也没有参数的传递,如果加上模版的渲染和参数的传递,则和 torando 的 render() 调用差不多,django 也有 render() 方法。下面是一个示例。

1
2
3
4
5
from django.shortcuts import render
def index(request):
something_to_get
return render(request, "templates_dir", somedata)

与 tornado 的 RequestHandler() 中实现的方法去渲染页面是差不多的调用方式。

除了要返回结果外,我们有时候还需要抛出错误,

关于渲染模版这里,会出现拼接 url 的时候,这里就有个可考虑的问题了,一个 project 可能有多个 app,某些 app 下可能有相同名字的路径,那么如何来保证拼接 url 的时候,不出现错误或者疑惑呢?

那就是在 root urlconf 中定义一个 namespacing 用来标示出是哪个 app 的路径。

下面是一个例子:

1
2
3
4
5
6
7
8
# root urlconf
from django.conf.urls import patterns, include, url
from django.contrib import admin
urlpatterns = patterns('',
url(r'^polls/', include('polls.urls', namespace="polls")),
url(r'^admin/', include(admin.site.urls)),
)
1
2
# polls/templates/polls/index.html
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

用了 namespace:name 来唯一的确定反转拼接一个 url。

这样做的目的,而不是硬编码一个路径,好处是灵活性和可重用性。
(参考: namespac)

项目打包

当编写好了自己的 app 后,一个很自然的考虑就是打包发布,让别人也可以安装,那么如何打包呢?使用 setuptools 来进行打包,我们需要写一些配置文件,方便我们进行处理,快速的打包教程可以参考 Django 的手册。
快速打包教程

总结

一个基本的 Django app 的包含三个部分, models/views/urls,也就是说数据库模型、操作数据库数据和返回给用户的函数(包含渲染等)、定义请求的路径以及和对应的操作函数的映射关系。

以上就是一个 app 需要关注的三个方面,而实际 web 开发的过程就是接收用户请求,处理请求,返回结果。

所以,而为什么要用 Django 呢?它和用 tornado 来开发 web 应用的差别在哪里呢?

Django 有自己的一套开发理念,集成了很多的有用模块,基本上把 web 开发的各个方面都考虑到了,开发者只需要引入对应的包,在上面处理处理就行了。

tornado 主要是一个 web server,其次是提供 web 服务器,开发起来很快速,但是要开发一个稳定、高效,各种功能都具体的服务时,需要开发者做的工作还有很多,这个就需要自己衡量了。

就我的使用体验来看,快速开发一个小的模型出来,用 tornado 非常方便,并且它定义的 HTTP action 函数很符合常规想法。如果想要节省精力,快速开发一个可生成部署的服务,那么选择 Django 可能比较好一点。