Maurinostroza
Maurinostroza

Reputation: 115

Add multiple models and custom fields to a json response in Django Rest Framework

i'm new into Python/Django programming and i got stuck with something in a personal project that i'm doing. My issue is that i want to return a custom response based on different models of my application, some of the values will come from custom queries and others are part of the models itself.

So, i have the following models in my app(some fields were deleted to not make the post too long):

class Parking(models.Model):
    google_id = models.CharField(max_length=100)
    short_name = models.CharField(max_length=100)
    long_name = models.CharField(max_length=300)
    price = models.DecimalField(max_digits=4, decimal_places=2, null=True, blank=True)

class ParkingLot(models.Model):        
    parking = models.ForeignKey(Parking, on_delete=models.CASCADE, null=False, related_name='parkinglots')
    size = models.ForeignKey(Size, on_delete=models.DO_NOTHING, null=False, related_name='size')
    width = models.DecimalField(max_digits=3, decimal_places=2, null=True, blank=True)
    height = models.DecimalField(max_digits=3, decimal_places=2, null=True, blank=True)

class ParkingAvailability(models.Model):
    parkinglot = models.ForeignKey(ParkingLot, on_delete=models.CASCADE, null=False, related_name='availability')
    available = models.BooleanField(null=False, blank=False)
    from_hour = models.TimeField(auto_now=False, auto_now_add=False, default='09:00:00')
    to_hour = models.TimeField(auto_now=False, auto_now_add=False, default='21:00:00')

These models are an exact representation of my database tables. All good. My problem is that now i want to make a custom json response, this needs to run queries over these tables but not show the entire objects and in some cases, custom fields based on filter or operations that i need to run over the tables (for example, numbers of parkinglots based on a parking_id and size_id). So, let's assume that i need something that will look like this:

[
    {
        "id": 1,
        "google_id": "JSDKKLAD888737283",
        "short_name": "Fernandez Albano",
        "long_name": "Comunidad Fernandez Albano",
        "price": 18.5,
        "parkinglots": 88, 
        "hours": {
            "from_hour": "09:00:00",
            "to_hour": "21:00:00",
        }
    }
]

The first 4 json values come from Parking model, the field parkinglots is the .count() of the parkinglots model with some filters (parking_id, size_id). Availability is a custom query from ParkingAvailability model that i can access through my parkinglot_id value.

So far, i have something like this but it seems to me that is not ok:

class parkingList(APIView):
    def get(self, request, format=None):
        parkinglotNumber=ParkingLot.objects.filter(parking_id = 2, size_id = 1).count()
        parkinglot = ParkingLot.objects.filter(parking_id = 2, size_id = 1)
        hours = ParkingAvailability.objects.filter(parkinglot__in=parkinglot, available=True).aggregate(Min('from_hour'), Max('to_hour'))

        content = {
            'parkinglots_number': parkinglotNumber,
            'hours': hours
        }

        return Response(content)

So, my questions are:

  1. It's ok to run queries like that? If want to add (for example) my parkinglot model entirely to that response i will need to run another query like "ParkingLot.objects.filter(parking_id = 2, size_id = 1)" and add it to my parking response?
  2. My serializer serialize all the models fields but in this case i don't use it, it's ok to return this with no serializers class?
  3. How i can build complex json response? Like in this case, i need to add different part to my json from different models and custom fields.

Thanks for your help! Sorry for my English but is not mi native language.

PS: My serializer class return all the information of my models. A part of the class it's like this:

class ParkingLotSerializer(serializers.ModelSerializer):
    size = SizeSerializer(many=False, read_only=True)
    availability = ParkingAvailabilitySerializer(many=True, read_only=True)
    class Meta:
        model = ParkingLot
        fields = ['id', 'price', 'floor', 'number', 'width', 'size', 'availability']

class ParkingSerializer(serializers.ModelSerializer):
    adress = AdressSerializer(many=False, read_only=True)
    parkinglots = ParkingLotSerializer(many=True, read_only=True)
    class Meta:
        model = Parking
        fields = ['id', 'google_id', 'price', 'short_name', 'long_name', 'adress', 'parkinglots' ]

Upvotes: 3

Views: 1841

Answers (1)

aredzko
aredzko

Reputation: 1730

Your other related models should be available through the Parking object via their related_name. For example, to access related ParkingLots, you could do obj.parkinglots.count(). You could even further filter if that was needed, such as obj.parkinglots.filter(size_id=1).count(). Because of this, you could use a SerializerMethodField.

class ParkingSerializer(serializers.ModelSerializer):
    ...
    parkinglots_count = serializers.SerializerMethodField()

    ...
    def get_parkinglots_count(self, obj):
        return obj.parkinglots.count()

You can rename the field to whatever makes sense for your api, but the method used should be get_<FIELD_NAME>, and be sure to add it to your fields Meta attribute! If you'd like to do more custom fields, this can work similarly.


To address your questions:

It's ok to run queries like that? If want to add (for example) my parkinglot model entirely to that response i will need to run another query like "ParkingLot.objects.filter(parking_id = 2, size_id = 1)" and add it to my parking response?

This can cause what's called an n+1 problem, since you will need to run a query for each serializer instance. So on your list, if you returned 25 parking lots, you might run 25 count queries. You can get around this by doing things like annotating to your queryset to help reduce the query count. That said, yes it's fine! It's really common to do things like this.

My serializer serialize all the models fields but in this case i don't use it, it's ok to return this with no serializers class?

I think I understand. You're asking about serializing fields you don't use? You definitely can do that. If you're interested in dynamically turning off fields (either on the fly, or when you use the serializer), look into something like django-restql (sorry for the shameless plug!).

How i can build complex json response? Like in this case, i need to add different part to my json from different models and custom fields.

As long as the data you pass in has what you need, you can make a serializer. It's not uncommon to make a serializer that you just pass a dictionary in to in order to return it on your api. Serializers don't necessarily need to be tied to models, and as covered in the answer, you can make your own custom fields to do work if needed. Django Rest Framework has a good tutorial that touches on non-model serializers.

Upvotes: 2

Related Questions