source: products/quintagroup.themetemplate/trunk/quintagroup/themetemplate/README.txt @ 1363

Last change on this file since 1363 was 1363, checked in by mylan, 14 years ago

Fix #109: Move importing zexps into separate profile, updated doctests

File size: 20.9 KB
Line 
1qplone3 theme template
2======================
3
4quintagroup.themetemplate is an enhanced "Plone 3 Theme" template from Zopeskel,
5that includes addcontent local command, which allows you to extend base Plone theme
6by additional elements, such as: skin layers, portlets, viewlets, css and js resources,
7and objects in zexp files. This package is an analogue of Archetype template in terms
8of its functionality.
9
10quintagroup.themetemplate package is used for development of all Quintagroup themes
11for Plone 3 (http://skins.quintagroup.com).
12
13Contents
14--------
151. Overview
162. Creating theme package
173. Extending theme
184. Release notes
19
20Overview
21========
22
23This theme template allows you to create initial theme package skeleton,
24i.e. create plone3 theme python package with nested namespace (this is different from
25deafult plone3_theme template in Zopeskel)
26
27After that you can extend theme package by the following elements:
28
29  - skin-layer(s)
30  - portlet(s)
31  - viewlet(s)
32  - css, js resource(s)
33  - objects in zexp files
34
35Creation of a package is performed with *paster create* PasteScript command.
36Theme extending with other resources can be done with *paster addcontent*
37local ZopeSkel command (extended in this product).
38
39Creating theme package
40======================
41
42Let's create plone-3 theme python package.
43Use `paster create` command for that::
44
45    >>> paster('create -t qplone3_theme plone.example --no-interactive --overwrite')
46    paster create -t qplone3_theme plone.example --no-interactive
47    ...
48
49You got standard python package content with
50  - *quintagroup* upper level namespace.
51  - *plone.example-configure.zcml* - zcml file
52    for adding into package-includes directory
53
54Check that::
55
56    >>> package_dir = 'plone.example'
57    >>> objects = ('setup.py', 'quintagroup', 'plone.example-configure.zcml')
58    >>> [True for o in objects if o in os.listdir(package_dir)]
59    [True, True, True]
60
61
62*qplone3_theme* template - creates theme with nested namespace.
63
64By default - theme is placed in
65
66    quintagroup.theme.<package name without dot> namespace
67
68in our case - quintagroup.theme.ploneexample
69
70So check namespaces::
71
72    >>> theme_namespace = os.path.join(package_dir,'quintagroup','theme','ploneexample')
73    >>> os.path.isdir(theme_namespace)
74    True
75
76Theme holds 3 subdirectories (browser, profiles, skins)::
77
78    >>> cd(theme_namespace)
79    >>> dirs = ('skins', 'browser', 'profiles')
80    >>> [True for d in dirs if d in os.listdir('.')]
81    [True, True, True]
82
83And initialization files (__init__.py, configure.zcml) ::
84
85    >>> files = ('__init__.py', 'configure.zcml')
86    >>> [True for d in files if d in os.listdir('.')]
87    [True, True]
88   
89
90*browser* directory
91-------------------
92
93Browser directory contains:
94  - 'templates' resource directory
95  - interfaces.py module with IThemeSpecific marker interface
96  - configure.zcml, with registered theme marker interface
97
98    >>> ls('browser')
99    __init__.py
100    configure.zcml
101    interfaces.py
102    templates
103
104    >>> cat('browser/interfaces.py')
105    from plone.theme.interfaces import IDefaultPloneLayer
106    <BLANKLINE>
107    class IThemeSpecific(IDefaultPloneLayer):
108    ...
109
110    >>> cat('browser/configure.zcml')
111    <configure
112    ...
113        <interface
114            interface=".interfaces.IThemeSpecific"
115            type="zope.publisher.interfaces.browser.IBrowserSkinType"
116            name="Custom Theme"
117            />
118    ...
119
120As we see, default theme name is 'Custom Theme', but on theme
121creation you can point out your own name. Check this ...
122
123First create configuration file with different skin name
124    >>> conf_data = """
125    ... [pastescript]
126    ... skinname=My Theme Name
127    ... """
128    >>> file('theme_config.conf','w').write(conf_data)
129
130Create the same theme with your own skin name and check this
131    >>> paster('create -t qplone3_theme plone.example --no-interactive --overwrite --config=theme_config.conf')
132    paster create ...
133    >>> cd(package_dir)
134    >>> cat('quintagroup/theme/ploneexample/browser/configure.zcml')
135    <configure
136    ...
137        <interface
138            interface=".interfaces.IThemeSpecific"
139            type="zope.publisher.interfaces.browser.IBrowserSkinType"
140            name="My Theme Name"
141            />
142    ...
143
144
145*skins* directory
146------------------------
147
148It contains only README.txt file and NO SKIN LAYERS YET.
149This is a job for localcommand ;)
150
151But check whether I am right ...
152    >>> cd('quintagroup/theme/ploneexample')
153    >>> ls('skins')
154    README.txt
155
156
157*profiles* directory.
158--------------------------------
159There is 'default' and uninstall profiles inside
160    >>> 'default' in os.listdir('profiles')
161    True
162    >>> 'uninstall' in os.listdir('profiles')
163    True
164
165There are the following items in default profile:
166 - import_steps.xml - for any reason.
167 - skins.xml - for registering skins directory
168
169    >>> cd('profiles/default')
170    >>> 'import_steps.xml' in os.listdir('.')
171    True
172    >>> 'skins.xml' in os.listdir('.')
173    True
174
175*skins.xml* profile makes your theme default on installation
176and uses layers list from 'Plone Default' for our theme,
177without any new layers (yet).
178
179    >>> cat('skins.xml')
180    <?xml version="1.0"?>
181    <object name="portal_skins" ...
182            default_skin="My Theme Name">
183    ...
184    <skin-path name="My Theme Name" based-on="Plone Default">
185      <!-- -*- extra layer stuff goes here -*- -->
186    <BLANKLINE>
187    </skin-path>
188    ...
189
190*import_steps.xml* - call _setupVarious_ function from
191_setuphandlers.py_ module for additional installation steps.
192
193    >>> cat('import_steps.xml')
194    <?xml version="1.0"?>
195    ...
196    <import-step id="quintagroup.theme.ploneexample.various"
197    ...
198                 handler="quintagroup.theme.ploneexample.setuphandlers.setupVarious"
199    ...
200    </import-step>
201    ...
202
203Look at setuphandlers.py module
204    >>> cd('../..')
205    >>> cat('setuphandlers.py')
206        def setupVarious(context):
207    ...
208
209
210Extending theme
211===============
212
213One of the best features, which ZopeSkel package brings, is *localcommand*.
214
215This part shows how you can extend a theme (generated with qplone3_theme
216ZopeSkel template) with additional useful stuff:
217
218  - skin layers
219  - views
220  - viewlets
221  - portlets
222  - css
223  - javascripts
224  - objects in zexp files
225
226So, in qplone3_theme generated package you can use *addcontent* ZopeSkel
227local command.
228
229IMPORTANT TO NOTE: localcommand (addcontent in our case) should be
230called in any subdirectory of the generated theme package. And it won't
231work outside this package..
232
233    >>> paster('addcontent -a')
234    paster addcontent -a
235      ...
236        css_dtml_skin:   A DTML file in skin layer with CSS registration
237        css_resource:    A Plone 3 CSS resource template
238      ...
239        import_zexps:    A template for importing zexp-objects into portal on installation
240        js_resource:     A Plone 3 JS resource template
241      N portlet:         A Plone 3 portlet
242      ...
243        skin_layer:      A Plone 3 Skin Layer
244      ...
245      N view:            A browser view skeleton
246        viewlet_hidden:  A Plone 3 Hidden Viewlet template
247        viewlet_order:   A Plone 3 Order Viewlet template
248      ...
249
250
251We can see a list of extention subtemplates, which can be used for our theme.
252'N' character tells us that these subtemplates are registered for other (archetype)
253template, but it does not matter - they can correctly extend our theme.
254
255
256Adding SKIN LAYER
257=================
258
259For that case use *skin_layer* subtemplate with *addcontent* local command
260
261    >>> paster('addcontent --no-interactive skin_layer')
262    paster addcontent --no-interactive skin_layer
263    Recursing into profiles
264    ...
265
266This command adds NEW 'skin_layer' (default name) directory to _skins_ directory,
267with only CONTENT.txt file inside.
268
269    >>> 'skin_layer' in os.listdir('skins')
270    True
271    >>> ls('skins/skin_layer')
272    CONTENT.txt
273
274*skins.xml* profile is also updated:
275
276    >>> cat('profiles/default/skins.xml')
277    <?xml version="1.0"?>
278    <object name="portal_skins" allow_any="False" cookie_persistence="False"
279       default_skin="My Theme Name">
280    ...
281     <object name="skin_layer"
282        meta_type="Filesystem Directory View"
283        directory="quintagroup.theme.ploneexample:skins/skin_layer"/>
284    <BLANKLINE>
285     <skin-path name="My Theme Name" based-on="Plone Default">
286    ...
287      <layer name="skin_layer"
288         insert-after="custom"/>
289    <BLANKLINE>
290     </skin-path>
291    ...
292
293We can see, that:
294  - skin_layer directory was registered as Filesystem Directory View
295  - skin_layer Filesystem Directory View was added to our theme layers list
296
297
298Adding PORTLET
299==========================
300
301Only initialization files are available in portlets directory before adding new portlet.
302
303    >>> ls('portlets')
304    __init__.py
305    configure.zcml
306
307Add portlet with *portlet* subtemplate.
308
309    >>> paster('addcontent --no-interactive portlet')
310    paster addcontent --no-interactive portlet
311    Recursing into portlets
312    ...
313
314After executing this local command ...
315
316configure.zcml file in the theme root directory - includes portlets registry:
317
318    >>> cat('configure.zcml')
319    <configure
320    ...
321    <include package=".portlets" />
322    ...
323
324exampleportlet.pt template and exampleportlet.py script added to portlets directory.
325    >>> files = ('exampleportlet.pt', 'exampleportlet.py')
326    >>> [True for d in files if d in os.listdir('portlets')]
327    [True, True]
328
329And portlets/configure.zcml - register new portlet
330    >>> cat('portlets/configure.zcml')
331    <configure
332    ...
333         <plone:portlet
334             name="quintagroup.theme.ploneexample.portlets.ExamplePortlet"
335             interface=".exampleportlet.IExamplePortlet"
336             assignment=".exampleportlet.Assignment"
337             view_permission="zope2.View"
338             edit_permission="cmf.ManagePortal"
339             renderer=".exampleportlet.Renderer"
340             addview=".exampleportlet.AddForm"
341             editview=".exampleportlet.EditForm"
342             />
343    ...
344
345Finally, new portlet type is registered in portlets.xml profile
346
347    >>> cat('profiles/default/portlets.xml')
348    <?xml version="1.0"?>
349    ...
350       <portlet
351         addview="quintagroup.theme.ploneexample.portlets.ExamplePortlet"
352         title="Example portlet"
353         description=""
354       />
355    ...
356
357Thanks to ZopeSkel developers for this subtempalte ;)
358
359
360
361Adding CSS resource
362===================
363
364Use *css_resource* subtemplate.
365
366    >>> paster("addcontent --no-interactive css_resource")
367    paster addcontent --no-interactive css_resource
368    Recursing into browser
369    ...
370    Recursing into profiles
371    ...
372
373This template adds (if does not exist yet) _stylesheets_ directory in _browser_
374directory
375
376    >>> 'stylesheets' in os.listdir('browser')
377    True
378
379In _stylesheets_ resource directory empty main.css stylesheet
380resource added
381
382    >>> 'main.css' in os.listdir('browser/stylesheets')
383    True
384    >>> cat('browser/stylesheets/main.css')
385    <BLANKLINE>
386
387
388New resource directory was registered in configure.zcml
389
390    >>> cat('browser/configure.zcml')
391    <configure
392    ...
393        <browser:resourceDirectory
394            name="quintagroup.theme.ploneexample.stylesheets"
395            directory="stylesheets"
396            layer=".interfaces.IThemeSpecific"
397            />
398    ...
399   
400
401And cssregistry.xml profile was added into profiles/default directory with
402registered main.css stylesheet
403
404    >>> 'cssregistry.xml' in os.listdir('profiles/default')
405    True
406    >>> cat('profiles/default/cssregistry.xml')
407    <?xml version="1.0"?>
408    <object name="portal_css">
409    <BLANKLINE>
410     <stylesheet title=""
411        id="++resource++quintagroup.theme.ploneexample.stylesheets/main.css"
412        media="screen" rel="stylesheet" rendering="inline"
413        cacheable="True" compression="safe" cookable="True"
414        enabled="1" expression=""/>
415    ...
416
417
418
419Adding CSS resource as dtml-file into skins layer
420=================================================
421
422This template actually absolutely same to the previouse one, but layer_name
423variable added to point in which skin layer css dtml-file should be added to.
424And, of course, css resource added into pointing *skins/<layer_name>/<css_reseource_name>.dtml* file.
425
426This subtemplate has several benefits before registering css as resource layer:
427  - in dtml file you can use power of dtml language
428  - this resource can be overriden by customer if he needs that
429
430IMPORTANT:
431For add css resource in registered skin layer - you should use this subtemplate
432in conjunction with *skin_layer* one.
433
434
435Use *css_dtml_skin* subtemplate.
436
437    >>> paster("addcontent --no-interactive css_dtml_skin")
438    paster addcontent --no-interactive css_dtml_skin
439    Recursing into profiles
440    ...
441    Recursing into skins
442    ...
443
444This template adds main.css.dtml file into skins/skin_layer folder
445
446    >>> 'main.css.dtml' in os.listdir('skins/skin_layer')
447    True
448
449The main.css.dtml file already prepared to use as dtml-document
450    >>> cat('skins/skin_layer/main.css.dtml')
451    /*
452    ...
453    /* <dtml-with base_properties> (do not remove this :) */
454    ...
455    /* </dtml-with> */
456    <BLANKLINE>
457 
458
459And cssregistry.xml profile was added into profiles/default directory with
460registered main.css stylesheet
461
462    >>> 'cssregistry.xml' in os.listdir('profiles/default')
463    True
464    >>> cat('profiles/default/cssregistry.xml')
465    <?xml version="1.0"?>
466    <object name="portal_css">
467    <BLANKLINE>
468     <stylesheet title=""
469        id="++resource++quintagroup.theme.ploneexample.stylesheets/main.css"
470        media="screen" rel="stylesheet" rendering="inline"
471        cacheable="True" compression="safe" cookable="True"
472        enabled="1" expression=""/>
473    ...
474
475
476Adding JAVASCRIPT resource
477--------------------------
478
479Use *js_resource* subtemplate.
480
481    >>> paster('addcontent --no-interactive js_resource')
482    paster addcontent --no-interactive js_resource
483    Recursing into browser
484    ...
485    Recursing into profiles
486    ...
487
488This template adds (if does not exist yet) _scripts_ directory in _browser_
489directory
490
491    >>> 'scripts' in os.listdir('browser')
492    True
493
494
495Empty foo.js javascript file was added to _scripts_ directory
496
497    >>> 'foo.js' in os.listdir('browser/scripts')
498    True
499    >>> cat('browser/scripts/foo.js')
500    <BLANKLINE>
501
502
503New resource directory was registered in configure.zcml, if has not been registered yet.
504
505    >>> cat('browser/configure.zcml')
506    <configure
507    ...
508        <browser:resourceDirectory
509            name="quintagroup.theme.ploneexample.scripts"
510            directory="scripts"
511            layer=".interfaces.IThemeSpecific"
512            />
513    ...
514   
515
516cssregistry.xml profile was added into profiles/default directory (if does not exist yet),
517and register new foo.js javascript resource.
518
519    >>> 'jsregistry.xml' in os.listdir('profiles/default')
520    True
521    >>> cat('profiles/default/jsregistry.xml')
522    <?xml version="1.0"?>
523    <object name="portal_javascripts">
524    ...
525     <javascript
526        id="++resource++quintagroup.theme.ploneexample.scripts/foo.js"
527        inline="False" cacheable="True" compression="safe"
528        cookable="True" enabled="1"
529        expression=""
530        />
531    ...
532
533
534
535Test viewlets subtemplates
536==========================
537
538There are 2 types of viewlet subtemplates:
539 - viewlet_order
540 - viewlet_hidden
541
542The first one is used for adding new viewlets and setting
543viewlets order for the ViewletManager, the second one only hides
544viewlet in pointed ViewletManager.
545
546Ordered NEW viewlet
547-------------------
548
549Use *viewlet_order* subtemplate
550
551    >>> paster('addcontent --no-interactive viewlet_order')
552    paster addcontent --no-interactive viewlet_order
553    Recursing into browser
554    ...
555    Recursing into templates
556    ...
557    Recursing into profiles
558    ...
559
560This template adds (if not exist ;)) _viewlets.py_ module in browser directory.
561With added Example ViewletBase class, which is bound to templates/example_viewlet.pt
562template
563
564    >>> 'viewlets.py' in os.listdir('browser')
565    True
566   
567    >>> cat('browser/viewlets.py')
568    from Products.CMFCore.utils import getToolByName
569    from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
570    from plone.app.layout.viewlets import common
571    ...
572    class Example(common.ViewletBase):
573        render = ViewPageTemplateFile('templates/example_viewlet.pt')
574    <BLANKLINE>
575
576Check template file in templates directory.
577
578    >>> 'example_viewlet.pt' in os.listdir('browser/templates')
579    True
580    >>> cat('browser/templates/example_viewlet.pt')
581    <BLANKLINE>
582
583New viewlet is registered in configure.zcml
584
585    >>> cat('browser/configure.zcml')
586    <configure
587    ...
588       <browser:viewlet
589            name="quintagroup.theme.ploneexample.example"
590            manager="plone.app.layout.viewlets.interfaces.IPortalHeader"
591            class=".viewlets.Example"
592            layer=".interfaces.IThemeSpecific"
593            permission="zope2.View"
594            />
595    ...
596   
597
598viewlets.xml profile is added to profiles/default directory with new viewlet
599registration, ordered for specified viewlet manager.
600
601    >>> 'viewlets.xml' in os.listdir('profiles/default')
602    True
603    >>> cat('profiles/default/viewlets.xml')
604    <?xml version="1.0"?>
605    <object>
606    ...
607     <order manager="plone.portalheader"
608             based-on="Plone Default"
609             skinname="My Theme Name" >
610    ...
611        <viewlet name="quintagroup.theme.ploneexample.example" insert-after="*" />
612    <BLANKLINE>
613      </order>
614    <BLANKLINE>
615    </object>
616
617
618
619Hide EXISTING viewlet
620---------------------
621
622For that case you can use *viewlet_hidden* subtemplate
623
624    >>> paster('addcontent --no-interactive viewlet_hidden')
625    paster addcontent --no-interactive viewlet_hidden
626    Recursing into profiles
627    ...
628
629As we see from upper log - there is stuff for adding/updating profiles only.
630   
631
632There is viewlet.xml profile in profiles/default directory
633which hides viewlet for specified viewlet manager
634
635    >>> 'viewlets.xml' in os.listdir('profiles/default')
636    True
637    >>> cat('profiles/default/viewlets.xml')
638    <?xml version="1.0"?>
639    <object>
640    ...
641      <hidden manager="plone.portalheader" skinname="My Theme Name">
642    ...
643        <viewlet name="example" />
644    <BLANKLINE>
645      </hidden>
646    ...
647    </object>
648
649
650Adding ZEXPs importing
651======================
652
653Imagine situation, when you develop a theme, which uses some
654extra portal objects (documents with text for some potlets)
655Then customer of your theme can edit these objects according
656to his need.
657
658For this situation *import_zexps* subtemplate exists.
659
660*import_zexps* subtemplate extends your theme with
661mechanism for importing list of zexp formated files
662into portal root on theme instllation.
663
664    >>> paster('addcontent --no-interactive import_zexps')
665    paster addcontent --no-interactive import_zexps
666    ...
667    Recursing into import
668    ...
669    Recursing into profiles
670    ...
671    Inserting from profiles.zcml_insert ...
672    ...
673    Inserting from setuphandlers.py_insert into ...
674    ...
675
676As we see from the upper log:
677   - 'import' directory was added into root of the theme
678   - profiles stuff was updated
679   - profiles.zcml file is updated
680   - some stuff into setuphandlers.py module was inserted
681   
6821. There was empty 'import' directory added, where you
683   will put zexp objects for install into portal root.
684
685    >>> ls('import')
686    CONTENT.txt
687
688
6892. import_steps.xml was added in profiles/import_zexps directory,
690   which contains additional *quintagroup.theme.ploneexample.import_zexps* step.
691
692    >>> 'import_zexps' in os.listdir('profiles')
693    True
694    >>> 'import_steps.xml' in os.listdir('profiles/import_zexps')
695    True
696
697    >>> cat('profiles/import_zexps/import_steps.xml')
698    <?xml version="1.0"?>
699    ...
700      <import-step id="quintagroup.theme.ploneexample.import_zexps"
701                   version="..."
702                   handler="quintagroup.theme.ploneexample.setuphandlers.importZEXPs"
703                   title="My Theme Name: Import zexps objects">
704        Import zexp objects into portal on My Theme Name theme installation
705      </import-step>
706    <BLANKLINE>
707    ...
708
7093. profiles.zcml configuration updated with new genericsetup profile for zexps
710   importing.
711
712    >>> cat('profiles.zcml')
713    <configure
714    ...
715      <genericsetup:registerProfile
716        name="import_zexps"
717        title="My Theme Name: Import ZEXPs"
718        directory="profiles/import_zexps"
719        description='Extension profile for importing objects of the "My Theme Name" Plone theme.'
720        provides="Products.GenericSetup.interfaces.EXTENSION"
721        />
722    <BLANKLINE>
723    ...
724   
7254. Check setuphandlers.py module - there must be importZEXPs function defined
726
727    >>> cat('setuphandlers.py')
728    def setupVarious(context):
729    ...
730    def importZEXPs(context):
731    ...
732
733Then simply prepare zexp objects and copy them to *import* directory.
734
735
736RELEASE NOTES !
737===============
738
739Before releasing theme - I suggest to clean up setup.py script:
740
741 - remove *theme_vars* argument (its value is useful only for
742   theme development)
743
744 - remove *entry_points* argument (same reason).
745   It's useless in plone for now.
746
747 - And remove *paster_plugins* argument too (it has sence
748   in conjunction with entry_points during theme developing)
749
750Steps mentioned above prevent possible problems with
751theme distribution/deployment.
Note: See TracBrowser for help on using the repository browser.