itota-trac-subtickets-plugin-a0fc153/0000755000175500017550000000000012043025237017367 5ustar debacledebacleitota-trac-subtickets-plugin-a0fc153/.gitignore0000644000175500017550000000003412043025237021354 0ustar debacledebacle*.pyc *.swp *.egg-info tags itota-trac-subtickets-plugin-a0fc153/tracsubtickets/0000755000175500017550000000000012043025237022421 5ustar debacledebacleitota-trac-subtickets-plugin-a0fc153/tracsubtickets/web_ui.py0000644000175500017550000001654712043025237024262 0ustar debacledebacle#!/usr/bin/python # # Copyright (c) 2010, Takashi Ito # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from trac.core import * from trac.web.api import IRequestFilter, ITemplateStreamFilter from trac.web.chrome import ITemplateProvider, add_stylesheet from trac.ticket.api import ITicketManipulator from trac.ticket.model import Ticket from trac.resource import ResourceNotFound from genshi.builder import tag from genshi.filters import Transformer from api import NUMBERS_RE, _ class SubTicketsModule(Component): implements(ITemplateProvider, IRequestFilter, ITicketManipulator, ITemplateStreamFilter) # ITemplateProvider methods def get_htdocs_dirs(self): from pkg_resources import resource_filename return [('subtickets', resource_filename(__name__, 'htdocs'))] def get_templates_dirs(self): return [] # IRequestFilter methods def pre_process_request(self, req, handler): return handler def post_process_request(self, req, template, data, content_type): path = req.path_info if path.startswith('/ticket/') or path.startswith('/newticket'): # get parent ticket's data if data and 'ticket' in data: ticket = data['ticket'] parents = ticket['parents'] or '' ids = set(NUMBERS_RE.findall(parents)) if len(parents) > 0: self._append_parent_links(req, data, ids) children = self.get_children(ticket.id) if children: data['subtickets'] = children return template, data, content_type def _append_parent_links(self, req, data, ids): links = [] for id in sorted(ids, key=lambda x: int(x)): try: ticket = Ticket(self.env, id) elem = tag.a('#%s' % id, href=req.href.ticket(id), class_='%s ticket' % ticket['status'], title=ticket['summary']) if len(links) > 0: links.append(', ') links.append(elem) except ResourceNotFound, e: pass for field in data.get('fields', ''): if field.get('name') == 'parents': field['rendered'] = tag.span(*links) # ITicketManipulator methods def prepare_ticket(self, req, ticket, fields, actions): pass def get_children(self, parent_id, db=None): children = {} if not db: db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT parent, child FROM subtickets WHERE parent=%s", (parent_id, )) for parent, child in cursor: children[child] = None for id in children: children[id] = self.get_children(id, db) return children def validate_ticket(self, req, ticket): action = req.args.get('action') if action == 'resolve': db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT parent, child FROM subtickets WHERE parent=%s", (ticket.id, )) for parent, child in cursor: if Ticket(self.env, child)['status'] != 'closed': yield None, _('Child ticket #%s has not been closed yet') % child elif action == 'reopen': ids = set(NUMBERS_RE.findall(ticket['parents'] or '')) for id in ids: if Ticket(self.env, id)['status'] == 'closed': yield None, _('Parent ticket #%s is closed') % id # ITemplateStreamFilter method def filter_stream(self, req, method, filename, stream, data): if req.path_info.startswith('/ticket/'): div = None if 'ticket' in data: # get parents data ticket = data['ticket'] # title div = tag.div(class_='description') if ticket['status'] != 'closed': link = tag.a(_('add'), href=req.href.newticket(parents=ticket.id), title=_('Create new child ticket')) link = tag.span('(', link, ')', class_='addsubticket') else: link = None div.append(tag.h3(_('Subtickets '), link)) if 'subtickets' in data: # table tbody = tag.tbody() div.append(tag.table(tbody, class_='subtickets')) # tickets def _func(children, depth=0): for id in sorted(children, key=lambda x: int(x)): ticket = Ticket(self.env, id) # 1st column attrs = {'href': req.href.ticket(id)} if ticket['status'] == 'closed': attrs['class_'] = 'closed' link = tag.a('#%s' % id, **attrs) summary = tag.td(link, ': %s' % ticket['summary'], style='padding-left: %dpx;' % (depth * 15)) # 2nd column type = tag.td(ticket['type']) # 3rd column status = tag.td(ticket['status']) # 4th column href = req.href.query(status='!closed', owner=ticket['owner']) owner = tag.td(tag.a(ticket['owner'], href=href)) tbody.append(tag.tr(summary, type, status, owner)) _func(children[id], depth + 1) _func(data['subtickets']) if div: add_stylesheet(req, 'subtickets/css/subtickets.css') stream |= Transformer('.//div[@id="ticket"]').append(div) return stream itota-trac-subtickets-plugin-a0fc153/tracsubtickets/checker.py0000644000175500017550000000645112043025237024405 0ustar debacledebacle#!/usr/bin/python # # Copyright (c) 2010, Takashi Ito # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import sys from optparse import OptionParser from trac.env import open_environment from api import NUMBERS_RE def check_subtickets(env): db = env.get_db_cnx() cursor = db.cursor() cfield = {} cursor.execute("SELECT ticket, value FROM ticket_custom WHERE name='parents'") for row in cursor: id = row[0] parents = [int(x) for x in NUMBERS_RE.findall(row[1])] cfield[id] = parents subtickets = {} cursor.execute("SELECT parent, child FROM subtickets") for row in cursor: parent = int(row[0]) child = int(row[1]) if child in subtickets: subtickets[child] += [parent] else: subtickets[child] = [parent] for id in set(cfield.keys() + subtickets.keys()): result = False if id in cfield and id in subtickets: cfield_values = set(cfield[id]) subtickets_values = set(subtickets[id]) if cfield_values == subtickets_values: result = True elif id not in subtickets: if not cfield.get(id): result = True if not result: print "Mismatch in ticket #%i" % id print " custom field :", cfield.get(id, '--') print " subtickets :", subtickets.get(id, '--') def main(args=sys.argv[1:]): parser = OptionParser('%prog [options] project ...') options, args = parser.parse_args(args) # if no projects, print usage if not args: parser.print_help() sys.exit(0) # get the environments envs = [] for arg in args: env = open_environment(arg) envs.append(env) # check all the environments for env in envs: check_subtickets(env) if __name__ == '__main__': main() itota-trac-subtickets-plugin-a0fc153/tracsubtickets/__init__.py0000644000175500017550000000000012043025237024520 0ustar debacledebacleitota-trac-subtickets-plugin-a0fc153/tracsubtickets/locale/0000755000175500017550000000000012043025237023660 5ustar debacledebacleitota-trac-subtickets-plugin-a0fc153/tracsubtickets/locale/de_DE/0000755000175500017550000000000012043025237024620 5ustar debacledebacleitota-trac-subtickets-plugin-a0fc153/tracsubtickets/locale/de_DE/LC_MESSAGES/0000755000175500017550000000000012043025237026405 5ustar debacledebacleitota-trac-subtickets-plugin-a0fc153/tracsubtickets/locale/de_DE/LC_MESSAGES/tracsubtickets.po0000644000175500017550000000341012043025237031775 0ustar debacledebacle# translation of tracsubtickets.po to German # German (Germany) translations for TracSubTicketsPlugin. # Copyright (C) 2010 # This file is distributed under the same license as the # TracSubTicketsPlugin project. # # Steffen Hoffmann , 2010. msgid "" msgstr "" "Project-Id-Version: TracSubTicketsPlugin 0.1.x\n" "Report-Msgid-Bugs-To: hoff.st@web.de\n" "POT-Creation-Date: 2010-05-04 01:38+0200\n" "PO-Revision-Date: 2010-05-04 01:48+0200\n" "Last-Translator: Steffen Hoffmann \n" "Language-Team: German de_DE \n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.0dev-r482\n" #: tracsubtickets/api.py:179 msgid "A ticket cannot be a parent to itself" msgstr "Ein Ticket kann nicht sein eigener Vorläufer sein" #: tracsubtickets/api.py:185 #, python-format msgid "Ticket #%s does not exist" msgstr "Ticket #%s ist nicht vorhanden" #: tracsubtickets/api.py:196 #, python-format msgid "Circularity error: %s" msgstr "Zirkelbezug: %s" #: tracsubtickets/api.py:205 tracsubtickets/web_ui.py:129 #, python-format msgid "Parent ticket #%s is closed" msgstr "Das Vorgänger-Ticket #%s ist geschossen" #: tracsubtickets/api.py:216 msgid "Not a valid list of ticket IDs" msgstr "Keine gültige Liste von Ticket-IDs" #: tracsubtickets/web_ui.py:123 #, python-format msgid "Child ticket #%s has not been closed yet" msgstr "Folge-Ticket #%s wurde bisher noch nicht geschlossen" #: tracsubtickets/web_ui.py:141 msgid "add" msgstr "hinzufügen" #: tracsubtickets/web_ui.py:143 msgid "Create new child ticket" msgstr "Neues Folge-Ticket erstellen" #: tracsubtickets/web_ui.py:147 msgid "Subtickets " msgstr "Folge-Tickets " itota-trac-subtickets-plugin-a0fc153/tracsubtickets/locale/messages.pot0000644000175500017550000000262712043025237026222 0ustar debacledebacle# Translations template for TracSubTicketsPlugin. # Copyright (C) 2010 ORGANIZATION # This file is distributed under the same license as the # TracSubTicketsPlugin project. # FIRST AUTHOR , 2010. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: TracSubTicketsPlugin 0.1.x\n" "Report-Msgid-Bugs-To: hoff.st@web.de\n" "POT-Creation-Date: 2010-05-04 01:38+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.0dev-r482\n" #: tracsubtickets/api.py:179 msgid "A ticket cannot be a parent to itself" msgstr "" #: tracsubtickets/api.py:185 #, python-format msgid "Ticket #%s does not exist" msgstr "" #: tracsubtickets/api.py:196 #, python-format msgid "Circularity error: %s" msgstr "" #: tracsubtickets/api.py:205 tracsubtickets/web_ui.py:129 #, python-format msgid "Parent ticket #%s is closed" msgstr "" #: tracsubtickets/api.py:216 msgid "Not a valid list of ticket IDs" msgstr "" #: tracsubtickets/web_ui.py:123 #, python-format msgid "Child ticket #%s has not been closed yet" msgstr "" #: tracsubtickets/web_ui.py:141 msgid "add" msgstr "" #: tracsubtickets/web_ui.py:143 msgid "Create new child ticket" msgstr "" #: tracsubtickets/web_ui.py:147 msgid "Subtickets " msgstr "" itota-trac-subtickets-plugin-a0fc153/tracsubtickets/htdocs/0000755000175500017550000000000012043025237023705 5ustar debacledebacleitota-trac-subtickets-plugin-a0fc153/tracsubtickets/htdocs/css/0000755000175500017550000000000012043025237024475 5ustar debacledebacleitota-trac-subtickets-plugin-a0fc153/tracsubtickets/htdocs/css/subtickets.css0000644000175500017550000000025312043025237027367 0ustar debacledebacleh3 .addsubticket { font-size: 90%; } #ticket table.subtickets { width: 100%; } #ticket table.subtickets td, #ticket table.subtickets th { font-size: 80%; } itota-trac-subtickets-plugin-a0fc153/tracsubtickets/api.py0000644000175500017550000002245212043025237023551 0ustar debacledebacle#!/usr/bin/python # # Copyright (c) 2010, Takashi Ito # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import re import pkg_resources from trac.core import * from trac.env import IEnvironmentSetupParticipant from trac.db import DatabaseManager from trac.resource import ResourceNotFound from trac.ticket.model import Ticket from trac.ticket.api import ITicketChangeListener, ITicketManipulator from trac.ticket.notification import TicketNotifyEmail from trac.util.translation import domain_functions import db_default NUMBERS_RE = re.compile(r'\d+', re.U) # i18n support for plugins, available since Trac r7705 # use _, tag_ and N_ as usual, e.g. _("this is a message text") _, tag_, N_, add_domain = domain_functions('tracsubtickets', '_', 'tag_', 'N_', 'add_domain') class SubTicketsSystem(Component): implements(IEnvironmentSetupParticipant, ITicketChangeListener, ITicketManipulator) def __init__(self): self._version = None self.ui = None # bind the 'traccsubtickets' catalog to the locale directory locale_dir = pkg_resources.resource_filename(__name__, 'locale') add_domain(self.env.path, locale_dir) # IEnvironmentSetupParticipant methods def environment_created(self): self.found_db_version = 0 self.upgrade_environment(self.env.get_db_cnx()) def environment_needs_upgrade(self, db): cursor = db.cursor() cursor.execute("SELECT value FROM system WHERE name=%s", (db_default.name, )) value = cursor.fetchone() try: self.found_db_version = int(value[0]) if self.found_db_version < db_default.version: return True except: self.found_db_version = 0 return True # check the custom field if 'parents' not in self.config['ticket-custom']: return True return False def upgrade_environment(self, db): db_manager, _ = DatabaseManager(self.env)._get_connector() # update the version old_data = {} # {table.name: (cols, rows)} cursor = db.cursor() if not self.found_db_version: cursor.execute("INSERT INTO system (name, value) VALUES (%s, %s)", (db_default.name, db_default.version)) else: cursor.execute("UPDATE system SET value=%s WHERE name=%s", (db_default.version, db_default.name)) for table in db_default.tables: cursor.execute("SELECT * FROM " + table.name) cols = [x[0] for x in cursor.description] rows = cursor.fetchall() old_data[table.name] = (cols, rows) cursor.execute("DROP TABLE " + table.name) # insert the default table for table in db_default.tables: for sql in db_manager.to_sql(table): cursor.execute(sql) # add old data if table.name in old_data: cols, rows = old_data[table.name] sql = 'INSERT INTO %s (%s) VALUES (%s)' % \ (table.name, ','.join(cols), ','.join(['%s'] * len(cols))) for row in rows: cursor.execute(sql, row) # add the custom field cfield = self.config['ticket-custom'] if 'parents' not in cfield: cfield.set('parents', 'text') cfield.set('parents.label', 'Parent Tickets') self.config.save() # ITicketChangeListener methods def ticket_created(self, ticket): self.ticket_changed(ticket, '', ticket['reporter'], {'parents': ''}) def ticket_changed(self, ticket, comment, author, old_values): if 'parents' not in old_values: return old_parents = old_values.get('parents', '') or '' old_parents = set(NUMBERS_RE.findall(old_parents)) new_parents = set(NUMBERS_RE.findall(ticket['parents'] or '')) if new_parents == old_parents: return db = self.env.get_db_cnx() cursor = db.cursor() # remove old parents for parent in old_parents - new_parents: cursor.execute("DELETE FROM subtickets WHERE parent=%s AND child=%s", (parent, ticket.id)) # add a comment to old parent xticket = Ticket(self.env, parent) xticket.save_changes(author, _('Remove a subticket #%s.') % ticket.id) tn = TicketNotifyEmail(self.env) tn.notify(xticket, newticket=False, modtime=xticket['changetime']) # add new parents for parent in new_parents - old_parents: cursor.execute("INSERT INTO subtickets VALUES(%s, %s)", (parent, ticket.id)) # add a comment to new parent xticket = Ticket(self.env, parent) xticket.save_changes(author, _('Add a subticket #%s.') % ticket.id) tn = TicketNotifyEmail(self.env) tn.notify(xticket, newticket=False, modtime=xticket['changetime']) db.commit() def ticket_deleted(self, ticket): db = self.env.get_db_cnx() cursor = db.cursor() # TODO: check if there's any child ticket cursor.execute("DELETE FROM subtickets WHERE child=%s", (ticket.id, )) db.commit() # ITicketManipulator methods def prepare_ticket(self, req, ticket, fields, actions): pass def validate_ticket(self, req, ticket): db = self.env.get_db_cnx() cursor = db.cursor() try: invalid_ids = set() _ids = set(NUMBERS_RE.findall(ticket['parents'] or '')) myid = str(ticket.id) for id in _ids: if id == myid: invalid_ids.add(id) yield 'parents', _('A ticket cannot be a parent to itself') else: # check if the id exists cursor.execute("SELECT id FROM ticket WHERE id=%s", (id, )) row = cursor.fetchone() if row is None: invalid_ids.add(id) yield 'parents', _('Ticket #%s does not exist') % id # circularity check function def _check_parents(id, all_parents): all_parents = all_parents + [id] errors = [] cursor.execute("SELECT parent FROM subtickets WHERE child=%s", (id, )) for x in [int(x[0]) for x in cursor]: if x in all_parents: invalid_ids.add(x) error = ' > '.join(['#%s' % n for n in all_parents + [x]]) errors.append(('parents', _('Circularity error: %s') % error)) else: errors += _check_parents(x, all_parents) return errors for x in [i for i in _ids if i not in invalid_ids]: # check parent ticket state try: parent = Ticket(self.env, x) if parent and parent['status'] == 'closed' and ticket['status'] != 'closed': invalid_ids.add(x) yield 'parents', _('Parent ticket #%s is closed.') % x else: # check circularity all_parents = ticket.id and [ticket.id] or [] for error in _check_parents(int(x), all_parents): yield error except ResourceNotFound, e: invalid_ids.add(x) valid_ids = _ids.difference(invalid_ids) ticket['parents'] = valid_ids and ', '.join(sorted(valid_ids, key=lambda x: int(x))) or '' except Exception, e: import traceback self.log.error(traceback.format_exc()) yield 'parents', _('Not a valid list of ticket IDs.') itota-trac-subtickets-plugin-a0fc153/tracsubtickets/db_default.py0000644000175500017550000000333712043025237025072 0ustar debacledebacle#!/usr/bin/python # # Copyright (c) 2010, Takashi Ito # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from trac.db import Table, Column name = 'subtickets' version = 2 tables = [ Table(name, key=('parent','child'))[ Column('parent', type='int'), Column('child', type='int'), ], ] itota-trac-subtickets-plugin-a0fc153/setup.cfg0000644000175500017550000000104212043025237021205 0ustar debacledebacle[egg_info] tag_build = .dev tag_date = True [extract_messages] add_comments = TRANSLATOR: msgid_bugs_address = hoff.st@web.de output_file = tracsubtickets/locale/messages.pot keywords = _ ngettext:1,2 N_ tag_ width = 72 [init_catalog] input_file = tracsubtickets/locale/messages.pot output_dir = tracsubtickets/locale domain = tracsubtickets [compile_catalog] directory = tracsubtickets/locale domain = tracsubtickets [update_catalog] input_file = tracsubtickets/locale/messages.pot output_dir = tracsubtickets/locale domain = tracsubtickets itota-trac-subtickets-plugin-a0fc153/setup.py0000755000175500017550000000547312043025237021115 0ustar debacledebacle#!/usr/bin/python # # Copyright (c) 2010, Takashi Ito # i18n and German translation by Steffen Hoffmann # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from setuptools import find_packages, setup setup( name = 'TracSubTicketsPlugin', version = '0.2.0', keywords = 'trac plugin ticket subticket', author = 'Takashi Ito', author_email = 'TakashiC.Ito@gmail.com', url = 'http://github.com/itota/trac-subtickets-plugin', description = 'Trac Sub-Tickets Plugin', long_description = """ This plugin for Trac 0.12 provides Sub-Tickets functionality. The association is done by adding parent tickets number to a custom field. Checks ensure i.e. resolving of sub-tickets before closing the parent. Babel is required to display localized texts. Currently only translation for de_DE is provided. """, license = 'BSD', install_requires = ['Trac >= 0.12dev'], packages = find_packages(exclude=['*.tests*']), package_data = { 'tracsubtickets': [ 'htdocs/css/*.css', 'locale/*.*', 'locale/*/LC_MESSAGES/*.*', ], }, entry_points = { 'trac.plugins': [ 'tracsubtickets.api = tracsubtickets.api', 'tracsubtickets.web_ui = tracsubtickets.web_ui', ], 'console_scripts': [ 'check-trac-subtickets = tracsubtickets.checker:main', ], }, )