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

Last change on this file since 3048 was 3048, checked in by mylan, 13 years ago

#238: Removde --no-interactive option (which needed only for tests) from README

File size: 20.8 KB
RevLine 
[1005]1qplone3 theme template
2======================
3
[1012]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.
[1011]9
[1013]10quintagroup.themetemplate package is used for development of all Quintagroup themes
[2573]11for Plone 3 at http://skins.quintagroup.com.
[1013]12
[1005]13Contents
14--------
151. Overview
162. Creating theme package
173. Extending theme
184. Release notes
19
20Overview
21========
22
[1011]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)
[1005]26
[1011]27After that you can extend theme package by the following elements:
28
[2573]29- skin-layer(s)
30- portlet(s)
31- viewlet(s)
32- css, js resource(s)
33- objects in zexp files
[1005]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
[3048]45    >>> paster('create -t qplone3_theme quintagroup.theme.example --overwrite')
46    paster create -t qplone3_theme quintagroup.theme.example ...
[1005]47    ...
48
[2573]49You got standard python package content with
[1005]50
[2573]51- *quintagroup* upper level namespace.
52- *quintagroup.theme.example-configure.zcml* - zcml file for adding into package-includes directory
53
[1005]54Check that::
55
[2570]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']
[1005]61
62
63*qplone3_theme* template - creates theme with nested namespace.
64
[2573]65By default - theme is placed in *quintagroup.theme.<3rd part of dotted package name> namespace*
[1005]66
[2570]67in our case - quintagroup.theme.example
[1005]68
69So check namespaces::
[1017]70
[2570]71    >>> theme_namespace = os.path.join(package_dir,'quintagroup','theme','example')
[1005]72    >>> os.path.isdir(theme_namespace)
73    True
74
75Theme holds 3 subdirectories (browser, profiles, skins)::
[1017]76
[1005]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) ::
[1017]83
[1005]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
[2573]94- 'templates' resource directory
95- interfaces.py module with IThemeSpecific marker interface
96- configure.zcml, with registered theme marker interface::
97
[1005]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
[2573]123First create configuration file with different skin name::
124
[1005]125    >>> conf_data = """
126    ... [pastescript]
127    ... skinname=My Theme Name
128    ... """
129    >>> file('theme_config.conf','w').write(conf_data)
130
[2573]131Create the same theme with your own skin name and check this::
132
[3048]133    >>> paster('create -t qplone3_theme quintagroup.theme.example --overwrite --config=theme_config.conf')
[1005]134    paster create ...
135    >>> cd(package_dir)
[2570]136    >>> cat('quintagroup/theme/example/browser/configure.zcml')
[1005]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
[2573]148-----------------
[1005]149
150It contains only README.txt file and NO SKIN LAYERS YET.
151This is a job for localcommand ;)
152
[2573]153But check whether I am right ...::
154
[2570]155    >>> cd('quintagroup/theme/example')
[1005]156    >>> ls('skins')
157    README.txt
158
159
[2573]160*profiles* directory
161--------------------
162
163There is 'default' and uninstall profiles inside::
164
[1005]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
[2573]172- import_steps.xml - for any reason.
173- skins.xml - for registering skins directory::
174
[1005]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,
[2573]183without any new layers (yet)::
[1005]184
185    >>> cat('skins.xml')
186    <?xml version="1.0"?>
[2570]187    ...
[1005]188    <object name="portal_skins" ...
[3048]189            default_skin="My Theme Name" request_varname="plone_skin">
[1005]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
[2573]198_setuphandlers.py_ module for additional installation steps::
[1005]199
200    >>> cat('import_steps.xml')
201    <?xml version="1.0"?>
202    ...
[2570]203    <import-step id="quintagroup.theme.example.various"
[1005]204    ...
[2570]205                 handler="quintagroup.theme.example.setuphandlers.setupVarious"
[1005]206    ...
207    </import-step>
208    ...
209
[2573]210Look at setuphandlers.py module::
211
[1005]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
[1010]223This part shows how you can extend a theme (generated with qplone3_theme
[1005]224ZopeSkel template) with additional useful stuff:
225
[2573]226- skin layers
227- views
228- viewlets
229- portlets
230- css
231- javascripts
232- objects in zexp files
[1005]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
[2573]239work outside this package::
[1005]240
241    >>> paster('addcontent -a')
242    paster addcontent -a
243      ...
[1360]244        css_dtml_skin:   A DTML file in skin layer with CSS registration
[1005]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
[2573]267For that case use *skin_layer* subtemplate with *addcontent* local command::
[1005]268
[3048]269    >>> paster('addcontent skin_layer')
270    paster addcontent skin_layer ...
[1005]271    Recursing into profiles
272    ...
273
274This command adds NEW 'skin_layer' (default name) directory to _skins_ directory,
[2573]275with only CONTENT.txt file inside::
[1005]276
277    >>> 'skin_layer' in os.listdir('skins')
278    True
279    >>> ls('skins/skin_layer')
280    CONTENT.txt
281
[2573]282*skins.xml* profile is also updated::
[1005]283
284    >>> cat('profiles/default/skins.xml')
285    <?xml version="1.0"?>
[2570]286    ...
[1005]287    <object name="portal_skins" allow_any="False" cookie_persistence="False"
[3048]288       default_skin="My Theme Name" request_varname="plone_skin">
[1005]289    ...
290     <object name="skin_layer"
291        meta_type="Filesystem Directory View"
[2570]292        directory="quintagroup.theme.example:skins/skin_layer"/>
293    ...
[1005]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
[2573]304- skin_layer directory was registered as Filesystem Directory View
305- skin_layer Filesystem Directory View was added to our theme layers list
[1005]306
[2573]307
[1005]308Adding PORTLET
[2573]309==============
[1005]310
[2573]311Only initialization files are available in portlets directory before adding new portlet::
[1005]312
313    >>> ls('portlets')
314    __init__.py
315    configure.zcml
316
[2573]317Add portlet with *portlet* subtemplate::
[1005]318
[3048]319    >>> paster('addcontent portlet')
320    paster addcontent portlet ...
[2570]321    ...
[1005]322    Recursing into portlets
323    ...
324
325After executing this local command ...
326
[2573]327configure.zcml file in the theme root directory - includes portlets registry::
[1005]328
329    >>> cat('configure.zcml')
330    <configure
331    ...
332    <include package=".portlets" />
333    ...
334
[2573]335exampleportlet.pt template and exampleportlet.py script added to portlets directory::
336 
337  >>> files = ('exampleportlet.pt', 'exampleportlet.py')
[1005]338    >>> [True for d in files if d in os.listdir('portlets')]
339    [True, True]
340
[2573]341And portlets/configure.zcml - register new portlet::
342
[1005]343    >>> cat('portlets/configure.zcml')
344    <configure
345    ...
346         <plone:portlet
[2570]347             name="quintagroup.theme.example.portlets.ExamplePortlet"
[1005]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
[2573]358Finally, new portlet type is registered in portlets.xml profile::
[1005]359
360    >>> cat('profiles/default/portlets.xml')
361    <?xml version="1.0"?>
362    ...
363       <portlet
[2570]364         addview="quintagroup.theme.example.portlets.ExamplePortlet"
[1005]365         title="Example portlet"
366         description=""
[2570]367         i18n:attributes="title; description"
368         />
[1005]369    ...
370
371Thanks to ZopeSkel developers for this subtempalte ;)
372
373
374Adding CSS resource
375===================
376
[2573]377Use *css_resource* subtemplate::
[1005]378
[3048]379    >>> paster("addcontent css_resource")
380    paster addcontent css_resource ...
[1005]381    Recursing into browser
382    ...
383    Recursing into profiles
384    ...
385
386This template adds (if does not exist yet) _stylesheets_ directory in _browser_
[2573]387directory::
[1005]388
389    >>> 'stylesheets' in os.listdir('browser')
390    True
391
392In _stylesheets_ resource directory empty main.css stylesheet
[2573]393resource added::
[1005]394
395    >>> 'main.css' in os.listdir('browser/stylesheets')
396    True
397    >>> cat('browser/stylesheets/main.css')
398    <BLANKLINE>
399
400
[2573]401New resource directory was registered in configure.zcml::
[1005]402
403    >>> cat('browser/configure.zcml')
404    <configure
405    ...
406        <browser:resourceDirectory
[2570]407            name="quintagroup.theme.example.stylesheets"
[1005]408            directory="stylesheets"
409            layer=".interfaces.IThemeSpecific"
410            />
411    ...
412   
413
414And cssregistry.xml profile was added into profiles/default directory with
[2573]415registered main.css stylesheet::
[1005]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=""
[2570]424        id="++resource++quintagroup.theme.example.stylesheets/main.css"
[1005]425        media="screen" rel="stylesheet" rendering="inline"
426        cacheable="True" compression="safe" cookable="True"
427        enabled="1" expression=""/>
428    ...
429
430
431
[1360]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
[2573]441- in dtml file you can use power of dtml language
442- this resource can be overriden by customer if he needs that
443
[1360]444IMPORTANT:
445For add css resource in registered skin layer - you should use this subtemplate
446in conjunction with *skin_layer* one.
447
448
[2573]449Use *css_dtml_skin* subtemplate::
[1360]450
[3048]451    >>> paster("addcontent css_dtml_skin")
452    paster addcontent css_dtml_skin ...
[1360]453    Recursing into profiles
454    ...
455    Recursing into skins
456    ...
457
[2573]458This template adds main.css.dtml file into skins/skin_layer folder::
[1360]459
460    >>> 'main.css.dtml' in os.listdir('skins/skin_layer')
461    True
462
[2573]463The main.css.dtml file already prepared to use as dtml-document::
464
[1360]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
[2573]475registered main.css stylesheet::
[1360]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=""
[2570]484        id="++resource++quintagroup.theme.example.stylesheets/main.css"
[1360]485        media="screen" rel="stylesheet" rendering="inline"
486        cacheable="True" compression="safe" cookable="True"
487        enabled="1" expression=""/>
488    ...
489
490
[1005]491Adding JAVASCRIPT resource
492--------------------------
493
[2573]494Use *js_resource* subtemplate::
[1005]495
[3048]496    >>> paster('addcontent js_resource')
497    paster addcontent js_resource ...
[1005]498    Recursing into browser
499    ...
500    Recursing into profiles
501    ...
502
503This template adds (if does not exist yet) _scripts_ directory in _browser_
[2573]504directory::
[1005]505
506    >>> 'scripts' in os.listdir('browser')
507    True
508
509
[2573]510Empty foo.js javascript file was added to _scripts_ directory::
[1005]511
512    >>> 'foo.js' in os.listdir('browser/scripts')
513    True
514    >>> cat('browser/scripts/foo.js')
515    <BLANKLINE>
516
517
[2573]518New resource directory was registered in configure.zcml, if has not been registered yet::
[1005]519
520    >>> cat('browser/configure.zcml')
521    <configure
522    ...
523        <browser:resourceDirectory
[2570]524            name="quintagroup.theme.example.scripts"
[1005]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),
[2573]532and register new foo.js javascript resource::
[1005]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
[2570]541        id="++resource++quintagroup.theme.example.scripts/foo.js"
[1005]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
[2573]555- viewlet_order
556- viewlet_hidden
557
[1005]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
[2573]565Use *viewlet_order* subtemplate::
[1005]566
[3048]567    >>> paster('addcontent viewlet_order')
568    paster addcontent viewlet_order ...
[1005]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
[2573]578template::
[1005]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
[2573]592Check template file in templates directory::
[1005]593
594    >>> 'example_viewlet.pt' in os.listdir('browser/templates')
595    True
596    >>> cat('browser/templates/example_viewlet.pt')
597    <BLANKLINE>
598
[2573]599New viewlet is registered in configure.zcml::
[1005]600
601    >>> cat('browser/configure.zcml')
602    <configure
603    ...
604       <browser:viewlet
[2570]605            name="quintagroup.theme.example.example"
[1005]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
[2573]615registration, ordered for specified viewlet manager::
[1005]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    ...
[2570]627        <viewlet name="quintagroup.theme.example.example" insert-after="*" />
[1005]628    <BLANKLINE>
629      </order>
630    <BLANKLINE>
631    </object>
632
633
634
635Hide EXISTING viewlet
636---------------------
637
[2573]638For that case you can use *viewlet_hidden* subtemplate::
[1005]639
[3048]640    >>> paster('addcontent viewlet_hidden')
641    paster addcontent viewlet_hidden ...
[1005]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
[2573]649which hides viewlet for specified viewlet manager::
[1005]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
[2573]678into portal root on theme instllation::
[1005]679
[3048]680    >>> paster('addcontent import_zexps')
681    paster addcontent import_zexps ...
[1005]682    ...
683    Recursing into import
684    ...
685    Recursing into profiles
686    ...
[1363]687    Inserting from profiles.zcml_insert ...
688    ...
[1005]689    Inserting from setuphandlers.py_insert into ...
690    ...
691
[2573]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
[1005]698   
6991. There was empty 'import' directory added, where you
[2573]700   will put zexp objects for install into portal root.::
[1005]701
702    >>> ls('import')
703    CONTENT.txt
704
705
[1363]7062. import_steps.xml was added in profiles/import_zexps directory,
[2573]707   which contains additional *quintagroup.theme.example.import_zexps* step::
[1005]708
[1363]709    >>> 'import_zexps' in os.listdir('profiles')
[1005]710    True
[1363]711    >>> 'import_steps.xml' in os.listdir('profiles/import_zexps')
712    True
[1005]713
[1363]714    >>> cat('profiles/import_zexps/import_steps.xml')
[1005]715    <?xml version="1.0"?>
716    ...
[2570]717      <import-step id="quintagroup.theme.example.import_zexps"
[1005]718                   version="..."
[2570]719                   handler="quintagroup.theme.example.setuphandlers.importZEXPs"
[1005]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>
[1363]724    ...
[1005]725
[1363]7263. profiles.zcml configuration updated with new genericsetup profile for zexps
[2573]727   importing::
[1005]728
[1363]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   
[2573]7424. Check setuphandlers.py module - there must be importZEXPs function defined::
[1363]743
[1005]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
[2573]758- remove *theme_vars* argument (its value is useful only for theme development)
[1005]759
[2573]760- remove *entry_points* argument (same reason). It's useless in plone for now.
[1005]761
[2573]762- And remove *paster_plugins* argument too (it has sence in conjunction with entry_points during theme developing)
[1005]763
764Steps mentioned above prevent possible problems with
765theme distribution/deployment.
[2571]766
767Notes:
768------
769
[2573]770* quintagroup.themetemplate v0.25 compatible with ZopeSkel >= 2.15
[2571]771
Note: See TracBrowser for help on using the repository browser.