Django ajax的簡單使用、自定義分頁器

一. ajax初識

1. 先後端傳輸數據編碼格式contentType

  使用form表單向後端提交數據時,必須將form表單的method由默認的get改成post,若是提交的數據中包含文件,還要將form表單的enctype由默認的"application/x-www-form-urlencoded"修改成"multipart/form-data"。css

  咱們能夠經過谷歌瀏覽器-》檢查 中的Network查看網絡請求的詳細信息。html

  以form表單爲例,其中代碼以下(用Bootstrap裝飾了一下):前端

  輸入用戶名密碼,而後隨便選一個文件點提交:python

  點擊view source查看原生數據:jquery

  隨後發現後端能拿到文件,不過只是文件名而已:ajax

  隨後修改將enctype修改成"multipart/form-data",而後再次提交該文件:數據庫

  

  此時原生數據中file看不到了,不事後端能夠看到request.FILES中收到了真實的文件,使用GET能夠拿到對應的文件對象。django

2. 小結

  先後端傳輸數據編碼格式contentType:編程

  1. application/x-www-form-urlencodedjson

  • 對應的數據格式:name=jason&password=666&myfile=test.py
  • 後端獲取數據:request.POST

  2. multipart/form-data

  • 對應的數據格式:name=jason&password=666
  • 後端獲取文件格式數據:request.FILES
  • 後端獲取普通鍵值對數據:request.POST

   注意:django會將urlencoded編碼的數據解析自動放到request.POST,即便修改了編碼格式,只要有其中有普通的鍵值對,都能經過request.POST取到數據。

二. ajax的簡單使用

  前端朝後端發送請求的方式有四種:

  1. 瀏覽器窗口手動輸入網址(get請求)
  2. a標籤的href屬性(get請求)
  3. form表單(get、post請求,默認是get請求)
  4. ajax(get、post請求)

  前面三種咱們都已經接觸過了,接下來來看看第四種ajax。

  • AJAXAsynchronous Javascript And XML)翻譯成中文就是異步的JavascriptXML」。即便用Javascript語言與服務器進行異步交互(客戶端發出一個請求後,無需等待服務器響應結束,就能夠發出第二個請求。),傳輸的數據爲XML(固然,傳輸的數據不僅是XML)。
  • AJAX 不是新的編程語言,而是一種使用現有標準的新方法。
  • AJAX 最大的優勢是在不從新加載整個頁面的狀況下,能夠與服務器交換數據並更新部分網頁內容。(這一特色給用戶的感覺是在不知不覺中完成請求和響應過程)
  • AJAX 不須要任何瀏覽器插件,但須要用戶容許JavaScript在瀏覽器上執行。

2.1 ajax的基本語法

  ajax主要由四個部分組成:

$('#d1').click(function () {
        $.ajax({
            // 提交的地址,不寫默認提交至當前頁面,同form表單的action
            url:'/index/',
            // 提交的方式
            type:'post',
            // 提交的數據,通常以鍵值對的形式出現
            data:{'name':'jason','password':'123'},
            // 回調函數
            success:function (data) {  // data接收的就是異步提交返回的結果
                alert(data)
            }
        })
    })

   注意:ajax傳輸數據的默認編碼格式也是urlencoded。先後端傳輸數據時,數據格式與編碼要一一對應,好比傳輸文件就要將編碼改成formdata。

2.2 ajax實現前端數字相加

  假設有三個input框,需求是前兩個input框輸入數字,點擊提交按鈕後將結果顯示在第三個input中,中途頁面不刷新且加法運算要經過後端實現。

  大體思路以下:

  1. 拿到input框中輸入的數字
  2. 將數字傳至後端進行運算
  3. 後端將運算的結果返回給前端
  4. 前端將結果展現在第三個input框中

  首先假設用戶輸入的均爲數字,因此咱們不作任何檢驗。而後ajax是異步JavaScript和XML,JQuery內部封裝了JavaScript,咱們這裏使用JQuery的語法來獲取input框中輸入內容(JQuery_obj.val()),而後使用ajax的回調函數seccess來將加法運算的結果顯示在第三個input框中(JQuery_obj.val(result))。

  代碼以下(前端未作任何裝飾,可能有點醜):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test1</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<input type="text" id="s1">+<input type="text" id="s2">=<input type="text" id="s3">
<button id="b1">提交</button>

<script>
    $('#b1').on('click', function () {
        $.ajax({
            url: '/test1/', //不寫默認提交至當前頁面
            type: 'post',
            data:{'s1':$('#s1').val(),'s2':$('#s2').val()},
            success:function (data) {
                $('#s3').val(data)
            }
        })
    })
</script>
</body>
</html>
html代碼
def test1(request):
    if request.method == 'POST':
        print(request.POST)
        s1 = request.POST.get('s1')
        s2 = request.POST.get('s2')
        # 拿到的前端數據均爲字符串格式,因此須要類型轉換
        res = int(s1) + int(s2)
        return HttpResponse(res)
    return render(request, 'test1.html')
相應的視圖函數

2.2 ajax傳輸JSON數據

  各編程語言與前端數據傳輸一般使用json格式,由於json支持多種語言,各編程語言都有相應的json語法。在python中是使用json.dumps()和json.loads()分別實現對象的序列化和反序列化。而JavaScript中是使用JSON.stringify()和JSON.parse()分別實現對象的序列化和反序列化。

  向後端傳輸JSON數據時,須要修改編碼類型,否則會出現如下狀況:

  以上狀況的出現是由於編碼與數據格式不匹配形成的,你傳輸的是JSON格式字符串,而編碼urlencode卻讓後端拿到的是字典,因此就直接把你的JSON字符串當初字典的key,value拿個空來本身造一個字典出來了。

   爲了統一編碼和數據格式,須要將編碼設置爲‘application/json’。

  代碼以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test1</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<button id="b1">提交</button>
<script>
    $('#b1').on('click', function () {
        $.ajax({
            url:'',
            type:'post',
            data:JSON.stringify({'name': 'json', 'password': '123'}),
            contentType:'application/json',
            success:function (data) {
                alert(data)
            }
        })
    })
</script>
</body>
</html>
html代碼
def test1(request):
    if request.method == 'POST':
        import json
        # 編碼改爲application/json以後,傳輸過來的是二進制數據,存在request.body中
        dic = json.loads(request.body.decode('utf-8'))
        print(dic, type(dic))
        return HttpResponse('get it')
    return render(request, 'test1.html')
視圖函數

 2.3 ajax傳輸文件

  傳輸文件相比傳輸json字符串要複雜一些,首先要想辦法把用戶上傳的文件取出來,這須要用到JavaScript中的FormData對象的方法,其次仍是要統一數據格式與編碼,將編碼改成false(由於formdata內部有自帶一個編碼)。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test1</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
file:<input type="file" name="myfile" id="f1">
<button id="b1">提交</button>
<script>
  $('#b1').on('click', function () {
      let formdata = new FormData();//生成一個FormData對象
      //$('#f1')[0]獲得的是JQuery對象中的第一個JS對象,JS_obj.files拿到全部的文件,經過索引取第一個文件
      formdata.append('file', $('#f1')[0].files[0]);
      $.ajax({
          url:'',
          type:'post',
          contentType:false,//用FormData自帶的編碼,因此不須要設置contentType
          processData:false,//告訴瀏覽器不要處理數據
          data: formdata,//直接將formdata提交至後端便可
          success:function (data) {
              alert(data)
          }
      })
  })
</script>
</body>
</html>
html代碼
def test1(request):
    if request.method == 'POST':
        # 沒有普通鍵值對,因此request.POST是空
        print(request.POST)
        print(request.FILES)
        # request.FIlES.get('file')拿出來的是文件對象
        file_obj = request.FILES.get('file')
        # 保存文件至本地
        with open(file_obj.name, 'wb') as f:
            for line in file_obj:
                f.write(line)
        return HttpResponse('get it')
    return render(request, 'test1.html')
對應視圖函數

 2.4 ajax小試驗

  初次接觸ajax,對ajax理解只是一點點皮毛,今天受一個朋友影響,對ajax的理解加深了一點點,在此感謝CC teacher。

  接下來講的方法可能有點雞肋,但是又有點意思。拿圖書管理系統舉例,點擊添加做者,直接在當前頁面局部刷新來實現,不用標籤的隱藏等各類方法,就只用ajax來實現(就是那麼頭鐵)。

  大體思路:

  1. 前端頁面展現做者數據的區域用一個div標籤包裹
  2. 點擊添加按鈕,利用click事件和ajax,從後端獲取一個用render渲染過的HTML頁面(該頁面須要啥就寫啥,不須要body和head標籤,同模板導入與自定義inclusion_tag同樣)。
  3. 將獲取的頁面data經過JQ語法.html(data)顯示在div中(這時本來顯示做者數據的區域數據就會全被獲取的頁面覆蓋了)。
{% extends 'home.html' %}
{% block content %}

    <div id="d1">
        <button id="i1" class="btn btn-info">添加</button>
        <table class="table table-hover table-striped table-bordered">
            <thead>
            <tr>
                <th>id</th>
                <th>name</th>
                <th>age</th>
                <th>gender</th>
                <th>phone</th>
                <th>addr</th>
                <th>action</th>
            </tr>
            </thead>
            <tbody>

            {% for author in author_list %}
                <tr>
                    <td>{{ author.pk }}</td>
                    <td>{{ author.name }}</td>
                    <td>{{ author.age }}</td>
                    <td>{{ author.get_gender_display }}</td>
                    <td>{{ author.authordetail.phone }}</td>
                    <td>{{ author.authordetail.addr }}</td>
                    <td>
                        <a href="{% url 'author_edit' author.pk %}" class="btn btn-success">編輯</a>
                        <a href="{% url 'author_delete' author.pk %}" class="btn btn-danger">刪除</a>
                    </td>
                </tr>
            {% endfor %}

            </tbody>
        </table>
    </div>
{% endblock %}

{% block js %}
    <script>
        $('#i1').click(function () {
            $.ajax({
                url: '{% url "author_add" %}',
                type: 'post',
                data: {'type': '333'},
                success: function (data) {
                    //將id爲d1的div標籤中的html換成data變量中存的數據
                    $('#d1').html(data)
                }
            })
        })
    </script>
{% endblock %}
大體代碼

  form表單和ajax是能夠一塊兒使用的,不過這樣感受沒什麼意義,試驗了一下,並無出現網上說的錯誤。ajax綁定form表單中的button按鈕及submit按鈕,二者效果同樣,後端先獲取到ajax提交的數據,而後再獲取到from表單中的數據,有興趣能夠自行試驗。

  注意:雖然ajax是異步提交(GitHub註冊示例)、局部刷新,可是並非全部ajax使用的越多越好,由於ajax異步的回調函數會向後端詢問執行的結果,當同時有不少該請求時,服務端會有很大的負擔。

三. 批量插入,分頁器的簡單實現

   先在數據庫中建立多條數據,而後展現在頁面上,這裏以1000條爲例。

def test1(request):
    for i in range(100):
        models.Book2.objects.create(name='第%s本' % i)
    book_list = models.Book2.objects.all()
    return render(request, 'test1.html', locals())

  以上這麼增長數據時,發現要在頁面等一段時間纔會有數據顯示,由於寫入數據庫要時間,前端只能等待數據寫入數據庫結束。這種狀況須要用到批量插入bulk_create:

def test1(request):
    # 定義一個列表
    book_list = []
    for i in range(100):
        # 實例化出Book2的對象,並將其加入列表
        book_list.append(models.Book2(name='第%s本' % i))
    # 這就是批量導入的精髓,至關於異步,程序執行無需等待該代碼執行完畢,可直接去執行後續代碼
    models.Book2.objects.bulk_create(book_list)
    # 隨後前端直接可使用book_list中的書籍對象點屬性去展現內容,不須要等數據庫中數據寫入完畢
    return render(request, 'test1.html', locals())

  上述使用的是列表,可是當列表中數據不少時,會佔用不少的內存,能夠採用生成器的方式來進行優化:g = (models.book(name = '第%s本' % i for i in range(10000)))。

  分頁器是須要考慮一共有幾頁的,這須要依據數據總條數來定,其次分頁器每一次只顯示幾個按鈕,這就意味着咱們是無法在前端來完成這個動態的過程的,只能在後端完成,這時候須要用到後端或者前端的取消轉義語法。

html = ''
for i in range(1,pager_nums+1):
html += '<li><a href="?page=%s">%s</a></li>'%(i,i)

  最終代碼以下:

class Pagination(object):
    def __init__(self, current_page, all_count, per_page_num=2, pager_count=11):
        """
        封裝分頁相關數據
        :param current_page: 當前頁
        :param all_count:    數據庫中的數據總條數
        :param per_page_num: 每頁顯示的數據條數
        :param pager_count:  最多顯示的頁碼個數

        用法:
        queryset = model.objects.all()
        page_obj = Pagination(current_page,all_count)
        page_data = queryset[page_obj.start:page_obj.end]
        獲取數據用page_data而再也不使用原始的queryset
        獲取前端分頁樣式用page_obj.page_html
        """
        try:
            current_page = int(current_page)
        except Exception as e:
            current_page = 1

        if current_page < 1:
            current_page = 1

        self.current_page = current_page

        self.all_count = all_count
        self.per_page_num = per_page_num

        # 總頁碼
        all_pager, tmp = divmod(all_count, per_page_num)
        if tmp:
            all_pager += 1
        self.all_pager = all_pager

        self.pager_count = pager_count
        self.pager_count_half = int((pager_count - 1) / 2)

    @property
    def start(self):
        return (self.current_page - 1) * self.per_page_num

    @property
    def end(self):
        return self.current_page * self.per_page_num

    def page_html(self):
        # 若是總頁碼 < 11個:
        if self.all_pager <= self.pager_count:
            pager_start = 1
            pager_end = self.all_pager + 1
        # 總頁碼  > 11
        else:
            # 當前頁若是<=頁面上最多顯示11/2個頁碼
            if self.current_page <= self.pager_count_half:
                pager_start = 1
                pager_end = self.pager_count + 1

            # 當前頁大於5
            else:
                # 頁碼翻到最後
                if (self.current_page + self.pager_count_half) > self.all_pager:
                    pager_end = self.all_pager + 1
                    pager_start = self.all_pager - self.pager_count + 1
                else:
                    pager_start = self.current_page - self.pager_count_half
                    pager_end = self.current_page + self.pager_count_half + 1

        page_html_list = []
        # 添加前面的nav和ul標籤
        page_html_list.append('''
                    <nav aria-label='Page navigation>'
                    <ul class='pagination'>
                ''')
        first_page = '<li><a href="?page=%s">首頁</a></li>' % (1)
        page_html_list.append(first_page)

        if self.current_page <= 1:
            prev_page = '<li class="disabled"><a href="#">上一頁</a></li>'
        else:
            prev_page = '<li><a href="?page=%s">上一頁</a></li>' % (self.current_page - 1,)

        page_html_list.append(prev_page)

        for i in range(pager_start, pager_end):
            if i == self.current_page:
                temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
            else:
                temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
            page_html_list.append(temp)

        if self.current_page >= self.all_pager:
            next_page = '<li class="disabled"><a href="#">下一頁</a></li>'
        else:
            next_page = '<li><a href="?page=%s">下一頁</a></li>' % (self.current_page + 1,)
        page_html_list.append(next_page)

        last_page = '<li><a href="?page=%s">尾頁</a></li>' % (self.all_pager,)
        page_html_list.append(last_page)
        # 尾部添加標籤
        page_html_list.append('''
                                           </nav>
                                           </ul>
                                       ''')
        return ''.join(page_html_list)
分頁器代碼

  先在應用下新建一個叫utils的文件夾,裏面建立一個.py文件,將該代碼拷貝進去保存。而後將該py文件導入到views.py中。

from django.shortcuts import render,HttpResponse
from app01 import models
# 導入拷貝了上述分頁器代碼的py文件
from app01.utils import my_page

def booklist(request):
    book_list = models.Book2.objects.all()
    # 拿到數據總條數
    all_count = book_list.count()
    # 獲得當前頁面,前端的分頁器被點擊時返回page,標明用戶點擊的頁編號
    current_page = request.GET.get('page',1)
    page_obj = my_page.Pagination(current_page=current_page,all_count=all_count)
    # 將總數據按用戶點擊的頁編號切片
    page_queryset = book_list[page_obj.start:page_obj.end]
    return render(request,'booklist.html',locals())
相關文章
相關標籤/搜索