Home > Back-end >  Nested serializers and data representation
Nested serializers and data representation

Time:12-05

Working on a django project I am a bit stuck on data representation through APIs. In fact when designing models the data model is quite stratighforward : I have a one to many relationship A--> B therefore I have added a FK to object B.

Object B has a boolean attribute "active".

I would like to make an API call to list all A objects having at least one assoicated object B with active = true.

The api could be like this :

/api/objectA/?ObjectB.active=True

Here is my code :

Models :

from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver


class Startup(models.Model):
    header = models.CharField("Header", max_length=255)
    title = models.CharField("Title", max_length=255)
    description = models.CharField("description", max_length=255)
    # TODO Change this to options instead of array
    tags = ArrayField(models.CharField(max_length=10, blank=True), size=5)
    # TODO Images to be stored in aws only url will be in DB
    card_image = models.ImageField(upload_to='media/images/cards')
    logo_image = models.ImageField(upload_to='media/images/logos')
    main_img = models.ImageField(upload_to='media/images/main', null=True)
    createdAt = models.DateTimeField("Created At", auto_now_add=True)

    def __str__(self):
        return self.title


class Investment(models.Model):
    # TODO change the name of Investment to fund round in back and front
    # TODO all price to be checked for max digits and decimal places

    startup = models.ForeignKey(Startup, related_name='startup_investments', on_delete=models.CASCADE, default="1")
    # Use the related_name as a serializer bale for investments inside startup serializer
    Investment_title = models.CharField("Investment_title", max_length=255, default="Missing Title")
    collected_amount = models.DecimalField(max_digits=12, decimal_places=2)
    goal_percentage = models.IntegerField(default=0)
    number_of_investors = models.IntegerField(default=0)
    days_left = models.IntegerField()
    active = models.BooleanField(default=False)
    # TODO Need to update this to prevent linking to a non existing startup
    createdAt = models.DateTimeField("Created At", auto_now_add=True)

    def clean(self):
        """Validate that the startup does not have already an active Investment """
        if self.active:
            qs = Investment.objects.filter(active=True).filter(startup=self.startup)
            if self.pk is not None:
                qs = qs.exclude(pk=self.pk)
                if qs:
                    raise ValidationError(message="An active investment already exists for this startup")

    def __str__(self):
        return self.Investment_title

Serializers :

from rest_framework import serializers
from .models import Startup, Investment


class InvestmentSerializer(serializers.ModelSerializer):
    class Meta:

        model = Investment
        fields = ('id', 'Investment_title', 'collected_amount', 'goal_percentage', 'number_of_investors',
                  'days_left', 'active')


class StartupSerializer(serializers.ModelSerializer):
    startup_investments = InvestmentSerializer(many=True, read_only=True)

    class Meta:
        model = Startup
        fields = ('id', 'header', 'title', 'description', 'tags', 'card_image',
                  'logo_image', 'main_img', 'startup_investments')

Views :

from django_filters import rest_framework as filters
from rest_framework.viewsets import ModelViewSet
from rest_framework_extensions.mixins import NestedViewSetMixin

from .serializers import *


class StartUpViewSet(NestedViewSetMixin, ModelViewSet):
    """
      Class that provides List, Retrieve, Create, Update, Partial Update and Destroy  actions for startups.
      It also include a filter by startup status
    """
    model = Startup
    queryset = Startup.objects.all()
    serializer_class = StartupSerializer


class InvestmentViewSet(NestedViewSetMixin, ModelViewSet):
    """
     Class that provides List, Retrieve, Create, Update, Partial Update and Destroy  actions for Investments.
     It also include a active and investment title
    """
    model = Investment
    serializer_class = InvestmentSerializer
    queryset = Investment.objects.all()
    filter_backends = (filters.DjangoFilterBackend,)
    filterset_fields = ('active', 'Investment_title')

routers :

router = ExtendedSimpleRouter()
(
    router.register(r'api/investments', views.InvestmentViewSet, basename='investment'),
    router.register(r'api/startups', views.StartUpViewSet, basename='startup')
          .register(r'investments', views.InvestmentViewSet, basename='startups_investment',
                    parents_query_lookups=['startup']),

)

Thanks for your help.

CodePudding user response:

I would try something like this:

class StartUpViewSet(NestedViewSetMixin, ModelViewSet):

    model = Startup
    #queryset = Startup.objects.all()
    serializer_class = StartupSerializer
    
    def get_queryset(self):
        Startup.objects.annotate(active_investments=Count('startup_investments', filter=Q(startup_investments__active=True)).filter(active_investments__gt=0)

CodePudding user response:

Hello I am posting this answer hoping it will help others as I have spent two days to make this work!!

class ActiveStartupSerializer(serializers.ListSerializer):

    def to_representation(self, data):
        """List all startups with one active investment"""
        data = data.filter(startup_investments__active=True)
        return super(ActiveStartupSerializer, self).to_representation(data)

    class Meta:
        model = Startup
        fields = ('id', 'header', 'title', 'description', 'tags', 'card_image',
                  'logo_image', 'main_img', 'startup_investments')


class InvestmentSerializer(serializers.ModelSerializer):
    class Meta:

        model = Investment
        fields = ('id', 'Investment_title', 'collected_amount', 'goal_percentage', 'number_of_investors',
                  'days_left', 'active')


class StartupSerializer(serializers.ModelSerializer):
    startup_investments = InvestmentSerializer(many=True, read_only=True)

    class Meta:
        model = Startup
        list_serializer_class = ActiveStartupSerializer
        fields = ('id', 'header', 'title', 'description', 'tags', 'card_image',
                  'logo_image', 'main_img', 'startup_investments')
  • Related