[django项目] 如何在网站上实现文档下载功能?

文档下载功能

I. 功能需求分析

1>功能
  1. 文档下载展示页,展示整体的网页框架
  2. 文档列表,展示可下载的文档,包括标题、封面、简介等
  3. 文档下载,点击下载?即可下载文档

II. 模型设计

1>字段分析

针对文档的下载需要满足以下字段:

  1. 文件url
  2. 文件名
  3. 文件标题
  4. 简介
  5. 封面图片url
2>模型定义

在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

定义模型 -> 生成迁移

III. 文档下载页面

1>接口设计

  1. 接口说明:
类目说明
请求方法GET
url定义/doc/download/
参数格式无参数
  1. 返回结果:

    文档下载页面

2>后端代码

2.1>文档页面视图

在doc/views.py里编写如下视图

from .models import Doc


class DocView(View):
    """
    文档下载页面视图
    """
    
    def get(self, request):
        return render(request, 'doc/docDownload.html')
2.2>路由
# 在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'))

3>前端代码

3.1>html
<!-- template/doc/docDownload.html -->
{% 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 %}
        <!-- main-contain start  -->
        <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">
                    <!-- doc info -->
                </ul>
            </div>
        <!-- btn-more start -->
        <a href="javascript:void(0);" class="btn-more">滚动加载更多</a>
        <!-- btn-more end -->
        </div>
        <!-- main-contain  end -->
{% endblock %}
3.2>css
/* 在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 ================= */

IIII. 文档列表

1>接口设计

  1. 接口说明:
类目说明
请求方法GET
url定义/doc/docs/
参数格式查询参数
  1. 参数说明:
参数名类型是否必须描述
page整数页码
  1. 返回结果:
{
    "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>后端代码

2.1>文档列表视图
# 在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)
2.2>常量配置
# 在doc文件夹下创建constants.py文件,配置如下常量
PER_PAGE_DOC_COUNT = 5
2.3>导入数据
# 将给定的media文件夹内容复制到项目media文件内
# 然后导入tb_docs.sql中的数据
mysql -uroot -pqwe123 -D tzproject < tb_docs.sql

3>前端代码

3.1>js代码
// 创建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 %}

V. 文档下载

其实是上面通过前端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


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值