More on uploads

Consider the following model:

  1. db.define_table('myfile',
  2. Field('image', 'upload', default='path/to/file'))

In the case of an “upload” field, the default value can optionally be set to a path (an absolute path or a path relative to the current app folder), the default value is then assigned to each new record that does not specify an image.

Notice that this way multiple records may end to reference the same default image file and this could be a problem on a Field having autodelete enabled. When you do not want to allow duplicates for the image field (i.e. multiple records referencing the same file) but still want to set a default value for the “upload” then you need a way to copy the default file for each new record that does not specify an image. This can be obtained using a file-like object referencing the default file as the default argument to Field, or even with:

  1. Field('image', 'upload', default=dict(data='<file_content>', filename='<file_name>'))

Normally an insert is handled automatically via a SQLFORM or a crud form (which is a SQLFORM) but occasionally you already have the file on the filesystem and want to upload it programmatically. This can be done in this way:

  1. with open(filename, 'rb') as stream:
  2. db.myfile.insert(image=db.myfile.image.store(stream, filename))

It is also possible to insert a file in a simpler way and have the insert method call store automatically:

  1. with open(filename, 'rb') as stream:
  2. db.myfile.insert(image=stream)

In this case the filename is obtained from the stream object if available.

The store method of the upload field object takes a file stream and a filename. It uses the filename to determine the extension (type) of the file, creates a new temp name for the file (according to web2py upload mechanism) and loads the file content in this new temp file (under the uploads folder unless specified otherwise). It returns the new temp name, which is then stored in the image field of the db.myfile table.

Note, if the file is to be stored in an associated blob field rather than the file system, the store method will not insert the file in the blob field (because store is called before the insert), so the file must be explicitly inserted into the blob field:

  1. db.define_table('myfile',
  2. Field('image', 'upload', uploadfield='image_file'),
  3. Field('image_file', 'blob'))
  4. with open(filename, 'rb') as stream:
  5. db.myfile.insert(image=db.myfile.image.store(stream, filename),
  6. image_file=stream.read())

The retrieve method does the opposite of store.

When uploaded files are stored on filesystem (as in the case of a plain Field('image', 'upload')) the code:

  1. row = db(db.myfile).select().first()
  2. (filename, fullname) = db.myfile.image.retrieve(row.image, nameonly=True)

retrieves the original file name (filename) as seen by the user at upload time and the name of stored file (fullname, with path relative to application folder). While in general the call:

  1. (filename, stream) = db.myfile.image.retrieve(row.image)

retrieves the original file name (filename) and a file-like object ready to access uploaded file data (stream).

Notice that the stream returned by retrieve is a real file object in the case that uploaded files are stored on filesystem. In that case remember to close the file when you have done calling stream.close().

Here is an example of safe usage of retrieve:

  1. from contextlib import closing
  2. import shutil
  3. row = db(db.myfile).select().first()
  4. (filename, stream) = db.myfile.image.retrieve(row.image)
  5. with closing(stream) as src, closing(open(filename, 'wb')) as dest:
  6. shutil.copyfileobj(src, dest)