Skip to content

Commit 3b32e2e

Browse files
authored
Preliminary step to deprecate docstring based publication control (#1197)
* Introduce `ZPublisher.zpublish` decorator to explicitly control zpublishabilty * add `CHANGES.rst` entry * fixes from review * Implement review suggestion to support request method restrictions * documentation fix from review [skip ci] * Cleanup * honor `mapply` specific signature specification via `__code__` and `__defaults__`. * replace `AccessControl.requestmethod` by `zpublish` if possible `zpublish` mark `manage_` methods * Avoid redundant `DocstringWarning`s; provide location info (if possible) * configure Python's warningfilter to not suppress `DocstringWarnings` when docstring publishing is deprecated * add missing `zpublish` decorations
1 parent 168963a commit 3b32e2e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+749
-59
lines changed

CHANGES.rst

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ https://github.com/zopefoundation/Zope/blob/4.x/CHANGES.rst
1818
- Fix redirections to URLs with host given as IP-literal with brackets.
1919
Fixes `#1191 <https://github.com/zopefoundation/Zope/issues/1191>`_.
2020

21+
- Introduce the decorator ``ZPublisher.zpublish`` to explicitly
22+
control publication by ``ZPublisher``.
23+
For details see
24+
`#1197 <https://github.com/zopefoundation/Zope/pull/1197>`_.
25+
2126
- Fix ``Content-Disposition`` filename for clients without rfc6266 support.
2227
(`#1198 <https://github.com/zopefoundation/Zope/pull/1198>`_)
2328

docs/zdgbook/ObjectPublishing.rst

+33-1
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,22 @@ Publishable Object Requirements
130130
-------------------------------
131131

132132
Zope has few restrictions on publishable objects. The basic rule is
133-
that the object must have a doc string. This requirement goes for
133+
that the object must have been marked as zpublishable. This requirement goes for
134134
methods, too.
135+
An object or method is marked as zpublishable by decorating
136+
its class (or a base class) or underlying function, respectively,
137+
with the ``Zpublisher.zpublish`` decorator.
138+
For backward compatibility, the existence of a docstring, too,
139+
marks an object or method as zpublishable; but this will be removed in
140+
the future.
141+
If you decorate a method or class with ``zpublsh(False)``,
142+
you explicitly mark it or its instances, respectively, as not
143+
zpublishable.
144+
If you decorate a method with ``zpublish(methods=...)``
145+
where the `...` is either a single request method name
146+
or a sequence of request method names,
147+
you specify that the object is zpublishable only for the mentioned request
148+
methods.
135149

136150
Another requirement is that a publishable object must not have a name
137151
that begins with an underscore. These two restrictions are designed to
@@ -270,9 +284,13 @@ allow you to navigate between methods.
270284

271285
Consider this example::
272286

287+
from ZPublisher import zpublish
288+
289+
@zpublish
273290
class Example:
274291
"""example class"""
275292

293+
@zpublish
276294
def one(self):
277295
"""render page one"""
278296
return """<html>
@@ -282,6 +300,7 @@ Consider this example::
282300
</body>
283301
</html>"""
284302

303+
@zpublish
285304
def two(self):
286305
"""render page two"""
287306
return """<html>
@@ -298,9 +317,11 @@ the URL, relative links returned by ``index_html`` won't work right.
298317

299318
For example::
300319

320+
@zpublish
301321
class Example:
302322
"""example class""""
303323

324+
@zpublish
304325
def index_html(self):
305326
"""render default view"""
306327
return """<html>
@@ -375,7 +396,9 @@ acquisition, you can use traversal to walk over acquired objects.
375396
Consider the the following object hierarchy::
376397

377398
from Acquisition import Implicit
399+
from ZPublisher import zpublish
378400

401+
@zpublish
379402
class Node(Implicit):
380403
...
381404

@@ -401,20 +424,27 @@ method that your acquire from outside your container.
401424
For example::
402425

403426
from Acquisition import Implicit
427+
from ZPublisher import zpublish
404428

429+
@zpublish
405430
class Basket(Implicit):
406431
...
432+
@zpublish
407433
def number_of_items(self):
408434
"""Returns the number of contained items."""
409435
...
410436

437+
@zpublish
411438
class Vegetable(Implicit):
412439
...
440+
@zpublish
413441
def texture(self):
414442
"""Returns the texture of the vegetable."""
415443

444+
@zpublish
416445
class Fruit(Implicit):
417446
...
447+
@zpublish
418448
def color(self):
419449
"""Returns the color of the fruit."""
420450

@@ -582,6 +612,7 @@ called from the web.
582612

583613
Consider this function::
584614

615+
@zpublish
585616
def greet(name):
586617
"""Greet someone by name."""
587618
return "Hello, %s!" % name
@@ -663,6 +694,7 @@ Argument Conversion
663694
The publisher supports argument conversion. For example consider this
664695
function::
665696

697+
@zpublish
666698
def one_third(number):
667699
"""returns the number divided by three"""
668700
return number / 3.0

src/App/ApplicationManager.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from urllib import parse
2020

2121
from AccessControl.class_init import InitializeClass
22-
from AccessControl.requestmethod import requestmethod
2322
from Acquisition import Implicit
2423
from App.CacheManager import CacheManager
2524
from App.config import getConfiguration
@@ -33,6 +32,7 @@
3332
from OFS.Traversable import Traversable
3433
from Persistence import Persistent
3534
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
35+
from ZPublisher import zpublish
3636

3737

3838
class FakeConnection:
@@ -365,7 +365,7 @@ def db_size(self):
365365
return '%.1fM' % (s / 1048576.0)
366366
return '%.1fK' % (s / 1024.0)
367367

368-
@requestmethod('POST')
368+
@zpublish(methods='POST')
369369
def manage_minimize(self, value=1, REQUEST=None):
370370
"Perform a full sweep through the cache"
371371
# XXX Add a deprecation warning about value?
@@ -376,7 +376,7 @@ def manage_minimize(self, value=1, REQUEST=None):
376376
url = f'{REQUEST["URL1"]}/manage_main?manage_tabs_message={msg}'
377377
REQUEST.RESPONSE.redirect(url)
378378

379-
@requestmethod('POST')
379+
@zpublish(methods='POST')
380380
def manage_pack(self, days=0, REQUEST=None):
381381
"""Pack the database"""
382382
if not isinstance(days, (int, float)):

src/App/DavLockManager.py

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from App.special_dtml import DTMLFile
2020
from OFS.Lockable import wl_isLocked
2121
from OFS.SimpleItem import Item
22+
from ZPublisher import zpublish
2223

2324

2425
class DavLockManager(Item, Implicit):
@@ -73,6 +74,7 @@ def unlockObjects(self, paths=[]):
7374
ob.wl_clearLocks()
7475

7576
@security.protected(webdav_manage_locks)
77+
@zpublish
7678
def manage_unlockObjects(self, paths=[], REQUEST=None):
7779
" Management screen action to unlock objects. "
7880
if paths:

src/App/FactoryDispatcher.py

+5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from Acquisition import aq_base
2626
from ExtensionClass import Base
2727
from OFS.metaconfigure import get_registered_packages
28+
from ZPublisher import zpublish
2829

2930

3031
def _product_packages():
@@ -45,6 +46,7 @@ def _product_packages():
4546
return _packages
4647

4748

49+
@zpublish
4850
class Product(Base):
4951
"""Model a non-persistent product wrapper.
5052
"""
@@ -68,6 +70,7 @@ def Destination(self):
6870
InitializeClass(Product)
6971

7072

73+
@zpublish
7174
class ProductDispatcher(Implicit):
7275
" "
7376
# Allow access to factory dispatchers
@@ -95,6 +98,7 @@ def __bobo_traverse__(self, REQUEST, name):
9598
return dispatcher.__of__(self)
9699

97100

101+
@zpublish
98102
class FactoryDispatcher(Implicit):
99103
"""Provide a namespace for product "methods"
100104
"""
@@ -157,6 +161,7 @@ def __getattr__(self, name):
157161
_owner = UnownableOwner
158162

159163
# Provide a replacement for manage_main that does a redirection:
164+
@zpublish
160165
def manage_main(trueself, self, REQUEST, update_menu=0):
161166
"""Implement a contents view by redirecting to the true view
162167
"""

src/App/Management.py

+4
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@
2929
from App.special_dtml import DTMLFile
3030
from ExtensionClass import Base
3131
from zope.interface import implementer
32+
from ZPublisher import zpublish
3233

3334

35+
@zpublish
3436
class Tabs(Base):
3537
"""Mix-in provides management folder tab support."""
3638

@@ -68,6 +70,7 @@ def filtered_manage_options(self, REQUEST=None):
6870

6971
manage_workspace__roles__ = ('Authenticated',)
7072

73+
@zpublish
7174
def manage_workspace(self, REQUEST):
7275
"""Dispatch to first interface in manage_options
7376
"""
@@ -181,6 +184,7 @@ def manage_page_header(self, *args, **kw):
181184
security.declarePublic('zope_copyright') # NOQA: D001
182185
zope_copyright = DTMLFile('dtml/copyright', globals())
183186

187+
@zpublish
184188
@security.public
185189
def manage_zmi_logout(self, REQUEST, RESPONSE):
186190
"""Logout current user"""

src/App/ProductContext.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
from App.FactoryDispatcher import FactoryDispatcher
2323
from OFS.ObjectManager import ObjectManager
2424
from zope.interface import implementedBy
25+
from ZPublisher import zpublish
26+
from ZPublisher import zpublish_marked
27+
from ZPublisher import zpublish_wrap
2528

2629

2730
if not hasattr(Products, 'meta_types'):
@@ -99,6 +102,9 @@ class will be registered.
99102
productObject = self.__prod
100103
pid = productObject.id
101104

105+
if instance_class is not None and not zpublish_marked(instance_class):
106+
zpublish(instance_class)
107+
102108
if permissions:
103109
if isinstance(permissions, str): # You goofed it!
104110
raise TypeError(
@@ -130,19 +136,21 @@ class will be registered.
130136
for method in legacy:
131137
if isinstance(method, tuple):
132138
name, method = method
139+
mname = method.__name__
133140
aliased = 1
134141
else:
135142
name = method.__name__
136143
aliased = 0
137144
if name not in OM.__dict__:
145+
method = zpublish_wrap(method)
138146
setattr(OM, name, method)
139147
setattr(OM, name + '__roles__', pr)
140148
if aliased:
141149
# Set the unaliased method name and its roles
142150
# to avoid security holes. XXX: All "legacy"
143151
# methods need to be eliminated.
144-
setattr(OM, method.__name__, method)
145-
setattr(OM, method.__name__ + '__roles__', pr)
152+
setattr(OM, mname, method)
153+
setattr(OM, mname + '__roles__', pr)
146154

147155
if isinstance(initial, tuple):
148156
name, initial = initial
@@ -186,7 +194,7 @@ class __FactoryDispatcher__(FactoryDispatcher):
186194
'container_filter': container_filter
187195
},)
188196

189-
m[name] = initial
197+
m[name] = zpublish_wrap(initial)
190198
m[name + '__roles__'] = pr
191199

192200
for method in constructors[1:]:
@@ -195,7 +203,7 @@ class __FactoryDispatcher__(FactoryDispatcher):
195203
else:
196204
name = os.path.split(method.__name__)[-1]
197205
if name not in productObject.__dict__:
198-
m[name] = method
206+
m[name] = zpublish_wrap(method)
199207
m[name + '__roles__'] = pr
200208

201209
def getApplication(self):

src/App/Undo.py

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from App.Management import Tabs
2424
from App.special_dtml import DTMLFile
2525
from DateTime.DateTime import DateTime
26+
from ZPublisher import zpublish
2627

2728

2829
class UndoSupport(Tabs, Implicit):
@@ -98,6 +99,7 @@ def undoable_transactions(self, first_transaction=None,
9899
return r
99100

100101
@security.protected(undo_changes)
102+
@zpublish
101103
def manage_undo_transactions(self, transaction_info=(), REQUEST=None):
102104
"""
103105
"""

src/App/special_dtml.py

+3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from DocumentTemplate.DT_String import DTReturn
3434
from DocumentTemplate.DT_String import _marker
3535
from Shared.DC.Scripts.Bindings import Bindings
36+
from ZPublisher import zpublish
3637

3738

3839
LOG = getLogger('special_dtml')
@@ -46,10 +47,12 @@ class Code:
4647
pass
4748

4849

50+
@zpublish
4951
class HTML(DocumentTemplate.HTML, Persistence.Persistent):
5052
"Persistent HTML Document Templates"
5153

5254

55+
@zpublish
5356
class ClassicHTMLFile(DocumentTemplate.HTMLFile, MethodObject.Method):
5457
"Persistent HTML Document Templates read from files"
5558

src/OFS/Application.py

+4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from zExceptions import Forbidden
4040
from zExceptions import Redirect as RedirectException
4141
from zope.interface import implementer
42+
from ZPublisher import zpublish
4243

4344
from . import Folder
4445
from . import misc_
@@ -107,6 +108,7 @@ def Redirect(self, destination, URL1):
107108

108109
ZopeRedirect = Redirect
109110

111+
@zpublish
110112
@security.protected(view_management_screens)
111113
def getZMIMainFrameTarget(self, REQUEST):
112114
"""Utility method to get the right hand side ZMI frame source URL
@@ -200,11 +202,13 @@ def ZopeVersion(self, major=False):
200202

201203
return version
202204

205+
@zpublish
203206
def DELETE(self, REQUEST, RESPONSE):
204207
"""Delete a resource object."""
205208
self.dav__init(REQUEST, RESPONSE)
206209
raise Forbidden('This resource cannot be deleted.')
207210

211+
@zpublish
208212
def MOVE(self, REQUEST, RESPONSE):
209213
"""Move a resource to a new location."""
210214
self.dav__init(REQUEST, RESPONSE)

0 commit comments

Comments
 (0)