products/CPSRSS

view browser/channels.py @ 297:eb4d70018698

Quickfix for the stock RSS portlet Make it work without switching to the new rendering engine
author Georges Racinet <georges@racinet.fr>
date Sat, 24 Jan 2015 01:21:22 +0100
parents de8a31cea808
children
line source
1 # (C) Copyright 2010 CPS-CMS Community <http://cps-cms.org/>
2 # Authors:
3 # G. Racinet <georges@racinet.fr>
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 from copy import deepcopy
19 import logging
20 import operator
22 from Globals import InitializeClass
23 from AccessControl import ClassSecurityInfo
24 from Acquisition import aq_inner
26 from Products.CMFCore.utils import getToolByName
27 from Products.CMFCore.permissions import View
28 from Products.CPSonFive.browser import AqSafeBrowserView
30 from Products.CPSUtil.id import generateId
32 from Products.CPSRSS.RSSChannel import RSSChannel
33 from Products.CPSRSS.RSSChannelContainer import RSSChannelContainer
34 from Products.CPSRSS.RSSChannelContainer import addRSSChannelContainer
36 from Products.CPSRSS.interfaces import IRSSChannelContainer
38 from Products.CPSUtil.text import summarize
40 logger = logging.getLogger(__name__)
42 DEFAULT_RSS_ITEM_DISPLAY = 'cpsportlet_rssitem_display'
44 class ManageChannels(AqSafeBrowserView):
45 """This view class serves as a view mostly for local channels.
47 It does the container lookup, and provides interface to the container.
48 It also maintains the list of channels to be rendered on context proxy,
49 and provides the rendering logic.
51 Some options make it possible for the regular portlet to call it as well,
52 by forcing the container.
53 This is still experimental, and will likely evolve in something
54 more uniform, in which the portlet can also render local channels.
55 """
57 security = ClassSecurityInfo()
59 def __init__(self, *args, **kwargs):
60 AqSafeBrowserView.__init__(self, *args, **kwargs)
61 self.aqSafeSet('container', self.lookupContainer())
63 security.declareProtected(View, 'setDataStructure')
64 def setDataStructure(self, ds):
65 self.datastructure = ds
67 def portlet(self):
68 return self.datastructure.getDataModel().getObject()
70 def lookupContainer(self, cont_id=None):
71 """Lookup the relevant container and set it up on self."""
72 folder = self.context.aq_inner
74 if cont_id is not None:
75 try:
76 cont = folder[cont_id]
77 except KeyError:
78 return None
79 if not IRSSChannelContainer.providedBy(cont):
80 return None
82 # coding style that works if objectValues turns out to be a generator
83 for cont in folder.objectValues([RSSChannelContainer.meta_type]):
84 return cont
86 def hasContainer(self):
87 return self.aqSafeGet('container') is not None
89 def delChannels(self, chan_ids):
90 chan_ids = self.request.form.get('chan_ids')
91 if chan_ids is None:
92 raise BadRequest("Missing channel ids to remove.")
93 if isinstance(chan_ids, basestring):
94 chan_ids = [chan_ids]
95 cont = self.aqSafeGet('container')
96 cont.manage_delObjects(chan_ids)
97 self.redirectManageChannels()
99 def channels(self, with_activation=True):
100 cont = self.aqSafeGet('container')
101 if cont is None:
102 return ()
103 proxy = self.context
104 channels = cont.objectValues([RSSChannel.meta_type])
105 if not with_activation:
106 return channels
108 dm = proxy.getContent().getDataModel(proxy=proxy)
109 activated = dm.get('channels', ())
110 return tuple(dict(channel=chan, activated=chan.getId() in activated)
111 for chan in channels)
113 # traditional security declaration is necessary for browser:view,
114 # and further in current Five 1.3.2 takes precedence over zcml
115 # Actual protection must be declared, docstring created on the fly if
116 # missing
117 security.declareProtected(View, 'rssItems')
118 def rssItems(self, cont_id=None, **kw):
119 # straight adaptation from old skins script
120 # now that proof-of-concept works, should be split
121 if cont_id is None:
122 cont = self.aqSafeGet('container')
123 else:
124 cont = self.context[cont_id]
126 if cont is None:
127 return ()
129 logger.info("RSS channels from %r", cont)
130 first_item = int(kw.get('first_item', 1))
131 max_items = int(kw.get('max_items', 0))
132 max_words = int(kw.get('max_words', 0))
134 data_items = []
135 channels_ids = kw.get('channels', [])
136 for channel_id in channels_ids:
137 if not cont.hasObject(channel_id):
138 continue
139 channel = cont[channel_id]
140 if channel is None:
141 continue
142 data = channel.getData(max_items + first_item - 1)
143 lines = deepcopy(data['lines']) # RSSChan did a simple copy
144 for line in lines:
145 # lines will be shuffled around (timely sort), so channel
146 # dependent display options have to be copied
147 line['newWindow'] = data['newWindow']
148 data_items += lines
149 if first_item > 1:
150 data_items = data_items[first_item - 1:]
152 # If there is more than 1 channel we need to sort the rss items to
153 # only keep the most recent ones, up to max_items.
154 if len(channels_ids) > 1:
155 # NOTE: One should replace "modified" with "updated" if switching
156 # to a newer version of Feed Parser
157 # http://feedparser.org/docs/date-parsing.html
158 # Relying on the 'modified_parsed' item for the sorting.
159 data_items.sort(key=operator.itemgetter('modified_parsed'),
160 reverse=True)
161 data_items = data_items[:max_items]
163 render_method = kw.get('render_method') or DEFAULT_RSS_ITEM_DISPLAY
164 render_method = getattr(aq_inner(self.context), render_method, None)
166 order = 0
167 for item in data_items:
168 description = item['description']
169 modified = item['modified']
170 author = item['author']
171 if not author:
172 author = 'unknown'
174 # Item rendering and display
175 rendered = ''
177 # render the item using a custom display method (.zpt, .py, .dtml)
178 if render_method is not None:
179 item['summary'] = summarize(description, max_words)
180 kw.update({'item': item,
181 'order': order,
182 })
183 rendered = apply(render_method, (), kw)
185 # this information is used by custom templates that call
186 # getRSSItems() directly. GR TODO: who are these ?
187 data_items[order].update(
188 {'description': description,
189 'rendered': rendered,
190 'metadata':
191 {'creator': author,
192 'contributor': author,
193 'date': modified,
194 'issued': modified,
195 'created': modified,
196 },
197 })
198 order += 1
200 return data_items
202 def setActivated(self):
203 """Set the list of activated channels."""
204 activated = self.request.form.get('activated', ())
205 proxy = self.context
206 doc = proxy.getEditableContent()
207 dm = doc.getDataModel(proxy=proxy)
208 if not 'channels' in dm:
209 raise RuntimeError(
210 "Document type %r lacks fields for channels management",
211 doc.portal_type)
213 available = set(chan.getId()
214 for chan in self.channels(with_activation=False))
215 dm['channels'] = [cid for cid in activated if cid in available]
216 dm._commit()
217 self.redirectManageChannels()
219 def redirectManageChannels(self):
220 self.request.RESPONSE.redirect('/'.join((
221 self.context.absolute_url_path(), 'manage_channels.html')))
223 def refresh(self):
224 cont = self.aqSafeGet('container')
225 if cont is not None:
226 cont.refresh()
227 self.redirectManageChannels()
229 def addChannel(self, url=None):
230 """Create a channel from explicit url or from request form.
232 All other properties are retrieved from the feed itself.
233 """
235 if url is None:
236 # taking from request
237 url = self.request.form['channel_url']
239 cont = self.aqSafeGet('container')
240 if cont is None:
241 cont = addRSSChannelContainer(self.context)
242 self.aqSafeSet('container', cont)
243 channel = RSSChannel('channel', url).__of__(cont)
244 d = channel.getData() # might be quite empty if feed has problems
246 title = d.get('title', '')
247 description = d.get('description', '')
248 cid = generateId(title, container=cont)
249 channel._setId(cid)
250 channel.manage_changeProperties(title=title, description=description)
252 cont._setObject(cid, channel)
253 self.redirectManageChannels()
255 InitializeClass(ManageChannels)