class MockupAsset(Asset, SimpleItem): """A mockup non-publishable. """ grok.implements(IMockupAsset) meta_type = 'Mockup Asset' silvaconf.priority(-10) silvaconf.icon('tests/mockers.png')
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()
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)
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'))
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', ]
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', ]
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
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))
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
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())
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
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
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)
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)
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
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 ‘comma separated values’. 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})
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 ‘original’ 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 – such as Images and Files – 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