web/views/actions.py
changeset 1808 aa09e20dd8c0
parent 1551 a41c1c0a9e13
child 1829 f54710fcdd62
equal deleted inserted replaced
1693:49075f57cf2c 1808:aa09e20dd8c0
     4 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     4 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     6 """
     6 """
     7 __docformat__ = "restructuredtext en"
     7 __docformat__ = "restructuredtext en"
     8 
     8 
     9 from cubicweb.common.selectors import (searchstate_accept, match_user_group, yes,
     9 from cubicweb.vregistry import objectify_selector
    10                                        one_line_rset, two_lines_rset, one_etype_rset,
    10 from cubicweb.selectors import (EntitySelector,
    11                                        authenticated_user, none_rset,
    11     one_line_rset, two_lines_rset, one_etype_rset, relation_possible,
    12                                        match_search_state, chainfirst, chainall)
    12     non_final_entity,
    13 
    13     authenticated_user, match_user_groups, match_search_state,
    14 from cubicweb.web.action import Action, EntityAction,  LinkToEntityAction
    14     has_permission, has_add_permission,
    15 from cubicweb.web.views import linksearch_select_url, linksearch_match
    15     )
    16 from cubicweb.web.views.baseviews import vid_from_rset
    16 from cubicweb.web.action import Action
       
    17 from cubicweb.web.views import linksearch_select_url, vid_from_rset
       
    18 from cubicweb.web.views.autoform import AutomaticEntityForm
    17 
    19 
    18 _ = unicode
    20 _ = unicode
    19 
    21 
       
    22 
       
    23 class has_editable_relation(EntitySelector):
       
    24     """accept if some relations for an entity found in the result set is
       
    25     editable by the logged user.
       
    26 
       
    27     See `EntitySelector` documentation for behaviour when row is not specified.
       
    28     """
       
    29 
       
    30     def score_entity(self, entity):
       
    31         # if user has no update right but it can modify some relation,
       
    32         # display action anyway
       
    33         for dummy in AutomaticEntityForm.esrelations_by_category(
       
    34             entity, 'generic', 'add'):
       
    35             return 1
       
    36         for rschema, targetschemas, role in AutomaticEntityForm.erelations_by_category(
       
    37             entity, ('primary', 'secondary'), 'add'):
       
    38             if not rschema.is_final():
       
    39                 return 1
       
    40         return 0
       
    41 
       
    42 @objectify_selector
       
    43 def match_searched_etype(cls, req, rset, **kwargs):
       
    44     return req.match_search_state(rset)
       
    45 
       
    46 @objectify_selector
       
    47 def view_is_not_default_view(cls, req, rset, **kwargs):
       
    48     # interesting if it propose another view than the current one
       
    49     vid = req.form.get('vid')
       
    50     if vid and vid != vid_from_rset(req, rset, cls.schema):
       
    51         return 1
       
    52     return 0
       
    53 
       
    54 @objectify_selector
       
    55 def addable_etype_empty_rset(cls, req, rset, **kwargs):
       
    56     if rset is not None and not rset.rowcount:
       
    57         rqlst = rset.syntax_tree()
       
    58         if len(rqlst.children) > 1:
       
    59             return 0
       
    60         select = rqlst.children[0]
       
    61         if len(select.defined_vars) == 1 and len(select.solutions) == 1:
       
    62             rset._searched_etype = select.solutions[0].itervalues().next()
       
    63             eschema = cls.schema.eschema(rset._searched_etype)
       
    64             if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
       
    65                    and eschema.has_perm(req, 'add'):
       
    66                 return 1
       
    67     return 0
       
    68 
    20 # generic primary actions #####################################################
    69 # generic primary actions #####################################################
    21 
    70 
    22 class SelectAction(EntityAction):
    71 class SelectAction(Action):
    23     """base class for link search actions. By default apply on
    72     """base class for link search actions. By default apply on
    24     any size entity result search it the current state is 'linksearch'
    73     any size entity result search it the current state is 'linksearch'
    25     if accept match.
    74     if accept match.
    26     """
    75     """
    27     category = 'mainactions'    
    76     id = 'select'
    28     __selectors__ = (searchstate_accept,)
    77     __select__ = match_search_state('linksearch') & match_searched_etype()
    29     search_states = ('linksearch',)
    78 
       
    79     title = _('select')
       
    80     category = 'mainactions'
    30     order = 0
    81     order = 0
    31     
    82 
    32     id = 'select'
       
    33     title = _('select')
       
    34     
       
    35     @classmethod
       
    36     def accept_rset(cls, req, rset, row, col):
       
    37         return linksearch_match(req, rset)
       
    38     
       
    39     def url(self):
    83     def url(self):
    40         return linksearch_select_url(self.req, self.rset)
    84         return linksearch_select_url(self.req, self.rset)
    41 
    85 
    42 
    86 
    43 class CancelSelectAction(Action):
    87 class CancelSelectAction(Action):
    44     category = 'mainactions'
       
    45     search_states = ('linksearch',)
       
    46     order = 10
       
    47     
       
    48     id = 'cancel'
    88     id = 'cancel'
       
    89     __select__ = match_search_state('linksearch')
       
    90 
    49     title = _('cancel select')
    91     title = _('cancel select')
    50     
    92     category = 'mainactions'
    51     def url(self):
    93     order = 10
    52         target, link_eid, r_type, searched_type = self.req.search_state[1]
    94 
    53         return self.build_url(rql="Any X WHERE X eid %s" % link_eid,
    95     def url(self):
       
    96         target, eid, r_type, searched_type = self.req.search_state[1]
       
    97         return self.build_url(str(eid),
    54                               vid='edition', __mode='normal')
    98                               vid='edition', __mode='normal')
    55 
    99 
    56 
   100 
    57 class ViewAction(Action):
   101 class ViewAction(Action):
    58     category = 'mainactions'    
   102     id = 'view'
    59     __selectors__ = (match_user_group, searchstate_accept)
   103     __select__ = (match_search_state('normal') &
    60     require_groups = ('users', 'managers')
   104                   match_user_groups('users', 'managers') &
       
   105                   view_is_not_default_view() &
       
   106                   non_final_entity())
       
   107 
       
   108     title = _('view')
       
   109     category = 'mainactions'
    61     order = 0
   110     order = 0
    62     
   111 
    63     id = 'view'
       
    64     title = _('view')
       
    65     
       
    66     @classmethod
       
    67     def accept_rset(cls, req, rset, row, col):
       
    68         # interesting if it propose another view than the current one
       
    69         vid = req.form.get('vid')
       
    70         if vid and vid != vid_from_rset(req, rset, cls.schema):
       
    71             return 1
       
    72         return 0
       
    73     
       
    74     def url(self):
   112     def url(self):
    75         params = self.req.form.copy()
   113         params = self.req.form.copy()
    76         params.pop('vid', None)
   114         params.pop('vid', None)
    77         params.pop('__message', None)
   115         params.pop('__message', None)
    78         return self.build_url(self.req.relative_path(includeparams=False),
   116         return self.build_url(self.req.relative_path(includeparams=False),
    79                               **params)
   117                               **params)
    80 
   118 
    81 
   119 
    82 class ModifyAction(EntityAction):
   120 class ModifyAction(Action):
    83     category = 'mainactions'
       
    84     __selectors__ = (one_line_rset, searchstate_accept)
       
    85     schema_action = 'update'
       
    86     order = 10
       
    87     
       
    88     id = 'edit'
   121     id = 'edit'
       
   122     __select__ = (match_search_state('normal') &
       
   123                   one_line_rset() &
       
   124                   (has_permission('update') | has_editable_relation('add')))
       
   125 
    89     title = _('modify')
   126     title = _('modify')
    90     
   127     category = 'mainactions'
    91     @classmethod
   128     order = 10
    92     def has_permission(cls, entity, action):
       
    93         if entity.has_perm(action):
       
    94             return True
       
    95         # if user has no update right but it can modify some relation,
       
    96         # display action anyway
       
    97         for dummy in entity.srelations_by_category(('generic', 'metadata'),
       
    98                                                    'add'):
       
    99             return True
       
   100         for rschema, targetschemas, role in entity.relations_by_category(
       
   101             ('primary', 'secondary'), 'add'):
       
   102             if not rschema.is_final():
       
   103                 return True
       
   104         return False
       
   105 
   129 
   106     def url(self):
   130     def url(self):
   107         entity = self.rset.get_entity(self.row or 0, self.col or 0)
   131         entity = self.rset.get_entity(self.row or 0, self.col or 0)
   108         return entity.absolute_url(vid='edition')
   132         return entity.absolute_url(vid='edition')
   109         
   133 
   110 
   134 
   111 class MultipleEditAction(EntityAction):
   135 class MultipleEditAction(Action):
   112     category = 'mainactions'
       
   113     __selectors__ = (two_lines_rset, one_etype_rset,
       
   114                      searchstate_accept)
       
   115     schema_action = 'update'
       
   116     order = 10
       
   117     
       
   118     id = 'muledit' # XXX get strange conflicts if id='edit'
   136     id = 'muledit' # XXX get strange conflicts if id='edit'
       
   137     __select__ = (match_search_state('normal') &
       
   138                   two_lines_rset() & one_etype_rset() &
       
   139                   has_permission('update'))
       
   140 
   119     title = _('modify')
   141     title = _('modify')
   120     
   142     category = 'mainactions'
       
   143     order = 10
       
   144 
   121     def url(self):
   145     def url(self):
   122         return self.build_url('view', rql=self.rset.rql, vid='muledit')
   146         return self.build_url('view', rql=self.rset.rql, vid='muledit')
   123 
   147 
   124 
   148 
   125 # generic secondary actions ###################################################
   149 # generic secondary actions ###################################################
   126 
   150 
   127 class ManagePermissions(LinkToEntityAction):
   151 class ManagePermissionsAction(Action):
   128     accepts = ('Any',)
   152     id = 'managepermission'
   129     category = 'moreactions'
   153     __select__ = one_line_rset() & non_final_entity() & match_user_groups('managers')
   130     id = 'addpermission'
   154 
   131     title = _('manage permissions')
   155     title = _('manage permissions')
   132     order = 100
   156     category = 'moreactions'
   133 
   157     order = 15
   134     etype = 'EPermission'
   158 
   135     rtype = 'require_permission'
   159     @classmethod
   136     target = 'object'
   160     def registered(cls, vreg):
   137     
   161         super(ManagePermissionsAction, cls).registered(vreg)
   138     def url(self):
   162         if 'require_permission' in vreg.schema:
   139         return self.rset.get_entity(0, 0).absolute_url(vid='security')
   163             cls.__select__ = (one_line_rset() & non_final_entity() &
   140 
   164                               (match_user_groups('managers')
   141     
   165                                | relation_possible('require_permission', 'subject', 'CWPermission',
   142 class DeleteAction(EntityAction):
   166                                                    action='add')))
   143     category = 'moreactions' 
   167         return super(ManagePermissionsAction, cls).registered(vreg)
   144     __selectors__ = (searchstate_accept,)
   168 
   145     schema_action = 'delete'
   169     def url(self):
       
   170         return self.rset.get_entity(self.row or 0, self.col or 0).absolute_url(vid='security')
       
   171 
       
   172 
       
   173 class DeleteAction(Action):
       
   174     id = 'delete'
       
   175     __select__ = has_permission('delete')
       
   176 
       
   177     title = _('delete')
       
   178     category = 'moreactions'
   146     order = 20
   179     order = 20
   147     
   180 
   148     id = 'delete'
       
   149     title = _('delete')
       
   150     
       
   151     def url(self):
   181     def url(self):
   152         if len(self.rset) == 1:
   182         if len(self.rset) == 1:
   153             entity = self.rset.get_entity(0, 0)
   183             entity = self.rset.get_entity(self.row or 0, self.col or 0)
   154             return self.build_url(entity.rest_path(), vid='deleteconf')
   184             return self.build_url(entity.rest_path(), vid='deleteconf')
   155         return self.build_url(rql=self.rset.printable_rql(), vid='deleteconf')
   185         return self.build_url(rql=self.rset.printable_rql(), vid='deleteconf')
   156     
   186 
   157         
   187 
   158 class CopyAction(EntityAction):
   188 class CopyAction(Action):
   159     category = 'moreactions'
   189     id = 'copy'
   160     schema_action = 'add'
   190     __select__ = one_line_rset() & has_permission('add')
       
   191 
       
   192     title = _('copy')
       
   193     category = 'moreactions'
   161     order = 30
   194     order = 30
   162     
   195 
   163     id = 'copy'
       
   164     title = _('copy')
       
   165     
       
   166     def url(self):
   196     def url(self):
   167         entity = self.rset.get_entity(self.row or 0, self.col or 0)
   197         entity = self.rset.get_entity(self.row or 0, self.col or 0)
   168         return entity.absolute_url(vid='copy')
   198         return entity.absolute_url(vid='copy')
   169 
   199 
   170 
   200 
   171 class AddNewAction(MultipleEditAction):
   201 class AddNewAction(MultipleEditAction):
   172     """when we're seeing more than one entity with the same type, propose to
   202     """when we're seeing more than one entity with the same type, propose to
   173     add a new one
   203     add a new one
   174     """
   204     """
   175     category = 'moreactions'
       
   176     id = 'addentity'
   205     id = 'addentity'
       
   206     __select__ = (match_search_state('normal') &
       
   207                   (addable_etype_empty_rset()
       
   208                    | (two_lines_rset() & one_etype_rset & has_add_permission()))
       
   209                   )
       
   210 
       
   211     category = 'moreactions'
   177     order = 40
   212     order = 40
   178     
       
   179     def etype_rset_selector(cls, req, rset, **kwargs):
       
   180         if rset is not None and not rset.rowcount:
       
   181             rqlst = rset.syntax_tree()
       
   182             if len(rqlst.children) > 1:
       
   183                 return 0
       
   184             select = rqlst.children[0]
       
   185             if len(select.defined_vars) == 1 and len(select.solutions) == 1:
       
   186                 rset._searched_etype = select.solutions[0].itervalues().next()
       
   187                 eschema = cls.schema.eschema(rset._searched_etype)
       
   188                 if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
       
   189                        and eschema.has_perm(req, 'add'):
       
   190                     return 1
       
   191         return 0
       
   192 
       
   193     def has_add_perm_selector(cls, req, rset, **kwargs):
       
   194         eschema = cls.schema.eschema(rset.description[0][0])
       
   195         if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
       
   196                and eschema.has_perm(req, 'add'):
       
   197             return 1
       
   198         return 0
       
   199     __selectors__ = (match_search_state,
       
   200                      chainfirst(etype_rset_selector,
       
   201                                 chainall(two_lines_rset, one_etype_rset,
       
   202                                          has_add_perm_selector)))
       
   203 
   213 
   204     @property
   214     @property
   205     def rsettype(self):
   215     def rsettype(self):
   206         if self.rset:
   216         if self.rset:
   207             return self.rset.description[0][0]
   217             return self.rset.description[0][0]
   216 
   226 
   217 
   227 
   218 # logged user actions #########################################################
   228 # logged user actions #########################################################
   219 
   229 
   220 class UserPreferencesAction(Action):
   230 class UserPreferencesAction(Action):
       
   231     id = 'myprefs'
       
   232     __select__ = authenticated_user()
       
   233 
       
   234     title = _('user preferences')
   221     category = 'useractions'
   235     category = 'useractions'
   222     __selectors__ = authenticated_user,
   236     order = 10
   223     order = 10
       
   224     
       
   225     id = 'myprefs'
       
   226     title = _('user preferences')
       
   227 
   237 
   228     def url(self):
   238     def url(self):
   229         return self.build_url(self.id)
   239         return self.build_url(self.id)
   230 
   240 
   231 
   241 
   232 class UserInfoAction(Action):
   242 class UserInfoAction(Action):
       
   243     id = 'myinfos'
       
   244     __select__ = authenticated_user()
       
   245 
       
   246     title = _('personnal informations')
   233     category = 'useractions'
   247     category = 'useractions'
   234     __selectors__ = authenticated_user,
       
   235     order = 20
   248     order = 20
   236     
   249 
   237     id = 'myinfos'
   250     def url(self):
   238     title = _('personnal informations')
   251         return self.build_url('cwuser/%s'%self.req.user.login, vid='edition')
   239 
       
   240     def url(self):
       
   241         return self.build_url('euser/%s'%self.req.user.login, vid='edition')
       
   242 
   252 
   243 
   253 
   244 class LogoutAction(Action):
   254 class LogoutAction(Action):
       
   255     id = 'logout'
       
   256     __select__ = authenticated_user()
       
   257 
       
   258     title = _('logout')
   245     category = 'useractions'
   259     category = 'useractions'
   246     __selectors__ = authenticated_user,
       
   247     order = 30
   260     order = 30
   248     
       
   249     id = 'logout'
       
   250     title = _('logout')
       
   251 
   261 
   252     def url(self):
   262     def url(self):
   253         return self.build_url(self.id)
   263         return self.build_url(self.id)
   254 
   264 
   255     
   265 
   256 # site actions ################################################################
   266 # site actions ################################################################
   257 
   267 
   258 class ManagersAction(Action):
   268 class ManagersAction(Action):
       
   269     __abstract__ = True
       
   270     __select__ = match_user_groups('managers')
       
   271 
   259     category = 'siteactions'
   272     category = 'siteactions'
   260     __abstract__ = True
       
   261     __selectors__ = match_user_group,
       
   262     require_groups = ('managers',)
       
   263 
   273 
   264     def url(self):
   274     def url(self):
   265         return self.build_url(self.id)
   275         return self.build_url(self.id)
   266 
   276 
   267     
   277 
   268 class SiteConfigurationAction(ManagersAction):
   278 class SiteConfigurationAction(ManagersAction):
   269     order = 10
       
   270     id = 'siteconfig'
   279     id = 'siteconfig'
   271     title = _('site configuration')
   280     title = _('site configuration')
   272 
   281     order = 10
   273     
   282 
       
   283 
   274 class ManageAction(ManagersAction):
   284 class ManageAction(ManagersAction):
   275     order = 20
       
   276     id = 'manage'
   285     id = 'manage'
   277     title = _('manage')
   286     title = _('manage')
   278 
   287     order = 20
   279 
   288 
   280 class ViewSchemaAction(Action):
   289 
   281     category = 'siteactions'
   290 from logilab.common.deprecation import class_moved
   282     id = 'schema'
   291 from cubicweb.web.views.bookmark import FollowAction
   283     title = _("site schema")
   292 FollowAction = class_moved(FollowAction)
   284     __selectors__ = yes,
   293 
   285     order = 30
       
   286     
       
   287     def url(self):
       
   288         return self.build_url(self.id)
       
   289 
       
   290 
       
   291 # content type specific actions ###############################################
       
   292 
       
   293 class FollowAction(EntityAction):
       
   294     category = 'mainactions'
       
   295     accepts = ('Bookmark',)
       
   296     
       
   297     id = 'follow'
       
   298     title = _('follow')
       
   299     
       
   300     def url(self):
       
   301         return self.rset.get_entity(self.row or 0, self.col or 0).actual_url()
       
   302 
       
   303 class UserPreferencesEntityAction(EntityAction):
       
   304     __selectors__ = EntityAction.__selectors__ + (one_line_rset, match_user_group,)
       
   305     require_groups = ('owners', 'managers')
       
   306     category = 'mainactions'
       
   307     accepts = ('EUser',)
       
   308     
       
   309     id = 'prefs'
       
   310     title = _('preferences')
       
   311     
       
   312     def url(self):
       
   313         login = self.rset.get_entity(self.row or 0, self.col or 0).login
       
   314         return self.build_url('euser/%s'%login, vid='epropertiesform')
       
   315 
       
   316 # schema view action
       
   317 def schema_view(cls, req, rset, row=None, col=None, view=None,
       
   318                 **kwargs):
       
   319     if view is None or not view.id == 'schema':
       
   320         return 0
       
   321     return 1
       
   322 
       
   323 class DownloadOWLSchemaAction(Action):
       
   324     category = 'mainactions'
       
   325     id = 'download_as_owl'
       
   326     title = _('download schema as owl')
       
   327     __selectors__ = none_rset, schema_view
       
   328    
       
   329     def url(self):
       
   330         return self.build_url('view', vid='owl')
       
   331