Skip to content

Commit

Permalink
Introduce ZPublisher.zpublish decorator to explicitly control zpubl…
Browse files Browse the repository at this point in the history
…ishabilty
  • Loading branch information
d-maurer committed Feb 17, 2024
1 parent 9d00e1b commit 74593fa
Show file tree
Hide file tree
Showing 32 changed files with 383 additions and 39 deletions.
29 changes: 28 additions & 1 deletion docs/zdgbook/ObjectPublishing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,17 @@ Publishable Object Requirements
-------------------------------

Zope has few restrictions on publishable objects. The basic rule is
that the object must have a doc string. This requirement goes for
that the object must have been marked as zpublishable. This requirement goes for
methods, too.
An object or method is marked as zpublishable by decorating
its class (or a base class) or underlying function, respectively,
with the ``Zpublisher.zpublish`` decorator.
For backward compatibility, the existence of a docstring, too,
marks an object or method as zpublishable; but this will be removed in
the future.
If you decorate a method or class with ``zpublsh(False)``,
you explicitly mark it or its instances, respectively, as not
zpublishable.

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

Consider this example::

from ZPublisher import zpublish

@zpublish
class Example:
"""example class"""

@zpublish
def one(self):
"""render page one"""
return """<html>
Expand All @@ -282,6 +295,7 @@ Consider this example::
</body>
</html>"""

@zpublish
def two(self):
"""render page two"""
return """<html>
Expand All @@ -298,9 +312,11 @@ the URL, relative links returned by ``index_html`` won't work right.

For example::

@zpublish
class Example:
"""example class""""

@zpublish
def index_html(self):
"""render default view"""
return """<html>
Expand Down Expand Up @@ -375,7 +391,9 @@ acquisition, you can use traversal to walk over acquired objects.
Consider the the following object hierarchy::

from Acquisition import Implicit
from ZPublisher import zpublish

@zpublish
class Node(Implicit):
...

Expand All @@ -401,20 +419,27 @@ method that your acquire from outside your container.
For example::

from Acquisition import Implicit
from ZPublisher import zpublish

@zpublish
class Basket(Implicit):
...
@zpublish
def number_of_items(self):
"""Returns the number of contained items."""
...

@zpublish
class Vegetable(Implicit):
...
@zpublish
def texture(self):
"""Returns the texture of the vegetable."""

@zpublish
class Fruit(Implicit):
...
@zpublish
def color(self):
"""Returns the color of the fruit."""

Expand Down Expand Up @@ -582,6 +607,7 @@ called from the web.

Consider this function::

@zpublish
def greet(name):
"""Greet someone by name."""
return "Hello, %s!" % name
Expand Down Expand Up @@ -663,6 +689,7 @@ Argument Conversion
The publisher supports argument conversion. For example consider this
function::

@zpublish
def one_third(number):
"""returns the number divided by three"""
return number / 3.0
Expand Down
4 changes: 4 additions & 0 deletions src/App/FactoryDispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from Acquisition import aq_base
from ExtensionClass import Base
from OFS.metaconfigure import get_registered_packages
from ZPublisher import zpublish


def _product_packages():
Expand All @@ -45,6 +46,7 @@ def _product_packages():
return _packages


@zpublish
class Product(Base):
"""Model a non-persistent product wrapper.
"""
Expand All @@ -68,6 +70,7 @@ def Destination(self):
InitializeClass(Product)


@zpublish
class ProductDispatcher(Implicit):
" "
# Allow access to factory dispatchers
Expand Down Expand Up @@ -95,6 +98,7 @@ def __bobo_traverse__(self, REQUEST, name):
return dispatcher.__of__(self)


@zpublish
class FactoryDispatcher(Implicit):
"""Provide a namespace for product "methods"
"""
Expand Down
4 changes: 4 additions & 0 deletions src/App/Management.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@
from App.special_dtml import DTMLFile
from ExtensionClass import Base
from zope.interface import implementer
from ZPublisher import zpublish


@zpublish
class Tabs(Base):
"""Mix-in provides management folder tab support."""

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

manage_workspace__roles__ = ('Authenticated',)

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

@zpublish
@security.public
def manage_zmi_logout(self, REQUEST, RESPONSE):
"""Logout current user"""
Expand Down
12 changes: 9 additions & 3 deletions src/App/ProductContext.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from App.FactoryDispatcher import FactoryDispatcher
from OFS.ObjectManager import ObjectManager
from zope.interface import implementedBy
from ZPublisher import zpublish


if not hasattr(Products, 'meta_types'):
Expand Down Expand Up @@ -99,6 +100,9 @@ class will be registered.
productObject = self.__prod
pid = productObject.id

if instance_class is not None:
zpublish(instance_class)

if permissions:
if isinstance(permissions, str): # You goofed it!
raise TypeError(
Expand Down Expand Up @@ -130,19 +134,21 @@ class will be registered.
for method in legacy:
if isinstance(method, tuple):
name, method = method
mname = method.__name__
aliased = 1
else:
name = method.__name__
aliased = 0
if name not in OM.__dict__:
method = zpublish(True, method)
setattr(OM, name, method)
setattr(OM, name + '__roles__', pr)
if aliased:
# Set the unaliased method name and its roles
# to avoid security holes. XXX: All "legacy"
# methods need to be eliminated.
setattr(OM, method.__name__, method)
setattr(OM, method.__name__ + '__roles__', pr)
setattr(OM, mname, method)
setattr(OM, mname + '__roles__', pr)

if isinstance(initial, tuple):
name, initial = initial
Expand Down Expand Up @@ -195,7 +201,7 @@ class __FactoryDispatcher__(FactoryDispatcher):
else:
name = os.path.split(method.__name__)[-1]
if name not in productObject.__dict__:
m[name] = method
m[name] = zpublish(True, method)
m[name + '__roles__'] = pr

def getApplication(self):
Expand Down
3 changes: 3 additions & 0 deletions src/App/special_dtml.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from DocumentTemplate.DT_String import DTReturn
from DocumentTemplate.DT_String import _marker
from Shared.DC.Scripts.Bindings import Bindings
from ZPublisher import zpublish


LOG = getLogger('special_dtml')
Expand All @@ -46,10 +47,12 @@ class Code:
pass


@zpublish
class HTML(DocumentTemplate.HTML, Persistence.Persistent):
"Persistent HTML Document Templates"


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

Expand Down
4 changes: 4 additions & 0 deletions src/OFS/Application.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from zExceptions import Forbidden
from zExceptions import Redirect as RedirectException
from zope.interface import implementer
from ZPublisher import zpublish

from . import Folder
from . import misc_
Expand Down Expand Up @@ -107,6 +108,7 @@ def Redirect(self, destination, URL1):

ZopeRedirect = Redirect

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

return version

@zpublish
def DELETE(self, REQUEST, RESPONSE):
"""Delete a resource object."""
self.dav__init(REQUEST, RESPONSE)
raise Forbidden('This resource cannot be deleted.')

@zpublish
def MOVE(self, REQUEST, RESPONSE):
"""Move a resource to a new location."""
self.dav__init(REQUEST, RESPONSE)
Expand Down
6 changes: 6 additions & 0 deletions src/OFS/DTMLMethod.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from zExceptions import ResourceLockedError
from zExceptions.TracebackSupplement import PathTracebackSupplement
from zope.contenttype import guess_content_type
from ZPublisher import zpublish
from ZPublisher.HTTPRequest import default_encoding
from ZPublisher.Iterators import IStreamIterator

Expand All @@ -51,6 +52,7 @@ class Code:
pass


@zpublish
class DTMLMethod(
PathReprProvider,
RestrictedDTML,
Expand Down Expand Up @@ -265,6 +267,7 @@ def get_size(self):
security.declareProtected(change_proxy_roles, 'manage_proxyForm') # NOQA: D001,E501
manage_proxyForm = DTMLFile('dtml/documentProxy', globals())

@zpublish
@security.protected(change_dtml_methods)
def manage_edit(self, data, title, SUBMIT='Change', REQUEST=None):
""" Replace contents with 'data', title with 'title'.
Expand Down Expand Up @@ -293,6 +296,7 @@ def manage_edit(self, data, title, SUBMIT='Change', REQUEST=None):
message = "Saved changes."
return self.manage_main(self, REQUEST, manage_tabs_message=message)

@zpublish
@security.protected(change_dtml_methods)
def manage_upload(self, file='', REQUEST=None):
""" Replace the contents of the document with the text in 'file'.
Expand Down Expand Up @@ -336,6 +340,7 @@ def _validateProxy(self, roles=None):
'do not have proxy roles.\n<!--%s, %s-->' % (
self.__name__, user, roles))

@zpublish
@security.protected(change_proxy_roles)
@requestmethod('POST')
def manage_proxy(self, roles=(), REQUEST=None):
Expand Down Expand Up @@ -363,6 +368,7 @@ def document_src(self, REQUEST=None, RESPONSE=None):
RESPONSE.setHeader('Content-Type', 'text/plain')
return self.read()

@zpublish
@security.protected(change_dtml_methods)
def PUT(self, REQUEST, RESPONSE):
""" Handle HTTP PUT requests.
Expand Down
3 changes: 3 additions & 0 deletions src/OFS/FindSupport.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from ExtensionClass import Base
from OFS.interfaces import IFindSupport
from zope.interface import implementer
from ZPublisher import zpublish
from ZPublisher.HTTPRequest import default_encoding


Expand All @@ -52,6 +53,7 @@ class FindSupport(Base):
},
)

@zpublish
@security.protected(view_management_screens)
def ZopeFind(self, obj, obj_ids=None, obj_metatypes=None,
obj_searchterm=None, obj_expr=None,
Expand All @@ -69,6 +71,7 @@ def ZopeFind(self, obj, obj_ids=None, obj_metatypes=None,
pre=pre, apply_func=None, apply_path=''
)

@zpublish
@security.protected(view_management_screens)
def ZopeFindAndApply(self, obj, obj_ids=None, obj_metatypes=None,
obj_searchterm=None, obj_expr=None,
Expand Down
2 changes: 2 additions & 0 deletions src/OFS/Folder.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from OFS.SimpleItem import PathReprProvider
from webdav.Collection import Collection
from zope.interface import implementer
from ZPublisher import zpublish


manage_addFolderForm = DTMLFile('dtml/folderAdd', globals())
Expand All @@ -50,6 +51,7 @@ def manage_addFolder(
return self.manage_main(self, REQUEST)


@zpublish
@implementer(IFolder)
class Folder(
PathReprProvider,
Expand Down
5 changes: 5 additions & 0 deletions src/OFS/History.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from DateTime.DateTime import DateTime
from ExtensionClass import Base
from zExceptions import Redirect
from ZPublisher import zpublish


view_history = 'View History'
Expand Down Expand Up @@ -122,6 +123,7 @@ class Historical(Base):
HistoryBatchSize=20,
first_transaction=0, last_transaction=20)

@zpublish
@security.protected(view_history)
def manage_change_history(self):
first = 0
Expand All @@ -146,6 +148,7 @@ def manage_change_history(self):
def manage_beforeHistoryCopy(self):
pass

@zpublish
def manage_historyCopy(self, keys=[], RESPONSE=None, URL1=None):
""" Copy a selected revision to the present """
if not keys:
Expand Down Expand Up @@ -176,6 +179,7 @@ def manage_afterHistoryCopy(self):
_manage_historyComparePage = DTMLFile(
'dtml/historyCompare', globals(), management_view='History')

@zpublish
@security.protected(view_history)
def manage_historyCompare(self, rev1, rev2, REQUEST,
historyComparisonResults=''):
Expand All @@ -186,6 +190,7 @@ def manage_historyCompare(self, rev1, rev2, REQUEST,
dt1=dt1, dt2=dt2,
historyComparisonResults=historyComparisonResults)

@zpublish
@security.protected(view_history)
def manage_historicalComparison(self, REQUEST, keys=[]):
""" Compare two selected revisions """
Expand Down
Loading

0 comments on commit 74593fa

Please sign in to comment.