o k`4@sddlZddlmZddlmZddlmZddlmZddl m Z m Z m Z ddl mZddlmZmZmZdd lmZmZmZmZmZmZdd lmZmZdd lmZmZm Z gd Z!d dZ"ddZ#ddZ$ddZ%ddZ&ddZ'ddZ(ddZ)Gddde*Z+Gdd d e*Z,Gd!d"d"e,Z-Gd#d$d$e*Z.Gd%d&d&e,Z/Gd'd(d(e,Z0Gd)d*d*e,Z1Gd+d,d,e*Z2Gd-d.d.ej2Z3Gd/d0d0e*Z4Gd1d2d2e*Z5Gd3d4d4e*Z6dS)5N)core) Attachment)cached) TracError)ResourceResourceExistsErrorResourceNotFound) TicketSystem)as_intembedded_numbersto_list) datetime_nowfrom_utimestamp parse_date to_utimestamputcutcmax)emptystripws)_N_gettext) TicketTypeStatus ResolutionPrioritySeverity Component MilestoneVersioncCs0g}t|dD] }||vr||qd|S)z0Fix up cc list separators and remove duplicates.z[;,\s]+z, )r appendjoin)cc_valuecclistccr&3/usr/lib/python3/dist-packages/trac/ticket/model.py_fixup_cc_list&s   r(cCsV|durdSztt|WStyYnwz t|tdWSty*YdSw)Ndatetime)rint ValueErrorrstripr Exceptionvaluer&r&r'_db_str_to_datetime/s  r0cCs0|sdSt|}|r|dkrdnd}||S|S)Nrz%018dz%+017d)r)dtis_custom_fieldtsfmtr&r&r'_datetime_to_db_str<sr5cCs|rt|SdSN)r)timer&r&r'_from_timestampHsr8cCs |r|SdSr6r&r.r&r&r'_to_nullL r9cCs |r|StSr6)rr.r&r&r'_null_to_emptyPr:r;cCs|r d|S|S)z5Strip spaces and remove duplicate spaces within names )r"splitnamer&r&r'simplify_whitespaceTsr@cCsp|j&}dd|D}ddgt|}|d||ddf|}Wdn1s,wYdd|DS) NcSsg|]}t|qSr&r*).0id_r&r&r' ]z,sort_tickets_by_priority..,%sa  SELECT id FROM ticket AS t LEFT OUTER JOIN enum p ON p.type='priority' AND p.name=t.priority WHERE t.id IN (%s) ORDER BY COALESCE(p.value,'')='', %s, t.id zp.valuer*cSg|]}|dqSrr&rBrowr&r&r'rDfrE)db_queryr"lencast)envidsdbticketsholdersrowsr&r&r'sort_tickets_by_priority[s rUc@seZdZdZdZeddZeddZd6dd Z d d Z ed d Z ddZ ddZ ddZddZddZddZddZddZddZd7d d!Zd"d#Zd8d$d%Zd&d'Zd7d(d)Zd*d+Zd6d,d-Zd9d.d/Zd7d0d1Zd6d2d3Zd4d5ZdS):rticket) resolutionstatusr7 changetimec Cs:zdt|ko dkWSWSttfyYdSw)NrlF)r*r+ TypeErrornumr&r&r' id_is_validqs  zTicket.id_is_validcCst|j|j|jSr6)rrealmidversionselfr&r&r'resourcexszTicket.resourceNcs|_tj_fddjD_ggg___jD]&}|dr4j |dnj |d|ddkrJj |dq$i_ i_ |dur[ |n d_|_dS)Ncs"h|] }|djvr|dqSr>)protected_fieldsrBfrar&r' sz"Ticket.__init__..customr?typer7)rOr get_ticket_fieldsfieldseditable_fields std_fields custom_fields time_fieldsgetr!values_old _fetch_ticket_init_defaultsr_r`)rbrOtkt_idr`rfr&rar'__init__|s&     zTicket.__init__cCd|jj|jfSNz<%s %r> __class____name__r_rar&r&r'__repr__zTicket.__repr__cC |jduSr6r_rar&r&r' zTicket.cCsh|jD].}d}|d|jvrn|ds!|jjdd|d}n||}|r1|j|d|qdS)Nr?rhrVdefault_)rkrdrprOconfig_custom_field_defaultrq setdefault)rbfielddefaultr&r&r'rts     zTicket._init_defaultsc Cs|d}|d}|r1|r1||vr1z|t|}Wnttfy0|jjd||dYnw|rh|ddkrhz t||dd}W|Styg}z|jjd ||d|d}WYd}~|Sd}~ww|S) Nr/optionsz0Invalid default value '%s' for custom field '%s'r?rir7format)hintz4Invalid default value '%s' for custom field '%s': %s) rpr*r+ IndexErrorrOlogwarningrr)rbrrrer&r&r'rs2     zTicket._custom_field_defaultcCsJd}||rt|}|jdd|j|fD]}|s)ttd|dtd||_t |jD]$\}}||}||j vrFt ||j |<q1|durPt |j |<q1||j |<q1|jd|fD]%\}}||jvr||j vrtt||j |<q^|dur~t |j |<q^||j |<q^|jD]}|d}|dr||j vr||}|r|||<qdS) NzL SELECT %s FROM ticket WHERE id=%%s rFzTicket %(id)s does not exist.rzInvalid ticket numberzW SELECT name, value FROM ticket_custom WHERE ticket=%s r?rh)r]r*rOrLr"rmrrr_ enumeraterorrqrrnr0rkrpr)rbrurKirr/r?rr&r&r'rssR           zTicket._fetch_ticketcCs |j|Sr6rqrprbr?r&r&r' __getitem__r:zTicket.__getitem__cCs|r"||jvr"t|trttd|j|iddkr"|}||j vr0|j ||kr0dS||j vr?|j ||j |<n |j ||krJ|j |=||j |<dS)zKLog ticket modifications so the table ticket_change can be updated z%Multi-values fields not supported yetritextareaN) ro isinstancelistrrrkby_namerpr,rqrrrbr?r/r&r&r' __setitem__s   zTicket.__setitem__cCs ||jvSr6)rq)rbitemr&r&r' __contains__ zTicket.__contains__cCs:z|j|}|tur |WS||WStyYdSw)zLReturn the value of a field or the default value if it is undefined N)rqr get_defaultKeyErrorrr&r&r'get_value_or_defaults   zTicket.get_value_or_defaultcCs|j|iddS)z$Return the default value of a field.r/)rkrrprr&r&r'rszTicket.get_defaultcspdd|jDfdd|DD]}||||<qfdd|DD]}|dd|vr5d||dd<q#dS)zr&rer&r&r'rD rEz#Ticket.populate..csg|]}|vr|qSr&r&rBr? field_namesr&r'rD cs*g|]}|ddvr|dr|qS) N checkbox_ startswithrrr&r'rDs rN0)rk)rbrqr?r&rr'populate szTicket.populatec sf|jrJdd|jvrt|jd|d<|durtt}||jd<|jd<||jg}g}|jD]}|d}||jvrO|drJ||q4||q4|j j >}| }| dd |d d gt|ffd d |D||d |r|dfdd |DWdn1swYt|_i|_t|j jD]}||q|jS)z Add ticket to database. z Cannot insert an existing ticketr%Nr7rYr?rhz#INSERT INTO ticket (%s) VALUES (%s)rFrGcsg|]}|qSr&rpr) db_valuesr&r'rD4sz!Ticket.insert..rVzoINSERT INTO ticket_custom (ticket, name, value) VALUES (%s, %s, %s) csg|] }||fqSr&r)rBcrrur&r'rD<s)existsrqr(r r _to_db_typesrkrpr!rOdb_transactioncursorexecuter"rM get_last_id executemanyr*r_rrr change_listenersticket_created) rbwhenrmrnrffnamerQrlistenerr&rr'insertsP            z Ticket.insertc CsXt|}|jd|j|fD]\}z t|dddWSty)YdSwdS)z$Return a comment number by its date.z SELECT oldvalue FROM ticket_change WHERE ticket=%s AND time=%s AND field='comment' .N)rrOrLr_r*rsplitr+)rbcdater3cnumr&r&r'get_comment_numberGs  zTicket.get_comment_numberc sdjsJddjvrtjdd<tfddjD}|r)t|s-|r-dS|dur5tt}|jd< j} j}j j }|d|dj fd } |d j j fD] \} } z| t | d d d 7} Wn ty| d 7} Yq`wt| d } |rd|| f} jD]`} || }|| }| jvr|dj | fD] }|d|j | fn!|dj | |f|durوj| }|}j| |krqn |d| |j f|dj |d|| ||fq|dj |d|| |fWdn 1s wYj}i_tj jD] }||||qt | d d d S)z Store ticket changes in the database. The ticket must already exist in the database. Returns False if there were no changes to save, True otherwise. zCannot update a new ticketr%c3s$|] \}}j||kVqdSr6rrBkvrar&r' ^sz&Ticket.save_changes..FNrY+UPDATE ticket SET changetime=%s WHERE id=%sram SELECT DISTINCT tc1.time, COALESCE(tc2.oldvalue,'') FROM ticket_change AS tc1 LEFT OUTER JOIN ticket_change AS tc2 ON tc2.ticket=%s AND tc2.time=tc1.time AND tc2.field='comment' WHERE tc1.ticket=%s ORDER BY tc1.time DESC rrrz%s.%szSELECT * FROM ticket_custom WHERE ticket=%s and name=%s zUPDATE ticket_custom SET value=%s WHERE ticket=%s AND name=%s z{INSERT INTO ticket_custom (ticket,name,value) VALUES(%s,%s,%s) %UPDATE ticket SET %s=%%s WHERE id=%%szINSERT INTO ticket_change (ticket,time,author,field,oldvalue,newvalue) VALUES (%s, %s, %s, %s, %s, %s) zINSERT INTO ticket_change (ticket,time,author,field,oldvalue,newvalue) VALUES (%s,%s,%s,'comment',%s,%s) )rrqr(allrritemsrr rrrOrr_r*rr+strrprnrkrrr rticket_changed)rbauthorcommentrreplytoprops_unchangedr old_db_valuesrQr\r3oldrr?db_val old_db_valrKrr old_valuesrr&rar' save_changesSs                      ?zTicket.save_changescCsN|}|D]\}}||jvr||jv}t||||<qt|||<q|Sr6)copyrrornr5r9)rbrqrr/r2r&r&r'rs  zTicket._to_db_typesc Cst|j}t|}|rd}|j|||||f}nd}|j||f}g}|j||D]&\}}} } } } | |jvr=t| } t| } |t||| | pGd| pJd| fq(|S)aHReturn the changelog as a list of tuples of the form (time, author, field, oldvalue, newvalue, permanent). While the other tuple elements are quite self-explanatory, the `permanent` flag is used to distinguish collateral changes that are not yet immutable (like attachments, currently). a` SELECT time, author, field, oldvalue, newvalue, 1 AS permanent FROM ticket_change WHERE ticket=%s AND time=%s UNION SELECT time, author, 'attachment', null, filename, 0 AS permanent FROM attachment WHERE type='ticket' AND id=%s AND time=%s UNION SELECT time, author, 'comment', null, description, 0 AS permanent FROM attachment WHERE type='ticket' AND id=%s AND time=%s ORDER BY time,permanent,author,field a< SELECT time, author, field, oldvalue, newvalue, 1 AS permanent FROM ticket_change WHERE ticket=%s UNION SELECT time, author, 'attachment', null, filename, 0 AS permanent FROM attachment WHERE type='ticket' AND id=%s UNION SELECT time, author, 'comment', null, description, 0 AS permanent FROM attachment WHERE type='ticket' AND id=%s ORDER BY time,permanent,author,field r) rr_rrOrLror0r!r) rbrsidwhen_tssqlargsrtrroldvaluenewvalue permanentr&r&r' get_changelogs"     zTicket.get_changelogcCs|jj'}t|j|j|j|d|jf|d|jf|d|jfWdn1s.wYt|jjD]}||q9dS)zDelete the ticket. zDELETE FROM ticket WHERE id=%sz)DELETE FROM ticket_change WHERE ticket=%sz)DELETE FROM ticket_custom WHERE ticket=%sN) rOrr delete_allr^r_r rticket_deletedrbrQrr&r&r'deletes  z Ticket.deletec Cs|dur||}|s dSt|d}t|}i}||d}|jd|j|fD]"\}}} } || | d||<|dkr?||d<q(|dsJ|d|q(|rO|SdS) z6Return a ticket change by its number or date. Nr)daterkz SELECT field, author, oldvalue, newvalue FROM ticket_change WHERE ticket=%s AND time=%s )rrnewrrr) _find_changerrrOrLr_rr) rbrrrKr3rkchangerrrrr&r&r' get_changes(      zTicket.get_changec Csd|dur||}|s dSt|d}t|}|durtt}t|}|jj_}dd|d|j|fD}|D]7\} } } |d|j|| fD]\} |d| |j| | | fn| |jvre|d| | |jfq7|d | |j| fq7|d |j|f|d ||jfWdn1swY| |jd d |D} t |jj D]}t |dr| ||| qdS)z8Delete a ticket change identified by its number or date.NrcSs.g|]\}}}|dkr|ds|||fqS)rrr)rBrrrr&r&r'rDs z(Ticket.delete_change..z SELECT field, oldvalue, newvalue FROM ticket_change WHERE ticket=%s AND time=%s zSELECT time FROM ticket_change WHERE ticket=%s AND time>%s AND field=%s LIMIT 1 zUPDATE ticket_change SET oldvalue=%s WHERE ticket=%s AND time=%s AND field=%s AND oldvalue=%s rrz5DELETE FROM ticket_change WHERE ticket=%s AND time=%srcSsi|] \}}}|||fqSr&r&)rBrrrr&r&r' Esz(Ticket.delete_change..ticket_change_deleted)rrrr rrOrr_rmrsr rhasattrr)rbrrrrKr3rrQrkrrrnext_tschangesrr&r&r' delete_changesb        ' zTicket.delete_changec Cst|}|dur tt}t|}d}|jj}|d|j|fD]\}||p'dkr2 WddS|d||j||df} | rOtdd| Dd nd } |d |j||d | |p]dt |f|dur|d ||j||dfD] \} |d|j|| |fqwn |d||j|f|d||jfWdn1swY||j d<|pd}t |jj D]} t | dr| |||||qdS)zaModify a ticket comment specified by its date, while keeping a history of edits. NFz SELECT newvalue FROM ticket_change WHERE ticket=%s AND time=%s AND field='comment' rzSELECT field FROM ticket_change WHERE ticket=%%s AND time=%%s AND field %s _commentcss"|] \}t|ddVqdSNrA)rBrr&r&r'res z(Ticket.modify_comment..rrzINSERT INTO ticket_change (ticket,time,author,field,oldvalue,newvalue) VALUES (%s,%s,%s,%s,%s,%s) z _comment%d SELECT author FROM ticket_change WHERE ticket=%%s AND time=%%s AND NOT field %s LIMIT 1 rzINSERT INTO ticket_change (ticket,time,author,field,oldvalue,newvalue) VALUES (%s,%s,%s,'comment','',%s) zUPDATE ticket_change SET newvalue=%s WHERE ticket=%s AND time=%s AND field='comment' rrYticket_comment_modified)rr rrOrr_ prefix_matchprefix_match_valuemaxrrqr rrr) rbrrrrr3r old_commentrQrkrev old_authorrr&r&r'modify_commentKsj         ,  zTicket.modify_commentcCs||dur||}|s dS|\}}}n t|dd}}}|jj}|dur5d}|d|j|fD]\}}|durX|d||j||dfD]\}}n WddS|d||j||df}tdd |D}g} |D]\} } } } | | t t ||| f| | }}qu| | r| d d d nd } | | t t |||f| WdS1swYdS) zYRetrieve the edit history of a comment identified by its number or date. Nrz SELECT author, newvalue FROM ticket_change WHERE ticket=%s AND time=%s AND field='comment' z SELECT author, newvalue FROM ticket_change WHERE ticket=%%s AND time=%%s AND NOT field %s LIMIT 1 rzSELECT field, author, oldvalue, newvalue FROM ticket_change WHERE ticket=%%s AND time=%%s AND field %s rcss0|]\}}}}t|dd|||fVqdSrrA)rBrrrrr&r&r'rs  z-Ticket.get_comment_history..rrr) rrrOrLr_rrsortedr!rr*sort)rbrrrKts0author0 last_commentrQrThistoryrrrr3r&r&r'get_comment_historysV      $zTicket.get_comment_historyc Cs&t|}|jj}|d||j|d|d|fD] }|WdSd}|d|j|jfD]&\}}}} z t|ddd}Wn tyT|d7}Ynw||kr[n q5 WddS|dur}|d | |j|| d fD]\}||| fWdS1swYdS) zFind a comment by its number.z SELECT time, author, newvalue FROM ticket_change WHERE ticket=%%s AND field='comment' AND (oldvalue=%%s OR oldvalue %s) %rNra SELECT DISTINCT tc1.time, COALESCE(tc2.oldvalue,''), tc2.author, COALESCE(tc2.newvalue,'') FROM ticket_change AS tc1 LEFT OUTER JOIN ticket_change AS tc2 ON tc2.ticket=%s AND tc2.time=tc1.time AND tc2.field='comment' WHERE tc1.ticket=%s ORDER BY tc1.time rrrr) rrOrLliker_ like_escaper*rr+rr) rbrscnumrQrKr\r3rrrr&r&r'rs@       $zTicket._find_change)NNr6)NNNN)NNN) r{ __module__ __qualname__r^rd staticmethodr]propertyrcrvr|rrtrrsrrrrrrrrrrrrrrrrrr&r&r&r'ris<    +  2 ^ 2  ; > 1rc@sbeZdZdZdZdZeddZdddZddZ dd Z d d Z d d Z e ddZddZdS) AbstractEnumNcCr~r6) _old_valuerar&r&r'rrzAbstractEnum.cCs|js|j|_||_d|_|_d|_|_d|_|rK|jd|j|fD]\}}||_|_t ||_||_|_dSt t dt |j d|ddS)a!Create a new `enum` instance. If `name` is specified and the enum with `name` exists, the enum will be retrieved from the database. :raises ResourceNotFound: if `name` is not `None` and an enum with `name` does not exist. Nz{ SELECT value, description FROM enum WHERE type=%s AND name=%s z!%(type)s %(name)s does not exist.rrir?) ticket_colrirOr? _old_namer/r  descriptionrLr;rrrlabel)rbrOr?r/rr&r&r'rvs&      zAbstractEnum.__init__cCsd|jj|j|jfS)Nz <%s %r %r>)rzr{r?r/rar&r&r'r| szAbstractEnum.__repr__c Cs|jsttdt|jdd|jjd|j|j |jj D}|d|j|j f| |jD]%}zt |jt |j krJtt |jd|_|Wq/tyTYq/wt|jWdn1sfwYd|_|_ d|_ |_d|_dS)zMDelete the enum. :raises TracError: if enum does not exist. z$Cannot delete non-existent %(type)s.rrizDeleting %s '%s'z+DELETE FROM enum WHERE type=%s AND value=%srN)rrrrrrOrinforir?rr selectr*r/rupdater+r reset_ticket_fieldsrr)rbrQenumr&r&r'r s.      zAbstractEnum.deletec Cs$|jrttdt|jd|jd||jj d|j |j|jj [}z2|j sH|d| dd|j f}|rEtt|dddnd|_ |d |j |j|j t|jfWn|jjjyqttd t|jd|jdwt|jWd n1swY|j|_|j |_d S) zAdd a new enum. :raises TracError: if enum name is empty. :raises ResourceExistsError: if enum with name already exists. z)%(type)s value "%(name)s" already exists.rrzCreating new %s '%s'zuSELECT COALESCE(MAX(%s), 0) FROM enum WHERE type=%%s r/r*rz INSERT INTO enum (type, name, value, description) VALUES (%s, %s, %s, %s) (%(type)s value "%(name)s" already existsN)rrrrrr?_check_and_coerce_fieldsrOrdebugrirr/rNr*floatr9rdb_excIntegrityErrorr rrr )rbrQrKr&r&r'r'sB  "  zAbstractEnum.insertc Cs|js ttd||jjd|j|j|jj b}z|d|j|j t |j |j|j fWn|jjjyIttdt|jd|jdw|j |_|j|j krv|d|j|jf|j|j f|j|_ t|jWddSWddS1swYdS) zUpdate the enum. :raises TracError: if enum does not exist or enum name is empty. :raises ResourceExistsError: if renamed enum already exists. z Cannot update non-existent enum.zUpdating %s '%s'zvUPDATE enum SET name=%s,value=%s,description=%s WHERE type=%s AND name=%s rrrz%UPDATE ticket SET %s=%%s WHERE %s=%%sN)rrrrrOrrrir?rr/r9rrrrrrrr rr rrbrQr&r&r'rHs>       "zAbstractEnum.updateccs|j4}|d|dd|jfD]\}}}||}||_|_||_|_t||_|VqWddS1s;wYdS)Nz~ SELECT name, value, description FROM enum WHERE type=%s ORDER BY r/r*) rLrNrir?rr/r r;r)clsrOrQr?r/robjr&r&r'rgs    "zAbstractEnum.selectcCs0t|j|_|jsttdt|jdddS)NzInvalid %(type)s name.rr)r@r?rrrrrar&r&r'rus  z%AbstractEnum._check_and_coerce_fieldsr6)r{rr rirrr rrvr|rrr classmethodrrr&r&r&r'r s  !  r c@s$eZdZdZdZededfZdS)r ticket_typeriz Ticket Typez Ticket TypesN)r{rr rirrrr&r&r&r'r|src@s(eZdZddZeddZddZdS)rcCs ||_dSr6)rO)rbrOr&r&r'rvrzStatus.__init__ccs,t|D] }||}||_|VqdSr6)r get_all_statusr?)r rOstaterXr&r&r'rs z Status.selectcCrwrxrzr{r?rar&r&r'r|r}zStatus.__repr__N)r{rr rvr"rr|r&r&r&r'rs   rc@ eZdZdZededfZdS)rrW ResolutionsNr{rr rirrr&r&r&r'rrc@r')rpriority PrioritiesNr)r&r&r&r'rr*rc@r')rseverity SeveritiesNr)r&r&r&r'rr*rc@feZdZdZeddZeddZdddZd d Zd d Z d dZ ddZ e ddZ ddZdS)r componentcCr~r6rrar&r&r'rrzComponent.cCt|j|jSr6rr^r?rar&r&r'rczComponent.resourceNcCst||_d|_|_|_|_|r8|jd|fD]\}}||_|_t||_t||_dSttd|ddS)a2Create a new `Component` instance. If `name` is specified and the component with `name` exists, the component will be retrieved from the database. :raises ResourceNotFound: if `name` is not `None` and component with `name` does not exist. Nz` SELECT owner, description FROM component WHERE name=%s z"Component %(name)s does not exist.r>) rOr?rownerrrLr;rr)rbrOr?r5rr&r&r'rvs   zComponent.__init__cCrwrxr&rar&r&r'r|r}zComponent.__repr__cCx|js ttd|jjd|j|jj}|d|jft|j Wdn1s/wYd|_|_ dS)zWDelete the component. :raises TracError: if component does not exist. z%Cannot delete non-existent component.zDeleting component '%s'z#DELETE FROM component WHERE name=%sN rrrrOrrr?rr rrrr&r&r'r  zComponent.deletec Cs|jr ttd|jd||jjd|j|jj4}z|d|jt |j t |j fWn|jj j yBttd|jdwt|jWdn1sTwY|j|_dS)zInsert a new component. :raises TracError: if component name is empty. :raises ResourceExistsError: if component with name already exists. $Component "%(name)s" already exists.r>zCreating new component '%s'z INSERT INTO component (name,owner,description) VALUES (%s,%s,%s) N)rrrr?rrOrrrr9r5rrrr rrrr&r&r'rs*     zComponent.insertc Cs|js ttd||jjd|j|jjR}z|d|jt |j t |j |j fWn|jj jyAttd|jdw|j|j krd|d|j|j f|j|_ t|jWddSWddS1sowYdS)zUpdate the component. :raises TracError: if component does not exist or component name is empty. :raises ResourceExistsError: if renamed component already exists. z%Cannot update non-existent component.zUpdating component '%s'zpUPDATE component SET name=%s,owner=%s, description=%s WHERE name=%s r9r>z1UPDATE ticket SET component=%s WHERE component=%sN)rrrrrOrrr?rr9r5rrrrrr rrr&r&r'rs2        "zComponent.updateccsJ|dD]\}}}||}||_|_t||_t||_|VqdS)Nz^ SELECT name, owner, description FROM component ORDER BY name )rLr?rr;r5r)r rOr?r5rr0r&r&r'rs   zComponent.selectcCs.t|j|_t|j|_|jsttddS)NzInvalid component name.)r@r?r5rrrar&r&r'rs   z"Component._check_and_coerce_fieldsr6r{rr r^r rrcrvr|rrrr"rrr&r&r&r'rs     rc@s8eZdZdZeddZd ddZddZd d d ZdS) MilestoneCachez?Cache for milestone data and factory for 'milestone' resources.cCs>i}|jdD]\}}}}|t|t||pdf||<q|S)zDictionary containing milestone data, indexed by name. Milestone data consist of a tuple containing the name, the datetime objects for due and completed dates and the description. zY SELECT name, due, completed, description FROM milestone r)rOrLr8)rb milestonesr?due completedrr&r&r'r<s zMilestoneCache.milestonesNcCs |j|}|r|||SdS)zRetrieve an existing milestone having the given `name`. If `milestone` is specified, fill that instance instead of creating a fresh one. :return: `None` if no such milestone exists N)r<rpfactory)rbr? milestonedatar&r&r'fetchone.s  zMilestoneCache.fetchoneccs"|jD]}||VqdS)zIterator on all milestones.N)r<rqr?)rbrAr&r&r'fetchall:szMilestoneCache.fetchallcCsB|\}}}}|p t|j}||_||_||_||_|jdd|S)aBuild a `Milestone` object from milestone data. That instance remains *private*, i.e. can't be retrieved by name by other processes or even by other threads in the same process, until its `~Milestone.insert` method gets called with success. F) invalidate)rrOr?r=r>rcheckin)rbrqr@r?r=r>rr&r&r'r??s  zMilestoneCache.factoryr6) r{rr __doc__rr<rBrCr?r&r&r&r'r;s   r;c@seZdZdZeddZdddZddZed d Zed d Z ed d Z edd Z dddZ ddZ ddZdddZ  dddZedddZdS) rr@cCr2r6r3rar&r&r'rcUr4zMilestone.resourceNcCsP||_d|_|_|r|j||sttd|dtddS|jd|dS)zCreate an undefined milestone or fetch one from the database, if `name` is given. In the latter case however, raise `~trac.resource.ResourceNotFound` if a milestone of that name doesn't exist yet. N"Milestone %(name)s does not exist.r>Invalid milestone name.)NNNr)rOr?rrcacherBrrr?)rbrOr?r&r&r'rvYs zMilestone.__init__cCrwrxr&rar&r&r'r|jr}zMilestone.__repr__cCs t|jSr6)r;rOrar&r&r'rIm zMilestone.cachecCs|jdduS)Nr?)rrrar&r&r'rqszMilestone.cCr~r6r>rar&r&r'rrrcCs|jo |jttkSr6)r=r rrar&r&r'rssTcCs*|j|j|j|jd|_|r|j`dSdS)N)r?r=r>r)r?r=r>rrrrIr<)rbrDr&r&r'rEvs zMilestone.checkincCs|jjd|j|jj#}|d|jft|j|j|j|j` t |j Wdn1s3wYd|j d<t |jj D]}||qCdS)zDelete the milestone.zDeleting milestone '%s'z#DELETE FROM milestone WHERE name=%sNr?)rOrrr?rrrr^rIr<r rrrmilestone_change_listenersmilestone_deletedrr&r&r'r}s   zMilestone.deletec Cst|j|_|jsttd|jjd|j|jj:}z|d|jt|j t|j |j fWn|jj j yCttd|jdw|t|jWdn1sYwYt|jjD]}||qddS)zInsert a new milestone. :raises TracError: if milestone name is empty. :raises ResourceExistsError: if milestone with name already exists. rHzCreating new milestone '%s'z INSERT INTO milestone (name, due, completed, description) VALUES (%s,%s,%s,%s) $Milestone "%(name)s" already exists.r>N)r@r?rrrOrrrrr=r>rrrrrEr rrLmilestone_createdrr&r&r'rs,       zMilestone.insertc sBtj_jsttdj}jj^}j|dkr7j|dt jj |dj jjj d|dz|djtjtjj|dfWnjjjyjttdjdwWdn1sywYtjfd d |D}tjjD]}||qdS) zUpdate the milestone. :raises TracError: if milestone does not exist or milestone name is empty. :raises ResourceExistsError: if renamed milestone already exists. rHr?zMilestone renamedzUpdating milestone '%s'zUPDATE milestone SET name=%s, due=%s, completed=%s, description=%s WHERE name=%s rNr>Ncs$i|]\}}t||kr||qSr&)getattrrrar&r'rs z$Milestone.update..)r@r?rrrrrrOr move_ticketsr reparent_allr^rrrr=r>rrrrrEr rrrLmilestone_changed)rbrrrQrrr&rar'rs<        zMilestone.updateFc Cs|r||jkr|j|sttd|dtdtt}d}|r$|d7}|jjC}dd|||j dfD}|r[|jj d |j d||D]} t |j| } || d <| |||qGWd |SWd |S1snwY|S) aMove tickets associated with this milestone to another milestone. :param new_milestone: milestone to which the tickets are moved :param author: author of the change :param comment: comment that is inserted into moved tickets. The string should not be translated. :param exclude_closed: whether tickets with status closed should be excluded :return: a list of ids of tickets that were moved rGr>rHz(SELECT id FROM ticket WHERE milestone=%sz AND status != 'closed'cSsg|]}t|dqSrIrArJr&r&r'rDrz*Milestone.move_tickets..r?z?Moving tickets associated with milestone '%s' to milestone '%s'r@N)r?rIrBrrr rrOrrrrrrr) rb new_milestonerrexclude_closednowrrQtkt_idsrurVr&r&r'rQs>         zMilestone.move_ticketscCs2t|}|sdd|D}dd}t||dS)NcSsg|] }|jdur|qSr6rK)rBmr&r&r'rDsz$Milestone.select..cSs|jpt|jptt|jfSr6)r>rr=r r?)rXr&r&r'milestone_ordersz)Milestone.select..milestone_order)key)r;rCr)r rOinclude_completedr<rYr&r&r'rs  zMilestone.selectr6)T)NF)r{rr r^r rcrvr|rIr is_completedis_laterErrrrQr"rr&r&r&r'rQs(        ( %rc@sTeZdZdZeddZdddZddZd d Zd d Z d dZ e dddZ dS)ReportreportcCr~r6rrar&r&r'rrJz Report.existsNcCs||_d|_|_|_|_|durIt|d}|dur>|jd|fD]\}}}||_t||_t||_t||_dStt d|dt ddS)Nz SELECT title, description, query FROM report WHERE id=%s z Report {%(num)s} does not exist.r[zInvalid Report Number) rOr_titlequeryrr rLr;rr)rbrOr_ id_as_intr`rrar&r&r'rvs&    zReport.__init__cCrwrxryrar&r&r'r|r}zReport.__repr__cCs*|jsJd|jd|jfd|_dS)zDelete the report.z!Cannot delete non-existent reportzDELETE FROM report WHERE id=%sN)rrOrr_rar&r&r'rs z Report.deletecCs|jrJd|jsttd|jjd|j|jj&}| }| dt |j t |jt |j f||d|_WddS1sFwYdS)zMInsert a new report. :raises TracError: if `query` is empty zCannot insert existing reportQuery cannot be empty.zCreating new report '%s'z` INSERT INTO report (title,query,description) VALUES (%s,%s,%s) r_N)rrarrrOrrr_rrrr9r`rr)rbrQrr&r&r'r!s  "z Report.insertcCs@|js ttd|jdt|jt|jt|j|jfdS)zIUpdate a report. :raises TracError: if `query` is empty rczf UPDATE report SET title=%s, query=%s, description=%s WHERE id=%s N) rarrrOrr9r`rr_rar&r&r'r3s  z Report.updater_Tc csl|d|dkr dnd|rdndfD]\}}}}||}||_t||_t||_t||_|VqdS)Nzq SELECT id, title, description, query FROM report ORDER BY %s %s r`r_rDESC)rLr_r;r`rra) r rOrascr_r`rrar_r&r&r'r@s    z Report.selectr6)r_T) r{rr r^r rrvr|rrrr"rr&r&r&r'r^s   r^c@r/)r r`cCr~r6r1rar&r&r'rSrzVersion.cCr2r6r3rar&r&r'rcUr4zVersion.resourceNcCst||_d|_|_|_|_|r8|jd|fD]\}}||_|_t||_t||_dStt d|ddS)Nz] SELECT time, description FROM version WHERE name=%s z Version %(name)s does not exist.r>) rOr?rr7rrLr8r;rr)rbrOr?r7rr&r&r'rvYs   zVersion.__init__cCrwrxr&rar&r&r'r|hr}zVersion.__repr__cCr6)zSDelete the version. :raises TracError: if version does not exist. z#Cannot delete non-existent version.zDeleting version '%s'z!DELETE FROM version WHERE name=%sNr7rr&r&r'rkr8zVersion.deletec Cs||jjd|j|jj4}z|d|jt|jt|j fWn|jj j y6t t d|jdwt|jWdn1sHwY|j|_dS)zInsert a new version. :raises TracError: if version name is empty. :raises ResourceExistsError: if version with name already exists. zCreating new version '%s'z{ INSERT INTO version (name,time,description) VALUES (%s,%s,%s) "Version "%(name)s" already exists.r>N)rrOrrr?rrr7r9rrrrrr rrrr&r&r'rys"    zVersion.insertc Cs|js ttd||jjd|j|jjH}z|d|jt |j |j |j fWn|jj jy?ttd|jdw|j|j krS|d|j|j f|j|_ t|jWddS1sewYdS)zUpdate the version. :raises TracError: if version does not exist or version name is empty. :raises ResourceExistsError: if renamed value already exists. z#Cannot update non-existent version.zUpdating version '%s'znUPDATE version SET name=%s, time=%s, description=%s WHERE name=%s rfr>z-UPDATE ticket SET version=%s WHERE version=%sN)rrrrrOrrr?rrr7rrrrrr rrr&r&r'rs.      "zVersion.updatecCsbg}|dD]\}}}||}||_|_t||_t||_||qdd}t||ddS)Nz< SELECT name, time, description FROM versioncSs|jptt|jfSr6)r7rr r?)rr&r&r' version_ordersz%Version.select..version_orderT)rZreverse) rLr?rr8r7r;rr!r)r rOversionsr?r7rr`rgr&r&r'rs    zVersion.selectcCs"t|j|_|jsttddS)NzInvalid version name.)r@r?rrrar&r&r'rs  z Version._check_and_coerce_fieldsr6r:r&r&r&r'r Os     r )7retracrtrac.attachmentr trac.cacher trac.corer trac.resourcerrrtrac.ticket.apir trac.utilr r r trac.util.datefmtr rrrrrtrac.util.textrrtrac.util.translationrrr__all__r(r0r5r8r9r;r@rUobjectrr rrrrrrr;rr^r r&r&r&r'sN         x9.Q