예제 #1
0
    def _generate_form(self, req, data):
        batchFormData = dict(data)
        batchFormData['query_href']= req.session['query_href'] \
                                     or req.href.query()
        batchFormData['notify_enabled'] = self.config.getbool(
            'notification', 'smtp_enabled', False)

        ticketSystem = TicketSystem(self.env)
        fields = []
        for field in ticketSystem.get_ticket_fields():
            if field['name'] not in ('summary', 'reporter', 'description'):
                fields.append(field)
            if field['name'] == 'owner' \
                and hasattr(ticketSystem, 'eventually_restrict_owner'):
                ticketSystem.eventually_restrict_owner(field)
        fields.sort(key=lambda f: f['name'])
        batchFormData['fields'] = fields

        add_script(req, 'batchmod/js/batchmod.js')
        add_stylesheet(req, 'batchmod/css/batchmod.css')
        stream = Chrome(self.env).render_template(req,
                                                  'batchmod.html',
                                                  batchFormData,
                                                  fragment=True)
        return stream.select('//form[@id="batchmod_form"]')
예제 #2
0
파일: batch.py 프로젝트: ohanar/trac
 def _get_action_controllers(self, req, ticket, action):
     """Generator yielding the controllers handling the given `action`"""
     for controller in TicketSystem(self.env).action_controllers:
         actions = [a for w, a in
                    controller.get_ticket_actions(req, ticket) or []]
         if action in actions:
             yield controller
예제 #3
0
    def _get_action_controls(self, req, tickets):
        action_controls = []
        ts = TicketSystem(self.env)
        tickets_by_action = {}
        for t in tickets:
            ticket = Ticket(self.env, t['id'])
            available_actions = ts.get_available_actions(req, ticket)
            for action in available_actions:
                tickets_by_action.setdefault(action, []).append(ticket)

        # Sort the allowed actions by the 'default' key.
        allowed_actions = set(tickets_by_action.keys())
        workflow = ConfigurableTicketWorkflow(self.env)
        all_actions = sorted(
            ((action['default'], name)
             for name, action in workflow.get_all_actions().iteritems()),
            reverse=True)
        sorted_actions = [
            action[1] for action in all_actions if action[1] in allowed_actions
        ]
        for action in sorted_actions:
            first_label = None
            hints = []
            widgets = []
            ticket = tickets_by_action[action][0]
            for controller in self._get_action_controllers(
                    req, ticket, action):
                label, widget, hint = controller.render_ticket_action_control(
                    req, ticket, action)
                if not first_label:
                    first_label = label
                widgets.append(widget)
                hints.append(hint)
            action_controls.append((action, first_label, tag(widgets), hints))
        return action_controls
def get_available_actions(env, action='resolve'):
    # The list should not have duplicates.
    ts = TicketSystem(env)
    for controller in ts.action_controllers:
        if isinstance(controller, ConfigurableTicketWorkflow):
            return controller.actions.get(action)
    return None
예제 #5
0
    def __init__(self,
                 env,
                 constraints=None,
                 order=None,
                 desc=0,
                 group=None,
                 groupdesc=0,
                 verbose=0):
        self.env = env
        self.constraints = constraints or {}
        self.order = order
        self.desc = desc
        self.group = group
        self.groupdesc = groupdesc
        self.verbose = verbose
        self.fields = TicketSystem(self.env).get_ticket_fields()
        self.cols = []  # lazily initialized

        if self.order != 'id' \
                and self.order not in [f['name'] for f in self.fields]:
            # order by priority by default
            self.order = 'priority'

        if self.group not in [f['name'] for f in self.fields]:
            self.group = None
예제 #6
0
    def _get_options(self, field_name):
        """Return a list of options for the given [dynvars] field:
        
         [dynvars]
         myfield.options = value1|value2|value3
        
        If no [dynvars] field is found, a select field is searched
        and its options returned.  For the milestone field, completed
        milestones are omitted.  If no select field is found, then an
        empty list is returned."""
        # look for [dynvars] field
        for key, val in self.env.config.options('dynvars'):
            if key == field_name + '.options':
                return val.split('|')

        # handle milestone special - skip completed milestones
        if field_name == 'milestone':
            return [''] + [
                m.name
                for m in Milestone.select(self.env, include_completed=False)
            ]

        # lookup select field
        for field in TicketSystem(self.env).get_ticket_fields():
            if field['name'] == field_name and 'options' in field:
                return field['options']
        return []
예제 #7
0
 def _get_action_controls(self, req, tickets):
     action_controls = []
     ts = TicketSystem(self.env)
     tickets_by_action = {}
     for t in tickets:
         ticket = Ticket(self.env, t['id'])
         actions = ts.get_available_actions(req, ticket)
         for action in actions:
             tickets_by_action.setdefault(action, []).append(ticket)
     sorted_actions = sorted(set(tickets_by_action.keys()))
     for action in sorted_actions:
         first_label = None
         hints = []
         widgets = []
         ticket = tickets_by_action[action][0]
         for controller in self._get_action_controllers(
                 req, ticket, action):
             label, widget, hint = controller.render_ticket_action_control(
                 req, ticket, action)
             if not first_label:
                 first_label = label
             widgets.append(widget)
             hints.append(hint)
         action_controls.append((action, first_label, tag(widgets), hints))
     return action_controls
예제 #8
0
 def process_request(self, req):
     if req.path_info == '/' and MobileDetect(req).is_mobile():
         req.redict('/mobile')
     else:
         ts = TicketSystem(self.env)
         fields = ts.get_ticket_fields()
         return 'test.html', {"JSON": custom_json, "fields": fields}, None
예제 #9
0
 def _add_ticket_fields(self, req):
     for field in TicketSystem(self.env).get_ticket_fields():
         name = field['name']
         del field['name']
         if name in ('summary', 'reporter', 'description', 'type', 'status',
                     'resolution', 'owner'):
             field['skip'] = True
         req.hdf['ticket.fields.' + name] = field
예제 #10
0
    def meta_getTypeDefinition(self, req):
        ticket_system = TicketSystem(self.env)
        fields = ticket_system.get_ticket_fields()

        # remove ticket type as this should be a parameter of the request
        # XXX Trac doesn't support fields based on ticket type
        fields = [field for field in fields if field['name'] != 'type']
        return fields
예제 #11
0
    def _do_save(self, req, db, ticket):
        if req.perm.has_permission('TICKET_CHGPROP'):
            # TICKET_CHGPROP gives permission to edit the ticket
            if not req.args.get('summary'):
                raise TracError('Tickets must contain summary.')

            if req.args.has_key('description') or req.args.has_key('reporter'):
                req.perm.assert_permission('TICKET_ADMIN')

            ticket.populate(req.args)
        else:
            req.perm.assert_permission('TICKET_APPEND')

        # Mid air collision?
        if int(req.args.get('ts')) != ticket.time_changed:
            raise TracError("Sorry, can not save your changes. "
                            "This ticket has been modified by someone else "
                            "since you started", 'Mid Air Collision')

        # Do any action on the ticket?
        action = req.args.get('action')
        actions = TicketSystem(self.env).get_available_actions(ticket, req.perm)
        if action not in actions:
            raise TracError('Invalid action')

        # TODO: this should not be hard-coded like this
        if action == 'accept':
            ticket['status'] =  'assigned'
            ticket['owner'] = req.authname
        if action == 'resolve':
            ticket['status'] = 'closed'
            ticket['resolution'] = req.args.get('resolve_resolution')
        elif action == 'reassign':
            ticket['owner'] = req.args.get('reassign_owner')
            ticket['status'] = 'new'
        elif action == 'reopen':
            ticket['status'] = 'reopened'
            ticket['resolution'] = ''

        self._validate_ticket(req, ticket)

        now = int(time.time())
        cnum = req.args.get('cnum')        
        replyto = req.args.get('replyto')
        internal_cnum = cnum
        if cnum and replyto: # record parent.child relationship
            internal_cnum = '%s.%s' % (replyto, cnum)
        if ticket.save_changes(get_reporter_id(req, 'author'),
                               req.args.get('comment'), when=now, db=db,
                               cnum=internal_cnum):
            db.commit()

            try:
                tn = TicketNotifyEmail(self.env)
                tn.notify(ticket, newticket=False, modtime=now)
            except Exception, e:
                self.log.exception("Failure sending notification on change to "
                                   "ticket #%s: %s" % (ticket.id, e))
예제 #12
0
    def _get_action_controllers(self, req, ticket, action):

        for controller in TicketSystem(self.env).action_controllers:
            actions = [
                action for weight, action in controller.get_ticket_actions(
                    req, ticket)
            ]
            if action in actions:
                yield controller
예제 #13
0
 def __init__(self, env, tkt_id=None, db=None):
     self.env = env
     self.fields = TicketSystem(self.env).get_ticket_fields()
     self.values = {}
     if tkt_id:
         self._fetch_ticket(tkt_id, db)
     else:
         self._init_defaults(db)
         self.id = self.time_created = self.time_changed = None
     self._old = {}
예제 #14
0
 def _get_new_ticket_values(self, req, env):
     """Pull all of the new values out of the post data."""
     values = {}
     for field in TicketSystem(env).get_ticket_fields():
         name = field['name']
         if name not in ('summary', 'reporter', 'description'):
             value = req.args.get('batchmod_value_' + name)
             if value is not None:
                 values[name] = value
     return values
예제 #15
0
def _get_groups(env, db, by='component'):
    for field in TicketSystem(env).get_ticket_fields():
        if field['name'] == by:
            if field.has_key('options'):
                return field['options']
            else:
                cursor = db.cursor()
                cursor.execute("SELECT DISTINCT %s FROM ticket ORDER BY %s" %
                               (by, by))
                return [row[0] for row in cursor]
    return []
예제 #16
0
    def _render_view(self, req, db, milestone):
        req.hdf['title'] = 'Milestone %s' % milestone.name
        req.hdf['milestone.mode'] = 'view'

        req.hdf['milestone'] = milestone_to_hdf(self.env, db, req, milestone)

        available_groups = []
        component_group_available = False
        for field in TicketSystem(self.env).get_ticket_fields():
            if field['type'] == 'select' and field['name'] != 'milestone' \
                    or field['name'] == 'owner':
                available_groups.append({
                    'name': field['name'],
                    'label': field['label']
                })
                if field['name'] == 'component':
                    component_group_available = True
        req.hdf['milestone.stats.available_groups'] = available_groups

        if component_group_available:
            by = req.args.get('by', 'component')
        else:
            by = req.args.get('by', available_groups[0]['name'])
        req.hdf['milestone.stats.grouped_by'] = by

        tickets = get_tickets_for_milestone(self.env, db, milestone.name, by)
        stats = calc_ticket_stats(tickets)
        req.hdf['milestone.stats'] = stats
        for key, value in get_query_links(req, milestone.name).items():
            req.hdf['milestone.queries.' + key] = value

        groups = _get_groups(self.env, db, by)
        group_no = 0
        max_percent_total = 0
        for group in groups:
            group_tickets = [t for t in tickets if t[by] == group]
            if not group_tickets:
                continue
            prefix = 'milestone.stats.groups.%s' % group_no
            req.hdf['%s.name' % prefix] = group
            percent_total = 0
            if len(tickets) > 0:
                percent_total = float(len(group_tickets)) / float(len(tickets))
                if percent_total > max_percent_total:
                    max_percent_total = percent_total
            req.hdf['%s.percent_total' % prefix] = percent_total * 100
            stats = calc_ticket_stats(group_tickets)
            req.hdf[prefix] = stats
            for key, value in \
                    get_query_links(req, milestone.name, by, group).items():
                req.hdf['%s.queries.%s' % (prefix, key)] = value
            group_no += 1
        req.hdf['milestone.stats.max_percent_total'] = max_percent_total * 100
예제 #17
0
 def process_request(self, req):
     if req.path_info == '/':
         try:
             from mobiledetect import MobileDetect
             if MobileDetect(
                     useragent=req.get_header('user-agent')).is_mobile():
                 req.redirect('/mobile')
         except ImportError:
             pass
     else:
         ts = TicketSystem(self.env)
         fields = ts.get_ticket_fields()
         return 'test.html', {"JSON": custom_json, "fields": fields}, None
예제 #18
0
    def _get_new_ticket_values(self, req):
        """Pull all of the new values out of the post data."""
        values = {}

        for field in TicketSystem(self.env).get_ticket_fields():
            name = field['name']
            if name not in ('id', 'resolution', 'status', 'owner', 'time',
                            'changetime', 'summary', 'reporter',
                            'description') and field['type'] != 'textarea':
                value = req.args.get('batchmod_value_' + name)
                if value is not None:
                    values[name] = value
        return values
예제 #19
0
 def get_value_and_options(self, req, target, key):
     """Returns the preference value for the given key if configured
     for being set by user preference.  If no user preference has been
     set yet, the target field's default value is returned."""
     value = ''
     options = []
     for field in TicketSystem(self.env).get_ticket_fields():
         if field['name'] == target:
             value = field.get('value', value)
             options = field.get('options', options)
             break
     if key in self._pref_defaults:
         value = req.session.get(PREFIX + key + '.value', value)
     return value, options
예제 #20
0
    def _get_constraints(self, req):
        constraints = {}
        ticket_fields = [
            f['name'] for f in TicketSystem(self.env).get_ticket_fields()
        ]

        # A special hack for Safari/WebKit, which will not submit dynamically
        # created check-boxes with their real value, but with the default value
        # 'on'. See also htdocs/query.js#addFilter()
        checkboxes = [k for k in req.args.keys() if k.startswith('__')]
        if checkboxes:
            import cgi
            for checkbox in checkboxes:
                (real_k, real_v) = checkbox[2:].split(':', 2)
                req.args.list.append(cgi.MiniFieldStorage(real_k, real_v))

        # For clients without JavaScript, we remove constraints here if
        # requested
        remove_constraints = {}
        to_remove = [
            k[10:] for k in req.args.keys() if k.startswith('rm_filter_')
        ]
        if to_remove:  # either empty or containing a single element
            match = re.match(r'(\w+?)_(\d+)$', to_remove[0])
            if match:
                remove_constraints[match.group(1)] = int(match.group(2))
            else:
                remove_constraints[to_remove[0]] = -1

        for field in [k for k in req.args.keys() if k in ticket_fields]:
            vals = req.args[field]
            if not isinstance(vals, (list, tuple)):
                vals = [vals]
            vals = map(lambda x: x.value, vals)
            if vals:
                mode = req.args.get(field + '_mode')
                if mode:
                    vals = map(lambda x: mode + x, vals)
                if remove_constraints.has_key(field):
                    idx = remove_constraints[field]
                    if idx >= 0:
                        del vals[idx]
                        if not vals:
                            continue
                    else:
                        continue
                constraints[field] = vals

        return constraints
예제 #21
0
    def delete(self, db=None):
        db, handle_ta = self._get_db_for_write(db)
        Attachment.delete_all(self.env, 'ticket', self.id, db)
        cursor = db.cursor()
        cursor.execute("DELETE FROM ticket WHERE id=%s", (self.id, ))
        cursor.execute("DELETE FROM ticket_change WHERE ticket=%s",
                       (self.id, ))
        cursor.execute("DELETE FROM ticket_custom WHERE ticket=%s",
                       (self.id, ))

        if handle_ta:
            db.commit()

        for listener in TicketSystem(self.env).change_listeners:
            listener.ticket_deleted(self)
예제 #22
0
 def get_statistics_source(self, active=None):
     stats_source = [{
         'value': None,
         'label': _('Number of tickets'),
         'active': not active
     }]
     fields = TicketSystem(self.env).get_ticket_fields()
     for field in fields:
         if field['name'] in self._calculate_statistics_on:
             stats_source.append({
                 'value': field['name'],
                 'label': field['label'],
                 'active': field['name'] == active
             })
     return stats_source
예제 #23
0
    def _get_new_ticket_values(self, req):
        """Pull all of the new values out of the post data."""
        values = {}

        for field in TicketSystem(self.env).get_ticket_fields():
            name = field['name']
            if name not in ('id', 'resolution', 'status', 'owner', 'time',
                            'changetime', 'summary', 'description') + \
                           (('reporter',) if 'TICKET_ADMIN' not in req.perm
                                          else ()) \
                    and field['type'] != 'textarea':
                value = req.args.get('batchmod_value_' + name)
                if value is not None:
                    values[name] = self._parse_field_value(req, field, value)
        return values
예제 #24
0
    def filter_stream(self, req, method, filename, stream, formdata):
        '''Add workflows to query/report output'''
        if filename != 'query.html' and filename != 'report_view.html':
            return stream
        if not (req.perm.has_permission('TICKET_ADMIN')
                or req.perm.has_permission('TICKET_GRID_WORKFLOW')):
            return stream

        ts = TicketSystem(self.env)

        add_script(req, 'gridflow/gridflow.js')

        html = stream.render()
        js = ''
        tickets = []

        copy = genshi.XML(html)
        nodes = genshi.path.Path('//td[contains(@class, "ticket")]//a/text()')
        tickets += [int(a[1][1:]) for a in nodes.select(copy)]

        copy = genshi.XML(html)
        nodes = genshi.path.Path('//td[contains(@class, "id")]//a/text()')
        tickets += [int(a[1][1:]) for a in nodes.select(copy)]

        copy = genshi.XML(html)
        tktDict = {}
        for tno in tickets:
            tktDict[tno] = {'labels': [], 'widgets': [], 'actions': []}
            tkt = trac.ticket.Ticket(self.env, tno)
            actions = ts.get_available_actions(req, tkt)
            for action in actions:
                for controller in self._get_action_controllers(
                        req, tkt, action):
                    (label, widget,
                     hint) = controller.render_ticket_action_control(
                         req, tkt, action)
                    tktDict[tno]['actions'].append(action)
                    tktDict[tno]['labels'].append(label)
                    tktDict[tno]['widgets'].append(widget.generate().render())

        js += 'tktDict = ' + repr(tktDict).replace(", u'", ", '").replace(
            "[u'", "['") + ';\n'
        js += 'baseURL = "%s";\n' % req.base_url

        script = genshi.builder.tag.script(js, type="text/javascript")
        xpath = '//head'
        copy |= Transformer(xpath).append(script)
        return copy
예제 #25
0
def get_tickets_for_milestone(env, db, milestone, field='component'):
    cursor = db.cursor()
    fields = TicketSystem(env).get_ticket_fields()
    if field in [f['name'] for f in fields if not f.get('custom')]:
        cursor.execute(
            "SELECT id,status,%s FROM ticket WHERE milestone like %%s "
            "ORDER BY %s" % (field, field), (milestone + '%', ))
    else:
        cursor.execute(
            "SELECT id,status,value FROM ticket LEFT OUTER "
            "JOIN ticket_custom ON (id=ticket AND name=%s) "
            "WHERE milestone like %s ORDER BY value", (field, milestone + '%'))
    tickets = []
    for tkt_id, status, fieldval in cursor:
        tickets.append({'id': tkt_id, 'status': status, field: fieldval})
    return tickets
예제 #26
0
    def _batch_modify(self, req):
        tickets = req.session['query_tickets'].split(' ')
        comment = req.args.get('comment', '')
        values = {}

        for field in TicketSystem(self.env).get_ticket_fields():
            name = field['name']
            if name not in ('summary', 'reporter', \
                        'description', 'type', 'status',
                        'resolution', 'owner'):
                if req.args.has_key('bm_' + name):
                    values[name] = req.args.get(name)

        for id in tickets:
            t = Ticket(self.env, id)
            t.populate(values)
            t.save_changes(req.authname, comment)
예제 #27
0
	def _do_checkdates(self):
		ts = TicketSystem(self.env)
		now = datetime.now(utc)
		hard_mail = []
		soft_mail = []
		with self.env.db_query as db:
			cursor = db.cursor()
			cursor.execute("SELECT id FROM ticket WHERE status != 'closed'")
			for row in cursor:
				ticket_id = row[0]
				t = Ticket(self.env, ticket_id, db)
				t['id'] = ticket_id
				if t['due_date']:
					due_diff = parse_date(t['due_date']) - now
					if due_diff <= self.diff_threshold:
						hard_mail.append(t)
				if t['soft_due_date']:
					soft_due_diff = parse_date(t['soft_due_date']) - now
					if soft_due_diff <= self.soft_diff_threshold:
						soft_mail.append(t)

		if len(hard_mail) == 0 and len(soft_mail) == 0:
			return

		mail_body = "This is your friendly Ticket Due Date Mailer for today.\n\n"

		hard_mail=sorted(hard_mail, key=lambda ticket: ticket['due_date'])
		soft_mail=sorted(soft_mail, key=lambda ticket: ticket['soft_due_date'])

		if len(hard_mail) != 0:
			mail_body += "The following tickets are nearing their HARD DEADLINE:\n"
			mail_body +=         "    id  due_date       component  summary\n"
			for ticket in hard_mail:
				mail_body += "  % 4d  %s  % 12s  %s\n" % (ticket['id'], ticket['due_date'], ticket['component'], ticket['summary'])
			mail_body += "\n"

		if len(soft_mail) != 0:
			mail_body += "The following tickets are nearing their SOFT DEADLINE:\n"
			mail_body +=         "    id  soft_due_date  component  summary\n"
			for ticket in soft_mail:
				mail_body += "  % 4d  %s  % 12s  %s\n" % (ticket['id'], ticket['soft_due_date'], ticket['component'], ticket['summary'])
			mail_body += "\n"

		mail_body += "See you next time!\n"
		mail_body += "Trac Assistent"
		print mail_body.encode('utf-8')
예제 #28
0
def grouped_stats_data(env, stats_provider, tickets, by, per_group_stats_data):
    """Get the `tickets` stats data grouped by ticket field `by`.
    
    `per_group_stats_data(gstat, group_name)` should return a data dict to
    include for the group with field value `group_name`.
    """
    group_names = []
    for field in TicketSystem(env).get_ticket_fields():
        if field['name'] == by:
            if 'options' in field:
                group_names = field['options']
                if field.get('optional'):
                    group_names.insert(0, '')
            else:
                group_names = [
                    name for name, in env.db_query("""
                    SELECT DISTINCT COALESCE(%s, '') FROM ticket
                    ORDER BY COALESCE(%s, '')
                    """ % (by, by))
                ]
    max_count = 0
    data = []

    for name in group_names:
        values = (name, ) if name else (None, name)
        group_tickets = [t for t in tickets if t[by] in values]
        if not group_tickets:
            continue

        gstat = get_ticket_stats(stats_provider, group_tickets)
        if gstat.count > max_count:
            max_count = gstat.count

        gs_dict = {'name': name}
        gs_dict.update(per_group_stats_data(gstat, name))
        data.append(gs_dict)

    for gs_dict in data:
        percent = 1.0
        if max_count:
            gstat = gs_dict['stats']
            percent = float(gstat.count) / float(max_count) * 100
        gs_dict['percent_of_max_total'] = percent
    return data
예제 #29
0
    def _process_request(self, req):
        '''Intercept and execute GridFlow AJAX callbacks.'''
        if not req.perm.has_permission('TICKET_ADMIN') and \
           not req.perm.has_permission('TICKET_GRID_WORKFLOW'):
            raise Exception('Permission denied')

        id = req.args.get('id')
        action = req.args.get('action')
        ticket = Ticket(self.env, id)
        ts = TicketSystem(self.env)

        validActions = ts.get_available_actions(req, ticket)
        if action not in validActions:
            req.send_response(500)
            req.send_header('Content-Type', 'text/plain')
            req.end_headers()
            req.write('"%s" is not a valid action for #%d\n' % (action, id))
            return

        changes, problems = self.get_ticket_changes(req, ticket, action)
        if problems:
            req.send_response(500)
            req.send_header('Content-Type', 'text/plain')
            req.end_headers()
            req.write('Problems: %s\n' % problems)
            return

        self._apply_ticket_changes(ticket, changes)
        valid, reasons = self._validate_ticket(req, ticket)
        if not valid:
            req.send_response(500)
            req.send_header('Content-Type', 'text/plain')
            req.end_headers()
            req.write('Changes not valid:\n')
            for reason in reasons:
                req.write('* ' + reason + '\n')
            return

        self._save(req, ticket, action)
        req.send_response(200)
        req.send_header('Content-Type', 'text/plain')
        req.end_headers()
        req.write('OK')
        return
예제 #30
0
    def _get_new_ticket_values(self, req, env):
        """Pull all of the new values out of the post data."""
        values = {}

        # Get the current users name.
        if req.authname and req.authname != 'anonymous':
            user = req.authname
        else:
            user = req.session.get('email') or req.session.get('name') or None

        for field in TicketSystem(env).get_ticket_fields():
            name = field['name']
            if name not in ('summary', 'reporter', 'description'):
                value = req.args.get('batchmod_value_' + name)
                if name == 'owner' and value == '$USER':
                    value = user
                if value is not None:
                    values[name] = value
        return values