Search code examples
djangodjango-modelsdjango-viewsdjango-signals

Update Foreignkey values using Signals in Django


I have two models Label and AlternateLabel. Each Label can have multiple AlternateLabel associated with it, that is there is ForeignKey relation between them.

class Label(models.Model):
    name = models.CharField(max_length=55, blank=True, unique=True)  
    lower_range = models.FloatField(null=True, blank=True)
    upper_range = models.FloatField(null=True, blank=True)

    def __str__(self) -> str:
        return f"{self.name}"
class AlternateLabel(models.Model):
    name = models.CharField(max_length=55, blank=False)
    label = models.ForeignKey(to=Label, on_delete=models.CASCADE)

    class Meta:
        unique_together  =('name', 'label')

    def __str__(self) -> str:
        return f"{self.name}"

    def __repr__(self) -> str:
        return f"{self.name}"

When a Label is created one AlternateLabel is also created using post_save signal.

@receiver(post_save, sender=Label)
def create_alternatelabel(sender, instance, created, **kwargs):
    """
    when label is created an alternate label with name as label is also created
    """
    if created:
        AlternateLabel.objects.get_or_create(
            name = instance.name,
            label = instance
        )

I also want AlternateLabel to be updated when Label name is updated For which I tried this

@receiver(post_save, sender=Label)
def save_alternatelabel(sender, instance, created, **kwargs):
    """
    when label is saved an alternate label with name as label is also saved
    """
    Label.alternatelabel_set.filter(
        name=instance.name,
        label = instance
    ).first().save()

But, on updating the Label I am getting error None type object has not method save(), I understand this is due to the fact that, this approach is trying to find the AlternateLable with new Label instance name which doesn't exist. I have been trying to figure out a way to achieve this for hours. I would appreciate any suggestion or guidance.


Solution

  • Since Label can have more than one AlternateLabel associated with it, I cannot use .update method. Therefore I ending up using Django pre_save signal. When Label is updated, its id remains same. So before the new Label name is saved into the database, Use instance.id to get the original name from the database, using which you can get the fix on AlternateLabel. Which can then be updated.

    @receiver(pre_save, sender=Label)
    def save_alternatelabel(sender, instance, **kwargs):
        """
        when label name is updated, an alternate label name is also updated.
        """
        try:# getting old label name
            old_label_name = Label.objects.get(id=instance.id).name
            
            # getting alternate_label with old label name | this will get the alternate label 
            # which has to be changed
            alternate_label = AlternateLabel.objects.get(name = old_label_name)
            
            # changing alternate_label.name to new name
            alternate_label.name = instance.name 
            alternate_label.save()
        except Exception as e:
            print(e)
    

    This solution is working exactly required and has no drawback to the best of my knowledge.