== Extension Point : ''ITicketManipulator'' ||'''Interface'''||''ITicketManipulator''||'''Since'''||0.10|| ||'''Module'''||''trac.ticket.api''||'''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 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 `validate_ticket` method is also called during preview. Check if `'submit' in req.args` or `'preview' in req.args` to determine if the validated ticket is getting saved or just previewed. Since 1.3.2, `validate_comment` is called when appending or editing a ticket comment. The method is optional, and therefore only invoked when the method is present on the class implementing `ITicketManipulator`. Check for `'edit_comment' in req.args` or `'preview_comment' in req.args` to determine if the ticket comment edit is being submitted or previewed. 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: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.