目录
5.1 基本框架
前面课程的内容重点介绍了门户网站的基本设计框架,涵盖页面制作、页面切换、数据库操作、后台系统管理、模板显示等基本知识点。本节课将在此基础上通过学习一些常见的第三方工具和接口来进一步丰富门户网站的功能。本章内容对应hengDaProject项目中的contactApp应用,分为两个子页面:“欢迎咨询”和“加入恒达”。“欢迎咨询”子页面主要将企业的地址、电话、联系人、定位地图等相关信息进行展示,这一部分内容都是静态页面,且内容延续前面的模板,所以读者可以自行尝试完成。“加入恒达”子页面主要以招聘和应聘需求为导向构造一个简易的在线互动系统,招聘人员可以通过后台管理系统发布招聘信息并显示在“加入恒达”页面上,用户浏览招聘信息后可以以表单的形式提交个人简历信息来进行应聘,在用户提交信息完成后,后台服务器会通过邮件服务向指定邮箱发送邮件通知。本节重点介绍基于表单的信息上传、邮件发送、触发器等内容。需要指出的是这些技术点并不是制作企业门户网站所必须的,但是掌握好本章的这些内容有助于加深前端和Python Web的理解,能够进一步丰富企业门户网站内容。
在阐述各组件应用前,需要先开发“人才招聘”模块两个子页面对应的基础页面。从示例网站所示的界面效果看出,两个子页面主体部分仍然采用左右布局形式,左侧为二级导航栏,右侧为具体内容信息。每个子页面采用独立页面的形式进行展示。由于内容相对简单并且与第3课具有高度的一致性,因此本章不再重复阐述页面的布局和子页面切换流程设计,读者可以按照第3课内容以及最终的实现效果自行完成页面设计和后台编码,同时可以参考本课程配套资源代码查看完整细节。这里仅对关键部分作一些说明,“欢迎咨询”子页面对应templates夹下面的contact.html文件,而“加入我们”子页面对应recruit.html文件。另外,我们同样针对“人才招聘”应用设计特有的css样式,因此在style.css同目录下面创建一个contact.css样式文件。
欢迎咨询页面效果图如下:
5.2 信号触发器的使用
参考示例网站效果,“加入恒达”页面作为招聘页面允许招聘者通过后台管理系统在该页面上发布招聘信息,同时也允许应聘人员通过表单将个人应聘信息上传到后台使招聘者可以查看应聘信息。应聘人员通过初面后将收到系统发送的邮件通知。通过这样一个应聘与招聘模块的开发,可以形成一个简易的在线互动系统。
5.2.1 招聘信息发布
为了能够让网站管理员动态的发布招聘信息,需要在数据库中创建对应的招聘广告模型从而可以使用后台管理系统进行招聘信息的发布、查看、管理。下面首先在contactApp中创建招聘广告模型Ad。打开contactApp中的models.py文件,添加模型:
from django.db import models
from django.utils import timezone
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', )
该Ad模型包含三个字段,分别表示“招聘岗位”、“岗位要求”、“发布时间”。添加完上述模型以后进行数据迁移,终端中依次输入下述命令完成数据库同步:
python manage.py makemigrations
python manage.py migrate
最后编辑admin.py文件,将模型注册到后台管理系统admin中:
from django.contrib import admin
from .models import Ad
admin.site.register(Ad)
然后打开后台管理系统,在招聘模块中添加几条招聘记录,如下图所示:
接下来修改视图处理函数,在每次请求“加入恒达”页面时后台系统从数据库中按照时间从近到远的顺序取出所有招聘信息,传入到模板文件recruit.html中,并由前端进行渲染。重新编辑views.py文件中的recruit函数:
from .models import Ad
def recruit(request):
AdList = Ad.objects.all().order_by('-publishDate')
return render(request, 'recruit.html', {
'active_menu': 'contact',
'sub_menu': 'recruit',
'AdList': AdList
})
最后,对前端页面进行设计并将招聘信息逐条显示。我们采用Bootstrap提供的“手风琴”面板组件来展现招聘列表信息,该组件的样式类为class="panel-group"。该组件可以看作是面板的一个容器,可以放置多个面板,每个面板用于展示一条信息并且每个面板均可以折叠。下面给出核心代码:
<div class="model-details">
<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}}">
<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>
</div>
值得注意的是,面板的折叠效果需要对每一个面板的id进行绑定。为了区分各个面板的id号,需要通过传入招聘信息id号来动态生成每个面板的id。另外,上述代码使用了模板标签语言中的条件判断语句:
{% if forloop.first %}
...执行1
{% else %}
...执行2
{% endif %}
如果当前循环执行到第一次,则执行条件1;否则执行条件2。通过上述逻辑判断,可以区分显示第一条和其余的招聘信息(第一条招聘信息默认为打开,其余默认为折叠)。同样的,为了美化列表显示效果,需要对其css样式进行设置。在contact.css文件中添加对应的样式代码:
/* 手风琴折叠面板样式 */
#accordion a:hover,a:focus{
text-decoration: none;
outline: none;
}
#accordion{
padding-right: 24px;
padding-left: 24px;
z-index: 1;
}
#accordion .panel{
border: none;
box-shadow: none;
}
#accordion .panel-heading{
padding: 0;
border-radius: 0;
border: none;
}
#accordion .panel-title{
padding: 0;
}
#accordion .panel-title a{
display: block;
font-size: 16px;
font-weight: bold;
background: #005197;
color: #fff;
padding: 15px 25px;
position: relative;
margin-left: -24px;
transition: all 0.3s ease 0s;
}
#accordion .panel-title a.collapsed{
background: #2e516d;
color: #fff;
margin-left: 0;
transition: all 0.3s ease 0s;
}
#accordion .panel-title a:before{
content: "";
border-left: 24px solid #005197;
border-top: 24px solid transparent;
border-bottom: 24px solid transparent;
position: absolute;
top: 0;
right: -24px;
transition: all 0.3s ease 0s;
}
#accordion .panel-title a.collapsed:before{
border-left-color: #2e516d;
}
#accordion .panel-title a:after{
content: "\e133";
font-family: 'Glyphicons Halflings';
position: absolute;
top: 30%;
right: 15px;
font-size: 18px;
color: #fff;
}
#accordion .panel-title a.collapsed:after{
content: "\e134";
color: #fff;
}
#accordion .panel-collapse{
position: relative;
}
#accordion .panel-collapse.in:before{
content: "";
border-right: 24px solid rgb(146, 143, 143);
border-bottom: 18px solid transparent;
position: absolute;
top: 0;
left: -24px;
}
#accordion .panel-body{
font-size: 14px;
color: #333;
background: #e4e4e4;
border-top: none;
z-index: 1;
}
最终效果如下图所示:
5.2.2 基于模型表单的应聘信息上传
本小节重点介绍Django中表单的使用方法。表单本身也是HTML的一种组件,表单可以将用户的输入信息规整、打包发送至后端服务器,后端服务器可以通过request.GET.get函数将这些信息逐个提取,验证通过后再封装成一条数据存入数据库。这种逐个提取信息的方式在实际使用时并不是很方便,需要在用户和后台数据库之间额外增加一个解析步骤,并且对于用户上传的字段不能自动进行错误检查(例如有些字段不能为空、有些字段只能是数字等)。那么是否有一种框架,可以直接完成中间的表单数据解析、验证和存储步骤,使得用户仿佛在直接操作后端数据库?答案是可以的。Django有一个内置的表单框架允许通过简单的方式来管理这一系列表单操作。在这个表单框架下面,可以根据数据模型定义对应的表单字段,并且指明每个字段采用何种验证方式。值得一提的是,Django表单还提供了一种灵活的方式来渲染表单以及操作数据。
Django提供了两种表单的基本类:
- Form:标准表单。可以对输入数据作验证,但没有与数据库模型关联;
- ModelForm:模型表单。可以对输入数据作验证,同时与数据库模型进行了关联,可以直接通过模型表单存储和修改数据库数据;
为了方便开发,本节重点阐述模型表单的使用方法。具体分为3个步骤:
(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='邮箱')
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',
verbose_name='个人照片')
grade_list = (
(1, '未审'),
(2, '通过'),
(3, '未通过'),
)
status = models.IntegerField(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')
上述模型中多数字段采用了文本CharField,该类型字段在前面章节中已经重点介绍过其使用方法。下面对其余字段作一些说明:
- sex:性别。尽管该字段依然使用了文本CharField,但是参照图8-2所示效果可以发现该字段在前端最终呈现形式为下拉菜单形式,因此必然需要进行转化显示。由于本章采用Django的模型表单来接收用户输入信息,因此其对应的显示方式将在表单部分来进行控制,实际在数据库中依然以文本形式存储该字段;
- email:邮箱。Django拥有专门针对邮箱字段的管理机制,与之对应的也拥有对应的模型表单字段用于邮箱格式的验证,这里只需要使用Django提供的EmailField字段即可;
- birth:出生日期。该字段使用了DateField,相比于DateTimeField,DateField只包含年、月、日信息。为了统一输出格式,这里对default属性指明的字符串进行了格式设置,采用datetime.strftime函数将当日时间转化为类似“1989-04-12”这种格式;
- experience:学习或工作经历。因为该字段内容较多,因此采用TextField长文本来进行声明;
- photo:个人照片。该字段采用ImageField进行声明,通过参数upload_to将用户上传的图像上传至/media/contact/recruit/%Y_%m_%d文件夹下。此处的%Y_%m_%d指用户上传照片的日期,采用这种方式可以按照日期将每日用户上传的照片存放于同一个文件夹内,方便管理员查看和管理;
- status:面试成绩。该字段采用IntegerField进行声明,由管理员通过后台管理系统进行操作,用来指明当前简历所处状态,其参数choices指向grade_list,在后台管理系统中以单选下拉菜单形式呈现;
除了上述字段以外,需要额外注意元信息类中的排序属性,该属性采用字段联合的方式进行排序,未审核的简历排在最前端,然后是通过的简历,排在最后的是未通过的简历。各个分类中再以时间顺序进行二级排序。创建完模型以后在终端中输入下述命令完成数据库同步:
python manage.py makemigrations
python manage.py migrate
本实例用户通过表单提交的数据较多,如果手动对用户上传的每个字段逐个进行检查并提取数据,这无疑降低了开发效率并且增加了大量功能重复的冗余代码。而采用模型表单则可以作为中间转换器,直接衔接前端用户输入的数据同时与后端数据库相连,大量的验证和数据提取工作可以直接由模型表单完成。从某种意义上来看,可以将模型表单看作是模型的一个格式定制化器,可以对模型中的每一个字段进行格式和验证方式的定制。
下面开始为前面创建的Resume模型建立对应的模型表单。在contactApp下新建一个Python文件,命名为forms.py,编辑代码如下:
from django import forms
from .models import Resume
class ResumeForm(forms.ModelForm):
class Meta:
model = Resume
fields = ('name', 'sex', 'personID', 'email', 'birth', 'edu', 'school',
'major', 'experience', 'position', 'photo')
sex_list = (
('男', '男'),
('女', '女'),
)
edu_list = (
('大专', '大专'),
('本科', '本科'),
('硕士', '硕士'),
('博士', '博士'),
('其它', '其它'),
)
widgets = {
'sex': forms.Select(choices=sex_list),
'edu': forms.Select(choices=edu_list),
'photo': forms.FileInput(),
}
- 首先从Django中导入forms类来使用表单组件。新建的模型表单类ResumeForm继承自forms.ModelForm,表明该表单是一个模型表单;
- 模型表单通过元信息类Meta来进行模型的定制化。model属性指向具体需要定制化的模型。fields属性用来指明需要定制化的具体字段;
- 由于模型中有两个字段是单选菜单形式展示,因此需要为这两个字段设置可选项sex_list和edu_list;
- 最后一个属性widgets用来控制各个表单字段在前端的具体展现形式,默认情况下模型中的CharField字段对应HTML中的输入文本框,其它字段的前端形式在widgets中进行设置;
- 两个单选下拉菜单sex和edu采用forms.Select来进行定制,对choices参数进行绑定即可完成可选项的设置;
- 图像字段采用文件输入形式forms.FileInput;
完成模型表单的构造以后,下面开始修改视图views.py中的recruit函数,具体如下:
from .models import Ad
from .forms import ResumeForm
def recruit(request):
AdList = Ad.objects.all().order_by('-publishDate')
if request.method == 'POST':
resumeForm = ResumeForm(data=request.POST, files=request.FILES)
if resumeForm.is_valid():
resumeForm.save()
return render(request, 'success.html', {
'active_menu': 'contactus',
'sub_menu': 'recruit',
})
else:
resumeForm = ResumeForm()
return render(
request, 'recruit.html', {
'active_menu': 'contactus',
'sub_menu': 'recruit',
'AdList': AdList,
'resumeForm': resumeForm,
})
- 在recruit函数中,当请求以POST形式提交时,通过带参数的表单类构造函数创建一个表单变量resumeForm,参数data用来从request.POST中获取对应的数据,files用来接收对应的文件(此处指用户上传的照片);
- 接下来采用is_valid()函数来验证表单各字段格式是否符合要求。如果符合则通过接下来的resumeForm.save()语句进行数据保存,并且返回成功页面给前端。如果当前并非处于提交状态,则通过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 %}实现。紧接着上一节中发布的招聘信息模块,下面开始在recruit.html中编写简历信息上传功能。详细代码如下:
<div class="panel panel-default">
<div class="panel-heading">
请填写个人简历
</div>
<div class="panel-body">
<div class="row">
<form action="." name="resumeForm" class="form-horizontal" method="post" role="form" enctype="multipart/form-data">
{% 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" style="width:120px" src="{% static 'img/sample.png' %}">
</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>
- 整体布局样式:采用Bootstrap提供的面板组件panel进行布局,样式采用默认样式panel-default,分为头部和主体两部分,头部显示表单主题,主体显示各个表单控件;
- 表单构造:基本形式如下:
<form action="." name="resumeForm" method="post" class="form-horizontal" role="form" enctype="multipart/form-data">
...各个表单控件...
</form>
上述表单中action参数用来指向表单提交的网址,此处"."表示当前网址。method表明提交形式,表单的提交方式一般为post。class="form-horizontal"表示当前表单里面的控件以行的形式进行排列。enctype="multipart/form-data"与post结合使用可以使得后台获取到request.FILES中用户上传的文件(图片)信息。下面解释如何通过Django表单对各个HTML表单元素进行渲染:
- 模型表单组件渲染:模型表单组件可以直接通过传入的模板表单变量resumeForm进行渲染,通过额外的过滤标签add_class来添加样式类,并且通过attr添加属性;
- 日期的使用和渲染:为了能够方便用户输入日期信息,本书采用额外的日期组件laydate.js来实现该功能(官方下载网址:https://www.layui.com/laydate/);将下载下来的laydate.js组件包放置在本项目static/js文件夹下(该组件包也可以从本书配套资源中获取),然后在recruit.html中进行引用:
<script src="{% static 'js/layDate-v5.0.9/laydate.js' %}"></script>
为了能够正常使用该日期组件功能,需要添加额外的js代码来调用:
<script>
laydate.render({
elem: '#id_birth'
});
</script>
这里调用laydate组件的render函数,其中elem用来指定需要绑定的组件,通过id号进行绑定。需要特别指出的是由于采用了模型表单,在组件渲染的过程中会自动的为每个组件生成id号,其生成形式为模型字段名前加上“id_”。
- 照片上传:为了在用户选择照片后能够显示照片缩略图,需要添加额外的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提供的消息提示框组件alert来实现,完整代码如下:
{% extends "base.html" %}
{% load static %}
{% block title %}
人才招聘
{% endblock %}
{% block content %}
<!-- 主体内容 -->
<div class="container">
<div class="row">
<div class="alert alert-success">
<strong>成功!</strong> 简历信息已成功上传,初试结果会以邮件方式发给您。
</div>
</div>
</div>
{% endblock %}
至此,已完成用户简历信息上传模块的开发。在admin.py文件中导出该模型到后台管理系统,添加代码如下:
from .models import Resume
admin.site.register(Resume)
启动项目,在加入恒达页面上输入完整的简历信息并提交,然后打开后台管理系统,管理员可以查看用户上传的简历信息。最终效果如下图所示:
这里需要注意的是后台管理系统中用户上传的照片是以路径字符串url形式提供的,并不是以缩略图形式显示,这对于管理员来说显得不直观,需要根据路径去本地寻找和查看图片。
下面介绍一种定制化后台管理系统展示列表的方法。具体的,模型的展示列表形式可以通过admin文件进行设置。在admin.py文件中对于模型Resume的注册是通过admin.site.register(Resume)来实现,即采用了默认的注册方式,并没有对模型字段的展示形式进行设置。因此,为了能够有效的浏览用户上传的简历信息,需要对各字段进行定制化设置。代码如下:
from django.utils.safestring import mark_safe
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)
image_data.short_description = u'个人照片'
admin.site.register(Resume, ResumeAdmin)
- 转义:首先引入make_safe转义函数用来对HTML语言关键字进行转义,将HTML代码转换为HTML实体;
- 模型管理器:为了能够定制化模型的输出,可以采用Django提供的模型管理器进行模型定制输出。模型管理器通过继承类admin.ModelAdmin来创建,其中list_display属性列出了需要在后台管理系统中展示的模型字段,此处并没有列出photo字段,而是采用了另一个变量image_data。通过函数image_data将photo的url赋值到HTML中,并通过转义字符进行转化,从而使得最终渲染的image_data能以缩略图形式显示;
- 绑定模型和模型管理器:通过admin.site.register函数将模型与模型管理器绑定并且注册;
打开后台管理系统,最终展现形式如下图所示:
5.2.3 信号触发器的使用
在上一节中,招聘人员可以通过企业门户网站的后台管理系统查看应聘者上传的简历信息。当招聘人员浏览完某一份简历并决定录用后,招聘人员可以通过修改status字段将其改为“通过”。后台管理系统在执行字段修改的同时需要做一些额外的处理操作,比如发送录用通知邮件给应聘者。这些操作均需要捕捉并判断招聘人员动作,即只有当招聘人员将简历中的status字段从“未审”改为“通过”或者从“未审”改为“未通过”时需要触发额外操作。本小节阐述如何利用Django的信号机制实现这种触发效果。
Django信号触发器的功能类似于回调函数,用于为项目增加事件的监听与触发机制。其中,灵活使用其内置的模型信号就可以监控大部分模型对象的变化,并且不需要修改原模型的代码。下面根据实际需求进行开发。
编辑models.py文件,添加代码如下
from django.db.models.signals import post_init,post_save
from django.dispatch import receiver
@receiver(post_init, sender=Resume)
def before_save_resume(sender, instance, **kwargs):
instance.__original_status = instance.status
@receiver(post_save, sender=Resume)
def post_save_resume(sender, instance, **kwargs):
print(instance.__original_status)
print(instance.status)
- 首先引入Django自带的模型信号post_init和post_save,其中post_init表示在管理员单击保存按钮前触发信号,post_save表示在单击保存按钮后触发信号;
- 信号的接收采用装饰器@receiver来实现,其中第一个参数是信号类型,第二个参数是需要监控的模型类;
- 对应两个不同的信号,分别采用两个不同的函数进行响应,其中instance参数即为传入的模型变量。在保存前,将当前状态记录在__original_status变量中,保存后输出前后变量的值来查看变化;
当管理员将简历中的status从“未审”改为“通过”时,此时可以查看控制台输出。控制台分别输出“1”和“2”,如下图所示:
说明管理员对status的状态更改可以被有效的监控到。这里我们暂时使用通过控制台输出状态的形式来查看触发器效果,具体触发内容将在下面进行介绍。
5.3 发送邮件
通过前面的开发学习,已经搭建完一个简易的招聘与应聘互动模块。这里梳理一下基本流程:
(1)招聘人员在后台发布招聘信息并且通过数据库显示在前端;
(2)应聘人员浏览招聘信息并且填写个人简历进行应聘,应聘信息以模型表单的形式直接与数据库进行关联,并且自动对每个字段进行格式检查;
(3)招聘人员通过后台管理系统浏览简历信息,在进行状态更改的时候触动触发器进行额外的操作;
对于步骤(3),上一节仅实现基本的控制台打印功能,即当招聘人员修改简历状态时在后台输出关键信息,实际情况下我们希望无论是招聘人员将简历状态statue从“未审”改为“通过”还是从“未审”改为“未通过”,都能够在修改后自动的将初试结果发送至应聘者邮箱,无需招聘人员再额外的进行邮件发送。为了实现这个自动发送邮件的功能,需要在Django中使用对应的邮箱功能。本节将详细阐述如何通过Django发送邮件。
为了能够使用Django的邮箱服务,需要预先设置好邮箱服务器信息。本质上来说通过Django搭建的Web应用只是一个外壳,只负责邮件内容的编辑,而实际的邮件发送等功能还是依托第三方邮箱服务器(例如QQ邮箱或新浪邮箱)来进行。下面以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 = 'xxxxxxxxxx' # 授权码
EMAIL_USE_TLS = True
将企业QQ邮箱账号和对应的授权码填入其中。接下来重新编辑models.py文件中的post_save_resume函数,当保存修改时检查当前状态变化,然后按条件发送指定的邮件内容,具体代码如下:
from django.core.mail import send_mail
@receiver(post_save, sender=Resume)
def post_save_resume(sender, instance, **kwargs):
email = instance.email # 应聘者邮箱
EMAIL_FROM = 'xxxxxxxx@qq.com' # 企业QQ邮箱
if instance.__original_status==1 and instance.status==2:
email_title = '通知:恒达科技招聘初试结果'
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])
在填写邮件内容时,email_title表示邮件标题,email_body为邮件主体内容,最后通过django.core.mail模块中的send_mail函数完成邮件的发送。
保存所有修改后运行项目,通过后台管理系统来修改应聘者简历的状态信息,查看邮件发送情况。
如下图所示,可以看到应聘者邮箱成功收到了邮件:
本节主要阐述Django中使用QQ邮箱的基本方法,其它邮箱服务器的使用请参照各个邮箱提供商提供的接口来执行。
5.4 资料下载
本节开发企业门户网站服务支持的资料下载模块,对应hengDaProject项目中的serviceApp应用。服务支持模块包含两个子页面:资料下载和人工智能开放平台。资料下载页面主要是为了进一步完善企业门户网站的实际需求而设计,用户在购买产品后往往会需要额外的下载各个产品对应的说明手册或SDK驱动包等,这时候就需要在官网提供相应的资料下载功能以方便用户获取。这一部分内容在进行开发时可以沿用新闻动态模块的设计思路,以列表形式展现每一条下载信息并且提供额外的下载链接,具体参考示例网站界面。通过该页面的开发,重点需要掌握Django中实现文件下载的方法。人工智能开放平台属于本课程重点扩展内容,我们将在最后一节课介绍。
5.4.1 创建资料模型
为了能够让管理员上传并且管理资料文件,可以对资料进行建模,在数据库中创建对应的资料类Doc。打开serviceApp应用中的models.py文件,定义资料模型如下:
from django.db import models
import django.utils.timezone as timezone
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包含三个字段:title、file和publishDate,其中file字段用于接收管理员上传的资料文件,通过参数upload_to的设置使得上传的文件统一放置在media/Service文件夹下面。
完成上述创建后使用迁移命令将Doc模型同步到数据库中:
python manage.py makemigrations
python manage.py migrate
接下来打开admin.py文件,将Doc模型注册到后台管理系统中:
from django.contrib import admin
from .models import Doc
admin.site.register(Doc)
保存所有修改后启动项目,登录后台管理系统,找到资料模型,为其添加几条数据,每条数据输入文件名并且上传对应的文件资料,如下图所示。注意,上传的文件资料路径中不能含有中文。
5.4.2 资料下载列表页面开发
资料下载列表页面的开发可以借鉴新闻列表的开发方法。首先在serviceApp应用下创建templates模板文件夹,然后在该templates文件夹下新建模板文件docList.html。将前面课程中创建的newList.html全部内容复制到docList.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 %}
模板文件中传入模板变量docList用来显示每项资料,通过模板标签{% for doc in docList %}逐条迭代显示。每一项资料的下载链接以“资料访问路由+文件id”的形式给出,资料访问路由{% url 'serviceApp:getDoc' %}将在urls.py文件中进行设置。
接下来修改urls.py文件,添加getDoc路由,具体如下:
urlpatterns = [
...其它路由...
path('getDoc/<int:id>/', views.getDoc, name='getDoc'),
]
然后打开views.py文件,修改download函数,该函数主要用来获取资料列表并进行页面渲染,具体内容与newsApp应用下views.py文件中的news函数基本一致,实际操作时只需要将news函数中的内容复制过来并且简单修改即可,具体内容不再重复阐述,请参考配套资源代码。
本节重点阐述如何编写资料下载函数getDoc。在第2课构建企业门户网站框架时通过HttpResponse返回字符串来响应用户请求,后面几课则采用了render函数来渲染页面并返回。无论是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:
while True:
c = fp.read(size)
if c:
yield c
else:
break
其中file_name参数为文件路径,size参数表示分批读取文件的大小。接下来开始编写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)
update_to, filename = str(doc.file).split('/')
filepath = '%s/media/%s/%s' % (os.getcwd(), update_to, filename)
response = StreamingHttpResponse(read_file(filepath, 512))
response['Content-Type'] = 'application/octet-stream'
response['Content-Disposition'] = 'attachment;filename="{}"'.format(
filename)
return response
- 获取文件:根据传入的文件id号通过get_object_or_404函数将文件从数据库中提取出来;
- 读取文件:通过read_file函数读取文件,并以512个字节为单位构造迭代器,该迭代器返回后直接传给StreamingHttpResponse;
- 设置文件类型:上述代码可以将服务器上的文件通过文件流传输到浏览器,但文件流通常会以乱码形式显示到浏览器中,而非下载到硬盘上,因此,还要对文件作一些申明,让文件流写入硬盘。实现时只需要给StreamingHttpResponse对象的Content-Type和Content-Disposition字段赋值即可;
保存所有修改后启动项目,打开“资料下载”页面,单击其中一条资料链接进行文件下载,效果图如下所示:
到这里,本节课程内容已结束。
本节课完整代码和数据下载地址如下:
百度网盘:https://pan.baidu.com/s/1Fj0Ne6ddgMhrYwafvsqqEA
提取码:zbur
课程介绍链接:Python Web企业门户网站—系列博客教程介绍_冰海的博客-CSDN博客_python web开发从入门到实战