1 .. _paragraphhowto: |
|
2 |
|
3 |
|
4 How to create a Paragraph type? |
|
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 advanced |
|
251 """""""""""""""""" |
|
252 |
|
253 You can attach Associated files, links or illustration and manage them directly through the rubric `Links and Attachments`. |
|
254 |
|
255 .. image:: _static/select_links_n_attachment.png |
|
256 |
|
257 |
|
258 1) Paragraph with Links and Attachements |
|
259 ---------------------------------------- |
|
260 |
|
261 To "activate" this features the paragrath object must to implement specific interface |
|
262 |
|
263 |
|
264 .. code-block:: python |
|
265 |
|
266 @implementer(IContactPhoneParagraph, IIllustrationTarget,ILinkContainerTarget,IExtFileContainerTarget)) |
|
267 @factory_config(provided=IContactPhoneParagraph) |
|
268 class ContactPhoneParagraph(BaseParagraph): |
|
269 """Contact paragraph""" |
|
270 ... |
|
271 |
|
272 |
|
273 These interfaces will allow to link other data to the paragraph. |
|
274 |
|
275 **Marker interfaces:** |
|
276 |
|
277 +--------------------------------+ |
|
278 |:py:class:`IIllustrationTarget` | |
|
279 +===================+============+ |
|
280 | | | |
|
281 +-------------------+------------+ |
|
282 |
|
283 +---------------------------------+ |
|
284 |:py:class:`ILinkContainerTarget` | |
|
285 +==============+==================+ |
|
286 | | Add internal link| |
|
287 | +------------------+ |
|
288 | | Add external link| |
|
289 | +------------------+ |
|
290 | | Add mailto link | |
|
291 +--------------+------------------+ |
|
292 |
|
293 +------------------------------------+ |
|
294 |:py:class:`IExtFileContainerTarget` | |
|
295 +================+===================+ |
|
296 | | Add external file | |
|
297 | +-------------------+ |
|
298 | | Add image | |
|
299 | +-------------------+ |
|
300 | | Add video | |
|
301 | +-------------------+ |
|
302 | | Add audio file | |
|
303 +----------------+-------------------+ |
|
304 |
|
305 |
|
306 **ZMI overview:** |
|
307 |
|
308 .. image:: _static/select_add_links.png |
|
309 |
|
310 |
|
311 |
|
312 2) Add link and association form |
|
313 -------------------------------- |
|
314 |
|
315 You can add form to manage links and attachments directly in paragraph form to do that your form must implement |
|
316 ``IPropertiesEditForm`` and/or ``IAssociationParentForm`` |
|
317 |
|
318 |
|
319 .. code-block:: python |
|
320 |
|
321 @adapter_config(context=(IContactPhoneParagraph, IPyAMSLayer), provides=IParagraphInnerEditor) |
|
322 @implementer(IInnerForm, IPropertiesEditForm, IAssociationParentForm) |
|
323 class ContactPhoneParagraphInnerEditForm(ContactPhoneParagraphPropertiesEditForm): |
|
324 """Contact paragraph inner edit form""" |
|
325 |
|
326 legend = None |
|
327 ajax_handler = 'inner-properties.json' |
|
328 |
|
329 |
|
330 **Marker interfaces:** |
|
331 |
|
332 +-----------------------------------+ |
|
333 |:py:class:`IPropertiesEditForm` | |
|
334 +=========+=========================+ |
|
335 | | Add Illustration form | |
|
336 +---------+-------------------------+ |
|
337 |
|
338 +-----------------------------------+ |
|
339 |:py:class:`IAssociationParentForm` | |
|
340 +===========+=======================+ |
|
341 | | Add Association form | |
|
342 +-----------+-----------------------+ |
|
343 |
|
344 .. image:: _static/associations_form.png |
|
345 |
|