11

私は次のものを持っています:

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で、この関係の自然キーを出力したいと思います。これは可能ですか?そうでない場合、ターゲットの主キーに私を結び付けない代替戦略はありますか?

4

3 に答える 3

9

TL; DR-Serializer現在、カスタム/Deserializerペアを作成する以外に、そうするための正しい方法はありません。

一般的な関係を持つモデルの問題は、Djangoがtargetフィールドとしてまったく認識せず、とのみtarget_content_typeを認識しtarget_object_id、それらを個別にシリアル化および逆シリアル化しようとすることです。

Djangoモデルのシリアル化と逆シリアル化を担当するクラスは、モジュールdjango.core.serializers.baseとにありdjango.core.serializers.pythonます。他のすべて(xml、、jsonおよびyaml)は、それらのいずれかを拡張します(およびpython拡張しますbase)。フィールドのシリアル化は次のように行われます(無関係な行は省略されています)。

    for obj in queryset:
        for field in concrete_model._meta.local_fields:
                if field.rel is None:
                        self.handle_field(obj, field)
                else:
                        self.handle_fk_field(obj, field)

最初の問題は次のとおりContentTypeです。toの外部キーは正常に処理され、予想どおり自然キーを使用します。しかし、PositiveIntegerFieldはによって処理されhandle_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)

つまり、ここでのカスタマイズ(カスタムPositiveIntegerFieldのサブクラス化と定義)の唯一の可能性は、シリアライザーがそれを呼び出さないため、効果がありません。のデータ型を整数以外に変更すると、他の多くのものが壊れる可能性があるため、オプションではありません。value_to_stringtarget_object_id

この場合、自然キーを出力するようにカスタムを定義できますhandle_fieldが、2番目の問題が発生します。逆シリアル化は次のように行われます。

   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メソッドをカスタマイズした場合でもfield_value、オブジェクトのコンテキスト外で、単独で機能します。整数を使用する場合は、どのモデルであってもモデルの主キーとして解釈されるため、問題はありません。ただし、自然キーを逆シリアル化するには、まず、そのキーがどのモデルに属しているかを知る必要があります。オブジェクトへの参照を取得しない限り、その情報は利用できません(target_content_typeフィールドはすでに逆シリアル化されています)。

ご覧のとおり、一般的な関係で自然キーをサポートすることは不可能な作業ではありませんが、それを実現するには、シリアル化および逆シリアル化のコードで多くのことを変更する必要があります。必要な手順は次のとおりです(誰かがそのタスクに満足している場合)。

  • オブジェクトをエンコード/デコードするメソッドを使用して、カスタムField拡張を作成します-参照されるモデルを呼び出します'および;PositiveIntegerFieldnatural_keyget_by_natural_key
  • シリアライザーをオーバーライドしてhandle_field、エンコーダーが存在する場合はそれを呼び出します。
  • 次のようなカスタムデシリアライザーを実装します。1)フィールドに順序を設定し、コンテンツタイプが自然キーの前にデシリアライズされるようにします。2)デコーダーを呼び出し、field_valueデコードされたものへの参照だけでなく、参照も渡しContentTypeます。
于 2013-02-15T16:59:28.037 に答える
3

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])
于 2016-04-04T17:08:31.360 に答える
0

Django2.2以降のOmriToptixの回答を更新しました。

Django 2.0の場合:

Model._meta.virtual_fields属性が削除されます。

したがって、新しいシリアライザーとデシリアライザーは次のようになります。

import json

from django.contrib.contenttypes.fields 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(JSONSerializer, self).get_dump_object(obj)

        if hasattr(obj, 'natural_key'):
            dumped_object['pk'] = obj.natural_key()
            for field in obj._meta.get_fields():
                if type(field) == GenericForeignKey:
                    content_object = getattr(obj, field.name)
                    dumped_object['fields'][field.name + '_natural_key'] = content_object.natural_key()
        return dumped_object


def Deserializer(stream_or_string, **options):
    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
                for field in Model._meta.get_fields():
                    if type(field) == GenericForeignKey:
                        natural_key_field_name = field.name + '_natural_key'
                        if natural_key_field_name in obj['fields']:
                            content_type = getattr(o, field.ct_field)
                            content_object_by_natural_key = content_type.model_class().\
                            objects.get_by_natural_key(*obj['fields'][natural_key_field_name])
                            obj['fields'][field.fk_field] = content_object_by_natural_key.pk
                            del obj['fields'][natural_key_field_name]

        for obj in PythonDeserializer(objects, **options):
            yield obj
    except GeneratorExit:
        raise
    except Exception as e:
        six.reraise(DeserializationError, DeserializationError(e), sys.exc_info()[2])

次に、settings.pyで次の構成を設定します。

    SERIALIZATION_MODULES = {
    "json": "path.to.serializer_file"
  }

今、あなたは使用することができます:

python3 manage.py dumpdata --natural-foreign --natural-primary > dump.json

また、一部のデータ(フィルタークエリセット)をダンプする必要がある場合は、コードから作成できます。

from path.to.serializers import Serializer, Deserializer

# Serialize
registers = YourModel.objects.filter(some_attribute=some_value)
dump = Serializer().serialize(registers, use_natural_foreign_keys=True, use_natural_primary_keys=True)

# Deserialize
for deserialized_object in Deserializer(dump, use_natural_foreign_keys=True, use_natural_primary_keys=True):
    print(deserialized_object.object)  # See here https://docs.djangoproject.com/en/2.2/topics/serialization/
于 2022-01-13T16:57:01.467 に答える