Back to index

plone3  3.1.7
content_replacer.py
Go to the documentation of this file.
00001 # -*- coding: UTF-8 -*-
00002 
00003 from urlparse import urlsplit
00004 
00005 from kss.core import kssaction, KSSExplicitError
00006 from kss.core.BeautifulSoup import BeautifulSoup
00007 
00008 from plone.app.layout.globals.interfaces import IViewView
00009 from plone.locking.interfaces import ILockable
00010 
00011 from zope.interface import alsoProvides
00012 from zope.interface import implements
00013 from zope.component import getMultiAdapter
00014 
00015 from Acquisition import aq_inner
00016 from Acquisition import aq_parent
00017 from Acquisition import Implicit
00018 from Products.CMFCore.utils import getToolByName
00019 from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
00020 
00021 from interfaces import IPloneKSSView
00022 from plonekssview import PloneKSSView
00023 
00024 
00025 class Acquirer(Implicit):
00026     # XXX the next should be best to avoid - but I don't know how!
00027     __allow_access_to_unprotected_subobjects__ = 1
00028     main_template = ZopeTwoPageTemplateFile('browser/main_template_standalone.pt')
00029 
00030 def acquirerFactory(context):
00031     return context.aq_chain[0].__of__(Acquirer().__of__(aq_parent(context)))
00032 
00033 def getCurrentContext(context):
00034     """ Check if context is default page in folder and/or portal
00035     """
00036     # check if context is default page
00037     context = aq_inner(context)
00038     context_state = context.restrictedTraverse('@@plone_context_state')
00039     portal = getToolByName(context, 'portal_url').getPortalObject()
00040     if context_state.is_default_page() and context != portal:
00041         context = aq_parent(context)
00042     return context
00043 
00044 
00045 class ContentView(Implicit, PloneKSSView):
00046 
00047     implements(IPloneKSSView)
00048     
00049     # --
00050     # Replacing content region
00051     # --
00052 
00053     # Override main template in this context
00054     main_template2 = ZopeTwoPageTemplateFile('browser/main_template_standalone.pt')
00055 
00056     #@staticmethod
00057     def _filter_action(actions, id, found=None):
00058         if found is not None:
00059             return found
00060         for action in actions:
00061             if action['id'] == id:
00062                 return action
00063     _filter_action = staticmethod(_filter_action)    # for zope 2.8 / python 2.3
00064 
00065     @kssaction
00066     def replaceContentRegion(self, url, tabid=''):
00067         '''Replace content region by tab id
00068 
00069         Usage::
00070             ul.contentViews li a:click {
00071             evt-click-preventdefault: True;
00072             action-server: replaceContentRegion;
00073             replaceContentRegion-tabid: nodeAttr(id, true);
00074             replaceContentRegion-url: nodeAttr(href);
00075             }
00076 
00077         REMARK:
00078 
00079         We use the acquisition context hack to replace the main template
00080         with one that only renders the content region. This means that if
00081         the target template reuses main_template we win. Otherwise we loose
00082         and we get a full page of which we have to take out the required
00083         part with BeautifulSoup.
00084 
00085         Warning ("Do you want to...") when we leave the page is not implemented.
00086 
00087         '''
00088         # REMARK on error handling: 
00089         # If KSSExplicitError is raised, the control will be passed
00090         # to the error handler defined on the client. I.e. for this rule,
00091         # the static plone-followLink should be activated. This means that
00092         # if this method decides it cannot handle the situation, it
00093         # raises this exception and we fallback to the non-AJAX behaviour.
00094         #
00095         # XXX The next checks could be left out - but we won't be able to change the tabs.
00096         # This could be solved with not using the tabs or doing server side quirks.
00097         # This affect management screens, for example, that are not real actions.
00098         # and unlock XXX
00099         context = aq_inner(self.context)
00100         lock = getMultiAdapter((context,self.request), name='plone_lock_operations')
00101         lock.safe_unlock()
00102 
00103         if not tabid or tabid == 'content':
00104             raise KSSExplicitError, 'No tabid on the tab'
00105         if not tabid.startswith('contentview-'):
00106             raise RuntimeError, 'Not a valid contentview id "%s"' % tabid
00107         # Split the url into it's components
00108         (proto, host, path, query, anchor) = urlsplit(url)
00109         # if the url doesn't use http(s) or has a query string or anchor
00110         # specification, don't bother
00111         if query or anchor or proto not in ('http', 'https'):
00112             raise KSSExplicitError, 'Unhandled protocol on the tab'
00113         # make the wrapping for the context, to overwrite main_template
00114         # note we have to use aq_chain[0] *not* aq_base.
00115         # XXX however just context would be good too? Hmmm
00116         wrapping = acquirerFactory(context)
00117         # Figure out the template to render.
00118         # We need the physical path which we can obtain from the url
00119         path = list(self.request.physicalPathFromURL(url))
00120         obj_path = list(context.getPhysicalPath())
00121         if path == obj_path:
00122             # target is the default view of the method.
00123             # url is like: ['http:', '', 'localhost:9777', 'kukitportlets', 'prefs_users_overview']
00124             # physical path is like: ('', 'kukitportlets')
00125             # We lookup the default view for the object, which may be
00126             # another object, if so we give up, otherwise we use the
00127             # appropriate template
00128             utils = getToolByName(context, 'plone_utils')
00129             if utils.getDefaultPage(context) is not None:
00130                 raise KSSExplicitError, 'no default page on the tab'
00131             viewobj, viewpath = utils.browserDefault(context)
00132             if len(viewpath) == 1:
00133                 viewpath = viewpath[0]
00134             template = viewobj.restrictedTraverse(viewpath)
00135         else:
00136             # see if it is a method on the same context object...
00137             # url is like: ['http:', '', 'localhost:9777', 'kukitportlets', 'prefs_users_overview']
00138             # physical path is like: ('', 'kukitportlets')
00139             if path[:-1] != obj_path:
00140                 raise KSSExplicitError, 'cannot reload since the tab visits a different context'
00141             method = path[-1]
00142             # Action method may be a method alias: Attempt to resolve to a template.
00143             try:
00144                 method = context.getTypeInfo().queryMethodID(method, default=method)
00145             except AttributeError:
00146                 # Don't raise if we don't have a CMF 1.5 FTI
00147                 pass
00148             template = wrapping.restrictedTraverse(method)
00149         # We render it
00150         content = template()
00151         # Now. We take out the required node from it!
00152         # We need this in any way, as we don't know if the template
00153         # actually used main_template! In that case we would have
00154         # the *whole* html which is wrong.
00155         soup = BeautifulSoup(content)
00156         replace_id = 'region-content'
00157         tag = soup.find('div', id=replace_id)
00158         if tag is None:
00159             raise RuntimeError, 'Result content did not contain <div id="%s">' % replace_id
00160         # now we send it back to the client
00161         result = unicode(tag)
00162         ksscore = self.getCommandSet('core')
00163         ksscore.replaceHTML(ksscore.getHtmlIdSelector(replace_id), result)
00164         # to remove old tab highlight,...
00165         ksscore.setAttribute(ksscore.getCssSelector("ul.contentViews li"), name='class', value='plain');
00166         # ... and put the highlight to the newly selected tab
00167         ksscore.setAttribute(ksscore.getHtmlIdSelector(tabid), name='class', value='selected');
00168         # Update the content menu to show them only in the "view"
00169         if tabid.endswith('view'):
00170             alsoProvides(self, IViewView)
00171         self.getCommandSet('plone').refreshContentMenu()
00172 
00173 
00174 class ContentMenuView(Implicit, PloneKSSView):
00175 
00176     implements(IPloneKSSView, IViewView)
00177     
00178     @kssaction
00179     def cutObject(self):
00180         context = getCurrentContext(self.context)
00181         context.object_cut()
00182         self.getCommandSet('plone').refreshContentMenu()
00183         self.issueAllPortalMessages()
00184         self.cancelRedirect()
00185 
00186     @kssaction
00187     def copyObject(self):
00188         context = getCurrentContext(self.context)
00189         context.object_copy()
00190         self.getCommandSet('plone').refreshContentMenu()
00191         self.issueAllPortalMessages()
00192         self.cancelRedirect()
00193 
00194     @kssaction
00195     def changeViewTemplate(self, url):
00196         '''Replace content region after selecting template from drop-down.
00197         
00198         Usage::
00199             dl#templateMenu dd a:click {
00200             evt-click-preventdefault: True;
00201             action-server: changeViewTemplate;
00202             changeViewTemplate-url: nodeAttr(href);
00203             }
00204 
00205         REMARK:
00206 
00207         Cheat at the moment: we render down the whole page
00208         but take out the required part only
00209         This will be optimized more to replace the main template
00210         for the context of the call
00211 
00212         Warning when we leave the page is not implemented.
00213         '''
00214         templateid = url.split('templateId=')[-1].split('&')[0]
00215         context = getCurrentContext(self.context)
00216         wrapping = acquirerFactory(context)
00217         # XXX I believe selectViewTemplate script will be replaced by an
00218         # adapter or a view in the new implementation of CMFDynamicFTI
00219         context.selectViewTemplate(templateid)
00220         # Figure out the template to render.
00221         template = wrapping.restrictedTraverse(templateid)
00222         # We render it
00223         content = template()
00224         # Now. We take out the required node from it!
00225         # We need this in any way, as we don't know if the template
00226         # actually used main_template! In that case we would have
00227         # the *whole* html which is wrong.
00228 
00229         soup = BeautifulSoup(content)
00230         replace_id = 'region-content'
00231         tag = soup.find('div', id=replace_id)
00232         if tag is None:
00233             raise RuntimeError, 'Result content did not contain <div id="%s">' % replace_id
00234         # now we send it back to the client
00235         result = unicode(tag)
00236         ksscore = self.getCommandSet('core')
00237         ksscore.replaceHTML(ksscore.getHtmlIdSelector(replace_id), result)
00238 
00239         self.getCommandSet('plone').refreshContentMenu()
00240         self.issueAllPortalMessages()
00241         self.cancelRedirect()
00242         # XXX We need to take care of the URL history here,
00243         # For instance if we come from the edit page and change the view we
00244         # stay on the edit URL but with a view page
00245 
00246     @kssaction
00247     def changeWorkflowState(self, url):
00248         context = aq_inner(self.context)
00249         ksscore = self.getCommandSet('core')
00250         zopecommands = self.getCommandSet('zope')
00251         plonecommands = self.getCommandSet('plone')
00252         
00253         locking = ILockable(context, None)
00254         if locking is not None and not locking.can_safely_unlock():
00255             selector = ksscore.getHtmlIdSelector('plone-lock-status')
00256             zopecommands.refreshViewlet(selector, 'plone.abovecontent', 'plone.lockinfo')
00257             plonecommands.refreshContentMenu()
00258             return self.render()
00259 
00260         (proto, host, path, query, anchor) = urlsplit(url)
00261         if not path.endswith('content_status_modify'):
00262             raise KSSExplicitError, 'content_status_modify is not handled'
00263         action = query.split("workflow_action=")[-1].split('&')[0]
00264         context.content_status_modify(action)
00265         selector = ksscore.getCssSelector('.contentViews')
00266         zopecommands.refreshViewlet(selector, 'plone.contentviews', 'plone.contentviews')
00267         plonecommands.refreshContentMenu()
00268         self.issueAllPortalMessages()
00269         self.cancelRedirect()