My Archetypes-based content type can't be added - plone

I'm developing an add-on package which introduces a few Archetypes-based content types;
these are defined in the default profile of that package.
After (re-) installing my package in the Quick-Installer, I can see my types in the types tool; but I can't add them TTW, and they are not listed in the folder_constraintypes_form. I did select them in the "Allowed content types" multiselect list of the Folder portal type.
Since I got a ValueError from FactoryTypeInformation._getFactoryMethod in an bin/instance debug session, I "developed" Products.CMFPlone (branch 2.2) and changed the TypesTool.py like so:
from pprint import pprint # ADDED
...
class FactoryTypeInformation(TypeInformation):
...
def _getFactoryMethod(self, container, check_security=1):
if not self.product or not self.factory:
raise ValueError, ('Product factory for %s was undefined' %
self.getId())
pd = container.manage_addProduct # ADDED
p = container.manage_addProduct[self.product]
self_product = self.product # ADDED
self_factory = self.factory # ADDED
m = getattr(p, self.factory, None)
if m is None:
pprint(locals()) # ADDED
raise ValueError, ('Product factory for %s was invalid' %
self.getId())
if not check_security:
return m
if getSecurityManager().validate(p, p, self.factory, m):
return m
raise AccessControl_Unauthorized( 'Cannot create %s' % self.getId() )
The debug session now looks like this:
>>> root = app.plone
>>> from Products.CMFCore.utils import getToolByName
>>> tmp_folder = root.temp
>>> type_name = 'MyType'
>>> types_tool = getToolByName(tmp_folder, 'portal_types')
>>> type_info = types_tool.getTypeInfo(type_name)
>>> type_info
<DynamicViewTypeInformation at /plone/portal_types/MyType>
>>> new_content_item = type_info._constructInstance(tmp_folder, 'shiny_new_object')
{'check_security': 0,
'container': <ATFolder at /plone/temp>,
'pd': <App.FactoryDispatcher.ProductDispatcher object at 0x227afd0>,
'p': <App.FactoryDispatcher.FactoryDispatcher object at 0x7b97450>,
'self': <DynamicViewTypeInformation at /plone/portal_types/MyType>,
'm': None,
'self_factory': 'addMyType',
'self_product': 'MyCompany.MyProduct'}
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/opt/zope/instances/zope-devel/src/Products.CMFCore/Products/CMFCore/TypesTool.py", line 551, in _constructInstance
m = self._getFactoryMethod(container, check_security=0)
File "/opt/zope/instances/zope-devel/src/Products.CMFCore/Products/CMFCore/TypesTool.py", line 467, in _getFactoryMethod
self.getId())
ValueError: Product factory for MyType was invalid
So, the FactoryDispatcher lacks the necessary addMyType attribute.
Probably my declarations are incomplete?
This is what I have:
config.py:
# -*- coding: utf-8 -*-
from Products.CMFCore.permissions import setDefaultRoles
from os import sep
from .permissions import (AddMyType,
)
PROJECTNAME = "MyCompany.MyProduct"
PRODUCT_HOME = sep.join(__file__.split(sep)[:-1])
MANAGERS_ONLY = ('Manager',)
MANAGERS_AND_OWNER = ('Manager', 'Owner')
# Permissions
DEFAULT_ADD_CONTENT_PERMISSION = "Add portal content"
setDefaultRoles(DEFAULT_ADD_CONTENT_PERMISSION, MANAGERS_AND_OWNER)
ADD_CONTENT_PERMISSIONS = {
'MyType': AddMyType,
}
for perm in ADD_CONTENT_PERMISSIONS.values():
setDefaultRoles(perm, MANAGERS_ONLY)
content/mytype.py:
# -*- coding: utf-8 -*-
__author__ = """unknown <unknown>"""
__docformat__ = 'plaintext'
from AccessControl import ClassSecurityInfo
from zope.interface import implements
from ..interfaces import IMyType
from ..config import PROJECTNAME
from Products.ATContentTypes.content.base import ATCTContent
from Products.ATContentTypes.content.schemata import ATContentTypeSchema
from Products.ATContentTypes.content.base import registerATCT as registerType
MyType_schema = (
ATContentTypeSchema.copy()
)
class MyType(ATCTContent):
"""
description of my type
"""
security = ClassSecurityInfo()
implements(IMyType)
meta_type = 'MyType'
_at_rename_after_creation = True
schema = MyType_schema
registerType(MyType, PROJECTNAME)
interfaces.py:
# -*- coding: utf-8 -*-
"""Module where all interfaces, events and exceptions live."""
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
from zope.interface import Interface
class ISupBetonqualiLayer(IDefaultBrowserLayer):
"""Marker interface that defines a browser layer."""
class IMyType(Interface):
"""Marker interface for .mytype.MyType
"""
permissions.py:
# -*- coding: utf-8 -*- vim: ts=8 sts=4 sw=4 si et tw=79
"""
Permissions
"""
AddMyType = 'MyCompany.MyProduct: Add MyType'
profiles/default/factorytool.xml:
<?xml version="1.0"?>
<object name="portal_factory" meta_type="Plone Factory Tool">
<factorytypes>
<type portal_type="MyType"/>
</factorytypes>
</object>
profiles/default/rolemap.xml:
<?xml version="1.0"?>
<rolemap>
<roles>
<role name="MyAuthor"/>
</roles>
<permissions>
<permission name="MyCompany.MyProduct: Add MyType" acquire="True">
<role name="MyAuthor"/>
<role name="Manager"/>
</permission>
</permissions>
</rolemap>
profiles/default/types.xml:
<?xml version="1.0"?>
<object name="portal_types"
meta_type="Plone Types Tool">
<object name="MyType"
meta_type="Factory-based Type Information with dynamic views"/>
</object>
profiles/default/types/MyType.xml:
<?xml version="1.0"?>
<object name="MyType"
meta_type="Factory-based Type Information with dynamic views"
xmlns:i18n="http://xml.zope.org/namespaces/i18n">
<property name="title">MyType</property>
<property name="description">
Some description text which is indeed visible in the types tool
</property>
<property name="content_icon">SomeExisting.png</property>
<property name="content_meta_type">MyType</property>
<property name="product">MyCompany.MyProduct</property>
<property name="factory">addMyType</property>
<property name="immediate_view">mytype_view</property>
<property name="global_allow">True</property>
<property name="filter_content_types">False</property>
<property name="allowed_content_types">
</property>
<property name="allow_discussion">False</property>
<property name="default_view">mytype_view</property>
<property name="view_methods">
<element value="base_view"/>
</property>
<property name="default_view_fallback">False</property>
<alias from="(Default)" to="(dynamic view)"/>
<alias from="index.html" to="(dynamic view)"/>
<alias from="view" to="(selected layout)"/>
<alias from="edit" to="base_edit"/>
<alias from="properties" to="base_metadata"/>
<action title="View"
action_id="view"
category="object"
condition_expr=""
url_expr="string:${object_url}/view"
visible="True">
<permission value="View"/>
</action>
<action title="Edit"
action_id="edit"
category="object"
condition_expr="not:object/##plone_lock_info/is_locked_for_current_user"
url_expr="string:${object_url}/edit"
visible="True">
<permission value="Modify portal content"/>
</action>
</object>
Should not Archetypes take care of creating that missing addMyType method?
What could make this fail?
Is there something obviously missing in my configuration?
The site contains Archtypes-based objects exclusively so far. Will I come into trouble if I add Dexterity-based types now? ''(I'm totally inexperienced with Dexterity)''
Before someone tells me to do so: I created a question in the Plone community forum already; no luck so far. If important information comes in on either page, I'll sync it.

These are the missing parts to make your contenttype addable:
1.) Register the content-directory in MyCompany/MyProduct/configure.zcml by adding:
<include package=".content" />
2.) Add the file MyCompany/MyProduct/content/configure.zcml with this content:
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five"
i18n_domain="MyCompany.MyProduct">
<class class=".mytype.MyType">
<require
permission="zope2.View"
interface="..interfaces.IMyType"
/>
</class>
</configure>
3.) Fix the then occurring syntax-error in MyCompany/MyProduct/content/mytype.py by replacing class MyType(*basecls) with class MyType(ATCTContent).
And last but not least remove the former attempts of making things work. Best would be to outsource the type to a dedicated pckg and create it with zopeskel, imo.
For the view-error occurring after adding a type, feel free to open a new quest ;-)

Related

Plone: Registry dict field with multiple fields values

I have an interface:
class ISomething(Interface):
something = schema.Dict(
title=u"Something",
description=u"Define something.",
key_type=schema.TextLine(title=u"Some Title"),
value_type=schema.Text(title=u"Some Text"))
used to create a form that saves values in registry (ControlPanelFormWrapper, RegistryEditForm).
In registry.xml:
<record name="something">
<field type="plone.registry.field.Dict">
<title>Something</title>
<key_type type="plone.registry.field.TextLine" />
<value_type type="plone.registry.field.Text" />
</field>
</record>
It's working: I can add key-value items {'Some Title': 'Some Text'}.
I need to modify my form to have multiple fields instead of Some text, but keeping the Dict. Example:
{'Some Title': {
'field_1': 'Value 1',
'field_2': 'Value 2'
}
}
I expect this to work then:
registry = getUtility(IRegistry)
reg_something = registry.get("something")
print reg_something['Some Title']['field_1']
>>> Value 1
So, how to change my interface and registry record to have the form updated in this way?
This is described in an article from Mark van Lent:
https://www.vlent.nl/weblog/2011/09/07/dict-list-value-ploneappregistry/
Adjust the registry.xml accordingly, exchange the record-name with yours:
<record name="my.package.example">
<field type="plone.registry.field.Dict">
<title>Verification filesnames</title>
<key_type type="plone.registry.field.TextLine">
<title>Key</title>
</key_type>
<value_type type="plone.registry.field.List">
<title>Value list</title>
<value_type type="plone.registry.field.TextLine">
<title>Values</title>
</value_type>
</value_type>
</field>
<value purge="false" />
See also this question where Luca Fabbri and Gil Forcada each provide alternative approaches, which might be true time-savers on the long term:
Plone- How can I create a control panel for a record in registry that is a dictionary type?
registry.xml in my default profile (imported with an upgrade step):
<registry>
<records interface="my.package.something.ISomethingItems">
<record name="mypackage_multiplesomething">
<field type="plone.registry.field.List">
<title>Something Items</title>
<value_type type="collective.z3cform.datagridfield.DictRow">
<schema>my.package.something.ISomething</schema>
</value_type>
</field>
</record>
</records>
</registry>
In something.py just define the interfaces:
from collective.z3cform.datagridfield import BlockDataGridFieldFactory
from collective.z3cform.datagridfield.registry import DictRow
from plone import api
from plone.app.registry.browser.controlpanel import ControlPanelFormWrapper
from plone.app.registry.browser.controlpanel import RegistryEditForm
from plone.autoform import directives
from zope import schema
from zope.interface import Interface
from zope.interface import implementer
from zope.schema.interfaces import IVocabularyFactory
from zope.schema.vocabulary import SimpleTerm
from zope.schema.vocabulary import SimpleVocabulary
class ISomething(Interface):
id = schema.ASCIILine(
title=u"Something ID",
description=u"Some description."
)
text = schema.Text(
title=u"A text field",
description=u"Human readable text"
)
url = schema.URI(
title=u"An URL",
description=u"Don't forget http:// or https://"
)
class ISomethingItems(Interface):
# the field is the same used in registry.xml
mypackage_multiplesomething = schema.List(
title=u"Something Items",
description=u"Define something items",
value_type=DictRow(title=u"Something", schema=ISomething)
)
directives.widget(mypackage_multiplesomething=BlockDataGridFieldFactory)
Now we can have an edit form (in something.py):
class SomethingItemsEditForm(RegistryEditForm):
schema = ISomethingItems
label = u"Something items definition"
class SomethingItemsView(ControlPanelFormWrapper):
""" Something items edit form """
form = SomethingItemsEditForm
defined as browser page (configure.zcml):
<browser:page
name="something-items-settings"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class=".something.SomethingItemsView"
permission="cmf.ManagePortal"
/>
Easy to get the values from registry using api:
>>> from plone import api
>>> reg_something_items = api.portal.get_registry_record(
'mypackage_somethingitems', interface=ISomethingItems)
[{'id': 'some id', 'text': 'some text', 'url': 'http://something.com'}, {'id': 'some id other', 'text': 'some text other', 'url': 'http://something-other.com'}]
>>> item = reg_something_items[0]
{'id': 'some id', 'text': 'some text', 'url': 'http://something.com'}
>>> item['id']
some id
>>> item['text']
some text
>>> item['url']
http://something.com
If you added an uninstall profile to your product it is a good idea to add registry.xml in it:
<registry>
<record name="my.package.something.ISomethingItems.mypackage_somethingitems"
delete="True" remove="True" />
</registry>
to be sure the registry will be clean after uninstall.
You can check anytime the values you have in registry in SITE/portal_registry (Site Setup -> Configuration Registry)

Change the id of an object in Plone when not published

In our use case, if the title of an unpublished object is modified we would like to change its id according with the new title.
We try to accomplish it using a simple subscriber without success:
ZCML:
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:zcml="http://namespaces.zope.org/zcml">
<subscriber
for="DummyContentType
zope.lifecycleevent.IObjectModifiedEvent"
handler=".subscriber.update_id"
/>
</configure>
Python:
# -*- coding: utf-8 -*-
from plone import api
from plone.i18n.normalizer.interfaces import IIDNormalizer
from zope.component import queryUtility
def update_id(obj, event):
state = api.content.get_state(obj)
if state != 'private':
return
util = queryUtility(IIDNormalizer)
new_id = util.normalize(obj.Title())
api.content.rename(obj=obj, new_id=new_id, safe_id=True)
obj.reindexObject()
When this code run we get an ResourceLockedError exception

Javascript Bundle Definition in Plone 5

I have define my Resource Directory in configure.zcml:
<plone:static
type="plone"
name="stuff.dropdownmenu"
directory="static" />
I have define my JS Resource in registry.xml:
<records
prefix="plone.resources/stuff"
interface='Products.CMFPlone.interfaces.IResourceRegistry'>
<value key="js">++plone++stuff.dropdownmenu/stuff.js</value>
</records>
My Question: What is the right value of the Element-Tag in the Bundle Definition:
<records
prefix="plone.bundles/stuffdropdown"
interface='Products.CMFPlone.interfaces.IBundleRegistry'>
<value key="resources" purge="false">
<element>???<element>
</value>
<value key="enabled">True</value>
</records>
Is it the stuff part of prefix-Attribute or ++plone++stuff.dropdownmenu/stuff.js ?
best regards
I found in CMFPlone registry.xml that the part of prefix is the right option. The Resource Definition is here in the same file: CMFPlone registry.xml

Spring Webflow - How to Get List of FLOW IDs

What is the best way to get the full list of FLOW IDs generated by Spring Webflow?
Here is my configuration:
<webflow:flow-registry id="flowRegistry"
flow-builder-services="flowBuilderServices"
base-path="/WEB-INF/pageFlows">
<webflow:flow-location-pattern value="/**/*-flow.xml"/>
</webflow:flow-registry>
[UPDATE 1] I should clarify that I want to do this in Java code, not by inspecting my configuration.
[UPDATE 2] answer: requestContext.getActiveFlow().getApplicationContext()
List of flow ids can be identified by the way they are defined in flow-registry. By default, flows will be assigned registry identifiers equal to their filenames minus the file extension, unless a registry base path is defined.
Let me explain this with examples:
Scenario 1:
flow-location and base-path is not specified:
<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
<webflow:flow-location path="/WEB-INF/pageFlows/example.xml" />
</webflow:flow-registry>
Flow id: example
Scenario 2:
flow-location-pattern and base-path is not specified :
<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
<webflow:flow-location-pattern value="/WEB-INF/pageFlows/**/*-flow.xml"/>
</webflow:flow-registry>
If you have flows like /WEB-INF/pageFlows/example1-flow.xml, /WEB-INF/pageFlows/example2-flow.xml, flow ids are: example1-flow, example2-flow respectively.
Scenario 3:
Your own id is specified:
<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
<webflow:flow-location path="/WEB-INF/pageFlows/example.xml" id="myExampleId" />
</webflow:flow-registry>
Flow id: myExampleId
Scenario 4:
base-path is specified:
<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices" base-path="/WEB-INF">
<webflow:flow-location path="/pageFlows/example.xml" />
</webflow:flow-registry>
Flows will now be assigned registry identifiers equal to the the path segment between their base path and file name.
Flow id: pageFlows
Scenario 5:
flow-location-pattern and base-path is specified:
<webflow:flow-registry id="flowRegistry" base-path="/WEB-INF">
<webflow:flow-location-pattern value="/**/*-flow.xml" />
</webflow:flow-registry>
Flows will now be assigned registry identifiers equal to the the path segment between their base path and file name.
So if you have flows located in /pageFlows1/example1, /pageFlows2/example2 directories within WEB-INF, flow ids are: pageFlows1, pageFlows2 respectively.
EDIT :
To get flow ids programmatically:
Assuming your flow controller and flowexecutor definitions as below in webflow-config xml file:
<bean name="flowController" class="org.springframework.webflow.executor.mvc.FlowController">
<property name="flowExecutor" ref="flowExecutor" />
</bean>
//flowRegistry is alredy mentioned in your question
<flow:executor id="flowExecutor" registry-ref="flowRegistry">
<flow:repository type="continuation" max-conversations="1" max-continuations="30" />
</flow:executor>
You can retrieve flow definition ids registered as below:
(I am calling this from a Controller which extends AbstractController, thats why you see getServletContext() method)
ApplicationContext context =
(ApplicationContext)getServletContext().getAttribute(
DispatcherServlet.SERVLET_CONTEXT_PREFIX + "yourWebContextName");
FlowController controller = (FlowController)context.getBean("flowController");
FlowExecutorImpl flowExecutorImpl = (FlowExecutorImpl)controller.getFlowExecutor();
FlowDefinitionRegistryImpl flowDefinitionRegistryImpl = (FlowDefinitionRegistryImpl)flowExecutorImpl.getDefinitionLocator();
//Assuming you have log configured
log.info("Registered Flow Ids are:"+flowDefinitionRegistryImpl.getFlowDefinitionIds());
FlowController has access to FlowExecutor(initial point of entry for webflow). FlowExecutor has access to flowDefinitionRegistry where all flows are registered before being served to requests.
Hope this helps.

How to make dexterity modifications available to other clients?

The modified model_source of a dexterity type is not available for other clients until the other client restarts.
Invalidating or clearing the SCHEMA_CACHE is not a working solution: every client seems to have its own SCHEMA_CACHE which afik cannot be cleared or invalidated from another client.
Is there any solution to solve this problem?
The scenario presented here uses ipython as second client. The same can be reproduced through the web by starting two clients: (1) create a dexterity type in client1, and (2) edit the XML Field Model in client2.
I'd like to put this as an issue on https://github.com/plone/plone.dexterity but as for today issues seem not to be available in the plone.dexterity github project
utils.sync()
from Products.CMFCore.utils import getToolByName
from plone.dexterity.fti import DexterityFTI
id = 'mydexteritytype'
plone_site = app.Plone
tool_portal_types = getToolByName(plone_site, 'portal_types')
if tool_portal_types.hasObject(id): tool_portal_types.manage_delObjects(id)
utils.commit()
utils.sync()
fti = DexterityFTI(id)
fti.id = id
data = {}
data['title'] = id
data['i18n_domain'] = 'plone'
data['behaviors'] = "\n".join([
'plone.app.dexterity.behaviors.metadata.IDublinCore',
'plone.app.content.interfaces.INameFromTitle',
])
data['model_source'] = '''
<model xmlns:security="http://namespaces.plone.org/supermodel/security"
xmlns:marshal="http://namespaces.plone.org/supermodel/marshal"
xmlns:form="http://namespaces.plone.org/supermodel/form"
xmlns="http://namespaces.plone.org/supermodel/schema">
<schema>
<field name="original" type="zope.schema.TextLine">
<default>original</default>
<description/>
<title>original</title>
</field>
</schema>
</model>'''
data['klass'] = 'plone.dexterity.content.Container'
data['filter_content_types'] = True
data['icon_expr'] = 'string:${portal_url}/document_icon.png'
fti.manage_changeProperties(**data)
tool_portal_types._setObject(fti.id, fti)
utils.commit()
After running the code above, the new created mydexteritytype is available for all other clients.
The modifications produced by the following code will be only available in the client running the code. All other clients are not aware of the changes.
utils.sync()
from plone.dexterity.interfaces import IDexterityFTI
from zope.component import getUtility
fti = getUtility(IDexterityFTI, name=id)
model_source = '''
<model xmlns:security="http://namespaces.plone.org/supermodel/security"
xmlns:marshal="http://namespaces.plone.org/supermodel/marshal"
xmlns:form="http://namespaces.plone.org/supermodel/form"
xmlns="http://namespaces.plone.org/supermodel/schema">
<schema>
<field name="modified" type="zope.schema.TextLine">
<default>modified</default>
<description/>
<title>modified</title>
</field>
</schema>
</model>'''
fti.manage_changeProperties(model_source=model_source)
from plone.dexterity.schema import SCHEMA_CACHE
SCHEMA_CACHE.invalidate(fti)
SCHEMA_CACHE.clear()
utils.commit()
This pull-request is supposed to fix this issue:
https://github.com/plone/plone.dexterity/pull/137

Resources