ZipExtFile 转换为 Django File

9 投票
3 回答
3008 浏览
提问于 2025-04-17 08:51

我在想有没有办法把一个zip文件上传到Django的网页服务器,并且把zip里的文件放到Django的数据库里,而不需要在这个过程中访问实际的文件系统(比如,不用把zip里的文件先解压到临时目录再加载)。

Django提供了一个功能,可以把Python的文件转换成Django的文件,所以如果有办法把ZipExtFile转换成Python文件,那就应该没问题。

谢谢大家的帮助!

Django模型:

from django.db import models

class Foo:
    file = models.FileField(upload_to='somewhere')

用法:

from zipfile import ZipFile 
from django.core.exceptions import ValidationError  
from django.core.files import File  
from io import BytesIO  

z = ZipFile('zipFile')
istream = z.open('subfile')
ostream = BytesIO(istream.read())
tmp = Foo(file=File(ostream))
try:
    tmp.full_clean()
except Validation, e:
    print e

输出:

{'file': [u'This field cannot be blank.']}

[解决方案] 使用一个不太优雅的技巧:

正如Don Quest正确指出的,像StringIO或BytesIO这样的类应该能把数据表示成一个虚拟文件。然而,Django File的构造函数只接受内置的文件类型,其他的都不行,虽然这些类也能完成这个工作。这个技巧就是手动设置Django::File中的变量:

buf = bytesarray(OPENED_ZIP_OBJECT.read(FILE_NAME))
tmp_file = BytesIO(buf)
dummy_file = File(tmp_file)   # this line actually fails
dummy_file.name = SOME_RANDOM_NAME
dummy_file.size = len(buf)
dummy_file.file = tmp_file
# dummy file is now valid

如果你有更好的解决方案(除了自定义存储),请继续评论。

3 个回答

1

我使用了下面这个Django文件类,这样就不需要把ZipExtFile读入到其他的数据结构(比如StringIO或BytesIO)中,同时还能正确地实现Django保存文件所需要的功能。

from django.core.files.base import File

class DjangoZipExtFile(File):
    def __init__(self, zipextfile, zipinfo):
        self.file = zipextfile
        self.zipinfo = zipinfo
        self.mode = 'r'
        self.name = zipinfo.filename
        self._size = zipinfo.file_size

    def seek(self, position):
        if position != 0:
            #this will raise an unsupported operation
            return self.file.seek(position)
        #TODO if we have already done a read, reopen file

zipextfile = archive.open(path, 'r')
zipinfo = archive.getinfo(path)
djangofile = DjangoZipExtFile(zipextfile, zipinfo)
storage = DefaultStorage()
result = storage.save(djangofile.name, djangofile)
8

其实有个更简单的方法来做到这一点:

from django.core.files.base import ContentFile

uploaded_zip = zipfile.ZipFile(uploaded_file, 'r')  # ZipFile

for filename in uploaded_zip.namelist():
    with uploaded_zip.open(filename) as f:  # ZipExtFile
        my_django_file = ContentFile(f.read())

用这个方法,你可以把上传到内存中的文件直接转换成Django可以使用的文件。举个更完整的例子,假设你想把一系列的图片文件放在一个压缩包里上传到文件系统:

# some_app/models.py
class Photo(models.Model):
    image = models.ImageField(upload_to='some/upload/path')

...

# Upload code    
from some_app.models import Photo

for filename in uploaded_zip.namelist():
    with uploaded_zip.open(filename) as f:  # ZipExtFile
        new_photo = Photo()
        new_photo.image.save(filename, ContentFile(f.read(), save=True)
7

虽然我对Django了解不多,但我可以告诉你可以看看“io”这个包。你可以这样做:

from zipfile import ZipFile
from io import StringIO
zname,zipextfile = 'zipcontainer.zip', 'file_in_archive'
istream = ZipFile(zname).open(zipextfile)
ostream = StringIO(istream.read())

然后你就可以对你的“虚拟”输出流(Stream)或文件(File)做任何你想做的事情。

撰写回答