Fabio Magarelli
Fabio Magarelli

Reputation: 1101

Override Django (DRF) Serializer object GET

I'm trying to "inject" some raw sql into my DRF nested Serializer:

# SERIALIZERS

class CarSerializer(serializers.ModelSerializer):

    class Meta:
        model = Car
        fields = '__all__'

class DriverSerializer(serializers.ModelSerializer):
    car = CarSerializer()   # <--- here I don't want to get the Car object but rather inject a raw sql.

    class Meta:
        model = Driver
        fields = '__all__'

The SQL injection is needed to request for a specific version of the data since I'm using MariaDB versioning tables but this is not relevant. How do I override the method that gets the object from CarSerializer? Thank you.

Upvotes: 0

Views: 3726

Answers (3)

Fabio Magarelli
Fabio Magarelli

Reputation: 1101

Thank you everyone for your answers, I managed to make it work although my solution is not as clean as the one suggested from @yvesonline and @iklinak:

I first checked the official DRF documentation on overriding serializers: https://www.django-rest-framework.org/api-guide/serializers/#overriding-serialization-and-deserialization-behavior

In particular I was interested in the overriding of the method: .to_representation(self, instance) that controls the fetching of the object from the database:

from datetime import datetime as dt
from collections import OrderedDict
from rest_framework.relations import PKOnlyObject
from rest_framework.fields import SkipField, empty

    def __init__(
            self, instance=None, data=empty, asTime=str(dt.now()), **kwargs):
        self.asTime = asTime
        self.instance = instance
        if data is not empty:
            self.initial_data = data
        self.partial = kwargs.pop('partial', False)
        self._context = kwargs.pop('context', {})
        kwargs.pop('many', None)
        super().__init__(**kwargs)

    def to_representation(self, instance):
        # substitute instance with my raw query
        # WARNING: self.asTime is a custom variable, check the
        # __init__ method above!

        instance = Car.objects.raw(
            '''
                    select * from db_car
                    for system_time as of timestamp %s
                    where id=%s;
                ''', [self.asTime, instance.id])[0]

        ret = OrderedDict()
        fields = self._readable_fields

        for field in fields:
            try:
                attribute = field.get_attribute(instance)
            except SkipField:
                continue

            check_for_none = attribute.pk if isinstance(
                attribute, PKOnlyObject) else attribute
            if check_for_none is None:
                ret[field.field_name] = None
            else:
                ret[field.field_name] = field.to_representation(attribute)

        return ret

You can find the original code here: https://github.com/encode/django-rest-framework/blob/19655edbf782aa1fbdd7f8cd56ff9e0b7786ad3c/rest_framework/serializers.py#L335

Then finally in the DriverSerializer class:

class DriverSerializer(serializers.ModelSerializer):
    car = CarSerializer(asTime='2021-02-05 14:34:00')

    class Meta:
        model = Driver
        fields = '__all__'

Upvotes: 0

iklinac
iklinac

Reputation: 15758

You could define method under your model to get related Car

class Car(models.Model):

    def current_car(self):
        return Car.objects.raw('SELECT ... FROM ...')[0]

Then in serializer you could reuse following method

class DriverSerializer(serializers.ModelSerializer):
    car = CarSerializer(source="current_car")

    class Meta:
        model = Driver
        fields = (...)

Upvotes: 1

yvesonline
yvesonline

Reputation: 4857

This is untested but I think you want to override the __init__ in DriverSerializer and then load the result of your raw SQL via data, something like this:

class DriverSerializer(serializers.ModelSerializer):
     [...]
     def __init__(self, *args, **kwargs):
          super(DriverSerializer, self).__init__(*args, **kwargs)
          name_map = {'column_1': 'obj_attr_1', 'column_2': 'obj_attr_1', 'pk': 'id'}
          raw = Car.objects.raw('SELECT ... FROM ...', translations=name_map)
          data = {k: getattr(raw[0], k) for k in name_map.keys()}
          self.car = CarSerializer(data=data)

Upvotes: 1

Related Questions