source: products/qPloneSkinDump/trunk/utils.py @ 1552

Last change on this file since 1552 was 74, checked in by chervol, 14 years ago

initial plone2.0.5 branch import

  • Property svn:eol-style set to native
File size: 22.5 KB
Line 
1import os, re, string, sets, time
2from zope.app import zapi
3from App.config import getConfiguration
4from Products.CMFCore.utils import getToolByName
5
6from config import *
7from write_utils import writeProps, writeFileContent, writeObjectsMeta
8
9from zope.interface import providedBy
10from zope.schema import getFields
11from zope.component import getMultiAdapter, getUtility, getSiteManager
12from zope.publisher.interfaces.browser import IBrowserRequest
13from five.customerize.interfaces import IViewTemplateContainer, ITTWViewTemplate
14from plone.portlets.interfaces import IPortletAssignmentMapping, IPortletManager, IPlacelessPortletManager
15from plone.portlets.interfaces import IPortletContext, IPortletDataProvider
16from plone.portlets.interfaces import ILocalPortletAssignmentManager
17from plone.app.customerize.registration import generateIdFromRegistration, interfaceName
18
19from Products.GenericSetup.utils import _getDottedName, _resolveDottedName
20from Products.GenericSetup.interfaces import IBody
21from Products.GenericSetup.context import BaseContext
22
23CSS_PATTERN = re.compile("^.+\.css$")
24JS_PATTERN = re.compile("^.+\.js$")
25_write_custom_meta_type_list = [
26    'Controller Page Template',
27    'Controller Python Script',
28    'Controller Validator',
29    'DTML Method',
30    'File',
31    'Image',
32    'Page Template',
33    'Script (Python)' ]
34_acceptable_meta_types = _write_custom_meta_type_list + ['Folder',]
35ospJoin = os.path.join
36
37def get_product_listdirs():
38    """ Return a contents of all plugged in Products directories."""
39    products = sets.Set()
40    [products.update(os.listdir(product_dir)) for product_dir in Products.__path__]
41    return products
42
43def get_id(obj):
44    """ Get real object's id."""
45    id = callable(obj.id) and obj.id() or obj.id
46    assert obj.getId() == id, "expected identical ids: '%s' != '%s'" % (obj.getId(), id)
47    return id
48
49def getData(obj, meta_type):
50    """ Return object's data."""
51    return meta_type in ['Image', 'File'] and obj.manage_FTPget() or obj.document_src()
52
53def dumpPortalViewCustomization(context):
54    result = []
55
56    components = getSiteManager(context)
57    localregs = [reg for reg in components.registeredAdapters() if (len(reg.required) in (2, 4, 5) and
58                                                                     reg.required[1].isOrExtends(IBrowserRequest) and
59                                                                     ITTWViewTemplate.providedBy(reg.factory))]
60    container = getUtility(IViewTemplateContainer)
61#    import pdb;pdb.set_trace()
62    for lreg in localregs:
63#        ttw_id = generateIdFromRegistration(lreg)
64        ttw_id = lreg.factory.__name__
65                           
66        if ttw_id not in container.objectIds():
67            continue
68        ttw = getattr(container, ttw_id)
69        ttw_info = {'for_name'  : interfaceName(lreg.required[0]),
70                    'type_name' : interfaceName(lreg.required[-1]),
71                    'view_name' : lreg.name,
72                    'kwargs'    : {'text' : ttw._text,
73                                   'content_type' : ttw.content_type,
74                                   'encoding' : ttw.output_encoding,
75                                   }}
76        result.append(ttw_info)
77
78    return result
79
80def extractInfoFromAssignment(name, assignment):
81    klass = assignment.__class__
82    a = {'name' : name, 'class' : '%s' % _getDottedName(klass)}
83    data = assignment.data
84    kwargs = {}
85    for i in list(providedBy(data)):
86        if i.isOrExtends(IPortletDataProvider):
87            for field_name, field in getFields(i).items():
88                kwargs[field_name] = field.get(assignment)
89    a['kwargs'] = kwargs
90    return a
91
92def extractSiteWidePortlets(context, managers):
93    """ Extract site-wide portlets
94        Data structure:
95            '__site-wide-portlets__', [(<manager1_name>, <manager1_info>),
96                                       (<manager2_name>, <manager2_info>),
97                                       (<manager3_name>, <manager3_info>)])
98            <manager_info>:
99                {'category1' : <catmapping1>,
100                 'category2' : <catmapping2>}
101            <catmapping>:
102                {'key1' : <mapping1>,
103                 'key2' : <mapping2>}
104            <mapping>:
105                {'assignment_name1' : <assignment1>,
106                 'assignment_name2' : <assignment2>}
107            <assignment>:
108                {'name'   : 'Assignment',
109                 'class'  : 'dotted.path.to.assignment.class',
110                 'kwargs' : {'parameter1' : 'value1',
111                             'parameter2' : 'value2'}
112    """
113    info = []
114    for manager_name, manager in managers:
115        manager_info = {}
116        for category, catmapping in manager.items():
117            catmapping_info = {}
118            for key, mapping in catmapping.items():
119                mapping_info = {}
120                for name, assignment in mapping.items():
121                    mapping_info[name] = extractInfoFromAssignment(name, assignment)
122                catmapping_info[key] = mapping_info
123            manager_info[category] = catmapping_info
124        info.append((manager_name, manager_info))
125    return info
126
127def extractContextPortletsFromManager(context, manager):
128    """ Extract all contextual portlets from given object and portlet manager, and portlets blacklists
129        Data structure:
130        <manager_info> =
131            {'blacklists'  : [(GROUP_CATEGORY, True),
132                              (CONTENT_TYPE_CATEGORY, False),
133                              (CONTEXT_CATEGORY, None)],
134             'assignments' : [{'name'   : 'Assignment-2',
135                               'class'  : 'dotted.path.to.assignment.class',
136                               'kwargs' : {'parameter1' : 'value1',
137                                          'parameter2' : 'value2'}},
138                              {'name'   : 'Assignment',
139                               'class'  : 'dotted.path.to.assignment.class',
140                               'kwargs' : {'parameter1' : 'value1',
141                                            'parameter2' : 'value2'}]}
142    """
143
144    info = {}
145    info['assignments'] = assignments = []
146    info['blacklists'] = blacklists = []
147
148    # Extract contextual portlets
149    mapping = getMultiAdapter((context, manager), IPortletAssignmentMapping, context=context)
150    for name, assignment in mapping.items():
151        assignments.append(extractInfoFromAssignment(name, assignment))
152
153    # Extract blacklists for given object and manager
154    localassignmentmanager = getMultiAdapter((context, manager), ILocalPortletAssignmentManager)
155    blacklist = localassignmentmanager._getBlacklist()
156    if blacklist is not None:
157        for category, key in blacklist.items():
158            blacklists.append((category, key))
159
160    return info
161
162def extractPortletsFromContext(context, slot_structure, typesToShow, managers):
163    """ Extract portlets for given object assigned through all portlet managers.
164        Data structure:
165            ('unique/path/to/context', [(<manager1_name>, <manager1_info>),
166                                        (<manager2_name>, <manager2_info>),
167                                        (<manager3_name>, <manager3_info>)])
168    """
169
170
171    info = []
172    key = '/'.join(context.getPhysicalPath()[2:])
173
174    for name, manager in managers:
175        info.append((name, extractContextPortletsFromManager(context, manager)))
176
177    slot_structure.append((key, info))
178
179    return slot_structure
180
181def dumpAllPortlets(context, slot_structure, typesToShow, managers):
182    extractPortletsFromContext(context, slot_structure, typesToShow, managers)
183    if getattr(context.aq_base, 'isPrincipiaFolderish', 0):
184        for id, obj in context.contentItems():
185            if obj.portal_type in typesToShow:
186                dumpAllPortlets(obj, slot_structure, typesToShow, managers)
187
188    return slot_structure
189
190def dumpPortlets(context, dump_policy, dump_portlets_selection):
191    """ Extract portlets from given set of objects and site-wide portlets too.
192        Data structure:
193            SLOT_STRUCTURE =
194                [(), (), ()]
195    """
196
197    portal = getToolByName(context, 'portal_url').getPortalObject()
198    portal_state = getMultiAdapter((portal, context.REQUEST), name=u'plone_portal_state')
199    typesToShow = portal_state.friendly_types()
200
201    components = getSiteManager(context)
202    managers = [r for r in components.registeredUtilities() if r.provided.isOrExtends(IPortletManager)]
203    context_managers = [(m.name, getUtility(IPortletManager, name=m.name, context=context)) for m in managers
204                                                                               if not IPlacelessPortletManager.providedBy(m.component)]
205    managers = [(m.name, getUtility(IPortletManager, name=m.name, context=context)) for m in managers]
206
207    slot_structure = []
208    if dump_policy == 'root':
209        extractPortletsFromContext(portal, slot_structure, typesToShow, context_managers)
210    elif dump_policy == 'all':
211        dumpAllPortlets(portal, slot_structure, typesToShow, context_managers)
212    elif dump_policy == 'selection':
213        for ppath in dump_portlets_selection:
214            obj = portal.restrictedTraverse(ppath)
215            extractPortletsFromContext(obj, slot_structure, typesToShow, context_managers)
216
217    slot_structure.append(('__site-wide-portlets__', extractSiteWidePortlets(portal, managers)))
218
219    return slot_structure
220
221def buildSkinLayers(context, zmi_base_skin_name):
222    pskins = getToolByName(context,'portal_skins')
223    layers = (pskins.getSkinPath(zmi_base_skin_name) or '').split(',')
224    return "\n".join(['   <layer name="%s"/>' % l for l in layers])
225
226def getFSSkinPath(folder, fs_dest_directory, fs_product_name):
227    """ Return file system skin path for subdir."""
228
229    folder_path = '/'.join(folder.getPhysicalPath()[list(folder.getPhysicalPath()).index('portal_skins')+1:])
230    skinpath = "%s/%s/skins/%s" % (fs_dest_directory, fs_product_name, folder_path)
231
232    # If in skin's subfolder - get its path
233    #skinp, subp = [obj.getPhysicalPath() for obj in [skin_obj, subdir]]
234    #if len(subp) != len(skinp):
235        ## adapt skinpath for creating directory
236        #skinpath += '/' + '/'.join( subp[len(skinp):] )
237    return skinpath
238
239def dumpFolder(folder, fs_dest_directory, fs_product_name):
240    skinpath = getFSSkinPath(folder, fs_dest_directory, fs_product_name)
241    # Create directory in FS if not yet exist
242    if not os.path.exists(skinpath):
243        os.makedirs(skinpath)
244    # Loop of copying content from ZMIskin-folder to FSskin-folder
245    obj_meta = {}
246    for o in folder.objectValues():
247        meta_type = o.meta_type
248        id = get_id(o)
249        if meta_type in _acceptable_meta_types:
250            # Adding to .objects all acceptable meta_types.
251            # Fixing bug of id-meta_type confusing.
252            obj_meta[id] = meta_type
253        if meta_type == 'Folder':
254            # very plone specific
255            if id in ['stylesheet_properties', 'base_properties'] or id.startswith('base_properties'):
256                writeProps(o, skinpath, extension = '.props')
257            else:
258                dumpFolder(o, fs_product_name, fs_product_name)
259        elif meta_type in _write_custom_meta_type_list:
260            #writeProps( o, skinpath )      # write object's properties
261            # extract content from object(depend on metatype) and write it to the file
262            writeFileContent(o, skinpath, getData(o, meta_type))
263        else:
264            print 'method ignoring ', meta_type
265    # write '.objects' file to directory if present objects with id without extension
266    if obj_meta :
267        writeObjectsMeta(obj_meta, skinpath)
268
269def dumpSkin(context, skin_names=['custom',], fs_dest_directory=PRODUCTS_PATH,
270             fs_product_name='QSkinTemplate', erase_from_skin=0):
271    """Dump custom information to file."""
272    if type(skin_names) not in (type([]), type(())):
273        skin_names = [skin_names,]
274    for skin_name in list(skin_names):
275        folder = getToolByName(context, 'portal_skins')[skin_name]
276        dumpFolder(folder, fs_dest_directory, fs_product_name)
277        # delete objects from the skin, if request
278        if erase_from_skin:
279            folder.manage_delObjects(ids = folder.objectIds())
280
281def fillinFileTemplate(f_path_read, f_path_write=None, dict={}):
282    """ Fillin file template with data from dictionary."""
283    if not f_path_write:
284        f_path_write = f_path_read
285    f_tmpl = open(f_path_read, 'r')
286    tmpl = f_tmpl.read()
287    f_tmpl.close()
288    f_tmpl = open(f_path_write, 'w')
289    try:
290       f_tmpl.write(tmpl % dict)
291    except:
292        raise str(tmpl)
293    f_tmpl.close()
294
295def getResourcesList(directory, resources_list, pattern=CSS_PATTERN):
296    """ Get resources list from 'directory' skin folder."""
297    for o in directory.objectValues():
298        meta_type = o.meta_type
299        id = get_id(o)
300        if meta_type == 'Folder':
301            # very plone specific
302            if id not in ['stylesheet_properties', 'base_properties'] \
303               and not id.startswith('base_properties'):
304                css_list = getResourcesList(o, resources_list, pattern)
305        elif pattern.match(id):
306            resources_list.append( id )
307    return resources_list
308 
309def getResourceProperties(context, regestry_id, prop_list, dflt=''):
310    """ Return list of dictionaries with all dumped resources properties."""
311    properties=[]
312    resource = getToolByName(context, regestry_id, None)
313    if resource:
314        for res in resource.getResources():
315            props = {}
316            for prop in prop_list:
317                accessor = getattr(res, 'get%s' % prop.capitalize(), None)
318                if accessor:
319                    props[prop] = accessor() or dflt
320            properties.append(props)
321    return properties
322
323def getResourceListRegdata(context, subdir, rsrc_pattern, rsrc_name, rsrc_reg_props):
324    rsrc_list = getResourcesList(subdir, resources_list=[], pattern=rsrc_pattern)#---CSS--#000000#aabbcc
325    result_rsrc_list = []
326    [result_rsrc_list.append(item) for item in rsrc_list if item not in result_rsrc_list]
327    skin_css_regdata = getResourceProperties(context, rsrc_name, rsrc_reg_props)   # Get Data from CSS Regestry
328    return result_rsrc_list, skin_css_regdata
329
330def copyDir(srcDirectory, dstDirectory, productName):
331    """Recursive copying from ZMIskin-folder to FS one""" 
332    for item in os.listdir(srcDirectory):
333        src_path = ospJoin(srcDirectory, item)
334        dst_path = ospJoin(dstDirectory, item)
335        if os.path.isfile(src_path):
336            if os.path.exists(dst_path):
337                continue
338            f_sorce = open(src_path,'r')
339            data = f_sorce.read()
340            f_sorce.close()
341            f_dst = open(dst_path,'w')
342            f_dst.write(data)
343            f_dst.close()
344        elif os.path.isdir(src_path) \
345             and not ".svn" in src_path:
346            if not os.path.exists(dst_path):
347                os.mkdir(dst_path)
348            copyDir(src_path, dst_path, productName)
349
350def fsDirectoryViewsXML(folder_names, product_name, remove=False):
351    pattern = """ <object name="%(folder_name)s" meta_type="Filesystem Directory View"
352       directory="Products.%(product_name)s:skins/%(folder_name)s" %(remove)s/>\n"""
353    xml = ''
354    if type(folder_names) not in (type([]), type(())):
355        folder_names = [folder_names,]
356    for name in folder_names:
357       xml += pattern % {'product_name' : product_name, \
358                         'folder_name'  : name, \
359                         'remove'       : remove and 'remove="True"' or '', \
360                        }
361    return xml
362
363def makeNewProduct(context, destinationDir, productName, productSkinName, \
364                   zmi_skin_names, zmi_base_skin_name, subdir,\
365                   doesCustomizeSlots, left_slots, right_slots, slot_forming, main_column, \
366                   doesExportObjects, import_policy, dump_CSS, dump_JS, \
367                   dump_portlets, dump_policy, dump_portlets_selection, dump_custom_views):
368    """Create new skin-product's directory and
369       copy skin-product template with little modification"""
370    products_path = destinationDir
371    productPath = ospJoin(products_path, productName)
372    if not (productName in os.listdir(products_path)):
373        os.mkdir(productPath)
374    files_to_remove = []
375    files_to_add    = []
376    # Form CSS and JS importing list and regestry data (looking in subdir too) for Plone 2.1.0+
377    stylesheets_xml = ''
378    javascripts_xml = ''
379    #subdir = subdir or getToolByName(context, 'portal_skins')[zmi_skin_name]
380    portal_setup = getToolByName(context, 'portal_setup')
381    result_css_list = skin_css_regdata = result_js_list = skin_js_regdata = []
382    base_context = BaseContext(portal_setup, portal_setup.getEncoding())
383    if dump_CSS:
384        res_reg = getToolByName(context, 'portal_css', None)
385        exporter = zapi.queryMultiAdapter((res_reg, base_context), IBody)
386        if exporter is not None:
387            stylesheets_xml = exporter.body
388        #result_css_list, skin_css_regdata = getResourceListRegdata(context, subdir,
389                                            #CSS_PATTERN, 'portal_css', CSS_REG_PROPS)
390    if stylesheets_xml == '':
391        files_to_remove.append(ospJoin('profiles', 'default', 'cssregistry.xml'))
392    if dump_JS:
393        res_reg = getToolByName(context, 'portal_javascripts', None)
394        exporter = zapi.queryMultiAdapter((res_reg, base_context), IBody)
395        if exporter is not None:
396            javascripts_xml = exporter.body
397        #result_js_list, skin_js_regdata = getResourceListRegdata(context, subdir,
398                                            #JS_PATTERN, 'portal_javascripts', JS_REG_PROPS)
399    if javascripts_xml == '':
400        files_to_remove.append(ospJoin('profiles', 'default', 'jsregistry.xml'))
401
402    slots = ()
403    if dump_portlets:
404        slots = dumpPortlets(context, dump_policy, dump_portlets_selection)
405
406    # Get Slots customization information
407    if not doesCustomizeSlots:
408        left_slots = right_slots = None
409        slot_forming = main_column = None
410
411    # Prepare XML strings for add to skins.xml
412    skin_layers = buildSkinLayers(context, zmi_base_skin_name)
413
414    # Prepare profiles
415    default_marker, afterinstall_marker, uninstall_marker = {}, {}, {}
416    profiles = ['default', 'afterinstall', 'uninstall']
417    profiles_path = ospJoin(products_path, productName, 'profiles')
418    for profile in profiles:
419        varname = "%s_marker" % profile
420        file_name = "%s_%s.txt" % (productName.lower(), profile)
421        locals()[varname].update({'fname' : file_name, \
422                                  'fpath' : ospJoin(profiles_path, profile, file_name), \
423                                  'fdata' : "# Marker file for %s profile of %s skin" % \
424                                            (profile, productName) })
425        files_to_add.append(locals()[varname])
426
427    # dump customized objects from portal_view_customization
428    custom_views = []
429    if dump_custom_views:
430        custom_views = dumpPortalViewCustomization(context)
431
432    # Copy skin_template to SKIN_PRODUCT directory
433    templatePath = ospJoin(PRODUCTS_PATH, PROJECTNAME, TEMPLATE_PATH)
434    copyDir(templatePath, productPath, productName)
435    # Form data dictionary and form Skin Product's files
436    conf_dict = {"IMPORT_POLICY" : import_policy \
437                ,"GENERATOR_PRODUCT" : PROJECTNAME \
438                ,"SKIN_PRODUCT_NAME" : productName \
439                ,"SKIN_NAME" : productSkinName \
440                ,"BASE_SKIN_NAME" : zmi_base_skin_name \
441                ,"DUMP_CSS": not not dump_CSS \
442                ,"DUMP_JS": not not dump_JS \
443                ,"CSS_LIST" : str(result_css_list) \
444                ,"JS_LIST" : str(result_js_list) \
445                ,"SKIN_CSS_REGDATA" : str(skin_css_regdata) \
446                ,"SKIN_JS_REGDATA" : str(skin_js_regdata) \
447                ,"LEFT_SLOTS" : str(left_slots) \
448                ,"RIGHT_SLOTS" : str(right_slots) \
449                ,"SLOT_FORMING" : slot_forming \
450                ,"MAIN_COLUMN" : main_column \
451                ,"product_name" : productName \
452                ,"skin_name" : productSkinName \
453                ,"skin_name_lowercase" : productSkinName.lower() \
454                ,"skin_name_capital" : '%s%s' % (productSkinName[0].upper(), productSkinName[1:]) \
455                ,"product_name_lowercase" : productName.lower() \
456                ,"viewlets_zcml" : '' \
457                ,"stylesheets_xml" : stylesheets_xml \
458                ,"javascripts_xml" : javascripts_xml \
459                ,"version" : time.strftime('%Y%m%d') \
460                ,"slot_structure" : str(slots) \
461                ,"skin_layers" : skin_layers \
462                ,"custom_views" : str(custom_views) \
463                ,"directory_views_xml" : fsDirectoryViewsXML(zmi_skin_names, productName) \
464                ,"remove_directory_views_xml" : fsDirectoryViewsXML(zmi_skin_names, productName, remove=True) \
465                ,"creation_date" : time.strftime('%d/%m/%Y') \
466                ,"install_profile_marker" : default_marker['fname'] \
467                ,"afterinstall_profile_marker" : afterinstall_marker['fname'] \
468                ,"uninstall_profile_marker" : uninstall_marker['fname'] \
469    }
470    sp_updated_files = ['config.py' \
471                       ,'README.txt' \
472                       ,'HISTORY.txt' \
473                       ,'setuphandlers.py' \
474                       ,'uninstallhandlers.py' \
475                       ,'profiles.zcml' \
476                       ,'utils.py' \
477                       ,'configure.zcml' \
478                       ,'skins.zcml' \
479                       ,ospJoin('Extensions', 'Install.py')\
480                       ,ospJoin('browser', 'interfaces.py')\
481                       ,ospJoin('browser', 'viewlets.zcml')\
482                       ,ospJoin('browser', 'configure.zcml')\
483                       ,ospJoin('profiles', 'default', 'skins.xml')\
484                       ,ospJoin('profiles', 'default', 'cssregistry.xml')\
485                       ,ospJoin('profiles', 'default', 'jsregistry.xml')\
486                       ,ospJoin('profiles', 'default', 'propertiestool.xml')\
487                       ,ospJoin('profiles', 'afterinstall', 'import_steps.xml')\
488                       ,ospJoin('profiles', 'uninstall', 'import_steps.xml')\
489                       ,ospJoin('profiles', 'uninstall', 'skins.xml')\
490                       ,ospJoin('browser', 'configure.zcml') \
491                       #,ospJoin('profiles', 'uninstall', 'cssregistry.xml')\
492                       #,ospJoin('profiles', 'uninstall', 'jsregistry.xml')\
493                       ]
494    for fp in sp_updated_files:
495        fillinFileTemplate(ospJoin(productPath, fp), dict=conf_dict)
496    for fp in files_to_remove:
497        os.remove(ospJoin(productPath,fp))
498    for data in files_to_add:
499        f = file(data['fpath'],'w')
500        f.write(data['fdata'])
501        f.close()
Note: See TracBrowser for help on using the repository browser.