Python自动化运维 - Django(一)Urls基础-Views基础-Orm基础
什么是web框架
众所周知,所有的Web应用基本都是基于B/S结构,其本质上其实就是一个socket服务端(web服务器),一个socket客户端(用户浏览器)。下面的代码就是一个简单的web端,运行后,使用浏览器访问,会打印hello world
#!/usr/bin/env python #_*_coding:utf-8_*_ import socket def handle_request(client): buf = client.recv(1024) client.send(b"HTTP/1.1 200 OK\r\n\r\n") client.send(b"Hello, world") def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost',8000)) sock.listen(5) while True: connection, address = sock.accept() handle_request(connection) connection.close() if __name__ == '__main__': main()
上述代码通过socket实现了web服务端的本质,而对于真实开发中的Python Web程序来说,一般会分为两部分:服务器程序和应用程序。
-
- 服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。
- 应用程序则负责具体的逻辑处理。
为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。
WSGI
WSGI全称为:The Python Web Server Gateway Interface,单从名字来看就是一种网关,而网关的作用就是在协议之间进行转换。
WSGI是为 Python 语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接口,是一种规范,定义了使用Python编写的Web app与Web server之间接口格式,实现web app与web server间的解耦。
Python标准库提供的独立WSGI服务器称为wsgiref。
# 利用wsgiref完成上面简单的web服务程序 from wsgiref.simple_server import make_server def RunServer(environ, start_response): # environ 存放的是用户访问时提交的信息,包涵http header等 start_response('200 OK', [('Content-Type', 'text/html')]) # 构建http response头部信息 return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ] # 返回给用户的信息,注意是 bytes 格式。 if __name__ == '__main__': httpd = make_server('', 8000, RunServer) # 启动一个服务,监听8000端口,请求进来交给RunServer函数处理 print("Serving HTTP on port 8000...") httpd.serve_forever() # 启动服务开始接受请求
MVC与MTV模式
说到web开发,就需要遵循一定的开发模式。著名的MVC/MTV模式,其本质是使各组件之间保持松耦合关系。
MVC框架
MVC 是一种使用 MVC(Model View Controller 模型-视图-控制器)设计创建 Web 应用程序的模式
- Model(模型)表示应用程序核心(比如数据库记录列表)
- View(视图)显示数据(数据库记录)
- Controller(控制器)处理输入(写入数据库记录)
MVC 模式同时提供了对 HTML、CSS 和 JavaScript 的完全控制。
- Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。
- View(视图)是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的前端网页。
- Controller(控制器)是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据映射,模式渲染等。
MTV框架
MTV是一种使用MTV(Model Templates Views 模型-模版-视图)设计创建 Web 应用程序的模式
- Model(模型):负责业务对象与数据库的对象(ORM)
- Template(模版):负责如何把页面展示给用户
- View(视图):负责业务逻辑,并在适当的时候调用Model和Template
此外,Django还有一个url分发器,它的作用是将一个个URL的页面请求分发给不同的view处理,view再调用相应的Model和Template
区别
MVC即模型-视图-控制器模式,就是为那些需要为同样的数据提供多个视图的应用程序而设计的。它很好地实现了数据层与表示层的分离,特别适用于开发与用户图形界面有关的应用程序。控制器用来处理用户命令以及程序事件;模型维护数据并提供数据访问方法;视图用于数据的显示。
MTV即模型-模版-视图模式,其标准名称是有争议的。在MVC的解释中,视图描述了展现给用户的数据,是指所看到的数据,而不是如何看见它。在python中视图是指对某一特定URL的回调函数,因为回调函数描述了所要展现的数据。模版用于将内容与展现分离。在django中,视图描述了要展现的数据,而视图一般转交给模版。模版描述了数据如何展现。控制器则是指django框架本身,通过URL配置,系统将一个请求发送到一个合适的视图。
Django处理顺序
Django是标准的MTV框架。
- wsgi:socket请求处理
- 控制器(django框架本身):控制用户输入,url匹配,通过映射列表将一个请求发送到一个合适的视图;
- views --Views:python程序,向模型和模板发送(或获取)数据;
- 模型绑定 --Model:数据库存取数据
- 模板引擎 --Templates:用于将内容与展现分离,描述了数据如何展现(如网页模板);
- 模式渲染 --Views:将模板和数据整合,形成最终网页;
- 控制器(django框架本身):返回用户展示。
Django基础命令
安装Django
使用命令行执行如下命令进行安装
pip3 install Django
注意:会安装在python安装目录的scripts目录下。针对多环境的情况下,使用哪个解释器安装,那么就会安装在哪个版本下。
安装完毕后会在scripts下产生一个django-admin.py文件,利用该文件可以在命令行下创建一个Django项目。
利用命令创建一个Django项目
1 创建一个django project
django-admin.py startproject PEOJECTNAME
创建完毕后:会产生PROJECTNAME命名的文件夹,里面包含 PROJECTNAME 目录 和 manager.py文件。
其中:
-
- manager.py 用来管理我们所有的命令的文件
- PROJECTNAME:存放全局的相关文件
- settings.py:项目的配置信息文件
- urls.py:把用户发过来的url通过urls文件来过滤(控制),交给某个view函数来处理 -->控制器
- wsgi.py:处理socket相关的任务(类nginx/apache的功能) --> 一个 WSGI 兼容的 Web 服务器的入口,以便运行你的项目。 生产上一般不会使用wsgi(第三方模块),一般会使用uwsgi + nginx 来运行。
2 创建一个app
python manage.py startapp APPNAME
为什么一个项目里要创建一个应用?
举个例子:微信是一个项目,那么通讯录,钱包,朋友圈就是一个个相互独立功能,这里叫做应用。一个项目是所有应用的集合。
创建一个应用之后会产生同名目录,目录下的主要文件功能如下:
-
- models.py:MTV框架,存放和数据进行操作的代码
- admin.py:django的数据库后台管理工具
- views.py:MTV框架,包含所有的视图函数,由urls.py 来选择发送给具体哪个view函数
- test.py:用来测试的
注意:views.py 中的函数:一定是来根据用户输入的内容来返回一个具体的页面,wsgi模块用来负责HTTP协议的解析,然后放在views视图函数中,作为一个固定的参数(request)使用。
3 启动Django项目
python manage.py runserver PORT
启动一个web应用服务,并监听8800端口
Django 版的 hello world
1 定义URL选择器
定义url选择器是为了能在用户输入url的时候把对应的url映射到指定的views函数中去处理,所以urls.py会存放在项目全局的目录下。
from blog import views # blog为项目名,导入views模块 urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^blog/',views.blog), # 匹配已blog结尾的url,交给views下的index函数处理 url(r'',views.index), # 就可以表示首页 ] # 注意:这里的url,是正则表达式
2 定义views函数
经过项目的urls控制,那么请求将会分配给具体的应用(函数)来处理,所以需要在应用的目录中的views.py文件中进行添加。由于我们返回的是静态页面,不需要进行数据库相关的处理,所以在index函数内,可以直接返回页面给客户端
这里有两种方式:直接返回html标签、返回html文件
返回html标签:
from django.shortcuts import render,HttpResponse
# request:请求信息对象 HttpResponse:响应信息对象 def index(request): return HttpResponse('<h1>hello world</h1>')
# 注意:用户的请求经由wsgi处理后,会返回一个对象标识,一般这里使用request来接受这个对象。 # HttpResponse 用户对客户的相应
返回html文件:
所有的html文件是由Templates来进行处理的,所以需要现在Templates目录下创建要返回的html文件
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <h1>hello world</h1> 9 </body> 10 </html>
from django.shortcuts import render,HttpResponse # request:请求信息对象 HttpResponse:响应信息对象 def index(request): return render(request,'index.html') # render,的第一个参数就是request,标识把后面的index文件返回给这个request # 答复的html文件,render会去Templates下寻找,因为在setting.py文件中已经定义了Templates的路径
3 启动服务
# 在项目目录下执行 python manage.py runserver 8800
启动完毕后,使用浏览器访问。
127.0.0.1:8800/blog # 这里的blog表示应用的名称
1 # 思路或者说流程如下 2 # 1.定义登陆界面的urls 3 # 2.定义处理函数 4 # 3.定义返回的页面 5 6 7 # 1.定义项目的urls 8 from django.conf.urls import url,include 9 from django.contrib import admin 10 from blog import views 11 12 urlpatterns = [ 13 url(r'^login_in/', views.login_in), 14 ] 15 16 ======================================== 17 18 # 2.定义处理函数views.login_in 19 def login_in(request): 20 21 if request.method == 'GET': 22 23 if not request.GET : 24 return render(request,'login_in.html') 25 else: 26 username = request.GET.get('username',None) 27 password = request.GET.get('password',None) 28 29 if username == 'dachenzi' and password == '123456': 30 return HttpResponse('login Success!') 31 else: 32 return HttpResponse('Wrong username or password!') 33 if request.method =='POST': 34 if not request.POST: 35 return render(request,'login_in.html') 36 else: 37 username = request.POST.get('username', None) 38 password = request.POST.get('password', None) 39 40 if username == 'dachenzi' and password == '123456': 41 return HttpResponse('login Success!') 42 else: 43 return HttpResponse('Wrong username or password!') 44 # 注意这里第一次访问会返回login_in页面,而在login页面中,我们提交了用户名密码等信息,由于我们没有传递csrftoken信息,DJango会对POST请求默认拒绝,所以会显示403,可以手动的关闭这个动作 45 # 在项目的配置文件中setting.py中取消django.middleware.csrf.CsrfViewMiddleware,即可 46 # request.POST/GET 会保存用户提交的数据(类似字典的形式,可以使用字典来获取) 47 # username = request.POST['username'] 48 # password = request.POST['pwd'] 49 # 上面这中方式也可以取,但是如果key不存在,那么会报错.所以建议使用get方法 50 51 52 ======================================== 53 54 55 # 3.编写返回的html文件 56 <!DOCTYPE html> 57 <html lang="en"> 58 <head> 59 <meta charset="UTF-8"> 60 <title>Title</title> 61 </head> 62 <body> 63 64 <form action="/login_in/" method="post"> 65 <p>姓名 <input type="text" name="username"></p> 66 <p>密码 <input type="text" name="password"></p> 67 <p><input type="submit" value="登陆"> </p> 68 </form> 69 70 </body> 71 </html>
基础配置
为了使我们的项目依赖的文件放在特定的地方提供访问,那么我们在创建完项目还需要做一些基础的配置。
配置静态文件存放路径
静态文件:比如css,js文件都是要提供给用户进行下载的,所以我们一般规定静态文件,存放在django项目的一级子目录static中,需要手动创建这个目录,然后做如下配置。
# 修改settings.py文件 STATICFILES_DIRS = ( os.path.join(BASE_DIR,'static'), # 添加静态文件路径 )
配置模板路径
用来集中存放用于返回给用户的模板文件(如果是利用pycharm创建的项目,它会自动帮我们创建template目录,并自动进行关联),创建templates目录(同样存放在django项目的一级子目录下)。
# 修改settings.py文件 TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')] # 添加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', ], }, }, ]
关闭csrftoken
csrftoken用于防止csrf攻击,csrf的相关配置将在后面进行说明,所以这里可以先关闭csrftoken。
# 修改settings.py文件 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', # 注释掉csrf中间件即可(需要重启django项目) 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
路由系统(urls控制)
url控制其实就是把不同的url对应到不同的views函数中去
格式:
urlpatterns = [ url(正则表达式,views视图函数,参数,别名), ]
url可以有多个,每个url都是一个独立的规则。
参数如下:
-
regex: 正则表达式,与之匹配的 URL 会执行对应的第二个参数 view。
-
view: 用于执行与正则表达式匹配的 URL 请求。
-
kwargs: 视图使用的字典类型的参数。 --> 很少使用
-
name: 用来反向获取 URL。
URLconf的正则字符串参数
例子:
urlpatterns = [ url(r'^articles/2003/$', views.special_case_2003), url(r'^articles/([0-9]{4})/$', views.year_archive), # 把url加上括号,就表示取出,那么会默认传给后面的views函数,如果不定义变量接受,就会报错 url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail), ]
注意:
- 一旦匹配成功则不再继续
- 不需要添加一个前导的反斜杠,因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。
- 每个正则表达式前面的'r' 是可选的但是建议加上。
url的分组
若要从URL 中捕获一个值,只需要在它周围放置一对圆括号。分为无名分组和有名分组:
无名分组
即捕获url中的一个值时,会当作参数按照位置传递给后面的views函数。
只需要把要分组的正则字符串用括号括起来即可。
urlpatterns = [ url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), ] # ([0-9]{4}) 第一个位置参数 # ([0-9]{2}) 第二个位置参数
views函数需要定义位置参数来一一对应
有名分组
即捕获url中的一个值时,并赋予其名称,使用关键字参数来进行传递。
在Python 正则表达式中,命名正则表达式组的语法是(?P<name>pattern),其中name 是组的名称,pattern 是要匹配的模式。
例子:
urlpatterns = [ url(r'^articles/(?P<year>[0-9]{4})/(?P<mouth>[0-9]{2})/$', views.article_detail), ] # 会传递year和mouth 这两个关键字参数。
这意味你的URLconf 会更加明晰且不容易产生参数顺序问题的错误 —— 你可以在你的视图函数定义中重新安排参数的顺序。
URL匹配规则在什么上查找
URLconf 在请求的URL 上查找,将它当做一个普通的Python 字符串。不包括GET和POST参数以及域名。
-
- GET:把用户发送的参数放在URL中进行传递,在URL中一般用?隔开。
- POST:把用户发送的参数放在请求头中传递。
例如,http://www.example.com/myapp/ 请求中,URLconf 将查找myapp/。
在http://www.example.com/myapp/?page=3 请求中,URLconf 仍将查找myapp/。
URLconf 不检查请求的方法。换句话讲,所有的请求方法 —— 同一个URL的POST、GET、HEAD等等 —— 都将路由到相同的函数。
指定视图参数的默认值
有一个方便的小技巧是指定视图参数的默认值。 下面是一个URLconf 和视图的示例:
# URLconf from django.conf.urls import url from . import views urlpatterns = [ url(r'^blog/$', views.page), url(r'^blog/page(?P<num>[0-9]+)/$', views.page), ] # View (in blog/views.py) def page(request, num="1"): ...
在上面的例子中,两个URL模式指向同一个视图views.page —— 但是第一个模式不会从URL 中捕获任何值。如果第一个模式匹配,page() 函数将使用num参数的默认值"1"。如果第二个模式匹配,page() 将使用正则表达式捕获的num 值。
上面的方法是通过给函数指定默认值,当然在url中也是可以指定默认值的
urlpatterns = [ url(r'text$',view.test,{'name':'test'}) # 等于在url传递的时候,传递name = test,给test函数 ]
include(路由分发)
当项目中的应用变得越来越多的时候,如果所有的应用的URL都通过项目的urls统一进行分配,那么耦合度会很高,另外也不利于管理,所以这里通过include来交给应用的urls来处理。
from django.conf.urls import include, url urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^blog/', include('blog.urls')), ] # 通过include 来指定交给那个应用的urls来处理 # include 包涵在 django.conf.urls中
1 # 项目的urls 2 3 from django.conf.urls import url,include 4 from django.contrib import admin 5 from blog import views 6 7 urlpatterns = [ 8 url(r'^admin/', admin.site.urls), 9 url(r'^blog/',include('blog.urls')) 10 ] 11 12 13 #应用的urls 14 from django.conf.urls import url 15 from django.contrib import admin 16 from blog import views 17 18 urlpatterns = [ 19 url(r'^admin/', admin.site.urls), 20 url(r'^([0-9]{4})/([0-9]{2})/([0-9]{2})/$',views.index) 21 ]
注意:路由分发后,子路径的起始位置就从分发的URL开始了。
urlpatterns = [ url(r'blog/',include('blog.urls')), ] # blog.urls urlpatterns = [ url(r'admin/$',views.admin), ] # 这里admin的路径为: blog/admin/
别名(name参数)
当我们在做路径匹配然后配合from表单等需要提交的数据的元素的时候会遇到以下问题
url文件:
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/', views.login), # 匹配路径 ]
返回的页面文件:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>登录页面</h1> <form action="/login/" method="post"> <!-- 根据路径进行提交 --> <p><h2>姓名</h2></p> <p><input type="text" name="username"></p> <p><h2>密码</h2></p> <p><input type="text" name="password"></p> <p><input type="submit" value="登录"></p> </form> </body> </html>
-
- 如果我们某一天改了url匹配的路径,那么,就绪要修改页面中所有的提交路径,否则将会提交失败
而url的name就是解决这个问题的。
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/$', views.login,name='LOGIN'), # 添加name关键字 ]
返回的html文件如下
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>登录页面</h1> <form action="{% url 'LOGIN' %}" method="post"> <!-- 这里使用name关键字来动态的传递url --> <p><h2>姓名</h2></p> <p><input type="text" name="username"></p> <p><h2>密码</h2></p> <p><input type="text" name="password"></p> <p><input type="submit" value="登录"></p> </form> </body> </html>
这样就算URL后期更改,也会动态指向最新的URL。
-
- 个人觉得说白了就是一个变量的替换,只不过是用的是django特有的语法格式。
- name='LOGIN' 就是把前面匹配的url保存到LOGIN中,然后Django 在返回html文件的时候再进行替换。
补充:
当url存在正则表达式的时候,只使用name参数是不行的。因为正则部分无法进行渲染。目前的解决方法是在url部分进行拼接
# --------------------------- url --------------------------- from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^abc/(\d+)/', views.login,name="LOGIN"), ] # -------------------------- Teamplates --------------------------- <form action="{% url "LOGIN" 4 %}"> # 这里正则匹配了几个部分,那么就需要传递几个参数 <p>Username:</p> <input type="text" name="username"> <p>Password:</p> <input type="text" name="password"> <input type="submit" value="提交"> </form>
反推URL
什么叫反推url?在views函数中,如果想要获取当前函数对应的url,该怎么办呢?还记得前面的name属性吗,反推url就是在views中根据name属性的值,获得对应的url
from django.urls import reverse url = reverse('name')
当然reverse还有两个参数(args,kwargs)
args = () kwargs = {} # 参数是配合urls中的正则表达式的 url('^detail/(\d+)' ,name='i1',views.detail) -- > reverse('i1',args=(10,)) # 反推的URL为: detail/10 # kwargs则表示在命名关键字的情况下 url('^detail/(?P<nid>\d+)' ,name='i2',views.detail) -- > reverse('i1',kwargs={'nid':10}) # 反推的url为: detail/10
PS:还有一个方法是利用request对象的path_info,因为其中存放的是用户提交的url。
命名空间
当在url(路由系统)中使用了include时,在views函数中,我们就无法单独的利用name参数来反推url了,因为在include时,是无法使用name关键字的,不过django提供了其他关键字提供类似功能:namespace,称作命名空间。
# urls.py url = [ url(r'crm/',include('crm.urls'),namespace='crm'), url(r'cmdb/',include('cmdb.urls'),namespace='cmdb'), ] # crm 中的 urls.py app_name = crm url = [ url(r'index/',views.index,name='index'), ] # crm 中的views.py def test(request): url = reverse('crm:index') # 这里通过namespace:name 来反向获取url print(url) return HttpResponse(200)
PS:在html中,通过{% url 'crm:index' %} 也是通过namespace:name来获取url的。
views视图函数
一个视图函数,或者简短来说叫做视图,是一个简单的Python函数,它接受web请求,并且返回web响应。无论视图本身包含什么逻辑,都要返回响应。
简单的视图函数
编写一个简单的视图函数,通过访问指定的url来获取当前时间。
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^timer/$', views.current_datetime), ]
views函数:
from django.http import HttpResponse import datetime def current_datetime(request): now = datetime.datetime.now() html = "<html><body>It is now %s.</body></html>" % now return HttpResponse(html)
1. 访问timer,会转到current_datetime函数进行处理
2. 通过date模块获取当前时间,然后通过Httpresponse进行返回,每个视图函数都要返回HttpResponse对象
注意:
- 请求对象 : Httprequest() 保存这请求对象的属性以及值(函数的参数request其实就是它的实例化对象)
- 响应对象 : Httpresponse() 在views函数中,每次返回的都是一个Httpresponse的对象,render() 其实也是在内部进行渲染后返回的还是一个Httpresponse对象
快捷函数
快捷函数存放的位置是:django.shortcuts。它收集了“跨越” 多层MVC 的辅助函数和类。 换句话讲,这些函数/类为了方便,引入了可控的耦合。
HttpResponse函数
返回给用户的信息,必须是一个HttpResponse对象,接受字符串,或者字节。
return HttpResponse('hello world')
render函数
主要用来做模板的渲染
注意:不管什么函数最后都会返回 Httpresponse对象,只不过render函数对这个对象进行封装了。
格式如下:
render(request,template_name,context=None)
-
- request:表示经过wsgi处理后返回的一个请求对象
- template_name:表示要返回的模板文件的名称
- context:表示要经过render渲染的变量及key的对应关系
通过render函数编写访问获得时间例子:
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^timer/$', views.timer), # 定义url入口 ]
views函数:
def timer(request): import datetime now = datetime.datetime.now() return render(request,'timer.html',{'TIME':now}) # 构建context关键字,进行传递
返回的html文件:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>current time {{ TIME }}</h1> <!-- 变量引用 --> </body> </html> <!-- 变量会在 render返回的时候进行渲染 -->
redirect函数
用于重定向跳转
格式如下:
redirect('/url/')
-
- 跳转到新的url进行访问
urls文件
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^timer/$', views.timer), url(r'^login/$', views.login,name='LOGIN'), # 登陆页面 url(r'^check/$', views.check,name='CHECK'), # 检查用户名 url(r'^welcome/$', views.welcome), # 欢迎页面 ]
views文件
def login(request): return render(request,'login.html') def check(request): username = request.POST.get('username',None) password = request.POST.get('password',None) if username == 'dachenzi' and password == '123': return redirect('/welcome/') #跳转链接到welcome def welcome(request): return render(request,'welcome.html')
PS:服务端应答的是要跳转的url,告诉client要去访问返回的这个url,client接受后,会访问应答的url。
request到底是什么
在views中定义函数的参数request到底是什么?包含了什么?它有哪些属性,方法?
我们通过打印它所属的类,类查看相关的类属性(或者在views函数中,直接打印request)
# views函数 def index(request): print(type(request)) return render(request,'index.html') # 查看打印结果 <class 'django.core.handlers.wsgi.WSGIRequest'>
导入这个模块来查看属性信息
class WSGIRequest(http.HttpRequest): def __init__(self, environ): script_name = get_script_name(environ) path_info = get_path_info(environ) if not path_info: path_info = '/' self.environ = environ self.path_info = path_info self.path = '%s/%s' % (script_name.rstrip('/'), path_info.replace('/', '', 1)) self.META = environ self.META['PATH_INFO'] = path_info self.META['SCRIPT_NAME'] = script_name self.method = environ['REQUEST_METHOD'].upper() self.content_type, self.content_params = cgi.parse_header(environ.get('CONTENT_TYPE', '')) ... ... ... ...
代码较多这里就不列出了。注意到各个参数都是通过一个叫environ的东西来获取的,那么什么是enviro呢?我们来打印一下
1 {'PROCESSOR_REVISION': '3a09', 2 'PROCESSOR_LEVEL': '6', 3 'CONTENT_TYPE': 'text/plain', 4 'PATH_INFO': '/welcome/', 5 'PUBLIC': 'C:\\Users\\Public', 6 'HTTP_UPGRADE_INSECURE_REQUESTS': '1', 7 'COMPUTERNAME': 'MANINTHEMIRROR', 'PROCESSOR_IDENTIFIER': 'Intel64 Family 6 Model 58 Stepping 9, GenuineIntel', 8 'ASL.LOG': 'Destination=file', 9 'wsgi.multithread': True, 10 'PYTHONIOENCODING': 'UTF-8', 11 'PROGRAMW6432': 'C:\\Program Files', 12 'SYSTEMROOT': 'C:\\WINDOWS', 13 'GOROOT': 'E:\\Go\\', 14 'USERDOMAIN': 'MANINTHEMIRROR', 15 'LOGONSERVER': '\\\\MANINTHEMIRROR', 16 'SCRIPT_NAME': '', 17 'LOCALAPPDATA': 'C:\\Users\\Are you SuperMan\\AppData\\Local', 18 'DJANGO_SETTINGS_MODULE': 'myblog.settings', 19 'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files', 20 'HOMEDRIVE': 'C:', 21 'APPDATA': 'C:\\Users\\Are you SuperMan\\AppData\\Roaming', 22 'ONEDRIVE': 'C:\\Users\\Are you SuperMan\\OneDrive', 23 'PROCESSOR_ARCHITECTURE': 'AMD64', 24 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, sdch, br', 25 'PATH':'D:\\Python35\\Scripts\\;D:\\Python35\\;C:\\ProgramData\\Oracle\\Java\\javapath;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files\\Intel\\WiFi\\bin\\;C:\\Program Files\\Common Files\\Intel\\WirelessCommon\\;C:\\Program Files (x86)\\NVIDIA Corporation\\PhysX\\Common;C:\\WINDOWS\\system32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem;C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files (x86)\\Common Files\\Acronis\\SnapAPI\\;D:\\Program Files\\TortoiseSVN\\bin;M:\\Program Files\\Git\\cmd;D:\\Python27;D:\\Python27\\Scripts\\;C:\\Program Files\\Intel\\WiFi\\bin\\;C:\\Program Files\\Common Files\\Intel\\WirelessCommon\\;H:\\putty;"C:\\Users\\Are you SuperMan\\Desktop\\Icon";D:\\Program Files\\IDM Computer Solutions\\UltraEdit\\;C:\\Users\\Are you SuperMan\\AppData\\Local\\Microsoft\\WindowsApps;', 26 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', 27 'USERDOMAIN_ROAMINGPROFILE': 'MANINTHEMIRROR', 28 'wsgi.file_wrapper': <class 'wsgiref.util.FileWrapper'>, 29 'FPS_BROWSER_USER_PROFILE_STRING': 'Default', 30 'OS': 'Windows_NT', 31 'REMOTE_HOST': '', 32 'USERNAME': 'Are you SuperMan', 33 'USERPROFILE': 'C:\\Users\\Are you SuperMan', 34 'PYTHONPATH': 'D:\\python Django 学习\\myblog', 35 'wsgi.input': <_io.BufferedReader name=280>, 36 'RUN_MAIN': 'true', 37 'SERVER_SOFTWARE': 'WSGIServer/0.2', 38 'SERVER_PORT': '8000', 39 'REQUEST_METHOD': 'GET', 40 'HTTP_CONNECTION': 'keep-alive', 41 'MOZ_PLUGIN_PATH': 'M:\\Program Files (x86)\\Foxit Software\\Foxit Reader\\plugins\\', 42 'HTTP_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', 43 'PYTHONUNBUFFERED': '1', 44 'TMP': 'C:\\Users\\AREYOU~1\\AppData\\Local\\Temp', 45 'PSMODULEPATH': 'C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\Modules\\', 46 'PROGRAMFILES': 'C:\\Program Files', 47 'REMOTE_ADDR': '127.0.0.1', 48 'HTTP_COOKIE': 'csrftoken=wHbeFOZqTco2hK46d2kK3O4n6zHJNTJ4xkxIMSLudnP0g3GjMqPD9oug6fJeCCw7', 49 'NUMBER_OF_PROCESSORS': '8', 50 'wsgi.version': (1, 0), 51 'SERVER_NAME': 'ManInTheMirror', 52 'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>, 53 'AWE_DIR': 'M:\\Program Files (x86)\\Khrona LLC\\Awesomium SDK\\1.6.6\\', 54 'GOPATH': 'E:\\GO\\bin', 55 'wsgi.multiprocess': False, 56 'wsgi.url_scheme': 'http', 57 'TEMP': 'C:\\Users\\AREYOU~1\\AppData\\Local\\Temp', 58 'HOMEPATH': '\\Users\\Are you SuperMan', 59 'HTTP_HOST': '127.0.0.1:8000', 60 'PROGRAMFILES(X86)': 'C:\\Program Files (x86)', 61 'CONTENT_LENGTH': '', 62 'SESSIONNAME': 'Console', 63 'wsgi.run_once': False, 64 'WINDIR': 'C:\\WINDOWS', 65 'QUERY_STRING': '', 66 'SERVER_PROTOCOL': 'HTTP/1.1', 67 'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.8', 68 'PYCHARM_HOSTED': '1', 69 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 70 'COMSPEC': 'C:\\WINDOWS\\system32\\cmd.exe', 71 'FP_NO_HOST_CHECK': 'NO', 72 'SYSTEMDRIVE': 'C:', 73 'ALLUSERSPROFILE': 'C:\\ProgramData', 74 'GATEWAY_INTERFACE': 'CGI/1.1', 75 'HTTP_CACHE_CONTROL': 'max-age=0', 76 'PROGRAMDATA': 'C:\\ProgramData', 77 'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files', 78 'FPS_BROWSER_APP_PROFILE_STRING': 'Internet Explorer', 79 'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PY;.PYW'}
通过上面的信息我们可以看到environ里面存放的都是用户发送的信息,比如HTTP报头。那么如果我想获取用户的浏览器标识就可以使用
request.environ.get['HTTP_USER_AGENT'] # 也可以针对不同的USER_AGENT来返回不同的页面
注意:
- 用户提交的数据,django内部会进行解析,能识别的会放在对应的对象中(比如request.GET,request.POST)。
- 对于PUT,DELETE方法,django没有提供解析方式,提交的数据会存放在request.body中。
- 关于请求头的信息可以通过request.environ,也可以通过request.Meta获取,因为它俩是一个东西。
获取用户提交数据
# 获取数据 request.method # 存放用户访问访问方法 post/get request.POST # 存放用户POST提交的数据 request.GET # 存放用户GET提交的数据 request.environ # 获取未经django解析的,用户提交的原数据。 request.FILES # 存放用户上传的文件 ... ... # 获取数据的方式 .get('name') # 获取单个数据 .getlist('name') # 获取列表数据(比如checkbox等)
form表单上传文件
针对form表单上传的文件,首先,form表单应该具有 enctype="multipart/form-data" 属性,否则文件无法上传,服务端要想获取用户上传的文件使用 request.POST.get('name') 是无法获取到信息的(表单不加form-data属性,这里会获取到文件名,因为上传的时候就没有上传文件的内容)。
想要获取用户上传的文件,那么需要在request.FILES中获取。
# request.FILES obj = request.FILES.get('name') # 获取文件对象 obj.name() # 文件的名称 obj.chunks() # 文件的内容(生成器) obj.size # 文件的大小
循环写入服务端
import os ... obj = request.FILES.get('filename') filename = os.path('upload',obj.name) with open(filename,'wb) as f: for chunk in obj.chunks(): f.write(chunk) ...
Template
使用 Django的 模板系统 (Template System)来实现将Python代码和HTML代码分开的目的。
python的模板包涵:HTML代码+逻辑控制代码
将具体数据嵌入到前端模板的语法,在一个html文件中包含模板语法的文件,可以认为是模板文件
模板的基础语法
主要分为两部分:渲染变量使用双大括号{{ }},渲染标签则使用双大括号双百分号{% %}
变量
在html页面中使用两个大括号包起来的字符串叫做变量:
{{ Var_name }}
这里通过python django的shell环境来举例(在这个环境中可以直接引用 所属django模块中的变量等信息)
# 使用 manage.py进入shell界面 python manage.py shell >>> from django.template import Context,Template >>> t = Template('My name is {{ name }}') >>> c = Context({'name':'dachenzi'}) >>> t.render(c) # render会把变量进行渲染 'My name is dachenzi' >>>
深度查询
如果传递的变量是属组或者字典,那么需要进行深度查询(通过使用.(点)来完成深度查询):
{{ Var_Name.username }} #获取Var_Name中的key为:username的值 --> 变量为字典类型 {{ Var_Name.2 }} # 获取Var_Name中第2个值 --> 变量为list类型
使用.带进行深度查询,查询list类型的数据
>>> from django.template import context,Template >>> t = Template('hello {{ name.1}}') >>> c = Context({'name':[1,2,3]}) >>> t.render(c) 'hello 2'
使用items循环字典类型
{% for key,value in user_dict.items %} <li>{{ key }}-{{ value }}</li> {% endfor %}
变量过滤器filter
可以理解为python中的内置函数,过滤器是模板的特有语法,通过前端来过滤部分数据。注意filter只能处理一个参数。
格式:
{{ var|method:parameter}}
method主要有:
# 1 add : 给变量加上相应的值 {{ num|add:3}} # 表示给后端传过来的num的值加上3 # 2 addslashes : 给变量中的引号前加上斜线 {{ book_info.book_name|addslashes }} #如果变量中包涵引号,打印的时候会添加斜线来转意 # 3 capfirst : 首字母大写 {{ book_info.book_name|capfirst }} # 4 cut : 从字符串中移除指定的字符 {{ book_info.book_name|cut:'L' }} #删除变量中的L # 5 date : 格式化日期字符串 {{ book_info.book_time|date:'Y-m-d' }} # 按照date指定的格式,进行显示 # 6 default : 如果值是False,就替换成设置的默认值,否则就是用本来的值 # 7 default_if_none: 如果值是None,就替换成设置的默认值,否则就使用本来的值 # 8 safe : 是否解析html标签 {{ book_info.book_name|safe }} #当变量是一个html标签的时候,为了安全隐患是不会进行解析的,如果想要让前端解析,那么需要使用safe方法说明其是安全的。 # 9 切片相关 {{ value7|filesizeformat }}<br> 文件的字节数 {{ value7|first }}<br> 第一个字符 {{ value7|length }}<br> 长度 {{ value7|slice:":-1" }}<br> 切片
自定义过滤器之filter
a、在app中创建templatetags模块(必须的) #名称必须为templatetags
b、在目录下创建任意 .py 文件,如:my_tags.py #用来存放自定义的tag
c、编写自定义过滤器
from django import template # 需要先导入文件 register = template.Library() #register的名字是固定的,不可改变,因为下面会进行调用 @register.filter def multi(num1,num2): return num1 * num2
d、在引用的html文件中进行加载
{% load my_tags %} # 在页面顶部,当和extend同时使用时,放在extend下面
e、调用
{{ hello|multi:2 }} # hello 表示传递的第一个参数,2表示传递的是第二个参数,冒号后面必须要紧跟传递的参数,多一个空格都不行
PS:filter 的函数只能接受一个额外参数(两个参数,默认会把要处理的对象当作第一个参数,第二个参数是我们需要传递的参数),可以用于if/for等语句的条件,渲染时 使用 {{ 参数1 | xxx:参数2 }} 渲染 。
自定义过滤器之simple_tag
a、在app中创建templatetags模块(必须的) #名称必须为templatetags
b、在目录下创建任意 .py 文件,如:my_tags.py #用来存放自定义的tag
c、编写自定义过滤器
from django import template # 需要先导入文件 register = template.Library() #register的名字是固定的,不可改变,因为下面会进行调用 @register.simple_tag # 必须要装饰我们自定义的函数才行 def simple_tag_multi(num1,num2): return num1 * num2
d、在引用的html文件中进行加载
{% load my_tags %} # 在页面顶部,当和extend同时使用时,放在extend下面
e、调用
{% simple_tag_multi 10 20 %} # 参数以空格隔开,多个空格效果相同
PS:simpletag 可以接受多个参数,但不可以用if/for等语句的条件,使用 {% xxx %} 渲染
逻辑控制语法
循环等逻辑语句需要使用{% %} 来进行渲染
for循环例子:
{% for i in li %} <p>{{ i }}</p> {% endfor %} #使用endfor来表示循环结束
在for循环中存在一个forloop变量,该变量记录循环相关数据
- forloop.counter 表示当前迭代数(第几次循环)从1开始
- forloop.counter0 同上,但是从0开始
- forloop.first 判断此次循环是否是第一次循环,是则返回True
- forloop.revcounter 表示当前迭代数(第几次循环)- 倒序
- forloop.last 判断此次循环是否是最后一次循环
- forloop.parentloop 用于在嵌套循环时,获取父循环的上面的信息
if循环例子:
{% if li.1 > 100 %} <p> 大于 </p> {% elif li.1 < 100 %} <p> 小于 </p> {% else %} <p> 等于 </p> {% endif %} #使用endif来表示条件判断结束
其他渲染标签
{% url 'varname' %}
引用路由配置的地址
<form action="{% url "LOGIN"%}" method="POST"> # 路由引用 <input type="text"> <input type="submit" value="提交"> </form>
{% verbatim %}
禁止render解析。
既在有的时候我们仅仅只是想打印出 {{ hello }} 而已,使用verbatim来禁止render进行解析
{% verbatim %} {{ hello }} {% endverbatim %} #使用verbatim 包起来的代码 将不会进行解析
extends模板继承
到目前为止,我们的模板范例都只是些零星的 HTML 片段,但在实际应用中,你将用 Django 模板系统来创建整个 HTML 页面。 这就带来一个常见的 Web 开发问题: 在整个网站中,如何减少共用页面区域(比如站点导航)所引起的重复和冗余代码?Django 解决此类问题的首选方法是使用一种优雅的策略—— 模板继承 。
本质上来说,模板继承就是先构造一个基础框架模板,而后在其子模板中对它所包含站点公用部分和定义块进行重载。
编写模板需要使用模板标签:
{% block name%} {% endblock %}:所有的 {% block %} 标签告诉模板引擎,子模板可以重载这些部分。 每个{% block %}标签所要做的是告诉模板引擎,该模板下的这一块内容将有可能被子模板覆盖。
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 7 <style> 8 * { 9 margin:0; 10 padding: 0; 11 } 12 .title { 13 width:100%; 14 height:100px; 15 background-color: darkblue; 16 position: absolute; 17 top:0; 18 } 19 .left { 20 width: 200px; 21 position: absolute; 22 top: 100px; 23 left:0; 24 bottom: 0; 25 background-color: green; 26 } 27 .right { 28 position: absolute; 29 top: 100px; 30 left: 200px; 31 bottom:0; 32 right:0; 33 overflow: auto; 34 {# background-color: wheat;#} 35 } 36 .left_list { 37 text-align: center; 38 padding-top: 20px; 39 } 40 </style> 41 42 43 44 </head> 45 <body> 46 47 <div class="title"></div> 48 <div class="left"> 49 <div class="left_list"> 50 <a href="/booklist/">文章列表</a> 51 </div> 52 53 54 </div> 55 <div class="right"> 56 {% block content %} # 这里定义content,表示这里可以被继承模板进行替换 57 {% endblock %} # 包起来的表示代码段 58 </div> 59 60 61 62 </body> 63 </html>
子模板进行继承,并定制自己要显示的内容(子模板不需要额外的html代码,其代码都来自于模板文件)。
{% extends 'index.html' %} # 继承模板文件 {% block content %} # 针对模板中content的代码块进行定义 <h1>hello world</h1> <h1>hello world</h1> <h1>hello world</h1> <h1>hello world</h1> <h1>hello world</h1> <h1>hello world</h1> {% endblock %}
模板使用的诀窍
<1>如果在模板中使用 {% extends %} ,必须保证其为模板中的第一个模板标记。 否则,模板继承将不起作用。 <2>一般来说,基础模板中的 {% block %} 标签越多越好。 记住,子模板不必定义父模板中所有的代码块,因此 你可以用合理的缺省值对一些代码块进行填充,然后只对子模板所需的代码块进行(重)定义。 俗话说,钩子越 多越好。 <3>如果发觉自己在多个模板之间拷贝代码,你应该考虑将该代码段放置到父模板的某个 {% block %} 中。 如果你需要访问父模板中的块的内容,使用 {{ block.super }}这个标签吧,这一个魔法变量将会表现出父模 板中的内容。 如果只想在上级代码块基础上添加内容,而不是全部重载,该变量就显得非常有用了。 <4>不允许在同一个模板中定义多个同名的 {% block %} 。 存在这样的限制是因为block 标签的工作方式是双向的。 也就是说,block 标签不仅挖了一个要填的坑,也定义了在父模板中这个坑所填充的内容。如果模板中出现了两个 相同名称的 {% block %} 标签,父模板将无从得知要使用哪个块的内容。
<5>一个html文件只能引入一个模版
include引入
在很多网站中,基本上的都会有一个开头和一个结尾,在每一个网页中都会显示。相对于这种的来说,在Django中,最好的方法就是使用include的标签,在每一个模板中都加入这个开头和结尾的标签。
简单来说,就是那些多个页面都需要的公共标签放在一个统一的html文件,供其他html文件引入。
{% include 'tpl.html' %} : 表示引入tpl.html文件。
# -------------------- tpl.html -------------------- <div> <h1>hello world</h1> </div> # -------------------- xxx.html -------------------- ... <div> {% include 'tpl.html' %} {% include 'tpl.html' %} ... </div> ...
PS:在一个页面中可以通过include引入不同的公共组件。
数据库与ORM
对象关系映射(英语:(Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换 。从效果上说,它其实是创建了一个可在编程语言里使用的--“虚拟对象数据库”。
简单一句话来说:就是把数据库的表映射为一个个对象,对对象的操作会被映射成SQL语句,在数据库执行。
数据库的配置
1 django默认支持sqlite,mysql, oracle,postgresql数据库。
<1> sqlite
django默认使用sqlite的数据库,默认自带sqlite的数据库驱动 , 引擎名称:django.db.backends.sqlite3
<2> mysql
引擎名称:django.db.backends.mysql
2 mysql驱动程序
- MySQLdb(mysql python)
- mysqlclient
- MySQL
- PyMySQL(纯python的mysql驱动程序)
3 在django的项目中会默认使用sqlite数据库,在settings里有如下设置:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } }
1 DATABASES = { 2 3 'default': { 4 5 'ENGINE': 'django.db.backends.mysql', 6 7 'NAME': 'books', #你的数据库名称 8 9 'USER': 'root', #你的数据库用户名 10 11 'PASSWORD': '', #你的数据库密码 12 13 'HOST': '', #你的数据库主机,留空默认为localhost 14 15 'PORT': '3306', #你的数据库端口 16 17 } 18 19 }
PS:
NAME即数据库的名字,在mysql连接前该数据库必须已经创建,而上面的sqlite数据库下的db.sqlite3则是项目自动创建,USER和PASSWORD分别是数据库的用户名和密码。
设置完后,再启动我们的Django项目前,我们需要激活我们的mysql。然后,启动项目,会报错:no module named MySQLdb,这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb对于py3有很大问题,所以我们需要的驱动是PyMySQL
所以,我们只需要找到项目名文件下的__init__,在里面写入:
import pymysql pymysql.install_as_MySQLdb()
问题解决!
ORM表模型
我们所说的ORM主要分为两种:
- DB First 数据库里先创建数据库表结构,根据表结构生成类,根据类操作数据库
- Code First 先写代码,执行代码创建数据库表结构
主流的orm都是code first。django 的orm也是code first,所以本质上分为两部分:
- 根据类自动创建数据库表
- 根据类对数据库表中的数据进行各种操作
创建表对象
现在有一张表,主要字段如下:
对应的python语句为:
from django.db import models # 导入models,django提供的对象包含很多建表的方法 # Create your models here. class UserInfo(models.Model): # 需要继承models.Model id = models.AutoField(primary_key=True,null=False,verbose_name='ID') name = models.CharField(max_length=4,null=False,verbose_name='用户名') password = models.CharField(max_length=64,null=False,verbose_name='密码') email = models.EmailField(null=False,verbose_name='邮箱') # AutoField : 自增字段,类似于mysql的int字段加auto_increment属性 # CharField:可变长字段,类似于mysql的varchar类型,需要指定长度 # EmailField:邮件字段,仅仅提供给 django admin进行约束使用,映射到MySQL上,根本上也是字符串类型 # null:是否为空,通用参数,默认为否。 # verbose_name:django admin上对表操作时,显示的字段名称 # primary_key:主键 # max_length:针对于CharField字段,标示其长度
更多的Django字段类型
AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bigint自增列,必须填入参数 primary_key=True 注:当model中如果没有自增列,则自动会创建一个列名为id的列 from django.db import models class UserInfo(models.Model): # 自动创建一个列名为id的且为自增的整数列 username = models.CharField(max_length=32) class Group(models.Model): # 自定义自增列 nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) SmallIntegerField(IntegerField): - 小整数 -32768 ~ 32767 PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正小整数 0 ~ 32767 IntegerField(Field) - 整数列(有符号的) -2147483648 ~ 2147483647 PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正整数 0 ~ 2147483647 BigIntegerField(IntegerField): - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 自定义无符号整数字段 class UnsignedIntegerField(models.IntegerField): def db_type(self, connection): return 'integer UNSIGNED' PS: 返回值为字段在数据库中的属性,Django字段默认的值为: 'AutoField': 'integer AUTO_INCREMENT', 'BigAutoField': 'bigint AUTO_INCREMENT', 'BinaryField': 'longblob', 'BooleanField': 'bool', 'CharField': 'varchar(%(max_length)s)', 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', 'DateField': 'date', 'DateTimeField': 'datetime', 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', 'DurationField': 'bigint', 'FileField': 'varchar(%(max_length)s)', 'FilePathField': 'varchar(%(max_length)s)', 'FloatField': 'double precision', 'IntegerField': 'integer', 'BigIntegerField': 'bigint', 'IPAddressField': 'char(15)', 'GenericIPAddressField': 'char(39)', 'NullBooleanField': 'bool', 'OneToOneField': 'integer', 'PositiveIntegerField': 'integer UNSIGNED', 'PositiveSmallIntegerField': 'smallint UNSIGNED', 'SlugField': 'varchar(%(max_length)s)', 'SmallIntegerField': 'smallint', 'TextField': 'longtext', 'TimeField': 'time', 'UUIDField': 'char(32)', BooleanField(Field) - 布尔值类型 NullBooleanField(Field): - 可以为空的布尔值 CharField(Field) - 字符类型 - 必须提供max_length参数, max_length表示字符长度 TextField(Field) - 文本类型 EmailField(CharField): - 字符串类型,Django Admin以及ModelForm中提供验证机制 IPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制 GenericIPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6 - 参数: protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6" unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both" URLField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证 URL SlugField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) CommaSeparatedIntegerField(CharField) - 字符串类型,格式必须为逗号分割的数字 UUIDField(Field) - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证 FilePathField(Field) - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能 - 参数: path, 文件夹路径 match=None, 正则匹配 recursive=False, 递归下面的文件夹 allow_files=True, 允许文件 allow_folders=False, 允许文件夹 FileField(Field) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage ImageField(FileField) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage width_field=None, 上传图片的高度保存的数据库字段名(字符串) height_field=None 上传图片的宽度保存的数据库字段名(字符串) DateTimeField(DateField) - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] DateField(DateTimeCheckMixin, Field) - 日期格式 YYYY-MM-DD TimeField(DateTimeCheckMixin, Field) - 时间格式 HH:MM[:ss[.uuuuuu]] DurationField(Field) - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型 FloatField(Field) - 浮点型 DecimalField(Field) - 10进制小数 - 参数: max_digits,小数总长度 decimal_places,小数位长度 BinaryField(Field) - 二进制类型
django中提供了很多的字段类型,大部分都是提供给django admin 来做验证的,实际体现在数据库中的,大部分都是字符串类型。
常用的字段参数
django中提供了很多参数对字段进行控制
null 是否可以为空 default 默认值 primary_key 主键 db_column 列名 db_index 索引(bool) unique 唯一索引 --------------时间日期类---------------- unique_for_date 对日期字段来说,表示只对时间做索引 unique_for_month 对日期字段来说,表示只对月份做索引 unique_for_year 对日期字段来说,表示只对年做索引 auto_now 无论是你添加还是修改对象,时间为你添加或者修改的时间。 auto_now_add 为添加时的时间,更新对象时不会有变动。 ---------------django admin ------------- choices 在django admin中显示下拉框,避免连表查询(比如用户类型,可以在存在内存中) blank 在django admin中是否可以为空 verbose_name 字段在django admin中显示的名称 editable 在django admin中是否可以进行编辑,默认是true error_message 当在django admin中输入的信息不匹配时,字段的提示信息 help_text 在django admin中输入框旁边进行提示 validators 在django admin中自定义规则限制
生成表
前面我们已经编写了对应数据库表的类,这里我们将进行实例化(创建数据库对应的表)。
利用django提供的命令进行数据库的初始化工作.(以及其他对数据库表进行修改的动作,比如修改表结构,字段属性等,都需要执行如下步骤)
# 进入项目目录下执行 python manage.py makemigrations # 大致含义是:把类转换成对应格式的sql语句。 # 创建表 python manage.py migrate # 在数据库中执行生成的sql语句
注意:
- 执行makemigrations后生成的文件,存放在应用目录下的migrations目录下,一般以0001_initial.py为文件名并且依次递增。
- 0001_initial.py存放的是 django 根据我们写的 class 生成的相关代码,执行migrate后,会根据这些代码生成对应的数据库表。
- 如果我们执行migrate后没有生成我们创建的表格,那么需要确认在django配置文件中,是否加载了我们的应用(因为django会加载setting里面的install_app中查找对应的modules.py)
- 表名默认是:应用名_class类名
1 INSTALLED_APPS = [ 2 'django.contrib.admin', 3 'django.contrib.auth', 4 'django.contrib.contenttypes', 5 'django.contrib.sessions', 6 'django.contrib.messages', 7 'django.contrib.staticfiles', 8 'app01.apps.App01Config', 9 '你的app名称' 10 ]
PS:当我们对已生成的表添加新字段时,会出现如下情况
ou are trying to add a non-nullable field 'code' to business without a default; we can't do that (the database needs something to populate existing rows). Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Quit, and let me add a default in models.py Select an option:
由于我们添加字段会影响之前的数据,所以这时django提醒我们,不能只是单纯的添加一个字段,还需要为该字段指定对应的值,当然有几种解决办法:
- 设置新增字段default属性,表示其默认值
- 设置新增字段null属性,表示其默认可以为空
- 根据上面的提示,选择1,手动输入默认值即可(注意默认值需要用引号引起来,不然django认为输入的数据是个变量)
利用orm完成数据库的增删改查
前面我们利用django 提供的orm创建了类对应数据库的表,接下来就需要利用orm来对数据库的表数据进行增删改查等基本操作了。
orm之增加
根据django的MTV架构,业务的处理是在views中完成的,那么对数据库的查询逻辑也应该在views中编写,而我们定义的数据库对象在model.py中,那么在导入之后,才能进行表操作。
django提供了两种增加数据的方式:1、create,2、save
#------------------------------------- create ------------------------------------- from app01 import models # 从自己的应用中导入models模块 username = 'daxin' password = 'abc.123' email = 'daixin@qq.com' models.UserInfo.objects.create(name=username,password=password,email=email) # 表对象.objects.create() 用来新增数据 # name,password,email 表示表的字段 # 等号右边的表示数据 # 传递的key也可以是字典利用**dic进行传递,在后面利用form的时候会经常使用这种方式。 dict = {'usernane':'daxin','password':'abc.123',email='daxin@qq.com'} models.UserInfo.objects.create(**dict) #------------------------------------- save ------------------------------------- from app01 import models # 从自己的应用中导入models模块 username = 'daxin' password = 'abc.123' email = 'daixin@qq.com' userobj = models.UserInfo(name=username,password=password,email=email) userobj.save() # save的方式是利用对象实例化的传递参数后,调用save方法进行保存的 # 利用对象,我们可以方便的进行数据修改后,再进行save # 比如 userobj.name = 'dahuaidan' # userobj.save()
注意:利用create新增数据时,会默认返回新增加的数据对象,我们可以接受该对象来进行其他的操作,比如添加多对多关系等
PS:比较常用的方式是 利用create进行数据的增加
orm之删除
想要对数据进行删除,首先需要匹配到数据,然后执行删除操作,那么就涉及两部分:查找,删除
models.UserInfo.objects.filter(id=1).delete() # 利用filter对数据唯一的字段进行过滤,匹配到删除的行,也可以用all()匹配所有数据,进行删除 # 利用delete方法进行删除操作
orm之查找
查找数据时非常灵活,我们可以针对不同的需求进行搭配使用。
# 获取表里所有的对象 models.UserInfo.objects.all() # 结果为一个QuerySet对象(django提供的),可以理解为一个列表 # 获取id为1的对象 models.UserInfo.objects.filter(id=1) # 类似于sql中的 where查询条件,结果也是一个QuerySet # 获取name字段包含da的记录 models.UserInfo.objects.filter(name__contains='da') # 这里涉及了万能的双下划线,在后续会进行说明 # get获取一条数据 models.UserInfo.objects.get(id=1) # 注意get不到数据,会直接抛出异常 # 除了id等于1的其他数据 models.UserInfo.objects.exclude(id=1)
注意:
- 查找到的不是想sql语句那样直接列出一行的各个字段及对应的值,而已把改行表示为一个行对象。匹配到一行,就表示1个表中的行对象。
- 每个对象内才包含了对应的字段和值。
- 通过 行对象.字段名,来获取对应的数据。
- filter中包含多个条件,那么它们是and的关系。
PS:由于filter、all取到的数据默认是 QuerySet格式,在某些场景下我们只需要验证是否取到,我们可以直接获取结果中的第一个数据即可,即在最后添加.first()即可,表示取第一个数据,或者使用.count()来统计匹配到的数据的个数。
module.UserInfo.filter(username='daxin').first()
1 fieldname__contains='' # 表示该fieldname包涵的关键字 2 fieldname__lt = '' # 表示filedname小于 3 fieldname__gte = '' # 表示fieldanem 大于等于
PS:由于all取到的是所有数据,返回的是一个对象,很多时候用起来并不是那么方便,在SQL中还有select id,name from table 这种只取固定某些列的语句,当然在djang中也是存在的,利用values(还有values_list,只是内部由字典变成了元组)
models.Userinfo.object.all().values('id','username') # 表示只取指定的id和username列 # 结果依旧返回的是QuerySet,但是数据不同,体现的形式是一个字典 QuerySet({'id':1,'username':'daxin'},{'id':2,'username':'dachenzi'}......) # 使用value_list('id','username'),体现的形式是一个元组 QuerySet((1,'daxin'),(2,'dachenzi')......)
orm之修改
整体思路和删除是相同的,首先查找到数据,然后对字段进行修改
models.UserInfo.objects.filter(id=1).update(name='dachenzi') # 利用filter过滤到要修改的数据,然后更新它的name字段的值
其他
有的时候我们想要查看我们写的代码对应的sql语句到底是什么,那么可以使用query来进行输出
print(modules.Book.objects.all().query) # SELECT "app01_book"."nid", "app01_book"."title", "app01_book"."price", "app01_book"."pub_id" FROM "app01_book"
表与表之间的关系
我们知道,在数据库中表与表的关系主要有三种,一对一,一对多,多对多,在 django中提供了专门的类型来做表之间的关联
一对多
表示当前表的某个字段的值,来自于其他表,比如人员表和部门表,在人员表中利用一对多外键关系标明该员工属于哪个部门。
user_type = models.ForeignKey('表名',to_field='字段') # 默认会自动关联对方表的ID字段(主键),手动指定的话必须是唯一列才行。
注意:
- 默认情况下,外键字段在数据库中存储的名称为:字段名_id (上面的例子的话,在数据库中的字段名:user_type_id)
- 外键字段(user_type_id)存放的是所关连表的id信息
- 同时还存在一个字段名(上面的例子就是user_type)对象,这个对象指代的就是所关联的表中的数据对象
- 我们可以通过user_type,来跨表获取其关联的信息的某写字段的值(通过.字段名,即可访问)
正向查询
什么叫正向查询?还是拿人员表和部门表举例,外键关系存在人员表中,那么我们通过人员表利用表中的外键字段就可以查询到该人员的部门信息,我一般称之为正向查询。
1 # -------------------- models.py -------------------- 2 3 from django.db import models 4 5 # Create your models here. 6 7 8 class Business(models.Model): 9 10 caption = models.CharField(max_length=32) 11 code = models.CharField(max_length=16,default='SA') 12 13 14 class Host(models.Model): 15 16 nid = models.AutoField(primary_key=True) 17 hostname = models.CharField(max_length=16) 18 ip = models.GenericIPAddressField(protocol='ipv4',db_index=True) 19 port = models.IntegerField() 20 b = models.ForeignKey(to='Business',to_field='id') # 外键关联Business表 21 22 23 # -------------------- views.py -------------------- 24 25 def host(request): 26 27 v1 = models.Host.objects.all() 28 29 return render(request,'host.html',{'v1':v1}) 30 31 32 33 # -------------------- host.html -------------------- 34 35 <!DOCTYPE html> 36 <html lang="en"> 37 <head> 38 <meta charset="UTF-8"> 39 <title>Title</title> 40 </head> 41 <body> 42 <div> 43 <p>主机信息</p> 44 {% for row in v1 %} 45 <p>{{ row.nid }} - {{ row.hostname }} - {{ row.ip }} - {{ row.port }} - {{ row.b_id }} - {{ row.b.caption }} - {{ row.b.code }}</p> # 通过b对象,进行跨表查询主机业务线相关信息 46 {% endfor %} 47 </div> 48 </body> 49 </html> 50 51 # 这里通过row.b_id 和通过b对象来获取 row.b.id 结果是相同的,区别在于使用b对象,会多一次sql查询
PS:当多个表进行及联跨表,那么都可以通过表的外键字段使用点来进行跨表访问(或者使用双下划线)
双下划线和点跨表的不同之处在于:
- 双下划线一般用于条件查询(values,values_list)
- 点则一般用于在结果中利用外键对象进行跨表查询
# -------------- 利用点进行跨表查询 -------------- # 如果我只想获取主机地址和所属业务线使用点跨表的话 v2 = models.Host.objects.all() return render(request,'host.html',{'v2':v2}) # 前端代码 {% for row in v2 %} <p>{{ row.hostname }} - {{ row.b.caption }}</p> {% endfor %} # 我们只用了两个字段,却取出了相关的所有数据。 # 是否可以在查询阶段只取出所需字段即可?使用双下划线__即可 # -------------- 利用双下划线跨表查询 -------------- v3 = models.Host.object.all().values('hostname','b__caption') # 我们可以看到在作为value的条件语句,通过b对象配合双下划线就进行了一次跨表查询 # 前端代码 <div> {% for row in v2 %} <p>{{ row.hostname }} - {{ row.b__caption }}</p> # 只需要向字典一样即可。 {% endfor %} </div> # 在django内部,它之所以能识别__,是因为,它在处理values条件时,会使用__作为分隔符,分隔后再进行相关处理
反向查询
很多时候我们会有另一种需求,查询一个部门下的所有员工,这时部门表中没有外间字段关联人员表啊,该怎么查?其实是可以的,我把这个查询方式称之为反向查询。
在建立外键关系时,不止会在外键所在表中产生外键所关联表的对象,在所关联表中也会产生一个那个关联我的表的对象(可能有点绕,没找到更好的表达方式),这个对象一般有两种体现方式:
- 关联我的表(小写):一般用在values,values_list当作条件做字段过滤
- 关联我的表名(小写)_set:一般当作对象,当作字段处理
# -------------------------- models.py ------------------------------------ from django.db import models class UserInfo(models.Model): id = models.AutoField(primary_key=True) username = models.CharField(max_length=32) password = models.CharField(max_length=64) dp = models.ForeignKey('Dept',on_delete=False) # 外键关联 class Dept(models.Model): id = models.AutoField(primary_key=True) title = models.CharField(max_length=12) # -------------------------- views.py ------------------------------------ from django.shortcuts import render,HttpResponse,redirect from app01 import models def test(request): v = models.Dept.objects.all() # 获取部门表中所有数据 for item in v: print(item.id,item.title,item.userinfo_set.all()) print(item.id,item.title,item.userinfo_set.values('username','password')) # 字典 print(item.id,item.title,item.userinfo_set.values_list('username','password')) # 元组 return HttpResponse('ok') # 可以看到部门表中存在 userinfo_set 用于跨表到 UserInfo 表中,并且支持all,values,values_list过滤
上面这种方式是通过对象的方式来跨表进行数据查询,在正向查询中,我们可以利用values配合双下划线直接进行跨表查询,在反向查询中当然也是可以的。
def test(request): v = models.Dept.objects.values('id','title','userinfo__username') # 在values,values_list等作为条件时,就不带_set了,直接使用表名就可以进行跨表 for item in v: print(item) return HttpResponse('ok')
注意:这种方式会列出部门内的所有人员(比如IT部门4个人员,那么会列出4条信息。
多对多
前面说了一对多的情况,这里还有一种情况叫多对对,比如一个主机可以关联多个业务线,不同的业务线可以关联多个主机,所以这里,业务线和主机的关系为多对多,在多对多的情况下,有需要一张额外的表来表示对应关系,这里有两种情况来创建这张关系表。
方式1 - 手动创建
手动创建,故名思议,我们需要手动的创建一张关系表,然后创建两个ForeignKey字段(一对多),关联两张表即可。
# 业务线表 class Business(models.Model): caption = models.CharField(max_length=32) code = models.CharField(max_length=16,default='SA') # 主机表 class Host(models.Model): nid = models.AutoField(primary_key=True) hostname = models.CharField(max_length=16) ip = models.GenericIPAddressField(protocol='ipv4',db_index=True) port = models.IntegerField() # 多对多关系表 class Application(models.Model): h = models.ForeignKey(to='Host',to_field='nid') # 关联主机id,字段名为h_id,同时存在对象h,存放对应的Host信息 b = models.ForeignKey(to='Business',to_field='id') # 关联业务线id,字段名为b_id,同时存在对象b,存放对应的Business信息
PS:一种手动创建三张表,可以利用创建的Application关系表来直接操作多对多关系。
方式2 - 自动创建
在Django中,还存在一种方式为自动创建, 通过django提供的ManyToMany关键字创建。
# 业务线表 class Business(models.Model): caption = models.CharField(max_length=32) code = models.CharField(max_length=16,default='SA') # 主机表 class Host(models.Model): nid = models.AutoField(primary_key=True) hostname = models.CharField(max_length=16) ip = models.GenericIPAddressField(protocol='ipv4',db_index=True) port = models.IntegerField() business = models.ManyToManyField('Business') # 通过manytomany字段创建多对多关系
注意:
- 虽然我们创建了两张表,但是由于我们使用了manytomany关键字创建多对对关系,django还会为我们创建一张表名为当前表名加当前字段名的表(上面的例子就是:app01_host_business),当作关系表,不会产生business字段。
- 由于我们没有关系表这个class类对象,所以我们不能直接操作关系表,需要通过Host对象间接通过business字段来操作关系表。
- 虽然利用manytomany关键字,能帮我们创建关系表,节省了很多代码,但是自动生成的表中只会生成两个字段(两张表的主键id字段),不能定制其他字段,相反手动生成的表可以定制,平时可以根据情况二者混用。
操作关系表
手动创建关系表的情况下,由于含有第三张表对应的class,那么我们可以直接使用这个class对关系表进行操作,但是多对多的情况下没有关系表的class,所以我们需要通过其他办法来操作。
# 通过manytomany对象操作关系表 add() # 添加关系 remove() # 删除关系 clear() # 清除所有关系 all() # 获取对应的所有关系对象(在查询时这里可以使用all,filter,get等,就像查找过滤其他数据一样) # --------------- 例子 --------------- # models.Host.objects.filter(nid=1).first().business.add(1,2) # 添加两条多对多关系 1(host) --> 1(business),1(host) --> 2(business) models.Host.objects.filter(nid=1).first().business.remove(2) # 删除一条多对多关系 1 -x-> 2 models.Host.objects.filter(nid=1).first().business.clear() # 清除nid为1,所有的多对多关系