[Zope3-checkins] SVN: Zope3/branches/jhauser-filefieldwidget/src/zope/ Implemented the interfaces as discussed on the mailinglist. Put the

Janko Hauser jhauser at zscout.de
Tue Jan 18 17:29:06 EST 2005


Log message for revision 28865:
  Implemented the interfaces as discussed on the mailinglist. Put the
  field, widget and mime implementation where they belong. Currently 
  not handled is the encoding handling and the display of text input 
  fields. Changed IFile, File to use the new mime schema field.
  

Changed:
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/configure.zcml
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/file.py
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/interfaces.py
  D   Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/mimefield.py
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/__init__.py
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/configure.zcml
  A   Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/tests/test_mimewidget.py
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/textwidgets.py
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/schema/__init__.py
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/schema/_field.py
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/schema/interfaces.py
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/schema/tests/test_strfield.py

-=-
Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/configure.zcml
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/configure.zcml	2005-01-18 22:24:27 UTC (rev 28864)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/configure.zcml	2005-01-18 22:29:04 UTC (rev 28865)
@@ -95,16 +95,21 @@
       permission="zope.ManageContent"
       />
 
-  <!-- define another widget for the mimefield -->
-  <view
-      type="zope.publisher.interfaces.browser.IBrowserRequest"
-      for=".mimefield.IFileData"
-      provides="zope.app.form.interfaces.IInputWidget"
-      factory=".mimefield.FileDataWidget"
-      permission="zope.Public"
-      />
+  <!-- handle the mime field value as content -->
+  <content class=".file.Mime">
 
+    <factory
+        id="zope.app.file.Mime"
+        title="Mime field Value"
+        description="Mime field Value" />
 
+    <require
+        permission="zope.ManageContent"
+        set_schema="zope.app.file.interfaces.IMime"
+        />
+
+  </content>
+
   <!-- include browser package -->
 
   <include package=".browser" />

Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/file.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/file.py	2005-01-18 22:24:27 UTC (rev 28864)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/file.py	2005-01-18 22:29:04 UTC (rev 28865)
@@ -22,35 +22,35 @@
 from zope.interface import implements
 
 from zope.publisher.browser import FileUpload
-from interfaces import IFile, IFileContent
+from interfaces import IMime, IFile, IFileContent
 
 # set the size of the chunks
 MAXCHUNKSIZE = 1 << 16
 
-class File(Persistent):
+class Mime(Persistent):
     """A persistent content component storing binary file data
 
     Let's test the constructor:
 
-    >>> file = File()
+    >>> file = Mime()
     >>> file.contentType
     ''
     >>> file.data
     ''
 
-    >>> file = File('Foobar')
+    >>> file = Mime('Foobar')
     >>> file.contentType
     ''
     >>> file.data
     'Foobar'
 
-    >>> file = File('Foobar', 'text/plain')
+    >>> file = Mime('Foobar', 'text/plain')
     >>> file.contentType
     'text/plain'
     >>> file.data
     'Foobar'
 
-    >>> file = File(data='Foobar', contentType='text/plain')
+    >>> file = Mime(data='Foobar', contentType='text/plain')
     >>> file.contentType
     'text/plain'
     >>> file.data
@@ -59,7 +59,7 @@
 
     Let's test the mutators:
 
-    >>> file = File()
+    >>> file = Mime()
     >>> file.contentType = 'text/plain'
     >>> file.contentType
     'text/plain'
@@ -76,7 +76,7 @@
 
     Let's test large data input:
 
-    >>> file = File()
+    >>> file = Mime()
 
     Insert as string:
 
@@ -111,13 +111,13 @@
     Last, but not least, verify the interface:
 
     >>> from zope.interface.verify import verifyClass
-    >>> IFile.implementedBy(File)
+    >>> IMime.implementedBy(Mime)
     True
-    >>> verifyClass(IFile, File)
+    >>> verifyClass(IMime, Mime)
     True
     """
     
-    implements(IFileContent, IFile)
+    implements(IMime)
 
     def __init__(self, data='', contentType=''):
         self.data = data
@@ -130,6 +130,7 @@
             return self._data
 
     def _setData(self, data):
+        # XXX can probably be removed
         # Handle case when data is a string
         if isinstance(data, unicode):
             data = data.encode('UTF-8')
@@ -213,6 +214,133 @@
         '''See `IFile`'''
         return self._size
 
+    def open(self, mode='r'):
+        pass
+
+    data = property(_getData, _setData)
+    
+
+class File(Persistent):
+    """
+    Let's test the constructor:
+
+    >>> file = File()
+    >>> file.data
+    ''
+
+    >>> file = File('Foobar')
+    >>> file.contentType
+    ''
+    >>> file.data
+    'Foobar'
+
+    >>> file = File('Foobar', 'text/plain')
+    >>> file.contentType
+    'text/plain'
+    >>> file.data
+    'Foobar'
+
+    >>> file = File(data='Foobar', contentType='text/plain')
+    >>> file.contentType
+    'text/plain'
+    >>> file.data
+    'Foobar'
+
+
+    Let's test the mutators:
+
+    >>> file = File()
+    >>> file.contentType = 'text/plain'
+    >>> file.contentType
+    'text/plain'
+
+    >>> file.data = 'Foobar'
+    >>> file.data
+    'Foobar'
+
+    >>> file.data = None
+    Traceback (most recent call last):
+    ...
+    TypeError: Cannot set None data on a file.
+
+
+    Let's test large data input:
+
+    >>> file = File()
+
+    Insert as string:
+
+    >>> file.data = 'Foobar'*60000
+    >>> file.getSize()
+    360000
+    >>> file.data == 'Foobar'*60000
+    True
+
+    Insert data as FileChunk:
+
+    >>> fc = FileChunk('Foobar'*4000)
+    >>> file.data = fc
+    >>> file.getSize()
+    24000
+    >>> file.data == 'Foobar'*4000
+    True
+
+    Insert data from file object:
+
+    >>> import cStringIO
+    >>> sio = cStringIO.StringIO()
+    >>> sio.write('Foobar'*100000)
+    >>> sio.seek(0)
+    >>> file.data = sio
+    >>> file.getSize()
+    600000
+    >>> file.data == 'Foobar'*100000
+    True
+
+
+    Last, but not least, verify the interface:
+
+    >>> from zope.interface.verify import verifyClass
+    >>> IFile.implementedBy(File)
+    True
+    >>> verifyClass(IFile, File)
+    True
+    """
+
+    implements(IFile, IFileContent)
+    
+    def __init__(self, data='', contentType=''):
+        self.contents = Mime()
+        self.contents.data = data
+        self.contentType = contentType
+
+    # old compatibility methods
+    def _getData(self):
+        if isinstance(self.contents._data, FileChunk):
+            # XXX here we loose our memory efficient handling
+            return str(self.contents._data)
+        else:
+            return self.contents._data
+
+    def _setData(self, data):
+        self.contents.data = data
+
+## Leads to maximum recursion erro ?
+##     # new access to file data
+##     def _getContents(self):
+##         return self.contents.open()
+
+##     def _setContents(self, data):
+##         self.contents = Mime()
+##         contents = getattr(self, 'contents')
+##         contents.data = data
+##         return
+
+##    contents = property(_getContents, _setContents)
+
+    def getSize(self):
+        return self.contents.getSize()
+    
     # See IFile.
     data = property(_getData, _setData)
 

Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/interfaces.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/interfaces.py	2005-01-18 22:24:27 UTC (rev 28864)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/interfaces.py	2005-01-18 22:29:04 UTC (rev 28865)
@@ -17,11 +17,11 @@
 """
 __docformat__ = 'restructuredtext'
 
-from zope.schema import BytesLine, Bytes
+from zope.schema import BytesLine, Bytes, Mime
 from zope.interface import Interface
 from zope.app.i18n import ZopeMessageIDFactory as _
 
-class IFile(Interface):
+class IMime(Interface):
 
     contentType = BytesLine(
         title = _(u'Content Type'),
@@ -31,6 +31,14 @@
         missing_value=''
         )
 
+    encoding = BytesLine(
+        title = _(u'Encoding type'),
+        description=_(u'The encoding of the data if it is text.'),
+        default='',
+        required=False,
+        missing_value=''
+        )
+
     data = Bytes(
         title=_(u'Data'),
         description=_(u'The actual content of the object.'),
@@ -42,7 +50,31 @@
     def getSize():
         """Return the byte-size of the data of the object."""
 
+    def open(mode='r'):
+        """Return a file like object for reading or updating."""
+    
+class IFile(Interface):
 
+    contents = Mime(
+        title = _(u'The mime data'),
+        description = _(u'The mime data, which can be read as a file.'),
+        default='',
+        missing_value='',
+        required=False,
+        )
+    
+    data = Bytes(
+        title=_(u'Data'),
+        description=_(u'The actual content of the object.'),
+        default='',
+        missing_value='',
+        required=False,
+        )
+
+    def getSize():
+        """Return the byte-size of the data of the object."""
+
+
 class IFileContent(Interface):
     """Marker interface for content that can be managed as files.
 

Deleted: Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/mimefield.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/mimefield.py	2005-01-18 22:24:27 UTC (rev 28864)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/mimefield.py	2005-01-18 22:29:04 UTC (rev 28865)
@@ -1,171 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""File content component
-
-$Id:$
-"""
-__docformat__ = 'restructuredtext'
-
-from persistent import Persistent
-from transaction import get_transaction
-from zope.interface import implements
-
-from zope.schema.interfaces import IBytes
-from zope.schema._bootstrapfields import Field
-from zope.schema._bootstrapfields import TextLine
-from zope.schema._field import Bytes
-from zope.app.file.file import File
-
-# import for the FileDataWidget
-from zope.app.form.browser import FileWidget
-from zope.app.form.browser.widget import renderElement
-
-from zope.i18nmessageid import MessageIDFactory
-_ = MessageIDFactory("zope")
-
-from interfaces import IFile
-
-#
-# The basic schema interface
-#
-class IMime(IBytes):
-    u"""Fields which hold data characterized by a mime type.
-
-    The data is stored memory effecient.
-    """
-
-    contentType = TextLine(title=_(u"Mime type"),
-                        description=_(u"The mime type of the stored data"),
-                        required=False,
-                        default=u"application/octet-stream"
-                        )
-
-    def getSize():
-        u"""Return the size of the stored data in bytes."""
-
-class IFileData(IMime):
-    u"""Fields which hold uploaded data, mainly file type data"""
-    
-    filename = TextLine(title=_(u"Filename"),
-                        description=_(u"The Filename of the uploaded file"),
-                        required=False)
-    
-# The field implementation, does currently assume to handle file-like data
-class FileData(Bytes):
-    u"""A field implementation for uploaded files. """
-
-    implements(IFileData)
-
-    def set(self, obj, value):
-        """
-        Do a two phase save, first create an empty file object, make it persistent
-        than read the data into it in chunks, to reduce memory consumption.
-
-        'value' is a FileUpload object.
-        """
-        if self.readonly:
-            raise TypeError("Can't set values on read-only fields "
-                            "(name=%s, class=%s.%s)"
-                            % (self.__name__,
-                               obj.__class__.__module__,
-                               obj.__class__.__name__))
-        # now create an empty file object and store it at the persistent object
-        setattr(obj, self.__name__, FileDataValue())
-        file = getattr(obj, self.__name__)
-        # now do the upload in chunks
-        file.data = value 
-        filename = self._extractFilename(value)
-        file.filename = filename
-
-    def _validate(self, value):
-        # just test for the seek method of FileUpload instances.
-        if value and not getattr(value, 'seek',''):
-            raise WrongType(value, self._type)
-
-    def _extractContentType(self, data):
-        u"""Extract the content type for the given data"""
-        # XXX Need to call some function here
-        return 'application/octet-stream'
-    
-    def _extractFilename(self, data):
-        # if it is a fileupload object
-        if hasattr(data,'filename'):
-            fid = data.filename
-            # some browsers include the full pathname
-            fid=fid[max(fid.rfind('/'),
-                        fid.rfind('\\'),
-                        fid.rfind(':')
-                              )+1:]
-            return fid
-        else:
-            return ''
-
-class FileDataWidget(FileWidget):
-    u"""a simple file upload widget"""
-
-    type = 'file'
-
-    def __call__(self):
-        # XXX set the width to 40 to be sure to recognize this widget
-        displayMaxWidth = self.displayMaxWidth or 0
-        if displayMaxWidth > 0:
-            return renderElement(self.tag,
-                                 type=self.type,
-                                 name=self.name,
-                                 id=self.name,
-                                 cssClass=self.cssClass,
-                                 size=40,
-                                 maxlength=40,
-                                 extra=self.extra)
-        else:
-            return renderElement(self.tag,
-                                 type=self.type,
-                                 name=self.name,
-                                 id=self.name,
-                                 cssClass=self.cssClass,
-                                 size=40,
-                                 extra=self.extra)
-
-    def _toFieldValue(self, input):
-        if input == '':
-            return self.context.missing_value
-        try:
-            seek = input.seek
-            read = input.read
-        except AttributeError, e:
-            raise ConversionError('Form input is not a file object', e)
-        else:
-            if getattr(input, 'filename', ''):
-                return input
-            else:
-                return self.context.missing_value
-
-    def applyChanges(self, content):
-        field = self.context
-        value = self.getInputValue()
-        # need to test for value, as an empty field is not an error, but
-        # the current file should not be replaced.
-        if value and (field.query(content, self) != value):
-            field.set(content, value)
-            return True
-        else:
-            return False
-
-class FileDataValue(File):
-    u"""Inherit a normal file content object."""
-
-    def __init__(self, *args):
-        super(File, self).__init__(*args)
-        self.filename = ''
-

Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/__init__.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/__init__.py	2005-01-18 22:24:27 UTC (rev 28864)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/__init__.py	2005-01-18 22:29:04 UTC (rev 28865)
@@ -28,6 +28,7 @@
 from zope.app.form.browser.textwidgets import DatetimeDisplayWidget
 from zope.app.form.browser.textwidgets import DateDisplayWidget
 from zope.app.form.browser.textwidgets import BytesDisplayWidget
+from zope.app.form.browser.textwidgets import MimeDisplayWidget, MimeWidget
 
 # Widgets for boolean fields
 from zope.app.form.browser.boolwidgets import CheckBoxWidget

Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/configure.zcml
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/configure.zcml	2005-01-18 22:24:27 UTC (rev 28864)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/configure.zcml	2005-01-18 22:29:04 UTC (rev 28865)
@@ -138,6 +138,22 @@
 
   <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"
+      for="zope.schema.interfaces.IMime"
+      provides="zope.app.form.interfaces.IInputWidget"
+      factory=".MimeWidget"
+      permission="zope.Public"
+      />
+
+  <view
+      type="zope.publisher.interfaces.browser.IBrowserRequest"
+      for="zope.schema.interfaces.IMime"
+      provides="zope.app.form.interfaces.IDisplayWidget"
+      factory=".MimeDisplayWidget"
+      permission="zope.Public"
+      />
+
+  <view
+      type="zope.publisher.interfaces.browser.IBrowserRequest"
       for="zope.schema.interfaces.IASCII"
       provides="zope.app.form.interfaces.IInputWidget"
       factory=".BytesAreaWidget"

Added: Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/tests/test_mimewidget.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/tests/test_mimewidget.py	2005-01-18 22:24:27 UTC (rev 28864)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/tests/test_mimewidget.py	2005-01-18 22:29:04 UTC (rev 28865)
@@ -0,0 +1,73 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""File Widget tests
+
+$Id:$
+"""
+import unittest, doctest
+
+from StringIO import StringIO
+from zope.app.form.interfaces import IInputWidget
+from zope.app.form.browser import MimeWidget
+
+from zope.app.form.browser.tests.test_browserwidget import SimpleInputWidgetTest
+
+from zope.interface.verify import verifyClass
+
+class MimeWidgetTest(SimpleInputWidgetTest):
+    """Documents and tests the mime widget.
+    
+        >>> verifyClass(IInputWidget, MimeWidget)
+        True
+    """
+
+    _WidgetFactory = MimeWidget
+
+    def setUp(self):
+        super(MimeWidgetTest, self).setUp()
+        file = StringIO('Foo Value')
+        file.filename = 'test.txt'
+        self._widget.request.form['field.foo'] = file
+
+    def testProperties(self):
+        self.assertEqual(self._widget.tag, 'input')
+        self.assertEqual(self._widget.type, 'file')
+        self.assertEqual(self._widget.cssClass, '')
+        self.assertEqual(self._widget.extra, '')
+        self.assertEqual(self._widget.default, '')
+        self.assertEqual(self._widget.displayWidth, 20)
+        self.assertEqual(self._widget.displayMaxWidth, '')
+
+    def testRender(self):
+        value = 'Foo Value'
+        self._widget.setRenderedValue(value)
+        check_list = ('type="file"', 'id="field.foo"', 'name="field.foo"',
+                      'size="40"')
+
+        self.verifyResult(self._widget(), check_list)
+        check_list = ('type="hidden"',) + check_list[1:-1]
+        self.verifyResult(self._widget.hidden(), check_list)
+        check_list = ('style="color: red"',) + check_list
+        self._widget.extra = 'style="color: red"'
+        self.verifyResult(self._widget.hidden(), check_list)
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(MimeWidgetTest),
+        doctest.DocTestSuite(),
+        ))
+
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')

Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/textwidgets.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/textwidgets.py	2005-01-18 22:24:27 UTC (rev 28864)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/textwidgets.py	2005-01-18 22:29:04 UTC (rev 28865)
@@ -196,6 +196,7 @@
         else:
             content = self.context.default
         return renderElement("pre", contents=escape(content))
+    
 
 class ASCII(Bytes):
     """ASCII"""
@@ -423,6 +424,75 @@
             else:
                 return self.context.missing_value
 
+class MimeWidget(TextWidget):
+    u"""Mime file upload widget"""
+
+    type = 'file'
+
+    def __call__(self):
+        # XXX set the width to 40 to be sure to recognize this widget
+        displayMaxWidth = self.displayMaxWidth or 0
+        if displayMaxWidth > 0:
+            return renderElement(self.tag,
+                                 type=self.type,
+                                 name=self.name,
+                                 id=self.name,
+                                 cssClass=self.cssClass,
+                                 size=40,
+                                 maxlength=40,
+                                 extra=self.extra)
+        else:
+            return renderElement(self.tag,
+                                 type=self.type,
+                                 name=self.name,
+                                 id=self.name,
+                                 cssClass=self.cssClass,
+                                 size=40,
+                                 extra=self.extra)
+
+    def _toFieldValue(self, input):
+        if input == '':
+            return self.context.missing_value
+        try:
+            seek = input.seek
+            read = input.read
+        except AttributeError, e:
+            raise ConversionError('Form input is not a file object', e)
+        else:
+            # if the FileUpload instance has no filename set, there is
+            # no upload.
+            if getattr(input, 'filename', ''):
+                return input
+            else:
+                return self.context.missing_value
+
+    def applyChanges(self, content):
+        field = self.context
+        value = self.getInputValue()
+        # need to test for value, as an empty field is not an error, but
+        # the current file should not be replaced.
+        if value and (field.query(content, self) != value):
+            field.set(content, value)
+            return True
+        else:
+            return False
+
+class MimeDisplayWidget(DisplayWidget):
+    """Mime data display widget."""
+    # There need to be probably some widget options to determine how
+    # the file is displayed, e.g. as a download link.
+
+    def __call__(self):
+        if self._renderedValueSet():
+            content = self._data
+        else:
+            content = self.context.default
+
+        show = u"Filename %s, size in bytes: %s" (content.filename,
+                                                  content.getSize())
+        return renderElement("span", contents=escape(show))
+   
+    
 class IntWidget(TextWidget):
     displayWidth = 10
 

Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/schema/__init__.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/schema/__init__.py	2005-01-18 22:24:27 UTC (rev 28864)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/schema/__init__.py	2005-01-18 22:29:04 UTC (rev 28865)
@@ -17,7 +17,7 @@
 """
 from zope.schema._field import Field, Container, Iterable, Orderable
 from zope.schema._field import MinMaxLen, Choice
-from zope.schema._field import Bytes, ASCII, BytesLine
+from zope.schema._field import Bytes, Mime, ASCII, BytesLine
 from zope.schema._field import Text, TextLine, Bool, Int, Float
 from zope.schema._field import Tuple, List, Set
 from zope.schema._field import Password, Dict, Datetime, Date, SourceText

Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/schema/_field.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/schema/_field.py	2005-01-18 22:24:27 UTC (rev 28864)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/schema/_field.py	2005-01-18 22:29:04 UTC (rev 28865)
@@ -28,7 +28,7 @@
 from zope.schema.interfaces import IMinMaxLen, IText, ITextLine
 from zope.schema.interfaces import ISourceText
 from zope.schema.interfaces import IInterfaceField
-from zope.schema.interfaces import IBytes, IASCII, IBytesLine
+from zope.schema.interfaces import IBytes, IMime, IASCII, IBytesLine
 from zope.schema.interfaces import IBool, IInt, IFloat, IDatetime
 from zope.schema.interfaces import IChoice, ITuple, IList, ISet, IDict
 from zope.schema.interfaces import IPassword, IObject, IDate
@@ -94,6 +94,55 @@
         self.validate(v)
         return v
 
+class Mime(Bytes):
+    __doc__ = IMime.__doc__
+    implements(IMime)
+
+    _type = str # Is this needed for the WrongType exception?
+
+    def set(self, obj, value):
+        """
+        Do a two phase save, first create an empty file-like object, make it
+        persistent, than read the data into it in chunks, to reduce memory
+        consumption.
+
+        'value' is a file-like, most often an FileUpload() object.
+        """
+        if self.readonly:
+            raise TypeError("Can't set values on read-only fields "
+                            "(name=%s, class=%s.%s)"
+                            % (self.__name__,
+                               obj.__class__.__module__,
+                               obj.__class__.__name__))
+        # now create an empty file object and store it at the persistent object
+        setattr(obj, self.__name__, MimeData())
+        file = getattr(obj, self.__name__)
+        # now do the upload in chunks
+        file.data = value
+        # save additional information 
+        file.filename = self._extractFilename(value)
+        small_body = file.read(64)
+        file.seek(0) # XXX needed?
+        file.contentType = guess_content_type(name=file.filename, body=small_body)
+
+    def _validate(self, value):
+        # just test that there is a read method, more is not needed.
+        if getattr(value, 'read',''):
+            return
+        super(Bytes, self)._validate(value)
+
+    def _extractFilename(self, data):
+        # if it is a fileupload object
+        if hasattr(data,'filename'):
+            fid = data.filename
+            # sometimes the full pathname is included
+            fid=fid[max(fid.rfind('/'),
+                        fid.rfind('\\'), # escaped backslash
+                        fid.rfind(':'))+1:]
+            return fid
+        else:
+            return ''
+
 class ASCII(Bytes):
     __doc__ = IASCII.__doc__
     implements(IASCII)

Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/schema/interfaces.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/schema/interfaces.py	2005-01-18 22:24:27 UTC (rev 28864)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/schema/interfaces.py	2005-01-18 22:29:04 UTC (rev 28865)
@@ -274,6 +274,12 @@
     The value might be constrained to be with length limits.
     """
 
+class IMime(IBytes):
+    u"""Field holding a byte string (in an efficient data structure).
+
+    The type of the data is described by it's mime type.
+    """
+    
 class IASCII(IBytes):
     u"""Field containing a 7-bit ASCII string. No characters > DEL
     (chr(127)) are allowed

Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/schema/tests/test_strfield.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/schema/tests/test_strfield.py	2005-01-18 22:24:27 UTC (rev 28864)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/schema/tests/test_strfield.py	2005-01-18 22:29:04 UTC (rev 28865)
@@ -16,7 +16,7 @@
 $Id$
 """
 from unittest import TestSuite, main, makeSuite
-from zope.schema import Bytes, BytesLine, Text, TextLine
+from zope.schema import Bytes, Mime, BytesLine, Text, TextLine
 from zope.schema.interfaces import ValidationError
 from zope.schema.interfaces import RequiredMissing, InvalidValue
 from zope.schema.interfaces import TooShort, TooLong, ConstraintNotSatisfied
@@ -101,7 +101,15 @@
         field = self._Field_Factory()
         self.assertRaises(ValidationError, field.validate, u'hello')
 
+class MimeTest(StrTest, MultiLine):
+    _Field_Factory = Mime
+    _convert = str
+    dummy_file = open('__init__.py','r')
 
+    def testValidateFile(self):
+        field = self._Field_Factory()
+        field.validate(self.dummy_file)
+    
 class TextTest(StrTest, MultiLine):
     _Field_Factory = Text
     def _convert(self, v):
@@ -129,6 +137,7 @@
 def test_suite():
     return TestSuite((
         makeSuite(BytesTest),
+        makeSuite(MimeTest),
         makeSuite(TextTest),
         makeSuite(LineTest),
         makeSuite(TextLineTest),



More information about the Zope3-Checkins mailing list