---
# System prepended metadata

title: Django models and its inheritance structure
tags: [python, django, Paraphrasing]

---

# Django models and its inheritance structure
## 閱讀須知
1. 這裡python都是指python3
2. 知道 django models 基本用法
3. 了解 python class到底是什麼，它是怎麼產生的，以及metaclass在其中扮演什麼角色。Stackoverflow的[這篇回答](https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python)寫得超棒。

## 內文
### 問題
上課時遇到一個奇怪的現象，當我們跑以下案例時，`MyModel.objects`會是內建的`models.Manager`的instance，而非`CustomManager`的instance。（但可以叫到`hehehaha`）
```python=
class CustomManager(models.Manager):
    def custom_method(self):
        return self.filter(some_field=True)


class BaseModelWithCustomManager:
    objects = CustomManager()

    def hehehaha():
        print("this is a test")


class MyModel(BaseModelWithCustomManager, models.Model):
    name = models.CharField(max_length=255)
    some_field = models.BooleanField(default=False)
    
    example = "for demonstration purpose"
    def meow():
        print("meow")
```
這很不符合一般對python OOP 繼承的理解，因為一般來說，當子層沒有某attribute或是method時，會先從第一個親層開始找。而`MyModel`既然沒有自己定義`objects`，難道不是應該先從`BaseModelWithCustomManager`開始找，然後找到為`CustomManager`Instance的`objects`嗎？假設沒有繼承到的話，為什麼`MyModel`又能叫到`hehehaha`呢？還是電腦壞掉了？

**先講結論，`MyModel`在編譯的過程中被塞入了一個`objects`，而這個`objects`及為`model.Manager`的Instance。在自己有`objects`的情況下，它就不會往上找了。**

接續章節會順著`objects`被塞入的過程解釋。
### Metaclass 
首先我們要先了解，python 的 class 本身也是一個object，會在interpret的過程中被metaclass 生成。換句話說，每一個class真正的樣貌不一定是我們寫得那樣，一個class確切有什麼attribute, methods，這些methods 或attributes到底長怎樣，都是由他的metaclass決定的。
在沒有特別定義的情況下，任何class的metaclass會是`type`。這裡`type`不是類別，而是有一個metaclass名叫`type`。

換句話說，interpreter跑到以下code時
```python=
class Example:
    pass
```
它實際上是用python跑這個東西
```python=
Example = type('Example', (), {})
```
這裡Example就是我們習以為常的class，可以直接用來實體化或操作任何我們習慣的行為。
另外，type 的參數為class_name, bases, attrs。所以我們其實可以把class寫成以下：
```python=
#範例一
Example = type('Example', (), {})
eg1 = Example() #eg1 是 Example class 實體化出來的物件

#範例二 
Example2 = type('Example2', (), {"name": "will"}) #有 name 這個 class attribute
eg2 = Example2()
eg2.name #印出 will

#範例三
Example3 = type('Example3', (Example2, ), {}) #繼承Example2
eg3 = Example3() 
eg3.name #印出 will
```

相當然而，既然有生成的方法(metaclass)，那就可以客製化生成的方法(customized metaclass)。而django models 就是有寫自己的metaclass，而這個metaclass 在生成我們的`MyModel`時，塞入了一個名叫`objects`的class attribute。

決定某個class的metaclass的方法也很簡單，只要給一個keyword argument `metaclass`就好了。
```python=
class Example(Parent1, metaclass=custom_metaclass):
    pass

#Python interpreter在生成 Example 前，會先去找他是否有metaclass，
#然後再根據這個metaclass 的__new__或是__init__來決定怎麼處理
#Example的 attributes, methods 以及
#Example和 Parent1 的關係
```
那假如class 本身沒有定義metaclass，但他的親層有呢？
```python=
class Parent1(metaclass=custom_metaclass):
    pass

class Example(Parent1):
    pass

# python interpreter 在生成Example 時，假如自已沒有metaclass
# 會去看他的上層中是否有定義metaclass
# 有的話就會用那個，沒有的話就用原生的 type
# 所以這個範例的Example, 就會被custom_metaclass生成
# 也可以說，Example 是 custom_metaclass的一個instance
```

關於metaclass 更多的說明請看閱讀須知第三項的連結，它寫得更簡單，更好懂，也更細。此文只寫到這裡，是希望讀完上述內容，便能理解Django model的繼承到底在幹嘛。


### Django Model生成的實際流程
*除去案例，以下其他code都是django model原始碼節錄過來的。*
> 這裡只會擷取解釋所需片段，建議有興趣的自己去爬[source code](https://github.com/django/django)。可以專注在`django/db/models/sql/base.py` 中的`ModelBase`。


回到我們一開始的案例：
```python=
class CustomManager(models.Manager):
    def custom_method(self):
        return self.filter(some_field=True)


class BaseModelWithCustomManager:
    objects = CustomManager()

    def hehehaha():
        print("this is a test")


class MyModel(BaseModelWithCustomManager, models.Model):
    name = models.CharField(max_length=255)
    some_field = models.BooleanField(default=False)
    
    example = "for demonstration purpose"
    def meow():
        print("meow")
```

當我們跑到`MyModel`時，python interpreter開始去找是否有一個metaclass。結果還真的有，`models.Model`定義了`metaclass=ModelBase`。
![Screenshot 2024-08-13 at 5.05.30 PM](https://hackmd.io/_uploads/SJJgGsO5A.png)
而`ModelBase`本身長這樣
![Screenshot 2024-08-13 at 5.27.57 PM](https://hackmd.io/_uploads/rkg4wid5R.png)

`ModelBase`這個metaclass在生成一個新的class時，會把某些特定的attributes拔出來特別對待。
例如，我們寫在`MyModel`中的`name = models.CharField(max_length=255)`和 `some_field = models.BooleanField(default=False)`會被轉移成所需資料型態（string 和 boolean），而不真的是`CharField`和`BooleanField`的instance。
下圖是`ModelBase`在分類attributes的過程。範例中的`name`和`some_field`會被丟到`contributable_attrs`裡。而`example`和`meow`則會被丟到`new_attrs`裡
![Screenshot 2024-08-13 at 5.14.14 PM](https://hackmd.io/_uploads/Byfd4sd5C.png)

接就會實體化出一個目標的`new_class`
![Screenshot 2024-08-13 at 5.20.16 PM](https://hackmd.io/_uploads/SJxOBjdqC.png)
這裡的`super_new`就會直接叫上`type.__new__`，然後用上一個章節提過的方法，`type(clsname, bases, attrs)`，生成一個class。

（沒錯，ModelBase這個metaclass繼承自type這個原生metaclass。metaclass type 基本上是所有 metaclass的root。）

看到這裡，你可能會疑惑，super_new中不是有塞入`bases`嗎？那不就包含了所有寫進去的親層，也就是包含我們自己定義的`BaseModelWithCustomManager`嗎？

是的，所以`MyModel`依舊會被當作`BaseModelWithCustomManager`的subclass，他們的確有繼承關係。這也是為什麼 `MyModel`可以呼叫到`BaseModelWithCustomManager`中定義的methods或是其他一般attributes。

但是接著`ModelBase`就會檢查`contributable_attrs`內是否包含所需資料，其中就會檢查有沒有`objects`。而`MyModel`本身是沒有`objects`的。`ModelBase`這時候就會好心的塞一個預設的`objects` 給`MyModel`。
![Screenshot 2024-08-13 at 5.54.34 PM](https://hackmd.io/_uploads/BkpP6sOqA.png)


所以這時候`MyModel`就會有自己的`objects`了，而且是`models.Manager`的instance，之後當然就不會去找親層的`objects`了。好奇的話可以開`MyModel.__dict__`看看。

### 正確寫法
所以假設我們想要自己設定的manager，我們一定得寫到`MyModel`裡
```python=
class CustomManager(models.Manager):
    def custom_method(self):
        return self.filter(some_field=True)


class BaseModelWithCustomManager:
    def hehehaha():
        print("this is a test")


class MyModel(BaseModelWithCustomManager, models.Model):
    name = models.CharField(max_length=255)
    some_field = models.BooleanField(default=False)
    
    objects = CustomManager() # 寫在這裡
    
    example = "for demonstration purpose"
    def meow():
        print("meow")
```

那假如我們還是想要拆開各個常用的零組件，讓這些零組件不用被重複書寫，符合OOP的精神呢？
```python=
class CustomManager(models.Manager):
    def custom_method(self):
        return self.filter(some_field=True)
    
# 繼承自 models.Model
class BaseModelWithCustomManager(models.Model):
    objects = CustomManager() # 寫在這裡
    
    def hehehaha():
        print("this is a test")


class MyModel(BaseModelWithCustomManager):
    name = models.CharField(max_length=255)
    some_field = models.BooleanField(default=False)
    
    example = "for demonstration purpose"
    def meow():
        print("meow")
        
# 直接讓 BaseModelWithCustomManager 是 ModelBase 生出來的東西
# 這樣 ModelBase 之後在生 MyModel 時就不會以為 objects 不存在了
```

但這樣寫會有個問題，就是migrate時除了`MyModel`外，還會生出`BaseModelWithCustomManager`這個table。這時候可以參考django官方寫法。
```python=
class CustomManager(models.Manager):
    def custom_method(self):
        return self.filter(some_field=True)
    
class BaseModelWithCustomManager(models.Model):
    objects = CustomManager() 
    
    def hehehaha():
        print("this is a test")
    
    # 加這行
    class Meta:
        abstract = True


class MyModel(BaseModelWithCustomManager):
    name = models.CharField(max_length=255)
    some_field = models.BooleanField(default=False)
    
    example = "for demonstration purpose"
    def meow():
        print("meow")

```

這樣就可以達成我們期待的目標了。

## 其他
Model的分拆方法不止設定`abstract = True` 而已。有興趣的可以看[這裡](https://docs.djangoproject.com/en/5.0/topics/db/models/)。

上面遇到的問題，假如在上層定義`Fields`一樣可能會遇到。Django 上針對這點有詳細的[討論](https://docs.djangoproject.com/en/5.0/howto/custom-model-fields/#background-theory)。

另外，Django model其實可以做超多客製化的東西，不論是`Fields`、`Manager`、`Query`都行。有興趣可以參考[這裡](https://docs.djangoproject.com/en/5.0/topics/db/models/)。

最後感謝django forum 裡大大的熱心協助與方向指引。
https://forum.djangoproject.com/t/custom-manager-not-working-through-inheritance/33793









