django DRF 一对多,多对多关系

小熊 2022年1月5日Python1 118 views53026字阅读10分5秒阅读模式

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):
    tags = TagsReadOnly(many=True)

    class Meta:
        model = PostModel
        fields = '__all__'

    def to_internal_value(self, data):
        return data

    def create(self, validated_data):
        tags_data = validated_data.pop('tags')
        post = PostModel.objects.create(**validated_data)
        tags = [TagModel.objects.get(
            pk=id) for id in tags_data]
        post.tags.set(tags)
        post.save()
        return post

其中 to_internal_value 方法用于验证前端传入的数据(上述代码中不做任何验证);create 方法用于新增或更新后台数据。

此时再向 posts 接口 POST 或 PUT 数据时,就可以使用如下格式:

{
    "tags": [1, 2, 3],
    "title": "An interesting 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')

取数据还可以看看这个

引用

Django REST framework 模型中 Many-to-many 关系的序列化
django:Many-to-many关系
How to combine select_related() and value()? (2016)
django-queries

weinxin
公众号
在号内与我交流,回复【资源】获取技术大礼包
小熊