How can I prevent post_save recursion in Django?

别来无恙 提交于 2020-01-10 02:58:04

问题


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.

  1. 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.

  1. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!