ZipExtFile 转换为 Django File
我在想有没有办法把一个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)做任何你想做的事情。