source: enpraxis.staticsite/trunk/enpraxis/staticsite/utilities/staticsiteutility.py @ 533

Revision 533, 27.0 KB checked in by david, 4 years ago (diff)

missing convert to abs for images

Line 
1##################################################################################
2#    Copyright (c) 2009 Massachusetts Institute of Technology, All rights reserved.
3#                                                                                 
4#    This program is free software; you can redistribute it and/or modify         
5#    it under the terms of the GNU General Public License as published by         
6#    the Free Software Foundation, version 2.                                     
7#                                                                                 
8#    This program is distributed in the hope that it will be useful,             
9#    but WITHOUT ANY WARRANTY; without even the implied warranty of               
10#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               
11#    GNU General Public License for more details.                                 
12#                                                                                 
13#    You should have received a copy of the GNU General Public License           
14#    along with this program; if not, write to the Free Software                 
15#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA                                                                 
16##################################################################################
17
18__author__ = '''Brent Lambert, David Ray, Jon Thomas'''
19__version__ = '$ Revision 0.0 $'[11:-2]
20
21import os
22import re
23import string
24import Globals
25from BeautifulSoup import BeautifulSoup
26from mimetypes import guess_type
27from os import makedirs as os_makedirs
28from os.path import join as os_join
29from os.path import lexists as os_lexists
30from os.path import split as os_split
31
32from urllib2 import urlopen
33from urllib2 import HTTPError
34from urlparse import urlparse, urlunparse
35from Products.PythonScripts.standard import url_quote
36
37from OFS.SimpleItem import SimpleItem
38from zope.interface import implements
39from interfaces import IStaticSiteUtility
40from zope.component import getUtility, getMultiAdapter
41
42class StaticSiteUtility(SimpleItem):
43    """ Deploy a static site """
44
45    implements(IStaticSiteUtility)
46
47
48    def deploySite(self, context):
49        """ Deploy the site """
50        ssprops = context.portal_url.portal_properties.staticsite_properties
51        dpath = self._getDeploymentPath(ssprops.getProperty('deployment_path'))
52        # Deploy default objects
53        self.deploySiteStructure(context, ssprops, dpath)
54        # Deploy site object
55        self.deployObject(context.absolute_url(), context, context.Type(), dpath, ssprops, True)
56        # Deploy objects based on catalog search
57        brains = context.portal_catalog.searchResults(
58            path={'query':'/'.join(context.getPhysicalPath()),
59                  'depth':1,},
60            review_state=ssprops.getProperty('states_to_add'))
61        for brain in brains:
62            self.deployObject(brain.getURL(),
63                              context,
64                              brain.Type,
65                              dpath,
66                              ssprops,
67                              folderish=brain.is_folderish)
68            self.traverse(brain, dpath, ssprops)
69
70
71    def deploySiteStructure(self, context, ssprops, dpath):
72        """ Get the base framework and resources needed to build the chrome locally  """
73        portal_url = context.portal_url()
74        portal_catalog = context.portal_catalog
75        url = urlparse(portal_url)
76        urlpath = url[2].split('/')
77
78        # Deploy base files that are used sitewide in the chrome
79        for x in ssprops.getProperty('base_files'):
80            objurl = urlunparse((url[0], url[1], '/'.join(urlpath + [x]), url[3], url[4], url[5]))
81            #path = self._getObjPath(objurl, portal_url, dpath)
82            raw = self._httpget(objurl)
83            ftype = guess_type(x)[0]
84            if ftype is None:
85                path = self._getObjPath(objurl, portal_url, dpath)               
86                self._writeFile(path, raw)               
87            elif 'css' in ftype:
88                path = self._getObjPath(objurl, portal_url, dpath + 'css/')
89                self._writeFile(path, raw)
90            elif 'javascript' in ftype:
91                path = self._getObjPath(objurl, portal_url, dpath + 'js/')               
92                self._writeFile(path, raw)
93            elif 'image' in ftype:
94                path = self._getObjPath(objurl, portal_url, dpath + 'images/')
95                self._writeFile(path, raw, True)
96            else:
97                path = self._getObjPath(objurl, portal_url, dpath)               
98                self._writeFile(path, raw)
99               
100        # Deploy Current Theme CSS
101        cssreg = context.portal_css
102        cssurl = cssreg.absolute_url()
103        skinname = context.getCurrentSkinName()
104        for x in cssreg.getEvaluatedResources(context):
105            if x.getRendering() != 'inline':
106                surl = '%s/%s/%s' %(cssurl, skinname, x.getId())
107                path = self._getObjPath(surl, portal_url, dpath)
108                surl = '%s/%s/%s' %(cssurl, url_quote(skinname), x.getId())
109                raw = self._httpget(surl)
110                self._writeFile(path, raw)
111
112        # Deploy Current Theme CSS Images into the appropriate skin folder
113        css_images = ssprops.getProperty('css_images')
114        if len(css_images) > 0:           
115            for x in css_images:   
116                if x != '':           
117                    iurl = '%s/%s' %(portal_url, x)
118                    surl = '%s/%s/%s' %(cssurl, skinname, x)
119                    path = self._getObjPath(surl, portal_url, dpath)
120                    raw = self._httpget(iurl)
121                    self._writeFile(path, raw)
122                   
123        # Deploy Javascript
124        jsreg = context.portal_javascripts
125        jsurl = jsreg.absolute_url()
126        for x in jsreg.getEvaluatedResources(context):
127            if x.getInline() != True:           
128                surl = '%s/%s/%s' %(jsurl, skinname, x.getId())
129                path = self._getObjPath(surl, portal_url, dpath)
130                surl = '%s/%s/%s' %(jsurl, url_quote(skinname), x.getId())
131                raw = self._httpget(surl)
132                self._writeFile(path, raw)   
133
134        # Deploy site actions
135        site_actions = context.portal_actions.site_actions.listActions()
136        for x in site_actions:
137            if x.id not in ssprops.getProperty('actions_to_ignore') and x.visible == True:
138                action_url = x.url_expr.split('/')[-1]
139                url = '%s/%s' % (portal_url, action_url)
140                path = self._getObjPath(url, portal_url, dpath)
141                path += '.html'
142                raw = self._httpget(url)       
143                self._writeFile(path, raw)
144       
145    def traverse(self, brain, dpath, ssprops):
146        """ Traverse the site. """
147        brains = brain.portal_catalog.searchResults(
148            path={'query':brain.getPath(), 'depth':1},
149                  review_state=ssprops.getProperty('states_to_add'))
150        for br in brains:
151            self.deployObject(br.getURL(),
152                              br.portal_url,
153                              br.Type,
154                              dpath,
155                              ssprops,
156                              folderish=br.is_folderish)
157            if br.is_folderish:
158                self.traverse(br, dpath, ssprops)
159           
160    def deployObject(self, url, portal, ctype, dpath, ssprops, folderish=False, hastext=False):
161        """ Deploy an object """
162        path = self._getObjPath(url, portal.portal_url(), dpath)
163        if folderish:
164            # print '@@@ '+  url + '/index.html'
165            self.processDocument(url, portal, dpath, ssprops, True)
166        elif ctype in ['Page']:
167            # Fix this so that it deals with the case where you have both a file and a file.html in the
168            # same folder
169            # print '*** '+  url
170            self.processDocument(url, portal, dpath, ssprops)
171        else:
172            # Write the object to the filesystem
173            raw = self._httpget(url)
174            self._writeFile(path, raw, True)
175            # Process the view of the object
176            aurl = urlparse(url)
177            aurl = urlunparse((aurl[0], aurl[1], aurl[2] + '-view.html', aurl[3], aurl[4], aurl[5]))
178            # print 'vvv '+  aurl
179            self.processDocument(url + '/view', portal, dpath, ssprops, alturl=aurl)
180            # If it is an image process the fullscreen view
181            if ctype in ['Image']:
182                aurl = urlparse(url)
183                aurl = urlunparse((aurl[0], aurl[1], aurl[2] + '-image_view_fullscreen.html', aurl[3], aurl[4], aurl[5]))
184                # print 'iii '+  aurl
185                self.processDocument(url + '/image_view_fullscreen', portal, dpath, ssprops, alturl=aurl)
186
187
188    def _getDeploymentPath(self, sp):
189        """ Get the default static path location. """
190        dpath = os_split(Globals.BobobaseName)[0]
191        return os_join(dpath, sp)
192       
193    def _getObjPath(self, url, portal_url, dpath):
194        """ Get the object path based on the deployment path. """
195        if portal_url + '/' in url:
196            objpath = url.replace(portal_url + '/', '')
197        else:
198            objpath = url.replace(portal_url, '')
199        path = dpath
200        for x in objpath.split('/'):
201            path = os_join(path, x)
202        return path
203   
204    def _createDirectory(self, path):
205        """ Create a directory on the filesystem """     
206        if not os_lexists(path):
207            os_makedirs(path)
208           
209    def _httpget(self, url):
210        """ Get html for the url """
211        try:
212            f = urlopen(url)
213            data = f.read()
214            f.close()
215            return data
216        except HTTPError, e:           
217            # print '!!! Error : %s %s for url: %s' % (e.code, e.msg, e.filename)
218            return ''
219           
220    def _writeFile(self, fn, data, binary=False):
221        self._createDirectory(os_split(fn)[0])
222        if binary:
223                    f = open(fn, 'wb')
224        else:
225            f = open(fn, 'w')
226        f.write(data)
227        f.close()
228   
229    def processDocument(self, url, portal, dpath, ssprops, isFolderish=False, alturl=None):
230        if alturl:
231            aurl = alturl
232        else:
233            aurl = url
234        path = self._getObjPath(aurl, portal.portal_url(), dpath)
235        raw = self._httpget(url)
236        soup = BeautifulSoup(raw)
237        self.deployDocumentActions(portal, aurl, dpath, soup, ssprops)
238        self.deployPresentationView(portal, aurl, dpath, soup, ssprops)       
239        self.deployNonActionNonHTMLViews(portal, aurl, dpath, soup, ssprops)
240        if isFolderish:
241            curl = aurl + '/index.html'
242        else:
243            curl = aurl
244        body = self.runDocumentFilters(portal, curl, soup, ssprops)
245        mpath = path
246        if isFolderish:
247            mpath += '/index.html'
248        elif '.htm' not in mpath:
249            mpath += '.html'
250        self._writeFile(mpath, body)
251
252
253    def deployPresentationView(self, portal, current, dpath, soup, ssprops):
254        #Look for presentation view
255        raw_pres = soup.find('p', id='link-presentation')
256        if raw_pres:
257            url = raw_pres.a['href']
258            upath = urlparse(url)
259            p = upath[2].split('/')
260            obj = p[:-1]
261            view = p[-1]
262            if view in ssprops.getProperty('views_to_add'):
263                mpath = self._getObjPath(url, portal.portal_url(), dpath)
264                mpath = os.path.split(mpath)
265                raw = self._httpget(url)
266                asoup = BeautifulSoup(raw)
267                body = self.runDocumentFilters(portal, current, asoup, ssprops)
268                results = portal.portal_catalog.searchResults(
269                    query={'path':'/'.join(obj[:-1]),},
270                    id=obj[-1])
271                if results:
272                    if results[0].is_folderish:
273                        mpath = '%s/index-%s' %(mpath[0], mpath[1])
274                    else:
275                        mpath = '%s-%s' %(mpath[0], mpath[1])
276                    if  view not in ssprops.getProperty('non_html_views'):
277                        mpath += '.html'
278                    self._writeFile(mpath, body)           
279           
280
281    def deployDocumentActions(self, portal, current, dpath, soup, ssprops):
282        # Look for document actions
283        raw_da = soup.find('div', {'class':'documentActions'})
284        if raw_da:
285            # Step through document actions and see if they are in the soup
286            da = portal.portal_actions.document_actions.listActions()
287            for x in da:
288                # If we are not ignoring the action
289                if x.id not in ssprops.getProperty('actions_to_ignore'):
290                    # Find the action in the soup
291                    act = raw_da.find('li', id='document-action-%s' %x.id)
292                    if act:
293                        link = act.find('a')
294                        if link and link.has_key('href'):
295                            # Process the action
296                            url = link['href']
297                            upath = urlparse(link['href'])
298                            p = upath[2].split('/')
299                            obj = p[:-1]
300                            view = p[-1]
301                            # If we are going to add the action, process it
302                            if view in ssprops.getProperty('views_to_add'):
303                                mpath = self._getObjPath(url, portal.portal_url(), dpath)
304                                mpath = os.path.split(mpath)
305                                raw = self._httpget(url)
306                                asoup = BeautifulSoup(raw)
307                                body = self.runDocumentFilters(portal, current, asoup, ssprops)
308                                results = portal.portal_catalog.searchResults(
309                                    query={'path':'/'.join(obj[:-1]),},
310                                    id=obj[-1])
311                                if results:
312                                    if results[0].is_folderish:
313                                        mpath = '%s/index-%s' %(mpath[0], mpath[1])
314                                    else:
315                                        mpath = '%s-%s' %(mpath[0], mpath[1])
316                                    if  view not in ssprops.getProperty('non_html_views'):
317                                        mpath += '.html'
318                                    self._writeFile(mpath, body)
319
320    def deployNonActionNonHTMLViews(self, portal, aurl, dpath, soup, ssprops):
321        #deploy non-html views that are not tied to views_to_add and not found as links within pages
322        views_to_add = ssprops.getProperty('views_to_add')
323        non_html_views = ssprops.getProperty('non_html_views')
324        views = [x for x in non_html_views if x not in views_to_add]
325        for view in views:
326            if view == 'rdf':
327                url = '%s/%s' % (aurl, view)
328                raw = self._httpget(url)
329                if len(raw) > 0:
330                    raw = '<?xml version="1.0" encoding="utf-8" ?>\n' + raw
331                    if '.htm' in aurl:
332                        parts = aurl.split('.htm')
333                        url = '%s.rdf' % parts[0]
334                    else:
335                        url = url.replace('/rdf', '.rdf')
336                    obj_path = url.replace(portal.portal_url()+'/', '')
337                    dpath += '%s' % obj_path
338                    self._writeFile(dpath, raw)
339
340    def runDocumentFilters(self, portal, current, soup, ssprops):
341        self.filterBaseTag(soup, current)
342        self.filterIgnoredSections(soup, ssprops)
343        self.filterIgnoredPortlets(soup, ssprops)       
344        self.filterIgnoredActions(soup, ssprops)
345        self.filterDocActionImages(soup, portal.portal_url(), current)
346        self.filterCSSLinks(soup, current)
347        self.filterIEFixesCSS(soup, current)
348        self.filterJSLinks(soup, current)
349        self.filterS5BaseUrl(soup, current)       
350        self.filterBaseFilesLinks(soup, current, portal, ssprops)
351        self.filterImageFullscreenBackLink(soup, current)
352        self.filterCSSValidatorLink(soup, current, portal, ssprops)
353        links = self.getDocumentLinks(soup)
354        for x in links:
355            orig = x['href']
356            x['href'] = self.filterDocumentLink(x['href'],
357                                                current,
358                                                portal,
359                                                ssprops.getProperty('views_to_add'),
360                                                ssprops.getProperty('non_html_views'))
361            # print '   %s => %s' %(orig, x['href'])
362        data = soup.prettify()
363        return self.filterPortalUrl(data, current)
364
365    def filterBaseTag(self, soup, current):
366        base = soup.findAll('base')
367        for x in base:
368            if x.has_key('href'):
369                url = x['href']
370                url = url.split(');')[0]
371                url = self._convertLinkToRelative(url, current)
372                x['href'] = url               
373
374    def filterIgnoredActions(self, soup, ssprops):
375        ftags = soup.findAll('div') + soup.findAll('li')
376        for x in ftags:
377            if x.has_key('id'):
378                id = x['id']
379                if 'document-action' in id:
380                    act = id.split('-')[-1]
381                    if act in ssprops.getProperty('actions_to_ignore'):
382                        x.extract()
383
384    def filterIgnoredSections(self, soup, ssprops):
385        for x in ssprops.getProperty('sections_to_ignore'):
386            tag = soup.find(id=x)
387            if tag:
388                if x == 'portal-personaltools':
389                    tag.contents[1].replaceWith('<li>&nbsp;</li>')
390                else:
391                    tag.extract()
392               
393    def filterIgnoredPortlets(self, soup, ssprops):
394        for x in ssprops.getProperty('portlets_to_ignore'):                   
395            tag = soup.find('dl', {'class': 'portlet %s' % x})
396            if tag:
397                portlet = tag.parent
398                column_wrapper = portlet.parent               
399                portlet.extract()
400                # Check for additional portlets in column, remove col if none
401                if not 'portletWrapper' in column_wrapper.renderContents():
402                    column_wrapper.parent.extract()
403               
404    def filterDocActionImages(self, soup, portal_url, current):
405        tags = soup.findAll('li')
406        for x in tags:
407            if x.has_key('id'):
408                id = x['id']
409                if 'document-action' in id:                   
410                    for z in x.findAll('img'):
411                        src = z['src']
412                        surl = '%s/%s' % (portal_url, src)
413                        url = self._convertLinkToRelative(surl, current)                       
414                        z['src'] = url
415
416    def filterCSSLinks(self, soup, current):
417        #There are 2 cases, importing stylesheets, and linked stylesheets
418        styles = soup.findAll('style', type="text/css")
419        for x in styles:
420            body = x.contents[0]
421            if '@import' in body:
422                url = body.split('url(')[-1]
423                url = url.split(');')[0]
424                url = self._convertLinkToRelative(url, current)
425                x.contents[0].replaceWith('<!-- @import url(%s); -->' %url)         
426        styles = soup.findAll('link', type="text/css")
427        for x in styles:
428            if '.htm' not in current:
429                current += '/index.html'
430            if x.has_key('href'):
431                url = x['href']
432                url = self._convertLinkToRelative(url, current)
433                x['href'] = url
434
435
436    def filterIEFixesCSS(self, soup, current):
437        ie_css = soup.find(text=re.compile("IEFixes.css"))
438        if ie_css:
439            url = ie_css.split('url(')[-1]
440            url = url.split(');')[0]
441            url.replace('IEFixes.css', 'css/IEFixes.css')
442            nurl = self._convertLinkToRelative(url, current)
443            ie_css.replaceWith('''<!--[if IE]>
444                                  <style type="text/css" media="all">@import url(%s);</style>
445                                  <![endif]-->''' %nurl)       
446
447    def filterJSLinks(self, soup, current):
448        scripts = soup.findAll('script', type="text/javascript")
449        for x in scripts:
450            if x.has_key('src') == True:
451                url = x['src']
452                url = url.split(');')[0]
453                url = self._convertLinkToRelative(url, current)
454                x['src'] = url               
455
456    def filterS5BaseUrl(self, soup, current):
457        scripts = soup.findAll('script', type="text/javascript")
458        for x in scripts:
459            if len(x.contents) > 0 and 'base' in x.contents[0]:
460                base_url = x.contents[0].split('url="')[-1]
461                base_url = base_url.split('";')[0]
462                if '.htm' not in base_url:
463                    base_url += '.html'
464                url = self._convertLinkToRelative(base_url, current)
465                x.contents[0].replaceWith('var base_url="%s";' % url)
466
467    def filterCSSValidatorLink(self, soup, current, portal, ssprops):
468        for link in soup('a', {'href' : re.compile('css-validator')}):
469            deployment_url = ssprops.getProperty('deployment_url')
470            portal_url = portal.portal_url()
471            url = link['href']
472            url = url.split('uri=')[-1]
473            url = url.split('%2F')[0]
474            if '.htm' not in current:
475                current += '.html'
476            nurl = current.replace(portal_url, deployment_url)
477            link['href'] = link['href'].replace(url, nurl)
478       
479    def filterImageFullscreenBackLink(self, soup, current):
480        if 'image_view_fullscreen' in current:
481            back = soup.find('a')
482            if back:
483                lt = back.find('span')
484                if lt:
485                    lt.contents[0].replaceWith('Back to Image')
486                if back.has_key('href'):
487                    back['href'] = current.replace('image_view_fullscreen.html', 'view.html')
488
489    def filterBaseFilesLinks(self, soup, current, portal, ssprops):
490        portal_url = portal.portal_url()
491        for x in ssprops.getProperty('base_files'):
492            ftype = guess_type(x)[0]
493            if ftype is None:
494                pass
495            elif 'css' in ftype:
496                tags = soup.findAll('link', {'href' : re.compile(x)})
497                for tag in tags:
498                    abs_link = self._convertLinkToAbsolute(tag['href'], current)
499                    rel_link = self._convertLinkToRelative(abs_link, current)
500                    tag['href']  = rel_link.replace(x, 'css/%s' % x)
501            elif 'javascript' in ftype:             
502                tags = soup.findAll('script', {'src' : re.compile(x)})
503                for tag in tags:
504                    abs_link = self._convertLinkToAbsolute(tag['src'], current)
505                    rel_link = self._convertLinkToRelative(abs_link, current)                   
506                    tag['src']  = rel_link.replace(x, 'js/%s' % x) 
507            elif 'image' in ftype:
508                tags = soup.findAll('img', {'src' : re.compile(x)})
509                for tag in tags:
510                    abs_link = self._convertLinkToAbsolute(tag['src'], current)                   
511                    rel_link = self._convertLinkToRelative(tag['src'], current)
512                    tag['src']  = rel_link.replace(x, 'images/%s' % x)
513
514
515
516
517    def getDocumentLinks(self, soup):
518        tags = soup.findAll('a') + soup.findAll('link')
519        links = []
520        for tag in tags:
521            if tag.has_key('href'):
522                url = urlparse(tag['href'])
523                if not url[1] or 'localhost' in url[1]:
524                    links.append(tag)
525        return links
526
527    def filterDocumentLink(self, link, current, portal, views, nviews):       
528        lnk = link
529        url = urlparse(lnk)
530        if url[2] and 'javascript' != url[0]:
531            lnk = self._convertLinkToAbsolute(lnk, current)
532            lnk = self._convertObjectLink(lnk, portal, views, nviews)
533            lnk = self._convertLinkToRelative(lnk, current)
534        return lnk
535
536    def _convertLinkToAbsolute(self, link, current):
537        result = link
538        c = urlparse(current)
539        hr = urlparse(link)
540        if not hr[0] and not hr[1]:
541            cp = c[2].split('/')
542            hp = hr[2].split('/')
543            p = []
544            for y in hp:
545                if '.' == y:
546                    pass
547                elif '..' == y:
548                    cp = cp[:-1]
549                else:
550                    p.append(y)
551            result = urlunparse((c[0], c[1], '/'.join(cp + p), hr[3], hr[4], hr[5]))
552        return result
553
554    def _convertObjectLink(self, link, portal, views, nviews):
555        # This only works for absolute links
556        result = link
557        hr = urlparse(link)
558        p = urlparse(portal.portal_url())
559        if p[1] == hr[1]:
560            h = hr[2].split('/')
561            view = ''
562            if h[-1] in views:
563                view = h[-1]
564                h = h[:-1]
565            results = portal.portal_catalog.searchResults(query={'path':'/'.join(h),}, id=h[-1])
566            if results:
567                path = ''
568                if results[0].is_folderish:
569                    if view:
570                        path = '/'.join(h) + '/index' + '-%s' %view
571                    else:
572                        path = '/'.join(h) + '/index.html'
573                else:
574                    if view:
575                        path = '/'.join(h) + '-%s' %view
576                    elif h:
577                        path = '/'.join(h)
578                        if 'Page' == results[0].Type and '.htm' not in path:
579                            path += '.html'
580                if view and view not in nviews:
581                    path += '.html'
582                result = urlunparse((hr[0], hr[1], path, hr[3], hr[4], hr[5]))
583            elif link == portal.portal_url():
584                # Link points to site root
585                result = urlunparse((hr[0], hr[1], '/'.join(h) + '/index.html', hr[3], hr[4], hr[5]))
586        return result
587
588    def _convertLinkToRelative(self, link, current):
589        # This will break if the last item in the url path is a folder
590        # Make sure you rewrite the link path before you call this function
591        hr = urlparse(link)
592        c = urlparse(current)
593        if c[1] == hr[1]:
594            url1 = c[2].split('/')
595            url2 = hr[2].split('/')
596            index = 0
597            while url1[index:] and url2[index:] and url1[index] == url2[index]:
598                index += 1
599            p = []
600            for y in range(len(url1[index+1:])):
601                p.append('..')
602            p = p + url2[index:]
603            return urlunparse(('', '', '/'.join(p), hr[3], hr[4], hr[5]))
604       
605
606    def filterPortalUrl(self, data, current):
607        """ Blanket filter to replace any remaining portal urls in the page. """
608        return data.replace(current, '')
609       
610
611    def getDeploymentPath(self, context):
612        ssprops = context.portal_url.portal_properties.staticsite_properties
613        dpath = self._getDeploymentPath(ssprops.getProperty('deployment_path'))
614        return dpath
615       
616       
Note: See TracBrowser for help on using the repository browser.