Циклическая зависимость в сериализаторах Django Rest Framework

Я борюсь с круговыми зависимостями внутри сериализаторов в своем веб-API, написанном с использованием Django Rest Framework 3. Принимая во внимание, что круговые зависимости в проекте почти всегда являются признаком плохого дизайна, я не могу найти достойный способ избегая этого, не делая приложение большим монолитным кошмаром.

Простые урезанные примеры изображений достаточно хорошо, что происходит во всех местах, у меня такая же проблема.

Пусть в двух приложениях есть две простые модели:

Приложение профилей

# 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)

Следуя принципу моделей жира, я часто использую несколько импортов в своих моделях, чтобы облегчить поиск связанных объектов с использованием методов в профиле, но это редко вызывает круговые зависимости, поскольку я редко делаю то же самое с другого конца.

Проблема начинается, когда я пытаюсь добавить сериализаторы в связку. Чтобы уменьшить площадь интерфейса API и ограничить количество необходимых вызовов до минимума, я хочу сериализовать на обоих концах некоторые связанные объекты в упрощенных формах.

Я хочу получить профили на конечной точке /profile, которая будет иметь упрощенную информацию о нескольких последних изображениях, созданных пользователем, вложенных. Кроме того, при получении изображений из конечной точки /images я хотел бы иметь информацию профиля, встроенную в образ JSON.

Чтобы добиться этого и избежать рекурсивного вложения, у меня есть два сериализатора - один из которых содержит связанные объекты, а другой - для обоих приложений.

Приложение профилей

# 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:

Приложение профилей в профилях

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

Приложение для изображений в/изображениях

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

но затем я ударил стену круговыми ввозами сериализаторов.

Я чувствую, что присоединение этих двух приложений к одному, определенно, не к лучшему - ведь изображения - это нечто совершенно отличное от пользовательских профилей.

Сериализаторы также, на мой взгляд, должны относиться к их соответствующим приложениям.

Единственный способ обойти эту проблему, которую я нашел сейчас, - это импорт в методе следующим образом:

class ImageSerializer(SimplifiedProfileSerializer):
    profile = SerializerMethodField()

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

но это похоже на уродливый, уродливый, неудобный хак.

Не могли бы вы поделиться своим опытом с подобными проблемами?

Спасибо!

Ответы

Ответ 1

По-моему, ваш код в порядке, потому что у вас нет логической циклической зависимости.

Ваш ImportError возникает только из-за того, что import() оценивает операторы верхнего уровня всего файла при вызове.

Однако в python ничего невозможного...

Есть способ обойти это, если вы действительно хотите, чтобы ваш импорт был сверху:

От David Beazleys отличный разговор Модули и пакеты: Live и Let Die! - PyCon 2015, 1:54:00, вот способ работы с циклическим импортом в python:

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

Это пытается импортировать SimplifiedImageSerializer, и если ImportError поднят, потому что он уже импортирован, он вытащит его из importcache.

PS: Вы должны прочитать весь этот пост в голосе Дэвида Бэйсли.

Ответ 2

Я бы принял другой подход, поскольку у вас есть связь так или иначе. Я бы пошел с определением сериализатора, который я фактически использовал в самом приложении.

Приложение профиля

# profiles/serializers.py

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

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

Image application

# images/serializers.py

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

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

Ответ 3

Разделение обычных и вложенных сериализаторов помогает мне.

Для вашей структуры это будет что-то вроде:

Приложение "Профили"

# 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()