Here is one way to automatically trigger the sync via a cron job. The sync will run in a separate process from your standard backend instances, so you must be using ZEO or RelStorage to allow multiple database connections.
In the root of your buildout, create a file cronviews.py.in:
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.SecurityManager import setSecurityPolicy
from Testing.makerequest import makerequest
from Products.CMFCore.tests.base.security import PermissiveSecurityPolicy, OmnipotentUser
from zope.app.publication.interfaces import BeforeTraverseEvent
from zope.event import notify
import transaction
# Get our variables from buildout in order.
site_path = '${site-path}'
view_names = """${views}"""
site_path = site_path.strip().strip('/')
views = [v.strip() for v in view_names.split() if v.strip()]
# Set up a generic manager user.
_policy=PermissiveSecurityPolicy()
_oldpolicy=setSecurityPolicy(_policy)
newSecurityManager(None, OmnipotentUser().__of__(app.acl_users))
# Set a request so that traversal succeeds.
app = makerequest(app)
# Fire a before traversal event so that browser layers get applied.
site = app.restrictedTraverse(site_path)
notify(BeforeTraverseEvent(site, site.REQUEST))
# Try to open the views.
for view in views:
try:
site.restrictedTraverse(view)()
transaction.commit()
except:
import traceback
traceback.print_exc()
Add sections to buildout.cfg to install this script and run it via cron (be sure to replace path/to/site):
[buildout]
parts =
cronviews
sfsync
[cronviews]
recipe = collective.recipe.template
input = ${buildout:directory}/cronviews.py.in
output = ${buildout:directory}/etc/cronviews.py
site-path = path/to/site
views =
@@sf_sync
[sfsync]
recipe = z3c.recipe.usercrontab
times = 00 1 * * *
command = ${buildout:executable} ${buildout:bin-directory}/instance run ${buildout:directory}/etc/cronviews.py
Run the buildout to install the cron job.
Go to the Salesforce Content control panel in Site Setup.
In the Salesforce object Id box, enter the Id of the Salesforce object you want to sync (you can obtain it from the URL when looking at the object in Salesforce). You must also select the content type corresponding to the Salesforce object type of the item.
Click Synchronize Now to sync the single item.
There is also an API to trigger this sync for a particular object from an external system (such as a button on an object in Salesforce). To set it up, go to the Configuration Registry and set a value for the collective.salesforce.content.sync_key key. Then you can run a sync via, e.g., http://path/to/plone/sf_sync?token=TOKEN&types:list=OBJTYPE&sf_object_id=ID where TOKEN is the sync key you configured, OBJTYPE is the Salesforce object type of the object you want to sync, and ID is its Salesforce Id.
Use the behavior adapter:
from collective.salesforce.content.interfaces import ISalesforceObject
sf_object_id = ISalesforceObject(item).sf_object_id
This works as long as item is an item whose type has the ISalesforceObject behavior enabled.
Query the sf_object_id catalog index:
from Products.CMFCore.utils import getToolByName
catalog = getToolByName(context, 'portal_catalog')
res = catalog.searchResults(sf_object_id=my_id)
if res:
item = res[0].getObject()
If you want to set up a Choice field using a vocabulary from Salesforce, you can use the SalesforcePicklist field instead:
<field name="experience" type="collective.salesforce.content.fields.SalesforcePicklist" sf:field="Experience__c">
<title>Experience</title>
<required>False</required>
</field>
The picklist values will be automatically retrieved from Salesforce when needed, and cached in the ZODB.
For a multi-select choice (Set of Choice) that obtains its vocabulary this way, use SalesforceMultiPicklist:
<field name="company_industry" type="collective.salesforce.content.fields.SalesforceMultiPicklist"
form:widget="z3c.form.browser.checkbox.CheckBoxFieldWidget"
sf:field="Industry__c">
<title>Industries</title>
<required>False</required>
</field>
Two events may be raised for an object during sync:
collective.salesforce.content.events.NotFoundInSalesforceEvent
An object event that indicates that this object was not returned by Salesforce when its Dexterity type was synchronized.
There is an included, optional behavior which handles this event:
- collective.salesforce.content.interfaces.IPublishUpdated
- Causes an object to be published after it is updated from Salesforce during a sync.
collective.salesforce.content.events.UpdatedFromSalesforceEvent
An object event that indicates that this object was updated from Salesforce.
There are optional behaviors which handle this event:
- collective.salesforce.content.interfaces.IDeleteNotFound
- Causes an item with this behavior to be deleted from Plone if it is not found during a Salesforce sync.
- collective.salesforce.content.interfaces.IRejectNotFound
- Causes an item to be rejected (in the workflow sense, i.e. made private) if it is not found during a Salesforce sync.
Since the criteria directive is added to the end of the generated SOQL query, it can be abused to specify an ORDER BY clause. For example, this schema:
<schema sf:object="Contact" sf:criteria="ORDER BY Name"></schema>
would result in the following SOQL:
SELECT Id FROM Contact ORDER BY Name
There is not currently any mechanism to control the order in which multiple content types using the ISalesforceObject behavior are synced, aside from triggering the sync multiple times specifying different sets of content types.