Merge lp:~leonardr/lazr.restful/web-link into lp:lazr.restful
- web-link
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Tim Penhey | ||||
Approved revision: | 164 | ||||
Merged at revision: | 169 | ||||
Proposed branch: | lp:~leonardr/lazr.restful/web-link | ||||
Merge into: | lp:lazr.restful | ||||
Diff against target: |
339 lines (+145/-34) 6 files modified
src/lazr/restful/_resource.py (+20/-13) src/lazr/restful/docs/webservice.txt (+25/-10) src/lazr/restful/publisher.py (+1/-1) src/lazr/restful/tales.py (+4/-0) src/lazr/restful/templates/wadl-root.pt (+7/-0) src/lazr/restful/tests/test_webservice.py (+88/-10) |
||||
To merge this branch: | bzr merge lp:~leonardr/lazr.restful/web-link | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tim Penhey (community) | Approve | ||
Review via email:
|
Commit message
Description of the change
This branch fixes numerous errors and oversights in my initial implementation of the web_link parameter, which caused test failures and other problems as I integrated web_link into Launchpad.
1. If you send a PUT or PATCH request that mentions web_link, you'll get an error if and only if you tried to _change_ the web_link.
2. I created a property 'publish_web_link' in the entry adapter utility which is used consistently when deciding whether or not a given entry type supports web_link.
3. I fixed a bug in browser_
4. I added a 'web_link' <param> tag to the WADL definition of every resource type that publishes web_link. This will tell lazr.restfulclient about the web links, so they can actually be used.

Leonard Richardson (leonardr) wrote : | # |
If there is no IWebBrowserOrig
IWebBrowserOrig
- 164. By Leonard Richardson
-
Improved the adaptation from a web service request to a website request.

Tim Penhey (thumper) : | # |
Preview Diff
1 | === modified file 'src/lazr/restful/_resource.py' | |||
2 | --- src/lazr/restful/_resource.py 2011-01-26 16:50:13 +0000 | |||
3 | +++ src/lazr/restful/_resource.py 2011-02-01 16:44:09 +0000 | |||
4 | @@ -54,7 +54,6 @@ | |||
5 | 54 | getMultiAdapter, | 54 | getMultiAdapter, |
6 | 55 | getSiteManager, | 55 | getSiteManager, |
7 | 56 | getUtility, | 56 | getUtility, |
8 | 57 | queryAdapter, | ||
9 | 58 | queryMultiAdapter, | 57 | queryMultiAdapter, |
10 | 59 | ) | 58 | ) |
11 | 60 | from zope.component.interfaces import ComponentLookupError | 59 | from zope.component.interfaces import ComponentLookupError |
12 | @@ -1021,10 +1020,14 @@ | |||
13 | 1021 | del changeset['self_link'] | 1020 | del changeset['self_link'] |
14 | 1022 | 1021 | ||
15 | 1023 | if 'web_link' in changeset: | 1022 | if 'web_link' in changeset: |
20 | 1024 | if changeset['web_link'] != absoluteURL( | 1023 | browser_request = IWebBrowserOriginatingRequest( |
21 | 1025 | self.entry.context, get_current_browser_request()): | 1024 | self.request, None) |
22 | 1026 | errors.append(modified_read_only_attribute % 'web_link') | 1025 | if browser_request is not None: |
23 | 1027 | del changeset['web_link'] | 1026 | existing_web_link = absoluteURL( |
24 | 1027 | self.entry.context, browser_request) | ||
25 | 1028 | if changeset['web_link'] != existing_web_link: | ||
26 | 1029 | errors.append(modified_read_only_attribute % 'web_link') | ||
27 | 1030 | del changeset['web_link'] | ||
28 | 1028 | 1031 | ||
29 | 1029 | if 'resource_type_link' in changeset: | 1032 | if 'resource_type_link' in changeset: |
30 | 1030 | if changeset['resource_type_link'] != self.type_url: | 1033 | if changeset['resource_type_link'] != self.type_url: |
31 | @@ -1428,12 +1431,10 @@ | |||
32 | 1428 | """ | 1431 | """ |
33 | 1429 | data = {} | 1432 | data = {} |
34 | 1430 | data['self_link'] = absoluteURL(self.context, self.request) | 1433 | data['self_link'] = absoluteURL(self.context, self.request) |
41 | 1431 | browser_request = queryAdapter( | 1434 | if self.adapter_utility.publish_web_link: |
42 | 1432 | self.request, IWebBrowserOriginatingRequest) | 1435 | # Objects in the web service correspond to pages on some website. |
43 | 1433 | if (browser_request is not None | 1436 | # Provide the link to the corresponding page on the website. |
44 | 1434 | and self.adapter_utility.publish_web_link): | 1437 | browser_request = IWebBrowserOriginatingRequest(self.request) |
39 | 1435 | # Objects in the web server correspond to objects on some website. | ||
40 | 1436 | # Provide the link to the correspnding object on the website. | ||
45 | 1437 | data['web_link'] = absoluteURL(self.context, browser_request) | 1438 | data['web_link'] = absoluteURL(self.context, browser_request) |
46 | 1438 | data['resource_type_link'] = self.type_url | 1439 | data['resource_type_link'] = self.type_url |
47 | 1439 | unmarshalled_field_values = {} | 1440 | unmarshalled_field_values = {} |
48 | @@ -2169,8 +2170,14 @@ | |||
49 | 2169 | 2170 | ||
50 | 2170 | @property | 2171 | @property |
51 | 2171 | def publish_web_link(self): | 2172 | def publish_web_link(self): |
54 | 2172 | """Should this object type have a web_link published?""" | 2173 | """Return true if this entry should have a web_link.""" |
55 | 2173 | return self._get_tagged_value('publish_web_link') | 2174 | # If we can't adapt a web service request to a website |
56 | 2175 | # request, we shouldn't publish a web_link for *any* entry. | ||
57 | 2176 | web_service_request = get_current_web_service_request() | ||
58 | 2177 | website_request = IWebBrowserOriginatingRequest( | ||
59 | 2178 | web_service_request, None) | ||
60 | 2179 | return website_request is not None and self._get_tagged_value( | ||
61 | 2180 | 'publish_web_link') | ||
62 | 2174 | 2181 | ||
63 | 2175 | @property | 2182 | @property |
64 | 2176 | def singular_type(self): | 2183 | def singular_type(self): |
65 | 2177 | 2184 | ||
66 | === modified file 'src/lazr/restful/docs/webservice.txt' | |||
67 | --- src/lazr/restful/docs/webservice.txt 2011-01-21 21:12:17 +0000 | |||
68 | +++ src/lazr/restful/docs/webservice.txt 2011-02-01 16:44:09 +0000 | |||
69 | @@ -555,13 +555,19 @@ | |||
70 | 555 | >>> from lazr.restful.interfaces import IEntry, LAZR_WEBSERVICE_NAME | 555 | >>> from lazr.restful.interfaces import IEntry, LAZR_WEBSERVICE_NAME |
71 | 556 | >>> class IAuthorEntry(IAuthor, IEntry): | 556 | >>> class IAuthorEntry(IAuthor, IEntry): |
72 | 557 | ... """The part of an author we expose through the web service.""" | 557 | ... """The part of an author we expose through the web service.""" |
75 | 558 | ... taggedValue(LAZR_WEBSERVICE_NAME, dict(singular="author", | 558 | ... taggedValue( |
76 | 559 | ... plural="authors")) | 559 | ... LAZR_WEBSERVICE_NAME, |
77 | 560 | ... dict( | ||
78 | 561 | ... singular="author", plural="authors", | ||
79 | 562 | ... publish_web_link=True)) | ||
80 | 560 | 563 | ||
81 | 561 | >>> class ICommentEntry(IComment, IEntry): | 564 | >>> class ICommentEntry(IComment, IEntry): |
82 | 562 | ... """The part of a comment we expose through the web service.""" | 565 | ... """The part of a comment we expose through the web service.""" |
85 | 563 | ... taggedValue(LAZR_WEBSERVICE_NAME, | 566 | ... taggedValue( |
86 | 564 | ... dict(singular="comment", plural="comments")) | 567 | ... LAZR_WEBSERVICE_NAME, |
87 | 568 | ... dict( | ||
88 | 569 | ... singular="comment", plural="comments", | ||
89 | 570 | ... publish_web_link=True)) | ||
90 | 565 | 571 | ||
91 | 566 | Most of the time, it doesn't work to expose to the web service the same data | 572 | Most of the time, it doesn't work to expose to the web service the same data |
92 | 567 | model we expose internally. Usually there are fields we don't want to expose, | 573 | model we expose internally. Usually there are fields we don't want to expose, |
93 | @@ -581,8 +587,11 @@ | |||
94 | 581 | >>> class IDishEntry(IEntry): | 587 | >>> class IDishEntry(IEntry): |
95 | 582 | ... "The part of a dish that we expose through the web service." | 588 | ... "The part of a dish that we expose through the web service." |
96 | 583 | ... recipes = CollectionField(value_type=Reference(schema=IRecipe)) | 589 | ... recipes = CollectionField(value_type=Reference(schema=IRecipe)) |
99 | 584 | ... taggedValue(LAZR_WEBSERVICE_NAME, | 590 | ... taggedValue( |
100 | 585 | ... dict(singular="dish", plural="dishes")) | 591 | ... LAZR_WEBSERVICE_NAME, |
101 | 592 | ... dict( | ||
102 | 593 | ... singular="dish", plural="dishes", | ||
103 | 594 | ... publish_web_link=True)) | ||
104 | 586 | 595 | ||
105 | 587 | In the following code block we define an interface that exposes the underlying | 596 | In the following code block we define an interface that exposes the underlying |
106 | 588 | ``Recipe``'s name but not its ID. References to associated objects (like the | 597 | ``Recipe``'s name but not its ID. References to associated objects (like the |
107 | @@ -595,8 +604,11 @@ | |||
108 | 595 | ... dish = Reference(schema=IDish) | 604 | ... dish = Reference(schema=IDish) |
109 | 596 | ... instructions = Text(title=u"Name", required=True) | 605 | ... instructions = Text(title=u"Name", required=True) |
110 | 597 | ... comments = CollectionField(value_type=Reference(schema=IComment)) | 606 | ... comments = CollectionField(value_type=Reference(schema=IComment)) |
113 | 598 | ... taggedValue(LAZR_WEBSERVICE_NAME, | 607 | ... taggedValue( |
114 | 599 | ... dict(singular="recipe", plural="recipes")) | 608 | ... LAZR_WEBSERVICE_NAME, |
115 | 609 | ... dict( | ||
116 | 610 | ... singular="recipe", plural="recipes", | ||
117 | 611 | ... publish_web_link=True)) | ||
118 | 600 | 612 | ||
119 | 601 | >>> from lazr.restful.fields import ReferenceChoice | 613 | >>> from lazr.restful.fields import ReferenceChoice |
120 | 602 | >>> class ICookbookEntry(IEntry): | 614 | >>> class ICookbookEntry(IEntry): |
121 | @@ -607,8 +619,11 @@ | |||
122 | 607 | ... recipes = CollectionField(value_type=Reference(schema=IRecipe)) | 619 | ... recipes = CollectionField(value_type=Reference(schema=IRecipe)) |
123 | 608 | ... comments = CollectionField(value_type=Reference(schema=IComment)) | 620 | ... comments = CollectionField(value_type=Reference(schema=IComment)) |
124 | 609 | ... cover = Bytes(0, 5000, title=u"An image of the cookbook's cover.") | 621 | ... cover = Bytes(0, 5000, title=u"An image of the cookbook's cover.") |
127 | 610 | ... taggedValue(LAZR_WEBSERVICE_NAME, | 622 | ... taggedValue( |
128 | 611 | ... dict(singular="cookbook", plural="cookbooks")) | 623 | ... LAZR_WEBSERVICE_NAME, |
129 | 624 | ... dict( | ||
130 | 625 | ... singular="cookbook", plural="cookbooks", | ||
131 | 626 | ... publish_web_link=True)) | ||
132 | 612 | 627 | ||
133 | 613 | The ``author`` field is a choice between ``Author`` objects. To make sure | 628 | The ``author`` field is a choice between ``Author`` objects. To make sure |
134 | 614 | that the ``Author`` objects are properly marshalled to JSON, we need to | 629 | that the ``Author`` objects are properly marshalled to JSON, we need to |
135 | 615 | 630 | ||
136 | === modified file 'src/lazr/restful/publisher.py' | |||
137 | --- src/lazr/restful/publisher.py 2011-01-21 21:12:17 +0000 | |||
138 | +++ src/lazr/restful/publisher.py 2011-02-01 16:44:09 +0000 | |||
139 | @@ -305,7 +305,7 @@ | |||
140 | 305 | if web_service_version is None: | 305 | if web_service_version is None: |
141 | 306 | web_service_version = config.active_versions[-1] | 306 | web_service_version = config.active_versions[-1] |
142 | 307 | 307 | ||
144 | 308 | body = website_request.bodyStream.getCacheStream().read() | 308 | body = website_request.bodyStream.getCacheStream() |
145 | 309 | environ = dict(website_request.environment) | 309 | environ = dict(website_request.environment) |
146 | 310 | # Zope picks up on SERVER_URL when setting the _app_server attribute | 310 | # Zope picks up on SERVER_URL when setting the _app_server attribute |
147 | 311 | # of the new request. | 311 | # of the new request. |
148 | 312 | 312 | ||
149 | === modified file 'src/lazr/restful/tales.py' | |||
150 | --- src/lazr/restful/tales.py 2011-01-21 21:12:17 +0000 | |||
151 | +++ src/lazr/restful/tales.py 2011-02-01 16:44:09 +0000 | |||
152 | @@ -441,6 +441,10 @@ | |||
153 | 441 | return self.utility.entry_page_representation_id | 441 | return self.utility.entry_page_representation_id |
154 | 442 | 442 | ||
155 | 443 | @property | 443 | @property |
156 | 444 | def publish_web_link(self): | ||
157 | 445 | return self.utility.publish_web_link | ||
158 | 446 | |||
159 | 447 | @property | ||
160 | 444 | def all_fields(self): | 448 | def all_fields(self): |
161 | 445 | "Return all schema fields for the object." | 449 | "Return all schema fields for the object." |
162 | 446 | return [field for name, field in | 450 | return [field for name, field in |
163 | 447 | 451 | ||
164 | === modified file 'src/lazr/restful/templates/wadl-root.pt' | |||
165 | --- src/lazr/restful/templates/wadl-root.pt 2010-08-09 20:05:08 +0000 | |||
166 | +++ src/lazr/restful/templates/wadl-root.pt 2011-02-01 16:44:09 +0000 | |||
167 | @@ -223,6 +223,13 @@ | |||
168 | 223 | <wadl:doc>The canonical link to this resource.</wadl:doc> | 223 | <wadl:doc>The canonical link to this resource.</wadl:doc> |
169 | 224 | <link tal:attributes="resource_type context/wadl_entry:type_link" /> | 224 | <link tal:attributes="resource_type context/wadl_entry:type_link" /> |
170 | 225 | </param> | 225 | </param> |
171 | 226 | <param style="plain" name="web_link" path="$['web_link']" | ||
172 | 227 | tal:condition="context/wadl_entry:publish_web_link"> | ||
173 | 228 | <wadl:doc> | ||
174 | 229 | The canonical human-addressable web link to this resource. | ||
175 | 230 | </wadl:doc> | ||
176 | 231 | <link /> | ||
177 | 232 | </param> | ||
178 | 226 | <param style="plain" name="resource_type_link" | 233 | <param style="plain" name="resource_type_link" |
179 | 227 | path="$['resource_type_link']"> | 234 | path="$['resource_type_link']"> |
180 | 228 | <wadl:doc> | 235 | <wadl:doc> |
181 | 229 | 236 | ||
182 | === modified file 'src/lazr/restful/tests/test_webservice.py' | |||
183 | --- src/lazr/restful/tests/test_webservice.py 2011-01-26 16:50:13 +0000 | |||
184 | +++ src/lazr/restful/tests/test_webservice.py 2011-02-01 16:44:09 +0000 | |||
185 | @@ -6,10 +6,12 @@ | |||
186 | 6 | 6 | ||
187 | 7 | from contextlib import contextmanager | 7 | from contextlib import contextmanager |
188 | 8 | from cStringIO import StringIO | 8 | from cStringIO import StringIO |
189 | 9 | from lxml import etree | ||
190 | 9 | from operator import attrgetter | 10 | from operator import attrgetter |
191 | 10 | from textwrap import dedent | 11 | from textwrap import dedent |
192 | 11 | import random | 12 | import random |
193 | 12 | import re | 13 | import re |
194 | 14 | import simplejson | ||
195 | 13 | import unittest | 15 | import unittest |
196 | 14 | 16 | ||
197 | 15 | from zope.component import getGlobalSiteManager, getUtility | 17 | from zope.component import getGlobalSiteManager, getUtility |
198 | @@ -24,6 +26,8 @@ | |||
199 | 24 | ) | 26 | ) |
200 | 25 | from zope.traversing.browser.interfaces import IAbsoluteURL | 27 | from zope.traversing.browser.interfaces import IAbsoluteURL |
201 | 26 | 28 | ||
202 | 29 | from wadllib.application import Application | ||
203 | 30 | |||
204 | 27 | from lazr.enum import EnumeratedType, Item | 31 | from lazr.enum import EnumeratedType, Item |
205 | 28 | from lazr.restful import ( | 32 | from lazr.restful import ( |
206 | 29 | EntryField, | 33 | EntryField, |
207 | @@ -133,6 +137,8 @@ | |||
208 | 133 | class EntryTestCase(WebServiceTestCase): | 137 | class EntryTestCase(WebServiceTestCase): |
209 | 134 | """A test suite that defines an entry class.""" | 138 | """A test suite that defines an entry class.""" |
210 | 135 | 139 | ||
211 | 140 | WADL_NS = "{http://research.sun.com/wadl/2006/10}" | ||
212 | 141 | |||
213 | 136 | class DummyWebsiteRequest: | 142 | class DummyWebsiteRequest: |
214 | 137 | """A request to the website, as opposed to the web service.""" | 143 | """A request to the website, as opposed to the web service.""" |
215 | 138 | implements(IWebBrowserOriginatingRequest) | 144 | implements(IWebBrowserOriginatingRequest) |
216 | @@ -142,20 +148,29 @@ | |||
217 | 142 | URL = 'http://www.website.url/' | 148 | URL = 'http://www.website.url/' |
218 | 143 | 149 | ||
219 | 144 | @contextmanager | 150 | @contextmanager |
220 | 151 | def request(self): | ||
221 | 152 | request = getUtility(IWebServiceConfiguration).createRequest( | ||
222 | 153 | StringIO(""), {}) | ||
223 | 154 | newInteraction(request) | ||
224 | 155 | yield request | ||
225 | 156 | endInteraction() | ||
226 | 157 | |||
227 | 158 | @property | ||
228 | 159 | def wadl(self): | ||
229 | 160 | """Get a parsed WADL description of the web service.""" | ||
230 | 161 | with self.request() as request: | ||
231 | 162 | return request.publication.application.toWADL().encode('utf-8') | ||
232 | 163 | |||
233 | 164 | @contextmanager | ||
234 | 145 | def entry_resource(self, entry_interface, entry_implementation): | 165 | def entry_resource(self, entry_interface, entry_implementation): |
235 | 146 | """Create a request to an entry resource, and yield the resource.""" | 166 | """Create a request to an entry resource, and yield the resource.""" |
236 | 147 | entry_class = get_resource_factory(entry_interface, IEntry) | 167 | entry_class = get_resource_factory(entry_interface, IEntry) |
237 | 148 | request = getUtility(IWebServiceConfiguration).createRequest( | ||
238 | 149 | StringIO(""), {}) | ||
239 | 150 | newInteraction(request) | ||
240 | 151 | |||
241 | 152 | data_object = entry_implementation("") | 168 | data_object = entry_implementation("") |
248 | 153 | entry = entry_class(data_object, request) | 169 | |
249 | 154 | resource = EntryResource(data_object, request) | 170 | with self.request() as request: |
250 | 155 | 171 | entry = entry_class(data_object, request) | |
251 | 156 | yield resource | 172 | resource = EntryResource(data_object, request) |
252 | 157 | 173 | yield resource | |
247 | 158 | endInteraction() | ||
253 | 159 | 174 | ||
254 | 160 | def _register_url_adapter(self, entry_interface): | 175 | def _register_url_adapter(self, entry_interface): |
255 | 161 | """Register an IAbsoluteURL implementation for an interface.""" | 176 | """Register an IAbsoluteURL implementation for an interface.""" |
256 | @@ -221,6 +236,26 @@ | |||
257 | 221 | self.assertEquals( | 236 | self.assertEquals( |
258 | 222 | representation['web_link'], self.DummyWebsiteURL.URL) | 237 | representation['web_link'], self.DummyWebsiteURL.URL) |
259 | 223 | 238 | ||
260 | 239 | def test_wadl_includes_web_link_when_available(self): | ||
261 | 240 | # If an entry includes a web_link, this information will | ||
262 | 241 | # show up in the WADL description of the entry. | ||
263 | 242 | service_root = "https://webservice_test/2.0/" | ||
264 | 243 | self._register_website_url_space(IHasOneField) | ||
265 | 244 | |||
266 | 245 | doc = etree.parse(StringIO(self.wadl)) | ||
267 | 246 | # Verify that the 'has_one_field-full' representation includes | ||
268 | 247 | # a 'web_link' param. | ||
269 | 248 | representation = [ | ||
270 | 249 | rep for rep in doc.findall('%srepresentation' % self.WADL_NS) | ||
271 | 250 | if rep.get('id') == 'has_one_field-full'][0] | ||
272 | 251 | param = [ | ||
273 | 252 | param for param in representation.findall( | ||
274 | 253 | '%sparam' % self.WADL_NS) | ||
275 | 254 | if param.get('name') == 'web_link'][0] | ||
276 | 255 | |||
277 | 256 | # Verify that the 'web_link' param includes a 'link' tag. | ||
278 | 257 | self.assertFalse(param.find('%slink' % self.WADL_NS) is None) | ||
279 | 258 | |||
280 | 224 | def test_entry_omits_web_link_when_not_available(self): | 259 | def test_entry_omits_web_link_when_not_available(self): |
281 | 225 | # When there is no way of turning a webservice request into a | 260 | # When there is no way of turning a webservice request into a |
282 | 226 | # website request, the 'web_link' attribute is missing from | 261 | # website request, the 'web_link' attribute is missing from |
283 | @@ -233,6 +268,13 @@ | |||
284 | 233 | representation['self_link'], DummyAbsoluteURL.URL) | 268 | representation['self_link'], DummyAbsoluteURL.URL) |
285 | 234 | self.assertFalse('web_link' in representation) | 269 | self.assertFalse('web_link' in representation) |
286 | 235 | 270 | ||
287 | 271 | def test_wadl_omits_web_link_when_not_available(self): | ||
288 | 272 | # When there is no way of turning a webservice request into a | ||
289 | 273 | # website request, the 'web_link' attribute is missing from | ||
290 | 274 | # WADL descriptions of entries. | ||
291 | 275 | self._register_url_adapter(IHasOneField) | ||
292 | 276 | self.assertFalse('web_link' in self.wadl) | ||
293 | 277 | |||
294 | 236 | 278 | ||
295 | 237 | class IHasNoWebLink(Interface): | 279 | class IHasNoWebLink(Interface): |
296 | 238 | """An entry that does not publish a web_link.""" | 280 | """An entry that does not publish a web_link.""" |
297 | @@ -292,6 +334,42 @@ | |||
298 | 292 | 334 | ||
299 | 293 | class TestEntryWrite(EntryTestCase): | 335 | class TestEntryWrite(EntryTestCase): |
300 | 294 | 336 | ||
301 | 337 | testmodule_objects = [IHasOneField, HasOneField] | ||
302 | 338 | |||
303 | 339 | |||
304 | 340 | def test_applyChanges_rejects_nonexistent_web_link(self): | ||
305 | 341 | # If web_link is not published, applyChanges rejects a request | ||
306 | 342 | # that references it. | ||
307 | 343 | with self.entry_resource(IHasOneField, HasOneField) as resource: | ||
308 | 344 | errors = resource.applyChanges({'web_link': u'some_value'}) | ||
309 | 345 | self.assertEquals( | ||
310 | 346 | errors, | ||
311 | 347 | 'web_link: You tried to modify a nonexistent attribute.') | ||
312 | 348 | |||
313 | 349 | def test_applyChanges_rejects_changed_web_link(self): | ||
314 | 350 | """applyChanges rejects an attempt to change web_link .""" | ||
315 | 351 | self._register_website_url_space(IHasOneField) | ||
316 | 352 | |||
317 | 353 | with self.entry_resource(IHasOneField, HasOneField) as resource: | ||
318 | 354 | errors = resource.applyChanges({'web_link': u'some_value'}) | ||
319 | 355 | self.assertEquals( | ||
320 | 356 | errors, | ||
321 | 357 | 'web_link: You tried to modify a read-only attribute.') | ||
322 | 358 | |||
323 | 359 | def test_applyChanges_accepts_unchanged_web_link(self): | ||
324 | 360 | # applyChanges accepts a reference to web_link, as long as the | ||
325 | 361 | # value isn't actually being changed. | ||
326 | 362 | self._register_website_url_space(IHasOneField) | ||
327 | 363 | |||
328 | 364 | with self.entry_resource(IHasOneField, HasOneField) as resource: | ||
329 | 365 | existing_web_link = resource.toDataForJSON()['web_link'] | ||
330 | 366 | representation = simplejson.loads(resource.applyChanges( | ||
331 | 367 | {'web_link': existing_web_link})) | ||
332 | 368 | self.assertEquals(representation['web_link'], existing_web_link) | ||
333 | 369 | |||
334 | 370 | |||
335 | 371 | class TestEntryWriteForRestrictedField(EntryTestCase): | ||
336 | 372 | |||
337 | 295 | testmodule_objects = [IHasRestrictedField, HasRestrictedField] | 373 | testmodule_objects = [IHasRestrictedField, HasRestrictedField] |
338 | 296 | 374 | ||
339 | 297 | def test_applyChanges_binds_to_resource_context(self): | 375 | def test_applyChanges_binds_to_resource_context(self): |
> if self.adapter_ utility. publish_ web_link: inatingRequest( self.request)
> # Objects in the web service correspond to pages on some website.
> # Provide the link to the corresponding page on the website.
> browser_request = IWebBrowserOrig
Can we be sure here that there is an adapter for request to a inatingRequest?
IWebBrowserOrig
I'm pretty sure that you can rewrite: web_service_ request, IWebBrowserOrig inatingRequest) iginatingReques t(web_service_ request, None)
queryAdapter(
as
IWebBrowserOr
Not sure if you think this is better or not.