框架基础, django下载与基本使用, 路由系统
模拟服务器给客户端发送数据
import socket import time sk = socket.socket() sk.bind(("127.0.0.1",8001)) sk.listen() def gen(): timer = str(time.time()) with open('text.html','r',encoding='utf-8') as fp: data = fp.read() data_str = data.replace('%abcd%',timer) data = data_str.encode() return data def photo(): with open('text.png','rb') as fp: data = fp.read() return data def jingdong(): with open('jd.ico','rb') as fp: data = fp.read() return data def css(): with open('text.css','rb') as fp: data = fp.read() return data def js(): with open('text.js','rb') as fp: data = fp.read() return data lst = [('/',gen),('/text.png',photo),('/jd.ico',jingdong),('/text.css',css),('/text.js',js)] while 1: print("服务器已启动") conn, addr = sk.accept() msg = conn.recv(1024) # print(msg) msg_str = msg.decode() # print(msg_str) conn.send(b'HTTP/1.1 200 ok\r\n\r\n') path = msg_str.split(" ")[1] print('请求url>>> ', path) for i in lst: if path == i[0]: data = i[1]() conn.send(data) break else: conn.send(b'404 notfound ') conn.close()
pip install jinja2
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> </head> <body> <h1>姓名:{{name}}</h1> <h1>爱好:</h1> <ul> {% for hobby in hobby_list %} <li>{{hobby}}</li> {% endfor %} </ul> </body> </html>
视图中的写法
from wsgiref.simple_server import make_server
from jinja2 import Template
def index():
with open("index2.html", "r",encoding='utf-8') as f:
data = f.read()
template = Template(data) # 生成模板文件
ret = template.render({"name": "bob", "hobby_list": ["running", "walking"]}) # 把数据填充到模板里面
return [bytes(ret, encoding="utf8"), ]
MVC Web服务器开发领域里著名的MVC模式,所谓MVC就是把Web应用分为模型(M),控制器(C)和视图(V)三层,结构说明如下:
M: models 数据库相关操作 V: views 视图,也就是业务逻辑相关操作 C: controller 控制器,也就是通过路径找到对应视图函数
MTV Django的MTV模式本质上和MVC是一样的,也是为了各组件间保持松耦合关系,只是定义上有些许不同。
M: models 数据库相关操作 T: templates html文件相关操作(模板渲染等) V: views 视图,也就是业务逻辑相关操作 加上一个url控制器,也就是通过路径找到对应视图函数
开发的项目分为两个部分的程序
1 服务器程序 socket相关功能的模块,wsgiref、uwsgi等等,负责接收网络请求并解析网络请求相关数据,分装加工成一些可用的数据格式,格式大家都是统一的,方便应用程序的开发
2 应用程序 就是使用接收到的网络请求相关数据,进行逻辑代码的开发,比如数据库相关操作,数据结构的调整、文件操作等等。。。
pip3 install django==1.11.9
django-admin startproject mysite #mysite是自己的项目名称
执行上面的指令之后会生成如下的目录,当前目录下会生成mysite的工程,里面有个主目录和我们创建的项目目录同名,在项目目录下有个manage.py文件,在主目录下有settings.py\urls.py\wsgi.py,每个文件的功能介绍如下:
manage.py ----- Django项目里面的工具,通过它可以调用django shell和数据库,启动关闭项目与项目交互等,不管你将框架分了几个文件,必然有一个启动文件,其实他们本身就是一个文件。
settings.py ---- 包含了项目的默认设置,包括数据库信息,调试标志以及其他一些工作的变量。
urls.py ----- 负责把URL模式映射到应用程序。
wsgi.py ---- runserver命令就使用wsgiref模块做简单的web server,后面会看到renserver命令,所有与socket相关的内容都在这个文件里面了
在一个项目中可以有多个应用,每个应用完成项目的部分功能,比如微信项目,其中有很多的应用,比如朋友圈功能、聊天、支付等等。在django框架看来,每个应用都有自己的逻辑部分,数据库部分等等,所以我们进行业务逻辑开发的时候,需要创建应用来写逻辑。
python manage.py runserver 127.0.0.1:8080
ip和port可以不用写,不写的话,默认是 127.0.0.1:8000
python manage.py startapp blog #blog是应用名称
目录介绍
models.py 数据库相关内容
views.py 视图,业务逻辑代码相关内容
tests.py 用来写一些测试代码,用来测试我们自己写的视图的,目前用不到
def home(request):
#request--environ 请求相关数据,request叫做HTTPRequest对象,请求相关数据都是request的属性
return render(request, 'home.html')
#render方法用来打开html文件,对文件进行模板渲染,第一个参数是request,第二个参数是html文件路径,(这里我只写了文件名称,因为django会自动去templates文件夹下面去找html文件),渲染完整之后,返回响应页面数据,最终交给wsgi中的socket将页面数据返回给客户端
第二步:创建html文件
如果视图响应的是一个html文件,那么我们需要在项目目录下面的templates文件夹中创建一个htm文件,比如叫做home.html
为什么django能够自动找到templates文件夹下面的home.html文件呢?也就是我们使用render方法时,只写了一个home.html文件名称,就能够自动帮我们找到这个home.html文件,是因为在django的默认配置中有一个关于templates模板相关功能的配置项,里面指定了html文件的存放目录,找到主目录下面的settings.py,打开这个文件,找到TEMPLATES这个配置,内容如下
#BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 项目根路径
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')] #DIRS指的就是html文件存放路径的配置,注意,有些版本的django或者通过指令来创建项目时,这个DIRS的配置是空的,需要我们自己指定html文件的存放路径,所以如果你发现这个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',
],
},
},
]
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ # url(r'^admin/', admin.site.urls), #这是django提供的一个内置应用,暂时忽略它 # ('/home',views.home), 我们之前的写法 url(r'^home/', views.home), ]
url中的正则路径如何找到对应的函数的,看简单分析:
#伪代码 import re path = request.path 当前请求路径 for item in urlpatterns: ret = re.match(item.regex,path) #item.regex -- ^home/ if ret: item.func(request) #item.func--views.home函数 break else: pass
简单介绍几个request对象的属性
在views.py文件的视图函数中打印以下几个属性,看看效果:
def home(request): print(request) #<WSGIRequest: GET '/home/'> wsgi封装的一个对象,WSGIRequest类的对象 print(request.path) #/home/ 请求路径 print(request.method) #GET 请求方法 print(request.POST) #获取post请求提交过来的数据 # <QueryDict: {'username': ['root'], 'password': ['123']}> uname = request.POST.get('username') #取出username数据 return render(request, 'xx/home.html')
####一个视图函数搞定登录功能##### def login(request): if request.method == 'GET': return render(request,'login.html') else: print(request.POST) # <QueryDict: {'username': ['root'], 'password': ['123']}> uname = request.POST.get('username') pwd = request.POST.get('password') if uname == 'root' and pwd == '123': return render(request,'xx/home.html') else: return HttpResponse('用户名或者密码错误!!请重新登录')
urls.py文件内容如下
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^home/', views.home), url(r'^login/', views.login), # url(r'^login2/', views.login2), ]
Login.html内容如下
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- /login2/ 相对路径,注意前置导航斜杠必须写,那么发送请求时,会自动在相对路径前面拼接上我们当前的ip和端口 http://127.0.0.1:8000/login2/--> {#<form action="/login2/" method="post">#} <form action="/login/" method="post"> 用户名: <input type="text" name="username"> 密码: <input type="password" name="password"> <button>提交</button> </form> </body> </html>
在django中,url中的路径写法是正则,正则里面有无名分组正则,有有名分组正则,那么对应django中的功能,我们称之为无名分组路由和有名分组路由
无名分组路由
看写法,urls.py文件中内容如下
urlpatterns = [ ... url(r'^books/(\d+)/(\d+)/', views.book), #正则里面()分组正则,会将分组中的正则匹配到的内容作为返回值返回 ] 简单分析,伪代码 ''' 当用户请求的路径是它: /books/2019/8/ url(r'^books/(\d+)/(\d+)/', views.book), 里面做的事情如下 re.match(^books/(\d+)/,/books/2019/) 2019 和 8 作为位置参数交给了要执行的book视图函数 视图函数book的写法 def book(request,xx,oo): xx = 2019 oo = 8 pass '''
视图views.py文件中函数的写法
#位置传参,url中正则^books/(\d+)/(\d+)/,那么第一个()分组匹配到的数据,作为book函数的第二个参数,第二个()分组匹配到的数据,作为book的第三个参数 def book(request, year, month): print('year', year, 'month', month) #year 2020 # return HttpResponse('%s所有书籍' % year) return HttpResponse('%s年%s月所有书籍' % (year, month))
使用url路由系统时需要注意几个点
1. urlpatterns中的元素按照书写顺序从上往下逐一匹配正则表达式,一旦匹配成功则不再继续。 2. 若要从URL中捕获一个值,只需要在它周围放置一对圆括号(正则分组匹配)。 3. 不需要添加一个前导的反斜杠(也就是写在正则最前面的那个/),因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。 4. 每个正则表达式前面的'r' 是可选的但是建议加上。 5. ^articles$ 以什么结尾,以什么开头,严格限制路径
urlpatterns = [ url(r'^admin/', admin.site.urls), # /books/2020/6/ url(r'^books/(?P<year>\d+)/(?P<month>\d+)/', views.book), # {'year':2020,'month':6},url类将有名分组正则匹配出来的数据,交给了book视图函数作为关键字参数来使用 ]
View.py文件中函数的写法如下
# ^books/(?P<year>\d+)/(?P<month>\d+)/ #获取到url中的有名分组正则匹配到的数据,那么函数形参名称必须和有名分组正则的那个名称相同才可以,也就是按照上面的url来看的话,函数的形参必须是year和month这两个名称,并且关键字传参不需要考虑函数形参的位置 def book(request, month, year): # print('year', year, 'month', month) #year 2020 print(request.path) #/books/2020/6/ # return HttpResponse('%s所有书籍' % year) return HttpResponse('%s年%s月所有书籍' % (year, month))
当用户通过浏览器访问django框架完整的项目中的某个路径时,如果用户在输入网址路径的最后,没有加上/斜杠,比如http://127.0.0.1:8000/test
,那么django会发将用户输入的网址路径加上一个后置的/,也就会将路径变成这样http://127.0.0.1:8000/test/
,然后给浏览器发送一个重定向的响应操作,状态码为301,那么浏览器拿到这个重定向操作之后,就会自动发起一个这样的路径请求http://127.0.0.1:8000/test/
,所以当我们打开浏览器控制台的network功能查看请求过程时,会看到两个请求,一个没有后置的斜杠的,一个是有后置斜杠的。第二个请求才会走到我们的urs.py文件中的路径配合和分发对应视图的地方。
我们可以通过一个配置项,告诉django,不要自动加路径后面的斜杠了,但是需要注意的就是你自己写的url中的正则,也别加后面的斜杠,不然正则匹配不到。
配置项直接写在settings配置文件中,任意位置
APPEND_SLASH = False #False表示告诉Django,不加路径后面的斜杠,默认值是True
# 在路由没有匹配任何参数的时候,num使用自己的默认值 # 当路由中有分组匹配数据的动作,比如url(r'^test/(\d+)/', views.test),用户输入网址:http://127.0.0.1:8000/test/22/,那么22就被匹配到了,会作为参数传给我们的test函数,那么num的值就变成了22 def test(request, num=10): print('number>>>',num) return HttpResponse('test')
urls.py文件
# url(r'^test/', views.test), url(r'^test/(\d+)/', views.test),
多个应用
路由分发 include和命名空间 namespace
1 创建app
python manage.py startapp app02
2 配置app,在settings.py文件中
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', 'app02'
路由分发步骤
1 在项目主目录下的urls.py(以后我们称之为总路由)文件中,写上如下内容
from django.conf.urls import url, include from django.contrib import admin # 路由分发功能 urlpatterns = [ url(r'^admin/', admin.site.urls), # /app01/index/ -- 去掉/app01/ -- index/拿到app01应用下面的urls.py文件中进行路径匹配 url(r'^app01/', include('app01.urls')), # 当访问路径是以/app01/开头的,那么django主目录下面的urls.py文件会自动帮我们去找app01下面的urls.py文件进行路径匹配 url(r'^app02/', include('app02.urls')), #include参数格式 '应用名称.urls' ]
2 在每个应用文件夹下面创建一个叫做urls.py的文件,里面写上如下内容
from django.conf.urls import url, include from app01 import views # 路由分发功能 urlpatterns = [ #' index/' # 写上自己应用中的每个路径对应的函数 url(r'^index/', views.index), ]
出现一个问题,当两个应用中的某个url的别名相同了,那么使用url反向解析的时候,发现都是解析出来的都是最后一个应用的对应路径,这叫做别名冲突,如下。
app01下的urls.py
from django.conf.urls import url, include from app01 import views # 路由分发功能 urlpatterns = [ url(r'^index/', views.index), url(r'^home/', views.home, name='home'), ]
App02下的urls.py
from django.conf.urls import url, include from app02 import views # 路由分发功能 urlpatterns = [ url(r'^index/', views.index), url(r'^home/', views.home, name='home'), ]
App01的views.py文件中进行别名发现解析,代码:
def index(request): print('app01>>>',reverse('home')) #app01>>> /app02/home/ return HttpResponse('app01-index')
App02的views.py文件中进行别名发现解析,代码:
def index(request): print('app02>>>',reverse('home')) #app02>>> /app02/home/ return HttpResponse('app02-index')
解决方法,命名空间
示例:总路由写法
from django.conf.urls import url, include from django.contrib import admin # 路由分发功能 urlpatterns = [ url(r'^admin/', admin.site.urls), # 路由分发时,给每个应用的路由进行了空间划分,使用namespace url(r'^app01/', include('app01.urls', namespace='app01')), url(r'^app02/', include('app02.urls', namespace='app02')), ]
views.py文件写法
def index(request): # 在进行反向解析时,需要用到命名空间:别名,来进行解析 print('app02>>>',reverse('app02:home')) #app02>>> /app02/home/ return HttpResponse('app02-index')