Back to index

plone3  3.1.7
rules.py
Go to the documentation of this file.
00001 from zope.interface import Interface
00002 from zope.interface import implements
00003 
00004 from zope.component import adapts
00005 from zope.component import getUtility
00006 from zope.component import queryMultiAdapter
00007 from zope.component import queryUtility
00008 
00009 from zope.schema.interfaces import IField
00010 from zope.schema.interfaces import ICollection
00011 from zope.schema.interfaces import IFromUnicode
00012 
00013 from zope.app.container.interfaces import INameChooser
00014 
00015 from Acquisition import aq_base
00016 
00017 from Products.CMFCore.interfaces import ISiteRoot
00018 
00019 from Products.GenericSetup.interfaces import IBody
00020 from Products.GenericSetup.interfaces import ISetupEnviron
00021 
00022 from Products.GenericSetup.utils import XMLAdapterBase
00023 from Products.GenericSetup.utils import _getDottedName
00024 from Products.GenericSetup.utils import _resolveDottedName
00025 
00026 from plone.contentrules.engine.interfaces import IRuleStorage
00027 from plone.contentrules.engine.interfaces import IRuleAssignmentManager
00028 from plone.contentrules.engine.assignments import RuleAssignment
00029 
00030 from plone.contentrules.rule.interfaces import IRuleCondition
00031 from plone.contentrules.rule.interfaces import IRuleAction
00032 from plone.contentrules.rule.interfaces import IRuleElement
00033 from plone.contentrules.rule.interfaces import IRuleElementData
00034 
00035 from plone.app.contentrules.exportimport.interfaces import IRuleElementExportImportHandler
00036 from plone.app.contentrules.rule import Rule
00037 from plone.app.contentrules.rule import get_assignments
00038 
00039 def as_bool(string, default=False):
00040     if string is None or not str(string):
00041         return default
00042     return string.lower() == 'true'
00043     
00044 
00045 class PropertyRuleElementExportImportHandler(object):
00046     """Import portlet assignment settings based on zope.schema properties
00047     """
00048     
00049     implements(IRuleElementExportImportHandler)
00050     adapts(Interface)
00051     
00052     def __init__(self, element):
00053         data = IRuleElementData(element)
00054         self.element = element
00055         self.descriptor = getUtility(IRuleElement, name=data.element)
00056         
00057     def import_element(self, node):
00058         
00059         if self.descriptor.schema is None:
00060             return
00061             
00062         for child in node.childNodes:
00063             if child.nodeName == 'property':
00064                 self.import_node(self.descriptor.schema, child)
00065     
00066     def export_element(self, doc, node):
00067         if self.descriptor.schema is None:
00068             return
00069         
00070         for field_name in self.descriptor.schema:
00071             field = self.descriptor.schema[field_name]
00072             
00073             if not IField.providedBy(field):
00074                 continue
00075             
00076             child = self.export_field(doc, field)
00077             node.appendChild(child)
00078             
00079         
00080     # Helper methods
00081     
00082     def import_node(self, interface, child):
00083         """Import a single <property /> node
00084         """
00085         property_name = child.getAttribute('name')
00086         
00087         field = interface.get(property_name, None)
00088         if field is None:
00089             return
00090         
00091         field = field.bind(self.element)
00092         value = None
00093         
00094         # If we have a collection, we need to look at the value_type.
00095         # We look for <element>value</element> child nodes and get the
00096         # value from there
00097         if ICollection.providedBy(field):
00098             value_type = field.value_type
00099             value = []
00100             for element in child.childNodes:
00101                 if element.nodeName != 'element':
00102                     continue
00103                 element_value = self.extract_text(element)
00104                 value.append(self.from_unicode(value_type, element_value))
00105             value = self.field_typecast(field, value)
00106         
00107         # Otherwise, just get the value of the <property /> node
00108         else:
00109             value = self.extract_text(child)
00110             value = self.from_unicode(field, value)
00111             
00112         field.validate(value)
00113         field.set(self.element, value)
00114         
00115     def export_field(self, doc, field):
00116         """Turn a zope.schema field into a node and return it
00117         """
00118         
00119         field = field.bind(self.element)
00120         value = field.get(self.element)
00121         
00122         child = doc.createElement('property')
00123         child.setAttribute('name', field.__name__)
00124         
00125         if value is not None:
00126             if ICollection.providedBy(field):
00127                 for e in value:
00128                     list_element = doc.createElement('element')
00129                     list_element.appendChild(doc.createTextNode(str(e)))
00130                     child.appendChild(list_element)
00131             else:
00132                 child.appendChild(doc.createTextNode(unicode(value)))
00133             
00134         return child
00135         
00136     def extract_text(self, node):
00137         node.normalize()
00138         text = u""
00139         for child in node.childNodes:
00140             if child.nodeType == node.TEXT_NODE:
00141                 text += child.nodeValue
00142         return text
00143         
00144     def from_unicode(self, field, value):
00145         
00146         # XXX: Bool incorrectly omits to declare that it implements
00147         # IFromUnicode, even though it does.
00148         import zope.schema
00149         if IFromUnicode.providedBy(field) or isinstance(field, zope.schema.Bool):
00150             return field.fromUnicode(value)
00151         else:
00152             return self.field_typecast(field, value)
00153     
00154     def field_typecast(self, field, value):
00155         # A slight hack to force sequence types to the right type
00156         typecast = getattr(field, '_type', None)
00157         if typecast is not None:
00158             if not isinstance(typecast, (list, tuple)):
00159                 typecast = (typecast,)
00160             for tc in reversed(typecast):
00161                 if callable(tc):
00162                     try:
00163                         value = tc(value)
00164                         break
00165                     except:
00166                         pass
00167         return value
00168 
00169 class RulesXMLAdapter(XMLAdapterBase):
00170     """In- and exporter for a local portlet configuration
00171     """
00172     implements(IBody)
00173     adapts(ISiteRoot, ISetupEnviron)
00174     
00175     name = 'contentrules'
00176     _LOGGER_ID = 'contentrules'
00177     
00178     def _exportNode(self):
00179         """Export rules
00180         """
00181         node = self._doc.createElement('contentrules')
00182         child = self._extractRules()
00183         if child is not None:
00184             node.appendChild(child)
00185         self._logger.info('Content rules exported')
00186         return node
00187 
00188     def _importNode(self, node):
00189         """Import rules
00190         """
00191         if self.environ.shouldPurge():
00192             self._purgeRules()
00193         self._initRules(node)
00194         self._logger.info('Content rules imported')
00195 
00196     def _purgeRules(self):
00197         """Purge all registered rules
00198         """
00199         storage = queryUtility(IRuleStorage)
00200         if storage is not None:
00201             # If we delete a rule, assignments will be removed as well
00202             for k in list(storage.keys()):
00203                 del storage[k]
00204 
00205     def _initRules(self, node):
00206         """Import rules from the given node
00207         """
00208 
00209         site = self.environ.getSite()
00210         storage = queryUtility(IRuleStorage)
00211         if storage is None:
00212             return
00213 
00214         for child in node.childNodes:
00215             if child.nodeName == 'rule':
00216                 
00217                 rule = None
00218                 name = child.getAttribute('name')
00219                 if name:
00220                     rule = storage.get(name, None)
00221                 
00222                 if rule is None:                    
00223                     rule = Rule()
00224                     
00225                     if not name:
00226                         chooser = INameChooser(storage)
00227                         name = chooser.chooseName(None, rule)
00228                     
00229                     storage[name] = rule
00230                 else:
00231                     # Clear out conditions and actions since we're expecting new ones
00232                     del rule.conditions[:]
00233                     del rule.actions[:]
00234                 
00235                 rule.title = child.getAttribute('title')
00236                 rule.description = child.getAttribute('description')
00237                 rule.event = _resolveDottedName(child.getAttribute('event'))
00238                 
00239                 rule.enabled = as_bool(child.getAttribute('enabled'), True)
00240                 rule.stop = as_bool(child.getAttribute('stop-after'))
00241                 
00242                 # Aq-wrap to enable complex setters for elements below
00243                 # to work
00244                 
00245                 rule = rule.__of__(site)
00246                 
00247                 for rule_config_node in child.childNodes:
00248                     if rule_config_node.nodeName == 'conditions':
00249                         for condition_node in rule_config_node.childNodes:
00250                             if not condition_node.nodeName == 'condition':
00251                                 continue
00252 
00253                             type_ = condition_node.getAttribute('type')
00254                             element_type = getUtility(IRuleCondition, name=type_)
00255                             if element_type.factory is None:
00256                                 continue
00257                             
00258                             condition = element_type.factory()
00259                             
00260                             # Aq-wrap in case of complex setters
00261                             condition = condition.__of__(rule)
00262                             
00263                             handler = IRuleElementExportImportHandler(condition)
00264                             handler.import_element(condition_node)
00265                             
00266                             rule.conditions.append(aq_base(condition))
00267                             
00268                     elif rule_config_node.nodeName == 'actions':
00269                         for action_node in rule_config_node.childNodes:
00270                             if not action_node.nodeName == 'action':
00271                                 continue
00272                             
00273                             type_ = action_node.getAttribute('type')
00274                             element_type = getUtility(IRuleAction, name=type_)
00275                             if element_type.factory is None:
00276                                 continue
00277                             
00278                             action = element_type.factory()
00279                             
00280                             # Aq-wrap in case of complex setters
00281                             action = action.__of__(rule)
00282                             
00283                             handler = IRuleElementExportImportHandler(action)
00284                             handler.import_element(action_node)
00285                             
00286                             rule.actions.append(aq_base(action))
00287                 
00288             elif child.nodeName == 'assignment':
00289                 location = child.getAttribute('location')
00290                 if location.startswith("/"):
00291                     location = location[1:]
00292 
00293                 try:
00294                     container = site.unrestrictedTraverse(str(location))
00295                 except KeyError:
00296                     continue
00297                 
00298                 assignable = IRuleAssignmentManager(container, None)
00299                 if assignable is None:
00300                     continue
00301                     
00302                 name = child.getAttribute('name')
00303                 assignment = assignable.get(name, None)
00304                 if assignment is None:
00305                     assignment = assignable[name] = RuleAssignment(name)
00306                 
00307                 assignment.enabled = as_bool(child.getAttribute('enabled'))
00308                 assignment.bubbles = as_bool(child.getAttribute('bubbles'))
00309                 
00310                 insert_before = child.getAttribute('insert-before')
00311                 if insert_before:
00312                     position = None
00313                     keys = list(assignable.keys())
00314                     
00315                     if insert_before == "*":
00316                         position = 0
00317                     elif insert_before in keys:
00318                         position = keys.index(insert_before)
00319                     
00320                     if position is not None:
00321                         keys.remove(name)
00322                         keys.insert(position, name)
00323                         assignable.updateOrder(keys)
00324                         
00325                 path = '/'.join(container.getPhysicalPath())
00326                 get_assignments(storage[name]).insert(path)
00327                         
00328     def _extractRules(self):
00329         """Extract rules to a document fragment
00330         """
00331         
00332         site = self.environ.getSite()
00333         storage = queryUtility(IRuleStorage)
00334         if storage is None:
00335             return
00336         fragment = self._doc.createDocumentFragment()
00337         
00338         assignment_paths = set()
00339         
00340         for name, rule in storage.items():
00341             rule_node = self._doc.createElement('rule')
00342             
00343             rule_node.setAttribute('name', name)
00344             rule_node.setAttribute('title', rule.title)
00345             rule_node.setAttribute('description', rule.description)
00346             rule_node.setAttribute('event',  _getDottedName(rule.event))
00347             rule_node.setAttribute('enabled', str(rule.enabled))
00348             rule_node.setAttribute('stop-after', str(rule.stop))
00349             
00350             # Aq-wrap so that exporting fields with clever getters or
00351             # vocabularies will work. We also aq-wrap conditions and
00352             # actions below.
00353 
00354             rule = rule.__of__(site)
00355             
00356             # Add conditions
00357             conditions_node = self._doc.createElement('conditions')
00358             for condition in rule.conditions:
00359                 condition_data = IRuleElementData(condition)
00360                 condition = condition.__of__(rule)
00361                 
00362                 condition_node = self._doc.createElement('condition')
00363                 condition_node.setAttribute('type', condition_data.element)
00364                 
00365                 handler = IRuleElementExportImportHandler(condition)
00366                 handler.export_element(self._doc, condition_node)
00367                 conditions_node.appendChild(condition_node)
00368             rule_node.appendChild(conditions_node)
00369             
00370             # Add actions
00371             actions_node = self._doc.createElement('actions')
00372             for action in rule.actions:
00373                 action_data = IRuleElementData(action)
00374                 action = action.__of__(rule)
00375                 
00376                 action_node = self._doc.createElement('action')
00377                 action_node.setAttribute('type', action_data.element)
00378                 
00379                 handler = IRuleElementExportImportHandler(action)
00380                 handler.export_element(self._doc, action_node)
00381                 actions_node.appendChild(action_node)
00382             rule_node.appendChild(actions_node)
00383             
00384             fragment.appendChild(rule_node)
00385             assignment_paths.update(get_assignments(rule))
00386             
00387         # Export assignments last - this is necessary to ensure they
00388         # are orderd properly
00389             
00390         site_path_length = len('/'.join(site.getPhysicalPath()))
00391         for path in assignment_paths:
00392             try:
00393                 container = site.unrestrictedTraverse(path)
00394             except KeyError:
00395                 continue
00396                 
00397             assignable = IRuleAssignmentManager(container, None)
00398             if assignable is None:
00399                 continue
00400             
00401             location = path[site_path_length:]
00402             for name, assignment in assignable.items():
00403                 assignment_node = self._doc.createElement('assignment')
00404                 assignment_node.setAttribute('location', location)
00405                 assignment_node.setAttribute('name', name)
00406                 assignment_node.setAttribute('enabled', str(assignment.enabled))
00407                 assignment_node.setAttribute('bubbles', str(assignment.bubbles))
00408                 fragment.appendChild(assignment_node)
00409 
00410         return fragment
00411 
00412 
00413 def importRules(context):
00414     """Import content rules
00415     """
00416     site = context.getSite()
00417     importer = queryMultiAdapter((site, context), IBody, name=u'plone.contentrules')
00418     if importer is not None:
00419         filename = '%s%s' % (importer.name, importer.suffix)
00420         body = context.readDataFile(filename)
00421         if body is not None:
00422             importer.filename = filename # for error reporting
00423             importer.body = body
00424 
00425 
00426 def exportRules(context):
00427     """Export content rules
00428     """
00429     site = context.getSite()
00430     exporter = queryMultiAdapter((site, context), IBody, name=u'plone.contentrules')
00431     if exporter is not None:
00432         filename = '%s%s' % (exporter.name, exporter.suffix)
00433         body = exporter.body
00434         if body is not None:
00435             context.writeDataFile(filename, body, exporter.mime_type)