Exemplo n.º 1
0
    def manage_afterAdd(self, item, container):
        """Handle pasting of new photos."""
        if not hasattr(self, '_original'):
            # Added Photo (vs. imported)
            # See note in PUT()
            store = self.propertysheets.get('photoconf').getProperty('store')
            if store == 'Image': from PhotoImage import PhotoImage
            elif store == 'ExtImage': from ExtPhotoImage import PhotoImage
            self._original = PhotoImage(self.id, self.title, path=self.absolute_url(1))
            self._original.manage_upload(StringIO(self._data), self.content_type())
            delattr(self, '_data')
            if self._validImage():
                self._makeDisplayPhotos()

        self._original.manage_afterAdd(item, container)
        if hasattr(self, '_photos'):
            for photo in self._photos.values():
                photo.manage_afterAdd(item, container)
Exemplo n.º 2
0
class Photo(Implicit, Persistent, PropertyManager, RoleManager, Item):
    """Photo object.

    Photo objects contain as properties a series of resized
    Zope Image objects according to the given display sizes.
    """

    try: __implements__ = (WriteLockInterface,)
    except NameError: pass

    meta_type = "Photo"

    _properties = (
        {'id':'title', 'type': 'string', 'mode': 'w'},
        )

    manage_options = (
        {'label': 'Edit', 'action': 'manage_editPhotoForm'},
        {'label': 'View', 'action': 'manage_viewPhoto'},
        {'label': 'Settings', 'action': 'manage_editSettingsForm'},
        {'label': 'Displays', 'action': 'manage_editDisplaysForm'},
        ) + PropertyManager.manage_options \
          + RoleManager.manage_options \
          + Item.manage_options

    security=ClassSecurityInfo()

    def __init__(self, id, title, file, content_type='', precondition='',
                 store='Image', engine='ImageMagick', quality=75, pregen=0, timeout=0):

        self.__version__ = '1.2.3'
        self.id = id
        self.title = title

        # Sheet to store photo settings.
        self.propertysheets.manage_addPropertySheet('photoconf', 'photoconf')
        photoconf = self.propertysheets.get('photoconf')
        photoconf.manage_addProperty('store', store, 'string')
        photoconf.manage_addProperty('engine', engine, 'string')
        photoconf.manage_addProperty('quality', quality, 'int')
        photoconf.manage_addProperty('pregen', pregen, 'boolean')
        photoconf.manage_addProperty('timeout', timeout, 'int')

        # Initialize with default hardcoded sizes.
        self._displays = defaultdisplays.copy()
        self._photos = {}

    #
    # Original photo attributes
    #

    security.declareProtected('Access contents information', 'height')
    def height(self):
        """Original photo height."""
        return self._original._height()

    security.declareProtected('Access contents information', 'width')
    def width(self):
        """Original photo width."""
        return self._original._width()

    security.declareProtected('Access contents information', 'size')
    def size(self):
        """Original photo size in bytes."""
        return self._original._size()

    security.declareProtected('Access contents information', 'content_type')
    def content_type(self):
        """Original photo content_type."""
        return self._original._content_type()

    #
    # Photo display methods
    #

    security.declareProtected('View', 'tag')
    def tag(self, display=None, height=None, width=None, cookie=0,
            alt=None, css_class=None, **kw):
        """Return HTML img tag."""

        # Get cookie if display is not specified.
        if display is None:
            display = self.REQUEST.cookies.get('display', None)

        # display may be set from a cookie.
        if display is not None and self._displays.has_key(display):
            if not self._isGenerated(display):
                # Generate photo on-the-fly
                self._makeDisplayPhoto(display, 1)
            image = self._photos[display]
            width, height = (image._width(), image._height())
            # Set cookie for chosen size
            if cookie:
                self.REQUEST.RESPONSE.setCookie('display', display, path="/")
        else:
            # TODO: Add support for on-the-fly resize?
            height = self._original._height()
            width = self._original._width()
            
        if display:
            result = '<img src="%s?display=%s"' % (self.absolute_url(), display)
        else:
            result = '<img src="%s"' % (self.absolute_url())

        if alt is None:
            alt = getattr(self, 'title', '')
        if alt == '':
            alt = self.getId()
        result = '%s alt="%s"' % (result, html_quote(alt))

        if height:
            result = '%s height="%s"' % (result, height)

        if width:
            result = '%s width="%s"' % (result, width)

        if not 'border' in map(string.lower, kw.keys()):
            result = '%s border="0"' % (result)

        if css_class is not None:
            result = '%s class="%s"' % (result, css_class)

        for key in kw.keys():
            value = kw.get(key)
            result = '%s %s="%s"' % (result, key, value)

        result = '%s />' % (result)

        return result

    security.declareProtected('View', 'exttag')
    def exttag(self, prefix, display=None, height=None, width=None, cookie=0,
               alt=None, css_class=None, **kw):
        """Return HTML img tag for serving outside Zope."""

        # Get cookie if display is not specified.
        if display is None:
            display = self.REQUEST.cookies.get('display', None)

        # display may be set from a cookie.
        if display is not None and self._displays.has_key(display):
            if not self._isGenerated(display):
                # Generate photo on-the-fly
                self._makeDisplayPhoto(display, 1)
            image = self._photos[display]
            width, height = (image._width(), image._height())
            # Set cookie for chosen size
            if cookie:
                self.REQUEST.RESPONSE.setCookie('display', display, path="/")

        if prefix[-1] != '/':
            prefix = prefix + '/'

        if display:
            
            filename = self._photos[display].filename
        else:
            filename = self._original.filename

        if type(filename) == type([]):
            filename = prefix + string.join(filename, '/')
        else:
            filename = prefix + filename

        result = '<img src="%s"' % (filename)

        if alt is None:
            alt = getattr(self, 'title', '')
        if alt == '':
            alt = self.getId()
        result = '%s alt="%s"' % (result, html_quote(alt))

        if height:
            result = '%s height="%s"' % (result, height)

        if width:
            result = '%s width="%s"' % (result, width)

        if not 'border' in map(string.lower, kw.keys()):
            result = '%s border="0"' % (result)

        if css_class is not None:
            result = '%s class="%s"' % (result, css_class)

        for key in kw.keys():
            value = kw.get(key)
            result = '%s %s="%s"' % (result, key, value)

        result = '%s />' % (result)

        return result

    def __str__(self):
        return self.tag()

    security.declareProtected('Access contents information', 'displayIds')
    def displayIds(self, exclude=('thumbnail',)):
        """Return list of display Ids."""
        ids = self._displays.keys()
        # Exclude specified displays
        if exclude:
            for id in exclude:
                if id in ids:
                    ids.remove(id)
        # Sort by desired photo surface area
        ids.sort(lambda x,y,d=self._displays: cmp(d[x][0]*d[x][1], d[y][0]*d[y][1]))
        return ids

    security.declareProtected('Access contents information', 'displayLinks')
    def displayLinks(self, exclude=('thumbnail',)):
        """Return list of HTML <a> tags for displays."""
        links = []
        for display in self.displayIds(exclude):
            links.append('<a href="%s?display=%s">%s</a>' % (self.REQUEST['URL'], display, display))
        return links

    security.declareProtected('Access contents information', 'displayMap')
    def displayMap(self, exclude=None):
        """Return list of displays with size info."""
        displays = []
        for id in self.displayIds(exclude):
            if self._isGenerated(id):
                photo_width = self._photos[id]._width()
                photo_height = self._photos[id]._height()
                bytes = self._photos[id]._size()
                age = self._photos[id]._age()
            else:
                (photo_width, photo_height, bytes, age) = (None, None, None, None)
            displays.append({'id': id,
                             'width': self._displays[id][0],
                             'height': self._displays[id][1],
                             'photo_width': photo_width,
                             'photo_height': photo_height,
                             'bytes': bytes,
                             'age': age
                             })
        return displays

    security.declareProtected('View', 'index_html')
    def index_html(self, REQUEST, RESPONSE, display=None):
        """Return the image data."""

        # display may be set from a cookie (?)
        if display and self._displays.has_key(display):
            if not self._isGenerated(display):
                # Generate photo on-the-fly
                self._makeDisplayPhoto(display, 1)
            else:
                timeout = self.propertysheets.get('photoconf').getProperty('timeout')
                if timeout and self._photos[display]._age() > (timeout / 2):
                    self._expireDisplays((display,), timeout)
            # Return resized image
            return self._photos[display].index_html(REQUEST, RESPONSE)

        # Return original image
        return self._original.index_html(REQUEST, RESPONSE)

    security.declareProtected('Access contents information', 'nextPhoto')
    def nextPhoto(self):
        """Return next Photo in folder."""
        id = self.getId()
        photoIds = self.aq_parent.objectIds(['Photo'])
        photoIds.sort()
        if id == photoIds[-1]:
            return None
        return getattr(self.aq_parent, photoIds[photoIds.index(id)+1])

    security.declareProtected('Access contents information', 'prevPhoto')
    def prevPhoto(self):
        """Return previous Photo in folder."""
        id = self.getId()
        photoIds = self.aq_parent.objectIds(['Photo'])
        photoIds.sort()
        if id == photoIds[0]:
            return None
        return getattr(self.aq_parent, photoIds[photoIds.index(id)-1])

    security.declareProtected('View', 'get_size')
    def get_size(self):
        """Return size in bytes of original photo."""
        return self._original.get_size()

    #
    # Photo processing
    #

    def _resize(self, display, width, height, engine='ImageMagick', quality=75):
        """Resize and resample photo."""
        origimg = self._original
        newimg = StringIO()
        if engine == 'PIL':  # Use PIL
            img = PIL.Image.open(origimg._PILdata())
            fmt = img.format
            img = img.resize((width, height))
            img.save(newimg, fmt, quality=quality)
        elif engine == 'ImageMagick':  # Use ImageMagick
            if sys.platform == 'win32':
                from win32pipe import popen2
                imgin, imgout = popen2('convert -quality %s -geometry %sx%s - -'
                                       % (quality, width, height), 'b')
            else:
                from popen2 import popen2
                imgout, imgin = popen2('convert -quality %s -geometry %sx%s - -'
                                       % (quality, width, height))
            imgin.write(origimg._IMdata())
            imgin.close()
            newimg.write(imgout.read())
            imgout.close()

        newimg.seek(0)
        return newimg

    def _getDisplayData(self, display):
        """Return raw photo data for given display."""
        (width, height) = self._displays[display]
        if width == 0 and height == 0:
            width = self._original._width()
            height = self._original._height()
        (width, height) = self._getAspectRatioSize(width, height)
        engine = self.propertysheets.get('photoconf').getProperty('engine')
        quality = self.propertysheets.get('photoconf').getProperty('quality')
        return self._resize(display, width, height, engine, quality)
        
    def _getDisplayPhoto(self, display):
        """Return photo object for given display."""
        try:
            base, ext = string.split(self.id, '.')
            id = base+'_'+display+'.'+ext
        except ValueError:
            id = self.id+'_'+display
        return self._original._newImage(id, self._getDisplayData(display), self.absolute_url(1))

    def _makeDisplayPhoto(self, display, force=0):
        """Create given display."""
        if self._shouldGenerate(display) or force:
            photo = self._photos
            if photo.has_key(display):
                print photo[display]
                print dir(photo[display])
                print type(photo[display])
                print photo[display].meta_type
                photo[display].manage_upload(self._getDisplayData(display), self.content_type())
            else:
                photo[display] = self._getDisplayPhoto(display)
            self._photos = photo

    def _makeDisplayPhotos(self):
        """Create all displays."""
        for display in self._displays.keys():
            self._makeDisplayPhoto(display)
            
    def _getAspectRatioSize(self, width, height):
        """Return proportional dimensions within desired size."""
        img_width, img_height = (self.width(), self.height())
        if height > img_height * width / img_width:
            height = img_height * width / img_width
        else:
            width =  img_width * height / img_height
        return (width, height)

    def _validImage(self):
        """At least see if it *might* be valid."""
        return self._original._isValid()

    def _isGenerated(self, display):
        """Return whether display has been generated."""
        return self._photos.has_key(display)

    def _shouldGenerate(self, display):
        """Return whether display should be generated."""
        return (self._isGenerated(display) or
               self.propertysheets.get('photoconf').getProperty('pregen'))

    def _expireDisplays(self, exclude=[], timeout=None):
        """Remove displays that have expired."""

        if timeout is None:
            timeout = self.propertysheets.get('photoconf').getProperty('timeout')
        if not timeout:
            return
        photos = self._photos
        for d in self._photos.keys():
            if d in exclude:
                self._photos[d]._p_changed = 1
            elif self._photos[d]._age() > timeout:
                photos[d].manage_beforeDelete(None, None)  # ExtImage support
                del photos[d]
        self._photos = photos

    #
    # FTP/WebDAV support
    #

    security.declareProtected('Change Photo', 'PUT')
    def PUT(self, REQUEST, RESPONSE):
        """Handle HTTP PUT requests."""
        self.dav__init(REQUEST, RESPONSE)
        if hasattr(self, 'dav__simpleifhandler'):
            self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
        file=REQUEST['BODYFILE']
        
        if hasattr(self, '_original'):
            # Updating existing Photo
            self._original.manage_upload(file, self.content_type())
            if self._validImage():
                self._makeDisplayPhotos()
        else:
            # Adding a new Photo.
            # At this point, the object is not yet in its final context.
            # Since ExtImage needs to know the URL of the new Photo object
            # so it can determine where to create new files, we store
            # the image data and let manage_afterAdd() generate the displays
            # once the Photo is in its final context.
            self._data = file.read()
            
        RESPONSE.setStatus(204)
        return RESPONSE

    security.declareProtected('FTP access', 'manage_FTPget', 'manage_FTPstat', 'manage_FTPlist')
    def manage_FTPget(self):
        """Handle GET requests."""
        return self._original._data()

    def manage_FTPstat(self, REQUEST):
        """Handle STAT requests."""
        return self._original.manage_FTPstat(REQUEST)

    def manage_FTPlist(self, REQUEST):
        """Handle LIST requests."""
        return self._original.manage_FTPlist(REQUEST)

    #
    # Management Interface
    #

    security.declareProtected('View management screens', 'manage_viewPhoto')
    manage_viewPhoto = DTMLFile('dtml/viewPhoto', globals())

    security.declareProtected('View management screens', 'manage_editPhotoForm')
    manage_editPhotoForm = DTMLFile('dtml/editPhotoForm', globals())

    security.declareProtected('Change Photo', 'manage_editPhoto')
    def manage_editPhoto(self, file='', REQUEST=None):
        """Changes Photo information."""
        if hasattr(self, 'wl_isLocked') and self.wl_isLocked():
            raise ResourceLockedError, "Photo is locked via WebDAV."
        self.manage_changeProperties(REQUEST)
        if file and file.filename:
            self._original.manage_upload(file, self.content_type())
            if self._validImage():
                self._makeDisplayPhotos()
        if REQUEST is not None:
            return self.manage_editPhotoForm(REQUEST,
                manage_tabs_message='Photo information updated.')

    security.declareProtected('View management screens', 'manage_editSettingsForm')
    manage_editSettingsForm = DTMLFile('dtml/editSettingsForm', globals())

    security.declareProtected('Manage properties', 'manage_editSettings')
    def manage_editSettings(self, REQUEST=None):
        """Edit photo settings."""
        photoconf = self.propertysheets.get('photoconf')
        photoconf.manage_editProperties(REQUEST)
        if REQUEST is not None:
            return self.manage_editSettingsForm(REQUEST,
        manage_tabs_message='Photo settings updated.')

    security.declareProtected('View management screens', 'manage_editDisplaysForm')
    manage_editDisplaysForm = DTMLFile('dtml/editDisplaysForm', globals())

    security.declareProtected('Manage properties', 'manage_editDisplays')
    def manage_editDisplays(self, displays, manage_editDisplays=None, REQUEST=None):
        """Edit displays."""
        d = self._displays
        p = self._photos
        for display in displays:
            if (d[display.id] != (display.width, display.height) or
                  manage_editDisplays == ' Regenerate All '):
                d[display.id] = (display.width, display.height)
                if self._shouldGenerate(display.id):
                    p[display.id].manage_beforeDelete(None, None)  # ExtImage support
                    p[display.id] = self._getDisplayPhoto(display.id)
        self._displays = d
        self._photos = p
        if REQUEST is not None:
            return self.manage_editDisplaysForm(REQUEST,
                manage_tabs_message='Displays changed.')

    security.declareProtected('Manage properties', 'manage_delDisplays')
    def manage_delDisplays(self, ids, REQUEST=None):
        """Delete displays."""
        d = self._displays
        p = self._photos
        for id in ids:
            try:
                del d[id]
                p[id].manage_beforeDelete(None, None)  # ExtImage support
                del p[id]
            except: pass
        self._displays = d
        self._photos = p
        if REQUEST is not None:
            return self.manage_editDisplaysForm(REQUEST,
                manage_tabs_message='Displays deleted.')

    security.declareProtected('Manage properties', 'manage_addDisplay')
    def manage_addDisplay(self, id, width, height, REQUEST=None):
        """Add display."""
        d = self._displays
        p = self._photos
        d[id] = (width, height)
        if self._shouldGenerate(id):
            p[id] = self._getDisplayPhoto(id)
        self._displays = d
        self._photos = p
        if REQUEST is not None:
            return self.manage_editDisplaysForm(REQUEST,
                manage_tabs_message='Display added.')

    security.declareProtected('Change Photo', 'manage_regenDisplays')
    def manage_regenDisplays(self, REQUEST=None):
        """Regenerate all displays."""
        self._makeDisplayPhotos()
        if REQUEST is not None:
            return self.manage_editDisplaysForm(REQUEST,
                manage_tabs_message='Displays regenerated.')

    security.declareProtected('Change Photo', 'manage_purgeDisplays')
    def manage_purgeDisplays(self, exclude=None, REQUEST=None):
        """Purge generated displays."""
        if exclude is None and REQUEST is not None:
            exclude = REQUEST.form.get('ids', [])
        self._expireDisplays(exclude or [], -1)
        if REQUEST is not None:
            return self.manage_editDisplaysForm(REQUEST,
                manage_tabs_message='Displays purged.')

    security.declareProtected('Change Photo', 'manage_cleanDisplays')
    def manage_cleanDisplays(self, exclude=None, REQUEST=None):
        """Purge all generated displays that have expired."""
        if exclude is None and REQUEST is not None:
            exclude = REQUEST.form.get('ids', [])
        self._expireDisplays(exclude or [])
        if REQUEST is not None:
            return self.manage_editDisplaysForm(REQUEST,
                manage_tabs_message='Expired displays purged.')

    #
    # ExtImage management support
    #

    def manage_afterClone(self, item):
        """Prepare photos for cloning."""
        self._original.manage_afterClone(item)
        for photo in self._photos.values():
            photo.manage_afterClone(item)

    def manage_afterAdd(self, item, container):
        """Handle pasting of new photos."""
        if not hasattr(self, '_original'):
            # Added Photo (vs. imported)
            # See note in PUT()
            store = self.propertysheets.get('photoconf').getProperty('store')
            if store == 'Image': from PhotoImage import PhotoImage
            elif store == 'ExtImage': from ExtPhotoImage import PhotoImage
            self._original = PhotoImage(self.id, self.title, path=self.absolute_url(1))
            self._original.manage_upload(StringIO(self._data), self.content_type())
            delattr(self, '_data')
            if self._validImage():
                self._makeDisplayPhotos()

        self._original.manage_afterAdd(item, container)
        if hasattr(self, '_photos'):
            for photo in self._photos.values():
                photo.manage_afterAdd(item, container)

    def manage_beforeDelete(self, item, container):
        """Delete (mark for undo) each photo file."""
        self._original.manage_beforeDelete(item, container)
        for photo in self._photos.values():
            photo.manage_beforeDelete(item, container)