示例#1
0
    def print_edit_view(self, req, error=None, filter=None, warning=None):

        userMail = self.get_user_email(req.authname)
        fields = TicketSystem(self.env).get_ticket_fields()
        customFields = TicketSystem(self.env).get_custom_fields()

        for  f in fields[:]:
            for cf in customFields:
                if f['name'] == cf['name']:
                    fields.remove(f)

        disableSubmitButton = ""
        if not userMail:
            userMail = ["No email specified"]
            disableSubmitButton = "disabled"

        data = {'userEmail' : userMail, 
                'submitDisabled': disableSubmitButton,
                'datetime_hint': get_datetime_format_hint(),
                'fields': fields,
                'filter': filter,
                'error': error,
                'warning': warning}
        
        add_stylesheet(req, 'hw/css/style.css')
        add_script(req, 'hw/js/xmail.js')
        
        return 'xmail-edit.html', data, None
    def ticket_created(self, ticket):
        ticketsystem = TicketSystem(self.env)
        resource_name = get_resource_shortname(self.env, ticket.resource)
        resource_desc = ticketsystem.get_resource_description(ticket.resource,
                                                              format='summary')
        # make sure we will index customerrequest name not id
        cr_id = ticket['customerrequest']
        if cr_id:
            db = self.env.get_read_db()
            cursor = db.cursor()
            cursor.execute("SELECT name FROM public.customer_requests "
                           " WHERE id='%s'" % cr_id)
            row = cursor.fetchone()
            if row:
                ticket.values['customerrequest'] = row[0]

        so = FullTextSearchObject(
                self.project, ticket.resource,
                title = u"%(title)s: %(message)s" % {'title': resource_name,
                                                     'message': resource_desc},
                author = ticket.values.get('reporter'),
                changed = ticket.values.get('changetime'),
                created = ticket.values.get('time'),
                tags = ticket.values.get('keywords'),
                involved = re.split(r'[;,\s]+', ticket.values.get('cc', ''))
                           or ticket.values.get('reporter'),
                popularity = 0, #FIXME
                oneline = shorten_result(ticket.values.get('description', '')),
                body = u'%r' % (ticket.values,),
                status = ticket.values.get('status'),
                comments = [t[4] for t in ticket.get_changelog()],
                )
        self.backend.create(so, quiet=True)
        self._update_ticket(ticket)
        self.log.debug("Ticket added for indexing: %s", ticket)
示例#3
0
    def print_edit_view(self, req, error=None, filter=None, warning=None):

        userMail = self.get_user_email(req.authname)
        fields = TicketSystem(self.env).get_ticket_fields()
        customFields = TicketSystem(self.env).get_custom_fields()

        for f in fields[:]:
            for cf in customFields:
                if f["name"] == cf["name"]:
                    fields.remove(f)

        disableSubmitButton = ""
        if not userMail:
            userMail = ["No email specified"]
            disableSubmitButton = "disabled"

        data = {
            "userEmail": userMail,
            "submitDisabled": disableSubmitButton,
            "datetime_hint": get_datetime_format_hint(),
            "fields": fields,
            "filter": filter,
            "error": error,
            "warning": warning,
        }

        add_stylesheet(req, "hw/css/style.css")
        add_script(req, "hw/js/xmail.js")

        return "xmail-edit.html", data, None
示例#4
0
 def __init__(self):
     self.log.debug('Starting Sqa Testing System API')
     ts = TicketSystem(self.env)
     '''
     When the object is created collect 
     a set of tickets configured in this
     running instance of track
     '''
     self.ticket_fields = ts.get_ticket_fields()
示例#5
0
 def _check_field_existance(self):
     
     ticket_system = TicketSystem(self.env)
     custom_fields = ticket_system.get_custom_fields()
     
     for custom_field in custom_fields:
         if custom_field['type'] == 'text':
             if custom_field['name'] == self.tfield:
                 return True
     
     return False
示例#6
0
 def describe_tagged_resource(self, req, resource):
     if not self.check_permission(req.perm, 'view'):
         return ''
     ticket = Ticket(self.env, resource.id)
     if ticket.exists:
         # Use the corresponding IResourceManager.
         ticket_system = TicketSystem(self.env)
         return ticket_system.get_resource_description(ticket.resource,
                                                       format='summary')
     else:
         return ''
示例#7
0
 def test_available_actions_no_perms(self):
     ts = TicketSystem(self.env)
     perm = Mock(has_permission=lambda x: 0)
     self.assertEqual(['leave'],
                      ts.get_available_actions({'status': 'new'}, perm))
     self.assertEqual(['leave'],
                      ts.get_available_actions({'status': 'assigned'}, perm))
     self.assertEqual(['leave'],
                      ts.get_available_actions({'status': 'reopened'}, perm))
     self.assertEqual(['leave'],
                      ts.get_available_actions({'status': 'closed'}, perm))
示例#8
0
 def test_available_actions_create_only(self):
     ts = TicketSystem(self.env)
     perm = Mock(has_permission=lambda x: x == 'TICKET_CREATE')
     self.assertEqual(['leave'],
                      ts.get_available_actions({'status': 'new'}, perm))
     self.assertEqual(['leave'],
                      ts.get_available_actions({'status': 'assigned'}, perm))
     self.assertEqual(['leave'],
                      ts.get_available_actions({'status': 'reopened'}, perm))
     self.assertEqual(['leave', 'reopen'],
                      ts.get_available_actions({'status': 'closed'}, perm))
示例#9
0
class ResetActionTestCase(unittest.TestCase):

    def setUp(self):
        self.env = EnvironmentStub(default_data=True)
        self.perm_sys = PermissionSystem(self.env)
        self.ctlr = TicketSystem(self.env).action_controllers[0]
        self.req1 = Mock(authname='user1', args={},
                         perm=PermissionCache(self.env, 'user1'))
        self.req2 = Mock(authname='user2', args={},
                         perm=PermissionCache(self.env, 'user2'))
        self.ticket = Ticket(self.env)
        self.ticket['status'] = 'invalid'
        self.ticket.insert()

    def tearDown(self):
        self.env.reset_db()

    def _reload_workflow(self):
        self.ctlr.actions = self.ctlr.get_all_actions()

    def test_default_reset_action(self):
        """Default reset action."""
        self.perm_sys.grant_permission('user2', 'TICKET_ADMIN')
        self._reload_workflow()

        actions1 = self.ctlr.get_ticket_actions(self.req1, self.ticket)
        actions2 = self.ctlr.get_ticket_actions(self.req2, self.ticket)
        chgs2 = self.ctlr.get_ticket_changes(self.req2, self.ticket, '_reset')

        self.assertEqual(1, len(actions1))
        self.assertNotIn((0, '_reset'), actions1)
        self.assertEqual(2, len(actions2))
        self.assertIn((0, '_reset'), actions2)
        self.assertEqual('new', chgs2['status'])

    def test_custom_reset_action(self):
        """Custom reset action in [ticket-workflow] section."""
        config = self.env.config['ticket-workflow']
        config.set('_reset', '-> review')
        config.set('_reset.operations', 'reset_workflow')
        config.set('_reset.permissions', 'TICKET_BATCH_MODIFY')
        config.set('_reset.default', 2)
        self.perm_sys.grant_permission('user2', 'TICKET_BATCH_MODIFY')
        self._reload_workflow()

        actions1 = self.ctlr.get_ticket_actions(self.req1, self.ticket)
        actions2 = self.ctlr.get_ticket_actions(self.req2, self.ticket)
        chgs2 = self.ctlr.get_ticket_changes(self.req2, self.ticket, '_reset')

        self.assertEqual(1, len(actions1))
        self.assertNotIn((2, '_reset'), actions1)
        self.assertEqual(2, len(actions2))
        self.assertIn((2, '_reset'), actions2)
        self.assertEqual('review', chgs2['status'])
示例#10
0
def _translation_deactivated(ticket=None):
    t = deactivate()
    if ticket is not None:
        ts = TicketSystem(ticket.env)
        translated_fields = ticket.fields
        ticket.fields = ts.get_ticket_fields()
    try:
        yield
    finally:
        if ticket is not None:
            ticket.fields = translated_fields
        reactivate(t)
示例#11
0
 def _complete_transition(self, args):
     if len(args) < 3:
         states = TicketSystem(self.env).get_all_status()
         if len(args) == 2 and args[0] in states:
             states.remove(args[0])
         return states
     if len(args) == 3:
         return self.common_days
     if len(args) == 4:
         return self._get_user_list()
     if len(args) == 5:
         return self._get_explanations(args[3])
示例#12
0
 def test_available_actions_chgprop_only(self):
     # CHGPROP is not enough for changing a ticket's state (#3289)
     ts = TicketSystem(self.env)
     perm = Mock(has_permission=lambda x: x == 'TICKET_CHGPROP')
     self.assertEqual(['leave'],
                      ts.get_available_actions({'status': 'new'}, perm))
     self.assertEqual(['leave'],
                      ts.get_available_actions({'status': 'assigned'}, perm))
     self.assertEqual(['leave'],
                      ts.get_available_actions({'status': 'reopened'}, perm))
     self.assertEqual(['leave'],
                      ts.get_available_actions({'status': 'closed'}, perm))
示例#13
0
    def get_search_results(self, req, terms, filters):
        """Overriding search results for Tickets"""
        if not "ticket" in filters:
            return
        ticket_realm = Resource("ticket")
        with self.env.db_query as db:
            sql, args = search_to_sql(
                db, ["summary", "keywords", "description", "reporter", "cc", db.cast("id", "text")], terms
            )
            sql2, args2 = search_to_sql(db, ["newvalue"], terms)
            sql3, args3 = search_to_sql(db, ["value"], terms)
            ticketsystem = TicketSystem(self.env)
            if req.args.get("product"):
                productsql = "product='%s' AND" % req.args.get("product")
            else:
                productsql = ""

            for summary, desc, author, type, tid, ts, status, resolution in db(
                """SELECT summary, description, reporter, type, id,
                                 time, status, resolution 
                          FROM ticket
                          WHERE (%s id IN (
                              SELECT id FROM ticket WHERE %s
                            UNION
                              SELECT ticket FROM ticket_change
                              WHERE field='comment' AND %s
                            UNION
                              SELECT ticket FROM ticket_custom WHERE %s
                          ))
                          """
                % (productsql, sql, sql2, sql3),
                args + args2 + args3,
            ):
                t = ticket_realm(id=tid)
                if "TICKET_VIEW" in req.perm(t):
                    yield (
                        req.href.ticket(tid),
                        tag_(
                            "%(title)s: %(message)s",
                            title=tag.span(get_resource_shortname(self.env, t), class_=status),
                            message=ticketsystem.format_summary(summary, status, resolution, type),
                        ),
                        from_utimestamp(ts),
                        author,
                        shorten_result(desc, terms),
                    )

        # Attachments
        for result in AttachmentModule(self.env).get_search_results(req, ticket_realm, terms):
            yield result
示例#14
0
    def _get_ticket_fields(self, data):
        """ Return a list of the ticket fields corresponding to the output columns

        The data returned is used for ticket field input
        """
        ts = TicketSystem(self.env)
        fields = ts.get_ticket_fields()
        results = []
        for header in data['headers'][1:]:
            for field in fields:
                if field['name'] == header['name']:
                    results.append(field)
                continue
            continue
        return results
示例#15
0
    def setUp(self):
        tmpdir = os.path.realpath(tempfile.gettempdir())
        self.env = EnvironmentStub(enable=['trac.*', AuthzPolicy], path=tmpdir)
        self.env.config.set('trac', 'permission_policies',
                            'AuthzPolicy, DefaultPermissionPolicy')
        self.env.config.set('ticket', 'restrict_owner', True)

        self.perm_sys = PermissionSystem(self.env)
        self.env.insert_known_users([
            ('user1', '', ''), ('user2', '', ''),
            ('user3', '', ''), ('user4', '', '')
        ])
        self.perm_sys.grant_permission('user1', 'TICKET_MODIFY')
        self.perm_sys.grant_permission('user2', 'TICKET_VIEW')
        self.perm_sys.grant_permission('user3', 'TICKET_MODIFY')
        self.perm_sys.grant_permission('user4', 'TICKET_MODIFY')
        self.authz_file = os.path.join(tmpdir, 'trac-authz-policy')
        create_file(self.authz_file)
        self.env.config.set('authz_policy', 'authz_file', self.authz_file)
        self.ctlr = TicketSystem(self.env).action_controllers[0]
        self.req1 = Mock(authname='user1', args={},
                         perm=PermissionCache(self.env, 'user1'))
        self.ticket = Ticket(self.env)
        self.ticket['status'] = 'new'
        self.ticket.insert()
示例#16
0
 def setUp(self):
     self.env = EnvironmentStub(default_data=True)
     self.perm_sys = PermissionSystem(self.env)
     self.ctlr = TicketSystem(self.env).action_controllers[0]
     self.ticket = Ticket(self.env)
     self.ticket['status'] = 'new'
     self.ticket.insert()
     with self.env.db_transaction as db:
         for user in ('user1', 'user2', 'user3', 'user4'):
             db("INSERT INTO session VALUES (%s, %s, %s)", (user, 1, 0))
     permissions = [
         ('user1', 'TICKET_EDIT_CC'),
         ('user2', 'TICKET_EDIT_CC'),
         ('user2', 'TICKET_BATCH_MODIFY'),
         ('user3', 'TICKET_ADMIN'),
         ('user4', 'TICKET_VIEW'),
         ('user1', 'group1'),
         ('user2', 'group1'),
         ('user2', 'group2'),
         ('user3', 'group2'),
         ('user4', 'group3')
     ]
     for perm in permissions:
         self.perm_sys.grant_permission(*perm)
     self.req = Mock(authname='user1', args={},
                     perm=PermissionCache(self.env, 'user0'))
     self.expected = """\
示例#17
0
 def setUp(self):
     self.env = EnvironmentStub(default_data=True)
     config = self.env.config
     config.set('ticket-workflow', 'change_owner', 'new -> new')
     config.set('ticket-workflow', 'change_owner.operations', 'set_owner')
     self.ctlr = TicketSystem(self.env).action_controllers[0]
     self.ticket_module = TicketModule(self.env)
        def _implementation(db):
            tkt = Ticket(self.env, ticket_id)
            ts = TicketSystem(self.env)
            tm = TicketModule(self.env)
            if action not in ts.get_available_actions(req, tkt):
                raise ValueError(["This ticket cannot be moved to this status,\
                      perhaps the ticket has been updated by someone else."])

            field_changes, problems = \
                tm.get_ticket_changes(req, tkt, action)

            if problems:
                raise ValueError(problems)

            tm._apply_ticket_changes(tkt, field_changes)
            valid = tm._validate_ticket(req, tkt, force_collision_check=True)
            if not valid:
                raise ValueError(req.chrome['warnings'])
            else:
                tkt.save_changes(req.authname, "", when=datetime.now(utc))
 def _index_ticket(self, ticket):
     ticketsystem = TicketSystem(self.env)
     resource_name = get_resource_shortname(self.env, ticket.resource)
     resource_desc = ticketsystem.get_resource_description(ticket.resource, format="summary")
     so = FullTextSearchObject(
         self.project,
         ticket.resource,
         title=u"%(title)s: %(message)s" % {"title": resource_name, "message": resource_desc},
         author=ticket.values.get("reporter"),
         changed=ticket.values.get("changetime"),
         created=ticket.values.get("time"),
         tags=ticket.values.get("keywords"),
         involved=re.split(r"[;,\s]+", ticket.values.get("cc", "")) or ticket.values.get("reporter"),
         popularity=0,  # FIXME
         oneline=shorten_result(ticket.values.get("description", "")),
         body=u"%r" % (ticket.values,),
         comments=[t[4] for t in ticket.get_changelog()],
     )
     self.backend.create(so, quiet=True)
     self.log.debug("Ticket added for indexing: %s", ticket)
示例#20
0
 def setUp(self):
     self.env = EnvironmentStub(default_data=True)
     self.perm_sys = PermissionSystem(self.env)
     self.ctlr = TicketSystem(self.env).action_controllers[0]
     self.req1 = Mock(authname='user1', args={},
                      perm=PermissionCache(self.env, 'user1'))
     self.req2 = Mock(authname='user2', args={},
                      perm=PermissionCache(self.env, 'user2'))
     self.ticket = Ticket(self.env)
     self.ticket['status'] = 'invalid'
     self.ticket.insert()
示例#21
0
 def getActions(self, req, id):
     """Returns the actions that can be performed on the ticket as a list of
     `[action, label, hints, [input_fields]]` elements, where `input_fields` is
     a list of `[name, value, [options]]` for any required action inputs."""
     ts = TicketSystem(self.env)
     t = model.Ticket(self.env, id)
     actions = []
     for action in ts.get_available_actions(req, t):
         fragment = genshi.builder.Fragment()
         hints = []
         first_label = None
         for controller in ts.action_controllers:
             if action in [c_action for c_weight, c_action \
                             in controller.get_ticket_actions(req, t)]:
                 label, widget, hint = \
                     controller.render_ticket_action_control(req, t, action)
                 fragment += widget
                 hints.append(to_unicode(hint).rstrip('.') + '.')
                 first_label = first_label == None and label or first_label
         controls = []
         for elem in fragment.children:
             if not isinstance(elem, genshi.builder.Element):
                 continue
             if elem.tag == 'input':
                 controls.append((elem.attrib.get('name'),
                                 elem.attrib.get('value'), []))
             elif elem.tag == 'select':
                 value = ''
                 options = []
                 for opt in elem.children:
                     if not (opt.tag == 'option' and opt.children):
                         continue
                     option = opt.children[0]
                     options.append(option)
                     if opt.attrib.get('selected'):
                         value = option
                 controls.append((elem.attrib.get('name'),
                                 value, options))
         actions.append((action, first_label, " ".join(hints), controls))
     return actions
示例#22
0
 def ticket_created(self, ticket):
     ticketsystem = TicketSystem(self.env)
     resource_name = get_resource_shortname(self.env, ticket.resource)
     resource_desc = ticketsystem.get_resource_description(ticket.resource,
                                                           format='summary')
     so = FullTextSearchObject(
             self.project, ticket.resource,
             title = u"%(title)s: %(message)s" % {'title': resource_name,
                                                  'message': resource_desc},
             author = ticket.values.get('reporter'),
             changed = ticket.values.get('changetime'),
             created = ticket.values.get('time'),
             tags = ticket.values.get('keywords'),
             involved = re.split(r'[;,\s]+', ticket.values.get('cc', ''))
                        or ticket.values.get('reporter'),
             popularity = 0, #FIXME
             oneline = shorten_result(ticket.values.get('description', '')),
             body = u'%r' % (ticket.values,),
             comments = [t[4] for t in ticket.get_changelog()],
             )
     self.backend.create(so, quiet=True)
     self._update_ticket(ticket)
     self.log.debug("Ticket added for indexing: %s", ticket)
示例#23
0
文件: model.py 项目: pkdevbox/trac
 def __init__(self, env, tkt_id=None, version=None):
     self.env = env
     self.fields = TicketSystem(self.env).get_ticket_fields()
     self.editable_fields = \
         set(f['name'] for f in self.fields
                       if f['name'] not in self.protected_fields)
     self.std_fields, self.custom_fields, self.time_fields = [], [], []
     for f in self.fields:
         if f.get('custom'):
             self.custom_fields.append(f['name'])
         else:
             self.std_fields.append(f['name'])
         if f['type'] == 'time':
             self.time_fields.append(f['name'])
     self.values = {}
     if tkt_id is not None:
         tkt_id = int(tkt_id)
         self._fetch_ticket(tkt_id)
     else:
         self._init_defaults()
         self.id = None
     self.version = version
     self._old = {}
示例#24
0
 def getAvailableActions(self, req, id):
     """Returns the actions that can be performed on the ticket."""
     ticketSystem = TicketSystem(self.env)
     t = model.Ticket(self.env, id)
     return ticketSystem.get_available_actions(t, req.perm)
示例#25
0
class ConfigurableTicketWorkflowTestCase(unittest.TestCase):

    def setUp(self):
        self.env = EnvironmentStub(default_data=True)
        config = self.env.config
        config.set('ticket-workflow', 'change_owner', 'new -> new')
        config.set('ticket-workflow', 'change_owner.operations', 'set_owner')
        self.ctlr = TicketSystem(self.env).action_controllers[0]
        self.ticket_module = TicketModule(self.env)

    def tearDown(self):
        self.env.reset_db()

    def _add_component(self, name='test', owner='owner1'):
        component = Component(self.env)
        component.name = name
        component.owner = owner
        component.insert()

    def _reload_workflow(self):
        self.ctlr.actions = self.ctlr.get_all_actions()

    def test_get_all_actions_custom_attribute(self):
        """Custom attribute in ticket-workflow."""
        config = self.env.config['ticket-workflow']
        config.set('resolve.set_milestone', 'reject')
        all_actions = self.ctlr.get_all_actions()

        resolve_action = None
        for name, attrs in all_actions.items():
            if name == 'resolve':
                resolve_action = attrs

        self.assertIsNotNone(resolve_action)
        self.assertIn('set_milestone', resolve_action.keys())
        self.assertEqual('reject', resolve_action['set_milestone'])

    def test_owner_from_component(self):
        """Verify that the owner of a new ticket is set to the owner
        of the component.
        """
        self._add_component('component3', 'cowner3')

        req = MockRequest(self.env, method='POST', args={
            'field_reporter': 'reporter1',
            'field_summary': 'the summary',
            'field_component': 'component3',
        })
        self.assertRaises(RequestDone, self.ticket_module.process_request, req)
        ticket = Ticket(self.env, 1)

        self.assertEqual('component3', ticket['component'])
        self.assertEqual('cowner3', ticket['owner'])

    def test_component_change(self):
        """New ticket owner is updated when the component is changed.
        """
        self._add_component('component3', 'cowner3')
        self._add_component('component4', 'cowner4')

        ticket = Ticket(self.env)
        ticket.populate({
            'reporter': 'reporter1',
            'summary': 'the summary',
            'component': 'component3',
            'owner': 'cowner3',
            'status': 'new',
        })
        tkt_id = ticket.insert()

        req = MockRequest(self.env, method='POST', args={
            'id': tkt_id,
            'field_component': 'component4',
            'submit': True,
            'action': 'leave',
            'view_time': str(to_utimestamp(ticket['changetime'])),
        })
        self.assertRaises(RequestDone, self.ticket_module.process_request, req)
        ticket = Ticket(self.env, tkt_id)

        self.assertEqual('component4', ticket['component'])
        self.assertEqual('cowner4', ticket['owner'])

    def test_component_change_and_owner_change(self):
        """New ticket owner is not updated if owner is explicitly
        changed.
        """
        self._add_component('component3', 'cowner3')
        self._add_component('component4', 'cowner4')

        ticket = Ticket(self.env)
        ticket.populate({
            'reporter': 'reporter1',
            'summary': 'the summary',
            'component': 'component3',
            'status': 'new',
        })
        tkt_id = ticket.insert()

        req = MockRequest(self.env, method='POST', args={
            'id': tkt_id,
            'field_component': 'component4',
            'submit': True,
            'action': 'change_owner',
            'action_change_owner_reassign_owner': 'owner1',
            'view_time': str(to_utimestamp(ticket['changetime'])),
        })
        self.assertRaises(RequestDone, self.ticket_module.process_request, req)
        ticket = Ticket(self.env, tkt_id)

        self.assertEqual('component4', ticket['component'])
        self.assertEqual('owner1', ticket['owner'])

    def test_old_owner_not_old_component_owner(self):
        """New ticket owner is not updated if old owner is not the owner
        of the old component.
        """
        self._add_component('component3', 'cowner3')
        self._add_component('component4', 'cowner4')

        ticket = Ticket(self.env)
        ticket.populate({
            'reporter': 'reporter1',
            'summary': 'the summary',
            'component': 'component3',
            'owner': 'owner1',
            'status': 'new',
        })
        tkt_id = ticket.insert()

        req = MockRequest(self.env, method='POST', args={
            'id': tkt_id,
            'field_component': 'component4',
            'submit': True,
            'action': 'leave',
            'view_time': str(to_utimestamp(ticket['changetime'])),
        })
        self.assertRaises(RequestDone, self.ticket_module.process_request, req)
        ticket = Ticket(self.env, tkt_id)

        self.assertEqual('component4', ticket['component'])
        self.assertEqual('owner1', ticket['owner'])

    def test_new_component_has_no_owner(self):
        """Ticket is not disowned when the component is changed to a
        component with no owner.
        """
        self._add_component('component3', 'cowner3')
        self._add_component('component4', '')

        ticket = Ticket(self.env)
        ticket.populate({
            'reporter': 'reporter1',
            'summary': 'the summary',
            'component': 'component3',
            'owner': 'cowner3',
            'status': 'new',
        })
        tkt_id = ticket.insert()

        req = MockRequest(self.env, method='POST', args={
            'id': tkt_id,
            'field_component': 'component4',
            'submit': True,
            'action': 'leave',
            'view_time': str(to_utimestamp(ticket['changetime'])),
        })
        self.assertRaises(RequestDone, self.ticket_module.process_request, req)
        ticket = Ticket(self.env, tkt_id)

        self.assertEqual('component4', ticket['component'])
        self.assertEqual('cowner3', ticket['owner'])

    def test_transition_to_star(self):
        """Workflow hint is not be added in a workflow transition to *,
        for example: <none> -> *

        AdvancedTicketWorkflow uses the behavior for the triage operation
        (see #12823)
        """
        config = self.env.config
        config.set('ticket-workflow', 'create_and_triage', '<none> -> *')
        config.set('ticket-workflow', 'create_and_triage.operations', 'triage')
        self._reload_workflow()
        ticket = Ticket(self.env)
        req = MockRequest(self.env, path_info='/newticket', method='POST')

        label, control, hints = \
            self.ctlr.render_ticket_action_control(req, ticket,
                                                   'create_and_triage')

        self.assertEqual('create and triage', label)
        self.assertEqual('', unicode(control))
        self.assertEqual('', unicode(hints))
示例#26
0
 def _get_actions(self, ticket_dict):
     ts = TicketSystem(self.env)
     ticket = Ticket(self.env)
     ticket.populate(ticket_dict)
     id = ticket.insert()
     return ts.get_available_actions(self.req, Ticket(self.env, id))
示例#27
0
 def _get_actions(self, ticket_dict):
     ts = TicketSystem(self.env)
     ticket = Ticket(self.env)
     ticket.populate(ticket_dict)
     id = ticket.insert()
     return ts.get_available_actions(self.req, Ticket(self.env, id))
示例#28
0
    def expand_macro(self, formatter, name, content, realms=[]):
        """Evaluate macro call and render results.

        Calls from web-UI come with pre-processed realm selection.
        """
        env = self.env
        req = formatter.req
        tag_system = TagSystem(env)

        all_realms = tag_system.get_taggable_realms()
        if not all_realms:
            # Tag providers are required, no result without at least one.
            return ''
        args, kw = parse_args(content)

        query = args and args[0].strip() or None
        if not realms:
            # Check macro arguments for realms (typical wiki macro call).
            realms = 'realm' in kw and kw['realm'].split('|') or []
        if query:
            # Add realms from query expression.
            realms.extend(query_realms(query, all_realms))
            # Remove redundant realm selection for performance.
            if set(realms) == all_realms:
                query = re.sub('(^|\W)realm:\S+(\W|$)', ' ', query).strip()
        if name == 'TagCloud':
            # Set implicit 'all tagged realms' as default.
            if not realms:
                realms = all_realms
            if query:
                all_tags = Counter()
                # Require per resource query including view permission checks.
                for resource, tags in tag_system.query(req, query):
                    all_tags.update(tags)
            else:
                # Allow faster per tag query, side steps permission checks.
                all_tags = tag_system.get_all_tags(req, realms=realms)
            mincount = 'mincount' in kw and kw['mincount'] or None
            return self.render_cloud(req, all_tags,
                                     caseless_sort=self.caseless_sort,
                                     mincount=mincount, realms=realms)
        elif name == 'ListTagged':
            if content and _OBSOLETE_ARGS_RE.search(content):
                data = {'warning': 'obsolete_args'}
            else:
                data = {'warning': None}
            context = formatter.context
            # Use TagsQuery arguments (most likely wiki macro calls).
            cols = 'cols' in kw and kw['cols'] or self.default_cols
            format = 'format' in kw and kw['format'] or self.default_format
            if not realms:
                # Apply ListTagged defaults to macro call w/o realm.
                realms = list(set(all_realms)-set(self.exclude_realms))
                if not realms:
                    return ''
            query = '(%s) (%s)' % (query or '', ' or '.join(['realm:%s' % (r)
                                                             for r in realms]))
            query_result = tag_system.query(req, query)
            excludes = [exc.strip()
                        for exc in kw.get('exclude', '' ).split(':')
                        if exc.strip()]
            if excludes and query_result:
                filtered_result = [(resource, tags)
                                   for resource, tags in query_result
                                   if not any(fnmatchcase(resource.id, exc)
                                              for exc in excludes)]
                query_result = filtered_result
            if not query_result:
                return ''

            def _link(resource):
                if resource.realm == 'tag':
                    # Keep realm selection in tag links.
                    return builder.a(resource.id,
                                     href=self.get_href(req, realms,
                                                        tag=resource))
                elif resource.realm == 'ticket':
                    # Return resource link including ticket status dependend
                    #   class to allow for common Trac ticket link style.
                    ticket = Ticket(env, resource.id)
                    return builder.a('#%s' % ticket.id,
                                     class_=ticket['status'],
                                     href=formatter.href.ticket(ticket.id),
                                     title=shorten_line(ticket['summary']))
                return render_resource_link(env, context, resource, 'compact')

            if format == 'table':
                cols = [col for col in cols.split('|')
                        if col in self.supported_cols]
                # Use available translations from Trac core.
                try:
                    labels = TicketSystem(env).get_ticket_field_labels()
                    labels['id'] = _('Id')
                except AttributeError:
                    # Trac 0.11 neither has the attribute nor uses i18n.
                    labels = {'id': 'Id', 'description': 'Description'}
                labels['realm'] = _('Realm')
                labels['tags'] = _('Tags')
                headers = [{'label': labels.get(col)}
                           for col in cols]
                data.update({'cols': cols,
                             'headers': headers})

            results = sorted(query_result, key=lambda r: \
                             embedded_numbers(to_unicode(r[0].id)))
            results = self._paginate(req, results, realms)
            rows = []
            for resource, tags in results:
                desc = tag_system.describe_tagged_resource(req, resource)
                tags = sorted(tags)
                wiki_desc = format_to_oneliner(env, context, desc)
                if tags:
                    rendered_tags = [_link(Resource('tag', tag))
                                     for tag in tags]
                    if 'oldlist' == format:
                        resource_link = _link(resource)
                    else:
                        resource_link = builder.a(wiki_desc,
                                                  href=get_resource_url(
                                                  env, resource, context.href))
                        if 'table' == format:
                            cells = []
                            for col in cols:
                                if col == 'id':
                                    cells.append(_link(resource))
                                # Don't duplicate links to resource in both.
                                elif col == 'description' and 'id' in cols:
                                    cells.append(wiki_desc)
                                elif col == 'description':
                                    cells.append(resource_link)
                                elif col == 'realm':
                                    cells.append(resource.realm)
                                elif col == 'tags':
                                    cells.append(
                                        builder([(tag, ' ')
                                                 for tag in rendered_tags]))
                            rows.append({'cells': cells})
                            continue
                rows.append({'desc': wiki_desc,
                             'rendered_tags': None,
                             'resource_link': _link(resource)})
            data.update({'format': format,
                         'paginator': results,
                         'results': rows,
                         'tags_url': req.href('tags')})

            # Work around a bug in trac/templates/layout.html, that causes a
            # TypeError for the wiki macro call, if we use add_link() alone.
            add_stylesheet(req, 'common/css/search.css')

            return Chrome(env).render_template(
                req, 'listtagged_results.html', data, 'text/html', True)
    def expand_macro(self, formatter, name, text, args):
        template_data = {'css_class': 'trac-kanban-board'}
        template_file = 'kanbanboard.html'
        board = None

        template_data['height'] = '300px'
        if args:
            template_data['height'] = args.get('height', '300px')

        project_name = self.env.path.split('/')[-1]
        page_name = formatter.req.path_info.split('/')[-1]
        is_editable = 'WIKI_MODIFY' in formatter.req.perm and 'TICKET_MODIFY' in formatter.req.perm

        js_globals = {
            'KANBAN_BOARD_ID': page_name,
            'TRAC_PROJECT_NAME': project_name,
            'TRAC_USER_NAME': formatter.req.authname,
            'IS_EDITABLE': is_editable
        }

        if not self.ticket_fields:
            self.ticket_fields = TicketSystem(self.env).get_ticket_fields()

        if text is None:
            template_data['error'] = 'Board data is not defined'
            template_data['usage'] = format_to_html(self.env,
                                                    formatter.context,
                                                    self.__doc__)
        else:
            try:
                board = KanbanBoard(page_name, [], self.ticket_fields,
                                    self.env, self.log)
            except InvalidDataError as e:
                template_data['error'] = e.msg
                template_data['usage'] = format_to_html(
                    self.env, formatter.context, self.__doc__)
            except InvalidFieldError as e:
                template_data[
                    'error'] = 'Invalid ticket fields: %s' % ', '.join(
                        e.fields)
                valid_fields = map(lambda x: x['name'], self.ticket_fields)
                template_data[
                    'usage'] = 'Valid field names are: %s.' % ', '.join(
                        valid_fields)

        if board:
            # TICKET_FIELDS is comma-separated list of user defined ticket field names
            js_globals['TICKET_FIELDS'] = board.get_field_string()

        add_stylesheet(formatter.req, 'trackanbanboard/css/kanbanboard.css')
        add_script_data(formatter.req, js_globals)

        if 'error' in template_data:
            template_file = 'kanbanerror.html'
        else:
            add_script(formatter.req,
                       'trackanbanboard/js/libs/jquery-1.8.3.js')
            add_script(
                formatter.req,
                'trackanbanboard/js/libs/jquery-ui-1.9.2.custom.min.js')
            add_script(formatter.req,
                       'trackanbanboard/js/libs/knockout-2.2.0.js')
            add_script(formatter.req,
                       'trackanbanboard/js/libs/knockout.mapping.js')
            add_script(formatter.req,
                       'trackanbanboard/js/libs/knockout-sortable.min.js')
            add_script(formatter.req, 'trackanbanboard/js/kanbanutil.js')
            add_script(formatter.req, 'trackanbanboard/js/kanbanboard.js')
            add_stylesheet(
                formatter.req,
                'trackanbanboard/css/jquery-ui-1.9.2.custom.min.css')

        return Chrome(self.env).render_template(
            formatter.req, template_file, template_data, None,
            fragment=True).render(strip_whitespace=False)
示例#30
0
 def test_custom_field_select_without_options(self):
     self.env.config.set('ticket-custom', 'test', 'select')
     self.env.config.set('ticket-custom', 'test.label', 'Test')
     self.env.config.set('ticket-custom', 'test.value', '1')
     fields = TicketSystem(self.env).get_custom_fields()
     self.assertEqual(0, len(fields))
示例#31
0
 def get_user_list(self):
     return TicketSystem(self.env).get_allowed_owners()
示例#32
0
class ResetActionTestCase(unittest.TestCase):
    def setUp(self):
        self.env = EnvironmentStub(default_data=True)
        self.perm_sys = PermissionSystem(self.env)
        self.ctlr = TicketSystem(self.env).action_controllers[0]
        self.req1 = MockRequest(self.env, authname='user1')
        self.req2 = MockRequest(self.env, authname='user2')
        self.ticket = insert_ticket(self.env, status='invalid')

    def tearDown(self):
        self.env.reset_db()

    def _reload_workflow(self):
        self.ctlr.actions = self.ctlr.get_all_actions()

    def test_default_reset_action(self):
        """Default reset action."""
        self.perm_sys.grant_permission('user2', 'TICKET_ADMIN')
        self._reload_workflow()

        actions1 = self.ctlr.get_ticket_actions(self.req1, self.ticket)
        actions2 = self.ctlr.get_ticket_actions(self.req2, self.ticket)
        chgs2 = self.ctlr.get_ticket_changes(self.req2, self.ticket, '_reset')

        self.assertEqual(1, len(actions1))
        self.assertNotIn((0, '_reset'), actions1)
        self.assertEqual(2, len(actions2))
        self.assertIn((0, '_reset'), actions2)
        self.assertEqual('new', chgs2['status'])

    def test_default_reset_action_without_new_state(self):
        """Default reset action not available when no new state."""
        self.perm_sys.grant_permission('user2', 'TICKET_ADMIN')
        config = self.env.config
        # Replace 'new' state with 'untriaged'
        config.set('ticket-workflow', 'create', '<none> -> untriaged')
        config.set('ticket-workflow', 'accept',
                   'untriaged,assigned,accepted,reopened -> accepted')
        config.set('ticket-workflow', 'resolve',
                   'untriaged,assigned,accepted,reopened -> closed')
        config.set('ticket-workflow', 'reassign',
                   'untriaged,assigned,accepted,reopened -> assigned')
        self._reload_workflow()

        actions = self.ctlr.get_ticket_actions(self.req2, self.ticket)

        self.assertEqual(1, len(actions))
        self.assertNotIn((0, '_reset'), actions)

    def test_custom_reset_action(self):
        """Custom reset action in [ticket-workflow] section."""
        config = self.env.config['ticket-workflow']
        config.set('_reset', '-> review')
        config.set('_reset.operations', 'reset_workflow')
        config.set('_reset.permissions', 'TICKET_BATCH_MODIFY')
        config.set('_reset.default', 2)
        self.perm_sys.grant_permission('user2', 'TICKET_BATCH_MODIFY')
        self._reload_workflow()

        actions1 = self.ctlr.get_ticket_actions(self.req1, self.ticket)
        actions2 = self.ctlr.get_ticket_actions(self.req2, self.ticket)
        chgs2 = self.ctlr.get_ticket_changes(self.req2, self.ticket, '_reset')

        self.assertEqual(1, len(actions1))
        self.assertNotIn((2, '_reset'), actions1)
        self.assertEqual(2, len(actions2))
        self.assertIn((2, '_reset'), actions2)
        self.assertEqual('review', chgs2['status'])
示例#33
0
class ConfigurableTicketWorkflowTestCase(unittest.TestCase):
    def setUp(self):
        self.env = EnvironmentStub()
        config = self.env.config
        config.set('ticket-workflow', 'change_owner', 'new -> new')
        config.set('ticket-workflow', 'change_owner.operations', 'set_owner')
        self.ctlr = TicketSystem(self.env).action_controllers[0]
        self.ticket_module = TicketModule(self.env)

    def tearDown(self):
        self.env.reset_db()

    def _add_component(self, name='test', owner='owner1'):
        component = Component(self.env)
        component.name = name
        component.owner = owner
        component.insert()

    def _reload_workflow(self):
        self.ctlr.actions = self.ctlr.get_all_actions()

    def test_get_all_actions_custom_attribute(self):
        """Custom attribute in ticket-workflow."""
        config = self.env.config['ticket-workflow']
        config.set('resolve.set_milestone', 'reject')
        all_actions = self.ctlr.get_all_actions()

        resolve_action = None
        for name, attrs in all_actions.items():
            if name == 'resolve':
                resolve_action = attrs

        self.assertIsNotNone(resolve_action)
        self.assertIn('set_milestone', list(resolve_action))
        self.assertEqual('reject', resolve_action['set_milestone'])

    def test_owner_from_component(self):
        """Verify that the owner of a new ticket is set to the owner
        of the component.
        """
        self._add_component('component3', 'cowner3')

        req = MockRequest(self.env,
                          method='POST',
                          args={
                              'field_reporter': 'reporter1',
                              'field_summary': 'the summary',
                              'field_component': 'component3',
                          })
        self.assertRaises(RequestDone, self.ticket_module.process_request, req)
        ticket = Ticket(self.env, 1)

        self.assertEqual('component3', ticket['component'])
        self.assertEqual('cowner3', ticket['owner'])

    def test_component_change(self):
        """New ticket owner is updated when the component is changed.
        """
        self._add_component('component3', 'cowner3')
        self._add_component('component4', 'cowner4')

        ticket = insert_ticket(self.env,
                               reporter='reporter1',
                               summary='the summary',
                               component='component3',
                               owner='cowner3',
                               status='new')

        req = MockRequest(self.env,
                          method='POST',
                          args={
                              'id':
                              ticket.id,
                              'field_component':
                              'component4',
                              'submit':
                              True,
                              'action':
                              'leave',
                              'view_time':
                              str(to_utimestamp(ticket['changetime'])),
                          })
        self.assertRaises(RequestDone, self.ticket_module.process_request, req)
        ticket = Ticket(self.env, ticket.id)

        self.assertEqual('component4', ticket['component'])
        self.assertEqual('cowner4', ticket['owner'])

    def test_component_change_and_owner_change(self):
        """New ticket owner is not updated if owner is explicitly
        changed.
        """
        self._add_component('component3', 'cowner3')
        self._add_component('component4', 'cowner4')

        ticket = insert_ticket(self.env,
                               reporter='reporter1',
                               summary='the summary',
                               component='component3',
                               status='new')

        req = MockRequest(self.env,
                          method='POST',
                          args={
                              'id':
                              ticket.id,
                              'field_component':
                              'component4',
                              'submit':
                              True,
                              'action':
                              'change_owner',
                              'action_change_owner_reassign_owner':
                              'owner1',
                              'view_time':
                              str(to_utimestamp(ticket['changetime'])),
                          })
        self.assertRaises(RequestDone, self.ticket_module.process_request, req)
        ticket = Ticket(self.env, ticket.id)

        self.assertEqual('component4', ticket['component'])
        self.assertEqual('owner1', ticket['owner'])

    def test_old_owner_not_old_component_owner(self):
        """New ticket owner is not updated if old owner is not the owner
        of the old component.
        """
        self._add_component('component3', 'cowner3')
        self._add_component('component4', 'cowner4')

        ticket = insert_ticket(self.env,
                               reporter='reporter1',
                               summary='the summary',
                               component='component3',
                               owner='owner1',
                               status='new')

        req = MockRequest(self.env,
                          method='POST',
                          args={
                              'id':
                              ticket.id,
                              'field_component':
                              'component4',
                              'submit':
                              True,
                              'action':
                              'leave',
                              'view_time':
                              str(to_utimestamp(ticket['changetime'])),
                          })
        self.assertRaises(RequestDone, self.ticket_module.process_request, req)
        ticket = Ticket(self.env, ticket.id)

        self.assertEqual('component4', ticket['component'])
        self.assertEqual('owner1', ticket['owner'])

    def test_new_component_has_no_owner(self):
        """Ticket is not disowned when the component is changed to a
        component with no owner.
        """
        self._add_component('component3', 'cowner3')
        self._add_component('component4', '')

        ticket = insert_ticket(self.env,
                               reporter='reporter1',
                               summary='the summary',
                               component='component3',
                               owner='cowner3',
                               status='new')

        req = MockRequest(self.env,
                          method='POST',
                          args={
                              'id':
                              ticket.id,
                              'field_component':
                              'component4',
                              'submit':
                              True,
                              'action':
                              'leave',
                              'view_time':
                              str(to_utimestamp(ticket['changetime'])),
                          })
        self.assertRaises(RequestDone, self.ticket_module.process_request, req)
        ticket = Ticket(self.env, ticket.id)

        self.assertEqual('component4', ticket['component'])
        self.assertEqual('cowner3', ticket['owner'])

    def _test_get_allowed_owners(self):
        ticket = insert_ticket(self.env, summary='Ticket 1')
        self.env.insert_users([('user1', None, None, 1),
                               ('user2', None, None, 1),
                               ('user3', None, None, 1)])
        ps = PermissionSystem(self.env)
        for user in ('user1', 'user3'):
            ps.grant_permission(user, 'TICKET_MODIFY')
        self.env.config.set('ticket', 'restrict_owner', True)
        return ticket

    def test_get_allowed_owners_returns_set_owner_list(self):
        """Users specified in `set_owner` for the action are returned."""
        req = None
        action = {'set_owner': ['user4', 'user5']}
        ticket = self._test_get_allowed_owners()
        self.assertEqual(['user4', 'user5'],
                         self.ctlr.get_allowed_owners(req, ticket, action))

    def test_get_allowed_owners_returns_user_with_ticket_modify(self):
        """Users with TICKET_MODIFY are are returned if `set_owner` is
        not specified for the action.
        """
        req = None
        action = {}
        ticket = self._test_get_allowed_owners()
        self.assertEqual(['user1', 'user3'],
                         self.ctlr.get_allowed_owners(req, ticket, action))

    def test_transition_to_star(self):
        """Workflow hint is not be added in a workflow transition to *,
        for example: <none> -> *

        AdvancedTicketWorkflow uses the behavior for the triage operation
        (see #12823)
        """
        config = self.env.config
        config.set('ticket-workflow', 'create_and_triage', '<none> -> *')
        config.set('ticket-workflow', 'create_and_triage.operations', 'triage')
        self._reload_workflow()
        ticket = Ticket(self.env)
        req = MockRequest(self.env, path_info='/newticket', method='POST')

        label, control, hints = \
            self.ctlr.render_ticket_action_control(req, ticket,
                                                   'create_and_triage')

        self.assertEqual('create and triage', label)
        self.assertEqual('', unicode(control))
        self.assertEqual('', unicode(hints))

    def test_get_actions_by_operation_for_req(self):
        """Request with no permission checking."""
        req = MockRequest(self.env, path_info='/ticket/1')
        ticket = insert_ticket(self.env, status='new')
        actions = self.ctlr.get_actions_by_operation_for_req(
            req, ticket, 'set_owner')
        self.assertEqual([(0, u'change_owner'), (0, u'reassign')], actions)

    def test_get_actions_by_operation_for_req_with_ticket_modify(self):
        """User without TICKET_MODIFY won't have reassign action."""
        req = MockRequest(self.env, authname='user1', path_info='/ticket/1')
        ticket = insert_ticket(self.env, status='new')
        actions = self.ctlr.get_actions_by_operation_for_req(
            req, ticket, 'set_owner')
        self.assertEqual([(0, u'change_owner')], actions)

    def test_get_actions_by_operation_for_req_without_ticket_modify(self):
        """User with TICKET_MODIFY will have reassign action."""
        PermissionSystem(self.env).grant_permission('user1', 'TICKET_MODIFY')
        req = MockRequest(self.env, authname='user1', path_info='/ticket/1')
        ticket = insert_ticket(self.env, status='new')
        actions = self.ctlr.get_actions_by_operation_for_req(
            req, ticket, 'set_owner')
        self.assertEqual([(0, u'change_owner'), (0, u'reassign')], actions)

    def test_ignores_other_operations(self):
        """Ignores operations not defined by ConfigurableTicketWorkflow.
        """
        self.env.config.set('ticket-workflow', 'review', 'assigned -> review')
        self.env.config.set('ticket-workflow', 'review.operations',
                            'CodeReview')
        ctw = ConfigurableTicketWorkflow(self.env)
        ticket = Ticket(self.env)
        ticket.populate({'summary': '#13013', 'status': 'assigned'})
        ticket.insert()
        req = MockRequest(self.env)

        self.assertNotIn((0, 'review'), ctw.get_ticket_actions(req, ticket))
示例#34
0
    def _render_view(self, req, id):
        """Retrieve the report results and pre-process them for rendering."""
        r = Report(self.env, id)
        title, description, sql = r.title, r.description, r.query
        try:
            args = self.get_var_args(req)
            sql = self.get_default_var_args(args, sql)
        except ValueError as e:
            raise TracError(_("Report failed: %(error)s", error=e))

        # If this is a saved custom query, redirect to the query module
        #
        # A saved query is either an URL query (?... or query:?...),
        # or a query language expression (query:...).
        #
        # It may eventually contain newlines, for increased clarity.
        #
        query = ''.join([line.strip() for line in sql.splitlines()])
        if query and (query[0] == '?' or query.startswith('query:?')):
            query = query if query[0] == '?' else query[6:]
            report_id = 'report=%s' % id
            if 'report=' in query:
                if report_id not in query:
                    err = _(
                        'When specified, the report number should be '
                        '"%(num)s".',
                        num=id)
                    req.redirect(req.href.report(id, action='edit', error=err))
            else:
                if query[-1] != '?':
                    query += '&'
                query += report_id
            req.redirect(req.href.query() + quote_query_string(query))
        elif query.startswith('query:'):
            from trac.ticket.query import Query, QuerySyntaxError
            try:
                query = Query.from_string(self.env, query[6:], report=id)
            except QuerySyntaxError as e:
                req.redirect(
                    req.href.report(id, action='edit', error=to_unicode(e)))
            else:
                req.redirect(query.get_href(req.href))

        format = req.args.get('format')
        if format == 'sql':
            self._send_sql(req, id, title, description, sql)

        title = '{%i} %s' % (id, title)

        report_resource = Resource(self.realm, id)
        req.perm(report_resource).require('REPORT_VIEW')
        context = web_context(req, report_resource)

        page = req.args.getint('page', 1)
        default_max = {
            'rss': self.items_per_page_rss,
            'csv': 0,
            'tab': 0
        }.get(format, self.items_per_page)
        max = req.args.get('max')
        limit = as_int(max, default_max, min=0)  # explict max takes precedence
        offset = (page - 1) * limit

        sort_col = req.args.get('sort', '')
        asc = req.args.getbool('asc', True)

        def report_href(**kwargs):
            """Generate links to this report preserving user variables,
            and sorting and paging variables.
            """
            params = args.copy()
            if sort_col:
                params['sort'] = sort_col
            params['page'] = page
            if max:
                params['max'] = max
            params.update(kwargs)
            params['asc'] = as_int(params.get('asc'), asc, min=0, max=1)
            return req.href.report(id, params)

        data = {
            'action': 'view',
            'report': {
                'id': id,
                'resource': report_resource
            },
            'context': context,
            'title': sub_vars(title, args),
            'description': sub_vars(description or '', args),
            'max': limit,
            'args': args,
            'show_args_form': False,
            'message': None,
            'paginator': None,
            'report_href': report_href,
        }

        res = self.execute_paginated_report(req, id, sql, args, limit, offset)

        if len(res) == 2:
            e, sql = res
            data['message'] = \
                tag_("Report execution failed: %(error)s %(sql)s",
                     error=tag.pre(exception_to_unicode(e)),
                     sql=tag(tag.hr(),
                             tag.pre(sql, style="white-space: pre")))
            return 'report_view.html', data, None

        cols, results, num_items, missing_args, limit_offset = res
        need_paginator = limit > 0 and limit_offset
        need_reorder = limit_offset is None
        results = [list(row) for row in results]
        numrows = len(results)

        paginator = None
        if need_paginator:
            paginator = Paginator(results, page - 1, limit, num_items)
            data['paginator'] = paginator
            if paginator.has_next_page:
                add_link(req, 'next', report_href(page=page + 1),
                         _('Next Page'))
            if paginator.has_previous_page:
                add_link(req, 'prev', report_href(page=page - 1),
                         _('Previous Page'))

            pagedata = []
            shown_pages = paginator.get_shown_pages(21)
            for p in shown_pages:
                pagedata.append([
                    report_href(page=p), None,
                    str(p),
                    _('Page %(num)d', num=p)
                ])
            fields = ['href', 'class', 'string', 'title']
            paginator.shown_pages = [dict(zip(fields, p)) for p in pagedata]
            paginator.current_page = {
                'href': None,
                'class': 'current',
                'string': str(paginator.page + 1),
                'title': None
            }
            numrows = paginator.num_items

        # Place retrieved columns in groups, according to naming conventions
        #  * _col_ means fullrow, i.e. a group with one header
        #  * col_ means finish the current group and start a new one

        field_labels = TicketSystem(self.env).get_ticket_field_labels()

        header_groups = [[]]
        for idx, col in enumerate(cols):
            if col in field_labels:
                title = field_labels[col]
            else:
                title = col.strip('_').capitalize()
            header = {
                'col': col,
                'title': title,
                'hidden': False,
                'asc': None,
            }

            if col == sort_col:
                header['asc'] = asc
                if not paginator and need_reorder:
                    # this dict will have enum values for sorting
                    # and will be used in sortkey(), if non-empty:
                    sort_values = {}
                    if sort_col in ('status', 'resolution', 'priority',
                                    'severity'):
                        # must fetch sort values for that columns
                        # instead of comparing them as strings
                        with self.env.db_query as db:
                            for name, value in db(
                                    "SELECT name, %s FROM enum WHERE type=%%s"
                                    % db.cast('value', 'int'), (sort_col, )):
                                sort_values[name] = value

                    def sortkey(row):
                        val = row[idx]
                        # check if we have sort_values, then use them as keys.
                        if sort_values:
                            return sort_values.get(val)
                        # otherwise, continue with string comparison:
                        if isinstance(val, basestring):
                            val = val.lower()
                        return val

                    results = sorted(results, key=sortkey, reverse=(not asc))

            header_group = header_groups[-1]

            if col.startswith('__') and col.endswith('__'):  # __col__
                header['hidden'] = True
            elif col[0] == '_' and col[-1] == '_':  # _col_
                header_group = []
                header_groups.append(header_group)
                header_groups.append([])
            elif col[0] == '_':  # _col
                header['hidden'] = True
            elif col[-1] == '_':  # col_
                header_groups.append([])
            header_group.append(header)

        # Structure the rows and cells:
        #  - group rows according to __group__ value, if defined
        #  - group cells the same way headers are grouped
        chrome = Chrome(self.env)
        row_groups = []
        authorized_results = []
        prev_group_value = None
        for row_idx, result in enumerate(results):
            col_idx = 0
            cell_groups = []
            row = {'cell_groups': cell_groups}
            realm = TicketSystem.realm
            parent_realm = ''
            parent_id = ''
            email_cells = []
            for header_group in header_groups:
                cell_group = []
                for header in header_group:
                    value = cell_value(result[col_idx])
                    cell = {'value': value, 'header': header, 'index': col_idx}
                    col = header['col']
                    col_idx += 1
                    # Detect and create new group
                    if col == '__group__' and value != prev_group_value:
                        prev_group_value = value
                        # Brute force handling of email in group by header
                        row_groups.append(
                            (value and chrome.format_author(req, value), []))
                    # Other row properties
                    row['__idx__'] = row_idx
                    if col in self._html_cols:
                        row[col] = value
                    if col in ('report', 'ticket', 'id', '_id'):
                        row['id'] = value
                    # Special casing based on column name
                    col = col.strip('_')
                    if col in ('reporter', 'cc', 'owner'):
                        email_cells.append(cell)
                    elif col == 'realm':
                        realm = value
                    elif col == 'parent_realm':
                        parent_realm = value
                    elif col == 'parent_id':
                        parent_id = value
                    cell_group.append(cell)
                cell_groups.append(cell_group)
            if parent_realm:
                resource = Resource(realm,
                                    row.get('id'),
                                    parent=Resource(parent_realm, parent_id))
            else:
                resource = Resource(realm, row.get('id'))
            # FIXME: for now, we still need to hardcode the realm in the action
            if resource.realm.upper() + '_VIEW' not in req.perm(resource):
                continue
            authorized_results.append(result)
            if email_cells:
                for cell in email_cells:
                    emails = chrome.format_emails(context.child(resource),
                                                  cell['value'])
                    result[cell['index']] = cell['value'] = emails
            row['resource'] = resource
            if row_groups:
                row_group = row_groups[-1][1]
            else:
                row_group = []
                row_groups = [(None, row_group)]
            row_group.append(row)

        data.update({
            'header_groups': header_groups,
            'row_groups': row_groups,
            'numrows': numrows
        })

        if format == 'rss':
            data['context'] = web_context(req, report_resource, absurls=True)
            return 'report.rss', data, 'application/rss+xml'
        elif format == 'csv':
            filename = 'report_%s.csv' % id if id else 'report.csv'
            self._send_csv(req,
                           cols,
                           authorized_results,
                           mimetype='text/csv',
                           filename=filename)
        elif format == 'tab':
            filename = 'report_%s.tsv' % id if id else 'report.tsv'
            self._send_csv(req,
                           cols,
                           authorized_results,
                           '\t',
                           mimetype='text/tab-separated-values',
                           filename=filename)
        else:
            p = page if max is not None else None
            add_link(req, 'alternate',
                     auth_link(req, report_href(format='rss', page=None)),
                     _('RSS Feed'), 'application/rss+xml', 'rss')
            add_link(req, 'alternate', report_href(format='csv', page=p),
                     _('Comma-delimited Text'), 'text/plain')
            add_link(req, 'alternate', report_href(format='tab', page=p),
                     _('Tab-delimited Text'), 'text/plain')
            if 'REPORT_SQL_VIEW' in req.perm(self.realm, id):
                add_link(req, 'alternate', req.href.report(id=id,
                                                           format='sql'),
                         _('SQL Query'), 'text/plain')

            # reuse the session vars of the query module so that
            # the query navigation links on the ticket can be used to
            # navigate report results as well
            try:
                req.session['query_tickets'] = \
                    ' '.join([str(int(row['id']))
                              for rg in row_groups for row in rg[1]])
                req.session['query_href'] = \
                    req.session['query_href'] = report_href()
                # Kludge: we have to clear the other query session
                # variables, but only if the above succeeded
                for var in ('query_constraints', 'query_time'):
                    if var in req.session:
                        del req.session[var]
            except (ValueError, KeyError):
                pass
            if set(data['args']) - set(['USER']):
                data['show_args_form'] = True
                add_script(req, 'common/js/folding.js')
            if missing_args:
                add_warning(
                    req,
                    _('The following arguments are missing: %(args)s',
                      args=", ".join(missing_args)))
            return 'report_view.html', data, None
示例#35
0
    def render_ticket_action_control(self, req, ticket, action):

        self.log.debug('render_ticket_action_control: action "%s"' % action)

        this_action = self.actions[action]
        status = this_action['newstate']
        operations = this_action['operations']
        current_owner_or_empty = ticket._old.get('owner', ticket['owner'])
        current_owner = current_owner_or_empty or '(none)'
        if not (Chrome(self.env).show_email_addresses
                or 'EMAIL_VIEW' in req.perm(ticket.resource)):
            format_user = obfuscate_email_address
        else:
            format_user = lambda address: address
        current_owner = format_user(current_owner)

        control = [] # default to nothing
        hints = []
        if 'reset_workflow' in operations:
            control.append(tag("from invalid state "))
            hints.append(_("Current state no longer exists"))
        if 'del_owner' in operations:
            hints.append(_("The ticket will be disowned"))
        if 'set_owner' in operations or 'may_set_owner' in operations:
            if 'set_owner' in operations:
                default_owner = req.authname
            elif 'may_set_owner' in operations:
                default_owner = \
                    ticket._old.get('owner', ticket['owner'] or None)
            else:
                # Protect against future modification for case that another
                # operation is added to the outer conditional
                raise AssertionError(operations)

            if 'set_owner' in this_action:
                owners = [x.strip() for x in
                          this_action['set_owner'].split(',')]
            elif self.config.getbool('ticket', 'restrict_owner'):
                perm = PermissionSystem(self.env)
                owners = perm.get_users_with_permission('TICKET_MODIFY')
                owners.sort()
            else:
                owners = None
            if owners is not None and default_owner not in owners:
                owners.insert(0, default_owner)

            id = 'action_%s_reassign_owner' % action
            selected_owner = req.args.get(id, default_owner)

            if owners is None:
                control.append(
                    tag_('to %(owner)s', owner=tag.input(type='text', id=id,
                         name=id, value=selected_owner)))
                hints.append(_("The owner will be changed from "
                               "%(current_owner)s to the specified user",
                               current_owner=current_owner))
            elif len(owners) == 1:
                owner = tag.input(type='hidden', id=id, name=id,
                                  value=owners[0])
                formatted_owner = format_user(owners[0])
                control.append(tag_('to %(owner)s ',
                                    owner=tag(formatted_owner, owner)))
                if ticket['owner'] != owners[0]:
                    hints.append(_("The owner will be changed from "
                                   "%(current_owner)s to %(selected_owner)s",
                                   current_owner=current_owner,
                                   selected_owner=formatted_owner))
            else:
                control.append(tag_('to %(owner)s', owner=tag.select(
                    [tag.option(x if x is not None else '(none)',
                                value=x if x is not None else '',
                                selected=(x == selected_owner or None))
                     for x in owners],
                    id=id, name=id)))
                hints.append(_("The owner will be changed from "
                               "%(current_owner)s to the selected user",
                               current_owner=current_owner))
        elif 'set_owner_to_self' in operations and \
                ticket._old.get('owner', ticket['owner']) != req.authname:
            hints.append(_("The owner will be changed from %(current_owner)s "
                           "to %(authname)s", current_owner=current_owner,
                           authname=req.authname))
        if 'set_resolution' in operations:
            if 'set_resolution' in this_action:
                resolutions = [x.strip() for x in
                               this_action['set_resolution'].split(',')]
            else:
                resolutions = [val.name for val in Resolution.select(self.env)]
            if not resolutions:
                raise TracError(_("Your workflow attempts to set a resolution "
                                  "but none is defined (configuration issue, "
                                  "please contact your Trac admin)."))
            id = 'action_%s_resolve_resolution' % action
            if len(resolutions) == 1:
                resolution = tag.input(type='hidden', id=id, name=id,
                                       value=resolutions[0])
                control.append(tag_('as %(resolution)s',
                                    resolution=tag(resolutions[0],
                                                   resolution)))
                hints.append(_("The resolution will be set to %(name)s",
                               name=resolutions[0]))
            else:
                selected_option = req.args.get(id,
                        TicketSystem(self.env).default_resolution)
                control.append(tag_('as %(resolution)s',
                                    resolution=tag.select(
                    [tag.option(x, value=x,
                                selected=(x == selected_option or None))
                     for x in resolutions],
                    id=id, name=id)))
                hints.append(_("The resolution will be set"))
        if 'del_resolution' in operations:
            hints.append(_("The resolution will be deleted"))
        if 'leave_status' in operations:
            control.append(_('as %(status)s ',
                             status=ticket._old.get('status',
                                                    ticket['status'])))
            if len(operations) == 1:
                hints.append(_("The owner will remain %(current_owner)s",
                               current_owner=current_owner)
                             if current_owner_or_empty else
                             _("The ticket will remain with no owner"))
        else:
            if status != '*':
                hints.append(_("Next status will be '%(name)s'", name=status))
        return (this_action['name'], tag(*control), '. '.join(hints) + '.'
                if hints else '')
示例#36
0
    def _render_admin_panel(self, req, cat, page, component):
        # Detail view?
        if component:
            comp = model.Component(self.env, component)
            if req.method == 'POST':
                if req.args.get('save'):
                    comp.name = name = req.args.get('name')
                    comp.owner = req.args.get('owner')
                    comp.description = req.args.get('description')
                    try:
                        comp.update()
                    except self.env.db_exc.IntegrityError:
                        raise TracError(
                            _('Component "%(name)s" already '
                              'exists.',
                              name=name))
                    add_notice(req, _("Your changes have been saved."))
                    req.redirect(req.href.admin(cat, page))
                elif req.args.get('cancel'):
                    req.redirect(req.href.admin(cat, page))

            Chrome(self.env).add_wiki_toolbars(req)
            data = {'view': 'detail', 'component': comp}

        else:
            default = self.config.get('ticket', 'default_component')
            if req.method == 'POST':
                # Add Component
                if req.args.get('add') and req.args.get('name'):
                    name = req.args.get('name')
                    try:
                        comp = model.Component(self.env, name=name)
                    except ResourceNotFound:
                        comp = model.Component(self.env)
                        comp.name = name
                        if req.args.get('owner'):
                            comp.owner = req.args.get('owner')
                        comp.insert()
                        add_notice(
                            req,
                            _('The component "%(name)s" has been '
                              'added.',
                              name=name))
                        req.redirect(req.href.admin(cat, page))
                    else:
                        if comp.name is None:
                            raise TracError(_("Invalid component name."))
                        raise TracError(
                            _('Component "%(name)s" already '
                              'exists.',
                              name=name))

                # Remove components
                elif req.args.get('remove'):
                    sel = req.args.getlist('sel')
                    if not sel:
                        raise TracError(_("No component selected"))
                    with self.env.db_transaction:
                        for name in sel:
                            model.Component(self.env, name).delete()
                            if name == default:
                                self.config.set('ticket', 'default_component',
                                                '')
                                self._save_config(req)
                    add_notice(
                        req, _("The selected components have been "
                               "removed."))
                    req.redirect(req.href.admin(cat, page))

                # Set default component
                elif req.args.get('apply'):
                    name = req.args.get('default')
                    if name and name != default:
                        self.log.info("Setting default component to %s", name)
                        self.config.set('ticket', 'default_component', name)
                        self._save_config(req)
                        req.redirect(req.href.admin(cat, page))

                # Clear default component
                elif req.args.get('clear'):
                    self.log.info("Clearing default component")
                    self.config.set('ticket', 'default_component', '')
                    self._save_config(req)
                    req.redirect(req.href.admin(cat, page))

            data = {
                'view': 'list',
                'components': list(model.Component.select(self.env)),
                'default': default
            }

        owners = TicketSystem(self.env).get_allowed_owners()
        if owners is not None:
            owners.insert(0, '')
        data.update({'owners': owners})

        return 'admin_components.html', data
    def process_request(self, req):
        self.log.debug('HTTP request: %s, method: %s, user: %s' %
                       (req.path_info, req.method, req.authname))

        if req.method != 'GET' and req.method != 'POST':
            return req.send([], content_type='application/json')

        board_id = None
        is_ticket_call = False
        match = self.request_regexp.match(req.path_info)
        if match:
            board_id = match.group('bid')
            is_ticket_call = match.group('ticket') is not None

        if not self.ticket_fields:
            self.ticket_fields = TicketSystem(self.env).get_ticket_fields()

        if board_id is None:
            meta_data = {}
            meta_data['ticketFields'] = self.ticket_fields
            return req.send(json.dumps(meta_data),
                            content_type='application/json')

        arg_list = parse_arg_list(req.query_string)
        detailed_tickets = []
        added_tickets = []
        removed_tickets = []
        for arg in arg_list:
            if arg[0] == 'detailed':
                detailed_tickets = self._parse_id_list(arg[1])
            elif arg[0] == 'add':
                added_tickets = self._parse_id_list(arg[1])
            elif arg[0] == 'remove':
                removed_tickets = self._parse_id_list(arg[1])

        board = KanbanBoard(board_id, detailed_tickets, self.ticket_fields,
                            self.env, self.log)

        added = 0
        if len(added_tickets) > 0:
            added = board.add_tickets(added_tickets)

        removed = 0
        if len(removed_tickets) > 0:
            removed = board.remove_tickets(removed_tickets)

        # We need to update board data to match (possibly changed) ticket states
        is_editable = 'WIKI_MODIFY' in req.perm and 'TICKET_MODIFY' in req.perm
        board.fix_ticket_columns(req, is_editable, added > 0 or removed > 0)

        if req.method == 'GET':
            return req.send(board.get_json(True, False),
                            content_type='application/json')
        else:
            if is_ticket_call:
                ticket_data = json.loads(req.read())
                is_new = 'id' not in ticket_data
                id = self.save_ticket(ticket_data, req.authname)
                if is_new:
                    board.add_tickets([id])
                else:
                    board.update_tickets([id])
            else:
                modified_tickets = []
                column_data = json.loads(req.read())
                for col in column_data:
                    for ticket in col['tickets']:
                        for key, value in ticket.items():
                            if key != 'id':
                                self.save_ticket(ticket, req.authname)
                                modified_tickets.append(ticket['id'])
                                break

                board.update_columns(column_data)
                if modified_tickets:
                    board.update_tickets(modified_tickets)

            board.fix_ticket_columns(req, True, True)
            return req.send(board.get_json(True, False),
                            content_type='application/json')
示例#38
0
文件: api.py 项目: pkdevbox/trac
 def setUp(self):
     self.env = EnvironmentStub(default_data=True)
     self.perm = PermissionSystem(self.env)
     self.ticket_system = TicketSystem(self.env)
     self.req = Mock()
示例#39
0
    def expand_macro(self, formatter, name, content, realms=[]):
        """Evaluate macro call and render results.

        Calls from web-UI come with pre-processed realm selection.
        """
        env = self.env
        req = formatter.req
        tag_system = TagSystem(env)

        all_realms = tag_system.get_taggable_realms()
        if not all_realms:
            # Tag providers are required, no result without at least one.
            return ''
        args, kw = parse_args(content)

        query = args and args[0].strip() or None
        if not realms:
            # Check macro arguments for realms (typical wiki macro call).
            realms = 'realm' in kw and kw['realm'].split('|') or []
        if query:
            # Add realms from query expression.
            realms.extend(query_realms(query, all_realms))
            # Remove redundant realm selection for performance.
            if set(realms) == all_realms:
                query = re.sub('(^|\W)realm:\S+(\W|$)', ' ', query).strip()
        if name == 'TagCloud':
            # Set implicit 'all tagged realms' as default.
            if not realms:
                realms = all_realms
            if query:
                all_tags = Counter()
                # Require per resource query including view permission checks.
                for resource, tags in tag_system.query(req, query):
                    all_tags.update(tags)
            else:
                # Allow faster per tag query, side steps permission checks.
                all_tags = tag_system.get_all_tags(req, realms=realms)
            mincount = 'mincount' in kw and kw['mincount'] or None
            return self.render_cloud(req,
                                     all_tags,
                                     caseless_sort=self.caseless_sort,
                                     mincount=mincount,
                                     realms=realms)
        elif name == 'ListTagged':
            if content and _OBSOLETE_ARGS_RE.search(content):
                data = {'warning': 'obsolete_args'}
            else:
                data = {'warning': None}
            context = formatter.context
            # Use TagsQuery arguments (most likely wiki macro calls).
            cols = 'cols' in kw and kw['cols'] or self.default_cols
            format = 'format' in kw and kw['format'] or self.default_format
            if not realms:
                # Apply ListTagged defaults to macro call w/o realm.
                realms = list(set(all_realms) - set(self.exclude_realms))
                if not realms:
                    return ''
            query = '(%s) (%s)' % (query or '', ' or '.join(
                ['realm:%s' % (r) for r in realms]))
            query_result = tag_system.query(req, query)
            excludes = [
                exc.strip() for exc in kw.get('exclude', '').split(':')
                if exc.strip()
            ]
            if excludes and query_result:
                filtered_result = [(resource, tags)
                                   for resource, tags in query_result
                                   if not any(
                                       fnmatchcase(resource.id, exc)
                                       for exc in excludes)]
                query_result = filtered_result
            if not query_result:
                return ''

            def _link(resource):
                if resource.realm == 'tag':
                    # Keep realm selection in tag links.
                    return builder.a(resource.id,
                                     href=self.get_href(req,
                                                        realms,
                                                        tag=resource))
                elif resource.realm == 'ticket':
                    # Return resource link including ticket status dependend
                    #   class to allow for common Trac ticket link style.
                    ticket = Ticket(env, resource.id)
                    return builder.a('#%s' % ticket.id,
                                     class_=ticket['status'],
                                     href=formatter.href.ticket(ticket.id),
                                     title=shorten_line(ticket['summary']))
                return render_resource_link(env, context, resource, 'compact')

            if format == 'table':
                cols = [
                    col for col in cols.split('|')
                    if col in self.supported_cols
                ]
                # Use available translations from Trac core.
                try:
                    labels = TicketSystem(env).get_ticket_field_labels()
                    labels['id'] = _('Id')
                except AttributeError:
                    # Trac 0.11 neither has the attribute nor uses i18n.
                    labels = {'id': 'Id', 'description': 'Description'}
                labels['realm'] = _('Realm')
                labels['tags'] = _('Tags')
                headers = [{'label': labels.get(col)} for col in cols]
                data.update({'cols': cols, 'headers': headers})

            try:
                results = sorted(
                    query_result,
                    key=lambda r: embedded_numbers(to_unicode(r[0].id)))
            except (InvalidQuery, InvalidTagRealm), e:
                return system_message(_("ListTagged macro error"), e)
            results = self._paginate(req, results, realms)
            rows = []
            for resource, tags in results:
                desc = tag_system.describe_tagged_resource(req, resource)
                tags = sorted(tags)
                wiki_desc = format_to_oneliner(env, context, desc)
                if tags:
                    rendered_tags = [
                        _link(Resource('tag', tag)) for tag in tags
                    ]
                    if 'oldlist' == format:
                        resource_link = _link(resource)
                    else:
                        resource_link = builder.a(wiki_desc,
                                                  href=get_resource_url(
                                                      env, resource,
                                                      context.href))
                        if 'table' == format:
                            cells = []
                            for col in cols:
                                if col == 'id':
                                    cells.append(_link(resource))
                                # Don't duplicate links to resource in both.
                                elif col == 'description' and 'id' in cols:
                                    cells.append(wiki_desc)
                                elif col == 'description':
                                    cells.append(resource_link)
                                elif col == 'realm':
                                    cells.append(resource.realm)
                                elif col == 'tags':
                                    cells.append(
                                        builder([(tag, ' ')
                                                 for tag in rendered_tags]))
                            rows.append({'cells': cells})
                            continue
                rows.append({
                    'desc': wiki_desc,
                    'rendered_tags': None,
                    'resource_link': _link(resource)
                })
            data.update({
                'format': format,
                'paginator': results,
                'results': rows,
                'tags_url': req.href('tags')
            })

            # Work around a bug in trac/templates/layout.html, that causes a
            # TypeError for the wiki macro call, if we use add_link() alone.
            add_stylesheet(req, 'common/css/search.css')

            return Chrome(env).render_template(req, 'listtagged_results.html',
                                               data, 'text/html', True)
示例#40
0
class RestrictOwnerTestCase(unittest.TestCase):

    def setUp(self):
        tmpdir = os.path.realpath(tempfile.gettempdir())
        self.env = EnvironmentStub(enable=['trac.*', AuthzPolicy], path=tmpdir)
        self.env.config.set('trac', 'permission_policies',
                            'AuthzPolicy, DefaultPermissionPolicy')
        self.env.config.set('ticket', 'restrict_owner', True)

        self.perm_sys = PermissionSystem(self.env)
        self.env.insert_users([('user1', 'User C', '*****@*****.**'),
                               ('user2', 'User A', '*****@*****.**'),
                               ('user3', 'User D', '*****@*****.**'),
                               ('user4', 'User B', '*****@*****.**')])
        self.perm_sys.grant_permission('user1', 'TICKET_MODIFY')
        self.perm_sys.grant_permission('user2', 'TICKET_VIEW')
        self.perm_sys.grant_permission('user3', 'TICKET_MODIFY')
        self.perm_sys.grant_permission('user4', 'TICKET_MODIFY')
        self.authz_file = os.path.join(tmpdir, 'trac-authz-policy')
        create_file(self.authz_file)
        self.env.config.set('authz_policy', 'authz_file', self.authz_file)
        self.ctlr = TicketSystem(self.env).action_controllers[0]
        self.req1 = MockRequest(self.env, authname='user1')
        self.ticket = Ticket(self.env)
        self.ticket['status'] = 'new'
        self.ticket.insert()

    def tearDown(self):
        self.env.reset_db()
        os.remove(self.authz_file)

    def _reload_workflow(self):
        self.ctlr.actions = self.ctlr.get_all_actions()

    def test_set_owner(self):
        """Restricted owners list contains users with TICKET_MODIFY.
        """
        self.env.config.set('trac', 'show_full_names', False)

        ctrl = self.ctlr.render_ticket_action_control(self.req1, self.ticket,
                                                      'reassign')

        self.assertEqual('reassign', ctrl[0])
        self.assertIn('value="user1">user1</option>', str(ctrl[1]))
        self.assertNotIn('value="user2">user2</option>', str(ctrl[1]))
        self.assertIn('value="user3">user3</option>', str(ctrl[1]))
        self.assertIn('value="user4">user4</option>', str(ctrl[1]))

    def test_set_owner_fine_grained_permissions(self):
        """Fine-grained permission checks when populating the restricted
        owners list (#10833).
        """
        self.env.config.set('trac', 'show_full_names', False)
        create_file(self.authz_file, """\
[ticket:1]
user4 = !TICKET_MODIFY
""")

        ctrl = self.ctlr.render_ticket_action_control(self.req1, self.ticket,
                                                      'reassign')

        self.assertEqual('reassign', ctrl[0])
        self.assertIn('value="user1">user1</option>', str(ctrl[1]))
        self.assertNotIn('value="user2">user2</option>', str(ctrl[1]))
        self.assertIn('value="user3">user3</option>', str(ctrl[1]))
        self.assertNotIn('value="user4">user4</option>', str(ctrl[1]))

    def test_set_owner_show_fullnames(self):
        """Full names are sorted when [trac] show_full_names = True."""
        ctrl = self.ctlr.render_ticket_action_control(self.req1, self.ticket,
                                                      'reassign')

        self.assertEqual('reassign', ctrl[0])
        self.assertEqual("""\
to <select name="action_reassign_reassign_owner" \
id="action_reassign_reassign_owner">\
<option value="user4">User B</option>\
<option selected="True" value="user1">User C</option>\
<option value="user3">User D</option></select>\
""", str(ctrl[1]))
示例#41
0
 def getAll(self, req):
     """ Returns all ticket states described by active workflow. """
     return TicketSystem(self.env).get_all_status()
示例#42
0
 def get_configurable_workflow(self):
     controllers = TicketSystem(self.env).action_controllers
     for controller in controllers:
         if isinstance(controller, ConfigurableTicketWorkflow):
             return controller
     return ConfigurableTicketWorkflow(self.env)
示例#43
0
 def getTicketFields(self, req):
     """ Return a list of all ticket fields fields. """
     return TicketSystem(self.env).get_ticket_fields()
示例#44
0
 def _get_ticket_field(self, field_name):
     fields = TicketSystem(self.env).get_ticket_fields()
     return next((i for i in fields if i['name'] == field_name))
示例#45
0
文件: admin.py 项目: exocad/exotrac
    def _render_admin_panel(self, req, cat, page, component):
        # Detail view?
        if component:
            comp = model.Component(self.env, component)
            if req.method == 'POST':
                if req.args.get('save'):
                    comp.name = name = req.args.get('name')
                    comp.owner = req.args.get('owner')
                    comp.description = req.args.get('description')
                    try:
                        comp.update()
                    except self.env.db_exc.IntegrityError:
                        raise TracError(_('Component "%(name)s" already '
                                          'exists.', name=name))
                    add_notice(req, _('Your changes have been saved.'))
                    req.redirect(req.href.admin(cat, page))
                elif req.args.get('cancel'):
                    req.redirect(req.href.admin(cat, page))

            Chrome(self.env).add_wiki_toolbars(req)
            data = {'view': 'detail', 'component': comp}

        else:
            default = self.config.get('ticket', 'default_component')
            if req.method == 'POST':
                # Add Component
                if req.args.get('add') and req.args.get('name'):
                    name = req.args.get('name')
                    try:
                        comp = model.Component(self.env, name=name)
                    except ResourceNotFound:
                        comp = model.Component(self.env)
                        comp.name = name
                        if req.args.get('owner'):
                            comp.owner = req.args.get('owner')
                        comp.insert()
                        add_notice(req, _('The component "%(name)s" has been '
                                          'added.', name=name))
                        req.redirect(req.href.admin(cat, page))
                    else:
                        if comp.name is None:
                            raise TracError(_("Invalid component name."))
                        raise TracError(_('Component "%(name)s" already '
                                          'exists.', name=name))

                # Remove components
                elif req.args.get('remove'):
                    sel = req.args.get('sel')
                    if not sel:
                        raise TracError(_('No component selected'))
                    if not isinstance(sel, list):
                        sel = [sel]
                    with self.env.db_transaction:
                        for name in sel:
                            model.Component(self.env, name).delete()
                    add_notice(req, _("The selected components have been "
                                      "removed."))
                    req.redirect(req.href.admin(cat, page))

                # Set default component
                elif req.args.get('apply'):
                    name = req.args.get('default')
                    if name and name != default:
                        self.log.info("Setting default component to %s", name)
                        self.config.set('ticket', 'default_component', name)
                        _save_config(self.config, req, self.log)
                        req.redirect(req.href.admin(cat, page))

            data = {'view': 'list',
                    'components': list(model.Component.select(self.env)),
                    'default': default}

        owners = TicketSystem(self.env).get_allowed_owners()
        if owners is not None:
            owners.insert(0, '')
        data.update({'owners': owners})

        return 'admin_components.html', data
示例#46
0
 def _get_actions(self, ticket_dict):
     ts = TicketSystem(self.env)
     ticket = insert_ticket(self.env, **ticket_dict)
     return ts.get_available_actions(self.req, Ticket(self.env, ticket.id))
示例#47
0
 def update(self,
            req,
            id,
            comment,
            attributes={},
            notify=False,
            author='',
            when=None):
     """ Update a ticket, returning the new ticket in the same form as
     get(). 'New-style' call requires two additional items in attributes:
     (1) 'action' for workflow support (including any supporting fields
     as retrieved by getActions()),
     (2) '_ts' changetime token for detecting update collisions (as received
     from get() or update() calls).
     ''Calling update without 'action' and '_ts' changetime token is
     deprecated, and will raise errors in a future version.'' """
     t = model.Ticket(self.env, id)
     # custom author?
     if author and not (req.authname == 'anonymous' \
                         or 'TICKET_ADMIN' in req.perm(t.resource)):
         # only allow custom author if anonymous is permitted or user is admin
         self.log.warn(
             "RPC ticket.update: %r not allowed to change author "
             "to %r for comment on #%d", req.authname, author, id)
         author = ''
     author = author or req.authname
     # custom change timestamp?
     if when and not 'TICKET_ADMIN' in req.perm(t.resource):
         self.log.warn(
             "RPC ticket.update: %r not allowed to update #%d with "
             "non-current timestamp (%r)", author, id, when)
         when = None
     when = when or to_datetime(None, utc)
     # never try to update 'time' and 'changetime' attributes directly
     if 'time' in attributes:
         del attributes['time']
     if 'changetime' in attributes:
         del attributes['changetime']
     # and action...
     if not 'action' in attributes:
         # FIXME: Old, non-restricted update - remove soon!
         self.log.warning("Rpc ticket.update for ticket %d by user %s " \
                 "has no workflow 'action'." % (id, req.authname))
         req.perm(t.resource).require('TICKET_MODIFY')
         time_changed = attributes.pop('_ts', None)
         if time_changed and \
                 str(time_changed) != str(to_utimestamp(t.time_changed)):
             raise TracError("Ticket has been updated since last get().")
         for k, v in attributes.iteritems():
             t[k] = v
         t.save_changes(author, comment, when=when)
     else:
         ts = TicketSystem(self.env)
         tm = TicketModule(self.env)
         # TODO: Deprecate update without time_changed timestamp
         time_changed = attributes.pop('_ts', to_utimestamp(t.time_changed))
         try:
             time_changed = int(time_changed)
         except ValueError:
             raise TracError("RPC ticket.update: Wrong '_ts' token " \
                             "in attributes (%r)." % time_changed)
         action = attributes.get('action')
         avail_actions = ts.get_available_actions(req, t)
         if not action in avail_actions:
             raise TracError("Rpc: Ticket %d by %s " \
                     "invalid action '%s'" % (id, req.authname, action))
         controllers = list(tm._get_action_controllers(req, t, action))
         all_fields = [field['name'] for field in ts.get_ticket_fields()]
         for k, v in attributes.iteritems():
             if k in all_fields and k != 'status':
                 t[k] = v
         # TicketModule reads req.args - need to move things there...
         req.args.update(attributes)
         req.args['comment'] = comment
         # Collision detection: 0.11+0.12 timestamp
         req.args['ts'] = str(from_utimestamp(time_changed))
         # Collision detection: 0.13/1.0+ timestamp
         req.args['view_time'] = str(time_changed)
         changes, problems = tm.get_ticket_changes(req, t, action)
         for warning in problems:
             add_warning(req, "Rpc ticket.update: %s" % warning)
         valid = problems and False or tm._validate_ticket(req, t)
         if not valid:
             raise TracError(" ".join(
                 [warning for warning in req.chrome['warnings']]))
         else:
             tm._apply_ticket_changes(t, changes)
             self.log.debug("Rpc ticket.update save: %s" % repr(t.values))
             t.save_changes(author, comment, when=when)
             # Apply workflow side-effects
             for controller in controllers:
                 controller.apply_action_side_effects(req, t, action)
     if notify:
         try:
             tn = TicketNotifyEmail(self.env)
             tn.notify(t, newticket=False, modtime=when)
         except Exception, e:
             self.log.exception("Failure sending notification on change of "
                                "ticket #%s: %s" % (t.id, e))
示例#48
0
 def setUp(self):
     self.env = EnvironmentStub(default_data=True)
     self.perm = PermissionSystem(self.env)
     self.ticket_system = TicketSystem(self.env)
     self.req = MockRequest(self.env)
示例#49
0
 def setUp(self):
     self.env = EnvironmentStub()
     self.perm = PermissionSystem(self.env)
     self.ticket_system = TicketSystem(self.env)
     self.req = Mock()
示例#50
0
class SetOwnerAttributeTestCase(unittest.TestCase):

    def setUp(self):
        self.env = EnvironmentStub(default_data=True)
        self.perm_sys = PermissionSystem(self.env)
        self.ctlr = TicketSystem(self.env).action_controllers[0]
        self.ticket = Ticket(self.env)
        self.ticket['status'] = 'new'
        self.ticket.insert()
        with self.env.db_transaction as db:
            for user in ('user1', 'user2', 'user3', 'user4'):
                db("INSERT INTO session VALUES (%s, %s, %s)", (user, 1, 0))
        permissions = [
            ('user1', 'TICKET_EDIT_CC'),
            ('user2', 'TICKET_EDIT_CC'),
            ('user2', 'TICKET_BATCH_MODIFY'),
            ('user3', 'TICKET_ADMIN'),
            ('user4', 'TICKET_VIEW'),
            ('user1', 'group1'),
            ('user2', 'group1'),
            ('user2', 'group2'),
            ('user3', 'group2'),
            ('user4', 'group3')
        ]
        for perm in permissions:
            self.perm_sys.grant_permission(*perm)
        self.req = Mock(authname='user1', args={},
                        perm=PermissionCache(self.env, 'user0'))
        self.expected = """\
to <select name="action_reassign_reassign_owner" \
id="action_reassign_reassign_owner"><option selected="True" \
value="user1">user1</option><option value="user2">user2</option>\
<option value="user3">user3</option></select>"""

    def _reload_workflow(self):
        self.ctlr.actions = self.ctlr.get_all_actions()

    def tearDown(self):
        self.env.reset_db()

    def test_users(self):
        self.env.config.set('ticket-workflow', 'reassign.set_owner',
                            'user1, user2, user3')
        self._reload_workflow()

        args = self.req, self.ticket, 'reassign'
        label, control, hints = self.ctlr.render_ticket_action_control(*args)

        self.assertEqual(self.expected, str(control))

    def test_groups(self):
        self.env.config.set('ticket-workflow', 'reassign.set_owner',
                            'group1, group2')
        self._reload_workflow()

        args = self.req, self.ticket, 'reassign'
        label, control, hints = self.ctlr.render_ticket_action_control(*args)

        self.assertEqual(self.expected, str(control))

    def test_permission(self):
        self.env.config.set('ticket-workflow', 'reassign.set_owner',
                            'TICKET_EDIT_CC, TICKET_BATCH_MODIFY')
        self._reload_workflow()

        args = self.req, self.ticket, 'reassign'
        label, control, hints = self.ctlr.render_ticket_action_control(*args)

        self.assertEqual(self.expected, str(control))
示例#51
0
    def ticket_changed(self, ticket, comment, author, old_values):
        link_fields = [f['name'] for f in ticket.fields if f.get('link')]
        ticket_system = TicketSystem(self.env)
        links_provider = LinksProvider(self.env)
        remote_tktsys = RemoteTicketSystem(self.env)

        # We go behind trac's back to augment the ticket with remote links
        # As a result trac doesn't provide a correct old_values so fetch
        # our own
        orig_old_vals = old_values
        if old_values is None:
            old_values = {}
        else:
            self._augment_values(ticket.id, old_values)

        @self.env.with_transaction()
        def do_changed(db):
            cursor = db.cursor()
            for end in link_fields:
                # Determine links added or removed in this change by taking the
                # set difference of new and old values
                new_rtkts = set(remote_tktsys.parse_links(ticket[end]))
                old_rtkts = set(remote_tktsys.parse_links(old_values.get(end)))

                links_added = new_rtkts - old_rtkts
                links_removed = old_rtkts - new_rtkts
                links_changed = old_rtkts ^ new_rtkts  # Additons and removals

                other_end = ticket_system.link_ends_map[end]

                # Add link records for remote links created in this change
                records = [('', ticket.id, end, rname, rid)
                           for rname, rid in links_added]
                if other_end:
                    records += [(rname, rid, other_end, '', ticket.id)
                                for rname, rid in links_added]
                cursor.executemany(
                    '''
                    INSERT INTO remote_ticket_links
                    (source_name, source, type, destination_name, destination)
                    VALUES (%s, %s, %s, %s, %s)''', records)

                # Remove link records for remote links removed in this change
                records = [('', ticket.id, end, rname, rid)
                           for rname, rid in links_removed]
                if other_end:
                    records += [(rname, rid, other_end, '', ticket.id)
                                for rname, rid in links_added]
                cursor.executemany(
                    '''
                    DELETE FROM remote_ticket_links 
                    WHERE source_name=%s AND source=%s AND type=%s
                    AND destination_name=%s AND destination=%s''', records)

                # Record change history in ticket_change
                # Again we're going behind trac's back, so take care not to
                # obliterate existing records:
                #  - If the field (end) has changed local links, as well as
                #    changed remote links then update the record
                #  - If the only change was to remote links then there is no
                #    ticket_change record to update, so insert one
                if links_changed and orig_old_vals is not None:
                    when_ts = to_utimestamp(ticket['changetime'])

                    cursor.execute(
                        '''
                        UPDATE ticket_change
                        SET oldvalue=%s, newvalue=%s
                        WHERE ticket=%s AND time=%s AND author=%s AND field=%s
                        ''', (old_values[end], ticket[end], ticket.id, when_ts,
                              author, end))

                    # Check that a row was updated, if so
                    if cursor.rowcount >= 1:
                        continue

                    cursor.execute(
                        '''
                        INSERT INTO ticket_change
                        (ticket, time, author, field, oldvalue, newvalue)
                        VALUES (%s, %s, %s, %s, %s, %s)
                        ''', (ticket.id, when_ts, author, end, old_values[end],
                              ticket[end]))
示例#52
0
class ConfigurableTicketWorkflowTestCase(unittest.TestCase):

    def setUp(self):
        self.env = EnvironmentStub(default_data=True)
        config = self.env.config
        config.set('ticket-workflow', 'change_owner', 'new -> new')
        config.set('ticket-workflow', 'change_owner.operations', 'set_owner')
        self.ctlr = TicketSystem(self.env).action_controllers[0]
        self.ticket_module = TicketModule(self.env)

    def tearDown(self):
        self.env.reset_db()

    def _create_request(self, authname='anonymous', **kwargs):
        kw = {'path_info': '/', 'perm': MockPerm(), 'args': {},
              'href': self.env.href, 'abs_href': self.env.abs_href,
              'tz': utc, 'locale': None, 'lc_time': locale_en,
              'session': {}, 'authname': authname,
              'chrome': {'notices': [], 'warnings': []},
              'method': None, 'get_header': lambda v: None, 'is_xhr': False,
              'form_token': None, }
        kw.update(kwargs)
        def redirect(url, permanent=False):
            raise RequestDone
        return Mock(add_redirect_listener=lambda x: [].append(x),
                    redirect=redirect, **kw)

    def _add_component(self, name='test', owner='owner1'):
        component = Component(self.env)
        component.name = name
        component.owner = owner
        component.insert()

    def test_get_all_actions_custom_attribute(self):
        """Custom attribute in ticket-workflow."""
        config = self.env.config['ticket-workflow']
        config.set('resolve.set_milestone', 'reject')
        all_actions = self.ctlr.get_all_actions()

        resolve_action = None
        for name, attrs in all_actions.items():
            if name == 'resolve':
                resolve_action = attrs

        self.assertIsNotNone(resolve_action)
        self.assertIn('set_milestone', resolve_action.keys())
        self.assertEqual('reject', resolve_action['set_milestone'])

    def test_owner_from_component(self):
        """Verify that the owner of a new ticket is set to the owner
        of the component.
        """
        self._add_component('component3', 'cowner3')

        req = self._create_request(method='POST', args={
            'field_reporter': 'reporter1',
            'field_summary': 'the summary',
            'field_component': 'component3',
        })
        self.assertRaises(RequestDone, self.ticket_module.process_request, req)
        ticket = Ticket(self.env, 1)

        self.assertEqual('component3', ticket['component'])
        self.assertEqual('cowner3', ticket['owner'])

    def test_component_change(self):
        """New ticket owner is updated when the component is changed.
        """
        self._add_component('component3', 'cowner3')
        self._add_component('component4', 'cowner4')

        ticket = Ticket(self.env)
        ticket.populate({
            'reporter': 'reporter1',
            'summary': 'the summary',
            'component': 'component3',
            'owner': 'cowner3',
            'status': 'new',
        })
        tkt_id = ticket.insert()

        req = self._create_request(method='POST', args={
            'id': tkt_id,
            'field_component': 'component4',
            'submit': True,
            'action': 'leave',
            'view_time': str(to_utimestamp(ticket['changetime'])),
        })
        self.assertRaises(RequestDone, self.ticket_module.process_request, req)
        ticket = Ticket(self.env, tkt_id)

        self.assertEqual('component4', ticket['component'])
        self.assertEqual('cowner4', ticket['owner'])

    def test_component_change_and_owner_change(self):
        """New ticket owner is not updated if owner is explicitly
        changed.
        """
        self._add_component('component3', 'cowner3')
        self._add_component('component4', 'cowner4')

        ticket = Ticket(self.env)
        ticket.populate({
            'reporter': 'reporter1',
            'summary': 'the summary',
            'component': 'component3',
            'status': 'new',
        })
        tkt_id = ticket.insert()

        req = self._create_request(method='POST', args={
            'id': tkt_id,
            'field_component': 'component4',
            'submit': True,
            'action': 'change_owner',
            'action_change_owner_reassign_owner': 'owner1',
            'view_time': str(to_utimestamp(ticket['changetime'])),
        })
        self.assertRaises(RequestDone, self.ticket_module.process_request, req)
        ticket = Ticket(self.env, tkt_id)

        self.assertEqual('component4', ticket['component'])
        self.assertEqual('owner1', ticket['owner'])

    def test_old_owner_not_old_component_owner(self):
        """New ticket owner is not updated if old owner is not the owner
        of the old component.
        """
        self._add_component('component3', 'cowner3')
        self._add_component('component4', 'cowner4')

        ticket = Ticket(self.env)
        ticket.populate({
            'reporter': 'reporter1',
            'summary': 'the summary',
            'component': 'component3',
            'owner': 'owner1',
            'status': 'new',
        })
        tkt_id = ticket.insert()

        req = self._create_request(method='POST', args={
            'id': tkt_id,
            'field_component': 'component4',
            'submit': True,
            'action': 'leave',
            'view_time': str(to_utimestamp(ticket['changetime'])),
        })
        self.assertRaises(RequestDone, self.ticket_module.process_request, req)
        ticket = Ticket(self.env, tkt_id)

        self.assertEqual('component4', ticket['component'])
        self.assertEqual('owner1', ticket['owner'])

    def test_new_component_has_no_owner(self):
        """Ticket is not disowned when the component is changed to a
        component with no owner.
        """
        self._add_component('component3', 'cowner3')
        self._add_component('component4', '')

        ticket = Ticket(self.env)
        ticket.populate({
            'reporter': 'reporter1',
            'summary': 'the summary',
            'component': 'component3',
            'owner': 'cowner3',
            'status': 'new',
        })
        tkt_id = ticket.insert()

        req = self._create_request(method='POST', args={
            'id': tkt_id,
            'field_component': 'component4',
            'submit': True,
            'action': 'leave',
            'view_time': str(to_utimestamp(ticket['changetime'])),
        })
        self.assertRaises(RequestDone, self.ticket_module.process_request, req)
        ticket = Ticket(self.env, tkt_id)

        self.assertEqual('component4', ticket['component'])
        self.assertEqual('cowner3', ticket['owner'])
示例#53
0
class RestrictOwnerTestCase(unittest.TestCase):

    def setUp(self):
        tmpdir = os.path.realpath(tempfile.gettempdir())
        self.env = EnvironmentStub(enable=['trac.*', AuthzPolicy], path=tmpdir)
        self.env.config.set('trac', 'permission_policies',
                            'AuthzPolicy, DefaultPermissionPolicy')
        self.env.config.set('ticket', 'restrict_owner', True)

        self.perm_sys = PermissionSystem(self.env)
        self.env.insert_known_users([
            ('user1', '', ''), ('user2', '', ''),
            ('user3', '', ''), ('user4', '', '')
        ])
        self.perm_sys.grant_permission('user1', 'TICKET_MODIFY')
        self.perm_sys.grant_permission('user2', 'TICKET_VIEW')
        self.perm_sys.grant_permission('user3', 'TICKET_MODIFY')
        self.perm_sys.grant_permission('user4', 'TICKET_MODIFY')
        self.authz_file = os.path.join(tmpdir, 'trac-authz-policy')
        create_file(self.authz_file)
        self.env.config.set('authz_policy', 'authz_file', self.authz_file)
        self.ctlr = TicketSystem(self.env).action_controllers[0]
        self.req1 = Mock(authname='user1', args={},
                         perm=PermissionCache(self.env, 'user1'))
        self.ticket = Ticket(self.env)
        self.ticket['status'] = 'new'
        self.ticket.insert()

    def tearDown(self):
        self.env.reset_db()
        os.remove(self.authz_file)

    def _reload_workflow(self):
        self.ctlr.actions = self.ctlr.get_all_actions()

    def test_set_owner(self):
        """Restricted owners list contains users with TICKET_MODIFY.
        """
        ctrl = self.ctlr.render_ticket_action_control(self.req1, self.ticket,
                                                      'reassign')

        self.assertEqual('reassign', ctrl[0])
        self.assertIn('value="user1">user1</option>', str(ctrl[1]))
        self.assertNotIn('value="user2">user2</option>', str(ctrl[1]))
        self.assertIn('value="user3">user3</option>', str(ctrl[1]))
        self.assertIn('value="user4">user4</option>', str(ctrl[1]))

    def test_set_owner_fine_grained_permissions(self):
        """Fine-grained permission checks when populating the restricted
        owners list (#10833).
        """
        create_file(self.authz_file, """\
[ticket:1]
user4 = !TICKET_MODIFY
""")

        ctrl = self.ctlr.render_ticket_action_control(self.req1, self.ticket,
                                                      'reassign')

        self.assertEqual('reassign', ctrl[0])
        self.assertIn('value="user1">user1</option>', str(ctrl[1]))
        self.assertNotIn('value="user2">user2</option>', str(ctrl[1]))
        self.assertIn('value="user3">user3</option>', str(ctrl[1]))
        self.assertNotIn('value="user4">user4</option>', str(ctrl[1]))
示例#54
0
class SetOwnerAttributeTestCase(unittest.TestCase):

    def setUp(self):
        self.env = EnvironmentStub(default_data=True)
        self.perm_sys = PermissionSystem(self.env)
        self.ctlr = TicketSystem(self.env).action_controllers[0]
        self.ticket = Ticket(self.env)
        self.ticket['status'] = 'new'
        self.ticket.insert()
        with self.env.db_transaction as db:
            for user in ('user1', 'user2', 'user3', 'user4'):
                db("INSERT INTO session VALUES (%s, %s, %s)", (user, 1, 0))
        permissions = [
            ('user1', 'TICKET_EDIT_CC'),
            ('user2', 'TICKET_EDIT_CC'),
            ('user2', 'TICKET_BATCH_MODIFY'),
            ('user3', 'TICKET_ADMIN'),
            ('user4', 'TICKET_VIEW'),
            ('user1', 'group1'),
            ('user2', 'group1'),
            ('user2', 'group2'),
            ('user3', 'group2'),
            ('user4', 'group3')
        ]
        for perm in permissions:
            self.perm_sys.grant_permission(*perm)
        self.req = MockRequest(self.env, authname='user1')
        self.expected = """\
to <select name="action_reassign_reassign_owner" \
id="action_reassign_reassign_owner"><option selected="True" \
value="user1">user1</option><option value="user2">user2</option>\
<option value="user3">user3</option></select>"""

    def _reload_workflow(self):
        self.ctlr.actions = self.ctlr.get_all_actions()

    def tearDown(self):
        self.env.reset_db()

    def test_users(self):
        self.env.config.set('ticket-workflow', 'reassign.set_owner',
                            'user1, user2, user3')
        self._reload_workflow()

        args = self.req, self.ticket, 'reassign'
        label, control, hints = self.ctlr.render_ticket_action_control(*args)

        self.assertEqual(self.expected, str(control))

    def test_groups(self):
        self.env.config.set('ticket-workflow', 'reassign.set_owner',
                            'group1, group2')
        self._reload_workflow()

        args = self.req, self.ticket, 'reassign'
        label, control, hints = self.ctlr.render_ticket_action_control(*args)

        self.assertEqual(self.expected, str(control))

    def test_permission(self):
        self.env.config.set('ticket-workflow', 'reassign.set_owner',
                            'TICKET_EDIT_CC, TICKET_BATCH_MODIFY')
        self._reload_workflow()

        args = self.req, self.ticket, 'reassign'
        label, control, hints = self.ctlr.render_ticket_action_control(*args)

        self.assertEqual(self.expected, str(control))
示例#55
0
 def expand_macro(self, formatter, name, content, args=[]):
     try:
         cols = []  # Sentinel
         group = ''  # Sentinel
         groups = {}
         lines = content.split('\r\n')
         for line in lines:
             if line.startswith('||= href =||= '):
                 cols = line[14:].split(' =||= ')
             elif line.startswith('|| group: '):
                 group = line[10:]
                 if group in [u'', u'None']:
                     group = None
                 groups[group] = []  # initialize for the group
             elif line.startswith('|| '):
                 values = iter(line[3:].split(' || '))
                 ticket = {'href': values.next()}
                 for col in cols:
                     ticket[col] = values.next()
                 groups[group].append(ticket)
             else:
                 pass
         ticketsystem = TicketSystem(self.env)
         #
         labels = ticketsystem.get_ticket_field_labels()
         headers = [{
             'name': col,
             'label': labels.get(col, _('Ticket'))
         } for col in cols]
         #
         fields = {}
         ticket_fields = ticketsystem.get_ticket_fields()
         for field in ticket_fields:
             fields[field['name']] = {
                 'label': field['label']
             }  # transform list to expected dict
         # fail safe
         fields[None] = 'NONE'
         for group in groups.keys():
             if not 'group' in fields:
                 fields[group] = group
         #
         group_name = 'group' in args and args['group'] or None
         if group_name not in fields:
             group_name = None
         query = {'group': group_name}
         #
         groups = [(name, groups[name])
                   for name in groups]  # transform dict to expected tuple
         #
         data = {
             'paginator': None,
             'headers': headers,
             'query': query,
             'fields': fields,
             'groups': groups,
         }
         add_stylesheet(formatter.req, 'common/css/report.css')
         chrome = Chrome(self.env)
         data = chrome.populate_data(formatter.req, data)
         template = chrome.load_template('query_results.html')
         content = template.generate(**data)
         # ticket id list as static
         tickets = ''
         if 'id' in cols:
             ticket_id_list = [
                 ticket.get('id') for group in groups for ticket in group[1]
             ]
             if len(ticket_id_list) > 0:
                 tickets = '([ticket:' + ','.join(
                     ticket_id_list) + ' query by ticket id])'
         return tag.div(
             content, format_to_html(self.env, formatter.context, tickets))
     except StopIteration:
         errorinfo = _('Not Enough fields in ticket: %s') % line
     except Exception:
         errorinfo = sys.exc_info()
     return tag.div(tag.div(errorinfo, class_='message'),
                    class_='error',
                    id='content')
示例#56
0
    def _render_view(self, req, milestone):
        milestone_groups = []
        available_groups = []
        component_group_available = False
        ticket_fields = TicketSystem(self.env).get_ticket_fields()

        # collect fields that can be used for grouping
        for field in ticket_fields:
            if field['type'] == 'select' and field['name'] != 'milestone' \
                    or field['name'] in ('owner', 'reporter'):
                available_groups.append({'name': field['name'],
                                         'label': field['label']})
                if field['name'] == 'component':
                    component_group_available = True

        # determine the field currently used for grouping
        by = None
        if component_group_available:
            by = 'component'
        elif available_groups:
            by = available_groups[0]['name']
        by = req.args.get('by', by)

        tickets = get_tickets_for_milestone(self.env, milestone=milestone.name,
                                            field=by)
        tickets = apply_ticket_permissions(self.env, req, tickets)
        stat = get_ticket_stats(self.stats_provider, tickets)

        context = web_context(req, milestone.resource)
        data = {
            'context': context,
            'milestone': milestone,
            'attachments': AttachmentModule(self.env).attachment_data(context),
            'available_groups': available_groups,
            'grouped_by': by,
            'groups': milestone_groups
            }
        data.update(milestone_stats_data(self.env, req, stat, milestone.name))

        if by:
            def per_group_stats_data(gstat, group_name):
                return milestone_stats_data(self.env, req, gstat,
                                            milestone.name, by, group_name)
            milestone_groups.extend(
                grouped_stats_data(self.env, self.stats_provider, tickets, by,
                                   per_group_stats_data))

        add_stylesheet(req, 'common/css/roadmap.css')
        add_script(req, 'common/js/folding.js')

        def add_milestone_link(rel, milestone):
            href = req.href.milestone(milestone.name, by=req.args.get('by'))
            add_link(req, rel, href, _('Milestone "%(name)s"',
                                       name=milestone.name))

        milestones = [m for m in Milestone.select(self.env)
                      if 'MILESTONE_VIEW' in req.perm(m.resource)]
        idx = [i for i, m in enumerate(milestones) if m.name == milestone.name]
        if idx:
            idx = idx[0]
            if idx > 0:
                add_milestone_link('first', milestones[0])
                add_milestone_link('prev', milestones[idx - 1])
            if idx < len(milestones) - 1:
                add_milestone_link('next', milestones[idx + 1])
                add_milestone_link('last', milestones[-1])
        prevnext_nav(req, _('Previous Milestone'), _('Next Milestone'),
                     _('Back to Roadmap'))

        return 'milestone_view.html', data, None
示例#57
0
class TicketSystemTestCase(unittest.TestCase):
    def setUp(self):
        self.env = EnvironmentStub(default_data=True)
        self.perm = PermissionSystem(self.env)
        self.ticket_system = TicketSystem(self.env)
        self.req = MockRequest(self.env)

    def tearDown(self):
        self.env.reset_db()

    def _get_actions(self, ticket_dict):
        ts = TicketSystem(self.env)
        ticket = insert_ticket(self.env, **ticket_dict)
        return ts.get_available_actions(self.req, Ticket(self.env, ticket.id))

    def _get_ticket_field(self, field_name):
        fields = TicketSystem(self.env).get_ticket_fields()
        return next((i for i in fields if i['name'] == field_name))

    def test_custom_field_text(self):
        self.env.config.set('ticket-custom', 'test', 'text')
        self.env.config.set('ticket-custom', 'test.label', 'Test')
        self.env.config.set('ticket-custom', 'test.value', 'Foo bar')
        self.env.config.set('ticket-custom', 'test.format', 'wiki')
        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual(
            {
                'name': 'test',
                'type': 'text',
                'label': 'Test',
                'value': 'Foo bar',
                'max_size': 0,
                'order': 0,
                'format': 'wiki',
                'custom': True
            }, fields[0])

    def test_custom_field_select(self):
        self.env.config.set('ticket-custom', 'test', 'select')
        self.env.config.set('ticket-custom', 'test.label', 'Test')
        self.env.config.set('ticket-custom', 'test.value', '1')
        self.env.config.set('ticket-custom', 'test.options', 'option1|option2')
        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual(
            {
                'name': 'test',
                'type': 'select',
                'label': 'Test',
                'value': '1',
                'options': ['option1', 'option2'],
                'order': 0,
                'custom': True
            }, fields[0])

    def test_custom_field_optional_select(self):
        self.env.config.set('ticket-custom', 'test', 'select')
        self.env.config.set('ticket-custom', 'test.label', 'Test')
        self.env.config.set('ticket-custom', 'test.value', '1')
        self.env.config.set('ticket-custom', 'test.options',
                            '|option1|option2')
        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual(
            {
                'name': 'test',
                'type': 'select',
                'label': 'Test',
                'value': '1',
                'options': ['option1', 'option2'],
                'order': 0,
                'optional': True,
                'custom': True
            }, fields[0])

    def test_custom_field_select_without_options(self):
        self.env.config.set('ticket-custom', 'test', 'select')
        self.env.config.set('ticket-custom', 'test.label', 'Test')
        self.env.config.set('ticket-custom', 'test.value', '1')
        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual(0, len(fields))

    def test_custom_field_textarea(self):
        self.env.config.set('ticket-custom', 'test', 'textarea')
        self.env.config.set('ticket-custom', 'test.label', 'Test')
        self.env.config.set('ticket-custom', 'test.value', 'Foo bar')
        self.env.config.set('ticket-custom', 'test.rows', '4')
        self.env.config.set('ticket-custom', 'test.format', 'wiki')
        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual(
            {
                'name': 'test',
                'type': 'textarea',
                'label': 'Test',
                'value': 'Foo bar',
                'height': 4,
                'order': 0,
                'max_size': 0,
                'format': 'wiki',
                'custom': True
            }, fields[0])

    def test_description_field(self):
        field = self._get_ticket_field('description')
        self.assertEqual(
            {
                'name': 'description',
                'label': 'Description',
                'type': 'textarea',
                'format': 'wiki'
            }, field)

    def test_custom_field_checkbox(self):
        def add_checkbox(name, value):
            self.env.config.set('ticket-custom', name, 'checkbox')
            self.env.config.set('ticket-custom', '%s.value' % name, value)

        add_checkbox('checkbox0', 'true')
        add_checkbox('checkbox1', 1)
        add_checkbox('checkbox2', 'enabled')
        add_checkbox('checkbox3', 0)
        add_checkbox('checkbox4', 'tru')
        add_checkbox('checkbox5', 'off')

        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual(
            {
                'name': 'checkbox0',
                'type': 'checkbox',
                'label': 'Checkbox0',
                'value': '1',
                'order': 0,
                'custom': True
            }, fields[0])
        self.assertEqual('1', fields[1]['value'])
        self.assertEqual('1', fields[2]['value'])
        self.assertEqual('0', fields[3]['value'])
        self.assertEqual('0', fields[4]['value'])
        self.assertEqual('0', fields[5]['value'])

    def test_custom_field_time(self):
        self.env.config.set('ticket-custom', 'test', 'time')
        self.env.config.set('ticket-custom', 'test.label', 'Test')
        self.env.config.set('ticket-custom', 'test.value', '')
        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual(
            {
                'name': 'test',
                'type': 'time',
                'label': 'Test',
                'value': '',
                'order': 0,
                'format': 'datetime',
                'custom': True
            }, fields[0])

    def test_custom_field_with_invalid_name(self):
        ticket_custom = self.env.config['ticket-custom']
        ticket_custom.set('_field1', 'text')
        ticket_custom.set('2field', 'text')
        ticket_custom.set('f3%^&*', 'text')
        ticket_custom.set('field4', 'text')
        ticket_custom.set('FiEld5', 'text')

        ts = TicketSystem(self.env)
        custom_fields = ts.custom_fields
        fields = ts.fields

        self.assertEqual(2, len(custom_fields))
        self.assertIsNotNone(custom_fields.by_name('field4'))
        self.assertIsNotNone(custom_fields.by_name('field5'))
        self.assertIsNotNone(fields.by_name('field4'))
        self.assertIsNotNone(fields.by_name('field5'))
        self.assertIn(('WARNING',
                       u'Invalid name for custom field: "_field1" (ignoring)'),
                      self.env.log_messages)
        self.assertIn(
            ('WARNING', u'Invalid name for custom field: "2field" (ignoring)'),
            self.env.log_messages)
        self.assertIn(
            ('WARNING', u'Invalid name for custom field: "f3%^&*" (ignoring)'),
            self.env.log_messages)

    def test_custom_field_with_reserved_name(self):
        ticket_custom = self.env.config['ticket-custom']
        ticket_custom.set('owner', 'select')
        ticket_custom.set('owner.options', 'u1|u2|u3')
        ticket_custom.set('description', 'text')

        ts = TicketSystem(self.env)
        custom_fields = ts.custom_fields

        self.assertIn(
            ('WARNING', u'Field name "owner" is a reserved name (ignoring)'),
            self.env.log_messages)
        self.assertIn(
            ('WARNING',
             u'Field name "description" is a reserved name (ignoring)'),
            self.env.log_messages)
        self.assertEqual({
            'name': 'owner',
            'label': 'Owner',
            'type': 'text'
        }, ts.fields.by_name('owner'))
        self.assertEqual(
            {
                'name': 'description',
                'label': 'Description',
                'type': 'textarea',
                'format': 'wiki'
            }, ts.fields.by_name('description'))
        self.assertIsNone(custom_fields.by_name('owner'))
        self.assertIsNone(custom_fields.by_name('description'))

    def test_custom_field_order(self):
        self.env.config.set('ticket-custom', 'test1', 'text')
        self.env.config.set('ticket-custom', 'test1.order', '2')
        self.env.config.set('ticket-custom', 'test2', 'text')
        self.env.config.set('ticket-custom', 'test2.order', '1')
        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual('test2', fields[0]['name'])
        self.assertEqual('test1', fields[1]['name'])

    def test_custom_field_label(self):
        self.env.config.set('ticket-custom', 'test_one', 'text')
        self.env.config.set('ticket-custom', 'test_two', 'text')
        self.env.config.set('ticket-custom', 'test_two.label', 'test_2')
        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual('Test one', fields[0]['label'])
        self.assertEqual('test_2', fields[1]['label'])

    def _test_custom_field_with_enum(self, name, cls):
        tktsys = TicketSystem(self.env)
        instance = cls(self.env)
        instance.name = '%s 42' % name
        instance.insert()
        self.env.config.set('ticket-custom', name, 'text')
        field = self._get_ticket_field(name)
        self.assertFalse(field.get('custom'))

        with self.env.db_transaction:
            instances = list(cls.select(self.env))
            if issubclass(cls, model.AbstractEnum):
                # delete from highest to lowest to avoid re-ordering enums
                instances.sort(reverse=True, key=lambda v: int(v.value))
            for instance in instances:
                instance.delete()
        field = self._get_ticket_field(name)
        self.assertTrue(field.get('custom'))

    def test_custom_field_type(self):
        self._test_custom_field_with_enum('type', model.Type)

    def test_custom_field_priority(self):
        self._test_custom_field_with_enum('priority', model.Priority)

    def test_custom_field_milestone(self):
        self._test_custom_field_with_enum('milestone', Milestone)

    def test_custom_field_component(self):
        self._test_custom_field_with_enum('component', model.Component)

    def test_custom_field_version(self):
        self._test_custom_field_with_enum('version', Version)

    def test_custom_field_severity(self):
        self._test_custom_field_with_enum('severity', model.Severity)

    def test_custom_field_resolution(self):
        self._test_custom_field_with_enum('resolution', model.Resolution)

    def test_available_actions_full_perms(self):
        self.perm.grant_permission('anonymous', 'TICKET_CREATE')
        self.perm.grant_permission('anonymous', 'TICKET_MODIFY')
        self.req.perm = PermissionCache(self.env)
        self.assertEqual(['leave', 'resolve', 'reassign', 'accept'],
                         self._get_actions({'status': 'new'}))
        self.assertEqual(['leave', 'resolve', 'reassign', 'accept'],
                         self._get_actions({'status': 'assigned'}))
        self.assertEqual(['leave', 'resolve', 'reassign', 'accept'],
                         self._get_actions({'status': 'accepted'}))
        self.assertEqual(['leave', 'resolve', 'reassign', 'accept'],
                         self._get_actions({'status': 'reopened'}))
        self.assertEqual(['leave', 'reopen'],
                         self._get_actions({'status': 'closed'}))

    def test_available_actions_no_perms(self):
        self.req.perm = PermissionCache(self.env)
        self.assertEqual(['leave'], self._get_actions({'status': 'new'}))
        self.assertEqual(['leave'], self._get_actions({'status': 'assigned'}))
        self.assertEqual(['leave'], self._get_actions({'status': 'accepted'}))
        self.assertEqual(['leave'], self._get_actions({'status': 'reopened'}))
        self.assertEqual(['leave'], self._get_actions({'status': 'closed'}))

    def test_available_actions_create_only(self):
        self.perm.grant_permission('anonymous', 'TICKET_CREATE')
        self.req.perm = PermissionCache(self.env)
        self.assertEqual(['leave'], self._get_actions({'status': 'new'}))
        self.assertEqual(['leave'], self._get_actions({'status': 'assigned'}))
        self.assertEqual(['leave'], self._get_actions({'status': 'accepted'}))
        self.assertEqual(['leave'], self._get_actions({'status': 'reopened'}))
        self.assertEqual(['leave', 'reopen'],
                         self._get_actions({'status': 'closed'}))

    def test_available_actions_chgprop_only(self):
        # CHGPROP is not enough for changing a ticket's state (#3289)
        self.perm.grant_permission('anonymous', 'TICKET_CHGPROP')
        self.req.perm = PermissionCache(self.env)
        self.assertEqual(['leave'], self._get_actions({'status': 'new'}))
        self.assertEqual(['leave'], self._get_actions({'status': 'assigned'}))
        self.assertEqual(['leave'], self._get_actions({'status': 'accepted'}))
        self.assertEqual(['leave'], self._get_actions({'status': 'reopened'}))
        self.assertEqual(['leave'], self._get_actions({'status': 'closed'}))

    def test_get_allowed_owners_restrict_owner_false(self):
        self.env.config.set('ticket', 'restrict_owner', False)
        self.assertIsNone(self.ticket_system.get_allowed_owners())

    def test_get_allowed_owners_restrict_owner_true(self):
        self.env.config.set('ticket', 'restrict_owner', True)
        self.env.insert_users([('user3', None, None), ('user1', None, None)])
        self.perm.grant_permission('user4', 'TICKET_MODIFY')
        self.perm.grant_permission('user3', 'TICKET_MODIFY')
        self.perm.grant_permission('user2', 'TICKET_VIEW')
        self.perm.grant_permission('user1', 'TICKET_MODIFY')
        self.assertEqual(['user1', 'user3'],
                         self.ticket_system.get_allowed_owners())

    def test_get_ticket_fields_version_rename(self):
        """Cached ticket fields are updated when version is renamed."""
        fields = self.ticket_system.get_ticket_fields()
        version_field = self._get_ticket_field('version')
        v2 = Version(self.env, '2.0')
        v2.name = '0.0'
        v2.update()
        updated_fields = self.ticket_system.get_ticket_fields()
        updated_version_field = self._get_ticket_field('version')

        self.assertNotEqual(fields, updated_fields)
        self.assertEqual(['2.0', '1.0'], version_field['options'])
        self.assertEqual(['1.0', '0.0'], updated_version_field['options'])

    def test_get_ticket_fields_version_update_time(self):
        """Cached ticket fields are updated when version release time
        is changed.
        """
        fields = self.ticket_system.get_ticket_fields()
        version_field = self._get_ticket_field('version')
        v1 = Version(self.env, '1.0')
        v1.time = datetime_now(utc)
        v2 = Version(self.env, '2.0')
        v2.time = v1.time - timedelta(seconds=1)

        v1.update()
        v2.update()
        updated_fields = self.ticket_system.get_ticket_fields()
        updated_version_field = self._get_ticket_field('version')

        self.assertNotEqual(fields, updated_fields)
        self.assertEqual(['2.0', '1.0'], version_field['options'])
        self.assertEqual(['1.0', '2.0'], updated_version_field['options'])

    def test_get_ticket_fields_milestone_rename(self):
        """Cached ticket fields are updated when milestone is renamed."""
        fields = self.ticket_system.get_ticket_fields()
        milestone_field = self._get_ticket_field('milestone')
        m2 = Milestone(self.env, 'milestone2')
        m2.name = 'milestone5'

        m2.update()
        updated_fields = self.ticket_system.get_ticket_fields()
        updated_milestone_field = self._get_ticket_field('milestone')

        self.assertNotEqual(fields, updated_fields)
        self.assertEqual(
            ['milestone1', 'milestone2', 'milestone3', 'milestone4'],
            milestone_field['options'])
        self.assertEqual(
            ['milestone1', 'milestone3', 'milestone4', 'milestone5'],
            updated_milestone_field['options'])

    def test_get_ticket_fields_milestone_update_completed(self):
        """Cached ticket fields are updated when milestone is completed
        date is changed.
        """
        fields = self.ticket_system.get_ticket_fields()
        milestone_field = self._get_ticket_field('milestone')
        m2 = Milestone(self.env, 'milestone2')
        m2.completed = datetime_now(utc)

        m2.update()
        updated_fields = self.ticket_system.get_ticket_fields()
        updated_milestone_field = self._get_ticket_field('milestone')

        self.assertNotEqual(fields, updated_fields)
        self.assertEqual(
            ['milestone1', 'milestone2', 'milestone3', 'milestone4'],
            milestone_field['options'])
        self.assertEqual(
            ['milestone2', 'milestone1', 'milestone3', 'milestone4'],
            updated_milestone_field['options'])

    def test_get_ticket_fields_milestone_update_due(self):
        """Cached ticket fields are updated when milestone due date is
        changed.
        """
        fields = self.ticket_system.get_ticket_fields()
        milestone_field = self._get_ticket_field('milestone')
        m2 = Milestone(self.env, 'milestone2')
        m2.due = datetime_now(utc)

        m2.update()
        updated_fields = self.ticket_system.get_ticket_fields()
        updated_milestone_field = self._get_ticket_field('milestone')

        self.assertNotEqual(fields, updated_fields)
        self.assertEqual(
            ['milestone1', 'milestone2', 'milestone3', 'milestone4'],
            milestone_field['options'])
        self.assertEqual(
            ['milestone2', 'milestone1', 'milestone3', 'milestone4'],
            updated_milestone_field['options'])

    def test_resource_exists_valid_resource_id(self):
        insert_ticket(self.env)
        r1 = Resource('ticket', 1)
        r2 = Resource('ticket', 2)

        self.assertTrue(self.ticket_system.resource_exists(r1))
        self.assertFalse(self.ticket_system.resource_exists(r2))

    def test_resource_exists_invalid_resource_id(self):
        """Exception is trapped from resource with invalid id."""
        r1 = Resource('ticket', None)
        r2 = Resource('ticket', 'abc')
        r3 = Resource('ticket', '2.')
        r4 = Resource('ticket', r2)

        self.assertFalse(self.ticket_system.resource_exists(r1))
        self.assertFalse(self.ticket_system.resource_exists(r2))
        self.assertFalse(self.ticket_system.resource_exists(r3))
        self.assertFalse(self.ticket_system.resource_exists(r4))
示例#58
0
    def render_ticket_action_control(self, req, ticket, action):

        self.log.debug('render_ticket_action_control: action "%s"', action)

        this_action = self.actions[action]
        status = this_action['newstate']
        operations = this_action['operations']
        current_owner = ticket._old.get('owner', ticket['owner'])
        author = get_reporter_id(req, 'author')
        author_info = partial(Chrome(self.env).authorinfo,
                              req,
                              resource=ticket.resource)
        format_author = partial(Chrome(self.env).format_author,
                                req,
                                resource=ticket.resource)
        formatted_current_owner = author_info(current_owner)
        exists = ticket._old.get('status', ticket['status']) is not None

        control = []  # default to nothing
        hints = []
        if 'reset_workflow' in operations:
            control.append(_("from invalid state"))
            hints.append(_("Current state no longer exists"))
        if 'del_owner' in operations:
            hints.append(_("The ticket will be disowned"))
        if 'set_owner' in operations or 'may_set_owner' in operations:
            if 'set_owner' in this_action:
                owners = self._to_users(this_action['set_owner'], ticket)
            elif self.config.getbool('ticket', 'restrict_owner'):
                perm = PermissionSystem(self.env)
                owners = perm.get_users_with_permission('TICKET_MODIFY')
                owners = [
                    user for user in owners
                    if 'TICKET_MODIFY' in PermissionCache(
                        self.env, user, ticket.resource)
                ]
                owners = sorted(owners)
            else:
                owners = None

            if 'set_owner' in operations:
                default_owner = author
            elif 'may_set_owner' in operations:
                if not exists:
                    default_owner = TicketSystem(self.env).default_owner
                else:
                    default_owner = ticket._old.get('owner', ticket['owner']
                                                    or None)
                if owners is not None and default_owner not in owners:
                    owners.insert(0, default_owner)
            else:
                # Protect against future modification for case that another
                # operation is added to the outer conditional
                raise AssertionError(operations)

            id = 'action_%s_reassign_owner' % action

            if not owners:
                owner = req.args.get(id, default_owner)
                control.append(
                    tag_("to %(owner)s",
                         owner=tag.input(type='text',
                                         id=id,
                                         name=id,
                                         value=owner)))
                if not exists or current_owner is None:
                    hints.append(_("The owner will be the specified user"))
                else:
                    hints.append(
                        tag_(
                            "The owner will be changed from "
                            "%(current_owner)s to the specified "
                            "user",
                            current_owner=formatted_current_owner))
            elif len(owners) == 1:
                owner = tag.input(type='hidden',
                                  id=id,
                                  name=id,
                                  value=owners[0])
                formatted_new_owner = author_info(owners[0])
                control.append(
                    tag_("to %(owner)s", owner=tag(formatted_new_owner,
                                                   owner)))
                if not exists or current_owner is None:
                    hints.append(
                        tag_("The owner will be %(new_owner)s",
                             new_owner=formatted_new_owner))
                elif ticket['owner'] != owners[0]:
                    hints.append(
                        tag_(
                            "The owner will be changed from "
                            "%(current_owner)s to %(new_owner)s",
                            current_owner=formatted_current_owner,
                            new_owner=formatted_new_owner))
            else:
                selected_owner = req.args.get(id, default_owner)
                control.append(
                    tag_("to %(owner)s",
                         owner=tag.select([
                             tag.option(
                                 label,
                                 value=value if value is not None else '',
                                 selected=(value == selected_owner or None))
                             for label, value in sorted(
                                 (format_author(owner), owner)
                                 for owner in owners)
                         ],
                                          id=id,
                                          name=id)))
                if not exists or current_owner is None:
                    hints.append(_("The owner will be the selected user"))
                else:
                    hints.append(
                        tag_(
                            "The owner will be changed from "
                            "%(current_owner)s to the selected user",
                            current_owner=formatted_current_owner))
        elif 'set_owner_to_self' in operations and \
                ticket._old.get('owner', ticket['owner']) != author:
            formatted_author = author_info(author)
            if not exists or current_owner is None:
                hints.append(
                    tag_("The owner will be %(new_owner)s",
                         new_owner=formatted_author))
            else:
                hints.append(
                    tag_(
                        "The owner will be changed from "
                        "%(current_owner)s to %(new_owner)s",
                        current_owner=formatted_current_owner,
                        new_owner=formatted_author))
        if 'set_resolution' in operations:
            if 'set_resolution' in this_action:
                resolutions = this_action['set_resolution']
            else:
                resolutions = [r.name for r in Resolution.select(self.env)]
            if not resolutions:
                raise TracError(
                    _("Your workflow attempts to set a resolution "
                      "but none is defined (configuration issue, "
                      "please contact your Trac admin)."))
            id = 'action_%s_resolve_resolution' % action
            if len(resolutions) == 1:
                resolution = tag.input(type='hidden',
                                       id=id,
                                       name=id,
                                       value=resolutions[0])
                control.append(
                    tag_("as %(resolution)s",
                         resolution=tag(resolutions[0], resolution)))
                hints.append(
                    tag_("The resolution will be set to %(name)s",
                         name=resolutions[0]))
            else:
                selected_option = req.args.get(
                    id,
                    TicketSystem(self.env).default_resolution)
                control.append(
                    tag_(
                        "as %(resolution)s",
                        resolution=tag.select([
                            tag.option(x,
                                       value=x,
                                       selected=(x == selected_option or None))
                            for x in resolutions
                        ],
                                              id=id,
                                              name=id)))
                hints.append(_("The resolution will be set"))
        if 'del_resolution' in operations:
            hints.append(_("The resolution will be deleted"))
        if 'leave_status' in operations:
            control.append(
                tag_("as %(status)s",
                     status=ticket._old.get('status', ticket['status'])))
            if len(operations) == 1:
                hints.append(
                    tag_("The owner will remain %(current_owner)s",
                         current_owner=formatted_current_owner) if
                    current_owner else _("The ticket will remain with no owner"
                                         ))
        else:
            if ticket['status'] is None:
                hints.append(tag_("The status will be '%(name)s'",
                                  name=status))
            elif status != '*':
                hints.append(
                    tag_("Next status will be '%(name)s'", name=status))
        return (this_action['label'], tag(separated(control, ' ')),
                tag(separated(hints, '. ', '.') if hints else ''))
示例#59
0
class SetOwnerToSelfAttributeTestCase(unittest.TestCase):
    def setUp(self):
        self.env = EnvironmentStub(default_data=True)
        self.ctlr = TicketSystem(self.env).action_controllers[0]
        self.req = MockRequest(self.env, authname='user1')
        ps = PermissionSystem(self.env)
        for user in ('user1', 'user2'):
            ps.grant_permission(user, 'TICKET_MODIFY')
        self.env.insert_users([('user1', 'User 1', None),
                               ('user2', 'User 2', None)])

    def _get_ticket_actions(self, req, ticket):
        return [
            action[1] for action in self.ctlr.get_ticket_actions(req, ticket)
        ]

    def _reload_workflow(self):
        self.ctlr.actions = self.ctlr.get_all_actions()

    def _insert_ticket(self, status, owner, resolution=None):
        ticket = Ticket(self.env)
        ticket['status'] = status
        ticket['owner'] = owner
        if resolution:
            ticket['resolution'] = resolution
        ticket.insert()
        return ticket

    def test_owner_is_other(self):
        """Ticket owner is not auth'ed user.

        The workflow action is shown when the state will be changed by
        the action.
        """
        ticket = self._insert_ticket('accepted', 'user2')
        args = self.req, ticket, 'accept'

        label, control, hints = self.ctlr.render_ticket_action_control(*args)
        ticket_actions = self._get_ticket_actions(*args[0:2])

        self.assertIn('accept', ticket_actions)
        self.assertEqual(label, 'accept')
        self.assertEqual('', unicode(control))
        self.assertEqual(
            'The owner will be changed from '
            '<span class="trac-author">User 2</span> to '
            '<span class="trac-author-user">User 1</span>.', unicode(hints))

    def test_owner_is_self_and_state_change(self):
        """Ticket owner is auth'ed user with state change.

        The workflow action is shown when the state will be changed by the
        action, even when the ticket owner is the authenticated user.
        """
        ticket = self._insert_ticket('new', 'user1')
        args = self.req, ticket, 'accept'

        label, control, hints = self.ctlr.render_ticket_action_control(*args)
        ticket_actions = self._get_ticket_actions(*args[0:2])

        self.assertIn('accept', ticket_actions)
        self.assertEqual(label, 'accept')
        self.assertEqual('', unicode(control))
        self.assertEqual(
            'The owner will remain <span class="trac-author-user">'
            'User 1</span>.', unicode(hints))

    def test_owner_is_self_and_no_state_change(self):
        """Ticket owner is the auth'ed user and no state change.

        The ticket action is not in the list of available actions
        when the state will not be changed by the action and the ticket
        owner is the authenticated user.
        """
        ticket = self._insert_ticket('accepted', 'user1')
        args = self.req, ticket, 'accept'

        ticket_actions = self._get_ticket_actions(*args[0:2])

        self.assertNotIn('accept', ticket_actions)

    def test_owner_is_self_state_change_and_multiple_operations(self):
        """Ticket owner is auth'ed user, state change and multiple ops.

        The set_owner_to_self workflow hint is shown when the ticket status
        is changed by the action, even when the ticket owner is the
        authenticated user.
        """
        ticket = self._insert_ticket('new', 'user1')
        workflow = self.env.config['ticket-workflow']
        workflow.set('resolve_as_owner', '* -> closed')
        workflow.set('resolve_as_owner.operations',
                     'set_owner_to_self, set_resolution')
        workflow.set('resolve_as_owner.set_resolution', 'fixed')
        self._reload_workflow()
        args = self.req, ticket, 'resolve_as_owner'

        label, control, hints = self.ctlr.render_ticket_action_control(*args)
        ticket_actions = self._get_ticket_actions(*args[0:2])

        self.assertIn('resolve_as_owner', ticket_actions)
        self.assertEqual(label, 'resolve as owner')
        self.assertEqual(
            'as fixed<input id="action_resolve_as_owner_resolve_resolution" '
            'name="action_resolve_as_owner_resolve_resolution" type="hidden" '
            'value="fixed" />', unicode(control))
        self.assertEqual(
            'The owner will remain <span class="trac-author-user">User 1'
            '</span>. The resolution will be set to fixed.', unicode(hints))

    def test_owner_is_self_no_state_change_and_multiple_operations(self):
        """Ticket owner is auth'ed user, no state change and multiple ops.

        The set_owner_to_self workflow hint is not shown when the ticket
        state is not changed by the action and the ticket owner is the
        authenticated user.
        """
        ticket = self._insert_ticket('closed', 'user1', 'fixed')
        workflow = self.env.config['ticket-workflow']
        workflow.set('fix_resolution', 'closed -> closed')
        workflow.set('fix_resolution.operations',
                     'set_owner_to_self, set_resolution')
        workflow.set('fix_resolution.set_resolution', 'invalid')
        self._reload_workflow()
        args = self.req, ticket, 'fix_resolution'

        label, control, hints = self.ctlr.render_ticket_action_control(*args)
        ticket_actions = self._get_ticket_actions(*args[0:2])

        self.assertIn('fix_resolution', ticket_actions)
        self.assertEqual(label, 'fix resolution')
        self.assertEqual(
            'as invalid<input id="action_fix_resolution_resolve_resolution" '
            'name="action_fix_resolution_resolve_resolution" type="hidden" '
            'value="invalid" />', unicode(control))
        self.assertEqual('The resolution will be set to invalid.',
                         unicode(hints))
示例#60
0
    def get_ticket_group_stats(self, ticket_ids):
        total_cnt = len(ticket_ids)
        all_statuses = set(TicketSystem(self.env).get_all_status())
        status_cnt = {}
        for s in all_statuses:
            status_cnt[s] = 0
        if total_cnt:
            for status, count in self.env.db_query("""
                    SELECT status, count(status) FROM ticket
                    WHERE id IN (%s) GROUP BY status
                    """ % ",".join(str(x) for x in sorted(ticket_ids))):
                status_cnt[status] = count

        stat = TicketGroupStats(_('ticket status'), _('tickets'))
        remaining_statuses = set(all_statuses)
        groups =  self._get_ticket_groups()
        catch_all_group = None
        # we need to go through the groups twice, so that the catch up group
        # doesn't need to be the last one in the sequence
        for group in groups:
            status_str = group['status'].strip()
            if status_str == '*':
                if catch_all_group:
                    raise TracError(_(
                        "'%(group1)s' and '%(group2)s' milestone groups "
                        "both are declared to be \"catch-all\" groups. "
                        "Please check your configuration.",
                        group1=group['name'], group2=catch_all_group['name']))
                catch_all_group = group
            else:
                group_statuses = set([s.strip()
                                      for s in status_str.split(',')]) \
                                      & all_statuses
                if group_statuses - remaining_statuses:
                    raise TracError(_(
                        "'%(groupname)s' milestone group reused status "
                        "'%(status)s' already taken by other groups. "
                        "Please check your configuration.",
                        groupname=group['name'],
                        status=', '.join(group_statuses - remaining_statuses)))
                else:
                    remaining_statuses -= group_statuses
                group['statuses'] = group_statuses
        if catch_all_group:
            catch_all_group['statuses'] = remaining_statuses
        for group in groups:
            group_cnt = 0
            query_args = {}
            for s, cnt in status_cnt.iteritems():
                if s in group['statuses']:
                    group_cnt += cnt
                    query_args.setdefault('status', []).append(s)
            for arg in [kv for kv in group.get('query_args', '').split(',')
                        if '=' in kv]:
                k, v = [a.strip() for a in arg.split('=', 1)]
                query_args.setdefault(k, []).append(v)
            stat.add_interval(group.get('label', group['name']),
                              group_cnt, query_args,
                              group.get('css_class', group['name']),
                              as_bool(group.get('overall_completion')))
        stat.refresh_calcs()
        return stat