class UUIDAware(Behavior): uuid = default(None) overwrite_recursiv_on_copy = default(True) @plumb def __init__(_next, self, *args, **kw): _next(self, *args, **kw) self.uuid = uuid.uuid4() @plumb def copy(_next, self): raise RuntimeError(u"Shallow copy useless on UUID aware node trees, " u"use deepcopy.") @plumb def deepcopy(_next, self): copied = _next(self) self.set_uuid_for(copied, True, self.overwrite_recursiv_on_copy) return copied @default def set_uuid_for(self, node, override=False, recursiv=False): if IUUIDAware.providedBy(node): if override or not node.uuid: node.uuid = uuid.uuid4() if recursiv: for child in node.values(): self.set_uuid_for(child, override, recursiv)
class ProtectedAttributesForm(Behavior): """Plumbing behavior supposed to be used for yafowil forms calculating widget modes based on security checks. Security declarations for attributes are stored at ``self.attribute_permissions`` containing the attribute names as key, and a 2-tuple containing required edit and view permission for this attribute. """ attribute_permissions = default(dict()) attribute_default_permissions = default(('edit', 'view')) @default def mode_for(self, name): """Calculate mode by checking permission defined in ``self.attribute_permissions`` for attribute ``name`` against model. If no permissions defined for attribute name, ``self.attribute_default_permissions`` is used. """ permissions = self.attribute_permissions.get(name) if not permissions: permissions = self.attribute_default_permissions if has_permission(permissions[0], self.model, self.request): return 'edit' if has_permission(permissions[1], self.model, self.request): return 'display' return 'skip'
def test_default(self): # First default wins from left to right def1 = default(1) self.assertTrue(def1 + def1 is def1) def2 = default(2) self.assertTrue(def1 + def2 is def1) self.assertTrue(def2 + def1 is def2) # Override wins over default ext3 = override(3) self.assertTrue(def1 + ext3 is ext3) # Finalize wins over default fin4 = finalize(4) self.assertTrue(def1 + fin4 is fin4) # Adding with something else than default/override, raises # ``PlumbingCollision`` err = None try: def1 + Instruction('foo') except PlumbingCollision as e: err = e finally: self.assertEqual(err.left.__class__.__name__, 'default') self.assertEqual(err.left.payload, 1) self.assertEqual(err.right.__class__.__name__, 'Instruction') self.assertEqual(err.right.payload, 'foo')
class Behavior2(Behavior1): # overrides ``J`` of ``Behavior1`` J = default('Behavior2') L = default('Behavior2') # this one wins, even if ``M`` on superclass is ``override`` # instruction due to ordinary inheritance behavior. M = default('Behavior2')
class ContentForm(FormHeading): """Form behavior rendering to content area. """ show_heading = default(True) show_contextmenu = default(True) @default @property def form_heading(self): return _('content_form_heading', default='Content Form Heading') @default @property def rendered_contextmenu(self): return render_tile(self.model, self.request, 'contextmenu') @plumb def __call__(_next, self, model, request): ajax_form_fiddle(request, '#content', 'inner') form = _next(self, model, request) if not form: form = u'' self.rendered_form = form path = self.path if not path: path = 'cone.app.browser:templates/content_form.pt' return render_template(path, request=request, model=model, context=self)
class ZODBAttributes(Behavior): attribute_access_for_attrs = default(False) attributes_factory = default(None) @finalize @property def attrs(self): try: attrs = self._attrs except AttributeError: self._attrs = attrs = self.attributes_factory(name='_attrs', parent=self) if self.attribute_access_for_attrs: return AttributeAccess(attrs) return attrs
class SQLRowStorage(Behavior): # SQL alchemy model class record_class = default(None) # SQL alchemy session session = default(None) @override def __init__(self, name=None, parent=None, record=None): self.__name__ = name self.__parent__ = parent self._new = False if record is None: self._new = True record = self.record_class() self.record = record @override def attributes_factory(self, name, parent): return SQLRowNodeAttributes(name, parent, self.record) @finalize def __setitem__(self, name, value): raise KeyError(name) @finalize def __getitem__(self, name): raise KeyError(name) @finalize def __delitem__(self, name): # pragma: no cover # not reached by default SQLRowNode, KeyError already thrown in # Lifecycle plumbing Behavior raise KeyError(name) @finalize def __iter__(self): return iter([]) @finalize def __call__(self): session = self.session if self._new: session.add(self.record) self._new = False if use_tm(): self.session.flush() else: self.session.commit()
def test_finalize(self): # First override wins against following equal overrides and arbitrary # defaults fin1 = finalize(1) self.assertTrue(fin1 + fin1 is fin1) self.assertTrue(fin1 + finalize(1) is fin1) self.assertTrue(fin1 + default(2) is fin1) self.assertTrue(fin1 + override(2) is fin1) # Two unequal finalize collide err = None try: fin1 + finalize(2) except PlumbingCollision as e: err = e finally: self.assertEqual(err.left.__class__.__name__, 'finalize') self.assertEqual(err.left.payload, 1) self.assertEqual(err.right.__class__.__name__, 'finalize') self.assertEqual(err.right.payload, 2) # Everything except default/override collides try: fin1 + Instruction(1) except PlumbingCollision as e: err = e finally: self.assertEqual(err.left.__class__.__name__, 'finalize') self.assertEqual(err.left.payload, 1) self.assertEqual(err.right.__class__.__name__, 'Instruction') self.assertEqual(err.right.payload, 1)
class AppNode(Behavior): node_info_name = default('') @default @property def __acl__(self): return acl_registry.lookup(self.__class__, self.node_info_name) @default @instance_property def properties(self): props = Properties() props.in_navtree = False return props @default @instance_property def metadata(self): name = self.name if not name: name = _('no_title', default='No Title') metadata = Metadata() metadata.title = name return metadata @default @property def nodeinfo(self): info = get_node_info(self.node_info_name) if not info: info = NodeInfo() info.title = str(self.__class__) info.node = self.__class__ info.icon = app_config().default_node_icon return info
class OwnerSupport(Behavior): """Plumbing behavior providing ownership information. """ @plumb def __init__(_next, self, *args, **kw): _next(self, *args, **kw) if not self.owner: request = get_current_request() self.owner = authenticated_userid(request) @plumb @property def __acl__(_next, self): acl = _next(self) if self.owner: for ace in acl: if ace[1] == 'role:owner': return [(Allow, self.owner, ace[2])] + acl return acl def _get_owner(self): return self.attrs.get('owner') def _set_owner(self, value): self.attrs['owner'] = value owner = default(property(_get_owner, _set_owner))
class PrincipalACL(Behavior): """Plumbing behavior providing principal ACL's. Warning: This behavior works only for nodes defining the ``__acl__`` attribute as property function. Plumber does not support class property plumbing (yet). """ role_inheritance = default(False) @default @property def principal_roles(self): raise NotImplementedError(u"Abstract ``PrincipalACL`` does not " u"implement ``principal_roles``.") @default @property def aggregated_roles(self): aggregated = dict() model = self while model: if not IPrincipalACL.providedBy(model): model = model.parent continue for id, roles in model.principal_roles.items(): if aggregated.get(id): aggregated[id].update(roles) else: aggregated[id] = set(roles) model = model.parent return aggregated @default def aggregated_roles_for(self, principal_id): return list(self.aggregated_roles.get(principal_id, list())) @plumb @property def __acl__(_next, self): base_acl = _next(self) acl = list() if self.role_inheritance: principal_roles = self.aggregated_roles else: principal_roles = self.principal_roles for id, roles in principal_roles.items(): aggregated = set() for role in roles: aggregated.update(self._permissions_for_role(base_acl, role)) acl.append((Allow, id, list(aggregated))) for ace in base_acl: acl.append(ace) return acl @default def _permissions_for_role(self, acl, role): for ace in acl: if ace[1] == 'role:%s' % role: return ace[2] return list()
class SubscriberDecoratorBehavior(Behavior): attr = EventAttribute() @attr.subscriber def attr_changed(self, value): self.changed_value = value attr = default(attr)
class UUIDAttributeAware(UUIDAware): def _get_uuid(self): return self.attrs['uuid'] def _set_uuid(self, value): self.attrs['uuid'] = value uuid = default(property(_get_uuid, _set_uuid))
class Lifecycle(Behavior): events = default({ 'created': NodeCreatedEvent, 'added': NodeAddedEvent, 'modified': NodeModifiedEvent, 'removed': NodeRemovedEvent, 'detached': NodeDetachedEvent, }) _notify_suppress = default(False) @plumb def __init__(_next, self, *args, **kw): _next(self, *args, **kw) objectEventNotify(self.events['created'](self)) @plumb def __setitem__(_next, self, key, val): _next(self, key, val) if self._notify_suppress: return objectEventNotify(self.events['added'](val, newParent=self, newName=key)) @plumb def __delitem__(_next, self, key): delnode = self[key] _next(self, key) if self._notify_suppress: return objectEventNotify(self.events['removed'](delnode, oldParent=self, oldName=key)) @plumb def detach(_next, self, key): self._notify_suppress = True node = _next(self, key) self._notify_suppress = False objectEventNotify(self.events['detached'](node, oldParent=self, oldName=key)) return node
class Attributes(Behavior): attribute_access_for_attrs = default(False) attributes_factory = default(NodeAttributes) @finalize @property def attrs(self): try: attrs = self.nodespaces['__attrs__'] except KeyError: attrs = self.nodespaces['__attrs__'] = \ self.attributes_factory(name='__attrs__', parent=self) if self.attribute_access_for_attrs: return AttributeAccess(attrs) return attrs # BBB attributes = finalize(attrs)
class PrincipalBehavior(Behavior): record = default(None) """Reference to sqlalchemy record instance.""" @override def __init__(self, parent, record): self.__parent__ = parent self.record = record @default @property def __name__(self): return self.id @default @property def ugm(self): return self.parent.parent @default @property def id(self): return self.record.id @default def add_role(self, role): if role not in self.own_roles: self.own_roles = self.own_roles + [role] @default def remove_role(self, role): if role in self.own_roles: # to trigger the json field self.own_roles = [r for r in self.own_roles if r != role] @property def own_roles(self): return self.record.principal_roles @default @own_roles.setter def own_roles(self, roles): self.record.principal_roles = roles @default @property def roles(self): return self.own_roles @default def __call__(self): if use_tm(): self.session.flush() else: self.session.commit()
class NodeChildValidate(Behavior): allow_non_node_childs = default(False) @plumb def __setitem__(_next, self, key, val): if not self.allow_non_node_childs and inspect.isclass(val): raise ValueError(u"It isn't allowed to use classes as values.") if not self.allow_non_node_childs and not INode.providedBy(val): raise ValueError("Non-node childs are not allowed.") _next(self, key, val)
class YAMLForm(Behavior): """Plumbing behavior for rendering yaml forms. """ action_resource = default(u'') # B/C form_template_path = default(None) # use form_template for pointing yaml files form_template = default(None) # considered in form_action, either 'add' or 'edit' form_flavor = default('edit') @default @property def message_factory(self): return None @default def form_action(self, widget, data): resource = self.action_resource if self.form_flavor == 'add': return make_url(self.request, node=self.model.parent, resource=resource) return make_url(self.request, node=self.model, resource=resource) @override def prepare(self): if self.form_template: self.form = parse_from_YAML(self.form_template, self, self.message_factory) return # BBB self.form = parse_from_YAML(self.form_template_path, self, self.message_factory)
class Nodespaces(Behavior): _nodespaces = default(None) @finalize @property def nodespaces(self): """A storage and general way to access our nodespaces. An ``AttributedNode`` uses this to store the ``attrs`` nodespace i.e. """ if self._nodespaces is None: self._nodespaces = odict() self._nodespaces['__children__'] = self return self._nodespaces @plumb def __getitem__(_next, self, key): # blend in our nodespaces as children, with name __<name>__ # isinstance check is required because odict tries to get item possibly # with ``_nil`` key, which is actually an object if isinstance(key, basestring) \ and key.startswith('__') \ and key.endswith('__'): # a reserved child key mapped to the nodespace behind # nodespaces[key], nodespaces is an odict return self.nodespaces[key] return _next(self, key) @plumb def __setitem__(_next, self, key, val): # blend in our nodespaces as children, with name __<name>__ if key.startswith('__') and key.endswith('__'): # a reserved child key mapped to the nodespace behind # nodespaces[key], nodespaces is an odict val.__name__ = key val.__parent__ = self self.nodespaces[key] = val # index checks below must not happen for other nodespace. return _next(self, key, val) @plumb def __delitem__(_next, self, key): # blend in our nodespaces as children, with name __<name>__ if key.startswith('__') and key.endswith('__'): # a reserved child key mapped to the nodespace behind # nodespaces[key], nodespaces is an odict del self.nodespaces[key] return _next(self, key)
class LDAPGroups(LDAPGroupsMapping): principal_factory = default(Group) @override @locktree def __delitem__(self, key): key = decode_utf8(key) group = self[key] parent = self.parent if parent and parent.rcfg is not None: for role in group.roles: group.remove_role(role) del self.context[key]
class LDAPGroups(LDAPGroupsMapping): principal_factory = default(Group) @override @locktree def __delitem__(self, key): group = self[key] parent = self.parent if parent and parent.rcfg is not None: for role in group.roles: group.remove_role(role) context = group.context del context.parent[context.name] del self.storage[key]
class WorkflowState(Behavior): """Behavior for nodes providing workflow states. This implementation persists to self.attrs['state'] """ workflow_tsf = default(None) workflow_name = default(None) @plumb def __init__(_next, self, *args, **kw): _next(self, *args, **kw) initialize_workflow(self) @plumb def copy(_next, self): """Set initial state for copied node and all children providing ``cone.app.interfaces.IWorkflowState``. """ ret = _next(self) def recursiv_initial_state(node): if IWorkflowState.providedBy(node): initialize_workflow(node, force=True) for child in node.values(): recursiv_initial_state(child) recursiv_initial_state(ret) return ret @property def state(self): return self.attrs.get('state', None) @default @state.setter def state(self, val): self.attrs['state'] = val
class Alias(Behavior): aliaser = default(None) @plumb def __getitem__(_next, self, key): if self.aliaser: unaliased_key = self.aliaser.unalias(key) else: unaliased_key = key try: return _next(self, unaliased_key) except KeyError: raise KeyError(key) @plumb def __setitem__(_next, self, key, val): if self.aliaser: unaliased_key = self.aliaser.unalias(key) else: unaliased_key = key try: _next(self, unaliased_key, val) except KeyError: raise KeyError(key) @plumb def __delitem__(_next, self, key): if self.aliaser: unaliased_key = self.aliaser.unalias(key) else: unaliased_key = key try: _next(self, unaliased_key) except KeyError: raise KeyError(key) @plumb def __iter__(_next, self): for key in _next(self): try: if self.aliaser: yield self.aliaser.alias(key) else: yield key except KeyError: if IEnumerableMapping.providedBy(self.aliaser): # an enumerable aliaser whitelists, we skip non-listed keys continue raise
class WorkflowACL(Behavior): """Behavior providing ACL's by worfklow state. Requires ``WorkflowState`` behavior. """ state_acls = default(dict()) default_acl = default([ (Allow, 'system.Authenticated', ['view']), (Allow, 'role:viewer', ['view']), (Allow, 'role:editor', ['view', 'add', 'edit']), (Allow, 'role:owner', [ 'view', 'add', 'edit', 'delete', 'change_state', 'manage_permissions' ]), (Allow, 'role:admin', [ 'view', 'add', 'edit', 'delete', 'change_state', 'manage_permissions' ]), (Allow, 'role:manager', [ 'view', 'add', 'edit', 'delete', 'change_state', 'manage_permissions', 'manage' ]), (Allow, Everyone, ['login']), (Deny, Everyone, ALL_PERMISSIONS), ]) @override @property def __acl__(self): acl = self.state_acls.get(self.state, self.default_acl) if not acl: raise ValueError(u"No ACL found for state '%s'" % self.state) if acl is self.default_acl: logger.warning(u"No ACL found for state " u"'%s'. Using default" % self.state) return acl
class GroupsBehavior(PrincipalsBehavior, BaseGroups): record_class = default(SQLGroup) @default def create(self, _id, **kw): for name, value in kw.items(): if value and name in self.ugm.binary_attrs: kw[name] = base64.b64encode(value).decode() sqlgroup = SQLGroup(id=_id, data=kw) self.session.add(sqlgroup) self.session.flush() return self[_id] @default def __getitem__(self, id, default=None): try: sqlgroup = self.session\ .query(SQLGroup)\ .filter(SQLGroup.id == id)\ .one() except NoResultFound: raise KeyError(id) return Group(self, sqlgroup) @default def __delitem__(self, id): try: sqlgroup = self.session\ .query(SQLGroup)\ .filter(SQLGroup.id == id)\ .one() self.session.delete(sqlgroup) except NoResultFound: raise KeyError(id) @default def __iter__(self): groups = self.session.query(SQLGroup) return iter(map(lambda u: u.id, groups)) @default def __setitem__(self, key, value): msg = 'groups can only be added using the create() method' raise NotImplementedError(msg) @default def invalidate(self, key=None, *a, **kw): self.parent.invalidate(key='groups')
class ChildFactory(Behavior): factories = default(odict()) @override def __iter__(self): return self.factories.__iter__() iterkeys = override(__iter__) @plumb def __getitem__(_next, self, key): try: child = _next(self, key) except KeyError: child = self.factories[key]() self[key] = child return child
class NamespaceUUID(Behavior): """Behavior calculating ``uuid`` by node path and namespace. """ uuid_namespace = default(uuid.UUID('83438507-fdff-45a2-af47-1e001884eab9')) @property def uuid(self): if self.__name__ is None and self.__parent__ is None: return None return uuid.uuid5(self.uuid_namespace, '/'.join([_ for _ in self.path if _ is not None])) @finalize @uuid.setter def uuid(self, uuid): msg = 'Ignore attempt to set {}.uuid'.format(self.__class__.__name__) raise NotImplementedError(msg)
class AppNode(Behavior): node_info_name = default('') @default @property def __acl__(self): return acl_registry.lookup(self.__class__, self.node_info_name) @default @property def layout(self): props = self.properties if props.default_child: return self[props.default_child].layout # XXX: consider adding and return add model layout here? return get_current_registry().getAdapter(self, ILayout) @default @instance_property def properties(self): props = Properties() props.in_navtree = False return props @default @instance_property def metadata(self): name = self.name if not name: name = _('no_title', default='No Title') metadata = Metadata() metadata.title = name return metadata @default @property def nodeinfo(self): info = get_node_info(self.node_info_name) if not info: info = NodeInfo() info.title = str(self.__class__) info.node = self.__class__ info.icon = app_config().default_node_icon return info
class RelatedViewProvider(Behavior): """Plumbing behavior for providing related view name. This behavior can be applied on tiles and is supposed to be used to set a related view name for the current request. This related view name can be used then by nested tiles to create browser URLs. """ related_view = default(None) """Related view name to set on request. """ @plumb def __call__(_next, self, model, request): """Set related view on request and call downstream function. """ set_related_view(request, self.related_view) return _next(self, model, request)
class Fallback(Behavior): fallback_key = default(_marker) @plumb def __getitem__(_next, self, key): """If key not found, look for fallback_key on parent(s) with the same subpath, take it's children and look there, fall back to unvisited parents until no fallback left. """ try: value = _next(self, key) except KeyError: with fallback_processing() as count: if count > 0: raise value = _to_root(self, path=self.path + [key], visited=set()) if value is _marker: raise return value
def test_override(self): # First override wins against following equal overrides and arbitrary # defaults ext1 = override(1) self.assertTrue(ext1 + ext1 is ext1) self.assertTrue(ext1 + override(1) is ext1) self.assertTrue(ext1 + override(2) is ext1) self.assertTrue(ext1 + default(2) is ext1) fin3 = finalize(3) self.assertTrue(ext1 + fin3 is fin3) # Everything except default/override collides err = None try: ext1 + Instruction(1) except PlumbingCollision as e: err = e finally: self.assertEqual(err.left.__class__.__name__, 'override') self.assertEqual(err.left.payload, 1) self.assertEqual(err.right.__class__.__name__, 'Instruction') self.assertEqual(err.right.payload, 1)
except KeyError: # No attributes loaded, yet - cannot be changed pass childs = getattr(self, '_deleted', []) if self._keys is not None: childs.extend( filter(lambda x: x is not None, self._keys.values())) for child in childs: if child.changed: return self._changed = False # And propagate to parent if self._changed is not oldval and self.parent is not None: self.parent.changed = self._changed changed = default(property(_get_changed, _set_changed)) # This is really ldap @default def child_dn(self, key): return self._child_dns[key] @default @debug def search(self, queryFilter=None, criteria=None, attrlist=None, relation=None, relation_node=None, exact_match=False, or_search=False): attrset = set(attrlist or []) attrset.discard('dn') # fetch also the key attribute if not self._key_attr == 'rdn':