diff --git a/doc/conf.py b/doc/conf.py index 077581d56af..4dafec15ae8 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -18,7 +18,13 @@ needs_sphinx = '1.1' # 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.todo', 'sphinx.ext.autodoc', 'odoodoc', 'patchqueue'] +extensions = [ + 'sphinx.ext.todo', + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'odoodoc', + 'patchqueue' +] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -152,3 +158,7 @@ html_sidebars = { # base URL from which the finished HTML is served. #html_use_opensearch = '' +intersphinx_mapping = { + 'python': ('https://docs.python.org/2/', None), + 'werkzeug': ('http://werkzeug.pocoo.org/docs/0.9/', None), +} diff --git a/doc/reference/http.rst b/doc/reference/http.rst index 6459e2bbd1b..c179691c4cb 100644 --- a/doc/reference/http.rst +++ b/doc/reference/http.rst @@ -12,16 +12,72 @@ Routing Request ======= +The request object is automatically set on :data:`openerp.http.request` at +the start of the request + +.. autoclass:: openerp.http.WebRequest + :members: + :member-order: bysource +.. autoclass:: openerp.http.HttpRequest + :members: +.. autoclass:: openerp.http.JsonRequest + :members: + Response ======== -JSON-RPC -======== +.. autoclass:: openerp.http.Response + :members: + :member-order: bysource + + .. maybe set this to document all the fine methods on Werkzeug's Response + object? (it works) + :inherited-members: .. _reference/http/controllers: -Extension: controllers -====================== +Controllers +=========== -.. this should be about inheritance/extension, do web controllers still do - anything else nowadays? +Controllers need to provide extensibility, much like +:class:`~openerp.models.Model`, but can't use the same mechanism as the +pre-requisites (a database with loaded modules) may not be available yet (e.g. +no database created, or no database selected). + +Controllers thus provide their own extension mechanism, separate from that of +models: + +Controllers are created by :ref:`inheriting ` from + +.. autoclass:: openerp.http.Controller + +and defining methods decorated with :func:`~openerp.http.route`:: + + class MyController(openerp.http.Controller): + @route('/some_url', auth='public') + def handler(self): + return stuff() + +To *override* a controller, :ref:`inherit ` from its +class and override relevant methods:: + + class Extension(MyController): + @route() + def handler(self): + do_before() + return super(Extension, self).handler() + +* decorating with :func:`~openerp.http.route` is necessary to keep the method + (and route) visible: if the method is redefined without decorating, it + will be "unpublished" +* the decorators of all methods are combined, if the overriding method's + decorator has no argument all previous ones will be kept, any provided + argument will override previously defined ones e.g.:: + + class Restrict(MyController): + @route(auth='user') + def handler(self): + return super(Restrict, self).handler() + + will change ``/some_url`` from public authentication to user (requiring a + log-in) diff --git a/openerp/http.py b/openerp/http.py index 6ae15f5a477..9cd0bfa0197 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -142,7 +142,7 @@ def redirect_with_hash(url, code=303): return "" % url class WebRequest(object): - """ Parent class for all OpenERP Web request types, mostly deals with + """ Parent class for all Odoo Web request types, mostly deals with initialization and setup of the request object (the dispatching itself has to be handled by the subclasses) @@ -154,60 +154,20 @@ class WebRequest(object): the original :class:`werkzeug.wrappers.Request` object provided to the request - .. attribute:: httpsession - - .. deprecated:: 8.0 - - Use :attr:`session` instead. - .. 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:`OpenERPSession` instance of - the current request - - .. attribute:: session - - a :class:`OpenERPSession` holding the HTTP session data for the - current http session - - .. attribute:: context - - :class:`~collections.Mapping` of context values for the current - request - - .. attribute:: db - - ``str``, the name of the database linked to the current request. Can - be ``None`` if the current request uses the ``none`` authentication - in ``web`` module's controllers. - - .. attribute:: uid - - ``int``, the id of the user related to the current request. Can be - ``None`` if the current request uses the ``none`` authentication. - - .. attribute:: env - - an :class:`openerp.api.Environment` bound to the current - request's ``cr``, ``uid`` and ``context`` """ def __init__(self, httprequest): self.httprequest = httprequest self.httpresponse = None self.httpsession = httprequest.session - self.session = httprequest.session - self.session_id = httprequest.session.sid self.disable_db = False self.uid = None self.endpoint = None self.auth_method = None - self._cr_cm = None self._cr = None # prevents transaction commit, use when you catch an exception during handling @@ -219,44 +179,49 @@ class WebRequest(object): threading.current_thread().dbname = self.db if self.session.uid: threading.current_thread().uid = self.session.uid - self.context = dict(self.session.context) - self.lang = self.context["lang"] - - @property - def registry(self): - """ - The registry to the database linked to this request. Can be ``None`` - if the current request uses the ``none`` authentication. - """ - return openerp.modules.registry.RegistryManager.get(self.db) if self.db else None - - @property - def db(self): - """ - The database linked to this request. Can be ``None`` - if the current request uses the ``none`` authentication. - """ - return self.session.db if not self.disable_db else None - - @property - def cr(self): - """ - The cursor initialized for the current method call. If the current - request uses the ``none`` authentication trying to access this - property will raise an exception. - """ - # some magic to lazy create the cr - if not self._cr: - self._cr = self.registry.cursor() - return self._cr @lazy_property def env(self): """ - The Environment bound to current request. + The :class:`~openerp.api.Environment` bound to current request. """ return openerp.api.Environment(self.cr, self.uid, self.context) + @lazy_property + def context(self): + """ + :class:`~collections.Mapping` of context values for the current + request + """ + return dict(self.session.context) + + @lazy_property + def lang(self): + return self.context["lang"] + + @lazy_property + def session(self): + """ + a :class:`OpenERPSession` holding the HTTP session data for the + current http session + """ + return self.httprequest.session + + @property + def cr(self): + """ + :class:`~openerp.sql_db.Cursor` initialized for the current method + call. + + Accessing the cursor when the current request uses the ``none`` + authentication will raise an exception. + """ + # can not be a lazy_property because manual rollback in _call_function + # if already set (?) + if not self._cr: + self._cr = self.registry.cursor() + return self._cr + def __enter__(self): _request_stack.push(self) return self @@ -316,6 +281,8 @@ class WebRequest(object): @property def debug(self): + """ Indicates whether the current request is in "debug" mode + """ return 'debug' in self.httprequest.args @contextlib.contextmanager @@ -323,6 +290,48 @@ class WebRequest(object): warnings.warn('please use request.registry and request.cr directly', DeprecationWarning) yield (self.registry, self.cr) + @lazy_property + def session_id(self): + """ + opaque identifier for the :class:`OpenERPSession` instance of + the current request + + .. deprecated:: 8.0 + + Use the ``id`` attribute on :attr:`.session` + """ + return self.session.id + + @property + def registry(self): + """ + The registry to the database linked to this request. Can be ``None`` + if the current request uses the ``none`` authentication. + + .. deprecated:: 8.0 + + use :attr:`.env` + """ + return openerp.modules.registry.RegistryManager.get(self.db) if self.db else None + + @property + def db(self): + """ + The database linked to this request. Can be ``None`` + if the current request uses the ``none`` authentication. + """ + return self.session.db if not self.disable_db else None + + @lazy_property + def httpsession(self): + """ HTTP session data + + .. deprecated:: 8.0 + + Use :attr:`.session` instead. + """ + return self.session + def route(route=None, **kw): """ Decorator marking the decorated method as being a handler for @@ -378,7 +387,15 @@ def route(route=None, **kw): return decorator class JsonRequest(WebRequest): - """ JSON-RPC2 over HTTP. + """ Request handler for `JSON-RPC 2 + `_ over HTTP + + * ``method`` is ignored + * ``params`` must be a JSON object (not an array) and is passed as keyword + arguments to the handler method + * the handler method's result is returned as JSON-RPC ``result`` and + wrapped in the `JSON-RPC Response + `_ Sucessful request:: @@ -490,8 +507,6 @@ class JsonRequest(WebRequest): return self._json_response(error=error) def dispatch(self): - """ Calls the method asked for by the JSON-RPC2 or JSONP request - """ if self.jsonp_handler: return self.jsonp_handler() try: @@ -541,7 +556,23 @@ def jsonrequest(f): return route([base, base + "/"], type="json", auth="user", combine=True)(f) class HttpRequest(WebRequest): - """ Regular GET/POST request + """ Handler for the ``http`` request type. + + matched routing parameters, query string parameters, form_ parameters + and files are passed to the handler method as keyword arguments. + + In case of name conflict, routing parameters have priority. + + The handler method's result can be: + + * a falsy value, in which case the HTTP response will be an + `HTTP 204`_ (No Content) + * a werkzeug Response object, which is returned as-is + * a ``str`` or ``unicode``, will be wrapped in a Response object and + interpreted as HTML + + .. _form: http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 + .. _HTTP 204: http://tools.ietf.org/html/rfc7231#section-6.3.5 """ _request_type = "http" @@ -596,7 +627,7 @@ class HttpRequest(WebRequest): return response def render(self, template, qcontext=None, lazy=True, **kw): - """ Lazy render of QWeb template. + """ Lazy render of a QWeb template. The actual rendering of the given template will occur at then end of the dispatching. Meanwhile, the template and/or qcontext can be @@ -604,7 +635,9 @@ class HttpRequest(WebRequest): :param basestring template: template to render :param dict qcontext: Rendering context to use - :param dict lazy: Lazy rendering is processed later in wsgi response layer (default True) + :param bool lazy: whether the template rendering should be deferred + until the last possible moment + :param kw: forwarded to werkzeug's Response object """ response = Response(template=template, qcontext=qcontext, **kw) if not lazy: @@ -612,7 +645,9 @@ class HttpRequest(WebRequest): return response def not_found(self, description=None): - """ Helper for 404 response, return its result from the method + """ Shortcut for a `HTTP 404 + `_ (Not Found) + response """ return werkzeug.exceptions.NotFound(description) @@ -1073,13 +1108,20 @@ class Retry(RuntimeError): class Response(werkzeug.wrappers.Response): """ Response object passed through controller route chain. - In addition to the werkzeug.wrappers.Response parameters, this - classe's constructor can take the following additional parameters + In addition to the :class:`werkzeug.wrappers.Response` parameters, this + class's constructor can take the following additional parameters for QWeb Lazy Rendering. :param basestring template: template to render :param dict qcontext: Rendering context to use - :param int uid: User id to use for the ir.ui.view render call + :param int uid: User id to use for the ir.ui.view render call, + ``None`` to use the request's user (the default) + + these attributes are available as parameters on the Response object and + can be altered at any time before rendering + + Also exposes all the attributes and methods of + :class:`werkzeug.wrappers.Response`. """ default_mimetype = 'text/html' def __init__(self, *args, **kw): @@ -1108,6 +1150,8 @@ class Response(werkzeug.wrappers.Response): return self.template is not None def render(self): + """ Renders the Response's template, returns the result + """ view_obj = request.registry["ir.ui.view"] uid = self.uid or request.uid or openerp.SUPERUSER_ID while True: @@ -1119,6 +1163,9 @@ class Response(werkzeug.wrappers.Response): self.qcontext.update(e.updates) def flatten(self): + """ Forces the rendering of the response's template, sets the result + as response body and unsets :attr:`.template` + """ self.response.append(self.render()) self.template = None diff --git a/openerp/tools/func.py b/openerp/tools/func.py index 7d04c62a1c9..baeae0b1465 100644 --- a/openerp/tools/func.py +++ b/openerp/tools/func.py @@ -42,6 +42,10 @@ class lazy_property(object): setattr(obj, self.fget.__name__, value) return value + @property + def __doc__(self): + return self.fget.__doc__ + @staticmethod def reset_all(obj): """ Reset all lazy properties on the instance `obj`. """