[Python自学] day-21 (1) (请求信息、html模板继承与导入、自定义模板函数、自定义分页)

[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:UsersAdministratorAppDataRoaming
COMMONPROGRAMFILES <=====> C:Program FilesCommon Files
COMMONPROGRAMFILES(X86) <=====> C:Program Files (x86)Common Files
COMMONPROGRAMW6432 <=====> C:Program FilesCommon Files
COMPUTERNAME <=====> 8RBSKRQCXJM9LF7
COMSPEC <=====> C:Windowssystem32cmd.exe
DJANGO_SETTINGS_MODULE <=====> secondsite.settings
HOMEDRIVE <=====> C:
HOMEPATH <=====> UsersAdministrator
IDEA_INITIAL_DIRECTORY <=====> C:UsersAdministratorDesktop
LOCALAPPDATA <=====> C:UsersAdministratorAppDataLocal
LOGONSERVER <=====> \8RBSKRQCXJM9LF7
NUMBER_OF_PROCESSORS <=====> 4
ONEDRIVE <=====> C:UsersAdministratorOneDrive
OS <=====> Windows_NT
PATH <=====> D:pycharm_workspacesecondsitevenvScripts;C:Windowssystem32;C:Windows;C:WindowsSystem32Wbem;C:WindowsSystem32WindowsPowerShellv1.0;C:Program Files (x86)NVIDIA CorporationPhysXCommon;C:Program FilesNVIDIA CorporationNVIDIA NvDLISR;D:AppsGitcmd;D:Dev_appsAnaconda5.3.0Scripts;D:Dev_appsAnaconda5.3.0;C:UsersAdministratorAppDataLocalMicrosoftWindowsApps;;D:AppsPyCharm 2019.3in;;D:Dev_appsAnaconda5.3.0libsite-packages
umpy.libs;D:Dev_appsAnaconda5.3.0libsite-packages
umpy.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 FilesWindowsPowerShellModules;C:Windowssystem32WindowsPowerShellv1.0Modules;C:Program FilesIntelWired Networking
PUBLIC <=====> C:UsersPublic
PYCHARM <=====> D:AppsPyCharm 2019.3in;
PYCHARM_DISPLAY_PORT <=====> 63342
PYCHARM_HOSTED <=====> 1
PYTHONIOENCODING <=====> UTF-8
PYTHONPATH <=====> D:pycharm_workspacesecondsite;D:AppsPyCharm 2019.3pluginspythonhelperspycharm_matplotlib_backend;D:AppsPyCharm 2019.3pluginspythonhelperspycharm_display
PYTHONUNBUFFERED <=====> 1
SESSIONNAME <=====> Console
SYSTEMDRIVE <=====> C:
SYSTEMROOT <=====> C:Windows
TEMP <=====> C:UsersADMINI~1AppDataLocalTemp
TMP <=====> C:UsersADMINI~1AppDataLocalTemp
USERDOMAIN <=====> 8RBSKRQCXJM9LF7
USERDOMAIN_ROAMINGPROFILE <=====> 8RBSKRQCXJM9LF7
USERNAME <=====> Administrator
USERPROFILE <=====> C:UsersAdministrator
VIRTUAL_ENV <=====> D:pycharm_workspacesecondsitevenv
WINDIR <=====> C:Windows
_OLD_VIRTUAL_PATH <=====> C:Windowssystem32;C:Windows;C:WindowsSystem32Wbem;C:WindowsSystem32WindowsPowerShellv1.0;C:Program Files (x86)NVIDIA CorporationPhysXCommon;C:Program FilesNVIDIA CorporationNVIDIA NvDLISR;D:AppsGitcmd;D:Dev_appsAnaconda5.3.0Scripts;D:Dev_appsAnaconda5.3.0;C:UsersAdministratorAppDataLocalMicrosoftWindowsApps;;D:AppsPyCharm 2019.3in;
_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中可以验证:

[Python自学] day-21 (1) (请求信息、html模板继承与导入、自定义模板函数、自定义分页)

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>

效果如下:

[Python自学] day-21 (1) (请求信息、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})

效果如下:

[Python自学] day-21 (1) (请求信息、html模板继承与导入、自定义模板函数、自定义分页)

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})

效果如下:

[Python自学] day-21 (1) (请求信息、html模板继承与导入、自定义模板函数、自定义分页)

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

[Python自学] day-21 (1) (请求信息、html模板继承与导入、自定义模板函数、自定义分页)

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>

观察效果:

[Python自学] day-21 (1) (请求信息、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只认识这个目录名)

[Python自学] day-21 (1) (请求信息、html模板继承与导入、自定义模板函数、自定义分页)

2.在templatetags目录中创建python模块文件

(例如xxoo.py)

[Python自学] day-21 (1) (请求信息、html模板继承与导入、自定义模板函数、自定义分页)

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攻击)。

效果:

[Python自学] day-21 (1) (请求信息、html模板继承与导入、自定义模板函数、自定义分页)

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})

效果:

[Python自学] day-21 (1) (请求信息、html模板继承与导入、自定义模板函数、自定义分页)

十、自定义分页(一)

当我们从数据库中查询到很多数据时,例如100条。我们要将其在页面中展示,全部一起展示肯定是不合适的,所以我们要将他们分页展示。

例如博客园首页效果:

[Python自学] day-21 (1) (请求信息、html模板继承与导入、自定义模板函数、自定义分页)

每页只显示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).计算我们要显示多少页的页签按钮,这里我们是计算的一共有多少页,如果内容非常多,一般只显示一部分。类似于:

[Python自学] day-21 (1) (请求信息、html模板继承与导入、自定义模板函数、自定义分页)

这个问题,在后面小节处理。

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.最终实现效果

[Python自学] day-21 (1) (请求信息、html模板继承与导入、自定义模板函数、自定义分页)

十一、自定义分页(二)

1.处理页签按钮的个数

当我们的内容条数非常多时,例如1000条。我们会发现页签按钮特别多(因为是由总数计算而来)。如图:

[Python自学] day-21 (1) (请求信息、html模板继承与导入、自定义模板函数、自定义分页)

这种情况下,我们应该只显示当前页的前后几个页签按钮即可:

# 模拟数据库的全部数据
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, "&lt;&lt;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&gt;&gt;')
    paginations.append(temp)

    # 添加一个跳转到多少页
    temp = """
        <input style='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.实现效果

[Python自学] day-21 (1) (请求信息、html模板继承与导入、自定义模板函数、自定义分页)

十二、自定义分页(三)

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, "&lt;&lt;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&gt;&gt;')
        paginations.append(temp)

        # 添加一个跳转到多少页
        temp = """
                <input style='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})