|
1 # copyright 2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
3 # |
|
4 # This file is part of CubicWeb. |
|
5 # |
|
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
|
7 # terms of the GNU Lesser General Public License as published by the Free |
|
8 # Software Foundation, either version 2.1 of the License, or (at your option) |
|
9 # any later version. |
|
10 # |
|
11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT |
|
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|
13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
|
14 # details. |
|
15 # |
|
16 # You should have received a copy of the GNU Lesser General Public License along |
|
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
|
18 |
|
19 __docformat__ = "restructuredtext en" |
|
20 _ = unicode |
|
21 |
|
22 |
|
23 from logilab.common.registry import Predicate, yes |
|
24 |
|
25 from cubicweb import UnknownEid, tags, transaction as tx |
|
26 from cubicweb.view import View, StartupView |
|
27 from cubicweb.predicates import match_kwargs, ExpectedValuePredicate |
|
28 from cubicweb.schema import display_name |
|
29 |
|
30 |
|
31 class undoable_action(Predicate): |
|
32 """Select only undoable actions depending on filters provided. Undo Action |
|
33 is expected to be specified by the `tx_action` argument. |
|
34 |
|
35 Currently the only implemented filter is: |
|
36 |
|
37 :param action_type: chars among CUDAR (standing for Create, Update, Delete, |
|
38 Add, Remove) |
|
39 """ |
|
40 |
|
41 # XXX FIXME : this selector should be completed to allow selection on the |
|
42 # entity or relation types and public / private. |
|
43 def __init__(self, action_type='CUDAR'): |
|
44 assert not set(action_type) & set('CUDAR') |
|
45 self.action_type = action_type |
|
46 |
|
47 def __str__(self): |
|
48 return '%s(%s)' % (self.__class__.__name__, ', '.join( |
|
49 "%s=%v" % (str(k), str(v)) for k, v in kwargs.iteritems() )) |
|
50 |
|
51 def __call__(self, cls, req, tx_action=None, **kwargs): |
|
52 # tx_action is expected to be a transaction.AbstractAction |
|
53 if not isinstance(tx_action, tx.AbstractAction): |
|
54 return 0 |
|
55 # Filter according to action type |
|
56 return int(tx_action.action in self.action_type) |
|
57 |
|
58 |
|
59 class UndoHistoryView(StartupView): |
|
60 __regid__ = 'undohistory' |
|
61 __select__ = yes() |
|
62 title = _('Undoing') |
|
63 item_vid = 'undoable-transaction-view' |
|
64 cache_max_age = 0 |
|
65 |
|
66 redirect_path = 'view' #TODO |
|
67 redirect_params = dict(vid='undohistory') #TODO |
|
68 public_actions_only = True |
|
69 |
|
70 # TODO Allow to choose if if want all actions or only the public ones |
|
71 # (default) |
|
72 |
|
73 def call(self, **kwargs): |
|
74 txs = self._cw.cnx.undoable_transactions() |
|
75 if txs : |
|
76 self.w(u"<ul class='undo-transactions'>") |
|
77 for tx in txs: |
|
78 self.cell_call(tx) |
|
79 self.w(u"</ul>") |
|
80 |
|
81 def cell_call(self, tx): |
|
82 self.w(u'<li>') |
|
83 self.wview(self.item_vid, None, txuuid=tx.uuid, |
|
84 public=self.public_actions_only, |
|
85 redirect_path=self.redirect_path, |
|
86 redirect_params=self.redirect_params) |
|
87 self.w(u'</li>\n') |
|
88 |
|
89 |
|
90 class UndoableTransactionView(View): |
|
91 __regid__ = 'undoable-transaction-view' |
|
92 __select__ = View.__select__ & match_kwargs('txuuid') |
|
93 |
|
94 item_vid = 'undoable-action-list-view' |
|
95 cache_max_age = 0 |
|
96 |
|
97 def build_undo_link(self, txuuid, |
|
98 redirect_path=None, redirect_params=None): |
|
99 """ the kwargs are passed to build_url""" |
|
100 _ = self._cw._ |
|
101 redirect = {} |
|
102 if redirect_path: |
|
103 redirect['__redirectpath'] = redirect_path |
|
104 if redirect_params: |
|
105 if isinstance(redirect_params, dict): |
|
106 redirect['__redirectparams'] = self._cw.build_url_params(**redirect_params) |
|
107 else: |
|
108 redirect['__redirectparams'] = redirect_params |
|
109 link_url = self._cw.build_url('undo', txuuid=txuuid, **redirect) |
|
110 msg = u"<span class='undo'>%s</span>" % tags.a( _('undo'), href=link_url) |
|
111 return msg |
|
112 |
|
113 def call(self, txuuid, public=True, |
|
114 redirect_path=None, redirect_params=None): |
|
115 _ = self._cw._ |
|
116 txinfo = self._cw.cnx.transaction_info(txuuid) |
|
117 try: |
|
118 #XXX Under some unknown circumstances txinfo.user_eid=-1 |
|
119 user = self._cw.entity_from_eid(txinfo.user_eid) |
|
120 except UnknownEid: |
|
121 user = None |
|
122 undo_url = self.build_undo_link(txuuid, |
|
123 redirect_path=redirect_path, |
|
124 redirect_params=redirect_params) |
|
125 txinfo_dict = dict( dt = self._cw.format_date(txinfo.datetime, time=True), |
|
126 user_eid = txinfo.user_eid, |
|
127 user = user and user.view('outofcontext') or _("undefined user"), |
|
128 txuuid = txuuid, |
|
129 undo_link = undo_url) |
|
130 self.w( _("By %(user)s on %(dt)s [%(undo_link)s]") % txinfo_dict) |
|
131 |
|
132 tx_actions = txinfo.actions_list(public=public) |
|
133 if tx_actions : |
|
134 self.wview(self.item_vid, None, tx_actions=tx_actions) |
|
135 |
|
136 |
|
137 class UndoableActionListView(View): |
|
138 __regid__ = 'undoable-action-list-view' |
|
139 __select__ = View.__select__ & match_kwargs('tx_actions') |
|
140 title = _('Undoable actions') |
|
141 item_vid = 'undoable-action-view' |
|
142 cache_max_age = 0 |
|
143 |
|
144 def call(self, tx_actions): |
|
145 if tx_actions : |
|
146 self.w(u"<ol class='undo-actions'>") |
|
147 for action in tx_actions: |
|
148 self.cell_call(action) |
|
149 self.w(u"</ol>") |
|
150 |
|
151 def cell_call(self, action): |
|
152 self.w(u'<li>') |
|
153 self.wview(self.item_vid, None, tx_action=action) |
|
154 self.w(u'</li>\n') |
|
155 |
|
156 |
|
157 class UndoableActionBaseView(View): |
|
158 __regid__ = 'undoable-action-view' |
|
159 __abstract__ = True |
|
160 |
|
161 def call(self, tx_action): |
|
162 raise NotImplementedError(self) |
|
163 |
|
164 def _build_entity_link(self, eid): |
|
165 try: |
|
166 entity = self._cw.entity_from_eid(eid) |
|
167 return entity.view('outofcontext') |
|
168 except UnknownEid: |
|
169 return _("(suppressed) entity #%d") % eid |
|
170 |
|
171 def _build_relation_info(self, rtype, eid_from, eid_to): |
|
172 return dict( rtype=display_name(self._cw, rtype), |
|
173 entity_from=self._build_entity_link(eid_from), |
|
174 entity_to=self._build_entity_link(eid_to) ) |
|
175 |
|
176 def _build_entity_info(self, etype, eid, changes): |
|
177 return dict( etype=display_name(self._cw, etype), |
|
178 entity=self._build_entity_link(eid), |
|
179 eid=eid, |
|
180 changes=changes) |
|
181 |
|
182 |
|
183 class UndoableAddActionView(UndoableActionBaseView): |
|
184 __select__ = UndoableActionBaseView.__select__ & undoable_action(action_type='A') |
|
185 |
|
186 def call(self, tx_action): |
|
187 _ = self._cw._ |
|
188 self.w(_("Added relation : %(entity_from)s %(rtype)s %(entity_to)s") % |
|
189 self._build_relation_info(tx_action.rtype, tx_action.eid_from, tx_action.eid_to)) |
|
190 |
|
191 |
|
192 class UndoableRemoveActionView(UndoableActionBaseView): |
|
193 __select__ = UndoableActionBaseView.__select__ & undoable_action(action_type='R') |
|
194 |
|
195 def call(self, tx_action): |
|
196 _ = self._cw._ |
|
197 self.w(_("Delete relation : %(entity_from)s %(rtype)s %(entity_to)s") % |
|
198 self._build_relation_info(tx_action.rtype, tx_action.eid_from, tx_action.eid_to)) |
|
199 |
|
200 |
|
201 class UndoableCreateActionView(UndoableActionBaseView): |
|
202 __select__ = UndoableActionBaseView.__select__ & undoable_action(action_type='C') |
|
203 |
|
204 def call(self, tx_action): |
|
205 _ = self._cw._ |
|
206 self.w(_("Created %(etype)s : %(entity)s") % # : %(changes)s |
|
207 self._build_entity_info( tx_action.etype, tx_action.eid, tx_action.changes) ) |
|
208 |
|
209 |
|
210 class UndoableDeleteActionView(UndoableActionBaseView): |
|
211 __select__ = UndoableActionBaseView.__select__ & undoable_action(action_type='D') |
|
212 |
|
213 def call(self, tx_action): |
|
214 _ = self._cw._ |
|
215 self.w(_("Deleted %(etype)s : %(entity)s") % |
|
216 self._build_entity_info( tx_action.etype, tx_action.eid, tx_action.changes)) |
|
217 |
|
218 |
|
219 class UndoableUpdateActionView(UndoableActionBaseView): |
|
220 __select__ = UndoableActionBaseView.__select__ & undoable_action(action_type='U') |
|
221 |
|
222 def call(self, tx_action): |
|
223 _ = self._cw._ |
|
224 self.w(_("Updated %(etype)s : %(entity)s") % |
|
225 self._build_entity_info( tx_action.etype, tx_action.eid, tx_action.changes)) |