Ticket #1823: batchmodify-0.11.diff

File batchmodify-0.11.diff, 28.5 kB (added by dgynn, 1 year ago)
  • batchmod/web_ui.py

    old new  
    11# -*- coding: utf-8 -*- 
    22# Copyright (C) 2006 Ashwin Phatak 
     3# Copyright (C) 2007 Dave Gynn 
    34 
    45from trac.core import * 
    5 from trac.web.chrome import INavigationContributor, ITemplateProvider 
    6 from trac.web.main import IRequestHandler 
    76from trac.perm import IPermissionRequestor         
     7from trac.ticket import TicketSystem, Ticket 
    88from trac.ticket.query import QueryModule 
    9 from trac.util.html import html 
    10 from trac.ticket import TicketSystem 
    11 from trac.ticket import Ticket 
    12 from trac.mimeview.api import IContentConverter 
    13 from trac.wiki import IWikiSyntaxProvider 
    14 from trac.wiki.macros import WikiMacroBase 
    15 from trac.ticket.query import TicketQueryMacro as Macro 
     9from trac.web.api import ITemplateStreamFilter 
     10from trac.web.chrome import ITemplateProvider, Chrome 
     11from trac.web.main import IRequestFilter 
     12from genshi.filters.transform import Transformer 
    1613 
    17 __all__ = ['BatchModifyModule','TicketQueryMacro'
     14__all__ = ['BatchModifyModule'
    1815 
    1916class BatchModifyModule(Component): 
    20     '''Allows batch modification of tickets''' 
     17    implements(IPermissionRequestor, ITemplateProvider, IRequestFilter, ITemplateStreamFilter) 
    2118 
    22     implements(INavigationContributor, IRequestHandler, ITemplateProvider, \ 
    23             IPermissionRequestor, IContentConverter, IWikiSyntaxProvider) 
    24      
    25     # INavigationContributor methods 
     19    # IPermissionRequestor methods 
     20    def get_permission_actions(self): 
     21        yield 'TICKET_BATCH_MODIFY' 
    2622 
    27     def get_active_navigation_item(self, req): 
    28         return QueryModule(self.env).get_active_navigation_item(req) 
    29  
    30     def get_navigation_items(self, req): 
    31         from trac.ticket.report import ReportModule 
    32         if req.perm.has_permission('TICKET_VIEW') and \ 
    33                 not self.env.is_component_enabled(ReportModule): 
    34             yield ('mainnav', 'tickets', 
    35                    html.A('View Tickets', href=req.href.query())) 
    36         yield ('mainnav', 'query', 
    37                    html.A('Custom Query', href=req.href.query())) 
    38  
    39  
    40     # IRequestHandler methods 
    41  
    42     def match_request(self, req): 
    43        return QueryModule(self.env).match_request(req) 
    44      
    45     def process_request(self, req): 
    46         if req.args.has_key('batchmod'): 
    47             req.perm.assert_permission('TICKET_BATCH_MODIFY') 
    48             self._batch_modify(req) 
    49  
    50         QueryModule(self.env).process_request(req) 
    51         self._add_ticket_fields(req) 
    52       
    53         return 'batchmod.cs', None 
    54  
    55  
    5623    # ITemplateProvider methods 
     24    def get_htdocs_dirs(self): 
     25        return [] 
    5726 
    5827    def get_templates_dirs(self): 
    5928        from pkg_resources import resource_filename 
    6029        return [resource_filename(__name__, 'templates')] 
    61     
    62   
    63     # IPermissionRequestor methods 
    64     def get_permission_actions(self): 
    65         yield 'TICKET_BATCH_MODIFY' 
    6630 
     31    # IRequestFilter methods 
     32    def pre_process_request(self, req, handler): 
     33        """Look for QueryHandler posts and hijack them""" 
     34        if isinstance(handler, QueryModule): 
     35            if req.method=='POST' and req.args.get('batchmod'): 
     36                self._batch_modify(req) 
     37        return handler 
    6738 
    68     # IContentConverter methods 
    69     def get_supported_conversions(self): 
    70         return QueryModule(self.env).get_supported_conversions()     
     39    def post_process_request(self, req, template, content_type): 
     40        """No-op""" 
     41        return (template, content_type) 
    7142 
    72     def convert_content(self, req, mimetype, query, key): 
    73         return QueryModule(self.env).convert_content(req, mimetype, query, key) 
    74  
    75  
    76     # IWikiSyntaxProvider methods 
    77  
    78     def get_wiki_syntax(self): 
    79         return QueryModule(self.env).get_wiki_syntax()  
    80  
    81     def get_link_resolvers(self): 
    82         return QueryModule(self.env).get_link_resolvers() 
    83  
    84  
     43    def post_process_request(self, req, template, data, content_type): 
     44        """No-op""" 
     45        return (template, data, content_type) 
     46     
    8547    # Internal methods  
    8648    def _batch_modify(self, req): 
    8749        tickets = req.session['query_tickets'].split(' ') 
    88         comment = req.args.get('comment', '')  
     50        comment = req.args.get('bmod_value_comment', '')  
    8951        values = {}  
    90          
     52 
     53        # TODO: improve validation and better handle advanced statuses 
    9154        for field in TicketSystem(self.env).get_ticket_fields(): 
    9255            name = field['name'] 
    9356            if name not in ('summary', 'reporter', \ 
    94                         'description', 'type', 'status', 
    95                         'resolution', 'owner'): 
    96                 if req.args.has_key('bm_' + name): 
    97                     values[name] = req.args.get(name) 
     57                        'description', 'status', 
     58                        'resolution'): 
     59                if req.args.has_key('bmod_flag_' + name): 
     60                    if name == 'owner': 
     61                        values['status'] = 'assigned' 
     62                    values[name] = req.args.get('bmod_value_' + name) 
    9863 
    99         for id in tickets: 
    100             t = Ticket(self.env, id)  
    101             t.populate(values) 
    102             t.save_changes(req.authname, comment) 
     64        selectedTickets = req.args.get('selectedTickets') 
     65        selectedTickets = isinstance(selectedTickets, list) and selectedTickets or selectedTickets.split(',') 
     66        if not selectedTickets: 
     67            raise TracError, 'No Tickets selected' 
     68         
     69        for id in selectedTickets: 
     70            if id in tickets: 
     71                t = Ticket(self.env, id)  
     72                t.populate(values) 
     73                t.save_changes(req.authname, comment) 
    10374 
     75                # TODO: Send email notifications - copied from ticket.web_ui 
     76                #try: 
     77                #    tn = TicketNotifyEmail(self.env) 
     78                #    tn.notify(ticket, newticket=False, modtime=now) 
     79                #except Exception, e: 
     80                #    self.log.exception("Failure sending notification on change to " 
     81                #                       "ticket #%s: %s" % (ticket.id, e)) 
     82     
     83                # TODO: deal with actions and side effects - copied from ticket.web_ui 
     84                #for controller in self._get_action_controllers(req, ticket, 
     85                #                                               action): 
     86                #    controller.apply_action_side_effects(req, ticket, action) 
    10487 
     88    # ITemplateStreamFilter methods 
     89    def filter_stream(self, req, method, filename, stream, formdata): 
     90        """Adds BatchModify form to the query page""" 
     91        if filename != 'query.html' or not req.perm.has_permission('TICKET_BATCH_MODIFY'): 
     92            return stream 
    10593 
    106     def _add_ticket_fields(self, req):    
    107         for field in TicketSystem(self.env).get_ticket_fields(): 
    108             name = field['name'] 
    109             del field['name'] 
    110             if name in ('summary', 'reporter', 'description', 'type', 'status', 
    111                         'resolution', 'owner'): 
    112                 field['skip'] = True 
    113             req.hdf['ticket.fields.' + name] = field 
     94        return stream | Transformer('//div[@id="help"]').before(self._generate_form(req, formdata) ) 
    11495 
     96     
     97    def _generate_form(self, req, data): 
     98        batchFormData = dict(data) 
     99        batchFormData['actionUri']= req.session['query_href'] or req.href.query() 
    115100 
    116 class TicketQueryMacro(WikiMacroBase): 
    117     __doc__ = Macro.__doc__ 
     101        fields = [] 
     102        for field in TicketSystem(self.env).get_ticket_fields(): 
     103            if field['name'] not in ('summary', 'reporter', 'description'): 
     104                fields.append(field) 
     105        batchFormData['fields']=fields 
    118106 
    119     def render_macro(self, req, name, content): 
    120         return Macro(self.env).render_macro(req, name, content) 
     107        stream = Chrome(self.env).render_template(req, 'batchmod.html', 
     108              batchFormData, fragment=True) 
     109        return stream.select('//form[@id="batchmod-form"]') 
  • batchmod/templates/batchmod.html

    old new  
     1<!DOCTYPE html 
     2    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
     3    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 
     4<html xmlns="http://www.w3.org/1999/xhtml" 
     5        xmlns:py="http://genshi.edgewall.org/" 
     6        xmlns:xi="http://www.w3.org/2001/XInclude"> 
     7<body> 
     8<form id="batchmod-form" method="post" action="${actionURI}"> 
     9<fieldset><legend>Batch Modify Properties</legend> 
     10<table class="properties"> 
     11        <tr py:for="row in group(fields, 2, lambda f: f.type != 'textarea')" 
     12                py:with="fullrow = len(row) == 1"> 
     13                <py:for each="idx, field in enumerate(row)"> 
     14                        <th style="text-align: left" class="col${idx + 1}" 
     15                                py:if="idx == 0 or not fullrow"> 
     16                                <input py:if="field" type="checkbox" id="bmod_flag_${field.name}" name="bmod_flag_${field.name}" /> 
     17                                <label py:if="field" for="bmod_value_${field.name}" class="disabled">${field.label or field.name}:</label> 
     18                        </th> 
     19                        <td class="col${idx + 1}" py:if="idx == 0 or not fullrow" 
     20                                colspan="${fullrow and 3 or None}"> 
     21                        <py:choose test="field.type" py:if="field"> 
     22                                <select py:when="'select'" id="bmod_value_${field.name}" 
     23                                        name="bmod_value_${field.name}" disabled="disabled"> 
     24                                        <option py:if="field.optional" /> 
     25                                        <option py:for="option in field.options" py:content="option"/> 
     26                                </select> 
     27                                <textarea py:when="'textarea'" id="bmod_value_${field.name}" 
     28                                        name="bmod_value_${field.name}" cols="${field.width}" 
     29                                        rows="${field.height}" disabled="disabled" /> 
     30                                <span py:when="'checkbox'"> <input type="checkbox" 
     31                                        id="bmod_value_${field.name}" name="bmod_value_${field.name}" 
     32                                        value="1" disabled="disabled" /> <input type="hidden" 
     33                                        name="bmod_value_checkbox_${field.name}" value="1" /> </span> 
     34                                <label py:when="'radio'" 
     35                                        py:for="idx, option in enumerate(field.options)" class="disabled"> 
     36                                        <input type="radio" name="bmod_value_${field.name}"  
     37                                        value="${option}" disabled="disabled" /> ${option}  
     38                                </label> 
     39                                <input py:otherwise="" type="text" id="bmod_value_${field.name}" 
     40                                        name="bmod_value_${field.name}" value="" disabled="disabled" /> 
     41                        </py:choose> 
     42                        </td> 
     43                </py:for> 
     44        </tr> 
     45        <tr> 
     46                <th style="text-align: left"><input type="checkbox" 
     47                        id="bmod_flag_comment" name="bmod_flag_comment" checked="checked" /> <label 
     48                        for="bmod_value_comment">Comment:</label></th> 
     49                <td class="fullrow" colspan="3"><input type="text" 
     50                        id="bmod_value_comment" name="bmod_value_comment" value="" size="70" /> 
     51                </td> 
     52        </tr> 
     53</table> 
     54<div> 
     55<input type="hidden" name="selectedTickets" value=""/> 
     56<input type="submit" id="batchmod" name="batchmod" value="Change tickets" /></div> 
     57<py:for each="constraint_name, constraint in constraints.items()"> 
     58  <py:for each="constraint_value in constraint['values']"> 
     59    <input type="hidden" name="${constraint_name}" value="${constraint_value}" /> 
     60    <input py:if="constraint['mode']" type="hidden" name="${constraint_name}_mode" value="${constraint['mode']}" /> 
     61  </py:for> 
     62</py:for> 
     63<py:for each="column in col"> 
     64  <input type="hidden" name="col" value="${column}" /> 
     65</py:for> 
     66<input py:if="req.args['group']" type="hidden" name="group" value="${req.args['group']}" /> 
     67<input py:if="req.args['groupdesc']" type="hidden" name="groupdesc" value="${req.args['groupdesc']}" /> 
     68</fieldset> 
     69<script type="text/javascript"> 
     70$(document).ready(function(){ 
     71<!-- 
     72  $("//table.listing//tr//td[@class=id]").each( 
     73    function() { 
     74      tId=$(this).text().substring(1);  
     75      $(this).before('<td><input type="checkbox" name="selectedTicket" class="bmod_selector" value="'+tId+'"/></td>'); 
     76    }   
     77  )  
     78  $("//table.listing//tr//th[@class=id]").each( 
     79    function() {  
     80      $(this).before('<th class="bmod_selector"><input type="checkbox" name="bmod_toggleGroup" /></th>'); 
     81    }   
     82  )  
     83  $("input[@name=bmod_toggleGroup]").change(function() {  
     84    $("../../../..//input[@type=checkbox]",this).attr("checked",this.checked); 
     85  }) 
     86--> 
     87  <py:for each="field in fields"> 
     88    $("input[@id=bmod_flag_${field.name}]").change(function() { $("*[@name=bmod_value_${field.name}]").enable(this.checked);} ) 
     89  </py:for> 
     90  $("input#bmod_flag_comment").change(function() {enableControl("bmod_value_comment",this.checked);} ) 
     91  $("form#batchmod-form").submit(function() { 
     92    var selectedTix=[];     
     93    $("input[@name=selectedTicket]:checked").each( function(){ selectedTix.push(this.value);} );  
     94    $("input[@name=selectedTickets]").val(selectedTix); 
     95  }) 
     96  $(document.getElementById("columns")).toggleClass("collapsed"); 
     97}); 
     98</script> 
     99</form> 
     100</body> 
     101</html> 
  • batchmod/templates/batchmod.cs

    old new  
    1 <?cs include:"header.cs" ?> 
    2 <?cs include:"macros.cs" ?> 
    3  
    4 <div id="ctxtnav" class="nav"><?cs 
    5  if:query.report_href ?><ul> 
    6   <li class="first"><a href="<?cs 
    7     var:query.report_href ?>">Available Reports</a></li> 
    8   </ul><?cs 
    9  /if ?> 
    10 </div> 
    11  
    12 <?cs def:num_matches(v) ?><span class="numrows">(<?cs  
    13  alt:v ?>No<?cs /alt ?> match<?cs if:v != 1 ?>es<?cs /if ?>)</span><?cs 
    14 /def ?> 
    15  
    16 <div id="content" class="query"> 
    17  <h1><?cs var:title ?> <?cs call:num_matches(query.num_matches) ?></h1> 
    18  
    19 <form id="query" method="post" action="<?cs var:trac.href.query ?>"> 
    20  <fieldset id="filters"> 
    21   <legend>Filters</legend> 
    22   <?cs def:checkbox_checked(constraint, option) ?><?cs 
    23    set:checked = 0 ?><?cs 
    24    each:value = constraint.values ?><?cs 
    25     if:(value == option) == (constraint.mode == '') ?><?cs 
    26       set:checked = 1 ?><?cs 
    27     /if ?><?cs 
    28    /each ?><?cs 
    29    if:checked ?> checked="checked"<?cs /if ?><?cs 
    30   /def ?> 
    31   <table summary="Query filters"> 
    32    <tbody><tr style="height: 1px"><td colspan="4"></td></tr></tbody><?cs 
    33    each:field = query.fields ?><?cs 
    34    each:constraint = query.constraints ?><?cs 
    35     if:name(field) == name(constraint) ?> 
    36      <tbody><tr class="<?cs var:name(field) ?>"> 
    37       <th scope="row"><label><?cs var:field.label ?></label></th><?cs 
    38       if:field.type != "radio" && field.type != "checkbox" ?> 
    39        <td class="mode"> 
    40         <select name="<?cs var:name(field) ?>_mode"><?cs 
    41          each:mode = query.modes[field.type] ?> 
    42           <option value="<?cs var:mode.value ?>"<?cs 
    43            if:mode.value == constraint.mode ?> selected="selected"<?cs 
    44            /if ?>><?cs var:mode.name ?></option><?cs 
    45          /each ?> 
    46         </select> 
    47        </td><?cs 
    48       /if ?> 
    49       <td class="filter"<?cs 
    50         if:field.type == "radio" || field.type == "checkbox" ?> colspan="2"<?cs 
    51         /if ?>><?cs 
    52        if:field.type == "select" ?><?cs 
    53         each:value = constraint.values ?> 
    54          <select name="<?cs var:name(constraint) ?>"><option></option><?cs 
    55          each:option = field.options ?> 
    56           <option<?cs if:option == value ?> selected="selected"<?cs /if ?>><?cs 
    57             var:option ?></option><?cs 
    58          /each ?></select><?cs 
    59          if:name(value) != len(constraint.values) - 1 ?> 
    60           </td> 
    61           <td class="actions"><input type="submit" name="rm_filter_<?cs 
    62              var:name(field) ?>_<?cs var:name(value) ?>" value="-" /></td> 
    63          </tr><tr class="<?cs var:name(field) ?>"> 
    64           <th colspan="2"><label>or</label></th> 
    65           <td class="filter"><?cs 
    66          /if ?><?cs 
    67         /each ?><?cs 
    68        elif:field.type == "radio" ?><?cs 
    69         each:option = field.options ?> 
    70          <input type="checkbox" id="<?cs var:name(field) ?>_<?cs 
    71            var:option ?>" name="<?cs var:name(field) ?>" value="<?cs 
    72            var:option ?>"<?cs call:checkbox_checked(constraint, option) ?> /> 
    73          <label for="<?cs var:name(field) ?>_<?cs var:option ?>"><?cs 
    74            alt:option ?>none<?cs /alt ?></label><?cs 
    75         /each ?><?cs 
    76        elif:field.type == "checkbox" ?> 
    77         <input type="radio" id="<?cs var:name(field) ?>_on" name="<?cs 
    78           var:name(field) ?>" value="1"<?cs 
    79           if:constraint.mode != '!' ?> checked="checked"<?cs /if ?> /> 
    80         <label for="<?cs var:name(field) ?>_on">yes</label> 
    81         <input type="radio" id="<?cs var:name(field) ?>_off" name="<?cs 
    82           var:name(field) ?>" value="!1"<?cs 
    83           if:constraint.mode == '!' ?> checked="checked"<?cs /if ?> /> 
    84         <label for="<?cs var:name(field) ?>_off">no</label><?cs 
    85        elif:field.type == "text" ?><?cs 
    86         each:value = constraint.values ?> 
    87         <input type="text" name="<?cs var:name(field) ?>" value="<?cs 
    88           var:value ?>" size="42" /><?cs 
    89          if:name(value) != len(constraint.values) - 1 ?> 
    90           </td> 
    91           <td class="actions"><input type="submit" name="rm_filter_<?cs 
    92              var:name(field) ?>_<?cs var:name(value) ?>" value="-" /></td> 
    93          </tr><tr class="<?cs var:name(field) ?>"> 
    94           <th colspan="2"><label>or</label></th> 
    95           <td class="filter"><?cs 
    96          /if ?><?cs 
    97         /each ?><?cs 
    98        /if ?> 
    99       </td> 
    100       <td class="actions"><input type="submit" name="rm_filter_<?cs 
    101          var:name(field) ?><?cs 
    102          if:field.type != 'radio' ?>_<?cs 
    103           var:len(constraint.values) - 1 ?><?cs 
    104          /if ?>" value="-" /></td> 
    105      </tr></tbody><?cs /if ?><?cs 
    106     /each ?><?cs 
    107    /each ?> 
    108    <tbody><tr class="actions"> 
    109     <td class="actions" colspan="4" style="text-align: right"> 
    110      <label for="add_filter">Add filter</label>&nbsp; 
    111      <select name="add_filter" id="add_filter"> 
    112       <option></option><?cs 
    113       each:field = query.fields ?> 
    114        <option value="<?cs var:name(field) ?>"<?cs 
    115          if:field.type == "radio" ?><?cs 
    116           if:len(query.constraints[name(field)]) != 0 ?> disabled="disabled"<?cs 
    117           /if ?><?cs 
    118          /if ?>><?cs var:field.label ?></option><?cs 
    119       /each ?>   
    120      </select> 
    121      <input type="submit" name="add" value="+" /> 
    122     </td> 
    123    </tr></tbody> 
    124   </table> 
    125  </fieldset> 
    126  <p class="option"> 
    127   <label for="group">Group results by</label> 
    128   <select name="group" id="group"> 
    129    <option></option><?cs 
    130    each:field = query.fields ?><?cs 
    131     if:field.type == 'select' || field.type == 'radio' || 
    132        name(field) == 'owner' ?> 
    133      <option value="<?cs var:name(field) ?>"<?cs 
    134        if:name(field) == query.group ?> selected="selected"<?cs /if ?>><?cs 
    135        var:field.label ?></option><?cs 
    136     /if ?><?cs 
    137    /each ?> 
    138   </select> 
    139   <input type="checkbox" name="groupdesc" id="groupdesc"<?cs 
    140     if:query.groupdesc ?> checked="checked"<?cs /if ?> /> 
    141   <label for="groupdesc">descending</label> 
    142   <script type="text/javascript"> 
    143     var group = document.getElementById("group"); 
    144     var updateGroupDesc = function() { 
    145       enableControl('groupdesc', group.selectedIndex > 0); 
    146     } 
    147     addEvent(window, 'load', updateGroupDesc); 
    148     addEvent(group, 'change', updateGroupDesc); 
    149   </script> 
    150  </p> 
    151  <p class="option"> 
    152   <input type="checkbox" name="verbose" id="verbose"<?cs 
    153     if:query.verbose ?> checked="checked"<?cs /if ?> /> 
    154   <label for="verbose">Show full description under each result</label> 
    155  </p> 
    156  <div class="buttons"> 
    157   <input type="hidden" name="order" value="<?cs var:query.order ?>" /> 
    158   <?cs if:query.desc ?><input type="hidden" name="desc" value="1" /><?cs /if ?> 
    159   <input type="submit" name="update" value="Update" /> 
    160  </div> 
    161  <hr /> 
    162 </form> 
    163 <script type="text/javascript"><?cs set:idx = 0 ?> 
    164  var properties={<?cs each:field = query.fields ?><?cs 
    165   var:name(field) ?>:{type:"<?cs var:field.type ?>",label:"<?cs 
    166   var:field.label ?>",options:[<?cs 
    167    each:option = field.options ?>"<?cs var:option ?>"<?cs 
    168     if:name(option) < len(field.options) -1 ?>,<?cs /if ?><?cs 
    169    /each ?>]}<?cs 
    170   set:idx = idx + 1 ?><?cs if:idx < len(query.fields) ?>,<?cs /if ?><?cs 
    171  /each ?>};<?cs set:idx = 0 ?> 
    172  var modes = {<?cs each:type = query.modes ?><?cs var:name(type) ?>:[<?cs 
    173   each:mode = type ?>{text:"<?cs var:mode.name ?>",value:"<?cs var:mode.value ?>"}<?cs 
    174    if:name(mode) < len(type) -1 ?>,<?cs /if ?><?cs 
    175   /each ?>]<?cs 
    176   set:idx = idx + 1 ?><?cs if:idx < len(query.modes) ?>,<?cs /if ?><?cs 
    177  /each ?>}; 
    178  initializeFilters(); 
    179 </script> 
    180  
    181 <?cs def:thead() ?> 
    182  <thead><tr><?cs each:header = query.headers ?> 
    183   <th class="<?cs var:header.name ?><?cs if:query.order == header.name ?> <?cs 
    184     if:query.desc ?>desc<?cs else ?>asc<?cs /if ?><?cs /if ?>"> 
    185    <a title="Sort by <?cs var:header.label ?><?cs 
    186      if:query.order == header.name && !query.desc ?> (descending)<?cs 
    187      /if ?>" href="<?cs var:header.href ?>"><?cs var:header.label ?></a> 
    188   </th><?cs 
    189  /each ?></tr></thead> 
    190 <?cs /def ?> 
    191  
    192 <?cs if:len(query.results) ?><?cs 
    193  if:!query.group ?> 
    194   <table class="listing tickets"> 
    195   <?cs call:thead() ?><tbody><?cs 
    196  /if ?><?cs 
    197  each:result = query.results ?><?cs 
    198   if:result[query.group] != prev_group ?> 
    199    <?cs if:prev_group ?></tbody></table><?cs /if ?> 
    200    <h2><?cs 
    201     each:field = query.fields ?><?cs 
    202      if:name(field) == query.group ?><?cs 
    203       var:field.label ?><?cs 
    204      /if ?><?cs 
    205     /each ?>: <?cs var:result[query.group] ?> <?cs call:num_matches(query.num_matches_group[result[query.group]]) ?></h2> 
    206    <table class="listing tickets"> 
    207    <?cs call:thead() ?><tbody><?cs 
    208   /if ?> 
    209   <tr class="<?cs 
    210    if:name(result) % 2 ?>odd<?cs else ?>even<?cs /if ?> prio<?cs 
    211    var:result.priority_value ?><?cs 
    212    if:result.added ?> added<?cs /if ?><?cs 
    213    if:result.changed ?> changed<?cs /if ?><?cs 
    214    if:result.removed ?> removed<?cs /if ?>"><?cs 
    215   each:header = query.headers ?><?cs 
    216    if:name(header) == 0 ?><td class="id"><a href="<?cs 
    217     var:result.href ?>" title="View ticket"><?cs var:result.id ?></a></td><?cs 
    218    else ?><td class="<?cs var:header.name ?>"><?cs 
    219      if:header.name == 'summary' ?><a href="<?cs 
    220       var:result.href ?>" title="View ticket"><?cs 
    221       var:result.summary ?></a><?cs 
    222      else ?><span><?cs var:result[header.name] ?></span><?cs 
    223      /if ?></td><?cs 
    224    /if ?><?cs 
    225   /each ?> 
    226   <?cs if:query.verbose ?> 
    227    </tr><tr class="fullrow"><td colspan="<?cs var:len(query.headers) ?>"> 
    228     <p class="meta">Reported by <strong><?cs var:result.reporter ?></strong>, 
    229     <?cs var:result.time ?><?cs if:result.description ?>:<?cs /if ?></p> 
    230     <?cs if:result.description ?><p><?cs var:result.description ?></p><?cs /if ?> 
    231    </td> 
    232   <?cs /if ?><?cs set:prev_group = result[query.group] ?> 
    233  </tr><?cs /each ?> 
    234 </tbody></table><?cs 
    235 /if ?> 
    236  
    237  
    238 <?cs if:trac.acl.TICKET_BATCH_MODIFY ?> 
    239  
    240 <br/> 
    241 <form id="batchmod" method="post" action="<?cs var:trac.href.query ?>"> 
    242 <fieldset id="properties"> 
    243 <legend>Batch Modify Properties</legend> 
    244  
    245   <table> 
    246     <tr> 
    247         <th style="text-align: left"> 
    248             <input type="checkbox" id="bm_comment" name="bm_comment" /> 
    249             <label for="comment">Comment:</label> 
    250         </th> 
    251         <td class="fullrow" colspan="3"> 
    252          <input type="text" id="comment" name="comment" value="<?cs 
    253          var:ticket.summary ?>" size="70" disabled="true"/> 
    254         </td> 
    255         <script type="text/javascript"> 
    256             var bm_comment = document.getElementById("bm_comment"); 
    257             var comment = document.getElementById("comment"); 
    258             var updateComment = function() { 
    259                 enableControl('comment', bm_comment.checked); 
    260             } 
    261             addEvent(window, 'load', updateComment); 
    262             addEvent(bm_comment, 'change', updateComment); 
    263         </script> 
    264     </tr> 
    265  
    266   <tr><?cs set:num_fields = 0 ?><?cs 
    267   each:field = ticket.fields ?><?cs 
    268    if:!field.skip ?><?cs 
    269     set:num_fields = num_fields + 1 ?><?cs 
    270    /if ?><?cs 
    271   /each ?><?cs set:idx = 0 ?><?cs 
    272    each:field = ticket.fields ?><?cs 
    273     if:!field.skip ?> 
    274  
    275  
    276    <?cs set:fullrow = field.type == 'textarea' ?><?cs 
    277      if:fullrow && idx % 2 ?><?cs set:idx = idx + 1 ?><th class="col2"></th><td></td></tr><tr><?cs /if ?> 
    278  
    279     <th class="col<?cs var:idx % 2 + 1 ?>" style="text-align: left"> 
    280     <?cs if:field.type != 'radio' ?> 
    281         <input type="checkbox" id="bm_<?cs var:name(field) ?>" name="bm_<?cs var:name(field) ?>" /> 
    282         <label for="<?cs var:name(field) ?>"> 
    283         <script type="text/javascript"> 
    284             var bm_<?cs var:name(field) ?> = document.getElementById("bm_<?cs var:name(field) ?>"); 
    285             var ctrl_<?cs var:name(field) ?> = document.getElementById("<?cs var:name(field) ?>"); 
    286             var update<?cs var:name(field) ?>  = function() { 
    287                 enableControl('<?cs var:name(field) ?>', bm_<?cs var:name(field) ?>.checked); 
    288             } 
    289             addEvent(window, 'load', update<?cs var:name(field) ?>); 
    290             addEvent(bm_<?cs var:name(field) ?>, 'change', update<?cs var:name(field) ?>); 
    291         </script> 
    292     <?cs /if ?> 
    293     <?cs alt:field.label ?> 
    294         <input type="checkbox" id="bm_<?cs var:field.name ?>" name="bm_<?cs var:field.name ?>" /> 
    295         <?cs var:field.name ?> 
    296         <script type="text/javascript"> 
    297             var bm_<?cs var:field.name ?> = document.getElementById("bm_<?cs var:field.name ?>"); 
    298             var ctrl_<?cs var:field.name ?> = document.getElementById("<?cs var:field.name ?>"); 
    299             var update<?cs var:field.name ?>  = function() { 
    300                 enableControl('<?cs var:field.name ?>', bm_<?cs var:field.name ?>.checked); 
    301             } 
    302             addEvent(window, 'load', update<?cs var:field.name ?>); 
    303             addEvent(bm_<?cs var:field.name ?>, 'change', update<?cs var:field.name ?>); 
    304         </script> 
    305     <?cs /alt ?>: 
    306     <?cs if:field.type != 'radio' ?></label><?cs /if ?> 
    307     </th> 
    308  
    309  
    310      <td<?cs if:fullrow ?> colspan="3"<?cs /if ?>><?cs 
    311       if:field.type == 'text' ?><input type="text" id="<?cs 
    312         var:name(field) ?>" name="<?cs 
    313         var:name(field) ?>" value="<?cs var:ticket[name(field)] ?>" disabled="true" /><?cs 
    314       elif:field.type == 'select' ?> 
    315         <select id="<?cs 
    316         var:name(field) ?>" name="<?cs 
    317         var:name(field) ?>" enabled="false"><?cs 
    318         if:field.optional ?><option></option><?cs /if ?><?cs 
    319         each:option = field.options ?><option<?cs 
    320          if:option == ticket[name(field)] ?> selected="selected"<?cs /if ?>><?cs 
    321          var:option ?></option><?cs 
    322         /each ?></select><?cs 
    323       elif:field.type == 'checkbox' ?><input type="hidden" name="checkbox_<?cs 
    324         var:name(field) ?>" /><input type="checkbox" id="<?cs 
    325         var:name(field) ?>" name="<?cs 
    326         var:name(field) ?>" value="1"<?cs 
    327         if:ticket[name(field)] ?> checked="checked"<?cs /if ?> disabled="true" /><?cs 
    328       elif:field.type == 'textarea' ?><textarea id="<?cs 
    329         var:name(field) ?>" name="<?cs 
    330         var:name(field) ?>"<?cs 
    331         if:field.height ?> rows="<?cs var:field.height ?>"<?cs /if ?><?cs 
    332         if:field.width ?> cols="<?cs var:field.width ?>"<?cs /if ?> disabled="true" > 
    333 <?cs var:ticket[name(field)] ?></textarea><?cs 
    334       elif:field.type == 'radio' ?><?cs set:optidx = 0 ?><?cs 
    335        each:option = field.options ?><label><input type="radio" id="<?cs 
    336          var:name(field) ?>" name="<?cs 
    337          var:name(field) ?>" value="<?cs var:option ?>"<?cs 
    338          if:ticket[name(field)] == option ?> checked="checked"<?cs /if ?> disabled="true"/> <?cs 
    339          var:option ?></label> <?cs set:optidx = optidx + 1 ?><?cs 
    340         /each ?><?cs 
    341       /if ?></td><?cs 
    342      if:idx % 2 || fullrow ?><?cs 
    343       if:idx < num_fields - 1 ?></tr><tr><?cs 
    344       /if ?><?cs 
    345      elif:idx == num_fields - 1 ?><th class="col2"></th><td></td><?cs 
    346      /if ?><?cs set:idx = idx + #fullrow + 1 ?><?cs 
    347     /if ?><?cs 
    348    /each ?></tr> 
    349  
    350   </table> 
    351 </fieldset> 
    352  
    353 <input type="submit" name="batchmod" value="Batch Modify" /> 
    354 </form> 
    355 <?cs /if ?> 
    356  
    357 <div id="help"> 
    358  <strong>Note:</strong> See <a href="<?cs var:trac.href.wiki ?>/TracQuery">TracQuery</a>  
    359  for help on using queries. 
    360 </div> 
    361  
    362 </div> 
    363 <?cs include:"footer.cs" ?> 
  • setup.py

    old new  
    11#!/usr/bin/env python 
    22# -*- coding: utf-8 -*- 
    3 # Copyright (C) 2006 Ashwin Phatak  
     3# Copyright (C) 2006 Ashwin Phatak 
     4# Copyright (C) 2007 Dave Gynn (dgynn@optaros.com) 
    45 
    56from setuptools import setup, find_packages 
    67 
    78PACKAGE = 'BatchModify' 
    8 VERSION = '0.1.0' 
     9VERSION = '0.5.0' 
    910 
    1011setup( 
    1112    name=PACKAGE, version=VERSION, 
    1213    description='Allows batch modification of tickets', 
    1314    author="Ashwin Phatak", author_email="ashwinpphatak@gmail.com", 
    1415    license='BSD', url='http://trac-hacks.org/wiki/BatchModifyPlugin', 
    15     packages=find_packages(exclude=['ez_setup', '*.tests*'])
     16    packages = ['batchmod']
    1617    package_data={ 
    1718        'batchmod': [ 
    18             'templates/*.cs
     19            'templates/*.html
    1920        ] 
    2021    }, 
    2122    entry_points = {