def process_request(self, req): if req.is_xhr and req.method == 'POST' and 'save_prefs' in req.args: self._do_save_xhr(req) panels, providers = self._get_panels(req) if not panels: raise HTTPNotFound(_("No preference panels available")) panels = [] child_panels = {} providers = {} for provider in self.panel_providers: for panel in provider.get_preference_panels(req) or []: if len(panel) == 3: name, label, parent = panel child_panels.setdefault(parent, []).append((name, label)) else: name = panel[0] panels.append(panel) providers[name] = provider panels = sorted(panels) panel_id = req.args.get('panel_id') if panel_id is None: panel_id = panels[1][0] \ if len(panels) > 1 and panels[0][0] == 'advanced' \ else panels[0][0] chosen_provider = providers.get(panel_id) if not chosen_provider: raise HTTPNotFound(_("Unknown preference panel '%(panel)s'", panel=panel_id)) session_data = { 'session': req.session, 'settings': {'session': req.session, # Compat: remove in 1.3.1 'session_id': req.session.sid}, } # Render child preference panels. chrome = Chrome(self.env) children = [] if child_panels.get(panel_id): for name, label in child_panels[panel_id]: ctemplate, cdata = provider.render_preference_panel(req, name) cdata.update(session_data) rendered = chrome.render_template(req, ctemplate, cdata, fragment=True) children.append((name, label, rendered)) template, data = \ chosen_provider.render_preference_panel(req, panel_id) data.update(session_data) data.update({ 'active_panel': panel_id, 'panels': panels, 'children': children, }) add_stylesheet(req, 'common/css/prefs.css') return template, data, None
def _do_deploy(self, dest): target = os.path.normpath(dest) chrome_target = os.path.join(target, "htdocs") script_target = os.path.join(target, "cgi-bin") # Copy static content makedirs(target, overwrite=True) makedirs(chrome_target, overwrite=True) from trac.web.chrome import Chrome printout(_("Copying resources from:")) for provider in Chrome(self.env).template_providers: paths = list(provider.get_htdocs_dirs() or []) if not len(paths): continue printout(" %s.%s" % (provider.__module__, provider.__class__.__name__)) for key, root in paths: if not root: continue source = os.path.normpath(root) printout(" ", source) if os.path.exists(source): dest = os.path.join(chrome_target, key) copytree(source, dest, overwrite=True) # Create and copy scripts makedirs(script_target, overwrite=True) printout(_("Creating scripts.")) data = {"env": self.env, "executable": sys.executable} for script in ("cgi", "fcgi", "wsgi"): dest = os.path.join(script_target, "trac." + script) template = Chrome(self.env).load_template("deploy_trac." + script, "text") stream = template.generate(**data) with open(dest, "w") as out: stream.render("text", out=out, encoding="utf-8")
def filter_stream(self, req, method, filename, stream, data): if req.get_header("X-Moz") == "prefetch": return stream if filename == "ticket.html": if not self.check_permissions(req): return stream chrome = Chrome(self.env) filter = Transformer('//fieldset[@id="properties"]') # add a hidden div to hold the ticket_fields input snippet = tag.div(style="display:none;") snippet = tag.input(type="hidden", id="field-ticket_fields", name="field_ticket_fields", value=','.join(data['ticket_fields'])) stream = stream | filter.after(snippet) if req.path_info != '/newticket': # insert the ticket field groups after the standard trac 'Change Properties' field group stream = stream | filter.after(chrome.render_template(req, 'ticket_fields_datatable.html', data, fragment=True)) elif filename == "admin_enums.html": if not self.check_permissions(req) or not req.args.get('path_info'): return stream for k,v in {'cat_id':'ticket', 'panel_id':'type'}.iteritems(): if k not in req.args or req.args.get(k) != v: return stream if 'ticket_fields' in data: chrome = Chrome(self.env) filter = Transformer('//div[@class="buttons"]') # add a hidden div to hold the ticket_fields input snippet = tag.div(style="display:none;") snippet = tag.input(type="hidden", id="field-ticket_fields", name="field_ticket_fields", value=','.join(data['ticket_fields'])) stream = stream | filter.before(snippet) stream = stream | filter.before(chrome.render_template(req, 'ticket_fields_datatable.html', data, fragment=True)) return stream
def expand_macro(self, formatter, name, content, args=None): """ Returns the outcome from macro. """ req = formatter.req userstore = get_userstore() user = userstore.getUser(req.authname) msgsrv = self.env[MessageService] # Parse optional arguments if args is None: args = parse_args(content) if len(args) > 1: args = args[1] data = { 'groups': msgsrv.get_messages_grouped_by(user.id) } # FIXME: Temporary fix for IE8 + jQuery 1.4.4 + Transparency combination agent = req.get_header('user-agent') if agent and 'MSIE 8.0' not in agent: add_script(req, 'multiproject/js/transparency.js') add_script(req, 'multiproject/js/multiproject.js') add_script(req, 'multiproject/js/messages_group_macro.js') chrome = Chrome(self.env) return chrome.render_template(req, 'multiproject_messages_group_macro.html', data, fragment=True)
def get_diffs(self, req, title, old_text, new_text): diff_style, diff_options, diff_data = get_diff_options(req) diff_context = 3 for option in diff_options: if option.startswith('-U'): diff_context = int(option[2:]) break if diff_context < 0: diff_context = None diffs = diff_blocks(old_text.splitlines(), new_text.splitlines(), context=diff_context, tabwidth=2, ignore_blank_lines=True, ignore_case=True, ignore_space_changes=True) chrome = Chrome(self.env) loader = TemplateLoader(chrome.get_all_templates_dirs()) tmpl = loader.load('diff_div.html') changes=[{'diffs': diffs, 'props': [], 'title': title, 'new': {'path':"", 'rev':'', 'shortrev': '', 'href':''}, 'old': {'path':"", 'rev':'', 'shortrev': '', 'href': ''}}] data = chrome.populate_data(req, { 'changes':changes , 'no_id':True, 'diff':diff_data, 'longcol': '', 'shortcol': ''}) diff_data['style']='sidebyside'; data.update({ 'changes':changes , 'no_id':True, 'diff':diff_data, 'longcol': '', 'shortcol': ''}) stream = tmpl.generate(**data) return stream.render()
def test_no_reports(self): req = MockRequest() config = Mock(name='trunk', min_rev_time=lambda env: 0, max_rev_time=lambda env: 1000, path='tmp/') build = Build(self.env, config='trunk', platform=1, rev=123, rev_time=42) build.insert() step = BuildStep(self.env, build=build.id, name='foo', status=BuildStep.SUCCESS) step.insert() summarizer = PyLintSummarizer(self.env) template, data = summarizer.render_summary(req, config, build, step, 'lint') self.assertEqual('bitten_summary_lint.html', template) self.assertEqual([], data['data']) self.assertEqual({'category': {'convention': 0, 'refactor': 0, 'warning': 0, 'error': 0}, 'files': 0, 'lines': 0, 'type': {}}, data['totals']) stream = Chrome(self.env).render_template(req, template, {'data': data}, 'text/html', fragment=True) stream = Stream(list(stream)) for i, category in enumerate(("Convention", "Refactor", "Warning", "Error", "Totals")): text = stream.select('//tbody[@class="totals"]//td[%d]/text()' % (i + 1)).render() self.assertEqual('0', text, "Expected total for %r to have " "value '0' but got %r" % (category, text))
def expand_macro(self, formatter, name, content): req = formatter.req # Parse arguments args, kwargs = parse_args(content, strict=False) assert not args and not ('status' in kwargs or 'format' in kwargs), \ "Invalid input!" # hack the `format` kwarg in order to display all-tickets stats # when no kwargs are supplied kwargs['format'] = 'count' # special case for values equal to 'self': replace with current # ticket number, if available for key in kwargs.keys(): if kwargs[key] == 'self': current_ticket = self._this_ticket(req) if current_ticket: kwargs[key] = current_ticket # Create & execute the query string qstr = '&'.join(['%s=%s' % item for item in kwargs.iteritems()]) query = Query.from_string(self.env, qstr, max=0) # Calculate stats qres = query.execute(req) tickets = apply_ticket_permissions(self.env, req, qres) stats = get_ticket_stats(self.stats_provider, tickets) stats_data = query_stats_data(req, stats, query.constraints) # ... and finally display them add_stylesheet(req, 'common/css/roadmap.css') chrome = Chrome(self.env) return chrome.render_template(req, 'progressmeter.html', stats_data, fragment=True)
def _generate_form(self, req, data): userQueryData = dict(data) userQueryData['user_queries'] =[] userQueryData['empty_url'] = req.href.report() self.log.debug("Generating UserQueries navigation selector") # not really sure which is the best/definitive way yet: if userQueryData['report']: # SQL queries userQueryData['current_report_id'] = userQueryData['report']['id'] elif userQueryData.has_key('report_resource'): # Custom queries in 0.11? userQueryData['current_report_id'] = int(userQueryData['report_resource'].id) elif userQueryData.has_key('context') and userQueryData['context'].req.args.has_key('report'): # Custom queries in 0.12? userQueryData['current_report_id'] = int(userQueryData['context'].req.args['report']) else: # User visiting /report userQueryData['current_report_id'] = -1 self.log.debug("User is visiting report %d", userQueryData['current_report_id']) db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT id AS report, title FROM report ORDER BY report") userQueryData['project_queries'] = [{'url': req.href.report(id), 'id': id, 'name': title} for id, title in cursor] stream = Chrome(self.env).render_template(req, 'userqueries.html', userQueryData, fragment=True) return stream.select('//form')
def _create_page_param(self, req, page_param): # page_param['workflow_config'] # sort config for display section = self.config['ticket-workflow'] name_list = [] for (name, value) in section.options(): name_list.append(name) name_list.sort() # create config data for display ret_val = '' for name in name_list: ret_val += name + '=' + section.get(name) + '\n' page_param['workflow_config'] = ret_val # page_param['workflow_default_config'] # localization locale = LocaleUtil().get_locale(req) if (locale == 'ja'): init_file = 'trac_jp.ini' else: init_file = 'trac.ini' # read defalut config template = Chrome(self.env).load_template(init_file, 'text') stream = template.generate() default_config = stream.render('text', encoding=None) page_param['workflow_default_config'] = default_config
def expand_macro(self, formatter, name, args): req = formatter.req chrome = Chrome(self.env) report = ReportModule(self.env) comma_splitter = re.compile(r'(?<!\\),') kwargs = {} for arg in comma_splitter.split(args): arg = arg.replace(r'\,', ',') m = re.match(r'\s*[^=]+=', arg) if m: kw = arg[:m.end() - 1].strip() value = arg[m.end():] if re.match(r'^\$[A-Z]*$', value): value = req.args.get(value[1:]) kwargs[kw] = value if value!= None else '' else: if re.match(r'^\$[A-Z]*$', arg): arg = req.args.get(arg[1:]) id = int(arg) req.args = kwargs req.args['page'] = '1' template, data, content_type = report._render_view(req, id) add_stylesheet(req, 'common/css/report.css') fullpath = '' if pkg_resources.resource_exists('wikireport', 'WikiReport.html'): fullpath = pkg_resources.resource_filename('wikireport', 'WikiReport.html') else: filepath = os.path.dirname(os.path.abspath( __file__ )) fullpath = os.path.join(filepath, 'WikiReport.html') return chrome.render_template(req, fullpath, data, None, fragment=True)
def _render_editor(self, req, milestone): # Suggest a default due time of 18:00 in the user's timezone now = datetime.now(req.tz) default_due = datetime(now.year, now.month, now.day, 18) if now.hour > 18: default_due += timedelta(days=1) default_due = to_datetime(default_due, req.tz) data = { 'milestone': milestone, 'datetime_hint': get_datetime_format_hint(req.lc_time), 'default_due': default_due, 'milestone_groups': [], } if milestone.exists: req.perm(milestone.resource).require('MILESTONE_MODIFY') milestones = [m for m in Milestone.select(self.env) if m.name != milestone.name and 'MILESTONE_VIEW' in req.perm(m.resource)] data['milestone_groups'] = group_milestones(milestones, 'TICKET_ADMIN' in req.perm) else: req.perm(milestone.resource).require('MILESTONE_CREATE') chrome = Chrome(self.env) chrome.add_jquery_ui(req) chrome.add_wiki_toolbars(req) return 'milestone_edit.html', data, None
def expand_macro(self, formatter, name, content, args=None): """ Returns the outcome from macro. """ req = formatter.context.req # Check permissions if 'TIMELINE_VIEW' not in req.perm: # Return default content / instructions return tag.div( tag.h2(_('Project team'), **{'class': 'title'}), tag.p(_('Project team cannot be found or no permission to follow it')), **{'class': 'watch'} ) # Load project info from optional project argument. Defaults to current env project = Project.get(self.env) team, members = self._get_team_info(project) # Return rendered HTML with JS attached to it data = { 'project_id': project.id, 'env_name': self.env.project_identifier, 'project_name': self.env.project_identifier, # TODO: redundant 'team': team, 'members': members } # NOTE: Use fragment to not to recreate chrome (add_script does not work) and run post processing manually chrome = Chrome(self.env) stream = chrome.render_template(req, 'multiproject_team.html', data, fragment=True) if req.form_token: stream |= chrome._add_form_token(req.form_token) return stream
def test_icon_links(self): req = Request(abs_href=Href('http://example.org/trac.cgi'), href=Href('/trac.cgi'), base_path='/trac.cgi', path_info='', add_redirect_listener=lambda listener: None) chrome = Chrome(self.env) # No icon set in config, so no icon links self.env.config.set('project', 'icon', '') links = chrome.prepare_request(req)['links'] assert 'icon' not in links assert 'shortcut icon' not in links # Relative URL for icon config option self.env.config.set('project', 'icon', 'foo.ico') links = chrome.prepare_request(req)['links'] self.assertEqual('/trac.cgi/chrome/common/foo.ico', links['icon'][0]['href']) self.assertEqual('/trac.cgi/chrome/common/foo.ico', links['shortcut icon'][0]['href']) # URL relative to the server root for icon config option self.env.config.set('project', 'icon', '/favicon.ico') links = chrome.prepare_request(req)['links'] self.assertEqual('/favicon.ico', links['icon'][0]['href']) self.assertEqual('/favicon.ico', links['shortcut icon'][0]['href']) # Absolute URL for icon config option self.env.config.set('project', 'icon', 'http://example.com/favicon.ico') links = chrome.prepare_request(req)['links'] self.assertEqual('http://example.com/favicon.ico', links['icon'][0]['href']) self.assertEqual('http://example.com/favicon.ico', links['shortcut icon'][0]['href'])
def expand_macro(self, formatter, name, content, args=None): """ Returns the outcome from macro. Supported arguments: - project: Name of the project to show status / provide follow buttons. Defaults to current project """ req = formatter.req # Parse optional arguments if args is None: args = parse_args(content) if len(args) > 1: args = args[1] # Read optional project name - fallback to current project_name = self.env.project_identifier if args and 'project' in args: project_name = args.get('project', '').strip() # Load project id from db project_id = Project.get(env_name=project_name).id watchers, is_watching = self._get_status(req, project_id) # If user is already watching, do not show the block if is_watching: return tag.div('') # Show macro only when user has permission to view timeline if name not in self.macros or 'TIMELINE_VIEW' not in req.perm or not project_id: # Return default content / instructions return tag.div( tag.h2(_('Follow project'), **{'class': 'title'}), tag.p(_('Project cannot be found or no permission to follow it')), **{'class': 'watch'} ) # For anonymous users, advice login/registering if req.authname == 'anonymous': return tag.div( tag.h2(_('Follow project'), **{'class': 'title'}), tag.p(_('Only registered users can follow the project activity. ')), tag.p(tag.a('Please login or register to service first', href=req.href('../home/user'))), **{'class': 'watch'} ) # Return rendered HTML with JS attached to it data = { 'project_id': project_id, 'env_name': self.env.project_identifier, 'project_name': project_name } chrome = Chrome(self.env) stream = chrome.render_template(req, 'multiproject_watch.html', data, fragment=True) if req.form_token: stream |= chrome._add_form_token(req.form_token) return stream
def expand_macro(self, formatter, name, content): req = formatter.req stats_provider, kwargs, is_preview_with_self = self._parse_macro_content(content, req) if is_preview_with_self: # previewing newticket, without a number but with a reference # to current ticket number; show a helpful message return tag.div('Progress meter will be inserted here in final ticket') # Create & execute the query string qstr = '&'.join(['%s=%s' % item for item in kwargs.iteritems()]) query = Query.from_string(self.env, qstr, max=0) try: constraints = query.constraints[0] except IndexError: constraints = query.constraints # Calculate stats qres = query.execute(req) tickets = apply_ticket_permissions(self.env, req, qres) stats = get_ticket_stats(stats_provider, tickets) stats_data = query_stats_data(req, stats, constraints) # ... and finally display them add_stylesheet(req, 'common/css/roadmap.css') chrome = Chrome(self.env) return chrome.render_template(req, 'progressmeter.html', stats_data, fragment=True)
class ChromeTestCase2(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(path=tempfile.mkdtemp()) self.chrome = Chrome(self.env) def tearDown(self): shutil.rmtree(self.env.path) def test_malicious_filename_raises(self): req = Request(path_info='/chrome/site/../conf/trac.ini') self.assertTrue(self.chrome.match_request(req)) self.assertRaises(TracError, self.chrome.process_request, req) def test_empty_shared_htdocs_dir_raises_file_not_found(self): req = Request(path_info='/chrome/shared/trac_logo.png') self.assertEqual('', self.chrome.shared_htdocs_dir) self.assertTrue(self.chrome.match_request(req)) from trac.web.api import HTTPNotFound self.assertRaises(HTTPNotFound, self.chrome.process_request, req) def test_shared_htdocs_dir_file_is_found(self): from trac.web.api import RequestDone def send_file(path, mimetype): raise RequestDone req = Request(path_info='/chrome/shared/trac_logo.png', send_file=send_file) shared_htdocs_dir = os.path.join(self.env.path, 'chrome', 'shared') os.makedirs(shared_htdocs_dir) create_file(os.path.join(shared_htdocs_dir, 'trac_logo.png')) self.env.config.set('inherit', 'htdocs_dir', shared_htdocs_dir) self.assertTrue(self.chrome.match_request(req)) self.assertRaises(RequestDone, self.chrome.process_request, req)
def expand_macro(self, formatter, name, content): req = formatter.req stats_provider, kwargs, preview = self._parse_macro_content(content, req) # Create & execute the query string qstr = '&'.join(['%s=%s' % item for item in kwargs.iteritems()]) query = Query.from_string(self.env, qstr) try: # XXX: simplification, may cause problems with more complex queries constraints = query.constraints[0] except IndexError: constraints = {} # Calculate stats qres = query.execute(req) tickets = apply_ticket_permissions(self.env, req, qres) stats = get_ticket_stats(stats_provider, tickets) stats_data = query_stats_data(req, stats, constraints) # ... and finally display them add_stylesheet(req, 'common/css/roadmap.css') chrome = Chrome(self.env) stats_data.update({'preview': preview}) # displaying a preview? return chrome.render_template(req, 'progressmeter.html', stats_data, fragment=True)
def expand_macro(self, formatter, name, content): data=dict(user_profiles=[], user_profile_fields={}) rendered_result="" content_args={} layout_args={} user_profile_templates=[] # collecting arguments if content: for i, macro_args in enumerate( content.split('|') ): if i == 0: content_args = MacroArguments( macro_args ) continue if i == 1: layout_args = MacroArguments( macro_args ) break # extracting userProfile attrs if len(content_args)>0: user_profile_templates.append(User(**content_args)) if len(content_args.get_list_args())>0: for list_item in content_args.get_list_args(): user_profile_templates.append(User( **MacroArguments(list_item[1:len(list_item)-1]))) # adding profiles fields description data['user_profile_fields'].update(UserProfileManager(self.env).get_user_profile_fields(ignore_internal=True)) # removing picture_href data['user_profile_fields'].pop('picture_href') def inline_wiki_to_html(text): return wiki_to_html(text, self.env, formatter.req) data['wiki_to_html'] = inline_wiki_to_html # grabbing users if len(user_profile_templates)>0: data['user_profiles'] = UserManager(self.env).search_users(user_profile_templates) else: data['user_profiles'] = UserManager(self.env).get_active_users() data['cells']=list(self._get_cells(data['user_profiles'])) # add stylesheet&script add_stylesheet(formatter.req,'tracusermanager/css/macros_um_profile.css') add_script(formatter.req,'tracusermanager/js/macros_um_profile.js') # render template template = Chrome(self.env).load_template('macro_um_profile.html',method='xhtml') data = Chrome(self.env).populate_data(formatter.req, {'users':data}) rendered_result = template.generate(**data) # wrap everything if len(layout_args)>0: rendered_result= html.div(rendered_result, **layout_args) return rendered_result
def render_property_diff(self, req, ticket, field, old, new, resource_new=None): "Version for Trac 0.11" rendered = None # per type special rendering of diffs type_ = None for f in ticket.fields: if f['name'] == field: type_ = f['type'] break if type_ == 'checkbox': rendered = new == '1' and "set" or "unset" elif type_ == 'textarea': if not resource_new: rendered = _('modified') else: href = get_resource_url(self.env, resource_new, req.href, action='diff') rendered = tag('modified (', tag.a('diff', href=href), ')') # per name special rendering of diffs old_list, new_list = None, None render_elt = lambda x: x sep = ', ' if field == 'cc': chrome = Chrome(self.env) old_list, new_list = chrome.cc_list(old), chrome.cc_list(new) if not (Chrome(self.env).show_email_addresses or 'EMAIL_VIEW' in req.perm(resource_new or ticket.resource)): render_elt = obfuscate_email_address elif field == 'keywords': old_list, new_list = (old or '').split(), new.split() sep = ' ' if (old_list, new_list) != (None, None): added = [tag.em(render_elt(x)) for x in new_list if x not in old_list] remvd = [tag.em(render_elt(x)) for x in old_list if x not in new_list] added = added and tag(separated(added, sep), " added") remvd = remvd and tag(separated(remvd, sep), " removed") if added or remvd: rendered = tag(added, added and remvd and '; ', remvd) return rendered if field in ('reporter', 'owner'): if not (Chrome(self.env).show_email_addresses or 'EMAIL_VIEW' in req.perm(resource_new or ticket.resource)): old = obfuscate_email_address(old) new = obfuscate_email_address(new) # Added by MS if field == 'attachment': rendered = tag(tag.em(new), " added") # changed 'if' to 'elif': elif old and not new: rendered = tag(tag.em(old), " deleted") elif new and not old: rendered = tag("set to ", tag.em(new)) elif old and new: rendered = tag("changed from ", tag.em(old), " to ", tag.em(new)) return rendered
def matches(self, event): if event.realm != 'ticket': return if event.category not in ('created', 'changed', 'attachment added', 'attachment deleted'): return # CC field is stored as comma-separated string. Parse to set. chrome = Chrome(self.env) to_set = lambda cc: set(chrome.cc_list(cc)) cc_set = to_set(event.target['cc'] or '') # Harvest previous CC field if 'fields' in event.changes and 'cc' in event.changes['fields']: cc_set.update(to_set(event.changes['fields']['cc']['old'])) matcher = RecipientMatcher(self.env) klass = self.__class__.__name__ sids = set() for cc in cc_set: recipient = matcher.match_recipient(cc) if not recipient: continue sid, auth, addr = recipient # Default subscription for s in self.default_subscriptions(): yield (s[0], s[1], sid, auth, addr, s[2], s[3], s[4]) if sid: sids.add((sid,auth)) for s in Subscription.find_by_sids_and_class(self.env, sids, klass): yield s.subscription_tuple()
def filter_stream(self, req, method, filename, stream, data): """Return a filtered Genshi event stream, or the original unfiltered stream if no match. `req` is the current request object, `method` is the Genshi render method (xml, xhtml or text), `filename` is the filename of the template to be rendered, `stream` is the event stream and `data` is the data for the current template. See the Genshi documentation for more information. """ if filename == "query.html" and self.inject_query: self.geoticket() # sanity check chrome = Chrome(self.env) variables = ("center_location", "radius") _data = dict([(i, data.get(i)) for i in variables]) # georegions _data["geo_column_label"] = None _data["regions"] = None if self.env.is_component_enabled(GeoRegions): georegions = GeoRegions(self.env) if georegions.enabled(): regions = georegions.regions() if regions: column, regions = regions _data["geo_column_label"] = column _data["regions"] = regions _data["region"] = req.args.get("region") template = chrome.load_template("geoquery.html") stream |= Transformer("//fieldset[@id='columns']").after(template.generate(**_data)) return stream
def expand_macro(self, formatter, name, content): req = formatter.req stats_provider, kwargs = self._parse_macro_content(content, req) # Create & execute the query string qstr = '&'.join(['%s=%s' % item for item in kwargs.iteritems()]) query = Query.from_string(self.env, qstr, max=0) try: constraints = query.constraints[0] except IndexError: constraints = query.constraints # Calculate stats qres = query.execute(req) tickets = apply_ticket_permissions(self.env, req, qres) stats = get_ticket_stats(stats_provider, tickets) stats_data = query_stats_data(req, stats, constraints) # ... and finally display them add_stylesheet(req, 'common/css/roadmap.css') chrome = Chrome(self.env) return chrome.render_template(req, 'progressmeter.html', stats_data, fragment=True)
def process_request(self, req): req.perm.assert_permission('STRACTISTICS_VIEW') add_stylesheet(req, 'hw/css/stractistics.css') #Reading options from trac.ini config = util.read_config_options(self.env.config) #Patch for Trac 0.11 if trac.__version__.find('0.11') != -1: chrome = Chrome(self.env) chrome.populate_hdf(req) #Populating our context navigation bar. strac_ref = req.href.stractistics() links = [] for elem in self.sections: links.append((elem[0], "/".join([strac_ref, elem[1]]))) req.hdf['section_links'] = links db = self.env.get_db_cnx() module = req.args.get('module', None) if module is not None and module == 'user_reports': template, content_type = user_reports.user_reports(req, config, db) else: template, content_type = global_reports.global_reports(req, config, db) return template, content_type
def test_attributes_preserved_in_navigation_item(self): class TestNavigationContributor1(Component): implements(INavigationContributor) def get_active_navigation_item(self, req): return None def get_navigation_items(self, req): yield 'mainnav', 'test1', \ tag.a('Test 1', href='test1', target='blank') class TestNavigationContributor2(Component): implements(INavigationContributor) def get_active_navigation_item(self, req): return None def get_navigation_items(self, req): yield 'mainnav', 'test2', \ tag.a('Test 2', href='test2', target='blank') req = Request(abs_href=Href('http://example.org/trac.cgi'), href=Href('/trac.cgi'), base_path='/trac.cgi', path_info='/', add_redirect_listener=lambda listener: None) self.env.config.set('mainnav', 'test1.label', 'Test One') self.env.config.set('mainnav', 'test2.label', 'Test Two') self.env.config.set('mainnav', 'test2.href', 'testtwo') chrome = Chrome(self.env) items = chrome.prepare_request(req)['nav']['mainnav'] item = self._get_navigation_item(items, 'test1') self.assertEqual(str(tag.a('Test One', href='test1', target='blank')), str(item['label'])) item = self._get_navigation_item(items, 'test2') self.assertEqual(str(tag.a('Test Two', href='testtwo', target='blank')), str(item['label']))
def _render_editor(self, req, milestone): data = { 'milestone': milestone, 'datetime_hint': get_datetime_format_hint(req.lc_time), 'default_due': self.get_default_due(req), 'milestone_groups': [], } if milestone.exists: req.perm(milestone.resource).require('MILESTONE_MODIFY') milestones = [m for m in Milestone.select(self.env) if m.name != milestone.name and 'MILESTONE_VIEW' in req.perm(m.resource)] data['milestone_groups'] = group_milestones(milestones, 'TICKET_ADMIN' in req.perm) data['num_open_tickets'] = milestone \ .get_num_tickets(exclude_closed=True) data['retarget_to'] = self.default_retarget_to else: req.perm(milestone.resource).require('MILESTONE_CREATE') if milestone.name: add_notice(req, _("Milestone %(name)s does not exist. You can" " create it here.", name=milestone.name)) chrome = Chrome(self.env) chrome.add_jquery_ui(req) chrome.add_wiki_toolbars(req) add_stylesheet(req, 'common/css/roadmap.css') return 'milestone_edit.html', data, None
def filter_stream(self, req, method, filename, stream, data): chrome = Chrome(self.env) if req.path_info.startswith('/milestone') \ and req.args.get('action') in ['edit', 'new'] \ and 'max_level' not in data: milestone = data.get('milestone') levels = IttecoMilestoneAdminPanel(self.env).milestone_levels mydata ={'structured_milestones':StructuredMilestone.select(self.env), 'max_level': levels and len(levels)-1 or 0, 'milestone_name' : milestone and milestone.parent or None, 'field_name' : 'parent'} stream |=Transformer('//*[@id="edit"]/fieldset').append( chrome.render_template(req, 'itteco_milestones_dd.html', mydata, fragment=True)) if 'ticket' in data: tkt = data['ticket'] mydata ={ 'structured_milestones':StructuredMilestone.select(self.env), 'milestone_name': data['ticket']['milestone'], 'field_name' : 'field_milestone', 'hide_completed' : not ( tkt.exists and 'TICKET_ADMIN' in req.perm(tkt.resource)) } req.chrome.setdefault('ctxtnav',[]).insert( -1, tag.a( _('Go To Whiteboard'), href=req.href.whiteboard('team_tasks', data['ticket']['milestone'] or 'none') ) ) stream |=Transformer('//*[@id="field-milestone"]').replace( chrome.render_template(req, 'itteco_milestones_dd.html', mydata, fragment=True)) return stream
def render_ticket_action_control(self, req, ticket, action): config = self.parse_config() assert action in config control = [] hints = [] data = config[action] action_name = action # @@TODO: config'able label/name chrome = Chrome(self.env) from trac.ticket.web_ui import TicketModule prepared_fields = TicketModule(self.env)._prepare_fields(req, ticket) for field in data.get('fields', []): id = "action_%s_%s" % (action, field) operation = data.get('operations', {}).get(field, "change") assert operation in ["change", "unset"] if operation == "unset": hints.append("%s will be unset" % field) # @@TODO: i18n continue assert operation == "change" current_value = ticket._old.get(field, ticket[field]) or "" rendered_control = '' prepared_field = [pfield for pfield in prepared_fields if pfield['name'] == field] if len(prepared_field): prepared_field = prepared_field[0] # we can't use chrome.render_template here, or it will blow away # key scripts like jquery.js and trac.js in the eventual 'primary' template # that's rendered by process_request template = chrome.load_template("ticket_fields.html", method="xhtml") rendered_control = template.generate(ticket=ticket, field=prepared_field) if rendered_control: rendered_control = Markup(rendered_control) control.append(tag.label(field, rendered_control or tag.input( name=id, id=id, type='text', value=current_value))) current_status = ticket._old.get('status', ticket['status']) new_status = data['status'].get(current_status) or \ data['status']['*'] if new_status != '*': hints.append("Next status will be %s" % new_status) # @@TODO: i18n add_script(req, "workflow_ticketfields/workflow_ticketfields.js") return (action_name, tag.div(*[tag.div(element, style=("display: inline-block; " "margin-right: 1em")) for element in control], class_="workflow_ticket_fields", style="margin-left: 2em; display: none"), '. '.join(hints) + '.' if hints else '')
def filter_stream(self, req, method, filename, stream, data): action = req.args.get('action', '') if filename == 'browser.html' and action == 'edit': req.perm.require('REPOSITORY_MODIFY') # NB TracBrowserOps already inserts javascript and css we need # So only add css/javascript needed solely by the editor if data['file'] and data['file']['preview']['rendered']: max_edit_size = self.max_edit_size data['max_edit_size'] = max_edit_size # Discard rendered table, replace with textarea of file contents # This means reading the file from the repository again # N.B. If a file is rendered as something other than a table # e.g. due to PreCodeBrowserPlugin this code won't trigger # Retrieve the same node that BrowserModule.process_request() # used to render the preview. # At this point reponame has been removed from data['path'] # and repos has already been determined repos = data['repos'] path = data['path'] rev = data['rev'] node = repos.get_node(path, rev) # If node is too large then don't allow editing, abort if max_edit_size > 0 and node.content_length > max_edit_size: return stream # Open the node and read it node_file = node.get_content() node_data = node_file.read() # Discover the mime type and character encoding of the node # Try in order # - svn:mime-type property # - detect from file name and content (BOM) # - use configured default for Trac mime_view = Mimeview(self.env) mime_type = node.content_type \ or mime_view.get_mimetype(node.name, node_data) \ or 'text/plain' encoding = mime_view.get_charset(node_data, mime_type) # Populate template data content = mime_view.to_unicode(node_data, mime_type, encoding) data['file_content'] = content data['file_encoding'] = encoding # Replace the already rendered preview with a form and textarea bsops_stream = Chrome(self.env).render_template(req, 'file_edit.html', data, fragment=True) transf = Transformer('//div[@id="preview"]' '/table[@class="code"]') stream |= transf.replace( bsops_stream.select('//div[@id="bsop_edit"]')) return stream
def content(self, req, ticket): tm = TicketMover(self.env) projects = tm.projects(req.authname) chrome = Chrome(self.env) template = chrome.load_template('ticketmover-sidebar.html') data = {'projects': projects, 'req': req, 'ticket': ticket} return template.generate(**data)
def diff_cc(self, old, new): chrome = Chrome(self.env) oldcc = chrome.cc_list(old) newcc = chrome.cc_list(new) added = [self.format_author(x) for x in newcc if x and x not in oldcc] rmved = [self.format_author(x) for x in oldcc if x and x not in newcc] return added, rmved
def render_admin_panel(self, req, cat, page, path_info): valid_default_handlers = [ handler.__class__.__name__ for handler in self.request_handlers if is_valid_default_handler(handler) ] if Locale: locale_ids = get_available_locales() locales = [Locale.parse(locale) for locale in locale_ids] # don't use str(locale) to prevent storing expanded locale # identifier, see #11258 languages = sorted((id, locale.display_name) for id, locale in zip(locale_ids, locales)) else: locale_ids, locales, languages = [], [], [] if req.method == 'POST': for option in ('name', 'url', 'descr'): self.config.set('project', option, req.args.get(option)) default_handler = req.args.get('default_handler') self.config.set('trac', 'default_handler', default_handler) default_timezone = req.args.get('default_timezone') if default_timezone not in all_timezones: default_timezone = '' self.config.set('trac', 'default_timezone', default_timezone) default_language = req.args.get('default_language') if default_language not in locale_ids: default_language = '' self.config.set('trac', 'default_language', default_language) default_date_format = req.args.get('default_date_format') if default_date_format != 'iso8601': default_date_format = '' self.config.set('trac', 'default_date_format', default_date_format) default_dateinfo_format = req.args.get('default_dateinfo_format') if default_dateinfo_format not in ('relative', 'absolute'): default_dateinfo_format = 'relative' self.config.set('trac', 'default_dateinfo_format', default_dateinfo_format) _save_config(self.config, req, self.log) req.redirect(req.href.admin(cat, page)) default_handler = self.config.get('trac', 'default_handler') default_timezone = self.config.get('trac', 'default_timezone') default_language = self.config.get('trac', 'default_language') default_date_format = self.config.get('trac', 'default_date_format') default_dateinfo_format = self.config.get('trac', 'default_dateinfo_format') data = { 'default_handler': default_handler, 'valid_default_handlers': sorted(valid_default_handlers), 'default_timezone': default_timezone, 'timezones': all_timezones, 'has_pytz': pytz is not None, 'default_language': default_language.replace('-', '_'), 'languages': languages, 'default_date_format': default_date_format, 'default_dateinfo_format': default_dateinfo_format, 'has_babel': Locale is not None, } Chrome(self.env).add_textarea_grips(req) return 'admin_basics.html', data
def dispatch(self, req): """Find a registered handler that matches the request and let it process it. In addition, this method initializes the data dictionary passed to the the template and adds the web site chrome. """ self.log.debug('Dispatching %r', req) chrome = Chrome(self.env) try: # Select the component that should handle the request chosen_handler = None for handler in self._request_handlers.values(): if handler.match_request(req): chosen_handler = handler break if not chosen_handler and req.path_info in ('', '/'): chosen_handler = self._get_valid_default_handler(req) # pre-process any incoming request, whether a handler # was found or not self.log.debug("Chosen handler is %s", chosen_handler) chosen_handler = self._pre_process_request(req, chosen_handler) if not chosen_handler: if req.path_info.endswith('/'): # Strip trailing / and redirect target = unicode_quote(req.path_info.rstrip('/')) if req.query_string: target += '?' + req.query_string req.redirect(req.href + target, permanent=True) raise HTTPNotFound('No handler matched request to %s', req.path_info) req.callbacks['chrome'] = partial(chrome.prepare_request, handler=chosen_handler) # Protect against CSRF attacks: we validate the form token # for all POST requests with a content-type corresponding # to form submissions if req.method == 'POST': ctype = req.get_header('Content-Type') if ctype: ctype, options = cgi.parse_header(ctype) if ctype in ('application/x-www-form-urlencoded', 'multipart/form-data') and \ req.args.get('__FORM_TOKEN') != req.form_token: if self.env.secure_cookies and req.scheme == 'http': msg = _('Secure cookies are enabled, you must ' 'use https to submit forms.') else: msg = _('Do you have cookies enabled?') raise HTTPBadRequest( _('Missing or invalid form token.' ' %(msg)s', msg=msg)) # Process the request and render the template resp = chosen_handler.process_request(req) if resp: resp = self._post_process_request(req, *resp) template, data, metadata, method = resp if 'hdfdump' in req.args: req.perm.require('TRAC_ADMIN') # debugging helper - no need to render first out = io.BytesIO() pprint( { 'template': template, 'metadata': metadata, 'data': data }, out) req.send(out.getvalue(), 'text/plain') self.log.debug("Rendering response with template %s", template) iterable = chrome.use_chunked_encoding if isinstance(metadata, dict): iterable = metadata.setdefault('iterable', iterable) content_type = metadata.get('content_type') else: content_type = metadata output = chrome.render_template(req, template, data, metadata, iterable=iterable, method=method) # TODO (1.5.1) remove iterable and method parameters req.send(output, content_type or 'text/html') else: self.log.debug("Empty or no response from handler. " "Entering post_process_request.") self._post_process_request(req) except RequestDone: raise except Exception as e: # post-process the request in case of errors err = sys.exc_info() try: self._post_process_request(req) except RequestDone: pass except TracError as e2: self.log.warning( "Exception caught while post-processing" " request: %s", exception_to_unicode(e2)) except Exception as e2: if not (type(e) is type(e2) and e.args == e2.args): self.log.error( "Exception caught while post-processing" " request: %s", exception_to_unicode(e2, traceback=True)) if isinstance(e, PermissionError): raise HTTPForbidden(e) if isinstance(e, ResourceNotFound): raise HTTPNotFound(e) if isinstance(e, NotImplementedError): tb = traceback.extract_tb(err[2])[-1] self.log.warning("%s caught from %s:%d in %s: %s", e.__class__.__name__, tb[0], tb[1], tb[2], to_unicode(e) or "(no message)") raise HTTPInternalServerError(TracNotImplementedError(e)) if isinstance(e, TracError): raise HTTPInternalServerError(e) raise err[0], err[1], err[2]
def send_internal_error(env, req, exc_info): if env: env.log.error("[%s] Internal Server Error: %r, referrer %r%s", req.remote_addr, req, req.environ.get('HTTP_REFERER'), exception_to_unicode(exc_info[1], traceback=True)) message = exception_to_unicode(exc_info[1]) traceback = get_last_traceback() frames, plugins, faulty_plugins, interface_custom = [], [], [], [] th = 'http://trac-hacks.org' has_admin = False try: has_admin = 'TRAC_ADMIN' in req.perm except Exception: pass tracker = default_tracker tracker_args = {} if has_admin and not isinstance(exc_info[1], MemoryError): # Collect frame and plugin information frames = get_frame_info(exc_info[2]) if env: plugins = [ p for p in get_plugin_info(env) if any(c['enabled'] for m in p['modules'].itervalues() for c in m['components'].itervalues()) ] match_plugins_to_frames(plugins, frames) # Identify the tracker where the bug should be reported faulty_plugins = [p for p in plugins if 'frame_idx' in p] faulty_plugins.sort(key=lambda p: p['frame_idx']) if faulty_plugins: info = faulty_plugins[0]['info'] home_page = info.get('home_page', '') if 'trac' in info: tracker = info['trac'] elif urlparse(home_page).netloc == urlparse(th).netloc: tracker = th plugin_name = info.get('home_page', '').rstrip('/') \ .split('/')[-1] tracker_args = {'component': plugin_name} interface_custom = Chrome(env).get_interface_customization_files() def get_description(_): if env and has_admin: sys_info = "".join( "|| '''`%s`''' || `%s` ||\n" % (k, (v.replace('\n', '` [[br]] `') if v else _('N/A'))) for k, v in env.system_info) sys_info += "|| '''`jQuery`''' || `#JQUERY#` ||\n" \ "|| '''`jQuery UI`''' || `#JQUERYUI#` ||\n" \ "|| '''`jQuery Timepicker`''' || `#JQUERYTP#` ||\n" enabled_plugins = "".join("|| '''`%s`''' || `%s` ||\n" % (p['name'], p['version'] or _('N/A')) for p in plugins) files = Chrome(env).get_interface_customization_files().items() interface_files = "".join("|| **%s** || %s ||\n" % (k, ", ".join("`%s`" % f for f in v)) for k, v in sorted(files)) else: sys_info = _("''System information not available''\n") enabled_plugins = _("''Plugin information not available''\n") interface_files = _("''Interface customization information not " "available''\n") return _("""\ ==== How to Reproduce ==== While doing a %(method)s operation on `%(path_info)s`, Trac issued an internal error. ''(please provide additional details here)'' Request parameters: {{{ %(req_args)s }}} User agent: `#USER_AGENT#` ==== System Information ==== %(sys_info)s ==== Enabled Plugins ==== %(enabled_plugins)s ==== Interface Customization ==== %(interface_customization)s ==== Python Traceback ==== {{{ %(traceback)s}}}""", method=req.method, path_info=req.path_info, req_args=pformat(req.args), sys_info=sys_info, enabled_plugins=enabled_plugins, interface_customization=interface_files, traceback=to_unicode(traceback)) # Generate the description once in English, once in the current locale description_en = get_description(lambda s, **kw: safefmt(s, kw)) try: description = get_description(_) except Exception: description = description_en data = { 'title': 'Internal Error', 'type': 'internal', 'message': message, 'traceback': traceback, 'frames': frames, 'shorten_line': shorten_line, 'repr': safe_repr, 'plugins': plugins, 'faulty_plugins': faulty_plugins, 'interface': interface_custom, 'tracker': tracker, 'tracker_args': tracker_args, 'description': description, 'description_en': description_en } Chrome(env).add_jquery_ui(req) try: req.send_error(exc_info, status=500, env=env, data=data) except RequestDone: pass
def MockRequest(env, **kwargs): """Request object for testing. Keyword arguments populate an `environ` dictionary and the callbacks. If `authname` is specified in a keyword arguments a `PermissionCache` object is created, otherwise if `authname` is not specified or is `None` a `MockPerm` object is used and the `authname` is set to 'anonymous'. The following keyword arguments are commonly used: :keyword args: dictionary of request arguments :keyword authname: the name of the authenticated user, or 'anonymous' :keyword method: the HTTP request method :keyword path_info: the request path inside the application Additionally `cookie`, `format`, `language`, `lc_time`, `locale`, `remote_addr`, `remote_user`, `script_name`, `server_name`, `server_port` and `tz` can be specified as keyword arguments. :since: 1.0.11 """ authname = kwargs.get('authname') if authname is None: authname = 'anonymous' perm = MockPerm() else: perm = PermissionCache(env, authname) if 'arg_list' in kwargs: arg_list = kwargs['arg_list'] args = arg_list_to_args(arg_list) else: args = _RequestArgs() args.update(kwargs.get('args', {})) arg_list = [(name, value) for name in args for value in args.getlist(name)] environ = { 'trac.base_url': env.abs_href(), 'wsgi.url_scheme': 'http', 'HTTP_ACCEPT_LANGUAGE': kwargs.get('language', ''), 'HTTP_COOKIE': kwargs.get('cookie', ''), 'PATH_INFO': kwargs.get('path_info', '/'), 'REQUEST_METHOD': kwargs.get('method', 'GET'), 'REMOTE_ADDR': kwargs.get('remote_addr', '127.0.0.1'), 'REMOTE_USER': kwargs.get('remote_user', authname), 'SCRIPT_NAME': kwargs.get('script_name', '/trac.cgi'), 'SERVER_NAME': kwargs.get('server_name', 'localhost'), 'SERVER_PORT': kwargs.get('server_port', '80'), } status_sent = [] headers_sent = {} response_sent = io.BytesIO() def start_response(status, headers, exc_info=None): status_sent.append(status) headers_sent.update(dict(headers)) return response_sent.write req = Mock(Request, environ, start_response) req.status_sent = status_sent req.headers_sent = headers_sent req.response_sent = response_sent req.callbacks.update({ 'arg_list': lambda req: arg_list, 'args': lambda req: args, 'authname': lambda req: authname, 'chrome': Chrome(env).prepare_request, 'form_token': lambda req: kwargs.get('form_token', 0), 'languages': Request._parse_languages, 'lc_time': lambda req: kwargs.get('lc_time', locale_en), 'locale': lambda req: kwargs.get('locale'), 'incookie': Request._parse_cookies, 'perm': lambda req: perm, 'session': lambda req: Session(env, req), 'tz': lambda req: kwargs.get('tz', utc), 'use_xsendfile': False, 'xsendfile_header': None, '_inheaders': Request._parse_headers }) return req
def render_ticket_action_control(self, req, ticket, action): self.log.debug('render_ticket_action_control: action "%s"', action) this_action = self.actions[action] label = this_action['label'] operations = this_action['operations'] ticket_owner = ticket._old.get('owner', ticket['owner']) ticket_status = ticket._old.get('status', ticket['status']) next_status = this_action['newstate'] author = get_reporter_id(req, 'author') author_info = partial(Chrome(self.env).authorinfo, req, resource=ticket.resource) format_author = partial(Chrome(self.env).format_author, req, resource=ticket.resource) formatted_current_owner = author_info(ticket_owner) exists = ticket_status is not None ticket_system = TicketSystem(self.env) control = [] # default to nothing hints = [] if 'reset_workflow' in operations: control.append(_("from invalid state")) hints.append(_("Current state no longer exists")) if 'del_owner' in operations: hints.append(_("The ticket will be disowned")) if 'set_owner' in operations or 'may_set_owner' in operations: owners = self.get_allowed_owners(req, ticket, this_action) if 'set_owner' in operations: default_owner = author elif 'may_set_owner' in operations: if not exists: default_owner = ticket_system.default_owner else: default_owner = ticket_owner or None if owners is not None and default_owner not in owners: owners.insert(0, default_owner) else: # Protect against future modification for case that another # operation is added to the outer conditional raise AssertionError(operations) id = 'action_%s_reassign_owner' % action if not owners: owner = req.args.get(id, default_owner) control.append( tag_("to %(owner)s", owner=tag.input(type='text', id=id, name=id, value=owner))) if not exists or ticket_owner is None: hints.append(_("The owner will be the specified user")) else: hints.append(tag_("The owner will be changed from " "%(current_owner)s to the specified " "user", current_owner=formatted_current_owner)) elif len(owners) == 1: owner = tag.input(type='hidden', id=id, name=id, value=owners[0]) formatted_new_owner = author_info(owners[0]) control.append(tag_("to %(owner)s", owner=tag(formatted_new_owner, owner))) if not exists or ticket_owner is None: hints.append(tag_("The owner will be %(new_owner)s", new_owner=formatted_new_owner)) elif ticket['owner'] != owners[0]: hints.append(tag_("The owner will be changed from " "%(current_owner)s to %(new_owner)s", current_owner=formatted_current_owner, new_owner=formatted_new_owner)) else: selected_owner = req.args.get(id, default_owner) control.append(tag_("to %(owner)s", owner=tag.select( [tag.option(text, value=value if value is not None else '', selected=(value == selected_owner or None)) for text, value in sorted((format_author(owner), owner) for owner in owners)], id=id, name=id))) if not exists or ticket_owner is None: hints.append(_("The owner will be the selected user")) else: hints.append(tag_("The owner will be changed from " "%(current_owner)s to the selected user", current_owner=formatted_current_owner)) elif 'set_owner_to_self' in operations: formatted_author = author_info(author) if not exists or ticket_owner is None: hints.append(tag_("The owner will be %(new_owner)s", new_owner=formatted_author)) elif ticket_owner != author: hints.append(tag_("The owner will be changed from " "%(current_owner)s to %(new_owner)s", current_owner=formatted_current_owner, new_owner=formatted_author)) elif ticket_status != next_status: hints.append(tag_("The owner will remain %(current_owner)s", current_owner=formatted_current_owner)) if 'set_resolution' in operations: resolutions = [r.name for r in Resolution.select(self.env)] if 'set_resolution' in this_action: valid_resolutions = set(resolutions) resolutions = this_action['set_resolution'] if any(x not in valid_resolutions for x in resolutions): raise ConfigurationError(_( "Your workflow attempts to set a resolution but uses " "undefined resolutions (configuration issue, please " "contact your Trac admin).")) if not resolutions: raise ConfigurationError(_( "Your workflow attempts to set a resolution but none is " "defined (configuration issue, please contact your Trac " "admin).")) id = 'action_%s_resolve_resolution' % action if len(resolutions) == 1: resolution = tag.input(type='hidden', id=id, name=id, value=resolutions[0]) control.append(tag_("as %(resolution)s", resolution=tag(resolutions[0], resolution))) hints.append(tag_("The resolution will be set to %(name)s", name=resolutions[0])) else: selected_option = req.args.get(id, ticket_system.default_resolution) control.append(tag_("as %(resolution)s", resolution=tag.select( [tag.option(x, value=x, selected=(x == selected_option or None)) for x in resolutions], id=id, name=id))) hints.append(_("The resolution will be set")) if 'del_resolution' in operations: hints.append(_("The resolution will be deleted")) if 'leave_status' in operations: if len(operations) == 1: control.append(tag_("as %(status)s", status=ticket_status)) hints.append(tag_("The owner will remain %(current_owner)s", current_owner=formatted_current_owner) if ticket_owner else _("The ticket will remain with no owner")) elif next_status == '*': label = None # Control won't be created elif ticket['status'] is None: # New ticket hints.append(tag_("The status will be '%(name)s'", name=next_status)) elif next_status != ticket_status: hints.append(tag_("Next status will be '%(name)s'", name=next_status)) return (label, tag(separated(control, ' ')), tag(separated(hints, '. ', '.') if hints else ''))
def _render_view(self, req, id): """Retrieve the report results and pre-process them for rendering.""" r = Report(self.env, id) title, description, sql = r.title, r.description, r.query # If this is a saved custom query, redirect to the query module # # A saved query is either an URL query (?... or query:?...), # or a query language expression (query:...). # # It may eventually contain newlines, for increased clarity. # query = ''.join(line.strip() for line in sql.splitlines()) if query and (query[0] == '?' or query.startswith('query:?')): query = query if query[0] == '?' else query[6:] report_id = 'report=%s' % id if 'report=' in query: if report_id not in query: err = _( 'When specified, the report number should be ' '"%(num)s".', num=id) req.redirect(req.href.report(id, action='edit', error=err)) else: if query[-1] != '?': query += '&' query += report_id req.redirect(req.href.query() + quote_query_string(query)) elif query.startswith('query:'): from trac.ticket.query import Query, QuerySyntaxError try: query = Query.from_string(self.env, query[6:], report=id) except QuerySyntaxError as e: req.redirect( req.href.report(id, action='edit', error=to_unicode(e))) else: req.redirect(query.get_href(req.href)) format = req.args.get('format') if format == 'sql': self._send_sql(req, id, title, description, sql) title = '{%i} %s' % (id, title) report_resource = Resource(self.realm, id) req.perm(report_resource).require('REPORT_VIEW') context = web_context(req, report_resource) page = req.args.getint('page', 1) default_max = { 'rss': self.items_per_page_rss, 'csv': 0, 'tab': 0 }.get(format, self.items_per_page) max = req.args.getint('max') limit = as_int(max, default_max, min=0) # explict max takes precedence offset = (page - 1) * limit sort_col = req.args.get('sort', '') asc = req.args.getint('asc', 0, min=0, max=1) args = {} def report_href(**kwargs): """Generate links to this report preserving user variables, and sorting and paging variables. """ params = args.copy() if sort_col: params['sort'] = sort_col if page != 1: params['page'] = page if max != default_max: params['max'] = max params.update(kwargs) params['asc'] = 1 if params.get('asc', asc) else None return req.href.report(id, params) data = { 'action': 'view', 'report': { 'id': id, 'resource': report_resource }, 'context': context, 'title': title, 'description': description, 'max': limit, 'args': args, 'show_args_form': False, 'message': None, 'paginator': None, 'report_href': report_href } try: args = self.get_var_args(req) sql = self.get_default_var_args(args, sql) except ValueError as e: data['message'] = _("Report failed: %(error)s", error=e) return 'report_view.html', data, None data.update({ 'args': args, 'title': sub_vars(title, args), 'description': sub_vars(description or '', args) }) try: res = self.execute_paginated_report(req, id, sql, args, limit, offset) except TracError as e: data['message'] = _("Report failed: %(error)s", error=e) else: if len(res) == 2: e, sql = res data['message'] = \ tag_("Report execution failed: %(error)s %(sql)s", error=tag.pre(exception_to_unicode(e)), sql=tag(tag.hr(), tag.pre(sql, style="white-space: pre"))) if data['message']: return 'report_view.html', data, None cols, results, num_items, missing_args, limit_offset = res need_paginator = limit > 0 and limit_offset need_reorder = limit_offset is None results = [list(row) for row in results] numrows = len(results) paginator = None if need_paginator: paginator = Paginator(results, page - 1, limit, num_items) data['paginator'] = paginator if paginator.has_next_page: add_link(req, 'next', report_href(page=page + 1), _('Next Page')) if paginator.has_previous_page: add_link(req, 'prev', report_href(page=page - 1), _('Previous Page')) pagedata = [] shown_pages = paginator.get_shown_pages(21) for p in shown_pages: pagedata.append([ report_href(page=p), None, str(p), _('Page %(num)d', num=p) ]) fields = ['href', 'class', 'string', 'title'] paginator.shown_pages = [dict(zip(fields, p)) for p in pagedata] paginator.current_page = { 'href': None, 'class': 'current', 'string': str(paginator.page + 1), 'title': None } numrows = paginator.num_items # Place retrieved columns in groups, according to naming conventions # * _col_ means fullrow, i.e. a group with one header # * col_ means finish the current group and start a new one field_labels = TicketSystem(self.env).get_ticket_field_labels() header_groups = [[]] for idx, col in enumerate(cols): if col in field_labels: title = field_labels[col] else: title = col.strip('_').capitalize() header = { 'col': col, 'title': title, 'hidden': False, 'asc': None, } if col == sort_col: if asc: data['asc'] = asc data['sort'] = sort_col header['asc'] = bool(asc) if not paginator and need_reorder: # this dict will have enum values for sorting # and will be used in sortkey(), if non-empty: sort_values = {} if sort_col in ('status', 'resolution', 'priority', 'severity'): # must fetch sort values for that columns # instead of comparing them as strings with self.env.db_query as db: for name, value in db( "SELECT name, %s FROM enum WHERE type=%%s" % db.cast('value', 'int'), (sort_col, )): sort_values[name] = value def sortkey(row): val = row[idx] # check if we have sort_values, then use them as keys. if sort_values: return sort_values.get(val) # otherwise, continue with string comparison: if isinstance(val, basestring): val = val.lower() return val results = sorted(results, key=sortkey, reverse=not asc) header_group = header_groups[-1] if col.startswith('__') and col.endswith('__'): # __col__ header['hidden'] = True elif col[0] == '_' and col[-1] == '_': # _col_ header_group = [] header_groups.append(header_group) header_groups.append([]) elif col[0] == '_': # _col header['hidden'] = True elif col[-1] == '_': # col_ header_groups.append([]) header_group.append(header) # Structure the rows and cells: # - group rows according to __group__ value, if defined # - group cells the same way headers are grouped chrome = Chrome(self.env) row_groups = [] authorized_results = [] prev_group_value = None for row_idx, result in enumerate(results): col_idx = 0 cell_groups = [] row = {'cell_groups': cell_groups} realm = TicketSystem.realm parent_realm = '' parent_id = '' email_cells = [] for header_group in header_groups: cell_group = [] for header in header_group: value = cell_value(result[col_idx]) cell = {'value': value, 'header': header, 'index': col_idx} col = header['col'] col_idx += 1 # Detect and create new group if col == '__group__' and value != prev_group_value: prev_group_value = value # Brute force handling of email in group by header row_groups.append( (value and chrome.format_author(req, value), [])) # Other row properties row['__idx__'] = row_idx if col in self._html_cols: row[col] = value if col in ('report', 'ticket', 'id', '_id'): row['id'] = value # Special casing based on column name col = col.strip('_') if col in ('reporter', 'cc', 'owner'): email_cells.append(cell) elif col == 'realm': realm = value elif col == 'parent_realm': parent_realm = value elif col == 'parent_id': parent_id = value cell_group.append(cell) cell_groups.append(cell_group) if parent_realm: resource = Resource(realm, row.get('id'), parent=Resource(parent_realm, parent_id)) else: resource = Resource(realm, row.get('id')) # FIXME: for now, we still need to hardcode the realm in the action if resource.realm.upper() + '_VIEW' not in req.perm(resource): continue authorized_results.append(result) if email_cells: for cell in email_cells: emails = chrome.format_emails(context.child(resource), cell['value']) result[cell['index']] = cell['value'] = emails row['resource'] = resource if row_groups: row_group = row_groups[-1][1] else: row_group = [] row_groups = [(None, row_group)] row_group.append(row) data.update({ 'header_groups': header_groups, 'row_groups': row_groups, 'numrows': numrows }) if format == 'rss': data['context'] = web_context(req, report_resource, absurls=True) return 'report.rss', data, 'application/rss+xml' elif format == 'csv': filename = 'report_%s.csv' % id if id else 'report.csv' self._send_csv(req, cols, authorized_results, mimetype='text/csv', filename=filename) elif format == 'tab': filename = 'report_%s.tsv' % id if id else 'report.tsv' self._send_csv(req, cols, authorized_results, '\t', mimetype='text/tab-separated-values', filename=filename) else: p = page if max is not None else None add_link(req, 'alternate', auth_link(req, report_href(format='rss', page=None)), _('RSS Feed'), 'application/rss+xml', 'rss') add_link(req, 'alternate', report_href(format='csv', page=p), _('Comma-delimited Text'), 'text/plain') add_link(req, 'alternate', report_href(format='tab', page=p), _('Tab-delimited Text'), 'text/plain') if 'REPORT_SQL_VIEW' in req.perm(self.realm, id): add_link(req, 'alternate', req.href.report(id=id, format='sql'), _('SQL Query'), 'text/plain') # reuse the session vars of the query module so that # the query navigation links on the ticket can be used to # navigate report results as well try: req.session['query_tickets'] = \ ' '.join(str(int(row['id'])) for rg in row_groups for row in rg[1]) req.session['query_href'] = \ req.session['query_href'] = report_href() # Kludge: we have to clear the other query session # variables, but only if the above succeeded for var in ('query_constraints', 'query_time'): if var in req.session: del req.session[var] except (ValueError, KeyError): pass if set(data['args']) - {'USER'}: data['show_args_form'] = True # Add values of all select-type ticket fields for autocomplete. fields = TicketSystem(self.env).get_ticket_fields() arg_values = {} for arg in set(data['args']) - {'USER'}: attrs = fields.by_name(arg.lower()) if attrs and 'options' in attrs: arg_values[attrs['name']] = attrs['options'] if arg_values: add_script_data(req, arg_values=arg_values) Chrome(self.env).add_jquery_ui(req) if missing_args: add_warning( req, _('The following arguments are missing: %(args)s', args=", ".join(missing_args))) return 'report_view.html', data, None
def render(): d = {'alist': attachments.copy()} d['compact'] = True d['foldable'] = True return Chrome(self.env).render_template( req, 'list_of_attachments.html', d, fragment=True)
def _format_author(self, author): return Chrome(self.env).format_author(None, author)
def _format_body(self, data, template_name): chrome = Chrome(self.env) template = chrome.load_template(template_name, text=True) with translation_deactivated(): # don't translate the e-mail stream body = chrome.render_template_string(template, data, text=True) return body.encode('utf-8')
def render_ticket_action_control(self, req, ticket, action): self.log.debug('render_ticket_action_control: action "%s"', action) this_action = self.actions[action] status = this_action['newstate'] operations = this_action['operations'] current_owner = ticket._old.get('owner', ticket['owner']) author = get_reporter_id(req, 'author') author_info = partial(Chrome(self.env).authorinfo, req, resource=ticket.resource) formatted_current_owner = author_info(current_owner) exists = ticket._old.get('status', ticket['status']) is not None control = [] # default to nothing hints = [] if 'reset_workflow' in operations: control.append(_("from invalid state")) hints.append(_("Current state no longer exists")) if 'del_owner' in operations: hints.append(_("The ticket will be disowned")) if 'set_owner' in operations or 'may_set_owner' in operations: if 'set_owner' in this_action: owners = self._to_users(this_action['set_owner'], ticket) elif self.config.getbool('ticket', 'restrict_owner'): perm = PermissionSystem(self.env) owners = perm.get_users_with_permission('TICKET_MODIFY') owners = [ user for user in owners if 'TICKET_MODIFY' in PermissionCache( self.env, user, ticket.resource) ] owners = sorted(owners) else: owners = None if 'set_owner' in operations: default_owner = author elif 'may_set_owner' in operations: if not exists: default_owner = TicketSystem(self.env).default_owner else: default_owner = ticket._old.get('owner', ticket['owner'] or None) if owners is not None and default_owner not in owners: owners.insert(0, default_owner) else: # Protect against future modification for case that another # operation is added to the outer conditional raise AssertionError(operations) id = 'action_%s_reassign_owner' % action if not owners: owner = req.args.get(id, default_owner) control.append( tag_("to %(owner)s", owner=tag.input(type='text', id=id, name=id, value=owner))) if not exists or current_owner is None: hints.append(_("The owner will be the specified user")) else: hints.append( tag_( "The owner will be changed from " "%(current_owner)s to the specified " "user", current_owner=formatted_current_owner)) elif len(owners) == 1: owner = tag.input(type='hidden', id=id, name=id, value=owners[0]) formatted_new_owner = author_info(owners[0]) control.append( tag_("to %(owner)s", owner=tag(formatted_new_owner, owner))) if not exists or current_owner is None: hints.append( tag_("The owner will be %(new_owner)s", new_owner=formatted_new_owner)) elif ticket['owner'] != owners[0]: hints.append( tag_( "The owner will be changed from " "%(current_owner)s to %(new_owner)s", current_owner=formatted_current_owner, new_owner=formatted_new_owner)) else: selected_owner = req.args.get(id, default_owner) control.append( tag_("to %(owner)s", owner=tag.select([ tag.option(x if x is not None else _("(none)"), value=x if x is not None else '', selected=(x == selected_owner or None)) for x in owners ], id=id, name=id))) if not exists or current_owner is None: hints.append(_("The owner will be the selected user")) else: hints.append( tag_( "The owner will be changed from " "%(current_owner)s to the selected user", current_owner=formatted_current_owner)) elif 'set_owner_to_self' in operations and \ ticket._old.get('owner', ticket['owner']) != author: formatted_author = author_info(author) if not exists or current_owner is None: hints.append( tag_("The owner will be %(new_owner)s", new_owner=formatted_author)) else: hints.append( tag_( "The owner will be changed from " "%(current_owner)s to %(new_owner)s", current_owner=formatted_current_owner, new_owner=formatted_author)) if 'set_resolution' in operations: if 'set_resolution' in this_action: resolutions = this_action['set_resolution'] else: resolutions = [r.name for r in Resolution.select(self.env)] if not resolutions: raise TracError( _("Your workflow attempts to set a resolution " "but none is defined (configuration issue, " "please contact your Trac admin).")) id = 'action_%s_resolve_resolution' % action if len(resolutions) == 1: resolution = tag.input(type='hidden', id=id, name=id, value=resolutions[0]) control.append( tag_("as %(resolution)s", resolution=tag(resolutions[0], resolution))) hints.append( tag_("The resolution will be set to %(name)s", name=resolutions[0])) else: selected_option = req.args.get( id, TicketSystem(self.env).default_resolution) control.append( tag_( "as %(resolution)s", resolution=tag.select([ tag.option(x, value=x, selected=(x == selected_option or None)) for x in resolutions ], id=id, name=id))) hints.append(_("The resolution will be set")) if 'del_resolution' in operations: hints.append(_("The resolution will be deleted")) if 'leave_status' in operations: control.append( tag_("as %(status)s", status=ticket._old.get('status', ticket['status']))) if len(operations) == 1: hints.append( tag_("The owner will remain %(current_owner)s", current_owner=formatted_current_owner) if current_owner else _("The ticket will remain with no owner" )) else: if ticket['status'] is None: hints.append(tag_("The status will be '%(name)s'", name=status)) elif status != '*': hints.append( tag_("Next status will be '%(name)s'", name=status)) return (this_action['label'], tag(separated(control, ' ')), tag(separated(hints, '. ', '.') if hints else ''))
def _render_admin_panel(self, req, cat, page, component): # Detail view? if component: comp = model.Component(self.env, component) if req.method == 'POST': if req.args.get('save'): comp.name = name = req.args.get('name') comp.owner = req.args.get('owner') comp.description = req.args.get('description') try: comp.update() except self.env.db_exc.IntegrityError: raise TracError( _('The component "%(name)s" already ' 'exists.', name=name)) add_notice(req, _("Your changes have been saved.")) req.redirect(req.href.admin(cat, page)) elif req.args.get('cancel'): req.redirect(req.href.admin(cat, page)) Chrome(self.env).add_wiki_toolbars(req) data = {'view': 'detail', 'component': comp} else: default = self.config.get('ticket', 'default_component') if req.method == 'POST': # Add Component if req.args.get('add') and req.args.get('name'): name = req.args.get('name') try: comp = model.Component(self.env, name=name) except ResourceNotFound: comp = model.Component(self.env) comp.name = name if req.args.get('owner'): comp.owner = req.args.get('owner') comp.insert() add_notice( req, _('The component "%(name)s" has been ' 'added.', name=name)) req.redirect(req.href.admin(cat, page)) else: if comp.name is None: raise TracError(_("Invalid component name.")) raise TracError( _("Component %(name)s already exists.", name=name)) # Remove components elif req.args.get('remove'): sel = req.args.get('sel') if not sel: raise TracError(_("No component selected")) if not isinstance(sel, list): sel = [sel] with self.env.db_transaction: for name in sel: model.Component(self.env, name).delete() add_notice( req, _("The selected components have been " "removed.")) req.redirect(req.href.admin(cat, page)) # Set default component elif req.args.get('apply'): name = req.args.get('default') if name and name != default: self.log.info("Setting default component to %s", name) self.config.set('ticket', 'default_component', name) _save_config(self.config, req, self.log) req.redirect(req.href.admin(cat, page)) # Clear default component elif req.args.get('clear'): self.log.info("Clearing default component") self.config.set('ticket', 'default_component', '') _save_config(self.config, req, self.log) req.redirect(req.href.admin(cat, page)) data = { 'view': 'list', 'components': list(model.Component.select(self.env)), 'default': default } if self.config.getbool('ticket', 'restrict_owner'): perm = PermissionSystem(self.env) def valid_owner(username): return perm.get_user_permissions(username).get('TICKET_MODIFY') data['owners'] = [ username for username, name, email in self.env.get_known_users() if valid_owner(username) ] data['owners'].insert(0, '') data['owners'].sort() else: data['owners'] = None return 'admin_components.html', data
def render_admin_panel(self, req, category, page, path_info): req.perm.require('VERSIONCONTROL_ADMIN') # Retrieve info for all repositories rm = RepositoryManager(self.env) all_repos = rm.get_all_repositories() db_provider = self.env[DbRepositoryProvider] if path_info: # Detail view reponame = path_info if not is_default(path_info) else '' info = all_repos.get(reponame) if info is None: raise TracError( _("Repository '%(repo)s' not found", repo=path_info)) if req.method == 'POST': if req.args.get('cancel'): req.redirect(req.href.admin(category, page)) elif db_provider and req.args.get('save'): # Modify repository changes = {} for field in db_provider.repository_attrs: value = normalize_whitespace(req.args.get(field)) if (value is not None or field == 'hidden') \ and value != info.get(field): changes[field] = value if 'dir' in changes \ and not self._check_dir(req, changes['dir']): changes = {} if changes: db_provider.modify_repository(reponame, changes) add_notice(req, _('Your changes have been saved.')) name = req.args.get('name') resync = tag.tt('trac-admin $ENV repository resync "%s"' % (name or '(default)')) if 'dir' in changes: msg = tag_( 'You should now run %(resync)s to ' 'synchronize Trac with the repository.', resync=resync) add_notice(req, msg) elif 'type' in changes: msg = tag_( 'You may have to run %(resync)s to ' 'synchronize Trac with the repository.', resync=resync) add_notice(req, msg) if name and name != path_info and not 'alias' in info: cset_added = tag.tt('trac-admin $ENV changeset ' 'added "%s" $REV' % (name or '(default)')) msg = tag_( 'You will need to update your post-commit ' 'hook to call %(cset_added)s with the new ' 'repository name.', cset_added=cset_added) add_notice(req, msg) if changes: req.redirect(req.href.admin(category, page)) Chrome(self.env).add_wiki_toolbars(req) data = {'view': 'detail', 'reponame': reponame} else: # List view if req.method == 'POST': # Add a repository if db_provider and req.args.get('add_repos'): name = req.args.get('name') type_ = req.args.get('type') # Avoid errors when copy/pasting paths dir = normalize_whitespace(req.args.get('dir', '')) if name is None or type_ is None or not dir: add_warning( req, _('Missing arguments to add a ' 'repository.')) elif self._check_dir(req, dir): db_provider.add_repository(name, dir, type_) name = name or '(default)' add_notice( req, _('The repository "%(name)s" has been ' 'added.', name=name)) resync = tag.tt('trac-admin $ENV repository resync ' '"%s"' % name) msg = tag_( 'You should now run %(resync)s to ' 'synchronize Trac with the repository.', resync=resync) add_notice(req, msg) cset_added = tag.tt('trac-admin $ENV changeset ' 'added "%s" $REV' % name) msg = tag_( 'You should also set up a post-commit hook ' 'on the repository to call %(cset_added)s ' 'for each committed changeset.', cset_added=cset_added) add_notice(req, msg) req.redirect(req.href.admin(category, page)) # Add a repository alias elif db_provider and req.args.get('add_alias'): name = req.args.get('name') alias = req.args.get('alias') if name is not None and alias is not None: db_provider.add_alias(name, alias) add_notice( req, _('The alias "%(name)s" has been ' 'added.', name=name or '(default)')) req.redirect(req.href.admin(category, page)) add_warning(req, _('Missing arguments to add an ' 'alias.')) # Refresh the list of repositories elif req.args.get('refresh'): req.redirect(req.href.admin(category, page)) # Remove repositories elif db_provider and req.args.get('remove'): sel = req.args.getlist('sel') if sel: for name in sel: db_provider.remove_repository(name) add_notice( req, _('The selected repositories have ' 'been removed.')) req.redirect(req.href.admin(category, page)) add_warning(req, _('No repositories were selected.')) data = {'view': 'list'} # Find repositories that are editable db_repos = {} if db_provider is not None: db_repos = dict(db_provider.get_repositories()) # Prepare common rendering data repositories = dict( (reponame, self._extend_info(reponame, info.copy(), reponame in db_repos)) for (reponame, info) in all_repos.iteritems()) types = sorted([''] + rm.get_supported_types()) data.update({ 'types': types, 'default_type': rm.repository_type, 'repositories': repositories }) return 'admin_repositories.html', data
def test_template_dirs_added(self): from trac.web.chrome import Chrome self.assertTrue(self.tag_wm in Chrome(self.env).template_providers)
def _render_admin_panel(self, req, cat, page, milestone): req.perm.require('MILESTONE_VIEW') # Detail view? if milestone: mil = model.Milestone(self.env, milestone) if req.method == 'POST': if req.args.get('save'): req.perm.require('MILESTONE_MODIFY') mil.name = name = req.args.get('name') mil.due = mil.completed = None due = req.args.get('duedate', '') if due: mil.due = user_time(req, parse_date, due, hint='datetime') if req.args.get('completed', False): completed = req.args.get('completeddate', '') mil.completed = user_time(req, parse_date, completed, hint='datetime') if mil.completed > datetime.now(utc): raise TracError( _('Completion date may not be in ' 'the future'), _('Invalid Completion Date')) mil.description = req.args.get('description', '') try: mil.update() except self.env.db_exc.IntegrityError: raise TracError( _('The milestone "%(name)s" already ' 'exists.', name=name)) add_notice(req, _('Your changes have been saved.')) req.redirect(req.href.admin(cat, page)) elif req.args.get('cancel'): req.redirect(req.href.admin(cat, page)) Chrome(self.env).add_wiki_toolbars(req) data = {'view': 'detail', 'milestone': mil} else: default = self.config.get('ticket', 'default_milestone') if req.method == 'POST': # Add Milestone if req.args.get('add') and req.args.get('name'): req.perm.require('MILESTONE_CREATE') name = req.args.get('name') try: mil = model.Milestone(self.env, name=name) except ResourceNotFound: mil = model.Milestone(self.env) mil.name = name if req.args.get('duedate'): mil.due = user_time(req, parse_date, req.args.get('duedate'), hint='datetime') mil.insert() add_notice( req, _('The milestone "%(name)s" has been ' 'added.', name=name)) req.redirect(req.href.admin(cat, page)) else: if mil.name is None: raise TracError(_('Invalid milestone name.')) raise TracError( _("Milestone %(name)s already exists.", name=name)) # Remove milestone elif req.args.get('remove'): req.perm.require('MILESTONE_DELETE') sel = req.args.get('sel') if not sel: raise TracError(_('No milestone selected')) if not isinstance(sel, list): sel = [sel] with self.env.db_transaction: for name in sel: mil = model.Milestone(self.env, name) mil.delete(author=req.authname) add_notice( req, _("The selected milestones have been " "removed.")) req.redirect(req.href.admin(cat, page)) # Set default milestone elif req.args.get('apply'): name = req.args.get('default') if name and name != default: self.log.info("Setting default milestone to %s", name) self.config.set('ticket', 'default_milestone', name) _save_config(self.config, req, self.log) req.redirect(req.href.admin(cat, page)) # Get ticket count milestones = [(milestone, self.env.db_query( """ SELECT COUNT(*) FROM ticket WHERE milestone=%s """, (milestone.name, ))[0][0]) for milestone in model.Milestone.select(self.env)] data = { 'view': 'list', 'milestones': milestones, 'default': default } data.update({ 'datetime_hint': get_datetime_format_hint(req.lc_time), }) return 'admin_milestones.html', data
def _format_body(self, data, template_name): template = Chrome(self.env).load_template(template_name, method='text') stream = template.generate(**data) # don't translate the e-mail stream with translation_deactivated(): return stream.render('text', encoding='utf-8')
def _render_admin_panel(self, req, cat, page, version): # Detail view? if version: ver = model.Version(self.env, version) if req.method == 'POST': if req.args.get('save'): ver.name = name = req.args.get('name') if req.args.get('time'): ver.time = user_time(req, parse_date, req.args.get('time'), hint='datetime') else: ver.time = None # unset ver.description = req.args.get('description') try: ver.update() except self.env.db_exc.IntegrityError: raise TracError( _('The version "%(name)s" already ' 'exists.', name=name)) add_notice(req, _('Your changes have been saved.')) req.redirect(req.href.admin(cat, page)) elif req.args.get('cancel'): req.redirect(req.href.admin(cat, page)) Chrome(self.env).add_wiki_toolbars(req) data = {'view': 'detail', 'version': ver} else: default = self.config.get('ticket', 'default_version') if req.method == 'POST': # Add Version if req.args.get('add') and req.args.get('name'): name = req.args.get('name') try: ver = model.Version(self.env, name=name) except ResourceNotFound: ver = model.Version(self.env) ver.name = name if req.args.get('time'): ver.time = user_time(req, parse_date, req.args.get('time'), hint='datetime') ver.insert() add_notice( req, _('The version "%(name)s" has been ' 'added.', name=name)) req.redirect(req.href.admin(cat, page)) else: if ver.name is None: raise TracError(_("Invalid version name.")) raise TracError( _("Version %(name)s already exists.", name=name)) # Remove versions elif req.args.get('remove'): sel = req.args.get('sel') if not sel: raise TracError(_("No version selected")) if not isinstance(sel, list): sel = [sel] with self.env.db_transaction: for name in sel: ver = model.Version(self.env, name) ver.delete() add_notice( req, _("The selected versions have been " "removed.")) req.redirect(req.href.admin(cat, page)) # Set default version elif req.args.get('apply'): name = req.args.get('default') if name and name != default: self.log.info("Setting default version to %s", name) self.config.set('ticket', 'default_version', name) _save_config(self.config, req, self.log) req.redirect(req.href.admin(cat, page)) data = { 'view': 'list', 'versions': model.Version.select(self.env), 'default': default } data.update({ 'datetime_hint': get_datetime_format_hint(req.lc_time), }) return 'admin_versions.html', data
def _render_admin_panel(self, req, cat, page, milestone): perm = req.perm('admin', 'ticket/' + self._type) # Detail view? if milestone: mil = model.Milestone(self.env, milestone) if req.method == 'POST': if req.args.get('save'): perm.require('MILESTONE_MODIFY') mil.name = name = req.args.get('name') mil.due = mil.completed = None if 'due' in req.args: duedate = req.args.get('duedate') mil.due = user_time(req, parse_date, duedate, hint='datetime') \ if duedate else None if req.args.get('completed', False): completed = req.args.get('completeddate', '') mil.completed = user_time(req, parse_date, completed, hint='datetime') if mil.completed > datetime.now(utc): raise TracError( _("Completion date may not be in " "the future"), _("Invalid Completion Date")) mil.description = req.args.get('description', '') try: mil.update(author=req.authname) except self.env.db_exc.IntegrityError: raise TracError( _('The milestone "%(name)s" already ' 'exists.', name=name)) add_notice(req, _('Your changes have been saved.')) req.redirect(req.href.admin(cat, page)) elif req.args.get('cancel'): req.redirect(req.href.admin(cat, page)) now = datetime.now(req.tz) default_due = datetime(now.year, now.month, now.day, 18) if now.hour > 18: default_due += timedelta(days=1) default_due = to_datetime(default_due, req.tz) Chrome(self.env).add_wiki_toolbars(req) data = { 'view': 'detail', 'milestone': mil, 'default_due': default_due } else: ticket_default = self.config.get('ticket', 'default_milestone') retarget_default = self.config.get('milestone', 'default_retarget_to') if req.method == 'POST': # Add Milestone if req.args.get('add') and req.args.get('name'): perm.require('MILESTONE_CREATE') name = req.args.get('name') try: mil = model.Milestone(self.env, name=name) except ResourceNotFound: mil = model.Milestone(self.env) mil.name = name if req.args.get('duedate'): mil.due = user_time(req, parse_date, req.args.get('duedate'), hint='datetime') mil.insert() add_notice( req, _('The milestone "%(name)s" has been ' 'added.', name=name)) req.redirect(req.href.admin(cat, page)) else: if mil.name is None: raise TracError(_('Invalid milestone name.')) raise TracError( _("Milestone %(name)s already exists.", name=name)) # Remove milestone elif req.args.get('remove'): perm.require('MILESTONE_DELETE') sel = req.args.get('sel') if not sel: raise TracError(_('No milestone selected')) if not isinstance(sel, list): sel = [sel] with self.env.db_transaction: for name in sel: mil = model.Milestone(self.env, name) mil.delete(author=req.authname) add_notice( req, _("The selected milestones have been " "removed.")) req.redirect(req.href.admin(cat, page)) # Set default milestone elif req.args.get('apply'): save = False name = req.args.get('ticket_default') if name and name != ticket_default: self.log.info( "Setting default ticket " "milestone to %s", name) self.config.set('ticket', 'default_milestone', name) save = True retarget = req.args.get('retarget_default') if retarget and retarget != retarget_default: self.log.info( "Setting default retargeting " "milestone to %s", retarget) self.config.set('milestone', 'default_retarget_to', retarget) save = True if save: _save_config(self.config, req, self.log) req.redirect(req.href.admin(cat, page)) # Clear defaults elif req.args.get('clear'): self.log.info("Clearing default ticket milestone " "and default retarget milestone") self.config.set('ticket', 'default_milestone', '') self.config.set('milestone', 'default_retarget_to', '') _save_config(self.config, req, self.log) req.redirect(req.href.admin(cat, page)) # Get ticket count milestones = [(milestone, self.env.db_query( """ SELECT COUNT(*) FROM ticket WHERE milestone=%s """, (milestone.name, ))[0][0]) for milestone in model.Milestone.select(self.env)] query_href = lambda name: req.href.query({ 'groupby': 'status', 'milestone': name }) data = { 'view': 'list', 'milestones': milestones, 'query_href': query_href, 'ticket_default': ticket_default, 'retarget_default': retarget_default } Chrome(self.env).add_jquery_ui(req) data.update({ 'datetime_hint': get_datetime_format_hint(req.lc_time), }) return 'admin_milestones.html', data
def _render_editor(self, req, page, action='edit', has_collision=False): if has_collision: if action == 'merge': page = WikiPage(self.env, page.name, version=None) req.perm(page.resource).require('WIKI_VIEW') else: action = 'collision' if page.readonly: req.perm(page.resource).require('WIKI_ADMIN') 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, version=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': 20} 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({ '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), }) 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_property(self, name, mode, context, props): def sha_link(sha, label=None): # sha is assumed to be a non-abbreviated 40-chars sha id try: reponame = context.resource.parent.id repos = self.env.get_repository(reponame) cset = repos.get_changeset(sha) if label is None: label = repos.display_rev(sha) return tag.a(label, class_='changeset', title=shorten_line(cset.message), href=context.href.changeset(sha, repos.reponame)) except Exception as e: return tag.a(sha, class_='missing changeset', title=to_unicode(e), rel='nofollow') if name == 'Branches': branches = props[name] # simple non-merge commit return tag(*intersperse(', ', (sha_link(rev, label) for label, rev in branches))) elif name in ('Parents', 'Children'): revs = props[name] # list of commit ids if name == 'Parents' and len(revs) > 1: # we got a merge... current_sha = context.resource.id reponame = context.resource.parent.id parent_links = intersperse(', ', \ ((sha_link(rev), ' (', tag.a('diff', title="Diff against this parent (show the " \ "changes merged from the other parents)", href=context.href.changeset(current_sha, reponame, old=rev)), ')') for rev in revs)) return tag( list(parent_links), tag.br(), tag.span(tag( "Note: this is a ", tag.strong("merge"), " changeset, " "the changes displayed below " "correspond to the merge itself."), class_='hint'), tag.br(), tag.span(tag( "Use the ", tag.code("(diff)"), " links above to see all the changes " "relative to each parent."), class_='hint')) # simple non-merge commit return tag(*intersperse(', ', map(sha_link, revs))) elif name in ('git-committer', 'git-author'): user_, time_ = props[name] _str = "%s (%s)" % (Chrome(self.env).format_author( context.req, user_), format_datetime(time_, tzinfo=context.req.tz)) return unicode(_str) raise TracError("Internal error")
def dispatch(self, req): """Find a registered handler that matches the request and let it process it. In addition, this method initializes the data dictionary passed to the the template and adds the web site chrome. """ self.log.debug('Dispatching %r', req) chrome = Chrome(self.env) # Setup request callbacks for lazily-evaluated properties req.callbacks.update({ 'authname': self.authenticate, 'chrome': chrome.prepare_request, 'perm': self._get_perm, 'session': self._get_session, 'locale': self._get_locale, 'lc_time': self._get_lc_time, 'tz': self._get_timezone, 'form_token': self._get_form_token }) try: try: # Select the component that should handle the request chosen_handler = None try: for handler in self.handlers: if handler.match_request(req): chosen_handler = handler break if not chosen_handler: if not req.path_info or req.path_info == '/': chosen_handler = self.default_handler # pre-process any incoming request, whether a handler # was found or not chosen_handler = self._pre_process_request( req, chosen_handler) except TracError, e: raise HTTPInternalError(e) if not chosen_handler: if req.path_info.endswith('/'): # Strip trailing / and redirect target = req.path_info.rstrip('/').encode('utf-8') if req.query_string: target += '?' + req.query_string req.redirect(req.href + target, permanent=True) raise HTTPNotFound('No handler matched request to %s', req.path_info) req.callbacks['chrome'] = partial(chrome.prepare_request, handler=chosen_handler) # Protect against CSRF attacks: we validate the form token # for all POST requests with a content-type corresponding # to form submissions if req.method == 'POST': ctype = req.get_header('Content-Type') if ctype: ctype, options = cgi.parse_header(ctype) if ctype in ('application/x-www-form-urlencoded', 'multipart/form-data') and \ req.args.get('__FORM_TOKEN') != req.form_token: if self.env.secure_cookies and req.scheme == 'http': msg = _('Secure cookies are enabled, you must ' 'use https to submit forms.') else: msg = _('Do you have cookies enabled?') raise HTTPBadRequest( _('Missing or invalid form token.' ' %(msg)s', msg=msg)) # Process the request and render the template resp = chosen_handler.process_request(req) if resp: if len(resp) == 2: # old Clearsilver template and HDF data self.log.error( "Clearsilver template are no longer " "supported (%s)", resp[0]) raise TracError( _("Clearsilver templates are no longer supported, " "please contact your Trac administrator.")) # Genshi template, data, content_type = \ self._post_process_request(req, *resp) if 'hdfdump' in req.args: req.perm.require('TRAC_ADMIN') # debugging helper - no need to render first out = StringIO() pprint(data, out) req.send(out.getvalue(), 'text/plain') output = chrome.render_template(req, template, data, content_type) req.send(output, content_type or 'text/html') else: self._post_process_request(req) except RequestDone: # Give the session a chance to persist changes after a send() req.session.save() raise except: # post-process the request in case of errors err = sys.exc_info() try: self._post_process_request(req) except RequestDone: raise except Exception, e: self.log.error( "Exception caught while post-processing" " request: %s", exception_to_unicode(e, traceback=True)) raise err[0], err[1], err[2]
def __init__(self): self.has_pretty_dateinfo = hasattr(Chrome(self.env), 'default_dateinfo_format')
class SearchModuleTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() self.search_module = SearchModule(self.env) self.chrome = Chrome(self.env) pages_dir = pkg_resources.resource_filename('trac.wiki', 'default-pages') for page_name in ('WikiStart', 'TracModWSGI'): page = os.path.join(pages_dir, page_name) WikiAdmin(self.env).import_page(page, page_name) def tearDown(self): self.env.reset_db() def _insert_ticket(self, **kw): """Helper for inserting a ticket into the database""" return insert_ticket(self.env, **kw) def _process_request(self, req): self.assertEqual(True, self.search_module.match_request(req)) return self.search_module.process_request(req) def _render_template(self, req, template, data): rendered = self.chrome.render_template(req, template, data, { 'iterable': False, 'fragment': False }) return rendered.decode('utf-8') def test_process_request_page_in_range(self): for _ in xrange(21): self._insert_ticket(summary="Trac") req = MockRequest(self.env, path_info='/search', args={ 'page': '3', 'q': 'Trac', 'ticket': 'on' }) data = self._process_request(req)[1] self.assertEqual([], req.chrome['warnings']) self.assertEqual(2, data['results'].page) def test_process_request_page_out_of_range(self): """Out of range value for page defaults to page 1.""" for _ in xrange(20): self._insert_ticket(summary="Trac") req = MockRequest(self.env, path_info='/search', args={ 'page': '3', 'q': 'Trac', 'ticket': 'on' }) data = self._process_request(req)[1] self.assertIn("Page 3 is out of range.", req.chrome['warnings']) self.assertEqual(0, data['results'].page) def test_camelcase_quickjump(self): """CamelCase word does quick-jump.""" req = MockRequest(self.env, path_info='/search', args={'q': 'WikiStart'}) self.assertRaises(RequestDone, self._process_request, req) self.assertEqual('http://example.org/trac.cgi/wiki/WikiStart', req.headers_sent['Location']) self.assertIn("You arrived here through", req.chrome['notices'][0]) self.assertIn( '<a href="/trac.cgi/search?' 'q=WikiStart&noquickjump=1">here</a>', req.chrome['notices'][0]) def test_non_camelcase_no_quickjump(self): """Non-CamelCase word does not quick-jump.""" req = MockRequest(self.env, path_info='/search', args={'q': 'TracModWSGI'}) data = self._process_request(req)[1] results = list(data['results']) self.assertIsNone(data['quickjump']) self.assertEqual('TracModWSGI', data['query']) self.assertEqual(1, len(results)) self.assertEqual('/trac.cgi/wiki/TracModWSGI', results[0]['href']) self.assertEqual([], req.chrome['notices']) def test_rendering_noquickjump_unicode_error(self): """Test for regression of https://trac.edgewall.org/ticket/13212 """ def do_render(query): req = MockRequest(self.env, path_info='/search', args={ 'q': query, 'noquickjump': '1' }) template, data = self._process_request(req) return self._render_template(req, template, data) self.assertIn( u'<a href="/trac.cgi/query?id=1-2">Quickjump to <em>' u'ticket:1,\u200b2</em></a>', do_render('ticket:1,2')) self.assertIn( u'<a href="mailto:[email protected]">Quickjump to <em>' u'<span class="icon">\u200b</span>[email protected]' u'</em></a>', do_render('*****@*****.**'))
def expand_macro(self, formatter, name, content, realms=[]): """Evaluate macro call and render results. Calls from web-UI come with pre-processed realm selection. """ env = self.env req = formatter.req tag_system = TagSystem(env) all_realms = set([p.get_taggable_realm() for p in tag_system.tag_providers]) if not all_realms: # Tag providers are required, no result without at least one. return '' args, kw = parse_args(content) query = args and args[0].strip() or None if not realms: # Check macro arguments for realms (typical wiki macro call). realms = 'realm' in kw and kw['realm'].split('|') or [] if query: # Add realms from query expression. realms.extend(query_realms(query, all_realms)) # Remove redundant realm selection for performance. if set(realms) == all_realms: query = re.sub('(^|\W)realm:\S+(\W|$)', ' ', query).strip() if name == 'TagCloud': # Set implicit 'all tagged realms' as default. if not realms: realms = all_realms if query: all_tags = Counter() # Require per resource query including view permission checks. for resource, tags in tag_system.query(req, query): all_tags.update(tags) else: # Allow faster per tag query, side steps permission checks. all_tags = tag_system.get_all_tags(req, realms=realms) mincount = 'mincount' in kw and kw['mincount'] or None return self.render_cloud(req, all_tags, caseless_sort=self.caseless_sort, mincount=mincount, realms=realms) elif name == 'ListTagged': if content and _OBSOLETE_ARGS_RE.search(content): data = {'warning': 'obsolete_args'} else: data = {'warning': None} context = formatter.context # Use TagsQuery arguments (most likely wiki macro calls). cols = 'cols' in kw and kw['cols'] or self.default_cols format = 'format' in kw and kw['format'] or self.default_format if not realms: # Apply ListTagged defaults to macro call w/o realm. realms = list(set(all_realms)-set(self.exclude_realms)) if not realms: return '' query = '(%s) (%s)' % (query or '', ' or '.join(['realm:%s' % (r) for r in realms])) env.log.debug('LISTTAGGED_QUERY: %s', query) query_result = tag_system.query(req, query) if not query_result: return '' def _link(resource): if resource.realm == 'tag': # Keep realm selection in tag links. return builder.a(resource.id, href=self.get_href(req, realms, tag=resource)) elif resource.realm == 'ticket': # Return resource link including ticket status dependend # class to allow for common Trac ticket link style. ticket = Ticket(env, resource.id) return builder.a('#%s' % ticket.id, class_=ticket['status'], href=formatter.href.ticket(ticket.id), title=shorten_line(ticket['summary'])) return render_resource_link(env, context, resource, 'compact') if format == 'table': cols = [col for col in cols.split('|') if col in self.supported_cols] # Use available translations from Trac core. try: labels = TicketSystem(env).get_ticket_field_labels() labels['id'] = _('Id') except AttributeError: # Trac 0.11 neither has the attribute nor uses i18n. labels = {'id': 'Id', 'description': 'Description'} labels['realm'] = _('Realm') labels['tags'] = _('Tags') headers = [{'label': labels.get(col)} for col in cols] data.update({'cols': cols, 'headers': headers}) results = sorted(query_result, key=lambda r: \ embedded_numbers(to_unicode(r[0].id))) results = self._paginate(req, results, realms) rows = [] for resource, tags in results: desc = tag_system.describe_tagged_resource(req, resource) # Fallback to resource provider method. desc = desc or get_resource_description(env, resource, context=context) tags = sorted(tags) wiki_desc = format_to_oneliner(env, context, desc) if tags: rendered_tags = [_link(Resource('tag', tag)) for tag in tags] if 'oldlist' == format: resource_link = _link(resource) else: resource_link = builder.a(wiki_desc, href=get_resource_url( env, resource, context.href)) if 'table' == format: cells = [] for col in cols: if col == 'id': cells.append(_link(resource)) # Don't duplicate links to resource in both. elif col == 'description' and 'id' in cols: cells.append(wiki_desc) elif col == 'description': cells.append(resource_link) elif col == 'realm': cells.append(resource.realm) elif col == 'tags': cells.append( builder([(tag, ' ') for tag in rendered_tags])) rows.append({'cells': cells}) continue rows.append({'desc': wiki_desc, 'rendered_tags': None, 'resource_link': _link(resource)}) data.update({'format': format, 'paginator': results, 'results': rows, 'tags_url': req.href('tags')}) # Work around a bug in trac/templates/layout.html, that causes a # TypeError for the wiki macro call, if we use add_link() alone. add_stylesheet(req, 'common/css/search.css') return Chrome(env).render_template( req, 'listtagged_results.html', data, 'text/html', True)
def render_preference_panel(self, req, panel, path_info=None): if req.method == 'POST': action_arg = req.args.get('action', '') m = re.match('^([^_]+)_(.+)', action_arg) if m: action, arg = m.groups() 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')) data = {'rules': {}, 'subscribers': []} desc_map = {} defaults = [] data['formatters'] = {} data['selected_format'] = {} data['adverbs'] = ('always', 'never') data['adverb_labels'] = dict(always=_("Notify"), never=_("Never notify")) for i in self.subscribers: if not i.description(): continue if not req.session.authenticated and i.requires_authentication(): continue data['subscribers'].append({ 'class': i.__class__.__name__, 'description': i.description() }) desc_map[i.__class__.__name__] = i.description() if hasattr(i, 'default_subscriptions'): defaults.extend(i.default_subscriptions()) for t in self._iter_transports(): data['rules'][t] = [] styles = self._get_supported_styles(t) data['formatters'][t] = styles data['selected_format'][t] = styles[0] for r in Subscription.find_by_sid_and_distributor( self.env, req.session.sid, req.session.authenticated, t): if desc_map.get(r['class']): data['rules'][t].append({ 'id': r['id'], 'adverb': r['adverb'], 'class': r['class'], 'description': desc_map[r['class']], 'priority': r['priority'] }) data['selected_format'][t] = r['format'] data['default_rules'] = {} for r in sorted(defaults, key=itemgetter(3)): klass, dist, format, priority, adverb = r if not data['default_rules'].get(dist): data['default_rules'][dist] = [] if desc_map.get(klass): data['default_rules'][dist].append({ 'adverb': adverb, 'description': desc_map.get(klass) }) Chrome(self.env).add_jquery_ui(req) return 'prefs_notification.html', dict(data=data)
def render_ticket_action_control(self, req, ticket, action): self.log.debug('render_ticket_action_control: action "%s"' % action) this_action = self.actions[action] status = this_action['newstate'] operations = this_action['operations'] current_owner = ticket._old.get('owner', ticket['owner'] or '(none)') if not (Chrome(self.env).show_email_addresses or 'EMAIL_VIEW' in req.perm(ticket.resource)): format_user = obfuscate_email_address else: format_user = lambda address: address current_owner = format_user(current_owner) control = [] # default to nothing hints = [] if 'reset_workflow' in operations: control.append(tag("from invalid state ")) hints.append(_("Current state no longer exists")) if 'del_owner' in operations: hints.append(_("The ticket will be disowned")) if 'set_owner' in operations: id = 'action_%s_reassign_owner' % action selected_owner = req.args.get(id, req.authname) if this_action.has_key('set_owner'): owners = [ x.strip() for x in this_action['set_owner'].split(',') ] elif self.config.getbool('ticket', 'restrict_owner'): perm = PermissionSystem(self.env) owners = perm.get_users_with_permission('TICKET_MODIFY') owners.sort() else: owners = None if owners == None: owner = req.args.get(id, req.authname) control.append( tag_('to %(owner)s', owner=tag.input(type='text', id=id, name=id, value=owner))) hints.append( _("The owner will be changed from " "%(current_owner)s", current_owner=current_owner)) elif len(owners) == 1: owner = tag.input(type='hidden', id=id, name=id, value=owners[0]) formatted_owner = format_user(owners[0]) control.append( tag_('to %(owner)s ', owner=tag(formatted_owner, owner))) if ticket['owner'] != owners[0]: hints.append( _( "The owner will be changed from " "%(current_owner)s to %(selected_owner)s", current_owner=current_owner, selected_owner=formatted_owner)) else: control.append( tag_('to %(owner)s', owner=tag.select([ tag.option(x, value=x, selected=(x == selected_owner or None)) for x in owners ], id=id, name=id))) hints.append( _("The owner will be changed from " "%(current_owner)s", current_owner=current_owner)) if 'set_owner_to_self' in operations and \ ticket._old.get('owner', ticket['owner']) != req.authname: hints.append( _( "The owner will be changed from %(current_owner)s " "to %(authname)s", current_owner=current_owner, authname=req.authname)) if 'set_resolution' in operations: if this_action.has_key('set_resolution'): resolutions = [ x.strip() for x in this_action['set_resolution'].split(',') ] else: resolutions = [val.name for val in Resolution.select(self.env)] if not resolutions: raise TracError( _("Your workflow attempts to set a resolution " "but none is defined (configuration issue, " "please contact your Trac admin).")) id = 'action_%s_resolve_resolution' % action if len(resolutions) == 1: resolution = tag.input(type='hidden', id=id, name=id, value=resolutions[0]) control.append( tag_('as %(resolution)s', resolution=tag(resolutions[0], resolution))) hints.append( _("The resolution will be set to %(name)s", name=resolutions[0])) else: selected_option = req.args.get( id, TicketSystem(self.env).default_resolution) control.append( tag_( 'as %(resolution)s', resolution=tag.select([ tag.option(x, value=x, selected=(x == selected_option or None)) for x in resolutions ], id=id, name=id))) hints.append(_("The resolution will be set")) if 'del_resolution' in operations: hints.append(_("The resolution will be deleted")) if 'leave_status' in operations: control.append( _('as %(status)s ', status=ticket._old.get('status', ticket['status']))) else: if status != '*': hints.append(_("Next status will be '%(name)s'", name=status)) return (this_action['name'], tag(*control), '. '.join(hints))
def _render_template(self, req): data = {'keywords': self.keywords} return Chrome(self.env). \ render_template(req, 'keywords.html', data, 'MarkupTemplate', True)
def process_request(self, req): """ Processing the request. """ req.perm('blog').assert_permission('BLOG_VIEW') blog_core = FullBlogCore(self.env) format = req.args.get('format', '').lower() command, pagename, path_items, listing_data = self._parse_path(req) action = req.args.get('action', 'view').lower() version = req.args.getint('version', 0) data = {} template = 'fullblog_view.html' data['blog_about'] = BlogPost(self.env, 'about') data['blog_infotext'] = blog_core.get_bloginfotext() blog_month_names = map_month_names( self.env.config.getlist('fullblog', 'month_names')) data['blog_month_names'] = blog_month_names self.log.debug("Blog debug: command=%r, pagename=%r, path_items=%r", command, pagename, path_items) if not command: # Request for just root (display latest) data['blog_post_list'] = [] count = 0 maxcount = self.num_items blog_posts = get_blog_posts(self.env) for post in blog_posts: bp = BlogPost(self.env, post[0], post[1]) if 'BLOG_VIEW' in req.perm(bp.resource): data['blog_post_list'].append(bp) count += 1 if maxcount and count == maxcount: # Only display a certain number on front page (from config) break data['blog_list_title'] = "Recent posts" + \ (len(blog_posts) > maxcount and \ " (max %d) - Browse or Archive for more" % (maxcount,) \ or '') add_link(req, 'alternate', req.href.blog(format='rss'), 'RSS Feed', 'application/rss+xml', 'rss') elif command == 'archive': # Requesting the archive page template = 'fullblog_archive.html' data['blog_archive'] = [] for period, period_posts in group_posts_by_month(get_blog_posts(self.env)): allowed_posts = [] for post in period_posts: bp = BlogPost(self.env, post[0], post[1]) if 'BLOG_VIEW' in req.perm(bp.resource): allowed_posts.append(post) if allowed_posts: data['blog_archive'].append((period, allowed_posts)) add_link(req, 'alternate', req.href.blog(format='rss'), 'RSS Feed', 'application/rss+xml', 'rss') elif command == 'view' and pagename: # Requesting a specific blog post the_post = BlogPost(self.env, pagename, version) req.perm(the_post.resource).require('BLOG_VIEW') if not the_post.version: raise HTTPNotFound("No blog post named '%s'." % pagename) if req.method == 'POST': # Adding/Previewing a comment # Permission? req.perm(the_post.resource).require('BLOG_COMMENT') comment = BlogComment(self.env, pagename) comment.comment = req.args.get('comment', '') comment.author = (req.authname != 'anonymous' and req.authname) \ or req.args.get('author') comment.time = datetime.datetime.now(utc) warnings = [] if 'cancelcomment' in req.args: req.redirect(req.href.blog(pagename)) elif 'previewcomment' in req.args: data['comment_preview'] = True warnings.extend(blog_core.create_comment(req, comment, verify_only=True)) elif 'submitcomment' in req.args and not warnings: warnings.extend(blog_core.create_comment(req, comment)) if not warnings: req.redirect(req.href.blog(pagename )+'#comment-'+str(comment.number)) data['blog_comment'] = comment # Push all warnings out to the user. for field, reason in warnings: if field: add_warning(req, "Field '%s': %s" % (field, reason)) else: add_warning(req, reason) data['blog_post'] = the_post context = web_context(req, the_post.resource, absurls=format=='rss' and True or False) data['context'] = context if format == 'rss': return 'fullblog_post.rss', data, 'application/rss+xml' # Regular web response context = web_context(req, the_post.resource) data['blog_attachments'] = AttachmentModule(self.env).attachment_data(context) # Previous and Next ctxtnav prev, next = blog_core.get_prev_next_posts(req.perm, the_post.name) if prev: add_link(req, 'prev', req.href.blog(prev), prev) if next: add_link(req, 'next', req.href.blog(next), next) if arity(prevnext_nav) == 4: # 0.12 compat following trac:changeset:8597 prevnext_nav(req, 'Previous Post', 'Next Post') else: prevnext_nav(req, 'Post') # RSS feed for post and comments add_link(req, 'alternate', req.href.blog(pagename, format='rss'), 'RSS Feed', 'application/rss+xml', 'rss') elif command in ['create', 'edit']: template = 'fullblog_edit.html' default_pagename = blog_core._get_default_postname(req.authname) the_post = BlogPost(self.env, pagename or default_pagename) warnings = [] if command == 'create' and req.method == 'GET' and not the_post.version: # Support appending query arguments for populating intial fields the_post.update_fields(req.args) if command == 'create' and the_post.version: # Post with name or suggested name already exists if 'BLOG_CREATE' in req.perm and the_post.name == default_pagename \ and not req.method == 'POST': if default_pagename: add_notice(req, "Suggestion for new name already exists " "('%s'). Please make a new name." % the_post.name) elif pagename: warnings.append( ('', "A post named '%s' already exists. Enter new name." % the_post.name)) the_post = BlogPost(self.env, '') if command == 'edit': req.perm(the_post.resource).require('BLOG_VIEW') # Starting point if req.method == 'POST': # Create or edit a blog post if 'blog-cancel' in req.args: if req.args.get('action','') == 'edit': req.redirect(req.href.blog(pagename)) else: req.redirect(req.href.blog()) # Assert permissions if command == 'create': req.perm(Resource('blog', None)).require('BLOG_CREATE') elif command == 'edit': if the_post.author == req.authname: req.perm(the_post.resource).require('BLOG_MODIFY_OWN') else: req.perm(the_post.resource).require('BLOG_MODIFY_ALL') # Check input orig_author = the_post.author if not the_post.update_fields(req.args): warnings.append(('', "None of the fields have changed.")) version_comment = req.args.get('new_version_comment', '') if 'blog-preview' in req.args: warnings.extend(blog_core.create_post( req, the_post, req.authname, version_comment, verify_only=True)) elif 'blog-save' in req.args and not warnings: warnings.extend(blog_core.create_post( req, the_post, req.authname, version_comment)) if not warnings: req.redirect(req.href.blog(the_post.name)) context = web_context(req, the_post.resource) data['context'] = context data['blog_attachments'] = AttachmentModule(self.env).attachment_data(context) data['blog_action'] = 'preview' data['blog_version_comment'] = version_comment if (orig_author and orig_author != the_post.author) and ( not 'BLOG_MODIFY_ALL' in req.perm(the_post.resource)): add_notice(req, "If you change the author you cannot " \ "edit the post again due to restricted permissions.") data['blog_orig_author'] = orig_author for field, reason in warnings: if field: add_warning(req, "Field '%s': %s" % (field, reason)) else: add_warning(req, reason) data['blog_edit'] = the_post elif command == 'delete': bp = BlogPost(self.env, pagename) req.perm(bp.resource).require('BLOG_DELETE') if 'blog-cancel' in req.args: req.redirect(req.href.blog(pagename)) comment = req.args.getint('comment', 0) warnings = [] if comment: # Deleting a specific comment bc = BlogComment(self.env, pagename, comment) if not bc.number: raise TracError( "Cannot delete. Blog post name and/or comment number missing.") if req.method == 'POST' and comment and pagename: warnings.extend(blog_core.delete_comment(bc)) if not warnings: add_notice(req, "Blog comment %d deleted." % comment) req.redirect(req.href.blog(pagename)) template = 'fullblog_delete.html' data['blog_comment'] = bc else: # Delete a version of a blog post or all versions # with comments and attachments if only version. if not bp.version: raise TracError( "Cannot delete. Blog post '%s' does not exist." % ( bp.name)) version = req.args.getint('version', 0) if req.method == 'POST': if 'blog-version-delete' in req.args: if bp.version != version: raise TracError( "Cannot delete. Can only delete most recent version.") warnings.extend(blog_core.delete_post(bp, version=bp.versions[-1])) elif 'blog-delete' in req.args: version = 0 warnings.extend(blog_core.delete_post(bp, version=version)) if not warnings: if version > 1: add_notice(req, "Blog post '%s' version %d deleted." % ( pagename, version)) req.redirect(req.href.blog(pagename)) else: add_notice(req, "Blog post '%s' deleted." % pagename) req.redirect(req.href.blog()) template = 'fullblog_delete.html' data['blog_post'] = bp for field, reason in warnings: if field: add_warning(req, "Field '%s': %s" % (field, reason)) else: add_warning(req, reason) elif command.startswith('listing-'): # 2007/10 or category/something or author/theuser title = category = author = '' from_dt = to_dt = None if command == 'listing-month': from_dt = listing_data['from_dt'] to_dt = listing_data['to_dt'] title = "Posts for the month of %s %d" % ( blog_month_names[from_dt.month -1], from_dt.year) add_link(req, 'alternate', req.href.blog(format='rss'), 'RSS Feed', 'application/rss+xml', 'rss') elif command == 'listing-category': category = listing_data['category'] if category: title = "Posts in category %s" % category add_link(req, 'alternate', req.href.blog('category', category, format='rss'), 'RSS Feed', 'application/rss+xml', 'rss') elif command == 'listing-author': author = listing_data['author'] if author: title = "Posts by author %s" % author add_link(req, 'alternate', req.href.blog('author', author, format='rss'), 'RSS Feed', 'application/rss+xml', 'rss') if not (author or category or (from_dt and to_dt)): raise HTTPNotFound("Not a valid path for viewing blog posts.") blog_posts = [] for post in get_blog_posts(self.env, category=category, author=author, from_dt=from_dt, to_dt=to_dt): bp = BlogPost(self.env, post[0], post[1]) if 'BLOG_VIEW' in req.perm(bp.resource): blog_posts.append(bp) data['blog_post_list'] = blog_posts data['blog_list_title'] = title else: raise HTTPNotFound("Not a valid blog path.") if (not command or command.startswith('listing-')) and format == 'rss': data['context'] = web_context(req, absurls=True) data['blog_num_items'] = self.num_items return 'fullblog.rss', data, 'application/rss+xml' data['blog_months'], data['blog_authors'], data['blog_categories'], \ data['blog_total'] = \ blog_core.get_months_authors_categories( user=req.authname, perm=req.perm) if 'BLOG_CREATE' in req.perm('blog'): add_ctxtnav(req, 'New Post', href=req.href.blog('create'), title="Create new Blog Post") chrome = Chrome(self.env) chrome.add_auto_preview(req) chrome.add_wiki_toolbars(req) add_stylesheet(req, 'tracfullblog/css/fullblog.css') add_stylesheet(req, 'common/css/code.css') data['blog_personal_blog'] = self.env.config.getbool('fullblog', 'personal_blog') data['blog_archive_rss_icon'] = self.all_rss_icons \ or self.archive_rss_icon data['blog_all_rss_icons'] = self.all_rss_icons return template, data, {}
def _format_plaintext(self, event): """Format ticket change notification e-mail (untranslated)""" ticket = event.target newticket = event.category == 'created' with translation_deactivated(ticket): link = self.env.abs_href.ticket(ticket.id) changes_body = '' changes_descr = '' change_data = {} if not newticket and event.time: # Ticket change from trac.ticket.web_ui import TicketModule for change in TicketModule(self.env) \ .grouped_changelog_entries(ticket, when=event.time): if not change['permanent']: # attachment with same time... continue author = change['author'] change_data.update({ 'author': self._format_author(author), 'comment': wrap(change['comment'], self.COLS, ' ', ' ', '\n', self.ambiwidth) }) link += '#comment:%s' % str(change.get('cnum', '')) for field, values in change['fields'].iteritems(): old = values['old'] new = values['new'] newv = '' if field == 'description': new_descr = wrap(new, self.COLS, ' ', ' ', '\n', self.ambiwidth) old_descr = wrap(old, self.COLS, '> ', '> ', '\n', self.ambiwidth) old_descr = old_descr.replace( 2 * '\n', '\n' + '>' + '\n') cdescr = '\n' cdescr += 'Old description:' + 2 * '\n' + old_descr + \ 2 * '\n' cdescr += 'New description:' + 2 * '\n' + new_descr + \ '\n' changes_descr = cdescr elif field == 'cc': addcc, delcc = self._diff_cc(old, new) chgcc = '' if delcc: chgcc += wrap( " * cc: %s (removed)" % ', '.join(delcc), self.COLS, ' ', ' ', '\n', self.ambiwidth) + '\n' if addcc: chgcc += wrap( " * cc: %s (added)" % ', '.join(addcc), self.COLS, ' ', ' ', '\n', self.ambiwidth) + '\n' if chgcc: changes_body += chgcc else: if field in ['owner', 'reporter']: old = self._format_author(old) new = self._format_author(new) elif field in ticket.time_fields: format = ticket.fields.by_name(field) \ .get('format') old = self._format_time_field(old, format) new = self._format_time_field(new, format) newv = new length = 7 + len(field) spacer_old, spacer_new = ' ', ' ' if len(old + new) + length > self.COLS: length = 5 if len(old) + length > self.COLS: spacer_old = '\n' if len(new) + length > self.COLS: spacer_new = '\n' chg = '* %s: %s%s%s=>%s%s' \ % (field, spacer_old, old, spacer_old, spacer_new, new) chg = chg.replace('\n', '\n' + length * ' ') chg = wrap(chg, self.COLS, '', length * ' ', '\n', self.ambiwidth) changes_body += ' %s%s' % (chg, '\n') if newv: change_data[field] = { 'oldvalue': old, 'newvalue': new } ticket_values = ticket.values.copy() ticket_values['id'] = ticket.id ticket_values['description'] = wrap(ticket_values.get( 'description', ''), self.COLS, initial_indent=' ', subsequent_indent=' ', linesep='\n', ambiwidth=self.ambiwidth) ticket_values['new'] = newticket ticket_values['link'] = link data = Chrome(self.env).populate_data( None, { 'CRLF': CRLF, 'ticket_props': self._format_props(ticket), 'ticket_body_hdr': self._format_hdr(ticket), 'ticket': ticket_values, 'changes_body': changes_body, 'changes_descr': changes_descr, 'change': change_data }) return self._format_body(data, 'ticket_notify_email.txt')
if col.startswith('__') and col.endswith('__'): # __col__ header['hidden'] = True elif col[0] == '_' and col[-1] == '_': # _col_ header_group = [] header_groups.append(header_group) header_groups.append([]) elif col[0] == '_': # _col header['hidden'] = True elif col[-1] == '_': # col_ header_groups.append([]) header_group.append(header) # Structure the rows and cells: # - group rows according to __group__ value, if defined # - group cells the same way headers are grouped chrome = Chrome(self.env) row_groups = [] authorized_results = [] prev_group_value = None for row_idx, result in enumerate(results): col_idx = 0 cell_groups = [] row = {'cell_groups': cell_groups} realm = 'ticket' parent_realm = '' parent_id = '' email_cells = [] for header_group in header_groups: cell_group = [] for header in header_group: value = cell_value(result[col_idx])
def test_format_emails(self): format_emails = Chrome(self.env).format_emails to_format = '[email protected], user2; [email protected]' self.assertEqual(u'user1@\u2026, user2, user3@\u2026', format_emails(None, to_format))