一、路由映射的参数
1.映射的一般使用
在app/urls.py中,我们定义URL与视图函数之间的映射:
from django.contrib import admin
from django.urls import path
from django.urls import re_path from mgmt import views urlpatterns = [
path('index', views.index),
path('host/', views.host),
re_path('test_ajax$', views.test_ajax),
]
我们查看一下path()和re_path()的源码,可以看到参数信息:
def _path(route, view, kwargs=None, name=None, Pattern=None):
pass
除了route表示url字符串,view表示视图函数的引用,name表示别名(可以在页面中用{% url name %}取url值)。
2.在映射中传递参数
还有一个形参是kwargs,这个参数接收一个字典,用来传递参数给视图函数:
from django.contrib import admin
from django.urls import path
from django.urls import re_path from mgmt import views urlpatterns = [
path('index', views.index),
path('host/', views.host),
re_path('test_ajax$', views.test_ajax),
path('test', views.test, {'name': 'root'}),
]
在视图函数的实现中,可以接受参数name:
# test视图函数,接受参数name
def test(request, name):
print(name)
return HttpResponse("OK")
二、路由系统中的命名空间
(暂不清楚有什么用,感觉就是第一层urls.py映射的name参数,然后和App urls.py中的映射的name参数一起来反射URL)
1.未使用路由分发时
当我们没有使用路由分发的时候,访问/index页面,直接使用http://127.0.0.0:8000/index就可以访问。
如果我们为其映射传入一个name参数:(参考:[Python自学] day-19 (1) (Django框架) 第六节)
path('test', views.test, {'name': 'root'}, name='tt')
我们在视图函数中,可以使用reverse来利用name获取URL:
# test视图函数,接受参数name
def test(request, name):
# 通过name='tt'来获取URL 'test'
from django.urls import reverse
url = reverse('tt') print(name)
return HttpResponse("OK")
2.使用路由分发时(分层)
当我们使用了路由分发,urls.py分为两层。
第一层是Django工程目录下的urls.py:
from django.contrib import admin
from django.urls import path
from django.urls import include urlpatterns = [
path('cmdb/', include("cmdb.urls")),
path('mgmt/', include("mgmt.urls")),
]
第二层才是App的urls.py(这里以mgmt为例):
from django.contrib import admin
from django.urls import path
from django.urls import re_path from mgmt import views urlpatterns = [
path('index', views.index),
path('host/', views.host),
re_path('test_ajax$', views.test_ajax),
path('test', views.test, {'name': 'root'}, name='tt'),
]
假设,第一层urls.py变为这样:
from django.contrib import admin
from django.urls import path
from django.urls import include urlpatterns = [
# path('cmdb/', include("cmdb.urls")),
path('mgmt/', include("mgmt.urls")),
path('mgmt2/', include("mgmt.urls")),
]
这里的mgmt/ 和mgmt2/都指向mgmt App的urls.py。这就会导致我们用http://127.0.0.0:8000/mtmg/test和http://127.0.0.0:8000/mtmg2/test访问到的视图函数是同一个。
在这种情况下,如果我们想要在视图函数中分别获取两个不同的url,例如mgmt/test和mgmt2/test。可以为每个第一层映射添加一个命名空间:
from django.contrib import admin
from django.urls import path
from django.urls import include urlpatterns = [
# path('cmdb/', include("cmdb.urls")),
path('mgmt/', include("mgmt.urls"), namespace='m1'),
path('mgmt2/', include("mgmt.urls"), namespace='m2'),
]
使用命名空间的情况下,App urls.py还需要添加一个app_name变量:
from django.contrib import admin
from django.urls import path
from django.urls import re_path from mgmt import views app_name = 'mgmt'
urlpatterns = [
path('index', views.index),
path('host/', views.host),
re_path('test_ajax$', views.test_ajax),
path('test', views.test, {'name': 'root'}, name='tt'),
]
然后,我们在视图函数中使用reverse来获取URL:
# test视图函数,接受参数name
def test(request, name):
from django.urls import reverse
# 通过'namespace:name',来获取url
url = reverse('m1:tt')
url2 = reverse('m2:tt')
print(url)
print(url2)
return HttpResponse("OK")
三、request获取请求信息
1.在Views.py中看看request的类
def test(request, name):
# 打印type(request)
print(type(request)) return HttpResponse("OK")
打印结果:<class 'django.core.handlers.wsgi.WSGIRequest'>
2.查看WSGIRequest类的源码
class WSGIRequest(HttpRequest):
def __init__(self, environ):
script_name = get_script_name(environ)
# If PATH_INFO is empty (e.g. accessing the SCRIPT_NAME URL without a
# trailing slash), operate as if '/' was requested.
path_info = get_path_info(environ) or '/'
self.environ = environ
self.path_info = path_info
# be careful to only replace the first slash in the path because of
# http://test/something and http://test//something being different as
# stated in https://www.ietf.org/rfc/rfc2396.txt
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()
# Set content_type, content_params, and encoding.
self._set_content_type_params(environ)
try:
content_length = int(environ.get('CONTENT_LENGTH'))
except (ValueError, TypeError):
content_length = 0
self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
self._read_started = False
self.resolver_match = None def _get_scheme(self):
return self.environ.get('wsgi.url_scheme') @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) def _get_post(self):
if not hasattr(self, '_post'):
self._load_post_and_files()
return self._post def _set_post(self, post):
self._post = post @cached_property
def COOKIES(self):
raw_cookie = get_str_from_wsgi(self.environ, 'HTTP_COOKIE', '')
return parse_cookie(raw_cookie) @property
def FILES(self):
if not hasattr(self, '_files'):
self._load_post_and_files()
return self._files POST = property(_get_post, _set_post)
我们可以看到,在创建一个request对象时,构造函数有一个参数叫做environ。这个参数就包含了所有来自客户端的请求信息。
我们还可以看到GET()和POST是如何从environ中获取相应数据的,Django帮我们将常用的数据封装成了字典或列表形式,方便我们使用。
除了Django为我们封装好的部分常用数据,如果我们还需要其他的数据,就需要我们自己从environ中获取,并处理。
3.打印environ中的信息
def test(request, name):
dic = request.environ # 使用request.META可以取到一样的数据
for k, v in dic.items():
print(k, '<=====>', v) return HttpResponse("OK")
打印结果:
ALLUSERSPROFILE <=====> C:\ProgramData
APPDATA <=====> C:\Users\Administrator\AppData\Roaming
COMMONPROGRAMFILES <=====> C:\Program Files\Common Files
COMMONPROGRAMFILES(X86) <=====> C:\Program Files (x86)\Common Files
COMMONPROGRAMW6432 <=====> C:\Program Files\Common Files
COMPUTERNAME <=====> 8RBSKRQCXJM9LF7
COMSPEC <=====> C:\Windows\system32\cmd.exe
DJANGO_SETTINGS_MODULE <=====> secondsite.settings
HOMEDRIVE <=====> C:
HOMEPATH <=====> \Users\Administrator
IDEA_INITIAL_DIRECTORY <=====> C:\Users\Administrator\Desktop
LOCALAPPDATA <=====> C:\Users\Administrator\AppData\Local
LOGONSERVER <=====> \\8RBSKRQCXJM9LF7
NUMBER_OF_PROCESSORS <=====> 4
ONEDRIVE <=====> C:\Users\Administrator\OneDrive
OS <=====> Windows_NT
PATH <=====> D:\pycharm_workspace\secondsite\venv\Scripts;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;D:\Apps\Git\cmd;D:\Dev_apps\Anaconda5.3.0\Scripts;D:\Dev_apps\Anaconda5.3.0;C:\Users\Administrator\AppData\Local\Microsoft\WindowsApps;;D:\Apps\PyCharm 2019.3\bin;;D:\Dev_apps\Anaconda5.3.0\lib\site-packages\numpy\.libs;D:\Dev_apps\Anaconda5.3.0\lib\site-packages\numpy\.libs
PATHEXT <=====> .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
PROCESSOR_ARCHITECTURE <=====> AMD64
PROCESSOR_IDENTIFIER <=====> Intel64 Family 6 Model 94 Stepping 3, GenuineIntel
PROCESSOR_LEVEL <=====> 6
PROCESSOR_REVISION <=====> 5e03
PROGRAMDATA <=====> C:\ProgramData
PROGRAMFILES <=====> C:\Program Files
PROGRAMFILES(X86) <=====> C:\Program Files (x86)
PROGRAMW6432 <=====> C:\Program Files
PROMPT <=====> (venv) $P$G
PSMODULEPATH <=====> C:\Program Files\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules;C:\Program Files\Intel\Wired Networking\
PUBLIC <=====> C:\Users\Public
PYCHARM <=====> D:\Apps\PyCharm 2019.3\bin;
PYCHARM_DISPLAY_PORT <=====> 63342
PYCHARM_HOSTED <=====> 1
PYTHONIOENCODING <=====> UTF-8
PYTHONPATH <=====> D:\pycharm_workspace\secondsite;D:\Apps\PyCharm 2019.3\plugins\python\helpers\pycharm_matplotlib_backend;D:\Apps\PyCharm 2019.3\plugins\python\helpers\pycharm_display
PYTHONUNBUFFERED <=====> 1
SESSIONNAME <=====> Console
SYSTEMDRIVE <=====> C:
SYSTEMROOT <=====> C:\Windows
TEMP <=====> C:\Users\ADMINI~1\AppData\Local\Temp
TMP <=====> C:\Users\ADMINI~1\AppData\Local\Temp
USERDOMAIN <=====> 8RBSKRQCXJM9LF7
USERDOMAIN_ROAMINGPROFILE <=====> 8RBSKRQCXJM9LF7
USERNAME <=====> Administrator
USERPROFILE <=====> C:\Users\Administrator
VIRTUAL_ENV <=====> D:\pycharm_workspace\secondsite\venv
WINDIR <=====> C:\Windows
_OLD_VIRTUAL_PATH <=====> C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;D:\Apps\Git\cmd;D:\Dev_apps\Anaconda5.3.0\Scripts;D:\Dev_apps\Anaconda5.3.0;C:\Users\Administrator\AppData\Local\Microsoft\WindowsApps;;D:\Apps\PyCharm 2019.3\bin;
_OLD_VIRTUAL_PROMPT <=====> $P$G
RUN_MAIN <=====> true
SERVER_NAME <=====> 8RBSKRQCXJM9LF7
GATEWAY_INTERFACE <=====> CGI/1.1
SERVER_PORT <=====> 8000
REMOTE_HOST <=====>
CONTENT_LENGTH <=====>
SCRIPT_NAME <=====>
SERVER_PROTOCOL <=====> HTTP/1.1
SERVER_SOFTWARE <=====> WSGIServer/0.2
REQUEST_METHOD <=====> GET
PATH_INFO <=====> /mgmt/test
QUERY_STRING <=====>
REMOTE_ADDR <=====> 127.0.0.1
CONTENT_TYPE <=====> text/plain
HTTP_HOST <=====> 127.0.0.1:8000
HTTP_CONNECTION <=====> keep-alive
HTTP_CACHE_CONTROL <=====> max-age=0
HTTP_UPGRADE_INSECURE_REQUESTS <=====> 1
HTTP_USER_AGENT <=====> Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36
HTTP_SEC_FETCH_USER <=====> ?1
HTTP_ACCEPT <=====> text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
HTTP_SEC_FETCH_SITE <=====> none
HTTP_SEC_FETCH_MODE <=====> navigate
HTTP_ACCEPT_ENCODING <=====> gzip, deflate, br
HTTP_ACCEPT_LANGUAGE <=====> zh-CN,zh;q=0.9
wsgi.input <=====> <django.core.handlers.wsgi.LimitedStream object at 0x000001CDBF3FF5C0>
wsgi.errors <=====> <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>
wsgi.version <=====> (1, 0)
wsgi.run_once <=====> False
wsgi.url_scheme <=====> http
wsgi.multithread <=====> True
wsgi.multiprocess <=====> False
wsgi.file_wrapper <=====> <class 'wsgiref.util.FileWrapper'>
我们可以从中看到很多熟悉的字段。
如果我们在访问页面是使用GET提交数据(leo=111),则在environ中可以看到一个叫做 "QUERY_STRING" 的字段,他的值为"leo=111"。
4.从environ中获取POST提交的数据
当我们使用POST提交时,我们可以从environ中获取POST传递的数据:
def test(request, name):
# 获取请求体的长度
request_body_size = int(request.environ.get('CONTENT_LENGTH', 0))
print("CONTENT_LENGTH:", request_body_size)
# 按请求体长度读取body
request_body = request.environ['wsgi.input'].read(request_body_size)
# 打印body的内容
print("request_body:", request_body.decode('utf-8')) return HttpResponse("OK")
打印结果为:
CONTENT_LENGTH: 49
request_body: hostname=host1&ip=192.168.1.1&port=1234&busi_id=4
我们可以看到,POST提交的数据,只是将其放在了body中,格式和GET提交时一样。在浏览器F12中可以验证:
5.请求头和请求体数据
当django拿到请求后,会将请求头和请求体分开处理,我们可以通过以下方式获取:
request.headers #获取请求头
request.body #获取请求体,例如POST提交的数据
request.META #获取和environ一样的数据,包含服务器本地的信息和请求头信息
四、HTML模板继承
如果多个HTML页面,只有内容部分不一样,标题栏和导航栏都是一样的。我们无需要每个页面都重复写一样的部分。
1.先写好标题栏和导航栏
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/commons.css"/>
<style>
.pg-header{
height: 48px;
background-color: seagreen;
color: lightgrey;
}
.left-navi{
width: 200px;
position: fixed;
left: 0;
top:48px;
bottom: 0;
background-color: cadetblue;
color: lightgray;
}
</style>
</head>
<body>
<div class="pg-header"><h3 style="display: inline">后台管理平台</h3></div>
<div class="left-navi">
<p><a href="#">主机管理</a></p>
<p><a href="#">用户管理</a></p>
<p><a href="#">应用管理</a></p>
</div>
<script src="/static/jquery-1.12.4.js"></script>
</body>
</html>
效果如下:
2.将master.html改写为可继承的模板
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<title>{% block title %}<!-- 这里用于替换title -->{% endblock %}</title>
<link rel="stylesheet" href="/static/commons.css"/>
<style>
.pg-header{
height: 48px;
background-color: seagreen;
color: lightgrey;
}
.left-navi{
width: 200px;
position: fixed;
left: 0;
top:48px;
bottom: 0;
background-color: cadetblue;
color: lightgray;
}
.content{
left:200px;
top:48px;
position: fixed;
right: 0;
bottom: 0;
background-color: #dddddd;
}
</style>
</head>
<body>
<div class="pg-header"><h3 style="display: inline">{% block h3 %}{% endblock %}</h3></div>
<div class="left-navi">
<p><a href="#">主机管理</a></p>
<p><a href="#">用户管理</a></p>
<p><a href="#">应用管理</a></p>
</div>
<div class="content">
{% block content %}
<!-- 这里用于替换content -->
{% endblock %}
</div>
<script src="/static/jquery-1.12.4.js"></script>
</body>
</html>
我们使用{%block block_name%}{%endblock%}来指定要替换的内容。这样这个模板就可以被其他html继承了。
3.hostmanage.html继承master.html
例如,我们有一个主机管理页面和应用界面,导航和标题栏样式都使用master.html。
hostmanage.html:
<!-- 继承master.html -->
{% extends 'master.html' %} <!-- 填充内容 -->
{% block title %}主机管理{% endblock %}
{% block h3 %}主机管理{% endblock %}
{% block content %}
<ul>
{% for i in host_list %}
<li>{{ i.hostname }}</li>
{% endfor %}
</ul>
{% endblock %}
对应视图函数:
def hostmanage(request):
objs = models.Host.objects.all()
return render(request, 'hostmanage.html', {'host_list': objs})
效果如下:
appmanage.html:
<!-- 继承master.html -->
{% extends 'master.html' %} <!-- 填充内容 -->
{% block title %}应用管理{% endblock %}
{% block h3 %}应用管理{% endblock %}
{% block content %}
<ul>
{% for i in app_list %}
<li>{{ i.appname }}</li>
{% endfor %}
</ul>
{% endblock %}
对应视图函数:
def appmanage(request):
objs = models.Application.objects.all()
return render(request, 'appmanage.html', {'app_list': objs})
效果如下:
4.各页面内容使用自己的css和JS
当主机管理页面和应用管理页面的内容都需要使用自己独有的css和js时,则需要在master.html再添加一个放置css和js的地方:
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<title>{% block title %}<!-- 这里用于替换title -->{% endblock %}</title>
<link rel="stylesheet" href="/static/commons.css"/>
<style>
<!-- master.html中使用的css -->
</style>
{% block css %}{% endblock %}
</head>
<body>
<!-- master.html中的固定内容 -->
{% block content %}
<!-- 这里用于替换content -->
{% endblock %}
<script src="/static/jquery-1.12.4.js"></script>
{% block js %}{% endblock %}
</body>
</html>
当我们的hostmanage.html继承master.html的时候,可以将自己的css和js替换到相应的位置:
<!-- 继承master.html -->
{% extends 'master.html' %} <!-- 独有的CSS -->
{% block css %}
<link rel="stylesheet" href="/static/host_content.css"/>
{% endblock %} <!-- 填充内容 -->
{% block title %}主机管理{% endblock %}
{% block h3 %}主机管理{% endblock %}
{% block content %}
<ul>
{% for i in host_list %}
<li>{{ i.hostname }}</li>
{% endfor %}
</ul>
{% endblock %}
<!-- 独有的JS -->
{% block css %}
<script src="/static/host_content.js"></script>
{% endblock %}
五、HTML导入小组件
第四节中,html继承于一个模板,相当于该html的内容嵌入到被继承的模板中,最终形成一个完整的html模板。被继承模板 > 继承模板
而在这节中,我们可以在html中导入其他html实现好的组件,从而完善本html页面。导入模板 > 被导入模板
1.实现一个小组件(单独在一个html中实现)
tag.html:
<div>
<form>
<div>用户名:<input type="text"/></div>
<div>密码:<input type="password"/></div>
<div>Email:<input type="text"/></div>
<div>Phone:<input type="text"/></div>
<div><input type="submit" value="确定"/></div>
</form>
</div>
2.在include.html导入tag.html组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Include</title>
<style>
.pg-header{
height: 48px;
background-color: seagreen;
color: lightgrey;
}
</style>
</head>
<body>
<div class="pg-header"><h3 style="display: inline">登录页面</h3></div>
{% include 'tag.html' %}
</body>
</html>
tag.html的内容就会被放到{%include 'tag.html'%}的位置。
3.添加urls.py和视图函数,并运行观察效果
访问http://127.0.0.1:8000/mgmt/include
4.如果组件搭配有css和js文件,则记得在include.html中导入
例如,我们为tag.html实现一个tag.css和tag.js文件:
tag.css:
#tag_form{
background-color: aquamarine;
}
tag.js:
$('#tag_form').find('input[type="button"]').click(function(){
alert('这是测试按钮');
});
在include.html导入tag.html的时候,需要同时导入tag.css和tag.js:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>Include</title>
<style>
.pg-header{
height: 48px;
background-color: seagreen;
color: lightgrey;
}
</style>
<link rel="stylesheet" href="/static/tag.css"/>
</head>
<body>
<div class="pg-header"><h3 style="display: inline">登录页面</h3></div>
{% include 'tag.html' %}
<script src="/static/jquery-1.12.4.js"></script>
<script src="/static/tag.js"></script>
</body>
</html>
观察效果:
六、Django提供的模板函数
我们在前面的章节使用模板语言,只是将render传递的数据简单的展示出来。而Django为我们提供的模板语言,其实还可以对数据进行二次加工。
Django为我们提供了一些模板语言函数
列举一些简单函数:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>Include</title>
<style>
.pg-header{
height: 48px;
background-color: seagreen;
color: lightgrey;
}
</style>
</head>
<body>
<div class="pg-header"><h3 style="display: inline">登录页面</h3></div>
<div>
<p>{{ test_string }}</p>
<p>{{ test_string|truncatechars:'20' }}</p>
<p>{{ test_string|lower }}</p>
<p>{{ test_string|cut:' '|upper }}</p>
</div>
<script src="/static/jquery-1.12.4.js"></script>
</body>
</html>
1)truncatechars:'10',截取字符串的前10个字符。
2)lower,全部转换为小写
3)cut:' ',按空格切割
这些函数实际上都是Django提供的python函数,在render进行渲染的时候,这些函数会被调用。
七、自定义模板函数(simple_tag)
自定义simple_tag的步骤如下:
1.在APP目录下创建templatetags目录
(目录名不能变,Django只认识这个目录名)
2.在templatetags目录中创建python模块文件
(例如xxoo.py)
3.在xxoo.py中写模板函数
from django import template
from django.utils.safestring import mark_safe register = template.Library() @register.simple_tag
def myfunc():
return ('My function')
4.在需要使用该模板函数的html中导入并调用
{% load xxoo %}
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>Include</title>
<style>
.pg-header{
height: 48px;
background-color: seagreen;
color: lightgrey;
}
</style>
</head>
<body>
<div class="pg-header"><h3 style="display: inline">登录页面</h3></div>
<div>
<p>{{ test_string }}</p>
<p>{% myfunc %}</p>
</div>
<script src="/static/jquery-1.12.4.js"></script>
</body>
</html>
导入模板函数,使用{% load ooxx %}。
调用模板函数,使用{% myfunc %},Django会将myfunc函数的返回值放置在该位置。
5.如果自定义模板函数带参数
from django import template
from django.utils.safestring import mark_safe register = template.Library() @register.simple_tag
def myfunc(name, age):
return 'My name is : %s , %s years old.' % (name, age)
在html中调用myfunc时,需要传入对应的参数:
{% myfunc 'leo' 32 %}
当然也可以用render传递给模板的数据(test_string)作为参数:
{% myfunc test_string 32 %}
八、自定义模板函数(filter)
Django还可以定义一种模板函数,调用形式和他自己提供的{% name|lower %}相似:
@register.filter
def myStringAdd(str1, str2):
return str1 + str2
该模板函数的装饰器是 @register.filter,实现的功能是将两个字符串拼接在一起。
以以下方式在html中调用:
{{ "My name"|my_string_add:" is leo" }}
特别注意:这里的调用方式是{{}} 而不是{%%}。 这种filter形式的模板函数,最多只能传两个参数。而且,这种方式书写机制很固定,不能随便加空格,例如 {{ "My name"|my_string_add:空格" is leo" }} 就会报错。
虽然filter形式有很多局限性,但是也有他独特的应用场景:
{% if 3|int_add:5 > 7 %}
<p>不成立</p>
{% endif %}
这种filter形式的模板函数,可以直接作为if的条件。而simple_tag不行:
{% if int_add 3 5 > 7 %}
<p>不成立</p>
{% endif %}
九、XSS攻击介绍
XSS:Cross Site Scripting 跨站点脚本攻击。
XSS和SQL注入比较相似,就是以恶意脚本作为用户输入,起到控制用户浏览器的目的。
例如我们在某个论坛的评论中发送一段JS代码,这段代码会被看成是字符串在评论区显示。但如果作为JS运行的话,就会对页面产生很大的影响。
1.Django默认阻止XSS攻击
在视图函数中,我们获取到评论<textarea>标签的数据后,我们要让其显示在评论区,会通过render将该数据替换到html模板中。
def include(request):
# 我们使用一个变量来模拟从<textarea>中获取的评论数据(用html举例)
test_string = '<h1>HELLO world!My name is Leo</h1>'
return render(request, 'include.html', {'test_string': test_string})
Django默认会将test_string在html中以字符串的形式展示(避免XSS攻击)。
效果:
2.让其JS脚本或html生效
from django.utils.safestring import mark_safe def include(request):
test_string = '<h1>HELLO world!My name is Leo</h1>'
# 使用mark_safe将test_string标记为安全的,让浏览器可以执行
test_string = mark_safe(test_string)
return render(request, 'include.html', {'test_string': test_string})
效果:
十、自定义分页(一)
当我们从数据库中查询到很多数据时,例如100条。我们要将其在页面中展示,全部一起展示肯定是不合适的,所以我们要将他们分页展示。
例如博客园首页效果:
每页只显示20篇文章,下面提供一个页签进行跳转。
我们来实现一个简单的分页效果:
1.添加一个urls.py映射
urlpatterns = [
# ...这里是其他的映射关系,省略
re_path('paging/(?P<pnum>\d*)', views.paging),
]
2.添加一个对应的视图函数paging()
# 模拟数据库的全部数据
PAGES = []
for content in range(100):
PAGES.append("这是来自数据库的内容,第%s条" % content) # 视图函数,pnum为第几页
def paging(request, pnum):
# 从url中获取请求的页数,如果没有带页数,则默认为第1页
if pnum == '':
pnum = ''
pnum = int(pnum) # 每一页显示行数
per_page = 10 # 取对应页数的数据
start = (pnum - 1) * per_page
end = pnum * per_page
contents = PAGES[start:end] # 当前处于第几页
current_page = pnum
# 获取总的条目数,select count(*) from tb;
max_page = len(PAGES)
# count为商(总页数),y为余数
count, y = divmod(max_page, per_page)
if y:
count += 1 # 从后台返回<a>标签,用于构建页签按钮
from django.utils.safestring import mark_safe
paginations = []
for i in range(1, count + 1):
# 当前页的页签按钮呈现不同的样式
if i == current_page:
temp = "<a class='page_num active' href='/mgmt/paging/%s'>%s</a>" % (i, i)
else:
temp = "<a class='page_num' href='/mgmt/paging/%s'>%s</a>" % (i, i)
paginations.append(temp) # 让html返回到页面能够生效
paginations = mark_safe("".join(paginations))
return render(request, 'paging.html', {"contents": contents, "paginations": paginations})
解释:
1).先使用列表模拟从数据中获取的全部内容,一共100条。
2).从url映射中获取用户请求的内容页数,http://127.0.0.1:8000/mgmt/paging/4,就获取到数字4。
3).从PAGES中获取应该显示的10条数据
4).计算我们要显示多少页的页签按钮,这里我们是计算的一共有多少页,如果内容非常多,一般只显示一部分。类似于:
这个问题,在后面小节处理。
5).在后台生成页签按钮对应的<a>标签,并使用mark_safe处理。
6).返回所有的内容数据,和页签按钮<a>标签。
3.实现对应html模板
(按钮效果是从博客园参考的)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Paging</title>
<style>
/*分别定义页签按钮的效果,以及当前页面的页签按钮效果*/
.pagination{
font-size: 12px;
/*一般页签按钮都居中*/
/*text-align: center;*/
}
.pagination .page_num{
display: inline-block;
padding: 2px 5px;
border: 1px solid #9aafe5;
color: #2e6ab1;
margin: 0 2px;
text-decoration: none;
}
.pagination .page_num.active{
background-color: #2e6ab1;
border: 1px solid #000080;
color: #fff;
font-weight: bold;
margin: 0 2px;
padding: 2px 5px;
}
a{
outline: 0;
}
</style>
</head>
<body>
<!-- 显示内容 -->
<ul>
{% for text in contents %}
<li>
{{ text }}
</li>
{% endfor %}
</ul>
<!-- 显示页签按钮 -->
<div class="pagination">
{{ paginations }}
</div>
</body>
</html>
4.最终实现效果
十一、自定义分页(二)
1.处理页签按钮的个数
当我们的内容条数非常多时,例如1000条。我们会发现页签按钮特别多(因为是由总数计算而来)。如图:
这种情况下,我们应该只显示当前页的前后几个页签按钮即可:
# 模拟数据库的全部数据
PAGES = []
for content in range(1000):
PAGES.append("这是来自数据库的内容,第%s条" % content) # 视图函数,pnum为第几页
def paging(request, pnum):
# 从url中获取请求的页数,如果没有带页数,则默认为第1页
if pnum == '':
pnum = ''
pnum = int(pnum) # 每一页显示行数
per_page = 10 # 当前处于第几页
current_page = pnum
# 获取总的条目数,select count(*) from tb;
max_page = len(PAGES)
# count为商(总页数),y为余数
count, y = divmod(max_page, per_page)
if y:
count += 1 # 检查URL传过来的页数是否处于正常范围,如果大于总页数,则置于最大值count
if current_page > count:
current_page = count
# 取对应页数的数据
start = (current_page - 1) * per_page
end = current_page * per_page
contents = PAGES[start:end] # 从后台返回<a>标签,用于构建页签按钮
from django.utils.safestring import mark_safe bf_and_af = 5
paginations = [] if current_page - bf_and_af <= 0:
p_start = 1
p_end = 12
elif current_page + bf_and_af > count:
p_start = count - 10
p_end = count + 1
else:
p_start = current_page - bf_and_af
p_end = current_page + bf_and_af + 1 if self.page_count <= self.pager_num * 2 + 1:
p_start = 1
p_end = self.page_count + 1 # 前面增加"Prev"
prev_page = current_page - 1 if current_page > 1 else 1
temp = "<a class='page_num' href='/mgmt/paging/%s'>%s</a>" % (prev_page, "<<Prev")
paginations.append(temp) for i in range(p_start, p_end):
# 当前页的页签按钮呈现不同的样式
if i == current_page:
temp = "<a class='page_num active' href='/mgmt/paging/%s'>%s</a>" % (i, i)
else:
temp = "<a class='page_num' href='/mgmt/paging/%s'>%s</a>" % (i, i)
paginations.append(temp) # 后面添加"Next"
next_page = current_page + 1 if current_page < count else count
temp = "<a class='page_num' href='/mgmt/paging/%s'>%s</a>" % (next_page, 'Next>>')
paginations.append(temp) # 添加一个跳转到多少页
temp = """
<input style='width:30px;' type='text'/>
<input id='jump_button' type='button' value="跳转"/>
<script>
var bt = document.getElementById('jump_button');
bt.onclick=function(){
var val = this.previousElementSibling.value;
var r = /^\+?[1-9][0-9]*$/;
var flag=r.test(val);
if(flag){
location.href = '/mgmt/paging/'+val;
} };
</script>
"""
paginations.append(temp) # 让html返回到页面能够生效
paginations = mark_safe("".join(paginations)) return render(request, 'paging.html', {"contents": contents, "paginations": paginations})
解释:
1)模拟数据库中1000条内容,计算得到需要100个页签按钮
2)从url中获得用户请求的页数,判断是否超出总页数
3)获取对应页数的内容
3)计算应该显示哪几个页签按钮,前后显示多少个,处理位于两端时的异常问题
4)在页签按钮前后添加"Prev"和"Next"按钮,点击可以上下页跳转
5)在最后添加跳转按钮和输入框,使用这则表达式判断输入是否为整数
2.实现效果
十二、自定义分页(三)
1.封装分页代码(代码重用)
将分页代码封装成类:
class Paging(object):
def __init__(self, page_num, data_count, per_page_count=10, pager_num=5):
self.page_num = page_num
self.data_count = data_count
self.per_page_count = per_page_count
self.pager_num = pager_num
self.current_page = self.proc_page_num() @property
def page_count(self):
# count为商(总页数),y为余数
count, y = divmod(self.data_count, self.per_page_count)
if y:
count += 1
return count def proc_page_num(self):
# 从url中获取请求的页数,如果没有带页数,则默认为第1页
if self.page_num == '':
self.page_num = '' self.page_num = int(self.page_num)
if self.page_num > self.page_count:
return self.page_count
else:
return self.page_num @property
def start_idx(self):
# 取对应页数的数据
return (self.current_page - 1) * self.per_page_count @property
def end_idx(self):
return self.current_page * self.per_page_count def pager_str(self, base_url):
# 从后台返回<a>标签,用于构建页签按钮
from django.utils.safestring import mark_safe paginations = [] if self.current_page - self.pager_num <= 0:
p_start = 1
p_end = 12
elif self.current_page + self.pager_num > self.page_count:
p_start = self.page_count - 10
p_end = self.page_count + 1
else:
p_start = self.current_page - self.pager_num
p_end = self.current_page + self.pager_num + 1 if self.page_count <= self.pager_num * 2 + 1:
p_start = 1
p_end = self.page_count + 1 # 前面增加"Prev"
prev_page = self.current_page - 1 if self.current_page > 1 else 1
temp = "<a class='page_num' href='%s%s'>%s</a>" % (base_url, prev_page, "<<Prev")
paginations.append(temp) for i in range(p_start, p_end):
# 当前页的页签按钮呈现不同的样式
if i == self.current_page:
temp = "<a class='page_num active' href='%s%s'>%s</a>" % (base_url, i, i)
else:
temp = "<a class='page_num' href='%s%s'>%s</a>" % (base_url, i, i)
paginations.append(temp) # 后面添加"Next"
next_page = self.current_page + 1 if self.current_page < self.page_count else self.page_count
temp = "<a class='page_num' href='%s%s'>%s</a>" % (base_url, next_page, 'Next>>')
paginations.append(temp) # 添加一个跳转到多少页
temp = """
<input style='width:30px;' type='text'/>
<input id='jump_button' type='button' value="跳转"/>
<script>
var bt = document.getElementById('jump_button');
bt.onclick=function(){
var val = this.previousElementSibling.value;
var r = /^\+?[1-9][0-9]*$/;
var flag=r.test(val);
if(flag){
location.href = '%s'+val;
}
};
</script>
""" % base_url
paginations.append(temp)
pager_str = mark_safe("".join(paginations)) return pager_str
2.使用封装好的分页类
# 模拟数据库的全部数据
PAGES = []
for content in range(1000):
PAGES.append("这是来自数据库的内容,第%s条" % content) def paging(request, pnum):
# 获取一个分页对象
pager_obj = Paging(pnum, len(PAGES))
# 获取页签按钮的list
paginations = pager_obj.pager_str('/mgmt/paging/')
# 根据start和end来获取数据,对应数据库的行数
contents = PAGES[pager_obj.start_idx: pager_obj.end_idx] return render(request, 'paging.html', {"contents": contents, "paginations": paginations})