如何实现Django后台删除商品图片时同步删除Firebase Storage中的对应文件?
如何实现Django后台删除商品图片时同步删除Firebase Storage中的对应文件?
我看你已经把Firebase的异步上传逻辑跑通了,现在要实现后台删图同步删Firebase文件的话,咱们得解决两个核心问题:一是怎么记录Firebase上的文件路径,二是怎么在Django删除数据时触发Firebase的删除操作。另外顺便提一下你提到的本地文件存两次的问题,咱们也一起优化掉。
第一步:修改模型,存储Firebase文件路径
现在你的数据库里只存了本地文件的路径,但Firebase的路径是在上传函数里临时生成的,删除时根本拿不到。所以得给Product和ProductImage加专门的字段,把Firebase路径存下来:
# product/models.py class Product(AbstractItem): # ... 原有字段 ... thumbnail = models.ImageField(upload_to=product_thumbnail_upload_path, blank=True, null=True, max_length=255) # 新增:存储缩略图在Firebase的完整路径 thumbnail_firebase_path = models.CharField(max_length=255, blank=True, null=True) # ... 其他字段 ... class ProductImage(models.Model): product = models.ForeignKey(Product, on_delete=models.CASCADE) image = models.ImageField(upload_to=product_image_upload_path, blank=True, null=True, max_length=255) # 新增:存储商品图片在Firebase的完整路径 image_firebase_path = models.CharField(max_length=255, blank=True, null=True) updated = models.DateTimeField(auto_now=True)
第二步:修改上传工具函数,把Firebase路径存到数据库
接下来要调整utils.py里的上传路径生成函数,把生成的Firebase路径绑定到模型字段上,同时解决本地文件存两次的问题:
# product/utils.py def product_image_upload_path(instance, filename): title = getattr(instance.product, 'title', 'unknown') product_title = sanitize_for_directory_name(title) timestamp = timezone.now().strftime('%Y-%m-%d_%H-%M-%S') extension = os.path.splitext(filename)[1] local_path = f'uploads/products/{product_title}/general/{timestamp}{extension}' firebase_path = f'products/{product_title}/general/{timestamp}{extension}' # 把Firebase路径存到ProductImage的对应字段 instance.image_firebase_path = firebase_path # 删掉手动写入本地文件的代码!Django的ImageField会自动处理本地存储,之前的写法导致存了两份 # os.makedirs(os.path.dirname(local_path), exist_ok=True) # with open(local_path, 'wb') as f: # f.write(instance.image.read()) # 直接用Django自动保存的本地文件路径传给Celery任务 upload_to_firebase.delay(instance.image.path, firebase_path, 'Image') return local_path def product_thumbnail_upload_path(instance, filename): title = getattr(instance, 'title', 'unknown') product_title = sanitize_for_directory_name(title) timestamp = timezone.now().strftime('%Y-%m-%d_%H-%M-%S') extension = os.path.splitext(filename)[1] local_path = f'uploads/products/{product_title}/thumbnail/{timestamp}{extension}' firebase_path = f'products/{product_title}/thumbnail/{timestamp}{extension}' # 把Firebase路径存到Product的对应字段 instance.thumbnail_firebase_path = firebase_path # 同样删掉手动写本地文件的代码,避免重复存储 upload_to_firebase.delay(instance.thumbnail.path, firebase_path, 'Thumbnail') return local_path
第三步:创建Firebase删除的Celery任务
和上传任务对应,咱们写一个异步删除的任务,专门处理Firebase文件的删除:
# product/tasks.py @shared_task(name='delete_from_firebase') def delete_from_firebase(firebase_path, instance_type): try: # 调用Firebase Storage的删除方法 storage.delete(firebase_path) print(f"Deleted {instance_type} from Firebase:", firebase_path) except Exception as e: # 可以加日志记录异常,避免任务失败影响Django后台操作 print(f"Failed to delete {instance_type} from Firebase: {str(e)}")
第四步:配置Django信号,触发删除操作
用post_delete信号(推荐用这个,因为即使Firebase删除失败,Django这边的数据已经删掉了,不会卡住后台操作),在删除Product或ProductImage实例时自动调用删除任务:
# product/models.py import os from django.db.models.signals import post_delete from django.dispatch import receiver from .tasks import delete_from_firebase # 处理Product缩略图的删除 @receiver(post_delete, sender=Product) def delete_product_thumbnail_from_firebase(sender, instance, **kwargs): if instance.thumbnail_firebase_path: delete_from_firebase.delay(instance.thumbnail_firebase_path, 'Product Thumbnail') # 可选:删除本地备份文件(如果不需要保留本地副本的话) if instance.thumbnail: try: os.remove(instance.thumbnail.path) except Exception as e: print(f"Failed to delete local thumbnail: {str(e)}") # 处理ProductImage的删除 @receiver(post_delete, sender=ProductImage) def delete_product_image_from_firebase(sender, instance, **kwargs): if instance.image_firebase_path: delete_from_firebase.delay(instance.image_firebase_path, 'Product Image') # 可选:删除本地备份文件 if instance.image: try: os.remove(instance.image.path) except Exception as e: print(f"Failed to delete local image: {str(e)}")
第五步:后台管理优化(可选)
如果不想让管理员看到Firebase路径的字段,可以在admin.py里把这些字段设为不可编辑:
# product/admin.py from django.contrib import admin from .models import Product, ProductImage class ProductAdmin(admin.ModelAdmin): exclude = ('thumbnail_firebase_path',) # ... 其他原有配置 ... class ProductImageAdmin(admin.ModelAdmin): exclude = ('image_firebase_path',) # ... 其他原有配置 ... admin.site.register(Product, ProductAdmin) admin.site.register(ProductImage, ProductImageAdmin)
备注:内容来源于stack exchange,提问作者Anee Mes
相关产品推荐
相关产品推荐

