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
}
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.
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