Django model

Model声明

在app的model.py文件里 声明 person是model名称

将每一个字段都声明出来 如果字段很多 可以用

1
$ django-admin startapp 名称
1
$ python manage.py inspectdb persons

将字段跑出来 直接粘到model文件里 但是需要调整

比如: max_length 或者 jsonb格式的数据

1
2
3
4
5
6
7
8
9
10
from django.db import models

class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)

class Meta:
managed = False
app_label = 'options'
db_table = 'persons'

字段命名约束:

Django不允许下面两种字段名:

  • 与Python关键字冲突。这会导致语法错误。例如:

    class Example(models.Model): pass = models.IntegerField() # ‘pass’是Python保留字!

  • 字段名中不能有两个以上下划线在一起,因为两个下划线是Django的查询语法。

ps: 声明之后要在base.py中PROJECT_APPS 中注册

参考:https://www.liujiangblog.com/course/django/95

Model关联

1对多

多对一的关系,通常被称为外键。外键字段类的定义如下:

1
class ForeignKey(to, on_delete, **options)[source]

外键要定义在‘多’的一方!

1
cv_account = models.ForeignKey(CvAccount, on_delete=models.SET_NULL, null=True)

如果要关联的对象在另外一个app中,可以显式的指出。下例假设Manufacturer模型存在于production这个app中,则Car模型的定义如下

1
2
3
4
5
class Car(models.Model):
manufacturer = models.ForeignKey(
'production.Manufacturer', # 关键在这里!!
on_delete=models.CASCADE,
)

如果要创建一个递归的外键,也就是自己关联自己的的外键,使用下面的方法:

1
models.ForeignKey('self', on_delete=models.CASCADE)

on_delete参数

该参数可选的值都内置在django.db.models中,包括:

  • CASCADE:模拟SQL语言中的ON DELETE CASCADE约束,将定义有外键的模型对象同时删除!(该操作为当前Django版本的默认操作!)
  • PROTECT:阻止上面的删除操作,但是弹出ProtectedError异常
  • SET_NULL:将外键字段设为null,只有当字段设置了null=True时,方可使用该值。
  • SET_DEFAULT:将外键字段设为默认值。只有当字段设置了default参数时,方可使用。
  • DO_NOTHING:什么也不做。
  • SET():设置为一个传递给SET()的值或者一个回调函数的返回值。注意大小写。

db_column参数

设置了外键的字段在下面表字段声明里就不要声明了

1
2
3
4
5
cv_account = models.ForeignKey('cv_accounts.CvAccount', on_delete=models.SET_NULL, null=True, db_column='user_id')
#models.Model 就可以直接声明关联关系
from api.cv_accounts.models import CvAccount
cv_account = models.ForeignKey('CvAccount', on_delete=models.SET_NULL, null=True, db_column='user_id')
#这两种是一样的

多对多(ManyToManyField)

多对多关系在数据库中也是非常常见的关系类型。比如一本书可以有好几个作者,一个作者也可以写好几本书。多对多的字段可以定义在任何的一方,请尽量定义在符合人们思维习惯的一方,但不要同时都定义。

1
class ManyToManyField(to, **options)[source]

多对多关系需要一个位置参数:关联的对象模型。它的用法和外键多对一基本类似。(通过两个一对多的关系 做多对多的关联)

可以通过db_table选项,自定义表名。

through(定义中间表)

through_fields

接着上面的例子。Membership模型中包含两个关联Person的外键,Django无法确定到底使用哪个作为和Group关联的对象。所以,在这个例子中,必须显式的指定through_fields参数,用于定义关系。

through_fields参数接收一个二元元组(‘field1’, ‘field2’),field1是指向定义有多对多关系的模型的外键字段的名称,这里是Membership中的‘group’字段(注意大小写),另外一个则是指向目标模型的外键字段的名称,这里是Membership中的‘person’,而不是‘inviter’。

再通俗的说,就是through_fields参数指定从中间表模型Membership中选择哪两个字段,作为关系连接字段。

db_table

设置中间表的名称。不指定的话,则使用默认值。

一对一(OneToOneField)

一对一关系类型的定义如下:

1
class OneToOneField(to, on_delete, parent_link=False, **options)[source]

用法和前面的多对一外键一样

Meta

每个模型都可以有自己的元数据类,每个元数据类也只对自己所在模型起作用。

abstract

如果abstract=True,那么模型会被认为是一个抽象模型。抽象模型本身不实际生成数据库表,而是作为其它模型的父类,被继承使用。

db_table

指定在数据库中,当前模型生成的数据表的表名。比如:

1
db_table = 'my_freinds'

managed

该元数据默认值为True,表示Django将按照既定的规则,管理数据库表的生命周期。如果设置为False,将不会针对当前模型创建和删除数据库表。

一般需要migration的时候才会设置成true

ordering

最常用的元数据之一了!

用于指定该模型生成的所有对象的排序方式,接收一个字段名组成的元组或列表。默认按升序排列,如果在字段名前加上字符“-”则表示按降序排列,如果使用字符问号“?”表示随机排列。请看下面的例子:

1
2
3
ordering = ['pub_date']             # 表示按'pub_date'字段进行升序排列
ordering = ['-pub_date'] # 表示按'pub_date'字段进行降序排列
ordering = ['-pub_date', 'author'] # 表示先按'pub_date'字段进行降序排列,再按`author`字段进行升序排列。

unique_together

这个元数据是非常重要的一个!它等同于数据库的联合约束!

举个例子,假设有一张用户表,保存有用户的姓名、出生日期、性别和籍贯等等信息。要求是所有的用户唯一不重复,可现在有好几个叫“张伟”的,如何区别它们呢?(不要和我说主键唯一,这里讨论的不是这个问题)

我们可以设置不能有两个用户在同一个地方同一时刻出生并且都叫“张伟”,使用这种联合约束,保证数据库能不能重复添加用户(也不要和我谈小概率问题)。在Django的模型中,如何实现这种约束呢?

使用unique_together,也就是联合唯一!

比如:

1
unique_together = (('name', 'birth_day', 'address'),)

这样,哪怕有两个在同一天出生的张伟,但他们的籍贯不同,也就是两个不同的用户。一旦三者都相同,则会被Django拒绝创建。这一元数据经常被用在admin后台,并且强制应用于数据库层面。

unique_together接收一个二维的元组((xx,xx,xx,…),(),(),()…),每一个元素都是一个元组,表示一组联合唯一约束,可以同时设置多组约束。为了方便,对于只有一组约束的情况下,可以简单地使用一维元素,例如:

1
unique_together = ('name', 'birth_day', 'address')

indexes

Django1.11新增的选项。

接收一个应用在当前模型上的索引列表,如下例所示:

1
2
3
4
5
6
7
8
9
10
11
from django.db import models

class Customer(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)

class Meta:
indexes = [
models.Index(fields=['last_name', 'first_name']),
models.Index(fields=['first_name'], name='first_name_idx'),
]

模型继承

Django有三种继承的方式:

  • 抽象基类:被用来继承的模型被称为Abstract base classes,将子类共同的数据抽离出来,供子类继承重用,它不会创建实际的数据表;
  • 多表继承:Multi-table inheritance,每一个模型都有自己的数据库表;
  • 代理模型:如果你只想修改模型的Python层面的行为,并不想改动模型的字段,可以使用代理模型。

注意!同Python的继承一样,Django也是可以同时继承两个以上父类的!

抽象基类:

只需要在模型的Meta类里添加abstract=True元数据项,就可以将一个模型转换为抽象基类。Django不会为这种类创建实际的数据库表,它们也没有管理器,不能被实例化也无法直接保存,它们就是用来被继承的。抽象基类完全就是用来保存子模型们共有的内容部分,达到重用的目的。当它们被继承时,它们的字段会全部复制到子模型中。看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
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)

Student模型将拥有name,age,home_group三个字段,并且CommonInfo模型不能当做一个正常的模型使用。

如果子类没有声明自己的Meta类,那么它将继承抽象基类的Meta类

ps:

  • 抽象基类中有的元数据,子模型没有的话,直接继承;
  • 抽象基类中有的元数据,子模型也有的话,直接覆盖;
  • 子模型可以额外添加元数据;
  • 抽象基类中的abstract=True这个元数据不会被继承。也就是说如果想让一个抽象基类的子模型,同样成为一个抽象基类,那你必须显式的在该子模型的Meta中同样声明一个abstract = True
  • 有一些元数据对抽象基类无效,比如db_table,首先是抽象基类本身不会创建数据表,其次它的所有子类也不会按照这个元数据来设置表名。

多表继承

这种继承方式下,父类和子类都是独立自主、功能完整、可正常使用的模型,都有自己的数据库表,内部隐含了一个一对一的关系。

在多表继承的情况下,由于父类和子类都在数据库内有物理存在的表,父类的Meta类会对子类造成不确定的影响,子类除了orderingget_latest_by都不会继承父类的Meta功能。这一点和抽象基类的继承方式有所不同。

如果在多表继承中,你不想让你的子类继承父类的上面两种参数,就必须在子类中显示的指出或重写。如下:

1
2
3
4
5
class ChildModel(ParentModel):
# ...
class Meta:
# 移除父类对子类的排序影响
ordering = []

代理模型

只想更改模型在Python层面的行为,比如更改默认的manager管理器,或者添加一个新方法。你可以创建、删除、更新代理模型的实例,并且所有的数据都可以像使用原始模型(非代理类模型)一样被保存。不同之处在于你可以在代理模型中改变默认的排序方式和默认的manager管理器等等,而不会对原始模型产生影响。

声明一个代理模型只需要将Meta中proxy的值设为True。

  • 代理模型必须继承自一个非抽象的基类,并且不能同时继承多个非抽象基类;
  • 代理模型可以同时继承任意多个抽象基类,前提是这些抽象基类没有定义任何模型字段。
  • 代理模型可以同时继承多个别的代理模型,前提是这些代理模型继承同一个非抽象基类。

多重继承

注意,多重继承和多表继承是两码事,两个概念。

Django的模型体系支持多重继承,就像Python一样。如果多个父类都含有Meta类,则只有第一个父类的会被使用,剩下的会忽略掉。

一般情况,能不要多重继承就不要,尽量让继承关系简单和直接,避免不必要的混乱和复杂。