Django signals - kwargs ['update_fields']在通过django管理员更新模型时始终为None

jm2pwxwz  于 2023-03-04  发布在  Go
关注(0)|答案(5)|浏览(114)

我的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中创建一个我的产品模型的示例,然后如果tax_rate或price的值更新了,我可以检查这些并相应地更新list_pricekwargs['update_fields']总是返回None。我错在哪里了?或者有其他方法可以在django管理中实现这个结果吗?

更新章节

现在,假设我在产品模型中引入了一个名为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中运行该行,它产生了预期的结果。这次是什么错误?

fae0ux8s

fae0ux8s1#

字段集应传递给Model.save(),以使它们在update_fields中可用。
像这样

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

如果你在django admin中创建了一些东西,并且总是得到None,这意味着update_fields没有被传递给model的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中测试成员资格。

nfs0ujit

nfs0ujit2#

为了补充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)

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

deyfvvtc

deyfvvtc3#

我想添加一个替代方法,它依赖于pre_保存信号来获取您正在评估的示例的先前版本(来自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,这应该不会有问题)

优点

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

如果您在许多类中都这样做,那么您可能应该考虑其他解决方案(但是公认的答案也不完美!)

qrjkbowd

qrjkbowd4#

你能做到的。

def save_model(self, request, obj, form, change):
        if change:
            obj.save(update_fields=form.changed_data)
        else:
            super().save_model(request, obj, form, change)
qrjkbowd

qrjkbowd5#

只为任何要进来的人准备!
我相信这是此情况的完整解决方案,请注意,如果模型中有任何ManyToMany字段**,则应跳过将其添加到更新字段的步骤**

def save_model(self, request, obj, form, change):
        """
        Given a model instance save it to the database.
        """
        update_fields = set()
        if change:
            for key, value in form.cleaned_data.items():
                # assuming that you have ManyToMany fields that are called groups and user_permissions
                # we want to avoid adding them to update_fields 
                if key in ['user_permissions', 'groups']:
                    continue
                if value != form.initial[key]:
                    update_fields.add(key)

        obj.save(update_fields=update_fields)

相关问题