Django模型定義

Django 模型定義

Django 模型是使用 Python 代碼對數(shù)據(jù)庫中數(shù)據(jù)的描述,是數(shù)據(jù)的結(jié)構(gòu)年扩,包含數(shù)據(jù)的字段和操作方法。

用 Python 代碼來定義模型

  • 每個模型都是 django.db.models.Model 的子類
  • Django 自動生成訪問數(shù)據(jù)庫的 API
  • 一個模型對應(yīng)數(shù)據(jù)庫中的一張表(多對多關(guān)系時兵志,會生成一張額外的表來管理兩者之間的關(guān)系)
  • 模型的屬性對應(yīng)的數(shù)據(jù)庫表中的一列
  • 屬性的名稱對應(yīng)著列的名稱
  • 字段的類型對應(yīng)著數(shù)據(jù)庫中列的類型
  • 數(shù)據(jù)庫中生成的表汤功,默認(rèn)格式是 '應(yīng)用名稱 + 下劃線 + 小寫類名'
  • 如果沒有指定主鍵,Django 會自動為定義一個自增量整數(shù)主鍵字段弃理,名字叫 ID

示列代碼:

from django.db import models

class BlogArticles(models.Model):
    title = models.CharField(max_length=64)

# 在數(shù)據(jù)庫生成 blog_blogarticles 表

字段

字段在 Python 中表現(xiàn)為類屬性 , 是模型中必不可少的溃论。

字段名稱的限制

  • 字段名稱不能是 Python 保留字
  • 由于 Django 中雙下劃線是查詢語法,所以字段名稱不能包含多個連續(xù)的下劃線

字段類型的作用

模型中的字段都是 Field 類的實(shí)例痘昌,字段有以下作用:

  • 決定數(shù)據(jù)庫列的類型(如 INTEGER 钥勋、 VARCHAR )
  • 確定在 Django 的表單和管理后臺中使用什么組件(widget)(如 <input type="text" />)。
  • 在 Admin 后臺和自動生成的表單中做最基本的數(shù)據(jù)驗(yàn)證
  • 字段都接受選項(xiàng)參數(shù)

模型字段類表

字段類 默認(rèn)小組件 說明
AutoField N/A 根據(jù) ID 自動遞增的 IntegerField
BigIntegerField NumberInput 64 位整數(shù)控汉,與 IntegerField 很像笔诵,但取值范圍是 -9223372036854775808 到 9223372036854775807 。
BinaryField N/A 存儲原始二進(jìn)制數(shù)據(jù)的字段姑子。只支持 bytes 類型乎婿。注意,這個字段的功能有限街佑。
BooleanField CheckboxInput 真假值字段谢翎。如果想接受 null 值,使用 NullBooleanField 沐旨。
CharField TextInput 字符串字段森逮,針對長度較小的字符串。大量文本應(yīng)該使用 TextField 磁携。有個額外的必須參數(shù):max_length 褒侧,即字段的最大長度(字符個數(shù))。
DateField DateInput 日期谊迄,在 Python 中使用 datetime.date 實(shí)例表示闷供。有兩個額外的可選參數(shù): auto_now ,每次保存對象時自動設(shè)為當(dāng)前日期 auto_now_add 统诺,創(chuàng)建對象時自動設(shè)為當(dāng)前日期歪脏。
DateTimeField DateTimeInput 日期和時間,在 Python 中使用 datetime.datetime 實(shí)例表示粮呢。與 DateField 具有相同的額外參數(shù)婿失。
DecimalField TextInput 固定精度的小數(shù)钞艇,在 Python 中使用 Decimal 實(shí)例表示。有兩個必須的參數(shù): max_digits 和 decimal_places 豪硅。
DurationField TextInput 存儲時間跨度哩照,在 Python 中使用 timedelta 表示。
EmailField TextInput 一種 CharField 舟误,使用 EmailValidator 驗(yàn)證輸入葡秒。max_length 的默認(rèn)值為 254 。
FileField ClearableFileInput 文件上傳字段嵌溢。詳情見下面眯牧。
FilePathField Select 一種 CharField ,限定只能在文件系統(tǒng)中的特定目錄里選擇文件赖草。
FloatField NumberInput 浮點(diǎn)數(shù)学少,在 Python 中使用 float 實(shí)例表示。注意秧骑, field.localize 的值為 False 時版确,默認(rèn)的小組件是 TextInput 。
ImageField ClearableFileInput 所有屬性和方法都繼承自 FileField 乎折,此外驗(yàn)證上傳的對象是不是有效的圖像绒疗。增加了 height 和 width 兩個屬性。需要 Pillow 庫支持骂澄。
IntegerField NumberInput 整數(shù)吓蘑。取值范圍是 -2147483648 到 2147483647 ,在 Django 支持的所有數(shù)據(jù)庫中可放心使用坟冲。
GenericIPAddressField TextInput IPv4 或 IPv6 地址磨镶,字符串形式(如 192.0.2.30 、2a02:42fe::4 )健提。
NullBooleanField NullBooleanSelect 類似于 BooleanField 琳猫,但是 NULL 可作為其中一個選項(xiàng)。
PositiveIntegerField NumberInput 整數(shù)私痹。取值范圍是 0 到 2147483647 脐嫂,在 Django 支持的所有數(shù)據(jù)庫中可放心使用。
SlugField TextInput 別名(slug)是報業(yè)術(shù)語紊遵,是某個事物的簡短標(biāo)注雹锣,只包含字母、數(shù)字癞蚕、下劃線或連字符。
SmallIntegerField NumberInput 類似于 IntegerField 辉哥,但是對值有限制桦山。取值范圍是 -32768 到 32767 攒射,在 Django 支持的所有數(shù)據(jù)庫中可放心使用。
TextField Textarea 大段文本字段恒水。如果指定了 max_length 選項(xiàng)会放,這一限制在自動生成的表單字段中會體現(xiàn)出來。
TimeField TextInput 時間钉凌,在 Python 中使用 datetime.time 實(shí)例表示咧最。
URLField URLInput 用于輸入 URL 的 CharField ∮瘢可選 max_length 選項(xiàng)矢沿。
UUIDField TextInput 用于存儲通用唯一標(biāo)識碼。使用 Python 的 UUID 類酸纲。

文件和圖片上傳

文件上傳

  • FileField 不支持 primary_key 和 unique 選項(xiàng)捣鲸,否則會拋出 TypeError
  • FileField 實(shí)例對應(yīng)的是 varchar 列闽坡,最大長度默認(rèn)為 100 個字符〖残幔可以通過 max_length 修改
  • 默認(rèn)的 HTML 中表單小組件是 ClearableFileInput
  • 數(shù)據(jù)庫里不存儲文件,保存的是字符串(相對于 MEDIA_ROOT 的文件路徑)
  • 查看圖片或者文件在數(shù)據(jù)庫中的路徑: 對象.字段名.url

參數(shù) upload_to 用于設(shè)置上傳地址的目錄和文件名汁蝶,示列代碼:

# 在 settings.py 中配置路徑
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

# 在 models.py 文件中定義模型
from django.db import models
from django.contrib.auth.models import AbstractUser

class UserProfile(AbstractUser):
    # workgerber 上傳到了 '/media/image/2018/10' 文件夾中
    workgerber = models.FileField(upload_to="image/%Y/%m", verbose_name='生產(chǎn)gerber文件')
    image = models.ImageField(upload_to="image/%Y/%m", default='image/default.png', verbose_name='用戶頭像')

upload_to 參數(shù)也可以接收一個回調(diào)函數(shù)次泽,回調(diào)函數(shù)接受兩個參數(shù):

  1. 實(shí)列穿仪, FileField 所在模型的實(shí)例
  2. 文件名

如:

from django.db import models
from django.contrib.auth.models import User

# instance 是位置參數(shù),代指 Articles 實(shí)列本身
# 下列代碼中文件保存在 MEDIA_ROOT/uid_1/ 文件夾
# 實(shí)現(xiàn)了根據(jù)用戶 ID 來將文件分開保存
def user_directory_path(instance, filename):
    return 'uid_{0}/{1}'.format(instance.user.id, filename)

class Articles(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
    up_file = models.FileField(upload_to=user_directory_path)

圖片上傳

ImageFiel 是用于保存圖像的字段意荤,方法和屬性都繼承 FileField , 增加了驗(yàn)證上傳的對象是否為有效的圖像啊片。增加了 heightwidth 兩個屬性。需要 Pillow 庫支持玖像。

MEDIA URL 路徑設(shè)置

settings.py 中配置路徑

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

假如 models.py 中 ImageField 字段:

class MyModel(models.Model):
    image = models.ImageField(upload_to="image/%Y/%m")

此時如果上傳 car.jpg 圖片紫谷,那么圖片在數(shù)據(jù)庫中保存的路徑為 image/2018/10/car.jpg。

如果想要在前端使用 MEDIA_URL 捐寥,需要在 settings.py > TEMPLATES > context_processors 中

添加 django.template.context_processors.media笤昨,

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')]
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                # '''''''
                'django.template.context_processors.media',  # 添加在這里
            ],
        },
    },
]

然后就可以在前端模版中使用 MEDIA_URL:

<img class="ui image" src="{{ MEDIA_URL }}{{ content.image }}">

這樣后端更改 MEDIA_URL 路徑時,就不用改前端了握恳。

字段選項(xiàng)參數(shù)

通過字段選項(xiàng)瞒窒,可以實(shí)現(xiàn)對字段的約束。

選項(xiàng) 說明
null 如果為 True乡洼,Django 將空值以 NULL 存儲到數(shù)據(jù)庫中崇裁,默認(rèn)值是 False匕坯, null 是數(shù)據(jù)庫范疇的概念。
blank 如果為 True拔稳,則該字段允許為空白葛峻,默認(rèn)值是 False, blank 是表單驗(yàn)證證范疇的巴比。
choices 可迭代的對象(如列表或元組)术奖,由兩個元組(包括自身)組成的可迭代對象構(gòu)成(如 [(A, B), (A, B) …]),用于設(shè)定字段的選項(xiàng)采记。如果設(shè)定這個選項(xiàng)挺庞,默認(rèn)的表單小組件將由標(biāo)準(zhǔn)的文本字段變成帶選項(xiàng)的選擇框选侨。各元組中的第一個元素是真正在模型上設(shè)定的值,第二個元素是人類可讀的名稱晨仑。
db_column 字段使用的數(shù)據(jù)庫列名稱洪己。如未指定答捕,Django 將使用字段的名稱。
db_index 設(shè)為 True 時沃琅,在字段上建立數(shù)據(jù)庫索引益眉。
default 字段的默認(rèn)值
editable 設(shè)為 False 時呜叫,字段不在管理后臺或其他 ModelForm 中顯示。驗(yàn)證模型時也會跳過娱颊。默認(rèn)為 True箱硕。
error_messages 用于覆蓋字段拋出異常時的默認(rèn)消息剧罩。值為一個字典,通過鍵指定想覆蓋的錯誤消息镇防。錯誤消息鍵包括 null来氧、blank、invalid凫碌、invalid_choice僚楞、unique 和 unique_for_date泉褐。
primary_key 設(shè)為 True 時,指定字段為模型的主鍵跳座。
unique 設(shè)為 True 時疲眷,在表中字段的值必須是唯一的狂丝。除了 ManyToManyField倍试、OneToOneField 和 FileField 之外县习,其他字段都可以設(shè)定這個選項(xiàng)躁愿。
verbose_name 字段的人類可讀名稱。如果未設(shè)定样勃,Django 將使用字段的屬性名稱(下劃線轉(zhuǎn)換成空格)自動生成一個峡眶。

關(guān)系

多對一 (ForeignKey)

多對一關(guān)系,需要兩個位置參數(shù)狮暑,一個是關(guān)聯(lián)的模型搬男,另一個是 on_delete 選項(xiàng)缔逛,外鍵要定義在多的一方按脚,如:

from django.db import models

# 一個作者可以有多篇文章
class Articles(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)

要創(chuàng)建一個遞歸的外鍵,即一個對象和自身的多對一關(guān)系伞辛,如:

class Comment(models.Model):
    title = models.CharField(max_length=64)
    body = models.CharField(max_length=256)
    # 一個評論下面可以有多個評論
    parent_comment = models.ForeignKey('self', on_delete=models.CASCADE,null=True, blank=True)

多對一(ForeignKey)的參數(shù)

ForeignKey.on_delete

當(dāng)外鍵關(guān)聯(lián)的對象被刪除時踊兜,Django 將模仿 on_delete 執(zhí)行相應(yīng)的操作,如:

# 當(dāng)刪除作者時垫言,不刪除對應(yīng)的文章,將作者設(shè)置為 null
author = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True)
  • CASCADE :模擬 SQL 語言中的 ON DELETE CASCADE 約束筷频,將定義有外鍵的模型對象同時刪除。
  • PROTECT : 阻止上面的刪除操作坯癣,但是彈出 ProtectedError 異常示罗。
  • SET_NULL :將外鍵字段設(shè)為 null蚜点,只有當(dāng)字段設(shè)置了 null=True 時,方可使用該值。
  • SET_DEFAULT : 將外鍵字段設(shè)為默認(rèn)值实辑。只有當(dāng)字段設(shè)置了 default 參數(shù)時,方可使用悠反。
  • DO_NOTHING :什么也不做斋否。
  • SET() :設(shè)置為一個傳遞給 SET() 的值或者一個回調(diào)函數(shù)的返回值疫诽。
ForeignKey.limit_choices_to

限制外鍵所能關(guān)聯(lián)的對象奇徒,參數(shù)只能在 ModelFormadmin 后臺使用,值可以是一個字典胖笛、Q 對象或者一個返回字典或 Q 對象的函數(shù)調(diào)用匀钧,如:

staff_member = models.ForeignKey(
    User,
    on_delete=models.CASCADE,
    limit_choices_to={'is_staff': True},
)
# 在 ModelForm 的 staff_member 字段列表中之斯,只會出現(xiàn)那些 is_staff=True 的 Users 對象

參考下面的方式,使用函數(shù)調(diào)用:

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

# 把可選對象限定到 pub_date 早于當(dāng)前時間的對象中
limit_choices_to = limit_pub_date_choices
ForeignKey.related_name

反向操作時瘫絮,使用的字段名麦萤,用于代替 '表名_set' 如: object. 表名_set.all(), 也是 related_query_name(目標(biāo)模型使用的反向過濾器名稱)的默認(rèn)值壮莹,如:

ForeignKey.related_query_name

反向操作時涝滴,使用的連接前綴歼疮,用于替換'表名' 如: models.UserGroup.objects.filter(表名__字段名稱 =1)

ForeignKey.to_field

默認(rèn)情況下,外鍵都是關(guān)聯(lián)到被關(guān)聯(lián)對象的主鍵上骤素。如果指定這個參數(shù),可以關(guān)聯(lián)到關(guān)聯(lián)表的指定字段上霎槐,但是該字段必須具有 unique=True 屬性。

ForeignKey.db_constraint

決定是否在數(shù)據(jù)庫中為這個外鍵創(chuàng)建約束. 默認(rèn)值為 True

ForeignKey.swappable

控制外鍵指向可交換的模型時遷移框架的反應(yīng),默認(rèn)值為 True

多對多 (ManyToManyField)

多對多有一個必須的位置參數(shù)唁桩,關(guān)聯(lián)的對象模型荒澡。

class Tags(models.Model):
    name = models.CharField(max_length=32)

class Articles(models.Model):
    title = models.CharField(max_length=32)
    tag = models.ManyToManyField(Tags, blank=True)

多對多除了生成各自的表以外碍现,還會生成第三張表昼接,用來管理雙方的關(guān)系慢睡,可以用 db_table 選項(xiàng)設(shè)定泪喊。

多對多 (ManyToManyField) 的參數(shù)

ManyToManyField.related_name

同 ForeignKey.related_name窘俺。

ManyToManyField.related_query_name

同 ForeignKey.related_query_name。

ManyToManyField.limit_choices_to

同 ForeignKey.limit_choices_to。如果通過 through 參數(shù)自定義了中間聯(lián)結(jié)表髓棋,ManyToManyField 的 limit_choices_to 參數(shù)沒有作用膳犹。

ManyToManyField.symmetrical

只用于與自身進(jìn)行關(guān)聯(lián)的ManyToManyField. 例如下面的模型

from django.db import models

class Person(models.Model):
    friends = models.ManyToManyField("self")

默認(rèn)的情況下,django 中多對多關(guān)系是對稱的豺旬,django 不會為 Person 類添加 person_set 屬性用于反向關(guān)聯(lián)族阅,如果不需要對稱關(guān)系可以將symmetrical 設(shè)置為 False,強(qiáng)制 Django 為反向關(guān)聯(lián)添加描述符

ManyToManyField.through

Django 會自動生成一個表求泰,用于管理多對多關(guān)系渴频。如果想自定義中間表拔第,可以通過 through 選項(xiàng)指定表示中間表的 Django 模型蚊俺。

from django.db import models

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

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(
        Person,
        through='Membership',   # 自定義中間表 ‘Membership’
        through_fields=('group', 'person'),
    )

# 定義中間表的模型
class Membership(models.Model):
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    # 增加邀請人
    inviter = models.ForeignKey(
        Person,
        on_delete=models.CASCADE,
        related_name="membership_invites",
    )
    # 增加進(jìn)入時間
    date_joined = models.DateField()
    # 增加邀請理由
    invite_reason = models.CharField(max_length=64)
ManyToManyField.through_fields

上面的例子中。Membership 模型中包含三個關(guān)聯(lián) Person 的外鍵,Django 無法確定到底使用哪個作為和 Group 關(guān)聯(lián)的對象忙上。所以疫粥,必須顯式的指定 through_fields 參數(shù),用于定義關(guān)系库糠。

through_fields 參數(shù)接受一個元組('field1', 'field2') field1 指向定義多對多模型的外鍵字段名稱(Membership模型中的 group),field2 指向目標(biāo)模型 的外鍵字段名稱()(Membership模型中的 person)贷屎。

如果中間表中只有兩個外鍵的話罢防,可以不用指定 through_fields,django一般可以自動識別。

ManyToManyField.db_table

設(shè)置中間表的名稱唉侄,不指定的話使用默認(rèn)值

ManyToManyField.db_constraint

同 ForeignKey.db_constraint

ManyToManyField.swappable

同 ForeignKey.swappable

一對一(OneToOneField)

一對一,在概念上, 它類似于設(shè)置了 unique=TrueForeignKey, 但是咒吐,一對一關(guān)系的反向關(guān)聯(lián)的對象只有一個

如果沒有為 OneToOneField 指定 related_name 參數(shù),Django 使用當(dāng)前模型的小寫作為默認(rèn)值:

from django.conf import settings
from django.db import models

class MySpecialUser(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL属划,on_delete=models.CASCADE)
    supervisor = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='supervisor_of'恬叹, on_delete=models.CASCADE)

'User' 模型便具有了以下屬性:

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

一對一(OneToOneField)參數(shù)

OneToOneField 接受的參數(shù)與 ForeignKey 完全一樣,此外還有一個 parent_link 參數(shù)目溉。

管理器

  • Django 模型至少有一個管理器脸狸,可以自定義管理器,定制訪問數(shù)據(jù)庫的方式
  • 模型的管理器是 Django 模型用于執(zhí)行數(shù)據(jù)庫查詢的對象
  • 當(dāng)定義模型類時沒有指定管理器媚值,則 Django 會為模型類提供一個名為 objects 的管理器
  • 自定義管理器可能出于兩方面的原因:添加額外的管理器方法和(或)修改管理器返回的 QuerySet。
  • 添加額外的管理器方法是為了給模型添加 數(shù)據(jù)表 層功能

添加額外的管理器方法

當(dāng)不滿足默認(rèn)的管理器方法時,可以給管理器添加額外的方法甩苛,新的管理器需要繼承 django.db.models.Manager , 在 model 中將 新管理器 賦值給 objects 即可

class BookManager(models.Manager):
    def title_count(self, keyword):
        return self.filter(title__icontains=keyword).count()
        # BookManager 類擴(kuò)展 django.db.models.Manager
        # self 代指管理器本身

class Book(models.Model):
    title = models.CharField(max_length=128)
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
    publication_data = models.DateField(blank=True, null=True)
    objects = BookManager()
        # BookManager() 賦值給模型的objects 屬性
        # 將BookManager()替換“默認(rèn)”管理器

    def __str__(self):
        return self.title

如此反浓,objects 出來的原來的方法外,還多了一個 title_count() 方法,如下調(diào)用:

In [4]: Book.objects.title_count('ava')
Out[4]: 1

In [6]: Book.objects.all()
Out[6]: <QuerySet [<Book: Python3 Cook Book>, <Book: Java>]># BookManager()除了有title_count方法外拴念,也有默認(rèn)的objects其它方法

修改管理器返回的查詢集合(QuerySet)

  • 如果想修改管理器返回的 QuerySet,需要覆蓋 Manager.get_queryset() 方法昧谊。get_queryset() 方法需要返回一個 Query-Set
  • Django 遇到的第一個管理器(按照在模型中定義的順序)定義為“默認(rèn)的”管理器草姻。
  • 同一個模型上可以使用多個管理器
class MaleManager(models.Manager):
    def get_queryset(self):
    return super(MaleManager, self).get_queryset().filter(gender='Male')

class FemaleManager(models.Manager):
    def get_queryset(self):
    return super(FemaleManager, self).get_queryset().filter(gender='Female')

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    gender = models.CharField(max_length=6,
                            choices=(
                                    ('Male', '男'),
                                    ('Female', '女')
                                    )
                            )
    people = models.Manager() # 默認(rèn)管理器,選擇全部對象
    men = MaleManager()       # 只選擇 gender = Male 的對象
    women = FemaleManager()   # 只選擇 gender = Female 的對象

模型方法

模型中自定義的方法為對象添加數(shù)據(jù)行層的功能谣妻。管理器的作用是執(zhí)行數(shù)據(jù)表層的操作减江,而模型方法處理的是具體的模型實(shí)例茵休。

自定義模型方法

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    birth_date = models.DateField()

    def baby_boomer_status(self):
        # 返回一個人的出生日期與嬰兒潮的關(guān)系
        import datetime
        if self.birth_date < datetime.date(1945, 8, 1):
            return "Pre-boomer"
        elif self.birth_date < datetime.date(1965, 1, 1):
            return "Baby boomer"
        else:
            return "Post-boomer"

    def _get_full_name(self):
        # 返回一個人的全名
        return '%s %s' % (self.first_name, self.last_name)

    # 使用 property 函數(shù)將類方法包裝為屬性
    full_name = property(_get_full_name)

重寫預(yù)定義的模型方法

如果想在調(diào)用 save() 等預(yù)定義方法之前做些什么的話唠雕,可以重寫這些方法阎毅,如:

# save()    將模型對象保存到數(shù)據(jù)表中
class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def save(self, *args, **kwargs):
        do_something()
        super(Blog, self).save(*args, **kwargs) # 調(diào)用 “真正的” save() 方法
        do_something_else()

需要在方法中繼承超類方法 super(Blog, self).save(*args, **kwargs) ,否則不會保存到數(shù)據(jù)庫

Meta選項(xiàng)

使用 class Meta: 在模型中增加一個子類,如

模型元數(shù)據(jù)選項(xiàng)

選項(xiàng) 說明
abstract 設(shè)為 True 時表明模型是抽象基類。
app_label 如果定義模型的應(yīng)用不在 INSTALLED_APPS 中赂韵,必須指定所屬的應(yīng)用瞬测。
db_table 模型使用的數(shù)據(jù)庫表名稱。
db_tablespace 模型使用的數(shù)據(jù)庫表空間。默認(rèn)為項(xiàng)目的 DEFAULT_TABLESPACE 設(shè)置(如果設(shè)定了)。如果數(shù)據(jù)庫后端不支持表空間,忽略這個選項(xiàng)。
default_related_name 關(guān)聯(lián)的對象回指這個模型默認(rèn)使用的名稱。默認(rèn)為<model_name>_set。
get_latest_by 模型中可排序字段的名稱,通常是一個 DateField、DateTimeField 或 IntegerField。
managed 默認(rèn)為 True俯艰,即讓 Django 在遷移中創(chuàng)建適當(dāng)?shù)臄?shù)據(jù)庫表啦辐,并在執(zhí)行 flush 管理命令時把表刪除跑芳。
order_with_respect_to 標(biāo)記對象為可排序的,排序依據(jù)是指定的字段。
ordering 對象的默認(rèn)排序疆液,獲取對象列表時使用。
permissions 創(chuàng)建對象時寫入權(quán)限表的額外權(quán)限陕贮。
default_permissions 默認(rèn)為 ('add', 'change', 'delete')堕油。
proxy 設(shè)為 True 時,定義為另一個模型的子類的模型視作代理模型肮之。
select_on_save 指明是否讓 Django 使用 1.6 版之前的 django.db.models.Model.save() 算法馍迄。
unique_together 設(shè)定組合在一起時必須唯一的多個字段名稱。
index_together 設(shè)定在一起建立索引的多個字段名稱局骤。
verbose_name 為對象設(shè)定人類可讀的名稱(單數(shù))攀圈。
verbose_name_plural 設(shè)定對象的復(fù)數(shù)名稱。

模型繼承

Django 的模型和 Python 的類一樣峦甩,支持繼承赘来,有 3 種繼承方式:

  • 抽象基類
  • 多表繼承
  • 代理模型

抽象基類

基類模型不會創(chuàng)建數(shù)據(jù)表现喳,在模型中的 Meta 類中添加 abstract=True 將普通模型轉(zhuǎn)換為抽象基類。子模型會擁有基類模型中的全部字段犬辰。如:

from django.db import models

class BaseModel(models.Model):
    name = models.CharField(max_length=32)
    thickness = models.CharField(max_length=6)
    length = models.CharField(max_length=12)
    height = models.CharField(max_length=12)
    create_time = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

class CustomerBoard(BaseModel):
    customer_code = models.CharField(max_length=12)

    def __str__(self):
        return '{}_{}'.format(self.customer_code, self.name)

上面的 CustomerBoard 模型擁有抽象基類 BaseModel 中所有的字段嗦篱,如:

In[5]: board = CustomerBoard.objects.get(pk=1)
In[6]: board.name
Out[6]: 'first board'
In[7]: board.thickness
Out[7]: '1.0mm'
In[8]: board.create_time
Out[8]: datetime.datetime(2018, 10, 11, 13, 1, 2, 190123, tzinfo=<UTC>)

抽象基類的 Meta 類

  • 當(dāng)子類沒有定義自己的 Meta 類,子類將會繼承抽象基類的 Meta 類幌缝。
  • 當(dāng)抽象基類有的元數(shù)據(jù)灸促,在子類沒有的話,直接繼承沒有的元數(shù)據(jù)涵卵。
  • 當(dāng)抽象基類有的元數(shù)據(jù)浴栽,在子類同樣有的話,直接覆蓋基類的元數(shù)據(jù)轿偎。
  • 子類可以先繼承抽象基類中的 Meta 元數(shù)據(jù)典鸡,然后再定義自己的 Meta 類添加額外的元數(shù)據(jù)。
  • 在子模型中 Meta 中添加 abstract = True 坏晦,可以把子類變成抽象基類

抽象基類中的 related_namerelated_query_name

ForeignKeyManyToManyField 字段上使用的 related_namerelated_query_name 屬性時萝玷,需要制定唯一的反向名稱,但是如果在抽象基類這樣做的話昆婿,會出現(xiàn)所有繼承該抽象基類的子模型 related_namerelated_query_name 完全相同球碉,可以使用 '%(app_label)s' 和 '%(class)s'字符串來解決

  • '%(app_label)s' 會被子類所在的 APP 的名字取代
  • '%(class)s' 會被子類的名字取代

如,在 app common 中仓蛆,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

在另外一個 app 中汁尺,rare/models.py:

from common.models import Base

class ChildB(Base):
    pass

上面的繼承關(guān)系中:

  • common.ChildA.m2m 字段的反向名稱為 common_childa_related
  • common.ChildB.m2m 字段的反向名稱為 common_childb_related
  • rare app 中 rare.ChildB.m2m 字段的反向名稱為 rare_childb_related

如果沒有在抽象基類中定義 related_name 屬性,反向名稱就是 小寫子模型名稱_set多律。

多表繼承

多表繼承中痴突,父類和子類都會創(chuàng)建數(shù)據(jù)庫,繼承關(guān)系是通過子模型和它每個父類添加一個自動創(chuàng)建的 OneToOneField 鏈接來實(shí)現(xià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)

父模型 Place 所有的字段在 Restaurant 中都是可以使用的辽装,但是字段不會保存在 Restaurant 中,如:

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

如果一個 Place 也是一個 Restaurant相味,可以使用 Place對象.restaurant 取到對應(yīng)的Restaurant 對象

>>> p = Place.objects.filter(name="Bob's Cafe")
# If Bob's Cafe is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>

如果上面的 P 只是單純的 Placep.restaurant 會拋出 Restaurant.DoesNotExist

多表繼承中的Meta類型

除了 orderingget_latest_by 兩個元數(shù)據(jù)外拾积,其余的元數(shù)據(jù)都不會被子類繼承。

如果不想繼承父類的 orderingget_latest_by 參數(shù)丰涉,需要在之類中重寫或者禁用:

class ChildModel(ParentModel):
    # ...
    class Meta:
        # 清空排序
        ordering = []

代理繼承

代理模型的作用:

  • 可以創(chuàng)建拓巧、刪除、更新代理模型的實(shí)例一死,并且所有的數(shù)據(jù)都可以像使用原始模型(非代理類模型)一樣被保存肛度。
  • 可以在代理模型中改變默認(rèn)的排序方式和默認(rèn)的 manager 管理器 等等,而不會對原始模型產(chǎn)生影響投慈。

聲明一個代理模型只需要將 Metaproxy 的值設(shè)為 True 承耿。如:

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訪問,反之亦然:

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

也可以讓父類模型正常查詢加袋,而代理排序凛辣,如:

class OrderedPerson(Person):
    class Meta:
        ordering = ["last_name"]
        proxy = True

現(xiàn)在,普通的Person查詢是無序的职烧,而 OrderedPerson查詢會按照last_name排序扁誓。

基類的限制

  • 代理模型必須繼承自一個非抽象基類。 并且不能繼承自多個非抽象基類
  • 代理模型可以繼承任意多個抽象基類蚀之,但前提是它們沒有定義任何 model 字段
  • 代理模型可以同時繼承多個別的代理模型蝗敢,但前提是這些代理模型繼承同一個非抽象基類

代理模型的管理器

如果代理模型中沒有定義管理器,代理模型就會從父類中繼承管理器恬总。如果代理模型中定義了管理器,它就會變成默認(rèn)的管理器肚邢,不過定義在父類中的管理器仍然有效壹堰。

from django.db import models

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

class MyPerson(Person):
    objects = NewManager()

    class Meta:
        proxy = True

只是需要添加管理器而不是替換默認(rèn)管理器時,可以創(chuàng)建一個含有新的管理器的基類骡湖,并且在繼承時把他放在主基類的后面:

# 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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末贱纠,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子响蕴,更是在濱河造成了極大的恐慌谆焊,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浦夷,死亡現(xiàn)場離奇詭異辖试,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)劈狐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門罐孝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肥缔,你說我怎么就攤上這事莲兢。” “怎么了续膳?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵改艇,是天一觀的道長。 經(jīng)常有香客問我坟岔,道長谒兄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任社付,我火速辦了婚禮舵变,結(jié)果婚禮上酣溃,老公的妹妹穿的比我還像新娘。我一直安慰自己纪隙,他們只是感情好赊豌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绵咱,像睡著了一般碘饼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上悲伶,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天艾恼,我揣著相機(jī)與錄音,去河邊找鬼麸锉。 笑死钠绍,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的花沉。 我是一名探鬼主播柳爽,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼碱屁!你這毒婦竟也來了磷脯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤娩脾,失蹤者是張志新(化名)和其女友劉穎赵誓,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柿赊,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡俩功,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碰声。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绑雄。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖奥邮,靈堂內(nèi)的尸體忽然破棺而出万牺,到底是詐尸還是另有隱情,我是刑警寧澤洽腺,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布脚粟,位于F島的核電站,受9級特大地震影響蘸朋,放射性物質(zhì)發(fā)生泄漏核无。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一藕坯、第九天 我趴在偏房一處隱蔽的房頂上張望团南。 院中可真熱鬧噪沙,春花似錦、人聲如沸吐根。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拷橘。三九已至局义,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間冗疮,已是汗流浹背萄唇。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留术幔,地道東北人另萤。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像诅挑,于是被迫代替她去往敵國和親四敞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容