Creating new Paragraph (or blocks)

Paragraphs or Blocks are components that contain elements/fields and provide one or many renderer methods to compose the front office website

How to create a Paragraph type?

In this example we will create a contact paragraph to display at the user, who to contact.

1) Interface

At first we create a new paragraph interface.

CONTACT_PHONE_PARAGRAPH_TYPE = 'PhoneContact'
CONTACT_PHONE_PARAGRAPH_RENDERERS = 'PyAMS.paragraph.contact.phone.renderers'


class IContactPhoneParagraph(IBaseParagraph):
"""Contact with phone number paragraph interface"""

name = TextLine(title=_("Contact identity"),
                description=_("Name of the contact"),
                required=True)

photo = ImageField(title=_("Photo"),
                   description=_("Use 'browse' button to select contact picture"),
                   required=False)

phone = TextLine(title=_("Phone Number"),
                 description=_("Name of the contact", required=False))

renderer = Choice(title=_("Contact template"),
                  description=_("Presentation template used for this contact"),
                  vocabulary=CONTACT_PHONE_PARAGRAPH_RENDERERS,
                  default='default')

2) Implement the interface

An implementation of the interface

@implementer(IContactPhoneParagraph)
@factory_config(provided=IContactPhoneParagraph)
class ContactPhoneParagraph(BaseParagraph):
    """Contact paragraph"""


    icon_class = 'fa-phone'
    icon_hint = _("Phone number card")

    name = FieldProperty(IContactPhoneParagraph['name'])
    _photo = FileProperty(IContactPhoneParagraph['photo'])

    renderer = FieldProperty(IContactParagraph['renderer'])

    @property
    def photo(self):
        return self._photo

    @photo.setter
    def photo(self, value):
        self._photo = value
        if IImage.providedBy(self._photo):
            alsoProvides(self._photo, IResponsiveImage)

3) Renderers Vocabulary

The list of rendered available for a paragraph is build automatically and is based on adapters that provide this interface

@vocabulary_config(name=CONTACT_PARAGRAPH_RENDERERS)
class ContactParagraphRendererVocabulary(RenderersVocabulary):
    """Contact Phone paragraph renderers vocabulary"""

    content_interface = IContactPhoneParagraph

See also

rendererhowto

Paragraph in the ZMI

1) Container Paragraph

For example IParagraphContainerTarget, it’s a marker interface for paragraph containers.

To create a new element instance inside the zodb, we need a container to this object. PyAMS provide IParagraphContainerTarget, it’s the default marker interface container for all paragraphs. We could use this interface as context to declare a new pagelet.

Definition of a Contact Phone form to create a new ContactPhone object

from pyams_form.form import ajax_config


@pagelet_config(name='add-contact-phone-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
                permission=MANAGE_CONTENT_PERMISSION)
@ajax_config(name='add-contact-phone-paragraph.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
             base=BaseParagraphAJAXAddForm)
class ContactPhoneParagraphAddForm(AdminDialogAddForm):
    """Contact phone paragraph add form"""

    legend = _("Add new contact phone card")
    dialog_class = 'modal-large'
    icon_css_class = 'fa fa-fw fa-phone'

    fields = field.Fields(IContactPhoneParagraph).omit('__parent__', '__name__', 'visible')
    edit_permission = MANAGE_CONTENT_PERMISSION

     def create(self, data):
        return ContactPhoneParagraph()

    def add(self, object):
        IParagraphContainer(self.context).append(object)

To display and manage the new paragraph in the ZMI, you should create this associated forms

2) Create the Paragraph addform button in the menu

We have created a new form and we want add a quick access button to create a new paragraph

@viewlet_config(name='add-contact-phone-paragraph.menu', context=IParagraphContainerTarget, view=IParagraphContainerView,
                layer=IPyAMSLayer, manager=IToolbarAddingMenu, weight=600)
class ContactPhoneParagraphAddMenu(BaseParagraphAddMenu):
    """Contact paragraph add menu"""

    label = _("Contact card...")
    label_css_class = 'fa fa-fw fa-id-card-o'
    url = 'add-contact-paragraph.html'
    paragraph_type = CONTACT_PARAGRAPH_TYPE

3) Create Edit inner form

@adapter_config(context=(IContactPhoneParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
            permission=MANAGE_CONTENT_PERMISSION)
@ajax_config(name='inner-properties.json', context=IContactPhoneParagraph, layer=IPyAMSLayer,
         base=BaseParagraphAJAXEditForm)
@implementer(IInnerForm)
class ContactPhoneParagraphInnerEditForm(ContactPhoneParagraphPropertiesEditForm):
    """Contact paragraph inner edit form"""

    legend = None

    @property
    def buttons(self):
        if self.mode == INPUT_MODE:
            return button.Buttons(IParagraphEditFormButtons)
        else:
            return button.Buttons()

    def get_ajax_output(self, changes):
        output = super(ContactParagraphInnerAJAXEditForm, self).get_ajax_output(changes)
        updated = changes.get(IBaseParagraph, ())
        if 'title' in updated:
            output.setdefault('events', []).append(get_json_paragraph_refresh_event(self.context, self.request))
        updated = changes.get(IContactParagraph, ())
        if ('photo' in updated) or ('renderer' in updated):
            # we have to commit transaction to be able to handle blobs...
            if 'photo' in updated:
                ITransactionManager(self.context).get().commit()
            output.setdefault('events', []).append(get_json_form_refresh_event(self.context, self.request,
                                                                               ContactParagraphInnerEditForm))
        return output

4) Create an Edit modal form

This form is used inside modals popup

@ajax_config(name='properties.json', context=IContactPhoneParagraph, request_type=IPyAMSLayer,
                 permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
@pagelet_config(name='properties.html', context=IContactParagraph, layer=IPyAMSLayer,
                permission=MANAGE_CONTENT_PERMISSION)
class ContactPhoneParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
    """Contact phone paragraph properties edit form"""

    prefix = 'contact_properties.'

    legend = _("Edit contact card properties")
    icon_css_class = 'fa fa-fw fa-id-card-o'

    fields = field.Fields(IContactParagraph).omit('__parent__', '__name__', 'visible')
    fields['renderer'].widgetFactory = RendererFieldWidget

    edit_permission = MANAGE_CONTENT_PERMISSION

Note

Add the new paragraph in “content block types” to make it available in tools

../_images/tool_management.png

5) Paragraph Factory

If you want to create automatically a paragraph object for a shared content you must define a factory

Declaration of the factory of ContactPhoneParagraph

@utility_config(name=CONTACT_PHONE_PARAGRAPH_TYPE, provides=IParagraphFactory)
class ContactPhoneParagraphFactory(BaseParagraphFactory):
    """Contact paragraph factory"""

    name = _("Contact Phone card")
    content_type = ContactPhoneParagraph
    secondary_menu = True

When the contributors will create new shared content with predefined paragraphs, it’s the container factory class that will be used to create the paragraph object.