DRF中对数据表的关联关系操作起来也相当简单,这节展开讲讲
一对多
一对多的关联关系,实际上就是靠外键来维护的
from appxx.orm2rest.xxx import Xxx
class Question(models.Model):
user = models.ForeignKey(Xxx, on_delete=models.CASCADE, null=True,
verbose_name='xxx', related_name='child_menus')
- 外键会自动追加到字段为关联表的主键,比如上面,存到数据库里就是
user_id
如果model
里配置了外键,可以使用外键字段关联查询,在filter
里这样配置就可以传入自动过滤了
user_name = filters.CharFilter(field_name="user_id__username")
user_name__icontains = filters.CharFilter(field_name="user_id__username", lookup_expr="icontains")
- 如上使用外键
user
表中的username
查询 - 不需要在
fields
字段里做任何配置
也可以取外键表的某些字段或者完整的数据关联查询出来
class XxxSerializer(serializers.ModelSerializer):
user_name = serializers.CharField(source="user.username", read_only=True)
多对多
比如下例定义方式会导致自动建立一个中间表
department = models.ManyToManyField(Department)
多对多查询过滤
在查询里这样配置可以查询关联关系,把关联的全部列出来
'department': ['exact'],
多对多api定制
也可以定义一个API在ViewSet
类里表展示多对多关联内容
def get_platform_by_department(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
多对多返回
直接查询会返回多表的主键列表,like this
{
"id": 1,
"tags": [
1,
3
]
}
如果想将关联表合并到查询记录里返回就定制序列化类,like this
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = TagModel
fields = '__all__'
class TagsReadOnly(serializers.ModelSerializer):
class Meta:
model = TagModel
fields = ['id', 'tag']
class PostSerializer(serializers.ModelSerializer):
tags = TagsReadOnly(many=True)
class Meta:
model = PostModel
fields = '__all__'
效果
{
"id": 1,
"tags": [
{
"id": 1,
"tag": "Python"
},
{
"id": 3,
"tag": "Linux"
}
]
}
多对多关系创建/更新数据(一对多、一对一类似)
如果使用上面的返回,此时若使用 POST 或 PUT 方法新增或更新数据,会报出如下错误(即在新增或修改 post 的同时,会尝试创建关联的 tags,但这些 tags 本就已经存在,从而导致冲突):
{
"tags": [
{
"tag": [
"tag model with this tag already exists."
]
}
]
}
为了使 posts 接口在接收数据时支持列表类型的 tags(类似 "tags": [1, 2, 3]
这种)且能够成功更新,可以选择覆盖 PostSerializer 的 to_internal_value 和 create 方法:
from rest_framework import serializers
from blogs.models import TagModel, PostModel
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = TagModel
fields = '__all__'
class TagsReadOnly(serializers.ModelSerializer):
class Meta:
model = TagModel
# 定义展开后展示哪些字段
fields = ['id', 'tag']
class PostSerializer(serializers.ModelSerializer):
# 这里如果是 fields = ['xx'] 指定了 tags那必须要加这个,不然无法成功展开关联的tags
tags = TagsReadOnly(many=True)
class Meta:
model = PostModel
fields = '__all__'
def to_internal_value(self, data):
return data
def create(self, validated_data):
tags = None
if 'tags' in validated_data:
tags_data = validated_data.pop('tags')
tags = [TagModel.objects.get(pk=id) for id in tags_data]
post = PostModel.objects.create(**validated_data)
post.tags.set(tags)
post.save()
return post
def update(self, instance, validated_data):
if 'tags' in validated_data:
tags_data = validated_data.pop('tags')
posts = [Post.objects.get(pk=id) for id in tags_data]
instance.tags.set(posts)
return super().update(instance,validated_data)
其中 to_internal_value 方法用于验证前端传入的数据(上述代码中不做任何验证),create
方法用于新增或更新后台数据。
此时再向 posts 接口 POST 或 PUT 数据时,就可以使用如下格式:
{
"tags": [1, 2, 3],
"title": "An interesting post"
}
但是覆盖了to_internal_value
方法以后所有的验证器都会失效,解决办法参考django 字段验证器/DRF#小心失效
参考 : Serializer
多对多关系复制(一对多、一对一类似)
依然是要重写 Serializer
中的方法, instance
方法类似
def create(self, validated_data):
xxx = None, None, None
if 'xxx' in validated_data and validated_data['xxx']:
mttr = MTTR.objects.get(id=validated_data['xxx'])
del validated_data['xxx']
post = Post.objects.create(**validated_data)
post.xxx = xxx
post.save()
return post
多对多关联关系维护
分别保存
pxx1.save()
pxx2.save()
axx1.save()
axx1.pxx.add(pxx1,pxx2)
axx1.save()
直接创建
axx1.pxx.create(title='xx')
axx1.save()
````
过滤
```python
Axx.object.filter(pxx=1)
Axx.object.filter(pxx__id_exact=1)
反向查询也是支持的,所以说只需要定义到其中一个model
里manytomany
就行了
取数据
MyModel.objects.values('author__id', 'author__name')
取数据还可以看看这个
跨关联查询
>>> Restaurant.objects.get(place=p1)
<Restaurant: Demon Dogs the restaurant>
>>> Restaurant.objects.get(place__pk=1)
<Restaurant: Demon Dogs the restaurant>
>>> Restaurant.objects.filter(place__name__startswith="Demon")
<QuerySet [<Restaurant: Demon Dogs the restaurant>]>
>>> Restaurant.objects.exclude(place__address__contains="Ashland")
<QuerySet [<Restaurant: Demon Dogs the restaurant>]>
反向查询
>>> Place.objects.get(pk=1)
<Place: Demon Dogs the place>
>>> Place.objects.get(restaurant__place=p1)
<Place: Demon Dogs the place>
>>> Place.objects.get(restaurant=r)
<Place: Demon Dogs the place>
>>> Place.objects.get(restaurant__place__name__startswith="Demon")
<Place: Demon Dogs the place>
详见:https://docs.djangoproject.com/zh-hans/4.0/topics/db/examples/one_to_one/
删除
在多对多,一对多的情况下,中间表,或维护表中会自动设置外键,无法对被关联关系进行删除,必须先删除外键所关联的记录。再通过配置的级联删除的办法进行清理,SET_NULL
或者 CASCADE
联动删除。
如果不想通过关联关系删除,可以手动设置解除绑定后再删除,重写删除方法
if self.menu_set.count() > 0:
return "Can not delete this menu"
return super().destroy(request, *args, **kwargs)
Q&A
- 有的人想用JSONField在存储一对多关系,这不会同步更新,建议不要用这种方式,可以在序列化方法中修改输出内容,屏蔽某些字段,其实直接输出是会级联查询出来详细内容的
引用
Django REST framework 模型中 Many-to-many 关系的序列化django:Many-to-many关系
How to combine select_related() and value()? (2016)
django-queries
评论