1. 分页功能
在web页面有大量数据要显示时,分页可以使阅读更为清晰,django的分页功能依赖于Paginator类实现,该类位于包django.core.paginator中。
Paginator对象负责分页数据整体的管理:
- Paginator类构造方法:
paginator = Paginator(object_list, per_page)
两个参数object_list:需要分页数据的对象列表;per_page:每页的数据个数
返回值:Paginator对象 - Paginator类的属性:
属性 说明 count 需要分页的对象总数 num_pages 分页后的页面总数 page_range 从1开始的range对象,记录当前的页码数 per_page 每页数据的个数 - Paginator对象的方法:
方法 说明 paginator.page(number) number为页码信息,返回当前number页的页信息,若页码不存在,则抛出InvalidPage异常 paginator.get_page(number) 返回一个给定的基于 1 索引的 Page 对象,若页数不是数字,它返回第一页。若页码为负数或大于页数,则返回最后一页。
上面提到Paginator对象负责分页数据整体的管理,那么Page对象则负责某一页数据的管理:
- 创建Page对象,使用Paginator对象的page()方法返回Page对象
page = paginator.page(页码) - Page对象属性
属性 说明 object_list 当前页上的所有数据对象 number 当前页的序号,从1开始 paginator 当前Page对象相关的Paginator对象 - Page对象方法
方法 说明 has_next() 如果有下一页,返回True has_previous() 如果有上一页,返回True has_other_pages() 如果有上一页或下一页,返回True next_page_number() 返回下一页的页码,若不存在,抛出InvalidPage异常 previous_page_number() 返回上一页的页码,若不存在,抛出InvalidPage异常
根据上面的知识,给出一个例子说明两个对象及其方法的使用方法
1.1 视图函数:
def test_page(request):
# 以带参数的方式定义url test_page?page=1
page_num = request.GET.get('page', 1)
# 模拟模型层从数据库取出的对象数据
all_data = ['这', '是', '一', '个', '页', '面']
# 初始化paginator,每页显示一个对象
paginator = Paginator(all_data, 1)
# 创建Page对象
c_page = paginator.page(int(page_num))
return render(request, 'test_page.html', locals())
1.2 模板层:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>分页</title>
</head>
<body>
{% for foo in c_page %}
<p>
{{ foo }}
</p>
{% endfor %}
{% if c_page.has_previous %}
<a href="/test_page?page={{ c_page.previous_page_number }}">上一页</a>
{% else %}
上一页
{% endif %}
{% for p_num in paginator.page_range %}
{% if p_num == c_page.number %}
{{ p_num }}
{% else %}
<a href="/test_page?page={{ p_num }}">{{ p_num }}</a>
{% endif %}
{% endfor %}
{% if c_page.has_next %}
<a href="/test_page?page={{ c_page.next_page_number }}">下一页</a>
{% else %}
下一页
{% endif %}
</body>
</html>
效果:
2. 文件上传
2.1 前端设计
实现文件上传的功能,在设计模板层时,有以下几点要求:
- 表单<form>文件上传时必须带有enctype="multipart/form-data"时,才会包含文件内容数据
- 文件上传必须为POST提交方式
- 表单中使用<input type=‘file’>标签上传文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<!--注意加:enctype="multipart/form-data"-->
<form action="/test_upload" method="post" enctype="multipart/form-data">
<!--csrf检查-->
{% csrf_token %}
<p>
<input type="text" name="title">
</p>
<p>
<input type="file" name="file_1">
</p>
<p>
<input type="submit" value="上传">
</p>
</form>
</body>
</html>
2.2 后端逻辑设计:
视图函数
不能用request.POST[‘xxx’]取得相应内容,应为file = request.FILES[‘xxx’],其中FILES的key对应页面中file框的name值,file绑定文件流的对象,可使用file.name取得文件名,使用file.file取得文件的字节流数据。
配置文件存储路径
django将用户上传的文件统称为media资源,首先需要在settings.py中设置MEDIA配置
# 值可更改,自定义
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL和MEDIA_ROOT还需要手动绑定:
在主路由中添加绑定路由,在主路由的urls.py中添加以下配置
# 配置media
from django.conf.urls.static import static
# 加载配置文件
from django.conf import settings
# 通过setting方法生成一个静态路由追加到urlpatterns中
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
以上的操作相当于配置了MEDIA_URL开头的路由,django接到该特征请求后去MEDIA_ROOT路径查找资源
文件写入方案
文件写入方式有两种,一种为Python中常见的open方式,这种方式步骤较为繁琐,且需要手动解决文件重名等问题,完整的视图函数处理逻辑如下:
def test_upload(request):
if request.method == "GET":
return render(request, 'test_file.html')
elif request.method == 'POST':
upload_file = request.FILES['file_1']
print('上传的文件名为:', upload_file.name)
# 拼接获得完整文件名
filename = os.path.join(settings.MEDIA_ROOT, upload_file.name)
# 开始写入文件
with open(filename, 'wb') as f:
data = upload_file.file.read()
f.write(data)
return HttpResponse('接收文件:' + upload_file.name + '成功')
第二种方式需要借助ORM框架,使用数据表的一个字段来存储,字段类型设置为FileField(upload=‘子目录名’),具体步骤为:
- 先创建一个应用,在其model.py中创建一个模型类来存储上传文件,注意的是创建文件字段时设置upload_to属性,指定要存储的子目录,这里设置为picture,则最后上传文件的存储位置就是media/picture目录,由此可知,借助ORM框架的文件上传并不是指文件存储在数据库中,模型类的FileField字段实际存储的是文件的路径。
class Content(models.Model):
title = models.CharField('文章名字', max_length=11)
# 指明子目录名字,在此示例中,上传文件存储到picture文件夹下
picture = models.FileField(upload_to='picture')
- 接收文件后,直接调用模型类的objects.create()方法创建记录即可
第二种方式的视图函数如下:
def test_upload(request):
if request.method == "GET":
return render(request, 'test_file.html')
elif request.method == 'POST':
title = request.POST['title']
myfile = request.FILES['file_1']
# 创建模型类对象
Content.objects.create(title=title, picture=myfile)
return HttpResponse('--文件上传成功--')
2.3 效果
选择文件页面:
上传成功:
数据库存储文件
服务器端存储图片:
3. 邮件发送
3.1 邮箱相关的协议
邮件的常用的三个协议:SMTP、IMAP和POP3,这三个协议在本科时期计算机网络课程中已有接触,它们均属于应用层协议。
SMTP用于发送邮件,属于推送协议。
IMAP和POP3则用于邮件拉取协议,二者区别主要有以下几点:
- IMAP具备摘要浏览的功能,可预览部分摘要再下载整个邮件,而POP3必须下载全部邮件,没有摘要功能
- IMAP为双向协议,客户端可向服务器端发出反馈,而POP3为单向协议,客户端操作无法同步服务器
三个协议协同配合完成邮件发送的过程,例如,用户A使用QQ邮箱向用户B的新浪邮箱中发送邮件,过程如下:
首先是用户A使用邮件客户端将邮件通过SMTP协议发送到QQ邮箱服务器,QQ邮箱服务器再次使用SMTP协议将邮件转发到新浪邮箱服务器,而用户B使用邮件客户端,可选择自主通过IMAP协议或者POP3从新浪邮箱服务器中接收邮件。整个过程中,django实际起到的作用就是充当邮件客户端的身份,完成转发或接收邮件的功能。
这样一来,配置django可以使其承担邮件客户端的角色就成为了问题的关键。
3.2 django发送邮件
要配置邮件发送,使用的协议为SMTP,要给django授权一个邮箱,使用该邮箱给对应的收件人发送邮件,django.core.mail中封装了SMTP协议,使用步骤如下:
- 给django授权一个邮箱(以QQ邮箱为例),具体步骤自行百度
- 配置django,在settings.py中进行相关的配置
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # 固定写法
EMAIL_HOST = 'smtp.qq.com' # SMTP地址
EMAIL_PORT = 25 # SMTP端口
EMAIL_HOST_USER = 'xxxxxxxxxx@qq.com' # 发送邮件的邮箱
EMAIL_HOST_PASSWORD = 'xxxxxxxxxx' # 授权码
EMAIL_SUBJECT_PREFIX = '[邮件测试] ' # 为邮件Subject-line前缀,默认是'[django]'
EMAIL_USE_TLS = False # 与SMTP服务器通信时,是否启动TLS链接(安全链接)默认false
- 调用函数发送邮件
from django.core import mail
mail.send_mail(
# 邮件标题
subject='test1',
# 邮件内容
message='一个测试邮件',
# 发送者(当前配置邮箱)
from_email='xxxxxx@qq.com',
# 接收者邮件列表
recipient_list=['xxxxxxx@qq.com']
)
使用django shell进行测试发送,可以正常接收到发送的邮件
更多的邮件配置可参考django官方文档:发送邮件
3.3 邮件报警练习
使用中间件抓取视图函数的异常,以邮件的形式将异常信息发送给指定的联系人,要求如下:
- 邮件主题:异常警告
- 内容:必须带有异常信息,其他自定义
- 收件人:自定义
实现方法:需要利用django的中间件处理
中间件介绍:
中间件是django请求/响应处理中的一个框架,是一个“插件”系统,用于对django输入/输出做全局改变,中间件以类的形式体现,每个中间件组件负责做一些特定的功能。
中间件类必须继承自django.utils.deprecation.MiddlewareMixin类,中间件类必须继承下列五个方法中的一个或多个,其中最常用的是前四个:
- process_request(self, request)
执行路由之前被调用,返回None或者HttpResponse对象 - process_view(self, request, callback, callback_args, callback_kwargs)
调用视图之前被调用,在每个请求上调用,返回None或者HttpResponse对象 - process_response(self, request, response)
所有响应返回浏览器时要调用,在每个请求上调用,返回一个HttpResponse对象 - process_exception(self, request, exception)
当处理过程中抛出异常时调用,返回一个HttpResponse对象 - process_template_response(self, request, response)
在视图函数执行完毕且视图返回对象中有render()方法时被调用,该方法必须返回一个实现了render方法的响应对象
中间件中的大多数方法在返回None时表示忽略当前操作,继续进入下一项事件,当返回HttpResponse对象时表示此次请求结束,直接返回客户端响应。
中间件的编写
一般在项目目录同级下建立一个目录middleware来存放中间件代码,在文件内需要引入django.utils.deprecation.MiddlewareMixin类,中间件类必须继承MiddlewareMixin类,下面给出一个中间件类的示例:
from django.http import HttpResponse
from django.utils.deprecation import MiddlewareMixin
class MyMW(MiddlewareMixin):
def process_request(self, request):
print('MyMW process_request')
def process_view(self, request, callback, callback_args, callback_kwargs):
print('MyMW process_view')
def process_response(self, request, response):
print("MyMW process_response")
return response
中间件类编写完成后,需要在项目的settings.py中注册自定义的中间件,即在MIDDLEWARE数组中加入新建中间件类的路径
自定义的中间件类只有配置后才会起作用,中间件被调用时按照向上到下再由下至上的顺序,具体调用过程在Django请求处理流程一图中有详细的响应顺序
3.3 邮件报警练习的解决
要求在视图函数出现异常时发送邮件,显然可以采用中间件方法process_exception(self, request, exception)来捕获异常,编写中间件类并在settings.py中注册
class ExceptionMW(MiddlewareMixin):
def process_exception(self, request, exception):
import traceback
print(exception)
mail.send_mail(
subject='网站出现异常......',
# 调用traceback的方法返回异常信息
message=traceback.format_exc(),
from_email='xxxxxx@qq.com',
recipient_list=['xxxxxxxx@qq.com']
)
return HttpResponse('---网页异常----')
测试时任意编写一个有异常的视图函数,如
def exception_view(request):
Except
return HttpResponse('提供异常')
将视图函数路径配置完毕,访问发出此请求后一定会出现异常,异常会被中间件方法process_exception捕获,在方法内发送异常的详细信息到指定的邮箱中,并且从process_exception方法中直接返回HttpResponse显示网页异常,结束请求过程。
邮件内容: