Skip to content

Instantly share code, notes, and snippets.

@silviot
Last active January 30, 2023 22:58
Show Gist options
  • Save silviot/69d702e5254e6e6f11750d5249f518a6 to your computer and use it in GitHub Desktop.
Save silviot/69d702e5254e6e6f11750d5249f518a6 to your computer and use it in GitHub Desktop.
Removing grok

Removing grok

Grok is no longer supported in Plone 5.2 (the first Plone to run on Python 3). Every grok statement can be easily expressed with ZCML. This guide shows how to change code to register ZCA components using ZCML instead of grok.

Form from plone.directives.form

plone.directives depends on grok so we can't use it anymore.

Before:

myform.py

from five import grok
from plone.directives import form
from myproduct.interfaces import MyInterface

class MyForm(form.SchemaForm):
    grok.name('myform-name')
    grok.require('cmf.ManagePortal')
    grok.context(MyInterface)
    schema = IMyForm

After:

configure.zcml

<browser:page
  for="myproduct.interfaces.MyInterface"
  name="myform-name"
  permission="cmf.ManagePortal"
  class=".my_module.MyForm"
/>

myform.py

from z3c.form import form
from plone.autoform.form import AutoExtensibleForm


class MyForm(AutoExtensibleForm, form.Form):
    schema = IMyForm

Hints from plone.directives.form

Before:

from plone.directives import form
form.primary("fieldname")
form.widget(myField=MyWidget)

After:

from plone.supermodel import directives as form
form.primary("fieldname")
form.widget(myField=MyWidget)

grok.global_adapter

Before:

grok.global_adapter(function_name, name="my-function")

After:

<adapter factory=".modulename.function_name" name="my-function" />

grok.global_utility

Before:

After:

<utility
    provides=".interfaces.IUtilityInterface"
    name="utility-name"
    factory=".modulename.UtilityClass"
    />

grok.subscribe

Before:

@grok.subscribe(IMyContext, IObjectModifiedEvent)
def function_name(context, event):
    pass

After:

<subscriber
    for=".interfaces.IMyContext
        zope.lifecycleevent.IObjectModifiedEvent"
    handler=".modulename.function_name"
/>

grok.provider

Change it to zope.component.provider:

Before:

from five import grok

@grok.provider
def my_function(context):
    pass

After:

from zope.component import provider

@provider
def my_function(context):
    pass

grok.implements

Use the class decorator zope.interface.implementer instead.

Before:

import grok

class InheritedPubVocab(object):
    grok.implements(IContextSourceBinder)
    pass

After:

from zope.interface import implementer

@implementer(IContextSourceBinder)
class InheritedPubVocab(object):
    pass

grok.MultiAdapter

Remove inheritance from grok.MultiAdapter and register using ZCML.

Before:

class MyProvider(grok.MultiAdapter, SomeBaseClass):
    grok.name(u'provider-name')
    grok.adapts(IFirstArg, ISecondArg, IThirdArg)
    grok.provides(ISomething)

After:

class MyProvider(SomeBaseClass):
    pass
<adapter
    for="myproduct.interfaces.IFirstArg
         myproduct.interfaces.ISecondArg
         myproduct.interfaces.IThirdArg"
    provides=".interfaces.ISomething"
    factory=".modulename.MyProvider"
/>

grok.Adapter

Follow the instruction from grok.MultiAdapter. Make sure your class has an __init__ method. If it doesn't, add one that takes a single context parameter.

After:

class MyAdapter(object):
    def __init__(self, context):
        self.context = context

grok.View

Use Products.Five.browser.BrowserView instead, and change method render to __call. Also invoke the update method yourself in the __call__ method.

Before:

class MyView(grok.View):
    grok.context(IMyInterface)
    grok.require('cmf.ManagePortal')
    grok.name('view-name')
    def update(self):
        self.context.do_something()

    def render(self):
        return "The view was called!"

After:

from Products.Five.browser import BrowserView

class MyView(BrowserView):
    def ___call__(self):
        self.update()
        return "The view was called!"
<browser:page
    for=".interfaces.IMyInterface"
    name="view-name"
    permission="zope2.Public"
    class=".views.MyView"
/>

grok.View with associated template

Grok can also implicitly associate a PageTemplate with the view. In this case we need to name the specific template in python code

Before:

grok.templatedir('templates')

class MyGrokView(grok.View):
    grok.name(u'my-items')
    grok.context(IPloneSiteRoot)
    grok.require('zope2.View')
    def update(self):
        super(MyGrokView, self).update() # This needs to be removed
        self.do_stuff()

After:

from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile


class MyGrokView(BrowserView):

    index = ViewPageTemplateFile("templates/mygrokview.pt")

    def __call__(self):
        self.update()
        return self.index()

    def update(self):
        self.do_stuff()
<browser:page
    for="Products.CMFPlone.interfaces.IPloneSiteRoot"
    name="view-name"
    permission="zope2.Public"
    class=".views.MyGrokView"
/>
@alecpm
Copy link

alecpm commented Sep 14, 2022

One more – important – note: sometimes grok views don't have explicitly declared grok.template. In those cases there's an implicit template lookup in {lowercase_module}_templates/{lowercase_class_name}.pt. The directory may have been overridden by a module level grok.templatedir declaration. These implicit/missing template declarations must be made explicit when doing the conversion, either in zcml or by setting an index attribute and __call__ method on the class.

@sauzher
Copy link

sauzher commented Jan 30, 2023

one note on viewlets: grok.Viewlet templates access class method throught viewlet. hook. (es. tal:content="python:viewlet.classmethod()")
ZCML registered viewlet keeps view. as hook (es: tal:content="python:view.classmethod()")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment