Handling file uploads with zope.app.file and zope.file
This How-to applies to:
0.14
This How-to is intended for:
Developer
zope.app.file
Using zope.app.file is a simple way to implement a file uploading feature. If you don't upload large file, it may be enough for you. If you use filestorage, all file data will be stored in the Data.fs. If you are uploading large files, it will consume lots of memory resources.
Edit the configuration file
Add zope.app.file to setup.py
install_requires=['setuptools',
'grok',
'grokui.admin',
'z3c.testsetup',
'zope.app.file',
# Add extra requirements here
],
And run ./bin/buildout. This will install zope.app.file and make it available to your project.
In a more formal development setting, or if you wish to ensure that you can recreate your exact development environment at a later date, you may wish to pin the zope.app.file package to the specific version you are developing against in your buildout.cfg. You can do this by adding:
[versions] zope.app.file = 3.4.4
Implement uploading form
Make a Container object to hold uploaded file(s). filecontainer.py is very simple.
import grok
class FileContainer(grok.Container):
pass
Add a uploading form to filecontainer.py.
import grok
import zope.app.file
from zope.app.container.interfaces import INameChooser
class FileContainer(grok.Container):
pass
class Upload(grok.AddForm):
grok.context(FileContainer)
form_fields = grok.AutoFields(zope.app.file.interfaces.IFile).select('data')
@grok.action('Add file')
def add(self, data):
self.upload(data)
self.redirect(self.url(self.context.__parent__))
def upload(self, data):
fileupload = self.request['form.data']
if fileupload and fileupload.filename:
contenttype = fileupload.headers.get('Content-Type')
file_ = zope.app.file.file.File(data, contenttype)
# use the INameChooser registered for your file upload container
filename = INameChooser(container).chooseName(fileupload.filename)
self.context[filename] = file_
Note that in order for files to have a valid URL in Grok they must not start with the + or @ character, nor can they contain a / character. The "chooseName" method of the INameChooser interface is normally used for choosing filenames, and the default implementation will check for invalid characters, as well as add a number to a filename if it's uploaded multiple times (e.g. myfile.txt, myfile-1.txt, myfile-2.txt). Note that this will only warn you if your filename contains illegal characters, it will also allow spaces in the filename and unicode characters. You might wish to to implement your own INameChooser if you want to enforce a custom filenaming policy for your application.
For example to provide a name chooser which converts leading + and @ characters to 'plus-' and 'at-' text only in the context of uploading files into your FileContainer:
from zope.app.container.interfaces import INameChooser
from zope.app.container.contained import NameChooser
class PrimitiveFilenameChangingNameChooser(grok.Adapter, NameChooser):
grok.implements(INameChooser)
grok.context(FileContainer)
def chooseName(self, name):
if name.startswith('+'):
name = 'plus-' + name[1:]
if name.startswith('@'):
name = 'at-' + name[1:]
To make file viewable to the browser, zope.app.file provides a default view for the File model objects so that you don't have to do anything.
zope.file
Next is zope.file. This is more efficient than zope.app.file, but requires blob storage.
Edit the configuration file
Add blob-dir to buildout.cfg.
[data]
recipe = zc.recipe.filestorage
blob-dir = ${buildout:parts-directory}/data/blobs
Next, Add zope.file and zope.mimetype to setup.py.
install_requires=['setuptools',
'grok',
'grokui.admin',
'z3c.testsetup',
'zope.file',
'zope.mimetype',
# Add extra requirements here
],
And run ./bin/buildout. This will enable blob support to filestorage and install zope.file and zope.mimetype, make them available to your project.
Implement uploading form for FileContainer class and view form for File class
We will use the same filecontainer.py and FileContainer class, but replace Upload class.
import grok
import zope.schema
import zope.file.file
import zope.file.upload
import zope.file.download
class FileContainer(grok.Container):
pass
class Add(grok.AddForm):
grok.context(FileContainer)
form_fields = grok.Fields(
zope.schema.Bytes(__name__='data',
title=u'Upload data',
description=u'Upload file',),
)
@grok.action('Add file')
def addFile(self, data):
self.upload(data)
self.redirect(self.url(self.context.__parent__))
def upload(self, data):
fileupload = self.request['form.data']
file_ = zope.file.file.File()
zope.file.upload.updateBlob(file_, fileupload)
self.context[fileupload.filename] = file_
class FileIndex(zope.file.download.Display, grok.View):
grok.name('index.html')
grok.context(zope.file.file.File)
def render(self):
return self()
FileIndex class is a view class for File class. Because, there is no default view class for File class. Now, you can upload large file to zope and see it. All file data will be stored in the blob directory.
That's all. Comments are welcome!


How-to needs to be updated
Can update to: filename = INameChooser(container).chooseName(fileupload.filename, None)
The zope.file example simply does not work as written.
You receive and error when attempting an upload:
Unsupported: Storing Blobs in <ZODB.FileStorage.FileStorage.FileStorage object at 0x457cc90> is not supported.
I have no idea how to resolve this.