def get_timeline_events(self, req, start, stop, filters): if ('sensitive_activity' in filters and 'SENSITIVE_ACTIVITY_VIEW' in req.perm and 'SENSITIVE_VIEW' not in req.perm): ts_start = to_utimestamp(start) ts_stop = to_utimestamp(stop) db = self.env.get_db_cnx() cursor = db.cursor() if 'ticket_details' in filters: # only show sensitive ticket changes (edits, closure) if the 'ticket_details' filter is on: cursor.execute(""" SELECT DISTINCT t.id,tc.time,tc.oldvalue FROM ticket_change tc INNER JOIN ticket t ON t.id = tc.ticket AND tc.time >= %s AND tc.time <= %s AND tc.field = %s INNER JOIN ticket_custom td ON t.id = td.ticket AND td.name = %s AND td.value = %s ORDER BY tc.time """, (ts_start, ts_stop, 'comment', 'sensitive', '1')) for tid,t,cid in cursor: yield ('sensitive_activity', from_utimestamp(t), 'redacted', (tid, cid)) # always show new sensitive tickets: cursor.execute(''' SELECT DISTINCT id, time FROM ticket t INNER JOIN ticket_custom tc ON t.id = tc.ticket AND t.time >= %s AND t.time <= %s AND tc.name = %s AND tc.value = %s ORDER BY time ''', (ts_start, ts_stop, 'sensitive', '1')) for tid,t in cursor: yield ('sensitive_activity', from_utimestamp(t), 'redacted', (tid, None))
def get_comment_history(self, cnum, db=None): """Retrieve the edit history of comment `cnum`. :since 0.13: the `db` parameter is no longer needed and will be removed in version 0.14 """ row = self._find_change(cnum) if row: ts0, author0, last_comment = row with self.env.db_query as db: # Get all fields of the form "_comment%d" rows = db("""SELECT field, author, oldvalue, newvalue FROM ticket_change WHERE ticket=%%s AND time=%%s AND field %s """ % db.like(), (self.id, ts0, db.like_escape('_comment') + '%')) rows = sorted((int(field[8:]), author, old, new) for field, author, old, new in rows) history = [] for rev, author, comment, ts in rows: history.append((rev, from_utimestamp(long(ts0)), author0, comment)) ts0, author0 = ts, author history.sort() rev = history[-1][0] + 1 if history else 0 history.append((rev, from_utimestamp(long(ts0)), author0, last_comment)) return history
def get_search_results(self, req, terms, filters): if not 'milestone' in filters: return db = self.env.get_db_cnx() sql_query, args = search_to_sql(db, ['name', 'description'], terms) cursor = db.cursor() cursor.execute( "SELECT name,due,completed,description " "FROM milestone " "WHERE " + sql_query, args) milestone_realm = Resource('milestone') for name, due, completed, description in cursor: milestone = milestone_realm(id=name) if 'MILESTONE_VIEW' in req.perm(milestone): dt = (completed and from_utimestamp(completed) or due and from_utimestamp(due) or datetime.now(utc)) yield (get_resource_url(self.env, milestone, req.href), get_resource_name(self.env, milestone), dt, '', shorten_result(description, terms)) # Attachments for result in AttachmentModule(self.env).get_search_results( req, milestone_realm, terms): yield result
def _from_database(self, row): name, due, completed, description = row self.name = name self.due = from_utimestamp(due) if due else None self.completed = from_utimestamp(completed) if completed else None self.description = description or '' self._to_old()
def select(cls, env, tkt_ids): if not tkt_ids: return {} db = _get_db(env) fields = TicketSystem(env).get_ticket_fields() std_fields = [f['name'] for f in fields if not f.get('custom')] time_fields = [f['name'] for f in fields if f['type'] == 'time'] custom_fields = set(f['name'] for f in fields if f.get('custom')) cursor = db.cursor() tickets = {} cursor.execute( 'SELECT %s,id FROM ticket WHERE %s' % (','.join(std_fields), _tkt_id_conditions('id', tkt_ids))) for row in cursor: id = row[-1] values = {} for idx, field in enumerate(std_fields): value = row[idx] if field in time_fields: value = from_utimestamp(value) elif value is None: value = empty values[field] = value tickets[id] = (values, []) # values, changelog cursor.execute('SELECT ticket,name,value FROM ticket_custom ' 'WHERE %s ORDER BY ticket' % _tkt_id_conditions('ticket', tkt_ids)) for id, rows in groupby(cursor, lambda row: row[0]): if id not in tickets: continue values = {} for id, name, value in rows: if name in custom_fields: if value is None: value = empty values[name] = value tickets[id][0].update(values) cursor.execute('SELECT ticket,time,author,field,oldvalue,newvalue ' 'FROM ticket_change WHERE %s ORDER BY ticket,time' % _tkt_id_conditions('ticket', tkt_ids)) for id, rows in groupby(cursor, lambda row: row[0]): if id not in tickets: continue tickets[id][1].extend( (from_utimestamp(t), author, field, oldvalue or '', newvalue or '', 1) for id, t, author, field, oldvalue, newvalue in rows) return dict((id, cls(env, id, values=values, changelog=changelog, fields=fields, time_fields=time_fields)) for id, (values, changelog) in tickets.iteritems())
def _fetch_ticket(self): rts = RemoteTicketSystem(self.env) db = self.env.get_read_db() cursor = db.cursor() # Try to retrieve remote ticket from cache cursor.execute('''SELECT %s FROM remote_tickets WHERE remote_name=%%s and id=%%s ''' % (', '.join(self.table_fields)), (self.remote_name, self.id)) row = cursor.fetchone() # Remote ticket not in cache if not row: self._refresh_ticket() self._cachetime = from_utimestamp(row[self.cachetime_pos]) ttl = timedelta(seconds=int(rts.cache_ttl) // 1000, microseconds=int(rts.cache_ttl) % 1000 * 1000) # Cached remote ticket is too old if self._cachetime < datetime.now(utc) - ttl: self._refresh_ticket() # Cached ticket is valid, populate instance for name, value in zip(self.remote_fields, row): if name in self.time_fields: self.values[name] = from_utimestamp(value) elif value is None: self.values[name] = empty else: self.values[name] = value
def _fetch_ticket(self): rts = RemoteTicketSystem(self.env) db = self.env.get_read_db() cursor = db.cursor() # Try to retrieve remote ticket from cache cursor.execute( '''SELECT %s FROM remote_tickets WHERE remote_name=%%s and id=%%s ''' % (', '.join(self.table_fields)), (self.remote_name, self.id)) row = cursor.fetchone() # Remote ticket not in cache if not row: self._refresh_ticket() self._cachetime = from_utimestamp(row[self.cachetime_pos]) ttl = timedelta(seconds=int(rts.cache_ttl) // 1000, microseconds=int(rts.cache_ttl) % 1000 * 1000) # Cached remote ticket is too old if self._cachetime < datetime.now(utc) - ttl: self._refresh_ticket() # Cached ticket is valid, populate instance for name, value in zip(self.remote_fields, row): if name in self.time_fields: self.values[name] = from_utimestamp(value) elif value is None: self.values[name] = empty else: self.values[name] = value
def _from_database(self, row): name, due, completed, description = row self.name = name self.due = due and from_utimestamp(due) or None self.completed = completed and from_utimestamp(completed) or None self.description = description or '' self._to_old()
def get_comment_history(self, cnum, db=None): db = self._get_db(db) history = [] cursor = db.cursor() row = self._find_change(cnum, db) if row: ts0, author0, last_comment = row # Get all fields of the form "_comment%d" cursor.execute(""" SELECT field,author,oldvalue,newvalue FROM ticket_change WHERE ticket=%%s AND time=%%s AND field %s """ % db.like(), (self.id, ts0, db.like_escape('_comment') + '%')) rows = sorted((int(field[8:]), author, old, new) for field, author, old, new in cursor) for rev, author, comment, ts in rows: history.append((rev, from_utimestamp(long(ts0)), author0, comment)) ts0, author0 = ts, author history.sort() rev = history and (history[-1][0] + 1) or 0 history.append((rev, from_utimestamp(long(ts0)), author0, last_comment)) return history
def get_data(self, limit=100, offset=0): cursor = self.env.get_db_cnx().cursor() cursor2 = self.env.get_db_cnx().cursor() cursor.execute( "SELECT buildqueue.id, owner, replace(replace(browseurl, '%OWNER%', buildqueue.owner), '%REVISION%', revision), revision, status, startdate, CASE WHEN enddate < startdate THEN startdate ELSE enddate END, description FROM buildqueue, portrepositories WHERE repository = portrepositories.id AND buildqueue.status >= 10 " + self._get_filter() + " ORDER BY buildqueue.id DESC LIMIT %s OFFSET %s", (limit, offset)) for queueid, owner, repository, revision, status, startdate, enddate, description in cursor: build = Build(self.env) build.queueid = queueid build.owner = owner build.repository = repository build.revision = revision build.setStatus(status) build.runtime = pretty_timedelta(from_utimestamp(startdate), from_utimestamp(enddate)) build.startdate = startdate build.enddate = enddate build.description = description cursor2.execute( "SELECT id, buildgroup, portname, pkgversion, status, buildstatus, buildreason, buildlog, wrkdir, startdate, CASE WHEN enddate < startdate THEN extract(epoch from now())*1000000 ELSE enddate END FROM builds WHERE queueid = %s ORDER BY id", (queueid, )) lastport = None for id, group, portname, pkgversion, status, buildstatus, buildreason, buildlog, wrkdir, startdate, enddate in cursor2: port = Port(self.env) port.id = id port.group = group port.portname = portname port.pkgversion = pkgversion port.buildstatus = buildstatus port.buildlog = buildlog port.wrkdir = wrkdir port.runtime = pretty_timedelta(from_utimestamp(startdate), from_utimestamp(enddate)) port.startdate = startdate port.enddate = enddate port.directory = '/~%s/%s-%s' % (owner, queueid, id) if buildstatus: port.buildstatus = buildstatus.lower() if buildstatus and not buildreason: buildreason = buildstatus.lower() port.setStatus(status, buildreason) if self.uniqueports and lastport == portname: continue if lastport != portname: port.head = True lastport = portname build.ports.append(port) yield build
def formatter(self, col, cell_value): if col == 'time': return cell_value != '' and format_time(from_utimestamp(long(cell_value))) or '--' if col in ('date', 'created', 'modified'): return cell_value != '' and format_date(from_utimestamp(long(cell_value))) or '--' if col == 'datetime': return cell_value != '' and format_datetime(from_utimestamp(long(cell_value))) or '--' return cell_value
def render_admin_panel(self, req, cat, page, path_info): req.perm.require('SVNVERIFY_REPORT') rm = RepositoryManager(self.env) all_repos = rm.get_all_repositories() db = self.env.get_read_db() cursor = db.cursor() if path_info: # detailed reponame = not is_default(path_info) and path_info or '' info = all_repos.get(reponame) if info is None: raise TracError(_("Repository '%(repo)s' not found", repo=path_info)) cursor.execute("SELECT type, time, result, log " "FROM svnverify_log WHERE repository_id = %s " "ORDER BY time DESC LIMIT 1", (info['id'],)) row = cursor.fetchone() if row: info['check_type'] = row[0] info['time_checked'] = format_datetime(from_utimestamp(row[1])) info['pretty_status'] = int(row[2]) == 0 and "OK" or "Warning" info['status'] = row[2] info['log'] = row[3] info['prettydir'] = breakable_path(info['dir']) if info['name'] == '': info['name'] = "(default)" return 'svnverify.html', {"info": info} else: repositories = {} for reponame, info in all_repos.iteritems(): if info.get('type',rm.repository_type) == "svn" or (rm.repository_type == 'svn' and info.get('type') == ''): info['prettydir'] = breakable_path(info['dir']) try: r = RepositoryManager(self.env).get_repository(reponame) info['rev'] = r.get_youngest_rev() info['display_rev'] = r.display_rev(info['rev']) except: pass cursor.execute("SELECT type, time, result " "FROM svnverify_log " "WHERE repository_id = %s " "ORDER BY time DESC LIMIT 1", (info['id'],)) row = cursor.fetchone() if row: info['check_type'] = row[0] info['time_checked'] = format_datetime(from_utimestamp(row[1])) info['pretty_status'] = int(row[2]) == 0 and "OK" or "Warning" info['status'] = row[2] repositories[reponame] = info add_stylesheet(req, 'svnverify/css/svnverify.css') return 'svnverifylist.html', {"repositories": repositories}
def get_comment_history(self, cnum=None, cdate=None, db=None): """Retrieve the edit history of a comment identified by its number or date. :since 1.0: the `db` parameter is no longer needed and will be removed in version 1.1.1 """ if cdate is None: row = self._find_change(cnum) if not row: return ts0, author0, last_comment = row else: ts0, author0, last_comment = to_utimestamp(cdate), None, None with self.env.db_query as db: # Get last comment and author if not available if last_comment is None: last_comment = "" for author0, last_comment in db( """ SELECT author, newvalue FROM ticket_change WHERE ticket=%s AND time=%s AND field='comment' """, (self.id, ts0), ): break if author0 is None: for author0, last_comment in db( """ SELECT author, newvalue FROM ticket_change WHERE ticket=%%s AND time=%%s AND NOT field %s LIMIT 1 """ % db.like(), (self.id, ts0, db.like_escape("_") + "%"), ): break else: return # Get all fields of the form "_comment%d" rows = db( """SELECT field, author, oldvalue, newvalue FROM ticket_change WHERE ticket=%%s AND time=%%s AND field %s """ % db.like(), (self.id, ts0, db.like_escape("_comment") + "%"), ) rows = sorted((int(field[8:]), author, old, new) for field, author, old, new in rows) history = [] for rev, author, comment, ts in rows: history.append((rev, from_utimestamp(long(ts0)), author0, comment)) ts0, author0 = ts, author history.sort() rev = history[-1][0] + 1 if history else 0 history.append((rev, from_utimestamp(long(ts0)), author0, last_comment)) return history
def process_request(self, req): id = req.args.getint('id') req.perm('ticket', id).require('TICKET_ADMIN') ticket = Ticket(self.env, id) action = req.args['action'] cnum = req.args.get('cnum') if req.method == 'POST': if 'cancel' in req.args: href = req.href.ticket(id) if action == 'delete-comment': href += '#comment:%s' % cnum req.redirect(href) if action == 'delete': ticket.delete() add_notice( req, _("Ticket #%(num)s and all associated data " "removed.", num=ticket.id)) req.redirect(req.href()) elif action == 'delete-comment': cdate = from_utimestamp(long(req.args.get('cdate'))) ticket.delete_change(cdate=cdate) add_notice( req, _( "The ticket comment %(num)s on ticket " "#%(id)s has been deleted.", num=cnum, id=ticket.id)) req.redirect(req.href.ticket(id)) tm = TicketModule(self.env) data = tm._prepare_data(req, ticket) tm._insert_ticket_data(req, ticket, data, get_reporter_id(req, 'author'), {}) data.update(action=action, cdate=None) if action == 'delete-comment': data['cdate'] = req.args.get('cdate') cdate = from_utimestamp(long(data['cdate'])) for change in data['changes']: if change.get('date') == cdate: data['change'] = change data['cnum'] = change.get('cnum') break else: raise TracError(_("Comment %(num)s not found", num=cnum)) elif action == 'delete': attachments = Attachment.select(self.env, ticket.realm, ticket.id) data.update(attachments=list(attachments)) add_stylesheet(req, 'common/css/ticket.css') return 'ticket_delete.html', data
def get_comment_history(self, cnum=None, cdate=None, db=None): """Retrieve the edit history of a comment identified by its number or date. :since 1.0: the `db` parameter is no longer needed and will be removed in version 1.1.1 """ if cdate is None: row = self._find_change(cnum) if not row: return ts0, author0, last_comment = row else: ts0, author0, last_comment = to_utimestamp(cdate), None, None with self.env.db_query as db: # Get last comment and author if not available if last_comment is None: last_comment = '' for author0, last_comment in db( """ SELECT author, newvalue FROM ticket_change WHERE ticket=%s AND time=%s AND field='comment' """, (self.id, ts0)): break if author0 is None: for author0, last_comment in db( """ SELECT author, newvalue FROM ticket_change WHERE ticket=%%s AND time=%%s AND NOT field %s LIMIT 1 """ % db.like(), (self.id, ts0, db.like_escape('_') + '%')): break else: return # Get all fields of the form "_comment%d" rows = db( """SELECT field, author, oldvalue, newvalue FROM ticket_change WHERE ticket=%%s AND time=%%s AND field %s """ % db.like(), (self.id, ts0, db.like_escape('_comment') + '%')) rows = sorted((int(field[8:]), author, old, new) for field, author, old, new in rows) history = [] for rev, author, comment, ts in rows: history.append( (rev, from_utimestamp(long(ts0)), author0, comment)) ts0, author0 = ts, author history.sort() rev = history[-1][0] + 1 if history else 0 history.append( (rev, from_utimestamp(long(ts0)), author0, last_comment)) return history
def formatter(self, col, cell_value): if col == 'time': return cell_value != '' and format_time( from_utimestamp(long(cell_value))) or '--' if col in ('date', 'created', 'modified'): return cell_value != '' and format_date( from_utimestamp(long(cell_value))) or '--' if col == 'datetime': return cell_value != '' and format_datetime( from_utimestamp(long(cell_value))) or '--' return cell_value
def parse_options(env, content, options): """Parses the parameters, makes some sanity checks, and creates default values for missing parameters. """ _, parsed_options = parse_args(content, strict=False) options.update(parsed_options) today = datetime.now().date() startdatearg = options.get('startdate') if startdatearg: options['startdate'] = \ datetime(*strptime(startdatearg, "%Y-%m-%d")[0:5]).date() enddatearg = options.get('enddate') options['enddate'] = None if enddatearg: options['enddate'] = \ datetime(*strptime(enddatearg, "%Y-%m-%d")[0:5]).date() if not options['enddate'] and options.get('milestone'): # use first milestone milestone = options['milestone'].split('|')[0] # try to get end date from db for completed, due in env.db_query( """ SELECT completed, due FROM milestone WHERE name = %s """, (milestone, )): if completed: options['enddate'] = from_utimestamp(completed).date() elif due: due = from_utimestamp(due).date() if due >= today: options['enddate'] = due break else: raise TracError("Couldn't find milestone %s" % milestone) options['enddate'] = options['enddate'] or today options['today'] = options.get('today') or today if options.get('weekends'): options['weekends'] = parse_bool(options['weekends']) if options.get('spent'): options['spent'] = parse_bool(options['spent']) # all arguments that are no key should be treated as part of the query query_args = {} for key in options.keys(): if key not in AVAILABLE_OPTIONS: query_args[key] = options[key] return options, query_args
def parse_options(env, content, options): """Parses the parameters, makes some sanity checks, and creates default values for missing parameters. """ _, parsed_options = parse_args(content, strict=False) options.update(parsed_options) today = datetime.now().date() startdatearg = options.get('startdate') if startdatearg: options['startdate'] = \ datetime(*strptime(startdatearg, "%Y-%m-%d")[0:5]).date() enddatearg = options.get('enddate') options['enddate'] = None if enddatearg: options['enddate'] = \ datetime(*strptime(enddatearg, "%Y-%m-%d")[0:5]).date() if not options['enddate'] and options.get('milestone'): # use first milestone milestone = options['milestone'].split('|')[0] # try to get end date from db for completed, due in env.db_query(""" SELECT completed, due FROM milestone WHERE name = %s """, (milestone,)): if completed: options['enddate'] = from_utimestamp(completed).date() elif due: due = from_utimestamp(due).date() if due >= today: options['enddate'] = due break else: raise TracError("Couldn't find milestone %s" % milestone) options['enddate'] = options['enddate'] or today options['today'] = options.get('today') or today if options.get('weekends'): options['weekends'] = parse_bool(options['weekends']) if options.get('spent'): options['spent'] = parse_bool(options['spent'] ) # all arguments that are no key should be treated as part of the query query_args = {} for key in options.keys(): if key not in AVAILABLE_OPTIONS: query_args[key] = options[key] return options, query_args
def process_request(self, req): id = int(req.args.get('id')) req.perm('ticket', id).require('TICKET_ADMIN') ticket = Ticket(self.env, id) action = req.args['action'] cnum = req.args.get('cnum') if req.method == 'POST': if 'cancel' in req.args: href = req.href.ticket(id) if action == 'delete-comment': href += '#comment:%s' % cnum req.redirect(href) if action == 'delete': ticket.delete() add_notice(req, _('The ticket #%(id)s has been deleted.', id=ticket.id)) req.redirect(req.href()) elif action == 'delete-comment': cdate = from_utimestamp(long(req.args.get('cdate'))) ticket.delete_change(cdate=cdate) add_notice(req, _('The ticket comment %(num)s on ticket ' '#%(id)s has been deleted.', num=cnum, id=ticket.id)) req.redirect(req.href.ticket(id)) tm = TicketModule(self.env) data = tm._prepare_data(req, ticket) tm._insert_ticket_data(req, ticket, data, get_reporter_id(req, 'author'), {}) data.update(action=action, cdate=None) if action == 'delete-comment': data['cdate'] = req.args.get('cdate') cdate = from_utimestamp(long(data['cdate'])) for change in data['changes']: if change.get('date') == cdate: data['change'] = change data['cnum'] = change.get('cnum') break else: raise TracError(_('Comment %(num)s not found', num=cnum)) elif action == 'delete': attachments = Attachment.select(self.env, ticket.realm, ticket.id) data.update(attachments=list(attachments)) add_stylesheet(req, 'common/css/ticket.css') return 'ticket_delete.html', data, None
def milestones(self): """Dictionary containing milestone data, indexed by name. Milestone data consist of a tuple containing the name, the datetime objects for due and completed dates and the description. """ milestones = {} for name, due, completed, description in self.env.db_query(""" SELECT name, due, completed, description FROM milestone """): milestones[name] = (name, from_utimestamp(due) if due else None, from_utimestamp(completed) if completed else None, description or '') return milestones
def get_comment_history(self, cnum=None, cdate=None): """Retrieve the edit history of a comment identified by its number or date. """ if cdate is None: row = self._find_change(cnum) if not row: return ts0, author0, last_comment = row else: ts0, author0, last_comment = to_utimestamp(cdate), None, None with self.env.db_query as db: # Get last comment and author if not available if last_comment is None: last_comment = '' for author0, last_comment in db(""" SELECT author, newvalue FROM ticket_change WHERE ticket=%s AND time=%s AND field='comment' """, (self.id, ts0)): break if author0 is None: for author0, last_comment in db(""" SELECT author, newvalue FROM ticket_change WHERE ticket=%%s AND time=%%s AND NOT field %s LIMIT 1 """ % db.prefix_match(), (self.id, ts0, db.prefix_match_value('_'))): break else: return # Get all fields of the form "_comment%d" rows = db("""SELECT field, author, oldvalue, newvalue FROM ticket_change WHERE ticket=%%s AND time=%%s AND field %s """ % db.prefix_match(), (self.id, ts0, db.prefix_match_value('_comment'))) rows = sorted((int(field[8:]), author, old, new) for field, author, old, new in rows) history = [] for rev, author, comment, ts in rows: history.append((rev, from_utimestamp(long(ts0)), author0, comment)) ts0, author0 = ts, author history.sort() rev = history[-1][0] + 1 if history else 0 history.append((rev, from_utimestamp(long(ts0)), author0, last_comment)) return history
def get_change(self, cnum=None, cdate=None, db=None): """Return a ticket change by its number or date. :since 1.0: the `db` parameter is no longer needed and will be removed in version 1.1.1 """ if cdate is None: row = self._find_change(cnum) if not row: return cdate = from_utimestamp(row[0]) ts = to_utimestamp(cdate) fields = {} change = {'date': cdate, 'fields': fields} for field, author, old, new in self.env.db_query(""" SELECT field, author, oldvalue, newvalue FROM ticket_change WHERE ticket=%s AND time=%s """, (self.id, ts)): fields[field] = {'author': author, 'old': old, 'new': new} if field == 'comment': change['author'] = author elif not field.startswith('_'): change.setdefault('author', author) if fields: return change
def _fetch_ticket(self, tkt_id): row = None if self.id_is_valid(tkt_id): # Fetch the standard ticket fields for row in self.env.db_query("SELECT %s FROM ticket WHERE id=%%s" % ','.join(self.std_fields), (tkt_id,)): break if not row: raise ResourceNotFound(_("Ticket %(id)s does not exist.", id=tkt_id), _("Invalid ticket number")) self.id = tkt_id for i, field in enumerate(self.std_fields): value = row[i] if field in self.time_fields: self.values[field] = from_utimestamp(value) elif value is None: self.values[field] = empty else: self.values[field] = value # Fetch custom fields if available for name, value in self.env.db_query(""" SELECT name, value FROM ticket_custom WHERE ticket=%s """, (tkt_id,)): if name in self.custom_fields: if name in self.time_fields: self.values[name] = _db_str_to_datetime(value) elif value is None: self.values[name] = empty else: self.values[name] = value
def get_search_results(self, req, terms, filters): if not 'wiki' in filters: return with self.env.db_query as db: sql_query, args = search_to_sql( db, ['w1.name', 'w1.author', 'w1.text'], terms) wiki_realm = Resource('wiki') for name, ts, author, text in db( """ SELECT w1.name, w1.time, w1.author, w1.text FROM wiki w1,(SELECT name, max(version) AS ver FROM wiki GROUP BY name) w2 WHERE w1.version = w2.ver AND w1.name = w2.name AND """ + sql_query, args): page = wiki_realm(id=name) if 'WIKI_VIEW' in req.perm(page): yield (get_resource_url(self.env, page, req.href), '%s: %s' % (name, shorten_line(text)), from_utimestamp(ts), author, shorten_result(text, terms)) # Attachments for result in AttachmentModule(self.env).get_search_results( req, wiki_realm, terms): yield result
def _from_database(self, filename, description, size, time, author, ipnr): self.filename = filename self.description = description self.size = int(size) if size else 0 self.date = from_utimestamp(time or 0) self.author = author self.ipnr = ipnr
def _fetch_message(self, name): """ Retrieves data representing an individual project message from the database, and unpacks this into a values dictionary. If the name provided does not match a row in the project_message table, we raise a ResourceNotFound exception.""" db = self.env.get_read_db() cursor = db.cursor() cursor.execute("""SELECT name, message, button, mode, groups, start, "end", author, created_at FROM project_message WHERE name=%s""", (name,)) row = cursor.fetchone() if row: for field, value in izip(self.message_keys, row): if field == 'groups': self.values[field] = json.loads(value) if field in ['start', 'date']: self.values[field] = from_utimestamp(value) else: self.values[field] = value else: raise ResourceNotFound("Project message '%s' does not exist.", (name))
def get_search_results(self, req, terms, filters): if not 'wiki' in filters: return db = self.env.get_db_cnx() sql_query, args = search_to_sql(db, ['w1.name', 'w1.author', 'w1.text'], terms) cursor = db.cursor() cursor.execute("SELECT w1.name,w1.time,w1.author,w1.text " "FROM wiki w1," "(SELECT name,max(version) AS ver " "FROM wiki GROUP BY name) w2 " "WHERE w1.version = w2.ver AND w1.name = w2.name " "AND " + sql_query, args) wiki_realm = Resource('wiki') for name, ts, author, text in cursor: page = wiki_realm(id=name) if 'WIKI_VIEW' in req.perm(page): yield (get_resource_url(self.env, page, req.href), '%s: %s' % (name, shorten_line(text)), from_utimestamp(ts), author, shorten_result(text, terms)) # Attachments for result in AttachmentModule(self.env).get_search_results( req, wiki_realm, terms): yield result
def get_change(self, cnum=None, cdate=None, db=None): """Return a ticket change by its number or date. :since 1.0: the `db` parameter is no longer needed and will be removed in version 1.1.1 """ if cdate is None: row = self._find_change(cnum) if not row: return cdate = from_utimestamp(row[0]) ts = to_utimestamp(cdate) fields = {} change = {"date": cdate, "fields": fields} for field, author, old, new in self.env.db_query( """ SELECT field, author, oldvalue, newvalue FROM ticket_change WHERE ticket=%s AND time=%s """, (self.id, ts), ): fields[field] = {"author": author, "old": old, "new": new} if field == "comment": change["author"] = author elif not field.startswith("_"): change.setdefault("author", author) if fields: return change
def _fetch(self, name, version=None, db=None): if not db: db = self.env.get_db_cnx() cursor = db.cursor() if version is not None: cursor.execute("SELECT version,time,author,text,comment,readonly " "FROM wiki " "WHERE name=%s AND version=%s", (name, int(version))) else: cursor.execute("SELECT version,time,author,text,comment,readonly " "FROM wiki " "WHERE name=%s ORDER BY version DESC LIMIT 1", (name,)) row = cursor.fetchone() if row: version, time, author, text, comment, readonly = row self.version = int(version) self.author = author self.time = from_utimestamp(time) self.text = text self.comment = comment self.readonly = readonly and int(readonly) or 0 else: self.version = 0 self.text = self.comment = self.author = '' self.time = None self.readonly = 0
def _fetch_ticket(self, tkt_id): row = None if self.id_is_valid(tkt_id): # Fetch the standard ticket fields for row in self.env.db_query( "SELECT %s FROM ticket WHERE id=%%s" % ','.join(self.std_fields), (tkt_id, )): break if not row: raise ResourceNotFound( _("Ticket %(id)s does not exist.", id=tkt_id), _("Invalid ticket number")) self.id = tkt_id for i, field in enumerate(self.std_fields): value = row[i] if field in self.time_fields: self.values[field] = from_utimestamp(value) elif value is None: self.values[field] = empty else: self.values[field] = value # Fetch custom fields if available for name, value in self.env.db_query( """ SELECT name, value FROM ticket_custom WHERE ticket=%s """, (tkt_id, )): if name in self.custom_fields: if name in self.time_fields: self.values[name] = _db_str_to_datetime(value) elif value is None: self.values[name] = empty else: self.values[name] = value
def get_change(self, cnum=None, cdate=None, db=None): """Return a ticket change by its number or date. :since 1.0: the `db` parameter is no longer needed and will be removed in version 1.1.1 """ if cdate is None: row = self._find_change(cnum) if not row: return cdate = from_utimestamp(row[0]) ts = to_utimestamp(cdate) fields = {} change = {'date': cdate, 'fields': fields} for field, author, old, new in self.env.db_query( """ SELECT field, author, oldvalue, newvalue FROM ticket_change WHERE ticket=%s AND time=%s """, (self.id, ts)): fields[field] = {'author': author, 'old': old, 'new': new} if field == 'comment': change['author'] = author elif not field.startswith('_'): change.setdefault('author', author) if fields: return change
def _fetch(self, name, version=None, db=None): if not db: db = self.env.get_db_cnx() cursor = db.cursor() if version is not None: cursor.execute( "SELECT version,time,author,text,comment,readonly " "FROM wiki " "WHERE name=%s AND version=%s", (name, int(version))) else: cursor.execute( "SELECT version,time,author,text,comment,readonly " "FROM wiki " "WHERE name=%s ORDER BY version DESC LIMIT 1", (name, )) row = cursor.fetchone() if row: version, time, author, text, comment, readonly = row self.version = int(version) self.author = author self.time = from_utimestamp(time) self.text = text self.comment = comment self.readonly = readonly and int(readonly) or 0 else: self.version = 0 self.text = self.comment = self.author = '' self.time = None self.readonly = 0
def get_history(self, item, db=None): if not item in self.pool.get_items(): raise Exception("Item not in pool") if item.is_new(): return if db is None: db = self.env.get_db_cnx() cursor = db.cursor() query = """ SELECT v.id, v.time, v.author, v.ipnr, v.comment FROM asa_version v""" if isinstance(item, Entity): # it's a spec query += """ INNER JOIN asa_spec s ON s.version_id=v.id WHERE s.name='%s' """ % (item.get_name()) else: # it's an artifact query += """ INNER JOIN asa_artifact a ON a.version_id=v.id WHERE a.id=%d""" % (item.get_id()) if not self.version is None: query += ' AND v.id <= %s' % (self.version,) query += ' ORDER BY v.id DESC' cursor.execute(query) for version, ts, author, ipnr, comment in cursor: yield version, from_utimestamp(ts), author, ipnr, comment
def get_history(self, item, db=None): if not item in self.pool.get_items(): raise Exception("Item not in pool") if item.is_new(): return if db is None: db = self.env.get_db_cnx() cursor = db.cursor() query = """ SELECT v.id, v.time, v.author, v.ipnr, v.comment FROM asa_version v""" if isinstance(item, Entity): # it's a spec query += """ INNER JOIN asa_spec s ON s.version_id=v.id WHERE s.name='%s' """ % ( item.get_name() ) else: # it's an artifact query += """ INNER JOIN asa_artifact a ON a.version_id=v.id WHERE a.id=%d""" % ( item.get_id() ) if not self.version is None: query += " AND v.id <= %s" % (self.version,) query += " ORDER BY v.id DESC" cursor.execute(query) for version, ts, author, ipnr, comment in cursor: yield version, from_utimestamp(ts), author, ipnr, comment
def delete_change(self, cnum=None, cdate=None): """Delete a ticket change identified by its number or date.""" if cdate is None: row = self._find_change(cnum) if not row: return cdate = from_utimestamp(row[0]) ts = to_utimestamp(cdate) with self.env.db_transaction as db: # Find modified fields and their previous value fields = [(field, old, new) for field, old, new in db( """ SELECT field, oldvalue, newvalue FROM ticket_change WHERE ticket=%s AND time=%s """, (self.id, ts)) if field != 'comment' and not field.startswith('_')] for field, oldvalue, newvalue in fields: # Find the next change for next_ts, in db( """SELECT time FROM ticket_change WHERE ticket=%s AND time>%s AND field=%s LIMIT 1 """, (self.id, ts, field)): # Modify the old value of the next change if it is equal # to the new value of the deleted change db( """UPDATE ticket_change SET oldvalue=%s WHERE ticket=%s AND time=%s AND field=%s AND oldvalue=%s """, (oldvalue, self.id, next_ts, field, newvalue)) break else: # No next change, edit ticket field if field in self.custom_fields: db( """UPDATE ticket_custom SET value=%s WHERE ticket=%s AND name=%s """, (oldvalue, self.id, field)) else: db("UPDATE ticket SET %s=%%s WHERE id=%%s" % field, (oldvalue, self.id)) # Delete the change db("DELETE FROM ticket_change WHERE ticket=%s AND time=%s", (self.id, ts)) # Fix the last modification time # Work around MySQL ERROR 1093 with the same table for the update # target and the subquery FROM clause db( """UPDATE ticket SET changetime=( SELECT time FROM ticket_change WHERE ticket=%s UNION SELECT time FROM ( SELECT time FROM ticket WHERE id=%s LIMIT 1) AS t ORDER BY time DESC LIMIT 1) WHERE id=%s """, (self.id, self.id, self.id)) self._fetch_ticket(self.id)
def get_history(self): """Retrieve the edit history of a wiki page. """ for version, ts, author, comment, ipnr in self.env.db_query(""" SELECT version, time, author, comment, ipnr FROM wiki WHERE name=%s AND version<=%s ORDER BY version DESC """, (self.name, self.version)): yield version, from_utimestamp(ts), author, comment, ipnr
def GlobalBuildqueueIterator(env, req): cursor = env.get_db_cnx().cursor() if req.args.get('group'): group = req.args.get('group') else: group = '' cursor.execute( "SELECT builds.id, builds.buildgroup, builds.portname, builds.pkgversion, builds.status, builds.buildstatus, builds.buildreason, builds.buildlog, builds.wrkdir, builds.startdate, CASE WHEN builds.enddate < builds.startdate THEN extract(epoch from now())*1000000 ELSE builds.enddate END, buildqueue.id, buildqueue.priority, buildqueue.owner FROM builds, buildqueue WHERE buildqueue.id = builds.queueid AND builds.status < 90 AND (builds.buildgroup = %s OR %s = '') ORDER BY builds.status DESC, buildqueue.priority, builds.id DESC LIMIT 50", (group, group)) lastport = None for id, group, portname, pkgversion, status, buildstatus, buildreason, buildlog, wrkdir, startdate, enddate, queueid, priority, owner in cursor: port = Port(env) port.id = id port.group = group port.portname = portname port.pkgversion = pkgversion port.buildstatus = buildstatus port.buildlog = buildlog port.wrkdir = wrkdir port.runtime = pretty_timedelta(from_utimestamp(startdate), from_utimestamp(enddate)) port.startdate = startdate port.enddate = enddate port.directory = '/~%s/%s-%s' % (owner, queueid, id) port.queueid = queueid port.owner = owner port.setPriority(priority) if buildstatus: port.buildstatus = buildstatus.lower() if buildstatus and not buildreason: buildreason = buildstatus.lower() if owner == req.authname or status != 20: port.highlight = True port.setStatus(status, buildreason) if lastport != portname: port.head = True lastport = portname yield port
def get_timeline_events(self, req, start, stop, filters): if 'project changes' in filters: cnx=self.env.get_db_cnx() cur=cnx.cursor() cur.execute("select who,change,time from project_change where time>=%s AND time<=%s"%\ (to_utimestamp(start), to_utimestamp(stop))); for who,change,ts in cur: yield('project',from_utimestamp(ts),who,change)
def _populate_from_database(self, row): """ Takes a row returned from a cursor and populates instance attributes based on these values.""" (name, message, button, mode, groups, start, end, author, created_at) = row self['name'] = name self['message'] = message self['button'] = button self['mode'] = mode self['groups'] = json.loads(groups) if groups else None self['start'] = from_utimestamp(start) self['end'] = from_utimestamp(end) self['author'] = author self['created_at'] = from_utimestamp(created_at)
def test_get_all_records(self): record = self._create_new_record() record.insert() all_records = ProjectMessageRecord.get_all_records(self.env) self.assertEqual(1, len(all_records)) self.assertEqual(1, all_records[0]['record_id']) self.assertEqual("Test Case", all_records[0]['message_name']) self.assertEqual("milsomd", all_records[0]['agreed_by']) self.assertEqual(from_utimestamp(1396975221114382), all_records[0]['agreed_at']) record2 = self._create_new_record_two() record2.insert() all_records = ProjectMessageRecord.get_all_records(self.env) self.assertEqual(2, len(all_records)) self.assertEqual(2, all_records[1]['record_id']) self.assertEqual("Another Test Case", all_records[1]['message_name']) self.assertEqual("goldinge", all_records[1]['agreed_by']) self.assertEqual(from_utimestamp(1396975221114388), all_records[1]['agreed_at'])
def _do_list(self): print_table( [(title, int(edits), format_datetime(from_utimestamp(modified), console_datetime_format)) for title, edits, modified in self.env.db_query(""" SELECT name, max(version), max(time) FROM wiki GROUP BY name ORDER BY name""") ], [_("Title"), _("Edits"), _("Modified")])
def delete_change(self, cnum=None, cdate=None, when=None): """Delete a ticket change identified by its number or date.""" if cdate is None: row = self._find_change(cnum) if not row: return cdate = from_utimestamp(row[0]) ts = to_utimestamp(cdate) if when is None: when = datetime.now(utc) when_ts = to_utimestamp(when) with self.env.db_transaction as db: # Find modified fields and their previous value fields = [(field, old, new) for field, old, new in db(""" SELECT field, oldvalue, newvalue FROM ticket_change WHERE ticket=%s AND time=%s """, (self.id, ts)) if field != 'comment' and not field.startswith('_')] for field, oldvalue, newvalue in fields: # Find the next change for next_ts, in db("""SELECT time FROM ticket_change WHERE ticket=%s AND time>%s AND field=%s LIMIT 1 """, (self.id, ts, field)): # Modify the old value of the next change if it is equal # to the new value of the deleted change db("""UPDATE ticket_change SET oldvalue=%s WHERE ticket=%s AND time=%s AND field=%s AND oldvalue=%s """, (oldvalue, self.id, next_ts, field, newvalue)) break else: # No next change, edit ticket field if field in self.std_fields: db("UPDATE ticket SET %s=%%s WHERE id=%%s" % field, (oldvalue, self.id)) else: db("""UPDATE ticket_custom SET value=%s WHERE ticket=%s AND name=%s """, (oldvalue, self.id, field)) # Delete the change db("DELETE FROM ticket_change WHERE ticket=%s AND time=%s", (self.id, ts)) # Update last changed time db("UPDATE ticket SET changetime=%s WHERE id=%s", (when_ts, self.id)) self._fetch_ticket(self.id) changes = dict((field, (oldvalue, newvalue)) for field, oldvalue, newvalue in fields) for listener in TicketSystem(self.env).change_listeners: if hasattr(listener, 'ticket_change_deleted'): listener.ticket_change_deleted(self, cdate, changes)
def ticket_activity_user(project_id, username, start_date, end_date, groupsize, groupcnt, db, req): """ Get query response for specified time interval and `username`: Data: <event>: <count events>. Events: 'created', 'closed'. """ q = ''' SELECT t.id, t.time, 'created' AS event FROM ticket t WHERE t.reporter=%s AND t.project_id=%s AND t.time >= %s AND t.time < %s UNION SELECT t.id, tc.time, 'closed' AS event FROM ticket t JOIN ticket_change tc ON t.id = tc.ticket AND tc.field='status' AND tc.newvalue='closed' WHERE t.owner=%s AND t.project_id=%s AND tc.time >= %s AND tc.time < %s ORDER BY event ''' cursor = db.cursor() cursor.execute(q, (username, project_id, to_utimestamp(start_date), to_utimestamp(end_date))*2) etypes = (N_('created'), N_('closed')) events = [(r[2], from_utimestamp(r[1]), r[0]) for r in cursor] # TODO: count closed once, use global closed set def init_set(e): return set() def add_to_set(stor, idx, event_data): stor[idx].add(event_data[2]) groups_list, groups_data = aggregate_events_by_periods(etypes, events, start_date, groupsize, groupcnt, add_to_set, init_set) for etype, groups in groups_data.iteritems(): for idx, ids in enumerate(groups): groups[idx] = len(ids) query_response = QueryResponse("ticket_activity", req.href('/chrome')) query_response.set_title(_("Ticket activity from %(start_date)s to %(end_date)s", start_date=format_date(start_date, tzinfo=req.tz), end_date=format_date(end_date, tzinfo=req.tz))) groups_data = translate_keys(groups_data) columns, rows = adapt_to_table(groups_list, groups_data) query_response.set_columns(columns) query_response.set_results(rows) chart = query_response.chart_info chart.type = 'Line' chart.width = 600 chart.x_legend = _('Time periods') chart.y_legend = _('Tickets') chart.x_labels = groups_list chart.data = restructure_data(groups_data) chart.tool_tip = "#key#<br>%s:#x_label#<br>%s:#val#" % (_('period'), _('tickets')) return query_response
def get_history(self, db=None): if not db: db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT version,time,author,comment,ipnr FROM wiki " "WHERE name=%s AND version<=%s " "ORDER BY version DESC", (self.name, self.version)) for version, ts, author, comment, ipnr in cursor: yield version, from_utimestamp(ts), author, comment, ipnr
def test_get_term(self): record = self._create_new_record() record.insert() # retrieve data from db retrieved_record = ProjectMessageRecord(self.env, 1) self.assertEqual(1, retrieved_record['record_id']) self.assertEqual("Test Case", retrieved_record['message_name']) self.assertEqual("milsomd", retrieved_record['agreed_by']) self.assertEqual(from_utimestamp(1396975221114382), retrieved_record['agreed_at'])
def get_last_modified(self): """Retrieve timestamp of last modification, in micro-seconds. (wraps ``fs.revision_prop``) """ _date = fs.revision_prop(self.fs_ptr, self.created_rev, core.SVN_PROP_REVISION_DATE, self.pool()) if not _date: return None return from_utimestamp(core.svn_time_from_cstring(_date, self.pool()))
def _do_list(self): db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT name, max(version), max(time) " "FROM wiki GROUP BY name ORDER BY name") print_table([(r[0], int(r[1]), format_datetime(from_utimestamp(r[2]), console_datetime_format)) for r in cursor], [_('Title'), _('Edits'), _('Modified')])
def get_history(self, db=None): if not db: db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute( "SELECT version,time,author,comment,ipnr FROM wiki " "WHERE name=%s AND version<=%s " "ORDER BY version DESC", (self.name, self.version)) for version, ts, author, comment, ipnr in cursor: yield version, from_utimestamp(ts), author, comment, ipnr
def delete_change(self, cnum=None, cdate=None, when=None): """Delete a ticket change identified by its number or date.""" if cdate is None: row = self._find_change(cnum) if not row: return cdate = from_utimestamp(row[0]) ts = to_utimestamp(cdate) if when is None: when = datetime.now(utc) when_ts = to_utimestamp(when) with self.env.db_transaction as db: # Find modified fields and their previous value fields = [(field, old, new) for field, old, new in db( """ SELECT field, oldvalue, newvalue FROM ticket_change WHERE ticket=%s AND time=%s """, (self.id, ts)) if field != 'comment' and not field.startswith('_')] for field, oldvalue, newvalue in fields: # Find the next change for next_ts, in db( """SELECT time FROM ticket_change WHERE ticket=%s AND time>%s AND field=%s LIMIT 1 """, (self.id, ts, field)): # Modify the old value of the next change if it is equal # to the new value of the deleted change db( """UPDATE ticket_change SET oldvalue=%s WHERE ticket=%s AND time=%s AND field=%s AND oldvalue=%s """, (oldvalue, self.id, next_ts, field, newvalue)) break else: # No next change, edit ticket field if field in self.std_fields: db("UPDATE ticket SET %s=%%s WHERE id=%%s" % field, (oldvalue, self.id)) else: db( """UPDATE ticket_custom SET value=%s WHERE ticket=%s AND name=%s """, (oldvalue, self.id, field)) # Delete the change db("DELETE FROM ticket_change WHERE ticket=%s AND time=%s", (self.id, ts)) # Update last changed time db("UPDATE ticket SET changetime=%s WHERE id=%s", (when_ts, self.id)) self._fetch_ticket(self.id)
def delete_change(self, cnum=None, cdate=None): """Delete a ticket change identified by its number or date.""" if cdate is None: row = self._find_change(cnum) if not row: return cdate = from_utimestamp(row[0]) ts = to_utimestamp(cdate) with self.env.db_transaction as db: # Find modified fields and their previous value fields = [(field, old, new) for field, old, new in db(""" SELECT field, oldvalue, newvalue FROM ticket_change WHERE ticket=%s AND time=%s """, (self.id, ts)) if field != 'comment' and not field.startswith('_')] for field, oldvalue, newvalue in fields: # Find the next change for next_ts, in db("""SELECT time FROM ticket_change WHERE ticket=%s AND time>%s AND field=%s LIMIT 1 """, (self.id, ts, field)): # Modify the old value of the next change if it is equal # to the new value of the deleted change db("""UPDATE ticket_change SET oldvalue=%s WHERE ticket=%s AND time=%s AND field=%s AND oldvalue=%s """, (oldvalue, self.id, next_ts, field, newvalue)) break else: # No next change, edit ticket field if field in self.custom_fields: db("""UPDATE ticket_custom SET value=%s WHERE ticket=%s AND name=%s """, (oldvalue, self.id, field)) else: db("UPDATE ticket SET %s=%%s WHERE id=%%s" % field, (oldvalue, self.id)) # Delete the change db("DELETE FROM ticket_change WHERE ticket=%s AND time=%s", (self.id, ts)) # Fix the last modification time # Work around MySQL ERROR 1093 with the same table for the update # target and the subquery FROM clause db("""UPDATE ticket SET changetime=( SELECT time FROM ticket_change WHERE ticket=%s UNION SELECT time FROM ( SELECT time FROM ticket WHERE id=%s LIMIT 1) AS t ORDER BY time DESC LIMIT 1) WHERE id=%s """, (self.id, self.id, self.id)) self._fetch_ticket(self.id)
def get_history(self): """Retrieve the edit history of a wiki page. :return: a tuple containing the `version`, `datetime`, `author` and `comment`. """ for version, ts, author, comment in self.env.db_query(""" SELECT version, time, author, comment FROM wiki WHERE name=%s AND version<=%s ORDER BY version DESC """, (self.name, self.version)): yield version, from_utimestamp(ts), author, comment
def get_history(self, db=None): """Retrieve the edit history of a wiki page. :since 1.0: the `db` parameter is no longer needed and will be removed in version 1.1.1 """ for version, ts, author, comment, ipnr in self.env.db_query(""" SELECT version, time, author, comment, ipnr FROM wiki WHERE name=%s AND version<=%s ORDER BY version DESC """, (self.name, self.version)): yield version, from_utimestamp(ts), author, comment, ipnr
def _db_str_to_datetime(value): if value is None: return None try: return from_utimestamp(long(value)) except ValueError: pass try: return parse_date(value.strip(), utc, 'datetime') except Exception: return None
def __init__(self, repos, rev, env): self.env = env for _date, author, message in self.env.db_query(""" SELECT time, author, message FROM revision WHERE repos=%s AND rev=%s """, (repos.id, repos.db_rev(rev))): date = from_utimestamp(_date) Changeset.__init__(self, repos, repos.rev_db(rev), message, author, date) break else: raise NoSuchChangeset(rev)
def get_changelog(self, when=None, db=None): """Return the changelog as a list of tuples of the form (time, author, field, oldvalue, newvalue, permanent). While the other tuple elements are quite self-explanatory, the `permanent` flag is used to distinguish collateral changes that are not yet immutable (like attachments, currently). :since 1.0: the `db` parameter is no longer needed and will be removed in version 1.1.1 """ sid = str(self.id) when_ts = to_utimestamp(when) if when_ts: sql = """ SELECT time, author, field, oldvalue, newvalue, 1 AS permanent FROM ticket_change WHERE ticket=%s AND time=%s UNION SELECT time, author, 'attachment', null, filename, 0 AS permanent FROM attachment WHERE type='ticket' AND id=%s AND time=%s UNION SELECT time, author, 'comment', null, description, 0 AS permanent FROM attachment WHERE type='ticket' AND id=%s AND time=%s ORDER BY time,permanent,author """ args = (self.id, when_ts, sid, when_ts, sid, when_ts) else: sql = """ SELECT time, author, field, oldvalue, newvalue, 1 AS permanent FROM ticket_change WHERE ticket=%s UNION SELECT time, author, 'attachment', null, filename, 0 AS permanent FROM attachment WHERE type='ticket' AND id=%s UNION SELECT time, author, 'comment', null, description, 0 AS permanent FROM attachment WHERE type='ticket' AND id=%s ORDER BY time,permanent,author """ args = (self.id, sid, sid) log = [] for t, author, field, oldvalue, newvalue, permanent \ in self.env.db_query(sql, args): if field in self.time_fields: oldvalue = _db_str_to_datetime(oldvalue) newvalue = _db_str_to_datetime(newvalue) log.append((from_utimestamp(t), author, field, oldvalue or '', newvalue or '', permanent)) return log