SVN: CMF/trunk/C - Add FSReSTMethod.

Tres Seaver <[email protected]>
Newsgroups gmane.comp.web.zope.cmf.cvs
Message-ID <20061019173542.5CF1A20317B__32262.6471207061$1161279356$gmane$org@mail.zope.org>
Log message for revision 70816:
   - Add FSReSTMethod.

Changed:
  U   CMF/trunk/CHANGES.txt
  A   CMF/trunk/CMFCore/FSReSTMethod.py
  U   CMF/trunk/CMFCore/FSSTXMethod.py
  U   CMF/trunk/CMFCore/__init__.py
  A   CMF/trunk/CMFCore/tests/fake_skins/fake_skin/testReST.rst
  A   CMF/trunk/CMFCore/tests/fake_skins/fake_skin/testReST.rst.metadata
  A   CMF/trunk/CMFCore/tests/test_FSReSTMethod.py

-=-
Modified: CMF/trunk/CHANGES.txt
===================================================================
--- CMF/trunk/CHANGES.txt	2006-10-19 17:07:40 UTC (rev 70815)
+++ CMF/trunk/CHANGES.txt	2006-10-19 17:35:41 UTC (rev 70816)
@@ -2,9 +2,11 @@
 
   New Features
 
-    - CMFCore.FSSTXMethdo:  Modernized, added tests, made customization
-      possible.
+    - Added CMFCore.FSRestMethod:  ReST equivalent of FSSTXMethod.
 
+    - CMFCore.FSSTXMethod:  Modernized, added tests, made customization
+      possible (now renders via ZPT by default, using 'main_template').
+
     - Portal: Added 'email_charset' property.
 
     - CMFDefault utils: Added 'makeEmail' function.

Added: CMF/trunk/CMFCore/FSReSTMethod.py
===================================================================
--- CMF/trunk/CMFCore/FSReSTMethod.py	2006-10-19 17:07:40 UTC (rev 70815)
+++ CMF/trunk/CMFCore/FSReSTMethod.py	2006-10-19 17:35:41 UTC (rev 70816)
@@ -0,0 +1,209 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+""" FSReSTMethod: Filesystem methodish Structured Text document.
+
+$Id$
+"""
+
+from AccessControl import ClassSecurityInfo
+from docutils.core import publish_parts
+from docutils.writers.html4css1 import Writer
+from Globals import DTMLFile
+from Globals import InitializeClass
+from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
+from Products.ZReST.ZReST import Warnings
+
+from Products.CMFCore.DirectoryView import registerFileExtension
+from Products.CMFCore.DirectoryView import registerMetaType
+from Products.CMFCore.FSObject import FSObject
+from Products.CMFCore.permissions import FTPAccess
+from Products.CMFCore.permissions import View
+from Products.CMFCore.permissions import ViewManagementScreens
+from Products.CMFCore.utils import _dtmldir
+from Products.CMFCore.utils import _checkConditionalGET
+from Products.CMFCore.utils import _setCacheHeaders
+from Products.CMFCore.utils import _ViewEmulator
+
+_DEFAULT_TEMPLATE_ZPT = """\
+<html metal:use-macro="context/main_template/macros/main">
+<body>
+
+<metal:block metal:fill-slot="body"
+><div tal:replace="structure options/cooked">
+COOKED TEXT HERE
+</div>
+</metal:block>
+
+</body>
+</html>
+"""
+
+_CUSTOMIZED_TEMPLATE_ZPT = """\
+<html metal:use-macro="context/main_template/macros/master">
+<body>
+
+<metal:block metal:fill-slot="body"
+><div tal:define="std modules/Products/PythonScripts/standard;
+                  rest nocall:std/restructured_text;"
+      tal:replace="structure python:rest(template.rest)">
+COOKED TEXT HERE
+</div>
+</metal:block>
+
+</body>
+</html>
+"""
+
+class FSReSTMethod(FSObject):
+    """ A chunk of StructuredText, rendered as a skin method of a CMF site.
+    """
+    meta_type = 'Filesystem ReST Method'
+    _owner = None # unowned
+    report_level = 1
+    input_encoding = 'ascii'
+    output_encoding = 'utf8'
+
+    manage_options=({'label' : 'Customize','action' : 'manage_main'},
+                    {'label' : 'View','action' : '',
+                     'help' : ('OFSP' ,'DTML-DocumentOrMethod_View.stx')},
+                   )
+
+    security = ClassSecurityInfo()
+    security.declareObjectProtected(View)
+
+    security.declareProtected(ViewManagementScreens, 'manage_main')
+    manage_main = DTMLFile('custstx', _dtmldir)
+
+    #
+    #   FSObject interface
+    #
+    def _createZODBClone(self):
+        """
+            Create a ZODB (editable) equivalent of this object.
+        """
+        target = ZopePageTemplate(self.getId(), _CUSTOMIZED_TEMPLATE_ZPT)
+        target._setProperty('rest', self.raw, 'text')
+        return target
+
+    def _readFile(self, reparse):
+        """Read the data from the filesystem.
+        """
+        file = open(self._filepath, 'r') # not 'rb', as this is a text file!
+        try:
+            data = file.read()
+        finally:
+            file.close()
+        self.raw = data
+
+        if reparse:
+            self.cook()
+
+    #
+    #   "Wesleyan" interface (we need to be "methodish").
+    #
+    class func_code:
+        pass
+
+    func_code = func_code()
+    func_code.co_varnames = ()
+    func_code.co_argcount = 0
+    func_code.__roles__ = ()
+
+    func_defaults__roles__ = ()
+    func_defaults = ()
+
+    index_html = None   # No accidental acquisition
+
+    default_content_type = 'text/html'
+
+    def cook(self):
+        if not hasattr(self, '_v_cooked'):
+            settings = {
+                'halt_level': 6,
+                'report_level' : self.report_level,
+                'input_encoding': self.input_encoding,
+                'output_encoding': self.output_encoding,
+                'initial_header_level' : 1,
+                'stylesheet' : None,
+                'stylesheet_path' : None,
+                'pub.settings.warning_stream' :  Warnings(),
+                'file_insertion_enabled' : 0,
+                'raw_enabled' : 0,
+                }
+
+            parts = publish_parts(self.raw, writer=Writer(),
+                                settings_overrides=settings)
+            self._v_cooked = parts['html_body']
+        return self._v_cooked
+
+    _default_template = ZopePageTemplate('restmethod_view',
+                                         _DEFAULT_TEMPLATE_ZPT, 'text/html')
+
+    def __call__( self, REQUEST={}, RESPONSE=None, **kw ):
+        """ Return our rendered StructuredText.
+        """
+        self._updateFromFS()
+
+        if RESPONSE is not None:
+            RESPONSE.setHeader( 'Content-Type', 'text/html' )
+
+        view = _ViewEmulator(self.getId()).__of__(self)
+        if _checkConditionalGET(view, extra_context={}):
+            return ''
+
+        _setCacheHeaders(view, extra_context={})
+
+        return self._render(REQUEST, RESPONSE, **kw)
+
+    security.declarePrivate('modified')
+    def modified(self):
+        return self.getModTime()
+
+    security.declarePrivate('_render')
+    def _render(self, REQUEST={}, RESPONSE=None, **kw):
+        """ Find the appropriate rendering template and use it to render us.
+        """
+        template = getattr(self, 'restmethod_view', self._default_template)
+
+        if getattr(template, 'isDocTemp', 0):
+            #posargs = (self, REQUEST, RESPONSE)
+            posargs = (self, REQUEST)
+        else:
+            posargs = ()
+
+        kwargs = {'cooked': self.cook()}
+        return template(*posargs, **kwargs)
+
+    security.declareProtected(FTPAccess, 'manage_FTPget')
+    def manage_FTPget(self):
+        """ Fetch our source for delivery via FTP.
+        """
+        return self.raw
+
+    security.declareProtected(ViewManagementScreens, 'PrincipiaSearchSource')
+    def PrincipiaSearchSource(self):
+        """ Fetch our source for indexing in a catalog.
+        """
+        return self.raw
+
+    security.declareProtected(ViewManagementScreens, 'document_src')
+    def document_src( self ):
+        """ Fetch our source for rendering in the ZMI.
+        """
+        return self.raw
+
+InitializeClass(FSReSTMethod)
+
+registerFileExtension('rst', FSReSTMethod)
+registerMetaType('ReST Method', FSReSTMethod)


Property changes on: CMF/trunk/CMFCore/FSReSTMethod.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Modified: CMF/trunk/CMFCore/FSSTXMethod.py
===================================================================
--- CMF/trunk/CMFCore/FSSTXMethod.py	2006-10-19 17:07:40 UTC (rev 70815)
+++ CMF/trunk/CMFCore/FSSTXMethod.py	2006-10-19 17:35:41 UTC (rev 70816)
@@ -49,20 +49,6 @@
 <dtml-var standard_html_footer>"""
 
 _DEFAULT_TEMPLATE_ZPT = """\
-<html metal:use-macro="context/main_template/macros/main">
-<body>
-
-<metal:block metal:fill-slot="body"
-><div tal:replace="structure options/cooked">
-COOKED TEXT HERE
-</div>
-</metal:block>
-
-</body>
-</html>
-"""
-
-_DEFAULT_TEMPLATE_ZPT = """\
 <html metal:use-macro="context/main_template/macros/master">
 <body>
 

Modified: CMF/trunk/CMFCore/__init__.py
===================================================================
--- CMF/trunk/CMFCore/__init__.py	2006-10-19 17:07:40 UTC (rev 70815)
+++ CMF/trunk/CMFCore/__init__.py	2006-10-19 17:35:41 UTC (rev 70816)
@@ -35,6 +35,7 @@
 
 
 # Make sure security is initialized
+import FSReSTMethod
 import FSSTXMethod
 import PortalContent
 import PortalObject

Added: CMF/trunk/CMFCore/tests/fake_skins/fake_skin/testReST.rst
===================================================================
--- CMF/trunk/CMFCore/tests/fake_skins/fake_skin/testReST.rst	2006-10-19 17:07:40 UTC (rev 70815)
+++ CMF/trunk/CMFCore/tests/fake_skins/fake_skin/testReST.rst	2006-10-19 17:35:41 UTC (rev 70816)
@@ -0,0 +1,9 @@
+Title Goes Here
+===============
+
+Subhead Here
+------------
+
+And this is a paragraph,
+broken across lines.
+

Added: CMF/trunk/CMFCore/tests/fake_skins/fake_skin/testReST.rst.metadata
===================================================================
--- CMF/trunk/CMFCore/tests/fake_skins/fake_skin/testReST.rst.metadata	2006-10-19 17:07:40 UTC (rev 70815)
+++ CMF/trunk/CMFCore/tests/fake_skins/fake_skin/testReST.rst.metadata	2006-10-19 17:35:41 UTC (rev 70816)
@@ -0,0 +1,2 @@
+[default]
+title=ReST Method

Added: CMF/trunk/CMFCore/tests/test_FSReSTMethod.py
===================================================================
--- CMF/trunk/CMFCore/tests/test_FSReSTMethod.py	2006-10-19 17:07:40 UTC (rev 70815)
+++ CMF/trunk/CMFCore/tests/test_FSReSTMethod.py	2006-10-19 17:35:41 UTC (rev 70816)
@@ -0,0 +1,216 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+""" Unit tests for FSReSTMethod module.
+
+$Id$
+"""
+import unittest
+import os
+import re
+
+from Products.CMFCore.tests.base.testcase import FSDVTest
+from Products.CMFCore.tests.base.testcase import RequestTest
+from Products.CMFCore.tests.base.testcase import SecurityTest
+
+class FSReSTMaker(FSDVTest):
+
+    def setUp(self):
+        from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
+        FSDVTest.setUp(self)
+        main = ZopePageTemplate('main_template', _TEST_MAIN_TEMPLATE)
+        self.root._setOb('main_template', main)
+
+    def _makeOne( self, id, filename ):
+        from Products.CMFCore.FSMetadata import FSMetadata
+        from Products.CMFCore.FSReSTMethod import FSReSTMethod
+        path = os.path.join(self.skin_path_name, filename)
+        metadata = FSMetadata(path)
+        metadata.read()
+        return FSReSTMethod( id, path, properties=metadata.getProperties() )
+
+_EXPECTED_HTML = """\
+<html>
+<body>
+
+<div class="document" id="title-goes-here">
+<h1 class="title">Title Goes Here</h1>
+<h2 class="subtitle" id="subhead-here">Subhead Here</h2>
+<p>And this is a paragraph,
+    broken across lines.</p>
+
+</div>
+
+</body>
+</html>
+"""
+
+_TEST_MAIN_TEMPLATE = """\
+<html metal:define-macro="main">
+<body>
+
+<metal:block define-slot="body">
+BODY GOES HERE
+</metal:block>
+</body>
+</html>
+"""
+
+WS = re.compile(r'\s+')
+
+def _normalize_whitespace(text):
+    return ' '.join(WS.split(text))
+
+
+class FSReSTMethodTests(RequestTest, FSReSTMaker):
+
+    def setUp(self):
+        RequestTest.setUp(self)
+        FSReSTMaker.setUp(self)
+
+    def tearDown(self):
+        FSReSTMaker.tearDown(self)
+        RequestTest.tearDown(self)
+
+    def test___call__( self ):
+        script = self._makeOne( 'testReST', 'testReST.rst' )
+        script = script.__of__(self.app)
+        self.assertEqual(_normalize_whitespace(script(self.REQUEST)),
+                         _normalize_whitespace(_EXPECTED_HTML))
+
+    def test_caching( self ):
+        #   Test HTTP caching headers.
+        from Products.CMFCore.tests.base.dummy import DummyCachingManager
+        self.root.caching_policy_manager = DummyCachingManager()
+        original_len = len( self.RESPONSE.headers )
+        script = self._makeOne('testReST', 'testReST.rst')
+        script = script.__of__(self.root)
+        script(self.REQUEST, self.RESPONSE)
+        self.failUnless( len( self.RESPONSE.headers ) >= original_len + 2 )
+        self.failUnless( 'foo' in self.RESPONSE.headers.keys() )
+        self.failUnless( 'bar' in self.RESPONSE.headers.keys() )
+
+    def test_ownership( self ):
+        script = self._makeOne( 'testReST', 'testReST.rst' )
+        script = script.__of__(self.root)
+        # FSReSTMethod has no owner
+        owner_tuple = script.getOwnerTuple()
+        self.assertEqual(owner_tuple, None)
+
+        # and ownership is not acquired [CMF/450]
+        self.root._owner= ('/foobar', 'baz')
+        owner_tuple = script.getOwnerTuple()
+        self.assertEqual(owner_tuple, None)
+
+    def test_304_response_from_cpm( self ):
+        # test that we get a 304 response from the cpm via this template
+        from DateTime import DateTime
+        from webdav.common import rfc1123_date
+        from Products.CMFCore.tests.base.dummy \
+            import DummyCachingManagerWithPolicy
+        from Products.CMFCore.tests.base.dummy import DummyContent
+
+        mod_time = DateTime()
+        self.root.caching_policy_manager = DummyCachingManagerWithPolicy()
+        script = self._makeOne('testReST', 'testReST.rst')
+        script = script.__of__(self.root)
+        self.REQUEST.environ[ 'IF_MODIFIED_SINCE'
+                            ] = '%s;' % rfc1123_date( mod_time+3600 )
+        data = script(self.REQUEST, self.RESPONSE)
+
+        self.assertEqual( data, '' )
+        self.assertEqual( self.RESPONSE.getStatus(), 304 )
+
+ADD_ZPT = 'Add page templates'
+ZPT_META_TYPES = ( { 'name'        : 'Page Template'
+                   , 'action'      : 'manage_addPageTemplate'
+                   , 'permission'  : ADD_ZPT
+                   }
+                 ,
+                 )
+
+class FSReSTMethodCustomizationTests( SecurityTest, FSReSTMaker ):
+
+    def setUp( self ):
+        from OFS.Folder import Folder
+        SecurityTest.setUp( self )
+        FSReSTMaker.setUp(self)
+
+        self.root._setObject( 'portal_skins', Folder( 'portal_skins' ) )
+        self.skins = self.root.portal_skins
+
+        self.skins._setObject( 'custom', Folder( 'custom' ) )
+        self.custom = self.skins.custom
+
+        self.skins._setObject( 'fsdir', Folder( 'fsdir' ) )
+        self.fsdir = self.skins.fsdir
+
+        self.fsdir._setObject( 'testReST'
+                             , self._makeOne( 'testReST', 'testReST.rst' ) )
+
+        self.fsReST = self.fsdir.testReST
+
+    def tearDown( self ):
+        FSReSTMaker.tearDown( self )
+        SecurityTest.tearDown( self )
+
+    def test_customize( self ):
+        from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
+        from Products.CMFCore.FSReSTMethod import _CUSTOMIZED_TEMPLATE_ZPT
+
+        self.custom.all_meta_types = ZPT_META_TYPES
+
+        self.fsReST.manage_doCustomize(folder_path='custom')
+
+        self.assertEqual(len(self.custom.objectIds()), 1)
+        self.failUnless('testReST' in self.custom.objectIds())
+        target = self.custom._getOb('testReST')
+
+        self.failUnless(isinstance(target, ZopePageTemplate))
+
+        propinfo = target.propdict()['rest']
+        self.assertEqual(propinfo['type'], 'text')
+        self.assertEqual(target.rest, self.fsReST.raw)
+
+        self.assertEqual(target.document_src(), _CUSTOMIZED_TEMPLATE_ZPT)
+
+    def test_customize_caching(self):
+        # Test to ensure that cache manager associations survive customizing
+        from Products.StandardCacheManagers import RAMCacheManager
+        cache_id = 'gofast'
+        self.custom.all_meta_types = ZPT_META_TYPES
+        RAMCacheManager.manage_addRAMCacheManager( self.root
+                                                 , cache_id
+                                                 , REQUEST=None
+                                                 )
+        self.fsReST.ZCacheable_setManagerId(cache_id, REQUEST=None)
+
+        self.assertEqual(self.fsReST.ZCacheable_getManagerId(), cache_id)
+
+        self.fsReST.manage_doCustomize(folder_path='custom')
+        custom_pt = self.custom.testReST
+
+        self.assertEqual(custom_pt.ZCacheable_getManagerId(), cache_id)
+
+    def tearDown(self):
+        SecurityTest.tearDown(self)
+        FSReSTMaker.tearDown(self)
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(FSReSTMethodTests),
+        unittest.makeSuite(FSReSTMethodCustomizationTests),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


Property changes on: CMF/trunk/CMFCore/tests/test_FSReSTMethod.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.