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

Last change on this file since 932 was 932, checked in by mylan, 17 years ago

Fix minor differences for run product on Plone-3.0. There is only problem with portlet customization due to changing this mechanism completely in Plone-3.0.

  • 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            permission="zope2.View"
519            />
520    ...
521   
522
523viewlets.xml profile is added to profiles/default directory with new viewlet
524registration, ordered for specified viewlet manager.
525
526    >>> 'viewlets.xml' in os.listdir('profiles/default')
527    True
528    >>> cat('profiles/default/viewlets.xml')
529    <?xml version="1.0"?>
530    <object>
531    ...
532     <order manager="plone.portalheader"
533             based-on="Plone Default"
534             skinname="My Theme Name" >
535    ...
536        <viewlet name="quintagroup.theme.ploneexample.example" insert-after="*" />
537    <BLANKLINE>
538      </order>
539    <BLANKLINE>
540    </object>
541
542
543
544Hide EXISTING viewlet
545---------------------
546
547For that case you can use *viewlet_hidden* subtemplate
548
549    >>> paster('addcontent --no-interactive viewlet_hidden')
550    paster addcontent --no-interactive viewlet_hidden
551    Recursing into profiles
552    ...
553
554As we see from upper log - there is stuff for adding/updating profiles only.
555   
556
557There is viewlet.xml profile in profiles/default directory
558which hides viewlet for specified viewlet manager
559
560    >>> 'viewlets.xml' in os.listdir('profiles/default')
561    True
562    >>> cat('profiles/default/viewlets.xml')
563    <?xml version="1.0"?>
564    <object>
565    ...
566      <hidden manager="plone.portalheader" skinname="My Theme Name">
567    ...
568        <viewlet name="example" />
569    <BLANKLINE>
570      </hidden>
571    ...
572    </object>
573
574
575Adding ZEXPs importing
576======================
577
578Imagine situation, when you develop a theme, which uses some
579extra portal objects (documents with text for some potlets)
580Then customer of your theme can edit these objects according
581to his need.
582
583For this situation *import_zexps* subtemplate exists.
584
585*import_zexps* subtemplate extends your theme with
586mechanism for importing list of zexp formated files
587into portal root on theme instllation.
588
589    >>> paster('addcontent --no-interactive import_zexps')
590    paster addcontent --no-interactive import_zexps
591    ...
592    Recursing into import
593    ...
594    Recursing into profiles
595    ...
596    Inserting from setuphandlers.py_insert into ...
597    ...
598
599As we see from the upper log:
600   - 'import' directory was added into root of the theme
601   - profiles stuff was updated
602   - some stuff into setuphandlers.py module was inserted
603   
6041. There was empty 'import' directory added, where you
605   will put zexp objects for install into portal root.
606
607    >>> ls('import')
608    CONTENT.txt
609
610
6112. import_steps.xml was added in profiles/default directory (if does not exist yet),
612   which contains additional *quintagroup.theme.ploneexample.import_zexps* step.
613
614    >>> 'import_steps.xml' in os.listdir('profiles/default')
615    True
616
617    >>> cat('profiles/default/import_steps.xml')
618    <?xml version="1.0"?>
619    <import-steps>
620    ...
621      <import-step id="quintagroup.theme.ploneexample.import_zexps"
622                   version="..."
623                   handler="quintagroup.theme.ploneexample.setuphandlers.importZEXPs"
624                   title="My Theme Name: Import zexps objects">
625        <dependency step="skins" />
626        Import zexp objects into portal on My Theme Name theme installation
627      </import-step>
628    <BLANKLINE>
629    </import-steps>
630
6313. Check setuphandlers.py module - there must be importZEXPs function defined
632
633    >>> cat('setuphandlers.py')
634    def setupVarious(context):
635    ...
636    def importZEXPs(context):
637    ...
638
639Then simply prepare zexp objects and copy them to *import* directory.
640
Note: See TracBrowser for help on using the repository browser.