def attach(self, req): path = req.path_info.split("/") realm, obj_id = path[3:] obj_resource = Resource(realm, id=obj_id) attachment = Attachment(self.env, obj_resource.child("attachment")) req.perm(attachment.resource).require("ATTACHMENT_CREATE") if req.method == "POST": self._save_attachement(req, attachment) pass return "itteco_attach_popup.html", {"resource": obj_resource}, "text/html"
def _render_view(self, req, db, version): db = self.env.get_db_cnx() sql = "SELECT name FROM milestone " \ "INNER JOIN milestone_version ON (name = milestone) " \ "WHERE version = %s " \ "ORDER BY due" cursor = db.cursor() cursor.execute(sql, (version.name,)) milestones = [] tickets = [] milestone_stats = [] for row in cursor: milestone = Milestone(self.env, row[0]) milestones.append(milestone) mtickets = get_tickets_for_milestone(self.env, db, milestone.name, 'owner') mtickets = apply_ticket_permissions(self.env, req, mtickets) tickets += mtickets stat = get_ticket_stats(self.milestone_stats_provider, mtickets) milestone_stats.append(milestone_stats_data(self.env, req, stat, milestone.name)) stats = get_ticket_stats(self.version_stats_provider, tickets) interval_hrefs = version_interval_hrefs(self.env, req, stats, [milestone.name for milestone in milestones]) version.resource = Resource('version', version.name) context = Context.from_request(req, version.resource) version.is_released = version.time and version.time.date() < date.today() version.stats = stats version.interval_hrefs = interval_hrefs version.stats_href = [] # Not implemented yet, see th:#10349 data = { 'context': context, 'version': version, 'attachments': AttachmentModule(self.env).attachment_data(context), 'milestones': milestones, 'milestone_stats': milestone_stats, 'show_milestone_description': self.show_milestone_description # Not implemented yet } add_stylesheet(req, 'extendedversion/css/version.css') add_script(req, 'common/js/folding.js') add_ctxtnav(req, _("Back to Versions"), req.href.versions()) return 'version_view.html', data, None
def test_view_build(self): config = BuildConfig(self.env, name='test', path='trunk') config.insert() platform = TargetPlatform(self.env, config='test', name='any') platform.insert() build = Build(self.env, config='test', platform=1, rev=123, rev_time=42, status=Build.SUCCESS, slave='hal') build.insert() PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') req = Mock(method='GET', base_path='', cgi_location='', path_info='/build/test/1', href=Href('/trac'), args={}, chrome={}, authname='joe', perm=PermissionCache(self.env, 'joe')) root = Mock(get_entries=lambda: ['foo'], get_history=lambda: [('trunk', rev, 'edit') for rev in range(123, 111, -1)]) self.repos = Mock(get_node=lambda path, rev=None: root, sync=lambda: None, normalize_path=lambda path: path, normalize_rev=lambda rev: rev, get_changeset=lambda rev: Mock(author='joe')) self.repos.authz = Mock(has_permission=lambda path: True, assert_permission=lambda path: None) module = BuildController(self.env) assert module.match_request(req) _, data, _ = module.process_request(req) self.assertEqual('view_build', data['page_mode']) from trac.resource import Resource self.assertEquals(Resource('build', 'test/1'), data['context'].resource) self.assertEquals([], data['build']['attachments']['attachments']) self.assertEquals('/trac/attachment/build/test/1/', data['build']['attachments']['attach_href'])
def __call__(self, realm_or_resource, id=False, version=False): """Convenience function for using thus: 'WIKI_VIEW' in perm(context) or 'WIKI_VIEW' in perm(realm, id, version) or 'WIKI_VIEW' in perm(resource) """ resource = Resource(realm_or_resource, id, version) if resource and self._resource and resource == self._resource: return self else: return PermissionCache(self.env, self.username, resource, self._cache)
def do_put(self, req, args): trac_type = AgiloTicketSystem(self.env).normalize_type( args.get(Key.TYPE)) if trac_type is None: self.error_response(req, {}, ['Must specify a type.']) success, ticket_id, errors = self._create_ticket(req, args) if not success: self.error_response(req, {}, errors) ticket_resource = Resource('ticket')(id=ticket_id) if not req.perm.has_permission(Action.TICKET_VIEW, ticket_resource): self.error_response(req, {}, ['No permission to see ticket %d' % ticket_id]) return self.get_ticket_as_json(req, ticket_id)
def get_blog_resources(env): """ Returns a list of resource instances of existing blog posts (current version). The list is ordered by publish_time (newest first). """ sql = "SELECT bp1.name FROM fullblog_posts bp1, " \ "(SELECT name, max(version) AS ver FROM fullblog_posts " \ "GROUP BY name) bp2 WHERE bp1.name = bp2.name AND " \ "bp1.version = ver ORDER BY bp1.publish_time DESC" if hasattr(env, 'db_query'): cursor = env.db_query(sql) else: db = env.get_db_cnx() cursor = db.cursor() cursor.execute(sql) blog_realm = Resource('blog') return [blog_realm(id=post[0], version=0) for post in cursor]
def test_set_tags(self): resource = Resource('wiki', 'TaggedPage') self.req.perm = PermissionCache(self.env, username='******') # Shouldn't raise an error with appropriate permission. self.tag_wp.set_resource_tags(self.req, resource, self.tags) self.tag_wp.set_resource_tags(self.req, resource, ['tag2']) # Check change records. rows = self.env.db_query( """ SELECT author,oldtags,newtags FROM tags_change WHERE tagspace=%s AND name=%s ORDER by time DESC """, ('wiki', 'TaggedPage')) self.assertEqual(rows[0], ('editor', 'tag1', 'tag2')) self.assertEqual(rows[1], ('editor', '', 'tag1'))
def _check_duplicate_id(self, req, ticket): if req.args.get('action') == 'resolve': resolution = req.args.get('action_resolve_resolve_resolution') if resolution == 'duplicate': duplicate_id = req.args.get('duplicate_id') if not duplicate_id: yield None, "Duplicate ticket ID must be provided." try: duplicate_ticket = self.find_ticket(duplicate_id) req.perm.require('TICKET_MODIFY', Resource(duplicate_ticket.id)) ticket.duplicate = duplicate_ticket except NoSuchTicketError: yield None, "Invalid duplicate ticket ID."
def post_process_request(self, req, template, data, content_type): if data and req.path_info == '/timeline' and \ 'TAGS_VIEW' in req.perm(Resource('tags')): def realm_handler(_, node, context): return query.match(node, [context.realm]) query_str = req.args.get(self.key) if query_str is None and req.args.get('format') != 'rss': query_str = req.session.get('timeline.%s' % self.key) else: query_str = (query_str or '').strip() # Record tag query expression between visits. req.session['timeline.%s' % self.key] = query_str if data.get('events') and query_str: tag_system = TagSystem(self.env) try: query = Query(query_str, attribute_handlers=dict(realm=realm_handler)) except InvalidQuery, e: add_warning( req, _("Tag query syntax error: %s" % to_unicode(e))) else: all_realms = tag_system.get_taggable_realms(req.perm) query_realms = set() for m in REALM_RE.finditer(query.as_string()): query_realms.add(m.group(1)) # Don't care about resources from non-taggable realms. realms = not query_realms and all_realms or \ query_realms.intersection(all_realms) events = [] self.log.debug("Filtering timeline events by tags '%s'" % query_str) for event in data['events']: resource = event['data'][0] if resource.realm in realms: # Shortcut view permission checks here. tags = tag_system.get_tags(None, resource) if query(tags, context=resource): events.append(event) # Overwrite with filtered list. data['events'] = events if query_str: # Add current value for next form rendering. data[self.key] = query_str elif self.key in req.session: del req.session[self.key]
def expand_macro(self, formatter, name, content, args=None): # pylint: disable=too-many-function-args args = args or {} reponame = args.get('repository') or '' rev = args.get('revision') # pylint: disable=no-member repos = RepositoryManager(self.env).get_repository(reponame) try: changeset = repos.get_changeset(rev) message = changeset.message rev = changeset.rev resource = repos.resource except Exception: # pylint: disable=broad-except message = content resource = Resource('repository', reponame) config = self.ticket_replace_section fields = {} for key, value in config.options(): idx = key.rfind('.') if idx >= 0: prefix, attribute = key[:idx], key[idx + 1:] field = fields.setdefault(prefix, {}) field[attribute] = config.get(key) else: fields[key] = {'': value} for prefix, field in fields.iteritems(): if not all(k in field for k in ['pattern', 'replace']): self.log.warn( "Ignoring [%s] %s, missing .pattern or .replace" % (self.ticket_replace_section_name, key)) continue subst = {'repository': reponame, 'revision': rev} pattern = field['pattern'].replace('$(', '%(') % subst replace = field['replace'].replace('$(', '%(') % subst message = re.sub(pattern, replace, message) if ChangesetModule(self.env).wiki_format_messages: message = '\n'.join( map(lambda line: "> " + line, message.split('\n'))) return tag.div(format_to_html(self.env, formatter.context.child( 'changeset', rev, parent=resource), message, escape_newlines=True), class_='message') else: return tag.pre(message, class_='message')
def render_widget(self, name, context, options): """Render widget considering given options. """ if name == 'WidgetDoc': add_stylesheet(context.req, 'dashboard/css/docs.css') widget_name, = self.bind_params(options, self.get_widget_params(name), 'urn') if widget_name is not None: try: providers = [([widget_name], self.resolve_widget(widget_name))] except LookupError: return 'widget_alert.html', { 'title': _('Widget documentation'), 'data': { 'msglabel': 'Alert', 'msgbody': 'Unknown identifier', 'msgdetails': [('Widget name', widget_name)] } }, context else: providers = [(provider.get_widgets(), provider) \ for provider in self.widget_providers] metadata = [self._prepare_doc_metadata(self.widget_metadata(wnm, p)) \ for widgets, p in providers for wnm in widgets] docs_resource = Resource('wiki', 'BloodhoundWidgets') insert_docs = resource_exists(self.env, docs_resource) and \ not (context.resource and \ docs_resource == context.resource) return 'widget_doc.html', { 'title': _('Widget documentation'), 'data': { 'items': metadata }, 'ctxtnav': [ tag.a(tag.i(class_='icon-info-sign'), ' ', _('Help'), href=get_resource_url(self.env, docs_resource, context.href)) ] if insert_docs else [], }, context else: raise InvalidIdentifier('Widget name MUST match any of ' + ', '.join(self.get_widgets()), title='Invalid widget identifier')
def get_search_results(self, req, terms, filters): """Overriding search results for Tickets""" if not 'ticket' in filters: return ticket_realm = Resource('ticket') with self.env.db_query as db: sql, args = search_to_sql(db, [ 'summary', 'keywords', 'description', 'reporter', 'cc', db.cast('id', 'text') ], terms) sql2, args2 = search_to_sql(db, ['newvalue'], terms) sql3, args3 = search_to_sql(db, ['value'], terms) ticketsystem = TicketSystem(self.env) if req.args.get('product'): productsql = "product='%s' AND" % req.args.get('product') else: productsql = "" for summary, desc, author, type, tid, ts, status, resolution in \ db("""SELECT summary, description, reporter, type, id, time, status, resolution FROM ticket WHERE (%s id IN ( SELECT id FROM ticket WHERE %s UNION SELECT ticket FROM ticket_change WHERE field='comment' AND %s UNION SELECT ticket FROM ticket_custom WHERE %s )) """ % (productsql, sql, sql2, sql3), args + args2 + args3): t = ticket_realm(id=tid) if 'TICKET_VIEW' in req.perm(t): yield (req.href.ticket(tid), tag_("%(title)s: %(message)s", title=tag.span(get_resource_shortname( self.env, t), class_=status), message=ticketsystem.format_summary( summary, status, resolution, type)), from_utimestamp(ts), author, shorten_result(desc, terms)) # Attachments for result in AttachmentModule(self.env) \ .get_search_results(req, ticket_realm, terms): yield result
def query(self, req, qstr='status!=closed'): """ Perform a ticket query, returning a list of ticket ID's. All queries will use stored settings for maximum number of results per page and paging options. Use `max=n` to define number of results to receive, and use `page=n` to page through larger result sets. Using `max=0` will turn off paging and return all results. """ q = query.Query.from_string(self.env, qstr) ticket_realm = Resource('ticket') out = [] for t in q.execute(req): tid = t['id'] if 'TICKET_VIEW' in req.perm(ticket_realm(id=tid)): out.append(tid) return out
def render_timeline_event(self, context, field, event): # Decompose event data. id = event[3] # Return apropriate content. resource = Resource('downloads', id) if field == 'url': if context.req.perm.has_permission('DOWNLOADS_VIEW', resource): return get_resource_url(self.env, resource, context.req.href) else: return '#' elif field == 'title': return tag('New download ', tag.em(get_resource_name(self.env, resource)), ' created') elif field == 'description': return get_resource_description(self.env, resource, 'summary')
def test_resource_link_ticket_context_milestone_exists(self): """Resource link in ticket context for viewable milestone. """ milestone = Milestone(self.env) milestone.name = 'milestone1' milestone.insert() req = MockRequest(self.env, path_info='/ticket/1') resource = Resource('milestone', 'milestone1') context = web_context(req) link = render_resource_link(self.env, context, resource, format='compact') self.assertEqual( '<a class="milestone" href="/trac.cgi/milestone/' 'milestone1" title="No date set">milestone1</a>', unicode(link))
def _wiki_view(self, req, stream): tags = self._page_tags(req) if not tags: return stream tag_system = TagSystem(self.env) add_stylesheet(req, 'tags/css/tractags.css') li = [] for tag_ in tags: resource = Resource('tag', tag_) anchor = render_resource_link(self.env, Context.from_request(req, resource), resource) li.append(tag.li(anchor, ' ')) insert = tag.ul(class_='tags')(tag.li('Tags', class_='header'), li) return stream | Transformer('//div[@class="buttons"]').before(insert)
def email_default_context(): class NoEmailViewPerm(MockPerm): def has_permission(self, action, realm_or_resource=None, id=False, version=False): return action != 'EMAIL_VIEW' __contains__ = has_permission context = RenderingContext(Resource('wiki', 'WikiStart'), href=Href('/'), perm=NoEmailViewPerm()) context.req = None # 1.0 FIXME .req shouldn't be required by formatter return context
def download_created(self, context, download): # Check proper permissions to modify tags. if not context.req.perm.has_permission('TAGS_MODIFY'): return # Create temporary resource. resource = Resource(self.realm, download['id']) # Delete tags of download with same ID for sure. tag_system = TagSystem(self.env) tag_system.delete_tags(context.req, resource) # Add tags of new download. new_tags = self._get_tags(download) self.log.debug('tags: %s' % (new_tags, )) tag_system.add_tags(context.req, resource, new_tags)
def _render_list(self, req): """products list""" products = [ p for p in Product.select(self.env) if 'PRODUCT_VIEW' in req.perm(Neighborhood('product', p.prefix)) ] map( lambda p: setattr( p, 'href', resolve_product_href(lookup_product_env(self.env, p.prefix), self.env)), products) data = { 'products': products, 'context': web_context(req, Resource('product', None)) } return 'product_list.html', data, None
def expand_macro(self, formatter, name, content, args={}): reponame = args.get('repository') or '' rev_str = args.get('revision') repos = RepositoryManager(self.env).get_repository(reponame) rev = None try: changeset = repos.get_changeset(repos.normalize_rev(rev_str)) message = changeset.message rev = repos.db_rev(changeset.rev) resource = repos.resource # add review status to commit message ( review = CodeReview(self.env, reponame, rev) status = review.encode(review.status) message += '\n\n{{{#!html \n' message += '<div class="codereviewstatus">' message += ' <div class="system-message %s">' % status.lower() message += ' <p>Code review status: ' message += ' <span>%s</span>' % review.status message += ' </p>' message += ' </div>' message += '</div>' message += '\n}}}' except Exception: message = content resource = Resource('repository', reponame) if formatter.context.resource.realm == 'ticket': ticket_re = CommitTicketUpdater.ticket_re if not any( int(tkt_id) == int(formatter.context.resource.id) for tkt_id in ticket_re.findall(message)): return tag.p( "(The changeset message doesn't reference this " "ticket)", class_='hint') if ChangesetModule(self.env).wiki_format_messages: ctxt = formatter.context return tag.div(format_to_html(self.env, ctxt('changeset', rev, parent=resource), message, escape_newlines=True), class_='message') else: return tag.pre(message, class_='message')
def filter_stream(self, req, method, filename, stream, data): page_name = req.args.get('page', 'WikiStart') planid = req.args.get('planid', '-1') if page_name == 'TC': # The root catalog does not have workflows return stream if page_name.startswith('TC') and filename == 'wiki_view.html': self.log.debug(">>> TestManagerWorkflowInterface - filter_stream") req.perm.require('TEST_VIEW') # Determine which object is being displayed (i.e. realm), # based on Wiki page name and the presence of the planid # request parameter. realm = None if page_name.find('_TC') >= 0: if not planid or planid == '-1': realm = 'testcase' key = {'id': page_name.rpartition('_TC')[2]} else: realm = 'testcaseinplan' key = { 'id': page_name.rpartition('_TC')[2], 'planid': planid } else: if not planid or planid == '-1': realm = 'testcatalog' key = {'id': page_name.rpartition('_TT')[2]} else: realm = 'testplan' key = {'id': planid} id = get_string_from_dictionary(key) res = Resource(realm, id) rwsystem = ResourceWorkflowSystem(self.env) workflow_markup = rwsystem.get_workflow_markup( req, '..', realm, res) self.log.debug("<<< TestManagerWorkflowInterface - filter_stream") return stream | Transformer( '//div[contains(@class,"wikipage")]').after(workflow_markup) return stream
def filter_stream(self, req, method, filename, stream, data): crumbs = self._get_crumbs(req.session) if not crumbs: return stream add_stylesheet(req, 'breadcrumbs/css/breadcrumbs.css') ul = [] path = req.path_info if path.count('/') >= 2: realm, resource_id = path.split('/', 2)[1:] if '&' in resource_id: resource_id = resource_id[0:resource_id.index('&')] current = '/'.join((realm, resource_id)) else: current = None offset = 0 if crumbs and crumbs[0] == current: offset = 1 for crumb in crumbs[offset: self.max_crumbs + offset]: realm, resource_id = crumb.split('/', 1) resource = Resource(realm, resource_id) name = get_resource_shortname(self.env, resource) if not resource_exists(self.env, resource): continue title = get_resource_summary(self.env, resource) link = req.href(realm, resource_id) first = ul == [] li = tag.li(tag.a(title=title, href=link)(name)) if first: li(class_="first") ul.append(li) if ul: last = ul.pop() ul.append(last(class_="last")) label = self.label if self.label else "Breadcrumbs:" insert = tag.ul(class_="nav", id="breadcrumbs")(tag.li(label), ul) else: insert = '' return stream | Transformer('//div[@id="metanav"]/ul').after(insert)
def get_timeline_events(self, req, start, stop, filters): if 'build' not in filters: return # Attachments (will be rendered by attachment module) for event in AttachmentModule(self.env).get_timeline_events( req, Resource('build'), start, stop): yield event start = to_timestamp(start) stop = to_timestamp(stop) add_stylesheet(req, 'bitten/bitten.css') db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute( "SELECT b.id,b.config,c.label,c.path, b.rev,p.name," "b.stopped,b.status FROM bitten_build AS b" " INNER JOIN bitten_config AS c ON (c.name=b.config) " " INNER JOIN bitten_platform AS p ON (p.id=b.platform) " "WHERE b.stopped>=%s AND b.stopped<=%s " "AND b.status IN (%s, %s) ORDER BY b.stopped", (start, stop, Build.SUCCESS, Build.FAILURE)) repos = self.env.get_repository(authname=req.authname) assert repos, 'No "(default)" Repository: Add a repository or alias ' \ 'named "(default)" to Trac.' event_kinds = { Build.SUCCESS: 'successbuild', Build.FAILURE: 'failedbuild' } for id_, config, label, path, rev, platform, stopped, status in cursor: if not _has_permission(req.perm, repos, path, rev=rev): continue errors = [] if status == Build.FAILURE: for step in BuildStep.select(self.env, build=id_, status=BuildStep.FAILURE, db=db): errors += [(step.name, error) for error in step.errors] display_rev = repos.normalize_rev(rev) yield (event_kinds[status], to_datetime(stopped, utc), None, (id_, config, label, display_rev, platform, status, errors))
def _download_link(self, formatter, ns, params, label): if ns == 'download': if formatter.req.perm.has_permission('DOWNLOADS_VIEW'): # Create context. context = Context.from_request(formatter.req)('downloads-wiki') db = self.env.get_db_cnx() context.cursor = db.cursor() # Get API component. api = self.env[DownloadsApi] # Get download. if re.match(r'\d+', params): download = api.get_download(context, params) else: download = api.get_download_by_file(context, params) if download: if formatter.req.perm.has_permission( 'DOWNLOADS_VIEW', Resource('downloads', download['id'])): # Return link to existing file. return html.a( label, href=formatter.href.downloads(params), title='%s (%s)' % (download['file'], pretty_size(download['size']))) else: # File exists but no permission to download it. html.a( label, href='#', title='%s (%s)' % (download['file'], pretty_size(download['size'])), class_='missing') else: # Return link to non-existing file. return html.a(label, href='#', title='File not found.', class_='missing') else: # Return link to file to which is no permission. return html.a(label, href='#', title='No permission to file.', class_='missing')
def refresh_project(self, project_identifier, from_date, to_date=datetime.now(datefmt.localtz), afilters=None, update=False): """ Will refresh a project events in cache in given date range. .. NOTE:: Dates needs to be given in datefmt.localtz form """ # Initialize objects project = Project.get(env_name=project_identifier) if not project: conf.log.warning('Project {0} is already removed from system or it cannot be found'.format(project_identifier)) return e = open_environment(conf.getEnvironmentSysPath(project.env_name), use_cache=True) pte = ProjectTimelineEvents(e) providers = pte.event_providers project_href = Href(conf.url_projects_path + '/' + project.env_name) context = Context(resource=Resource(), href=project_href) req = self._create_dummy_req(project_identifier) context.req = req context.perm = req.perm # Read events from timeline events = [] for provider in providers: try: # Use filters in parameter or check filters from providers if afilters: filters = afilters else: available_filters = provider.get_timeline_filters(req) or [] filters = [f[0] for f in available_filters] for event in provider.get_timeline_events(req, from_date, to_date, filters): event_data = self._event_data(provider, event) if event_data['author'] != 'trac': # Skip system events events.append(CachedEvent.from_event_data(project, event_data, context, filters[0])) except: conf.log.error("Could not read timeline events for %s from %s" % (project_identifier, str(provider))) # Write events into sql table self._write_events_into_cache(events, update)
def validate_blog_comment(self, req, postname, fields): if 'previewcomment' in req.args: return [] blog_res = Resource('blog', postname) if req.perm(blog_res).has_permission('BLOG_ADMIN'): return [] author = fields.get('author', '') changes = [(None, fields.get('comment', '')), (None, author)] if arity(FilterSystem.test) == 4: # 0.11 compatible method signature FilterSystem(self.env).test(req, author, changes) else: # 0.12+ compatible that adds an 'ip' argument FilterSystem(self.env).test(req, author, changes, req.remote_addr) return []
def _sticky_from_query(self, req, query): for col in ['id', 'summary', 'type'] + self._fields: if col not in query.rows: query.rows.append(col) query.has_more_pages = False query.max = 0 if hasattr(self.env, 'get_read_db'): db = self.env.get_read_db() else: db = self.env.get_db_cnx() tickets = [] for ticket in query.execute(req, db): resource = Resource('ticket', ticket['id']) if 'TICKET_VIEW' in req.perm(resource): tickets.append(ticket) return self._sticky(req, tickets)
def _render_link(self, context, name, label, extra=''): try: version = Version(self.env, name) except TracError: version = None # Note: the above should really not be needed, `Milestone.exists` # should simply be false if the milestone doesn't exist in the db # (related to #4130) href = context.href.version(name) if version and version.exists: resource = Resource('version', name) if 'VERSION_VIEW' in context.perm(resource): return tag.a(label, class_='version', href=href + extra) elif 'VERSION_CREATE' in context.perm('version', name): return tag.a(label, class_='missing version', href=href + extra, rel='nofollow') return tag.a(label, class_='missing version')
def test_log_link_checking_repository_resource(self): self.env.config.set( 'trac', 'permission_policies', 'TestLogModulePermissionPolicy, DefaultPermissionPolicy') resource = Resource('wiki', 'WikiStart') req = MockRequest(self.env, authname='anonymous') rendered = unicode( format_to_oneliner(self.env, web_context(req, resource), 'log:mock@42-43')) self.assertIn(' title="No permission to view change log"', rendered) req = MockRequest(self.env, authname='blah') rendered = unicode( format_to_oneliner(self.env, web_context(req, resource), 'log:mock@42-43')) self.assertIn(' href="/trac.cgi/log/mock/?revs=42-43"', rendered)
def _wiki_view(self, req, stream): add_stylesheet(req, 'tags/css/tractags.css') tags = self._page_tags(req) if not tags: return stream li = [] for tag_ in tags: resource = Resource('tag', tag_) anchor = render_resource_link(self.env, web_context(req, resource), resource) anchor = anchor(rel='tag') li.append(tag.li(anchor, ' ')) # TRANSLATOR: Header label text for tag list at wiki page bottom. insert = tag.ul(class_='tags')(tag.li(_("Tags"), class_='header'), li) return stream | ( Transformer('//div[contains(@class,"wikipage")]').after(insert))
def process_request(self, req): # Allow all POST requests (with a valid __FORM_TOKEN, ensuring that # the client has at least some permission). Additionally, allow GET # requests from TRAC_ADMIN for testing purposes. if req.method != 'POST': req.perm.require('TRAC_ADMIN') # @todo: Embed "tips" within the rendered output for the editor # (recognize TracLinks, table-stuff, macros, processors) # @todo: Save the content in server-side user-specific field for recovery realm = req.args.get('realm', 'wiki') id = req.args.get('id') version = req.args.get('version') if version is not None: try: version = int(version) except ValueError: version = None text = req.args.get('text', '') flavor = req.args.get('flavor') options = {} if 'escape_newlines' in req.args: options['escape_newlines'] = bool(int(req.args['escape_newlines'] or 0)) if 'shorten' in req.args: options['shorten'] = bool(int(req.args['shorten'] or 0)) resource = Resource(realm, id=id, version=version) context = Context.from_request(req, resource) rendered = format_to_cke_html(self.env, context, text, self.code_styles, **options) # since Trac renders underlined text as `<span class="underlined">text</span> # instead of u-tag, we need to adjust it for compatibility's sake # see also discussion at Google Groups: # https://groups.google.com/group/trac-dev/browse_thread/thread/833206a932d1f918 html = HTML(rendered) html |= Transformer('//span[@class="underline"]').rename('u').attr('class', None) # CKEditor renders indentation by using p style="margin-left: 40px" # instead of blockquote-tag html |= Transformer('//blockquote/p').attr('style', 'margin-left: 40px') html |= Transformer('//blockquote').unwrap() buffer = StringIO() html.render(out=buffer, encoding='utf-8') req.send( buffer.getvalue() )
def __init__(self, name, params, log): """Initialize a repository. :param name: a unique name identifying the repository, usually a type-specific prefix followed by the path to the repository. :param params: a `dict` of parameters for the repository. Contains the name of the repository under the key "name" and the surrogate key that identifies the repository in the database under the key "id". :param log: a logger instance. """ self.name = name self.params = params self.reponame = params['name'] self.id = params['id'] self.log = log self.resource = Resource('repository', self.reponame)
def filter_stream(self, req, method, filename, stream, data): # self.list_namespaces() # generate TracLink string resource = None if filename in ['ticket.html', 'wiki_view.html', 'report_view.html', 'milestone_view.html', 'agilo_ticket_view.html'] \ and 'context' in data: resource = copy(data['context'].resource) elif filename in ['search.html']: # search: resource = Resource('search', data['query']) elif filename in ['browser.html']: # source: resource = copy(data['context'].resource) if resource.parent and resource.parent.realm == 'repository': resource.id = '%s/%s' % (resource.parent.id, resource.id) resource.parent = None elif filename in ['revisionlog.html']: # log: resource = copy(data['context'].resource) resource.realm = 'log' if resource.parent and resource.parent.realm == 'repository': resource.id = '%s/%s' % (resource.parent.id, resource.id) resource.parent = None revranges = data.get('revranges', None) rev = data.get('rev', None) if revranges: resource.version = '%s:%s' % (revranges.a, revranges.b) elif rev: resource.version = rev elif filename in ['attachment.html']: if isinstance(data['attachment'], Attachment): # attachment: resource = copy(data['attachment'].resource) else: pass # attachment list page of the ticket; no TracLinks defined elif filename in ['timeline.html']: # timeline: resource = Resource('timeline', format_datetime(data['precisedate'], 'iso8601')) elif filename in ['changeset.html']: if data['changeset']: # changeset: resource = copy(data['context'].resource) if resource.parent and resource.parent.realm == 'repository': resource.id = '%s/%s' % (resource.id, resource.parent.id) # OK, I know resource.parent = None if data['restricted']: resource.id = '%s/%s' % (resource.id, data['new_path']) else: # diff: args = req.args old_path, new_path = args.get('old_path', ''), args.get('new_path', '') old_rev, new_rev = args.get('old'), args.get('new') if old_path == new_path: # diff:path@1:3 style resource = Resource('diff', old_path, '%s:%s' % (old_rev, new_rev)) else: # diff:path@1//path@3 style if old_rev: old_path += '@%s' % old_rev if new_rev: new_path += '@%s' % new_rev resource = Resource('diff', '%s//%s' % (old_path, new_path)) elif filename in ['query.html']: if 'report_resource' in data: resource = copy(data['report_resource']) else: resource = Resource('query', data['query'].to_string().replace("\n", "")[7:]) else: pass # link hash TODO: check wiki_view for agilo if filename in ['browser.html', 'changeset.html', 'ticket.html', 'agilo_ticket_view.html', 'wiki_view.html']: add_script(req, 'traclinks/js/jquery.ba-hashchange.js') add_script(req, 'traclinks/js/onhashchange.js') # if resource: traclinks = '%s' % (resource.id) if resource.version != None: traclinks += (resource.realm == 'ticket' and '?version=%s' or '@%s') % resource.version parent = resource.parent while parent and parent.id: traclinks += ':%s:%s' % (parent.realm, parent.id) if parent.version != None: traclinks += '@%s' % parent.version parent = parent.parent if ' ' in traclinks: traclinks = '"%s"' % traclinks # surround quote if needed traclinks = '%s:%s' % (resource.realm, traclinks) # new ticket template if resource.id == None and resource.realm == 'ticket': query_string = unicode_urlencode( [(k, v) for k, v in data['ticket'].values.iteritems() if v not in (None, '')]) traclinks = '[/newticket?%s]' % query_string #return stream | Transformer('//input[@id="proj-search"]').attr('placeholder', traclinks).attr('size', '50') _input = tag.input(value=traclinks, readonly='', style=self.style) div = tag.div(_input, id="banner-traclink", style="float:right") return stream | Transformer('//div[@id="header"]').before(div) return stream
class Repository(object): """Base class for a repository provided by a version control system.""" has_linear_changesets = False scope = '/' def __init__(self, name, params, log): """Initialize a repository. :param name: a unique name identifying the repository, usually a type-specific prefix followed by the path to the repository. :param params: a `dict` of parameters for the repository. Contains the name of the repository under the key "name" and the surrogate key that identifies the repository in the database under the key "id". :param log: a logger instance. """ self.name = name self.params = params self.reponame = params['name'] self.id = params['id'] self.log = log self.resource = Resource('repository', self.reponame) def close(self): """Close the connection to the repository.""" raise NotImplementedError def get_base(self): """Return the name of the base repository for this repository. This function returns the name of the base repository to which scoped repositories belong. For non-scoped repositories, it returns the repository name. """ return self.name def clear(self, youngest_rev=None): """Clear any data that may have been cached in instance properties. `youngest_rev` can be specified as a way to force the value of the `youngest_rev` property (''will change in 0.12''). """ pass def sync(self, rev_callback=None, clean=False): """Perform a sync of the repository cache, if relevant. If given, `rev_callback` must be a callable taking a `rev` parameter. The backend will call this function for each `rev` it decided to synchronize, once the synchronization changes are committed to the cache. When `clean` is `True`, the cache is cleaned first. """ pass def sync_changeset(self, rev): """Resync the repository cache for the given `rev`, if relevant. Returns a "metadata-only" changeset containing the metadata prior to the resync, or `None` if the old values cannot be retrieved (typically when the repository is not cached). """ return None def get_quickjump_entries(self, rev): """Generate a list of interesting places in the repository. `rev` might be used to restrict the list of available locations, but in general it's best to produce all known locations. The generated results must be of the form (category, name, path, rev). """ return [] def get_path_url(self, path, rev): """Return the repository URL for the given path and revision. The returned URL can be `None`, meaning that no URL has been specified for the repository, an absolute URL, or a scheme-relative URL starting with `//`, in which case the scheme of the request should be prepended. """ return None def get_changeset(self, rev): """Retrieve a Changeset corresponding to the given revision `rev`.""" raise NotImplementedError def get_changeset_uid(self, rev): """Return a globally unique identifier for the ''rev'' changeset. Two changesets from different repositories can sometimes refer to the ''very same'' changeset (e.g. the repositories are clones). """ def get_changesets(self, start, stop): """Generate Changeset belonging to the given time period (start, stop). """ rev = self.youngest_rev while rev: chgset = self.get_changeset(rev) if chgset.date < start: return if chgset.date < stop: yield chgset rev = self.previous_rev(rev) def has_node(self, path, rev=None): """Tell if there's a node at the specified (path,rev) combination. When `rev` is `None`, the latest revision is implied. """ try: self.get_node(path, rev) return True except TracError: return False def get_node(self, path, rev=None): """Retrieve a Node from the repository at the given path. A Node represents a directory or a file at a given revision in the repository. If the `rev` parameter is specified, the Node corresponding to that revision is returned, otherwise the Node corresponding to the youngest revision is returned. """ raise NotImplementedError def get_oldest_rev(self): """Return the oldest revision stored in the repository.""" raise NotImplementedError oldest_rev = property(lambda self: self.get_oldest_rev()) def get_youngest_rev(self): """Return the youngest revision in the repository.""" raise NotImplementedError youngest_rev = property(lambda self: self.get_youngest_rev()) def previous_rev(self, rev, path=''): """Return the revision immediately preceding the specified revision. If `path` is given, filter out ancestor revisions having no changes below `path`. In presence of multiple parents, this follows the first parent. """ raise NotImplementedError def next_rev(self, rev, path=''): """Return the revision immediately following the specified revision. If `path` is given, filter out descendant revisions having no changes below `path`. In presence of multiple children, this follows the first child. """ raise NotImplementedError def parent_revs(self, rev): """Return a list of parents of the specified revision.""" parent = self.previous_rev(rev) return [parent] if parent is not None else [] def rev_older_than(self, rev1, rev2): """Provides a total order over revisions. Return `True` if `rev1` is an ancestor of `rev2`. """ raise NotImplementedError def get_path_history(self, path, rev=None, limit=None): """Retrieve all the revisions containing this path. If given, `rev` is used as a starting point (i.e. no revision ''newer'' than `rev` should be returned). The result format should be the same as the one of Node.get_history() """ raise NotImplementedError def normalize_path(self, path): """Return a canonical representation of path in the repos.""" raise NotImplementedError def normalize_rev(self, rev): """Return a (unique) canonical representation of a revision. It's up to the backend to decide which string values of `rev` (usually provided by the user) should be accepted, and how they should be normalized. Some backends may for instance want to match against known tags or branch names. In addition, if `rev` is `None` or '', the youngest revision should be returned. """ raise NotImplementedError def short_rev(self, rev): """Return a compact representation of a revision in the repos.""" return self.normalize_rev(rev) def display_rev(self, rev): """Return a representation of a revision in the repos for displaying to the user. This can be a shortened revision string, e.g. for repositories using long hashes. """ return self.normalize_rev(rev) def get_changes(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=1): """Generates changes corresponding to generalized diffs. Generator that yields change tuples (old_node, new_node, kind, change) for each node change between the two arbitrary (path,rev) pairs. The old_node is assumed to be None when the change is an ADD, the new_node is assumed to be None when the change is a DELETE. """ raise NotImplementedError def is_viewable(self, perm): """Return True if view permission is granted on the repository.""" return 'BROWSER_VIEW' in perm(self.resource.child('source', '/')) can_view = is_viewable # 0.12 compatibility