Is there a way to extend Plone Dexterity's INameFromTitle behavior? - plone

The project I am working on uses Plone's awesome Dexterity plugin. A couple of my custom content types have very specific names that must be computed. The way I had originally accomplished this before was by adding plone.app.content.interfaces.INameFromTitle as a behavior in the object's generic setup entry, per the manual's directions:
<?xml version="1.0"?>
<object name="avrc.aeh.cycle" meta_type="Dexterity FTI">
...
<property name="schema">myproject.mytype.IMyType</property>
<property name="klass">plone.dexterity.content.Item</property>
...
<property name="behaviors">
<element value="plone.app.content.interfaces.INameFromTitle" />
</property>
...
</object>
Then I created an adapter that would provide INameFromTitle:
from five import grok
from zope.interface import Interface
import zope.schema
from plone.app.content.interfaces import INameFromTitle
class IMyType(Interface):
foo = zope.schema.TextLine(
title=u'Foo'
)
class NameForMyType(grok.Adapter):
grok.context(IMyType)
grok.provides(INameFromTitle)
#property
def title(self):
return u'Custom Title %s' % self.context.foo
This method is very similar to that suggested in this blog post:
http://davidjb.com/blog/2010/04/plone-and-dexterity-working-with-computed-fields
Unfortunately, this method stopped working after plone.app.dexterity beta and now my content items don't have their names assigned properly.
Would anyone happen to know how to extend Dexterity's INameFromTitle behavior for very specific naming use-cases?
Your help is much appreciated, thanks!

You could try the following.
in interfaces.py
from plone.app.content.interfaces import INameFromTitle
class INameForMyType(INameFromTitle):
def title():
"""Return a custom title"""
in behaviors.py
from myproject.mytype.interfaces import INameForMyType
class NameForMyType(object):
implements(INameForMyType)
def __init__(self, context):
self.context = context
#property
def title(self):
return u"Custom Title %s" % self.context.foo
I generally prefer defining my adapters using ZCML; in configure.zcml
<adapter for="myproject.mytype.IMyType"
factory=".behaviors.NameForMyType"
provides=".behaviors.INameForMyType"
/>
but you could probably also use a grok.global_adapter.

I did it with a behavior, by adapting to INameFromTitle
in behaviors.py
class INameFromBrandAndModel(Interface):
""" Interface to adapt to INameFromTitle """
class NameFromBrandAndModel(object):
""" Adapter to INameFromTitle """
implements(INameFromTitle)
adapts(INameFromBrandAndModel)
def __init__(self, context):
pass
def __new__(cls, context):
brand = context.brand
model = context.modeltype
title = u'%s %s' % (brand,model)
inst = super(NameFromBrandAndModel, cls).__new__(cls)
inst.title = title
context.setTitle(title)
return inst
in behaviors.zcml or configure.zcml
<plone:behavior
title="Name from brand and model"
description="generates a name from brand and model attributes"
for="plone.dexterity.interfaces.IDexterityContent"
provides=".behavios.INameFromBrandAndModel"
/>
<adapter factory=".behaviors.NameFromBrandAndModel" />
Then disable INameFromTitle behavior in profiles/types/your.contenttype.xml.
Voila. This integrates very well and shows a proper title in the default view and navigation. Removing context.setTitle(title) from the adapter would just leave us with a proper id, but no title set.
This does not change the title autocratically after editing. I had, so far, no success with overriding the klass property of my content types, as often suggested.
If you define the title attribute in your schema, like:
class IBike(form.Schema):
"""A bike
"""
title = schema.TextLine(
title = _(u'Title'),
required = False,
)
you can easily change the title later. Hiding the title field in the addForm should be done, to avoid misunderstandings.

Related

How to use LazyPaginator together with RequestConfig and Table classes?

I want to visualize a big, indexed table - big enough for count(*) to be too slow for my use case. Here's my views.py code:
import django_tables2
from projectname.models import Growth
def dashboard(request):
class StatisticsTable(django_tables2.Table):
class Meta:
model = Growth
table = StatisticsTable(Growth.objects.all())
django_tables2.RequestConfig(
request
).configure(table)
return render(request, "plain_table.html", {'table': table,
'title': 'Growth dashboard',
'search': None})
I was looking for examples on how to use django_tables2.paginators.LazyPaginator here and so far only found that I should pass it as a paginate= in django_tables2.RequestConfig, but I still get a regular paginator if I pass a reference to the class there. What's the proper use of this class in this context?
RequestConfig(paginate={"paginator_class": LazyPaginator}).configure(table)

Plone- In a dexterity.EditForm why is attempting to disable a widget causing a ConstraintNotSatisfied error?

I'm trying to disable a widget in a dexterity.EditForm, but I'm getting an error .
Here is a part of my interface class with the particular widget I want to disable
class IRestaurant(IPlace):
restaurant_code = schema.TextLine(title=_(u""),
required=False,
)
IPlace is a form.Schema that IRestaurant inherits from. (from plone.directives)
Here is the code I for the dexterity.EditForm class:
class Edit(dexterity.EditForm):
grok.context(IRestaurant)
def updateWidgets(self):
super(Edit, self).updateWidgets()
self.widgets['restaurant_code'].disabled = True
When I go to the edit form, I get an error:
ConstraintNotSatisfied: True
Why is this error occurring and how can I fix this?
Also, the version of Plone I am using is Plone 4.3.5.
Edit: When I tried printing the type of object that self.widgets['restaurant_code'].disabled was, it said it was a NoneType object.
You might have better luck using the mode property.
Try something like this:
from z3c.form.interfaces import HIDDEN_MODE
def updateWidgets(self):
super(Edit, self).updateWidgets()
self.widgets['restaurant_code'].mode = HIDDEN_MODE

Using dexterity behaviors to provide a method

I've been using a schema behavior with no problem, but I want to also have a method that provides some logic. Right now I have
class IMyFoo(form.Schema):
requester = schema.TextLine(title=_(u"Requestor"),
required=False,
)
def foo(self):
""" foo """
alsoProvides(IMyFoo, IFormFieldProvider)
And in the zcml
<plone:behavior
title="My behavior"
description="Some desc"
provides=".behaviors.IMyFoo"
for=".interfaces.ISomeInterface"
/>
I included IMyFoo in the behaviors section of a content type in portal_types. This gives me the schema but not that foo() method. So I tried to add a factory for it from reading http://plone-training.readthedocs.org/en/latest/behaviors2.html with the following code
class MyFoo(object):
def foo(self):
return 'bar'
And in the zcml
<plone:behavior
title="My behavior"
description="Some desc"
provides=".behaviors.IMyFoo"
factory=".behaviors.MyFoo"
for=".interfaces.ISomeInterface"
/>
But this didn't seem to make a difference, or at least, I don't know how to access that method. The closest I've been able to come is the following:
class IMyFoo(Interface):
""" Marker """
class MyFoo(object):
def __init__(self, context):
self.context = context
def foo(self):
return 'bar'
<adapter for=".interfaces.ISomeInterface"
factory=".behaviors.MyFoo"
provides=".behaviors.IMyFoo" />
I put IMyFoo in the behaviors attribute in the fti, and then call it by walking through all behaviors with something like
behavior = resolveDottedName(context.portal_types.getTypeInfo(context.portal_type).behaviors[-1]))
behavior(self).myfoo()
Surely going through the FTI like that is not the proper way to do it. But I'm at a loss at this point. In Archetypes I would just make a mixin class and inherit it with any content type I wanted to use it. I could do the same here, but my understanding is that behaviors are supposed to be a replacement for them, so I'd like to figure out how to use this preferred method.
As you've discovered, the schema class really is just an interface. It can't provide any methods. To provide more functionality, you need to connect your behavior interface to a factory class that adapts a dexterity object to provide your interface.
So, if your behaviors.py looks like this:
# your imports plus:
from plone.dexterity.interfaces import IDexterityContent
from zope.component import adapts
from zope.interface import implements
class IMyFoo(form.Schema):
requester = schema.TextLine(
title=_(u"Requestor"),
required=False,
)
def foo(self):
""" foo """
alsoProvides(IMyFoo, IFormFieldProvider)
class MyFoo(object):
implements(IMyFoo)
adapts(IDexterityContent)
def __init__(self, context):
self.context = context
def foo(self):
return 'bar'
Then your one-and-only zcml declaration would be:
<plone:behavior
title="My behavior name"
description="Behavior description"
provides=".behavior.IMyFoo"
factory=".behavior.MyFoo"
for="plone.dexterity.interfaces.IDexterityContent"
/>
And, you would reach your method with code like:
IMyFoo(myFooishObject).foo()
Note the use of IDexterityContent. You're creating a behavior that could be applied to any Dexterity content. So, the behavior adapter should be for that very general interface.

Canoncial way to retrieve the IntID of an content object based on Dexterity

The five.intid documentation states the following pattern for retrieving the IntID for
an object:
from five.intid import site
intids = site.get_intids(app.plone)
intid_obj = intids.getId(app.plone.restrictedTraverse('path/to/object')
Is this the canoncical way also in Plone or is there some helper/utility method
available wrapping the code above?
I always use:
from zope.component import getUtility
from zope.intid.interfaces import IIntIds
intid = getUtility(IIntIds).getId(object)
as the intid utility is registered with the local component manager.

z3c.forms dynamic sources provider returns empty dictionary as a context object

I'm using Plone 4.1.4 and I'm trying to get dynamic sources for a schema.Choice to work, I need to populate country list which in turn depends on the context object.
I'm using this example:
http://plone.org/products/dexterity/documentation/manual/developer-manual/advanced/vocabularies
For IContextSourceBinder, example, an empty dictionary instead of actual context object is returned:
from zope import interface
from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
from zope.schema.interfaces import IContextSourceBinder
import zope.schema
from z3c.form import form
class CountryGenerator(object):
interface.implements(IContextSourceBinder)
def __call__(self, context):
#context is == {}
import pdb; pdb.set_trace()
return SimpleVocabulary([
SimpleTerm(value="not_selected", title=_("Country Not Selected"))
])
class IStep(interface.Interface):
region = schema.Choice(title=_("Select your country"),
required=True,
source=CountryGenerator,
default="not_selected")
class Step(form.Form):
fields = field.Fields(IStep)
label = _("Step")
description = _("Select your country")
When debugging point is hit inside CountryGenerator.__call__() method and I examine context object, the latter turn out to be just an empty dictionary.
When I try to use named utility example in the article mentioned above, and the similar thing happens, context there is also {}.
Could anyone point me to what I might be doing wrong?
UPDATE
ZCML for the form wrapper that calls the form is
<browser:page
name="view"
for="Products.oldproduct.MyFolderishClass"
class=".file.RegionClass"
permission="zope2.View"
/>
Where RegionClass inherits from Form wrapper, could it be permission or traversal issue?
Because your source is a class, you need to instantiate it:
class IStep(interface.Interface):
region = schema.Choice(title=_("Select your country"),
required=True,
source=CountryGenerator(),
default="not_selected")
In certain circumstances, such as with using sub forms or complex form widgets (widget within a widget for list selections, etc), you need to follow the __parent__ pointers to a proper outer context for getting back to the Plone context.

Resources