I have a form with a SelectFieldWidget, that is currently using a static vocabularly, which is basically this:
from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
primary_contacts = SimpleVocabulary([
SimpleTerm( unicode(token), title=unicode(token.upper()), token=token ) for token in [
'one','two','three','four','five','six','seven','eight','nine','ten',
]
])
The vocabulary is assigned to the field in the form schema:
form.widget( primary_contact_person=SelectFieldWidget )
primary_contact_person = schema.List(
title = u'Nominate Primary Contact',
required = False,
value_type = schema.Choice(
vocabulary=primary_contacts,
)
)
The schema is then serialized using plone.supermodel & then deserialized when needed by the form (this is for another requirement).
The form is using a custom, handwritten template, and I'm in the process of adding the tal statements to generate the select field options. I had thought I could do this through referencing the widgets on the form, but when I do that I hit a problem:
(Pdb) self # break point in form
<Products.Five.metaclass.edit_metadata object at 0xc1ce450>
(Pdb) select = self.widgets['primary_contact_person']
(Pdb) first = [t for t in select.terms][0]
(Pdb) first.token
'one'
(Pdb) first.value
u'one'
(Pdb) first.title
(Pdb)
The title is None on the term when it's accessed through the widget. I've tried looking it up through the vocabulary:
(Pdb) select.terms.getTermByToken('one').title
(Pdb)
But again, it's None. However, it is there for terms in the original vocabulary object:
(Pdb) from my.package import primary_contacts
(Pdb) [t for t in primary_contacts][0].title
u'ONE'
So while I could use the source vocab object directly to provide the values the template needs, the plan is for this vocabulary to eventually be dynamic, at which point I would expect I'd need to interrogate the widget itself.
What am I doing wrong here, why is title not being defined?
The problem was with plone.supermodel. I should have mentioned more clearly that I'm using the serialized schema to produce the form, and I apologise for this.
Basically, plone.supermodel provides an export/import process, which can only deal with simple lists of values.
# line 263 in plone.supermodel.exportimport
term = SimpleTerm(token = encoded, value = value, title = value)
The solution was to use named vocabularies, which serializes the reference to the vocabulary rather than the vocabulary itself.
Sorry again for the lack of information that made this harder to debug.
Related
I'm running Python 3.8.10 and have a dataclass with some attributes. Some of them have a default value but are not part of the constructor.
Attributes which have the init value set to False are not showing up in the object dict.
Is this the expected behavior? How can I force these attributes to show up in vars?
from dataclasses import dataclass, field
#dataclass
class Book:
name: str = field(default="", init=False)
author: str = field(default="", init=True)
b = Book()
b
> Book(name='', author='')
b.name
> ''
b.author
> ''
## name does not show up here
vars(b)
> {'author': ''}
b.__dict__
> {'author': ''}
I just hit the same issue. Some experimenting showed me that these variables show up in vars after they have been set.
I added the following to my __post_init__() and then they showed up in vars
def __post_init__(self):
for field in dataclasses.fields(self):
#Ensure that all dataclass fields show up in vars
if field.name not in vars(self):
setattr(self, field.name, getattr(self, field.name))
The docs also mention some other weirdness with using init=False. https://docs.python.org/3/library/dataclasses.html#dataclasses.replace
Be forewarned about how init=False fields work during a call to
replace(). They are not copied from the source object, but rather are
initialized in post_init(), if they’re initialized at all. It is
expected that init=False fields will be rarely and judiciously used.
Definitely check your assumptions when it comes to this feature I think.
I have the following code which is meant to programmatically assign relation values to a custom content type.
publications = # some data
catalog = getToolByName(context, 'portal_catalog')
for pub in publications:
if pub['custom_id']:
results = catalog(custom_id=pub['custom_id'])
if len(results) == 1:
obj = results[0].getObject()
measures = []
for m in pub['measure']:
if m in context.objectIds():
m_id = intids.getId(context[m])
relation = RelationValue(m_id)
measures.append(relation)
obj.measures = measures
obj.reindexObject()
notify(ObjectModifiedEvent(obj))
Snippet of schema for custom content type
measures = RelationList(
title=_(u'Measure(s)'),
required=False,
value_type=RelationChoice(title=_(u'Measure'),
source=ObjPathSourceBinder(object_provides='foo.bar.interfaces.measure.IMeasure')),
)
When I run my script everything looks good. The problem is when my template for the custom content tries to call "pub/from_object/absolute_url" the value is blank - only after a restart. Interestingly, I can get other attributes of pub/from_object after a restart, just not it's URL.
from_object retrieves the referencing object from the relation catalog, but doesn't put the object back in its proper Acquisition chain. See http://docs.plone.org/external/plone.app.dexterity/docs/advanced/references.html#back-references for a way to do it that should work.
I have a dexterity content type and I have been following along with the schema-driven types tutorial from the Dexterity Manual (http://docs.plone.org/external/plone.app.dexterity/docs/schema-driven-types.html#the-schema) and I am trying to change the default value of a field.
The value being returned is based on a portal catalog search. The field is an int type in which I want it equal to the highest number + 1 of the field in question. Because this is a field I want to hide, I figured maybe it would be possible to set the default value through returning a value from a function
value into the default parameter of a field.
Here is my Interface:
class ISupplier(model.Schema):
"""
Supplier of an asset or services
"""
supplier_id = schema.Int(
title=_(u"Supplier ID"),
description=_(u"ID that links to the database"),
required=True,
default=newID
)
...
Here is my function that I am trying to use to return a value. It is outside of classes.
#grok.provider(IContextSourceBinder)
def newID(context):
limit = 1
catalog = getToolByName(context, 'portal_catalog')
result = catalog.searchResults(portal_type='gpcl.supplier.supplier',
sort_on='supplier_id',
sort_order='descending',
sort_limit=limit
)[:1]
return result.supplier_id + 1
The reason I thought it'd be possible to do something like this is because in a choice type field I set the source equal to a value returned from a function:
form.widget(supplierType=CheckBoxFieldWidget)
supplierType = schema.List(title=u'Types',
value_type=schema.Choice(source=supplierTypes),
required=True
supplierTypes, the function, starts off like this:
#grok.provider(IContextSourceBinder)
def supplierTypes(context):
"""
"""
...
I did try using default_value:
#form.default_value(field=ISupplier['supplier_id'])
def supplierIDDefaultValue(data):
defaultID = newID
return defaultID
This did not work unfortunately. I believe I actually may be having a misunderstanding altogether. If I could have any help, I would greatly appreciate it.
In one of my Plone sites, I have a few dexterity models that I use to generate letters. The models are: "Model" (the base content of the letter), "Contact" (that contains the contact information, such as name, address etc) and "Merge" (which is a Model object rendered, in which we substitute some parts of the model with the recipients information).
The schema of the "Merge" object is the following:
class IMergeSchema(form.Schema):
"""
"""
title = schema.TextLine(
title=_p(u"Title"),
)
form.widget(text='plone.app.z3cform.wysiwyg.WysiwygFieldWidget')
text = schema.Text(
title=_p(u"Text"),
required=False,
)
form.widget(recipients=MultiContentTreeFieldWidget)
recipients = schema.List(
title=_('label_recipients',
default='Recipients'),
value_type=schema.Choice(
title=_('label_recipients',
default='Recipients'),
# Note that when you change the source, a plone.reload is
# not enough, as the source gets initialized on startup.
source=UUIDSourceBinder(portal_type='Contact')),
)
form.widget(model=ContentTreeFieldWidget)
form.mode(model='display')
model = schema.Choice(
title=_('label_model',
default='Model'),
source=UUIDSourceBinder(portal_type='Model'),
)
When creating a new "Merge" object, I want to have the "recipients" fields be preset with all contacts available in the folder where the new object is created.
I followed Martin Aspelli's guide to add a default value for a field: http://plone.org/products/dexterity/documentation/manual/developer-manual/reference/default-value-validator-adaptors
It works fine for text input fields, but I can't have it working for the "recipients" field. The method to generate the default values is the following (with some debug info with ugly print, but they'll be removed later ;) ):
#form.default_value(field=IMergeSchema['recipients'])
def all_recipients(data):
contacts = [x for x in data.context.contentValues()
if IContact.providedBy(x)]
paths = [u'/'.join(c.getPhysicalPath()) for c in contacts]
uids = [IUUID(c, None) for c in contacts]
print 'Contacts: %s' % contacts
print 'Paths: %s' % paths
print 'UIDs: %s' % uids
return paths
I tried to return the objects directly, their relative path (in the add view, when accessing "self.widgets['recipients'].value", I get this type of data) their UIDs but none of the solution as any effect.
I also tried to return tuples instead of lists or even generators, but still no effect at all.
The method is called for sure, as I see traces in the instance log.
I think you need to get the "int_id" of the related content. That's how dexterity relation fields store relation info::
from zope.component import getUtility
from zope.intid.interfaces import IIntIds
#form.default_value(field=IMergeSchema['recipients'])
def all_recipients(data):
contacts = [x for x in data.context.contentValues()
if IContact.providedBy(x)]
intids = getUtility(IIntIds)
# The following gets the int_id of the object and turns it into
# RelationValue
values = [RelationValue(intids.getId(c)) for c in contacts]
print 'Contacts: %s' % contacts
print 'Values: %s' % values
return values
I'm working on a form with Formlib that looks like this:
from zope.schema import Choice, Float, Int, Date, TextLine
from Products.Five.formlib.formbase import PageForm
class ISimuladorForm(Interface):
"""
Zope Interface for the financial simulator for sofomanec.
"""
start_date = Date(title=_(u'Start Date'),
description=_(u'Loan start date.'),
required=False)
.
.
.
class SimuladorForm(PageForm):
form_fields = form.FormFields(ISimuladorForm)
The default input format for start_date is "mm/dd/yy", but users need to input the start_date in this format: "dd/mm/yy".
How do I change the default Date format for this Interface/Schema/Form?
You can use the DateI18nWidget instead of the default DateWidget.
It takes a displayStyle attribute that controls the formatting of the value, and it'll use the request locale to format the date. displayStyle must be one of 'full', 'long', 'medium', 'short', or None and refers to the date formats defined in zope.i18n; the default is None, which I think means 'short' but this is unclear from the code.
The exact formatting is taken from the request locale, which in turn is based on the language set for the Plone site by the portal_languages tool. Thus setting the language of the site also determines what date formats the DateI18nWidget will use; these are defined in the zope.i18n package in the locales/data directory, in a set of XML files (look for the <dateFormats> element).
If this isn't satisfactory then you'll have to create a custom browser widget. Your best bet is to subclass the DateWidget yourself and provide a new _toFormValue method to format the dates the way you want.
This might be helpful to add a custom date widget to your formlib form:
http://plone.org/documentation/manual/developer-manual/forms/using-zope.formlib/customizing-the-template-and-the-widgets
I suggest to write your own date widget by deriving from one of the existing
date widget classes:
http://svn.zope.org/zope.formlib/trunk/src/zope/formlib/textwidgets.py?rev=113031&view=markup
A custom conversion of the date format using the
_toFieldValue()
_fromFieldValue()
hooks is pretty easy...look at the existing code.
This is what I did:
from zope.app.form.browser import DateI18nWidget
from zope.i18n.format import DateTimeParseError
from zope.app.form.interfaces import ConversionError
class MyDateI18nWidget(DateI18nWidget):
displayStyle = None
def _toFieldValue(self, input):
if input == self._missing:
return self.context.missing_value
else:
try:
formatter = self.request.locale.dates.getFormatter(
self._category, (self.displayStyle or None))
return formatter.parse(input.lower())
except (DateTimeParseError, ValueError), v:
raise ConversionError(_("Invalid datetime data"),
"%s (%r)" % (v, input))
class SimuladorForm(PageForm):
...
form_fields['start_date'].custom_widget = MyDateI18nWidget