问题
I have some problems when using signal
in Django.
post_save
occurs recursion because of instance.save()
inside of function.
But strange thing is only one case occurs recursion.
- Case not occuring recursion.
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()
When I create Product
using Product.objects.create()
it doesn't occur recursion.
- Case occuring recursion
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()
This case occurs recursion errors:
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
I think those two cases have same structure but only one case occurs recursion.
Have no idea why. Need helps, Thanks.
回答1:
Disconnect the signal before save, then connect again. 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)
回答2:
If you want to avoid recursion in post_save
signal, just use Model.objects.filter(id=id).update(object=object)
回答3:
In the second case, you are comparing the database value of instance.color
to the display value. These will never match. You should check against the database value instead:
@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()
Similarly you should set the default to the database value, i.e. default = 'black'
.
In your original code, all the checks will fail, and instance.price
is never updated to a non-empty value. The call to instance.save()
will trigger the post_save
signal again, not instance.price
is still true, and the instance is saved again without setting the price. This is the infinite recursion you're seeing.
In the first example, the slug
is always set to a non-empty value, so when the post_save
signal is triggered the second time, the if not instance.slug
check will fail, and the instance will not be saved a third time.
In both cases you're saving the instance at least twice if the slug/price is not set. To prevent this, you can use the pre_save
signal. You won't have to save the instance again in the signal handler:
@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
回答4:
Just use pre_save
, you don't need to use .save()
method inside it again.
来源:https://stackoverflow.com/questions/39481625/how-can-i-prevent-post-save-recursion-in-django