Hello World,

I have a table named File is supposed to be referenced by other tables in 
an attempt to simulate the "upload' field type. This table is coupled with 
a Folder table and simulates a file system in the database.

File Fields:
Field('name', 'string', label=T("Name"), requires=IS_NOT_EMPTY(), required=
True, notnull=True, length=255),
Field('caption', 'text', label=T("Caption")),
Field('content_type', 'string', label=T("Content Type"), requires=
IS_NOT_EMPTY(), required=True, notnull=True, length=255),
Field('data_storage', 'upload', label=T("Storage"), uploadfolder=cls.
__UPLOAD_FOLDER__, uploadseparate=True, autodelete=True, required=True, 
notnull=True),
Field('folder_id', cls.__PARENT_CLASS__.Reference(), label=T("Folder"), 
ondelete='CASCADE'),
Field('order_hint', 'integer', label=T("Order")),
Field('absolute_path', compute=lambda row: absolute_path_from_storage(row.
data_storage, cls.__UPLOAD_FOLDER__))

I have built an ORM around pydal, so the code looks a little funny - but 
this problem relates directly to web2py.

Now, I have another table that references this file table. This is fine and 
works well with Smart Grid as long as you are only using file(s) that are 
already in the database. Smart grid provides the usual popup button on an 
html page and you can choose which file you want to reference.

Here is an example field set from a table Building that references the File 
Table:
Field('address', 'string', label=T("Address"), requires=IS_NOT_EMPTY(), 
required=True, notnull=True, length=255),
Field('description', 'text', label=T("Description")),
Field('picture_id', File.Reference(), label=T("Picture"), ondelete='SET 
NULL'),
Field('electric_company', ElectricCompany.Reference(), label=T("Electric 
Company"), ondelete='SET NULL'),
Field('school_district', SchoolDistrict.Reference(), label=T("School 
District"), ondelete='SET NULL'),
Field('services_ids', Service.ListReference(), label=T("Included Services"), 
ondelete='SET NULL')

File.Reference() == 'reference plugin_substratum_file' (The pydal table 
name for the File table is plugin_substratum_file)

Obviously the file reference is now like any other table reference - there 
is no upload capability. I have written a widget and a validator (that are 
automatically added to the picture_id field by my ORM)  that create a file 
input for the field through SQLFORM:
class FileUploadWidget(FormWidget):
    _class = 'file'

    DEFAULT_WIDTH = '150px'
    ID_DELETE_SUFFIX = '__delete'
    GENERIC_DESCRIPTION = 'file ## download'
    DELETE_FILE = 'delete'

    @classmethod
    def widget(cls, field, value, **attributes):
        from gluon.contrib.simplejson import JSONEncoder
    
        """
        generates a INPUT file tag.

        Optionally provides an A link to the file, including a checkbox so
        the file can be deleted.

        All is wrapped in a DIV.

        see also: `FormWidget.widget`

        Args:
            field: the field
            value: the field value
            download_url: url for the file download (default = None)
        """

        default = dict(_type='file')
        attr = cls._attributes(field, default, **attributes)

        attr['_data-show-upload'] = 'false'
        attr['_data-show-close'] = 'false'
        attr['_data-overwrite-initial'] = 'true'

        file = File.With_ID(int(value)) if value else None
        if file:
            json = JSONEncoder()
            attr['_data-initial-preview'] = "<img src={!r} />".format(file.
url)
            attr['_data-initial-caption'] = file.name
            attr['_data-initial-preview-config'] = json.encode([{
                    'caption' : file.name,
                    'key'     : file.id
                }])
            attr['_data-layout-templates'] = json.encode({
                    'actionDelete' : ""
                })

        return  INPUT(**attr)

    @classmethod
    def represent(cls, field, value):
        """
        How to represent the file:

        - with download url and if it is an image: <A href=...><IMG ...></A>
        - otherwise with download url: <A href=...>file</A>
        - otherwise: file

        Args:
            field: the field
            value: the field value
            download_url: url for the file download (default = None)
        """

        inp = current.T(cls.GENERIC_DESCRIPTION)
        file = File.With_ID(int(value)) if value else None

        if file:
            url = file.url
            if cls.is_image(file):
                inp = IMG(_src=url, _width=cls.DEFAULT_WIDTH)
            inp = A(inp, _href=url)

        return inp

    @staticmethod
    def is_image(file):
        """
        Tries to check if the filename provided references to an image

        Checking is based on mime type.

        Args:
            file: File record
        """
        mime_type = file.content_type
        if len(mime_type) > 5 and mime_type[:5] == 'image':
            return True
        return False


The problem is that now the returned value from the HTML form is a 
FieldStorage object instead of the id of a row from the File table. What I 
would like to accomplish is the creation of a new File row, then changing 
the picture_id field value into a reference to that row - this should 
happen before creation or update - but it should not happen if the form 
doesn't fully validate.

My custom validator creates a FileUploadToken object that differentiates it 
from normal 'upload' field types so that I can catch it later in the 
onvalidation function that I pass to SQLFORM.smartgrid:
class FileUploadToken(object):
    def __init__(self, field_storage):
        self.field_storage = field_storage
        
class FILE_VALIDATOR(Validator):
    def __call__(self, value):
        from cgi import FieldStorage
        
        error = None
        if isinstance(value, basestring):
            if not value:
                value = None
        elif isinstance(value, FieldStorage):
            value = FileUploadToken(value)

        return (value, error)

Then in the onvalidation function that I pass to SQLFORM.smartgrid I find 
any FileUploadToken objects and create the new File record and reassign the 
field value to the id of the new record:

def onvalidation(form, super_call=super_validation):
        from plugin_substratum import File
            
        for key,value in form.vars.iteritems():
            if isinstance(value, FileUploadToken):
                new_file = File.Upload(value.field_storage)
                form.vars[key] = new_file.id

This all feels extremely contrived and I have to believe that there is a 
more direct way to accomplish this end. This solution also depends on the 
onvalidation function being passed to SQLFORM.smartgrid - which is 
unacceptable.

Is there a better way to do this? Would a custom field type be a more 
elegant solution?



-- 
Resources:
- http://web2py.com
- http://web2py.com/book (Documentation)
- http://github.com/web2py/web2py (Source code)
- https://code.google.com/p/web2py/issues/list (Report Issues)
--- 
You received this message because you are subscribed to the Google Groups 
"web2py-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to