1. 程式人生 > 其它 >Django序列化元件與資料批量操作與簡單使用Forms元件

Django序列化元件與資料批量操作與簡單使用Forms元件

目錄

SweetAlert前端外掛

SweetAlert官方使用手冊

Django自帶的序列化元件

serializers序列化元件可以把我們用ORM產生的QuerySet物件轉換成json格式資料。

from django.core import serializers

def index(request):
    book_queryset = models.Book.objects.all()
    res = serializers.serialize('json', book_queryset)
    return HttpResponse(res)

轉換後的格式長這樣:

批量資料操作

如果我們想要使用ORM去迴圈插入10萬條資料,每次新增資料都執行一次create(),這樣會頻繁走資料庫操作,效率極低,比如:

for i in range(100000):
    models.Book.objects.create(title=f'第{i}本書')

這樣操作需要等待很久,所以我們可以換一個方法:先用類建立多個物件,在用bulk_create(),這樣只要走一次資料庫操作就可以新增多個數據了:

obj_list = []  # 存放物件
for i in range(100000):
    obj = models.Book(title=f'第{i}本書')  # 例項化多個數據物件
    obj_list.append(obj)  # 物件追加到列表種
models.Book.objects.bulk_create(obj_list)  # 一次性全部新增

分頁器與推導流程

網站不可能將所有的資料全部展示到一頁,應該考慮使用分頁,每頁只展示部分資料。那麼分頁該如何實現呢?

推導流程

1.首先需要知道ORM中的all()方法返回的結果集是支援正數的索引切片的。

# 取第一個到第10個的結果
book_queryset = models.Books.objects.all()[0:10]  

2.在使用者點選分頁的頁數時肯定是要向後端請求資料的,比如第5頁就給前端返回第41到第50的結果(每頁展示10條資料的情況),所以後端需要用一個變數接收前端傳來的頁數。

前端:傳送第五頁的請求,可以用a標籤傳送GET請求,並攜帶資料page=5。

<a href='?page=5'>5</a>

後端:用變數接收

current_page = request.GET.get('page')

3.既然需要分頁,那麼每頁肯定都有最多的展示條數,這裡我們設定每頁10條,返回指定頁數的資料。

def index(request):
    current_page = request.GET.get('page')
    try:  # 異常處理,防止current_page值為空時報錯
        current_page = int(current_page)
    except:
        current_page = 1
    start = (current_page - 1) * 10  # 資料起始位置
    end = current_page * 10  # 資料結束位置
    book_queryset = models.Books.objects.all()[start:end]
    return render(request, 'index.html', {'book_queryset': book_queryset})

4.前端接收後端資料:

<div class="text-center">
    {% for book_obj in book_queryset %}
        <p>{{ book_obj.name }}</p>
    {% endfor %}
</div>

5.這時候我們只需要在瀏覽器地址後面輸入?page=10,就可以獲取第10頁的資料。

6.新增Bootstrap提供的分頁器

<div class="text-center">
    {% for book_obj in book_queryset %}
        <p>{{ book_obj.name }}</p>
    {% endfor %}
    <nav aria-label="Page navigation">
        <ul class="pagination">
            <li>
                <a href="#" aria-label="Previous">
                    <span aria-hidden="true">&laquo;</span>
                </a>
            </li>
            <li><a href="#">1</a></li>
            <li><a href="#">2</a></li>
            <li><a href="#">3</a></li>
            <li><a href="#">4</a></li>
            <li><a href="#">5</a></li>
            <li>
                <a href="#" aria-label="Next">
                    <span aria-hidden="true">&raquo;</span>
                </a>
            </li>
        </ul>
    </nav>
</div>

7.由於前端不好寫動態的分頁器,所以我們用後端編寫html標籤,編寫頁數時還需要用到divmod()獲取所有資料需要的頁數,比如99條資料要10頁,100條資料要10頁,101條資料要11頁。

後端:

def index(request):
    # 獲取當前頁數
    current_page = request.GET.get('page')
    try:  # 異常處理,防止current_page值為空時報錯
        current_page = int(current_page)
    except:
        current_page = 1
    data_queryset = models.Books.objects.all()
    start = (current_page - 1) * 10  # 資料起始位置
    end = current_page * 10  # 資料結束位置
    book_queryset = data_queryset[start:end]
    
    data_count = data_queryset.count()
    # 接收整數和餘數
    page_count, m = divmod(data_count, 10)
    # 餘數不為0,則要把整數部分加一
    if m != 0:
        page_count += 1
    html = []
    # 讓當前頁數左邊顯示5個頁碼,右邊顯示五個頁碼
    for i in range(current_page - 5, current_page + 5):
        if i == current_page:  # 當前頁數高亮顯示
            html.append(f"<li class='active'><a href='?page={i}'>{i}</a></li>")
        else:  # 當前頁數普通顯示
            html.append(f"<li><a href='?page={i}'>{i}</a></li>")
    return render(request, 'index.html', {'book_queryset': book_queryset, 'html': html})

前端:

<div class="text-center">
    {% for book_obj in book_queryset %}
        <p>{{ book_obj.name }}</p>
    {% endfor %}
    <nav aria-label="Page navigation">
        <ul class="pagination">
            <li>
                <a href="#" aria-label="Previous">
                    <span aria-hidden="true">&laquo;</span>
                </a>
            </li>
            {% for h in html %}
                {{ h|safe }}
            {% endfor %}
            <li>
                <a href="#" aria-label="Next">
                    <span aria-hidden="true">&raquo;</span>
                </a>
            </li>
        </ul>
    </nav>
</div>

8.現在可以點選分頁器到指定頁面了,但是出現了新問題,當前頁碼小於6時,分頁器有零或負數,當前頁碼過大時,分頁器會超出。

這個問題加個變數就可以了。

temp_page = current_page
# 頁數過小
if current_page < 6:
    temp_page = 6
# 頁數過大
if current_page > page_count - 4:
    temp_page = page_count - 4
for i in range(temp_page - 5, temp_page + 5):
    if i == current_page:  # 當前頁數高亮顯示
        html.append(f"<li class='active'><a href='?page={i}'>{i}</a></li>")
    else:  # 當前頁數普通顯示
        html.append(f"<li><a href='?page={i}'>{i}</a></li>")

究極大法

上面是自定義分頁器開發流程的基本思路,我們不需要掌握程式碼的編寫,只需要掌握基本用法即可,原文部落格:自定義分頁器 - JasonJi - 部落格園 (cnblogs.com)

自定義分頁器封裝程式碼

點選檢視程式碼
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:  最多顯示的頁碼個數
        """
        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)

前端使用:

<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            {% for book in page_queryset %}
            <p>{{ book.title }}</p>
            {% endfor %}
            {{ page_obj.page_html|safe }}
        </div>
    </div>
</div>

後端使用:

def get_book(request):
    book_list = models.Book.objects.all()
    current_page = request.GET.get("page", 1)
    all_count = book_list.count()
    page_obj = Pagination(current_page=current_page, all_count=all_count, per_page_num=10)
    page_queryset = book_list[page_obj.start:page_obj.end]
    return render(request, 'booklist.html', locals())

Forms元件之建立

Forms元件功能:資料校驗、標籤渲染、展示資訊。

  • 資料校驗:資料是否符合規範(長度、格式等)
  • 標籤渲染:快速生成輸入標籤等
  • 資訊展示:展示錯誤的提示資訊,並保留原輸入內容

基本使用

from django import forms
# 建立表單類
class MyForm(forms.Form):
    # 使用者名稱至少三個字元最多八個字元
    username = forms.CharField(min_length=3, max_length=8)
    # 年齡最小不能小於0 最大不能超過150
    age = forms.IntegerField(min_value=0, max_value=150)
    # 郵箱必須符合郵箱格式(@關鍵符號)
    email = forms.EmailField()

Forms元件之資料校驗

建立好表單類之後,在檢視函式中使用:

將資料傳入並例項化物件,需要字典型別,字典的鍵名稱與表單類中自定義的名稱一致:

form_obj = MyForm({
    'username': 'abc', 
    'age': 999, 
    'email': '12qq'
})

檢視資料是否合法(全部合法結果才是True):

form_obj.isvalid()

檢視不符合條件的資料及原因:

form_obj.errors

檢視符合條件的資料:

form_obj.cleaned_data

補充

1.forms類中所有的欄位資料預設都是必填的,不能少,如果想忽略某些欄位,可以新增 required=False。

email = forms.EmailField(required=False)

2.forms類中額外傳入的欄位資料不會做任何的校驗,直接忽略。

Forms元件之渲染標籤

後端返回給前端form物件,前端可以使用這個物件建立標籤:

def index(request):
    form_obj = MyForm()
    return render(request, 'index.html', locals())

建立方式一:封裝程度高,擴充套件性較差,主要用於快速生成頁面測試功能

1.每一個輸入框佔一行

<form action="" method="post">
    {{ form_obj.as_p }}
    <input type="submit">
</form>

2.所有輸入框佔一行

<form action="" method="post">
    {{ form_obj.as_table }}
    <input type="submit">
</form>

3.輸入框以無序列表形式展示

<form action="" method="post">
    {{ form_obj.as_ul }}
    <input type="submit">
</form>

建立方式二:封裝程度低,擴充套件性較好,但是欄位比較多的情況下不方便。

form物件.欄位名.label:文字提示
form物件.欄位名:輸入標籤

<form action="" method="post">

    {{ form_obj.username.label }}
    {{ form_obj.username }}

    {{ form_obj.age.label }}
    {{ form_obj.age }}

    {{ form_obj.email.label }}
    {{ form_obj.email }}
    <input type="submit">
</form>

建立方式三:建立方式二使用for迴圈建立

<form action="" method="post">
    {% for form in form_obj %}
        <p>
            {{ form.label }}
            {{ form }}
        </p>
    {% endfor %}
    <input type="submit">
</form>

補充

1.forms元件只負責渲染獲取使用者資料的標籤,form表單標籤和提交按鈕需要自己寫。

2.渲染標籤中文提示,可以在建立Form類中,建立欄位時用引數 label指定,不指定預設使用欄位名(首字母大寫)。

username = forms.CharField(min_length=3, max_length=8, label='使用者名稱')

Forms元件之資訊展示

在你點選提交表單資訊後,它會提醒你錯誤資訊:

如果不想要這種提示方式,form表單可以取消瀏覽器自動新增校驗功能的操作:新增屬性novalidate。

<form action="" method="post" novalidate>
</form>

這時候前端的校驗功能沒了,我們可以在後端進行校驗:

def index(request):
    form_obj = MyForm()
    if request.method == 'POST':
        # request.POST可以看成字典型別
        form_obj = MyForm(request.POST)
        # 校驗資料
        if form_obj.is_valid():  
            return HttpResponse('資料正常!')
    return render(request, 'index.html', locals())

前端使用form.errors.0獲取錯誤資訊

<form action="" method="post" novalidate>
    {% for form in form_obj %}
        <p>
            {{ form.label }}
            {{ form }}
            <span style="color: red">{{ form.errors.0 }}</span>
        </p>
    {% endfor %}
    <input type="submit">
</form>

錯誤資訊是可以自定義的,在Form類中建立欄位時定義:

# 使用者名稱至少三個字元最多八個字元
    username = forms.CharField(min_length=3, max_length=8, label='使用者名稱',
                               error_messages={
                                   'min_length': '使用者名稱最短3位',
                                   'max_length': '使用者名稱最長8位',
                                   'required': '使用者名稱必填'
                               })