PKãhô49‰„l¾9¾9tracgantt/gantt.py# Copyright (c) 2005, 2006 Will Barton # All rights reserved. # # Author: Will Barton # # 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. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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. # Standard Python modules import re, datetime, time import tempfile, os import sys, traceback # Trac from trac import util from trac.core import * #from trac.core import ComponentManager from trac.web import IRequestHandler from trac.web.chrome import add_link, add_stylesheet, INavigationContributor, ITemplateProvider from trac.perm import IPermissionRequestor, PermissionSystem from trac.ticket.model import Ticket from trac import db_default class GanttComponent(Component): implements(INavigationContributor, IRequestHandler, ITemplateProvider, IPermissionRequestor) # INavigationContributor def get_active_navigation_item(self, req): return 'gantt' def get_navigation_items(self, req): yield ('mainnav', 'gantt', util.Markup('Gantt Charts' \ % self.env.href.gantt())) # ITemplateProvider def get_htdocs_dirs(self): from pkg_resources import resource_filename return [('gantt',resource_filename(__name__, 'htdocs'))] def get_templates_dirs(self): from pkg_resources import resource_filename return [resource_filename(__name__, 'templates')] # IPermissionRequestor methods def get_permission_actions(self): actions = ['GANTT_VIEW'] return actions + [('GANTT_ADMIN', actions)] # IRequestHandler methods def match_request(self, req): match = re.match(r'/gantt(?:/([0-9]+))?', req.path_info) if match: if match.group(1): req.args['id'] = match.group(1) return 1 def process_request(self, req): # We require both the ability to view reports and the ability to # view gantt charts, since gantt charts are from reports. req.perm.assert_permission('REPORT_VIEW') req.perm.assert_permission('GANTT_VIEW') id = int(req.args.get('id', -1)) action = req.args.get('action', 'list') db = self.env.get_db_cnx() if id == -1: title = 'Available Charts' description = 'This is a list of charts available.' cols,rows = self._reports(db) req.hdf['gantt.id'] = -1 req.hdf['title'] = title req.hdf['description'] = description req.hdf['cols'] = cols req.hdf['rows'] = rows add_stylesheet(req, 'common/css/report.css') else: add_link(req, 'up', self.env.href.gantt(), 'Available Charts') report = self._report_for_id(db, id) if report['id'] > 0: report['title'] = '{%i} %s' % (report['id'], report['title']) req.hdf['title'] = report['title'] req.hdf['gantt.title'] = report['title'] req.hdf['gantt.id'] = report['id'] show_opened = self.env.config.getbool('gantt-charts', 'show_opened', 'false') tickets,dates,broken = self._tickets_for_report(db, report['query']) tickets,dates = self._paginate_tickets(tickets, dates) req.hdf['gantt.tickets'] = tickets req.hdf['gantt.dates'] = dates req.hdf['gantt.broken'] = broken req.hdf['gantt.broken_no'] = len(broken) req.hdf['gantt.show_opened'] = show_opened add_stylesheet(req, 'gantt/gantt.css') return 'gantt.cs', None def _reports(self, db): cursor = db.cursor() cursor.execute("SELECT id AS report,title FROM report ORDER BY " + "report") info = cursor.fetchall() or [] cols = [s[0] for s in cursor.description or []] db.rollback() rows = [{'report':i[0], 'title':i[1], 'href':self.env.href.gantt(i[0])} for i in info] return cols, rows def _report_for_id(self, db, id): cursor = db.cursor() # The 'sql' column changed to 'query' in 0.10, so we want to # continue supporting both cases. # This happened at http://trac.edgewall.org/changeset/3300, # apparently the new db_version is 19 (with the query column) if db_default.db_version >= 19: cursor.execute("SELECT title,query,description from report " \ + "WHERE id=%s", (id,)) else: cursor.execute("SELECT title,sql,description from report " \ + "WHERE id=%s", (id,)) row = cursor.fetchone() if not row: raise util.TracError('Report %d does not exist.' % id, 'Invalid Report Number') title = row[0] or '' query = row[1] description = row[2] or '' return {'id':id, 'title':title, 'query':query, 'description':description} def _tickets_for_report(self, db, query): """ Get a list of Ticket instances for the tickets in a report """ tickets = [] dates = [] broken = [] ## Get tickets for this report cursor = db.cursor() cursor.execute(query) info = cursor.fetchall() or [] cols = [s[0] for s in cursor.description or []] db.rollback() ## Functions for processing the SQL results into the datatypes ## we need # Function to check if ticket is included in the gantt chart ticket_in_gantt = lambda t : \ int(t.values.get('include_gantt', 0)) != 0 # Function to strip everything but numbers out of the given # string, and create an int. Closed ticket ids have a unicode # checkmark. # XXX: This is ugly, since we can't guarnatee types on ticket # ids, we cast to a string, then replace any chars, and then # back to int. ticket_id = lambda i : int(re.sub("[^0-9]*", "", str(i))) # Function to append a Ticket object to a result row ticket_for_info = lambda r : Ticket(self.env, ticket_id(r[cols.index('ticket')])) ## Now process the results # Add ticket objects to each row in the query result, # Note: fetchall() returns a list of tuples, so we have to # convert those tuples to lists # XXX: the cols bit sucks. tlist = map(ticket_for_info, info) # Create a dict from that list with ticket.id as the keys tdict = {} map(lambda t : tdict.setdefault(t.id, t), filter(ticket_in_gantt, tlist)) show_opened = self.env.config.getbool('gantt-charts', 'show_opened', 'false') for i in range(len(info)): row = info[i] # If we get a KeyError, the ticket is not in tdict, because # it is not checked to include in gantt charts. try: ticket = tdict[row[cols.index('ticket')]] except KeyError: continue try: # Get the due to start, due to end, open, and last # change time for the ticket (this also takes into # consideration dependencies times.) start,end,open,changed = \ self._dates_for_ticket(ticket, tdict) # Limit the summary to the max characters configured, or # 16 chars in the gantt chart display. We expose the # full summary to the template, but it's not currently # used. try: sumlen = self.env.config.getint('gantt-charts', 'summary_length', 16) except AttributeError: sumlen = int(self.env.config.get('gantt-charts', 'summary_length', 16)) summary = ticket.values['summary'] if len(summary) > sumlen: shortsum = "%s..." % summary[:16] else: shortsum = summary tickets.append( {'id': ticket.id, 'summary':summary, 'shortsum':shortsum, 'href': self.env.href.ticket(ticket.id), 'start': start.toordinal(), 'end': end.toordinal(), 'open': open.toordinal(), 'changed': changed.toordinal(), 'color': row[cols.index("__color__")] }) if start not in dates: dates.append(start) if end not in dates: dates.append(end) if open not in dates and show_opened: dates.append(open) except Exception, e: self.env.log.debug("Exception for ticket %s" % ticket.id) self.env.log.debug(e) broken.append( {'id': ticket.id, 'href': self.env.href.ticket(ticket.id), 'error':str(e)}) # Get the dates from the tdict, stored as both string values and # ordinal values # Catching a NameError if we're in python 2.3 try: dates = [{'str':str(d), 'ord':d.toordinal()} \ for d in sorted(dates)] except NameError: dates.sort() dates = [{'str':str(d), 'ord':d.toordinal()} for d in dates] # Using that dates list, set the spans of each ticket in the # tickets list. dlist = [d['ord'] for d in dates] map(lambda t : \ t.setdefault('span', 1 + dlist.index(t['end']) - dlist.index(t['start'])), tickets) if show_opened: map(lambda t : \ t.setdefault('ospan', dlist.index(t['start']) - dlist.index(t['open'])), tickets) return tickets,dates,broken def _dates_for_ticket(self, ticket, tdict): import locale # XXX: Conf value date_format = self.env.config.get('gantt-charts', 'date_format', '%m/%d/%Y') # Function to create date objects from date strings date_for_string = lambda s,f : datetime.date.fromtimestamp( time.mktime(time.strptime(s,f))) date_from_date_by_num = lambda n,s : \ datetime.date.fromordinal(s.toordinal() + int(n)) depends = ticket.values.get('dependencies') # Get the start date, if there is one, otherwise we'll find it # later through dependencies start_s = ticket.values.get('due_assign') if start_s: start = date_for_string(start_s, date_format) else: start = None # Cycle through the depends values, and set the start date # appropriately if necessary, based on the due date of a dep if depends: for d in depends.split(","): d = int(d.strip().strip("#")) # If the dependency is not in the ticket dictionary, # it's either closed or not included in gantt charts, # therefore we ignore it. if d not in tdict.keys(): continue d_start,d_due,o,c = self._dates_for_ticket(tdict[d], tdict) if not start or d_due > start: start = d_due # The start date is optional when the ticket depends on another # ticket, otherwise if it is None, we've got a problem. # # Unless explicitly disabled in the config file, use the # creation date as the start date for this ticket. if not start: use_cdate = self.env.config.getbool("gantt-charts", "use_creation_date", "true") if use_cdate: start = datetime.date.fromtimestamp(ticket.time_created) else: raise ValueError, "Couldn't get start date" due_s = ticket.values.get('due_close') if not due_s: raise ValueError, "due date required for inclusion" # due_close can either be an integer for number of days from # the start, or an actual date matching our format. Try the # date first, then if we get a value error (it doesn't match # the format), try it as an integer try: due = date_for_string(due_s, date_format) except ValueError, e: due = date_from_date_by_num(due_s, start) # If the start date is greater than the due date, something # is wrong, so we raise an error, which will mark this # ticket as broken for gantt purposes if start > due: raise ValueError, \ "Ticket #%s start date (%s) is after due date (%s)" \ % (str(ticket.id), str(start), str(due)) # Finally the ticket itself's open and close dates open = datetime.date.fromtimestamp(ticket.time_created) changed = datetime.date.fromtimestamp(ticket.time_changed) return (start, due, open, changed) def _paginate_tickets(self, tickets, dates): return tickets, dates PK‚´ó4:Ыtracgantt/__init__.pyfrom gantt import * PKEjô4ܹ¿^a5a5tracgantt/gantt.pyc;ò :¸¿Dc@sÆdkZdkZdkZdkZdkZdkZdkZdklZdk Tdk l Z dk l Z lZlZlZdklZlZdklZdklZdefd „ƒYZdS( N(sutil(s*(sIRequestHandler(sadd_linksadd_stylesheetsINavigationContributorsITemplateProvider(sIPermissionRequestorsPermissionSystem(sTicket(s db_defaultsGanttComponentcBs‡tZeeeeeƒd„Zd„Zd„Z d„Z d„Z d„Z d„Z d„Zd „Zd „Zd „Zd „ZRS( NcCsdSdS(Nsgantt((sselfsreq((s:build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/gantt.pysget_active_navigation_item3sccs*ddtid|iiiƒƒfVdS(NsmainnavsganttsGantt Charts(sutilsMarkupsselfsenvshrefsgantt(sselfsreq((s:build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/gantt.pysget_navigation_items5scCs'dkl}d|tdƒfgSdS(N(sresource_filenamesganttshtdocs(s pkg_resourcessresource_filenames__name__(sselfsresource_filename((s:build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/gantt.pysget_htdocs_dirs;s cCs!dkl}|tdƒgSdS(N(sresource_filenames templates(s pkg_resourcessresource_filenames__name__(sselfsresource_filename((s:build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/gantt.pysget_templates_dirs>s cCsdg}|d|fgSdS(Ns GANTT_VIEWs GANTT_ADMIN(sactions(sselfsactions((s:build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/gantt.pysget_permission_actionsDs cCsRtid|iƒ}|o2|idƒo|idƒ|id¸scCsttiddt|ƒƒƒS(Ns[^0-9]*s(sintsressubsstrsi(si((s:build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/gantt.pysÁscs#tˆiˆ|ˆidƒƒƒS(Nsticket(sTicketsselfsenvs ticket_idsrscolssindex(sr(scolssselfs ticket_id(s:build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/gantt.pysÄs csˆi|i|ƒS(N(stdicts setdefaultstsid(st(stdict(s:build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/gantt.pysÑss gantt-chartss show_openedsfalsesticketssummary_lengthissummarys%s...sidsshortsumshrefsstartsendsopenschangedscolors __color__sException for ticket %sserrorsstrsordcs2|iddˆi|dƒˆi|dƒƒS(Nsspanisendsstart(sts setdefaultsdlistsindex(st(sdlist(s:build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/gantt.pys s cs.|idˆi|dƒˆi|dƒƒS(Nsospansstartsopen(sts setdefaultsdlistsindex(st(sdlist(s:build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/gantt.pys%sN(<sticketssdatessbrokensdbscursorsexecutesquerysfetchallsinfosappends_[1]s descriptionssscolssrollbacksticket_in_gantts ticket_idsticket_for_infosmapstliststdictsfiltersselfsenvsconfigsgetbools show_openedsrangeslensisrowsindexsticketsKeyErrors_dates_for_ticketsstartsendsopenschangedsgetintssumlensAttributeErrorsintsgetsvaluesssummarysshortsumsidshrefs toordinals Exceptionseslogsdebugsstrssortedsds NameErrorssortsdlist(sselfsdbsqueryssumlenscolssticket_in_ganttsopensrows_[1]s show_openedsendsdlistsshortsumsstarts ticket_idsinfosbrokensticketsticketssdatessesdstdictsischangedsticket_for_infossummaryscursorssstlist((sselfstdictsdlistscolss ticket_ids:build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/gantt.pys_tickets_for_report¦sx  5       ˜  LO J+   cCs@dk}|iiidddƒ} d„}d„}|i idƒ}|i idƒ}|o||| ƒ} nt } |o”x‘|idƒD]|}t|iƒid ƒƒ}||iƒjoq‘n|i|||ƒ\}}}}| p || jo |} q‘q‘Wn| oH|iiidd d ƒ} | otii|iƒ} qetd ‚n|i id ƒ} | o td‚ny|| | ƒ} Wn$tj o}|| | ƒ} nX| | jo/tdt#|i$ƒt#| ƒt#| ƒf‚ntii|iƒ}tii|i&ƒ}| | ||fSdS(Ns gantt-chartss date_formats%m/%d/%YcCs%tiititi||ƒƒƒS(N(sdatetimesdates fromtimestampstimesmktimesstrptimesssf(sssf((s:build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/gantt.pys4scCs tii|iƒt|ƒƒS(N(sdatetimesdates fromordinalsss toordinalsintsn(snss((s:build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/gantt.pys6ss dependenciess due_assigns,s#suse_creation_datestruesCouldn't get start dates due_closesdue date required for inclusions1Ticket #%s start date (%s) is after due date (%s)((slocalesselfsenvsconfigsgets date_formatsdate_for_stringsdate_from_date_by_numsticketsvaluessdependssstart_ssstartsNonessplitsdsintsstripstdictskeyss_dates_for_ticketsd_startsd_duesoscsgetbools use_cdatesdatetimesdates fromtimestamps time_createds ValueErrorsdue_ssduesesstrsidsopens time_changedschanged(sselfsticketstdictslocalesdependssdate_from_date_by_numsopensd_startsd_duesduesstarts use_cdatesdue_ss date_formatscsesdschangedsosdate_for_stringsstart_s((s:build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/gantt.pys_dates_for_ticket,sJ    "    /cCs||fSdS(N(sticketssdates(sselfsticketssdates((s:build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/gantt.pys_paginate_tickets{s(s__name__s __module__s implementssINavigationContributorsIRequestHandlersITemplateProvidersIPermissionRequestorsget_active_navigation_itemsget_navigation_itemssget_htdocs_dirssget_templates_dirssget_permission_actionss match_requestsprocess_requests_reportss_report_for_ids_tickets_for_reports_dates_for_tickets_paginate_tickets(((s:build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/gantt.pysGanttComponent-s         3  † O(sresdatetimestimestempfilesosssyss tracebackstracsutils trac.corestrac.websIRequestHandlerstrac.web.chromesadd_linksadd_stylesheetsINavigationContributorsITemplateProviders trac.permsIPermissionRequestorsPermissionSystemstrac.ticket.modelsTickets db_defaults ComponentsGanttComponent(sINavigationContributorsutilsITemplateProvidersIRequestHandlerstempfilesadd_stylesheets tracebacks db_defaultsdatetimessyssresPermissionSystemsadd_linksIPermissionRequestorstimesGanttComponentsTicketsos((s:build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/gantt.pys?s    PKEjô4!,B©©tracgantt/__init__.pyc;ò ì¾Dc@s dkTdS((s*N(sgantt(((s=build/bdist.freebsd-5.4-STABLE-i386/egg/tracgantt/__init__.pys?sPK‚´ó4ôqu(© © tracgantt/templates/gantt.cs

Gantt Charts

{}

Gantt Chart

ticket.end) ?>
# # Opened ticket.end) ?>
0 ?>

WARNING: The following tickets had errors that prevented them from being included in the gantt chart:

  • # -
PK‚´ó4ž%‡5``tracgantt/htdocs/gantt.css .gantt td, .gantt td.active, .gantt td.open { padding: 0.2em 0.1em; } .gantt td a { width: 100%; display: block; border: 0; } .gantt td.open a { background: transparent; border-color: #eed; color: #eed; font-style: italic; text-align: right; border-top: 0; border-left: 0; border-right: 0; } .gantt td.open a:hover { background: transparent; color: #eed; } .gantt a.color1 { background: #fdc; color: #a22; border: 1px solid #e88; border-bottom: 5px solid #e88; } .gantt a.color2 { background: #ffb; color: #880; border: 1px solid #eea; border-bottom: 5px solid #eea; } .gantt a.color3 { background: #fbfbfb; color: #444; border: 1px solid #ddd; border-bottom: 5px solid #ddd; } .gantt a.color4 { background: #e7ffff; color: #099; border: 1px solid #cee; border-bottom: 5px solid #cee; } .gantt a.color5 { background: #e7eeff; color: #469; border: 1px solid #cde; border-bottom: 5px solid #cde; } .gantt a.color6 { background: #f0f0f0; color: #888; border: 1px solid #ddd; border-bottom: 5px solid #ddd; } .gantt a.color1:hover { background: #fdc; color: #a22 } .gantt a.color2:hover { background: #ffb; color: #880 } .gantt a.color3:hover { background: #fbfbfb; color: #444 } .gantt a.color4:hover { background: #e7ffff; color: #099 } .gantt a.color5:hover { background: #e7eeff; color: #469 } .gantt a.color6:hover { background: #f0f0f0; color: #888 } PKEjô4ÙǪEGG-INFO/PKG-INFOMetadata-Version: 1.0 Name: TracGantt Version: 0.3.2a Summary: This is a Gantt-Chart creation plugin for Trac. Home-page: http://willbarton.com/code/tracgantt/ Author: Will Barton Author-email: wbb4@opendarwin.org License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN PKEjô4 ÂüüEGG-INFO/SOURCES.txtREADME ez_setup.py setup.py TracGantt.egg-info/PKG-INFO TracGantt.egg-info/SOURCES.txt TracGantt.egg-info/top_level.txt TracGantt.egg-info/trac_plugin.txt tracgantt/__init__.py tracgantt/gantt.py tracgantt/htdocs/gantt.css tracgantt/templates/gantt.cs PKEjô4Õ$Fö EGG-INFO/top_level.txttracgantt PK‚´ó4Õ$Fö EGG-INFO/trac_plugin.txttracgantt PKEjô4EGG-INFO/zip-safePKãhô49‰„l¾9¾9¤tracgantt/gantt.pyPK‚´ó4:Ы¤î9tracgantt/__init__.pyPKEjô4ܹ¿^a5a5¤5:tracgantt/gantt.pycPKEjô4!,B©©¤Çotracgantt/__init__.pycPK‚´ó4ôqu(© © ¤¤ptracgantt/templates/gantt.csPK‚´ó4ž%‡5``¤‡ztracgantt/htdocs/gantt.cssPKEjô4ÙǪ¤€EGG-INFO/PKG-INFOPKEjô4 Âüü¤\EGG-INFO/SOURCES.txtPKEjô4Õ$Fö ¤Š‚EGG-INFO/top_level.txtPK‚´ó4Õ$Fö ¤È‚EGG-INFO/trac_plugin.txtPKEjô4¤ƒEGG-INFO/zip-safePK ä7ƒ