我在Django中使用
signal
时遇到了一些问题。
post_save
发生递归,因为 instance.save()
在函数内部。
但奇怪的是只有一种情况发生递归。
models.py
class Product(TimeStampedModel):
name = models.CharField(max_length=120)
slug = models.SlugField(null=True, blank=True)
description = models.CharField(max_length=400, blank=True)
is_active = models.BooleanField(default=True)
objects = ProductManager()
class Meta:
ordering = ('-created',)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse(
"products:product_detail",
kwargs={
"slug": self.slug,
}
)
signals.py
@receiver(post_save, sender=Product)
def post_save_product(sender, instance, created, **kwargs):
if not instance.slug:
instance.slug = slugify(instance.name, allow_unicode=True)
instance.save()
当我使用
Product
创建Product.objects.create()
时,它不会发生递归。
models.py
class Variation(TimeStampedModel):
COLOR_CHOICES = (
('black', '흑백'),
('single', '단색'),
('multi', '컬러'),
)
price = models.DecimalField(
decimal_places=2,
max_digits=15,
blank=True,
null=True,
)
product = models.ForeignKey(Product)
color = models.CharField(
max_length=10,
choices=COLOR_CHOICES,
default='흑백'
)
is_active = models.BooleanField(default=True)
class Meta:
ordering = ('product',)
def __str__(self):
return "{product} - {color}".format(
product=self.product,
color=self.color
)
signals.py
@receiver(post_save, sender=Variation)
def post_save_variation(sender, instance, created, **kwargs):
if not instance.price:
if instance.color == '흑백':
instance.price = 40000
elif instance.color == '단색':
instance.price = 50000
elif instance.color == '컬러':
instance.price = 60000
instance.save()
本例出现递归错误:
File "/Users/Chois/.pyenv/versions/spacegraphy/lib/python3.5/site-packages/django/db/models/base.py", line 708, in save
force_update=force_update, update_fields=update_fields)
File "/Users/Chois/.pyenv/versions/spacegraphy/lib/python3.5/site-packages/django/db/models/base.py", line 745, in save_base
update_fields=update_fields, raw=raw, using=using)
File "/Users/Chois/.pyenv/versions/spacegraphy/lib/python3.5/site-packages/django/dispatch/dispatcher.py", line 192, in send
response = receiver(signal=self, sender=sender, **named)
File "/Users/Chois/Dropbox/Workspace/django/spacegraphy-project/spacegraphy/products/signals/post_save.py", line 24, in post_save_variation
instance.save()
File "/Users/Chois/.pyenv/versions/spacegraphy/lib/python3.5/site-packages/django/db/models/base.py", line 708, in save
force_update=force_update, update_fields=update_fields)
File "/Users/Chois/.pyenv/versions/spacegraphy/lib/python3.5/site-packages/django/db/models/base.py", line 745, in save_base
update_fields=update_fields, raw=raw, using=using)
File "/Users/Chois/.pyenv/versions/spacegraphy/lib/python3.5/site-packages/django/dispatch/dispatcher.py", line 192, in send
response = receiver(signal=self, sender=sender, **named)
File "/Users/Chois/Dropbox/Workspace/django/spacegraphy-project/spacegraphy/products/signals/post_save.py", line 24, in post_save_variation
instance.save()
File "/Users/Chois/.pyenv/versions/spacegraphy/lib/python3.5/site-packages/django/db/models/base.py", line 708, in save
force_update=force_update, update_fields=update_fields)
File "/Users/Chois/.pyenv/versions/spacegraphy/lib/python3.5/site-packages/django/db/models/base.py", line 736, in save_base
updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
File "/Users/Chois/.pyenv/versions/spacegraphy/lib/python3.5/site-packages/django/db/models/base.py", line 796, in _save_table
base_qs = cls._base_manager.using(using)
File "/Users/Chois/.pyenv/versions/spacegraphy/lib/python3.5/site-packages/django/db/models/manager.py", line 122, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/Users/Chois/.pyenv/versions/spacegraphy/lib/python3.5/site-packages/django/db/models/manager.py", line 214, in get_queryset
return self._queryset_class(model=self.model, using=self._db, hints=self._hints)
File "/Users/Chois/.pyenv/versions/spacegraphy/lib/python3.5/site-packages/django/db/models/query.py", line 171, in __init__
self.query = query or sql.Query(self.model)
File "/Users/Chois/.pyenv/versions/spacegraphy/lib/python3.5/site-packages/django/db/models/sql/query.py", line 155, in __init__
self.where = where()
RecursionError: maximum recursion depth exceeded while calling a Python object
我认为这两个案例具有相同的结构,但只有一个案例发生递归。
不知道为什么。需要帮助,谢谢。
保存前断开信号,然后重新连接。 https://docs.djangoproject.com/en/1.10/topics/signals/#disconnecting-signals
def post_save_product(sender, instance, **kwargs):
post_save.disconnect(post_save_product, sender=sender)
instance.do_stuff()
instance.save()
post_save.connect(post_save_product, sender=sender)
post_save.connect(post_save_product, sender= Product)
如果你想避免在
post_save
信号中递归,只需使用 Model.objects.filter(id=id).update(object=object)
只需使用
pre_save
,你不需要再次使用里面的.save()
方法。
在第二种情况下,您正在将
instance.color
的数据库值与显示值进行比较。这些永远不会匹配。您应该检查数据库值:
@receiver(post_save, sender=Variation)
def post_save_variation(sender, instance, created, **kwargs):
if not instance.price:
if instance.color == 'black':
instance.price = 40000
elif instance.color == 'single':
instance.price = 50000
elif instance.color == 'multi':
instance.price = 60000
instance.save()
同样,您应该将默认值设置为数据库值,即
default = 'black'
。
在您的原始代码中,所有检查都会失败,并且
instance.price
永远不会更新为非空值。调用instance.save()
会再次触发post_save
信号,not instance.price
仍然为真,再次保存实例,不设置价格。这就是你看到的无限递归。
第一个例子中,
slug
一直设置为非空值,所以第二次触发post_save
信号时,if not instance.slug
检查会失败,第三次不会保存实例时间。
在这两种情况下,如果未设置 slug/price,您将至少保存实例两次。为防止这种情况,您可以使用
pre_save
信号。您不必在信号处理程序中再次保存实例:
@receiver(pre_save, sender=Variation)
def pre_save_variation(sender, instance, **kwargs):
if not instance.price:
if instance.color == 'black':
instance.price = 40000
elif instance.color == 'single':
instance.price = 50000
elif instance.color == 'multi':
instance.price = 60000
你可以这样做
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=MyModel)
def my_model_post_save(sender, instance, **kwargs):
if kwargs.get('raw', False): # skip signal for objects created during fixture loading
return
if not getattr(instance, '_skip_signal', False):
try:
instance._skip_signal = True
# Your post_save code here
instance.save()
finally:
instance._skip_signal = False
我使用 .save() 递归触发我的接收函数时遇到了同样的问题。
我通过使用 update() 方法而不是 save() 解决了这个问题。这是我的代码:
@receiver(post_save, sender=User)
def update_consultant_fields(instance, created, **kwargs):
if not created:
try:
consultant = instance.as_consultant
except Consultants.DoesNotExist:
consultant = Consultants.objects.create(user=instance)
for field in ['first_name', 'last_name', 'email']:
setattr(consultant, field, getattr(instance, field, None))
Consultants.objects.filter(pk=consultant.id).update(**{field: getattr(instance, field) for field in ['first_name', 'last_name', 'email']})
@receiver(post_save, sender=Consultants)
def update_user_fields(instance, created, **kwargs):
if not created:
try:
user = instance.user
except User.DoesNotExist:
return
for field in ['first_name', 'last_name', 'email']:
setattr(user, field, getattr(instance, field, None))
user.save(update_fields=['first_name', 'last_name', 'email'])
精华部分在这里:
for field in ['first_name', 'last_name', 'email']:
setattr(consultant, field, getattr(instance, field, None))
Consultants.objects.filter(pk=consultant.id).update(**{field: getattr(instance, field) for field in ['first_name', 'last_name', 'email']})
事实上,我首先迭代我的所有字段(在顾问模型中)并将顾问实例的属性设置为匹配用户实例属性的值(在用户模型中)。
然后,进入更新方法,我正在解压一个字典理解,它将使用每个字段的名称和这个相关字段的值。
由于更新方法来自 QuerySet 类,不能从实例调用,我不得不过滤 Consultants 模型,其中主键设置为我的顾问实例的 ID。由此,我能够使用 update() 方法
那个的结论是我现在可以通过更新顾问模型自动更新我的用户表,反之亦然。