Django模型(1)

1. ORM模型介紹

隨著項(xiàng)目越來越大,采用寫原生SQL的方式在代碼中會(huì)出現(xiàn)大量的SQL語句,那么問題就出現(xiàn)了:

  1. SQL語句重復(fù)利用率不高,越復(fù)雜的SQL語句條件越多,代碼越長(zhǎng)。會(huì)出現(xiàn)很多相近的SQL語句。
  2. 很多SQL語句是在業(yè)務(wù)邏輯中拼出來的,如果有數(shù)據(jù)庫需要更改,就要去修改這些邏輯,這會(huì)很容易漏掉對(duì)某些SQL語句的修改。
  3. SQL時(shí)容易忽略web安全問題,給未來造成隱患。SQL注入。

ORM,全稱Object Relational Mapping,中文叫做對(duì)象關(guān)系映射,通過ORM我們可以通過類的方式去操作數(shù)據(jù)庫,而不用再寫原生的SQL語句。通過把表映射成類,把行作實(shí)例,把字段作為屬性,ORM在執(zhí)行對(duì)象操作的時(shí)候最終還是會(huì)把對(duì)應(yīng)的操作轉(zhuǎn)換為數(shù)據(jù)庫原生語句。使用ORM有許多優(yōu)點(diǎn):

  1. 易用性:使用ORM做數(shù)據(jù)庫的開發(fā)可以有效的減少重復(fù)SQL語句的概率,寫出來的模型也更加直觀、清晰。
  2. 性能損耗?。?code>ORM轉(zhuǎn)換成底層數(shù)據(jù)庫操作指令確實(shí)會(huì)有一些開銷。但從實(shí)際的情況來看,這種性能損耗很少(不足5%),只要不是對(duì)性能有嚴(yán)苛的要求,綜合考慮開發(fā)效率、代碼的閱讀性,帶來的好處要遠(yuǎn)遠(yuǎn)大于性能損耗,而且項(xiàng)目越大作用越明顯。
  3. 設(shè)計(jì)靈活:可以輕松的寫出復(fù)雜的查詢。
  4. 可移植性:Django封裝了底層的數(shù)據(jù)庫實(shí)現(xiàn),支持多個(gè)關(guān)系數(shù)據(jù)庫引擎,包括流行的MySQL、PostgreSQLSQLite。可以非常輕松的切換數(shù)據(jù)庫。

2. 數(shù)據(jù)庫的相關(guān)配置

Django數(shù)據(jù)庫的相關(guān)配置在settings.py文件中的DATABASE進(jìn)行設(shè)置。Django默認(rèn)使用SQLite數(shù)據(jù)庫可以不用配置(創(chuàng)建項(xiàng)目時(shí)已經(jīng)配置好) ,如果需要使用其他數(shù)據(jù)庫則需要指定數(shù)據(jù)庫的類型(ENGINE),然后根據(jù)選擇的數(shù)據(jù)庫類型配置其所需的其他參數(shù)選項(xiàng)。

# 默認(rèn)數(shù)據(jù)庫配置
DATABASES = {
    'default': {
        # 使用sqlite3數(shù)據(jù)庫
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

# 配置mysql數(shù)據(jù)庫配置
DATABASES = {
    'default': {
        # 數(shù)據(jù)庫引擎(是mysql還是oracle等)
        'ENGINE': 'django.db.backends.mysql',
        # 數(shù)據(jù)庫的名字
        'NAME': 'mytest',
        # 連接mysql數(shù)據(jù)庫的用戶名
        'USER': 'root',
        # 連接mysql數(shù)據(jù)庫的密碼
        'PASSWORD': 'root',
        # mysql數(shù)據(jù)庫的主機(jī)地址
        'HOST': '127.0.0.1',
        # mysql數(shù)據(jù)庫的端口號(hào)
        'PORT': '3306',
    }
}

ENGINE:數(shù)據(jù)庫引擎,指定Django使用的數(shù)據(jù)庫類型,Django支持PostgreSQL、MySQLSQLite、 MariaDB 、Oracle等數(shù)據(jù)庫,具體可以查看官方文檔

3. 數(shù)據(jù)庫模型的定義

ORM模型一般都是放在appmodels.py文件中 ,每個(gè)模型都是一個(gè)繼承至 django.db.models.Modelpython類, 模型的每個(gè)屬性表示一個(gè)數(shù)據(jù)庫字段 。

from django.db import models

# 定義一個(gè)myapp_person表,表名沒有特別指定默認(rèn)使用[app名稱_模型類名小寫]的方式定義
class Person(models.Model):
    # 如果沒有定義主鍵,模型類會(huì)自動(dòng)在數(shù)據(jù)庫創(chuàng)建一個(gè)id字段,id字段是int類型自動(dòng)增長(zhǎng)并設(shè)置為主鍵
    # 定義一個(gè)user_name的字段,映射到數(shù)據(jù)庫類型時(shí)varchar類型,最大長(zhǎng)度為30
    user_name = models.CharField(max_length=30)
    # 定義一個(gè)user_age的字段,映射到數(shù)據(jù)庫類型時(shí)int類型
    user_age = models.IntField()

3.1 模型類字段的類型

3.1.1 內(nèi)置的字段類型

Django模型類字段的類型在 django.db.models.fields可以查看所有的字段類型及其實(shí)現(xiàn)源碼,也可以在官方文檔中查看,Django模型類字段類型大概有30個(gè)左右,這里只例舉常見的字段類型:

  1. AutoField: 映射到數(shù)據(jù)庫中是int類型,可以有自動(dòng)增長(zhǎng)的特性。一般不需要使用這個(gè)類型,如果不指定主鍵,那么模型會(huì)自動(dòng)的生成一個(gè)叫做id的自動(dòng)增長(zhǎng)的主鍵。如果你想指定一個(gè)其他名字的并且具有自動(dòng)增長(zhǎng)的主鍵,使用AutoField也是可以的。

  2. BigAutoField:64位的整形,類似于AutoField,只不過是產(chǎn)生的數(shù)據(jù)的范圍是從1-9223372036854775807

  3. BooleanField:在模型層面接收的是True/False。在數(shù)據(jù)庫層面是tinyint類型。如果沒有指定默認(rèn)值,默認(rèn)值是None

  4. CharField:在數(shù)據(jù)庫層面是varchar類型。在Python層面就是普通的字符串。這個(gè)類型在使用的時(shí)候必須要指定最大的長(zhǎng)度,也即必須要傳遞max_length這個(gè)關(guān)鍵字參數(shù)進(jìn)去。

  5. DateField:日期類型。在Python中是datetime.date類型,可以記錄年月日。在映射到數(shù)據(jù)庫中也是date類型。使用這個(gè)Field可以傳遞以下幾個(gè)參數(shù):

    ? (1). auto_now:在每次這個(gè)數(shù)據(jù)保存的時(shí)候,都使用當(dāng)前的時(shí)間。比如作為一個(gè)記錄修改日期的字段,可以將這個(gè)屬性設(shè)置為True

    ? (2). auto_now_add:在每次數(shù)據(jù)第一次被添加進(jìn)去的時(shí)候,都使用當(dāng)前的時(shí)間。比如作為一個(gè)記錄第一次入庫的字段,可以將這個(gè)屬性設(shè)置為True

  6. DateTimeField:日期時(shí)間類型,類似于DateField。不僅僅可以存儲(chǔ)日期,還可以存儲(chǔ)時(shí)間。映射到數(shù)據(jù)庫中是datetime類型。這個(gè)Field也可以使用auto_nowauto_now_add兩個(gè)屬性。

  7. TimeField:時(shí)間類型。在數(shù)據(jù)庫中是time類型。在Python中是datetime.time類型。

  8. EmailField:類似于CharField。在數(shù)據(jù)庫底層也是一個(gè)varchar類型。最大長(zhǎng)度是254個(gè)字符。

  9. FileField:用來存儲(chǔ)文件的。這個(gè)請(qǐng)參考后面的文件上傳章節(jié)部分。

  10. ImageField:用來存儲(chǔ)圖片文件的。這個(gè)請(qǐng)參考后面的圖片上傳章節(jié)部分。

  11. FloatField:浮點(diǎn)類型。映射到數(shù)據(jù)庫中是float類型。

  12. IntegerField:整形。值的區(qū)間是-2147483648——2147483647。

  13. BigIntegerField:大整形。值的區(qū)間是-9223372036854775808——9223372036854775807

  14. PositiveIntegerField:正整形。值的區(qū)間是0——2147483647。

  15. SmallIntegerField:小整形。值的區(qū)間是-32768——32767

  16. PositiveSmallIntegerField:正小整形。值的區(qū)間是0——32767

  17. TextField:大量的文本類型。映射到數(shù)據(jù)庫中是longtext類型。

  18. UUIDField:只能存儲(chǔ)uuid格式的字符串。uuid是一個(gè)32位的全球唯一的字符串,一般用來作為主鍵。

  19. URLField:類似于CharField,只不過只能用來存儲(chǔ)url格式的字符串。并且默認(rèn)的max_length是200。

3.1.2 自定義字段類型

有時(shí)Django內(nèi)置的字段類型無法滿足你的精確要求,或者你希望使用與Django附帶的字段類型完全不同的字段類型,Django支持用戶自定義字段類型。

自定義字段類型:定義一個(gè)類,這個(gè)類需繼承至Field類(django.db.models.Field)或者Field類的子類,如:CharField、IntegerField類等

from django.db import models

class HandField(models.Field):

    description = "幫助卡牌分類,專門設(shè)置的類型"

    def __init__(self, *args, **kwargs):
        kwargs['max_length'] = 104
        super().__init__(*args, **kwargs)

所謂的自定義字段類型其實(shí)就是類的繼承(繼承內(nèi)置的字段類型),在內(nèi)置的字段類型基礎(chǔ)之上擴(kuò)展其他功能特性,因?yàn)樽远x字段類型繼承至Field類或其子類,所以自定義類的__init__()方法支持內(nèi)置類型所有的字段可選參數(shù)[見3.2模型類字段可選參數(shù)]

如果需要在繼承的字段上添加額外的選項(xiàng),則需編寫新的 deconstruct() 方法 。deconstruct()方法很簡(jiǎn)單,它返回由四個(gè)項(xiàng)組成的元組:字段的屬性名、字段類的完整導(dǎo)入路徑、位置參數(shù)(作為列表)和關(guān)鍵字參數(shù)(作為dict)。

from django.db import models

class CommaSepField(models.Field):
    "實(shí)現(xiàn)列表的逗號(hào)分隔存儲(chǔ)"

    def __init__(self, separator=",", *args, **kwargs):
        self.separator = separator
        super().__init__(*args, **kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        # 如果不是默認(rèn)值,則僅包含kwarg
        if self.separator != ",":
            kwargs['separator'] = self.separator
        return name, path, args, kwargs

一當(dāng)你自定義好了你的字段類型,你就可以像內(nèi)置字段類型一樣在models.py文件中使用它了。

class Person(models.Model):
    name = models.CharField(max_length=80)
    something_else = MytypeField()

如何自定義字段類型更加深入的用法請(qǐng)查看Django官方文檔。

3.2 模型類字段可選參數(shù)

Django模型類字段除了可選擇字段類型,還可以給字段增加一些限定參數(shù), 以下常用參數(shù)可用于所有字段類型。所有都是可選的,全部可選參數(shù)見官方文檔 ,特定的模型類字段類型還有特定的可選參數(shù),如CharFieldmax_length參數(shù),但是在其他字段類型卻不一定能使用。

null: 如果設(shè)置為TrueDjango將會(huì)在映射表的時(shí)候指定是否為空。默認(rèn)是為False。在使用字符串相關(guān)的FieldCharField/TextField)的時(shí)候,官方推薦盡量不要使用這個(gè)參數(shù),也就是保持默認(rèn)值False。因?yàn)?code>Django在處理字符串相關(guān)的Field的時(shí)候,即使這個(gè)Fieldnull=False,如果你沒有給這個(gè)Field傳遞任何值,那么Django也會(huì)使用一個(gè)空的字符串""來作為默認(rèn)值存儲(chǔ)進(jìn)去。因此如果再使用null=True,Django會(huì)產(chǎn)生兩種空值的情形(NULL或者空字符串)。如果想要在表單驗(yàn)證的時(shí)候允許這個(gè)字符串為空,那么建議使用blank=True。如果你的FieldBooleanField,那么對(duì)應(yīng)的可空的字段則為NullBooleanField

blank:如果 True ,該字段允許為空。默認(rèn)是 False .

注意:null純粹與數(shù)據(jù)庫相關(guān),而blank是否與驗(yàn)證相關(guān)。如果字段有blank=True,表單驗(yàn)證將允許輸入空值。如果字段有blank=False,字段將是必需的。

db_column:用于此字段的數(shù)據(jù)庫列的名稱。如果沒有給出,Django將使用模型類的屬性作為數(shù)據(jù)庫字段的名稱。如果你的數(shù)據(jù)庫列名是SQL保留字,或者包含在python變量名中不允許使用的字符(尤其是連字符),那么就可以使用這個(gè)參數(shù)。Django會(huì)在后臺(tái)引用列和表名。

db_index: 如果 True ,將為此字段創(chuàng)建數(shù)據(jù)庫索引。

default:字段的默認(rèn)值。這可以是一個(gè)值或可調(diào)用對(duì)象。如果可調(diào)用,則每次創(chuàng)建新對(duì)象時(shí)都會(huì)調(diào)用它,此外lambda 不能用于字段的default選項(xiàng) 。默認(rèn)值不能是可變對(duì)象(模型實(shí)例, list , set 等),作為對(duì)該對(duì)象同一實(shí)例的引用,如果設(shè)置了default所有新模型實(shí)例在沒有指定值時(shí)都會(huì)使用此默認(rèn)值。當(dāng)然也可以將所需的默認(rèn)值包裝在可調(diào)用文件中。

primary_key: 如果 True ,此字段是模型的主鍵 , 默認(rèn)是False 。 如果您不指定 primary_key=True 對(duì)于模型中的任何字段,Django將自動(dòng)添加 一個(gè)字段名為id類型為AutoField保留主鍵,這樣意味著定義模型類可以不需要設(shè)置 primary_key=True 在任何字段上,模型就有一個(gè)主鍵,除非要重寫默認(rèn)的主鍵行為才需要手動(dòng)設(shè)置primary_key=True 在響應(yīng)的字段上。 primary_key=True 暗示這字段 null=False(不能為空) 和 unique=True(必須唯一),一個(gè)對(duì)象上只允許有一個(gè)主鍵。

unique: 如果 True ,此字段在整個(gè)表中必須是唯一的。

validators:給字段指定特定的驗(yàn)證器,根據(jù)驗(yàn)證器來約束字段。

from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

# 只允許偶數(shù)的驗(yàn)證器
def validate_even(value):
    if value % 2 != 0:
        raise ValidationError(
            _('%(value)不是一個(gè)偶數(shù)!'),
            params={'value': value},
        )

可以通過字段的validators約束字段:

from django.db import models

class MyModel(models.Model):
    # 使用驗(yàn)證器約束字段
    even_field = models.IntegerField(validators=[validate_even])

注意:

  1. 有些可選參數(shù)有默認(rèn)值,我們可以不用進(jìn)行相關(guān)配置,除非需要對(duì)其默認(rèn)值進(jìn)行修改
  2. 有些參數(shù)是相互影響的,比如primary_key=True 暗示字段 null=False(不能為空) 和 unique=True(必須唯一)

3.3 模型類的繼承

類同于Python的類繼承,Django也有完善的繼承機(jī)制。Django中所有的模型類都必須繼承django.db.models.ModelDjango模型類之間有三種繼承的方式:

  • 抽象基類:被用來繼承的模型被稱為Abstract base classes,將子類共同的數(shù)據(jù)抽離出來,供子類繼承重用,它不會(huì)創(chuàng)建實(shí)際的數(shù)據(jù)表;
  • 多表繼承:Multi-table inheritance,每一個(gè)模型都有自己的數(shù)據(jù)庫表;
  • 代理模型:如果你只想修改模型的Python層面的行為,并不想改動(dòng)模型的字段,可以使用代理模型。

3.3.1 抽象基類

只需要在模型的Meta類里添加abstract=True元數(shù)據(jù)項(xiàng),就可以將一個(gè)模型轉(zhuǎn)換為抽象基類。Django不會(huì)為這種類創(chuàng)建實(shí)際的數(shù)據(jù)庫表,它們也沒有管理器,不能被實(shí)例化也無法直接保存,它們就是用來被繼承的。抽象基類完全就是用來保存子模型們共有的內(nèi)容部分,達(dá)到重用的目的。當(dāng)它們被繼承時(shí),它們的字段會(huì)全部復(fù)制到子模型中。看下面的例子:

from django.db import models

class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True

class Student(CommonInfo):
    home_group = models.CharField(max_length=5)

Student模型將擁有name,agehome_group三個(gè)字段,并且CommonInfo模型不能當(dāng)做一個(gè)正常的模型使用。

抽象基類的Meta數(shù)據(jù):

如果子類沒有聲明自己的Meta類,那么它將繼承抽象基類的Meta類。 子類想擴(kuò)展父類的 Meta類,可以將其子類化 ,下面的例子則擴(kuò)展了基類的Meta

from django.db import models

class CommonInfo(models.Model):
    # ...
    class Meta:
        abstract = True
        ordering = ['name']

class Student(CommonInfo):
    # ...
    class Meta(CommonInfo.Meta):
        db_table = 'student_info'

這里有幾點(diǎn)要特別說明:

  • 抽象基類中有的元數(shù)據(jù),子模型沒有的話,直接繼承;
  • 抽象基類中有的元數(shù)據(jù),子模型也有的話,直接覆蓋;
  • 子模型可以額外添加元數(shù)據(jù);
  • 抽象基類中的abstract=True這個(gè)元數(shù)據(jù)不會(huì)被繼承。也就是說如果想讓一個(gè)抽象基類的子模型,同樣成為一個(gè)抽象基類,那你必須顯式的在該子模型的Meta中同樣聲明一個(gè)abstract = True;
  • 有一些元數(shù)據(jù)對(duì)抽象基類無效,比如db_table,首先是抽象基類本身不會(huì)創(chuàng)建數(shù)據(jù)表,其次它的所有子類也不會(huì)按照這個(gè)元數(shù)據(jù)來設(shè)置表名。

注意related_name和related_query_name參數(shù)

如果在你的抽象基類中存在ForeignKey或者ManyToManyField字段,并且使用了related_name或者related_query_name參數(shù),那么一定要小心了。因?yàn)榘凑漳J(rèn)規(guī)則,每一個(gè)子類都將擁有同樣的字段,這顯然會(huì)導(dǎo)致錯(cuò)誤。為了解決這個(gè)問題,當(dāng)你在抽象基類中使用related_name或者related_query_name參數(shù)時(shí),它們兩者的值中應(yīng)該包含%(app_label)s%(class)s部分:

  • %(class)s用字段所屬子類的小寫名替換
  • %(app_label)s用子類所屬app的小寫名替換

例如,對(duì)于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

對(duì)于另外一個(gè)應(yīng)用中的rare/models.py:

from common.models import Base

class ChildB(Base):
    pass

對(duì)于上面的繼承關(guān)系:

  • common.ChildA.m2m字段的reverse name(反向關(guān)系名)應(yīng)該是common_childa_relatedreverse query name(反向查詢名)應(yīng)該是common_childas。
  • common.ChildB.m2m字段的反向關(guān)系名應(yīng)該是common_childb_related;反向查詢名應(yīng)該是common_childbs。
  • rare.ChildB.m2m字段的反向關(guān)系名應(yīng)該是rare_childb_related;反向查詢名應(yīng)該是rare_childbs。

當(dāng)然,如果你不設(shè)置related_name或者related_query_name參數(shù),這些問題就不存在了。

3.3.2 多表繼承

這種繼承方式下,父類和子類都是獨(dú)立自主、功能完整、可正常使用的模型,都有自己的數(shù)據(jù)庫表,內(nèi)部隱含了一個(gè)一對(duì)一的關(guān)系。例如:

from django.db import models

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

Restaurant將包含Place的所有字段,并且各有各的數(shù)據(jù)庫表和字段,比如:

>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")

如果一個(gè)Place對(duì)象同時(shí)也是一個(gè)Restaurant對(duì)象,你可以使用小寫的子類名,在父類中訪問它,例如:

>>> p = Place.objects.get(id=12)
# 如果p也是一個(gè)Restaurant對(duì)象,那么下面的調(diào)用可以獲得該Restaurant對(duì)象。
>>> p.restaurant
<Restaurant: ...>

但是,如果這個(gè)Place是個(gè)純粹的Place對(duì)象,并不是一個(gè)Restaurant對(duì)象,那么上面的調(diào)用方式會(huì)彈出Restaurant.DoesNotExist異常。

自動(dòng)創(chuàng)建的 OneToOneFieldRestaurant 鏈接到 Place 如下所示:

place_ptr = models.OneToOneField(
    Place, on_delete=models.CASCADE,
    parent_link=True,
)

可以通過創(chuàng)建一個(gè)OneToOneField字段并設(shè)置 parent_link=True,自定義這個(gè)一對(duì)一字段。

Meta和多表繼承

在多表繼承的情況下,由于父類和子類都在數(shù)據(jù)庫內(nèi)有物理存在的表,父類的Meta類會(huì)對(duì)子類造成不確定的影響,因此,Django在這種情況下關(guān)閉了子類繼承父類的Meta功能。這一點(diǎn)和抽象基類的繼承方式有所不同。

但是,還有兩個(gè)Meta元數(shù)據(jù)特殊一點(diǎn),那就是orderingget_latest_by,這兩個(gè)參數(shù)是會(huì)被繼承的。因此,如果在多表繼承中,你不想讓你的子類繼承父類的上面兩種參數(shù),就必須在子類中顯示的指出或重寫。如下:

class ChildModel(ParentModel):
    # ...

    class Meta:
        # 移除父類對(duì)子類的排序影響
        ordering = []

多表繼承和反向關(guān)聯(lián)

因?yàn)槎啾砝^承使用了一個(gè)隱含的OneToOneField來鏈接子類與父類,所以象上例那樣,你可以從父類訪問子類。但是這個(gè)OnetoOneField字段默認(rèn)的related_name值與ForeignKeyManyToManyField默認(rèn)的反向名稱相同。如果你與父類或另一個(gè)子類做多對(duì)一或是多對(duì)多關(guān)系,你就必須在每個(gè)多對(duì)一和多對(duì)多字段上強(qiáng)制指定related_name。如果你沒這么做,Django就會(huì)在你運(yùn)行或驗(yàn)證(validation)時(shí)拋出異常。

仍以上面Place類為例,我們創(chuàng)建一個(gè)帶有ManyToManyField字段的子類:

class Supplier(Place):
    customers = models.ManyToManyField(Place)

這會(huì)產(chǎn)生下面的錯(cuò)誤:

Reverse query name for 'Supplier.customers' clashes with reverse query
name for 'Supplier.place_ptr'.
HINT: Add or change a related_name argument to the definition for
'Supplier.customers' or 'Supplier.place_ptr'.

解決方法是:向customers字段中添加related_name參數(shù).

customers = models.ManyToManyField(Place, related_name='provider')。

3.3.3 代理模型

使用多表繼承時(shí),父類的每個(gè)子類都會(huì)創(chuàng)建一張新數(shù)據(jù)表,通常情況下,這是我們想要的操作,因?yàn)樽宇愋枰粋€(gè)空間來存儲(chǔ)不包含在父類中的數(shù)據(jù)。但有時(shí),你可能只想更改模型在Python層面的行為,比如更改默認(rèn)的manager管理器,或者添加一個(gè)新方法。

代理模型就是為此而生的。你可以創(chuàng)建、刪除、更新代理模型的實(shí)例,并且所有的數(shù)據(jù)都可以像使用原始模型(非代理類模型)一樣被保存。不同之處在于你可以在代理模型中改變默認(rèn)的排序方式和默認(rèn)的manager管理器等等,而不會(huì)對(duì)原始模型產(chǎn)生影響。

聲明一個(gè)代理模型只需要將Meta中proxy的值設(shè)為True。

例如你想給Person模型添加一個(gè)方法。你可以這樣做:

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

class MyPerson(Person):
    class Meta:
        proxy = True

    def do_something(self):
        # ...
        pass

MyPerson類將操作和Person類同一張數(shù)據(jù)庫表。并且任何新的Person實(shí)例都可以通過MyPerson類進(jìn)行訪問,反之亦然。

>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>

下面的例子通過代理進(jìn)行排序,但父類卻不排序:

class OrderedPerson(Person):
    class Meta:
        # 現(xiàn)在,普通的Person查詢是無序的,而OrderedPerson查詢會(huì)按照`last_name`排序。
        ordering = ["last_name"]
        proxy = True

一些約束:

  • 代理模型必須繼承自一個(gè)非抽象的基類,并且不能同時(shí)繼承多個(gè)非抽象基類;
  • 代理模型可以同時(shí)繼承任意多個(gè)抽象基類,前提是這些抽象基類沒有定義任何模型字段。
  • 代理模型可以同時(shí)繼承多個(gè)別的代理模型,前提是這些代理模型繼承同一個(gè)非抽象基類。(早期Django版本不支持這一條)

代理模型的管理器

如不指定,則繼承父類的管理器。如果你自己定義了管理器,那它就會(huì)成為默認(rèn)管理器,但是父類的管理器依然有效。如下例子:

from django.db import models

class NewManager(models.Manager):
    # ...
    pass

class MyPerson(Person):
    objects = NewManager()

    class Meta:
        proxy = True

如果你想要向代理中添加新的管理器,而不是替換現(xiàn)有的默認(rèn)管理器,你可以創(chuàng)建一個(gè)含有新的管理器的基類,并在繼承時(shí)把他放在主基類的后面:

# Create an abstract class for the new manager.
class ExtraManagers(models.Model):
    secondary = NewManager()

    class Meta:
        abstract = True

class MyPerson(Person, ExtraManagers):
    class Meta:
        proxy = True

3.3.4 多重繼承

注意,多重繼承和多表繼承是兩碼事,兩個(gè)概念。

Django的模型體系支持多重繼承,就像Python一樣。如果多個(gè)父類都含有Meta類,則只有第一個(gè)父類的會(huì)被使用,剩下的會(huì)忽略掉。

一般情況,能不要多重繼承就不要,盡量讓繼承關(guān)系簡(jiǎn)單和直接,避免不必要的混亂和復(fù)雜。

請(qǐng)注意,繼承同時(shí)含有相同id主鍵字段的類將拋出異常。為了解決這個(gè)問題,你可以在基類模型中顯式的使用AutoField字段。如下例所示:

class Article(models.Model):
    article_id = models.AutoField(primary_key=True)
    ...

class Book(models.Model):
    book_id = models.AutoField(primary_key=True)
    ...

class BookReview(Book, Article):
    pass

或者使用一個(gè)共同的祖先來持有AutoField字段,并在直接的父類里通過一個(gè)OneToOne字段保持與祖先的關(guān)系,如下所示:

class Piece(models.Model):
    pass

class Article(Piece):
    article_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
    ...

class Book(Piece):
    book_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
    ...

class BookReview(Book, Article):
    pass

警告

Python語言層面,子類可以擁有和父類相同的屬性名,這樣會(huì)造成覆蓋現(xiàn)象。但是對(duì)于Django,如果繼承的是一個(gè)非抽象基類,那么子類與父類之間不可以有相同的字段名!

比如下面是不行的!

class A(models.Model):
    name = models.CharField(max_length=30)

class B(A):
    name = models.CharField(max_length=30)

如果你執(zhí)行python manage.py makemigrations會(huì)彈出下面的錯(cuò)誤:

django.core.exceptions.FieldError: Local field 'name' in class 'B' clashes with field of the same name from base class 'A'.

但是!如果父類是個(gè)抽象基類就沒有問題了,如下:

class A(models.Model):
    name = models.CharField(max_length=30)

    class Meta:
        abstract = True

class B(A):
    name = models.CharField(max_length=30)

3.4 外鍵與表關(guān)系

3.4.1 外鍵

大部分關(guān)系型數(shù)據(jù)庫都支持外鍵,以MySQL為例,在MySQL中,表有兩種引擎,一種是InnoDB,另外一種是myisam。如果使用的是InnoDB引擎,是支持外鍵約束的。外鍵的存在使得ORM框架在處理表關(guān)系的時(shí)候異常的強(qiáng)大。

3.4.2 模型類中外鍵的定義

外鍵的類定義格式為class ForeignKey(to,on_delete,**options)。第一個(gè)參數(shù)是引用的是哪個(gè)模型,第二個(gè)參數(shù)是在使用外鍵引用的模型數(shù)據(jù)被刪除了,這個(gè)字段該如何處理,有CASCADESET_NULL等處理方式。

實(shí)際案例:有一個(gè)User和一個(gè)Article兩個(gè)模型。一個(gè)User可以發(fā)表多篇文章,一個(gè)Article只能有一個(gè)Author,并且通過外鍵進(jìn)行引用。那么相關(guān)的示例代碼如下:

class User(models.Model):
    username = models.CharField(max_length=20)
    password = models.CharField(max_length=100)


class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()

    author = models.ForeignKey("User",on_delete=models.CASCADE)

以上使用ForeignKey來定義模型之間的關(guān)系。即在article的實(shí)例中可以通過author屬性來操作對(duì)應(yīng)的User模型。這樣使用起來非常的方便。

使用了ForeignKey后,DjangoArticle表添加了一個(gè)屬性名_id的字段(比如author的字段名稱是author_id),這個(gè)字段是一個(gè)外鍵,記錄著對(duì)應(yīng)的作者(User模型)的主鍵。

如果想要引用另外一個(gè)app的模型,那么應(yīng)該在傳遞to參數(shù)的時(shí)候,使用app.model_name進(jìn)行指定。以上例為例,如果UserArticle不是在同一個(gè)app中,那么在引用的時(shí)候的示例代碼如下:

# User模型在user這個(gè)app中
class User(models.Model):
    username = models.CharField(max_length=20)
    password = models.CharField(max_length=100)

# Article模型在article這個(gè)app中
class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()

    author = models.ForeignKey("user.User",on_delete=models.CASCADE)

如果模型的外鍵引用的是本身自己這個(gè)模型,那么to參數(shù)可以為'self',或者是這個(gè)模型的名字。在論壇開發(fā)中,一般評(píng)論都可以進(jìn)行二級(jí)評(píng)論,即可以針對(duì)另外一個(gè)評(píng)論進(jìn)行評(píng)論,那么在定義模型的時(shí)候就需要使用外鍵來引用自身。示例代碼如下:

class Comment(models.Model):
    content = models.TextField()
    origin_comment = models.ForeignKey('self',on_delete=models.CASCADE,null=True)
    # 或者
    # origin_comment = models.ForeignKey('Comment',on_delete=models.CASCADE,null=True)

limit_choices_to參數(shù)用于限制外鍵所能關(guān)聯(lián)的對(duì)象,只能用于DjangoModelFormDjango的表單模塊)和admin后臺(tái),對(duì)其它場(chǎng)合無限制功能。其值可以是一個(gè)字典、Q對(duì)象或者一個(gè)返回字典或Q對(duì)象的函數(shù)調(diào)用,如下例所示:

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

這樣定義,則ModelFormstaff_member字段列表中,只會(huì)出現(xiàn)那些is_staff=TrueUsers對(duì)象,這一功能對(duì)于admin后臺(tái)非常有用。

3.4.3 外鍵刪除操作

如果一個(gè)模型使用了外鍵。那么在對(duì)方那個(gè)模型被刪掉后,該進(jìn)行什么樣的操作??梢酝ㄟ^on_delete來指定??梢灾付ǖ念愋腿缦拢?/p>

  1. CASCADE:級(jí)聯(lián)操作。如果外鍵對(duì)應(yīng)的那條數(shù)據(jù)被刪除了,那么這條數(shù)據(jù)也會(huì)被刪除。
  2. PROTECT:受保護(hù)。即只要這條數(shù)據(jù)引用了外鍵的那條數(shù)據(jù),那么就不能刪除外鍵的那條數(shù)據(jù)。
  3. SET_NULL:設(shè)置為空。如果外鍵的那條數(shù)據(jù)被刪除了,那么在本條數(shù)據(jù)上就將這個(gè)字段設(shè)置為空。如果設(shè)置這個(gè)選項(xiàng),前提是要指定這個(gè)字段可以為空。
  4. SET_DEFAULT:設(shè)置默認(rèn)值。如果外鍵的那條數(shù)據(jù)被刪除了,那么本條數(shù)據(jù)上就將這個(gè)字段設(shè)置為默認(rèn)值。如果設(shè)置這個(gè)選項(xiàng),前提是要指定這個(gè)字段一個(gè)默認(rèn)值。
  5. SET():如果外鍵的那條數(shù)據(jù)被刪除了。那么將會(huì)獲取SET函數(shù)中的值來作為這個(gè)外鍵的值。SET函數(shù)可以接收一個(gè)可以調(diào)用的對(duì)象(比如函數(shù)或者方法),如果是可以調(diào)用的對(duì)象,那么會(huì)將這個(gè)對(duì)象調(diào)用后的結(jié)果作為值返回回去。
  6. DO_NOTHING:不采取任何行為。一切全看數(shù)據(jù)庫級(jí)別的約束。

以上這些選項(xiàng)只是Django級(jí)別的,數(shù)據(jù)級(jí)別依舊是RESTRICT!

3.4.5 一對(duì)多

一對(duì)多或者多對(duì)一,都是通過ForeignKey來實(shí)現(xiàn)的, 外鍵需要兩個(gè)位置參數(shù),一個(gè)是關(guān)聯(lián)的模型,另一個(gè)是on_delete選項(xiàng)。 外鍵要定義在的一方! 舉個(gè)例子比如文章和作者之間的關(guān)系。一個(gè)文章只能由一個(gè)作者編寫,但是一個(gè)作者可以寫多篇文章。文章和作者之間的關(guān)系就是典型的多對(duì)一的關(guān)系 。

 class User(models.Model):
     username = models.CharField(max_length=20)
     password = models.CharField(max_length=100)

 class Article(models.Model):
     title = models.CharField(max_length=100)
     content = models.TextField()
     author = models.ForeignKey("User",on_delete=models.CASCADE)

那么以后在給Article對(duì)象指定author,就可以使用以下代碼來完成:

article = Article(title='abc',content='123')
author = User(username='zhiliao',password='111111')
# 要先保存到數(shù)據(jù)庫中
author.save()
article.author = author
article.save()

并且以后如果想要獲取某個(gè)用戶下所有的文章,可以通過article_set來實(shí)現(xiàn)。示例代碼如下:

user = User.objects.first()
# 獲取第一個(gè)用戶寫的所有文章
articles = user.article_set.all()
for article in articles:
    print(article)

3.4.6 一對(duì)一

Django為一對(duì)一提供了一個(gè)專門的Field叫做OneToOneField來實(shí)現(xiàn)一對(duì)一操作。 舉個(gè)例子 比如一個(gè)用戶表和一個(gè)用戶信息表。在實(shí)際網(wǎng)站中,可能需要保存用戶的許多信息,但是有些信息是不經(jīng)常用的。如果把所有信息都存放到一張表中可能會(huì)影響查詢效率,因此可以把用戶的一些不常用的信息存放到另外一張表中我們叫做UserExtension。但是用戶表User和用戶信息表UserExtension就是典型的一對(duì)一了。

 class User(models.Model):
     username = models.CharField(max_length=20)
     password = models.CharField(max_length=100)

 class UserExtension(models.Model):  
     birthday = models.DateTimeField(null=True)  
     school = models.CharField(blank=True,max_length=50)  
     user = models.OneToOneField("User", on_delete=models.CASCADE)

UserExtension模型上增加了一個(gè)一對(duì)一的關(guān)系映射。其實(shí)底層是在UserExtension這個(gè)表上增加了一個(gè)user_id,來和user表進(jìn)行關(guān)聯(lián),并且這個(gè)外鍵數(shù)據(jù)在表中必須是唯一的,來保證一對(duì)一。

有時(shí)候,我們關(guān)聯(lián)的模型并不在當(dāng)前模型的文件內(nèi),沒關(guān)系,就像我們導(dǎo)入第三方庫一樣的從別的模塊內(nèi)導(dǎo)入進(jìn)來就好,如下例所示:

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,
    )

3.4.6 多對(duì)多

Django為這種多對(duì)多的實(shí)現(xiàn)提供了專門的Field。叫做ManyToManyField 。 舉個(gè)例子比如文章和標(biāo)簽的關(guān)系。一篇文章可以有多個(gè)標(biāo)簽,一個(gè)標(biāo)簽可以被多個(gè)文章所引用。因此標(biāo)簽和文章的關(guān)系是典型的多對(duì)多的關(guān)系。

 class Article(models.Model):
     title = models.CharField(max_length=100)
     content = models.TextField()
     tags = models.ManyToManyField("Tag",related_name="articles")

 class Tag(models.Model):
     name = models.CharField(max_length=50)

在數(shù)據(jù)庫層面,實(shí)際上Django是為這種多對(duì)多的關(guān)系建立了一個(gè)中間表。這個(gè)中間表分別定義了兩個(gè)外鍵,引用到articletag兩張表的主鍵。

ManyToManyField多對(duì)多字段不支持Django內(nèi)置的validators驗(yàn)證功能。

null參數(shù)對(duì)ManyToManyField多對(duì)多字段無效!設(shè)置null=True毫無意義

3.4.7 related_name和related_query_name

related_name:

還是以UserArticle為例來進(jìn)行說明。如果一個(gè)article想要訪問對(duì)應(yīng)的作者,那么可以通過author來進(jìn)行訪問。但是如果有一個(gè)user對(duì)象,想要通過這個(gè)user對(duì)象獲取所有的文章,該如何做呢?這時(shí)候可以通過user.article_set來訪問,這個(gè)名字的規(guī)律是模型名字小寫_set。示例代碼如下:

user = User.objects.get(name='張三')
user.article_set.all()

如果不想使用模型名字小寫_set的方式,想要使用其他的名字,那么可以在定義模型的時(shí)候指定related_name。示例代碼如下:

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    # 傳遞related_name參數(shù),以后在方向引用的時(shí)候使用articles進(jìn)行訪問
    author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='articles')

以后在方向引用的時(shí)候。使用articles可以訪問到這個(gè)作者的文章模型。示例代碼如下:

user = User.objects.get(name='張三')
user.articles.all()

如果不想使用反向引用,那么可以指定related_name='+'。示例代碼如下:

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    # 傳遞related_name參數(shù),以后在方向引用的時(shí)候使用articles進(jìn)行訪問
    author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='+')

以后將不能通過user.article_set來訪問文章模型了。

related_query_name:

在查找數(shù)據(jù)的時(shí)候,可以使用filter進(jìn)行過濾。使用filter過濾的時(shí)候,不僅僅可以指定本模型上的某個(gè)屬性要滿足什么條件,還可以指定相關(guān)聯(lián)的模型滿足什么屬性。比如現(xiàn)在想要獲取寫過標(biāo)題為abc的所有用戶,那么可以這樣寫:

users = User.objects.filter(article__title='abc')

如果你設(shè)置了related_namearticles,因?yàn)榉崔D(zhuǎn)的過濾器的名字將使用related_name的名字,那么上例代碼將改成如下:

users = User.objects.filter(articles__title='abc')

可以通過related_query_name將查詢的反轉(zhuǎn)名字修改成其他的名字。比如article。示例代碼如下:

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    # 傳遞related_name參數(shù),以后在方向引用的時(shí)候使用articles進(jìn)行訪問
    author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='articles',related_query_name='article')

那么在做反向過濾查找的時(shí)候就可以使用以下代碼:

users = User.objects.filter(article__title='abc')

related_query_name的默認(rèn)值為 related_namedefault_related_name

3.5 模型Meta選項(xiàng)

模型的元數(shù)據(jù),指的是“除了字段外的所有內(nèi)容”,例如排序方式、數(shù)據(jù)庫表名、人類可讀的單數(shù)或者復(fù)數(shù)名等等。所有的這些都是非必須的,甚至元數(shù)據(jù)本身對(duì)模型也是非必須的。但是,我要說但是,有些元數(shù)據(jù)選項(xiàng)能給予你極大的幫助,在實(shí)際使用中具有重要的作用,是實(shí)際應(yīng)用的‘必須’。

想在模型中增加元數(shù)據(jù),方法很簡(jiǎn)單,在模型類中添加一個(gè)子類,名字是固定的Meta,然后在這個(gè)Meta類下面增加各種元數(shù)據(jù)選項(xiàng)或者說設(shè)置項(xiàng)。參考下面的例子:

from django.db import models

class Ox(models.Model):
    horn_length = models.IntegerField()

    class Meta:         # 注意,是模型的子類,要縮進(jìn)!
        ordering = ["horn_length"]
        verbose_name_plural = "oxen"

上面的例子中,我們?yōu)槟P?code>Ox增加了兩個(gè)元數(shù)據(jù)orderingverbose_name_plural,分別表示排序和復(fù)數(shù)名。

強(qiáng)調(diào):每個(gè)模型都可以有自己的元數(shù)據(jù)類,每個(gè)元數(shù)據(jù)類也只對(duì)自己所在模型起作用。


3.5.1 常可用的元數(shù)據(jù)選項(xiàng)

  1. abstract:如果abstract=True,那么模型會(huì)被認(rèn)為是一個(gè)抽象模型。抽象模型本身不實(shí)際生成數(shù)據(jù)庫表,而是作為其它模型的父類,被繼承使用。具體內(nèi)容可以參考Django模型的繼承。

  2. app_label:如果定義了模型的app沒有在INSTALLED_APPS中注冊(cè),則必須通過此元選項(xiàng)聲明它屬于哪個(gè)app,例如:

app_label = 'myapp'
  1. base_manager_name:自定義模型的_base_manager管理器的名字。模型管理器是Django為模型提供的API所在。

  2. db_table:指定在數(shù)據(jù)庫中,當(dāng)前模型生成的數(shù)據(jù)表的表名。比如:

db_table = 'my_freinds'

? 友情建議:使用MySQL數(shù)據(jù)庫時(shí),db_table用小寫英文。

  1. default_manager_name:自定義模型的_default_manager管理器的名字。

  2. default_related_name:默認(rèn)情況下,從一個(gè)模型反向關(guān)聯(lián)設(shè)置有關(guān)系字段的源模型,我們使用_set,也就是源模型的名字+下劃線+set

    這個(gè)元數(shù)據(jù)選項(xiàng)可以讓你自定義反向關(guān)系名,同時(shí)也影響反向查詢關(guān)系名!看下面的例子:

from django.db import models

class Foo(models.Model):
    pass

class Bar(models.Model):
    foo = models.ForeignKey(Foo)

    class Meta:
        default_related_name = 'bars'   # 關(guān)鍵在這里

具體的使用差別如下:

>>> bar = Bar.objects.get(pk=1)
>>> # 不能再使用"bar"作為反向查詢的關(guān)鍵字了。
>>> Foo.objects.get(bar=bar)
>>> # 而要使用你自己定義的"bars"了。
>>> Foo.objects.get(bars=bar)
  1. ordering:最常用的元數(shù)據(jù)之一了!

    用于指定該模型生成的所有對(duì)象的排序方式,接收一個(gè)字段名組成的元組或列表。默認(rèn)按升序排列,如果在字段名前加上字符“-”則表示按降序排列,如果使用字符問號(hào)“?”表示隨機(jī)排列。請(qǐng)看下面的例子:

ordering = ['pub_date']             # 表示按'pub_date'字段進(jìn)行升序排列
ordering = ['-pub_date']            # 表示按'pub_date'字段進(jìn)行降序排列
ordering = ['-pub_date', 'author']  # 表示先按'pub_date'字段進(jìn)行降序排列,再按`author`字段進(jìn)行升序排列。
  1. permissions:該元數(shù)據(jù)用于當(dāng)創(chuàng)建對(duì)象時(shí)增加額外的權(quán)限。它接收一個(gè)所有元素都是二元元組的列表或元組,每個(gè)元素都是(權(quán)限代碼, 直觀的權(quán)限名稱)的格式。比如下面的例子:
permissions = (("can_deliver_pizzas", "可以送披薩"),)
  1. default_permissionsDjango默認(rèn)給所有的模型設(shè)置(add, change, delete)的權(quán)限,也就是增刪改。你可以自定義這個(gè)選項(xiàng),比如設(shè)置為一個(gè)空列表,表示你不需要默認(rèn)的權(quán)限,但是這一操作必須在執(zhí)行migrate命令之前。

  2. proxy:如果設(shè)置了proxy = True,表示使用代理模式的模型繼承方式。

  3. indexes:接收一個(gè)應(yīng)用在當(dāng)前模型上的索引列表,如下例所示:

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'),
        ]

12 . verbose_name:最常用的元數(shù)據(jù)之一!用于設(shè)置模型對(duì)象的直觀、人類可讀的名稱??梢杂弥形?。例如:

verbose_name = "story"
verbose_name = "披薩"

? 如果你不指定它,那么Django會(huì)使用小寫的模型名作為默認(rèn)值。


至于Meta其他的元數(shù)據(jù)選項(xiàng)可以查看官方文檔

3.6 Mode類的常用的方法與屬性

3.6.1 Mode類的常用屬性

objects屬性,Model.objects實(shí)際上是一個(gè) Manager對(duì)象實(shí)例, Django規(guī)定在模型類中至少有一個(gè)默認(rèn)值 Manager,如果你不添加自己的 Managerdjango將添加一個(gè)屬性objects包含默認(rèn)值Manager實(shí)例。如果你添加你自己的Manager實(shí)例屬性,不顯示默認(rèn)屬性。 Manager對(duì)象實(shí)例主要用于模型類的查詢操作,請(qǐng)考慮以下示例:

from django.db import models

class Person(models.Model):
    # 添加一個(gè)其他名字的管理器
    people = models.Manager()

3.6.2 Mode類的常用預(yù)定義方法

save(): 將對(duì)象保存回?cái)?shù)據(jù)庫

article = Article(title='abc',content='123')
article.save()

delete(): 這只會(huì)刪除數(shù)據(jù)庫中的對(duì)象;python實(shí)例仍然存在,并且其字段中仍有數(shù)據(jù)。此方法返回已刪除的對(duì)象數(shù)和一個(gè)字典,其中包含每個(gè)對(duì)象類型的刪除數(shù)。

book = Book.objects.get(name='三國演義')
book.delete()

3.7 數(shù)據(jù)庫模型映射到數(shù)據(jù)庫

將定義好的數(shù)據(jù)庫模型類映射到數(shù)據(jù)庫中分兩步:

(1). 通過Django自帶的命令:python manage.py makemigrations生成遷移腳本

(2). 再通過Django自帶命令python manage.py migrate執(zhí)行遷移

注意:

  1. 在遷移前沒有添加到settings INSTALL_APP配置項(xiàng)中的應(yīng)用中的模型類是不會(huì)映射到數(shù)據(jù)庫中
  2. 在第一次遷移時(shí),Django會(huì)生成一些自帶的表,這些表對(duì)應(yīng)的模型是Django自動(dòng)創(chuàng)建的,創(chuàng)建項(xiàng)目時(shí)Django自動(dòng)把一些默認(rèn)的應(yīng)用放到了settings.py文件中的 INSTALL_APP配置項(xiàng)中,這些應(yīng)用大多是sessionadmin后臺(tái)、auth權(quán)限認(rèn)證相關(guān)的應(yīng)用,這些應(yīng)用都有自己的模型類,所有遷移時(shí)會(huì)生成一些表

3.7.1 遷移命令

  1. makemigrations:將模型生成遷移腳本。這個(gè)命令有以下幾個(gè)常用選項(xiàng):
    • app_label:后面可以跟一個(gè)或者多個(gè)app,那么就只會(huì)針對(duì)這幾個(gè)app生成遷移腳本。如果沒有任何的app_label,那么會(huì)檢查INSTALLED_APPS中所有的app下的模型,針對(duì)每一個(gè)app都生成響應(yīng)的遷移腳本。
    • --name:給這個(gè)遷移腳本指定一個(gè)名字。
    • --empty:生成一個(gè)空的遷移腳本。如果你想寫自己的遷移腳本,可以使用這個(gè)命令來實(shí)現(xiàn)一個(gè)空的文件,然后自己再在文件中寫遷移腳本。
  2. migrate:將新生成的遷移腳本。映射到數(shù)據(jù)庫中。創(chuàng)建新的表或者修改表的結(jié)構(gòu)。以下一些常用的選項(xiàng):
    • app_label:將某個(gè)app下的遷移腳本映射到數(shù)據(jù)庫中。如果沒有指定,那么會(huì)將所有在INSTALLED_APPS中的app下的模型都映射到數(shù)據(jù)庫中。
    • app_label migrationname:將某個(gè)app下指定名字的migration文件映射到數(shù)據(jù)庫中。
    • --fake:可以將指定的遷移腳本名字添加到數(shù)據(jù)庫中。但是并不會(huì)把遷移腳本轉(zhuǎn)換為SQL語句,修改數(shù)據(jù)庫中的表。
    • --fake-initial:將第一次生成的遷移文件版本號(hào)記錄在數(shù)據(jù)庫中。但并不會(huì)真正的執(zhí)行遷移腳本。
  3. showmigrations:查看某個(gè)app下的遷移文件。如果后面沒有app,那么將查看INSTALLED_APPS中所有的遷移文件。
  4. sqlmigrate:查看某個(gè)遷移文件在映射到數(shù)據(jù)庫中的時(shí)候,轉(zhuǎn)換的SQL語句。

3.7.2 遷移常見問題

migrations中的遷移版本和數(shù)據(jù)庫中的遷移版本對(duì)不上:

  1. 找到哪里不一致,然后使用python manage.py --fake [版本名字],將這個(gè)版本標(biāo)記為已經(jīng)映射。
  2. 刪除指定appmigrations和數(shù)據(jù)庫表django_migrations中和這個(gè)app相關(guān)的版本號(hào),然后將模型中的字段和數(shù)據(jù)庫中的字段保持一致,再使用命令python manage.py makemigrations重新生成一個(gè)初始化的遷移腳本,之后再使用命令python manage.py makemigrations --fake-initial來將這個(gè)初始化的遷移腳本標(biāo)記為已經(jīng)映射。以后再修改就沒有問題了。

3.7.3 根據(jù)已有的表自動(dòng)生成模型

在實(shí)際開發(fā)中,有些時(shí)候可能數(shù)據(jù)庫已經(jīng)存在了。如果我們用Django來開發(fā)一個(gè)網(wǎng)站,讀取的是之前已經(jīng)存在的數(shù)據(jù)庫中的數(shù)據(jù)。那么該如何將模型與數(shù)據(jù)庫中的表映射呢?根據(jù)舊的數(shù)據(jù)庫生成對(duì)應(yīng)的ORM模型,需要以下幾個(gè)步驟:

  1. Django給我們提供了一個(gè)inspectdb的命令,可以非常方便的將已經(jīng)存在的表,自動(dòng)的生成模型。想要使用inspectdb自動(dòng)將表生成模型。首先需要在settings.py中配置好數(shù)據(jù)庫相關(guān)信息。不然就找不到數(shù)據(jù)庫。示例代碼如下:

     DATABASES = {
         'default': {
             'ENGINE': 'django.db.backends.mysql',
             'NAME': "migrations_demo",
             'HOST': '127.0.0.1',
             'PORT': '3306',
             'USER': 'root',
             'PASSWORD': 'root'
         }
     }
    

    比如有以下表:

    • article表:


      article表.png
    • tag表:


      tag表.png
    • article_tag表:


      article_tag表.png
    • front_user表:

      frontuser.png

      那么通過python manage.py inspectdb,就會(huì)將表轉(zhuǎn)換為模型后的代碼,顯示在終端:

      from django.db import models
      
      class ArticleArticle(models.Model):
        title = models.CharField(max_length=100)
        content = models.TextField(blank=True, null=True)
        create_time = models.DateTimeField(blank=True, null=True)
        author = models.ForeignKey('FrontUserFrontuser', models.DO_NOTHING, blank=True, null=True)
      
        class Meta:
            managed = False
            db_table = 'article_article'
      
      class ArticleArticleTags(models.Model):
        article = models.ForeignKey(ArticleArticle, models.DO_NOTHING)
        tag = models.ForeignKey('ArticleTag', models.DO_NOTHING)
      
        class Meta:
            managed = False
            db_table = 'article_article_tags'
            unique_together = (('article', 'tag'),)
      
      class ArticleTag(models.Model):
        name = models.CharField(max_length=100)
      
        class Meta:
            managed = False
            db_table = 'article_tag'
      
      class FrontUserFrontuser(models.Model):
        username = models.CharField(max_length=100)
        telephone = models.CharField(max_length=11)
      
        class Meta:
            managed = False
            db_table = 'front_user_frontuser'
      

      以上代碼只是顯示在終端。如果想要保存到文件中。那么可以使用>重定向輸出到指定的文件。比如讓他輸出到models.py文件中。示例命令如下:

      python manage.py inspectdb > models.py
      

      以上的命令,只能在終端執(zhí)行,不能在pycharm->Tools->Run manage.py Task...中使用。

      如果只是想要轉(zhuǎn)換一個(gè)表為模型。那么可以指定表的名字。示例命令如下:

      python manage.py inspectdb article_article > models.py
      
  2. 修正模型:新生成的ORM模型有些地方可能不太適合使用。比如模型的名字,表之間的關(guān)系等等。那么以下選項(xiàng)還需要重新配置一下:

    • 模型名:自動(dòng)生成的模型,是根據(jù)表的名字生成的,可能不是你想要的。這時(shí)候模型的名字你可以改成任何你想要的。

    • 模型所屬app:根據(jù)自己的需要,將相應(yīng)的模型放在對(duì)應(yīng)的app中。放在同一個(gè)app中也是沒有任何問題的。只是不方便管理。

    • 模型外鍵引用:將所有使用ForeignKey的地方,模型引用都改成字符串。這樣不會(huì)產(chǎn)生模型順序的問題。另外,如果引用的模型已經(jīng)移動(dòng)到其他的app中了,那么還要加上這個(gè)app的前綴。

    • Django管理模型:將Meta下的managed=False刪掉,如果保留這個(gè),那么以后這個(gè)模型有任何的修改,使用migrate都不會(huì)映射到數(shù)據(jù)庫中。

    • 當(dāng)有多對(duì)多的時(shí)候,應(yīng)該也要修正模型。將中間表注視了,然后使用ManyToManyField來實(shí)現(xiàn)多對(duì)多。并且,使用ManyToManyField生成的中間表的名字可能和數(shù)據(jù)庫中那個(gè)中間表的名字不一致,這時(shí)候肯定就不能正常連接了。那么可以通過db_table來指定中間表的名字。示例代碼如下:

      class Article(models.Model):
       title = models.CharField(max_length=100, blank=True, null=True)
       content = models.TextField(blank=True, null=True)
       author = models.ForeignKey('front.User', models.SET_NULL, blank=True, null=True)
       # 使用ManyToManyField模型到表,生成的中間表的規(guī)則是:article_tags
       # 但現(xiàn)在已經(jīng)存在的表的名字叫做:article_tag
       # 可以使用db_table,指定中間表的名字
       tags = models.ManyToManyField("Tag",db_table='article_tag')
      
       class Meta:
           db_table = 'article'
      
    • 表名:切記不要修改表的名字。不然映射到數(shù)據(jù)庫中,會(huì)發(fā)生找不到對(duì)應(yīng)表的錯(cuò)誤。

  3. 執(zhí)行命令python manage.py makemigrations生成初始化的遷移腳本。方便后面通過ORM來管理表。這時(shí)候還需要執(zhí)行命令python manage.py migrate --fake-initial,因?yàn)槿绻皇褂?code>--fake-initial,那么會(huì)將遷移腳本會(huì)映射到數(shù)據(jù)庫中。這時(shí)候遷移腳本會(huì)新創(chuàng)建表,而這個(gè)表之前是已經(jīng)存在了的,所以肯定會(huì)報(bào)錯(cuò)。此時(shí)我們只要將這個(gè)0001-initial的狀態(tài)修改為已經(jīng)映射,而不真正執(zhí)行映射,下次再migrate的時(shí)候,就會(huì)忽略他。

  4. Django的核心表映射到數(shù)據(jù)庫中:Django中還有一些核心的表也是需要?jiǎng)?chuàng)建的。不然有些功能是用不了的。比如auth相關(guān)表。如果這個(gè)數(shù)據(jù)庫之前就是使用Django開發(fā)的,那么這些表就已經(jīng)存在了??梢圆挥霉芰恕H绻斑@個(gè)數(shù)據(jù)庫不是使用Django開發(fā)的,那么應(yīng)該使用migrate命令將Django中的核心模型映射到數(shù)據(jù)庫中。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容