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')
select_related()
返回一个带有外键关系的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 使用select_related
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' )
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了