Django 信号 - kwargs['update_fields'] 在通过 django admin 更新模型时总是 None
Django signals - kwargs['update_fields'] is always None on model update via django admin
我的 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
这个returns错误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 的值或价格已更新,我可以检查这些并相应地更新 list_price
。但是,kwargs['update_fields']
总是returnsNone。
我错了什么?还是有其他方法可以在 django admin 中实现该结果?
更新部分
现在,假设我在我的产品模型中引入了一个名为 inputed_by
的字段,它指向用户模型,并且我希望在首次保存模型时填充该字段。然后另一个字段 updated_by
存储最后更新模型的用户。同时我想检查 tax_rate
或 price
是否已更新。
在我的模型管理员中,我有以下方法...
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 内的那一行,它产生了预期的结果。这次怎么了?
应将字段集传递给 Model.save()
以使其在 update_fields
中可用。
像这样
model.save(update_fields=['tax_rate'])
如果你从 django admin 创建一些东西并且总是得到 None
这意味着 update_fields
没有被传递给模型的 save
方法。因此,它将永远是 None
.
如果您检查 ModelAdmin
class 和 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
中测试成员资格。
添加到 Davit Tovmasyan 的post。我制作了一个更通用的版本,它使用 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)
编辑:警告这实际上不是一个完整的解决方案。似乎不适用于对象创建,只能更改。我会尽快找出完整的解决方案。
我想添加一个替代方案,它依赖于 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']:
与相比
缺点
- 对没有
update_fields
的每个保存进行额外查询(如果您不向世界开放 Django Admin,这应该没有问题)
优势
- 不需要覆盖任何方法或class
- 你只需要实现你想要评估的字段的逻辑,它们在同一个方法中,所以没有任何错误的借口;)
如果您对许多 classes 执行此操作,您可能应该看看其他解决方案(但公认的答案也并不完美!)
你可以做到这一点。
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)
我的 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
这个returns错误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 的值或价格已更新,我可以检查这些并相应地更新 list_price
。但是,kwargs['update_fields']
总是returnsNone。
我错了什么?还是有其他方法可以在 django admin 中实现该结果?
更新部分
现在,假设我在我的产品模型中引入了一个名为 inputed_by
的字段,它指向用户模型,并且我希望在首次保存模型时填充该字段。然后另一个字段 updated_by
存储最后更新模型的用户。同时我想检查 tax_rate
或 price
是否已更新。
在我的模型管理员中,我有以下方法...
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 内的那一行,它产生了预期的结果。这次怎么了?
应将字段集传递给 Model.save()
以使其在 update_fields
中可用。
像这样
model.save(update_fields=['tax_rate'])
如果你从 django admin 创建一些东西并且总是得到 None
这意味着 update_fields
没有被传递给模型的 save
方法。因此,它将永远是 None
.
如果您检查 ModelAdmin
class 和 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
中测试成员资格。
添加到 Davit Tovmasyan 的post。我制作了一个更通用的版本,它使用 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)
编辑:警告这实际上不是一个完整的解决方案。似乎不适用于对象创建,只能更改。我会尽快找出完整的解决方案。
我想添加一个替代方案,它依赖于 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']:
与相比
缺点
- 对没有
update_fields
的每个保存进行额外查询(如果您不向世界开放 Django Admin,这应该没有问题)
优势
- 不需要覆盖任何方法或class
- 你只需要实现你想要评估的字段的逻辑,它们在同一个方法中,所以没有任何错误的借口;)
如果您对许多 classes 执行此操作,您可能应该看看其他解决方案(但公认的答案也并不完美!)
你可以做到这一点。
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)