== Extension Point : ''ITicketManipulator'' == ||'''Interface'''||''ITicketManipulator''||'''Since'''||0.10|| ||'''Module'''||''trac.ticket''||'''Source'''||[source:trunk/trac/ticket/api.py#/ITicketManipulator api.py]|| The ''ITicketManipulator'' can manipulate and validate tickets before saving. == Purpose == The Trac ticketing system is extendable by plugins. These plugins might add additional fields to a plugin or introduce new restrictions on existing fields. They might want to automatically manage some fields. Any such ticket manipulations or validations can be added by implementing the ITicketManipulator interface. == Usage == Implementing the interface follows the standard guidelines found in [wiki:TracDev/ComponentArchitecture] and of course [wiki:TracDev/PluginDevelopment]. The `validate_ticket` method is called when a user creates or modifies a ticket. Returning a list of messages rejects the (changed) ticket, returning `[]` accepts it. Note that the `validate_ticket` method can also be used to ''manipulate'' the ticket's fields before they are saved. Note that the previous values of a changed ticket can be accessed in `ticket._old`. (See #10495.) The `prepare_ticket` method should always be implemented as an empty dummy method for compatibility reasons: {{{#!python def prepare_ticket(self, req, ticket, fields, actions): pass }}} == Examples == One might want to maintain a wiki page with the MostFrequentDuplicates. One could [TracDev/Proposals/NewTicketDuplicateCheck take this idea further] and implement a minimal example ITicketManipulator implementation that warns about tickets already listed there: {{{#!python import re from trac.core import Component, implements from trac.ticket.api import ITicketManipulator class MostFrequentDuplicates(Component): implements(ITicketManipulator) def prepare_ticket(self, req, ticket, fields, actions): pass def validate_ticket(self, req, ticket): for id, summary in self.mostfrequentduplicates: if summary == ticket['summary'] and int(id) != ticket['id']: return [(None, "Duplicate of ticket #%s" % id)] return [] _page_name = 'MostFrequentDuplicates' _duplicate_re = re.compile(r" - #(\d*): '''(.*)'''") @cached def mostfrequentduplicates(self, db): """List of (id, summary) values.""" from trac.wiki.model import WikiPage duplicates = [] content = WikiPage(self.env, MostFrequentDuplicates._page_name, db=db).text for line in content.split('\n'): m = re.match(MostFrequentDuplicates._duplicate_re, line) if m: duplicates.append(m.groups()) return duplicates }}} * comment:1:ticket:10384 Contains an example that makes milestone a required field. == Available Implementations == * SpamFilter: Reject ticket changes that contain spam * th:InputfieldTrapPlugin: Rejects spam with a hidden input field. * th:MathCaptchaPlugin: Rejects spam with math question CAPTCHA. * th:TracTicketValidatorPlugin: Rejects invalid (configurable) fields. * th:RestrictKeywordsPlugin: Rejects tickets with invalid (configurable) ''keywords''. * th:BlackMagicTicketTweaksPlugin: Rejects ticket changes to disabled or hidden fields. * th:SensitiveTicketsPlugin: Rejects anonymous sensitive tickets. * th:ChildTicketsPlugin: Rejects invalid child/parent ticket relations. * th:MasterTicketsPlugin: Rejects closing a ticket that is blocked. * th:RemoteTicketPlugin: Rejects closing a ticket that is blocked. * th:TracDupPlugin: Rejects changes to the duplicate ticket management fields. * th:DateFieldPlugin: Rejects tickets with invalid dates. * th:TimingAndEstimationPlugin: Rejects invalid ''hours'' and ''estimatedhours'' fields. * th:TracHoursPlugin: Changes ticket comments that mention hours to link to a special hours management page. * th:BudgetingPlugin: Rejects tickets with invalid budgeting fields. * th:EpochFieldPlugin: Converts epoch fields. * th:ImageTracPlugin: Rejects tickets with invalid images. * th:TracStoryPointsPlugin: Rejects tickets with invalid ''storypoints'' and ''completed'' fields. * th:GeoTicketPlugin: Updates a ticket's geolocation fields. == Additional Information and References == * [http://www.edgewall.org/docs/trac-trunk/epydoc/trac.ticket.api.ITicketManipulator-class.html epydoc] * [http://www.edgewall.org/docs/trac-trunk/html/api/trac_ticket_api.html#trac.ticket.api.ITicketManipulator API Reference] * See [../trac.ticket.api.ITicketChangeListener trac.ticket.api.ITicketChangeListener] * See [../trac.ticket.api.ITicketActionController trac.ticket.api.ITicketActionController] * See [../trac.attachment.IAttachmentManipulator trac.attachment.IAttachmentManipulator], [../trac.wiki.api.IWikiPageManipulator trac.wiki.api.IWikiPageManipulator] * Related tickets: * [query:"status!=closed&component=ticket system" ticket system component] * #3029 Initial implementation. * #10125 Discusses unused `prepare_ticket` method * comment:15:ticket:10125 and following discuss ideas for fundamental refactoring * #6879 Discusses relation to ticket fields changed by the workflow and ITicketActionController * #6634 Discusses validation messages containing markup (e.g. links). * Archived mailing list discussions: * [trac-dev:289 Example] illustrating the original idea for `prepare_ticket_for_render` (This would not work now.)