ComponentModule Examples
Some interfaces are not very useful (but possible) when implemented in isolation and are usually accompanied by implementations of other interfaces. It is therefore difficult to provide meaningful minimal examples. It is more useful to look at a few related examples together.
Trac allows assigning tickets to components, but has no full-fledged component module. This provides an opportunity to build (parts of) such a component to illustrate such interfaces in this context.
Per-Interface examples
The following interface descriptions contain minimal examples that can be viewed as partial pieces that together make up a slightly more realistic (but still simplistic) ComponentModule
:
- IPermissionRequestor
- IResourceManager
- IRequestHandler
- INavigationContributor
- ITemplateProvider
- ILegacyAttachmentPolicyDelegate
Full example
One could combine and slightly expand the above examples into one module trac/component/web_ui.py
:
import pkg_resources import re from genshi.builder import tag from trac.attachment import AttachmentModule, ILegacyAttachmentPolicyDelegate from trac.core import * from trac.mimeview.api import Context from trac.perm import IPermissionRequestor from trac.resource import IResourceManager, Resource from trac.ticket import model from trac.util.text import shorten_line from trac.util.translation import _ from trac.web import IRequestHandler from trac.web.chrome import INavigationContributor, ITemplateProvider class ComponentModule(Component): implements(IPermissionRequestor, ILegacyAttachmentPolicyDelegate, IResourceManager, IRequestHandler, INavigationContributor, ITemplateProvider) # IPermissionRequestor methods def get_permission_actions(self): return ('COMPONENT_LIST', 'COMPONENT_VIEW') # ILegacyAttachmentPolicyDelegate methods def check_attachment_permission(self, action, username, resource, perm): if resource.parent.realm == 'component': if action == 'ATTACHMENT_VIEW': return 'COMPONENT_VIEW' in perm(resource.parent) elif action in ('ATTACHMENT_CREATE','ATTACHMENT_DELETE'): return 'TICKET_ADMIN' in perm(resource.parent) # IResourceManager methods def get_resource_realms(self): yield 'component' def get_resource_url(self, resource, href, **kwargs): return href.component(resource.id) def get_resource_description(self, resource, format='default', context=None, **kwargs): if format == 'compact': return resource.id desc = _('Component %(name)s', name=resource.id) if format == 'summary': comp = model.Component(self.env, resource.id) if comp.owner: desc += _(' (by %(owner)s)', owner=comp.owner) if comp.description: desc += ': %s' % shorten_line(comp.description) return desc def resource_exists(self, resource): return bool(self.env.db_query(""" SELECT name FROM component WHERE name=%s""", (resource.id,))) # IRequestHandler methods def match_request(self, req): match = re.match(r'/component(?:/(.+))?$', req.path_info) if match: if match.group(1): req.args['name'] = match.group(1) return True def process_request(self, req): if 'name' in req.args: name = req.args.get('name') req.perm('component', name).require('COMPONENT_VIEW') resource = Resource('component', name) context = Context.from_request(req, resource) attachments = AttachmentModule(self.env).attachment_data(context) data = {'component': model.Component(self.env, name), 'attachments': attachments} return 'component.html', data, None req.perm.require('COMPONENT_LIST') list = [{'name': c.name, 'href': self.get_resource_url(Resource('component', c.name), req.href)} for c in model.Component.select(self.env)] data = {'components': list} return 'component_list.html', data, None # INavigationContributor methods def get_active_navigation_item(self, req): return 'component' def get_navigation_items(self, req): if 'COMPONENT_LIST' in req.perm: yield ('mainnav', 'component', tag.a(_("Components"), href=req.href.component())) # ITemplateProvider methods def get_htdocs_dirs(self): return [] def get_templates_dirs(self): return [pkg_resources.resource_filename('trac.component', 'templates')]
With the templates in trac/component/templates/component.html
:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:py="http://genshi.edgewall.org/" xmlns:i18n="http://genshi.edgewall.org/i18n"> <xi:include href="layout.html" /> <head> <title>Component $component.name</title> </head> <body> <h2>Component $component.name</h2> <p>Owner: $component.owner</p> <p>$component.description</p> <xi:include href="list_of_attachments.html" py:with="alist = attachments; compact = True; foldable = True"/> <div class="buttons"> <xi:include href="attach_file_form.html" py:with="alist = attachments"/> </div> </body> </html>
and trac/component/templates/component_list.html
:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:py="http://genshi.edgewall.org/" xmlns:i18n="http://genshi.edgewall.org/i18n"> <xi:include href="layout.html" /> <head> <title>Components</title> </head> <body> <h2>Components</h2> <ul> <li py:for="c in components"> <a href="$c.href">$c.name</a> </li> </ul> </body> </html>