web-dev-qa-db-ja.com

Django Rest Frameworkシリアライザの循環依存

Django Rest Framework 3。アプリを大きなモノリシックな悪夢にすることなく、それを回避する適切な方法を見つけます。

すべての場所で何が起こっているかを示す、単純なストリップダウンの例の画像は、同様の問題を抱えています。

2つのアプリに2つのシンプルなモデルがあるとします。

プロフィールアプリ

# profiles/models.py

from images.models import Image

class Profile(models.Model):
    name = models.CharField(max_length=140)  

    def recent_images(self):
        return Image.objects.recent_images_for_user(self)

画像アプリ

# images/models.py

class Image(models.Model):
    profile = models.ForeignKey('profiles.Profile')
    title = models.CharField(max_length=140)

fat modelsの原則に従って、プロファイルでメソッドを使用して関連オブジェクトを簡単に取得できるように、モデルで複数のインポートを使用することがよくありますが、循環する依存関係が発生することはほとんどありません。 。

束にserializersを追加しようとすると、問題が始まります。 APIフットプリントを小さくし、必要な呼び出しの量を最小限に抑えるために、関連するオブジェクトの一部を簡略化された形式で両端でシリアル化します。

/profileエンドポイントのプロファイルを取得できるようにしたいのですが、ネストされたユーザーが作成した最近のいくつかの画像に関する情報が簡略化されています。また、/imagesエンドポイントから画像を取得する場合、画像のJSONにプロファイル情報を埋め込みたいです。

これを実現し、再帰的なネストを回避するために、2つのシリアライザーを使用しています。1つは関連するオブジェクトをネストし、もう1つはネストしません。

プロフィールアプリ

# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

class SimplifiedProfileSerializer(serializers.Serializer):
    name = serializers.CharField()

class ProfileSerializer(SimplifiedProfileSerializer):
    recent_images = SimplifiedImageSerializer(many=True)

画像アプリ

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

class SimplifiedImageSerializer(serializers.Serializer):
    title = serializers.CharField()

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

予想される動作は、次のJSON結果を取得することです。

/ profilesのプロフィールアプリ

[{
    'name': 'Test profile',
    'recent_images': [{
        'title': 'Test image 1'
    }, {
        'title': 'Test image 2'
    }]
]]

/ imagesの画像アプリ

[{
    'title': 'Test image 1',
    'profile': {
        'name': 'Test profile'
    }
},
{
    'title': 'Test image 2',
    'profile': {
        'name': 'Test profile'
    }
}]

でも、シリアライザの循環インポートで壁にぶつかった。

これらの2つのアプリを1つに結合することは間違いなく進むべき道ではないと感じています-結局のところ、画像はユーザープロファイルとはまったく異なるものです。

私の見解では、シリアライザもそれぞれのアプリに属している必要があります。

この時点で私が見つけたこの問題を回避する唯一の方法は、次の方法でインポートすることです。

class ImageSerializer(SimplifiedProfileSerializer):
    profile = SerializerMethodField()

    def get_profile(self, instance):
        from profiles.serializers import SimplifiedProfileSerializer
        return SimplifiedProfileSerializer(instance.profile).data

glyglyuuglyハックのように感じます。

同様の問題についてあなたの経験を共有していただけませんか?

ありがとう!

25

logic循環依存関係がないため、私の意見ではコードは問題ありません。

ImportErrorは、呼び出されたときにimport()がファイル全体の最上位のステートメントを評価する方法が原因で発生します。

ただし、Pythonでは不可能はありません...

確実にインポートしたい場合は、回避策があります

David Beazleysからのすばらしい講演 モジュールとパッケージ:Live and Let Die!-PyCon 20151:54:00、Pythonで循環インポートを処理する方法を次に示します。

try:
    from images.serializers import SimplifiedImageSerializer
except ImportError:
    import sys
    SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']

これはSimplifiedImageSerializerをインポートしようとしますが、ImportErrorが発生した場合は、すでにインポートされているため、インポートキャッシュからプルされます。

PS:この投稿全体をDavid Beazleyの声で読む必要があります。

22
Sebastian Wozny

次のようなシリアライザのローカルインポートを実行できます。

class MySerializer(Serializer):
    from app.core.serializers import AnotherSerializer

両方のインポートでそれを行います。 sys.modulesを使用する必要はありません

つまり、セバスチャンウォズニーが述べたように、論理的な循環依存関係はありません。

1
Exis Zhang

通常のシリアライザと入れ子になったシリアライザを分離することが私にとってはうまくいきます。

あなたの構造では、それは次のようなものになります:

プロフィールアプリ

# profiles/serializers/common.py

from images.serializers.nested import SimplifiedImageSerializer

class ProfileSerializer(SimplifiedProfileSerializer):
    recent_images = SimplifiedImageSerializer(many=True)

そしてネスト:

# profiles/serializers/nested.py

class SimplifiedProfileSerializer(serializers.Serializer):
    name = serializers.CharField()

画像アプリ

# images/serializers/common.py

from profiles.serializers.nested import SimplifiedProfileSerializer

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

そしてネスト:

# images/serializers/nested.py

class SimplifiedImageSerializer(serializers.Serializer):
    title = serializers.CharField()
0
Andrii Dubonos

あなたが何らかの方法でカップリングを持っているので、私は別のアプローチを取るでしょう。アプリケーション自体の中で実際に使用するシリアライザーを定義します。

プロファイルアプリケーション

# profiles/serializers.py

class SimplifiedImageSerializer(serializers.Serializer):
    title = serializers.CharField()

class ProfileSerializer(SimplifiedProfileSerializer):
    recent_images = SimplifiedImageSerializer(many=True)

画像アプリケーション

# images/serializers.py

class SimplifiedProfileSerializer(serializers.Serializer):
    name = serializers.CharField()

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()
0
Linovia