django DRF 一对多,多对多关系

小熊 2022年1月5日Python1 1,940 views54762字阅读15分52秒阅读模式

DRF中对数据表的关联关系操作起来也相当简单,这节展开讲讲

django 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)

反向查询也是支持的,所以说只需要定义到其中一个modelmanytomany就行了

取数据

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

weinxin
公众号
在号内与我交流,回复【资源】获取技术大礼包
Python最后更新:2022-9-8
小熊