Back to index

plone3  3.1.7
collection.py
Go to the documentation of this file.
00001 import random
00002 
00003 from zope.interface import implements
00004 from zope.component import getMultiAdapter
00005 
00006 from plone.portlets.interfaces import IPortletDataProvider
00007 from plone.app.portlets.portlets import base
00008 
00009 from zope import schema
00010 from zope.formlib import form
00011 
00012 from plone.memoize.instance import memoize
00013 from plone.memoize import ram
00014 from plone.memoize.compress import xhtml_compress
00015 
00016 from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
00017 from plone.app.vocabularies.catalog import SearchableTextSourceBinder
00018 from plone.app.form.widgets.uberselectionwidget import UberSelectionWidget
00019 
00020 from Products.ATContentTypes.interface import IATTopic
00021 
00022 from plone.portlet.collection import PloneMessageFactory as _
00023 
00024 class ICollectionPortlet(IPortletDataProvider):
00025     """A portlet which renders the results of a collection object.
00026     """
00027 
00028     header = schema.TextLine(title=_(u"Portlet header"),
00029                              description=_(u"Title of the rendered portlet"),
00030                              required=True)
00031 
00032     target_collection = schema.Choice(title=_(u"Target collection"),
00033                                   description=_(u"Find the collection which provides the items to list"),
00034                                   required=True,
00035                                   source=SearchableTextSourceBinder({'object_provides' : IATTopic.__identifier__},
00036                                                                     default_query='path:'))
00037 
00038     limit = schema.Int(title=_(u"Limit"),
00039                        description=_(u"Specify the maximum number of items to show in the portlet. "
00040                                        "Leave this blank to show all items."),
00041                        required=False)
00042                        
00043     random = schema.Bool(title=_(u"Select random items"),
00044                          description=_(u"If enabled, items will be selected randomly from the collection, "
00045                                         "rather than based on its sort order."),
00046                          required=True,
00047                          default=False)
00048                        
00049     show_more = schema.Bool(title=_(u"Show more... link"),
00050                        description=_(u"If enabled, a more... link will appear in the footer of the portlet, "
00051                                       "linking to the underlying Collection."),
00052                        required=True,
00053                        default=True)
00054                        
00055     show_dates = schema.Bool(title=_(u"Show dates"),
00056                        description=_(u"If enabled, effective dates will be shown underneath the items listed."),
00057                        required=True,
00058                        default=False)
00059 
00060 class Assignment(base.Assignment):
00061     """
00062     Portlet assignment.    
00063     This is what is actually managed through the portlets UI and associated
00064     with columns.
00065     """
00066 
00067     implements(ICollectionPortlet)
00068 
00069     header = u""
00070     target_collection=None
00071     limit = None
00072     random = False
00073     show_more = True
00074     show_dates = False
00075 
00076     def __init__(self, header=u"", target_collection=None, limit=None, random=False, show_more=True, show_dates=False):
00077         self.header = header
00078         self.target_collection = target_collection
00079         self.limit = limit
00080         self.random = random
00081         self.show_more = show_more
00082         self.show_dates = show_dates
00083 
00084     @property
00085     def title(self):
00086         """This property is used to give the title of the portlet in the
00087         "manage portlets" screen. Here, we use the title that the user gave.
00088         """
00089         return self.header
00090 
00091 
00092 class Renderer(base.Renderer):
00093     """Portlet renderer.
00094     
00095     This is registered in configure.zcml. The referenced page template is
00096     rendered, and the implicit variable 'view' will refer to an instance
00097     of this class. Other methods can be added and referenced in the template.
00098     """
00099 
00100     _template = ViewPageTemplateFile('collection.pt')
00101 
00102     def __init__(self, *args):
00103         base.Renderer.__init__(self, *args)
00104 
00105     # Cached version - needs a proper cache key
00106     # @ram.cache(render_cachekey)
00107     # def render(self):
00108     #     if self.available:
00109     #         return xhtml_compress(self._template())
00110     #     else:
00111     #         return ''
00112 
00113     render = _template
00114 
00115     @property
00116     def available(self):
00117         return len(self.results())
00118 
00119     def collection_url(self):
00120         collection = self.collection()
00121         if collection is None:
00122             return None
00123         else:
00124             return collection.absolute_url()
00125 
00126     def results(self):
00127         """ Get the actual result brains from the collection. 
00128             This is a wrapper so that we can memoize if and only if we aren't
00129             selecting random items."""
00130         if self.data.random:
00131             return self._random_results()
00132         else:
00133             return self._standard_results()
00134 
00135     @memoize
00136     def _standard_results(self):
00137         results = []
00138         collection = self.collection()
00139         if collection is not None:
00140             results = collection.queryCatalog()
00141             if self.data.limit and self.data.limit > 0:
00142                 results = results[:self.data.limit]
00143         return results
00144         
00145     # intentionally non-memoized
00146     def _random_results(self):
00147         results = []
00148         collection = self.collection()
00149         if collection is not None:
00150             """
00151             Kids, do not try this at home.
00152             
00153             We're poking at the internals of the (lazy) catalog results to avoid
00154             instantiating catalog brains unnecessarily.
00155             
00156             We're expecting a LazyCat wrapping two LazyMaps as the return value from
00157             Products.ATContentTypes.content.topic.ATTopic.queryCatalog.  The second
00158             of these contains the results of the catalog query.  We force sorting
00159             off because it's unnecessary and might result in a different structure of
00160             lazy objects.
00161             
00162             Using the correct LazyMap (results._seq[1]), we randomly pick a catalog index
00163             and then retrieve it as a catalog brain using the _func method.
00164             """
00165             
00166             results = collection.queryCatalog(sort_on=None)
00167             limit = self.data.limit and min(len(results), self.data.limit) or 1
00168             try:
00169                 results = [results._seq[1]._func(i) for i in random.sample(results._seq[1]._seq, limit)]
00170             except AttributeError, IndexError:
00171                 # This handles the cases where the lazy objects returned by the catalog
00172                 # are structured differently than expected.
00173                 results = []
00174         return results
00175         
00176     @memoize
00177     def collection(self):
00178         """ get the collection the portlet is pointing to"""
00179         
00180         collection_path = self.data.target_collection
00181         if not collection_path:
00182             return None
00183 
00184         if collection_path.startswith('/'):
00185             collection_path = collection_path[1:]
00186         
00187         if not collection_path:
00188             return None
00189 
00190         portal_state = getMultiAdapter((self.context, self.request), name=u'plone_portal_state')
00191         portal = portal_state.portal()
00192         return portal.restrictedTraverse(collection_path, default=None)
00193         
00194 class AddForm(base.AddForm):
00195     """Portlet add form.
00196     
00197     This is registered in configure.zcml. The form_fields variable tells
00198     zope.formlib which fields to display. The create() method actually
00199     constructs the assignment that is being added.
00200     """
00201     form_fields = form.Fields(ICollectionPortlet)
00202     form_fields['target_collection'].custom_widget = UberSelectionWidget
00203     
00204     label = _(u"Add Collection Portlet")
00205     description = _(u"This portlet display a listing of items from a Collection.")
00206 
00207     def create(self, data):
00208         return Assignment(**data)
00209 
00210 class EditForm(base.EditForm):
00211     """Portlet edit form.
00212     
00213     This is registered with configure.zcml. The form_fields variable tells
00214     zope.formlib which fields to display.
00215     """
00216 
00217     form_fields = form.Fields(ICollectionPortlet)
00218     form_fields['target_collection'].custom_widget = UberSelectionWidget
00219 
00220     label = _(u"Edit Collection Portlet")
00221     description = _(u"This portlet display a listing of items from a Collection.")