基於Django+Bootstrap框架,設計微型小說網站

一、項目背景:

  爲了回顧關於django的文件上傳分頁功能,打算寫一個微型的小說網站練練手。花了一個下午的時間,寫了個小項目,發現其中其實遇到了許多問題,不過大部分通過debug之後就解決了,其他部分通過閱讀了Pagination插件以及Bootstrap-FileInput插件的官方文檔。

二、詳細設計:

  省去小說網站的用戶模塊的功能,小說網站主要的功能就是上傳文件在線閱讀小說。針對這兩個功能,
  主要用到dajngo內置的Pagination模塊,以及選擇一個上傳文件插件即可。因爲用的是Bootsrap前端框架,所以就選擇了Bootsrap比較多人用的FileInput插件。
  大致的流程:

  • 在首頁可以選擇上傳本地的txt文件到服務器上,然後首頁上同時會異步更新已上傳的txt文本文件列表。
  • 並且可以在上面選擇閱讀或者刪除的操作。閱讀則跳轉到另外一個頁面,後臺會讀取該文本文件,並且進行分頁操作,返回到前端。主要的流程就是這樣。接下來講講Pagination和FileInput插件和核心代碼。

三、合適的工具:

 Django內置的Pagination實現分頁功能,這個不用多說,用Django做web開發分頁功能都會用到。
 Bootstrap本身自帶upload file文件上傳插件太醜了,加上功能也不夠完善。所以選擇了Bootstrap FileInput插件。

版本選擇:

  • Python 3.6.6
  • Django==2.1.7
  • Bootstrap v4.3.1
  • bootstrap-fileinput v4.5.2

四、代碼詳解:

首先代碼主要分爲兩塊,一塊爲文件上傳後,接收文件對象,保存到指定的目錄下;第二塊爲讀取txt文本文件內容,分頁展示到前端頁面。
 首先講講文件上傳的代碼,主要涉及到前端的bootstrap-fileinputt插件。該插件將簡單的HTML文件輸入轉換爲高級文件選擇器控件。對於不支持JQuery或Javascript的瀏覽器,將有助於回退到正常的HTML文件輸入。

基於Django+Bootstrap框架,設計微型小說網站
 以上這段是官方的自我介紹,說說我個人感受吧。首先這個插件支持批量上傳,異步上傳等功能,簡化大部分JS邏輯方面的代碼,具體只要跟着官方的API文檔看一看,修改一些參數即可。其次,對於上傳時會顯示一個進度條,用於顯示上傳的完成度,這樣直觀反映了完成度。

bootstrap-fileinput的github地址:
https://github.com/kartik-v/bootstrap-fileinput
bootstrap-fileinput的官方文檔地址:
http://plugins.krajee.com/file-input
bootstrap-fileinput的官方DEMO:
http://plugins.krajee.com/file-basic-usage-demo

4.1、文件上傳

HTML代碼:

 <div dir=rtl class="file-loading">
    <input id="input-b8" name="input-b8" multiple type="file">
</div>

JS代碼:

$(document).ready( function() {
$("#input-b8").fileinput({
    rtl: true,
    uploadUrl: '/file_receive/',
    dropZoneEnabled: false,
    showPreview: false,
    allowedFileExtensions: ['txt'],
    initialPreviewConfig: []
});
});

代碼說明:
fileinput()方法裏面傳入的是一個json數據,裏面有很多個屬性,每個數值代表初始化上傳控件時的特性,如果沒有設置的屬性則按照控件的默認屬性設置。簡單說下里面幾個屬性的設置:uploadUrl:上傳文件地址;dropZoneEnabled:是否顯示拖曳區域;showPreview:是否顯示預覽區域;allowedFileExtensions:允許上傳的文件格式。

後臺代碼

def file_receive(request):
    #   接收File-Input空間傳送的文件
    if request.method == 'POST':
        file = request.FILES['input-b8']
        file_path = "static/books/"+file.name
        with open(file_path,"wb") as f:
            for chunk in file.chunks():
                f.write(chunk)
    return JsonResponse({'status':'success'})

代碼說明:
 以上是後臺接收文件對象並且保存的代碼。我這邊省略判斷上傳文件大小的方法,感興趣的可以在with open()中添加判斷。最後接收文件後,會返回給前端一個json數據,前端插件接收到返回的JSON數據纔會確定是否上傳文件成功,bootstrap Fileinput纔會先Done狀態。

拓展:

 這裏有點需要注意的就是,後臺接收上傳的文件,雖然是通過POST的方式上傳,但是不能通過request.POST["filename"]或者request.POST.get("filename","None")兩種方式來訪問。
 而是需要用另外一種方式:
 request.FILES["filename"]或者request.FILES.get("filename","None")
 接下來已經得到文件對象,需要把在內存中的文件寫入到硬盤中。讀取文件的幾個方法和屬性:

  1. filename.read():從文件讀取整個上傳的數據,這個方法只適合小文件
  2. filename.chunks():按塊返回文件,通過for循環進行迭代,可以將大文件按塊寫入到服務器中
  3. filename.multiple_chunks():當filename文件大於2.5M時,該方法返回True,否則返回False。可以根據該方法來判斷選擇用1方法還是2方法。

4.2、異步更新已上傳的文件列表

HTML代碼:

<div style="padding-top: 20px">
    <table id="book_list" class="table table-striped table-bordered table-hover">
        <tr>
            <th>上傳書籍</th>
            <th>上傳時間</th>
            <th>文件大小</th>
            <th>操作</th>
        </tr>
        {% for book in objects %}
        <tr>
            <td>{{ book.name}}</td>
            <td>{{ book.book_time }}</td>
            <td>{{ book.book_size }}</td>
            <td><a href="/book_read/?book_name={{ book.name }}">閱讀</a>
            <a href="/book_del/?book_name={{ book.name }}">刪除</a></td>
        </tr>
        {% endfor %}
    </table>
</div>

JS代碼:

$("#input-b8").on('fileuploaded',function(){
    console.log('success');
    $.get('/book_update/',function(data){
        var book_html ="<tr>\n" +
            "<th>上傳書" +
            "籍</th>" +
            "<th>上傳時間</th>" +
            "<th>文件大小</th>" +
            "<th>操作</th>"+
            "</tr>";

        console.log(data);
        for (var i in data){
            book_html += "<tr><td>"+ data[i]['name']+"</td>" +
                "<td>"+data[i]['book_time']+"</td>" +
                "<td>"+data[i]['book_size']+"</td>" +
                "<td><a href=\"/book_read/?book_name="+data[i]['name']+"\">閱讀</a>"+
                "<a href=\"/book_del/?book_name="+data[i]['name']+"\">刪除</a></td>"+
                "</tr>"
        }
        $("#book_list").html(book_html)
        console.log(book_html)
    });
});

代碼說明:
$("#input-b8").on('fileuploaded',function(){})這個方法時在上傳完文件後進行回調事件的函數;就是指上傳一個文件成功後就會調用該方法;所以我將異步更新上傳文件列表的代碼放在這個回調事件中。當每個文件上傳後,就會請求後臺,查詢指定目錄下的文件列表,生成json格式的數據返回前臺,前臺再通過遍歷的形式拿到其中的數據,進行展示,具體效果如下:

基於Django+Bootstrap框架,設計微型小說網站

後臺代碼

def book_list():
    #   獲取books目錄下的書籍
    file_list = []
    filedir_path = "static/books/"
    list_file = os.listdir(filedir_path)
    for book in list_file:
        book_info = {}
        book_path = filedir_path + book

        book_info['name'] = book
        book_info['timestamp'] = os.path.getctime(book_path)
        book_info['book_time'] = time_format(book_info['timestamp'])
        book_info['book_size'] = os.path.getsize(book_path)
        file_list.append(book_info)
    books = sorted(file_list,key= lambda x:x['timestamp'],reverse=True)
    return books 

def time_format(timestamp):
    #   格式化時間戳成指定的時間
    time_struct = time.localtime(timestamp)
    time_string = time.strftime('%Y-%m-%d %H:%M',time_struct)
    return time_string  

代碼說明:
 代碼其實很簡單,主要是對通過os模塊獲取靜態目錄static下的books目錄下的文件列表,然後在獲取每個文件的時間戳,通過列表推導式,按時間戳爲key值進行逆向排序。

4.3、文章分頁模塊

HTML代碼:

<div class="header text-center ">
    <a href="/index/" style="float: left;">
        <i class="fa fa-home fa-2x" aria-hidden="true">Home</i>
    </a>
    <h3>{{ book_name }}</h3>
</div>

<div class="col-md-12 col-sm-offset-1 main">
    {% for content in book_content %}
    <span>{{ content }}</span>
    {% endfor %}
</div>

<div class="pagination">
    <div class="col-md-4  ">
        {% if book_content.has_previous  %}
        <i class="fa fa-arrow-left" aria-hidden="true">
            <a href="?book_name={{ book_name }}&page={{ book_content.previous_page_number }}">
                上一頁
            </a>
        </i>

        {% endif %}
    </div>

    <div class="col-md-4  ">
        <h5>
            第{{ book_content.number }}頁/共{{ book_content.paginator.num_pages }}頁
        </h5>

    </div>
    {% if book_content.has_next %}
    <div class="col-md-4  ">
        <a href="?book_name={{book_name}}&page={{ book_content.next_page_number }}">
            下一頁
        </a>
        <i class="fa fa-arrow-right" aria-hidden="true">
        </i>
    </div>
    {% endif %}
</div>

JS代碼:

def book_read(request):
    #   獲取上傳書籍的內容
    if request.method == 'GET':
        book_name = request.GET['book_name']            # 書籍名稱
        file_path = "static/books/" + book_name         # 書籍路徑

        with open(file_path,encoding='gbk', errors='ignore') as f:
            book_contents = f.readlines()

        paginator = Paginator(book_contents, 50)
        try:
            page = int(request.GET['page'])  # 頁碼
            book_content = paginator.page(page)
        except Exception as e:
            book_content = paginator.page(1)
        return render_to_response('book.html',{'book_content': book_content, 'book_name': book_name})

代碼說明:
 讀取文件的所有行,保存在一個列表中(list),每行作爲一個元素。然後實例化一個Paginator對象,並且在實例化中傳入一個需要分頁的對象列表,以及一頁包含多少個數據。再從接收前端傳送過來的頁碼,取特定頁碼的數據,再傳回前端。

基於Django+Bootstrap框架,設計微型小說網站

基於Django+Bootstrap框架,設計微型小說網站

拓展:

1、分頁功能有Django內置的Paginator類提供的,該類位於django/core/paginator,需要用的地方導入即可:
from django.core.paginator improt Paginator

2、read()、readline()、readlines()方法的區別:
三者都是讀取文件內容:
read([size]):從當前位置其讀取size字節,如果方法裏面沒有參數size,讀取至文件結束爲止。返回的是一個字符串對象。
readline():方法調用一次就讀文件一行,該方法返回一個字符串。
readlines():讀取整個文件所有行,保存在一個列表中,每行作爲一個元素

3、Paginator對象操作:
實例化對象:
book_list = [1,2,3,4,5,6,7,8]
book_content = Paginator(book_list,3)

取特定頁的數據
content = book_content.page(2)

查特定頁當前頁碼數:
content.number

查分頁後的總頁數
content.num_pages

查詢某一頁是否有上一頁或者查詢上一頁頁碼:
content.has_previous()
content.previous_page_number()

查詢某一頁是否有下一頁或者查詢下一頁頁碼:
content.has_next()
content.next_page_number()

感興趣的同學歡迎關注公衆號獲取源碼:南城故夢

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章