<commit_before># ChildTickets plugin

import re

from trac.core import *
from trac.cache import cached
from trac.ticket.api import ITicketManipulator, ITicketChangeListener
from trac.web.chrome import ITemplateProvider, add_stylesheet
from trac.web.api import ITemplateStreamFilter
from trac.perm import IPermissionRequestor
from trac.ticket.model import Ticket
from trac.resource import ResourceNotFound

from genshi.builder import tag
from genshi.filters import Transformer

class TracchildticketsModule(Component):
    implements(ITicketManipulator, ITemplateStreamFilter, ITicketChangeListener, ITemplateProvider)


    @cached
    def childtickets(self,db):
        # NOTE: Ignore the above 'db' arg that is supplied to '@cached' decorated functions - I think I read somewhere that
        # this is no longer supported. (But I'm keeping it here for compatibility!)
        db = self.env.get_db_cnx() 
        cursor = db.cursor() 
        cursor.execute("SELECT ticket,value FROM ticket_custom WHERE name='parent'")
        x = {}    # { parent -> children } - 1:n
        for child,parent in cursor.fetchall():
            if parent and re.match('\d+', parent):
                x.setdefault(int(parent), []).append(child)
        return x


    # ITicketChangeListener methods
    def ticket_changed(self, ticket, comment, author, old_values):
        if 'parent' in old_values:
            del self.childtickets

    def ticket_created(self, ticket):
        del self.childtickets

    def ticket_deleted(self, ticket):
        # NOTE: Is there a way to 'block' a ticket deletion if it still has child tickets?
        del self.childtickets


    # ITicketManipulator methods
    def prepare_ticket(self, req, ticket, fields, actions):
        pass
    
    def validate_ticket(self, req, ticket):

        # Don't allow ticket to be 'resolved' if any child tickets are still open.
        if req.args.get('action') == 'resolve':
            for t in self.childtickets.get(ticket.id,[]):
                if Ticket(self.env,t)['status'] != 'closed':
                    yield '', 'Cannot resolve ticket while child ticket (#%s) is still open.' % t

        # Check if the 'parent' field is being used.
        if ticket.values.get('parent'):

            # Is it of correct 'format'?
            if not re.match('#?\d+',ticket.values.get('parent')):
                yield 'parent', "The parent id must be a number."

            if ticket['parent'].startswith('#'):
                ticket['parent'] = ticket['parent'][1:]

            pid = int(ticket.values.get('parent'))

            # Check we're not being daft and setting own id as parent.
            if ticket.id and pid == ticket.id:
                yield 'parent', "The ticket has same id as parent id."

            # Recursive/Circular ticket check : Does ticket recursion goes too deep (as defined by 'default.max_depth' in 'trac.ini')?
            max_depth = self.config.getint('childtickets', 'default.max_depth', default=5)

            fam_tree = [ ticket.id, pid ]   # The 'family tree' already consists of this ticket id plus the parent
<commit_msg>use Ticket[] accessor instead of internal dictionary<commit_after># ChildTickets plugin

import re

from trac.core import *
from trac.cache import cached
from trac.ticket.api import ITicketManipulator, ITicketChangeListener
from trac.web.chrome import ITemplateProvider, add_stylesheet
from trac.web.api import ITemplateStreamFilter
from trac.perm import IPermissionRequestor
from trac.ticket.model import Ticket
from trac.resource import ResourceNotFound

from genshi.builder import tag
from genshi.filters import Transformer

class TracchildticketsModule(Component):
    implements(ITicketManipulator, ITemplateStreamFilter, ITicketChangeListener, ITemplateProvider)


    @cached
    def childtickets(self,db):
        # NOTE: Ignore the above 'db' arg that is supplied to '@cached' decorated functions - I think I read somewhere that
        # this is no longer supported. (But I'm keeping it here for compatibility!)
        db = self.env.get_db_cnx() 
        cursor = db.cursor() 
        cursor.execute("SELECT ticket,value FROM ticket_custom WHERE name='parent'")
        x = {}    # { parent -> children } - 1:n
        for child,parent in cursor.fetchall():
            if parent and re.match('\d+', parent):
                x.setdefault(int(parent), []).append(child)
        return x


    # ITicketChangeListener methods
    def ticket_changed(self, ticket, comment, author, old_values):
        if 'parent' in old_values:
            del self.childtickets

    def ticket_created(self, ticket):
        del self.childtickets

    def ticket_deleted(self, ticket):
        # NOTE: Is there a way to 'block' a ticket deletion if it still has child tickets?
        del self.childtickets


    # ITicketManipulator methods
    def prepare_ticket(self, req, ticket, fields, actions):
        pass
    
    def validate_ticket(self, req, ticket):

        # Don't allow ticket to be 'resolved' if any child tickets are still open.
        if req.args.get('action') == 'resolve':
            for t in self.childtickets.get(ticket.id,[]):
                if Ticket(self.env,t)['status'] != 'closed':
                    yield '', 'Cannot resolve ticket while child ticket (#%s) is still open.' % t

        # Check if the 'parent' field is being used.
        if 'parent' in ticket.values:

            # Is it of correct 'format'?
            if not re.match('#?\d+',ticket['parent']):
                yield 'parent', "The parent id must be a number."

            if ticket['parent'].startswith('#'):
                ticket['parent'] = ticket['parent'][1:]

            pid = int(ticket['parent'])

            # Check we're not being daft and setting own id as parent.
            if ticket.id and pid == ticket.id:
                yield 'parent', "The ticket has same id as parent id."

            # Recursive/Circular ticket check : Does ticket recursion goes too deep (as defined by 'default.max_depth' in 'trac.ini')?
            max_depth = self.config.getint('childtickets', 'default.max_depth', default=5)

            fam_tree = [ ticket.id, pid ]   # The 'family tree' already consists of this ticket id plus the parent

{'fraction_non_alphanumeric': 0.09717568214456677, 'fraction_numerical': 0.0009573958831977022, 'mean_word_length': 2.7220902612826605, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2857168', 'n_tokens_mistral': 1754, 'n_tokens_neox': 1675, 'n_words': 598}