如何使用 DRF 序列化 Django 中的一些嵌套关系模型?

How to serialize some nested relational models in django using DRF?

我有一些彼此之间关系不同的 Django 模型 — Many-to-manyForeignkey。通过这种方式,我想使用 djnago-rest.

序列化它们

以下是模型:

class CommonFieldsAbstract(models.Model):
    name = models.CharField(max_length=30, unique=True)

class ServerModel(CommonFieldsAbstract):
    server_ip = models.GenericIPAddressField(default='172.17.0.1')
    server_port = models.IntegerField(default='9001')

class SNMPLineModel(CommonFieldsAbstract):
    ip_address = models.GenericIPAddressField()
    port = models.IntegerField(default=161)

class SNMPModel(CommonFieldsAbstract):  # target
    line = models.ForeignKey(SNMPLineModel, on_delete=CASCADE)
    servers = models.ManyToManyField(ServerModel)

class MetaDataModel(models.Model):
    key = models.CharField(max_length=20)
    value = models.CharField(max_length=20)
    snmp_device = models.ForeignKey(SNMPModel, on_delete=CASCADE)

之前,我使用以下方法手动创建 JSON:

def meta_data_json(meta_data):
    meta_data_list = []
    for meta in meta_data:
        meta_data_list.append({
            meta.key: meta.value
        })
    return meta_data_list

def server_json(servers):
    return [{'ip': server.server_ip,
             'port': server.server_port}
            for server in servers]

def create_json():
    snmp = SNMPModel.objects.filter(name__contains='a-name')
    return {
        'name': snmp.name,
        'address': snmp.line.ip_address,
        'port': snmp.line.port,
        'servers': server_json(snmp.servers.all()),
        'meta_data': meta_data_json(MetaDataModel.objects.filter(
                snmp_device=snmp.pk
            )
        ),
        'device_pk': snmp.pk
    }

我的问题:

现在,我怎样才能通过 django-rest-framework 创建这样一个上面的 json?

我对多对多字段没有任何问题。事实上,我的问题是 foreignkey(s).

这是我到目前为止所做的:

# serializers.py

from rest_framework import serializers

class MetaDataSerializer(serializers.ModelSerializer):
    class Meta:
        fields = [
            'id',
            'key',
            'value',
            ]
        model = MetaDataModel

class ServerSerializer(serializers.ModelSerializer):
    class Meta:
        fields = [
            'id',
            'server_ip',
            'server_port',
            ]
        model = ServerModel

class LineSerializer(serializers.ModelSerializer):
    port = serializers.RelatedField(many=True)

    class Meta:
        fields = '__all__'
        model = SNMPLineModel

class SNMPSerializer(serializers.ModelSerializer):
    servers = ServerSerializer(many=True, read_only=True)  # It is ok
    meta_data = MetaDataSerializer(many=True, read_only=True)  # It's not ok
    line = LineSerializer(many=True, read_only=True)  # It's not ok
    address = serializers.CharField(source=SNMPLineModel.ip_address)  # It's not ok
    port = serializers.CharField(source=SNMPLineModel.port)  # It's not ok

    class Meta:
        fields = [
            'id',
            'servers',
            'name',
            'address',
            'port',
            'line',
            'meta_data'
            ]
        model = SNMPModel
# views.py

from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse, JsonResponse

@csrf_exempt
def snippet_detail(request, name):
    try:
        snmp_conf = SNMPModel.objects.filter(name__contains=name)
    except SNMPModel.DoesNotExist:
        return HttpResponse(status=404)

    if request.method == 'GET':
        serializer = SNMPSerializer(snmp_conf, many=True)
        return JsonResponse(serializer.data, status=200, safe=False)
# urls.py

from django.urls import path

urlpatterns = [
    path('snippets/<name>/', views.snippet_detail)
]

如有任何帮助,我们将不胜感激。

serializers.SerializerMethodField() 是添加这样的关系的有用方法。 get_meta_data() 评估字段名以调用方法有点神奇。

地址和端口似乎是一个简单的关系,line.FOO 应该可以。

class SNMPSerializer(serializers.ModelSerializer):
    servers = ServerSerializer(many=True, read_only=True)  # It is ok
    meta_data = serializers.SerializerMethodField()
    line = serializers.SerializerMethodField()
    address = serializers.CharField(source="line.ip_address", read_only=True)
    port = serializers.CharField(source="line.port" , read_only=True)

    class Meta:
        fields = ['id', 'servers', 'name', 'address', 'port', 'line', 'meta_data']
        model = SNMPModel

    def get_meta_data(self, instance):
        metadatamodels = MetaDataModel.objects.filter(snmp_device=instance)
        serializer = MetaDataSerializer(instance=metadatamodels, many=True, read_only=True)

        return serializer.data

    def get_line(self, instance):
        serializer = LineSerializer(instance.line, read_only=True)

        return serializer.data

为了解决上述问题,我做了以下解决方案:

  1. 对于前向外键,您不需要 many=True
  2. 对于反向外键,您需要在尚未定义 related_name.
  3. 时使用 related-model_set
  4. 对于额外的字段,您只需要确切的查询集。

因此,我得到了以下代码片段,而没有像 Michael 在他的回答中使用的那样使用 .SerializerMethodField()

class SNMPSerializer(serializers.ModelSerializer):
    servers = ServerSerializer(many=True)
    meta_data = MetaDataSerializer(many=True, source="metadatamodel_set")
    line = LineSerializer()
    address = serializers.CharField(source="line.ip_address")
    port = serializers.CharField(source="line.port")

    class Meta:
        fields = (
            "id",
            "servers",
            "name",
            "address",
            "port",
            "line",
            "meta_data",
        )
        model = SNMPModel