web/action.py
changeset 0 b97547f5f1fa
child 254 b1eda3dd844a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/action.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,221 @@
+"""abstract action classes for CubicWeb web client
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb.common.appobject import AppRsetObject
+from cubicweb.common.registerers import action_registerer
+from cubicweb.common.selectors import add_etype_selector, \
+     searchstate_selector, searchstate_accept_one_selector, \
+     searchstate_accept_one_but_etype_selector
+    
+_ = unicode
+
+
+class Action(AppRsetObject):
+    """abstract action. Handle the .search_states attribute to match
+    request search state. 
+    """
+    __registry__ = 'actions'
+    __registerer__ = action_registerer
+    __selectors__ = (searchstate_selector,)
+    # by default actions don't appear in link search mode
+    search_states = ('normal',) 
+    property_defs = {
+        'visible':  dict(type='Boolean', default=True,
+                         help=_('display the action or not')),
+        'order':    dict(type='Int', default=99,
+                         help=_('display order of the action')),
+        'category': dict(type='String', default='moreactions',
+                         vocabulary=('mainactions', 'moreactions', 'addrelated',
+                                     'useractions', 'siteactions', 'hidden'),
+                         help=_('context where this component should be displayed')),
+    }
+    site_wide = True # don't want user to configuration actions eproperties
+    category = 'moreactions'
+    
+    @classmethod
+    def accept_rset(cls, req, rset, row, col):
+        user = req.user
+        action = cls.schema_action
+        if row is None:
+            score = 0
+            need_local_check = [] 
+            geteschema = cls.schema.eschema
+            for etype in rset.column_types(0):
+                accepted = cls.accept(user, etype)
+                if not accepted:
+                    return 0
+                if action:
+                    eschema = geteschema(etype)
+                    if not user.matching_groups(eschema.get_groups(action)):
+                        if eschema.has_local_role(action):
+                            # have to ckeck local roles
+                            need_local_check.append(eschema)
+                            continue
+                        else:
+                            # even a local role won't be enough
+                            return 0
+                score += accepted
+            if need_local_check:
+                # check local role for entities of necessary types
+                for i, row in enumerate(rset):
+                    if not rset.description[i][0] in need_local_check:
+                        continue
+                    if not cls.has_permission(rset.get_entity(i, 0), action):
+                        return 0
+                    score += 1
+            return score
+        col = col or 0
+        etype = rset.description[row][col]
+        score = cls.accept(user, etype)
+        if score and action:
+            if not cls.has_permission(rset.get_entity(row, col), action):
+                return 0
+        return score
+    
+    @classmethod
+    def has_permission(cls, entity, action):
+        """defined in a separated method to ease overriding (see ModifyAction
+        for instance)
+        """
+        return entity.has_perm(action)
+    
+    def url(self):
+        """return the url associated with this action"""
+        raise NotImplementedError
+    
+    def html_class(self):
+        if self.req.selected(self.url()):
+            return 'selected'
+        if self.category:
+            return 'box' + self.category.capitalize()
+
+class UnregisteredAction(Action):
+    """non registered action used to build boxes. Unless you set them
+    explicitly, .vreg and .schema attributes at least are None.
+    """
+    category = None
+    id = None
+    
+    def __init__(self, req, rset, title, path, **kwargs):
+        Action.__init__(self, req, rset)
+        self.title = req._(title)
+        self._path = path
+        self.__dict__.update(kwargs)
+        
+    def url(self):
+        return self._path
+
+
+class AddEntityAction(Action):
+    """link to the entity creation form. Concrete class must set .etype and
+    may override .vid
+    """
+    __selectors__ = (add_etype_selector, searchstate_selector)
+    vid = 'creation'
+    etype = None
+    
+    def url(self):
+        return self.build_url(vid=self.vid, etype=self.etype)
+
+
+class EntityAction(Action):
+    """an action for an entity. By default entity actions are only
+    displayable on single entity result if accept match.
+    """
+    __selectors__ = (searchstate_accept_one_selector,)
+    schema_action = None
+    condition = None
+    
+    @classmethod
+    def accept(cls, user, etype):
+        score = super(EntityAction, cls).accept(user, etype)
+        if not score:
+            return 0
+        # check if this type of entity has the necessary relation
+        if hasattr(cls, 'rtype') and not cls.relation_possible(etype):
+            return 0
+        return score
+
+    
+class LinkToEntityAction(EntityAction):
+    """base class for actions consisting to create a new object
+    with an initial relation set to an entity.
+    Additionaly to EntityAction behaviour, this class is parametrized
+    using .etype, .rtype and .target attributes to check if the
+    action apply and if the logged user has access to it
+    """
+    etype = None
+    rtype = None
+    target = None
+    category = 'addrelated'
+
+    @classmethod
+    def accept_rset(cls, req, rset, row, col):
+        entity = rset.get_entity(row or 0, col or 0)
+        # check if this type of entity has the necessary relation
+        if hasattr(cls, 'rtype') and not cls.relation_possible(entity.e_schema):
+            return 0
+        score = cls.accept(req.user, entity.e_schema)
+        if not score:
+            return 0
+        if not cls.check_perms(req, entity):
+            return 0
+        return score
+
+    @classmethod
+    def check_perms(cls, req, entity):
+        if not cls.check_rtype_perm(req, entity):
+            return False
+        # XXX document this:
+        # if user can create the relation, suppose it can create the entity
+        # this is because we usually can't check "add" permission before the
+        # entity has actually been created, and schema security should be
+        # defined considering this
+        #if not cls.check_etype_perm(req, entity):
+        #    return False
+        return True
+        
+    @classmethod
+    def check_etype_perm(cls, req, entity):
+        eschema = cls.schema.eschema(cls.etype)
+        if not eschema.has_perm(req, 'add'):
+            #print req.user.login, 'has no add perm on etype', cls.etype
+            return False
+        #print 'etype perm ok', cls
+        return True
+
+    @classmethod
+    def check_rtype_perm(cls, req, entity):
+        rschema = cls.schema.rschema(cls.rtype)
+        # cls.target is telling us if we want to add the subject or object of
+        # the relation
+        if cls.target == 'subject':
+            if not rschema.has_perm(req, 'add', toeid=entity.eid):
+                #print req.user.login, 'has no add perm on subject rel', cls.rtype, 'with', entity
+                return False
+        elif not rschema.has_perm(req, 'add', fromeid=entity.eid):
+            #print req.user.login, 'has no add perm on object rel', cls.rtype, 'with', entity
+            return False
+        #print 'rtype perm ok', cls
+        return True
+            
+    def url(self):
+        current_entity = self.rset.get_entity(self.row or 0, self.col or 0)
+        linkto = '%s:%s:%s' % (self.rtype, current_entity.eid, self.target)
+        return self.build_url(vid='creation', etype=self.etype,
+                              __linkto=linkto,
+                              __redirectpath=current_entity.rest_path(), # should not be url quoted!
+                              __redirectvid=self.req.form.get('__redirectvid', ''))
+
+
+class LinkToEntityAction2(LinkToEntityAction):
+    """LinkToEntity action where the action is not usable on the same
+    entity's type as the one refered by the .etype attribute
+    """
+    __selectors__ = (searchstate_accept_one_but_etype_selector,)
+