source: products/qtheme.template/trunk/qthemetemplate/README.txt @ 2539

Last change on this file since 2539 was 998, checked in by mylan, 15 years ago

Add Release Notes to README

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