Django 进阶


MVC框架 和 MTV框架

MVC,全名是Model View Controller,是软件工程中的一种软件架构模式,
把软件系统分为三个基本部分:模型(Model)、视图(View) 和 控制器(Controller),
具有耦合性低、重用性高、生命周期成本低等优点。

Django的 MTV模式

Django框架的设计模式借鉴了MVC框架的思想,也是分成三部分,来降低各个部分之间的耦合性。
Django框架的不同之处在于它拆分的三部分为:Model(模型)、Template(模板)和 View(视图),也就是MTV框架。

1
2
3
Model(模型):负责业务对象与数据库的对象(ORM)
Template(模版):负责如何把页面展示给用户
View(视图):负责业务逻辑,并在适当的时候调用Model和Template

介绍Django框架时,可以说: Django框架类似MCV模式,不同的是他的模式成为MTV模式。
此外,Django还有一个urls分发器,它的作用是将一个个URL的页面请求分发给不同的view处理,view再调用相应的Model和Template

Django 模板系统

常用语法

1
2
3
1. 只需要记两种特殊符号:{{ }}和 {% %}

2. 变量相关的用{{ }},逻辑相关的用{% %}。

变量

1
2
3
4
5
6
7
8
9
10
11
12
在Django的模板语言中按此语法使用:{{ 变量名 }}
当模版引擎遇到一个变量,它将计算这个变量,然后用结果替换掉它本身。
变量的命名包括任何字母数字以及下划线 ("_")的组合。 变量名称中不能有空格或标点符号。

# 点(.)在模板语言中有特殊的含义。当模版系统遇到点("."),它将以这样的顺序查询:
字典查询(Dictionary lookup)
属性或方法查询(Attribute or method lookup)
数字索引查询(Numeric index lookup)

# 注意事项:
如果计算结果的值是可调用的,它将被无参数的调用。调用的结果将成为模版的值。
如果使用的变量不存在, 模版系统将插入 string_if_invalid 选项的值, 它被默认设置为'' (空字符串) 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# views中的变量的例子:
# 模板语言测试例子

class Person:
def __init__(self,name,age):
self.name = name
self.age = age

def __str__(self):
return "<Object {} name>" .format(self.name)

def dream(self):
return "梦想"

def __unicode__(self):
return "打印的对象 Python2 调用这个"

def t_test(request):
name = "leo"
age = 28
name_list = ["leo", "lex", "rubin"]
name_dict = {"first_name": "leo", "last_name": "Lex"}

# 实例化类
p1 = Person("雷尼",18)
p2 = Person("加纳",18)

# 列表类
p_list = [p1,p2]


return render(
request,
't_test.html',
{
"name": name,
"age": age,
"name_list": name_list,
"name_dict": name_dict,
"person":p1,
"p_list":p_list,
}
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!--模板中支持的写法:-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板语言测试</title>
</head>
<body>

<p>传值:字符、数字、列表、字典</p>
<p>{{ name }}</p>
<p>{{ age }}</p>
<p>{{ name_list }}</p>
<p>{{ name_dict }}</p>
{#不传值不显示#}
<p>传送不存在的变量: {{ null }}</p>

<hr>
<p>传值:类对象,类列表</p>
<p>{{ person }}</p>
<p>{{ person.name }}</p>
{#模板语言里,方法不要加括号(),只能调用不带参数的方法#}
<p>{{ person.dream }}</p>

<p>{{ p_list.0 }}</p>
<p>{{ p_list.1.name }}</p>
<p>{{ p_list.1.dream }}</p>

<hr>
<p>字典</p>
<!--如果字典中有一个key叫做items,字典key的优先级,要大于方法items的优先级-->
<p>{{ name_dict.first_name }}</p>
<p>{{ name_dict.last_name }}</p>
{% for k,v in name_dict.items %}
{{ k }} : {{ v }}
{% endfor %}



</body>
</html>

Filters(过滤器)

1
2
3
4
在Django的模板语言中,通过使用 过滤器 来改变变量的显示。
过滤器的语法: {{ value|filter_name:参数 }}
使用管道符"|"来应用过滤器。
例如:{{ name|lower }}会将name变量应用lower过滤器之后再显示它的值。lower在这里的作用是将文本全都变成小写。
1
2
3
4
5
6
注意事项:

过滤器支持“链式”操作。即一个过滤器的输出作为另一个过滤器的输入。
过滤器可以接受参数,例如:{{ sss|truncatewords:30 }},这将显示sss的前30个词。
过滤器参数包含空格的话,必须用引号包裹起来。比如使用逗号和空格去连接一个列表中的元素,如:{{ list|join:', ' }}
'|'左右没有空格没有空格没有空格

Django的模板语言中提供了大约六十个内置过滤器。

default

如果一个变量是false或者为空,使用给定的默认值。 否则,使用变量的值。

1
2
<!--如果value没有传值或者值为空的话就显示nothing-->
<p>传送不存在的变量: {{ null|default:"没有传值" }}</p>

length

返回值的长度,作用于字符串和列表。

1
2
3
4
<!--返回value的长度,如 value=['a', 'b', 'c', 'd']的话,就显示4-->
<p>{{ name|length }}</p>
<p>{{ name_list|length }}</p>
<p>{{ name_dict|length }}</p>

filesizeformat

将值格式化为一个 “人类可读的” 文件尺寸 (例如 ‘13 KB’, ‘4.1 MB’, ‘102 bytes’, 等等)。例如:

1
2
<!-- 如果 value 是 123456789,输出将会是 117.7 MB。-->
<p>{{ file_size|filesizeformat }}</p>

slice

切片

1
{{value|slice:"2:-1"}}

date

格式化

1
2
{{ value|date:"Y-m-d H:i:s"}}
<p>{{ now_time|date:"Y-m-d H:i:s" }}</p>

safe

1
2
3
4
5
Django的模板中会对HTML标签和JS等语法标签进行自动转义,原因显而易见,这样是为了安全。
但是有的时候我们可能不希望这些HTML元素被转义,比如我们做一个内容管理系统,后台添加的文章中是经过修饰的,
这些修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本,如果自动转义的话显示的就是保护HTML标签的源文件。
为了在Django中关闭HTML的自动转义有两种方式,
如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。
1
2
3
4
{#多用于文章评论时,放入的html连接或者其他恶意如死循环代码XSS攻击,跨站脚本攻击,做安全效验转义#}
{#评论一定不要加save,就让其成为文本即可#}
<p>a标签 {{ a_html|safe }}</p>
<p>script签: {{ script_html }}</p>

truncatechars

truncatechars 如果字符串字符多于指定的字符数量,那么会被截断。
截断的字符串将以可翻译的省略号序列(“…”)结尾。
参数:截断的字符数

1
<p>{{ p_str|truncatechars:5 }}</p>

自定义filter

1
2
3
4
5
自定义过滤器只是带有一个或两个参数的Python函数:

变量(输入)的值 - -不一定是一个字符串
参数的值 - 这可以有一个默认值,或完全省略
例如,在过滤器{{var | foo:'bar'}}中,过滤器foo将传递变量var和参数“bar”。
  1. 自定义filter代码文件摆放位置:

    1
    2
    3
    4
    5
    6
    7
    app01/
    __init__.py
    models.py
    templatetags/ # 在app01下面新建一个package package:templatetags
    __init__.py
    app01_filters.py # 建一个存放自定义filter的文件
    views.py
  2. 编写自定义filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from django import template
# 注册器
register = template.Library()


@register.filter(name="come_on")
def come_on(arg):
return "{} you got it!" .format(arg)

@register.filter(name="addstr")
def addstr(arg,arg2):
"""
:param arg: 第一个参数永远是管道符|,前面的那个变量
:param arg2: 后面的参数,冒号后面引号里面的变量
:return:
"""
return "{} {} you got it!" .format(arg,arg2)
  1. 使用自定义filter
    需要重启项目加载
1
2
3
4
5
6
7
8
9
<hr>
{# 自定义filter #}
{# 先导入我们自定义filter那个文件 #}
{# 需要重启项目加载 #}
{% load app01_filters %}

{# 使用我们自定义的filter #}
<p>{{ name|come_on }}</p>
<p>{{ name|addstr:"加油兄弟" }}</p>

Tags

for循环

普通for循环

1
2
3
4
5
<ul>
{% for user in user_list %}
<li>{{ user.name }}</li>
{% endfor %}
</ul>

for循环可用的一些参数:

1
2
3
4
5
<p>for 循环
{% for name in name_list %}
<li>{{ forloop.counter }} {{ name }}</li>
{% endfor %}
</p>

for … empty

1
2
3
4
{% for book in book_list %}
{% empty %}
<p>暂时没有数据</p>
{% endfor %}

双层for循环

1
2
3
4
5
6
7
8
9
10
<p>双层for循环</p>
{% for li in name2_list %}
{{ li }}
{{ forloop.counter }}
{% for name in li %}
{{ name }}
{{ forloop.counter }}
{{ forloop.parentloop.counter }}
{% endfor %}
{% endfor %}

if判断

if,elif和else
if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断。

1
2
3
4
5
6
7
{% if user_list %}
用户人数:{{ user_list|length }}
{% elif black_list %}
黑名单数:{{ black_list|length }}
{% else %}
没有用户
{% endif %}
1
2
3
4
5
6
7
8
<p>if ... elif ... else</p>
{% if p3 %}
<p>p3:{{ p3 }}</p>
{% elif p2 %}
<p>p2:{{ p2 }}</p>
{% else %}
<p>什么人都没有</p>
{% endif %}
1
2
3
4
5
{% if name_list|length >= 3 %}
<p>需要两辆车</p>
{% else %}
<p>一台车</p>
{% endif %}

with
定义一个中间变量,多用于给一个复杂的变量起别名,注意等号左右不要加空格

1
2
3
4
5
6
<p>with</p>
{{ name_list.1.1 }}

{% with k=name_list.1.1 %}
{{ k }}
{% endwith %}

注释 和 注意事项

1
{# ...注释... #}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. Django的模板语言不支持连续判断,即不支持以下写法:
{% if a > b > c %}
...
{% endif %}


2. Django的模板语言中属性的优先级大于方法
def xx(request):
d = {"a": 1, "b": 2, "c": 3, "items": "100"}
return render(request, "xx.html", {"data": d})
如上,我们在使用render方法渲染一个页面的时候,
传的字典d有一个key是items并且还有默认的 d.items() 方法,此时在模板语言中:

{{ data.items }}
默认会取d的items key的值。

母版和继承

为什么要有母版和继承

html页面有重复的时候,把他们提取出来放到一个单独的html文件(比如:导航条和左侧菜单),
把多个页面公用的部分提取出来,放在一个母版页面里面,其他的页面只需要 继承 母版就可以了

具体使用的步骤

  1. 把公用的HTML部分提取出来,在项目目录templates中新建一个 base.html 基础页面,将内容放入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<!DOCTYPE html>
<!-- saved from url=(0042)https://v3.bootcss.com/examples/dashboard/ -->
<html lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="https://v3.bootcss.com/favicon.ico">

<title>图书管理系统</title>

<!-- Bootstrap core CSS -->
<link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet">

<!-- Custom styles for this template -->
<link href="/static/dashboard.css" rel="stylesheet">
<link rel="stylesheet" href="/static/fontawesome/css/font-awesome.min.css">

{#专门替换css文件的块,别的页面没有用到#}
{% block page_css %}

{% endblock %}

</head>

<body>
{#{% include "nav.html" %}#}

<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
{#只要传来的值有all_book或者author_list、publisher_list 我就给这个li加成选中状态#}
<li class= "{% block publisher_class %}{% endblock %}"> <a href="/publisher_list/">出版社列表页</a></li>
<li class= "{% block book_class %}{% endblock %}"> <a href="/book_list/">书籍列表</a></li>
<li class= "{% block author_class %}{% endblock %}"> <a href="/author_list/">作者列表</a></li>
</ul>
</div>

{# 需要替换的部分,每个页面内容不同 #}
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
{# 这里是每个页面内容不同 #}
{% block page-main %}

{% endblock %}
</div>
</div>
</div>


<div class="modal fade" tabindex="-1" role="dialog" id="myModal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<h4 class="modal-title">用户信息</h4>
</div>
<div class="modal-body">
<form class="form-horizontal">
<div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">邮箱</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="inputEmail3" placeholder="Email">
</div>
</div>
<div class="form-group">
<label for="inputPassword3" class="col-sm-2 control-label">密码</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword3" placeholder="Password">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary">保存</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->

<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="/static/jquery-3.3.1.js"></script>
<script src="/static/bootstrap/js/bootstrap.min.js"></script>

{% block page_js %}

{% endblock %}
</body>
</html>
  1. 在base.html 中,通过定义block,把每个页面不同的部分区分出来:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{#专门替换css文件的块,别的页面没有用到#}
{% block page_css %}

{% endblock %}

{# 需要替换的部分,每个页面内容不同 #}
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
{# 这里是每个页面内容不同 #}
{% block page-main %}

{#专门替换js文件的块,别的页面没有用到#}
{% endblock %}

{% block page_js %}

{% endblock %}
  1. 在具体的页面中,先继承母版
1
{% extends 'base.html' %}
  1. 然后通过block名字去指定替换母版中相应的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
{% extends 'base.html' %}
{% block page-main %}
<h1 class="page-header">出版社管理页面</h1>

<div class="panel panel-primary">
<!-- Default panel contents -->
<div class="panel-heading">出版社列表 <i class="fa fa-thumb-tack pull-right"></i></div>
<div class="panel-body">
<div class="row" style="margin-bottom: 15px">
<div class="col-md-4">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search for...">
<span class="input-group-btn">
<button class="btn btn-default" type="button">搜索</button>
</span>
</div><!-- /input-group -->
</div><!-- /.col-md-4 -->
<div class="col-md-1 pull-right">
<button class="btn btn-success" data-toggle="modal" data-target="#myModal">新增</button>
</div>

</div><!-- /.row -->

<table class="table table-bordered">
<thead>
<tr>
<th>#</th>
<th>id</th>
<th>出版社名称</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for publisher in publisher_list %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ publisher.id }}</td>
<td>{{ publisher.name }}</td>
<td>
<a class="btn btn-danger" href="/del_publisher/?id={{ publisher.id }}">删除</a>
<a class="btn btn-danger" href="/del_publisher/{{ publisher.id }}/">删除2</a>
<a class="btn btn-info" href="/edit_publisher/?id={{ publisher.id }}">编辑</a>
<a class="btn btn-info" href="{% url 'edit_publisher' publisher_id %}">编辑</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>

<nav aria-label="Page navigation" class="text-right">
<ul class="pagination">
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<li><a href="#">1</a></li>
<li><a href="#">2</a></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
{% endblock %}

导航栏选中的判断方法

1
2
3
4
5
6
7
8
9
<!--方法1:判断传进来的变量是否有值-->
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
{#只要传来的值有all_book或者author_list、publisher_list 我就给这个li加成选中状态#}
<li {% if publisher_list %} class = "active" {% endif %}><a href="/publisher_list/">出版社列表页</a></li>
<li {% if all_book %} class = "active" {% endif %}><a href="/book_list/">书籍列表</a></li>
<li {% if author_list %} class = "active" {% endif %}><a href="/author_list/">作者列表</a></li>
</ul>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--方法2:使用block-->
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
{#只要传来的值有all_book或者author_list、publisher_list 我就给这个li加成选中状态#}
<li class= "{% block publisher_class %}{% endblock %}"> <a href="/publisher_list/">出版社列表页</a></li>
<li class= "{% block book_class %}{% endblock %}"> <a href="/book_list/">书籍列表</a></li>
<li class= "{% block author_class %}{% endblock %}"> <a href="/author_list/">作者列表</a></li>
</ul>
</div>

<!--在相应的页面 添加块-->
{% block book_class %}
active
{% endblock %}

专门替换CSS样式的块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 定义一个book_list_only.css文件
body{
background-color: lawngreen;
}

# 只有在指定的页面 用到这个样式
{#专门替换css文件的块,别的页面没有用到#}
<head>
...
{% block page_css %}

{% endblock %}
</head>

# book_list2.html替换
{% block page_css %}
<link rel="stylesheet" href="/static/book_list_only.css">
{% endblock %}

专门替换JS文件的块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 定义一个author_list_only.js 文件
alert('这是作者页面')

# 只有在指定的页面 用到这个JS
</body>
...
<script src="/static/jquery-3.3.1.js"></script>
<script src="/static/bootstrap/js/bootstrap.min.js"></script>
{% block page_js %}

{% endblock %}
</body>
</html>

# 引用
{% block page_js %}
<script src="/static/author_list_only.js"></script>
{% endblock %}

使用母版和继承的注意事项

1
2
3
4
1. {% extends 'base.html' %}	--> 母版文件:base.html要加引号,
2. {% extends 'base.html' %} --> 必须放在子页面的第一行
3. 可以在base.html中定义很多block,通常我们会额外定义page_cs和page_js,这两个块
4. views.py相应的函数中,返回的是对应的子页面文件不是不是不是base.html

组件

可以将常用的页面内容如导航条,页尾信息等组件保存在单独的文件中,然后在需要使用的地方按如下语法导入即可。
什么时候用组件:重复的代码,包装成一个独立的小html文件。

  1. 单独提取导航条 成为小组件,把导航条单独写成一个nav.html
    把base.html中的nav剪切分割到nav.html,里面的内容只放导航条的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="https://v3.bootcss.com/examples/dashboard/#">BMS</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="https://v3.bootcss.com/examples/dashboard/#">Dashboard</a></li>
<li><a href="https://v3.bootcss.com/examples/dashboard/#">Settings</a></li>
<li><a href="https://v3.bootcss.com/examples/dashboard/#">Profile</a></li>
<li><a href="https://v3.bootcss.com/examples/dashboard/#">Help</a></li>
</ul>
<form class="navbar-form navbar-right">
<input type="text" class="form-control" placeholder="Search...">
</form>
</div>
</div>
</nav>
  1. 如何使用
    在base.html中导入
1
2
<body>
{% include "nav.html" %}
  1. 在add_book.html中添加导航条
1
2
3
4
5
{#加导航条#}
{% include "nav.html" %}

<div class="container" style="margin-top: 100px">
..

静态文件路径的灵活写法

  1. 利用static方法帮我拼接静文件的路径
    多用于base.html 母版中的css和js文件引入
1
2
3
{% load static %}
<link href = "{% static 'bootstrap/css/bootstrap.min.css' %}" rel="stylesheet"
<script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
  1. 利用内置的get_static_prefix获取静态文件路径的别名,我们自行拼接路径
1
2
{% load static %}
<link href="{% get_static_prefix %}bootstrap/css/bootstrap.min.css" rel=stylesheet>
  1. as 语法(一个路径多次用到,可以使用as保存到一个变量,后面直接使用变量代替具体路径

自定义的simple_tag

比filter高级一点点,它可以接收的参数大于2

1
2
3
4
5
6
7
from django import template

register = template.Library()

@register.simple_tag(name="my_sum")
def my_sum(arg,arg2,arg3):
return "{} {} {}" .format(arg,arg2,arg3)

自定义的inclusion_tag

用来返回一段html代码(示例:返回ul标签)

  1. 定义阶段:
    在app下新建:templatetags 目录(注意是Python包)
    新建python文件 my_inclusion.py
1
2
3
4
5
6
7
8
from django import template
# 生成一个注册的实例,必须写成是register
register = template.Library()

@register.inclusion_tag("ul.html")def show_ul(num):
num = 1 if num < 1 else int(num)
data = ["第{:0>3}" .format(i) for i in range(1,num+1)]
return {"data":data}
  1. 编辑要用到的ul.html

    1
    2
    3
    4
    5
    <ul>
    {% for ret in data %}
    <li> {{ ret }}</li>
    {% endfor %}
    </ul>
  2. 调用阶段:

1
2
3
<!--# 要记得重启-->
{% load my_inclusion %}
{% show_ul 5 %}

Django 视图系统

什么是视图系统

  1. 一个视图函数(类),简称视图,是一个简单的Python 函数(类),它接受Web请求并且返回Web响应。
  2. 视图view(接收请求,返回响应这一部分就叫视图,也可以叫处理函数)

CBV 和 FBV

  1. CBV( class base view 基于类的视图 )和FBV( function base view 基于函数的视图 )

例如将出版社的添加方法,修改为CBV 基于类的视图:
views.py添加:

1
2
3
4
5
6
7
8
9
10
11
12
# CBV 出版社添加
from django.views import View
class AddPublisher(View):
def get(self,request):
# 用户第一次来,我给他返回一个用来填写的HTML页面
return render(request, 'add_publisher.html')

def post(self,request):
if request.method == "POST":
publisher_name = request.POST.get("name")
models.Publisher.objects.create(name=publisher_name)
return redirect('/publisher_list/')
1
2
3
4
5
6
7
8
9
# FBV 出版社添加
# 出版社添加
def add_publisher(request):
if request.method == "POST":
publisher_name = request.POST.get("name")
models.Publisher.objects.create(name=publisher_name)
return redirect('/publisher_list/')

return render(request, 'add_publisher.html')
  • 使用CBV时,urls.py中也做对应的修改:
1
2
3
# urls.py中
# url(r'^add_publisher/', views.add_publisher),
url(r'^add_publisher/', views.AddPublisher.as_view()),

Request 对象 和 Response 对象

Request 对象

当一个页面被请求时,Django就会创建一个包含本次请求原信息的HttpRequest对象。
Django会将这个对象自动传递给响应的视图函数,一般视图函数约定俗成地使用 request 参数承接这个对象。

请求相关的常用值:

1
2
3
4
5
path_info     返回用户访问url,不包括域名
method 请求中使用的HTTP方法的字符串表示,全大写表示。
GET 包含所有HTTP GET参数的类字典对象
POST 包含所有HTTP POST参数的类字典对象
body 请求体,byte类型 request.POST的数据就是从body里面提取到的

常用属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
request.method      # 获取请求的方法(GET,POST等)

request.GET # 获取URL里面的参数
127.0.0.1:8000/edit_book/?id=1&name=python
request.GET --> {"id":1,"name":"python"}
request.GET.get("id")

request.POST # 用来获取POST提交过来的数据
request.POST.get("book_title")

request.path_info # 获取用户请求的路径(不包含IP端口和URL参数)

request.body # post提交中会有数据

注意:键值对的值是多个的时候,比如checkbox类型的input标签,select标签,需要用:

1
request.POST.getlist("hobby")

上传文件示例

  • urls.py
1
2
# 上传文件
url(r'^upload_files/', views.upload_files),
  • views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 处理上传文件的函数
def upload_files(request):
"""
1. 保存上传文件前,数据需要存放在某个位置。
默认当上传文件小于2.5M时,django会将上传文件的全部内容读进内存。
2. 从内存读取一次,写磁盘一次。
在f.chunks()上循环而不是用read()保证大文件不会大量使用你的系统内存。
3. 但当上传文件很大时,django会把上传文件写到临时文件中,然后存放到系统临时文件夹中。
:param request:
:return:
"""
if request.method == "POST":
# Request.FILES
# 从请求的FILES中获取上传文件的文件名,file为页面上type=files类型input的name属性值
filename = request.FILES["file1"].name
# 在项目目录下新建一个文件
with open(filename,"wb") as f:
# 从上传的文件对象中一点一点读
for chunk in request.FILES["file1"].chunks():
# 写入本地文件
f.write(chunk)

return HttpResponse('{} 上传完成' .format(filename))

return render(request,'upload_files.html')
  • upload_files.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/upload_files/" method="post" enctype="multipart/form-data">
{# FILES 中的每个键为<input type="file" name="" /> 中的name,值则为对应的数据。#}
{# 注意:FILES 只有在请求的方法为POST 且提交的<form> 带有enctype="multipart/form-data" 的情况下才会#}
{# 包含数据。否则,FILES 将为一个空的类似于字典的对象。#}
<p><input type="file" name="file1"></p>
<p><input type="file" name="file2"></p>
<p><input type="submit" value="提交"></p>
</form>

</body>
</html>

Response 对象

与由Django自动创建的HttpRequest对象相比,HttpResponse对象是我们的职责范围了。
我们写的每个视图都需要实例化,填充和返回一个HttpResponse。
HttpResponse类位于django.http模块中。

  • 基础必备三件套
1
2
3
HttpResponse        -->  返回字符串内容
render --> 返回一个页面
redirect --> 返回一个重定向告诉浏览器去访问另外的网址

JsonResponse 对象

JsonResponse是HttpResponse的子类,专门用来生成JSON编码的响应。

urls.py

1
2
# JsonResponse对象
url(r'^json_test/', views.json_test),

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# json_test
def json_test(request):
import json
data = {"name": "夜雨", "age": 18}
data2 = [1,2,3,4,5,"夜雨"]

# 默认只能传递字典类型
# json_data = json.dumps(data,ensure_ascii=False) # 把data序列化成json格式的字符串
# return HttpResponse(json_data)

# django帮我们封装的 专门用来返回JSON格式字符串响应方法
# return JsonResponse(data,json_dumps_params={'ensure_ascii':False})

# django通常只能接收字典格式的,单独返回列表类型数据,需要加上safe=False
return JsonResponse(data2,safe=False,json_dumps_params={'ensure_ascii':False})

Django 路由系统

URL配置(URLconf)就像Django 所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表。
你就是以这种方式告诉Django,对于这个URL调用这段代码,对于那个URL调用那段代码。

URLconf配置

  • 基本格式:
1
2
3
4
5
from django.conf.urls import url

urlpatterns = [
url(正则表达式, views视图函数,参数,别名),
]
  • 参数说明:
1
2
3
4
正则表达式:一个正则表达式字符串
views视图函数:一个可调用对象,通常为一个视图函数或一个指定视图函数路径的字符串
参数:可选的要传递给视图函数的默认参数(字典形式)
别名:一个可选的name参数

正则表达式详解

  • 基本配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# urls.py:
# JsonResponse对象
url(r'^json_test/$', views.json_test),
# http://127.0.0.1:8000/json_test/1/2/3 无法访问
# http://127.0.0.1:8000/json_test/?id=1 可以访问,因为?id是参数

# 路由系统
# book/2-4位数字
# http://127.0.0.1:8000/book/12/
# url(r'^book/[0-9]{2,4}/$', views.book),

# 分组匹配()
url(r'^book/([0-9]{2,4})/([a-zA-Z]{2})/$', views.book),
# http://127.0.0.1:8000/book/12/ab/
# book() takes 1 positional argument but 3 were given
# 如果我们使用分组匹配,会将()分组匹配里面的值,当做参数发送给视图函数,函数中除了request,还需要接收()里面的参数
# 这种方式可以代替?id=1,地址栏传值

# views.py:
# 分组匹配,位置参数
def book(request,arg1,arg2):
print('arg1:',arg1) # arg1: 12
print('arg2:',arg2) # arg2: ab
return HttpResponse("分组匹配")
  • 注意事项:
1
2
3
4
urlpatterns中的元素按照书写顺序从上往下逐一匹配正则表达式,一旦匹配成功则不再继续。
若要从URL中捕获一个值,只需要在它周围放置一对圆括号(分组匹配)。
不需要添加一个前导的反斜杠,因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。
每个正则表达式前面的'r' 是可选的但是建议加上。

分组匹配

分组匹配:给视图函数传递位置参数
分组匹配和分组命名匹配不能混用,看需求使用哪一种

1
2
3
4
5
6
7
# urls.py
# 分组匹配()
url(r'^book/([0-9]{2,4})/([a-zA-Z]{2})/$', views.book),
# http://127.0.0.1:8000/book/12/ab/
# book() takes 1 positional argument but 3 were given
# 如果我们使用分组匹配,会将()分组匹配里面的值,当做参数发送给视图函数,函数中除了request,还需要接收()里面的参数
# 这种方式可以代替?id=1,地址栏传值

1
2
3
4
5
6
7
8
9
10
11
12
13
# views.py
# 分组匹配,位置参数
def book(request,arg1,arg2):
print('arg1:',arg1) # arg1: 1000
print('arg2:',arg2) # arg2: py
return HttpResponse("分组匹配")

# 分组匹配,args接收参数
def book(request,*args):
print(args) # args接收返回元祖('1000', 'py')
print(args[0])
print(args[1])
return HttpResponse("分组匹配")

分组命名匹配

分组命名:给视图函数传关键字参数

1
2
3
4
5
6
# urls.py
# 分组命名匹配
url(r'^book/(?P<year>[0-9]{2,4})/(?P<title>[a-zA-Z]{2})/$', views.book),
# 会将year和title当做关键字参数
# 分组匹配相当于 位置传参
# 分组命名相当于 关键字传参
1
2
3
4
5
6
7
8
9
10
11
12
13
14
views.py
# 分组命名匹配,关键字参数
def book(request,year,title):
print('year:',year)
print('title:',title)
return HttpResponse("分组命名匹配")

# 分组命名匹配,**kwargs接收参数
def book(request,**kwargs):
print(kwargs) # {'year': '1000', 'title': 'py'}
print(kwargs['year']) # 1000
print(type(kwargs['year'])) # <class 'str'> 捕获的参数永远是字符串类型
print(kwargs['title']) # py
return HttpResponse("分组命名匹配")

设置默认值

  1. 如果用户访问blog/ 那么我们默认返回num1,也就是blog的第一页
  2. 如果用户到这page100访问,那么我们直接返回对应的页面
  3. 返回的是blog/page具体第几页的blog,num=捕获的值,而不是num=1
1
2
3
4
5
6
7
8
9
10
11
12
13
# urls.py中
from django.conf.urls import url

from . import views

urlpatterns = [
url(r'^blog/$', views.page),
url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
]

# views.py中,可以为num指定默认值
def page(request, num="1"):
pass

分组匹配的使用

出版社删除 改为分组匹配

1
2
3
4
# urls.py
url(r'^del_publisher/$', views.del_publisher),
#url分组匹配,括号里的值会传给函数
url(r'^del_publisher2/([0-9]+)/$', views.del_publisher2),
1
2
3
4
5
6
7
8
# views.py
# url分组匹配传参形式-删除出版社
def del_publisher2(request,del_id):
# url(r'^del_publisher/([0-9]+)', views.del_publisher2),
# 至少接收一个数字作为del_id
# 不用再从request.GET取值
models.Publisher.objects.get(id=del_id).delete()
return redirect("/publisher_list/")
1
<a href="/del_publisher2/{{ publisher.id }}/">分组匹配删除</a>

出版社修改 改为分组匹配

1
2
3
4
# urls.py
url(r'^edit_publisher/$', views.edit_publisher),
# url分组匹配,括号里的值会传给函数
url(r'^edit_publisher2/([0-9]+)/$', views.edit_publisher2),
1
2
3
4
5
6
7
8
9
10
11
12
13
# views.py
# url分组匹配传参形式-编辑出版社
def edit_publisher2(request,edit_id):
if request.method == "POST":
edit_id = request.POST.get("id")
edit_name = request.POST.get("name")
edit_obj = models.Publisher.objects.get(id=edit_id)
edit_obj.name = edit_name
edit_obj.save()
return redirect("/publisher_list/")
# 不用再从request.GET取值edit_id
edit_obj = models.Publisher.objects.get(id=edit_id)
return render(request, "edit_publisher.html", {"edit_obj": edit_obj})
1
<a href="/edit_publisher2/{{ publisher.id }}/">分组匹配编辑</a>

include 其他的URLconfs

  1. 我们现在所有的路由指向都是写在一个配置文件 project/urls.py
  2. 当我们的app有很多的时候,就需要分组管理

include 分流urls

  1. 执行命令
1
D:\MyProject\test_0922>python manage.py startapp app02
  1. 在settings中添加配置,告诉Django我们添加了新的app
1
2
3
4
5
6
7
8
9
10
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
'app02.apps.App02Config',
]
  1. 在app01中新建ursl.py

  1. 编辑2级路由app01/urls.py
1
2
3
4
5
6
7
8
9
10
from django.conf.urls import url
from app01 import views

urlpatterns = [
# 分组匹配()
url(r'^book/([0-9]{2,4})/([a-zA-Z]{2})/$', views.book),

# index
url(r'^$', views.publisher_list),
]
  1. 编辑1级路由project/urls.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.conf.urls import url,include
from django.contrib import admin
from app01 import views,urls

urlpatterns = [
url(r'^admin/', admin.site.urls),

# http://127.0.0.1:8000/app01/publisher_list/
# 要记得导入include
# 不要忘记修改对应的路径如:base下的
# <a href="/app01/publisher_list/">
# 所有以app01开头的url都交给app01/urls.py处理
url(r'^app01/',include(urls)),
]

include app02.urls

1
2
3
4
5
6
7
# app02.urls
from django.conf.urls import url
from app02 import views

urlpatterns = [
url(r'^home/$', views.home),
]
1
2
3
# app02.views
def home(request):
return HttpResponse('app02/home')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# project.urls
# 要注意urls会重名,导入app02的urls后as别名
from django.conf.urls import url,include
from django.contrib import admin
from app01 import views,urls as app01_urls
from app02 import urls as app02_urls

urlpatterns = [
url(r'^admin/', admin.site.urls),

# http://127.0.0.1:8000/app01/publisher_list/
url(r'^app01/',include(urls)),

# http://127.0.0.1:8000/app02/home/
url(r'^app02/',include(app02_urls)),
]

命名URL和URL反向解析

  1. Templates里面创建多级目录
  • templates
    • car
      • home.html
    • house
      • home.html
  1. 编写urls.py 对应各自的路由:
1
2
3
# project/urls.py
url(r'^car/',include(app01_urls)),
url(r'^house/',include(app02_urls))
1
2
3
4
# app01/urls.py
urlpatterns = [
url(r'^home/$', views.home),
]
1
2
3
4
# app02/urls.py
urlpatterns = [
url(r'^home/$', views.home),
]
  1. 编写views.py
1
2
def home(request):
return render(request,'car/home.html')
1
2
def home(request):
return render(request,'house/home.html')
  1. 编写html
1
2
3
<h1>app01/home 这是卖车的首页</h1>
<p>友情链接</p>
<a href="/house/home">想买房子点我</a>
1
2
3
<h1>app02/home 这是卖房子的首页</h1>
<p>友情链接</p>
<a href="/car/home">想买车点我</a>
  • 现在我们有了两个app并且都各自用有自己的路由和视图函数,返回各自的首页,并且在页面中都由a标签跳转
  • 这个时候如果我们的urls的路径被修改了,相应的跳转也就无法访问,这是由于url被固定了
1
url(r'^carcar/',include(app01_urls)),

使用url反向解析来获取页面

  1. 一级路由解析

路由侧添加了别名:name=’别名’,html就可以动态调用url=’别名’
此功能可以防止url被修改,导致页面无法找到url路径

1
2
3
4
5
# project.urls
# JsonResponse对象
# url(r'^json_test/$', views.json_test,name='json_test'),
# 如果我们修改页面,跳转将失败,这是由于url被固定了
url(r'^json_data111/$', views.json_test,name='json_test'),
1
2
3
4
5
6
7
# house/home.html
<p>返回JsonResponse页面
# <a href="/json_data/">Json_test的反向解析</a>
# /json_data/不能被固定写死,通过别名去动态查询url
# url会去整个项目的urls去找别名是json_test的url
<a href="{% url 'json_test' %}">Json_test的反向解析</a>
</p>
  1. 二级路由,通过别名反向解析找到url
1
2
3
# project/urls.py
# url(r'^car/',include(app01_urls)),
url(r'^car999/',include(app01_urls)),
1
2
3
4
# app01/urls.py
urlpatterns = [
url(r'^home/$', views.home,name='car_home'),
]
1
<a href="{% url 'car_home' %}">想买车点我</a>

使用url反向解析,在视图views中跳转

  1. 一般views中的跳转使用redirect
1
2
# project/urls
url(r'^json_data3/$', views.json_test,name='json_test'),
1
2
def home(request):
return redirect('/json_data3/')
  1. 根据别名找到跳转的url
    1
    2
    3
    4
    5
    6
    7

    def home(request):
    from django.urls import reverse
    # reverse 反向解析,根据别名找到跳转的url
    # 会去urls里面找到对应的别名
    redirect_url = reverse("json_test")
    return redirect(redirect_url)
  • 反向解析URL
  1. 本质上就是给url匹配模式起别名,然后通过别名拿到具体的url地址
  • 如何使用
  1. 在url匹配模式中,定义name=’别名’
  2. 在模板语言里使用 url ‘别名’
  3. 在视图函数里使用
1
2
from django.urls import reverse
redirect_url = reverse("json_test") # 得到 URL

使用url反向解析,加上参数跳转

  1. 当url里面含有参数的时候,别名如何解析呢?
1
2
3
app01/urls
# 分组匹配()
url(r'^book/([0-9]{2,4})/([a-zA-Z]{2})/$', views.book,name='book'),
1
2
3
4
5
6
7
8
9
10
11
12
# 在反转的时候带参数
def home(request):
from django.urls import reverse
# reverse 反向解析,根据别名找到跳转的url
# 会去urls里面找到对应的别名
# redirect_url = reverse("book",kwargs={"year":2018,"title":"py"})
redirect_url = reverse("book",args=[2018,"py"])
print(redirect_url) # /car999/book/2018/py/

# return redirect(redirect_url)

return render(request,'car/home.html')

2.在模板语言中,测试带参数的url反向解析

1
2
3
4
5
<p>测试带参数的url反向解析:
<a href="{% url 'book' 2018 "py" %}">book点我</a>
</p>

# http://127.0.0.1:8000/car999/book/2018/py/

把编辑按钮的连接改成反向解析URL形式

  1. 适用于分组匹配
    1
    2
    # url分组匹配,括号里的值会传给函数
    url(r'^edit_publisher2/([0-9]+)/$', views.edit_publisher2,name="edit_pub"),
1
<a href="{% url 'edit_pub' publisher.id %}">分组匹配编辑</a>

命名空间模式

  1. 当app多了,urls中的别名也可能会出现重复
1
2
3
# project/urls.py
url(r'^car999/',include(app01_urls,namespace="car")),
url(r'^house/',include(app02_urls,namespace="house")),
1
2
3
4
# app02/urls.py
urlpatterns = [
url(r'^home/$', views.home,name='home'),
]
1
2
<a href="{% url 'car:car_home' %}">想买车点我</a>
<a href="{% url 'house:home' %}">想买房子点我</a>

通过URL分组和反射 将方法三合一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# models
from django.db import models

# Create your models here.

class Publisher(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=64,null=False,unique=True)

def __str__(self):
return '<{}出版社是:>' .format(self.name)

class Book(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=64,null=False)
publisher = models.ForeignKey(to='Publisher')

def __str__(self):
return '<{}书名是:>' .format(self.title)

class Author(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=64,null=False)
book = models.ManyToManyField(to='Book')

def __str__(self):
return '<{}作家姓名:>' .format(self.name)
1
2
3
4
# urls.py
# url(r'^delete/表名/id值',views.delete),
# http://127.0.0.1:8000/delete/book/10/
url(r'^delete/([a-zA-Z]+)/(\d+)/$',views.delete),
1
2
3
4
5
6
7
8
import re

r = re.compile(r'^delete/([a-zA-Z]+)/(\d+)/$')
ret = r.match("delete/author/10/")

print(ret.groups()) # ('author', '10')
print(ret.group(1)) # author
print(ret.group(2)) # 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 正则表达式的匹配参数:
# table_name = ([a-zA-Z]+)
# del_id = (\d+)
def delete(request,table_name,del_id):
print(table_name,del_id)
# 额外需要判断下表名和ID值,是否都是正经的数据,表名有并且ID值存在
# 反射,通过字符串去找一个函数,变量或者是类

# 从另外一个文件 根据字符串 反射具体的变量
# models 里面有没有table_name
table_name = table_name.capitalize() # 首字母大写

if hasattr(models,table_name):
# 如果能找到
table_class = getattr(models,table_name)
try:
table_class.objects.get(id=del_id).delete()
except Exception as e:
print(str(e))
print('id值不存在!')

return HttpResponse("表名是{},ID是{}" .format(table_name,del_id))
else:
return HttpResponse("表不存在!")