14 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses |
14 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses |
15 """ |
15 """ |
16 __docformat__ = "restructuredtext en" |
16 __docformat__ = "restructuredtext en" |
17 _ = unicode |
17 _ = unicode |
18 |
18 |
|
19 from warnings import warn |
|
20 |
19 from logilab.mtconverter import xml_escape |
21 from logilab.mtconverter import xml_escape |
20 |
22 |
21 from cubicweb.selectors import match_user_groups, non_final_entity |
23 from cubicweb.selectors import match_user_groups, non_final_entity |
22 from cubicweb.view import EntityView |
24 from cubicweb.view import EntityView |
23 from cubicweb.schema import display_name |
25 from cubicweb.schema import display_name |
24 from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem |
26 from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem |
25 from cubicweb.web import uicfg |
27 from cubicweb.web import uicfg |
26 from cubicweb.web.box import BoxTemplate |
28 from cubicweb.web.box import BoxTemplate |
27 |
29 |
28 |
30 |
29 class EditBox(BoxTemplate): |
31 class EditBox(BoxTemplate): # XXX rename to ActionsBox |
30 """ |
32 """ |
31 box with all actions impacting the entity displayed: edit, copy, delete |
33 box with all actions impacting the entity displayed: edit, copy, delete |
32 change state, add related entities |
34 change state, add related entities |
33 """ |
35 """ |
34 id = 'edit_box' |
36 id = 'edit_box' |
35 __select__ = BoxTemplate.__select__ & non_final_entity() |
37 __select__ = BoxTemplate.__select__ & non_final_entity() |
36 |
38 |
37 title = _('actions') |
39 title = _('actions') |
38 order = 2 |
40 order = 2 |
39 # class attributes below are actually stored in the uicfg module since we |
|
40 # don't want them to be reloaded |
|
41 appearsin_addmenu = uicfg.actionbox_appearsin_addmenu |
|
42 |
41 |
43 def call(self, view=None, **kwargs): |
42 def call(self, view=None, **kwargs): |
44 _ = self.req._ |
43 _ = self.req._ |
45 title = _(self.title) |
44 title = _(self.title) |
46 if self.rset: |
45 if self.rset: |
48 if len(etypes) == 1: |
47 if len(etypes) == 1: |
49 plural = self.rset.rowcount > 1 and 'plural' or '' |
48 plural = self.rset.rowcount > 1 and 'plural' or '' |
50 etypelabel = display_name(self.req, iter(etypes).next(), plural) |
49 etypelabel = display_name(self.req, iter(etypes).next(), plural) |
51 title = u'%s - %s' % (title, etypelabel.lower()) |
50 title = u'%s - %s' % (title, etypelabel.lower()) |
52 box = BoxWidget(title, self.id, _class="greyBoxFrame") |
51 box = BoxWidget(title, self.id, _class="greyBoxFrame") |
|
52 self._menus_in_order = [] |
|
53 self._menus_by_id = {} |
53 # build list of actions |
54 # build list of actions |
54 actions = self.vreg['actions'].possible_actions(self.req, self.rset, |
55 actions = self.vreg['actions'].possible_actions(self.req, self.rset, |
55 view=view) |
56 view=view) |
56 add_menu = BoxMenu(_('add')) # 'addrelated' category |
57 other_menu = self._get_menu('moreactions', _('more actions')) |
57 other_menu = BoxMenu(_('more actions')) # 'moreactions' category |
58 for category, defaultmenu in (('mainactions', box), |
58 searchstate = self.req.search_state[0] |
59 ('moreactions', other_menu), |
59 for category, menu in (('mainactions', box), |
60 ('addrelated', None)): |
60 ('addrelated', add_menu), |
|
61 ('moreactions', other_menu)): |
|
62 for action in actions.get(category, ()): |
61 for action in actions.get(category, ()): |
63 menu.append(self.box_action(action)) |
62 if category == 'addrelated': |
64 if self.rset and self.rset.rowcount == 1 and \ |
63 warn('"addrelated" category is deprecated, use "moreaction"' |
65 not self.schema[self.rset.description[0][0]].is_final() and \ |
64 ' category w/ "addrelated" submenu', |
66 searchstate == 'normal': |
65 DeprecationWarning) |
67 entity = self.rset.get_entity(0, 0) |
66 defaultmenu = self._get_menu('addrelated', _('add'), _('add')) |
68 for action in self.schema_actions(entity): |
67 if action.submenu: |
69 add_menu.append(action) |
68 menu = self._get_menu(action.submenu) |
70 self.workflow_actions(entity, box) |
69 else: |
|
70 menu = defaultmenu |
|
71 action.fill_menu(self, menu) |
71 if box.is_empty() and not other_menu.is_empty(): |
72 if box.is_empty() and not other_menu.is_empty(): |
72 box.items = other_menu.items |
73 box.items = other_menu.items |
73 other_menu.items = [] |
74 other_menu.items = [] |
74 self.add_submenu(box, add_menu, _('add')) |
75 else: # ensure 'more actions' menu appears last |
75 self.add_submenu(box, other_menu) |
76 self._menus_in_order.remove(other_menu) |
|
77 self._menus_in_order.append(other_menu) |
|
78 for submenu in self._menus_in_order: |
|
79 self.add_submenu(box, submenu) |
76 if not box.is_empty(): |
80 if not box.is_empty(): |
77 box.render(self.w) |
81 box.render(self.w) |
78 |
82 |
|
83 def _get_menu(self, id, title=None, label_prefix=None): |
|
84 try: |
|
85 return self._menus_by_id[id] |
|
86 except KeyError: |
|
87 if title is None: |
|
88 title = self.req._(id) |
|
89 self._menus_by_id[id] = menu = BoxMenu(title) |
|
90 menu.label_prefix = label_prefix |
|
91 self._menus_in_order.append(menu) |
|
92 return menu |
|
93 |
79 def add_submenu(self, box, submenu, label_prefix=None): |
94 def add_submenu(self, box, submenu, label_prefix=None): |
80 if len(submenu.items) == 1: |
95 appendanyway = getattr(submenu, 'append_anyway', False) |
|
96 if len(submenu.items) == 1 and not appendanyway: |
81 boxlink = submenu.items[0] |
97 boxlink = submenu.items[0] |
82 if label_prefix: |
98 if submenu.label_prefix: |
83 boxlink.label = u'%s %s' % (label_prefix, boxlink.label) |
99 boxlink.label = u'%s %s' % (submenu.label_prefix, boxlink.label) |
84 box.append(boxlink) |
100 box.append(boxlink) |
85 elif submenu.items: |
101 elif submenu.items: |
86 box.append(submenu) |
102 box.append(submenu) |
87 |
103 elif appendanyway: |
88 def schema_actions(self, entity): |
104 box.append(RawBoxItem(xml_escape(submenu.label))) |
89 user = self.req.user |
|
90 actions = [] |
|
91 _ = self.req._ |
|
92 eschema = entity.e_schema |
|
93 for rschema, teschema, x in self.add_related_schemas(entity): |
|
94 if x == 'subject': |
|
95 label = 'add %s %s %s %s' % (eschema, rschema, teschema, x) |
|
96 url = self.linkto_url(entity, rschema, teschema, 'object') |
|
97 else: |
|
98 label = 'add %s %s %s %s' % (teschema, rschema, eschema, x) |
|
99 url = self.linkto_url(entity, rschema, teschema, 'subject') |
|
100 actions.append(self.mk_action(_(label), url)) |
|
101 return actions |
|
102 |
|
103 def add_related_schemas(self, entity): |
|
104 """this is actually used ui method to generate 'addrelated' actions from |
|
105 the schema. |
|
106 |
|
107 If you don't want any auto-generated actions, you should overrides this |
|
108 method to return an empty list. If you only want some, you can configure |
|
109 them by using uicfg.actionbox_appearsin_addmenu |
|
110 """ |
|
111 req = self.req |
|
112 eschema = entity.e_schema |
|
113 for role, rschemas in (('subject', eschema.subject_relations()), |
|
114 ('object', eschema.object_relations())): |
|
115 for rschema in rschemas: |
|
116 if rschema.is_final(): |
|
117 continue |
|
118 # check the relation can be added as well |
|
119 # XXX consider autoform_permissions_overrides? |
|
120 if role == 'subject'and not rschema.has_perm(req, 'add', |
|
121 fromeid=entity.eid): |
|
122 continue |
|
123 if role == 'object'and not rschema.has_perm(req, 'add', |
|
124 toeid=entity.eid): |
|
125 continue |
|
126 # check the target types can be added as well |
|
127 for teschema in rschema.targets(eschema, role): |
|
128 if not self.appearsin_addmenu.etype_get(eschema, rschema, |
|
129 role, teschema): |
|
130 continue |
|
131 if teschema.has_local_role('add') or teschema.has_perm(req, 'add'): |
|
132 yield rschema, teschema, role |
|
133 |
|
134 |
|
135 def workflow_actions(self, entity, box): |
|
136 if entity.e_schema.has_subject_relation('in_state') and entity.in_state: |
|
137 _ = self.req._ |
|
138 menu_title = u'%s: %s' % (_('state'), entity.printable_state) |
|
139 menu_items = [] |
|
140 for tr in entity.possible_transitions(): |
|
141 url = entity.absolute_url(vid='statuschange', treid=tr.eid) |
|
142 menu_items.append(self.mk_action(_(tr.name), url)) |
|
143 # don't propose to see wf if user can't pass any transition |
|
144 if menu_items: |
|
145 wfurl = self.build_url('cwetype/%s'%entity.e_schema, vid='workflow') |
|
146 menu_items.append(self.mk_action(_('view workflow'), wfurl)) |
|
147 if entity.workflow_history: |
|
148 wfurl = entity.absolute_url(vid='wfhistory') |
|
149 menu_items.append(self.mk_action(_('view history'), wfurl)) |
|
150 box.append(BoxMenu(menu_title, menu_items)) |
|
151 return None |
|
152 |
|
153 def linkto_url(self, entity, rtype, etype, target): |
|
154 return self.build_url(vid='creation', etype=etype, |
|
155 __linkto='%s:%s:%s' % (rtype, entity.eid, target), |
|
156 __redirectpath=entity.rest_path(), # should not be url quoted! |
|
157 __redirectvid=self.req.form.get('vid', '')) |
|
158 |
105 |
159 |
106 |
160 class SearchBox(BoxTemplate): |
107 class SearchBox(BoxTemplate): |
161 """display a box with a simple search form""" |
108 """display a box with a simple search form""" |
162 id = 'search_box' |
109 id = 'search_box' |