Mosaic: Relation fields in mosaic layouts

Just to note, this is not a persistent tile. I am using standardtiles.field

The “top story “ relation field I have is part of the dexterity type’s schema, not a tile schema.

In mosaic, I am choosing to add a tile for a field. That’s why I was poking around in standardtiles.field call where I manually lookup the reference and hack an existingcontent tile together, and render that.

The endgame is a layout that supports changing the ‘top story’ on my main page without visiting the mosaic editor, only the ‘top story’ field through the main page’s properties

Do you want to render (on a Mosaic page) content (Title, description, image etc) and you want to choose which content by just referencing it?

If so, I would add a tile (or in fact, I would not add a tile since it is much work, so I would use themefragments for this).

Then I would look up the content by UID ( plone.api find content by uid) and just render it in a template (without any use of widgets).

Or if you are lazy, you could use pat-inject

Thanks for the endgame. This discussion went into too complex options. Customizing field tile is not trivial in that use case. (I may have merged, but not documented support for that.)

Probably the easiest solution is a new custom tile copied from the existingcontent tile code (or inherit it if that works) but reads the target content value from the dedicated field of your content type.

Exactly. I want a user to place "The Top Story" onto the page using mosaic (presentation) and then choose what the top story is through a reference field (data). Replace the word "Story" with "News Item" in the previous sentence for a more OOTB experience.

This is the path I was hacking with standardtiles.field: Dereference / lookup then render an existingcontent tile.

I really think it makes sense to treat a field tile 'that happens to be a reference' as an existingcontent tile and this is the basis of the whole idea.

Need to learn that - I have no clue.

I'm actually more of a fan of:


It's not trivial at all, but I'm willing to work on it if it fits the architecture. If it breaks architecture rules for standardtiles.field to dereference reference fields (repeating the work in z3cform) I'll drop the idea.

But I do feel that reference fields should 'just work'. If I reference content through a field, and drop that field into my layout, the field should render the referenced content. Otherwise, reference fields feel really broken in Mosaic.

This is what I do here (but another option would be to set a keyword on the item(s) itself and show those that have that keyword:

in your theme fragments xml file:

<model xmlns:form="http://namespaces.plone.org/supermodel/form"
  xmlns:i18n="http://xml.zope.org/namespaces/i18n"
  xmlns:indexer="http://namespaces.plone.org/supermodel/indexer"
  xmlns:lingua="http://namespaces.plone.org/supermodel/lingua"
  xmlns:marshal="http://namespaces.plone.org/supermodel/marshal"
  xmlns:security="http://namespaces.plone.org/supermodel/security"
  xmlns:users="http://namespaces.plone.org/supermodel/users"
  xmlns="http://namespaces.plone.org/supermodel/schema">
<schema>
  <field name="content_item" type="zope.schema.Choice">
        <description>Needs plone patternslib installed</description>
        <required>True</required>
        <title>Choose conten item to inject (load)</title>
        <source>collective.themefragments.tiles.CatalogSource</source>
    </field>
    <field name="load_div" type="zope.schema.Choice">
        <description/>
        <required>True</required>
        <title>Block to load</title>
        <values>
          <element>#content-core</element>
          <element>#content</element>
          <element>#main-container</element>
          <element>body</element>
        </values>
    </field>
  </schema>
</model>

in you theme fragments py file (I add one extra in case you need it for something):

def item_url(self):
    item = load_item(self)
    return item.getURL()

def load_item(self):
    item =  self.data['content_item']
    items =self.context.portal_catalog(UID=item)
    return items[0]

In your theme fragment pt file:

 <section tal:define="my_id view/id; item view/load_item; title item/Title;"
 class="pat-inject"
 id="fragment-${my_id}">
 <div id="pat-target-${my_id}">
   <a href="${view/item_url}"
     class="pat-inject" 
     data-pat-inject="target: #pat-target-${my_id}; 
     trigger: autoload; 
     source: ${view/data/load_div}">Loading…</a>
 </div>

I need my own blog :blush:

This got done today. Took a few hours to hack up an adapter by spelunking z3c.form:

/eggs/z3c.form-3.6-py2.7.egg/z3c/form/widget.py(151)render()

 template = zope.component.getMultiAdapter(
       (self.context, self.request, self.form, self.field, self),
       IPageTemplate, name=self.mode)

Therefore (check my interfaces please)

<adapter for="zope.interface.Interface
              rfasite.basetheme.interfaces.IRFASpecific <----- My layer to match request
              plone.tiles.interfaces.ITile
              z3c.relationfield.interfaces.IRelationChoice
              plone.app.z3cform.interfaces.IRelatedItemsWidget"
           provides="zope.pagetemplate.interfaces.IPageTemplate"
           name="display"
           factory="rfasite.content.foo.betterdisplaytemplate"
  />

and the factory (rfasite.content.foo.betterdisplaytemplate)

from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile

class betterdisplaytemplate(object):
    def __init__(self, *args, **kwargs):
        args = args
        kwargs = kwargs
        
    def __call__(self, *args):
        return self.index()
    
    def index(self):
        return "<span>Hello World!</span>"

and lo and behold - RelationChoice fields (all of them) now print Hello World.

From here, it's obvious what to do.

Comments:

  1. I wish I could provide a 'template' attribute to the 'adapter' registration in ZCML so I could properly use browserlayers to switch out templates.
  2. I could not find the existing adapter registration that pulls the textfield display widget, so I had to work backwards through providedBy(self.field) and such. This seems sloppy. The only way I got a 'more specific adapter' than the z3cform built-in was by using my browserlayer interface for the request.

@datakurre - could an adapter like this be registered in mosaic to handle RelatedItemsWidget (and RelationList, which I also need)

Better answer. Reading docs instead of code:

  <z3c:widgetTemplate
    mode="display"
    widget="plone.app.z3cform.interfaces.IRelatedItemsWidget"
    layer="rfasite.basetheme.interfaces.IRFASpecific"
    template="relation_display.pt"
    />

I also found the existing registration that makes relation fields act like text widgets when 'displayed'

plone.app.z3cform widget.py:

class RelatedItemsWidget(BaseWidget, z3cform_TextWidget):

1 Like

And...... It works, but it's not a 'tile' Just a template and a widget.

After making it work, I think this is a better answer.
But if a custom tile is not the right answer, at least there is now a possible OOTB solution for relation fields. It's a template that uses the portal catalog to get the object. Then renders title(s) description(s) and Lead Image(s)

1 Like