模型
django提供了一個強大的orm(關系映射模型)系統。
模型包含了你要在數據庫中創建的字段信息及對數據表的一些操作
使用模型
定義好模型后,要告訴django使用這些模型,你要做的就是在配置文件中的INSTALLED_APPS中添加模型所在的應用名稱
字段類型
模型中的每個字段都是Field類相應的實例,django根據Field類型來確定以下信息:
- 列類型,告知數據庫要存儲那種數據
- 渲染表單時使用的默認HTML widget
- 驗證,被用在admin和表單中
通用字段參數(常用)
null:如果為True,Django將在數據庫中將該字段存儲為NULL(如果該字段為空),默認False
blank:如果為True,該字段允許為空值,默認False
注意,null是數據庫范疇,blank是表單驗證范疇
choices:如果設置了該選項,在渲染HTML時,將會是一個下拉選擇框。該選項是一個二元組構成的可迭代對象,選擇框中的值就是二元組內的值
如:
YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
('GR', 'Graduate'),
)
每個元組中的第一個元素是將被存儲在數據庫中值,第二個元素由窗體小部件顯示
給定一個模型,可以使用get_FOO_display()來訪問字段在窗體中的顯示值
如:
from django.db import models class Person(models.Model): SHIRT_SIZES = ( ('S', 'Small'), ('M', 'Medium'), ('L', 'Large'), ) name = models.CharField(max_length=60) shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
執行以下代碼可分別訪問字段的數據庫值和顯示值:
>>> p = Person(name="Fred Flintstone", shirt_size="L") >>> p.save() >>> p.shirt_size 'L' >>> p.get_shirt_size_display() 'Large'
default: 字段的默認值,可以是一個值也可以是一個對象,如果該對象可調用,那么每次創建一新模型對象時它都會被調用
注意,default值是個可調用的對象時,賦值一個對象的引用和調用該對象的區別
help_text:表單部件額外的顯示內容,對生成文檔也很有用
primary_key:如果為true,該字段就是模型的主鍵,如果沒有指定該選項,會默認生成一個IntergerField的自增ID字段作為主鍵字段
注意:主鍵字段時只讀的,如果在一個已經存在的對象上面更改主鍵的值並保存,一個新的對象將會被創建
unique:如果為true,則該字段的值必須唯一
字段別名
除ForeignKey ManyToManyField OneToOneField之外,每個字段都接受一個可選的位置參數(第一個參數),若沒有提供該參數,將根據字段名稱,將字段名稱下划線替換成空格作為別名
ForeignKey ManyToManyField OneToOneField 第一個參數是關聯的模型類,使用關鍵字參數verbose_name指定別名
關系
多對一:如一個汽車廠生產多種汽車,一輛汽車只有一個生產廠家
代碼如下:
from django.db import models class Manufacturer(models.Model): # ... pass class Car(models.Model): manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
多對多:一個披薩上可以放多種配料,一種配料可以放在多個披薩上
代碼如下:
from django.db import models class Topping(models.Model): # ... pass class Pizza(models.Model): # ... toppings = models.ManyToManyField(Topping)
一般來說ManyToManyField應該放在要在表單中被編輯的對象(如在admin中該字段會被渲染成多選框),如本例:一個披薩選擇多種配料
多對多關系的額外字段:throuth
例如:這樣一個應用,它記錄音樂家所屬的音樂小組。 我們可以用一個ManyToManyField
表示小組和成員之間的多對多關系。 但是,有時你可能想知道更多成員關系的細節,比如成員是何時加入小組的。
對於這些情況,Django 允許你指定一個中介模型來定義多對多關系。 你可以將其他字段放在中介模型里面。 源模型的ManyToManyField
字段將使用through
參數指向中介模型。 對於上面的音樂小組的例子,代碼如下:
from django.db import models class Person(models.Model): name = models.CharField(max_length=128) def __str__(self): # __unicode__ on Python 2 return self.name class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField(Person, through='Membership') def __str__(self): # __unicode__ on Python 2 return self.name class Membership(models.Model): person = models.ForeignKey(Person, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE) date_joined = models.DateField() invite_reason = models.CharField(max_length=64)
應用實例如下:
>>> ringo = Person.objects.create(name="Ringo Starr") >>> paul = Person.objects.create(name="Paul McCartney") >>> beatles = Group.objects.create(name="The Beatles") >>> m1 = Membership(person=ringo, group=beatles, ... date_joined=date(1962, 8, 16), ... invite_reason="Needed a new drummer.") >>> m1.save() >>> beatles.members.all() <QuerySet [<Person: Ringo Starr>]> >>> ringo.group_set.all() <QuerySet [<Group: The Beatles>]> >>> m2 = Membership.objects.create(person=paul, group=beatles, ... date_joined=date(1960, 8, 1), ... invite_reason="Wanted to form a band.") >>> beatles.members.all() <QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>
與常規的多對多字段不同,不能使用add()
,create()
或set()
創建關系:
>>> # 下列語句都是無法工作的 >>> beatles.members.add(john) >>> beatles.members.create(name="George Harrison") >>> beatles.members.set([john, paul, ringo, george])
為什么不能這樣做? 這是因為你不能只創建 Person
和 Group
之間的關聯關系,你還要指定 Membership
模型中所需要的所有信息; 而簡單的add
、create
和賦值語句是做不到這一點的。 所以它們不能在使用中介模型的多對多關系中使用。 此時,唯一的辦法就是創建中介模型的實例。
remove方法被禁用也是出於同樣的原因。 例如,如果通過中介模型定義的表沒有在源模型(Group)和目標模型(perseon)
上強制執行唯一性,則remove()
調用將不能提供足夠的信息,說明應該刪除哪個中介模型實例:
>>> Membership.objects.create(person=ringo, group=beatles, ... date_joined=date(1968, 9, 4), ... invite_reason="You've been gone for a month and we miss you.") >>> beatles.members.all() <QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]> >>> # This will not work because it cannot tell which membership to remove >>> beatles.members.remove(ringo)
但是clear()
方法卻是可用的。它可以清空某個實例所有的多對多關系:
>>> # Beatles have broken up >>> beatles.members.clear() >>> # Note that this deletes the intermediate model instances >>> Membership.objects.all()
通過中介模型建立的m2m關系和普通m2m關系,在查詢方面是相似的:
>>> Group.objects.filter(members__name__startswith='Paul') <QuerySet [<Group: The Beatles>]>
也可以利用中介模型的屬性查詢:
# Find all the members of the Beatles that joined after 1 Jan 1961 >>> Person.objects.filter( ... group__name='The Beatles', ... membership__date_joined__gt=date(1961,1,1)) <QuerySet [<Person: Ringo Starr]>
如果你需要訪問一個成員的信息,你可以直接獲取Membership
模型:
>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo) >>> ringos_membership.date_joined datetime.date(1962, 8, 16) >>> ringos_membership.invite_reason 'Needed a new drummer.'
另一種獲取相同信息的方法是,在Person
對象上反向查詢:
>>> ringos_membership = ringo.membership_set.get(group=beatles) >>> ringos_membership.date_joined datetime.date(1962, 8, 16) >>> ringos_membership.invite_reason 'Needed a new drummer.'
一對一:和其他關系一樣,當某個對象擴展自另一個對象時,最常用的方式就是在這個對象的主鍵上添加一對一關系
模型屬性
objects:模型最重要的屬性是Manager
。 它是Django 模型進行數據庫查詢操作的接口,並用於從數據庫提取實例。 默認的名稱為objects
。 Manager只能通過模型類訪問,而不能通過模型實例訪問。
模型方法
可以在模型上定義自定義方法來給對象添加自定義底層功能。 Manager
方法用於“表范圍”的事務,模型的方法應該着眼於特定的模型實例。這是一個非常有價值的技術,讓業務邏輯位於同一個地方 — 模型中。
例如,下面的模型具有一些自定義的方法:
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): "Returns the person's baby-boomer status." 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"
覆蓋預定義的模型方法
models.Model中封裝了對數據庫的各種操作,特別是,你將要經常改變save()
和delete()
的工作方式
覆蓋內建模型方法的一個典型的使用場景是,你想在保存一個對象時做一些其它事情:
from django.db import models 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) # Call the "real" save() method. do_something_else()
你還可以阻止保存:
from django.db import models class Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() def save(self, *args, **kwargs): if self.name == "Yoko Ono's blog": return # Yoko shall never have her own blog! else: super(Blog, self).save(*args, **kwargs) # Call the "real" save() method
注意:批量操作中被覆蓋的模型方法不會被調用
當使用QuerySet批量刪除對象或由於級聯刪除
時,對象的delete()
方法不一定被調用。 為確保自定義的刪除邏輯得到執行,你可以使用pre_delete
和/或post_delete
信號。
不幸的是,當批量creating
或updating
對象時沒有變通方法,因為不會調用save()
、pre_save
和 post_save
模型繼承
在Django 中有3種風格的繼承:
- 通常,你只想使用父類來持有一些信息,你不想在每個子模型中都敲一遍。 這個父類永遠不會單獨使用,所以你要使用抽象的基類
- 如果你繼承一個已經存在的模型且想讓每個模型具有它自己的數據庫表,那么應該使用多表繼承。
- 最后,如果你只是想改變一個模塊Python 級別的行為,而不用修改模型的字段,你可以使用代理模型。
抽象基類
需要在父類中編寫一個Meta類,設置abstract=True,要注意,如果父類和子類有相同的字段名,會出現錯誤(ps:難道不是重寫嗎?為毛會報錯)
Meta類的繼承
如果子類沒有聲明自己的Meta類, 它將會繼承父類的Meta如果子類想要擴展父類的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'
注意:abstract屬性不會被繼承
多表繼承
django會在子類中自動創建一個 OneToOneField字段來鏈接子類和父類
實例:
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")
Meta和多表繼承
在多表繼承中,子類繼承父類的 Meta類是沒什么意義的。 所有的 Meta選項已經對父類起了作用,再次使用只會起反作用(這與使用抽象基類的情況正好相反,因為抽象基類並沒有屬於它自己的內容)。
代理模型
有時你可能只想更改 model 在 Python 層的行為實現。比如:更改默認的 manager ,或是添加一個新方法,而這,正是代理繼承要做的:為原始模型創建一個代理 。 你可以創建,刪除,更新代理 model 的實例,而且所有的數據都可以像使用原始 model 一樣被保存。 不同之處在於:你可以在代理 model 中改變默認的排序設置和默認的 manager ,更不會對原始 model 產生影響。聲明代理 model 和聲明普通 model 沒有什么不同。 設置Meta
類中 proxy
的值為 True
,就完成了對代理 model 的聲明。
舉個例子,假設你想給 Person
模型添加一個方法。 你可以這樣做:
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
操作同一個數據表。 特別的是,Person
的任何實例也可以通過 MyPerson
訪問,反之亦然:
>>> p = Person.objects.create(first_name="foobar") >>> MyPerson.objects.get(first_name="foobar")
代理模型管理器:如果你沒有在代理模型中定義管理器,代理模型會繼承基類管理器,如果代理模型中定義了管理器,它就會變成默認管理器,不過在父類定義的管理器仍然有效
如果你想在代理模型中添加新的管理器,並非替換基類管理器,可以這樣,創建一個含有新管理器的基類,作為代理模型的基類放在后面:
# 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
模型繼承中隱藏的規則
普通的python類允許子類覆蓋父類的任何屬性,在Django中,模型字段不允許這樣做,如果非抽象基類有一個A字段,那么不能在任何繼承自該基類的類中創建A字段。對於抽象基類沒有這個限制,抽象基類的子類中會被覆蓋,也可以通過設置field_name=None 來刪除字段
在包中組織模型
如果有多個模型文件,可以使用一個名為models的python包來替換原有的models.py文件,但是必須將模型文件導入到所在包的__init__.py文件中:
#myapp/models/__init__.py from .organic import Person from .synthetic import Robot
更多細節可參照django內置模塊,如django.db.models