| 1 | """ |
|---|
| 2 | RepositoryHookAdmin: |
|---|
| 3 | admin panel interface for controlling hook setup and listeners |
|---|
| 4 | """ |
|---|
| 5 | |
|---|
| 6 | from pkg_resources import resource_filename |
|---|
| 7 | from repository_hook_system.interface import IRepositoryHookSystem |
|---|
| 8 | from repository_hook_system.interface import IRepositoryHookSubscriber |
|---|
| 9 | from trac.admin.api import IAdminPanelProvider |
|---|
| 10 | from trac.config import Option |
|---|
| 11 | from trac.core import * |
|---|
| 12 | from trac.web.chrome import ITemplateProvider |
|---|
| 13 | |
|---|
| 14 | class RepositoryHookAdmin(Component): |
|---|
| 15 | """webadmin panel for hook configuration""" |
|---|
| 16 | |
|---|
| 17 | implements(ITemplateProvider, IAdminPanelProvider) |
|---|
| 18 | listeners = ExtensionPoint(IRepositoryHookSubscriber) |
|---|
| 19 | |
|---|
| 20 | systems = ExtensionPoint(IRepositoryHookSystem) |
|---|
| 21 | # XXX maybe should be IRepositoryHookSetup? |
|---|
| 22 | # or perhaps thes IRepositoryHookSetup and IRepositoryChangeListener |
|---|
| 23 | # interfaces should be combined |
|---|
| 24 | |
|---|
| 25 | def system(self): |
|---|
| 26 | """returns the IRepositoryHookSystem appropriate to the repository""" |
|---|
| 27 | # XXX could abstract this, as this is not specific to TTW functionality |
|---|
| 28 | for system in self.systems: |
|---|
| 29 | if self.env.config.get('trac', 'repository_type') in system.type(): |
|---|
| 30 | return system |
|---|
| 31 | |
|---|
| 32 | |
|---|
| 33 | ### methods for ITemplateProvider |
|---|
| 34 | |
|---|
| 35 | """Extension point interface for components that provide their own |
|---|
| 36 | ClearSilver templates and accompanying static resources. |
|---|
| 37 | """ |
|---|
| 38 | |
|---|
| 39 | def get_htdocs_dirs(self): |
|---|
| 40 | """Return a list of directories with static resources (such as style |
|---|
| 41 | sheets, images, etc.) |
|---|
| 42 | |
|---|
| 43 | Each item in the list must be a `(prefix, abspath)` tuple. The |
|---|
| 44 | `prefix` part defines the path in the URL that requests to these |
|---|
| 45 | resources are prefixed with. |
|---|
| 46 | |
|---|
| 47 | The `abspath` is the absolute path to the directory containing the |
|---|
| 48 | resources on the local file system. |
|---|
| 49 | """ |
|---|
| 50 | return [] |
|---|
| 51 | |
|---|
| 52 | def get_templates_dirs(self): |
|---|
| 53 | """Return a list of directories containing the provided template |
|---|
| 54 | files. |
|---|
| 55 | """ |
|---|
| 56 | return [resource_filename(__name__, 'templates')] |
|---|
| 57 | |
|---|
| 58 | ### methods for IAdminPanelProvider |
|---|
| 59 | |
|---|
| 60 | """Extension point interface for adding panels to the web-based |
|---|
| 61 | administration interface. |
|---|
| 62 | """ |
|---|
| 63 | |
|---|
| 64 | def get_admin_panels(self, req): |
|---|
| 65 | """Return a list of available admin panels. |
|---|
| 66 | |
|---|
| 67 | The items returned by this function must be tuples of the form |
|---|
| 68 | `(category, category_label, page, page_label)`. |
|---|
| 69 | """ |
|---|
| 70 | if req.perm.has_permission('TRAC_ADMIN'): |
|---|
| 71 | system = self.system() |
|---|
| 72 | if system is not None and self.env.config.get('trac', 'repository_dir'): |
|---|
| 73 | for hook in system.available_hooks(): |
|---|
| 74 | yield ('repository_hooks', 'Repository Hooks', hook, hook) |
|---|
| 75 | |
|---|
| 76 | def render_admin_panel(self, req, category, page, path_info): |
|---|
| 77 | """Process a request for an admin panel. |
|---|
| 78 | |
|---|
| 79 | This function should return a tuple of the form `(template, data)`, |
|---|
| 80 | where `template` is the name of the template to use and `data` is the |
|---|
| 81 | data to be passed to the template. |
|---|
| 82 | """ |
|---|
| 83 | hookname = page |
|---|
| 84 | system = self.system() |
|---|
| 85 | data = {} |
|---|
| 86 | data['hook'] = hookname |
|---|
| 87 | |
|---|
| 88 | if req.method == 'POST': |
|---|
| 89 | |
|---|
| 90 | # implementation-specific post-processing |
|---|
| 91 | # XXX should probably handle errors, etc |
|---|
| 92 | system.process_post(hookname, req) |
|---|
| 93 | |
|---|
| 94 | # toggle invocation of the hook |
|---|
| 95 | if req.args.get('enable'): |
|---|
| 96 | system.enable(hookname) |
|---|
| 97 | else: |
|---|
| 98 | system.disable(hookname) |
|---|
| 99 | |
|---|
| 100 | # set available listeners on a hook |
|---|
| 101 | listeners = req.args.get('listeners', []) |
|---|
| 102 | if isinstance(listeners, basestring): |
|---|
| 103 | listeners = [ listeners ] # XXX ', '.join ? |
|---|
| 104 | self.env.config.set('repository-hooks', hookname, |
|---|
| 105 | ', '.join(listeners)) |
|---|
| 106 | |
|---|
| 107 | # process posted options to configuration |
|---|
| 108 | for listener in self.listeners: |
|---|
| 109 | name = listener.__class__.__name__ |
|---|
| 110 | options = self.options(listener) |
|---|
| 111 | args = dict([(key.split('%s-' % name, 1)[1], value) |
|---|
| 112 | for key, value in req.args.items() |
|---|
| 113 | if key.startswith('%s-' % name)]) |
|---|
| 114 | for option in options: |
|---|
| 115 | option_type = options[option]['type'] |
|---|
| 116 | section = options[option]['section'] |
|---|
| 117 | value = args.get(option, '') |
|---|
| 118 | if option_type == 'bool': |
|---|
| 119 | value = value == "on" and "true" or "false" |
|---|
| 120 | self.env.config.set(section, option, value) |
|---|
| 121 | |
|---|
| 122 | self.env.config.save() |
|---|
| 123 | |
|---|
| 124 | |
|---|
| 125 | data['enabled'] = system.is_enabled(hookname) |
|---|
| 126 | data['can_enable'] = system.can_enable(hookname) |
|---|
| 127 | activated = [ i.__class__.__name__ for i in system.subscribers(hookname) ] |
|---|
| 128 | data['snippet'] = system.render(hookname, req) |
|---|
| 129 | |
|---|
| 130 | data['listeners'] = [] |
|---|
| 131 | for listener in self.listeners: |
|---|
| 132 | _cls = listener.__class__ |
|---|
| 133 | data['listeners'].append(dict(name=_cls.__name__, |
|---|
| 134 | activated=(_cls.__name__ in activated), |
|---|
| 135 | description=listener.__doc__, |
|---|
| 136 | options=self.options(listener))) |
|---|
| 137 | |
|---|
| 138 | return ('repositoryhooks.html', data) |
|---|
| 139 | |
|---|
| 140 | def options(self, listener): |
|---|
| 141 | _cls = listener.__class__ |
|---|
| 142 | options = [ (i, getattr(_cls, i)) for i in dir(_cls) |
|---|
| 143 | if isinstance(getattr(_cls, i), Option) ] |
|---|
| 144 | options = dict([(option.name, dict(section=option.section, |
|---|
| 145 | type=option.__class__.__name__.lower()[:-6] or 'text', |
|---|
| 146 | value=getattr(listener, attr), |
|---|
| 147 | description=option.__doc__)) |
|---|
| 148 | for attr, option in options ]) |
|---|
| 149 | return options |
|---|