Django雜篇(1)

Django雜篇(1)

這裏咱們介紹如下Django經常使用的一些小工具,分別是:前端

  1. bulk_create,一種將數據批量插入數據庫的方法,效率較高
  2. Pagination,自定義的一種分頁器,重點在於其思路和使用
  3. 多對多表關係的建立方法,常見的有三種
  4. form校驗組件的應用,其主要做用就是按照咱們的要求校驗數據的格式,並取到符合條件以及不符合條件的數據和報錯

bulk_create

其實批量插入數據的原理很是簡單,在平常來看,咱們在向數據庫插入數據的時候,一般都是一條一條的插入,若是有類似數據,咱們一般會用一個循環,來持續插入,實際上這種方法若是在數據量比較大的狀況下會很是耗時,因此咱們纔會引入這種插入方式,下面就用一個很是簡單的例子來作一下對比,首先咱們建立一個Django項目,命名第一個應用名爲app01.python

# 咱們在models裏面建立一個最簡單的書籍表
# app01/models.py
class Book(models.Model):
    title=models.CharField(max_length=32)
# 而後在Terminal窗口,輸入python manage.py makemigrations和python manage.py migrate以後就成功創建了表,而後在urls.py裏面加路由

# urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', views.index),
]

# 而後在views.py裏面寫對應路由的函數,如下這種就是效率最低,也是最經常使用的插入方式,1000條數據甚至要用兩分鐘左右,並且對數據庫的壓力特別大,由於每次寫入都要操做數據庫
# views.py,常規插入方式
def index(request):
    # 往書籍表中插入數據 1000
    for i in range(1000):  # 這種插入方式 效率極低
         models.Book.objects.create(title='第%s本書'%i)
    book_queryset = models.Book.objects.all()
    return render(request,'index.html',locals())

# index.html
<body>
{% for book_obj in book_queryset %}
    <p>{{ book_obj.title }}</p>
{% endfor %}
</body>

換用bulk_create的話,views.py裏面就要按如下這種方式寫:數據庫

# views.py,bulk_create批量插入方式
def index(request):
    book_list = []
    for i in range(100000):
         book_list.append(models.Book(title='第%s本書'%i))
    models.Book.objects.bulk_create(book_list)  # 批量插入數據
    book_queryset = models.Book.objects.all()
    return render(request,'index.html',locals())

咱們能夠很明確的發現,用批量插入十萬條數據僅僅用了不到二十秒,差距很是明顯.其實原理很是簡單django

  • 無非就是平時是一條一條插入,用了bulk_create以後咱們是把全部須要插入的數據先插入一個列表中,這個操做是在計算機內部完成的,耗時很是低,而後經過bulk_create把這個列表插入到數據庫中,從而極大的提高了效率

Pagination

首先咱們要知道分頁器的概念,由於一般狀況下一個網站其頁面大小是有限的,不能展現其所有的信息,因此須要分頁器來吧這些內容分紅不一樣的頁面,以便於咱們瀏覽,那麼咱們首先用本身的思路來實現一個簡單的分頁器,咱們繼續上面的項目寫,把插入數據的那幾行註釋掉就好bootstrap

# views.py
# 自定義分頁器
def index(request):
    # 1. 獲取用戶想要訪問的頁碼數
    current_page = request.GET.get('page', 1)  # 獲取用戶輸入,若是沒有page參數,默認展現第一頁
    current_page = int(current_page)
    # 2. 每頁展現多少條數據,這裏咱們設置爲每頁10條
    per_page_num = 10
    # 3. 定義起始位置和終止位置
    start_page = (current_page - 1) * per_page_num
    end_page = current_page * per_page_num

    # 4. 統計數據的總條數
    book_queryset = models.Book.objects.all()
    all_count = book_queryset.count()

    # 5. 求數據到底須要多少個頁面才能展現完
    page_num, more = divmod(all_count, per_page_num)
    if more:
        page_num += 1
    # page_num就決定了須要多少個頁碼
    page_html = ''
    xxx = current_page  # 這裏取出來當前選中的頁碼賦給xxx,以便於後面判斷當前頁面
    if current_page < 6:    # 若是當前頁面頁碼小於6,就不能再往前數五個頁面,因此要爲其重置爲6
        current_page=6
    for i in range(current_page-5,current_page+6): # 這裏咱們但願分頁器是以選中頁面的頁碼爲中心,而後左右各有五個頁面的頁碼供選擇
        if xxx == i:
            page_html+='<li class="active"><a href="?page=%s">%s</a></li>'   # 這裏能夠設置當前頁面的頁碼爲高亮的形式
        else:
            page_html+='<li><a href="?page=%s">%s</a></li>'
    book_queryset = models.Book.objects.all()[start_page:end_page]
    return render(request, 'index.html', locals())

# 而後,在index.html裏面,咱們以下寫,這裏分頁的格式是來自於bootstrap,咱們修改後便可使用,
<body>
<nav aria-label="Page navigation">
  <ul class="pagination">
    <li>
      <a href="#" aria-label="Previous">
        <span aria-hidden="true">&laquo;</span>
      </a>
    </li>
    {{ page_html|safe }}{#這裏就是咱們所寫的最關鍵的一句,|safe的意思是取消轉義,即後端傳過來的符合前端標籤要求的語句能夠被表現出其應有的形式,而不僅是字符串形式#}
    <li>
      <a href="#" aria-label="Next">
        <span aria-hidden="true">&raquo;</span>
      </a>
    </li>
  </ul>
</nav>
</body>

以上就是咱們手動實現的一個簡略的分頁器,固然還有好多功能沒有完善,不過咱們瞭解分頁器的思路便可,由於實際生產中咱們並不須要手動去寫分頁器,這裏有一個較完整的現成的分頁器代碼,咱們將其記錄下來,會調用便可後端

# 在app01下面新建一個utils文件夾,而後新建一個mypage.py文件,將如下代碼複製粘貼便可
class Pagination(object):
    def __init__(self, current_page, all_count, per_page_num=10, 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)

下面咱們介紹怎樣去調用這個已經存在定義好的分頁器安全

# urls.py,寫對應路由
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r'^login/', views.login),
]


# views.py
from django.shortcuts import render,HttpResponse,redirect
from app01 import models
from app01.utils.mypage import Pagination   # 這裏導入剛纔粘貼的已經定義好的分頁器類
# 使用封裝好的分頁器代碼
def login(request):
    book_queryset = models.Book.objects.all()   # 這裏取到表內全部的數據,生成一個queryset對象
    current_page = request.GET.get('page',1)    # 從前端發來的get請求裏面取出用戶點擊的page頁面,若是沒有默認就是1,也就是首頁
    all_count = book_queryset.count()   #這裏對queryset對象進行計數,獲得一共多少條記錄
    # 1.實例化產生對象
    page_obj = Pagination(current_page=current_page,all_count=all_count)# 咱們只須要把記錄的總數量和當前頁面的page數傳進函數裏面便可
    # 2.對真實數據進行切片操做
    page_queryset = book_queryset[page_obj.start:page_obj.end]
    return render(request,'login.html',locals())

# login.html,前端的調用也很是簡單
<body>
{% for book_obj in page_queryset %} {#這裏把後端傳來的queryset對象直接循環,顯示出來,便可以看到第*本書#}
    <p>{{ book_obj.title }}</p>
{% endfor %}
{{ page_obj.page_html|safe }}   {#這裏是顯示分頁器的核心操做,加|safe是取消轉義#}
</body>

建立多對多表關係的經常使用方法

  1. 第一種app

    就是咱們以前在建表的時候用到的,利用models.ManyToManyField方法來創建表和表之間的多對多關係,Django會自動生成第三張表.函數

    可是這種方法的弊端在於,第三張表只有主鍵和兩張表的關係字段,且咱們不能額外對其添加字段,因此其可擴展性較差,不利於項目後期的維護與開發.實例以下:

    class Book(models.Model):
        ...
        author=models.ManyToManyField(to='Author')
    class Author(models.Model):
        ...
  2. 第二種

    純手動建立,即咱們不依賴於ORM自動幫咱們建立表關係,而是本身手動建立,這種方法所創建的表可擴展性好,能夠添加不少咱們想要額外增長的功能,好比表數據建立的時間等.可是弊端在於不少ORM的查詢方法不能用,查詢的效率會比較低,並且代碼較複雜,開發效率也比較低,實例以下:

    class Book(models.Model):
        ...
    
    class Author(models.Models):
        ...
    
    class Book2Author(models.Model):
        book_id = models.ForeignKey(to='Book')
        author_id = models.ForeignKey(to='Author')
        create_time = models.DateField(auto_now_add=True)
        ...
  3. 第三種

    半自動的建立,即多對多的關係表仍是咱們本身手動建立,可是咱們會告訴Django的ORM關係表的名字和位置,這樣咱們既保留了ORM查詢的便利性,又保留了第三張關係表的可擴展性,可謂一箭雙鵰,可是惟一的缺點可能就是須要寫的代碼比較多,不過也無可厚非.

    半自動的建立表關係所須要額外加的兩個參數是through='關係表的名字',through_fields=('表名(外鍵所在的表)','表名(外鍵不在的那個表)'),注意表名的順序,不能反

    class Book(models.Model):
          ...
          authors = models.ManyToManyField(to='Author', through='Book_Author', through_fields=('book','author'))
    
    
    class Author(models.Model):
          ...
          books = models.ManyToManyField(to='Book', through='Book2Author', through_fields=('author', 'book'))
    
    class Book_Author(models.Model):
          book = models.ForeignKey(to='Book')
          author = models.ForeignKey(to='Author')
          ...

form校驗組件的應用

咱們在寫一些大大小小的項目的時候,多多少少都會用到校驗的功能,好比用戶註冊帳號的時候,驗證用戶名是否存在,用戶密碼兩次輸入是否相同,都須要用到校驗.不論是前端仍是後端,都有必要加上數據校驗的功能,一方面是爲了數據庫的安全,另一方面也能夠阻止一些非法數據的流入.不過相比之下,後端的校驗比前端校驗更有必要,由於前端頁面是寫給大衆看的,不少東西都是公開的,能夠直接修改,對數據的校驗有很大的阻力,因此咱們就更要重視在後端對數據的校驗.

那麼form組件就能夠幫助咱們來完成數據的校驗工做,固然,他不止能完成數據的校驗,還能夠完成頁面的渲染和錯誤信息的展現,可謂十分強大.

在對form進行說明以前,咱們首先要在views.py裏面創建一個類,以便於後面的調用和測試

# views.py
from django import forms
class MyRegForm(forms.Form):
      sername = forms.CharField(min_length=3,max_length=8)
      password = forms.CharField(min_length=3,max_length=8)
      email = forms.EmailField()
# 是否是以爲以上定義的類有些眼熟?確實,跟在models.py裏面定義表結構十分類似

渲染頁面

渲染頁面的實際意義是咱們在後端用form校驗過數據以後把數據發送到前端,而後前端經過一些形式來展現出來,forms組件會自動幫咱們渲染用戶輸入(或者是選擇,下拉框等)的標籤,可是提交按鈕以及一些別的數據並不會自動幫咱們渲染,仍是須要咱們手動去設置.forms經常使用的三種渲染頁面的方式以下:

# formm.html
<body>
<p>
第一種渲染前端頁面的方式:封裝程度很是高,可是標籤樣式及參數不方便調整,可擴展性較差
    {{ form_obj.as_p }}
    {{ form_obj.as_ul }}
</p>

<p>第二種渲染頁面的方式:擴展性較高,須要咱們手寫的代碼量比較多</p>
<p>
    {{ form_obj.username.label }}{{ form_obj.username }}{#.lable能夠看到其標籤,.username則是顯示出其真實的內容,這裏是一個input輸入框#}
</p>
<p>
    {{ form_obj.password.label }}{{ form_obj.password }}
</p>
<p>
    {{ form_obj.email.label }}{{ form_obj.email }}
</p>

<p>第三種渲染前端頁面的方式:代碼量和擴展性都很高(推薦使用)</p>
<form action="" method="post" novalidate> {#這裏添加novalidate參數能夠取消前端幫咱們作校驗,以便於咱們只在後端作校驗#}
    {% for foo in form_obj %}
    <p>
        {{ foo.label }}:{{ foo }}
        <span style="color: red">{{ foo.errors.0 }}</span>
    </p>
    {% endfor %}
    <input type="submit">
</form>
</body>

展現錯誤信息

在forms組件裏面展現錯誤信息很是簡單,只須要用對象點errors.0就能夠了,以下:

#formm.html
<body>
<form action="" method="post" novalidate>
     {% for foo in form_obj %}
     <p>
         {{ foo.label }}:{{ foo }}
         <span style="color: red">{{ foo.errors.0 }}</span> {#這裏就是真正展現出錯誤的地方,即對象foo.errors.0 #}
     </p>
     {% endfor %}
     <input type="submit">
</form>
</body>

固然以上的報錯都是英文顯示的,咱們能夠手動重寫來實現報錯用中文來顯示,具體就是在以前咱們在views.py裏定義的類MyRegForm,在裏面加參數error_message={}便可,內部咱們能夠以報錯類型來重寫報錯內容,直接在冒號後面寫便可,

from django import forms
class MyRegForm(forms.Form):
      sername = forms.CharField(min_length=3,max_length=8,label='用戶名',
                               error_messages={
                                   'min_length':'用戶名最短三位',  # 這裏用戶名小於三位的話就會報這個錯,報錯內容被咱們重寫以後就會這樣報錯,如下同理
                                   'max_length':'用戶名最長八位',
                                   'required':'用戶名不能爲空'
                               },initial='我是初始值',required=False,# required賦值false的話該項不填也不會報錯,即容許不填,實際運用就是能夠用在非必填項
widget= widgets.TextInput(attrs={'class':'form-control others'}))# widget能夠改變該框的type屬性
      password = forms.CharField(min_length=3,max_length=8)
      email = forms.EmailField()

校驗數據

手動校驗(is_valid,cleaned_data,errors)

手動校驗即咱們手動調用幾個函數來觀察數據是否經過校驗,好比is_valid,cleaned_data和errors,測試代碼以下:

# 這裏咱們測試的時候在左下角的Python Console裏面測試,能夠實時看到結果,比較方便
from app01 import views

#1. 給自定義的類定義一個字典
obj = views.MyRegForm({'username':'jason','password':'123','email':'12'})

#2. is_vaild()數據所有符校驗標準合纔會返回True,但凡是有不符合標準的都會返回False
obj.is_valid()  
Out[5]: False

#3. cleaned_data能夠查看查看全部符合條件的數據
obj.cleaned_data
Out[9]: {'username': 'jason', 'password': '123'}

#4. errors能夠查看不符合條件的數據以及報錯的緣由
obj.errors
Out[10]: {'email': ['Enter a valid email address.']}

#5. 校驗數據的時候,默認狀況下類裏面全部的字段都必須傳值
obj = views.MyRegForm({'username':'jason','password':'123'})
obj.is_valid()
Out[12]: False
obj.errors
Out[13]: {'email': ['This field is required.']}

#6. 默認狀況下能夠多傳,多傳後面的數據會捨棄,可是絕對不能少傳,少傳就會不符合校驗標準,出現False
obj=views.MyRegForm({'username':'jason','password':'1233','email':'123@qq.com','xxx':'ooo'})
obj.is_valid()
Out[15]: True

鉤子函數的校驗

對於數據的字段來講,定義的時候限制其格式是一方面,另一方面咱們能夠用鉤子函數來對其作額外的校驗,這種方式在對於大項目的後期修改,維護和二次開發上有較大的應用.

局部鉤子

當咱們須要對數據庫的表中的某一個字段的數據進行額外的校驗的時候,局部鉤子就是很是好的選擇,示例以下:

class MyRegForm(forms.Form):
    def clean_username(self):# 局部鉤子要寫在MyRegForm總類的下面
        username = self.cleaned_data.get('username')
        if '黃' in username:# 檢測全部的用戶姓名,帶'黃'字的不符合標準,加入報錯信息
            self.add_error('username','這本書的名字不符合標準')
        return username # 最後要返回咱們校驗的字段的數據,否則會看不到最後結果
全局鉤子

上面局部鉤子是針對單個字段進行二次校驗,因此全局鉤子就是能夠根據多個字段進行校驗,好比,用戶註冊的時候咱們用來校驗其輸入的兩次密碼是否一致,示例以下:

class MyRegForm(forms.Form):
     def clean(self):
        password = self.cleaned_data.get('password')
        confirm_password = self.cleaned_data.get('confirm_password')
        if not password == confirm_password:
            self.add_error('confirm_password','兩次密碼不一致')
        return self.cleaned_data

正則校驗

咱們還有第三種校驗數據的方式,即正則校驗.

其實正則校驗也是在咱們定義類的時候就定義好的,與前面手動校驗的前提比較類似

# views.py 這裏是對於159開頭的手機號的校驗,RegexValidator即爲正則校驗的關鍵字
from django import forms
from django.forms import Form
from django.core.validators import RegexValidator

class MyForm(Form):
    user = forms.CharField(
        validators=[RegexValidator(r'^[0-9]+$', '請輸入數字'), RegexValidator(r'^159[0-9]+$', '數字必須以159開頭')],
    )

經常使用字段

如下爲幾個經常使用字段,須要使用時直接複製便可.

# 單選框select
gender = forms.ChoiceField(
    choices=((1, "男"), (2, "女"), (3, "保密")),
    label="性別",
    initial=3,
    widget=widgets.RadioSelect()
)


# 多選框select
hobby1 = forms.MultipleChoiceField(
    choices=((1, "籃球"), (2, "足球"), (3, "雙色球"),),
    label="愛好",
    initial=[1, 3],
    widget=widgets.SelectMultiple()
)

# 單選heckbox
keep = forms.ChoiceField(
    label="是否記住密碼",
    initial="checked",
    widget=forms.widgets.CheckboxInput()
)

# 多選checkbox
hobby2 = forms.MultipleChoiceField(
    choices=((1, "籃球"), (2, "足球"), (3, "雙色球"),),
    label="愛好",
    initial=[1, 3],
    widget=forms.widgets.CheckboxSelectMultiple()
)
相關文章
相關標籤/搜索