コード例 #1
0
class MockupAsset(Asset, SimpleItem):
    """A mockup non-publishable.
    """
    grok.implements(IMockupAsset)
    meta_type = 'Mockup Asset'
    silvaconf.priority(-10)
    silvaconf.icon('tests/mockers.png')
コード例 #2
0
class NewsItem(Document.Document):
    """A News item that appears as an individual page. By adjusting
       settings the Author can determine which subjects, and
       for which audiences the Article should be presented.
    """
    grok.implements(INewsItem)
    security = ClassSecurityInfo()
    meta_type = "Obsolete Article"
    silvaconf.icon("www/news_item.png")
    silvaconf.priority(3.7)
    silvaconf.version_class(NewsItemVersion)

    security.declareProtected(SilvaPermissions.ApproveSilvaContent,
                              'set_next_version_display_datetime')
    def set_next_version_display_datetime(self, dt):
        """Set display datetime of next version.
        """
        version = getattr(self, self.get_next_version())
        version.set_display_datetime(dt)

    security.declareProtected(SilvaPermissions.ChangeSilvaContent,
                              'set_unapproved_version_display_datetime')
    def set_unapproved_version_display_datetime(self, dt):
        """Set display datetime for unapproved
        """
        version = getattr(self, self.get_unapproved_version())
        version.set_display_datetime(dt)

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_unapproved_version_display_datetime')
    def get_unapproved_version_display_datetime(self):
        """get display datetime for unapproved
        """
        version = getattr(self, self.get_unapproved_version())
        version.get_display_datetime()
コード例 #3
0
class AgendaItem(NewsItem):
    """A News item for events. Includes date and location
       metadata, as well settings for subjects and audiences.
    """
    security = ClassSecurityInfo()
    implements(IAgendaItem)
    meta_type = "Obsolete Agenda Item"
    silvaconf.icon("www/agenda_item.png")
    silvaconf.priority(3.8)
    silvaconf.versionClass(AgendaItemVersion)
コード例 #4
0
class Topic(ForumContainer, ForumPost, Folder):
    """Topic of a Silva Forum. It will contains comments posted by users.
    """
    grok.implements(ITopic)
    silvaconf.icon('www/topic.gif')
    meta_type = 'Silva Forum Topic'
    silvaconf.priority(0)

    security = ClassSecurityInfo()

    def __init__(self, *args, **kwargs):
        super(Topic, self).__init__(*args, **kwargs)
        self._lastid = 0
        self.comment_batch_size = 10

    def get_silva_addables_allowed_in_container(self):
        return ['Silva Forum Comment']

    security.declareProtected('Change Silva content', 'add_comment')

    def add_comment(self, title, text, anonymous=False):
        """ add a comment to the topic
        """
        if anonymous and not self.anonymous_posting_allowed():
            raise ValueError('anonymous posting is not allowed!')
        idstring = title
        if not idstring:
            idstring = text
        identifier = self._generate_id(idstring)
        factory = self.manage_addProduct['SilvaForum']
        factory.manage_addComment(identifier, title, text=text)
        comment = self[identifier]
        if anonymous:
            binding = self.get_root().service_metadata.getMetadata(comment)
            binding.setValues('silvaforum-item', {'anonymous': 'yes'})
        return comment

    def comments(self):
        """ returns an iterable of all comments
        """
        return [{
            'id': comment.id,
            'url': comment.absolute_url(),
            'title': comment.get_title(),
            'creator': comment.get_creator(),
            'creation_datetime': comment.get_creation_datetime(),
            'text': comment.get_text(),
            'topic_url': comment.aq_parent.absolute_url(),
        } for comment in self.objectValues('Silva Forum Comment')]

    security.declareProtected('View', 'number_of_comments')

    def number_of_comments(self):
        return len(self.objectValues('Silva Forum Comment'))
コード例 #5
0
class SilvaSoftwareCenter(Publication):
    meta_type = 'Silva Software Center'
    grok.implements(interfaces.ISilvaSoftwareCenter)

    silvaconf.icon('SilvaSoftwareCenter.png')
    silvaconf.priority(9)

    def get_silva_addables_allowed_in_container(self):
        return [
            'Silva Software Group',
            'Silva Software Activity Aggregator',
            'Silva Software Remote Group',
            'Silva Software Package',
        ]
コード例 #6
0
class SilvaSoftwareGroup(SilvaSoftwareContent):
    meta_type = 'Silva Software Group'
    grok.implements(interfaces.ISilvaSoftwareGroup)

    silvaconf.icon('SilvaSoftwareGroup.png')
    silvaconf.priority(8)

    group_tag = u""
    is_group_archive = False

    def get_silva_addables_allowed_in_container(self):
        return [
            'Silva Document',
            'Silva Link',
            'Silva Software Activity Aggregator',
            'Silva Software Group',
            'Silva Software Package',
        ]
コード例 #7
0
class SilvaSoftwarePackage(SilvaSoftwareContent):
    """A package represent a software and contains releases.
    """
    meta_type = 'Silva Software Package'
    grok.implements(interfaces.ISilvaSoftwarePackage)

    silvaconf.icon('SilvaSoftwarePackage.png')
    silvaconf.priority(9)

    is_package_deprecated = False
    package_version_matrix = u""

    def get_silva_addables_allowed_in_container(self):
        result = [
            'Silva Document', 'Silva Software Release',
            'Silva Software Activity'
        ]
        result.extend(IAddableContents(self).get_all_addables(IAsset))
        return result
コード例 #8
0
class MockupVersionedContent(VersionedContent):
    """Test versioned content.

    (Note: the docstring is required for traversing to work)
    """
    meta_type = 'Mockup VersionedContent'
    grok.implements(IMockupVersionedContent)
    silvaconf.priority(-11)
    silvaconf.version_class(MockupVersion)
    silvaconf.icon('tests/mockers.png')

    def __init__(self, *args):
        super(MockupVersionedContent, self).__init__(*args)
        self.__entries = []

    def set_entries(self, entries):
        self.__entries = entries

    def get_entries(self):
        return list(self.__entries)

    def get_mockup_version(self, version_id):
        return self._getOb(str(version_id))
コード例 #9
0
class Publication(Folder):
    __doc__ = _("""Publications are special folders. They function as the
       major organizing blocks of a Silva site. They are comparable to
       binders, and can contain folders, documents, and assets.
       Publications are opaque. They instill a threshold of view, showing
       only the contents of the current publication. This keeps the overview
       screens manageable. Publications have configuration settings that
       determine which core and pluggable objects are available. For
       complex sites, sub-publications can be nested.
    """)
    security = ClassSecurityInfo()

    meta_type = "Silva Publication"

    grok.implements(IPublication)
    silvaconf.priority(-5)
    silvaconf.icon('icons/publication.gif')

    @property
    def manage_options(self):
        base_options = super(Publication, self).manage_options
        manage_options = (base_options[0], )
        if ISiteManager(self).is_site():
            manage_options += ({
                'label': 'Services',
                'action': 'manage_services'
            }, )
        return manage_options + base_options[1:]

    security.declareProtected(SilvaPermissions.ViewManagementScreens,
                              'manage_main')
    manage_main = DTMLFile('folderContents', globals())

    security.declarePublic('objectItemsContents')

    def objectItemsContents(self, spec=None):
        """Don't display services by default in the Silva root.
        """
        return [
            item for item in super(Publication, self).objectItems()
            if not item[0].startswith('service_')
        ]

    security.declareProtected(SilvaPermissions.ViewManagementScreens,
                              'manage_services')
    manage_services = DTMLFile('folderServices', globals())

    security.declarePublic('objectItemsServices')

    def objectItemsServices(self, spec=None):
        """Display services separately.
        """
        return [
            item for item in super(Publication, self).objectItems()
            if item[0].startswith('service_')
            and not IInvisibleService.providedBy(item[1])
        ]

    # MANIPULATORS

    security.declareProtected(SilvaPermissions.ApproveSilvaContent,
                              'to_folder')

    def to_folder(self):
        """Publication becomes a folder instead.
        """
        helpers.convert_content(self, Folder)

    security.declareProtected(SilvaPermissions.ApproveSilvaContent,
                              'to_publication')

    def to_publication(self):
        raise ContentError(
            _("You cannot convert a publication into a publication."), self)

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'validate_wanted_quota')

    def validate_wanted_quota(self, value, REQUEST=None):
        """Validate the wanted quota is correct the current
        publication.
        """
        if value < 0:
            # Quota can't be negative.
            return False
        if not value:
            # 0 or means no quota.
            return True
        parent = aq_parent(self).get_publication()
        quota = parent.get_current_quota()
        if quota and quota < value:
            return False
        return True

    def _verify_quota(self, REQUEST=None):
        quota = self.get_current_quota() * 1024 * 1024
        if quota and self.used_space > quota:
            excess = self.used_space - quota
            raise OverQuotaException(excess)

    # ACCESSORS

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_current_quota')

    def get_current_quota(self):
        """Return the current quota value on the publication.
        """
        binding = getUtility(IMetadataService).getMetadata(self)
        try:
            local = int(binding.get('silva-quota', element_id='quota') or 0)
            if local > 0:
                return local
        except KeyError:
            pass
        # Test parent publication/root.
        parent = aq_parent(self)
        return parent.get_current_quota()

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_publication')

    def get_publication(self):
        """Get publication. Can be used with acquisition to get
        'nearest' Silva publication.
        """
        return aq_inner(self)

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'is_transparent')

    def is_transparent(self):
        return 0
コード例 #10
0
class Forum(ForumContainer, Publication):
    """Silva Forum is a web forum component where visitors can create
       topics, add comments, and carry on discussions. Posters must be
       logged in or fill in a CAPTCHA in order to participate.
    """
    grok.implements(IForum)
    meta_type = 'Silva Forum'
    silvaconf.icon('www/forum.gif')
    silvaconf.priority(0)

    security = ClassSecurityInfo()

    def __init__(self, *args, **kwargs):
        super(Forum, self).__init__(*args, **kwargs)
        self._lastid = 0
        self.topic_batch_size = 10

    def get_silva_addables_allowed_in_container(self):
        return ['Silva Forum Topic']

    security.declareProtected('View', 'get_topics')

    def get_topics(self):
        """
        return a list of topic objects
        """
        return self.objectValues('Silva Forum Topic')

    security.declareProtected('Change Silva content', 'add_topic')

    def add_topic(self, topic, anonymous=False):
        """ add a topic to the forum
        """
        if anonymous and not self.anonymous_posting_allowed():
            raise ValueError('anonymous posting is not allowed!')
        identifier = self._generate_id(topic)
        factory = self.manage_addProduct['SilvaForum']
        factory.manage_addTopic(identifier, topic)
        topic = self[identifier]
        if anonymous:
            metadata = component.getUtility(IMetadataService)
            binding = metadata.getMetadata(topic)
            binding.setValues('silvaforum-item', {'anonymous': 'yes'})
        return topic

    def topics(self):
        """ returns an iterable of all topics (topics)
        """
        # XXX Why not return a list of topic objects?

        # XXX note that this mostly exists because we intend to add more
        # functionality (e.g. searching, ordering) later
        topics = [{
            'url': obj.absolute_url(),
            'title': obj.get_title(),
            'creation_datetime': obj.get_creation_datetime(),
            'creator': obj.get_creator(),
            'commentlen': len(obj.comments()),
        } for obj in self.get_topics()]
        topics.reverse()
        return topics

    security.declareProtected('View', 'number_of_topics')

    def number_of_topics(self):
        return len(self.get_topics())
コード例 #11
0
class AutoTOC(Content, SimpleItem):
    __doc__ = _("""This is a special document type that automatically generates
    a Table of Contents. Usually it's used as the 'index' document of a folder.
    Then the parent folder displays a TOC when accessed (e.g.
    http://www.x.yz/silva/myFolder). The AutoTOC is configurable: it can display
    any selection of Silva content including assets, include descriptions or
    icons, be set to stop at a specific depth, and use various sorting
    methods.
    """)
    security = ClassSecurityInfo()

    meta_type = "Silva AutoTOC"

    grok.implements(IAutoTOC)
    silvaconf.icon('icons/autotoc.png')
    silvaconf.priority(0.2)

    _local_types = ['Silva Document', 'Silva Publication', 'Silva Folder']
    _toc_depth = -1
    _display_desc_flag = False
    # values: 'silva', 'alpha', 'reversealpha', 'chronmod', 'rchronmod'
    _sort_order = 'silva'
    _show_icon = False
    _show_container_link = False

    security.declareProtected(SilvaPermissions.ChangeSilvaContent,
                              'set_toc_depth')

    def set_toc_depth(self, depth):
        self._toc_depth = depth

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_toc_depth')

    def get_toc_depth(self):
        """get the depth to which the toc will be rendered"""
        return self._toc_depth

    security.declareProtected(SilvaPermissions.ChangeSilvaContent,
                              'set_show_container_link')

    def set_show_container_link(self, flag):
        self._show_container_link = flag

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_show_container_link')

    def get_show_container_link(self):
        """get the depth to which the toc will be rendered"""
        return self._show_container_link

    security.declareProtected(SilvaPermissions.View, 'get_local_types')

    def get_local_types(self):
        return self._local_types

    security.declareProtected(SilvaPermissions.ChangeSilvaContent,
                              'set_local_types')

    def set_local_types(self, types):
        self._local_types = types

    security.declareProtected(SilvaPermissions.View, 'get_display_desc_flag')

    def get_display_desc_flag(self):
        return self._display_desc_flag

    security.declareProtected(SilvaPermissions.ChangeSilvaContent,
                              'set_display_desc_flag')

    def set_display_desc_flag(self, flag):
        self._display_desc_flag = flag

    security.declareProtected(SilvaPermissions.View, 'get_show_icon')

    def get_show_icon(self):
        return self._show_icon

    security.declareProtected(SilvaPermissions.ChangeSilvaContent,
                              'set_show_icon')

    def set_show_icon(self, flag):
        self._show_icon = flag

    security.declareProtected(SilvaPermissions.View, 'get_sort_order')

    def get_sort_order(self):
        return self._sort_order

    security.declareProtected(SilvaPermissions.ChangeSilvaContent,
                              'set_sort_order')

    def set_sort_order(self, order):
        self._sort_order = order
コード例 #12
0
class Image(Asset):
    __doc__ = _("""Web graphics (gif, jpg, png) can be uploaded and inserted in
       documents, or used as viewable assets.
    """)
    security = ClassSecurityInfo()

    meta_type = "Silva Image"

    grok.implements(interfaces.IImage)

    re_WidthXHeight = re.compile(r'^([0-9]+|\*)[Xx]([0-9\*]+|\*)$')
    re_percentage = re.compile(r'^([0-9\.]+)\%$')
    re_box = re.compile(r'^([0-9]+)[Xx]([0-9]+)-([0-9]+)[Xx]([0-9]+)')

    thumbnail_size = Size(120, 120)

    image = None
    hires_image = None
    thumbnail_image = None
    web_scale = '100%'
    web_crop = ''
    web_format = Format.JPEG
    web_formats = (Format.JPEG, Format.GIF, Format.PNG)

    _web2ct = {
        Format.JPEG: 'image/jpeg',
        Format.GIF: 'image/gif',
        Format.PNG: 'image/png',
    }

    silvaconf.priority(-3)
    silvaconf.icon('icons/image.gif')
    silvaconf.factory('manage_addImage')

    security.declareProtected(SilvaPermissions.ChangeSilvaContent,
                              'set_web_presentation_properties')

    def set_web_presentation_properties(self, web_format, web_scale, web_crop):
        """Sets format and scaling for web presentation.

        web_format (str): either JPEG or PNG (or whatever other format
        makes sense, must be recognised by PIL).
        web_scale (str): WidthXHeight or nn.n%.
        web_crop (str): X1xY1-X2xY2, crop-box or empty for no cropping.


        Automaticaly updates cached web presentation image.
        """
        update = False
        if self.hires_image is None:
            update = True
            self.hires_image = self.image
            self.image = None

        # Set web format.
        if web_format not in ('unknown', '') and self.web_format != web_format:
            if web_format in self.web_formats:
                self.web_format = web_format
                update = True
            else:
                raise ValueError('Unknown image format %s' % web_format)
        # check if web_scale can be parsed:
        try:
            self.get_canonical_web_scale(web_scale)
        except ValueError:
            # if not, we set web_scale back to default value
            web_scale = '100%'

        if self.web_scale != web_scale:
            self.web_scale = web_scale
            update = True
        # check if web_crop can be parsed:
        self.get_crop_box(web_crop)
        if self.web_crop != web_crop:
            # if web_crop is None it should be replaced by an empty string
            self.web_crop = web_crop and web_crop or ''
            update = True
        if update and self.hires_image is not None:
            self._create_derived_images()

    security.declareProtected(SilvaPermissions.ChangeSilvaContent, 'set_image')

    def set_image(self, file):
        """Set the image object.
        """
        validate_image(file)
        self._image_factory('hires_image', file)
        # Image change, reset scale, crop box: they can be invalid for
        # this new image.
        format = self.get_format()
        if format in self.web_formats:
            self.web_format = format
        self.web_scale = '100%'
        self.web_crop = ''
        self._create_derived_images()
        # XXX Should be on event
        self.update_quota()

    security.declareProtected(SilvaPermissions.View, 'get_image')

    def get_image(self, hires=True, webformat=False):
        """Return image data.
        """
        if hires:
            if self.hires_image is not None:
                if webformat:
                    # Create web format of original image.
                    with ImageFile(self.hires_image) as working:
                        data = working.save(self.web_format)
                    if data is not None:
                        return data.getvalue()
                # Original format of the original image is the orginal.
                return self.hires_image.get_file()
            return None
        if self.image is not None:
            if webformat:
                # Webformat of the cropped/resized image is already computed.
                return self.image.get_file()
            # Original format of the cropped/resize image is not possible.
            raise ValueError(
                _(u"Low resolution image in original format is "
                  u"not supported"))
        return None

    security.declareProtected(SilvaPermissions.View, 'get_canonical_web_scale')

    def get_canonical_web_scale(self, scale=None):
        """returns (width, height) of web image"""
        if scale is None:
            scale = self.web_scale
        m = self.re_WidthXHeight.match(scale)
        if m is None:
            m = self.re_percentage.match(scale)
            if m is None:
                msg = _(
                    "'${scale}' is not a valid scale identifier. "
                    "Probably a percent symbol is missing.",
                    mapping={'scale': scale})
                raise ValueError(msg)
            cropbox = Rect.parse(self.web_crop)
            if cropbox:
                width, height = cropbox.size
            else:
                width, height = self.get_dimensions()
            percentage = float(m.group(1)) / 100.0
            width = int(width * percentage)
            height = int(height * percentage)
        else:
            img_w, img_h = self.get_dimensions()
            width = m.group(1)
            height = m.group(2)
            if width == height == '*':
                msg = _(
                    "'${scale} is not a valid scale identifier. "
                    "At least one number is required.",
                    mapping={'scale': scale})
                raise ValueError(msg)
            if width == '*':
                height = int(height)
                width = img_w * height / img_h
            elif height == '*':
                width = int(width)
                height = img_h * width / img_w
            else:
                width = int(width)
        return width, height

    security.declareProtected(SilvaPermissions.View, 'get_crop_box')

    def get_crop_box(self, crop=None):
        """return crop box"""
        crop = crop or self.web_crop
        if crop is None or crop.strip() == '':
            return None
        rect = Rect.parse(crop)
        if rect is None:
            msg = _("'${crop} is not a valid crop identifier",
                    mapping={'crop': crop})
            raise ValueError(msg)
        with ImageFile(self.hires_image) as image:
            Crop(rect).validate(image)
        return (rect.lower_edge.x, rect.lower_edge.y, rect.higher_edge.x,
                rect.higher_edge.y)

    security.declareProtected(SilvaPermissions.View, 'get_dimensions')

    def get_dimensions(self, thumbnail=False, hires=False):
        """Returns width, heigt of (hi res) image.

        Raises ValueError if there is no way of determining the dimenstions,
        Return 0, 0 if there is no image,
        Returns width, height otherwise.
        """
        data = None
        if thumbnail:
            data = self.thumbnail_image
        elif hires:
            data = self.hires_image
        else:
            data = self.image

        if data is None:
            return Size(0, 0)
        try:
            with ImageFile(data) as image:
                return image.get_size()
        except (ValueError, TypeError):
            return Size(0, 0)

    security.declareProtected(SilvaPermissions.View, 'tag')

    def tag(self,
            hires=False,
            thumbnail=False,
            request=None,
            preview=False,
            **extra_attributes):
        warnings.warn(
            'tag have been replaced with get_html_tag. '
            'It will be removed, please update your code.',
            DeprecationWarning,
            stacklevel=2)
        return self.get_html_tag(hires=hires,
                                 thumbnail=thumbnail,
                                 request=request,
                                 preview=preview,
                                 **extra_attributes)

    security.declareProtected(SilvaPermissions.View, 'get_html_tag')

    def get_html_tag(self,
                     preview=False,
                     request=None,
                     hires=False,
                     thumbnail=False,
                     **extra_attributes):
        """ return xhtml tag

        Since 'class' is a Python reserved word, it cannot be passed in
        directly in keyword arguments which is a problem if you are
        trying to use 'tag()' to include a CSS class. The tag() method
        will accept a 'css_class' argument that will be converted to
        'class' in the output tag to work around this.
        """
        url = self.get_download_url(request=request,
                                    preview=preview,
                                    hires=hires,
                                    thumbnail=thumbnail)

        title = self.get_title_or_id()
        width, height = self.get_dimensions(thumbnail=thumbnail, hires=hires)
        if extra_attributes.has_key('css_class'):
            extra_attributes['class'] = extra_attributes['css_class']
            del extra_attributes['css_class']

        extra_html_attributes = [
            u'{name}="{value}"'.format(name=escape(name, 1),
                                       value=escape(value, 1))
            for name, value in extra_attributes.iteritems()
        ]

        return u'<img src="{src}" width="{width}" height="{height}" ' \
               u'alt="{alt}" {extra_attributes} />'.format(
                    src=url,
                    width=str(width),
                    height=str(height),
                    alt=escape(title, 1),
                    extra_attributes=u" ".join(extra_html_attributes))

    security.declareProtected(SilvaPermissions.View, 'url')

    def url(self, hires=False, thumbnail=False, request=None, preview=False):
        warnings.warn(
            'url have been replaced with get_download_url. '
            'It will be removed, please update your code.',
            DeprecationWarning,
            stacklevel=2)
        return self.get_download_url(hires=hires,
                                     thumbnail=thumbnail,
                                     request=request,
                                     preview=preview)

    security.declareProtected(SilvaPermissions.View, 'get_download_url')

    def get_download_url(self,
                         preview=False,
                         request=None,
                         hires=False,
                         thumbnail=False):
        "return url of image"
        if request is None:
            request = self.REQUEST
        url = getMultiAdapter((self, request),
                              IContentURL).url(preview=preview)
        more = '?'
        if hires:
            url += '?hires'
            more = '&'
        elif thumbnail:
            url += '?thumbnail'
            more = '&'
        if preview:
            # In case of preview we add something that change at the
            # end of the url to prevent caching from the browser.
            url += more + str(int(time.time()))
        return url

    security.declareProtected(SilvaPermissions.View, 'get_web_format')

    def get_web_format(self):
        """Return file format of web presentation image
        """
        try:
            with ImageFile(self.image) as image:
                return image.get_format()
        except (ValueError, TypeError):
            return 'unknown'

    security.declareProtected(SilvaPermissions.View, 'get_web_scale')

    def get_web_scale(self):
        """Return scale percentage / WxH of web presentation image
        """
        return str(self.web_scale)

    security.declareProtected(SilvaPermissions.View, 'get_web_crop')

    def get_web_crop(self):
        """Return crop identifier
        """
        return str(self.web_crop)

    security.declareProtected(SilvaPermissions.View, 'get_orientation')

    def get_orientation(self):
        """Returns translated Image orientation (string).
        """
        width, height = self.get_dimensions()
        if width == height:
            return _("square")
        elif width > height:
            return _("landscape")
        return _("portrait")

    security.declareProtected(SilvaPermissions.ChangeSilvaContent,
                              'get_file_system_path')

    def get_file_system_path(self):
        """return path on filesystem for containing image"""
        if self.hires_image is not None:
            return self.hires_image.get_file_system_path()
        return None

    security.declareProtected(SilvaPermissions.View, 'get_format')

    def get_format(self):
        """Returns image format.
        """
        try:
            with ImageFile(self.hires_image) as image:
                return image.get_format()
        except (ValueError, TypeError):
            return 'unknown'

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_filename')

    def get_filename(self):
        if self.hires_image is None:
            return self.getId()
        return self.hires_image.get_filename()

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_mime_type')

    def get_mime_type(self):
        if self.hires_image is None:
            return 'application/octet-stream'
        return self.hires_image.get_mime_type()

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_content_type')

    def get_content_type(self):
        if self.hires_image is None:
            return 'application/octet-stream'
        return self.hires_image.get_content_type()

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_file_size')

    def get_file_size(self):
        if self.hires_image is None:
            return 0
        return self.hires_image.get_file_size()

    ##########
    ## private

    def _create_derived_images(self):
        self._create_web_presentation()
        self._create_thumbnail()

    def _create_web_presentation(self):
        try:
            transformer = Transformer()
            cropbox = self.get_crop_box()
            if cropbox is not None:
                crop_rect = Rect.from_points(Point(cropbox[0], cropbox[1]),
                                             Point(cropbox[2], cropbox[3]))
                transformer.append(Crop(crop_rect))

            if self.web_scale != '100%':
                spec = WHResizeSpec.parse(self.web_scale)
                if spec is None:
                    spec = PercentResizeSpec.parse(self.web_scale)
                if spec is not None:
                    transformer.append(Resize(spec))

            image_io = transformer.transform(self.hires_image, self.web_format)
            if image_io:
                content_type = self._web2ct[self.web_format]
                self._image_factory('image', image_io, content_type)
            else:
                self.image = self.hires_image
        except IOError as error:
            logger.error("Web presentation creation failed for %s with %s" %
                         ('/'.join(self.getPhysicalPath()), str(error)))
            if str(error.args[0]) == "cannot read interlaced PNG files":
                self.image = self.hires_image
                return
            raise ValueError(str(error))
        except ValueError as error:
            logger.error("Web presentation creation failed for %s with %s" %
                         ('/'.join(self.getPhysicalPath()), str(error)))
            self.image = self.hires_image
            return

    def _create_thumbnail(self):
        try:
            transformer = Transformer(ThumbnailResize(self.thumbnail_size))
            thumb = transformer.transform(self.image or self.hires_image,
                                          self.web_format)
            if thumb:
                content_type = self._web2ct[self.web_format]
                self._image_factory('thumbnail_image', thumb, content_type)
        except IOError as error:
            logger.info("Thumbnail creation failed for %s with %s" %
                        ('/'.join(self.getPhysicalPath()), str(error)))
            if str(error.args[0]) == "cannot read interlaced PNG files":
                self.thumbnail_image = None
                return
            else:
                raise ValueError(str(error))
        except ValueError, e:
            logger.info("Thumbnail creation failed for %s with %s" %
                        ('/'.join(self.getPhysicalPath()), str(e)))
            # no thumbnail
            self.thumbnail_image = None
            return
コード例 #13
0
class SilvaSoftwareActivity(Content, SimpleItem, ExternalSource):
    """Collect activity from an RSS feed and generate statistics about
    it.
    """
    grok.implements(ISilvaSoftwareActivity)
    meta_type = 'Silva Software Activity'
    security = ClassSecurityInfo()
    silvaconf.icon('SilvaSoftwareActivity.png')
    silvaconf.priority(9)

    _rss_url = None
    _data = None

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_rss_url')

    def get_rss_url(self):
        return self._rss_url

    security.declareProtected(SilvaPermissions.ChangeSilvaContent,
                              'set_rss_url')

    def set_rss_url(self, url):
        self._rss_url = url

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_changes_since')

    def get_changes_since(self, days=31, empty=True):
        """Return the changes since the given number of days.
        """
        if self._data is not None:
            today = datetime.date.today()
            today_day = today.toordinal()
            since = (today - datetime.timedelta(days))
            since_day = since.toordinal()
            if not empty:
                return list(self._data.values(since_day))
            data = self._data.items(since_day)
            result = []
            for day, values in data:
                while day > since_day:
                    result.append([])
                    since += datetime.timedelta(1)
                    since_day = since.toordinal()
                result.append(values)
                since += datetime.timedelta(1)
                since_day = since.toordinal()
            if result:
                while today_day > since_day:
                    result.append([])
                    since += datetime.timedelta(1)
                    since_day = since.toordinal()
            return result
        return []

    security.declareProtected(SilvaPermissions.ChangeSilvaContent, 'refresh')

    def refresh(self):
        """Refresh the data stored.
        """
        rss_url = self.get_rss_url()
        if rss_url is None:
            return
        if self._data is None:
            self._data = IOBTree()
        data = feedparser.parse(rss_url)
        changed = False
        for entry in data['entries']:
            date = datetime.date(*entry['updated_parsed'][:3])
            key = date.toordinal()
            if key not in self._data:
                self._data[key] = Changes()
            change = Change(entry['id'], entry['author'], date,
                            entry['summary'])
            changed = self._data[key].add(change) or changed
        if changed:
            self._p_changed = True

    def is_previewable(self):
        return False

    def to_html(self, content, request, **parameters):
        return silvaviews.render(self, request)
コード例 #14
0
class SilvaSoftwareActivityAggregator(Content, SimpleItem, ExternalSource):
    """Aggregate multiple activities together.
    """
    grok.implements(ISilvaSoftwareActivityAggregator)
    meta_type = 'Silva Software Activity Aggregator'
    security = ClassSecurityInfo()
    silvaconf.icon('SilvaSoftwareActivity.png')
    silvaconf.priority(9)

    _data = None
    _most_actives = []

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_changes_since')

    def get_changes_since(self, days=31, empty=True):
        """Return the changes since the given number of days.
        """
        if self._data is not None:
            today = datetime.date.today()
            today_day = today.toordinal()
            since = (today - datetime.timedelta(days))
            since_day = since.toordinal()
            if not empty:
                return list(self._data.values(since_day))
            data = self._data.items(since_day)
            result = []
            for day, values in data:
                while day > since_day:
                    result.append([])
                    since += datetime.timedelta(1)
                    since_day = since.toordinal()
                result.append(values)
                since += datetime.timedelta(1)
                since_day = since.toordinal()
            if result:
                while today_day > since_day:
                    result.append([])
                    since += datetime.timedelta(1)
                    since_day = since.toordinal()
            return result
        return []

    security.declareProtected(SilvaPermissions.ChangeSilvaContent, 'refresh')

    def refresh(self):
        """Refresh the data stored.
        """
        if self._data is None:
            self._data = IOBTree()

        counts = {}
        catalog = getUtility(ICatalogService)
        for brain in catalog(meta_type=['Silva Software Activity'],
                             path='/'.join(
                                 self.get_container().getPhysicalPath())):
            activity = brain.getObject()
            counts[brain.content_intid] = 0
            changes = activity.get_changes_since(empty=False)
            print activity.get_title(), len(changes)
            for change in changes:
                for commit in change:
                    key = commit.date.toordinal()
                    if key not in self._data:
                        self._data[key] = Changes()
                    self._data[key].add(commit)
                counts[brain.content_intid] += len(change)
        self._most_actives = map(
            operator.itemgetter(0),
            sorted(counts.items(), key=operator.itemgetter(1), reverse=True))

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_most_active')

    def get_most_active(self, limit=5):
        get_activity = getUtility(IIntIds).getObject
        return map(lambda i: get_activity(i).get_container(),
                   self._most_actives[:limit])

    def is_previewable(self):
        return False

    def to_html(self, content, request, **parameters):
        return silvaviews.render(self, request)
コード例 #15
0
class Folder(Publishable, QuotaContainer, BaseFolder):
    __doc__ = _("""The presentation of the information within a
       publication is structured with folders. They determine the visual
       hierarchy that a Visitor sees. Folders on the top level
       define sections of a publication, subfolders define chapters, etc.
       Note that unlike publications, folders are transparent, meaning you
       can see through them in the sidebar tree navigation and the Publish
       screen.
    """)
    meta_type = "Silva Folder"

    grok.implements(IFolder)
    silvaconf.icon('icons/folder.gif')
    silvaconf.priority(-5)

    security = ClassSecurityInfo()

    @property
    def manage_options(self):
        # A hackish way to get a Silva tab in between the standard ZMI tabs
        manage_options = (BaseFolder.manage_options[0], )
        return manage_options + \
            ({'label':'Silva /edit...', 'action':'edit'}, ) + \
            BaseFolder.manage_options[1:]

    _allow_feeds = False

    def __init__(self, id):
        super(Folder, self).__init__(id)
        self._addables_allowed_in_container = None

    # override ObjectManager implementation, so that additional filtering
    # can be done to remove those objects that aren't zmi-addable
    def filtered_meta_types(self, user=None):
        mt = Folder.inheritedAttribute('filtered_meta_types')(self, user)
        newm = []
        for m in mt:
            cf = m['container_filter']
            #If the container_filter is the special filter for
            #Silva content types, then call it to see if that type
            #should be filtered from the zmi-add list as well
            if cf and cf.__name__ == "SilvaZMIFilter" \
                   and not cf(self, filter_addable=True):
                continue
            newm.append(m)
        return newm

    # override ObjectManager implementaton to trigger all events
    # before deleting content / after deleting all content.
    def manage_delObjects(self, ids=[], REQUEST=None):
        """Delete objects.
        """
        if isinstance(ids, str):
            ids = [ids]

        try:
            protected = self._reserved_names
        except:
            protected = ()

        deleted_objects = []
        for identifier in ids:
            if identifier in protected:
                continue
            ob = self._getOb(identifier, None)
            if ob is None:
                continue
            deleted_objects.append((identifier, ob))

        for identifier, ob in deleted_objects:
            compatibilityCall('manage_beforeDelete', ob, ob, self)
            notify(ObjectWillBeRemovedEvent(ob, self, identifier))

        for identifier, ob in deleted_objects:
            self._objects = tuple(
                [i for i in self._objects if i['id'] != identifier])
            self._delOb(identifier)
            try:
                ob._v__object_deleted__ = 1
            except:
                pass

        for identifier, ob in deleted_objects:
            notify(ObjectRemovedEvent(ob, self, identifier))

        if deleted_objects:
            notifyContainerModified(self)

        if REQUEST is not None:
            # For ZMI
            REQUEST.RESPONSE.redirect(
                absoluteURL(self, REQUEST) + '/manage_main')

    # Override ObjectManager _getOb to publish any approved for future
    # content. This is the only entry point available when the content
    # is fetched from the database.
    def _getOb(self, id, default=_marker):
        content = super(Folder, self)._getOb(id, default)
        if content is _marker:
            raise AttributeError(id)
        if IVersionedContent.providedBy(content):
            if not hasattr(content, '_v_publication_status_updated'):
                try:
                    content._update_publication_status()
                    content._v_publication_status_updated = True
                except:
                    logger.exception(
                        "error while updating publication status for: %s",
                        '/'.join(self.getPhysicalPath() + (id, )))
        return content

    # MANIPULATORS
    security.declareProtected(SilvaPermissions.ApproveSilvaContent,
                              'set_allow_feeds')

    def set_allow_feeds(self, allow):
        """change the flag that indicates whether rss/atom feeds are allowed
        on this container"""
        self._allow_feeds = allow

    security.declareProtected(SilvaPermissions.ApproveSilvaContent,
                              'to_publication')

    def to_publication(self):
        """Turn this folder into a publication.
        """
        from Products.Silva.Publication import Publication
        helpers.convert_content(self, Publication)

    security.declareProtected(SilvaPermissions.ApproveSilvaContent,
                              'to_folder')

    def to_folder(self):
        """Turn this folder into a folder.
        """
        raise ContentError(_("You cannot convert a folder into a folder."),
                           self)

    # Silva addables

    security.declareProtected(SilvaPermissions.ApproveSilvaContent,
                              'set_silva_addables_allowed_in_container')

    def set_silva_addables_allowed_in_container(self, addables):
        self._addables_allowed_in_container = addables

    security.declareProtected(SilvaPermissions.ReadSilvaContent,
                              'get_silva_addables_allowed_in_container')

    def get_silva_addables_allowed_in_container(self):
        return self._addables_allowed_in_container

    security.declareProtected(SilvaPermissions.ReadSilvaContent,
                              'is_silva_addables_acquired')

    def is_silva_addables_acquired(self):
        return self._addables_allowed_in_container is None

    # get_container API

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_container')

    def get_container(self):
        """Get the container an object is in. Can be used with
        acquisition to get the 'nearest' container.
        FIXME: currently the container of a container is itself. Is this the
        right behavior? It leads to subtle bugs..
        """
        return self.aq_inner

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_real_container')

    def get_real_container(self):
        """Get the container, even if we're a container.

        If we're the root object, returns None.

        Can be used with acquisition to get the 'nearest' container.
        """
        container = self.get_container()
        if container is self:
            return container.aq_parent.get_container()
        return container

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'allow_feeds')

    def allow_feeds(self):
        """return the flag that indicates whether rss/atom feeds are allowed
        on this container"""
        return self._allow_feeds

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'is_transparent')

    def is_transparent(self):
        return 1

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'is_published')

    def is_published(self):
        # Folder is published if its default document is published, or,
        # when no default document exists, if any of the objects it contains
        # are published.
        default = self.get_default()
        if default:
            return default.is_published()
        for content in self.get_ordered_publishables():
            if content.is_published():
                return True
        return False

    security.declareProtected(SilvaPermissions.ReadSilvaContent, 'is_approved')

    def is_approved(self):
        # Folder is approved if anything inside is approved
        default = self.get_default()
        if default and default.is_approved():
            return True
        for content in self.get_ordered_publishables():
            if content.is_approved():
                return True
        return False

    def is_deletable(self):
        """deletable if all containing objects are deletable

            NOTE: this will be horribly slow for large trees
        """
        default = self.get_default()
        if default is not None:
            default.is_deletable()
        for content in self.get_ordered_publishables():
            content.is_deletable()

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'fulltext')

    def fulltext(self):
        return [self.id, self.get_title()]

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_default')

    def get_default(self):
        """Get the default content object of the folder.
        """
        content = self._getOb('index', None)
        if IContent.providedBy(content):
            return content
        return None

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_ordered_publishables')

    def get_ordered_publishables(self, interface=IPublishable):
        assert interface.isOrExtends(IPublishable), "Invalid interface"
        result = [
            content for content in self.objectValues(
                meta_types_for_interface(interface))
            if not content.is_default()
        ]
        result.sort(key=IOrderManager(self).get_position)
        return result

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_non_publishables')

    def get_non_publishables(self, interface=INonPublishable):
        assert interface.isOrExtends(INonPublishable), "Invalid interface"
        result = self.objectValues(meta_types_for_interface(interface))
        result.sort(key=lambda o: o.getId())
        return result
コード例 #16
0
class CSVSource(Folder, Asset, EditableExternalSource):
    """CSV Source is an asset that displays tabular data from a
    spreadsheet or database. The format of the uploaded text file
    should be &#8216;comma separated values&#8217;. The asset can
    be linked directly, or inserted in a document with the External
    Source element. If necessary, all aspects of the display can be
    customized in the rendering templates of the CSV Source.
    """
    grok.implements(ICSVSource)

    meta_type = "Silva CSV Source"
    security = ClassSecurityInfo()

    _layout_id = 'layout'
    _default_batch_size = 20

    # register priority, icon and factory
    silvaconf.priority(1)
    silvaconf.icon('www/csvsource.png')

    def __init__(self, id):
        super(CSVSource, self).__init__(id)
        self._raw_data = ''
        self._data = []

    # ACCESSORS

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'to_html')

    def to_html(self, content, request, **parameters):
        """ render HTML for CSV source
        """
        rows = self._data[:]
        param = {}
        param.update(parameters)
        if not param.get('csvtableclass'):
            param['csvtableclass'] = 'default'
        batch_size = self._default_batch_size
        batch = ''
        if param.get('csvbatchsize'):
            batch_size = int(param.get('csvbatchsize'))
        model = content
        if IVersion.providedBy(content):
            model = content.get_content()
        if rows:
            headings = rows[0]
            rows = Batch(rows[1:],
                         count=batch_size,
                         name=self.getId(),
                         request=request)
            param['headings'] = headings
            batch = getMultiAdapter((model, rows, request), IBatching)()
        return self.layout(table=rows, batch=batch, parameters=param)

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_file')

    def get_file(self):
        """Return the file content.
        """
        return self._raw_data

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_file_size')

    def get_file_size(self):
        """Get the size of the file as it will be downloaded.
        """
        if self._raw_data:
            return len(self._raw_data)
        return 0

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_mime_type')

    def get_mime_type(self):
        return 'text/csv'

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_filename')

    def get_filename(self):
        return self.getId() + '.csv'

    security.declareProtected(SilvaPermissions.ViewManagementScreens,
                              'get_table_class')

    def get_table_class(self):
        """Returns css class for table """
        return self._table_class

    security.declareProtected(SilvaPermissions.AccessContentsInformation,
                              'get_description')

    def get_description(self):
        """ Return desc from meta-data system"""
        ms = self.service_metadata
        return ms.getMetadataValue(self, 'silva-extra', 'content_description')

    # MODIFIERS

    def _update_data(self, data):
        def convert_to_unicode(line):
            return [v.decode(self._data_encoding, 'replace') for v in line]

        try:
            csv_data = list(map(convert_to_unicode,
                                csv.reader(StringIO(data))))
        except csv.Error as error:
            raise ValueError("Invalid CSV file: %s" % error.args[0])

        self._data = csv_data
        self._raw_data = data
        notify(ObjectModifiedEvent(self))

    security.declareProtected(SilvaPermissions.ChangeSilvaContent, 'set_file')

    def set_file(self, file):
        return self._update_data(file.read())

    security.declareProtected(SilvaPermissions.ChangeSilvaContent,
                              'set_data_encoding')

    def set_data_encoding(self, encoding):
        self._data_encoding = encoding
        self._update_data(self._raw_data)

    security.declareProtected(SilvaPermissions.ChangeSilvaContent,
                              'set_table_class')

    def set_table_class(self, css_class):
        self._table_class = css_class

    security.declareProtected(SilvaPermissions.ChangeSilvaContent,
                              'set_description')

    def set_description(self, desc):
        if not isinstance(desc, str):
            desc = desc.encode('utf-8')

        binding = getUtility(IMetadataService).getMetadata(self)
        binding.setValues('silva-extra', {'content_description': desc})
コード例 #17
0
ファイル: content.py プロジェクト: silvacms/Products.Silva
class GhostFolder(GhostBase, Folder):
    __doc__ = _("""Ghost Folders are similar to Ghosts, but instead of being a
       placeholder for a document, they create placeholders and/or copies of all
       the contents of the &#8216;original&#8217; folder. The advantage of Ghost
       Folders is the contents stay in sync with the original, by manual or
       automatic resyncing. Note that when a folder is
       ghosted, assets &#8211; such as Images and Files &#8211; are copied
       (physically duplicated) while documents are ghosted.""")

    meta_type = 'Silva Ghost Folder'

    grok.implements(IGhostFolder)
    silvaconf.icon('icons/ghost_folder.png')
    silvaconf.priority(0)

    security = ClassSecurityInfo()

    security.declareProtected(SilvaPermissions.ApproveSilvaContent, 'haunt')

    def haunt(self):
        """populate the the ghost folder with ghosts
        """
        haunted = self.get_haunted()
        if haunted is None:
            return False
        stack = self._haunt_diff(haunted, self)
        errors = []

        while stack:
            # breadth first search
            h_container, h_id, g_container, g_id = stack.pop(0)

            if h_id is None:
                # object was removed from haunted, so just remove it and
                # continue
                g_container.manage_delObjects([g_id])
                continue

            h_ob = h_container._getOb(h_id)
            g_ob = None
            if g_id is not None:
                g_ob = g_container._getOb(g_id)

            try:
                g_ob = get_factory(h_ob)(ghost=g_ob,
                                         container=g_container,
                                         auto_delete=True,
                                         auto_publish=True).modify(
                                             h_ob, h_id).verify()
            except ContentError as error:
                errors.append(error)

            if IContainer.providedBy(h_ob) and g_ob is not None:
                stack.extend(self._haunt_diff(h_ob, g_ob))

        if errors:
            raise ContentErrorBundle(_(
                u"Error while synchronizing the Ghost Folder: "
                u"not all its content have been updated properly."),
                                     content=self,
                                     errors=errors)
        return True

    def _haunt_diff(self, haunted, ghost):
        """diffes two containers

            haunted: IContainer, container to be haunted
            ghost: IContainer, ghost

            returns list of tuple:
            [(haunted, h_id, ghost, g_id)]
            whereby
                h_id is the haunted object's id or None if a ghost exists but
                    no object to be haunted
                g_id is the ghost's id or None if the ghost doesn't exist but
                    has to be created
                haunted and ghost are the objects passed in
        """
        assert IContainer.providedBy(haunted)
        assert IContainer.providedBy(ghost)
        h_ids = list(haunted.objectIds())
        g_ids = list(ghost.objectIds())
        h_ids.sort()
        g_ids.sort()
        ids = []
        while h_ids or g_ids:
            h_id = None
            g_id = None
            if h_ids:
                h_id = h_ids[0]
            if g_ids:
                g_id = g_ids[0]
            if h_id == g_id or h_id is None or g_id is None:
                ids.append((h_id, g_id))
                if h_ids:
                    del h_ids[0]
                if g_ids:
                    del g_ids[0]
            elif h_id < g_id:
                ids.append((h_id, None))
                del h_ids[0]
            elif h_id > g_id:
                ids.append((None, g_id))
                del g_ids[0]
        return [(haunted, h_id, ghost, g_id) for (h_id, g_id) in ids]

    security.declareProtected(SilvaPermissions.ApproveSilvaContent,
                              'to_publication')

    def to_publication(self):
        """replace self with a folder"""
        haunted = self.get_haunted()
        if haunted is not None:
            binding = getUtility(IMetadataService).getMetadata(haunted)
            data_content = binding.get('silva-content', acquire=0)
            data_extra = binding.get('silva-extra', acquire=0)
        helpers.convert_content(self, Publication)
        if haunted is not None:
            binding = getUtility(IMetadataService).getMetadata(self)
            binding.setValues('silva-content', data_content)
            binding.setValues('silva-extra', data_extra)

    security.declareProtected(SilvaPermissions.ApproveSilvaContent,
                              'to_folder')

    def to_folder(self):
        """replace self with a folder"""
        haunted = self.get_haunted()
        if haunted is not None:
            binding = getUtility(IMetadataService).getMetadata(haunted)
            data_content = binding.get('silva-content', acquire=0)
            data_extra = binding.get('silva-extra', acquire=0)
        helpers.convert_content(self, Folder)
        if haunted is not None:
            binding = getUtility(IMetadataService).getMetadata(self)
            binding.setValues('silva-content', data_content)
            binding.setValues('silva-extra', data_extra)

    security.declareProtected(SilvaPermissions.View, 'get_publication')

    def get_publication(self):
        """returns self if haunted object is a publication"""
        content = self.get_haunted()
        if IPublication.providedBy(content):
            return self.aq_inner
        return aq_parent(self).get_publication()

    def is_transparent(self):
        """show in subtree? depends on haunted object"""
        content = self.get_haunted()
        if IContainer.providedBy(content):
            return content.is_transparent()
        return 0

    def is_deletable(self):
        pass