o k`b@s8ddlmZmZddlZddlZddlZddlmZmZddlm Z m Z m Z ddl Tddl mZddlmZddlTddlmZmZmZdd lmZmZdd lmZmZmZmZmZmZm Z m!Z!m"Z"m#Z#dd l$m%Z%dd l&m'Z'dd l(m)Z)m*Z*m+Z+ddl,m-Z-m.Z.ddl/m0Z0ddl1m2Z2ddl3m4Z4m5Z5m6Z6ddl7m8Z8ddl9m:Z:m;Z;mZ>m?Z?m@Z@mAZAmBZBmCZCmDZDmEZEmFZFmGZGddlHmIZIddlJmKZKGdddeLZMGdddeNZOGdddePZQddZRd3d d!ZSd4d"d#ZTd5d%d&ZUd'd(ZV  d6d)d*ZWd+d,ZXd-d.ZYGd/d0d0ePZZGd1d2d2ePZ[dS)7)datetime timedeltaN) AttachmentAttachmentModule) ConfigSectionExtensionOptionOption)*)NotificationSystem)IPermissionRequestor) ISearchSourcesearch_to_regexpsshorten_result)as_bool partition) datetime_now format_dateformat_datetimefrom_utimestampget_datetime_format_hint parse_datepretty_timedelta to_datetime user_timeutc)tag)classes)CRLFexception_to_unicode to_unicode)_tag_) TicketSystem)BatchTicketChangeEvent) MilestoneMilestoneCacheTicket)ITimelineEventProvider)HTTPBadRequestIRequestHandler RequestDone) ChromeINavigationContributor accesskeyadd_link add_noticeadd_stylesheet add_warning auth_link prevnext_nav web_context)IWikiSyntaxProvider) format_toc@seZdZddZdS)ITicketGroupStatsProvidercCdS)zr Gather statistics on a group of tickets. This method returns a valid `TicketGroupStats` object. N) ticket_idsr9r95/usr/lib/python3/dist-packages/trac/ticket/roadmap.pyget_ticket_group_stats5sz0ITicketGroupStatsProvider.get_ticket_group_statsN)__name__ __module__ __qualname__r<r9r9r9r;r73s r7c@s,eZdZdZddZ d ddZddZdS) TicketGroupStatsz.Encapsulates statistics on a group of tickets.cCs.||_||_d|_i|_g|_d|_d|_dS)z :param title: the display name of this group of stats (e.g. ``'ticket status'``) :param unit: is the units for these stats in plural form, e.g. ``_('hours'``) rN)titleunitcountqry_args intervals done_percent done_count)selfrArBr9r9r;__init__?s zTicketGroupStats.__init__Nc Cs*|j||||d|d|j||_dS)aAdds a division to this stats' group's progress bar. :param title: the display name (e.g. ``'closed'``, ``'spent effort'``) of this interval that will be displayed in front of the unit name :param count: the number of units in the interval :param qry_args: a dict of extra params that will yield the subset of tickets in this interval on a query. :param css_class: is the css class that will be used to display the division :param overall_completion: can be set to true to make this interval count towards overall completion of this group of tickets. .. versionchanged :: 0.12 deprecated `countsToProg` argument was removed, use `overall_completion` instead N)rArCrD css_classpercentoverall_completion)rEappendrC)rHrArCrDrJrLr9r9r; add_intervalNszTicketGroupStats.add_intervalcCs|jdkrdSd}d|_d|_|jD]/}tt|dt|jd|d<||d}|drA|j|d7_|j|d7_q|jrq|dkrsd|}ddt|jd d |dkd Dd}|d|7<|j|7_dSdSdS) NrrCdrKrLcSsg|]}|dr|qS)rKr9).0ir9r9r; s z2TicketGroupStats.refresh_calcs..cS|dS)NrKr9)kr9r9r;z0TicketGroupStats.refresh_calcs..)keyreverse)rCrFrGrEroundfloatsorted)rH total_percentinterval fudge_amt fudge_intr9r9r; refresh_calcsms6     zTicketGroupStats.refresh_calcsN)r=r>r?__doc__rIrNrar9r9r9r;r@<s  r@c@sLeZdZdZeeeddZddddddd d d gZd d Z ddZ dS)DefaultTicketGroupStatsProviderzConfigurable ticket group statistics provider. See :teo:`TracIni#milestone-groups-section` for a detailed example configuration. milestone-groupsaS As the workflow for tickets is now configurable, there can be many ticket states, and simply displaying closed tickets vs. all the others is maybe not appropriate in all cases. This section enables one to easily create ''groups'' of states that will be shown in different colors in the milestone progress bar. Note that the groups can only be based on the ticket //status//, nothing else. In particular, it's not possible to distinguish between different closed tickets based on the //resolution//. Example configuration with three groups, //closed//, //new// and //active// (the default only has closed and active): {{{ # the 'closed' group correspond to the 'closed' tickets closed = closed # .order: sequence number in the progress bar closed.order = 0 # .query_args: optional parameters for the corresponding # query. In this example, the changes from the # default are two additional columns ('created' and # 'modified'), and sorting is done on 'created'. closed.query_args = group=resolution,order=time,col=id,col=summary,col=owner,col=type,col=priority,col=component,col=severity,col=time,col=changetime # .overall_completion: indicates groups that count for overall # completion percentage closed.overall_completion = true new = new new.order = 1 new.css_class = new new.label = new # Note: one catch-all group for other statuses is allowed active = * active.order = 2 # .css_class: CSS class for this interval active.css_class = open # .label: displayed label for this group active.label = in progress }}} The definition consists in a comma-separated list of accepted status. Also, '*' means any status and could be used to associate all remaining states to one catch-all group. The CSS class can be one of: new (yellow), open (no color) or closed (green). Other styles can easily be added using custom CSS rule: `table.progress td. { background: }` to a [TracInterfaceCustomization#SiteAppearance site/style.css] file for example. closedzgroup=resolutiontrue)namestatus query_argsrLactiver open)rhrirJcCsd|jvrCi}d}|jD]*\}}d}d|vr |dd\}}||||d}|||<t|t|dd}qt|dd d S|j S) zReturns a list of dict describing the ticket groups in the expected order of appearance in the milestone progress bars. rerri.rO)rhorderrncSs t|dS)Nrn)int)gr9r9r;rVs zDDefaultTicketGroupStatsProvider._get_ticket_groups..)rX) configmilestone_groups_sectionoptionssplit setdefaultmaxror\valuesdefault_milestone_groups)rHgroupsrn groupnamevalue qualifiergroupr9r9r;_get_ticket_groupss z2DefaultTicketGroupStatsProvider._get_ticket_groupsc Cst|}tt|j}i}|D]}d||<q|r5|jddddt|DD]\}}|||<q,tt dt d}t|} | } d} | D]F} | d } | d krh| ret t d | d | d d | } qJd d| dD|@}|| rt t d| d d|| d| |8} || d<qJ| r| | d<| D]m} d}i}|D]!\}}|| dvr||7}|dg|d|vr|dqdd| dd dDD]}dd| ddD\}}||g|q|| d| d ||| d| d t| dq||S)Nrz SELECT status, count(status) FROM ticket WHERE id IN (%s) GROUP BY status ,css|]}t|VqdSrb)str)rQxr9r9r; szIDefaultTicketGroupStatsProvider.get_ticket_group_stats..z ticket statusticketsrir z{'%(group1)s' and '%(group2)s' milestone groups both are declared to be "catch-all" groups. Please check your configuration.rh)group1group2cSsh|]}|qSr9strip)rQsr9r9r; zIDefaultTicketGroupStatsProvider.get_ticket_group_stats..zz'%(groupname)s' milestone group reused status '%(status)s' already taken by other groups. Please check your configuration.z, )rzristatusescSsg|]}d|vr|qS=r9)rQkvr9r9r;rSszJDefaultTicketGroupStatsProvider.get_ticket_group_stats..rjcSsg|]}|qSr9r)rQar9r9r;rSrrrOlabelrJrL)lensetr"envget_all_statusdb_queryjoinr\r@r r~r TracErrorrtitemsrurMsortgetrNrra)rHr: total_cnt all_statuses status_cntrrirCstatremaining_statusesrycatch_all_groupr} status_strgroup_statuses group_cntrjcntargrUvr9r9r;r<sp          z6DefaultTicketGroupStatsProvider.get_ticket_group_statsN) r=r>r?rc implementsr7rrrrxr~r<r9r9r9r;rds<  rdcCs|dd|DS)NcSsg|]}|dqSidr9rQtr9r9r;rS(rz$get_ticket_stats..)r<)providerrr9r9r;get_ticket_stats'sr componentcsXt|}dd|Dvrdf}|f}nd}|f}fdd|||DS)z@Retrieve all tickets associated with the given `milestone`. cSsg|] }|ds|dqS)customrhrrQfr9r9r;rS/sz-get_tickets_for_milestone..zVSELECT id, status, %s FROM ticket WHERE milestone=%%s ORDER BY %s, idzSELECT id, status, value FROM ticket LEFT OUTER JOIN ticket_custom ON (id=ticket AND name=%s) WHERE milestone=%s ORDER BY value, idcs"g|] \}}}d|d||iqS)rrir9)rQtkt_idrifieldvalfieldr9r;rS8s)r"get_ticket_fieldsr)r milestonerfieldssqlargsr9rr;get_tickets_for_milestone+s   rc s|jP}t|}tfdd|Dr!dd|i}d}nd}f}|}|||i}t|ddD]\}} fd d | D||<q:|WdS1sVwYdS) Nc3s(|]}|dko|d VqdS)rhrNrrrr9r;r?s&z1get_tickets_for_all_milestones..zSELECT id, status, %(field)s, milestone FROM ticket WHERE milestone != '' ORDER BY milestone, %(field)s, id rr9a%SELECT t.id, t.status, c.value, t.milestone FROM ticket AS t LEFT OUTER JOIN ticket_custom AS c ON (t.id=c.ticket AND c.name=%s) WHERE t.milestone != '' ORDER BY t.milestone, c.value, t.idcSrT)Nr9)rowr9r9r;rVQrWz0get_tickets_for_all_milestones..c s(g|]}d|dd|d|diqS)rrrirOr9)rQrrr9r;rSRs z2get_tickets_for_all_milestones..) rr"ranyquotecursorexecute itertoolsgroupby) rrdbrrrrresultsrr}r9rr;get_tickets_for_all_milestones<s$     $rFcCs:t|tr|jn|}d}|r|d7}|||fddS)a'Returns the number of tickets associated with the milestone. :param milestone: name of a milestone or a Milestone instance. :param exclude_closed: whether tickets with status 'closed' should be excluded from the count. Defaults to False. :since: 1.2 z.SELECT COUNT(*) FROM ticket WHERE milestone=%sz AND status != 'closed'r) isinstancer$rhr)rrexclude_closedrhrr9r9r;get_num_tickets_for_milestoneWs  rcsfdd|DS)zaApply permissions to a set of milestone tickets as returned by `get_tickets_for_milestone()`.cs$g|]}dd|dvr|qS) TICKET_VIEWticketr)permrreqr9r;rSjsz,apply_ticket_permissions..r9)rrrr9rr;apply_ticket_permissionsgsrcsNddlm}||dufdd||jfdd|jDdS)Nr) QueryModulecs.sdSdddi}||j|S)Nrr}ri)updatehrefquery) extra_argsr)r} grouped_by has_queryrhrr9r; query_hrefrs   z(milestone_stats_data..query_hrefcsg|]}|dqS)rDr9)rQr^)rr9r;rSzz(milestone_stats_data..)stats stats_hrefinterval_hrefs)trac.ticket.queryrrDrE)rrrrhrr}rr9)r}rrrhrrr;milestone_stats_datans   rcsTg}t|D]F}|dkrNd|vr$|d}|dr#|ddq|dr@dd|d fD}d|vr?|ddqd d|d fD}qd}g}|D]6} | r\| fnd | ffd d|D} | smqUt|| } | j|krz| j}d| i} | || | || qU|D]} d} |r| d} t | jt |d} | | d<q|S)zGet the `tickets` stats data grouped by ticket field `by`. `per_group_stats_data(gstat, group_name)` should return a data dict to include for the group with field value `group_name`. rhrsoptionalrrrcSg|]\}|qSr9r9rQrhr9r9r;rSz&grouped_stats_data..z SELECT DISTINCT COALESCE(c.value, '') FROM ticket_custom c WHERE c.name=%s ORDER BY COALESCE(c.value, '') cSrr9r9rr9r9r;rSrz SELECT DISTINCT COALESCE(%s, '') FROM ticket ORDER BY COALESCE(%s, '') Ncsg|] }|vr|qSr9r9rbyrwr9r;rSg?rrPpercent_of_max_total) r"rrinsertrrrCrrMr[)rstats_providerrrper_group_stats_data group_namesr max_countdatarh group_ticketsgstatgs_dictrKr9rr;grouped_stats_data~sN            rcsXddtfdd|Dd\}}}td|ftd|fg}|r*|td|f|S) zGroup milestones into "open with due date", "open with no due date", and possibly "completed". Return a list of (label, milestones) tuples.cSs|jrdS|jr dSdS)NrOrr) is_completeddue)mr9r9r;categorysz"group_milestones..categorycsg|]}||fqSr9r9rQrrr9r;rSrz$group_milestones..)rrrOzOpen (by due date)zOpen (no due date)Closed)rr rM) milestonesinclude_completedopen_due_milestonesopen_not_due_milestonesclosed_milestonesryr9rr;group_milestoness   rc@s\eZdZdZeeeeedde ddZ ddZ dd Z d d Z d d ZddZddZdS) RoadmapModulezOverview of all the milestones.roadmaprrdzName of the component implementing `ITicketGroupStatsProvider`, which is used to collect statistics on groups of tickets for display in the roadmap views.cCr8Nrr9rHrr9r9r;get_active_navigation_itemz(RoadmapModule.get_active_navigation_itemccs<d|jvrddtjtd|jt|ddfVdSdS)N ROADMAP_VIEWmainnavrRoadmapr)rr-)rrrr rrr-rr9r9r;get_navigation_itemss  z"RoadmapModule.get_navigation_itemscCsgd}dgd|fgS)N)MILESTONE_CREATEMILESTONE_DELETEMILESTONE_MODIFYMILESTONE_VIEWrr ROADMAP_ADMINr9rHactionsr9r9r;get_permission_actionssz$RoadmapModule.get_permission_actionscCs |jdkS)Nz/roadmap) path_inforr9r9r; match_requests zRoadmapModule.match_requestc s@jdjd}d|vrdg}t|jd|v}d|vr'dd|D}fdd|D}g}g}t|jd d }|D]#}||j pFg}t |j|}t |j |} | t|j| |j q=jd d krq||dSd} jryj} jj|| d d } tdt| tddd ||||d} tdd| fS)Nrshowall completed noduedatecSs g|] }|jdus |jr|qSrb)rrrr9r9r;rSz1RoadmapModule.process_request..c g|] }d|jvr|qSr rresourcerrr9r;rSrownerrformatics)ruserr alternate iCalendarz text/calendar)rmilestone_statsqueriesrcommon/css/roadmap.cssz roadmap.html)rrequirergetlistr$selectrrrrhrrrrMr _render_icsis_authenticatedauthnamerrr.r2r r0) rHrrrrr$ all_ticketsrrrusernameicshrefrr9rr;process_requestsF     zRoadmapModule.process_requestc s6dddtddlm}i||jD] }t|j |j <qfdd}dd }d d iffd d iffdd }iffdd }j j ddd} j dddddddd|jjddd|jjdtd d!|jjd"tjt|jd#d$} |D]} d%j| j | f} | jrdd&d'| |d(| j|d)| jd*td+| j d,d-j| j | jrd.| jd/d&| | j pg} t|j| } fd0d1| DD]{}t|j|}dd2d'd3j|| f| jr&d4| |d5| jd*td6|j|d7d8d-j|jd.|d9||}|rRd:t|d;|||d<d=kru|jd>|jfD] \}|d?t |qid/d2qqd/d!"d@}dAt#|$%|t&)BNz Content-Typeztext/calendar;charset=utf-8r)Prioritycs:|d}|rttd|dtdSdS)Npriority rO)rror)rr{) prioritiesr9r; get_priority s  z/RoadmapModule._render_ics..get_prioritycSsP|d}|dks|dkr|dsdS|dvrdS|dkr&|d d kr$d Sd Sd S)Nrinewreopenedrz NEEDS-ACTION)assignedr8z IN-PROCESSrf resolutionfixed COMPLETED CANCELLEDrr9)rrir9r9r; get_status&s z-RoadmapModule._render_ics..get_statuscSs&dtdd|}dtd|S)NrcSs|dvrd|S|S)Nz;,\\r9)cr9r9r;rV6rzARoadmapModule._render_ics..escape_value..z\nz[\r\n]+)rmaprert)textrr9r9r; escape_value5sz/RoadmapModule._render_ics..escape_valuecszd|gdd|Dd|}d}t|}|r;|s$d|}nd}|ddt|dd}|sdSdS) N;cSsg|] \}}|d|qSrr9)rQrUrr9r9r;rS;rzARoadmapModule._render_ics..write_prop..:rO rK)rrrwriter)rhr{paramsrC firstline)bufrDr9r; write_prop9s    z-RoadmapModule._render_ics..write_propcs"d|d<|t|dj|dS)NDATEVALUEz%Y%m%d)rtzrhr{rJ)rrMr9r; write_dateGsz-RoadmapModule._render_ics..write_datecs|t|dt|dS)Nz%Y%m%dT%H%M%SZ)rrrQ)rMr9r; write_utctimeKsz0RoadmapModule._render_ics..write_utctimez://rr  anonymousBEGIN VCALENDARVERSIONz2.0PRODIDz)-//Edgewall Software//NONSGML Trac %s//ENMETHODPUBLISHz X-WR-CALNAMEz - rz X-WR-CALDESCz X-WR-TIMEZONErrz<%s/milestone/%s@%s>VEVENTUIDDTSTAMPDTSTARTSUMMARYMilestone %(name)srhURL DESCRIPTIONENDcs g|] }|dkr|dqS)rrr9)rQr)r r9r;rSms z-RoadmapModule._render_ics..VTODOz<%s/ticket/%s@%s>z RELATED-TODUEzTicket #%(num)s: %(summary)ssummary)numrg descriptionPRIORITYSTATUSrirfz SELECT time FROM ticket_change WHERE ticket=%s AND field='status' ORDER BY time desc LIMIT 1 r<zutf-8zContent-Length)' send_response send_headerioStringIO trac.ticketr1r(rr[r{rhbase_urlfindrr trac_version project_namer project_descriptionrrPr base_pathrabs_hrefrrirr&rrrrgetvalueencoder end_headersrIr*)rHrrr1r2r6r>rRrShostr,ruidrrrtimeics_strr9)rLrDr5rr rMr;r)s                       zRoadmapModule._render_icsN)r=r>r?rcrr,r r)rr7rrr rrr/r)r9r9r9r;rs   1rc@s(eZdZdZeeeeee e e dZ e ddeddZeddddZedd d d Zd d ZddZddZddZddZddZddZddZddZddZeZed d!Zd"d#Zd$d%Z d&d'Z!d(d)Z"d*d+Z#d,d-Z$d.d/Z%d0d1Z&d@d3d4Z'd5d6Z(dAd8d9Z)d:d;Z*dd?Z,d7S)BMilestoneModulez$View and edit individual milestones.rrrdzName of the component implementing `ITicketGroupStatsProvider`, which is used to collect statistics on groups of tickets for display in the milestone views.default_retarget_tozuDefault milestone to which tickets are retargeted when closing or deleting a milestone. (''since 1.1.2''))docdefault_group_byrz^Default field to use for grouping tickets in the grouped progress bar. (''since 1.2'')cCr8rr9rr9r9r;rrz*MilestoneModule.get_active_navigation_itemcCgSrbr9rr9r9r;r rz$MilestoneModule.get_navigation_itemscCsgd}|d|fgS)N)r r r r MILESTONE_ADMINr9rr9r9r;rsz&MilestoneModule.get_permission_actionscc"d|jvrdtdfVdSdS)Nr rzMilestones completedrr rr9r9r;get_timeline_filters z$MilestoneModule.get_timeline_filtersc csd|vrMt|j}t|jjD])\}}}} |r;||kr$|kr;nq||d} d|| vr;d|d| | ffVqt|j||||D]} | VqGdSdS)Nrrr r) Resourcerealmr%rrrwrrget_timeline_events) rHrstartstopfiltersmilestone_realmrhrrrireventr9r9r;rs$    z#MilestoneModule.get_timeline_eventscCsf|d\}}|dkr|j|jS|dkrtdt|jdS|dkr1|j|d}t|jd||SdS)NrurlrAzMilestone %(name)s completedrari)r) rrrr!remchildr6r)rHcontextrrrrichild_resourcer9r9r;render_timeline_events   z%MilestoneModule.render_timeline_eventcCs4td|j}|r|dr|d|jd<dSdS)Nz/milestone(?:/(.+))?$rOrT)rBmatchrr}r)rHrrr9r9r;rs  zMilestoneModule.match_requestcCsp|jd}|jdd}|s|dkr||j||j|dt|d|jt dzt |j |}Wnt yVd||j|vrJt |j }||_ d}Ynw|jd krd |jvrx|jro||j|j n8||jn/|dkr|||S|d kr|||ntt d |d vr|||S|d kr|||S|j s||j|||S)Nractionviewr uprr editPOSTcanceldeletezInvalid request arguments.)r7r)rrredirectrrrrr&r.r r$rResourceNotFoundrhmethodexistsr_do_save _do_deleter(_render_editor_render_confirm _render_view)rHr milestone_idrrr9r9r;r/s@           zMilestoneModule.process_requestcCsBt|j}t|j|j|jd}|jdkr|tdd7}t||jS)zReturns a `datetime` object representing the default due date in the user's timezone. The default due time is 18:00 in the user's time zone. rO)days) rrPryearmonthdayhourrr)rHrnow default_duer9r9r;get_default_due s   zMilestoneModule.get_default_duec sgfdd}jdd|_djvr*jd}|r&tt|ddnd|_nd|_d jvrTjd d}|rCtt|ddnd}|rS|ttkrS|td nd}||_ jd }zt |j |}Wn t ys||_ Ynw|j |j kr|j r|td |j dn|tdrdS|jr+z |jjdWntyttd |j dw|r"djvr"jdd}jdpd} |j| j|dd} ttd|j | dd| i} |ptd}t| dj|| d} z t|j | Wn'ty!} z|jdt| ttdt| dWYd} ~ nd} ~ wwttddSz|WntyBttd |j dwttd|j ddS) Ncst||dSrb)r1rM)msgrwarningsr9r;warns z,MilestoneModule.save_milestone..warnrirrduedater)hintr completeddatez(Completion date may not be in the futurerhz@Milestone "%(name)s" already exists, please choose another name.raz*You must provide a name for the milestone.F)authorretargetcommenttargetTrzgThe open tickets associated with milestone "%(name)s" have been retargeted to milestone "%(retarget)s".rhrrz.Open tickets retargeted after milestone closed7Failure sending notification on ticket batch change: %s[The changes have been saved, but an error occurred while sending notifications: %(message)smessagezYour changes have been saved.z(The milestone "%(name)s" has been added.)rrrirrrrrr rr$rrrhrrr+ResourceExistsError move_ticketsr/r#r notify Exceptionlogerrorrr1r!rr)rHrrrrrnew_name new_milestoner retarget_toretargeted_tickets new_valuesrer9rr;save_milestones               zMilestoneModule.save_milestonecs:jrtfddtjDsjdjjS)Nc3s|] }j|jkVqdSrb)_default_retarget_torhrrHr9r;rsz6MilestoneModule.default_retarget_to..zmMilestone "%s" does not exist. Update the "default_retarget_to" option in the [milestone] section of trac.ini)rrr$r(rrwarningrr9rr;rs  z#MilestoneModule.default_retarget_toc Cs||jd|jdpd}|||jd}|t|t d|j d|rwt|t d|j |dd|i}t d }t |d|j||d}z t |j |Wn&tyv}z|jd t|t|td t|d WYd}~nd}~ww||jdS) Nr rz)Ticket retargeted after milestone deletedz*The milestone "%(name)s" has been deleted.razbThe tickets associated with milestone "%(name)s" have been retargeted to milestone "%(retarget)s".rrz*Tickets retargeted after milestone deletedrrr)rrr&rrrr+rr/r rhr#r rrrrrrr1r!rrrr) rHrrrrrrrrr9r9r;rsB zMilestoneModule._do_deletecCsX|jr ||jdn ||jd|||r&||j|j| ||S)Nr r ) rrrr&rrrrrhr)rHrrr9r9r;rs   zMilestoneModule._do_savecs~jdfddt|jD}t|j|jj}t |djvt |j|j t |d}t dd|fS)Nr c,g|]}|jjkrd|jvr|qSrrhrrrrrr9r;rS  z3MilestoneModule._render_confirm.. TICKET_ADMIN)rmilestone_groups num_ticketsr attachmentsr%zmilestone_delete.html)rrr&r$r(rrrrhrrrlistr0)rHrrrrrr9rr;rs  zMilestoneModule._render_confirmcstj|gd}jrAjdfddt|j D}t |djv|d<t |j dd|d <|j |d <njd j rWttd j d t|j }|||tdd|fS)N)r datetime_hintrrr crrrrrr9r;rSrz2MilestoneModule._render_editor..rrTrnum_open_ticketsrr z:Milestone %(name)s does not exist. You can create it here.rar%zmilestone_edit.html)rlc_timerrrrr&r$r(rrrrrhr/r r+ add_jquery_uiadd_wiki_toolbarsadd_auto_previewr0)rHrrrrchromer9rr;rs4      zMilestoneModule._render_editorcs g}g}d}tj}|D])}|ddkr|ddks#|ddvr8||d|dd|djkr8d }qd|rAjn|rI|d djd tjjd }t j|}t j |} t j } | tj| ||d } | tj| jrfdd} |tjj || tdfdd} fddtjD}fddt|D}|r|d }|d kr| d|d | d||d|t|dkr| d||d| d|dttdtdtdd| fS) NFtyper(rhr)rreporterr)rhrTrr)rr)rrravailable_groupsrrycstj|j|Srb)rrrh)r group_namerrrrHr9r;rs z:MilestoneModule._render_view..per_group_stats_datar%cs6jj|jjdd}t||td|jddS)Nr)rzMilestone "%(name)s"ra)rrrhrrr.r )relrrrr9r;add_milestone_links  z8MilestoneModule._render_view..add_milestone_linkcrrrrrr9r;rS rz0MilestoneModule._render_view..cs g|] \}}|jjkr|qSr9ra)rQrRr)rr9r;rS"s firstprevrOnextlastzPrevious MilestonezNext MilestonezBack to Roadmapzmilestone_view.html)r"rrrMrrgetfirstrrhrrrr4rrattachment_datarrextendrr0r$r( enumeraterr3r )rHrrrrdefault_group_by_available ticket_fieldsrrrrrrrridxr9rr;rsr         zMilestoneModule._render_viewcCrrbr9rr9r9r;get_wiki_syntax2rzMilestoneModule.get_wiki_syntaxccsd|jfVdS)Nr) _format_linkrr9r9r;get_link_resolvers5sz"MilestoneModule.get_link_resolverscCs&||\}}}||j||||Srb) split_link _render_linkr)rH formatternsrhrrfragmentr9r9r;r8s zMilestoneModule._format_linkrc CsX|s|stSzt|j|}Wn tyd}Ynw|j|}|o%|j}|rd||jvrd}t |drw|j rKt dt |j t|jt|j d}n,|jr_t dt |jt|jt|jd}n|jrst dt |jt|jt|jd}nt d}|j r|dnd } tj|d | |||d Snd ||j|vrtj|d ||ddStj|td| ddS)Nr rz%Completed %(duration)s ago (%(date)s))durationdatez%(duration)s late (%(date)s)zDue in %(duration)s (%(date)s)z No date setzclosed rz %smilestone)class_rrAr zmissing milestonenofollow)rrrr)missing)r)rr$rrrrrrrhasattrrr rrrrris_laterrrr) rHrrhrextrarrrrArfr9r9r;r=s\     zMilestoneModule._render_linkccs|jVdSrb)rrr9r9r;get_resource_realmsis z#MilestoneModule.get_resource_realmsNcKs4|j}|dkrtd|jd}|r|||j|S|S)Ncompactr`ra)rr r)rHrrrkwargsdescr9r9r;get_resource_descriptionls z(MilestoneModule.get_resource_descriptioncCs|jt|jjvS)ak >>> from trac.test import EnvironmentStub >>> env = EnvironmentStub() >>> m1 = Milestone(env) >>> m1.name = 'M1' >>> m1.insert() >>> MilestoneModule(env).resource_exists(Resource('milestone', 'M1')) True >>> MilestoneModule(env).resource_exists(Resource('milestone', 'M2')) False )rr%rr)rHrr9r9r;resource_existsvszMilestoneModule.resource_existsccr)Nr r Milestonesrrr9r9r;get_search_filtersrz"MilestoneModule.get_search_filtersc #sd|vrdSt|}t|j}t|jjD]?\}}tfdd|DrW|d}d||vrW|r:|n|r>|nt t } t |j||j t |j|| dt|fVqt|j|||D]} | VqbdS)Nrc3s$|] }|p |VqdSrb)search)rQrrirhr9r;rsz5MilestoneModule.get_search_results..rr r)r rrr%rrrwrrrrget_resource_urlrget_resource_namerrget_search_results) rHrtermsr term_regexpsrrrrdtresultr9rr;rs2     z"MilestoneModule.get_search_results)r)NN)-r=r>r?rcrr,r r)IResourceManagerr r'r5rrr7rrrrrr rrrrrr/rrrpropertyrrrrrrrrrrr r r rr9r9r9r;rsZ * e  !  J ,   r)Nr)r)F)rN)\rrrnrrBtrac.attachmentrr trac.configrrr trac.coretrac.notification.apir trac.permr trac.resource trac.searchr r r trac.utilrrtrac.util.datefmtrrrrrrrrrrtrac.util.htmlrtrac.util.presentationrtrac.util.textrrrtrac.util.translationr r!trac.ticket.apir"trac.ticket.notificationr#trac.ticket.modelr$r%r&trac.timeline.apir' trac.web.apir(r)r*trac.web.chromer+r,r-r.r/r0r1r2r3r4 trac.wiki.apir5trac.wiki.formatterr6 Interfacer7objectr@ Componentrdrrrrrrrrrrr9r9r9r;sT  0     0   M    3O