products/CPSRSS

changeset 242:d5edc7d46e93

#2300: consolidated of RSS portlet logic and declaration here
author Georges Racinet on purity.racinet.fr <georges@racinet.fr>
date Mon, 06 Dec 2010 02:40:50 +0100
parents 4a1f6e273c39
children 56a93ccb2b27
files CHANGES __init__.py profiles/default/layouts.xml profiles/default/layouts/rss_portlet.xml profiles/default/schemas.xml profiles/default/schemas/rss_portlet.xml profiles/default/skins.xml profiles/default/types.xml profiles/default/types/Portlet_RSS.xml skins/getRSSItems.py skins/widget_portlet_rss.pt
diffstat 11 files changed, 422 insertions(+), 2 deletions(-) [+]
line diff
     1.1 --- a/CHANGES
     1.2 +++ b/CHANGES
     1.3 @@ -6,6 +6,7 @@
     1.4  - #446: local RSS Channels and management via normal CPS UI
     1.5  Bug fixes
     1.6  ~~~~~~~~~
     1.7 +- #2300: consolidated of RSS portlet logic and declaration here
     1.8  - Related to #2185: remaining problems with non ascii chars in title/descr
     1.9  New internal features
    1.10  ~~~~~~~~~~~~~~~~~~~~~
     2.1 --- a/__init__.py
     2.2 +++ b/__init__.py
     2.3 @@ -21,14 +21,17 @@
     2.4  # $Id$
     2.5  
     2.6  from Products.CMFCore.utils import ToolInit
     2.7 -
     2.8 -import RSSTool
     2.9 +from Products.CMFCore.DirectoryView import registerDirectory
    2.10  
    2.11  from Products.GenericSetup import profile_registry
    2.12  from Products.GenericSetup import EXTENSION
    2.13  
    2.14 +import RSSTool
    2.15 +
    2.16  from Products.CPSCore.interfaces import ICPSSite
    2.17  
    2.18 +registerDirectory('skins', globals())
    2.19 +
    2.20  tools = (RSSTool.RSSTool, )
    2.21  
    2.22  def initialize(registrar):
     3.1 new file mode 100644
     3.2 --- /dev/null
     3.3 +++ b/profiles/default/layouts.xml
     3.4 @@ -0,0 +1,4 @@
     3.5 +<?xml version="1.0"?>
     3.6 +<object name="portal_layouts" meta_type="CPS Layouts Tool">
     3.7 + <object name="rss_portlet" meta_type="CPS Layout"/>
     3.8 +</object>
     3.9 \ No newline at end of file
     4.1 new file mode 100644
     4.2 --- /dev/null
     4.3 +++ b/profiles/default/layouts/rss_portlet.xml
     4.4 @@ -0,0 +1,188 @@
     4.5 +<?xml version="1.0"?>
     4.6 +<object name="rss_portlet" meta_type="CPS Layout">
     4.7 + <property name="layout_create_method"></property>
     4.8 + <property name="layout_edit_method"></property>
     4.9 + <property name="layout_view_method"></property>
    4.10 + <property name="style_prefix">layout_portlet_</property>
    4.11 + <property name="flexible_widgets"/>
    4.12 + <property name="validate_values_expr"></property>
    4.13 + <widget name="cache_timeout" meta_type="Int Widget">
    4.14 +  <property name="fields">
    4.15 +   <element value="cache_timeout"/>
    4.16 +  </property>
    4.17 +  <property name="is_required">True</property>
    4.18 +  <property name="is_limited">True</property>
    4.19 +  <property name="min_value">60</property>
    4.20 +  <property name="max_value">2000000000</property>
    4.21 +  <property
    4.22 +     name="label_edit">cpsportlets_rss_update_frequency_label</property>
    4.23 +  <property name="is_i18n">True</property>
    4.24 +  <property name="readonly_layout_modes"/>
    4.25 +  <property name="hidden_layout_modes">
    4.26 +   <element value="view"/>
    4.27 +  </property>
    4.28 +  <property name="hidden_readonly_layout_modes"/>
    4.29 + </widget>
    4.30 + <widget name="channels" meta_type="MultiSelect Widget">
    4.31 +  <property name="fields">
    4.32 +   <element value="channels"/>
    4.33 +  </property>
    4.34 +  <property name="label_edit">cpsportlets_rss_channel_label</property>
    4.35 +  <property name="is_i18n">True</property>
    4.36 +  <property name="readonly_layout_modes"/>
    4.37 +  <property name="hidden_layout_modes">
    4.38 +   <element value="view"/>
    4.39 +  </property>
    4.40 +  <property name="hidden_readonly_layout_modes"/>
    4.41 +  <property name="vocabulary">cpsportlets_rss_channels_voc</property>
    4.42 + </widget>
    4.43 + <widget name="display_description" meta_type="Boolean Widget">
    4.44 +  <property name="fields">
    4.45 +   <element value="display_description"/>
    4.46 +  </property>
    4.47 +  <property
    4.48 +     name="label_edit">cpsportlets_common_display_description_label</property>
    4.49 +  <property name="is_i18n">True</property>
    4.50 +  <property name="readonly_layout_modes"/>
    4.51 +  <property name="hidden_layout_modes">
    4.52 +   <element value="view"/>
    4.53 +  </property>
    4.54 +  <property name="hidden_readonly_layout_modes"/>
    4.55 +  <property name="hidden_if_expr">context/render_method</property>
    4.56 + </widget>
    4.57 + <widget name="first_item" meta_type="Int Widget">
    4.58 +  <property name="fields">
    4.59 +   <element value="first_item"/>
    4.60 +  </property>
    4.61 +  <property name="label_edit">cpsportlets_common_first_item_label</property>
    4.62 +  <property name="is_i18n">True</property>
    4.63 +  <property name="readonly_layout_modes"/>
    4.64 +  <property name="hidden_layout_modes">
    4.65 +   <element value="view"/>
    4.66 +  </property>
    4.67 +  <property name="hidden_readonly_layout_modes"/>
    4.68 +  <property name="is_limited">True</property>
    4.69 +  <property name="min_value">1</property>
    4.70 +  <property name="max_value">15</property>
    4.71 + </widget>
    4.72 + <widget name="link_string" meta_type="String Widget">
    4.73 +  <property name="fields">
    4.74 +   <element value="link_string"/>
    4.75 +  </property>
    4.76 +  <property name="label_edit">cpsportlets_common_link_string_label</property>
    4.77 +  <property name="is_i18n">True</property>
    4.78 +  <property name="readonly_layout_modes"/>
    4.79 +  <property name="hidden_layout_modes">
    4.80 +   <element value="view"/>
    4.81 +  </property>
    4.82 +  <property name="hidden_readonly_layout_modes"/>
    4.83 +  <property name="hidden_if_expr">context/render_method</property>
    4.84 + </widget>
    4.85 + <widget name="max_items" meta_type="Int Widget">
    4.86 +  <property name="fields">
    4.87 +   <element value="max_items"/>
    4.88 +  </property>
    4.89 +  <property name="label_edit">cpsportlets_common_max_items_label</property>
    4.90 +  <property name="is_i18n">True</property>
    4.91 +  <property name="readonly_layout_modes"/>
    4.92 +  <property name="hidden_layout_modes">
    4.93 +   <element value="view"/>
    4.94 +  </property>
    4.95 +  <property name="hidden_readonly_layout_modes"/>
    4.96 +  <property name="min_value">1</property>
    4.97 +  <property name="max_value">15</property>
    4.98 + </widget>
    4.99 + <widget name="max_words" meta_type="Int Widget">
   4.100 +  <property name="fields">
   4.101 +   <element value="max_words"/>
   4.102 +  </property>
   4.103 +  <property name="label_edit">cpsportlets_rss_max_words_label</property>
   4.104 +  <property name="is_i18n">True</property>
   4.105 +  <property name="readonly_layout_modes"/>
   4.106 +  <property name="hidden_layout_modes">
   4.107 +   <element value="view"/>
   4.108 +  </property>
   4.109 +  <property name="hidden_readonly_layout_modes"/>
   4.110 +  <property name="hidden_if_expr">not:context/display_description</property>
   4.111 + </widget>
   4.112 + <widget name="portlet" meta_type="Generic Portlet Widget">
   4.113 +  <property name="fields">
   4.114 +   <element value="portlet"/>
   4.115 +  </property>
   4.116 +  <property name="readonly_layout_modes"/>
   4.117 +  <property name="hidden_layout_modes">
   4.118 +   <element value="edit"/>
   4.119 +  </property>
   4.120 +  <property name="hidden_readonly_layout_modes"/>
   4.121 +  <property name="render_method">widget_portlet_rss</property>
   4.122 + </widget>
   4.123 + <widget name="render_method" meta_type="String Widget">
   4.124 +  <property name="fields">
   4.125 +   <element value="render_method"/>
   4.126 +  </property>
   4.127 +  <property
   4.128 +     name="label_edit">cpsportlets_common_render_method_label</property>
   4.129 +  <property name="is_i18n">True</property>
   4.130 +  <property name="hidden_layout_modes">
   4.131 +   <element value="view"/>
   4.132 +  </property>
   4.133 +  <property name="display_width">30</property>
   4.134 + </widget>
   4.135 + <widget name="short_syndication_formats" meta_type="CheckBox Widget">
   4.136 +  <property name="fields">
   4.137 +   <element value="short_syndication_formats"/>
   4.138 +  </property>
   4.139 +  <property
   4.140 +     name="label_edit">cpsportlets_common_short_syndication_formats_label</property>
   4.141 +  <property name="is_i18n">True</property>
   4.142 +  <property name="hidden_layout_modes">
   4.143 +   <element value="view"/>
   4.144 +  </property>
   4.145 + </widget>
   4.146 + <widget name="syndication_formats" meta_type="MultiSelect Widget">
   4.147 +  <property name="fields">
   4.148 +   <element value="syndication_formats"/>
   4.149 +  </property>
   4.150 +  <property
   4.151 +     name="label_edit">cpsportlets_common_syndication_formats_label</property>
   4.152 +  <property name="is_i18n">True</property>
   4.153 +  <property name="hidden_layout_modes">
   4.154 +   <element value="view"/>
   4.155 +  </property>
   4.156 +  <property name="vocabulary">cpsportlets_syndication_formats_voc</property>
   4.157 +  <property name="translated">True</property>
   4.158 + </widget>
   4.159 + <table>
   4.160 +  <row>
   4.161 +   <cell name="portlet" ncols="2"/>
   4.162 +  </row>
   4.163 +  <row>
   4.164 +   <cell name="channels" ncols="2"/>
   4.165 +  </row>
   4.166 +  <row>
   4.167 +   <cell name="cache_timeout" ncols="2"/>
   4.168 +  </row>
   4.169 +  <row>
   4.170 +   <cell name="render_method" ncols="2"/>
   4.171 +  </row>
   4.172 +  <row>
   4.173 +   <cell name="display_description" ncols="2"/>
   4.174 +  </row>
   4.175 +  <row>
   4.176 +   <cell name="first_item" ncols="2"/>
   4.177 +  </row>
   4.178 +  <row>
   4.179 +   <cell name="max_items" ncols="2"/>
   4.180 +  </row>
   4.181 +  <row>
   4.182 +   <cell name="max_words" ncols="2"/>
   4.183 +  </row>
   4.184 +  <row>
   4.185 +   <cell name="link_string" ncols="2"/>
   4.186 +  </row>
   4.187 +  <row>
   4.188 +   <cell name="syndication_formats"/>
   4.189 +   <cell name="short_syndication_formats"/>
   4.190 +  </row>
   4.191 + </table>
   4.192 +</object>
     5.1 new file mode 100644
     5.2 --- /dev/null
     5.3 +++ b/profiles/default/schemas.xml
     5.4 @@ -0,0 +1,4 @@
     5.5 +<?xml version="1.0"?>
     5.6 +<object name="portal_schemas" meta_type="CPS Schemas Tool">
     5.7 + <object name="rss_portlet" meta_type="CPS Schema"/>
     5.8 +</object>
     5.9 \ No newline at end of file
     6.1 new file mode 100644
     6.2 --- /dev/null
     6.3 +++ b/profiles/default/schemas/rss_portlet.xml
     6.4 @@ -0,0 +1,20 @@
     6.5 +<?xml version="1.0"?>
     6.6 +<object name="rss_portlet" meta_type="CPS Schema">
     6.7 + <field name="channels" meta_type="CPS String List Field"/>
     6.8 + <field name="display_description" meta_type="CPS Boolean Field">
     6.9 +  <property name="default_expr">python:True</property>
    6.10 + </field>
    6.11 + <field name="first_item" meta_type="CPS Int Field">
    6.12 +  <property name="default_expr">python:1</property>
    6.13 + </field>
    6.14 + <field name="link_string" meta_type="CPS String Field">
    6.15 +  <property name="default_expr">string:&gt;&gt;</property>
    6.16 + </field>
    6.17 + <field name="max_items" meta_type="CPS Int Field">
    6.18 +  <property name="default_expr">python:5</property>
    6.19 + </field>
    6.20 + <field name="max_words" meta_type="CPS Int Field"/>
    6.21 + <field name="render_method" meta_type="CPS String Field"/>
    6.22 + <field name="short_syndication_formats" meta_type="CPS Int Field"/>
    6.23 + <field name="syndication_formats" meta_type="CPS String List Field"/>
    6.24 +</object>
     7.1 new file mode 100644
     7.2 --- /dev/null
     7.3 +++ b/profiles/default/skins.xml
     7.4 @@ -0,0 +1,11 @@
     7.5 +<?xml version="1.0"?>
     7.6 +<object name="portal_skins">
     7.7 + <object name="cps_rss" meta_type="Filesystem Directory View"
     7.8 +    directory="CPSRSS/skins"/>
     7.9 + <skin-path name="CPSSkins">
    7.10 +  <layer name="cps_rss" insert-after="custom"/>
    7.11 + </skin-path>
    7.12 + <skin-path name="CPSSkins-macroless">
    7.13 +  <layer name="cps_rss" insert-after="custom"/>
    7.14 + </skin-path>
    7.15 +</object>
    7.16 \ No newline at end of file
     8.1 new file mode 100644
     8.2 --- /dev/null
     8.3 +++ b/profiles/default/types.xml
     8.4 @@ -0,0 +1,4 @@
     8.5 +<?xml version="1.0"?>
     8.6 +<object name="portal_types">
     8.7 + <object name="Portlet RSS" meta_type="CPS Flexible Type Information"/>
     8.8 +</object>
     8.9 \ No newline at end of file
     9.1 new file mode 100644
     9.2 --- /dev/null
     9.3 +++ b/profiles/default/types/Portlet_RSS.xml
     9.4 @@ -0,0 +1,55 @@
     9.5 +<?xml version="1.0"?>
     9.6 +<object name="RSS Portlet" meta_type="CPS Flexible Type Information"
     9.7 +   xmlns:i18n="http://xml.zope.org/namespaces/i18n">
     9.8 + <property name="title">portal_type_RSSPortlet_title</property>
     9.9 + <property name="description">portal_type_RSSPortlet_description</property>
    9.10 + <property name="content_icon">rss_portlet_icon.png</property>
    9.11 + <property name="content_meta_type">CPS Portlet</property>
    9.12 + <property name="product">CPSPortlets</property>
    9.13 + <property name="factory">addCPSPortlet</property>
    9.14 + <property name="immediate_view">cpsportlet_edit_form</property>
    9.15 + <property name="global_allow">False</property>
    9.16 + <property name="filter_content_types">False</property>
    9.17 + <property name="allowed_content_types"/>
    9.18 + <property name="allow_discussion">False</property>
    9.19 + <property name="cps_is_searchable">False</property>
    9.20 + <property name="cps_proxy_type"></property>
    9.21 + <property name="cps_display_as_document_in_listing">False</property>
    9.22 + <property name="schemas">
    9.23 +  <element value="portlet_common"/>
    9.24 +  <element value="rss_portlet"/>
    9.25 +  <element value="common"/>
    9.26 +  <element value="metadata"/>
    9.27 + </property>
    9.28 + <property name="layouts">
    9.29 +  <element value="portlet_common"/>
    9.30 +  <element value="rss_portlet"/>
    9.31 + </property>
    9.32 + <property name="layout_clusters">
    9.33 +  <element value="metadata:metadata"/>
    9.34 + </property>
    9.35 + <property name="flexible_layouts"/>
    9.36 + <property name="storage_methods"/>
    9.37 + <property name="cps_is_portlet">True</property>
    9.38 + <alias from="(Default)" to="render"/>
    9.39 + <alias from="view" to="render"/>
    9.40 + <action title="action_create" action_id="create" category="object"
    9.41 +    condition_expr="" url_expr="string:${object_url}/cpsportlet_create_form"
    9.42 +    visible="False">
    9.43 +  <permission value="Manage Portlets"/>
    9.44 + </action>
    9.45 + <action title="action_edit" action_id="edit" category="object"
    9.46 +    condition_expr="" url_expr="string:${object_url}/cpsportlet_edit_form"
    9.47 +    visible="False">
    9.48 +  <permission value="Manage Portlets"/>
    9.49 + </action>
    9.50 + <action title="action_view" action_id="view" category="object"
    9.51 +    condition_expr="" url_expr="string:${object_url}/render" visible="False">
    9.52 +  <permission value="View"/>
    9.53 + </action>
    9.54 + <action title="action_metadata" action_id="metadata" category="object"
    9.55 +    condition_expr="" url_expr="string:${object_url}/cpsportlet_metadata"
    9.56 +    visible="False">
    9.57 +  <permission value="Manage Portlets"/>
    9.58 + </action>
    9.59 +</object>
    10.1 new file mode 100644
    10.2 --- /dev/null
    10.3 +++ b/skins/getRSSItems.py
    10.4 @@ -0,0 +1,101 @@
    10.5 +##parameters=**kw
    10.6 +#
    10.7 +# $Id$
    10.8 +"""The script that returns RSS items according to the portlet parameters.
    10.9 +"""
   10.10 +
   10.11 +from copy import deepcopy
   10.12 +from logging import getLogger
   10.13 +
   10.14 +import operator
   10.15 +
   10.16 +LOG_KEY = 'CPSPortlets.getRSSItems'
   10.17 +
   10.18 +DEFAULT_RSS_ITEM_DISPLAY = 'cpsportlet_rssitem_display'
   10.19 +
   10.20 +#logger = getLogger(LOG_KEY)
   10.21 +
   10.22 +rsstool = getattr(context, 'portal_rss', None)
   10.23 +if rsstool is None:
   10.24 +    return []
   10.25 +
   10.26 +first_item = int(kw.get('first_item', 1))
   10.27 +max_items = int(kw.get('max_items', 0))
   10.28 +max_words = int(kw.get('max_words', 0))
   10.29 +
   10.30 +data_items = []
   10.31 +channels_ids = kw.get('channels')
   10.32 +for channel_id in channels_ids:
   10.33 +    channel = getattr(rsstool.aq_inner.aq_explicit, channel_id, None)
   10.34 +    if channel is None:
   10.35 +        continue
   10.36 +    data = channel.getData(max_items + first_item - 1)
   10.37 +    lines = deepcopy(data['lines']) # RSSChan did a simple copy
   10.38 +    for line in lines:
   10.39 +        # lines will be shuffled around (timely sort), so channel dependent
   10.40 +        # display options have to be copied
   10.41 +        line['newWindow'] = data['newWindow']
   10.42 +    data_items += lines
   10.43 +    if first_item > 1:
   10.44 +        data_items = data_items[first_item - 1:]
   10.45 +
   10.46 +# If there is more than 1 channel we need to sort the rss items to only keep the
   10.47 +# more recent ones, up to max_items.
   10.48 +if len(channels_ids) > 1:
   10.49 +    # NOTE: One should replace "modified" with "updated" if switching to a newer
   10.50 +    # version of Feed Parser http://feedparser.org/docs/date-parsing.html
   10.51 +    # Relying on the 'modified_parsed' item for the sorting.
   10.52 +    data_items.sort(key=operator.itemgetter('modified_parsed'), reverse=True)
   10.53 +    data_items = data_items[:max_items]
   10.54 +
   10.55 +def summarize(text='', max_words=20):
   10.56 +    """summarize the text by returning the first max_words
   10.57 +    """
   10.58 +    if not max_words:
   10.59 +        return text
   10.60 +    if not text:
   10.61 +        return ''
   10.62 +    split_text = text.split(' ', max_words)[0:max_words]
   10.63 +    res = ''
   10.64 +    if split_text:
   10.65 +        res = ' '.join(split_text) + ' ...'
   10.66 +    return res
   10.67 +
   10.68 +render_method = kw.get('render_method') or DEFAULT_RSS_ITEM_DISPLAY
   10.69 +render_method = getattr(context, render_method, None)
   10.70 +
   10.71 +order = 0
   10.72 +for item in data_items:
   10.73 +    description = item['description']
   10.74 +    modified = item['modified']
   10.75 +    author = item['author']
   10.76 +    if not author:
   10.77 +        author = 'unknown'
   10.78 +
   10.79 +    # Item rendering and display
   10.80 +    rendered = ''
   10.81 +
   10.82 +    # render the item using a custom display method (.zpt, .py, .dtml)
   10.83 +    if render_method is not None:
   10.84 +        item['summary'] = summarize(description, max_words)
   10.85 +        kw.update({'item': item,
   10.86 +                   'order': order,
   10.87 +                  })
   10.88 +        rendered = apply(render_method, (), kw)
   10.89 +
   10.90 +    # this information is used by custom templates that call getRSSItems()
   10.91 +    # directly.
   10.92 +    data_items[order].update(
   10.93 +        {'description': description,
   10.94 +         'rendered': rendered,
   10.95 +         'metadata':
   10.96 +            {'creator': author,
   10.97 +             'contributor': author,
   10.98 +             'date': modified,
   10.99 +             'issued': modified,
  10.100 +             'created': modified,
  10.101 +            },
  10.102 +        })
  10.103 +    order += 1
  10.104 +
  10.105 +return data_items
    11.1 new file mode 100644
    11.2 --- /dev/null
    11.3 +++ b/skins/widget_portlet_rss.pt
    11.4 @@ -0,0 +1,29 @@
    11.5 +<tal:block define="ds options/datastructure;
    11.6 +                   export request/form/export|nothing;
    11.7 +                   rssitems python: here.getRSSItems(**dict(ds))"
    11.8 +           condition="rssitems">
    11.9 +
   11.10 +  <tal:block condition="not:export">
   11.11 +    <ul class="cpsskinsMenu">
   11.12 +      <tal:block repeat="item rssitems">
   11.13 +        <li tal:define="item_url item/url;
   11.14 +                        rendered item/rendered">
   11.15 +          <a tal:attributes="href item_url;
   11.16 +	   onclick python:item['newWindow'] and 'window.open(\x22%s\x22)\x3b return false\x3b' % item_url or None"
   11.17 +             tal:content="structure rendered" />
   11.18 +        </li>
   11.19 +      </tal:block>
   11.20 +    </ul>
   11.21 +    <tal:block condition="rssitems">
   11.22 +      <metal:block use-macro="here/cpsportlet_macros/macros/xml_exports" />
   11.23 +    </tal:block>
   11.24 +  </tal:block>
   11.25 +
   11.26 +  <tal:block condition="export">
   11.27 +    <tal:block define="export_macro string:export_${export};
   11.28 +                       items rssitems">
   11.29 +      <metal:block use-macro="here/cpsportlet_macros/macros/?export_macro|
   11.30 +                              here/cpsportlet_macros/macros/export_not_implemented" />
   11.31 +    </tal:block>
   11.32 +  </tal:block>
   11.33 +</tal:block>