diff -r 000000000000 -r b97547f5f1fa web/webconfig.py --- /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