30. Relations#
In this part you will use relations to connect talks to other content items.
Tools and techniques covered:
relation fields
Check out mastering-plone-project at tag user_generated_content:
git checkout user_generated_content
The code at the end of the chapter:
git checkout relations
More info in The code for the training
You can model relationships between content items by placing them in a hierarchy — for example a (folderish) page speakers containing the speakers and within each speaker their talks — or by linking them to each other in blocks. 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 plone.relateditems provides the field Related Items in the section 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.
30.1. Use relation fields in a schema#
Relate to one item only with a RelationChoice field.
from z3c.relationfield.schema import RelationChoice
speaker = RelationChoice(
title="Speaker",
description="The speaker of the talk",
vocabulary="plone.app.vocabularies.Catalog",
required=False
)
Relate to multiple items with a RelationList field.
from z3c.relationfield.schema import RelationChoice
from z3c.relationfield.schema import RelationList
speakers = RelationList(
title="Speaker",
description="Speakers of the talk",
value_type=RelationChoice(
vocabulary="plone.app.vocabularies.Catalog",
),
required=False,
default=[]
)
See also
Plone documentation Relation fields
The vocabulary controls which content items can be the target of the relation.
1 speakers = RelationList(
2 title="Speaker",
3 description="Speakers of the talk",
4 value_type=RelationChoice(
5 vocabulary="ploneconf.speakers"
6 ),
7 required=False,
8 default=[]
9 )
We want to relate to content instances of type 'speaker'. So we define a vocabulary of speakers.
backend/src/ploneconf/site/vocabularies/configure.zcml
1 <utility
2 name="ploneconf.speakers"
3 component="ploneconf.site.vocabularies.speaker.SpeakerVocabularyFactory"
4 />
backend/src/ploneconf/site/vocabularies/speaker.py
1from plone.app.vocabularies.catalog import StaticCatalogVocabulary
2from zope.interface import provider
3from zope.schema.interfaces import IVocabularyFactory
4
5
6@provider(IVocabularyFactory)
7def SpeakerVocabularyFactory(context=None):
8 return StaticCatalogVocabulary(
9 {
10 "portal_type": ["speaker"],
11 "review_state": "published",
12 "sort_on": "sortable_title",
13 }
14 )
30.2. The widget#
The widget allows the editor to edit the relations.
The default widget for relation fields in Volto is the object browser widget, which opens the tree of content for the editor to browse and select. On saving the talk, the selection is validated against the vocabulary. That also means that if you select anything that is not a published speaker, you will get an error message.
One way to work around this is to use a select widget that only allows you to choose from the field's vocabulary.
1 textindexer.searchable("speakers")
2 speakers = RelationList(
3 title="Speakers",
4 description="Speakers of the talk",
5 value_type=RelationChoice(vocabulary="ploneconf.speakers"),
6 required=False,
7 default=[],
8 )
9 directives.widget(
10 "speakers",
11 frontendOptions={
12 "widget": "select",
13 },
14 )
For more info on widgets see Forms and widgets.
30.4. Inspect relations#
You can inspect all relations and inverse relations in your site using the Relations control panel at http://localhost:3000/controlpanel/relations. You can even edit the relations.
The relations controlpanel#
You can find the inverse relations of a content instance via the menu item Links and references
Links and references menu#
Links and references#
30.5. Programming with relations#
plone.api has methods to create, read, and delete relations.
1from plone import api
2
3portal = api.portal.get()
4source = portal.schedule["workflows-made-easy"]
5target = portal.speakers["urs-herbst"]
6api.relation.create(source=source, target=target, relationship="speaker")
1from plone import api
2
3api.relation.get(source=portal.schedule["workflows-made-easy"])
4api.relation.get(relationship="speaker")
5api.relation.get(target=portal.speakers["urs-herbst"])
List all relations of name "speaker":
>>> for rel in api.relation.get(relationship="speaker"): rel.from_object, rel.to_object, rel.from_attribute
...
(<Talk at /Plone/schedule/talkli>, <Speaker at /Plone/speakers/urs-herbst>, 'speaker')
(<Talk at /Plone/schedule/advanced-relations>, <Speaker at /Plone/speakers/katja-i-e-suss>, 'speaker')
See the chapter Relations of the docs for plone.api for more details.
30.6. Exercise 1#
Add a Speaker content type and modify the Talk content type to relate to speakers.
Write an upgrade step for the change of the field speaker.
30.7. Exercise 2#
The speaker is now a relation on talk. The TalkView includes a subset of attributes of the speaker. How would you achieve showing the GitHub handle of the speaker? So far, it is not included in the available attributes.