.. _paragraphhowto: 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. .. code-block:: python 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 .. code-block:: python @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 .. code-block:: python @vocabulary_config(name=CONTACT_PARAGRAPH_RENDERERS) class ContactParagraphRendererVocabulary(RenderersVocabulary): """Contact Phone paragraph renderers vocabulary""" content_interface = IContactPhoneParagraph .. seealso:: :ref:`rendererhowto` Paragraph in the ZMI -------------------- 1) Container Paragraph ^^^^^^^^^^^^^^^^^^^^^^ For example :py:class:`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 .. code-block:: python 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 .. code-block:: python @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 ^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: python @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 .. code-block:: python @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 .. image:: ../_static/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` .. code-block:: python @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. How to associate links or Illustrations to a Paragraph ? -------------------------------------------------------- Adding the following marker interface, we add new behavior to the Paragraph `ContactPhoneParagraph` . You can attach Associated files, links or illustration and manage them directly through the rubric `Links and Attachments`. .. image:: ../_static/select_links_n_attachment.png 1) Paragraph with Links and Attachements ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To "activate" this features the paragrath object must to implement specific interface .. code-block:: python @implementer(IContactPhoneParagraph, IIllustrationTarget,ILinkContainerTarget,IExtFileContainerTarget)) @factory_config(provided=IContactPhoneParagraph) class ContactPhoneParagraph(BaseParagraph): """Contact paragraph""" ... These interfaces will allow to link other data to the paragraph. **Marker interfaces:** +--------------------------------+ |:py:class:`IIllustrationTarget` | +===================+============+ | | | +-------------------+------------+ +---------------------------------+ |:py:class:`ILinkContainerTarget` | +==============+==================+ | | Add internal link| | +------------------+ | | Add external link| | +------------------+ | | Add mailto link | +--------------+------------------+ +------------------------------------+ |:py:class:`IExtFileContainerTarget` | +================+===================+ | | Add external file | | +-------------------+ | | Add image | | +-------------------+ | | Add video | | +-------------------+ | | Add audio file | +----------------+-------------------+ **ZMI overview:** .. image:: ../_static/select_add_links.png 2) Add link and association form ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can add form to manage links and attachments directly in paragraph form to do that your form must implement ``IPropertiesEditForm`` and/or ``IAssociationParentForm`` .. code-block:: python @adapter_config(context=(IContactPhoneParagraph, IPyAMSLayer), provides=IParagraphInnerEditor) @implementer(IInnerForm, IPropertiesEditForm, IAssociationParentForm) class ContactPhoneParagraphInnerEditForm(ContactPhoneParagraphPropertiesEditForm): """Contact paragraph inner edit form""" legend = None ajax_handler = 'inner-properties.json' **Marker interfaces:** +-----------------------------------+ |:py:class:`IPropertiesEditForm` | +=========+=========================+ | | Add Illustration form | +---------+-------------------------+ +-----------------------------------+ |:py:class:`IAssociationParentForm` | +===========+=======================+ | | Add Association form | +-----------+-----------------------+ .. image:: ../_static/associations_form.png