Ejemplo n.º 1
0
class TicketAdminPanel(Component):

    implements(IAdminPanelProvider, IAdminCommandProvider)

    abstract = True

    _label = (N_('(Undefined)'), N_('(Undefined)'))

    # i18n note: use gettext() whenever refering to the above as text labels,
    #            and don't use it whenever using them as field names (after
    #            a call to `.lower()`)

    # IAdminPanelProvider methods

    def get_admin_panels(self, req):
        if 'TICKET_ADMIN' in req.perm('admin', 'ticket/' + self._type):
            # in global scope show only products
            # in local scope everything but products
            parent = getattr(self.env, 'parent', None)
            if (parent is None and self._type == 'products') or \
               (parent and self._type != 'products'):
                yield ('ticket', _('Ticket System'), self._type,
                       gettext(self._label[1]))

    def render_admin_panel(self, req, cat, page, version):
        # Trap AssertionErrors and convert them to TracErrors
        try:
            return self._render_admin_panel(req, cat, page, version)
        except AssertionError, e:
            raise TracError(e)
Ejemplo n.º 2
0
class TicketAdminPanel(Component):

    implements(IAdminPanelProvider, IAdminCommandProvider)

    abstract = True

    _label = (N_('(Undefined)'), N_('(Undefined)'))

    # i18n note: use gettext() whenever refering to the above as text labels,
    #            and don't use it whenever using them as field names (after
    #            a call to `.lower()`)

    # IAdminPanelProvider methods

    def get_admin_panels(self, req):
        if 'TICKET_ADMIN' in req.perm:
            yield ('ticket', _('Ticket System'), self._type,
                   gettext(self._label[1]))

    def render_admin_panel(self, req, cat, page, version):
        req.perm.require('TICKET_ADMIN')
        # Trap AssertionErrors and convert them to TracErrors
        try:
            return self._render_admin_panel(req, cat, page, version)
        except AssertionError, e:
            raise TracError(e)
Ejemplo n.º 3
0
 def get_radio_fields(self):
     """Default radio fields"""
     from trac.ticket import model
     radios = [(20, {'name': 'status', 'label': N_('Status'),
                     'cls': model.Status}),
               (80, {'name': 'resolution', 'label': N_('Resolution'), 
                     'cls': model.Resolution})]
     return radios
Ejemplo n.º 4
0
class TicketTypeAdminPanel(AbstractEnumAdminPanel):
    _type = 'type'
    _enum_cls = model.Type
    _label = N_("Ticket Type"), N_("Ticket Types")

    _command_type = 'ticket_type'
    _command_help = {
        'list': 'Show possible %s',
        'add': 'Add a %s',
        'change': 'Change a %s',
        'remove': 'Remove a %s',
        'order': 'Move a %s up or down in the list',
    }
Ejemplo n.º 5
0
class PermissionError(TracBaseError):
    """Insufficient permissions to perform the operation."""

    title = N_("Forbidden")

    def __init__(self, action=None, resource=None, env=None, msg=None):
        self.action = action
        self.resource = resource
        self.env = env
        if self.action:
            if self.resource and self.resource.id:
                msg = _(
                    "%(perm)s privileges are required to perform "
                    "this operation on %(resource)s. You don't have the "
                    "required permissions.",
                    perm=self.action,
                    resource=get_resource_name(self.env, self.resource))
            else:
                msg = _(
                    "%(perm)s privileges are required to perform this "
                    "operation. You don't have the required "
                    "permissions.",
                    perm=self.action)
        elif msg is None:
            msg = _("Insufficient privileges to perform this operation.")
        super(PermissionError, self).__init__(msg)

    @property
    def message(self):
        return self.args[0]
Ejemplo n.º 6
0
class PermissionError(StandardError):
    """Insufficient permissions to perform the operation.

    :since 1.0.5: the `msg` attribute is deprecated and will be removed in
                  1.3.1. Use the `message` property instead.
    """

    title = N_("Forbidden")

    def __init__(self, action=None, resource=None, env=None, msg=None):
        self.action = action
        self.resource = resource
        self.env = env
        if self.action:
            if self.resource:
                msg = _(
                    "%(perm)s privileges are required to perform "
                    "this operation on %(resource)s. You don't have the "
                    "required permissions.",
                    perm=self.action,
                    resource=get_resource_name(self.env, self.resource))
            else:
                msg = _(
                    "%(perm)s privileges are required to perform this "
                    "operation. You don't have the required "
                    "permissions.",
                    perm=self.action)
        elif msg is None:
            msg = _("Insufficient privileges to perform this operation.")
        self.msg = msg
        super(PermissionError, self).__init__(msg)

    @property
    def message(self):
        return self.args[0]
Ejemplo n.º 7
0
class TicketAdminPanel(Component):

    implements(IAdminPanelProvider, IAdminCommandProvider)

    abstract = True

    _type = 'undefined'
    _label = N_("(Undefined)"), N_("(Undefined)")
    _view_perms = ['TICKET_ADMIN']

    # i18n note: use gettext() whenever referring to the above as text labels,
    #            and don't use it whenever using them as field names (after
    #            a call to `.lower()`)

    # IAdminPanelProvider methods

    def get_admin_panels(self, req):
        if all(perm in req.perm('admin', 'ticket/' + self._type)
               for perm in self._view_perms):
            yield ('ticket', _('Ticket System'), self._type,
                   gettext(self._label[1]))

    def render_admin_panel(self, req, cat, page, path_info):
        # Trap AssertionErrors and convert them to TracErrors
        try:
            return self._render_admin_panel(req, cat, page, path_info)
        except AssertionError as e:
            raise TracError(e)

    def _save_config(self, req):
        """Try to save the config, and display either a success notice or a
        failure warning.
        """
        try:
            self.config.save()
        except EnvironmentError as e:
            self.log.error("Error writing to trac.ini: %s",
                           exception_to_unicode(e))
            add_warning(req, _("Error writing to trac.ini, make sure it is "
                               "writable by the web server. Your changes "
                               "have not been saved."))
        else:
            add_notice(req, _("Your changes have been saved."))

    def _render_admin_panel(self, req, cat, page, path_info):
        raise NotImplemented("Class inheriting from TicketAdminPanel has not "
                             "implemented the _render_admin_panel method.")
Ejemplo n.º 8
0
 def get_select_fields(self):
     """Product select fields"""
     return [(35, {
         'name': 'product',
         'label': N_('Product'),
         'cls': Product,
         'optional': True
     })]
Ejemplo n.º 9
0
class ConfigurationError(TracError):
    """Exception raised when a value in the configuration file is not valid."""
    title = N_("Configuration Error")

    def __init__(self, message=None, title=None, show_traceback=False):
        if message is None:
            message = _("Look in the Trac log for more information.")
        super().__init__(message, title, show_traceback)
Ejemplo n.º 10
0
class TracNotImplementedError(TracError, NotImplementedError):
    """Raised when a `NotImplementedError` is trapped.

    This exception is for internal use and should not be raised by
    plugins. Plugins should raise `NotImplementedError`.

    :since: 1.0.11
    """

    title = N_("Not Implemented Error")
Ejemplo n.º 11
0
 def get_select_fields(self):
     """Default select and radio fields"""
     from trac.ticket import model
     selects = [(10, {'name': 'type', 'label': N_('Type'), 
                      'cls': model.Type}),
                (30, {'name':'priority', 'label': N_('Priority'), 
                      'cls': model.Priority}),
                (40, {'name': 'milestone', 'label': N_('Milestone'), 
                      'cls': model.Milestone, 'optional': True}),
                (50, {'name': 'component', 'label': N_('Component'), 
                      'cls': model.Component}),
                (60, {'name': 'version', 'label': N_('Version'), 
                      'cls': model.Version, 'optional': True}),
                (70, {'name': 'severity', 'label': N_('Severity'), 
                      'cls': model.Severity})]
     return selects
Ejemplo n.º 12
0
class PermissionError(TracBaseError, StandardError):
    """Insufficient permissions to complete the operation"""

    title = N_("Forbidden")

    def __init__(self, action=None, resource=None, env=None, msg=None):
        self.action = action
        self.resource = resource
        self.env = env
        if self.action:
            if self.resource:
                msg = _("%(perm)s privileges are required to perform "
                        "this operation on %(resource)s. You don't have the "
                        "required permissions.",
                        perm=self.action,
                        resource=get_resource_name(self.env, self.resource))
            else:
                msg = _("%(perm)s privileges are required to perform this "
                        "operation. You don't have the required "
                        "permissions.", perm=self.action)
        elif msg is None:
            msg = _("Insufficient privileges to perform this operation.")
        self.msg = msg
        StandardError.__init__(self, msg)
Ejemplo n.º 13
0
class VersionAdminPanel(TicketAdminPanel):

    _type = 'versions'
    _label = N_("Version"), N_("Versions")

    # TicketAdminPanel methods

    def _render_admin_panel(self, req, cat, page, version):
        # Detail view?
        if version:
            ver = model.Version(self.env, version)
            if req.method == 'POST':
                if req.args.get('save'):
                    ver.name = req.args.get('name')
                    if req.args.get('time'):
                        ver.time = user_time(req, parse_date,
                                             req.args.get('time'),
                                             hint='datetime')
                    else:
                        ver.time = None  # unset
                    ver.description = req.args.get('description')
                    ver.update()
                    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 = Chrome(self.env)
            chrome.add_wiki_toolbars(req)
            chrome.add_auto_preview(req)
            data = {'view': 'detail', 'version': ver}

        else:
            default = self.config.get('ticket', 'default_version')
            if req.method == 'POST':
                # Add Version
                if req.args.get('add') and req.args.get('name'):
                    ver = model.Version(self.env)
                    ver.name = req.args.get('name')
                    if req.args.get('time'):
                        ver.time = user_time(req, parse_date,
                                             req.args.get('time'),
                                             hint='datetime')
                    ver.insert()
                    add_notice(req, _('The version "%(name)s" has been '
                                      'added.', name=ver.name))
                    req.redirect(req.href.admin(cat, page))

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

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

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

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

        Chrome(self.env).add_jquery_ui(req)
        data.update({'datetime_hint': get_datetime_format_hint(req.lc_time)})

        return 'admin_versions.html', data

    # IAdminCommandProvider methods

    def get_admin_commands(self):
        locale = get_console_locale(self.env)
        hints = {
            'datetime': get_datetime_format_hint(locale),
            'iso8601': get_datetime_format_hint('iso8601'),
        }
        yield ('version list', '',
               "Show versions",
               None, self._do_list)
        yield ('version add', '<name> [time]',
               "Add version",
               None, self._do_add)
        yield ('version rename', '<name> <newname>',
               "Rename version",
               self._complete_name, self._do_rename)
        yield ('version remove', '<name>',
               "Remove version",
               self._complete_name, self._do_remove)
        yield ('version time', '<name> <time>',
               """Set version date

               The <time> must be specified in the "%(datetime)s"
               or "%(iso8601)s" (ISO 8601) format.
               Alternatively, "now" can be used to set the version date to
               the current time. To remove the date from a version, specify
               an empty string ("").
               """ % hints,
               self._complete_name, self._do_time)

    def get_version_list(self):
        return [v.name for v in model.Version.select(self.env)]

    def _complete_name(self, args):
        if len(args) == 1:
            return self.get_version_list()

    def _do_list(self):
        print_table([(v.name,
                      format_date(v.time, console_date_format)
                      if v.time else None)
                     for v in model.Version.select(self.env)],
                    [_("Name"), _("Time")])

    def _do_add(self, name, time=None):
        version = model.Version(self.env)
        version.name = name
        version.time = parse_date(time, hint='datetime',
                                  locale=get_console_locale(self.env)) \
                       if time else None
        version.insert()

    def _do_rename(self, name, newname):
        version = model.Version(self.env, name)
        version.name = newname
        version.update()

    def _do_remove(self, name):
        model.Version(self.env, name).delete()

    def _do_time(self, name, time):
        version = model.Version(self.env, name)
        version.time = parse_date(time, hint='datetime',
                                  locale=get_console_locale(self.env)) \
                       if time else None
        version.update()
Ejemplo n.º 14
0
class MilestoneAdminPanel(TicketAdminPanel):

    _type = 'milestones'
    _label = N_("Milestone"), N_("Milestones")
    _view_perms = TicketAdminPanel._view_perms + ['MILESTONE_VIEW']

    # TicketAdminPanel methods

    def _render_admin_panel(self, req, cat, page, milestone_name):
        perm_cache = req.perm('admin', 'ticket/' + self._type)

        # Detail view
        if milestone_name:
            milestone = model.Milestone(self.env, milestone_name)
            milestone_module = MilestoneModule(self.env)
            if req.method == 'POST':
                if 'save' in req.args:
                    perm_cache.require('MILESTONE_MODIFY')
                    if milestone_module.save_milestone(req, milestone):
                        req.redirect(req.href.admin(cat, page))

                elif 'cancel' in req.args:
                    req.redirect(req.href.admin(cat, page))

            chrome = Chrome(self.env)
            chrome.add_wiki_toolbars(req)
            chrome.add_auto_preview(req)
            data = {'view': 'detail',
                    'milestone': milestone,
                    'default_due': milestone_module.get_default_due(req)}

        # List view
        else:
            ticket_default = self.config.get('ticket', 'default_milestone')
            retarget_default = self.config.get('milestone',
                                               'default_retarget_to')
            if req.method == 'POST':

                # Add milestone
                if 'add' in req.args and req.args.get('name'):
                    perm_cache.require('MILESTONE_CREATE')
                    name = req.args.get('name')
                    try:
                        model.Milestone(self.env, name=name)
                    except ResourceNotFound:
                        milestone = model.Milestone(self.env)
                        milestone.name = name
                        MilestoneModule(self.env).save_milestone(req,
                                                                 milestone)
                        req.redirect(req.href.admin(cat, page))
                    else:
                        add_warning(req, _('Milestone "%(name)s" already '
                                           'exists, please choose another '
                                           'name.', name=name))

                # Remove milestone
                elif 'remove' in req.args:
                    save = False
                    perm_cache.require('MILESTONE_DELETE')
                    sel = req.args.getlist('sel')
                    if not sel:
                        raise TracError(_("No milestone selected"))
                    with self.env.db_transaction:
                        for name in sel:
                            milestone = model.Milestone(self.env, name)
                            milestone.move_tickets(None, req.authname,
                                                   "Milestone deleted")
                            milestone.delete()
                            if name == ticket_default:
                                self.config.set('ticket',
                                                'default_milestone', '')
                                save = True
                            if name == retarget_default:
                                self.config.set('milestone',
                                                'default_retarget_to', '')
                                save = True
                    if save:
                        self._save_config(req)
                    add_notice(req, _("The selected milestones have been "
                                      "removed."))
                    req.redirect(req.href.admin(cat, page))

                # Set default milestone
                elif 'apply' in req.args:
                    save = False
                    name = req.args.get('ticket_default')
                    if name and name != ticket_default:
                        self.log.info("Setting default ticket "
                                      "milestone to %s", name)
                        self.config.set('ticket', 'default_milestone', name)
                        save = True
                    retarget = req.args.get('retarget_default')
                    if retarget and retarget != retarget_default:
                        self.log.info("Setting default retargeting "
                                      "milestone to %s", retarget)
                        self.config.set('milestone', 'default_retarget_to',
                                        retarget)
                        save = True
                    if save:
                        self._save_config(req)
                        req.redirect(req.href.admin(cat, page))

                # Clear default milestone
                elif 'clear' in req.args:
                    self.log.info("Clearing default ticket milestone "
                                  "and default retarget milestone")
                    self.config.set('ticket', 'default_milestone', '')
                    self.config.set('milestone', 'default_retarget_to', '')
                    self._save_config(req)
                    req.redirect(req.href.admin(cat, page))

            # Get ticket count
            num_tickets = dict(self.env.db_query("""
                    SELECT milestone, COUNT(milestone) FROM ticket
                    WHERE milestone != ''
                    GROUP BY milestone
                """))
            query_href = lambda name: req.href.query([('group', 'status'),
                                                      ('milestone', name)])

            data = {'view': 'list',
                    'milestones': model.Milestone.select(self.env),
                    'query_href': query_href,
                    'num_tickets': lambda m: num_tickets.get(m.name, 0),
                    'ticket_default': ticket_default,
                    'retarget_default': retarget_default}

        Chrome(self.env).add_jquery_ui(req)

        data.update({
            'datetime_hint': get_datetime_format_hint(req.lc_time),
        })
        return 'admin_milestones.html', data

    # IAdminCommandProvider methods

    def get_admin_commands(self):
        locale = get_console_locale(self.env)
        hints = {
            'datetime': get_datetime_format_hint(locale),
            'iso8601': get_datetime_format_hint('iso8601'),
        }
        yield ('milestone list', '',
               "Show milestones",
               None, self._do_list)
        yield ('milestone add', '<name> [due]',
               "Add milestone",
               None, self._do_add)
        yield ('milestone rename', '<name> <newname>',
               "Rename milestone",
               self._complete_name, self._do_rename)
        yield ('milestone due', '<name> <due>',
               """Set milestone due date

               The <due> date must be specified in the "%(datetime)s"
               or "%(iso8601)s" (ISO 8601) format.
               Alternatively, "now" can be used to set the due date to the
               current time. To remove the due date from a milestone, specify
               an empty string ("").
               """ % hints,
               self._complete_name, self._do_due)
        yield ('milestone completed', '<name> <completed>',
               """Set milestone complete date

               The <completed> date must be specified in the "%(datetime)s"
               or "%(iso8601)s" (ISO 8601) format.
               Alternatively, "now" can be used to set the completion date to
               the current time. To remove the completion date from a
               milestone, specify an empty string ("").
               """ % hints,
               self._complete_name, self._do_completed)
        yield ('milestone remove', '<name>',
               "Remove milestone",
               self._complete_name, self._do_remove)

    def get_milestone_list(self):
        return [m.name for m in model.Milestone.select(self.env)]

    def _complete_name(self, args):
        if len(args) == 1:
            return self.get_milestone_list()

    def _do_list(self):
        print_table([(m.name,
                      format_date(m.due, console_date_format)
                      if m.due else None,
                      format_datetime(m.completed, console_datetime_format)
                      if m.completed else None)
                     for m in model.Milestone.select(self.env)],
                    [_("Name"), _("Due"), _("Completed")])

    def _do_add(self, name, due=None):
        milestone = model.Milestone(self.env)
        milestone.name = name
        if due is not None:
            milestone.due = parse_date(due, hint='datetime',
                                       locale=get_console_locale(self.env))
        milestone.insert()

    def _do_rename(self, name, newname):
        milestone = model.Milestone(self.env, name)
        milestone.name = newname
        milestone.update(author=getuser())

    def _do_due(self, name, due):
        milestone = model.Milestone(self.env, name)
        milestone.due = parse_date(due, hint='datetime',
                                   locale=get_console_locale(self.env)) \
                        if due else None
        milestone.update()

    def _do_completed(self, name, completed):
        milestone = model.Milestone(self.env, name)
        milestone.completed = parse_date(completed, hint='datetime',
                                         locale=get_console_locale(self.env)) \
                              if completed else None
        milestone.update()

    def _do_remove(self, name):
        model.Milestone(self.env, name).delete()
Ejemplo n.º 15
0
class MilestoneAdminPanel(TicketAdminPanel):

    _type = 'milestones'
    _label = (N_('Milestone'), N_('Milestones'))

    # IAdminPanelProvider methods

    def get_admin_panels(self, req):
        if 'MILESTONE_VIEW' in req.perm('admin', 'ticket/' + self._type):
            return TicketAdminPanel.get_admin_panels(self, req)

    # TicketAdminPanel methods

    def _render_admin_panel(self, req, cat, page, milestone):
        perm = req.perm('admin', 'ticket/' + self._type)
        # Detail view?
        if milestone:
            mil = model.Milestone(self.env, milestone)
            if req.method == 'POST':
                if req.args.get('save'):
                    perm.require('MILESTONE_MODIFY')
                    mil.name = name = req.args.get('name')
                    mil.due = mil.completed = None
                    due = req.args.get('duedate', '')
                    if due:
                        mil.due = user_time(req,
                                            parse_date,
                                            due,
                                            hint='datetime')
                    if req.args.get('completed', False):
                        completed = req.args.get('completeddate', '')
                        mil.completed = user_time(req,
                                                  parse_date,
                                                  completed,
                                                  hint='datetime')
                        if mil.completed > datetime_now(utc):
                            raise TracError(
                                _('Completion date may not be in '
                                  'the future'), _('Invalid Completion Date'))
                    mil.description = req.args.get('description', '')
                    try:
                        mil.update(author=req.authname)
                    except self.env.db_exc.IntegrityError:
                        raise TracError(
                            _('Milestone "%(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', 'milestone': mil}

        else:
            default = self.config.get('ticket', 'default_milestone')
            if req.method == 'POST':
                # Add Milestone
                if req.args.get('add') and req.args.get('name'):
                    perm.require('MILESTONE_CREATE')
                    name = req.args.get('name')
                    try:
                        mil = model.Milestone(self.env, name=name)
                    except ResourceNotFound:
                        mil = model.Milestone(self.env)
                        mil.name = name
                        if req.args.get('duedate'):
                            mil.due = user_time(req,
                                                parse_date,
                                                req.args.get('duedate'),
                                                hint='datetime')
                        mil.insert()
                        add_notice(
                            req,
                            _('The milestone "%(name)s" has been '
                              'added.',
                              name=name))
                        req.redirect(req.href.admin(cat, page))
                    else:
                        if mil.name is None:
                            raise TracError(_('Invalid milestone name.'))
                        raise TracError(
                            _('Milestone "%(name)s" already '
                              'exists.',
                              name=name))

                # Remove milestone
                elif req.args.get('remove'):
                    perm.require('MILESTONE_DELETE')
                    sel = req.args.get('sel')
                    if not sel:
                        raise TracError(_('No milestone selected'))
                    if not isinstance(sel, list):
                        sel = [sel]
                    with self.env.db_transaction:
                        for name in sel:
                            milestone = model.Milestone(self.env, name)
                            milestone.move_tickets(None, req.authname,
                                                   "Milestone deleted")
                            milestone.delete()
                    add_notice(
                        req, _("The selected milestones have been "
                               "removed."))
                    req.redirect(req.href.admin(cat, page))

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

            # Get ticket count
            milestones = [(milestone,
                           self.env.db_query(
                               """
                    SELECT COUNT(*) FROM ticket WHERE milestone=%s
                    """, (milestone.name, ))[0][0])
                          for milestone in model.Milestone.select(self.env)]

            data = {
                'view': 'list',
                'milestones': milestones,
                'default': default
            }

        Chrome(self.env).add_jquery_ui(req)

        data.update({
            'datetime_hint': get_datetime_format_hint(req.lc_time),
        })
        return 'admin_milestones.html', data

    # IAdminCommandProvider methods

    def get_admin_commands(self):
        hints = {
            'datetime': get_datetime_format_hint(get_console_locale(self.env)),
            'iso8601': get_datetime_format_hint('iso8601'),
        }
        yield ('milestone list', '', "Show milestones", None, self._do_list)
        yield ('milestone add', '<name> [due]', "Add milestone", None,
               self._do_add)
        yield ('milestone rename', '<name> <newname>', "Rename milestone",
               self._complete_name, self._do_rename)
        yield ('milestone due', '<name> <due>', """Set milestone due date

               The <due> date must be specified in the "%(datetime)s"
               or "%(iso8601)s" (ISO 8601) format.
               Alternatively, "now" can be used to set the due date to the
               current time. To remove the due date from a milestone, specify
               an empty string ("").
               """ % hints, self._complete_name, self._do_due)
        yield ('milestone completed', '<name> <completed>',
               """Set milestone complete date

               The <completed> date must be specified in the "%(datetime)s"
               or "%(iso8601)s" (ISO 8601) format.
               Alternatively, "now" can be used to set the completion date to
               the current time. To remove the completion date from a
               milestone, specify an empty string ("").
               """ % hints, self._complete_name, self._do_completed)
        yield ('milestone remove', '<name>', "Remove milestone",
               self._complete_name, self._do_remove)

    def get_milestone_list(self):
        return [m.name for m in model.Milestone.select(self.env)]

    def _complete_name(self, args):
        if len(args) == 1:
            return self.get_milestone_list()

    def _do_list(self):
        print_table(
            [(m.name,
              format_date(m.due, console_date_format) if m.due else None,
              format_datetime(m.completed, console_datetime_format)
              if m.completed else None)
             for m in model.Milestone.select(self.env)],
            [_("Name"), _("Due"), _("Completed")])

    def _do_add(self, name, due=None):
        milestone = model.Milestone(self.env)
        milestone.name = name
        if due is not None:
            milestone.due = parse_date(due,
                                       hint='datetime',
                                       locale=get_console_locale(self.env))
        milestone.insert()

    def _do_rename(self, name, newname):
        milestone = model.Milestone(self.env, name)
        milestone.name = newname
        milestone.update(author=getuser())

    def _do_due(self, name, due):
        milestone = model.Milestone(self.env, name)
        milestone.due = parse_date(due, hint='datetime',
                                   locale=get_console_locale(self.env)) \
                        if due else None
        milestone.update()

    def _do_completed(self, name, completed):
        milestone = model.Milestone(self.env, name)
        milestone.completed = parse_date(completed, hint='datetime',
                                         locale=get_console_locale(self.env)) \
                              if completed else None
        milestone.update()

    def _do_remove(self, name):
        model.Milestone(self.env, name).delete(author=getuser())
Ejemplo n.º 16
0
Archivo: env.py Proyecto: hanotch/trac
class Environment(Component, ComponentManager):
    """Trac environment manager.

    Trac stores project information in a Trac environment. It consists
    of a directory structure containing among other things:

    * a configuration file,
    * project-specific templates and plugins,
    * the wiki and ticket attachments files,
    * the SQLite database file (stores tickets, wiki pages...)
      in case the database backend is sqlite

    """

    implements(ISystemInfoProvider)

    required = True

    system_info_providers = ExtensionPoint(ISystemInfoProvider)
    setup_participants = ExtensionPoint(IEnvironmentSetupParticipant)

    components_section = ConfigSection(
        'components', """This section is used to enable or disable components
        provided by plugins, as well as by Trac itself. The component
        to enable/disable is specified via the name of the
        option. Whether its enabled is determined by the option value;
        setting the value to `enabled` or `on` will enable the
        component, any other value (typically `disabled` or `off`)
        will disable the component.

        The option name is either the fully qualified name of the
        components or the module/package prefix of the component. The
        former enables/disables a specific component, while the latter
        enables/disables any component in the specified
        package/module.

        Consider the following configuration snippet:
        {{{
        [components]
        trac.ticket.report.ReportModule = disabled
        acct_mgr.* = enabled
        }}}

        The first option tells Trac to disable the
        [wiki:TracReports report module].
        The second option instructs Trac to enable all components in
        the `acct_mgr` package. Note that the trailing wildcard is
        required for module/package matching.

        To view the list of active components, go to the ''Plugins''
        page on ''About Trac'' (requires `CONFIG_VIEW`
        [wiki:TracPermissions permissions]).

        See also: TracPlugins
        """)

    shared_plugins_dir = PathOption(
        'inherit', 'plugins_dir', '',
        """Path to the //shared plugins directory//.

        Plugins in that directory are loaded in addition to those in
        the directory of the environment `plugins`, with this one
        taking precedence.

        Non-absolute paths are relative to the Environment `conf`
        directory.
        """)

    base_url = Option(
        'trac', 'base_url', '', """Reference URL for the Trac deployment.

        This is the base URL that will be used when producing
        documents that will be used outside of the web browsing
        context, like for example when inserting URLs pointing to Trac
        resources in notification e-mails.""")

    base_url_for_redirect = BoolOption(
        'trac', 'use_base_url_for_redirect', False,
        """Optionally use `[trac] base_url` for redirects.

        In some configurations, usually involving running Trac behind
        a HTTP proxy, Trac can't automatically reconstruct the URL
        that is used to access it. You may need to use this option to
        force Trac to use the `base_url` setting also for
        redirects. This introduces the obvious limitation that this
        environment will only be usable when accessible from that URL,
        as redirects are frequently used.
        """)

    secure_cookies = BoolOption(
        'trac', 'secure_cookies', False,
        """Restrict cookies to HTTPS connections.

        When true, set the `secure` flag on all cookies so that they
        are only sent to the server on HTTPS connections. Use this if
        your Trac instance is only accessible through HTTPS.
        """)

    anonymous_session_lifetime = IntOption(
        'trac', 'anonymous_session_lifetime', '90',
        """Lifetime of the anonymous session, in days.

        Set the option to 0 to disable purging old anonymous sessions.
        (''since 1.0.17'')""")

    project_name = Option('project', 'name', 'My Project',
                          """Name of the project.""")

    project_description = Option('project', 'descr', 'My example project',
                                 """Short description of the project.""")

    project_url = Option(
        'project', 'url', '',
        """URL of the main project web site, usually the website in
        which the `base_url` resides. This is used in notification
        e-mails.""")

    project_admin = Option(
        'project', 'admin', '',
        """E-Mail address of the project's administrator.""")

    project_admin_trac_url = Option(
        'project', 'admin_trac_url', '.',
        """Base URL of a Trac instance where errors in this Trac
        should be reported.

        This can be an absolute or relative URL, or '.' to reference
        this Trac instance. An empty value will disable the reporting
        buttons.
        """)

    project_footer = Option(
        'project', 'footer',
        N_('Visit the Trac open source project at<br />'
           '<a href="http://trac.edgewall.org/">'
           'http://trac.edgewall.org/</a>'),
        """Page footer text (right-aligned).""")

    project_icon = Option('project', 'icon', 'common/trac.ico',
                          """URL of the icon of the project.""")

    log_type = ChoiceOption('logging',
                            'log_type',
                            log.LOG_TYPES + log.LOG_TYPE_ALIASES,
                            """Logging facility to use.

        Should be one of (`none`, `file`, `stderr`, `syslog`, `winlog`).""",
                            case_sensitive=False)

    log_file = Option(
        'logging', 'log_file', 'trac.log',
        """If `log_type` is `file`, this should be a path to the
        log-file.  Relative paths are resolved relative to the `log`
        directory of the environment.""")

    log_level = ChoiceOption('logging',
                             'log_level',
                             log.LOG_LEVELS + log.LOG_LEVEL_ALIASES,
                             """Level of verbosity in log.

        Should be one of (`CRITICAL`, `ERROR`, `WARNING`, `INFO`, `DEBUG`).
        """,
                             case_sensitive=False)

    log_format = Option(
        'logging', 'log_format', None, """Custom logging format.

        If nothing is set, the following will be used:

        `Trac[$(module)s] $(levelname)s: $(message)s`

        In addition to regular key names supported by the
        [http://docs.python.org/library/logging.html Python logger library]
        one could use:

        - `$(path)s`     the path for the current environment
        - `$(basename)s` the last path component of the current environment
        - `$(project)s`  the project name

        Note the usage of `$(...)s` instead of `%(...)s` as the latter form
        would be interpreted by the !ConfigParser itself.

        Example:
        `($(thread)d) Trac[$(basename)s:$(module)s] $(levelname)s: $(message)s`
        """)

    def __init__(self, path, create=False, options=[]):
        """Initialize the Trac environment.

        :param path:   the absolute path to the Trac environment
        :param create: if `True`, the environment is created and
                       populated with default data; otherwise, the
                       environment is expected to already exist.
        :param options: A list of `(section, name, value)` tuples that
                        define configuration options
        """
        ComponentManager.__init__(self)

        self.path = os.path.normpath(os.path.normcase(path))
        self.log = None
        self.config = None

        if create:
            self.create(options)
            for setup_participant in self.setup_participants:
                setup_participant.environment_created()
        else:
            self.verify()
            self.setup_config()

    def __repr__(self):
        return '<%s %r>' % (self.__class__.__name__, self.path)

    @lazy
    def name(self):
        """The environment name.

        :since: 1.2
        """
        return os.path.basename(self.path)

    @property
    def env(self):
        """Property returning the `Environment` object, which is often
        required for functions and methods that take a `Component` instance.
        """
        # The cached decorator requires the object have an `env` attribute.
        return self

    @property
    def system_info(self):
        """List of `(name, version)` tuples describing the name and
        version information of external packages used by Trac and plugins.
        """
        info = []
        for provider in self.system_info_providers:
            info.extend(provider.get_system_info() or [])
        return sorted(set(info),
                      key=lambda args: (args[0] != 'Trac', args[0].lower()))

    def get_systeminfo(self):
        """Return a list of `(name, version)` tuples describing the name
        and version information of external packages used by Trac and plugins.

        :since 1.3.1: deprecated and will be removed in 1.5.1. Use
                      system_info property instead.
        """
        return self.system_info

    # ISystemInfoProvider methods

    def get_system_info(self):
        yield 'Trac', self.trac_version
        yield 'Python', sys.version
        yield 'setuptools', setuptools.__version__
        if pytz is not None:
            yield 'pytz', pytz.__version__
        if hasattr(self, 'webfrontend_version'):
            yield self.webfrontend, self.webfrontend_version

    def component_activated(self, component):
        """Initialize additional member variables for components.

        Every component activated through the `Environment` object
        gets three member variables: `env` (the environment object),
        `config` (the environment configuration) and `log` (a logger
        object)."""
        component.env = self
        component.config = self.config
        component.log = self.log

    def _component_name(self, name_or_class):
        name = name_or_class
        if not isinstance(name_or_class, basestring):
            name = name_or_class.__module__ + '.' + name_or_class.__name__
        return name.lower()

    @lazy
    def _component_rules(self):
        _rules = {}
        for name, value in self.components_section.options():
            name = name.rstrip('.*').lower()
            _rules[name] = as_bool(value)
        return _rules

    def is_component_enabled(self, cls):
        """Implemented to only allow activation of components that are
        not disabled in the configuration.

        This is called by the `ComponentManager` base class when a
        component is about to be activated. If this method returns
        `False`, the component does not get activated. If it returns
        `None`, the component only gets activated if it is located in
        the `plugins` directory of the environment.
        """
        component_name = self._component_name(cls)

        rules = self._component_rules
        cname = component_name
        while cname:
            enabled = rules.get(cname)
            if enabled is not None:
                return enabled
            idx = cname.rfind('.')
            if idx < 0:
                break
            cname = cname[:idx]

        # By default, all components in the trac package except
        # in trac.test or trac.tests are enabled
        return component_name.startswith('trac.') and \
               not component_name.startswith('trac.test.') and \
               not component_name.startswith('trac.tests.') or None

    def enable_component(self, cls):
        """Enable a component or module."""
        self._component_rules[self._component_name(cls)] = True
        super(Environment, self).enable_component(cls)

    @contextmanager
    def component_guard(self, component, reraise=False):
        """Traps any runtime exception raised when working with a component
        and logs the error.

        :param component: the component responsible for any error that
                          could happen inside the context
        :param reraise: if `True`, an error is logged but not suppressed.
                        By default, errors are suppressed.

        """
        try:
            yield
        except TracError as e:
            self.log.warning("Component %s failed with %s", component,
                             exception_to_unicode(e))
            if reraise:
                raise
        except Exception as e:
            self.log.error("Component %s failed with %s", component,
                           exception_to_unicode(e, traceback=True))
            if reraise:
                raise

    def verify(self):
        """Verify that the provided path points to a valid Trac environment
        directory."""
        try:
            tag = read_file(os.path.join(self.path, 'VERSION')).splitlines()[0]
            if tag != _VERSION:
                raise Exception(
                    _("Unknown Trac environment type '%(type)s'", type=tag))
        except Exception as e:
            raise TracError(
                _("No Trac environment found at %(path)s\n"
                  "%(e)s",
                  path=self.path,
                  e=e))

    @lazy
    def db_exc(self):
        """Return an object (typically a module) containing all the
        backend-specific exception types as attributes, named
        according to the Python Database API
        (http://www.python.org/dev/peps/pep-0249/).

        To catch a database exception, use the following pattern::

            try:
                with env.db_transaction as db:
                    ...
            except env.db_exc.IntegrityError as e:
                ...
        """
        return DatabaseManager(self).get_exceptions()

    @property
    def db_query(self):
        """Return a context manager
        (`~trac.db.api.QueryContextManager`) which can be used to
        obtain a read-only database connection.

        Example::

            with env.db_query as db:
                cursor = db.cursor()
                cursor.execute("SELECT ...")
                for row in cursor.fetchall():
                    ...

        Note that a connection retrieved this way can be "called"
        directly in order to execute a query::

            with env.db_query as db:
                for row in db("SELECT ..."):
                    ...

        :warning: after a `with env.db_query as db` block, though the
          `db` variable is still defined, you shouldn't use it as it
          might have been closed when exiting the context, if this
          context was the outermost context (`db_query` or
          `db_transaction`).

        If you don't need to manipulate the connection itself, this
        can even be simplified to::

            for row in env.db_query("SELECT ..."):
                ...

        """
        return QueryContextManager(self)

    @property
    def db_transaction(self):
        """Return a context manager
        (`~trac.db.api.TransactionContextManager`) which can be used
        to obtain a writable database connection.

        Example::

            with env.db_transaction as db:
                cursor = db.cursor()
                cursor.execute("UPDATE ...")

        Upon successful exit of the context, the context manager will
        commit the transaction. In case of nested contexts, only the
        outermost context performs a commit. However, should an
        exception happen, any context manager will perform a rollback.
        You should *not* call `commit()` yourself within such block,
        as this will force a commit even if that transaction is part
        of a larger transaction.

        Like for its read-only counterpart, you can directly execute a
        DML query on the `db`::

            with env.db_transaction as db:
                db("UPDATE ...")

        :warning: after a `with env.db_transaction` as db` block,
          though the `db` variable is still available, you shouldn't
          use it as it might have been closed when exiting the
          context, if this context was the outermost context
          (`db_query` or `db_transaction`).

        If you don't need to manipulate the connection itself, this
        can also be simplified to::

            env.db_transaction("UPDATE ...")

        """
        return TransactionContextManager(self)

    def shutdown(self, tid=None):
        """Close the environment."""
        from trac.versioncontrol.api import RepositoryManager
        RepositoryManager(self).shutdown(tid)
        DatabaseManager(self).shutdown(tid)
        if tid is None:
            log.shutdown(self.log)

    def create(self, options=[]):
        """Create the basic directory structure of the environment,
        initialize the database and populate the configuration file
        with default values.

        If options contains ('inherit', 'file'), default values will
        not be loaded; they are expected to be provided by that file
        or other options.

        :raises TracError: if the base directory of `path` does not exist.
        :raises TracError: if `path` exists and is not empty.
        """
        base_dir = os.path.dirname(self.path)
        if not os.path.exists(base_dir):
            raise TracError(
                _(
                    "Base directory '%(env)s' does not exist. Please create it "
                    "and retry.",
                    env=base_dir))

        if os.path.exists(self.path) and os.listdir(self.path):
            raise TracError(_("Directory exists and is not empty."))

        # Create the directory structure
        if not os.path.exists(self.path):
            os.mkdir(self.path)
        os.mkdir(self.htdocs_dir)
        os.mkdir(self.log_dir)
        os.mkdir(self.plugins_dir)
        os.mkdir(self.templates_dir)

        # Create a few files
        create_file(os.path.join(self.path, 'VERSION'), _VERSION + '\n')
        create_file(
            os.path.join(self.path, 'README'),
            'This directory contains a Trac environment.\n'
            'Visit http://trac.edgewall.org/ for more information.\n')

        # Setup the default configuration
        os.mkdir(self.conf_dir)
        config = Configuration(self.config_file_path)
        for section, name, value in options:
            config.set(section, name, value)
        config.save()
        self.setup_config()
        if not any((section, option) == ('inherit', 'file')
                   for section, option, value in options):
            self.config.set_defaults(self)
            self.config.save()

        # Create the sample configuration
        create_file(self.config_file_path + '.sample')
        self._update_sample_config()

        # Create the database
        DatabaseManager(self).init_db()

    @lazy
    def database_version(self):
        """Returns the current version of the database.

        :since 1.0.2:
        """
        return DatabaseManager(self) \
               .get_database_version('database_version')

    @lazy
    def database_initial_version(self):
        """Returns the version of the database at the time of creation.

        In practice, for database created before 0.11, this will
        return `False` which is "older" than any db version number.

        :since 1.0.2:
        """
        return DatabaseManager(self) \
               .get_database_version('initial_database_version')

    @lazy
    def trac_version(self):
        """Returns the version of Trac.
        :since: 1.2
        """
        from trac import core, __version__
        return get_pkginfo(core).get('version', __version__)

    def setup_config(self):
        """Load the configuration file."""
        self.config = Configuration(self.config_file_path,
                                    {'envname': self.name})
        if not self.config.exists:
            raise TracError(
                _("The configuration file is not found at "
                  "%(path)s",
                  path=self.config_file_path))
        self.setup_log()
        plugins_dir = self.shared_plugins_dir
        load_components(self, plugins_dir and (plugins_dir, ))

    @lazy
    def config_file_path(self):
        """Path of the trac.ini file."""
        return os.path.join(self.conf_dir, 'trac.ini')

    @lazy
    def log_file_path(self):
        """Path to the log file."""
        if not os.path.isabs(self.log_file):
            return os.path.join(self.log_dir, self.log_file)
        return self.log_file

    def _get_path_to_dir(self, *dirs):
        path = self.path
        for dir in dirs:
            path = os.path.join(path, dir)
        return os.path.realpath(path)

    @lazy
    def attachments_dir(self):
        """Absolute path to the attachments directory.

        :since: 1.3.1
        """
        return self._get_path_to_dir('files', 'attachments')

    @lazy
    def conf_dir(self):
        """Absolute path to the conf directory.

        :since: 1.0.11
        """
        return self._get_path_to_dir('conf')

    @lazy
    def files_dir(self):
        """Absolute path to the files directory.

        :since: 1.3.2
        """
        return self._get_path_to_dir('files')

    @lazy
    def htdocs_dir(self):
        """Absolute path to the htdocs directory.

        :since: 1.0.11
        """
        return self._get_path_to_dir('htdocs')

    @lazy
    def log_dir(self):
        """Absolute path to the log directory.

        :since: 1.0.11
        """
        return self._get_path_to_dir('log')

    @lazy
    def plugins_dir(self):
        """Absolute path to the plugins directory.

        :since: 1.0.11
        """
        return self._get_path_to_dir('plugins')

    @lazy
    def templates_dir(self):
        """Absolute path to the templates directory.

        :since: 1.0.11
        """
        return self._get_path_to_dir('templates')

    def setup_log(self):
        """Initialize the logging sub-system."""
        self.log, log_handler = \
            self.create_logger(self.log_type, self.log_file_path,
                               self.log_level, self.log_format)
        self.log.addHandler(log_handler)
        self.log.info('-' * 32 + ' environment startup [Trac %s] ' + '-' * 32,
                      self.trac_version)

    def create_logger(self, log_type, log_file, log_level, log_format):
        log_id = 'Trac.%s' % hashlib.sha1(self.path).hexdigest()
        if log_format:
            log_format = log_format.replace('$(', '%(') \
                                   .replace('%(path)s', self.path) \
                                   .replace('%(basename)s', self.name) \
                                   .replace('%(project)s', self.project_name)
        return log.logger_handler_factory(log_type,
                                          log_file,
                                          log_level,
                                          log_id,
                                          format=log_format)

    def get_known_users(self, as_dict=False):
        """Returns information about all known users, i.e. users that
        have logged in to this Trac environment and possibly set their
        name and email.

        By default this function returns a iterator that yields one
        tuple for every user, of the form (username, name, email),
        ordered alpha-numerically by username. When `as_dict` is `True`
        the function returns a dictionary mapping username to a
        (name, email) tuple.

        :since 1.2: the `as_dict` parameter is available.
        """
        return self._known_users_dict if as_dict else iter(self._known_users)

    @cached
    def _known_users(self):
        return self.db_query("""
                SELECT DISTINCT s.sid, n.value, e.value
                FROM session AS s
                 LEFT JOIN session_attribute AS n ON (n.sid=s.sid
                  AND n.authenticated=1 AND n.name = 'name')
                 LEFT JOIN session_attribute AS e ON (e.sid=s.sid
                  AND e.authenticated=1 AND e.name = 'email')
                WHERE s.authenticated=1 ORDER BY s.sid
        """)

    @cached
    def _known_users_dict(self):
        return {u[0]: (u[1], u[2]) for u in self._known_users}

    def invalidate_known_users_cache(self):
        """Clear the known_users cache."""
        del self._known_users
        del self._known_users_dict

    def backup(self, dest=None):
        """Create a backup of the database.

        :param dest: Destination file; if not specified, the backup is
                     stored in a file called db_name.trac_version.bak
        """
        return DatabaseManager(self).backup(dest)

    def needs_upgrade(self):
        """Return whether the environment needs to be upgraded."""
        for participant in self.setup_participants:
            with self.component_guard(participant, reraise=True):
                if participant.environment_needs_upgrade():
                    self.log.warning(
                        "Component %s requires environment upgrade",
                        participant)
                    return True
        return False

    def upgrade(self, backup=False, backup_dest=None):
        """Upgrade database.

        :param backup: whether or not to backup before upgrading
        :param backup_dest: name of the backup file
        :return: whether the upgrade was performed
        """
        upgraders = []
        for participant in self.setup_participants:
            with self.component_guard(participant, reraise=True):
                if participant.environment_needs_upgrade():
                    upgraders.append(participant)
        if not upgraders:
            return

        if backup:
            try:
                self.backup(backup_dest)
            except Exception as e:
                raise BackupError(e)

        for participant in upgraders:
            self.log.info("upgrading %s...", participant)
            with self.component_guard(participant, reraise=True):
                participant.upgrade_environment()
            # Database schema may have changed, so close all connections
            dbm = DatabaseManager(self)
            if dbm.connection_uri != 'sqlite::memory:':
                dbm.shutdown()

        self._update_sample_config()
        del self.database_version
        return True

    @lazy
    def href(self):
        """The application root path"""
        return Href(urlsplit(self.abs_href.base).path)

    @lazy
    def abs_href(self):
        """The application URL"""
        if not self.base_url:
            self.log.warning("base_url option not set in configuration, "
                             "generated links may be incorrect")
        return Href(self.base_url)

    def _update_sample_config(self):
        filename = os.path.join(self.config_file_path + '.sample')
        if not os.path.isfile(filename):
            return
        config = Configuration(filename)
        config.set_defaults()
        try:
            config.save()
        except EnvironmentError as e:
            self.log.warning("Couldn't write sample configuration file (%s)%s",
                             e, exception_to_unicode(e, traceback=True))
        else:
            self.log.info(
                "Wrote sample configuration file with the new "
                "settings and their default values: %s", filename)
Ejemplo n.º 17
0
class Environment(Component, ComponentManager):
    """Trac environment manager.

    Trac stores project information in a Trac environment. It consists
    of a directory structure containing among other things:
        * a configuration file, 
        * project-specific templates and plugins,
        * the wiki and ticket attachments files,
        * the SQLite database file (stores tickets, wiki pages...)
          in case the database backend is sqlite
    """

    implements(ISystemInfoProvider)

    required = True

    system_info_providers = ExtensionPoint(ISystemInfoProvider)
    setup_participants = ExtensionPoint(IEnvironmentSetupParticipant)

    components_section = ConfigSection(
        'components', """This section is used to enable or disable components
        provided by plugins, as well as by Trac itself. The component
        to enable/disable is specified via the name of the
        option. Whether its enabled is determined by the option value;
        setting the value to `enabled` or `on` will enable the
        component, any other value (typically `disabled` or `off`)
        will disable the component.

        The option name is either the fully qualified name of the
        components or the module/package prefix of the component. The
        former enables/disables a specific component, while the latter
        enables/disables any component in the specified
        package/module.

        Consider the following configuration snippet:
        {{{
        [components]
        trac.ticket.report.ReportModule = disabled
        webadmin.* = enabled
        }}}
        
        The first option tells Trac to disable the
        [wiki:TracReports report module]. 
        The second option instructs Trac to enable all components in
        the `webadmin` package. Note that the trailing wildcard is
        required for module/package matching.
        
        To view the list of active components, go to the ''Plugins''
        page on ''About Trac'' (requires `CONFIG_VIEW`
        [wiki:TracPermissions permissions]).
        
        See also: TracPlugins
        """)

    shared_plugins_dir = PathOption(
        'inherit', 'plugins_dir', '',
        """Path to the //shared plugins directory//.
        
        Plugins in that directory are loaded in addition to those in
        the directory of the environment `plugins`, with this one
        taking precedence.
        
        (''since 0.11'')""")

    base_url = Option(
        'trac', 'base_url', '', """Reference URL for the Trac deployment.
        
        This is the base URL that will be used when producing
        documents that will be used outside of the web browsing
        context, like for example when inserting URLs pointing to Trac
        resources in notification e-mails.""")

    base_url_for_redirect = BoolOption(
        'trac', 'use_base_url_for_redirect', False,
        """Optionally use `[trac] base_url` for redirects.
        
        In some configurations, usually involving running Trac behind
        a HTTP proxy, Trac can't automatically reconstruct the URL
        that is used to access it. You may need to use this option to
        force Trac to use the `base_url` setting also for
        redirects. This introduces the obvious limitation that this
        environment will only be usable when accessible from that URL,
        as redirects are frequently used. ''(since 0.10.5)''""")

    secure_cookies = BoolOption(
        'trac', 'secure_cookies', False,
        """Restrict cookies to HTTPS connections.
        
        When true, set the `secure` flag on all cookies so that they
        are only sent to the server on HTTPS connections. Use this if
        your Trac instance is only accessible through HTTPS. (''since
        0.11.2'')""")

    project_name = Option('project', 'name', 'My Project',
                          """Name of the project.""")

    project_description = Option('project', 'descr', 'My example project',
                                 """Short description of the project.""")

    project_url = Option(
        'project', 'url', '',
        """URL of the main project web site, usually the website in
        which the `base_url` resides. This is used in notification
        e-mails.""")

    project_admin = Option(
        'project', 'admin', '',
        """E-Mail address of the project's administrator.""")

    project_admin_trac_url = Option(
        'project', 'admin_trac_url', '.',
        """Base URL of a Trac instance where errors in this Trac
        should be reported.
        
        This can be an absolute or relative URL, or '.' to reference
        this Trac instance. An empty value will disable the reporting
        buttons.  (''since 0.11.3'')""")

    project_footer = Option(
        'project', 'footer',
        N_('Visit the Trac open source project at<br />'
           '<a href="http://trac.edgewall.org/">'
           'http://trac.edgewall.org/</a>'),
        """Page footer text (right-aligned).""")

    project_icon = Option('project', 'icon', 'common/trac.ico',
                          """URL of the icon of the project.""")

    log_type = Option(
        'logging', 'log_type', 'none', """Logging facility to use.
        
        Should be one of (`none`, `file`, `stderr`, `syslog`, `winlog`).""")

    log_file = Option(
        'logging', 'log_file', 'trac.log',
        """If `log_type` is `file`, this should be a path to the
        log-file.  Relative paths are resolved relative to the `log`
        directory of the environment.""")

    log_level = Option(
        'logging', 'log_level', 'DEBUG', """Level of verbosity in log.
        
        Should be one of (`CRITICAL`, `ERROR`, `WARN`, `INFO`, `DEBUG`).""")

    log_format = Option(
        'logging', 'log_format', None, """Custom logging format.

        If nothing is set, the following will be used:
        
        Trac[$(module)s] $(levelname)s: $(message)s

        In addition to regular key names supported by the Python
        logger library (see
        http://docs.python.org/library/logging.html), one could use:
         - $(path)s     the path for the current environment
         - $(basename)s the last path component of the current environment
         - $(project)s  the project name

        Note the usage of `$(...)s` instead of `%(...)s` as the latter form
        would be interpreted by the ConfigParser itself.

        Example:
        `($(thread)d) Trac[$(basename)s:$(module)s] $(levelname)s: $(message)s`

        ''(since 0.10.5)''""")

    def __init__(self, path, create=False, options=[]):
        """Initialize the Trac environment.
        
        :param path:   the absolute path to the Trac environment
        :param create: if `True`, the environment is created and
                       populated with default data; otherwise, the
                       environment is expected to already exist.
        :param options: A list of `(section, name, value)` tuples that
                        define configuration options
        """
        ComponentManager.__init__(self)

        self.path = path
        self.systeminfo = []
        self._href = self._abs_href = None

        if create:
            self.create(options)
        else:
            self.verify()
            self.setup_config()

        if create:
            for setup_participant in self.setup_participants:
                setup_participant.environment_created()

    def get_systeminfo(self):
        """Return a list of `(name, version)` tuples describing the
        name and version information of external packages used by Trac
        and plugins.
        """
        info = self.systeminfo[:]
        for provider in self.system_info_providers:
            info.extend(provider.get_system_info() or [])
        info.sort(key=lambda (name, version): (name != 'Trac', name.lower()))
        return info

    # ISystemInfoProvider methods

    def get_system_info(self):
        from trac import core, __version__ as VERSION
        yield 'Trac', get_pkginfo(core).get('version', VERSION)
        yield 'Python', sys.version
        yield 'setuptools', setuptools.__version__
        from trac.util.datefmt import pytz
        if pytz is not None:
            yield 'pytz', pytz.__version__

    def component_activated(self, component):
        """Initialize additional member variables for components.
        
        Every component activated through the `Environment` object
        gets three member variables: `env` (the environment object),
        `config` (the environment configuration) and `log` (a logger
        object)."""
        component.env = self
        component.config = self.config
        component.log = self.log

    def _component_name(self, name_or_class):
        name = name_or_class
        if not isinstance(name_or_class, basestring):
            name = name_or_class.__module__ + '.' + name_or_class.__name__
        return name.lower()

    @property
    def _component_rules(self):
        try:
            return self._rules
        except AttributeError:
            self._rules = {}
            for name, value in self.components_section.options():
                if name.endswith('.*'):
                    name = name[:-2]
                self._rules[name.lower()] = value.lower() in ('enabled', 'on')
            return self._rules

    def is_component_enabled(self, cls):
        """Implemented to only allow activation of components that are
        not disabled in the configuration.
        
        This is called by the `ComponentManager` base class when a
        component is about to be activated. If this method returns
        `False`, the component does not get activated. If it returns
        `None`, the component only gets activated if it is located in
        the `plugins` directory of the environment.
        """
        component_name = self._component_name(cls)

        # Disable the pre-0.11 WebAdmin plugin
        # Please note that there's no recommendation to uninstall the
        # plugin because doing so would obviously break the backwards
        # compatibility that the new integration administration
        # interface tries to provide for old WebAdmin extensions
        if component_name.startswith('webadmin.'):
            self.log.info("The legacy TracWebAdmin plugin has been "
                          "automatically disabled, and the integrated "
                          "administration interface will be used "
                          "instead.")
            return False

        rules = self._component_rules
        cname = component_name
        while cname:
            enabled = rules.get(cname)
            if enabled is not None:
                return enabled
            idx = cname.rfind('.')
            if idx < 0:
                break
            cname = cname[:idx]

        # By default, all components in the trac package are enabled
        return component_name.startswith('trac.') or None

    def enable_component(self, cls):
        """Enable a component or module."""
        self._component_rules[self._component_name(cls)] = True

    def verify(self):
        """Verify that the provided path points to a valid Trac environment
        directory."""
        with open(os.path.join(self.path, 'VERSION'), 'r') as fd:
            assert fd.read(26) == 'Trac Environment Version 1'

    def get_db_cnx(self):
        """Return a database connection from the connection pool

        :deprecated: Use :meth:`db_transaction` or :meth:`db_query` instead

        `db_transaction` for obtaining the `db` database connection
        which can be used for performing any query
        (SELECT/INSERT/UPDATE/DELETE)::
        
           with env.db_transaction as db:
               ...

           
        `db_query` for obtaining a `db` database connection which can
        be used for performing SELECT queries only::

           with env.db_query as db:
               ...
        """
        return DatabaseManager(self).get_connection()

    def with_transaction(self, db=None):
        """Decorator for transaction functions :deprecated:"""
        return with_transaction(self, db)

    def get_read_db(self):
        """Return a database connection for read purposes :deprecated:

        See `trac.db.api.get_read_db` for detailed documentation."""
        return DatabaseManager(self).get_connection(readonly=True)

    @property
    def db_query(self):
        """Return a context manager which can be used to obtain a
        read-only database connection.

        Example::

            with env.db_query as db:
                cursor = db.cursor()
                cursor.execute("SELECT ...")
                for row in cursor.fetchall():
                    ...

        Note that a connection retrieved this way can be "called"
        directly in order to execute a query::

            with env.db_query as db:
                for row in db("SELECT ..."):
                    ...
        
        If you don't need to manipulate the connection itself, this
        can even be simplified to::

            for row in env.db_query("SELECT ..."):
                ...

        :warning: after a `with env.db_query as db` block, though the
          `db` variable is still available, you shouldn't use it as it
          might have been closed when exiting the context, if this
          context was the outermost context (`db_query` or
          `db_transaction`).
        """
        return QueryContextManager(self)

    @property
    def db_transaction(self):
        """Return a context manager which can be used to obtain a
        writable database connection.
        
        Example::

            with env.db_transaction as db:
                cursor = db.cursor()
                cursor.execute("UPDATE ...")

        Upon successful exit of the context, the context manager will
        commit the transaction. In case of nested contexts, only the
        outermost context performs a commit. However, should an
        exception happen, any context manager will perform a rollback.

        Like for its read-only counterpart, you can directly execute a
        DML query on the `db`::

            with env.db_transaction as db:
                db("UPDATE ...")

        If you don't need to manipulate the connection itself, this
        can also be simplified to::

            env.db_transaction("UPDATE ...")

        :warning: after a `with env.db_transaction` as db` block,
          though the `db` variable is still available, you shouldn't
          use it as it might have been closed when exiting the
          context, if this context was the outermost context
          (`db_query` or `db_transaction`).
        """
        return TransactionContextManager(self)

    def shutdown(self, tid=None):
        """Close the environment."""
        RepositoryManager(self).shutdown(tid)
        DatabaseManager(self).shutdown(tid)
        if tid is None:
            self.log.removeHandler(self._log_handler)
            self._log_handler.flush()
            self._log_handler.close()
            del self._log_handler

    def get_repository(self, reponame=None, authname=None):
        """Return the version control repository with the given name,
        or the default repository if `None`.
        
        The standard way of retrieving repositories is to use the
        methods of `RepositoryManager`. This method is retained here
        for backward compatibility.
        
        :param reponame: the name of the repository
        :param authname: the user name for authorization (not used
                         anymore, left here for compatibility with
                         0.11)
        """
        return RepositoryManager(self).get_repository(reponame)

    def create(self, options=[]):
        """Create the basic directory structure of the environment,
        initialize the database and populate the configuration file
        with default values.

        If options contains ('inherit', 'file'), default values will
        not be loaded; they are expected to be provided by that file
        or other options.
        """
        # Create the directory structure
        if not os.path.exists(self.path):
            os.mkdir(self.path)
        os.mkdir(self.get_log_dir())
        os.mkdir(self.get_htdocs_dir())
        os.mkdir(os.path.join(self.path, 'plugins'))

        # Create a few files
        create_file(os.path.join(self.path, 'VERSION'),
                    'Trac Environment Version 1\n')
        create_file(
            os.path.join(self.path, 'README'),
            'This directory contains a Trac environment.\n'
            'Visit http://trac.edgewall.org/ for more information.\n')

        # Setup the default configuration
        os.mkdir(os.path.join(self.path, 'conf'))
        create_file(os.path.join(self.path, 'conf', 'trac.ini.sample'))
        config = Configuration(os.path.join(self.path, 'conf', 'trac.ini'))
        for section, name, value in options:
            config.set(section, name, value)
        config.save()
        self.setup_config()
        if not any((section, option) == ('inherit', 'file')
                   for section, option, value in options):
            self.config.set_defaults(self)
            self.config.save()

        # Create the database
        DatabaseManager(self).init_db()

    def get_version(self, db=None, initial=False):
        """Return the current version of the database.  If the
        optional argument `initial` is set to `True`, the version of
        the database used at the time of creation will be returned.

        In practice, for database created before 0.11, this will
        return `False` which is "older" than any db version number.

        :since: 0.11

        :since 0.13: deprecation warning: the `db` parameter is no
                     longer used and will be removed in version 0.14
        """
        rows = self.db_query("""
                SELECT value FROM system WHERE name='%sdatabase_version'
                """ % ('initial_' if initial else ''))
        return rows and int(rows[0][0])

    def setup_config(self):
        """Load the configuration file."""
        self.config = Configuration(
            os.path.join(self.path, 'conf', 'trac.ini'),
            {'envname': os.path.basename(self.path)})
        self.setup_log()
        from trac.loader import load_components
        plugins_dir = self.shared_plugins_dir
        load_components(self, plugins_dir and (plugins_dir, ))

    def get_templates_dir(self):
        """Return absolute path to the templates directory."""
        return os.path.join(self.path, 'templates')

    def get_htdocs_dir(self):
        """Return absolute path to the htdocs directory."""
        return os.path.join(self.path, 'htdocs')

    def get_log_dir(self):
        """Return absolute path to the log directory."""
        return os.path.join(self.path, 'log')

    def setup_log(self):
        """Initialize the logging sub-system."""
        from trac.log import logger_handler_factory
        logtype = self.log_type
        logfile = self.log_file
        if logtype == 'file' and not os.path.isabs(logfile):
            logfile = os.path.join(self.get_log_dir(), logfile)
        format = self.log_format
        if format:
            format = format.replace('$(', '%(') \
                     .replace('%(path)s', self.path) \
                     .replace('%(basename)s', os.path.basename(self.path)) \
                     .replace('%(project)s', self.project_name)
        self.log, self._log_handler = logger_handler_factory(logtype,
                                                             logfile,
                                                             self.log_level,
                                                             self.path,
                                                             format=format)
        from trac import core, __version__ as VERSION
        self.log.info('-' * 32 + ' environment startup [Trac %s] ' + '-' * 32,
                      get_pkginfo(core).get('version', VERSION))

    def get_known_users(self, cnx=None):
        """Generator that yields information about all known users,
        i.e. users that have logged in to this Trac environment and
        possibly set their name and email.

        This function generates one tuple for every user, of the form
        (username, name, email) ordered alpha-numerically by username.

        :param cnx: the database connection; if ommitted, a new
                    connection is retrieved

        :since 0.13: deprecation warning: the `cnx` parameter is no
                     longer used and will be removed in version 0.14
        """
        for username, name, email in self.db_query("""
                SELECT DISTINCT s.sid, n.value, e.value
                FROM session AS s
                 LEFT JOIN session_attribute AS n ON (n.sid=s.sid
                  and n.authenticated=1 AND n.name = 'name')
                 LEFT JOIN session_attribute AS e ON (e.sid=s.sid
                  AND e.authenticated=1 AND e.name = 'email')
                WHERE s.authenticated=1 ORDER BY s.sid
                """):
            yield username, name, email

    def backup(self, dest=None):
        """Create a backup of the database.

        :param dest: Destination file; if not specified, the backup is
                     stored in a file called db_name.trac_version.bak
        """
        return DatabaseManager(self).backup(dest)

    def needs_upgrade(self):
        """Return whether the environment needs to be upgraded."""
        with self.db_query as db:
            for participant in self.setup_participants:
                if participant.environment_needs_upgrade(db):
                    self.log.warn("Component %s requires environment upgrade",
                                  participant)
                    return True
            return False

    def upgrade(self, backup=False, backup_dest=None):
        """Upgrade database.
        
        :param backup: whether or not to backup before upgrading
        :param backup_dest: name of the backup file
        :return: whether the upgrade was performed
        """
        upgraders = []
        with self.db_query as db:
            for participant in self.setup_participants:
                if participant.environment_needs_upgrade(db):
                    upgraders.append(participant)
        if not upgraders:
            return

        if backup:
            self.backup(backup_dest)

        for participant in upgraders:
            self.log.info("%s.%s upgrading...", participant.__module__,
                          participant.__class__.__name__)
            with self.db_transaction as db:
                participant.upgrade_environment(db)
            # Database schema may have changed, so close all connections
            DatabaseManager(self).shutdown()
        return True

    @property
    def href(self):
        """The application root path"""
        if not self._href:
            self._href = Href(urlsplit(self.abs_href.base)[2])
        return self._href

    @property
    def abs_href(self):
        """The application URL"""
        if not self._abs_href:
            if not self.base_url:
                self.log.warn("base_url option not set in configuration, "
                              "generated links may be incorrect")
                self._abs_href = Href('')
            else:
                self._abs_href = Href(self.base_url)
        return self._abs_href
Ejemplo n.º 18
0
class ConfigurationError(TracError):
    """Exception raised when a value in the configuration file is not valid."""
    title = N_('Configuration Error')
Ejemplo n.º 19
0
class ComponentAdminPanel(TicketAdminPanel):

    _type = 'components'
    _label = (N_('Component'), N_('Components'))

    # TicketAdminPanel methods

    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

    # IAdminCommandProvider methods

    def get_admin_commands(self):
        yield ('component list', '', 'Show available components', None,
               self._do_list)
        yield ('component add', '<name> [owner]', 'Add a new component',
               self._complete_add, self._do_add)
        yield ('component rename', '<name> <newname>', 'Rename a component',
               self._complete_remove_rename, self._do_rename)
        yield ('component remove', '<name>', 'Remove/uninstall a component',
               self._complete_remove_rename, self._do_remove)
        yield ('component chown', '<name> <owner>',
               'Change component ownership', self._complete_chown,
               self._do_chown)

    def get_component_list(self):
        return [c.name for c in model.Component.select(self.env)]

    def get_user_list(self):
        return TicketSystem(self.env).get_allowed_owners()

    def _complete_add(self, args):
        if len(args) == 2:
            return self.get_user_list()

    def _complete_remove_rename(self, args):
        if len(args) == 1:
            return self.get_component_list()

    def _complete_chown(self, args):
        if len(args) == 1:
            return self.get_component_list()
        elif len(args) == 2:
            return self.get_user_list()

    def _do_list(self):
        print_table([(c.name, c.owner)
                     for c in model.Component.select(self.env)],
                    [_('Name'), _('Owner')])

    def _do_add(self, name, owner=None):
        component = model.Component(self.env)
        component.name = name
        component.owner = owner
        component.insert()

    def _do_rename(self, name, newname):
        component = model.Component(self.env, name)
        component.name = newname
        component.update()

    def _do_remove(self, name):
        model.Component(self.env, name).delete()

    def _do_chown(self, name, owner):
        component = model.Component(self.env, name)
        component.owner = owner
        component.update()
Ejemplo n.º 20
0
class PriorityAdminPanel(AbstractEnumAdminPanel):
    _type = 'priority'
    _enum_cls = model.Priority
    _label = (N_('Priority'), N_('Priorities'))
Ejemplo n.º 21
0
    def fields(self):
        """Return the list of fields available for tickets."""
        from trac.ticket import model

        fields = TicketFieldList()

        # Basic text fields
        fields.append({
            'name': 'summary',
            'type': 'text',
            'label': N_('Summary')
        })
        fields.append({
            'name': 'reporter',
            'type': 'text',
            'label': N_('Reporter')
        })

        # Owner field, by default text but can be changed dynamically
        # into a drop-down depending on configuration (restrict_owner=true)
        fields.append({'name': 'owner', 'type': 'text', 'label': N_('Owner')})

        # Description
        fields.append({
            'name': 'description',
            'type': 'textarea',
            'format': 'wiki',
            'label': N_('Description')
        })

        # Default select and radio fields
        selects = [('type', N_('Type'), model.Type),
                   ('status', N_('Status'), model.Status),
                   ('priority', N_('Priority'), model.Priority),
                   ('milestone', N_('Milestone'), model.Milestone),
                   ('component', N_('Component'), model.Component),
                   ('version', N_('Version'), model.Version),
                   ('severity', N_('Severity'), model.Severity),
                   ('resolution', N_('Resolution'), model.Resolution)]
        for name, label, cls in selects:
            options = [val.name for val in cls.select(self.env)]
            if not options:
                # Fields without possible values are treated as if they didn't
                # exist
                continue
            field = {
                'name': name,
                'type': 'select',
                'label': label,
                'value': getattr(self, 'default_' + name, ''),
                'options': options
            }
            if name in ('status', 'resolution'):
                field['type'] = 'radio'
                field['optional'] = True
            elif name in self.allowed_empty_fields:
                field['optional'] = True
            fields.append(field)

        # Advanced text fields
        fields.append({
            'name': 'keywords',
            'type': 'text',
            'format': 'list',
            'label': N_('Keywords')
        })
        fields.append({
            'name': 'cc',
            'type': 'text',
            'format': 'list',
            'label': N_('Cc')
        })

        # Date/time fields
        fields.append({
            'name': 'time',
            'type': 'time',
            'format': 'relative',
            'label': N_('Created')
        })
        fields.append({
            'name': 'changetime',
            'type': 'time',
            'format': 'relative',
            'label': N_('Modified')
        })

        for field in self.custom_fields:
            if field['name'] in [f['name'] for f in fields]:
                self.log.warning('Duplicate field name "%s" (ignoring)',
                                 field['name'])
                continue
            fields.append(field)

        return fields
Ejemplo n.º 22
0
class SeverityAdminPanel(AbstractEnumAdminPanel):
    _type = 'severity'
    _enum_cls = model.Severity
    _label = N_("Severity"), N_("Severities")
Ejemplo n.º 23
0
class ResolutionAdminPanel(AbstractEnumAdminPanel):
    _type = 'resolution'
    _enum_cls = model.Resolution
    _label = (N_('Resolution'), N_('Resolutions'))
Ejemplo n.º 24
0
class ResolutionAdminPanel(AbstractEnumAdminPanel):
    _type = 'resolution'
    _enum_cls = model.Resolution
    _label = N_("Resolution"), N_("Resolutions")
Ejemplo n.º 25
0
class SeverityAdminPanel(AbstractEnumAdminPanel):
    _type = 'severity'
    _enum_cls = model.Severity
    _label = (N_('Severity'), N_('Severities'))
Ejemplo n.º 26
0
 def get_macro_description(self, name):
     return 'messages', \
            N_("Provide a description list for the known InterWiki "
               "prefixes.")
Ejemplo n.º 27
0
class ComponentAdminPanel(TicketAdminPanel):

    _type = 'components'
    _label = N_("Component"), N_("Components")

    # TicketAdminPanel methods

    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 = req.args.get('name')
                    comp.owner = req.args.get('owner')
                    comp.description = req.args.get('description')
                    comp.update()
                    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 = Chrome(self.env)
            chrome.add_wiki_toolbars(req)
            chrome.add_auto_preview(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'):
                    comp = model.Component(self.env)
                    comp.name = req.args.get('name')
                    comp.owner = req.args.get('owner')
                    comp.insert()
                    add_notice(req, _('The component "%(name)s" has been '
                                      'added.', name=comp.name))
                    req.redirect(req.href.admin(cat, page))

                # 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

    # IAdminCommandProvider methods

    def get_admin_commands(self):
        yield ('component list', '',
               "Show components",
               None, self._do_list)
        yield ('component add', '<name> [owner]',
               "Add component",
               self._complete_add, self._do_add)
        yield ('component rename', '<name> <newname>',
               "Rename component",
               self._complete_name, self._do_rename)
        yield ('component remove', '<name>',
               "Remove component",
               self._complete_name, self._do_remove)
        yield ('component chown', '<name> <owner>',
               "Change component owner",
               self._complete_chown, self._do_chown)

    def get_component_list(self):
        return [c.name for c in model.Component.select(self.env)]

    def get_user_list(self):
        return TicketSystem(self.env).get_allowed_owners()

    def _complete_add(self, args):
        if len(args) == 2:
            return self.get_user_list()

    def _complete_name(self, args):
        if len(args) == 1:
            return self.get_component_list()

    def _complete_chown(self, args):
        if len(args) == 1:
            return self.get_component_list()
        elif len(args) == 2:
            return self.get_user_list()

    def _do_list(self):
        print_table([(c.name, c.owner)
                     for c in model.Component.select(self.env)],
                    [_("Name"), _("Owner")])

    def _do_add(self, name, owner=None):
        component = model.Component(self.env)
        component.name = name
        component.owner = owner
        component.insert()

    def _do_rename(self, name, newname):
        component = model.Component(self.env, name)
        component.name = newname
        component.update()

    def _do_remove(self, name):
        model.Component(self.env, name).delete()

    def _do_chown(self, name, owner):
        component = model.Component(self.env, name)
        component.owner = owner
        component.update()
Ejemplo n.º 28
0
 def get_macro_description(self, name):
     return 'messages', N_("Provide a list of known InterTrac prefixes.")
Ejemplo n.º 29
0
class PriorityAdminPanel(AbstractEnumAdminPanel):
    _type = 'priority'
    _enum_cls = model.Priority
    _label = N_("Priority"), N_("Priorities")
Ejemplo n.º 30
0
    def fields(self):
        """Return the list of fields available for crashes."""
        from trac.ticket import model

        fields = []

        # Basic text fields
        fields.append({'name': 'summary', 'type': 'text',
                       'label': N_('Summary')})
        fields.append({'name': 'reporter', 'type': 'text',
                       'label': N_('Reporter')})

        # Owner field, by default text but can be changed dynamically
        # into a drop-down depending on configuration (restrict_owner=true)
        field = {'name': 'owner', 'label': N_('Owner')}
        field['type'] = 'text'
        fields.append(field)

        simple_string_fields = [
            ('uuid', N_('Crash identifier') ),
            ('applicationname', N_('Application') ),
            ('applicationfile', N_('Application file') ),
            ('uploadhostname', N_('Upload FQDN') ),
            ('uploadusername', N_('Upload username') ),
            ('crashhostname', N_('Crash FQDN') ),
            ('crashusername', N_('Crash username') ),
            ('productname', N_('Product name') ),
            ('productcodename', N_('Product code name') ),
            ('productversion', N_('Product version') ),
            ('producttargetversion', N_('Product target version') ),
            ('buildtype', N_('Build type') ),
            ('buildpostfix', N_('Build postfix') ),
            ('machinetype', N_('Machine type') ),
            ('systemname', N_('System name') ),
            ('osversion', N_('OS version') ),
            ('osrelease', N_('OS release') ),
            ('osmachine', N_('OS machine') ),
            ('minidumpfile', N_('Minidump file') ),
            ('minidumpreporttextfile', N_('Minidump text report') ),
            ('minidumpreportxmlfile', N_('Minidump XML report') ),
            ('minidumpreporthtmlfile', N_('Minidump HTML report') ),
            ('coredumpfile', N_('Coredump file') ),
            ('coredumpreporttextfile', N_('Coredump text report') ),
            ('coredumpreportxmlfile', N_('Coredump XML report') ),
            ('coredumpreporthtmlfile', N_('Coredump HTML report') ),
            ]
        for (name, label) in simple_string_fields:
            fields.append({'name': name, 'type': 'text', 'label': label})

        # Description
        fields.append({'name': 'description', 'type': 'textarea',
                       'label': N_('Description')})

        # Default select and radio fields
        selects = [('type', N_('Type'), model.Type),
                   ('status', N_('Status'), model.Status),
                   ('priority', N_('Priority'), model.Priority),
                   ('milestone', N_('Milestone'), model.Milestone),
                   ('component', N_('Component'), model.Component),
                   ('version', N_('Version'), model.Version),
                   ('severity', N_('Severity'), model.Severity),
                   ('resolution', N_('Resolution'), model.Resolution)]
        for name, label, cls in selects:
            options = [val.name for val in cls.select(self.env)]
            if not options:
                # Fields without possible values are treated as if they didn't
                # exist
                continue
            field = {'name': name, 'type': 'select', 'label': label,
                     'value': getattr(self, 'default_' + name, ''),
                     'options': options}
            if name in ('status', 'resolution'):
                field['type'] = 'radio'
                field['optional'] = True
            elif name in ('milestone', 'version'):
                field['optional'] = True
            fields.append(field)

        # Advanced text fields
        fields.append({'name': 'keywords', 'type': 'text', 'format': 'list',
                       'label': N_('Keywords')})
        fields.append({'name': 'cc', 'type': 'text',  'format': 'list',
                       'label': N_('Cc')})

        # Date/time fields
        fields.append({'name': 'crashtime', 'type': 'time',
                       'label': N_('Crash time')})
        fields.append({'name': 'reporttime', 'type': 'time',
                       'label': N_('Report time')})
        fields.append({'name': 'uploadtime', 'type': 'time',
                       'label': N_('Upload time')})
        fields.append({'name': 'changetime', 'type': 'time',
                       'label': N_('Modified')})
        fields.append({'name': 'closetime', 'type': 'time',
                       'label': N_('Closed')})

        for field in self.get_custom_fields():
            if field['name'] in [f['name'] for f in fields]:
                self.log.warning('Duplicate field name "%s" (ignoring)',
                                 field['name'])
                continue
            if field['name'] in self.reserved_field_names:
                self.log.warning('Field name "%s" is a reserved name '
                                 '(ignoring)', field['name'])
                continue
            if not re.match('^[a-zA-Z][a-zA-Z0-9_]+$', field['name']):
                self.log.warning('Invalid name for custom field: "%s" '
                                 '(ignoring)', field['name'])
                continue
            field['custom'] = True
            fields.append(field)

        return fields