【问题标题】:Django signals - kwargs['update_fields'] is always None on model update via django adminDjango 信号 - kwargs['update_fields'] 在通过 django admin 更新模型时始终为 None
【发布时间】:2019-07-01 20:58:17
【问题描述】:

我的 django 应用程序中有一个信号,我想检查模型中的某个字段是否已更新,这样我就可以继续执行某些操作。

我的模型看起来像这样......

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.PositiveIntegerField()
    tax_rate = models.PositiveIntegerField()
    display_price = models.PositiveInteger()
    inputed_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL)
    updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL)

我的信号是这样的……

@receiver(post_save, sender=Product)
def update_model(sender, **kwargs):
    instance = kwargs['instance']
    if 'tax_rate' in kwargs['update_fields']:
        # do something

这会返回错误None is not an iterable。我已经阅读了关于update_fields 的django 信号文档,上面写着The set of fields to update as passed to Model.save(), or None if update_fields wasn’t passed to save().

我应该提到我在 django admin 中工作,所以我希望会发生的是,我可以在 django admin 中创建我的 Product 模型的实例,然后如果更新了 tax_rate 或 price 的值,我可以检查对于那些并相应地更新list_price。但是,kwargs['update_fields'] 始终返回 None。 我怎么了?还是有其他方法可以在 django admin 中实现该结果?

更新部分

现在,假设我在我的产品模型中引入了一个名为 inputed_by 的字段,它指向用户模型,并且我希望在首次保存模型时填充该字段。然后另一个字段updated_by 存储最后更新模型的用户。同时我想检查tax_rateprice中的一个或两个是否已更新。

在我的模型管理员中,我有以下方法...

def save_model(self, request, obj, form, change):
    update_fields = []
    if not obj.pk:
        obj.inputed_by = request.user
    elif change:
        obj.updated_by = request.user

        if form.initial['tax_rate'] != form.cleaned_data['tax_rate']:
            update_fields.append('tax_rate')
        if form.initial['price'] != form.cleaned_data['price']:
            update_fields.append('price')

    obj.save(update_fields=update_fields)
    super().save_model(request, obj, form, change)

我的信号现在看起来像这样......

@receiver(post_save, sender=Product, dispatch_uid="update_display_price")
def update_display_price(sender, **kwargs):
    created = kwargs['created']
    instance = kwargs['instance']
    updated = kwargs['update_fields']
    checklist = ['tax_rate', 'price']

    # Prints out the frozenset containing the updated fields and then below that `The update_fields is None`

    print(f'The update_fields is {updated}')

    if created:
        instance.display_price = instance.price+instance.tax_rate
        instance.save()
    elif set(checklist).issubset(updated):
        instance.display_price = instance.price+instance.tax_rate
        instance.save() 

我收到错误'NoneType' object is not iterable 错误似乎来自set(checklist).issubset(updated) 行。我已经尝试在 python shell 中专门运行该行,它产生了预期的结果。这次怎么了?

【问题讨论】:

    标签: django django-admin django-signals


    【解决方案1】:

    我想添加一个替代方案,它依赖于 pre_save 信号来获取您正在评估的实例的先前版本(来自this SO answer):

    @receiver(pre_save, sender=Product)
    def pre_update_model(sender, **kwargs):
        
        # check if the updated fields exist and if you're not creating a new object
        if not kwargs['update_fields'] and kwargs['instance'].id:
            # Save it so it can be used in post_save
            kwargs['instance'].old = User.objects.get(id=kwargs['instance'].id)
    
    
    @receiver(post_save, sender=Product)
    def update_model(sender, **kwargs):
        instance = kwargs['instance']
    
        # Add updated_fields, from old instance, so the method logic remains unchanged
        if not kwargs['update_fields'] and hasattr(instance, 'old'):
            kwargs['update_fields'] = []
            if (kwargs['update_fields'].instance.tax_rate != 
                    kwargs['update_fields'].instance.old.tax_rate):
                kwargs['update_fields'].append('tax_rate')
    
        if 'tax_rate' in kwargs['update_fields']:
    

    accepted answer 相比

    缺点

    • 对每个没有update_fields 的保存进行额外查询(如果您没有向全世界开放 Django Admin,这应该没有问题)

    优势

    • 不需要重写任何方法或类
    • 你只需要实现你要评估的字段的逻辑,并且它们在相同的方法中,所以没有任何错误的借口;)

    如果您为许多课程这样做,您可能应该查看其他解决方案(但接受的答案也不是完美的!)

    【讨论】:

      【解决方案2】:

      应将字段集传递给 Model.save() 以使其在 update_fields 中可用。

      这样

      model.save(update_fields=['tax_rate'])
      

      如果你从 django admin 创建东西并且总是得到None 这意味着update_fields 没有被传递给模型的save 方法。因此,它将永远是None

      如果您检查 ModelAdmin 类和 save_model 方法,您会看到调用发生时没有 update_fields 关键字参数。

      如果您编写自己的save_model,它将起作用。

      下面的代码会解决你的问题:

      class ProductAdmin(admin.ModelAdmin):
          ...
          def save_model(self, request, obj, form, change):
              update_fields = []
      
              # True if something changed in model
              # Note that change is False at the very first time
              if change: 
                  if form.initial['tax_rate'] != form.cleaned_data['tax_rate']:
                      update_fields.append('tax_rate')
      
              obj.save(update_fields=update_fields)
      

      现在您可以在update_model 中测试会员资格。

      【讨论】:

      • 这还不是很完整,这里真正的处理是获取update_fields 的内容,因为当你按顺序点击按钮save 时,你必须弄乱post 请求检测已更改的表单字段。
      • @RaydelMiranda 无需触摸request 数据,检查编辑。
      • 我已经更新了我的问题。你能看一下吗?
      • 删除 save_model 方法的最后一行。超级会再次触发信号。无需在上面的行中保存对象。
      • 这会导致问题,因为您在管理模型本身上覆盖了保存功能,您需要检查使用 save_model() 的其他条件
      【解决方案3】:

      添加到 Davit Tovmasyan 的帖子。我制作了一个更通用的版本,它使用 for 循环涵盖了任何字段更改:

      class ProductAdmin(admin.ModelAdmin): 
          ...
          def save_model(self, request, obj, form, change):
              update_fields = []
              for key, value in form.cleaned_data.items():
                  # True if something changed in model
                  if value != form.initial[key]:
                      update_fields.append(key)
      
              obj.save(update_fields=update_fields)
      

      编辑:警告 这实际上不是一个完整的解决方案。似乎不适用于对象创建,只有更改。我会尽快找出完整的解决方案。

      【讨论】:

      • 只是好奇,您找到完整的解决方案了吗?我在一个字段为 m2m 时遇到问题
      猜你喜欢
      • 2011-05-05
      • 2020-07-27
      • 1970-01-01
      • 2019-12-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-03
      相关资源
      最近更新 更多