--- html_meta: "description": "" "property=og:description": "" "property=og:title": "" "keywords": "" --- # Relations You can model relationships between content items by placing them in a hierarchy (e.g. a folder _speakers_ containing the (folderish) speakers and within each speaker the talks) or by linking them to each other in Richtext fields. But where would you then store a talk that two speakers give together? Relations allow developers to model relationships between objects without using links or a hierarchy. The behavior {py:class}`plone.app.relationfield.behavior.IRelatedItems` provides the field {guilabel}`Related Items` in the tab {guilabel}`Categorization`. That field simply says `a` is somehow related to `b`. By using custom relations you can model your data in a much more meaningful way. ## Creating relations in a schema Relate to one item only. ```{code-block} python :linenos: from plone.app.vocabularies.catalog import CatalogSource from z3c.relationfield.schema import RelationChoice from z3c.relationfield.schema import RelationList evil_mastermind = RelationChoice( title='The Evil Mastermind', vocabulary='plone.app.vocabularies.Catalog', required=False, ) ``` Relate to multiple items. ```python from z3c.relationfield.schema import RelationChoice from z3c.relationfield.schema import RelationList minions = RelationList( title='Minions', default=[], value_type=RelationChoice( vocabulary='plone.app.vocabularies.Catalog', ) required=False, ) ``` We can see that the [code for the behavior IRelatedItems](https://github.com/plone/plone.app.relationfield/blob/master/plone/app/relationfield/behavior.py) does exactly the same. ## Controlling what to relate to The best way to control wich item should be relatable to is to configure the widget with `directives.widget()`. In the following example you can only relate to Documents: ```{code-block} python :emphasize-lines: 12 :linenos: from plone.app.z3cform.widget import RelatedItemsFieldWidget relationchoice_field = RelationChoice( title='Relationchoice field', vocabulary='plone.app.vocabularies.Catalog', required=False, ) directives.widget( 'relationchoice_field', RelatedItemsFieldWidget, pattern_options={ 'selectableTypes': ['Document', 'Event'], }, ) ``` ## Configure the RelatedItemsFieldWidget With `pattern_options` you can further configure the widget. In the following example you can specify a) where to start browsing using the _pattern-option_ `basePath` and and b) to leave the dropwdown open using `closeOnSelect`. ```{code-block} python :emphasize-lines: 11 :linenos: relationlist_field = RelationList( title='Relationlist Field', default=[], value_type=RelationChoice(vocabulary='plone.app.vocabularies.Catalog'), required=False, missing_value=[], ) directives.widget( 'relationlist_field', RelatedItemsFieldWidget, pattern_options={ 'basePath': ', 'closeOnSelect': False, # Leave dropdown open for multiple selection }, ) ``` `basePath` can also be a method. In this exmaple we use the helper-method `plone.app.multilingual.browser.interfaces.make_relation_root_path`. ```{code-block} python :emphasize-lines: 13 :linenos: from plone.app.multilingual.browser.interfaces import make_relation_root_path relationlist_field = RelationList( title='Relationlist Field', default=[], value_type=RelationChoice(vocabulary='plone.app.vocabularies.Catalog'), required=False, missing_value=[], ) directives.widget( 'relationlist_field', RelatedItemsFieldWidget, pattern_options=make_relation_root_path, ) ``` ## Using the search mode of the Related Items Widget So far we only used the vocabulary `plone.app.vocabularies.Catalog` that returns the full content tree. Alternatively you can use `CatalogSource` to specify a catalog query that only return the values from the query. You can pass to `CatalogSource` the same arguments you use for catalog queries. This makes it very flexible for limiting relateable items by type, path, date, and so on. Setting the mode of the widget to `search` makes it easier to select from the content that result form your catalog-query instead of having to navigate through your content-tree. The problem is that in the default mode of the Related Items wisget items that are in container s are not shown unless you add thes types of contaibers to the query. Therefore is is recommended to use CatalogSource only in in `search` mode. ```{code-block} python :emphasize-lines: 10 :linenos: from plone.app.vocabularies.catalog import CatalogSource speakers = RelationList( title='Speaker(s) for this talk', value_type=RelationChoice(source=CatalogSource(portal_type='speaker')), required=False, ) directives.widget( 'speakers', RelatedItemsFieldWidget, pattern_options={ 'baseCriteria': [ # This is a optimization that limits the catalog-query { 'i': 'portal_type', 'o': 'plone.app.querystring.operation.selection.any', 'v': ['speaker'], } ], 'mode': 'search', }, ) ``` ```{figure} _static/relationlist_searchmode.png :alt: Seach mode of RelationWidget Seach mode of RelationWidget ``` ## Using different widgets for relations Often the widget for relations is not what you want since it can be hard to navigate to the content you want to relate to. If you want to use checkboxes, radiobuttons or a selection-dropdown you need to use `StaticCatalogVocabulary` instead of `CatalogSource` to specify your options. ```{code-block} python :emphasize-lines: 8, 18 :linenos: from plone.app.vocabularies.catalog import StaticCatalogVocabulary from plone.app.z3cform.widget import SelectFieldWidget from plone.autoform import directives from z3c.relationfield.schema import RelationChoice relationchoice_field_select = RelationChoice( title='RelationChoice with Select Widget', vocabulary=StaticCatalogVocabulary( { 'portal_type': ['Document', 'Event'], 'review_state': 'published', } ), required=False, ) directives.widget( 'relationchoice_field_select', SelectFieldWidget, ) ``` The field should then look like this: ```{figure} _static/relation_select.png :alt: RelationList field with select widget RelationList field with select widget ``` Another example is the `AjaxSelectFieldWidget` that only queries the catalog for results if you start typing: ```{code-block} python :linenos: relationlist_field_ajax_select = RelationList( title='Relationlist Field with AJAXSelect', description='z3c.relationfield.schema.RelationList', value_type=RelationChoice( vocabulary=StaticCatalogVocabulary( { 'portal_type': ['Document', 'Event'], 'review_state': 'published', } ) ), required=False, ) directives.widget( 'relationlist_field_ajax_select', AjaxSelectFieldWidget, vocabulary=StaticCatalogVocabulary( { 'portal_type': ['Document', 'Event', 'Folder'], }, title_template='{brain.Type}: {brain.Title} at {path}', ), # Custom item rendering pattern_options={ # Options for Select2 'minimumInputLength': 2, # - Don't query until at least two characters have been typed 'ajax': {'quietMillis': 500}, # - Wait 500ms after typing to make query }, ) ``` ```{figure} _static/relationliste_ajax.png :alt: Relationlist Field with AJAXSelect ``` Relationlist Field with AJAXSelect ## Define Favorite Locations The `RelatedItemsFieldWidget` also allow you to set favorites: ```{code-block} python :linenos: directives.widget( 'minions', RelatedItemsFieldWidget, pattern_options={ 'favorites': [{'title': 'Minions', 'path': '/Plone/minions'}] }, ) ``` `favorites` can also be a method that takes the current context. Here is a full example as a behavior: ```{code-block} python :linenos: from plone import api from plone.app.vocabularies.catalog import CatalogSource from plone.app.z3cform.widget import RelatedItemsFieldWidget from plone.autoform import directives from plone.autoform.interfaces import IFormFieldProvider from plone.supermodel import model from z3c.relationfield.schema import RelationChoice from z3c.relationfield.schema import RelationList from zope.interface import provider def minion_favorites(context): portal = api.portal.get() minions_path = '/'.join(portal['minions'].getPhysicalPath()) one_eyed_minions_path = '/'.join(portal['one-eyed-minions'].getPhysicalPath()) return [ { 'title': 'Current Content', 'path': '/'.join(context.getPhysicalPath()) }, { 'title': 'Minions', 'path': minions_path, }, { 'title': 'One eyed minions', 'path': one_eyed_minions_path, } ] @provider(IFormFieldProvider) class IHaveMinions(model.Schema): minions = RelationList( title='My minions', default=[], value_type=RelationChoice( source=CatalogSource( portal_type=['one_eyed_minion', 'minion'], review_state='published', ) ), required=False, ) directives.widget( 'minions', RelatedItemsFieldWidget, pattern_options={ 'mode': 'auto', 'favorites': minion_favorites, } ) ``` ## Accessing and displaying related items To display related items you can use the render method of the default widget e.g.: ```html
``` This would render the related items like this: ```{figure} https://user-images.githubusercontent.com/453208/77223704-4b714100-6b5f-11ea-855b-c6e209f1c25c.png :alt: Default rendering of a RelationList (since Plone 5.2.2) ``` If you want to access and render relations yourself you can use the Plone add-on [collective.relationhelpers](https://pypi.org/project/collective.relationhelpers) and add a method like in the following example. ```{code-block} python :linenos: from collective.relationhelpers import api as relapi from Products.Five import BrowserView class EvilMastermindView(BrowserView): def minions(self): """Returns a list of related items.""" return relapi.relations(self.context, 'underlings') ``` This returns the related items so that you will able to render them anyhow you like. ## Inspecting relations You Plone 6 Classic you can inspect all relations and backrelations in your site using the control panel `/@@inspect-relations`. ```{figure} _static/inspect-relations.png :alt: The relations controlpanel The relations controlpanel ``` In Plone 5 this is available through the addon [collective.relationhelpers](https://pypi.org/project/collective.relationhelpers). ## Creating RelationFields through the web It is surprisingly easy to create RelationFields through the web - Using the Dexterity schema editor, add a new field and select _Relation List_ or _Relation Choice_, depending on whether you want to relate to multiple items or not. - When configuring the field you can even select the content type the relation should be limited to. When you click on `Edit XML field model` you will see the fields in the XML schema: RelationChoice: ```{code-block} python :linenos: