Home > Software engineering >  Intercept and replace serializer fields on initialization in Django
Intercept and replace serializer fields on initialization in Django

Time:10-26

So I have a Django project with Django REST Framework with large number of models. For frontend to be user friendly I should display not only related object's id but also name. My idea for the solution was to replace all the PrimaryKeyRelated fields with StringRelatedFields in serializers on response. As the number of models is large I decided to make a single abstract serializer/mixin and intercept field creation replacing the field if is of correct type. This is how far I got up to now:

class AbstractSerializer(serializers.ModelSerializer):
class Meta:
    model: AbstractModel = AbstractModel
    read_only_fields: list = [
        'created_at',
        'created_by',
        'modified_at',
        'modified_by',
        'is_deleted',
        'deleted_at',
        'deleted_by'
    ]   ['is_active'] if 'is_active' in [field.attname for field in model._meta.fields] else []
    abstract: bool = True

def to_representation(self, instance):
    serializer = AbstractRequestResponseSerializer(instance)
    return serializer.data


class AbstractRequestResponseSerializer(AbstractSerializer):

class Meta(AbstractSerializer.Meta):
    pass

@classmethod
def _get_declared_fields(cls, bases, attrs):
    fields = [(field_name, attrs.pop(field_name))
              for field_name, obj in list(attrs.items())
              if isinstance(obj, Field)]
    fields.sort(key=lambda x: x[1]._creation_counter)

    new_fields = []
    for field in fields:
        if isinstance(field, PrimaryKeyRelatedField):
            field = StringRelatedField(source=field.source, required=False)
        new_fields.append(field)
    fields = new_fields

    known = set(attrs)

    def visit(name):
        known.add(name)
        return name

    base_fields = [
        (visit(name), f)
        for base in bases if hasattr(base, '_declared_fields')
        for name, f in base._declared_fields.items() if name not in known
    ]

    return OrderedDict(base_fields   fields)

This gives an infinite loop error because of __new__ method and I started to wonder if I am overriding the right function. I also tried to replace to_representation function but I guess that function occurs too late in the flow when all the field instances are created already. Which function should I override?

CodePudding user response:

class ParentModelSerializer(serializers.ModelSerializer):

    class Meta:
        model = ParentModel
        fields = '__all__'

class ChildModelSerializer(serializers.ModelSerializer):
    parent = ParentModelSerializer(read_only=True)

    class Meta:
        model = ChildModel
        fields = '__all__'

Or if you want to display the children in your parent:

class ParentModelSerializer(serializers.ModelSerializer):
    children = ChildModelSerializer(read_only=True, many=True)
    # children is the "related_name"

    class Meta:
        model = ParentModel
        fields = '__all__'

class ChildModelSerializer(serializers.ModelSerializer):

    class Meta:
        model = ChildModel
        fields = '__all__'

CodePudding user response:

Maybe I phrased the question incorrectly (you could rephrase that to help future generations :) ), but the solution I made looks like this:

class AbstractSerializer(serializers.ModelSerializer):
class Meta:
    model: AbstractModel = AbstractModel
    read_only_fields: list = [
        'created_at',
        'created_by',
        'modified_at',
        'modified_by',
        'is_deleted',
        'deleted_at',
        'deleted_by'
    ]   ['is_active'] if 'is_active' in [field.attname for field in model._meta.fields] else []
    abstract: bool = True

def to_representation(self, instance):
    ret = OrderedDict()
    fields = self._readable_fields

    for field in fields:
        if isinstance(field, PrimaryKeyRelatedField):
            parent = field.parent
            field_name = field.field_name
            source = field.source
            if source != field_name:
                field = StringRelatedField(source=field.source, required=False)
            else:
                field = StringRelatedField(required=False)
            field.bind(field_name, parent)
        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
  • Related