|
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 |