Personal tools
You are here: Home Documentation How-Tos Handling file uploads with zope.app.file and zope.file

Handling file uploads with zope.app.file and zope.file

This How-to applies to: 0.14
This How-to is intended for: Developer

If you want to handle files in zope, you can use the zope.app.file and zope.file packages.

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

Posted by Steve Schmechel at May 27, 2009 09:53 PM
INameChooser's "chooseName" method now requires two parameters - a name and an object.
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.