As previously discussed on the mailing list, a new generic interface IResourceChangeListener should be introduced. The commit introduces the interface and provides the first usage of the interface for Component resource. I would kindly ask community to review the change and provide feedback.
For the next step, I plan to add usage of IResourceChangeListener by other resources such as ticket, milestone, version and other AbstractEnum based resources. Cheers, Andrej On 25 February 2013 18:10, <[email protected]> wrote: > Author: andrej > Date: Mon Feb 25 17:10:42 2013 > New Revision: 1449780 > > URL: http://svn.apache.org/r1449780 > Log: > patch Trac to enable genereic IResourceChangeListener interfce required for > support of consistancy between DB and index (#394, #411) > > Modified: > incubator/bloodhound/trunk/trac/trac/ticket/api.py > incubator/bloodhound/trunk/trac/trac/ticket/model.py > incubator/bloodhound/trunk/trac/trac/ticket/tests/model.py > > Modified: incubator/bloodhound/trunk/trac/trac/ticket/api.py > URL: > http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/ticket/api.py?rev=1449780&r1=1449779&r2=1449780&view=diff > ============================================================================== > --- incubator/bloodhound/trunk/trac/trac/ticket/api.py (original) > +++ incubator/bloodhound/trunk/trac/trac/ticket/api.py Mon Feb 25 17:10:42 > 2013 > @@ -156,6 +156,31 @@ class IMilestoneChangeListener(Interface > def milestone_deleted(milestone): > """Called when a milestone is deleted.""" > > +class IResourceChangeListener(Interface): > + """Extension point interface for components that require notification > + when resources are created, modified, or deleted. > + > + 'resource' instance of the a resource e.g. ticket, milestone etc. > + 'context' action context, may contain author, comment etc. Context > + content depends on a resource type. > + """ > + > + def resource_created(resource, context): > + """ > + Called when a resource is created. > + """ > + > + def resource_changed(resource, old_values, context): > + """Called when a resource is modified. > + > + `old_values` is a dictionary containing the previous values of the > + resource properties that changed. Properties are specific for > resource > + type. > + """ > + > + def resource_deleted(resource, context): > + """Called when a resource is deleted.""" > + > class ITicketFieldProvider(Interface): > """Extension point interface for components that provide fields for the > ticket system.""" > @@ -193,6 +218,7 @@ class TicketSystem(Component): > ticket_field_providers = ExtensionPoint(ITicketFieldProvider) > change_listeners = ExtensionPoint(ITicketChangeListener) > milestone_change_listeners = ExtensionPoint(IMilestoneChangeListener) > + resource_change_listeners = ExtensionPoint(IResourceChangeListener) > > ticket_custom_section = ConfigSection('ticket-custom', > """In this section, you can define additional fields for tickets. See > > Modified: incubator/bloodhound/trunk/trac/trac/ticket/model.py > URL: > http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/ticket/model.py?rev=1449780&r1=1449779&r2=1449780&view=diff > ============================================================================== > --- incubator/bloodhound/trunk/trac/trac/ticket/model.py (original) > +++ incubator/bloodhound/trunk/trac/trac/ticket/model.py Mon Feb 25 17:10:42 > 2013 > @@ -844,9 +844,12 @@ class Component(object): > with self.env.db_transaction as db: > self.env.log.info("Deleting component %s", self.name) > db("DELETE FROM component WHERE name=%s", (self.name,)) > - self.name = self._old_name = None > TicketSystem(self.env).reset_ticket_fields() > > + for listener in TicketSystem(self.env).resource_change_listeners: > + listener.resource_deleted(self) > + self.name = self._old_name = None > + > def insert(self, db=None): > """Insert a new component. > > @@ -866,6 +869,9 @@ class Component(object): > self._old_name = self.name > TicketSystem(self.env).reset_ticket_fields() > > + for listener in TicketSystem(self.env).resource_change_listeners: > + listener.resource_created(self) > + > def update(self, db=None): > """Update the component. > > @@ -877,6 +883,7 @@ class Component(object): > if not self.name: > raise TracError(_("Invalid component name.")) > > + old_name = self._old_name > with self.env.db_transaction as db: > self.env.log.info("Updating component '%s'", self.name) > db("""UPDATE component SET name=%s,owner=%s, description=%s > @@ -890,6 +897,10 @@ class Component(object): > self._old_name = self.name > TicketSystem(self.env).reset_ticket_fields() > > + old_values = dict(name=old_name) > + for listener in TicketSystem(self.env).resource_change_listeners: > + listener.resource_changed(self, old_values) > + > @classmethod > def select(cls, env, db=None): > """ > > Modified: incubator/bloodhound/trunk/trac/trac/ticket/tests/model.py > URL: > http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/ticket/tests/model.py?rev=1449780&r1=1449779&r2=1449780&view=diff > ============================================================================== > --- incubator/bloodhound/trunk/trac/trac/ticket/tests/model.py (original) > +++ incubator/bloodhound/trunk/trac/trac/ticket/tests/model.py Mon Feb 25 > 17:10:42 2013 > @@ -15,7 +15,8 @@ from trac.ticket.model import ( > Ticket, Component, Milestone, Priority, Type, Version > ) > from trac.ticket.api import ( > - IMilestoneChangeListener, ITicketChangeListener, TicketSystem > + IMilestoneChangeListener, ITicketChangeListener, TicketSystem, > + IResourceChangeListener, > ) > from trac.test import EnvironmentStub > from trac.util.datefmt import from_utimestamp, to_utimestamp, utc > @@ -1005,6 +1006,27 @@ class MilestoneTestCase(unittest.TestCas > self.assertEqual('deleted', listener.action) > self.assertEqual(milestone, listener.milestone) > > +class TestResourceChangeListener(core.Component): > + implements(IResourceChangeListener) > + > + def callback(self, action, resource, old_values = None): > + pass > + > + def resource_created(self, resource): > + self.action = "created" > + self.resource = resource > + self.callback(self.action, self.resource) > + > + def resource_changed(self, resource, old_values): > + self.action = "changed" > + self.resource = resource > + self.old_values = old_values > + self.callback(self.action, self.resource, old_values=self.old_values) > + > + def resource_deleted(self, resource): > + self.action = "deleted" > + self.resource = resource > + self.callback(self.action, self.resource) > > class ComponentTestCase(unittest.TestCase): > > @@ -1041,6 +1063,49 @@ class ComponentTestCase(unittest.TestCas > self.assertEqual([('Test', 'joe', None)], self.env.db_query( > "SELECT name, owner, description FROM component WHERE > name='Test'")) > > + def test_change_listener_created(self): > + listener = TestResourceChangeListener(self.env) > + self._create_component(name='Component 1') > + self.assertEqual('created', listener.action) > + self.assertIsInstance(listener.resource, Component) > + self.assertEqual('Component 1', listener.resource.name) > + > + def test_change_listener_changed(self): > + listener = TestResourceChangeListener(self.env) > + component = self._create_component(name='Component 1') > + component.name = 'Component 2' > + component.update() > + self.assertEqual('changed', listener.action) > + self.assertIsInstance(listener.resource, Component) > + self.assertEqual('Component 2', listener.resource.name) > + self.assertEqual("Component 1" ,listener.old_values["name"]) > + > + def test_change_listener_deleted(self): > + listener = TestResourceChangeListener(self.env) > + > + #component.name property is set to None during delete operation > + #We need mechanism to remember component name > + listener.callback = self.listener_callback > + > + component = self._create_component(name='Component 1') > + component.delete() > + self.assertEqual('deleted', listener.action) > + self.assertIsInstance(listener.resource, Component) > + self.assertEqual('Component 1', self.resource_name) > + > + def listener_callback(self, action, resource, old_values = None): > + self.resource_name = resource.name > + > + def _create_component(self, name, description = None, owner=None): > + component = Component(self.env) > + component.name = name > + if description is None: > + component.description = description > + if owner is None: > + component.owner = owner > + component.insert() > + return component > + > class VersionTestCase(unittest.TestCase): > > def setUp(self): > >
