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

Last change on this file since 2570 was 2570, checked in by mylan, 14 years ago

#218: Fix tests for ZopeSkel?>=2.15

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