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

Last change on this file since 2573 was 2573, checked in by olha, 14 years ago

README file formatting corrected

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