I'm trying to use a many to many field in order to add a set of facilities to a lead. The trouble is that i need an extra field for the date and time of the tour scheduled for each of the facilities i add to the lead. So when i create a lead and add facilities to the lead i have that extra field where i can enter date and time and access it afterwards as a list of facilities with their tour dates on the leads page.
I came across many to many fields using "through" but I'm not sure if that's even the right option to use in this case. ManyToMany Fields with extra fields
How can i use a many to many field using "through" and have the through field automatically be generated for each facility i add to my lead with the many to many field? Or is using through not a good option?
I'm using Django Rest Framework with a 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(() => {
})
}
CodePudding user response:
Using through= seems the way to go in your case. To achieve it you need to create a Model for your M2M table
LeadFacility(models.Model):
facility = models.ForeignKey(Facility, on_delete=models.CASCADE)
lead = models.ForeignKey(Lead, on_delete=models.CASCADE)
datetime = models.DateTimeField()
You can set the datetime value using the following syntax :
my_lead.assigned_facilities.add(my_facility, through_defautlts={"datetime": my_datetime})
Or simply creating the LeadFacilty explicitly :
LeadFacility.objects.create(lead=my_lead, facility=my_facility, datetime=my_datetime)
To access those fields, you'll to define related_names in the LeadFacility model
CodePudding user response:
ManyToManyField
is basically create another association model on Database level to associate ModelA(id) with ModelB(id). We are unable to add additional fields in it.
Now in your case, let's restructure the schema.
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
: The fields remains same in this Model
.
Class Model
: Remove assigned_facilities
field from because we no longer need it.
Class LeadFacilityAssociation
: Adding this Model
to associate the Lead
and Facility
with additional scheduled_datetime
field. Now you are able to mapped multiple Leads
with multiple Facility
with separate scheduled_datetime
.
Serializer
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
Note
This serializer is designed to create only one Lead
with one Facility
. For creating multiple facilities you can change the overrided method create
. Use bulk_create
for creating multiple facilities against Lead
. Also change the to_representation
method body for returning multiple facilities against Lead
.