[Framework-Team] Re: plip 149 (better markup support) request for merge

Martin Aspeli optilude at gmx.net
Fri Dec 29 00:53:01 UTC 2006


Tom Lazar wrote:

... that plip149 
(http://dev.plone.org/plone/browser/review/plip-149-markup-support) is 
ready for merge.

+1 to merge.

The PLIP touches AT, ATCT, CMFPlone, PortalTransforms and 
MimetypesRegistries. It's obviously very AT specific, but save for 
re-implementing portal_transforms as something very generic and Zope 3 
savvy, I don't think we have any other way, and in practice I doubt 
it'll matter. In any case, the settings in CMFPlone could be re-used for 
another implementation.

Btw: Negative brownie points for accidentally making 
plone.app.controlpanel depend on the AT branch, Tom :-)

I've tested the bundle, and read the diff. I'm pasting the diff below so 
others can read it. Post-merge, I think this needs to happen:

  - the content types that are available when Plone is first installed 
or migrated (!) should be limited to a few, e.g. plain text, HTML, 
Markdown and StructuredText. The list atm is quite intimidating

  - for some reason reStructuredText is in there twice...

  - the control panel should be fixed up (may depend on limi's grand 
designs)

  - it should be possible to disable content types entirely in the 
control panel (atm you can only select the default; I believe this is 
just a control panel UI issue)

  - kupu probably needs a jolt to respect the default settings (e.g. it 
shouldn't load itself for non-HTML types, I *think*, Duncan and Limi may 
have opinions)

But still, this can be fixed up after the merge more easily, so again, 
+1 to merge.

Martin

Diff below, for lazy people:

Index: __init__.py
===================================================================
--- __init__.py	(.../trunk)	(revision 7303)
+++ __init__.py	(.../branches/plip149-markup-support)	(revision 7303)
@@ -8,7 +8,7 @@
  from Products.Archetypes.utils import DisplayList, getPkgInfo

  from AccessControl import ModuleSecurityInfo
-from AccessControl import allow_class
+from AccessControl import allow_class, allow_module
  from Products.CMFCore import permissions
  from Products.CMFCore.DirectoryView import registerDirectory

@@ -16,8 +16,16 @@
  ## security
  ###
  # make log and log_exc public
+allow_module('Products.Archetypes.utils')
+
  ModuleSecurityInfo('Products.Archetypes.debug').declarePublic('log')
  ModuleSecurityInfo('Products.Archetypes.debug').declarePublic('log_exc')
+ModuleSecurityInfo('Products.Archetypes.mimetype_utils').declarePublic('getAllowableContentTypes')
+ModuleSecurityInfo('Products.Archetypes.mimetype_utils').declarePublic('getAllowedContentTypes')
+ModuleSecurityInfo('Products.Archetypes.mimetype_utils').declarePublic('getForbiddenContentTypes')
+ModuleSecurityInfo('Products.Archetypes.mimetype_utils').declarePublic('getDefaultContentType')
+ModuleSecurityInfo('Products.Archetypes.mimetype_utils').declareProtected(permissions.ManagePortal, 
'setForbiddenContentTypes')
+ModuleSecurityInfo('Products.Archetypes.mimetype_utils').declareProtected(permissions.ManagePortal, 
'setDefaultContentType')

  # Plone compatibility in plain CMF. Templates should use IndexIterator 
from
  # Archetypes and not from CMFPlone
Index: Schema/__init__.py
===================================================================
--- Schema/__init__.py	(.../trunk)	(revision 7303)
+++ Schema/__init__.py	(.../branches/plip149-markup-support)	(revision 7303)
@@ -9,6 +9,7 @@
  from Products.Archetypes.interfaces.schema import ISchema, ISchemata, \
       IManagedSchema
  from Products.Archetypes.utils import OrderedDict, mapply, shasattr
+from Products.Archetypes.mimetype_utils import getDefaultContentType, 
getAllowedContentTypes
  from Products.Archetypes.debug import log, warn
  from Products.Archetypes.exceptions import SchemaException
  from Products.Archetypes.exceptions import ReferenceException
@@ -488,7 +489,12 @@
              if shasattr(field, 'default_content_type'):
                  # specify a mimetype if the mutator takes a
                  # mimetype argument
-                kw['mimetype'] = field.default_content_type
+                # if the schema supplies a default, we honour that,
+                # otherwise we use the site property
+                default_content_type = field.default_content_type
+                if default_content_type is None:
+                    default_content_type = getDefaultContentType(instance)
+                kw['mimetype'] = default_content_type
              mapply(mutator, *args, **kw)

      security.declareProtected(permissions.ModifyPortalContent,
Index: Field.py
===================================================================
--- Field.py	(.../trunk)	(revision 7303)
+++ Field.py	(.../branches/plip149-markup-support)	(revision 7303)
@@ -67,6 +67,7 @@
  from Products.Archetypes.utils import mapply
  from Products.Archetypes.utils import shasattr
  from Products.Archetypes.utils import contentDispositionHeader
+from Products.Archetypes.mimetype_utils import getAllowedContentTypes 
as getAllowedContentTypesProperty
  from Products.Archetypes.debug import log
  from Products.Archetypes.debug import log_exc
  from Products.Archetypes.debug import deprecated
@@ -1134,9 +1135,9 @@
          'type' : 'text',
          'default' : '',
          'widget': StringWidget,
-        'default_content_type' : 'text/plain',
+        'default_content_type' : None,
          'default_output_type'  : 'text/plain',
-        'allowable_content_types' : ('text/plain',),
+        'allowable_content_types' : None,
          'primary' : False,
          'content_class': BaseUnit,
          })
@@ -1161,6 +1162,18 @@

      getContentType = ObjectField.getContentType.im_func

+    security.declarePublic('getAllowedContentTypes')
+    def getAllowedContentTypes(self, instance):
+        """ returns the list of allowed content types for this field.
+            If the fields schema doesn't define any, the site's default
+            values are returned.
+        """
+        act_attribute = getattr(self, 'allowable_content_types', None)
+        if act_attribute is None:
+            return getAllowedContentTypesProperty(instance)
+        else:
+            return act_attribute
+
      def _make_file(self, id, title='', file='', instance=None):
          return self.content_class(id, file=file, instance=instance)

Index: ExtensibleMetadata.py
===================================================================
--- ExtensibleMetadata.py	(.../trunk)	(revision 7303)
+++ ExtensibleMetadata.py	(.../branches/plip149-markup-support) 
(revision 7303)
@@ -74,6 +74,8 @@
              default='',
              searchable=1,
              accessor="Description",
+            default_content_type = 'text/plain',
+            allowable_content_types = ('text/plain',),
              widget=TextAreaWidget(
                  label=_(u'label_description', default=u'Description'),
                  description=_(u'help_description',
Index: Widget.py
===================================================================
--- Widget.py	(.../trunk)	(revision 7303)
+++ Widget.py	(.../branches/plip149-markup-support)	(revision 7303)
@@ -344,10 +344,8 @@
          if emptyReturnsMarker and value == '':
              return empty_marker

-        if hasattr(field, 'allowable_content_types') and \
-               field.allowable_content_types:
-            format_field = "%s_text_format" % field.getName()
-            text_format = form.get(format_field, empty_marker)
+        format_field = "%s_text_format" % field.getName()
+        text_format = form.get(format_field, empty_marker)
          kwargs = {}

          if text_format is not empty_marker and text_format:
@@ -536,12 +534,9 @@
          isFile = False
          value = None

-        # text field with formatting
-        if hasattr(field, 'allowable_content_types') and \
-           field.allowable_content_types:
-            # was a mimetype specified
-            format_field = "%s_text_format" % field.getName()
-            text_format = form.get(format_field, empty_marker)
+        # was a mimetype specified
+        format_field = "%s_text_format" % field.getName()
+        text_format = form.get(format_field, empty_marker)

          # or a file?
          fileobj = form.get('%s_file' % field.getName(), empty_marker)
Index: skins/archetypes/widgets/visual.pt
===================================================================
--- skins/archetypes/widgets/visual.pt	(.../trunk)	(revision 7303)
+++ skins/archetypes/widgets/visual.pt 
(.../branches/plip149-markup-support)	(revision 7303)
@@ -30,8 +30,8 @@
        <tal:define define="
                    fieldCType python:here.getContentType(fieldName);
                    contentType python:request.get('%s_text_format' % 
fieldName, fieldCType);
-                  allowableContentTypes python: list(getattr(field, 
'allowable_content_types', ()));
-                  mimetypes python:  allowableContentTypes + 
(fieldCType not in allowableContentTypes and [fieldCType] or []);">
+                  mimetypes python:field.getAllowedContentTypes(here);
+                  ">

  <div class="fieldTextFormat" tal:condition="python: len(mimetypes) 
&gt; 1">
      <label>Text Format</label>
Index: skins/archetypes/widgets/textarea.pt
===================================================================
--- skins/archetypes/widgets/textarea.pt	(.../trunk)	(revision 7303)
+++ skins/archetypes/widgets/textarea.pt 
(.../branches/plip149-markup-support)	(revision 7303)
@@ -81,7 +81,7 @@
        <tal:define
            define="field_text_format string:${fieldName}_text_format;
                    contentType python:request.get(field_text_format, 
here.getContentType(fieldName));
-                  allowable_ct python:getattr(field, 
'allowable_content_types', ());
+                  allowable_ct python:field.getAllowedContentTypes(here);
                    mimetypes python:[t for t in allowable_ct if 
t.startswith('text/')];
                    contenttype python:hasattr(field, 'getContentType') 
and field.getContentType(here) or ''">

Index: tests/test_classgen.py
===================================================================
--- tests/test_classgen.py	(.../trunk)	(revision 7303)
+++ tests/test_classgen.py	(.../branches/plip149-markup-support) 
(revision 7303)
@@ -107,6 +107,9 @@
      def getProperty(self, name, default=None):
          return getattr(self, name, default)

+    def hasProperty(self, name):
+        return hasattr(self, name)
+
  class PortalProperties:
      site_properties = SiteProperties()

Index: tests/test_default_mimetypes.py
===================================================================
--- tests/test_default_mimetypes.py	(.../trunk)	(revision 0)
+++ tests/test_default_mimetypes.py 
(.../branches/plip149-markup-support)	(revision 7303)
@@ -0,0 +1,46 @@
+# -*- coding: UTF-8 -*-
+# test initialisation and setup
+
+import os, sys
+if __name__ == '__main__':
+    execfile(os.path.join(sys.path[0], 'framework.py'))
+
+from Products.CMFCore.utils import getToolByName
+from Testing.ZopeTestCase import FunctionalDocFileSuite, 
FunctionalDocTestSuite
+from Products.Archetypes.tests.atsitetestcase import ATSiteTestCase
+from unittest import TestSuite, makeSuite
+
+class TestDefaultMimeTypes(ATSiteTestCase):
+
+    def test_ATDocumentDefaultType(self):
+        self.loginAsPortalOwner()
+        # we create a new document:
+        self.portal.invokeFactory('Document', id='testdoc', 
title='TestDocument')
+        obj = self.portal.testdoc
+        # its text field should have the site wide default 'text/html'
+        textfield = obj.getField('text')
+        self.assertEqual(textfield.getContentType(obj), 'text/html')
+        # but not the description field:
+        descriptionfield = obj.getField('description')
+        self.assertEqual(descriptionfield.getContentType(obj), 
'text/plain')
+
+        # then we change the sitewide default:
+        from Products.Archetypes.mimetype_utils import 
setDefaultContentType
+        setDefaultContentType(self.portal, "text/x-web-markdown")
+        self.assertEqual(textfield.getContentType(obj), 'text/html')
+        # this should only affect new objects:
+        self.failIf(textfield.getContentType(obj) == 'text/x-web-markdown')
+        self.portal.invokeFactory('Document', id='testdoc2', 
title='TestDocument with new default')
+        second_object = self.portal.testdoc2
+        second_field = second_object.getField('text')
+        self.failUnless(second_field.getContentType(second_object) == 
'text/x-web-markdown')
+
+def test_suite():
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestDefaultMimeTypes))
+    return suite
+
+if  __name__ == '__main__':
+    framework()
+
+
Index: mimetype_utils.py
===================================================================
--- mimetype_utils.py	(.../trunk)	(revision 0)
+++ mimetype_utils.py	(.../branches/plip149-markup-support)	(revision 7303)
@@ -0,0 +1,44 @@
+from Products.CMFCore.utils import getToolByName
+
+#
+# default- and allowable content type handling
+#
+
+def getDefaultContentType(context):
+    portal_properties = getToolByName(context, 'portal_properties', None)
+    site_properties = getattr(portal_properties, 'site_properties', None)
+    return site_properties.getProperty('default_contenttype')
+
+def setDefaultContentType(context, value):
+    portal_properties = getToolByName(context, 'portal_properties', None)
+    site_properties = getattr(portal_properties, 'site_properties', None)
+    site_properties.manage_changeProperties(default_contenttype=value)
+
+def getAllowedContentTypes(context):
+    """ computes the list of allowed content types by subtracting the 
site property blacklist
+        from the list of installed types.
+    """
+    allowable_types = getAllowableContentTypes(context)
+    forbidden_types = getForbiddenContentTypes(context)
+    allowed_types = [type for type in allowable_types if type not in 
forbidden_types]
+    return allowed_types
+
+def getAllowableContentTypes(context):
+    """ retrieves the list of installed content types by querying 
portal transforms. """
+    portal_transforms = getToolByName(context, 'portal_transforms', None)
+    return portal_transforms.listAvailableTextInputs()
+
+def setForbiddenContentTypes(context, forbidden_contenttypes=[]):
+    """ Convenience method for settng the site property 
'forbidden_contenttypes'."""
+    portal_properties = getToolByName(context, 'portal_properties', None)
+    site_properties = getattr(portal_properties, 'site_properties', None)
+ 
site_properties.manage_changeProperties(forbidden_contenttypes=tuple(forbidden_contenttypes))
+
+def getForbiddenContentTypes(context):
+    """ Convenence method for retrevng the site property 
'forbidden_contenttypes'."""
+    portal_properties = getToolByName(context, 'portal_properties', None)
+    site_properties = getattr(portal_properties, 'site_properties', None)
+    if site_properties.hasProperty('forbidden_contenttypes'):
+        return list(site_properties.getProperty('forbidden_contenttypes'))
+    else:
+        return []


Index: tests/test_atdocument.py
===================================================================
--- tests/test_atdocument.py	(.../trunk)	(revision 35150)
+++ tests/test_atdocument.py	(.../branches/plip149-markup-support) 
(revision 35150)
@@ -296,13 +296,13 @@
          self.failUnless(tuple(vocab) == (), 'Value is %s' % 
str(tuple(vocab)))

          self.failUnless(field.primary == 1, 'Value is %s' % field.primary)
-        self.failUnless(field.default_content_type == 'text/html',
+        self.failUnless(field.default_content_type is None,
                          'Value is %s' % field.default_content_type)
          self.failUnless(field.default_output_type == 'text/x-html-safe',
                          'Value is %s' % field.default_output_type)

-        self.failUnless('text/html' in field.allowable_content_types)
-        self.failUnless('text/structured'  in 
field.allowable_content_types)
+        self.failUnless('text/html' in field.getAllowedContentTypes(dummy))
+        self.failUnless('text/structured'  in 
field.getAllowedContentTypes(dummy))

  tests.append(TestATDocumentFields)

Index: tests/test_atnewsitem.py
===================================================================
--- tests/test_atnewsitem.py	(.../trunk)	(revision 35150)
+++ tests/test_atnewsitem.py	(.../branches/plip149-markup-support) 
(revision 35150)
@@ -164,12 +164,12 @@
          self.failUnless(tuple(vocab) == (), 'Value is %s' % 
str(tuple(vocab)))

          self.failUnless(field.primary == 1, 'Value is %s' % field.primary)
-        self.failUnless(field.default_content_type == 'text/html',
+        self.failUnless(field.default_content_type is None,
                          'Value is %s' % field.default_content_type)
          self.failUnless(field.default_output_type == 'text/x-html-safe',
                          'Value is %s' % field.default_output_type)
-        self.failUnless('text/html' in field.allowable_content_types)
-        self.failUnless('text/structured'  in 
field.allowable_content_types)
+        self.failUnless('text/html' in field.getAllowedContentTypes(dummy))
+        self.failUnless('text/structured'  in 
field.getAllowedContentTypes(dummy))

  tests.append(TestATNewsItemFields)

Index: tests/test_atevent.py
===================================================================
--- tests/test_atevent.py	(.../trunk)	(revision 35150)
+++ tests/test_atevent.py	(.../branches/plip149-markup-support) 
(revision 35150)
@@ -638,12 +638,12 @@
          self.failUnless(tuple(vocab) == (), 'Value is %s' % 
str(tuple(vocab)))

          self.failUnless(field.primary == 1, 'Value is %s' % field.primary)
-        self.failUnless(field.default_content_type == 'text/html',
+        self.failUnless(field.default_content_type is None,
                          'Value is %s' % field.default_content_type)
          self.failUnless(field.default_output_type == 'text/x-html-safe',
                          'Value is %s' % field.default_output_type)
-        self.failUnless('text/html' in field.allowable_content_types)
-        self.failUnless('text/structured'  in 
field.allowable_content_types)
+        self.failUnless('text/html' in field.getAllowedContentTypes(dummy))
+        self.failUnless('text/structured'  in 
field.getAllowedContentTypes(dummy))

      def beforeTearDown(self):
          # more
Index: content/newsitem.py
===================================================================
--- content/newsitem.py	(.../trunk)	(revision 35150)
+++ content/newsitem.py	(.../branches/plip149-markup-support)	(revision 
35150)
@@ -65,9 +65,7 @@
          storage = AnnotationStorage(migrate=True),
          validators = ('isTidyHtmlWithCleanup',),
          #validators = ('isTidyHtml',),
-        default_content_type = zconf.ATNewsItem.default_content_type,
          default_output_type = 'text/x-html-safe',
-        allowable_content_types = zconf.ATNewsItem.allowed_content_types,
          widget = RichWidget(
              description = '',
              label = _(u'label_body_text', u'Body Text'),
Index: content/document.py
===================================================================
--- content/document.py	(.../trunk)	(revision 35150)
+++ content/document.py	(.../branches/plip149-markup-support)	(revision 
35150)
@@ -63,9 +63,7 @@
                storage = AnnotationStorage(migrate=True),
                validators = ('isTidyHtmlWithCleanup',),
                #validators = ('isTidyHtml',),
-              default_content_type = zconf.ATDocument.default_content_type,
                default_output_type = 'text/x-html-safe',
-              allowable_content_types = 
zconf.ATDocument.allowed_content_types,
                widget = RichWidget(
                          description = '',
                          label = _(u'label_body_text', default=u'Body 
Text'),
Index: content/event.py
===================================================================
--- content/event.py	(.../trunk)	(revision 35150)
+++ content/event.py	(.../branches/plip149-markup-support)	(revision 35150)
@@ -96,9 +96,7 @@
                primary=True,
                storage = AnnotationStorage(migrate=True),
                validators = ('isTidyHtmlWithCleanup',),
-              default_content_type = zconf.ATEvent.default_content_type,
                default_output_type = 'text/x-html-safe',
-              allowable_content_types = 
zconf.ATEvent.allowed_content_types,
                widget = RichWidget(
                          description = '',
                          label = _(u'label_event_announcement', 
default=u'Event body text'),


Index: __init__.py
===================================================================
--- __init__.py	(.../trunk)	(revision 11764)
+++ __init__.py	(.../branches/plip-149-markup-support-2)	(revision 11764)
@@ -102,9 +102,6 @@
      allow_module('OFS.ObjectManager')
      allow_class(BeforeDeleteException)

-    # Make cgi.escape available TTW
-    ModuleSecurityInfo('cgi').declarePublic('escape')
-
      # Setup migrations
      import migrations
      migrations.executeMigrations()
Index: HISTORY.txt
===================================================================
--- HISTORY.txt	(.../trunk)	(revision 11764)
+++ HISTORY.txt	(.../branches/plip-149-markup-support-2)	(revision 11764)
@@ -27,7 +27,7 @@

      - Merged changes necessary to support PLIP125 Link Integrity, 
mostly found
        in the plone.app.linkintegrity package.
-      [witsch, optilude]
+      [witch, optilude]

      - Removed the setup tab from the migration tool. It wasn't working 
anymore
        and the functionality can be found on the controlpanel tool anyways.
Index: profiles/default/propertiestool.xml
===================================================================
--- profiles/default/propertiestool.xml	(.../trunk)	(revision 11764)
+++ profiles/default/propertiestool.xml 
(.../branches/plip-149-markup-support-2)	(revision 11764)
@@ -126,5 +126,7 @@
     <element value="Topic"/>
    </property>
    <property name="use_folder_contents" type="lines"/>
+  <property name="forbidden_contenttypes" type="lines"></property>
+  <property name="default_contenttype" type="string">text/html</property>
   </object>
  </object>
Index: profiles/default/controlpanel.xml
===================================================================
--- profiles/default/controlpanel.xml	(.../trunk)	(revision 11764)
+++ profiles/default/controlpanel.xml 
(.../branches/plip-149-markup-support-2)	(revision 11764)
@@ -46,6 +46,12 @@
      url_expr="string:${portal_url}/prefs_error_log_form" visible="True">
    <permission>Manage portal</permission>
   </configlet>
+ <configlet title="Types Settings" action_id="TypesSettings"
+    appId="TypesSettings" category="Plone" condition_expr=""
+    url_expr="string:${portal_url}/@@types-controlpanel.html"
+    visible="True">
+  <permission>Manage portal</permission>
+ </configlet>
   <configlet title="Zope Management Interface" action_id="ZMI" appId="ZMI"
      category="Plone" condition_expr=""
      url_expr="string:${portal_url}/manage_main" visible="True">
Index: profiles/default/actionicons.xml
===================================================================
--- profiles/default/actionicons.xml	(.../trunk)	(revision 11764)
+++ profiles/default/actionicons.xml 
(.../branches/plip-149-markup-support-2)	(revision 11764)
@@ -48,6 +48,9 @@
   <action-icon category="controlpanel" action_id="errorLog"
                title="Error Log" priority="0"
                icon_expr="error_log_icon.gif"/>
+ <action-icon category="controlpanel" action_id="TypesSettings"
+              title="Types Settings" priority="0"
+              icon_expr="document_icon.gif"/>
   <action-icon category="controlpanel" action_id="ZMI"
                title="Zope Management Interface" priority="0"
                icon_expr="zope_icon.gif"/>
Index: skins/plone_templates/default_error_message.pt
===================================================================
--- skins/plone_templates/default_error_message.pt	(.../trunk)	(revision 
11764)
+++ skins/plone_templates/default_error_message.pt 
(.../branches/plip-149-markup-support-2)	(revision 11764)
@@ -173,10 +173,7 @@
              </dl>

          </div>
-        <tal:ksserrors
-                tal:define="kss_view kss_view | context/@@kss_view | 
nothing;
-                            _dummy python: kss_view and 
kss_view.attach_error(err_type, err_value);"/>
-
  </div>
+
  </body>
  </html>
Index: skins/plone_kss/plone.kss
===================================================================
--- skins/plone_kss/plone.kss	(.../trunk)	(revision 11764)
+++ skins/plone_kss/plone.kss	(.../branches/plip-149-markup-support-2) 
(revision 11764)
@@ -124,6 +124,7 @@

  /*
  In-place calendar changing
+must have the kssPortletRefresh class for selection.
  */

  a.kssCalendarChange:click {
Index: tests/testMigrations.py
===================================================================
--- tests/testMigrations.py	(.../trunk)	(revision 11764)
+++ tests/testMigrations.py	(.../branches/plip-149-markup-support-2) 
(revision 11764)
@@ -57,6 +57,8 @@
  from Products.CMFPlone.migrations.v3_0.alphas import enableZope3Site
  from Products.CMFPlone.migrations.v3_0.alphas import migrateOldActions
  from Products.CMFPlone.migrations.v3_0.alphas import addNewCSSFiles
+from Products.CMFPlone.migrations.v3_0.alphas import 
addDefaultAndForbiddenContentTypesProperties
+from Products.CMFPlone.migrations.v3_0.alphas import addTypesConfiglet
  from Products.CMFPlone.migrations.v3_0.alphas import 
updateActionsI18NDomain
  from Products.CMFPlone.migrations.v3_0.alphas import updateFTII18NDomain
  from Products.CMFPlone.migrations.v3_0.alphas import convertLegacyPortlets
@@ -1020,6 +1022,8 @@
          self.skins = self.portal.portal_skins
          self.types = self.portal.portal_types
          self.workflow = self.portal.portal_workflow
+        self.properties = self.portal.portal_properties
+        self.cp = self.portal.portal_controlpanel

      def testEnableZope3Site(self):
          # First we remove the site and site manager
@@ -1069,6 +1073,44 @@
          for id in added_ids:
              self.failUnless(id in stylesheet_ids)

+    def testAddDefaultAndForbiddenContentTypesProperties(self):
+        # Should add the forbidden_contenttypes and default_contenttype 
property
+        self.removeSiteProperty('forbidden_contenttypes')
+        self.removeSiteProperty('default_contenttype')
+ 
self.failIf(self.properties.site_properties.hasProperty('forbidden_contenttypes'))
+ 
self.failIf(self.properties.site_properties.hasProperty('default_contenttype'))
+        addDefaultAndForbiddenContentTypesProperties(self.portal, [])
+ 
self.failUnless(self.properties.site_properties.hasProperty('forbidden_contenttypes'))
+ 
self.failUnless(self.properties.site_properties.hasProperty('default_contenttype'))
+
+    def testAddDefaultAndForbiddenContentTypesPropertiesTwice(self):
+        # Should not fail if migrated again
+        self.removeSiteProperty('forbidden_contenttypes')
+        self.removeSiteProperty('default_contenttype')
+ 
self.failIf(self.properties.site_properties.hasProperty('forbidden_contenttypes'))
+ 
self.failIf(self.properties.site_properties.hasProperty('default_contenttype'))
+        addDefaultAndForbiddenContentTypesProperties(self.portal, [])
+        addDefaultAndForbiddenContentTypesProperties(self.portal, [])
+ 
self.failUnless(self.properties.site_properties.hasProperty('forbidden_contenttypes'))
+ 
self.failUnless(self.properties.site_properties.hasProperty('default_contenttype'))
+
+    def testAddTypesConfiglet(self):
+        self.removeActionFromTool('TypesSettings', 
action_provider='portal_controlpanel')
+        addTypesConfiglet(self.portal, [])
+        self.failUnless('TypesSettings' in [action.getId() for action 
in self.cp.listActions()])
+        types = self.cp.getActionObject('Plone/TypesSettings')
+        self.assertEquals(types.action.text,
+                          'string:${portal_url}/@@types-controlpanel.html')
+
+    def testAddTypesConfigletTwice(self):
+        self.removeActionFromTool('TypesSettings', 
action_provider='portal_controlpanel')
+        addTypesConfiglet(self.portal, [])
+        addTypesConfiglet(self.portal, [])
+        self.failUnless('TypesSettings' in [action.getId() for action 
in self.cp.listActions()])
+        types = self.cp.getActionObject('Plone/TypesSettings')
+        self.assertEquals(types.action.text,
+                          'string:${portal_url}/@@types-controlpanel.html')
+
      def testAddFormTabbingJS(self):
          jsreg = self.portal.portal_javascripts
          # unregister first
Index: tests/testContentTypes.py
===================================================================
--- tests/testContentTypes.py	(.../trunk)	(revision 11764)
+++ tests/testContentTypes.py	(.../branches/plip-149-markup-support-2) 
(revision 11764)
@@ -127,7 +127,42 @@
          self.folder.topic.edit(title='Foo')
          self.assertEqual(self.folder.topic.Title(), 'Foo')

+class TestDefaultMimeTypes(PloneTestCase.PloneTestCase):
+    """ This tests the Archetypes mechanism for getting and setting a 
default content type.
+        Since the site property that these methods rely on are part of 
the CMFPlone profile,
+        they are tested here, eventhough the methods themselves reside 
in Archetypes.utils.
+    """

+    def test_getDefaultContentType(self):
+        from Products.Archetypes.mimetype_utils import 
getDefaultContentType
+        self.failUnless(getDefaultContentType(self.portal) == 'text/html')
+
+    def test_setDefaultContentType(self):
+        from Products.Archetypes.mimetype_utils import 
getDefaultContentType, setDefaultContentType
+        setDefaultContentType(self.portal, 'text/html')
+        self.failUnless(getDefaultContentType(self.portal) == 'text/html')
+
+    def test_getForbiddenContentTypes(self):
+        from Products.Archetypes.mimetype_utils import 
getForbiddenContentTypes
+        self.failUnless(getForbiddenContentTypes(self.portal) == [])
+
+    def test_setForbiddenContentTypes(self):
+        from Products.Archetypes.mimetype_utils import 
getForbiddenContentTypes, setForbiddenContentTypes
+        forbidden_types = ['text/plain', 'text/rst']
+        setForbiddenContentTypes(self.portal, forbidden_types)
+        self.failUnless(getForbiddenContentTypes(self.portal) == 
forbidden_types)
+
+    def test_getAllowedContentTypes(self):
+        from Products.Archetypes.mimetype_utils import 
getAllowedContentTypes, setForbiddenContentTypes
+        self.failUnless('text/html' in getAllowedContentTypes(self.portal))
+        forbidden_types = ['text/html', 'text/rst']
+        setForbiddenContentTypes(self.portal, forbidden_types)
+        self.failIf('text/html' in getAllowedContentTypes(self.portal))
+        self.failIf('text/rst' in getAllowedContentTypes(self.portal))
+        self.failUnless('text/plain' in 
getAllowedContentTypes(self.portal))
+
+
+
  class TestContentTypeInformation(PloneTestCase.PloneTestCase):

      def afterSetUp(self):
@@ -147,6 +182,7 @@
      suite = TestSuite()
      suite.addTest(makeSuite(TestATContentTypes))
      suite.addTest(makeSuite(TestContentTypes))
+    suite.addTest(makeSuite(TestDefaultMimeTypes))
      suite.addTest(makeSuite(TestContentTypeInformation))
      return suite

Index: migrations/v3_0/alphas.py
===================================================================
--- migrations/v3_0/alphas.py	(.../trunk)	(revision 11764)
+++ migrations/v3_0/alphas.py	(.../branches/plip-149-markup-support-2) 
(revision 11764)
@@ -6,6 +6,7 @@
  from zope.component.globalregistry import base
  from zope.component.persistentregistry import PersistentComponents

+from Products.CMFCore.permissions import ManagePortal
  from Acquisition import aq_base

  from Products.ATContentTypes.migration.v1_2 import upgradeATCTTool
@@ -43,6 +44,10 @@
      # Add new css files to RR
      addNewCSSFiles(portal, out)

+    # Add new properties for default- and forbidden content types
+    addDefaultAndForbiddenContentTypesProperties(portal, out)
+    addTypesConfiglet(portal, out)
+
      # Actions should gain a i18n_domain now, so their title and 
description are
      # returned as Messages
      updateActionsI18NDomain(portal, out)
@@ -165,6 +170,38 @@
          cssreg.moveResourceAfter('forms.css', 'invisibles.css')
          out.append("Added forms.css to the registry")

+def addDefaultAndForbiddenContentTypesProperties(portal, out):
+    """Adds sitewide config for default and forbidden content types for 
AT textfields."""
+    propTool = getToolByName(portal, 'portal_properties', None)
+    if propTool is not None:
+        propSheet = getattr(aq_base(propTool), 'site_properties', None)
+        if propSheet is not None:
+            if not propSheet.hasProperty('default_contenttype'):
+                propSheet.manage_addProperty('default_contenttype', 
'text/html', 'string')
+            out.append("Added 'default_contenttype' property to 
site_properties.")
+            if not propSheet.hasProperty('forbidden_contenttypes'):
+                propSheet.manage_addProperty('forbidden_contenttypes', 
[], 'lines')
+            out.append("Added 'forbidden_contenttypes' property to 
site_properties.")
+
+def addTypesConfiglet(portal, out):
+    """Add the types configlet."""
+    controlPanel = getToolByName(portal, 'portal_controlpanel', None)
+    if controlPanel is not None:
+        gotTypes = False
+        for configlet in controlPanel.listActions():
+            if configlet.getId() == 'TypesSettings':
+                gotTypes = True
+        if not gotTypes:
+            controlPanel.registerConfiglet(
+                id         = 'TypesSettings',
+                appId      = 'Plone',
+                name       = 'Types Settings',
+                action     = 
'string:${portal_url}/@@types-controlpanel.html',
+                category   = 'Plone',
+                permission = ManagePortal,
+            )
+            out.append("Added Types Settings to the control panel")
+
  def updateActionsI18NDomain(portal, out):
      actions = portal.portal_actions.listActions()
      domainless_actions = [a for a in actions if not a.i18n_domain]


Index: mime_types/mtr_mimetypes.py
===================================================================
--- mime_types/mtr_mimetypes.py	(.../trunk)	(revision 7303)
+++ mime_types/mtr_mimetypes.py	(.../branches/plip149-markup-support-2) 
(revision 7303)
@@ -107,6 +107,23 @@
      extensions = ()
      binary     = 0

+class text_web_markdown(MimeTypeItem):
+
+    __implements__ = MimeTypeItem.__implements__
+    __name__   = "Markdown"
+    mimetypes  = ('text/x-web-markdown',)
+    extensions = ('markdown',)
+    binary     = 0
+
+class text_web_textile(MimeTypeItem):
+
+    __implements__ = MimeTypeItem.__implements__
+    __name__   = "Textile"
+    mimetypes  = ('text/x-web-textile',)
+    extensions = ('textile',)
+    binary     = 0
+
+
  reg_types = [
      text_plain,
      text_pre_plain,
@@ -120,6 +137,8 @@
      application_rtf,
      text_html,
      text_html_safe,
+    text_web_markdown,
+    text_web_textile,
      ]

  def initialize(registry):


Index: transforms/__init__.py
===================================================================
--- transforms/__init__.py	(.../trunk)	(revision 7303)
+++ transforms/__init__.py	(.../branches/plip149-markup-support-2) 
(revision 7303)
@@ -37,6 +37,8 @@
      'lynx_dump',      # lynx -dump
      'python',         # python source files, no dependancies
      'identity',       # identity transform, no dependancies
+    'markdown_to_html', # markdown, depends on 
http://surfnet.dl.sourceforge.net/sourceforge/python-markdown/markdown-1-5.py
+    'textile_to_html',# textile, depends on PyTextile 
http://dom.eav.free.fr/python/textile-mirror-2.0.10.tar.gz
      ]

  g = globals()
Index: transforms/textile_to_html.py
===================================================================
--- transforms/textile_to_html.py	(.../trunk)	(revision 0)
+++ transforms/textile_to_html.py 
(.../branches/plip149-markup-support-2)	(revision 7303)
@@ -0,0 +1,41 @@
+"""
+Uses Roberto A. F. De Almeida's http://dealmeida.net/ module to do its 
handy work
+
+author: Tom Lazar <tom at tomster.org> at the archipelago sprint 2006
+
+"""
+
+from Products.PortalTransforms.interfaces import itransform
+from Products.PortalTransforms.libtransforms.utils import bin_search, 
sansext
+from Products.PortalTransforms.libtransforms.commandtransform import 
commandtransform
+from Products.CMFDefault.utils import bodyfinder
+import os
+
+try:
+    import textile as textile_transformer
+except ImportError:
+    HAS_TEXTILE = False
+else:
+    HAS_TEXTILE = True
+
+
+class textile:
+    __implements__ = itransform
+
+    __name__ = "textile_to_html"
+    inputs  = ("text/x-web-textile",)
+    output = "text/html"
+
+    def name(self):
+        return self.__name__
+
+    def convert(self, orig, data, **kwargs):
+        if HAS_TEXTILE:
+            html = textile_transformer.textile(orig, encoding='utf-8', 
output='utf-8')
+        else:
+            html = orig
+        data.setData(html)
+        return data
+
+def register():
+    return textile()
Index: transforms/markdown_to_html.py
===================================================================
--- transforms/markdown_to_html.py	(.../trunk)	(revision 0)
+++ transforms/markdown_to_html.py 
(.../branches/plip149-markup-support-2)	(revision 7303)
@@ -0,0 +1,41 @@
+"""
+Uses the http://www.freewisdom.org/projects/python-markdown/ module to 
do its handy work
+
+author: Tom Lazar <tom at tomster.org> at the archipelago sprint 2006
+
+"""
+
+from Products.PortalTransforms.interfaces import itransform
+from Products.PortalTransforms.libtransforms.utils import bin_search, 
sansext
+from Products.PortalTransforms.libtransforms.commandtransform import 
commandtransform
+from Products.CMFDefault.utils import bodyfinder
+import os
+
+try:
+    import markdown as markdown_transformer
+except ImportError:
+    HAS_MARKDOWN = False
+else:
+    HAS_MARKDOWN = True
+
+
+class markdown:
+    __implements__ = itransform
+
+    __name__ = "markdown_to_html"
+    inputs  = ("text/x-web-markdown",)
+    output = "text/html"
+
+    def name(self):
+        return self.__name__
+
+    def convert(self, orig, data, **kwargs):
+        if HAS_MARKDOWN:
+            html = markdown_transformer.markdown(orig)
+        else:
+            html = orig
+        data.setData(html)
+        return data
+
+def register():
+    return markdown()
Index: tests/input/markdown.txt
===================================================================
--- tests/input/markdown.txt	(.../trunk)	(revision 0)
+++ tests/input/markdown.txt	(.../branches/plip149-markup-support-2) 
(revision 7303)
@@ -0,0 +1,3 @@
+## Testing Markdown
+
+`code` and _italic_ and *bold* and even a [link](http://plone.org).
Index: tests/input/input.textile
===================================================================
--- tests/input/input.textile	(.../trunk)	(revision 0)
+++ tests/input/input.textile	(.../branches/plip149-markup-support-2) 
(revision 7303)
@@ -0,0 +1,16 @@
+h1. Textile test text
+
+_This_ is quite *boring*, but it needs to be "done":http://plone.org, 
right?
+
+h2. Cheeses
+
+# Gouda
+# Roquefort
+# Emmentaler
+
+h2. Episodes
+
+* Bicycle Repairman
+* Spanish Inquisition
+* Fishslapping Dance
+
Index: tests/output/textile.html
===================================================================
--- tests/output/textile.html	(.../trunk)	(revision 0)
+++ tests/output/textile.html	(.../branches/plip149-markup-support-2) 
(revision 7303)
@@ -0,0 +1,24 @@
+<h1>Textile test text</h1>
+
+
+	<p><em>This</em> is quite <strong>boring</strong>, but it needs to be 
<a href="http://plone.org">done</a>, right?</p>
+
+
+	<h2>Cheeses</h2>
+
+
+	<ol>
+	<li>Gouda</li>
+		<li>Roquefort</li>
+		<li>Emmentaler</li>
+	</ol>
+
+
+	<h2>Episodes</h2>
+
+
+	<ul>
+	<li>Bicycle Repairman</li>
+		<li>Spanish Inquisition</li>
+		<li>Fishslapping Dance</li>
+	</ul>
Index: tests/output/markdown.html
===================================================================
--- tests/output/markdown.html	(.../trunk)	(revision 0)
+++ tests/output/markdown.html	(.../branches/plip149-markup-support-2) 
(revision 7303)
@@ -0,0 +1,6 @@
+
+
+<h2> Testing Markdown </h2>
+<p> <code>code</code> and <em>italic</em> and <em>bold</em> and even a 
<a href="http://plone.org">link</a>.
+</p>
+
Index: tests/test_transforms.py
===================================================================
--- tests/test_transforms.py	(.../trunk)	(revision 7303)
+++ tests/test_transforms.py	(.../branches/plip149-markup-support-2) 
(revision 7303)
@@ -179,7 +179,13 @@
      ('Products.PortalTransforms.transforms.image_to_pcx',
       "logo.png", "logo.pcx", None, 0
       ),
-    )
+    ('Products.PortalTransforms.transforms.markdown_to_html',
+     "markdown.txt", "markdown.html", None, 0
+     ),
+    ('Products.PortalTransforms.transforms.textile_to_html',
+     "input.textile", "textile.html", None, 0
+    ),
+   )

  def initialise(transform, normalize, pattern):
      global TRANSFORMS_TESTINFO
Index: TransformEngine.py
===================================================================
--- TransformEngine.py	(.../trunk)	(revision 7303)
+++ TransformEngine.py	(.../branches/plip149-markup-support-2)	(revision 
7303)
@@ -499,4 +499,17 @@
              log('objectItems: catched MissingBinary exception')
              return []

+    # available mimetypes 
####################################################
+    def listAvailableTextInputs(self):
+        """ Returns a list of mimetypes that can be used as input for 
textfields
+            by building a list of the inputs beginning with "text/" of 
all transforms.
+        """
+        available_types = []
+        candidate_transforms = [object[1] for object in self.objectItems()]
+        for candidate in candidate_transforms:
+            for input in candidate.inputs:
+                if input.startswith("text/") and input not in 
available_types:
+                    available_types.append(input)
+        return available_types
+
  InitializeClass(TransformTool)






More information about the Framework-Team mailing list