持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情
前面的网站用的都是Python的django编写的,模型作为django的数据核心,是开发过程中必不可少的一环。这里谈谈对模型使用的感悟。
一、模型基础
django中的模型一般是一个模型类,由字段,元数据选项和方法组成。
示例
其中id、create_date等是字段,id这个字段如果不定义,又没有指定其他字段为primary_key的话,系统会默认为表生成一个用整形的id主键。
下方class Meta被称为元数据选项,常用于配置一些名称、数据表、多字段的不重复键这种信息
中间的方法相当于是类方法,和类的功能是基本一致的,可以重写一些models.model原有的方法,也可以自己写些普通方法、魔法方法、属性方法等,都是可以的。
二、虚拟模型
虚拟模型指的是在class Meta中用了abtract = True的属性,此时不设置其他选项,这个模型是。
虚拟模型一般用于给其他模型继承,用于定义一些公共字段和处理方法。
例如可以把上一节的id和create_date以及id的保存方法放到一个虚拟基类中,以后其他的模型就可以继承这个基类,而不需要自己再添加这些字段。
值得注意的是,目前django不允许子类重新定义普通视图模型类中相同的字段,但可以重新定义继承自虚拟基类中的相同字段。
三、非管理模型
在元数据选项中设置managed = False可以指定这个模型的字段不由这个程序管理。通俗易懂的解释就是你自己的表已经存在了,如果你还定义了这个模型,那么执行迁移必然是失败的,此时要么把模型的迁移信息自己构造出来,要么就用这个选项来脱离托管。
构造这个迁移信息可以先借用另外一个数据库表名进行迁移,再改动所在模块里migrations文件夹对应的那个文件的内容,文件夹里的文件名是要和数据库里的django_migrations表格对应的,否则系统运行中会提示迁移不一致。
四、实现数据库中group by的语法
django中有order_by的排序操作,但却没有groupby这个分组操作。在mysql中这个group by有个隐含的语法是select的选择的非聚合的字段必须是group by语句中存在的,但在django中使用raw来执行原生sql语句,却又要必须包含主键列,等你把主键列加进去,他拿到的结果就又不是需要的了。
那我们该如何分组呢?
答案是使用annotate,annotate本意是指的取别名,常用于聚合函数操作某个列,如:
.annotate(ip__count=Count('ip'))
此时取出来的数据就是
如果在聚合函数前不取别名,系统自己会设置一个别名,这个别名和我们当前设置的是一样的,设置的规则就是字段名称加双下划线加聚合函数的小写名称。
此时对于模型而言只是计算了个聚合的值,分组还需要确定对哪个字段执行groupby操作,思考自己想要那种操作得到什么信息,在聚合之前通过values方法取分组的字段。
objs = IPModel.objects.all().values('application_label').annotate(Count('ip'))
这句话就相当于数据库中的
select application_label,count(ip) from ipmodel group by application;
得到的结果就是这两个字段的查询集
如果在编写代码中发现objects是标灰色的话,在模型类中加上 objects=models.Manager()
五、模型筛选和操作中双下划线的使用方法
双下划线我们可以将其看作为一个取值或者方法的过程,类似于常规操作中的点号(.),常见于filter操作中进行条件的筛选。
比如对字段接操作符过滤值:
objs = IPModel.objects.all().filter(expire_date__gte=datetime.datetime.now())
gte是大于等于的意思,来源于英文的greater than equal,相对的还有小于等于lte,less than equal。
如果模型中有外键的话,还可以通过双下划线取外键的模型的属性,这是可以多层级联的。
六、复制模型
怎么复制一个模型,这个问题以前也困扰了我很久,比如我想要增加一些数据,但这个数据只有一个属性是不一样的,如果重新创建的话,我每次复制的时候要输入很多的数据,这很不合理。特别是当我想在一个层级比较高表里添加一条数据,然后想让他关联的数据和表里另外一条数据是一样的,相当于重新复制一份,这就很麻烦。后来查询了官方文档后发现,模型还有个隐藏属性_state,主要用于判断这个模型是新增还是修改。
此时我们把主键置为None,再把新增置为True,再保存时就是个新的模型了,中间可以加上我们修改字段的过程。
但这个方式不能对部分类型的关联关系进行复制,需要重新添加。
重新修改前面的模型和数据进行演示,修改后模型的标签被提取为外键进行管理。
描述已自动生成]()\
先演示上一节下划线的使用
objs = IPModel.objects.all().values('label').annotate(Count('ip')).values('label__label_name', 'ip__count')
print(objs)
此时可以看到,模型本来是通过标签字段分组的,但通过后缀values内的双下划线属性label__label_name,取到标签的名称。
再演示下复制模型
测试的视图函数
def sql_test2(request):
obj: IPModel = IPModel.objects.all().first()
obj.pk = None
obj._state.adding = True
obj.ip = '114.114.114.114'
obj.save()
print(obj.label.label_name)
return JsonResponse(data={})
运行前的数据库
打印的结果还是有label的
数据库是这样的
反过来我们复制下标签呢?
测试的视图函数:
def sql_test3(request):
obj: LabelModel = LabelModel.objects.all().first()
obj.pk = None
obj._state.adding = True
obj.label_name = '测试使用'
obj.save()
print(obj.ips)
return JsonResponse(data={})
标签列表状态
此时打印的就是None
经过对比,再结合官方文档的示例,我们可以总结出这样的特征,关联关系的复制,是基于不破坏原有关系为前提的:
1.一对多的关联关系中,多端复制模型是不会损失外键关系的,因为复制出来的模型的外键不会因为多了一个而改变原有数据的语义和语法,一端复制出的模型因为它原本对应的数据集合不可能多一个外键,所以是默认置空的。
2.多对多的关联关系中,复制模型都会损失关联关系,可以认为是这样考虑的:多对多一般存于第三张表,如果复制模型就要将原模型的关系也连过来的话,势必会造成数据的改变,所以默认置空。
3.一对一的关联关系中复制一个模型,还需要原来模型的关联对象拷贝,因为大家都是一对一的,你的复制不能变成2对1了。