def _validate_common(self, req, biff_values): name = req.args.get('name') cc = req.args.get('cc') filename = req.args.get('filename') if not (name and filename): add_warning(req, _('Name and Filename is required.')) return False if re.search(WHITESPACE_PATTERN, name): add_warning(req, _('Whitespace is not allowed for the name.')) return False if cc: all_users = [_u for _u, __, __ in self.env.get_known_users()] cc_users = [_u.strip() for _u in cc.split(',')] for user in cc_users: if user not in all_users: _msg = _("The user '%(user)s' is not existed.", user=user) add_warning(req, _msg) return False biff_names = map(lambda x: x['name'], biff_values) if name in biff_names: add_warning(req, _('The name is already used.')) return False multiple_fields = ['filename'] for field_name in multiple_fields: if not self._validate_multiple_field(req, field_name, biff_values): return False return True
def validate_ticket(self, req, ticket): action = req.args.get('action') if action in self.opt_skip_validation: return if action == 'resolve': for parent, child in self.env.db_query( """ SELECT parent, child FROM subtickets WHERE parent=%s """, (ticket.id, )): if Ticket(self.env, child)['status'] != 'closed': yield None, _("""Cannot close/resolve because child ticket #%(child)s is still open""", child=child) elif action == 'reopen': ids = set(NUMBERS_RE.findall(ticket['parents'] or '')) for id in ids: if Ticket(self.env, id)['status'] == 'closed': msg = _( "Cannot reopen because parent ticket #%(id)s " "is closed", id=id) yield None, msg
def id_is_valid(num): try: return 0 < int(num) <= 1L << 31 except ValueError: raise ResourceNotFound( _("TracForm %(form_id)s does not exist.", form_id=num), _("Invalid form number"))
def _render_change(old, new): rendered = None if old and not new: rendered = tag( Markup(_("%(value)s reset to default value", value=tag.em(old)))) elif new and not old: rendered = tag( Markup(_("from default value set to %(value)s", value=tag.em(new)))) elif old and new: if len(old) < 20 and len(new) < 20: rendered = tag( Markup( _("changed from %(old)s to %(new)s", old=tag.em(old), new=tag.em(new)))) else: nbsp = tag.br() # TRANSLATOR: same as before, but with additional line breaks rendered = tag( Markup( _("changed from %(old)s to %(new)s", old=tag.em(nbsp, old), new=tag.em(nbsp, new)))) return rendered
def post_process_request(self, req, template, data, content_type): env = self.env page = req.path_info realm, resource_id = resource_from_page(env, page) # break (recursive) search for form in forms realm if tfpageRE.match(page) == None and resource_id is not None: if page == '/wiki' or page == '/wiki/': page = '/wiki/WikiStart' form = Form(env, realm, resource_id) if 'FORM_VIEW' in req.perm(form.resource): if len(form.siblings) == 0: # no form record found for this parent resource href = req.href.form() return (template, data, content_type) elif form.resource.id is not None: # single form record found href = req.href.form(form.resource.id) else: # multiple form records found href = req.href.form(action='select', realm=realm, resource_id=resource_id) add_ctxtnav(req, _("Form details"), href=href, title=_("Review form data")) elif page.startswith('/form') and not resource_id == '': form = Form(env, form_id=resource_id) parent = form.resource.parent if len(form.siblings) > 1: href = req.href.form(action='select', realm=parent.realm, resource_id=parent.id) add_ctxtnav(req, _("Back to forms list"), href=href) return (template, data, content_type)
def filter_stream(self, req, method, filename, stream, data): if req.path_info.startswith('/ticket/'): div = None if 'ticket' in data: # get parents data ticket = data['ticket'] # title div = tag.div(class_='description') if ticket['status'] != 'closed': link = tag.a(_('add'), href=req.href.newticket(parents=ticket.id), title=_('Create new child ticket')) link = tag.span('(', link, ')', class_='addsubticket') else: link = None div.append(tag.h3(_('Subtickets '), link)) if 'subtickets' in data: # table tbody = tag.tbody() div.append(tag.table(tbody, class_='subtickets')) # tickets def _func(children, depth=0): for id in sorted(children, key=lambda x: int(x)): ticket = Ticket(self.env, id) # 1st column attrs = {'href': req.href.ticket(id)} if ticket['status'] == 'closed': attrs['class_'] = 'closed' link = tag.a('#%s' % id, **attrs) summary = tag.td(link, ': %s' % ticket['summary'], style='padding-left: %dpx;' % (depth * 15)) # 2nd column type = tag.td(ticket['type']) # 3rd column status = tag.td(ticket['status']) # 4th column href = req.href.query(status='!closed', owner=ticket['owner']) owner = tag.td(tag.a(ticket['owner'], href=href)) tbody.append(tag.tr(summary, type, status, owner)) if depth >= 0: _func(children[id], depth + 1) if self.recursion: _func(data['subtickets']) else: _func(data['subtickets'],-1) if div: add_stylesheet(req, 'subtickets/css/subtickets.css') stream |= Transformer('.//div[@id="ticket"]').append(div) return stream
def filter_stream(self, req, method, filename, stream, data): if req.path_info.startswith('/ticket/'): div = None if 'ticket' in data: # get parents data ticket = data['ticket'] # title div = tag.div(class_='description') if ticket['status'] != 'closed': link = tag.a(_('add'), href=req.href.newticket(parents=ticket.id), title=_('Create new child ticket')) link = tag.span('(', link, ')', class_='addsubticket') else: link = None div.append(tag.h3(_('Subtickets '), link)) if 'subtickets' in data: # table tbody = tag.tbody() div.append(tag.table(tbody, class_='subtickets')) # tickets def _func(children, depth=0): for id in sorted(children, key=lambda x: int(x)): ticket = Ticket(self.env, id) # 1st column attrs = {'href': req.href.ticket(id)} if ticket['status'] == 'closed': attrs['class_'] = 'closed' link = tag.a('#%s' % id, **attrs) summary = tag.td(link, ': %s' % ticket['summary'], style='padding-left: %dpx;' % (depth * 15)) # 2nd column type = tag.td(ticket['type']) # 3rd column status = tag.td(ticket['status']) # 4th column href = req.href.query(status='!closed', owner=ticket['owner']) owner = tag.td(tag.a(ticket['owner'], href=href)) tbody.append(tag.tr(summary, type, status, owner)) _func(children[id], depth + 1) _func(data['subtickets']) if div: add_stylesheet(req, 'subtickets/css/subtickets.css') stream |= Transformer('.//div[@id="ticket"]').append(div) return stream
def post_process_request(self, req, template, data, content_type): if req.path_info.startswith('/ticket/'): # In case of an invalid ticket, the data is invalid if not data: return template, data, content_type tkt = data['ticket'] self.pm.check_component_enabled(self, pid=tkt.pid) links = TicketLinks(self.env, tkt) # Add link to depgraph if needed if links: add_ctxtnav(req, _('Depgraph'), req.href.depgraph(get_resource_url(self.env, tkt.resource))) for change in data.get('changes', {}): if not change.has_key('fields'): continue for field, field_data in change['fields'].iteritems(): if field in self.fields: vals = {} for i in ('new', 'old'): if isinstance(field_data[i], basestring): val = field_data[i].strip() else: val = '' if val: vals[i] = set([int(n) for n in val.split(',')]) else: vals[i] = set() add = vals['new'] - vals['old'] sub = vals['old'] - vals['new'] elms = tag() if add: elms.append( tag.em(u', '.join([unicode(n) for n in sorted(add)])) ) elms.append(u' added') if add and sub: elms.append(u'; ') if sub: elms.append( tag.em(u', '.join([unicode(n) for n in sorted(sub)])) ) elms.append(u' removed') field_data['rendered'] = elms #add a link to generate a dependency graph for all the tickets in the milestone if req.path_info.startswith('/milestone/'): if not data or not 'milestone' in data: return template, data, content_type milestone=data['milestone'] self.pm.check_component_enabled(self, pid=milestone.pid) add_ctxtnav(req, _('Depgraph'), req.href.depgraph(get_resource_url(self.env, milestone.resource))) return template, data, content_type
def _gen_wiki_links(self, wiki, label, a_class, url, wiki_page_template): if WikiSystem(self.env).has_page(wiki.lstrip('/')): a_class += " page" title = _("Go to page %s") % wiki else: url += "?action=edit" # adding template name, if specified if wiki_page_template != "": url += "&template=" + wiki_page_template title = _("Create page %s") % wiki link = tag.a(tag(label), href=url) link(class_=a_class, title_=title) return link
def validate_ticket(self, req, ticket, action): if not ticket.exists: # new ticket return if not action: yield None, _('Valid ticket action must be provided to validate ticket dependencies') return syllabus_id = ticket.syllabus_id actions = self.check_actions.syllabus(syllabus_id) if action['alias'] in actions: links = TicketLinks(self.env, ticket) for i in links.blocked_by: if Ticket(self.env, i)['status'] != 'closed': yield None, _('Ticket #%(id)s is blocking this ticket', id=i)
def validate_ticket(self, req, ticket): action = req.args.get('action') if action == 'resolve': db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT parent, child FROM subtickets WHERE parent=%s", [str(ticket.id)] ) for parent, child in cursor: if Ticket(self.env, child)['status'] != 'closed': yield None, _('Child ticket #%s has not been closed yet') % child elif action == 'reopen': ids = set(NUMBERS_RE.findall(ticket['parents'] or '')) for id in ids: if Ticket(self.env, id)['status'] == 'closed': yield None, _('Parent ticket #%s is closed') % id
def get_tracform_ids(self, src, cursor=None): """Returns all child forms of resource specified by parent realm and parent id as a list of tuples (form_id and corresponding subcontext). """ cursor = self.get_cursor(cursor) sql = """ SELECT id, subcontext FROM forms WHERE realm = %s AND resource_id = %s """ ids = [] results = cursor(sql, *src) if results is not None: for form_id, subcontext in results: ids.append(tuple([int(form_id), subcontext])) else: raise ResourceNotFound( _("""No data recorded for a TracForms form in %(realm)s:%(parent_id)s """, realm=realm, parent_id=resource_id)) return ids
def __init__(self): # bind 'wikicalendar' catalog to the specified locale directory locale_dir = resource_filename(__name__, 'locale') add_domain(self.env.path, locale_dir) # Read options from Trac configuration system, adjustable in trac.ini. # [wiki] section self.sanitize = True if self.config.getbool('wiki', 'render_unsafe_content') is True: self.sanitize = False # TRANSLATOR: Keep macro doc style formatting here, please. self.doc_calendar = _( """Inserts a small calendar where each day links to a wiki page whose name matches `wiki-page-format`. The current day is highlighted, and days with Milestones are marked in bold. This version makes heavy use of CSS for formatting. Usage: {{{ [[WikiCalendar([year, [month, [show-buttons, [wiki-page-format]]]])]] }}} Arguments: 1. `year` (4-digit year) - defaults to `*` (current year) 1. `month` (2-digit month) - defaults to `*` (current month) 1. `show-buttons` (boolean) - defaults to `true` 1. `wiki-page-format` (string) - defaults to `%Y-%m-%d` Examples: {{{ [[WikiCalendar(2006,07)]] [[WikiCalendar(2006,07,false)]] [[WikiCalendar(*,*,true,Meeting-%Y-%m-%d)]] [[WikiCalendar(2006,07,false,Meeting-%Y-%m-%d)]] }}} """) self.doc_ticketcalendar = _( """Display Milestones and Tickets in a calendar view. displays a calendar, the days link to: - milestones (day in bold) if there is one on that day - a wiki page that has wiki_page_format (if exist) - create that wiki page if it does not exist - use page template (if exist) for new wiki page """)
def update_ticket_field(self, authname, old_value, new_value): ticket_ids = self._get_ticket_ids(old_value) if not ticket_ids: return comment = _('Updated File Biff field value by Trac administrator.') fb_methodcaller = methodcaller('update', old_value, new_value) self._update_field(authname, comment, ticket_ids, fb_methodcaller)
def __init__(self, env, form_resource_or_parent_realm=None, parent_id=None, subcontext=None, form_id=None, version=None): self.env = env # prepare db access self.forms = FormSystem(env) self.realm = 'form' self.subcontext = subcontext self.siblings = [] # DEVEL: support for handling form revisions not implemented yet if isinstance(form_resource_or_parent_realm, Resource): self.resource = form_resource_or_parent_realm parent = self.resource.parent if self.siblings == []: self._get_siblings(parent.realm, parent.id) else: parent_realm = form_resource_or_parent_realm if form_id not in [None, ''] and self.id_is_valid(form_id): self.id = int(form_id) else: self.id = None if self.id is not None and (parent_realm is None or \ parent_id is None or subcontext is None): # get complete context, required as resource parent ctxt = self.forms.get_tracform_meta(self.id)[1:4] parent_realm = ctxt[0] parent_id = ctxt[1] self.subcontext = ctxt[2] elif isinstance(parent_realm, basestring) and \ parent_id is not None and self.id is None: # find form(s), if parent descriptors are available if subcontext is not None: ctxt = tuple([parent_realm, parent_id, subcontext]) self.id = self.forms.get_tracform_meta(ctxt)[0] self._get_siblings(parent_realm, parent_id) if isinstance(parent_realm, basestring) and \ parent_id is not None: self.resource = Resource(parent_realm, parent_id ).child('form', self.id, version) else: raise ResourceNotFound( _("""No data recorded for a TracForms form in %(realm)s:%(parent_id)s """, realm=parent_realm, parent_id=parent_id), subcontext and _("with subcontext %(subcontext)s", subcontext=subcontext) or '')
def remove_ticket_field(self, authname, remove_values): for value in remove_values: ticket_ids = self._get_ticket_ids(value) if not ticket_ids: continue comment = _('Removed File Biff field value by Trac administrator.') fb_methodcaller = methodcaller('remove', value) self._update_field(authname, comment, ticket_ids, fb_methodcaller)
def _add_per_ticket_type_option(self, ticket_type): self.opt_inherit_fields[ticket_type] = ListOption \ ('subtickets','type.%s.child_inherits' % ticket_type, default='', doc = _(""" Comma-separated list of ticket fields whose values are to be copied from a parent ticket into a newly created child ticket """) ) self.opt_columns[ticket_type] = ListOption \ ('subtickets', 'type.%s.table_columns' % ticket_type, default='status,owner', doc = _(""" Comma-separated list of ticket fields whose values are to be shown for each child ticket in the subtickets list """) )
def _render_fields(self, form_id, state): fields = json.loads(state is not None and state or '{}') rendered = [] for name, value in fields.iteritems(): if value == 'on': value = _("checked (checkbox)") elif value == '': value = _("empty (text field)") else: value = '\'' + value + '\'' author, time = self.get_tracform_fieldinfo(form_id, name) rendered.append( {'name': name, 'value': value, 'author': tag.span(tag_("by %(author)s", author=author), class_='author'), 'time': time is not None and tag.span( format_datetime(time), class_='date') or None}) return rendered
def _render_change(old, new): rendered = None if old and not new: rendered = tag(Markup(_("%(value)s reset to default value", value=tag.em(old)))) elif new and not old: rendered = tag(Markup(_("from default value set to %(value)s", value=tag.em(new)))) elif old and new: if len(old) < 20 and len(new) < 20: rendered = tag(Markup(_("changed from %(old)s to %(new)s", old=tag.em(old), new=tag.em(new)))) else: nbsp = tag.br() # TRANSLATOR: same as before, but with additional line breaks rendered = tag(Markup(_("changed from %(old)s to %(new)s", old=tag.em(nbsp, old), new=tag.em(nbsp, new)))) return rendered
def validate_ticket(self, req, ticket): action = req.args.get('action') if action == 'resolve': db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute( "SELECT parent, child FROM subtickets WHERE parent=%s", (ticket.id, )) for parent, child in cursor: if Ticket(self.env, child)['status'] != 'closed': yield None, _( 'Child ticket #%s has not been closed yet') % child elif action == 'reopen': ids = set(NUMBERS_RE.findall(ticket['parents'] or '')) for id in ids: if Ticket(self.env, id)['status'] == 'closed': yield None, _('Parent ticket #%s is closed') % id
def _do_switch(self, env, req, form): data = {'_dgettext': dgettext} data['page_title'] = get_resource_description(env, form.resource, href=req.href) data['title'] = get_resource_shortname(env, form.resource) data['siblings'] = [] for sibling in form.siblings: form_id = tag.strong(tag.a( _("Form %(form_id)s", form_id=sibling[0]), href=req.href.form(sibling[0]))) if sibling[1] == '': data['siblings'].append(form_id) else: # TRANSLATOR: Form list entry for form select page data['siblings'].append(tag(Markup(_( "%(form_id)s (subcontext = '%(subcontext)s')", form_id=form_id, subcontext = sibling[1])))) add_stylesheet(req, 'tracforms/tracforms.css') return 'switch.html', data, None
def _validate_multiple_field(self, req, field_name, biff_values): _values = map(lambda x: x[field_name], biff_values) biff_fvalues = [__f.strip() for _f in _values for __f in _f.split(',')] fvalues = [_f.strip() for _f in req.args.get(field_name).split(',')] for fvalue in fvalues: if fvalue in biff_fvalues: _msg = _("The value '%(fvalue)s' is already configured.", fvalue=fvalue) add_warning(req, _msg) return False return True
def build_form(self, text): if not self.subform: form_class = self.form_class form_cssid = self.form_cssid or self.subcontext form_name = self.form_name or self.subcontext dest = self.formatter.req.href('/form/update') yield ('<FORM class="printableform" ' + 'method="POST" action=%r' % str(dest) + (form_cssid is not None and ' id="%s"' % form_cssid or '') + (form_name is not None and ' name="%s"' % form_name or '') + (form_class is not None and ' class="%s"' % form_class or '') + '>') yield text if self.allow_submit: # TRANSLATOR: Default submit button label submit_label = self.submit_label or _("Update Form") yield '<INPUT class="buttons" type="submit"' if not self.macro.save_tracform_allowed(self.context[0], self.context[1]): yield ' disabled="disabled"' if self.submit_name: yield ' name=%r' % str(self.submit_name) yield ' value=%r' % xml_escape(submit_label) yield '>' if self.keep_history: yield '<INPUT type="hidden"' yield ' name="__keep_history__" value="yes">' if self.track_fields: yield '<INPUT type="hidden"' yield ' name="__track_fields__" value="yes">' if self.form_updated_on is not None: yield '<INPUT type="hidden" name="__basever__"' yield ' value="' + str(self.form_updated_on) + '">' context = json.dumps( self.context, separators=(',', ':')) yield '<INPUT type="hidden" ' + \ 'name="__context__" value=%r>' % context backpath = self.formatter.req.href(self.formatter.req.path_info) yield '<INPUT type="hidden" ' \ 'name="__backpath__" value=%s>' % str(backpath) form_token = self.formatter.req.form_token yield '<INPUT type="hidden" ' \ 'name="__FORM_TOKEN" value=%r>' % str(form_token) yield '</FORM>' else: yield text
def get(self, search, default=None, singleton=True, all=False): values = tuple(dict.__getitem__(self, key) for key in sorted(self.keyset(search, all))) if singleton: if not values: return default elif len(values) == 1: return values[0] else: raise ValueError( _("Too many results for singleton %r" % key)) else: return values
def get(self, search, default=None, singleton=True, all=False): values = tuple( dict.__getitem__(self, key) for key in sorted(self.keyset(search, all))) if singleton: if not values: return default elif len(values) == 1: return values[0] else: raise ValueError(_("Too many results for singleton %r" % key)) else: return values
def _render_fields(self, req, form_id, state): fields = json.loads(state is not None and state or '{}') rendered = [] for name, value in fields.iteritems(): if value == 'on': value = _("checked (checkbox)") elif value == '': value = _("empty (text field)") elif isinstance(value, str): value = "'".join(['', value, '']) else: # Still try to display something useful instead of corrupting # parent page beyond hope of recovery through the web_ui. value = "'".join(['', repr(value), '']) author, time = self.get_tracform_fieldinfo(form_id, name) author = format_author(self.env, req, author, 'value') rendered.append( {'name': name, 'value': value, 'author': tag.span(tag(_("by %(author)s", author=author)), class_='author'), 'time': time}) return rendered
def _do_switch(self, env, req, form): data = {'_dgettext': dgettext} data['page_title'] = get_resource_description(env, form.resource, href=req.href) data['title'] = get_resource_shortname(env, form.resource) data['siblings'] = [] for sibling in form.siblings: form_id = tag.strong( tag.a(_("Form %(form_id)s", form_id=sibling[0]), href=req.href.form(sibling[0]))) if sibling[1] == '': data['siblings'].append(form_id) else: # TRANSLATOR: Form list entry for form select page data['siblings'].append( tag( Markup( _("%(form_id)s (subcontext = '%(subcontext)s')", form_id=form_id, subcontext=sibling[1])))) add_stylesheet(req, 'tracforms/tracforms.css') return 'switch.html', data, None
def build_form(self, text): if not self.subform: form_class = self.form_class form_cssid = self.form_cssid or self.subcontext form_name = self.form_name or self.subcontext dest = self.formatter.req.href('/form/update') yield ('<FORM class="printableform" ' + 'method="POST" action=%r' % str(dest) + (form_cssid is not None and ' id="%s"' % form_cssid or '') + (form_name is not None and ' name="%s"' % form_name or '') + (form_class is not None and ' class="%s"' % form_class or '') + '>') yield text if self.allow_submit: # TRANSLATOR: Default submit button label submit_label = self.submit_label or _("Update Form") yield '<INPUT class="buttons" type="submit"' if self.submit_name: yield ' name=%r' % str(self.submit_name) yield ' value=%r' % xml_escape(submit_label) yield '>' if self.keep_history: yield '<INPUT type="hidden"' yield ' name="__keep_history__" value="yes">' if self.track_fields: yield '<INPUT type="hidden"' yield ' name="__track_fields__" value="yes">' if self.form_updated_on is not None: yield '<INPUT type="hidden" name="__basever__"' yield ' value="' + str(self.form_updated_on) + '">' context = json.dumps( self.context, separators=(',', ':')) yield '<INPUT type="hidden" ' + \ 'name="__context__" value=%r>' % context backpath = self.formatter.req.href(self.formatter.req.path_info) yield '<INPUT type="hidden" ' \ 'name="__backpath__" value=%s>' % str(backpath) form_token = self.formatter.req.form_token yield '<INPUT type="hidden" ' \ 'name="__FORM_TOKEN" value=%r>' % str(form_token) yield '</FORM>' else: yield text
def create_node(tkt): node = g.get_node(tkt.id) summary = q(tkt['summary']) if label_summary: node['label'] = u'#%s %s' % (tkt.id, summary) else: node['label'] = u'#%s'%tkt.id if tkt['status'] == 'closed': color = tkt['resolution'] in bc_resolutions and self.bad_closed_color or self.closed_color else: color = self.opened_color node['fillcolor'] = color node['URL'] = req.href.ticket(tkt.id) node['alt'] = _('Ticket #%(id)s', id=tkt.id) node['tooltip'] = summary.replace('\\n', ' ') return node
def render_admin_panel(self, req, cat, page, biff_key): req.perm.require('TICKET_ADMIN') biff_config = ChangefileBiffConfig(self.env, self.config) template = 'changefilebiff_admin.html' if biff_key: # in detail view if req.method == 'POST': if req.args.get('save') and \ self._validate_update(req, biff_key, biff_config): biff_config.update(req.authname, req.args, biff_key) self._add_notice_saved(req) req.redirect(req.href.admin(cat, page)) elif req.args.get('cancel'): req.redirect(req.href.admin(cat, page)) biff = biff_config.biff[biff_key] return template, {'view': 'detail', 'biff': biff} # in list view if req.method == 'POST': if req.args.get('add') and self._validate_add(req, biff_config): biff_config.add(req.args) self._add_notice_saved(req) req.redirect(req.href.admin(cat, page)) elif req.args.get('remove'): select_keys = req.args.get('sel') if select_keys: if not isinstance(select_keys, list): select_keys = [select_keys] biff_config.remove(req.authname, select_keys) self._add_notice_removed(req) req.redirect(req.href.admin(cat, page)) else: add_warning(req, _('No Biff configuration selected.')) biff_values = biff_config.biff.values() return template, { 'view': 'list', 'biff': biff_config.get_i18n_message_catalog(), 'biff_values': biff_values }
def execute(self): formatter = self.formatter args = self.args name = self.name # Look in the formatter req object for evidence we are executing. self.subform = getattr(formatter.req, type(self).__name__, False) if not self.subform: setattr(formatter.req, type(self).__name__, True) self.env = dict(getattr(formatter.req, 'tracform_env', ())) # Setup preliminary context self.page = formatter.req.path_info if self.page == '/wiki' or self.page == '/wiki/': self.page = '/wiki/WikiStart' realm, resource_id = resource_from_page(formatter.env, self.page) # Remove leading comments and process commands. textlines = [] errors = [] srciter = iter(args.split('\n')) for line in srciter: if line[:1] == '#': # Comment or operation. line = line.strip()[1:] if line[:1] == '!': # It's a command, parse the arguments... kw = {} args = list(self.getargs(line[1:], kw)) if len(args): cmd = args.pop(0) fn = getattr(self, 'cmd_' + cmd.lower(), None) if fn is None: errors.append( _("ERROR: No TracForms command '%s'" % cmd)) else: try: fn(*args, **kw) except FormError, e: errors.append(str(e)) except Exception, e: errors.append(traceback.format_exc())
def filter_stream(self, req, method, filename, stream, data): if req.path_info.startswith('/ticket/'): div = None if 'ticket' in data: # get parents data ticket = data['ticket'] # title div = tag.div(class_='description') if ticket['status'] != 'closed': link = tag.a(_('add'), href=req.href.newticket(parents=ticket.id), title=_('Create new child ticket')) link = tag.span('(', link, ')', class_='addsubticket') else: link = None div.append(tag.h3(_('Subtickets '), link)) if 'subtickets' in data: # table tbody = tag.tbody() div.append(tag.table(tbody, class_='subtickets')) # tickets def _func(children, depth=0): def _sort(children): for sort in reversed(literal_eval(self.sort_children)): transform_key = lambda x: x if isinstance(sort, str): sort_by = sort else: assert(isinstance(sort, list)) assert(isinstance(sort[0], str)) sort_by = sort[0] if isinstance(sort[1], str): if sort[1] == "int": transform_key = int else: assert(sort[1] == "float") transform_key = float else: assert(isinstance(sort[1], list)) lookup_dict = {v: k for (k, v) in enumerate(sort[1])} def _lookup(value): try: return lookup_dict[value] except KeyError: return len(lookup_dict) transform_key = _lookup if sort_by == 'id': children = sorted(children, key=lambda x: transform_key(Ticket(self.env, int(x)).id)) else: children = sorted(children, key=lambda x: transform_key(Ticket(self.env, int(x))[sort_by])) return children for id in _sort(children): ticket = Ticket(self.env, id) properties_to_show = [] # 1st column attrs = {'href': req.href.ticket(id)} if ticket['status'] == 'closed': attrs['class_'] = 'closed' link = tag.a('#%s' % id, **attrs) properties_to_show.append(tag.td(link, ': %s' % ticket['summary'], style='padding-left: %dpx;' % (depth * 15))) for property in literal_eval(self.show_fields): properties_to_show.append(tag.td(ticket[property])) tbody.append(apply(tag.tr, properties_to_show)) _func(children[id], depth + 1) _func(data['subtickets']) if div: add_stylesheet(req, 'subtickets/css/subtickets.css') stream |= Transformer('.//div[@id="ticket"]').append(div) div_accumulations = None accumulations = literal_eval(self.show_accumulations) if 'subtickets' in data and accumulations: def _accumulate(children, field, method): assert(method == 'sum') result = 0 for id in children: ticket = Ticket(self.env, id) try: result += float(ticket[field]) except ValueError: pass result += _accumulate(children[id], field, method) return result div_accumulations = tag.div(class_='description') tbody = tag.tbody() div_accumulations(tag.table(tbody, class_='properties')) for accumulation in accumulations: tbody.append(tag.tr(tag.td(accumulation[1]), tag.td(_accumulate(data['subtickets'], accumulation[0], accumulation[2])))) if div_accumulations: stream |= Transformer('.//div[@id="ticket"]').append(div_accumulations) return stream
def save_tracform(self, src, state, author, base_version=None, keep_history=False, track_fields=False, cursor=None): cursor = self.get_cursor(cursor) (form_id, realm, resource_id, subcontext, last_updater, last_updated_on, form_keep_history, form_track_fields) = self.get_tracform_meta(src) if form_keep_history is not None: keep_history = form_keep_history old_state = self.get_tracform_state(src) if form_track_fields is not None: track_fields = form_track_fields if base_version is not None: base_version = int(base_version or 0) if ((base_version is None and last_updated_on is None) or (base_version == last_updated_on)): if state != old_state: updated_on = int(time.time()) if form_id is None: form_id = cursor(""" INSERT INTO forms (realm, resource_id, subcontext, state, author, time) VALUES (%s, %s, %s, %s, %s, %s) """, realm, resource_id, subcontext, state, author, updated_on) \ .last_id(cursor, 'forms', 'id') else: cursor( """ UPDATE forms SET state = %s, author = %s, time = %s WHERE id = %s """, state, author, updated_on, form_id) if keep_history: cursor( """ INSERT INTO forms_history (id, time, author, old_state) VALUES (%s, %s, %s, %s) """, form_id, last_updated_on, last_updater, old_state) if track_fields: # Break down old version and new version. old_fields = json.loads(old_state or '{}') new_fields = json.loads(state or '{}') updated_fields = [] for field, old_value in old_fields.iteritems(): if new_fields.get(field) != old_value: updated_fields.append(field) for field in new_fields: if old_fields.get(field) is None: updated_fields.append(field) self.log.debug('UPDATED: ' + str(updated_fields)) for field in updated_fields: if cursor( """ SELECT COUNT(*) FROM forms_fields WHERE id = %s AND field = %s""", form_id, field).value: cursor( """ UPDATE forms_fields SET author = %s, time = %s WHERE id = %s AND field = %s """, author, updated_on, form_id, field) else: cursor( """ INSERT INTO forms_fields (id, field, author, time) VALUES (%s, %s, %s, %s) """, form_id, field, author, updated_on) else: updated_on = last_updated_on author = last_updater return ((form_id, realm, resource_id, subcontext, state, author, updated_on), (form_id, realm, resource_id, subcontext, old_state, last_updater, last_updated_on)) else: raise ValueError(_("Conflict"))
def get_admin_panels(self, req): if 'TICKET_ADMIN' in req.perm: yield ('ticket', _('Ticket System'), 'filebiff', _('File Biff'))
class Schema(FancyValidator): """ A schema validates a dictionary of values, applying different validators (be key) to the different values. If allow_extra_fields=True, keys without validators will be allowed; otherwise they will raise Invalid. If filter_extra_fields is set to true, then extra fields are not passed back in the results. Validators are associated with keys either with a class syntax, or as keyword arguments (class syntax is usually easier). Something like:: class MySchema(Schema): name = Validators.PlainText() phone = Validators.PhoneNumber() These will not be available as actual instance variables, but will be collected in a dictionary. To remove a validator in a subclass that is present in a superclass, set it to None, like:: class MySubSchema(MySchema): name = None Note that missing fields are handled at the Schema level. Missing fields can have the 'missing' message set to specify the error message, or if that does not exist the *schema* message 'missingValue' is used. """ # These validators will be applied before this schema: pre_validators = [] # These validators will be applied after this schema: chained_validators = [] # If true, then it is not an error when keys that aren't # associated with a validator are present: allow_extra_fields = False # If true, then keys that aren't associated with a validator # are removed: filter_extra_fields = False # If this is given, then any keys that aren't available but # are expected will be replaced with this value (and then # validated!) This does not override a present .if_missing # attribute on validators: if_key_missing = NoDefault # If true, then missing keys will be missing in the result, # if the validator doesn't have if_missing on it already: ignore_key_missing = False compound = True fields = {} order = [] accept_iterator = True messages = dict( notExpected=_('The input field %(name)s was not expected.'), missingValue=_('Missing value'), badDictType=_('The input must be dict-like' ' (not a %(type)s: %(value)r)'), singleValueExpected=_('Please provide only one value'), ) __mutableattributes__ = ('fields', 'chained_validators', 'pre_validators') @staticmethod def __classinit__(cls, new_attrs): FancyValidator.__classinit__(cls, new_attrs) # Don't bother doing anything if this is the most parent # Schema class (which is the only class with just # FancyValidator as a superclass): if cls.__bases__ == (FancyValidator, ): return cls # Scan through the class variables we've defined *just* # for this subclass, looking for validators (both classes # and instances): for key, value in new_attrs.iteritems(): if key in ('pre_validators', 'chained_validators'): if is_validator(value): msg = "Any validator with the name %s will be ignored." % \ (key,) warnings.warn(msg, FERuntimeWarning) continue if is_validator(value): cls.fields[key] = value delattr(cls, key) # This last case means we're overwriting a validator # from a superclass: elif key in cls.fields: del cls.fields[key] for name, value in cls.fields.iteritems(): cls.add_field(name, value) def __initargs__(self, new_attrs): for key, value in new_attrs.iteritems(): if key in ('pre_validators', 'chained_validators'): if is_validator(value): msg = "Any validator with the name %s will be ignored." % \ (key,) warnings.warn(msg, FERuntimeWarning) continue if is_validator(value): self.fields[key] = value delattr(self, key) # This last case means we're overwriting a validator # from a superclass: elif key in self.fields: del self.fields[key] def assert_dict(self, value, state): """ Helper to assure we have proper input """ if not hasattr(value, 'items'): # Not a dict or dict-like object raise Invalid( self.message('badDictType', state, type=type(value), value=value), value, state) def _convert_to_python(self, value_dict, state): if not value_dict: if self.if_empty is not NoDefault: return self.if_empty value_dict = {} for validator in self.pre_validators: value_dict = validator.to_python(value_dict, state) self.assert_dict(value_dict, state) new = {} errors = {} unused = self.fields.keys() if state is not None: previous_key = getattr(state, 'key', None) previous_full_dict = getattr(state, 'full_dict', None) state.full_dict = value_dict try: for name, value in value_dict.items(): try: unused.remove(name) except ValueError: if not self.allow_extra_fields: raise Invalid( self.message('notExpected', state, name=repr(name)), value_dict, state) if not self.filter_extra_fields: new[name] = value continue validator = self.fields[name] # are iterators (list, tuple, set, etc) allowed? if self._value_is_iterator(value) and not getattr( validator, 'accept_iterator', False): errors[name] = Invalid( self.message('singleValueExpected', state), value_dict, state) if state is not None: state.key = name try: new[name] = validator.to_python(value, state) except Invalid, e: errors[name] = e for name in unused: validator = self.fields[name] try: if_missing = validator.if_missing except AttributeError: if_missing = NoDefault if if_missing is NoDefault: if self.ignore_key_missing: continue if self.if_key_missing is NoDefault: try: message = validator.message('missing', state) except KeyError: message = self.message('missingValue', state) errors[name] = Invalid(message, None, state) else: if state is not None: state.key = name try: new[name] = validator.to_python( self.if_key_missing, state) except Invalid, e: errors[name] = e else: new[name] = validator.if_missing if state is not None: state.key = previous_key for validator in self.chained_validators: if (not hasattr(validator, 'validate_partial') or not getattr( validator, 'validate_partial_form', False)): continue try: validator.validate_partial(value_dict, state) except Invalid, e: sub_errors = e.unpack_errors() if not isinstance(sub_errors, dict): # Can't do anything here continue merge_dicts(errors, sub_errors)
def op_when(self, field, format='%m/%d/%Y %H:%M:%S'): when = self.macro.get_tracform_fieldinfo(self.context, field)[1] return (when is not None and format_datetime(when, format=str(format)) or _("unknown"))
class FormNoOperationError(FormError): def __init__(self, name): FormError.__init__(self, name) message = _("ERROR: No TracForms operation '%r'")
def save_tracform(self, src, state, author, base_version=None, keep_history=False, track_fields=False, db=None): (form_id, realm, resource_id, subcontext, last_updater, last_updated_on, form_keep_history, form_track_fields) = self.get_tracform_meta(src, db=db) if form_keep_history is not None: keep_history = form_keep_history old_state = form_id and self.get_tracform_state(form_id) or '{}' if form_track_fields is not None: track_fields = form_track_fields if base_version is not None: base_version = int(base_version or 0) if ((base_version is None and last_updated_on is None) or (base_version == last_updated_on)): if state != old_state: updated_on = int(time.time()) db = self._get_db(db) cursor = db.cursor() if form_id is None: cursor.execute(""" INSERT INTO forms (realm, resource_id, subcontext, state, author, time) VALUES (%s, %s, %s, %s, %s, %s) """, (realm, resource_id, subcontext, state, author, updated_on)) form_id = db.get_last_id(cursor, 'forms') else: cursor.execute(""" UPDATE forms SET state=%s, author=%s, time=%s WHERE id=%s """, (state, author, updated_on, form_id)) if keep_history: cursor.execute(""" INSERT INTO forms_history (id, time, author, old_state) VALUES (%s, %s, %s, %s) """, (form_id, last_updated_on, last_updater, old_state)) if track_fields: # Break down old version and new version. old_fields = json.loads(old_state) new_fields = json.loads(state or '{}') updated_fields = [] for field, old_value in old_fields.iteritems(): if new_fields.get(field) != old_value: updated_fields.append(field) for field in new_fields: if old_fields.get(field) is None: updated_fields.append(field) self.log.debug('UPDATED: ' + str(updated_fields)) for field in updated_fields: cursor.execute(""" SELECT COUNT(*) FROM forms_fields WHERE id=%s AND field=%s""", (form_id, field)) if cursor.fetchone()[0] > 0: cursor.execute(""" UPDATE forms_fields SET author=%s, time=%s WHERE id=%s AND field=%s """, (author, updated_on, form_id, field)) else: cursor.execute(""" INSERT INTO forms_fields (id, field, author, time) VALUES (%s, %s, %s, %s) """, (form_id, field, author, updated_on)) db.commit() else: updated_on = last_updated_on author = last_updater return ((form_id, realm, resource_id, subcontext, state, author, updated_on), (form_id, realm, resource_id, subcontext, old_state, last_updater, last_updated_on)) else: raise ValueError(_("Conflict"))
class FormNoCommandError(FormError): def __init__(self, name): FormError.__init__(self, name) message = _("ERROR: No TracForms command '%r'")
def get_navigation_items(self, req): if 'TICKET_VIEW' not in req.perm: return yield ('mainnav', 'depgraph', tag.a(_('Depgraph'), href=req.href.depgraph()))
def process_request(self, req): path_info = req.path_info[10:] img_format = req.args.get('format') m = self.IMAGE_RE.search(path_info) is_img = m is not None if is_img: img_format = m.group(1) path_info = path_info[:-(10+len(img_format))] is_full_graph = not path_info with_clusters = req.args.getbool('with_clusters', False) cur_pid = self.pm.get_current_project(req) #list of tickets to generate the depgraph for tkt_ids=[] if is_full_graph: # depgraph for full project # cluster by milestone self.pm.check_component_enabled(self, pid=cur_pid) db = self.env.get_read_db() cursor = db.cursor() if with_clusters: q = ''' SELECT milestone, id FROM ticket WHERE project_id=%s ORDER BY milestone, id ''' else: q = ''' SELECT id FROM ticket WHERE project_id=%s ORDER BY id ''' cursor.execute(q, (cur_pid,)) rows = cursor.fetchall() if with_clusters: tkt_ids = rows else: tkt_ids = [r[0] for r in rows] else: # degraph for resource resource = get_real_resource_from_url(self.env, path_info, req.args) # project check res_pid = resource.pid self.pm.check_component_enabled(self, pid=res_pid) if res_pid != cur_pid: self.pm.redirect_to_project(req, res_pid) is_milestone = isinstance(resource, Milestone) #Urls to generate the depgraph for a ticket is /depgraph/ticketnum #Urls to generate the depgraph for a milestone is /depgraph/milestone/milestone_name if is_milestone: #we need to query the list of tickets in the milestone milestone = resource query=Query(self.env, constraints={'milestone' : [milestone.name]}, max=0, project=milestone.pid) tkt_ids=[fields['id'] for fields in query.execute()] else: #the list is a single ticket ticket = resource tkt_ids = [ticket.id] #the summary argument defines whether we place the ticket id or #it's summary in the node's label label_summary=0 if 'summary' in req.args: label_summary=int(req.args.get('summary')) clustering = is_full_graph and with_clusters g = self._build_graph(req, tkt_ids, label_summary=label_summary, with_clusters=clustering) if is_img or img_format: if img_format == 'text': #in case g.__str__ returns unicode, we need to convert it in ascii req.send(to_unicode(g).encode('ascii', 'replace'), 'text/plain') elif img_format == 'debug': import pprint req.send( pprint.pformat( [TicketLinks(self.env, tkt_id) for tkt_id in tkt_ids] ), 'text/plain') elif img_format == 'svg': req.send(g.render(self.dot_path, img_format), 'image/svg+xml') elif img_format is not None: req.send(g.render(self.dot_path, img_format), 'text/plain') if self.use_gs: ps = g.render(self.dot_path, 'ps2') gs = subprocess.Popen([self.gs_path, '-q', '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4', '-sDEVICE=png16m', '-sOutputFile=%stdout%', '-'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) img, err = gs.communicate(ps) if err: self.log.debug('MasterTickets: Error from gs: %s', err) else: img = g.render(self.dot_path) req.send(img, 'image/png') else: data = { 'graph': g, 'graph_render': partial(g.render, self.dot_path), 'use_gs': self.use_gs, 'full_graph': is_full_graph, 'img_format': self.default_format, 'summary': label_summary, 'with_clusters': with_clusters, } if is_full_graph: rsc_url = None else: if is_milestone: resource = milestone.resource add_ctxtnav(req, _('Back to Milestone %(name)s', name=milestone.name), get_resource_url(self.env, resource, req.href)) data['milestone'] = milestone.name else: # ticket data['tkt'] = ticket resource = ticket.resource add_ctxtnav(req, _('Back to Ticket #%(id)s', id=ticket.id), get_resource_url(self.env, resource, req.href)) rsc_url = get_resource_url(self.env, resource) data['img_url'] = req.href.depgraph(rsc_url, 'depgraph.%s' % self.default_format, summary=g.label_summary, with_clusters=int(with_clusters)) return 'depgraph.html', data, None
def get_navigation_items(self, req): if 'TEAMCALENDAR_VIEW' in req.perm: yield ('mainnav', 'teamcalendar', tag.a(_('Team Calendar'), href=req.href.teamcalendar()))
def get_search_filters(self, req): if 'FORM_VIEW' in req.perm: # TRANSLATOR: The realm name used as TracSearch filter label yield ('form', _("Forms"))
def op_who(self, field): # TRANSLATOR: Default updater name who = self.macro.get_tracform_fieldinfo( self.form_id is not None and self.form_id or self.context, field)[0] or _("unknown") return format_author(self.formatter.env, self.formatter.req, who)
class FormTooManyValuesError(FormError): def __init__(self, name): FormError.__init__(self, name) message = _("""ERROR: Too many values for TracForms form variable %r (maybe the same field is being used multiple times?)""")
def op_when(self, field, format='%m/%d/%Y %H:%M:%S'): when = self.macro.get_tracform_fieldinfo( self.form_id is not None and self.form_id or \ self.context, field)[1] return (when is not None and format_datetime( when, format=str(format)) or _("unknown"))
def filter_stream(self, req, method, filename, stream, data): if not req.path_info.startswith('/ticket/'): return stream div = None link = None button = None if 'ticket' in data: # get parents data ticket = data['ticket'] # title div = tag.div(class_='description') if 'TICKET_CREATE' in req.perm(ticket.resource) \ and ticket['status'] != 'closed': opt_inherit = self.env.config.getlist( 'subtickets', 'type.%(type)s.child_inherits' % ticket) if self.opt_add_style == 'link': inh = {f: ticket[f] for f in opt_inherit} link = tag.a(_('add'), href=req.href.newticket(parents=ticket.id, **inh)) link = tag.span('(', link, ')', class_='addsubticket') else: inh = [ tag.input(type='hidden', name=f, value=ticket[f]) for f in opt_inherit ] button = tag.form(tag.div(tag.input( type="submit", value=_("Create"), title=_("Create a child ticket")), inh, tag.input(type="hidden", name="parents", value=str(ticket.id)), class_="inlinebuttons"), method="get", action=req.href.newticket()) div.append(button) div.append(tag.h3(_('Subtickets '), link)) if 'subtickets' in data: # table tbody = tag.tbody() div.append(tag.table(tbody, class_='subtickets')) # tickets self._create_subtickets_table(req, data['subtickets'], tbody) if div: add_stylesheet(req, 'subtickets/css/subtickets.css') ''' If rendered in preview mode, DIV we're interested in isn't a child but the root and transformation won't succeed. According to HTML specification, id's must be unique within a document, so it's safe to omit the leading '.' in XPath expression to select all matching regardless of hierarchy their in. ''' stream |= Transformer('//div[@id="ticket"]').append(div) return stream
def op_who(self, field): # TRANSLATOR: Default updater name who = self.macro.get_tracform_fieldinfo(self.context, field)[0] or _("unknown") return who
class SubTicketsModule(Component): implements(IRequestFilter, ITicketManipulator, ITemplateProvider, ITemplateStreamFilter) # Simple Options opt_skip_validation = ListOption('subtickets', 'skip_closure_validation', default=[], doc=_(""" Normally, reopening a child with a `closed` parent will be refused and closing a parent with non-`closed` children will also be refused. Adding either of `reopen` or `resolve` to this option will make Subtickets skip this validation for the respective action. Separate by comma if both actions are listed. Caveat: This functionality will be made workflow-independent in a future release of !SubTicketsPlugin. """)) opt_recursion_depth = IntOption('subtickets', 'recursion_depth', default=-1, doc=_(""" Limit the number of recursive levels when listing subtickets. Default is infinity, represented by`-1`. The value zero (0) limits the listing to immediate children. """)) opt_add_style = ChoiceOption('subtickets', 'add_style', ['button', 'link'], doc=_(""" Choose whether to make `Add` look like a button (default) or a link """)) opt_owner_url = Option('subtickets', 'owner_url', doc=_(""" Currently undocumented. """)) # Per-ticket type options -- all initialised in __init__() opt_inherit_fields = dict() opt_columns = dict() def _add_per_ticket_type_option(self, ticket_type): self.opt_inherit_fields[ticket_type] = ListOption( 'subtickets', 'type.%s.child_inherits' % ticket_type, default='', doc=_("""Comma-separated list of ticket fields whose values are to be copied from a parent ticket into a newly created child ticket. """)) self.opt_columns[ticket_type] = ListOption('subtickets', 'type.%s.table_columns' % ticket_type, default='status,owner', doc=_(""" Comma-separated list of ticket fields whose values are to be shown for each child ticket in the subtickets list """)) def __init__(self): # The following initialisations must happen inside init() # in order to be able to access self.env for tt in TicketType.select(self.env): self._add_per_ticket_type_option(tt.name) # ITemplateProvider methods def get_htdocs_dirs(self): from pkg_resources import resource_filename return [('subtickets', resource_filename(__name__, 'htdocs'))] def get_templates_dirs(self): return [] # IRequestFilter methods def pre_process_request(self, req, handler): return handler def post_process_request(self, req, template, data, content_type): path = req.path_info if path.startswith('/ticket/') or path.startswith('/newticket'): # get parent ticket's data if data and 'ticket' in data: ticket = data['ticket'] parents = ticket['parents'] or '' ids = set(NUMBERS_RE.findall(parents)) if len(parents) > 0: self._append_parent_links(req, data, ids) children = self.get_children(ticket.id) if children: data['subtickets'] = children elif path.startswith('/admin/ticket/type') \ and data \ and set(['add', 'name']).issubset(data.keys()) \ and data['add'] == 'Add': self._add_per_ticket_type_option(data['name']) return template, data, content_type def _append_parent_links(self, req, data, ids): links = [] for id in sorted(ids, key=lambda x: int(x)): try: ticket = Ticket(self.env, id) elem = tag.a('#%s' % id, href=req.href.ticket(id), class_='%s ticket' % ticket['status'], title=ticket['summary']) if len(links) > 0: links.append(', ') links.append(elem) except ResourceNotFound: pass for field in data.get('fields', ''): if field.get('name') == 'parents': field['rendered'] = tag.span(*links) # ITicketManipulator methods def prepare_ticket(self, req, ticket, fields, actions): pass def get_children(self, parent_id, depth=0): children = {} for parent, child in self.env.db_query( """ SELECT parent, child FROM subtickets WHERE parent=%s """, (parent_id, )): children[child] = None if self.opt_recursion_depth > depth or self.opt_recursion_depth == -1: for id in children: children[id] = self.get_children(id, depth + 1) return children def validate_ticket(self, req, ticket): action = req.args.get('action') if action in self.opt_skip_validation: return if action == 'resolve': for parent, child in self.env.db_query( """ SELECT parent, child FROM subtickets WHERE parent=%s """, (ticket.id, )): if Ticket(self.env, child)['status'] != 'closed': yield None, _("""Cannot close/resolve because child ticket #%(child)s is still open""", child=child) elif action == 'reopen': ids = set(NUMBERS_RE.findall(ticket['parents'] or '')) for id in ids: if Ticket(self.env, id)['status'] == 'closed': msg = _( "Cannot reopen because parent ticket #%(id)s " "is closed", id=id) yield None, msg # ITemplateStreamFilter method def _create_subtickets_table(self, req, children, tbody, depth=0): """Recursively create list table of subtickets """ if not children: return for id in sorted(children, key=lambda x: int(x)): ticket = Ticket(self.env, id) # the row r = [] # Always show ID and summary attrs = {'href': req.href.ticket(id)} if ticket['status'] == 'closed': attrs['class_'] = 'closed' link = tag.a('#%s' % id, **attrs) summary = tag.td(link, ': %s' % ticket['summary'], style='padding-left: %dpx;' % (depth * 15)) r.append(summary) # Add other columns as configured. for column in \ self.env.config.getlist('subtickets', 'type.%(type)s.table_columns' % ticket): if column == 'owner': if self.opt_owner_url: href = req.href(self.opt_owner_url % ticket['owner']) else: href = req.href.query(status='!closed', owner=ticket['owner']) e = tag.td(tag.a(ticket['owner'], href=href)) elif column == 'milestone': href = req.href.query(status='!closed', milestone=ticket['milestone']) e = tag.td(tag.a(ticket['milestone'], href=href)) else: e = tag.td(ticket[column]) r.append(e) tbody.append(tag.tr(*r)) self._create_subtickets_table(req, children[id], tbody, depth + 1) def filter_stream(self, req, method, filename, stream, data): if not req.path_info.startswith('/ticket/'): return stream div = None link = None button = None if 'ticket' in data: # get parents data ticket = data['ticket'] # title div = tag.div(class_='description') if 'TICKET_CREATE' in req.perm(ticket.resource) \ and ticket['status'] != 'closed': opt_inherit = self.env.config.getlist( 'subtickets', 'type.%(type)s.child_inherits' % ticket) if self.opt_add_style == 'link': inh = {f: ticket[f] for f in opt_inherit} link = tag.a(_('add'), href=req.href.newticket(parents=ticket.id, **inh)) link = tag.span('(', link, ')', class_='addsubticket') else: inh = [ tag.input(type='hidden', name=f, value=ticket[f]) for f in opt_inherit ] button = tag.form(tag.div(tag.input( type="submit", value=_("Create"), title=_("Create a child ticket")), inh, tag.input(type="hidden", name="parents", value=str(ticket.id)), class_="inlinebuttons"), method="get", action=req.href.newticket()) div.append(button) div.append(tag.h3(_('Subtickets '), link)) if 'subtickets' in data: # table tbody = tag.tbody() div.append(tag.table(tbody, class_='subtickets')) # tickets self._create_subtickets_table(req, data['subtickets'], tbody) if div: add_stylesheet(req, 'subtickets/css/subtickets.css') ''' If rendered in preview mode, DIV we're interested in isn't a child but the root and transformation won't succeed. According to HTML specification, id's must be unique within a document, so it's safe to omit the leading '.' in XPath expression to select all matching regardless of hierarchy their in. ''' stream |= Transformer('//div[@id="ticket"]').append(div) return stream
def process_request(self, req): req.perm.require('TEAMCALENDAR_VIEW') pid = self.pm.get_current_project(req) syllabus_id = req.data['syllabus_id'] self.pm.check_component_enabled(self, syllabus_id=syllabus_id) work_days = [int(d) for d in self.work_days.syllabus(syllabus_id)] weeks_prior = self.weeks_prior.syllabus(syllabus_id) weeks_after = self.weeks_after.syllabus(syllabus_id) data = {} from_date = req.args.get('from_date', '') to_date = req.args.get('to_date', '') from_date = from_date and parse_date_only(from_date) or self.find_default_start(weeks_prior) to_date = to_date and parse_date_only(to_date) or self.find_default_end(weeks_after) # Check time interval force_default = True delta = (to_date - from_date).days if delta < 0: add_warning(req, _('Negative time interval selected. Using default.')) elif delta > self.MAX_INTERVAL: add_warning(req, _('Too big time interval selected (%(interval)s). ' 'Using default.', interval=pretty_timedelta(to_date, from_date))) else: force_default = False # Reset interval to default if force_default: from_date = self.find_default_start(weeks_prior) to_date = self.find_default_end(weeks_after) # Message data['message'] = '' # Current user data['authname'] = authname = req.authname # Can we update? data['can_update_own'] = can_update_own = ('TEAMCALENDAR_UPDATE_OWN' in req.perm) data['can_update_others'] = can_update_others = ('TEAMCALENDAR_UPDATE_OTHERS' in req.perm) data['can_update'] = can_update_own or can_update_others # Store dates data['today'] = date.today() data['from_date'] = from_date data['to_date'] = to_date # Get all people data['people'] = people = self.pm.get_project_users(pid) # Update timetable if required if 'update_calendar' in req.args: req.perm.require('TEAMCALENDAR_UPDATE_OWN') # deliberately override dates: want to show result of update from_date = current_date = parse_date_only(req.args.get('orig_from_date', '')) to_date = parse_date_only(req.args.get('orig_to_date', '')) tuples = [] while current_date <= to_date: if can_update_others: for person in people: status = Decimal(req.args.get(u'%s.%s' % (current_date.isoformat(), person), False)) tuples.append((current_date, person, status,)) elif can_update_own: status = Decimal(req.args.get(u'%s.%s' % (current_date.isoformat(), authname), False)) tuples.append((current_date, authname, status,)) current_date += timedelta(1) self.update_timetable(tuples, pid, from_date, to_date) data['message'] = _('Timetable updated.') # Get the current timetable timetable = self.get_timetable(from_date, to_date, people, pid, work_days) data['timetable'] = [] current_date = from_date while current_date <= to_date: data['timetable'].append(dict(date=current_date, people=timetable[current_date])) current_date += timedelta(1) for day in data['timetable']: day['strdate'] = to_unicode(day['date'].strftime('%a %d/%m/%Y')) add_stylesheet(req, 'common/css/jquery-ui/jquery.ui.core.css') add_stylesheet(req, 'common/css/jquery-ui/jquery.ui.datepicker.css') add_stylesheet(req, 'common/css/jquery-ui/jquery.ui.theme.css') add_script(req, 'common/js/jquery.ui.core.js') add_script(req, 'common/js/jquery.ui.widget.js') add_script(req, 'common/js/jquery.ui.datepicker.js') add_script(req, 'common/js/datepicker.js') add_stylesheet(req, 'teamcalendar/css/calendar.css') data['_'] = _ return 'teamcalendar.html', data, None
def op_who(self, field): # TRANSLATOR: Default updater name who = self.macro.get_tracform_fieldinfo( self.context, field)[0] or _("unknown") return who