def get_content(self, req, entry, data): # Make sure that repository is a Subversion repository, since # we're relying for now on Subversion to provide WebDAV # support. if not is_subversion_repository(data.get('repos')): return None # don't show up in the menu if the user is browsing a non-HEAD revision if data.get('stickyrev'): return None reponame = data['reponame'] or '' ext = os.path.splitext(entry.name)[1][1:] if not entry.isdir and ext in self.office_file_extensions: path = self.get_subversion_path(entry) href = self.get_subversion_href(data, path) if re.search('(MSIE |Trident/)', req.environ.get('HTTP_USER_AGENT', '')): # for IE, we'll use ActiveX as this may work with older Office installations return tag.a(tag.i(class_="fa fa-edit"), ' %s with Microsoft Office' % self._verb, href=href, class_=self._link_class) else: # otherwise, let's try https://msdn.microsoft.com/en-us/library/office/dn906146.aspx # which is Office 2010 SP2 and above application = self.office_file_extensions[ext] return tag.a(tag.i(class_="fa fa-edit"), ' %s with Microsoft %s' % (self._verb, application.title()), href="ms-%s:%s|u|%s" % (application, self._href_mode, href))
def product_media_data(icons, product): return dict(href=product.href(), thumb=icons.get(product.prefix, no_thumbnail), title=product.name, description=format_to_html(self.env, product_ctx(product), product.description), links={'extras': (([{'href': req.href.products( product.prefix, action='edit'), 'title': _('Edit product %(prefix)s', prefix=product.prefix), 'icon': tag.i(class_='icon-edit'), 'label': _('Edit')},] if 'PRODUCT_MODIFY' in req.perm else []) + [{'href': product.href(), 'title': _('Home page'), 'icon': tag.i(class_='icon-home'), 'label': _('Home')}, {'href': product.href.dashboard(), 'title': _('Tickets dashboard'), 'icon': tag.i(class_='icon-tasks'), 'label': _('Tickets')}, {'href': product.href.wiki(), 'title': _('Wiki'), 'icon': tag.i(class_='icon-book'), 'label': _('Wiki')}]), 'main': {'href': product.href(), 'title': None, 'icon': tag.i(class_='icon-chevron-right'), 'label': _('Browse')}})
def expand_macro(self, formatter, name, content): req = formatter.req query_string = "" argv, kwargs = parse_args(content, strict=False) if "order" not in kwargs: kwargs["order"] = "id" if "max" not in kwargs: kwargs["max"] = "0" # unlimited by default query_string = "&".join(["%s=%s" % item for item in kwargs.iteritems()]) query = Query.from_string(self.env, query_string) tickets = query.execute(req) tickets = [t for t in tickets if "TICKET_VIEW" in req.perm("ticket", t["id"])] ticket_ids = [t["id"] for t in tickets] # locate the tickets geoticket = GeoTicket(self.env) locations = geoticket.locate_tickets(ticket_ids, req) if not locations: return tag.div(tag.b("MapTickets: "), "No locations found for ", tag.i(content)) data = dict(locations=Markup(simplejson.dumps(locations)), query_href=query.get_href(req), query_string=content) # set an id for the map map_id = req.environ.setdefault("MapTicketsId", 0) + 1 req.environ["MapTicketsId"] = map_id data["map_id"] = "tickets-map-%d" % map_id return Chrome(self.env).render_template(req, "map_tickets.html", data, None, fragment=True)
def get_navigation_items(self, req): if req.authname and req.authname != 'anonymous': # Use the same names as LoginModule to avoid duplicates. yield ('metanav', 'login', _('logged in as %(user)s', user=req.authname)) logout_href = req.href('%s/logout' % self.auth_path_prefix) from pkg_resources import parse_version if parse_version(trac.__version__) < parse_version('1.0.2'): yield ('metanav', 'logout', tag.a(_('Logout'), logout_href)) else: yield ('metanav', 'logout', tag.form(tag.div( tag.button(_('Logout'), name='logout', type='submit')), action=logout_href, method='post', id='logout', class_='trac-logout')) else: text = _('GitHub Login') if self.fontawesome_url: add_script(req, self.fontawesome_url) text = tag(tag.i(class_='fab fa-github-alt'), ' ', text) yield ('metanav', 'github_login', tag.a(text, href=req.href('%s/login' % self.auth_path_prefix)))
def filter_stream(self, req, method, filename, stream, data): if filename == 'browser.html' and req.method == 'GET': # we can only work from the 'dir' view at the moment if data.get('file'): return stream # TODO check that contextmenu's InternalNameHolder is enabled, as our js needs it? add_stylesheet(req, 'sourcesharer/filebox.css') add_javascript(req, 'sourcesharer/filebox.js') # Render the filebox template for stream insertion # TODO introduce a new interface to allow putting extra buttons into this filebox? tmpl = TemplateLoader(self.get_templates_dirs()).load('filebox.html') filebox = tmpl.generate(href=req.href, reponame=data['reponame'] or '', rev=data['rev'], files=[]) # Wrap and float dirlist table, add filebox div # TODO change the id names, left/right seems a bit generic to assume we can have to ourselves stream |= Transformer('//table[@id="dirlist"]').wrap(tag.div(id="outer",style="clear:both")).wrap(tag.div(id="left")) stream |= Transformer('//div[@id="outer"]').append(tag.div(filebox, id="right")) is_svn_repo = False if 'repos' in data: is_svn_repo = isinstance(data.get('repos'), (SvnCachedRepository, SubversionRepository)) or False if is_svn_repo: add_ctxtnav(req, tag.a(_(tag.i(class_="fa fa-envelope-o")), " Send", href="", title=_("Send selected files"), id='share-files', class_='alt-button share-files-multiple'), category='ctxtnav', order=10) return stream
def get_content(self, req, entry, data): if 'BUSINESSINTELLIGENCE_TRANSFORMATION_EXECUTE' in req.perm: if entry.path.startswith("define-reports/"): transform = entry.path.split("/")[1] return tag.a(tag.i(class_="fa fa-cog"), ' Regenerate with %s (provide parameters)' % transform, href=req.href.businessintelligence() + "#" + transform)
def get_content(self, req, entry, data): if 'BUSINESSINTELLIGENCE_TRANSFORMATION_EXECUTE' in req.perm: if entry.path.startswith("define-reports/"): transform = entry.path.split("/")[1] return tag.a(tag.i(class_="fa fa-cog"), ' Regenerate with %s (default parameters)' % transform, href=req.href.businessintelligence(action='execute', transform=transform, returnto=req.href.browser(entry.path)))
def get_content(self, req, entry, data): # Make sure that repository is a Subversion repository. if not is_subversion_repository(data.get('repos')): return None if data.get('stickyrev'): return None path = self.get_subversion_path(entry) href = self.get_subversion_href(data, path) # create a url which uses the tsvncmd protocol and repobrowser tortoise_href = "tsvncmd:command:repobrowser?path:" + href return tag.a(_(tag.i(class_="fa fa-code-fork")),' Browse With TortoiseSVN', href=tortoise_href, id_='browse-with-tortoise')
def get_content(self, req, entry, data): # Make sure that repository is a Subversion repository. if not is_subversion_repository(data.get('repos')): return None if data.get('stickyrev'): return None if self.env.is_component_enabled("svnurls.svnurls.svnurls"): # They are already providing links to subversion, so we won't duplicate them. return None path = self.get_subversion_path(entry) href = self.get_subversion_href(data, path) return tag.a(_(tag.i(class_="fa fa-globe")),' Subversion URL', href=href, class_='external svn')
def render_widget(self, name, context, options): """Render widget considering given options. """ if name == 'WidgetDoc': add_stylesheet(context.req, 'dashboard/css/docs.css') widget_name, = self.bind_params(options, self.get_widget_params(name), 'urn') if widget_name is not None: try: providers = [([widget_name], self.resolve_widget(widget_name))] except LookupError: return 'widget_alert.html', { 'title': _('Widget documentation'), 'data': { 'msglabel': 'Alert', 'msgbody': 'Unknown identifier', 'msgdetails': [('Widget name', widget_name)] } }, context else: providers = [(provider.get_widgets(), provider) \ for provider in self.widget_providers] metadata = [self._prepare_doc_metadata(self.widget_metadata(wnm, p)) \ for widgets, p in providers for wnm in widgets] docs_resource = Resource('wiki', 'BloodhoundWidgets') insert_docs = resource_exists(self.env, docs_resource) and \ not (context.resource and \ docs_resource == context.resource) return 'widget_doc.html', { 'title': _('Widget documentation'), 'data': { 'items': metadata }, 'ctxtnav': [ tag.a(tag.i(class_='icon-info-sign'), ' ', _('Help'), href=get_resource_url(self.env, docs_resource, context.href)) ] if insert_docs else [], }, context else: raise InvalidIdentifier('Widget name MUST match any of ' + ', '.join(self.get_widgets()), title='Invalid widget identifier')
def render_widget(self, name, context, options): """Render widget considering given options. """ if name == 'WidgetDoc': add_stylesheet(context.req, 'dashboard/css/docs.css') widget_name, = self.bind_params(options, self.get_widget_params(name), 'urn') if widget_name is not None: try: providers = [([widget_name], self.resolve_widget(widget_name))] except LookupError: return 'widget_alert.html', { 'title': _('Widget documentation'), 'data': { 'msglabel': 'Alert', 'msgbody': 'Unknown identifier', 'msgdetails': [ ('Widget name', widget_name) ] } }, context else: providers = [(provider.get_widgets(), provider) \ for provider in self.widget_providers] metadata = [self._prepare_doc_metadata(self.widget_metadata(wnm, p)) \ for widgets, p in providers for wnm in widgets] docs_resource = Resource('wiki', 'BloodhoundWidgets') insert_docs = resource_exists(self.env, docs_resource) and \ not (context.resource and \ docs_resource == context.resource) return 'widget_doc.html', { 'title': _('Widget documentation'), 'data': { 'items': metadata }, 'ctxtnav': [tag.a(tag.i(class_='icon-info-sign'), ' ', _('Help'), href=get_resource_url( self.env, docs_resource, context.href) )] if insert_docs else [], }, context else: raise InvalidIdentifier('Widget name MUST match any of ' + ', '.join(self.get_widgets()), title='Invalid widget identifier')
def expand_macro(self, formatter, name, content): req = formatter.req query_string = '' argv, kwargs = parse_args(content, strict=False) if 'order' not in kwargs: kwargs['order'] = 'id' if 'max' not in kwargs: kwargs['max'] = '0' # unlimited by default query_string = '&'.join( ['%s=%s' % item for item in kwargs.iteritems()]) query = Query.from_string(self.env, query_string) tickets = query.execute(req) tickets = [ t for t in tickets if 'TICKET_VIEW' in req.perm('ticket', t['id']) ] ticket_ids = [t['id'] for t in tickets] # locate the tickets geoticket = GeoTicket(self.env) locations = geoticket.locate_tickets(ticket_ids, req) if not locations: return tag.div(tag.b('MapTickets: '), "No locations found for ", tag.i(content)) data = dict(locations=Markup(simplejson.dumps(locations)), query_href=query.get_href(req), query_string=content) # set an id for the map map_id = req.environ.setdefault('MapTicketsId', 0) + 1 req.environ['MapTicketsId'] = map_id data['map_id'] = 'tickets-map-%d' % map_id return Chrome(self.env).render_template(req, 'map_tickets.html', data, None, fragment=True)
def filter_stream(self, req, method, filename, stream, data): if 'default_user_query' in req.session: # Inject js for replacing all standard query links add_script_data(req, ( ('queryHref', req.href.query()), ('replacementQueryHref', req.session['default_user_query']))) stream |= Transformer('html/head').append( self._replace_query_links_js) if req.path_info == '/query': # Add a link to the ribbon for setting the default query add_ctxtnav(req, tag.a(tag.i(class_='fa fa-bookmark'), _(" Set as default"), id_='set-default-query', title=_("Make this your default query"))) # Add js to redirect the form when the link is clicked add_script_data(req, ( ('defaultUserQueryAction', req.href('defaultuserquery')),)) stream |= Transformer('html/head').append( self._redirect_and_submit_js) return stream
def filter_stream(self, req, method, filename, stream, data): if "WIKI_ADMIN" in req.perm and filename == "wiki_view.html": remote_url = self._get_config("url", "") if remote_url: pagename = req.args.get("page", "WikiStart") dao = WikiSyncDao(self.env) item = dao.find(pagename) if not item: item = dao.factory(name=pagename) params = { "model": item, "remote_url": remote_url, "req": req } add_ctxtnav(req, tag.span( tag.a( tag.i( item.status.upper(), class_="status" ), Markup("↓"), href="#", class_=item.status, id_="wikisync-panel-toggle" ), class_="wikisync" ) ) self._render_assets(req) stream |= Transformer('.//body').prepend( Chrome(self.env).load_template( "wikisync_page.html" ).generate(**params) ) return stream
def render_fdx_subset(self,fdx,start_with_scene,end_with_scene,mode,formatter): theoutput = tag.div(class_="scrippet"+mode) # self.log.debug("FDX: %s START: %d END %d" % (fdx,start_with_scene,end_with_scene)) fdx_obj = self._get_src(self.env, formatter.req, *fdx) fd_doc = cElementTree.fromstring(fdx_obj.getStream().read()) renderParagraphs = False for fd_content in fd_doc.findall("Content"): for fd_paragraph in fd_content.findall("Paragraph"): ptype = fd_paragraph.get('Type') if ptype == "Action": ptype = "action" elif ptype == "Character": ptype = "character" elif ptype == "Dialogue": ptype = "dialogue" elif ptype == "Parenthetical": ptype = "parenthetical" elif ptype == "Shot": ptype = "shot" elif ptype == "Scene Heading": if int(fd_paragraph.get('Number')) == start_with_scene: renderParagraphs = True if int(fd_paragraph.get('Number')) == end_with_scene: renderParagraphs = False ptype = "sceneheader" elif ptype == "Transition": ptype = "transition" elif ptype == "Teaser/Act One": ptype = "header" elif ptype == "New Act": ptype = "header" elif ptype == "End Of Act": ptype = "header" else: ptype = "action" #UNHANDLED FOR THE MOMENT #Show/Ep. Title ptext = [] for fd_text in fd_paragraph.findall("Text"): text_style = fd_text.get('Style') if fd_text.text != None: if "FADE IN:" in fd_text.text.upper(): fd_text.text = fd_text.text.upper() if ptype in ["character","transition","sceneheader","header","shot"]: fd_text.text = fd_text.text.upper() #clean smart quotes fd_text.text = fd_text.text.replace(u"\u201c", "\"").replace(u"\u201d", "\"") #strip double curly quotes fd_text.text = fd_text.text.replace(u"\u2018", "'").replace(u"\u2019", "'").replace(u"\u02BC", "'") #strip single curly quotes ptext.append({"style":text_style,"text":fd_text.text}) content = [] for block in ptext: if block["style"] == "Italic": content.append(tag.i(block["text"])) elif block["style"] == "Underline": content.append(tag.u(block["text"])) elif block["style"] == "Bold": content.append(tag.b(block["text"])) elif block["style"] == "Bold+Underline": content.append(tag.b(tag.u(block["text"]))) else: content.append(block["text"]) if renderParagraphs: theoutput.append(tag.p(content,class_=ptype+mode)) return theoutput
def filter_stream(self, req, method, filename, stream, data): """ Check for alert messages to show authenticated user. If there are any project messages that the authenticated user has not seen, which are selected to be viewed as alert based notifications, we add the necessary mark-up and javscript. """ if req.authname != 'anonymous': timeout_exceeded = self._timeout_limit_exceeded(req) if timeout_exceeded or timeout_exceeded is None: # we can check for alert notifications unagreed = ProjectMessage.get_unagreed_messages(self.env, req.authname, 'Alert') if unagreed: # we only shown one notification at a time currently msg = unagreed[0] msg['message'] = format_to_html(self.env, Context.from_request(req), msg['message']) alert_markup = tag( tag.div( tag.i( class_="alert-icon fa fa-info-circle" ), tag.ul( tag.li(msg['message'], class_="alert-message" ), ), tag.button(msg['button'], class_="close btn btn-mini", type="button", data_dismiss="alert" ), class_="project-message cf alert alert-info alert-dismissable individual" ), tag.form( tag.input( name="name", value=msg['name'], type="text", ), tag.input( name="agree", value=True, type="text", ), class_="hidden", method="post", action="", ), ) stream |= Transformer("//*[@id='main']/*[1]").before(alert_markup) add_script(req, 'projectmessage/js/project_message.js') # if the timeout has been exceeded or does not exist yet, # and there are no notifications to show, we update the # session attribute table if not ProjectMessage.get_unagreed_messages(self.env, req.authname): stamp = str(to_utimestamp(datetime.now(pytz.utc))) req.session['project_message_timeout'] = stamp req.session.save() return stream
def process_request(self, req): if 'action' in req.args: transform_id = self._generate_running_transformation_id() parameters = {} for k in req.args: if k.startswith("parameter:"): parameter_name = k.split(":",2)[1] parameter_value = req.args[k] parameters[parameter_name] = parameter_value req.perm.require("BUSINESSINTELLIGENCE_TRANSFORMATION_EXECUTE") if req.args['action'] == "execute_async": # execute the transformation thread.start_new_thread(self._do_execute_transformation, (req.args['transform'],), {'transformation_id': transform_id, 'parameters': parameters}) # send transform_id generated via uuid back to JS via JSON # we have to do this after we invoke a new thread as req.send() # returns from this function and stops further flow control req.send(to_json({'transform_id': transform_id}), 'text/json') elif req.args['action'] == "execute_download": filename, stat, filestream = self._do_execute_transformation(req.args['transform'], transformation_id=transform_id, store=False, return_bytes_handle=True, parameters=parameters) req.send_response(200) req.send_header('Content-Type', mimetypes.guess_type(filename)[0] or 'application/octet-stream') req.send_header('Content-Length', stat.st_size) req.send_header('Content-Disposition', content_disposition('attachment', filename)) req.end_headers() while True: bytes = filestream.read(4096) if not bytes: break req.write(bytes) filestream.close() raise RequestDone elif req.args['action'] == "execute": self._do_execute_transformation(req.args['transform'], transformation_id=transform_id, parameters=parameters) elif req.args['action'] == 'check_status': if 'uuid' not in req.args: raise KeyError running_transformations = json.loads(req.args['uuid']) req.send(to_json(self._generate_status_response(running_transformations)), 'text/json') else: add_warning(req, "No valid action found") req.redirect(req.href.businessintelligence()) if req.get_header('X-Requested-With') == 'XMLHttpRequest': req.send_response(200) req.send_header('Content-Length', 0) req.end_headers() return else: if 'returnto' in req.args: req.redirect(req.args['returnto']) else: req.redirect(req.href.businessintelligence()) else: req.perm.require("BUSINESSINTELLIGENCE_TRANSFORMATION_LIST") data = {'transformations': self._list_transformation_files(listall=False)} add_script(req, 'contextmenu/contextmenu.js') add_script(req, 'businessintelligenceplugin/js/business-intelligence.js') add_stylesheet(req, 'common/css/browser.css') add_ctxtnav(req, tag.a(tag.i(class_="fa fa-upload"), ' Upload Transformations', id="uploadbutton")) add_ctxtnav(req, tag.a(tag.i(class_="fa fa-calendar"), ' Schedule Transformations', id="schedulebutton")) add_ctxtnav(req, tag.a(tag.i(class_="fa fa-cog"), ' Running Transformations', id="runningbutton")) return "listtransformations.html", data, None
def get_timeline_markup(self, req, call, maxrows=10): """ Generates the markup needed when this component is called both explicitly inside wiki pages and implicitly by the ISideBarBoxProvider. Note this code uses methods from the trac TimelineModule module. """ chrome = Chrome(self.env) # last 14 days should be enough stop = datetime.now(req.tz) start = stop - timedelta(days=50) # use code from trac/timeline to generate event data timeline = TimelineModule(self.env) available_filters, filters = timeline.get_filters(req) include_authors, exclude_authors = timeline.authors() events = timeline.get_events(req, start, stop, filters, available_filters, include_authors, exclude_authors, maxrows) show_gravatar = self.config.get('avatar','mode').lower() != 'off' # create the mark up context = Context.from_request(req) event_list = tag.ul(class_="timeline-list no-style") for event in events: event_title = event['render']('title', context) event_url = event['render']('url', context) event_list.append(tag.li( show_gravatar and tag.img( src=req.href.avatar(event['author'] if event['author'] else 'anonymous'), class_="avatar", ) or "", tag.span( chrome.authorinfo(req, event['author']), class_="author" ), tag.span( pretty_age(event['date']), class_="date", ), tag.div( tag.i(class_="event-type fa fa-" + event['kind']), tag.a( event_title, href=event_url, ), class_="event-summary" ), class_="cf" )) # if the markup is being generated via ISideBarBoxProvider we don't # need to add a span3 class div_classes = "box-sidebar" if call == "macro": div_classes += " span3 right" return tag.div( tag.h3( tag.i( class_="fa fa-calendar" ), " Recent Project Activity" ), event_list, class_=div_classes, )
def __call__(self,obj,depth=0,dontRender=False): from genshi.builder import tag if depth>self.maxDepth: raise RuntimeError("Maximum nesting depth %d exceeded"%self.maxDepth) kw=self.padding.copy() if depth>0: kw.update(width='100%') # was [1:] to omit leading woo./wooExtra., but that is not desirable objInExtra=obj.__class__.__module__.startswith('wooExtra.') if self.hideWooExtra and objInExtra: head=tag.span(tag.b(obj.__class__.__name__),title=_ensureUnicode(obj.__class__.__doc__)) else: head=tag.b('.'.join(obj.__class__.__module__.split('.')[0:])+'.'+obj.__class__.__name__) head=tag.a(head,href=woo.document.makeObjectUrl(obj),title=_ensureUnicode(obj.__class__.__doc__)) ret=tag.table(tag.th(head,colspan=3,align='left'),frame='box',rules='all',**kw) # get all attribute traits first traits=obj._getAllTraits() for trait in traits: if trait.hidden or (self.hideNoGui and trait.noGui) or trait.noDump or (trait.hideIf and eval(trait.hideIf,globals(),{'self':obj})): continue # start new group (additional line) if trait.startGroup: ret.append(tag.tr(tag.td(tag.i(u'▸ %s'%_ensureUnicode(trait.startGroup)),colspan=3))) attr=getattr(obj,trait.name) if self.showDoc: tr=tag.tr(tag.td(_ensureUnicode(trait.doc))) else: try: if self.hideWooExtra and objInExtra: label=tag.span(tag.b(trait.name),title=_ensureUnicode(trait.doc)) else: label=tag.a(trait.name,href=woo.document.makeObjectUrl(obj,trait.name),title=_ensureUnicode(trait.doc)) tr=tag.tr(tag.td(label)) except UnicodeEncodeError: print('ERROR: UnicodeEncodeError while formatting the attribute ',obj.__class__.__name__+'.'+trait.name) print('ERROR: the docstring is',trait.doc) raise # tr=tag.tr(tag.td(trait.name if not self.showDoc else trait.doc.decode('utf-8'))) # nested object if isinstance(attr,woo.core.Object): tr.append([tag.td(self(attr,depth+1),align='justify'),tag.td()]) # sequence of objects (no units here) elif hasattr(attr,'__len__') and len(attr)>0 and isinstance(attr[0],woo.core.Object): tr.append(tag.td(tag.ol([tag.li(self(o,depth+1)) for o in attr]))) else: # !! make deepcopy so that the original object is not modified !! import copy attr=copy.deepcopy(attr) if not trait.multiUnit: # the easier case if not trait.prefUnit: unit=u'−' else: unit=_ensureUnicode(trait.prefUnit[0][0]) # create new list, where entries are multiplied by the multiplier if type(attr)==list: attr=[a*trait.prefUnit[0][1] for a in attr] else: attr=attr*trait.prefUnit[0][1] else: # multiple units unit=[] wasList=isinstance(attr,list) if not wasList: attr=[attr] # handle uniformly for i in range(len(attr)): attr[i]=[attr[i][j]*trait.prefUnit[j][1] for j in range(len(attr[i]))] for pu in trait.prefUnit: unit.append(_ensureUnicode(pu[0])) if not wasList: attr=attr[0] unit=', '.join(unit) # sequence type, or something similar if hasattr(attr,'__len__') and not isinstance(attr,(str,unicode,bytes)): if len(attr)>0: tr.append(tag.td(self.htmlSeq(attr,insideTable=False),align='right')) else: tr.append(tag.td(tag.i('[empty]'),align='right')) else: tr.append(tag.td(float2str(attr) if isinstance(attr,float) else str(attr),align='right')) if unit: tr.append(tag.td(unit,align='right')) ret.append(tr) if depth>0 or dontRender: return ret r1=ret.generate().render('xhtml',encoding='ascii') if isinstance(r1,bytes): r1=r1.decode('ascii') return r1+u'\n'
def filter_stream(self, req, method, filename, stream, data): """ We add JavaScript files and other DOM elements via the filter stream, but actually query the DB and parse the data after the initial page load via AJAX for a page load performance boost. See IRequestHandler methods. We only add these scripts and elements if the milestone has a minimum of atleast one ticket. We also only render the completed work DOM elements if a minimum of one ticket has been closed. """ if req.path_info.startswith('/milestone/'): milestone = self._get_milestone(req.args['id']) if milestone and self._milestone_has_ticket(milestone.name): add_script(req, 'common/js/jqPlot/jquery.jqplot.js') add_script(req, 'common/js/jqPlot/excanvas.min.js') add_script(req, 'common/js/jqPlot/plugins/jqplot.pieRenderer.min.js') add_stylesheet(req, 'common/js/jqPlot/jquery.jqplot.css') add_stylesheet(req, 'workload/css/workload.css') add_script(req, 'workload/js/workload.js') add_script_data(req, {'milestone_name': milestone.name}) if not milestone.completed: workload_tag = tag( tag.h2("Remaining Work ", class_="inline-block"), tag.i(id_='workload-help',class_='fa fa-question-circle color-muted'), tag.div( tag.div(id_='milestone-workload', class_='milestone-info span6 center', style="display:inline;" ), tag.div(id_='milestone-workload-hours', class_='milestone-info span6 center', style="display:inline;" ), id_="workload-charts", class_="row-fluid" ), tag.div( tag.p("Remaining work pie charts are generated to help projects recognise the effort necessary to complete the milestone."), tag.p("The open tickets chart counts the number of open tickets each project member has to complete within the milestone."), tag.p("The remaining hours chart reflects the cumulative estimated hours of efforts required to close these tickets."), tag.p("In both charts only the top {0} members with the highest ticket/hours count are displayed. Remaining members have their data aggregated into a 'other' group.".format(self.user_limit)), id_="workload-dialog", class_="hidden" ), ) stream = stream | Transformer("//*[@id='field-analysis']").after(workload_tag) if self._milestone_has_closed_ticket(milestone.name): workdone_tag = tag( tag.h2("Completed Work ", class_="inline-block"), tag.i(id_='workdone-help', class_='fa fa-question-circle color-muted'), tag.div( tag.div(id_='milestone-workdone', class_='milestone-info span6 center', style="display:inline;" ), tag.div(id_='milestone-workdone-hours', class_='milestone-info span6 center', style="display:inline;" ), id_="workdone-charts", class_="row-fluid" ), tag.div( tag.p("Completed work pie charts are generated to help projects analyse the contribution of members during the milestone."), tag.p("The closed tickets charts counts the number of tickets each project member has completed during the milestone."), tag.p("The hours logged chart reflects the cumulative hours of work that were required to close these tickets."), tag.p("In both charts only the top {0} members with the highest ticket/hours count are displayed. Remaining members have their data aggregated into a 'other' group.".format(self.user_limit)), id_="workdone-dialog", class_="hidden" ), ) div = 'workload-charts' if not milestone.completed else 'field-analysis' stream = stream | Transformer("//*[@id='{0}']".format(div)).after(workdone_tag) return stream
def filter_stream(self, req, method, filename, stream, data): if re.match('/milestone/[^ ]', req.path_info): help_page_url = req.href.help('DefineGuide', 'DefineAgile', 'BurndownCharts') stream = stream | Transformer("//*[@id='milestone-overview']").after(tag( tag.h2("Burn Down Chart ", tag.a( tag.i(class_="fa fa-question-circle color-muted", id_="burndown_more_info"), href=help_page_url, target="_blank") ), tag.div(id_='milestone-burndown', class_='milestone-info') ) ) return stream
def post_process_request(self, req, template, data, content_type): """The actual burndown chart is generated and rendered after page load using a AJAX call which is picked up by the match_request() and process_request() methods. This method determines if we should send that AJAX call, by checking the milestone has both a start and end date. If so, the jqPlot JS files are loaded and the script data 'render_burndown' passed via JSON.""" # check we are on an individual milestone page if req.path_info.startswith("/milestone/") and req.args.get('id') \ and "stats" in data: milestone = self._get_milestone(req) if milestone: # Load the burn down JS file add_script(req, 'burndown/js/burndown.js') if milestone.start: approx_start_date = False else: # no milestone start value so try and estimate start date approx_start_date = self.guess_start_date(milestone) if not approx_start_date: # no milestone start or estimated start date # dont show a burn down chart add_script_data(req, {'render_burndown': False}) return template, data, content_type # If we do have a start date (explicit or implied), # tell JS it should send a request via JSON and use # the default effort value add_script_data(req, { 'render_burndown': True, 'milestone_name': milestone.name, 'print_burndown': False, 'effort_units': self.unit_value, 'approx_start_date': approx_start_date, }) # Add a burndown unit option to the context nav add_ctxtnav(req, tag.div( tag.a( tag.i(class_="fa fa-bar-chart "), " Burn Down Units"), tag.ul( tag.li( tag.a('Tickets', href=None, id_="tickets-metric"), ), tag.li( tag.a('Hours', href=None, id_="hours-metric"), ), tag.li( tag.a('Story Points', href=None, id_="points-metric"), ), class_="styled-dropdown fixed-max" ), class_="dropdown-toggle inline block", ) ) # Add a print link to the context nav add_ctxtnav(req, tag.a( tag.i(class_="fa fa-print"), " Print Burn Down", id_="print-burndown")) # Adds jqPlot library needed by burndown charts self._add_static_files(req) return template, data, content_type
def get_box(self, req): if req.authname == "anonymous": return # hoped that ITicketGroupStatsProvider should tell us what the # set of "active" statuses, but seems # not. DefaultTicketGroupStatsProvider has an idea, from the # ini file, but we want to provide new grouping from the # LogicaOrderTracker module so it has to be at the interface # level rather than component level. db = self.env.get_read_db() cursor = db.cursor() counts_ul = tag.ul() cursor.execute("""SELECT status, COUNT(status) FROM ticket WHERE owner = %s GROUP BY status ORDER BY CASE WHEN status = 'assigned' THEN 1 ELSE 0 END DESC, CASE WHEN status = 'closed' THEN 1 ELSE 0 END ASC, status ASC""", (req.authname,)) for status, count in cursor: link = tag(tag.span(class_="ticket-state state-" + status), tag.a(status,href=req.href.query(owner=req.authname, status=status))) counts_ul.append(tag.li(link, ": ", count)) recent_ul = tag.ul() cursor.execute("""SELECT id FROM ticket WHERE ticket.owner = %s GROUP BY id ORDER BY max(changetime) DESC""", (req.authname,)) shown_count = 0 ts = TicketSystem(self.env) for ticket, in cursor: resource = Resource('ticket', ticket) if "TICKET_VIEW" in req.perm(resource): shown_count = shown_count + 1 if shown_count > 5: break compact = ts.get_resource_description(resource, 'compact') summary = ts.get_resource_description(resource, 'summary') link = tag.a(tag.strong(compact), " ", tag.span(summary), href=req.href.ticket(ticket)) recent_ul.append(tag.li(link)) return tag( tag.div( tag.h3( tag.i( class_="fa fa-ticket" ), " Your Ticket Counts" ), counts_ul, class_='box-sidebar color-none', id="sidebar-count" ), tag.div( tag.h3( tag.i( class_="fa fa-star" ), " Your Recently Modified Tickets" ), recent_ul, class_='box-sidebar', id="sidebar-recent" ) )
def render(self, context, mimetype, content, filename=None, url=None): add_stylesheet(context.req, 'scrippets/css/scrippets-full.css') if hasattr(content, 'read'): content = content.read() mode = "-full" theoutput = tag.div(class_="scrippet" + mode) fd_doc = cElementTree.fromstring(content) for fd_content in fd_doc.findall("Content"): for fd_paragraph in fd_content.findall("Paragraph"): ptype = fd_paragraph.get('Type') if ptype == "Action": ptype = "action" elif ptype == "Character": ptype = "character" elif ptype == "Dialogue": ptype = "dialogue" elif ptype == "Parenthetical": ptype = "parenthetical" elif ptype == "Scene Heading": ptype = "sceneheader" elif ptype == "Shot": ptype = "shot" elif ptype == "Transition": ptype = "transition" elif ptype == "Teaser/Act One": ptype = "header" elif ptype == "New Act": ptype = "header" elif ptype == "End Of Act": ptype = "header" else: ptype = "action" #UNHANDLED FOR THE MOMENT #Show/Ep. Title ptext = [] for fd_text in fd_paragraph.findall("Text"): text_style = fd_text.get('Style') if fd_text.text != None: if "FADE IN:" in fd_text.text.upper(): fd_text.text = fd_text.text.upper() if ptype in [ "character", "transition", "sceneheader", "header", "shot" ]: fd_text.text = fd_text.text.upper() #clean smart quotes fd_text.text = fd_text.text.replace( u"\u201c", "\"").replace(u"\u201d", "\"") #strip double curly quotes fd_text.text = fd_text.text.replace( u"\u2018", "'").replace(u"\u2019", "'").replace( u"\u02BC", "'") #strip single curly quotes ptext.append({ "style": text_style, "text": fd_text.text }) content = [] for block in ptext: if block["style"] == "Italic": content.append(tag.i(block["text"])) elif block["style"] == "Underline": content.append(tag.u(block["text"])) elif block["style"] == "Bold": content.append(tag.b(block["text"])) elif block["style"] == "Bold+Underline": content.append(tag.b(tag.u(block["text"]))) else: content.append(block["text"]) theoutput += tag.p(content, class_=ptype + mode) for fd_titlepage in fd_doc.findall("TitlePage"): for fd_content in fd_titlepage.findall("Content"): for fd_paragraph in fd_content.findall("Paragraph"): ptype = fd_paragraph.get('Type') for fd_text in fd_paragraph.findall("Text"): text_style = fd_text.get('Style') if fd_text.text != None: self.log.debug("SCRIPPET: " + fd_text.text) return "%s" % theoutput
def render(self, context, mimetype, content, filename=None, url=None): add_stylesheet(context.req, 'scrippets/css/scrippets-full.css') if hasattr(content, 'read'): content = content.read() mode = "-full" theoutput = tag.div(class_="scrippet"+mode) fd_doc = cElementTree.fromstring(content) for fd_content in fd_doc.findall("Content"): for fd_paragraph in fd_content.findall("Paragraph"): ptype = fd_paragraph.get('Type') if ptype == "Action": ptype = "action" elif ptype == "Character": ptype = "character" elif ptype == "Dialogue": ptype = "dialogue" elif ptype == "Parenthetical": ptype = "parenthetical" elif ptype == "Scene Heading": ptype = "sceneheader" elif ptype == "Shot": ptype = "shot" elif ptype == "Transition": ptype = "transition" elif ptype == "Teaser/Act One": ptype = "header" elif ptype == "New Act": ptype = "header" elif ptype == "End Of Act": ptype = "header" else: ptype = "action" #UNHANDLED FOR THE MOMENT #Show/Ep. Title ptext = [] for fd_text in fd_paragraph.findall("Text"): text_style = fd_text.get('Style') if fd_text.text != None: if "FADE IN:" in fd_text.text.upper(): fd_text.text = fd_text.text.upper() if ptype in ["character","transition","sceneheader","header","shot"]: fd_text.text = fd_text.text.upper() #clean smart quotes fd_text.text = fd_text.text.replace(u"\u201c", "\"").replace(u"\u201d", "\"") #strip double curly quotes fd_text.text = fd_text.text.replace(u"\u2018", "'").replace(u"\u2019", "'").replace(u"\u02BC", "'") #strip single curly quotes ptext.append({"style":text_style,"text":fd_text.text}) content = [] for block in ptext: if block["style"] == "Italic": content.append(tag.i(block["text"])) elif block["style"] == "Underline": content.append(tag.u(block["text"])) elif block["style"] == "Bold": content.append(tag.b(block["text"])) elif block["style"] == "Bold+Underline": content.append(tag.b(tag.u(block["text"]))) else: content.append(block["text"]) theoutput += tag.p(content,class_=ptype+mode) for fd_titlepage in fd_doc.findall("TitlePage"): for fd_content in fd_titlepage.findall("Content"): for fd_paragraph in fd_content.findall("Paragraph"): ptype = fd_paragraph.get('Type') for fd_text in fd_paragraph.findall("Text"): text_style = fd_text.get('Style') if fd_text.text != None: self.log.debug("SCRIPPET: " + fd_text.text) return "%s" % theoutput
def process_request(self, req): req.perm.assert_permission('TICKET_VIEW') # set the default user query if req.path_info == '/taskboard/set-default-query' and req.method == 'POST': self._set_default_query(req) # these headers are only needed when we update tickets via ajax req.send_header("Cache-Control", "no-cache, no-store, must-revalidate") req.send_header("Pragma", "no-cache") req.send_header("Expires", 0) xhr = req.get_header('X-Requested-With') == 'XMLHttpRequest' group_by = req.args.get("group", "status") user_saved_query = False milestones = Milestone.select_names_select2(self.env, include_complete=False) # Try to find a user selected milestone in request - if not found # check session_attribute for a user saved default, and if that is also # not found and fall back on the most upcoming milestone by due date milestone = req.args.get("milestone") milestone_not_found = False if milestone: try: Milestone(self.env, milestone) except ResourceNotFound: milestone_not_found = True milestone = None if not milestone: # try and find a user saved default default_milestone = req.session.get('taskboard_user_default_milestone') if default_milestone: milestone = default_milestone group_by = req.session.get('taskboard_user_default_group') user_saved_query = True # fall back to most imminent milestone by due date elif len(milestones["results"]): milestone = milestones["results"][0]["text"] # Ajax post if req.args.get("ticket") and xhr: result = self.save_change(req, milestone) req.send(to_json(result), 'text/json') else: data = {} constr = {} if milestone: constr['milestone'] = [milestone] # Ajax update: tickets changed between a period if xhr: from_iso = req.args.get("from", "") to_iso = req.args.get("to", "") if from_iso and to_iso: constr['changetime'] = [from_iso + ".." + to_iso] # Get all tickets by milestone and specify ticket fields to retrieve cols = self._get_display_fields(req, user_saved_query) tickets = self._get_permitted_tickets(req, constraints=constr, columns=cols) sorted_cols = sorted([f for f in self.valid_display_fields if f['name'] not in ('summary', 'type')], key=lambda f: f.get('label')) if tickets: s_data = self.get_ticket_data(req, milestone, group_by, tickets) s_data['total_tickets'] = len(tickets) s_data['display_fields'] = cols data['cur_group'] = s_data['groupName'] else: s_data = {} data['cur_group'] = group_by if xhr: if constr.get("changetime"): s_data['otherChanges'] = \ self.all_other_changes(req, tickets, constr['changetime']) req.send(to_json(s_data), 'text/json') else: s_data.update({ 'formToken': req.form_token, 'milestones': milestones, 'milestone': milestone, 'group': group_by, 'default_columns': self.default_display_fields }) data.update({ 'milestone_not_found': milestone_not_found, 'current_milestone': milestone, 'group_by_fields': self.valid_grouping_fields, 'fields': dict((f['name'], f) for f in self.valid_display_fields), 'all_columns': [f['name'] for f in sorted_cols], 'col': cols, 'condensed': self._show_condensed_view(req, user_saved_query) }) add_script(req, 'agiletools/js/update_model.js') add_script(req, 'agiletools/js/taskboard.js') add_script(req, 'common/js/query.js') add_script_data(req, s_data) add_stylesheet(req, 'agiletools/css/taskboard.css') add_stylesheet(req, 'common/css/ticket.css') add_ctxtnav(req, tag.a(tag.i(class_='fa fa-bookmark'), _(" Set as default"), id_='set-default-query', title=_("Make this your default taskboard"))) return "taskboard.html", data, None
def __call__(self,obj,depth=0,dontRender=False): from genshi.builder import tag if depth>self.maxDepth: raise RuntimeError("Maximum nesting depth %d exceeded"%self.maxDepth) kw=self.padding.copy() if depth>0: kw.update(width='100%') # was [1:] to omit leading woo./wooExtra., but that is not desirable head=tag.b('.'.join(obj.__class__.__module__.split('.')[0:])+'.'+obj.__class__.__name__) head=tag.a(head,href=woo.document.makeObjectUrl(obj),title=_ensureUnicode(obj.__class__.__doc__)) ret=tag.table(tag.th(head,colspan=3,align='left'),frame='box',rules='all',**kw) # get all attribute traits first traits=obj._getAllTraits() for trait in traits: if trait.hidden or (self.hideNoGui and trait.noGui) or trait.noDump or (trait.hideIf and eval(trait.hideIf,globals(),{'self':obj})): continue # start new group (additional line) if trait.startGroup: ret.append(tag.tr(tag.td(tag.i(u'▸ %s'%_ensureUnicode(trait.startGroup)),colspan=3))) attr=getattr(obj,trait.name) if self.showDoc: tr=tag.tr(tag.td(_ensureUnicode(trait.doc))) else: try: tr=tag.tr(tag.td(tag.a(trait.name,href=woo.document.makeObjectUrl(obj,trait.name),title=_ensureUnicode(trait.doc)))) except UnicodeEncodeError: print 'ERROR: UnicodeEncodeError while formatting the attribute ',obj.__class__.__name__+'.'+trait.name print 'ERROR: the docstring is',trait.doc raise # tr=tag.tr(tag.td(trait.name if not self.showDoc else trait.doc.decode('utf-8'))) # nested object if isinstance(attr,woo.core.Object): tr.append([tag.td(self(attr,depth+1),align='justify'),tag.td()]) # sequence of objects (no units here) elif hasattr(attr,'__len__') and len(attr)>0 and isinstance(attr[0],woo.core.Object): tr.append(tag.td(tag.ol([tag.li(self(o,depth+1)) for o in attr]))) else: # !! make deepcopy so that the original object is not modified !! import copy attr=copy.deepcopy(attr) if not trait.multiUnit: # the easier case if not trait.prefUnit: unit=u'−' else: unit=_ensureUnicode(trait.prefUnit[0][0]) # create new list, where entries are multiplied by the multiplier if type(attr)==list: attr=[a*trait.prefUnit[0][1] for a in attr] else: attr=attr*trait.prefUnit[0][1] else: # multiple units unit=[] wasList=isinstance(attr,list) if not wasList: attr=[attr] # handle uniformly for i in range(len(attr)): attr[i]=[attr[i][j]*trait.prefUnit[j][1] for j in range(len(attr[i]))] for pu in trait.prefUnit: unit.append(_ensureUnicode(pu[0])) if not wasList: attr=attr[0] unit=', '.join(unit) # sequence type, or something similar if hasattr(attr,'__len__') and not isinstance(attr,str): if len(attr)>0: tr.append(tag.td(self.htmlSeq(attr,insideTable=False),align='right')) else: tr.append(tag.td(tag.i('[empty]'),align='right')) else: tr.append(tag.td(float2str(attr) if isinstance(attr,float) else str(attr),align='right')) if unit: tr.append(tag.td(unit,align='right')) ret.append(tr) if depth>0 or dontRender: return ret return ret.generate().render('xhtml',encoding='ascii')+b'\n'
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