web/webconfig.py
changeset 0 b97547f5f1fa
child 30 25ef1dddaab8
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 """common web configuration for twisted/modpython applications
       
     2 
       
     3 :organization: Logilab
       
     4 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     6 """
       
     7 __docformat__ = "restructuredtext en"
       
     8 
       
     9 import os
       
    10 from os.path import join, dirname, exists
       
    11 from urlparse import urljoin
       
    12 
       
    13 from logilab.common.configuration import Method
       
    14 from logilab.common.decorators import cached
       
    15 
       
    16 from cubicweb.toolsutils import read_config
       
    17 from cubicweb.cwconfig import CubicWebConfiguration, register_persistent_options, merge_options
       
    18 
       
    19 _ = unicode
       
    20 
       
    21 register_persistent_options( (
       
    22     # site-wide only web ui configuration
       
    23     ('site-title',
       
    24      {'type' : 'string', 'default': 'unset title',
       
    25       'help': _('site title'),
       
    26       'sitewide': True, 'group': 'ui', 
       
    27       }),
       
    28     ('main-template',
       
    29      {'type' : 'string', 'default': 'main',
       
    30       'help': _('id of main template used to render pages'),
       
    31       'sitewide': True, 'group': 'ui',
       
    32       }),
       
    33     # user web ui configuration
       
    34     ('fckeditor',
       
    35      {'type' : 'yn', 'default': True,
       
    36       'help': _('should html fields being edited using fckeditor (a HTML '
       
    37                 'WYSIWYG editor).  You should also select text/html as default '
       
    38                 'text format to actually get fckeditor.'),
       
    39       'group': 'ui',
       
    40       }),
       
    41     # navigation configuration
       
    42     ('page-size',
       
    43      {'type' : 'int', 'default': 40,
       
    44       'help': _('maximum number of objects displayed by page of results'),
       
    45       'group': 'navigation',
       
    46       }),
       
    47     ('related-limit',
       
    48      {'type' : 'int', 'default': 8,
       
    49       'help': _('maximum number of related entities to display in the primary '
       
    50                 'view'),
       
    51       'group': 'navigation',
       
    52       }),
       
    53     ('combobox-limit',
       
    54      {'type' : 'int', 'default': 20,
       
    55       'help': _('maximum number of entities to display in related combo box'),
       
    56       'group': 'navigation',
       
    57       }),
       
    58     
       
    59     ))
       
    60 
       
    61 
       
    62 class WebConfiguration(CubicWebConfiguration):
       
    63     """the WebConfiguration is a singleton object handling application's
       
    64     configuration and preferences
       
    65     """
       
    66     cubicweb_vobject_path = CubicWebConfiguration.cubicweb_vobject_path | set(['web/views'])
       
    67     cube_vobject_path = CubicWebConfiguration.cube_vobject_path | set(['views'])
       
    68     
       
    69     options = merge_options(CubicWebConfiguration.options + (
       
    70         ('anonymous-user',
       
    71          {'type' : 'string',
       
    72           'default': None,
       
    73           'help': 'login of the CubicWeb user account to use for anonymous user (if you want to allow anonymous)',
       
    74           'group': 'main', 'inputlevel': 1,
       
    75           }),
       
    76         ('anonymous-password',
       
    77          {'type' : 'string',
       
    78           'default': None,
       
    79           'help': 'password of the CubicWeb user account to use for anonymous user, '
       
    80           'if anonymous-user is set',
       
    81           'group': 'main', 'inputlevel': 1,
       
    82           }),
       
    83         ('query-log-file',
       
    84          {'type' : 'string',
       
    85           'default': None,
       
    86           'help': 'web application query log file',
       
    87           'group': 'main', 'inputlevel': 2,
       
    88           }),
       
    89         ('pyro-application-id',
       
    90          {'type' : 'string',
       
    91           'default': Method('default_application_id'),
       
    92           'help': 'CubicWeb application identifier in the Pyro name server',
       
    93           'group': 'pyro-client', 'inputlevel': 1,
       
    94           }),
       
    95         # web configuration
       
    96         ('https-url',
       
    97          {'type' : 'string',
       
    98           'default': None,
       
    99           'help': 'web server root url on https. By specifying this option your '\
       
   100           'site can be available as an http and https site. Authenticated users '\
       
   101           'will in this case be authenticated and once done navigate through the '\
       
   102           'https site. IMPORTANTE NOTE: to do this work, you should have your '\
       
   103           'apache redirection include "https" as base url path so cubicweb can '\
       
   104           'differentiate between http vs https access. For instance: \n'\
       
   105           'RewriteRule ^/demo/(.*) http://127.0.0.1:8080/https/$1 [L,P]\n'\
       
   106           'where the cubicweb web server is listening on port 8080.',
       
   107           'group': 'main', 'inputlevel': 2,
       
   108           }),
       
   109         ('auth-mode',
       
   110          {'type' : 'choice',
       
   111           'choices' : ('cookie', 'http'),
       
   112           'default': 'cookie',
       
   113           'help': 'authentication mode (cookie / http)',
       
   114           'group': 'web', 'inputlevel': 1,
       
   115           }),
       
   116         ('realm',
       
   117          {'type' : 'string',
       
   118           'default': 'cubicweb',
       
   119           'help': 'realm to use on HTTP authentication mode',
       
   120           'group': 'web', 'inputlevel': 2,
       
   121           }),
       
   122         ('http-session-time',
       
   123          {'type' : 'int',
       
   124           'default': 0,
       
   125           'help': 'duration in seconds for HTTP sessions. 0 mean no expiration. '\
       
   126           'Should be greater than RQL server\'s session-time.',
       
   127           'group': 'web', 'inputlevel': 2,
       
   128           }),
       
   129         ('cleanup-session-time',
       
   130          {'type' : 'int',
       
   131           'default': 43200,
       
   132           'help': 'duration in seconds for which unused connections should be '\
       
   133           'closed, to limit memory consumption. This is different from '\
       
   134           'http-session-time since in some cases you may have an unexpired http '\
       
   135           'session (e.g. valid session cookie) which will trigger transparent '\
       
   136           'creation of a new session. In other cases, sessions may never expire \
       
   137           and cause memory leak. Should be smaller than http-session-time, '\
       
   138           'unless it\'s 0. Default to 12 h.',
       
   139           'group': 'web', 'inputlevel': 2,
       
   140           }),
       
   141         ('cleanup-anonymous-session-time',
       
   142          {'type' : 'int',
       
   143           'default': 120,
       
   144           'help': 'Same as cleanup-session-time but specific to anonymous '\
       
   145           'sessions. Default to 2 min.',
       
   146           'group': 'web', 'inputlevel': 2,
       
   147           }),
       
   148         ('embed-allowed',
       
   149          {'type' : 'regexp',
       
   150           'default': None,
       
   151           'help': 'regular expression matching URLs that may be embeded. \
       
   152 leave it blank if you don\'t want the embedding feature, or set it to ".*" \
       
   153 if you want to allow everything',
       
   154           'group': 'web', 'inputlevel': 1,
       
   155           }),
       
   156         ('submit-url',
       
   157          {'type' : 'string',
       
   158           'default': Method('default_submit_url'),
       
   159           'help': ('URL that may be used to report bug in this application '
       
   160                    'by direct access to the project\'s (jpl) tracker, '
       
   161                    'if you want this feature on. The url should looks like '
       
   162                    'http://mytracker.com/view?__linkto=concerns:1234:subject&etype=Ticket&type=bug&vid=creation '
       
   163                    'where 1234 should be replaced by the eid of your project in '
       
   164                    'the tracker. If you have no idea about what I\'am talking '
       
   165                    'about, you should probably let no value for this option.'),
       
   166           'group': 'web', 'inputlevel': 2,
       
   167           }),
       
   168         ('submit-mail',
       
   169          {'type' : 'string',
       
   170           'default': None,
       
   171           'help': ('Mail used as recipient to report bug in this application, '
       
   172                    'if you want this feature on'),
       
   173           'group': 'web', 'inputlevel': 2,
       
   174           }),
       
   175 
       
   176         ('language-negociation',
       
   177          {'type' : 'yn',
       
   178           'default': True,
       
   179           'help': 'use Accept-Language http header to try to set user '\
       
   180           'interface\'s language according to browser defined preferences',
       
   181           'group': 'web', 'inputlevel': 2,
       
   182           }),
       
   183         
       
   184         ('print-traceback',
       
   185          {'type' : 'yn',
       
   186           'default': not CubicWebConfiguration.mode == 'installed',
       
   187           'help': 'print the traceback on the error page when an error occured',
       
   188           'group': 'web', 'inputlevel': 2,
       
   189           }),
       
   190         ))
       
   191 
       
   192     def default_submit_url(self):
       
   193         try:
       
   194             cube = self.cubes()[0]
       
   195             cubeeid = self.cube_pkginfo(cube).cube_eid
       
   196         except Exception, ex:
       
   197             return None
       
   198         if cubeeid:
       
   199             return 'http://intranet.logilab.fr/jpl/view?__linkto=concerns:%s:subject&etype=Ticket&type=bug&vid=creation' % cubeeid
       
   200         return None
       
   201 
       
   202     # method used to connect to the repository: 'inmemory' / 'pyro'
       
   203     # Pyro repository by default
       
   204     repo_method = 'pyro'
       
   205     
       
   206     # don't use @cached: we want to be able to disable it while this must still
       
   207     # be cached
       
   208     def repository(self, vreg=None):
       
   209         """return the application's repository object"""
       
   210         try:
       
   211             return self.__repo
       
   212         except AttributeError:
       
   213             from cubicweb.dbapi import get_repository
       
   214             if self.repo_method == 'inmemory':
       
   215                 repo = get_repository('inmemory', vreg=vreg, config=self)
       
   216             else:
       
   217                 repo = get_repository('pyro', self['pyro-application-id'],
       
   218                                       config=self)
       
   219             self.__repo = repo
       
   220             return repo
       
   221 
       
   222     def vc_config(self):
       
   223         return self.repository().get_versions()
       
   224     
       
   225     # mapping to external resources (id -> path) (`external_resources` file) ##
       
   226     ext_resources = {
       
   227         'FAVICON':  'DATADIR/favicon.ico',
       
   228         'LOGO':     'DATADIR/logo.png',
       
   229         'RSS_LOGO': 'DATADIR/rss.png',
       
   230         'HELP':     'DATADIR/help.png',
       
   231         'CALENDAR_ICON': 'DATADIR/calendar.gif',
       
   232         'SEARCH_GO':'DATADIR/go.png',
       
   233         
       
   234         'FCKEDITOR_PATH':  '/usr/share/fckeditor/',
       
   235         
       
   236         'IE_STYLESHEETS':    ['DATADIR/cubicweb.ie.css'],
       
   237         'STYLESHEETS':       ['DATADIR/cubicweb.css'],
       
   238         'STYLESHEETS_PRINT': ['DATADIR/cubicweb.print.css'],
       
   239         
       
   240         'JAVASCRIPTS':       ['DATADIR/jquery.js',
       
   241                               'DATADIR/cubicweb.compat.js',
       
   242                               'DATADIR/jquery.json.js',
       
   243                               'DATADIR/cubicweb.python.js',
       
   244                               'DATADIR/cubicweb.htmlhelpers.js'],
       
   245         }
       
   246         
       
   247         
       
   248     def anonymous_user(self):
       
   249         """return a login and password to use for anonymous users. None
       
   250         may be returned for both if anonymous connections are not allowed
       
   251         """
       
   252         try:
       
   253             user = self['anonymous-user']
       
   254             passwd = self['anonymous-password']
       
   255         except KeyError:
       
   256             user, passwd = None, None
       
   257         if user is not None:
       
   258             user = unicode(user)
       
   259         return user, passwd
       
   260     
       
   261     def has_resource(self, rid):
       
   262         """return true if an external resource is defined"""
       
   263         return bool(self.ext_resources.get(rid))
       
   264 
       
   265     @cached
       
   266     def locate_resource(self, rid):
       
   267         """return the directory where the given resource may be found"""
       
   268         return self._fs_locate(rid, 'data')
       
   269             
       
   270     @cached
       
   271     def locate_doc_file(self, fname):
       
   272         """return the directory where the given resource may be found"""
       
   273         return self._fs_locate(fname, 'wdoc')
       
   274             
       
   275     def _fs_locate(self, rid, rdirectory):
       
   276         """return the directory where the given resource may be found"""
       
   277         path = [self.apphome] + self.cubes_path() + [join(self.shared_dir())]
       
   278         for directory in path:
       
   279             if exists(join(directory, rdirectory, rid)):
       
   280                 return join(directory, rdirectory)
       
   281             
       
   282     def locate_all_files(self, rid, rdirectory='wdoc'):
       
   283         """return all files corresponding to the given resource"""
       
   284         path = [self.apphome] + self.cubes_path() + [join(self.shared_dir())]
       
   285         for directory in path:
       
   286             fpath = join(directory, rdirectory, rid)
       
   287             if exists(fpath):
       
   288                 yield join(fpath)
       
   289 
       
   290     def load_configuration(self):
       
   291         """load application's configuration files"""
       
   292         super(WebConfiguration, self).load_configuration()
       
   293         # load external resources definition
       
   294         self._build_ext_resources()
       
   295         self._init_base_url()
       
   296         
       
   297     def _init_base_url(self):
       
   298         # normalize base url(s)
       
   299         baseurl = self['base-url']
       
   300         if baseurl and baseurl[-1] != '/':
       
   301             baseurl += '/'
       
   302             self.global_set_option('base-url', baseurl)
       
   303         httpsurl = self['https-url']
       
   304         if httpsurl and httpsurl[-1] != '/':
       
   305             httpsurl += '/'
       
   306             self.global_set_option('https-url', httpsurl)
       
   307 
       
   308     def _build_ext_resources(self):
       
   309         libresourcesfile = join(self.shared_dir(), 'data', 'external_resources')
       
   310         self.ext_resources.update(read_config(libresourcesfile))
       
   311         for path in reversed([self.apphome] + self.cubes_path()):
       
   312             resourcesfile = join(path, 'data', 'external_resources')
       
   313             if exists(resourcesfile):
       
   314                 self.debug('loading %s', resourcesfile)
       
   315                 self.ext_resources.update(read_config(resourcesfile))
       
   316         for resource in ('STYLESHEETS', 'STYLESHEETS_PRINT',
       
   317                          'IE_STYLESHEETS', 'JAVASCRIPTS'):
       
   318             val = self.ext_resources[resource]
       
   319             if isinstance(val, str):
       
   320                 files = [w.strip() for w in val.split(',') if w.strip()]
       
   321                 self.ext_resources[resource] = files