Merge lp:~leonardr/lazr.restful/449561 into lp:lazr.restful

Proposed by Leonard Richardson
Status: Merged
Merged at revision: 187
Proposed branch: lp:~leonardr/lazr.restful/449561
Merge into: lp:lazr.restful
Diff against target: 175 lines (+84/-34)
3 files modified
src/lazr/restful/NEWS.txt (+4/-1)
src/lazr/restful/_resource.py (+36/-32)
src/lazr/restful/tests/test_webservice.py (+44/-1)
To merge this branch: bzr merge lp:~leonardr/lazr.restful/449561
Reviewer Review Type Date Requested Status
Abel Deuring (community) code Approve
Review via email: mp+54849@code.launchpad.net

Description of the change

This branch stops lazr.restful from sending out an ObjectModifiedEvent if the object was not actually modified. A representation of the object is still returned. This should fix the oops seen in bug 449561, though not necessarily the bug itself.

To post a comment you must log in.
Revision history for this message
Abel Deuring (adeuring) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/lazr/restful/NEWS.txt'
2--- src/lazr/restful/NEWS.txt 2011-03-24 20:02:41 +0000
3+++ src/lazr/restful/NEWS.txt 2011-03-25 11:56:29 +0000
4@@ -2,11 +2,14 @@
5 NEWS for lazr.restful
6 =====================
7
8-0.18.1 (2011-03-24)
9+0.18.1 (2011-03-25)
10 ===================
11
12 Fixed minor test failures.
13
14+The object modification event will not be fired if a client sends an
15+empty changeset via PATCH.
16+
17 0.18.0 (2011-03-23)
18 ===================
19
20
21=== modified file 'src/lazr/restful/_resource.py'
22--- src/lazr/restful/_resource.py 2011-03-15 17:13:48 +0000
23+++ src/lazr/restful/_resource.py 2011-03-25 11:56:29 +0000
24@@ -1174,41 +1174,45 @@
25 self.request.response.setHeader('Content-type', 'text/plain')
26 return "\n".join(errors)
27
28- # Make a snapshot of the entry to use in a notification event.
29- entry_before_modification = Snapshot(
30- self.entry.context, providing=providedBy(self.entry.context))
31-
32 # Store the entry's current URL so we can see if it changes.
33 original_url = absoluteURL(self.entry.context, self.request)
34
35- # Make the changes, in the same order obtained from
36- # get_entry_fields_in_write_order.
37- for field, value in validated_changeset:
38- field.set(self.entry, value)
39- # The representation has changed, and etags will need to be
40- # recalculated.
41- self.etags_by_media_type = {}
42-
43- # Send a notification event.
44- event = ObjectModifiedEvent(
45- object=self.entry.context,
46- object_before_modification=entry_before_modification,
47- edited_fields=[field for field, value in validated_changeset])
48- notify(event)
49-
50- # The changeset contained new values for some of this object's
51- # fields, and the notification event may have changed others.
52- # Clear out any fields that changed.
53- for name, ignored in self._unmarshalled_field_cache.items():
54- force_clear = False
55- try:
56- old_value = getattr(entry_before_modification, name, None)
57- new_value = getattr(self.entry.context, name, None)
58- except Unauthorized:
59- # Clear the cache, just to be safe.
60- force_clear = True
61- if force_clear or new_value != old_value:
62- del(self._unmarshalled_field_cache[name])
63+ if len(validated_changeset) > 0:
64+ # The representation will actually change.
65+
66+ # Make a snapshot of the entry to use in a notification event.
67+ entry_before_modification = Snapshot(
68+ self.entry.context, providing=providedBy(self.entry.context))
69+
70+ # Make the changes, in the same order obtained from
71+ # get_entry_fields_in_write_order.
72+ for field, value in validated_changeset:
73+ field.set(self.entry, value)
74+
75+ # ETags will need to be recalculated.
76+ self.etags_by_media_type = {}
77+
78+ # Send a notification event.
79+ event = ObjectModifiedEvent(
80+ object=self.entry.context,
81+ object_before_modification=entry_before_modification,
82+ edited_fields=[
83+ field for field, value in validated_changeset])
84+ notify(event)
85+
86+ # The changeset contained new values for some of this object's
87+ # fields, and the notification event may have changed others.
88+ # Clear out any fields that changed.
89+ for name, ignored in self._unmarshalled_field_cache.items():
90+ force_clear = False
91+ try:
92+ old_value = getattr(entry_before_modification, name, None)
93+ new_value = getattr(self.entry.context, name, None)
94+ except Unauthorized:
95+ # Clear the cache, just to be safe.
96+ force_clear = True
97+ if force_clear or new_value != old_value:
98+ del(self._unmarshalled_field_cache[name])
99
100 self._applyChangesPostHook()
101
102
103=== modified file 'src/lazr/restful/tests/test_webservice.py'
104--- src/lazr/restful/tests/test_webservice.py 2011-03-07 15:16:57 +0000
105+++ src/lazr/restful/tests/test_webservice.py 2011-03-25 11:56:29 +0000
106@@ -14,7 +14,11 @@
107 import simplejson
108 import unittest
109
110-from zope.component import getGlobalSiteManager, getUtility
111+from zope.component import (
112+ eventtesting,
113+ getGlobalSiteManager,
114+ getUtility,
115+ )
116 from zope.interface import implements, Interface
117 from zope.interface.interface import InterfaceClass
118 from zope.publisher.browser import TestRequest
119@@ -30,6 +34,7 @@
120 from wadllib.application import Application
121
122 from lazr.enum import EnumeratedType, Item
123+from lazr.lifecycle.interfaces import IObjectModifiedEvent
124 from lazr.restful import (
125 EntryField,
126 ResourceOperation,
127@@ -403,6 +408,16 @@
128 resource.applyChanges({'web_link': existing_web_link}))
129 self.assertEquals(representation['web_link'], existing_web_link)
130
131+ def test_applyChanges_returns_representation_on_empty_changeset(self):
132+ # Even if the changeset is empty, applyChanges returns a
133+ # representation of the (unchanged) resource.
134+ self._register_website_url_space(IHasOneField)
135+
136+ with self.entry_resource(IHasOneField, HasOneField, "") as resource:
137+ existing_representation = resource.toDataForJSON()
138+ representation = simplejson.loads(resource.applyChanges({}))
139+ self.assertEquals(representation, existing_representation)
140+
141
142 class TestEntryWriteForRestrictedField(EntryTestCase):
143
144@@ -908,3 +923,31 @@
145 return ResourceOperation(None, request)
146
147
148+class EventTestCase(EntryTestCase):
149+
150+ testmodule_objects = [IHasOneField]
151+
152+ def setUp(self):
153+ super(EventTestCase, self).setUp()
154+ self._register_url_adapter(IHasOneField)
155+ eventtesting.setUp()
156+
157+ def test_event_fired_when_changeset_is_not_empty(self):
158+ # Passing in a non-empty changeset spawns an
159+ # IObjectModifiedEvent.
160+ with self.entry_resource(
161+ IHasOneField, HasOneField, "") as resource:
162+ resource.applyChanges({'a_field': u'Some value'})
163+ events = eventtesting.getEvents()
164+ self.assertEquals(len(events), 1)
165+ event = events[0]
166+ self.assertEquals(event.object_before_modification.a_field, "")
167+ self.assertEquals(event.object.a_field, "Some value")
168+
169+ def test_event_not_fired_when_changeset_is_empty(self):
170+ # Passing in an empty changeset does not spawn an
171+ # IObjectModifiedEvent.
172+ with self.entry_resource(
173+ IHasOneField, HasOneField, "") as resource:
174+ resource.applyChanges({})
175+ self.assertEquals(len(eventtesting.getEvents()), 0)

Subscribers

People subscribed via source and target branches