文章内容大部分参考官方文档,以作者理解叙述
Django中模型是你的数据的唯一的,确定的信息源.它包含你所存储数据所必须的字段和行为.通常,每个模型对应数据库中唯一的一张表.
基础
- 每个模型都是一个python类,它们都是django.db.models.Model的子类
- 每个模型属性都代表数据a库中的一个字段
- 通过所有这一切,Django为你提供了一个自动生成的数据库访问API.
使用模型
- 如下代码,在一个app(database)中创建一个记录人姓名的数据表
from django.db import models
# Create your models here.
class Person(models.Model):
first_name = models.CharField(verbose_name='姓名1', max_length=30)
last_name = models.CharField('姓氏', max_length=30)
- 然后使用 manage.py makemigrations 生成迁移脚本,控制台输入如下:
Operations to perform:
Apply all migrations: admin, auth, contenttypes, database, sessions
Running migrations:
Applying database.0001_initial... OK
# 翻译
执行的操作:
应用所有迁移:admin,auth,contenttypes,数据库,会话
正在运行迁移:
应用database.0001_initial ...确定
- 生成的迁移脚本内容如下:
迁移脚本在你的app下的migrations文件夹下.
# Generated by Django 2.0.7 on 2018-07-27 10:32
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Person',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=30)),
('last_name', models.CharField(max_length=30)),
],
),
]
除了我们命名的first_name和last_name以外,多出一个id的字段.
当我们创建模型类时,如果我们没有在模型类中显式的设置了主键,django会自动的添加id列,作为主键.
怎样显式的设置主键,在字段讲解中阐述.
- 然后使用manage.py migrate 创建数据库,控制台输出如下:
Operations to perform:
Apply all migrations: admin, auth, contenttypes, database, sessions
Running migrations:
Applying database.0001_initial... OK
# 翻译
执行的操作:
应用所有迁移:admin,auth,contenttypes,数据库,会话
正在运行迁移:
应用database.0001_initial ...确定
到此,数据库已经完成创建.
字段
上一小节,遗留了如何显式设置主键的问题,要解决他,了解下字段.
了解字段
根据数据表结构来说:
- 表名
表明,就是数据表的名称,上一小节表明是Person,最终在数据库中的表明是database_Person,为什么暂且不谈. - 字段名
字段名就是表属性的名字,也就是模型类中被赋予字段类型的变量名.
字段名不能是python保留关键字,并且字段名中连续的下环线不超过一个,可以使用db_column参数/属性来规避这些问题. - 字段类型
字段类型表示这个字段存储的数据类型,在模型类中就是models.xxx的方法对象. - 字段属性
字段属性表示该字段的大小、格式、默认值、必填字段、有效性规则、有效性文本和索引等,与之对应的就是models.xxx(max_length=100)关键字参数.
字段在模型类中就是表示数据表中字段和字段类型及字段属性的表达式
first_name = models.CharField(max_length=50)
字段名 字段类型 字段属性
了解了数据表和模型类的对应关系后,只需要了解有哪些可选项供我们使用,就能简单,快速的创建模型类(数据表).
字段类型
- 告诉数据库要存储那种数据,并设置列类型
- 渲染表单时使用默认的HTML widget
- 最低限度的验证需求,在django管理站点和自动生成的表单中
这是字段类型的作用,在了解django admin前,只需关注一点,字段的类型是'告诉数据库要存储那种数据,并设置列类型'.
了解django的所有内置类型[https://yiyibooks.cn/xx/Django_1.11.6/ref/models/fields.html#model-field-types]
也可以编写自定义的模型字段,暂且不说.
字段属性
不同的字段类型其字段属性也有不同,可以在上面的字段类型链接后中详细了解,下面列举一些通用的字段属性.
- null
设置为True,django把空值存储为null,默认False - blank
设置为True,该字段允许为空,默认你False
blank与null不同,blank作用于表单验证,null作用于数据存储.blank在前,null在后.blank验证输入,通过后把数据传递给数据库. - default
设置字段默认值,可以是一个值或者可调用对象,如果是可调用对象,每个新对象创建时,都会执行这个可调用对象. - help_text
表单部件额外现实的帮助内容. - primary_key
如果设置为True,那么这个字段就是主键.
主键是只读对象,如果在一个已存在的对象对主键重新赋值并保存,将会创建一个新的对象,类似于python中的不可变类型. - unique
设置为True,该字段在整张表必须是唯一值. - db_column
显式设置数据库中列的名称,如果没有设置,列名称就是字段名. - db_index
如果设置,将为该字段创建一个数据库索引. - db_tablespace
如果当前字段设置了索引,数据库database tablespace的名称将作为该字段索引名.
如果DEFAULT_INDEX_TABLESPACE已经设置,默认使用DEFAULT_INDEX_TABLESPACE.
如果数据库不支持索引的tablespace,该属性将被忽略
关系
django提供了三种数据库关系:
- 多对一
- 多对多
- 一对一
多对一关系
django使用ForeignKey(to, on_delete, **options)来定义多对一关系.
class Manufacturer(models.Model):
# ...
pass
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
on_delete表示删除时的行为或者理解为如何执行删除动作,models.CASCADE表示级联删除.
如果需要创建递归关联关系,应该这样写
models.ForeignKey('self', on_delete=models.CASCADE)
ForeignKey会主动创建索引,如果不需要,通过db_index=False显式的取消.
如果想要在两个app之间创建一对多关系,应该在源模型(被ForeignKey的模型类)的models中,引入目标模型(定义ForeignKey的模型类)的名称.
多对多关系
dajngo使用ManyTOManyField来定义多对多关系
class Topping(models.Model):
# ...
pass
class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(Topping)
ER图
从ER图可以清楚的了解,当使用多对对模型时,django会自动创建一个中间表,这个表维护了topping与pizza两张表之间的关系.
如果想要在中间表添加额外的字段,那就要使用django的中介模型
django允许指定一个中介模型来定义多对多关系,你可以将其他字段放在中介模型里面,源模型的ManyToMany字段将使用through参数/属性指向中介模型,在中介模型中需要显式的定义两个模型是如何关联的,就如上面没有指定中介模型的多对多关系中的pizza_id和topping_id.
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) # 其他字段
ER图与上面没有使用中介模型的多对多关系相比,中间表多了额外的字段信息.
使用中介模型的限制:
- 中介模型必须有且只有一个外键到源模型,如果超过一个外键,必须使用ManyToManyFirld的through_fields参数/属性,显式设置django在连接多张表的关系中应该使用的外键.
- 如果利用中介模型与自己进行多对多关联,不需要指定through_fields参数/属性,但必须设置symmetrcal=False
- 使用中介模型后,不能在源模型对象上使用add(), create(), set(), remove(),可以使用clear(),因为创建关系时,还需要指定除了关联关系之外的额外字段的信息.add等方法做不到这一点.
一对一关系
django使用OneToOneField字段定义一对一关系,工作方式与ForeignKey完全一致,接受一个可选的的参数/属性 parent_link
- parent_link
设置为True时,同时继承了另一个模型类,表示该字段应该用于反查的父类的链接,而不是在子类化时隐式创建的OneToOneField.
Meta选项
模型元数据,是指任何不适字段的数据,比如排序选项, 数据库表名等,使用class Meta来定义
from django.db import models
class ox(models.Model):
horn_length = models.IntegerField()
class Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
- ordering
获取对象列表时的默认排序
正序 ordering=['字段名']
倒序 ordering=['-字段名'] #减(-)运算符
多个排序字段 ordering=['-字段名, 字段名'] - verbose_name_plural
- abstract
设置为True表示该模型为抽象基类 - app_lable
- db_table
设置改模型数据库名称
如果没有设置db_table,django会自动使用模型类的名称加上该模型类所属app的名称来构建.形如 appName_
modelName - db_tablespace
当前模型类使用的数据库命名空间名称,默认值时setting中设置的DEFAULT_TABLESPACE,如果数据库不支持,将自动忽略 - default_manage_name
默认使用的manage管理器名称 - get_last_by
指定latest()排序使用的字段名称,该字段名称必须是可排序的,例如DateFiels, DateTimeField, IntegrField等 - 等等
模型属性
- objects
模型最重要的属性是manage.他是django模型进行数据库查询操作的接口,并用于从数据库提取实例.如果没有定义manage,默认的名称叫做objects.manage只能通过模型类访问,不能通过模型实例访问.
模型方法
可以在模型上定义自定义的方法给对象的添加增加一些功能.Manage方法用于'表范围'的事务,模型的方法也应该着眼于特定的模型实例.
也可以覆盖模型的预定义方法:
-
str() # python3
python的魔法方法,返回以'unicode'表示的对象,当模型实例需要强制转换并显式为普通的字符串时,python和django会调用这个方法
*unicode() # python2
str的python2版本 - get_absolute_url()
定义如何计算一个对象的url. - save()
如何保存一个对象,在定义保存逻辑时,必须model的save()方法
super(模型类名称, self).save(*args, **kwargs)
需要注意的是,当使用QuerSet批量删除对象或级联删除时,模型上定义的delete方法不会被调用,且没有其他方法可以解决这个问题. - delete()
如何删除一个对象,必须继承model的delete方法
super(模型类名称, self).delete(*args, **kwargs)
需要注意的是,当使用QuerSet批量删除对象或级联删除时,模型上定义的delete方法不一定会被调用.使用pre_delete或post_delete来避免这个问题
执行自定义SQL
执行自定义SQL需要使用管理器(manage)的raw()方法,该方法返回模型的实例.
Manager.raw(raw_query, params=None, translations=None)
class Person(models.Model):
first_name = models.CharField(...)
last_name = models.CharField(...)
birth_date = models.DateField(...)
这样来使用,objects是默认的管理器(manage)名称,前文有提到.
person = Person.objects.raw('SELECT * FROM myapp_person')
模型的继承
模型继承方式与pytho类的继承使用相同,有三种形式
- 抽象基类
- 多表继承
- 代理模型
抽象基类
抽象基类是把模型类的共有信息,抽取出来,作为一个基类存在.这样避免在多个模型类中重复定义相同的字段.
第一步,定义基类
基类除了需要定义模型类的共有信息外,还需要在Meta类中声明属性 abstract=True,这个属性的作用是,阻止django对基类生成数据表.
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
第二步,继承基类
只需把基类名作为父类给子类即可,与python的继承相同.
class Student(CommonInfo):
home_group = models.CharField(max_length=5)
注意,如果子类定义了与基类相同的字段,django将抛出异常.也就是说,子类定义的字段名不能与基类重复.
基类除了定义的字段可以继承,meta类也可以被继承,如下
class CommonInfo(models.Model):
# ...
class Meta:
abstract = True
ordering = ['name']
class Student(CommonInfo):
# ...
class Meta(CommonInfo.Meta):
db_table = 'student_info'
不需要担心继承抽象基类的meta类后,子类也会变成抽象基类,原因是django在meta类继承时,会自动把abstract设置为False.
如果你需要抽象基类继承另一个抽象基类的场景,就需要在meta类中显式设置abstract=True.
抽象基类有效的避免代码不必要的重复代码.
注意,通过继承抽象基类的方式定义的模型类,在使用ForeignKey和ManyToMany时,将会遇到麻烦,因为继承自同一抽象基类,所以这些模型类的信息将完全相同.要解决此问题,应该是指定唯一反向名称和查询名称.
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
或者阻止该模型进行反向关联,将related_name设置为 +
class Base(models.Model):
m2m = models.ManyToManyField(
OtherModel,
related_name='+',
)
class Meta:
abstract = True
class ChildA(Base):
pass
class ChildB(Base):
pass
多表继承
多表继承使得每个模型类都有自己的数据表,都能够执行全部的model方法(完整性),多表继承是django通过隐式创建一对一关系的OneToOneField来实现的
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的全部数据,但Restaurant的数据表并没有Place的任何字段,Place也一样没有Restaurant的数据.
还可以通过多表继承的model对象,来获取对应的model对象.格式是:model对象.与之对应的模型类 类名(小写)
也可以在继承的模型类中显式声明OneToOneField,并设置parent_link=True来覆盖隐式声明的字段,覆盖后,自己定义的OneToOneField字段就是,访问父类的字段.
多表继承的meta也是可以继承的,但不能访问父类的meta类.
比如,子类没有定义ordering排序,就会继承父类的排序规则,这些都是隐式完成的.
如果不想继父类的meta类属性,那么可以在子类显式声明这些属性.
注意,如果两个父类相同的模型类(子类),发生了ManyToManyField或者ForeignKey,会因为related_name相同,而引发一个一场.所以我们在同一父类的多个子类间发生多对多(ManyToManyField)或者一对多(ForeignKey)关系时,指定一个唯一的related_name,来解决这个问题.
例如: models.ManyToManyField(Place, related_name='provider')
代理模型
代理模型可以理解为,继承了模型类,创建了一个新的入口类,在这个类中可以更改默认的mange,增加需要的方法.并且这个类具备源模型类的所有方法.
设置代理模型,只需要在meta类中设置proxy=True,其余与声明一个普通模型类完全一样.
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
meta类继承方式同多表继承的meta类继承方式相同.
代理模型限制
- 代理模型必须继承一个非抽象基类,且只能继承一个
- 代理模型可以继承任意多个抽象基类,但抽象基类不能定义任何模型字段
- 代理模型可以继承任意多个同一父类的代理模型(在django1.10中更改,早期版本不支持)
代理模型管理器
如果没有在代理模型中定义管理器(manage),代理模型会继承父类的管理器
如果想要替换管理器,需要在代理模型中,对默认管理器重新赋值为新的管理器对象.这样做不影响父类的管理器.
class NewManager(models.Manager):
# ...
pass
class MyPerson(Person):
objects = NewManager()
class Meta:
proxy = True
如果不想替换,而是想添加一个新的管理器,也就是说,想要代理模型中有一个以上的管理器.
需要定义一个含有新管理器的抽象基类,然后在代理模型中,把他放在主基类的后面.这样代理模型就有了两个管理器,一个objects,另一个secondary.
class ExtraManagers(models.Model):
secondary = NewManager()
class Meta:
abstract = True
class MyPerson(Person, ExtraManagers):
class Meta:
proxy = True
代理继承与非托管之间的差异
代理继承就是我们的代理模型的使用方式,什么是非托管?
非托管是当我们定义了一个模型类,但我们不需要由django来创建和删除这个数据表,这时,需要设置meta类中的managed=False.这样这个模型类,在数据库迁移时,就不会被创建.这就是非托管,不让django管理了.
代理继承是继承父类模型,并允许新增和重定义父类方法.根据他们的差异,应用场景如下:
- 如果模型数据库创建不需要django就使用非托管模型
- 如果想对一个模型增加一些与现有属性不同,或者重新定义一些方法,增加方法,但又不想影响模型,就是用代理继承
多重继承
同python子类相同,django模型类也可以继承自多个父类模型,特定名称(例如meta)将继承自第一个基类,其他的被忽略.
多重继承的目的,应该是为了使子类拥有不同的属性和方法.
注意,多重继承遇到多个隐式声明主键id的父类时,会引发错误.
- 通过在父类中,通过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
- 通过在父类中,OneToOneField连接到同意祖先,来解决这个问题.
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
字段名称不允许覆盖
django模型里的继承,不允许子类覆盖父类的字段名称.继承的是抽象基类,不适用此规则.
抽象基类字段可以被子类覆盖或者通过 字段名=None的方式删除.(django1.10更改,早期版本不支持)
使用包(模块)组织模型(模型类)
如果有很多模型文件,可以使用包来组织模型,方法是,把需要组织的app下的models.py删除,创建app/models/目录,目录下创建init.py文件和你的模型文件.,每创建一个模型文件,就在init中,导入他们.
例如:
myapp/models/__init__.py
from .organic import Person
from .synthetic import Robot