Turning Talks into Events – Mastering Plone 5 development

Turning Talks into Events

Turning Talks into Events#

We forgot something: a list of talks is great, especially if you can sort it according to your preferences. But if a visitor decides she wants to actually go to see a talk she needs to know when it will take place.

We need a schedule and for this we need to store the information when a talk will happen.

Luckily the default type Event is based on reusable behaviors from the package plone.app.event that we can reuse.

In this chapter you will

  • enable this behavior for talks

  • display the date in the talkview and talklistview

First enable the behavior IEventBasic for talks in profiles/default/types/talk.xml

1<property name="behaviors">
2  <element value="plone.dublincore"/>
3  <element value="plone.namefromtitle"/>
4  <element value="ploneconf.social"/>
5  <element value="ploneconf.talk"/>
6  <element value="plone.eventbasic"/>
7</property>

After you activate the behavior by hand or you reinstalled the add-on you will now have some additional fields for start and end.

To display the new field we reuse a default event summary view as documented in https://ploneappevent.readthedocs.io/en/latest/development.html#reusing-the-event-summary-view-to-list-basic-event-information

Edit browser/templates/talkview.pt

 1<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
 2      metal:use-macro="context/main_template/macros/master"
 3      i18n:domain="ploneconf.site">
 4<body>
 5    <metal:content-core fill-slot="content-core" tal:define="widgets view/w">
 6
 7        <tal:eventsummary replace="structure context/@@event_summary"/>
 8
 9        <p>
10            <span tal:content="context/type_of_talk">
11                Talk
12            </span>
13            suitable for
14            <span tal:replace="structure widgets/audience/render">
15                Audience
16            </span>
17        </p>
18
19        <div tal:content="structure widgets/details/render">
20            Details
21        </div>
22
23        <div class="newsImageContainer">
24            <img tal:condition="python:getattr(context, 'image', None)"
25                 tal:attributes="src string:${context/absolute_url}/@@images/image/thumb" />
26        </div>
27
28        <div>
29            <a class="email-link" tal:attributes="href string:mailto:${context/email}">
30                <strong tal:content="context/speaker">
31                    Jane Doe
32                </strong>
33            </a>
34            <div tal:content="structure widgets/speaker_biography/render">
35                Biography
36            </div>
37        </div>
38
39    </metal:content-core>
40</body>
41</html>

Similar to the field room, the problem now appears that speakers submitting their talks should not be able to set a time and day for their talks. Sadly it is not easy to modify permissions of fields provided by behaviors (unless you write the behavior yourself). At least in this case we can take the easy way out since the field does not contain secret information: we will simply hide the fields from contributors using css and show them for reviewers. We will do so in chapter Resources when we add some CSS files.

Modify browser/static/ploneconf.css and add:

body.userrole-contributor #formfield-form-widgets-IEventBasic-start,
body.userrole-contributor #formfield-form-widgets-IEventBasic-end > *,
body.userrole-contributor #formfield-form-widgets-IEventBasic-whole_day,
body.userrole-contributor #formfield-form-widgets-IEventBasic-open_end {
  display: none;
}

body.userrole-reviewer #formfield-form-widgets-IEventBasic-start,
body.userrole-reviewer #formfield-form-widgets-IEventBasic-end > *,
body.userrole-reviewer #formfield-form-widgets-IEventBasic-whole_day,
body.userrole-reviewer #formfield-form-widgets-IEventBasic-open_end {
  display: block;
}

You can now display the start date of a talk in the talklist. Modify the class TalkListView and the template browser/templates/talklistview.pt to show the new info:

 1class TalkListView(BrowserView):
 2    """ A list of talks
 3    """
 4
 5    def talks(self):
 6        results = []
 7        brains = api.content.find(context=self.context, portal_type='talk')
 8        for brain in brains:
 9            results.append({
10                'title': brain.Title,
11                'description': brain.Description,
12                'url': brain.getURL(),
13                'audience': ', '.join(brain.audience or []),
14                'type_of_talk': brain.type_of_talk,
15                'speaker': brain.speaker,
16                'room': brain.room,
17                'start': brain.start,
18                })
19        return results
 1[...]
 2<td tal:content="python:talk['audience']">
 3    Advanced
 4</td>
 5<td class="pat-moment"
 6    data-pat-moment="format:calendar"
 7    tal:content="python:talk['start']">
 8    Time
 9</td>
10<td tal:content="python:talk['room']">
11    101
12</td>
13[...]

Note

If you changed the view TalkListView to only return brains as described in Exercise 1 you can save yourself a lot of work and simply use the existing index start (generously provided by plone.app.event) in the template as python:brain.start.

Exercise 1#

Find out where event_summary comes from and describe how you could override it.

Solution

Use your editor or grep to search all ZCML files in the folder packages for the string name="event_summary"

$ grep -siRn --include \*.zcml 'name="event_summary"' ./packages
./packages/plone/app/event/browser/configure.zcml:66:        name="event_summary"
./packages/plone/app/event/browser/configure.zcml:75:        name="event_summary"

The relevant registration is:

<browser:page
    for="plone.event.interfaces.IEvent"
    name="event_summary"
    class=".event_summary.EventSummaryView"
    template="event_summary.pt"
    permission="zope2.View"
    layer="..interfaces.IBrowserLayer"
    />

So there is a class plone.app.event.browser.event_summary.EventSummaryView and a template event_summary.pt that could be overridden with z3c.jbot by copying it as plone.app.event.browser.event_summary.pt in browser/overrides.

Exercise 2#

Find out where the event behavior is defined and which fields it offers.

Solution

The id with which the behavior is registered in Talk.xml is a Python path. So plone.app.event.dx.behaviors.IEventBasic can be found in packages/plone.app.event/plone/app/event/dx/behaviors.py

class IEventBasic(model.Schema, IDXEvent):

    """ Basic event schema.
    """
    start = schema.Datetime(
        title=_(
            u'label_event_start',
            default=u'Event Starts'
        ),
        description=_(
            u'help_event_start',
            default=u'Date and Time, when the event begins.'
        ),
        required=True,
        defaultFactory=default_start
    )
    directives.widget(
        'start',
        DatetimeFieldWidget,
        default_timezone=default_timezone,
        klass=u'event_start'
    )

    end = schema.Datetime(
        title=_(
            u'label_event_end',
            default=u'Event Ends'
        ),
        description=_(
            u'help_event_end',
            default=u'Date and Time, when the event ends.'
        ),
        required=True,
        defaultFactory=default_end
    )
    directives.widget(
        'end',
        DatetimeFieldWidget,
        default_timezone=default_timezone,
        klass=u'event_end'
    )

    whole_day = schema.Bool(
        title=_(
            u'label_event_whole_day',
            default=u'Whole Day'
        ),
        description=_(
            u'help_event_whole_day',
            default=u'Event lasts whole day.'
        ),
        required=False,
        default=False
    )
    directives.widget(
        'whole_day',
        SingleCheckBoxFieldWidget,
        klass=u'event_whole_day'
    )

    open_end = schema.Bool(
        title=_(
            u'label_event_open_end',
            default=u'Open End'
        ),
        description=_(
            u'help_event_open_end',
            default=u"This event is open ended."
        ),
        required=False,
        default=False
    )
    directives.widget(
        'open_end',
        SingleCheckBoxFieldWidget,
        klass=u'event_open_end'
    )

Note how it uses defaultFactory to set an initial value.

Summary#

  • You reused a existing behavior to add new fields

  • You reused existing indexes to display the time of a talk

  • You did not have to write your own datetime fields and indexers o/