def render_usermanager_admin_panel(self, req, panel, user, path_info): data = dict(messages=[], errors=[], um_profile_fields=UserProfileManager(self.env).get_user_profile_fields()) if req.method=="POST": if req.args.has_key("um_profile_picture_remove"): if UserProfileManager(self.env).remove_user_file(user["picture_href"]): del user["picture_href"] if user.save(): data['messages'].append(_("Successfully removed %s's picture.")%(user.username)) if req.args.has_key("um_profile_update"): for field in data['um_profile_fields'].keys(): if req.args.has_key("um_profile_%s"%(field)): if data['um_profile_fields'][field]['type']=='file': user_file_new = UserProfileManager(self.env).get_uploaded_file_href(req, user, field, "um_profile_%s"%(field)) user_file_old = user[field] if user_file_new!=user_file_old: user[field] = user_file_new if user_file_old: try: UserProfileManager(self.env).remove_user_file(user_file_old) except Exception, e: self.log.error(e) data['errors'].append(_("Unable to remove previous %s=[%s]")%(field, user_file_old)) elif data['um_profile_fields'][field]['type']=='multichecks': user[field] = '|'.join(req.args.getlist("um_profile_%s"%(field))) else: user[field] = req.args.get("um_profile_%s"%(field)) elif data['um_profile_fields'][field]['type']=='multichecks': # cleanup if none selected user[field]='' if user.save(): data['messages'].append(_("Successfully updated profile for user [%s].")%(user.username))
def __init__(self, path, log=None, params={}): assert have_pysqlite > 0 self.cnx = None if path != ':memory:': if not os.access(path, os.F_OK): raise TracError(_('Database "%(path)s" not found.', path=path)) dbdir = os.path.dirname(path) if not os.access(path, os.R_OK + os.W_OK) or \ not os.access(dbdir, os.R_OK + os.W_OK): raise TracError( _('The user %(user)s requires read _and_ write ' 'permissions to the database file %(path)s ' 'and the directory it is located in.', user=getuser(), path=path)) self._active_cursors = weakref.WeakKeyDictionary() timeout = int(params.get('timeout', 10.0)) self._eager = params.get('cursor', 'eager') == 'eager' # eager is default, can be turned off by specifying ?cursor= if isinstance(path, unicode): # needed with 2.4.0 path = path.encode('utf-8') cnx = sqlite.connect(path, detect_types=sqlite.PARSE_DECLTYPES, check_same_thread=sqlite_version < (3, 3, 1), timeout=timeout) # load extensions extensions = params.get('extensions', []) if len(extensions) > 0: cnx.enable_load_extension(True) for ext in extensions: cnx.load_extension(ext) cnx.enable_load_extension(False) ConnectionWrapper.__init__(self, cnx, log)
def render_admin_panel(self, req, cat, page, path_info): req.perm.require('TRAC_ADMIN') status, message = init_admin(self.gitosis_user, self.gitosis_server, self.admrepo, self.env.path) data = {} if status != 0: add_warning(req, _('Error while cloning gitosis-admin repository. Please check your settings and/or passphrase free connection to this repository for the user running trac (in most cases, the web server user)')) message = 'return code: '+str(status)+'\nmessage:\n'+message if message: add_warning(req, _(message)) repo = replace(os.path.basename(self.config.get('trac', 'repository_dir')), '.git', '') if req.method == 'POST': config = {} self.log.debug('description: '+req.args.get('description')) for option in ('daemon', 'gitweb', 'description', 'owner'): config[option] = req.args.get(option) self.set_config(repo, config) req.redirect(req.href.admin(cat, page)) repo = replace(os.path.basename(self.config.get('trac', 'repository_dir')), '.git', '') if repo != '': data = self.get_config(repo) self.log.debug('data: %s', str(data)) if not data: data = {} for option in ('daemon', 'gitweb', 'description', 'owner'): if option not in data: data[option] = '' data['gitweb'] = data['gitweb'] in _TRUE_VALUES data['daemon'] = data['daemon'] in _TRUE_VALUES return 'admin_tracgitosis_repo.html', {'repo': data}
def rename(self, new_name): """Rename wiki page in-place, keeping the history intact. Renaming a page this way will eventually leave dangling references to the old page - which litterally doesn't exist anymore. """ assert self.exists, "Cannot rename non-existent page" if not validate_page_name(new_name): raise TracError(_("Invalid Wiki page name '%(name)s'", name=new_name)) old_name = self.name with self.env.db_transaction as db: new_page = WikiPage(self.env, new_name) if new_page.exists: raise TracError(_("Can't rename to existing %(name)s page.", name=new_name)) db("UPDATE wiki SET name=%s WHERE name=%s", (new_name, old_name)) # Invalidate page name cache del WikiSystem(self.env).pages # Reparent attachments from trac.attachment import Attachment Attachment.reparent_all(self.env, 'wiki', old_name, 'wiki', new_name) self.name = new_name self.env.log.info('Renamed page %s to %s', old_name, new_name) for listener in WikiSystem(self.env).change_listeners: if hasattr(listener, 'wiki_page_renamed'): listener.wiki_page_renamed(self, old_name)
def _do_list(self): print_table([(m.name, m.due and format_date(m.due, console_date_format), m.completed and format_datetime(m.completed, console_datetime_format)) for m in model.Milestone.select(self.env)], [_("Name"), _("Due"), _("Completed")])
def render_usermanager_admin_panel(self, req, panel, user, path_info): data={'TYPES':['trac-managed', 'server-managed'], 'set_password_enabled': AccountManager(self.env).supports('set_password'), 'delete_enabled': AccountManager(self.env).supports('delete_user')} messages=[] errors=[] if req.method=='POST': if req.args.has_key('um_account_update_type'): if req.args.get('um_account_type')=='trac-managed' and not AccountManager(self.env).has_user(user.username): AccountManager(self.env).set_password(user.username, ''.join([Random().choice('pleaseChangeThisPassword') for x in range(10)]) ) messages.append(_("Successfully changed %s's authentication method")%(user.username)) elif req.args.get('um_account_type')=='server-managed': AccountManager(self.env).delete_user(user.username) messages.append(_("Successfully changed %s's authentication method")%(user.username)) else: raise TracError("Unknow account type") elif req.args.has_key('um_account_change_password'): if req.args['um_account_confirm_password'] == req.args['um_account_new_password']: AccountManager(self.env).set_password(user.username, req.args['um_account_new_password'] ) messages.append(_("Successfully changed %s's password")%(user.username)) else: errors.append(_('Passwords don\'t match')) else: raise TracError("Unknow action") # Adding type data.update(type=AccountManager(self.env).has_user(user.username) and 'trac-managed' or 'server-managed') return 'admin_um_account.html',{'um_account':data, 'messages':messages, 'errors':errors}
def render_usermanager_admin_panel(self, req, panel, user, path_info): user_actions = self._get_user_permissions(user) all_user_actions = PermissionSystem(self.env).get_user_permissions(user.username) actions = PermissionSystem(self.env).get_actions()+list(set([group for group, permissions in PermissionSystem(self.env).get_all_permissions()])) data = dict(actions=actions, all_user_actions=all_user_actions, user_actions=user_actions, permsys = PermissionSystem(self.env), messages=[], errors=[]) if req.method=="POST": updated_user_permissions = req.args.getlist('um_permission') for action in actions: if action in updated_user_permissions: if not all_user_actions.has_key(action): try: PermissionSystem(self.env).grant_permission(user.username, action) data['messages'].append(_("Granted permission [%s] for user [%s].")%(action, user.username)) except Exception, e: data['errors'].append(e) else: if user_actions.has_key(action): try: PermissionSystem(self.env).revoke_permission(user.username, action) data['messages'].append(_("Revoked permission [%s] for user [%s].")%(action, user.username)) except Exception, e: data['errors'].append(e)
def commit(self, req): """Perform the operation for all processed rows. Return a list of new or changed tickets""" tickets = [] if not self.do_preview: for fields in self.rows: ticket = self._get_ticket_from_id_in_csv(req, fields) if ticket == None: continue ticket_type = self._get_type_for_ticket(fields) if not self._may_create_ticket(req.perm, ticket_type): add_warning(req, _("No permission to create a %s.") % ticket_type) continue if not self.force: csv_summary = fields[Key.SUMMARY] db_summary = ticket[Key.SUMMARY] if csv_summary != db_summary: msg = _("Ticket %d has a different summary: '%s' (CSV) - '%s' (DB)") add_warning(req, msg % (ticket.id, repr(csv_summary), repr(db_summary))) continue tickets.append(ticket) ticket.delete() return tickets
def check_header(self, header): """Check the headers for mandatory fields""" if not ((Key.ID in header) or ("ticket" in header)): return _("Header must contain '%s' or '%s'") % (Key.ID, "ticket") if not (Key.SUMMARY in header): return _("Header must contain '%s'") % Key.SUMMARY return None
def _get_connector(self, rtype): """Retrieve the appropriate connector for the given repository type. Note that the self._lock must be held when calling this method. """ if self._connectors is None: # build an environment-level cache for the preferred connectors self._connectors = {} for connector in self.connectors: for type_, prio in connector.get_supported_types() or []: keep = (connector, prio) if type_ in self._connectors and \ prio <= self._connectors[type_][1]: keep = None if keep: self._connectors[type_] = keep if rtype in self._connectors: connector, prio = self._connectors[rtype] if prio >= 0: # no error condition return connector else: raise TracError( _('Unsupported version control system "%(name)s"' ': %(error)s', name=rtype, error=to_unicode(connector.error))) else: raise TracError( _('Unsupported version control system "%(name)s": ' 'Can\'t find an appropriate component, maybe the ' 'corresponding plugin was not enabled? ', name=rtype))
def __init__(self, path, rev, msg=None): if msg is None: msg = _("No node %(path)s at revision %(rev)s", path=path, rev=rev) else: msg = _("%(msg)s: No node %(path)s at revision %(rev)s", msg=msg, path=path, rev=rev) ResourceNotFound.__init__(self, msg, _('No such node'))
def _select_login_action(self, req, remote_user): """ Select login action based on user status. - If user is expired, error msg is shown - If user is active, user will be logged in - If user is inactive, legal process is started - Otherwise user has no way of logging in (is banned or disabled) """ user_store = get_userstore() user = user_store.getUser(remote_user) if not user: # This may happen if authentication method is case-insensitive add_notice(req, _("Incorrect username or password - please try again")) return self._show_login(req) # Check if expired if user.expires and user.expires <= datetime.utcnow(): author = user_store.get_user_author(user) if author: add_warning(req, _('Account expired. Contact {0} for extension'.format(author.getDisplayName()))) else: add_warning(req, _('Account expired. Contact service support for extension')) return self._show_login(req) # User is authentic but can not log in before knowing he is not banned or disabled or activation required if user.status == user.STATUS_INACTIVE and self.env.config.getbool('multiproject', 'login_requires_agreed_terms'): return self._request_legal_approval(req, remote_user) if user.status in (user.STATUS_ACTIVE, user.STATUS_INACTIVE): return self._login_success(req, remote_user) add_warning(req, _("User status is '%s'. Can not log in." % user_store.USER_STATUS_LABELS[user.status])) return self._show_login(req)
def modify_repository(self, reponame, changes): """Modify attributes of a repository.""" if is_default(reponame): reponame = '' new_reponame = changes.get('name', reponame) if is_default(new_reponame): new_reponame = '' rm = RepositoryManager(self.env) with self.env.db_transaction as db: id = rm.get_repository_id(reponame) if reponame != new_reponame: if db("""SELECT id FROM repository WHERE name='name' AND value=%s""", (new_reponame,)): raise TracError(_('The repository "%(name)s" already ' 'exists.', name=new_reponame or '(default)')) for (k, v) in changes.iteritems(): if k not in self.repository_attrs: continue if k in ('alias', 'name') and is_default(v): v = '' if k == 'dir' and not os.path.isabs(v): raise TracError(_("The repository directory must be " "absolute")) db("UPDATE repository SET value=%s WHERE id=%s AND name=%s", (v, id, k)) if not db( "SELECT value FROM repository WHERE id=%s AND name=%s", (id, k)): db("""INSERT INTO repository (id, name, value) VALUES (%s, %s, %s) """, (id, k, v)) rm.reload_repositories()
def notify(self, resid, subject, author=None): self.subject = subject config = self.config['notification'] if not config.getbool('smtp_enabled'): return from_email, from_name = '', '' if author and config.getbool('smtp_from_author'): from_email = self.get_smtp_address(author) if from_email: from_name = self.name_map.get(author, '') if not from_name: mo = self.longaddr_re.search(author) if mo: from_name = mo.group(1) if not from_email: from_email = config.get('smtp_from') from_name = config.get('smtp_from_name') or self.env.project_name self.replyto_email = config.get('smtp_replyto') self.from_email = from_email or self.replyto_email self.from_name = from_name if not self.from_email and not self.replyto_email: message = tag( tag.p(_('Unable to send email due to identity crisis.')), # convert explicitly to `Fragment` to avoid breaking message # when passing `LazyProxy` object to `Fragment` tag.p(to_fragment(tag_( "Neither %(from_)s nor %(reply_to)s are specified in the " "configuration.", from_=tag.strong("[notification] smtp_from"), reply_to=tag.strong("[notification] smtp_replyto"))))) raise TracError(message, _("SMTP Notification Error")) Notify.notify(self, resid)
def _sync(self, reponame, rev, clean): rm = RepositoryManager(self.env) if reponame == '*': if rev is not None: raise TracError(_('Cannot synchronize a single revision ' 'on multiple repositories')) repositories = rm.get_real_repositories() else: if is_default(reponame): reponame = '' repos = rm.get_repository(reponame) if repos is None: raise TracError(_("Repository '%(repo)s' not found", repo=reponame or '(default)')) if rev is not None: repos.sync_changeset(rev) printout(_('%(rev)s resynced on %(reponame)s.', rev=rev, reponame=repos.reponame or '(default)')) return repositories = [repos] db = self.env.get_db_cnx() for repos in sorted(repositories, key=lambda r: r.reponame): printout(_('Resyncing repository history for %(reponame)s... ', reponame=repos.reponame or '(default)')) repos.sync(self._sync_feedback, clean=clean) cursor = db.cursor() cursor.execute("SELECT count(rev) FROM revision WHERE repos=%s", (repos.id,)) for cnt, in cursor: printout(ngettext('%(num)s revision cached.', '%(num)s revisions cached.', num=cnt)) printout(_('Done.'))
def _wiki_ctxtnav(self, req, page): """Add the normal wiki ctxtnav entries.""" add_ctxtnav(req, _('Start Page'), req.href.wiki('WikiStart')) add_ctxtnav(req, _('Index'), req.href.wiki('TitleIndex')) if page.exists: add_ctxtnav(req, _('History'), req.href.wiki(page.name, action='history'))
def get_navigation_items(self, req): if 'WIKI_VIEW' in req.perm('wiki'): yield ('mainnav', 'wiki', tag.a(_('Wiki'), href=req.href.wiki(), accesskey=1)) yield ('metanav', 'help', tag.a(_('Help/Guide'), href=req.href.wiki('TracGuide'), accesskey=6))
def get_initenv_args(self): returnvals = [] printout(_("Creating a new Trac environment at %(envname)s", envname=self.envname)) printout(_(""" Trac will first ask a few questions about your environment in order to initialize and prepare the project database. Please enter the name of your project. This name will be used in page titles and descriptions. """)) dp = 'My Project' returnvals.append(raw_input(_("Project Name [%(default)s]> ", default=dp)).strip() or dp) printout(_(""" Please specify the connection string for the database to use. By default, a local SQLite database is created in the environment directory. It is also possible to use an already existing PostgreSQL database (check the Trac documentation for the exact connection string syntax). """)) ddb = 'sqlite:db/trac.db' prompt = _("Database connection string [%(default)s]> ", default=ddb) returnvals.append(raw_input(prompt).strip() or ddb) print() return returnvals
def _do_delete(self, req, page): if page.readonly: req.perm(page.resource).require('WIKI_ADMIN') else: req.perm(page.resource).require('WIKI_DELETE') if 'cancel' in req.args: req.redirect(get_resource_url(self.env, page.resource, req.href)) version = int(req.args.get('version', 0)) or None old_version = int(req.args.get('old_version', 0)) or version with self.env.db_transaction as db: if version and old_version and version > old_version: # delete from `old_version` exclusive to `version` inclusive: for v in range(old_version, version): page.delete(v + 1, db) else: # only delete that `version`, or the whole page if `None` page.delete(version, db) if not page.exists: add_notice(req, _("The page %(name)s has been deleted.", name=page.name)) req.redirect(req.href.wiki()) else: if version and old_version and version > old_version + 1: add_notice(req, _('The versions %(from_)d to %(to)d of the ' 'page %(name)s have been deleted.', from_=old_version + 1, to=version, name=page.name)) else: add_notice(req, _('The version %(version)d of the page ' '%(name)s has been deleted.', version=version, name=page.name)) req.redirect(req.href.wiki(page.name))
def _do_import(self, filename=None): permsys = PermissionSystem(self.env) try: with file_or_std(filename, 'rb') as f: encoding = stream_encoding(f) linesep = os.linesep if filename else '\n' reader = csv.reader(f, lineterminator=linesep) for row in reader: if len(row) < 2: raise AdminCommandError( _("Invalid row %(line)d. Expected <user>, " "<action>, [action], [...]", line=reader.line_num)) user = to_unicode(row[0], encoding) actions = [to_unicode(action, encoding) for action in row[1:]] if user.isupper(): raise AdminCommandError( _("Invalid user %(user)s on line %(line)d: All " "upper-cased tokens are reserved for permission " "names.", user=user, line=reader.line_num)) old_actions = self.get_user_perms(user) for action in set(actions) - set(old_actions): permsys.grant_permission(user, action) except csv.Error as e: raise AdminCommandError( _("Cannot import from %(filename)s line %(line)d: %(error)s ", filename=path_to_unicode(filename or 'stdin'), line=reader.line_num, error=e)) except IOError as e: raise AdminCommandError( _("Cannot import from %(filename)s: %(error)s", filename=path_to_unicode(filename or 'stdin'), error=e.strerror))
def notify(self, resid, subject, author=None): self.subject = subject config = self.config['notification'] if not config.getbool('smtp_enabled'): return from_email, from_name = '', '' if author and config.getbool('smtp_from_author'): from_email = self.get_smtp_address(author) if from_email: from_name = self.name_map.get(author, '') if not from_name: mo = self.longaddr_re.search(author) if mo: from_name = mo.group(1) if not from_email: from_email = config.get('smtp_from') from_name = config.get('smtp_from_name') or self.env.project_name self.replyto_email = config.get('smtp_replyto') self.from_email = from_email or self.replyto_email self.from_name = from_name if not self.from_email and not self.replyto_email: raise TracError(tag( tag.p(_('Unable to send email due to identity crisis.')), tag.p(_('Neither %(from_)s nor %(reply_to)s are specified ' 'in the configuration.', from_=tag.b('notification.from'), reply_to=tag.b('notification.reply_to')))), _('SMTP Notification Error')) Notify.notify(self, resid)
def get_backlog_list(self, sql, db=None): if not db: db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute(sql) closed_backlogs = list() running_backlogs = list() t = datetime.datetime.now() now = time.mktime(t.timetuple()) for row in cursor.fetchall(): _name, _start, _end = row if now > _end: #handler milestone if _end == 0: running_backlogs.append(_name) closed_backlogs.append(_name) elif now >= _start and now <= _end: running_backlogs.append(_name) else: pass cursor.close() backlog_list = [ {'label': _('Running (by Start Date)'), 'options': running_backlogs}, {'label': _('Closed (by End Date)'), 'options': closed_backlogs}, ] return backlog_list
def get_navigation_items(self, req): # The 'Admin' navigation item is only visible if at least one # admin panel is available panels, providers = self._get_panels(req) if panels: yield ('mainnav', 'monitoring', tag.a(_('Monitoring'), href=req.href.monitoring(), title=_('Monitoring')))
def _do_delete(self, req, milestone): req.perm(milestone.resource).require('MILESTONE_DELETE') retarget_to = req.args.get('target') or None # Don't translate ticket comment (comment:40:ticket:5658) retargeted_tickets = \ milestone.move_tickets(retarget_to, req.authname, "Ticket retargeted after milestone deleted") milestone.delete(author=req.authname) add_notice(req, _('The milestone "%(name)s" has been deleted.', name=milestone.name)) if retargeted_tickets: add_notice(req, _('The tickets associated with milestone ' '"%(name)s" have been retargeted to milestone ' '"%(retarget)s".', name=milestone.name, retarget=retarget_to)) new_values = {'milestone': retarget_to} comment = _("Tickets retargeted after milestone deleted") tn = BatchTicketNotifyEmail(self.env) try: tn.notify(retargeted_tickets, new_values, comment, None, req.authname) except Exception, e: self.log.error("Failure sending notification on ticket batch " "change: %s", exception_to_unicode(e)) add_warning(req, tag_("The changes have been saved, but an " "error occurred while sending " "notifications: %(message)s", message=to_unicode(e)))
def _render_editor(self, req, id, copy): if id != -1: req.perm.require("REPORT_MODIFY") for title, description, query in self.env.db_query( "SELECT title, description, query FROM report WHERE id=%s", (id,) ): break else: raise TracError(_("Report {%(num)s} does not exist.", num=id), _("Invalid Report Number")) else: req.perm.require("REPORT_CREATE") title = description = query = "" # an explicitly given 'query' parameter will override the saved query query = req.args.get("query", query) if copy: title += " (copy)" if copy or id == -1: data = {"title": _("Create New Report"), "action": "new", "error": None} else: data = { "title": _("Edit Report {%(num)d} %(title)s", num=id, title=title), "action": "edit", "error": req.args.get("error"), } data["report"] = {"id": id, "title": title, "sql": query, "description": description} return data
def _launch(self, encoded_input, *args): """Launch a process (cmd), and returns exitcode, stdout + stderr""" # Note: subprocess.Popen doesn't support unicode options arguments # (http://bugs.python.org/issue1759845) so we have to encode them. # Anyway, dot expects utf-8 or the encoding specified with -Gcharset. encoded_cmd = [] for arg in args: if isinstance(arg, unicode): arg = arg.encode(self.encoding, 'replace') encoded_cmd.append(arg) p = subprocess.Popen(encoded_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if encoded_input: p.stdin.write(encoded_input) p.stdin.close() out = p.stdout.read() err = p.stderr.read() failure = p.wait() != 0 if failure or err or out: return (failure, tag.p(tag.br(), _("The command:"), tag.pre(repr(' '.join(encoded_cmd))), (_("succeeded but emitted the following output:"), _("failed with the following output:"))[failure], out and tag.pre(repr(out)), err and tag.pre(repr(err)))) else: return (False, None)
def _render_editor(self, req, id, copy): if id != -1: req.perm.require('REPORT_MODIFY') for title, description, query in self.env.db_query( "SELECT title, description, query FROM report WHERE id=%s", (id,)): break else: raise TracError(_("Report {%(num)s} does not exist.", num=id), _("Invalid Report Number")) else: req.perm.require('REPORT_CREATE') title = description = query = '' # an explicitly given 'query' parameter will override the saved query query = req.args.get('query', query) if copy: title += ' (copy)' if copy or id == -1: data = {'title': _('Create New Report'), 'action': 'new', 'error': None} else: data = {'title': _('Edit Report {%(num)d} %(title)s', num=id, title=title), 'action': 'edit', 'error': req.args.get('error')} data['report'] = {'id': id, 'title': title, 'sql': query, 'description': description} return data
def pre_process_request(self, req, handler): if req.path_info.startswith('/admin/ticket/components/'): if req.method == "POST" and 'renamechildren' in req.args: if req.args.get('renamechildren') != 'on': return handler # Let trac handle this update # First process the parent component. parentcomponentname = req.path_info[25:] parentcomponent = model.Component(self.env, parentcomponentname) parentcomponent.name = req.args.get('name') parentcomponent.owner = req.args.get('owner') parentcomponent.description = req.args.get('description') try: parentcomponent.update() except self.env.db_exc.IntegrityError: raise TracError(_('The component "%(name)s" already ' 'exists.', name=parentcomponentname)) # Now update the child components childcomponents = self._get_component_children(parentcomponentname) for component in childcomponents: component.name = component.name.replace(parentcomponentname, req.args.get('name'), 1) component.update() add_notice(req, _('Your changes have been saved.')) req.redirect(req.href.admin('ticket', 'components')) return handler
def process_request(self, req): presel = req.args.get('preselected') if presel and (presel + '/').startswith(req.href.browser() + '/'): req.redirect(presel) path = req.args.get('path', '/') rev = req.args.get('rev', '') if rev.lower() in ('', 'head'): rev = None format = req.args.get('format') order = req.args.get('order', 'name').lower() desc = 'desc' in req.args xhr = req.get_header('X-Requested-With') == 'XMLHttpRequest' rm = RepositoryManager(self.env) all_repositories = rm.get_all_repositories() reponame, repos, path = rm.get_repository_by_path(path) # Repository index show_index = not reponame and path == '/' if show_index: if repos and (as_bool(all_repositories[''].get('hidden')) or not repos.is_viewable(req.perm)): repos = None if not repos and reponame: raise ResourceNotFound(_("Repository '%(repo)s' not found", repo=reponame)) if reponame and reponame != repos.reponame: # Redirect alias qs = req.query_string req.redirect(req.href.browser(repos.reponame or None, path) + ('?' + qs if qs else '')) reponame = repos.reponame if repos else None # Find node for the requested path/rev context = web_context(req) node = None changeset = None display_rev = lambda rev: rev if repos: try: if rev: rev = repos.normalize_rev(rev) # If `rev` is `None`, we'll try to reuse `None` consistently, # as a special shortcut to the latest revision. rev_or_latest = rev or repos.youngest_rev node = get_existing_node(req, repos, path, rev_or_latest) except NoSuchChangeset, e: raise ResourceNotFound(e, _('Invalid changeset number')) if node: try: # use changeset instance to retrieve branches and tags changeset = repos.get_changeset(node.rev) except NoSuchChangeset: pass context = context.child(repos.resource.child('source', path, version=rev_or_latest)) display_rev = repos.display_rev
def _prepare_results(self, result_docs, hits): ui_docs = [self._process_doc(doc) for doc in result_docs] results = Paginator( ui_docs, self.page - 1, self.pagelen, hits) self._prepare_shown_pages(results) results.current_page = {'href': None, 'class': 'current', 'string': str(results.page + 1), 'title': None} parameters = self.parameters if results.has_next_page: next_href = parameters.create_href(page=parameters.page + 1) add_link(self.req, 'next', next_href, _('Next Page')) if results.has_previous_page: prev_href = parameters.create_href(page=parameters.page - 1) add_link(self.req, 'prev', prev_href, _('Previous Page')) self.data[self.DATA_RESULTS] = results prevnext_nav(self.req, _('Previous'), _('Next'))
def _do_oauth(self, req): try: state = req.session['oauth_state'] except KeyError as exc: self._reject_oauth(req, exc) oauth = self._oauth_session(req, state) # Inner import to avoid a hard dependency on requests-oauthlib. import oauthlib import requests github_oauth_url = os.environ.get("TRAC_GITHUB_OAUTH_URL", "https://github.com/") github_api_url = os.environ.get("TRAC_GITHUB_API_URL", "https://api.github.com/") try: oauth.fetch_token( github_oauth_url + 'login/oauth/access_token', authorization_response=req.abs_href(req.path_info) + '?' + req.query_string, client_secret=_config_secret(self.client_secret)) except (oauthlib.oauth2.OAuth2Error, requests.exceptions.ConnectionError) as exc: self._reject_oauth(req, exc) try: user = oauth.get(github_api_url + 'user').json() # read all required data here to deal with errors correctly name = user.get('name') email = user.get('email') login = user.get('login') except Exception as exc: # pylint: disable=broad-except self._reject_oauth( req, exc, reason=_( "An error occurred while communicating with the GitHub API" )) if self.request_email: try: for item in oauth.get(github_api_url + 'user/emails').json(): if not item['verified']: # ignore unverified email addresses continue if (self.preferred_email_domain and item['email'].lower().endswith( '@' + self.preferred_email_domain.lower())): email = item['email'] break if item['primary']: email = item['email'] if not self.preferred_email_domain: break except Exception as exc: # pylint: disable=broad-except self._reject_oauth( req, exc, reason=_( "An error occurred while retrieving your email address " "from the GitHub API")) # Small hack to pass the username to _do_login. req.environ['REMOTE_USER'] = self.username_prefix + login # Save other available values in the session. req.session.setdefault('name', name or '') req.session.setdefault('email', email or '') return super(GitHubLoginModule, self)._do_login(req)
def get_timeline_filters(self, req): if 'WIKI_VIEW' in req.perm: yield ('wiki', _('Wiki changes'))
def _render_view(self, req, page): version = page.resource.version # Add registered converters if page.exists: for conversion in Mimeview(self.env) \ .get_supported_conversions('text/x-trac-wiki'): conversion_href = req.href.wiki(page.name, version=version, format=conversion.key) add_link(req, 'alternate', conversion_href, conversion.name, conversion.in_mimetype) data = self._page_data(req, page) if page.name == 'WikiStart': data['title'] = '' ws = WikiSystem(self.env) context = web_context(req, page.resource) higher, related = [], [] if not page.exists: if 'WIKI_CREATE' not in req.perm(page.resource): raise ResourceNotFound( _("Page %(name)s not found", name=page.name)) formatter = OneLinerFormatter(self.env, context) if '/' in page.name: parts = page.name.split('/') for i in range(len(parts) - 2, -1, -1): name = '/'.join(parts[:i] + [parts[-1]]) if not ws.has_page(name): higher.append( ws._format_link(formatter, 'wiki', '/' + name, name, False)) else: name = page.name name = name.lower() related = [ each for each in ws.pages if name in each.lower() and 'WIKI_VIEW' in req.perm(self.realm, each) ] related.sort() related = [ ws._format_link(formatter, 'wiki', '/' + each, each, False) for each in related ] latest_page = WikiPage(self.env, page.name) prev_version = next_version = None if version: try: version = int(version) for hist in latest_page.get_history(): v = hist[0] if v != version: if v < version: if not prev_version: prev_version = v break else: next_version = v except ValueError: version = None prefix = self.PAGE_TEMPLATES_PREFIX templates = [ template[len(prefix):] for template in ws.get_pages(prefix) if 'WIKI_VIEW' in req.perm(self.realm, template) ] # -- prev/up/next links if prev_version: add_link(req, 'prev', req.href.wiki(page.name, version=prev_version), _("Version %(num)s", num=prev_version)) parent = None if version: add_link(req, 'up', req.href.wiki(page.name, version=None), _("View latest version")) elif '/' in page.name: parent = page.name[:page.name.rindex('/')] add_link(req, 'up', req.href.wiki(parent, version=None), _("View parent page")) if next_version: add_link(req, 'next', req.href.wiki(page.name, version=next_version), _('Version %(num)s', num=next_version)) # Add ctxtnav entries if version: prevnext_nav(req, _("Previous Version"), _("Next Version"), _("View Latest Version")) else: if parent: add_ctxtnav(req, _('Up'), req.href.wiki(parent)) self._wiki_ctxtnav(req, page) # Plugin content validation fields = {'text': page.text} for manipulator in self.page_manipulators: manipulator.prepare_wiki_page(req, page, fields) text = fields.get('text', '') data.update({ 'context': context, 'text': text, 'latest_version': latest_page.version, 'attachments': AttachmentModule(self.env).attachment_data(context), 'default_template': self.DEFAULT_PAGE_TEMPLATE, 'templates': templates, 'version': version, 'higher': higher, 'related': related, 'resourcepath_template': 'wiki_page_path.html', }) add_script(req, 'common/js/folding.js') return 'wiki_view.html', data, None
def get_supported_conversions(self): yield ('txt', _("Plain Text"), 'txt', 'text/x-trac-wiki', 'text/plain', 9)
def _render_editor(self, req, page, action='edit', has_collision=False): if has_collision: if action == 'merge': page = WikiPage(self.env, page.name) req.perm(page.resource).require('WIKI_VIEW') else: action = 'collision' if not page.exists: req.perm(page.resource).require('WIKI_CREATE') else: req.perm(page.resource).require('WIKI_MODIFY') original_text = page.text comment = req.args.get('comment', '') if 'text' in req.args: page.text = req.args.get('text') elif 'template' in req.args: template = self.PAGE_TEMPLATES_PREFIX + req.args.get('template') template_page = WikiPage(self.env, template) if template_page and template_page.exists and \ 'WIKI_VIEW' in req.perm(template_page.resource): page.text = template_page.text elif 'version' in req.args: old_page = WikiPage(self.env, page.name, int(req.args['version'])) req.perm(page.resource).require('WIKI_VIEW') page.text = old_page.text comment = _("Reverted to version %(version)s.", version=req.args['version']) if action in ('preview', 'diff'): page.readonly = 'readonly' in req.args author = get_reporter_id(req, 'author') defaults = {'editrows': str(self.default_edit_area_height)} prefs = dict((key, req.session.get('wiki_%s' % key, defaults.get(key))) for key in ('editrows', 'sidebyside')) if 'from_editor' in req.args: sidebyside = req.args.get('sidebyside') or None if sidebyside != prefs['sidebyside']: req.session.set('wiki_sidebyside', int(bool(sidebyside)), 0) else: sidebyside = prefs['sidebyside'] if sidebyside: editrows = max(int(prefs['editrows']), len(page.text.splitlines()) + 1) else: editrows = req.args.get('editrows') if editrows: if editrows != prefs['editrows']: req.session.set('wiki_editrows', editrows, defaults['editrows']) else: editrows = prefs['editrows'] data = self._page_data(req, page, action) context = web_context(req, page.resource) data.update({ 'context': context, 'author': author, 'comment': comment, 'edit_rows': editrows, 'sidebyside': sidebyside, 'scroll_bar_pos': req.args.get('scroll_bar_pos', ''), 'diff': None, 'attachments': AttachmentModule(self.env).attachment_data(context), 'show_readonly_checkbox': ReadonlyWikiPolicy.__name__ in self.config.get('trac', 'permission_policies') }) if action in ('diff', 'merge'): old_text = original_text.splitlines() if original_text else [] new_text = page.text.splitlines() if page.text else [] diff_data, changes = self._prepare_diff(req, page, old_text, new_text, page.version, '') data.update({ 'diff': diff_data, 'changes': changes, 'action': 'preview', 'merge': action == 'merge', 'longcol': 'Version', 'shortcol': 'v' }) elif sidebyside and action != 'collision': data['action'] = 'preview' self._wiki_ctxtnav(req, page) Chrome(self.env).add_wiki_toolbars(req) Chrome(self.env).add_auto_preview(req) add_script(req, 'common/js/folding.js') return 'wiki_edit.html', data, None
def _render_diff(self, req, page): if not page.exists: raise TracError( _("Version %(num)s of page \"%(name)s\" does not " "exist", num=req.args.get('version'), name=page.name)) old_version = req.args.get('old_version') if old_version: old_version = int(old_version) if old_version == page.version: old_version = None elif old_version > page.version: # FIXME: what about reverse diffs? old_version = page.resource.version page = WikiPage(self.env, page.name, old_version) req.perm(page.resource).require('WIKI_VIEW') latest_page = WikiPage(self.env, page.name) req.perm(latest_page.resource).require('WIKI_VIEW') new_version = int(page.version) date = author = comment = ipnr = None num_changes = 0 prev_version = next_version = None for version, t, a, c, i in latest_page.get_history(): if version == new_version: date = t author = a or 'anonymous' comment = c or '--' ipnr = i or '' else: if version < new_version: num_changes += 1 if not prev_version: prev_version = version if old_version is None or version == old_version: old_version = version break else: next_version = version if not old_version: old_version = 0 old_page = WikiPage(self.env, page.name, old_version) req.perm(old_page.resource).require('WIKI_VIEW') # -- text diffs old_text = old_page.text.splitlines() new_text = page.text.splitlines() diff_data, changes = self._prepare_diff(req, page, old_text, new_text, old_version, new_version) # -- prev/up/next links if prev_version: add_link( req, 'prev', req.href.wiki(page.name, action='diff', version=prev_version), _("Version %(num)s", num=prev_version)) add_link(req, 'up', req.href.wiki(page.name, action='history'), _('Page history')) if next_version: add_link( req, 'next', req.href.wiki(page.name, action='diff', version=next_version), _("Version %(num)s", num=next_version)) data = self._page_data(req, page, 'diff') data.update({ 'change': { 'date': date, 'author': author, 'ipnr': ipnr, 'comment': comment }, 'new_version': new_version, 'old_version': old_version, 'latest_version': latest_page.version, 'num_changes': num_changes, 'longcol': 'Version', 'shortcol': 'v', 'changes': changes, 'diff': diff_data, }) prevnext_nav(req, _("Previous Change"), _("Next Change"), _("Wiki History")) return 'wiki_diff.html', data, None
def process_request(self, req): action = req.args.get('action', 'view') pagename = req.args.get('page', 'WikiStart') version = req.args.get('version') old_version = req.args.get('old_version') if pagename.startswith('/') or pagename.endswith('/') or \ '//' in pagename: pagename = re.sub(r'/{2,}', '/', pagename.strip('/')) req.redirect(req.href.wiki(pagename)) if not validate_page_name(pagename): raise TracError( _("Invalid Wiki page name '%(name)s'", name=pagename)) if version is not None: try: version = int(version) except (ValueError, TypeError): raise ResourceNotFound( _('No version "%(num)s" for Wiki page "%(name)s"', num=version, name=pagename)) page = WikiPage(self.env, pagename) versioned_page = WikiPage(self.env, pagename, version) req.perm(versioned_page.resource).require('WIKI_VIEW') if version and versioned_page.version != int(version): raise ResourceNotFound( _('No version "%(num)s" for Wiki page "%(name)s"', num=version, name=page.name)) add_stylesheet(req, 'common/css/wiki.css') if req.method == 'POST': if action == 'edit': if 'cancel' in req.args: req.redirect(req.href.wiki(page.name)) has_collision = int(version) != page.version for a in ('preview', 'diff', 'merge'): if a in req.args: action = a break versioned_page.text = req.args.get('text') valid = self._validate(req, versioned_page) if action == 'edit' and not has_collision and valid: return self._do_save(req, versioned_page) else: return self._render_editor(req, page, action, has_collision) elif action == 'edit_comment': self._do_edit_comment(req, versioned_page) elif action == 'delete': self._do_delete(req, versioned_page) elif action == 'rename': return self._do_rename(req, page) elif action == 'diff': style, options, diff_data = get_diff_options(req) contextall = diff_data['options']['contextall'] req.redirect( req.href.wiki(versioned_page.name, action='diff', old_version=old_version, version=version, contextall=contextall or None)) elif action == 'delete': return self._render_confirm_delete(req, page) elif action == 'rename': return self._render_confirm_rename(req, page) elif action == 'edit': return self._render_editor(req, page) elif action == 'edit_comment': return self._render_edit_comment(req, versioned_page) elif action == 'diff': return self._render_diff(req, versioned_page) elif action == 'history': return self._render_history(req, versioned_page) else: format = req.args.get('format') if format: Mimeview(self.env).send_converted(req, 'text/x-trac-wiki', versioned_page.text, format, versioned_page.name) return self._render_view(req, versioned_page)
def get_ticket_field_labels(self): """Produce a (name,label) mapping from `get_ticket_fields`.""" labels = {f['name']: f['label'] for f in self.get_ticket_fields()} labels['attachment'] = _("Attachment") return labels
def _render_view(self, req, milestone): milestone_groups = [] available_groups = [] component_group_available = False ticket_fields = TicketSystem(self.env).get_ticket_fields() # collect fields that can be used for grouping for field in ticket_fields: if field['type'] == 'select' and field['name'] != 'milestone' \ or field['name'] in ('owner', 'reporter'): available_groups.append({ 'name': field['name'], 'label': field['label'] }) if field['name'] == 'component': component_group_available = True # determine the field currently used for grouping by = None if component_group_available: by = 'component' elif available_groups: by = available_groups[0]['name'] by = req.args.get('by', by) tickets = get_tickets_for_milestone(self.env, milestone=milestone.name, field=by) tickets = apply_ticket_permissions(self.env, req, tickets) stat = get_ticket_stats(self.stats_provider, tickets) context = web_context(req, milestone.resource) data = { 'context': context, 'milestone': milestone, 'attachments': AttachmentModule(self.env).attachment_data(context), 'available_groups': available_groups, 'grouped_by': by, 'groups': milestone_groups } data.update(milestone_stats_data(self.env, req, stat, milestone.name)) if by: groups = [] for field in ticket_fields: if field['name'] == by: if 'options' in field: groups = field['options'] if field.get('optional'): groups.insert(0, '') else: groups = [ group for group, in self.env.db_query(""" SELECT DISTINCT COALESCE(%s, '') FROM ticket ORDER BY COALESCE(%s, '') """ % (by, by)) ] max_count = 0 group_stats = [] for group in groups: values = (group, ) if group else (None, group) group_tickets = [t for t in tickets if t[by] in values] if not group_tickets: continue gstat = get_ticket_stats(self.stats_provider, group_tickets) if gstat.count > max_count: max_count = gstat.count group_stats.append(gstat) gs_dict = {'name': group} gs_dict.update( milestone_stats_data(self.env, req, gstat, milestone.name, by, group)) milestone_groups.append(gs_dict) for idx, gstat in enumerate(group_stats): gs_dict = milestone_groups[idx] percent = 1.0 if max_count: percent = float(gstat.count) / float(max_count) * 100 gs_dict['percent_of_max_total'] = percent add_stylesheet(req, 'common/css/roadmap.css') add_script(req, 'common/js/folding.js') def add_milestone_link(rel, milestone): href = req.href.milestone(milestone.name, by=req.args.get('by')) add_link(req, rel, href, _('Milestone "%(name)s"', name=milestone.name)) milestones = [ m for m in Milestone.select(self.env) if 'MILESTONE_VIEW' in req.perm(m.resource) ] idx = [i for i, m in enumerate(milestones) if m.name == milestone.name] if idx: idx = idx[0] if idx > 0: add_milestone_link('first', milestones[0]) add_milestone_link('prev', milestones[idx - 1]) if idx < len(milestones) - 1: add_milestone_link('next', milestones[idx + 1]) add_milestone_link('last', milestones[-1]) prevnext_nav(req, _('Previous Milestone'), _('Next Milestone'), _('Back to Roadmap')) return 'milestone_view.html', data, None
def get_admin_panels(self, req): if 'BACKLOG_ADMIN' in req.perm: yield 'ticket', _("Ticket System"), 'backlogs', _("Backlogs")
def get_timeline_filters(self, req): if 'MILESTONE_VIEW' in req.perm: yield ('milestone', _('Milestones reached'))
def add_milestone_link(rel, milestone): href = req.href.milestone(milestone.name, by=req.args.get('by')) add_link(req, rel, href, _('Milestone "%(name)s"', name=milestone.name))
def get_navigation_items(self, req): if 'ROADMAP_VIEW' in req.perm: yield ('mainnav', 'roadmap', tag.a(_('Roadmap'), href=req.href.roadmap(), accesskey=3))
def _do_save(self, req, milestone): if milestone.exists: req.perm(milestone.resource).require('MILESTONE_MODIFY') else: req.perm(milestone.resource).require('MILESTONE_CREATE') old_name = milestone.name new_name = req.args.get('name') milestone.description = req.args.get('description', '') if 'due' in req.args: due = req.args.get('duedate', '') milestone.due = user_time(req, parse_date, due, hint='datetime') \ if due else None else: milestone.due = None completed = req.args.get('completeddate', '') retarget_to = req.args.get('target') # Instead of raising one single error, check all the constraints and # let the user fix them by going back to edit mode showing the warnings warnings = [] def warn(msg): add_warning(req, msg) warnings.append(msg) # -- check the name # If the name has changed, check that the milestone doesn't already # exist # FIXME: the whole .exists business needs to be clarified # (#4130) and should behave like a WikiPage does in # this respect. try: new_milestone = Milestone(self.env, new_name) if new_milestone.name == old_name: pass # Creation or no name change elif new_milestone.name: warn( _( 'Milestone "%(name)s" already exists, please ' 'choose another name.', name=new_milestone.name)) else: warn(_('You must provide a name for the milestone.')) except ResourceNotFound: milestone.name = new_name # -- check completed date if 'completed' in req.args: completed = user_time(req, parse_date, completed, hint='datetime') if completed else None if completed and completed > datetime.now(utc): warn(_('Completion date may not be in the future')) else: completed = None milestone.completed = completed if warnings: return self._render_editor(req, milestone) # -- actually save changes if milestone.exists: milestone.update() # eventually retarget opened tickets associated with the milestone if 'retarget' in req.args and completed: self.env.db_transaction( """ UPDATE ticket SET milestone=%s WHERE milestone=%s and status != 'closed' """, (retarget_to, old_name)) self.log.info("Tickets associated with milestone %s " "retargeted to %s" % (old_name, retarget_to)) else: milestone.insert() add_notice(req, _("Your changes have been saved.")) req.redirect(req.href.milestone(milestone.name))
def get_search_filters(self, req): if 'MILESTONE_VIEW' in req.perm: yield ('milestone', _('Milestones'))
def _render_ics(self, req, milestones): req.send_response(200) req.send_header('Content-Type', 'text/calendar;charset=utf-8') buf = StringIO() from trac.ticket import Priority priorities = {} for priority in Priority.select(self.env): priorities[priority.name] = float(priority.value) def get_priority(ticket): value = priorities.get(ticket['priority']) if value: return int( (len(priorities) + 8 * value - 9) / (len(priorities) - 1)) def get_status(ticket): status = ticket['status'] if status == 'new' or status == 'reopened' and not ticket['owner']: return 'NEEDS-ACTION' elif status == 'assigned' or status == 'reopened': return 'IN-PROCESS' elif status == 'closed': if ticket['resolution'] == 'fixed': return 'COMPLETED' else: return 'CANCELLED' else: return '' def escape_value(text): s = ''.join(map(lambda c: '\\' + c if c in ';,\\' else c, text)) return '\\n'.join(re.split(r'[\r\n]+', s)) def write_prop(name, value, params={}): text = ';'.join([name] + [k + '=' + v for k, v in params.items()]) \ + ':' + escape_value(value) firstline = 1 while text: if not firstline: text = ' ' + text else: firstline = 0 buf.write(text[:75] + CRLF) text = text[75:] def write_date(name, value, params={}): params['VALUE'] = 'DATE' write_prop(name, format_date(value, '%Y%m%d', req.tz), params) def write_utctime(name, value, params={}): write_prop(name, format_datetime(value, '%Y%m%dT%H%M%SZ', utc), params) host = req.base_url[req.base_url.find('://') + 3:] user = req.args.get('user', 'anonymous') write_prop('BEGIN', 'VCALENDAR') write_prop('VERSION', '2.0') write_prop('PRODID', '-//Edgewall Software//NONSGML Trac %s//EN' % __version__) write_prop('METHOD', 'PUBLISH') write_prop('X-WR-CALNAME', self.env.project_name + ' - ' + _('Roadmap')) write_prop('X-WR-CALDESC', self.env.project_description) write_prop('X-WR-TIMEZONE', str(req.tz)) for milestone in milestones: uid = '<%s/milestone/%s@%s>' % (req.base_path, milestone.name, host) if milestone.due: write_prop('BEGIN', 'VEVENT') write_prop('UID', uid) write_utctime('DTSTAMP', milestone.due) write_date('DTSTART', milestone.due) write_prop('SUMMARY', _('Milestone %(name)s', name=milestone.name)) write_prop('URL', req.abs_href.milestone(milestone.name)) if milestone.description: write_prop('DESCRIPTION', milestone.description) write_prop('END', 'VEVENT') tickets = get_tickets_for_milestone(self.env, milestone=milestone.name, field='owner') tickets = apply_ticket_permissions(self.env, req, tickets) for tkt_id in [ ticket['id'] for ticket in tickets if ticket['owner'] == user ]: ticket = Ticket(self.env, tkt_id) write_prop('BEGIN', 'VTODO') write_prop('UID', '<%s/ticket/%s@%s>' % (req.base_path, tkt_id, host)) if milestone.due: write_prop('RELATED-TO', uid) write_date('DUE', milestone.due) write_prop( 'SUMMARY', _('Ticket #%(num)s: %(summary)s', num=ticket.id, summary=ticket['summary'])) write_prop('URL', req.abs_href.ticket(ticket.id)) write_prop('DESCRIPTION', ticket['description']) priority = get_priority(ticket) if priority: write_prop('PRIORITY', unicode(priority)) write_prop('STATUS', get_status(ticket)) if ticket['status'] == 'closed': for time, in self.env.db_query( """ SELECT time FROM ticket_change WHERE ticket=%s AND field='status' ORDER BY time desc LIMIT 1 """, (ticket.id, )): write_utctime('COMPLETED', from_utimestamp(time)) write_prop('END', 'VTODO') write_prop('END', 'VCALENDAR') ics_str = buf.getvalue().encode('utf-8') req.send_header('Content-Length', len(ics_str)) req.end_headers() req.write(ics_str) raise RequestDone
def get_annotation_type(self): return 'lineno', _('Line'), _('Line numbers')
def get_ticket_group_stats(self, ticket_ids): total_cnt = len(ticket_ids) all_statuses = set(TicketSystem(self.env).get_all_status()) status_cnt = {} for s in all_statuses: status_cnt[s] = 0 if total_cnt: for status, count in self.env.db_query(""" SELECT status, count(status) FROM ticket WHERE id IN (%s) GROUP BY status """ % ",".join(str(x) for x in sorted(ticket_ids))): status_cnt[status] = count stat = TicketGroupStats(_('ticket status'), _('tickets')) remaining_statuses = set(all_statuses) groups = self._get_ticket_groups() catch_all_group = None # we need to go through the groups twice, so that the catch up group # doesn't need to be the last one in the sequence for group in groups: status_str = group['status'].strip() if status_str == '*': if catch_all_group: raise TracError( _( "'%(group1)s' and '%(group2)s' milestone groups " "both are declared to be \"catch-all\" groups. " "Please check your configuration.", group1=group['name'], group2=catch_all_group['name'])) catch_all_group = group else: group_statuses = set([s.strip() for s in status_str.split(',')]) \ & all_statuses if group_statuses - remaining_statuses: raise TracError( _( "'%(groupname)s' milestone group reused status " "'%(status)s' already taken by other groups. " "Please check your configuration.", groupname=group['name'], status=', '.join(group_statuses - remaining_statuses))) else: remaining_statuses -= group_statuses group['statuses'] = group_statuses if catch_all_group: catch_all_group['statuses'] = remaining_statuses for group in groups: group_cnt = 0 query_args = {} for s, cnt in status_cnt.iteritems(): if s in group['statuses']: group_cnt += cnt query_args.setdefault('status', []).append(s) for arg in [ kv for kv in group.get('query_args', '').split(',') if '=' in kv ]: k, v = [a.strip() for a in arg.split('=', 1)] query_args.setdefault(k, []).append(v) stat.add_interval(group.get('label', group['name']), group_cnt, query_args, group.get('css_class', group['name']), as_bool(group.get('overall_completion'))) stat.refresh_calcs() return stat
def get_admin_panels(self, req): if 'TRAC_ADMIN' in req.perm: yield ('general', _('General'), 'plugin', _('Plugins'))
def render(self, context, mimetype, content, filename=None, url=None, annotations=None, force_source=False): """Render an XHTML preview of the given `content`. `content` is the same as an `IHTMLPreviewRenderer.render`'s `content` argument. The specified `mimetype` will be used to select the most appropriate `IHTMLPreviewRenderer` implementation available for this MIME type. If not given, the MIME type will be infered from the filename or the content. Return a string containing the XHTML text. When rendering with an `IHTMLPreviewRenderer` fails, a warning is added to the request associated with the context (if any), unless the `disable_warnings` hint is set to `True`. """ if not content: return '' if not isinstance(context, RenderingContext): raise TypeError("RenderingContext expected (since 0.11)") # Ensure we have a MIME type for this content full_mimetype = mimetype if not full_mimetype: if hasattr(content, 'read'): content = content.read(self.max_preview_size) full_mimetype = self.get_mimetype(filename, content) if full_mimetype: mimetype = ct_mimetype(full_mimetype) # split off charset else: mimetype = full_mimetype = 'text/plain' # fallback if not binary # Determine candidate `IHTMLPreviewRenderer`s candidates = [] for renderer in self.renderers: qr = renderer.get_quality_ratio(mimetype) if qr > 0: candidates.append((qr, renderer)) candidates.sort(lambda x, y: cmp(y[0], x[0])) # Wrap file-like object so that it can be read multiple times if hasattr(content, 'read'): content = Content(content, self.max_preview_size) # First candidate which renders successfully wins. # Also, we don't want to expand tabs more than once. expanded_content = None for qr, renderer in candidates: if force_source and not getattr(renderer, 'returns_source', False): continue # skip non-source renderers in force_source mode if isinstance(content, Content): content.reset() try: ann_names = ', '.join(annotations) if annotations else \ 'no annotations' self.log.debug('Trying to render HTML preview using %s [%s]', renderer.__class__.__name__, ann_names) # check if we need to perform a tab expansion rendered_content = content if getattr(renderer, 'expand_tabs', False): if expanded_content is None: content = content_to_unicode(self.env, content, full_mimetype) expanded_content = content.expandtabs(self.tab_width) rendered_content = expanded_content result = renderer.render(context, full_mimetype, rendered_content, filename, url) if not result: continue if not (force_source or getattr(renderer, 'returns_source', False)): # Direct rendering of content if isinstance(result, basestring): if not isinstance(result, unicode): result = to_unicode(result) return Markup(to_unicode(result)) elif isinstance(result, Fragment): return result.generate() else: return result # Render content as source code if annotations: marks = context.req.args.get('marks') if context.req \ else None if marks: context.set_hints(marks=marks) return self._render_source(context, result, annotations) else: if isinstance(result, list): result = Markup('\n').join(result) return tag.div(class_='code')(tag.pre(result)).generate() except Exception as e: self.log.warning('HTML preview using %s with %r failed: %s', renderer.__class__.__name__, context, exception_to_unicode(e, traceback=True)) if context.req and not context.get_hint('disable_warnings'): from trac.web.chrome import add_warning add_warning( context.req, _("HTML preview using %(renderer)s failed (%(err)s)", renderer=renderer.__class__.__name__, err=exception_to_unicode(e)))
def get_admin_panels(self, req): if 'PERMISSION_GRANT' in req.perm or 'PERMISSION_REVOKE' in req.perm: yield ('general', _('General'), 'perm', _('Permissions'))
def process_request(self, req): panels, providers = self._get_panels(req) if not panels: raise HTTPNotFound(_('No administration panels available')) def _panel_order(p1, p2): if p1[::2] == ('general', 'basics'): return -1 elif p2[::2] == ('general', 'basics'): return 1 elif p1[0] == 'general': if p2[0] == 'general': return cmp(p1[1:], p2[1:]) return -1 elif p2[0] == 'general': if p1[0] == 'general': return cmp(p1[1:], p2[1:]) return 1 return cmp(p1, p2) panels.sort(_panel_order) cat_id = req.args.get('cat_id') or panels[0][0] panel_id = req.args.get('panel_id') path_info = req.args.get('path_info') if not panel_id: try: panel_id = filter(lambda panel: panel[0] == cat_id, panels)[0][2] except IndexError: raise HTTPNotFound(_('Unknown administration panel')) provider = providers.get((cat_id, panel_id), None) if not provider: raise HTTPNotFound(_('Unknown administration panel')) if hasattr(provider, 'render_admin_panel'): template, data = provider.render_admin_panel( req, cat_id, panel_id, path_info) else: # support for legacy WebAdmin panels data = {} cstmpl, ct = provider.process_admin_request( req, cat_id, panel_id, path_info) output = cstmpl.render() title = _("Untitled") for panel in panels: if (panel[0], panel[2]) == (cat_id, panel_id): title = panel[3] data.update({'page_title': title, 'page_body': HTML(output)}) template = 'admin_legacy.html' data.update({ 'active_cat': cat_id, 'active_panel': panel_id, 'panel_href': partial(req.href, 'admin', cat_id, panel_id), 'panels': [{ 'category': { 'id': panel[0], 'label': panel[1] }, 'panel': { 'id': panel[2], 'label': panel[3] } } for panel in panels] }) add_stylesheet(req, 'common/css/admin.css') return template, data, None
def get_admin_panels(self, req): if 'TRAC_ADMIN' in req.perm: yield ('general', _('General'), 'logging', _('Logging'))
def render_admin_panel(self, req, cat, page, path_info): perm = PermissionSystem(self.env) all_permissions = perm.get_all_permissions() all_actions = perm.get_actions() if req.method == 'POST': subject = req.args.get('subject', '').strip() action = req.args.get('action') group = req.args.get('group', '').strip() if subject and subject.isupper() or \ group and group.isupper(): raise TracError( _('All upper-cased tokens are reserved for ' 'permission names')) # Grant permission to subject if req.args.get('add') and subject and action: req.perm.require('PERMISSION_GRANT') if action not in all_actions: raise TracError(_('Unknown action')) req.perm.require(action) if (subject, action) not in all_permissions: perm.grant_permission(subject, action) add_notice( req, _( 'The subject %(subject)s has been ' 'granted the permission %(action)s.', subject=subject, action=action)) req.redirect(req.href.admin(cat, page)) else: add_warning( req, _( 'The permission %(action)s was already ' 'granted to %(subject)s.', action=action, subject=subject)) # Add subject to group elif req.args.get('add') and subject and group: req.perm.require('PERMISSION_GRANT') for action in perm.get_user_permissions(group): if not action in all_actions: # plugin disabled? self.env.log.warn("Adding %s to group %s: " \ "Permission %s unavailable, skipping perm check." \ % (subject, group, action)) else: req.perm.require(action) if (subject, group) not in all_permissions: perm.grant_permission(subject, group) add_notice( req, _( 'The subject %(subject)s has been added ' 'to the group %(group)s.', subject=subject, group=group)) req.redirect(req.href.admin(cat, page)) else: add_warning( req, _( 'The subject %(subject)s was already ' 'added to the group %(group)s.', subject=subject, group=group)) # Remove permissions action elif req.args.get('remove') and req.args.get('sel'): req.perm.require('PERMISSION_REVOKE') sel = req.args.get('sel') sel = sel if isinstance(sel, list) else [sel] for key in sel: subject, action = key.split(':', 1) subject = unicode_from_base64(subject) action = unicode_from_base64(action) if (subject, action) in perm.get_all_permissions(): perm.revoke_permission(subject, action) add_notice(req, _('The selected permissions have been ' 'revoked.')) req.redirect(req.href.admin(cat, page)) perms = [perm for perm in all_permissions if perm[1].isupper()] groups = [perm for perm in all_permissions if not perm[1].isupper()] return 'admin_perms.html', { 'actions': all_actions, 'perms': perms, 'groups': groups, 'unicode_to_base64': unicode_to_base64 }
def initenv_error(msg): printerr(_("Initenv for '%(env)s' failed.", env=self.envname), "\n%s" % msg)
def render_admin_panel(self, req, cat, page, path_info): log_type = self.env.log_type log_level = self.env.log_level log_file = self.env.log_file log_dir = os.path.join(self.env.path, 'log') log_types = [ dict(name='none', label=_('None'), selected=log_type == 'none', disabled=False), dict(name='stderr', label=_('Console'), selected=log_type == 'stderr', disabled=False), dict(name='file', label=_('File'), selected=log_type == 'file', disabled=False), dict(name='syslog', label=_('Syslog'), disabled=os.name != 'posix', selected=log_type in ('unix', 'syslog')), dict(name='eventlog', label=_('Windows event log'), disabled=os.name != 'nt', selected=log_type in ('winlog', 'eventlog', 'nteventlog')), ] log_levels = ['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'] if req.method == 'POST': changed = False new_type = req.args.get('log_type') if new_type not in [t['name'] for t in log_types]: raise TracError(_('Unknown log type %(type)s', type=new_type), _('Invalid log type')) if new_type != log_type: self.config.set('logging', 'log_type', new_type) changed = True log_type = new_type if log_type == 'none': self.config.remove('logging', 'log_level') changed = True else: new_level = req.args.get('log_level') if new_level not in log_levels: raise TracError( _('Unknown log level %(level)s', level=new_level), _('Invalid log level')) if new_level != log_level: self.config.set('logging', 'log_level', new_level) changed = True log_level = new_level if log_type == 'file': new_file = req.args.get('log_file', 'trac.log') if new_file != log_file: self.config.set('logging', 'log_file', new_file) changed = True log_file = new_file if not log_file: raise TracError(_('You must specify a log file'), _('Missing field')) else: self.config.remove('logging', 'log_file') changed = True if changed: _save_config(self.config, req, self.log), req.redirect(req.href.admin(cat, page)) data = { 'type': log_type, 'types': log_types, 'level': log_level, 'levels': log_levels, 'file': log_file, 'dir': log_dir } return 'admin_logging.html', {'log': data}
def render_preference_panel(self, req, panel, path_info=None): if req.method == 'POST': action_arg = req.args.getfirst('action', '').split('_', 1) if len(action_arg) == 2: action, arg = action_arg handler = self.post_handlers.get(action) if handler: handler(arg, req) add_notice(req, _("Your preferences have been saved.")) req.redirect(req.href.prefs('notification')) rules = {} subscribers = [] formatters = {} selected_format = {} defaults = [] for i in self.subscribers: description = i.description() if not description: continue if not req.session.authenticated and i.requires_authentication(): continue subscribers.append({ 'class': i.__class__.__name__, 'description': description }) if hasattr(i, 'default_subscriptions'): defaults.extend(i.default_subscriptions()) desc_map = dict((s['class'], s['description']) for s in subscribers) for t in self._iter_transports(): rules[t] = [] formatters[t] = self._get_supported_styles(t) selected_format[t] = req.session.get('notification.format.%s' % t) for r in self._iter_rules(req, t): description = desc_map.get(r['class']) if description: values = {'description': description} values.update( (key, r[key]) for key in ('id', 'adverb', 'class', 'priority')) rules[t].append(values) default_rules = {} for r in sorted(defaults, key=itemgetter(3)): # sort by priority klass, dist, format, priority, adverb = r default_rules.setdefault(dist, []) description = desc_map.get(klass) if description: default_rules[dist].append({ 'adverb': adverb, 'description': description }) data = { 'rules': rules, 'subscribers': subscribers, 'formatters': formatters, 'selected_format': selected_format, 'default_rules': default_rules, 'adverbs': ('always', 'never'), 'adverb_labels': { 'always': _("Notify"), 'never': _("Never notify") } } Chrome(self.env).add_jquery_ui(req) return 'prefs_notification.html', dict(data=data)
def get_admin_panels(self, req): if 'TRAC_ADMIN' in req.perm: yield ('general', _('General'), 'basics', _('Basic Settings'))
def get_preference_panels(self, req): yield ('notification', _('Notifications'))
def do_initenv(self, line): def initenv_error(msg): printerr(_("Initenv for '%(env)s' failed.", env=self.envname), "\n%s" % msg) if self.env_check(): initenv_error(_("Does an environment already exist?")) return 2 arg = self.arg_tokenize(line) inherit_paths = [] config_file_path = None i = 0 while i < len(arg): item = arg[i] if item.startswith('--inherit='): inherit_paths.append(arg.pop(i)[10:]) elif item.startswith('--config='): config_file_path = arg.pop(i)[9:] else: i += 1 config = None if config_file_path: if not os.path.exists(config_file_path): initenv_error(_("The file specified in the --config argument " "does not exist: %(path)s.", path=config_file_path)) return 2 try: config = Configuration(config_file_path) except TracError as e: initenv_error(e) return 2 arg = arg or [''] # Reset to usual empty in case we popped the only one if len(arg) == 1 and not arg[0] and not config: project_name, db_str = self.get_initenv_args() elif len(arg) < 2 and config: project_name = db_str = None if arg[0]: project_name = arg[0] elif len(arg) == 2: project_name, db_str = arg else: initenv_error('Wrong number of arguments: %d' % len(arg)) return 2 options = [] if config: for section in config.sections(defaults=False): options.extend((section, option, value) for option, value in config.options(section)) if project_name is not None: options.append(('project', 'name', project_name)) if db_str is not None: options.append(('trac', 'database', db_str)) if inherit_paths: options.append(('inherit', 'file', ",\n ".join(inherit_paths))) printout(_("Creating and Initializing Project")) try: self.__env = Environment(self.envname, create=True, options=options) except TracError as e: initenv_error(e) return 2 except Exception as e: initenv_error(_('Failed to create environment.')) printerr(e) traceback.print_exc() sys.exit(1) printout(_(""" --------------------------------------------------------------------- Project environment for '%(project_name)s' created. You may now configure the environment by editing the file: %(config_path)s If you'd like to take this new project environment for a test drive, try running the Trac standalone web server `tracd`: tracd --port 8000 %(project_path)s Then point your browser to http://localhost:8000/%(project_dir)s. There you can also browse the documentation for your installed version of Trac, including information on further setup (such as deploying Trac to a real web server). The latest documentation can also always be found on the project website: http://trac.edgewall.org/ Congratulations! """, project_name=project_name, project_path=self.envname, project_dir=os.path.basename(self.envname), config_path=self.__env.config_file_path))