参考文档
点我跳转
篇首吐槽
又一次快写完博客没有保存
恕我直言, 博客园的自动保存真的是辣鸡
再不完善本地编辑器博客园吃枣药丸
你瞅瞅别人CSDN
1 模型
模型是关于数据信息的单一明确的资源, 模型包含了数据的必要的基本字段和行为
一个模型对应数据库中的一张表
其中
1) 所有的模型类都是django.db.models.Model的子类
2) 模型的每个字段( fileds )也就是类的属性( attribute )代表着数据库中表的一列( colum )
3) Django提供了操作数据库的API
2 简单的示例
定义一个含有 first_name 和 last_name 的Person表
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
上述模型相当于SQL
CREATE TABLE myapp_person (
"id" serial NOT NULL PRIMARY KEY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);
其中
- 表名为 myapp_person , 这是因为派生自模型的元数据(model metadata)
- id列时自动添加的
- 这只是一个PostgreSQL的示例, 实际具体会根据设置为什么样的数据库语言
3 使用模型
定义好的模型需要告知Django这个模型需要生效
因此需要配置INSTALLED_APPS
如果我们的模型模块为myapp.models
INSTALLED_APPS = [
#...
“myapp” ,
#...
]
每当配置INSTALLED_APPS的时候, 需要对数据库操作
python manage.py makemigrations
python manage.py migrate
4 字段(Fields)
字段用类中的属性定义
字段是模型中最重要的部分
注意命名的时候字段的名字不要和模型的API冲突, 如clean, save, delete
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()
4.1 字段类型
每个字段都应该是Field类的对象
不同的Field对象有以下区分:
- 根据不同的Field类来设置不同的数据类型(INTEGER, VARCHAR, TEXT等)
- 同时不同的Field类型在HTML的组件也是不一样的
- 最后不同的数据类型有一些针对性的验证, 这个在后台管理和一些自动生成的表单中会体现
具体类型参考
自定义数据类型
4.2 字段选项
每个字段类型都会传入一些特定的参数选项
例如CharField就会传入max_length来限定字符的长度
除了特定的参数还是有一些公共的参数:
1) null
默认为False, 设置为True表示空值的时候设置为NULL
2) blank
默认为False, 设置为True该字段可以为空
null和blank的不同
null是数据库的要求, blank是验证性要求(主要应用于后台管理时)
也就是说如果blank=True那么该地段可以不用传值
3) choices
它指定的数据类型是一个可迭代对象(列表或者元组), 其中的值必须是一个含有两个值的元组, 形式为
YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
('GR', 'Graduate'),
)
如果设定为choice, 那么相对应的HTML空间就不是一个文本框(默认字段是)而是一个下拉菜单(select)
关于它的值
- 元组中的第一个元素是用于存放在数据库中的值, 因此查询出来的值也就是一个元素的值
- 元组中的第二个元素是表单组件或者是ModelChoiceField的显示, 取得它的值可以使用方法 get_字段名字_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'
4) default
字段的默认值
值可以有两种
- 一般的值
- 可调用对象, 每次创建一个模型的对象的时候都会调用它
5) help_text
用于在表单组件的帮组信息
6) primary_key
如果设置为True, 表示这是模型的主键
如果在模型中不特地的指定主键, 那么就会默认的创建一个自增的IntegerField的主键
主键是只读的, 因此如果修改一个已经存在的对象的主键为另一个值, 并且保存, 那么得到的结果是再创建一个新的对象
如创建一个主键为name的水果模型
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)
['Apple', 'Pear']
7) unique
如果设置为true, 那么整个表中该字段中的每个值应该是唯一的
还有更多的公共选项
4.3 自增主键字段
如果在整个模型中没有指定任何字段为主键, 那么Django会自动创建一个主键字段
id = models.AutoField(primary_key=True)
注意在模型中, 明确添加的主键字段应该是只有一个
4.4 verbose name
verbose name是用于展示的名字, 如在后台管理中的名字
设定verbose name分为两种字段
1) 一般字段
设置的时候传入一个字符串就可以设置了
如果不设置verbose name, Django会将该字段的名字处理(下换线替换成空格)一下得到
first_name = models.CharField(max_length=30)
# 此时的verbose name为first name
first_name = models.CharField("person's first name", max_length=30)
# 此时的verbose name为person's first name
2) 特殊字段
特殊字段有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",
)
另外不用处理首字母大写的问题, Django会在合适的时候自己转换首字母大写
4.5 关系
关系型数据库的优势就是各个表之间的相互关系, Django支持常见的表的关系
4.5.1 一对多(Many-to-one)
要定义一对多关系, 需要使用 django.db.models.ForeignKey , 用它来创建一个字段
ForeignKey需要传入一个位置参数: 相关联的模型
创建范例如下
from django.db import models
class Manufacturer(models.Model):
# ...
pass
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
# ...
另外还可以创建递归关系(自己与自己是一对多关系)
另外, 官方建议:
一对多的字段名字推荐命名为: 关联表的小写形式
4.5.2 多对多(Many-to-many)
要定义一对多关系, 需要使用 ManyToManyField , 用它来创建一个字段
ManyToManyField 同样需要传入一个位置参数: 相关联的模型
现在定义披萨和配料的模型为
from django.db import models
class Topping(models.Model):
# ...
pass
class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(Topping)
ManyToManyField在哪一张表定义都是可以的, 只要定义一个就可以了
但是这或许还是有一些习惯的问题, 针对上面的例子, 一般来说我们都是说披萨有多个配料(而不是配料有多个披萨)
而且一般也是在披萨中选择配料, 因此将多对多的关系设定到了披萨的模型中
同样可以创建递归关系
官方也建议:
多对多的字段名字推荐命名为: 关联表小写的复数形式
4.5.3 多对多额外的字段
标准的多对多关系只需要一个ManyToManyField字段就可以完成
但是在大多数情况下, 多对多的关系可能需要保存一些额外的信息, 这个时候就可以需要使用第三张表来存储数据了
例如音乐家与音乐团体是多对多的关系, 但是可能我们不仅需要了解他们之间的关系, 还需要知道音乐家在什么时候加入该团队的, 邀请的理由又是什么
对此我们可以创建如下模型
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)
为了定义第三张表, 我们必须明确指定关联的那张表表, 为此有两个外键字段
注意事项:
1) 针对多对多的两方, 每一方必须在中间表中指定一个且唯一一个外键, 如果不指定外键, 那就需要在自己表中定义ManyToManyField.through_fields
2) 如果是自己与自己形成的多对多关系, 运行表中这个表的两个外键, 但是会被看成多对多的两个方面, 如果出现了两个以上的外键, 则需要指定through_fields否则会校验错误
3) 对于自己与自己多对多, 需要指定symmetrical=False
使用多对多API操作数据库
>>> 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()
]>
>>> ringo.group_set.all()
]>
>>> m2 = Membership.objects.create(person=paul, group=beatles,
... date_joined=date(1960, 8, 1),
... invite_reason="Wanted to form a band.")
>>> beatles.members.all()
, ]>
带有额外字段的不能使用add(), create(), or set()来创建关系
原因是:
由于第三张表有附带的额外信息, 简单的添加, 创建和调用是无法得到这些详细信息的
因此要创建只能通过第三张表的对象来实现了
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()
, , ]>
>>> # 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()
同样的可以执行一般的多对多查询
# Find all the groups with a member whose name starts with 'Paul'
>>> Group.objects.filter(members__name__startswith='Paul')
]>
也可以加入中间件的属性
# 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))
也可以直接查询第三方表
>>> 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.'
4.5.4 一对一
要定义一对一关系, 可以使用OneToOneField来创建一个字段
OneToOneField同样需要一个位置参数传递关联表
4.6 引用模型
如果需要使用别的应用的模型, 需要导入该模型
例如
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,
)
4.7 字段名字的限制
Django对名字的限制有两个
1) 名字不能是Python的关键字
class Example(models.Model):
pass = models.IntegerField() # 'pass' is a reserved word!
2) 名字不能连续多个下划线
class Example(models.Model):
foo__bar = models.IntegerField() # 'foo__bar' has two underscores!
3) 名字可以是SQL中的保留字, 因为Django会对名字进行转义处理, 而且也会使用特定的数据库引擎使用语法
4.8 自定义字段类型
参看详细介绍
5 元选项(Meta options)
通过使用内部类Meta可以使用模型的元数据(model metadata)
from django.db import models
class Ox(models.Model):
horn_length = models.IntegerField()
class Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
元数据(model metadata)也就是除字段以外的其他数据
例如排序(ordering), 数据库表名(db_table), 单数形式(verbose_name ), 复数形式(verbose_name_plural)
更多参见
6 模型属性
模型最重要的属性是Manager
它是Django提供用于查询数据库操作的接口
如果不自定义Manager, 那么默认的名字就是objects
只有通过模型类来调用Manager, 不能通过模型的对象来调用
7 模型方法
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"
@property
def full_name(self):
"Returns the person's full name."
return '%s %s' % (self.first_name, self.last_name)
常用的需要被重写的函数
1) __str__()
在python2中是__unicode__()
这是Python的魔法函数, 用于修改对象输出的时候的输出信息
2) get_absolute_url()
该方法告诉Django如何为对象计算URL
每个有唯一定义的URL的对象都应该定义这个方法
7.1 重写预定义的方法
模型中还有一些封装了一系列数据库操作的函数, 如save()和delete()
自定义这些函数也是十分有益的
例如重写save()
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.
在重写save()函数的时候, 一定要记得调用原来的函数以便于完成正常的功能
同时使用万能参数也是必不可少的
7.2 执行自定义SQL
使用raw SQL
更多信息
8 模型继承
Django中的模型继承和正常的Python继承没有差别
但是仍要注意, 模型始终要是django.db.models.Model的子类
在Django中有三种继承方式
1) 抽象基类
2) 多表继承
3) 代理模型
8.1 抽象基类
当你想把一些公共的数据分发给其他模型的时候, 就可以使用抽象基类
编写基类的时候需要在Meta类中设置 abstract=True
且抽象基类不能作为正常的模型使用, 不能生成表和Manager, 也不能直接实例化和保存
但是要注意, 子类中不要出现和父类中同名的字段
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)
8.1.1 元继承
继承抽象基类的模型, 同时还会继承抽象基类的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'
注意一个细节, 如果子类把抽象基类的Meta都继承了, 那么abstract=True 也被继承了那岂不是无限的抽象基类了?
为此, Django做了一个隐性的调整, 在被继承之前, 会把抽象基类的abstract=False
还要注意不要在抽象基类的Meta中设置一些无效的属性, 例如设置 db_table , 这样如果子类不重写, 那岂不是每个表名都一样了?
8.1.2 related_name和related_query_name
当你在ForeignKey 或者 ManyToManyField 中使用 related_name 或者 related_query_name的时候一定要注意一件事情:
你必须为该字段设置唯一的 反向名字(reverse name)和查询名字(query name)
为了解决这个问题, 需要在基类使用 related_name 或者related_query_name的时候使用 '%(app_label)s'
和 '%(class)s'
- '%(class)s' 表示字子类类名的小写
- '%(app_label)s' 表示子类所在应用名的小写
由于应用名不应该相同, 同一个应用下模型名不相同, 这就会保证始终不相同
例如
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
rare/models.py
from common.models import Base
class ChildB(Base):
pass
common.ChildA.m2m的反向名为: common_childa_related
common.ChildA.m2m的查询名为: common_childas
common.ChildB.m2m的反向名为: common_childb_related
common.ChildA.m2m的查询名为: common_childbs
rare.ChildB.m2m的反向名为: rare_childb_related
rare.ChildB.m2m的查询名为: rare_childbs
注意, 如果不在抽象基类中定义 related_name, 那么默认的反向名字为 小写类名_set
8.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)
Restaurant继承了Place表, Place表中的所有字段Restaurant都可以使用, 尽管数据存在于不同的表当中, 因而可以进行如下查询
>>> 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
但是如果刚才的那个p不是一个餐馆对象, 而是一个直接用place创建的对象或者是其他类的基类锁创建的对象, 再使用p.restaurant会报错
在餐馆表中自动创建与place一对一关系类似于
place_ptr = models.OneToOneField(
Place, on_delete=models.CASCADE,
parent_link=True,
)
如果你希望能够重写这个字段, 那么你可以选择自己创建一个 OneToOneField 字段并且在子表当中设置parent_link=True
8.2.1 多表继承和元
在多表继承的情况下, 子类继承父类的Meta是没有用的
因此子类是无法访问到父类的Meta的
但是如果有两个属性除外, 就是 ordering和get_latest_by
子类如果想消除父类对子类的影响, 就可以自定义自己的Meta类
class ChildModel(ParentModel):
# ...
class Meta:
# Remove parent's ordering effect
ordering = []
8.2.2 继承和逆向关系
因为多表继承隐藏的使用的一对一的关系, 因此从父类中获取子类的信息是可以的
但是这将要使用related_name来标记外键, 如果这样设置的话, 每个子类都要设置related_name, 不设置的话就会校验出错
对于上述的place, 在创建一个子类拥有多对多的字段
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'.
这个时候再对customers添加一个related_name 就可以解决了
models.ManyToManyField(Place, related_name='provider')
8.2.3 指定父链接字段
正如之前所说, Django会自动创建一个 OneToOneField来链接子类与父类
如果你想控制这个属性的话
你可以自己创建一个 OneToOneField字段并且设置 parent_link=True
8.3 代理模式
8.4 多重继承
8.5 字段名字不能为Hiding
9 组织包中的模型