import common
import controllers
-import common.dispatch
import logging
import optparse
o.serve_static = True
o.backend = 'local'
- app = common.dispatch.Root(o)
+ app = common.http.Root(o)
openerp.wsgi.register_wsgi_handler(app)
{
"name" : "web",
+ "category" : "Hidden",
"depends" : [],
'active': True,
'post_load' : 'wsgi_postload',
#!/usr/bin/python
-from dispatch import *
+import http
+import nonliterals
+import release
+import session
+import xml2json
+++ /dev/null
-# -*- coding: utf-8 -*-
-""" Backport of Python 2.6's ast.py for Python 2.5
-"""
-__all__ = ['literal_eval']
-try:
- from ast import literal_eval
-except ImportError:
- from _ast import *
- from _ast import __version__
-
-
- def parse(expr, filename='<unknown>', mode='exec'):
- """
- Parse an expression into an AST node.
- Equivalent to compile(expr, filename, mode, PyCF_ONLY_AST).
- """
- return compile(expr, filename, mode, PyCF_ONLY_AST)
-
-
- def literal_eval(node_or_string):
- """
- Safely evaluate an expression node or a string containing a Python
- expression. The string or node provided may only consist of the
- following Python literal structures: strings, numbers, tuples, lists,
- dicts, booleans, and None.
- """
- _safe_names = {'None': None, 'True': True, 'False': False}
- if isinstance(node_or_string, basestring):
- node_or_string = parse(node_or_string, mode='eval')
- if isinstance(node_or_string, Expression):
- node_or_string = node_or_string.body
- def _convert(node):
- if isinstance(node, Str):
- return node.s
- elif isinstance(node, Num):
- return node.n
- elif isinstance(node, Tuple):
- return tuple(map(_convert, node.elts))
- elif isinstance(node, List):
- return list(map(_convert, node.elts))
- elif isinstance(node, Dict):
- return dict((_convert(k), _convert(v)) for k, v
- in zip(node.keys, node.values))
- elif isinstance(node, Name):
- if node.id in _safe_names:
- return _safe_names[node.id]
- raise ValueError('malformed string')
- return _convert(node_or_string)
+++ /dev/null
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
-# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-##############################################################################
-
-import datetime
-
-DEFAULT_SERVER_DATE_FORMAT = "%Y-%m-%d"
-DEFAULT_SERVER_TIME_FORMAT = "%H:%M:%S"
-DEFAULT_SERVER_DATETIME_FORMAT = "%s %s" % (
- DEFAULT_SERVER_DATE_FORMAT,
- DEFAULT_SERVER_TIME_FORMAT)
-
-def str_to_datetime(str):
- """
- Converts a string to a datetime object using OpenERP's
- datetime string format (exemple: '2011-12-01 15:12:35').
-
- No timezone information is added, the datetime is a naive instance, but
- according to OpenERP 6.1 specification the timezone is always UTC.
- """
- if not str:
- return str
- return datetime.datetime.strptime(str, DEFAULT_SERVER_DATETIME_FORMAT)
-
-def str_to_date(str):
- """
- Converts a string to a date object using OpenERP's
- date string format (exemple: '2011-12-01').
- """
- if not str:
- return str
- return datetime.datetime.strptime(str, DEFAULT_SERVER_DATE_FORMAT).date()
-
-def str_to_time(str):
- """
- Converts a string to a time object using OpenERP's
- time string format (exemple: '15:12:35').
- """
- if not str:
- return str
- return datetime.datetime.strptime(str, DEFAULT_SERVER_TIME_FORMAT).time()
-
-def datetime_to_str(obj):
- """
- Converts a datetime object to a string using OpenERP's
- datetime string format (exemple: '2011-12-01 15:12:35').
-
- The datetime instance should not have an attached timezone and be in UTC.
- """
- if not obj:
- return False
- return obj.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
-
-def date_to_str(obj):
- """
- Converts a date object to a string using OpenERP's
- date string format (exemple: '2011-12-01').
- """
- if not obj:
- return False
- return obj.strftime(DEFAULT_SERVER_DATE_FORMAT)
-
-def time_to_str(obj):
- """
- Converts a time object to a string using OpenERP's
- time string format (exemple: '15:12:35').
- """
- if not obj:
- return False
- return obj.strftime(DEFAULT_SERVER_TIME_FORMAT)
+++ /dev/null
-#!/usr/bin/python
-from __future__ import with_statement
-
-import functools
-import logging
-import os
-import pprint
-import sys
-import traceback
-import uuid
-import xmlrpclib
-
-import simplejson
-import werkzeug.datastructures
-import werkzeug.exceptions
-import werkzeug.utils
-import werkzeug.wrappers
-import werkzeug.wsgi
-
-import ast
-import nonliterals
-import http
-import session
-import openerplib
-
-__all__ = ['Root', 'jsonrequest', 'httprequest', 'Controller',
- 'WebRequest', 'JsonRequest', 'HttpRequest']
-
-_logger = logging.getLogger(__name__)
-
-#-----------------------------------------------------------
-# Globals (wont move into a pool)
-#-----------------------------------------------------------
-
-addons_module = {}
-addons_manifest = {}
-controllers_class = {}
-controllers_object = {}
-controllers_path = {}
-
-#----------------------------------------------------------
-# OpenERP Web 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`
- :param config: configuration object
-
- .. 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:: config
-
- config parameter provided to the request object
-
- .. 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, config):
- self.httprequest = request
- self.httpresponse = None
- self.httpsession = request.session
- self.config = config
-
- 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.setdefault(self.session_id, session.OpenERPSession())
- self.session.config = self.config
- 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, requestf=None, request=None):
- """ Calls the method asked for by the JSON-RPC2 request
-
- :param controller: the instance of the controller which received the request
- :param method: the method which received the request
- :param requestf: a file-like object containing an encoded JSON-RPC2 request
- :param request: a JSON-RPC2 request
-
- :returns: an utf8 encoded JSON-RPC2 reply
- """
- 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 openerplib.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))
- content = simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder)
- return werkzeug.wrappers.Response(
- content, headers=[('Content-Type', 'application/json'),
- ('Content-Length', len(content))])
-
-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, config):
- return JsonRequest(request, config).dispatch(
- controller, f, requestf=request.stream)
- 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)
- r = method(controller, self, **self.params)
- if self.debug or 1:
- if isinstance(r, werkzeug.wrappers.BaseResponse):
- _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, config):
- return HttpRequest(request, config).dispatch(controller, f)
- http_handler.exposed = True
- return http_handler
-
-class ControllerType(type):
- def __init__(cls, name, bases, attrs):
- super(ControllerType, cls).__init__(name, bases, attrs)
- controllers_class["%s.%s" % (cls.__module__, cls.__name__)] = cls
-
-class Controller(object):
- __metaclass__ = ControllerType
-
-class Root(object):
- """Root WSGI application for the OpenERP Web Client.
-
- :param options: mandatory initialization options object, must provide
- the following attributes:
-
- ``server_host`` (``str``)
- hostname of the OpenERP server to dispatch RPC to
- ``server_port`` (``int``)
- RPC port of the OpenERP server
- ``serve_static`` (``bool | None``)
- whether this application should serve the various
- addons's static files
- ``storage_path`` (``str``)
- filesystem path where HTTP session data will be stored
- ``dbfilter`` (``str``)
- only used in case the list of databases is requested
- by the server, will be filtered by this pattern
- """
- def __init__(self, options):
- self.root = '/web/webclient/home?debug=1'
- self.config = options
-
- if self.config.backend == 'local':
- conn = openerplib.get_connector(protocol='local')
- else:
- conn = openerplib.get_connector(hostname=self.config.server_host,
- port=self.config.server_port)
- self.config.connector = conn
-
- self.session_cookie = 'sessionid'
- self.addons = {}
-
- static_dirs = self._load_addons()
- if options.serve_static:
- self.dispatch = werkzeug.wsgi.SharedDataMiddleware(
- self.dispatch, static_dirs)
-
- if options.session_storage:
- if not os.path.exists(options.session_storage):
- os.mkdir(options.session_storage, 0700)
- self.session_storage = options.session_storage
-
- def __call__(self, environ, start_response):
- """ Handle a WSGI request
- """
- return self.dispatch(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
-
- if request.path == '/':
- return werkzeug.utils.redirect(self.root, 301)(environ, start_response)
- elif request.path == '/mobile':
- return werkzeug.utils.redirect(
- '/web_mobile/static/src/web_mobile.html', 301)(environ, start_response)
-
- handler = self.find_handler(*(request.path.split('/')[1:]))
-
- if not handler:
- response = werkzeug.exceptions.NotFound()
- else:
- with http.session(request, self.session_storage, self.session_cookie) as session:
- result = handler(
- request, self.config)
-
- if isinstance(result, basestring):
- response = werkzeug.wrappers.Response(
- result, headers=[('Content-Type', 'text/html; charset=utf-8'),
- ('Content-Length', len(result))])
- else:
- response = result
-
- response.set_cookie(self.session_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 self.config.addons_path:
- if addons_path not in sys.path:
- sys.path.insert(0, addons_path)
- 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.info("Loading %s", module)
- m = __import__(module)
- addons_module[module] = m
- addons_manifest[module] = manifest
- statics['/%s/static' % module] = path_static
- for k, v in controllers_class.items():
- 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
-
- 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 len(l) > 1:
- for i in range(len(l), 1, -1):
- ps = "/" + "/".join(l[0:i])
- if ps in controllers_path:
- c = controllers_path[ps]
- rest = l[i:] or ['index']
- meth = rest[0]
- m = getattr(c, meth)
- if getattr(m, 'exposed', False):
- _logger.debug("Dispatching to %s %s %s", ps, c, meth)
- return m
- return None
# -*- coding: utf-8 -*-
-
+#----------------------------------------------------------
+# OpenERP Web HTTP layer
+#----------------------------------------------------------
+import ast
import contextlib
+import functools
+import logging
+import urllib
+import os
+import pprint
+import sys
+import traceback
+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 nonliterals
+import session
+import openerplib
+
+__all__ = ['Root', 'jsonrequest', 'httprequest', 'Controller',
+ 'WebRequest', 'JsonRequest', 'HttpRequest']
+
+_logger = logging.getLogger(__name__)
+
+#----------------------------------------------------------
+# OpenERP Web 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`
+ :param config: configuration object
+
+ .. 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:: config
+
+ config parameter provided to the request object
+
+ .. 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, config):
+ self.httprequest = request
+ self.httpresponse = None
+ self.httpsession = request.session
+ self.config = config
+
+ 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.setdefault(self.session_id, session.OpenERPSession())
+ self.session.config = self.config
+ 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, requestf=None, request=None):
+ """ Calls the method asked for by the JSON-RPC2 request
+
+ :param controller: the instance of the controller which received the request
+ :param method: the method which received the request
+ :param requestf: a file-like object containing an encoded JSON-RPC2 request
+ :param request: a JSON-RPC2 request
+
+ :returns: an utf8 encoded JSON-RPC2 reply
+ """
+ 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 openerplib.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))
+ content = simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder)
+ return werkzeug.wrappers.Response(
+ content, headers=[('Content-Type', 'application/json'),
+ ('Content-Length', len(content))])
+
+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, config):
+ return JsonRequest(request, config).dispatch(
+ controller, f, requestf=request.stream)
+ 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)
+ r = method(controller, self, **self.params)
+ if self.debug or 1:
+ if isinstance(r, werkzeug.wrappers.BaseResponse):
+ _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, config):
+ return HttpRequest(request, config).dispatch(controller, f)
+ http_handler.exposed = True
+ return http_handler
+
+#----------------------------------------------------------
+# OpenERP Web werkzeug Session Managment wraped using with
+#----------------------------------------------------------
STORES = {}
@contextlib.contextmanager
-def session(request, storage_path, session_cookie='sessionid'):
+def session_context(request, storage_path, session_cookie='sessionid'):
session_store = STORES.get(storage_path)
if not session_store:
session_store = werkzeug.contrib.sessions.FilesystemSessionStore(
yield request.session
finally:
session_store.save(request.session)
+
+#----------------------------------------------------------
+# OpenERP Web Module/Controller Loading and URL Routing
+#----------------------------------------------------------
+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["%s.%s" % (cls.__module__, cls.__name__)] = cls
+
+class Controller(object):
+ __metaclass__ = ControllerType
+
+class Root(object):
+ """Root WSGI application for the OpenERP Web Client.
+
+ :param options: mandatory initialization options object, must provide
+ the following attributes:
+
+ ``server_host`` (``str``)
+ hostname of the OpenERP server to dispatch RPC to
+ ``server_port`` (``int``)
+ RPC port of the OpenERP server
+ ``serve_static`` (``bool | None``)
+ whether this application should serve the various
+ addons's static files
+ ``storage_path`` (``str``)
+ filesystem path where HTTP session data will be stored
+ ``dbfilter`` (``str``)
+ only used in case the list of databases is requested
+ by the server, will be filtered by this pattern
+ """
+ def __init__(self, options):
+ self.root = '/web/webclient/home'
+ self.config = options
+
+ if self.config.backend == 'local':
+ conn = openerplib.get_connector(protocol='local')
+ else:
+ conn = openerplib.get_connector(hostname=self.config.server_host,
+ port=self.config.server_port)
+ self.config.connector = conn
+
+ self.session_cookie = 'sessionid'
+ self.addons = {}
+
+ static_dirs = self._load_addons()
+ if options.serve_static:
+ self.dispatch = werkzeug.wsgi.SharedDataMiddleware(
+ self.dispatch, static_dirs)
+
+ if options.session_storage:
+ if not os.path.exists(options.session_storage):
+ os.mkdir(options.session_storage, 0700)
+ self.session_storage = options.session_storage
+
+ def __call__(self, environ, start_response):
+ """ Handle a WSGI request
+ """
+ return self.dispatch(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
+
+ if request.path == '/':
+ params = urllib.urlencode(dict(request.args, debug=''))
+ return werkzeug.utils.redirect(self.root + '?' + params, 301)(
+ environ, start_response)
+ elif request.path == '/mobile':
+ return werkzeug.utils.redirect(
+ '/web_mobile/static/src/web_mobile.html', 301)(environ, start_response)
+
+ handler = self.find_handler(*(request.path.split('/')[1:]))
+
+ if not handler:
+ response = werkzeug.exceptions.NotFound()
+ else:
+ with session_context(request, self.session_storage, self.session_cookie) as session:
+ result = handler( request, self.config)
+
+ 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
+
+ response.set_cookie(self.session_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 self.config.addons_path:
+ if addons_path not in sys.path:
+ sys.path.insert(0, addons_path)
+ 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.info("Loading %s", module)
+ m = __import__(module)
+ addons_module[module] = m
+ addons_manifest[module] = manifest
+ statics['/%s/static' % module] = path_static
+ for k, v in controllers_class.items():
+ 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
+
+ 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 len(l) > 1:
+ for i in range(len(l), 1, -1):
+ ps = "/" + "/".join(l[0:i])
+ if ps in controllers_path:
+ c = controllers_path[ps]
+ rest = l[i:] or ['index']
+ meth = rest[0]
+ m = getattr(c, meth)
+ if getattr(m, 'exposed', False):
+ _logger.debug("Dispatching to %s %s %s", ps, c, meth)
+ return m
+ return None
+
+#
#!/usr/bin/python
import datetime
import dateutil.relativedelta
+import logging
import time
import openerplib
import nonliterals
-import logging
_logger = logging.getLogger(__name__)
#----------------------------------------------------------
# OpenERPSession RPC openerp backend access
#----------------------------------------------------------
-
class OpenERPSession(object):
"""
An OpenERP RPC session, a given user can own multiple such sessions
--- /dev/null
+# xml2json-direct
+# Simple and straightforward XML-to-JSON converter in Python
+# New BSD Licensed
+#
+# URL: http://code.google.com/p/xml2json-direct/
+
+class Xml2Json(object):
+ @staticmethod
+ def convert_to_json(s):
+ return simplejson.dumps(
+ Xml2Json.convert_to_structure(s), sort_keys=True, indent=4)
+
+ @staticmethod
+ def convert_to_structure(s):
+ root = ElementTree.fromstring(s)
+ return Xml2Json.convert_element(root)
+
+ @staticmethod
+ def convert_element(el, skip_whitespaces=True):
+ res = {}
+ if el.tag[0] == "{":
+ ns, name = el.tag.rsplit("}", 1)
+ res["tag"] = name
+ res["namespace"] = ns[1:]
+ else:
+ res["tag"] = el.tag
+ res["attrs"] = {}
+ for k, v in el.items():
+ res["attrs"][k] = v
+ kids = []
+ if el.text and (not skip_whitespaces or el.text.strip() != ''):
+ kids.append(el.text)
+ for kid in el:
+ kids.append(Xml2Json.convert_element(kid))
+ if kid.tail and (not skip_whitespaces or kid.tail.strip() != ''):
+ kids.append(kid.tail)
+ res["children"] = kids
+ return res
# -*- coding: utf-8 -*-
+import ast
import base64
import csv
import glob
import re
import simplejson
import textwrap
-import xmlrpclib
import time
+import xmlrpclib
import zlib
from xml.etree import ElementTree
from cStringIO import StringIO
-from babel.messages.pofile import read_po
-
-import web.common.dispatch as openerpweb
-import web.common.ast
-import web.common.nonliterals
-import web.common.release
-openerpweb.ast = web.common.ast
-openerpweb.nonliterals = web.common.nonliterals
-
-
-# Should move to openerpweb.Xml2Json
-class Xml2Json:
- # xml2json-direct
- # Simple and straightforward XML-to-JSON converter in Python
- # New BSD Licensed
- #
- # URL: http://code.google.com/p/xml2json-direct/
- @staticmethod
- def convert_to_json(s):
- return simplejson.dumps(
- Xml2Json.convert_to_structure(s), sort_keys=True, indent=4)
-
- @staticmethod
- def convert_to_structure(s):
- root = ElementTree.fromstring(s)
- return Xml2Json.convert_element(root)
-
- @staticmethod
- def convert_element(el, skip_whitespaces=True):
- res = {}
- if el.tag[0] == "{":
- ns, name = el.tag.rsplit("}", 1)
- res["tag"] = name
- res["namespace"] = ns[1:]
- else:
- res["tag"] = el.tag
- res["attrs"] = {}
- for k, v in el.items():
- res["attrs"][k] = v
- kids = []
- if el.text and (not skip_whitespaces or el.text.strip() != ''):
- kids.append(el.text)
- for kid in el:
- kids.append(Xml2Json.convert_element(kid))
- if kid.tail and (not skip_whitespaces or kid.tail.strip() != ''):
- kids.append(kid.tail)
- res["children"] = kids
- return res
+import babel.messages.pofile
+
+import web.common
+openerpweb = web.common.http
#----------------------------------------------------------
# OpenERP Web web Controllers
continue
try:
with open(f_name) as t_file:
- po = read_po(t_file)
+ po = babel.messages.pofile.read_po(t_file)
except:
continue
for x in po:
@openerpweb.jsonrequest
def modules(self, req):
- # TODO query server for installed web modules
- mods = []
- for name, manifest in openerpweb.addons_manifest.items():
- # TODO replace by ir.module.module installed web
- if name not in req.config.server_wide_modules and manifest.get('active', True):
- mods.append(name)
- return mods
+ # Compute available candidates module
+ loadable = openerpweb.addons_manifest.iterkeys()
+ loaded = req.config.server_wide_modules
+ candidates = [mod for mod in loadable if mod not in loaded]
+
+ # Compute active true modules that might be on the web side only
+ active = set(name for name in candidates
+ if openerpweb.addons_manifest[name].get('active'))
+
+ # Retrieve database installed modules
+ Modules = req.session.model('ir.module.module')
+ installed = set(module['name'] for module in Modules.search_read(
+ [('state','=','installed'), ('name','in', candidates)], ['name']))
+
+ # Merge both
+ return list(active | installed)
@openerpweb.jsonrequest
def eval_domain_and_context(self, req, contexts, domains,
no group by should be performed)
"""
context, domain = eval_context_and_domain(req.session,
- openerpweb.nonliterals.CompoundContext(*(contexts or [])),
- openerpweb.nonliterals.CompoundDomain(*(domains or [])))
+ web.common.nonliterals.CompoundContext(*(contexts or [])),
+ web.common.nonliterals.CompoundDomain(*(domains or [])))
group_by_sequence = []
for candidate in (group_by_seq or []):
xml = self.transform_view(arch, session, evaluation_context)
else:
xml = ElementTree.fromstring(arch)
- fvg['arch'] = Xml2Json.convert_element(xml)
+ fvg['arch'] = web.common.xml2json.Xml2Json.convert_element(xml)
for field in fvg['fields'].itervalues():
if field.get('views'):
def parse_domain(self, domain, session):
""" Parses an arbitrary string containing a domain, transforms it
- to either a literal domain or a :class:`openerpweb.nonliterals.Domain`
+ to either a literal domain or a :class:`web.common.nonliterals.Domain`
:param domain: the domain to parse, if the domain is not a string it
is assumed to be a literal domain and is returned as-is
if not isinstance(domain, (str, unicode)):
return domain
try:
- return openerpweb.ast.literal_eval(domain)
+ return ast.literal_eval(domain)
except ValueError:
# not a literal
- return openerpweb.nonliterals.Domain(session, domain)
+ return web.common.nonliterals.Domain(session, domain)
def parse_context(self, context, session):
""" Parses an arbitrary string containing a context, transforms it
- to either a literal context or a :class:`openerpweb.nonliterals.Context`
+ to either a literal context or a :class:`web.common.nonliterals.Context`
:param context: the context to parse, if the context is not a string it
is assumed to be a literal domain and is returned as-is
if not isinstance(context, (str, unicode)):
return context
try:
- return openerpweb.ast.literal_eval(context)
+ return ast.literal_eval(context)
except ValueError:
- return openerpweb.nonliterals.Context(session, context)
+ return web.common.nonliterals.Context(session, context)
def parse_domains_and_contexts(self, elem, session):
""" Converts domains and contexts from the view into Python objects,
@openerpweb.jsonrequest
def save_filter(self, req, model, name, context_to_save, domain):
Model = req.session.model("ir.filters")
- ctx = openerpweb.nonliterals.CompoundContext(context_to_save)
+ ctx = web.common.nonliterals.CompoundContext(context_to_save)
ctx.session = req.session
ctx = ctx.evaluate()
- domain = openerpweb.nonliterals.CompoundDomain(domain)
+ domain = web.common.nonliterals.CompoundDomain(domain)
domain.session = req.session
domain = domain.evaluate()
uid = req.session._uid
try:
if not id:
- res = Model.default_get([field], context).get(field, '')
+ res = Model.default_get([field], context).get(field)
else:
- res = Model.read([int(id)], [field], context)[0].get(field, '')
+ res = Model.read([int(id)], [field], context)[0].get(field)
image_data = base64.b64decode(res)
except (TypeError, xmlrpclib.Fault):
image_data = self.placeholder(req)
return req.make_response(image_data, [
('Content-Type', 'image/png'), ('Content-Length', len(image_data))])
def placeholder(self, req):
- return open(os.path.join(req.addons_path, 'web', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
+ addons_path = openerpweb.addons_manifest['web']['addons_path']
+ return open(os.path.join(addons_path, 'web', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
@openerpweb.httprequest
def saveas(self, req, model, id, field, fieldname, **kw):
report_srv = req.session.proxy("report")
context = req.session.eval_context(
- openerpweb.nonliterals.CompoundContext(
+ web.common.nonliterals.CompoundContext(
req.context or {}, action[ "context"]))
report_data = {}
('Content-Length', len(report))],
cookies={'fileToken': int(token)})
-
class Import(View):
_cp_path = "/web/import"
cursor: help;
}
-.openerp .oe_form_field label.oe_label, .openerp .oe_form_field label.oe_label_help {
+.openerp .oe_forms label.oe_label, .openerp .oe_forms label.oe_label_help {
text-align: right;
margin: 3px 0 0 10px;
}
position: relative;
}
.openerp input.oe-binary-file {
- z-index: 2;
+ z-index: 0;
line-height: 0;
font-size: 50px;
position: absolute;
session_login: function(db, login, password, success_callback) {
var self = this;
var params = { db: db, login: login, password: password };
- this.rpc("/web/session/login", params, function(result) {
+ return this.rpc("/web/session/login", params, function(result) {
self.session_id = result.session_id;
self.uid = result.uid;
self.user_context = result.context;
self.db = result.db;
self.session_save();
- if (self.uid)
- self.on_session_valid();
return true;
}).then(success_callback);
},
* @param {String} [column.string] button label
* @param {String} [column.icon] button icon
* @param {String} [value_if_empty=''] what to display if the field's value is ``false``
+ * @param {Boolean} [process_modifiers=true] should the modifiers be computed ?
*/
-openerp.web.format_cell = function (row_data, column, value_if_empty) {
- var attrs = column.modifiers_for(row_data);
+openerp.web.format_cell = function (row_data, column, value_if_empty, process_modifiers) {
+ var attrs = {};
+ if (process_modifiers !== false) {
+ attrs = column.modifiers_for(row_data);
+ }
if (attrs.invisible) { return ''; }
if (column.tag === 'button') {
return [
* @param view_id
* @param defaults
*/
- init: function(parent, dataset, view_id, defaults) {
+ init: function(parent, dataset, view_id, defaults, hidden) {
this._super(parent);
this.dataset = dataset;
this.model = dataset.model;
this.view_id = view_id;
this.defaults = defaults || {};
+ this.has_defaults = !_.isEmpty(this.defaults);
this.inputs = [];
this.enabled_filters = [];
this.has_focus = false;
+ this.hidden = !!hidden;
+ this.headless = this.hidden && !this.has_defaults;
+
this.ready = $.Deferred();
},
start: function() {
this._super();
- this.rpc("/web/searchview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded);
+ if (this.hidden) {
+ this.$element.hide();
+ }
+ if (this.headless) {
+ this.ready.resolve();
+ } else {
+ this.rpc("/web/searchview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded);
+ }
return this.ready.promise();
},
show: function () {
*/
make_field: function (item, field) {
try {
- return new (openerp.web.search.fields.get_object(field.type))
- (item, field, this);
+ return new (openerp.web.search.fields.get_any(
+ [item.attrs.widget, field.type]))
+ (item, field, this);
} catch (e) {
if (! e instanceof openerp.web.KeyNotFound) {
throw e;
'defaults': this.defaults
});
- // We don't understand why the following commented line does not work in Chrome but
- // the non-commented line does. As far as we investigated, only God knows.
- //this.$element.html(render);
- jQuery(render).appendTo(this.$element);
+ this.$element.html(render);
this.$element.find(".oe_search-view-custom-filter-btn").click(ext.on_activate);
var f = this.$element.find('form');
* @param e jQuery event object coming from the "Search" button
*/
do_search: function (e) {
+ if (this.headless && !this.has_defaults) {
+ return this.on_search([], [], []);
+ }
// reset filters management
var select = this.$element.find(".oe_search-view-filters-management");
select.val("_filters");
-
+
if (e && e.preventDefault) { e.preventDefault(); }
var data = this.build_search_data();
this.notification.notify("Invalid Search", "triggered from search view");
},
do_clear: function () {
- this.$element.find('.filter_label').removeClass('enabled');
+ this.$element.find('.filter_label, .filter_icon').removeClass('enabled');
this.enabled_filters.splice(0);
var string = $('a.searchview_group_string');
_.each(string, function(str){
* @extends openerp.web.search.Field
*/
openerp.web.search.SelectionField = openerp.web.search.Field.extend(/** @lends openerp.web.search.SelectionField# */{
+ // This implementation is a basic <select> field, but it may have to be
+ // altered to be more in line with the GTK client, which uses a combo box
+ // (~ jquery.autocomplete):
+ // * If an option was selected in the list, behave as currently
+ // * If something which is not in the list was entered (via the text input),
+ // the default domain should become (`ilike` string_value) but **any
+ // ``context`` or ``filter_domain`` becomes falsy, idem if ``@operator``
+ // is specified. So at least get_domain needs to be quite a bit
+ // overridden (if there's no @value and there is no filter_domain and
+ // there is no @operator, return [[name, 'ilike', str_val]]
template: 'SearchView.field.selection',
+ init: function () {
+ this._super.apply(this, arguments);
+ // prepend empty option if there is no empty option in the selection list
+ this.prepend_empty = !_(this.attrs.selection).detect(function (item) {
+ return !item[1];
+ });
+ },
get_value: function () {
- return this.$element.val();
+ var index = parseInt(this.$element.val(), 10);
+ if (isNaN(index)) { return null; }
+ var value = this.attrs.selection[index][0];
+ if (value === false) { return null; }
+ return value;
}
});
openerp.web.search.BooleanField = openerp.web.search.SelectionField.extend(/** @lends openerp.web.search.BooleanField# */{
return $.Deferred().then(success).resolve(_.extend(r, {created: true}));
}
},
- do_search: function (domains, contexts, groupbys) {
- console.debug("Searching form");
- },
on_action: function (action) {
console.debug('Executing action', action);
},
this.width = this.node.attrs.width;
},
start: function() {
- this.$element = this.view.$element.find('.' + this.element_class);
+ this.$element = this.view.$element.find(
+ '.' + this.element_class.replace(/[^\r\n\f0-9A-Za-z_-]/g, "\\$&"));
},
process_modifiers: function() {
var compute_domain = openerp.web.form.compute_domain;
for (var i = 0; i < node.children.length; i++) {
var n = node.children[i];
if (n.tag == "page") {
- var page = new openerp.web.form.WidgetNotebookPage(
+ var page = new (this.view.registry.get_object('notebookpage'))(
this.view, n, this, this.pages.length);
this.pages.push(page);
}
self.on_confirmed().then(function() {
def.resolve();
});
- $(self).dialog("close");
+ $(this).dialog("close");
},
Cancel: function() {
def.resolve();
- $(self).dialog("close");
+ $(this).dialog("close");
}
}
});
return self.on_confirmed();
}
};
- if ((!this.node.attrs.special && this.view.dirty_for_user) || !this.view.datarecord.id) {
+ if (!this.node.attrs.special && (this.view.dirty_for_user || !this.view.datarecord.id)) {
return this.view.recursive_save().pipe(exec_action);
} else {
return exec_action();
result.result.context = _.extend(result.result.context || {}, additional_context);
self.do_action(result.result);
});
+ },
+ focus: function () {
+ this.$input.focus();
}
});
});
this.searchview.on_search.add(function(domains, contexts, groupbys) {
if (self.initial_ids) {
- self.view_list.do_search.call(self, domains.concat([[["id", "in", self.initial_ids]], self.domain]),
+ self.do_search(domains.concat([[["id", "in", self.initial_ids]], self.domain]),
contexts, groupbys);
self.initial_ids = undefined;
} else {
- self.view_list.do_search.call(self, domains.concat([self.domain]), contexts, groupbys);
+ self.do_search(domains.concat([self.domain]), contexts, groupbys);
}
});
this.searchview.on_loaded.add_last(function () {
}).pipe(function() {
self.searchview.do_search();
});
-
});
this.searchview.appendTo($("#" + this.element_id + "_search"));
},
+ do_search: function(domains, contexts, groupbys) {
+ var self = this;
+ this.rpc('/web/session/eval_domain_and_context', {
+ domains: domains || [],
+ contexts: contexts || [],
+ group_by_seq: groupbys || []
+ }, function (results) {
+ self.view_list.do_search(results.domain, results.context, results.group_by);
+ });
+ },
create_row: function(data) {
var self = this;
var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain);
'frame' : 'openerp.web.form.WidgetFrame',
'group' : 'openerp.web.form.WidgetFrame',
'notebook' : 'openerp.web.form.WidgetNotebook',
+ 'notebookpage' : 'openerp.web.form.WidgetNotebookPage',
'separator' : 'openerp.web.form.WidgetSeparator',
'label' : 'openerp.web.form.WidgetLabel',
'button' : 'openerp.web.form.WidgetButton',
if (this.sidebar) {
this.sidebar.$element.show();
}
- if (!_(this.dataset.ids).isEmpty()) {
- this.reload_content();
- }
},
do_hide: function () {
this.$element.hide();
}));
},
/**
- * Event handler for a search, asks for the computation/folding of domains
- * and contexts (and group-by), then reloads the view's content.
- *
- * @param {Array} domains a sequence of literal and non-literal domains
- * @param {Array} contexts a sequence of literal and non-literal contexts
- * @param {Array} groupbys a sequence of literal and non-literal group-by contexts
- * @returns {$.Deferred} fold request evaluation promise
- */
- do_search: function (domains, contexts, groupbys) {
- return this.rpc('/web/session/eval_domain_and_context', {
- domains: _([this.dataset.get_domain()].concat(domains)).compact(),
- contexts: _([this.dataset.get_context()].concat(contexts)).compact(),
- group_by_seq: groupbys
- }, $.proxy(this, 'do_actual_search'));
- },
- /**
* Handler for the result of eval_domain_and_context, actually perform the
* searching
*
* @param {Object} results results of evaluating domain and process for a search
*/
- do_actual_search: function (results) {
+ do_search: function (domain, context, group_by) {
this.groups.datagroup = new openerp.web.DataGroup(
- this, this.model,
- results.domain,
- results.context,
- results.group_by);
+ this, this.model, domain, context, group_by);
this.groups.datagroup.sort = this.dataset._sort;
- if (_.isEmpty(results.group_by) && !results.context['group_by_no_leaf']) {
- results.group_by = null;
+ if (_.isEmpty(group_by) && !context['group_by_no_leaf']) {
+ group_by = null;
}
- this.reload_view(!!results.group_by, results.context).then(
+ this.reload_view(!!group_by, context).then(
$.proxy(this, 'reload_content'));
},
/**
do_select: function (ids, records) {
this.$element.find('.oe-list-delete')
.attr('disabled', !ids.length);
-
+ if (this.sidebar) {
+ if (ids.length) {
+ this.sidebar.do_unfold();
+ } else {
+ this.sidebar.do_fold();
+ }
+ }
if (!records.length) {
this.compute_aggregates();
return;
* @param {openerp.web.DataSet} dataset dataset in which the record is available (may not be the listview's dataset in case of nested groups)
*/
do_activate_record: function (index, id, dataset) {
- var self = this;
- // TODO is it needed ?
- this.dataset.read_slice([],{
- context: dataset.get_context(),
- domain: dataset.get_domain()
- }, function () {
- self.select_record(index);
- });
+ this.dataset.ids = dataset.ids;
+ this.select_record(index);
},
/**
* Handles signal for the addition of a new record (can be a creation,
}
$footer_cells.filter(_.sprintf('[data-field=%s]', column.id))
- .html(openerp.web.format_cell(aggregation, column));
+ .html(openerp.web.format_cell(aggregation, column, undefined, false));
});
},
get_selected_ids: function() {
cells.push('</tr>');
var row = cells.join('');
- this.$current.append(new Array(count - this.records.length + 1).join(row));
+ this.$current
+ .children('tr:not([data-id])').remove().end()
+ .append(new Array(count - this.records.length + 1).join(row));
this.refresh_zebra(this.records.length);
},
/**
options: this.options,
record: record,
row_parity: (index % 2 === 0) ? 'even' : 'odd',
+ view: this.view,
render_cell: openerp.web.format_cell
});
},
|| this.defaults.editable);
},
/**
- * Replace do_actual_search to handle editability process
+ * Replace do_search to handle editability process
*/
- do_actual_search: function (results) {
- this.set_editable(results.context['set_editable']);
- this._super(results);
+ do_search: function(domain, context, group_by) {
+ this.set_editable(context['set_editable']);
+ this._super.apply(this, arguments);
},
/**
* Replace do_add_record to handle editability (and adding new record
delete self.edition_id;
delete self.edition;
});
+ this.pad_table_to(5);
return cancelled.promise();
},
/**
var $new_row = $('<tr>', {
id: _.uniqueId('oe-editable-row-'),
'data-id': record_id,
- 'class': $(row).attr('class') + ' oe_forms',
+ 'class': row ? $(row).attr('class') : '' + ' oe_forms',
click: function (e) {e.stopPropagation();}
})
.delegate('button.oe-edit-row-save', 'click', function () {
});
if (row) {
$new_row.replaceAll(row);
- } else if (self.options.editable === 'top') {
- self.$current.prepend($new_row);
} else if (self.options.editable) {
- self.$current.append($new_row);
+ var $last_child = self.$current.children('tr:last');
+ if (self.records.length) {
+ if (self.options.editable === 'top') {
+ $new_row.insertBefore(
+ self.$current.children('[data-id]:first'));
+ } else {
+ $new_row.insertAfter(
+ self.$current.children('[data-id]:last'));
+ }
+ } else {
+ $new_row.prependTo(self.$current);
+ }
+ if ($last_child.is(':not([data-id])')) {
+ $last_child.remove();
+ }
}
self.edition = true;
self.edition_id = record_id;
this.$element.children().css('visibility', '');
if (this.modifiers.tree_invisible) {
var old_invisible = this.invisible;
- this.invisible = !!this.modifiers.tree_invisible;
+ this.invisible = true;
this._super();
this.invisible = old_invisible;
} else if (this.invisible) {
this.$element.children().css('visibility', 'hidden');
+ } else {
+ this._super();
}
}
});
*/
},
ir_actions_act_window_close: function (action, on_closed) {
+ if (!this.dialog && on_closed) {
+ on_closed();
+ }
this.dialog_stop();
},
ir_actions_server: function (action, on_closed) {
this.model = dataset ? dataset.model : undefined;
this.dataset = dataset;
this.searchview = null;
+ this.last_search = false;
this.active_view = null;
this.views_src = _.map(views, function(x) {return x instanceof Array? {view_id: x[0], view_type: x[1]} : x;});
this.views = {};
controller.set_embedded_view(view.embedded_view);
}
controller.do_switch_view.add_last(this.on_mode_switch);
- if (view_type === 'list' && this.flags.search_view === false && this.action && this.action['auto_search']) {
- // In case the search view is not instantiated: manually call ListView#search
- var domains = !_(self.action.domain).isEmpty()
- ? [self.action.domain] : [],
- contexts = !_(self.action.context).isEmpty()
- ? [self.action.context] : [];
- controller.on_loaded.add({
- callback: function () {
- controller.do_search(domains, contexts, []);
- },
- position: 'last',
- unique: true
- });
- }
var container = $("#" + this.element_id + '_view_' + view_type);
view_promise = controller.appendTo(container);
+ this.views[view_type].controller = controller;
$.when(view_promise).then(function() {
self.on_controller_inited(view_type, controller);
+ if (self.searchview && view.controller.searchable !== false) {
+ self.do_searchview_search();
+ }
});
- this.views[view_type].controller = controller;
+ } else if (this.searchview && view.controller.searchable !== false) {
+ self.do_searchview_search();
}
-
if (this.searchview) {
- if (view.controller.searchable === false) {
- this.searchview.hide();
- } else {
- this.searchview.show();
- }
+ this.searchview[(view.controller.searchable === false || this.searchview.hidden) ? 'hide' : 'show']();
}
this.$element
return view_promise;
},
/**
- * Event launched when a controller has been inited.
- *
- * @param {String} view_type type of view
- * @param {String} view the inited controller
- */
- on_controller_inited: function(view_type, view) {
- },
- /**
* Sets up the current viewmanager's search view.
*
* @param {Number|false} view_id the view to use or false for a default one
}
this.searchview = new db.web.SearchView(
this, this.dataset,
- view_id, search_defaults);
+ view_id, search_defaults, this.flags.search_view === false);
- this.searchview.on_search.add(function(domains, contexts, groupbys) {
- var controller = self.views[self.active_view].controller;
- controller.do_search.call(controller, domains, contexts, groupbys);
- });
+ this.searchview.on_search.add(this.do_searchview_search);
return this.searchview.appendTo($("#" + this.element_id + "_search"));
},
+ do_searchview_search: function(domains, contexts, groupbys) {
+ var self = this,
+ controller = this.views[this.active_view].controller;
+ if (domains || contexts) {
+ this.rpc('/web/session/eval_domain_and_context', {
+ domains: [this.action.domain || []].concat(domains || []),
+ contexts: [this.action.context || {}].concat(contexts || []),
+ group_by_seq: groupbys || []
+ }, function (results) {
+ self.dataset.context = results.context;
+ self.dataset.domain = results.domain;
+ self.last_search = [results.domain, results.context, results.group_by];
+ controller.do_search(results.domain, results.context, results.group_by);
+ });
+ } else if (this.last_search) {
+ controller.do_search.apply(controller, this.last_search);
+ }
+ },
+ /**
+ * Event launched when a controller has been inited.
+ *
+ * @param {String} view_type type of view
+ * @param {String} view the inited controller
+ */
+ on_controller_inited: function(view_type, view) {
+ },
/**
* Called when one of the view want to execute an action
*/
* launches an initial search after both views are done rendering.
*/
start: function() {
- var self = this;
-
- var searchview_loaded;
- if (this.flags.search_view !== false) {
- var search_defaults = {};
- _.each(this.action.context, function (value, key) {
- var match = /^search_default_(.*)$/.exec(key);
- if (match) {
- search_defaults[match[1]] = value;
- }
- });
- // init search view
- var searchview_id = this.action['search_view_id'] && this.action['search_view_id'][0];
+ var self = this,
+ searchview_loaded,
+ search_defaults = {};
+ _.each(this.action.context, function (value, key) {
+ var match = /^search_default_(.*)$/.exec(key);
+ if (match) {
+ search_defaults[match[1]] = value;
+ }
+ });
+ // init search view
+ var searchview_id = this.action['search_view_id'] && this.action['search_view_id'][0];
- searchview_loaded = this.setup_search_view(
- searchview_id || false, search_defaults);
- }
+ searchview_loaded = this.setup_search_view(searchview_id || false, search_defaults);
var main_view_loaded = this._super();
var manager_ready = $.when(searchview_loaded, main_view_loaded);
- if (searchview_loaded && this.action['auto_search']) {
+ if (searchview_loaded && this.action['auto_search'] !== false) {
// schedule auto_search
manager_ready.then(this.searchview.do_search);
}
},
do_switch_view: function(view) {
},
+ do_search: function(view) {
+ },
set_common_sidebar_sections: function(sidebar) {
sidebar.add_section('customize', "Customize", [
{
<input type="text" size="1"
t-att-name="widget.name"
t-att-id="widget.element_id"
- t-attf-class="field_#{widget.type} #{widget.element_class}"
+ t-attf-class="field_#{widget.type}"
t-attf-style="width: #{widget.field.translate ? '99' : '100'}%"
/>
<img class="oe_field_translate" t-if="widget.field.translate" src="/web/static/src/img/icons/terp-translate.png" width="16" height="16" border="0"/>
<t t-name="FieldChar.readonly">
<div
t-att-id="widget.element_id"
- t-attf-class="field_#{widget.type} #{widget.element_class}"
+ t-attf-class="field_#{widget.type}"
t-attf-style="width: #{widget.field.translate ? '99' : '100'}%">
</div>
</t>
<textarea rows="6"
t-att-name="widget.name"
t-att-id="widget.element_id"
- t-attf-class="field_#{widget.type} #{widget.element_class}"
+ t-attf-class="field_#{widget.type}"
t-attf-style="width: #{widget.field.translate ? '99' : '100'}%"
></textarea>
<img class="oe_field_translate" t-if="widget.field.translate" src="/web/static/src/img/icons/terp-translate.png" width="16" height="16" border="0"/>
<t t-name="FieldSelection">
<select
t-att-name="widget.name"
- t-att-id="widget.element_id + '_field'"
- t-attf-class="field_#{widget.type} #{widget.element_class}"
+ t-att-id="widget.element_id"
+ t-attf-class="field_#{widget.type}"
style="width: 100%">
<t t-foreach="widget.values" t-as="option">
<option><t t-esc="option[1]"/></option>
</select>
</t>
<t t-name="FieldMany2One">
- <div t-att-class="widget.element_class" class="oe-m2o">
- <input type="text" size="1" style="width: 100%;"/>
+ <div class="oe-m2o">
+ <input type="text" size="1" style="width: 100%;"
+ t-att-id="widget.element_id"/>
<span class="oe-m2o-drop-down-button">
<img src="/web/static/src/img/down-arrow.png" /></span>
<span class="oe-m2o-cm-button" t-att-id="widget.name + '_open'">
<t t-name="FieldBoolean">
<input type="checkbox"
t-att-name="widget.name"
- t-att-id="widget.element_id + '_field'"
- t-attf-class="field_#{widget.type} #{widget.element_class}"/>
+ t-att-id="widget.element_id"
+ t-attf-class="field_#{widget.type}"/>
</t>
<t t-name="FieldProgressBar">
<div t-opentag="true" class="oe-progressbar">
t-att-border="widget.readonly ? 0 : 1"
t-att-id="widget.element_id + '_field'"
t-att-name="widget.name"
- t-attf-class="field_#{widget.type} #{widget.element_class}"
+ t-attf-class="field_#{widget.type}"
t-att-width="widget.node.attrs.img_width || widget.node.attrs.width"
t-att-height="widget.node.attrs.img_height || widget.node.attrs.height"
/>
<input type="text" size="1"
t-att-name="widget.name"
t-att-id="widget.element_id + '_field'"
- t-attf-class="field_#{widget.type} #{widget.element_class}" style="width: 100%"
+ t-attf-class="field_#{widget.type}" style="width: 100%"
/>
</td>
<td class="oe-binary" nowrap="true">
</t>
<t t-name="WidgetButton">
<button type="button"
- t-attf-class="#{widget.element_class}"
t-att-title="widget.help"
style="width: 100%" class="button">
<img t-if="widget.node.attrs.icon" t-att-src="'/web/static/src/img/icons/' + widget.node.attrs.icon + '.png'" width="16" height="16"/>
<div style="white-space: nowrap;">
<select t-att-name="attrs.name" t-att-id="element_id"
t-att-autofocus="attrs.default_focus === '1' || undefined">
- <option/>
+ <option t-if="prepend_empty"/>
<t t-foreach="attrs.selection" t-as="option">
<t t-set="selected" t-value="defaults[attrs.name] === option[0]"/>
<option t-if="selected"
- t-att-value="option[0]" selected="selected">
+ t-att-value="option_index" selected="selected">
<t t-esc="option[1]"/>
</option>
- <option t-if="!selected" t-att-value="option[0]">
+ <option t-if="!selected" t-att-value="option_index">
<t t-esc="option[1]"/>
</option>
</t>
{
"name": "web calendar",
+ "category" : "Hidden",
"version": "2.0",
"depends": ['web'],
"js": [
this.set_default_options(options);
this.dataset = dataset;
this.model = dataset.model;
+ this.fields_view = {};
this.view_id = view_id;
- this.domain = this.dataset.domain || [];
- this.context = this.dataset.context || {};
this.has_been_loaded = $.Deferred();
this.creating_event_id = null;
this.dataset_events = [];
},
start: function() {
this._super();
- this.rpc("/web/view/load", {"model": this.model, "view_id": this.view_id, "view_type":"calendar", 'toolbar': true}, this.on_loaded);
+ return this.rpc("/web/view/load", {"model": this.model, "view_id": this.view_id, "view_type":"calendar", 'toolbar': true}, this.on_loaded);
},
stop: function() {
scheduler.clearAll();
this.name = this.fields_view.name || this.fields_view.arch.attrs.string;
this.view_id = this.fields_view.view_id;
+ // mode, one of month, week or day
+ this.mode = this.fields_view.arch.attrs.mode;
+
+ // date_start is mandatory, date_delay and date_stop are optional
this.date_start = this.fields_view.arch.attrs.date_start;
this.date_delay = this.fields_view.arch.attrs.date_delay;
this.date_stop = this.fields_view.arch.attrs.date_stop;
this.day_length = this.fields_view.arch.attrs.day_length || 8;
this.color_field = this.fields_view.arch.attrs.color;
this.fields = this.fields_view.fields;
+
+ if (!this.date_start) {
+ throw new Error("Calendar view has not defined 'date_start' attribute.");
+ }
//* Calendar Fields *
this.calendar_fields.date_start = {'name': this.date_start, 'kind': this.fields[this.date_start].type};
if (this.date_stop) {
this.calendar_fields.date_stop = {'name': this.date_stop, 'kind': this.fields[this.date_stop].type};
}
- if (!this.date_delay && !this.date_stop) {
- throw new Error("Calendar view has none of the following attributes : 'date_stop', 'date_delay'");
- }
for (var fld = 0; fld < this.fields_view.arch.children.length; fld++) {
this.info_fields.push(this.fields_view.arch.children[fld].attrs.name);
this.init_scheduler();
this.has_been_loaded.resolve();
- if (this.dataset.ids.length) {
- this.dataset.read_ids(this.dataset.ids, _.keys(this.fields), this.on_events_loaded);
- }
},
init_scheduler: function() {
var self = this;
scheduler.config.drag_resize = true;
scheduler.config.drag_create = true;
- // Initialize Sceduler
- this.mode = this.mode || 'month';
- scheduler.init('openerp_scheduler', null, this.mode);
+ scheduler.init('openerp_scheduler', null, this.mode || 'month');
scheduler.detachAllEvents();
scheduler.attachEvent('onEventAdded', this.do_create_event);
convert_event: function(evt) {
var date_start = openerp.web.str_to_datetime(evt[this.date_start]),
date_stop = this.date_stop ? openerp.web.str_to_datetime(evt[this.date_stop]) : null,
- date_delay = evt[this.date_delay] || null,
+ date_delay = evt[this.date_delay] || 1.0,
res_text = '',
res_description = [];
}
return data;
},
- do_search: function(domains, contexts, groupbys) {
+ do_search: function(domain, context, group_by) {
var self = this;
scheduler.clearAll();
$.when(this.has_been_loaded).then(function() {
- self.rpc('/web/session/eval_domain_and_context', {
- domains: domains,
- contexts: contexts,
- group_by_seq: groupbys
- }, function (results) {
- // TODO: handle non-empty results.group_by with read_group
- self.dataset.context = self.context = results.context;
- self.dataset.domain = self.domain = results.domain;
- self.dataset.read_slice(_.keys(self.fields), {
- offset:0,
- limit: self.limit
- }, function(events) {
- self.dataset_events = events;
- self.on_events_loaded(events);
- }
- );
+ // TODO: handle non-empty results.group_by with read_group
+ self.dataset.read_slice(_.keys(self.fields), {
+ offset: 0,
+ limit: self.limit
+ }, function(events) {
+ self.dataset_events = events;
+ self.on_events_loaded(events);
});
});
},
{
"name": "Web Chat",
+ "category" : "Hidden",
"version": "2.0",
"depends": ['web'],
"js": [
'static/src/js/web_chat.js'
],
"css": [],
-# 'active': True,
'active': False,
+ 'installable': False,
}
import time
import simplejson
-import web.common as openerpweb
+import web.common.http as openerpweb
import logging
_logger = logging.getLogger(__name__)
{
"name": "web Dashboard",
+ "category" : "Hidden",
"version": "2.0",
"depends": ['web'],
"js": [
# -*- coding: utf-8 -*-
-import web.common as openerpweb
+import web.common.http as openerpweb
WIDGET_CONTENT_PATTERN = """<!DOCTYPE html>
<html>
{
"name" : "OpenERP Web installer home",
+ "category" : "Hidden",
"version" : "2.0",
"depends" : ['web'],
'active': True,
})
},
install_module: function (module_name) {
+ var self = this;
var Modules = new openerp.web.DataSetSearch(
- this, 'ir.module.module', null, [['name', '=', module_name], ['state', '=', 'uninstalled']]);
+ this, 'ir.module.module', null,
+ [['name', '=', module_name], ['state', '=', 'uninstalled']]);
var Upgrade = new openerp.web.DataSet(this, 'base.module.upgrade');
$.blockUI({message:'<img src="/web/static/src/img/throbber2.gif">'});
Modules.read_slice(['id'], {}, function (records) {
- if (!(records.length === 1)) { return; }
+ if (!(records.length === 1)) { $.unblockUI(); return; }
Modules.call('state_update',
[_.pluck(records, 'id'), 'to install', ['uninstalled']],
function () {
Upgrade.call('upgrade_module', [[]], function () {
- $.unblockUI();
- // TODO: less brutal reloading
- window.location.reload(true);
+ self.run_configuration_wizards();
});
}
)
});
+ },
+ run_configuration_wizards: function () {
+ var self = this;
+ new openerp.web.DataSet(this, 'res.config').call('start', [[]], function (action) {
+ $.unblockUI();
+ self.do_action(action, function () {
+ // TODO: less brutal reloading
+ window.location.reload(true);
+ });
+ });
}
});
};
{
"name" : "OpenERP Web Diagram",
+ "category" : "Hidden",
"version" : "2.0",
"depends" : ["base"],
"js": [
-import web.common as openerpweb
+import web.common.http as openerpweb
from web.controllers.main import View
class DiagramView(View):
{
"name": "web Gantt",
+ "category" : "Hidden",
"version": "2.0",
"depends": ['web'],
"js": [
do_search: function (domains, contexts, groupbys) {
var self = this;
this.grp = groupbys;
- return this.rpc('/web/session/eval_domain_and_context', {
- domains: domains,
- contexts: contexts,
- group_by_seq: groupbys
- }, function (results) {
- self.dataset.context = results.context;
- self.dataset.domain = results.domain;
- self.reload_gantt();
- });
+ self.reload_gantt();
}
});
{
"name": "web Graph",
+ "category" : "Hidden",
"version": "2.0",
"depends": ['web'],
"js": [
"static/src/js/graph.js"],
"css": ["static/lib/dhtmlxGraph/codebase/dhtmlxchart.css"],
"active": True
-}
\ No newline at end of file
+}
this.group_field = null;
},
do_show: function () {
- // TODO: re-trigger search
this.$element.show();
},
do_hide: function () {
}
}, this);
this.ordinate = this.columns[0].name;
-
- this.dataset.read_slice(
- this.list_fields(), {}, $.proxy(this, 'schedule_chart'));
},
schedule_chart: function(results) {
var self = this;
});
},
- do_search: function(domains, contexts, groupbys) {
- var self = this;
- this.rpc('/web/session/eval_domain_and_context', {
- domains: domains,
- contexts: contexts,
- group_by_seq: groupbys
- }, function (results) {
- // TODO: handle non-empty results.group_by with read_group?
- if(!_(results.group_by).isEmpty()){
- self.abscissa = results.group_by[0];
- } else {
- self.abscissa = self.first_field;
- }
- self.dataset.read_slice(self.list_fields(), {
- context: results.context,
- domain: results.domain
- }, $.proxy(self, 'schedule_chart'));
- });
+ do_search: function(domain, context, group_by) {
+ // TODO: handle non-empty group_by with read_group?
+ if (!_(group_by).isEmpty()) {
+ this.abscissa = group_by[0];
+ } else {
+ this.abscissa = this.first_field;
+ }
+ this.dataset.read_slice(this.list_fields(), {}, $.proxy(this, 'schedule_chart'));
}
});
};
{
"name": "Hello",
+ "category" : "Hidden",
"version": "2.0",
"depends": [],
"js": ["static/*/*.js", "static/*/js/*.js"],
{
"name" : "Base Kanban",
+ "category" : "Hidden",
"version" : "2.0",
"depends" : ["web"],
"js": [
this.set_default_options(options);
this.dataset = dataset;
this.model = dataset.model;
- this.domain = dataset.domain;
- this.context = dataset.context;
this.view_id = view_id;
this.fields_view = {};
this.group_by = [];
var self = this;
this.fields_view = data;
this.add_qweb_template();
- if (this.qweb.has_template('kanban-box')) {
- this.do_actual_search();
- }
},
add_qweb_template: function() {
var group_operator = ["avg", "max", "min", "sum", "count"]
this.do_execute_action(
button_attrs, dataset,
record_id, function () {
- self.do_actual_search();
+ self.on_reload_record(record_id);
}
);
},
});
return new_record;
},
- do_search: function (domains, contexts, group_by) {
+ do_search: function (domain, context, group_by) {
var self = this;
- this.rpc('/web/session/eval_domain_and_context', {
- domains: [this.dataset.get_domain()].concat(domains),
- contexts: [this.dataset.get_context()].concat(contexts),
- group_by_seq: group_by
- }, function (results) {
- self.domain = results.domain;
- self.context = results.context;
- self.group_by = results.group_by;
- self.do_actual_search();
- });
- },
- do_actual_search : function () {
+ self.group_by = group_by;
var self = this,
group_by = self.group_by;
if (!group_by.length && this.fields_view.arch.attrs.default_group_by) {
group_by = [this.fields_view.arch.attrs.default_group_by];
self.group_by = group_by;
}
- self.datagroup = new openerp.web.DataGroup(self, self.model, self.domain, self.context, group_by);
- self.datagroup.list(_.keys(self.fields_view.fields),
+ self.datagroup = new openerp.web.DataGroup(self, self.model, domain, context, group_by);
+ self.datagroup.list(
+ _.keys(self.fields_view.fields),
function (groups) {
self.groups = groups;
if (groups.length) {
},
function (dataset) {
self.groups = [];
- self.dataset.read_slice([], {'domain': self.domain, 'context': self.context}, function(records) {
- if (records.length) self.all_display_data = [{'records': records, 'value':false, 'header' : false, 'ids': self.dataset.ids}];
- else self.all_display_data = [];
+ self.dataset.read_slice([], {}, function(records) {
+ if (records.length) {
+ self.all_display_data = [{'records': records, 'value':false, 'header' : false, 'ids': self.dataset.ids}];
+ } else {
+ self.all_display_data = [];
+ }
self.$element.find(".oe_kanban_view").remove();
self.on_show_data();
});
_.each(self.aggregates, function(value, key) {
group_aggregates[value] = group.aggregates[key];
});
- self.dataset.read_slice([], {'domain': group.domain, 'context': group.context}, function(records) {
+ self.dataset.read_slice([], {'domain': group.domain, 'conext': group.context}, function(records) {
self.all_display_data.push({"value" : group_value, "records": records, 'header':group_name, 'ids': self.dataset.ids, 'aggregates': group_aggregates});
if (datagroups.length == self.all_display_data.length) {
self.$element.find(".oe_kanban_view").remove();
{
"name" : "OpenERP Web mobile",
+ "category" : "Hidden",
"version" : "2.0",
"depends" : [],
'active': True,
{
"name" : "OpenERP Web web",
+ "category" : "Hidden",
"version" : "2.0",
"depends" : [],
'active': False,
{
"name": "Tests",
+ "category" : "Hidden",
"version": "2.0",
"depends": [],
"js": ["static/src/js/*.js"],
default=True, action='store_false',
help="Do not serve static files via this server")
server_options.add_option('--multi-threaded', dest='threaded',
- default=True, action='store_true',
+ default=False, action='store_true',
help="Spawn one thread per HTTP request")
server_options.add_option('--proxy-mode', dest='proxy_mode',
default=False, action='store_true',
help="Logging configuration file", metavar="FILE")
optparser.add_option_group(logging_opts)
-import web.common.dispatch
+import web.common.http
if __name__ == "__main__":
(options, args) = optparser.parse_args(sys.argv[1:])
else:
logging.basicConfig(level=getattr(logging, options.log_level.upper()))
- app = web.common.dispatch.Root(options)
+ app = web.common.http.Root(options)
if options.proxy_mode:
app = werkzeug.contrib.fixers.ProxyFix(app)