Web框架
Web开发除了代码全部从头写起以外,还可以在其他人已经有的基础上进行开发,简化开发流程,这些Web开发环境统称为Web框架。
全栈表示可以开发Web应用所有阶段和层次的代码。框架可以提供所有相关的服务,如Web服务器,数据库ORM,模板和所有需要的中间件hook。
Django是由一个开发团队作为单个突出创建的,但并不是所有框架都要遵循这中哲学,以TurboGears为例,这是一个非常优秀的全栈系统,由分散在全世界的开发者开发,其作为胶水代码,将栈中其他独立的组件组合起来,如ToscaWidgets(高级Web部件,它可利用多种JS框架,如ExLtJs,JQuery)、SQLAlchemy(ORM)、Pylons,还有Genshi(模板化)。
Web服务器网关接口(WSGI)标准的建立加速了Web框架的发展。有了WSGI,应用开发者就可以方便的切换WSGI兼容的服务器,而无须担心需要改变应用代码。
Django简介
Django自称是“能够很好的应对应用上线期限的Web框架”。Django社区使用另一种形式的MVC模式,在Django中,它称为模型-模版-视图,简称MTV,数据模型保持不变,但视图在Django中是模版,因为模版用来定义用户可见的内容,最后,Django中的视图表示视图函数,他们组成控制器的逻辑,这与MVC完全相同,但仅仅是对不同角色进行了不同的解释。
安装
easy_install django
or
sudo python setup.py install
这里建议使用“容器”环境,如virtualenv,使用virtualenv可以同时安装多个版本的Python,Django,数据库等。每个环境在独立的容器中运行,可以自由创建,管理,执行,销毁。关于virtualenv可以参见http://pypi.python.org/pypi/virtualenv
项目和应用
项目是一系列文件,用来创建并运行一个完整的Web站点,在项目文件夹下,有一个或者多个子文件夹,每个子文件夹有特定的功能,称为应用,应用并不一定要位于项目文件夹中,应用可以专注于项目某一方面的功能,或可以作为通用组件,用于不同的项目。应用是一个具有特定功能的子模块,这些子模块合起来就能完成Web站点的功能。
创建项目
使用python安装文件下的Script中的django-admin创建Django项目
django-admin startproject mysite
创建成功后
在Django中基本的文件就包含4个,
文件名 | 描述/用途 |
---|---|
__init__.py | 告诉python这是一个软件包 |
urls.py | 全局URL配置 |
setting.py | 项目相关的配置 |
manage.py | 应用的命令行接口 |
startproject创建的每个文件都是纯python文件,没有ini文件或者xml文件,因为纯粹的Python既可以在不同的框架添加复杂东西的情况下拥有灵活性,同时也可以根据不同的情况从其他文件导入额外的配置,或动态计算数值,而不是硬编码。
运行开发服务器
Django内置了一个Web服务器,该服务器运行在本地,专门用于开发阶段。存在这个开发服务器的原因有三个:
- 使用开发服务器,可以直接运行与测试项目和应用,无需完整的生产环境
- 当改动python源码文件并重新载入模块的时候,开发服务器会自动检测,这样既能节省时间,也能方便地使用系统,无需你每次编辑代码后手动启动
- 开发服务器知道如何为Django管理应用程序寻找和显示静态媒体文件,所以无须立即了解管理方面的内容
python manage.py runserver 0.0.0.0:8888
在浏览器中访问localhost:8888
说明创建成功,在命令行中我们可以看见
[30/Oct/2016 19:42:27] "GET / HTTP/1.1" 200 1767
这是日志,每一行含有四个部分,从左到右,依次是时间戳,请求,HTTP响应编码,以及字节数。
创建应用
在这个项目下创建一个应用
>python manage.py startapp blog
创建成功
应用文件介绍
文件名 | 描述/目的 |
---|---|
__init__.py | 告诉python这是一个包 |
urls.py | 应用的URL配置文件,这个文件并不像项目的URL配置文件那样自动创建 |
models.py | 数据模型 |
views.py | 视图模型 |
tests.py | 单元测试 |
为了让Django知道这个新的应用是项目的一部分,需要编辑settting.py。在INSTALLED_APPS这个元组中将blog添加到元组的末尾
创建模型来添加数据库服务
models.py文件,在这里将定义博客的数据结构。数据模型表示将会存储在数据库每条记录中的数据类型,Django提供了许多字段,用来将数据映射到应用中。
blog/models.py
from __future__ import unicode_literals
from django.db import models
# Create your models here.
class BlogPost(models.Model):
title=models.CharField(max_length=150,null=False)
body=models.TextField(null=True,blank=True)
timestamp=models.DateTimeField()
设置数据库
在这里我们使用mysql数据库,不过在测试环境中,建议使用sqlite,SQLite将数据库另存为文件系统中的单个文件。访问控制就是简单的文件访问权限。
使用MYSQL
需要在setting.py中配置数据库信息,首先我们应该在数据库中创建一个名叫blog数据库。
mysql> create database blog charset utf8;
setting.py
如果是SQLite,由于SQLite使用本地文件来存储数据,本地文件系统的访问权限就是数据库的访问控制,SQLIte不仅可以使用本地文件,还可以使用纯内存数据库,因此针对SQLITE数据库的配置,在settings.py中
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': '/tmp/mysite.db',
}
}
创建表
生成配置文件
G:\python_study\PYCHARM\WEB\DjangoStudy\mysite>python manage.py migrates
Unknown command: 'migrates'
Type 'manage.py help' for usage.
根据配置文件创建数据库相关
G:\python_study\PYCHARM\WEB\DjangoStudy\mysite>python manage.py migrate
System check identified some issues:
WARNINGS:
?: (mysql.W002) MySQL Strict Mode is not set for database connection 'default'
HINT: MySQL's Strict Mode fixes many data integrity problems in MySQL, s
uch as data truncation upon insertion, by escalating warnings into errors. It is
strongly recommended you activate it. See: https://docs.djangoproject.com/en/1.
10/ref/databases/#mysql-sql-mode
Operations to perform:
Apply all migrations: admin, auth, blog, 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 blog.0001_initial... OK
Applying sessions.0001_initial... OK
然后我们授权一个创建一个超级用户,用于登陆Django自带的管理界面
G:\python_study\PYCHARM\WEB\DjangoStudy\mysite>python manage.py createsuperuser
Username (leave blank to use 'abnerchow'): admin
Email address:
Password:
Password (again):
Superuser created successfully.
查看数据库中的表
Django管理应用
设置admin
每次向项目中添加新应用时,需要执行makemigrations和migrate来确保在数据库中创建所需要的表。既然admin设置完成,所要做的就是给定一个URL,这样才能访问admin页面,在自动生成的项目urls.py中。
from django.conf.urls import url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
]
最后,应用程序需要告知Django哪个模型需要在admin页面中显示并编辑,为了做到这一点,只需要注册BolgPost,在blog/admin.py中添加一下代码
from django.contrib import admin
import models
# Register your models here.
admin.site.register(models.BlogPost)
使用admin
在浏览器的地址栏中输入localhost:8888/admin进入
接下来用我们刚刚注册的超级用户登陆
注意:如果列表中没有列出我的类,有三种常见的原因:
- 忘记通过admin.site.register()注册模型类
- 应用的models.py文件中存在的错误
忘记将应用添加到settings.py文件中的INSTALLED_APPS元组中
接下来我们就可以使用blog,向数据库中添加内容了
添加成功
但是我们发现,我们无法看见内容
这时候就需要修改一下代码了
在admin.py中修改
from django.contrib import admin
import models
# Register your models here.
class BlogPostAdmin(admin.ModelAdmin):
list_display = ('title','timestamp')
admin.site.register(models.BlogPost,BlogPostAdmin)
创建用户界面
从Django的角度来看,Web页面应该有以下几个经典组件:
1. 一个模版:用于显示通过Python类字典对象传入的信息
2. 一个视图:用于执行针对请求核心逻辑,视图会从数据库中获取信息,并格式化显示结果
3. 一个URL模式:将传入的请求映射到对应的视图中,同时也可以将参数传递给视图。
Django Template
>>> from django.template import Context, Template
>>> t = Template('My name is {{ name }}.')
>>> c = Context({'name': 'Stephane'})
>>> t.render(c)
u'My name is Stephane.'
同一模板,多个上下文
一旦有了 模板 对象,你就可以通过它渲染多个context, 例如:
>>> from django.template import Template, Context
>>> t = Template('Hello, {{ name }}')
>>> print t.render(Context({'name': 'John'}))
Hello, John
>>> print t.render(Context({'name': 'Julie'}))
Hello, Julie
>>> print t.render(Context({'name': 'Pat'}))
Hello, Pat
无论何时我们都可以像这样使用同一模板源渲染多个context,只进行 一次模板创建然后多次调用render()方法渲染会更为高效:
# Bad
for name in ('John', 'Julie', 'Pat'):
t = Template('Hello, {{ name }}')
print t.render(Context({'name': name}))
# Good
t = Template('Hello, {{ name }}')
for name in ('John', 'Julie', 'Pat'):
print t.render(Context({'name': name}))
在到目前为止的例子中,我们通过 context 传递的简单参数值主要是字符串,然而,模板系统能够非常简洁地处理更加复杂的数据结构,例如list、dictionary和自定义的对象。
在 Django 模板中遍历复杂数据结构的关键是句点字符 (.)。
最好是用几个例子来说明一下。 比如,假设你要向模板传递一个 Python 字典。 要通过字典键访问该字典的值,可使用一个句点:
>>> from django.template import Template, Context
>>> person = {'name': 'Sally', 'age': '43'}
>>> t = Template('{{ person.name }} is {{ person.age }} years old.')
>>> c = Context({'person': person})
>>> t.render(c)
u'Sally is 43 years old.'
同样,也可以通过句点来访问对象的属性。 比方说, Python 的 datetime.date 对象有 year 、 month 和 day 几个属性,你同样可以在模板中使用句点来访问这些属性:
>>> from django.template import Template, Context
>>> import datetime
>>> d = datetime.date(1993, 5, 2)
>>> d.year
1993
>>> d.month
5
>>> d.day
2
>>> t = Template('The month is {{ date.month }} and the year is {{ date.year }}.')
>>> c = Context({'date': d})
>>> t.render(c)
u'The month is 5 and the year is 1993.'
创建URL模式
服务器通过WSGI功能,最终会将请求传递给Django,接受请求的类型(GET,POST)和路径(URL中除了协议、主机、端口之外的内容)并传递到项目中的URLconf文件(mysite/urls.py)这些信息必须通过正则表达式正确匹配到对应的路径中,否则,服务器会返回404错误。
为了正确分离项目和应用的URL配置,需要通过两步来定义URL映射规则并创建两个URLconf,一个是用于项目,一个用于应用。
路由系统
1.静态路由
2.动态路由:按照顺序,第n个匹配的数据,交给函数的第n个参数,严格按照顺序模版的方法:将匹配的参数,传给指定的形式参数
3.二级路由
project_name:
urls.py
from django.conf.urls import url,include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^app01/',include('app01.urls')),
url(r'^app02/',include('app02.urls')),
]
当使用include()时,会移除当前的URL路径头,路径中剩下的部分传递给下游URLconf中的patterns()函数。
app01
urls.py
from django.conf.urls import url
from django.contrib import admin
import views
#匹配并调用views中对应的函数
urlpatterns = [
url(r'^test/',views.test),
url(r'^news/(\d+)/(\d+)',views.news),
url(r'^page/(?P<n1>\d+)/(?P<n2>\d+)',views.page),
]
创建视图函数
#coding:utf-8
from django.shortcuts import render
from datetime import datetime
import models
import random
# Create your views here.
def blogInfo(request):
#插入数据
title=''
for i in range(random.randint(1,7)):
title+=chr(random.randrange(65,123))
body=''
for i in range(random.randint(1,100)):
body+=chr(random.randrange(65,123))
models.BlogPost.objects.create(title=title,body=body,timestamp=datetime.now())
post=models.BlogPost.objects.all()
#返回给客户端的模版内容内容
return render(request,'archive.html',{'posts':post})
示例
archive.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Blog</title>
</head>
<body>
{% for post in posts %}
<h2>{{ post.title|title }}</h2>
<p>{{ post.timestamp }}</p>
<p>{{ post.body }}</p>
{% endfor %}
</body>
</html>
blog/mysite/urls.py
from django.conf.urls import url,include
from django.contrib import admin
import blog
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^blog/',include('blog.urls')),
]
blog/blog/urls.py
from django.conf.urls import url,include
import views
urlpatterns = [
url(r'^$',views.blogInfo)
]
blog/blog/models.py
from __future__ import unicode_literals
from django.db import models
# Create your models here.
class BlogPost(models.Model):
title=models.CharField(max_length=150,null=False)
body=models.TextField(null=True,blank=True)
timestamp=models.DateTimeField()
blog/blog/views.py
#coding:utf-8
from django.shortcuts import render,HttpResponse
from datetime import datetime
from django.template import loader,Context
import models
import random
# Create your views here.
def blogInfo(request):
#插入数据
for i in range(10):
models.BlogPost.objects.create(title="post #%d"%i,body="body of post #%d"%i,timestamp=datetime.now())
#查看前10条数据
post=models.BlogPost.objects.all()[:10]
t=loader.get_template('archive.html')
c=Context({'posts':post})
return HttpResponse(t.render(c))
# return render(request,'archive.html',{'posts':post})
结果示例
处理用户输入
使用form表单
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Blog</title>
</head>
<body>
{% if flag == 1 %}
<form method="post" action="/blog/">
<br>Title
<p><input type="text" name="title"></p>
<br>内容
<p><textarea name="body" style="width:500px;height:200px; "></textarea></p>
<br><input type="submit">
</form>
<hr>
{% endif %}
{% for post in posts %}
<h2>{{ post.title|title }}</h2>
<p>{{ post.timestamp }}</p>
<p>{{ post.body }}</p>
{% endfor %}
</body>
</html>
views.py
#coding:utf-8
from django.shortcuts import render,HttpResponse
from datetime import datetime
from django.template import loader,Context
import models
import random
# Create your views here.
def blogInfo(request):
#插入数据
# for i in range(10):
# models.BlogPost.objects.create(title="post #%d"%i,body="body of post #%d"%i,timestamp=datetime.now())
# 查看数据
post = models.BlogPost.objects.all()[:10]
#接受数据
if request.POST:
title=request.POST['title']
body=request.POST['body']
flag=0
else:
flag=1
return render(request,'archive.html',{'flag':flag,'posts':post})
#查看数据
post=models.BlogPost.objects.all()[:10]
t=loader.get_template('archive.html')
c=Context({'posts':post,'flag':flag})
return HttpResponse(t.render(c))
# return render(request,'archive.html',{'posts':post})
结果示例:
- 提交
跨站点请求伪造
Django有数据保留特性,这不允许不安全的POST通过跨站点请求伪造来进行攻击,对CSRF的解释超出了本书的范畴,对于这个简单的应用,需要修改两个地方,这两处都需要向已经有的代码添加一些代码
1. 向表单中添加CSRF标记({% csrf_token %}),让这些POST回到对应的页面。
2. 通过模版发送向这些标记请求的上下文实例
请求上下文实际上是一个字典,它包含关于请求的信息
<form method="post" action="/blog/">
{% csrf_token %}