Дополнительные поля в отношениях "многие-ко-многим": Как автоматически создавать "сквозные" поля?

Я пытаюсь использовать поле "многие ко многим", чтобы добавить набор объектов в лид. Проблема в том, что мне нужно дополнительное поле для даты и времени запланированного тура для каждого из объектов, которые я добавляю в лид. Таким образом, когда я создаю лид и добавляю в него объекты, у меня есть дополнительное поле, в которое я могу ввести дату и время, а затем получить доступ к нему в виде списка объектов с датами их туров на странице лидов.

Я сталкивался с полями "многие ко многим", используя "через", но я не уверен, что это вообще правильный вариант для использования в данном случае. Поля "многие ко многим" с дополнительными полями

Как я могу использовать поле "многие ко многим", используя "через", и чтобы поле "через" автоматически генерировалось для каждого объекта, который я добавляю к моему лиду с помощью поля "многие ко многим"? Или использование сквозного поля не является хорошим вариантом?

Я использую Django Rest Framework с React Frontend:

models.py

class Facility(models.Model):

    Name = models.CharField(max_length=150, null=True, blank=False)
    mainimage = models.ImageField(null=True, blank=True)
    Email = models.EmailField(max_length=150, null=True, blank=True)
    TelephoneNumber = models.CharField(max_length=30, null=True, blank=True)
    FacilityDescription = models.TextField(max_length=1000, null=True, blank=True)

    def __str__(self):
        return self.Name


class Lead(models.Model):
    assigned_facilities = models.ManyToManyField(Facility,  related_name='assigned_facilities', null=True, blank=True)

    first_name = models.CharField(max_length=40, null=True, blank=True)
    last_name = models.CharField(max_length=40, null=True, blank=True)


    def __str__(self):
        return f"{self.first_name} {self.last_name}"

serializers.py

class LeadUpdateSerializer(serializers.ModelSerializer):
    is_owner = serializers.SerializerMethodField()
    class Meta:
        model = Lead
        fields = (
            "id",
            "first_name",
            "last_name",
            "assigned_facilities",
        )
        read_only_fields = ("id")

    def get_is_owner(self, obj):
        user = self.context["request"].user
        return obj.agent == user

Leads.js

    const cpBoard = useSelector((state) => state.cpBoard);
    const facilityIds = (cpBoard.cpBoardItems?.map(cpBoardItem => (cpBoardItem.id)));

    function submitFacilities() {

        axios.patch(API.leads.update(id), { "assigned_facilities": facilityIds}, {

            headers: {
                "Authorization": `Bearer ${accessToken}`,
                'Accept' : 'application/json',
            },
            withCredentials: true,
        })
            .then(res => {
                fetchLeads()

            })
            .finally(() => {                
            })
    }

Использование through= кажется подходящим способом в вашем случае. Чтобы добиться этого, вам нужно создать модель для вашей таблицы M2M

LeadFacility(models.Model):
    facility = models.ForeignKey(Facility, on_delete=models.CASCADE)
    lead = models.ForeignKey(Lead, on_delete=models.CASCADE)
    datetime = models.DateTimeField()

Вы можете установить значение времени даты, используя следующий синтаксис :

my_lead.assigned_facilities.add(my_facility, through_defautlts={"datetime": my_datetime})

Или просто создать LeadFacilty явным образом :

LeadFacility.objects.create(lead=my_lead, facility=my_facility, datetime=my_datetime)

Для доступа к этим полям необходимо определить related_names в модели LeadFacility

ManyToManyField по сути это создание еще одной ассоциативной модели на уровне базы данных, чтобы связать ModelA(id) с ModelB(id). Мы не можем добавить в нее дополнительные поля.

Теперь в вашем случае давайте реструктурируем схему.

class Facility(models.Model):

    name = models.CharField(max_length=150, null=True, blank=False)
    main_image = models.ImageField(null=True, blank=True)
    email = models.EmailField(max_length=150, null=True, blank=True)
    telephone_number = models.CharField(max_length=30, null=True, blank=True)
    facility_description = models.TextField(max_length=1000, null=True, blank=True)

    def __str__(self):
        return self.Name


class Lead(models.Model):
    first_name = models.CharField(max_length=40, null=True, blank=True)
    last_name = models.CharField(max_length=40, null=True, blank=True)

    def __str__(self):
        return f"{self.first_name} {self.last_name}"


class LeadFacilityAssociation(models.Model):
    assigned_facilities = models.ForeignKey(Facility,  related_name='leadfacilityassociation')
    lead = models.ForeignKey(Lead,  related_name='leadfacilityassociation')
    scheduled_datetime = models.DateTimeField(null=True, blank=True)


Class Facility: Поля остаются теми же в этом Model.

Class Model: Удалите поле assigned_facilities, потому что оно нам больше не нужно.

Class LeadFacilityAssociation: Добавление этого Model позволяет связать Lead и Facility с дополнительным scheduled_datetime полем. Теперь вы сможете сопоставить несколько Leads с несколькими Facility с отдельными scheduled_datetime.

Сериализатор

class LeadUpdateSerializer(serializers.ModelSerializer):
    is_owner = serializers.SerializerMethodField()
    assigned_facilities = serializers.Integer(required=True)
    scheduled_datetime = serializers.DateTimeField(required=True)

    class Meta:
        model = Lead
        fields = (
            "id",
            "first_name",
            "last_name",
            "assigned_facilities",
            "scheduled_datetime",
        )
        read_only_fields = ("id")

    def get_is_owner(self, obj):
        user = self.context["request"].user
        return obj.agent == user
    
    def create(self, validated_data):
        assigned_facilities = validated_data.pop("assigned_facilities")
        scheduled_datetime = validated_data.pop("scheduled_datetime")
        instance = Lead.objects.create(**validated_data)
        instance.leadfacilityassociation.create(assigned_facilities=assigned_facilities,scheduled_datetime=scheduled_datetime)
        return instance

    def to_representation(self, instance):
        ret = super().to_representation(instance)
        ret["scheduled_datetime"] = str(instance.leadfacilityassociation.first().scheduled_datetime)
        ret["assigned_facilities"] = instance.leadfacilityassociation.first().assigned_facilities
        return ret

Примечание. Данный сериализатор предназначен для создания только одного Lead с одним Facility. Для создания нескольких объектов вы можете изменить переопределенный метод create. Используйте bulk_create для создания нескольких объектов против Lead. Также измените тело метода to_representation для возврата нескольких объектов против Lead.

Вернуться на верх