模型是关于你数据的单独的,明确的信息源。它包含你储存数据的重要的字段和行为。通常的,每个模型都映射一个单独的数据表。
基本:
- 每个模型都是继承django.db.models.Model的Python子类
- 每个模型的属性都表示一个数据库字段。
- 使用所有这些,Django提供给你一个自动生成数据库访问API;阅读<进行查询>。
1. 简单的例子
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
CREATE TABLE myapp_person (
"id" serial NOT NULL PRIMARY KEY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);
- 表的名字,myapp_person,是自动从一些模型的元数据推导的但也能被覆写。阅读<表名>获得更多细节
- id字段是自动添加的,但是这个行为也能被覆写。阅读<自动主键字段>
- 这个例子中的CREATE TABLE SQL使用PostgreSQL语法组成,但值得注意的是,Django使用了在你设置文件中指定的数据库后端。
2. 使用模型
INSTALLED_APPS = [
#...
'myapp',
#...
]
3. 字段
from django.db import models
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
3.1 字段类型
- 列类型,告诉数据库存储的数据的类型(例如 INTEGER,VARCHAR,TEXT)。
- 当渲染一个窗体字段时默认的HTML 小部件(例如 <input type = "text">,<select>)
- 用于Django管理和自动生成表单的最小的验证需求。
3.2 字段选项
YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
('GR', 'Graduate'),
)
在每个元组中的第一个元素将会被储存到数据库。第二个元素将会被窗口小部件显示或在一个
ModelChoiceField。给定一个模型实例,可以使用get_F00_display()方法来访问一个choices的显示值。例如:
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
from django.db import models
class Fruit(models.Model):
name = models.CharField(max_length=100, primary_key=True)
>>> fruit = Fruit.objects.create(name='Apple')
>>> fruit.name = 'Pear'
>>> fruit.save()
>>> Fruit.objects.values_list('name', flat=True)
<QuerySet ['Apple', 'Pear']>
unique
3.3 自动主键字段
id = models.AutoField(primary_key=True)
3.4 详细字段名称
first_name = models.CharField("person's first name", max_length=30)
first_name = models.CharField(max_length=30)
ForeignKey,ManyToManyField和OneToOneField需要第一个参数作为一个模型类,所以使用verbose_name关键字参数:
poll = models.ForeignKey(
Poll,
on_delete=models.CASCADE,
verbose_name="the related poll",
)
sites = models.ManyToManyField(Site, verbose_name="list of sites")
place = models.OneToOneField(
Place,
on_delete=models.CASCADE,
verbose_name="related place",
)
3.5 关系
from django.db import models
class Manufacturer(models.Model):
# ...
pass
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
# ...
class Car(models.Model):
company_that_makes_it = models.ForeignKey(
Manufacturer,
on_delete=models.CASCADE,
)
# ...
另请参见
ForeignKey字段接受许多在<模型字段指引>中解释的额外的参数。这些选项帮助定义关系应该如何工作;所有都是可选的。
访问后端相关对象,阅读<遵循后端例子关系>
想要样本代码,阅读<多对一关系模型例子>
from django.db import models
class Topping(models.Model):
# ...
pass
class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(Topping)
另请参见
阅读<多对多关系模型例子>来获得完整的例子。
多对多关系的额外字段
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)
- 你的中间模型必须包含一个 --并且只能是一个 -- 指定源模型的外键(在上例中是Group),或者你必须明确指定Django使用ManyToManyField用于关系的外键。如果你有超过一个外键并且through_fields没有指定,一个校验错误将会被抛出。对于目标模型中外键的一个相似的限制(在我们例子中的Person)
- 对于一个通过中间模型对自己本身有一个多对多关系的模型,对相同模型的2个外键是允许的,但他们将会被当成是2种(不同)边的多对多关系。如果有超过2个外键,你也必须向上面一样指定through_fields,否则将会抛出校验错误。
- 当给一个模型自己定义多对多关系是,使用一个中间模型,你必须使用symmetrical=False(阅读<模型字段指引>)
>>> 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>]>
>>> # The following statements will not work
>>> beatles.members.add(john)
>>> beatles.members.create(name="George Harrison")
>>> beatles.members.set([john, paul, ringo, george])
>>> 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)
>>> # Beatles have broken up
>>> beatles.members.clear()
>>> # Note that this deletes the intermediate model instances
>>> Membership.objects.all()
<QuerySet []>
# Find all the groups with a member whose name starts with 'Paul'
>>> 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]>
>>> 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.'
>>> 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.'
一对一关系
另请参见
阅读<一对一关系模型例子>来获得完整的例子
3.6 文件间的模型
rom 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.7 字段名称限制
- 字段名称不能是Python保留字,因为那将会导致Python语法错误。例如:
class Example(models.Model): pass = models.IntegerField() # 'pass' is a reserved word!
- 一个字段名称不能在一行中包含超过一个下划线, 取决于Django的查询查找语法工作的方式。例如:
class Example(models.Model): foo__bar = models.IntegerField() # 'foo__bar' has two underscores!
3.8 自定义字段类型
from django.db import models
class Ox(models.Model):
horn_length = models.IntegerField()
class Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
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"
@property
def full_name(self):
"Returns the person's full name."
return '%s %s' % (self.first_name, self.last_name)
在这个例子中最后一个方法是一个属性。
一个Python的“魔法方法”,返回任何对象的一个unicode“展示”。这是Python和Django将会使用的,不管什么时候一个模型实例需要强制作为一个普通的东西显示。最值得注意的是,这发生在当你在一个交互控制台或者管理里显示一个对象时。
3.9 覆写与定义模型方法
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符号。
不幸的是,当批量创建或者批量更新时,没有解决方案,因为save(),pre_save,和post_save将不会被调用。
3.10 执行自定义SQL
4. 模型继承
- 通常,你将想要使用父类持有哪些你不需要在每个子类都重复输入的信息。这个类通常不会单独出现,所以抽象基类是你想要的。
- 如果你从一个现有模型继承(可能完全来自另一个应用)并且想要每个模型有它自己的数据表,多表继承将会是很好的选择。
- 最后,如果你想要修改一个模型的Python级行为,而不像修改模型文件,你可以使用代理模型。
4.1 抽象基类
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)
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'
- "%(class)s"被替换为使用字段所在的子类的小写名称。
- "%(app_label)s"被替换为包含子类的应用的小写名称。每个安装的应用名称都应该独一无二,每个应用中的模型名称也应该独一无二,因此最终的名称将会不同。
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
from common.models import Base
class ChildB(Base):
pass
4.2 多表继承
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.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")
>>> p = Place.objects.get(id=12)
# If p is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>
place_ptr = models.OneToOneField(
Place, on_delete=models.CASCADE,
parent_link=True,
)
class ChildModel(ParentModel):
# ...
class Meta:
# Remove parent's ordering effect
ordering = []
class Supplier(Place):
customers = models.ManyToManyField(Place)
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'
4.3 代理模型
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
>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>
你也能同样适用一个代理模型在模型上来定义一个不同的默认排序。你也许不总是想要对Person模型进行排序,但是当使用代理时经常会按last_name排序。这很简单:
class OrderedPerson(Person):
class Meta:
ordering = ["last_name"]
proxy = True
from django.db import models
class NewManager(models.Manager):
# ...
pass
class MyPerson(Person):
objects = NewManager()
class Meta:
proxy = True
# 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
- 如果你正在备份一个现有的模型或者数据表并且不想要所有的源数据表列,使用Meta.managed=False。那个选项通常是用于数据库试图和表,并且不再Django的控制下。
- 如果你想要修改一个模型的只使用Python的行为,但是保留所有和源模型一样的字段,使用Meta.proxy=True.这设置好了,所以当保存数据时,代理模型是源模型存储结构的一个完整本分。
4.4 多重继承
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
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
4.5 字段名称 "hiding" 是不允许的
警告
模型管理器继承自抽象基类。覆写一个被继承Manager引用的继承字段可能引起微妙的bugs。阅读<自定义管理器和模型继承>
注意
一些字段在模型上定义额外的属性,例如,一个ForeignKey使用添加在字段名称后的_id来定义一个额外的属性,对外模型的related_name和related_query_name也是一样。
这些额外的属性不能被覆写除非定义它的字段被改变或者移除所以它不再定义额外属性。
4.6 在包内组织模型
myapp/models/__init__.py
from .organic import Person
from .synthetic import Robot
另请参见
<模型指引>
涵括所有模型相关的API包括模型字段,相关对象,和查询集。