Merge lp:~stevenk/launchpad/subscription-no-longer-updates into lp:launchpad

Proposed by Steve Kowalik on 2012-09-10
Status: Merged
Approved by: William Grant on 2012-09-10
Approved revision: no longer in the source branch.
Merged at revision: 15926
Proposed branch: lp:~stevenk/launchpad/subscription-no-longer-updates
Merge into: lp:launchpad
Diff against target: 133 lines (+59/-9)
2 files modified
lib/lp/bugs/subscribers/buglastupdated.py (+8/-8)
lib/lp/bugs/tests/test_bugs_webservice.py (+51/-1)
To merge this branch: bzr merge lp:~stevenk/launchpad/subscription-no-longer-updates
Reviewer Review Type Date Requested Status
William Grant code 2012-09-10 Approve on 2012-09-10
Review via email: mp+123467@code.launchpad.net

Commit Message

Do not update IBug.date_last_updated in the subscriber method if no fields on the bug changed.

Description of the Change

When you call IBug.subscribe(), the date_last_updated property was not updated, by design. The API broke this rule, calling bug.subscribe(person=launchpad.me) over the API would create the subscription and then update date_last_updated. The API machinery is the cause -- it will call notify(ObjectModifiedEvent(bug)) since it assumes that the object has changed since you just called a method on it.

I am now checking the details of the event in the Zope subscriber for ObjectModifiedEvent, and have added two API tests. I also took the opportunity to shorten update_bug_date_last_updated().

To post a comment you must log in.
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/subscribers/buglastupdated.py'
2--- lib/lp/bugs/subscribers/buglastupdated.py 2012-03-08 11:51:36 +0000
3+++ lib/lp/bugs/subscribers/buglastupdated.py 2012-09-10 08:09:21 +0000
4@@ -5,8 +5,9 @@
5
6 __metaclass__ = type
7
8-import datetime
9+from datetime import datetime
10
11+from lazr.lifecycle.interfaces import IObjectModifiedEvent
12 import pytz
13 from zope.security.proxy import removeSecurityProxy
14
15@@ -16,17 +17,16 @@
16
17 def update_bug_date_last_updated(object, event):
18 """Update IBug.date_last_updated to the current date."""
19+ # If no fields on the bug have changed, do nothing.
20+ if IObjectModifiedEvent.providedBy(event) and not event.edited_fields:
21+ return
22 if IBug.providedBy(object):
23- current_bug = object
24+ bug = object
25 elif IHasBug.providedBy(object):
26- current_bug = object.bug
27+ bug = object.bug
28 else:
29 raise AssertionError(
30 "Unable to retrieve current bug to update 'date last updated'. "
31 "Event handler expects object implementing IBug or IHasBug. "
32 "Got: %s" % repr(object))
33-
34- UTC = pytz.timezone('UTC')
35- now = datetime.datetime.now(UTC)
36-
37- removeSecurityProxy(current_bug).date_last_updated = now
38+ removeSecurityProxy(bug).date_last_updated = datetime.now(pytz.UTC)
39
40=== modified file 'lib/lp/bugs/tests/test_bugs_webservice.py'
41--- lib/lp/bugs/tests/test_bugs_webservice.py 2012-09-04 04:27:53 +0000
42+++ lib/lp/bugs/tests/test_bugs_webservice.py 2012-09-10 08:09:21 +0000
43@@ -5,6 +5,10 @@
44
45 __metaclass__ = type
46
47+from datetime import (
48+ datetime,
49+ timedelta,
50+ )
51 import re
52
53 from BeautifulSoup import BeautifulSoup
54@@ -13,6 +17,7 @@
55 BadRequest,
56 HTTPError,
57 )
58+import pytz
59 from simplejson import dumps
60 from storm.store import Store
61 from testtools.matchers import (
62@@ -20,6 +25,7 @@
63 LessThan,
64 )
65 from zope.component import getMultiAdapter
66+from zope.security.proxy import removeSecurityProxy
67
68 from lp.bugs.browser.bugtask import get_comments_for_bugtask
69 from lp.bugs.interfaces.bug import IBug
70@@ -29,6 +35,7 @@
71 )
72 from lp.registry.interfaces.product import License
73 from lp.services.webapp import snapshot
74+from lp.services.webapp.interfaces import OAuthPermission
75 from lp.services.webapp.servers import LaunchpadTestRequest
76 from lp.testing import (
77 api_url,
78@@ -45,7 +52,10 @@
79 LaunchpadFunctionalLayer,
80 )
81 from lp.testing.matchers import HasQueryCount
82-from lp.testing.pages import LaunchpadWebServiceCaller
83+from lp.testing.pages import (
84+ LaunchpadWebServiceCaller,
85+ webservice_for_person,
86+ )
87 from lp.testing.sampledata import (
88 ADMIN_EMAIL,
89 USER_EMAIL,
90@@ -433,3 +443,43 @@
91 bug = bugs_collection.createBug(
92 target=api_url(project), title='title', description='desc')
93 self.assertEqual('Proprietary', bug.information_type)
94+
95+
96+class TestBugDateLastUpdated(TestCaseWithFactory):
97+
98+ layer = DatabaseFunctionalLayer
99+
100+ def make_old_bug(self):
101+ bug = self.factory.makeBug()
102+ one_year_ago = datetime.now(pytz.UTC) - timedelta(days=365)
103+ removeSecurityProxy(bug).date_last_updated = one_year_ago
104+ owner = bug.owner
105+ with person_logged_in(owner):
106+ webservice = webservice_for_person(
107+ owner, permission=OAuthPermission.WRITE_PUBLIC)
108+ return (bug, owner, webservice)
109+
110+ def test_subscribe_does_not_update(self):
111+ # Calling subscribe over the API does not update date_last_updated.
112+ (bug, owner, webservice) = self.make_old_bug()
113+ subscriber = self.factory.makePerson()
114+ date_last_updated = bug.date_last_updated
115+ api_sub = api_url(subscriber)
116+ bug_url = api_url(bug)
117+ logout()
118+ response = webservice.named_post(bug_url, 'subscribe', person=api_sub)
119+ self.assertEqual(200, response.status)
120+ with person_logged_in(owner):
121+ self.assertEqual(date_last_updated, bug.date_last_updated)
122+
123+ def test_change_status_does_update(self):
124+ # Changing the status of a bugtask does change date_last_updated.
125+ (bug, owner, webservice) = self.make_old_bug()
126+ task_url = api_url(bug.default_bugtask)
127+ date_last_updated = bug.date_last_updated
128+ logout()
129+ response = webservice.patch(
130+ task_url, 'application/json', dumps(dict(status='Invalid')))
131+ self.assertEqual(209, response.status)
132+ with person_logged_in(owner):
133+ self.assertNotEqual(date_last_updated, bug.date_last_updated)