[albatross-users] file upload capability for Albatross

Andrew McNamara andrewm at object-craft.com.au
Fri Mar 14 10:43:59 EST 2003


> "At some stage we will have to implement file upload capability for
> Albatross."
>
> Any idea when a new release with this capability will happen?

We needed this recently ourselves - this is one of the changes that is
waiting for us to get off our butt and release a new albatross.

Note to self - need to add example type="file" use to doco.

The changelog entry reads:

    Added support for <input type="file">. For inputs of this sort, the
    local context now gets a list of FileField instances. The FileField
    class contains attributes for filename (filename), open file object
    (file), and mime type (type).

    The NameRecorder Mixin tracks whether a type="file" input has
    been seen, and the to_html method of the Form class generates the
    appropriate enctype attribute if seen (otherwise file inputs simply
    pass back the filename).

    On type="file" inputs, "value" attributes are now ignored.

    Currently this is only supported on the cgiapp request class, and
    no documentation or tests have been written.

> Should I make the change to a cgiapp.Request.field_value() as you recommended 
>in an earlier post - and just go with that?  Would a new albatross release do 
>any more for me?

The changes were recently involved, and I'm sure the diff won't apply
cleanly to the 1.01 release. For information purposes only (you probably
don't want to try to apply it unless you're really desperate):

cvs server: Diffing .
Index: cgiapp.py
===================================================================
RCS file: /usr/local/cvsroot/object-craft/albatross/albatross/cgiapp.py,v
retrieving revision 1.28
retrieving revision 1.30
diff -u -r1.28 -r1.30
--- cgiapp.py   29 Nov 2002 10:12:47 -0000      1.28
+++ cgiapp.py   11 Dec 2002 05:08:00 -0000      1.30
@@ -5,9 +5,21 @@
 #
 import cgi, sys, os, string
 
+class FileField:
+    def __init__(self, field):
+        self.filename = field.filename
+        self.file = field.file
+        self.type = field.type
+
+    def __repr__(self):
+        return 'FileField(%s, %s, %s)' % (self.filename, self.file, self.type)
+
 class Request:
-    def __init__(self):
-        self.__fields = cgi.FieldStorage()
+    def __init__(self, fields = None):
+        if fields:
+            self.__fields = fields
+        else:
+            self.__fields = cgi.FieldStorage()
         self.__sent_headers = 0
         self.__status = 200
 
@@ -16,12 +28,18 @@
 
     def field_value(self, name):
         field = self.__fields[name]
-        if type(field) is type([]):
-            value = []
-            for elem in field:
-                value.append(elem.value)
-            return value
+        if isinstance(field, type([])):
+            return map(lambda f: f.value, field)
         return field.value
+
+    def field_file(self, name):
+        field = self.__fields[name]
+        if isinstance(field, type([])):
+            return map(FileField, field)
+        if field.type[:9] == 'multipart':
+            return map(FileField, field.value)
+        else:
+            return [FileField(field)]
 
     def field_names(self):
         return self.__fields.keys()
Index: context.py
===================================================================
RCS file: /usr/local/cvsroot/object-craft/albatross/albatross/context.py,v
retrieving revision 1.72
retrieving revision 1.73
diff -u -r1.72 -r1.73
--- context.py  6 Dec 2002 07:06:34 -0000       1.72
+++ context.py  11 Dec 2002 04:10:48 -0000      1.73
@@ -229,11 +229,14 @@
         return '(%s,%s)' % (self.x, self.y)
 
 class NameRecorderMixin:
+    NORMAL, LIST, FILE = range(3)
+
     def __init__(self):
         self.__elem_names = {}
 
     def form_open(self):
         self.__elem_names = {}
+        self.__need_multipart_enc = 0
 
     def form_close(self):
         text = cPickle.dumps(self.__elem_names, 1)
@@ -245,9 +248,13 @@
         self.write_content(text)
         self.write_content('">\n')
         self.__elem_names = {}
+        return self.__need_multipart_enc
 
     def input_add(self, itype, name, unused_value = None, return_list = 0):
-        if self.__elem_names.has_key(name):
+        if itype == 'file':
+            self.__need_multipart_enc = 1
+            self.__elem_names[name] = self.FILE
+        elif self.__elem_names.has_key(name):
             prev_multiples = self.__elem_names[name]
             implicit_multi = itype in ('radio', 'submit', 'image')
             if not return_list and not implicit_multi:
@@ -261,7 +268,10 @@
                 raise FieldTypeError('"%s" initially defined as "list"' % \
                                      name)
         else:
-            self.__elem_names[name] = return_list
+            if return_list:
+                self.__elem_names[name] = self.LIST
+            else:
+                self.__elem_names[name] = self.NORMAL
 
     def merge_request(self):
         if not self.request.has_field('__albform__'):
@@ -277,8 +287,10 @@
         if not text:
             return
         elem_names = cPickle.loads(text)
-        for name, return_list in elem_names.items():
-            if self.request.has_field(name):
+        for name, mode in elem_names.items():
+            if mode == self.FILE:
+                value = self.request.field_file(name)
+            elif self.request.has_field(name):
                 value = self.request.field_value(name)
             else:
                 x_name = '%s.x' % name
@@ -289,7 +301,7 @@
                                   int(self.request.field_value(y_name)))
                 else:
                     value = None
-            if return_list:
+            if mode == self.LIST:
                 if not value:
                     value = []
                 elif type(value) is not type([]):
Index: tags.py
===================================================================
RCS file: /usr/local/cvsroot/object-craft/albatross/albatross/tags.py,v
retrieving revision 1.100
retrieving revision 1.102
diff -u -r1.100 -r1.102
--- tags.py     18 Nov 2002 23:18:39 -0000      1.100
+++ tags.py     11 Dec 2002 04:10:48 -0000      1.102
@@ -149,18 +149,18 @@
             ctx.write_content(' checked')
         ctx.input_add('checkbox', name, value_attr, self.has_attrib('list'))
 
-    def image_to_html(self, ctx):
+    def generic_novalue_to_html(self, ctx):
         # value= not applicable
-        self.write_attribs_except(ctx)
+        self.write_attribs_except(ctx, 'value')
         name = self.get_name(ctx)
         ctx.write_content(' name="%s"' % name)
-        ctx.input_add('image', name, None, self.has_attrib('list'))
+        ctx.input_add(self.__itype, name, None, self.has_attrib('list'))
 
     type_dict = {
         'text': generic_to_html, 'password': generic_to_html,
         'radio': radio_to_html, 'checkbox': checkbox_to_html,
         'submit': generic_to_html, 'reset': generic_to_html,
-        'image': image_to_html, 'file': generic_to_html,
+        'image': generic_novalue_to_html, 'file': generic_novalue_to_html,
         'hidden': generic_to_html, 'button': generic_to_html, }
 
 # Allows the application need to modify the HREF attribute of the <A>
@@ -229,18 +229,27 @@
 # Using the albatross <FORM> tag allows Albatross to record the
 # contents of each form.  This is used to register valid browser
 # requests with the toolkit.
+#
+# We use a content_trap to capture the enclosed content so we know before
+# emiting the <form> element whether or not to use a multipart/form-data
+# encoding (required to make <input type="file"> work correctly).
 class Form(EnclosingTag):
     name = 'al-form'
 
     def to_html(self, ctx):
+        ctx.push_content_trap()
+        ctx.form_open()
+        EnclosingTag.to_html(self, ctx)
+        use_multipart_enc = ctx.form_close()
+        content = ctx.pop_content_trap()
         ctx.write_content('<form')
         self.write_attribs_except(ctx)
         if not self.has_attrib('action'):
             ctx.write_content(' action="%s"' % ctx.current_url())
+        if use_multipart_enc and not self.has_attrib('enctype'):
+            ctx.write_content(' enctype="multipart/form-data"')
         ctx.write_content('>')
-        ctx.form_open()
-        EnclosingTag.to_html(self, ctx)
-        ctx.form_close()
+        ctx.write_content(content)
         ctx.write_content('</form>')
 
 # Applications which need to know the list of valid <al-select>
@@ -250,6 +259,10 @@
 # Two forms implemented:
 # <al-option>12</al-option>
 # <al-option value="12">Arbitrary text</al-option>
+# 
+# In the first form, we need to evaluate the content of the tag before
+# we output the start tag so we can determine whether or not to set
+# the selected attribute on the option, hence the use of the content_trap.
 class Option(EnclosingTag):
     name = 'al-option'


-- 
Andrew McNamara, Senior Developer, Object Craft
http://www.object-craft.com.au/



More information about the Albatross-users mailing list