Event subscriber for when Plone site is ready

I have some code that I want to run when the Plone site is started, and I need to be able to access the portal (anywhere I've tried to put the code I get CannotGetPortalError)

It would be nice if there was an event triggered when the Plone site is first loaded, but I could not find one. Anyone know of one I could hook in to?

I only need this code to run once after the site has been started. Specifically, I'm trying to access portal_registry and portal_catalog.

Please describe your particular usecase.

You are looking for an event after an instance restart? Where is something in the Zope core.
But I can hardly imagine a different usecase as described...some more details might be helpful.

Try IDatabaseOpenedWithRoot , though you'll have to call setSite with your Plone object before plone.api.portal.get will work.

As @jaroel suggest you might need to use setSite:

from plone import api
from zope.component.hooks import setSite

# Sets the current site as the active site
setSite(app['Plone'])

# Enable the context manager to switch the user
with api.env.adopt_user(username="admin"):
    # You're now posing as admin!
    portal.restrictedTraverse("manage_propertiesForm")

Example taken by https://docs.plone.org/develop/plone/misc/commandline.html#id11
If your goal is to get those two tools, it will be probably simpler to just use the dear old getToolByName to get them.

I have a subscriber set up as:

  <subscriber
  for="zope.processlifetime.IDatabaseOpenedWithRoot"
  handler=".events.site_load"
  />

and I've put a pdb in site_load, but I don't appear to have access to the Plone object. I'm trying to do this in an add-on, not as a script, so I don't have app.

I can set the code to be run at different times and make it work, I was just hoping this would be possible that the code would only be hit once.

Please describe your particular usecase.

I have a behavior that creates fields dynamically, as the instance is initialized. Based on the fields that are added, I also need to build a vocabulary and catalog indexes.

I can instead check the vocabulary and catalog bits as different actions happen in the site, but it would be nice if I could make the code run only once after the instance is started.

Here's some code I found in a Zope test which shows how to open a db connection and get app from the IDatabaseOpenedWithRoot event:

def logevent(event):
    logger = logging.getLogger('Zope2.App.test_startup')
    logger.info(event.__class__)
    db = event.database
    logger.info(db.__class__)
    conn = db.open()
    try:
        try:
            root = conn.root()
            app = root['Application']
            logger.info(app.__class__)
        except KeyError:
            logger.info('Root not ready.')
    finally:
        conn.close()

Another probably crazy idea: maybe you could register an IBeforeTraverseEvent handler for ISiteRoot which unregisters itself once it's done.

Make sure you think through what happens if there are multiple instances and only one of them restarts. The changes to catalog indexes made by the newly started instance would also be available to the one that didn't restart.

Are catalog indexes something that you create once and forever...wouldn't that belong into an upgrade step or something similar? Keep in mind that creating an index and reindexing might taking some time depending on the size of the catalog. I would implement such a functionality as an explicit step triggered manually or as part of an automatic setup or deployment?!

For the vocabulary part: can't you create a vocabulary either during startup or at runtime? In some projects we Excel files from which we generated named vocabularies (configured through ZCML) during the startup phase of Zope or Plone. Also in some case we register a dynamic source for vocabularies and calculate the vocabulary at runtime.

-aj

Are catalog indexes something that you create once and forever…wouldn’t that belong into an upgrade step or something similar?

An upgrade step wouldn't necessarily work, because new values can be added at any time, it will be an automatic process. My code just checks if the index exists, and creates one if it isn't in the catalog. No reindexing or anything (since it would be a new field in the site, it wouldn't have any content yet).

For the vocabulary part: can’t you create a vocabulary either during startup or at runtime?

yes, it can happen at runtime. I originally had it that way, but was trying to streamline the code a bit.

Here’s some code I found in a Zope test which shows how to open a db connection and get app from the IDatabaseOpenedWithRoot event

That got me what I need, thanks! I'd found some other code that got me the db, but then I didn't know where to go from there.

For reference, here is the final code I put in for the subscriber:

from plone import api  
from zope.component.hooks import setSite


def site_load(event):
    conn = event.database.open()
    app = conn.root()['Application']
    setSite(app['Plone'])
    portal = api.portal.get()
    ...
1 Like

Make sure you close the connection when you're done. This lives outside of the normal publisher request cycle so it's your responsibility to do that so that you aren't permanently holding one of the connections from the connection pool.

1 Like