Named adapters with grok and Plone? - plone

Is it possible to register named adapters with five.grok.
The ZCML equivalent would be:
<adapter name="description" factory=".indexers.mytype_description" />

The grok equivalent is
from five import grok
grok.global_adapter(mytype_description, name='description')

Related

Getting the interface of a Zope 3 browser layer knowing only it's name

Having a Plone skin interface registered as follow:
<interface
interface=".interfaces.IThemeSpecific"
type="zope.publisher.interfaces.browser.IBrowserSkinType"
name="My Theme Name"
/>
What is the simplest way to get the interface (my.app.browser.interfaces.IThemeSpecific) knowing the name ("My Theme Name")?
Probably this:
>>> from zope.component import getUtility
>>> from zope.publisher.interfaces.browser import IBrowserSkinType
>>> getUtility(IBrowserSkinType, name="Old Plone 3 Custom Theme")
<InterfaceClass plonetheme.classic.browser.interfaces.IThemeSpecific>
If you have a buildout where this plone skin is installed, adding the collective.recipe.omelette to it will help you later being able to grep on all your packages for it.
So something like this will work:
grep -R --include=*.zcml 'My Theme Name' parts/omelette

Extending the personal preferences page

I'm attempting to make an add-on that, among other things, adds another option to the personal preferences page. I've tried to piece together how to do this properly from various guides on loosely related topics, but I've had no luck. I'm just getting into Plone development, and a lot of this is somewhat foreign to me, so please be kind :)
I'm doing all this in Plone 4.3
When I have the add-on enabled on the site, it doesn't give me any errors, but the extra field isn't included on the preferences page.
So far, I've got something like this, ignore the odd naming scheme. Again, this was pieced together from other guides, and I wanted to get it working before I refactored.
userdataschema.py
from plone.app.users.browser.personalpreferences import IPersonalPreferences
from zope.interface import Interface
from zope import schema
class IEnhancedUserDataSchema(IPersonalPreferences):
""" Use all the fields from the default user data schema, and add various
extra fields.
"""
buttonsEnabled = schema.Bool(title=u'Transition button widget.',
default=True,
description=u'Uncheck to remove the transition button box from ALL pages.',
required=False
)
adapter.py:
from plone.app.users.browser.personalpreferences import PersonalPreferencesPanelAdapter
from app.statebuttons.userdataschema import IEnhancedUserDataSchema
from zope.interface import implements
class EnhancedUserDataPanelAdapter(PersonalPreferencesPanelAdapter):
"""
"""
implements(IEnhancedUserDataSchema)
def get_buttonEnabled(self):
return self.context.getProperty('buttonsEnabled', '')
def set_buttonsEnabled(self, value):
return self.context.setMemberProperties({'buttonsEnabled': value})
buttonsEnabled = property(get_buttonEnabled, set_buttonsEnabled)
overrides.zcml:
<configure
xmlns="http://namespaces.zope.org/zope"
i18n_domain="collective.examples.userdata">
<adapter
provides=".userdataschema.IEnhancedUserDataSchema"
for="Products.CMFCore.interfaces.ISiteRoot"
factory=".adapter.EnhancedUserDataPanelAdapter"
/>
</configure>
If anyone could give me some input on what I'm doing wrong, that would be awesome. If I'm way off the mark, I'd appreciate some input on where to go next.
You can make your new field show up on ##personal-preferences by adding this to your existing code:
browser/configure.zcml
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<browser:page
for="plone.app.layout.navigation.interfaces.INavigationRoot"
name="personal-preferences"
class=".personalpreferences.CustomPersonalPreferencesPanel"
permission="cmf.SetOwnProperties"
layer="..interfaces.IAppThemeLayer"
/>
</configure>
browser/personalpreferences.py
from plone.app.users.browser.personalpreferences import PersonalPreferencesPanel
from plone.app.users.browser.personalpreferences import LanguageWidget
from plone.app.users.browser.personalpreferences import WysiwygEditorWidget
from zope.formlib import form
from app.statebuttons.userdataschema import IEnhancedUserDataSchema
class CustomPersonalPreferencesPanel(PersonalPreferencesPanel):
form_fields = form.FormFields(IEnhancedUserDataSchema)
# Apply same widget overrides as in the base class
form_fields['language'].custom_widget = LanguageWidget
form_fields['wysiwyg_editor'].custom_widget = WysiwygEditorWidget
Note that you'll need a browser layer IAppThemeLayer registered in profiles/default/browserlayer.xml, and to define storage for your field in profiles/default/memberdata_properties.xml.
You can see why this is necessary in plone.app.users.browser.personalpreferences. IPersonalPreferences is looked up as follows:
class PersonalPreferencesPanel(AccountPanelForm):
...
form_fields = form.FormFields(IPersonalPreferences)
...
The schema is bound to the IPersonalPreferences interface. You have to change the control panel in order to change the looked-up schema.
Instead you could use the IUserDataSchema approach described in https://pypi.python.org/pypi/collective.examples.userdata but as you've seen in order to override ##personal-information you need to use overrides.zcml which is an if-all-else-fails kind of override and not a very welcome citizen in a third party add-on.
Others may disagree but my personal preference (should I say my IPersonalPreference?) for this kind of problem is to create a dedicated form for the setting(s) and link to it from e.g. the personal tools menu.
overwriting personal-preferences should not be necessary.
just follow the documentation on https://pypi.python.org/pypi/plone.app.users
seems you're missing:
a userdatascheaprovider
from plone.app.users.userdataschema import IUserDataSchemaProvider
class UserDataSchemaProvider(object):
implements(IUserDataSchemaProvider)
def getSchema(self):
"""
"""
return IEnhancedUserDataSchema
the GS configuration for it in componentregistry.xml:
a registration of your new field in GS properties.xml to make the field show up on the registration page (optionally)
<object name="site_properties" meta_type="Plone Property Sheet">
<property name="user_registration_fields" type="lines" purge="False">
<element value="yourfield" />
</property>
</object>
to make your field show up in personal preferences follow the documentation on https://pypi.python.org/pypi/plone.app.users/1.1.5
section "How to update the personal information form"

Plone 4.3 - How to build a Form package using Zc3.form without Grok?

I am trying to build a form package for a Plone website. I am currently working with Plone 4.3. Before I was using Dexterity with five.grok and grok libraries. But after reading the Plone 4.3 migration and five.grok dependency section of this article: http://developer.plone.org/components/grok.html it appears that Plone developers are moving away from using grok all together.
So should I move away from using Grok and how would I go about doing so when all the current documentation is currently using Grok? Additionally I am developing from a Windows based machine.
First creating form without grok is not that hard and do not depends on your Operating System.
Creating a form is always the same. Here is how I proceed:
Some imports
from Products.Five.browser import BrowserView
from plone.autoform.form import AutoExtensibleForm
from plone.app.z3cform import layout
from zope import interface
from zope import schema
from zope import component
from z3c.form import form
from collective.my.i18n import _
Create a schema
class AddFormSchema(interface.Interface):
what = schema.Choice(
title=_(u"What"),
vocabulary="plone.app.vocabularies.UserFriendlyTypes"
)
where = schema.Choice(
title=u"Where",
vocabulary="collective.my.vocabulary.groups"
)
create a generic adapter to fill the form from anywhere
class AddFormAdapter(object):
interface.implements(AddFormSchema)
component.adapts(interface.Interface)
def __init__(self, context):
self.what = None
self.where = None
Then write the form
class AddForm(AutoExtensibleForm, form.Form):
schema = AddFormSchema
form_name = 'add_content'
Add a view
class AddButton(layout.FormWrapper):
"""Add button"""
form = AddForm
Now ZCML time this is the step you don't need when using grok:
<adapter factory=".my.AddFormAdapter"/>
<browser:page
for="*"
name="my.addbutton"
class=".my.AddButton"
template="addbutton.pt"
permission="zope2.View"
/>
Should you move from grok:
This is depending of what are you doing. For an addon I say Yes but for a project, it's up to you.
Grok is not parts of the already big Zope. So adding dependency is something that always should be done only if needed. Grok is an option so I have never used it.

Better way of tagging content types with a common interface

I want a viewlet to apply to the view of several content types in the same python egg. What I have been doing is applying the marker interface via browser/configure.zcml
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
i18n_domain="AnnualProgramModule.content">
<include package="plone.app.contentmenu" />
<class class="..content.programyear.ProgramYear">
<implements interface=".viewlets.IAnnualProgram" />
</class>
<class class="..content.institution.Institution">
<implements interface=".viewlets.IAnnualProgram" />
</class>
</configure>
And in my Grok-based template I have:
from zope.interface import Interface
from five import grok
from plone.app.layout.viewlets.interfaces import IAboveContentTitle
from AnnualProgramModule.content.interfaces import IInstitution
grok.templatedir('templates')
class IAnnualProgram(Interface):
"""Marker Interface for AnnualProgram content types
"""
class AnnualProgramViewlet(grok.Viewlet):
grok.require('zope2.View')
grok.viewletmanager(IAboveContentTitle)
grok.context(IAnnualProgram)
class InstitutionViewlet(grok.Viewlet):
grok.require('zope2.View')
grok.context(IInstitution)
grok.viewletmanager(IAboveContentTitle)
This works. But I am interested to know if there is a better way to do it.
No, what you are doing is actually the best way to approach this. Using marker interfaces is the way I would go about it in any case. :-)
The alternative would be for you to re-register the viewlet for the interfaces or classes of all the different content types instead:
<browser:viewlet
name="yourpackage.annualprogram"
for="..content.programyear.ProgramYear"
manager="plone.app.layout.viewlets.interfaces.IAboveContentTitle"
template="annualprogram.pt"
permission="zope2.View"
/>
<browser:viewlet
name="yourpackage.annualprogram"
for="..content.interfaces.IInstitution"
manager="plone.app.layout.viewlets.interfaces.IAboveContentTitle"
template="annualprogram.pt"
permission="zope2.View"
/>
but that is a whole lot more verbose.
As an alternative, this also works:
I added interfaces/annualprogram.py (I used paster to create my product). In it I have:
from zope.interface import Interface
class IAnnualProgram(Interface):
"""A common marker interface for AnnualProgram ContentTypes"""
Then in my institution.py I added:
from AnnualProgramModule.content.interfaces import IAnnualProgram
.....
class Institution(folder.ATFolder):
"""Institution Profile"""
implements(IInstitution, IAnnualProgram)
I then applied the same thing to the other content types that needed the IAnnualProgram.
This works but is not necessarily better.

Conditionally including Flex libraries (SWCs) in mxmlc/compc ant tasks

I have been struggling trying to figure out how to conditionally include Flex libraries in an ant build based on a property that is set on the command line. I have tried a number of approaches with the <condition/> task, but so far have not gotten it to work. Here is where I am currently.
I have an init target that includes condition tasks like this:
<condition property="automation.libs" value="automation.qtp">
<equals arg1="${automation}" arg2="qtp" casesensitive="false" trim="true"/>
</condition>
The purpose of this task is to set a property that determines the name of the patternset to be used when declaring the implicit fileset on a mxmlc or compc task. The pattern set referenced above is defined as:
<patternset id="automation.qtp">
<include name="automation*.swc"/>
<include name="qtp.swc"/>
</patternset>
The named patternset is then referenced by the mxmlc or compc task like this:
<compc>
<compiler.include-libraries dir="${FLEX_HOME}/frameworks/libs" append="true">
<patternset refid="${automation.libs}"/>
</compiler.include-libraries>
</compc>
This doesn't appear to work. At least the SWC size does not indicate that the additional automation libraries have been compiled in. I want to be able to specify a command line property that determine which patternset to use for various types of builds.
Does anyone have any ideas about how to accomplish this? Thanks!
If you can't get <patternset> to work correctly, you might want to take a look at the <if> <then> and <else> tasks provided by ant-contrib. We ended up doing something like this:
<target name = "build">
<if>
<equals arg1="automation.qtp" arg2="true"/>
<then>
<!--
- Build with QTP support.
-->
</then>
<else>
<!--
- Build without QTP support.
-->
</else>
</if>
</target>
There is some duplication of build logic between the if and else branch, but you can factor some of that out if you wrap <mxmlc> with a macrodef.
The mxmlc task supports loading configuration files <load-config filename="path/to/flex-config.xml" />. So, generate the config xml on the fly, by combining the echoxml task and if-then-else.
<echoxml file="path/to/flex-config.xml">
<flex-config>
<compiler>
<library-path append="true">
<path-element>${lib.qtp}</path-element>
</library-path>
</compiler>
</flex-config>
</echoxml>
If your needs are more complicated, you could even generate several xml configs and <load-config ... /> them all.
Personally, I find any logic very terse and ugly to write using Ant's conditions or if-then-else, XML is not a pretty language to use for programming. Luckily, it's possible to use more flexible approach - write a script to produce the config xml, before calling mxmlc. E.g. use the script task with your favorite scripting language
<script language="javascript">
<![CDATA[
// Create your XML dynamically here.
// Write that XML to an external file.
// Later, feed that file to mxmlc using `<load-config ... />`.
]]>
</script>

Resources