自定义Web框架
跟着老师一点一点完善代码和文档结构,最终一个Web框架的雏形会显现出来,然后引出之后要学习完善的的Web框架。
Web框架的本质
Web服务端的本质就是一个socket服务端,而我们的浏览器就是socket客户端。浏览器发送的请求就是一次socket请求。一次请求获得一次响应,然后就断开,这是一个短连接。
下面是一个服务端的python代码,直接用socket,实现一个简单的Hello World:
import socket
def handle_request(conn):
data = conn.recv(1024) # 接收数据,随便收到啥我们都回复Hello World
conn.send(‘HTTP/1.1 200 OK\r\n\r\n‘.encode(‘utf-8‘)) # 这是什么暂时不需要知道,客户端浏览器会处理
conn.send(‘Hello World‘.encode(‘utf-8‘)) # 回复的内容,就是网页的内容
def main():
# 先起一个socket服务端
server = socket.socket()
server.bind((‘localhost‘, 8000))
server.listen(5)
# 然后持续监听
while True:
conn, addr = server.accept() # 开启监听
handle_request(conn) # 将连接传递给handle_request函数处理
conn.close() # 关闭连接
if __name__ == ‘__main__‘:
main()
然后打开本机的浏览器访问我们的Web服务。在地址栏输入 http://127.0.0.1:8000/ 或 http://localhost:8000/ 之后,浏览器上就会显示服务端返回的内容了。
WSGI
WSGI(Web Server Gateway Interface),是一种规范。它定义了使用python编写的web app与web server之间接口格式,实现web app与web server间的解耦。
对我们来说,就是我们不用写socket了,直接写Web服务。
python标准库提供的独立WSGI服务的模块称为wsgiref。
from wsgiref.simple_server import make_server
def RunServer(environ, start_response):
# environ:封装了客户端发来的所有的数据
# start_response:封装了要返回给用户的数据,响应头和响应状态
start_response(‘200 OK‘, [(‘Content-Type‘, ‘text/html‘)])
# 返回给客户端的内容,用return返回。记得要转成bytes类型
return [‘<h1>Hello, web!</h1>‘.encode(‘utf-8‘), ]
if __name__ == ‘__main__‘:
httpd = make_server(‘‘, 8000, RunServer) # 创建服务,不需要我们自己创建socket服务了
print("Serving HTTP on port 8000...")
httpd.serve_forever() # 运行服务
响应不同的url请求
通常我们的请求可能要访问的是网站中的某个页面,比如是这样的形式 http://localhost:8000/data 。这需要在服务端能够区别出不同的请求,然后给客户端返回不同的内容。
environ,封装了客户端发来的所有的数据,现在来找一下请求的数据具体在哪里。
在上面例子中的 RunServer 函数中添加断点,然后Debug模式启动。此时再用上面的url发起请求。
如上图,可以看到返回了两个值。展开 environ 后,可以找到 PATH_INFO 内存放的就是 ‘/data‘ ,也就是我们请求的页面。
修改一下上面的函数,在函数内判断一下 environ[‘PATH_INFO‘] 的值,返回给客户端不同的结果:
from wsgiref.simple_server import make_server
def handle_data():
return [‘<h1>Hello, this is data.</h1>‘.encode(‘utf-8‘), ]
def handle_default():
return [‘<h1>Hello, web!</h1>‘.encode(‘utf-8‘), ]
def RunServer(environ, start_response):
start_response(‘200 OK‘, [(‘Content-Type‘, ‘text/html‘)])
current_url = environ[‘PATH_INFO‘]
if current_url == ‘/data‘:
# 处理方法单独写一个函数
return handle_data()
else:
# 写一个默认的处理方法,比如也可以是返回404
return handle_default()
if __name__ == ‘__main__‘:
httpd = make_server(‘‘, 8000, RunServer)
print("Serving HTTP on port 8000...")
httpd.serve_forever() # 运行服务
上面的结构还不是很好。如果请求的分支很多,这个if要写的很长。这里可以用字典的形式来存放,看着结构更加清楚。题外话,python没有case语句,这也是一个变通的方式。
from wsgiref.simple_server import make_server
# 这里是返回页面的处理函数
def handle_data():
return [‘<h1>Hello, this is data.</h1>‘.encode(‘utf-8‘), ]
def handle_home():
return [‘<h1>You are at home.</h1>‘.encode(‘utf-8‘), ]
def handle_default():
return [‘<h1>Hello, web!</h1>‘.encode(‘utf-8‘), ]
# 写一个字典,里面是url和处理函数的对应关系
URL_DICT = {
‘/data‘: handle_data,
‘/home‘: handle_home,
‘default‘: handle_default
}
def RunServer(environ, start_response):
start_response(‘200 OK‘, [(‘Content-Type‘, ‘text/html‘)])
current_url = environ[‘PATH_INFO‘]
if current_url in URL_DICT:
func = URL_DICT[current_url]
else:
func = URL_DICT[‘default‘]
if func:
return func()
else:
return [‘<h1>404</h1>‘.encode(‘utf-8‘), ]
if __name__ == ‘__main__‘:
httpd = make_server(‘‘, 8000, RunServer)
print("Serving HTTP on port 8000...")
httpd.serve_forever() # 运行服务
上面的字典(URL_DICT)里的key就是客户端请求的url,例子中是明确的对应关系。再复杂一点key值写成正则表达式,if再用正则匹配,就可以把一类请求匹配到一个处理函数中了。这么没继续深入。
分离出html文件
上面的例子中,html的代码是直接写在handle方法里的。实际操作中,这个html代码会很长,并且我们学习Web前端的时候,已经编写了完整的html页面文件。这里就需要把handle函数再改进一下,直接去读取对应的文件然后返回文件内容:
def handle_request():
with open("index.html", mode=‘rb‘) as file:
data = file.read()
return [data, ]
既然已经分离出文件了,那么把所有html文件整理一下,统一存在在一个目录下,比如:View。
分离出handle代码
一个站点会有很多的页面,所以对应的handle函数也会有很多个。把所有的handle函数也单独拿出来,写到一个或几个模块中去。把这些模块也放到一个目录下,比如:Controller/account.py。用的时候导入模块,修改一下字典的函数名:
from Contraller import account
# 写一个字典,里面是url和处理函数的对应关系
URL_DICT = {
‘/data‘: account.handle_data,
‘/home‘: account.handle_home,
‘default‘: account.handle_default
}
返回动态数据
之前例子中返回客户端的都是固定的html,现在要动态的返回数据。一般数据是从数据库获取的,这里直接返回系统时间吧。修改html文件,加入一些特殊的标记,比如:“@time”。那么现在我们的 View/index.html 文件内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Hellow Web! It is @time.</h1>
</body>
</html>
然后修改我们的handle方法来处理这个特殊的标记:
import time
def handle_default():
with open(‘View/index.html‘, mode=‘rb‘) as file:
data = file.read()
data = data.replace(b‘@time‘, time.asctime().encode(‘utf-8‘))
return [data, ]
这里写死了一个@time的标记,实际使用中多数数据都是是读取数据库的,并且要读的东西也很多。获取动态数据的方法也要单独分离出来写成模块。并且也建一个专门的文件夹存放,比如:Model。
MVC框架
现在我们已经有了这样一个文档结构了:
web_server.py
├─Model
│ └─get_data.py
├─View
│ └─index.html
└─Contraller
└─account.py
于是一个MVC(Model View Controller 模型-视图-控制器)的Web架构的模式就出来了。
MTV框架
另外还有一种,MTV(Model Templates View 模型-模板-视图)。也就是把MVC中的View分成了视图(展现哪些数据)和模板(如何展现)2个部分,而Contorller这个要素由框架自己来实现了,我们需要做的就是把(带正则表达式的)URL对应到视图就可以了,通过这样的URL配置,系统将一个请求发送到一个合适的视图。
在Python的世界中,基本(除了Pylons)都使用了MTV框架,包括Django。所以后面会重点学习Django。
另外,Flask这种微框架就不是一个MVC模式的,因为它没有提供Model,除非集成了SQLAlchemy之类的ORM进来。
Django基础
Python的WEB框架有 Django、Tornado、Flask 等多种,Django 相较与其他WEB框架其优势为:大而全,框架本身集成了ORM、模型绑定、模板引擎、缓存、Session等诸多功能。
安装
可以用pip3安装:
pip3 install django
或者也可以通过PyCharm来安装:File-->Settings-->Project 。
安装完后,看一下Scripts目录,也就是你的pip3程序所在的目录,多了2个django-admin程序。这个是帮我创建Django程序的。
创建Django项目
切换到你要创建Django项目的目录,执行django-admin程序,就可以完成创建:
django-admin.exe startproject [你的项目名称]
你的确认把Script加到环境变量里去了,否则得输入完整的路径,比如下面这样:
G:\PycharmProjects>D:\Python36\Scripts\django-admin.exe startproject mysite01
现在进入你的项目目录,启动你的项目,命令如下。默认参数是 127.0.0.1:8000 ,可以输入方框号中完整的IP和端口号来指定,比如:0.0.0.0:9001。
python manage.py runserver [127.0.0.1:8000]
然后可以打开浏览器访问你的测试页了,能成功访问则说明上面的步骤都正确了。
上面的步骤也可以通过PyCharm来完成,如果是照着上面用命令行来创建的,现在还需要把我们的项目加到PyCharm里面去。File-->Open 然后选择你项目的目录,如果按上面的例子那么就是 G:\PycharmProjects\mysite01 ,点OK完成。
直接通过PyCharm创建项目:文件-->新建项目,选择Django
Location 里修改一下项目的路径和项目名,默认项目名是untitled,改掉。
Project Interpreter 里选择python环境,可以新建一个环境,或者使用已有的环境。
More Settings 里默认会帮我们创建templates文件夹。如果需要,还可以把 Application name 填上,就是创建项目的同时创建一个app。
templates 和 app 在下面的 “##创建app“ 和 “##写一个登录页面” 里会讲
创建一个页面
在项目下新建一个文件夹存放我们的页面,文件夹下新建一个python程序如下:
from django.shortcuts import HttpResponse
def hello(request):
return HttpResponse("Hello Web")
现在去修改一下原来urls.py文件,加入上面这个页面的对应关系:
from django.contrib import admin
from django.urls import path
from page import mypage # 导入刚才写的程序
urlpatterns = [
path(‘admin/‘, admin.site.urls),
path(‘hello/‘, mypage.hello), # 添加我们的对应关系
]
添加2行代码,导入py文件,添加对应关系。现在再去重启一个Web服务。直接访问这次页面会返回 Page not found (404) 。在url后加上 hello 能返回程序中 return 的字符串。
创建app
通过创建app,相当于在网站下创建了一个相对独立的功能模块。我们可以创建多个app,为网站建立多个功能模块。创建的方法,是在命令行下输入命令:
python manage.py startapp [你的app的名字]
创建完之后,你的项目里又多了一个文件夹(app的名字),以及一些文件。其中views是给我放置处理函数的,把前面写的处理函数移动到这个文件里。然后再去 urls.py 里修改一下。
创建一个app,比如名字就叫cmdb:
python manage.py startapp cmdb
在 cmdb/views.py文件的后面追加我们的处理函数:
from django.shortcuts import render
# Create your views here.
# 上面是原来文件的内容,在下面添加我们的处理函数
from django.shortcuts import HttpResponse
def hello(request):
return HttpResponse("Hello Web")
def home(request):
return HttpResponse("<h1>Hello here is home</h1>")
修改urls.py里的对应关系:
from django.contrib import admin
from django.urls import path
from cmdb import views
urlpatterns = [
path(‘admin/‘, admin.site.urls),
path(‘home/‘, views.home),
path(‘hello/‘, views.hello),
]
功能文件介绍
templates文件夹 :默认不会创建这个文件夹,不过使用PyCharm创建项目的时候会为我们创建它。存放我们的html文件。
setting.py :配置文件
url.py :url的对应关系
wsgi.py :遵循WSGI规范,默认用的是wsgiref。老师推荐上线的系统用 uwsgi 替换掉默认的,也是这里改。推荐 uwsgi + nginx 。
manage.py :管理Django程序。有很多功能通过命令行参数控制,比如前面用的 runserver 和 startapp。
app下的文件
migrations文件夹 :存放数据库操作的记录,是修改表结构的记录,不包括数据修改的记录。我们可以不用管这个文件夹里的内容
admin.py :Django提供的后台管理
apps.py :配置当前app的
models.py :创建数据库表结构,就是我们的ORM
tests.py :单元测试
views.py :业务代码
文件结构如下:
{project}
├─{app}
│ ├─migrations
│ │ └─__init__.py
│ │
│ ├─__init__.py
│ ├─admin.py
│ ├─apps.py
│ ├─models.py
│ ├─tests.py
│ └─views.py
│
├─{project}
│ ├─__init__.py
│ ├─settings.py
│ ├─urls.py
│ └─wsgi.py
│
├─templates
│ └─index.html # 这里放我们自己写的页面
│
└─manage.py
{project} 指代项目名,{app} 指代app名。之后写文件路径路径的时候,也可能这么写
写一个登录页面
在app里创建页面,views.py 里不再返回字符串,而是去读取html文件。
创建html页面文件,templates/login.html :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
label{
width: 80px;
text-align: right;
display: inline-block;
}
</style>
</head>
<body>
<!--
这里注意"/login/"结尾的"/"。浏览器中我们不输最后一个"/",但是Django默认会帮我们加上
但是代码里的,不会自动添加。加不加最后的"/",对程序而言就是两个url。
我们在urls.py里写的是带"/"的,写不对会导致找不到url。
不过Django会判断出来是因为你的url最后没加"/",会提示你去settings.py里加一条APPEND_SLASH=False。
人家默认开启是有意义的,我们需要做的就是别忘记在url最后写上各个"/"
-->
<form action="/login/" method="post">
<p>
<label for="usernmae">用户名:</label>
<input id="usernmae" type="text" />
</p>
<p>
<label for="password">密码:</label>
<input id="password" type="password" />
<input type="submit" value="提交">
</p>
</form>
</body>
</html>
{app}/views.py里写一个login方法:
from django.shortcuts import HttpResponse
def login(request):
with open(‘templates/login.html‘, encoding=‘utf-8‘) as file:
data = file.read()
return HttpResponse(data)
记得修改一下{project}/urls.py添加对应关系,现在可以打开新页面看一下了。
一般都是打开文件返回文件内容,所以django给我们提供了一个 render 方法,直接返回文件内容,login方法改成下面这样:
from django.shortcuts import render # 这行默认在文件第一行就自动帮我引入了,没有你就加上
def login(request):
# 现在可以直接读取文件返回文件内容了,一行搞定
return render(request, ‘login.html‘)
这里的文件直接写文件名就好了,不过现在测试的话,回显示 TemplateDoesNotExist at /login/ ,因为系统找不到文件。再去设置一下 {project}/settings.py 里的变量 TEMPLATES 。里面有个字典,设置前是这样的:
TEMPLATES = [
{
‘BACKEND‘: ‘django.template.backends.django.DjangoTemplates‘,
‘DIRS‘: [], # 这里加上我们的查找路径
‘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的暂时是个空列表,加上templates的绝对目录 ‘DIRS‘: [os.path.join(BASE_DIR, ‘templates‘)]
。这样就可以直接通过文件名找到 templates 目录下的文件了。
支持引入静态文件
网页中还需要引入css模板,jQuery文件等,现在尝试把这些加上。
建立一个文件夹 “static” 来存放这些静态文件。然后创建一个css文件 “commons.cc” 给body加一个背景色:
body{
background-color: grey;
}
记得在页面的 head 里引入:
<link rel="stylesheet" href="/static/commons.css">
以默认的配置,系统是找不到我们的静态文件的,还需要在配置文件 settings.py 里条件配置:
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/
STATIC_URL = ‘/static/‘
# 上面都是原来就有的内容,下面的STATICFILES_DIRS是我们要添加的
STATICFILES_DIRS = (
os.path.join(BASE_DIR, ‘static‘),
)
现在,就可以支持引入静态文件了,包括jQuery以及前端组件现在都能用了。
课上暂时没讲。上面的引入方式,还是有问题。现在通过Django是可以全部访问了,但是本地打开html文件的时候是找不到引入的文件的。把路径的前面加上 ".." 表示上一级目录,就可以两边兼顾了:
<link rel="stylesheet" href="../static/commons.css">
前端小结
总结一下上面的步骤
PyCharm创建项目:
如果用Pycharm创建,修改一下项目名称,确认是新建python环境还是用已有的。可以选择同时创建app。
命令行创建项目:
创建项目 django-admin.exe startproject [你的项目名称]
创建app python manage.py startapp [你的app的名字]
页面文件html:
把html文件放到templates文件夹下,PyCharm创建的时候会自动创建这个文件
静态文件:
创建一个static文件夹,放置我们的静态文件。html文件中类似这样引入
<link rel="stylesheet" href="../static/commons.css">
添加templates的文件夹路径:
PyCharm创建的项目会自动给我们加上,如果是用命令行创建的话,需要自己修改。
settings.py 文件的 TEMPLATES 变量 ‘DIRS‘: [os.path.join(BASE_DIR, ‘templates‘)]
添加static的文件夹路径:
settings.py 文件的最后加上下面这些
STATICFILES_DIRS = (
os.path.join(BASE_DIR, ‘static‘),
)
编辑处理函数 {app}/views.py :
from django.shortcuts import render # 这行默认就在文件第一行
def login(request):
return render(request, ‘index.html‘)
添加对应关系:
修改urls.py的内容,添加对应关系
Django请求
下面以一个表单的示例来说明前端和后端之间的通信,提交和获取。
提交表单
现在页面已经可以打开了。然后看一下我们的form表单,form标签内的内容如下:
<form action="/login/" method="post">
action :是我们要提交表单的地址,这里要注意最后的"/",平时浏览器上我们有没有无所谓,系统会自动补上的,代码里就不能省了
method :是提交表单的方式,有post和get两种。我们浏览器输入地址访问也是get
现在用post提交会有问题,可以先换成get,甚至把action也改一下,看下效果:
<form action="/admin/" method="get">
但是换回post就是有问题,提示CSRF(跨站请求伪造)。先去settings.py里把这个设置关掉,如下:
MIDDLEWARE = [
‘django.middleware.security.SecurityMiddleware‘,
‘django.contrib.sessions.middleware.SessionMiddleware‘,
‘django.middleware.common.CommonMiddleware‘,
# ‘django.middleware.csrf.CsrfViewMiddleware‘,
‘django.contrib.auth.middleware.AuthenticationMiddleware‘,
‘django.contrib.messages.middleware.MessageMiddleware‘,
‘django.middleware.clickjacking.XFrameOptionsMiddleware‘,
]
现在再提交就不会报CSRF了,由于提交的地址就是当前页面,所以目前能看到的效果只是页面的刷新。
通过F12进入开发者模式,看网络标签,提交页面后会刷出请求的服务器资源。右边可以展开显示详细的信息。可以看到请求URL、请求方法。如果你是get方法,请求方法就是GET,如果你是post方法,请求方法就是POST。下面还有请求头、响应头。
请求头里有个内容知道一下,你的系统和浏览器信息会在这里,如果是手机,这里也能看到手机的系统
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299
修改一下我们的views.py文件中的方法,也能获取到用户请求的方法:
def login(request):
# request 包含用户提交的所有信息
# request.method 获取用户提交的方法
print(request.method)
return render(request, ‘login.html‘)
request :包含用户提交的所有信息
request.method : 获取用户提交的方式 get 或 post
现在我们可以通过request.method判断出收到的是get请求还是post请求。如果是get就正常返回页面,如果是post就处理用户表单。
为了能获取到post的信息,还需要为input加上name属性。之前的input未设置name,现在需要加上:
<form action="/login/" method="post">
<p>
<label for="usernmae">用户名:</label>
<input id="usernmae" name="user" type="text" />
</p>
<p>
<label for="password">密码:</label>
<input id="password" name="pwd" type="password" />
<input type="submit" value="提交">
</p>
</form>
解决了上面的问题,终于可以着手处理浏览器提交的信息了。
request.POST 就是用户提交的表单信息。可以把这个当成是一个字典来操作。上面已经为input加上的name属性,这个就是字典的key。推荐用字典的get方法来获取值,因为如果获取不到,默认是返回None不会报错。获取到用户名和密码后简单验证一下,然后如果正确就跳转到另外一个页面。页面跳转要用到 redirect 函数,还得在开头导入模块 from django.shortcuts import redirect
,然后login处理函数如下:
from django.shortcuts import render
from django.shortcuts import redirect
def login(request):
# request 包含用户提交的所有信息
# request.method 获取用户提交的方法
# print(request.method)
if request.method == ‘POST‘:
# 这里用字典的操作方法,用get方法获取值
user = request.POST.get(‘user‘)
pwd = request.POST.get(‘pwd‘)
# print(user, pwd)
if user == ‘steed‘ and pwd == ‘123‘:
return redirect(‘http://blog.51cto.com/steed‘)
return render(request, ‘login.html‘)
redirect() 里面只能填url,就上例子中那样。如果需要跳转本站的地址,也可以,必须以 / 开头,这里开头的 / 就代指本机的地址,如:/login
就是一个本机的url。
返回数据
通过Django给客户端返回动态的数据,需要用到模板自己的语言,该语言可以实现数据展示。这里要用到模板语言中的变量。
变量如下所示:{{ item }}。 当模板引擎遇到变量时,它将评估该变量并将其替换为结果。
这样在html中遇到用两个大括号包起来的形式,Django会把它替换成我们后端指定的内容。现在我们来输出错误信息,继续修改我们的login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="../static/commons.css">
<style>
label{
width: 80px;
text-align: right;
display: inline-block;
}
</style>
</head>
<body>
<form action="/login/" method="post">
<p>
<label for="usernmae">用户名:</label>
<input id="usernmae" name="user" type="text" />
</p>
<p>
<label for="password">密码:</label>
<input id="password" name="pwd" type="password" />
<input type="submit" value="提交">
<span style="color: red">{{ error_msg }}</span>
</p>
</form>
</body>
</html>
上面是完整的文件内容,这次是在最后加了一个span标签,里面用来返回错误信息,错误信息用{{ error_msg }}代替。
然后继续去编辑之前的login处理函数,之前还没写认证失败的逻辑。现在加上并且替换到页面里的错误信息。这里还是用到之前的render方法。这个方法还有第三个参数,传入一个字典,key就是页面中需要替换的内容,value是我们要替换上去的内容。具体如下:
from django.shortcuts import render
from django.shortcuts import redirect
def login(request):
# request 包含用户提交的所有信息
# request.method 获取用户提交的方法
# print(request.method)
if request.method == ‘POST‘:
# 这里用字典的操作方法,用get方法获取值
user = request.POST.get(‘user‘)
pwd = request.POST.get(‘pwd‘)
# print(user, pwd)
if user == ‘steed‘ and pwd == ‘123‘:
return redirect(‘http://blog.51cto.com/steed‘)
if user and user != ‘steed‘:
return render(request, ‘login.html‘, {‘error_msg‘: ‘用户名%s不存在‘ % user})
return render(request, ‘login.html‘)
get方法提交的时候,返回的值是没有写第三个参数的,页面里也看不到错误信息。这里Django也帮我么把要替换的内容默认设为空了。如果输入一个错误的用户名,这里也动态的把这个用户名返回到页面上了。
使用点(.)来访问变量的属性:
从技术上讲,当模板系统遇到点时,会按以下顺序尝试以下查找:
- 字典查找
- 属性或方法查找
- 数字索引查找
所以还可以用点来访问到字典或列表中的元素。
返回表格数据-for标签
Django的模板语言还支持循环,这样我们可以方便的获取一系列的数据,比如字典和列表。模板语句中叫做标签。
标签看起来像这样:{% tag %}。 一些标签需要开始和结束标签(即 {% tag %} ... 标签 内容 ... {% endtag %})。现在先重点来看一下for标签,循环数组中的每个项目。 例如,要显示athlete_list中提供的运动员列表:
<ul>
{% for item in item_list %}
<li>{{ item.key }}</li>
{% endfor %}
</ul>
按照上面的格式写好我们的html文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<table border="1">
<thead>
<tr>
<th>NAME</th>
<th>AGE</th>
<th>DEPT.</th>
</tr>
</thead>
<tbody>
<!--
<tr>
<td>Adam</td>
<td>22</td>
<td>IT</td>
</tr>
<tr>
<td>Bob</td>
<td>32</td>
<td>IT</td>
</tr>
<tr>
<td>Carmen</td>
<td>30</td>
<td>Sales</td>
</tr>
<tr>
<td>David</td>
<td>40</td>
<td>HR</td>
</tr>
<tr>
<td>Edda</td>
<td>26</td>
<td>HR</td>
</tr>
-->
{% for row in members %}
<tr>
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
<td>{{ row.dept }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>
表格的内容用html也写了一份,但是注释掉了。这里的标签使用的变量是 members ,然后标签内部是对members做遍历,一次取出name、age、dept。这是类似字典的操作。members是个列表,列表的成员是字典并且有nage、age、dept这些key。
现在要去写我们的处理函数,把表格的数据写进去。首先我们要有一份表格的数据,一般是去数据库里读取的。暂时先不做数据库操作,写一个列表(TAB_MEMBER)存放表格数据。然后同样是通过render来返回。这次把整个字典传进去。views.py 文件:
# 现在也不搞数据库,先创建这么一个列表来存数据
TAB_MEMBER = [
{‘name‘: ‘Adam‘, ‘age‘: 22, ‘dept‘: ‘IT‘},
{‘name‘: ‘Bob‘, ‘age‘: 32, ‘dept‘: ‘IT‘},
{‘name‘: ‘Carmen‘, ‘age‘: 30, ‘dept‘: ‘Sales‘},
{‘name‘: ‘David‘, ‘age‘: 40, ‘dept‘: ‘HR‘},
{‘name‘: ‘Edda‘, ‘age‘: 26, ‘dept‘: ‘HR‘},
]
def table(request):
return render(request, ‘table.html‘, {‘members‘: TAB_MEMBER})
现在可以去重启Web服务,打开新的页面,检查是否可以获取到表格的数据显示出来。
为表格添加数据
接下来为上面的表格添加数据,修改html文件的内容,在表格上面加上输入框填写数据以及一个添加按钮:
<div>
<form action="/table/" method="post">
<input type="text" name="name" placeholder="NAME" />
<input type="text" name="age" placeholder="AGE" />
<input type="text" name="dept" placeholder="DEPT." />
<input type="submit" value="添加" />
</form>
</div>
<!-- 在表格的上面添加这个div,下面是原来表格的代码 -->
通过上面的添加按钮,会把添加的数据通过post请求发送到Web服务端,最后会返回一个更新后的表格。
现在要修改处理函数,添加对post请求的处理:
def table(request):
if request.method == ‘POST‘:
name = request.POST.get(‘name‘)
age = request.POST.get(‘age‘)
dept = request.POST.get(‘dept‘)
tmp = {‘name‘: name, ‘age‘: age, ‘dept‘: dept}
TAB_MEMBER.append(tmp)
return render(request, ‘table.html‘, {‘members‘: TAB_MEMBER})
最后都是返回列表的数据来生成表格,上面的做法是把收到的添加请求的内容生成一个新列表成员(字典)添加到 TAB_MEMBER 列表的末尾。
使用GET请求,上面用的是POST请求,也可以改成GET请求。把html中的form标签的methon的值改成get,就是发送get请求了。而处理函数可以用 request.method == ‘GET‘
做条件判断,取值也和POST的处理方法一样,使用 request.GET.get()
来获取请求的数据。对处理函数修改一下可以同时处理 get 和 post 请求:
def table(request):
if request.method == ‘POST‘:
name = request.POST.get(‘name‘)
age = request.POST.get(‘age‘)
dept = request.POST.get(‘dept‘)
tmp = {‘name‘: name, ‘age‘: age, ‘dept‘: dept}
TAB_MEMBER.append(tmp)
elif request.method == ‘GET‘ and request.GET:
name = request.GET.get(‘name‘)
age = request.GET.get(‘age‘)
dept = request.GET.get(‘dept‘)
tmp = {‘name‘: name, ‘age‘: age, ‘dept‘: dept}
TAB_MEMBER.append(tmp)
return render(request, ‘table.html‘, {‘members‘: TAB_MEMBER})
get请求也可以直接通过浏览器的地址栏发出。在url的后面加问号(?),然后拼接上请求的内容:
http://127.0.0.1:8000/table/?name=Frank&age=37&dept=Marketing
url和请求之间用 ? 分隔,字段之间用 & 分隔,字段左边是 key 右边是 value 中间是 = 。
选择性的返回数据-if标签
if标签的语法如下:
{% if 条件1 %}
当条件1为 true 时的代码
{% elif 条件2 %}
当条件2为 true 时的代码
{% else %}
所有条件都不为 true 时的代码
{% endif %}
在上面的代码的基础上,修改一下html中的内容,在上面for循环里的加一层if,比如只显示age>30的条目:
<div>
<table border="1">
<thead>
<tr>
<th>NAME</th>
<th>AGE</th>
<th>DEPT.</th>
</tr>
</thead>
<tbody>
{% for row in members %}
<tr>
{% if row.age > 30 %}
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
<td>{{ row.dept }}</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
Djang请求的生命周期
下面是一个请求的整个流程,括号里是我们的代码存放的位置
请求 <--> 路由系统(urls.py) <--> 视图函数(views.py) <--> DB、html(templates)、静态文件(static)
课后练习
主机管理:
- 后台数据使用数据库,PyMySql 或者 SQLAlchemy 操作。
- 一张主机管理数据表,至少8列。比如:主机名、IP地址、端口号、描述、状态等
- 一张用户登录认证表,有用户名和密码。不要求操作和管理,就是用来登录验证的
- 一个登录页面,验证通过跳转到下一个页面
- 一个主机管理页面,可以用模板也可以自己写。至少3部分:头部内容、左侧菜单、中间显示我们的数据表格
- 查看所有的主机信息,信息只需要显示部分数据,4列数据即可。其余的不用显示出来,用下面的查看详细显示出来
- 可以添加主机,就是添加一行数据,用模态对话框
- 查看详细,点击打开一个新页面,显示当前主机的所有信息
- 删除主机,就是删除一行
原文地址:http://blog.51cto.com/steed/2087370