Django model總結(上)

Django model是django框架中處於比較核心的一個部位,準備分三個博客從不同的方面分別進行闡述,本文為《上篇》,主要對【a】Model的基本流程,比如它的創建,遷移等;默認行為,以及用定製的行為來覆蓋默認的行為;遷移文件相關的操作,比如不同的創建遷移文件的方法,對遷移文件進行重命名,將多個遷移文件壓縮成一個遷移文件,遷移文件中的變量的含義,以及遷移文件的回滾【b】數據類型,不同model之間的關係,以及對model的事務管理【c】一些用於減輕數據庫與model之間交互作業的工具,比如用fixture文件對model數據進行備份和加載【d】支持軟件observer pattern的signal【e】在默認位置以外的配置以及對多種數據庫的配置及其在model中的應用。

Django model與遷移流程

Django的主要存儲技術是利用關係型數據庫,所以Django的model對應的是數據庫中的table,該表的表名默認是<app_name>_<model_name>,而model的實例則是該表的一個記錄。因為跟數據打交道,所以model經常需要不斷的更改,而管理這樣的變動是通過遷移文件實現的。

創建Django model

Django model是在每個app中的models.py中,這個models.py文件是當app創建時候就自動創建的。

#demo
from django.db import models
from datetime import date,timedelta
from django.utils import timezone 
# Create your models here.
class Stores(models.Model):
    name=models.CharField(max_length=30,unique_for_date='date_lastupdate',db_column='my_custom_name')
    #id=models.AutoField(primary_key=True) 
    #objects=models.Manage()
    address=models.CharField(max_length=30,unique=True)
    city=models.CharField(max_length=30)
    state=models.CharField(max_length=2)
    email=models.EmailField(help_text='valide email please')
    date=models.DateField(default=date.today)
    datetime=models.DateTimeField(default=timezone.now)
    date_lastupdate=models.DateField(auto_now=True)
    date_added=models.DateField(auto_now_add=True)
    timestamp_lastupdated=models.DateTimeField(auto_now=True)
    timestamp_added=models.DateTimeField(auto_now_add=True)
    testeditable=models.CharField(max_length=3,editable=False)
    def __str__(self):
        return self.name 

以上代碼就創建了一個model,其中被注釋掉的id的AutoField被默認自動添加;被注釋掉的objects是model的Manager,負責該Model的增刪查改;還包含了很多字段如CharField,DateField等;__str__方法方便model實例的顯示;以上具體內容將在下面各個位置進行總結。

最後應注意該app必須被包含在project的settings.py的INSTALLED_APPS列表中。

遷移及基本流程

Django model 數據類型

Django model中的數據類型與在兩個層面有關:底層數據庫層面Django/Python層面

當創建model後,並進行首次遷移,Django產生並執行DDL(Data definition language)來創建數據庫表,這個DDL包含了model中各字段的定義,比如Django model的IntegerField被轉換為底層數據庫的INTEGER,這就意味着如果後面要修改該字段的類型,就必須再次進行遷移操作。

model的數據類型也有在Django/Python層面進行操作的,比如

ITEM_SIZES=(
    ('S','Small'),
    ('M','Medium'),
    ('L','Large'),
    ('P','Portion')
)
class Menu(models.Model):
    name=models.CharField(max_length=30)
    def __str__(self):
        return self.name 
class Item(models.Model):
    menu=models.ForeignKey(Menu,on_delete=models.CASCADE,related_name='menus')
    name=models.CharField(max_length=30)
    description=models.CharField(max_length=100)
    size=models.CharField(choices=ITEM_SIZES,max_length=1)
    colories=models.IntegerField(validators=[calorie_watcher])
    def __str__(self):
        return self.name 

這裡,Item的size中有choices,這個限制就是在python層面進行限制的,我們可以通過python manage.py banners 0001來查看(banner 是app名字,0001是首次遷移的前綴):

(env) F:\PycharmProject\webDev\Django project\testBegin>python manage.py sqlmigrate banners 0001
BEGIN;
--
-- Create model Item
--
CREATE TABLE "banners_item" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(30) NOT NULL, "description" varchar(100) NOT NULL, "size" varchar(1) NOT NULL, "menu_id" integer NOT NULL REFERENCES "banners_menu" ("id") DEFERRABLE INITIALLY DEFERRED);
COMMIT;

可以發現底層SQL中關於size並沒有choice的限制,也就是該限制是在python層面中實施的,即如果後續更改choices,可以不再進行數據遷移。

Data type Django model Data type Django model
Binary BinaryField Boolean BooleanField
Date/time DateField Date/time TimeField
Date/time DurationField Number AutoField
Number BigInteger Number DecimalField
Number FloatField Number IntegerField
Number PositiveIntegerField Number PositiveSmallIntegerField
Text CharField Text TextField
Text CommaSeparatedIntegerField Text EmailField
Text FileField Text FilePathField
Text(specialized) ImageField Text(specialized) GenericIPAddress
Text(specialized) SlugField Text(specialized) URLField
限值:max_length,min_value,max_value,max_digits,decimal_places

對於text-based的字段,max_length限定了最大字符數量;對於IntegerField數據類型,Django提供了min_value來限制其最小值,max_value限制其最大值;對於DecimalField,需要標明max_digitsdecimal_places來限制數值的最多的位數和有效值。

Empty,Null,Not Null: Blank 和Null

所有字段默認都是數據庫級別的NOT NULL限制(即默認null=False),即如果創建/更新一個NOT NULL字段,必須為該字段提供值,否則數據庫拒絕接受該字段;而有時候,允許一個空字段是必要的,因為數據庫中可以識別NULL,因此為了允許在數據庫層面支持NULL值,可通過null=True進行設置。

除了 null,Django還支持blank選項,該選項默認為False,被用在python/Django層面的校驗通過在model上的Form。如果一個字段被聲明為blank=True,Django允許在form中留空,否則Django拒絕接受該值。

例如:

class Person(models.Model):
    first_name=models.CharField(max_length=30)
    middle_name=models.CharField(max_length=30)
    last_name=models.CharField(max_length=30)
blank和null的組合 默認(blank=False,null=False) null=True blank=True null=True,blank=True,
Person.objects.create(first_name=’john’,middle_name=None,last_name=’Batch’) None被處理為NULL,數據庫拒絕 數據庫層面可以為NULL None被處理為NULL,數據庫拒絕 數據庫層面可以為NULL,OK
Person.objects.create(first_name=’john’,last_name=’Batch’) 未標明的被當作空字符,數據庫層面不拒絕,空字符不是NULL 數據庫層面OK 數據庫層面OK 數據庫層面OK
Person.objects.create(first_name=’john’,middle_name=『 』,last_name=’Batch’) 數據庫層面OK 數據庫層面OK 數據庫層面OK 數據庫層面OK
Form Validation validation error,因為blank=False validation error,因為blank=False 校驗成功 校驗成功

對於blank,null的參數有四種組合,分別是default(blank=False,null=False),blank=True(null=False),null=True(blank=False),blank=True& null=True,而’middle_name’有三種選擇,分別是賦值為None,”,以及不賦值(使用默認值,等同於賦值為”),所有一共12種情況(如上表所示),但不管哪種情況,只要null=False,賦值為None在數據庫層面不通過,只要blank=False,在form校驗時就不通過。

預設定值(predetermined values):default,auto_now,auto_now_add,choices

可使用default對model字段進行默認值設定,該defaut參數既可以是一個值,也可以是方法的引用(method reference),該選項是完全由python/Django層面控制的。

儘管default選項對於text 字段,number字段以及boolean字段都是一樣的操作,但對於DateFieldDatetimeField來講,它們有自己獨特的默認值設定選項,該選項為auto_nowauto_now_add

在#demo中,datedatetime字段均用default使用了method reference(注意到datetime的default為timezone.now,date的default用了date.today,這裡都使用method reference,因為如果帶上(),其實也沒有錯,但是將在編譯階段就調用了函數,從而得到一個值,不能動態的返回當前計算值)。

對於auto_nowauto_now_add,這兩個選項與default類似的地方是都自動為字段提供了默認值,,但也有不同,不同之處在於:(1)僅對字段DateField,DateTimeField有效,對於DateFieldauto_nowauto_now_add通過date.today來產生值,對於DateTimeField,auto_nowauto_now_add是通過django.utils.timezone.now來產生值(2)auto_nowauto_now_add不能被覆蓋,auto_now選項每當一個record被改變,這個值就會改變,而auto_now_add選項在record存在期間保持不變,這就意味着我們可以用auto_now來追蹤record的最近的一次修改,用auto_now_add來追蹤record的創建時間(3)需要注意的是,在admin中,auto_nowauto_now_add不顯示,因為它們會被自動取值,且不能被覆蓋,所有就不顯示出來。

另一個預設值是choices,它是在python/Django層面控制的。

Unique:unique,unique_for_date,unique_for_month,unique_for_year

比如name=models.CharField(max_length=30,unique=True),它告訴了Django來確保所有記錄都有一個唯一的name

unique是在數據庫層執行的(通過DDL的UNIQUESQL常量),還有Pyhton/Django層面,另外除了ManyToMany,OneToOne,FileField,對於其他的字段都適用。

對於伴隨着唯一時間的字段,比如name=models.CharField(max_length=30,unique_for_date=’date_lastupdated’),不允許同一個name有相同的date_lasupdated字段,而這個validation由於其複雜性,在python/Django層面進行控制。

表單值:Editable,help_text,verbose_name,error_messages

當form的字段由models字段支撐時,form字段可能被models字段的選項所影響。

默認地,所有models字段都是可編輯的,但是可通過editable=False來告訴Django來忽略它,然後任何與它相關的validation也會被忽略。

例如:將#demo中的city改為city=models.CharField(max_length=30,editable=False),經過makemigrations後,再sqlmigrate,查看

BEGIN;
--
-- Alter field city on store
--
CREATE TABLE "new__customized store" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "city" varchar(30) NOT NULL, "name" varchar(30) NOT NULL, "address" varchar(30) NOT NULL UNIQUE, "state" varchar(2) NOT NULL, "email" varchar(254) NOT NULL, "date" date NOT NULL, "datetime" datetime NOT NULL, "date_lastupdated" date NOT NULL, "date_added" date NOT NULL, "timestamp_lastupdated" datetime NOT NULL, "timestamp_added" datetime NOT NULL);
INSERT INTO "new__customized store" ("id", "name", "address", "state", "email", "date", "datetime", "date_lastupdated", "date_added", "timestamp_lastupdated", "timestamp_added", "city") SELECT "id", "name", "address", "state", "email", "date", "datetime", "date_lastupdated", "date_added", "timestamp_lastupdated", "timestamp_added", "city" FROM "customized store";
DROP TABLE "customized store";
ALTER TABLE "new__customized store" RENAME TO "customized store";
COMMIT;

可以發現(1)修改字段是通過新建一個表,將record移植後,刪除舊錶,再重新更改新表為舊錶名字(2)由於設置了city為editable=False,DDL中未出現city,也說明該操作在數據庫層面進行的(3)migrate後,查看admin,發現city確實不見了。

之前:

之後:

help_text在旁邊給出了關於該字段的解釋信息,verbose_name選項使得在表單中輸出為該字段的label,error_messages可接受字典,該字典的keys代表錯誤代碼,values是錯誤信息。

DDL值:db_column,db_index,db_tablespace,primary_key

默認地,Django用model的字段名作為表的column的名字,如果要改變,可以在字段里使用db_column選項,另一個就是db_index選項,但應注意以下兩種情況不需要再設定db_index:

  • unique=True
  • ForeignKey

比如:修改#demo為

city=models.CharField(max_length=30,db_index=True)
state=models.CharField(max_length=2,db_column='customized state JOHNYANG')

經過sqlmigrate:

CREATE TABLE "new__customized store" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(30) NOT NULL, "address" varchar(30) NOT NULL UNIQUE, "city" varchar(30) NOT NULL, "email" varchar(254) NOT NULL, "date" date NOT NULL, "datetime" datetime NOT NULL, "date_lastupdated" date NOT NULL, "date_added" date NOT NULL, "timestamp_lastupdated" datetime NOT NULL, "timestamp_added" datetime NOT NULL, "customized state JOHNYANG" varchar(2) NOT NULL);
INSERT INTO "new__customized store" ("id", "name", "address", "city", "email", "date", "datetime", "date_lastupdated", "date_added", "timestamp_lastupdated", "timestamp_added", "customized state JOHNYANG") SELECT "id", "name", "address", "city", "email", "date", "datetime", "date_lastupdated", "date_added", "timestamp_lastupdated", "timestamp_added", "state" FROM "customized store";  
DROP TABLE "customized store";
ALTER TABLE "new__customized store" RENAME TO "customized store";
CREATE INDEX "customized store_city_5ed81ba0" ON "customized store" ("city");
COMMIT;

可以發現state字段消失,增加了customized state JOHNYSNG,和最後CREATE INDEX "customized store_city_5ed81ba0" ON "customized store" ("city")

最後,primary_key選項允許對Model定義一個主鍵,如果沒有一個字段定義primary_key=True,則自動創建一個名為idAutoField字段來充當主鍵(系統自動執行id=models.AutoField(primary_key=True).

內置和自定義校核器:validators

除了數據類型,長度限制,預設值等之前的選項的約束限制,Django還提供了validator選項(賦值為列表),來允許進行更複雜,高級的邏輯校核。

django.core.validators包中提供了一系列內置的校核方法。比如models.EmailField數據類型依賴django.core.validators.EmailValidator來校核email值,而models.IntegerField使用MinValueValidatorMaxValueValidator來對min_valuemax_value來進行校核。

除了內置validator方法,也可以自定義validator方法,定義該方法很簡單,只需要接受一個字段為輸入參數,然後如果不滿足某個條件就拋出django.core.exceptions.ValidatorError就可以。

ITEM_SIZE=(
    ('S','Small'),
    ('M','Medium'),
    ('L','Large'),
    ('P','Portion')
)
from django.core.exceptions import ValidationError
def calorie_watcher(value):
    if value>5000:
        raise ValidationError(('calories are %(value)s? try something less than 5000'),params={'value':value})
    if value<0:
        raise ValidationError(('Strange calories are %(value)s'),params={'value':value})

class Menu(models.Model):
    name=models.CharField(max_length=30)
    def __str__(self):
        return self.name 

class Item(models.Model):
    menu=models.ForeignKey(Menu,on_delete=models.CASCADE)
    name=models.CharField(max_length=30)
    description=models.CharField(max_length=100)
    price=models.FloatField(blank=True,null=True)
    size=models.CharField(choices=ITEM_SIZE,max_length=1)
    calories=models.IntegerField(validators=[calorie_watcher])
    def __str__(self):
        return 'Menu %s name %s' %(self.menu,self.name )
        

在admin中(需要注意的是如果要在admin中管理數據庫,需要在admin.py對需要管理的表進行註冊,如

from django.contrib import admin

# Register your models here.
from .models import Question,Choice,Store,Menu,Item
admin.site.register((Question,Choice,Store,Menu,Item))

)對Item進行添加,如果calorie小於0,可以看到報錯信息:

Django model默認和定製的行為

Django對Model提供了很多函數,比如基本操作save(),delete(),還有命名慣例以及查詢行為等。

Model方法

所有model都繼承了一系列方法,如保存,刪除,校核,加載,還有對model 數據施加特定的邏輯等。

save()

save()方法是最常見的一個操作,對一個記錄進行保存。一旦創建或者獲取了model instance的reference,則可以調用save()來進行創建/更新 該instance。

那麼django是如何判定一個save()到底是該創建還是更新一個instance?

它是通過id來進行判斷的,如果reference的id在數據庫中還沒有,那麼創建,如果已經有了則是更新。

save()方法可接受的參數如下:

參數 默認值 描述
force_insert False 顯式的告訴Django對一個記錄進行創建(force_insert=True)。極少這樣用,但是對於不能依靠Django來決定是否創建的情況是有用的。
force_update False 顯式的告訴Django對一個記錄進行更新(force_update=True)。極少這樣用,但是對於不能依靠Django來決定是否更新的情況是有用的。
using DEFAULT_DB_ALLAS 允許save()對settings.py中不是默認值的數據庫進行保存/更新操作(比如,using=’oracle’,這裡’oracle’必須是settings.py的’DATABASES’變量的一個key),詳細用法見本文多數據庫部分總結
update_fields None 接受包含多個字段的list(比如save(update_fields)=[‘name’]),僅更新list中的字段。當有比較大的model,像進行更有效率更精細的更新,可以使用該選項。
commit True 確保記錄保存到了數據庫。在特定情況下(比如,models forms或者關係型操作),commit被設定為False,來創建一個instance,而不將其保存到數據庫。這允許基於它們的輸出來進行額外的操作 。
class Store(models.Model):
    name=models.CharField(max_length=30)
    address=models.CharField(max_length=30)
    city=models.CharField(max_length=30)
    state=models.CharField(max_length=2)
    def save(self,*args,**kwargs):
        #Do custom logic here(e.g. validation,logging,call third party service)
        #Run default save() method
        super().save(*args,**kwargs)
        

也可以對save()method進行重寫,可以在它之前或者之後做一些事情。

delete()

該方法是通過一個reference來從數據庫中消除一條記錄。delete()實際上是依靠id主鍵來移除一個記錄,所以對於一個reference應該有id值來進行delete()操作。

delete()被一個reference調用時,它的id主鍵被移除,但它剩餘的值還保存在內存中。另外它用刪除記錄的數量作為回應。比如(1, {‘testapp.Item’: 1}).

save()類似,delete()也接受兩個參數:using=DEFAULT_DB_ALIAS,keep_parents=False。using允許對其他數據庫進行delete操作,當delete()發生在關係型model,希望保持parent model完整或者移除,keep_parents是很有用的。

最後,也可以對delete()方法進行重寫。

校驗方法:clean_fields(),clean(),validate_unique,full_clean()

理解校驗方法最重要的部分還是從兩方面進行:數據庫層面和Python/Django層面。

對於數據庫層面的校驗,在第一次遷移時就自動完成了,而對於Python/Django層面,必須調用校驗函數來進行校驗。

需要注意的是:對於sqlite3,除primary key必須是整數外,其他的數據類型並沒有嚴格的校核,Char(10)的含義是至少佔用10個位元組,所以會出現,如果直接創建一個超過10個位元組的記錄,也是可以保持到數據庫的現象。

比如:

image-20201019103900920

>>> s=Stores.objects.create(name='this is a veryvery long name which exceeds 30 characters let us see if it will raise an error',address='cd',city='cd',state='ca',email='[email protected]')
>>> len('this is a veryvery long name which exceeds 30 characters let us see if it will raise an error')
93
  • clean_fields()

儘管可以通過調用save()來讓數據庫校驗字段的合法性(對sqlite3,數據庫不對字段進行數據類型檢查,除primary key外),但也可以通過clean_fields()在Python/Django層面進行字段數據類型的合法性檢驗。

>>> s.clean_fields()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 1253, in clean_fields
    raise ValidationError(errors)
django.core.exceptions.ValidationError: {'name': ['Ensure this value has at most 30 characters (it has 93).']}
  • validate_unique()

對於有unique選項的字段,可通過記錄實例的validate_unique()方法進行unique檢驗。

>>> p=Stores(name='testname',address='科華北路',city='cd',state='ca',email='[email protected]')
>>> p.validate_unique()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 1013, in validate_unique
    raise ValidationError(errors)
django.core.exceptions.ValidationError: {'address': ['Stores with this Address already exists.']}

除了對字段的unique選項進行unique檢驗,validate_unique()還對model的Meta class的unique_together屬性(賦值為元組)進行檢驗,即多個字段組成的唯一性。

例如:

class TestAll(models.Model):
    test0=models.CharField(max_length=3)
    test1=models.CharField(max_length=3)
    class Meta:
        unique_together=('test0','test1')
(env) F:\PycharmProject\webDev\Django project\testBegin>python manage.py sqlmigrate banners 0007
CREATE TABLE "banners_testall" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "test0" varchar(3) NOT NULL, "test1" varchar(3) NOT NULL);
CREATE UNIQUE INDEX "banners_testall_test0_test1_2f0f2688_uniq" ON "banners_testall" ("test0", "test1");
>>> from banners.models import *
>>> s=TestAll(test0='a',test1='b')
>>> ss=TestAll(test0='a',test1='b')
>>> ss.validate_unique()
>>> s.validate_unique()
>>> s.save()
>>> ss.validate_unique()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 1013, in validate_unique
    raise ValidationError(errors)
django.core.exceptions.ValidationError: {'__all__': ['Test all with this Test0 and Test1 already exists.']}
>>> s.validate_unique()
>>> ss.save()
Traceback (most recent call last):
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\backends\utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\backends\sqlite3\base.py", line 413, in execute
    return Database.Cursor.execute(self, query, params)
sqlite3.IntegrityError: UNIQUE constraint failed: banners_testall.test0, banners_testall.test1

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 751, in save
    force_update=force_update, update_fields=update_fields)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 789, in save_base
    force_update, using, update_fields,
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 892, in _save_table
    results = self._do_insert(cls._base_manager, using, fields, returning_fields, raw)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 932, in _do_insert
    using=using, raw=raw,
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\query.py", line 1249, in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\sql\compiler.py", line 1395, in execute_sql
    cursor.execute(sql, params)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\backends\utils.py", line 98, in execute
    return super().execute(sql, params)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\backends\utils.py", line 66, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\backends\utils.py", line 75, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\backends\utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\utils.py", line 90, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\backends\utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\backends\sqlite3\base.py", line 413, in execute
    return Database.Cursor.execute(self, query, params)
django.db.utils.IntegrityError: UNIQUE constraint failed: banners_testall.test0, banners_testall.test1
>>> sss=TestAll(test0='a',test1='b')
>>> sss.validate_unique()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 1013, in validate_unique
    raise ValidationError(errors)
django.core.exceptions.ValidationError: {'__all__': ['Test all with this Test0 and Test1 already exists.']}
>>>

可見,當s保存前,sss並不衝突,但是當s保存後,再調用ss.validate_unique()報錯。

  • clean()

除了clean_fields(),還有clean(),用來提供更靈活的校驗方法(比如特定的關係或者值)。

例如:

from django.core.exceptions import ValidationError
class TestAll(models.Model):
    test0=models.CharField(max_length=3)
    test1=models.CharField(max_length=3)
    def clean(self):
        if self.test0==self.test1:
            raise ValidationError('test0 shall not equil to test1')
>>> from banners.models import *
>>> ss=TestAll(test0='c',test1='c')
>>> ss.save()
>>> ss.clean()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "F:\PycharmProject\webDev\Django project\testBegin\banners\models.py", line 99, in clean
    raise ValidationError('test0 shall not equil to test1')
django.core.exceptions.ValidationError: ['test0 shall not equil to test1']
  • full_clean()

最後是full_clean(),它是依次執行clean_fields(),clean(),validate_unique().

數據加載方法:refresh_from_db()

當數據庫被另一個進程更新,或者不小心改變了model instance的值,也就是想用數據庫中的數據來更新model instance的值,refresh_from_db()方法就是用來做這件事的方法.

儘管一般都是不帶參數的使用refresh_from_db(),但它可以接受兩個參數:(1)using,該參數跟save(),delete()方法中的含義一樣(2)fields,用來挑選需要refresh的字段,如果沒有指定,則refresh model的所有字段.

from_db(),get_deferred_fields()用來定製加載數據過程,用的情況相對比較少,詳見//docs.djangoproject.com/en/3.1/ref/models/instances/

定製方法

例:

from django.core.exceptions import ValidationError
class TestAll(models.Model):
    test0=models.CharField(max_length=3)
    test1=models.CharField(max_length=3)
    def clean(self):
        if self.test0==self.test1:
            raise ValidationError('test0 shall not equil to test1')
    def special(self):
        return self.test0,self.test1
>>> from banners.models import *
>>> s=TestAll.objects.all().first()
>>> s.test0
'a'
>>> s.special()
('a', 'b')

Model 管理字段:Objects

objects字段是所有Django models的默認管理字段,用來管理各種query操作(增刪查改).

它是直接用在class上,而不是instance上,比如,要讀取Store的id=1的記錄,可以Store.objects.get(id=1),而刪除所有記錄則可以Store.objects.all().delete().

可以定製管理字段,比如重命名為mgr,則只需要添加類屬性mgr=models.Manager().

Model Meta class及選項

Django中的Meta類是用來把model的行為作為一個整體進行定義,與數據類型不同,後者在字段上定義了更精細的行為.

比如為了避免不斷的顯式的聲明一個model的搜索順序,可以直接在Meta 類中定義ordering.

比如:

class Stores(models.Model):
    name=models.CharField(max_length=30,unique_for_date='date_lastupdate',db_column='my_custom_name')
    address=models.CharField(max_length=30,unique=True)
    city=models.CharField(max_length=30)
    state=models.CharField(max_length=2)
    email=models.EmailField(help_text='valide email please')
    date=models.DateField(default=date.today)
    datetime=models.DateTimeField(default=timezone.now)
    date_lastupdate=models.DateField(auto_now=True)
    date_added=models.DateField(auto_now_add=True)
    timestamp_lastupdated=models.DateTimeField(auto_now=True)
    timestamp_added=models.DateTimeField(auto_now_add=True)
    testeditable=models.CharField(max_length=3,editable=False)
    def __str__(self):
        return self.name 
    class Meta:
        ordering=['-timestamp_added']
>>>a=Stores.objects.all()
>>> for i in a:
...     print(i.timestamp_added)
...
2020-10-19 02:38:22.767910+00:00
2020-09-28 07:45:12.708000+00:00
2020-09-28 05:05:08.066000+00:00

可以看到是按照timestamp_added降序排列的.

DDL table options: db_table,db_tablespace,managed,unique_together
  • db_table

默認地,創建表的名字為<app_name>_<model_name>,比如,app名字為’banners’,model名字為’testall’,那麼創建的表名為’banners_testall’.

sqlite> .tables
auth_group                  banners_entry_authors
auth_group_permissions      banners_item
auth_permission             banners_menu
auth_user                   banners_question
auth_user_groups            banners_stores
auth_user_user_permissions  banners_testall
banners_author              django_admin_log
banners_blog                django_content_type
banners_choice              django_migrations
banners_entry               django_session

現在添加meta option:

class TestAll(models.Model):
    test0=models.CharField(max_length=3)
    test1=models.CharField(max_length=3)
    def clean(self):
        if self.test0==self.test1:
            raise ValidationError('test0 shall not equil to test1')
    def special(self):
        return self.test0,self.test1
    class Meta:
        unique_together=('test0','test1')
        db_table='specialTestAll' #添加db_table
(env) F:\PycharmProject\webDev\Django project\testBegin>python manage.py sqlmigrate banners 0008
BEGIN;
--
-- Rename table for testall to specialTestAll
--
ALTER TABLE "banners_testall" RENAME TO "specialTestAll";
COMMIT;

可見,已經將表名進行了更改。

  • db_tablespace

默認地,如果Django的數據庫支持(比如Oracle)tablespace的概念,Django 就用settings.py中的DEFAULT_TABLESPACE變量作為默認的tablespace.也可以通過meta的db_tablespace選項來對model設置其他的tablespace.

Django管理創建/銷毀數據表,如果不需要這樣的管理,可以設置meta的managed=False

DDL index options: indexes,index_together

Index在關係型數據庫記錄的高效查詢中是非常重要的,簡單的說,它是包含了用於確保查詢速度更快的包含了特定記錄的column values。

Django meta類提供了2個選項來對model字段的index創建相應的DDL (如CREATE INDEX…):indexes,index_together.

對於primary keyunique的字段,不需要再進行標註為index

index參數可接受包含多個models.Index實例的列表,models.Index實例接受fields列表,其包含了需要被index的字段名,和name,用於命名Index的名字,缺省情況下,自動為其進行命名。

from django.core.exceptions import ValidationError
class TestAll(models.Model):
    test0=models.CharField(max_length=3)
    test1=models.CharField(max_length=3)
    test2=models.CharField(max_length=3)
    test3=models.CharField(max_length=3)
    def clean(self):
        if self.test0==self.test1:
            raise ValidationError('test0 shall not equil to test1')
    def special(self):
        return self.test0,self.test1
    class Meta:
        unique_together=('test0','test1')
        db_table='specialTestAll'
        indexes=[
            models.Index(fields=['test0','test1']),
            models.Index(fields=['test0'],name='test0_idx')
        ]
        index_together=['test2','test3']
(env) F:\PycharmProject\webDev\Django project\testBegin>python manage.py sqlmigrate banners 0009
BEGIN;
CREATE UNIQUE INDEX "specialTestAll_test0_test1_f3575fe2_uniq" ON "specialTestAll" ("test0", "test1");
--
-- Alter index_together for testall (1 constraint(s))
--
CREATE INDEX "specialTestAll_test2_test3_20a904e7_idx" ON "specialTestAll" ("test2", "test3");
--
-- Create index specialTest_test0_eb859d_idx on field(s) test0, test1 of model testall
--
CREATE INDEX "specialTest_test0_eb859d_idx" ON "specialTestAll" ("test0", "test1");
--
-- Create index test0_idx on field(s) test0 of model testall
--
CREATE INDEX "test0_idx" ON "specialTestAll" ("test0");
COMMIT;

index_together允許定義多字段index,index_together=['test0','test1']等效於indexes=[models.Index(fields=['test0','test1']) ].

命名選項:verbose_name,verbose_name_plural,label,label_lower,app_label

默認地,Django models是通過類名來refer model的,絕大多數都是可以的。但是對於有些情況,比如,做開發的時候,類名使用了縮寫,但如果想要在UI上,或者admin中不這樣顯示,該怎麼辦呢?

這就是verbose_name出現的原因了,verbose_name_plural是對英語的複數,比如class Choice的複數顯示是choices。

class TestAll(models.Model):
    test0=models.CharField(max_length=3)
    test1=models.CharField(max_length=3)
    test2=models.CharField(max_length=3)
    test3=models.CharField(max_length=3)
    class Meta:
        verbose_name='verboseTestAll'
        verbose_name_plural='VerboseTestAllPlural'

在admin中:

image-20201020203055640

>>> from testapp.models import *
>>> TestAll._meta.verbose_name
'verboseTestAll'
>>> TestAll._meta.verbose_name_plural
'VerboseTestAllPlural'
>>> TestAll._meta.label
'testapp.TestAll'
>>> TestAll._meta.label_lower
'testapp.testall'

繼承 Meta option: Abstract and proxy

類似於OOP的繼承,這裡model 的Meta類也有類似的概念,分別是abstractproxy

  • Abstract

對於abstract,它的用法是在model的Meta class中聲明abstract=True,這樣該model就變成了一個類似於base class的model,它不創建表,而繼承它的class自動獲取它定義的所有字段。

  • Proxy

abstract相反,它的用法是base class就是正常的model,在其子類的Meta class中聲明proxy=True,子類不會創建新的表,子類所定義的行為選項都好像是直接作用在基類上的,但真正的基類的行為選項並沒有實質被改變,可以理解為子類對基類的修修補補,但還是作用在基類的表上的。

簡單總結下就是:在abstract和proxy情況下,帶有meta的沒有表,abstract出現在基類meta中,proxy 出現在子類的meta中。

class TestAbs(models.Model):
    test=models.CharField(max_length=3)
    class Meta:
        abstract=True 
class subAbs(TestAbs):
    pass 

class TestProxy(models.Model):
    test=models.CharField(max_length=3)

class subPro(TestProxy):
    class Meta:
        proxy=True 
(env) E:\programming\DjangoProject\TangleWithTheDjango\testDebug>python manage.py sqlmigrate testapp 0014
BEGIN;
--
-- Create model subAbs
--
CREATE TABLE "testapp_subabs" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "test" varchar(3) NOT NULL);
--
-- Create model TestProxy
--
CREATE TABLE "testapp_testproxy" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "test" varchar(3) NOT NULL);
--
-- Create proxy model subPro
--
COMMIT;

當進行django model query時,有很多默認的行為,如果要用非默認的query行為,則需要顯式的參數顯式的調用query的各種方法。然而,一次又一次的這樣做,無聊且易錯,所以我們可以依靠本節的query meta option來改變model query的各種默認行為。

  • ordering

ordering是用來定義默認的排序的,比如Store.objects.all().sort_by(‘-name’)是按照name降序排列,等效為ordering=['-name'],需要注意的是賦值為列表類型。

  • order_with_respect_to

    按照某字段進行排序,通常是外鍵,這樣就可以使相互關聯的object允許它的parent object(parent object的id就是子object 的外鍵)來通過get_RELATED_order,set_RELATED_ORDER來獲得及設定子object的順序。比如:

    class Question(models.Model):
        question_text=models.CharField(max_length=200)
        pub_date=models.DateTimeField('date published')
        def __str__(self):
            return self.question_text
        def was_published_recently(self):
            return self.pub_date>=timezone.now()-timedelta(days=1)
    class Answer(models.Model):
        question=models.ForeignKey(Question,on_delete=models.CASCADE)
    
    ;sqlmigrate來查看其底層SQL語句
    CREATE TABLE "banners_question" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "question_text" varchar(200) NOT NULL, "pub_date" datetime NOT NULL);
    CREATE TABLE "banners_answer" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "question_id" integer NOT NULL REFERENCES "banners_question" ("id") DEFERRABLE INITIALLY DEFERRED, "_order" integer NOT NULL);
    CREATE INDEX "banners_answer_question_id_b2a3fa7a" ON "banners_answer" ("question_id");
    
    >>> from banners.models import *
    >>> import datetime
    >>> Question.objects.create(question_text='what?',pub_date=datetime.date.today())
    F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\fields\__init__.py:1312: RuntimeWarning: DateTimeField Question.pub_date received a naive datetime (2020-10-21 00:00:00) while time zone support
    is active.
      RuntimeWarning)
    <Question: what?>
     >>> s=Question.objects.get(id=1)
    >>> s
    <Question: what?>
    >>> s.pub_date
    datetime.datetime(2020, 10, 20, 16, 0, tzinfo=<UTC>)
    >>> Answer.objects.create(question=s)
    <Answer: Answer object (1)>
    >>> Answer.objects.cretae(question=s)
    Traceback (most recent call last):
      File "<console>", line 1, in <module>
    AttributeError: 'Manager' object has no attribute 'cretae'
    >>> Answer.objects.create(question=s)
    <Answer: Answer object (2)>
    >>> Answer.objects.create(question=s)
    <Answer: Answer object (3)>
    >>> s.get_answer_order()
    <QuerySet [1, 2, 3]>
    >>> s.set_answer_order([3,1,2])
    >>> a=Answer.objects.get(id=2)
    >>> a.get_next_in_order()
    Traceback (most recent call last):
      File "<console>", line 1, in <module>
      File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 981, in _get_next_or_previous_in_order
        }).order_by(order)[:1].get()
      File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\query.py", line 431, in get
        self.model._meta.object_name
    banners.models.Answer.DoesNotExist: Answer matching query does not exist.
    >>> a.get_previous_in_order()
    <Answer: Answer object (1)>
    
  • get_latest_by

在Meta中,get_latest_byDateField,DateTimeField,IntegerField的字段的名字,或包含這種字段名字的列表。這樣就可以用Managerlatest()earliest()方法。

比如:

class Question(models.Model):
    question_text=models.CharField(max_length=200)
    pub_date=models.DateTimeField('date published')
    def __str__(self):
        return self.question_text
    def was_published_recently(self):
        return self.pub_date>=timezone.now()-timedelta(days=1)
    class Meta:
        get_latest_by='pub_date'
 
;查看sqlmigrate
(env) F:\PycharmProject\webDev\Django project\testBegin>python manage.py sqlmigrate banners 0011
BEGIN;
--
-- Change Meta options on question
--
--
-- Set order_with_respect_to on answer to None
--
CREATE TABLE "new__banners_answer" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "question_id" integer NOT NULL REFERENCES "banners_question" ("id") DEFERRABLE INITIALLY DEFERRED);
INSERT INTO "new__banners_answer" ("id", "question_id") SELECT "id", "question_id" FROM "banners_answer";
DROP TABLE "banners_answer";
ALTER TABLE "new__banners_answer" RENAME TO "banners_answer";
CREATE INDEX "banners_answer_question_id_b2a3fa7a" ON "banners_answer" ("question_id");
COMMIT;
>>> from banners.models import *
>>> import datetime
>>> datetime.date(2019,8,19)
datetime.date(2019, 8, 19)
>>> Question.objects.create(question_text='how?',pub_date=datetime.date(2019,3,4))
F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\fields\__init__.py:1312: RuntimeWarning: DateTimeField Question.pub_date received a naive datetime (2019-03-04 00:00:00) while time zone support
is active.
  RuntimeWarning)
<Question: how?>
>>> Question.objects.create(question_text='when?',pub_date=datetime.date(2017,2,2))
F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\fields\__init__.py:1312: RuntimeWarning: DateTimeField Question.pub_date received a naive datetime (2017-02-02 00:00:00) while time zone support
is active.
  RuntimeWarning)
<Question: when?>
>>> Question.objects.create(question_text='where?',pub_date=datetime.day(2018,3,3))
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: module 'datetime' has no attribute 'day'
>>> Question.objects.create(question_text='where?',pub_date=datetime.date(2018,3,3))
F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\fields\__init__.py:1312: RuntimeWarning: DateTimeField Question.pub_date received a naive datetime (2018-03-03 00:00:00) while time zone support
is active.
  RuntimeWarning)
<Question: where?>
>>> Question.objects.latest()
<Question: what?>
>>> Question.objects.earliest()
<Question: when?>
>>> s=Question.objects.earliest()
>>> s.pub_date
datetime.datetime(2017, 2, 1, 16, 0, tzinfo=<UTC>)
>>> Answer.objects.latest()  #可以看到,沒有設定get_latest_by,就不能用manager的latest方法
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\query.py", line 674, in latest
    return self.reverse()._earliest(*fields)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\query.py", line 658, in _earliest
    "earliest() and latest() require either fields as positional "
ValueError: earliest() and latest() require either fields as positional arguments or 'get_latest_by' in the model's Meta.
  • default_manager_name

所有的models都將objects作為默認的model manager,但是當存在多個manager時,就必須指定哪個才是默認的manger,這時候,就需要在meta中對default_manager_name進行指定。

  • default_related_name

對於相互有關係的object,可以通過parent object 來反向得到子object:

>>> from banners.models import *
>>> q=Question.objects.get(id=1)
>>> q.answer_set.get(id=1)
<Answer: Answer object (1)>

這裡,默認是<model_name>_set,所以在本例中為answer_set.

因為對於字段的反向名字應該是唯一的.所以在abstract subclass情況下需要注意不能衝突,可以用包含’%(app_label)s’和’%(model_name)s’來規避這種衝突.

# common/models.py
from django.db import models

class Base(models.Model):
    m2m = models.ManyToManyField(
        OtherModel,
        related_name="%(app_label)s_%(class)s_related",
        related_query_name="%(app_label)s_%(class)ss",
    )

    class Meta:
        abstract = True

class ChildA(Base):
    pass

class ChildB(Base):
    pass
# rare/models.py
from common.models import Base

class ChildB(Base):
    pass
  • Permisssion Meta operation: default_permissions, permission

默認地,default_permissions是(‘add’,’change’,’delete’,『view’),這就允許了對model instance的增刪改的操作.

比如:

class testPermission(models.Model):
    test0=models.TextField()
    class Meta:
        default_permissions=('add',)
        permissions=(('can_do_someting','we can do something'),)
(env) F:\PycharmProject\webDev\Django project\testBegin>python manage.py shell
Python 3.7.1 (v3.7.1:260ec2c36a, Oct 20 2018, 14:05:16) [MSC v.1915 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.contrib.auth.models import User
>>> user=User.objects.create_user('xiaoming','[email protected]','3152')
>>> user.last_name
''
>>> user.save()
>>> op=User.objects.get(id=1)
>>> op.first_name
''
>>> op.name
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'User' object has no attribute 'name'
>>> op.get_all_permissions()
{'banners.delete_menu', 'banners.view_menu', 'banners.add_blog', 'sessions.view_session', 'banners.delete_entry', 'auth.change_user', 'banners.view_testall', 'banners.change_menu', 'sessions.delete_session', 'banners.view_choice', 'banners.view_item', 'banners.change_stores', 'banners.add_item', 'auth.view_user', 'banners.delete_item', 'admin.add_logentry', 'auth.add_permission', 'banners.add_testall', 'banners.view_stores', 'admin.delete_logentry', 'contenttypes.change_contenttype', 'banners.delete_answer', 'banners.change_author', 'banners.view_blog', 'admin.view_logentry', 'banners.change_answer', 'banners.change_blog', 'auth.delete_user', 'auth.add_user', 'banners.add_testpermission', 'banners.view_author', 'sessions.add_session', 'banners.add_menu', 'contenttypes.view_contenttype', 'contenttypes.delete_contenttype', 'banners.view_entry', 'banners.delete_author', 'banners.add_entry', 'auth.view_permission', 'banners.change_testall', 'banners.add_answer', 'auth.change_group', 'auth.view_group', 'banners.change_question', 'banners.add_question', 'banners.add_author', 'banners.view_answer', 'sessions.change_session', 'auth.delete_group', 'contenttypes.add_contenttype', 'banners.delete_blog', 'banners.delete_stores', 'banners.delete_choice', 'banners.change_entry', 'banners.can_do_someting', 'auth.delete_permission', 'auth.change_permission', 'banners.add_stores', 'banners.delete_question', 'banners.change_item', 'banners.add_choice', 'admin.change_logentry', 'banners.view_question', 'banners.delete_testall', 'auth.add_group', 'banners.change_choice'}
>>> op.has_perm('banners.delete_testpermission')
True

更詳細的解答可參考://www.cnblogs.com/37Y37/p/11658651.html

Django models 的關係型數據

簡而言之,關係型數據就是用不同數據庫的記錄的id或者pk來連接在一起,從而達到易維護,提高query性能,減少冗雜數據的目的.

Django支持以下三種關係型數據:一對多,多對多,一對一.

  • 一對多

一對多是指,一個model的記錄可聯結另一個model的多個記錄.比如一個Menu的記錄可包含多個Item記錄,而一個Item記錄只能對應一個Menu記錄,為了表達這樣的關係,可在Itemmodel中加入ForeignKey字段,該字段指明為Menu

  • 多對多

多對多關係指,一個model的記錄可對應另一個model的多個記錄,而另一個model的一個記錄也可以對應前者的多個記錄.比如Book的一個記錄可對應多個Author的記錄,而Author的一個記錄也可以對應多個Book.為了表達這種關係,可將ManyToManyField用在任意一個Model上.

  • 一對一

一對一有點像面向對象繼承的意思,比如Item如果和Drink進行一對一的聯結,那麼Item之用定義通用的字段,而Drink定義詳細的,特定的字段.

關係型數據類型的選型
數據整體性選型(Data integrity options):on_delete

關係型數據將不同的model綁定了起來,當一端被刪除,怎麼處理另一端是非常重要的.on_delete就是用於這個目的.

on_delete適合以上三種關係型數據類型,它的選型為:

  • models.CASCADE(default). 自動刪除相關的記錄.(比如Menubreakfast實例被刪除,所有相關的Item的記錄將被刪除)
Reference option: Self,literal strings
  • 'self'

model 關係有時候有遞歸關係。在一對多的父-子關係中比較常見。比如Categorymodel可以有一個parent,而這個parent就是另一個Category。為了定義這種關係,必須用self來指代相同的model。

但是在實際操作中,往往需要加上null=True,blank=True(在admin操作時,需要),因為如果要定義它,它的parent就必須存在,如果不允許null值,那麼就會報錯。

比如:

class Category(models.Model):
    menumenu=models.ForeignKey('self',on_delete=models.CASCADE,null=True,blank=True)

image-20201022214155077

  • literal string

儘管model關係型數據通常都用reference,但是使用model名字的字符也是合法的,比如models.ForeignKey('menu')。當model定義順序不允許索引還沒有創建的model時,該方法很有用,該方法也被稱為’laze loading’。如果跨app,可以將app_label加在前面,如models.ForeignKey(production.Manufacturer').

反向關係:related_name,related_query_name,symmetrical

對於關係型model,Django自動的用_set在數據間建立了反向關係。

例:

class parent(models.Model):
    name=models.CharField(max_length=5)
    def __str__(self):
        return self.name

class sub(models.Model):
    par=models.ForeignKey(parent,on_delete=models.CASCADE)
    name=models.CharField(max_length=5)
    def __str__(self):
        return self.name 
>>> from testapp.models import *
>>> a=parent.objects.create(name='johnyang')
>>> b=sub.objects.create(par=a,name='johnsub')
>>> c=sub.objects.create(par=a,name='johnsub2')
>>> d=parent.objects.create(name='tom')
>>> e=sub.objects.create(par=d,name='tomsub')
>>> a.sub_set.all()
<QuerySet [<sub: johnsub>, <sub: johnsub2>]>
>>> f=a.sub_set.all()
>>> f[0]
<sub: johnsub>
>>> print(f[0])
johnsub
>>> len(f)
2
>>> parent.objects.filter(sub__name='johnsub')
<QuerySet [<parent: johnyang>]>

從上面可以可看出,反向關係就是通過parent來反向索引/querysub

反向獲取所有其子model,默認通過instance的<submodelname>_set屬性,可通過related_name來進行定製;

通過model class manager的<submodelname>__...來query符合條件的parent instance,該<submodelname>可通過related_query_name來進行定製。

class parent(models.Model):
    name=models.CharField(max_length=5)
    def __str__(self):
        return self.name

class sub(models.Model):
    par=models.ForeignKey(parent,on_delete=models.CASCADE,related_name='subRelated',related_query_name='subQuery')
    name=models.CharField(max_length=5)
    def __str__(self):
        return self.name 
>>> from testapp.models import *
>>> parent.objects.filter(subQuery__id=1)
<QuerySet [<parent: johnyang>]>
>>> a=parent.objects.filter(subQuery__id=1)
>>> a[0]
<parent: johnyang>
>>> a[0].id
1
>>> a[0].subRelated.all()[0].id
1

related_name='+',表示拒絕進行反向索引:

class parent(models.Model):
    name=models.CharField(max_length=5)
    def __str__(self):
        return self.name

class sub(models.Model):
    par=models.ForeignKey(parent,on_delete=models.CASCADE,related_name='+',related_query_name='subQuery')
    name=models.CharField(max_length=5)
    def __str__(self):
        return self.name 
>>> from testapp.models import *
>>> a=parent.objects.get(id=1)
>>> a.sub_set.all()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'parent' object has no attribute 'sub_set'
  • symmetrical

ManyToMany='self',那麼Django認為這種多對多關係是對稱的,所以就沒有_set的反向索引,多對多的添加只能通過manytomany鍵的addcreate來進行添加。

class person(models.Model):
    friends=models.ManyToManyField('self')
>>> from testapp.models import *
>>> a=person.objects.create()
>>> b=person.objects.create()
>>> a.friends.create()
<person: person object (7)>
>>> a.friends.add(b)
>>> a.friends.all()
<QuerySet [<person: person object (6)>, <person: person object (7)>]>
>>> e=b.friends.create()
>>> e.friends.all()
<QuerySet [<person: person object (6)>]>
>>> b.friends.all()
<QuerySet [<person: person object (5)>, <person: person object (8)>]>
>>> e.friends_set #無_set
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'person' object has no attribute 'friends_set'
>>> e.person_set
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'person' object has no attribute 'person_set'

如果要查詢b的所有朋友,只需要b.friends.all()就可以了,而不能b.friends_set.all()

>>> from testapp.models import *
>>> amenity.objects.get(id=1).storee_set.all()
<QuerySet [<storee: storee object (1)>, <storee: storee object (2)>]>
>>> person.objects.get(id=1).friends.all()
<QuerySet []>
>>> person.objects.get(id=1).friends.create()
<person: person object (10)>
>>> amenity.objects.get(id=1).storee_set.create(name='now1024')
<storee: storee object (4)>

值得注意的是,對於多對多,不能像一對多那樣分別對多個model進行分別賦值,它可以進行反向索引進行createadd操作,對於selfsymmetrical=True,反向索引不能用_set

如果要取消這種對稱,只需要symmetrical=False

>>> from testapp.models import *
>>> a=person1.objects.create()
>>> a.friends_set.all()
Traceback (most recent call last):     
  File "<console>", line 1, in <module>
AttributeError: 'person1' object has no attribute 'friends_set'
>>> a.friends.all()
<QuerySet []>
>>> b=person1.objects.create()
>>> a.friends.add(b)
>>> a.friends.all()
<QuerySet [<person1: person1 object (2)>]>
>>> b.person_set.all()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'person1' object has no attribute 'person_set'
>>> b.person1_set.all()
<QuerySet [<person1: person1 object (1)>]>
>>> b.friends.all()
<QuerySet []>

這時候,查詢b的朋友就需要b.person1_set.all()了。

數據庫選項:to_field,db_constraint,swappable,through,through_fields,db_table

  • to_field

一般地,Django的關係型model是通過primary key,(默認是id)來建立的。比如,menu=models.ForeignKey(Menu)儲存Menu實例的id來作為索引。但也可以覆蓋該行為,用to_field,需要注意的是如果要對to_field進行賦值,必須設定unique=True

  • db_constraint

默認地,Django在數據庫層面遵循關係型數據庫的各種慣例。db_constrait選項,默認為True,允許通過設定為False來忽略這種約束,而這種設定也僅僅當你事先知道一個數據庫是破損的,不需要在數據庫層面檢查約束。

  • swappable

如果ManyToManyField指向一個swappable model,它控制數據的遷移行為。一般用在Usermodel,較少應用該選項。

  • through,through_fields,db_table

through,through_fields,db_table影響着junction table,如果要改變交叉表的名字,可以使用db_table

交叉表存儲了多對多的關係型數據的最少信息:對於關係的id。來指定一個單獨的model來作為交叉表,然後存儲多對多的額外信息也是可能的(比如,through=MyCustomModel,用MyCustomModel來作為一個多對多的額外表。如果定義了throuth選項,那麼用through_fields來告訴Django在新表中,哪些字段用來存儲關係索引。

class person2(models.Model):
    name=models.CharField(max_length=128)
    def __str__(self):
        return self.name 
class group2(models.Model):
    name=models.CharField(max_length=128)
    members=models.ManyToManyField(person2,through='membership')
    def __str__(self):
        return self.name 
class membership(models.Model):
    person=models.ForeignKey(person2,on_delete=models.CASCADE)
    group=models.ForeignKey(group2,on_delete=models.CASCADE)
    date_joined=models.DateField(default=timezone.now)
    invite_reason=models.CharField(max_length=64)
    
#直接通過中間表來創建
>>> from testapp.models import *
>>> a=person2.objects.create(name='johnyang')
>>> b=group2.objects.create(name='gp1')
>>> m1=membership.objects.create(person=a,group=b,invite_reason='for test purpose')
>>> a.group2_set.all()
<QuerySet [<group2: gp1>]>
>>> a.group2_set.all().create(name='gp2')
<group2: gp2>
>>> m1.group
<group2: gp1>

如果標明了through_defaults,也可以用addcreate,set來添加關係

>>> c=person2.objects.create(name='tom')
>>> from datetime import date                   
>>> b.members.add(c,through_defaults={'date_joined':date(1991,5,5),'invite_reason':'startup'})
>>> b.members.create(name='hanson',through_defaults={'date_joined':date(2000,2,3),'invite_reason':'for development'})
<person2: hanson>
>>> d=person2.objects.create(name='yohn')
>>> e=person2.objects.create(name='porken')
>>> b.members.set([d,e],through_defaults={'date_joined':date(1991,5,5),'invite_reason':'startup'})
>>> m1 
<membership: membership object (1)>
>>> m1.person
<person2: johnyang>
>>> b.members.all()
<QuerySet [<person2: yohn>, <person2: porken>]>

remove用來移除一個記錄,clear用來移除全部的記錄。

>>> n=person2.objects.filter(name='yohn')
>>> b.members.remove(n[0])
>>> b.members.al()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'ManyRelatedManager' object has no attribute 'al'
>>> b.members.all()
<QuerySet [<person2: porken>]>
>>> b.members.clear()
>>> b.members.all()
<QuerySet []>

也可以從person2反向查詢:

>>> from testapp.models import *
>>> person2.objects.all()
<QuerySet [<person2: johnyang>, <person2: tom>, <person2: hanson>, <person2: yohn>, <person2: porken>]>
>>> group2.objects.all()
<QuerySet [<group2: gp1>, <group2: gp2>]>
>>> gp1=group2.objects.get(id=1)
>>> gp1
<group2: gp1>
>>> gp1.members.all()
<QuerySet []>
>>> gp2=group2.objects.get(id=2)
>>> gp2
<group2: gp2>
>>> gp2.members.all()
<QuerySet []>
>>> johnyang,tom,hanson,yohn,porken=person2.objects.all()
>>> johnyang
<person2: johnyang>
>>> gp1.members.set([johnyang,tom,hanson],through_defaults={'invite_reason':'for test'})
>>> gp2.members.set([hanson,yohn,porken],through_defaults={'invite_reason':'for interest'})
>>> membership.objects.all()
<QuerySet [<membership: membership object (6)>, <membership: membership object (7)>, <membership: membership object (8)>, <membership: membership object (9)>, <membership: membership object (10)>, <membership: membership object (11)>]>
>>> johnyang.membership_set.all()
<QuerySet [<membership: membership object (6)>]>
>>> johnyang.group2_set.all()
<QuerySet [<group2: gp1>]>
>>> hanson.membership_set.get(group=gp1).invite_reason
'for test'
>>> hanson.membership_set.get(group=gp2).invite_reason
'for interest'
  • Form 值:limit_choices_to

當Django model在forms中使用時,限制顯示關係型數據的數量是很有必要的。比如,對於有幾百乃至幾千個記錄的Item,其對應的model是Menu,那麼全部顯示出Item的記錄就是不切實際的。

limit_choices_to可用來在form中過濾顯示的關係型數據。

Django Model 事務

事務在model數據操作中扮演着非常重要的角色。當在Django中建立了一個數據庫,下面的有關事務的設置默認為:

AUTOCOMMIT=True
ATOMIC_REQUESTS=False

AUTOCOMMIT設定為True,保證了改變數據(Create,Update,Delete)的操作都進行自身的事務,然而也有很多種情況,是有必要將成組的操作包裝成一個事務(all or nothing)。

每個請求(REQUEST)的事務:ATOMIC_REQUESTS,裝飾器

Django支持ATOMIC_REQUESTS,默認是False,該選項是用來對每個對django application的請求進行一個事務操作,通過設定為True,保證了所有的數據操作都在一個請求內完成,如果這個請求成功的話。

當在view method中的邏輯是’all or nothing’的任務,比如,一個view method需要執行5個與數據有關的子任務(驗證,發郵件等),來確保所有子任務都成功,數據操作才結束,如果有一個子任務失敗,所有的子任務都回回滾,就像什麼也沒發生一樣,是非常重要的。

因為ATOMIC_REQUEST=True對每個request都開了事務,這就意味着可能對「高速」應用的性能有一定的影響。也是基於這樣的因素,單獨的對特定的request開啟/關閉」原子請求「(atomic request)也是被django支持的。

from django.db import transaction
# 當ATOMIC_REQUESTS=True ,可以單獨的關閉atomic requests
@transaction.non_atomic_requests
def index(request):
    #事務的commit/rollback
    data_operation_1()
    data_operation_2()
    data_operation_3()
#當ATOMIC_REQUESTS=False ,可以單獨的開啟atomic requests
@transaction.atomic
def detail(request):
    data_operation_1()
    data_operation_2()
    data_operation_3()

環境管理和回調:atomic(),on_commit()

除了AUTOCOMMIT,ATOMIC_REQUEST配置和view method transaction decorator,也可以將事務運用在一個中等的規模,意思就是,比單獨的數據操作(比如save())要粗糙,但是比atomic request(比如view method)要精細.

with可以激發context manager,它也是用`django.db.transaction.atomic,但不是裝飾器了,而是用在方法的內部。

from django.db import transaction

def login(request):
    # AUTO_COMMIT=True,ATOMIC_REQUESTS=False
    data_operation_standalone()
    
    with transaction.atomic:;
        # 開始事務
        #失敗後,回滾
        data_operation_1()
    	data_operation_2()
    	data_operation_3()
        #如果成功就commit
     data_operation_standalone2()

callback也是可以被支持的,通過on_commit,它的語法如下:

from django.db import transaction
transaction.on_commit(only_after_success_operation)
transaction.on_committ(lambda:only_affter_success_operation_with_args('success'))

Django model 遷移

遷移文件實際上是model到數據庫的緩衝,我們可以在創建完遷移文件後(python manage.py makemigrations,先不寫入數據庫,先提前看下model的變化以及相應的sql語句(使用python manage.py sqlmigrate),當然也可以回滾到models.py的某個時間點。

Migration 文件創建

python manage.py makemigrations命令就是創建遷移文件的第一步。如果不帶任何參數的執行該命令,則Django檢查INSTALLED_APP中聲明的所有app的models.py,然後為每個有改變的models.py創建遷移文件。

下表給出了常用的參數:

Argument Description
<app_name> 專門對某個app的models.py進行創建遷移文件,如python manage.py makemigrations stores
–dry-run 模擬創建遷移文件,但不產生實際的遷移文件,比如:(env) E:\programming\python manage.py makemigrations --dry-run<br/>Migrations for 'testapp':<br/> testapp\migrations\0028_testmig.py<br/> - Create model testmig
–empty 必須提供一個app_name,為其創建一個空遷移文件。
–name ‘my_mig_file’ 為其創建一個自定義名字的遷移文件,需要注意的是前綴依然自動帶有序列號。
–merge 為有衝突的兩個遷移文件進行merge操作。

Migration 文件重命名

遷移文件不是放着不動,對它們進行重命名也是可以的,或者說有時候甚至是必要的。需要進行的重命名步驟取決於該遷移文件是否已經遷移至數據庫。可以執行python manage.py showmigrations來查看是否已經遷移至數據庫,帶有’x’的就是已經遷移到數據庫了。

比如:

image-20201025214540730

對於那些還沒有遷移到數據庫的,可以直接進行更改名字,因為這時候遷移文件只是model 改變的一個表示,並沒有將這種變化反映到數據庫中。

對於已經反映到數據庫中的遷移文件,有兩種辦法:(1)第一種方法是:修改遷移文件名字,更改掌控遷移活動的數據庫表來反映這個新名字,更新其他遷移文件的dependencies(如果有的話)也來反映這個新名字。一旦更改了新的名字,進入django_migrations表中,查看舊的記錄,更新為新的名字,如果有更為新一點的遷移文件,更新一點的遷移文件會有dependencies聲明,這個也需要更新。(2)第二種方法是:回滾到需要更該名字的遷移文件之前,這就相當於還沒有將遷移文件遷移至數據庫,然後直接更改遷移文件的名字,再執行遷移。

遷移文件壓縮(Migration file squashing)

一個經歷了很多改變的models.py,它的遷移文件看可能達到數十,甚至數百個,在這種情況下,對它們進行壓縮也是有可能的。注意是『壓縮』,而不是『merge’,』merge’往往指解決衝突。

語法是:

python manage.py squashmigrations <app_name> <squash_up_to_migration_file_serial_number>

該命令需要app名字以及需要壓縮到的遷移文件的序列號,比如:squashmigrations stores 0004就是將遷移文件0001,0002,0003,0004進行了壓縮;;squashmigrations stores 0002 0004對0002,0003,0004進行了壓縮。

壓縮後的遷移文件遵從如下慣例:

<initial_serial_number>__squashed__<up_to_serial_number>__<date>.py

image-20201025225101091

壓縮的文件替代了被壓縮的文件的功能,也可以選擇保留老的文件,之後的機理是新的壓縮文件中用replaces字段來表明哪箇舊文件被代替。

image-20201025225609594

遷移文件結構

from django.db import migrations,models
class Migration(migrations.Migration):
    initial=True
    replaces=[
        
    ]
    dependencies=[
        
    ]
    operations=[
        
    ]

initial是布爾值,用來表示是否是initial migration file,replaces是壓縮文件中替換了哪個遷移文件的列表,dependenciesoperations是用的最為頻繁的兩個字段。

dependencies是一個包含了元組的('<app_name>’,'<migration_file>’)列表,它告訴了遷移文件是依賴<app_name>下的<migration_file>的,最常見的是添加跨app間的遷移文件。比如,如果onlineapp依賴stores0002_data_population遷移文件,那麼就可以添加一個dependency tuple到onlineapp的第一個遷移文件,以確保它在stores的遷移文件之後運行(比如(‘stores’,’0002_data_population’))

operations字段聲明了包含遷移操作的列表。對於大部分情況,Django根據models的變化來創建遷移操作,比如,加了一個新的model,下一個遷移文件就包含了django.db.migrations.operations.CreateModel();當重新命名了一個model,則下一個遷移文件就包括了django.db.migrations.operations.RenameModel;當改變了一個model字段就有AlterModel();等等。

最為常見的編輯operation是添加非DDL操作,這個不能被作為是models 的改變。可以通過RunSQL來插入SQL查詢的遷移,或者RunPython來進行python的邏輯遷移。

遷移文件回滾

遷移文件回滾非常簡單,只需要python manage.py migrate xxxx就可以回滾到xxxx序列號的地方。

比如:

image-20201026000445955

現在所有表為:

image-20201026000546857

回滾到最後一個:

image-20201026000954772

相應的所有表:

image-20201026001024920

通過比較,發現確實是回滾到了任一指定的位置。

這不是說一定就能回滾成功,當遇到不可逆的操作,就拋出django.db.migrations.exceptions.IrreversibleError,當然,這並不意味不能進行回滾,而是需要額外的工作來使得文件變得可回滾。

大多不可逆的操作是在遷移文件中執行了特定的邏輯(RunSQL,RunPython),為了讓這些操作可回滾,就必須一樣地提供相應的回滾邏輯。下面」Django model初始值設立「部分將詳細講為RunSQLRunPython遷移操作創建可回滾操作。

Django 數據庫任務

Django的manage.py提供了很多命令來執行像備份,加載,刪除數據等任務。

備份:Fixture文件,dumpdata,loaddata,inspectdb

dumpdataloaddata命令是django的數據備份與加載工具,Django用術語fixture來表示由dumpdata產生的,由loaddata來加載的數據結構。默認使用JSON格式,但是也可以用XML,YAML格式(需要安裝pyYAML).

dumpdata的常見用法如下:

  • manage.py dumpdata > all_model_data.json:將所有app的model的數據庫的數據輸出為json文件,注意如果沒有>符號,全部打印在命令框中。
  • manage.py dumpdata <app_name> --format xml:將<app_name>下的model的數據以xml輸出。
  • manage.py dumpdata <app_name>.<model_name> --indent 2:對<app_name>下的<model_name>model的數據以縮進2字符的樣式輸出json格式。

manage.py loaddata來加載dumpdata產生的fixture文件,它的語法就是manage.py loaddata <fixture_file_name>,fixture文件路徑可以是相對的也可以是絕對的,除了給定的路徑,它還對每個app中的fixtures文件夾進行搜索。

loaddata可以接受多個參數(比如loaddata <fixture_>,<fixture_2>…),這是有必要的,如果fixtures文件有內部相互依賴關係。它還可以限制在特定app中進行搜索/加載fixture文件(比如,manage.py loaddata menu –app item,僅搜索/加載在itemapp 下的fixtue 文件menu`。)

manage.py inspectdb是由現有數據庫而創建model的逆向工程。

image-20201026130845698

刪除數據:flush,sqlflush,sqlsequencereset

manage.py flush將清除數據庫中所有數據,而manage.py sqlflush給出清除全部數據所使用的SQL,而並不實際清除。

image-20201026131912845

與數據直接交互:dbshell

manage.py dbshell將根據默認的數據庫而打開相應的命令行shell。

Django model初始數據設立

在很多情況下,對Django model進行預先加載一些數據是有必要啊的,Django允許要麼在python中手工添加,或者用sql語句來添加,或者用fixture文件。

第一步加載一個預先的數據就是先創建一個空的遷移文件,來處理數據加載過程(manage.py makemigrations --empty <app_name>).

  • RunPython

例如:

創建model後,migrate,形成000_initial.py,但此時不要migrate,否則等創建空遷移文件後,將不能把數據寫入數據庫.

python manage.py makemigrations --empty testmodel
# Generated by Django 3.1 on 2020-10-26 12:48

from django.db import migrations,models

def load_stores(apps,schema_editor):
    store=apps.get_model('testmodel','store')#<model_name>,<app_name>
    store_corporate=store(name='Corporate',address='623 borradway',city='San diego',state='CA')
    store_corporate.save()
    store_downtown=store(name='Downtown',address='Horton plazza',city='San diego',state='CA')
    store_downtown.save()
    store_uptown=store(name='Uptown',address='1240 university',city='San diego',state='CA')
    store_uptown.save()
    store_midtown=store(name='Midtown',address='784 washhington',city='San diego',state='CA')
    store_midtown.save()

def delete_stores(apps,schema_editor):
    """
    回滾.
    """
    store=apps.get_model('testmodel','store')
    store.objects.all().delete()

class Migration(migrations.Migration):

    dependencies = [
        ('testmodel', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(load_stores,delete_stores),
    ]

python manage.py migrate testmodel

image-20201026215649357

可見,已經寫入了數據庫。

  • RunSQL

刪除剛才的RunPython的遷移文件,新建一個空的0002號遷移文件,在app下的sql文件夾下寫入以下store.sql文件:

INSERT INTO testmodel_store (id,name,address,city,state) VALUES (0,'hongqi','tianrenlu','chengdu','CD');
INSERT INTO testmodel_store (id,name,address,city,state) VALUES (1,'haoyouduo','kehuabeilu','chengdu','CD');
INSERT INTO testmodel_store (id,name,address,city,state) VALUES (2,'jailefu','shuangdianxilu','chengdu','CD');
INSERT INTO testmodel_store (id,name,address,city,state) VALUES (3,'hongqi','wenwenglu','chengdu','CD');
# Generated by Django 3.1 on 2020-10-26 14:08

from django.db import migrations,models

def load_store_from_sql():
    from testDebug.settings import BASE_DIR
    import os
    sql_statement=open(os.path.join(BASE_DIR,'testmodel/sql/store.sql'),'r').read()
    return sql_statement

def delte_stores_from_sql():
    return 'DELETE from testmodel_store'


class Migration(migrations.Migration):

    dependencies = [
        ('testmodel', '0001_initial'),
    ]

    operations = [
        migrations.RunSQL(load_store_from_sql(),delte_stores_from_sql())#需要注意的是傳入的不是函數索引,而是函數返回值。
    ]

執行遷移命令後(有必要的話,可以刪除db.sqltie3文件)

image-20201026222915557

  • fixture 文件

在app下設fixtures文件夾,文件夾下寫入store.jsonfixture文件(非常重要),如下:

[
    {
        "fields":{
            "city":"beijing",
            "state":"BJ",
            "name":"beijingkaoya",
            "address":"wenhuadadao"
        },
        "model":"testmodel.store",
        "pk":0
    },
    {
        "fields":{
            "city":"nanjing",
            "name":"tianshuimian",
            "state":"NJ",
            "address":"dongfenglu"
        },
        "model":"testmodel.store",
        "pk":1
    }
]

空遷移文件

# Generated by Django 3.1 on 2020-10-26 14:51

from django.db import migrations,models
def load_stores_from_fixture(apps,schema_editor):
    from django.core.management import call_command
    call_command('loaddata','store.json')
def delete_stores(apps,schema_editor):
    Store=apps.get_model('testmodel','store')
    Store.objects.all().delete()


class Migration(migrations.Migration):

    dependencies = [
        ('testmodel', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(load_stores_from_fixture,delete_stores)
    ]

執行遷移後:

image-20201026232651547

Django model 信號

Django models有很多可以覆蓋的方法,如save,__init__等,這樣的話,就可以在裏面嵌入我們特定的邏輯就可以達到model A的某個方法可以觸發model B的另一個方法,但是這樣卻不是比較好的方法,因為model A中有了其他模型的一些信息,不符合低耦合的原則,所以在項目變得逐漸龐大後,這樣會變得很亂,怎麼辦呢?

這樣的問題在軟件工程中非常常見,軟件工程也提供了一個非常棒的方法,那就是signal-slot,每個控件都往環境里發射信號,然後有專門的監聽器,監測到某個信號,就調用與該信號相應的slot函數,類似的,Django也借鑒了這種思路,所有的model都有自己的signal,只需要定義相應的接受函數即可,一般的模式為:

from django.dispatch import receiver
@receiver(<signal_to_listen>,sender=<model_class_to_listen>)
def method_with_logic(sender,**kwargs):
    #logic when signal is emitted
    #Access sender and kwargs to get info on model that emitted signal

需要注意的是sender不一定非得是model索引,也可以是索引的字符,這種情況下是lazy-loading模式。

內置的model signal如下表:

Signal Signal class Description
pre_init,post_init django.db.models.signal.pre_init/post_init 在model的__init__的開始與結束時發射信號
pre_save,post_save django.db.models.signal.pre_save/post_save 在model的save開始與結束時發射信號
pre_delete,post_delete django.db.models.signal.pre_delete/post_delete 在model的delete開始與結束時發射信號
m2m_changed django.db.models.signal.m2m_changed 當一個多對多模型被改變發射信號
class_prepared django.db.models.signals.class_prepared 當一個模型被定義和註冊時候發射信號,一般是django內部機制,較少的使用於其他地方

建議專門把一個app的signal全部寫入signals.py,便於管理。

# app內的signals.py
from django.dispatch import receiver
from django.db.models.signals import pre_save,post_save
from datetime import datetime 
import logging

# logging=logging.getLogger(__name__)
logging.basicConfig(filename='testP.log',level=logging.DEBUG)
@receiver(pre_save,sender='banners.testP')
def run_before_save(sender,**kwargs):
    now=datetime.now().strftime('%Y-%m-%d %H:%m:%S')
    logging.info('new pre save'.center(30,'*'))
    logging.info('Start pre_save testP at %s' % now)
    logging.info('Sender is %s' % sender)
    logging.info('kwargs is %s' % str(kwargs))
@receiver(post_save,sender='banners.testP')
def run_after_save(sender,**kwargs):
    now=datetime.now().strftime('%Y-%m-%d %H:%m:%S')
    logging.info('new post save'.center(30,'*'))
    logging.info('Post_save testP at %s' % now)
    logging.info('Sender is %s' % sender)
    logging.info('kwargs is %s' % str(kwargs))

需要注意的是,寫完signals.py後,需要在app.py中定義ready方法來引入該signal文件

#app.py
from django.apps import AppConfig
class BannersConfig(AppConfig):
    name = 'banners'
    def ready(self):  #定義ready方法,引入signals.py
        import banners.signals

最後還需要在app的__init__.py中定義如下:

# __init__.py
default_app_config='banners.apps.BannersConfig'
>>> from banners.models import *
>>> testP.objects.create(name='johnyang3')
<testP: johnyang3>
>>> testP.objects.create(name='johnyang4')
<testP: johnyang4>
>>> a=testP(name='johnyang5')
>>> a.save()

log文件如下:

image-20201026155750199

定製signal

#models.py引入Signal類
from django.dispatch import Signal
testPClosed=Signal(providing_args=['employee']) #自定義signal,但必須提供providing_args,該參數為sender,receiver都使用
class testP(models.Model):
    name=models.CharField(max_length=5)
    def closing(self,employee):
        testPClosed.send(sender=self.__class__,employee=employee)
    def __str__(self):
        return self.name 
#在signal.py中加入
import logging
from .models import testPClosed
# logging=logging.getLogger(__name__)
logging.basicConfig(filename='testP.log',level=logging.DEBUG)
@receiver(testPClosed)
def run_when_testP_close(sender,**kwargs):
    now=datetime.now().strftime('%Y-%m-%d %H:%m:%S')
    logging.info('new closing'.center(30,'*'))
    logging.info('closing testP at %s' % now)
    logging.info('Sender is %s' % sender)
    logging.info('kwargs is %s' % str(kwargs))

>>> from banners.models import *
>>> a=testP(name='johnyang')
>>> a.closing(2)

image-20201027102155222

除models.py外的其他model放置方法

默認地,Django 的model 是放置在app里的models.py。然而隨着定義的model的數量的增長,儲存在一個文件的方法就越來越不便於管理,對此,有3種方法,將model存放在除models.py之外的位置。

Apps內的models文件夾

model可以存放在apps的models的文件夾中的文件中,首先刪去models.py,然後在models文件夾中添加__init__使之成為一個包,並在__init__中引入models文件夾下各種.py文件中的model,注意,不建議使用from xxx import *,這樣會導致命名空間紊亂。

image-20201027123259552

#__init__.py
from .testModelFolder import storeFolder
#models/testModelFolder.py
from django.db import models
class storeFolder(models.Model):
    name=models.CharField(max_length=3)

image-20201027123604350

Apps內的自定義文件夾

可以在apps內建立多個文件夾,只要每個文件夾下都有一個__init__.py文件,然後就可以在每個文件夾下任意定義.py來儲存各種model,只需要在models.py中引入進來就行from .<sub_folder>.<file> import <model_class>

image-20201027131848894

#testfolds/cutomFile.py
from django.db import models
class storeFolder(models.Model):
    name=models.CharField(max_length=3)
#models.py
from .testfolds.customFile import storeFolder

經過makemigration,migrate後,

image-20201027132043399

Apps外及model「賦值」到apps

這種方法一般不推薦。

它是將apps外任意一個.py的model的meta的app_label賦值為給定的app,然後該model就屬於該app了。

例如:

#about/models.py
from django.db import models

# Create your models here.
class testAcrossApp(models.Model):
    name=models.CharField(max_length=3)
    class Meta:
        app_label='banners'

image-20201027133913543

Django 多數據庫

settings.py中,DATABASES用來定義與app相關的數據庫。既然DATABASES是複數,也就意味着該變量可以有多個值:

#settings.py
DATABASES={
    'default':{
        ...
    },
    'devops':{
        ...
    },
    'analytics':{
        ...
    }
}

...表示數據庫連接參數(比如ENGINE,NAME)。最重要的一點就是必須要有default鍵,除非聲明了DEFAULT_DB_ALIAS值,那麼該值不叫’default’了,但承擔的就是默認數據庫的任務。

model的多數據庫選擇:using

using來使用非默認數據庫,有兩種用法,一種是參數選項,一種是objects的方法。

  • 比如save(using=...),delete(using=...).
  • 比如Item.objects.using('somedb').all().

多數據庫工具:–database

對於manage.py的用法中,可以加入--database,用以對特定數據庫進行操作(migrate,dumpdata等)。比如:python manage.py migrate --database devops就是只對該數據庫執行操作。