[Python自学] day-21 (1) (请求信息、html模板继承与导入、自定义模板函数、自定义分页)
一、路由映射的参数
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 = '1' 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 = '1' 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 = '1' 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})