web/action.py
changeset 0 b97547f5f1fa
child 254 b1eda3dd844a
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 """abstract action classes for CubicWeb web client
       
     2 
       
     3 :organization: Logilab
       
     4 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     6 """
       
     7 __docformat__ = "restructuredtext en"
       
     8 
       
     9 from cubicweb.common.appobject import AppRsetObject
       
    10 from cubicweb.common.registerers import action_registerer
       
    11 from cubicweb.common.selectors import add_etype_selector, \
       
    12      searchstate_selector, searchstate_accept_one_selector, \
       
    13      searchstate_accept_one_but_etype_selector
       
    14     
       
    15 _ = unicode
       
    16 
       
    17 
       
    18 class Action(AppRsetObject):
       
    19     """abstract action. Handle the .search_states attribute to match
       
    20     request search state. 
       
    21     """
       
    22     __registry__ = 'actions'
       
    23     __registerer__ = action_registerer
       
    24     __selectors__ = (searchstate_selector,)
       
    25     # by default actions don't appear in link search mode
       
    26     search_states = ('normal',) 
       
    27     property_defs = {
       
    28         'visible':  dict(type='Boolean', default=True,
       
    29                          help=_('display the action or not')),
       
    30         'order':    dict(type='Int', default=99,
       
    31                          help=_('display order of the action')),
       
    32         'category': dict(type='String', default='moreactions',
       
    33                          vocabulary=('mainactions', 'moreactions', 'addrelated',
       
    34                                      'useractions', 'siteactions', 'hidden'),
       
    35                          help=_('context where this component should be displayed')),
       
    36     }
       
    37     site_wide = True # don't want user to configuration actions eproperties
       
    38     category = 'moreactions'
       
    39     
       
    40     @classmethod
       
    41     def accept_rset(cls, req, rset, row, col):
       
    42         user = req.user
       
    43         action = cls.schema_action
       
    44         if row is None:
       
    45             score = 0
       
    46             need_local_check = [] 
       
    47             geteschema = cls.schema.eschema
       
    48             for etype in rset.column_types(0):
       
    49                 accepted = cls.accept(user, etype)
       
    50                 if not accepted:
       
    51                     return 0
       
    52                 if action:
       
    53                     eschema = geteschema(etype)
       
    54                     if not user.matching_groups(eschema.get_groups(action)):
       
    55                         if eschema.has_local_role(action):
       
    56                             # have to ckeck local roles
       
    57                             need_local_check.append(eschema)
       
    58                             continue
       
    59                         else:
       
    60                             # even a local role won't be enough
       
    61                             return 0
       
    62                 score += accepted
       
    63             if need_local_check:
       
    64                 # check local role for entities of necessary types
       
    65                 for i, row in enumerate(rset):
       
    66                     if not rset.description[i][0] in need_local_check:
       
    67                         continue
       
    68                     if not cls.has_permission(rset.get_entity(i, 0), action):
       
    69                         return 0
       
    70                     score += 1
       
    71             return score
       
    72         col = col or 0
       
    73         etype = rset.description[row][col]
       
    74         score = cls.accept(user, etype)
       
    75         if score and action:
       
    76             if not cls.has_permission(rset.get_entity(row, col), action):
       
    77                 return 0
       
    78         return score
       
    79     
       
    80     @classmethod
       
    81     def has_permission(cls, entity, action):
       
    82         """defined in a separated method to ease overriding (see ModifyAction
       
    83         for instance)
       
    84         """
       
    85         return entity.has_perm(action)
       
    86     
       
    87     def url(self):
       
    88         """return the url associated with this action"""
       
    89         raise NotImplementedError
       
    90     
       
    91     def html_class(self):
       
    92         if self.req.selected(self.url()):
       
    93             return 'selected'
       
    94         if self.category:
       
    95             return 'box' + self.category.capitalize()
       
    96 
       
    97 class UnregisteredAction(Action):
       
    98     """non registered action used to build boxes. Unless you set them
       
    99     explicitly, .vreg and .schema attributes at least are None.
       
   100     """
       
   101     category = None
       
   102     id = None
       
   103     
       
   104     def __init__(self, req, rset, title, path, **kwargs):
       
   105         Action.__init__(self, req, rset)
       
   106         self.title = req._(title)
       
   107         self._path = path
       
   108         self.__dict__.update(kwargs)
       
   109         
       
   110     def url(self):
       
   111         return self._path
       
   112 
       
   113 
       
   114 class AddEntityAction(Action):
       
   115     """link to the entity creation form. Concrete class must set .etype and
       
   116     may override .vid
       
   117     """
       
   118     __selectors__ = (add_etype_selector, searchstate_selector)
       
   119     vid = 'creation'
       
   120     etype = None
       
   121     
       
   122     def url(self):
       
   123         return self.build_url(vid=self.vid, etype=self.etype)
       
   124 
       
   125 
       
   126 class EntityAction(Action):
       
   127     """an action for an entity. By default entity actions are only
       
   128     displayable on single entity result if accept match.
       
   129     """
       
   130     __selectors__ = (searchstate_accept_one_selector,)
       
   131     schema_action = None
       
   132     condition = None
       
   133     
       
   134     @classmethod
       
   135     def accept(cls, user, etype):
       
   136         score = super(EntityAction, cls).accept(user, etype)
       
   137         if not score:
       
   138             return 0
       
   139         # check if this type of entity has the necessary relation
       
   140         if hasattr(cls, 'rtype') and not cls.relation_possible(etype):
       
   141             return 0
       
   142         return score
       
   143 
       
   144     
       
   145 class LinkToEntityAction(EntityAction):
       
   146     """base class for actions consisting to create a new object
       
   147     with an initial relation set to an entity.
       
   148     Additionaly to EntityAction behaviour, this class is parametrized
       
   149     using .etype, .rtype and .target attributes to check if the
       
   150     action apply and if the logged user has access to it
       
   151     """
       
   152     etype = None
       
   153     rtype = None
       
   154     target = None
       
   155     category = 'addrelated'
       
   156 
       
   157     @classmethod
       
   158     def accept_rset(cls, req, rset, row, col):
       
   159         entity = rset.get_entity(row or 0, col or 0)
       
   160         # check if this type of entity has the necessary relation
       
   161         if hasattr(cls, 'rtype') and not cls.relation_possible(entity.e_schema):
       
   162             return 0
       
   163         score = cls.accept(req.user, entity.e_schema)
       
   164         if not score:
       
   165             return 0
       
   166         if not cls.check_perms(req, entity):
       
   167             return 0
       
   168         return score
       
   169 
       
   170     @classmethod
       
   171     def check_perms(cls, req, entity):
       
   172         if not cls.check_rtype_perm(req, entity):
       
   173             return False
       
   174         # XXX document this:
       
   175         # if user can create the relation, suppose it can create the entity
       
   176         # this is because we usually can't check "add" permission before the
       
   177         # entity has actually been created, and schema security should be
       
   178         # defined considering this
       
   179         #if not cls.check_etype_perm(req, entity):
       
   180         #    return False
       
   181         return True
       
   182         
       
   183     @classmethod
       
   184     def check_etype_perm(cls, req, entity):
       
   185         eschema = cls.schema.eschema(cls.etype)
       
   186         if not eschema.has_perm(req, 'add'):
       
   187             #print req.user.login, 'has no add perm on etype', cls.etype
       
   188             return False
       
   189         #print 'etype perm ok', cls
       
   190         return True
       
   191 
       
   192     @classmethod
       
   193     def check_rtype_perm(cls, req, entity):
       
   194         rschema = cls.schema.rschema(cls.rtype)
       
   195         # cls.target is telling us if we want to add the subject or object of
       
   196         # the relation
       
   197         if cls.target == 'subject':
       
   198             if not rschema.has_perm(req, 'add', toeid=entity.eid):
       
   199                 #print req.user.login, 'has no add perm on subject rel', cls.rtype, 'with', entity
       
   200                 return False
       
   201         elif not rschema.has_perm(req, 'add', fromeid=entity.eid):
       
   202             #print req.user.login, 'has no add perm on object rel', cls.rtype, 'with', entity
       
   203             return False
       
   204         #print 'rtype perm ok', cls
       
   205         return True
       
   206             
       
   207     def url(self):
       
   208         current_entity = self.rset.get_entity(self.row or 0, self.col or 0)
       
   209         linkto = '%s:%s:%s' % (self.rtype, current_entity.eid, self.target)
       
   210         return self.build_url(vid='creation', etype=self.etype,
       
   211                               __linkto=linkto,
       
   212                               __redirectpath=current_entity.rest_path(), # should not be url quoted!
       
   213                               __redirectvid=self.req.form.get('__redirectvid', ''))
       
   214 
       
   215 
       
   216 class LinkToEntityAction2(LinkToEntityAction):
       
   217     """LinkToEntity action where the action is not usable on the same
       
   218     entity's type as the one refered by the .etype attribute
       
   219     """
       
   220     __selectors__ = (searchstate_accept_one_but_etype_selector,)
       
   221