Django中Project模型图片压缩Celery任务报错:无法找到实例
Django中Celery异步压缩图片触发Http404的解决方法
问题场景
我定义了如下Project模型:
class Project(models.Model): id = models.UUIDField(primary_key=True, unique=True, default=uuid4, editable=False) logo = models.ImageField(validators=[validate_image_size], blank=True, null=True, default=None) name = models.CharField(max_length=64) description = models.TextField() @transaction.atomic def save(self, *args, **kwargs): super().save(*args, **kwargs) def __str__(self): return self.name
通过post_save信号触发Celery异步任务压缩logo字段:
@receiver(post_save, sender=Project) def compress_project_logo(sender, instance, **kwargs): compress_image.apply_async((instance.id,))
对应的异步任务代码:
@shared_task def compress_image(project_id): from api.models import Project project = get_object_or_404(Project, id=project_id) compressed_image = Image.open(project.logo) compressed_image = compressed_image.convert("RGB") compressed_image = ImageOps.exif_transpose(compressed_image) image_io = BytesIO() compressed_image.save(image_io, "JPEG", quality=70) project.logo = InMemoryUploadedFile(image_io, "ImageField", project.logo.file.name, "image/jpeg", sys.getsizeof(image_io), None) project.save()
但在Django admin中保存Project模型时,Celery任务抛出如下错误:
Traceback (most recent call last): 2023-02-28 10:27:45 File "/usr/local/lib/python3.8/site-packages/celery/app/trace.py", line 450, in trace_task R = retval = fun(*args, **kwargs) File "/usr/local/lib/python3.8/site-packages/celery/app/trace.py", line 731, in __protected_call__ return self.run(*args, **kwargs) File "/code/api/tasks/compress.py", line 14, in compress_image project = get_object_or_404(Project, id=project_id) File "/usr/local/lib/python3.8/site-packages/rest_framework/generics.py", line 19, in get_object_or_404 return _get_object_or_404(queryset, *filter_args, **filter_kwargs) File "/usr/local/lib/python3.8/site-packages/django/shortcuts.py", line 78, in get_object_or_404 raise Http404('No %s matches the given query.' % queryset.model._meta.object_name) django.http.response.Http404: No Project matches the given query.
问题核心原因
这是事务隔离机制导致的:Django admin的保存操作和自定义的save方法都使用了transaction.atomic,当post_save信号触发时,事务尚未提交到数据库,Celery异步任务立即执行时,无法查询到这条刚创建的Project记录,从而抛出404异常。
解决方法
方法1:用transaction.on_commit延迟任务执行
将信号接收器中的任务调用包裹在transaction.on_commit内,确保事务完全提交后再触发Celery任务:
from django.db import transaction @receiver(post_save, sender=Project) def compress_project_logo(sender, instance, **kwargs): transaction.on_commit(lambda: compress_image.apply_async((instance.id,)))
此方法能兼容新增和更新场景,确保任务执行时数据库中已存在目标记录。
方法2:移除save方法中的冗余事务装饰器
当前save方法中的@transaction.atomic是多余的——Django默认会在模型save操作时自动开启事务,移除该装饰器后,保存完成事务立即提交,信号触发的任务就能正常查询到数据:
# 修改后的Project模型save方法 def save(self, *args, **kwargs): super().save(*args, **kwargs)
额外优化建议
- 在异步任务中增加异常捕获,避免因logo为空或记录不存在导致任务失败:
@shared_task def compress_image(project_id): from api.models import Project try: project = Project.objects.get(id=project_id) if not project.logo: return # 图片压缩逻辑... project.save() except Project.DoesNotExist: return
- 确认Celery worker与Django使用同一数据库配置,避免跨数据库查询问题。
内容的提问来源于stack exchange,提问作者Gtihon9
相关产品推荐
相关产品推荐

