零基础篇-03阶段:Django框架与项目-下

@Django框架与项目-下

一、模板

作为Web框架,Django提供了模板,用于编写html代码,还可以嵌入模板代码更快更方便的完成页面开发,再通过在视图中渲染模板,将生成最终的html字符串返回给客户端浏览器。模版致力于表达外观,而不是程序逻辑。模板的设计实现了业务逻辑view与显示内容template的分离,一个视图可以使用任意一个模板,一个模板可以供多个视图使用。

模板包含两部分:

静态部分,包含html、css、js。
动态部分,就是模板语言。
Django模板语言,简写DTL,定义在django.template包中。 创建项目后,在"项目名称/settings.py"文件中定义了关于模板的配置。
在这里插入图片描述
DIRS定义一个目录列表,模板引擎按列表顺序搜索这些目录以查找模板文件,通常是在项目的根目录下创建templates目录。

Django处理模板分为两个阶段:

1.加载:根据给定的路径找到模板文件,编译后放在内存中。
2.渲染:使用上下文数据对模板插值并返回生成的字符串。
为了减少开发人员重复编写加载、渲染的代码,Django提供了简写函数render,用于调用模板。

** 创建示例项目**
1)创建项目test4。

django-admin startproject test4
在这里插入图片描述

2)进入项目目录test4,创建应用booktest。

cd test4
python manage.py startapp booktest

在这里插入图片描述

4)在test4/settings.py中INSTALLED_APPS项安装应用。
在这里插入图片描述

5)在test4/settings.py中DATABASES项配置使用MySQL数据库test2,数据库在第二部分已经创建。

在这里插入图片描述

6)在test4/settings.py中TEMPLATES项配置模板查找路径。

在这里插入图片描述

7)创建模板目录结构如下。

在这里插入图片描述

8)打开test4/urls.py文件,包含booktest的url配置。

在这里插入图片描述

9)在booktest/目录下创建urls.py,配置url。

from django.conf.urls import url
from booktest import views
urlpatterns=[
    url(r'^$',views.index),
]

10)打开booktest/views.py文件,定义视图index。

from django.shortcuts import render

def index(request):
    return render(request,'booktest/index.html')

11)在templates/booktest目录下创建文件index.html,代码如下:

<html>
<head>
    <title>首页</title>
</head>
<body>

</body>
</html>

12)打开booktest/models.py文件,定义模型类BookInfo,结构参照第二部分设计。

from django.db import models

class BookInfo(models.Model):
    btitle = models.CharField(max_length=20)
    bpub_date = models.DateField()
    bread = models.IntegerField(default=0)
    bcommet = models.IntegerField(default=0)
    isDelete = models.BooleanField(default=False)

模板语言

1.1 模板变量

模板变量的作用是计算并输出,变量名必须由字母、数字、下划线(不能以下划线开头)和点组成。

语法如下:

{{变量}}

当模版引擎遇到点如book.title,会按照下列顺序解析:

1.字典book[‘title’]
2.先属性后方法,将book当作对象,查找属性title,如果没有再查找方法title()
3.如果是格式为book.0则解析为列表book[0]
如果变量不存在则插入空字符串’'。

在模板中调用方法时不能传递参数。

示例
1)打开booktest/views.py文件,创建视图temp_var。

def temp_var(request):
    dict={'title':'字典键值'}
    book=BookInfo()
    book.btitle='对象属性'
    context={'dict':dict,'book':book}
    return render(request,'booktest/temp_var.html',context)

2)打开booktest/urls.py文件,配置url。

url(r’^temp_var/$', views.temp_var),

3)修改在templates/booktest下创建temp_var.html。

<html>
<head>
    <title>模板变量</title>
</head>
<body>
模板变量:<br/>
{{dict.title}}<br/>
{{book.btitle}}<br/>
</body>
</html>

4)运行服务器,在浏览器中输入如下网址。

http://127.0.0.1:8000/temp_var/

5)浏览效果如下图。

在这里插入图片描述

1.2 标签

语法如下:

{%代码段%}

for标签语法如下:

{%for item in 列表%}
循环逻辑
{{forloop.counter}}表示当前是第几次循环,从1开始
{%empty%}
列表为空或不存在时执行此逻辑
{%endfor%}

if标签语法如下:

{%if ...%}
逻辑1
{%elif ...%}
逻辑2
{%else%}
逻辑3
{%endif%}

比较运算符如下:

注意:运算符左右两侧不能紧挨变量或常量,必须有空格。

==
!=
<
>
<=
>=

布尔运算符如下:

and
or
not

点击查看内建标签了解更多标签http://python.usyiyi.cn/translate/django_182/ref/templates/builtins.html
示例
1)打开booktest/views.py文件,创建视图temp_tag。

from booktest.models import BookInfo
def temp_tags(request):
    context={'list':BookInfo.objects.all()}
    return render(request,'booktest/temp_tag.html',context)

2)打开booktest/urls.py文件,配置url。

url(r’^temp_tag/$', views.temp_tags),

3)在templates/booktest下创建temp_tag.html。

<html>
<head>
    <title>标签</title>
</head>
<body>
图书列表如下:
<ul>
    {%for book in list%}
        {%if book.id <= 2%}
            <li style="background-color: red;">{{book.btitle}}</li>
        {%elif book.id <= 3%}
            <li style="background-color: blue;">{{book.btitle}}</li>
        {%else%}
            <li style="background-color: green;">{{book.btitle}}</li>
        {%endif%}
    {%empty%}
        <li>对不起,没有图书</li>
    {%endfor%}
</ul>
</body>
</html>

4)运行服务器,在浏览器中输入如下网址。

http://127.0.0.1:8000/temp_tag/

浏览效果如下图:

在这里插入图片描述

1.3 过滤器

语法如下:

使用管道符号|来应用过滤器,用于进行计算、转换操作,可以使用在变量、标签中。
如果过滤器需要参数,则使用冒号:传递参数。

变量|过滤器:参数

长度length,返回字符串包含字符的个数,或列表、元组、字典的元素个数。

默认值default,如果变量不存在时则返回默认值。

data|default:'默认值'

日期date,用于对日期类型的值进行字符串格式化,常用的格式化字符如下:

Y表示年,格式为4位,y表示两位的年。
m表示月,格式为01,02,12等。
d表示日, 格式为01,02等。
j表示日,格式为1,2等。
H表示时,24进制,h表示12进制的时。
i表示分,为0-59。
s表示秒,为0-59。
value|date:“Y年m月j日 H时i分s秒”
点击查看内建过滤器了解更多过滤器。

示例
1)打开booktest/views.py文件,创建视图temp_filter。

def temp_filter(request):
    context={'list':BookInfo.objects.all()}
    return render(request,'booktest/temp_filter.html',context)

2)打开booktest/urls.py文件,配置url。

url(r’^temp_filter/$', views.temp_filter),

3)在templates/booktest下创建temp_filter.html。

<head>
    <title>过滤器</title>
</head>
<body>
图书列表如下:
<ul>
    {%for book in list%}
        {%if book.btitle|length > 4%}
            <li style="background-color: red;">
                {{book.btitle}}
                ---默认时间格式为:
                {{book.bpub_date}}
            </li>
        {%else%}
            <li style="background-color: green;">
                {{book.btitle}}
                ---格式化时间为:
                {{book.bpub_date|date:"Y-m-j"}}
            </li>
        {%endif%}
    {%endfor%}
</ul>
</body>
</html>

4)运行服务器,在浏览器中输入如下网址。

http://127.0.0.1:8000/temp_filter/

浏览效果如下图:
在这里插入图片描述

1.4 自定义过滤器

过滤器就是python中的函数,注册后就可以在模板中当作过滤器使用,下面以求余为例开发一个自定义过滤器mod。

示例
1)在应用中创建templatetags目录,当前示例为"booktest/templatetags",创建_init_文件,内容为空。

在这里插入图片描述

2)在"booktest/templatetags"目录下创建filters.py文件,代码如下:

#导入Library类
from django.template import Library

#创建一个Library类对象
register=Library()

#使用装饰器进行注册
@register.filter
#定义求余函数mod,将value对2求余
def mod(value):
    return value%2 == 0

3)在templates/booktest/temp_filter.html中,使用自定义过滤器。

首先使用load标签引入模块。

{%load filters%}

在遍历时根据编号判断奇偶,代码改为如下:
在这里插入图片描述

4)运行服务器,浏览效果如下:

在这里插入图片描述

过滤器可以接收参数,将booktest/templatetags/filters.py中增加mod_num函数。

#使用装饰器进行注册
@register.filter
#定义求余函数mod_num,将value对num求余
def mod_num(value,num):
    return value%num

5)在templates/booktest/temp_filter.html中修改遍历时判断代码。

在这里插入图片描述

6)运行服务器,浏览效果如下:

在这里插入图片描述

1.5 注释

在模板中使用如下模板注释,这段代码不会被编译,不会输出到客户端;html注释只能注释html内容,不能注释模板语言。

1)单行注释语法如下:

{#…#}

注释可以包含任何模版代码,有效的或者无效的都可以。

{# { % if foo % }bar{ % else % } #}

2)多行注释使用comment标签,语法如下:

{%comment%}

{%endcomment%}

1.6 模板继承

模板继承和类的继承含义是一样的,主要是为了提高代码重用,减轻开发人员的工作量。

典型应用:网站的头部、尾部信息。

父模板
如果发现在多个模板中某些内容相同,那就应该把这段内容定义到父模板中。

标签block:用于在父模板中预留区域,留给子模板填充差异性的内容,名字不能相同。 为了更好的可读性,建议给endblock标签写上名字,这个名字与对应的block名字相同。父模板中也可以使用上下文中传递过来的数据。

{%block 名称%}
预留区域,可以编写默认内容,也可以没有默认内容
{%endblock  名称%}

子模板
标签extends:继承,写在子模板文件的第一行。

{% extends “父模板路径”%}

子模版不用填充父模版中的所有预留区域,如果子模版没有填充,则使用父模版定义的默认值。

填充父模板中指定名称的预留区域。

{%block 名称%}
实际填充内容
{{block.super}}用于获取父模板中block的内容
{%endblock 名称%}

示例
1)打开booktest/views.py文件,创建视图temp_inherit。

def temp_inherit(request):
    context={'title':'模板继承','list':BookInfo.objects.all()}
    return render(request,'booktest/temp_inherit.html',context)

2)打开booktest/urls.py文件,配置url。

url(r’^temp_inherit/$', views.temp_inherit),

3)在templates下创建inherit_base.html。

<html>
<head>
    <title>{{title}}</title>
</head>
<body>
<h2>这是头</h2>
<hr>
{%block qu1%}
这是区域一,有默认值
{%endblock qu1%}
<hr>
{%block qu2%}
{%endblock qu2%}
<hr>
<h2>这是尾</h2>
</body>
</html>

4)在templates/booktest下创建temp_inherit.html。

{%extends 'booktest/inherit_base.html'%}
{%block qu2%}
<ul>
    {%for book in list%}
    <li>{{book.btitle}}</li>
    {%endfor%}
</ul>
{%endblock qu2%}

5)运行服务器,在浏览器中输入如下网址。

http://127.0.0.1:8000/temp_inherit/

6)浏览效果如下图。
在这里插入图片描述

1.7 HTML转义

模板对上下文传递的字符串进行输出时,会对以下字符自动转义。

小于号< 转换为 &lt;

大于号> 转换为 &gt;

单引号' 转换为 &#39;

双引号" 转换为 &quot;

与符号& 转换为 &amp;

示例
1)打开booktest/views.py文件,创建视图html_escape。

def html_escape(request):
    context={'content':'<h1>hello world</h1>'}
    return render(request,'booktest/html_escape.html',context)

2)打开booktest/urls.py文件,配置url。

url(r’^html_escape/$', views.html_escape),

3)在templates/booktest/目录下创建html_escape.html。

<html>
<head>
    <title>转义</title>
</head>
<body>
自动转义:{{content}}
</body>
</html>

4)运行服务器,在浏览器中输入如下网址。

http://127.0.0.1:8000/html_escape/

转义后标记代码不会被直接解释执行,而是被直接呈现,防止客户端通过嵌入js代码攻击网站.

浏览效果如下图:

在这里插入图片描述

关闭转义
过滤器escape可以实现对变量的html转义,默认模板就会转义,一般省略。

{{t1|escape}}

过滤器safe:禁用转义,告诉模板这个变量是安全的,可以解释执行。

{{data|safe}}

1)修改templates/booktest/html_escape.html代码如下。

<html>
<head>
    <title>转义</title>
</head>
<body>
自动转义:{{content}}
<hr>
过滤器safe关闭转义:{{content|safe}}
</body>
</html>

刷新浏览器后效果如下图:

转义

标签autoescape:设置一段代码都禁用转义,接受on、off参数。

{%autoescape off%}
...
{%endautoescape%}

1)修改templates/booktest/html_escape.html代码如下。

<html>
<head>
    <title>转义</title>
</head>
<body>
自动转义:{{content}}
<hr>
过滤器safe关闭转义:{{content|safe}}
<hr>
标签autoescape关闭转义:
{%autoescape off%}
{{content}}
{%endautoescape%}
</body>
</html>

刷新浏览器后效果如下图:
在这里插入图片描述

字符串字面值
对于在模板中硬编码的html字符串,不会转义。

1)修改templates/booktest/html_escape.html代码如下:

<html>
<head>
    <title>转义</title>
</head>
<body>
自动转义:{{content}}
<hr>
过滤器safe关闭转义:{{content|safe}}
<hr>
标签autoescape关闭转义:
{%autoescape off%}
{{content}}
{%endautoescape%}
<hr>
模板硬编码不转义:{{data|default:'<b>hello</b>'}}
</body>
</html>

2)刷新浏览器后效果如下图:

转义

如果希望出现转义的效果,则需要手动编码转义。

1)修改templates/booktest/html_escape.html代码如下:

<html>
<head>
    <title>转义</title>
</head>
<body>
自动转义:{{content}}
<hr>
过滤器safe关闭转义:{{content|safe}}
<hr>
标签autoescape关闭转义:
{%autoescape off%}
{{content}}
{%endautoescape%}
<hr>
模板硬编码不转义:{{data|default:'<b>hello</b>'}}
<hr>
模板硬编码手动转义:{{data|default:"&lt;b&gt;123&lt;/b&gt;"}}
</body>
</html>

2)刷新浏览器后效果如下图:
在这里插入图片描述

1.8 CSRF

CSRF
CSRF全拼为Cross Site Request Forgery,译为跨站请求伪造。CSRF指攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账…造成的问题包括:个人隐私泄露以及财产安全。

CSRF示意图如下:

在这里插入图片描述

如果想防止CSRF,首先是重要的信息传递都采用POST方式而不是GET方式,接下来就说POST请求的攻击方式以及在Django中的避免。

示例
攻击过程的操作了解即可,不需要重现。

1)打开booktest/views.py文件,创建视图login,login_check, post和post_action。

def login(reqeust):
    return render(reqeust, 'booktest/login.html')

def login_check(request):
    username = request.POST.get('username') #获取用户名
    password = request.POST.get('password') #获取密码

    # 校验
    if username == 'smart' and password == '123':
        request.session['username']=name #记住登录用户名
        request.session['islogin']=True #判断用户是否已登录
        return redirect('/post/')
    else:
        return redirect('/login/')

def post(request):
    return render(request, 'booktest/post.html')

def post_action(request):
    if request.session['islogin']:
        username = request.session['username']
        return HttpResponse('用户'+username+'发了一篇帖子')
    else:
        return HttpResponse('发帖失败')

2)打开booktest/urls.py文件,配置url。

url(r'^login/$', views.login),
url(r'^login_check/$', views.login_check),
url(r'^post/$', views.post),
url(r'^post_action/$',views.post_action),

3)在templates/booktest/目录下创建login.html和post.html。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录案例</title>
</head>
<body>
<form method="post" action="/login_check/">
    用户名:<input type="text" name="username"/><br/>
    密码:<input type="password" name="password"/><br/>
    <input type="submit" value="提交"/>
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>发帖页</title>
</head>
<body>
<form method="post" action="/post_action/">
    标题:<input type="text" name="title"/><br/>
    内容:<textarea name="content"></textarea>
    <input type="submit" value="发帖"/>
</form>
</body>
</html>

4)启动运行服务器,采用IP的方式,因为要演示其它IP的请求。

python manage.py runserver 172.16.179.130:8000

5)回到windows中,在浏览器中输入如下网址,将这个标签称为网站A。

http://172.16.179.130:8000/login/

浏览效果如下图:

CSRF

输入用户名和密码,点击登录,效果如下图:

CSRF

6)下面使用windows中的IIS服务器模拟另外一个网站,创建post.html,复制templates/booktest/post.html内容,并修改action路径。

<html>
<head>
    <title>发帖页</title>
</head>
<body>
<form method="post" action="http://172.16.179.130:8000/post_action/">
  标题:<input type="text" name="title"/><br/>
  内容:<textarea name="content"></textarea>
  <input type="submit" value="发帖"/>
</form>
</body>
</html>

7)在windows中浏览器查看效果如下图,将这个标签称为网站B。

CSRF

8)Django项目中默认启用了csrf保护,现在先禁用,打开test4/settings.py文件,注释掉csrf中间件。

CSRF

9)点击游览器的第一个标签即网站A,点击"发帖"按钮后如下图:

CSRF

10)点击游览器的第二个标签即IIS网站B,点击“发帖”按钮后如下图:

CSRF

对比上面两张图,发现无论从网站A还是网站B都可以访问网站A的post_action视图,这就是不安全的。

1)Django提供了csrf中间件用于防止CSRF攻击,只需要在test4/settings.py中启用csrf中间件即可。

在这里插入图片描述

2)回到windows浏览器中,分别在网站A、网站B中点击“提交”按钮,效果一样,如下图:
在这里插入图片描述

3)这下麻烦了,因为网站A自己也不能访问了,接下来templates/booktest/post.html内容,在form表单中使用标签csrf_token。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>发帖页</title>
</head>
<body>
<form method="post" action="/post_action/">
    {% csrf_token %}
    标题:<input type="text" name="title"/><br/>
    内容:<textarea name="content"></textarea>
    <input type="submit" value="发帖"/>
</form>
</body>
</html>

4)回到windows浏览器中,在网站A中点击“提交”按钮,效果如下图:

CSRF

5)回到windows浏览器中,在网站B中点击“提交”按钮,效果如下图:

CSRF

好了,Django中成功完成CSRF防护。

总结
以上的演示过程了解即可,不需要重现,以下的内容是重点,必须记住
重要信息如金额、积分等,采用POST方式传递
启用CSRF中间件,默认启用
在form表单中post提交时加入标签csrf_token
保护原理
加入标签后,可以查看post.html的源代码,发现多了一个隐藏域。

了解原理即可。

CSRF

在浏览器的“开发者工具”中查看cookie信息。

CSRF

说明:当启用中间件并加入标签csrf_token后,会向客户端浏览器中写入一条Cookie信息,这条信息的值与隐藏域input元素的value属性是一致的,提交到服务器后会先由csrf中间件进行验证,如果对比失败则返回403页面,而不会进行后续的处理。

1.9 验证码

在用户注册、登录页面,为了防止暴力请求,可以加入验证码功能,如果验证码错误,则不需要继续处理,可以减轻业务服务器、数据库服务器的压力。

手动实现验证码
接下来的代码不要求手动写出来,因为这种代码在网上可以搜到很多。

1)安装包Pillow3.4.1。

pip install Pillow==3.4.1

点击查看PIL模块API,以下代码中用到了Image、ImageDraw、ImageFont对象及方法。

2)在booktest/views.py文件中,创建视图verify_code。

提示1:随机生成字符串后存入session中,用于后续判断。
提示2:视图返回mime-type为image/png。

from PIL import Image, ImageDraw, ImageFont
from django.utils.six import BytesIO
...
def verify_code(request):
    #引入随机函数模块
    import random
    #定义变量,用于画面的背景色、宽、高
    bgcolor = (random.randrange(20, 100), random.randrange(
        20, 100), 255)
    width = 100
    height = 25
    #创建画面对象
    im = Image.new('RGB', (width, height), bgcolor)
    #创建画笔对象
    draw = ImageDraw.Draw(im)
    #调用画笔的point()函数绘制噪点
    for i in range(0, 100):
        xy = (random.randrange(0, width), random.randrange(0, height))
        fill = (random.randrange(0, 255), 255, random.randrange(0, 255))
        draw.point(xy, fill=fill)
    #定义验证码的备选值
    str1 = 'ABCD123EFGHIJK456LMNOPQRS789TUVWXYZ0'
    #随机选取4个值作为验证码
    rand_str = ''
    for i in range(0, 4):
        rand_str += str1[random.randrange(0, len(str1))]
    #构造字体对象,ubuntu的字体路径为“/usr/share/fonts/truetype/freefont”
    font = ImageFont.truetype('FreeMono.ttf', 23)
    #构造字体颜色
    fontcolor = (255, random.randrange(0, 255), random.randrange(0, 255))
    #绘制4个字
    draw.text((5, 2), rand_str[0], font=font, fill=fontcolor)
    draw.text((25, 2), rand_str[1], font=font, fill=fontcolor)
    draw.text((50, 2), rand_str[2], font=font, fill=fontcolor)
    draw.text((75, 2), rand_str[3], font=font, fill=fontcolor)
    #释放画笔
    del draw
    #存入session,用于做进一步验证
    request.session['verifycode'] = rand_str
    #内存文件操作
    buf = BytesIO()
    #将图片保存在内存中,文件类型为png
    im.save(buf, 'png')
    #将内存中的图片数据返回给客户端,MIME类型为图片png
    return HttpResponse(buf.getvalue(), 'image/png')

3)打开booktest/urls.py文件,配置url。

url(r’^verify_code/$', views.verify_code),

4)运行服务器,在浏览器中输入如下网址。

http://127.0.0.1:8000/verify_code/

5)浏览效果如下图:

在这里插入图片描述

可以多刷新几次看值会不会变。

调用验证码
1)在booktest/views.py文件中,创建视图verify_show。

def verify_show(request):
    return render(request,'booktest/verify_show.html')

2)打开booktest/urls.py文件,配置url。

url(r’^verify_show/$', views.verify_show),

3)在templates/booktest/目录下创建verify_show.html。

<html>
<head>
    <title>验证码</title>
</head>
<body>
<form method="post" action="/verify_yz/">
    {%csrf_token%}
    <input type="text" name="yzm">
    <img id="yzm" src="/verify_code/"/>
    <span id="change">看不清,换一个</span>
    <br>
    <input type="submit" value="提交">
</form>
</body>
</html>

4)运行服务器,在浏览器中输入如下网址。

http://127.0.0.1:8000/verify_show/

5)浏览效果如下图:

在这里插入图片描述

验证
1)在booktest/views.py文件中,创建视图verify_yz。

def verify_yz(request):
    yzm=request.POST.get('yzm')
    verifycode=request.session['verifycode']
    response=HttpResponse('no')
    if yzm==verifycode:
        response=HttpResponse('ok')
    return response

2)打开booktest/urls.py文件,配置url。

url(r’^verify_yz/$', views.verify_yz),

3)回到浏览器后刷新,在文本框中填写验证码,点击提交按钮。

验证码

4)浏览效果如下图:

验证码

1.10 反向解析

先看看原来怎么做

  1. 打开booktest/views.py文件,创建视图fan1、fan2。
def fan1(request):
    return render(request,'booktest/fan1.html')
def fan2(request):
    return HttpResponse('fan2')

2)打开booktest/urls.py文件,配置url。

url(r’^fan1/ ′ , v i e w s . f a n 1 ) , u r l ( r ′ f a n 2 / ', views.fan1), url(r'^fan2/ ,views.fan1),url(rfan2/', views.fan2),

3)在templates/booktest/目录下创建fan1.html。

<html>
<head>
    <title>反向解析</title>
</head>
<body>
普通链接:<a href="/fan2/">fan2</a>
</body>
</html>

4)运行服务器,在浏览器中输入如下网址:

http://127.0.0.1:8000/fan1/

浏览效果如下图:

反射解析

5)点击链接后转向fan2,效果如下图:

反射解析

6)打开booktest/urls.py文件,修改"fan2"的正则表达式为"fan_show"。

url(r’^fan_show/$', views.fan2),

7)打开浏览器,后退一下,刷新后再次点击链接,浏览如下图:

反射解析

问题就来了:随着功能的增加会出现更多的视图,可能之前配置的正则表达式不够准确,于是就要修改正则表达式,但是正则表达式一旦修改了,之前所有对应的超链接都要修改,真是一件麻烦的事情,而且可能还会漏掉一些超链接忘记修改,有办法让链接根据正则表达式动态生成吗? 答:反向解析。

反向解析应用在两个地方:模板中的超链接,视图中的重定向。

反向解析
要实现反向解析功能,需要如下步骤:

1)在test4/urls.py中为include定义namespace属性。

url(r’^',include(‘booktest.urls’,namespace=‘booktest’)),

2)在booktest/urls.py中为url定义name属性,并修改为fan2。

url(r’^fan2/$', views.fan2,name=‘fan2’),

3)在模板中使用url标签做超链接,此处为templates/booktest/fan1.html文件。

<html>
<head>
    <title>反向解析</title>
</head>
<body>
普通链接:<a href="/fan2/">fan2</a>
<hr>
反向解析:<a href="{%url 'booktest:fan2'%}">fan2</a>
</body>
</html>

4)回到浏览器中,后退,刷新,查看源文件如下图,两个链接地址一样。

反射解析

5)在booktest/urls.py中,将fan2修改为fan_show。

url(r’^fan_show/$', views.fan2,name=‘fan2’),

6)回到浏览器中,刷新,查看源文件如下图,两个链接地址不一样。

反射解析

7)反向解析也可以应用在视图的重定向中。

from django.shortcuts import redirect
from django.core.urlresolvers import reverse

return redirect(reverse('booktest:fan2'))

总结:在定义url时,需要为include定义namespace属性,为url定义name属性,使用时,在模板中使用url标签,在视图中使用reverse函数,根据正则表达式动态生成地址,减轻后期维护成本。

URL的参数
有些url配置项正则表达式中是有参数的,接下来讲解如何传递参数。

情况一:位置参数
1)在booktest/urls.py中,修改fan2如下:

url(r’^fan(\d+)_(\d+)/$', views.fan3,name=‘fan2’),

2)在booktest/views中,定义视图fan3如下:

def fan3(request, a, b):
    return HttpResponse(a+b)

3)修改templates/booktest/fan1.html文件如下:

<html>
<head>
    <title>反向解析</title>
</head>
<body>
普通链接:<a href="/fan2_3/">fan2</a>
<hr>
反向解析:<a href="{%url 'booktest:fan2' 2 3%}">fan2</a>
</body>
</html>

4)回到浏览器中,刷新,查看源文件如下图:

反向解析

使用重定向传递位置参数格式如下:

return redirect(reverse(‘booktest:fan2’, args=(2,3)))
情况二:关键字参数
1)在booktest/urls.py中,修改fan2如下:

url(r’^fan(?P\d+)_(?P\d+)/$', views.fan4,name=‘fan2’),

2)在booktest/views中,定义视图fan4如下:

def fan4(request, id, age):
    return HttpResponse(id+age)

2)修改templates/booktest/fan1.html文件如下:

<html>
<head>
    <title>反向解析</title>
</head>
<body>
普通链接:<a href="/fan100_18/">fan2</a>
<hr>
反向解析:<a href="{%url 'booktest:fan2' id=100 age=18%}">fan2</a>
</body>
</html>

3)回到浏览器中,刷新,查看源文件如下图:
在这里插入图片描述

使用重定向传递关键字参数格式如下:

return redirect(reverse(‘booktest:fan2’, kwargs={‘id’:100,‘age’:18}))

1.11 换一个

点击“看不清,换一个”,换一个新的验证码

<script type="text/javascript" src="/static/jquery-1.12.4.min.js"></script>
<script type="text/javascript">
    $(function(){
        $('#change').css('cursor','pointer').click(function() {
            $('#yzm').attr('src',$('#yzm').attr('src')+1)
        });
    });
</script>
...
<img id="yzm" src="/verify_code/?1"/>
<span id="change">看不清,换一个</span>

二、常用

2.1 静态文件

项目中的CSS、图片、js都是静态文件。一般会将静态文件放到一个单独的目录中,以方便管理。在html页面中调用时,也需要指定静态文件的路径,Django中提供了一种解析的方式配置静态文件路径。静态文件可以放在项目根目录下,也可以放在应用的目录下,由于有些静态文件在项目中是通用的,所以推荐放在项目的根目录下,方便管理。

示例
1)在test5/settings.py文件中定义静态文件存放的物理目录。

STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]

2)在项目根目录下创建static目录,再创建img、css、js目录。

3)在booktest/views.py中定义视图static_test。

def static_test(request):
    return render(request,'booktest/static_test.html')

4)在booktest/urls.py中配置url。

url(r’^static_test/$',views.static_test),

5)在templates/booktest/下创建static_test.html文件。

<html>
<head>
    <title>静态文件</title>
</head>
<body>
<img src="/static/img/sg.png"/>
</body>
</html>

6)保存图片到static/img/目录下,名称为sg.png。

7)运行服务器,浏览效果如下图:

在这里插入图片描述

配置静态文件
Django提供了一种配置,可以在html页面中可以隐藏真实路径。

1)在test5/settings.py文件中修改STATIC_URL项。

# STATIC_URL = '/static/'
STATIC_URL = '/abc/'

2)刷新浏览器,图片找不到了,效果如下图:

静态文件

3)修改templates/booktest/static_test.html如下:

<html>
<head>
    <title>静态文件</title>
</head>
<body>
修改前:<img src="/static/img/sg.png"/>
<hr>
修改后:<img src="/abc/img/sg.png"/>
</body>
</html>

3)刷新浏览器,效果如下图:

静态文件

4)查看网页源代码,发现可以网址和真实地址之间没有关系。

静态文件

为了安全可以通过配置项隐藏真实图片路径,在模板中写成固定路径,后期维护太麻烦,可以使用static标签,根据配置项生成静态文件路径。

1)修改templates/booktest/static_test.html如下:

<html>
<head>
    <title>静态文件</title>
</head>
<body>
修改前:<img src="/static/img/sg.png"/>
<hr>
修改后:<img src="/abc/img/sg.png"/>
<hr>
动态配置:
{%load static from staticfiles%}
<img src="{%static "img/sg.png" %}"/>
</body>
</html>

2)刷新浏览器,效果如下图:

静态文件

查看网页源代码如下图:

静态文件

说明:这种方案可以隐藏真实的静态文件路径,但是结合Nginx布署时,会将所有的静态文件都交给Nginx处理,而不用转到Django部分,所以这项配置就无效了。

2.2 中间件

Django中的中间件是一个轻量级、底层的插件系统,可以介入Django的请求和响应处理过程,修改Django的输入或输出。中间件的设计为开发者提供了一种无侵入式的开发方式,增强了Django框架的健壮性,其它的MVC框架也有这个功能,名称为IoC。

Django在中间件中预置了五个方法,这五个方法的区别在于不同的阶段执行,对输入或输出进行干预,方法如下:

1)初始化:无需任何参数,服务器响应第一个请求的时候调用一次,用于确定是否启用当前中间件。

def __init__(self):
    pass

2)处理请求前:在每个请求上,request对象产生之后,url匹配之前调用,返回None或HttpResponse对象。

def process_request(self, request):
    pass

3)处理视图前:在每个请求上,url匹配之后,视图函数调用之前调用,返回None或HttpResponse对象。

def process_view(self, request, view_func, *view_args, **view_kwargs):
    pass

4)处理响应后:视图函数调用之后,所有响应返回浏览器之前被调用,在每个请求上调用,返回HttpResponse对象。

def process_response(self, request, response):
    pass

5)异常处理:当视图抛出异常时调用,在每个请求上调用,返回一个HttpResponse对象。

def process_exception(self, request,exception):
    pass

示例
中间件是一个独立的python类,,可以定义这五个方法中的一个或多个。

1)在booktest/目录下创建middleware.py文件,代码如下:

class my_mid:
    def __init__(self):
        print '--------------init'

    def process_request(self,request):
        print '--------------request'

    def process_view(self,request, view_func, *view_args, **view_kwargs):
        print '--------------view'

    def process_response(self,request, response):
        print '--------------response'
        return response

2)在test5/settings.py文件中,向MIDDLEWARE_CLASSES项中注册。

中间件

3)修改booktest/views.py中视图index。

def index(request):
    print '======index======'
    return render(request,'booktest/index.html')

4)运行服务器,命令行中效果如下图:

中间件

3)刷新页面,命令行中效果如下图:

中间件

异常中间件
1)在booktest/middleware.py中定义两个异常类如下:

class exp1:
    def process_exception(self,request,exception):
        print '--------------exp1'
class exp2:
    def process_exception(self,request,exception):
        print '--------------exp2'

2)在test5/settings.py文件中,向MIDDLEWARE_CLASSES项中注册。

中间件

3)修改booktest/views.py中视图index。

def index(request):
    print '======index======'
    raise Exception('自定义异常')
    return render(request,'booktest/index.html')

总结:如果多个注册的中间件类中都有process_exception的方法,则先注册的后执行。

2.3 Admin站点

内容发布的部分由网站的管理员负责查看、添加、修改、删除数据,开发这些重复的功能是一件单调乏味、缺乏创造力的工作,为此,Django能够根据定义的模型类自动地生成管理模块。

在第一部分对管理站点做了简单介绍,现在做详细讲解。在Django项目中默认启用Admin管理站点。

1)准备工作:创建管理员的用户名和密码。

python manage.py createsuperuser

按提示填写用户名、邮箱、密码。

admin

2)使用:在应用的admin.py中注册模型类

例:打开booktest/admin.py文件,注册地区模型。

from django.contrib import admin
from booktest.models import *

admin.site.register(AreaInfo)

3)输入如下网址:

http://127.0.0.1:8000/admin/

按提示填写用户名、密码,点击“Log in”按钮登录。

admin

登录成功后,可以看到AreaInfos,可以进行增加、修改、删除、查询的管理。

admin

列表页显示效果如下图:

admin

控制管理页展示
类ModelAdmin可以控制模型在Admin界面中的展示方式,主要包括在列表页的展示方式、添加修改页的展示方式。

1)在booktest/admin.py中,注册模型类前定义管理类AreaAdmin。

class AreaAdmin(admin.ModelAdmin):
    pass

管理类有两种使用方式:

注册参数
装饰器
注册参数:打开booktest/admin.py文件,注册模型类代码如下:

admin.site.register(AreaInfo,AreaAdmin)

装饰器:打开booktest/admin.py文件,在管理类上注册模型类,代码如下:

@admin.register(AreaInfo)
class AreaAdmin(admin.ModelAdmin):
    pass

接下来介绍如何控制列表页、增加修改页展示效果。

2.3.1 列表页选项

页大小
每页中显示多少条数据,默认为每页显示100条数据,属性如下:

list_per_page=100

1)打开booktest/admin.py文件,修改AreaAdmin类如下:

class AreaAdmin(admin.ModelAdmin):
    list_per_page = 10

2)在浏览器中查看区域信息的列表页面,效果如下图:

列表页选项

"操作选项"的位置
顶部显示的属性,设置为True在顶部显示,设置为False不在顶部显示,默认为True。

actions_on_top=True

底部显示的属性,设置为True在底部显示,设置为False不在底部显示,默认为False。

actions_on_bottom=False

1)打开booktest/admin.py文件,修改AreaAdmin类如下:

class AreaAdmin(admin.ModelAdmin):
    ...
    actions_on_top = True
    actions_on_bottom = True

2)在浏览器中刷新效果如下图:

列表页选项在这里插入图片描述

列表中的列
属性如下:

list_display=[模型字段1,模型字段2,…]

1)打开booktest/admin.py文件,修改AreaAdmin类如下:

class AreaAdmin(admin.ModelAdmin):
    ...
    list_display = ['id','atitle']

2)在浏览器中刷新效果如下图:

列表页选项

点击列头可以进行升序或降序排列。

将方法作为列
列可以是模型字段,还可以是模型方法,要求方法有返回值。

1)打开booktest/models.py文件,修改AreaInfo类如下:

class AreaInfo(models.Model):
    ...
    def title(self):
        return self.atitle

2)打开booktest/admin.py文件,修改AreaAdmin类如下:

class AreaAdmin(admin.ModelAdmin):
    ...
    list_display = ['id','atitle','title']

3)在浏览器中刷新效果如下图:

列表页选项在这里插入图片描述

方法列是不能排序的,如果需要排序需要为方法指定排序依据。

admin_order_field=模型类字段

1)打开booktest/models.py文件,修改AreaInfo类如下:

class AreaInfo(models.Model):
    ...
    def title(self):
        return self.atitle
    title.admin_order_field='atitle'

2)在浏览器中刷新效果如下图:

列表页选项

列标题
列标题默认为属性或方法的名称,可以通过属性设置。需要先将模型字段封装成方法,再对方法使用这个属性,模型字段不能直接使用这个属性。

short_description=‘列标题’

1)打开booktest/models.py文件,修改AreaInfo类如下:

class AreaInfo(models.Model):
    ...
    title.short_description='区域名称'

2)在浏览器中刷新效果如下图:

列表页选项

关联对象
无法直接访问关联对象的属性或方法,可以在模型类中封装方法,访问关联对象的成员。

1)打开booktest/models.py文件,修改AreaInfo类如下:

class AreaInfo(models.Model):
    ...
    def parent(self):
        if self.aParent is None:
          return ''
        return self.aParent.atitle
    parent.short_description='父级区域名称'

2)打开booktest/admin.py文件,修改AreaAdmin类如下:

class AreaAdmin(admin.ModelAdmin):
    ...
    list_display = ['id','atitle','title','parent']

3)在浏览器中刷新效果如下图:

列表页选项

右侧栏过滤器
属性如下,只能接收字段,会将对应字段的值列出来,用于快速过滤。一般用于有重复值的字段。

list_filter=[]

1)打开booktest/admin.py文件,修改AreaAdmin类如下:

class AreaAdmin(admin.ModelAdmin):
    ...
    list_filter=['atitle']

2)在浏览器中刷新效果如下图:

列表页选项

搜索框
属性如下,用于对指定字段的值进行搜索,支持模糊查询。列表类型,表示在这些字段上进行搜索。

search_fields=[]

1)打开booktest/admin.py文件,修改AreaAdmin类如下:

class AreaAdmin(admin.ModelAdmin):
    ...
    search_fields=['atitle']

2)在浏览器中刷新效果如下图:

列表页选项

中文标题
1)打开booktest/models.py文件,修改模型类,为属性指定verbose_name参数,即第一个参数。

class AreaInfo(models.Model):
    atitle=models.CharField('标题',max_length=30)#名称
    ...

2)在浏览器中刷新效果如下图:

列表页选项

2.3.2 编辑页选项

显示字段顺序
属性如下:

fields=[]

1)点击某行ID的链接,可以转到修改页面,默认效果如下图:

编辑页选项

2)打开booktest/admin.py文件,修改AreaAdmin类如下:

class AreaAdmin(admin.ModelAdmin):
    ...
    fields=['aParent','atitle']

3)刷新浏览器效果如下图:

编辑页选项

在下拉列表中输出的是对象的名称,可以在模型类中定义str方法用于对象转换字符串。

1)打开booktest/models.py文件,修改AreaInfo类,添加str方法。

class AreaInfo(models.Model):
    ...
    def __str__(self):
        return self.atitle

2)刷新浏览器效果如下图:

编辑页选项

分组显示
属性如下:

fieldset=(
    ('组1标题',{'fields':('字段1','字段2')}),
    ('组2标题',{'fields':('字段3','字段4')}),
)

1)打开booktest/admin.py文件,修改AreaAdmin类如下:

class AreaAdmin(admin.ModelAdmin):
    ...
    # fields=['aParent','atitle']
    fieldsets = (
        ('基本', {'fields': ['atitle']}),
        ('高级', {'fields': ['aParent']})
    )

2)刷新浏览器效果如下图:

编辑页选项

说明:fields与fieldsets两者选一使用。

关联对象
在一对多的关系中,可以在一端的编辑页面中编辑多端的对象,嵌入多端对象的方式包括表格、块两种。 类型InlineModelAdmin:表示在模型的编辑页面嵌入关联模型的编辑。子类TabularInline:以表格的形式嵌入。子类StackedInline:以块的形式嵌入。

1)打开booktest/admin.py文件,创建AreaStackedInline类。

class AreaStackedInline(admin.StackedInline):
    model = AreaInfo#关联子对象
    extra = 2#额外编辑2个子对象

2)打开booktest/admin.py文件,修改AreaAdmin类如下:

class AreaAdmin(admin.ModelAdmin):

inlines = [AreaStackedInline]
3)刷新浏览器效果如下图:

编辑页选项

可以用表格的形式嵌入。

1)打开booktest/admin.py文件,创建AreaTabularInline类。

class AreaTabularInline(admin.TabularInline):
    model = AreaInfo#关联子对象
    extra = 2#额外编辑2个子对象

2)打开booktest/admin.py文件,修改AreaAdmin类如下:

class AreaAdmin(admin.ModelAdmin):
    ...
    inlines = [AreaTabularInline]

3)刷新浏览器效果如下图:

编辑页选项

2.3.3 重写模板

1)在templates/目录下创建admin目录,结构如下图:

重写模板

2)打开当前虚拟环境中Django的目录,再向下找到admin的模板,目录如下:

/home/python/.virtualenvs/py_django/lib/python3.5/site-packages/django/contrib/admin/templates/admin

3)将需要更改文件拷贝到第一步建好的目录里,此处以base_site.html为例。

重写模板

编辑base_site.html文件:

{% extends "admin/base.html" %}

{% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}

{% block branding %}
<h1 id="site-name"><a href="{% url 'admin:index' %}">{{ site_header|default:_('Django administration') }}</a></h1>
<hr>
<h1>自定义的管理页模板</h1>
<hr>
{% endblock %}

{% block nav-global %}{% endblock %}

4)在浏览器中转到列表页面,刷新后如下图:

重写模板

其它后台的模板可以按照相同的方式进行修改。

2.4 上传图片

在python中进行图片操作,需要安装包PIL。

pip install Pillow==3.4.1

在Django中上传图片包括两种方式:

在管理页面admin中上传图片
自定义form表单中上传图片
上传图片后,将图片存储在服务器上,然后将图片的路径存储在表中。

创建包含图片的模型类
将模型类的属性定义成models.ImageField类型。

1)打开booktest/models.py文件,定义模型类PicTest。

class PicTest(models.Model):
    pic = models.ImageField(upload_to='booktest/')

2)回到命令行中,生成迁移。

python manage.py makemigrations

3)打开booktest/migrations/0001_initial.py文件,删除AreaInfo部分,因为这个表已经存在。

上传图片

4)回到命令行中,执行迁移。

python manage.py migrate

5)因为当前没有定义图书、英雄模型类,会提示“是否删除”,输入“no”后回车,表示不删除。

上传图片

6)打开test5/settings.py文件,设置图片保存路径。

因为图片也属于静态文件,所以保存到static目录下。

MEDIA_ROOT=os.path.join(BASE_DIR,"static/media")

7)在static目录下创建media目录,再创建应用名称的目录,此例为booktest。

上传图片

2.4.1 在admin中

1)打开booktest/admin.py文件,注册PicTest。

from django.contrib import admin
from booktest.models import *

admin.site.register(PicTest)

2)运行服务器,输入如下网址。

http://127.0.0.1:8000/admin/
在这里插入图片描述

3)点击“Add”添加数据,打开新页面。

上传图片

4)选择图片,点击“save”按钮完成图片上传。 5)回到数据库命令行,查询表pictest中的数据如下图:

上传图片

6)图片被保存到目录static/media/booktest/下,如下图:

上传图片

2.4.2 自定义上传

1)打开booktest/views.py文件,创建视图pic_upload。

def pic_upload(request):
    return render(request,'booktest/pic_upload.html')

2)打开booktest/urls.py文件,配置url。

url(r’^pic_upload/$', views.pic_upload),

3)在templates/booktest/目录下创建模板pic_upload.html。

在模板中定义上传表单,要求如下:

form的属性enctype="multipart/form-data"
form的method为post
input的类型为file
<html>
<head>
    <title>自定义上传图片</title>
</head>
<body>
    <form method="post" action="/pic_handle/" enctype="multipart/form-data">
        {%csrf_token%}
        <input type="file" name="pic"/><br>
        <input type="submit" value="上传">
    </form>
</body>
</html>

4)打开booktest/views.py文件,创建视图pic_handle,用于接收表单保存图片。

request对象的FILES属性用于接收请求的文件,包括图片。

from django.conf import settings
from django.http import HttpResponse
...
def pic_handle(request):
    f1=request.FILES.get('pic')
    fname='%s/booktest/%s'%(settings.MEDIA_ROOT,f1.name)
    with open(fname,'wb') as pic:
        for c in f1.chunks():
            pic.write(c)
    return HttpResponse('OK')

5)打开booktest/urls.py文件,配置url。

url(r’^pic_handle/$', views.pic_handle),

6)运行服务器,在浏览器中输入如下网址:

http://127.0.0.1:8000/pic_upload/
上传图片

选择文件后点击按钮上传图片。

7)图片上传目录如下图:

上传图片

这里只是完成图片上传的代码,如果需要保存数据到表中需要创建PicTest对象完成保存。

2.4.3 显示图片

1)打开booktest/views.py文件,创建视图pic_show。

from booktest.models import PicTest
...
def pic_show(request):
    pic=PicTest.objects.get(pk=1)
    context={'pic':pic}
    return render(request,'booktest/pic_show.html',context)

2)打开booktest/urls.py文件,配置url。

url(r’^pic_show/$', views.pic_show),

3)在templates/booktest/目录下创建模板pic_show.html。

<html>
<head>
    <title>显示上传的图片</title>
</head>
<body>
<img src="/static/media/{{pic.pic}}"/>
</body>
4)运行服务器,在浏览器中输入如下网址:

http://127.0.0.1:8000/pic_show/
显示上传的图片

2.5 分页

Django提供了数据分页的类,这些类被定义在django/core/paginator.py中。 类Paginator用于对列进行一页n条数据的分页运算。类Page用于表示第m页的数据。

Paginator类实例对象
方法_init_(列表,int):返回分页对象,第一个参数为列表数据,第二个参数为每页数据的条数。
属性count:返回对象总数。
属性num_pages:返回页面总数。
属性page_range:返回页码列表,从1开始,例如[1, 2, 3, 4]。
方法page(m):返回Page类实例对象,表示第m页的数据,下标以1开始。
Page类实例对象
调用Paginator对象的page()方法返回Page对象,不需要手动构造。
属性object_list:返回当前页对象的列表。
属性number:返回当前是第几页,从1开始。
属性paginator:当前页对应的Paginator对象。
方法has_next():如果有下一页返回True。
方法has_previous():如果有上一页返回True。
方法len():返回当前页面对象的个数。
示例
1)在booktest/views.py文件中创建视图page_test。

from django.core.paginator import Paginator
from booktest.models import AreaInfo
...
#参数pIndex表示:当前要显示的页码
def page_test(request,pIndex):
    #查询所有的地区信息
    list1 = AreaInfo.objects.filter(aParent__isnull=True)
    #将地区信息按一页10条进行分页
    p = Paginator(list1, 10)
    #如果当前没有传递页码信息,则认为是第一页,这样写是为了请求第一页时可以不写页码
    if pIndex == '':
        pIndex = '1'
    #通过url匹配的参数都是字符串类型,转换成int类型
    pIndex = int(pIndex)
    #获取第pIndex页的数据
    list2 = p.page(pIndex)
    #获取所有的页码信息
    plist = p.page_range
    #将当前页码、当前页的数据、页码信息传递到模板中
    return render(request, 'booktest/page_test.html', {'list': list2, 'plist': plist, 'pIndex': pIndex})

2)在booktest/urls.py文件中配置url。

url(r’^page(?P[0-9]*)/$', views.page_test),

3)在templates/booktest/目录下创建page_test.html模板文件。

<html>
<head>
    <title>分页</title>
</head>
<body>
显示当前页的地区信息:<br>
<ul>
{%for area in list%}
<li>{{area.id}}--{{area.atitle}}</li>
{%endfor%}
</ul>
<hr>
显示页码信息:当前页码没有链接,其它页码有链接<br>
{%for pindex in plist%}
    {%if pIndex == pindex%}
        {{pindex}}&nbsp;&nbsp;
    {%else%}
        <a href="/page{{pindex}}/">{{pindex}}</a>&nbsp;&nbsp;
    {%endif%}
{%endfor%}
</body>
</html>

4)运行服务器,在浏览器中输入如下网址:

http://127.0.0.1:8000/page/
在这里插入图片描述

5)点击页码数字,效果如下图:

分页

2.6 示例

本示例讲解在Django中使用jquery的ajax进行数据交互。 jquery框架中提供了 . a j a x 、 .ajax、 .ajax.get、 . p o s t 方法,用于进行异步交互,由于 D j a n g o 中默认使用 C S R F 约束,推荐使用 .post方法,用于进行异步交互,由于Django中默认使用CSRF约束,推荐使用 .post方法,用于进行异步交互,由于Django中默认使用CSRF约束,推荐使用.get。

示例:实现省市区的选择。

最终实现效果如图:

city

1)将jquery文件拷贝到static/js/目录下。

city

2)打开booktest/views.py文件,定义视图area1,用于显示下拉列表。

#提供显示下拉列表的控件,供用户操作
def area1(request):
    return render(request,'booktest/area1.html')

3)打开booktest/urls.py文件,配置url。

url(r’^area1/$', views.area1),

4)在templates/booktest/目录下创建area1.html。

<html>
<head>
    <title>省市区列表</title>
    <script type="text/javascript" src="/static/js/jquery-1.12.4.min.js"></script>
    <script type="text/javascript">
        $(function(){
            //页面加载完成后获取省信息,并添加到省select
            $.get('/area2/',function(dic) {
                pro=$('#pro')
                $.each(dic.data,function(index,item){
                    pro.append('<option value='+item[0]+'>'+item[1]+'</option>');
                })
            });
            //为省select绑定change事件,获取市信息,并添加到市select
            $('#pro').change(function(){
                $.get('/area3_'+$(this).val()+'/',function(dic){
                    city=$('#city');
                    city.empty().append('<option value="">请选择市</option>');
                    dis=$('#dis');
                    dis.empty().append('<option value="">请选择区县</option>');
                    $.each(dic.data,function(index,item){
                        city.append('<option value='+item[0]+'>'+item[1]+'</option>');
                    })
                });
            });
            //为市select绑定change事件,获取区县信息,并添加到区县select
            $('#city').change(function(){
                $.get('/area3_'+$(this).val()+'/',function(dic){
                    dis=$('#dis');
                    dis.empty().append('<option value="">请选择区县</option>');
                    $.each(dic.data,function(index,item){
                        dis.append('<option value='+item[0]+'>'+item[1]+'</option>');
                    })
                })
            });

        });
    </script>
</head>
<body>
<select id="pro">
    <option value="">请选择省</option>
</select>
<select id="city">
    <option value="">请选择市</option>
</select>
<select id="dis">
    <option value="">请选择区县</option>
</select>
</body>
</html>

5)运行服务器,在浏览器中输入如下网址:

http://127.0.0.1:8000/area1/
浏览效果如下图:

city

6)打开booktest/views.py文件,定义视图area2,用于获取省信息。

from django.http import JsonResponse
...
#获取省信息
def area2(request):
    list = AreaInfo.objects.filter(aParent__isnull=True)
    list2 = []
    for item in list:
        list2.append([item.id, item.atitle])
    return JsonResponse({'data': list2})

7)打开booktest/urls.py文件,配置url。

url(r’^area2/$', views.area2),

8)在浏览器中输入如下网址。

http://127.0.0.1:8000/area2/
浏览效果如下图:

city

9)打开booktest/views.py文件,定义视图area3,用于根据编号获取对应的子级信息,如果传递的是省编号则获取市信息,如果传递的是市编号则获取区县信息。

#根据pid查询子级区域信息
def area3(request, pid):
    list = AreaInfo.objects.filter(aParent_id=pid)
    list2 = []
    for item in list:
        list2.append([item.id, item.atitle])
    return JsonResponse({'data': list2})

10)打开booktest/urls.py文件,配置url。

url(r’^area3_(\d+)/$', views.area3),

11)在浏览器中输入如下网址:

http://127.0.0.1:8000/area3_140000/
浏览效果如下图:

city

12)在浏览器中输入如下网址:

http://127.0.0.1:8000/area1/
选择效果如下图:

city

三、第三方

布署
当项目开发完成后,需要将代码放到服务器上,这个过程称为布署,服务器上需要有一个运行代码的环境,这个环境一般使用uWSGI+Nginx。

创建示例项目
1)在~/Desktop/pytest目录下,进入工作环境py_django。

cd ~/Desktop/pytest
workon py_django

在这里插入图片描述

2)创建项目test6。

django-admin startproject test6

在这里插入图片描述

3)进入项目目录test6,创建应用booktest。

cd test6
python manage.py startapp booktest

在这里插入图片描述

4)在test6/settings.py中INSTALLED_APPS项安装应用。

在这里插入图片描述

5)在test6/settings.py中DATABASES项配置使用MySQL数据库test2,数据库在第二部分已经创建。

在这里插入图片描述

6)在test6/settings.py中TEMPLATES项配置模板查找路径。

在这里插入图片描述

7)创建模板目录结构如下。

在这里插入图片描述

8)打开test6/urls.py文件,包含booktest的url配置。

在这里插入图片描述

9)在booktest/目录下创建urls.py,配置url。

from django.conf.urls import url
from booktest import views
urlpatterns=[
    url(r'^$',views.index),
]

10)打开booktest/views.py文件,定义视图index。

from django.shortcuts import render

def index(request):
    return render(request,'booktest/index.html')

11)在templates/booktest目录下创建文件index.html,代码如下:

<html>
<head>
    <title>第三方包</title>
</head>
<body>
<h1>第三方包</h1>
</body>
</html>

12)运行服务器。

python manage.py runserver

13)浏览效果如下图:

浏览项目

3.1 富文本编辑器

借助富文本编辑器,网站的编辑人员能够像使用offfice一样编写出漂亮的、所见即所得的页面。此处以tinymce为例,其它富文本编辑器的使用也是类似的。

在虚拟环境中安装包。

pip install django-tinymce==2.6.0

安装完成后,可以使用在Admin管理中,也可以自定义表单使用。

示例
1)在test6/settings.py中为INSTALLED_APPS添加编辑器应用。

INSTALLED_APPS = (
    ...
    'tinymce',
)

2)在test6/settings.py中添加编辑器配置。

TINYMCE_DEFAULT_CONFIG = {
    'theme': 'advanced',
    'width': 600,
    'height': 400,
}

3)在test6/urls.py中配置编辑器url。

urlpatterns = [
    ...
    url(r'^tinymce/', include('tinymce.urls')),
]

3.1.1 admin使用

1)在booktest/models.py中,定义模型的属性为HTMLField()类型。

from django.db import models
from tinymce.models import HTMLField

class GoodsInfo(models.Model):
    gcontent=HTMLField()

2)生成迁移文件。

python manage.py makemigrations
在这里插入图片描述

3)执行迁移。

python manage.py migrate

4)在本示例中没有定义其它的模型类,但是数据库中有这些表,提示是否删除,输入no后回车,表示不删除,因为其它的示例中需要使用这些表。

在Admin中使用

5)迁移完成,新开终端,连接mysql,使用test2数据库,查看表如下:

在Admin中使用

6)发现并没有表GoodsInfo,解决办法是删除迁移表中关于booktest应用的数据。

delete from django_migrations where app=‘booktest’;

7)再次执行迁移。

python manage.py migrate

成功完成迁移,记得不删除no。

在Admin中使用

8)在booktest/admin.py中注册模型类GoodsInfo

from django.contrib import admin
from booktest.models import *
class GoodsInfoAdmin(admin.ModelAdmin):
    list_display = ['id']

admin.site.register(GoodsInfo,GoodsInfoAdmin)

9)运行服务器,进入admin后台管理,点击GoodsInfo的添加,效果如下图

在Admin中使用

在编辑器中编辑内容后保存。

3.1.2 自定义使用

1)在booktest/views.py中定义视图editor,用于显示编辑器。

def editor(request):
    return render(request, 'booktest/editor.html')

2)在booktest/urls.py中配置url。

url(r’^editor/',views.editor),

3)在项目目录下创建静态文件目录如下图:

自定义使用

4)打开py_django虚拟环境的目录,找到tinymce的目录。

/home/python/.virtualenvs/py_django/lib/python3.5/site-packages/tinymce/static/tiny_mce

5)拷贝tiny_mce_src.js文件、langs文件夹以及themes文件夹拷贝到项目目录下的static/js/目录下。

自定义使用

6)在test6/settings.py中配置静态文件查找路径。

STATICFILES_DIRS=[
    os.path.join(BASE_DIR,'static'),
]

7)在templates/booktest/目录下创建editor.html模板。

<html>
<head>
    <title>自定义使用tinymce</title>
    <script type="text/javascript" src='/static/js/tiny_mce.js'></script>
    <script type="text/javascript">
        tinyMCE.init({
            'mode':'textareas',
            'theme':'advanced',
            'width':400,
            'height':100
        });
    </script>
</head>
<body>
<form method="post" action="#">
    <textarea name='gcontent'>哈哈,这是啥呀</textarea>
</form>
</body>
</html>

8)运行服务器,在浏览器中输入如下网址:

http://127.0.0.1:8000/editor/

浏览效果如下图:

自定义使用

3.1.3 显示

通过富文本编辑器产生的字符串是包含html的。 在数据库中查询如下图:

显示

在模板中显示字符串时,默认会进行html转义,如果想正常显示需要关闭转义。

问:在模板中怎么关闭转义

方式一:过滤器safe
方式二:标签autoescape off
1)在booktest/views.py中定义视图show,用于显示富文本编辑器的内容。

from booktest.models import *
...
def show(request):
    goods=GoodsInfo.objects.get(pk=1)
    context={'g':goods}
    return render(request,'booktest/show.html',context)

2)在booktest/urls.py中配置url。

url(r’^show/', views.show),

3)在templates/booktest/目录下创建show.html模板。

<html>
<head>
    <title>展示富文本编辑器内容</title>
</head>
<body>
id:{{g.id}}
<hr>
{%autoescape off%}
{{g.gcontent}}
{%endautoescape%}
<hr>
{{g.gcontent|safe}}
</body>
</html>

4)运行服务器,在浏览器中输入如下网址:

http://127.0.0.1:8000/show/

浏览效果如下图:

显示

3.2 全文检索

全文检索不同于特定字段的模糊查询,使用全文检索的效率更高,并且能够对于中文进行分词处理。

haystack:全文检索的框架,支持whoosh、solr、Xapian、Elasticsearc四种全文检索引擎,点击查看官方网站。
whoosh:纯Python编写的全文搜索引擎,虽然性能比不上sphinx、xapian、Elasticsearc等,但是无二进制包,程序不会莫名其妙的崩溃,对于小型的站点,whoosh已经足够使用,点击查看whoosh文档。
jieba:一款免费的中文分词包,如果觉得不好用可以使用一些收费产品。
1)在虚拟环境中依次安装需要的包。

pip install django-haystack
pip install whoosh
pip install jieba

2)修改test6/settings.py文件,安装应用haystack。

INSTALLED_APPS = (
    ...
    'haystack',
)

3)在test6/settings.py文件中配置搜索引擎。

...
HAYSTACK_CONNECTIONS = {
    'default': {
        #使用whoosh引擎
        'ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine',
        #索引文件路径
        'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
    }
}

#当添加、修改、删除数据时,自动生成索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'

4)在test6/urls.py中添加搜索的配置。

url(r’^search/', include(‘haystack.urls’)),

3.2.1 创建引擎及索引

1)在booktest目录下创建search_indexes.py文件。

from haystack import indexes
from booktest.models import GoodsInfo
#指定对于某个类的某些数据建立索引
class GoodsInfoIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.CharField(document=True, use_template=True)

    def get_model(self):
        return GoodsInfo

    def index_queryset(self, using=None):
        return self.get_model().objects.all()

2)在templates目录下创建"search/indexes/booktest/"目录。

全文检索

3)在上面的目录中创建"goodsinfo_text.txt"文件。

#指定索引的属性
{{object.gcontent}}

4)找到虚拟环境py_django下的haystack目录。

/home/python/.virtualenvs/py_django/lib/python3.5/site-packages/haystack/backends/

5)在上面的目录中创建ChineseAnalyzer.py文件。

import jieba
from whoosh.analysis import Tokenizer, Token

class ChineseTokenizer(Tokenizer):
    def __call__(self, value, positions=False, chars=False,
                 keeporiginal=False, removestops=True,
                 start_pos=0, start_char=0, mode='', **kwargs):
        t = Token(positions, chars, removestops=removestops, mode=mode,
                  **kwargs)
        seglist = jieba.cut(value, cut_all=True)
        for w in seglist:
            t.original = t.text = w
            t.boost = 1.0
            if positions:
                t.pos = start_pos + value.find(w)
            if chars:
                t.startchar = start_char + value.find(w)
                t.endchar = start_char + value.find(w) + len(w)
            yield t

def ChineseAnalyzer():
    return ChineseTokenizer()

6)复制whoosh_backend.py文件,改为如下名称:

注意:复制出来的文件名,末尾会有一个空格,记得要删除这个空格。

whoosh_cn_backend.py

7)打开复制出来的新文件,引入中文分析类,内部采用jieba分词。

from .ChineseAnalyzer import ChineseAnalyzer

8)更改词语分析类。

查找
analyzer=StemmingAnalyzer()
改为
analyzer=ChineseAnalyzer()

9)初始化索引数据。

python manage.py rebuild_index

10)按提示输入y后回车,生成索引。

全文检索

11)索引生成后目录结构如下图:

全文检索

3.2.2 使用

按照配置,在admin管理中添加数据后,会自动为数据创建索引,可以直接进行搜索,可以先创建一些测试数据。

1)在booktest/views.py中定义视图query。

def query(request):
    return render(request,'booktest/query.html')

2)在booktest/urls.py中配置。

url(r’^query/', views.query),

3)在templates/booktest/目录中创建模板query.html。

参数q表示搜索内容,传递到模板中的数据为query。

<html>
<head>
    <title>全文检索</title>
</head>
<body>
<form method='get' action="/search/" target="_blank">
    <input type="text" name="q">
    <br>
    <input type="submit" value="查询">
</form>
</body>
</html>

4)自定义搜索结果模板:在templates/search/目录下创建search.html。

搜索结果进行分页,视图向模板中传递的上下文如下:

query:搜索关键字
page:当前页的page对象
paginator:分页paginator对象
视图接收的参数如下:

参数q表示搜索内容,传递到模板中的数据为query
参数page表示当前页码

<html>
<head>
    <title>全文检索--结果页</title>
</head>
<body>
<h1>搜索&nbsp;<b>{{query}}</b>&nbsp;结果如下:</h1>
<ul>
{%for item in page%}
    <li>{{item.object.id}}--{{item.object.gcontent|safe}}</li>
{%empty%}
    <li>啥也没找到</li>
{%endfor%}
</ul>
<hr>
{%for pindex in page.paginator.page_range%}
    {%if pindex == page.number%}
        {{pindex}}&nbsp;&nbsp;
    {%else%}
        <a href="?q={{query}}&amp;page={{pindex}}">{{pindex}}</a>&nbsp;&nbsp;
    {%endif%}
{%endfor%}
</body>
</html>

5)运行服务器,在浏览器中输入如下地址:

http://127.0.0.1:8000/query/

在文本框中填写要搜索的信息,点击”搜索“按钮。

使用

搜索结果如下:

使用

3.3 发送邮件

Django中内置了邮件发送功能,被定义在django.core.mail模块中。发送邮件需要使用SMTP服务器,常用的免费服务器有:163、126、QQ,下面以163邮件为例。

1)注册163邮箱itcast88,登录后设置。

发送邮件

2)在新页面中点击“客户端授权密码”,勾选“开启”,弹出新窗口填写手机验证码。

发送邮件

3)填写授权码。

发送邮件

4)提示开启成功。

发送邮件

5)打开test6/settings.py文件,点击下图配置。

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.163.com'
EMAIL_PORT = 25
#发送邮件的邮箱
EMAIL_HOST_USER = 'itcast88@163.com'
#在邮箱中设置的客户端授权密码
EMAIL_HOST_PASSWORD = 'python808'
#收件人看到的发件人
EMAIL_FROM = 'python<itcast88@163.com>'

6)在booktest/views.py文件中新建视图send。

from django.conf import settings
from django.core.mail import send_mail
from django.http import HttpResponse
...
def send(request):
    msg='<a href="http://www.itcast.cn/subject/pythonzly/index.shtml" target="_blank">点击激活</a>'
    send_mail('注册激活','',settings.EMAIL_FROM,
              ['itcast88@163.com'],
              html_message=msg)
    return HttpResponse('ok')

7)在booktest/urls.py文件中配置。

url(r'^send/$',views.send),

8)启动服务器,在浏览器中输入如下网址:

http://127.0.0.1:8000/send/
邮件发送成功后,在邮箱中查看邮件如下图:

发送邮件

3.4 celery

celery
情景:用户发起request,并等待response返回。在本些views中,可能需要执行一段耗时的程序,那么用户就会等待很长时间,造成不好的用户体验,比如发送邮件、手机验证码等。

使用celery后,情况就不一样了。解决:将耗时的程序放到celery中执行。

celery名词:

任务task:就是一个Python函数。
队列queue:将需要执行的任务加入到队列中。
工人worker:在一个新进程中,负责执行队列中的任务。
代理人broker:负责调度,在布置环境中使用redis。
安装包:

celery3.1.25
django-celery
3.1.17

示例
1)在booktest/views.py文件中创建视图sayhello。

import time
...
def sayhello(request):
    print('hello ...')
    time.sleep(2)
    print('world ...')
    return HttpResponse("hello world")

2)在booktest/urls.py中配置。

url(r'^sayhello$',views.sayhello),

3)启动服务器,在浏览器中输入如下网址:

http://127.0.0.1:8000/sayhello/
4)在终端中效果如下图,两次输出之间等待一段时间才会返回结果。

celery

5)在test6/settings.py中安装。

INSTALLED_APPS = (
  ...
  'djcelery',
}

6)在test6/settings.py文件中配置代理和任务模块。

import djcelery
djcelery.setup_loader()
BROKER_URL = 'redis://127.0.0.1:6379/2'

7)在booktest/目录下创建tasks.py文件。

import time
from celery import task

@task
def sayhello():
    print('hello ...')
    time.sleep(2)
    print('world ...')

8)打开booktest/views.py文件,修改sayhello视图如下:

from booktest import tasks
...
def sayhello(request):
    # print('hello ...')
    # time.sleep(2)
    # print('world ...')
    tasks.sayhello.delay()
    return HttpResponse("hello world")

9)执行迁移生成celery需要的数据表。

python manage.py migrate
生成表如下:

celery

10)启动Redis,如果已经启动则不需要启动。

sudo service redis start
11)启动worker。

python manage.py celery worker --loglevel=info
启动成功后提示如下图:

celery

11)打开新终端,进入虚拟环境,启动服务器,刷新浏览器。 在旧终端中两个输出间仍有时间间隔。

celery

运行完成后如下图,注意两个终端中的时间,服务器的响应是立即返回的。

celery

12)打开booktest/task.py文件,修改为发送邮件的代码,就可以实现无阻塞发送邮件。

from django.conf import settings
from django.core.mail import send_mail
from celery import task

@task
def sayhello():
    msg='<a href="http://www.itcast.cn/subject/pythonzly/index.shtml" target="_blank">点击激活</a>'
    send_mail('注册激活','',settings.EMAIL_FROM,
              ['itcast88@163.com'],
              html_message=msg)

3.5 部署

当项目开发完成后,需要将项目代码放到服务器上,这个服务器拥有固定的IP,再通过域名绑定,就可以供其它人浏览,对于python web开发,可以使用wsgi、apache服务器,此处以wsgi为例进行布署。

服务器首先是物理上的一台性能高、线路全、运行稳定的机器,分为私有服务器、公有服务器。

私有服务器:公司自己购买、自己维护,只布署自己的应用,可供公司内部或外网访问,成本高,需要专业人员维护,适合大公司使用。
公有服务器:集成好运营环境,销售空间或主机,供其布署自己的应用,适合初创公司使用,成本低。
常用的公有服务器,如阿里云、青云等,可按流量收费或按时间收费。服务器还需要安装服务器软件,此处需要uWSGI、Nginx。

服务器架构如下图:

布署

示例
1)布署前需要关闭调试、允许任何机器访问,打开test6/settings.py文件。

DEBUG = False
ALLOW_HOSTS=[‘*’,]

2)打开templates/booktest/index.html文件,修改如下:

<html>
<head>
    <title>第三方包</title>
</head>
<body>
<h1>第三方包</h1>
<hr>
<h1>布署</h1>
<img src="/static/img/sg.png"/>
</body>
</html>

3)将图片sg.png拷贝到static/img/目录下。

布署

4)运行服务器,在浏览器中输入如下网址:

http://127.0.0.1:8000/
浏览效果如下图,图片是看不到的。

布署

3.5.1 搭建环境

1)在本机进入虚拟环境,执行命令导出当前需要的所有包。

pip freeze > plist.txt

2)通过ftp软件将项目代码和plist.txt文件上传到服务器。

3)创建虚拟环境,在虚拟环境上安装包。

mkvirtualenv 虚拟环境名称
pip install -r plist.txt

3.5.2 WSGI

在生产环境中使用WSGI作为python web的服务器。WSGI:全拼为Python Web Server Gateway Interface,Python Web服务器网关接口,是Python应用程序或框架和Web服务器之间的一种接口,被广泛接受。WSGI没有官方的实现, 因为WSGI更像一个协议,只要遵照这些协议,WSGI应用(Application)都可以在任何服务器(Server)上运行。

项目默认会生成一个wsgi.py文件,确定了settings模块、application对象。

application对象:在Python模块中使用application对象与应用服务器交互。
settings模块:用于进行项目配置。
uWSGI
uWSGI实现了WSGI的所有接口,是一个快速、自我修复、开发人员和系统管理员友好的服务器。uWSGI代码完全用C编写,效率高、性能稳定。

1)安装uWSGI。

pip install uwsgi

2)配置uWSGI,在项目目录下创建uwsgi.ini文件,配置如下:

[uwsgi]
#使用nginx连接时使用
#socket=127.0.0.1:8080
#直接做web服务器使用
http=127.0.0.1:8080
#项目目录
chdir=/home/python/Desktop/pytest/test6
#项目中wsgi.py文件的目录,相对于项目目录
wsgi-file=test6/wsgi.py
processes=4
threads=2
master=True
pidfile=uwsgi.pid
daemonize=uwsgi.log

3)启动。

uwsgi --ini uwsgi.ini

4)查看。

ps ajx|grep uwsgi
效果如下图:

uWSGI

5)停止。

uwsgi --stop uwsgi.pid

6)在浏览器中输入如下网址:

http://127.0.0.1:8080/

浏览效果如下图,图片是看不到的。

uWSGI

7)测试没问题,将配置中启用socket,禁用http。

uWSGI

8)停止uwsgi服务,然后再启动uwsgi。

3.5.3 NGINX

使用nginx的作用主要包括负载均衡、反向代理。
1)下载nginx后放到桌面上,解压缩。

tar zxvf nginx-1.6.3.tar.gz

2)进入nginx-1.6.3目录,依次执行以下命令进行安装。

./configure
make
sudo make install

3)默认安装到/usr/local/nginx/目录,进入此目录。

cd /usr/local/nginx/

4)启动。

sudo sbin/nginx

5)查看进程。

ps ajx|grep nginx
在这里插入图片描述

6)停止。

sudo sbin/nginx -s stop

7)打开浏览器,输入如下网址:

http://127.0.0.1/
浏览效果如下图:

nginx

指向uwsgi项目
1)打开conf/nginx.conf文件。

sudo gedit conf/nginx.conf

2)在server节点下添加新的location项,指向uwsgi的ip与端口。

   location / {
        #将所有的参数转到uwsgi下
        include uwsgi_params;
        #uwsgi的ip与端口
        uwsgi_pass 127.0.0.1:8080;
    }

代码效果如下图:

nginx

3)关闭nginx后再开启。

4)打开浏览器,刷新后如下图:

nginx

静态文件
所有的静态文件都会由nginx处理,不会将请求转到uwsgi。

1)打开conf/nginx.conf文件。

sudo gedit conf/nginx.conf

2)在server节点下添加新的location项,用于处理静态文件。

location /static {
    alias /var/www/test6/static/;
}

3)在服务器上创建如下目录。

sudo mkdir -vp /var/www/test6/static/

修改目录权限。

sudo chmod 777 /var/www/test6/static/

最终目录结构如下图:

nginx

4)修改test6/settings.py文件。

STATIC_ROOT='/var/www/test6/static/'
STATIC_URL='/static/'

5)收集所有静态文件到static_root指定目录。

python manage.py collectstatic
按提示输入yes,收集文件。

nginx

6)停止后再启动nginx服务。

7)在浏览器中刷新,浏览效果如下图:

nginx

布署完成。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值