Django信号-通过django管理员更新模型时,kwargs ['update_fields']始终为无

时间:2019-02-07 16:57:47

标签: django django-admin django-signals

我的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不可迭代。我已经阅读了有关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中运行该行,它会产生所需的结果。这次怎么了?

4 个答案:

答案 0 :(得分:3)

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

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

如果您是从django admin创建内容并始终获得None,则意味着update_fields没有传递给模型的save方法。因此,它将始终为None

如果您检查ModelAdmin类和save_model方法,您会发现调用是在没有update_fields关键字参数的情况下进行的。

enter image description here

如果您编写自己的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中测试成员资格。

答案 1 :(得分:2)

添加到Davit Tovmasyan的帖子中。我做了一个更通用的版本,其中涵盖了使用for循环进行的所有字段更改:

result = [n for n, v in bank_accounts.items() if sum(v) > 0]

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

答案 2 :(得分:1)

Davit解决方案很好,但是当更改其他字段然后不更改(保存)或在该模型上创建新对象时将导致问题,这将导致问题。

因此需要添加检查tax_rate之外的其他字段是否已更改,或者在创建新对象时,我的解决方案是对他的扩展:

class ProductAdmin(admin.ModelAdmin):

  def save_model(self, request, obj, form, change):
      update_fields = []

      if change: 
         if form.initial['tax_rate'] != form.cleaned_data['tax_rate']:
            update_fields.append('tax_rate')
            obj.save(update_fields=update_fields)
         else:
            obj.save() # on other fields changing
      else:
         obj.save() # save if new model created

答案 3 :(得分: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,这应该没有问题)

优势

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

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