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):