= Writing Plugins for Trac = [[PageOutline]] Starting with version [milestone:0.9 0.9], you can develop plugins for Trac that extend the builtin functionality. The plugin functionality is based on the [wiki:TracDev/ComponentArchitecture component architecture], so please read that document before continuing here. == Extension points == Trac offers an increasing number of ''extension points'' that allow you to plugin custom extensions for various functions. You can view a list of provided extension points on the page ''About !Trac/Plugins'' of your Trac installation. === Trac Extension Points === Below you find a still incomplete list of extension points available in the current trac release ([milestone:0.12 0.12]). If you want to contribute documentation on any of the below described extension point interfaces, feel free to add the documentation. Please use the template [wiki:TracDev/PluginDevelopment/ExtensionPointDocumentationTemplate] when authoring new documentation. ||='''Extension Point Interface'''[[br]]'''Source''' =||='''Description''' =|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.env.IEnvironmentSetupParticipant trac.env.IEnvironmentSetupParticipant][[br]]([source:trunk/trac/env.py source])||Plugins that provide their own data models must implement this interface to be able to create the required tables in the database on either the creation of a new environment, or during the upgrade of an existing environment.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.db.api.IDatabaseConnector trac.db.api.IDatabaseConnector][[br]]([source:/trunk/trac/db/api.py source])||By implementing this interface you can add additional database backends to the system. Currently available are backends for MySQL, PostgreSQL and SQLITE2/3.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.admin.api.IAdminCommandProvider trac.admin.api.IAdminCommandProvider][[br]]([source:/trunk/trac/admin/api.py source])||Allows plugins to add additional commands to the trac-admin console command.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.admin.api.IAdminPanelProvider trac.admin.api.IAdminPanelProvider][[br]]([source:trunk/trac/admin/api.py source])||Allows plugins to add additional admin panels to the web-based administration module.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.mimeview.api.IContentConverter trac.mimeview.api.IContentConverter][[br]]([source:trunk/trac/mimeview/api.py source])||Allows plugins to implement conversion strategies for arbitrary content types. (NOTE: API is likely to change in the future)|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.mimeview.api.IHTMLPreviewAnnotator trac.mimeview.api.IHTMLPreviewAnnotator][[br]]([source:trunk/trac/mimeview/api.py source])||Allows plugins to add additional information to an XHTML representation of a given file, for example meta data on the file and so on.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.mimeview.api.IHTMLPreviewRenderer trac.mimeview.api.IHTMLPreviewRenderer][[br]]([source:trunk/trac/mimeview/api.py source])||Allows plugins to provide support for rendering specific content of a specific type as HTML (used for TracSyntaxColoring and image preview).|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.notification.IEmailSender trac.notification.IEmailSender][[br]]([source:trunk/trac/notification.py source])||Implement this interface to be able to send notification mails.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.perm.IPermissionGroupProvider trac.perm.IPermissionGroupProvider][[br]]([source:trunk/trac/perm.py source])||Implement this extension point interface when you can provide information on groups that a given user is a member of.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.perm.IPermissionPolicy trac.perm.IPermissionPolicy][[br]]([source:trunk/trac/perm.py source])||By implementing this interface you can do more finely grained permission checks.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.perm.IPermissionRequestor trac.perm.IPermissionRequestor][[br]]([source:trunk/trac/perm.py source])||Plugins can use this extension point to define additional "actions", or permission roles, for the permission system.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.perm.IPermissionStore trac.perm.IPermissionStore][[br]]([source:trunk/trac/perm.py source])||Plugins can use this extension point to implement a storage for permissions and also to handle user authorization.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/prefs/trac.prefs.api.IPreferencePanelProvider trac.prefs.api.IPreferencePanelProvider][[br]]([source:trunk/trac/api.py source])||Allows plugins to add additional preferences panels to the user's preferences page.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.resource.IResourceManager trac.resource.IResourceManager][[br]]([source:trunk/trac/resource.py source])||Allows plugins to take over full control of the realms that they provide, incl. also introducing new resource types to the system.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.search.api.ISearchSource trac.search.api.ISearchSource][[br]]([source:trunk/trac/search/api.py source])||Allows plugins to provide additional searchable (re)sources to the system.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.ticket.api.ITicketActionController trac.ticket.api.ITicketActionController][[br]]([source:trunk/trac/ticket/api.py source])||Allows plugins to participate in a ticket's workflow.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.ticket.api.ITicketChangeListener trac.ticket.api.ITicketChangeListener][[br]]([source:trunk/trac/ticket/api.py source])||Extension point interface for components that require notification on when tickets are created, modified, or deleted.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.ticket.api.ITicketManipulator trac.ticket.api.ITicketManipulator][[br]]([source:trunk/trac/ticket/api.py source])||Allows plugins to both prepare tickets on creation and also to validate them prior to that they get stored in the database.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.ticket.api.IMilestoneChangeListener trac.ticket.api.IMilestoneChangeListener][[br]]([source:trunk/trac/ticket/api.py source])||Allows plugins to listen on changes to either existing or newly created milestones.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.ticket.roadmap.ITicketGroupStatsProvider trac.ticket.roadmap.ITicketGroupStatsProvider][[br]]([source:trunk/trac/ticket/roadmap.py source])||Allows plugins to implement their own scheme of ticket stats, and provide that to the system via the !RoadmapModule.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.timeline.ITimelineEventProvider trac.timeline.ITimelineEventProvider][[br]]([source:trunk/trac/timeline/api.py source])||Allows plugins to contribute events to the [wiki:TracTimeline timeline].|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.web.api.IAuthenticator trac.web.api.IAuthenticator][[br]]([source:trunk/trac/web/api.py source])||Allows plugins to authenticate users and HTTP sessions thereof. The first authenticator able to authenticate a user is the authoritative authenticator, meaning that other authenticators available in the system will not be called. Users that cannot be authenticated by the request and an associated session thereof are called 'anonymous'.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.web.api.IRequestFilter trac.web.api.IRequestFilter][[br]]([source:trunk/trac/web/api.py source])||Allows plugins to both preprocess and postprocess HTTP requests.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.web.api.IRequestHandler trac.web.api.IRequestHandler][[br]]([source:trunk/trac/web/api.py source])||Allows plugins to process HTTP requests.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.web.api.ITemplateStreamFilter trac.web.api.ITemplateStreamFilter][[br]]([source:trunk/trac/web/api.py source])||Allows plugins to filter existing [http://genshi.edgewall.org. Genshi] streams prior to that the template will be rendered.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.web.chrome.INavigationContributor trac.web.chrome.INavigationContributor][[br]]([source:trunk/trac/web/chrome.py source])||Allows plugins to extend the navigation menus of the web interface.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.web.chrome.ITemplateProvider trac.web.chrome.ITemplateProvider][[br]]([source:trunk/trac/web/chrome.py source])||Extension point interface for components that provide their own templates and accompanying static resources.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.wiki.api.IWikiChangeListener trac.wiki.api.IWikiChangeListener][[br]]([source:trunk/trac/wiki/api.py source])||Allows plugins to observe creation, modification and renaming (since trac-0.12), and deletion of wiki pages.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.wiki.api.IWikiMacroProvider trac.wiki.api.IWikiMacroProvider][[br]]([source:trunk/trac/wiki/api.py source])||Allows plugins to contribute WikiMacros to Trac.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.wiki.api.IWikiPageManipulator trac.wiki.api.IWikiPageManipulator][[br]]([source:trunk/trac/wiki/api.py source])||Allows plugins to validate wiki pages prior to that they get stored in the database.|| ||[wiki:TracDev/PluginDevelopment/ExtensionPoints/trac.wiki.api.IWikiSyntaxProvider trac.wiki.api.IWikiSyntaxProvider][[br]]([source:trunk/trac/wiki/api.py source])||Plugins can extend this extension point to add custom syntax rules to the wiki formatting system. In particular, this allows registration of additional TracLinks types.|| ''Note that plugins can themselves add new extension points, so the list above is incomplete by nature.'' == Writing the plugin code == To extend Trac with a custom plugin, you need to implement a ''component''. For example, to add a new web module to Trac (i.e. a component that handles HTTP requests and extends the navigation bar), you'd start with something like the following code: {{{ #!python from trac.core import * from trac.util.html import html from trac.web import IRequestHandler from trac.web.chrome import INavigationContributor class HelloWorldPlugin(Component): implements(INavigationContributor, IRequestHandler) # INavigationContributor methods def get_active_navigation_item(self, req): return 'helloworld' def get_navigation_items(self, req): yield ('mainnav', 'helloworld', html.A('Hello world', href= req.href.helloworld())) # IRequestHandler methods def match_request(self, req): return req.path_info == '/helloworld' def process_request(self, req): req.send_response(200) req.send_header('Content-Type', 'text/plain') req.end_headers() req.write('Hello world!') }}} Look at the API documentation for the extension point interfaces to see what you're expected to return. See the [http://trac-hacks.org/wiki/tutorial tutorial] page for more plugin tutorials. == Component member variables == Every component that gets instantiated through the Trac environment gets three extra member variables for convenience: * {{{env}}}: The environment, an instance of the {{{trac.env.Environment}}} class (see [source:/trunk/trac/env.py trac.env]). * {{{config}}}: The configuration, an instance of the {{{trac.config.Configuration}}} class (see [source:/trunk/trac/config.py trac.config]). * {{{log}}}: The configured logger, see the Python [http://docs.python.org/lib/module-logging.html logging API] for more information. These variables can also be accessed from the initializer (`__init__`) of a component. Storing any other objects as instance variables of your component is probably a bad idea: remember that a component is only instantiated once for a given environment; unless your plugin is used in a CGI deployment of Trac, that means that the same component instance will get invoked for multiple HTTP requests; if the server is multi-threaded, this will even happen concurrently. == Packaging plugins == TracPlugins are packaged as [http://peak.telecommunity.com/DevCenter/PythonEggs Python Eggs]. You can use [http://peak.telecommunity.com/DevCenter/setuptools setuptools] to make a `setup.py` script that will produce a Python egg for your plugin. The egg needs to export an [http://peak.telecommunity.com/DevCenter/setuptools#dynamic-discovery-of-services-and-plugins entry point] group named `trac.plugins`, listing the names of the modules that Trac should import for the plugin-provided components to get registered. For example: {{{ #!python from setuptools import find_packages, setup setup( name='TracHelloWorld', version='1.0', packages=find_packages(exclude=['*.tests*']), entry_points = """ [trac.plugins] helloworld = myplugs.helloworld """, ) }}} This assumes that the `HelloWorldPlugin` example above is defined in the module `helloworld.py` in the `myplugs` package. The entry point ''name'' (in this example “helloworld”) is required by the Python egg runtime, but not currently used by Trac. In most cases, you can simply use the qualified module name there. ''For backwards-compatibility reasons, Trac still supports an alternative to entry points: you can have a file named `trac_plugin.txt` in the `EGG-INFO` directory. This file should contain the names of all modules that need to be imported by Trac to register your components. Note that this method is '''deprecated''' and will be removed in a future version of Trac (probably as soon as [milestone:0.11]).'' == Internationalization/Localization of plugins == '' relevant since trac-0.12 introduced i18n/l10n support for Trac utilizing Babel'' See the [wiki:CookBook/PluginL10N plugin i18n/l10n cookbook page] for details. == Plugin deployment == A plugin can either be deployed globally, or only for a specific environment. Global deployment is done by installing the plugin: {{{ $ cd /path/to/pluginsource $ python setup.py install }}} To deploy a plugin only to a specific Trac environment, copy the egg file into the `plugins` directory of that environment: {{{ $ cd /path/to/pluginsource $ python setup.py bdist_egg $ cp dist/*.egg /path/to/projenv/plugins }}} During development of a plugin, it is inconvenient to have to install it in either of the ways described above. Instead, you should use the setuptools `develop` command: {{{ $ cd /path/to/pluginsource $ python setup.py develop --multi-version --install-dir /path/to/projenv/plugins }}} or the short version: {{{ $ python setup.py develop -md /path/to/projenv/plugins }}} You can omit the `--install-dir` and `--multi-version` arguments to make the development version of your plugin available globally. This will install an `.egg-link` file instead of the actual egg. That file is basically a link to the source directory of your plugin, so that Trac will always see the latest version of your code. In this case you will have to explicitly enable your plugin in the trac configuration as explained on TracPlugins. A tutorial to build your own plugins is available [http://trac-hacks.org/wiki/EggCookingTutorial here]. == Disabling built-in components == Sometimes you might want to write a plugin that completely replaces a built-in component, for example to develop an advanced variant of an existing module. Trac uses a list of default component to load, as specified in the `default_components` list in [source:/trunk/trac/db_default.py#latest trac.db_default]. These built-in components are always loaded, and might therefore conflict with your replacement plugin. You can however disable built-in components using a special [wiki:TracIni trac.ini] section called `[components]`. This section contains the qualified name of the components to disable, along with `disabled` or `off` as the value. For example, to disable the built-in Wiki macro `RecentChanges`, you'd include the following in [wiki:TracIni trac.ini]: {{{ [components] trac.wiki.macros.RecentChangesMacro = disabled }}} You can also use a wildcard at the end of a name, so you could even disable the complete Wiki module (although wiki formatting will still work in the remaining modules, of course): {{{ [components] trac.wiki.* = disabled }}} == Debugging == The logging API is very good debugging tool. Simply use this code when you want to view value of a variable: {{{ #!python env.log.debug("*** Hey, varname is %r ***", varname) }}} where `env` is the `Environment` instance. If you are inside the methods of a `Component` subclass, better use: {{{ #!python self.log.debug("Hey, varname is %r", varname) }}} This will implicitly use the `self.env` Environment, but your component name will now be used for the $module (see TracLogging#LogFormat). This makes it easier to identify the relevant debug lines. Note that there's no way to log something at the global level, outside the scope of a Trac environment, as the configuration of logging is done at that level and usually the log file is located in `$tracenv/log/trac.log`. ---- See also: TracDev, TracDev/ComponentArchitecture, TracPlugins, TracHacks:wiki:EggCookingTutorialTrac0.11