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