Home > Net >  Extra fields on many-to-many relationships: How to automatically create the "through" fiel
Extra fields on many-to-many relationships: How to automatically create the "through" fiel

Time:10-22

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.

  • Related