1 .. _paragraphhowto: |
1 .. _paragraphhowto: |
2 |
2 |
3 |
3 |
4 How to create a new paragraph type? |
4 How to create a Paragraph type? |
5 =================================== |
5 =============================== |
|
6 |
|
7 Paragraphs or Blocks are components that contain elements/fields and provide one or many renderer methods to compose |
|
8 the front office website |
|
9 |
|
10 |
|
11 Create a Paragraph |
|
12 """""""""""""""""" |
|
13 |
|
14 In this example we will create a contact paragraph to display at the user, who to contact. |
|
15 |
|
16 1) Interface |
|
17 ------------ |
|
18 |
|
19 At first we create a new paragraph interface. |
|
20 |
|
21 .. code-block:: python |
|
22 |
|
23 CONTACT_PHONE_PARAGRAPH_TYPE = 'PhoneContact' |
|
24 CONTACT_PHONE_PARAGRAPH_RENDERERS = 'PyAMS.paragraph.contact.phone.renderers' |
|
25 |
|
26 |
|
27 class IContactPhoneParagraph(IBaseParagraph): |
|
28 """Contact with phone number paragraph interface""" |
|
29 |
|
30 name = TextLine(title=_("Contact identity"), |
|
31 description=_("Name of the contact"), |
|
32 required=True) |
|
33 |
|
34 photo = ImageField(title=_("Photo"), |
|
35 description=_("Use 'browse' button to select contact picture"), |
|
36 required=False) |
|
37 |
|
38 phone = TextLine(title=_("Phone Number"), |
|
39 description=_("Name of the contact", required=False)) |
|
40 |
|
41 renderer = Choice(title=_("Contact template"), |
|
42 description=_("Presentation template used for this contact"), |
|
43 vocabulary=CONTACT_PHONE_PARAGRAPH_RENDERERS, |
|
44 default='default') |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 2) Implement the interface |
|
50 -------------------------- |
|
51 |
|
52 An implementation of the interface |
|
53 |
|
54 .. code-block:: python |
|
55 |
|
56 @implementer(IContactPhoneParagraph) |
|
57 @factory_config(provided=IContactPhoneParagraph) |
|
58 class ContactPhoneParagraph(BaseParagraph): |
|
59 """Contact paragraph""" |
|
60 |
|
61 |
|
62 icon_class = 'fa-phone' |
|
63 icon_hint = _("Phone number card") |
|
64 |
|
65 name = FieldProperty(IContactPhoneParagraph['name']) |
|
66 _photo = FileProperty(IContactPhoneParagraph['photo']) |
|
67 |
|
68 renderer = FieldProperty(IContactParagraph['renderer']) |
|
69 |
|
70 @property |
|
71 def photo(self): |
|
72 return self._photo |
|
73 |
|
74 @photo.setter |
|
75 def photo(self, value): |
|
76 self._photo = value |
|
77 if IImage.providedBy(self._photo): |
|
78 alsoProvides(self._photo, IResponsiveImage) |
|
79 |
|
80 |
|
81 3) Renderers Vocabulary |
|
82 ----------------------- |
|
83 |
|
84 The list of rendered available for a paragraph is build automatically and is based on adapters that provide this interface |
|
85 |
|
86 .. code-block:: python |
|
87 |
|
88 @vocabulary_config(name=CONTACT_PARAGRAPH_RENDERERS) |
|
89 class ContactParagraphRendererVocabulary(RenderersVocabulary): |
|
90 """Contact Phone paragraph renderers vocabulary""" |
|
91 |
|
92 content_interface = IContactPhoneParagraph |
|
93 |
|
94 |
|
95 .. seealso:: |
|
96 |
|
97 :ref:`rendererhowto` |
|
98 |
|
99 |
|
100 Paragraph in the ZMI |
|
101 """""""""""""""""""" |
|
102 |
|
103 |
|
104 To display and manage the new paragraph in the ZMI, you should create this associated forms |
|
105 |
|
106 1) Paragraph factory |
|
107 -------------------- |
|
108 |
|
109 To create a new element instance inside the zodb, we need a container to this object. PyAMS provide |
|
110 `IParagraphContainerTarget`, it's the default container for all paragraphs. We could use this container interface |
|
111 as context to create a new paragraph. |
|
112 |
|
113 |
|
114 Declaration of the **factory** of `ContactPhoneParagraph` |
|
115 |
|
116 .. code-block:: python |
|
117 |
|
118 @utility_config(name=CONTACT_PHONE_PARAGRAPH_TYPE, provides=IParagraphFactory) |
|
119 class ContactPhoneParagraphFactory(BaseParagraphFactory): |
|
120 """Contact paragraph factory""" |
|
121 |
|
122 name = _("Contact Phone card") |
|
123 content_type = ContactPhoneParagraph |
|
124 secondary_menu = True |
|
125 |
|
126 |
|
127 Definition of a form to create a new ContactPhone instance |
|
128 |
|
129 .. code-block:: python |
|
130 |
|
131 from pyams_form.form import ajax_config |
|
132 |
|
133 |
|
134 @pagelet_config(name='add-contact-phone-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer, |
|
135 permission=MANAGE_CONTENT_PERMISSION) |
|
136 @ajax_config(name='add-contact-phone-paragraph.json', context=IParagraphContainerTarget, layer=IPyAMSLayer, |
|
137 base=BaseParagraphAJAXAddForm) |
|
138 class ContactPhoneParagraphAddForm(AdminDialogAddForm): |
|
139 """Contact phone paragraph add form""" |
|
140 |
|
141 legend = _("Add new contact phone card") |
|
142 dialog_class = 'modal-large' |
|
143 icon_css_class = 'fa fa-fw fa-phone' |
|
144 |
|
145 fields = field.Fields(IContactPhoneParagraph).omit('__parent__', '__name__', 'visible') |
|
146 edit_permission = MANAGE_CONTENT_PERMISSION |
|
147 |
|
148 def create(self, data): |
|
149 return ContactPhoneParagraph() |
|
150 |
|
151 def add(self, object): |
|
152 IParagraphContainer(self.context).append(object) |
|
153 |
|
154 |
|
155 2) Create the Paragraph addform button in the menu |
|
156 -------------------------------------------------- |
|
157 |
|
158 We have created a new form and we want add a quick access button to create a new paragraph |
|
159 |
|
160 .. code-block:: python |
|
161 |
|
162 @viewlet_config(name='add-contact-phone-paragraph.menu', context=IParagraphContainerTarget, view=IParagraphContainerView, |
|
163 layer=IPyAMSLayer, manager=IToolbarAddingMenu, weight=600) |
|
164 class ContactPhoneParagraphAddMenu(BaseParagraphAddMenu): |
|
165 """Contact paragraph add menu""" |
|
166 |
|
167 label = _("Contact card...") |
|
168 label_css_class = 'fa fa-fw fa-id-card-o' |
|
169 url = 'add-contact-paragraph.html' |
|
170 paragraph_type = CONTACT_PARAGRAPH_TYPE |
|
171 |
|
172 |
|
173 |
|
174 3) Create Edit inner form |
|
175 ------------------------- |
|
176 |
|
177 .. code-block:: python |
|
178 |
|
179 @adapter_config(context=(IContactPhoneParagraph, IPyAMSLayer), provides=IParagraphInnerEditor) |
|
180 permission=MANAGE_CONTENT_PERMISSION) |
|
181 @ajax_config(name='inner-properties.json', context=IContactPhoneParagraph, layer=IPyAMSLayer, |
|
182 base=BaseParagraphAJAXEditForm) |
|
183 @implementer(IInnerForm) |
|
184 class ContactPhoneParagraphInnerEditForm(ContactPhoneParagraphPropertiesEditForm): |
|
185 """Contact paragraph inner edit form""" |
|
186 |
|
187 legend = None |
|
188 |
|
189 @property |
|
190 def buttons(self): |
|
191 if self.mode == INPUT_MODE: |
|
192 return button.Buttons(IParagraphEditFormButtons) |
|
193 else: |
|
194 return button.Buttons() |
|
195 |
|
196 def get_ajax_output(self, changes): |
|
197 output = super(ContactParagraphInnerAJAXEditForm, self).get_ajax_output(changes) |
|
198 updated = changes.get(IBaseParagraph, ()) |
|
199 if 'title' in updated: |
|
200 output.setdefault('events', []).append(get_json_paragraph_refresh_event(self.context, self.request)) |
|
201 updated = changes.get(IContactParagraph, ()) |
|
202 if ('photo' in updated) or ('renderer' in updated): |
|
203 # we have to commit transaction to be able to handle blobs... |
|
204 if 'photo' in updated: |
|
205 ITransactionManager(self.context).get().commit() |
|
206 output.setdefault('events', []).append(get_json_form_refresh_event(self.context, self.request, |
|
207 ContactParagraphInnerEditForm)) |
|
208 return output |
|
209 |
|
210 |
|
211 4) Create an Edit modal form |
|
212 ----------------------------- |
|
213 |
|
214 This form is used inside modals popup |
|
215 |
|
216 |
|
217 .. code-block:: python |
|
218 |
|
219 @ajax_config(name='properties.json', context=IContactPhoneParagraph, request_type=IPyAMSLayer, |
|
220 permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True) |
|
221 @pagelet_config(name='properties.html', context=IContactParagraph, layer=IPyAMSLayer, |
|
222 permission=MANAGE_CONTENT_PERMISSION) |
|
223 class ContactPhoneParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm): |
|
224 """Contact phone paragraph properties edit form""" |
|
225 |
|
226 prefix = 'contact_properties.' |
|
227 |
|
228 legend = _("Edit contact card properties") |
|
229 icon_css_class = 'fa fa-fw fa-id-card-o' |
|
230 |
|
231 fields = field.Fields(IContactParagraph).omit('__parent__', '__name__', 'visible') |
|
232 fields['renderer'].widgetFactory = RendererFieldWidget |
|
233 |
|
234 edit_permission = MANAGE_CONTENT_PERMISSION |
|
235 |
|
236 |
|
237 .. note:: |
|
238 |
|
239 Select the new content block types in ZMI to make it available in tools |
|
240 |
|
241 .. image:: _static/select_paragraph.png |
|
242 |
|
243 |
|
244 How to associate links or Illustrations to a Paragraph ? |
|
245 ======================================================== |
|
246 |
|
247 Adding the following marker interface, we add new behavior to the Paragraph `ContactPhoneParagraph` . |
|
248 |
|
249 |
|
250 Paragraph in the ZMI |
|
251 """""""""""""""""""" |
|
252 |
|
253 1) Inner form |
|
254 ------------- |
|
255 |
|
256 .. code-block:: python |
|
257 |
|
258 @adapter_config(context=(IContactPhoneParagraph, IPyAMSLayer), provides=IParagraphInnerEditor) |
|
259 @implementer(IInnerForm, IPropertiesEditForm, IAssociationParentForm) |
|
260 class ContactPhoneParagraphInnerEditForm(ContactPhoneParagraphPropertiesEditForm): |
|
261 """Contact paragraph inner edit form""" |
|
262 |
|
263 legend = None |
|
264 ajax_handler = 'inner-properties.json' |
|
265 |
|
266 |
|
267 Marker interfaces: |
|
268 |
|
269 :py:class:`IPropertiesEditForm` |
|
270 |
|
271 :py:class:`IAssociationParentForm` |
|
272 |
|
273 |
|
274 .. image:: _static/associations_form.png |
|
275 |
|
276 |
|
277 |
|
278 2) Inside a dedicated menu |
|
279 -------------------------- |
|
280 |
|
281 .. image:: _static/select_links_n_attachment.png |
|
282 |
|
283 |
|
284 .. code-block:: python |
|
285 |
|
286 @implementer(IContactPhoneParagraph, IIllustrationTarget,ILinkContainerTarget,IExtFileContainerTarget)) |
|
287 @factory_config(provided=IContactPhoneParagraph) |
|
288 class ContactPhoneParagraph(BaseParagraph): |
|
289 """Contact paragraph""" |
|
290 ... |
|
291 |
|
292 |
|
293 Marker interfaces: |
|
294 |
|
295 +--------------------------------+ |
|
296 |:py:class:`IIllustrationTarget` | |
|
297 +===================+============+ |
|
298 | | | |
|
299 +-------------------+------------+ |
|
300 |
|
301 +---------------------------------+ |
|
302 |:py:class:`ILinkContainerTarget` | |
|
303 +==============+==================+ |
|
304 | | Add internal link| |
|
305 | +------------------+ |
|
306 | | Add external link| |
|
307 | +------------------+ |
|
308 | | Add mailto link | |
|
309 +--------------+------------------+ |
|
310 |
|
311 +------------------------------------+ |
|
312 |:py:class:`IExtFileContainerTarget` | |
|
313 +================+===================+ |
|
314 | | Add external file | |
|
315 | +-------------------+ |
|
316 | | Add image | |
|
317 | +-------------------+ |
|
318 | | Add video | |
|
319 | +-------------------+ |
|
320 | | Add audio file | |
|
321 +----------------+-------------------+ |
|
322 |
|
323 |
|
324 ZMI overview: |
|
325 .. image:: _static/select_add_links.png |
|
326 |
|
327 |
|
328 |