web/webconfig.py
changeset 0 b97547f5f1fa
child 30 25ef1dddaab8
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/webconfig.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,321 @@
+"""common web configuration for twisted/modpython applications
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+import os
+from os.path import join, dirname, exists
+from urlparse import urljoin
+
+from logilab.common.configuration import Method
+from logilab.common.decorators import cached
+
+from cubicweb.toolsutils import read_config
+from cubicweb.cwconfig import CubicWebConfiguration, register_persistent_options, merge_options
+
+_ = unicode
+
+register_persistent_options( (
+    # site-wide only web ui configuration
+    ('site-title',
+     {'type' : 'string', 'default': 'unset title',
+      'help': _('site title'),
+      'sitewide': True, 'group': 'ui', 
+      }),
+    ('main-template',
+     {'type' : 'string', 'default': 'main',
+      'help': _('id of main template used to render pages'),
+      'sitewide': True, 'group': 'ui',
+      }),
+    # user web ui configuration
+    ('fckeditor',
+     {'type' : 'yn', 'default': True,
+      'help': _('should html fields being edited using fckeditor (a HTML '
+                'WYSIWYG editor).  You should also select text/html as default '
+                'text format to actually get fckeditor.'),
+      'group': 'ui',
+      }),
+    # navigation configuration
+    ('page-size',
+     {'type' : 'int', 'default': 40,
+      'help': _('maximum number of objects displayed by page of results'),
+      'group': 'navigation',
+      }),
+    ('related-limit',
+     {'type' : 'int', 'default': 8,
+      'help': _('maximum number of related entities to display in the primary '
+                'view'),
+      'group': 'navigation',
+      }),
+    ('combobox-limit',
+     {'type' : 'int', 'default': 20,
+      'help': _('maximum number of entities to display in related combo box'),
+      'group': 'navigation',
+      }),
+    
+    ))
+
+
+class WebConfiguration(CubicWebConfiguration):
+    """the WebConfiguration is a singleton object handling application's
+    configuration and preferences
+    """
+    cubicweb_vobject_path = CubicWebConfiguration.cubicweb_vobject_path | set(['web/views'])
+    cube_vobject_path = CubicWebConfiguration.cube_vobject_path | set(['views'])
+    
+    options = merge_options(CubicWebConfiguration.options + (
+        ('anonymous-user',
+         {'type' : 'string',
+          'default': None,
+          'help': 'login of the CubicWeb user account to use for anonymous user (if you want to allow anonymous)',
+          'group': 'main', 'inputlevel': 1,
+          }),
+        ('anonymous-password',
+         {'type' : 'string',
+          'default': None,
+          'help': 'password of the CubicWeb user account to use for anonymous user, '
+          'if anonymous-user is set',
+          'group': 'main', 'inputlevel': 1,
+          }),
+        ('query-log-file',
+         {'type' : 'string',
+          'default': None,
+          'help': 'web application query log file',
+          'group': 'main', 'inputlevel': 2,
+          }),
+        ('pyro-application-id',
+         {'type' : 'string',
+          'default': Method('default_application_id'),
+          'help': 'CubicWeb application identifier in the Pyro name server',
+          'group': 'pyro-client', 'inputlevel': 1,
+          }),
+        # web configuration
+        ('https-url',
+         {'type' : 'string',
+          'default': None,
+          'help': 'web server root url on https. By specifying this option your '\
+          'site can be available as an http and https site. Authenticated users '\
+          'will in this case be authenticated and once done navigate through the '\
+          'https site. IMPORTANTE NOTE: to do this work, you should have your '\
+          'apache redirection include "https" as base url path so cubicweb can '\
+          'differentiate between http vs https access. For instance: \n'\
+          'RewriteRule ^/demo/(.*) http://127.0.0.1:8080/https/$1 [L,P]\n'\
+          'where the cubicweb web server is listening on port 8080.',
+          'group': 'main', 'inputlevel': 2,
+          }),
+        ('auth-mode',
+         {'type' : 'choice',
+          'choices' : ('cookie', 'http'),
+          'default': 'cookie',
+          'help': 'authentication mode (cookie / http)',
+          'group': 'web', 'inputlevel': 1,
+          }),
+        ('realm',
+         {'type' : 'string',
+          'default': 'cubicweb',
+          'help': 'realm to use on HTTP authentication mode',
+          'group': 'web', 'inputlevel': 2,
+          }),
+        ('http-session-time',
+         {'type' : 'int',
+          'default': 0,
+          'help': 'duration in seconds for HTTP sessions. 0 mean no expiration. '\
+          'Should be greater than RQL server\'s session-time.',
+          'group': 'web', 'inputlevel': 2,
+          }),
+        ('cleanup-session-time',
+         {'type' : 'int',
+          'default': 43200,
+          'help': 'duration in seconds for which unused connections should be '\
+          'closed, to limit memory consumption. This is different from '\
+          'http-session-time since in some cases you may have an unexpired http '\
+          'session (e.g. valid session cookie) which will trigger transparent '\
+          'creation of a new session. In other cases, sessions may never expire \
+          and cause memory leak. Should be smaller than http-session-time, '\
+          'unless it\'s 0. Default to 12 h.',
+          'group': 'web', 'inputlevel': 2,
+          }),
+        ('cleanup-anonymous-session-time',
+         {'type' : 'int',
+          'default': 120,
+          'help': 'Same as cleanup-session-time but specific to anonymous '\
+          'sessions. Default to 2 min.',
+          'group': 'web', 'inputlevel': 2,
+          }),
+        ('embed-allowed',
+         {'type' : 'regexp',
+          'default': None,
+          'help': 'regular expression matching URLs that may be embeded. \
+leave it blank if you don\'t want the embedding feature, or set it to ".*" \
+if you want to allow everything',
+          'group': 'web', 'inputlevel': 1,
+          }),
+        ('submit-url',
+         {'type' : 'string',
+          'default': Method('default_submit_url'),
+          'help': ('URL that may be used to report bug in this application '
+                   'by direct access to the project\'s (jpl) tracker, '
+                   'if you want this feature on. The url should looks like '
+                   'http://mytracker.com/view?__linkto=concerns:1234:subject&etype=Ticket&type=bug&vid=creation '
+                   'where 1234 should be replaced by the eid of your project in '
+                   'the tracker. If you have no idea about what I\'am talking '
+                   'about, you should probably let no value for this option.'),
+          'group': 'web', 'inputlevel': 2,
+          }),
+        ('submit-mail',
+         {'type' : 'string',
+          'default': None,
+          'help': ('Mail used as recipient to report bug in this application, '
+                   'if you want this feature on'),
+          'group': 'web', 'inputlevel': 2,
+          }),
+
+        ('language-negociation',
+         {'type' : 'yn',
+          'default': True,
+          'help': 'use Accept-Language http header to try to set user '\
+          'interface\'s language according to browser defined preferences',
+          'group': 'web', 'inputlevel': 2,
+          }),
+        
+        ('print-traceback',
+         {'type' : 'yn',
+          'default': not CubicWebConfiguration.mode == 'installed',
+          'help': 'print the traceback on the error page when an error occured',
+          'group': 'web', 'inputlevel': 2,
+          }),
+        ))
+
+    def default_submit_url(self):
+        try:
+            cube = self.cubes()[0]
+            cubeeid = self.cube_pkginfo(cube).cube_eid
+        except Exception, ex:
+            return None
+        if cubeeid:
+            return 'http://intranet.logilab.fr/jpl/view?__linkto=concerns:%s:subject&etype=Ticket&type=bug&vid=creation' % cubeeid
+        return None
+
+    # method used to connect to the repository: 'inmemory' / 'pyro'
+    # Pyro repository by default
+    repo_method = 'pyro'
+    
+    # don't use @cached: we want to be able to disable it while this must still
+    # be cached
+    def repository(self, vreg=None):
+        """return the application's repository object"""
+        try:
+            return self.__repo
+        except AttributeError:
+            from cubicweb.dbapi import get_repository
+            if self.repo_method == 'inmemory':
+                repo = get_repository('inmemory', vreg=vreg, config=self)
+            else:
+                repo = get_repository('pyro', self['pyro-application-id'],
+                                      config=self)
+            self.__repo = repo
+            return repo
+
+    def vc_config(self):
+        return self.repository().get_versions()
+    
+    # mapping to external resources (id -> path) (`external_resources` file) ##
+    ext_resources = {
+        'FAVICON':  'DATADIR/favicon.ico',
+        'LOGO':     'DATADIR/logo.png',
+        'RSS_LOGO': 'DATADIR/rss.png',
+        'HELP':     'DATADIR/help.png',
+        'CALENDAR_ICON': 'DATADIR/calendar.gif',
+        'SEARCH_GO':'DATADIR/go.png',
+        
+        'FCKEDITOR_PATH':  '/usr/share/fckeditor/',
+        
+        'IE_STYLESHEETS':    ['DATADIR/cubicweb.ie.css'],
+        'STYLESHEETS':       ['DATADIR/cubicweb.css'],
+        'STYLESHEETS_PRINT': ['DATADIR/cubicweb.print.css'],
+        
+        'JAVASCRIPTS':       ['DATADIR/jquery.js',
+                              'DATADIR/cubicweb.compat.js',
+                              'DATADIR/jquery.json.js',
+                              'DATADIR/cubicweb.python.js',
+                              'DATADIR/cubicweb.htmlhelpers.js'],
+        }
+        
+        
+    def anonymous_user(self):
+        """return a login and password to use for anonymous users. None
+        may be returned for both if anonymous connections are not allowed
+        """
+        try:
+            user = self['anonymous-user']
+            passwd = self['anonymous-password']
+        except KeyError:
+            user, passwd = None, None
+        if user is not None:
+            user = unicode(user)
+        return user, passwd
+    
+    def has_resource(self, rid):
+        """return true if an external resource is defined"""
+        return bool(self.ext_resources.get(rid))
+
+    @cached
+    def locate_resource(self, rid):
+        """return the directory where the given resource may be found"""
+        return self._fs_locate(rid, 'data')
+            
+    @cached
+    def locate_doc_file(self, fname):
+        """return the directory where the given resource may be found"""
+        return self._fs_locate(fname, 'wdoc')
+            
+    def _fs_locate(self, rid, rdirectory):
+        """return the directory where the given resource may be found"""
+        path = [self.apphome] + self.cubes_path() + [join(self.shared_dir())]
+        for directory in path:
+            if exists(join(directory, rdirectory, rid)):
+                return join(directory, rdirectory)
+            
+    def locate_all_files(self, rid, rdirectory='wdoc'):
+        """return all files corresponding to the given resource"""
+        path = [self.apphome] + self.cubes_path() + [join(self.shared_dir())]
+        for directory in path:
+            fpath = join(directory, rdirectory, rid)
+            if exists(fpath):
+                yield join(fpath)
+
+    def load_configuration(self):
+        """load application's configuration files"""
+        super(WebConfiguration, self).load_configuration()
+        # load external resources definition
+        self._build_ext_resources()
+        self._init_base_url()
+        
+    def _init_base_url(self):
+        # normalize base url(s)
+        baseurl = self['base-url']
+        if baseurl and baseurl[-1] != '/':
+            baseurl += '/'
+            self.global_set_option('base-url', baseurl)
+        httpsurl = self['https-url']
+        if httpsurl and httpsurl[-1] != '/':
+            httpsurl += '/'
+            self.global_set_option('https-url', httpsurl)
+
+    def _build_ext_resources(self):
+        libresourcesfile = join(self.shared_dir(), 'data', 'external_resources')
+        self.ext_resources.update(read_config(libresourcesfile))
+        for path in reversed([self.apphome] + self.cubes_path()):
+            resourcesfile = join(path, 'data', 'external_resources')
+            if exists(resourcesfile):
+                self.debug('loading %s', resourcesfile)
+                self.ext_resources.update(read_config(resourcesfile))
+        for resource in ('STYLESHEETS', 'STYLESHEETS_PRINT',
+                         'IE_STYLESHEETS', 'JAVASCRIPTS'):
+            val = self.ext_resources[resource]
+            if isinstance(val, str):
+                files = [w.strip() for w in val.split(',') if w.strip()]
+                self.ext_resources[resource] = files