08 文档下载
一、功能分析
- 文档下载页面
- 文档列表
- 文档下载
二、模型设计
1、字段分析
- 文件url
- 文件名
- 文件标题
- 简介
- 封面url
2、模型定义
from django.db import models
from utils.models.models import BaseModel
class Doc(BaseModel):
'''
文件模型
'''
file_url = models.URLField('文件url', help_text='文件url')
file_name = models.CharField('文件名', max_length=48, help_text='文件名')
title = models.CharField('文件标题', max_length=150, help_text='文件标题')
desc = models.TextField('文件描述', help_text='文件描述')
image_url = models.URLField('封面图片url', help_text='封面图片url')
author = models.ForeignKey('user.User', on_delete=models.SET_NULL, null=True)
class Meta:
db_table = 'tb_docs' # 数据库表名
verbose_name = '文件表' # admin 站点中显示的名称
verbose_name_plural = verbose_name
def __str__(self):
return self.title
三、文档列表
1、接口设计
- 接口说明:
类目 | 说明 |
---|---|
请求方法 | GET |
url定义 | /doc/docs/ |
参数格式 | 查询参数 |
- 参数说明:
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
page | 整数 | 否 | 页码 |
- 返回结果:
{
"errno": "0",
"errmsg": "OK",
"data": {
"total_page": 2,
"docs": [
{
"desc": "youkou老师说:每天一个单词,充实每一天!",
"file_name": "django项目班_英语单词2.doc",
"file_url": "/media/django项目班_英语单词2.doc",
"image_url": "/media/django项目班_英语单词.jpg",
"title": "django项目班_英语单词2"
},
{
"desc": "本书由奋战在Python开发一线近20年的Luciano Ramalho执笔,从语言设计层面剖析编程细节,兼顾Python 3和Python 2,教你写出风格地道的Python代码。",
"file_name": "流畅的Python.pdf",
"file_url": "/media/流畅的Python.pdf",
"image_url": "/media/fluent_python_1.jpg",
"title": "流畅的Python"
}
]
}
}
2、后端代码
class DocListView(View):
def get(self, request):
# 1.拿到所有文档
docs = Doc.objects.values('file_url', 'file_name', 'title', 'desc', 'image_url').filter(is_delete=False).all()
# 2.分页
paginator = Paginator(docs, constants.PER_PAGE_DOC_COUNT)
try:
page = paginator.get_page(int(request.GET.get('page')))
except Exception as e:
page = paginator.get_page(1)
data = {
'total_page': paginator.num_pages,
'docs': list(page)
}
return json_response(data=data)
路由
path('doc/docs/', views.DocListView.as_view(), name='doc_list'),
四、文档下载页面
1、后端代码
def index(request):
return render(request, 'doc/docDownload.html')
路由
path('download/', views.index, name='index'),
2、前端html
{% extends 'base/base.html' %}
{% load static %}
{% block title %}下载文档{% endblock title %}
{% block link %}
<link rel="stylesheet" href="{% static 'css/doc/docDownload.css' %}">
{% endblock link %}
{% block main_contain %}
<div class="main-contain ">
<div class="banner">
<img src="https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1802845035,3786939119&fm=26&gp=0.jpg"
alt="">
</div>
<div class="pay-doc-contain">
<ul class="pay-list">
</ul>
</div>
</div>
{% endblock main_contain %}
{% block otherjs %}
<script src="{% static 'js/doc/doc.js' %}"></script>
{% endblock otherjs %}
3、前端js
<a href="" class="pay-price" download="">下载</a>
以上含有download属性可直接下载
// 创建static/js/doc/doc.js文件,代码如下
$(() => {
let iPage = 1; // 当前页面页数
let iTotalPage = 1; // 总页数
let bIsLoadData =false; // 是否正在加载
fn_load_docs(); // 加载文件列表
// 页面滚动加载
$(window).scroll(function () {
// 浏览器窗口高度
let showHeigtht = $(window).height();
// 整个网页高度
let pageHeight = $(document).height();
//页面可以滚动的距离
let canScrollHeight = pageHeight - showHeigtht;
// 页面滚动了多少, 整个是随着页面滚动实时变化的
let nowScroll = $(document).scrollTop();
if ((canScrollHeight - nowScroll) < 100){
if(!bIsLoadData){
bIsLoadData = true;
//判断页数,去更新新闻,小于总数才加载
if(iPage < iTotalPage){
iPage += 1;
fn_load_docs();
}else {
message.showInfo('已全部加载,没有更多内容!');
$('a.btn-more').html('已全部加载,没有更多内容!')
}
}
}
});
// 获取docs信息
function fn_load_docs() {
$
.ajax({
url: '/doc/docs/',
type: 'GET',
data: {page: iPage},
dataType: 'json'
})
.done((res) => {
if (res.errno === '0') {
iTotalPage = res.data.total_page;
res.data.docs.forEach((doc) => {
let content = `<li class="pay-item">
<div class="pay-img doc"></div>
<img src="${ doc.image_url }" alt="" class="pay-img doc">
<div class="d-contain">
<p class="doc-title">${ doc.title }</p>
<p class="doc-desc">${ doc.desc }</p>
<!-- /www/?xxx -->
<a href="${ doc.file_url }" class="pay-price" download="${ doc.file_name }">下载</a>
</div>
</li>`;
$('.pay-list').append(content);
bIsLoadData = false;
$('a.btn-more').html('滚动加载更多');
})
} else {
message.showError(res.errmsg)
}
})
.fail(() => {
message.showError('服务器超时,请重试!')
})
}
});
五、通用下载视图
1、接口设计
接口说明:
类目 | 说明 |
---|---|
请求方法 | GET |
url定义 | /doc/download/ |
参数格式 | 查询参数 |
参数说明:
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
doc_id | 整数 | 是 | 文档id |
返回结果:
文件
2、后端代码
# 文件下载视图
class DocDownload(View):
"""
通用文件下载视图
"""
def get(self, request):
doc_id = request.GET.get('doc_id')
doc = Doc.objects.filter(id=doc_id).first()
if not doc:
raise Http404("无此文件!")
file_name = doc.file_name
file_url = doc.file_url
file_path = settings.BASE_DIR + file_url
file_fb = open(file_path, 'rb')
try:
res = FileResponse(file_fb)
except Exception as e:
logger.info("获取文档内容出现异常:\n{}".format(e))
raise Http404("文档下载异常!")
# 文件流
# res['content_type'] = "application/octet-stream"
ex_name = file_name.split('.')[-1] # 文件后缀,表明文件类型
# https://stackoverflow.com/questions/23714383/what-are-all-the-possible-values-for-http-content-type-header
# http://www.iana.org/assignments/media-types/media-types.xhtml#image
if not ex_name:
raise Http404("文档url异常!")
else:
ex_name = ex_name.lower()
if ex_name == "pdf":
res["Content-type"] = "application/pdf"
elif ex_name == "zip":
res["Content-type"] = "application/zip"
elif ex_name == "doc":
res["Content-type"] = "application/msword"
elif ex_name == "xls":
res["Content-type"] = "application/vnd.ms-excel"
elif ex_name == "docx":
res["Content-type"] = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
elif ex_name == "ppt":
res["Content-type"] = "application/vnd.ms-powerpoint"
elif ex_name == "pptx":
res["Content-type"] = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
else:
raise Http404("文档格式不正确!")
doc_filename = escape_uri_path(file_name.split('/')[-1])
res["Content-Disposition"] = "attachment; filename*=UTF-8''{}".format(doc_filename)
return res
路由
path('doc/download/', views.DocDownload.as_view()),