Дополнительные поля в отношениях "многие-ко-многим": Как автоматически создавать "сквозные" поля?
Я пытаюсь использовать поле "многие ко многим", чтобы добавить набор объектов в лид. Проблема в том, что мне нужно дополнительное поле для даты и времени запланированного тура для каждого из объектов, которые я добавляю в лид. Таким образом, когда я создаю лид и добавляю в него объекты, у меня есть дополнительное поле, в которое я могу ввести дату и время, а затем получить доступ к нему в виде списка объектов с датами их туров на странице лидов.
Я сталкивался с полями "многие ко многим", используя "через", но я не уверен, что это вообще правильный вариант для использования в данном случае. Поля "многие ко многим" с дополнительными полями
Как я могу использовать поле "многие ко многим", используя "через", и чтобы поле "через" автоматически генерировалось для каждого объекта, который я добавляю к моему лиду с помощью поля "многие ко многим"? Или использование сквозного поля не является хорошим вариантом?
Я использую 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
.