Search code examples
djangodjango-modelsdjango-rest-frameworkdjango-ormdjango-serializer

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


I have some Django models with different relations to each other — Many-to-many, and Foreignkey. By that means, I want to serialize them using djnago-rest.

Here are the models:

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)

Before, I used to use the following approach to create the JSON manually:

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
    }

My Question:

Now, how can I create such an above json via django-rest-framework instead?

I don't have any problem with many-to-many fields. In fact, my problem is the foreignkey(s).

Here's what I've done so far:

# 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)
]

Any help would be greatly appreciated.


Solution

  • The serializers.SerializerMethodField() is a useful method to add in relations like this. get_meta_data() is a bit of magic evaluating the fieldname to call the method.

    Address and port seem to be a simple relation and line.FOO should work.

    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