一、视图
1.1 FBV和CBV
1.1.1 FBV
FBV就是常用的function base views,基于函数的视图
# urls.py
urlpatterns = [
path('login/',views.login)
]
#views.py
def login(request):
return HttpResponse('登录')
诸如此类的函数和路由一一对应,匹配到路由,就去执行相应的函数。
1.1.2 CBV
CBV是指 class base views,基于类的视图
# urls.py
urlpatterns = [
path('user/',views.UserView.as_view())
]
# views.py
form django.views import View
# 需要继承View这个类
class UserView(View):
def get(self,request):
return HttpResponse('Get')
def post(self,request):
return HttpResponse('Post')
稍微复杂点,在定义路由时,后面加入的自己定义的类.as_view()
然后在views定义的类中,定义get,post,put,delete等会用的方法,然后就会根据请求执行相应的函数
path('login/',views.login)
# 对象内部存储对应关系 ‘login/’ =====》 login函数
path('login/',views.UserView.as_view())
# 对象内部存储对应关系‘user/’ =====> as_view()的返回值
UserView里没有定义as_view(),那就去父类View中找,
class View:
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
...
@classonlymethod
def as_view(cls, **initkwargs):
...
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.setup(request, *args, **kwargs)
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
return self.dispatch(request, *args, **kwargs)
return view
as_view()函数最后返回了view这个函数
def setup(self, request, *args, **kwargs):
"""Initialize attributes shared by all view methods."""
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
def view(request, *args, **kwargs):
self = cls(**initkwargs)
# 1. self = UserView
self.setup(request, *args, **kwargs)
# 对request等内容进行封装
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
return self.dispatch(request, *args, **kwargs)
def dispatch(self, request, *args, **kwargs):
if request.method.lower() in self.http_method_names:
# 2. 判断request的方法是否在 定义的 http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] 里面
# get请求为例
# handler = self.get
"""
class UserView(View):
def get(self,request):
return HttpResponse('Get')
self.get 又是我们定义好的,所以handler就等于 get 函数
"""
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
# 3. 最后 handler()相当于执行函数 get(request)
return handler(request, *args, **kwargs)
# 4. handler返回的get(),那么相当于前面dispatch返回的就是get(),也就相当于前面View.as_view()返回就是get()
1.1.3 FBV和CBV常见使用
FBV常用于前后端不分离
CBV常用于前后端分离的,如小程序API,后续常用django rest framekwork框架就是CBV模式
1.2 参数
- request参数
通过type(request)可以查看request的类型,再导入查看request具体内内容
type(reqeust) # <class 'django.core.handlers.wsgi.WSGIRequest'>
from django.core.handlers.wsgi import WSGIRequest
request.method
"""
@cached_property
def GET(self):
# The WSGI spec says 'QUERY_STRING' may be absent.
raw_query_string = get_bytes_from_wsgi(self.environ, "QUERY_STRING", "")
return QueryDict(raw_query_string, encoding=self._encoding)
"""
# GET源码中会将原始参数转化成QueryDict类型
request.GET ?name=123&age=123&age=199 => 特殊的字典:{name:[123],"age":[123,1999]}
reqeust.POST
request.FILES
request.path_info
request.body
request.resover_math
request.session
#urls.py
from django import path,re_path
urlpatterns = [
path('info/<int:v1>',views.info)
path('mine/<int:v1>',views.MineView.as_view())
# 参数为正则表达式
re_path('repath/(\d+)', views.MineView.as_view())
]
#views.py
def info(request,v1):
return HttpResponse('info')
class MineView(View):
def get(self,request,v1):
print(reques,v1)
print(self.request,self.args,self.kwargs)
return HttpResponse('mine')
- args和kwargs 参数
-
当访问这个
path('mine/<int:v1>',views.MineView.as_view())
时
可以看到链接访问’mine/12’的时候,12这个参数是放在kwargs里传递
-
当访问
re_path('repath/(\d+)', views.MineView.as_view())
时
可以看到问带正则的re_path时,参数放在了args里面传递
1.3业务处理(视图函数)
有了请求的数据,将数据拿到进行处理,如登录,注册,上传文件,数据库操作等等,处理完成得到结果返还给浏览器。
1.4 返回值
1.4.1 HttpResponse
from django.http import HttpResponse
# `构建` 响应体和响应头,
return HttpResponse("成都")
1.4.2 JsonResponse
from django.http import JsonResponse
result = {'staus': True, 'city': '成都'}
return JsonResponse(result)
而JsonResponse返回的数据是Unicode格式
在它内部,本质上是基于HttpResponse的一个类,进行json.dumps操作,然后结果返给HttpResponse返回
class JsonResponse(HttpResponse):
...
data = json.dumps(data, cls=encoder, **json_dumps_params)
super().__init__(content=data, **kwargs)
需要加入参数"ensure_ascii": False
from django.http import JsonResponse
result = {'staus': True, 'city': '成都'}
return JsonResponse(result, json_dumps_params={'ensure_ascii':False})
1.4.3 render
from django.shortcuts import render
return render(request,'login.html')
查看render源码
def render(
request, template_name, context=None, content_type=None, status=None, using=None
)
content = loader.render_to_string(template_name, context, request, using=using)
return HttpResponse(content, content_type, status)
content是已经将模板中特殊字符替换好的字符串。最后本质也是返回一个HttpResponse对象。
1.4.4 redirect
def redirect(to, *args, permanent=False, **kwargs):
# Return an HttpResponseRedirect to the appropriate URL for the arguments
...
class HttpResponseRedirect(HttpResponseRedirectBase):
status_code = 302 #临时重定向
class HttpResponsePermanentRedirect(HttpResponseRedirectBase):
status_code = 301 #永久重定向
class HttpResponseRedirectBase(HttpResponse):
....
self["Location"] = iri_to_uri(redirect_to)
...
返回HttpResponseRedirect
,从类的继承关系看出,最后还是一个HttpResponse对象
返回数据,响应头多了一个Location
,响应体为空,状态码为301或者302,浏览器接收到301或302就重新向Location这个地址发起新的请求
ps:301和302对开发来说影响不大,只是影响搜索引擎收入网址。
二、HTML模板
2.1 模板的使用
def demo(request):
return render(request,"demo.html")
2.1.1 读取模板的位置
- 首先读取
‘demo.html’
这个文件。读取位置就需要看settings
里的设置
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.staticfiles',
"apps.app01.apps.App01Config",
"apps.www.apps.WwwConfig",
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
...
},
]
先从DIR定义的目录中去寻找templates文件,如果找不到再去已注册的APP的下去找。
注意:必须是已注册的APP
可能存在的问题,当多个app下都有同样的文件,那么python从上往下读取,就会出现bug,所以参考django源码进行相同编写
在templates下不直接写html文件,而是建立一个跟自己app同名的文件夹,将html文件放到文件夹里面,
在使用的时候,就可以return render(request,"admin/demo.html")
避免出现问题。
如果有需要复用的公共模板,一般在项目根目录下创建templates,这样优先级就会更高,即使其他App中存在相同的也不会使用。
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
...
},
]
2.1.2 读取文件内容
创建文件,创建为任意后缀都行,最后都是按字符串读取,只是.html后缀,在开发时比较方便,有提示而已。
- 如果模板中没有需要替换的文件,直接读取
- 如果有参数,那么就需要先进行模板渲染(替换),再读取返回的字符串
2.1.3 封装到HttpResponse
封装返回数据,返回给浏览器。
2.2 底层处理机制*
从其他地方看到的,底层处理机制。现目前也不是很了解
namespace = {'city': '成都', 'data': ['2021', '2022', '2023']}
info="""
def _execute():
_buffer = []
_buffer.append("<h1>")
_buffer.append(city)
_buffer.append(data[2])
_buffer.append(" 大运会")
_buffer.append("</h1>")
return "".join(_buffer)
"""
func = compile(info, '<string>', "exec")
exec(func, namespace)
result = namespace['_execute']()
print(result)
2.3 模板的继承
当有一些基础页面需要重复使用很多次,就会使用继承。将基础框架搭建好,再往里面塞不同的东西
继承只能继承一个
{% extends 'xxx.html'%}
2.4 导入
导入可以导入多次
{% include 'xxx.html'%}
2.5 常见易错的模版导入
1.返回的值是数值
return render(request, "demo.html", {'info': 999})
...
<script>
alert({{info}})
</script>
...
此时浏览器会弹出警告窗,且文案为999
2. 返回的值是字符串
return render(request, "demo.html", {'info': '成都'})
...
<script>
alert({{info}})
</script>
...
此时,在进行模板渲染的时候,会替换成alert(成都)
,再返回给浏览器。就会报错。
需要在django项目的html模板中,修改加引号,就不会报错了。
...
<script>
alert('{{info}}')
</script>
...
3. script引入
...
<script src='xxxx.js'> </script>
...
如果在xxx.js
中引入了{{info}}
,那么在进行模板渲染的时候,就不会进行替换。
2.6 自定义模板函数
- app必须注册
- 创建templatetags的文件夹
- 任意创建文件,必须导入
Library
和实例化对象
from django.template.library import Library
register = Library()
2.6.1 simple_tag
# xxx.py
from django.template.library import Library
register = Library()
@register.simple_tag()
def my_func(v1, v2):
return "成都" + v1 + v2
需要在使用的模板,引用{% load xxx %}
# demo.html
{% load xxx %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>{% my_func "2023" "大运会" %}</div>
</body>
</html>
...
自定义的函数会将直接执行,空格后面填写参数。
2.6.2 inclusion_tag(‘xxx.html’)
# xxx.py
@register.inclusion_tag("select.html")
def my_select(num):
return {"x": [item for item in num if item > 22]}
inclusion_tag
会将得到的返回值,返回给xxx.html
,然后再返回给引用的对象。适用于显示页面小片段
例如
# select.html
<ul>
{% for item in x %}
<li>{{ item }}</li>
{% endfor %}
</ul>
# views.py
def demo(request):
return render(
request,
"demo.html",
{
'num': [11, 22, 33, 44],
'ctime': datetime.datetime.now()
}
)
# demo.html
...
<div>{% my_select num %}</div>
...
首先views.py
传参num给demo.html
然后 my_select
检测到时inclusion_tag
,调用自定义函数my_select(num)
,得到结果返回给select.html
,在select.html
中,又执行一次循环展示
最终得到实际结果
2.6.3 filter
filter最多支持2个参数。
#xxx.py
@register.filter
def my_f1(a1, a2):
return "Hello" + a1 + a2
# demo.html
...
<div>{{ "成都"|my_f1:"下雨" }}</div>
...
2.6.4 区别
filter
与simple_tag
和inclusion_tag
使用不同,
使用{{xxx|filter}}
双括号使用,必须带至少一个参数,且只能传2个参数
simple_tag
和inclusion_tag
则是使用{% my_select num %}
,但括号+%使用,且参数不限制。
filter虽然使用没有其他两个方便,但是当模板中存在if判断条件时,只有filter可以当做判断条件,其他两个则不行
{% if "成都"|my_tt:"下雨" %}
<h1>真</h1>
{% else %}
<h1>假</h1>
{% endif %}