Image Widgets ============= This is an infrastructure to create an image file widget that behaves as much as possible like a normal text widget in formlib. Normally a file widget loses its file data when a form is re-presented for reasons of failing form validation. A ``hurry.file`` widget retains the file, for example by storing it in a session. In order to do this, we have a special way to store file data along with its filename: >>> import os >>> from waeup.ikoba.image import IkobaImageFile >>> testimage = os.path.join(os.path.dirname(__file__), 'sample.jpg') >>> testimage2 = os.path.join(os.path.dirname(__file__), 'sample2.jpg') >>> some_file = IkobaImageFile('foo.jpg', open(testimage, 'rb').read()) >>> some_file.filename 'foo.jpg' >>> 'Created with GIMP' in some_file.data True We can provide a download widget. In this case, there's nothing to download: >>> from hurry.file.browser import DownloadWidget >>> from hurry.file.schema import File >>> from waeup.ikoba.image.schema import ImageFile >>> from zope.publisher.browser import TestRequest >>> field = ImageFile(__name__='foo', title=u'Foo') >>> field = field.bind(None) >>> request = TestRequest() >>> widget = DownloadWidget(field, request) >>> widget() u'
Download not available
' Even if there were data in the request, there'd be nothing to download: >>> from zope.publisher.browser import FileUpload >>> request = TestRequest(form={'field.foo': FileUpload(some_file)}) >>> widget = DownloadWidget(field, request) >>> widget() u'
Download not available
' Now set a value: >>> widget.setRenderedValue(some_file) >>> widget() u'foo.jpg' Instead of downloading, we can also use a thumbnail widget: >>> from waeup.ikoba.image.browser import ThumbnailWidget >>> request = TestRequest(form={'field.foo': FileUpload(some_file)}) >>> widget = ThumbnailWidget(field, request) >>> widget.setRenderedValue(some_file) >>> widget() u'' Now on to an edit widget. First the case in an add form with no data already available, and no data in request: >>> from waeup.ikoba.image.browser import EncodingImageFileWidget >>> field = ImageFile(__name__='foo', title=u'Foo', required=False) >>> field = field.bind(None) >>> request = TestRequest() >>> widget = EncodingImageFileWidget(field, request) >>> def normalize(s): ... return u'\n '.join(filter(None, s.split(' '))) >>> print normalize(widget()) Now let's try a situation where data is available in the request, but it's an empty string for the file: >>> request = TestRequest(form={'field.foo': u''}) >>> widget = EncodingImageFileWidget(field, request) >>> def normalize(s): ... return '\n '.join(filter(None, s.split(' '))) >>> print normalize(widget()) Now let's render again when there's already available data. What should show up is an extra, hidden field which contains the file_id: >>> widget.setRenderedValue(some_file) >>> print normalize(widget()) ... ... (foo.jpg) Now let's render again, this time with file data available in the request instead. The same should happen: >>> request = TestRequest(form={'field.foo': FileUpload(some_file)}) >>> widget = EncodingImageFileWidget(field, request) >>> print normalize(widget()) ... ... (foo.jpg) Now let's render again, this time not with file data available in the request, but an id. Again, we should see the same: >>> request = TestRequest(form={'field.foo.file_id': ... 'Zm9vLnR4dAp0aGUgY29udGVudHM='}) >>> widget = EncodingImageFileWidget(field, request) >>> print normalize(widget()) ... ... (foo.txt) If there is both file data and an id, something else happens. First, let's prepare some new file: >>> another_file = IkobaImageFile('bar.txt', 'bar contents') We happen to know, due to the implementation of EncodingImageFileWidget, that the file_id is going to be "Zm9vLnR4dAp0aGUgY29udGVudHM=". Let's make a request with the original id, but a new file upload: >>> request = TestRequest(form={'field.foo': FileUpload(another_file), ... 'field.foo.file_id': ... 'Zm9vLnR4dAp0aGUgY29udGVudHM='}) We expect the new file to be the one that's uploaded: >>> widget = EncodingImageFileWidget(field, request) >>> print normalize(widget()) ... ... (bar.txt) Support for File Retrievals --------------------------- As :class:`waeup.ikoba.image.IkobaImageFile` objects support storing image data by using external 'storages', also our widgets should do so. We create a simple IFileRetrieval utility and enable it: >>> import hashlib >>> from zope.component import provideUtility >>> from hurry.file.interfaces import IFileRetrieval >>> from StringIO import StringIO >>> class MyFileRetrieval(object): ... storage = dict() ... def getFile(self, data): ... entry = self.storage.get(data, None) ... if entry is None: ... return None ... return StringIO(entry) ... def createFile(self, filename, f): ... contents = f.read() ... id_string = hashlib.md5(contents).hexdigest() ... result = IkobaImageFile(filename, id_string) ... self.storage[id_string] = contents ... return result >>> retrieval = MyFileRetrieval() >>> provideUtility(retrieval, IFileRetrieval) With this utility in place we can post requests. When no data was posted and the field also contains no data, we will get a simple input as appropriate for add forms: >>> field = ImageFile(__name__='foo', title=u'Foo', required=False) >>> field = field.bind(None) >>> request = TestRequest() >>> widget = EncodingImageFileWidget(field, request) >>> print normalize(widget()) If the request contains empty data but it is only an empty string, the result will be the same: >>> request = TestRequest(form={'field.foo': u''}) >>> widget = EncodingImageFileWidget(field, request) >>> print normalize(widget()) We now want to simulate, that the field contains already data, identified by some `file_id`. To do so, we first store the data in our file retrieval and then create a IkobaImageFile object with that file_id stored: >>> from waeup.ikoba.image import createIkobaImageFile >>> image = createIkobaImageFile( ... 'sample.jpg', open(testimage, 'rb')) >>> file_id = image.data >>> file_id # MD5 sum of the file contents '9feac4265077922000aa8b88748e25be' >>> import hashlib >>> hashlib.md5(open(testimage, 'rb').read()).hexdigest() '9feac4265077922000aa8b88748e25be' The new file was stored by our utility, as createIkobaImageFile looks up IFileRetrieval utilities and uses them: >>> retrieval.storage.keys() ['9feac4265077922000aa8b88748e25be'] We set this image as value of the widget: >>> widget.setRenderedValue(image) >>> print normalize(widget())
(sample.jpg) >>> retrieval.storage.keys() ['9feac4265077922000aa8b88748e25be'] The stored hidden value contains the filename and our file_id: >>> "c2FtcGxlLmpwZwo5ZmVhYzQyNjUwNzc5MjIwMDBhYThiODg3NDhlMjViZQ==".decode( ... 'base64') 'sample.jpg\n9feac4265077922000aa8b88748e25be' Now, we want the the widget rendered again but this time with the data coming from the request. To do so, we have to create a FileUpload object that wraps the real file: >>> class UploadedFile(object): ... headers = {} ... def __init__(self, filename, f): ... self.filename = filename ... self.file = f Now we can 'post' the request and will get the same result as above: >>> upload = FileUpload(UploadedFile('sample.jpg', open(testimage, 'rb'))) >>> request = TestRequest(form={'field.foo': upload}) >>> widget = EncodingImageFileWidget(field, request) >>> print normalize(widget()) ... ... (sample.jpg) Now let's render again, this time not with file data available in the request, but an id. Again, we should see the same: >>> request = TestRequest(form={ ... 'field.foo.file_id': ... 'c2FtcGxlLmpwZwo5ZmVhYzQyNjUwNzc5MjIwMDBhYThiODg3NDhlMjViZQ=='}) >>> widget = EncodingImageFileWidget(field, request) >>> print normalize(widget()) ... ... (sample.jpg) If there is both file data and an id, something else happens. First, let's prepare some new file: >>> upload = FileUpload(UploadedFile('sample2.jpg', open(testimage2, 'rb'))) We happen to know, due to the implementation of EncodingImageFileWidget, that the file_id is going to be "Zm9vLnR4dAp0aGUgY29udGVudHM=". Let's make a request with the original id, but a new file upload: >>> request = TestRequest(form={'field.foo': upload, ... 'field.foo.file_id': ... 'Z2FtcGxlLmpwZwr/2P/gAA=='}) We expect the new file to be the one that's uploaded: >>> field = ImageFile(__name__='foo', title=u'Foo', required=False) >>> field = field.bind(None) >>> widget = EncodingImageFileWidget(field, request) >>> print normalize(widget()) ... ... (sample2.jpg) The value displayed above again means the filename and md5 sum of the stored file: >>> "c2FtcGxlMi5qcGcKZDk2MDkwZWRlMmRjODlkZDdkZWM5ZDU3MmFkNThjNzQ=".decode( ... 'base64') 'sample2.jpg\nd96090ede2dc89dd7dec9d572ad58c74' where the md5 sum is in fact correct: >>> import hashlib >>> hashlib.md5(open(testimage2, 'rb').read()).hexdigest() 'd96090ede2dc89dd7dec9d572ad58c74' Our file retrieval utility now contains two files: >>> retrieval.storage.keys() ['9feac4265077922000aa8b88748e25be', 'd96090ede2dc89dd7dec9d572ad58c74']