如何在单个序列化程序中合并来自两个不同来源(没有RDBMS关系)的数据?

时间:2018-02-15 10:15:13

标签: python django django-rest-framework

我正在尝试序列化一些对象,这些对象的数据存储在2个数据库中,由普通的UUID链接。第二个数据库DB2存储个人数据,因此它作为隔离的微服务运行,以符合各种隐私法。我将数据作为解码的dicts列表(而不是模型实例的实际查询集)接收。如何调整ModelSerializer以序列化此数据?

这是与DB2交互以获取个人数据的最小示例:

# returns a list of dict objects, approx representing PersonalData.__dict__
# `custom_filter` is a wrapper for the Microservice API using `requests`
personal_data = Microservice.objects.custom_filter(uuid__in=uuids)

这是一种序列化的最小方式,包括出生日期:

class PersonalDataSerializer(serializers.Serializer):
    uuid = serializers.UUIDField() # common UUID in DB1 and DB2
    dob = serializers.DateField() # personal, so can't be stored in DB1

在我的应用程序中,我需要将Person查询集和相关的personal_data序列化为一个JSON数组。

class PersonSerializer(serializers.ModelSerializer):
    dob = serializers.SerializerMethodField()
    # can't use RelatedField for `dob` because the relationship isn't
    # codified in the RDBMS, due to it being a separate Microservice.

    class Meta:
        model = Person
        # A Person object has `uuid` and `date_joined` fields.
        # The `dob` comes from the personal_data, fetched from the Microservice
        fields = ('uuid', 'date_joined', 'dob',)

    def get_dob(self):
        raise NotImplementedError # for the moment

我不知道是否有一个很好的DRF方式来链接这两者。我绝对不希望通过在get_dob中包含单个请求来向微服务发送(可能有数千个)个别请求。实际视图看起来像这样:

class PersonList(generics.ListAPIView):
    model = Person
    serializer_class = PersonSerializer

    def get_queryset(self):
        self.kwargs.get('some_filter_criteria')
        return Person.objects.filter(some_filter_criteria)

逻辑应该将微服务数据链接到串行器中,它应该是什么样的?

2 个答案:

答案 0 :(得分:5)

我建议您覆盖序列化程序和列表方法。

串行:

class PersonSerializer(models.Serializer):
    personal_data = serializers.DictField()

    class Meta:
        model = Person

创建一个函数,将personal_data字典添加到person对象。在将人物对象列表提供给序列化程序之前使用此方法。

def prepare_persons(persons):
    person_ids = [p.uuid for p in persons]
    personal_data_list = Microservice.objects.custom_filter(uuid__in=person_ids)
    personal_data_dict = {pd['uuid']: pd for pd in personal_data_list}
    for p in persons:
        p.personal_data = personal_data_dict[p.id]
    return persons


def list(self, request, *args, **kwargs):

    queryset = self.filter_queryset(self.get_queryset())

    page = self.paginate_queryset(queryset)

    if page is not None:
        page = prepare_persons(page)
        serializer = self.get_serializer(page, many=True)
        return self.get_paginated_response(serializer.data)
    else:
        persons = prepare_persons(queryset)

    serializer = self.get_serializer(persons, many=True)
    return Response(serializer.data)

答案 1 :(得分:4)

因为您只希望一次点击数据库,所以将额外数据添加到查询集的好方法是向ViewSet添加ListModelMixin的自定义版本includes extra context

class PersonList(generics.ListAPIView):
    ...

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        # Pseudo-code for filtering, adjust to work for your use case
        filter_criteria = self.kwargs.get('some_filter_criteria')
        personal_data = Microservice.objects.custom_filter(filter_criteria)

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(
                page, 
                many=True, 
                context={'personal_data': personal_data}
            )
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(
            queryset, 
            many=True, 
            context={'personal_data': personal_data}
        )
        return Response(serializer.data)

然后,通过overriding the to_representation method访问序列化程序中的额外上下文:

def to_representation(self, instance):
    """Add `personal_data` to the object from the Microservice"""
    ret = super().to_representation(instance)
    personal_data = self.context['personal_data']
    ret['personal_data'] = personal_data[instance.uuid]
    return ret