--- /dev/null
+ # -*- coding: utf-8 -*-
+ #
+ # OpenERP Technical Documentation configuration file, created by
+ # sphinx-quickstart on Fri Feb 17 16:14:06 2012.
+ #
+ # This file is execfile()d with the current directory set to its containing dir.
+ #
+ # Note that not all possible configuration values are present in this
+ # autogenerated file.
+ #
+ # All configuration values have a default; values that are commented out
+ # serve to show the default.
+
+ import sys, os
+
+ # If extensions (or modules to document with autodoc) are in another directory,
+ # add these directories to sys.path here. If the directory is relative to the
+ # documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
+ sys.path.append(os.path.abspath('_themes'))
-sys.path.append(os.path.abspath('..'))
-sys.path.append(os.path.abspath('../openerp'))
++sys.path.insert(0, os.path.abspath('../addons'))
++sys.path.insert(0, os.path.abspath('..'))
+
+ # -- General configuration -----------------------------------------------------
+
+ # If your documentation needs a minimal Sphinx version, state it here.
+ #needs_sphinx = '1.0'
+
+ # Add any Sphinx extension module names here, as strings. They can be extensions
+ # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.viewcode']
+
+ # Add any paths that contain templates here, relative to this directory.
+ templates_path = ['_templates']
+
+ # The suffix of source filenames.
+ source_suffix = '.rst'
+
+ # The encoding of source files.
+ #source_encoding = 'utf-8-sig'
+
+ # The master toctree document.
+ master_doc = 'index'
+
+ # General information about the project.
-project = u'OpenERP Server Developers Documentation'
++project = u'OpenERP Web Developers Documentation'
+ copyright = u'2012, OpenERP s.a.'
+
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+ # built documents.
+ #
+ # The short X.Y version.
+ version = '7.0'
+ # The full version, including alpha/beta/rc tags.
-release = '7.0b'
++release = '7.0'
+
+ # The language for content autogenerated by Sphinx. Refer to documentation
+ # for a list of supported languages.
+ #language = None
+
+ # There are two options for replacing |today|: either, you set today to some
+ # non-false value, then it is used:
+ #today = ''
+ # Else, today_fmt is used as the format for a strftime call.
+ #today_fmt = '%B %d, %Y'
+
+ # List of patterns, relative to source directory, that match files and
+ # directories to ignore when looking for source files.
+ exclude_patterns = ['_build']
+
+ # The reST default role (used for this markup: `text`) to use for all documents.
+ #default_role = None
+
+ # If true, '()' will be appended to :func: etc. cross-reference text.
+ #add_function_parentheses = True
+
+ # If true, the current module name will be prepended to all description
+ # unit titles (such as .. function::).
+ #add_module_names = True
+
+ # If true, sectionauthor and moduleauthor directives will be shown in the
+ # output. They are ignored by default.
+ #show_authors = False
+
+ # The name of the Pygments (syntax highlighting) style to use.
+ pygments_style = 'sphinx'
+
+ # A list of ignored prefixes for module index sorting.
+ #modindex_common_prefix = []
+
+
+ # -- Options for HTML output ---------------------------------------------------
+
+ # The theme to use for HTML and HTML Help pages. See the documentation for
+ # a list of builtin themes.
+ html_theme = 'flask'
+
+ # Theme options are theme-specific and customize the look and feel of a theme
+ # further. For a list of options available for each theme, see the
+ # documentation.
+ #html_theme_options = {}
+
+ # Add any paths that contain custom themes here, relative to this directory.
+ html_theme_path = ['_themes']
+
+ # The name for this set of Sphinx documents. If None, it defaults to
+ # "<project> v<release> documentation".
+ #html_title = None
+
+ # A shorter title for the navigation bar. Default is the same as html_title.
+ #html_short_title = None
+
+ # The name of an image file (relative to this directory) to place at the top
+ # of the sidebar.
+ #html_logo = None
+
+ # The name of an image file (within the static path) to use as favicon of the
+ # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+ # pixels large.
+ #html_favicon = None
+
+ # Add any paths that contain custom static files (such as style sheets) here,
+ # relative to this directory. They are copied after the builtin static files,
+ # so a file named "default.css" will overwrite the builtin "default.css".
+ html_static_path = ['_static']
+
+ # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+ # using the given strftime format.
+ #html_last_updated_fmt = '%b %d, %Y'
+
+ # If true, SmartyPants will be used to convert quotes and dashes to
+ # typographically correct entities.
+ #html_use_smartypants = True
+
+ # Custom sidebar templates, maps document names to template names.
+ html_sidebars = {
+ 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'],
+ '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html',
+ 'sourcelink.html', 'searchbox.html']
+ }
+
+ # Additional templates that should be rendered to pages, maps page names to
+ # template names.
+ #html_additional_pages = {}
+
+ # If false, no module index is generated.
+ #html_domain_indices = True
+
+ # If false, no index is generated.
+ #html_use_index = True
+
+ # If true, the index is split into individual pages for each letter.
+ #html_split_index = False
+
+ # If true, links to the reST sources are added to the pages.
+ #html_show_sourcelink = True
+
+ # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+ #html_show_sphinx = True
+
+ # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+ #html_show_copyright = True
+
+ # If true, an OpenSearch description file will be output, and all pages will
+ # contain a <link> tag referring to it. The value of this option must be the
+ # base URL from which the finished HTML is served.
+ #html_use_opensearch = ''
+
+ # This is the file name suffix for HTML files (e.g. ".xhtml").
+ #html_file_suffix = None
+
+ # Output file base name for HTML help builder.
-htmlhelp_basename = 'openerp-server-doc'
++htmlhelp_basename = 'openerp-web-doc'
+
+
+ # -- Options for LaTeX output --------------------------------------------------
+
+ latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #'preamble': '',
+ }
+
+ # Grouping the document tree into LaTeX files. List of tuples
+ # (source start file, target name, title, author, documentclass [howto/manual]).
+ latex_documents = [
- ('index', 'openerp-server-doc.tex', u'OpenERP Server Developers Documentation',
++ ('index', 'openerp-web-doc.tex', u'OpenERP Web Developers Documentation',
+ u'OpenERP s.a.', 'manual'),
+ ]
+
+ # The name of an image file (relative to this directory) to place at the top of
+ # the title page.
+ #latex_logo = None
+
+ # For "manual" documents, if this is true, then toplevel headings are parts,
+ # not chapters.
+ #latex_use_parts = False
+
+ # If true, show page references after internal links.
+ #latex_show_pagerefs = False
+
+ # If true, show URL addresses after external links.
+ #latex_show_urls = False
+
+ # Documents to append as an appendix to all manuals.
+ #latex_appendices = []
+
+ # If false, no module index is generated.
+ #latex_domain_indices = True
+
+
+ # -- Options for manual page output --------------------------------------------
+
+ # One entry per manual page. List of tuples
+ # (source start file, name, description, authors, manual section).
+ man_pages = [
- ('index', 'openerp-server-doc', u'OpenERP Server Developers Documentation',
++ ('index', 'openerp-web-doc', u'OpenERP Web Developers Documentation',
+ [u'OpenERP s.a.'], 1)
+ ]
+
+ # If true, show URL addresses after external links.
+ #man_show_urls = False
+
+
+ # -- Options for Texinfo output ------------------------------------------------
+
+ # Grouping the document tree into Texinfo files. List of tuples
+ # (source start file, target name, title, author,
+ # dir menu entry, description, category)
+ texinfo_documents = [
- ('index', 'OpenERPServerDocumentation', u'OpenERP Server Developers Documentation',
- u'OpenERP s.a.', 'OpenERPServerDocumentation', 'Developers documentation for the openobject-server project.',
++ ('index', 'OpenERPWebDocumentation', u'OpenERP Web Developers Documentation',
++ u'OpenERP s.a.', 'OpenERPWebDocumentation', 'Developers documentation for the openerp-web project.',
+ 'Miscellaneous'),
+ ]
+
+ # Documents to append as an appendix to all manuals.
+ #texinfo_appendices = []
+
+ # If false, no module index is generated.
+ #texinfo_domain_indices = True
+
+ # How to display URL addresses: 'footnote', 'no', or 'inline'.
+ #texinfo_show_urls = 'footnote'
+
++todo_include_todos = True
+
+ # Example configuration for intersphinx: refer to the Python standard library.
+ intersphinx_mapping = {
+ 'python': ('http://docs.python.org/', None),
- 'openerpweb': ('http://doc.openerp.com/trunk/developers/web', None),
++ 'openerpserver': ('http://doc.openerp.com/trunk/developers/server', None),
+ 'openerpdev': ('http://doc.openerp.com/trunk/developers', None),
+ }
--- /dev/null
+ # -*- coding: utf-8 -*-
+ #----------------------------------------------------------
+ # OpenERP Web HTTP layer
+ #----------------------------------------------------------
+ import ast
+ import cgi
+ import contextlib
+ import functools
+ import getpass
+ import logging
+ import mimetypes
+ import os
+ import pprint
+ import sys
+ import tempfile
+ import threading
+ import time
+ import traceback
+ import urllib
+ import urlparse
+ import uuid
+ import xmlrpclib
+
+ import simplejson
+ import werkzeug.contrib.sessions
+ import werkzeug.datastructures
+ import werkzeug.exceptions
+ import werkzeug.utils
+ import werkzeug.wrappers
+ import werkzeug.wsgi
+
+ import openerp
+
+ import nonliterals
+ import session
+
+ _logger = logging.getLogger(__name__)
+
+ #----------------------------------------------------------
+ # RequestHandler
+ #----------------------------------------------------------
+ class WebRequest(object):
+ """ Parent class for all OpenERP Web request types, mostly deals with
+ initialization and setup of the request object (the dispatching itself has
+ to be handled by the subclasses)
+
+ :param request: a wrapped werkzeug Request object
+ :type request: :class:`werkzeug.wrappers.BaseRequest`
+
+ .. attribute:: httprequest
+
+ the original :class:`werkzeug.wrappers.Request` object provided to the
+ request
+
+ .. attribute:: httpsession
+
+ a :class:`~collections.Mapping` holding the HTTP session data for the
+ current http session
+
+ .. attribute:: params
+
+ :class:`~collections.Mapping` of request parameters, not generally
+ useful as they're provided directly to the handler method as keyword
+ arguments
+
+ .. attribute:: session_id
+
+ opaque identifier for the :class:`session.OpenERPSession` instance of
+ the current request
+
+ .. attribute:: session
+
+ :class:`~session.OpenERPSession` instance for the current request
+
+ .. attribute:: context
+
+ :class:`~collections.Mapping` of context values for the current request
+
+ .. attribute:: debug
+
+ ``bool``, indicates whether the debug mode is active on the client
+ """
+ def __init__(self, request):
+ self.httprequest = request
+ self.httpresponse = None
+ self.httpsession = request.session
+
+ def init(self, params):
+ self.params = dict(params)
+ # OpenERP session setup
+ self.session_id = self.params.pop("session_id", None) or uuid.uuid4().hex
+ self.session = self.httpsession.get(self.session_id)
+ if not self.session:
+ self.httpsession[self.session_id] = self.session = session.OpenERPSession()
+ self.context = self.params.pop('context', None)
+ self.debug = self.params.pop('debug', False) != False
+
+ class JsonRequest(WebRequest):
+ """ JSON-RPC2 over HTTP.
+
+ Sucessful request::
+
+ --> {"jsonrpc": "2.0",
+ "method": "call",
+ "params": {"session_id": "SID",
+ "context": {},
+ "arg1": "val1" },
+ "id": null}
+
+ <-- {"jsonrpc": "2.0",
+ "result": { "res1": "val1" },
+ "id": null}
+
+ Request producing a error::
+
+ --> {"jsonrpc": "2.0",
+ "method": "call",
+ "params": {"session_id": "SID",
+ "context": {},
+ "arg1": "val1" },
+ "id": null}
+
+ <-- {"jsonrpc": "2.0",
+ "error": {"code": 1,
+ "message": "End user error message.",
+ "data": {"code": "codestring",
+ "debug": "traceback" } },
+ "id": null}
+
+ """
+ def dispatch(self, controller, method):
+ """ Calls the method asked for by the JSON-RPC2 or JSONP request
+
+ :param controller: the instance of the controller which received the request
+ :param method: the method which received the request
+
+ :returns: an utf8 encoded JSON-RPC2 or JSONP reply
+ """
+ args = self.httprequest.args
+ jsonp = args.get('jsonp')
+ requestf = None
+ request = None
+ request_id = args.get('id')
+
+ if jsonp and self.httprequest.method == 'POST':
+ # jsonp 2 steps step1 POST: save call
+ self.init(args)
+ self.session.jsonp_requests[request_id] = self.httprequest.form['r']
+ headers=[('Content-Type', 'text/plain; charset=utf-8')]
+ r = werkzeug.wrappers.Response(request_id, headers=headers)
+ return r
+ elif jsonp and args.get('r'):
+ # jsonp method GET
+ request = args.get('r')
+ elif jsonp and request_id:
+ # jsonp 2 steps step2 GET: run and return result
+ self.init(args)
+ request = self.session.jsonp_requests.pop(request_id, "")
+ else:
+ # regular jsonrpc2
+ requestf = self.httprequest.stream
+
+ response = {"jsonrpc": "2.0" }
+ error = None
+ try:
+ # Read POST content or POST Form Data named "request"
+ if requestf:
+ self.jsonrequest = simplejson.load(requestf, object_hook=nonliterals.non_literal_decoder)
+ else:
+ self.jsonrequest = simplejson.loads(request, object_hook=nonliterals.non_literal_decoder)
+ self.init(self.jsonrequest.get("params", {}))
+ if _logger.isEnabledFor(logging.DEBUG):
+ _logger.debug("--> %s.%s\n%s", controller.__class__.__name__, method.__name__, pprint.pformat(self.jsonrequest))
+ response['id'] = self.jsonrequest.get('id')
+ response["result"] = method(controller, self, **self.params)
+ except session.AuthenticationError:
+ error = {
+ 'code': 100,
+ 'message': "OpenERP Session Invalid",
+ 'data': {
+ 'type': 'session_invalid',
+ 'debug': traceback.format_exc()
+ }
+ }
+ except xmlrpclib.Fault, e:
+ error = {
+ 'code': 200,
+ 'message': "OpenERP Server Error",
+ 'data': {
+ 'type': 'server_exception',
+ 'fault_code': e.faultCode,
+ 'debug': "Client %s\nServer %s" % (
+ "".join(traceback.format_exception("", None, sys.exc_traceback)), e.faultString)
+ }
+ }
+ except Exception:
+ logging.getLogger(__name__ + '.JSONRequest.dispatch').exception\
+ ("An error occured while handling a json request")
+ error = {
+ 'code': 300,
+ 'message': "OpenERP WebClient Error",
+ 'data': {
+ 'type': 'client_exception',
+ 'debug': "Client %s" % traceback.format_exc()
+ }
+ }
+ if error:
+ response["error"] = error
+
+ if _logger.isEnabledFor(logging.DEBUG):
+ _logger.debug("<--\n%s", pprint.pformat(response))
+
+ if jsonp:
+ mime = 'application/javascript'
+ body = "%s(%s);" % (jsonp, simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder),)
+ else:
+ mime = 'application/json'
+ body = simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder)
+
+ r = werkzeug.wrappers.Response(body, headers=[('Content-Type', mime), ('Content-Length', len(body))])
+ return r
+
+ def jsonrequest(f):
+ """ Decorator marking the decorated method as being a handler for a
+ JSON-RPC request (the exact request path is specified via the
+ ``$(Controller._cp_path)/$methodname`` combination.
+
+ If the method is called, it will be provided with a :class:`JsonRequest`
+ instance and all ``params`` sent during the JSON-RPC request, apart from
+ the ``session_id``, ``context`` and ``debug`` keys (which are stripped out
+ beforehand)
+ """
+ @functools.wraps(f)
+ def json_handler(controller, request):
+ return JsonRequest(request).dispatch(controller, f)
+ json_handler.exposed = True
+ return json_handler
+
+ class HttpRequest(WebRequest):
+ """ Regular GET/POST request
+ """
+ def dispatch(self, controller, method):
+ params = dict(self.httprequest.args)
+ params.update(self.httprequest.form)
+ params.update(self.httprequest.files)
+ self.init(params)
+ akw = {}
+ for key, value in self.httprequest.args.iteritems():
+ if isinstance(value, basestring) and len(value) < 1024:
+ akw[key] = value
+ else:
+ akw[key] = type(value)
+ _logger.debug("%s --> %s.%s %r", self.httprequest.method, controller.__class__.__name__, method.__name__, akw)
+ try:
+ r = method(controller, self, **self.params)
+ except xmlrpclib.Fault, e:
+ r = werkzeug.exceptions.InternalServerError(cgi.escape(simplejson.dumps({
+ 'code': 200,
+ 'message': "OpenERP Server Error",
+ 'data': {
+ 'type': 'server_exception',
+ 'fault_code': e.faultCode,
+ 'debug': "Server %s\nClient %s" % (
+ e.faultString, traceback.format_exc())
+ }
+ })))
+ except Exception:
+ logging.getLogger(__name__ + '.HttpRequest.dispatch').exception(
+ "An error occurred while handling a json request")
+ r = werkzeug.exceptions.InternalServerError(cgi.escape(simplejson.dumps({
+ 'code': 300,
+ 'message': "OpenERP WebClient Error",
+ 'data': {
+ 'type': 'client_exception',
+ 'debug': "Client %s" % traceback.format_exc()
+ }
+ })))
+ if self.debug or 1:
+ if isinstance(r, (werkzeug.wrappers.BaseResponse, werkzeug.exceptions.HTTPException)):
+ _logger.debug('<-- %s', r)
+ else:
+ _logger.debug("<-- size: %s", len(r))
+ return r
+
+ def make_response(self, data, headers=None, cookies=None):
+ """ Helper for non-HTML responses, or HTML responses with custom
+ response headers or cookies.
+
+ While handlers can just return the HTML markup of a page they want to
+ send as a string if non-HTML data is returned they need to create a
+ complete response object, or the returned data will not be correctly
+ interpreted by the clients.
+
+ :param basestring data: response body
+ :param headers: HTTP headers to set on the response
+ :type headers: ``[(name, value)]``
+ :param collections.Mapping cookies: cookies to set on the client
+ """
+ response = werkzeug.wrappers.Response(data, headers=headers)
+ if cookies:
+ for k, v in cookies.iteritems():
+ response.set_cookie(k, v)
+ return response
+
+ def not_found(self, description=None):
+ """ Helper for 404 response, return its result from the method
+ """
+ return werkzeug.exceptions.NotFound(description)
+
+ def httprequest(f):
+ """ Decorator marking the decorated method as being a handler for a
+ normal HTTP request (the exact request path is specified via the
+ ``$(Controller._cp_path)/$methodname`` combination.
+
+ If the method is called, it will be provided with a :class:`HttpRequest`
+ instance and all ``params`` sent during the request (``GET`` and ``POST``
+ merged in the same dictionary), apart from the ``session_id``, ``context``
+ and ``debug`` keys (which are stripped out beforehand)
+ """
+ @functools.wraps(f)
+ def http_handler(controller, request):
+ return HttpRequest(request).dispatch(controller, f)
+ http_handler.exposed = True
+ return http_handler
+
+ #----------------------------------------------------------
+ # Controller registration with a metaclass
+ #----------------------------------------------------------
+ addons_module = {}
+ addons_manifest = {}
+ controllers_class = []
+ controllers_object = {}
+ controllers_path = {}
+
+ class ControllerType(type):
+ def __init__(cls, name, bases, attrs):
+ super(ControllerType, cls).__init__(name, bases, attrs)
+ controllers_class.append(("%s.%s" % (cls.__module__, cls.__name__), cls))
+
+ class Controller(object):
+ __metaclass__ = ControllerType
+
+ #----------------------------------------------------------
+ # Session context manager
+ #----------------------------------------------------------
+ STORES = {}
+
+ @contextlib.contextmanager
+ def session_context(request, storage_path, session_cookie='httpsessionid'):
+ session_store, session_lock = STORES.get(storage_path, (None, None))
+ if not session_store:
+ session_store = werkzeug.contrib.sessions.FilesystemSessionStore( storage_path)
+ session_lock = threading.Lock()
+ STORES[storage_path] = session_store, session_lock
+
+ sid = request.cookies.get(session_cookie)
+ with session_lock:
+ if sid:
+ request.session = session_store.get(sid)
+ else:
+ request.session = session_store.new()
+
+ try:
+ yield request.session
+ finally:
+ # Remove all OpenERPSession instances with no uid, they're generated
+ # either by login process or by HTTP requests without an OpenERP
+ # session id, and are generally noise
+ removed_sessions = set()
+ for key, value in request.session.items():
+ if not isinstance(value, session.OpenERPSession):
+ continue
+ if getattr(value, '_suicide', False) or (
+ not value._uid
+ and not value.jsonp_requests
+ # FIXME do not use a fixed value
+ and value._creation_time + (60*5) < time.time()):
+ _logger.debug('remove session %s', key)
+ removed_sessions.add(key)
+ del request.session[key]
+
+ with session_lock:
+ if sid:
+ # Re-load sessions from storage and merge non-literal
+ # contexts and domains (they're indexed by hash of the
+ # content so conflicts should auto-resolve), otherwise if
+ # two requests alter those concurrently the last to finish
+ # will overwrite the previous one, leading to loss of data
+ # (a non-literal is lost even though it was sent to the
+ # client and client errors)
+ #
+ # note that domains_store and contexts_store are append-only (we
+ # only ever add items to them), so we can just update one with the
+ # other to get the right result, if we want to merge the
+ # ``context`` dict we'll need something smarter
+ in_store = session_store.get(sid)
+ for k, v in request.session.iteritems():
+ stored = in_store.get(k)
+ if stored and isinstance(v, session.OpenERPSession):
+ v.contexts_store.update(stored.contexts_store)
+ v.domains_store.update(stored.domains_store)
+ if not hasattr(v, 'jsonp_requests'):
+ v.jsonp_requests = {}
+ v.jsonp_requests.update(getattr(
+ stored, 'jsonp_requests', {}))
+
+ # add missing keys
+ for k, v in in_store.iteritems():
+ if k not in request.session and k not in removed_sessions:
+ request.session[k] = v
+
+ session_store.save(request.session)
+
+ #----------------------------------------------------------
+ # WSGI Application
+ #----------------------------------------------------------
+ # Add potentially missing (older ubuntu) font mime types
+ mimetypes.add_type('application/font-woff', '.woff')
+ mimetypes.add_type('application/vnd.ms-fontobject', '.eot')
+ mimetypes.add_type('application/x-font-ttf', '.ttf')
+
+ class DisableCacheMiddleware(object):
+ def __init__(self, app):
+ self.app = app
+ def __call__(self, environ, start_response):
+ def start_wrapped(status, headers):
+ referer = environ.get('HTTP_REFERER', '')
+ parsed = urlparse.urlparse(referer)
+ debug = parsed.query.count('debug') >= 1
+
+ new_headers = []
+ unwanted_keys = ['Last-Modified']
+ if debug:
+ new_headers = [('Cache-Control', 'no-cache')]
+ unwanted_keys += ['Expires', 'Etag', 'Cache-Control']
+
+ for k, v in headers:
+ if k not in unwanted_keys:
+ new_headers.append((k, v))
+
+ start_response(status, new_headers)
+ return self.app(environ, start_wrapped)
+
+ class Root(object):
+ """Root WSGI application for the OpenERP Web Client.
+ """
+ def __init__(self):
+ self.httpsession_cookie = 'httpsessionid'
+ self.addons = {}
++ self.statics = {}
+
- static_dirs = self._load_addons()
- app = werkzeug.wsgi.SharedDataMiddleware( self.dispatch, static_dirs)
- self.dispatch = DisableCacheMiddleware(app)
++ self._load_addons()
+
+ try:
+ username = getpass.getuser()
+ except Exception:
+ username = "unknown"
+ self.session_storage = os.path.join(tempfile.gettempdir(), "oe-sessions-" + username)
+
+ if not os.path.exists(self.session_storage):
+ os.mkdir(self.session_storage, 0700)
+ _logger.debug('HTTP sessions stored in: %s', self.session_storage)
+
+ def __call__(self, environ, start_response):
+ """ Handle a WSGI request
+ """
+ return self.dispatch(environ, start_response)
+
- def dispatch(self, environ, start_response):
++ def _dispatch(self, environ, start_response):
+ """
+ Performs the actual WSGI dispatching for the application, may be
+ wrapped during the initialization of the object.
+
+ Call the object directly.
+ """
+ request = werkzeug.wrappers.Request(environ)
+ request.parameter_storage_class = werkzeug.datastructures.ImmutableDict
+ request.app = self
+
+ handler = self.find_handler(*(request.path.split('/')[1:]))
+
+ if not handler:
+ response = werkzeug.exceptions.NotFound()
+ else:
+ with session_context(request, self.session_storage, self.httpsession_cookie) as session:
+ result = handler( request)
+
+ if isinstance(result, basestring):
+ headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))]
+ response = werkzeug.wrappers.Response(result, headers=headers)
+ else:
+ response = result
+
+ if hasattr(response, 'set_cookie'):
+ response.set_cookie(self.httpsession_cookie, session.sid)
+
+ return response(environ, start_response)
+
+ def _load_addons(self):
+ """
+ Loads all addons at the specified addons path, returns a mapping of
+ static URLs to the corresponding directories
+ """
- statics = {}
++
+ for addons_path in openerp.modules.module.ad_paths:
+ for module in os.listdir(addons_path):
+ if module not in addons_module:
+ manifest_path = os.path.join(addons_path, module, '__openerp__.py')
+ path_static = os.path.join(addons_path, module, 'static')
+ if os.path.isfile(manifest_path) and os.path.isdir(path_static):
+ manifest = ast.literal_eval(open(manifest_path).read())
+ manifest['addons_path'] = addons_path
+ _logger.debug("Loading %s", module)
+ if 'openerp.addons' in sys.modules:
+ m = __import__('openerp.addons.' + module)
+ else:
+ m = __import__(module)
+ addons_module[module] = m
+ addons_manifest[module] = manifest
- statics['/%s/static' % module] = path_static
++ self.statics['/%s/static' % module] = path_static
++
+ for k, v in controllers_class:
+ if k not in controllers_object:
+ o = v()
+ controllers_object[k] = o
+ if hasattr(o, '_cp_path'):
+ controllers_path[o._cp_path] = o
- return statics
++
++ app = self._dispatch
++ if self.config.serve_static:
++ app = werkzeug.wsgi.SharedDataMiddleware(self._dispatch, self.statics)
++
++ self.dispatch = DisableCacheMiddleware(app)
+
+ def find_handler(self, *l):
+ """
+ Tries to discover the controller handling the request for the path
+ specified by the provided parameters
+
+ :param l: path sections to a controller or controller method
+ :returns: a callable matching the path sections, or ``None``
+ :rtype: ``Controller | None``
+ """
+ if l:
+ ps = '/' + '/'.join(l)
+ meth = 'index'
+ while ps:
+ c = controllers_path.get(ps)
+ if c:
+ m = getattr(c, meth, None)
+ if m and getattr(m, 'exposed', False):
+ _logger.debug("Dispatching to %s %s %s", ps, c, meth)
+ return m
+ ps, _slash, meth = ps.rpartition('/')
+ if not ps and meth:
+ ps = '/'
+ return None
+
+ def wsgi_postload():
+ openerp.wsgi.register_wsgi_handler(Root())
+
+ # vim:et:ts=4:sw=4: