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

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

Update tests for viewlet subtemplate

  • Property svn:eol-style set to native
File size: 17.4 KB
Line 
1qplone3 theme template
2======================
3
4Contents
5--------
61. Overview
72. Creating theme package
83. Extending theme
94. Release theme package
10
11Overview
12========
13
14This theme template allows you to create plone3 theme python package
15with nested namespace. Initial package is theme package skeleton.
16This package can be extended (filled in) with:
17
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*
25local ZopeSkel command (extended in this product).
26
27Creating theme package
28======================
29
30Let's create plone-3 theme python package.
31Use `paster create` command for that::
32
33    >>> paster('create -t qplone3_theme plone.example --no-interactive --overwrite')
34    paster create -t qplone3_theme plone.example --no-interactive
35    ...
36
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
44    >>> package_dir = 'plone.example'
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]
48
49
50*qplone3_theme* template - creates theme with nested namespace.
51
52By default - theme is placed in
53
54    quintagroup.theme.<package name without dot> namespace
55
56in our case - quintagroup.theme.ploneexample
57
58So check namespaces::
59    >>> theme_namespace = os.path.join(package_dir,'quintagroup','theme','ploneexample')
60    >>> os.path.isdir(theme_namespace)
61    True
62
63Theme holds 3 subdirectories (browser, profiles, skins)::
64    >>> cd(theme_namespace)
65    >>> dirs = ('skins', 'browser', 'profiles')
66    >>> [True for d in dirs if d in os.listdir('.')]
67    [True, True, True]
68
69And initialization files (__init__.py, configure.zcml) ::
70    >>> files = ('__init__.py', 'configure.zcml')
71    >>> [True for d in files if d in os.listdir('.')]
72    [True, True]
73   
74
75*browser* directory
76-------------------
77
78Browser directory contains:
79  - 'templates' resource directory
80  - interfaces.py module with IThemeSpecific marker interface
81  - configure.zcml, with registered theme marker interface
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
105As we see, default theme name is 'Custom Theme', but on theme
106creation you can point out your own name. Check this ...
107
108First create configuration file with different skin name
109    >>> conf_data = """
110    ... [pastescript]
111    ... skinname=My Theme Name
112    ... """
113    >>> file('theme_config.conf','w').write(conf_data)
114
115Create the same theme with your own skin name and check this
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
130*skins* directory
131------------------------
132
133It contains only README.txt file and NO SKIN LAYERS YET.
134This is a job for localcommand ;)
135
136But check whether I am right ...
137    >>> cd('quintagroup/theme/ploneexample')
138    >>> ls('skins')
139    README.txt
140
141
142*profiles* directory.
143--------------------------------
144There is 'default' and uninstall profiles inside
145    >>> 'default' in os.listdir('profiles')
146    True
147    >>> 'uninstall' in os.listdir('profiles')
148    True
149
150There are the following items in default profile:
151 - import_steps.xml - for any reason.
152 - skins.xml - for registering skins directory
153
154    >>> cd('profiles/default')
155    >>> 'import_steps.xml' in os.listdir('.')
156    True
157    >>> 'skins.xml' in os.listdir('.')
158    True
159
160*skins.xml* profile makes your theme default on installation
161and uses layers list from 'Plone Default' for our theme,
162without any new layers (yet).
163
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
175*import_steps.xml* - call _setupVarious_ function from
176_setuphandlers.py_ module for additional installation steps.
177
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
188Look at setuphandlers.py module
189    >>> cd('../..')
190    >>> cat('setuphandlers.py')
191        def setupVarious(context):
192    ...
193
194
195Extending theme
196===============
197
198One of the best features, which ZopeSkel package brings, is *localcommand*.
199
200In this part I will show how you can extend a theme (generated with qplone3_theme
201ZopeSkel template) with additional useful stuff:
202
203  - skin layers
204  - views
205  - viewlets
206  - portlets
207  - css
208  - javascripts
209
210So, in qplone3_theme generated package you can use *addcontent* ZopeSkel
211local command.
212
213IMPORTANT TO NOTE: localcommand (addcontent in our case) should be
214called in any subdirectory of the generated theme package. And it won't
215work outside this package..
216
217    >>> paster('addcontent -a')
218    paster addcontent -a
219      ...
220        css_resource:    A Plone 3 CSS resource template
221      ...
222        import_zexps:    A template for importing zexp-objects into portal on installation
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      ...
232
233
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.
237
238
239Adding SKIN LAYER
240=================
241
242For that case use *skin_layer* subtemplate with *addcontent* local command
243
244    >>> paster('addcontent --no-interactive skin_layer')
245    paster addcontent --no-interactive skin_layer
246    Recursing into profiles
247    ...
248
249This command adds NEW 'skin_layer' (default name) directory to _skins_ directory,
250with only CONTENT.txt file inside.
251
252    >>> 'skin_layer' in os.listdir('skins')
253    True
254    >>> ls('skins/skin_layer')
255    CONTENT.txt
256
257*skins.xml* profile is also updated:
258
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
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
279
280
281Adding PORTLET
282==========================
283
284Only initialization files are available in portlets directory before adding new portlet.
285
286    >>> ls('portlets')
287    __init__.py
288    configure.zcml
289
290Add portlet with *portlet* subtemplate.
291
292    >>> paster('addcontent --no-interactive portlet')
293    paster addcontent --no-interactive portlet
294    Recursing into portlets
295    ...
296
297After executing this local command ...
298
299configure.zcml file in the theme root directory - includes portlets registry:
300
301    >>> cat('configure.zcml')
302    <configure
303    ...
304    <include package=".portlets" />
305    ...
306
307exampleportlet.pt template and exampleportlet.py script added to portlets directory.
308    >>> files = ('exampleportlet.pt', 'exampleportlet.py')
309    >>> [True for d in files if d in os.listdir('portlets')]
310    [True, True]
311
312And portlets/configure.zcml - register new portlet
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
328Finally, new portlet type is registered in portlets.xml profile
329
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
340Thanks to ZopeSkel developers for this subtempalte ;)
341
342
343
344Adding CSS resource
345===================
346
347Use *css_resource* subtemplate.
348
349    >>> paster("addcontent --no-interactive css_resource")
350    paster addcontent --no-interactive css_resource
351    Recursing into browser
352    ...
353    Recursing into profiles
354    ...
355
356This template adds (if does not exist yet) _stylesheets_ directory in _browser_
357directory
358
359    >>> 'stylesheets' in os.listdir('browser')
360    True
361
362In _stylesheets_ resource directory empty main.css stylesheet
363resource added
364
365    >>> 'main.css' in os.listdir('browser/stylesheets')
366    True
367    >>> cat('browser/stylesheets/main.css')
368    <BLANKLINE>
369
370
371New resource directory was registered in configure.zcml
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
384And cssregistry.xml profile was added into profiles/default directory with
385registered main.css stylesheet
386
387    >>> 'cssregistry.xml' in os.listdir('profiles/default')
388    True
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
402Adding JAVASCRIPT resource
403--------------------------
404
405Use *js_resource* subtemplate.
406
407    >>> paster('addcontent --no-interactive js_resource')
408    paster addcontent --no-interactive js_resource
409    Recursing into browser
410    ...
411    Recursing into profiles
412    ...
413
414This template adds (if does not exist yet) _scripts_ directory in _browser_
415directory
416
417    >>> 'scripts' in os.listdir('browser')
418    True
419
420
421Empty foo.js javascript file was added to _scripts_ directory
422
423    >>> 'foo.js' in os.listdir('browser/scripts')
424    True
425    >>> cat('browser/scripts/foo.js')
426    <BLANKLINE>
427
428
429New resource directory was registered in configure.zcml, if has not been registered yet.
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
442cssregistry.xml profile was added into profiles/default directory (if does not exist yet),
443and register new foo.js javascript resource.
444
445    >>> 'jsregistry.xml' in os.listdir('profiles/default')
446    True
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
461Test viewlets subtemplates
462==========================
463
464There are 2 types of viewlet subtemplates:
465 - viewlet_order
466 - viewlet_hidden
467
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.
471
472Ordered NEW viewlet
473-------------------
474
475Use *viewlet_order* subtemplate
476
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
486This template adds (if not exist ;)) _viewlets.py_ module in browser directory.
487With added Example ViewletBase class, which is bound to templates/example_viewlet.pt
488template
489
490    >>> 'viewlets.py' in os.listdir('browser')
491    True
492   
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    ...
498    class Example(common.ViewletBase):
499        render = ViewPageTemplateFile('templates/example_viewlet.pt')
500    <BLANKLINE>
501
502Check template file in templates directory.
503
504    >>> 'example_viewlet.pt' in os.listdir('browser/templates')
505    True
506    >>> cat('browser/templates/example_viewlet.pt')
507    <BLANKLINE>
508
509New viewlet is registered in configure.zcml
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"
518            layer=".interfaces.IThemeSpecific"
519            permission="zope2.View"
520            />
521    ...
522   
523
524viewlets.xml profile is added to profiles/default directory with new viewlet
525registration, ordered for specified viewlet manager.
526
527    >>> 'viewlets.xml' in os.listdir('profiles/default')
528    True
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
545Hide EXISTING viewlet
546---------------------
547
548For that case you can use *viewlet_hidden* subtemplate
549
550    >>> paster('addcontent --no-interactive viewlet_hidden')
551    paster addcontent --no-interactive viewlet_hidden
552    Recursing into profiles
553    ...
554
555As we see from upper log - there is stuff for adding/updating profiles only.
556   
557
558There is viewlet.xml profile in profiles/default directory
559which hides viewlet for specified viewlet manager
560
561    >>> 'viewlets.xml' in os.listdir('profiles/default')
562    True
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
576Adding ZEXPs importing
577======================
578
579Imagine situation, when you develop a theme, which uses some
580extra portal objects (documents with text for some potlets)
581Then customer of your theme can edit these objects according
582to his need.
583
584For this situation *import_zexps* subtemplate exists.
585
586*import_zexps* subtemplate extends your theme with
587mechanism for importing list of zexp formated files
588into portal root on theme instllation.
589
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
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
604   
6051. There was empty 'import' directory added, where you
606   will put zexp objects for install into portal root.
607
608    >>> ls('import')
609    CONTENT.txt
610
611
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.
614
615    >>> 'import_steps.xml' in os.listdir('profiles/default')
616    True
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"
623                   version="..."
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
6323. Check setuphandlers.py module - there must be importZEXPs function defined
633
634    >>> cat('setuphandlers.py')
635    def setupVarious(context):
636    ...
637    def importZEXPs(context):
638    ...
639
640Then simply prepare zexp objects and copy them to *import* directory.
641
Note: See TracBrowser for help on using the repository browser.