Django中的GenericForeignKey是否可以使用自然密钥?

Django中的GenericForeignKey是否可以使用自然密钥?,django,natural-key,generic-foreign-key,Django,Natural Key,Generic Foreign Key,我有以下资料: target_content_type = models.ForeignKey(ContentType, related_name='target_content_type') target_object_id = models.PositiveIntegerField() target = generic.GenericForeignKey('target_content_type', 'target_object_id') 我希望dumpdata--natural为这个关系

我有以下资料:

target_content_type = models.ForeignKey(ContentType, related_name='target_content_type')
target_object_id = models.PositiveIntegerField()
target = generic.GenericForeignKey('target_content_type', 'target_object_id')

我希望dumpdata--natural为这个关系发出一个自然键。这可能吗?如果没有,是否有其他策略不会将我与目标公司的主键绑定?

TL;DR-目前,除了创建自定义的
序列化程序
/
反序列化程序
对之外,没有一种明智的方法可以做到这一点

具有泛型关系的模型的问题在于,Django根本不将
target
视为字段,只将
target\u content\u type
target\u object\u id
视为字段,它会尝试分别对它们进行序列化和反序列化

负责序列化和反序列化Django模型的类位于模块和中。所有其他(
xml
json
yaml
)都扩展了它们中的任何一个(并且
python
扩展了
base
)。字段序列化是这样完成的(不相关的行被忽略):

这里是第一个复杂的问题:
ContentType
的外键处理得很好,使用我们预期的自然键。但是,
PositiveIntegerField
handle\u field
处理,其实现方式如下:

def handle_field(self, obj, field):
    value = field._get_val_from_obj(obj)
    # Protected types (i.e., primitives like None, numbers, dates,
    # and Decimals) are passed through as is. All other values are
    # converted to string first.
    if is_protected_type(value):
        self._current[field.name] = value
    else:
        self._current[field.name] = field.value_to_string(obj)
   for (field_name, field_value) in six.iteritems(d["fields"]):
        field = Model._meta.get_field(field_name)
        ...
            data[field.name] = field.to_python(field_value)
i、 e.此处唯一的定制可能性(子类化
PositiveIntegerField
并定义a)将无效,因为序列化程序不会调用它。将
target\u object\u id
的数据类型更改为整数以外的其他类型可能会破坏许多其他内容,因此这不是一个选项

在这种情况下,我们可以定义自定义的
handle\u字段
,以发出自然键,但接下来会出现第二个复杂问题:反序列化是这样完成的:

def handle_field(self, obj, field):
    value = field._get_val_from_obj(obj)
    # Protected types (i.e., primitives like None, numbers, dates,
    # and Decimals) are passed through as is. All other values are
    # converted to string first.
    if is_protected_type(value):
        self._current[field.name] = value
    else:
        self._current[field.name] = field.value_to_string(obj)
   for (field_name, field_value) in six.iteritems(d["fields"]):
        field = Model._meta.get_field(field_name)
        ...
            data[field.name] = field.to_python(field_value)
即使我们定制了
to_python
方法,它也会在对象上下文之外单独作用于
字段_值。使用整数时没有问题,因为无论是什么模型,它都将被解释为模型的主键。但是要反序列化一个自然密钥,首先我们需要知道该密钥属于哪个模型,并且除非我们获得对该对象的引用(并且
target\u content\u type
字段已经反序列化),否则该信息不可用

正如您所看到的,支持泛型关系中的自然键并不是一项不可能的任务,但要实现这一点,需要在序列化和反序列化代码中更改很多内容。然后,必要的步骤(如果有人认为能够胜任该任务)是:

  • 创建一个自定义的
    字段
    扩展
    PositiveIntegerField
    ,使用编码/解码对象的方法-调用参考模型的
    natural_key
    get_by_natural_key
  • 覆盖序列化程序的
    句柄\u字段
    ,以调用编码器(如果存在)
  • 实现一个自定义反序列化器:1)在字段中施加一些顺序,确保在自然键之前反序列化内容类型;2) 调用解码器,不仅传递
    字段\u值
    ,还传递对已解码
    内容类型
    的引用

我编写了一个支持GenericFK的自定义序列化程序和反序列化程序。简单地检查了一下,它似乎完成了任务

这就是我想到的:

import json

from django.contrib.contenttypes.generic import GenericForeignKey
from django.utils import six
from django.core.serializers.json import Serializer as JSONSerializer
from django.core.serializers.python import Deserializer as \
    PythonDeserializer, _get_model
from django.core.serializers.base import DeserializationError
import sys


class Serializer(JSONSerializer):

    def get_dump_object(self, obj):
        dumped_object = super(CustomJSONSerializer, self).get_dump_object(obj)
        if self.use_natural_keys and hasattr(obj, 'natural_key'):
            dumped_object['pk'] = obj.natural_key()
            # Check if there are any generic fk's in this obj
            # and add a natural key to it which will be deserialized by a matching Deserializer.
            for virtual_field in obj._meta.virtual_fields:
                if type(virtual_field) == GenericForeignKey:
                    content_object = getattr(obj, virtual_field.name)
                    dumped_object['fields'][virtual_field.name + '_natural_key'] = content_object.natural_key()
        return dumped_object


def Deserializer(stream_or_string, **options):
    """
    Deserialize a stream or string of JSON data.
    """
    if not isinstance(stream_or_string, (bytes, six.string_types)):
        stream_or_string = stream_or_string.read()
    if isinstance(stream_or_string, bytes):
        stream_or_string = stream_or_string.decode('utf-8')
    try:
        objects = json.loads(stream_or_string)
        for obj in objects:
            Model = _get_model(obj['model'])
            if isinstance(obj['pk'], (tuple, list)):
                o = Model.objects.get_by_natural_key(*obj['pk'])
                obj['pk'] = o.pk
                # If has generic fk's, find the generic object by natural key, and set it's
                # pk according to it.
                for virtual_field in Model._meta.virtual_fields:
                    if type(virtual_field) == GenericForeignKey:
                        natural_key_field_name = virtual_field.name + '_natural_key'
                        if natural_key_field_name in obj['fields']:
                            content_type = getattr(o, virtual_field.ct_field)
                            content_object_by_natural_key = content_type.model_class().\
                            objects.get_by_natural_key(obj['fields'][natural_key_field_name][0])
                            obj['fields'][virtual_field.fk_field] = content_object_by_natural_key.pk
        for obj in PythonDeserializer(objects, **options):
            yield obj
    except GeneratorExit:
        raise
    except Exception as e:
        # Map to deserializer error
        six.reraise(DeserializationError, DeserializationError(e), sys.exc_info()[2])

我很好奇,你找到解决办法了吗?我做了一些搜索,但是没有找到任何有用的东西。还没有,但是如果我找到了,我会用一个解决方案来更新它。你能详细说明你的问题吗?举个例子。