04 视图层FBV和CBV 源码 setting源码 模板层
一、视图层
1.小白必会三板斧
1.HttpResponse
2.render
3.redirect
django视图函数必须要给返回一个HttpResponse对象(看源码就知道了,三板斧本质都是)
详细内容:https://www.cnblogs.com/xp1315458571/p/11524988.html#_label5
2.前后端分离
前端一个人干
后端另一个干
后端只负责产生接口,前端调用该接口能拿到一个大字典(后端用字典,字典json传给前端,前端接收字典,反json转成自定义对象)
后端只需要写一个接口文档 ,即说明书,里面描述字典的详细信息以及参数的传递
只要涉及到数据交互,一般情况下都是用的json格式
JSON.stringify() ——>对应后端 :json.dumps()
JSON.parse() ——> 对应后端:json.loads()
3.JsonReponse
后端数据怎样传到前端呢?用到JsonReponse
#传字典到前端
from django.http import JsonResponse import json def index(request): data = {'name':'jason好帅哦 我好喜欢','password':123} # res = json.dumps(data,ensure_ascii=False) #告诉json不要瞎鸡儿转码,我写什么你加双引号成json类型就行了,不写的话汉字在前端会转成二进制 # return HttpResponse(res) return JsonResponse(data,json_dumps_params={'ensure_ascii':False}) #代替上面两句
#传其他类型到前端
from django.http import JsonResponse def index(request): l = [1,2,3,4,5,6,7,8] return JsonResponse(l,safe=False) # 如果返回的不是字典 只需要修改safe参数为false即可
4.form表单上传文件
form表单上传文件需要注意的事项 1.enctype需要由默认的urlencoded变成formdata 2.method需要由默认的get变成post (目前还需要考虑的是 提交post请求需要将配置文件中的csrf中间件注释) 3.如果form表单上传文件 后端需要在request.FILES获取文件数据 而不再是POST里面
#上传文件html
#后端代码
def upload(request): """ 保存上传文件前,数据需要存放在某个位置。默认当上传文件小于2.5M时,django会将上传文件的全部内容读进内存。从内存读取一次,写磁盘一次。 但当上传文件很大时,django会把上传文件写到临时文件中,然后存放到系统临时文件夹中。 """ if request.method == "POST": # 从请求的FILES中获取上传文件的文件名,file为页面上type=files类型input的name属性值 filename = request.FILES["file"].name # 在项目目录下新建一个文件 with open(filename, "wb") as f: # 从上传的文件对象中一点一点读 for chunk in request.FILES["file"].chunks(): # 写入本地文件 f.write(chunk) return HttpResponse("上传OK")
5.FBV和CBV
视图函数并不只是指函数 也可以是类
只要是处理业务逻辑的视图函数都需要接受一个request参数
FBV(基于函数的视图) 面向函数式编程
CBV(基于类的视图) 面向对象式编程
# render内部原理(FBV)
render返回一个html页面 并且还能够给该页面传数据
from django.shortcuts import render,HttpResponse from django.template import Template,Context # FBV def index(request): temp = Template('<h3>{{ user }}</h3>') con = Context({"user":{"name":'jason',"password":'123'}}) res = temp.render(con) print(res) #<h3>{'name': 'jason', 'password': '123'}</h3> return HttpResponse(res)
# CBV
问题:基于CBV的视图函数
#urls.py中: url(r'^login/',views.MyLogin.as_view()) #views.py中: from django.shortcuts import render, HttpResponse from django.template import Template, Context from django.views import View class MyLogin(View): # 这里继承View,下面才接收request参数 def get(self,request): # 这里接收request print("from MyLogin get方法") return render(request,'login.html') def post(self,request): return HttpResponse("from MyLogin post方法")
【问题】FBV中我们通过判断request.method是get还是post来进行不同的操作,那么CBV中get请求来就会自动走走类里面get方法,post请求来就会走类里面post方法 为什么???
#研究方向 #1.从url入手 url(r'^login/',views.MyLogin.as_view()) #由于函数名加括号执行优先级最高,所以这一句话一写完会立刻执行as_view()方法,
#进入as_view()源码 @classonlymethod #这里是类绑定 def as_view(cls, **initkwargs): # cls就是我们自己的写的类 MyLogin def view(request, *args, **kwargs): self = cls(**initkwargs) # 实例化产生MyLogin的对象 self = MyLogin(**ininkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs # 上面的几句话都仅仅是在给对象新增属性 return self.dispatch(request, *args, **kwargs) # dispatch返回什么 浏览器就会收到什么 # 对象在查找属性或者方法的时候 你一定要默念 先从对象自己这里找 然后从产生对象的类里面找 最后类的父类依次往后 return view
#通过源码发现url匹配关系可以变形成 url(r'^login/',views.view) # FBV和CBV在路由匹配上是一致的 都是url后面跟函数的内存地址 #2.当浏览器中输入login 会立刻触发view函数的运行,
#dispatch返回什么浏览器就受到什么,我们进入dispatch源码 def dispatch(self, request, *args, **kwargs): # 我们先以GET为例 if request.method.lower() in self.http_method_names:
# http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] # 反射获取我们自己写的类产生的对象的属性或者方法 # 以GET为例 handler = getattr(self,'get','取不到报错的信息') # handler = get(request) handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs) # 直接调用我们自己的写类里面的get方法 # 源码中先通过判断请求方式是否符合默认的八个请求方法 然后通过反射获取到自定义类中的对应的方法执行
二、django中settings源码解析
1.问题:为什么配置中所有变量名必须大写才能生效?
前提: 1.django通常都会有两个配置,除了暴露给用户一个settings.py配置文件之外 自己内部还有一个默认的配置文件,用户配置了就用用户的,没有配置就用默认的
2.django中暴露给用户的配置settings.py默认在与项目同名的文件夹中
django项目内部默认的配置文件 from django.conf import global_settings
3.可以直接导入与项目同名的文件夹中的配置文件 : from 项目名 import settings
但是通常我们在django中使用配置文件一般都是按照一下方式导入:from django.conf import settings (自定义+全局的 这样理解吧)
4.django的启动入口是manage.py
from django.conf import settings #这样导入配置,点进settings
class LazySettings(LazyObject): def _setup(self, name=None): # os.environ你可以把它看成是一个全局的大字典 settings_module = os.environ.get(ENVIRONMENT_VARIABLE) # 从大字典中取值键为DJANGO_SETTINGS_MODULE所对应的值:day54.settings # settings_module = 'day54.settings' self._wrapped = Settings(settings_module) # Settings('day54.settings') settings = LazySettings() # settings是lazysettings产生的一个单例对象,我们继续进入lazysettings源码
settings_module = os.environ.get(ENVIRONMENT_VARIABLE) #这是从全局大字典中取值,那么ENVIRONMENT_VARIABLE是什么呢?点进去会跳到顶部 ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" #这个字符串是什么呢?我们开始去启动文件中查找
import os import sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day54.settings") # django在启动的时候 就会往全局的大字典(environ)中设置一个键值对 值是暴露给用户的配置文件的路径字符串 self._wrapped = Settings(settings_module)# 继续点进Settings class Settings(object): def __init__(self, settings_module): # settings_module = 'day54.settings' for setting in dir(global_settings): # django全局配置文件 # dir获取django全局配置文件中所有的变量名 if setting.isupper(): # 判断文件中的变量名是否是大写 如果是大写才会执行/生效 setattr(self, setting, getattr(global_settings, setting)) # 给最初我们导入的settings对象设置键值对 # 给settings对象设置键值对 settings[配置文件中大写的变量名] = 配置文件中大写的变量名所对应的值
self.SETTINGS_MODULE = settings_module # 'day54.settings' mod = importlib.import_module(self.SETTINGS_MODULE) # mod = 模块settings(暴露给用户的配置文件) for setting in dir(mod): # for循环获取暴露给用户的配置文件中所有的变量名 if setting.isupper(): # 判断变量名是否是大写 setting_value = getattr(mod, setting) # 获取大写的变量名所对应的值 setattr(self, setting, setting_value) # 给settings对象设置键值对
""" d = {} d['username'] = 'jason' d['username'] = 'egon' 用户如果配置了就用用户的 用户如果没有配置就用系统默认的 其实本质就是利用字典的键存在就是替换的原理 实现了用户配置就用用户的用户没配置就用默认的 """
2.补充 importlib 模块
3.根据settings源码原理自定义一个实例
要求:正常启动输出‘我是自定义配置’,去掉自定义配置则输出‘我是默认配置’
三、模板层
1.基本语法
只需要记两种特殊符号: {{ }}和 {% %} 变量相关的用{{}},逻辑相关的用{%%}。
1.views中传值: render(request, "template_test.html", {"l": l, "d": d, "person_list": person_list}) 2.html模板中就收使用:
{# 取列表l中的第一个参数 #} {{ l.0 }} {# 取字典中key的值 #} {{ d.name }} {# 取对象的name属性 #} {{ person_list.0.name }} {# .操作只能调用不带参数的方法 #} {{ person_list.0.dream }}
{# 我是模板语法的注释方法#}
2. 视图给模板传值
1.基本的数据类型都可以传给模板
2.传递函数的时候 会自动加括号调用 前端展示的是函数调用的之后的返回值,没有返回值就收到None,
注意:django的模板语法 不支持给函数传参!!!
方式1 通过字典的键值对 指名道姓的一个个的传 return render(request,'reg.html',{'n':n,'f':f}) 方式2 locals会将它所在的名称空间中的所有的名字全部传递给前端 该方法虽然好用 但是在某些情况下会造成资源的浪费,比如你只用一个就可以了,一下子全丢给你 return render(request, 'reg.html', locals())
3.过滤器
<h1>模板语法之标签:内部原理(会将|前面的当做第一个参数传入标签中)</h1> <p>{{ l|length }}获取列表或者字符串长度</p> 7<p>{{ ss|default:'nothing' }}当|左边变量为false或空则使用|右边默认值,自己有用自己的</p> <p>{{ file_size|filesizeformat }}获得大小</p> 11.7M <p>{{ info|truncatewords:3 }} 就是按空格截取 三个点不算</p> my name is ... <p>{{ info1|truncatewords:3 }}</p> 傻大姐 撒旦 技术 ... <p>{{ info|truncatechars:6 }}按字符截取内容 三个点也算</p>my... <p>{{ ctime }}</p> Sept. 18, 2019, 9:19 p.m. <p>{{ ctime|date:'Y-m-d' }} 只需要掌握年月日就可以了</p> 2019-09-18 <p>{{ n|add:100 }}数值加减</p> 100(n原本为0) <p>{{ s|add:'hahah 翻车啦' }}字符串拼接</p> 你妹的hahah 翻车啦 <p>{{ l|slice:'0:3' }}支持切片</p> [1,2,3] <p>{{ l|slice:'0:5:2' }}支持步长</p> [1,3,5]
safe
Django的模板中会对HTML标签和JS等语法标签进行自动转义(字符串化),原因显而易见,这样是为了安全。
yyy = '<script>alert(123)</script>'
<p>{{ yyy|safe }}</p> 如果不转义,标签生效,会出现弹窗,类似这个有些人利用这一特性标签内写一个死循环那服务器就炸了
<p>{{ yyy }}</p> 转义后输出字符串'<script>alert(123)</script>'就安全了
但是有的时候我们可能不希望这些HTML元素被转义,比如(我们显然是需要这个样式生效)
<p>{{ xxx}}</p> 会得到字符串 '<h1>波波棋牌室</h1>' <p>{{ xxx|safe }}</p> 波波棋牌室
为了在Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。
比如:
前后端取消转义:(******) 前端: |safe 后端: from django.utils.safestring import mark_safe zzz = mark_safe('<h1>啊啊啊</h1>') <p>{{ zzz }}</p> 啊啊啊
4.标签
# for
{% for foo in t %} <p>{{ forloop }}</p> {% endfor %}
# if ... else
{% if '' %} <p>xxx条件为true</p> {% else %} <p>xxx条件为false</p> {% endif %}
输出:xxx条件为false
# for if 嵌套
{% for %} {{ forloop }} {{ forloop.first }} {{ forloop.last }} {{ forloop.counter0 }} {{ forloop.counter }} 数据库中的数据它的主键值 不可能是依次递增的 有可能被你删除了几条数据 主键值就不再连续的了,但是你又不想让用户看出来,就这样搞 {% empty %} 当for循环对象不能被for循环的时候会走empty逻辑 {% endfor %}
{% for foo in '' %}
{% if forloop.first %} <p>这是我的第一次</p> {% elif forloop.last %} <p>这是最后一次了啊</p> {% else %} <p>来啊来啊!!!</p> {% endif %}
{% empty %} <p>当for循环的对象为空的时候 会走empty</p> {% endfor %}
输出:
这是我的第一次
来啊来啊!!!
这是最后一次了啊
#循环字典
d = {"name": 'jason', 'password': 123} {% for foo in d.keys %} <p>{{ foo }}</p> {% endfor %}
{% for foo in d.values %} <p>{{ foo }}</p> {% endfor %}
{% for foo in d.items %} <p>{{ foo }}</p> {% endfor %}
输出:
name
password
jason
123
('name', 'jason')
('password', 123)
#句点符取值,with取别名
如果你想在前端获取后端传递的某个容器类型中的具体元素
那么你可以通过句点符来获取具体的元素
可以给一个比较复杂的取值操作取一个别名 之后在with语句中 就可以使用该别名(外部不能使用)
django模板语法在取值的时候 统一使用句点符(大白话就是 点号 .)
l = [1, 2, 3, 4, 5, 6, [12, 3, 4, {'name': 'heiheihei'}]]
{% with l.6.3.name as ttt %} 可以给一个比较复杂的取值操作取一个别名 之后在with语句中 就可以使用该别名
{{ ttt }} heiheihei
{{ l.6.3.name }} heiheihei
{% endwith %}
5.自定义标、过滤器、inclusion_tag
自定义标签固定的三步走战略:
1.必须在你的应用下新建一个名为templatetags文件夹
2.在该文件夹内新建一个任意名称的py文件
3.在该py文件中固定先写下面两句代码
from django import template
register = template.Library()
#自定义过滤器
{% load mytag %}
{{ 123|baby:1}}
结果: 124
自定义过滤器 只能有两个形参,但是你可以在给第二个参数传值的时候 传一个字符串
{% load mytag %}
{{ 'abc'|baby:'d-e=f+'}}
结果: abcd-e=f+
#自定义标签
#支持传多个值
{% load mytag %}
{% jason 1 2 3 year=2 %}
结果: 1?2|3{2
# 自定义inclusion_tag
当你的页面上有一部分html代码需要经常被各个地方使用 并且需要传参才能渲染出,
那么你可以把该html代码部分制作成一个inclusion_tag
任何页面都能使用
任何html文件中都可以调用
{% load mytag %}
{% bigplus 5 %}
6.模板的继承
当多个页面整体的样式都大差不差的情况下 可以设置一个模板文件
在该模板文件中 使用block块划分多个区域,并起个名字,比如css块
之后子版在使用模板的时候 可以通过选择block块的名字,来修改对应区域分区域
#模板文件分块处理
模板一般情况下 应该至少有三个可以被修改的区域(这些标记本身就是注释语法在前端不会有任何显示,更不会对模板本身产生任何影响) {% block css %} 父页面自己的css代码 {% endblock %} {% block content %} 父页面自己的html代码 {% endblock %} {% block js %} 父页面自己的js代码 {% endblock %}
#模板中必须有相应的块,子版才能修改,比如模板中有了css块即使为空,子版的样式才能修改,否则不能
# 模板的继承 使用方式
{{ block.super }}
{% extends 'home.html' %} #起手就是继承 {% block css %} <style> h1 {color: red;} </style> {% endblock %} {% block content %}
# {{block.super}} 加上这句话会原封不动的生成一个原来这个块的界面,最后会出现上面是home主页下面是登录界面 <h1>登陆页面</h1> <form action=""> <p>username:<input type="text" class="form-control"></p> <p>password:<input type="text" class="form-control"></p> <input type="submit" class="btn btn-danger"> </form> {% endblock %} {% block js %} {% endblock %} # 一般情况下 模板上的block越多 页面的可扩展性就越强
7.模板的导入
当你有个特别好看的表单或者样式别的地方需要使用,直接以模块导入即可
{% include 'beautiful.html' %}
#beautiful.html
<h1>我是导入的</h1>