Django Meta元数据类属性解析

Django Meta元数据类属性解析

Model 是 Django ORM 的核心,它有许多特性,比如我们提到过的模型类继承,还有未讲到过的的元数据。每个 Model 都是一个 Python 类,且通常会包含四个部分,它们分别如下:

继承自 django.db.model.Model;

Model 元数据声明;

Filed类型字段;

魔术方法__str__

除了元数据以外,其他三个部分我们在前面的章节都做了相应的介绍,在本节将详细讲解元数据 Meta 类属性。

1. 初识Meta内部类

每个模型类(Model)下都有一个子类 Meta,这个子类就是定义元数据的地方。Meta 类封装了一些数据库的信息,称之为 Model 的元数据。Django 会将 Meta 中的元数据选项定义附加到 Model 中。常见的元数据定义有 db_table(数据表名称)、abstract(抽象类) 、ordering(字段排序) 等,Meta 作为内部类,它定义的元数据可以让admin 管理后台对人类更加友好,数据的可读性更高。

Meta 定义的元数据相当于 Model 的配置信息,所以我们可以根据自己的需求进行选择性的添加。当没有需要的时候也可以不定义 Meta,这个时候 Django 会应用默认的 Meta 元数据。

2. Meta类元数据

通过上面的介绍我们知道 Meta 类的作用就是用于定义 Model 的元数据,即不属于 Model 的字段,但是可以用来标识字段一些属性,下面我们介绍 Meta 定义的常见元数据以及如何在 Model 中使用它们。

1) abstract

一个布尔类型的变量。这个属性是定义当前的模型是不是一个抽象类。所谓抽象类是不会对应数据库表的。一般我们用它来归纳一些公共属性字段,然后继承它的子类可以继承这些字段。如果 abstract = True 这个 model 就是一个抽象类。
比如下面的代码中Human是一个抽象类,Employee是一个继承了Human的子类,那么在运行syncdb命令时,不会生成Human表,但是会生成一个Employee表,
它包含了Human中继承来的字段,以后如果再添加一个Customer模型类,它可以同样继承Human的公共属性:

class Human (models. Model ):
name =models. CharField (max_length = 100 )
GENDER_CHOICE = ( (u 'M' ,u 'Male' ) , (u 'F' ,u 'Female' ) , )
gender =models. CharField (max_length = 2 ,choices =GENDER_CHOICE ,null = True )
class Meta:
abstract = True
class Employee (Human ):
joint_date =models. DateField ( )
class Customer (Human ):
first_name =models. CharField (max_length = 100 )
birth_day =models. DateField ( )

上面的代码,执行python manage.py syncdb 后的输出结果入下,可以看出Human表并没有被创建:

$ python manage.py syncdb
Creating tables ...
Creating table myapp_employee
Creating table myapp_customer
Installing custom SQL ...
Installing indexes ...
No fixtures found.

2) ordering

用于执行获取对象列表时的排序规则。它是一个字符串的列表或元组对象,它的使用格式是由代表字段的字符串和一个表明降序的’-‘构成。当字段名前面没有’-‘时,将默认使用升序排列。使用’?'将会随机排列。示例如下所示:

ordering=["add_time"] #按照升序排序
ordering=["-add_time"]#按照降序
ordering=["?add_time"]#随机排序
#同时指定多个字段来进行排序
ordering=['add_time','-last_login_time']#先按升序,在按降序

3) verbose_name_plural

这个元数据主要用在管理后台的展示上,verbose_name_plural 是模型类的复数名 。如果不设置的话,Django 会使用小写的模型名作为默认值,并且在结尾加上 s。通过此项元数据设置名字可以去掉 s。

4) db_table

这个字段用于指定数据表的名称,通常没有特别需求,将按照 Django 默认的规则生成 Model 对应的数据库表名。

定义该model在数据库中的表名称

db_table = 'Students'

使用自定义的表名,可以通过以下属性

table_name = 'my_owner_table'

5) app_lable

这个选项只在一种使用情形,就是你的模型不在默认的应用程序包下的 models.py 文件中,这时候需要指定你这个模型是哪个应用程序的
比如你在其他地方写了一个模型类,而这个模型类是属于myapp的,那么你这是需要指定为:

app_label = ‘app_name’。

6) managed

它是一个布尔类型的变量,默认为 Ture,代表 Django 会管理数据的生命周期,即利用 Django 提供的 syncdb 和 reset 命令可以完成创建和删除数据表。如果为 False,则不会对此模型执行数据库表创建或删除操作。比如数据表之间存在 ManyToMany 的关系,在指定为 managed=False 的情况下,Django 不会自动创建中间表,需要我们自己手动创建。

7) indexs

它是一个列表类型的元数据项,用来定义 Model 的索引,列表中的每一个元素都是 Index 类型的实例。
Index 引自 django.db.models.indexes.Index

8) default_permissions

Django 默认会给每一个定义的 Model 设置三个权限即添加、更改、删除,它使用格式:

default_permissions=('add','change','delete','view')

9) permissions

除了 Django 默认给 Model 添加的三个权限之外,还可以通过 permisssions 给 Model 添加额外的权限。不过 permissions 是一个包含二元组的元组或者列表,所以使用时应该注意格式,即 permissions=[(权限代码,权限名称)],示例如下所示:

permissions = [(have_read_permission', '有读的权限')]

10) unique_together

这个选项用于下面情形:当你需要通过两个字段保持唯一性时使用。比如用户的姓名(name)和 身份证号码(ID number)两者的组合必须是唯一的,那么需要这样设置:

unique_together = (("first_name", "last_name"),)

一个 ManyToManyField 不能包含在 unique_together 中。如果你需要验证 ManyToManyField 字段的唯一验证,尝试使用 through 属性进行关联。

11) proxy

默认值为为 False, 如果设置成 Ture,则表示为基类、父类的代理模型。这个选项在后续章节还会进行相关介绍,它的主要作用就是创建父模型的代理模型。

12) db_tablespace

表空间,用于优化数据库性能,常用于 Oracle、PostgerSQL 数据库。MySQL 数据库不支持表空间,所以当数据存储后端数据库不支持的时候,Django 会在自动忽略这个元数据选项。

13) get_latest_by

由于Django的管理方法中有个lastest()方法,就是得到最近一行记录。
如果你的数据模型中有 DateField 或 DateTimeField 类型的字段,你可以通过这个选项来指定lastest()是按照哪个字段进行选取的。
指定一个 DateField 或者 DateTimeField 字段的名字,即 model 的属性名字。使用示例如下:

get_latest_by = "order_date"

这个设置让你在使用模型管理器的 lastest() 方法时,默认使用order_date指定字段来排序。

14) order_with_respect_to

这个选项一般用于多对多的关系中,它指向一个关联对象。就是说关联对象找到这个对象后它是经过排序的。
使用元数据项后你会得到一个 get_xxx_order() 和set_xxx_order() 的方法,通过它们你可以设置或者得到排序的对象。

15) verbose_name 指明一个易于理解和表述的对象名称。如果这个值没有设置, Django 将会使用该 model 的类名的分词形式作为他的对象表述名: CamelCase 将会转换为 camel case 。 :

verbose_name = "pizza"

一些常见的元信息的例子:

class Meta:
# 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
db_table = "table_name"
# 联合索引
index_together = [
("pub_date", "deadline"),
]
# 联合唯一索引
unique_together = (("driver", "restaurant"),)
# admin中显示的表名称
verbose_name = 'my_app'
# verbose_name加s
verbose_name_plural = verbose_name

多表关系和参数的例子:

ForeignKey(ForeignObject) # ForeignObject(RelatedField)
to, # 要进行关联的表名
to_field=None, # 要关联的表中的字段名称
on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为
- models.CASCADE,删除关联数据,与之关联也删除
- models.DO_NOTHING,删除关联数据,引发错误IntegrityError
- models.PROTECT,删除关联数据,引发错误ProtectedError
- models.SET_NULL,删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
- models.SET_DEFAULT,删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
- models.SET,删除关联数据,
a. 与之关联的值设置为指定值,设置:models.SET(值)
b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
def func():
return 10
class MyModel(models.Model):
user = models.ForeignKey(
to="User",
to_field="id"
on_delete=models.SET(func),)
related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件:
# 如:
- limit_choices_to={'nid__gt': 5}
- limit_choices_to=lambda : {'nid__gt': 5}
from django.db.models import Q
- limit_choices_to=Q(nid__gt=10)
- limit_choices_to=Q(nid=8) | Q(nid__gt=10)
- limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
db_constraint=True # 是否在数据库中创建外键约束
parent_link=False # 在Admin中是否显示关联数据
OneToOneField(ForeignKey)
to, # 要进行关联的表名
to_field=None # 要关联的表中的字段名称
on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为
###### 对于一对一 ######
# 1. 一对一其实就是 一对多 + 唯一索引
# 2.当两个类之间有继承关系时,默认会创建一个一对一字段
# 如下会在A表中额外增加一个c_ptr_id列且唯一:
class C(models.Model):
nid = models.AutoField(primary_key=True)
part = models.CharField(max_length=12)
class A(C):
id = models.AutoField(primary_key=True)
code = models.CharField(max_length=1)
ManyToManyField(RelatedField)
to, # 要进行关联的表名
related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件:
# 如:
- limit_choices_to={'nid__gt': 5}
- limit_choices_to=lambda : {'nid__gt': 5}
from django.db.models import Q
- limit_choices_to=Q(nid__gt=10)
- limit_choices_to=Q(nid=8) | Q(nid__gt=10)
- limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
symmetrical=None, # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段
# 做如下操作时,不同的symmetrical会有不同的可选字段
models.BB.objects.filter(...)
# 可选字段有:code, id, m1
class BB(models.Model):
code = models.CharField(max_length=12)
m1 = models.ManyToManyField('self',symmetrical=True)
# 可选字段有: bb, code, id, m1
class BB(models.Model):
code = models.CharField(max_length=12)
m1 = models.ManyToManyField('self',symmetrical=False)
through=None, # 自定义第三张表时,使用字段用于指定关系表
through_fields=None, # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=50)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(
Person,
through='Membership',
through_fields=('group', 'person'),
)
class Membership(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
inviter = models.ForeignKey(
Person,
on_delete=models.CASCADE,
related_name="membership_invites",
)
invite_reason = models.CharField(max_length=64)
db_constraint=True, # 是否在数据库中创建外键约束
db_table=None, # 默认创建第三张表时,数据库中表的名称

注意事项:

null和blank的区别:

null纯粹与数据库相关,blank与验证相关。
null在数据库中表示为空字符串。
CharField同时具有unique = True和blank = True。在这种情况下,需要null = True以避免在使用空值保存多个对象时发生唯一约束违规。

upload_to:上传路径

django在upload_to上内置了strftime()函数:
eg.:avatar = ImageField(upload_to = 'avatar/%Y/%m/%d/')

form中属性的名字和html文件中input标签中定义的名字必须一样.

注册流程:

register_view -> send_registr_email -> 得到激活链接 -> 进入激活链接 -> active_view ->将该用户的is_activate字段设为True

提交表单:

html页面中form标签,有method字段为“post”,action属性“{% url 'url_name' %}

render()函数

必选参数:

request: 用于生成此响应的请求对象。
template_name: 要使用的模板的全名或模板名称的序列。如果给定一个序列,则将使用存在的第一个模板。有关如何查找模板的更多信息,请参见 template loading documentation 。

可选参数:

context:要添加到模板上下文的值的字典。 默认情况下,这是一个空的字典。 如果字典中的值是可调用的,则视图将在渲染模板之前调用它。
content_type: 用于结果文档的MIME类型默认为:设置:setting:DEFAULT_CONTENT_TYPE 设置的值。
status:响应的状态代码默认为“200”。
using:用于加载模板的模板引擎的 :setting:`NAME ` 。
eg:
from django.shortcuts import render
def my_view(request):
# View code here...
return render(request, 'myapp/index.html',
{'foo': 'bar',}, content_type='application/xhtml+xml')
相当于:
from django.http import HttpResponse
from django.template import loader
def my_view(request):
# View code here...
t = loader.get_template('myapp/index.html')
c = {'foo': 'bar'}
return HttpResponse(t.render(c, request), content_type='application/xhtml+xml')

返回一个带有外键关系的QuerySet,在执行查询时可以选择其他关联对象的数据,这是一个性能增强,这样可以做更复杂的查询,而且意味着以后使用外键关系不需要再次做数据库查询。
下面的例子解释了普通查询和select_related()查询的区别, 下面是一个标准查询:

# 访问数据库
e = Entry.objects.get(id=1)
# 在此访问数据库以得到关联的Blog对象(外键)
b = e.blog

select_related()查询:

# 访问数据库
e = Entry.objects.select_related('blog').get(id=1)
# 不会访问数据库,因为e.blog已经在前面的查询中预先填充
b = e.blog

select_related()可以用于objects的所有查询集:

from django.uils import timezone
# 查询所有计划在未来发布的博客
blogs = set()
for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog'):
# 如果没有select_related(),每一次循环都会查询一次数据库去取每一个entry关联的blog
blogs.add(e.blog)
filter()和select_related()的顺序无所谓,下面两条查询是一样的:
Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog')
Entry.objects.select_related('blog').filter(pub_date__gt=timezone.now())

可以用类似的方式查询外键,例如下面的models:

from django.db import models
class City(models.Model):
# ...
pass
class Person(models.Model):
# ...
hometown = models.ForeignKey(
City,
on_delete=models.SET_NULL,
blank=True,
null=True,
)
class Book(models.Model):
# ...
author = models.ForeignKey(Person, on_delete=models.CASCADE)

然后这条查询Book.objects.select_related('author__hometown').get(id=4)会缓存关联的Person和与Person关联的City。

通过与author和hometown表的连接访问数据库,缓存好author和hometown

b = Book.objects.select_related('author__hometown').get(id=4)
p = b.author # 不会访问数据库
c = p.hometown # 不会访问数据库
# Without select_related()...
b = Book.objects.get(id=4) # Hits the database.
p = b.author # 访问数据库
c = p.hometown # 访问数据库

你可以在传递给select_related()的字段列表中引用任何ForeignKey或OneToOneField关系。

如果需要清除QuerySet上以前的select_related调用添加的关联字段,可以传递一个None作为参数:

without_relations = queryset.select_related(None)

select_related可以链式调用,也就是说,select_related('foo', 'bar') 等同于 select_related('foo').select_related('bar')

select_related和prefetch_related的用法与区别

1. 介绍

select_related:

将会根据外键关系(注意: 仅限单对单和单对多关系),在执行查询语句的时候通过创建一条包含SQL inner join操作的SELECT语句来一次性获得主对象及相关对象的信息

prefetch_related

对于多对多字段,你不能使用select_related方法,这样做是为了避免对多对多字段执行JOIN操作从而造成最后的表非常大。

Django提供了prefect_related方法来解决这个问题。

prefect_related可用于多对多关系字段,也可用于反向外键关系(related_name)。

相同点:

都作用于queryset对象上面

注意点:

对与单对单或单对多外键ForeignKey字段,使用select_related方法

对于多对多字段和反向外键关系,使用prefetch_related方法

两种方法均支持双下划线指定需要查询的关联对象的字段名

使用Prefetch方法可以给prefetch_related方法额外添加额外条件和属性。

2. 使用

from django.db import models
class UserInfo(models.Model):
username = models.CharField(verbose_name='用户名', max_length=225)
def __str__(self):
return self.username
class Tag(models.Model):
name = models.CharField(verbose_name='标签名称', max_length=225)
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField(verbose_name='标题', max_length=225)
content = models.CharField(verbose_name='内容', max_length=225)
# 外键
username = models.ForeignKey(verbose_name='用户', to='UserInfo', on_delete=models.DO_NOTHING)
tag = models.ManyToManyField(verbose_name='标签', to='Tag')
def __str__(self):
return self.title

2.1 原生的查询

2.1.1 代码

def article_list(request):
if request.method == 'GET':
# select_related---->queryset
article_queryset = models.Article.objects.all()
return render(request, 't2.html', context={'article_queryset': article_queryset})

2.1.2 图示

2.1.3 查询解释

1.从图示我们可以看出来,一共进行13次查询,且有10次重复的!!!

原因是:当我们第一次查询时,返回的值,只有文章对象,对于标签以及用户,并没有查询,当前端界面需要这两个时,每循环一次,就会去数据库查询一次

2.为了避免重复查询,django提供select_related和prefetch_related方法来提升数据库查询效率,类似于SQL的JOIN方法。
3.效果就是当第一次查询时,进行连表,一次性把所有数据全部查询到

2.2.2 代码

from django.shortcuts import render
from blog import models
def article_list(request):
if request.method == 'GET':
# select_related---->queryset
article_queryset = models.Article.objects.all().select_related('tag', 'username')
return render(request, 't2.html', context={'article_queryset': article_queryset})

2.2.3 图示

2.2.4 解释

可以看到现在只有三次查询,耗时大大减少

2.2.5 其他常用用法

# 获取id=1的文章对象同时,获取其相关username信息
Article.objects.select_related('username').get(id=1)
# 获取id=1的文章对象同时,获取其相关作者名字信息
Article.objects.select_related('username__username').get(id=1)
# 获取id=1的文章对象同时,获取其相关tag和相关作者名字信息。下面方法等同。
# 方式一:
Article.objects.select_related('tag', 'username__username').get(id=1)
# 方式二:
Article.objects.select_related('tag').select_related('username__username').get(id=1)
# 使用select_related()可返回所有相关主键信息。all()非必需。
Article.objects.all().select_related()
# 获取Article信息同时获取username信息。filter方法和selected_related方法顺序不重要。
# 方式一:
Article.objects.filter(tag__gt=3).select_related('username')
# 方式二:
Article.objects.select_related('username').filter(tag__gt=3)

2.3. 使用prefetch_related方法

对于多对多字段,你不能使用select_related方法,这样做是为了避免对多对多字段执行JOIN操作从而造成最后的表非常大。

2.3.1 常用的案例

articles = Article.objects.all().select_related('category').prefecth_related('tags')
# 文章列表及每篇文章的tags对象名字信息
Article.objects.all().prefetch_related('tags__name')
# 获取id=13的文章对象同时,获取其相关tags信息
Article.objects.prefetch_related('tags').get(id=13)
# 获取文章列表及每篇文章相关的名字以P开头的tags对象信息
Article.objects.all().prefetch_related(
Prefetch('tags', queryset=Tag.objects.filter(name__startswith="P"))
)
# 文章列表及每篇文章的名字以P开头的tags对象信息, 放在article_p_tag列表
Article.objects.all().prefetch_related(
Prefetch('tags', queryset=Tag.objects.filter(name__startswith="P")),
to_attr='article_p_tag'
)
posted @   冀未然  阅读(99)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示