hooks.py
author Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
Fri, 08 Jan 2010 16:55:52 +0100
branchstable
changeset 96 39b0f2292d0f
parent 74 5deaa2e20bb7
child 92 87b2293a11df
permissions -rw-r--r--
Added tag cubicweb-keyword-version-1.5.0 for changeset 40beb6f7c57d

"""specific hooks for Classification and Keyword entities

:organization: Logilab
:copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
from cubicweb import ValidationError
from cubicweb.server.hooksmanager import Hook
from cubicweb.server.pool import PreCommitOperation
from itertools import chain


class SetDescendantOfKeywordOp(PreCommitOperation):
    def precommit_event(self):
        """transitive closure of ``descendant_of`` relations to current entity"""
        closure = set()
        entity = self.entity
        parent = self.parent
        for parent in chain([parent, entity], parent.iterparents()):
            for child in chain([entity], entity.iterchildren()):
                if child.eid != parent.eid:
                    closure.add((child, parent))
        for child, parent in closure:
            if not parent in child.descendant_of:
                self.session.execute('SET C descendant_of P WHERE C eid %(c)s, P eid %(p)s',
                                     {'c': child.eid, 'p':parent.eid})


class BeforeAddDescendantOf(Hook):
    """check indirect cycle for ``descendant_of`` relation
    """
    events = ('before_add_relation', )
    accepts = ('descendant_of',)

    def call(self, session, fromeid, rtype, toeid):
        entity = session.entity_from_eid(fromeid)
        parent = session.entity_from_eid(toeid)
        parents = set([x.eid for x in chain([parent,], parent.iterparents())])
        children = set([x.eid for x in chain([entity], entity.iterchildren())])
        if children & parents:
            msg = _('detected descendant_of cycle')
            raise ValidationError(fromeid, {'descendant_of': msg})


class AfterAddSubKeywordOf(Hook):
    """sets ``descendant_of`` relation
    """
    events = ('after_add_relation', )
    accepts = ('subkeyword_of',)

    def call(self, session, fromeid, rtype, toeid):
        entity = session.entity_from_eid(fromeid)
        parent = session.entity_from_eid(toeid)
        SetDescendantOfKeywordOp(session, parent=parent, entity=entity)


class SetIncludedInRelationOp(PreCommitOperation):
    """delay this operation to commit to avoid conflict with a late rql query
    already setting the relation
    """
    def precommit_event(self):
        session = self.session
        # test for indirect cycles
        self.check_cycle()
        subkw = session.entity_from_eid(self.fromeid)
        if subkw.included_in:
            kw = session.entity_from_eid(self.toeid)
            if subkw.included_in[0].eid != kw.included_in[0].eid:
                msgid = "keywords %(subkw)s and %(kw)s belong to different classifications"
                raise ValidationError(subkw.eid, {'subkeyword_of': session._(msgid) %
                                                  {'subkw' : subkw.eid, 'kw' : kw.eid}})
        else:
            session.execute('SET SK included_in C WHERE SK eid %(x)s, '
                            'SK subkeyword_of K, K included_in C',
                            {'x': subkw.eid}, 'x')

    def check_cycle(self):
        parents = set([self.toeid])
        parent = self.session.entity_from_eid(self.toeid)
        while parent.subkeyword_of:
            parent = parent.subkeyword_of[0]
            if parent.eid in parents:
                msg = self.session._('detected subkeyword cycle')
                raise ValidationError(self.fromeid, {'subkeyword_of': msg})
            parents.add(parent.eid)


class SetIncludedInRelationHook(Hook):
    """sets the included_in relation on a subkeyword if not already set
    """
    events = ('before_add_relation',)
    accepts = ('subkeyword_of',)

    def call(self, session, fromeid, rtype, toeid):
        # immediate test direct cycles
        if fromeid == toeid:
            msg = session._('keyword cannot be subkeyword of himself')
            raise ValidationError(fromeid, {rtype : msg})
        SetIncludedInRelationOp(session, vreg=self.vreg,
                                fromeid=fromeid, toeid=toeid)


class RemoveDescendantOfRelation(Hook):
    """removes ``descendant_of`` relation

    we delete the relation for entity's parents recursively
    """
    events = ('after_delete_relation',)
    accepts = ('subkeyword_of',)

    def call(self, session, fromeid, rtype, toeid):
        parent = session.entity_from_eid(toeid)
        for parent in chain([parent], parent.iterparents()):
            session.execute('DELETE K descendant_of P WHERE K eid %(k)s, '
                            'P eid %(p)s', {'p':parent.eid, 'k': fromeid})