/usr/share/pyshared/customfieldadmin/api.py is in trac-customfieldadmin 0.2.6+r10460-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | # -*- coding: utf-8 -*-
"""
API for administrating custom ticket fields in Trac.
Supports creating, getting, updating and deleting custom fields.
License: BSD
(c) 2005-2011 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
"""
import re
from trac.core import *
from trac.ticket.api import TicketSystem
__all__ = ['CustomFields']
class CustomFields(Component):
    """ These methods should be part of TicketSystem API/Data Model.
    Adds update_custom_field and delete_custom_field methods.
    (The get_custom_fields is already part of the API - just redirect here,
     and add option to only get one named field back.)
    
    Input to methods is a 'customfield' dict supporting these keys:
        name = name of field (alphanumeric only)
        type = text|checkbox|select|radio|textarea
        label = label description
        value = default value for field content
        options = options for select and radio types (list, leave first empty for optional)
        cols = number of columns for text area
        rows = number of rows for text area
        order = specify sort order for field
        format = text|wiki (for text and textarea)
    """
    
    implements()
    
    def get_custom_fields(self, customfield=None):
        """ Returns the custom fields from TicketSystem component.
        Use a cfdict with 'name' key set to find a specific custom field only.
        """
        if not customfield:    # return full list
            return TicketSystem(self.env).get_custom_fields()
        else:                  # only return specific item with cfname
            all = TicketSystem(self.env).get_custom_fields()
            for item in all:
                if item['name'] == customfield['name']:
                    return item
            return None        # item not found
    
    def verify_custom_field(self, customfield, create=True):
        """ Basic validation of the input for modifying or creating
        custom fields. """
        # Name, Type and Label is required
        if not (customfield.get('name') and customfield.get('type') \
                and customfield.get('label')):
            raise TracError("Custom field needs at least a name, type and label.")
        # Use lowercase custom fieldnames only
        customfield['name'] = customfield['name'].lower()
        # Only alphanumeric characters (and [-_]) allowed for custom fieldname
        if re.search('^[a-z][a-z0-9_]+$', customfield['name']) == None:
           raise TracError("Only alphanumeric characters allowed for custom field name "
                "('a-z' or '0-9' or '_'), with 'a-z' as first character.")
        # Name must begin with a character - anything else not supported by Trac
        if not customfield['name'][0].isalpha():
            raise TracError("Custom field name must begin with a character (a-z).")
        # Check that it is a valid field type
        if not customfield['type'] in ['text', 'checkbox', 'select', 'radio', 'textarea']:
            raise TracError("%s is not a valid field type" % customfield['type'])
        # Check that field does not already exist (if modify it should already be deleted)
        if create and self.config.get('ticket-custom', customfield['name']):
            raise TracError("Can not create as field already exists.")
    
    def create_custom_field(self, customfield):
        """ Create the new custom fields (that may just have been deleted as part
        of 'modify'). Note: Caller is responsible for verifying input before create."""
        # Set the mandatory items
        self.config.set('ticket-custom', customfield['name'], customfield['type'])
        self.config.set('ticket-custom', customfield['name'] + '.label', customfield['label'])
        # Optional items
        if 'value' in customfield:
            self.config.set('ticket-custom', customfield['name'] + '.value', customfield['value'])
        if 'options' in customfield:
            if customfield.get('optional', False):
                self.config.set('ticket-custom', customfield['name'] + '.options',
                                '|' + '|'.join(customfield['options']))
            else:
                self.config.set('ticket-custom', customfield['name'] + '.options',
                               '|'.join(customfield['options'])) 
        if 'format' in customfield and customfield['type'] in ('text', 'textarea'):
            self.config.set('ticket-custom', customfield['name'] + '.format', customfield['format'])
        # Textarea
        if customfield['type'] == 'textarea':
            cols = customfield.get('cols') and int(customfield.get('cols', 0)) > 0 \
                                                and customfield.get('cols') or 60
            rows = customfield.get('rows', 0) and int(customfield.get('rows', 0)) > 0 \
                                                and customfield.get('rows') or 5
            self.config.set('ticket-custom', customfield['name'] + '.cols', cols)
            self.config.set('ticket-custom', customfield['name'] + '.rows', rows)
        # Order
        order = customfield.get('order', "")
        if order == "":
            order = len(self.get_custom_fields())
        self.config.set('ticket-custom', customfield['name'] + '.order', order)
        self.config.save()
    def update_custom_field(self, customfield, create=False):
        """ Updates a custom. Option to 'create' is kept in order to keep
        the API backwards compatible. """
        if create:
            self.verify_custom_field(customfield)
            self.create_custom_field(customfield)
            return
        # Check input, then delete and save new
        self.verify_custom_field(customfield, create=False)
        self.delete_custom_field(customfield, modify=True)
        self.create_custom_field(customfield)
    
    def delete_custom_field(self, customfield, modify=False):
        """ Deletes a custom field.
        Input is a dictionary (see update_custom_field), but only ['name'] is required.
        """
        if not self.config.get('ticket-custom', customfield['name']):
            return # Nothing to do here - cannot find field
        if not modify:
            # Permanent delete - reorder later fields to lower order
            order_to_delete = self.config.getint('ticket-custom', customfield['name']+'.order')
            cfs = self.get_custom_fields()
            for field in cfs:
                if field['order'] > order_to_delete:
                    self.config.set('ticket-custom', field['name']+'.order', field['order'] -1 )
        # Remove any data for the custom field (covering all bases)
        for option, _value in self.config.options('ticket-custom'):
            if option == customfield['name'] \
                    or option.startswith(customfield['name'] + '.'):
                self.config.remove('ticket-custom', option)
        # Persist permanent deletes
        if not modify:
            self.config.save()
 |