访问ModelForm中的上传文件clean()

时间:2014-05-01 20:10:15

标签: python django django-forms

我的系统有产品,图像与之关联,如下所示:

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')

工作流程如下:

  1. 用户通过django admin
  2. 上传文件
  3. 获取zip文件内容并存储以供日后使用
  4. 将zip解压缩到tmp目录
  5. 开始交易。如果发生任何意外情况,我们会回滚
  6. 对于csv中的每一行
    1. 使用第一列中指定的名称创建和保存产品。
    2. 从其他csv字段中抓取图像文件名
    3. 检查图片是否在zip中,否则回滚
    4. 检查目标目录中是否已存在的图像,否则回滚
    5. 将图像移动到目标目录并将fk设置为已保存的产品对象,回滚任何错误。
    6. 提交交易
    7. 删除zip和csv,然后删除批量上传对象(或者只是不保存)
    8. 如果我们在任何时候回滚,我们应该以某种方式告知用户出了什么问题。

      我最初的想法是覆盖保存或使用post_save信号,但无法访问请求意味着我既不能使用消息也不会引发验证错误。在管理员中覆盖model_save()会遇到问题,无法进行任何验证。

      所以现在我的想法是改变ModelForm并将其交给django管理员。我可以覆盖clean()方法,引发ValidationErrors并(可能)在事务中运行我的所有东西。但我正在努力弄清楚如何以这样的方式访问文件,以便我可以在它们上使用Python的ZipFile和csv库。在表单验证方法中进行实际工作也感觉有点脏,但我不知道我还能在哪里做到。

      我可能已经详细介绍了,但我想解释一下解决方案,以便提出替代解决方案。

1 个答案:

答案 0 :(得分:2)

我认为您不应该使用BulkUpload或任何代表此操作的模型,至少如果您打算同时执行同步过程提示。我会在管理区域添加一个额外的视图,by hand或使用third party library,然后我会处理该表单并执行工作流程。

但无论如何,鉴于您已经拥有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通知用户任何错误。

对于上传文件的实际处理,您将其命名为:只需使用标准库中的zipfilecsv模块即可。您应该创建一个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实例是没有意义的,所以你应该删除它。这就是为什么我在开始时说那个模型有点无用

显然,你需要为交易和其他一些东西添加代码,但我希望你能得到一般的想法。