def _prepare_results(self, result_docs, hits): ui_docs = [self._process_doc(doc) for doc in result_docs] results = Paginator( ui_docs, self.page - 1, self.pagelen, hits) self._prepare_shown_pages(results) results.current_page = {'href': None, 'class': 'current', 'string': str(results.page + 1), 'title': None} parameters = self.parameters if results.has_next_page: next_href = parameters.create_href(page=parameters.page + 1) add_link(self.req, 'next', next_href, _('Next Page')) if results.has_previous_page: prev_href = parameters.create_href(page=parameters.page - 1) add_link(self.req, 'prev', prev_href, _('Previous Page')) self.data[self.DATA_RESULTS] = results prevnext_nav(self.req, _('Previous'), _('Next'))
def _set_prev_next(self, req): """Sets in the chrome links the navigation to previous and next page""" next = prev = None history = req.session.get('help-history', '').split(',') page_url = req.href(req.path_info) if page_url in history: # user visited the page already history_idx = history.index(page_url) if history_idx > 0: prev = history[history_idx - 1] if history_idx + 1 < len(history): next = history[history_idx + 1] else: if len(history) > 0: prev = history[-1] # add current page to help history if len(history) >= 10: history = history[1:] history.append(page_url) req.session['help-history'] = ','.join(history) # Now add links if present if prev: add_link(req, 'prev', prev) if next: add_link(req, 'next', next) # add index link add_link(req, 'up', req.href('agilo-help')) prevnext_nav(req, 'page', 'index')
def _render_view(self, req, db, id): data = {} data['action'] = 'view' from model import Mail mail = Mail(self.env, id, db=db) target_threadroot = mail.thread_root # Todo:Raise error when messsageid is wrong. if 'mailarc_mails' in req.session: self.log.debug(req.session['mailarc_mails']) mails = req.session['mailarc_mails'].split() if str(id) in mails: idx = mails.index(str(id)) if idx > 0: #add_ctxtnav(req, _('first'), req.href.mailarchive(mails[0])) add_link(req, _('prev'), req.href.mailarchive(mails[idx - 1])) if idx < len(mails) - 1: add_link(req, _('next'), req.href.mailarchive(mails[idx + 1])) #add_ctxtnav(req, _('last'), req.href.mailarchive(mails[-1])) add_link(req, _('up'), req.session['mailarc_category_href']) prevnext_nav(req, u'メール', u'リストに戻る') #if target_threadroot == '': # target_threadroot = messageid data['mail'] = mail data['related_tickets'] = get_related_tickets(self.env, req, db, mail.id) data['attachment'] = MailArchiveAttachment(self.env, mail.id) return 'maildetail.html', data, None
def _add_links_to_navigation_bar(self, req, sprint): milestone = sprint.milestone previous_sprint = milestone.get_previous_sprint(sprint) next_sprint = milestone.get_next_sprint(sprint) if previous_sprint is not None: add_link(req, 'prev', previous_sprint.url()) if next_sprint is not None: add_link(req, 'next', next_sprint.url()) add_link(req, 'up', milestone.url(), 'Milestone') prevnext_nav(req, 'Sprint', 'Milestone')
def _modify_search_data(self, req, template, data, content_type, is_active): """Insert breadcumbs and context navigation items in search web UI """ if is_active: # Insert query string in search box (see bloodhound_theme.html) req.search_query = data.get('query') # Context nav prevnext_nav(req, _('Previous'), _('Next')) # Breadcrumbs nav data['resourcepath_template'] = 'bh_path_search.html'
def _prepare_links(self, req, name, milestone): """Prepares the links for the navigation bar""" list_sprints = SprintController.ListSprintsCommand( self.env, criteria={'milestone': name}) sprints = self.controller.process_command(list_sprints) for i, s in enumerate(sprints): s = s['sprint'] if s['name'] == name: if i > 0: add_link(req, 'prev', self.href(sprints[i - 1]['sprint']['name'])) if i < len(sprints) - 1: add_link(req, 'next', self.href(sprints[i + 1]['sprint']['name'])) break # add back link to Milestone if milestone: add_link(req, 'up', req.href.milestone(milestone), 'Milestone') prevnext_nav(req, 'Sprint', 'Milestone')
def _prepare_links(self, req, name, milestone): """Prepares the links for the navigation bar""" list_sprints = SprintController.ListSprintsCommand(self.env, criteria={'milestone': name}) sprints = self.controller.process_command(list_sprints) for i, s in enumerate(sprints): s = s['sprint'] if s['name'] == name: if i > 0: add_link(req, 'prev', self.href(sprints[i - 1]['sprint']['name'])) if i < len(sprints) - 1: add_link(req, 'next', self.href(sprints[i + 1]['sprint']['name'])) break # add back link to Milestone if milestone: add_link(req, 'up', req.href.milestone(milestone), 'Milestone') prevnext_nav(req, 'Sprint', 'Milestone')
def post_process_request(self, req, template, data, content_type): if data and 'context' in data and \ data['context'].resource.realm == 'wiki' and \ 'action' not in req.args and \ 'version' not in req.args: page = data['page'] prefix = '/' in page.name and page.name[:page.name.rindex('/') + 1] or '' wiki = WikiSystem(self.env) start = prefix.count('/') pages = sorted(page for page in wiki.get_pages(prefix) \ if (start >= page.count('/')) and 'WIKI_VIEW' in req.perm('wiki', page)) if page.name in pages: index = pages.index(page.name) if index > 0: add_link(req, 'prev', req.href.wiki(pages[index - 1])) if index < len(pages) - 1: add_link(req, 'next', req.href.wiki(pages[index + 1])) prevnext_nav(req, _('Previous Page'), _('Next Page')) return template, data, content_type
def _prepare_results(self, result_docs, hits): ui_docs = [self._process_doc(doc) for doc in result_docs] results = Paginator(ui_docs, self.page - 1, self.pagelen, hits) self._prepare_shown_pages(results) results.current_page = { 'href': None, 'class': 'current', 'string': str(results.page + 1), 'title': None } parameters = self.parameters if results.has_next_page: next_href = parameters.create_href(page=parameters.page + 1) add_link(self.req, 'next', next_href, _('Next Page')) if results.has_previous_page: prev_href = parameters.create_href(page=parameters.page - 1) add_link(self.req, 'prev', prev_href, _('Previous Page')) self.data[self.DATA_RESULTS] = results prevnext_nav(self.req, _('Previous'), _('Next'))
def _render_view(self, req, page): version = page.resource.version # Add registered converters if page.exists: for conversion in Mimeview(self.env).get_supported_conversions( 'text/x-trac-wiki'): conversion_href = req.href.wiki(page.name, version=version, format=conversion[0]) # or... conversion_href = get_resource_url(self.env, page.resource, req.href, format=conversion[0]) add_link(req, 'alternate', conversion_href, conversion[1], conversion[3]) data = self._page_data(req, page) if page.name == 'WikiStart': data['title'] = '' if not page.exists: if 'WIKI_CREATE' not in req.perm(page.resource): raise ResourceNotFound(_('Page %(name)s not found', name=page.name)) latest_page = WikiPage(self.env, page.name, version=None) req.perm(latest_page.resource).require('WIKI_VIEW') prev_version = next_version = None if version: try: version = int(version) for hist in latest_page.get_history(): v = hist[0] if v != version: if v < version: if not prev_version: prev_version = v break else: next_version = v except ValueError: version = None prefix = self.PAGE_TEMPLATES_PREFIX templates = [template[len(prefix):] for template in WikiSystem(self.env).get_pages(prefix) if 'WIKI_VIEW' in req.perm('wiki', template)] # -- prev/up/next links if prev_version: add_link(req, 'prev', req.href.wiki(page.name, version=prev_version), _('Version %(num)s', num=prev_version)) parent = None if version: add_link(req, 'up', req.href.wiki(page.name, version=None), _('View latest version')) elif '/' in page.name: parent = page.name[:page.name.rindex('/')] add_link(req, 'up', req.href.wiki(parent, version=None), _("View parent page")) if next_version: add_link(req, 'next', req.href.wiki(page.name, version=next_version), _('Version %(num)s', num=next_version)) # Add ctxtnav entries if version: prevnext_nav(req, _('Version'), _('View Latest Version')) add_ctxtnav(req, _('Last Change'), req.href.wiki(page.name, action='diff', version=page.version)) else: if parent: add_ctxtnav(req, _('Up'), req.href.wiki(parent)) self._wiki_ctxtnav(req, page) context = Context.from_request(req, page.resource) data.update({ 'context': context, 'latest_version': latest_page.version, 'attachments': AttachmentModule(self.env).attachment_data(context), 'default_template': self.DEFAULT_PAGE_TEMPLATE, 'templates': templates, 'version': version }) return 'wiki_view.html', data, None
def display_html(self, req, query): """returns the HTML according to a query for /hours view""" db = self.env.get_db_cnx() # The most recent query is stored in the user session; orig_list = None orig_time = datetime.now(utc) query_time = int(req.session.get("query_time", 0)) query_time = datetime.fromtimestamp(query_time, utc) query_constraints = unicode(query.constraints) if query_constraints != req.session.get("query_constraints") or query_time < orig_time - timedelta(hours=1): tickets = query.execute(req, db) # New or outdated query, (re-)initialize session vars req.session["query_constraints"] = query_constraints req.session["query_tickets"] = " ".join([str(t["id"]) for t in tickets]) else: orig_list = [int(id) for id in req.session.get("query_tickets", "").split()] tickets = query.execute(req, db, orig_list) orig_time = query_time context = Context.from_request(req, "query") ticket_data = query.template_data(context, tickets, orig_list, orig_time, req) # For clients without JavaScript, we add a new constraint here if # requested constraints = ticket_data["clauses"][0] if "add" in req.args: field = req.args.get("add_filter") if field: constraint = constraints.setdefault(field, {}) constraint.setdefault("values", []).append("") # FIXME: '' not always correct (e.g. checkboxes) req.session["query_href"] = query.get_href(context.href) req.session["query_time"] = to_timestamp(orig_time) req.session["query_tickets"] = " ".join([str(t["id"]) for t in tickets]) # data dictionary for genshi data = {} # get data for saved queries query_id = req.args.get("query_id") if query_id: try: query_id = int(query_id) except ValueError: add_warning(req, "query_id should be an integer, you put '%s'" % query_id) query_id = None if query_id: data["query_id"] = query_id query_data = self.get_query(query_id) data["query_title"] = query_data["title"] data["query_description"] = query_data["description"] data.setdefault("report", None) data.setdefault("description", None) data["all_columns"] = query.get_all_columns() + self.get_columns() # Don't allow the user to remove the id column data["all_columns"].remove("id") data["all_textareas"] = query.get_all_textareas() # need to re-get the cols because query will remove our fields cols = req.args.get("col") if isinstance(cols, basestring): cols = [cols] if not cols: cols = query.get_columns() + self.get_default_columns() data["col"] = cols now = datetime.now() # get the date range for the query if "from_year" in req.args: from_date = get_date(req.args["from_year"], req.args.get("from_month"), req.args.get("from_day")) else: from_date = datetime(now.year, now.month, now.day) from_date = from_date - timedelta(days=7) # 1 week ago, by default if "to_year" in req.args: to_date = get_date(req.args["to_year"], req.args.get("to_month"), req.args.get("to_day"), end_of_day=True) else: to_date = now data["prev_week"] = from_date - timedelta(days=7) data["months"] = list(enumerate(calendar.month_name)) data["years"] = range(now.year, now.year - 10, -1) data["days"] = range(1, 32) data["users"] = get_all_users(self.env) data["cur_worker_filter"] = req.args.get("worker_filter", "*any") data["from_date"] = from_date data["to_date"] = to_date ticket_ids = [t["id"] for t in tickets] # generate data for ticket_times time_records = self.get_ticket_hours( ticket_ids, from_date=from_date, to_date=to_date, worker_filter=data["cur_worker_filter"] ) data["query"] = ticket_data["query"] data["context"] = ticket_data["context"] data["row"] = ticket_data["row"] if "comments" in req.args.get("row", []): data["row"].append("comments") data["constraints"] = ticket_data["clauses"] our_labels = dict([(f["name"], f["label"]) for f in self.fields]) labels = TicketSystem(self.env).get_ticket_field_labels() labels.update(our_labels) data["labels"] = labels order = req.args.get("order") desc = bool(req.args.get("desc")) data["order"] = order data["desc"] = desc headers = [ { "name": col, "label": labels.get(col), "href": self.get_href(query, req.args, context.href, order=col, desc=(col == order and not desc)), } for col in cols ] data["headers"] = headers data["fields"] = ticket_data["fields"] data["modes"] = ticket_data["modes"] # group time records time_records_by_ticket = {} for record in time_records: id = record["ticket"] if id not in time_records_by_ticket: time_records_by_ticket[id] = [] time_records_by_ticket[id].append(record) data["extra_group_fields"] = dict( ticket=dict(name="ticket", type="select", label="Ticket"), worker=dict(name="worker", type="select", label="Worker"), ) num_items = 0 data["groups"] = [] # merge ticket data into ticket_time records for key, tickets in ticket_data["groups"]: ticket_times = [] total_time = 0 total_estimated_time = 0 for ticket in tickets: records = time_records_by_ticket.get(ticket["id"], []) [rec.update(ticket) for rec in records] ticket_times += records # sort ticket_times, if needed if order in our_labels: ticket_times.sort(key=lambda x: x[order], reverse=desc) data["groups"].append((key, ticket_times)) num_items += len(ticket_times) data["double_count_warning"] = "" # group by ticket id or other time_ticket fields if necessary if req.args.get("group") in data["extra_group_fields"]: query.group = req.args.get("group") if not query.group == "id": data[ "double_count_warning" ] = "Warning: estimated hours may be counted more than once if a ticket appears in multiple groups" tickets = data["groups"][0][1] groups = {} for time_rec in tickets: key = time_rec[query.group] if not key in groups: groups[key] = [] groups[key].append(time_rec) data["groups"] = sorted(groups.items()) total_times = dict((k, self.format_hours(sum(rec["seconds_worked"] for rec in v))) for k, v in data["groups"]) total_estimated_times = {} for key, records in data["groups"]: seen_tickets = set() est = 0 for record in records: # do not double-count tickets id = record["ticket"] if id in seen_tickets: continue seen_tickets.add(id) estimatedhours = record.get("estimatedhours") or 0 try: estimatedhours = float(estimatedhours) except ValueError: estimatedhours = 0 est += estimatedhours * 3600 total_estimated_times[key] = self.format_hours(est) data["total_times"] = total_times data["total_estimated_times"] = total_estimated_times # format records for record in time_records: if "seconds_worked" in record: record["seconds_worked"] = self.format_hours(record["seconds_worked"]) # XXX misleading name if "time_started" in record: record["time_started"] = self.format_date(record["time_started"]) if "time_submitted" in record: record["time_submitted"] = self.format_date(record["time_submitted"]) data["query"].num_items = num_items data["labels"] = TicketSystem(self.env).get_ticket_field_labels() data["labels"].update(labels) data["can_add_hours"] = req.perm.has_permission("TICKET_ADD_HOURS") data["multiproject"] = self.env.is_component_enabled(MultiprojectHours) from web_ui import TracUserHours data["user_hours"] = self.env.is_component_enabled(TracUserHours) # return the rss, if requested if req.args.get("format") == "rss": return self.queryhours2rss(req, data) # return the csv, if requested if req.args.get("format") == "csv": self.queryhours2csv(req, data) # add rss link rss_href = req.href(req.path_info, format="rss") add_link(req, "alternate", rss_href, _("RSS Feed"), "application/rss+xml", "rss") # add csv link add_link(req, "alternate", req.href(req.path_info, format="csv", **req.args), "CSV", "text/csv", "csv") # add navigation of weeks prev_args = dict(req.args) next_args = dict(req.args) prev_args["from_year"] = (from_date - timedelta(days=7)).year prev_args["from_month"] = (from_date - timedelta(days=7)).month prev_args["from_day"] = (from_date - timedelta(days=7)).day prev_args["to_year"] = from_date.year prev_args["to_month"] = from_date.month prev_args["to_day"] = from_date.day next_args["from_year"] = to_date.year next_args["from_month"] = to_date.month next_args["from_day"] = to_date.day next_args["to_year"] = (to_date + timedelta(days=7)).year next_args["to_month"] = (to_date + timedelta(days=7)).month next_args["to_day"] = (to_date + timedelta(days=7)).day add_link(req, "prev", self.get_href(query, prev_args, context.href), _("Prev Week")) add_link(req, "next", self.get_href(query, next_args, context.href), _("Next Week")) prevnext_nav(req, _("Prev Week"), _("Next Week")) add_ctxtnav(req, "Cross-Project Hours", req.href.hours("multiproject")) add_ctxtnav( req, "Hours by User", req.href.hours( "user", from_day=from_date.day, from_month=from_date.month, from_year=from_date.year, to_day=to_date.year, to_month=to_date.month, to_year=to_date.year, ), ) add_ctxtnav(req, "Saved Queries", req.href.hours("query/list")) add_stylesheet(req, "common/css/report.css") add_script(req, "common/js/query.js") return ("hours_timeline.html", data, "text/html")
previous_start = format_date(to_datetime(previous_start, req.tz), format='%Y-%m-%d', tzinfo=req.tz) add_link(req, 'prev', req.href.timeline(from_=previous_start, authors=authors, daysback=daysback), _("Previous Period")) if today - fromdate > timedelta(days=0): next_start = fromdate.replace(tzinfo=None) + \ timedelta(days=daysback + 1) next_start = format_date(to_datetime(next_start, req.tz), format='%Y-%m-%d', tzinfo=req.tz) add_link(req, 'next', req.href.timeline(from_=next_start, authors=authors, daysback=daysback), _("Next Period")) prevnext_nav(req, _("Previous Period"), _("Next Period")) return 'timeline.html', data, None # ITemplateProvider methods def get_htdocs_dirs(self): return [] def get_templates_dirs(self): return [pkg_resources.resource_filename('trac.timeline', 'templates')] # IRequestFilter methods def pre_process_request(self, req, handler): return handler
def _render_config(self, req, config_name): db = self.env.get_db_cnx() config = BuildConfig.fetch(self.env, config_name, db=db) if not config: raise HTTPNotFound("Build configuration '%s' does not exist." \ % config_name) repos = self.env.get_repository(authname=req.authname) assert repos, 'No "(default)" Repository: Add a repository or alias ' \ 'named "(default)" to Trac.' rev = config.max_rev or repos.youngest_rev try: _has_permission(req.perm, repos, config.path, rev=rev, raise_error=True) except NoSuchNode: raise TracError("Permission checking against repository path %s " "at revision %s failed." % (config.path, rev)) data = {'title': 'Build Configuration "%s"' \ % config.label or config.name, 'page_mode': 'view_config'} add_link(req, 'up', req.href.build(), 'Build Status') description = config.description if description: description = wiki_to_html(description, self.env, req) pending_builds = list(Build.select(self.env, config=config.name, status=Build.PENDING)) inprogress_builds = list(Build.select(self.env, config=config.name, status=Build.IN_PROGRESS)) data['config'] = { 'name': config.name, 'label': config.label, 'path': config.path, 'min_rev': config.min_rev, 'min_rev_href': req.href.changeset(config.min_rev), 'max_rev': config.max_rev, 'max_rev_href': req.href.changeset(config.max_rev), 'active': config.active, 'description': description, 'browser_href': req.href.browser(config.path), 'builds_pending' : len(pending_builds), 'builds_inprogress' : len(inprogress_builds) } context = Context.from_request(req, config.resource) data['context'] = context data['config']['attachments'] = AttachmentModule(self.env).attachment_data(context) platforms = list(TargetPlatform.select(self.env, config=config_name, db=db)) data['config']['platforms'] = [ { 'name': platform.name, 'id': platform.id, 'builds_pending': len(list(Build.select(self.env, config=config.name, status=Build.PENDING, platform=platform.id))), 'builds_inprogress': len(list(Build.select(self.env, config=config.name, status=Build.IN_PROGRESS, platform=platform.id))) } for platform in platforms ] has_reports = False for report in Report.select(self.env, config=config.name, db=db): has_reports = True break if has_reports: chart_generators = [] report_categories = list(self._report_categories_for_config(config)) for generator in ReportChartController(self.env).generators: for category in generator.get_supported_categories(): if category in report_categories: chart_generators.append({ 'href': req.href.build(config.name, 'chart/' + category), 'category': category, 'style': self.config.get('bitten', 'chart_style'), }) data['config']['charts'] = chart_generators page = max(1, int(req.args.get('page', 1))) more = False data['page_number'] = page repos = self.env.get_repository(authname=req.authname) assert repos, 'No "(default)" Repository: Add a repository or alias ' \ 'named "(default)" to Trac.' builds_per_page = 12 * len(platforms) idx = 0 builds = {} revisions = [] for platform, rev, build in collect_changes(repos, config): if idx >= page * builds_per_page: more = True break elif idx >= (page - 1) * builds_per_page: if rev not in builds: revisions.append(rev) builds.setdefault(rev, {}) builds[rev].setdefault('href', req.href.changeset(rev)) builds[rev].setdefault('display_rev', repos.normalize_rev(rev)) if build and build.status != Build.PENDING: build_data = _get_build_data(self.env, req, build) build_data['steps'] = [] for step in BuildStep.select(self.env, build=build.id, db=db): build_data['steps'].append({ 'name': step.name, 'description': step.description, 'duration': to_datetime(step.stopped or int(time.time()), utc) - \ to_datetime(step.started, utc), 'status': _step_status_label[step.status], 'cls': _step_status_label[step.status].replace(' ', '-'), 'errors': step.errors, 'href': build_data['href'] + '#step_' + step.name }) builds[rev][platform.id] = build_data idx += 1 data['config']['builds'] = builds data['config']['revisions'] = revisions if page > 1: if page == 2: prev_href = req.href.build(config.name) else: prev_href = req.href.build(config.name, page=page - 1) add_link(req, 'prev', prev_href, 'Previous Page') if more: next_href = req.href.build(config.name, page=page + 1) add_link(req, 'next', next_href, 'Next Page') if arity(prevnext_nav) == 4: # Trac 0.12 compat, see #450 prevnext_nav(req, 'Previous Page', 'Next Page') else: prevnext_nav (req, 'Page') return data
def process_request(self, req): req.perm('timeline').require('TIMELINE_VIEW') format = req.args.get('format') maxrows = int(req.args.get('max', 50 if format == 'rss' else 0)) lastvisit = int(req.session.get('timeline.lastvisit', '0')) # indication of new events is unchanged when form is updated by user revisit = any(a in req.args for a in ['update', 'from', 'daysback', 'author']) if revisit: lastvisit = int(req.session.get('timeline.nextlastvisit', lastvisit)) # Parse the from date and adjust the timestamp to the last second of # the day fromdate = today = datetime.now(req.tz) yesterday = to_datetime(today.replace(tzinfo=None) - timedelta(days=1), req.tz) precisedate = precision = None if 'from' in req.args: # Acquire from date only from non-blank input reqfromdate = req.args['from'].strip() if reqfromdate: try: precisedate = user_time(req, parse_date, reqfromdate) except TracError as e: add_warning(req, e) else: fromdate = precisedate.astimezone(req.tz) precision = req.args.get('precision', '') if precision.startswith('second'): precision = timedelta(seconds=1) elif precision.startswith('minute'): precision = timedelta(minutes=1) elif precision.startswith('hour'): precision = timedelta(hours=1) else: precision = None fromdate = to_datetime(datetime(fromdate.year, fromdate.month, fromdate.day, 23, 59, 59, 999999), req.tz) daysback = as_int(req.args.get('daysback'), 90 if format == 'rss' else None) if daysback is None: daysback = as_int(req.session.get('timeline.daysback'), None) if daysback is None: daysback = self.default_daysback daysback = max(0, daysback) if self.max_daysback >= 0: daysback = min(self.max_daysback, daysback) authors = req.args.get('authors') if authors is None and format != 'rss': authors = req.session.get('timeline.authors') authors = (authors or '').strip() data = {'fromdate': fromdate, 'daysback': daysback, 'authors': authors, 'today': user_time(req, format_date, today), 'yesterday': user_time(req, format_date, yesterday), 'precisedate': precisedate, 'precision': precision, 'events': [], 'filters': [], 'abbreviated_messages': self.abbreviated_messages, 'lastvisit': lastvisit} available_filters = [] for event_provider in self.event_providers: available_filters += event_provider.get_timeline_filters(req) or [] # check the request or session for enabled filters, or use default filters = [f[0] for f in available_filters if f[0] in req.args] if not filters and format != 'rss': filters = [f[0] for f in available_filters if req.session.get('timeline.filter.' + f[0]) == '1'] if not filters: filters = [f[0] for f in available_filters if len(f) == 2 or f[2]] # save the results of submitting the timeline form to the session if 'update' in req.args: for filter_ in available_filters: key = 'timeline.filter.%s' % filter_[0] if filter_[0] in req.args: req.session[key] = '1' elif key in req.session: del req.session[key] stop = fromdate start = to_datetime(stop.replace(tzinfo=None) - timedelta(days=daysback + 1), req.tz) # create author include and exclude sets include = set() exclude = set() for match in self._authors_pattern.finditer(authors): name = (match.group(2) or match.group(3) or match.group(4)).lower() if match.group(1): exclude.add(name) else: include.add(name) # gather all events for the given period of time events = [] for provider in self.event_providers: try: for event in provider.get_timeline_events(req, start, stop, filters) or []: author = (event[2] or '').lower() if (not include or author in include) \ and author not in exclude: events.append(self._event_data(provider, event)) except Exception as e: # cope with a failure of that provider self._provider_failure(e, req, provider, filters, [f[0] for f in available_filters]) # prepare sorted global list events = sorted(events, key=lambda e: e['date'], reverse=True) if maxrows: events = events[:maxrows] data['events'] = events if format == 'rss': rss_context = web_context(req, absurls=True) rss_context.set_hints(wiki_flavor='html', shorten_lines=False) data['context'] = rss_context return 'timeline.rss', data, 'application/rss+xml' else: req.session.set('timeline.daysback', daysback, self.default_daysback) req.session.set('timeline.authors', authors, '') # store lastvisit if events and not revisit: lastviewed = to_utimestamp(events[0]['date']) req.session['timeline.lastvisit'] = max(lastvisit, lastviewed) req.session['timeline.nextlastvisit'] = lastvisit html_context = web_context(req) html_context.set_hints(wiki_flavor='oneliner', shorten_lines=self.abbreviated_messages) data['context'] = html_context add_stylesheet(req, 'common/css/timeline.css') rss_href = req.href.timeline([(f, 'on') for f in filters], daysback=90, max=50, authors=authors, format='rss') add_link(req, 'alternate', auth_link(req, rss_href), _('RSS Feed'), 'application/rss+xml', 'rss') Chrome(self.env).add_jquery_ui(req) for filter_ in available_filters: data['filters'].append({'name': filter_[0], 'label': filter_[1], 'enabled': filter_[0] in filters}) # Navigation to the previous/next period of 'daysback' days previous_start = fromdate.replace(tzinfo=None) - \ timedelta(days=daysback + 1) previous_start = format_date(to_datetime(previous_start, req.tz), format='%Y-%m-%d', tzinfo=req.tz) add_link(req, 'prev', req.href.timeline(from_=previous_start, authors=authors, daysback=daysback), _("Previous Period")) if today - fromdate > timedelta(days=0): next_start = fromdate.replace(tzinfo=None) + \ timedelta(days=daysback + 1) next_start = format_date(to_datetime(next_start, req.tz), format='%Y-%m-%d', tzinfo=req.tz) add_link(req, 'next', req.href.timeline(from_=next_start, authors=authors, daysback=daysback), _("Next Period")) prevnext_nav(req, _("Previous Period"), _("Next Period")) return 'timeline.html', data, None
def _render_view(self, req, milestone): milestone_groups = [] available_groups = [] default_group_by_available = False ticket_fields = TicketSystem(self.env).get_ticket_fields() # collect fields that can be used for grouping for field in ticket_fields: if field['type'] == 'select' and field['name'] != 'milestone' \ or field['name'] in ('owner', 'reporter'): available_groups.append({ 'name': field['name'], 'label': field['label'] }) if field['name'] == self.default_group_by: default_group_by_available = True # determine the field currently used for grouping by = None if default_group_by_available: by = self.default_group_by elif available_groups: by = available_groups[0]['name'] by = req.args.get('by', by) tickets = get_tickets_for_milestone(self.env, milestone=milestone.name, field=by) tickets = apply_ticket_permissions(self.env, req, tickets) stat = get_ticket_stats(self.stats_provider, tickets) context = web_context(req, milestone.resource) data = { 'context': context, 'milestone': milestone, 'attachments': AttachmentModule(self.env).attachment_data(context), 'available_groups': available_groups, 'grouped_by': by, 'groups': milestone_groups } data.update(milestone_stats_data(self.env, req, stat, milestone.name)) if by: def per_group_stats_data(gstat, group_name): return milestone_stats_data(self.env, req, gstat, milestone.name, by, group_name) milestone_groups.extend( grouped_stats_data(self.env, self.stats_provider, tickets, by, per_group_stats_data)) add_stylesheet(req, 'common/css/roadmap.css') def add_milestone_link(rel, milestone): href = req.href.milestone(milestone.name, by=req.args.get('by')) add_link(req, rel, href, _('Milestone "%(name)s"', name=milestone.name)) milestones = [ m for m in Milestone.select(self.env) if 'MILESTONE_VIEW' in req.perm(m.resource) ] idx = [i for i, m in enumerate(milestones) if m.name == milestone.name] if idx: idx = idx[0] if idx > 0: add_milestone_link('first', milestones[0]) add_milestone_link('prev', milestones[idx - 1]) if idx < len(milestones) - 1: add_milestone_link('next', milestones[idx + 1]) add_milestone_link('last', milestones[-1]) prevnext_nav(req, _("Previous Milestone"), _("Next Milestone"), _("Back to Roadmap")) return 'milestone_view.html', data
def process_request(self, req): presel = req.args.get("preselected") if presel and (presel + "/").startswith(req.href.browser() + "/"): req.redirect(presel) path = req.args.get("path", "/") rev = req.args.get("rev", "") if rev.lower() in ("", "head"): rev = None format = req.args.get("format") order = req.args.get("order", "name").lower() desc = "desc" in req.args rm = RepositoryManager(self.env) all_repositories = rm.get_all_repositories() reponame, repos, path = rm.get_repository_by_path(path) # Repository index show_index = not reponame and path == "/" if show_index: if repos and (as_bool(all_repositories[""].get("hidden")) or not repos.is_viewable(req.perm)): repos = None if not repos and reponame: raise ResourceNotFound(_("Repository '%(repo)s' not found", repo=reponame)) if reponame and reponame != repos.reponame: # Redirect alias qs = req.query_string req.redirect(req.href.browser(repos.reponame or None, path) + ("?" + qs if qs else "")) reponame = repos.reponame if repos else None # Find node for the requested path/rev context = web_context(req) node = None changeset = None display_rev = lambda rev: rev if repos: try: if rev: rev = repos.normalize_rev(rev) # If `rev` is `None`, we'll try to reuse `None` consistently, # as a special shortcut to the latest revision. rev_or_latest = rev or repos.youngest_rev node = get_existing_node(req, repos, path, rev_or_latest) except NoSuchChangeset as e: raise ResourceNotFound(e, _("Invalid changeset number")) if node: try: # use changeset instance to retrieve branches and tags changeset = repos.get_changeset(node.rev) except NoSuchChangeset: pass context = context.child(repos.resource.child(self.realm, path, version=rev_or_latest)) display_rev = repos.display_rev # Prepare template data path_links = get_path_links(req.href, reponame, path, rev, order, desc) repo_data = dir_data = file_data = None if show_index: repo_data = self._render_repository_index(context, all_repositories, order, desc) if node: if not node.is_viewable(req.perm): raise PermissionError("BROWSER_VIEW" if node.isdir else "FILE_VIEW", node.resource, self.env) if node.isdir: if format in ("zip",): # extension point here... self._render_zip(req, context, repos, node, rev) # not reached dir_data = self._render_dir(req, repos, node, rev, order, desc) elif node.isfile: file_data = self._render_file(req, context, repos, node, rev) if not repos and not (repo_data and repo_data["repositories"]): # If no viewable repositories, check permission instead of # repos.is_viewable() req.perm.require("BROWSER_VIEW") if show_index: raise ResourceNotFound(_("No viewable repositories")) else: raise ResourceNotFound(_("No node %(path)s", path=path)) quickjump_data = properties_data = None if node and not req.is_xhr: properties_data = self.render_properties("browser", context, node.get_properties()) quickjump_data = list(repos.get_quickjump_entries(rev)) data = { "context": context, "reponame": reponame, "repos": repos, "repoinfo": all_repositories.get(reponame or ""), "path": path, "rev": node and node.rev, "stickyrev": rev, "display_rev": display_rev, "changeset": changeset, "created_path": node and node.created_path, "created_rev": node and node.created_rev, "properties": properties_data, "path_links": path_links, "order": order, "desc": 1 if desc else None, "repo": repo_data, "dir": dir_data, "file": file_data, "quickjump_entries": quickjump_data, "wiki_format_messages": self.config["changeset"].getbool("wiki_format_messages"), "xhr": req.is_xhr, # Remove in 1.3.1 } if req.is_xhr: # render and return the content only return "dir_entries.html", data, None if dir_data or repo_data: add_script(req, "common/js/expand_dir.js") add_script(req, "common/js/keyboard_nav.js") # Links for contextual navigation if node: if node.isfile: prev_rev = repos.previous_rev(rev=node.created_rev, path=node.created_path) if prev_rev: href = req.href.browser(reponame, node.created_path, rev=prev_rev) add_link(req, "prev", href, _("Revision %(num)s", num=display_rev(prev_rev))) if rev is not None: add_link(req, "up", req.href.browser(reponame, node.created_path)) next_rev = repos.next_rev(rev=node.created_rev, path=node.created_path) if next_rev: href = req.href.browser(reponame, node.created_path, rev=next_rev) add_link(req, "next", href, _("Revision %(num)s", num=display_rev(next_rev))) prevnext_nav(req, _("Previous Revision"), _("Next Revision"), _("Latest Revision")) else: if path != "/": add_link(req, "up", path_links[-2]["href"], _("Parent directory")) add_ctxtnav( req, tag.a(_("Last Change"), href=req.href.changeset(node.created_rev, reponame, node.created_path)) ) if node.isfile: annotate = data["file"]["annotate"] if annotate: add_ctxtnav( req, _("Normal"), title=_("View file without annotations"), href=req.href.browser(reponame, node.created_path, rev=rev), ) if annotate != "blame": add_ctxtnav( req, _("Blame"), title=_( "Annotate each line with the last " "changed revision " "(this can be time consuming...)" ), href=req.href.browser(reponame, node.created_path, rev=rev, annotate="blame"), ) add_ctxtnav(req, _("Revision Log"), href=req.href.log(reponame, path, rev=rev)) path_url = repos.get_path_url(path, rev) if path_url: if path_url.startswith("//"): path_url = req.scheme + ":" + path_url add_ctxtnav(req, _("Repository URL"), href=path_url) add_stylesheet(req, "common/css/browser.css") return "browser.html", data, None
def _render_diff(self, req, page): if not page.exists: raise TracError(_('Version %(num)s of page "%(name)s" does not ' 'exist', num=req.args.get('version'), name=page.name)) old_version = req.args.get('old_version') if old_version: old_version = int(old_version) if old_version == page.version: old_version = None elif old_version > page.version: # FIXME: what about reverse diffs? old_version = page.resource.version page = WikiPage(self.env, page.name, version=old_version) req.perm(page.resource).require('WIKI_VIEW') latest_page = WikiPage(self.env, page.name, version=None) req.perm(latest_page.resource).require('WIKI_VIEW') new_version = int(page.version) date = author = comment = ipnr = None num_changes = 0 prev_version = next_version = None for version, t, a, c, i in latest_page.get_history(): if version == new_version: date = t author = a or 'anonymous' comment = c or '--' ipnr = i or '' else: if version < new_version: num_changes += 1 if not prev_version: prev_version = version if old_version is None or version == old_version: old_version = version break else: next_version = version if not old_version: old_version = 0 old_page = WikiPage(self.env, page.name, old_version) req.perm(old_page.resource).require('WIKI_VIEW') # -- text diffs old_text = old_page.text.splitlines() new_text = page.text.splitlines() diff_data, changes = self._prepare_diff(req, page, old_text, new_text, old_version, new_version) # -- prev/up/next links if prev_version: add_link(req, 'prev', req.href.wiki(page.name, action='diff', version=prev_version), _('Version %(num)s', num=prev_version)) add_link(req, 'up', req.href.wiki(page.name, action='history'), _('Page history')) if next_version: add_link(req, 'next', req.href.wiki(page.name, action='diff', version=next_version), _('Version %(num)s', num=next_version)) data = self._page_data(req, page, 'diff') data.update({ 'change': {'date': date, 'author': author, 'ipnr': ipnr, 'comment': comment}, 'new_version': new_version, 'old_version': old_version, 'latest_version': latest_page.version, 'num_changes': num_changes, 'longcol': 'Version', 'shortcol': 'v', 'changes': changes, 'diff': diff_data, }) prevnext_nav(req, _('Previous Change'), _('Next Change'), _('Wiki History')) return 'wiki_diff.html', data, None
def process_request(self, req): """process request handler""" req.perm.require('PRODUCT_VIEW') pid = req.args.get('productid', None) action = req.args.get('action', 'view') products = [ p for p in Product.select(self.env) if 'PRODUCT_VIEW' in req.perm(p.resource) ] if pid is not None: add_link(req, 'up', req.href.products(), _('Products')) try: product = Product(self.env, {'prefix': pid}) except ResourceNotFound: product = Product(self.env) data = {'product': product} if req.method == 'POST': if req.args.has_key('cancel'): req.redirect(req.href.products(product.prefix)) elif action == 'edit': return self._do_save(req, product) elif action == 'delete': req.perm(product.resource).require('PRODUCT_DELETE') retarget_to = req.args.get('retarget', None) name = product.name product.delete(resources_to=retarget_to) add_notice(req, _('The product "%(n)s" has been deleted.', n=name)) req.redirect(req.href.products()) elif action in ('new', 'edit'): return self._render_editor(req, product) elif action == 'delete': req.perm(product.resource).require('PRODUCT_DELETE') return 'product_delete.html', data, None if pid is None: data = {'products': products} return 'product_list.html', data, None def add_product_link(rel, product): href = req.href.products(product.prefix) add_link(req, rel, href, _('Product "%(name)s"', name=product.name)) idx = [i for i, p in enumerate(products) if p.name == product.name] if idx: idx = idx[0] if idx > 0: add_product_link('first', products[0]) add_product_link('prev', products[idx - 1]) if idx < len(products) - 1: add_product_link('next', products[idx + 1]) add_product_link('last', products[-1]) prevnext_nav(req, _('Previous Product'), _('Next Product'), _('Back to Product List')) return 'product_view.html', data, None
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() try: version = int(req.args.get("version", 0)) except: 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.env.log.debug("Blog debug: command=%r, pagename=%r, path_items=%r" % (command, pagename, path_items)) if has_UserPictureModule and self.avatar_size > 0: data["avatars"] = avatars = {} else: avatars = None page = int(req.args.get("page", 1)) if not command: # Request for just root (display latest) data["blog_post_list"] = [] count = 0 mincount = (page - 1) * self.num_items maxcount = mincount + 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): count += 1 if count <= mincount: continue data["blog_post_list"].append(bp) if avatars != None and bp.author not in avatars: avatars[bp.author] = self._get_avatar(req, bp.author) if maxcount and count == maxcount: # Only display a certain number on front page (from config) break data["blog_list_title"] = _("Recent posts") if len(blog_posts) > maxcount: data["blog_list_title"] += _(" (max %d) - Browse or Archive for") % (self.num_items,) data["blog_list_title_more"] = _("more") data["blog_list_title_href"] = req.href.blog(page=page + 1) 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: 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 if avatars != None: avatars[the_post.author] = self._get_avatar(req, the_post.author) context = Context.from_request(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 = Context.from_request(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 self.use_authname: the_post.update_fields({"author": req.authname}) 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 self.use_authname: the_post.update_fields({"author": req.authname}) 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 = Context.from_request(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 = int(req.args.get("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 = int(req.args.get("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 mincount = maxcount = 0 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"] mincount = (page - 1) * self.num_items_category maxcount = mincount + self.num_items_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"] mincount = (page - 1) * self.num_items_category maxcount = mincount + self.num_items_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 = [] count = 0 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): count += 1 if count <= mincount: continue blog_posts.append(bp) if avatars != None and bp.author not in avatars: avatars[bp.author] = self._get_avatar(req, bp.author) if maxcount and count == maxcount: break if maxcount and count == maxcount: title += _(" (max %d) - Browse or Archive for") % (maxcount - mincount) data["blog_list_title_more"] = _("more") if category: rs = u"category/" + category else: rs = u"author/" + author data["blog_list_title_href"] = req.href.blog(rs, page=page + 1) 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"] = Context.from_request(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")) 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, None)
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() try: version = int(req.args.get('version', 0)) except: 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 user_recent_post = get_blog_posts(self.env,author=req.authname, per_num='3' ,current_num='0') if user_recent_post: data['user_recent_post'] = user_recent_post # data['user_recent_post']['url']= user_recent_post[0][0] # data['user_recent_post']['title']= user_recent_post[0][4] self.env.log.debug( "Blog debug: command=%r, pagename=%r, path_items=%r" % ( command, pagename, path_items)) if not command: # Request for just root (display latest) try: page=req.args.get('page') print page except: pass try: page=int(page) except: page=1 if page>1: data['page'] = int(page) else: page = 1 data['page'] = 1 if data['page'] > 1 : data['page_pre4'] = data['page'] - 1 data['page_pre'] = data['page'] -1 if data['page'] > 2 : data['page_pre3'] = data['page'] - 2 if data['page'] > 3 : data['page_pre2'] = data['page'] - 3 if data['page'] > 4 : data['page_pre1'] = data['page'] - 4 data['page_next1'] = data['page'] + 1 data['page_next2'] = data['page'] + 2 data['page_next3'] = data['page'] + 3 data['page_next4'] = data['page'] + 4 data['page_next'] = data['page_next1'] data['blog_post_list'] = [] count = 0 maxcount = self.num_items if page: blog_posts = get_blog_posts(self.env,per_num=maxcount, current_num=(page-1)) else: blog_posts = get_blog_posts(self.env,per_num=maxcount) if not blog_posts: del data['page_next1'],data['page_next2'],data['page_next3'],data['page_next4'],data['page_next'] # data['page_next1'] = data['page_next2'] = data['page_next3'] = data['page_next4'] = data['page_next'] = '' 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_all_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: 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 = Context.from_request(req, the_post.resource) data['context'] = context 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') 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 = Context.from_request(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_ADMIN') if 'blog-cancel' in req.args: req.redirect(req.href.blog(pagename)) comment = int(req.args.get('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 = int(req.args.get('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'] = Context.from_request(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") 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') b=data['blog_categories'] data['blog_categories']=sorted(b, key =lambda b:b[1],reverse=True) return (template, data, None)
req, 'prev', req.href.timeline(from_=previous_start, authors=authors, daysback=daysback), _("Previous Period")) if today - fromdate > timedelta(days=0): next_start = fromdate.replace(tzinfo=None) + \ timedelta(days=daysback + 1) next_start = format_date(to_datetime(next_start, req.tz), format='iso8601', tzinfo=req.tz) add_link( req, 'next', req.href.timeline(from_=next_start, authors=authors, daysback=daysback), _("Next Period")) prevnext_nav(req, _("Previous Period"), _("Next Period")) return 'timeline.html', data, None # ITemplateProvider methods def get_htdocs_dirs(self): return [] def get_templates_dirs(self): return [pkg_resources.resource_filename('trac.timeline', 'templates')] # IRequestFilter methods def pre_process_request(self, req, handler): return handler
def _render_config(self, req, config_name): db = self.env.get_db_cnx() config = BuildConfig.fetch(self.env, config_name, db=db) repos = self.env.get_repository(req.authname) if hasattr(repos, 'sync'): repos.sync() repos.authz.assert_permission(config.path) data = {'title': 'Build Configuration "%s"' \ % config.label or config.name, 'page_mode': 'view_config'} add_link(req, 'up', req.href.build(), 'Build Status') description = config.description if description: description = wiki_to_html(description, self.env, req) pending_builds = list(Build.select(self.env, config=config.name, status=Build.PENDING)) inprogress_builds = list(Build.select(self.env, config=config.name, status=Build.IN_PROGRESS)) data['config'] = { 'name': config.name, 'label': config.label, 'path': config.path, 'min_rev': config.min_rev, 'min_rev_href': req.href.changeset(config.min_rev), 'max_rev': config.max_rev, 'max_rev_href': req.href.changeset(config.max_rev), 'active': config.active, 'description': description, 'browser_href': req.href.browser(config.path), 'builds_pending' : len(pending_builds), 'builds_inprogress' : len(inprogress_builds) } platforms = list(TargetPlatform.select(self.env, config=config_name, db=db)) data['config']['platforms'] = [ { 'name': platform.name, 'id': platform.id, 'builds_pending': len(list(Build.select(self.env, config=config.name, status=Build.PENDING, platform=platform.id))), 'builds_inprogress': len(list(Build.select(self.env, config=config.name, status=Build.IN_PROGRESS, platform=platform.id))) } for platform in platforms ] has_reports = False for report in Report.select(self.env, config=config.name, db=db): has_reports = True break if has_reports: chart_generators = [] for generator in ReportChartController(self.env).generators: for category in generator.get_supported_categories(): chart_generators.append({ 'href': req.href.build(config.name, 'chart/' + category) }) data['config']['charts'] = chart_generators charts_license = self.config.get('bitten', 'charts_license') if charts_license: data['config']['charts_license'] = charts_license page = max(1, int(req.args.get('page', 1))) more = False data['page_number'] = page repos = self.env.get_repository(req.authname) if hasattr(repos, 'sync'): repos.sync() builds_per_page = 12 * len(platforms) idx = 0 builds = {} for platform, rev, build in collect_changes(repos, config): if idx >= page * builds_per_page: more = True break elif idx >= (page - 1) * builds_per_page: builds.setdefault(rev, {}) builds[rev].setdefault('href', req.href.changeset(rev)) if build and build.status != Build.PENDING: build_data = _get_build_data(self.env, req, build) build_data['steps'] = [] for step in BuildStep.select(self.env, build=build.id, db=db): build_data['steps'].append({ 'name': step.name, 'description': step.description, 'duration': datetime.fromtimestamp(step.stopped) - \ datetime.fromtimestamp(step.started), 'failed': not step.successful, 'errors': step.errors, 'href': build_data['href'] + '#step_' + step.name }) builds[rev][platform.id] = build_data idx += 1 data['config']['builds'] = builds if page > 1: if page == 2: prev_href = req.href.build(config.name) else: prev_href = req.href.build(config.name, page=page - 1) add_link(req, 'prev', prev_href, 'Previous Page') if more: next_href = req.href.build(config.name, page=page + 1) add_link(req, 'next', next_href, 'Next Page') prevnext_nav(req, 'Page') return data
def display_html(self, req, query): """returns the HTML according to a query for /hours view""" db = self.env.get_db_cnx() # The most recent query is stored in the user session; orig_list = None orig_time = datetime.now(utc) query_time = int(req.session.get('query_time', 0)) query_time = datetime.fromtimestamp(query_time, utc) query_constraints = unicode(query.constraints) if query_constraints != req.session.get('query_constraints') \ or query_time < orig_time - timedelta(hours=1): tickets = query.execute(req, db) # New or outdated query, (re-)initialize session vars req.session['query_constraints'] = query_constraints req.session['query_tickets'] = ' '.join([str(t['id']) for t in tickets]) else: orig_list = [int(id) for id in req.session.get('query_tickets', '').split()] tickets = query.execute(req, db, orig_list) orig_time = query_time context = Context.from_request(req, 'query') ticket_data = query.template_data(context, tickets, orig_list, orig_time, req) # For clients without JavaScript, we add a new constraint here if # requested constraints = ticket_data['constraints'] if 'add' in req.args: field = req.args.get('add_filter') if field: constraint = constraints.setdefault(field, {}) constraint.setdefault('values', []).append('') # FIXME: '' not always correct (e.g. checkboxes) req.session['query_href'] = query.get_href(context.href) req.session['query_time'] = to_timestamp(orig_time) req.session['query_tickets'] = ' '.join([str(t['id']) for t in tickets]) # data dictionary for genshi data = {} # get data for saved queries query_id = req.args.get('query_id') if query_id: try: query_id = int(query_id) except ValueError: add_warning(req, "query_id should be an integer, you put '%s'" % query_id) query_id = None if query_id: data['query_id'] = query_id query_data = self.get_query(query_id) data['query_title'] = query_data['title'] data['query_description'] = query_data['description'] data.setdefault('report', None) data.setdefault('description', None) data['all_columns'] = query.get_all_columns() + self.get_columns() # Don't allow the user to remove the id column data['all_columns'].remove('id') data['all_textareas'] = query.get_all_textareas() # need to re-get the cols because query will remove our fields cols = req.args.get('col') if isinstance(cols, basestring): cols = [cols] if not cols: cols = query.get_columns() + self.get_default_columns() data['col'] = cols now = datetime.now() # get the date range for the query if 'from_year' in req.args: from_date = get_date(req.args['from_year'], req.args.get('from_month'), req.args.get('from_day')) else: from_date = datetime(now.year, now.month, now.day) from_date = from_date - timedelta(days=7) # 1 week ago, by default if 'to_year' in req.args: to_date = get_date(req.args['to_year'], req.args.get('to_month'), req.args.get('to_day'), end_of_day=True) else: to_date = now data['prev_week'] = from_date - timedelta(days=7) data['months'] = [ (i, calendar.month_name[i]) for i in range(1,13) ] data['years'] = range(now.year, now.year - 10, -1) data['days'] = range(1, 32) data['users'] = get_all_users(self.env) data['cur_worker_filter'] = req.args.get('worker_filter', '*any') data['from_date'] = from_date data['to_date'] = to_date ticket_ids = [t['id'] for t in tickets] # generate data for ticket_times time_records = self.get_ticket_hours(ticket_ids, from_date=from_date, to_date=to_date, worker_filter=data['cur_worker_filter']) data['query'] = ticket_data['query'] data['context'] = ticket_data['context'] data['row'] = ticket_data['row'] if 'comments' in req.args.get('row', []): data['row'].append('comments') data['constraints'] = ticket_data['constraints'] our_labels = dict([(f['name'], f['label']) for f in self.fields]) labels = ticket_data['labels'] labels.update(our_labels) data['labels'] = labels order = req.args.get('order') desc = bool(req.args.get('desc')) data['order'] = order data['desc'] = desc headers = [{'name': col, 'label' : labels.get(col), 'href': self.get_href(query, req.args, context.href, order=col, desc=(col == order and not desc) ) } for col in cols] data['headers'] = headers data['fields'] = ticket_data['fields'] data['modes'] = ticket_data['modes'] # group time records time_records_by_ticket = {} for record in time_records: id = record['ticket'] if id not in time_records_by_ticket: time_records_by_ticket[id] = [] time_records_by_ticket[id].append(record) data['extra_group_fields'] = dict(ticket = dict(name='ticket', type='select', label='Ticket'), worker = dict(name='worker', type='select', label='Worker')) num_items = 0 data['groups'] = [] # merge ticket data into ticket_time records for key, tickets in ticket_data['groups']: ticket_times = [] total_time = 0 total_estimated_time = 0 for ticket in tickets: records = time_records_by_ticket.get(ticket['id'], []) [rec.update(ticket) for rec in records] ticket_times += records # sort ticket_times, if needed if order in our_labels: ticket_times.sort(key=lambda x: x[order], reverse=desc) data['groups'].append((key, ticket_times)) num_items += len(ticket_times) data['double_count_warning'] = '' # group by ticket id or other time_ticket fields if necessary if req.args.get('group') in data['extra_group_fields']: query.group = req.args.get('group') if not query.group == "id": data['double_count_warning'] = "Warning: estimated hours may be counted more than once if a ticket appears in multiple groups" tickets = data['groups'][0][1] groups = {} for time_rec in tickets: key = time_rec[query.group] if not key in groups: groups[key] = [] groups[key].append(time_rec) data['groups'] = sorted(groups.items()) total_times = dict((k, self.format_hours(sum(rec['seconds_worked'] for rec in v))) for k, v in data['groups']) total_estimated_times = {} for key, records in data['groups']: seen_tickets = set() est = 0 for record in records: # do not double-count tickets id = record['ticket'] if id in seen_tickets: continue seen_tickets.add(id) estimatedhours = record.get('estimatedhours') or 0 try: estimatedhours = float(estimatedhours) except ValueError: estimatedhours = 0 est += estimatedhours * 3600 total_estimated_times[key] = self.format_hours(est) data['total_times'] = total_times data['total_estimated_times'] = total_estimated_times # format records for record in time_records: if 'seconds_worked' in record: record['seconds_worked'] = self.format_hours(record['seconds_worked']) # XXX misleading name if 'time_started' in record: record['time_started'] = self.format_date(record['time_started']) if 'time_submitted' in record: record['time_submitted'] = self.format_date(record['time_submitted']) data['query'].num_items = num_items data['labels'] = ticket_data['labels'] data['labels'].update(labels) data['can_add_hours'] = req.perm.has_permission('TICKET_ADD_HOURS') data['multiproject'] = self.env.is_component_enabled(MultiprojectHours) from web_ui import TracUserHours data['user_hours'] = self.env.is_component_enabled(TracUserHours) # return the rss, if requested if req.args.get('format') == 'rss': return self.queryhours2rss(req, data) # return the csv, if requested if req.args.get('format') == 'csv': self.queryhours2csv(req, data) # add rss link rss_href = req.href(req.path_info, format='rss') add_link(req, 'alternate', rss_href, _('RSS Feed'), 'application/rss+xml', 'rss') # add csv link add_link(req, 'alternate', req.href(req.path_info, format='csv', **req.args), 'CSV', 'text/csv', 'csv') # add navigation of weeks prev_args = dict(req.args) next_args = dict(req.args) prev_args['from_year'] = (from_date - timedelta(days=7)).year prev_args['from_month'] = (from_date - timedelta(days=7)).month prev_args['from_day'] = (from_date - timedelta(days=7)).day prev_args['to_year'] = from_date.year prev_args['to_month'] = from_date.month prev_args['to_day'] = from_date.day next_args['from_year'] = to_date.year next_args['from_month'] = to_date.month next_args['from_day'] = to_date.day next_args['to_year'] = (to_date + timedelta(days=7)).year next_args['to_month'] = (to_date + timedelta(days=7)).month next_args['to_day'] = (to_date + timedelta(days=7)).day add_link(req, 'prev', self.get_href(query, prev_args, context.href), _('Prev Week')) add_link(req, 'next', self.get_href(query, next_args, context.href), _('Next Week')) prevnext_nav(req, _('Prev Week'), _('Next Week')) add_ctxtnav(req, 'Cross-Project Hours', req.href.hours('multiproject')) add_ctxtnav(req, 'Hours by User', req.href.hours('user', from_day=from_date.day, from_month=from_date.month, from_year=from_date.year, to_day=to_date.year, to_month=to_date.month, to_year=to_date.year)) add_ctxtnav(req, 'Saved Queries', req.href.hours('query/list')) add_stylesheet(req, 'common/css/report.css') add_script(req, 'common/js/query.js') return ('hours_timeline.html', data, 'text/html')
def _render_view(self, req, page): version = page.resource.version # Add registered converters if page.exists: for conversion in Mimeview(self.env) \ .get_supported_conversions('text/x-trac-wiki'): conversion_href = req.href.wiki(page.name, version=version, format=conversion.key) add_link(req, 'alternate', conversion_href, conversion.name, conversion.in_mimetype) data = self._page_data(req, page) if page.name == self.START_PAGE: data['title'] = '' ws = WikiSystem(self.env) context = web_context(req, page.resource) higher, related = [], [] if not page.exists: if 'WIKI_CREATE' not in req.perm(page.resource): raise ResourceNotFound(_("Page %(name)s not found", name=page.name)) formatter = OneLinerFormatter(self.env, context) if '/' in page.name: parts = page.name.split('/') for i in xrange(len(parts) - 2, -1, -1): name = '/'.join(parts[:i] + [parts[-1]]) if not ws.has_page(name): higher.append(ws._format_link(formatter, 'wiki', '/' + name, name, False)) else: name = page.name name = name.lower() related = [each for each in ws.pages if name in each.lower() and 'WIKI_VIEW' in req.perm(self.realm, each)] related.sort() related = [ws._format_link(formatter, 'wiki', '/' + each, each, False) for each in related] latest_page = WikiPage(self.env, page.name) prev_version = next_version = None if version: version = as_int(version, None) if version is not None: for hist in latest_page.get_history(): v = hist[0] if v != version: if v < version: if not prev_version: prev_version = v break else: next_version = v prefix = self.PAGE_TEMPLATES_PREFIX templates = [template[len(prefix):] for template in ws.get_pages(prefix) if 'WIKI_VIEW' in req.perm(self.realm, template)] # -- prev/up/next links if prev_version: add_link(req, 'prev', req.href.wiki(page.name, version=prev_version), _("Version %(num)s", num=prev_version)) parent = None if version: add_link(req, 'up', req.href.wiki(page.name, version=None), _("View latest version")) elif '/' in page.name: parent = page.name[:page.name.rindex('/')] add_link(req, 'up', req.href.wiki(parent, version=None), _("View parent page")) if next_version: add_link(req, 'next', req.href.wiki(page.name, version=next_version), _('Version %(num)s', num=next_version)) # Add ctxtnav entries if version: prevnext_nav(req, _("Previous Version"), _("Next Version"), _("View Latest Version")) else: if parent: add_ctxtnav(req, _('Up'), req.href.wiki(parent)) self._wiki_ctxtnav(req, page) # Plugin content validation fields = {'text': page.text} for manipulator in self.page_manipulators: manipulator.prepare_wiki_page(req, page, fields) text = fields.get('text', '') data.update({ 'context': context, 'text': text, 'latest_version': latest_page.version, 'attachments': AttachmentModule(self.env).attachment_data(context), 'start_page': self.START_PAGE, 'default_template': self.DEFAULT_PAGE_TEMPLATE, 'templates': templates, 'version': version, 'higher': higher, 'related': related, 'resourcepath_template': 'wiki_page_path.html', 'fullwidth': req.session.get('wiki_fullwidth'), }) add_script(req, 'common/js/wiki.js') return 'wiki_view.html', data
def _render_view(self, req, milestone): milestone_groups = [] available_groups = [] component_group_available = False ticket_fields = TicketSystem(self.env).get_ticket_fields() # collect fields that can be used for grouping for field in ticket_fields: if field['type'] == 'select' and field['name'] != 'milestone' \ or field['name'] in ('owner', 'reporter'): available_groups.append({'name': field['name'], 'label': field['label']}) if field['name'] == 'component': component_group_available = True # determine the field currently used for grouping by = None if component_group_available: by = 'component' elif available_groups: by = available_groups[0]['name'] by = req.args.get('by', by) tickets = get_tickets_for_milestone(self.env, milestone=milestone.name, field=by) tickets = apply_ticket_permissions(self.env, req, tickets) stat = get_ticket_stats(self.stats_provider, tickets) context = web_context(req, milestone.resource) data = { 'context': context, 'milestone': milestone, 'attachments': AttachmentModule(self.env).attachment_data(context), 'available_groups': available_groups, 'grouped_by': by, 'groups': milestone_groups } data.update(milestone_stats_data(self.env, req, stat, milestone.name)) if by: def per_group_stats_data(gstat, group_name): return milestone_stats_data(self.env, req, gstat, milestone.name, by, group_name) milestone_groups.extend( grouped_stats_data(self.env, self.stats_provider, tickets, by, per_group_stats_data)) add_stylesheet(req, 'common/css/roadmap.css') add_script(req, 'common/js/folding.js') def add_milestone_link(rel, milestone): href = req.href.milestone(milestone.name, by=req.args.get('by')) add_link(req, rel, href, _('Milestone "%(name)s"', name=milestone.name)) milestones = [m for m in Milestone.select(self.env) if 'MILESTONE_VIEW' in req.perm(m.resource)] idx = [i for i, m in enumerate(milestones) if m.name == milestone.name] if idx: idx = idx[0] if idx > 0: add_milestone_link('first', milestones[0]) add_milestone_link('prev', milestones[idx - 1]) if idx < len(milestones) - 1: add_milestone_link('next', milestones[idx + 1]) add_milestone_link('last', milestones[-1]) prevnext_nav(req, _('Previous Milestone'), _('Next Milestone'), _('Back to Roadmap')) return 'milestone_view.html', data, None
def process_request(self, req): req.perm('timeline').require('TIMELINE_VIEW') format = req.args.get('format') maxrows = req.args.getint('max', 50 if format == 'rss' else 0) lastvisit = req.session.as_int('timeline.lastvisit', 0) # indication of new events is unchanged when form is updated by user revisit = any(a in req.args for a in ['update', 'from', 'daysback', 'author']) if revisit: lastvisit = req.session.as_int('timeline.nextlastvisit', lastvisit) # Parse the from date and adjust the timestamp to the last second of # the day fromdate = datetime_now(req.tz) today = truncate_datetime(fromdate) yesterday = to_datetime(today.replace(tzinfo=None) - timedelta(days=1), req.tz) precisedate = precision = None if 'from' in req.args: # Acquire from date only from non-blank input reqfromdate = req.args.get('from').strip() if reqfromdate: try: precisedate = user_time(req, parse_date, reqfromdate) except TracError as e: add_warning(req, e) else: fromdate = precisedate.astimezone(req.tz) precision = req.args.get('precision', '') if precision.startswith('second'): precision = timedelta(seconds=1) elif precision.startswith('minute'): precision = timedelta(minutes=1) elif precision.startswith('hour'): precision = timedelta(hours=1) else: precision = None fromdate = to_datetime(datetime(fromdate.year, fromdate.month, fromdate.day, 23, 59, 59, 999999), req.tz) pref = req.session.as_int('timeline.daysback', self.default_daysback) default = 90 if format == 'rss' else pref daysback = req.args.as_int('daysback', default, min=1, max=self.max_daysback) authors = req.args.get('authors') if authors is None and format != 'rss': authors = req.session.get('timeline.authors') authors = (authors or '').strip() data = {'fromdate': fromdate, 'daysback': daysback, 'authors': authors, 'today': today, 'yesterday': yesterday, 'precisedate': precisedate, 'precision': precision, 'events': [], 'filters': [], 'abbreviated_messages': self.abbreviated_messages} available_filters = [] for event_provider in self.event_providers: with component_guard(self.env, req, event_provider): available_filters += (event_provider.get_timeline_filters(req) or []) # check the request or session for enabled filters, or use default filters = [f[0] for f in available_filters if f[0] in req.args] if not filters and format != 'rss': filters = [f[0] for f in available_filters if req.session.as_int('timeline.filter.' + f[0])] if not filters: filters = [f[0] for f in available_filters if len(f) == 2 or f[2]] # save the results of submitting the timeline form to the session if 'update' in req.args: for filter_ in available_filters: key = 'timeline.filter.%s' % filter_[0] if filter_[0] in req.args: req.session[key] = '1' elif key in req.session: del req.session[key] stop = fromdate start = to_datetime(stop.replace(tzinfo=None) - timedelta(days=daysback + 1), req.tz) # create author include and exclude sets include = set() exclude = set() for match in self._authors_pattern.finditer(authors): name = (match.group(2) or match.group(3) or match.group(4)).lower() if match.group(1): exclude.add(name) else: include.add(name) # gather all events for the given period of time events = [] for provider in self.event_providers: with component_guard(self.env, req, provider): for event in provider.get_timeline_events(req, start, stop, filters) or []: author = (event[2] or '').lower() if ((not include or author in include) and author not in exclude): events.append( self._event_data(req, provider, event, lastvisit)) # prepare sorted global list events = sorted(events, key=lambda e: e['datetime'], reverse=True) if maxrows: events = events[:maxrows] data['events'] = events if format == 'rss': rss_context = web_context(req, absurls=True) rss_context.set_hints(wiki_flavor='html', shorten_lines=False) data['context'] = rss_context return 'timeline.rss', data, {'content_type': 'application/rss+xml'} else: req.session.set('timeline.daysback', daysback, self.default_daysback) req.session.set('timeline.authors', authors, '') # store lastvisit if events and not revisit: lastviewed = to_utimestamp(events[0]['datetime']) req.session['timeline.lastvisit'] = max(lastvisit, lastviewed) req.session['timeline.nextlastvisit'] = lastvisit html_context = web_context(req) html_context.set_hints(wiki_flavor='oneliner', shorten_lines=self.abbreviated_messages) data['context'] = html_context add_stylesheet(req, 'common/css/timeline.css') rss_href = req.href.timeline([(f, 'on') for f in filters], daysback=90, max=50, authors=authors, format='rss') add_link(req, 'alternate', auth_link(req, rss_href), _('RSS Feed'), 'application/rss+xml', 'rss') Chrome(self.env).add_jquery_ui(req) for filter_ in available_filters: data['filters'].append({'name': filter_[0], 'label': filter_[1], 'enabled': filter_[0] in filters}) # Navigation to the previous/next period of 'daysback' days previous_start = fromdate.replace(tzinfo=None) - \ timedelta(days=daysback + 1) previous_start = format_date(previous_start, format='iso8601', tzinfo=req.tz) add_link(req, 'prev', req.href.timeline(from_=previous_start, authors=authors, daysback=daysback), _("Previous Period")) if today - fromdate > timedelta(days=0): next_start = fromdate.replace(tzinfo=None) + \ timedelta(days=daysback + 1) next_start = format_date(to_datetime(next_start, req.tz), format='iso8601', tzinfo=req.tz) add_link(req, 'next', req.href.timeline(from_=next_start, authors=authors, daysback=daysback), _("Next Period")) prevnext_nav(req, _("Previous Period"), _("Next Period")) return 'timeline.html', data
def _render_view(self, req, page): version = page.resource.version # Add registered converters if page.exists: for conversion in Mimeview(self.env).get_supported_conversions( 'text/x-trac-wiki'): conversion_href = req.href.wiki(page.name, version=version, format=conversion[0]) # or... conversion_href = get_resource_url(self.env, page.resource, req.href, format=conversion[0]) add_link(req, 'alternate', conversion_href, conversion[1], conversion[3]) data = self._page_data(req, page) if page.name == 'WikiStart': data['title'] = '' ws = WikiSystem(self.env) context = web_context(req, page.resource) higher, related = [], [] if not page.exists: if 'WIKI_CREATE' not in req.perm(page.resource): raise ResourceNotFound(_('Page %(name)s not found', name=page.name)) formatter = OneLinerFormatter(self.env, context) if '/' in page.name: parts = page.name.split('/') for i in range(len(parts) - 2, -1, -1): name = '/'.join(parts[:i] + [parts[-1]]) if not ws.has_page(name): higher.append(ws._format_link(formatter, 'wiki', '/' + name, name, False)) else: name = page.name name = name.lower() related = [each for each in ws.pages if name in each.lower() and 'WIKI_VIEW' in req.perm('wiki', each)] related.sort() related = [ws._format_link(formatter, 'wiki', '/' + each, each, False) for each in related] latest_page = WikiPage(self.env, page.name, version=None) req.perm(latest_page.resource).require('WIKI_VIEW') prev_version = next_version = None if version: try: version = int(version) for hist in latest_page.get_history(): v = hist[0] if v != version: if v < version: if not prev_version: prev_version = v break else: next_version = v except ValueError: version = None prefix = self.PAGE_TEMPLATES_PREFIX templates = [template[len(prefix):] for template in ws.get_pages(prefix) if 'WIKI_VIEW' in req.perm('wiki', template)] # -- prev/up/next links if prev_version: add_link(req, 'prev', req.href.wiki(page.name, version=prev_version), _('Version %(num)s', num=prev_version)) parent = None if version: add_link(req, 'up', req.href.wiki(page.name, version=None), _('View latest version')) elif '/' in page.name: parent = page.name[:page.name.rindex('/')] add_link(req, 'up', req.href.wiki(parent, version=None), _("View parent page")) if next_version: add_link(req, 'next', req.href.wiki(page.name, version=next_version), _('Version %(num)s', num=next_version)) # Add ctxtnav entries if version: prevnext_nav(req, _('Previous Version'), _('Next Version'), _('View Latest Version')) else: if parent: add_ctxtnav(req, _('Up'), req.href.wiki(parent)) self._wiki_ctxtnav(req, page) # Plugin content validation fields = {'text': page.text} for manipulator in self.page_manipulators: manipulator.prepare_wiki_page(req, page, fields) text = fields.get('text', '') data.update({ 'context': context, 'text': text, 'latest_version': latest_page.version, 'attachments': AttachmentModule(self.env).attachment_data(context), 'default_template': self.DEFAULT_PAGE_TEMPLATE, 'templates': templates, 'version': version, 'higher': higher, 'related': related, 'resourcepath_template': 'wiki_page_path.html', }) add_script(req, 'common/js/folding.js') return 'wiki_view.html', data, None
def _render_view(self, req, milestone): milestone_groups = [] available_groups = [] component_group_available = False ticket_fields = TicketSystem(self.env).get_ticket_fields() # collect fields that can be used for grouping for field in ticket_fields: if field['type'] == 'select' and field['name'] != 'milestone' \ or field['name'] in ('owner', 'reporter'): available_groups.append({ 'name': field['name'], 'label': field['label'] }) if field['name'] == 'component': component_group_available = True # determine the field currently used for grouping by = None if component_group_available: by = 'component' elif available_groups: by = available_groups[0]['name'] by = req.args.get('by', by) tickets = get_tickets_for_milestone(self.env, milestone=milestone.name, field=by) tickets = apply_ticket_permissions(self.env, req, tickets) stat = get_ticket_stats(self.stats_provider, tickets) context = web_context(req, milestone.resource) data = { 'context': context, 'milestone': milestone, 'attachments': AttachmentModule(self.env).attachment_data(context), 'available_groups': available_groups, 'grouped_by': by, 'groups': milestone_groups } data.update(milestone_stats_data(self.env, req, stat, milestone.name)) if by: groups = [] for field in ticket_fields: if field['name'] == by: if 'options' in field: groups = field['options'] if field.get('optional'): groups.insert(0, '') else: groups = [ group for group, in self.env.db_query(""" SELECT DISTINCT COALESCE(%s, '') FROM ticket ORDER BY COALESCE(%s, '') """ % (by, by)) ] max_count = 0 group_stats = [] for group in groups: values = (group, ) if group else (None, group) group_tickets = [t for t in tickets if t[by] in values] if not group_tickets: continue gstat = get_ticket_stats(self.stats_provider, group_tickets) if gstat.count > max_count: max_count = gstat.count group_stats.append(gstat) gs_dict = {'name': group} gs_dict.update( milestone_stats_data(self.env, req, gstat, milestone.name, by, group)) milestone_groups.append(gs_dict) for idx, gstat in enumerate(group_stats): gs_dict = milestone_groups[idx] percent = 1.0 if max_count: percent = float(gstat.count) / float(max_count) * 100 gs_dict['percent_of_max_total'] = percent add_stylesheet(req, 'common/css/roadmap.css') add_script(req, 'common/js/folding.js') def add_milestone_link(rel, milestone): href = req.href.milestone(milestone.name, by=req.args.get('by')) add_link(req, rel, href, _('Milestone "%(name)s"', name=milestone.name)) milestones = [ m for m in Milestone.select(self.env) if 'MILESTONE_VIEW' in req.perm(m.resource) ] idx = [i for i, m in enumerate(milestones) if m.name == milestone.name] if idx: idx = idx[0] if idx > 0: add_milestone_link('first', milestones[0]) add_milestone_link('prev', milestones[idx - 1]) if idx < len(milestones) - 1: add_milestone_link('next', milestones[idx + 1]) add_milestone_link('last', milestones[-1]) prevnext_nav(req, _('Previous Milestone'), _('Next Milestone'), _('Back to Roadmap')) return 'milestone_view.html', data, None
def _render_diff(self, req, page): if not page.exists: raise TracError(_("Version %(num)s of page \"%(name)s\" does not " "exist", num=req.args.get('version'), name=page.name)) old_version = req.args.getint('old_version') if old_version: if old_version == page.version: old_version = None elif old_version > page.version: # FIXME: what about reverse diffs? old_version = page.resource.version page = WikiPage(self.env, page.name, old_version) req.perm(page.resource).require('WIKI_VIEW') latest_page = WikiPage(self.env, page.name) req.perm(latest_page.resource).require('WIKI_VIEW') new_version = page.version date = author = comment = None num_changes = 0 prev_version = next_version = None for version, t, a, c in latest_page.get_history(): if version == new_version: date = t author = a or 'anonymous' comment = c or '--' else: if version < new_version: num_changes += 1 if not prev_version: prev_version = version if old_version is None or version == old_version: old_version = version break else: next_version = version if not old_version: old_version = 0 old_page = WikiPage(self.env, page.name, old_version) req.perm(old_page.resource).require('WIKI_VIEW') # -- text diffs old_text = old_page.text.splitlines() new_text = page.text.splitlines() diff_data, changes = self._prepare_diff(req, page, old_text, new_text, old_version, new_version) # -- prev/up/next links if prev_version: add_link(req, 'prev', req.href.wiki(page.name, action='diff', version=prev_version), _("Version %(num)s", num=prev_version)) add_link(req, 'up', req.href.wiki(page.name, action='history'), _('Page history')) if next_version: add_link(req, 'next', req.href.wiki(page.name, action='diff', version=next_version), _("Version %(num)s", num=next_version)) data = self._page_data(req, page, 'diff') data.update({ 'change': {'date': date, 'author': author, 'comment': comment}, 'new_version': new_version, 'old_version': old_version, 'latest_version': latest_page.version, 'num_changes': num_changes, 'longcol': 'Version', 'shortcol': 'v', 'changes': changes, 'diff': diff_data, 'can_edit_comment': 'WIKI_ADMIN' in req.perm(page.resource), }) prevnext_nav(req, _("Previous Change"), _("Next Change"), _("Wiki History")) return 'wiki_diff.html', data
def process_request(self, req): offset = req.args.get("offset",0) page = req.args.get('page', 1) try: offset = int(offset) except: raise TracError(_('Invalid offset used: %(offset)s', offset=offset)) try: page = int(page) except: raise TracError(_('Invalid page used: %(page)s', page=page)) offset = (page - 1) * self.limit add_stylesheet(req, 'mailinglist/css/mailinglist.css') add_javascript(req, 'mailinglist/mailinglist.js') mailinglists = [m for m in Mailinglist.select(self.env) if "MAILINGLIST_VIEW" in req.perm(m.resource)] data = {"mailinglists": mailinglists, "offset": offset, "limit": self.limit} if req.method == 'POST': if 'subscribe' in req.args: subscribe = True unsubscribe = False mailinglist_email = req.args.get('subscribe') elif 'unsubscribe' in req.args: subscribe = False unsubscribe = True mailinglist_email = req.args.get('unsubscribe') else: # at the moment we only post subscription info to # mailing list page - so if there is none in req.args we # can just redirect to mailing list page req.redirect(req.href.mailinglist()) # get mailing list object and check permissions mailinglist = Mailinglist.select_by_address(self.env, mailinglist_email, localpart=True) req.perm(mailinglist.resource).require("MAILINGLIST_VIEW") if subscribe: mailinglist.subscribe(user=req.authname) # subscribe does not return a value to indicate if it # was successful, so we have to explicitly check if mailinglist.is_subscribed(req.authname): add_notice(req, _('You have been subscribed to %s.' % mailinglist.name)) else: add_notice(req, _('Unable to subscribe to %s.' % mailinglist.name)) elif unsubscribe: mailinglist.unsubscribe(user=req.authname) # unsubscribe does not return a value to indicate if it # was successful, so we have to explicitly check if not mailinglist.is_subscribed(req.authname): add_notice(req, _('You have been unsubscribed from %s.' % mailinglist.name)) else: add_notice(req, _('Unable to unsubscribe from %s.' % mailinglist.name)) if req.path_info.endswith('/mailinglist'): # overview mailing list page req.redirect(req.href.mailinglist()) elif 'conversationid' in req.args: # individual mailing list conversation log req.redirect(req.href.mailinglist(mailinglist_email, req.args['conversationid'])) else: # individual mailing list homepage req.redirect(req.href.mailinglist(mailinglist_email)) #for mailinglist in mailinglists: # add_ctxtnav(req, # _("List: %s") % mailinglist.name, # req.href.mailinglist(mailinglist.emailaddress)) if 'messageid' in req.args: message = MailinglistMessage(self.env, req.args['messageid']) # leaks the subject of the email in the error, wonder if # that's a problem... req.perm(message.resource).require("MAILINGLIST_VIEW") if req.args.get('format') == "raw": req.send_header('Content-Disposition', 'attachment') req.send_response(200) content = message.raw.bytes req.send_header('Content-Type', 'application/mbox') req.send_header('Content-Length', len(content)) req.end_headers() if req.method != 'HEAD': req.write(content) return context = Context.from_request(req, message.resource) data['message'] = message data['attachments'] = AttachmentModule(self.env).attachment_data(context) add_link(req, 'up', get_resource_url(self.env, message.conversation.resource, req.href, offset=data['offset']), _("Back to conversation")) prevnext_nav(req, _("Newer message"), _("Older message"), _("Back to conversation")) raw_href = get_resource_url(self.env, message.resource, req.href, format='raw') add_link(req, 'alternate', raw_href, _('mbox'), "application/mbox") if 'MAILINGLIST_ADMIN' in req.perm: add_ctxtnav(req, tag.a(tag.i(class_="fa fa-cog"), ' Manage List', href=req.href.admin('mailinglist', 'lists', message.conversation.mailinglist.emailaddress), title='Manage and subscribe users to the %s mailing list' % message.conversation.mailinglist.name)) return 'mailinglist_message.html', data, None if 'conversationid' in req.args: conversation = MailinglistConversation(self.env, req.args['conversationid']) # also leaks the subject of the first email in the error message req.perm(conversation.resource).require("MAILINGLIST_VIEW") data['conversation'] = conversation data['attachmentselect'] = partial(Attachment.select, self.env) results = Paginator(conversation.messages(), page - 1, self.limit) if results.has_next_page: next_href = get_resource_url(self.env, conversation.resource, req.href, page=page + 1) add_link(req, 'next', next_href, _('Next Page')) if results.has_previous_page: prev_href = get_resource_url(self.env, conversation.resource, req.href, page=page - 1) add_link(req, 'prev', prev_href, _('Previous Page')) shown_pages = results.get_shown_pages() pagedata = [{'href': get_resource_url(self.env, conversation.resource, req.href, page=page), 'class': None, 'string': str(page), 'title': _('Page %(num)d', num=page)} for page in shown_pages] results.shown_pages = pagedata results.current_page = {'href': None, 'class': 'current', 'string': str(results.page + 1), 'title': None} data['paginator'] = results add_link(req, 'up', get_resource_url(self.env, conversation.mailinglist.resource, req.href, offset=data['offset']), _("List of conversations")) prevnext_nav(req, _("Newer conversation"), _("Older conversation"), _("Back to list of conversations")) if 'MAILINGLIST_ADMIN' in req.perm: add_ctxtnav(req, tag.a(tag.i(class_="fa fa-cog"), ' Manage List', href=req.href.admin('mailinglist', 'lists', conversation.mailinglist.emailaddress), title='Manage and subscribe users to the %s mailing list' % conversation.mailinglist.name)) # Check if user is already subscribed to mailing list # and add the appropriate subscribe / unsubscribe ribbon option if conversation.mailinglist.is_subscribed(req.authname): add_ctxtnav(req, tag.form(tag.input(tag.a(tag.i(class_='fa fa-eye-slash'), ' Unsubscribe', title='Unsubscribe from the %s mailing list' % conversation.mailinglist.name, id='subscribe-link'), name='unsubscribe', value=conversation.mailinglist.emailaddress, class_='hidden'), method_='post', action='', id='subscribe-form', class_='hidden')) else: add_ctxtnav(req, tag.form(tag.input(tag.a(tag.i(class_='fa fa-eye'), ' Subscribe', title='Subscribe to the %s mailing list' % conversation.mailinglist.name, id='subscribe-link'), name='subscribe', value=conversation.mailinglist.emailaddress, class_='hidden'), method_='post', action='', id='subscribe-form', class_='hidden')) return 'mailinglist_conversation.html', data, None elif 'listname' in req.args: mailinglist = Mailinglist.select_by_address(self.env, req.args['listname'], localpart=True) # leaks the name of the mailinglist req.perm(mailinglist.resource).require("MAILINGLIST_VIEW") data['mailinglist'] = mailinglist results = Paginator(mailinglist.conversations(), page - 1, self.limit) if results.has_next_page: next_href = get_resource_url(self.env, mailinglist.resource, req.href, page=page + 1) add_link(req, 'next', next_href, _('Next Page')) if results.has_previous_page: prev_href = get_resource_url(self.env, mailinglist.resource, req.href, page=page - 1) add_link(req, 'prev', prev_href, _('Previous Page')) shown_pages = results.get_shown_pages() pagedata = [{'href': get_resource_url(self.env, mailinglist.resource, req.href, page=page), 'class': None, 'string': str(page), 'title': _('Page %(num)d', num=page)} for page in shown_pages] results.shown_pages = pagedata results.current_page = {'href': None, 'class': 'current', 'string': str(results.page + 1), 'title': None} data['paginator'] = results if data['offset'] + data['limit'] < mailinglist.count_conversations(): add_link(req, 'next', get_resource_url(self.env, mailinglist.resource, req.href, offset=data['offset']+data['limit']), _("Older conversations")) if offset > 0: add_link(req, 'prev', get_resource_url(self.env, mailinglist.resource, req.href, offset=data['offset']-data['limit']), _("Newer conversations")) add_link(req, 'up', req.href.mailinglist(), _("List of mailinglists")) prevnext_nav(req, _("Newer conversations"), _("Older conversations"), ("Back to Mailinglists")) if 'MAILINGLIST_ADMIN' in req.perm: add_ctxtnav(req, tag.a(tag.i(class_="fa fa-cog"), ' Manage List', href=req.href.admin('mailinglist', 'lists', mailinglist.emailaddress), title='Manage and subscribe users to the %s mailing list' % mailinglist.name)) # Check if user is already subscribed to mailing list # and add the appropriate subscribe / unsubscribe ribbon option if mailinglist.is_subscribed(req.authname): add_ctxtnav(req, tag.form(tag.input(tag.a(tag.i(class_='fa fa-eye-slash'), ' Unsubscribe', title='Unsubscribe from the %s mailing list' % mailinglist.name, id='subscribe-link'), name='unsubscribe', value=mailinglist.emailaddress, class_='hidden'), method_='post', action='', id='subscribe-form', class_='hidden')) else: add_ctxtnav(req, tag.form(tag.input(tag.a(tag.i(class_='fa fa-eye'), ' Subscribe', title='Subscribe to the %s mailing list' % mailinglist.name, id='subscribe-link'), name='subscribe', value=mailinglist.emailaddress, class_='hidden'), method_='post', action='', id='subscribe-form', class_='hidden')) return 'mailinglist_conversations.html', data, None else: return 'mailinglist_list.html', data, None
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() try: version = int(req.args.get('version', 0)) except: 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.env.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: 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 = int(req.args.get('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 = int(req.args.get('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") 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, None)
def process_request(self, req): presel = req.args.get('preselected') if presel and (presel + '/').startswith(req.href.browser() + '/'): req.redirect(presel) path = req.args.get('path', '/') rev = req.args.get('rev', '') if rev.lower() in ('', 'head'): rev = None format = req.args.get('format') order = req.args.get('order', 'name').lower() desc = 'desc' in req.args rm = RepositoryManager(self.env) all_repositories = rm.get_all_repositories() reponame, repos, path = rm.get_repository_by_path(path) # Repository index show_index = not reponame and path == '/' if show_index: if repos and (as_bool(all_repositories[''].get('hidden')) or not repos.is_viewable(req.perm)): repos = None if not repos and reponame: raise ResourceNotFound(_("Repository '%(repo)s' not found", repo=reponame)) if reponame and reponame != repos.reponame: # Redirect alias qs = req.query_string req.redirect(req.href.browser(repos.reponame or None, path) + ('?' + qs if qs else '')) reponame = repos.reponame if repos else None # Find node for the requested path/rev context = web_context(req) node = None changeset = None display_rev = lambda rev: rev if repos: try: if rev: rev = repos.normalize_rev(rev) # If `rev` is `None`, we'll try to reuse `None` consistently, # as a special shortcut to the latest revision. rev_or_latest = rev or repos.youngest_rev node = get_existing_node(req, repos, path, rev_or_latest) except NoSuchChangeset as e: raise ResourceNotFound(e, _('Invalid changeset number')) if node: try: # use changeset instance to retrieve branches and tags changeset = repos.get_changeset(node.rev) except NoSuchChangeset: pass context = context.child(repos.resource.child(self.realm, path, version=rev_or_latest)) display_rev = repos.display_rev # Prepare template data path_links = get_path_links(req.href, reponame, path, rev, order, desc) repo_data = dir_data = file_data = None if show_index: repo_data = self._render_repository_index( context, all_repositories, order, desc) if node: if not node.is_viewable(req.perm): raise PermissionError('BROWSER_VIEW' if node.isdir else 'FILE_VIEW', node.resource, self.env) if node.isdir: if format in ('zip',): # extension point here... self._render_zip(req, context, repos, node, rev) # not reached dir_data = self._render_dir(req, repos, node, rev, order, desc) elif node.isfile: file_data = self._render_file(req, context, repos, node, rev) if not repos and not (repo_data and repo_data['repositories']): # If no viewable repositories, check permission instead of # repos.is_viewable() req.perm.require('BROWSER_VIEW') if show_index: raise ResourceNotFound(_("No viewable repositories")) else: raise ResourceNotFound(_("No node %(path)s", path=path)) quickjump_data = properties_data = None if node and not req.is_xhr: properties_data = self.render_properties( 'browser', context, node.get_properties()) quickjump_data = list(repos.get_quickjump_entries(rev)) data = { 'context': context, 'reponame': reponame, 'repos': repos, 'repoinfo': all_repositories.get(reponame or ''), 'path': path, 'rev': node and node.rev, 'stickyrev': rev, 'display_rev': display_rev, 'changeset': changeset, 'created_path': node and node.created_path, 'created_rev': node and node.created_rev, 'properties': properties_data, 'path_links': path_links, 'order': order, 'desc': 1 if desc else None, 'repo': repo_data, 'dir': dir_data, 'file': file_data, 'quickjump_entries': quickjump_data, 'wiki_format_messages': self.config['changeset'].getbool('wiki_format_messages'), } if req.is_xhr: # render and return the content only return 'dir_entries.html', data if dir_data or repo_data: add_script(req, 'common/js/expand_dir.js') add_script(req, 'common/js/keyboard_nav.js') # Links for contextual navigation if node: if node.isfile: prev_rev = repos.previous_rev(rev=node.created_rev, path=node.created_path) if prev_rev: href = req.href.browser(reponame, node.created_path, rev=prev_rev) add_link(req, 'prev', href, _('Revision %(num)s', num=display_rev(prev_rev))) if rev is not None: add_link(req, 'up', req.href.browser(reponame, node.created_path)) next_rev = repos.next_rev(rev=node.created_rev, path=node.created_path) if next_rev: href = req.href.browser(reponame, node.created_path, rev=next_rev) add_link(req, 'next', href, _('Revision %(num)s', num=display_rev(next_rev))) prevnext_nav(req, _('Previous Revision'), _('Next Revision'), _('Latest Revision')) else: if path != '/': add_link(req, 'up', path_links[-2]['href'], _('Parent directory')) add_ctxtnav(req, tag.a(_('Last Change'), href=req.href.changeset(node.created_rev, reponame, node.created_path))) if node.isfile: annotate = data['file']['annotate'] if annotate: add_ctxtnav(req, _('Normal'), title=_('View file without annotations'), href=req.href.browser(reponame, node.created_path, rev=rev)) if annotate != 'blame': add_ctxtnav(req, _('Blame'), title=_('Annotate each line with the last ' 'changed revision ' '(this can be time consuming...)'), href=req.href.browser(reponame, node.created_path, rev=rev, annotate='blame')) add_ctxtnav(req, _('Revision Log'), href=req.href.log(reponame, path, rev=rev)) path_url = repos.get_path_url(path, rev) if path_url: if path_url.startswith('//'): path_url = req.scheme + ':' + path_url add_ctxtnav(req, _('Repository URL'), href=path_url) add_stylesheet(req, 'common/css/browser.css') return 'browser.html', data
class TimelineModule(Component): implements(INavigationContributor, IPermissionRequestor, IRequestHandler, IRequestFilter, ITemplateProvider, IWikiSyntaxProvider) event_providers = ExtensionPoint(ITimelineEventProvider) default_daysback = IntOption( 'timeline', 'default_daysback', 30, """Default number of days displayed in the Timeline, in days. (''since 0.9.'')""") max_daysback = IntOption( 'timeline', 'max_daysback', 90, """Maximum number of days (-1 for unlimited) displayable in the Timeline. (''since 0.11'')""") abbreviated_messages = BoolOption( 'timeline', 'abbreviated_messages', True, """Whether wiki-formatted event messages should be truncated or not. This only affects the default rendering, and can be overriden by specific event providers, see their own documentation. (''Since 0.11'')""") _authors_pattern = re.compile(r'(-)?(?:"([^"]*)"|\'([^\']*)\'|([^\s]+))') # INavigationContributor methods def get_active_navigation_item(self, req): return 'timeline' def get_navigation_items(self, req): if 'TIMELINE_VIEW' in req.perm: yield ('mainnav', 'timeline', tag.a(_('Timeline'), href=req.href.timeline(), accesskey=2)) # IPermissionRequestor methods def get_permission_actions(self): return ['TIMELINE_VIEW'] # IRequestHandler methods def match_request(self, req): return req.path_info == '/timeline' def process_request(self, req): req.perm.assert_permission('TIMELINE_VIEW') format = req.args.get('format') maxrows = int(req.args.get('max', format == 'rss' and 50 or 0)) # Parse the from date and adjust the timestamp to the last second of # the day fromdate = today = datetime.now(req.tz) precisedate = precision = None if 'from' in req.args: # Acquire from date only from non-blank input reqfromdate = req.args['from'].strip() if reqfromdate: precisedate = parse_date(reqfromdate, req.tz) fromdate = precisedate precision = req.args.get('precision', '') if precision.startswith('second'): precision = timedelta(seconds=1) elif precision.startswith('minute'): precision = timedelta(minutes=1) elif precision.startswith('hour'): precision = timedelta(hours=1) else: precision = None fromdate = fromdate.replace(hour=23, minute=59, second=59, microsecond=999999) daysback = as_int(req.args.get('daysback'), format == 'rss' and 90 or None) if daysback is None: daysback = as_int(req.session.get('timeline.daysback'), None) if daysback is None: daysback = self.default_daysback daysback = max(0, daysback) if self.max_daysback >= 0: daysback = min(self.max_daysback, daysback) authors = req.args.get('authors') if authors is None and format != 'rss': authors = req.session.get('timeline.authors') authors = (authors or '').strip() data = { 'fromdate': fromdate, 'daysback': daysback, 'authors': authors, 'today': format_date(today, tzinfo=req.tz), 'yesterday': format_date(today - timedelta(days=1), tzinfo=req.tz), 'precisedate': precisedate, 'precision': precision, 'events': [], 'filters': [], 'abbreviated_messages': self.abbreviated_messages } available_filters = [] for event_provider in self.event_providers: available_filters += event_provider.get_timeline_filters(req) or [] # check the request or session for enabled filters, or use default filters = [f[0] for f in available_filters if f[0] in req.args] if not filters and format != 'rss': filters = [ f[0] for f in available_filters if req.session.get('timeline.filter.' + f[0]) == '1' ] if not filters: filters = [f[0] for f in available_filters if len(f) == 2 or f[2]] # save the results of submitting the timeline form to the session if 'update' in req.args: for filter in available_filters: key = 'timeline.filter.%s' % filter[0] if filter[0] in req.args: req.session[key] = '1' elif key in req.session: del req.session[key] stop = fromdate start = stop - timedelta(days=daysback + 1) # create author include and exclude sets include = set() exclude = set() for match in self._authors_pattern.finditer(authors): name = (match.group(2) or match.group(3) or match.group(4)).lower() if match.group(1): exclude.add(name) else: include.add(name) # gather all events for the given period of time events = [] for provider in self.event_providers: try: for event in provider.get_timeline_events( req, start, stop, filters) or []: # Check for 0.10 events author = (event[len(event) < 6 and 2 or 4] or '').lower() if (not include or author in include) \ and not author in exclude: events.append(self._event_data(provider, event)) except Exception, e: # cope with a failure of that provider self._provider_failure(e, req, provider, filters, [f[0] for f in available_filters]) # prepare sorted global list events = sorted(events, key=lambda e: e['date'], reverse=True) if maxrows: events = events[:maxrows] data['events'] = events if format == 'rss': data['email_map'] = Chrome(self.env).get_email_map() rss_context = Context.from_request(req, absurls=True) rss_context.set_hints(wiki_flavor='html', shorten_lines=False) data['context'] = rss_context return 'timeline.rss', data, 'application/rss+xml' else: req.session['timeline.daysback'] = daysback req.session['timeline.authors'] = authors html_context = Context.from_request(req) html_context.set_hints(wiki_flavor='oneliner', shorten_lines=self.abbreviated_messages) data['context'] = html_context add_stylesheet(req, 'common/css/timeline.css') rss_href = req.href.timeline([(f, 'on') for f in filters], daysback=90, max=50, authors=authors, format='rss') add_link(req, 'alternate', rss_href, _('RSS Feed'), 'application/rss+xml', 'rss') for filter_ in available_filters: data['filters'].append({ 'name': filter_[0], 'label': filter_[1], 'enabled': filter_[0] in filters }) # Navigation to the previous/next period of 'daysback' days previous_start = format_date(fromdate - timedelta(days=daysback + 1), format='%Y-%m-%d', tzinfo=req.tz) add_link( req, 'prev', req.href.timeline(from_=previous_start, authors=authors, daysback=daysback), _('Previous Period')) if today - fromdate > timedelta(days=0): next_start = format_date(fromdate + timedelta(days=daysback + 1), format='%Y-%m-%d', tzinfo=req.tz) add_link( req, 'next', req.href.timeline(from_=next_start, authors=authors, daysback=daysback), _('Next Period')) prevnext_nav(req, _('Previous Period'), _('Next Period')) return 'timeline.html', data, None
def _render_config(self, req, config_name): db = self.env.get_db_cnx() config = BuildConfig.fetch(self.env, config_name, db=db) if not config: raise HTTPNotFound("Build configuration '%s' does not exist." \ % config_name) repos = self.env.get_repository(req.authname) repos.authz.assert_permission(config.branch) data = {'title': 'Build Configuration "%s"' \ % config.label or config.name, 'page_mode': 'view_config'} add_link(req, 'up', req.href.build(), 'Build Status') description = config.description if description: description = wiki_to_html(description, self.env, req) pending_builds = list( Build.select(self.env, config=config.name, status=Build.PENDING)) inprogress_builds = list( Build.select(self.env, config=config.name, status=Build.IN_PROGRESS)) rev = '' for b in repos.git.get_branches(): if b[0] == config.branch: rev = b[1] break data['config'] = { 'name': config.name, 'label': config.label, 'branch': config.branch, 'active': config.active, 'description': description, 'browser_href': req.href.browser(rev=rev), 'builds_pending': len(pending_builds), 'builds_inprogress': len(inprogress_builds) } context = Context.from_request(req, config.resource) data['context'] = context data['config']['attachments'] = AttachmentModule( self.env).attachment_data(context) platforms = list( TargetPlatform.select(self.env, config=config_name, db=db)) data['config']['platforms'] = [{ 'name': platform.name, 'id': platform.id, 'builds_pending': len( list( Build.select(self.env, config=config.name, status=Build.PENDING, platform=platform.id))), 'builds_inprogress': len( list( Build.select(self.env, config=config.name, status=Build.IN_PROGRESS, platform=platform.id))) } for platform in platforms] has_reports = False for report in Report.select(self.env, config=config.name, db=db): has_reports = True break if has_reports: chart_generators = [] for generator in ReportChartController(self.env).generators: for category in generator.get_supported_categories(): chart_generators.append({ 'href': req.href.build(config.name, 'chart/' + category) }) data['config']['charts'] = chart_generators charts_license = self.config.get('bitten', 'charts_license') if charts_license: data['config']['charts_license'] = charts_license page = max(1, int(req.args.get('page', 1))) more = False data['page_number'] = page repos = self.env.get_repository(req.authname) builds_per_page = 12 * len(platforms) idx = 0 builds = {} for platform, rev, build in collect_changes(repos, config): if idx >= page * builds_per_page: more = True break elif idx >= (page - 1) * builds_per_page: builds.setdefault(rev, {}) builds[rev].setdefault('href', req.href.changeset(rev)) if build and build.status != Build.PENDING: build_data = _get_build_data(self.env, req, build) build_data['steps'] = [] for step in BuildStep.select(self.env, build=build.id, db=db): build_data['steps'].append({ 'name': step.name, 'description': step.description, 'duration': to_datetime(step.stopped, utc) - \ to_datetime(step.started, utc), 'failed': not step.successful, 'errors': step.errors, 'href': build_data['href'] + '#step_' + step.name }) builds[rev][platform.id] = build_data idx += 1 data['config']['builds'] = builds if page > 1: if page == 2: prev_href = req.href.build(config.name) else: prev_href = req.href.build(config.name, page=page - 1) add_link(req, 'prev', prev_href, 'Previous Page') if more: next_href = req.href.build(config.name, page=page + 1) add_link(req, 'next', next_href, 'Next Page') prevnext_nav(req, 'Page') return data
class BrowserModule(Component): implements(INavigationContributor, IPermissionRequestor, IRequestHandler, IWikiSyntaxProvider, IHTMLPreviewAnnotator, IWikiMacroProvider) property_renderers = ExtensionPoint(IPropertyRenderer) downloadable_paths = ListOption('browser', 'downloadable_paths', '/trunk, /branches/*, /tags/*', doc="""List of repository paths that can be downloaded. Leave this option empty if you want to disable all downloads, otherwise set it to a comma-separated list of authorized paths (those paths are glob patterns, i.e. "*" can be used as a wild card). In a multi-repository environment, the path must be qualified with the repository name if the path does not point to the default repository (e.g. /reponame/trunk). Note that a simple prefix matching is performed on the paths, so aliases won't get automatically resolved. (''since 0.10'')""") color_scale = BoolOption('browser', 'color_scale', True, doc="""Enable colorization of the ''age'' column. This uses the same color scale as the source code annotation: blue is older, red is newer. (''since 0.11'')""") NEWEST_COLOR = (255, 136, 136) newest_color = Option('browser', 'newest_color', repr(NEWEST_COLOR), doc="""(r,g,b) color triple to use for the color corresponding to the newest color, for the color scale used in ''blame'' or the browser ''age'' column if `color_scale` is enabled. (''since 0.11'')""") OLDEST_COLOR = (136, 136, 255) oldest_color = Option('browser', 'oldest_color', repr(OLDEST_COLOR), doc="""(r,g,b) color triple to use for the color corresponding to the oldest color, for the color scale used in ''blame'' or the browser ''age'' column if `color_scale` is enabled. (''since 0.11'')""") intermediate_point = Option('browser', 'intermediate_point', '', doc="""If set to a value between 0 and 1 (exclusive), this will be the point chosen to set the `intermediate_color` for interpolating the color value. (''since 0.11'')""") intermediate_color = Option('browser', 'intermediate_color', '', doc="""(r,g,b) color triple to use for the color corresponding to the intermediate color, if two linear interpolations are used for the color scale (see `intermediate_point`). If not set, the intermediate color between `oldest_color` and `newest_color` will be used. (''since 0.11'')""") render_unsafe_content = BoolOption('browser', 'render_unsafe_content', 'false', """Whether raw files should be rendered in the browser, or only made downloadable. Pretty much any file may be interpreted as HTML by the browser, which allows a malicious user to create a file containing cross-site scripting attacks. For open repositories where anyone can check-in a file, it is recommended to leave this option disabled (which is the default).""") hidden_properties = ListOption('browser', 'hide_properties', 'svk:merge', doc="""Comma-separated list of version control properties to hide from the repository browser. (''since 0.9'')""") # public methods def get_custom_colorizer(self): """Returns a converter for values from [0.0, 1.0] to a RGB triple.""" def interpolate(old, new, value): # Provides a linearly interpolated color triple for `value` # which must be a floating point value between 0.0 and 1.0 return tuple([int(b + (a - b) * value) for a, b in zip(new, old)]) def parse_color(rgb, default): # Get three ints out of a `rgb` string or return `default` try: t = tuple([int(v) for v in re.split(r'(\d+)', rgb)[1::2]]) return t if len(t) == 3 else default except ValueError: return default newest_color = parse_color(self.newest_color, self.NEWEST_COLOR) oldest_color = parse_color(self.oldest_color, self.OLDEST_COLOR) try: intermediate = float(self.intermediate_point) except ValueError: intermediate = None if intermediate: intermediate_color = parse_color(self.intermediate_color, None) if not intermediate_color: intermediate_color = tuple([(a + b) / 2 for a, b in zip(newest_color, oldest_color)]) def colorizer(value): if value <= intermediate: value = value / intermediate return interpolate(oldest_color, intermediate_color, value) else: value = (value - intermediate) / (1.0 - intermediate) return interpolate(intermediate_color, newest_color, value) else: def colorizer(value): return interpolate(oldest_color, newest_color, value) return colorizer # INavigationContributor methods def get_active_navigation_item(self, req): return 'browser' def get_navigation_items(self, req): rm = RepositoryManager(self.env) if 'BROWSER_VIEW' in req.perm and rm.get_real_repositories(): yield ('mainnav', 'browser', tag.a(_('Browse Source'), href=req.href.browser())) # IPermissionRequestor methods def get_permission_actions(self): return ['BROWSER_VIEW', 'FILE_VIEW'] # IRequestHandler methods def match_request(self, req): match = re.match(r'/(export|browser|file)(/.*)?$', req.path_info) if match: mode, path = match.groups() if mode == 'export': if path and '/' in path: path_elts = path.split('/', 2) if len(path_elts) != 3: return False path = path_elts[2] req.args['rev'] = path_elts[1] req.args['format'] = 'raw' elif mode == 'file': req.redirect(req.href.browser(path, rev=req.args.get('rev'), format=req.args.get('format')), permanent=True) req.args['path'] = path or '/' return True def process_request(self, req): req.perm.require('BROWSER_VIEW') presel = req.args.get('preselected') if presel and (presel + '/').startswith(req.href.browser() + '/'): req.redirect(presel) path = req.args.get('path', '/') rev = req.args.get('rev', '') if rev.lower() in ('', 'head'): rev = None order = req.args.get('order', 'name').lower() desc = req.args.has_key('desc') xhr = req.get_header('X-Requested-With') == 'XMLHttpRequest' rm = RepositoryManager(self.env) all_repositories = rm.get_all_repositories() reponame, repos, path = rm.get_repository_by_path(path) # Repository index show_index = not reponame and path == '/' if show_index: if repos and (as_bool(all_repositories[''].get('hidden')) or not repos.is_viewable(req.perm)): repos = None if not repos and reponame: raise ResourceNotFound(_("Repository '%(repo)s' not found", repo=reponame)) if reponame and reponame != repos.reponame: # Redirect alias qs = req.query_string req.redirect(req.href.browser(repos.reponame or None, path) + ('?' + qs if qs else '')) reponame = repos.reponame if repos else None # Find node for the requested path/rev context = web_context(req) node = None display_rev = lambda rev: rev if repos: try: if rev: rev = repos.normalize_rev(rev) # If `rev` is `None`, we'll try to reuse `None` consistently, # as a special shortcut to the latest revision. rev_or_latest = rev or repos.youngest_rev node = get_existing_node(req, repos, path, rev_or_latest) except NoSuchChangeset, e: raise ResourceNotFound(e.message, _('Invalid changeset number')) context = context.child(repos.resource.child('source', path, version=rev_or_latest)) display_rev = repos.display_rev # Prepare template data path_links = get_path_links(req.href, reponame, path, rev, order, desc) repo_data = dir_data = file_data = None if show_index: repo_data = self._render_repository_index( context, all_repositories, order, desc) if node: if node.isdir: dir_data = self._render_dir(req, repos, node, rev, order, desc) elif node.isfile: file_data = self._render_file(req, context, repos, node, rev) if not repos and not (repo_data and repo_data['repositories']): raise ResourceNotFound(_("No node %(path)s", path=path)) quickjump_data = properties_data = None if node and not xhr: properties_data = self.render_properties( 'browser', context, node.get_properties()) quickjump_data = list(repos.get_quickjump_entries(rev)) data = { 'context': context, 'reponame': reponame, 'repos': repos, 'repoinfo': all_repositories.get(reponame or ''), 'path': path, 'rev': node and node.rev, 'stickyrev': rev, 'display_rev': display_rev, 'created_path': node and node.created_path, 'created_rev': node and node.created_rev, 'properties': properties_data, 'path_links': path_links, 'order': order, 'desc': 1 if desc else None, 'repo': repo_data, 'dir': dir_data, 'file': file_data, 'quickjump_entries': quickjump_data, 'wiki_format_messages': \ self.config['changeset'].getbool('wiki_format_messages'), 'xhr': xhr, } if xhr: # render and return the content only return 'dir_entries.html', data, None if dir_data or repo_data: add_script(req, 'common/js/expand_dir.js') add_script(req, 'common/js/keyboard_nav.js') # Links for contextual navigation if node: if node.isfile: prev_rev = repos.previous_rev(rev=node.created_rev, path=node.created_path) if prev_rev: href = req.href.browser(reponame, node.created_path, rev=prev_rev) add_link(req, 'prev', href, _('Revision %(num)s', num=display_rev(prev_rev))) if rev is not None: add_link(req, 'up', req.href.browser(reponame, node.created_path)) next_rev = repos.next_rev(rev=node.created_rev, path=node.created_path) if next_rev: href = req.href.browser(reponame, node.created_path, rev=next_rev) add_link(req, 'next', href, _('Revision %(num)s', num=display_rev(next_rev))) prevnext_nav(req, _('Previous Revision'), _('Next Revision'), _('Latest Revision')) else: if path != '/': add_link(req, 'up', path_links[-2]['href'], _('Parent directory')) add_ctxtnav(req, tag.a(_('Last Change'), href=req.href.changeset(node.created_rev, reponame, node.created_path))) if node.isfile: annotate = data['file']['annotate'] if annotate: add_ctxtnav(req, _('Normal'), title=_('View file without annotations'), href=req.href.browser(reponame, node.created_path, rev=rev)) if annotate != 'blame': add_ctxtnav(req, _('Blame'), title=_('Annotate each line with the last ' 'changed revision ' '(this can be time consuming...)'), href=req.href.browser(reponame, node.created_path, rev=rev, annotate='blame')) add_ctxtnav(req, _('Revision Log'), href=req.href.log(reponame, path, rev=rev)) path_url = repos.get_path_url(path, rev) if path_url: if path_url.startswith('//'): path_url = req.scheme + ':' + path_url add_ctxtnav(req, _('Repository URL'), href=path_url) add_stylesheet(req, 'common/css/browser.css') return 'browser.html', data, None
def display_html(self, req, query): """returns the HTML according to a query for /hours view""" db = self.env.get_db_cnx() # The most recent query is stored in the user session; orig_list = None orig_time = datetime.now(utc) query_time = int(req.session.get('query_time', 0)) query_time = datetime.fromtimestamp(query_time, utc) query_constraints = unicode(query.constraints) if query_constraints != req.session.get('query_constraints') \ or query_time < orig_time - timedelta(hours=1): tickets = query.execute(req, db) # New or outdated query, (re-)initialize session vars req.session['query_constraints'] = query_constraints req.session['query_tickets'] = ' '.join( [str(t['id']) for t in tickets]) else: orig_list = [ int(id) for id in req.session.get('query_tickets', '').split() ] tickets = query.execute(req, db, orig_list) orig_time = query_time context = Context.from_request(req, 'query') ticket_data = query.template_data(context, tickets, orig_list, orig_time, req) # For clients without JavaScript, we add a new constraint here if # requested constraints = ticket_data['clauses'][0] if 'add' in req.args: field = req.args.get('add_filter') if field: constraint = constraints.setdefault(field, {}) constraint.setdefault('values', []).append('') # FIXME: '' not always correct (e.g. checkboxes) req.session['query_href'] = query.get_href(context.href) req.session['query_time'] = to_timestamp(orig_time) req.session['query_tickets'] = ' '.join( [str(t['id']) for t in tickets]) # data dictionary for genshi data = {} # get data for saved queries query_id = req.args.get('query_id') if query_id: try: query_id = int(query_id) except ValueError: add_warning( req, "query_id should be an integer, you put '%s'" % query_id) query_id = None if query_id: data['query_id'] = query_id query_data = self.get_query(query_id) data['query_title'] = query_data['title'] data['query_description'] = query_data['description'] data.setdefault('report', None) data.setdefault('description', None) data['all_columns'] = query.get_all_columns() + self.get_columns() # Don't allow the user to remove the id column data['all_columns'].remove('id') data['all_textareas'] = query.get_all_textareas() # need to re-get the cols because query will remove our fields cols = req.args.get('col') if isinstance(cols, basestring): cols = [cols] if not cols: cols = query.get_columns() + self.get_default_columns() data['col'] = cols now = datetime.now() # get the date range for the query if 'from_year' in req.args: from_date = get_date(req.args['from_year'], req.args.get('from_month'), req.args.get('from_day')) else: from_date = datetime(now.year, now.month, now.day) from_date = from_date - timedelta(days=7) # 1 week ago, by default if 'to_year' in req.args: to_date = get_date(req.args['to_year'], req.args.get('to_month'), req.args.get('to_day'), end_of_day=True) else: to_date = now data['prev_week'] = from_date - timedelta(days=7) data['months'] = list(enumerate(calendar.month_name)) data['years'] = range(now.year, now.year - 10, -1) data['days'] = range(1, 32) data['users'] = get_all_users(self.env) data['cur_worker_filter'] = req.args.get('worker_filter', '*any') data['from_date'] = from_date data['to_date'] = to_date ticket_ids = [t['id'] for t in tickets] # generate data for ticket_times time_records = self.get_ticket_hours( ticket_ids, from_date=from_date, to_date=to_date, worker_filter=data['cur_worker_filter']) data['query'] = ticket_data['query'] data['context'] = ticket_data['context'] data['row'] = ticket_data['row'] if 'comments' in req.args.get('row', []): data['row'].append('comments') data['constraints'] = ticket_data['clauses'] our_labels = dict([(f['name'], f['label']) for f in self.fields]) labels = TicketSystem(self.env).get_ticket_field_labels() labels.update(our_labels) data['labels'] = labels order = req.args.get('order') desc = bool(req.args.get('desc')) data['order'] = order data['desc'] = desc headers = [{ 'name': col, 'label': labels.get(col), 'href': self.get_href(query, req.args, context.href, order=col, desc=(col == order and not desc)) } for col in cols] data['headers'] = headers data['fields'] = ticket_data['fields'] data['modes'] = ticket_data['modes'] # group time records time_records_by_ticket = {} for record in time_records: id = record['ticket'] if id not in time_records_by_ticket: time_records_by_ticket[id] = [] time_records_by_ticket[id].append(record) data['extra_group_fields'] = dict(ticket=dict(name='ticket', type='select', label='Ticket'), worker=dict(name='worker', type='select', label='Worker')) num_items = 0 data['groups'] = [] # merge ticket data into ticket_time records for key, tickets in ticket_data['groups']: ticket_times = [] total_time = 0 total_estimated_time = 0 for ticket in tickets: records = time_records_by_ticket.get(ticket['id'], []) [rec.update(ticket) for rec in records] ticket_times += records # sort ticket_times, if needed if order in our_labels: ticket_times.sort(key=lambda x: x[order], reverse=desc) data['groups'].append((key, ticket_times)) num_items += len(ticket_times) data['double_count_warning'] = '' # group by ticket id or other time_ticket fields if necessary if req.args.get('group') in data['extra_group_fields']: query.group = req.args.get('group') if not query.group == "id": data[ 'double_count_warning'] = "Warning: estimated hours may be counted more than once if a ticket appears in multiple groups" tickets = data['groups'][0][1] groups = {} for time_rec in tickets: key = time_rec[query.group] if not key in groups: groups[key] = [] groups[key].append(time_rec) data['groups'] = sorted(groups.items()) total_times = dict( (k, self.format_hours(sum(rec['seconds_worked'] for rec in v))) for k, v in data['groups']) total_estimated_times = {} for key, records in data['groups']: seen_tickets = set() est = 0 for record in records: # do not double-count tickets id = record['ticket'] if id in seen_tickets: continue seen_tickets.add(id) estimatedhours = record.get('estimatedhours') or 0 try: estimatedhours = float(estimatedhours) except ValueError: estimatedhours = 0 est += estimatedhours * 3600 total_estimated_times[key] = self.format_hours(est) data['total_times'] = total_times data['total_estimated_times'] = total_estimated_times # format records for record in time_records: if 'seconds_worked' in record: record['seconds_worked'] = self.format_hours( record['seconds_worked']) # XXX misleading name if 'time_started' in record: record['time_started'] = self.format_date( record['time_started']) if 'time_submitted' in record: record['time_submitted'] = self.format_date( record['time_submitted']) data['query'].num_items = num_items data['labels'] = TicketSystem(self.env).get_ticket_field_labels() data['labels'].update(labels) data['can_add_hours'] = req.perm.has_permission('TICKET_ADD_HOURS') data['multiproject'] = self.env.is_component_enabled(MultiprojectHours) from web_ui import TracUserHours data['user_hours'] = self.env.is_component_enabled(TracUserHours) # return the rss, if requested if req.args.get('format') == 'rss': return self.queryhours2rss(req, data) # return the csv, if requested if req.args.get('format') == 'csv': self.queryhours2csv(req, data) # add rss link rss_href = req.href(req.path_info, format='rss') add_link(req, 'alternate', rss_href, _('RSS Feed'), 'application/rss+xml', 'rss') # add csv link add_link(req, 'alternate', req.href(req.path_info, format='csv', **req.args), 'CSV', 'text/csv', 'csv') # add navigation of weeks prev_args = dict(req.args) next_args = dict(req.args) prev_args['from_year'] = (from_date - timedelta(days=7)).year prev_args['from_month'] = (from_date - timedelta(days=7)).month prev_args['from_day'] = (from_date - timedelta(days=7)).day prev_args['to_year'] = from_date.year prev_args['to_month'] = from_date.month prev_args['to_day'] = from_date.day next_args['from_year'] = to_date.year next_args['from_month'] = to_date.month next_args['from_day'] = to_date.day next_args['to_year'] = (to_date + timedelta(days=7)).year next_args['to_month'] = (to_date + timedelta(days=7)).month next_args['to_day'] = (to_date + timedelta(days=7)).day add_link(req, 'prev', self.get_href(query, prev_args, context.href), _('Prev Week')) add_link(req, 'next', self.get_href(query, next_args, context.href), _('Next Week')) prevnext_nav(req, _('Prev Week'), _('Next Week')) add_ctxtnav(req, 'Cross-Project Hours', req.href.hours('multiproject')) add_ctxtnav( req, 'Hours by User', req.href.hours('user', from_day=from_date.day, from_month=from_date.month, from_year=from_date.year, to_day=to_date.year, to_month=to_date.month, to_year=to_date.year)) add_ctxtnav(req, 'Saved Queries', req.href.hours('query/list')) add_stylesheet(req, 'common/css/report.css') add_script(req, 'common/js/query.js') return ('hours_timeline.html', data, 'text/html')
def process_request(self, req): """process request handler""" req.perm.require('PRODUCT_VIEW') pid = req.args.get('productid', None) if pid: req.perm('product', pid).require('PRODUCT_VIEW') action = req.args.get('action', 'view') products = [p for p in Product.select(self.env) if 'PRODUCT_VIEW' in req.perm(p.resource)] if pid is not None: add_link(req, 'up', req.href.products(), _('Products')) try: product = Product(self.env, {'prefix': pid}) except ResourceNotFound: product = Product(self.env) data = {'product': product, 'context': web_context(req, product.resource)} if req.method == 'POST': if req.args.has_key('cancel'): req.redirect(req.href.products(product.prefix)) elif action == 'edit': return self._do_save(req, product) elif action == 'delete': req.perm(product.resource).require('PRODUCT_DELETE') retarget_to = req.args.get('retarget', None) name = product.name product.delete(resources_to=retarget_to) add_notice(req, _('The product "%(n)s" has been deleted.', n = name)) req.redirect(req.href.products()) elif action in ('new', 'edit'): return self._render_editor(req, product) elif action == 'delete': req.perm(product.resource).require('PRODUCT_DELETE') return 'product_delete.html', data, None if pid is None: data = {'products': products, 'context': web_context(req, Resource('products', None))} return 'product_list.html', data, None def add_product_link(rel, product): href = req.href.products(product.prefix) add_link(req, rel, href, _('Product "%(name)s"', name=product.name)) idx = [i for i, p in enumerate(products) if p.name == product.name] if idx: idx = idx[0] if idx > 0: add_product_link('first', products[0]) add_product_link('prev', products[idx - 1]) if idx < len(products) - 1: add_product_link('next', products[idx + 1]) add_product_link('last', products[-1]) prevnext_nav(req, _('Previous Product'), _('Next Product'), _('Back to Product List')) return 'product_view.html', data, None
def _render_config(self, req, config_name): db = self.env.get_db_cnx() config = BuildConfig.fetch(self.env, config_name, db=db) if not config: raise HTTPNotFound("Build configuration '%s' does not exist." \ % config_name) repos = self.env.get_repository(authname=req.authname) assert repos, 'No "(default)" Repository: Add a repository or alias ' \ 'named "(default)" to Trac.' rev = config.max_rev or repos.youngest_rev try: _has_permission(req.perm, repos, config.path, rev=rev, raise_error=True) except NoSuchNode: raise TracError("Permission checking against repository path %s " "at revision %s failed." % (config.path, rev)) data = {'title': 'Build Configuration "%s"' \ % config.label or config.name, 'page_mode': 'view_config'} add_link(req, 'up', req.href.build(), 'Build Status') description = config.description if description: description = wiki_to_html(description, self.env, req) pending_builds = list( Build.select(self.env, config=config.name, status=Build.PENDING)) inprogress_builds = list( Build.select(self.env, config=config.name, status=Build.IN_PROGRESS)) data['config'] = { 'name': config.name, 'label': config.label, 'path': config.path, 'min_rev': config.min_rev, 'min_rev_href': req.href.changeset(config.min_rev), 'max_rev': config.max_rev, 'max_rev_href': req.href.changeset(config.max_rev), 'active': config.active, 'description': description, 'browser_href': req.href.browser(config.path), 'builds_pending': len(pending_builds), 'builds_inprogress': len(inprogress_builds) } context = Context.from_request(req, config.resource) data['context'] = context data['config']['attachments'] = AttachmentModule( self.env).attachment_data(context) platforms = list( TargetPlatform.select(self.env, config=config_name, db=db)) data['config']['platforms'] = [{ 'name': platform.name, 'id': platform.id, 'builds_pending': len( list( Build.select(self.env, config=config.name, status=Build.PENDING, platform=platform.id))), 'builds_inprogress': len( list( Build.select(self.env, config=config.name, status=Build.IN_PROGRESS, platform=platform.id))) } for platform in platforms] has_reports = False for report in Report.select(self.env, config=config.name, db=db): has_reports = True break if has_reports: chart_generators = [] report_categories = list( self._report_categories_for_config(config)) for generator in ReportChartController(self.env).generators: for category in generator.get_supported_categories(): if category in report_categories: chart_generators.append({ 'href': req.href.build(config.name, 'chart/' + category), 'category': category, 'style': self.config.get('bitten', 'chart_style'), }) data['config']['charts'] = chart_generators page = max(1, int(req.args.get('page', 1))) more = False data['page_number'] = page repos = self.env.get_repository(authname=req.authname) assert repos, 'No "(default)" Repository: Add a repository or alias ' \ 'named "(default)" to Trac.' builds_per_page = 12 * len(platforms) idx = 0 builds = {} revisions = [] for platform, rev, build in collect_changes(repos, config): if idx >= page * builds_per_page: more = True break elif idx >= (page - 1) * builds_per_page: if rev not in builds: revisions.append(rev) builds.setdefault(rev, {}) builds[rev].setdefault('href', req.href.changeset(rev)) builds[rev].setdefault('display_rev', repos.normalize_rev(rev)) if build and build.status != Build.PENDING: build_data = _get_build_data(self.env, req, build) build_data['steps'] = [] for step in BuildStep.select(self.env, build=build.id, db=db): build_data['steps'].append({ 'name': step.name, 'description': step.description, 'duration': to_datetime(step.stopped or int(time.time()), utc) - \ to_datetime(step.started, utc), 'status': _step_status_label[step.status], 'cls': _step_status_label[step.status].replace(' ', '-'), 'errors': step.errors, 'href': build_data['href'] + '#step_' + step.name }) builds[rev][platform.id] = build_data idx += 1 data['config']['builds'] = builds data['config']['revisions'] = revisions if page > 1: if page == 2: prev_href = req.href.build(config.name) else: prev_href = req.href.build(config.name, page=page - 1) add_link(req, 'prev', prev_href, 'Previous Page') if more: next_href = req.href.build(config.name, page=page + 1) add_link(req, 'next', next_href, 'Next Page') if arity(prevnext_nav) == 4: # Trac 0.12 compat, see #450 prevnext_nav(req, 'Previous Page', 'Next Page') else: prevnext_nav(req, 'Page') return data