Нужен архитектурный совет о том, как обрабатывать и / или избегать циклического импорта при использовании Django / DRF

Я хотел бы получить совет относительно архитектурной проблемы, с которой я сталкивался много раз. У меня есть модельное событие в events.py:

# models.py
import events.serializers

class Event(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=255, db_index=True)
    ...

    def save(self, *args, **kwargs):
        is_new = not self.pk
        super().save(*args, **kwargs)
        if is_new:
            self.notify_organiser_followers()

    def notify_organiser_followers(self):
        if self.organiser:
            event_data = events.serializers.BaseEventSerializer(self).data
            payload = {'title': f'New event by {self.organiser.name}',
                       'body': f'{self.organiser.name} has just created a new event: {self.name}',
                       'data': {'event': event_data}}
            send_fcm_messages(self.organiser.followers.all(), payload)

Модель имеет сериализатор BaseEventSerializer. В методе сохранения я использую notify_organiser_followers и в процессе сериализации текущего сохраняемого события. Для этого мне нужно импортировать BaseEventSerializer.

Вот как выглядит код events.serializers:

# serializers.py
import events.models

class EventTrackSerializer(serializers.ModelSerializer):
    class Meta:
        model = events.models.EventTrack
        fields = ('id', 'name', 'color')


class BaseEventSerializer(serializers.ModelSerializer):
    event_type = serializers.CharField(source='get_event_type_display')
    locations = serializers.SerializerMethodField()

Как видите, serializers.py должен импортировать модели, чтобы использовать их в ModelSerializer. На этом этапе я получаю очевидный круговой импорт.

Я решил это путем локального импорта BaseEventSerializer в функцию notify_organiser_followers:

def notify_organiser_followers(self):
    if self.organiser:
        from events.serializers import BaseEventSerializer

Это устраняет проблему, но я бы очень хотел избежать этого, особенно потому, что мне пришлось бы делать одно и то же исправление в нескольких местах моего репо. Другой подход, о котором я подумал, - разделить «обычные» сериализаторы и сериализаторы моделей на отдельные файлы. Тем не менее, это все еще чувствует, что это только исцеление симптома, а не причины.

Я хотел бы получить совет о том, как вообще избежать этой ситуации. У меня была такая же проблема при импорте двух разных приложений, которые используют сериализатор друг друга. Например, Пользователь сериализует События, которые он посещает, Событие сериализует своих посетителей. Как бы вы развели эти две модели? Кажется, что модельные отношения часто вынуждают меня в такие ситуации, и избежать кругового импорта становится действительно сложно.

Я также был бы признателен, если бы у вас были более крупные проекты Django / DRF Github, демонстрирующие, как этого избежать, поскольку эта проблема продолжает появляться для меня, как только мое приложение становится достаточно большим.

Всего 1 ответ


Есть несколько разных способов сделать это, и, вероятно, разные комбинации стратегий.

Я рекомендовал бы здесь использовать сигналы django и создать класс обслуживания.

Во-первых, используйте сигналы django для запуска определенных действий после события Model . Django имеет встроенные слушатели, когда такие вещи, как save происходят на модели, и вы можете выполнять разные вещи, основываясь на этом. Это позволяет вам отделить ваш слой событий от вашей модели.

Для одиночной части: я думаю, что метод Models не должен выполнять действия, которые не связаны с самим собой. Я думаю, что было бы лучше создать какой-то общий сервис в виде синглтона, что-то вроде

EventService(): 
   def do_event_related_logic(self, event):
       ...

event_service = EventService()

и вы можете просто импортировать event_service и использовать этот класс для всей логики, связанной с событием. В этом случае вы можете вызвать один из методов вашего синглтона для события post_save .


Есть идеи?

10000