[Testbot] Plone 5.0 - Python 2.7 - Build # 2969 - Still failing! - 0 failure(s)

jenkins at plone.org jenkins at plone.org
Sun Aug 10 18:40:00 UTC 2014


-------------------------------------------------------------------------------
Plone 5.0 - Python 2.7 - Build # 2969 - Still Failing!
-------------------------------------------------------------------------------

http://jenkins.plone.org/job/plone-5.0-python-2.7/2969/


-------------------------------------------------------------------------------
CHANGES
-------------------------------------------------------------------------------

Repository: Products.CMFPlone
Branch: refs/heads/master
Date: 2014-08-10T20:32:05+02:00
Author: Timo Stollenwerk (tisto) <tisto at plone.org>
Commit: https://github.com/plone/Products.CMFPlone/commit/4286f87113bfad4abd50f374ced0b652e9037045

Add lock on ttw robot test (not finished yet).

Files changed:
M Products/CMFPlone/interfaces/controlpanel.py
M Products/CMFPlone/tests/robot/test_controlpanel_editing.robot

diff --git a/Products/CMFPlone/interfaces/controlpanel.py b/Products/CMFPlone/interfaces/controlpanel.py
index 7076bf6..64e8c17 100644
--- a/Products/CMFPlone/interfaces/controlpanel.py
+++ b/Products/CMFPlone/interfaces/controlpanel.py
@@ -206,7 +206,6 @@ class ISearchSchema(Interface):
     )
 
 
-# XXX: Why does ISiteSchema inherit from ILockSettings here ???
 class ISiteSchema(ILockSettings):
 
     site_title = schema.TextLine(
diff --git a/Products/CMFPlone/tests/robot/test_controlpanel_editing.robot b/Products/CMFPlone/tests/robot/test_controlpanel_editing.robot
index 887472b..23a3b1a 100644
--- a/Products/CMFPlone/tests/robot/test_controlpanel_editing.robot
+++ b/Products/CMFPlone/tests/robot/test_controlpanel_editing.robot
@@ -35,6 +35,13 @@ Scenario: Enable Link Integrity Check in the Editing Control Panel
 # See https://github.com/plone/Products.CMFPlone/issues/255 for details.
 #   Then I will be warned if I remove a linked document
 
+Scenario: Enable Lock on Through The Web in the Editing Control Panel
+  Given a logged-in site administrator
+    and the editing control panel
+   When I enable lock on through the web
+# XXX: This test is not finished yet.
+#   Then I will see a warning if a document is edited by another user
+
 
 *** Keywords *****************************************************************
 
@@ -67,6 +74,11 @@ I enable link integrity checks
   Click Button  Save
   Wait until page contains  Changes saved
 
+I enable lock on through the web
+  Select Checkbox  name=form.widgets.lock_on_ttw_edit:list
+  Click Button  Save
+  Wait until page contains  Changes saved
+
 
 # --- THEN -------------------------------------------------------------------
 
@@ -90,3 +102,6 @@ I will be warned if I remove a linked document
   ${doc1_uid}=  Create content  id=doc1  type=Document
   ${doc2_uid}=  Create content  id=doc2  type=Document
   Set field value  ${doc1_uid}  text  <p><a href='resolveuid/${doc2_uid}' data-val='${doc2_uid}' data-linktype='internal'>link</a></p>  text/html
+
+I will see a warning if a document is edited by another user
+  ${doc1_uid}=  Create content  id=doc1  type=Document


Repository: Products.CMFPlone
Branch: refs/heads/master
Date: 2014-08-10T20:32:14+02:00
Author: Timo Stollenwerk (tisto) <tisto at plone.org>
Commit: https://github.com/plone/Products.CMFPlone/commit/7daf6e9c57d8dfdab0458635e32339c682a1d057

Merge branch 'master' of git://github.com/plone/Products.CMFPlone

Files changed:
A Products/CMFPlone/controlpanel/browser/types.pt
A Products/CMFPlone/controlpanel/browser/types.py
A Products/CMFPlone/controlpanel/events.py
A Products/CMFPlone/controlpanel/tests/test_controlpanel_browser_types.py
A Products/CMFPlone/controlpanel/tests/test_controlpanel_types.py
A Products/CMFPlone/tests/robot/test_controlpanel_types.robot
M Products/CMFPlone/controlpanel/browser/configure.zcml
M Products/CMFPlone/interfaces/__init__.py
M Products/CMFPlone/interfaces/controlpanel.py
M Products/CMFPlone/interfaces/events.py

diff --git a/Products/CMFPlone/controlpanel/browser/configure.zcml b/Products/CMFPlone/controlpanel/browser/configure.zcml
index 204346a..bf55faf 100644
--- a/Products/CMFPlone/controlpanel/browser/configure.zcml
+++ b/Products/CMFPlone/controlpanel/browser/configure.zcml
@@ -51,4 +51,12 @@
     permission="plone.app.controlpanel.Site"
     />
 
+  <!-- Types Control panel -->
+  <browser:page
+    name="types-controlpanel"
+    for="Products.CMFPlone.interfaces.IPloneSiteRoot"
+    class=".types.TypesControlPanel"
+    permission="plone.app.controlpanel.Types"
+    />
+
 </configure>
diff --git a/Products/CMFPlone/controlpanel/browser/types.pt b/Products/CMFPlone/controlpanel/browser/types.pt
new file mode 100644
index 0000000..882c84e
--- /dev/null
+++ b/Products/CMFPlone/controlpanel/browser/types.pt
@@ -0,0 +1,271 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:metal="http://xml.zope.org/namespaces/metal"
+      xmlns:tal="http://xml.zope.org/namespaces/tal"
+      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+      xml:lang="en" lang="en"
+      metal:use-macro="context/prefs_main_template/macros/master"
+      i18n:domain="plone">
+
+<body>
+
+<metal:main metal:fill-slot="prefs_configlet_main">
+
+    <h1 class="documentFirstHeading"
+        i18n:translate="heading_type_settings">Type Settings</h1>
+
+    <div class="documentDescription" i18n:translate="description_types_setup">
+        Workflow, visibility and versioning settings for your content types.
+    </div>
+
+    <div id="content-core">
+        <form method="post"
+              tal:define="type_id view/type_id"
+              tal:attributes="action string:${context/absolute_url}/@@types-controlpanel">
+    
+            <input type="hidden" name="form.submitted:boolean" value="True" />
+            <input type="hidden" name="old_type_id" tal:attributes="value type_id" />
+    
+            <fieldset>
+                <legend>
+                    <select name="type_id" onchange="form.submit()">
+    
+                        <tal:option repeat="selectable view/selectable_types">
+                            <option tal:content="selectable/title" i18n:translate=""
+                                    tal:attributes="value selectable/id;
+                                                    selected python:type_id == selectable['id'] and 'selected' or None">
+                                    Content type
+                            </option>
+                        </tal:option>
+                        <option
+                            value=""
+                            i18n:translate="label_default_type"
+                            tal:attributes="selected python:type_id == '' and 'selected' or None">
+                          (Default)
+                        </option>
+                    </select>
+                    <noscript>
+                        <input type="submit"
+                               name="form.button.SelectContentType"
+                               class="standalone"
+                               value="Change"
+                               i18n:attributes="value label_change;" />
+                    </noscript>
+               </legend>
+    
+               <tal:block tal:condition="python:type_id!=''">
+    
+                   <p class="discreet"
+                      tal:content="view/selected_type_description"
+                      tal:condition="view/selected_type_description"
+                      i18n:translate="">
+                       Type description
+                   </p>
+    
+                   <div class="field">
+                        <input id="addable"
+                               type="checkbox"
+                               class="noborder"
+                               name="addable:boolean"
+                               tal:attributes="checked python:view.is_addable() and 'checked' or None"
+                               />
+                        <label for="addable" i18n:translate="types_controlpanel_addable">
+                            Globally addable
+                        </label>
+                        <br />
+    
+                        <input id="allow_discussion"
+                               type="checkbox"
+                               class="noborder"
+                               name="allow_discussion:boolean"
+                               tal:attributes="checked python:view.is_discussion_allowed() and 'checked' or None" />
+                        <label for="allow_discussion" i18n:translate="types_controlpanel_allow_discussion">
+                            Allow comments
+                        </label>
+                        <br />
+    
+    
+                        <input id="searchable"
+                               type="checkbox"
+                               class="noborder"
+                               name="searchable"
+                               tal:attributes="checked python: view.is_searchable() and 'checked' or None" />
+                        <label for="searchable" i18n:translate="types_controlpanel_searchable">
+                            Visible in searches
+                        </label>
+                        <br />
+    
+    
+                        <tal:block tal:condition="python:type_id=='Link'">
+    
+                          <input id="redirect_links"
+                                 type="checkbox"
+                                 class="noborder"
+                                 name="redirect_links:boolean"
+                                 tal:attributes="checked python: view.is_redirect_links_enabled() and 'checked' or None" />
+                          <label for="redirect_links" i18n:translate="types_controlpanel_redirect_links">
+                              Redirect immediately to link target
+                          </label>
+    
+                        </tal:block>
+                    </div>
+    
+                    <div class="field">
+                        <label for="versionpolicy" i18n:translate="types_controlpanel_versionpolicy">
+                            Versioning policy:
+                        </label>
+                        <select id="versionpolicy" name="versionpolicy"
+                                tal:define="current_policy view/current_versioning_policy">
+                            <option tal:repeat="policy view/versioning_policies"
+                                    tal:attributes="value policy/id;
+                                                    selected python:policy['id']==current_policy and 'selected' or None"
+                                    tal:content="policy/title">No versioning</option>
+                        </select>
+                    </div>
+    
+    
+                    <div class="field">
+                        <a tal:attributes="href string:${context/absolute_url}/@@manage-content-type-portlets?key=${type_id}"
+                            i18n:translate="types_controlpanel_manage_portlets">
+                            Manage portlets assigned to this content type
+                        </a>
+                    </div>
+                </tal:block>
+    
+                <tal:workflows define="current_wf view/current_workflow">
+    
+                    <div class="field">
+                        <label i18n:translate="types_controlpanel_current_workflow">Current workflow:</label>
+                        <span tal:content="current_wf/title"
+                              i18n:translate="">Community Workflow</span>
+                    </div>
+    
+                    <ul>
+                      <tal:list-type
+                        repeat="desc current_wf/description"
+                        condition="current_wf/description">
+                        <li tal:content="desc"
+                            i18n:translate="">Workflow description</li>
+                      </tal:list-type>
+                    </ul>
+    
+                    <div class="field">
+                        <label for="new_workflow"
+                               i18n:translate="types_controlpanel_new_workflow">New workflow:</label>
+    
+                        <select onchange="form.submit()" id="workflows" name="new_workflow"
+                            tal:define="selected_wf view/new_workflow">
+    
+                            <tal:wfs repeat="wf view/available_workflows">
+                                <option i18n:translate=""
+                                        tal:content="wf/title"
+                                        tal:attributes="selected python:wf['id'] == selected_wf and 'selected' or None;
+                                                        value wf/id">Intranet Workflow
+                                </option>
+                            </tal:wfs>
+    
+                            <option
+                                value="[none]"
+                                tal:attributes="selected python:selected_wf == '[none]' and 'selected' or None"
+                                i18n:translate="types_controlpanel_no_workflow">No Workflow</option>
+                        </select>
+                        <noscript>
+                            <input type="submit"
+                                   name="form.button.SelectWorkflow"
+                                   class="standalone"
+                                   value="Change"
+                                   i18n:attributes="value label_change;" />
+                        </noscript>
+                    </div>
+    
+                    <input
+                        type="hidden"
+                        name="form.workflow.submitted:boolean"
+                        value="True"
+                        tal:condition="view/have_new_workflow"
+                        />
+                    <ul>
+                      <tal:list-type
+                        repeat="desc view/new_workflow_description"
+                        condition="view/new_workflow_description">
+                        <li tal:content="desc">Workflow description</li>
+                      </tal:list-type>
+                    </ul>
+    
+                    <div tal:define="new_workflow view/new_workflow"
+                         tal:condition="python:view.have_new_workflow() and not view.new_workflow_is_none() and view.new_workflow_is_different()">
+    
+                        <label for="states" i18n:translate="types_controlpanel_state_mapping">
+                            State Mapping
+                        </label>
+    
+                        <div class="formHelp" i18n:translate="types_controlpanel_state_mapping_help">
+                            When changing workflows, you have to select a state equivalent in the
+                            new workflow.
+                        </div>
+    
+                        <table id="states"
+                               class="listing nosort"
+                               tal:define="new_wf_states view/new_workflow_available_states">
+                            <thead>
+                                <tr>
+                                    <th i18n:translate="types_controlpanel_old_state">Old State</th>
+                                    <th i18n:translate="types_controlpanel_new_state">New State</th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                <tal:states repeat="state_map view/suggested_state_map">
+                                    <tr tal:define="oddrow repeat/state_map/odd;"
+                                        tal:attributes="class python:oddrow and 'odd' or 'even'">
+                                        <td>
+                                            <input type="hidden"
+                                                   tal:attributes="name string:new_wfstates.old_state:records;
+                                                                   value state_map/old_id" />
+                                            <span tal:content="state_map/old_title">Published</span>
+                                        </td>
+                                        <td>
+                                            <select tal:attributes="name string:new_wfstates.new_state:records">
+                                              <tal:states repeat="new_state new_wf_states">
+                                                <option
+                                                    tal:attributes="value new_state/id;
+                                                                    selected python:new_state['id'] == state_map['suggested_id'] and 'selected' or None"
+                                                    tal:content="new_state/title">Pending</option>
+                                              </tal:states>
+                                            </select>
+                                        </td>
+                                    </tr>
+                                </tal:states>
+                            </tbody>
+                        </table>
+                    </div>
+    
+                </tal:workflows>
+    
+                <div tal:condition="view/have_new_workflow" class="portalMessage info">
+                    <strong>Info</strong>
+                    <span i18n:translate="types_controlpanel_warn_remap">
+                        Changing the workflow of a type will take a while, and may slow down
+                        the site significantly while the content is updated to the new setting.
+                    </span>
+                </div>
+    
+                <div class="formControls">
+                  <input type="submit"
+                         value="Apply Changes"
+                         name="form.button.Save"
+                         class="context"
+                         i18n:attributes="value label_apply_changes;" />
+                  <input type="submit"
+                         value="Cancel"
+                         name="form.button.Cancel"
+                         class="standalone"
+                         i18n:attributes="value label_cancel;" />
+                </div>
+    
+            </fieldset>
+    
+        </form>
+    </div>
+</metal:main>
+
+</body>
+</html>
diff --git a/Products/CMFPlone/controlpanel/browser/types.py b/Products/CMFPlone/controlpanel/browser/types.py
new file mode 100644
index 0000000..e7043c0
--- /dev/null
+++ b/Products/CMFPlone/controlpanel/browser/types.py
@@ -0,0 +1,535 @@
+from Products.CMFCore.utils import getToolByName
+from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
+from Products.statusmessages.interfaces import IStatusMessage
+from z3c.form import button
+from z3c.form import form
+
+from Products.CMFPlone.interfaces import ITypesSchema
+
+from Products.CMFPlone import PloneMessageFactory as _
+
+from plone.autoform.form import AutoExtensibleForm
+
+from plone.app.workflow.remap import remap_workflow
+from plone.memoize.instance import memoize
+
+from zope.component import getUtility
+
+from zope.event import notify
+
+from zope.i18n import translate
+from zope.schema.interfaces import IVocabularyFactory
+
+from Acquisition import aq_inner
+
+from Products.CMFPlone.controlpanel.events import ConfigurationChangedEvent
+
+
+def format_description(text, request=None):
+    # We expect the workflow to be a text of '- ' divided bullet points.
+    text = translate(text.strip(), domain='plone', context=request)
+    return [s.strip() for s in text.split('- ') if s]
+
+
+# These are convenient / user friendly versioning policies.
+VERSION_POLICIES = [
+    dict(id="off",
+         policy=(),
+         title=_(u"versioning_off",
+                 default=u"No versioning")),
+
+    dict(id="manual",
+         policy=("version_on_revert",),
+         title=_(u"versioning_manual",
+                 default=u"Manual")),
+
+    dict(id="automatic",
+         policy=("at_edit_autoversion", "version_on_revert"),
+         title=_(u"versioning_automatic",
+                 default=u"Automatic")),
+]
+
+
+class TypesControlPanel(AutoExtensibleForm, form.EditForm):
+    schema = ITypesSchema
+    id = "types-control-panel"
+    label = _("Types settings")
+    description = _("General types settings.")
+    form_name = _("Types settings")
+    control_panel_view = "types-controlpanel"
+    template = ViewPageTemplateFile('types.pt')
+
+    @button.buttonAndHandler(_('Save'), name='save')
+    def handleSave(self, action):
+        data, errors = self.extractData()
+        if errors:
+            self.status = self.formErrorsMessage
+            return
+        #portal = getSite()
+        #pprop = getToolByName(portal, 'portal_properties')
+        #site_properties = pprop.site_properties
+        #site_properties.manage_changeProperties(
+        #    visible_ids=data['visible_ids'],
+        #    enable_inline_types=data['enable_inline_types'],
+        #    enable_link_integrity_checks=data['enable_link_integrity_checks'],
+        #    ext_editor=data['ext_editor'],
+        #    default_editor=data['default_editor'],
+        #    lock_on_ttw_edit=data['lock_on_ttw_edit'])
+        #IStatusMessage(self.request).addStatusMessage(_(u"Changes saved"),
+        #S                                              "info")
+        self.request.response.redirect("@@types-controlpanel")
+
+    @button.buttonAndHandler(_(u"Cancel"), name='cancel')
+    def handleCancel(self, action):
+        IStatusMessage(self.request).addStatusMessage(
+            _(u"Changes canceled."), "info")
+        self.request.response.redirect("plone_control_panel")
+
+    @property
+    @memoize
+    def type_id(self):
+        type_id = self.request.get('type_id', None)
+        if type_id is None:
+            type_id = ''
+        return type_id
+
+    @property
+    @memoize
+    def fti(self):
+        type_id = self.type_id
+        portal_types = getToolByName(self.context, 'portal_types')
+        return getattr(portal_types, type_id)
+
+    def __call__(self):
+        """Perform the update and redirect if necessary, or render the page
+        """
+        postback = True
+        context = aq_inner(self.context)
+
+        form = self.request.form
+        submitted = form.get('form.submitted', False)
+        save_button = form.get('form.button.Save', None) is not None
+        cancel_button = form.get('form.button.Cancel', None) is not None
+        type_id = form.get('old_type_id', None)
+
+        if submitted and not cancel_button:
+            if type_id:
+                portal_types = getToolByName(self.context, 'portal_types')
+                portal_repository = getToolByName(self.context,
+                                                  'portal_repository')
+                portal_properties = getToolByName(self.context,
+                                                  'portal_properties')
+                site_properties = getattr(portal_properties, 'site_properties')
+
+                fti = getattr(portal_types, type_id)
+
+                # Set FTI properties
+
+                addable = form.get('addable', False)
+                allow_discussion = form.get('allow_discussion', False)
+
+                fti.manage_changeProperties(
+                    global_allow=bool(addable),
+                    allow_discussion=bool(allow_discussion)
+                )
+
+                version_policy = form.get('versionpolicy', "off")
+                if version_policy != self.current_versioning_policy():
+                    newpolicy = [
+                        p for p in VERSION_POLICIES
+                        if p["id"] == version_policy][0]
+
+                    versionable_types = list(
+                        portal_repository.getVersionableContentTypes()
+                    )
+                    if not newpolicy["policy"]:
+                        if type_id in versionable_types:
+                            versionable_types.remove(type_id)
+                    else:
+                        if type_id not in versionable_types:
+                            versionable_types.append(type_id)
+
+                    for policy in portal_repository.listPolicies():
+                        policy_id = policy.getId()
+                        if policy_id in newpolicy["policy"]:
+                            portal_repository.addPolicyForContentType(
+                                type_id,
+                                policy_id
+                            )
+                        else:
+                            portal_repository.removePolicyFromContentType(
+                                type_id,
+                                policy_id
+                            )
+
+                    portal_repository.setVersionableContentTypes(
+                        versionable_types
+                    )
+
+                searchable = form.get('searchable', False)
+                blacklisted = list(
+                    site_properties.getProperty('types_not_searched')
+                )
+                if searchable and type_id in blacklisted:
+                    blacklisted.remove(type_id)
+                elif not searchable and type_id not in blacklisted:
+                    blacklisted.append(type_id)
+                site_properties.manage_changeProperties(
+                    types_not_searched=blacklisted
+                )
+
+                redirect_links = form.get('redirect_links', False)
+                site_properties.manage_changeProperties(
+                    redirect_links=redirect_links
+                )
+
+            # Update workflow
+            if self.have_new_workflow() \
+                    and form.get('form.workflow.submitted', False) \
+                    and save_button:
+                if self.new_workflow_is_different():
+                    new_wf = self.new_workflow()
+                    if new_wf == '[none]':
+                        chain = ()
+                    elif new_wf == '(Default)':
+                        chain = new_wf
+                    else:
+                        chain = (new_wf,)
+                    state_map = dict([
+                        (s['old_state'], s['new_state'])
+                        for s in form.get('new_wfstates', [])
+                    ])
+                    if '[none]' in state_map:
+                        state_map[None] = state_map['[none]']
+                        del state_map['[none]']
+                    if type_id:
+                        type_ids = (type_id,)
+                    else:
+                        wt = getToolByName(self.context, 'portal_workflow')
+                        tt = getToolByName(self.context, 'portal_types')
+                        nondefault = [
+                            info[0] for info in wt.listChainOverrides()
+                        ]
+                        type_ids = [
+                            type for type in tt.listContentTypes()
+                            if type not in nondefault
+                        ]
+                        wt.setChainForPortalTypes(
+                            type_ids,
+                            wt.getDefaultChain()
+                        )
+                        wt.setDefaultChain(','.join(chain))
+                        chain = '(Default)'
+
+                    remap_workflow(context, type_ids=type_ids, chain=chain,
+                                   state_map=state_map)
+
+                    data = {'workflow': new_wf}
+                    notify(ConfigurationChangedEvent(self, data))
+
+                else:
+                    portal_workflow = getToolByName(context, 'portal_workflow')
+                    if self.new_workflow() == '(Default)':
+                        # The WorkflowTool API can not handle this sanely
+                        cbt = portal_workflow._chains_by_type
+                        if type_id in cbt:
+                            del cbt[type_id]
+                    else:
+                        portal_workflow.setChainForPortalTypes(
+                            (type_id,),
+                            self.new_workflow()
+                        )
+
+                self.request.response.redirect(
+                    '%s/@@types-controlpanel?type_id=%s' % (
+                        context.absolute_url(),
+                        type_id
+                    )
+                )
+                postback = False
+
+        elif cancel_button:
+            self.request.response.redirect(
+                self.context.absolute_url() + '/plone_control_panel')
+            postback = False
+
+        if postback:
+            return self.template()
+
+    # View
+
+    def versioning_policies(self):
+        return VERSION_POLICIES
+
+    @memoize
+    def selectable_types(self):
+        vocab_factory = getUtility(
+            IVocabularyFactory,
+            name="plone.app.vocabularies.ReallyUserFriendlyTypes"
+        )
+        types = []
+        for v in vocab_factory(self.context):
+            if v.title:
+                title = translate(v.title, context=self.request)
+            else:
+                title = translate(
+                    v.token,
+                    domain='plone',
+                    context=self.request
+                )
+            types.append(dict(id=v.value, title=title))
+
+        def _key(v):
+            return v['title']
+
+        types.sort(key=_key)
+        return types
+
+    def selected_type_title(self):
+        return self.fti.Title()
+
+    def selected_type_description(self):
+        return self.fti.Description()
+
+    def is_addable(self):
+        return self.fti.getProperty('global_allow', False)
+
+    def is_discussion_allowed(self):
+        return self.fti.getProperty('allow_discussion', False)
+
+    def current_versioning_policy(self):
+        portal_repository = getToolByName(self.context, 'portal_repository')
+        if self.type_id not in portal_repository.getVersionableContentTypes():
+            return "off"
+        policy = set(portal_repository.getPolicyMap().get(self.type_id, ()))
+        for info in VERSION_POLICIES:
+            if set(info["policy"]) == policy:
+                return info["id"]
+        return None
+
+    def is_searchable(self):
+        context = aq_inner(self.context)
+        portal_properties = getToolByName(context, 'portal_properties')
+        blacklisted = portal_properties.site_properties.types_not_searched
+        return (self.type_id not in blacklisted)
+
+    def is_redirect_links_enabled(self):
+        context = aq_inner(self.context)
+        portal_properties = getToolByName(context, 'portal_properties')
+        site_props = portal_properties.site_properties
+        return self.type_id == 'Link' \
+            and site_props.getProperty('redirect_links') or False
+
+    @memoize
+    def current_workflow(self):
+        context = aq_inner(self.context)
+        portal_workflow = getToolByName(context, 'portal_workflow')
+        default_workflow = self.default_workflow(False)
+        nondefault = [info[0] for info in portal_workflow.listChainOverrides()]
+        chain = portal_workflow.getChainForPortalType(self.type_id)
+        empty_workflow_dict = dict(
+            id='[none]',
+            title=_(u"label_no_workflow"),
+            description=[_(
+                u"description_no_workflow",
+                default=u"This type has no workflow. The visibilty "
+                        u"of items of this type is determined by "
+                        u"the folder they are in.")
+            ]
+        )
+
+        if self.type_id in nondefault:
+            if chain:
+                wf_id = chain[0]
+                wf = getattr(portal_workflow, wf_id)
+                title = translate(
+                    wf.title,
+                    domain='plone',
+                    context=self.request
+                )
+                return dict(
+                    id=wf.id,
+                    title=title,
+                    description=format_description(
+                        wf.description,
+                        self.request
+                    )
+                )
+            else:
+                return empty_workflow_dict
+
+        if default_workflow == '[none]':
+            return empty_workflow_dict
+
+        default_title = translate(default_workflow.title, domain='plone',
+                                  context=self.request)
+        return dict(id='(Default)',
+                    title=_(
+                        u"label_default_workflow_title",
+                        default=u"Default workflow (${title})",
+                        mapping=dict(title=default_title)),
+                    description=format_description(
+                        default_workflow.description,
+                        self.request)
+                    )
+
+    def available_workflows(self):
+        vocab_factory = getUtility(IVocabularyFactory,
+                                   name="plone.app.vocabularies.Workflows")
+        workflows = []
+        for v in vocab_factory(self.context):
+            if v.title:
+                title = translate(v.title, context=self.request)
+            else:
+                title = translate(
+                    v.token,
+                    domain='plone',
+                    context=self.request
+                )
+            workflows.append(dict(id=v.value, title=title))
+
+        def _key(v):
+            return v['title']
+
+        workflows.sort(key=_key)
+
+        default_workflow = self.default_workflow(False)
+        if self.type_id and default_workflow != '[none]':
+            # Only offer a default workflow option on a real type
+            default_workflow = self.default_workflow(False)
+            default_title = translate(default_workflow.title,
+                                      domain='plone', context=self.request)
+            workflows.insert(
+                0,
+                dict(
+                    id='(Default)',
+                    title=_(u"label_default_workflow_title",
+                            default=u"Default workflow (${title})",
+                            mapping=dict(title=default_title)),
+                    description=format_description(
+                        default_workflow.description,
+                        self.request
+                    )
+                )
+            )
+
+        return workflows
+
+    @memoize
+    def new_workflow(self):
+        current_workflow = self.current_workflow()['id']
+        if self.type_id == '':
+            # If we are looking at the default workflow we need to show
+            # the real workflow
+            current_workflow = self.real_workflow(current_workflow)
+        old_type_id = self.request.form.get('old_type_id', self.type_id)
+        if old_type_id != self.type_id:
+            return current_workflow
+        else:
+            return self.request.form.get('new_workflow', current_workflow)
+
+    @memoize
+    def have_new_workflow(self):
+        return self.current_workflow()['id'] != self.new_workflow()
+
+    @memoize
+    def default_workflow(self, id_only=True):
+        portal_workflow = getToolByName(self.context, 'portal_workflow')
+        default_chain = portal_workflow.getDefaultChain()
+        if not default_chain:
+            # There is no default workflow
+            return '[none]'
+        id = default_chain[0]
+        if id_only:
+            return id
+        else:
+            return portal_workflow.getWorkflowById(id)
+
+    @memoize
+    def real_workflow(self, wf):
+        if wf == '(Default)':
+            return self.default_workflow()
+        else:
+            return wf
+
+    @memoize
+    def new_workflow_is_different(self):
+        new_workflow = self.new_workflow()
+        current_workflow = self.current_workflow()['id']
+
+        return self.real_workflow(new_workflow) != self.real_workflow(
+            current_workflow)
+
+    @memoize
+    def new_workflow_is_none(self):
+        return self.new_workflow() == '[none]'
+
+    def new_workflow_description(self):
+        portal_workflow = getToolByName(self.context, 'portal_workflow')
+        new_workflow = self.new_workflow()
+
+        if self.new_workflow_is_different():
+            if self.new_workflow_is_none():
+                return [_(
+                    u"description_no_workflow",
+                    default=u"This type has no workflow. The visibilty of "
+                            u"items of this type is determined by the "
+                            u"folder they are in.")]
+            new_workflow = self.real_workflow(self.new_workflow())
+            wf = getattr(portal_workflow, new_workflow)
+            return format_description(wf.description, self.request)
+
+        return None
+
+    def new_workflow_available_states(self):
+        if self.new_workflow_is_different():
+            new_workflow = self.real_workflow(self.new_workflow())
+            portal_workflow = getToolByName(self.context, 'portal_workflow')
+            wf = getattr(portal_workflow, new_workflow)
+            states = []
+            for s in wf.states.objectValues():
+                title = translate(
+                    s.title,
+                    domain='plone',
+                    context=self.request
+                )
+                states.append(dict(id=s.id, title=title))
+            return states
+        else:
+            return []
+
+    def suggested_state_map(self):
+        current_workflow = self.real_workflow(self.current_workflow()['id'])
+        new_workflow = self.real_workflow(self.new_workflow())
+
+        portal_workflow = getToolByName(self.context, 'portal_workflow')
+
+        if current_workflow == '[none]':
+            new_wf = getattr(portal_workflow, new_workflow)
+            default_state = new_wf.initial_state
+            return [dict(old_id='[none]',
+                         old_title=_(u"No workflow"),
+                         suggested_id=default_state)]
+
+        elif self.new_workflow_is_different():
+            old_wf = getattr(portal_workflow, current_workflow)
+            new_wf = getattr(portal_workflow, new_workflow)
+
+            new_states = set([s.id for s in new_wf.states.objectValues()])
+            default_state = new_wf.initial_state
+
+            states = []
+            for old in old_wf.states.objectValues():
+                title = translate(
+                    old.title,
+                    domain='plone',
+                    context=self.request
+                )
+                states.append(dict(
+                    old_id=old.id,
+                    old_title=title,
+                    suggested_id=(old.id in new_states and
+                                  old.id or default_state)))
+            return states
+        else:
+            return []
diff --git a/Products/CMFPlone/controlpanel/events.py b/Products/CMFPlone/controlpanel/events.py
new file mode 100644
index 0000000..206ba47
--- /dev/null
+++ b/Products/CMFPlone/controlpanel/events.py
@@ -0,0 +1,20 @@
+from zope.component import adapter
+from zope.component import queryUtility
+from zope.interface import implements
+from zope.ramcache.interfaces.ram import IRAMCache
+
+from Products.CMFPlone.interfaces import IConfigurationChangedEvent
+
+class ConfigurationChangedEvent(object):
+    implements(IConfigurationChangedEvent)
+
+    def __init__(self, context, data):
+        self.context = context
+        self.data = data
+
+
+ at adapter(IConfigurationChangedEvent)
+def handleConfigurationChangedEvent(event):
+    util = queryUtility(IRAMCache)
+    if util is not None:
+        util.invalidateAll()
diff --git a/Products/CMFPlone/controlpanel/tests/test_controlpanel_browser_types.py b/Products/CMFPlone/controlpanel/tests/test_controlpanel_browser_types.py
new file mode 100644
index 0000000..582e339
--- /dev/null
+++ b/Products/CMFPlone/controlpanel/tests/test_controlpanel_browser_types.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+from plone.app.testing import SITE_OWNER_NAME, SITE_OWNER_PASSWORD
+from plone.testing.z2 import Browser
+
+from Products.CMFPlone.testing import \
+    PRODUCTS_CMFPLONE_FUNCTIONAL_TESTING
+
+import unittest2 as unittest
+
+
+class TypesControlPanelFunctionalTest(unittest.TestCase):
+    """Test that changes in the types control panel are actually
+    stored in the registry.
+    """
+
+    layer = PRODUCTS_CMFPLONE_FUNCTIONAL_TESTING
+
+    def setUp(self):
+        self.app = self.layer['app']
+        self.portal = self.layer['portal']
+        self.portal_url = self.portal.absolute_url()
+        self.types_url = "%s/@@types-controlpanel" % self.portal_url
+        self.browser = Browser(self.app)
+        self.browser.handleErrors = False
+        self.browser.addHeader(
+            'Authorization',
+            'Basic %s:%s' % (SITE_OWNER_NAME, SITE_OWNER_PASSWORD,)
+        )
+
+    def test_types_control_panel_link(self):
+        self.browser.open(
+            "%s/plone_control_panel" % self.portal_url)
+        self.browser.getLink('Editing').click()
+
+    def test_standard_type_select(self):
+        self.browser.open(self.types_url)
+        self.browser.getControl(name='type_id').value = ['Link']
+        self.browser.getForm(action=self.types_url).submit()
+        self.assertIn('types-controlpanel', self.browser.url)
+
+    def test_standard_type_cancel(self):
+        self.browser.open(self.types_url)
+        self.browser.getControl(name='type_id').value = ['Link']
+        self.browser.getControl('Cancel').click()
+        self.assertIn('plone_control_panel', self.browser.url)
+
+    def test_standard_type_allow_commenting(self):
+        self.browser.open(self.types_url)
+        self.browser.getControl(name='type_id').value = ['Link']
+        self.browser.getForm(action=self.types_url).submit()
+        self.browser.getControl('Allow comments').selected = True
+        self.browser.getControl('Apply Changes').click()
+
+        # Check if settings got saved correctly
+        self.browser.open(self.types_url)
+        self.browser.getControl(name='type_id').value = ['Link']
+        self.browser.getForm(action=self.types_url).submit()
+        self.assertIn('Globally addable', self.browser.contents)
+        self.assertIn('Allow comments', self.browser.contents)
+        self.assertEquals(
+            self.browser.getControl('Allow comments').selected,
+            True
+        )
+        self.assertIn('Visible in searches', self.browser.contents)
+        self.assertIn(
+            '<input id="redirect_links" type="checkbox" class="noborder"'
+            ' name="redirect_links:boolean" checked="checked" />',
+            self.browser.contents)
+        self.assertIn(
+            '<label for="redirect_links">Redirect immediately to link target',
+            self.browser.contents
+        )
+
+    def test_set_no_default_workflow(self):
+        # references http://dev.plone.org/plone/ticket/11901
+        self.browser.open(self.types_url)
+        self.browser.getControl(name="new_workflow").value = ['[none]']
+        self.browser.getControl(name="form.button.Save").click()
+
+        # Check that setting No workflow as default workflow doesn't break
+        # break editing types
+        self.browser.open(self.types_url)
+        self.browser.getControl(name='type_id').value = ['Link']
+        self.browser.getForm(action=self.types_url).submit()
+        self.assertIn('Globally addable', self.browser.contents)
+        self.assertIn('Allow comments', self.browser.contents)
+        self.assertIn('Visible in searches', self.browser.contents)
diff --git a/Products/CMFPlone/controlpanel/tests/test_controlpanel_types.py b/Products/CMFPlone/controlpanel/tests/test_controlpanel_types.py
new file mode 100644
index 0000000..3ec7c2e
--- /dev/null
+++ b/Products/CMFPlone/controlpanel/tests/test_controlpanel_types.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+from Products.CMFPlone.interfaces import ITypesSchema
+import unittest2 as unittest
+
+from zope.component import getMultiAdapter
+from zope.component import getUtility
+from plone.registry.interfaces import IRegistry
+
+from Products.CMFCore.utils import getToolByName
+
+from Products.CMFPlone.testing import \
+    PRODUCTS_CMFPLONE_INTEGRATION_TESTING
+
+
+class TypesRegistryIntegrationTest(unittest.TestCase):
+    """Tests that the types settings are stored as plone.app.registry
+    settings.
+    """
+
+    layer = PRODUCTS_CMFPLONE_INTEGRATION_TESTING
+
+    def setUp(self):
+        self.portal = self.layer['portal']
+        self.request = self.layer['request']
+        registry = getUtility(IRegistry)
+        self.settings = registry.forInterface(
+            ITypesSchema, prefix="plone")
+
+    def test_types_controlpanel_view(self):
+        view = getMultiAdapter((self.portal, self.portal.REQUEST),
+                               name="types-controlpanel")
+        view = view.__of__(self.portal)
+        self.assertTrue(view())
+
+    def test_editing_in_controlpanel(self):
+        self.controlpanel = getToolByName(self.portal, "portal_controlpanel")
+        self.assertTrue('TypesSettings' in [
+            a.getAction(self)['id']
+            for a in self.controlpanel.listActions()
+        ])
diff --git a/Products/CMFPlone/interfaces/__init__.py b/Products/CMFPlone/interfaces/__init__.py
index e6edee3..20862b4 100644
--- a/Products/CMFPlone/interfaces/__init__.py
+++ b/Products/CMFPlone/interfaces/__init__.py
@@ -10,6 +10,8 @@
 from controlpanel import INavigationSchema
 from controlpanel import ISearchSchema
 from controlpanel import ISiteSchema
+from controlpanel import ITypesSchema
+from events import IConfigurationChangedEvent
 from events import ISiteManagerCreatedEvent
 from events import IReorderedEvent
 from interface import IInterfaceTool
diff --git a/Products/CMFPlone/interfaces/controlpanel.py b/Products/CMFPlone/interfaces/controlpanel.py
index 64e8c17..c0f8656 100644
--- a/Products/CMFPlone/interfaces/controlpanel.py
+++ b/Products/CMFPlone/interfaces/controlpanel.py
@@ -1,6 +1,7 @@
 from Products.CMFPlone import PloneMessageFactory as _
 from basetool import IPloneBaseTool
 from plone.locking.interfaces import ILockSettings
+from zope.interface import Attribute
 from zope.interface import Interface
 from zope import schema
 
@@ -279,3 +280,8 @@ class IDateAndTimeSchema(Interface):
         required=True,
         default=None,
         vocabulary="plone.app.vocabularies.Weekdays")
+
+
+class ITypesSchema(Interface):
+    """
+    """
diff --git a/Products/CMFPlone/interfaces/events.py b/Products/CMFPlone/interfaces/events.py
index efd536d..22ec020 100644
--- a/Products/CMFPlone/interfaces/events.py
+++ b/Products/CMFPlone/interfaces/events.py
@@ -1,4 +1,6 @@
 from zope.component.interfaces import IObjectEvent
+from zope.interface import Attribute
+from zope.interface import Interface
 
 
 class ISiteManagerCreatedEvent(IObjectEvent):
@@ -9,3 +11,12 @@ class IReorderedEvent(IObjectEvent):
     """An event that's fired once the Plone Tool has been notified of
        a reordering
     """
+
+
+class IConfigurationChangedEvent(Interface):
+    """An event which is fired after a configuration setting has been changed.
+    """
+
+    context = Attribute("The configuration context which was changed.")
+
+    data = Attribute("The configuration data which was changed.")
diff --git a/Products/CMFPlone/tests/robot/test_controlpanel_types.robot b/Products/CMFPlone/tests/robot/test_controlpanel_types.robot
new file mode 100644
index 0000000..061d97b
--- /dev/null
+++ b/Products/CMFPlone/tests/robot/test_controlpanel_types.robot
@@ -0,0 +1,80 @@
+*** Settings *****************************************************************
+
+Resource  plone/app/robotframework/keywords.robot
+Resource  plone/app/robotframework/saucelabs.robot
+
+Library  Remote  ${PLONE_URL}/RobotRemote
+
+Resource  keywords.robot
+
+Test Setup  Open SauceLabs test browser
+Test Teardown  Run keywords  Report test status  Close all browsers
+
+
+*** Test Cases ***************************************************************
+
+Scenario: Allow comments for Link Type
+  Given a logged-in manager
+    and Globaly enabled comments
+    and the types control panel
+   When I select 'Link' in types dropdown
+    and Allow discussion
+   Then Wait until page contains  Type Settings
+   When I add new Link 'my_link'
+    Then Link 'my_link' should have comments enabled
+
+Scenarion: Change default workflow
+  Given a logged-in site administrator
+    and the types control panel
+   When I select 'Single State Workflow' workflow
+   Then Wait until page contains  Type Settings
+   When I add new Link 'my_link'
+    Then Link 'my_link' should have Single State Workflow enabled
+
+
+*** Keywords *****************************************************************
+
+# --- GIVEN ------------------------------------------------------------------
+
+a logged-in manager
+  Enable autologin as  Manager
+
+the types control panel
+  Go to  ${PLONE_URL}/@@types-controlpanel
+
+Globaly enabled comments
+  Go to  ${PLONE_URL}/@@discussion-settings
+  Select checkbox  name=form.widgets.globally_enabled:list
+  Click button  Save
+
+
+
+# --- WHEN -------------------------------------------------------------------
+
+I select '${content_type}' in types dropdown
+  Select from list  name=type_id  ${content_type}
+  Wait until page contains  Globally addable
+
+Allow discussion
+  Select checkbox  name=allow_discussion:boolean
+  Click Button  Apply Changes
+
+I select '${workflow}' workflow
+  Select from list  name=new_workflow  ${workflow}
+  Click Button  Apply Changes
+
+I add new Link '${id}'
+  Go to  ${PLONE_URL}
+  Create content  type=Link  id=${id}  title=${id}
+
+
+# --- THEN -------------------------------------------------------------------
+
+Link '${id}' should have comments enabled
+  Go to  ${PLONE_URL}/${id}
+  Page should contain element  xpath=//div[@id="commenting"]
+
+Link '${id}' should have Single State Workflow enabled
+  Go to  ${PLONE_URL}/${id}
+  # We check that single state worklow is used, publish button is not present
+  Page should not contain element  xpath=//a[@id="workflow-transition-publish"]




-------------------------------------------------------------------------------
-------------- next part --------------
A non-text attachment was scrubbed...
Name: CHANGES.log
Type: application/octet-stream
Size: 50044 bytes
Desc: not available
URL: <http://lists.plone.org/pipermail/plone-testbot/attachments/20140810/66bf859c/attachment-0002.obj>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: build.log
Type: application/octet-stream
Size: 321342 bytes
Desc: not available
URL: <http://lists.plone.org/pipermail/plone-testbot/attachments/20140810/66bf859c/attachment-0003.obj>


More information about the Testbot mailing list