wkhzwmr

第三章

企业门户网站框架设计

需求概述

1.展示企业形象
2.人才招聘
3.服务与支持(可做的网站界面最多)
4.后台管理

文件结构设计

1.创建个文件夹(可任意命名)
2.在该文件夹下用终端输入django命令得到django项目的文件夹

django-admin startproject hengDaProject

3.在cd到hengDaProject文件夹下,用命令创建多个应用

python manage.py startapp homeApp # homeApp是该应用名,也是文件夹名,就是在hengDaProject文件夹下的文件夹

门户网站的特点

通常门户网站各个功能页面具有统一的风格,即网页头部、导航栏、尾部等内容一般是相同的(其他类型的网站很多也是相同的),因此可以将这些相同的内容编辑成模板文件,其他功能页面开发的过程中可以继承该模板文件,然后通过少量代码的修改即可实现页面的复用,这种方式可以极大的提高开发效率。

多级路由的配置与访问

本项目使用django2版本,用的是path()函数配置路由,其基本形式如下

path(\'home/\',home,name=\'home\')

第一个参数是相对访问路由:如果部署在本地8000端口的话,对应的网址为http://172.0.0.1:8000/home
第二个参数是绑定的视图函数(views.py中的函数)
第三个参数用于模板中进行逆向解析

二级路由的配置

一级路由指定路由所在的应用App,在项目根路由文件urls.py中进行声明,形式如下
头部需导入include()函数
from django.conf.urls import include # 将应用下的二级路由添加进来

配置一级路由

path(\'aboutApp/\',include(\'aboutApp.urls\')),# 访问二级子路由需要在aboutApp后添加相应的函数名
path()函数第一个参数用来匹配应用对应的前缀,即如果想要访问aboutApp应用下的路由,在根网址后都必须加上aboutApp前缀。
这里有个问题:根网址后加上aboutApp那就是空网页了,如何指定该路由的网页呢,创建一个函数,用该aboutApp当第一个参数,也就是相对访问路由即可


这里一级路由配置好了

二级路由的话就是在该应用下创建个urls.py文件,导入相应的模块

from django.urls import path
# from views import honor,survey  # ModuleNotFoundError: No module named \'views\'报错,没有模块名
# .代表的是当前目录
from . import views

然后在使用path()函数配置二级路由

urlpatterns = [
    path(\'survey/\',views.survey,name=\'survey\'),
    path(\'honor/\',views.honor,name=\'honor\')
]

视图函数的编写(views.py)

这里有3种函数,常用的是HttpResponse()和render();
(1)返回的是一个HttpResponse对象,可以设定任意的对象类型返回(字符串对象,json格式数据,迭代器对象等)

(2)redirect:表示重定向到指定的一个地址,相当于返回的状态码是301.302(URL重定向)

(3)render:表示的是返回指定配置模板中的静态资源,该操作必须配置settings中的模板路径
render() 缺少 1 个必需的位置参数:\'template_name\';这个是没有加request的报错信息。

HttpResponse()是python以硬编码的方式生成HTML

def home(requset):
    html = \'<html><body>首页</body></html>\'
    return HttpResponse(html)

render()函数是返回指定配置模板中的静态资源;

# python的render函数模板渲染
def home(request):
  return render(request,\'home.html\')

需配置置settings中的模板路径,一般是静态资源路径

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

静态资源配置

在根目录下,也就是hengDaProject文件夹下,创建一个名为static的文件夹。static文件夹下再创建4个文件夹分别是
css
fonts
img
js
用这些文件来存储项目共享的样式文件、字体库文件、图片文件和JavaScript代码文件,本项目采用的是bootstrap框架,如下图所示

主页面的编写

在应用文件夹下(这里是首页homeApp文件夹下)创建个templates文件夹,注意该文件夹的名字必须为templates,因为Django框架会自动搜索每个应用下的templates文件夹,在该文件夹下创建home.html文件。
1.引入Django模板提供的staticfiles文件夹
通过导入该标签可以在页面中通过关键字static定位到项目的静态资源

{% load staticfiles %}

最终所有静态资源引用的方式都可以采用类似{% static \'css/bootstrap.css\' %}这种形式
下面采用的是herf属性,使用了Django提供的模板标签{% url \'逆向路径\' %}(逆向路径就是path的第三个参数)
逆向路径的寻找方式采用应用名:函数名的形式,应用名没有可以不使用。例如首页仅采用下面这种无应用名的形式

 <a href="{% url \'home\' %}">首页</a>

定义为aboutApp下views.py中survey函数的逆向解析是
<li><a href="{% url \'aboutApp:survey\' %}">企业概况</a></li>
这样就可以点击企业概况这个标记,就可以通过逆向解析自动的跳转到该survey函数路由下,url是http://172.0.0.1:8000/aboutApp/survey

完整的首页页面home.html

{% load staticfiles %}
<!--通过导入该标签可以在页面中通过关键字static定位到项目的静态资源 -->
<!DOCTYPE html>
<html lang="zh-cn">

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1"> <!--手机上自适应观看-->
    <title>恒达科技(教学示例网站)</title>
    <link href="{% static \'css/bootstrap.css\' %}" rel="stylesheet"><!--需要告知Django模板当前静态资源路径位置-->
    <link href="{% static \'css/style.css\' %}" rel="stylesheet"><!--在settings.py文件末尾添加静态资源路径设置-->
    <script src="{% static \'js/jquery.min.js\' %}"></script>
    <script src="{% static \'js/bootstrap.min.js\' %}"></script>
</head>

<body>
    <!-- 导航条 -->
    <nav class="navbar navbar-default" role="navigation">
        <div class="container">
            <div class="navbar-header"> <!--Bootstrap提供的现成的导航栏组件nav -->
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                    data-target="#bs-example" aria-expanded="false"> <!--data-toggle指以什么事件触发,常用的如modal,popover,tooltips等,data-target指事件的目标,你这段代码的意思就是指将#signin-signup-tab这个Dom元素的内容以模态框的形式展示。-->
                    <span>导航栏</span>
                </button>
            </div>
            <div class="collapse navbar-collapse" id="bs-example">
                <ul class="nav navbar-nav" style="width:100%;">
                    <li class="active nav-top">
                        <a href="{% url \'home\' %}">首页</a>
                    </li>
                    <li class="dropdown nav-top"> <!--data-toggle="dropdown"以下拉菜单的方式触发-->
                        <a href="#" class="dropdown-toggle on" data-toggle="dropdown">
                            公司简介</a>
                        <ul class="dropdown-menu">
                            <li><a href="{% url \'aboutApp:survey\' %}">企业概况</a></li>
                            <li><a href="{% url \'aboutApp:honor\' %}">荣誉资质</a></li>
                        </ul>
                    </li>

                    <li class="dropdown nav-top">
                        <a href="#" class="dropdown-toggle on" data-toggle="dropdown">
                            新闻动态</a>
                        <ul class="dropdown-menu">
                            <li><a href="{% url \'newsApp:company\' %}">公司要闻</a></li>
                            <li><a href="{% url \'newsApp:industry\' %}">行业新闻</a></li>
                            <li><a href="{% url \'newsApp:notice\' %}">通知公告</a></li>
                        </ul>
                    </li>
                    <li class="dropdown nav-top">
                        <a href="#" class="dropdown-toggle on" data-toggle="dropdown">
                            产品中心</a>
                        <ul class="dropdown-menu">
                            <li><a href="{% url \'productsApp:robot\' %}">
                                    家用机器人</a></li>
                            <li><a href="{% url \'productsApp:monitoring\' %}">
                                    智能监控</a></li>
                            <li><a href="{% url \'productsApp:face\' %}">
                                    人脸识别解决方案</a></li>
                        </ul>
                    </li>
                    <li class="dropdown nav-top">
                        <a href="#" class="dropdown-toggle on" data-toggle="dropdown">
                            服务支持</a>
                        <ul class="dropdown-menu">
                            <li><a href="{% url \'serviceApp:download\' %}">
                                    资料下载</a></li>
                            <li><a href="{% url \'serviceApp:platform\' %}">
                                    人脸识别开放平台</a></li>
                        </ul>
                    </li>
                    <li class="nav-top">
                        <a href="{% url \'scienceApp:science\' %}">科研基地</a>
                    </li>
                    <li class="dropdown nav-top">
                        <a href="#" class="dropdown-toggle on" data-toggle="dropdown">
                            人才招聘</a>
                        <ul class="dropdown-menu">
                            <li><a href="{% url \'contactApp:contact\' %}">欢迎咨询</a></li>
                            <li><a href="{% url \'contactApp:recruit\' %}">加入恒达</a></li>
                        </ul>
                    </li>
                </ul>
            </div>
        </div>
    </nav>
</body>

</html>






第四章

简介

科研基地页面是企业门户网站独立的一个子模块,该模块只有一个页面,没有子页面,呈现项式比较单一,主要以静态文字和静态图片为主,开发内容相对来说比较固定,可以脱离后台数据库采用静态文件直接引用的方式实现。所有的文字和图片信息均以静态资源的方式直接在HTML中调用而不需要通过后台数据库。(这里页面的编写有空再复现下,采用bootstrap4框架)

编写科研基地页面

1.在scienceApp文件夹下创建个templates文件夹,在之下建立个science.html文件

2.修改views.py文件中的science函数,采用render()函数直接将scienceApp文件夹下的science.html,返回给前端浏览器进行显示。

def science(request):
    return render(request,\'science.html\') 

这里要注意Django会自动寻找每个应用下名为templates文件夹中的模板文件,因此使用render()函数进行页面渲染时不需要提供templates路径。

3.编辑head头部

{% load staticfiles %}
<!DOCTYPE html>
<html lang="zh-cn">

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>恒达科技|{% block title %}{% endblock %} </title>
    <link href="{% static \'css/bootstrap.css\' %}" rel="stylesheet">
    <link href="{% static \'css/style.css\' %}" rel="stylesheet">
    <script src="{% static \'js/jquery.min.js\' %}"></script>
    <script src="{% static \'js/bootstrap.min.js\' %}"></script>
</head>

4.编写页面头部logo和图标说明文字

头部可以分为上下两部分,第一部分包括企业logo图片和电话,第二部分是导航栏
头部第一行,也就是第一部分,采用6-3-3栅格结构,后面2个3-3栅格,采用hidden-xs样式,在手机页面上显示的时候隐藏,使布局更具美观

 <!-- 头部  均是一个div几个栅格,然后div中包含a-->
    <div class="container top"><!--top类并不是bootstrap的样式类,需要手工创建,用于设置整个div的边据、内容颜色等样式-->
        <div class="row">
            <div class="col-md-6">
                <a>
                    <img class="img-responsive" src="{% static \'img/logo.jpg\' %}"> <!--img-responsive Bootstrap提供的响应式img-responsive ,该图片会根据不同的浏览器分辨率自适应地调整图片大小-->
                </a>
            </div>
            <div class="col-md-3 hidden-xs"> <!--hidden-xs表示小屏幕不显示颜色-->
                <a class="phone ant"> <!--由于是嵌套在<a>标签内,鼠标移过或者单击鼠标是会有默认的下划线-->
                    <span class="glyphicon glyphicon-phone"></span>电话:400 1111 0000 &nbsp; &nbsp;
                </a><!--phone mail 是bootstrap提供的样式类 ant是自定义样式,为了对小图标进行样式设置-->
            </div>
            <div class="col-md-3 hidden-xs">
                <a class="mail ant"><!--glyphicon这样式不知道干啥的-->
                    <span class="glyphicon glyphicon-envelope"></span>邮箱:hengda@126.com
                </a>
            </div>
        </div>
    </div>

修改第一部分中小图标的样式和总的div边界

.top{
    /* 距离上边界10px*/
    margin-top: 10px; /* */
}
/* 修改bootstrap中的phone和mail图标样式*/
.phone{
    color: #666;/* #666是16进制用法么,是的;16进制一般是6位,不写的话,补什么*/
    float: left;/* float应该是浮动,right就是往右边浮动,应该要相对于文字吧*/
} /* 我把这个浮动换成 left 感觉还是一样的,影响不大*/
.mail{
    color: #666;/* #666是16进制用法么,是的;16进制一般是6位,不写的话,补什么*/
    float: right;
}

/*图标嵌入在<a>标签中的,因此将鼠标移过或者单击图标时会有默认的下划线;通过ant样式取消*/
/* 伪类*/
.ant:link{/*未访问 */
    text-decoration: none; /* 文本修饰属性无,后面还可以加line;color,style等如text-decoration-style*/
}
.ant:visited{/* 已访问的超链接*/
    text-decoration: none;
} 
.ant:hover{/* 光标移动到超链接上*/
    text-decoration: none;
}
.ant:active{
    text-decoration: none;/* 指激活的超链接*/
}

头部第二行,也就是第二部分,是导航栏,沿用home.html中的

<nav class="navbar navbar-default" role="navigation">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example"
                    aria-expanded="false">
                    <span>导航栏</span>
                </button>
            </div>
            <div class="collapse navbar-collapse" id="bs-example">
                <ul class="nav navbar-nav" style="width:100%;">
                    <li class="nav-top">
                        <a href="{% url \'home\' %}">首页</a>
                    </li>
                    <li class="dropdown nav-top">
                        <a href="#" class="dropdown-toggle on" data-toggle="dropdown">
                            公司简介</a>
                        <ul class="dropdown-menu">
                            <li><a href="{% url \'aboutApp:survey\' %}">企业概况</a></li>
                            <li><a href="{% url \'aboutApp:honor\' %}">荣誉资质</a></li>
                        </ul>
                    </li>

                    <li class="dropdown nav-top">
                        <a href="#" class="dropdown-toggle on" data-toggle="dropdown">
                            新闻动态</a>
                        <ul class="dropdown-menu">
                            <li><a href="{% url \'newsApp:company\' %}">公司要闻</a></li>
                            <li><a href="{% url \'newsApp:industry\' %}">行业新闻</a></li>
                            <li><a href="{% url \'newsApp:notice\' %}">通知公告</a></li>
                        </ul>
                    </li>
                    <li class="dropdown nav-top">
                        <a href="#" class="dropdown-toggle on" data-toggle="dropdown">
                            产品中心</a>
                        <ul class="dropdown-menu">
                            <li><a href="{% url \'productsApp:robot\' %}">
                                    家用机器人</a></li>
                            <li><a href="{% url \'productsApp:monitoring\' %}">
                                    智能监控</a></li>
                            <li><a href="{% url \'productsApp:face\' %}">
                                    人脸识别解决方案</a></li>
                        </ul>
                    </li>
                    <li class="dropdown nav-top">
                        <a href="#" class="dropdown-toggle on" data-toggle="dropdown">
                            服务支持</a>
                        <ul class="dropdown-menu">
                            <li><a href="{% url \'serviceApp:download\' %}">
                                    资料下载</a></li>
                            <li><a href="{% url \'serviceApp:platform\' %}">
                                    人脸识别开放平台</a></li>
                        </ul>
                    </li>
                    <!--id=\'science\'是为了显示激活状态在该标签上,也就是点击一级菜单常亮-->
                    <li class="nav-top" id=\'science\'>
                        <a href="{% url \'scienceApp:science\' %}">科研基地</a>
                    </li>
                    <li class="dropdown nav-top">
                        <a href="#" class="dropdown-toggle on" data-toggle="dropdown">
                            人才招聘</a>
                        <ul class="dropdown-menu">
                            <li><a href="{% url \'contactApp:contact\' %}">欢迎咨询</a></li>
                            <li><a href="{% url \'contactApp:recruit\' %}">加入恒达</a></li>
                        </ul>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

这里的导航栏有点讲究,若是采用bootstrap4的组件来运行它,只能见到一个button元素包含的\'\'字导航栏\'\',而在bs2中这个是在大于手机的样式(就是md,lg等)下隐藏\'\'导航栏\'\',显示其他具体的导航标签。在手机屏幕大小的情况下具体的导航标签隐藏,只有\'\'导航栏\'\'显示,点击导航栏即可显示具体的导航标签。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="bootstrap-4.2.1-dist/css/bootstrap.css">
    <link rel="stylesheet" href="font-awesome-4.7.0/css/font-awesome.css">
    <script src="jquery-3.3.1.slim.js"></script>
    <script src="https://cdn.staticfile.org/popper.js/1.14.6/umd/popper.js"></script>
    <script src="bootstrap-4.2.1-dist/js/bootstrap.min.js"></script>
    <title>Document</title>
</head>
<body>
    <body>
        <!-- 导航条 -->
        <nav class="navbar navbar-default" role="navigation">
            <div class="container">
                <div class="navbar-header"> <!--Bootstrap提供的现成的导航栏组件nav -->
                    <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                        data-target="#bs-example" aria-expanded="false"> <!--data-toggle指以什么事件触发,常用的如modal,popover,tooltips等,data-target指事件的目标,你这段代码的意思就是指将#signin-signup-tab这个Dom元素的内容以模态框的形式展示。-->
                        <span>导航栏</span>
                    </button>
                </div>
                <div class="collapse navbar-collapse" id="bs-example">
                    <ul class="nav navbar-nav" style="width:100%;">
                        <li class="active nav-top">
                            <a href="#">首页</a>
                        </li>
                        <li class="dropdown nav-top"> <!--data-toggle="dropdown"以下拉菜单的方式触发-->
                            <a href="#" class="dropdown-toggle on" data-toggle="dropdown">
                                公司简介</a>
                            <ul class="dropdown-menu">
                                <li><a href="#">企业概况</a></li>
                                <li><a href="#">荣誉资质</a></li>
                            </ul>
                        </li>
    
                        <li class="dropdown nav-top">
                            <a href="#" class="dropdown-toggle on" data-toggle="dropdown">
                                新闻动态</a>
                            <ul class="dropdown-menu">
                                <li><a href="#">公司要闻</a></li>
                                <li><a href="#">行业新闻</a></li>
                                <li><a href="#">通知公告</a></li>
                            </ul>
                        </li>
                        <li class="dropdown nav-top">
                            <a href="#" class="dropdown-toggle on" data-toggle="dropdown">
                                产品中心</a>
                            <ul class="dropdown-menu">
                                <li><a href="#">
                                        家用机器人</a></li>
                                <li><a href="#">
                                        智能监控</a></li>
                                <li><a href="#">
                                        人脸识别解决方案</a></li>
                            </ul>
                        </li>
                        <li class="dropdown nav-top">
                            <a href="#" class="dropdown-toggle on" data-toggle="dropdown">
                                服务支持</a>
                            <ul class="dropdown-menu">
                                <li><a href="#">
                                        资料下载</a></li>
                                <li><a href="#">
                                        人脸识别开放平台</a></li>
                            </ul>
                        </li>
                        <li class="nav-top">
                            <a href="#">科研基地</a>
                        </li>
                        <li class="dropdown nav-top">
                            <a href="#" class="dropdown-toggle on" data-toggle="dropdown">
                                人才招聘</a>
                            <ul class="dropdown-menu">
                                <li><a href="#">欢迎咨询</a></li>
                                <li><a href="#">加入恒达</a></li>
                            </ul>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
        <span bg-default>上面的是用bootstrap2版本写的导航栏,和下方bootstrap4写的导航栏差距有点大</span>
        <span bg-default>或者说bootstrap2已经不能在bootstrap4上运行,关于导航栏组件的,要么就要更改组件了</span>
        <nav>
            <ul class="nav nav-tabs"><!--nav-tabs 标签页导航-->
                <li class="nav-item">
                    <a class="nav-link active" href="#">网站首页</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">新闻中心</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">模板展示</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link disabled" href="#">关于我们</a>
                </li>
            </ul>
        </nav>
        <br>
        <br>
        <nav>
            <ul class="nav nav-tabs">
                <li class="nav-item">
                    <a class="nav-link active" href="#">网站首页</a>
                </li>
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#">新闻中心</a>
                    <div class="dropdown-menu">
                        <a class="dropdown-item active" href="#">新闻1</a>
                        <a class="dropdown-item" href="#">新闻2</a>
                        <a class="dropdown-item" href="#">新闻3</a>
                    </div>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">模板展示</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link disabled" href="#">关于我们</a>
                </li>
            </ul>
        </nav>
    </body>
    
</body>
</html>

导航栏在bs2和bs4在导航栏方面的差异和通用性

均是采用的是nav标签定义导航栏,采用设置导航标签的方式是一个<ul>里面写多个<li>,<li>中一般采用<a>标签来当作导航标签元素。
两者相同之处是导航栏组件下拉菜单必须有dropdown
b2并没有nav-item属性只有nav-top表示是导航栏组件

 <nav>
        <ul class="nav nav-tabs">
            <li class="nav-item">
                <a class="nav-link active" href="#">网站首页</a>
            </li>
            <li class="nav-item dropdown">
                <a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#">新闻中心</a>
                <div class="dropdown-menu">
                    <a class="dropdown-item active" href="#">新闻1</a>
                    <a class="dropdown-item" href="#">新闻2</a>
                    <a class="dropdown-item" href="#">新闻3</a>
                </div>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="#">模板展示</a>
            </li>
            <li class="nav-item">
                <a class="nav-link disabled" href="#">关于我们</a>
            </li>
            <li class="nav-top dropdown">
                <a href="www.bidu.com" class="dropdown-toggle on" data-toggle="dropdown">
                    新闻动态
                </a>
                <ul class="dropdown-menu">
                    <li><a href="www.123.com">公司新闻</a></li>
                    <li><a href="www.135.com">行业新闻</a></li>
                    <li><a href="www.136.com">企业要闻</a></li>

                </ul>
            </li>
            <!--b2并没有nav-item属性只有nav-top表示是导航栏组件 -->
            <!--b2和b4相同的地方导航栏组件下拉菜单必须有dropdown-->
            <li class="dropdown nav-top"> <!--data-toggle="dropdown"以下拉菜单的方式触发-->
                <a href="#" class="dropdown-toggle on" data-toggle="dropdown">
                    公司简介</a>
                <ul class="dropdown-menu"><!--b2和b4均是以dropdown-menu来定义下拉菜单的-->
                    <!--包含的li能使用class="dropdown-item"属性,li中包含的a标签当然也可以-->
                    <li class="dropdown-item"><a href="{% url \'aboutApp:survey\' %}">企业概况</a></li>
                    <li class="dropdown-item"><a href="{% url \'aboutApp:honor\' %}">荣誉资质</a></li>
                </ul><!--不能采用上方的div包含,上面div包含的关键在于a标签采用了dropdown-item属性-->
                <!--采用div包含的话由于有nav-top这个组件的css样式存在,所以2个li中的显示在同一行-->
            </li>
        </ul>
    </nav>

导航条默认属性设置

/* 导航条默认属性设置 */
.navbar-default {
    margin-bottom: 0px;
    /* 底部边距调整为0 */
    border: 0px;
    /* 去掉边框 */
    background-color: #e7e7e7;
    /* 设置导航栏背景色 */
    margin-top: 30px;
    /* 设置导航栏的上边距 */
}

/* 导航栏栏目激活时属性 */
.navbar-default .navbar-nav .active a,
.navbar-default .navbar-nav .active a:hover,
.navbar-default .navbar-nav .active a:focus {
    background-color: #005197;
    /* 背景色设置为深蓝色 */
    color: #fff;
    /* 前景文字颜色设置为白色 */
}

/* 一级菜单鼠标移过时属性 */
.navbar-default .navbar-nav li a:hover {
    color: #fff;
    background-color: #005197;
}

/* 一级菜单单击展开时属性 */
.navbar-default .navbar-nav li.open a.on {
    color: #fff;
    background-color:blueviolet;
}

/* 下拉菜单内边距 */
.dropdown-menu {
    padding: 0px;
}

/* 二级菜单标签属性 */
.dropdown-menu li a {
    padding-top: 10px;
    padding-bottom: 10px;
    color: #777;
    text-align: center;
}

/* 一级菜单宽度和文字对齐方式 */
.nav-top {
    width: 14%;
    text-align: center;
}

写个js脚本控制鼠标实现移动到一级菜单自动展开二级菜单,移开实现自动折叠效果

通过jQuery中的mouseover()函数定义鼠标移动到一级菜单的事件,再用addClass函数把open传入那个.dropdown也就是那个一级菜单的标签;实现移动到一级菜单自动展开二级菜单
mouseleave函数表示移开一级菜单时去除open属性,自然就实现了折叠效果

$(function () {
            $(".dropdown").mouseover(function () {
                $(this).addClass("open");
            });
    // mouseleave函数表示移开一级菜单时去除open属性,自然就实现了折叠效果
            $(".dropdown").mouseleave(function () {
                $(this).removeClass("open");
            });
        });

制作广告横幅(一张图片)

<!-- 广告横幅 就是一张照片 -->
<div class="container-fluid">
    <div class="row">
        <img class="img-responsive model-img" src="{% static \'img/science.jpg\' %}" alt="科研基地">
    </div>
</div>

设置导航栏与广告横幅之间的分割线

<!-- 分割线 -->
    <div class="line"></div>

定义.line样式

/* 导航栏与横幅的分割线 */
.line {
    height: 30px;
    width: 100%;
    background: #005197;
}

制作页面主体(这里是copy的)

外层定义一个container容器用来限制主体显示区域,在容器内分别定义了两个div用来显示主体标题和主体详细内容

主体标题如下

<div class="model-details-title">
        科研基地介绍
    </div>

在style.css文件中添加主体标题的样式类 model-details-title;对字体和边距做了一些调整,通过下边框border-bottom进行设置可以构造出一条蓝色的下边框线用作分割线

/* 主题标题 */
.model-details-title{
	padding:15px 0px;
	font-size:18px;
	border-bottom:1px #005197 solid; /* 底部添加蓝色边框 */
	color:#005197;
	margin-bottom:10px;
	margin-top:30px;
}

主体详细内容是关于"科研基地"的一些说明文字和图片,采用来p,h3,h5常用的html标签

<div class="model-details ant">
        <span>
            近二十年来,恒达致力打造“志存高远,一诺千钧”的企业文化,不断吸纳和培养人工智能高精尖人才,
            逐步形成了一支技术过硬、乐于钻研、勇于创新的核心技术团队。目前,恒达科研基地分为计算机视觉、
            机器人和视觉深度学习三个事业部,已为恒达在人脸识别、物联网平台搭建、机器人导航等高新算法
            和模型研究领域打下了坚实基础。未来,恒达将继续坚定不移地投入科研,持续做好团队结构优化
            和技术创新升级,力争实现引领全球人工智能发展的未来愿景。
        </span>
        <img class="img-responsive" style="max-width:700px;" src="{% static \'img/kyjd.jpg\' %}">
        <h3>研究方向</h3>
        <h5>机器人导航:</h5>
        <p>
            多传感器路径规划、物联网一体化平台、远程人机交互、强化学习控制。
        </p>
        <h5>人体行为识别:</h5>
        <p>
            单用户行为识别、人体骨骼大数据分析、鲁棒特征抽取、多用户行为识别。
        </p>
        <h5>人脸属性识别:</h5>
        <p>
            人脸检测、属性分析、行人再识别。
        </p>
    </div>

美化主体内容,设置行高,对齐方式,图片位置等,在style.css中编辑如下

/* 文字段落 */
.model-details span{
	line-height:3px;
	text-indent:2em;
	text-align:justify;
	text-justify:inter-ideograph;
    color:#005197;
}
/* 主体图像 */
.model-details img{
	margin:0px auto;
}

制作logo的二维码放在页面

用的是python的库;qrcode库 :二维码生成包;PIL库:图片处理包

import qrcode # 二维码生成包
from PIL import Image # 图片处理包


def creat_qrcode(url,filename):
    qr = qrcode.QRCode(
        version= 1,
        # 设置容错率未最高
        error_correction=qrcode.ERROR_CORRECT_H,
        box_size=10, # 应该是二维码尺寸
        border= 4, # 二维码边框,边界宽度
    )
    qr.add_data(url) # 把链接传进去
    qr.make(fit=True) # fit等于true是干啥为了制作
    img = qr.make_image() # 不知
    # 设置二维码为彩色
    img = img.convert(\'RGBA\')
    icon = Image.open(filename) # 调用Image模块中的open函数,导入二维码中的图标
    w,h = img.size
    factor = 4 # 不知
    size_w = int(w/factor) # 为了之后的尺寸运算么
    size_h = int(h/factor)
    icon_w,icon_h = icon.size # 和上面img二维码一样,不过这里是图标

    if icon_w > size_w:
        icon_w = size_w
    if icon_h > size_h:
        icon_h = size_h
    icon = icon.resize((icon_w,icon_h),Image.ANTIALIAS) # 不知
    w = int((w-icon_w)/2) # 设置二维码图片大小
    h = int((h-icon_h)/2)
    icon = icon.convert(\'RGBA\')
    newing = Image.new(\'RGBA\',(icon_w + 8,icon_h+8),(255,255,255)) # 不知
    img.paste(newing,(w-4,h-4),newing) # 不知
    img.paste(icon,(w,h),icon)
    img.save(\'qr2.png\',quality = 100) #quality = 100难道是像素

if __name__ == \'__main__\':
    # 这里不能直接加图片名,不知为啥,明明放在同一文件夹下
    creat_qrcode(\'http://120.27.220.235:8001/\',r\'test\\logo1.png\') # 估计Image.open函数的传参有关系
    print(\'完成\')    

制作页脚

页脚主要包括站点地图和版权两部分。

站点地图可以用来通知搜索引擎页面的网址和页面的重要性,帮助站点得到比较好的收录。
版权部分主要用来著名制作的站点的备案信息。网络备案时指向主管击关报告事由存案以备考察,其目的就是为了防止在网上从事非法的网站经营活动,打击不良互联网信息的传播。如果网站不备案的话,很有可能被查处以后关停。

页脚部分设计如下:

下面代码的站点地图和版权部分分别内嵌在class= "row"的两个div内。外层通过样式类class="container web-footer"进行内容限定,其中container为Bootstrap提供的样式类,web-footer为额外定制的样式类

<div class="container web-footer"> 
        <!-- 站点地图 -->
        <div class="row" id="map-footer">这里可填写网页链接</div>
 <!-- 版权 -->
        <div class="row" id="patent-footer">
            <p> © 2019 Python Web企业门户网站开发示例 版权所有 | 苏ICP备19006378号 </p>
        </div>
</div>

编写style.css文件,添加web-footer的样式

.web-footer{
	width:100%;        /* 占满整个浏览器宽度 */
	margin-top:30px;   /* 设置与上边缘距离 */
}

制作站点地图

采用栅格2-2-2-2-4

<dl>来表示这是一个表格,<dt>来表示表格标题,<dd>来表示表格具体的内容

<div class="col-md-2">
                <dl><!--定义是个表格-->
                    <dt>公司简介</dt><!--dt定义表格的标题-->
                    <dd><a href="{% url \'aboutApp:survey\' %}">企业概况</a></dd><!--用来表示表格具体的内容-->
                    <dd><a href="{% url \'aboutApp:honor\' %}">荣誉资质</a></dd>
                </dl>
            </div>
            <div class="col-md-2">
                <dl>
                    <dt>产品中心</dt>
                    <dd><a href="{% url \'productsApp:robot\' %}">家用机器人</a></dd>
                    <dd><a href="{% url \'productsApp:monitoring\' %}">智能监控</a></dd>
                    <dd><a href="{% url \'productsApp:face\' %}">人脸识别解决方案</a></dd>
                </dl>
            </div>
            <div class="col-md-2">
                <dl>
                    <dt>服务支持</dt>
                    <dd><a href="{% url \'serviceApp:download\' %}">资料下载</a></dd>
                    <dd><a href="{% url \'serviceApp:platform\' %}">人脸识别开放平台</a></dd>
                </dl>
            </div>
            <div class="col-md-2">
                <dl>
                    <dt>人才招聘</dt>
                    <dd><a href="{% url \'contactApp:contact\' %}">欢迎咨询</a></dd>
                    <dd><a href="{% url \'contactApp:recruit\' %}">加入恒达</a></dd>
                </dl>
            </div>
            <div class="col-md-4" id="wx">
                <p>扫描二维码,关注我们</p>
                <img class="qrimg" src="{% static \'img/qq.png\' %}" alt="wx">
                <p>客服热线:<b style="font-size:20px">400 111 222</b></p>
            </div>

对站点地图进行样式调整

#map-footer{
	background-color:#3A3A3A;        /* 对整个站点地图设置背景色灰色 */
}
#map-footer dl{
	text-align:center;               /* 站点链接文字对齐 */
	margin-top:40px;                 /* 设置表格与上边缘边距 */
}
#map-footer dt{
	padding:3px;                     /* 表格标题内边距为3像素 */
	color:#fff;                      /* 表格标题颜色为白色 */
}
#map-footer dd{
	padding:3px;                     /* 表格内容内边距为3像素 */	
}

/* 二维码广告段落 */
#map-footer p{
	margin-top:20px;
	margin-bottom:10px;
	color:#fff;
	font-size:16px;
}
#map-footer a{
	color:#A6A6A6;                   /* 站点链接文字颜色设置 */
	font-size:13px;                  /* 站点链接文字大小 设置 */
}
#map-footer a:hover{
	color:#fff;
	text-decoration:none;             /* 去除站点链接鼠标移过时出现的下划线 */
}
#wx{
	text-align:center;                /* 二维码居中对齐 */
/* 二维码所在为最后右边4个栅格,所以居中对齐,还是右边*/
}

.qrimg{
	max-width: 130px;                 /* 二维码最大宽度为170像素 */
}

版权部分的样式

#patent-footer{
	text-align:center;
	background-color:#3A3A3A;
}
#patent-footer p{
	margin-top:10px;
	padding-right:80px;
	color:#8d8d8d;
	font-size:11px;
}

Django模板的页面复用

Django页面复用的模板标签主要有2个

1继承标签

{% extends "base.html" %}

2.动态内容声明标签

block和endblock是固定标签语句,后面的title是自定义的,使用时将非共享的部分用该标签声明(在base.html文件夹中),在继承时也采用该方法将动态内容填入标签中(在各个应用下的html文件中)

{% block title %} 

{% endblock %}

制作共享模板(没做)

一般就是把各个页面相同的部分抽出来,单独放在一个html文件中,在为该文件建个trmplates文件夹,再settings.py中,添加该文件夹的路径,其他应用下的html文件就可以调用该模板了。(这里是跟目录前面才没加前缀,若不是根路径,因该要加前缀;反正碰到路径错误,相对路径不行的话就用绝对路径)

\'DIRS\': [os.path.join(BASE_DIR,\'templates\')], # 配置共享模板路径的地方,也就是根目录下的templates文件夹

共享模板的使用

1.继承共享模板

{% extends "base.html" %} <!--继承标签-->
{% load staticfiles %}

第二行代码是为了导入Django模板静态资源标签

该science.html文件只有两处需要填入动态内容,一处是标题

{% block title %} 
科研基地
{% endblock %}

另一处是主体内容

% block content %} 
<!-- 广告横幅 就是一张照片 -->
<div class="container-fluid">
    <div class="row">
        <img class="img-responsive model-img" src="{% static \'img/science.jpg\' %}" alt="科研基地">
    </div>
</div>
<div class="container">
    <!-- 主体标题 -->
    <div class="model-details-title">
        科研基地介绍
    </div>
    <!-- 主体内容 -->
    <div class="model-details ant">
        <span>
            近二十年来,恒达致力打造“志存高远,一诺千钧”的企业文化,不断吸纳和培养人工智能高精尖人才,
            逐步形成了一支技术过硬、乐于钻研、勇于创新的核心技术团队。目前,恒达科研基地分为计算机视觉、
            机器人和视觉深度学习三个事业部,已为恒达在人脸识别、物联网平台搭建、机器人导航等高新算法
            和模型研究领域打下了坚实基础。未来,恒达将继续坚定不移地投入科研,持续做好团队结构优化
            和技术创新升级,力争实现引领全球人工智能发展的未来愿景。
        </span>
        <img class="img-responsive" style="max-width:700px;" src="{% static \'img/kyjd.jpg\' %}">
        <h3>研究方向</h3>
        <h5>机器人导航:</h5>
        <p>
            多传感器路径规划、物联网一体化平台、远程人机交互、强化学习控制。
        </p>
        <h5>人体行为识别:</h5>
        <p>
            单用户行为识别、人体骨骼大数据分析、鲁棒特征抽取、多用户行为识别。
        </p>
        <h5>人脸属性识别:</h5>
        <p>
            人脸检测、属性分析、行人再识别。
        </p>
    </div>
</div>
{% endblock %}

注意:共享的模板base.html文件也需要用前面所讲的动态声明标签来占据再html页面中,来表示该主体内容

向模板传递动态参数

页面渲染的过程中需要根据实际情况进行导航栏激活状态转换,也就是说后台服务器会根据不同的渲染页面传入不同的模板参数,页面导航栏根据传入的不同参数选择不同的激活状态
(缘由是:切换页面时导航栏的激活效果并没有同步,还是再\'\'首页上\'\')

这就需要jQuery

 <script type="text/JavaScript">
    //dango变量注释它也是读的出来的,是django变量,可以在应用文件夹下的views.py中设置对应变量,一般用字典表示
        $(\'#{{active_menu}}\').addClass("active"); //是为了添加激活属性到#{{active_menu}}该标签中
    </script>

{{active_menu}}也可以在views.py文件中设置,用字典的形式把id为science的值传入变量中

也就是用{{active_menu}}来代替固定的id值

def science(request):
    return render(request,\'science.html\',{\'active_menu\':\'science\'})

第五章

该模块一共包含两个子页面:"企业概况"和"荣誉资质",主要用于介绍企业的基本情况和展示企业获得的荣誉
企业概况页面全部由静态资源构成,包括说明文字和图片等,所以数据不需要从后台数据库读取
企业概况页面主要学习制作侧边栏(竖的列表)
荣誉资质页面则采用动态数据嵌入的方式生成页面,其页面文字和图像均需要从后台读取
其好处在于可以让网站管理员方便的对企业荣誉进行添加、修改和删除。

建立两个页面

在aboutApp文件夹下建立templates文件夹,在其下分别建立honor.html文件和survey.html文件。
接着继承base.html模板

{% extends \'base.html\' %}
{% load staticfiles %}
{% block title %}
这里输入标题名
{% endblock %}
{% block content %}
主体内容
{% endblock %}

编写views.py文件

def survey(requset): # 企业概况 
    return render(requset,\'survey.html\',{\'active_menu\':\'about\',})

def honor(requset):
    return render(requset,\'honor.html\',{\'active_menu\':\'about\',})

如上所见,在render()函数中添加了active_menu参数,这是为了页面切换到公司简介时能够同步地切换导航栏激活状态

重点:虽然我浏览器上没出现,但这存在(我这有可能是把很多样式都编写到style.css文件中去了,其实这不好,可以新键个该应用专用的css文件,放在static中的css文件夹下。然后再html也面用link标签引入该文件)

如果当前已经在\'企业概况\'页面内,此时光标移动到\'荣誉资质\'导航链接上会发现2个链接颜色是一样的。为了有效进行区分,在style.css文件中添加如下代码

/* 二级菜鼠标移过时属性 */
.navbar-default .navbar-nav li ul a:hover{
	color:#fff;
	background-color:#005197;
}
/* 一级菜单激活时二级菜单鼠标移过时属性 */
/* li后面没有active属性,我在base.html中没找到,这是个问题*/
.navbar-default .navbar-nav li.active ul a:hover{ /* hover属性 伪类么 作用呢*/
	color:#fff;
	background-color:#022a4d;
}

制作侧边栏

这里的页面主体分为左右2部分,左边是侧边导航栏,右边是固定位置的图片和介绍性文字

<div class="container">
    <div class="row row-3">
        <!-- 侧边导航栏就是一个div里是标题,另一个div里是列表,并且里面是一个ul,ul里面是2个li,li中是a写链接 -->
        <!--row-3是为div添加额外的样式-->
        <div class="col-md-3">
            这里写的是侧边导航栏链接
        </div>
        <!-- 说明文字和图片 -->
        <div class="col-md-9">
           
    </div>
</div>

row-3是为div添加额外的样式

.row-3{
	margin-top: 30px; /* 设置顶部边距*/
}

侧边导航栏部分采用bs提供的列表组件list-group实现,其中每一个链接列表项用样式list-group-item;

具体写法:

侧边导航栏就是一个div里是标题,另一个div里是列表,并且里面是一个ul,ul里面是2个li,li中是a写链接

div class="col-md-3">
            <div class="model-title"><!--定制导航栏标题样式-->
                公司简介
            </div>
            <div class="model-list"><!--定制列表样式-->
                <ul class="list-group"><!--list-group是bs2中的列表组件-->
                    <li class="list-group-item" id="survey">
                        <a href="{% url \'aboutApp:survey\' %}">企业概况</a>
                    </li>
                    <li class="list-group-item" id="honor">
                        <a href="{% url \'aboutApp:honor\' %}">荣誉资质</a>
                    </li>
                </ul>
            </div>
        </div>

这个{% url \'aboutApp:survey\' %}用于逆向寻找路由,方便后期部署




model-title定制导航栏标题样式;model-list定制列表样式

/* 侧边导航栏样式*/
.model-title{
	text-align: center;
	color: #fff;
	font-size: 22px;
	padding: 15px 0px;
	background: #005197;
	margin-top: 25px;
}
.model-list{ /* 谷歌浏览器中的css样式变化很小,这是整个div很难看出来,看看具体的链接*/
	text-align: center;
	background-color: #f6f6f6;
	font-size: 16px;
}
/*侧边导航栏列表项链接样式 */
.model-list li a{
	color:green; /*链接样式本该是绿色,但谷歌浏览器中没变 */
}
/* 侧边导航栏列表项链接激活样式*/
.model-list li a:hover{
	text-decoration: none;
	color: red;/* 激活看看有没有变化,也没变,这哎*/
}

侧边导航栏也是要写链接切换样式:处于激活状态的链接文字呈现蓝色,其他链接文字呈现灰色

/*侧边导航栏激活样式*/
.model-list li.active{
	text-align:center;
	background-color:#f6f6f6;
	font-size:16px;
	border-color: #ddd;
}
/*侧边导航栏激活状态下鼠标移过时样式*/
.model-list li.active:hover{
	text-align:center;
	background-color:#f6f6f6;
	font-size:16px;
	border-color: #ddd;
}
/*侧边导航栏激活状态时链接样式 li还有active属性 表示激活么*/
/*li.active a a中就有survey和honor两个逆向解析路由,就可以定位到,加上个脚本,用submenu传递变量honor和survey变量,就可以实现*/
/*某一链接处于激活状态时,其链接文字显示为蓝色*/

在base.html文件中添加js脚本,激活样式

 <script>
        $(\'#{{sub_menu}}\').addClass("active");
    </script>

接着在views.py文件中添加额外的sub_menu变量;这样就可以实现侧边导航栏二级菜单的切换

def survey(requset): # 企业概况 
# render() 缺少 1 个必需的位置参数:\'template_name\'

    return render(requset,\'survey.html\',{\'active_menu\':\'about\',
                                        \'sub_menu\':\'about\',

    })
def honor(requset):
    awards = Award.objects.all() # 通过模型的objects.all()的得到一个查询集并存放于变量awards中
    return render(requset,\'honor.html\',{\'active_menu\':\'about\',
                                        \'submenu\':\'honor\',

    })

Django数据库模型

本节用数据库模型渲染"荣誉资质"页面,其基本流程如下

1.用户通过浏览器请求页面

2.服务器收到浏览器请求,更具URL路由找到匹配的视图处理函数

3.视图处理函数首先找到需要返回的HTML模板文件,然后从数据库中提取出数据(图片数据对应的就是图片的存储路径),然后将数据过滤后以模板变量形式插入到模板文件中,最后通过render()函数返回生成的页面

4.浏览器收到请求页面并显示

使用django进行数据库开发的步骤

1.配置数据库连接信息

2.在模型文件models.py中定义模型类

3.数据库模型迁移

4.通过类和对象完成数据增删改查操作

第一项配置数据库连接信息在settings.py文件中,找到其中的DATABASES字段,默认配置如下

DATABASES = {# 这里为数据库配置参数
    \'default\': {
        \'ENGINE\': \'django.db.backends.sqlite3\',# 默认数据库引擎
        \'NAME\': os.path.join(BASE_DIR, \'db.sqlite3\'), #数据库路径为当前根目录下的db.sqlite3数据库文件
    }# djano中与数据库相关的代码通常写在models.py文件中
}

第二项这里是创建荣誉模型

class Award(models.Model): # 荣誉模型 在后台界面Award会以模型名的复数形式显示
    # TextField和ImageField需要额外配置路径
    description = models.TextField(max_length=500, # 字段允许的最大长度
                                   blank=True, # 允许字段为空
                                   null=True, # 允许字段为空
                                   verbose_name=\'荣誉描述\') # verbose_name是把该函数的名字description定义为中文了
    photo = models.ImageField(upload_to=\'Award/\', # 定义图片的上传路径,上传的图片会存在服务器媒体资源路径的Award文件夹下
                              blank=True,
                              verbose_name=\'荣誉照片\')

    class Meta:
        verbose_name = \'获奖和荣誉\' # verbose_name为模型定义的别名
        verbose_name_plural = \'获奖和荣誉\' # verbose_name_plural为模型定义别名的负数形式

额外链接 django 数据模型常用字段类型

注意点:关于文件上传的字段;ImageField和FileField需要额外进行路径设置。即需要在项目中指定媒体资源文件存储目录

坑点: media/就不可以形成保存图片的文件夹Award了

MEDIA_URL = \'/media/\'
MEDIA_ROOT = os.path.join(BASE_DIR,\'media\') # 这个media后不需要加/不然报路径错误;django路径错误太多了
# media/就不可以形成保存图片的文件夹Award了

创建完模型就需要将创建的模型同步到数据库中;在终端输入如下命令

python manage.py makemigrations # 这步只是做好模型存入数据库的准备,有点像git add. 并没有推送,只是提交到暂缓区;还需要git push才能推到库中
python manage.py migrate # 同步数据库

Django后台管理系统

Django能够根据定义的模型自动生成管理模板,使用Django的管理功能只需要以下两步

1.创建超级管理员

python manage.py createsuperuser
#提示输入 用户名 ;email;password(输入两次)

2.注册模型类

在admin.py文件下

from django.contrib import admin
from .models import Award # 同一目录下,还需用.么


class AwardAdmin(admin.ModelAdmin): # 继承admin.ModelAdmin
    list_display = [\'description\',\'photo\'] # 将需要编辑的模型字段添加进来
# 下方的Award是models中的类
admin.site.register(Award,AwardAdmin) # admin.site.register()函数将Award和AwardAdmin绑定并实现注册

动态页面渲染

重新编辑views.py文件中的honor()函数;通过模型的objects.all()的得到一个查询集并存放于变量awards中;在页面渲染时通过render()函数将awards变量以参数形式添加到页面中

def honor(requset):
    awards = Award.objects.all() # 通过模型的objects.all()的得到一个查询集并存放于变量awards中
    return render(requset,\'honor.html\',{\'active_menu\':\'about\',
                                        \'submenu\':\'honor\',
                                        \'awards\': awards, # 在页面渲染时通过render()函数将awards变量以参数形式添加到页面中
    })

编辑honor.html页面,使用Bootstrap的缩略图组件(样式类 thumbnail)略加修改即可实现;有两个缩略图,每个缩略图包括一个标签用于显示图像以及一个<div class="caption">的div标签用于显示图片对应的描述信息。每个缩略图在大屏幕浏览器下占4个栅格,在小屏浏览器上占6个栅格;
动态数据写入HTML中,采用的是模板语句是{% for award in awards %}{% endfor %},这个语句地意思是逐个取出awards中地每一条数据并复制到新地临时变量award中,每一个award都包含一项photo和descriptions数据(这里可查看views.py文件地编写);

该模板标签可以动态地遍历传入地变量,实现页面循环写入;

{% block content%}
<!-- 广告横幅 -->
<!--关于后台服务器渲染页面的过程:把前端的静态页面写好,把模型model写好,创建号图片文件夹,然后创建admin管理员,model中已经有该文件夹的路径,直接导入model到views.py中,然后从views.py中渲染到前端页面-->
<div class="container-fluid">
    <div class="row">
        <img class="img-responsive model-img" src="{% static \'img/about.jpg\' %}">
    </div>
</div>
<!-- 主体内容 -->
<div class="container">
    <div class="row row-3">
        <!-- 侧边导航栏 -->
        <div class="col-md-3">
            <div class="model-title">
                公司简介
            </div>
            <div class="model-list">
                <ul class="list-group"> <!--侧边栏部分采用Bootstrap提供的列表组件 list-group实现-->
                    <li class="list-group-item" id="survey">
                        <a href="{% url \'aboutApp:survey\' %}">企业概况</a>
                    </li>
                    <li class="list-group-item" id="honor">
                        <a href="{% url \'aboutApp:honor\' %}">荣誉资质</a>
                    </li>
                </ul>
            </div>
        </div>
        <!-- 说明文字和图片 -->
        <div class="col-md-9">
            <div class="model-details-title">
                荣誉资质
            </div>
            <div class="row">
                {% for award in awards %}
                <div class="col-sm-6 col-md-4">
                    <div class="thumbnail"> <!--Bootstrapy现成的缩略图组件thumbnail样式类-->
                        <img src="{{award.photo.url}}"> <!--缩略图的路由-->
                        <div class="caption">
                            <p>{{award.description}}</p><!--缩略图的描述-->
                        </div>
                    </div>
                </div>
                {% endfor %}
            </div>
        </div>
    </div>
</div>
{% endblock %}

坑点

这里由于是debug模式下没有将动态资源路径MEDIA_URL添加到静态路由static下,需要配置主应用地urls.py文件

from django.conf import settings
from django.conf.urls.static import static
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL,
                        document_root = settings.MEDIA_ROOT)

优化管理后台界面

1.界面汉化

在settings.py文件中配置

LANGUAGE_CODE = \'zh-Hans\' # 大写的H

TIME_ZONE = \'Asia/Shanghai\'# 大写的上海开头S;那为啥上面的z中国不大写

2.修改管理系统名称(异或:好像每个应用下地admin文件夹都可以修改管理系统地名字,若同时修改,以哪一个为先呢)

打开aboutApp应用下地admin.py文件,在文件末尾添加如下代码

# 修改管理系统名称
admin.site.site_header = \'xinbancan的Django后台管理\'
admin.site.site_title = \'xinbancan登录

主界面优化

1.模型名称修改
用别名代替模型地真实名来显示,只需修改模型地Meta元信息即可实现。打开aboutApp文件夹下的models.py文件
在后台管理界面 Award模型类会以复数形式表示;verbose_name_plural为模型定义别名的负数形式;

class Award(models.Model): # 荣誉模型 在后台界面Award会以模型名的复数形式显示
    # TextField和ImageField需要额外配置路径
    description = models.TextField(max_length=500, # 字段允许的最大长度
                                   blank=True, # 允许字段为空
                                   null=True, # 允许字段为空
                                   verbose_name=\'荣誉描述\') # verbose_name是把该函数的名字description定义为中文了
    photo = models.ImageField(upload_to=\'Award/\', # 定义图片的上传路径,上传的图片会存在服务器媒体资源路径的Award文件夹下
                              blank=True,
                              verbose_name=\'荣誉照片\')

    class Meta:
        verbose_name = \'获奖和荣誉\' # verbose_name为模型定义的别名
        verbose_name_plural = \'获奖和荣誉\' # verbose_name_plural为模型定义别名的负数形式

2.应用名称地修改

编写aboutApp文件夹下的__init__.py文件(不太理解;理解的话一般就会自己写了)

# __init__.py文件竟然可以修改app名字
from os import path
from django.apps import AppConfig # 修改名字主要用到的是django.apps模块中的AppConfig方法
 
VERBOSE_APP_NAME = \'公司简介\' #VERBOSE_APP_NAME通过修改VERBOSE_APP_NAME字段为应用添加别名

# 这因该是设置名字路径,可下面的__file__如何理解
def get_current_app_name(file):
    return path.dirname(file).replace(\'\\\', \'/\').split(\'/\')[-1]
 
class AppVerboseNameConfig(AppConfig):
    name = get_current_app_name(__file__)
    verbose_name = VERBOSE_APP_NAME # 设置别名
 
# 这里是传参等于这个变量default_app_config 不理解 看Django官方文档把
default_app_config = get_current_app_name(__file__) + \'.__init__.AppVerboseNameConfig\'

列表界面优化

重写编辑Award模型中地description和photo字段,为每个字段添加verbose_name属性

description = models.TextField(max_length=500, # 字段允许的最大长度
                                   blank=True, # 允许字段为空
                                   null=True, # 允许字段为空
                                   verbose_name=\'荣誉描述\') # verbose_name是把该函数的名字description定义为中文了
    photo = models.ImageField(upload_to=\'Award/\', # 定义图片的上传路径,上传的图片会存在服务器媒体资源路径的Award文件夹下
                              blank=True,
                              verbose_name=\'荣誉照片\')

总结:

本章学了Django数据模型创建方式;
后台管理系统地使用技巧;
前端动态页面渲染动态数据地方法;
bs组件侧边栏和缩略图



第六章(使用了大量的模板和变量)

开发产品中心模块,制作的3个产品列表字页面,每个页面均是以产品展示列表,其设计基本相同,唯一不同的是每个子页面展示不同种类地产品。
这里实现的功能是:路由传递参数实现页面切换,就是让每一个子页面共享同一个访问路由并且共享同一个视图处理函数,仅需在路由后面附带不同地参数,视图处理函数根据这个附带参数来区别开子页面。
实现效果就是:只需要编写一个子页面实现3个页面均能展示;产品列表页面到产品详情页面仅需要用户单击某一产品时将该产品的id随页面请求传递到后台,后台根据编号从数据库中取出该产品详情信息,包括文字描述和图片信息等,最后将数据写入产品详情页面并返回给用户浏览器浏览。

编写页面

在productsApp文件夹下创建个templates文件夹,在其下新建个productList.html文件,
这里采用了模板变量{{productName}}进行传参,后台在渲染过程中需要额外地传入productName参数;
每个产品链接herf属性均采用如下形式href="{% url \'应用名:路由名\' \'字符串\' %}";这里相应的要修改views.py文件中路由名和base.html文件中的productApp应用的链接属性
上述路由通过逆向寻找网址,在该标签后面的字符串表示该路由带字符型参数,该参数将由后台解析。

{% extends "base.html" %}
{% load staticfiles %}

{% block title %}
{{productName}}<!--后台在渲染过程中需要额外地传入productName参数-->
{% endblock %}

{% block content %}
<link href="{% static \'css/products.css\' %}" rel="stylesheet">
<!-- 广告横幅 -->
<div class="container-fluid">
    <div class="row">
        <img class="img-responsive model-img" src="{% static \'img/products.jpg\' %}">
    </div>
</div>
<!-- 主体内容 -->
<div class="container">
    <div class="row row-3">
        <!-- 侧边导航栏 -->
        <div class="col-md-3">
            <div class="model-title">
                产品中心
            </div>
            <div class="model-list">
                <ul class="list-group">
                    <li class="list-group-item" id="robot">
                        <!--逆向解析  \'应用名:路由名\' \'字符串\' (字符串表示该路由带字符型参数,该参数将由后台进行解析)-->
                        <a href="{% url \'productsApp:products\' \'robot\' %}">家用机器人</a> <!--这里后面代入的参数不一样 如 \'robot\'-->
                    </li>
                    <li class="list-group-item" id="monitor">
                        <a href="{% url \'productsApp:products\' \'monitor\' %}">智能监控</a>
                    </li>
                    <li class="list-group-item" id="face">
                        <a href="{% url \'productsApp:products\' \'face\' %}">
                            人脸识别解决方案</a>
                    </li>
                </ul>
            </div>
        </div>
        <!-- 说明文字和图片 -->
        <div class="col-md-9">
            <div class="model-details-title">
                {{productName}}
            </div>
            <!-- 此处填入产品列表内容 -->
            <!--第六章使用了大量的模板;一般模板变量都是在views.py文件中定义的模板来源是从数据库中提取的,也就是models.py文件中-->
            <div class="model-details">
                {% for product in productList %} <!--这些变量由来,在视频的1个小时14分钟左右-->
                <div class="row">
                    <div class="col-md-6">
                        <!--product.productImgs.all是通过models.py文件中ProductImg模型类中product字段的erlated_name参数  因该是映射,没找到其他模板定义了product.productImgs.all-->
                        {% for img in product.productImgs.all %} <!--取出每一张图片,productImgs-->
                        {% if forloop.first %}<!--判断当前是否遍历到产品图片中的第一幅图片,是的话,则显示第一幅图片-->
                        <a href="{% url \'productsApp:productDetail\' product.id %}" class="thumbnail row-4">
                            <img class="img-responsive model-img" src="{{img.photo.url}}">
                        </a>
                        {% endif %}
                        {% endfor %}
                    </div>
                    <div class="col-md-6">
                        <!--truncatechars是Django提供的模板过滤器,可以对文字进行截断显示-->
                        <!--linebreaks使文字的换行能够有效的在HTML中实现 -->
                        <h3>{{ product.title|truncatechars:"20" }}</h3> <!--这种定义名称好厉害-->
                        <p>{{ product.description|truncatechars:"150"|linebreaks }}</p> <!--linebreaks文字换行-->
                        <div class="thumbnail row-5">
                            <div class="caption"><!-- product.id 是个模板变量,根据产品id可变更-->
                                <a href="{% url \'productsApp:productDetail\' product.id %}" class="btn btn-primary" role="button">
                                    查看详情
                                </a><!--btn-primary按钮样式类-->
                            </div>
                        </div>
                    </div>
                </div>
                {% endfor %}

                {% if pageData %} <!--传入pageData变量来实现分页-->
                <div class="paging">
                    <ul id="pages" class="pagination pagination-sm pagination-xs">
                        {% if pageData.first %}
                        <li><a href="?page=1">1</a></li>
                        {% endif %}
                        {% if pageData.left %}
                        {% if pageData.left_has_more %}
                        <li><span>...</span></li>
                        {% endif %}
                        {% for i in pageData.left %}
                        <li><a href="?page={{i}}">{{i}}</a></li>
                        {% endfor %}
                        {% endif %}
                        <li class="active"><a href="?page={{pageData.page}}">{{pageData.page}}</a></li>
                        {% if pageData.right %}
                        {% for i in pageData.right %}
                        <li><a href="?page={{i}}">{{i}}</a></li>
                        {% endfor %}
                        {% if pageData.right_has_more %}
                        <li><span>...</span></li>
                        {% endif %}
                        {% endif %}
                        {% if pageData.last %}
                        <li><a href="?page={{pageData.total_pages}}">{{pageData.total_pages}}</a></li>
                        {% endif %}
                    </ul>
                </div>
                {% endif %}
            </div>
        </div>
    </div>
</div>
{% endblock %}

在编写productsApp文件夹下的ulrs.py文件

这里重写把三个路由变成一个路由;要从URL中捕获值,需要使用尖括号<>来定义路由附带参数

urlpatterns = [
    # <str:productName>其实没看董;str因该是参数类型,参数类型可以自行定义
    path(\'products/<str:productName>/\', views.products, name=\'products\'),#产品列表
    path(\'productDetail/<int:id>/\', views.productDetail, name=\'productDetail\'),#产品详情
]

修改views.py文件用来对该映射路由进行处理
删除原来三个映射路由处理函数,重写编写一个

1.这里首先定义了带参数的products()函数,参数名productName与URL文件中的路由参数名相同。请求经过url解析,对应的productName参数会自动传入products()函数(也就是页面中也就是html页面中传参过来)

2.根据传入的productName参数转化为对应的中文,并将变量传入最后的render()响应函数的字典变量中。

def products(request,productName): # productName这个从哪传来,页面中也就是html页面中传参过来,这个实现好厉害
    submenu = productName
    if productName == \'robot\':
        productName = \'家用机器人\'
    elif productName == \'monitor\':
        productName = \'智能监控\'
    else:# 这算不算漏洞,没有判断,什么最后都等于他
        productName = \'人脸识别解决方案\'

 return render(request,\'productList.html\',{
                \'active_menu\':\'products\',
                \'sub_menu\':submenu, # 这里的sub_menu我记得第五章是激活侧边栏状态把(就是个列表 竖的)
                \'productName\':productName, # 这是定义的变量})

产品列表模型的操作

一个产品可以有多张图片,这就是外键的概念了;模型在创建时都有一个主键id用来区分模型中的每一条数据,对于产品模型,使用主键id作为唯一的区分标识。对于产品图片来说可以通过定义外键的形式来表示与产品模型之间的多对一关系,这里的外键也就是产品模型的主键。(实话,最后一句没理解)
这里类型字段的作用代码中有注释
写个self吧,它就好像是这个类Product自身,可以随意调用类中的属性self.title,可以看我之前写的类属性直接调用类
定义产品类

class Product(models.Model):
    \'\'\'
    产品分为3种类型,每种类型的定义在PRODUCTS_CHOICES中
    给出,PRODUCTS_CHOICES是一个元组,在定义productType字段
    时通过参数choices字段传入使用
    choices因该是特殊的django变量,在后台管理器页面呈现带下拉按钮的选择框
    不用choice还是这个的话,那就是原来元组数据在后台显示为选择框
    
    \'\'\'
    PRODUCTS_CHOICES = (
        (\'家用机器人\', \'家用机器人\'),
        (\'智能监控\', \'智能监控\'),
        (\'人脸识别解决方案\', \'人脸识别解决方案\'),
    )
    title = models.CharField(max_length=50, verbose_name=\' 产品标题\') # 字符字段CharField最大长度限定在50以内
    description = models.TextField(verbose_name=\'产品详情描述\') # TextField文本字段
    productType = models.CharField(choices=PRODUCTS_CHOICES, # 定义了产品类型 选择choice就是下拉菜单
                                   max_length=50,
                                   verbose_name=\'产品类型\')
     # 小数字段DecimalField;max_digits和decimal_places不知道是啥意思
    price = models.DecimalField(max_digits=7,
                                decimal_places=1,
                                blank=True,
                                null=True,
                                verbose_name=\'产品价格\')
    # 时间字段DateTimeField,这里使用timezone.now设置默认值为当前时间
    publishDate = models.DateTimeField(max_length=20,
                                       default=timezone.now, #from django.utils import timezone导入timezone模块  

                                       verbose_name=\'发布时间\')
    # 整数字段PositiveIntegerField,该字段记录该产品页面被浏览的次数
    views = models.PositiveIntegerField(\'浏览量\', default=0) # 浏览量默认为0
    # 额外定义了一个__str__字段,该函数以self为参数,这里的self即为自身,返回值为当前模型的title字段
    # 该函数的作用是使得每条数据在后台管理系统列表中均以每条数据的title字段来显示
    def __str__(self):
        return self.title

    class Meta:
        verbose_name = \'产品\'
        verbose_name_plural = \'产品\'
        ordering = (\'-publishDate\', )  # 在后台管理系统中产品将按产品发布时间由近到远来显示

定义产品图片类,该类从属于产品类(留一下:related_name难道是特殊django变量,用空查下)

该类有两个字段:

product:产品字段,这是一个外键models.ForeignKey,外键第一个参数必须指明所属的类,这里是产品类
photo:用来存储上传图片的文件夹,这里是其根路径由settings.py中的media参数设置,而upload_to决定了子路径,就是在前面的路径下创建一个文件夹

class ProductImg(models.Model):
    # product是小写,在该函数中唯一
    # 这是一个外键models.ForeignKey,外键第一个参数必须指明所属的类,这里是产品类
    # related_name难道是特殊django变量,用空查下
    product = models.ForeignKey(Product,  #ForeignKey外键一定要在该产品列表中 Product类导入到该文档中
                                related_name=\'productImgs\', # 这里也是小写,related_name用来声明逆向名称
                                verbose_name=\'产品\',
                                on_delete=models.CASCADE) # models.CASCADE这个数据模型不清楚,他说这里是为了
                                # 避免两个表的数据不一致问题而额外设置的;并不理解哪两个表,还有外键

    photo = models.ImageField(upload_to=\'Product/\',  # 用来存储上传图片的文件夹
                              blank=True,
                              verbose_name=\'产品图片\')

    class Meta:
        verbose_name = \'产品图片\'
        verbose_name_plural = \'产品图片\'

注册模型

在admin.py中

from django.contrib import admin

# Register your models here.
# 注册你的模块在这里
from .models import Product,ProductImg

# 每注册一次,或者说,改变模型一次,都要进行一次同步到数据库中
# 下面是分开注册两个模型类

admin.site.register(Product)
admin.site.register(ProductImg)

然后记得同步数据库,我就不写代码了

后台管理系统多对一模型处理

由于模型之间的关联性(产品模型和产品图片模型),当删除\'产品\'模型中的某一条数据时,会同时删除其包含的所有\'产品图片数据\'。
由于是分开注册的两个模型,添加过程需要分为2步完成,但还有一种方式可以一步完成,就是关联模型协同管理方法,即将产品和产品图片模型放在一个页面进行管理

这里再admin.py中编写如下代码

admin.StackedInline对象 创建了内联模型管理器ProductImgInline(不加内联两个字也行)
一起注册的话,要创建2个模型管理器,一个模型管理器内嵌另一个就可以了,然后在和另一个模型一起注册

# 一起注册两个模型类
# admin.StackedInline对象 创建了内联模型管理器ProductImgInline(不加内联两个字也行)
class ProductImgInline(admin.StackedInline): # 模型管理器
    model = ProductImg # 对应的model属性是ProductImg图片类
    extra = 1     # 默认显示条目的数量;设置子模型在父模型中的显示数目

#  admin.StackedInline对象 创建了模型管理器ProductAdmin
class ProductAdmin(admin.ModelAdmin):
    # 内联属性inlines指向前面创建的ProductImgInline
    inlines = [ProductImgInline,] # 模型内嵌模型管理器

# 原来一起注册的话,要创建2个模型管理器,一个模型管理器内嵌另一个就可以了,然后在和另一个模型一起注册
admin.site.register(Product, ProductAdmin) # 一起注册显示,文字和图片

模型数据过滤、排序和渲染

在读取数据时,需要根据不同的产品类型提取不同的数据,因此需要实现数据过滤的功能。还希望最小发布的产品能够显示在页面最前端,也就是说,在数据提取时需要按照时间进行排序。
数据过滤和排序的核心函数分别是filter()和order_by()
在views.py文件中添加如下代码

 # 从数据库中提取数据,也就是这里的Product模块
    # 提取数据用objects.all()
    # 过滤用filter;这里是找到所有产品模型Product中productType字段为productName的产品并把它提取出来
    # 排序用order_by(\'-publishDate\');排序方式以产品模型中的publishDate字段为依据;负号表示时间由近到远
    productList = Product.objects.all().filter(
        productType = productName).order_by(\'-publishDate\')

设计产品列表的页面主体,然后实现数据模型的渲染

产品列表部分采用左右对称方式排列产品内容。左侧显示产品图像,右侧显示产品标题和详细内容。

注意点:

1.一款产品可显示多款图片,这里为了方便,只显示每款产品的第一幅图像
2.由于产品的描述信息可能较长,这里使用bs的缩略图组件来排列产品文字信息,达到字数限制的目的

<div class="model-details">
                {% for product in productList %} 
                <div class="row">
                    <div class="col-md-6">
                        <!--product.productImgs.all是通过models.py文件中ProductImg模型类中product字段的related_name参数  因该是映射,没找到其他模板定义了product.productImgs.all-->
                        {% for img in product.productImgs.all %} <!--取出每一张图片,productImgs-->
                        {% if forloop.first %}<!--判断当前是否遍历到产品图片中的第一幅图片,是的话,则显示第一幅图片-->
                        <a href="{% url \'productsApp:productDetail\' product.id %}" class="thumbnail row-4">
                            <img class="img-responsive model-img" src="{{img.photo.url}}">
                        </a>
                        {% endif %}
                        {% endfor %}
                    </div>
                    <div class="col-md-6">
                        <!--truncatechars是Django提供的模板过滤器,可以对文字进行截断显示-->
                        <!--linebreaks使文字的换行能够有效的在HTML中实现 -->
                        <h3>{{ product.title|truncatechars:"20" }}</h3> <!--这种定义名称好厉害-->
                        <p>{{ product.description|truncatechars:"150"|linebreaks }}</p> <!--linebreaks文字换行-->
                        <div class="thumbnail row-5">
                            <div class="caption"><!-- product.id 是个模板变量,根据产品id可变更-->
                                <a href="{% url \'productsApp:productDetail\' product.id %}" class="btn btn-primary" role="button">
                                    查看详情
                                </a><!--btn-primary按钮样式类-->
                            </div>
                        </div>
                    </div>
                </div>
                {% endfor %}

1.首先使用模板标签 {% for product in productList %} 来遍历传入的产品列表变量productList,此时遍历的每个产品赋值到新变量product中
2.为了能够取出每一幅图像,需要使用ProductImg模型中product字段的relate_name参数,通过该参数来获取对应的产品图片列表。对应上述代码中的 {% for img in product.productImgs.all %}(待补:其实这里没有立即relate_name参数的作用,难道是Django特殊变量)
3.接下来采用模型标签{% if forloop.first %}判断当前是否遍历到产品图片中的第一幅图片,是的话,则显示第一幅图片
4.产品文字部分采用{{product.title}}模板变量来实现,其后紧跟的truncatechars为Django提供模板过滤器,可以对文字进行截断显示;truncatechars:"20" 表示截断字数20,linebreaks文字换行。多个模板标签同时作用可用|来分隔开{{|}}

对列表的页面做些美化

.model-details .row-4{
  margin-top:20px;
}
.model-details .row-5{
 border:none
}

Django分页显示(重点)

Django提供了一个分页类Paginator来帮助管理分页数据,这个类存放在django/core/paginator.py文件中,它可以接收列表、元组或其他可迭代的对象。

1.首先在views.py文件中引入分页对象

from django.core.paginator import Paginator # Django提供的分页类

2.接下来可以在product()函数中使用该分页对象,具体操作是将数据库中取出的产品数据productList直接作为分页类的参数来创建分页对象,然后通过分页对象来控制页面的切换


    # 分页 用空研究下原理
    p = Paginator(productList, 2) # 参数2表示希望每页显示多少条数据  #  Paginator帮助管理分页数据
    if p.num_pages <= 1:
        pageData = \'\'
    else:
        page = int(request.GET.get(\'page\', 1)) # 页面的获取方式是通过这段代码得到的
        productList = p.page(page)
        left = []
        right = []
        left_has_more = False
        right_has_more = False
        first = False
        last = False
        total_pages = p.num_pages
        page_range = p.page_range
        if page == 1:
            right = page_range[page:page + 2]
            print(total_pages)
            if right[-1] < total_pages - 1:
                right_has_more = True
            if right[-1] < total_pages:
                last = True
        elif page == total_pages:
            left = page_range[(page - 3) if (page - 3) > 0 else 0:page - 1]
            if left[0] > 2:
                left_has_more = True
            if left[0] > 1:
                first = True
        else:
            left = page_range[(page - 3) if (page - 3) > 0 else 0:page - 1]
            right = page_range[page:page + 2]
            if left[0] > 2:
                left_has_more = True
            if left[0] > 1:
                first = True
            if right[-1] < total_pages - 1:
                right_has_more = True
            if right[-1] < total_pages:
                last = True
        pageData = {
            \'left\': left,
            \'right\': right,
            \'left_has_more\': left_has_more,
            \'right_has_more\': right_has_more,
            \'first\': first,
            \'last\': last,
            \'total_pages\': total_pages,
            \'page\': page,
        }

描述下上述代码就是productList直接作为分页类的参数,这样就可以对页数进行拆分,然后找到对应页数的数据。为了能够动态的变换页面,在逻辑上进行了多种判断处理。这段代码着重分析(待补)

3.最后将有关分页的一些关键数据以字典\'键-值\'对形式存储与变量pagaData中,在最终渲染时,只需要额外的添加变量pagaData即可

return render(request,\'productList.html\',{
                \'active_menu\':\'products\',
                \'sub_menu\':submenu, # 这里的sub_menu我记得第五章是激活侧边栏状态把(就是个列表 竖的)
                \'productName\':productName, # 这是定义的变量
                \'productList\':productList, # 这是定义的模板
                \'pageData\': pageData, #\' pageData\'这个我之前加了个空格,导致并不能分页显示;只能显示前两个页面
    })

4.编写前端分页控件代码逻辑(重点)

通过传入的pagaData变量来实现分页。由于当前分页数据以两条数据为一页,因此当产品数量不足两条时不显示分页控件,这个功能通过代码 {% if pageData %}来实现。当产品数量超过两个以上时,就需要分页控件
分页控件本质上是由指定数量的链接<a>组成的,每个链接采用类似<a href="?page=1">的语句来连接到指定页数的列表项,这里注意带参数的访问网址的基本写法(这里是加问好?),用户在单击该链接时会将参数page封装到request中,后台会从request中解析到page变量值

 {% if pageData %} <!--传入pageData变量来实现分页-->
                <div class="paging">
                    <ul id="pages" class="pagination pagination-sm pagination-xs">
                        {% if pageData.first %}
                        <li><a href="?page=1">1</a></li>
                        {% endif %}
                        {% if pageData.left %}
                        {% if pageData.left_has_more %}
                        <li><span>...</span></li>
                        {% endif %}
                        {% for i in pageData.left %}
                        <li><a href="?page={{i}}">{{i}}</a></li>
                        {% endfor %}
                        {% endif %}
                        <li class="active"><a href="?page={{pageData.page}}">{{pageData.page}}</a></li>
                        {% if pageData.right %}
                        {% for i in pageData.right %}
                        <li><a href="?page={{i}}">{{i}}</a></li>
                        {% endfor %}
                        {% if pageData.right_has_more %}
                        <li><span>...</span></li>
                        {% endif %}
                        {% endif %}
                        {% if pageData.last %}
                        <li><a href="?page={{pageData.total_pages}}">{{pageData.total_pages}}</a></li>
                        {% endif %}
                    </ul>
                </div>
                {% endif %}

为分页控件做些样式调整(注意:一般时按钮都需要做样式调整),使其居中显示并且对激活状态的链接设置背景色和边框色。编辑products.css文件,添加代码如下

/* 分页控件样式 */
.paging{
	text-align:center;
}
.pagination .active a{
	background-color:#005197;
	border-color:#005197;
}

制作产品详情页面(待补)

\'产品详情\'页面附属于产品列表页面,用户在浏览产品列表时单击某一产品即可进入\'产品详情\'页面查看该款产品的详细介绍。

实现上述功能的基本流程(重点)

1.用户在产品列表中单击某一产品链接,该链接包含产品的唯一id作为附带参数
2.服务器接收请求,通过定义好的URL调用带参数的视图函数进行处理
3.视图函数根据传入的id参数从数据库中查找对应的产品,找到产品后通过render()函数返回产品内容给客户端
这里的难点在于:用户单击产品链接的时候需要能够准确的获取产品id,并将其作为参数附带在访问网址中。
之前链接的参数是如下形式:<a href="{% url \'productsApp:products\' \'robots\' %},这里的参数就是字符串\'robot\',该参数是通过硬编码的方式直接写入到HTML文件中,因此该链接的参数是无法更具实际情况动态变化的。而根据产品详情页面,所附带的参数是需要更具产品id动态变更的
所以解决方案就是:其链接附带的参数也通过模板变量来代替,而模板变量的值即为产品id
对应的链接形式如下:

<a href="{% url \'productsApp:productDetail\' product.id %}

将上述访问路径替换到productList.html产品列表图片和c\'查看详情\'按钮的herf属性中,使得用户无论单击产品图片还是单击\'查看详情\'按钮都可以进入该产品的详情页面

为产品详情页面配置路由

 path(\'productDetail/<int:id>/\', views.productDetail, name=\'productDetail\'),#产品详情

该路由映射到视图中的productDetail()函数

def productDetail(request, id):
    # 根据模型id查找指定的产品数据,如果查找不到则会返回404错误
    product = get_object_or_404(Product, id=id)  # 需导入from django.shortcuts import get_object_or_404
    product.views += 1 # 同步更新该款产品的访问量
    product.save() # 保存到数据库中
    return render(request, \'productDetail.html\', {
        \'active_menu\': \'products\',
        \'product\': product,
        
    })

get_object_or_404()函数可以根据模型id查找指定的产品数据,如果查找不到则会返回404错误。(重点:想想这个函数是如何实现的呢)
这里是productDetail()函数根据前面定义的路由附带参数id,然后再函数实现部分通过get_object_or_404()函数查找指定id的产品并由render()函数返回给前端
找到指定的产品后,需要同步更新该款产品的访问量views,并且通过product.save()函数将改动保存到数据库中

设计产品详情页面

由于视图处理函数已经传回了当前需要渲染的product变量,因此只需要再产品详情中调用product相关字段即可

在productsApp/templates文件夹中新增productDetail.html文件

{% extends "base.html" %}
{% load staticfiles %}

{% block title %}
产品详情
{% endblock %}

{% block content %}
<link href="{% static \'css/products.css\' %}" rel="stylesheet">
<!-- 主体内容 :产品图片、产品完整描述信息、产品参考价格-->
<div class="container">
    <div class="model-details-product-title"> <!-- 主体内容 引用变量标题-->
        {{product.title}}
    </div>
    <div class="model-details"> <!-- 主体内容 引用变量图片-->
        {% for img in product.productImgs.all %}
        <div class="row-4">
            <img class="img-responsive" src="{{img.photo.url}}">
        </div> <!--产品图片以堆叠的方式显示在主体头部-->
        {% endfor %}
        <h3>产品介绍</h3><!-- 主体内容 引用变量产品介绍-->
        <p>
            {{product.description|linebreaks}}
        </p>
        <h3>参考价格</h3>
        <p>
            {{product.price}}元
        </p>
    </div>
</div>
{% endblock %}

在productd.css文件,添加样式代码

.model-details-product-title{
	padding:15px 0px;
	font-size:18px;
	border-bottom:1px #005197 solid; 
	color:#005197;
	margin-bottom:10px;
	margin-top:10px;
	text-align:center;
}

第七章

开发新闻动态模块
同样有三个子页面:\'企业要闻\'、\'行业新闻\'和通知公告
本章对于新闻模型的渲染不再局限于固定格式(图像显示在前、文字显示在后),而是可以通过一种富文本技术让后台管理员自己编辑页面,使得图像和文字可以在任意位置排布。
另外本章会提供新闻的搜索功能,通过简单的数据库精确匹配搜索和基于django-haystack的模糊匹配搜索学习如何构建模型数据的搜索功能。

富文本编辑器介绍

在第六章制作产品详情页面时,后台需要先创建一条包含图片和文字描述的产品数据,然后再将产品文字和图片渲染在前端浏览器上。这些文字和图片子在前端显示的时候往往没有格式或者格式是固定的,并且这些数据需要采用模型外键关联的方式进行管理,这种处理不方便也不直观,渲染的页面比较死板,管理员无法个性化的定制页面。富文本编辑器就是为了解决这个问题而产生的
富文本编辑器(Rich Text Editor,RTE)是一种内嵌于浏览器、所见即所得的文本编辑器。以Django为例,通过富文本的导入,可以让用户在后台管理系统中像使用Word一样编辑文章,而文章在前端的显示形式与编辑时候的一致,用户就不用去管理繁杂的数据存储机制,仅需简单的配置接口和路径即可完成上述功能。
本章采用的是百度的UEdior,具有轻量、可定制、注重用户体验等特点,基于开源MIT协议,允许*使用和修改。

富文本编辑器的安装

python3需要手动的下载;下载地址
解压文件后需要采用本地安装,cd命令到解压文件的根文件夹下(与setup.py同级目录),然后输入下述命令即可完成安装

python setup.py install

安装完成后,为了能够在自己的Django项目中集成UEdito编辑器,需要将该解压文件夹中的DjangoUeditor文件夹复制到当前项目中。从本质来说,DjangoUeditor即作为一个Django应用,接下来只需要将该应用添加到henDaProject项目中,即在settings.py文件中找到INSTALLED_APPS,然后将DjangoUeditor添加进去即可。然后为DjangoUeditor应用进行路由配置,打开urls.py文件,在urlpatterns字段中添加DjangoUeditor应用对应的路由:
path(\'ueditor/\',include(\'DjangoUeditor.urls\'))
至此,完成了Django项目中富文本组件DjangoUeditor应用的安装和配置

创造富文本编辑模型

在创建新闻模型前,先分析该模型需要创建的字段。参考第六章创建的"产品"模型,新闻模型同样需要标题(title)和详细内容(description)字段,其中,标题是字符型数据,而详细内容则可以包含文字、图片、文件下载链接等,并且可以任意排布这些内容元素,该字段需要使用富文本来实现。除了上述两个字段以外,还需要添加新闻类型、发布时间、浏览量等字段,这些字段可以使用Django的常用模型字段来实现
打开newsApp文件夹下面的models.py文件,在该文件中创建"新闻"模型
1.首先引入所需要的模块,其中尤其注意富文本模块UEditorField,models中UEditorField的导入,这样可以在模型中使用 UEditorField来创建富文本字段从而可以方便的嵌入各种文本、图像、链接元素。
2.接下来创建\'新闻\'模型MyNew类。My_New类包含新闻标题(title)、内容描述(description)、新闻类型(newType)、发布时间(publishDate)和浏览量(views)字段。各字段除了description以外,均采用django.db默认提供的常规数据字段。
3.description字段使用了富文本 UEditorField来声明,其中,u"内容"用来定义该字段在后台管理系统中的别名。 width= 1000,height= 300表示后台管理系统中该字段最后的编辑界面宽度为1000像素,高为300像素。imagePath和filePath分别用来指明用户上传的图像和最终的存储目录。这里注意imagePath和filePath参数的使用需要依赖项目配置文件settings.py中MEDIA_URL和MEDIA_ROOT的配置。
4.除了上述模型字段以外,MyNew模型还通过定义def str(self)()函数来设置后台管理系统中新闻列表每条新闻的显示名称。通过定义Meta类来声明模型数据的排序方式以及模型在后台管理系统中的别名。
创建模型之后记得执行那两步同步数据库:

python manage.py makemigrations
pytho manage.py migrate

后台管理系统中使用富文本

为了能够在后台管理系统中使用前面创建的MyNew模型,需要在admin中进行模型注册。打开newsApp应用中的admin.py文件,对创建的MyNew模型进行注册

from django.contrib import admin
from . models import MyNew
# Register your models here.


class MyNewAdmin(admin.ModelAdmin):
    # 定义style_field属性来绑定富文本字段
    style_field = {\'description\':\'ueditor\'}

admin.site.register(MyNew,MyNewAdmin)

添加数据

打开后台管理系统添加一些数据

开发新闻列表和新闻详情页面

"新闻"模块整体访问流程如下:
1.用户单击"企业要闻"\'行业新闻\'‘通知公告’任一子页面产生请求,通过浏览器将请求发送至服务器
2.服务器采用统一的路由进行映射,匹配指定的视图函数进行处理
3.视图函数根据请求附带的参数来确定请求的子页面类型,同过ORM操作来过滤、查询数据并返回页面
4.前端收到返回的页面内容进行输出,其中富文本内容按照编辑时候的样式进行渲染。

"新闻列表"后台视图处理函数

"新闻列表"后台视图处理函数主要完成数据的读取任务,其中,为了方便浏览需要使用分页组件进行分页处理。跟第六章的分页函数差不多

from .models import MyNew
from django.core.paginator import Paginator
def news(request, newName):
    # 解析请求的新闻类型
    submenu = newName
    if newName == \'company\':
        newName = \'企业要闻\'
    elif newName == \'industry\':
        newName = \'行业新闻\'
    else:
        newName = \'通知公告\'
    # 从数据库获取、过滤和排序数据
    newList = MyNew.objects.all().filter(
        newType=newName).order_by(\'-publishDate\')
    for mynew in newList:
        html = pq(mynew.description)  # 使用pq方法解析html内容
        # mytxt其实是个临时变量,真实的数据库模型MyNew并没有添加mytxt字段
        mynew.mytxt = pq(html)(\'p\').text()  # 截取html段落文字
    # 分页
    p = Paginator(newList, 5)
    if p.num_pages <= 1:
        pageData = \'\'
    else:
        page = int(request.GET.get(\'page\', 1))
        newList = p.page(page)
        left = []
        right = []
        left_has_more = False
        right_has_more = False
        first = False
        last = False
        total_pages = p.num_pages
        page_range = p.page_range
        if page == 1:
            right = page_range[page:page + 2]
            print(total_pages)
            if right[-1] < total_pages - 1:
                right_has_more = True
            if right[-1] < total_pages:
                last = True
        elif page == total_pages:
            left = page_range[(page - 3) if (page - 3) > 0 else 0:page - 1]
            if left[0] > 2:
                left_has_more = True
            if left[0] > 1:
                first = True
        else:
            left = page_range[(page - 3) if (page - 3) > 0 else 0:page - 1]
            right = page_range[page:page + 2]
            if left[0] > 2:
                left_has_more = True
            if left[0] > 1:
                first = True
            if right[-1] < total_pages - 1:
                right_has_more = True
            if right[-1] < total_pages:
                last = True
        pageData = {
            \'left\': left,
            \'right\': right,
            \'left_has_more\': left_has_more,
            \'right_has_more\': right_has_more,
            \'first\': first,
            \'last\': last,
            \'total_pages\': total_pages,
            \'page\': page,
        }
    return render(
        request, \'newList.html\', {
            \'active_menu\': \'news\',
            \'sub_menu\': submenu,
            \'newName\': newName,
            \'newList\': newList,
            \'pageData\': pageData,
        })

接下来修改对应的路由,打开newsApp应用下的urls.py文件,由于新闻动态中的三个子页面共享同一个路由,因此删除原先设计的路由,重写编辑urlpatterns字段

path(\'news/<str:newName>/\',views.news,name=\'news\'), # 新闻列表

自此,已完成新闻列表的视图处理函数处理部分以及对应路由的设置。由于修改了"新闻动态"各子页面的路由形式,因此需要修改基础模板base.html中"新闻动态"各子页面的访问路径。打开base.html文件,修改"新闻动态"部分代码

<li><a href="{% url \'newsApp:news\' \'company\' %}">企业要闻</a></li>
<li><a href="{% url \'newsApp:news\' \'industry\' %}">行业新闻</a></li>
<li><a href="{% url \'newsApp:news\' \'notice\' %}">通知公告</a></li>

设计新闻列表页面

在newsApp应用中创建templates文件夹,然后再该文件夹下创建newList.html文件。编辑newList.html文件,详细代码如下

{% extends "base.html" %}
{% load staticfiles %}

{% block title %}
{{newName}}
{% endblock %}

{% block content %}
<link href="{% static \'css/news.css\' %}" rel="stylesheet"><!--引用自建的news.css文件-->
<!-- 广告横幅 -->
<div class="container-fluid">
    <div class="row">
        <img class="img-responsive model-img" src="{% static \'img/new.jpg\' %}">
    </div>
</div>
<!-- 主体内容 -->
<div class="container">
    <div class="row row-3">
        <!-- 侧边导航栏 -->
        <div class="col-md-3">
            <div class="model-title">
                新闻动态
            </div>
            <div class="model-list">
                <ul class="list-group">
                    <li class="list-group-item" id=\'company\'>
                        <a href="{% url \'newsApp:news\' \'company\' %}">企业要闻</a>
                    </li>
                    <li class="list-group-item" id=\'industry\'>
                        <a href="{% url \'newsApp:news\' \'industry\' %}">行业新闻</a>
                    </li>
                    <li class="list-group-item" id=\'notice\'>
                        <a href="{% url \'newsApp:news\' \'notice\' %}">通知公告</a>
                    </li>
                </ul>
            </div>
        </div>
        <!-- 说明文字和图片 -->
            <div class="model-details-title"><!--在该模块中添加表单,在表单中添加对应的文本搜索框和提交按钮-->
                {{newName}}
                <div class="col-md-7 hidden-xs model-details-title-search">
                    <!--haystack_search这就是享受快的痛苦,直接粘贴赋值,
                        报错了haystack_searchReverse for \'haystack_search\' not found. \'haystack_search\' is not a valid view function or pattern name.
                        太惨了,搜索是后面的模块,我注释掉一个模板没啥用,估计这里有很多个这样没定义的模板
                    -->
                    <form method="get" action="{% url \'haystack_search\' %}"> <!--haystack模块还要加入到settings.py中的应用中INSTALLED_APPS;还要进行很多配置-->
                        <!--这是搜索模块,这里采用的是get方法,
                    表单请求就不会被Django后台系统所屏蔽,如果采用的提交方式是post,则需要在对应的视图函数前添加require_POST装饰器。-->
                        {% csrf_token %}
                        <div class="input-group"><!--name=\'keyword\'么,q又是个啥,后台视图处理函数通过name属性来解析用户输入的文本信息-->
                            <!--name="q"是为了能让django-haystack默认的视图处理函数能够解析成数据;解析的数据传递给项目根目录下模板文件夹中的search中的search.html文件-->
                            <input type="text" name="q" class="form-control" placeholder="请输入关键词" required />
                            <span class="input-group-btn">
                                <input type="submit" class="btn btn-default" value="查询" />
                            </span>
                        </div>
                    </form>
                </div>
            </div>
            <div class="model-details">
                {% for mynew in newList %}
                <div class="news-model"><img src="{% static \'img/newsicon.gif\' %}">
                    <!--新闻详情页面点击的链接-->
                    <a href="{% url \'newsApp:newDetail\' mynew.id %}"><b>{{mynew.title}}</b></a>
                    <span>【{{mynew.publishDate|date:"Y-m-d"}}】</span>
                    <p>
                        <!-- 添加新闻简要说明 -->
                        {{mynew.mytxt|truncatechars:"110"}}...
                    </p><!--truncatechars:"110"截断字符串,使得只显示前面110个字符-->
                </div>
                {% endfor %}

                {% if pageData %}
                <div class="paging">
                    <ul id="pages" class="pagination">
                        {% if pageData.first %}
                        <li><a href="?page=1">1</a></li>
                        {% endif %}
                        {% if pageData.left %}
                        {% if pageData.left_has_more %}
                        <li><span>...</span></li>
                        {% endif %}
                        {% for i in pageData.left %}
                        <li><a href="?page={{i}}">{{i}}</a></li>
                        {% endfor %}
                        {% endif %}
                        <li class="active"><a href="?page={{pageData.page}}">
                                {{pageData.page}}</a></li>
                        {% if pageData.right %}
                        {% for i in pageData.right %}
                        <li><a href="?page={{i}}">{{i}}</a></li>
                        {% endfor %}
                        {% if pageData.right_has_more %}
                        <li><span>...</span></li>
                        {% endif %}
                        {% endif %}
                        {% if pageData.last %}
                        <li><a href="?page={{pageData.total_pages}}">
                                {{pageData.total_pages}}</a></li>
                        {% endif %}
                    </ul>
                </div>
                {% endif %}
            </div>
        </div>
    </div>
</div>
{% endblock %}

1.上述代码与第六章"产品中心"模块productList.html内容大致相同,只是在主体渲染部分没有采用Bootstrap的缩略图组件,而是采用了一个class="news-model"的<div>类来进行新闻列表显示,该样式类的定义将在后面给出。
2.在newList.html文件中新闻日期在输出时采用了模板过滤器date:"Y-m-d"来格式化显示。在新闻列表的每个<div>内还包含一个

标签用于显示新闻的简要介绍,也就是将每条新闻的文字部分提取出一部分进行显示。
3.由于新闻模型采用了富文本进行内容绑定,而富文本本身是以格式化的HTML字符串存储在数据库中,因此需要借助第三方HTML字符串解析工具来对新闻内容解析以获得页面元素。




为了定制化"新闻列表"样式,在style.css的同目录下创建news.css文件,在该文件中添加样式定义

/* 分页控件样式 */
.paging{
	text-align:center;
}
.pagination .active a{
	background-color:#005197;
	border-color:#005197;
}
/* 新闻列表样式 */
.news-model{
	margin-top:15px;
}
.news-model span{
	float:right;
}
.news-model a{
	color:#666;
	font-size:16px;
}
.news-model a:hover, .news-model a:focus{
	text-decoration:none;
	color:#d30a1c;
}
.news-model p{
	margin-top:5px;
	font-size:13px;
}

最后在newList.html文件的{block content}内添加样式引用

<link href="{% static \'css/news.css\' %}" rel="stylesheet"><!--引用自建的news.css文件-->

新闻详情后台处理函数

"新闻详情"页面的渲染主要通过在"新闻列表"页面为每条新闻绑定id号实现,后台视图处理函数解析该id然后从数据库中获取数据在返回页面内容。
在newsApp的views()函数中添加"新闻详情"视图处理函数
其中,mynew.views+=1表示每次页面访问的时候浏览次数累计加1,从而方便统计每条新闻的浏览次数。mynew.save()表示将数据的更改保存到数据库中。

from django.shortcuts import get_object_or_404
# 新闻详情页后台处理函数
def newDetail(request, id):
    mynew = get_object_or_404(MyNew, id=id)
    mynew.views += 1 # 每次浏览数加1
    mynew.save()
    return render(request, \'newDetail.html\', {
        \'active_menu\': \'news\',
        \'mynew\': mynew,
    })

接下来修改newsApp应用下的urls.py文件,为newDetail绑定对应的路由,在urlpatterns字段中添加路由path(\'newDetail/<int:id>/\',views.newDetail,name=\'newDetail\'), # 新闻详情页面 newDatail名字写错了 正确的是newDetail
最后修改newList.html文件中每一条新闻的访问路径,将(重点)
<a herf="#"> <b> {{mynew.title}}</b></a>
替换为

<!--新闻详情页面点击的链接-->
<a href="{% url \'newsApp:newDetail\' mynew.id %}"><b>{{mynew.title}}</b></a>

这样就可以将每条新闻的id作为参数动态的绑定到访问路径中,通过使用逆向解析的方式得到每条新闻的真实URL

设计新闻详情页面

在newsApp应用的templates文件夹下创建一个newDetail.html文件,添加代码如下

{% extends \'base.html\' %}
{% load staticfiles %}
{% block title %}
新闻详情
{% endblock %}
{% block content %}
<link rel="stylesheet" href="{% static \'css/news.css\' %}">
<!--主体内容-->
<div class="container">
    <div class="model-details-product-title">
        {{mynew.title}} <!--这变量哪来的,views.py中么;是views中的变量,他是从数据库中提取出来的变量-->
        <div class="model-foot">发布时间:{{mynew.publishDate|date:"Y-m-d"}} &nbsp;&nbsp; 浏览次数:{{mynew.views}}</div>
    </div>
    <div class="model-details">
        {{mynew.description | safe}} <!--这个|的分割符前后可有可无空格-->
    </div>
</div>
{% endblock %}

上述代码在主体标题部分通过传入的模板变量mynew来渲染"发布时间"和"浏览次数"。在主体描述部分则采用了{{mynew.description|safe}}来实现富文本的内容渲染,此时Django模板标签会自动解析富文本中的内容,并将其按照特点的格式进行展现,最后,在news.css文件中添加相关样式

/* 新闻详情 */
.model-details-product-title{
	padding:15px 0px;
	font-size:18px;
	border-bottom:1px #005197 solid; 
	color:#005197;
	margin-bottom:10px;
	margin-top:10px;
	text-align:center;
}
/* 新闻主体副标题 */
.model-foot{
	padding:5px 0px;
	font-size:14px;
	color:#545353;
	margin-top:10px;
	text-align:center;
}

从富文本中提取文字

从本质上来说富文本存储的内容都是以带格式的HTML字符串存储的,在渲染时Django模板会自动解析字符串中的特定HTML标签符号。因此如果需要解析特定的内容,只需要在获取到数据后抽取出对应标签的内容即可。这里主要借助第三方工具包pyquery来进行HTML解析
首先安装pyquery

pip install pyquery

修改views.py文件,在头部引入pyquery模块
from pyquery import PyQuery as pq
然后修改news()函数,在取出模型数据之后利用pyquery()函数对每一条新闻进行HTML解析,取出其中的<p>标签对应的内容

# 从数据库获取、过滤和排序数据
    newList = MyNew.objects.all().filter(
        newType=newName).order_by(\'-publishDate\')
    for mynew in newList:
        html = pq(mynew.description)  # 使用pq方法解析html内容
        # mytxt其实是个临时变量,真实的数据库模型MyNew并没有添加mytxt字段
        mynew.mytxt = pq(html)(\'p\').text()  # 截取html段落文字

新闻搜索

本节主要介绍如何在Django中利用表单技术获取用户提交的信息并进行内容检索,检索按照难易程度分为基于模糊查询的新闻标题搜索以及数据库查询语句即可实现,不需要安装和配置额外的第三方组件。

基于模糊查询的新闻标题搜索

打开newList.html文件,在<div class=""model-details-title>标签中添加表单,并且在表单中添加对应的文本搜索按钮和提交按钮。

        <div class="model-details-title"><!--在该模块中添加表单,在表单中添加对应的文本搜索框和提交按钮-->
            {{newName}}
            <div class="col-md-7 hidden-xs model-details-title-search">
                <!--haystack_search这就是享受快的痛苦,直接粘贴赋值,
                    报错了haystack_searchReverse for \'haystack_search\' not found. \'haystack_search\' is not a valid view function or pattern name.
                    太惨了,搜索是后面的模块,我注释掉一个模板没啥用,估计这里有很多个这样没定义的模板
                -->
                <form method="get" action="{% url \'haystack_search\' %}"> <!--haystack模块还要加入到settings.py中的应用中INSTALLED_APPS;还要进行很多配置-->
                    <!--这是搜索模块,这里采用的是get方法,
                表单请求就不会被Django后台系统所屏蔽,如果采用的提交方式是post,则需要在对应的视图函数前添加require_POST装饰器。-->
                    {% csrf_token %}
                    <div class="input-group"><!--name=\'keyword\'么,q又是个啥,后台视图处理函数通过name属性来解析用户输入的文本信息-->
                        <!--name="q"是为了能让django-haystack默认的视图处理函数能够解析成数据;解析的数据传递给项目根目录下模板文件夹中的search中的search.html文件-->
                        <input type="text" name="q" class="form-control" placeholder="请输入关键词" required />
                        <span class="input-group-btn">
                            <input type="submit" class="btn btn-default" value="查询" />
                        </span>
                    </div>
                </form>
            </div>
        </div>

在news.css文件中添加对应的样式设计

/* 新闻搜索框 */
.model-details-title-search{
	font-size:18px;
	width: 300px;
	float: right;
	margin-bottom: 20px;
}

在视图文件views.py中添加新闻搜索对应的响应处理函数

# 新闻搜索对应的响应处理函数
def search(request):
    keyword = request.GET.get(\'keyword\') # 提取出用户输入的关键字信息
    newList = MyNew.objects.filter(title__icontains=keyword)# 数据库模糊查询的方式找到新闻标题中包括的关键字
    newName = "关于 " + "\"" + keyword + "\"" + " 的搜索结果"
    return render(request, \'searchList.html\', {
        \'active_menu\': \'news\',
        \'newName\': newName,
        \'newList\': newList,
    })

上述代码首先利用request.GET.get()函数提取出用户输入的关键词信息,然后采用数据库模糊查询的方式找到新闻标题中包括的关键字,最后将结果传入模板文件searchList.html进行渲染。在templates文件夹中新建searchList.html文件用来制作新闻搜索页面

<!--用来制作新闻搜索页面-->
{% extends "base.html" %}
{% load staticfiles %}
{% block title %}
{{newName}}
{% endblock %}
{% block content %}
<link href="{% static \'css/news.css\' %}" rel="stylesheet">
<!-- 广告横幅 -->
<div class="container-fluid">
    <div class="row">
        <img class="img-responsive model-img" src="{% static \'img/new.jpg\' %}">
    </div>
</div>
<!-- 主体内容 -->
<div class="container">
    <div class="row row-3">
        <div class="model-details-title">
            {{newName}}
            <div class="col-md-7 hidden-xs model-details-title-search">
                <form method="get" action="/search/"> <!--form表单中action="/search/啥意思-->
                    {% csrf_token %}
                    <div class="input-group">
                        <input type="text" name="keyword" class="form-control" placeholder="请输入关键词" required />
                        <span class="input-group-btn">
                            <input type="submit" class="btn btn-default" value="查询" />
                        </span>
                    </div>
                </form>
            </div>
        </div>
        <div class="model-details">
            {% for mynew in newList %}
            <div class="news-model">
                <img src="{% static \'img/newsicon.gif\' %}">
                <a href="{% url \'newsApp:newDetail\' mynew.id %}"><b>{{mynew.title}}</b></a>
                <span>【{{mynew.publishDate|date:"Y-m-d"}}】</span>
            </div>
            {% endfor %}
        </div>
    </div>
</div>
{% endblock %}

最后配置URL路由,打开urls.py文件,在urlpatterns字段中添加新闻搜索对应的路由
path(\'search/\',views.search,name=\'search\'), #新闻搜索对应的路由

基于django-haystack的全文高级搜索

django-haystack是一个专门提供搜索功能的Django第三方应用,它支持Solr、ElasticSearch、Whoosh、Xapian等多种搜索引擎,配合著名的中文自然语言处理库jieba分词,就可以为"新闻动态"模块构建一个效果不错的新闻搜索系统。要使用django-haystack,首先必须安装它,并且安装一些必要的依赖,具体需要安装的依赖如下
Whoosh:一个由纯Python实现的全文搜索引擎,功能小巧,配置简单
jieba:中文分词库,由于Whoosh自带的是英文分词,对中文分词的支持不是太好,所以需要使用jieba替换Whoosh的分词组件。
安装这三个库,直接使用pip安装即可

pip install whoosh django-haystack jieba

安装好需要在settings.py中做一些简单的配置,首先是把django-haystack加入到INSTALLED_APPS选项里:

INSTALLED_APPS = [
    \'\'\'其他应用\'\'\'
    \'haystack\',  # 添加搜索应用
]

然后在settings.py末尾添加如下配置项

# 新闻搜索配置django-haystack
HAYSTACK_CONNECTIONS = {
    \'default\': {
        \'ENGINE\': \'newsApp.whoosh_backend.WhooshEngine\', # ENGINE指定了django-haystack使用的搜索引擎
        \'PATH\': os.path.join(BASE_DIR, \'whoosh_index\'), # PATH指定了搜索引擎文件需要存放的位置,此处设置为项目根目录BASE_DIR下的whoosh_index文件夹(在建立索引时会自动创建)
    },
}
HAYSTACK_SEARCH_RESULTS_PER_PAGE  =  10 # 表示搜索结果每页显示数目,默认为20页
HAYSTACK_SIGNAL_PROCESSOR = \'haystack.signals.RealtimeSignalProcessor\' # 指定什么时候更新索引,这里指的是每当有新闻更新时就更新索引(那索引是什么)

完成配置后接下来就要告诉django-haystack使用哪些数据建立索引以及如何存放索引。如果要对newsApp应用下的新闻内容进行全文检索,具体做法是在newsApp应用下建立一个search_indexes.py文件,然后添加代码如下。

from haystack import indexes
from .models import MyNew

class MyNewIndex(indexes.SearchIndex,indexes.Indexable):
    text = indexes.CharField(document = True, use_template = True)
    def get_model(self):
        return MyNew
    def index_queryset(self,using=None):
        return self.get_model().objects.all()

django-haystack规定如果要要对某个App下的数据进行全文检索,就要在该App下创建一个search_indexes.py文件,然后创建一个xxxIndex类(xx为含有检索数据的模型,如这里的MyNew),并且继承SearchIndex和Indexable。这里要为指定的数据添加一个索引,在这里是为MyNew创建一个索引,索引实现的细节不用关心,只需要关注为哪些字段创建索引。每个索引里面必须有且只能由一个字段document = True,这代表django-haystack和搜索引擎将使用此字段的内容作为索引。注意,如果其中一个字段设置了document=True,则一般约定此字段名为text,这是在SearchIndex类里面一贯的命名,以防止后台混乱。在text字段中,Haystack还设置了参数use_template = True,这样就允许使用数据模板去建立搜索引擎索引的文件,简单来说就是索引里面需要存放一些检索字段,例如MyNew的title和description字段,这样就可以通过title和description内容来检索MyNew数据了。数据模板的路径为:
templates/search/indexes/<应用名>/<模型名>_text.txt
以本节例子为例的化,其路径为:
templates/search/indexes/newsApp/MyNew_text.txt
创建上述路径和txt文件,编辑其中内容如下

{{object.title}}
{{object.description}}

这个数据模板的作用是对MyNew中的title和description两个字段建立索引,当检索的时候会对这两个字段做全文检索匹配,然后将匹配的结果排序后返回
接下来配置URL,搜索应用的视图处理函数和URL在django-haystack中都已经实现,只需要在项目全局路由文件urls.py中包含它:

urlpatterns = [
      \'\'\'其他应用\'\'\'
      path(\'search/\',include(\'haystack.urls\')),
]

然后修改newList.html文件中的表单action属性,让它提交关键词数据到django-haystack搜索视图对应的URL.

<form method="get" action="{% url \'haystack_search\' %}"> <!--haystack模块还要加入到settings.py中的应用中INSTALLED_APPS;还要进行很多配置-->
                        <!--这是搜索模块,这里采用的是get方法,
                    表单请求就不会被Django后台系统所屏蔽,如果采用的提交方式是post,则需要在对应的视图函数前添加require_POST装饰器。-->
                        {% csrf_token %}
                        <div class="input-group"><!--name=\'keyword\'么,q又是个啥,后台视图处理函数通过name属性来解析用户输入的文本信息-->
                            <!--name="q"是为了能让django-haystack默认的视图处理函数能够解析成数据;解析的数据传递给项目根目录下模板文件夹中的search中的search.html文件-->
                            <input type="text" name="q" class="form-control" placeholder="请输入关键词" required />
                            <span class="input-group-btn">
                                <input type="submit" class="btn btn-default" value="查询" />
                            </span>
                        </div>
                    </form>

django-haystack视图函数会将搜索结果传递给项目根目录下模板文件夹templates/search中的search.html文件,因此创建这个模板文件,对搜索结果进行渲染。
这里就不把代码贴出来了
简要描述就是:
1.上述模板基本和searchList.html一样,由于haystack对搜索结果做了分页,传给模板的变量是一个page对象,所以从page中取出这一页对应的搜索结果,然后对其循环显示,即 {% for result in page.object_list %}
2.为了取得新闻的标题和详细内容,需要从result的object属性中获取。query变量的值即为用户搜索的关键字。实现搜索内容的高亮显示在django-haystack中只需要使用{{% highlight %}模板标签即可。
3.在新闻搜索页面中对description做了高亮处理:{% highlight result.object.description with query %}。高亮处理的原理其实就是给文本中的关键字包上一个span标签并且为其添加highlighted样式。
为了实现高亮效果,还需要给hightlighted类定义对应的样式,在news.css中添加相关样式。

/* 全文搜索样式 */
.news-search-model span{
	float:none;
}
.news-model a span{
	float: none;
}
.highlighted {
	color: red;
}

本文使用Whoosh作为搜索引擎,Whoosh指定的分词器是英文分词器,要将其替换成中文分词器;在python的haystack包中找到backends文件夹在从中找到whoosh_backend.py模块复制到newsApp文件夹下,(前面在settings.py中的HAYSTACK_CONNECTIONS指定的就是这个文件,然后找到如下一行代码),修改成如下样式

 schema_fields[field_class.index_fieldname] = TEXT(
                    stored=True,
                    analyzer=ChineseAnalyzer(), # StemmingAnalyzer()英文分词器1;ChineseAnalyzer()中文分词器,需要导入jeieba包
                    field_boost=field_class.boost,
                    sortable=True,
                )

为了使用它,还需要导入jeieba包

from jieba.analyse import ChineseAnalyzer

最后,运行下述命令来重建索引文件完成内容索引

python manage.py rebuild_index

第八章

本章将学习一些常见的第三方工具和接口来进一步丰富门户网站的功能,本章内容对应的是contactApp应用,分为两个子页面:\'欢迎咨询\'和\'加入恒达\'。
\'欢迎咨询\'子页面主要将企业的地址、电话、联系人、定位地图等相关信息进行展示,其中重点介绍如何在项目中集成百度地图。
\'加入恒达\'子页面主要以发布招聘信息并显示在\'加入恒达\'页面上,用户浏览招聘信息后可以以表单的形式提交个人简历信息来进行应聘,在用户提交信息完成后后台服务器会通过邮件服务向指定邮箱发送邮件通知。

先写下感受:有点懵;还是在邮件发送环节,需要仔细研究django实现邮件发送的代码。网上找资料,邮件自动发送并没有实现

欢迎咨询模块

嵌入百度地图

contact.html文件内容分为两列,左列为一些联系信息,包括咨询电话、企业传真、邮编、地址等信息;右列用于放置地图
左列部分代码如下

<div class="col-md-9">
            <div class="model-details-title">
                欢迎咨询
            </div>
            <div class="col-md-4">
                <!--左列部分代码-->
                <div class="contact-left">
                    <h1>
                        <strong>恒达科技有限公司</strong>
                        <span>HengDa Science and Technology</span>
                    </h1>
                    <ul>
                        <li><span>业务质询一:</span>111-111111</li>
                        <li><span>业务质询二:</span>222-222222</li>
                        <li><span>咨询电话:</span>0111-1111111</li>
                        <li><span>企业传真:</span>0222-2222222</li>
                        <li><span>地址:</span>某某路某某大道某某号</li>
                        <li><span>邮编:</span>2222-222222</li>
                        <li><span>网址:</span>
                            <a href="http://python3web.com">http://python3web.com</a>
                        </li>
                    </ul>
                </div>
            </div>

他是col-md-9里面再包个div;里面的div又采用栅格4-8;4的化是左边部分,8是右边
这里在css文件夹中新建个名为contact.css的样式文件,添加对应的CSS代码
记得要在{% block content %}中引入该css文件`
上述说明信息均采用静态文字形式呈现

创建地图

1.百度提供了免费的接口用于地图标注,打开网址http://api.map.baidu.com/lbsapi/creatmap/index.html.接着按照打开的网页操作。最后把单击\'获取代码\'按钮的代码保存下来
2.地图代码集成
把获取代码下来的代码最上面的style样式和api脚本复制粘贴到contact.html文件的{% block content %}模板标签中

<!--引用百度地图API-->
<style type="text/css">
    html,
    body {
        margin: 0;
        padding: 0;
    }

    .iw_poi_title {
        color: #CC5522;
        font-size: 14px;
        font-weight: bold;
        overflow: hidden;
        padding-right: 13px;
        white-space: nowrap
    }

    .iw_poi_content {
        font: 12px arial, sans-serif;
        overflow: visible;
        padding-top: 4px;
        white-space: -moz-pre-wrap;
        word-wrap: break-word
    }
</style>
<script type="text/javascript" src="http://api.map.baidu.com/api?key=&v=1.1&services=true"></script>

然后再主体内容右侧添加一个<div>用于引用和显示地图组件

<div class="col-md-8" style="margin-top: 20px;">
                <!--百度地图容器-->
                <div style="width:697px;height:550px;border:#ccc solid 1px;" id="dituContent"></div>
</div>

最后将获取代码下来的代码的<script>脚本文件全部复制到contact.html文件中。

加入恒达页面和模块开发

招聘信息发布

需要在数据库中创建对应的招聘广告模型,从而可以是后台管理系统进行招聘信息的发布、查看、管理。
打开contactApp中的models.py文件,添加模型

class Ad(models.Model):
    title = models.CharField(max_length=50,verbose_name=\'招聘岗位\')
    description = models.TextField(verbose_name=\'岗位要求\')
    publishDate = models.DateTimeField(max_length=20,default=timezone.now,verbose_name=\'发布时间\')

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = \'招聘广告\'
        verbose_name_plural = \'招聘广告\'
        ordering = (\'-publishDate\',) #     ording写错了;是ordering;这里还要注意加逗号,

然后再进行那两步数据库同步;接着在注册该模型在admin.py模块中。为了页面美观和后续操作,打开后台管理系统,在招聘模块中添加几条招聘记录.
修改视图处理函数,在每次请求"加入恒达"页面时后台系统从数据库中按照时间由近到远的顺序取出所有招聘信息,传入到模板文件recruit.html中,并由前端进行渲染

def recruit(request):
    AdList = Ad.objects.all().order_by(\'-publishDate\')
    return render(request, \'success.html\', {
                \'active_menu\': \'contactus\',# 为什么active_menu是contactus,而\'sub_menu\'是\'recruit\'
                \'sub_menu\': \'recruit\',
            })

接着对招聘信息前端页面进行设计并将招聘信息逐条显示,采用的是bs2提供的手风琴面板组件来展示招聘信息

<div class="model-details"><!--bs2提供的手风琴面板组件class="panel-group"-->
                <div class="panel-group" id="accordion"><!--该组件可以看成是面板的容器,可以放置多个面板,每个面板用于展示一条信息并且每个面板均可以折叠-->
                    {% for ad in AdList %}<!--从数据库模型中提取数据-->
                    <div class="panel panel-default">
                        <div class="panel-heading" role="tab" id="panel{{ad.id}}"><!--{{ad.id}}动态生成每一个面板的id-->
                            <h4 class="panel-title">
                                {% if forloop.first %}<!--这个是只打开第一条面板,即第一条面板默认打开;之后的判断是,打开其他面板则关闭前面已经打开的面板-->
                                <a role="button" data-toggle="collapse" data-parent="#accordion"
                                    href="#collapse{{ad.id}}">
                                    {% else %}
                                    <a class="collapsed" data-toggle="collapse" data-parent="#accordion"
                                        href="#collapse{{ad.id}}">
                                        {% endif %}
                                        {{ad.title}}
                                    </a>
                            </h4>
                        </div>
                        {% if forloop.first %}
                        <div id="collapse{{ad.id}}" class="panel-collapse collapse in">
                            {% else %}
                            <div id="collapse{{ad.id}}" class="panel-collapse collapse">
                                {% endif %}
                                <div class="panel-body">
                                    <p>{{ad.description}}</p>
                                </div>
                            </div>
                        </div>
                        {% endfor %}
                    </div>
                </div>

基于模型表单的应聘信息上传
额外知识:
Form:标准表单。可以对输入数据做验证,但没有与数据库模型关联
ModelForm:模型表单,可以对输入数据做验证,同时与数据库模型关联,可以直接通过模型表单存储和修改数据库数据
模型表单的使用方法:
1.定义模型
2.更具模型创建模型表单
3.视图处理函数中通过模型表单接收并解析数据,最后渲染页面
首先为用户上传的数据定义模型,上传数据主要为用户的个人应聘信息,包括:姓名、性别、身份证号、邮箱、出生日期、学历、毕业院校、专业、照片、申请职位、学习或工作经历。
在models.py文件中新增Resume模型,该模型作为"简历"模型将对用户上传的所有信息字段进行管理和存储。具体代码如下

from datetime import datetime


class Resume(models.Model):
    name = models.CharField(max_length=20, verbose_name=\'姓名\')
    personID = models.CharField(max_length=30, verbose_name=\'身份证号\')
    sex = models.CharField(max_length=5, default=\'男\', verbose_name=\'性别\')
    email = models.EmailField(max_length=30, verbose_name=\'邮箱\')# EmailField邮箱格式的验证
    birth = models.DateField(max_length=20,
                             default=datetime.strftime(datetime.now(),
                                                       "%Y-%m-%d"),
                             verbose_name=\'出生日期\')
    edu = models.CharField(max_length=5, default=\'本科\', verbose_name=\'学历\')
    school = models.CharField(max_length=40, verbose_name=\'毕业院校\')
    major = models.CharField(max_length=40, verbose_name=\'专业\')
    position = models.CharField(max_length=40, verbose_name=\'申请职位\')
    experience = models.TextField(blank=True,
                                  null=True,
                                  verbose_name=\'学习或工作经历\')
    photo = models.ImageField(upload_to=\'contact/recruit/%Y_%m_%d\', # %Y_%m_%d\'按照日期来保存存储用户上传照片的文件夹
                              verbose_name=\'个人照片\')
    grade_list = (
        (1, \'未审\'),
        (2, \'通过\'),
        (3, \'未通过\'),
    )
     # IntegerField由管理员通过后台管理系统进行操作
    status = models.IntegerField(choices=grade_list, # choices=grade_list,下拉菜单形式呈现
                                 default=1,
                                 verbose_name=\'面试成绩\')
    publishDate = models.DateTimeField(max_length=20,
                                       default=timezone.now,
                                       verbose_name=\'提交时间\')

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = \'简历\'
        verbose_name_plural = \'简历\'
        ordering = (\'-status\', \'-publishDate\') # 两个参数,采用的是字段联合的方式进行排序;先看审核与否,再看提交时间

接着在终端输入那两步命令来同步数据库

python manage.py makemigration
python manage.py migrate

本实例用户通过表单提交的数据较多,尽管可以采用前面章节中的方法,对用户上传的每个字段逐个进行检查并提取数据,但这无疑降低了开发效率并且增加了大量功能重复的冗余代码。而采用模型表单则可以作为中间转换器,直接衔接前端用户输入的数据同时和后端数据库相连,大量的验证和数据提取工作可以直接由模型表单完成。从某种意义上来看,可以将模型表单看作模型的一个格式定制化器,可以对模型中的每一个字段进行格式和验证方式的定制。
下面开始为前面创建的Resume模型建立对应的模型表单。在contactApp下新建一个python文件,命名为forms.py,编辑代码如下:

from django import forms # 从Django中导入Forms类来使用表单组件
from .models import Resume # 导入models中的Resume模型

class ResumeForm(forms.ModelForm): # 新建模型表单类ResumeForm继承forms.ModelForm
    class Meta: # 通过元信息类 Meta来进行模型的定制化
        model = Resume # model属性指向需要定制化的模型
        fields = (\'name\', \'sex\', \'personID\', \'email\', \'birth\', \'edu\', \'school\',
                  \'major\', \'experience\', \'position\', \'photo\') #  fields属性用来指明需要定制化的具体字段
        # 以下两个字段是单选菜单,为他们设置可选项 sex_list 和edu_list
        sex_list = (
            (\'男\', \'男\'),
            (\'女\', \'女\'),
        )
        edu_list = (
            (\'大专\', \'大专\'),
            (\'本科\', \'本科\'),
            (\'硕士\', \'硕士\'),
            (\'博士\', \'博士\'),
            (\'其它\', \'其它\'),
        )
        # 用来控制各个表单字段在前端的具体展现形式,默认情况下的CharField字段对应HTML中的输入文本框
        widgets = {
            \'sex\': forms.Select(choices=sex_list), # 对choices进行绑定实现下拉菜单
            \'edu\': forms.Select(choices=edu_list),
            \'photo\': forms.FileInput(), # 图像字段采用文件输入形式为forms.FileInput
        }

完成模型表单的构造以后,下面开始修改视图views.py中的recruit()函数,具体如下

from .forms import ResumeForm

def recruit(request):
    AdList = Ad.objects.all().order_by(\'-publishDate\')
    if request.method == \'POST\':
        # 参数data用来从request.POST中获取对应的数据,files用来接收对应的文件(此处指用户上传文件)
        resumeForm = ResumeForm(data=request.POST, files=request.FILES)
        if resumeForm.is_valid(): #is_valid()函数用来验证表单各字段格式是否符合要求;符合则通过接下来的resumeForm.save()进行数据保存 
            resumeForm.save()
            return render(request, \'success.html\', {
                \'active_menu\': \'contactus\',# 为什么active_menu是contactus,而\'sub_menu\'是\'recruit\'
                \'sub_menu\': \'recruit\',
            })
    else:
        resumeForm = ResumeForm() # 处于非提交状态,则通过ResumeForm()建立非带参的模板表单变量,然后将该变量一起返回给前端
    return render(
        request, \'recruit.html\', {
            \'active_menu\': \'contactus\',
            \'sub_menu\': \'recruit\',
            \'AdList\': AdList,
            \'resumeForm\': resumeForm,
        })

使用Django的模型表单类可以自动地对模型的各个字段做检查,可以极大程度的简化代码。最后开始进行模型表单的渲染,由于原生的模型表单渲染出来的表单组件不够美观,因此本章采用Bootstrap提供的表单组件。为了能够使用Bootstrap表单组件,同时能够利用模型表单的自动渲染功能,需要使用一个额外的第三方应用django-widget-tweaks,该应用允许在前端中使用特定的模板标签语言为模型表单组件添加样式类和属性。首先下载和安装该应用:
pip install django-widget-tweaks
在settings.py文件中的INSTALLED_APPS字段中添加应用

INSTALLED_APPS = [
    \'\'\'其他应用\'\'\'
    \'widget_tweaks\', # 添加模型表单组件定制化渲染应用
]

为了能够在前端模板中使用该应用,需要在模板文件recruit.html头部添加引用,采用标签语句{% load widget_tweaks %}实现。紧接着上一节中发布的招聘信息模块,下面开始在reruit.html中编写建立信息上传功能

<!--编写简历信息上传功能-->
                <div class="panel panel-default"><!--布局采用bs2提供的panel;样式类是panel-default-->
                    <div class="panel-heading">
                        请填写个人简历
                    </div>
                    <div class="panel-body">
                        <div class="row">
                            <!--action参数用来指向表单提交的网址,此处.表示当前网址;method表明提交方式;class="form-horizontal"白哦是表单中的控件以行的形式来排列-->
                            <form action="." name="resumeForm" method="post" class="form-horizontal" role="form"
                                enctype="multipart/form-data"><!--enctype="multipart/form-data"与post结合使用可以使得后台获取到request.FILES中用户上传的文件(图片)信息-->
                                {% csrf_token %}
                                <!-- 左侧 -->
                                <div class="col-md-6">
                                    <div class="form-group">
                                        <label class="col-sm-3 control-label">姓名:</label>
                                        <div class="col-sm-9">
                                            {{resumeForm.name|add_class:"form-control"|attr:"placeholder=请填写姓名"}}
                                        </div>
                                    </div>
                                    <div class="form-group">
                                        <label class="col-sm-3 control-label">身份证号:</label>
                                        <div class="col-sm-9">
                                            {{resumeForm.personID|add_class:"form-control"}}
                                        </div>
                                    </div>
                                    <div class="form-group">
                                        <label class="col-sm-3 control-label">性别:</label>
                                        <div class="col-sm-9">
                                            {{resumeForm.sex|add_class:"form-control"}}
                                        </div>
                                    </div>
                                    <div class="form-group">
                                        <label class="col-sm-3 control-label">出生日期:</label>
                                        <div class="col-sm-9">
                                            {{resumeForm.birth|add_class:"form-control"}}
                                        </div>
                                    </div>
                                    <div class="form-group">
                                        <label class="col-sm-3 control-label">邮箱:</label>
                                        <div class="col-sm-9">
                                            {{resumeForm.email|add_class:"form-control"}}
                                        </div>
                                    </div>
                                    <div class="form-group">
                                        <label class="col-sm-3 control-label">学历:</label>
                                        <div class="col-sm-9">
                                            {{resumeForm.edu|add_class:"form-control"}}
                                        </div>
                                    </div>
                                    <div class="form-group">
                                        <label class="col-sm-3 control-label">毕业学校:</label>
                                        <div class="col-sm-9">
                                            {{resumeForm.school|add_class:"form-control"}}
                                        </div>
                                    </div>
                                    <div class="form-group">
                                        <label class="col-sm-3 control-label">专业:</label>
                                        <div class="col-sm-9">
                                            {{resumeForm.major|add_class:"form-control"}}
                                        </div>
                                    </div>
                                    <div class="form-group">
                                        <label class="col-sm-3 control-label">申请职位:</label>
                                        <div class="col-sm-9">
                                            {{resumeForm.position|add_class:"form-control"}}
                                        </div>
                                    </div>
                                </div>
                                <!-- 右侧 -->
                                <div class="col-md-6">
                                    <div class="form-group">
                                        <div class="col-sm-12" style="text-align:center">
                                            <img id="profileshow" src="{% static \'img/sample.png\' %}"
                                                style="width:120px">
                                        </div>
                                        <label class="col-sm-5 control-label">上传证件照片:</label>
                                        {{resumeForm.photo}}
                                    </div>
                                    <div class="form-group">
                                        <label class="col-sm-12 control-label">学习或工作经历:</label>
                                        <div class="col-sm-12">
                                            {{resumeForm.experience|add_class:"form-control"}}
                                        </div>
                                    </div>
                                </div>
                                <div class="col-md-12">
                                    <center><input type="submit" class="btn btn-primary" value="提交" /></center>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>

日期的使用和渲染:为了能够方便用户输入日期信息,本书采用额外的日期组件laydate.js来实现该功能(官方下载网址:https://www.layui.com.com/laydate/);将下载下来的laydate.js组件包放置在本项目static/js文件夹下,然后在recruit.html中进行引用。

<script src="{% static \'js/layDate-v5.0.9/laydate.js\' %}"></script>

为了能够正常使用该日期组件功能,需要额外的js代码来调用

<script>
        //日期组件:这里导入了额外的js文件laydate。js
        laydate.render({
            elem: \'#id_birth\'//elem指的是需要绑定的组件,通过id号绑定
        });
 </script>

照片上传:为了用户选择照片后能够显示照片缩略图,需要添加额外的js代码来控制<img>标签的图片切换,主要通过响应按钮的change事件实现

<script>
    $(function () {
        $(\'#id_photo\').on(\'change\', function () {
            var r = new FileReader();
            f = document.getElementById(\'id_photo\').files[0];
            r.readAsDataURL(f);
            r.onload = function (e) {
                document.getElementById(\'profileshow\').src = this.result;
            };
        });
    });
</script>

需要制作成功页面success.html用于在提交成功后进行提示,这里主要采用Bootstrap提供的消息提示框组件alter来实现。完整代码如下

{% extends "base.html" %}
{% load staticfiles %}

{% block title %}
人才招聘
{% endblock %}

{% block content %}
<!-- 主体内容 -->
<div class="container">
    <div class="row">
        <div class="alert alert-success"><!--bs2 的消息提示框组件alert-->
            <strong>成功!</strong> 简历信息已成功上传,初试结果近期会以邮件方式发送至您的邮箱。
        </div>
    </div>
</div>
{% endblock %}

至此,已完成用户简历信息上传模块的开发。在admin.py文件中导出该模型到后台管理系统,添加代码如下

from .models import Resume

admin.site.register(Resume)

定制化后台管理系统展示列表的方法。具体地,模型的展示列表形式可以通过admin文件进行设置。在admin.py文件中对应模型Resume的注册是通过admin.site.register(Resume)来实现,即采用了默认的注册方法,并没有对模型字段的展示形式。因此,为了能够有效的浏览用户上传的简历信息,需要对各字段进行定制化设置。
代码如下

from django.utils.safestring import mark_safe # mark_safe转义函数对HTML关键字进行转义,将HTML代码转化为HTML实体
from .models import Resume

class ResumeAdmin(admin.ModelAdmin):
    list_display = (\'name\', \'status\', \'personID\', \'birth\', \'edu\', \'school\',
                    \'major\', \'position\', \'image_data\') # 在后台管理系统中需要展示的模型

    def image_data(self, obj):
        return mark_safe(u\'<img src="%s" width="120px" />\' % obj.photo.url) # 将照片的URL路径赋值到HTML中,并通过转义字符进行转换,从而得到最终的image_data()能以缩略图显示

    image_data.short_description = u\'个人照片\'

admin.site.register(Resume,ResumeAdmin) # 把加入恒达模型注册到后台管理系统中,再加上个模型管理器

信号触发器

招聘人员可以通过企业门户的后台管理系统查看应聘者上传的简历信息。当招聘人员浏览完成某一份简历并决定录用后,招聘人员可以通过修改ststus字段将其改为"通过"。后台管理系统在执行字段修改的同时需要做一些额外的处理操作,比如发送录用邮件给应聘者,本地生成word简历进行后期归档等。这些操作均需要捕捉并判断招聘人员动作,即只有当招聘人员将简历中的status字段从未审改为通过或者从未审改为未通过时需要触发额外操作。这节阐述如何利用Django的信号机制实现这种触发效果
Django信号触发器的功能类似于回调函数,用于为项目增加事件的监听与触发机制。其中,灵活使用其内置的模型信号就可以监控大部分模型对象的变换,并且不需要修改原模型的代码。下面进行实际需求进行开发
编辑models.py文件,添加代码如下

from django.db.models.signals import post_init, post_save # post_init表示在管理员单击\'保存\'按钮前触发信号;post_save表示在单击\'保存\'按钮后触发信号
from django.dispatch import receiver # 信号的接收采用装饰器@receiver来实现

# 信号接收采用装饰器@receiver来实现
@receiver(post_init, sender=Resume) # 第一个参数是信号类型,第二个参数是需要监控的模型类
def before_save_resume(sender, instance, **kwargs):
    instance.__original_status = instance.status # instance为传入的模型变量,保存前将值保存在instance.__original_status,通过比较前后变量的值查看变量


@receiver(post_save, sender=Resume)
def post_save_resume(sender, instance, **kwargs):
    # 用控制台输出状态查看触发器的效果,控制台分别输出1,2。当把简历的状态由未审改为通过
   print(instance.__original_status) # 1
   print(instance.status) # 2

发送邮件

已经搭建完了一个简易的招聘与应聘互动模块。这里梳理一下基本流程
1.招聘人员在后台发布招聘信息并且通过数据库显示在前端
2.应聘人员浏览招聘信息并且填写个人简历进行应聘,应聘信息以模型表单的形式直接与数据库进行关联,并且自动对每个字段进行格式检查
3.招聘人员通过后台管理系统浏览简历信息,在进行状态更改的时候触动触发器进行额外的操作。
对于步骤3,在models.py文件中我们已经实现了基本的控制台打印功能(当管理员将简历中的"status"从"未审"改为"通过"时候,此时可以查看控制台分别输出"1" 和 "2")。实际是希望修改“status”状态,能够在修改后自动地将初试结果发送至应聘者邮箱,无需招聘人员再额外的进行邮件发送。为了实现这个自动发送的邮件功能。Django中使用对应的邮件功能。
为了能够使用Django的邮箱服务,需要预先设置好邮箱服务信息。本质上说Django搭建的Web应用只是一个外壳,只负责邮件内容的编辑,而实际邮件发送等功能还是依托第三方邮箱服务器(例如QQ邮箱或新浪邮箱)来进行。
1.QQ邮箱设置
单击QQ邮箱顶部的"设置"按钮,然后再邮件设置中单击"账户"进入账户信息编辑界面。然后向下滚动找到"开启服务"面板,选择开启POP/SMTP服务。
在开启服务的过程中需要通过手机短信验证,验证通过后会获得一个授权码,将该授权码保存下来,稍后会使用该授权码
2.Django配置
Django框架中自带了邮箱模块,只需要简单地进行配置即可使用。打开配置文件settings.py,添加对应地配置

# 邮箱设置
EMAIL_HOST = \'smtp.qq.com\'
EMAIL_PORT = 25
EMAIL_HOST_USER = \'xxxx@qq.com\'      # QQ 账号
EMAIL_HOST_PASSWORD = \'xxxxx\'        # 授权码
EMAIL_USE_TLS = True

将企业QQ邮箱账号和对应地授权码填入其中。接下来重新编辑models.py文件中的post_save_resume()函数,当保存修改时检查当前状态变化,然后按条件发送指定的邮件内容。
在填写邮件内容时,email.title表示邮件标题,email_body为邮件主体内容,最后通过django.core.mail模块中的send_mail()函数完成邮件的发送

from django.core.mail import send_mail

@receiver(post_save, sender=Resume)
def post_save_resume(sender, instance, **kwargs):
    # 用控制台输出状态查看触发器的效果,控制台分别输出1,2。当把简历的状态由未审改为通过
    # print(instance.__original_status) # 1
    # print(instance.status) # 2
    email = instance.email  # 应聘者邮箱
    EMAIL_FROM = \'xxxxx@qq.com\'  # 企业QQ邮箱;暂时还不能发送邮箱,难道是因为我这个邮箱不是企业邮箱么
    if instance.__original_status == 1 and instance.status == 2:
      email_title = \'通知:恒达科技招聘初试结果\' # 邮件标题 邮件和word模板生成不能共存么,不然就报database is locked
      email_body = \'恭喜您通过本企业初试\' # 邮件主题内容
      send_status = send_mail(email_title, email_body, EMAIL_FROM, [email]) # 要不就是这个函数会结束主函数,需要放在最后
    elif instance.__original_status == 1 and instance.status == 3:
        email_title = \'通知:恒达科技招聘初试结果\'
        email_body = \'很遗憾,您未能通过本企业初试,感谢您的关注\'
        # send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])为啥要等于它
        send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])  # send_mail()函数把邮件发送出去

动态生成word文档

Python提供了简单易用的Word文档操作包python-docx-template,可以用来对Word文档进行修改,包括文档中的文本、图片、富文本等内容,具体执行时以模板变量形式将指定字段替换到指定位置,操作起来就如同Django框架中的模板变量一样,因为他也是和jinjia2模板语言结合使用的。
首先安装第三方工具包

pip install docxtpl

接下来可以直接在post_save_resume()函数中使用Python进行Word操作。具体操作流程主要分为下面两个步骤
1.制作Word模板文件,在指定位置插入模板标签,使用模板标签{{模板变量}}来声明
2.在Python脚本中对模板文件进行替换,包括文字和图片
在python中操作Word的一种简单方式就是以模板文件的形式进行文字和图片替换,这样可以大幅减少Word文档操作的任务,仅需要完成字段替换即可。
首先制作Word模块文件,在media文件夹下面创建一个简历模板文件recruit.docx,然后再关键位置处用模板标签对模板变量进行标识(双大括号),如下图所示

接下来编辑models.py文件中的post_save_resume()函数,当简历状态从未审变为通过时,开始进行Word模板文件渲染,将关键字插入到Word模板文件中并保存。
主要过程是
1.建立模板文件,通过DocxTemplate()函数建立模板文件,该函数传入的参数是提前制作好的Word模板文件路径
2.设置渲染内容,从instance实例中获取当前简历的各个字段信息,并安装"键-值"对形式封装成context变量,最后调用render()函数进行字段替换。其中注意图像字段,需要通过特殊的InlineImage()函数进行图像字段的替换, width和height参数分别用来设置最终在word中显示的宽度和高度
3.Word文件保存:通过模板文件的save()函数将文件保存到指定路径,这里为了防止Word文件命名冲突,以每个实例的姓名加上ID来命名

 if instance.__original_status == 1 and instance.status == 2:
        
        
        template_path = os.getcwd() + \'/media/contact/recruit.docx\'  #模板文件
        template = DocxTemplate(template_path) # 通过DocxTemplate()函数建立模板文件,该函数传入的参数是提前制作好的Word模板文件路径
        # 从instance实例中获取当前简历字段信息
        name = instance.name
        personID = instance.personID
        sex = instance.sex
        email = instance.email
        birth = instance.birth
        edu = instance.edu
        school = instance.school
        major = instance.major
        position = instance.position
        experience = instance.experience
        photo = instance.photo

        context = {
            \'name\': name,
            \'personID\': personID,
            \'sex\': sex,
            \'email\': email,
            \'birth\': birth,
            \'edu\': edu,
            \'school\': school,
            \'major\': major,
            \'position\': position,
            \'experience\': experience,
             # 图像字段通过特殊的InlineImage()函数进行替换
            \'photo\': InlineImage(template, photo, width=Mm(30), height=Mm(40)), 
        }# width和height参数分别用来设置最终在word中显示的宽度和高度
        
        template.render(context) # render()函数进行字段替换
        filename = \'%s/media/contact/recruit/%s_%d.docx\' % (
            os.getcwd(), instance.name, instance.id)
        template.save(filename)

为了能够使用上述Word模板文件库,需要在models.py文件头部引入相关库文件

import os
from docxtpl import DocxTemplate
from docxtpl import InlineImage
from docx.shared import Mm, Inches, Pt

第九章

开发\'服务支持\'模块,这里包含两个子页面:\'资料下载\'和\'人脸识别开放平台\'。
资料下载页面是为了完善企业门户网站的实际需求而设计,用户在购买产品后往往需要下载说明书或SDK驱动安装包等,这时候就需要在官网提供相应的下载功能以方便用户获取。
人脸识别开放平台是模拟上述流程,通过Django搭建一个基于Web的人脸检测平台,以API形式对外提供服务,这里为了简单,只使用了OpenCv提供的现成的人脸检测算法来进行检测。实际情况下可以根据服务器配置采用更高级的人脸检测算法(例如基于深度学习的MTCNN人脸检测算法等等)来提高检测精度。

开发资料下载功能

创建资料下载模型

为了能够让管理员上传并且管理资料文件,可以对资料进行建模,在数据库中创建对应的资料类Doc
打开serviceApp应用中的models.py文件,参照之前创建的"新闻"模型,定义资料模型如下

from django.db import models
import django.utils.timezone as timezone

# Create your models here.


class Doc(models.Model):
    title = models.CharField(max_length=250, verbose_name=\'资料名称\')
    file = models.FileField(upload_to=\'Service/\',
                            blank=True,
                            verbose_name=\'文件资料\')
    publishDate = models.DateTimeField(max_length=20,
                                       default=timezone.now,
                                       verbose_name=\'发布时间\')

    def __str__(self):
        return self.title

    class Meta:
        ordering = [\'-publishDate\']
        verbose_name = "资料"
        verbose_name_plural = verbose_name

创建完成后使用迁移命令将Doc模型同步到数据库中

python manage.py makemigrations
python manage.py migrate

接下来打开admin文件,将Doc模型注册到后台管理系统中

from django.contrib import admin
from .models import Doc
# Register your models here.


admin.site.register(Doc)

保存所有修改后启动项目,登录后台管理系统,找到"资料"模型,为其添加几条数据,每条数据输入文件名并且上传对应的文件资料(我这里是由于上传的文件名没带后缀么,不是,而是上传路径有中文导致,下载下来的文件没有后缀)。注意:上传的文件资料路径中不能含有中文。

资料下载列表页面开发

"资料下载列表页面开发"可以借鉴新闻列表的开发方法。首先在serviceApp应用下创建templates模板文件夹,然后在该templates文件夹下新建模板文件docList.html。将前面newsApp应用下的newList.html全部内容复制到docList.html内,然后对关键部分进行修改。
这里注意每项资料的链接设置,具体如下

{% for doc in docList %} <!--每项资料的设置-->
                <div class="news-model">
                    <img src="{% static \'img/newsicon.gif\' %}">
                    <!--资料访问路由-->
                    <a href="{% url \'serviceApp:getDoc\' doc.id %}"><b>{{doc.title}}</b></a>
                    <span>【{{doc.publishDate|date:"Y-m-d"}}】</span>
                </div>
{% endfor %}

每一项资料的下载链接以"资料访问路由+文件id"的形式给出,资料访问路由{% url \'serviceApp:getDoc\' doc.id %}将在urls.py文件中进行设置
接下来修改urls.py文件,添加getDoc路由,具体如下

urlpatterns = [
      \'\'\'其他路由\'\'\'
      path(\'getDoc/<int:id>/\', views.getDoc, name=\'getDoc\'),#单项资料下载
  ]

打开views.py文件,修改download函数,该函数主要用来获取资料列表并进行页面渲染,具体内容与newsApp应用下views.py文件中的news()函数基本一致,实际操作时只需要将news()函数中的内容复制过来并且简单修改即可
本节重点阐述如何编写资料下载函数getDoc(),在第3章构建企业门户网站框架时通过HttpResponse()返回字符串来响应用户请求,后面几章则采用render()函数来渲染页面并返回。他们的本质均可以看成将这种页面的请求和响应看作是文件下载,即用户请求指定内容,服务器收到请求后将内容安装指定的格式下载到浏览器,浏览器再将内容进行输出。因此,对于特定的文件(Word文档、PDF文档、压缩包等)下载均可以采用HttpResponse()来直接响应用户的下载行为。HttpResponse()会使用迭代器对象,将迭代器对象的内容存储成字符串,然后返回给客户端,同时释放内存。但是当文件较大时,如果仍然采用HttpResponse(),将会时一个非常耗费时间和内存的过程,并且容易导致服务器崩溃。因此,一般文件下载并不会采用这种方式
Django针对文件下载专门提供了StreamingHttpResponse对象来代替HttpResponse对象,以流的形式提供下载功能。StreamingHttpResponse用于将文件流发送给浏览器,与HttpResponse对象非常相似。但对于文件下载功能,使用StreamingHttpResponse对象更稳定和有效。

具体的,在views.py文件中先添加一个文件分批读取的函数read_file(),该函数通过构建一个迭代器,分批处理文件,然后将这个迭代器作为结果返回。

def read_file(file_name, size):  #分批读取文件
    with open(file_name, mode=\'rb\') as fp: # file_name参数为文件路径,size参数表示分批读取文件的大小
        while True:
            c = fp.read(size)
            if c:
                yield c
            else:
                break

接下来开始编写getDoc()函数,代码如下

from django.shortcuts import get_object_or_404
from django.http import StreamingHttpResponse
import os


def getDoc(request, id):
    doc = get_object_or_404(Doc, id=id) # 获取文件:根据传入的文件id通过get_object_or_404()函数将文件从数据库中提取出来
    update_to, filename = str(doc.file).split(\'/\')
    filepath = \'%s/media/%s/%s\' % (os.getcwd(), update_to, filename)
    response = StreamingHttpResponse(read_file(filepath, 512)) # # 通过read_file()函数读取文件,并以512B为单位构造迭代器,该迭代器返回后直接传给StreamingHttpResponse
    # 设置文件类型
    response[\'Content-Type\'] = \'application/octet-stream\'
    response[\'Content-Disposition\'] = \'attachment;filename="{}"\'.format(
        filename)
    return response

搭建"人脸识别开放平台"

通过Django框架搭建一个人脸检测Web平台,以API形式提供对外服务。这里采用OpenCV提供现成的人脸检测算法来进行检测,实际情况下可以更具服务器的CPU或GPU性能,采用更高级的人年检测算法来提高检测精度。

人脸识别后台搭建

OpenCV是一个开放源代码的计算机视觉应用库,由英特尔企业下属研发中心俄罗斯团队发起,开源免费,设计目标是实现高效的计算机视觉任务,是一个跨平台的计算机视觉库,从开发之日起就得到了迅猛发展。OpenCV中多数模块是基于C++实现的,其中有少部分是基于C语言实现的,算法经过高度优化,实现效率高,非常适合生产环境使用。当前OpenCV提供的SDK已经支持C++\java\Python等语言的应用开发
首先,下载并安装用于Python的OpenCV开发包

pip install opencv_python

离线安装包的后缀名称是whl,离线安装方式是

pip install 路径\包名.whl

接下来开发后台视图处理函数。由于需要开发基于API的接口,因此大部分功能代码都会在views.py文件中编写。当后台收到用户的请求以后,由指定的视图处理函数对用户上传的图片进行读取,然后调回OpenCV人脸检测算法进行检测,最后将检测结果以JSON字符串形式返回给用户
打开serviceApp应用下的views.py文件,在头部导入一些Python库

# 人脸识别相关包导入
import numpy as np  # 矩阵运算
import urllib # URL解析
import json # json字符串使用
import cv2 # openCv包
import os # 执行操作系统命令
from django.views.decorators.csrf import csrf_exempt # 跨站点验证
from django.http import JsonResponse

为了能够进行人脸检测,需要使用特定的人脸检测器,一般情况下需要运用机器学习算法进行训练得到,而OPenCV库自带高效的人脸检测器,无需在训练直接拿来使用即可
在OpenCV的安装目录中找到haarcascade_frontalface_default.xml文件(路径:Python 安装目录+\Lib\site-packages\cv2\data)。该XML文件本质上是一个配置文件,用于保存训练好的人脸特征检测器模型参数,使用时只需要导入该文件即可进行人脸检测。为了方便项目使用,将该XML文件放置在项目serviceApp目录下。
继续编辑views.py文件,添加facedetect()函数如下

face_detector_path = "serviceApp\\haarcascade_frontalface_default.xml"
face_detector = cv2.CascadeClassifier(face_detector_path)  # 生成人脸检测器

@csrf_exempt # 用于规避跨站点请求攻击
def facedetect(result):
    result = {}

    if request.methon == "POST": # 规定客户端使用POST上传照片
        if request.FILES.get("image",None) is not None: # 读取图像
            img = read_image(stream = request.FILES["image"])
        else:
            result.update({"#faceNum":-1,})
            return JsonResponse(result)
        if img.shape[2] == 3:
            img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) # 彩色图像转为灰色;这里我之前打了,怪不得显示黄色下标波浪线
        
        # 进行人脸检测
        values = face_detector.detecMultiScale(img,scaleFactor = 1.1,minNeighbors=5,minSize=(30,30),flags=cv2.CASCADE_SCALE_IMAGE)
        # 将检测得到的人脸检测关键点坐标封装
        values = [(int(a),int(b),int(a+c),int(b+d)) for (a,b,c,d) in values] # 这a,b,c,d分别表示什么呢
        result.update({
            "#faceNum":len(values),
            "faces":values
        })
        return JsonResponse(result)

下面给出自定义的图像读取read_image()函数

def read_image(stream=None):
    if stream is not None:
        data_temp = stream.read()
        img = np.asfarray(bytearray(data_temp),dtype="uint8")
        img = cv2.imdecode(img,cv2.IMREAD_COLOR)
        return img

打开serviceApp应用下的urls.py文件,在urlpatterns字段中添加路由:

path(\'facedetect/\', views.facedetect, name=\'facedetect\'),  # 人脸检测api

本地脚本测试

为了能够在本地使用Python发送HTTP请求,需要下载requests库并进行安装

pip install requests

这里为了项目集成方便将本地调用脚本放置在项目根目录下的test文件夹中,命名为faceDetectDemo.py,然后再文件同目录下放置一张测试图片face.jpg,用于进行人脸检测。编辑代码如下

import cv2,requests # 调用网站接口本地测试
url = "http://localhost:8000/serviceApp/facedetect" # 本地测试localhost

# 上传图像并检测
tracker = None
imgPath = \'face.jpg\' # 图像路径
files = {"image":("filename2",open(imgPath,"rb"),"image/jpeg"),}
# 第9行返回json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
req = requests.post(url,data=tracker,files=files).json() #返回的请求数据转化为字符串
print("获取信息:{}".format(req))


# 将检测结果框显示在图像上
img = cv2.imread(imgPath)
for (w,x,y,z) in req["face"]:
    cv2.rectangle(img,(w,x),(y,z),(0,255,0),2)

cv2.imshow(\'face\',img)
cv2.waitKey(0)

由于每一个响应都需要主服务器进行人脸检测操作,而本身人脸检测算法香港队其他类型的常规操作会更加耗时并且耗资源,当并发访问数过大时会导致网站崩溃,这就是通常所说的访问量过载。访问量过载包含两个方面:一是超负荷访问,即后台主机性能有限,无法抗住过大的访问量;二是网站代码存在性能问题,将系统拖慢,导致网站服务器崩溃。
针对以上问题,有两种修复手段:
1.限制访问。比如限制访问的频率,这个调整应该是动态的。这样做可以确保服务的可用性,但也会牺牲部分用户的访问。正常情况下,服务器能支撑多大的访问量是需要技术人员再系统业务上线之前做好测试,提前做好数据支撑的。
2.在时间允许的情况下,如果后端服务器具有扩容条件,则可以对崩溃期间的访问数据进行分析,然后根据分析结果进行扩容服务,再逐步开放访问限制。

前端说明页面

在templates文件夹中新建文件platForm.html,该文件头尾部分与docList.html文件基本一致,只需要修改对应的名称即可。
页面主体部分以说明文字为主,采用常规的HTML标签进行编写,主要对"人脸识别开放平台"的接口信息进行阐述,同时给出了基于Python的接口调用示例。在代码演示部分,实现了适合Python格式的语法高亮功能,可以让用户方便的进行代码浏览和复制。为了实现这一功能,这里集成CodeMirror插件来开发。
从CodeMirror插件包中引入必要的JS和CSS文件;其中codemirror.css、codemirror.js、python.js文件可以从CodeMirror插件包中找到。

<!--引入的是CodeMirror插件,本质上是一个在线代码编辑器的基础库;可以使代码在页面上显示的时候高亮-->
<link rel="stylesheet" href="{% static \'css/codemirror.css\' %}">
<script src="{% static \'js/codemirror.js\' %}"></script>
<script src="{% static \'js/python.js\' %}"></script>
<style type="text/css">
    .CodeMirror {
        border-top: 1px solid black;
        border-bottom: 1px solid black;
    }
</style>

通过HTML标签<textarea>实现代码的显示和编辑

<div><textarea id="code" name="code"> <!--textarea实现代码的显示和编辑-->
此处填写python代码
</textarea></div>

最后添加js代码

<script>
    var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
        mode: {
            name: "python",
            version: 3,
            singleLineStringErrors: false
        },
        lineNumbers: true,
        indentUnit: 4,
        tabMode: "shift",
        matchBrackets: true
    });
</script>

在线人脸检测

在线Web调用的方式,即用户在网页上上传一张图片,然后单击点击检测即可在网页上实现人脸检测并获取检测结果。
首先编写网页端功能。为了能够与当前的“人脸识别开放平台”说明页面区分开来,将通过Bootstrap提供的模式对话框来实现图片的在线检测。打开serviceApp应用下的platform.html文件,在主体部分<div class="model-details">中添加标题,并增加一个按你有用来弹出模式对话框

当在页面中单击"人脸检测"按钮时,弹出右侧所示对话框,此时背景页面会呈现灰色,只有在模式对话框关闭时页面才会恢复正常。采用这种方式,可以方便地在页面中进行额外地界面操作而不影响页面原有布局。Bootstrap提供了现成地模式对话框组件,可以方便地直接使用。具体代码如下(重点)

<h3>一. 体验产品</h3>
                </br>
                <!-- 按钮触发模态框 modal组件的作用就是当点击按钮弹出对话框时,背景页面呈现灰色,只有在模式对话框关闭时页面才恢复正常-->
                <button class="btn btn-primary btn-lg" data-toggle="modal" data-target="#myModal">
                    人脸检测
                </button>
                <!-- 模态框(Modal) -->
                <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"
                    aria-hidden="true">
                    <div class="modal-dialog">
                        <div class="modal-content">
                            <div class="modal-header">
                                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">
                                    &times;
                                </button>
                                <h4 class="modal-title" id="myModalLabel">
                                    在线人脸检测
                                </h4>
                            </div>
                            <div class="modal-body">
                                <img id="photoIn" src="{% static \'img/sample.png\' %}" class="img-responsive"
                                    style="max-width:250px">
                                <input type="file" id="photo" name="photo" />
                            </div>
                            <div class="modal-footer">
                                <button type="button" class="btn btn-default" data-dismiss="modal">关闭
                                </button>
                                <button type="button" id="compute" class="btn btn-primary">
                                    开始检测
                                </button>
                            </div>
                        </div><!-- /.modal-content -->
                    </div><!-- /.modal -->
                </div>
                <script>//对于上传图片及时显示的操作
                    $(function () {
                        $(\'#photo\').on(\'change\', function () {
                            var r = new FileReader();
                            f = document.getElementById(\'photo\').files[0];
                            r.readAsDataURL(f);
                            r.onload = function (e) {
                                document.getElementById(\'photoIn\').src = this.result; //把图片内容赋值给图片组建的src属性
                            };
                        });
                    });
                </script>

接下来使用Ajax技术来实现图片发送功能,将图片发送至服务器并且进行人脸检测,返回地结果也以图像地形式进行接收并显示。具体代码如下

 <script>
                    $(\'#compute\').click(function () {
                        formdata = new FormData();
                        var file = $("#photo")[0].files[0];
                        formdata.append("image", file);//把图片文件这样封装
                        $.ajax({
                            url: \'/serviceApp/facedetectDemo/\', // 调用Django服务器计算函数
                            type: \'POST\', // 请求类型
                            data: formdata,
                            dataType: \'json\', // 期望获得的响应类型为json
                            processData: false,
                            contentType: false,
                            success: ShowResult // 在请求成功之后调用该回调函数输出结果
                        })
                    })
                </script>
                <script>
                    function ShowResult(data) {
                        var v = data[\'img64\']; //图像编码
                        document.getElementById(\'photoIn\').src = "data:image/jpeg;base64, " + v;
                    }
</script>

开发后台视图处理程序

需要在前端页面中根据返回的坐标值在画布Canvas中进行坐标换算和画框,页面逻辑较为复杂,为了简化实现过程,我们在后端检测完成后直接将显示框绘制在原图上。
具体的,在views.py中添加facedetectDemo()函数,详细代码如下

import base64


@csrf_exempt
def facedetectDemo(request):
    result = {}

    if request.method == "POST":
        if request.FILES.get(\'image\') is not None:  #
            img = read_image(stream=request.FILES["image"])
        else:
            result["#faceNum"] = -1
            return JsonResponse(default)

        if img.shape[2] == 3:
            imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 彩色图像转灰度图像
        else:
            imgGray = img

        #进行人脸检测
        values = face_detector.detectMultiScale(imgGray,
                                           scaleFactor=1.1,
                                           minNeighbors=5,
                                           minSize=(30, 30),
                                           flags=cv2.CASCADE_SCALE_IMAGE)

        #将检测得到的人脸检测关键点坐标封装
        values = [(int(a), int(b), int(a + c), int(b + d))
                  for (a, b, c, d) in values]

        # 将检测框显示在原图上
        for (w, x, y, z) in values:
            cv2.rectangle(img, (w, x), (y, z), (0, 255, 0), 2)

        retval, buffer_img = cv2.imencode(\'.jpg\', img)  # 在内存中编码为jpg格式
        img64 = base64.b64encode(buffer_img)  # base64编码用于网络传输
        img64 = str(img64, encoding=\'utf-8\')  # bytes转换为str类型
        result["img64"] = img64  # json封装
    return JsonResponse(result)

最后为新添加的视图处理函数添加对应的路由,打开urls.py文件,添加路由如下:

path(\'facedetectDemo/\', views.facedetectDemo, name=\'facedetectDemo\'),  # 人脸检测api

第十章

开发首页模块,该模块作为一个集成模块,将会使用到之前构建的各个子模块的内容,通过数据筛选和渲染实现各个子模块的整合显示。另外首页作为门户网站的初始页面,在页面的设计过程中将重点考虑其排版、美观等特性。
首页模块包括:\'轮播横幅\'、"企业概况"、"通知公告"、"科研基地"、"联系我们"、"产品中心"六个模块组成
1.轮播横幅展示
轮播组件使用样式类carousel slide进行声明,data-ride="carousel"用来设定该组件的形式为轮播,data-interval="5000"设置每张图像的显示时间,单位是毫秒

<!--轮播组件使用样式类carousel slide进行声明,data-ride="carousel"用来设定该组件的形式为轮播,data-interval="5000"设置每张图像的显示时间,单位是毫秒-->
<div id="ad" class="carousel slide" data-ride="carousel" data-interval="5000">
    <ol class="carousel-indicators">
        <!--绑定轮播索引-->
        <li data-target="#ad" data-slide-to="0" class="active"></li>
        <!--data-slide-to设置每张图片的轮播标号-->
        <li data-target="#ad" data-slide-to="1"></li>
        <li data-target="#ad" data-slide-to="2"></li>
    </ol>
    <div class="carousel-inner">
        <!--carousel-inner实现每张图像的内容显示-->
        <div class="item active">
            <img src="{% static \'img/banner1.jpg\' %}" alt="广告横幅1">
        </div>
        <div class="item">
            <img src="{% static \'img/banner2.jpg\' %}" alt="广告横幅2">
        </div>
        <div class="item">
            <img src="{% static \'img/banner3.jpg\' %}" alt="广告横幅3">
        </div>
    </div>
    <!--left carousel-control用于控制左右侧的图片切换功能-->
    <a class="left carousel-control" href="#ad" data-slide="prev"><span
            class="glyphicon glyphicon-chevron-left"></span></a>
    <a class="right carousel-control" href="#ad" data-slide="next"><span
            class="glyphicon glyphicon-chevron-right"></span></a>
</div>

2.主体效果分析
可以将主体部分分为4行:\'企业概况\' 和新闻动态占一行(所占栅格分别为4和8);\'通知公告\'‘科研基地’和‘联系我们’占一行(所占栅格数均为4);‘产品中心’站一整行;’友情链接‘占一整行,
1.\'企业概况\'

<div class="col-md-4">
            <!-- 企业概况 -->
            <span class="part1">
                <a href="{% url \'aboutApp:survey\' %}">企业概况</a>
            </span>
            <span class="part1 en">
                &nbsp;&nbsp;/ About Us
            </span>
            <div class="line1">
                <div class="line2 theme"></div>
            </div>
            <div style="margin-top:20px;">
                <img class="img-responsive" src="{% static \'img/aboutCompany.jpg\' %}">
                <p class="text1">
                    恒达科技有限企业,位于中国某高新技术产业开发区,
                    以社会公共安全领域为主要应用方向,提供极速、准确和防伪装的人脸识别产品。
                </p>
            </div>
        </div>

2.新闻动态
1.编辑后台处理函数,打开homeApp应用下的views.py文件,重写编辑home()函数如下
首先再头部引入了newsApp.models中定义的MyNew模型,然后从django.db.models中引入Q()函数。该对象可以用于复杂查询,可以对关键字参数进行封装,从而更好地应用于多条件查询,同时可以结合使用&(and),|(or),~(not)等操作符
2.数据过滤查询:在home()函数中,首先需要获取新闻数据,获取的数据按照时间由近到远进行排序,因此末尾采用order_by(\'-publishDate\');目前新闻类型包括3类,因此只需要排除最后一类"通知公告"即可获得另外两类的集合,此处采用了Q()函数结合~操作符进行过滤查询
3.新闻展报:新闻展报部分只需要截取最近的3条包含展报图片的新闻,因此对于查询得到的QuerySet逐个进行检查,当新闻中包含展报时则将该条新闻添加进新的集合变量postList中,重复检查直到找满3条展报新闻为止。
4.新闻列表:为了方便首页显示,对于获取的新闻列表只选取其中的7条进行显示,使用newList[0:7]进行截取

from newsApp.models import MyNew
from django.db.models import Q # 用于对象的复杂查询,可以对关键字参数进行封装,从而更好地应用于多条件查询
def home(request):
  # 新闻展报
  # Q()函数结合~操作符进行过滤查询
  newList = MyNew.objects.all().filter(~Q(
        newType=\'通知公告\')).order_by(\'-publishDate\')
  postList = set()
  postNum = 0
  for s in newList:
      if s.photo:
          postList.add(s)
          postNum += 1
      if postNum == 3:  # 只截取最近的3个展报
          break
  
  # 新闻列表
  if (len(newList)>7):
    newList = newList[0:7]
  return render(request,\'home.html\',{
    "active_menu": "home",
    "postList":postList,
    "newList":newList,})

2.1编写前端页面代码

前端页面分为左右两个部分:左边是展报(占5个栅格),右边是新闻列表(占7个栅格)。展报部分用于展示近期重要的新闻信息,以图片形式来吸引用户进行新闻浏览。这里采Bootstrap的轮播组件进行实现,与轮播横幅不同之处在于,每张图像上均附有新闻标题.新闻列表部分主要用于显示第七章开发的’新闻动态模块列表信息‘。(巨坑:新闻动态轮播组件的缩略图没有实现
布局结构:"新闻动态"模块整体占8个栅格,在其内部进行二次栅格划分,分成左右两个子模块,分别对应"新闻展报"(占5格)和"新闻列表"(占7格)
新闻展报:展报部分采用Bootstrap提供的轮播组件实现,其中需要注意每幅展报的data-slide-to属性,该属性从0开始,用于区分控制每幅展报,这里采用了模板标签{{forloop.counter0}}来表示,该模板标签在迭代的过程中从0开始计数;另外,对于第一幅海报,需要为其添加class="active"样式,这样就能正常的启动轮播组件,因此使用模板标签{% if forloop.first %}来判断当前迭代是否是第一次;同样的,在轮播项目中需要使用{% if forloop.first %}来动态地判断当前展报是否需要声明class="item active"
新闻列表:新闻列表部分主要将后台传递地newlist变量逐个进行渲染,同时考虑到排版因素,对每条新闻地title字数使用truncatechars进行限制

 <div class="col-md-8">
            <!-- 新闻动态 -->
            <span class="part1">
                <a href="#">新闻动态</a>
            </span>
            <span class="part1 en">
                &nbsp;&nbsp;/ News
            </span>
            <a class="btn btn-default btn-xs more-btn" href="{% url \'newsApp:news\' \'company\' %}">
                +&nbsp;更多
            </a>
            <div class="line1">
                <div class="line2 theme"></div>
            </div>
            <div class="col-md-5">
                <div id="myCarousel" class="carousel slide" data-ride="carousel">
                    <ol class="carousel-indicators nav-point">
                        {% for post in postList %}
                        <li data-target="#myCarousel" data-slide-to="{{forloop.counter0}}" {% if forloop.first %}
                            class="active" {% endif %}>
                        </li>
                        {% endfor %}
                    </ol>
                    <!-- 轮播(Carousel)项目 -->
                    <div class="carousel-inner" style="margin-top:15px;">
                        {% for post in postList %}
                        <div {% if forloop.first %} class="item active" {% else %} class="item" {% endif %}
                            style="background-size:cover;">
                            <a href="{% url \'newsApp:newDetail\' post.id %}">
                                <!--这里不显示链接符号-->
                                <img src="{{post.photo.url}}" class="img-responsive" onload="DrawImage(this)"></a>
                            <div class="carousel-caption nav-title">{{post.title}}</div>
                        </div>
                        {% endfor %}
                    </div>
                </div>
            </div>
            <div class="col-md-7">
                <ul class="list-unstyled list-new">
                    {% for mynew in newList %}
                    <li>
                        <a href="{% url \'newsApp:newDetail\' mynew.id %}">
                            {{mynew.title|truncatechars:"15"}}</a>
                        <span>【{{mynew.publishDate|date:"Y-m-d"}}】</span>
                    </li>
                    {% endfor %}
                </ul>
            </div>
        </div>

    </div>

对样式进行设置,在home.css文件中添加如下代码

/*新闻中心*/
.carousel {
	margin-bottom: 40px;
}
.carousel .item {
	background-color: #000;
}
.carousel .nav-point{
	bottom:-14px;
}	
.carousel .nav-title{
	font-size:12px;
	bottom:-14px;
}
.carousel .item img {
    width: 100%;    
}
.carousel-caption {
	z-index: 10;
}
.carousel-caption p {
	margin-bottom: 20px;
	font-size: 20px;
	line-height: 1.8;
}
.more-btn{
	float:right;
	margin-top:7px;
	color:#828282;
	font-size:11px;
}
.list-new li{
	border-bottom:1px dashed #eae7e7;
	line-height:40px;
}
.list-new a{
	text-decoration:none;
	color:#666;
	font-size:13px;
}
.list-new a:hover{
	color:#d30a1c;
}
.list-new span{
	float:right;
	font-size:12px;
}
.list-new .public-detail{
	float:right;
	color:#d30a1c;
}

这里还存在个问题,当上传的展报图像尺寸不一致时,新闻展报在进行轮播的时候会出现错位现象,影响页面的整体美观性,一种简单的方法就是直接定义好渲染图像的长宽大小,但是这种方式会造成图像的畸变。另一种方法就是直接将图片按比例裁剪成固定像素宽高的图片再进行输出,本节采用jqthumb.min.js插件实现该功能。下载地址,下载解压后找到根目录下的dist子文件夹,其中jqthumb.min.js文件就是最终要获取的插件。
配置js插件
将jqthumb.min.js文件复制到项目根目录/static/js文件夹中,然后在home.html文件的{%block content%}中引入该插件

<script src="{% static \'js/jqthumb.min.js\' %}"></script>

然后添加对图像额外的裁剪代码:

<script>
    //处理缩略图
    function DrawImage(hotimg) {
        $(hotimg).jqthumb({
            width: \'100%\', // 宽度
            height: \'220px\', // 高度
            zoom: \'1\', // 缩放比例
            method: \'auto\' // 提交方法,用于不同的浏览器环境,默认为‘auto’
        });
    }
</script>

最后将轮播项目的<img>标签代码修改如下

 <img src="{{post.photo.url}}" class="img-responsive" onload="DrawImage(this)">

通知公告模块开发

通知公告部分主要是将"新闻"模型中类型为"通知公告"的新闻列表进行显示,在home()函数添加如下代码

# 通知公告
noteList = MyNew.objects.all().filter(
    Q(newType=\'通知公告\')).order_by(\'-publishDate\')
if (len(noteList) > 4):
    noteList = noteList[0:4]

然后在render返回函数中将变量noteList一起导出,之列只显示最近的4条公告
编写的前端页面代码

<div class="col-md-4">
            <!-- 通知公告 -->
            <span class="part1">
                <a href="#">通知公告</a>
            </span>
            <span class="part1 en">
                &nbsp;&nbsp;/ Public Release
            </span>
            <a class="btn btn-default btn-xs more-btn" href="{% url \'newsApp:news\' \'notice\' %}">
                +&nbsp;更多
            </a>
            <div class="line1">
                <div class="line2 theme"></div>
            </div>
            <div>
                <img class="img-responsive" src="{% static \'img/note.jpg\' %}">
                <ul class="list-unstyled list-new">
                    {% for note in noteList %}
                    <li>
                        <a href="{% url \'newsApp:newDetail\' note.id %}">
                            {{note.title|truncatechars:"25"}}
                        </a>
                        <a href="{% url \'newsApp:newDetail\' note.id %}" class="public-detail">
                            查看详情>>
                        </a>
                    </li>
                    {% endfor %}
                </ul>
            </div>

科研基地

\'科研基地\'模块主要用于显示科研基地相关的介绍性信息,包括静态图片和静态文字的渲染,内容不需要依赖后台服务器,该模块实现简单

<div class="col-md-4">
            <!-- 科研基地 -->
            <span class="part1">
                <a href="{% url \'scienceApp:science\' %}">科研基地</a>
            </span>
            <span class="part1 en">
                &nbsp;&nbsp;/ Technology Center
            </span>
            <div class="line1">
                <div class="line2 theme"></div>
            </div>
            <div>
                <a href="{% url \'scienceApp:science\' %}">
                    <img class="img-responsive" src="{% static \'img/ky.jpg\' %}">
                </a>
                <p class="text1">
                    <font color="#d30a1c">恒达科技科研基地</font>
                    恒达科研基地分为计算机视觉、 机器人和视觉深度学习三个事业部,共拥有高级研发人员近30名,
                    以各领域高级工程师和知名院校博士为主体的多层次研发梯队。当前,科研基地优秀的技术团队
                    已为恒达在人脸识别、物联网平台搭建、机器人导航等 领域打下了坚实基础
                </p>
            </div>

        </div>

联系我们

联系我们子模块同样也是静态内容,下面给出前端代码

<div class="col-md-4">
            <!-- 联系我们 -->
            <span class="part1">
                <a href="{% url \'contactApp:contact\' %}">联系我们</a>
            </span>
            <span class="part1 en">
                &nbsp;&nbsp;/ Contact us
            </span>
            <div class="line1">
                <div class="line2 theme"></div>
            </div>
            <div>
                <ul class="list-unstyled procurement-li">
                    <li>业务质询一:111-XXXXXX</li>
                    <li>业务质询二:222-XXXXXX</li>
                    <li>咨询电话:0111-XXXXXX</li>
                    <li>企业传真:0222-XXXXXX</li>
                    <li>地址:某某某新区某某大道1号</li>
                    <li>邮编:XXXXXX</li>
                    <li>
                        网址:<a href="http://python3web.com">http://python3web.com</a>
                    </li>
                </ul>
                <div class="platform"><a href="{% url \'contactApp:contact\' %}">详情</a></div>
            </div>

        </div>

在home.css文件中添加css样式设置

/* 联系我们 */
.procurement-li{
	line-height:35px;
	color:#666;
	font-size:13px;
}
.procurement-li a{
	text-decoration:none;
	color:#666;
}
.platform{
	width:100%;
	height:45px;
	background:#eae7e7;
	color:#fff;
	text-align:center;
	font-size:17px;
	line-height:42px;
}
.platform a{
	text-decoration:none;
	color:#666;
}
.platform a:hover{
	color:#d30a1c;
}

产品中心

在首页"产品中心"子模块上将显示4幅产品图片,本节将按照产品的浏览次数对产品进行排序,将浏览次数前4名的产品放置在首页进行显示
首先编辑后端视图处理函数,为了能够在homeApp中查询产品模型的信息,需要将productsApp应用中定义的Product模型导入到home.py文件中。

from productsApp.models import Product

然后在home函数中添加对应的代码

  # 产品中心
  productList = Product.objects.all().order_by(\'-views\')
  if (len(productList)>4):
    productList = productList[0:4]

最后将productList变量通过render()函数渲染到模板文件中
编辑前端页面

<div class="row row-3">
        <!-- 产品中心 -->
        <div class="col-md-12 col-pro">

            <span class="part1">
                <a href="{% url \'productsApp:products\' \'robot\' %}">产品中心</a>
            </span>
            <span class="part1 en">
                &nbsp;&nbsp;/ Products
            </span>
            <a class="btn btn-default btn-xs more-btn" href="{% url \'productsApp:products\' \'robot\' %}">
                +&nbsp;更多
            </a>
            <div class="line1" style="margin-bottom:5px;">
                <div class="line2 theme"></div>
            </div>
            <div class="col-md-12 col-pro">
                <div id="Carousel" class="carousel slide" style="margin-bottom:30px">
                    <ol class="carousel-indicators" style="display:none;">
                        <li data-target="#Carousel" data-slide-to="0" class="active"></li>
                    </ol>
                    <div class="carousel-inner">
                        <div class="item active">
                            <div class="row">
                                {% for product in productList %}
                                <div class="col-md-3 pro-images">
                                    <a href="{% url \'productsApp:productDetail\' product.id %}" class="thumbnail">
                                        {% for img in product.productImgs.all %}
                                        {% if forloop.first %}
                                        <img src="{{img.photo.url}}" alt="产品图片" class="img-responsive"
                                            onload="DrawImage(this)">
                                        {% endif %}
                                        {% endfor %}
                                    </a>
                                    <div class="carousel-caption nav-title">{{product.title}}</div>
                                </div>
                                {% endfor %}
                            </div>
                        </div>
                    </div>
                </div>
            </div>

        </div>
    </div>

在home.css文件中添加样式设置

/* 产品中心 */
.pro-images{
	background-color:#f6f6f6; 
 }
 .thumbnail{
	 margin-bottom:0px;
 }
 .col-pro{
	 background-color:#F6F6F6;
	 padding-top:15px;
 }

Django缓存系统

缓存系统工作原理:对于给定的网址,服务器首先尝试从缓存中找到网址,如果页面在缓存中,直接返回缓存的页面,如果缓存中没有,则执行相关操作(比如查找数据库)后,保存生成的页面内容到缓存系统以供下一次使用,然后返回生成的页面内容。
使用缓存一种简单方法就是利用服务器本地的内存来当缓存,速度响应较快,但是这种方式不利于管理,当数据量达的时候可以采用另一种更稳妥、更有效的方式;使用数据库作为缓存后台。
首先在数据库中创建缓存表,在终端中输入命令

python manage.py createcachetable cache_table_home

通过上述方式在数据库中创建了一个名为cache_table_home的缓存表,接下来在项目配置文件中配置缓存,打开settings.py文件,在文件末尾添加配置代码如下

#配置缓存表
CACHES = {
    \'default\': {
        \'BACKEND\': \'django.core.cache.backends.db.DatabaseCache\', # BACKEND属性用于设置当前缓存使用何种后台,这里设置为数据库形式
        \'LOCATION\': \'cache_table_home\', # 用于设置具体的的缓存表
        \'TIMEOUT\': 600, # 设置默认超时时间,以秒为单位
        \'OPTIONS\': {
            \'MAX_ENTRIES\': 2000 # 最大并发量
        }
    }
}

完成上述配置后,只需要在需要缓存的页面视图处理函数前添加缓存装饰器代码即可。这里缓存15分钟是指,15分钟以内用户请求首页时,后台服务器在缓存中存在首页,因此会直接将缓存中的首页返回给用户而不用经视图函数home()处理,当时间超过15分钟以后,新的请求会经过home()函数处理,新的页面会返回用户同时在缓存中更新备份。

@cache_page(60 * 15) # 单位:秒数,这里指缓存 15 分钟
# python的render函数模板渲染
def home(request):
\'\'\'首页代码部分\'\'\'

第十一章

基于Windows的项目部署

本地服务器部署

Python WSGI部署原理介绍:
WSGI是建立在CGI的基础上的,CGI的全称是Common Gateway Interface,即\'通用网关接口\',而WSGI就是针对Python的网页应用接口 Python Web Server Gateway Interface.WSGI只是一个接口定义,他不负责服务器的实现,也不负责网页应用的实现,他只是两边接口方式的阅读,只是一种规范,描述Web服务器如何与应用通信的规范。要实现WSGI协议,必须同时实现服务器和Web应用。简单来说,WSGI规范了一种简单的接口,将服务器和应用分开来,使得两边的开发者能够更加专注自身的开发。例如前面编写的网页,就是web应用,而Django运行它展示在网页上就是服务器,这是Django自身提供的服务器。

WSGI协议主要包括Web服务器和应用两部分

1.Web服务器:即HTTP服务器,按照HTTP接收用户HTTP请求并提供并发访问,调用Web应用处理业务逻辑。通常Web服务器采用C/C++编写,典型Web服务器有:Apache、Nginx和IIS.WSGI服务器负责从客户端接收request,然后将request转发给应用,处理完以后再将应用的response返回给客户端。
2.Python Web应用:应用程序接收由服务器转发的request,处理对应的请求,并将处理结果返回给服务器。应用中可以包括多个栈式的中间件middlewares,主要起调节作用。
传统CGI接口性能较差,每次HTTP服务器遇到动态程序时需要重启解析器来执行解析,然后才将结果返回给HTTP服务器。这种方式在处理高并发访问时几乎是不可能完成任务的,因此诞生了FastCGI,这是一个可伸缩的、高速的在HTTP服务器和动态脚本语言间通信的接口。主要优点是把动态语言和HTTP服务器分离开来,目前多数流行的HTTP服务器都支持FastCGI,包括Apache、Nginx和IIS等。
wFastCGI为支持Python语言的FastCGI,接口方式采用C/S架构,可以将HTTP服务器和Python脚本解析器分开,同时在脚本解析服务器上启动一个或多个脚本解析并守护进程。当HTTP服务器每次遇到动态程序时,可以将其直接交付给FastCGI进程来执行,然后将得到的结果返回给浏览器。这种方式可以让HTTP服务器祝阿姨处理静态请求或者将动态脚本服务器的结果返回给客户端,这在很大程度上提高了整个应用系统的性能。

准备部署环境

1.在cmd或VS code的命令终端中输入命令
pip freeze > requirement.txt
允许命令后,Python会自动搜索当前环境中的所有依赖包名及其对应的版本号,并将这些信息写入到requirements.txt文件中。
2.如果部署工程到新的机器上,此时只需要安装此文件中的依赖即可,在目标机器上输入下述命令即可快速安装项目相关的依赖包。
pip install -r requirement.txt

安装和配置IIS

开放端口

在防火墙那里,单击左侧菜单栏中的"高级设置"按钮进入高级设置界面,在左侧"入站规则"面板中单击"新建规则"按钮,进入入站规则向导界面。
选中"端口"单选按钮,然后单击下一步按钮,进入端口设置界面,将8001端口开放。(其中,应用规则选择TCP,规则应用范围选择"特定本地端口"。最后单击"下一步"按钮即可完成整个端口开放任务)

本地部署

1.安装并启动wfastcgi为了将Django项目能够部署到IIS服务器上,需要安装Python包wfastcgi,该包作为Python的脚本解析器在动态程序和IIS服务器间实现脚本解析。以管理员身份打开命令行工具进行安装,输入如下命令

pip install wfastcgi

完成安装后在命令行输入

wfastcgi - enable

输出结果最后冒号中的内容即为当前Python和wfastcgi解释器核心脚本的具体路径,用符号"|"隔开

2.配置web.config文件

<?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <system.webServer>
            <handlers>
                <add name="Python FastCGI" path="*" verb="*" modules="FastCgiModule" scriptProcessor="c:\users\囧包子\appdata\local\programs\python\python36\python.exe|c:\users\囧包子\appdata\local\programs\python\python36\lib\site-packages\wfastcgi.py" resourceType="Unspecified" requireAccess="Script" />
            </handlers>
            <httpErrors errorMode="Detailed" />
        </system.webServer>
        <appSettings>
            <add key="WSGI_HANDLER" value="django.core.wsgi.get_wsgi_application()" />
            <add key="PYTHONPATH" value="D:\百度网盘企业版\a_pactice\Django_tets\企业门户网站\hengDaProject"/>
            <add key="DJANGO_SETTINGS_MODULE" value="hengDaProject.settings" />
        </appSettings>
    </configuration>

这里对照着自己的网站,修改三处地方替换即可
1.scriptProcessor中冒号部分分别填入前面对应的Python和wfastcgi解释器核心脚本文件内所在路劲
2. ,这里的value要替换成当前的项目根目录(跟manage.py同目录)
3.,这里的value处需要写入项目配置模块名称

3.静态文件迁移

在项目根目录下的static文件夹中同样创建一个web.config文件,文件内容如下

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <remove name="Python FastCGI" />
    </handlers>
  </system.webServer>
</configuration>

该文件同于为IIS指明静态资源文件的渲染方式。接下来需要将项目所有的静态资源文件css、js、img等全部导入到项目根目录下的static文件夹中。打开项目配置文件settings.py,将

STATICFILES_DIRS = (
    os.path.join(BASE_DIR, "static"), # 若采用上面的话谷歌浏览器还可以正常访问,而edge则只能加载静态页面,模板变量都无法加载
)

替换为

STATIC_ROOT = os.path.join(BASE_DIR,\'static\')

然后在终端中执行命令:

python manage.py collectstatic 

然后按照提示即可完成静态资源文件迁移。从本质上来说,静态文件迁移就是将原先分散在各个应用下的静态文件全部复制到项目根目录下的static文件夹中,这样做是为了方便IIS服务器查找静态资源文件。

4.IIS创建网站

在控制面板的管理工具中打开"Internet Information Services(IIS)管理器"。在IIS管理器左侧导航面板中右击"网站",在弹出的快捷菜单中选择"添加网站"选项,进入网站添加界面。
其中,物理路径为项目根路径,IP地址和主机名可以不用填写,端口号采用之前开放的端口,端口号的设置不能与本机当前其他网站或程序冲突。

把sqlite3替换成mysql

首先下载python连接mysql的包,这里使用mysqlclient(连接也可以用pymysql,挺好操作的,但迁移不知道)

pip install mysqlclient

然后在settings.py文件中配置数据库,找到DATABASES字段
修改如下

DATABASES = {# 这里为数据库配置参数
    \'default\':{
        \'ENGINE\': \'django.db.backends.mysql\', # 数据库引擎
        \'NAME\':\'pythonweb\', # 数据库名字
        \'HOST\':\'\', # 数据库IP地址,默认为空即为本机
        \'PORT\':\'3306\', # 端口号
        \'USER\':\'root\',  # 远程登录账号,一般设置为root
        \'PASSWORD\':\'1024jihaikang\', # MySQL登录密码
    }
}

然后再执行同步数据库

python manage.py createcachetable # 这个是创建个缓存表
python manage.py makemigrations # 完成同步数据库的准备工作
python manage.py migrate # 同步数据库

至此就把django自带的sqlite3数据库数据迁移到mysql

分类:

技术点:

相关文章: