Package trac :: Package notification :: Module model

Source Code for Module trac.notification.model

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (C) 2014-2023 Edgewall Software 
  4  # Copyright (C) 2010 Robert Corsaro 
  5  # All rights reserved. 
  6  # 
  7  # This software is licensed as described in the file COPYING, which 
  8  # you should have received as part of this distribution. The terms 
  9  # are also available at https://trac.edgewall.org/wiki/TracLicense. 
 10  # 
 11  # This software consists of voluntary contributions made by many 
 12  # individuals. For the exact contribution history, see the revision 
 13  # history and logs, available at https://trac.edgewall.org/log/. 
 14   
 15  from trac.util.datefmt import datetime_now, utc, to_utimestamp 
 16   
 17  __all__ = ['Subscription', 'Watch'] 
18 19 20 -class Subscription(object):
21 22 __slots__ = ('env', 'values') 23 24 fields = ('id', 'sid', 'authenticated', 'distributor', 'format', 25 'priority', 'adverb', 'class') 26
27 - def __init__(self, env):
28 self.env = env 29 self.values = {}
30
31 - def __repr__(self):
32 values = ' '.join('%s=%r' % (name, self.values.get(name)) 33 for name in self.fields) 34 return '<%s %s>' % (self.__class__.__name__, values)
35
36 - def __getitem__(self, name):
37 if name not in self.fields: 38 raise KeyError(name) 39 return self.values.get(name)
40
41 - def __setitem__(self, name, value):
42 if name not in self.fields: 43 raise KeyError(name) 44 self.values[name] = value
45
46 - def _from_database(self, id, sid, authenticated, distributor, format, 47 priority, adverb, class_):
48 self['id'] = id 49 self['sid'] = sid 50 self['authenticated'] = int(authenticated) 51 self['distributor'] = distributor 52 self['format'] = format or None 53 self['priority'] = int(priority) 54 self['adverb'] = adverb 55 self['class'] = class_
56 57 @classmethod
58 - def add(cls, env, subscription):
59 """id and priority overwritten.""" 60 with env.db_transaction as db: 61 priority = len(cls.find_by_sid_and_distributor( 62 env, subscription['sid'], subscription['authenticated'], 63 subscription['distributor'])) + 1 64 now = to_utimestamp(datetime_now(utc)) 65 cursor = db.cursor() 66 cursor.execute(""" 67 INSERT INTO 68 notify_subscription (time, changetime, sid, authenticated, 69 distributor, format, priority, adverb, 70 class) 71 VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)""", 72 (now, now, subscription['sid'], int(subscription['authenticated']), 73 subscription['distributor'], subscription['format'] or None, 74 int(priority), subscription['adverb'], 75 subscription['class'])) 76 return db.get_last_id(cursor, 'notify_subscription')
77 78 @classmethod
79 - def delete(cls, env, rule_id, sid=None, authenticated=None):
80 with env.db_transaction as db: 81 kwargs = {'id': rule_id} 82 if sid is not None or authenticated is not None: 83 kwargs['sid'] = sid 84 kwargs['authenticated'] = 1 if authenticated else 0 85 for sub in cls._find(env, **kwargs): 86 break 87 else: 88 return 89 db("DELETE FROM notify_subscription WHERE id=%s", (sub['id'],)) 90 subs = cls.find_by_sid_and_distributor( 91 env, sub['sid'], sub['authenticated'], sub['distributor']) 92 now = to_utimestamp(datetime_now(utc)) 93 values = [(new_priority, now, sub['id']) 94 for new_priority, sub in enumerate(subs, 1) 95 if new_priority != sub['priority']] 96 db.executemany(""" 97 UPDATE notify_subscription 98 SET priority=%s, changetime=%s WHERE id=%s 99 """, values)
100 101 @classmethod
102 - def move(cls, env, rule_id, priority, sid=None, authenticated=None):
103 with env.db_transaction as db: 104 kwargs = {'id': rule_id} 105 if sid is not None or authenticated is not None: 106 kwargs['sid'] = sid 107 kwargs['authenticated'] = 1 if authenticated else 0 108 for sub in cls._find(env, **kwargs): 109 break 110 else: 111 return 112 subs = cls.find_by_sid_and_distributor( 113 env, sub['sid'], sub['authenticated'], sub['distributor']) 114 if not (1 <= priority <= len(subs)): 115 return 116 for idx, sub in enumerate(subs): 117 if sub['id'] == rule_id: 118 break 119 else: 120 return 121 subs.insert(priority - 1, subs.pop(idx)) 122 now = to_utimestamp(datetime_now(utc)) 123 values = [(new_priority, now, sub['id']) 124 for new_priority, sub in enumerate(subs, 1) 125 if new_priority != sub['priority']] 126 db.executemany(""" 127 UPDATE notify_subscription 128 SET priority=%s, changetime=%s WHERE id=%s 129 """, values)
130 131 @classmethod
132 - def replace_all(cls, env, sid, authenticated, subscriptions):
133 authenticated = int(authenticated) 134 with env.db_transaction as db: 135 ids_map = {} 136 for id_, distributor, class_ in db("""\ 137 SELECT id, distributor, class FROM notify_subscription 138 WHERE sid=%s AND authenticated=%s""", 139 (sid, authenticated)): 140 ids_map.setdefault((distributor, class_), []).append(id_) 141 for ids in ids_map.itervalues(): 142 ids.sort(reverse=True) 143 144 priorities = {} 145 now = to_utimestamp(datetime_now(utc)) 146 for sub in subscriptions: 147 distributor = sub['distributor'] 148 priorities.setdefault(distributor, 0) 149 priorities[distributor] += 1 150 prio = priorities[distributor] 151 key = (distributor, sub['class']) 152 if ids_map.get(key): 153 id_ = ids_map[key].pop() 154 db("""\ 155 UPDATE notify_subscription 156 SET changetime=%s,distributor=%s,format=%s,priority=%s, 157 adverb=%s,class=%s 158 WHERE id=%s""", 159 (now, sub['distributor'], sub['format'] or None, prio, 160 sub['adverb'], sub['class'], id_)) 161 else: 162 db("""\ 163 INSERT INTO notify_subscription ( 164 time,changetime,sid,authenticated,distributor, 165 format,priority,adverb,class) 166 VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)""", 167 (now, now, sid, authenticated, sub['distributor'], 168 sub['format'] or None, prio, sub['adverb'], 169 sub['class'])) 170 171 delete_ids = [] 172 for ids in ids_map.itervalues(): 173 delete_ids.extend(ids) 174 if delete_ids: 175 db("DELETE FROM notify_subscription WHERE id IN (%s)" % 176 ','.join(('%s',) * len(delete_ids)), delete_ids)
177 178 179 @classmethod
180 - def update_format_by_distributor_and_sid(cls, env, distributor, sid, 181 authenticated, format):
182 with env.db_transaction as db: 183 db(""" 184 UPDATE notify_subscription 185 SET format=%s 186 WHERE distributor=%s 187 AND sid=%s 188 AND authenticated=%s 189 """, (format or None, distributor, sid, int(authenticated)))
190 191 @classmethod
192 - def _find(cls, env, order=None, **kwargs):
193 with env.db_query as db: 194 conditions = [] 195 args = [] 196 for name, value in sorted(kwargs.iteritems()): 197 if name.endswith('_'): 198 name = name[:-1] 199 if name == 'authenticated': 200 value = int(value) 201 conditions.append(db.quote(name) + '=%s') 202 args.append(value) 203 query = 'SELECT id, sid, authenticated, distributor, format, ' \ 204 'priority, adverb, class FROM notify_subscription' 205 if conditions: 206 query += ' WHERE ' + ' AND '.join(conditions) 207 if order: 208 if not isinstance(order, (tuple, list)): 209 order = (order,) 210 query += ' ORDER BY ' + \ 211 ', '.join(db.quote(name) for name in order) 212 cursor = db.cursor() 213 cursor.execute(query, args) 214 for row in cursor: 215 sub = Subscription(env) 216 sub._from_database(*row) 217 yield sub
218 219 @classmethod
220 - def find_by_sid_and_distributor(cls, env, sid, authenticated, distributor):
221 return list(cls._find(env, sid=sid, authenticated=authenticated, 222 distributor=distributor, order='priority'))
223 224 @classmethod
225 - def find_by_sids_and_class(cls, env, uids, class_):
226 """uids should be a collection to tuples (sid, auth)""" 227 subs = [] 228 for sid, authenticated in uids: 229 subs.extend(cls._find(env, class_=class_, sid=sid, 230 authenticated=authenticated, 231 order='priority')) 232 return subs
233 234 @classmethod
235 - def find_by_class(cls, env, class_):
236 return list(cls._find(env, class_=class_))
237
238 - def subscription_tuple(self):
239 return ( 240 self.values['class'], 241 self.values['distributor'], 242 self.values['sid'], 243 self.values['authenticated'], 244 None, 245 self.values['format'] or None, 246 int(self.values['priority']), 247 self.values['adverb'] 248 )
249
250 - def _update_priority(self):
251 with self.env.db_transaction as db: 252 cursor = db.cursor() 253 now = to_utimestamp(datetime_now(utc)) 254 cursor.execute(""" 255 UPDATE notify_subscription 256 SET changetime=%s, priority=%s 257 WHERE id=%s 258 """, (now, int(self.values['priority']), self.values['id']))
259
260 261 -class Watch(object):
262 263 __slots__ = ('env', 'values') 264 265 fields = ('id', 'sid', 'authenticated', 'class', 'realm', 'target') 266
267 - def __init__(self, env):
268 self.env = env 269 self.values = {}
270
271 - def __getitem__(self, name):
272 if name not in self.fields: 273 raise KeyError(name) 274 return self.values.get(name)
275
276 - def __setitem__(self, name, value):
277 if name not in self.fields: 278 raise KeyError(name) 279 self.values[name] = value
280
281 - def _from_database(self, id, sid, authenticated, class_, realm, target):
282 self['id'] = id 283 self['sid'] = sid 284 self['authenticated'] = int(authenticated) 285 self['class'] = class_ 286 self['realm'] = realm 287 self['target'] = target
288 289 @classmethod
290 - def add(cls, env, sid, authenticated, class_, realm, targets):
291 with env.db_transaction as db: 292 for target in targets: 293 db(""" 294 INSERT INTO notify_watch (sid, authenticated, class, 295 realm, target) 296 VALUES (%s, %s, %s, %s, %s) 297 """, (sid, int(authenticated), class_, realm, target))
298 299 @classmethod
300 - def delete(cls, env, watch_id):
301 with env.db_transaction as db: 302 db("DELETE FROM notify_watch WHERE id = %s", (watch_id,))
303 304 @classmethod
305 - def delete_by_sid_and_class(cls, env, sid, authenticated, class_):
306 with env.db_transaction as db: 307 db(""" 308 DELETE FROM notify_watch 309 WHERE sid = %s AND authenticated = %s AND class = %s 310 """, (sid, int(authenticated), class_))
311 312 @classmethod
313 - def delete_by_class_realm_and_target(cls, env, class_, realm, target):
314 with env.db_transaction as db: 315 db(""" 316 DELETE FROM notify_watch 317 WHERE class = %s AND realm = %s AND target = %s 318 """, (realm, class_, target))
319 320 @classmethod
321 - def _find(cls, env, order=None, **kwargs):
322 with env.db_query as db: 323 conditions = [] 324 args = [] 325 for name, value in sorted(kwargs.iteritems()): 326 if name.endswith('_'): 327 name = name[:-1] 328 if name == 'authenticated': 329 value = int(value) 330 conditions.append(db.quote(name) + '=%s') 331 if name == 'authenticated': 332 value = int(value) 333 args.append(value) 334 query = 'SELECT id, sid, authenticated, class, realm, target ' \ 335 'FROM notify_watch' 336 if conditions: 337 query += ' WHERE ' + ' AND '.join(conditions) 338 if order: 339 if not isinstance(order, (tuple, list)): 340 order = (order,) 341 query += ' ORDER BY ' + \ 342 ', '.join(db.quote(name) for name in order) 343 cursor = db.cursor() 344 cursor.execute(query, args) 345 for row in cursor: 346 watch = Watch(env) 347 watch._from_database(*row) 348 yield watch
349 350 @classmethod
351 - def find_by_sid_and_class(cls, env, sid, authenticated, class_):
352 return list(cls._find(env, sid=sid, authenticated=authenticated, 353 class_=class_, order='target'))
354 355 @classmethod
356 - def find_by_sid_class_realm_and_target(cls, env, sid, authenticated, 357 class_, realm, target):
358 return list(cls._find(env, sid=sid, authenticated=authenticated, 359 class_=class_, realm=realm, order='target'))
360 361 @classmethod
362 - def find_by_class_realm_and_target(cls, env, class_, realm, target):
363 return list(cls._find(env, class_=class_, realm=realm, target=target))
364 365 @classmethod
366 - def find_by_class_and_realm(cls, env, class_, realm):
367 return list(cls._find(env, class_=class_, realm=realm))
368