Plone and Dexterity: default values for "relation" field - plone

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

Related

Dataclass attributes with init=False do not show in vars

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.

Losing some z3c relation data on restart

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.

Plone Dexterity forms- Is it possible to set the default of a field equal to a value returned from a function in Add form?

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.

What's wrong with my filter query to figure out if a key is a member of a list(db.key) property?

I'm having trouble retrieving a filtered list from google app engine datastore (using python for server side). My data entity is defined as the following
class Course_Table(db.Model):
course_name = db.StringProperty(required=True, indexed=True)
....
head_tags_1=db.ListProperty(db.Key)
So the head_tags_1 property is a list of keys (which are the keys to a different entity called Headings_1).
I'm in the Handler below to spin through my Course_Table entity to filter the courses that have a particular Headings_1 key as a member of the head_tags_1 property. However, it doesn't seem like it is retrieving anything when I know there is data there to fulfill the request since it never displays the logs below when I go back to iterate through the results of my query (below). Any ideas of what I'm doing wrong?
def get(self,level_num,h_key):
path = []
if level_num == "1":
q = Course_Table.all().filter("head_tags_1 =", h_key)
for each in q:
logging.info('going through courses with this heading name')
logging.info("course name filtered is %s ", each.course_name)
MANY MANY THANK YOUS
I assume h_key is key of headings_1, since head_tags_1 is a list, I believe what you need is IN operator. https://developers.google.com/appengine/docs/python/datastore/queries
Note: your indentation inside the for loop does not seem correct.
My bad apparently '=' for list is already check membership. Using = to check membership is working for me, can you make sure h_key is really a datastore key class?
Here is my example, the first get produces result, where the 2nd one is not
import webapp2 from google.appengine.ext import db
class Greeting(db.Model):
author = db.StringProperty()
x = db.ListProperty(db.Key)
class C(db.Model): name = db.StringProperty()
class MainPage(webapp2.RequestHandler):
def get(self):
ckey = db.Key.from_path('C', 'abc')
dkey = db.Key.from_path('C', 'def')
ekey = db.Key.from_path('C', 'ghi')
Greeting(author='xxx', x=[ckey, dkey]).put()
x = Greeting.all().filter('x =',ckey).get()
self.response.write(x and x.author or 'None')
x = Greeting.all().filter('x =',ekey).get()
self.response.write(x and x.author or 'None')
app = webapp2.WSGIApplication([('/', MainPage)],
debug=True)

SimpleTerm title not being set

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.

Resources