Django - serializing multiple rows to a list

Viewed 202

I have three tables Vertex, Edge, Graph

  1. Vertex can be part of many graphs. can reoccur in the same graph.
  2. Edge table denotes the edge between any two vertices in a graph.

Models

class Graph(models.Model):
    id = models.TextField(
        primary_key=True, null=False)
    name = models.TextField()

class Vertex(models.Model):
    id = models.TextField(
        primary_key=True, null=False)
    name = models.TextField()
    extras = models.JsonField()
 
class Edge(models.Model):
    graph = models.ForeignKey(
        "Graph", related_name="edges")
    node = models.ForeignKey(
        "Vertex", related_name="g_vertex")
    dependency = models.ForeignKey(
        "Vertex", related_name="e_vertex")

Serializers

class VertexNameSerializer(serializers.ModelSerializer):
    class Meta:
        model = Vertex
        fields = ['name']

class VertexSerializer(serializers.ModelSerializer):
    class Meta:
        model = Vertex
        fields = ['name', 'extras']


class EdgeSerializer(serializers.ModelSerializer):
    node = VertexSerializer()
    dependency = VertexNameSerializer()

    class Meta:
        model = Edge
        fields = ['node', 'dependency']


class GraphSerializer(serializers.ModelSerializer):
    edges = EdgeSerializer(many=True)

    class Meta:
        model = Graph
        fields = ['id', 'name', 'edges']
        depth = 3

GraphViewSet

class GraphViewSet(viewsets.ModelViewSet):
    queryset = Graph.objects.all()
    serializer_class = GraphSerializer

For the above Graph Graph g-1

Tables:

+-----+--------------+
| id  |     Name     |
+-----+--------------+
| g-1 | Sample Graph |
+-----+--------------+

Vertex
+-----+------+-------+
| id  | Name | Extra |
+-----+------+-------+
| v-1 | A    | {}    |
| v-2 | B    | {}    |
| v-3 | C    | {}    |
+-----+------+-------+

Edge
+-------+------+------------+
| Graph | Node | Dependency |
+-------+------+------------+
| g-1   | v-1  | null       |
| g-1   | v-2  | null       |
| g-1   | v-3  | v-1        |
| g-1   | v-3  | v-2        |
+-------+------+------------+

Since i have viewsets if i do a GET call on the /graph endpoint, this is the output i get

{
  "id": "g-1",
  "name": "Sample Graph",
  "edges": [
    {
      "node": {
        "name": "A",
        "extras": {}
      },
      "dependency": null
    },
    {
      "node": {
        "name": "B",
        "extras": {}
      },
      "dependency": null
    },
    {
      "node": {
        "name": "C",
        "extras": {}
      },
      "dependency": {
        "name": "A"
      }
    },
    {
      "node": {
        "name": "C",
        "extras": {}
      },
      "dependency": {
        "name": "B"
      }
    }
  ]
}

but i want the output as

{
  "id": "g-1",
  "name": "Sample Graph",
  "edges": [
    {
      "node": {
        "name": "A",
        "extras": {}
      },
      "dependency": null
    },
    {
      "node": {
        "name": "B",
        "extras": {}
      },
      "dependency": null
    },
    {
      "node": {
        "name": "C",
        "extras": {}
      },
      "dependency": ["A", "B"]
    }
  ]
}

how should i change the serializers to get the desired output?

1 Answers

Something like this should work

class GraphSerializer(serializers.ModelSerializer):
    edges = serializers.SerializerMethodField()

    class Meta:
        model = Graph
        fields = ['id', 'name', 'edges']

    def get_edges(self, obj):
        dependencies_raw = obj.edges.values_list('node__name', 'dependency__name')
        dependencies_mapper = {}
        for node_name, dependency_name in dependencies_raw:
            dependencies_mapper.setdefault(node_name, [])
            dependencies_mapper[node_name].append(dependency_name)

        nodes = EdgeSerializer(obj.edges.distinct('name').all(), many=True).data

        data = [
            {
                **node,
                'dependencies': dependencies_mapper.get(node['node']['name'], None)
            } for node in nodes
        ]
        return data

I can't test it all right now. Basically I'd make two requests instead of the default 1.

First to obtain node name and its dependency, and the second one for distinct nodes. Then we just glue it together.

And remove dependency field from EdgeSerializer.

Related