DRF Best Practices01: DynamicFieldsModelSerializer

DRF Serializer 容易由于字段过多,表间级联查询过多,造成性能较差,可以通过动态选择字段极大提升性能

场景

list 需要的字段少于 retrive 字段

class X(ModelViewSet)
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        exclude_fields = ["field1", "field2", "fk_obj__field3" ]
        if page is not None:
            serializer = self.get_serializer(
                page, many=True, exclude_fields=exclude_fields
            )
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(
            queryset, many=True, exclude_fields=exclude_fields
        )
        return Response(serializer.data)
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
    """
    A ModelSerializer that takes an additional `fields` argument that
    controls which fields should be displayed.
    """

    def __init__(self, *args, **kwargs):
        def _remove_fields(fields, field_names):
            if field_names is not None:
                allowed_fields = [field.split("__")[0] for field in field_names]
                # Drop any fields that are not specified in the `fields` argument.
                allowed = set(allowed_fields)
                existing = set(fields)
                for field_name in existing - allowed:
                    fields.pop(field_name)

            childs = defaultdict(list)
            for field in field_names:
                if len(field.split("__")) > 1:
                    childs[field.split("__")[0]].append(
                        "__".join(field.split("__")[1:])
                    )

            for key, value in childs.items():
                if isinstance(fields[key], serializers.ListSerializer):
                    _remove_fields(fields[key].child.fields, value)
                else:
                    if hasattr(fields[key], "fields"):
                        _remove_fields(fields[key].fields, value)

            # Don't pass the 'fields' arg up to the superclass

        def _delete_fields(fields, field_names):
            if field_names is not None:
                allowed_fields = [field.split("__")[0] for field in field_names]
                # Drop any fields that are not specified in the `fields` argument.
                allowed = set(allowed_fields)
                for field_name in allowed:
                    fields.pop(field_name)

            childs = defaultdict(list)
            for field in field_names:
                if len(field.split("__")) > 1:
                    childs[field.split("__")[0]].append(
                        "__".join(field.split("__")[1:])
                    )

            for key, value in childs.items():
                if isinstance(fields[key], serializers.ListSerializer):
                    _delete_fields(fields[key].child.fields, value)
                else:
                    if hasattr(fields[key], "fields"):
                        _delete_fields(fields[key].fields, value)

            # Don't pass the 'fields' arg up to the superclass

        _fields = kwargs.pop("fields", None)
        _exclude_fields = kwargs.pop("exclude_fields", None)

        # Instantiate the superclass normally
        super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
        # assert _fields or _exclude_fields, 'exclude_fields,fields 只支持一个'

        if _fields:
            _remove_fields(self.fields, _fields)

        if _exclude_fields:
            _delete_fields(self.fields, _exclude_fields)

reference

  • https://stackoverflow.com/questions/54893891/django-rest-framework-using-dynamicfieldsmodelserializer-for-excluding-serializ
  • https://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields

links

social