[Checkins] SVN: CMF/trunk/C - Add FSReSTMethod.

Tres Seaver tseaver at palladion.com
Thu Oct 19 13:35:42 EDT 2006


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



More information about the Checkins mailing list