11 from cubicweb import UnknownEid |
11 from cubicweb import UnknownEid |
12 from cubicweb.selectors import none_rset |
12 from cubicweb.selectors import none_rset |
13 from cubicweb.schema import display_name |
13 from cubicweb.schema import display_name |
14 from cubicweb.view import Component |
14 from cubicweb.view import Component |
15 from cubicweb.common.mail import format_mail |
15 from cubicweb.common.mail import format_mail |
16 from cubicweb.server.hooksmanager import Hook |
|
17 from cubicweb.server.hookhelper import SendMailOp |
16 from cubicweb.server.hookhelper import SendMailOp |
18 |
|
19 |
|
20 class SomethingChangedHook(Hook): |
|
21 events = ('before_add_relation', 'before_delete_relation', |
|
22 'after_add_entity', 'before_update_entity') |
|
23 accepts = ('Any',) |
|
24 |
|
25 def call(self, session, *args): |
|
26 dest = session.vreg.config['supervising-addrs'] |
|
27 if not dest: # no supervisors, don't do this for nothing... |
|
28 return |
|
29 self.session = session |
|
30 if self._call(*args): |
|
31 SupervisionMailOp(session) |
|
32 |
|
33 def _call(self, *args): |
|
34 if self._event() == 'update_entity': |
|
35 if args[0].eid in self.session.transaction_data.get('neweids', ()): |
|
36 return False |
|
37 if args[0].e_schema == 'CWUser': |
|
38 updated = set(args[0].iterkeys()) |
|
39 if not (updated - frozenset(('eid', 'modification_date', |
|
40 'last_login_time'))): |
|
41 # don't record last_login_time update which are done |
|
42 # automatically at login time |
|
43 return False |
|
44 self.session.transaction_data.setdefault('pendingchanges', []).append( |
|
45 (self._event(), args)) |
|
46 return True |
|
47 |
|
48 def _event(self): |
|
49 return self.event.split('_', 1)[1] |
|
50 |
|
51 |
|
52 class EntityDeleteHook(SomethingChangedHook): |
|
53 events = ('before_delete_entity',) |
|
54 |
|
55 def _call(self, eid): |
|
56 entity = self.session.entity_from_eid(eid) |
|
57 try: |
|
58 title = entity.dc_title() |
|
59 except: |
|
60 # may raise an error during deletion process, for instance due to |
|
61 # missing required relation |
|
62 title = '#%s' % eid |
|
63 self.session.transaction_data.setdefault('pendingchanges', []).append( |
|
64 ('delete_entity', (eid, str(entity.e_schema), title))) |
|
65 return True |
|
66 |
17 |
67 |
18 |
68 def filter_changes(changes): |
19 def filter_changes(changes): |
69 """ |
20 """ |
70 * when an entity has been deleted: |
21 * when an entity has been deleted: |
109 try: |
60 try: |
110 for change in index['add_relation'].copy(): |
61 for change in index['add_relation'].copy(): |
111 changedescr = change[1] |
62 changedescr = change[1] |
112 # skip meta-relations which are set automatically |
63 # skip meta-relations which are set automatically |
113 # XXX generate list below using rtags (category = 'generated') |
64 # XXX generate list below using rtags (category = 'generated') |
114 if changedescr[1] in ('created_by', 'owned_by', 'is', 'is_instance_of', |
65 if changedescr.rtype in ('created_by', 'owned_by', 'is', 'is_instance_of', |
115 'from_state', 'to_state', 'wf_info_for',) \ |
66 'from_state', 'to_state', 'wf_info_for',) \ |
116 and changedescr[0] == eid: |
67 and changedescr.eidfrom == eid: |
117 index['add_relation'].remove(change) |
68 index['add_relation'].remove(change) |
118 # skip in_state relation if the entity is being created |
69 # skip in_state relation if the entity is being created |
119 # XXX this may be automatized by skipping all mandatory relation |
70 # XXX this may be automatized by skipping all mandatory relation |
120 # at entity creation time |
71 # at entity creation time |
121 elif changedescr[1] == 'in_state' and changedescr[0] in added: |
72 elif changedescr.rtype == 'in_state' and changedescr.eidfrom in added: |
122 index['add_relation'].remove(change) |
73 index['add_relation'].remove(change) |
123 |
74 |
124 except KeyError: |
75 except KeyError: |
125 break |
76 break |
126 for eid in deleted: |
77 for eid in deleted: |
127 try: |
78 try: |
128 for change in index['delete_relation'].copy(): |
79 for change in index['delete_relation'].copy(): |
129 fromeid, rtype, toeid = change[1] |
80 if change.eidfrom == eid: |
130 if fromeid == eid: |
|
131 index['delete_relation'].remove(change) |
81 index['delete_relation'].remove(change) |
132 elif toeid == eid: |
82 elif change.eidto == eid: |
133 index['delete_relation'].remove(change) |
83 index['delete_relation'].remove(change) |
134 if rtype == 'wf_info_for': |
84 if change.rtype == 'wf_info_for': |
135 for change in index['delete_entity'].copy(): |
85 for change_ in index['delete_entity'].copy(): |
136 if change[1][0] == fromeid: |
86 if change_[1].eidfrom == change.eidfrom: |
137 index['delete_entity'].remove(change) |
87 index['delete_entity'].remove(change_) |
138 except KeyError: |
88 except KeyError: |
139 break |
89 break |
140 for change in changes: |
90 for change in changes: |
141 event, changedescr = change |
91 event, changedescr = change |
142 if change in index[event]: |
92 if change in index[event]: |
159 user = self.req.actual_session().user |
109 user = self.req.actual_session().user |
160 self.w(self.req._('user %s has made the following change(s):\n\n') |
110 self.w(self.req._('user %s has made the following change(s):\n\n') |
161 % user.login) |
111 % user.login) |
162 for event, changedescr in filter_changes(changes): |
112 for event, changedescr in filter_changes(changes): |
163 self.w(u'* ') |
113 self.w(u'* ') |
164 getattr(self, event)(*changedescr) |
114 getattr(self, event)(changedescr) |
165 self.w(u'\n\n') |
115 self.w(u'\n\n') |
166 |
116 |
167 def _entity_context(self, entity): |
117 def _entity_context(self, entity): |
168 return {'eid': entity.eid, |
118 return {'eid': entity.eid, |
169 'etype': entity.dc_type().lower(), |
119 'etype': entity.dc_type().lower(), |
170 'title': entity.dc_title()} |
120 'title': entity.dc_title()} |
171 |
121 |
172 def add_entity(self, entity): |
122 def add_entity(self, changedescr): |
173 msg = self.req._('added %(etype)s #%(eid)s (%(title)s)') |
123 msg = self.req._('added %(etype)s #%(eid)s (%(title)s)') |
174 self.w(u'%s\n' % (msg % self._entity_context(entity))) |
124 self.w(u'%s\n' % (msg % self._entity_context(changedescr.entity))) |
175 self.w(u' %s' % entity.absolute_url()) |
125 self.w(u' %s' % changedescr.entity.absolute_url()) |
176 |
126 |
177 def update_entity(self, entity): |
127 def update_entity(self, changedescr): |
178 msg = self.req._('updated %(etype)s #%(eid)s (%(title)s)') |
128 msg = self.req._('updated %(etype)s #%(eid)s (%(title)s)') |
179 self.w(u'%s\n' % (msg % self._entity_context(entity))) |
129 self.w(u'%s\n' % (msg % self._entity_context(changedescr.entity))) |
180 # XXX print changes |
130 # XXX print changes |
181 self.w(u' %s' % entity.absolute_url()) |
131 self.w(u' %s' % changedescr.entity.absolute_url()) |
182 |
132 |
183 def delete_entity(self, eid, etype, title): |
133 def delete_entity(self, (eid, etype, title)): |
184 msg = self.req._('deleted %(etype)s #%(eid)s (%(title)s)') |
134 msg = self.req._('deleted %(etype)s #%(eid)s (%(title)s)') |
185 etype = display_name(self.req, etype).lower() |
135 etype = display_name(self.req, etype).lower() |
186 self.w(msg % locals()) |
136 self.w(msg % locals()) |
187 |
137 |
188 def change_state(self, entity, fromstate, tostate): |
138 def change_state(self, (entity, fromstate, tostate)): |
189 msg = self.req._('changed state of %(etype)s #%(eid)s (%(title)s)') |
139 msg = self.req._('changed state of %(etype)s #%(eid)s (%(title)s)') |
190 self.w(u'%s\n' % (msg % self._entity_context(entity))) |
140 self.w(u'%s\n' % (msg % self._entity_context(entity))) |
191 self.w(_(' from state %(fromstate)s to state %(tostate)s\n' % |
141 self.w(_(' from state %(fromstate)s to state %(tostate)s\n' % |
192 {'fromstate': _(fromstate.name), 'tostate': _(tostate.name)})) |
142 {'fromstate': _(fromstate.name), 'tostate': _(tostate.name)})) |
193 self.w(u' %s' % entity.absolute_url()) |
143 self.w(u' %s' % entity.absolute_url()) |
194 |
144 |
195 def _relation_context(self, fromeid, rtype, toeid): |
145 def _relation_context(self, changedescr): |
196 _ = self.req._ |
146 _ = self.req._ |
197 session = self.req.actual_session() |
147 session = self.req.actual_session() |
198 def describe(eid): |
148 def describe(eid): |
199 try: |
149 try: |
200 return _(session.describe(eid)[0]).lower() |
150 return _(session.describe(eid)[0]).lower() |
201 except UnknownEid: |
151 except UnknownEid: |
202 # may occurs when an entity has been deleted from an external |
152 # may occurs when an entity has been deleted from an external |
203 # source and we're cleaning its relation |
153 # source and we're cleaning its relation |
204 return _('unknown external entity') |
154 return _('unknown external entity') |
|
155 eidfrom, rtype, eidto = changedescr.eidfrom, changedescr.rtype, changedescr.eidto |
205 return {'rtype': _(rtype), |
156 return {'rtype': _(rtype), |
206 'fromeid': fromeid, |
157 'eidfrom': eidfrom, |
207 'frometype': describe(fromeid), |
158 'frometype': describe(eidfrom), |
208 'toeid': toeid, |
159 'eidto': eidto, |
209 'toetype': describe(toeid)} |
160 'toetype': describe(eidto)} |
210 |
161 |
211 def add_relation(self, fromeid, rtype, toeid): |
162 def add_relation(self, changedescr): |
212 msg = self.req._('added relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%(toeid)s') |
163 msg = self.req._('added relation %(rtype)s from %(frometype)s #%(eidfrom)s to %(toetype)s #%(eidto)s') |
213 self.w(msg % self._relation_context(fromeid, rtype, toeid)) |
164 self.w(msg % self._relation_context(changedescr)) |
214 |
165 |
215 def delete_relation(self, fromeid, rtype, toeid): |
166 def delete_relation(self, eidfrom, rtype, eidto): |
216 msg = self.req._('deleted relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%(toeid)s') |
167 msg = self.req._('deleted relation %(rtype)s from %(frometype)s #%(eidfrom)s to %(toetype)s #%(eidto)s') |
217 self.w(msg % self._relation_context(fromeid, rtype, toeid)) |
168 self.w(msg % self._relation_context(changedescr)) |
218 |
169 |
219 |
170 |
220 class SupervisionMailOp(SendMailOp): |
171 class SupervisionMailOp(SendMailOp): |
221 """special send email operation which should be done only once for a bunch |
172 """special send email operation which should be done only once for a bunch |
222 of changes |
173 of changes |