From: Jean-Paul L. <svn...@pl...> - 2008-01-20 20:09:20
|
Author: jladage Date: Sun Jan 20 20:09:21 2008 New Revision: 19049 Added: plone.portlets/branches/zest-dashboard-dnd/plone/portlets/columns.py Modified: plone.portlets/branches/zest-dashboard-dnd/plone/portlets/README.txt plone.portlets/branches/zest-dashboard-dnd/plone/portlets/interfaces.py plone.portlets/branches/zest-dashboard-dnd/plone/portlets/tests.py Log: First draft on the column manager Modified: plone.portlets/branches/zest-dashboard-dnd/plone/portlets/README.txt ============================================================================== --- plone.portlets/branches/zest-dashboard-dnd/plone/portlets/README.txt (original) +++ plone.portlets/branches/zest-dashboard-dnd/plone/portlets/README.txt Sun Jan 20 20:09:21 2008 @@ -880,22 +880,60 @@ turn, is a marker interface that you can apply to a portlet manager to get the placeless behaviour. - >>> from plone.portlets.interfaces import IPlacelessPortletManager - +A Dashboard typically renders an number of PortletManagers. Even the default +Portlets in Plone have a left_column and right_column. We want to be able to +move around portlets between those managers. + +A utility named ColumnManager allows you to store the PortletManagers id and +lookup other PortletManager. + + >>> from plone.portlets.columns import ColumnManager + >>> from plone.portlets.interfaces import IColumnManager + >>> dashboardManager = ColumnManager() >>> sm = getSiteManager(rootFolder) - >>> dashboardPortletManager = PortletManager() - >>> directlyProvides(dashboardPortletManager, IPlacelessPortletManager) + >>> sm.registerUtility(component=dashboardManager, + ... provided=IColumnManager, + ... name='plone.dashboard') + + >>> from plone.portlets.interfaces import IPlacelessPortletManager - >>> sm.registerUtility(component=dashboardPortletManager, + >>> dashboard1 = PortletManager() + >>> dashboard2 = PortletManager() + >>> directlyProvides(dashboard1, IPlacelessPortletManager) + >>> directlyProvides(dashboard2, IPlacelessPortletManager) + >>> sm.registerUtility(component=dashboard1, ... provided=IPortletManager, - ... name='columns.dashboard') + ... name='columns.dashboard1') + >>> sm.registerUtility(component=dashboard2, + ... provided=IPortletManager, + ... name='columns.dashboard2') + +We setup a ColumnManager to which we add the to PortletManagers + + >>> db_manager = getUtility(IColumnManager, name='plone.dashboard') + >>> db_manager.append('columns.dashboard1') + >>> db_manager.append('columns.dashboard2') + + >>> db_manager.left('columns.dashboard1') + + >>> db_manager.right('columns.dashboard1') + 'columns.dashboard2' + + >>> db_manager.left('columns.dashboard2') + 'columns.dashboard1' + + >>> db_manager.right('columns.dashboard2') + >>> dashboardFileName = os.path.join(tempDir, 'dashboard.pt') >>> open(dashboardFileName, 'w').write(""" ... <html> ... <body> - ... <div class="dashboard"> - ... <tal:block replace="structure provider:columns.dashboard" /> + ... <div class="dashboard1"> + ... <tal:block replace="structure provider:columns.dashboard1" /> + ... </div> + ... <div class="dashboard2"> + ... <tal:block replace="structure provider:columns.dashboard2" /> ... </div> ... </body> ... </html> @@ -909,45 +947,54 @@ >>> print view().strip() <html> <body> - <div class="dashboard"> + <div class="dashboard1"> + </div> + <div class="dashboard2"> </div> </body> </html> Let's register some portlets for the dashboard. - >>> dashboard = getUtility(IPortletManager, name='columns.dashboard') - - >>> dashboard[USER_CATEGORY] = PortletCategoryMapping() - >>> dashboard[USER_CATEGORY][Anonymous.id] = PortletAssignmentMapping() - >>> dashboard[USER_CATEGORY][user1.id] = PortletAssignmentMapping() - - >>> dashboard[GROUP_CATEGORY] = PortletCategoryMapping() - >>> dashboard[GROUP_CATEGORY][group1.id] = PortletAssignmentMapping() - >>> dashboard[GROUP_CATEGORY][group2.id] = PortletAssignmentMapping() - - >>> saveAssignment(dashboard[USER_CATEGORY][user1.id], userPortlet) - >>> saveAssignment(dashboard[GROUP_CATEGORY][group1.id], groupPortlet1) - >>> saveAssignment(dashboard[GROUP_CATEGORY][group2.id], groupPortlet2) + >>> dashboard1 = getUtility(IPortletManager, name='columns.dashboard1') + >>> dashboard2 = getUtility(IPortletManager, name='columns.dashboard2') + >>> + + >>> dashboard1[USER_CATEGORY] = PortletCategoryMapping() + >>> dashboard1[USER_CATEGORY][Anonymous.id] = PortletAssignmentMapping() + >>> dashboard1[USER_CATEGORY][user1.id] = PortletAssignmentMapping() + + >>> dashboard2[GROUP_CATEGORY] = PortletCategoryMapping() + >>> dashboard2[GROUP_CATEGORY][group1.id] = PortletAssignmentMapping() + >>> dashboard2[GROUP_CATEGORY][group2.id] = PortletAssignmentMapping() + + >>> saveAssignment(dashboard1[USER_CATEGORY][user1.id], userPortlet) + >>> saveAssignment(dashboard2[GROUP_CATEGORY][group1.id], groupPortlet1) + >>> saveAssignment(dashboard2[GROUP_CATEGORY][group2.id], groupPortlet2) When we render this, contextual portlets are ignored. Blacklistings also do not apply. - >>> dashboardAtChild1Manager = getMultiAdapter((child1, dashboard), ILocalPortletAssignmentManager) + >>> dashboardAtChild1Manager = getMultiAdapter((child1, dashboard1), ILocalPortletAssignmentManager) >>> dashboardAtChild1Manager.setBlacklistStatus(USER_CATEGORY, True) - >>> dashboardAtChild1 = getMultiAdapter((child1, dashboard), IPortletAssignmentMapping) + >>> dashboardAtChild1 = getMultiAdapter((child1, dashboard1), IPortletAssignmentMapping) >>> saveAssignment(dashboardAtChild1, DummyPortlet('dummy for dashboard in context')) + + +# >>> db_manager.move_portlet_to_column('Assignment-1', 'columns.dashboard2') +# >>> TODO actually check if it was moved >>> view = getMultiAdapter((child1, TestRequest()), name='dashboard.html') >>> print view().strip() <html> <body> - <div class="dashboard"> + <div class="dashboard1"> <div>Dummy for user1</div> + </div> + <div class="dashboard2"> <div>Dummy for group1</div> <div>Dummy for group2</div> </div> </body> </html> - Added: plone.portlets/branches/zest-dashboard-dnd/plone/portlets/columns.py ============================================================================== --- (empty file) +++ plone.portlets/branches/zest-dashboard-dnd/plone/portlets/columns.py Sun Jan 20 20:09:21 2008 @@ -0,0 +1,44 @@ +from persistent.list import PersistentList +from persistent import Persistent +from zope.component import queryUtility +from plone.portlets.interfaces import IPortletManager + + +class ColumnManager(Persistent): + + def __init__(self): + self.columns = PersistentList() + + def insert(self, name): + self.columns.insert(0, name) + + def append(self, name): + self.columns.append(name) + + def left(self, name): + if name == self.columns[0]: + return None + else: + current = self.columns.index(name) + return self.columns[current-1] + + def right(self, name): + if name == self.columns[-1]: + return None + else: + current = self.columns.index(name) + return self.columns[current+1] + + def move_portlet_to_column(self, portlet, column): + # 1. get the portletManager and copy the portlet + # 2. get the column and append the portlet + # 3. remove the portlet from the old portletManager + pass + + def get_column_name_of(self, name): + for column_name in self.columns: + column = queryUtility(IPortletManager, column_name) + # if name in column.getUserPortletAssignment() + + def __iter__(self): + return self.columns.__iter__() Modified: plone.portlets/branches/zest-dashboard-dnd/plone/portlets/interfaces.py ============================================================================== --- plone.portlets/branches/zest-dashboard-dnd/plone/portlets/interfaces.py (original) +++ plone.portlets/branches/zest-dashboard-dnd/plone/portlets/interfaces.py Sun Jan 20 20:09:21 2008 @@ -8,10 +8,10 @@ from zope.app.container.interfaces import IOrderedContainer from zope.app.container.interfaces import IContainerNamesContainer -from zope.app.container.constraints import containers, contains +from zope.app.container.constraints import contains from zope.contentprovider.interfaces import IContentProvider -# Context - the application layer must provide these +# Context - the application layer must provide these class ILocalPortletAssignable(IAttributeAnnotatable): """Marker interface for content objects that want to have local portlet @@ -20,69 +20,69 @@ class IPortletContext(Interface): """A context in which portlets may be rendered. - + No default implementation exists for this interface - it must be provided by the application in order to tell the portlets infrastructure how to render portlets. """ - + uid = schema.TextLine(title=u"A unique id or path for this specific context", required=True) - + def getParent(): """Get the portlet parent of the current context. - + This is used to aggregate portlets by walking up the content hierarchy. - + This should be adaptable to IPortletContext. If there is no portlet parent (e.g. this is the site root), return None. """ - + def globalPortletCategories(placeless=False): """Get global portlet key-value pairs, in order. - + When rendered, a portlet manger (column) will be filled first by contextual portlets (if the context and/or its parents provide ILocalPortletAssignable), and then by global portlets. Global portlet assignments may include portlets per user, per group, or per content type. - + This function should return a tuple of tuples where each inner tuple contains a category such as 'user' or 'group' and the key to use in - this category. - - For example, if the current content object is a 'Document', the current + this category. + + For example, if the current content object is a 'Document', the current user is 'fred' and he is a member of 'group1' and 'group2', this may be: - + (('content_type', 'Documment'), ('user', 'fred',), ('group', 'group1',), ('group', 'group2',),) - + In this case, all contextual portlets may be rendered first, followed - by all global portlets in the content_type category assigned to + by all global portlets in the content_type category assigned to 'Document', followed by user portlets for 'fred' and group portlets for 'group1' and then 'group2'. - - If ``placeless`` is True, the categories should only include those + + If ``placeless`` is True, the categories should only include those which are independent of the specific location. In this case, that may mean that the 'content_type' category is excluded. """ - + # Utility interface for registrations of available portlets class IPortletType(Interface): """A registration for a portlet type. - + Each new type of portlet should register a utility with a unique name providing IPortletType, so that UI can find them. """ - + title = schema.TextLine( title = u'Title', required = True) - + description = schema.Text( title = u'Description', required = False) @@ -91,23 +91,23 @@ title = u'Add view', description = u'The name of the add view for assignments for this portlet type', required = True) - + for_ = Attribute('An interface a portlet manager must have to allow this type of portlet. ' \ 'May be None if there are no restrictions.') - + # Generic marker interface - a portlet may reference one of these class IPortletDataProvider(Interface): """A marker interface for objects providing portlet data. - - This can be used as a marker by implementations requiring a regular content + + This can be used as a marker by implementations requiring a regular content object to be able to be "switched on" as a portlet. Alternatively, a more specific sub-interface can provide the necessary information to render the portlet. - - An adapter should exist from the specific type of IPortletDataProvider to + + An adapter should exist from the specific type of IPortletDataProvider to an appropriate IPortletViewlet to render it. - + The data provider will also be referenced in an IPortletAssignment so that it can be retrieved on demand. """ @@ -115,63 +115,63 @@ # Portlet assignment - new types of portlets may need one of these class IPortletAssignment(IContained): - """Assignment of a portlet to a given portlet manager relative to a + """Assignment of a portlet to a given portlet manager relative to a context, user or group. - + Implementations of this interface will typically be persistent, stored in an IPortletStorage. - + The 'data' attribute may be implemented as a property that retrieves the data object on-demand. - + Portlet assignments are contained in and will have their __name__ attribute - managed by an IPortletContextMapping, which in turn are stored inside + managed by an IPortletContextMapping, which in turn are stored inside IPortletStorages. """ - + title = schema.Bool(title=u'Title', description=u'The title of this assignment as displayed to the user', required=True) - + available = schema.Bool(title=u'Available', description=u'Whether or not this portlet should be rendered', required=True, readonly=True) - + data = Attribute(u'Portlet data object') - + # A content provider capable of rendering portlets - each type of portlet will # need one of these - + class IPortletRenderer(IContentProvider): """A special implementation of a content provider which is managed by an IPortletManager. - - Any object providing IPortletDataProvider should be adaptable to + + Any object providing IPortletDataProvider should be adaptable to IPortletRenderer in order to be renderable as a portlet. (In fact, the return value of IPortletAssignment.data needs to have such an adapter, regardless of whether it actually implements IPortletDataProvider) """ - + available = schema.Bool(title=u'Available', description=u'Whether or not this portlet shuld be rendered', required=True, readonly=True) - + # Discovery of portlets class IPortletRetriever(Interface): """A component capable of discovering portlets assigned to it for rendering. - + Typically, a content object and an IPortletManager will be multi- adapted to IPortletRetriever. """ def getPortlets(): """Return a list of IPortletAssignment's to be rendered. - + Returns a list of dicts with keys 'assignment', containing the actual - assignment object; 'category', containing the category the + assignment object; 'category', containing the category the assignment came from; 'key', being the key within this category; and 'name' being the name of the assignment. """ @@ -180,18 +180,18 @@ class IPortletStorage(IContainer): """A component for storing global (site-wide) portlet assignments. - - This manages one IPortletCategoryMapping for each category of portlet, + + This manages one IPortletCategoryMapping for each category of portlet, e.g. 'user' or 'group' (the exact keys are up to the application layer). - + Some common keys are found in plone.portlets.constants. """ - contains('plone.portlets.interfaces.IPortletCategoryMapping') + contains('plone.portlets.interfaces.IPortletCategoryMapping') class IPortletCategoryMapping(IContainer, IContained): """A mapping of the portlets assigned to a particular categories under various keys. - + This manages one IPortletAssignmentMapping for each key. For example, if this is the 'user' category, the keys could be user ids, each of which would be given a particular IPortletAssignmentMapping. @@ -200,81 +200,114 @@ class IPortletAssignmentMapping(IOrderedContainer, IContainerNamesContainer, IContained): """A storage for portlet assignments. - - An IPortletCategoryMapping manages one of these for each category of + + An IPortletCategoryMapping manages one of these for each category of context. It may also be stored in an annotation on an object to manage portlets assigned to that object. In this case, a multi-adapter from ILocalPortletAssignable and IPortletManager will be able to obtain the appropriate container. """ contains('plone.portlets.interfaces.IPortletAssignment') - + __manager__ = schema.TextLine(title=u"Name of the portlet manager this mapping belongs to") __category__ = schema.TextLine(title=u'Name of the category this mapping belongs to') - + class ILocalPortletAssignmentManager(Interface): """A component that can manage the display of locally assigned portlets. - + An ILocalPortletAssignable may be multi-adapted along with an IPortletManager to this interface, to manage how portlets will be displayed relative to this context. """ - + def setBlacklistStatus(category, status): """Manage the blacklisting status of the given category. - + If status is None, the blacklist status will be obtained from a parent, - defaulting to False. If status is False, the given portlet category + defaulting to False. If status is False, the given portlet category will always be eligible for display. If status is True, the given portlet category will always be blocked. - + Thus, call setBlacklistStatus('user', True) to always black out 'user' - portlets in this context, or setBlacklistStatus('user', False) to + portlets in this context, or setBlacklistStatus('user', False) to override any blacklisting done by a parent object. Calling setBlacklistStatus('user', None) will cause the status to be acquired from the parent instead (defaulting to no blacklisting). """ - + def getBlacklistStatus(category): """Get the blacklisting status of the given category. - + Note that this only applies to the current context - the status is not inherited, and will default to None if not set. """ class IPortletManager(IPortletStorage, IContained): """A manager for portlets. - + Typically, objects providing this interface will be persisted and used - to manage portlet assignments. + to manage portlet assignments. """ def getAddablePortletTypes(): """Get all addable portlet types. - + This is achieved by looking up utilities providing IPortletType and returning those which either have no for_ attribute (globally addable portlets) or those which specify an interface available on this portlet manager instance. """ - + def __call__(context, request, view): """Act as an adapter factory. - - When called, should return an IPortletManagerRenderer for rendering + + When called, should return an IPortletManagerRenderer for rendering this portlet manager and its portlets. - + The IPortletManager instance will be registered as a site-local adapter factory that the component architecture will use when it looks up adapters in the handler for a TAL provider: expression. - + See zope.contentprovider for more. """ +class IColumnManager(Interface): + """ A ColumnManager is a utility that stores an ordered list of + PortletManagers and is responsible for moving portlet Assigments from + one PortletManager to another. + """ + + def insert(name): + """ Insert column at the first position + """ + + def append(name): + """ Add a new column at the end of the list + """ + + def left(name): + """ Return the name of the PortletManager left of the given + PortletManager. Returns None if at the end of the list. + """ + + def right(name): + """ Return the name of the PortletManager right of the given + PortletManager. Returns None if at the beginning of the list. + """ + + def move_portlet_to_column(name, column): + """ Move a given portlet to a target column + """ + + def get_column_name_of(name): + """ Return the name of the ColumnManager in which the porlet is + assigned + """ + class IPlacelessPortletManager(IPortletManager): """A marker interface for managers for placeless portlets. - + A placeless portlet manager is one which does not examine the context or the context's parent. This is achieved by way of a different adapter to IPortletRetriever. @@ -283,13 +316,13 @@ class IPortletManagerRenderer(IContentProvider): """A content provider for rendering a portlet manager. """ - + template = Attribute( """A page template object to render the manager with. If given, this will be passed an option 'portlets' that is a list of the IPortletRenderer objects to render. - + If not set, the renderers will simply be called one by one, and their output will be concatenated, separated by newlines. """) @@ -304,10 +337,10 @@ the list of portlets passed in. The list contains dicts as returned by IPortletRetriever.getPortlets(). """ - + def portletsToShow(): """Get a list of portlets that will be shown. - + Returns a list of dicts with keys corresponding to that returned by IPortletRetriever.getPortlets(), with the additional key 'renderer' containing the appropriate IPortletRenderer. Modified: plone.portlets/branches/zest-dashboard-dnd/plone/portlets/tests.py ============================================================================== --- plone.portlets/branches/zest-dashboard-dnd/plone/portlets/tests.py (original) +++ plone.portlets/branches/zest-dashboard-dnd/plone/portlets/tests.py Sun Jan 20 20:09:21 2008 @@ -7,8 +7,11 @@ import zope.app.security import zope.app.component import zope.app.pagetemplate +try: + import zope.event as event +except ImportError: + import zope.app.event as event -import zope.app.event import zope.app.container import zope.contentprovider @@ -34,7 +37,7 @@ pass # In Zope 2.10 they are in zope.app.event try: - XMLConfig('configure.zcml', zope.app.event)() + XMLConfig('configure.zcml', event)() except IOError: pass |