如何在Django中防止post_保存递归?

7hiiyaii  于 2023-04-07  发布在  Go
关注(0)|答案(5)|浏览(113)

在Django中使用signal时遇到一些问题。
post_save发生递归,因为函数内部有instance.save()
但奇怪的是只有一种情况发生递归。
1.未发生递归的情况。
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.objects.create()创建Product时,它不会发生递归。
1.出现递归的情况
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

我认为这两种情况有相同的结构,但只有一种情况发生递归。
不知道为什么。需要帮助,谢谢。

xa9qqrwz

xa9qqrwz1#

保存前断开信号,然后重新连接。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)
sz81bmfz

sz81bmfz2#

如果你想避免post_save信号中的递归,只需要使用Model.objects.filter(id=id).update(object=object)

q5iwbnjs

q5iwbnjs3#

只需使用pre_save,您不需要再次在其中使用.save()方法。

8iwquhpp

8iwquhpp4#

在第二种情况下,您将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仍然为true,并且示例再次保存,而不设置价格。这就是您看到的无限递归。
在第一个示例中,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
cnh2zyt3

cnh2zyt35#

你可以做这样的事

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

相关问题