Customizing PyDitz

There are several ways to customize the way PyDitz operates. The most basic is to adjust the settings in your Configuration File. But you can also change the look and feel of the exported issue database, and add your own export formats and PyDitz commands. This section tells you how to do these things.

Customizing exported data

PyDitz uses two types of file when exporting its data: static files, and template files. Static files are copied verbatim from where they’re found into the destination directory, whereas template files are modified by inserting content into them at various points (and typically reference the static files). The engine used to insert template content is Jinja2.

Static and template files for the builtin export formats are found by default in PyDitz’s installed package directory. But other places are searched first, and this is where you can put customized files to override things. The search path for files is (in order of searching):

  1. The directory where issues are stored for the project
  2. HOMEDIR/.ditz
  3. PyDitz’s installed package directory

When exporting issues in a given format, PyDitz looks in each of these directories for subdirectories:

DIR/static/FORMAT
Static files for the export format
DIR/templates/FORMAT
Template files for the export format

Modifying static files

So, for example, if you wanted to modify the stylesheet for the HTML output for a particular PyDitz project, you could do the following:

  1. Generate HTML normally.
  2. Copy the style.css file from the generated output to ISSUEDIR/static/html/style.css.
  3. Modify style.css as required, putting any extra static files (e.g., images referenced by the style sheet) in the same directory.

If you wanted to modify the stylesheet for all PyDitz projects, you’d copy the files into subdirectories of HOMEDIR/.ditz instead.

Modifying template files

If you want to modify a template, the same procedure applies: copy the appropriate template file from the PyDitz installation directory to the corresponding place below either the project issue directory or HOMEDIR/.ditz, and modify it. This involves knowing how Jinja2 templating works (which I will summarize here when I get around to it GRIN).

Builtin export formats

At the moment, there’s only one builtin export format: HTML. It has these static files:

style.css: The HTML style sheet.

sorttable.js: Some JavaScript to enable sorting of HTML tables. Referenced by the style sheet.

*.png: The small images used to illustrate issue status, next to their titles, in the output.

and these template files:

base.html: The base HTML page template all the others are derived from.

index.html: Template for the main index page.

issue.html: Template for a single issue page.

component.html: Template for a component page.

release.html: Template for a release summary page.

unassigned.html: Template for the page of issues unassigned to any release.

macros.html: Comon Jinja macros used by the other templates.

Creating new export formats

You can write your own PyDitz export formats by writing a plugin which subclasses the Exporter class. An exporter has a bunch of class attributes which you should set appropriately:

Exporter.name = None

Name of the exporter. This is the argument given to the export command.

Exporter.description = 'undocumented'

One-line description. This gets printed when you list the available export formats.

Exporter.suffix = None

Exporter file suffix. This is usually the same as the exporter name string.

Exporter.static_dir = None

If the exporter uses static files, this is the subdirectory of static to find them. Usually the same as the name attribute.

Exporter.template_dir = None

If the exporter uses templates, this is the subdirectory of templates to find them. Usually the same as the name attribute.

What happens during export

Here’s an outline of what happens on export:

  1. The setup() method is called, to initialise Jinja filters and export configuration variables. Here’s where you can define filters (via add_filter()) or configuration variables (via the config attribute). The exporter db attribute is the Ditz issue database being exported, which you can use to set up the filters.

    Exporter.setup()[source]

    Do exporter-specific setup.

    By default, this does nothing.

    Exporter.add_filter(func, name=None)[source]

    Add a custom Jinja filter.

    This returns the original function, so the method can be used as a decorator.

    Parameters:
    • func (callable) – Filter function.
    • name (str, optional) – Name to use in templates (same as function name, if None).
    Returns:

    original function

    Return type:

    func

  2. The write() method is called to do the actual exporting. If using templates, this should use the render() method to render them.

    The export_filename() method is available to create a standard filename for each issue database item, using the exporter suffix. Or, you can generate your own.

    Exporter.write(dirname)[source]

    Write exported files to the given directory.

    This method must be overridden. If using templates, it should call the render() method.

    Parameters:dirname (str) – Directory to write files into.
    Exporter.render(dirname, templatefile, targetfile=None, **kw)[source]

    Render a single file from a template.

    Parameters:
    • dirname (str) – Directory to write file into.
    • templatefile (str) – Jinja template to use.
    • targetfile (str, optional) – Filename to write (same as templatefile, if None).
    • kw (dict) – Template parameters.
    Exporter.export_filename(item)[source]

    Return a unique export filename for a Ditz item.

    The filename is based on the item’s ID (if it’s an issue) or its name. Each filename has the exporter suffix appended to it.

    Parameters:item (DitzObject) – Ditz item.
    Returns:Filename string.

The exporter has several attribute which can be used during setup and export:

  • The db attribute is a DitzDB object.

  • The config attribute is a ConfigSection object which allows you to access those settings of the user configuration file which control the export.

    The values of these can be set in the [export] section of a config file, prepending the name of the exporter and an underscore. For example, for the html exporter, the variable foo can be referred to as html_foo.

    The write() method can access the value of configuration variables by calling methods of the exporter’s config object.

Creating new PyDitz commands

New in version 0.9: Command plugins.

You can also write your own PyDitz commands by writing a plugin which subclasses the CommandSet class. The class you create should contain one more new commands as described in the Cmd documentation—i.e., methods with names of the form do_cmd and help_cmd. These are copied into the DitzCmd class after loading the plugin, and you can use methods and attributes of that class in your plugin.

Commands should use the write() method to write output to the console, and the error() method to give an error message (and exit the command). Here’s an example command plugin:

from ditz.commands import CommandSet

class AdventCommands(CommandSet):
    name = 'advent'
    description = 'adventuring commands'

    def do_xyzzy(self, arg):
        self.write("Nothing happens.")

    def help_xyzzy(self):
        self.write("xyzzy -- a secret magic word")

    def do_plugh(self, arg):
        "plugh -- another secret magic word"
        self.write("Nothing happens.")

The DitzCmd object has several attributes which can be used when running the command:

  • The db attribute is a DitzDB object.

  • The config attribute is a ConfigSection object which allows you to access those settings of the user configuration file which control how the command works.

    The values of these can be set in the [command] section of a user config file, prepending the name of the command and an underscore. For example, for the log command, the variable foo can be referred to as log_foo.

The PyDitz Customization API

Warning

This API may change incompatibly before PyDitz 1.0.

DitzCmd – the command interpreter

DitzCmd.db = None

The DitzDB database.

DitzCmd.interactive = False

Whether running interactively.

DitzCmd.getissue(name)[source]

Get an issue by name or ID. Return it and its assigned name.

Parameters:name (str) – Issue name or ID.
Returns:Tuple(Issue, str).
DitzCmd.getrelease(name)[source]

Get a release and return it.

Parameters:name (str or None) – Release name.
DitzCmd.getline(prompt='> ', default='', allowempty=True)[source]

Get a single line of input.

Parameters:
  • prompt (str) – Prompt string.
  • default (str) – Default value.
  • allowempty (bool) – Whether value is optional.
Returns:

Reply (str).

DitzCmd.gettext(title=None, prompt='> ', endchar='.')[source]

Get multiline text.

Parameters:
  • title (str) – Initial title text.
  • prompt (str) – Prompt for each line of input.
  • endchar (str) – Character terminating input.
Returns:

Text (str).

DitzCmd.getchoice(thing, choices)[source]

Get a choice of several things.

Parameters:
  • thing (str) – Description of thing being chosen.
  • choices (list) – List of available options.
Returns:

Choice made (str).

DitzCmd.getcomment(comment='')[source]

Get a comment string.

Parameters:comment (str) – Comment given in command option.
Returns:Comment text (str).
DitzCmd.getyesno(question, default=False)[source]

Get the answer to a yes/no question.

Parameters:
  • question (str) – The question.
  • default (bool) – Default if no reply given.
Returns:

Reply (bool).

DitzCmd.write(*args)[source]

Write the given list of arguments to stdout.

Also performs syntax highlighting and paging.

DitzCmd.error(*args)[source]

Signal an error.

DitzCmd.unimplemented()[source]

Write a ‘not implemented yet’ message.

DitzDB – an issue database

DitzDB.project = None

Project (a Project object).

DitzDB.issues = []

List of issues. Each entry is an Issue.

DitzDB.issue_events

Yield all issue-related events and their issues.

Each yielded event is a tuple containing:

  • date (datetime)
  • user (string)
  • description (string)
  • comment (string)
  • issue (Issue)
DitzDB.convert_to_name(text, idmap=None)[source]

Replace {issue ...} with names in issue text.

Parameters:
  • text (str) – Text to convert.
  • idmap (dict, optional) – ID mapping.

If the ID mapping is specified and contains the issue’s ID as a key, the replacement is the mapping value. Otherwise, it’s the issue’s name.

Returns:Converted text (str).

Project – a container for components and releases

Project.name = ''

Name of the project.

Project.components = []

List of components. Each entry is a Component.

Project.releases = []

List of releases. Each entry is a Release.

Component – a single issue component

Component.name = ''

Name of the component.

Release – a single release

Release.name = None

Name of the release.

Release.released

Whether release has actually been released.

Issue – a single issue

Issue.attributes = ['title', 'desc', 'type', 'component', 'release', 'reporter', 'claimer', 'status', 'disposition', 'creation_time', 'references', 'id', 'log_events']

Attributes that get saved to file.

Issue.name

Name of the issue. This is the same as its title.

Issue.closed

Whether the issue is closed.

Issue.progress_time

Total seconds the issue’s state has been in-progress.

Issue.references = []

List of references. Each entry is a string.

Issue.log_events = []

List of log events. Each entry is a tuple of (time, username, text, comment).

ConfigSection – configuration values

ConfigSection.add(name, default)[source]

Add a named configuration value.

ConfigSection.get(name)[source]

Get the named configuration value as a string.

ConfigSection.getint(name)[source]

Get the named configuration value as an integer.

ConfigSection.getfloat(name)[source]

Get the named configuration value as a float.

ConfigSection.getboolean(name)[source]

Get the named configuration value as a boolean.