我的系统有产品,并与它们相关联的图像,如下所示:
class Product(models.Model):
name = models.CharField(max_length=100)
...
class Image(models.Model):
product = models.ForeignKey(Product)
image = models.ImageField(upload_to='products')
到目前为止一切顺利。当然,客户希望以csv格式批量上传他们的产品,并上传包含图像的zip文件。我将csv格式设置为:
product_name,image_1.jpg,image_2.jpg,...
product_2,image.jpg,...
到目前为止,我已经做了一个模型,只是作为一个帮助:
class BulkUpload(models.Model):
csv = models.FileField(upload_to='tmp')
img_zip = models.FileField(upload_to='tmp')
工作流程是这样的:
- 用户通过django admin上传文件
- 获取zip文件内容并存储以供以后使用
- 解压到tmp目录
- 启动事务。如果这里发生任何意外,我们回滚
- csv文件中的每一行
- 使用第一列中指定的名称创建并保存产品。
- 从其他csv字段中抓取图像文件名
- 检查图像是否在zip中,否则回滚
- 检查目标目录中不存在的图像,否则回滚
- 将图像移动到目标目录,并将fk设置为保存的产品对象,如果有错误则回滚。
- 提交事务
- 删除zip和csv文件,并删除批量上传对象(或者干脆不保存)
如果我们在任何时候回滚,我们应该以某种方式通知用户哪里出错了。
我最初的想法是覆盖save或使用postrongave信号,但没有访问请求意味着我既不能使用消息也不能引发验证错误。在管理中重写model_save()有它自己的问题,不能做任何验证。
所以现在我的想法是改变ModelForm并把它给django管理员。我可以重写clean()方法,引发ValidationErrors并(大概)在事务中运行我的所有内容。但我正在努力弄清楚如何以这样一种方式访问文件,我可以在它们上使用Python的ZipFile和csv库。在表单验证方法中做实际工作也感觉有点脏,但我不确定我还能在哪里做到这一点。
我可能讲得太详细了,但是我想解释一下解决方案,以便大家可以提出其他的解决方案。
我认为您不应该使用BulkUpload
或任何表示此操作的模型,至少如果您计划像您目前建议的那样同步执行过程。我会手动或使用第三方库向管理区域添加一个额外的视图,然后在那里处理表单并执行工作流。
但无论如何,鉴于您已经拥有BulkUpload
模型,使用admin.ModelAdmin
对象当然更容易。您主要关心的似乎是应该将交易代码放在哪里。正如你所提到的,有几种选择。在我看来,最好的选择是把这个过程分成两个部分:
首先,在你的模型的clean
方法中,你应该检查所有可能由用户产生的潜在错误:已经存在的图像、缺失的图像、重复的产品等等。在这里,您应该检查上传的文件是否正确,例如使用如下命令:
def clean(self):
if not zipfile.is_zipfile(self.img_zip.file):
raise ValidationError('Not a zip file')
之后,您知道从这一点可能出现的任何错误都将由系统错误产生:bd失败,HD没有足够的空间等,因为所有其他可能的错误都应该在前一步检查过。在ModelAdmin.save_model
方法中,您应该执行工作流程的其余部分。您可以使用ModelAdmin.message_user
通知用户任何错误。
至于上传文件的实际处理,好吧,您命名了它:只需使用标准库中的zipfile和csv模块。您应该创建一个ZipFile对象并将其提取到某个地方。现在,您应该使用csv.reader检查csv文件的数据。如下所示(未测试):
def save_model(self, request, obj, form, change):
# ...
with open('tmp/' + obj.img_zip.name, 'r') as csvfile:
productreader = csv.reader(csvfile)
for product_details in productreader:
p = Product(name=product_details[0])
p.save()
for image in product_details[1:]:
i = ImageField()
i.product = p
i.image = File(open('tmp/' + image)) # not tested
i.save()
在所有这些之后,拥有BulkUpload
实例将没有意义,因此您应该删除它。这就是为什么我一开始就说这个模型有点没用
显然,您需要为事务添加代码和其他一些东西,但我希望您了解总体思路。