在Django中使用Signals更新外键值

问题描述

我有两个模型LabelAlternateLabel。每个Label可以具有多个与其关联的AlternateLabel,即它们之间存在ForeignKey关系。

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}"

创建Label时,也会使用AlternateLabel信号创建一个post_save

@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
        )

我还希望在AlternateLabel名称更新时更新Label

@receiver(post_save,sender=Label)
def save_alternatelabel(sender,**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()

但是,在更新Label时出现错误None type object has not method save(),我知道这是由于以下事实造成的:该方法试图使用新的{{1 }}实例AlternateLable不存在。我一直在努力寻找一种方法来实现这一目标。我将不胜感激任何建议或指导。

解决方法

好吧,您可以通过主键访问实例的备用项。

只需使用:

instance.alternatelabel_set.update(name=instance.name)

但是,我建议不要在信号Label上创建方法并使用它们,而不要使用信号 以视图和形式。 例如:

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}"

    def set_name(self,name):
        self.name = name
        self.save()
        self.alternatelabel_set.update(name=name)
,

由于Label可以关联多个AlternateLabel,因此无法使用.update方法。因此,我最终使用了Django pre_save信号。 Label更新后,其id保持不变。因此,在将新的Label名称保存到数据库中之前,请使用instance.id从数据库中获取原始名称,您可以使用该名称来修复AlternateLabel。然后可以对其进行更新。

@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)

此解决方案完全可以正常工作,并且据我所知没有任何缺点。