Django2.0使用

建立項目:

  1. 經過命令行的方式:首先要進入到安裝了django的虛擬環境中。而後執行命令:
    django-admin startproject [項目的名稱]
    這樣就能夠在當前目錄下建立一個項目了。
  2. 經過pycharm的方式:文件->新建項目->選擇django。而後指定項目所在的路徑,以及Python解釋器,再點擊Create就能夠建立項目了。

運行項目:

  1. 終端:進入到項目文件夾中,而後執行如下命令便可運行:
    python manage.py runserver
  2. pycharm:直接點擊右上角的綠色三角箭頭按鈕就能夠了。注意:用pycharm運行項目,要避免一個項目運行屢次。。在項目配置中,把「只用單一實例」那個選項勾選上,避免以上的問題。

改變端口號:

  1. 在終端:運行的時候加上一個端口號就能夠了。命令爲:python manage.py runserver 9000
  2. 在pycharm中:右上角->項目配置->port。改爲你想要的端口號,從新運行。

讓同局域網中的其餘電腦訪問本機的項目:

  1. 讓項目運行到額時候,host爲0.0.0.0。css

    • 在終端,使用命令:python manage.py runserver 0.0.0.0:8000
    • 在pycharm,右上角->項目配置->host。改爲0.0.0.0
  2. settings.py文件中,配置ALLOWED_HOSTS,將本機的ip地址添加進去。示例代碼以下:html

    ALLOWED_HOSTS = ['192.168.0.103']

    注意:要關閉本身電腦的防火牆才行。前端

項目結構分析:

  1. manange.py:之後和項目交互基本上都是基於這個文件。通常都是在終端輸入python manage.py [子命令]。能夠輸入python manage.py help看下能作什麼事情。除非你知道你本身在作什麼,通常狀況下不該該編輯這個文件。
  2. settings.py:保存項目全部的配置信息。
  3. urls.py:用來作url與視圖函數映射的。之後來了一個請求,就會從這個文件中找到匹配的視圖函數。
  4. wsig.py:專門用來作部署的。不須要修改。

django推薦的項目規範:

按照功能或者模塊進行分層,分紅一個個app。全部和某個模塊相關的視圖都寫在對應的app的views.py中,而且模型和其餘的也是相似。而後django已經提供了一個比較方便建立app的命令叫作python manage.py startapp [app的名稱]。把全部的代碼寫在各自的app中。python

DEBUG模式:

  1. 若是開啓了DEBUG模式,那麼之後咱們修改了Django項目的代碼,而後按下ctrl+s,那麼Django就會自動的給咱們重啓項目,不須要手動重啓。
  2. 若是開啓了DEBUG模式,那麼之後Django項目中的代碼出現bug了,那麼在瀏覽器中和控制檯會打印出錯信息。
  3. 在生產環境中,禁止開啓DEBUG模式,否則有很大的安全隱患。
  4. 若是將DEBUG設置爲False,那麼必需要設置ALLOWED_HOSTS.

ALLOWED_HOSTS:

這個變量是用來設置之後別人只能經過這個變量中的ip地址或者域名來進行訪問。mysql

視圖函數

  1. 視圖函數的第一個參數必須是request。這個參數絕對不能少。
  2. 視圖函數的返回值必須是django.http.response.HttpResponseBase的子類的對象。

url相關:django2.0

url映射:

不使用緩存去加載頁面ctrl+shift+rlinux

  1. 爲何會去urls.py文件中尋找映射呢? 是由於在settings.py文件中配置了ROOT_URLCONFurls.py。全部django會去urls.py中尋找。
  2. urls.py中咱們全部的映射,都應該放在urlpatterns這個變量中。
  3. 全部的映射不是隨便寫的,而是使用path函數或者是re_path函數進行包裝的。

url傳參數:

  1. 採用在url中使用變量的方式:在path的第一個參數中,使用<參數名>的方式能夠傳遞參數。而後在視圖函數中也要寫一個參數,視圖函數中的參數必須和url中的參數名稱保持一致,否則就找不到這個參數。另外,url中能夠傳遞多個參數。path("book/detail/<book_id>/<category_id>/",views.book_detail)多個參數git

  2. 採用查詢字符串的方式:在url中,不須要單獨的匹配查詢字符串的部分。只須要在視圖函數中使用request.GET.get('參數名稱')的方式來獲取。示例代碼以下:github

    def author_detail(request):  author_id = request.GET['id']  text = '做者的id是:%s' % author_id  return HttpResponse(text)

    由於查詢字符串使用的是GET請求,因此咱們經過request.GET來獲取參數。而且由於GET是一個相似於字典的數據類型,全部獲取值跟字典的方式都是同樣的。web

url參數的轉換器:

path("book/publisher/<path:publisher_id>/",views.publisher_detail)正則表達式

  1. str:除了斜槓/之外全部的字符都是能夠的。
  2. int:只有是一個或者多個的阿拉伯數字。
  3. path:全部的字符都是知足的。
  4. uuid:只有知足uuid.uuid4()這個函數返回的字符串的格式。
  5. slug:英文中的橫杆或者英文字符或者阿拉伯數字或者下劃線才知足

urls模塊化(路由轉發):

若是項目變得愈來愈大。那麼url會變得愈來愈多。若是都放在主urls.py文件中,那麼將不太好管理。所以咱們能夠將每一個app本身的urls放到本身的app中進行管理。通常咱們會在app中新建一個urls.py文件用來存儲全部和這個app相關的子url。 須要注意的地方:

  1. 應該使用include函數包含子urls.py,而且這個urls.py的路徑是相對於項目的路徑。示例代碼以下:
    # 父url urlpatterns = [  path('admin/', admin.site.urls),  path('book/',include('book.urls')) ] # 子url urlpatterns = [  path('reports/', credit_views.report),  path('reports/<int:id>/', credit_views.report), ]
  2. appurls.py中,全部的url匹配也要放在一個叫作urlpatterns的變量中,不然找不到。
  3. url是會根據主urls.py和app中的urls.py進行拼接的,所以注意不要多加斜槓。

include函數的用法:

  1. include(module,namespace=None):
    • module:子url的模塊字符串。
    • namespace:實例命名空間。這個地方須要注意一點。若是指定實例命名空間,那麼前提必需要先指定應用命名空間。也就是在子urls.py中添加app_name變量。
  2. include((pattern_list, app_namespace), namespace=None):include函數的第一個參數既能夠爲一個字符串,也能夠爲一個元組,若是是元組,那麼元組的第一個參數是子urls.py模塊的字符串,元組的第二個參數是應用命名空間。這樣的話,namespace就可傳可不傳。也就是說,應用命名空間既能夠在子urls.py中經過app_name指定,也能夠在include函數中指定。
  3. include(pattern_list):pattern_list是一個列表。這個列表中裝的是path或者re_path函數。也就是說,能夠把子url中的path放在這個列表中,實例代碼以下:
    path('movie/',include([  path('',views.movie),  path('list/',views.movie_list), ]))

url命名:

爲何須要url命名?

由於url是常常變化的。若是在代碼中寫死可能會常常改代碼。給url取個名字,之後使用url的時候就使用他的名字進行反轉就能夠了,就不須要寫死url了。

如何給一個url指定名稱?

path函數中,傳遞一個name參數就能夠指定。示例代碼以下:

urlpatterns = [  path('',views.index,name='index'),  path('login/',views.login,name='login') ]

應用命名空間:

在多個app之間,有可能產生同名的url。這時候爲了不反轉url的時候產生混淆,能夠使用應用命名空間,來作區分。定義應用命名空間很是簡單,只要在appurls.py中定義一個叫作app_name的變量,來指定這個應用的命名空間便可。示例代碼以下:

# 應用命名空間 app_name = 'front'  urlpatterns = [  path('',views.index,name='index'),  path('login/',views.login,name='login') ]

之後在作反轉的時候就能夠使用應用命名空間:url名稱的方式進行反轉。示例代碼以下:

login_url = reverse('front:login')

應用(app)命名空間和實例命名空間:

一個app,能夠建立多個實例。能夠使用多個url映射同一個app。因此這就會產生一個問題。之後在作反轉的時候,若是使用應用命名空間,那麼就會發生混淆。爲了不這個問題。咱們能夠使用實例命名空間。實例命名空間也是很是簡單,只要在include函數中傳遞一個namespace變量便可。示例代碼以下:

urlpatterns = [  path('',include('front.urls')),  # 同一個app下有兩個實例  path('cms1/',include('cms.urls',namespace='cms1')),  path('cms2/',include('cms.urls',namespace='cms2')), ]

之後在作反轉的時候,就能夠根據實例命名空間來指定具體的url。示例代碼以下:

def index(request):  username = request.GET.get("username")  if username:  return HttpResponse('CMS首頁')  else:  # 獲取當前的命名空間  current_namespace = request.resolver_match.namespace  return redirect(reverse("%s:login"%current_namespace))

re_path

  1. re_path和path的做用都是同樣的。只不過re_path是在寫url的時候能夠用正則表達式,功能更增強大。

  2. 寫正則表達式都推薦使用原生字符串。也就是以r開頭的字符串。

  3. 在正則表達式中定義變量,須要使用圓括號括起來。這個參數是有名字的,那麼須要使用?P<參數的名字>。而後在後面添加正則表達式的規則。示例代碼以下:

    from django.urls import re_path from . import views  urlpatterns = [  # r"":表明的是原生字符串(raw)  re_path(r'^$',views.article),  # /article/list/<year>/  re_path(r"^list/(?P<year>\d{4})/$",views.article_list),  re_path(r"^list/(?P<month>\d{2})/$",views.article_list_month) ]
  4. 若是不是特別要求。直接使用path就夠了,省的把代碼搞的很麻煩(由於正則表達式實際上是很是晦澀的,特別是一些比較複雜的正則表達式,今天寫的明天可能就不記得了)。除非是url中確實是須要使用正則表達式來解決才使用re_path

reverse

  1. 若是在反轉url的時候,須要添加參數,那麼能夠傳遞kwargs參數到revers函數中。示例代碼以下:
    detail_url = reverse('detail',kwargs={"article_id":1,'page':2})
  2. 若是想要添加查詢字符串的參數,則必須手動的進行拼接。示例代碼以下:
    login_url = reverse('login') + "?next=/"

在「文章分類」參數傳到視圖函數以前要把這些分類分開來存儲到列表中。 好比參數是python+django,那麼傳到視圖函數的時候就要變成['python','django']

之後在使用reverse反轉的時候,限制傳遞「文章分類」的參數應該是一個列表,而且要將這個列表變成python+django的形式。

自定義URL轉換器

第一種辦法:

以前已經學到過一些django內置的url轉換器,包括有int、uuid等。有時候這些內置的url轉換器並不能知足咱們的需求,所以django給咱們提供了一個接口可讓咱們本身定義本身的url轉換器。

自定義url轉換器按照如下五個步驟來走就能夠了:

  1. 爲了模塊化,在app裏建一個單獨的文件如:converters.py,在其中定義一個類,直接繼承自object就能夠了。
  2. 在類中定義一個屬性regex,這個屬性是用來限制url轉換器規則的正則表達式。
  3. 實現to_python(self,value)方法,這個方法是將url中的值轉換一下,而後傳給視圖函數的。
  4. 實現to_url(self,value)方法,這個方法是在作url反轉的時候,將傳進來的參數轉換後拼接成一個正確的url。
  5. 將定義好的轉換器,使用django.urls.converters.register_converter方法註冊到django中。
  6. 須要在當前app包中的__init__.py中引入一下這個包from . import converters
  7. 源碼參照from django.urls import converters中已經定義好的去寫。

示例代碼以下:

from django.urls import register_converter  class CategoryConverter(object):  regex = r'\w+|(\w+\+\w+)+'   def to_python(self,value):  # python+django+flask  # ['python','django','flask']  result = value.split("+")  return result   def to_url(self,value):  # value:['python','django','flask']  # python+django+flask  if isinstance(value,list):  result = "+".join(value)  return result  else:  raise RuntimeError("轉換url的時候,分類參數必須爲列表!")  register_converter(CategoryConverter,'cate')

第二種辦法:

寫一個類,幷包含下面的成員和屬性:

  • 類屬性regex:一個字符串形式的正則表達式屬性;
  • to_python(self, value) 方法:一個用來將匹配到的字符串轉換爲你想要的那個數據類型,並傳遞給視圖函數。若是轉換失敗,它必須彈出ValueError異常;
  • to_url(self, value)方法:將Python數據類型轉換爲一段url的方法,上面方法的反向操做。

例如,新建一個converters.py文件,與urlconf同目錄,寫個下面的類:

class FourDigitYearConverter:
 regex = '[0-9]{4}'   def to_python(self, value):  return int(value)   def to_url(self, value):  return '%04d' % value

寫完類後,在URLconf 中註冊,並使用它,以下所示,註冊了一個xxxx:

from django.urls import register_converter, path  from . import converters, views  register_converter(converters.FourDigitYearConverter, 'xxxx') # 註冊  urlpatterns = [  path('articles/2003/', views.special_case_2003),  path('articles/<xxxx:year>/', views.year_archive),  ... ]

URL映射的時候指定默認參數

使用path或者是re_path的後,在route中均可以包含參數,而有時候想指定默認的參數,這時候能夠經過如下方式來完成。示例代碼以下:

from django.urls import path  from . import views  urlpatterns = [  path('blog/', views.page),  path('blog/page<int:num>/', views.page), ]  # View (in blog/views.py) def page(request, num=1):  # Output the appropriate page of blog entries, according to num.  ...

當在訪問blog/的時候,由於沒有傳遞num參數,因此會匹配到第一個url,這時候就執行view.page這個視圖函數,而在page函數中,又有num=1這個默認參數。所以這時候就能夠不用傳遞參數。而若是訪問blog/1的時候,由於在傳遞參數的時候傳遞了num,所以會匹配到第二個url,這時候也會執行views.page,而後把傳遞進來的參數傳給page函數中的num。

使用正則表達式

Django2.0的url雖然改‘配置’了,但它依然向老版本兼容。而這個兼容的辦法,就是用re_path()方法代替path()方法。re_path()方法在骨子裏,根本就是之前的url()方法,只不過導入的位置變了。下面是一個例子,對比一下Django1.11時代的語法,有什麼太大的差異?

from django.urls import path, re_path  from . import views  urlpatterns = [  path('articles/2003/', views.special_case_2003),  re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),  re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),  re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-]+)/$', views.article_detail), ]

path()方法不一樣的在於兩點:

  • year中匹配不到10000等非四位數字,這是正則表達式決定的
  • 傳遞給視圖的全部參數都是字符串類型。而不像path()方法中能夠指定轉換成某種類型。在視圖中接收參數時必定要當心。

自定義錯誤頁面

當Django找不到與請求匹配的URL時,或者當拋出一個異常時,將調用一個錯誤處理視圖。Django默認的自帶的錯誤視圖包括400、40三、404和500,分別表示請求錯誤、拒絕服務、頁面不存在和服務器錯誤。它們分別位於:

  • handler400 —— django.conf.urls.handler400。
  • handler403 —— django.conf.urls.handler403。
  • handler404 —— django.conf.urls.handler404。
  • handler500 —— django.conf.urls.handler500。

這些值能夠在根URLconf中設置。在其它app中的二級URLconf中設置這些變量無效。

Django有內置的HTML模版,用於返回錯誤頁面給用戶,可是這些403,404頁面實在醜陋,一般咱們都自定義錯誤頁面。

首先,在根URLconf中額外增長下面的條目,並導入views模塊:

from django.contrib import admin from django.urls import path from app import views  urlpatterns = [  path('admin/', admin.site.urls), ]  # 增長的條目 handler400 = views.bad_request handler403 = views.permission_denied handler404 = views.page_not_found handler500 = views.error

而後在,app/views.py文件中增長四個處理視圖:

def bad_request(request):  return render(request, '400.html')   def permission_denied(request):  return render(request, '403.html')   def page_not_found(request):  return render(request, '404.html')   def error(request):  return render(request, '500.html') 

再根據本身的需求,建立對應的400、40三、40四、500.html四個頁面文件,就能夠了(要注意好模板文件的引用方式,視圖的放置位置等等)。

模板

在以前的章節中,視圖函數只是直接返回文本,而在實際生產環境中其實不多這樣用,由於實際的頁面大可能是帶有樣式的HTML代碼,這可讓瀏覽器渲染出很是漂亮的頁面。目前市面上有很是多的模板系統,其中最知名最好用的就是DTL和Jinja2。DTL是Django Template Language三個單詞的縮寫,也就是Django自帶的模板語言。固然也能夠配置Django支持Jinja2等其餘模板引擎,可是做爲Django內置的模板語言,和Django能夠達到無縫銜接而不會產生一些不兼容的狀況。所以建議你們學習好DTL。

DTL與普通的HTML文件的區別

DTL模板是一種帶有特殊語法的HTML文件,這個HTML文件能夠被Django編譯,能夠傳遞參數進去,實現數據動態化。在編譯完成後,生成一個普通的HTML文件,而後發送給客戶端。

渲染模板:

渲染模板有多種方式。這裏講下兩種經常使用的方式。

  1. render_to_string:找到模板,而後將模板編譯後渲染成Python的字符串格式。最後再經過HttpResponse類包裝成一個HttpResponse對象返回回去。示例代碼以下:
from django.template.loader import render_to_string  from django.http import HttpResponse  def book_detail(request,book_id):  html = render_to_string("detail.html")  return HttpResponse(html)
  1. 以上方式雖然已經很方便了。可是django還提供了一個更加簡便的方式,直接將模板渲染成字符串和包裝成HttpResponse對象一步到位完成。示例代碼以下:
 from django.shortcuts import render  def book_list(request):  return render(request,'list.html')

模版查找路徑

在項目的settings.py文件中。有一個TEMPLATES配置,這個配置包含了模板引擎的配置,模板查找路徑的配置,模板上下文的配置等。模板路徑能夠在兩個地方配置。

  1. DIRS:這是一個列表,在這個列表中能夠存放全部的模板路徑,之後在視圖中使用render或者render_to_string渲染模板的時候,會在這個列表的路徑中查找模板。
  2. APP_DIRS:默認爲True,這個設置爲True後,會在INSTALLED_APPS的安裝了的APP下的templates文件加中查找模板。
  3. 查找順序:好比代碼render('list.html')。先會在DIRS這個列表中依次查找路徑下有沒有這個模板,若是有,就返回。若是DIRS列表中全部的路徑都沒有找到,那麼會先檢查當前這個視圖所處的app是否已經安裝,若是已經安裝了,那麼就先在當前這個app下的templates文件夾中查找模板,若是沒有找到,那麼會在其餘已經安裝了的app中查找。若是全部路徑下都沒有找到,那麼會拋出一個TemplateDoesNotExist的異常。
  4. DIRS > 當前APP > 其餘APP

模版變量

  1. 在模版中使用變量,須要將變量放到{{ 變量 }}中。
  2. 若是想要訪問對象的屬性,那麼能夠經過對象.屬性名來進行訪問。
    class Person(object):  def __init__(self,username):  self.username = username  context = {  'person': p }
    之後想要訪問personusername,那麼就是經過person.username來訪問。
  3. 若是想要訪問一個字典的key對應的value,那麼只能經過字典.key的方式進行訪問,不能經過中括號[]的形式進行訪問。
    context = {  'person': {  'username':'zhiliao'  } }
    那麼之後在模版中訪問username。就是如下代碼person.username
  4. 由於在訪問字典的key時候也是使用點.來訪問,所以不能在字典中定義字典自己就有的屬性名看成key,不然字典的那個屬性將編程字典中的key了。
    context = {  'person': {  'username':'zhiliao',  'keys':'abc'  } }
    以上由於將keys做爲person這個字典的key了。所以之後在模版中訪問person.keys的時候,返回的不是這個字典的全部key,而是對應的值。
  5. 若是想要訪問列表或者元組,那麼也是經過點.的方式進行訪問,不能經過中括號[]的形式進行訪問。這一點和python中是不同的。示例代碼以下:
    {{ persons.1 }}

if語句

  1. 全部的標籤都是在{%%}之間。
  2. if標籤有閉合標籤。就是{% endif %}
  3. if標籤的判斷運算符,就跟python中的判斷運算符是同樣的。==、!=、<、<=、>、>=、in、not in、is、is not這些均可以使用。
  4. 還能夠使用elif以及else等標籤。

for...in...

for...in…標籤

for...in...相似於Python中的for...in...。能夠遍歷列表、元組、字符串、字典等一切能夠遍歷的對象。示例代碼以下:

{% for person in persons %} <p>{{ person.name }}</p> {% endfor %}

若是想要反向遍歷,那麼在遍歷的時候就加上一個reversed。示例代碼以下:

{% for person in persons reversed %} <p>{{ person.name }}</p> {% endfor %}

遍歷字典的時候,須要使用itemskeysvalues等方法。在DTL中,執行一個方法不能使用圓括號的形式。遍歷字典示例代碼以下:

{% for key,value in person.items %} <p>key{{ key }}</p> <p>value{{ value }}</p> {% endfor %}

for循環中,DTL提供了一些變量可供使用。這些變量以下:

  • forloop.counter:當前循環的下標。以1做爲起始值。
  • forloop.counter0:當前循環的下標。以0做爲起始值。
  • forloop.revcounter:當前循環的反向下標值。好比列表有5個元素,那麼第一次遍歷這個屬性是等於5,第二次是4,以此類推。而且是以1做爲最後一個元素的下標。
  • forloop.revcounter0:相似於forloop.revcounter。不一樣的是最後一個元素的下標是從0開始。
  • forloop.first:是不是第一次遍歷。
  • forloop.last:是不是最後一次遍歷。
  • forloop.parentloop:若是有多個循環嵌套,那麼這個屬性表明的是上一級的for循環。

** 模板中的for...in...沒有continue和break語句,這一點和Python中有很大的不一樣,必定要記清楚! **

for...in...empty`標籤

這個標籤使用跟for...in...是同樣的,只不過是在遍歷的對象若是沒有元素的狀況下,會執行empty中的內容。示例代碼以下:

{% for person in persons %} <li>{{ person }}</li> {% empty %} 暫時尚未任何人 {% endfor %}

with標籤

  1. 在模板中,想要定義變量,能夠經過with語句來實現。
  2. with語句有兩種使用方式,第一種是with xx=xxx的形式,第二種是with xxx as xxx的形式。
  3. 定義的變量只能在with語句塊中使用,在with語句塊外面使用取不到這個變量。 示例代碼以下:
    {% with zs=persons.0%}  <p>{{ zs }}</p>  <p>{{ zs }}</p>  {% endwith %}  下面這個由於超過了with語句塊,所以不能使用  <p>{{ zs }}</p>   {% with persons.0 as zs %}  <p>{{ zs }}</p>  {% endwith %}

url標籤

url標籤:在模版中,咱們常常要寫一些url,好比某個a標籤中須要定義href屬性。固然若是經過硬編碼的方式直接將這個url寫死在裏面也是能夠的。可是這樣對於之後項目維護可能不是一件好事。所以建議使用這種反轉的方式來實現,相似於django中的reverse同樣。示例代碼以下:

<a href="{% url 'book:list' %}">圖書列表頁面</a>

若是url反轉的時候須要傳遞參數,那麼能夠在後面傳遞。可是參數分位置參數和關鍵字參數。位置參數和關鍵字參數不能同時使用。示例代碼以下:

# path部分 path('detail/<book_id>/',views.book_detail,name='detail')  # url反轉,使用位置參數 <a href="{% url 'book:detail' 1 %}">圖書詳情頁面</a>  # url反轉,使用關鍵字參數 <a href="{% url 'book:detail' book_id=1 %}">圖書詳情頁面</a>

若是想要在使用url標籤反轉的時候要傳遞查詢字符串的參數,那麼必需要手動在在後面添加。示例代碼以下:

<a href="{% url 'book:detail' book_id=1 %}?page=1">圖書詳情頁面</a>

若是須要傳遞多個參數,那麼經過空格的方式進行分隔。示例代碼以下:

<a href="{% url 'book:detail' book_id=1 page=2 %}">圖書詳情頁面</a>

autoescape自動轉義

  1. DTL中默認已經開啓了自動轉義。會將那些特殊字符進行轉義。好比會將<轉義成&lt;等。
  2. 若是你不知道本身在幹什麼,那麼最好是使用DTL的自動轉義。這樣網站纔不容易出現XSS漏洞。
  3. 若是變量確實是可信任的。那麼能夠使用autoescape標籤來關掉自動轉義。示例代碼以下:
    {% autoescape off %}  {{ info }} {% endautoescape %}

verbatim標籤

verbatim標籤:默認在DTL模板中是會去解析那些特殊字符的。和其餘模板相沖突的時候,須要關閉解析的時候,好比{%%}以及{{等。若是你在某個代碼片斷中不想使用DTL的解析引擎。那麼你能夠把這個代碼片斷放在verbatim標籤中。示例代碼下:

{% verbatim %} {{if dying}}Still alive.{{/if}} // 這樣就是原始字符不會被當成變量解析 {% endverbatim %}

Django模板過濾器

爲何須要過濾器?

由於在DTL中,不支持函數的調用形式(),所以不能給函數傳遞參數,這將有很大的侷限性。而過濾器其實就是一個函數,能夠對須要處理的參數進行處理,而且還能夠額外接收一個參數(也就是說,最多隻能有2個參數)。

add過濾器:

將傳進來的參數添加到原來的值上面。這個過濾器會嘗試將參數轉換成整形而後進行相加。若是轉換成整形過程當中失敗了,那麼會將參數進行拼接。若是是字符串,那麼會拼接成字符串,若是是列表,那麼會拼接成一個列表。示例代碼以下:

{{ value|add:"2" }}

若是value是等於4,那麼結果將是6。若是value是等於一個普通的字符串,好比abc,那麼結果將是abc2add過濾器的源代碼以下:

def add(value, arg): """Add the arg to the value.""" try: return int(value) + int(arg) except (ValueError, TypeError): try: return value + arg except Exception: return ''

cut過濾器

移除值中全部指定的字符串。相似於python中的replace(args,"")。示例代碼以下:

{{ value|cut:" " }}

以上示例將會移除value中全部的空格字符。cut過濾器的源代碼以下:

def cut(value, arg): """Remove all values of arg from the given string.""" safe = isinstance(value, SafeData) value = value.replace(arg, '') if safe and arg != ';': return mark_safe(value) return value

date過濾器

將一個日期按照指定的格式,格式化成字符串。示例代碼以下:

# 數據 context = { "birthday": datetime.now() }  # 模版 {{ birthday|date:"Y/m/d" }}

那麼將會輸出2018/02/01。其中Y表明的是四位數字的年份,m表明的是兩位數字的月份,d表明的是兩位數字的日。
還有更多時間格式化的方式。見下表。

格式字符 描述 示例
Y 四位數字的年份 2018
m 兩位數字的月份 01-12
n 月份,1-9前面沒有0前綴 1-12
d 兩位數字的天 01-31
j 天,可是1-9前面沒有0前綴 1-31
g 小時,12小時格式的,1-9前面沒有0前綴 1-12
h 小時,12小時格式的,1-9前面有0前綴 01-12
G 小時,24小時格式的,1-9前面沒有0前綴 1-23
H 小時,24小時格式的,1-9前面有0前綴 01-23
i 分鐘,1-9前面有0前綴 00-59
s 秒,1-9前面有0前綴 00-59

default

若是值被評估爲False。好比[]""None{}等這些在if判斷中爲False的值,都會使用default過濾器提供的默認值。爲True 時,則會使用value的值,示例代碼以下:

{{ value|default:"nothing" }}

若是value是等於一個空的字符串。好比"",那麼以上代碼將會輸出nothing

default_if_none

若是值是None,那麼將會使用default_if_none提供的默認值。這個和default有區別,default是全部被評估爲False的都會使用默認值。而default_if_none則只有這個值是等於None的時候纔會使用默認值。示例代碼以下:

{{ value|default_if_none:"nothing" }}

若是value是等於""也即空字符串,那麼以上會輸出空字符串。若是value是一個None值,以上代碼纔會輸出nothing

first

返回列表/元組/字符串中的第一個元素。示例代碼以下:

{{ value|first }}

若是value是等於['a','b','c'],那麼輸出將會是a

last

返回列表/元組/字符串中的最後一個元素。示例代碼以下:

{{ value|last }}

若是value是等於['a','b','c'],那麼輸出將會是c

floatformat

使用四捨五入的方式格式化一個浮點類型。若是這個過濾器沒有傳遞任何參數。那麼只會在小數點後保留一個小數,若是小數後面全是0,那麼只會保留整數。固然也能夠傳遞一個參數,標識具體要保留幾個小數。

  1. 若是沒有傳遞參數:
value 模版代碼 輸出
34.23234 `{{ value floatformat }}`
34.000 `{{ value floatformat }}`
34.260 `{{ value floatformat }}`
  1. 若是傳遞參數:
value 模版代碼 輸出
34.23234 `{{value floatformat:3}}`
34.0000 `{{value floatformat:3}}`
34.26000 `{{value floatformat:3}}`

join

相似與Python中的join,將列表/元組/字符串用指定的字符進行拼接。示例代碼以下:

{{ value|join:"/" }}

若是value是等於['a','b','c'],那麼以上代碼將輸出a/b/c

length

獲取一個列表/元組/字符串/字典的長度。示例代碼以下:

{{ value|length }}

若是value是等於['a','b','c'],那麼以上代碼將輸出3。若是valueNone,那麼以上將返回0

lower

將值中全部的字符所有轉換成小寫。示例代碼以下:

{{ value|lower }}

若是value是等於Hello World。那麼以上代碼將輸出hello world

upper

相似於lower,只不過是將指定的字符串所有轉換成大寫。

random

在被給的列表/字符串/元組中隨機的選擇一個值。示例代碼以下:

{{ value|random }}

若是value是等於['a','b','c'],那麼以上代碼會在列表中隨機選擇一個。

safe

標記一個字符串是安全的。也即會關掉這個字符串的自動轉義。示例代碼以下:

{{value|safe}}

若是value是一個不包含任何特殊字符的字符串,好比<a>這種,那麼以上代碼就會把字符串正常的輸入。若是value是一串html代碼,那麼以上代碼將會把這個html代碼渲染到瀏覽器中。

也能夠把返回的字符串經過導入from django.utils.safestring import mark_safe mark_safe(字符串)

slice

相似於Python中的切片操做。示例代碼以下:

{{ some_list|slice:"2:" }}

以上代碼將會給some_list2開始作切片操做。

stringtags

刪除字符串中全部的html標籤。示例代碼以下:

{{ value|striptags }}

若是value<strong>hello world</strong>,那麼以上代碼將會輸出hello world

truncatechars

若是給定的字符串長度超過了過濾器指定的長度。那麼就會進行切割,而且會拼接三個點來做爲省略號。示例代碼以下:

{{ value|truncatechars:5 }}

若是value是等於北京歡迎您~,那麼輸出的結果是北京...。可能你會想,爲何不會北京歡迎您...呢。由於三個點也佔了三個字符,因此北京+三個點的字符長度就是5。

truncatechars_html

相似於truncatechars,只不過是不會切割html標籤。示例代碼以下:

{{ value|truncatechars:5 }}

若是value是等於<p>北京歡迎您~</p>,那麼輸出將是<p>北京...</p>

查看源碼:from django.template import defaultfilters

自定義過濾器

  1. 首先在某個app中,建立一個python包,叫作templatetags,注意,這個包的名字必須爲templatetags,否則就找不到。
  2. 在這個templatetags包下面,建立一個python文件用來存儲過濾器。
  3. 在新建的python文件中,定義過濾器(也就是函數),這個函數的第一個參數永遠是被過濾的那個值,而且若是在使用過濾器的時候傳遞參數,那麼還能夠定義另一個參數。可是過濾器最多隻能有2個參數。
  4. 在寫完過濾器(函數)後,要使用register=django.template.Library() register.filter(過濾器名子,函數名子)進行註冊。
  5. 還要把這個過濾器所在的這個app添加到settings.INSTALLED_APS中,否則Django也找不到這個過濾器。
  6. 在模板中使用load標籤加載過濾器所在的python包。
  7. 能夠使用過濾器了。
  8. django.template.Library.filter還能夠看成裝飾器來使用。若是filter函數沒有傳遞任何參數,那麼將會使用這個函數的名字來做爲過濾器的名字。固然若是你不想使用函數的名字來做爲過濾器的名字,也能夠傳遞一個name參數。示例代碼以下:
    @register.filter('my_greet') def greet(value,word):  return value + word
@register.filter
def time_since(value):  """  time距離如今的時間間隔  1.若是時間間隔小於1分鐘之內,那麼就顯示「剛剛」  2.若是是大於1分鐘小於1小時,那麼就顯示「xx分鐘前」  3.若是是大於1小時小於24小時,那麼就顯示「xx小時前」  4.若是是大於24小時小於30天之內,那麼就顯示「xx天前」  5.不然就是顯示具體的時間  2017/10/20 16:15  """  if not isinstance(value,datetime):  return value  now = datetime.now()  # timedelay.total_seconds  timestamp = (now - value).total_seconds()  if timestamp < 60:  return '剛剛'  elif timestamp >= 60 and timestamp < 60*60:  minutes = int(timestamp/60)  return '%s分鐘前' % minutes  elif timestamp >= 60*60 and timestamp < 60*60*24:  hours = int(timestamp/60/60)  return '%s小時前' % hours  elif timestamp >= 60*60*24 and timestamp < 60*60*24*30:  days = int(timestamp/60/60/24)  return '%s天前' % days  else:  return value.strftime("%Y/%m/%d %H:%M")

include

  1. 有些模版代碼是重複的。所以能夠單獨抽取出來,之後哪裏須要用到,就直接使用include進來就能夠了。
  2. 若是想要在include子模版的時候,傳遞一些參數,那麼能夠使用with xxx=xxx的形式。示例代碼以下:
    {% include 'header.html' with username='zhiliao' %}

模版繼承

在前端頁面開發中。有些代碼是須要重複使用的。這種狀況能夠使用include標籤來實現。也能夠使用另一個比較強大的方式來實現,那就是模版繼承。模版繼承相似於Python中的類,在父類中能夠先定義好一些變量和方法,而後在子類中實現。模版繼承也能夠在父模版中先定義好一些子模版須要用到的代碼,而後子模版直接繼承就能夠了。而且由於子模版確定有本身的不一樣代碼,所以能夠在父模版中定義一個block接口,而後子模版再去實現。如下是父模版的代碼:

{% load static %}
<!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" href="{% static 'style.css' %}" /> <title>{% block title %}個人站點{% endblock %}</title> </head>  <body> <div id="sidebar"> {% block sidebar %} <ul> <li><a href="/">首頁</a></li> <li><a href="/blog/">博客</a></li> </ul> {% endblock %} </div> <div id="content"> {% block content %}{% endblock %} </div> </body> </html>

這個模版,咱們取名叫作base.html,定義好一個簡單的html骨架,而後定義好兩個block接口,讓子模版來根據具體需求來實現。子模板而後經過extends標籤來實現,示例代碼以下:

{% extends "base.html" %}
 {% block title %}博客列表{% endblock %}  {% block content %} {% for entry in blog_entries %} <h2>{{ entry.title }}</h2> <p>{{ entry.body }}</p> {% endfor %} {% endblock %}

須要注意的是:extends標籤必須放在模版的第開始的位置 子模板中的代碼必須放在block中,不然將不會被渲染。 若是在某個block中須要使用父模版的內容,那麼能夠使用{{block.super}}來繼承。好比上例,{%block title%},若是想要使用父模版的title,那麼能夠在子模版的title block中使用{{ block.super }}來實現。

在定義block的時候,除了在block開始的地方定義這個block的名字,還能夠在block結束的時候定義名字。好比{% block title %}{% endblock title %}。這在大型模版中顯得尤爲有用,能讓你快速的看到block包含在哪裏。

加載靜態文件

在一個網頁中,不只僅只有一個html骨架,還須要css樣式文件,js執行文件以及一些圖片等。所以在DTL中加載靜態文件是一個必需要解決的問題。在DTL中,使用static標籤來加載靜態文件。要使用static標籤,首先須要{% load static %}。加載靜態文件的步驟以下:

  1. 首先確保django.contrib.staticfiles已經添加到settings.INSTALLED_APPS中,建立時已經默認添加好了。

  2. 確保在settings.py中設置了STATIC_URLSTATIC_URL=\static\

  3. 在已經安裝了的app下建立一個文件夾叫作static,而後再在這個static文件夾下建立一個當前app的名字的文件夾,再把靜態文件放到這個文件夾下。例如你的app叫作book,有一個靜態文件叫作zhiliao.jpg,那麼路徑爲book/static/book/zhiliao.jpg。(爲何在app下建立一個static文件夾,還須要在這個static下建立一個同app名字的文件夾呢?緣由是若是直接把靜態文件放在static文件夾下,那麼在模版加載靜態文件的時候就是使用zhiliao.jpg,若是在多個app之間有同名的靜態文件,這時候可能就會產生混淆。而在static文件夾下加了一個同名app文件夾,在模版中加載的時候就是使用<img src="{% static 'app/zhiliao.jpg' %}">,這樣就能夠避免產生混淆。)

  4. 若是有一些靜態文件是不和任何app掛鉤的。那麼能夠在settings.py中添加STATICFILES_DIRS,之後DTL就會在這個列表的路徑中查找靜態文件。那麼查找的有順序就變爲,先在本身的app裏找,找不到就會這個目錄下去找。好比能夠設置爲:

STATICFILES_DIRS = [ os.path.join(BASE_DIR,"static") ]
  1. 在模版中使用load標籤加載static標籤。好比要加載在項目的static文件夾下的style.css的文件。那麼示例代碼以下:
{% load static %}
<link rel="stylesheet" href="{% static 'style.css' %}">
  1. 若是不想每次在模版中加載靜態文件都使用load加載static標籤,那麼能夠在settings.py中的TEMPLATES/OPTIONS添加'builtins':['django.templatetags.static'],這樣之後在模版中就能夠直接使用static標籤,變成一個內置的標籤,而不用手動的load了。

  2. 若是沒有在settings.INSTALLED_APPS中添加django.contrib.staticfiles。那麼咱們就須要手動的將請求靜態文件的url與靜態文件的路徑進行映射了。示例代碼以下:

from django.conf import settings from django.conf.urls.static import static  urlpatterns = [ # 其餘的url映射 ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

數據庫操做和ORM模型

基本配置

  1. 安裝 pip install pymysql

  2. 在Django的settings.py中設置數據庫的配置

    DATABASES = {  'default': {  'ENGINE': 'django.db.backends.mysql',  'NAME': 'book',  'USER': 'root',  'PASSWORD': '123',  'HOST': '192.168.17.88',  'PORT': '3306'  } }

    app配置單獨的數據庫

    DATABASES = {  'default': {  'ENGINE': 'django.db.backends.mysql',  'NAME':'bms',      # 要鏈接的數據庫,鏈接前須要建立好  'USER':'root',       # 鏈接數據庫的用戶名  'PASSWORD':'',       # 鏈接數據庫的密碼  'HOST':'127.0.0.1', # 鏈接主機,默認本級  'PORT'3306     # 端口 默認3306  } 'app01': { #能夠爲每一個app都配置本身的數據,而且數據庫還能夠指定別的,也就是不必定就是mysql,也能夠指定sqlite等其餘的數據庫  'ENGINE': 'django.db.backends.mysql',  'NAME':'bms',      # 要鏈接的數據庫,鏈接前須要建立好  'USER':'root',       # 鏈接數據庫的用戶名  'PASSWORD':'',       # 鏈接數據庫的密碼  'HOST':'127.0.0.1', # 鏈接主機,默認本級  'PORT'3306     # 端口 默認3306  } } 
  3. 在主目錄下的__init__.py中添加代碼以下:

    import pymysql pymysql.install_as_MySQLdb()
  4. 若是想打印orm轉換過程當中的sql,須要在settings中進行以下配置:

    LOGGING = {  'version': 1,  'disable_existing_loggers': False,  'handlers': {  'console':{  'level':'DEBUG',  'class':'logging.StreamHandler',  },  },  'loggers': {  'django.db.backends': {  'handlers': ['console'],  'propagate': True,  'level':'DEBUG',  },  } }

使用原生sql語句

  1. 從django.db中導入connection包

  2. 生成一個遊標對象 cursor=connection.cursor()

  3. 進行經常使用的操做:

    cursor.execute("原生sql語句")

    ret = cursor.fetchall()拿到查詢後的結果

    ret = cursor.fetchone()拿到查詢後單個的結果

    from app01 import models  def add_book(request):  '''  添加表記錄  :param request: http請求信息  :return:  '''  book_obj = models.Book(title='python',price=123,pub_date='2012-12-12',publish='人民出版社')  book_obj.save()  from django.db import connection #經過這種方式也能查看執行的sql語句  print(connection.queries)  return HttpResponse('ok')

建立ORM模型

ORM模型通常都是放在appmodels.py文件中。每一個app均可以擁有本身的模型。而且若是這個模型想要映射到數據庫中,那麼這個app必需要放在settings.pyINSTALLED_APP中進行安裝。如下是寫一個簡單的書籍ORM模型。示例代碼以下:

from django.db import models class Book(models.Model):  name = models.CharField(max_length=20,null=False)  author = models.CharField(max_length=20,null=False)  pub_time = models.DateTimeField(default=datetime.now)  price = models.FloatField(default=0)

以上便定義了一個模型。這個模型繼承自django.db.models.Model,若是這個模型想要映射到數據庫中,就必須繼承自這個類。這個模型之後映射到數據庫中,表名是模型名稱的小寫形式,爲book。在這個表中,有四個字段,一個爲name,這個字段是保存的是書的名稱,是varchar類型,最長不能超過20個字符,而且不能爲空。第二個字段是做者名字類型,一樣也是varchar類型,長度不能超過20個。第三個是出版時間,數據類型是datetime類型,默認是保存這本書籍的時間。第五個是這本書的價格,是浮點類型。

還有一個字段咱們沒有寫,就是主鍵id,在django中,若是一個模型沒有定義主鍵,那麼將會自動生成一個自動增加的int類型的主鍵,而且這個主鍵的名字就叫作id

若是是自定義的主鍵,好比id,給他設置了primary_key,就會也增長自增加的屬性。

映射模型到數據庫中

ORM模型映射到數據庫中,總結起來就是如下幾步:

  1. settings.py中,配置好DATABASES,作好數據庫相關的配置。
  2. app中的models.py中定義好模型,這個模型必須繼承自django.db.models
  3. 將這個app添加到settings.pyINSTALLED_APP中。
  4. 在命令行終端,進入到項目所在的路徑,而後執行命令python manage.py makemigrations來生成遷移腳本文件。
  5. 一樣在命令行中,執行命令python manage.py migrate來將遷移腳本文件映射到數據庫中。

migrate怎麼判斷哪些遷移腳本須要執行

他會將代碼中的遷移腳本和數據庫中django_migrations中的遷移腳本進行對比,若是發現數據庫中,沒有這個遷移腳本,那麼就會執行這個遷移腳本。

migrate作了什麼事情:

  1. 將相關的遷移腳本翻譯成SQL語句,在數據庫中執行這個SQL語句。
  2. 若是這個SQL語句執行沒有問題,那麼就會將這個遷移腳本的名字記錄到django_migrations中。

執行migrate命令的時候報錯的解決辦法

緣由:

​ 執行migrate命令會報錯的緣由是。數據庫的django_migrations表中的遷移版本記錄和代碼中的遷移腳本不一 致致使的。

錯誤信息:django.db.utils.InternalError: (1050, "Table 'django_content_type' already exists")

解決辦法

使用—fake參數

首先對比數據庫中的遷移腳本和代碼中的遷移腳本。而後找到哪一個不一樣,以後再使用--fake,將代碼中的遷移腳本添加到django_migrations中,可是並不會執行sql語句。這樣就能夠避免每次執行migrate的時候,都執行一些重複的遷移腳本。

執行python manage.py migrate --fake

再次執行python manage.py migrate

終極解決方案

若是代碼中的遷移腳本和數據庫中的遷移腳本實在太多,就是搞不清了。那麼這時候就能夠使用如下終極解決方案:

  1. 終極解決方案原理:就是將以前的那些遷移腳本都不用了。從新來過。要將出問題的app下的全部模型和數據庫中表保持一致,從新映射。
  2. 將出問題的app下的全部模型,都和數據庫中的表保持一致。
  3. 將出問題的app下的全部遷移腳本文件都刪掉。再在django_migrations表中將出問題的app相關的遷移記錄都刪掉。
  4. 使用makemigrations,從新將模型生成一個遷移腳本。
  5. 使用migrate --fake-initial參數,將剛剛生成的遷移腳本,標記爲已經完成(由於這些模型相對應的表,其實都已經在數據庫中存在了,不須要重複執行了。)
  6. 能夠作其餘的映射了。

django項目中,執行python manage migrate時 報錯WARNINGS: ?: (mysql.W002) MySQL Strict Mode is not set for data

django項目中,執行python manage migrate

解決

在settings中,在DATABASES變量定義處下面添加

DATABASES['OPTIONS']['init_command'] = "SET sql_mode='STRICT_TRANS_TABLES'"

或者在DATABASES變量定義時,添加上面命令中所示的鍵值對。

這種狀況出現的緣由是django版本的差別引發的,使用的是django1.9,而Django1.10後添加了一個新特性,就是在migrate的時候會檢查歷史一致性,就像github在push前會檢查新舊版本之間是否有衝突

navie時間和aware時間:

什麼是navie時間?什麼是aware時間?

  1. navie時間:不知道本身的時間表示的是哪一個時區的。也就是不知道本身幾斤幾兩。比較幼稚。
  2. aware時間:知道本身的時間表示的是哪一個時區的。也就是比較清醒。也就是說數據庫存的時候要存這種類型的時間。再轉換成其餘時區的時間就比較好轉了。

navie和aware介紹以及在django中的用法

https://docs.djangoproject.com/en/2.0/topics/i18n/timezones/EmailField:

pytz庫

專門用來處理時區的庫。這個庫會常常更新一些時區的數據,不須要咱們擔憂。而且這個庫在安裝Django的時候會默認的安裝。若是沒有安裝,那麼能夠經過pip install pytz的方式進行安裝。

astimezone方法

將一個時區的時間轉換爲另一個時區的時間。這個方法只能被aware類型的時間調用。不能被navie類型的時間調用。 示例代碼以下:

import pytz from datetime import datetime now = datetime.now() # 這是一個navie類型的時間 utc_timezone = pytz.timezone("UTC") # 定義UTC的時區對象 utc_now = now.astimezone(utc_timezone) # 將當前的時間轉換爲UTC時區的時間 >> ValueError: astimezone() cannot be applied to a naive datetime # 在linux上會拋出一個異常,緣由就是由於navie類型的時間不能調用astimezone方法,在windows和mac上不會報錯,會成功轉換。 now = now.replace(tzinfo=pytz.timezone('Asia/Shanghai')) utc_now = now.astimezone(utc_timezone) # 這時候就能夠正確的轉換。

replace方法

能夠將一個時間的某些屬性進行更改。

django.utils.timezone.now方法

會根據settings.py中是否設置了USE_TZ=True獲取當前的時間。若是設置了,那麼就獲取一個aware類型的UTC時間。若是沒有設置或者設置爲False,那麼就會獲取一個navie類型的時間。

導包:

from django.utils.timezone import now, localtime

使用:

articlee = Article(title='abc', createe_time=now())注意這裏的now和datetime.now()是不同的。

def now():  """  Returns an aware or naive datetime.datetime, depending on settings.USE_TZ.  """  if settings.USE_TZ:  # timeit shows that datetime.now(tz=utc) is 24% slower若是設置了,就會返回一個清醒的時間aware。  return datetime.utcnow().replace(tzinfo=utc)  else:  return datetime.now()

django.utils.timezone.localtime方法

會根據setting.py中的TIME_ZONE來將一個aware類型的時間轉換爲TIME_ZONE指定時區的時間。好比Asia/Shanghai

在模板中使用時,先{% load tz %}, 而後再使用過濾器的方式使用{{ create_time | localtime }}也能夠不使用過濾器,django也會自動的把utc時間轉換成一個本地的時間。

經常使用Field

字段是模型中最重要的內容之一,也是惟一必須的部分。字段在Python中表現爲一個類屬性,體現了數據表中的一個列。請不要使用cleansavedelete等Django內置的模型API名字,防止命名衝突。

字段命名約束:

Django不容許下面兩種字段名:

  1. 與Python關鍵字衝突。這會致使語法錯誤。例如:

class Example(models.Model): pass = models.IntegerField() # 'pass'是Python保留字!

  1. 字段名中不能有兩個以上下劃線在一塊兒,由於兩個下劃線是Django的查詢語法。例如:

class Example(models.Model): foo__bar = models.IntegerField() # 'foo__bar' 有兩個下劃線在一塊兒!

因爲你能夠自定義表名、列名,上面的規則可能被繞開,可是請養成良好的習慣,必定不要那麼起名。

SQL語言的join、where和select等保留字能夠做爲字段名,由於Django對它們都進行了轉義。

經常使用字段類型

字段類型的做用:

  • 決定數據庫中對應列的數據類型(例如:INTEGER, VARCHAR, TEXT)
  • HTML中對應的表單標籤的類型,例如<input type=「text」 />
  • 在admin後臺和自動生成的表單中最小的數據驗證需求

Django內置了許多字段類型,它們都位於django.db.models中,例如models.CharField。這些類型基本知足需求,若是還不夠,你也能夠自定義字段。

下表列出了全部Django內置的字段類型,但不包括關係字段類型(字段名採用駝峯命名法,必定要注意,加粗的是經常使用的):

類型 說明
AutoField 一個自動增長的整數類型字段。一般你不須要本身編寫它,Django會自動幫你添加字段:id = models.AutoField(primary_key=True),這是一個自增字段,從1開始計數。若是你非要本身設置主鍵,那麼請務必將字段設置爲primary_key=True。Django在一個模型中只容許有一個自增字段,而且該字段必須爲主鍵!
BigAutoField (1.10新增)64位整數類型自增字段,數字範圍更大,從1到9223372036854775807
BigIntegerField 64位整數字段(看清楚,非自增),相似IntegerField ,-9223372036854775808 到9223372036854775807。在Django的模板表單裏體現爲一個textinput標籤。
BinaryField 二進制數據類型。使用受限,少用。
BooleanField 布爾值類型。默認值是None。在HTML表單中體現爲CheckboxInput標籤。若是要接收null值,請使用NullBooleanField。
CharField 字符串類型。必須接收一個max_length參數,表示字符串長度不能超過該值。默認的表單標籤是input text。最經常使用的filed,沒有之一!
CommaSeparatedIntegerField 逗號分隔的整數類型。必須接收一個max_length參數。經常使用於表示較大的金額數目,例如1,000,000元。
DateField class DateField(auto_now=False, auto_now_add=False, **options)日期類型。一個Python中的datetime.date的實例。在HTML中表現爲TextInput標籤。在admin後臺中,Django會幫你自動添加一個JS的日曆表和一個「Today」快捷方式,以及附加的日期合法性驗證。兩個重要參數:(參數互斥,不能共存) auto_now:每當對象被保存時將字段設爲當前日期,經常使用於保存最後修改時間。auto_now_add:每當對象被建立時,設爲當前日期,經常使用於保存建立日期(注意,它是不可修改的)。設置上面兩個參數就至關於給field添加了editable=Falseblank=True屬性。若是想具備修改屬性,請用default參數。例子:pub_time = models.DateField(auto_now_add=True),自動添加發布時間。
DateTimeField 日期時間類型。Python的datetime.datetime的實例。與DateField相比就是多了小時、分和秒的顯示,其它功能、參數、用法、默認值等等都同樣。
DecimalField 固定精度的十進制小數。至關於Python的Decimal實例,必須提供兩個指定的參數!參數max_digits:最大的位數,必須大於或等於小數點位數 。decimal_places:小數點位數,精度。 當localize=False時,它在HTML表現爲NumberInput標籤,不然是text類型。例子:儲存最大不超過999,帶有2位小數位精度的數,定義以下:models.DecimalField(..., max_digits=5, decimal_places=2)
DurationField 持續時間類型。存儲必定期間的時間長度。相似Python中的timedelta。在不一樣的數據庫實現中有不一樣的表示方法。經常使用於進行時間之間的加減運算。可是當心了,這裏有坑,PostgreSQL等數據庫之間有兼容性問題!
EmailField 郵箱類型,默認max_length最大長度254位。使用這個字段的好處是,能夠使用DJango內置的EmailValidator進行郵箱地址合法性驗證。
FileField class FileField(upload_to=None, max_length=100, **options)上傳文件類型,後面單獨介紹。
FilePathField 文件路徑類型,後面單獨介紹
FloatField 浮點數類型,參考整數類型
ImageField 圖像類型,後面單獨介紹。
IntegerField 整數類型,最經常使用的字段之一。取值範圍-2147483648到2147483647。在HTML中表現爲NumberInput標籤。
GenericIPAddressField class GenericIPAddressField(protocol='both', unpack_ipv4=False, **options)[source],IPV4或者IPV6地址,字符串形式,例如192.0.2.30或者2a02:42fe::4在HTML中表現爲TextInput標籤。參數protocol默認值爲‘both’,可選‘IPv4’或者‘IPv6’,表示你的IP地址類型。
NullBooleanField 相似布爾字段,只不過額外容許NULL做爲選項之一。
PositiveIntegerField 正整數字段,包含0,最大2147483647。
PositiveSmallIntegerField 較小的正整數字段,從0到32767。
SlugField slug是一個新聞行業的術語。一個slug就是一個某種東西的簡短標籤,包含字母、數字、下劃線或者鏈接線,一般用於URLs中。能夠設置max_length參數,默認爲50。
SmallIntegerField 小整數,包含-32768到32767。
TextField 大量文本內容,在HTML中表現爲Textarea標籤,最經常使用的字段類型之一!若是你爲它設置一個max_length參數,那麼在前端頁面中會受到輸入字符數量限制,然而在模型和數據庫層面卻不受影響。只有CharField才能同時做用於二者。
TimeField 時間字段,Python中datetime.time的實例。接收同DateField同樣的參數,只做用於小時、分和秒。
URLField 一個用於保存URL地址的字符串類型,默認最大長度200。
UUIDField 用於保存通用惟一識別碼(Universally Unique Identifier)的字段。使用Python的UUID類。在PostgreSQL數據庫中保存爲uuid類型,其它數據庫中爲char(32)。這個字段是自增主鍵的最佳替代品,後面有例子展現。
1. FileField:
 def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **kwargs):  self._primary_key_set_explicitly = 'primary_key' in kwargs   self.storage = storage or default_storage  self.upload_to = upload_to   kwargs['max_length'] = kwargs.get('max_length', 100)  super(FileField, self).__init__(verbose_name, name, **kwargs)  ..........

上傳文件字段(不能設置爲主鍵)。默認狀況下,該字段在HTML中表現爲一個ClearableFileInput標籤。在數據庫內,咱們實際保存的是一個字符串類型,默認最大長度100,能夠經過max_length參數自定義。真實的文件是保存在服務器的文件系統內的。

重要參數upload_to用於設置上傳地址的目錄和文件名。以下例所示:

class MyModel(models.Model):  # 文件被傳至`MEDIA_ROOT/uploads`目錄,MEDIA_ROOT由你在settings文件中設置  upload = models.FileField(upload_to='uploads/')  # 或者  # 被傳到`MEDIA_ROOT/uploads/2015/01/30`目錄,增長了一個時間劃分  upload = models.FileField(upload_to='uploads/%Y/%m/%d/')

Django很人性化地幫咱們實現了根據日期生成目錄的方式!

upload_to參數也能夠接收一個回調函數,該函數返回具體的路徑字符串,以下例:

def user_directory_path(instance, filename):  #文件上傳到MEDIA_ROOT/user_<id>/<filename>目錄中  return 'user_{0}/{1}'.format(instance.user.id, filename)  class MyModel(models.Model):  upload = models.FileField(upload_to=user_directory_path)

例子中,user_directory_path這種回調函數,必須接收兩個參數,而後返回一個Unix風格的路徑字符串。參數instace表明一個定義了FileField的模型的實例,說白了就是當前數據記錄。filename是本來的文件名。


2. ImageField
def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):  self.width_field, self.height_field = width_field, height_field  super(ImageField, self).__init__(verbose_name, name, **kwargs)

用於保存圖像文件的字段。其基本用法和特性與FileField同樣,只不過多了兩個屬性height和width。默認狀況下,該字段在HTML中表現爲一個ClearableFileInput標籤。在數據庫內,咱們實際保存的是一個字符串類型,默認最大長度100,能夠經過max_length參數自定義。真實的圖片是保存在服務器的文件系統內的。

height_field參數:保存有圖片高度信息的模型字段名。 width_field參數:保存有圖片寬度信息的模型字段名。

使用Django的ImageField須要提早安裝pillow模塊,pip install pillow便可。

使用FileField或者ImageField字段的步驟:

  1. 在settings文件中,配置MEDIA_ROOT,做爲你上傳文件在服務器中的基本路徑(爲了性能考慮,這些文件不會被儲存在數據庫中)。再配置個MEDIA_URL,做爲公用URL,指向上傳文件的基本路徑。請確保Web服務器的用戶帳號對該目錄具備寫的權限。
  2. 添加FileField或者ImageField字段到你的模型中,定義好upload_to參數,文件最終會放在MEDIA_ROOT目錄的「upload_to」子目錄中。
  3. 全部真正被保存在數據庫中的,只是指向你上傳文件路徑的字符串而已。能夠經過url屬性,在Django的模板中方便的訪問這些文件。例如,假設你有一個ImageField字段,名叫mug_shot,那麼在Django模板的HTML文件中,能夠使用{{ object.mug_shot.url }}來獲取該文件。其中的object用你具體的對象名稱代替。
  4. 能夠經過namesize屬性,獲取文件的名稱和大小信息。

安全建議

不管你如何保存上傳的文件,必定要注意他們的內容和格式,避免安全漏洞!務必對全部的上傳文件進行安全檢查,確保它們不出問題!若是你不加任何檢查就盲目的讓任何人上傳文件到你的服務器文檔根目錄內,好比上傳了一個CGI或者PHP腳本,極可能就會被訪問的用戶執行,這具備致命的危害。


3. FilePathField
    def __init__(self, verbose_name=None, name=None, path='', match=None,  recursive=False, allow_files=True, allow_folders=False, **kwargs):  self.path, self.match, self.recursive = path, match, recursive  self.allow_files, self.allow_folders = allow_files, allow_folders  kwargs['max_length'] = kwargs.get('max_length', 100)  super(FilePathField, self).__init__(verbose_name, name, **kwargs)

一種用來保存文件路徑信息的字段。在數據表內以字符串的形式存在,默認最大長度100,能夠經過max_length參數設置。

它包含有下面的一些參數:

path:必須指定的參數。表示一個系統絕對路徑。

match:可選參數,一個正則表達式,用於過濾文件名。只匹配基本文件名,不匹配路徑。例如foo.*\.txt$,只匹配文件名foo23.txt,不匹配bar.txtfoo23.png

recursive:可選參數,只能是True或者False。默認爲False。決定是否包含子目錄,也就是是否遞歸的意思。

allow_files:可選參數,只能是True或者False。默認爲True。決定是否應該將文件名包括在內。它和allow_folders其中,必須有一個爲True。

allow_folders: 可選參數,只能是True或者False。默認爲False。決定是否應該將目錄名包括在內。

好比:

FilePathField(path="/home/images", match="foo.*", recursive=True)

它只匹配/home/images/foo.png,但不匹配/home/images/foo/bar.png,由於默認狀況,只匹配文件名,而無論路徑是怎麼樣的。


4. UUIDField:

數據庫沒法本身生成uuid,所以須要以下使用default參數:

import uuid     # Python的內置模塊
from django.db import models  class MyUUIDModel(models.Model):  id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)  # 其它字段
5. 自定義字段
class UnsignedIntegerField(models.IntegerField):  def db_type(self, connection):  return 'integer UNSIGNED'

自定義char類型字段:

class FixedCharField(models.Field):  """  自定義的char類型的字段類  """  def __init__(self, max_length, *args, **kwargs):  super().__init__(max_length=max_length, *args, **kwargs)  self.length = max_length   def db_type(self, connection):  """  限定生成數據庫表的字段類型爲char,長度爲length指定的值  """  return 'char(%s)' % self.length   class Class(models.Model):  id = models.AutoField(primary_key=True)  title = models.CharField(max_length=25)  # 使用上面自定義的char類型的字段  cname = FixedCharField(max_length=25)

關係類型字段

1、多對一(ForeignKey)

多對一的關係,一般被稱爲外鍵。外鍵字段類的定義以下:

def __init__(self, to, on_delete=None, related_name=None, related_query_name=None,  limit_choices_to=None, parent_link=False, to_field=None,  db_constraint=True, **kwargs):  .........

外鍵須要兩個位置參數,一個是關聯的模型,另外一個是on_delete選項。實際上,在目前版本中,on_delete選項也能夠不設置,但Django極力反對如此,所以在Django2.0版本後,該選項會設置爲必填。

外鍵要定義在‘多’的一方!

from django.db import models  class Book(models.Model):  publisher = models.ForeignKey(  'Publisher',  on_delete=models.CASCADE,  )  # ...  class Publisher(models.Model):  # ...  pass

上面的例子中,每本書都有一個出版社,出版社能夠出版N本書,因而用一個外鍵字段Publisher表示,並放在Book模型中。注意,此Publisher非彼Publisher模型類,它是一個字段的名稱。在Django的模型定義中,常常出現相似的英文單詞大小寫不一樣,必定要注意區分!

若是要關聯的對象在另一個app中,能夠顯式的指出。下例假設Publisher模型存在於production這個app中,則Book模型的定義以下:

class Book(models.Model):  publisher = models.ForeignKey(  'production.Publisher', # 關鍵在這裏!!  on_delete=models.CASCADE,  )

若是要建立一個遞歸的外鍵,也就是本身關聯本身的的外鍵,使用下面的方法:

models.ForeignKey('self', on_delete=models.CASCADE)

核心在於‘self’這個引用。何時須要本身引用本身的外鍵呢?典型的例子就是評論系統!一條評論能夠被不少人繼續評論,以下所示:

class Comment(models.Model):  title = models.CharField(max_length=128)  text = models.TextField()  parent_comment = models.ForeignKey('self', on_delete=models.CASCADE)  # .....

注意上面的外鍵字段定義的是父評論,而不是子評論。爲何呢?由於外鍵要放在‘多’的一方!

在實際的數據庫後臺,Django會爲每個外鍵添加_id後綴,並以此建立數據表裏的一列。在上面的出版社與書的例子中,Book模型對應的數據表中,會有一列叫作publisher_id。但實際上,在Django代碼中你不須要使用這個列名,除非你書寫原生的SQL語句,通常咱們都直接使用字段名publisher

關係字段的定義還有個小坑。在後面咱們會講到的verbose_name參數用於設置字段的別名。不少狀況下,爲了方便,咱們都會設置這麼個值,而且做爲字段的第一位置參數。可是對於關係字段,其第一位置參數永遠是關係對象,不能是verbose_name,必定要注意!

參數說明:

外鍵還有一些重要的參數,說明以下:

on_delete

注意:這個參數在Django2.0以後,不能夠省略了,須要顯式的指定!這也是除了路由編寫方式外,Django2和Django1.x最大的不一樣點之一!

當一個被外鍵關聯的對象被刪除時,Django將模仿on_delete參數定義的SQL約束執行相應操做。好比,你有一個可爲空的外鍵,而且你想讓它在關聯的對象被刪除時,自動設爲null,能夠以下定義:

user = models.ForeignKey(  User,  models.SET_NULL,  blank=True,  null=True, )

該參數可選的值都內置在django.db.models中,包括:

  1. CASCADE:級聯操做。若是外鍵對應的那條數據被刪除了,那麼這條數據也會被刪除。

  2. PROTECT:受保護。即只要這條數據引用了外鍵的那條數據,那麼就不能刪除外鍵的那條數據。

  3. SET_NULL:設置爲空。若是外鍵的那條數據被刪除了,那麼在本條數據上就將這個字段設置爲空null=True。若是設置這個選項,前提是要指定這個字段能夠爲空。

  4. SET_DEFAULT:設置默認值。若是外鍵的那條數據被刪除了,那麼本條數據上就將這個字段設置爲默認值。若是設置這個選項,前提是要指定這個字段一個默認值。category = models.ForeignKey("Category",on_delete=models.SET_DEFAULT,default=Category.obgects.get(pk=1))

  5. SET():若是外鍵的那條數據被刪除了。那麼將會獲取SET函數中的值來做爲這個外鍵的值。SET函數能夠接收一個能夠調用的對象(好比函數或者方法),若是是能夠調用的對象,那麼會將這個對象調用後的結果做爲值返回回去。category = models.ForeignKey("Category",on_delete=models.SET(Category.obgects.get(pk=1)))

    SET(函數)

    def defaule_category():  return Category.obgects.get(pk=1)  .... category = models.ForeignKey("Category",on_delete=models.SET(defaule_category) 
  6. DO_NOTHING:不採起任何行爲。一切全看數據庫級別的約束。

以上這些選項只是Django級別的,數據級別依舊是RESTRICT!

limit_choices_to

該參數用於限制外鍵所能關聯的對象,只能用於DjangoModelForm(Django的表單模塊)和admin後臺,對其它場合無限制功能。其值能夠是一個字典、Q對象或者一個返回字典或Q對象的函數調用,以下例所示:

staff_member = models.ForeignKey(  User,  on_delete=models.CASCADE,  limit_choices_to={'is_staff': True}, )

能夠參考下面的方式,使用函數調用:

def limit_pub_date_choices():  return {'pub_date__lte': datetime.date.utcnow()}  # ... limit_choices_to = limit_pub_date_choices # ...

又好比有這麼一個例子:

在使用Django Admin後臺時,有時候想自定義某一字段的Choice_field,例如屏蔽某些選項,只顯示某些指定的選項。

想象這樣的應用場景,我有一個網站,導航欄是這樣的:

點開「技術雜談」後,顯示成這樣:

在這裏,我在後臺設計model時,將「技術雜談」這種顯示在導航欄的分類定義成一級分類,將「C/C++」、「Python」這種隱藏在摺疊欄中的分類定義成二級分類,二級分類有一個字段存儲有連接到對應的父分類的id號。

但這裏出現了一個問題,我在後臺建立文章時分類選項卡中顯示成了這樣:

一級分類和二級分類混雜到了一塊兒,而我實際上只但願它顯示二級分類。由於當選擇「C/C++」、「python」這些二級分類時,會自動歸類到對應的一級分類之中,因此不必在選項卡里顯示一級分類。

……

那麼問題來了,如何在文章的分類選項卡中屏蔽掉一級分類呢?

咱們須要修改文章的Model類,使用 ForeignKey.limit_choices_to 限制分類顯示的內容,只顯示符合條件的選項。

舉個簡單的例子,首先建立一個文章類:

class Article(models.Model):  title = models.CharField(max_length=50,verbose_name='文章標題')  content = models.TextField(verbose_name='文章內容')  category = models.ForeignKey(Category,blank=True,null=True,verbose_name='分類')

能夠看出,category字段導入了外鍵Category。咱們在ForeignKey函數中設置limit_choices_to參數:

class Article(models.Model):  title = models.CharField(max_length=50,verbose_name='文章標題')  content = models.TextField(verbose_name='文章內容')  category = models.ForeignKey(Category,blank=True,null=True,verbose_name='分類',limit_choices_to={'level':2})

limit_choices_to={'level':2}的意思是隻顯示分類等級爲2的條目,即只顯示二級分類。'level'是我在Category中定義的字段,用來表明該分類的等級,通常只有一級和二級。

總的來講,limit_choices_to的做用是設置篩選條件,在admin中只顯示篩選後的內容。

設置好這個參數,Django在後臺就知道你要選擇顯示的內容了。

如此,在後臺建立文章時就會發現分類表單中只有兩個選項。

你也能夠根據你的須要,顯示你想要顯示的選項。固然對其它字段自定義Choice_field也是同樣的道理。

related_name

用於關聯對象反向引用模型的名稱。之前面書和出版社的例子解釋,就是從出版社反向關聯到書的關係名稱。

一般狀況下,這個參數咱們能夠不設置,Django會默認以模型的小寫加上_set做爲反向關聯名,好比對於出版社就是book_set,若是你以爲book_set還不夠直觀,能夠以下定義:

class Book(models.Model):  publisher = models.ForeignKey(  'production.Publisher',  on_delete=models.CASCADE,  related_name='book_producted_by_this_publisher', # 看這裏!!  )

也許我定義了一個蹩腳的詞,但表達的意思很清楚。之後從工廠對象反向關聯到它所生產的汽車,就能夠使用publisher.book_producted_by_this_publisher了。

若是你不想爲外鍵設置一個反向關聯名稱,能夠將這個參數設置爲「+」或者以「+」結尾,以下所示:

user = models.ForeignKey(  User,  on_delete=models.CASCADE,  related_name='+', )

related_query_name

反向關聯查詢名。用於從目標模型反向過濾模型對象的名稱

class Tag(models.Model):  article = models.ForeignKey(  Article,  on_delete=models.CASCADE,  related_name="tags",  related_query_name="tag", # 注意這一行  )  name = models.CharField(max_length=255)  # 如今能夠使用‘tag’做爲查詢名了 Article.objects.filter(tag__name="important")

to_field

默認狀況下,外鍵都是關聯到被關聯對象的主鍵上(通常爲id)。若是指定這個參數,能夠關聯到指定的字段上,可是該字段必須具備unique=True屬性,也就是具備惟一屬性。

db_constraint

默認狀況下,這個參數被設爲True,表示遵循數據庫約束,這也是大多數狀況下你的選擇。若是設爲False,那麼將沒法保證數據的完整性和合法性。在下面的場景中,你可能須要將它設置爲False:

  • 有歷史遺留的不合法數據,沒辦法的選擇
  • 你正在分割數據表

當它爲False,而且你試圖訪問一個不存在的關係對象時,會拋出DoesNotExist 異常。

swappable

控制遷移框架的動做,若是當前外鍵指向一個可交換的模型。使用場景很是稀少,一般請將該參數保持默認的True。


2、多對多(ManyToManyField)

多對多關係在數據庫中也是很是常見的關係類型。好比一本書能夠有好幾個做者,一個做者也能夠寫好幾本書。多對多的字段能夠定義在任何的一方,請儘可能定義在符合人們思惟習慣的一方,但不要同時都定義。

def __init__(self, to, related_name=None, related_query_name=None,  limit_choices_to=None, symmetrical=None, through=None,  through_fields=None, db_constraint=True, db_table=None,  swappable=True, **kwargs):  ........

多對多關係須要一個位置參數:關聯的對象模型。它的用法和外鍵多對一基本相似。

在數據庫後臺,Django實際上會額外建立一張用於體現多對多關係的中間表。默認狀況下,該表的名稱是「多對多字段名+關聯對象模型名+一個獨一無二的哈希碼」,例如‘author_books_9cdf4’,固然你也能夠經過db_table選項,自定義表名。

參數說明:

related_name

參考外鍵的相同參數。

related_query_name

參考外鍵的相同參數。

limit_choices_to

參考外鍵的相同參數。可是對於使用through參數自定義中間表的多對多字段無效。

symmetrical

默認狀況下,Django中的多對多關係是對稱的。看下面的例子:

from django.db import models  class Person(models.Model):  friends = models.ManyToManyField("self")

Django認爲,若是我是你的朋友,那麼你也是個人朋友,這是一種對稱關係,Django不會爲Person模型添加person_set屬性用於反向關聯。若是你不想使用這種對稱關係,能夠將symmetrical設置爲False,這將強制Django爲反向關聯添加描述符。

through(定義中間表)

若是你想自定義多對多關係的那張額外的關聯表,能夠使用這個參數!參數的值爲一箇中間模型。

最多見的使用場景是你須要爲多對多關係添加額外的數據,好比兩我的創建QQ好友的時間。

一般狀況下,這張表在數據庫內的結構是這個樣子的:

中間表的id....模型對象的id.....被關聯對象的id# 各行數據

若是自定義中間表並添加時間字段,則在數據庫內的表結構以下:

中間表的id....模型對象的id.....被關聯對象的id.....時間對象列 # 各行數據

看下面的例子:

from django.db import models  class Person(models.Model):  name = models.CharField(max_length=50)  class Group(models.Model):  name = models.CharField(max_length=128)  members = models.ManyToManyField(  Person,  through='Membership', ## 自定義中間表  through_fields=('group', 'person'),  )  class Membership(models.Model): # 這就是具體的中間表模型  group = models.ForeignKey(Group, on_delete=models.CASCADE)  person = models.ForeignKey(Person, on_delete=models.CASCADE)  inviter = models.ForeignKey(  Person,  on_delete=models.CASCADE,  related_name="membership_invites",  )  invite_reason = models.CharField(max_length=64)

上面的代碼中,經過class Membership(models.Model)定義了一個新的模型,用來保存Person和Group模型的多對多關係,而且同時增長了‘邀請人’和‘邀請緣由’的字段。

through參數在某些使用場景中是必須的,相當重要,請務必掌握!

through_fields

接着上面的例子。Membership模型中包含兩個關聯Person的外鍵,Django沒法肯定到底使用哪一個做爲和Group關聯的對象。因此,在這個例子中,必須顯式的指定through_fields參數,用於定義關係。

through_fields參數接收一個二元元組('field1', 'field2'),field1是指向定義有多對多關係的模型的外鍵字段的名稱,這裏是Membership中的‘group’字段(注意大小寫),另一個則是指向目標模型的外鍵字段的名稱,這裏是Membership中的‘person’,而不是‘inviter’。

再通俗的說,就是through_fields參數指定從中間表模型Membership中選擇哪兩個字段,做爲關係鏈接字段。

db_table

設置中間表的名稱。不指定的話,則使用默認值。

db_constraint

參考外鍵的相同參數。

swappable

參考外鍵的相同參數。

ManyToManyField多對多字段不支持Django內置的validators驗證功能。

null參數對ManyToManyField多對多字段無效!設置null=True毫無心義


3、一對一(OneToOneField)

一對一關係類型的定義以下:

def __init__(self, to, on_delete=None, to_field=None, **kwargs):  kwargs['unique'] = True  .........

從概念上講,一對一關係很是相似具備unique=True屬性的外鍵關係,可是反向關聯對象只有一個。這種關係類型多數用於當一個模型須要從別的模型擴展而來的狀況。好比,Django自帶auth模塊的User用戶表,若是你想在本身的項目裏建立用戶模型,又想方便的使用Django的認證功能,那麼一個比較好的方案就是在你的用戶模型裏,使用一對一關係,添加一個與auth模塊User模型的關聯字段。

該關係的第一位置參數爲關聯的模型,其用法和前面的多對一外鍵同樣。

若是你沒有給一對一關係設置related_name參數,Django將使用當前模型的小寫名做爲默認值。

看下面的例子:

from django.conf import settings from django.db import models  # 兩個字段都使用一對一關聯到了Django內置的auth模塊中的User模型 class MySpecialUser(models.Model):  user = models.OneToOneField(  settings.AUTH_USER_MODEL,  on_delete=models.CASCADE,  )  supervisor = models.OneToOneField(  settings.AUTH_USER_MODEL,  on_delete=models.CASCADE,  related_name='supervisor_of',  )

這樣下來,你的User模型將擁有下面的屬性:

>>> user = User.objects.get(pk=1) >>> hasattr(user, 'myspecialuser') True >>> hasattr(user, 'supervisor_of') True

OneToOneField一對一關係擁有和多對一外鍵關係同樣的額外可選參數,只是多了一個parent_link參數。

跨模塊的模型:

有時候,咱們關聯的模型並不在當前模型的文件內,不要緊,就像咱們導入第三方庫同樣的從別的模塊內導入進來就好,以下例所示:

from django.db import models from geography.models import ZipCode  class Restaurant(models.Model):  # ...  zip_code = models.ForeignKey(  ZipCode,  on_delete=models.SET_NULL,  blank=True,  null=True,  )

ORM對數據庫的基本操做

1、單表操做

添加數據

方式一:

只要使用ORM模型建立一個對象。而後再調用這個ORM模型的save方法就能夠保存了。 示例代碼以下:

book = Book(name='西遊記',author='吳承恩',price=100) book.save()

方式二:

# create方法的返回值book_obj就是插入book表中的python葵花寶典這本書籍紀錄對象  book_obj=Book.objects.create(title="python葵花寶典",state=True,price=100,publish="蘋果出版社",pub_date="2012-12-12") #這個返回值就像是mysql裏面我們講的那個new對象,還記得嗎,他跟上面那種建立方式建立的那個對象是同樣的  #這個Book.objects就像是一個Book表的管理器同樣,提供了增刪改查全部的方法  print(book_obj.title) #能夠基於這個對象來取這個新添加的記錄對象的屬性值  dic1 = {'title':'linux','state'=True,'price':100,'publish'='2018-12-12'} #這樣寫的時候,注意若是你用post提交過來的請求,有個csrf_token的鍵值對要刪除,而且request.POST是不能直接在request.POST裏面進行修改和刪除的,data = request.POST.dict()轉換成普通的字典-->Book.objects.create(**data)  book.objects.create(**dic1)

方式三:批量插入

book_list = []  for i in range(10):  bk_obj = models.Book(  name='chao%s'%i,  addr='北京%s'%i  )  book_list.append(bk_obj)  models.Book.objects.bulk_create(book_list) #批量插入,速度快

update_or_create:有就更新,沒有就建立

obj,created = models.UserToken.objects.update_or_create(  user=user, # 查找篩選條件  defaults={ # 添加或者更新的數據       "token":random_str,     }  ) 

查找數據

全部的查找工做都是使用模型上的objects屬性來完成的。固然也能夠自定義查詢對象。這部分功能會在後面講到。

  1. 根據主鍵進行查找:使用主鍵進行查找。能夠使用objects.get方法。而後傳遞pk=xx的方式進行查找。示例代碼以下:

    book = Book.objects.get(pk=2)
  2. 根據其餘字段進行查找:能夠使用objects.filter方法進行查找。示例代碼以下:

    books = Book.objects.filter(name='三國演義')

    使用filter方法返回來的是一個QuerySet對象。這個對象相似於列表。咱們能夠使用這個對象的first方法來獲取第一個值。

刪除數據

首先查找到對應的數據模型。而後再執行這個模型的delete方法便可刪除。示例代碼以下:

book = Book.objects.get(pk=1) book.delete()

修改數據

首先查找到對應的數據模型。而後修改這個模型上的屬性的值。再執行save方法便可修改完成。示例代碼以下:

    book = Book.objects.get(pk=2)  book.price = 200  book.save()

2、多表操做

一對多

添加記錄

方式1:  publish_obj=Publish.objects.get(nid=1) #拿到nid爲1的出版社對象  book_obj=Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=100,publish=publish_obj) #出版社對象做爲值給publish,其實就是自動將publish字段變成publish_id,而後將publish_obj的id給取出來賦值給publish_id字段,注意你若是不是publish類的對象確定會報錯的,別亂昂  方式2:  book_obj=Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=100,publish_id=1)  #直接能夠寫id值,注意字段屬性的寫法和上面不一樣,這個是publish_id=xxx,上面是publish=xxx。

**注意區別:book_obj.publish與book_obj.publish_id是什麼? **

刪除更新記錄

一對一和一對多的刪改和單表的刪改是同樣的,別忘了刪除表的時候,有些是作了級聯刪除的。

更新: book_obj = models.Book.objects.get(id=1) #獲取一個書籍對象 data = {'title':'xxx','price':100} #這個書籍對象更新後的數據 models.Book.objects.filter(id=n).update(**data) #將新數據更新到原來的記錄中 book_obj.authors.set(author_list) #將數據和做者的多對多關係加上,這麼寫也能夠,可是注意列表中的元素是字符串,列表前面沒有*,以前我測試有*,感受是版本的問題,沒事,可以用哪一個用哪一個  刪除: models.Book.objects.filter(id=1).delete()
多對多

添加

方式一: 多對多通常在前端頁面上使用的時候是多選下拉框的樣子來給用戶選擇多個數據,這裏可讓用戶選擇多個書籍,多個做者   # 當前生成的書籍對象  book_obj=Book.objects.create(title="追風箏的人",price=200,publishDate="2012-11-12",publish_id=1)  # 爲書籍綁定的作做者對象  yuan=Author.objects.filter(name="yuan").first() # 在Author表中主鍵爲2的紀錄,注意取的是author的model對象  egon=Author.objects.filter(name="alex").first() # 在Author表中主鍵爲1的紀錄   #有人可能會說,咱們能夠直接給第三張表添加數據啊,這個自動生成的第三張表你能經過models獲取到嗎,是獲取不到的,用不了的,固然若是你知道了這個表的名字,那麼你經過原生sql語句能夠進行書的添加,因此要經過orm間接的給第三張表添加數據,若是是你手動添加的第三張表你是能夠直接給第三張表添加數據  # 綁定多對多關係,即向關係表book_authors中添加紀錄,給書添加兩個做者,下面的語法就是告訴orm給第三張表添加兩條數據  book_obj.authors.add(yuan,egon) # 將某些特定的 model 對象添加到被關聯對象集合中。 ======= book_obj.authors.add(*[])  #book_obj是書籍對象,authors是book表裏面那個多對多的關係字段名稱。  #其實orm就是先經過book_obj的authors屬性找到第三張表,而後將book_obj的id值和兩個做者對象的id值組合成兩條記錄添加到第三張表裏面去   方式二     book_obj.authors.add(12)     book_obj.authors.add(*[12]) #這種方式用的最多,由於通常是給用戶來選擇,用戶選擇是多選的,選完給你發送過來的就是一堆的id值 

理解book_obj.authors.all()是什麼

多對多關係其它經常使用API:

book_obj.authors.remove() # 將某個特定的對象從被關聯對象集合中去除。 ====== book_obj.authors.remove(*[1,2]),將多對多的關係數據刪除 book_obj.authors.clear() #清空被關聯對象集合 book_obj.authors.set() #先清空再設置   ===== 

刪除

book_obj = models.Book.objects.filter(nid=4)[0]  # book_obj.authors.remove(2) #將第三張表中的這個book_obj對象對應的那個做者id爲2的那條記錄刪除  # book_obj.authors.clear()  # book_obj.authors.set('2') #先清除掉全部的關係數據,而後只給這個書對象綁定這個id爲2的做者,因此只剩下一條記錄 3---2,好比用戶編輯數據的時候,選擇做者發生了變化,那麼須要從新選擇,因此咱們就能夠先清空,而後再從新綁定關係數據,注意這裏寫的是字符串,數字類型不能夠  book_obj.authors.set(['1',]) #這麼寫也能夠,可是注意列表中的元素是字符串,列表前面沒有*,以前我測試有*,感受是版本的問題,沒事,可以用哪一個用哪一個 
基於對象的跨表查詢

跨表查詢是分組查詢的基礎,F和Q查詢是最簡單的,因此認真學習跨表查詢

一對多查詢(Publish 與 Book)

正向查詢(按字段:publish):關聯屬性字段所在的表查詢被關聯表的記錄就是正向查詢,反之就是反向查詢

# 查詢主鍵爲1的書籍的出版社所在的城市 book_obj=Book.objects.filter(pk=1).first() # book_obj.publish 是主鍵爲1的書籍對象關聯的出版社對象,book對象.外鍵字段名稱 print(book_obj.publish.city)  

反向查詢(按表名:book_set,由於加上_set是由於反向查詢的時候,你查詢出來的多是多條記錄的集合):

publish=Publish.objects.get(name="蘋果出版社") #publish.book_set.all() : 與蘋果出版社關聯的全部書籍對象集合,寫法:小寫的表名_set.all(),獲得queryset類型數據 book_list=publish.book_set.all() for book_obj in book_list:  print(book_obj.title)

**一對一查詢(Author與AuthorDetail) **

正向查詢(按字段:authorDetail):

egon=Author.objects.filter(name="egon").first() print(egon.authorDetail.telephone) egon.authorDeail就拿到了這個對象,由於一對一找到的就是一條記錄,注意寫法:做者對象.字段名,就拿到了那個關聯對象

反向查詢(按表名:author):不須要_set,由於一對一正向反向都是找到一條記錄

# 查詢全部住址在北京的做者的姓名  authorDet=AuthorDetail.objects.filter(addr="beijing")[0] authorDet.author.name

多對多查詢(Author與Book)

正向查詢(按字段:authors):

# 金瓶眉全部做者的名字以及手機號  book_obj=Book.objects.filter(title="金瓶眉").first() authors=book_obj.authors.all() for author_obj in authors:  print(author_obj.name,author_obj.authorDetail.telephone)

反向查詢(按表名:book_set):

# 查詢egon出過的全部書籍的名字   author_obj=Author.objects.get(name="egon")  book_list=author_obj.book_set.all() #與egon做者相關的全部書籍  for book_obj in book_list:  print(book_obj.title)

注意:

你能夠經過在 ForeignKey() 和ManyToManyField的定義中設置 related_name 的值來覆寫 FOO_set 的名稱。例如,若是 Article model 中作一下更改:

publish = ForeignKey(Book, related_name ='bookList')

那麼接下來就會如咱們看到這般:

# 查詢 人民出版社出版過的全部書籍  publish=Publish.objects.get(name="人民出版社") book_list=publish.bookList.all() # 與人民出版社關聯的全部書籍對象集合,若是沒有related_name應該是publish.book_set.all()
基於雙下劃線的跨表查詢(基於join實現的)

Django 還提供了一種直觀而高效的方式在查詢(lookups)中表示關聯關係,它能自動確認 SQL JOIN 聯繫。要作跨關係查詢,就使用兩個下劃線來連接模型(model)間關聯字段的名稱,直到最終連接到你想要的model 爲止。

基於雙下劃線的查詢就一句話:正向查詢按字段,反向查詢按表名小寫用來告訴ORM引擎join哪張表,一對1、一對多、多對多都是一個寫法,注意,咱們寫orm查詢的時候,哪一個表在前哪一個表在後都沒問題,由於走的是join連表操做。

一對多查詢

練習: 查詢蘋果出版社出版過的全部書籍的名字與價格(一對多)   # 正向查詢 按字段:publish   queryResult=Book.objects  .filter(publish__name="蘋果出版社") #經過__告訴orm將book表和publish表進行join,而後找到全部記錄中publish.name='蘋果出版社'的記錄(注意publish是屬性名稱),而後select book.title,book.price的字段值  .values_list("title","price") #values或者values_list   # 反向查詢 按表名:book   queryResult=Publish.objects  .filter(name="蘋果出版社")  .values_list("book__title","book__price") 

多對多查詢

# 練習: 查詢yuan出過的全部書籍的名字(多對多)  # 正向查詢 按字段:authors:  queryResult=Book.objects  .filter(authors__name="yuan")  .values_list("title")  # 反向查詢 按表名:book  queryResult=Author.objects  .filter(name="yuan")  .values_list("book__title","book__price") 

一對一查詢

# 查詢yuan的手機號  # 正向查詢 ret=Author.objects.filter(name="yuan").values("authordetail__telephone")  # 反向查詢 ret=AuthorDetail.objects.filter(author__name="yuan").values("telephone") 

進階練習(連續跨表)

# 練習: 查詢人民出版社出版過的全部書籍的名字以及做者的姓名   # 正向查詢 queryResult=Book.objects  .filter(publish__name="人民出版社")  .values_list("title","authors__name") # 反向查詢 queryResult=Publish.objects  .filter(name="人民出版社")  .values_list("book__title","book__authors__age","book__authors__name")   # 練習: 手機號以151開頭的做者出版過的全部書籍名稱以及出版社名稱   # 方式1: queryResult=Book.objects  .filter(authors__authorDetail__telephone__regex="151")  .values_list("title","publish__name") # 方式2: ret=Author.objects  .filter(authordetail__telephone__startswith="151")  .values("book__title","book__publish__name") 

related_name

publish = ForeignKey(Blog, related_name='bookList')

反向查詢時,若是定義了related_name ,則用related_name替換 表名,例如:

# 練習: 查詢人民出版社出版過的全部書籍的名字與價格(一對多)  # 反向查詢 再也不按表名:book,而是related_name:bookList   queryResult=Publish.objects  .filter(name="人民出版社")  .values_list("bookList__title","bookList__price") 

Field經常使用的參數

null

若是設置爲TrueDjango將會在映射表的時候指定是否爲空。默認是爲False。就是不能爲空,在使用字符串相關的Field(CharField/TextField)的時候,官方推薦儘可能不要使用這個參數,也就是保持默認值False。由於Django在處理字符串相關的Field的時候,即便這個Fieldnull=False,若是你沒有給這個Field傳遞任何值,那麼Django也會使用一個空的字符串""來做爲默認值存儲進去。所以若是再使用null=TrueDjango會產生兩種空值的情形(NULL或者空字符串)。若是想要在表單驗證的時候容許這個字符串爲空,那麼建議使用blank=True。若是你的FieldBooleanField,那麼對應的可空的字段則爲NullBooleanField

blank

標識這個字段在表單驗證的時候是否能夠爲空。默認是False。 這個和null是有區別的,null是一個純數據庫級別的。而blank是表單驗證級別的。

db_column

這個字段在數據庫中的名字。若是沒有設置這個參數,那麼將會使用模型中屬性的名字。

default

默認值。能夠爲一個值,或者是一個函數,可是不支持lambda表達式。而且不支持列表/字典/集合等可變的數據結構。

from django.utils.timezone import now class Author(models.Model):  create_time = models.DateTimeFField(default=now())  # time.now()返回的是一個幼稚的時間,而這裏的now返回的是一個清醒的時 間。

primary_key

若是你沒有給模型的任何字段設置這個參數爲True,Django將自動建立一個AutoField自增字段,名爲‘id’,並設置爲主鍵。也就是id = models.AutoField(primary_key=True)

若是你爲某個字段設置了primary_key=True,則當前字段變爲主鍵,並關閉Django自動生成id主鍵的功能。

primary_key=True隱含null=False和unique=True的意思。一個模型中只能有一個主鍵字段!

另外,主鍵字段不可修改,若是你給某個對象的主鍵賦個新值其實是建立一個新對象,並不會修改原來的對象。

from django.db import models class Fruit(models.Model):  name = models.CharField(max_length=100, primary_key=True) ############### >>> fruit = Fruit.objects.create(name='Apple') >>> fruit.name = 'Pear' >>> fruit.save() >>> Fruit.objects.values_list('name', flat=True) ['Apple', 'Pear']

unique

在表中這個字段的值是否惟一。通常是設置手機號碼/郵箱等。

注意:對於ManyToManyField和OneToOneField關係類型,該參數無效。

注意: 當unique=True時,db_index參數無須設置,由於unqiue隱含了索引。

注意:自1.11版本後,unique參數能夠用於FileField字段。

choices

用於頁面上的選擇框標籤,須要先提供一個二維的二元元組,第一個元素表示存在數據庫內真實的值,第二個表示頁面上顯示的具體內容。在瀏覽器頁面上將顯示第二個元素的值。例如:

    YEAR_IN_SCHOOL_CHOICES = (  ('FR', 'Freshman'),  ('SO', 'Sophomore'),  ('JR', 'Junior'),  ('SR', 'Senior'),  ('GR', 'Graduate'),  )

通常來講,最好將選項定義在類裏,並取一個直觀的名字,以下所示:

from django.db import models  class Student(models.Model):  FRESHMAN = 'FR'  SOPHOMORE = 'SO'  JUNIOR = 'JR'  SENIOR = 'SR'  YEAR_IN_SCHOOL_CHOICES = (  (FRESHMAN, 'Freshman'),  (SOPHOMORE, 'Sophomore'),  (JUNIOR, 'Junior'),  (SENIOR, 'Senior'),  )  year_in_school = models.CharField(  max_length=2,  choices=YEAR_IN_SCHOOL_CHOICES,  default=FRESHMAN,  )   def is_upperclass(self):  return self.year_in_school in (self.JUNIOR, self.SENIOR)

要獲取一個choices的第二元素的值,能夠使用get_FOO_display()方法,其中的FOO用字段名代替。對於下面的例子:

from django.db import models  class Person(models.Model):  SHIRT_SIZES = (  ('S', 'Small'),  ('M', 'Medium'),  ('L', 'Large'),  )  name = models.CharField(max_length=60)  shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)

使用方法:

>>> p = Person(name="Fred Flintstone", shirt_size="L") >>> p.save() >>> p.shirt_size 'L' >>> p.get_shirt_size_display() 'Large'

db_index

該參數接收布爾值。若是爲True,數據庫將爲該字段建立索引。

db_tablespace

用於字段索引的數據庫表空間的名字,前提是當前字段設置了索引。默認值爲工程的DEFAULT_INDEX_TABLESPACE設置。若是使用的數據庫不支持表空間,該參數會被忽略。

editable

若是設爲False,那麼當前字段將不會在admin後臺或者其它的ModelForm表單中顯示,同時還會被模型驗證功能跳過。參數默認值爲True。

error_messages

用於自定義錯誤信息。參數接收字典類型的值。字典的鍵能夠是null、 blank、 invalid、 invalid_choice、 uniqueunique_for_date其中的一個。

help_text

額外顯示在表單部件上的幫助文本。使用時請注意轉義爲純文本,防止腳本攻擊。

unique_for_date

日期惟一。可能不太好理解。舉個栗子,若是你有一個名叫title的字段,並設置了參數unique_for_date="pub_date",那麼Django將不容許有兩個模型對象具有一樣的title和pub_date。有點相似聯合約束。

unique_for_month

同上,只是月份惟一。

unique_for_year

同上,只是年份惟一。

verbose_name

爲字段設置一我的類可讀,更加直觀的別名。

對於每個字段類型,除了ForeignKeyManyToManyFieldOneToOneField這三個特殊的關係類型,其第一可選位置參數都是verbose_name。若是沒指定這個參數,Django會利用字段的屬性名自動建立它,並將下劃線轉換爲空格。

下面這個例子的verbose name是"第一個名子":

first_name = models.CharField("第一個名子", max_length=30)

下面這個例子的verbose name是"first name":

first_name = models.CharField(max_length=30)

對於外鍵、多對多和一對一字字段,因爲第一個參數須要用來指定關聯的模型,所以必須用關鍵字參數verbose_name來明確指定。以下:

poll = models.ForeignKey(  Poll,  on_delete=models.CASCADE,  verbose_name="the related poll",  ) sites = models.ManyToManyField(Site, verbose_name="list of sites")  place = models.OneToOneField(  Place,  on_delete=models.CASCADE,  verbose_name="related place", )

另外,你無須大寫verbose_name的首字母,Django自動爲你完成這一工做。

validators

運行在該字段上的驗證器的列表。

關於auto_now,你須要知道的事情

當須要更新時間的時候,咱們儘可能經過datetime模塊來建立當前時間,並保存或者更新到數據庫裏面,看下面的分析: 假如咱們的表結構是這樣的

class User(models.Model):
 username = models.CharField(max_length=255, unique=True, verbose_name='用戶名')  is_active = models.BooleanField(default=False, verbose_name='激活狀態')

那麼咱們修改用戶名和狀態能夠使用以下兩種方法:

方法一:

User.objects.filter(id=1).update(username='nick',is_active=True)

方法二:

_t = User.objects.get(id=1) _t.username='nick' _t.is_active=True _t.save()

方法一適合更新一批數據,相似於mysql語句update user set username='nick' where id = 1

方法二適合更新一條數據,也只能更新一條數據,當只有一條數據更新時推薦使用此方法,另外此方法還有一個好處,咱們接着往下看

具備auto_now屬性字段的更新 咱們一般會給表添加三個默認字段

  • 自增ID,這個django已經默認加了,就像上邊的建表語句,雖然只寫了username和is_active兩個字段,但表建好後也會有一個默認的自增id字段
  • 建立時間,用來標識這條記錄的建立時間,具備auto_now_add屬性,建立記錄時會自動填充當前時間到此字段
  • 修改時間,用來標識這條記錄最後一次的修改時間,具備auto_now屬性,當記錄發生變化時填充當前時間到此字段

就像下邊這樣的表結構

class User(models.Model):  create_time = models.DateTimeField(auto_now_add=True, verbose_name='建立時間')  update_time = models.DateTimeField(auto_now=True, verbose_name='更新時間')  username = models.CharField(max_length=255, unique=True, verbose_name='用戶名')  is_active = models.BooleanField(default=False, verbose_name='激活狀態')

當表有字段具備auto_now屬性且你但願他能自動更新時,必須使用上邊方法二的更新,否則auto_now字段不會更新,也就是:

_t = User.objects.get(id=1) _t.username='nick' _t.is_active=True _t.save()

json/dict類型數據更新字段 目前主流的web開放方式都講究先後端分離,分離以後先後端交互的數據格式大都用通用的jason型,那麼如何用最少的代碼方便的更新json格式數據到數據庫呢?一樣能夠使用以下兩種方法:

方法一:

data = {'username':'nick','is_active':'0'} User.objects.filter(id=1).update(**data)

一樣這種方法不能自動更新具備auto_now屬性字段的值 一般咱們再變量前加一個星號(*)表示這個變量是元組/列表,加兩個星號表示這個參數是字典 方法二:

data = {'username':'nick','is_active':'0'} _t = User.objects.get(id=1) _t.__dict__.update(**data) _t.save()

方法二和方法一一樣沒法自動更新auto_now字段的值 注意這裏使用到了一個__dict__方法 方法三:

_t = User.objects.get(id=1) _t.role=Role.objects.get(id=3) _t.save()

想讓auto_now更新數據時自動更新時間,必須使用save方法來更新數據,因此很不方便,因此這個建立時自動添加時間或者更新時間的auto_now方法咱們最好就別用了,比較噁心,而且支持咱們本身來給這個字段更新時間:

models.py: class Book(models.Model):  name = models.CharField(max_length=32)  date1 = models.DateTimeField(auto_now=True,null=True)  date2 = models.DateTimeField(auto_now_add=True,null=True)  views.py:  import datetime  models.Book.objects.filter(id=1).update(  name='chao',  date1=datetime.datetime.now(),  date2=datetime.datetime.now(),  )

更多Field參數請參考官方文檔:https://docs.djangoproject.com/zh-hans/2.0/ref/models/fields/


多對多中間表

咱們都知道對於ManyToMany字段,Django採用的是第三張中間表的方式。經過這第三張表,來關聯ManyToMany的雙方。下面咱們根據一個具體的例子,詳細解說中間表的使用。

1、默認中間表

首先,模型是這樣的:

class Person(models.Model):  name = models.CharField(max_length=128)   def __str__(self):  return self.name   class Group(models.Model):  name = models.CharField(max_length=128)  members = models.ManyToManyField(Person)   def __str__(self):  return self.name

在Group模型中,經過members字段,以ManyToMany方式與Person模型創建了關係。

讓咱們到數據庫內看一下實際的內容,Django爲咱們建立了三張數據表,其中的app1是應用名。

而後我在數據庫中添加了下面的Person對象:

再添加下面的Group對象:

讓咱們來看看,中間表是個什麼樣子的:

首先有一列id,這是Django默認添加的,沒什麼好說的。而後是Group和Person的id列,這是默認狀況下,Django關聯兩張表的方式。若是你要設置關聯的列,能夠使用to_field參數。

可見在中間表中,並非將兩張表的數據都保存在一塊兒,而是經過id的關聯進行映射。

2、自定義中間表

通常狀況,普通的多對多已經夠用,無需本身建立第三張關係表。可是某些狀況可能更復雜一點,好比若是你想保存某我的加入某個分組的時間呢?想保存進組的緣由呢?

Django提供了一個through參數,用於指定中間模型,你能夠將相似進組時間,邀請緣由等其餘字段放在這個中間模型內。例子以下:

from django.db import models  class Person(models.Model):  name = models.CharField(max_length=128)  def __str__(self):  return self.name  class Group(models.Model):  name = models.CharField(max_length=128)  members = models.ManyToManyField(Person, through='Membership')  def __str__(self):  return self.name  class Membership(models.Model):  person = models.ForeignKey(Person, on_delete=models.CASCADE)  group = models.ForeignKey(Group, on_delete=models.CASCADE)  date_joined = models.DateField() # 進組時間  invite_reason = models.CharField(max_length=64) # 邀請緣由

在中間表中,咱們至少要編寫兩個外鍵字段,分別指向關聯的兩個模型。在本例中就是‘Person’和‘group’。 這裏,咱們額外增長了‘date_joined’字段,用於保存人員進組的時間,‘invite_reason’字段用於保存邀請進組的緣由。

下面咱們依然在數據庫中實際查看一下(應用名爲app2):

注意中間表的名字已經變成「app2_membership」了。

 

Person和Group沒有變化。

可是中間表就大相徑庭了!它完美的保存了咱們須要的內容。

3、使用中間表

針對上面的中間表,下面是一些使用例子(以歐洲著名的甲殼蟲樂隊成員爲例):

>>> ringo = Person.objects.create(name="Ringo Starr") >>> paul = Person.objects.create(name="Paul McCartney") >>> beatles = Group.objects.create(name="The Beatles") >>> m1 = Membership(person=ringo, group=beatles, ... date_joined=date(1962, 8, 16), ... invite_reason="Needed a new drummer.") >>> m1.save() >>> beatles.members.all() <QuerySet [<Person: Ringo Starr>]> >>> ringo.group_set.all() <QuerySet [<Group: The Beatles>]> >>> m2 = Membership.objects.create(person=paul, group=beatles, ... date_joined=date(1960, 8, 1), ... invite_reason="Wanted to form a band.") >>> beatles.members.all() <QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>

與普通的多對多不同,使用自定義中間表的多對多不能使用add(), create(),remove(),和set()方法來建立、刪除關係,看下面:

>>> # 無效 >>> beatles.members.add(john) >>> # 無效 >>> beatles.members.create(name="George Harrison") >>> # 無效 >>> beatles.members.set([john, paul, ringo, george])

爲何?由於上面的方法沒法提供加入時間、邀請緣由等中間模型須要的字段內容。惟一的辦法只能是經過建立中間模型的實例來建立這種類型的多對多關聯。可是,clear()方法是有效的,它能清空全部的多對多關係。

>>> # 甲殼蟲樂隊解散了 >>> beatles.members.clear() >>> # 刪除了中間模型的對象 >>> Membership.objects.all() <QuerySet []>

一旦你經過建立中間模型實例的方法創建了多對多的關聯,你馬上就能夠像普通的多對多那樣進行查詢操做:

# 查找組內有Paul這我的的全部的組(以Paul開頭的名字) >>> Group.objects.filter(members__name__startswith='Paul') <QuerySet [<Group: The Beatles>]>

能夠使用中間模型的屬性進行查詢:

# 查找甲殼蟲樂隊中加入日期在1961年1月1日以後的成員 >>> Person.objects.filter( ... group__name='The Beatles', ... membership__date_joined__gt=date(1961,1,1)) <QuerySet [<Person: Ringo Starr]>

能夠像普通模型同樣使用中間模型:

>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo) >>> ringos_membership.date_joined datetime.date(1962, 8, 16) >>> ringos_membership.invite_reason 'Needed a new drummer.' >>> ringos_membership = ringo.membership_set.get(group=beatles) >>> ringos_membership.date_joined datetime.date(1962, 8, 16) >>> ringos_membership.invite_reason 'Needed a new drummer.'

對於中間表,有一點要注意(在前面章節已經介紹過,再次重申一下),默認狀況下,中間模型只能包含一個指向源模型的外鍵關係,上面例子中,也就是在Membership中只能有Person和Group外鍵關係各一個,不能多。不然,你必須顯式的經過ManyToManyField.through_fields參數指定關聯的對象。參考下面的例子:

from django.db import models  class Person(models.Model):  name = models.CharField(max_length=50)  class Group(models.Model):  name = models.CharField(max_length=128)  members = models.ManyToManyField(  Person,  through='Membership',  through_fields=('group', 'person'),  )  class Membership(models.Model):  group = models.ForeignKey(Group, on_delete=models.CASCADE)  person = models.ForeignKey(Person, on_delete=models.CASCADE)  inviter = models.ForeignKey(  Person,  on_delete=models.CASCADE,  related_name="membership_invites",  )  invite_reason = models.CharField(max_length=64)

模型中Meta配置

對於一些模型級別的配置。咱們能夠在模型中定義一個類,叫作Meta。而後在這個類中添加一些類屬性來控制模型的做用。好比咱們想要在數據庫映射的時候使用本身指定的表名,而不是使用模型的名稱。那麼咱們能夠在Meta類中添加一個db_table的屬性。示例代碼以下:

class Book(models.Model):  name = models.CharField(max_length=20,null=False)  desc = models.CharField(max_length=100,name='description',db_column="description1")   class Meta: # 注意,是模型的子類,要縮進!  db_table = 'book_model'

如下將對Meta類中的一些經常使用配置進行解釋。

db_table

這個模型映射到數據庫中的表名。若是沒有指定這個參數,那麼在映射的時候將會使用模型名來做爲默認的表名。

db_tablespace

自定義數據庫表空間的名字。默認值是工程的DEFAULT_TABLESPACE設置。

ordering

設置在提取數據的排序方式。後面章節會講到如何查找數據。好比我想在查找數據的時候根據添加的時間排序,那麼示例代碼以下:

class Book(models.Model): name = models.CharField(max_length=20,null=False) desc = models.CharField(max_length=100,name='description',db_column="description1") pub_date = models.DateTimeField(auto_now_add=True)  class Meta: db_table = 'book_model' ordering = ['pub_date'] # 相取反的話['-pub_date']加個負號

abstract

若是abstract=True,那麼模型會被認爲是一個抽象模型。抽象模型自己不實際生成數據庫表,而是做爲其它模型的父類,被繼承使用。具體內容能夠參考Django模型的繼承。

app_label

若是定義了模型的app沒有在INSTALLED_APPS中註冊,則必須經過此元選項聲明它屬於哪一個app,例如:

app_label = 'myapp'

base_manager_name

自定義模型的_base_manager管理器的名字。模型管理器是Django爲模型提供的API所在。Django1.10新增。

default_manager_name

自定義模型的_default_manager管理器的名字。Django1.10新增。

default_related_name

默認狀況下,從一個模型反向關聯設置有關係字段的源模型,咱們使用<model_name>_set,也就是源模型的名字+下劃線+set

這個元數據選項可讓你自定義反向關係名,同時也影響反向查詢關係名!看下面的例子:

from django.db import models  class Foo(models.Model):  pass  class Bar(models.Model):  foo = models.ForeignKey(Foo)   class Meta:  default_related_name = 'bars' # 關鍵在這裏

具體的使用差異以下:

>>> bar = Bar.objects.get(pk=1) >>> # 不能再使用"bar"做爲反向查詢的關鍵字了。 >>> Foo.objects.get(bar=bar) >>> # 而要使用你本身定義的"bars"了。 >>> Foo.objects.get(bars=bar)

get_latest_by

Django管理器給咱們提供有latest()和earliest()方法,分別表示獲取最近一個和最前一個數據對象。可是,如何來判斷最近一個和最前面一個呢?也就是根據什麼來排序呢?

get_latest_by元數據選項幫你解決這個問題,它能夠指定一個相似 DateFieldDateTimeField或者IntegerField這種能夠排序的字段,做爲latest()和earliest()方法的排序依據,從而得出最近一個或最前面一個對象。例如:

get_latest_by = "order_date"

managed

該元數據默認值爲True,表示Django將按照既定的規則,管理數據庫表的生命週期。

若是設置爲False,將不會針對當前模型建立和刪除數據庫表。在某些場景下,這可能有用,但更多時候,你能夠忘記該選項。

order_with_respect_to

這個選項很差理解。其用途是根據指定的字段進行排序,一般用於關係字段。看下面的例子:

from django.db import models  class Question(models.Model):  text = models.TextField()  # ...  class Answer(models.Model):  question = models.ForeignKey(Question, on_delete=models.CASCADE)  # ...   class Meta:  order_with_respect_to = 'question'

上面在Answer模型中設置了order_with_respect_to = 'question',這樣的話,Django會自動提供兩個API,get_RELATED_order()set_RELATED_order(),其中的RELATED用小寫的模型名代替。假設如今有一個Question對象,它關聯着多個Answer對象,下面的操做返回包含關聯的Anser對象的主鍵的列表[1,2,3]:

>>> question = Question.objects.get(id=1) >>> question.get_answer_order() [1, 2, 3]

咱們能夠經過set_RELATED_order()方法,指定上面這個列表的順序:

>>> question.set_answer_order([3, 1, 2])

一樣的,關聯的對象也得到了兩個方法get_next_in_order()get_previous_in_order(),用於經過特定的順序訪問對象,以下所示:

>>> answer = Answer.objects.get(id=2) >>> answer.get_next_in_order() <Answer: 3> >>> answer.get_previous_in_order() <Answer: 1>

這個元數據的做用......還沒用過,囧。

ordering

最經常使用的元數據之一了!

用於指定該模型生成的全部對象的排序方式,接收一個字段名組成的元組或列表。默認按升序排列,若是在字段名前加上字符「-」則表示按降序排列,若是使用字符問號「?」表示隨機排列。請看下面的例子:

ordering = ['pub_date'] # 表示按'pub_date'字段進行升序排列 ordering = ['-pub_date'] # 表示按'pub_date'字段進行降序排列 ordering = ['-pub_date', 'author'] # 表示先按'pub_date'字段進行降序排列,再按`author`字段進行升序排列。

permissions

該元數據用於當建立對象時增長額外的權限。它接收一個全部元素都是二元元組的列表或元組,每一個元素都是(權限代碼, 直觀的權限名稱)的格式。好比下面的例子:

permissions = (("can_deliver_pizzas", "能夠送披薩"),)

default_permissions

Django默認給全部的模型設置('add', 'change', 'delete')的權限,也就是增刪改。你能夠自定義這個選項,好比設置爲一個空列表,表示你不須要默認的權限,可是這一操做必須在執行migrate命令以前。

proxy

若是設置了proxy = True,表示使用代理模式的模型繼承方式。具體內容與abstract選項同樣,參考模型繼承章節。

required_db_features

聲明模型依賴的數據庫功能。好比['gis_enabled'],表示模型的創建依賴GIS功能。

required_db_vendor

聲明模型支持的數據庫。Django默認支持sqlite, postgresql, mysql, oracle

select_on_save

決定是否使用1.6版本以前的django.db.models.Model.save()算法保存對象。默認值爲False。這個選項咱們一般不用關心。

indexes

Django1.11新增的選項。

接收一個應用在當前模型上的索引列表,以下例所示:

from django.db import models  class Customer(models.Model):  first_name = models.CharField(max_length=100)  last_name = models.CharField(max_length=100)   class Meta:  indexes = [  models.Index(fields=['last_name', 'first_name']),  models.Index(fields=['first_name'], name='first_name_idx'),  ]

unique_together

這個元數據是很是重要的一個!它等同於數據庫的聯合約束!

舉個例子,假設有一張用戶表,保存有用戶的姓名、出生日期、性別和籍貫等等信息。要求是全部的用戶惟一不重複,可如今有好幾個叫「張偉」的,如何區別它們呢?(不要和我說主鍵惟一,這裏討論的不是這個問題)

咱們能夠設置不能有兩個用戶在同一個地方同一時刻出生而且都叫「張偉」,使用這種聯合約束,保證數據庫能不能重複添加用戶(也不要和我談小几率問題)。在Django的模型中,如何實現這種約束呢?

使用unique_together,也就是聯合惟一!

好比:

unique_together = (('name', 'birth_day', 'address'),)

聯合惟一沒法做用於普通的多對多字段。

index_together

即將廢棄,使用index元數據代替。

verbose_name

最經常使用的元數據之一!用於設置模型對象的直觀、人類可讀的名稱。能夠用中文。例如:

verbose_name = "story" verbose_name = "披薩"

verbose_name_plural

英語有單數和複數形式。這個就是模型對象的複數名,好比「apples」。由於咱們中文一般不區分單複數,因此保持和verbose_name一致也能夠。

verbose_name_plural = "stories" verbose_name_plural = "披薩"

若是不指定該選項,那麼默認的複數名字是verbose_name加上‘s’

label

前面介紹的元數據都是可修改和設置的,但還有兩個只讀的元數據,label就是其中之一。

label等同於app_label.object_name。例如polls.Question,polls是應用名,Question是模型名。

label_lower

同上,不過是小寫的模型名

官方文檔:https://docs.djangoproject.com/en/2.0/ref/models/options/


查詢條件(查詢參數)

在window機器上mysql在排序的時候不管排序規則怎麼樣,都對大小寫不敏感,可是在linux上,mysql的排序規則若是是utf8_bin,就對大小寫敏感

下表列出了全部的字段查詢參數:

字段名 說明
exact 精確匹配
iexact 不區分大小寫的精確匹配
contains 包含匹配
icontains 不區分大小寫的包含匹配
in 在..以內的匹配
gt 大於
gte 大於等於
lt 小於
lte 小於等於
startswith 從開頭匹配
istartswith 不區分大小寫從開頭匹配
endswith 從結尾處匹配
iendswith 不區分大小寫從結尾處匹配
range 範圍匹配
date 日期匹配
year 年份
month 月份
day 日期
week 第幾周
week_day 周幾
time 時間
hour 小時
minute 分鐘
second
isnull 判斷是否爲空
search 1.10中被廢棄
regex 區分大小寫的正則匹配
iregex 不區分大小寫的正則匹配
  1. exact:在底層會被翻譯成=

  2. iexact:在底層會被翻譯成LIKE

    • LIKE和=:大部分狀況下都是等價的,只有少數狀況下是不等價的。
    • exict和iexact:他們的區別其實就是LIKE和=的區別,由於exact會被翻譯成=,而iexact會被翻譯成LIKE。
    • 由於field__exact=xxx其實等價於filed=xxx,所以咱們直接使用filed=xxx就能夠了,而且由於大部分狀況exactiexact又是等價的,所以咱們之後直接使用field=xxx就能夠了。
  3. QuerySet.query:query能夠用來查看這個ORM查詢語句最終被翻譯成的SQL語句。可是query只能被用在QuerySet對象上,不能用在普通的ORM模型上。所以若是你的查詢語句是經過get來獲取數據的,那麼就不能使用query,由於get返回的是知足條件的ORM模型,而不是QuerySet。若是你是經過filter等其餘返回QuerySet的方法查詢的,那麼就能夠使用query

  4. contains:使用大小寫敏感的判斷,某個字符串是否在指定的字段中。這個判斷條件會使用大小敏感,所以在被翻譯成SQL語句的時候,會使用like binary,而like binary就是使用大小寫敏感的。

  5. icontains:使用大小寫不敏感的判斷,某個字符串是否被包含在指定的字段中。這個查詢語句在被翻譯成SQL的時候,使用的是like,而likeMySQL層面就是不區分大小寫的。

  6. contains和icontains:在被翻譯成SQL的時候使用的是%hello%,就是隻要整個字符串中出現了hello都能過夠被找到,而iexact沒有百分號,那麼意味着只有徹底相等的時候纔會被匹配到。

  7. in:能夠直接指定某個字段的是否在某個集合中。示例代碼以下:

    articles = Article.objects.filter(id__in=[1,2,3])

    也能夠經過其餘的表的字段來判斷是否在某個集合中。示例代碼以下:

    categories = Category.objects.filter(article__id__in=[1,2,3])

    若是要判斷相關聯的表的字段,那麼也是經過__來鏈接。而且在作關聯查詢的時候,不須要寫models_set,直接使用模型的名字的小寫化就能夠了。好比經過分類去查找相應的文章,那麼經過article__id__in就能夠了,而不是寫成article_set__id__in的形式。固然若是你不想使用默認的形式,能夠在外鍵定義的時候傳遞related_query_name來指定反向查詢的名字。示例代碼以下:

    class Category(models.Model):  name = models.CharField(max_length=100)   class Meta:  db_table = 'category'

  1. class Article(models.Model):  title = models.CharField(max_length=200)  content = models.TextField()  cateogry = models.ForeignKey("Category",on_delete=models.CASCADE,null=True,related_query_name='articles')   class Meta:  db_table = 'article'

    由於在cateogryForeignKey中指定了related_query_namearticles,所以你不能再使用article來進行反向查詢了。這時候就須要經過articles__id__in來進行反向查詢。

    反向查詢是將模型名字小寫化。好比article__in。能夠經過related_query_name來指定本身的方式,而不使用默認的方式。 反向引用是將模型名字小寫化,而後再加上_set,好比article_set,能夠經過related_name來指定本身的方式,而不是用默認的方式。

    而且,若是在作反向查詢的時候,若是查詢的字段就是模型的主鍵,那麼能夠省略掉這個字段,直接寫成article__in就能夠了,不須要這個id了。

    in不只僅能夠指定列表/元組,還能夠爲QuerySet。好比要查詢「文章標題中包含有hello的全部分類」,那麼能夠經過如下代碼來實現:

    articles = Article.objects.filter(title__icontains='hello') categories = Category.objects.filter(articles__in=articles) for cateogry in categories:  print(cateogry)
  2. gt、gte、lt、lte:表明的是大於、大於等於、小於、小於等於的條件。示例代碼以下:

    articles = Article.objects.filter(id__lte=3)
  3. startswith、istartswith、endswith、iendswith:表示以某個值開始,不區分大小寫的以某個值開始、以某個值結束、不區分大小寫的以某個值結束。示例代碼以下:

    articles = Article.objects.filter(title__endswith="hello")
  4. 關於時間的查詢條件:

  • range:能夠指定一個時間段。而且時間應該標記爲aware時間,否則django會報警告。示例代碼以下:

    from django.utils.timezone import make_aware start_time = make_aware(datetime(year=2018,month=4,day=4,hour=17,minute=0,second=0)) end_time = make_aware(datetime(year=2018,month=4,day=4,hour=18,minute=0,second=0)) articles = Article.objects.filter(create_time__range=(start_time,end_time)) print(articles.query) print(articles)
  • date:用年月日來進行過濾。若是想要使用這個過濾條件,那麼前提必需要在MySQL中添加好那些時區文件。如何添加呢?參考教案。示例代碼以下:

    articles = Article.objects.filter(create_time__date=datetime(year=2018,month=4,day=4))
  • year/month/day:表示根據年/月/日進行查找。示例代碼以下:

    articles = Article.objects.filter(create_time__year__gte=2018)
  • week_day:根據星期來進行查找。1表示星期天,7表示星期六,2-6表明的是星期一到星期五。好比要查找星期三的全部文章,那麼能夠經過如下代碼來實現:

    articles = Article.objects.filter(create_time__week_day=4)
  • time:根據分時秒來進行查找。若是要具體到秒,通常比較難匹配到,能夠使用區間的方式來進行查找。區間使用range條件。好比想要獲取17時/10分/27-28秒之間的文章,那麼能夠經過如下代碼來實現:

    start_time = time(hour=17,minute=10,second=27) end_time = time(hour=17,minute=10,second=28) articles = Article.objects.filter(create_time__time__range=(start_time,end_time))
  1. isnull:

根據值是否爲空進行查找。示例代碼以下:

articles = Article.objects.filter(pub_date__isnull=False)

以上的代碼的意思是獲取全部發布日期不爲空的文章。 未來翻譯成SQL語句以下:

select ... where pub_date is not null;
  1. regex和iregex:

大小寫敏感和大小寫不敏感的正則表達式。示例代碼以下:

articles = Article.objects.filter(title__regex=r'^hello')

以上代碼的意思是提取全部標題以hello字符串開頭的文章。 將翻譯成如下的SQL語句:

select ... where title regexp binary '^hello';

iregex是大小寫不敏感的。

根據關聯的表進行查詢

假如如今有兩個ORM模型,一個是Article,一個是Category。代碼以下:

class Category(models.Model):  """文章分類表"""  name = models.CharField(max_length=100)  class Article(models.Model):  """文章表"""  title = models.CharField(max_length=100,null=True)  category = models.ForeignKey("Category",on_delete=models.CASCADE)

好比想要獲取文章標題中包含"hello"的全部的分類。那麼能夠經過如下代碼來實現:

categories = Category.object.filter(article__title__contains("hello"))

聚合函數

  1. 全部的聚合函數都是放在django.db.models下面。

  2. 聚合函數不可以單獨的執行,須要放在一些能夠執行聚合函數的方法下面中去執行。好比aggregate。示例代碼以下:

    result = Book.objects.aggregate(Avg("price"))
  3. 聚合函數執行完成後,給這個聚合函數的值取個名字。取名字的規則,默認是filed+__+聚合函數名字造成的。好比以上代碼造成的名字叫作price__avg。若是不想使用默認的名字,那麼能夠在使用聚合函數的時候傳遞關鍵字參數進去,參數的名字就是聚合函數執行完成的名字。實示例代碼以下:

    result = Book.objects.aggregate(avg=Avg("price"))

    以上傳遞了關鍵字參數avg=Avg("price"),那麼之後Avg聚合函數執行完成的名字就叫作avg

  4. aggregate:這個方法不會返回一個QuerySet對象,而是返回一個字典。這個字典中的key就是聚合函數的名字,值就是聚合函數執行後的結果。

  5. aggregateannotate的相同和不一樣:

    • 相同:這兩個方法均可以執行聚合函數。
    • 不一樣:
      • aggregate返回的是一個字典,在這個字典中存儲的是這個聚合函數執行的結果。而annotate返回的是一個QuerySet對象,而且會在查找的模型上添加一個聚合函數的屬性。
      • aggregate不會作分組,而annotate會使用group by子句進行分組,只有調用了group by子句,才能對每一條數據求聚合函數的值。
  6. Count:用來求某個數據的個數。好比要求全部圖書的數量,那麼能夠使用如下代碼:

    result = Book.objects.aggregate(book_nums=Count("id"))

    而且Count能夠傳遞distinct=True參數,用來剔除那些重複的值,只保留一個。好比要獲取做者表中,不一樣郵箱的個數,那麼這時候能夠使用distinct=True。示例代碼以下:

    result = Author.objects.aggregate(email_nums=Count('email',distinct=True))

    也能夠傳遞一個參數filter,用來過濾須要統計的條件,能夠用Q作爲表達式

  7. MaxMin:求指定字段的最大值和最小值。示例代碼以下:

    result = Author.objects.aggregate(max=Max("age"),min=Min("age"))
  8. Sum:求某個字段值的總和。示例代碼以下:

    result = BookOrder.objects.aggregate(total=Sum('price'))

    aggregateannotate方法能夠在任何的QuerySet對象上調用。所以只要是返回了QuerySet對象,那麼就能夠進行鏈式調用。好比要獲取2018年度的銷售總額,那麼能夠先過濾年份,再求聚合函數。示例代碼以下:

    BookOrder.objects.filter(create_time__year=2018).aggregate(total=Sum('price'))
  9. F表達式: 動態的獲取某個字段上的值。而且這個F表達式,不會真正的去數據庫中查詢數據,他至關於只是起一個標識的做用。好比想要將原來每本圖書的價格都在原來的基礎之上增長10元,那麼能夠使用如下代碼來實現:

    from django.db.models import F Book.objects.update(price=F("price")+10)
  10. Q表達式:使用Q表達式包裹查詢條件,能夠在條件之間進行多種操做。與/或非等,從而實現一些複雜的查詢操做。例子以下:

    • 查找價格大於100,而且評分達到4.85以上的圖書:
      # 不使用Q表達式的 books = Book.objects.filter(price__gte=100,rating__gte=4.85) # 使用Q表達式的 books = Book.objects.filter(Q(price__gte=100)&Q(rating__gte=4.85))
    • 查找價格低於100元,或者評分低於4分的圖書:
      books = Book.objects.filter(Q(price__gte=100)&Q(rating__gte=4.85))
    • 獲取價格大於100,而且圖書名字中不包含」傳「字的圖書:
      books = Book.objects.filter(Q(price__gte=100)&~Q(name__icontains='傳'))

QuerySet API:

模型.objects

這個對象是django.db.models.manager.Manager的對象,這個類是一個空殼類,他上面的全部方法都是從QuerySet這個類上面拷貝過來的。所以咱們只要學會了QuerySet,這個objects也就知道該如何使用了。 Manager源碼解析:

class_name = "BaseManagerFromQuerySet"  class_dict = {  '_queryset_class': QuerySet }  class_dict.update(cls._get_queryset_methods(QuerySet))  # type動態的時候建立類 # 第一個參數是用來指定建立的類的名字。建立的類名是:BaseManagerFromQuerySet # 第二個參數是用來指定這個類的父類。 # 第三個參數是用來指定這個類的一些屬性和方法 return type(class_name,(cls,),class_dict)  _get_queryset_methods:這個方法就是將QuerySet中的一些方法拷貝出來

返回新QuerySets的API

**如下的方法都將返回一個新的QuerySets。**重點是加粗的幾個API,其它的使用場景不多。

方法名 解釋
filter() 過濾查詢對象。
exclude() 排除知足條件的對象
annotate() 使用聚合函數
order_by() 對查詢集進行排序
reverse() 反向排序
distinct() 對查詢集去重
values() 返回包含對象具體值的字典的QuerySet
values_list() 與values()相似,只是返回的是元組而不是字典。
dates() 根據日期獲取查詢集
datetimes() 根據時間獲取查詢集
none() 建立空的查詢集
all() 獲取全部的對象
union() 並集
intersection() 交集
difference() 差集
select_related() 附帶查詢關聯對象
prefetch_related() 預先查詢
extra() 附加SQL查詢
defer() 不加載指定字段
only() 只加載指定的字段
using() 選擇數據庫
select_for_update() 鎖住選擇的對象,直到事務結束。
raw() 接收一個原始的SQL查詢

filter/exclude/annotate

過濾/排除知足條件的/給模型添加新的字段。

order_by

# 根據建立的時間正序排序 articles = Article.objects.order_by("create_time") # 根據建立的時間倒序排序 articles = Article.objects.order_by("-create_time") # 根據做者的名字進行排序 articles = Article.objects.order_by("author__name") # 首先根據建立的時間進行排序,若是時間相同,則根據做者的名字進行排序 articles = Article.objects.order_by("create_time",'author__name')

必定要注意的一點是,多個order_by,會把前面排序的規則給打亂,而使用後面的排序方式。好比如下代碼:

articles = Article.objects.order_by("create_time").order_by("author__name")

他會根據做者的名字進行排序,而不是使用文章的建立時間。 固然,也能夠在模型定義的在Meta類中定義ordering來指定默認的排序方式。示例代碼以下:

    class Meta:  db_table = 'book_order'  ordering = ['create_time','-price']

還能夠根據annotate定義的字段進行排序。好比要實現圖書的銷量進行排序,那麼示例代碼以下:

books = Book.objects.annotate(order_nums=Count("bookorder")).order_by("-order_nums")  for book in books:  print('%s/%s'%(book.name,book.order_nums))

values

有時候咱們在表中查找數據的時候,並非想把全部的字段都提取出來,咱們有可能只想要其中的幾個字段,這時候就能夠使用values來實現,須要什麼字段,就把這個字段的名子傳到這個方法中,示例代碼以下:

books = Book.objects.values("id", "name")

values的返回值一樣也是一個QuerySet, 可是這個QuerySet中裝的就再也不是模型了,而是字典。

若是咱們想要提取的是這個模型上關聯對象的屬性,那麼也是能夠的,查找順序跟fiter的用法是同樣的。

books = Book.objects.values('id', "name", 'author__name') 

以上將會提取authorname字段,若是咱們不想要這個名字,想要更改一個名字,那麼能夠使用關鍵字參數。注意這個名子不能和模型自己有的名子同樣。

books = Book.object.values("id", "name", author_name=F("author__name"))

values中也能夠使用聚合函數來造成一個新的字段,好比,我想要獲取每本書的銷量,那麼示例代碼以下:

books = Book.objects.values("id", "name", order_nums=Count('bookorder'))

若是 使用values方法的時候,沒有傳遞任何的參數,那麼會獲取這個模型上的全部字段以及對應的值造成的字典。

values_list

values是同樣的做用,只不過這個方法返回的QuerySet中,裝的不是字典,而是元組

books = Book.objects.values_list("id", "name")

那麼以上代碼的返回結果是:

(1, "三國演義")

若是 給values_list只指定一個字段 ,那麼咱們能夠指定flat=True,這樣返回回來的結果就再也不是一個元組,而是這個字段的值。

books = Book.objects.values_list(name', flat=True)

那麼以上返回 的結果 是:

'三國演義'

必定要注意的是,flat只能用在只有一個字段的狀況下,不然就會報錯。

all

這個返回簡單的返回一個QuerySet對象,這個QuerySet對象沒有通過任何的修改,好比:過濾等

select_related

在查找某個表的數據的時候,能夠一次性把相關聯的其餘表的數據都提取出來,這樣能夠在之後訪問相關聯的表的數據的時候,不用再次查找數據庫,能夠節省一些開銷,注意的是隻能用在外鍵關聯的對象上,也就是正向查找,對於多對多,反向查詢的狀況下不能使用。而應該使用prefetch_relate來實現。

print(models.Book.objects.select_related('publisher')[0].publisher.name)

prefetch_related

這個方法相似於select_related方法,也是用來在查詢語句的時候,提早將指定的數據查找出來,不過這個方法是用來解決我對多,或者多對一的狀況,就是反向查找的時候,這個方法會產生兩個查詢語句。因此在這個方法中查詢使用外鍵關聯的模型的時候,也會產生兩個查詢語句,所以若是查詢的是外鍵關聯的模型,建議使用select_related,在查詢多對多或者多對一的關聯的對象的時候,你在使用模型怎麼訪問這個多對多,那麼就在這個方法中傳遞什麼字符串,好比要獲取圖書上的全部訂單,那麼示例以下

books = Book.objects.prefetch_related('bookorder_set')

須要注意的是:在使用prefetch_related查找出來的bookorder_set,建議不要再對他進行任何操做,好比:filter,否則雙會產生n多的查詢語句,好比以下代碼是不對的

books = Book.objects.prefetch_related("bookorder_set") for book in books:  print('='*30)  print(book.name)  # 在這個地方若是對bookorder_set進行了操做,那麼就又會產生新的sql語句,前面的prefetch_related就至關於白作了  orders = book.bookorder_set.all()  for order in orders:  print(order.id)

那麼若是確實是想要對預先查找的集合進行操做,那麼咱們能夠使用django.db.models.Prefetch來完成。

from django.db.models import Prefetch prefetch = Prefetch("bookorder_set",queryset=BookOrder.objects.filter(price__gte=90)) books = Book.objects.prefetch_related(prefetch) # 先使用prefetch把查找的條件寫好,再放到prefetch_related for book in books:  print('='*30)  print(book.name)  orders = book.bookorder_set.all()  for order in orders:  print(order.id)

defer和only

這兩個方法都會返回一個QuerySet對象,而且這個QuerySet中裝的都是模型,而不是字典

一、defer:這個方法用來告訴ORM,在查詢某個模型的時候,過濾掉某些字段。若是使用了過濾掉的字段,會從新發起一次請求,所以要謹慎。

二、only這個方法用來告訴ORM在查詢 某個模型的時候,只提取某幾個字段 。注意,沒有加在only中的字段,之後若是使用了,那麼也會從新發起一次請求。所以要謹慎。

不返回QuerySets的API

如下的方法不會返回QuerySets,可是做用很是強大,尤爲是粗體顯示的方法,須要背下來。

方法名 解釋
get() 獲取單個對象
create() 建立對象,無需save()
get_or_create() 查詢對象,若是沒有找到就新建對象
update_or_create() 更新對象,若是沒有找到就建立對象
bulk_create() 批量建立對象
count() 統計對象的個數
in_bulk() 根據主鍵值的列表,批量返回對象
iterator() 獲取包含對象的迭代器
latest() 獲取最近的對象
earliest() 獲取最先的對象
first() 獲取第一個對象
last() 獲取最後一個對象
aggregate() 聚合操做
exists() 判斷queryset中是否有對象
update() 批量更新對象
delete() 批量刪除對象
as_manager() 獲取管理器

get方法

這個方法給定的條件,只能匹配到一條數據,若是匹配到多條數據,會報錯,若是沒有匹配到數據也會報錯。

create 方法

建立一條數據 ,而且將這個數據保存到數據庫中

publisher = Publisher(name='出版社') publisher.save() # 下面的這行代碼等價於上面2條 Publisher.objects.create(name='出版社')

get_or_create

若是給定的條件有數據,那麼就會把這個數據直接提取出來,若是給定的條件沒有數據,那麼就會先建立數據,而後再把數據返回回來。

bulk_create

一次性建立多個數據,示例以下:

Tag.objects.bulk_create([  Tag(name='111'),  Tag(name='222'), ])

count

獲取提取的數據的個數。若是想要知道總共有多少條數據,那麼建議使用count,而不是使用len(articles)這種。由於count在底層是使用select count(*)來實現的,這種方式比使用len函數更加的高效。

first和last

返回QuerySet中的第一條和最後一條數據。

**aggregate **

使用聚合函數。

exists

判斷某個條件的數據是否存在。若是要判斷某個條件的元素是否存在,那麼建議使用exists,這比使用count或者直接判斷QuerySet更有效得多。示例代碼以下:

if Article.objects.filter(title__contains='hello').exists():  print(True) 比使用count更高效: if Article.objects.filter(title__contains='hello').count() > 0:  print(True) 也比直接判斷QuerySet更高效: if Article.objects.filter(title__contains='hello'):  print(True)

distinct

去除掉那些重複的數據。這個方法若是底層數據庫用的是MySQL,那麼不能傳遞任何的參數。好比想要提取全部銷售的價格超過80元的圖書,而且刪掉那些重複的,那麼能夠使用distinct來幫咱們實現,示例代碼以下

books = Book.objects.filter(bookorder__price__gte=80).distinct()

須要注意的是,若是在distinct以前使用了order_by,那麼由於order_by會提取order_by中指定的字段,所以再使用distinct就會根據多個字段來進行惟一化,因此就不會把那些重複的數據刪掉。示例代碼以下:

orders = BookOrder.objects.order_by("create_time").values("book_id").distinct()

那麼以上代碼由於使用了order_by,即便使用了distinct,也會把重複的book_id提取出來。

update

一次性能夠把全部的數據都更新完

delete

一次性能夠把全部知足條件的數據都刪除掉,可是須要注意,on_delete指定的處理方式。


Form和ModelForm組件

熊掌狀況下,咱們須要本身手動在HTML頁面中,編寫form標籤和基內的其它元素,比較費力,數據驗證也比較麻煩,django在內部集成了一個表單模塊,專門幫助咱們快速處理表單相關的內容,Django的表單模塊給咱們提供了下面三個主要功能

* 前端頁面的渲染,爲數據建立HTML表單元素
* 接收和處理用戶從表單發送過來的數據的驗證 * 保留上次輸入內容

Django中表單使用流程:

首先,在你當前app內新建一個forms.py文件(這個套路是Django的慣用手法,就像views.pymodels.py等等),而後輸入下面的內容:

# forms.py class MessageBoardForm(forms.Form):  title = forms.CharField(max_length=3,label='標題',min_length=2,error_messages={"min_length":'標題字符段不符合要求!'})  content = forms.CharField(widget=forms.Textarea,label='內容')  email = forms.EmailField(label='郵箱')  reply = forms.BooleanField(required=False,label='回覆')

要點:

  • 提早導入forms模塊
  • 全部的表單類都要繼承forms.Form類
  • 每一個表單字段都有本身的字段類型好比CharField,它們分別對應一種HTML語言中的<form>元素中的表單元素。這一點和Django模型系統的設計很是類似。
  • 例子中的label用於設置說明標籤
  • max_length限制最大長度爲100。它同時起到兩個做用,一是在瀏覽器頁面限制用戶輸入不可超過100個字符,二是在後端服務器驗證用戶輸入的長度不可超過100。

(警告:因爲瀏覽器頁面是能夠被篡改、僞造、禁用、跳過的,全部的HTML手段的數據驗證只能防止意外不能防止惡意行爲,是沒有安全保證的,破壞分子徹底能夠跳過瀏覽器的防護手段僞造發送請求!因此,在服務器後端,必須將前端當作「裸機」來對待,再次進行徹底完全的數據驗證和安全防禦!)

每一個Django表單的實例都有一個內置的is_valid()方法,用來驗證接收的數據是否合法。若是全部數據都合法,那麼該方法將返回True,並將全部的表單數據轉存到它的一個叫作cleaned_data的屬性中,該屬性是以個字典類型數據。

# views.py class IndexView(View):  def get(self,request):  form = MessageBoardForm()  return render(request,'index.html',{'form':form})   def post(self,request):  form = MessageBoardForm(request.POST)  if form.is_valid():  title = form.cleaned_data.get('title')  content = form.cleaned_data.get('content')  email = form.cleaned_data.get('email')  reply = form.cleaned_data.get('reply')  return HttpResponse('success')  else:  print(form.errors)  return HttpResponse('fail')

要點是:

  • 對於GET方法請求頁面時,返回空的表單,讓用戶能夠填入數據;
  • 對於POST方法,接收表單數據,並驗證;
  • 若是數據合法,按照正常業務邏輯繼續執行下去;
  • 若是不合法,返回一個包含先前數據的表單給前端頁面,方便用戶修改。

經過表單的is_bound屬性能夠獲知一個表單已經綁定了數據,仍是一個空表。

is_valid()方法和is_bound屬性的區別:

is_bound 當form對象中有數據時能正確判斷數據是有的、而這個有與數據是否能經過校驗無關。

is_valid()方法是看提交的數據能不能經過驗證。

  • {{ form.as_table }} 將表單渲染成一個表格元素,每一個輸入框做爲一個<tr>標籤
# html
<table>  <form action="" method="post">  {{ form.as_table }}  <tr>  <td></td>  <td><input type="submit" value="提交"></td>  </tr>  </form> </table>
  • {{ form.as_p }} 將表單的每一個輸入框包裹在一個<p>標籤內 tags
<div>  <form action="" method="post">  {{ form.as_p }}  <p><input type="submit" value="提交"></p>  </form> </div>
  • {{ form.as_ul }} 將表單渲染成一個列表元素,每一個輸入框做爲一個<li>標籤
<form action="" method="post">  <ul>  {{ form.as_ul }}  <li><input type="submit" value="提交"></li>  </ul> </form>

注意:你要本身手動編寫<table><ul>標籤。Django自動爲每一個input元素設置了一個id名稱,對應label的for參數

  • 手動渲染表單字段

直接{{ form }}雖然好,啥都不用操心,可是每每並非你想要的,好比你要使用CSS和JS,好比你要引入Bootstarps框架,這些都須要對錶單內的input元素進行額外控制,那怎麼辦呢?手動渲染字段就能夠了。

能夠經過{{ form.name_of_field }}獲取每個字段,而後分別渲染,以下例所示:

<form action="" method="post" > {% csrf_token %}  {{ form.title.errors }}  <label for="{{ form.title.id_for_label }}">{{ form.title.label }}</label>  {{ form.title }}  {{ form.content.errors }}  <label for="{{ form.content }}">{{ form.content.label }}</label>  {{ form.content }}  {{ form.email.errors }}  <label for="{{ form.email.id_for_label }}">{{ form.email.label }}</label>  {{ form.email }}  {{ form.reply.errors }}  <label for="{{ form.reply.id_for_label }}">{{ form.reply.label }}</label>  {{ form.reply }}  <input type="submit" value="提交"> </form>

其中的label標籤甚至能夠用label_tag()方法來生成,因而能夠簡寫成下面的樣子

<div class="fieldWrapper">  {{ form.title.errors }}  {{ form.title.label_tag }}  {{ form.tiele }} </div>
  • 循環表單的字段

若是你的表單字段有相同格式的HMTL表現,那麼徹底能夠循環生成,沒必要要手動的編寫每一個字段,減小冗餘和重複代碼,只須要使用模板語言中的{% for %}循環,以下所示:

<form action="" method="post">  <table>  {% for foo in form %}  <tr>  <td></td>  <td>{{ foo.errors.0 }}</td>  </tr>  <tr>  <td>{{ foo.label_tag }}</td>  <td>{{ foo }}</td>  </tr>  {% endfor %}  </table>  <input type="submit" value="提交"> </form>

下表是{{ field }}中很是有用的屬性,這些都是Django內置的模板語言給咱們提供的方便:

屬性 說明
{{ field.label }} 字段對應的label信息
{{ field.label_tag }} 自動生成字段的label標籤,注意與{{ field.label }}的區別。
{{ field.id_for_label }} 自定義字段標籤的id
{{ field.value }} 當前字段的值,好比一個Email字段的值someone@example.com
{{ field.html_name }} 指定字段生成的input標籤中name屬性的值
{{ field.help_text }} 字段的幫助信息
{{ field.errors }} 包含錯誤信息的元素
{{ field.is_hidden }} 用於判斷當前字段是否爲隱藏的字段,若是是,返回True
{{ field.field }} 返回字段的參數列表。例如{{ char_field.field.max_length }}
  • 不可見字段的處理

不少時候,咱們的表單中會有一些隱藏的不可見的字段,好比honeypot。咱們須要讓它在任什麼時候候都彷彿不存在通常,好比有錯誤的時候,若是你在頁面上顯示了不可見字段的錯誤信息,那麼用戶會很迷惑,這是哪來的呢?因此,一般咱們是不顯示不可見字段的錯誤信息的。

Django提供了兩種獨立的方法,用於循環那些不可見的和可見的字段,hidden_fields()visible_fields()。這裏,咱們能夠稍微修改一下前面的例子:

{# 循環那些不可見的字段 #}
{% for hidden in form.hidden_fields %} {{ hidden }} {% endfor %} {# 循環可見的字段 #} {% for field in form.visible_fields %}  <div class="fieldWrapper">  {{ field.errors }}  {{ field.label_tag }} {{ field }}  </div> {% endfor %}
  • 重用表單模板

若是你在本身的HTML文件中,屢次使用同一種表單模板,那麼你徹底能夠把表單模板存成一個獨立的HTML文件,而後在別的HTML文件中經過include模板語法將其包含進來,以下例所示:

# 實際的頁面文件中:
{% include "form_snippet.html" %}  -----------------------------------------------------  # 單獨的表單模板文件form_snippet.html: {% for field in form %}  <div class="fieldWrapper">  {{ field.errors }}  {{ field.label_tag }} {{ field }}  </div> {% endfor %}

若是你的頁面同時引用了好幾個不一樣的表單模板,那麼爲了防止衝突,你能夠使用with參數,給每一個表單模板取個別名,以下所示:

{% include "form_snippet.html" with form=comment_form %}

在使用的時候就是:

{% for field in comment_form %}
......

django表單API

1. 表單的綁定屬性

Form.is_bound:

若是你須要區分綁定的表單和未綁定的表單,能夠檢查下表單的is_bound屬性值:

>>> f = ContactForm() >>> f.is_bound False >>> f = ContactForm({'subject': 'hello'}) >>> f.is_bound True

注意,傳遞一個空的字典將建立一個帶有空數據的綁定的表單:

>>> f = ContactForm({}) >>> f.is_bound True

若是你有一個綁定的Form實例可是想改下數據,或者你想給一個未綁定的Form表單綁定某些數據,你須要建立另一個Form實例。由於,Form實例的數據沒是自讀的,Form實例一旦建立,它的數據將不可變。

2. 表單的驗證數據

1. 經常使用的驗證器

在驗證某個字段的時候,能夠傳遞一個validators參數用來指定驗證器,進一步對數據進行過濾。驗證器有不少,可是不少驗證器咱們其實已經經過這個Field或者一些參數就能夠指定了。好比EmailValidator,咱們能夠經過EmailField來指定,好比MaxValueValidator,咱們能夠經過max_value參數來指定。如下是一些經常使用的驗證器:

  1. MaxValueValidator:驗證最大值。

  2. MinValueValidator:驗證最小值。

  3. MinLengthValidator:驗證最小長度。

  4. MaxLengthValidator:驗證最大長度。

  5. EmailValidator:驗證是不是郵箱格式。

  6. URLValidator:驗證是不是URL格式。

  7. RegexValidator:若是還須要更加複雜的驗證,那麼咱們能夠經過正則表達式的驗證器:RegexValidator。好比如今要驗證手機號碼是否合格,那麼咱們能夠經過如下代碼實現:

    class MyForm(forms.Form):  telephone = forms.CharField(validators=[validators.RegexValidator("1[345678]\d{9}",message='請輸入正確格式的手機號碼!')])
2. 自定義驗證器

有時候對一個字段驗證,不是一個長度,一個正則表達式可以寫清楚的,還須要一些其餘複雜的邏輯,那麼咱們能夠對某個字段,進行自定義的驗證。好比在註冊的表單驗證中,咱們想要驗證手機號碼是否已經被註冊過了,那麼這時候就須要在數據庫中進行判斷才知道。對某個字段進行自定義的驗證方式是,定義一個方法,這個方法的名字定義規則是:clean_fieldname。若是驗證失敗,那麼就拋出一個驗證錯誤。好比要驗證用戶表中手機號碼以前是否在數據庫中存在,那麼能夠經過如下代碼實現:

  1. 定義clean_字段名()的方法,也叫局部鉤子
from django import forms from django.forms.utils import ErrorDict from django.core import validators from app01 import models  class MyForms(forms.Form):  user = forms.CharField(min_length=2, max_length=32)  tel = forms.CharField(validators=[validators.RegexValidator(r'1[3456789]\d{9}', message='手機號碼格式不對')])  pw1 = forms.CharField(min_length=2, max_length=12)  pw2 = forms.CharField(min_length=2, max_length=12)   def clean_tel(self):  tel = self.cleaned_data.get('tel')  ret = models.UserInfo.objects.filter(tel=tel).exists()  if ret:  raise forms.ValidationError('手機號碼已經存在')  return tel
  1. 重寫clean()的方法,也叫全局鉤子
def clean(self):  cleaned_data = super().clean()  pw1 = cleaned_data.get('pw1')  pw2 = cleaned_data.get('pw2')  if pw1 != pw2:  raise forms.ValidationError('兩次密碼不同')  return cleaned_data
  1. 自定義函數驗證
import re from django.forms import Form from django.forms import widgets from django.forms import fields from django.core.exceptions import ValidationError   # 自定義驗證規則 def mobile_validate(value):  mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')  if not mobile_re.match(value):  raise ValidationError('手機號碼格式錯誤') #自定義驗證規則的時候,若是不符合你的規則,須要本身發起錯誤   class PublishForm(Form):    title = fields.CharField(max_length=20,  min_length=5,  error_messages={'required': '標題不能爲空',  'min_length': '標題最少爲5個字符',  'max_length': '標題最多爲20個字符'},  widget=widgets.TextInput(attrs={'class': "form-control",  'placeholder': '標題5-20個字符'}))    # 使用自定義驗證規則  phone = fields.CharField(validators=[mobile_validate, ],  error_messages={'required': '手機不能爲空'},  widget=widgets.TextInput(attrs={'class': "form-control",  'placeholder': u'手機號碼'}))   email = fields.EmailField(required=False,  error_messages={'required': u'郵箱不能爲空','invalid': u'郵箱格式錯誤'},  widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'郵箱'}))

3. 錯誤信息

  1. Form.errors

    表單的errors屬性保存了錯誤信息字典

    >>> f.errors {'sender': ['Enter a valid email address.'], 'subject': ['This field is required.']}
  2. Form.errors.as_data()

    在源碼中的位置from django.forms.utils import ErrorDict

    返回一個字典,它將字段映射到原始的ValidationError實例。

    >>> f.errors.as_data() {'sender': [ValidationError(['Enter a valid email address.'])], 'subject': [ValidationError(['This field is required.'])]}
  3. Form.errors.as_json(escape_html=False)

    返回JSON序列化後的錯誤信息字典。

    >>> f.errors.as_json() {"sender": [{"message": "Enter a valid email address.", "code": "invalid"}], "subject": [{"message": "This field is required.", "code": "required"}]}
  4. Form.add_error(field, error)

    向表單特定字段添加錯誤信息。

    field參數爲字段的名稱。若是值爲None,error將做爲Form.non_field_errors()的一個非字段錯誤。

  5. Form.has_error(field, code=None)

    判斷某個字段是否具備指定code的錯誤。當code爲None時,若是字段有任何錯誤它都將返回True。

  6. Form.non_field_errors()

    返回Form.errors中不是與特定字段相關聯的錯誤。

4. 檢查表單數據是否被修改

  1. Form.has_changed()

    當你須要檢查表單的數據是否從初始數據發生改變時,能夠使用has_changed()方法。

    >>> data = {'subject': 'hello', ... 'message': 'Hi there', ... 'sender': 'foo@example.com', ... 'cc_myself': True} >>> f = ContactForm(data, initial=data) >>> f.has_changed() False

    提交表單後,咱們能夠從新構建表單並提供初始值,進行比較:

    >>> f = ContactForm(request.POST, initial=data) >>> f.has_changed()

    若是request.POST與initial中的數據有區別,則返回True,不然返回False。

  2. Form.changed_data

    返回有變化的字段的列表。

    >>> f = ContactForm(request.POST, initial=data) >>> if f.has_changed(): ... print("The following fields changed: %s" % ", ".join(f.changed_data))

5. 訪問表單中的字段

經過fileds屬性訪問表單的字段:

>>> for row in f.fields.values(): print(row) ... <django.forms.fields.CharField object at 0x7ffaac632510> <django.forms.fields.URLField object at 0x7ffaac632f90> <django.forms.fields.CharField object at 0x7ffaac3aa050> >>> f.fields['name'] <django.forms.fields.CharField object at 0x7ffaac6324d0>

能夠修改Form實例的字段來改變字段在表單中的表示:

form.fields['title'].label = '琦村伯' print(form.as_table().split('\n')[0]) ## <tr><th><label for="id_title">琦村伯:</label></th><td><input type="text" name="title" maxlength="3" minlength="2" required id="id_title" /></td></tr>

注意不要改變base_fields屬性,由於一旦修改將影響同一個Python進程中接下來全部的Form實例:

form = MessageBoardForm() # 已經實例了 form.base_fields['title'].label = '你的力量' print(form.as_table().split('\n')[0]) # 已經實例化了,因此無法改變 another_f = MessageBoardForm(auto_id=False) # 可是影響了下一下實例化的對象 print(another_f.as_table().split('\n')[0]) ################ <tr><th><label for="id_title">標題:</label></th><td><input type="text" name="title" maxlength="3" minlength="2" required id="id_title" /></td></tr> <tr><th>你的力量:</th><td><input type="text" name="title" maxlength="3" minlength="2" required /></td></tr>

6. 訪問cleaned_data

Form.cleaned_data

Form類中的每一個字段不只負責驗證數據,還負責將它們轉換爲正確的格式。例如,DateField將輸入轉換爲Python的datetime.date對象。不管你傳遞的是普通字符串'1994-07-15'、DateField格式的字符串、datetime.date對象、仍是其它格式的數字,Django將始終把它們轉換成datetime.date對象。

一旦你建立一個Form實例並經過驗證後,你就能夠經過它的cleaned_data屬性訪問乾淨的數據:

>>> data = {'subject': 'hello', ... 'message': 'Hi there', ... 'sender': 'foo@example.com', ... 'cc_myself': True} >>> f = ContactForm(data) >>> f.is_valid() True >>> f.cleaned_data {'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}

若是你的數據沒有經過驗證,cleaned_data字典中只包含合法的字段:

>>> data = {'subject': '', # 是個空的字符串 ... 'message': 'Hi there', ... 'sender': 'invalid email address', ... 'cc_myself': True} >>> f = ContactForm(data) >>> f.is_valid() False >>> f.cleaned_data {'cc_myself': True, 'message': 'Hi there'} # 因此cleaned_date字典中就沒有subject的鍵

cleaned_data字典始終只包含Form中定義的字段或者使用ModelForm時模型中的字段,即便你在構建Form時傳遞了額外的數據。 在下面的例子中,咱們傳遞了一組額外的字段給ContactForm構造函數,可是cleaned_data將只包含表單的字段:

>>> data = {'subject': 'hello', ... 'message': 'Hi there', ... 'sender': 'foo@example.com', ... 'cc_myself': True, ... 'extra_field_1': 'foo', ... 'extra_field_2': 'bar', ... 'extra_field_3': 'baz'} >>> f = ContactForm(data) >>> f.is_valid() True >>> f.cleaned_data # Doesn't contain extra_field_1, etc. {'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}

當Form經過驗證後,cleaned_data將包含全部字段的鍵和值,即便傳遞的數據中沒有提供某些字段的值。 在下面的例子中,提供的實際數據中不包含nick_name字段,可是cleaned_data任然包含它,只是值爲空:

>>> from django import forms >>> class OptionalPersonForm(forms.Form): ... first_name = forms.CharField() ... last_name = forms.CharField() ... nick_name = forms.CharField(required=False) # 字段中有nick_name字段,可是能夠不傳 >>> data = {'first_name': 'John', 'last_name': 'Lennon'} # 在字典中並無傳這個nick_name >>> f = OptionalPersonForm(data) >>> f.is_valid() True >>> f.cleaned_data {'nick_name': '', 'first_name': 'John', 'last_name': 'Lennon'} # 驗證後的數據這個字段爲空

7. 爲錯誤信息添加CSS樣式

Form.error_css_class 爲錯誤的信息添加類

Form.required_css_class 爲必填的字段添加類

爲一些特別強調的或者須要額外顯示的內容設置醒目的CSS樣式是一種經常使用作法,也是很是有必要的。好比給必填字段加粗顯示,設置錯誤文字爲紅色等等。

Form.error_css_classForm.required_css_class屬性就是作這個用的:

from django import forms  class ContactForm(forms.Form):  error_css_class = 'error'  required_css_class = 'required'   # ... and the rest of your fields here

屬性名是固定的,不可變(廢話),經過賦值不一樣的字符串,表示給這兩類屬性添加不一樣的CSS的class屬性。之後Django在渲染form成HTML時將自動爲error和required行添加對應的CSS樣式。

8. 將上傳的文件綁定到表單

處理帶有FileField和ImageField字段的表單比普通的表單要稍微複雜一點。

首先,爲了上傳文件,你須要確保你的<form>元素定義enctype爲"multipart/form-data":

<form enctype="multipart/form-data" method="post" action="/foo/">

其次,當你使用表單時,你須要綁定文件數據。文件數據的處理與普通的表單數據是分開的,因此若是表單包含FileField和ImageField,綁定表單時你須要指定第二個參數,參考下面的例子。

# 爲表單綁定image字段 >>> from django.core.files.uploadedfile import SimpleUploadedFile >>> data = {'subject': 'hello', ... 'message': 'Hi there', ... 'sender': 'foo@example.com', ... 'cc_myself': True} >>> file_data = {'mugshot': SimpleUploadedFile('face.jpg', <file data>)} >>> f = ContactFormWithMugshot(data, file_data)

實際上,通常使用request.FILES做爲文件數據的源:

# Bound form with an image field, data from the request >>> f = ContactFormWithMugshot(request.POST, request.FILES)

構造一個未綁定的表單和往常同樣,將表單數據和文件數據同時省略:

# Unbound form with an image field >>> f = ContactFormWithMugshot()

Form經常使用的字段與插件

Field.clean(value)[source]

雖然表單字段的Field類主要使用在Form類中,但也能夠直接實例化它們來使用,以便更好地瞭解它們是如何工做的。每一個Field的實例都有一個clean()方法,它接受一個參數,而後返回「清潔的」數據或者拋出一個django.forms.ValidationError異常:

>>> from django import forms >>> f = forms.EmailField() >>> f.clean('foo@example.com') 'foo@example.com' >>> f.clean('invalid email address') Traceback (most recent call last): ... ValidationError: ['Enter a valid email address.']

這個clean方法常常被咱們用來在開發或測試過程當中對數據進行驗證和測試。

建立Form類時,主要涉及到 【字段】 和 【插件】,字段用於對用戶請求數據的驗證,插件用於自動生成HTML;

  1. initial

​ 初始值,input框裏面的初始值。

class LoginForm(forms.Form):  username = forms.CharField(  min_length=8,  label="用戶名",  initial="張三" # 設置默認值  )  pwd = forms.CharField(min_length=6, label="密碼")
  1. error_messages

    重寫錯誤信息

    class LoginForm(forms.Form):  username = forms.CharField(  min_length=8,  label="用戶名",  initial="張三",  error_messages={  "required": "不能爲空",  "invalid": "格式錯誤",  "min_length": "用戶名最短8位"  }  )  pwd = forms.CharField(min_length=6, label="密碼")
  2. password

    class LoginForm(forms.Form):  ...  pwd = forms.CharField(  min_length=6,  label="密碼",  widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True) #這個密碼字段和其餘字段不同,默認在前端輸入數據錯誤的時候,點擊提交以後,默認是不保存的原來數據的,可是能夠經過這個render_value=True讓這個字段在前端保留用戶輸入的數據  )
  3. radioSelect

    單radio值爲字符串

    class LoginForm(forms.Form):  username = forms.CharField( #其餘選擇框或者輸入框,基本都是在這個CharField的基礎上經過插件來搞的  min_length=8,  label="用戶名",  initial="張三",  error_messages={  "required": "不能爲空",  "invalid": "格式錯誤",  "min_length": "用戶名最短8位"  }  )  pwd = forms.CharField(min_length=6, label="密碼")  gender = forms.fields.ChoiceField(  choices=((1, "男"), (2, "女"), (3, "保密")),  label="性別",  initial=3,  widget=forms.widgets.RadioSelect()  )
  4. 單選Select

    class LoginForm(forms.Form):  ...  hobby = forms.fields.ChoiceField( #注意,單選框用的是ChoiceField,而且裏面的插件是Select,否則驗證的時候會報錯, Select a valid choice的錯誤。  choices=((1, "籃球"), (2, "足球"), (3, "雙色球"), ),  label="愛好",  initial=3,  widget=forms.widgets.Select()  )
  5. 多選Select

    class LoginForm(forms.Form):  ...  hobby = forms.fields.MultipleChoiceField( #多選框的時候用MultipleChoiceField,而且裏面的插件用的是SelectMultiple,否則驗證的時候會報錯。  choices=((1, "籃球"), (2, "足球"), (3, "雙色球"), ),  label="愛好",  initial=[1, 3],  widget=forms.widgets.SelectMultiple()  )
  6. 單選checkbox

    class LoginForm(forms.Form):  ...  keep = forms.fields.ChoiceField(  label="是否記住密碼",  initial="checked",  widget=forms.widgets.CheckboxInput()  )

    單選checkbox示例:

    #單選的checkbox  class TestForm2(forms.Form):  keep = forms.ChoiceField(  choices=(  ('True',1),  ('False',0),  ),   label="是否7天內自動登陸",  initial="1",  widget=forms.widgets.CheckboxInput(),  )  選中:'True' #form只是幫咱們作校驗,校驗選擇內容的時候,就是看在沒在咱們的choices裏面,裏面有這個值,表示合法,沒有就不合法  沒選中:'False'  ---保存到數據庫裏面 keep:'True'  if keep == 'True':  session 設置有效期7 else:  pass
  7. 多選checkbox

    class LoginForm(forms.Form):  ...  hobby = forms.fields.MultipleChoiceField(  choices=((1, "籃球"), (2, "足球"), (3, "雙色球"),),  label="愛好",  initial=[1, 3],  widget=forms.widgets.CheckboxSelectMultiple()  )
  8. date類型

    from django import forms from django.forms import widgets class BookForm(forms.Form):  date = forms.DateField(widget=widgets.TextInput(attrs={'type':'date'})) #必須指定type,否則不能渲染成選擇時間的input框
  9. choice字段注意事項

    在使用選擇標籤時,須要注意choices的選項能夠配置從數據庫中獲取,可是因爲是靜態字段 獲取的值沒法實時更新,須要重寫構造方法從而實現choice實時更新。

    方式一:

    from django.forms import Form from django.forms import widgets from django.forms import fields   class MyForm(Form):   user = fields.ChoiceField(  # choices=((1, '上海'), (2, '北京'),),  initial=2,  widget=widgets.Select  )   def __init__(self, *args, **kwargs):  super(MyForm,self).__init__(*args, **kwargs) #注意重寫init方法的時候,*args和**kwargs必定要給人家寫上,否則會出問題,而且驗證老是不能經過,還不顯示報錯信息  # self.fields['user'].choices = ((1, '上海'), (2, '北京'),)  # 或  self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')

    方式二:

    from django import forms from django.forms import fields from django.forms import models as form_model   class FInfo(forms.Form):     authors = forms.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多選  #或者下面這種方式,經過forms裏面的models中提供的方法也是同樣的。  authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多選  #authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all()) # 單選   #或者,forms.ModelChoiceField(queryset=models.Publisth.objects.all(),widget=forms.widgets.Select()) 單選  #   authors = forms.ModelMultipleChoiceField(  queryset=models.Author.objects.all(),  widget = forms.widgets.Select(attrs={'class': 'form-control'}  ))  #若是用這種方式,別忘了model表中,NNEWType的__str__方法要寫上,否則選擇框裏面是一個個的object對象
  10. label

    label參數用來給字段添加‘人類友好’的提示信息。若是沒有設置這個參數,那麼就用字段的首字母大寫名字。好比:

    下面的例子,前兩個字段有,最後的comment沒有label參數:

    >>> from django import forms >>> class CommentForm(forms.Form): ... name = forms.CharField(label='Your name') ... url = forms.URLField(label='Your website', required=False) ... comment = forms.CharField() >>> f = CommentForm(auto_id=False) >>> print(f) <tr><th>Your name:</th><td><input type="text" name="name" required /></td></tr> <tr><th>Your website:</th><td><input type="url" name="url" /></td></tr> <tr><th>Comment:</th><td><input type="text" name="comment" required /></td></tr>
  11. label_suffix

    Django默認爲上面的label參數後面加個冒號後綴,若是想自定義,能夠使用label_suffix參數。好比下面的例子用「?」代替了冒號:

    >>> class ContactForm(forms.Form): ... age = forms.IntegerField() ... nationality = forms.CharField() ... captcha_answer = forms.IntegerField(label='2 + 2', label_suffix=' =') >>> f = ContactForm(label_suffix='?') >>> print(f.as_p()) <p><label for="id_age">Age?</label> <input id="id_age" name="age" type="number" required /></p> <p><label for="id_nationality">Nationality?</label> <input id="id_nationality" name="nationality" type="text" required /></p> <p><label for="id_captcha_answer">2 + 2 =</label> <input id="id_captcha_answer" name="captcha_answer" type="number" required /></p>
  12. help_text

    該參數用於設置字段的輔助描述文本。

    >>> from django import forms >>> class HelpTextContactForm(forms.Form): ... subject = forms.CharField(max_length=100, help_text='100 characters max.') ... message = forms.CharField() ... sender = forms.EmailField(help_text='A valid email address, please.') ... cc_myself = forms.BooleanField(required=False) >>> f = HelpTextContactForm(auto_id=False) >>> print(f.as_table()) <tr><th>Subject:</th><td><input type="text" name="subject" maxlength="100" required /><br /><span class="helptext">100 characters max.</span></td></tr> <tr><th>Message:</th><td><input type="text" name="message" required /></td></tr> <tr><th>Sender:</th><td><input type="email" name="sender" required /><br />A valid email address, please.</td></tr> <tr><th>Cc myself:</th><td><input type="checkbox" name="cc_myself" /></td></tr> >>> print(f.as_ul())) <li>Subject: <input type="text" name="subject" maxlength="100" required /> <span class="helptext">100 characters max.</span></li> <li>Message: <input type="text" name="message" required /></li> <li>Sender: <input type="email" name="sender" required /> A valid email address, please.</li> <li>Cc myself: <input type="checkbox" name="cc_myself" /></li> >>> print(f.as_p()) <p>Subject: <input type="text" name="subject" maxlength="100" required /> <span class="helptext">100 characters max.</span></p> <p>Message: <input type="text" name="message" required /></p> <p>Sender: <input type="email" name="sender" required /> A valid email address, please.</p> <p>Cc myself: <input type="checkbox" name="cc_myself" /></p>
  13. validators

    指定一個列表,其中包含了爲字段進行驗證的函數。也就是說,若是你自定義了驗證方法,不用Django內置的驗證功能,那麼要經過這個參數,將字段和自定義的驗證方法連接起來。

  14. localize

    localize參數幫助實現表單數據輸入的本地化。

  15. disabled

    設置有該屬性的字段在前端頁面中將顯示爲不可編輯狀態。

    該參數接收布爾值,當設置爲True時,使用HTML的disabled屬性禁用表單域,以使用戶沒法編輯該字段。即便非法篡改了前端頁面的屬性,向服務器提交了該字段的值,也將依然被忽略。

Form全部內置字段

內置字段:

Field
 required=True, 是否容許爲空  widget=None, HTML插件  label=None, 用於生成Label標籤或顯示內容  initial=None, 初始值  help_text='', 幫助信息(在標籤旁邊顯示)  error_messages=None, 錯誤信息 {'required': '不能爲空', 'invalid': '格式錯誤'}  validators=[], 自定義驗證規則  localize=False, 是否支持本地化  disabled=False, 是否能夠編輯  label_suffix=None Label內容後綴   CharField(Field)  max_length=None, 最大長度  min_length=None, 最小長度  strip=True 是否移除用戶輸入空白  IntegerField(Field)  max_value=None, 最大值  min_value=None, 最小值  FloatField(IntegerField)  ...  DecimalField(IntegerField)  max_value=None, 最大值  min_value=None, 最小值  max_digits=None, 總長度  decimal_places=None, 小數位長度  BaseTemporalField(Field)  input_formats=None 時間格式化  DateField(BaseTemporalField) 格式:2015-09-01 TimeField(BaseTemporalField) 格式:11:12 DateTimeField(BaseTemporalField)格式:2015-09-01 11:12  DurationField(Field) 時間間隔:%d %H:%M:%S.%f  ...  RegexField(CharField)  regex, 自定製正則表達式  max_length=None, 最大長度  min_length=None, 最小長度  error_message=None, 忽略,錯誤信息使用 error_messages={'invalid': '...'}  EmailField(CharField)  ...  FileField(Field)  allow_empty_file=False 是否容許空文件  ImageField(FileField)  ...  注:須要PIL模塊,pip3 install Pillow  以上兩個字典使用時,須要注意兩點:  - form表單中 enctype="multipart/form-data"  - view函數中 obj = MyForm(request.POST, request.FILES)  URLField(Field)  ...   BooleanField(Field)  ...  NullBooleanField(BooleanField)  ...  ChoiceField(Field)  ...  choices=(), 選項,如:choices = ((0,'上海'),(1,'北京'),)  required=True, 是否必填  widget=None, 插件,默認select插件  label=None, Label內容  initial=None, 初始值  help_text='', 幫助提示   ModelChoiceField(ChoiceField)  ... django.forms.models.ModelChoiceField  queryset, # 查詢數據庫中的數據  empty_label="---------", # 默認空顯示內容  to_field_name=None, # HTML中value的值對應的字段  limit_choices_to=None # ModelForm中對queryset二次篩選  ModelMultipleChoiceField(ModelChoiceField)  ... django.forms.models.ModelMultipleChoiceField    TypedChoiceField(ChoiceField)  coerce = lambda val: val 對選中的值進行一次轉換  empty_value= '' 空值的默認值  MultipleChoiceField(ChoiceField)  ...  TypedMultipleChoiceField(MultipleChoiceField)  coerce = lambda val: val 對選中的每個值進行一次轉換  empty_value= '' 空值的默認值  ComboField(Field)  fields=() 使用多個驗證,以下:即驗證最大長度20,又驗證郵箱格式  fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])  MultiValueField(Field)  PS: 抽象類,子類中能夠實現聚合多個字典去匹配一個值,要配合MultiWidget使用  SplitDateTimeField(MultiValueField)  input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']  input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']  FilePathField(ChoiceField) 文件選項,目錄下文件顯示在頁面中  path, 文件夾路徑  match=None, 正則匹配  recursive=False, 遞歸下面的文件夾  allow_files=True, 容許文件  allow_folders=False, 容許文件夾  required=True,  widget=None,  label=None,  initial=None,  help_text=''  GenericIPAddressField  protocol='both', both,ipv4,ipv6支持的IP格式  unpack_ipv4=False 解析ipv4地址,若是是::ffff:192.0.2.1時候,可解析爲192.0.2.1PSprotocol必須爲both才能啓用  SlugField(CharField) 數字,字母,下劃線,減號(連字符)  ...  UUIDField(CharField) uuid類型 複製代碼  內置字段

Django表單內置的Field類

對於每一個字段類,介紹其默認的widget,當輸入爲空時返回的值,以及採起何種驗證方式。‘規範化爲’表示轉換爲PYthon的何種對象。可用的錯誤信息鍵,表示該字段可自定義錯誤信息的類型(字典的鍵)。

  1. BooleanField
  • 默認的Widget:CheckboxInput
  • 空值:False
  • 規範化爲:Python的True或者False
  • 可用的錯誤信息鍵:required
  1. CharField
  • 默認的Widget:TextInput
  • 空值:與empty_value給出的任何值。
  • 規範化爲:一個Unicode 對象。
  • 驗證max_lengthmin_length,若是設置了這兩個參數。 不然,全部的輸入都是合法的。
  • 可用的錯誤信息鍵:min_length, max_length, required

有四個可選參數:

  • max_length,min_length:設置字符串的最大和最小長度。
  • strip:若是True(默認),去除輸入的前導和尾隨空格。
  • empty_value:用來表示「空」的值。 默認爲空字符串。
  1. ChoiceField
  • 默認的Widget:Select
  • 空值:''(一個空字符串)
  • 規範化爲:一個Unicode 對象。
  • 驗證給定的值是否在選項列表中。
  • 可用的錯誤信息鍵:required, invalid_choice

參數choices:用來做爲該字段選項的一個二元組組成的可迭代對象(例如,列表或元組)或者一個可調用對象。格式與用於和ORM模型字段的choices參數相同。

  1. TypedChoiceField

像ChoiceField同樣,只是還有兩個額外的參數:coerce和empty_value。

  • 默認的Widget:Select
  • 空值:empty_value參數設置的值。
  • 規範化爲:coerce參數類型的值。
  • 驗證給定的值在選項列表中存在而且能夠被強制轉換。
  • 可用的錯誤信息的鍵:required, invalid_choice
  1. DateField
  • 默認的Widget:DateInput
  • 空值:None
  • 規範化爲:datetime.date對象。
  • 驗證給出的值是一個datetime.date、datetime.datetime 或指定日期格式的字符串。
  • 錯誤信息的鍵:required, invalid

接收一個可選的參數:input_formats。一個格式的列表,用於轉換字符串爲datetime.date對象。

若是沒有提供input_formats,默認的輸入格式爲:

['%Y-%m-%d',      # '2006-10-25'
 '%m/%d/%Y', # '10/25/2006'  '%m/%d/%y'] # '10/25/06'

另外,若是你在設置中指定USE_L10N=False,如下的格式也將包含在默認的輸入格式中:

['%b %d %Y',      # 'Oct 25 2006'
 '%b %d, %Y', # 'Oct 25, 2006'  '%d %b %Y', # '25 Oct 2006'  '%d %b, %Y', # '25 Oct, 2006'  '%B %d %Y', # 'October 25 2006'  '%B %d, %Y', # 'October 25, 2006'  '%d %B %Y', # '25 October 2006'  '%d %B, %Y'] # '25 October, 2006'
  1. DateTimeField
  • 默認的Widget:DateTimeInput
  • 空值:None
  • 規範化爲:Python的datetime.datetime對象。
  • 驗證給出的值是一個datetime.datetime、datetime.date或指定日期格式的字符串。
  • 錯誤信息的鍵:required, invalid

接收一個可選的參數:input_formats

若是沒有提供input_formats,默認的輸入格式爲:

['%Y-%m-%d %H:%M:%S',    # '2006-10-25 14:30:59'
 '%Y-%m-%d %H:%M', # '2006-10-25 14:30'  '%Y-%m-%d', # '2006-10-25'  '%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59'  '%m/%d/%Y %H:%M', # '10/25/2006 14:30'  '%m/%d/%Y', # '10/25/2006'  '%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59'  '%m/%d/%y %H:%M', # '10/25/06 14:30'  '%m/%d/%y'] # '10/25/06'
  1. DecimalField
  • 默認的Widget:當Field.localize是False時爲NumberInput,不然爲TextInput。
  • 空值:None
  • 規範化爲:Python decimal對象。
  • 驗證給定的值爲一個十進制數。 忽略前導和尾隨的空白。
  • 錯誤信息的鍵:max_whole_digitsmax_digitsmax_decimal_places,max_value, invalid, required,min_value

接收四個可選的參數:

max_value,min_value:容許的值的範圍,須要賦值decimal.Decimal對象,不能直接給個整數類型。

max_digits:值容許的最大位數(小數點以前和以後的數字總共的位數,前導的零將被刪除)。

decimal_places:容許的最大小數位。

  1. DurationField
  • 默認的Widget:TextInput
  • 空值:None
  • 規範化爲:Python timedelta。
  • 驗證給出的值是一個字符串,並且能夠轉換爲timedelta對象。
  • 錯誤信息的鍵:required, invalid.
  1. EmailField
  • 默認的Widget:EmailInput
  • 空值:''(一個空字符串)
  • 規範化爲:Unicode 對象。
  • 使用正則表達式驗證給出的值是一個合法的郵件地址。
  • 錯誤信息的鍵:required, invalid

兩個可選的參數用於驗證,max_length 和min_length。

  1. FileField
  • 默認的Widget:ClearableFileInput
  • 空值:None
  • 規範化爲:一個UploadedFile對象,它封裝文件內容和文件名到一個對象內。
  • 驗證非空的文件數據已經綁定到表單。
  • 錯誤信息的鍵:missing, invalid, required, empty, max_length

具備兩個可選的參數用於驗證:max_length 和 allow_empty_file。

  1. FilePathField
  • 默認的Widget:Select
  • 空值:None
  • 規範化爲:Unicode 對象。
  • 驗證選擇的選項在選項列表中存在。
  • 錯誤信息的鍵:required, invalid_choice

這個字段容許從一個特定的目錄選擇文件。 它有五個額外的參數,其中的path是必須的:

path:要列出的目錄的絕對路徑。 這個目錄必須存在。

recursive:若是爲False(默認值),只用直接位於path下的文件或目錄做爲選項。若是爲True,將遞歸訪問這個目錄,其內全部的子目錄和文件都將做爲選項。

match:正則表達模式;只有具備與此表達式匹配的文件名稱才被容許做爲選項。

allow_files:可選。默認爲True。表示是否應該包含指定位置的文件。它和allow_folders必須有一個爲True。

allow_folders可選。默認爲False。表示是否應該包含指定位置的目錄。

  1. FloatField
  • 默認的Widget:當Field.localize是False時爲NumberInput,不然爲TextInput。
  • 空值:None
  • 規範化爲:Float 對象。
  • 驗證給定的值是一個浮點數。
  • 錯誤信息的鍵:max_value, invalid, required, min_value

接收兩個可選的參數用於驗證,max_value和min_value,控制容許的值的範圍。

  1. ImageField
  • 默認的Widget:ClearableFileInput
  • 空值:None
  • 規範化爲:一個UploadedFile 象,它封裝文件內容和文件名爲一個單獨的對象。
  • 驗證文件數據已綁定到表單,而且該文件是Pillow能夠解析的圖像格式。
  • 錯誤信息的鍵:missing, invalid, required, empty, invalid_image

使用ImageField須要安裝Pillow(pip install pillow)。若是在上傳圖片時遇到圖像損壞錯誤,一般意味着使用了Pillow不支持的格式。

  1. IntegerField
  • 默認的Widget:當Field.localize是False時爲NumberInput,不然爲TextInput。
  • 空值:None
  • 規範化爲:Python 整數或長整數。
  • 驗證給定值是一個整數。 容許前導和尾隨空格,相似Python的int()函數。
  • 錯誤信息的鍵:max_value, invalid, required, min_value

兩個可選參數:max_value和min_value,控制容許的值的範圍。

  1. GenericIPAddressField

包含IPv4或IPv6地址的字段。

  • 默認的Widget:TextInput
  • 空值:''(一個空字符串)
  • 規範化爲:一個Unicode對象。
  • 驗證給定值是有效的IP地址。
  • 錯誤信息的鍵:required, invalid

有兩個可選參數:protocol和unpack_ipv4

  1. MultipleChoiceField
  • 默認的Widget:SelectMultiple
  • 空值:[](一個空列表)
  • 規範化爲:一個Unicode 對象列表。
  • 驗證給定值列表中的每一個值都存在於選擇列表中。
  • 錯誤信息的鍵:invalid_list, invalid_choice, required
  1. TypedMultipleChoiceField

相似MultipleChoiceField,除了須要兩個額外的參數,coerce和empty_value。

  • 默認的Widget:SelectMultiple
  • 空值:empty_value
  • 規範化爲:coerce參數提供的類型值列表。
  • 驗證給定值存在於選項列表中而且能夠強制。
  • 錯誤信息的鍵:required, invalid_choice
  1. NullBooleanField
  • 默認的Widget:NullBooleanSelect
  • 空值:None
  • 規範化爲:Python None, False 或True 值。
  • 不驗證任何內容(即,它從不引起ValidationError)。

19.RegexField

  • 默認的Widget:TextInput
  • 空值:''(一個空字符串)
  • 規範化爲:一個Unicode 對象。
  • 驗證給定值與某個正則表達式匹配。
  • 錯誤信息的鍵:required, invalid

須要一個必需的參數:regex,須要匹配的正則表達式。

還能夠接收max_length,min_length和strip參數,相似CharField。

  1. SlugField
  • 默認的Widget:TextInput
  • 空值:''(一個空字符串)
  • 規範化爲:一個Unicode 對象。
  • 驗證給定的字符串只包括字母、數字、下劃線及連字符。
  • 錯誤信息的鍵:required, invalid

此字段用於在表單中表示模型的SlugField。

  1. TimeField
  • 默認的Widget:TextInput
  • 空值:None
  • 規範化爲:一個Python 的datetime.time 對象。
  • 驗證給定值是datetime.time或以特定時間格式格式化的字符串。
  • 錯誤信息的鍵:required, invalid

接收一個可選的參數:input_formats,用於嘗試將字符串轉換爲有效的datetime.time對象的格式列表。

若是沒有提供input_formats,默認的輸入格式爲:

'%H:%M:%S',     # '14:30:59'
'%H:%M', # '14:30'
  1. URLField
  • 默認的Widget:URLInput
  • 空值:''(一個空字符串)
  • 規範化爲:一個Unicode 對象。
  • 驗證給定值是個有效的URL。
  • 錯誤信息的鍵:required, invalid

可選參數:max_length和min_length

  1. UUIDField
  • 默認的Widget:TextInput
  • 空值:''(一個空字符串)
  • 規範化爲:UUID對象。
  • 錯誤信息的鍵:required, invalid
  1. ComboField
  • 默認的Widget:TextInput
  • 空值:''(一個空字符串)
  • 規範化爲:Unicode 對象。
  • 根據指定爲ComboField的參數的每一個字段驗證給定值。
  • 錯誤信息的鍵:required, invalid

接收一個額外的必選參數:fields,用於驗證字段值的字段列表(按提供它們的順序)。

>>> from django.forms import ComboField
>>> f = ComboField(fields=[CharField(max_length=20), EmailField()]) >>> f.clean('test@example.com') 'test@example.com' >>> f.clean('longemailaddress@example.com') Traceback (most recent call last): ... ValidationError: ['Ensure this value has at most 20 characters (it has 28).']
  1. MultiValueField
  • 默認的Widget:TextInput
  • 空值:''(一個空字符串)
  • 規範化爲:子類的compress方法返回的類型。
  • 根據指定爲MultiValueField的參數的每一個字段驗證給定值。
  • 錯誤信息的鍵:incomplete, invalid, required
  1. SplitDateTimeField
  • 默認的Widget:SplitDateTimeWidget
  • 空值:None
  • 規範化爲:Python datetime.datetime 對象。
  • 驗證給定的值是datetime.datetime或以特定日期時間格式格式化的字符串。
  • 錯誤信息的鍵:invalid_date, invalid, required, invalid_time

建立自定義字段

若是內置的Field真的不能知足你的需求,還能夠自定義Field。

只須要建立一個django.forms.Field的子類,並實現clean()和__init__()構造方法。__init__()構造方法須要接收前面提過的那些核心參數,好比widget、required,、label、help_text、initial。

還能夠經過覆蓋get_bound_field()方法來自定義訪問字段的方式。

Django ModelForm/Form修改默認的widgets控件屬性css樣式

Django 中利用ModelForm 能夠快速地利用數據庫對應的Model 子類來自動建立對應表單.

from django.db import models from django.forms import ModelForm   class Book(models.Model):  name = models.CharField(max_length=100)  description = models.CharField(max_length=100)  url= models.CharField(max_length=100)  authors = models.ManyToManyField(Author)

修改Form的widgtes屬性方法

from django import forms  class BookForm(forms.Form):  name = forms.CharField()  url = forms.URLField()  description = forms.CharField()

第一種:直接修改

class BookForm(forms.Form):  name = forms.CharField(widget=forms.TextInput(attrs={'class': 'special'}))  url = forms.URLField()  comment = forms.CharField(widget=forms.TextInput(attrs={'size': '40'}))

第二種:在表單定義中修改widget屬性

class CommentForm(forms.Form):  name = forms.CharField()  url = forms.URLField()  comment = forms.CharField()   name.widget.attrs.update({'class': 'special'})  comment.widget.attrs.update(size='40')

修改ModelForm的widgets屬性兩種方法:

第一種方法:

class BookForm(forms.ModelForm):  class Meta:  model = Book  fields = ['name', 'description']  widgets = {  'name': Textarea(attrs={'cols': 80, 'rows': 20}),  'description': Textarea(attrs={'cols': 80, 'rows': 20}),  }

第二種重寫__init__方法:

class BookForm(forms.ModelForm):  class Meta:  model = Book  def __init__(self, *args, **kwargs):  super().__init__(*args, **kwargs)  self.fields['name'].widget.attrs.update({'class': 'special'})  self.fields['description'].widget.attrs.update(size='40')

Django將在渲染輸出中包含額外屬性:

>>> f = CommentForm(auto_id=False) >>> f.as_table() <tr><th>Name:</th><td><input type="text" name="name" class="special" required></td></tr> <tr><th>Url:</th><td><input type="url" name="url" required></td></tr> <tr><th>Description:</th><td><input type="text" name="comment" size="40" required></td></tr>

批量添加樣式適用於Form和ModelForm

class BookForm(forms.ModelForm):  class Meta:  model = Book  def __init__(self, *args, **kwargs):  super().__init__(*args, **kwargs)  for field in iter(self.fields):  self.fields[field].widget.attrs.update({  'class': 'form-control'  })

ModelForm

一般在Django項目中,咱們編寫的大部分都是與Django 的模型緊密映射的表單。 舉個例子,你也許會有個Book 模型,而且你還想建立一個form表單用來添加和編輯書籍信息到這個模型中。 在這種狀況下,在form表單中定義字段將是冗餘的,由於咱們已經在模型中定義了那些字段。

基於這個緣由,Django 提供一個輔助類來讓咱們能夠從Django 的模型建立Form,這就是ModelForm。

modelForm定義

form與model的終極結合,會根據你model中的字段轉換成對應的form字段,而且並你生成標籤等操做。

好比你的models中的表是下面的內容:

class Book(models.Model):   nid = models.AutoField(primary_key=True)  title = models.CharField( max_length=32)  publishDate=models.DateField()  price=models.DecimalField(max_digits=5,decimal_places=2)  publish=models.ForeignKey(to="Publish",to_field="nid")  authors=models.ManyToManyField(to='Author',)  def __str__(self):  return self.title  class UserInfo(models.Model):  user = models.CharField(max_length=32)  tel = models.CharField(max_length=16,  validators=[validators.RegexValidator(r'1[3456789]\d{9}', message='號碼格式不對')])  passwd = models.CharField(max_length=32,  validators=[validators.MinLengthValidator(limit_value=6, message='密碼過短')])

modelform類的寫法。

class BookForm(forms.ModelForm):  r_password = forms.CharField() #想多驗證一些字段能夠單獨拿出來寫,按照form的寫法,寫在Meta的上面或者下面均可以  class Meta:  model = models.Book  # fields = ['title','price']  fields = "__all__" #['title,'price'] 指定字段生成form  # exclude=['title',] #排除字段  labels = {  "title": "書名",  "price": "價格"  }  error_messages = {  'title':{'required':'不能爲空',} #每一個字段的錯誤均可以寫  }  #若是models中的字段和我們須要驗證的字段對不齊的是,好比註冊時,我們須要驗證密碼和確認密碼兩個字段數據,可是後端數據庫就保存一個數據就行,那麼驗證是兩個,數據保存是一個,就能夠再接着寫form字段  r_password = forms.CharField() #一樣的,若是想作一些特殊的驗證定製,那麼和form一昂,也是那兩個鉤子(全局和局部),寫法也是form那個的寫法,直接在我們的類裏面寫:  #局部鉤子:  def clean_title(self):  pass   #全局鉤子  def clean(self):  pass  def __init__(self,*args,**kwargs): #批量操做  super().__init__(*args,**kwargs)  for field in self.fields:  #field.error_messages = {'required':'不能爲空'} #批量添加錯誤信息,這是都同樣的錯誤,不同的仍是要單獨寫。  self.fields[field].widget.attrs.update({'class':'form-control'})   class UserForm(forms.ModelForm):  pw1 = forms.CharField(min_length=6)  pw2 = forms.CharField(min_length=6)  def clean(self):  cleaned_data = super().clean()  pw1 = cleaned_data.get('pw1')  pw2 = cleaned_data.get('pw2')  if pw1 != pw2:  raise ValidationError('兩次密碼不一致')  return cleaned_data  class Meta:  model = models.UserInfo  # fields = '__all__'  exclude = ['passwd']  help_texts = {  'user': _('輸入用戶名'), # 這是另一種寫法  }  error_messages = {  'user':{  'required':'必填',  },  'tel':{  'required': '必填',  'invalid': '手機號碼格式不對'  }  }

class Meta下經常使用參數:

model = models.Book # 對應的Model中的類 fields = "__all__" # 字段,若是是__all__,就是表示列出全部的字段 exclude = None # 排除的字段 labels = None # 提示信息 help_texts = None # 幫助提示信息 widgets = None # 自定義插件 error_messages = None # 自定義錯誤信息  error_messages = {  'title':{'required':'不能爲空',...} #每一個字段的全部的錯誤均可以寫,...是省略的意思,複製黏貼我代碼的時候別忘了刪了... }

ModelForm的驗證

與普通的Form表單驗證類型相似,ModelForm表單的驗證在調用is_valid() 或訪問errors 屬性時隱式調用。

咱們能夠像使用Form類同樣自定義局部鉤子方法和全局鉤子方法來實現自定義的校驗規則。

若是咱們不重寫具體字段並設置validators屬性的話,ModelForm是按照模型中字段的validators來校驗的。

save()方法

每一個ModelForm還具備一個save()方法。 這個方法根據表單綁定的數據建立並保存數據庫對象。 ModelForm的子類能夠接受現有的模型實例做爲關鍵字參數instance;若是提供此功能,則save()將更新該實例。 若是沒有提供,save() 將建立模型的一個新實例。

form = 類名(initial={'headline': 'Initial headline'},instance=obj,data=request.POST,files=request.FILES)

若是不用ModelForm,編輯的時候得顯示以前的數據吧,還得挨個取一遍值,若是ModelForm,只須要加一個instance=obj(obj是要修改的數據庫的一條數據的對象)就能夠獲得一樣的效果 保存的時候要注意,必定要注意有這個對象(instance=obj),不然不知道更新哪個數據。

另外,咱們在調用save方法的時候,若是傳入一個commit=False,那麼只會生成這個模型的對象,而不會把這個對象真正的插入到數據庫中。好比表單上驗證的字段沒有包含模型中全部的字段,這時候就能夠先建立對象,再根據填充其餘字段,把全部字段的值都補充完成後,再保存到數據庫中。

# views from django.shortcuts import render from django.views.decorators.http import require_POST from django.http import HttpResponse from app01 import models from app01 import forms from django.forms.utils import ErrorDict  # 使用post方式 @require_POST def regist(request):  form = forms.UserForm(request.POST)  if form.is_valid():  user = form.save(commit=False)  user.passwd = form.cleaned_data.get('pw2')  user.save()  return HttpResponse('註冊成功')  print(form.errors.as_json())  return HttpResponse('註冊失敗')  def index(request):  if request.method == 'GET':  form_obj = BookForm()   return render(request,'index.html',{'form_obj':form_obj})   else:  form_obj = BookForm(request.POST)  if form_obj.is_valid():  # authors_obj = form_obj.cleaned_data.pop('authors')  # new_book_obj = models.Book.objects.create(**form_obj.cleaned_data)  # new_book_obj.authors.add(*authors_obj)  form_obj.save() #由於咱們再Meta中指定了是哪張表,因此它會自動識別,無論是外鍵仍是多對多等,都會自行處理保存,它完成的就是上面三句話作的事情,而且還有就是若是你驗證的數據比你後端數據表中的字段多,那麼他自會自動剔除多餘的不須要保存的字段,好比那個重複確認密碼就不要保存  return redirect('show')   else:  print(form_obj.errors)  return render(request,'index.html',{'form_obj':form_obj})

提供初始值

能夠在實例化一個表單時經過指定initial參數來提供表單中數據的初始值。

>>> article = Article.objects.get(pk=1) >>> article.headline 'My headline' >>> form = ArticleForm(initial={'headline': 'Initial headline'}, instance=article) >>> form['headline'].value() 'Initial headline'

文件上傳

前端代碼的實現

  1. 在前端中,咱們須要填入一個form標籤,而後在這個form標籤中指定enctype="multipart/form-data",否則就不能上傳文件。
  2. form標籤中添加一個input標籤,而後指定input標籤的name,以及type="file"

以上兩步的示例代碼以下:

<form action="" method="post" enctype="multipart/form-data">  {% csrf_token %}  <input type="file", name="myfile">  <input type="submit", value="提交"> </form>

普通版的文件上傳

views: def save_file(file):  with open('upload', 'wb') as f:  # chunks()是把一個文件切成塊,默認的這個值是2.5M,固然這個值是能夠調節的  for chunk in file.chunks():  f.write(chunk)   def upload_1(request):  if request.method == 'GET':  return render(request, 'upload_1.html')  file = request.FILES.get('myfile')  save_file(file)  return HttpResponse('success')

這樣一來,文件的上傳位置就是項目的根目錄,不利於管理。

使用模型和MdelForm來處理上傳的文件

步驟:

  1. settings.py中增長
# 圖片文件上傳 MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 若是指定了MEDIA_ROOT,就不須要在FielField中指定upload_to,他會自動的將文件上傳到MEDIA_ROOT的目錄下。若是咱們同時指定MEDIA_ROOT和upload_to,那麼會將文件上傳到MEDIA_ROOT下的upload_to文件夾中。
  1. model裏添加模型,
from django.db import models from django.utils.timezone import now from django.core import validators  class Book(models.Model):  name = models.CharField(max_length=32)  date = models.DateField(default=now)  img = models.ImageField(upload_to='%Y/%m/%d') # 注意:使用ImageField,必需要先安裝Pillow庫:pip install pillow  # 使用ImageField字段時,Django會判斷上傳的文件是不是圖片的格式(除了判斷後綴名,還會判斷是不是可用的圖片)。若是不是,那麼就會驗證失敗。  file = models.FileField(upload_to='file'validators=[validators.FileExtensionValidator(['pdf'])]) # validators=[validators.FileExtensionValidator(['pdf'])]表示,只容許上傳後綴名是pdf的文件
  1. ModelForm進行文件擴展名的限制

限制上傳的文件的拓展名,那麼咱們就須要用到表單來進行限制。咱們能夠使用普通的Form表單,也能夠使用ModelForm,直接從模型中讀取字段。

froms:

from django.forms import ModelForm from app01 import models  class BookForm(ModelForm):  class Meta:  model = models.Book  fields = ['name', 'img', 'file']  labels = {  'name': '書名',  'img': '圖片地址',  'file': '文件地址'  }

views:

from django.shortcuts import render, HttpResponse from app01.forms import BookForm  def upload_2(request):  if request.method == 'GET':  forms = BookForm()   return render(request, 'upload_2.html', {'forms':forms})   forms = BookForm(request.POST, files=request.FILES) # 注意必定要加上files=request.FILES否則,驗證的時候會通不過,找不到上傳的文件。  if forms.is_valid():  forms.save()  return HttpResponse('success')  return render(request, 'upload_2.html', {'forms':forms})

前端代碼

<form action="" method="post" enctype="multipart/form-data"> {% csrf_token %}  {% for foo in forms %}  <label for="{{ foo.id_for_label }}">{{ foo.label }}</label>  {{ foo }}  <p>{{ foo.errors.0 }}</p>  {% endfor %}  <input type="submit" value="提交">  </form>
  1. url路由中配置
from django.conf.urls import url from django.contrib import admin from app01 import views from django.conf import settings from django.conf.urls.static import static  urlpatterns = [  url(r'^admin/', admin.site.urls),  url(r'^upload_1', views.upload_1, name='upload1'),  url(r'^upload_2', views.upload_2, name='upload2'), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # 若是單純的是上傳,文件並不用來顯示或者讀取,就不用加這個,若是前端要展現的話那必需要加上
  1. 前端的展現
普通版
<img src="/media/{{ book_obj.img }}" alt=""> 上下文管理器版 必須先在settings中的TEMPLATES下的context_processors中添加django.template.context_processors.media <img src="{{ MEDIA_URL }}{{ book_obj.img }}" alt="">
  1. 更新上傳的文件

更新上傳了的文件(注意,只是會更新數據庫中那個字段保存的文件的路徑,可是以前上傳的文件是不會被自動刪除的,須要咱們自行再寫邏輯來刪除以前上傳錯的或者須要被覆蓋的文件。還有就是若是上傳的文件名稱是相同的那麼你會發現數據庫中這個字段的路徑後面的文件名稱會出現一個亂起八糟的隨機字符串,這是由於上傳的文件名稱衝突了,django爲了解決這個衝突,給你改了一下你的文件名稱。)

views

class Edit1(View):  def __init__(self):  self.book_obj = book_obj = Book.objects.filter(pk=1).first()  def get(self, request):  forms = BookForm(instance=self.book_obj)  return render(request, 'edit1.html', {'forms': forms})  def post(self, request):  forms = BookForm(request.POST, files=request.FILES, instance=self.book_obj)  if forms.is_valid():  forms.save()  return HttpResponse('success')  return render(request, 'edit1.html', {'forms': forms})

文件的下載

前端頁面

<div>  <a href="{% url 'download' %}">文件下載</a> </div>

view視圖函數的寫法有三種:

  1. HttpResponse的寫法
def download1(request):  # 從數據庫取到這個對象  file_obj = Book.objects.filter(pk=2).first()  # 打開這這個文件,注意這裏的路徑  file = open('{}/{}'.format(settings.MEDIA_ROOT,file_obj.file), 'rb')  # 將文件句柄給HttpResponse對象  httprespon = HttpResponse(file)  # 設置頭信息,告訴瀏覽器這是個文件  httprespon['Content-Type'] = 'application/octet-stream'  # 這是文件的簡單描述,注意寫法就是這個固定的寫法  httprespon['Content-Disposition'] = 'attachment;filename="{}"'.format(file_obj.name)  return httprespon

HttpResponse會直接使用迭代器對象,將迭代器對象的內容存儲城字符串,而後返回給客戶端,同時釋放內存。當文件變大看出這是一個很是耗費時間和內存的過程。

  1. StreamingHttpResponse
from django.http import StreamingHttpResponse def download2(request):  # 從數據庫取到這個對象  file_obj = Book.objects.filter(pk=2).first()  # 打開這個文件,注意路徑  file = open('{}/{}'.format(settings.MEDIA_ROOT, file_obj.file), 'rb')  # 將文件的句柄給StreamingHttpResponse  httprespon = StreamingHttpResponse(file)  # 設置頭信息,告訴瀏覽器這是個文件  httprespon['Content-Type'] = 'application/octet-stream'  # 這是文件的簡單描述,注意寫法就是這個固定的寫法  httprespon['Content-Disposition'] = 'attachment;filename="{}"'.format(file_obj.name)  return httprespon

StreamingHttpResponse是將文件內容進行流式傳輸,數據量大能夠用這個方法

  1. FileResponse
from django.http import FileResponse def download3(request):  # 從數據庫中取到這個對象  file_obj = Book.objects.filter(pk=2).first()  # 打開這個文件的,注意路徑  file = open('{}/{}'.format(settings.MEDIA_ROOT, file_obj.file), 'rb')  # 將文件句柄傳給FileRespone  httprespone = FileResponse(file)  # 設置響應頭信息,告訴瀏覽器這是個文件  httprespone['Content-Type'] = 'application/octet-stream'  # 這是文件的描述,注意寫法就是固定的寫法  httprespone['Content-Disposition'] = 'attachment;filename={}'.format(file_obj.name)  return httprespone

推薦使用FileResponse,從源碼中能夠看出FileResponse是StreamingHttpResponse的子類,內部使用迭代器進行數據流傳輸。

django的User模型和四種擴展/重寫方法

User模型 User模型是這個框架的核心部分。他的完整的路徑是在django.contrib.auth.models.User。如下對這個User對象作一個簡單瞭解:

字段: 內置的User模型擁有如下的字段:

  1. username: 用戶名。150個字符之內。能夠包含數字和英文字符,以及_、@、+、.和-字符。不能爲空,且必須惟一!
  2. first_name:歪果仁的first_name,在30個字符之內。能夠爲空。
  3. last_name:歪果仁的last_name,在150個字符之內。能夠爲空。
  4. email:郵箱。能夠爲空。
  5. password:密碼。通過哈希事後的密碼。
  6. groups:分組。一個用戶能夠屬於多個分組,一個分組能夠擁有多個用戶。groups這個字段是跟Group的一個多對多的關係。
  7. user_permissions:權限。一個用戶能夠擁有多個權限,一個權限能夠被多個用戶全部用。和Permission屬於一種多對多的關係。
  8. is_staff:是否能夠進入到admin的站點。表明是不是員工。
  9. is_active:是不是可用的。對於一些想要刪除帳號的數據,咱們設置這個值爲False就能夠了,而不是真正的從數據庫中刪除。
  10. is_superuser:是不是超級管理員。若是是超級管理員,那麼擁有整個網站的全部權限。
  11. last_login:上次登陸的時間。
  12. date_joined:帳號建立的時間。

登陸驗證:

Django的驗證系統已經幫咱們實現了登陸驗證的功能。經過django.contrib.auth.authenticate便可實現。這個方法只能經過username和password來進行驗證。示例代碼以下:

from django.contrib.auth import authenticate user = authenticate(username='Tom', password='111111') # 若是驗證經過了,那麼就會返回一個user對象。 if user is not None:  # 執行驗證經過後的代碼 else:  # 執行驗證沒有經過的代碼。

擴展用戶模型:

Django內置的User模型雖然已經足夠強大了。可是有時候仍是不能知足咱們的需求。好比在驗證用戶登陸的時候,他用的是用戶名做爲驗證,而咱們有時候須要經過手機號碼或者郵箱來進行驗證。還有好比咱們想要增長一些新的字段。那麼這時候咱們就須要擴展用戶模型了。擴展用戶模型有多種方式。

1. 設置Proxy模型:

做用: 給模型增長操做方法

侷限: 不能增長或減小User模型的字段

好處: 不破壞原來的User模型的表結構

若是你對Django提供的字段,以及驗證的方法都比較滿意,沒有什麼須要改的。可是隻是須要在他原有的基礎之上增長一些操做的方法。那麼建議使用這種方式。示例代碼以下:

# models.py class Person(User):  # 若是模型是一個代理模型  # 那麼就不能在這個模型中添加新的Field  # telephone = models.CharField(max_length=11) # 錯誤寫法  class Meta:  proxy = True   # proxy正確用法是給模型添加自定義方法  # 如添加列出黑名單的方法  def get_blacklist(self):  return self.objects.filter(is_active=False)

2. 一對一外鍵:

做用: 給模型增長新的字段, 新方法

侷限: 只能增長, 不能減小字段, 不能修改戶驗證方法: authenticate

好處: 不破壞原來的User模型的表結構

若是你對用戶驗證方法authenticate沒有其餘要求,就是使用usernamepassword便可完成。可是想要在原來模型的基礎之上添加新的字段,那麼能夠使用一對一外鍵的方式。示例代碼以下:

# models.py from django.contrib.auth.models import User from django.db import models from django.dispatch import receiver from django.db.models.signals import post_save  class UserExtension(models.Model):  user = models.OneToOneField(User,on_delete=models.CASCADE,related_name='extension')  birthday = models.DateField(null=True,blank=True)  school = models.CharField(max_length=100)   @receiver(post_save,sender=User) def create_user_extension(sender,instance,created,**kwargs):  if created:  UserExtension.objects.create(user=instance)  else:  instance.extension.save()

以上定義一個UserExtension的模型,而且讓她和User模型進行一對一的綁定,之後咱們新增的字段,就添加到UserExtension上。而且還寫了一個接受保存模型的信號處理方法,只要是User調用了save方法,那麼就會建立一個UserExtension和User進行綁定。

# views.py from django.contrib.auth.models import User from django.http import HttpResponse   def one_to_one_view(request):  user = User.objects.create_user(username='Tom',email='tom@qq.com',password='111111')  # 給擴展的字段設置值  user.extension.school = 'Harvard'  user.save()  return HttpResponse('一對一擴展User模型')

3. 繼承自AbstractUser

做用: 給模型增長新的字段, 修改戶驗證方法: authenticate

侷限: 只能增長, 不能減小字段

壞處: 破壞了原來的User模型的表結構

對於authenticate不滿意,而且不想要修改原來User對象上的一些字段,可是想要增長一些字段,那麼這時候能夠直接繼承自django.contrib.auth.models.AbstractUser,其實這個類也是django.contrib.auth.models.User的父類。好比咱們想要在原來User模型的基礎之上添加一個telephone和school字段。示例代碼以下:

# models.py from django.contrib.auth.models import AbstractUser class User(AbstractUser):  telephone = models.CharField(max_length=11,unique=True)  school = models.CharField(max_length=100)  # 指定telephone做爲USERNAME_FIELD, 而不是原來的username字段, 因此username要重寫  username = models.CharField(max_length=150)   # 指定telephone做爲USERNAME_FIELD,之後使用authenticate  # 函數驗證的時候,就能夠根據telephone來驗證  # 而不是原來的username  USERNAME_FIELD = 'telephone'  # USERNAME_FIELD對應的'telephone'字段和密碼字段默認是必須的字段  # 下[]能夠添加其它必須的字段, 好比['username', 'email']  REQUIRED_FIELDS = []   # 從新定義Manager對象,在建立user的時候使用telephone和  # password,而不是使用username和password  objects = UserManager()   # 重寫UserManager from django.contrib.auth.models import BaseUserManager class UserManager(BaseUserManager):  use_in_migrations = True   def _create_user(self, telephone, password, **extra_fields):  if not telephone:  raise ValueError("請填入手機號碼!")  if not password:  raise ValueError("請填入密碼!")  user = self.model(telephone=telephone, **extra_fields)  user.set_password(password)  user.save(using=self._db)  return user   def create_user(self, telephone, password, **extra_fields):  extra_fields.setdefault('is_staff', False)  extra_fields.setdefault('is_superuser', False)  return self._create_user(telephone, password, **extra_fields)   def create_superuser(self, telephone, password, **extra_fields):  extra_fields.setdefault('is_staff', True)  extra_fields.setdefault('is_superuser', True)   if extra_fields.get('is_staff') is not True:  raise ValueError('Superuser must have is_staff=True.')  if extra_fields.get('is_superuser') is not True:  raise ValueError('Superuser must have is_superuser=True.')   return self._create_user(telephone, password, **extra_fields)

而後再在settings中配置好

# settings.py AUTH_USER_MODEL = 'youappname.User'

這種方式由於破壞了原來User模型的表結構,因此必需要在第一次migrate前就先定義好。

4. 繼承自AbstractBaseUser模型:

做用: 給模型增長或減小字段, 修改戶驗證方法: authenticate

壞處: 破壞了原來的User模型的表結構

注意: 繼承自AbstractBaseUser同時還要繼承PermissionsMixin

若是你想修改默認的驗證方式,而且對於原來User模型上的一些字段不想要,那麼能夠自定義一個模型,而後繼承自AbstractBaseUser,再添加你想要的字段。這種方式會比較麻煩,最好是肯定本身對Django比較瞭解才推薦使用。步驟以下:

  1. 建立模型。示例代碼以下:

    # models.py from django.contrib.auth.base_user import AbstractBaseUser from django.contrib.auth.models import PermissionsMixin from django.db import models   class User(AbstractBaseUser,PermissionsMixin):  email = models.EmailField(unique=True)  username = models.CharField(max_length=150)  telephone = models.CharField(max_length=11,unique=True)  is_staff = models.BooleanField(default=False)  is_active = models.BooleanField(default=True)   USERNAME_FIELD = 'telephone'  REQUIRED_FIELDS = []   # 這裏的UserManager同方法3, 須要重寫  objects = UserManager()   def get_full_name(self):  return self.username   def get_short_name(self):  return self.username

    其中password和last_login是在AbstractBaseUser中已經添加好了的,咱們直接繼承就能夠了。而後咱們再添加咱們想要的字段。好比email、username、telephone等。這樣就能夠實現本身想要的字段了。可是由於咱們重寫了User,因此應該儘量的模擬User模型:

    1. USERNAME_FIELD:用來描述User模型名字字段的字符串,做爲惟一的標識(驗證字段)。若是沒有修改,那麼會使用USERNAME來做爲惟一字段。
    2. REQUIRED_FIELDS:一個字段名列表,用於當經過createsuperuser管理命令建立一個用戶時除了驗證的字段還有那些字段是必須填的,不包括驗證字段。
    3. is_active:一個布爾值,用於標識用戶當前是否可用。
    4. get_full_name():獲取完整的名字。
    5. get_short_name():一個比較簡短的用戶名
  2. 從新定義UserManager:咱們還須要定義本身的UserManager,由於默認的UserManager在建立用戶的時候使用的是usernamepassword,那麼咱們要替換成telephone。示例代碼以下:

    # models.py from django.contrib.auth.base_user import BaseUserManager   # 重寫UserManager class UserManager(BaseUserManager):  use_in_migrations = True   def _create_user(self, telephone, password, **extra_fields):  if not telephone:  raise ValueError("請填入手機號碼!")  if not password:  raise ValueError("請填入密碼!")  user = self.model(telephone=telephone, **extra_fields)  user.set_password(password)  user.save(using=self._db)  return user   def create_user(self, telephone, password, **extra_fields):  extra_fields.setdefault('is_staff', False)  extra_fields.setdefault('is_superuser', False)  return self._create_user(telephone, password, **extra_fields)   def create_superuser(self, telephone, password, **extra_fields):  extra_fields.setdefault('is_staff', True)  extra_fields.setdefault('is_superuser', True)   if extra_fields.get('is_staff') is not True:  raise ValueError('Superuser must have is_staff=True.')  if extra_fields.get('is_superuser') is not True:  raise ValueError('Superuser must have is_superuser=True.')   return self._create_user(telephone, password, **extra_fields)
  3. 在建立了新的User模型後,還須要在settings中配置好。配置AUTH_USER_MODEL='appname.User'

    # settings.py AUTH_USER_MODEL = 'youappname.User'
  4. 如何使用這個自定義的模型:好比之後咱們有一個Article模型,須要經過外鍵引用這個User模型,那麼能夠經過如下兩種方式引用。 第一種就是直接將User導入到當前文件中。示例代碼以下:

    # models.py from django.db import models from myauth.models import User class Article(models.Model):  title = models.CharField(max_length=100)  content = models.TextField()  author = models.ForeignKey(User, on_delete=models.CASCADE)

    這種方式是能夠行得通的。可是爲了更好的使用性,建議仍是將User抽象出來,使用settings.AUTH_USER_MODEL來表示。示例代碼以下:

    # models.py from django.db import models from django.conf import settings class Article(models.Model):  title = models.CharField(max_length=100)  content = models.TextField()  author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

    這種方式由於破壞了原來User模型的表結構,因此必需要在第一次migrate前就先定義好。

服務器的安裝部署

MYSQL(mariadb)

MariaDB數據庫管理系統是MySQL的一個分支,主要由開源社區在維護,採用GPL受權許可。 開發這個分支的緣由之一是:甲骨文公司收購了MySQL後,有將MySQL閉源的潛在風險,所以社區採用分支的方式來避開這個風險。 MariaDB的目的是徹底兼容MySQL,包括API和命令行,使之能輕鬆成爲MySQL的代替品。

方法一:yum安裝mariadb

  1. 添加 MariaDB yum 倉庫

    首先在 RHEL/CentOS 和 Fedora 操做系統中添加 MariaDB 的 YUM 配置文件 MariaDB.repo 文件

    #編輯建立mariadb.repo倉庫文件 vi /etc/yum.repos.d/MariaDB.repo

    其次添加repo倉庫的配置

    [mariadb] name = MariaDB baseurl = http://yum.mariadb.org/10.1/centos7-amd64 gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB gpgcheck=1
  2. 在Centos7中安裝MariaDB

    MariaDB倉庫地址添加好後,一條命令進行安裝

    yum install MariaDB-server MariaDB-client -y
  3. 啓動MariaDB相關的命令

    mariadb數據庫的相關命令是:
    systemctl start mariadb #啓動MariaDB systemctl stop mariadb #中止MariaDB systemctl restart mariadb #重啓MariaDB systemctl enable mariadb #設置開機啓動
  4. 初始化``mysql`

    在確認 MariaDB 數據庫軟件程序安裝完畢併成功啓動後請不要當即使用。爲了確保數據 庫的安全性和正常運轉,須要先對數據庫程序進行初始化操做。這個初始化操做涉及下面 5 個 步驟。
    ➢ 設置 root 管理員在數據庫中的密碼值(注意,該密碼並不是 root 管理員在系統中的密 碼,這裏的密碼值默認應該爲空,可直接按回車鍵)。 ➢ 設置 root 管理員在數據庫中的專有密碼。 ➢ 隨後刪除匿名帳戶,並使用 root 管理員從遠程登陸數據庫,以確保數據庫上運行的業 務的安全性。 ➢ 刪除默認的測試數據庫,取消測試數據庫的一系列訪問權限。 ➢ 刷新受權列表,讓初始化的設定當即生效。

    確保mariadb服務器啓動後,執行命令初始化

    mysql_secure_installation

    初始化mysql

    1

    2

    3

    4

    5

    6

  5. mysql基本命令

    修改mysql密碼

    MariaDB [(none)]> set password = PASSWORD('redhat123');

    生產環境裏不會死磕root用戶,爲了數據庫的安全以及和其餘用戶協同管理數據庫,就須要建立其餘數據庫帳戶,而後分配權限,知足工做需求。

    MariaDB [(none)]> create user yuchao@'127.0.0.1' identified by 'redhat123'; MariaDB [(none)]> use mysql; MariaDB [mysql]> select host,user,password from user where user='yuchao';

    切換普通用戶yuchao,查看數據庫信息,發現沒法看到完整的數據庫列表

    [root@master ~]# mysql -uyuchao -p -h 127.0.0.1 MariaDB [(none)]> show databases;

    數據庫權限設置

    mysql使用grant命令對帳戶進行受權,grant命令常見格式以下

    grant 權限 on 數據庫.表名 to 帳戶@主機名            對特定數據庫中的特定表受權
    grant 權限 on 數據庫.* to 帳戶@主機名   對特定數據庫中的全部表給與受權 grant 權限1,權限2,權限3 on *.* to 帳戶@主機名    對全部庫中的全部表給與多個受權 grant all privileges on *.* to 帳戶@主機名    對全部庫和全部表受權全部權限

    退出數據庫,使用root登陸,開始權限設置

    [root@master ~]# mysql -uroot -p MariaDB [(none)]> use mysql; MariaDB [(none)]> grant all privileges on *.* to yuchao@127.0.0.1; MariaDB [mysql]> show grants for yuchao@127.0.0.1;

    移除權限

    MariaDB [(none)]> revoke all privileges on *.* from yuchao@127.0.0.1;

    配置mysql

    1. 中文編碼設置,編輯mysql配置文件/etc/my.cnf,下入如下內容

      [mysqld] character-set-server=utf8 collation-server=utf8_general_ci log-error=/var/log/mysqld.log [client] default-character-set=utf8 [mysql] default-character-set=utf8
    2. 受權配置

      遠程鏈接設置哦設置全部庫,全部表的全部權限,賦值權限給全部ip地址的root用戶
      mysql > grant all privileges on *.* to root@'%' identified by 'password'; #建立用戶 mysql > create user 'username'@'%' identified by 'password'; #刷新權限 flush privileges;

    數據庫的備份與恢復

    mysqldump命令用於備份數據庫數據

    [root@master ~]# mysqldump -u root -p --all-databases > /tmp/db.dump 導出db一、db2兩個數據庫的全部數據 mysqldump -uroot -proot --databases db1 db2 >/tmp/user.sql

    進入mariadb數據庫,刪除一個db

    [root@master ~]# mysql -uroot -p MariaDB [(none)]> drop database s11;

    進行數據恢復,吧剛纔重定向備份的數據庫文件導入到mysql中

    [root@master ~]# mysql -uroot -p < /tmp/db.dump

    MySQL數據庫的主從複製方案,是其自帶的功能,而且主從複製並非複製磁盤上的數據庫文件,而是經過binlog日誌複製到須要同步的從服務器上。

    MySQL數據庫支持單向、雙向、鏈式級聯,等不一樣業務場景的複製。在複製的過程當中,一臺服務器充當主服務器(Master),接收來自用戶的內容更新,而一個或多個其餘的服務器充當從服務器(slave),接收來自Master上binlog文件的日誌內容,解析出SQL,從新更新到Slave,使得主從服務器數據達到一致。

    主從複製的邏輯有如下幾種

    一主一從,單向主從同步模式,只能在Master端寫入數據

    一主多從

    雙主主複製邏輯架構,此架構能夠在Master1或Master2進行數據寫入,或者兩端同事寫入(特殊設置)

    在生產環境中,MySQL主從複製都是異步的複製方式,即不是嚴格的實時複製,可是給用戶的體驗都是實時的。
    MySQL主從複製集羣功能使得MySQL數據庫支持大規模高併發讀寫成爲可能,且有效的保護了服務器宕機的數據備份。

應用場景

利用複製功能當Master服務器出現問題時,咱們能夠人工的切換到從服務器繼續提供服務,此時服務器的數據和宕機時的數據幾乎徹底一致。
複製功能也可用做數據備份,可是若是人爲的執行drop,delete等語句刪除,那麼從庫的備份功能也就失效了.

主從機制實現原理

(1) master將改變記錄到二進制日誌(binary log)中(這些記錄叫作二進制日誌事件,binary log events); 
(2) slave將master的binary log events拷貝到它的中繼日誌(relay log); (3) slave重作中繼日誌中的事件,將改變反映它本身的數據。

master主庫配置

#查看數據庫狀態 systemctl status mariadb #停mariadb systemctl stop mariadb  #修改配置文件 vim /etc/my.cnf #修改內容 #解釋:server-id服務的惟一標識(主從之間都必須不一樣);log-bin啓動二進制日誌名稱爲mysql-bin   [mysqld]   server-id=1   log-bin=mysql-bin  #重啓mariadb systemctl start mariadb 

master主庫添加從庫帳號

1.新建用於主從同步的用戶chaoge,容許登陸的從庫是'192.168.178.130' create user 'chaoge'@'192.168.178.130' identified by 'redhat';  2.#題外話:若是提示密碼太簡單不復合策略加在前面加這句 mysql> set global validate_password_policy=0;  3.給從庫帳號受權,說明給chaoge從庫複製的權限,在192.168.178.130機器上覆制 grant replication slave on *.* to 'chaoge'@'192.168.178.130'; #檢查主庫建立的複製帳號 select user,host from mysql.user; #檢查受權帳號的權限 show grants for chaoge@'192.168.178.130';  實現對主數據庫鎖表只讀,防止數據寫入,數據複製失敗 flush table with read lock;  4.檢查主庫的狀態 MariaDB [(none)]> show master status -> ; +------------------+----------+--------------+------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | +------------------+----------+--------------+------------------+ | mysql-bin.000001 | 575 | | | +------------------+----------+--------------+------------------+ 1 row in set (0.00 sec)  File是二進制日誌文件名,Position 是日誌開始的位置。後面從庫會用到 後面從庫會用到 後面從庫會用到!!!!!!    5.鎖表後,必定要單獨再打開一個SSH窗口,導出數據庫的全部數據,  [root@oldboy_python ~ 19:32:45]#mysqldump -uroot -p --all-databases > /data/all.sql  6.確保數據導出後,沒有數據插入,完畢再查看主庫狀態  show master status;  7.導出數據完畢後,解鎖主庫,恢復可寫;  unlock tables;  8.將備份導出的數據scpSlave數據庫  scp /data/all.sql root@192.168.178.130:/data/ 

slave從庫配置

1.設置server-id值並關閉binlog功能參數 數據庫的server-id在主從複製體系內是惟一的,Slaveserver-id要與主庫和其餘從庫不一樣,而且註釋掉Slavebinlog參數。 2.所以修改Slave/etc/my.cnf,寫入 [mysqld] server-id=3 3.重啓數據庫 systemctl restart mariadb 4.檢查Slava從數據庫的各項參數 show variables like 'log_bin'; show variables like 'server_id'; 5.恢復主庫Master的數據導入到Slave導入數據(注意sql文件的路徑) mysql>source /data/all.sql; 方法二: #mysql -uroot -p < abc.sql 6.配置複製的參數,Slave從庫鏈接Master主庫的配置 mysql > change master to master_host='192.168.178.129', master_user='chaoge', master_password='redhat', master_log_file='mysql-bin.000001', master_log_pos=575; 7.啓動從庫的同步開關,測試主從複製的狀況 start slave; 8.查看複製狀態 show slave status\G; 

檢查主從複製是否成功的關鍵在於

MariaDB [(none)]> show slave status\G *************************** 1. row ***************************  Slave_IO_State: Waiting for master to send event  Master_Host: 192.168.119.10  Master_User: chaoge  Master_Port: 3306  Connect_Retry: 60  Master_Log_File: mysql-bin.000001  Read_Master_Log_Pos: 1039  Relay_Log_File: slave-relay-bin.000002  Relay_Log_Pos: 537  Relay_Master_Log_File: mysql-bin.000001  Slave_IO_Running: Yes  Slave_SQL_Running: Yes 

注意此處還未配置從庫的只讀模式,只需在slave服務器上配置/etc/my.cnf,加上如下配置,而且在slave上建立普通用戶,使用普通用戶主從同步便可達到只讀的效果

若是用root用戶,沒法達到readonly,這是一個坑

[mysqld] character-set-server=utf8 collation-server=utf8_general_ci log-error=/var/log/mysqld.log server-id=3 read-only=true [client] default-character-set=utf8 [mysql] default-character-set=utf8
相關文章
相關標籤/搜索