一、 Django开篇
MVT与MVC
Django 采用了 MVT 的软件设计模式,即模型(Model),视图(View)和模板(Template)。
这个MVT模式并非django首创,在其他的语言里面也有类似的设计模式MVC,甚至可以说django里面的MVT事实上是借鉴了MVC模式衍生出来的。
M,Model,模型,是用于完成操作数据库的。
V,View,视图,里面的代码就是用于展示给客户端的页面效果。
C,Controller,控制器,是一个类或者函数,里面的代码就是用于项目功能逻辑的,一般用于调用模型来获取数据,获取到的数据通过调用视图文件返回给客户端。
而MVT指的是:
M全拼为Model,与MVC中的M功能相同,负责和数据库交互,进行数据处理。
V全拼为View,与MVC中的C功能相同,接收请求,进行业务处理,返回应答。
T全拼为Template,与MVC中的V功能相同,负责封装构造要返回的html。
MVT模型的工作流程
(其实在url路由分发前会先经过wsgi,里面有封装好了的基于socket实现的代码用于处理数据,之后会依次经过中间件的process_request 到view,view返回相应时会倒着依次经过中间件的process_response,再经过wsgi发送到浏览器,这里图片只画一个大概,详细的后面会记)
终端操作
什么由来,Django介绍历史,下载等的就略了。
二、路由控制器
Route路由, 是一种映射关系!路由是把客户端请求的url路径和用户请求的应用程序[这里意指django里面的视图进行绑定映射的一种关系。
请求路径和视图函数不是一对一映射关系!
在django中所有的路由最终都被保存到一个变量 urlpatterns., urlpatterns必须声明在主应用下的urls.py总路由中。这是由配置文件settings设置的。
在django运行中,当客户端发送了一个http请求到服务端,服务端的web服务器则会从http协议中提取url地址, 从程序内部找到项目中添加到urlpatterns里面的所有路由信息的url进行遍历匹配。如果相等或者匹配成功,则调用当前url对象的视图方法。
无论是哪种方式通过路由去映射函数,它的内部都会进行url的字符串分割,进行逐一地查找匹配。
在给urlpatterns路由列表添加路由的过程中,django一共提供了2个函数给开发者注册路由.
from django.urls import path # 字符串路由 from django.urls import re_path # 正则路由,会把url地址看成一个正则模式与客户端的请求url地址进行正则匹配
1、 基本使用
path(r'^articles/2003/$', views.special_case_2003), re_path(r'^articles/([0-9]{4})/$', views.year_archive), re_path(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive2)
对于从路径中获取参数,view视图函数中除了request要记得加上相应的位置/关键字参数用来接收。
一些参数可以从路径中获取,一些参数可以在get post等请求中get出来。
2、 路由分发
1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
3、 路由转发器
有时候上面的内置的url转换器并不能满足我们的需求,因此django给我们提供了一个接口可以让我们自己定义自己的url转换器。
from django.urls import register_converter from django.shortcuts import HttpResponse # 自定义路由转换器 class MobileConverter(object): regex = "1[3-9]\d{9}" def to_python(self,value): print(type(value)) # 将匹配结果传递到视图内部时使用 # 返回str还是int主要看需求,纯数字的可以返回int return value def to_url(self,value): # 将匹配结果用于反向解析传值时使用 return value # register_converter(路由转换器的类名,调用别名) register_converter(MobileConverter,"mobile")
path("index/<mobile:mobile>",index)
def index(request,mobile): print(":::",type(mobile)) return HttpResponse(f"hi,{mobile}用户")
4、 url的命名与namespace
name
假如我们url里的一条路径为publisher_list/,后续我们想将publisher_list改为publisher,因为假如我们在视图函数的重定向、前端模板的一些路径如a标签、form表单里的active等无论是以相对路径还是绝对路径的方式写出的url都是写死的状态,都需要再重新修改,而假如我们给路径起了个名字,每次通过名字去寻找路径,这样即使路径改变,如publisher_list改为publisher,其名字不变,各种跳转依旧可以执行(因为它是每次通过名字去寻找路径的)。
(当然,该图片只是一个例子,像这种增删改查路径一样可以优化)
(虽然前后端分离项目中我们大多数用ajax发送请求,但路径的命名依然重要)
namespace
上面介绍了命名,我们可以看到上面的图片中的路由,其实是从项目文件中的urls.py里include路由分发出的多个app中的urls.py一个分路由。假如我们多个人开发一个项目,在这个项目中我写app01,你写app02,难免出现些起名字重名的意外,这种情况下再进行反向解析,只会根据settings.py里的app注册的顺序去开始寻找,找到第一个就返回了。
因此,我们给每个app在进行路由分发时起一个‘大名字’,namespace,这样以后进行反向解析时,会先去找大名字再去找小名字。(小知识:manage.py里可以修改配置文件)
(这里我只注册了一个app,下面图片会有飘黄,不用管,当作我创建了)
这样,假如students和children里都有name='xx'的url时,可以根据需要,进行'namespace:name'
5 反向解析
在使用Django 项目时,一个常见的需求是获得URL 的最终形式,以用于嵌入到生成的内容中(视图中和显示给用户的URL等)或者用于处理服务器端的导航(重定向等)。人们强烈希望不要硬编码这些URL(费力、不可扩展且容易产生错误)或者设计一种与URLconf 毫不相关的专门的URL 生成机制,因为这样容易导致一定程度上产生过期的URL。
在需要URL 的地方,对于不同层级,Django 提供不同的工具用于URL 反查:
在模板中:使用url模板标签
在Python 代码中:使用from django.urls import reverse 函数。urls.py中为url设置别名参数:
from django.conf.urls import url from . import views urlpatterns = [ #... url(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'), #... ]
应用之在模板中反向解析:
<a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a> {#相当于#} <a href="/articles/2012/">2012 Archive</a> <a href="{% url 'news-year-archive' year=2012 %}">2012 Archive</a> {#也可以这种方式传参#} <a href="{% url 'news-year-archive'%}?year=2012">2012 Archive</a>
对于第四种因为a标签发的是get请求,其请求里的url可以通过request.GET.get('year')的形式获取出来,对于第一种,是/articles/2012/这样的,urls.py中记得要加一些如正则表达式的接收。
应用之在py文本中反向解析:
from django.shortcuts import redirect from django.urls import reverse def redirect_to_year(request): year = 2006 reverse_path=reverse('news-year-archive', args=(year,)) return redirect(reverse_path) # 等效 redirect("/articles/2006/")
args/kwargs传参,也相当于上面的路径拼接。
三、视图
django的视图主要有2种,分别是函数视图和类视图.现在刚开始学习django,我们先学习函数视图(FBV),后面再学习类视图(CBV).
1、请求对象
django将请求报文中的请求行、首部信息、内容主体封装成 HttpRequest 类中的属性。 除了特殊说明的之外,其他均为只读的。
(1)请求方式
print(request.method)
(2)请求数据
# 1.HttpRequest.GET:一个类似于字典的对象,包含 HTTP GET 的所有参数。详情请参考 QueryDict 对象。
# 2.HttpRequest.POST:一个类似于字典的对象,如果请求中包含表单数据,则将这些数据封装成 QueryDict 对象。
# 注意:键值对的值是多个的时候,比如checkbox类型的input标签,select标签,需要用: request.POST.getlist("hobby")
# 3.HttpRequest.body:一个字符串,代表请求报文的请求体的原数据。
(3)请求路径
# HttpRequest.path:表示请求的路径组件(不含get参数) # HttpRequest.get_full_path():含参数路径
(4)请求头
# HttpRequest.META:一个标准的Python 字典,包含所有的HTTP 首部。具体的头部信息取决于客户端和服务器
简单访问下,在地址栏输入127.0.0.1:8000/students/index (get请求)
request.path_info 不包含域名端口 不包含路径参数 request.get_full_path 会携带参数
当我们发送post请求时,如果form表单发送的默认数据的编码方式是URLencode,那么才可以被request.POST得到,否则POST理由但请求体里会有(byte类型) 。
2、响应对象
响应对象主要有三种形式:
- HttpResponse()
- render()
- redirect()
(1)HttpResponse()
Django服务器接收到客户端发送过来的请求后,会将提交上来的这些数据封装成一个 HttpRequest 对象传给视图函数。那么视图函数在处理完相关的逻辑后,也需要返回一个响应给浏览器。而这个响应,我们必须返回 HttpResponseBase 或者他的子类的对象。而 HttpResponse 则是 HttpResponseBase 用得最多的子类。
常用属性:
content:返回的内容。实例化HttpResponse('content')
status:返回的HTTP响应状态码。200 404
content_type:返回的数据的MIME类型,默认为 text/html 。浏览器会根据这个属性,来显示数据。如果是 text/html ,那么就会解析这个字符串,如果 text/plain ,那么就会显示一个纯文本。
设置响应头: response['X-Access-Token'] = 'xxxx' 。
def index(request): res = HttpResponse('<h1>ok</h1>',content_type='text/plain') res['add_head'] = 'xxxx' return res
留一张图当作铺垫
JsonResponse类:
用来对象 dump 成 json 字符串,然后返回将 json 字符串封装成 Response 对象返回给浏览器。并且他的 Content-Type 是 application/json 。示例代码如下:
from django.http import JsonResponse def index(request): return JsonResponse({"title":"三国演义","price":199})
默认情况下 JsonResponse 只能对字典进行 dump ,如果想要对非字典的数据进行 dump ,那么需要给 JsonResponse 传递一个 safe=False 参数。示例代码如下:
一般用来响应ajax请求,相比于HttpResponse(json.dumps(dct,ensure_ascii=Fasle)),这个会自动序列化并且设置content_type=application/json,ajax会自动反序列化为JS的object类型。
(2)render()
def index(request): return render(request,'index.html',{'xx':xx})#locals()
#结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的 HttpResponse 对象。
参数:
/* request: 用于生成响应的请求对象。 template_name:要使用的模板的完整名称,可选的参数 context:添加到模板上下文的一个字典, 默认是一个空字典。如果字典中的某个值是可调用的,视图将在渲染模板之前调用它。 */
render方法就是将一个模板页面中的模板语法进行渲染,最终渲染成一个html页面作为响应体。
注意templates里的html路径是在settings.py里设置的,要改templates名或者选择html时要注意补全。
render函数内部本质
from django.shortcuts import render from django.template.loader import get_template from django.http.response import HttpResponse def index(request): name = "hello world!" # 1. 初始化模板,读取模板内容,实例化模板对象 # get_template会从项目配置中找到模板目录,我们需要填写的参数就是补全模板文件的路径 template = get_template("index.html") # 2. 识别context内容, 和模板内容里面的标记[标签]替换,针对复杂的内容,进行正则的替换 context = {"name": name} content = template.render(context, request) # render中完成了变量替换成变量值的过程,这个过程使用了正则。 print(content) # 3. 通过response响应对象,把替换了数据的模板内容返回给客户端 return HttpResponse(content) # 上面代码的简写,直接使用 django.shortcuts.render # return render(request, "index.html",context={"name":name}) # return render(request,"index3.html", locals()) # data = {} # data["name"] = "xiaoming" # data["message"] = "你好!" # return render(request,"index3.html", data)
(3)redirect方法
当您使用Django框架构建Python Web应用程序时,您在某些时候必须将用户从一个URL重定向到另一个URL。
通过redirect方法实现重定向。
参数可以是:
一个绝对的或相对的URL, 将原封不动的作为重定向的位置.
一个url的别名: 可以使用reverse来反向解析url
#传递要重定向到的一个具体的网址 def my_view(request): ... return redirect("/some/url/") #当然也可以是一个完整的网址 def my_view(request): ... return redirect("http://www.baidu.com") #传递一个视图的名称 def my_view(request): ... return redirect(reverse("namespace:name"))
浏览器发起请求->服务器响应重定向一个新地址->浏览器发送响应到新地址->服务器返回响应。
响应头里有Location:'地址'
再往上去他俩父亲继承 HttpResponse
用HttpResponse简单实现
def red(request): ret = HttpResponse('', status=301) ret['Location'] = '/students/index/' return ret
初学者除了要分清redirect的render的区别原理,还要明白什么时候用redirect什么时候用render,这里不做赘述。
小知识1:
APPEND_SLASH的实现就是基于redirect
这里index后加了/,如果我访问127.0.0.1/students/index依然可以成功,原因是Django中的APPEND_SLASH帮助我们拼接了一个/,原理是其发生了一次重定向(第一次请求失败,重定向第二次加了/的地址),不需要的话就在settings.py里加一句APPEND_SLASH=False即可。
小知识2:
假如我urls.py没做任何处理:
此时我访问
结果:
也发生了重定向
直接访问 http://127.0.0.1:8000/students/oo/?xx 结果与上图一样。
但去掉问号则为报错,找不到该路径。
四、模板
模板引擎是一种可以让开发者把服务端数据填充到html网页中完成渲染效果的技术。它实现了把前端代码和服务端代码分离的作用,让项目中的业务逻辑代码和数据表现代码分离,让前端开发者和服务端开发者可以更好的完成协同开发。
静态网页:页面上的数据都是写死的,万年不变
动态网页:页面上的数据是从后端动态获取的(比如后端获取当前时间;后端获取数据库数据然后传递给前端页面)
Django框架中内置了web开发领域非常出名的一个DjangoTemplate模板引擎(DTL)。
要在django框架中使用模板引擎把视图中的数据更好的展示给客户端,需要完成3个步骤:
在项目配置文件中指定保存模板文件的模板目录。一般模板目录都是设置在项目根目录或者主应用目录下。
在视图中基于django提供的渲染函数绑定模板文件和需要展示的数据变量
在模板目录下创建对应的模板文件,并根据模板引擎内置的模板语法,填写输出视图传递过来的数据。(详情见上面render函数原理)
配置模板目录:在当前项目根目录下创建了模板目录templates. 然后在settings.py, 模板相关配置,找到TEMPLATES配置项,填写DIRS设置模板目录。
DTL模板文件与普通html文件的区别在哪里?
DTL模板文件是一种带有特殊语法的HTML文件,这个HTML文件可以被Django编译,可以传递参数进去,实现数据动态化。在编译完成后,生成一个普通的HTML文件,然后发送给客户端。
开发中,我们一般把开发中的文件分2种,分别是静态文件和动态文件。
* 静态文件,数据保存在当前文件,不需要经过任何处理就可以展示出去。普通html文件,图片,视频,音频等这一类文件叫静态文件。 * 动态文件,数据并不在当前文件,而是要经过服务端或其他程序进行编译转换才可以展示出去。 编译转换的过程往往就是使用正则或其他技术把文件内部具有特殊格式的变量转换成真实数据。 动态文件,一般数据会保存在第三方存储设备,如数据库中。django的模板文件,就属于动态文件。
1、模板语法
1.1、查询、深度查询
直接通过 . 就行 下面是列表索引的方式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <p>复杂列表</p> <p>{{ book_list.0.name }}</p> <p>{{ book_list.1.name }}</p> </body> </html>
1.2、过滤器
语法:
{{obj|过滤器名称:过滤器参数}}
内置过滤器:
def index(request): """过滤器 filters""" from datetime import datetime now = datetime.now() content2= "hello wrold!" return render(request,"index.html",locals())
# 模板代码,templates/index.html: {{ content | safe }} {{ content1 | safe }} {# 过滤器本质就是函数,但是模板语法不支持小括号调用,所以需要使用:号分割参数 #} <p>{{ now | date:"Y-m-d H:i:s" }}</p> <p>{{ conten1 | default:"默认值" }}</p> {# 一个数据可以连续调用多个过滤器 #} <p>{{ content2 | truncatechars:6 | upper }}</p>
自定义过滤器
虽然官方已经提供了许多内置的过滤器给开发者,但是很明显,还是会有存在不足的时候。例如:希望输出用户的手机号码时, 13912345678 ---->> 139*****678,这时我们就需要自定义过滤器。要声明自定义过滤器并且能在模板中正常使用,需要完成2个前置的工作:
1. 当前使用和声明过滤器的子应用必须在setting.py配置文件中的INSTALLED_APPS中注册了!!!
2. 自定义过滤器函数必须被 template.register进行装饰使用.而且过滤器函数所在的模块必须在templatetags包里面保存
在home子应用下创建templatetags包[必须包含__init__.py], 在包目录下创建任意py文件
# home.templatetags.my_filters.py代码: from django import template register = template.Library() # 自定义过滤器 @register.filter("mobile") def mobile(content): return content[:3]+"*****"+content[-3:]
3. 在需要使用的模板文件中顶部使用load标签加载过滤器文件my_filters.py并调用自定义过滤器
# home.views.py,代码: def index(request): """自定义过滤器 filters""" moblie_number = "13312345678" return render(request,"index2.html",locals())
# templates/index2.html,代码: {% load my_filters %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {{ moblie_number| mobile }} </body> </html>
2、标签
{{% %}} if或者for完后记得end,符号之间记得加空格,不然会报错。
循环中, 模板引擎提供的forloop对象,用于给开发者获取循环次数或者判断循环过程的.
2、模板嵌套继承
{% include "模板文件名"%} # 模板嵌入 {% extends "base.html" %} # 模板继承 {% block xx %} # 重写block块 {% end block%}
3、静态文件
开发中在开启了debug模式时,django可以通过配置,允许用户通过对应的url地址访问django的静态文件。
setting.py,代码:
#STATIC_ROOT = BASE_DIR / 'static' STATIC_URL = '/static/' # django模板中,可以引用{{STATIC_URL}}变量避免把路径写死。 STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), os.path.join(BASE_DIR, 'static2'), os.path.join(BASE_DIR, 'static3'), ]
从上往下依次查找
像前端后面写的这些路径
<script src="/static/Jquery3.6.js"></script>
这里的/static/虽然写的时候就会提示static文件名,但实际上它的名字要和settings.py里的STATIC_URL一致。同样STATICFILES_DIRS对应的名字则为文件夹名。
前端也可以写作:
这样就算STATIC_URL改名也一样可以匹配成功
{% load static %} <link href="{% static 'assets/plugins/bootstrap/css/bootstrap.min.css' %}" rel="stylesheet">
总路由,urls.py,代码:
from django.views.static import serve as serve_static urlpatterns = [ path('admin/', admin.site.urls), # 对外提供访问静态文件的路由,serve_static 是django提供静态访问支持的映射类。依靠它,客户端才能访问到django的静态文件。 path(r'static/<path:path>', serve_static, {'document_root': settings.STATIC_ROOT},), ]
注意:项目上线以后,关闭debug模式时,django默认是不提供静态文件的访问支持,项目部署的时候,我们会通过收集静态文件使用nginx这种web服务器来提供静态文件的访问支持。
(后面还会创建media,与static类似)