Example #1
0
    def render_property_diff(self, req, ticket, field, old, new,
                              resource_new=None):
        "Version for Trac 0.11"
        rendered = None
        # per type special rendering of diffs
        type_ = None
        for f in ticket.fields:
            if f['name'] == field:
                type_ = f['type']
                break
        if type_ == 'checkbox':
            rendered = new == '1' and "set" or "unset"
        elif type_ == 'textarea':
            if not resource_new:
                rendered = _('modified')
            else:
                href = get_resource_url(self.env, resource_new, req.href,
                                        action='diff')
                rendered = tag('modified (', tag.a('diff', href=href), ')')

        # per name special rendering of diffs
        old_list, new_list = None, None
        render_elt = lambda x: x
        sep = ', '
        if field == 'cc':
            chrome = Chrome(self.env)
            old_list, new_list = chrome.cc_list(old), chrome.cc_list(new)
            if not (Chrome(self.env).show_email_addresses or
                    'EMAIL_VIEW' in req.perm(resource_new or ticket.resource)):
                render_elt = obfuscate_email_address
        elif field == 'keywords':
            old_list, new_list = (old or '').split(), new.split()
            sep = ' '
        if (old_list, new_list) != (None, None):
            added = [tag.em(render_elt(x)) for x in new_list
                     if x not in old_list]
            remvd = [tag.em(render_elt(x)) for x in old_list
                     if x not in new_list]
            added = added and tag(separated(added, sep), " added")
            remvd = remvd and tag(separated(remvd, sep), " removed")
            if added or remvd:
                rendered = tag(added, added and remvd and '; ', remvd)
                return rendered
        if field in ('reporter', 'owner'):
            if not (Chrome(self.env).show_email_addresses or
                    'EMAIL_VIEW' in req.perm(resource_new or ticket.resource)):
                old = obfuscate_email_address(old)
                new = obfuscate_email_address(new)
        # Added by MS
        if field == 'attachment':
            rendered = tag(tag.em(new), " added")
        # changed 'if' to 'elif':
        elif old and not new:
            rendered = tag(tag.em(old), " deleted")
        elif new and not old:
            rendered = tag("set to ", tag.em(new))
        elif old and new:
            rendered = tag("changed from ", tag.em(old),
                            " to ", tag.em(new))
        return rendered
Example #2
0
 def diff_cc(self, old, new):
     oldcc = NotifyEmail.addrsep_re.split(old)
     newcc = NotifyEmail.addrsep_re.split(new)
     added = [obfuscate_email_address(x) \
                             for x in newcc if x and x not in oldcc]
     removed = [obfuscate_email_address(x) \
                             for x in oldcc if x and x not in newcc]
     return (added, removed)
Example #3
0
 def diff_cc(self, old, new):
     oldcc = NotifyEmail.addrsep_re.split(old)
     newcc = NotifyEmail.addrsep_re.split(new)
     added = [obfuscate_email_address(x) \
                             for x in newcc if x and x not in oldcc]
     removed = [obfuscate_email_address(x) \
                             for x in oldcc if x and x not in newcc]
     return (added, removed)
Example #4
0
def _chrome_format_author_replacement(self, req, author):
    """
    Audit Chrome.format_author method so that we get link to user profile

    Downside: This is a hack that interfere with the way trac renders usernames.
              Will have some unwanted behaviour.

              One such known unwanted behaviour is in the ticket view where owner and
              reporter links are changed
    """
    if not author:
        return ""
    unwanted_users = [
        'trac', 'tracadmin', 'anonymous', 'authenticated', 'somebody'
    ]
    not_ticket = req.path_info.rsplit('/', 2)[1] != 'ticket'
    contain_email = bool(re.search('<.+>', author))
    ok_user = author not in unwanted_users
    username = author
    if not contain_email:
        user = get_userstore().getUser(author)
        if user:
            author = user.getDisplayName()
    elif not Chrome(self.env).show_email_addresses:
        author = obfuscate_email_address(author)

    # Create a link to profile page or return author in plain
    if ok_user and not_ticket and not contain_email and conf.public_user_page_url:
        return tag.a(
            author, **{
                'href': conf.public_user_page_url + username,
                'class': 'author'
            })
    else:
        return author
Example #5
0
def _chrome_format_author_replacement(self, req, author):
    """
    Audit Chrome.format_author method so that we get link to user profile

    Downside: This is a hack that interfere with the way trac renders usernames.
              Will have some unwanted behaviour.

              One such known unwanted behaviour is in the ticket view where owner and
              reporter links are changed
    """
    if not author:
        return ""
    unwanted_users = ['trac', 'tracadmin', 'anonymous', 'authenticated', 'somebody']
    not_ticket = req.path_info.rsplit('/', 2)[1] != 'ticket'
    contain_email = bool(re.search('<.+>', author))
    ok_user = author not in unwanted_users
    username = author
    if not contain_email:
        user = get_userstore().getUser(author)
        if user:
            author = user.getDisplayName()
    elif not Chrome(self.env).show_email_addresses:
        author = obfuscate_email_address(author)

    # Create a link to profile page or return author in plain
    if ok_user and not_ticket and not contain_email and conf.public_user_page_url:
        return tag.a(author, **{'href':conf.public_user_page_url + username, 'class':'author'})
    else:
        return author
Example #6
0
 def obfuscate_email(self, text):
     """ Obfuscate text when `show_email_addresses` is disabled in config.
     Obfuscation happens once per email, regardless of recipients, so
     cannot use permission-based obfuscation.
     """
     if self.env.config.getbool('trac', 'show_email_addresses'):
         return text
     else:
         return obfuscate_email_address(text)
Example #7
0
 def obfuscate_email(self, text):
     """ Obfuscate text when `show_email_addresses` is disabled in config.
     Obfuscation happens once per email, regardless of recipients, so
     cannot use permission-based obfuscation.
     """
     if self.env.config.getbool('trac', 'show_email_addresses'):
         return text
     else:
         return obfuscate_email_address(text)
Example #8
0
    def format_emails(self, context, value, sep=', '):
        """Normalize a list of e-mails and obfuscate them if needed.

        :param context: the context in which the check for obfuscation should
                        be done
        :param value: a string containing a comma-separated list of e-mails
        :param sep: the separator to use when rendering the list again
        """
        all_cc = self.cc_list(value)
        if not (self.show_email_addresses or 'EMAIL_VIEW' in context.perm):
            all_cc = [obfuscate_email_address(cc) for cc in all_cc]
        return sep.join(all_cc)
Example #9
0
    def format_emails(self, context, value, sep=', '):
        """Normalize a list of e-mails and obfuscate them if needed.

        :param context: the context in which the check for obfuscation should
                        be done
        :param   value: a string containing a comma-separated list of e-mails
        :param     sep: the separator to use when rendering the list again
        """
        all_cc = self.cc_list(value)
        if not (self.show_email_addresses or 'EMAIL_VIEW' in context.perm):
            all_cc = [obfuscate_email_address(cc) for cc in all_cc]
        return sep.join(all_cc)
    def notify_attachment(self, ticket, author, filename, modtime, body):
        """Send ticket attachment notification (untranslated)"""
        t = deactivate()
        translated_fields = ticket.fields
        try:
            ticket.fields = TicketSystem(self.env).get_ticket_fields()

            self.ticket = ticket
            self.modtime = modtime
            self.newticket = False
            self.reporter = ''
            self.owner = ''

            link = self.env.abs_href.ticket(ticket.id)
            summary = self.ticket['summary']
            ticket_values = ticket.values.copy()
            ticket_values['id'] = ticket.id

            wrap_kargs = {
                'initial_indent': ' ',
                'subsequent_indent': ' ',
                'linesep': CRLF
            }
            if 'ambiwidth' in getargspec(wrap)[0]:
                wrap_kargs['ambiwidth'] = self.ambiwidth

            ticket_values['description'] = wrap(
                ticket_values.get('description', ''),
                self.COLS,
                **wrap_kargs
            )

            ticket_values['new'] = self.newticket
            ticket_values['link'] = link
            subject = 'Re: ' + self.format_subj(summary)
            author = obfuscate_email_address(author)
            change = { 'author': author }

            self.data.update({
                'ticket_props': self.format_props(),
                'ticket_body_hdr': self.format_hdr(),
                'subject': subject,
                'ticket': ticket_values,
                'change': change,
                'changes_body': body,
                })

            NotifyEmail.notify(self, ticket.id, subject)
        finally:
            ticket.fields = translated_fields
            reactivate(t)
Example #11
0
 def format_props(self):
     tkt = self.ticket
     fields = [f for f in tkt.fields if f['name'] not in ('summary', 'cc')]
     width = [0, 0, 0, 0]
     i = 0
     for f in [f['name'] for f in fields if f['type'] != 'textarea']:
         if not tkt.values.has_key(f):
             continue
         fval = tkt[f] or ''
         if fval.find('\n') != -1:
             continue
         idx = 2 * (i % 2)
         if len(f) > width[idx]:
             width[idx] = len(f)
         if len(fval) > width[idx + 1]:
             width[idx + 1] = len(fval)
         i += 1
     format = ('%%%is:  %%-%is  |  ' % (width[0], width[1]),
               ' %%%is:  %%-%is%s' % (width[2], width[3], CRLF))
     l = (width[0] + width[1] + 5)
     sep = l * '-' + '+' + (self.COLS - l) * '-'
     txt = sep + CRLF
     big = []
     i = 0
     for f in [f for f in fields if f['name'] != 'description']:
         fname = f['name']
         if not tkt.values.has_key(fname):
             continue
         fval = tkt[fname] or ''
         if fname in ['owner', 'reporter']:
             fval = obfuscate_email_address(fval)
         if f['type'] == 'textarea' or '\n' in unicode(fval):
             big.append((fname.capitalize(), CRLF.join(fval.splitlines())))
         else:
             txt += format[i % 2] % (fname.capitalize(), fval)
             i += 1
     if i % 2:
         txt += CRLF
     if big:
         txt += sep
         for name, value in big:
             txt += CRLF.join(['', name + ':', value, '', ''])
     txt += sep
     return txt
 def obfuscate_email(self, text):
     if self.env.config.getbool('trac', 'show_email_addresses'):
         return text
     else:
         return obfuscate_email_address(text)
    def expand_macro(self, formatter, name, content, args=None):
        req = formatter.req
        # prepare options
        options, query_args = parse_options(self.env, content,
                                            copy.copy(DEFAULT_OPTIONS))

        query_args[self.remaining_field + "!"] = None
        tickets = execute_query(self.env, req, query_args)

        sum = 0.0
        estimations = {}
        for ticket in tickets:
            if ticket['status'] in self.closed_states:
                continue
            try:
                estimation = float(ticket[self.remaining_field])
                owner = ticket['owner']
                sum += estimation
                if owner in estimations:
                    estimations[owner] += estimation
                else:
                    estimations[owner] = estimation
            except:
                pass

        estimations_string = []
        labels = []
        for owner, estimation in estimations.iteritems():
            # Note: Unconditional obfuscation of owner in case it represents
            # an email adress, and as the chart API doesn't support SSL
            # (plain http transfer only, from either client or server).
            labels.append("%s %g%s" % (obfuscate_email_address(owner),
                                       round(estimation, 2),
                                       self.estimation_suffix))
            estimations_string.append(str(int(estimation)))

        # Title
        title = 'Workload'

        # calculate remaining work time
        if options.get('today') and options.get('enddate'):
            currentdate = options['today']
            day = timedelta(days=1)
            days_remaining = 0
            while currentdate <= options['enddate']:
                if currentdate.weekday() < 5:
                    days_remaining += 1
                currentdate += day
            title += ' %g%s (~%s workdays left)' % (round(sum, 2),
                                                    self.estimation_suffix,
                                                    days_remaining)

        chart_args = unicode_urlencode(
            {'chs': '%sx%s' % (options['width'], options['height']),
             'chf': 'bg,s,00000000',
             'chd': 't:%s' % ",".join(estimations_string),
             'cht': 'p3',
             'chtt': title,
             'chl': "|".join(labels),
             'chco': options['color']})
        self.log.debug("WorkloadChart data: %s", chart_args)
        if self.serverside_charts:
            return tag.image(
                src="%s?data=%s" % (req.href.estimationtools('chart'),
                                    unicode_quote(chart_args)),
                alt="Workload Chart (server)")
        else:
            return tag.image(
                src="http://chart.googleapis.com/chart?%s" % chart_args,
                alt="Workload Chart (client)")
Example #14
0
 def format_author(self, req, author):
     if not author or author == 'anonymous':
         return _("anonymous")
     if self.show_email_addresses or not req or 'EMAIL_VIEW' in req.perm:
         return author
     return obfuscate_email_address(author)
Example #15
0
    def render_property_diff(self,
                             req,
                             ticket,
                             field,
                             old,
                             new,
                             resource_new=None):
        "Version for Trac 0.11"
        rendered = None
        # per type special rendering of diffs
        type_ = None
        for f in ticket.fields:
            if f['name'] == field:
                type_ = f['type']
                break
        if type_ == 'checkbox':
            rendered = new == '1' and "set" or "unset"
        elif type_ == 'textarea':
            if not resource_new:
                rendered = _('modified')
            else:
                href = get_resource_url(self.env,
                                        resource_new,
                                        req.href,
                                        action='diff')
                rendered = tag('modified (', tag.a('diff', href=href), ')')

        # per name special rendering of diffs
        old_list, new_list = None, None
        render_elt = lambda x: x
        sep = ', '
        if field == 'cc':
            chrome = Chrome(self.env)
            old_list, new_list = chrome.cc_list(old), chrome.cc_list(new)
            if not (Chrome(self.env).show_email_addresses or 'EMAIL_VIEW'
                    in req.perm(resource_new or ticket.resource)):
                render_elt = obfuscate_email_address
        elif field == 'keywords':
            old_list, new_list = (old or '').split(), new.split()
            sep = ' '
        if (old_list, new_list) != (None, None):
            added = [
                tag.em(render_elt(x)) for x in new_list if x not in old_list
            ]
            remvd = [
                tag.em(render_elt(x)) for x in old_list if x not in new_list
            ]
            added = added and tag(separated(added, sep), " added")
            remvd = remvd and tag(separated(remvd, sep), " removed")
            if added or remvd:
                rendered = tag(added, added and remvd and '; ', remvd)
                return rendered
        if field in ('reporter', 'owner'):
            if not (Chrome(self.env).show_email_addresses or 'EMAIL_VIEW'
                    in req.perm(resource_new or ticket.resource)):
                old = obfuscate_email_address(old)
                new = obfuscate_email_address(new)
        # Added by MS
        if field == 'attachment':
            rendered = tag(tag.em(new), " added")
        # changed 'if' to 'elif':
        elif old and not new:
            rendered = tag(tag.em(old), " deleted")
        elif new and not old:
            rendered = tag("set to ", tag.em(new))
        elif old and new:
            rendered = tag("changed from ", tag.em(old), " to ", tag.em(new))
        return rendered
    def expand_macro(self, formatter, name, content):
        req = formatter.req
        db = self.env.get_db_cnx()
        # prepare options
        options, query_args = parse_options(db, content, copy.copy(DEFAULT_OPTIONS))

        query_args[self.estimation_field + "!"] = None
        query_args['col'] = '|'.join([self.totalhours_field, 'owner'])
        tickets = execute_query(self.env, req, query_args)

        sum = 0.0
        estimations = {}
        for ticket in tickets:
            if ticket['status'] in self.closed_states:
                continue
            try:
                estimation = float(ticket[self.estimation_field]) - float(ticket[self.totalhours_field])
                owner = ticket['owner']
                sum += estimation
                if estimations.has_key(owner):
                    estimations[owner] += estimation
                else:
                    estimations[owner] = estimation
            except:
                pass

        data = []
        data.append(['Owner', 'Workload'])

        for owner, estimation in estimations.iteritems():
            estimation = max(0, estimation)
            label = "%s %g%s" % (obfuscate_email_address(owner),
                            round(estimation, 2),
                            self.estimation_suffix)
            data.append([label, float(estimation)])

        # Title
        title = 'Workload'

        # calculate remaining work time
        if options.get('today') and options.get('enddate'):
            currentdate = options['today']
            day = timedelta(days=1)
            days_remaining = 0
            while currentdate <= options['enddate']:
                if currentdate.weekday() < 5:
                    days_remaining += 1
                currentdate += day
            title += ' %g%s (~%s workdays left)' % (round(sum, 2),
                                    self.estimation_suffix, days_remaining)

        element_id = 'chart-%d' % random.randint(0, 0xffffffff)
        args = {
            'containerId': element_id,
            'chartType': 'PieChart',
            'options': {
                'width': int(options['width']),
                'height': int(options['height']),
                'title': title,
                'legend': { 'position': 'labeled' },
                'pieSliceText': 'none',
                'tooltip': 'percentage',
            },
        }
        script = "EstimationCharts.push(function() {\n"
        script += 'var data=' + to_json(data) + ";\n"
        script += 'var args=' + to_json(args) + ";\n"
        script += 'DrawWorkloadChart(data, args);'
        script += '});'

        return tag.div(tag.div(id=element_id), tag.script(script))
Example #17
0
    def _notify(self, ticket, newticket=True, modtime=None):
        self.ticket = ticket
        self.modtime = modtime
        self.newticket = newticket

        changes_body = ''
        self.reporter = ''
        self.owner = ''
        changes_descr = ''
        change_data = {}
        link = self.env.abs_href.ticket(ticket.id)
        summary = self.ticket['summary']
        
        if not self.newticket and modtime:  # Ticket change
            from trac.ticket.web_ui import TicketModule
            for change in TicketModule(self.env).grouped_changelog_entries(
                                                ticket, self.db, when=modtime):
                if not change['permanent']: # attachment with same time...
                    continue
                change_data.update({
                    'author': obfuscate_email_address(change['author']),
                    'comment': wrap(change['comment'], self.COLS, ' ', ' ',
                                    CRLF)
                    })
                link += '#comment:%s' % str(change.get('cnum', ''))
                for field, values in change['fields'].iteritems():
                    old = values['old']
                    new = values['new']
                    newv = ''
                    if field == 'description':
                        new_descr = wrap(new, self.COLS, ' ', ' ', CRLF)
                        old_descr = wrap(old, self.COLS, '> ', '> ', CRLF)
                        old_descr = old_descr.replace(2 * CRLF, CRLF + '>' + \
                                                      CRLF)
                        cdescr = CRLF
                        cdescr += 'Old description:' + 2 * CRLF + old_descr + \
                                  2 * CRLF
                        cdescr += 'New description:' + 2 * CRLF + new_descr + \
                                  CRLF
                        changes_descr = cdescr
                    elif field == 'summary':
                        summary = "%s (was: %s)" % (new, old)
                    elif field == 'cc':
                        (addcc, delcc) = self.diff_cc(old, new)
                        chgcc = ''
                        if delcc:
                            chgcc += wrap(" * cc: %s (removed)" %
                                          ', '.join(delcc), 
                                          self.COLS, ' ', ' ', CRLF) + CRLF
                        if addcc:
                            chgcc += wrap(" * cc: %s (added)" %
                                          ', '.join(addcc), 
                                          self.COLS, ' ', ' ', CRLF) + CRLF
                        if chgcc:
                            changes_body += chgcc
                        self.prev_cc += old and self.parse_cc(old) or []
                    else:
                        if field in ['owner', 'reporter']:
                            old = obfuscate_email_address(old)
                            new = obfuscate_email_address(new)
                        newv = new
                        length = 7 + len(field)
                        spacer_old, spacer_new = ' ', ' '
                        if len(old + new) + length > self.COLS:
                            length = 5
                            if len(old) + length > self.COLS:
                                spacer_old = CRLF
                            if len(new) + length > self.COLS:
                                spacer_new = CRLF
                        chg = '* %s: %s%s%s=>%s%s' % (field, spacer_old, old,
                                                      spacer_old, spacer_new,
                                                      new)
                        chg = chg.replace(CRLF, CRLF + length * ' ')
                        chg = wrap(chg, self.COLS, '', length * ' ', CRLF)
                        changes_body += ' %s%s' % (chg, CRLF)
                    if newv:
                        change_data[field] = {'oldvalue': old, 'newvalue': new}
        
        ticket_values = ticket.values.copy()
        ticket_values['id'] = ticket.id
        ticket_values['description'] = wrap(
            ticket_values.get('description', ''), self.COLS,
            initial_indent=' ', subsequent_indent=' ', linesep=CRLF)
        ticket_values['new'] = self.newticket
        ticket_values['link'] = link
        
        subject = self.format_subj(summary)
        if not self.newticket:
            subject = 'Re: ' + subject
        self.data.update({
            'ticket_props': self.format_props(),
            'ticket_body_hdr': self.format_hdr(),
            'subject': subject,
            'ticket': ticket_values,
            'changes_body': changes_body,
            'changes_descr': changes_descr,
            'change': change_data
            })
        NotifyEmail.notify(self, ticket.id, subject)
Example #18
0
    def _notify(self, ticket, newticket=True, modtime=None):
        self.ticket = ticket
        self.modtime = modtime
        self.newticket = newticket

        changes_body = ''
        self.reporter = ''
        self.owner = ''
        changes_descr = ''
        change_data = {}
        link = self.env.abs_href.ticket(ticket.id)
        summary = self.ticket['summary']
        
        if not self.newticket and modtime:  # Ticket change
            from trac.ticket.web_ui import TicketModule
            for change in TicketModule(self.env).grouped_changelog_entries(
                                                ticket, self.db, when=modtime):
                if not change['permanent']: # attachment with same time...
                    continue
                change_data.update({
                    'author': obfuscate_email_address(change['author']),
                    'comment': wrap(change['comment'], self.COLS, ' ', ' ',
                                    CRLF)
                    })
                link += '#comment:%s' % str(change.get('cnum', ''))
                for field, values in change['fields'].iteritems():
                    old = values['old']
                    new = values['new']
                    newv = ''
                    if field == 'description':
                        new_descr = wrap(new, self.COLS, ' ', ' ', CRLF)
                        old_descr = wrap(old, self.COLS, '> ', '> ', CRLF)
                        old_descr = old_descr.replace(2 * CRLF, CRLF + '>' + \
                                                      CRLF)
                        cdescr = CRLF
                        cdescr += 'Old description:' + 2 * CRLF + old_descr + \
                                  2 * CRLF
                        cdescr += 'New description:' + 2 * CRLF + new_descr + \
                                  CRLF
                        changes_descr = cdescr
                    elif field == 'summary':
                        summary = "%s (was: %s)" % (new, old)
                    elif field == 'cc':
                        (addcc, delcc) = self.diff_cc(old, new)
                        chgcc = ''
                        if delcc:
                            chgcc += wrap(" * cc: %s (removed)" %
                                          ', '.join(delcc), 
                                          self.COLS, ' ', ' ', CRLF) + CRLF
                        if addcc:
                            chgcc += wrap(" * cc: %s (added)" %
                                          ', '.join(addcc), 
                                          self.COLS, ' ', ' ', CRLF) + CRLF
                        if chgcc:
                            changes_body += chgcc
                        self.prev_cc += old and self.parse_cc(old) or []
                    else:
                        if field in ['owner', 'reporter']:
                            old = obfuscate_email_address(old)
                            new = obfuscate_email_address(new)
                        newv = new
                        length = 7 + len(field)
                        spacer_old, spacer_new = ' ', ' '
                        if len(old + new) + length > self.COLS:
                            length = 5
                            if len(old) + length > self.COLS:
                                spacer_old = CRLF
                            if len(new) + length > self.COLS:
                                spacer_new = CRLF
                        chg = '* %s: %s%s%s=>%s%s' % (field, spacer_old, old,
                                                      spacer_old, spacer_new,
                                                      new)
                        chg = chg.replace(CRLF, CRLF + length * ' ')
                        chg = wrap(chg, self.COLS, '', length * ' ', CRLF)
                        changes_body += ' %s%s' % (chg, CRLF)
                    if newv:
                        change_data[field] = {'oldvalue': old, 'newvalue': new}
        
        ticket_values = ticket.values.copy()
        ticket_values['id'] = ticket.id
        ticket_values['description'] = wrap(
            ticket_values.get('description', ''), self.COLS,
            initial_indent=' ', subsequent_indent=' ', linesep=CRLF)
        ticket_values['new'] = self.newticket
        ticket_values['link'] = link
        
        subject = self.format_subj(summary)
        if not self.newticket:
            subject = 'Re: ' + subject
        self.data.update({
            'ticket_props': self.format_props(),
            'ticket_body_hdr': self.format_hdr(),
            'subject': subject,
            'ticket': ticket_values,
            'changes_body': changes_body,
            'changes_descr': changes_descr,
            'change': change_data
            })
        NotifyEmail.notify(self, ticket.id, subject)
Example #19
0
 def format_props(self):
     tkt = self.ticket
     fields = [f for f in tkt.fields 
               if f['name'] not in ('summary', 'cc', 'time', 'changetime')]
     width = [0, 0, 0, 0]
     i = 0
     for f in fields:
         if f['type'] == 'textarea':
             continue
         fname = f['name']
         if not fname in tkt.values:
             continue
         fval = tkt[fname] or ''
         if fval.find('\n') != -1:
             continue
         if fname in ['owner', 'reporter']:
             fval = obfuscate_email_address(fval)
         idx = 2 * (i % 2)
         width[idx] = max(self.get_text_width(f['label']), width[idx])
         width[idx + 1] = max(self.get_text_width(fval), width[idx + 1])
         i += 1
     width_l = width[0] + width[1] + 5
     width_r = width[2] + width[3] + 5
     half_cols = (self.COLS - 1) / 2
     if width_l + width_r + 1 > self.COLS:
         if ((width_l > half_cols and width_r > half_cols) or 
                 (width[0] > half_cols / 2 or width[2] > half_cols / 2)):
             width_l = half_cols
             width_r = half_cols
         elif width_l > width_r:
             width_l = min((self.COLS - 1) * 2 / 3, width_l)
             width_r = self.COLS - width_l - 1
         else:
             width_r = min((self.COLS - 1) * 2 / 3, width_r)         
             width_l = self.COLS - width_r - 1
     sep = width_l * '-' + '+' + width_r * '-'
     txt = sep + CRLF
     cell_tmp = [u'', u'']
     big = []
     i = 0
     width_lr = [width_l, width_r]
     for f in [f for f in fields if f['name'] != 'description']:
         fname = f['name']
         if not tkt.values.has_key(fname):
             continue
         fval = tkt[fname] or ''
         if fname in ['owner', 'reporter']:
             fval = obfuscate_email_address(fval)
         if f['type'] == 'textarea' or '\n' in unicode(fval):
             big.append((f['label'], CRLF.join(fval.splitlines())))
         else:
             # Note: f['label'] is a Babel's LazyObject, make sure its
             # __str__ method won't be called.
             str_tmp = u'%s:  %s' % (f['label'], unicode(fval))
             idx = i % 2
             cell_tmp[idx] += wrap(str_tmp, width_lr[idx] - 2 + 2 * idx,
                                   (width[2 * idx]
                                    - self.get_text_width(f['label'])
                                    + 2 * idx) * ' ',
                                   2 * ' ', CRLF)
             cell_tmp[idx] += CRLF
             i += 1
     cell_l = cell_tmp[0].splitlines()
     cell_r = cell_tmp[1].splitlines()
     for i in range(max(len(cell_l), len(cell_r))):
         if i >= len(cell_l):
             cell_l.append(width_l * ' ')
         elif i >= len(cell_r):
             cell_r.append('')
         fmt_width = width_l - self.get_text_width(cell_l[i]) \
                     + len(cell_l[i])
         txt += u'%-*s|%s%s' % (fmt_width, cell_l[i], cell_r[i], CRLF)
     if big:
         txt += sep
         for name, value in big:
             txt += CRLF.join(['', name + ':', value, '', ''])
     txt += sep
     return txt
Example #20
0
 def format_author(self, req, author):
     if self.show_email_addresses or not req or 'EMAIL_VIEW' in req.perm:
         return author
     else:
         return obfuscate_email_address(author)
    def expand_macro(self, formatter, name, content):
        req = formatter.req
        db = self.env.get_db_cnx()
        # prepare options
        options, query_args = parse_options(db, content,
                                            copy.copy(DEFAULT_OPTIONS))

        query_args[self.estimation_field + "!"] = None
        tickets = execute_query(self.env, req, query_args)

        sum = 0.0
        estimations = {}
        for ticket in tickets:
            if ticket['status'] in self.closed_states:
                continue
            try:
                estimation = float(ticket[self.estimation_field] or 0.0)

                if options.get('remainingworkload'):

                    completion_cursor = db.cursor()

                    completion_cursor.execute(
                        "SELECT t.value AS totalhours, c.value AS complete, d.value AS due_close FROM ticket tk LEFT JOIN ticket_custom t ON (tk.id = t.ticket AND t.name = 'totalhours') LEFT JOIN ticket_custom c ON (tk.id = c.ticket AND c.name = 'complete') LEFT JOIN ticket_custom d ON (tk.id = d.ticket AND d.name = 'due_close') WHERE tk.id = %s"
                        % ticket['id'])

                    for row in completion_cursor:

                        ticket['totalhours'], ticket['complete'], ticket[
                            'due_close'] = row
                        break

                    # skip ticket ticket if due date is later than 'enddate':
                    if options.get('showdueonly'):

                        if not ticket['due_close']:
                            continue  # skip tickets with empty ETA when in 'showdueonly' mode
                        due_close = parse_date(ticket['due_close'],
                                               ["%Y/%m/%d"])
                        startdate = options.get('startdate')
                        enddate = options.get('enddate')

                        if startdate and startdate > due_close:
                            continue  # skip tickets with ETA in the past

                        if enddate and enddate < due_close:
                            continue  # skip tickets with ETA in the future

                        pass

                    totalhours = float(ticket['totalhours'] or 0.0)

                    completed = (float(ticket['complete'] or 0.0) /
                                 100) * estimation
                    completed_hours = min(estimation,
                                          max(totalhours, completed))

                    estimation -= completed_hours

                    pass

                owner = ticket['owner']

                sum += estimation
                if estimations.has_key(owner):
                    estimations[owner] += estimation
                else:
                    estimations[owner] = estimation
            except:
                raise

        # Title
        title = 'Workload'

        days_remaining = None

        # calculate remaining work time
        if options.get('today') and options.get('enddate'):
            currentdate = options['today']
            day = timedelta(days=1)
            days_remaining = 0
            while currentdate <= options['enddate']:
                if currentdate.weekday() < 5:
                    days_remaining += 1
                currentdate += day
            title += ' %g%s (~%s workdays left)' % (round(
                sum, 2), self.estimation_suffix, days_remaining)

        estimations_string = []
        labels = []
        workhoursperday = max(float(options.get('workhoursperday')), 0.0)
        chts = '000000'

        for owner, estimation in estimations.iteritems():
            # Note: Unconditional obfuscation of owner in case it represents
            # an email adress, and as the chart API doesn't support SSL
            # (plain http transfer only, from either client or server).
            label = "%s %g%s" % (obfuscate_email_address(owner),
                                 round(estimation, 2), self.estimation_suffix)

            if days_remaining != None:

                user_remaining_hours = days_remaining * workhoursperday

                if not user_remaining_hours or (estimation /
                                                user_remaining_hours) > 1:
                    label = "%s (~%g hours left)!" % (
                        label, round(user_remaining_hours, 2)
                    )  # user does not have enough hours left
                    chts = 'FF0000'  # set chart title style to red
                    pass
                pass

            labels.append(label)

            estimations_string.append(str(int(estimation)))

            pass

        chart_args = unicode_urlencode({
            'chs':
            '%sx%s' % (options['width'], options['height']),
            'chf':
            'bg,s,00000000',
            'chd':
            't:%s' % ",".join(estimations_string),
            'cht':
            'p3',
            'chtt':
            title,
            'chts':
            chts,
            'chl':
            "|".join(labels),
            'chco':
            options['color']
        })

        self.log.debug("WorkloadChart data: %s" % repr(chart_args))
        if self.serverside_charts:
            return tag.image(
                src="%s?data=%s" %
                (req.href.estimationtools('chart'), unicode_quote(chart_args)),
                alt="Workload Chart (server)")
        else:
            return tag.image(src="https://chart.googleapis.com/chart?%s" %
                             chart_args,
                             alt="Workload Chart (client)")
Example #22
0
    def get_list(self, realm, wl, req, fields=None):
        db = self.env.get_db_cnx()
        cursor = db.cursor()
        user = req.authname
        locale = getattr( req, 'locale', LC_TIME)
        context = Context.from_request(req)
        wikilist = []
        extradict = {}
        if not fields:
            fields = set(self.default_fields['wiki'])
        else:
            fields = set(fields)

        if 'changetime' in fields:
            max_changetime = datetime(1970,1,1,tzinfo=utc)
            min_changetime = datetime.now(utc)

        for name, last_visit in wl.get_watched_resources( 'wiki', req.authname ):
            wikipage = WikiPage(self.env, name, db=db)
            wikidict = {}

            if not wikipage.exists:
                wikidict['deleted'] = True
                if 'name' in fields:
                    wikidict['name'] = name
                if 'author' in fields:
                    wikidict['author'] = '?'
                if 'changetime' in fields:
                    wikidict['changedsincelastvisit'] = 1
                    wikidict['changetime'] = '?'
                    wikidict['ichangetime'] = 0
                if 'comment' in fields:
                    wikidict['comment'] = tag.strong(t_("deleted"), class_='deleted')
                if 'notify' in fields:
                    wikidict['notify'] =  wl.is_notify(req, 'wiki', name)
                wikilist.append(wikidict)
                continue

            comment = wikipage.comment
            changetime = wikipage.time
            author = wikipage.author
            if wl.options['attachment_changes']:
                latest_attachment = None
                for attachment in Attachment.select(self.env, 'wiki', name, db):
                    if attachment.date > changetime:
                        latest_attachment = attachment
                if latest_attachment:
                    changetime = latest_attachment.date
                    author = latest_attachment.author
                    if 'comment' in fields:
                        wikitext = '[attachment:"' + ':'.join([latest_attachment.filename,'wiki',name]) + \
                                   '" ' + latest_attachment.filename  + ']'
                        desc = latest_attachment.description
                        comment = tag(tag_("Attachment %(attachment)s added",\
                                attachment=format_to_oneliner(self.env, context, wikitext, shorten=False)),
                                desc and ': ' or '.', moreless(desc,10))
            if 'attachment' in fields:
                attachments = []
                for attachment in Attachment.select(self.env, 'wiki', name, db):
                    wikitext = '[attachment:"' + ':'.join([attachment.filename,'wiki',name]) + '" ' + attachment.filename  + ']'
                    attachments.extend([tag(', '), format_to_oneliner(self.env, context, wikitext, shorten=False)])
                if attachments:
                    attachments.reverse()
                    attachments.pop()
                ticketdict['attachment'] = moreless(attachments, 5)
            if 'name' in fields:
                wikidict['name'] = name
            if 'author' in fields:
                if not (Chrome(self.env).show_email_addresses or
                        'EMAIL_VIEW' in req.perm(wikipage.resource)):
                    wikidict['author'] = obfuscate_email_address(author)
                else:
                    wikidict['author'] = author
            if 'version' in fields:
                wikidict['version'] = unicode(wikipage.version)
            if 'changetime' in fields:
                wikidict['changetime'] = format_datetime( changetime, locale=locale, tzinfo=req.tz )
                wikidict['ichangetime'] = to_timestamp( changetime )
                wikidict['changedsincelastvisit'] = last_visit < wikidict['ichangetime'] and 1 or 0
                wikidict['timedelta'] = pretty_timedelta( changetime )
                wikidict['timeline_link'] = req.href.timeline(precision='seconds',
                    from_=trac_format_datetime ( changetime, 'iso8601', tzinfo=req.tz))
                if changetime > max_changetime:
                    max_changetime = changetime
                if changetime < min_changetime:
                    min_changetime = changetime
            if 'comment' in fields:
                comment = moreless(comment or "", 200)
                wikidict['comment'] = comment
            if 'notify' in fields:
                wikidict['notify']   = wl.is_notify(req, 'wiki', name)
            if 'readonly' in fields:
                wikidict['readonly'] = wikipage.readonly and t_("yes") or t_("no")
            if 'tags' in fields and self.tagsystem:
                tags = []
                for t in self.tagsystem.get_tags(req, Resource('wiki', name)):
                    tags.extend([tag.a(t,href=req.href('tags',q=t)), tag(', ')])
                if tags:
                    tags.pop()
                wikidict['tags'] = moreless(tags, 10)
            #if 'ipnr' in fields:
            #    wikidict['ipnr'] = wikipage.ipnr,  # Note: Not supported by Trac 0.12
            wikilist.append(wikidict)

        if 'changetime' in fields:
            extradict['max_changetime'] = format_datetime( max_changetime, locale=locale, tzinfo=req.tz )
            extradict['min_changetime'] = format_datetime( min_changetime, locale=locale, tzinfo=req.tz )

        return wikilist, extradict
Example #23
0
    def render_property_diff(env, req, ticket, field, old, new,
                              resource_new=None):
        "Version for Trac 0.12"
        rendered = None
        # per type special rendering of diffs
        type_ = None
        for f in ticket.fields:
            if f['name'] == field:
                type_ = f['type']
                break
        if type_ == 'checkbox':
            rendered = new == '1' and _("set") or _("unset")
        elif type_ == 'textarea':
            if not resource_new:
                rendered = _("modified")
            else:
                href = get_resource_url(env, resource_new, req.href,
                                        action='diff')
                # TRANSLATOR: modified ('diff') (link)
                diff = tag.a(_("diff"), href=href)
                rendered = tag_("modified (%(diff)s)", diff=diff)

        # per name special rendering of diffs
        old_list, new_list = None, None
        render_elt = lambda x: x
        sep = ', '
        if field == 'cc':
            chrome = Chrome(env)
            old_list, new_list = chrome.cc_list(old), chrome.cc_list(new)
            if not (Chrome(env).show_email_addresses or
                    'EMAIL_VIEW' in req.perm(resource_new or ticket.resource)):
                render_elt = obfuscate_email_address
        elif field == 'keywords':
            old_list, new_list = old.split(), new.split()
            sep = ' '
        if (old_list, new_list) != (None, None):
            added = [tag.em(render_elt(x)) for x in new_list
                     if x not in old_list]
            remvd = [tag.em(render_elt(x)) for x in old_list
                     if x not in new_list]
            added = added and tagn_("%(items)s added", "%(items)s added",
                                    len(added), items=separated(added, sep))
            remvd = remvd and tagn_("%(items)s removed", "%(items)s removed",
                                    len(remvd), items=separated(remvd, sep))
            if added or remvd:
                rendered = tag(added, added and remvd and _("; "), remvd)
                return rendered
        if field in ('reporter', 'owner'):
            if not (Chrome(env).show_email_addresses or
                    'EMAIL_VIEW' in req.perm(resource_new or ticket.resource)):
                old = obfuscate_email_address(old)
                new = obfuscate_email_address(new)
        # Added by MS
        # The `wtag_` is the `tag_` from tracwatchlist.translation, e.g. 
        # using its translation domain.
        if field == 'attachment':
            rendered = wtag_("%(value)s added", value=tag.em(new))
        # changed 'if' to 'elif':
        elif old and not new:
            rendered = tag_("%(value)s deleted", value=tag.em(old))
        elif new and not old:
            rendered = tag_("set to %(value)s", value=tag.em(new))
        elif old and new:
            rendered = tag_("changed from %(old)s to %(new)s",
                            old=tag.em(old), new=tag.em(new))
        return rendered
Example #24
0
    def _notify(self, ticket, newticket=True, modtime=None):
        self.ticket = ticket
        self.modtime = modtime
        self.newticket = newticket

        changes_body = ""
        self.reporter = ""
        self.owner = ""
        changes_descr = ""
        change_data = {}
        link = self.env.abs_href.ticket(ticket.id)
        summary = self.ticket["summary"]

        if not self.newticket and modtime:  # Ticket change
            from trac.ticket.web_ui import TicketModule

            for change in TicketModule(self.env).grouped_changelog_entries(ticket, self.db, when=modtime):
                if not change["permanent"]:  # attachment with same time...
                    continue
                change_data.update(
                    {
                        "author": obfuscate_email_address(change["author"]),
                        "comment": wrap(change["comment"], self.COLS, " ", " ", CRLF),
                    }
                )
                link += "#comment:%s" % str(change.get("cnum", ""))
                for field, values in change["fields"].iteritems():
                    old = values["old"]
                    new = values["new"]
                    newv = ""
                    if field == "description":
                        new_descr = wrap(new, self.COLS, " ", " ", CRLF)
                        old_descr = wrap(old, self.COLS, "> ", "> ", CRLF)
                        old_descr = old_descr.replace(2 * CRLF, CRLF + ">" + CRLF)
                        cdescr = CRLF
                        cdescr += "Old description:" + 2 * CRLF + old_descr + 2 * CRLF
                        cdescr += "New description:" + 2 * CRLF + new_descr + CRLF
                        changes_descr = cdescr
                    elif field == "summary":
                        summary = "%s (was: %s)" % (new, old)
                    elif field == "cc":
                        (addcc, delcc) = self.diff_cc(old, new)
                        chgcc = ""
                        if delcc:
                            chgcc += wrap(" * cc: %s (removed)" % ", ".join(delcc), self.COLS, " ", " ", CRLF) + CRLF
                        if addcc:
                            chgcc += wrap(" * cc: %s (added)" % ", ".join(addcc), self.COLS, " ", " ", CRLF) + CRLF
                        if chgcc:
                            changes_body += chgcc
                        self.prev_cc += old and self.parse_cc(old) or []
                    else:
                        if field in ["owner", "reporter"]:
                            old = obfuscate_email_address(old)
                            new = obfuscate_email_address(new)
                        newv = new
                        length = 7 + len(field)
                        spacer_old, spacer_new = " ", " "
                        if len(old + new) + length > self.COLS:
                            length = 5
                            if len(old) + length > self.COLS:
                                spacer_old = CRLF
                            if len(new) + length > self.COLS:
                                spacer_new = CRLF
                        chg = "* %s: %s%s%s=>%s%s" % (field, spacer_old, old, spacer_old, spacer_new, new)
                        chg = chg.replace(CRLF, CRLF + length * " ")
                        chg = wrap(chg, self.COLS, "", length * " ", CRLF)
                        changes_body += " %s%s" % (chg, CRLF)
                    if newv:
                        change_data[field] = {"oldvalue": old, "newvalue": new}

        ticket_values = ticket.values.copy()
        ticket_values["id"] = ticket.id
        ticket_values["description"] = wrap(
            ticket_values.get("description", ""), self.COLS, initial_indent=" ", subsequent_indent=" ", linesep=CRLF
        )
        ticket_values["new"] = self.newticket
        ticket_values["link"] = link

        subject = self.format_subj(summary)
        if not self.newticket:
            subject = "Re: " + subject
        self.data.update(
            {
                "ticket_props": self.format_props(),
                "ticket_body_hdr": self.format_hdr(),
                "subject": subject,
                "ticket": ticket_values,
                "changes_body": changes_body,
                "changes_descr": changes_descr,
                "change": change_data,
            }
        )
        NotifyEmail.notify(self, ticket.id, subject)
Example #25
0
    def render_property_diff(env,
                             req,
                             ticket,
                             field,
                             old,
                             new,
                             resource_new=None):
        "Version for Trac 0.12"
        rendered = None
        # per type special rendering of diffs
        type_ = None
        for f in ticket.fields:
            if f['name'] == field:
                type_ = f['type']
                break
        if type_ == 'checkbox':
            rendered = new == '1' and _("set") or _("unset")
        elif type_ == 'textarea':
            if not resource_new:
                rendered = _("modified")
            else:
                href = get_resource_url(env,
                                        resource_new,
                                        req.href,
                                        action='diff')
                # TRANSLATOR: modified ('diff') (link)
                diff = tag.a(_("diff"), href=href)
                rendered = tag_("modified (%(diff)s)", diff=diff)

        # per name special rendering of diffs
        old_list, new_list = None, None
        render_elt = lambda x: x
        sep = ', '
        if field == 'cc':
            chrome = Chrome(env)
            old_list, new_list = chrome.cc_list(old), chrome.cc_list(new)
            if not (Chrome(env).show_email_addresses or 'EMAIL_VIEW'
                    in req.perm(resource_new or ticket.resource)):
                render_elt = obfuscate_email_address
        elif field == 'keywords':
            old_list, new_list = old.split(), new.split()
            sep = ' '
        if (old_list, new_list) != (None, None):
            added = [
                tag.em(render_elt(x)) for x in new_list if x not in old_list
            ]
            remvd = [
                tag.em(render_elt(x)) for x in old_list if x not in new_list
            ]
            added = added and tagn_("%(items)s added",
                                    "%(items)s added",
                                    len(added),
                                    items=separated(added, sep))
            remvd = remvd and tagn_("%(items)s removed",
                                    "%(items)s removed",
                                    len(remvd),
                                    items=separated(remvd, sep))
            if added or remvd:
                rendered = tag(added, added and remvd and _("; "), remvd)
                return rendered
        if field in ('reporter', 'owner'):
            if not (Chrome(env).show_email_addresses or 'EMAIL_VIEW'
                    in req.perm(resource_new or ticket.resource)):
                old = obfuscate_email_address(old)
                new = obfuscate_email_address(new)
        # Added by MS
        # The `wtag_` is the `tag_` from tracwatchlist.translation, e.g.
        # using its translation domain.
        if field == 'attachment':
            rendered = wtag_("%(value)s added", value=tag.em(new))
        # changed 'if' to 'elif':
        elif old and not new:
            rendered = tag_("%(value)s deleted", value=tag.em(old))
        elif new and not old:
            rendered = tag_("set to %(value)s", value=tag.em(new))
        elif old and new:
            rendered = tag_("changed from %(old)s to %(new)s",
                            old=tag.em(old),
                            new=tag.em(new))
        return rendered
Example #26
0
 def format_props(self):
     tkt = self.ticket
     fields = [f for f in tkt.fields if f["name"] not in ("summary", "cc", "time", "changetime")]
     width = [0, 0, 0, 0]
     i = 0
     for f in fields:
         if f["type"] == "textarea":
             continue
         fname = f["name"]
         if not fname in tkt.values:
             continue
         fval = tkt[fname] or ""
         if fval.find("\n") != -1:
             continue
         if fname in ["owner", "reporter"]:
             fval = obfuscate_email_address(fval)
         idx = 2 * (i % 2)
         width[idx] = max(self.get_text_width(f["label"]), width[idx])
         width[idx + 1] = max(self.get_text_width(fval), width[idx + 1])
         i += 1
     width_l = width[0] + width[1] + 5
     width_r = width[2] + width[3] + 5
     half_cols = (self.COLS - 1) / 2
     if width_l + width_r + 1 > self.COLS:
         if (width_l > half_cols and width_r > half_cols) or (width[0] > half_cols / 2 or width[2] > half_cols / 2):
             width_l = half_cols
             width_r = half_cols
         elif width_l > width_r:
             width_l = min((self.COLS - 1) * 2 / 3, width_l)
             width_r = self.COLS - width_l - 1
         else:
             width_r = min((self.COLS - 1) * 2 / 3, width_r)
             width_l = self.COLS - width_r - 1
     sep = width_l * "-" + "+" + width_r * "-"
     txt = sep + CRLF
     cell_tmp = [u"", u""]
     big = []
     i = 0
     width_lr = [width_l, width_r]
     for f in [f for f in fields if f["name"] != "description"]:
         fname = f["name"]
         if not tkt.values.has_key(fname):
             continue
         fval = tkt[fname] or ""
         if fname in ["owner", "reporter"]:
             fval = obfuscate_email_address(fval)
         if f["type"] == "textarea" or "\n" in unicode(fval):
             big.append((f["label"], CRLF.join(fval.splitlines())))
         else:
             # Note: f['label'] is a Babel's LazyObject, make sure its
             # __str__ method won't be called.
             str_tmp = u"%s:  %s" % (f["label"], unicode(fval))
             idx = i % 2
             cell_tmp[idx] += wrap(
                 str_tmp,
                 width_lr[idx] - 2 + 2 * idx,
                 (width[2 * idx] - self.get_text_width(f["label"]) + 2 * idx) * " ",
                 2 * " ",
                 CRLF,
             )
             cell_tmp[idx] += CRLF
             i += 1
     cell_l = cell_tmp[0].splitlines()
     cell_r = cell_tmp[1].splitlines()
     for i in range(max(len(cell_l), len(cell_r))):
         if i >= len(cell_l):
             cell_l.append(width_l * " ")
         elif i >= len(cell_r):
             cell_r.append("")
         fmt_width = width_l - self.get_text_width(cell_l[i]) + len(cell_l[i])
         txt += u"%-*s|%s%s" % (fmt_width, cell_l[i], cell_r[i], CRLF)
     if big:
         txt += sep
         for name, value in big:
             txt += CRLF.join(["", name + ":", value, "", ""])
     txt += sep
     return txt
Example #27
0
 def format_props(self):
     tkt = self.ticket
     fields = [f for f in tkt.fields 
               if f['name'] not in ('summary', 'cc', 'time', 'changetime')]
     width = [0, 0, 0, 0]
     i = 0
     for f in fields:
         if f['type'] == 'textarea':
             continue
         fname = f['name']
         if not fname in tkt.values:
             continue
         fval = tkt[fname] or ''
         if fval.find('\n') != -1:
             continue
         if fname in ['owner', 'reporter']:
             fval = obfuscate_email_address(fval)
         idx = 2 * (i % 2)
         width[idx] = max(self.get_text_width(f['label']), width[idx])
         width[idx + 1] = max(self.get_text_width(fval), width[idx + 1])
         i += 1
     width_l = width[0] + width[1] + 5
     width_r = width[2] + width[3] + 5
     half_cols = (self.COLS - 1) / 2
     if width_l + width_r + 1 > self.COLS:
         if ((width_l > half_cols and width_r > half_cols) or 
                 (width[0] > half_cols / 2 or width[2] > half_cols / 2)):
             width_l = half_cols
             width_r = half_cols
         elif width_l > width_r:
             width_l = min((self.COLS - 1) * 2 / 3, width_l)
             width_r = self.COLS - width_l - 1
         else:
             width_r = min((self.COLS - 1) * 2 / 3, width_r)         
             width_l = self.COLS - width_r - 1
     sep = width_l * '-' + '+' + width_r * '-'
     txt = sep + CRLF
     cell_tmp = [u'', u'']
     big = []
     i = 0
     width_lr = [width_l, width_r]
     for f in [f for f in fields if f['name'] != 'description']:
         fname = f['name']
         if not tkt.values.has_key(fname):
             continue
         fval = tkt[fname] or ''
         if fname in ['owner', 'reporter']:
             fval = obfuscate_email_address(fval)
         if f['type'] == 'textarea' or '\n' in unicode(fval):
             big.append((f['label'], CRLF.join(fval.splitlines())))
         else:
             # Note: f['label'] is a Babel's LazyObject, make sure its
             # __str__ method won't be called.
             str_tmp = u'%s:  %s' % (f['label'], unicode(fval))
             idx = i % 2
             cell_tmp[idx] += wrap(str_tmp, width_lr[idx] - 2 + 2 * idx,
                                   (width[2 * idx]
                                    - self.get_text_width(f['label'])
                                    + 2 * idx) * ' ',
                                   2 * ' ', CRLF)
             cell_tmp[idx] += CRLF
             i += 1
     cell_l = cell_tmp[0].splitlines()
     cell_r = cell_tmp[1].splitlines()
     for i in range(max(len(cell_l), len(cell_r))):
         if i >= len(cell_l):
             cell_l.append(width_l * ' ')
         elif i >= len(cell_r):
             cell_r.append('')
         fmt_width = width_l - self.get_text_width(cell_l[i]) \
                     + len(cell_l[i])
         txt += u'%-*s|%s%s' % (fmt_width, cell_l[i], cell_r[i], CRLF)
     if big:
         txt += sep
         for name, value in big:
             txt += CRLF.join(['', name + ':', value, '', ''])
     txt += sep
     return txt