Циклическая зависимость в сериализаторах 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()