OK, I am coming up blank and VS Code's code, although it looks plausible, does not work. Could someone point me to some working code for creating a content rule?
Here's a thought: since Plone is spread out over so many repos in GitHub, it would be cool to have a way to search easily through the entire code base...
And yes, I tried searching via GitHub ![]()
Too many requests
You have exceeded a secondary rate limit.
Oh, of course, I can find it using VS Code and explicitly searching in my virtual env folder... a difference with PyCharm, which makes searching a bit easier.
Well, you didn’t say what you tried. It’s pretty easy to do a github search through the entire plone organization, i.e. Code search results · GitHub
(I wouldn’t expect to find the answer here though, since Plone doesn’t come with preconfigured content rules active. Maybe search the collective instead?)
This is what I'd tried: Repository search results · GitHub (I was looking for the code that decides when this Volto viewless is shown)
Ah, it's because I needed to be logged in... what a crappy error message!
I often look at the tests, but in this case the plone.app.contentrules and plone.contentrules tests are not that helpful (at least, they weren't on my first pass through).
Will look at the collective next...
Lots of examples! Code search results · GitHub
A shame plone.api doesn't have methods for creating content rules!
OK, the easiest was to use portal_setup and export the rules I'd created using the UI, and add that to my add-on:
In profiles/default create a file contentrules.xml containing:
<?xml version="1.0" encoding="utf-8"?>
<contentrules>
<rule name="rule-move-note" title="Move new note to notes folder" cascading="False"
description="There should be no notes outside the notes folder"
enabled="True" event="zope.lifecycleevent.interfaces.IObjectAddedEvent"
stop-after="False">
<conditions>
<condition type="plone.conditions.PortalType">
<property name="check_types">
<element>note</element>
</property>
</condition>
</conditions>
<actions>
<action type="plone.actions.Move">
<property name="target_folder">/notes</property>
</action>
</actions>
</rule>
<rule name="rule-move-tool" title="Move new tool to tools folder" cascading="False"
description="There should be no tools outside the tools folder"
enabled="True" event="zope.lifecycleevent.interfaces.IObjectAddedEvent"
stop-after="False">
<conditions>
<condition type="plone.conditions.PortalType">
<property name="check_types">
<element>tool</element>
</property>
</condition>
</conditions>
<actions>
<action type="plone.actions.Move">
<property name="target_folder">/tools</property>
</action>
</actions>
</rule>
<assignment name="rule-move-note" bubbles="True" enabled="True" location=""/>
<assignment name="rule-move-tool" bubbles="True" enabled="True" location=""/>
</contentrules>
Thanks for volunteering to add it!
![]()

You know, something that took GenericSetup XML files and generated the equivalent Python code would be even more generally useful.
That would make a really cool API method (accepts GenericSetup XML and runs it)! ...with lots and lots of security issues, probably...
Why do you need to generate Python code from GenericSetup XML when there is already an import step which reads the XML and applies it to a site?
That would make a really cool API method (accepts GenericSetup XML and runs it)!
That’s essentially what portal_setup.runImportStepFromProfile does, unless I misunderstood.
It would be useful to generate the sample content rule creation code I started out looking for. My brain is not powerful enough to abstract what I need from the code I found in the collective (and from the tests in plone.contentrules and plone.app.contentrules).
...but yeah, GenericSetup is wonderful. I was able to hide that voltobackendwarning viewlet nicely using it.
In profiles/default create viewlets.xml:
<?xml version="1.0" encoding="utf-8"?>
<object>
<hidden manager="plone.globalstatusmessage" skinname="Plone Default">
<viewlet name="voltobackendwarning"/>
</hidden>
</object>
The code of the genericsetup handler for content rules is the python code you’re searching for:
Doesnt that require a (defined) profile. If he just wants to import one xml file, would that work? Maybe there is some code that is used in ‘portal_setup’ advanced import that can be used ?
I did a quick test, and it might be possible, at least this worked:
# -*- coding: utf-8 -*-
from plone import api
from plone.restapi.services import Service
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.exportimport.catalog import importCatalogTool
from Products.GenericSetup.context import SnapshotImportContext
from io import BytesIO
# from plone.protect.interfaces import IDisableCSRFProtection
# from zope.interface import alsoProvides
class XmlImport(Service):
def reply(self):
# alsoProvides(self.request, IDisableCSRFProtection)
xml_data = b"""\
<?xml version="1.0"?>
<object name="portal_catalog" meta_type="Plone Catalog Tool">
<index name="my_index" meta_type="FieldIndex" />
</object>
"""
portal = api.portal.get()
setup = getToolByName(portal, "portal_setup")
# Create a fake import context that returns our XML
class InlineImportContext(SnapshotImportContext):
def readDataFile(self, filename):
if filename == "catalog.xml":
return xml_data
return None
context = InlineImportContext(setup, "utf-8")
importCatalogTool(context) # now only one argument
self.request.response.setStatus(200)
return {
"status": "success",
"message": "Catalog XML imported from inline data."
}