文档下载功能
I. 功能需求分析
1>功能
- 文档下载展示页,展示整体的网页框架
- 文档列表,展示可下载的文档,包括标题、封面、简介等
- 文档下载,点击
下载
?即可下载文档
II. 模型设计
1>字段分析
针对文档的下载需要满足以下字段:
- 文件url
- 文件名
- 文件标题
- 简介
- 封面图片url
2>模型定义
在doc/models.py中定义如下模型
from django.db import models
下载
?即可下载文档针对文档的下载需要满足以下字段:
在doc/models.py中定义如下模型
from django.db import models
from utils.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
定义模型 -> 生成迁移
类目 | 说明 |
---|---|
请求方法 | GET |
url定义 | /doc/download/ |
参数格式 | 无参数 |
返回结果:
文档下载页面
在doc/views.py里编写如下视图
from .models import Doc
class DocView(View):
“”"
文档下载页面视图
“”"
def get(self, request):
return render(request, 'doc/docDownload.html')
# 在doc/urls.py中添加如下路由
from django.urls import path
from . import views
app_name = ‘doc’
urlpatterns = [
path(‘download/’, views.DocView.as_view(), name=‘index’),
]
# 在根urls.py中添加如下路由
path('doc/', include('doc.urls'))
{% extends 'base/base.html' %}
{% load static %}
{% block title %}文档下载{% endblock %}
{% block link %}
<link rel="stylesheet" href="{% static 'css/doc/docDownload.css' %}">
<script>
iMenuIndex = 2
script>
{% endblock %}
{% 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>
<a href=“javascript:void(0);” class=“btn-more”>滚动加载更多a>
div>
{% endblock %}
/* 在static/css/doc/docDownload.css中,把如下代码覆盖之前的样式 */
/* ================= main start ================= */
#main {
margin-top: 25px;
min-height: 700px;
flex: 1;
}
/* ========= main-contain start ============ */
#main .main-contain {
width: 800px;
float: left;
margin-bottom: 30px;
}
/* ========= banner start =========== */
.main-contain .banner {
width: 100%;
}
.main-contain .banner img {
max-width: 100%;
}
.main-contain .pay-doc-contain {
background: #fff;
}
.main-contain .pay-doc-contain .pay-list {
display: flex;
justify-content: space-between;
flex-flow: wrap;
padding: 0 20px 20px;
}
.main-contain .pay-doc-contain .pay-item {
width: 800px;
height: 200px;
border-top: 1px solid #ddd;
margin-top: 20px;
display: flex;
}
.main-contain .pay-doc-contain .pay-item:hover {
box-shadow: 2px 2px 2px #ccc;
}
.pay-doc-contain .pay-item .pay-img {
width: 120px;
margin-right: 30px;
}
.pay-doc-contain .pay-item .pay-contain {
width: 250px;
position: relative
}
.pay-item .pay-contain .pay-title {
font-size: 20px;
line-height: 40px;
white-space: nowrap;
overflow: hidden;
}
.pay-item .pay-contain .pay-desc {
line-height: 20px;
color: #878787;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
font-size: 14px;
overflow: hidden;
}
.pay-item .pay-price {
display: block;
font-size: 20px;
text-align: right;
padding-right: 20px;
color: coral;
}
.d-contain {
width: 100%;
margin: 20px 0 20px ;
font-size: 18px;
line-height: normal;
}
.d-contain .doc-desc {
text-indent: 2em;
margin: 15px;
/padding: 10px;/
}
.d-contain .doc-title {
font-size: 20px;
font-weight: bold;
color: chocolate;
}
.btn-more {
display: block;
border: none;
width: 400px;
line-height: 40px;
text-align: center;
background: skyblue;
color: #efefef;
margin: 20px auto;
}
/* ========= banner end =========== /
/ ========= main-contain end ============ /
/ ================= main end ================= */
类目 | 说明 |
---|---|
请求方法 | 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"
}
]
}
}
# 在doc/views.py中添加DocListView视图如下 class DocListView(View): """ 文档列表视图 """ def get(self, request): # 1.拿到所有文档 docs = Doc.objects.values('file_url', 'file_name', 'title', 'desc', 'image_url').filter(is_delete=False) # 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)
# 在doc文件夹下创建constants.py文件,配置如下常量
PER_PAGE_DOC_COUNT = 5
# 将给定的media文件夹内容复制到项目media文件内
# 然后导入tb_docs.sql中的数据
mysql -uroot -pqwe123 -D tzproject < tb_docs.sql
// 创建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('服务器超时,请重试!') }) }
});
js写完记得引用到docDownload.html
:
{% block script %}
<script src="{% static 'js/doc/doc.js' %}">script>
{% endblock %}
其实是上面通过前端a标签我们已经实现了本项目中的文件下载。但是实际项目中经常出现的文件下载功能中的文件是动态生成的,这种情况怎么处理呢?
# 文件下载视图 class DocDownload(View): """ """ def get(self, request, doc_id):
file_fb = makefile() # 生成文件流 try: res = FileResponse(file_fb) except Exception as e: logger.info("获取文档内容出现异常:\n{}".format(e)) raise Http404("文档下载异常!") ex_name = 'xls' # 文件后缀,表明文件类型 # 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('某表格.xls') res["Content-Disposition"] = "attachment; filename*=UTF-8''{}".format(doc_filename) return res