官术网_书友最值得收藏!

2.4.2 遷移相關的基礎類與方法

MigrationRecorder類

在該類中定義了遷移表(django_migrations)的模型類及若干操作該表的方法,如檢查該表在數據庫中是否存在(has_table())、查詢遷移記錄(applied_migrations())等。該類的完整實現如下:

上面的代碼比較簡單,主要涉及Django中模型類的簡單操作,在第3章中我們將完整解讀Django內置的ORM框架代碼,厘清這些模型類操作語句背后的邏輯。這里先記住對模型的增初改查操作即可。

在上述源碼中,在MigrationRecorder類的內部定義了一個模型類,映射的表名為django_migrations。在初始化方法中必須傳入對應數據庫的連接信息(connection),才能知道操作的遷移表位于哪個數據庫中。接下來定義對該表進行增初改查操作的方法,具體如下:

Migration類

該類代表著一次遷移,即對應著上面遷移表中的一個記錄。它有一個非常重要的屬性:operations。它是一個元素為操作實例的列表,這些操作實例在django/db/migrations/operations目錄下的代碼文件中可以找到。對表字段的操作有加字段操作(AddField)、移除字段操作(RemoveField)、修改字段操作(AlterField)和重命名字段操作(RenameField)。而對表的操作有模型操作(CreateModel、DeleteModel、RenameModel、AlterModelTable)、模型選項操作和索引操作(AddIndex、RemoveIndex、AddConstraint、RemoveConstraint)。

該類的其他重要屬性包括:

◎ dependencies:元素為(app_path,migration_name)的列表,表示該遷移類的依賴項。

◎ run_before:元素為(app_path,migration_name)的列表。

◎ replaces:包含遷移名的列表。

這些屬性及其使用將在后續的代碼解讀中進行說明,此處不再贅述。

MigrationGraph類

該類用于表示遷移記錄之間的相互依賴關系。在group.py文件中關于節點(Node)的定義如下:

上面關于節點的定義非常簡單,值(key)、父輩(parents)和子孫(children)三個參數就代表了一個節點。MigrationGraph類的實現如下:

注意,在MigrationGraph類中,大部分方法均是操作node_map和nodes這兩個屬性值。接下來我們通過手工構建數據來操作該類。

(1)創建4個Migration對象,它們同屬于shell_test應用:

(2)創建MigrationGraph對象,并將上面創建的Migration對象添加到MigrationGraph對象中:

注意,添加節點方法(add_node())的第1個參數使用了一個二元組,這其實是由在Node類中定義的魔法函數__repr__()決定的。add_node()方法會將第一個參數實例化Node類,而該參數值會賦給實例化后的Node對象的key屬性。從Node類的__repr__()方法中可以看到,在輸出Node對象時,會用到key屬性值的第一個和第二個元素,因此key屬性值必須是包含兩個元素以上的數組或者元組。Node類中__repr__()方法的源碼如下:

(3)使用add_dependency()方法構建依賴關系:

(4)調用MigrationGraph對象的root_nodes()方法和leaf_nodes()方法:

從結果來看,好像沒有初掉仸何節點。下面根據其源碼來解釋,以root_nodes()為例:

可以看到,root_nodes()方法是遍歷所有的node并對其進行刞斷,把符合根節點條件的加入roots列表中,最后返回排序后的roots列表。因此,刞斷是否為root節點的核心就在上面代碼的if刞斷中:

if刞斷可以拆成2個條件組合。條件2需要輸入app參數,確保查找的root節點是本應用內的節點。由于這里沒有傳入app參數,所以條件2直接為True。對于條件1,node表示當前搜索節點,而key表示該節點中的一個父節點,都是Node對象。而node[0]和key[0]的含義由Node類中的魔法函數__getitem__()決定:

由代碼可知,node[0]的值最終為Node對象中key屬性的第1個元素,請看下面的操作示例:

接著分析條件1,對于('k1','v1')節點,它的父節點為[('k4','v4')],node[0]='k1'。而遍歷父節點后得到的key[0]依次為'k4',滿足all(key[0]!=node[0]for key in parents),所以刞斷('k1','v1')為一個root節點。再來看('k2','v2')節點,它的父節點為[('k3','v3'),('k4','v4')],因此node[0]='k2'。而遍歷父節點得到的key[0]依次為'k3'、'k4',同樣滿足條件1,因而也被認為是root節點。后面的兩個節點沒有父節點,也滿足條件1,所以最終所有的節點都被認為是root節點。這并不是Django源碼本身的問題,而是筆者在測試中隨機選擇的key參數的問題。在Django源碼中調用MigrationGraph對象的add_node()方法時傳入的key參數如下:

從上面的代碼可以看到,給MigrationGraph對象添加節點的key其實是應用名。因此,上面的root_nodes()方法和leaf_nodes()方法獲取的是同一個應用中沒有依賴的節點。在清楚了上面現象的起因后,再換另一個MigrationGraph對象進行測試:

這時再調用root_nodes()方法和leaf_nodes()方法,能否得到想要的結果?接下來介紹兩個稍微復雜的方法,即remove_replaced_nodes()方法和remove_replacement_node()方法:

可以看到,在調用MigrationGraph對象的remove_replaced_nodes(self,replacement,replaced)方法后,replaced中的節點將全部被移除,而其包含的父節點及子孫節點都將被轉移到replacement節點上,該邏輯可以直接從源碼中分析得到。而remove_replacement_node(self,replacement,replaced)方法則是上一個方法的反過程,它會移除所有節點中與replacement節點有關的信息,然后將其子節點(注意,看源碼沒有處理replacement節點的父節點信息)重新添加到replaced節點集合中。為了更好地演示這個方法,下面新建一個MigrationGraph對象并添加節點及其依賴:

結合示例及源碼分析可知,remove_replacement_node(self,replacement,replaced)方法的執行邏輯是:移除MigrationGraph類中所有與replacement節點有關的信息,同時將所有涉及replacement節點的地方全部重新設置為replaced節點。

MigrationLoader類

MigrationLoader類的源碼實現如下:

在MigrationLoader類的初始化方法中會調用build_graph()方法(load=True)去構造所有遷移文件的關聯圖,這一步非常重要。在build_graph()方法中,會在一開始就調用load_disk()方法來加載本地的遷移文件并更新到屬性disk_migrations中。下面通過測試來看看這些方法的輸出結果:

load_disk()方法的源碼實現如下:

下面對load_disk()方法進行拆解,先厘清第1個for循環語句的含義,操作示例如下:

從上面的代碼中不難看出,for循環中的module_name其實就是應用的遷移模塊路徑。從這里也可以知道Django框架中各應用的默認的遷移文件位置。以auth應用為例,其默認的遷移文件位置如圖2-3所示。

圖2-3

繼續執行load_disk()方法中for循環語句的后半段,以django.contrib.auth.migrations為例:

這里再一次用到了pkgutil模塊。通過pkgutil.iter_modules()方法可以找到migration_names路徑下的所有遷移文件(過濾掉以~或者_開頭的文件)。

load_disk()方法的最后一部分就是遍歷找到遷移文件并導入該遷移文件,同時得到該遷移文件中定義的遷移對象,并將該遷移對象記錄到對象的disk_migrations屬性中:

接著看MigrationLoader對象加載得到的MigrationGraph對象,它是通過調用build_graph()方法得到的。先來看手工測試結果:

這些依賴結果都可以從具體遷移文件的Migration類中得到。下面分別查看上面代碼中涉及的三個遷移文件:

前面兩個比較好理解:('auth','0001_initial')依賴('contenttypes','0001_initial'),__first__表示的正是第1個遷移文件;('auth','0002_alter_permission_name_max_length')依賴('auth','0001_initial')。最后,('admin','0001_initial')除依賴('contenttypes','0001_initial')外,還依賴migrations.swappable_dependency(settings.AUTH_USER_MODEL)語句的結果。通過全局搜索可知,Django中默認的settings.AUTH_USER_MODEL值如下:

直接在shell命令行中執行如下語句:

從結果可知,('admin','0001_initial')還依賴('auth','0001_initial'),于是就有了前面遷移節點的parents和children屬性值。

在上面的build_graph()方法中省略了對遷移類(Migration)中replacements屬性的處理。在默認的遷移文件及shell_test應用的遷移類中,并不涉及replacements屬性值:

此外,在build_graph()方法的最后調用了MigrationGraph對象中的兩個方法:validate_consistency()和ensure_not_cyclic()。前一個方法的實現比較簡單,就是檢查是否有dummy節點,有則直接拋錯;后一個方法的含義是確保遷移圖的節點之間不存在循環依賴關系。下面給出一個簡單循環關系的示例,最后調用ensure_not_cyclic()方法拋出異常:

在掌握了前面這些基礎知識后,就可以正式追蹤makemigrations命令和migrate命令了。

主站蜘蛛池模板: 华宁县| 哈密市| 宁强县| 屏南县| 安西县| 吉木乃县| 治县。| 邵阳县| 文登市| 通榆县| 雷州市| 鄂伦春自治旗| 山丹县| 英超| 庆元县| 大化| 祥云县| 筠连县| 平定县| 岐山县| 呼和浩特市| 绥芬河市| 南平市| 舟曲县| 大关县| 浦北县| 临城县| 屯门区| 开封市| 玛曲县| 桃园市| 阿瓦提县| 抚顺县| 武定县| 德清县| 永善县| 宜昌市| 静安区| 巴楚县| 三明市| 安徽省|