Merge with trunk

bzr revid: rgaopenerp-20121012115527-xn5iqpctzbb9qmkv
This commit is contained in:
RGA(OpenERP) 2012-10-12 17:25:27 +05:30
commit 7b41571e32
71 changed files with 596 additions and 1348 deletions

View File

@ -1,34 +0,0 @@
.PHONY: all doc release clean
HOST = 127.0.0.1
PORT = 8080
all: run
run:
python openerp-web.py -a ${HOST} -p ${PORT}
release:
python setup.py sdist
install:
python setup.py install
clean:
@find . -name '*.pyc' -exec rm -f {} +
@find . -name '*.pyo' -exec rm -f {} +
@find . -name '*.swp' -exec rm -f {} +
@find . -name '*~' -exec rm -f {} +
@rm -rf build
@rm -rf dist
@rm -rf *.egg-info
doc:
make -C doc html
cloc:
cloc addons/*/common/*.py addons/*/controllers/*.py addons/*/static/src/*.js addons/*/static/src/js/*.js addons/*/static/src/css/*.css addons/*/static/src/xml/*.xml
blamestat:
echo addons/*/common/*.py addons/*/controllers/*.py addons/*/static/src/js/*.js addons/*/static/src/css/*.css addons/*/static/src/xml/*.xml | xargs -t -n 1 bzr blame -v --long --all | awk '{print $2}' | sort | uniq -c | sort -n

View File

@ -1,9 +1,11 @@
OpenERP Web OpenERP Web
----------- -----------
To build the documentation use: The OpenERP Web Client supports the following web browsers:
$ make doc * Internet Explorer 9+
* Google Chrome 22+
* Firefox 13+
* Any browser using the latest version of Chrome Frame
then look at doc/build/html/index.html

View File

@ -1,31 +1,4 @@
import logging import http
import controllers
from . import common
from . import controllers
_logger = logging.getLogger(__name__)
class Options(object):
pass
def wsgi_postload():
import openerp
import os
import tempfile
import getpass
_logger.info("embedded mode")
o = Options()
o.dbfilter = openerp.tools.config['dbfilter']
o.server_wide_modules = openerp.conf.server_wide_modules or ['web']
try:
username = getpass.getuser()
except Exception:
username = "unknown"
o.session_storage = os.path.join(tempfile.gettempdir(), "oe-sessions-" + username)
o.addons_path = openerp.modules.module.ad_paths
o.serve_static = True
o.backend = 'local'
app = common.http.Root(o)
openerp.wsgi.register_wsgi_handler(app)
wsgi_postload = http.wsgi_postload

View File

@ -1,6 +0,0 @@
#!/usr/bin/python
from . import http
from . import nonliterals
from . import release
from . import session
from . import xml2json

View File

@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) Stephane Wirtel
# Copyright (C) 2011 Nicolas Vanhoren
# Copyright (C) 2011 OpenERP s.a. (<http://openerp.com>).
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##############################################################################
from .main import *

View File

@ -1,97 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) Stephane Wirtel
# Copyright (C) 2011 Nicolas Vanhoren
# Copyright (C) 2011 OpenERP s.a. (<http://openerp.com>).
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##############################################################################
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)

View File

@ -1,311 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) Stephane Wirtel
# Copyright (C) 2011 Nicolas Vanhoren
# Copyright (C) 2011 OpenERP s.a. (<http://openerp.com>).
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##############################################################################
"""
OpenERP Client Library
Home page: http://pypi.python.org/pypi/openerp-client-lib
Code repository: https://code.launchpad.net/~niv-openerp/openerp-client-lib/trunk
"""
import xmlrpclib
import logging
_logger = logging.getLogger(__name__)
def _getChildLogger(logger, subname):
return logging.getLogger(logger.name + "." + subname)
class Connector(object):
"""
The base abstract class representing a connection to an OpenERP Server.
"""
__logger = _getChildLogger(_logger, 'connector')
def get_service(self, service_name):
"""
Returns a Service instance to allow easy manipulation of one of the services offered by the remote server.
:param service_name: The name of the service.
"""
return Service(self, service_name)
class XmlRPCConnector(Connector):
"""
A type of connector that uses the XMLRPC protocol.
"""
PROTOCOL = 'xmlrpc'
__logger = _getChildLogger(_logger, 'connector.xmlrpc')
def __init__(self, hostname, port=8069):
"""
Initialize by specifying the hostname and the port.
:param hostname: The hostname of the computer holding the instance of OpenERP.
:param port: The port used by the OpenERP instance for XMLRPC (default to 8069).
"""
self.url = 'http://%s:%d/xmlrpc' % (hostname, port)
def send(self, service_name, method, *args):
url = '%s/%s' % (self.url, service_name)
service = xmlrpclib.ServerProxy(url)
return getattr(service, method)(*args)
class XmlRPCSConnector(XmlRPCConnector):
"""
A type of connector that uses the secured XMLRPC protocol.
"""
PROTOCOL = 'xmlrpcs'
__logger = _getChildLogger(_logger, 'connector.xmlrpcs')
def __init__(self, hostname, port=8069):
super(XmlRPCSConnector, self).__init__(hostname, port)
self.url = 'https://%s:%d/xmlrpc' % (hostname, port)
class Service(object):
"""
A class to execute RPC calls on a specific service of the remote server.
"""
def __init__(self, connector, service_name):
"""
:param connector: A valid Connector instance.
:param service_name: The name of the service on the remote server.
"""
self.connector = connector
self.service_name = service_name
self.__logger = _getChildLogger(_getChildLogger(_logger, 'service'),service_name or "")
def __getattr__(self, method):
"""
:param method: The name of the method to execute on the service.
"""
self.__logger.debug('method: %r', method)
def proxy(*args):
"""
:param args: A list of values for the method
"""
self.__logger.debug('args: %r', args)
result = self.connector.send(self.service_name, method, *args)
self.__logger.debug('result: %r', result)
return result
return proxy
class Connection(object):
"""
A class to represent a connection with authentication to an OpenERP Server.
It also provides utility methods to interact with the server more easily.
"""
__logger = _getChildLogger(_logger, 'connection')
def __init__(self, connector,
database=None,
login=None,
password=None,
user_id=None):
"""
Initialize with login information. The login information is facultative to allow specifying
it after the initialization of this object.
:param connector: A valid Connector instance to send messages to the remote server.
:param database: The name of the database to work on.
:param login: The login of the user.
:param password: The password of the user.
:param user_id: The user id is a number identifying the user. This is only useful if you
already know it, in most cases you don't need to specify it.
"""
self.connector = connector
self.set_login_info(database, login, password, user_id)
self.user_context = None
def set_login_info(self, database, login, password, user_id=None):
"""
Set login information after the initialisation of this object.
:param connector: A valid Connector instance to send messages to the remote server.
:param database: The name of the database to work on.
:param login: The login of the user.
:param password: The password of the user.
:param user_id: The user id is a number identifying the user. This is only useful if you
already know it, in most cases you don't need to specify it.
"""
self.database, self.login, self.password = database, login, password
self.user_id = user_id
def check_login(self, force=True):
"""
Checks that the login information is valid. Throws an AuthenticationError if the
authentication fails.
:param force: Force to re-check even if this Connection was already validated previously.
Default to True.
"""
if self.user_id and not force:
return
if not self.database or not self.login or self.password is None:
raise AuthenticationError("Credentials not provided")
# TODO use authenticate instead of login
self.user_id = self.get_service("common").login(self.database, self.login, self.password)
if not self.user_id:
raise AuthenticationError("Authentication failure")
self.__logger.debug("Authenticated with user id %s", self.user_id)
def get_user_context(self):
"""
Query the default context of the user.
"""
if not self.user_context:
self.user_context = self.get_model('res.users').context_get()
return self.user_context
def get_model(self, model_name):
"""
Returns a Model instance to allow easy remote manipulation of an OpenERP model.
:param model_name: The name of the model.
"""
return Model(self, model_name)
def get_service(self, service_name):
"""
Returns a Service instance to allow easy manipulation of one of the services offered by the remote server.
Please note this Connection instance does not need to have valid authentication information since authentication
is only necessary for the "object" service that handles models.
:param service_name: The name of the service.
"""
return self.connector.get_service(service_name)
class AuthenticationError(Exception):
"""
An error thrown when an authentication to an OpenERP server failed.
"""
pass
class Model(object):
"""
Useful class to dialog with one of the models provided by an OpenERP server.
An instance of this class depends on a Connection instance with valid authentication information.
"""
def __init__(self, connection, model_name):
"""
:param connection: A valid Connection instance with correct authentication information.
:param model_name: The name of the model.
"""
self.connection = connection
self.model_name = model_name
self.__logger = _getChildLogger(_getChildLogger(_logger, 'object'), model_name or "")
def __getattr__(self, method):
"""
Provides proxy methods that will forward calls to the model on the remote OpenERP server.
:param method: The method for the linked model (search, read, write, unlink, create, ...)
"""
def proxy(*args, **kw):
"""
:param args: A list of values for the method
"""
self.connection.check_login(False)
self.__logger.debug(args)
result = self.connection.get_service('object').execute_kw(
self.connection.database,
self.connection.user_id,
self.connection.password,
self.model_name,
method,
args, kw)
if method == "read":
if isinstance(result, list) and len(result) > 0 and "id" in result[0]:
index = {}
for r in result:
index[r['id']] = r
result = [index[x] for x in args[0] if x in index]
self.__logger.debug('result: %r', result)
return result
return proxy
def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None, context=None):
"""
A shortcut method to combine a search() and a read().
:param domain: The domain for the search.
:param fields: The fields to extract (can be None or [] to extract all fields).
:param offset: The offset for the rows to read.
:param limit: The maximum number of rows to read.
:param order: The order to class the rows.
:param context: The context.
:return: A list of dictionaries containing all the specified fields.
"""
record_ids = self.search(domain or [], offset, limit or False, order or False, context or {})
if not record_ids: return []
records = self.read(record_ids, fields or [], context or {})
return records
def get_connector(hostname=None, protocol="xmlrpc", port="auto"):
"""
A shortcut method to easily create a connector to a remote server using XMLRPC.
:param hostname: The hostname to the remote server.
:param protocol: The name of the protocol, must be "xmlrpc" or "xmlrpcs".
:param port: The number of the port. Defaults to auto.
"""
if port == 'auto':
port = 8069
if protocol == "xmlrpc":
return XmlRPCConnector(hostname, port)
elif protocol == "xmlrpcs":
return XmlRPCSConnector(hostname, port)
else:
raise ValueError("You must choose xmlrpc or xmlrpcs")
def get_connection(hostname=None, protocol="xmlrpc", port='auto', database=None,
login=None, password=None, user_id=None):
"""
A shortcut method to easily create a connection to a remote OpenERP server.
:param hostname: The hostname to the remote server.
:param protocol: The name of the protocol, must be "xmlrpc" or "xmlrpcs".
:param port: The number of the port. Defaults to auto.
:param connector: A valid Connector instance to send messages to the remote server.
:param database: The name of the database to work on.
:param login: The login of the user.
:param password: The password of the user.
:param user_id: The user id is a number identifying the user. This is only useful if you
already know it, in most cases you don't need to specify it.
"""
return Connection(get_connector(hostname, protocol, port), database, login, password, user_id)

View File

@ -1,10 +0,0 @@
name = 'openerp-web'
version = '7.0alpha'
description = "OpenERP Web"
long_description = "OpenERP Web is the web interface of OpenERP, an Open Source Business Application Suite"
author = "OpenERP SA"
author_email = "info@openerp.com"
support_email = 'support@openerp.com'
url = "http://www.openerp.com/"
download_url = ''
license = "AGPL"

View File

@ -1,26 +0,0 @@
# xml2json-direct
# Simple and straightforward XML-to-JSON converter in Python
# New BSD Licensed
#
# URL: http://code.google.com/p/xml2json-direct/
def from_elementtree(el, preserve_whitespaces=False):
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 (preserve_whitespaces or el.text.strip() != ''):
kids.append(el.text)
for kid in el:
kids.append(from_elementtree(kid, preserve_whitespaces))
if kid.tail and (preserve_whitespaces or kid.tail.strip() != ''):
kids.append(kid.tail)
res["children"] = kids
return res

View File

@ -27,8 +27,11 @@ try:
except ImportError: except ImportError:
xlwt = None xlwt = None
from .. import common import openerp
openerpweb = common.http
from .. import http
from .. import nonliterals
openerpweb = http
#---------------------------------------------------------- #----------------------------------------------------------
# OpenERP Web helpers # OpenERP Web helpers
@ -135,7 +138,7 @@ def db_list(req):
dbs = proxy.list() dbs = proxy.list()
h = req.httprequest.environ['HTTP_HOST'].split(':')[0] h = req.httprequest.environ['HTTP_HOST'].split(':')[0]
d = h.split('.')[0] d = h.split('.')[0]
r = req.config.dbfilter.replace('%h', h).replace('%d', d) r = openerp.tools.config['dbfilter'].replace('%h', h).replace('%d', d)
dbs = [i for i in dbs if re.match(r, i)] dbs = [i for i in dbs if re.match(r, i)]
return dbs return dbs
@ -227,11 +230,12 @@ def module_installed_bypass_session(dbname):
return sorted_modules return sorted_modules
def module_boot(req): def module_boot(req):
return [m for m in req.config.server_wide_modules if m in openerpweb.addons_manifest] server_wide_modules = openerp.conf.server_wide_modules or ['web']
return [m for m in server_wide_modules if m in openerpweb.addons_manifest]
# TODO the following will be enabled once we separate the module code and translation loading # TODO the following will be enabled once we separate the module code and translation loading
serverside = [] serverside = []
dbside = [] dbside = []
for i in req.config.server_wide_modules: for i in server_wide_modules:
if i in openerpweb.addons_manifest: if i in openerpweb.addons_manifest:
serverside.append(i) serverside.append(i)
# if only one db load every module at boot # if only one db load every module at boot
@ -502,12 +506,12 @@ def fix_view_modes(action):
def parse_domain(domain, session): def parse_domain(domain, session):
""" Parses an arbitrary string containing a domain, transforms it """ Parses an arbitrary string containing a domain, transforms it
to either a literal domain or a :class:`common.nonliterals.Domain` to either a literal domain or a :class:`nonliterals.Domain`
:param domain: the domain to parse, if the domain is not a string it :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 is assumed to be a literal domain and is returned as-is
:param session: Current OpenERP session :param session: Current OpenERP session
:type session: openerpweb.openerpweb.OpenERPSession :type session: openerpweb.OpenERPSession
""" """
if not isinstance(domain, basestring): if not isinstance(domain, basestring):
return domain return domain
@ -515,24 +519,23 @@ def parse_domain(domain, session):
return ast.literal_eval(domain) return ast.literal_eval(domain)
except ValueError: except ValueError:
# not a literal # not a literal
return common.nonliterals.Domain(session, domain) return nonliterals.Domain(session, domain)
def parse_context(context, session): def parse_context(context, session):
""" Parses an arbitrary string containing a context, transforms it """ Parses an arbitrary string containing a context, transforms it
to either a literal context or a :class:`common.nonliterals.Context` to either a literal context or a :class:`nonliterals.Context`
:param context: the context to parse, if the context is not a string it :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 is assumed to be a literal domain and is returned as-is
:param session: Current OpenERP session :param session: Current OpenERP session
:type session: openerpweb.openerpweb.OpenERPSession :type session: openerpweb.OpenERPSession
""" """
if not isinstance(context, basestring): if not isinstance(context, basestring):
return context return context
try: try:
return ast.literal_eval(context) return ast.literal_eval(context)
except ValueError: except ValueError:
return common.nonliterals.Context(session, context) return nonliterals.Context(session, context)
def _local_web_translations(trans_file): def _local_web_translations(trans_file):
messages = [] messages = []
@ -546,6 +549,31 @@ def _local_web_translations(trans_file):
messages.append({'id': x.id, 'string': x.string}) messages.append({'id': x.id, 'string': x.string})
return messages return messages
def from_elementtree(el, preserve_whitespaces=False):
""" xml2json-direct
Simple and straightforward XML-to-JSON converter in Python
New BSD Licensed
http://code.google.com/p/xml2json-direct/
"""
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 (preserve_whitespaces or el.text.strip() != ''):
kids.append(el.text)
for kid in el:
kids.append(from_elementtree(kid, preserve_whitespaces))
if kid.tail and (preserve_whitespaces or kid.tail.strip() != ''):
kids.append(kid.tail)
res["children"] = kids
return res
#---------------------------------------------------------- #----------------------------------------------------------
# OpenERP Web web Controllers # OpenERP Web web Controllers
@ -568,7 +596,27 @@ html_template = """<!DOCTYPE html>
}); });
</script> </script>
</head> </head>
<body></body> <body>
<!--[if lte IE 8]>
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
<script>
var test = function() {
CFInstall.check({
mode: "overlay"
});
};
if (window.localStorage && false) {
if (! localStorage.getItem("hasShownGFramePopup")) {
test();
localStorage.setItem("hasShownGFramePopup", true);
}
} else {
test();
}
</script>
<![endif]-->
</body>
</html> </html>
""" """
@ -592,7 +640,6 @@ class Home(openerpweb.Controller):
def login(self, req, db, login, key): def login(self, req, db, login, key):
return login_and_redirect(req, db, login, key) return login_and_redirect(req, db, login, key)
class WebClient(openerpweb.Controller): class WebClient(openerpweb.Controller):
_cp_path = "/web/webclient" _cp_path = "/web/webclient"
@ -726,7 +773,7 @@ class WebClient(openerpweb.Controller):
@openerpweb.jsonrequest @openerpweb.jsonrequest
def version_info(self, req): def version_info(self, req):
return { return {
"version": common.release.version "version": openerp.release.version
} }
class Proxy(openerpweb.Controller): class Proxy(openerpweb.Controller):
@ -842,12 +889,10 @@ class Session(openerpweb.Controller):
@openerpweb.jsonrequest @openerpweb.jsonrequest
def authenticate(self, req, db, login, password, base_location=None): def authenticate(self, req, db, login, password, base_location=None):
wsgienv = req.httprequest.environ wsgienv = req.httprequest.environ
release = common.release
env = dict( env = dict(
base_location=base_location, base_location=base_location,
HTTP_HOST=wsgienv['HTTP_HOST'], HTTP_HOST=wsgienv['HTTP_HOST'],
REMOTE_ADDR=wsgienv['REMOTE_ADDR'], REMOTE_ADDR=wsgienv['REMOTE_ADDR'],
user_agent="%s / %s" % (release.name, release.version),
) )
req.session.authenticate(db, login, password, env) req.session.authenticate(db, login, password, env)
@ -922,8 +967,8 @@ class Session(openerpweb.Controller):
no group by should be performed) no group by should be performed)
""" """
context, domain = eval_context_and_domain(req.session, context, domain = eval_context_and_domain(req.session,
common.nonliterals.CompoundContext(*(contexts or [])), nonliterals.CompoundContext(*(contexts or [])),
common.nonliterals.CompoundDomain(*(domains or []))) nonliterals.CompoundDomain(*(domains or [])))
group_by_sequence = [] group_by_sequence = []
for candidate in (group_by_seq or []): for candidate in (group_by_seq or []):
@ -1145,14 +1190,14 @@ class DataSet(openerpweb.Controller):
def _call_kw(self, req, model, method, args, kwargs): def _call_kw(self, req, model, method, args, kwargs):
for i in xrange(len(args)): for i in xrange(len(args)):
if isinstance(args[i], common.nonliterals.BaseContext): if isinstance(args[i], nonliterals.BaseContext):
args[i] = req.session.eval_context(args[i]) args[i] = req.session.eval_context(args[i])
elif isinstance(args[i], common.nonliterals.BaseDomain): elif isinstance(args[i], nonliterals.BaseDomain):
args[i] = req.session.eval_domain(args[i]) args[i] = req.session.eval_domain(args[i])
for k in kwargs.keys(): for k in kwargs.keys():
if isinstance(kwargs[k], common.nonliterals.BaseContext): if isinstance(kwargs[k], nonliterals.BaseContext):
kwargs[k] = req.session.eval_context(kwargs[k]) kwargs[k] = req.session.eval_context(kwargs[k])
elif isinstance(kwargs[k], common.nonliterals.BaseDomain): elif isinstance(kwargs[k], nonliterals.BaseDomain):
kwargs[k] = req.session.eval_domain(kwargs[k]) kwargs[k] = req.session.eval_domain(kwargs[k])
# Temporary implements future display_name special field for model#read() # Temporary implements future display_name special field for model#read()
@ -1283,7 +1328,7 @@ class View(openerpweb.Controller):
xml = self.transform_view(arch, session, evaluation_context) xml = self.transform_view(arch, session, evaluation_context)
else: else:
xml = ElementTree.fromstring(arch) xml = ElementTree.fromstring(arch)
fvg['arch'] = common.xml2json.from_elementtree(xml, preserve_whitespaces) fvg['arch'] = from_elementtree(xml, preserve_whitespaces)
if 'id' in fvg['fields']: if 'id' in fvg['fields']:
# Special case for id's # Special case for id's
@ -1416,12 +1461,12 @@ class SearchView(View):
try: try:
parsed_context = parse_context(filter["context"], req.session) parsed_context = parse_context(filter["context"], req.session)
filter["context"] = (parsed_context filter["context"] = (parsed_context
if not isinstance(parsed_context, common.nonliterals.BaseContext) if not isinstance(parsed_context, nonliterals.BaseContext)
else req.session.eval_context(parsed_context)) else req.session.eval_context(parsed_context))
parsed_domain = parse_domain(filter["domain"], req.session) parsed_domain = parse_domain(filter["domain"], req.session)
filter["domain"] = (parsed_domain filter["domain"] = (parsed_domain
if not isinstance(parsed_domain, common.nonliterals.BaseDomain) if not isinstance(parsed_domain, nonliterals.BaseDomain)
else req.session.eval_domain(parsed_domain)) else req.session.eval_domain(parsed_domain))
except Exception: except Exception:
logger.exception("Failed to parse custom filter %s in %s", logger.exception("Failed to parse custom filter %s in %s",
@ -1912,7 +1957,7 @@ class Reports(View):
report_srv = req.session.proxy("report") report_srv = req.session.proxy("report")
context = req.session.eval_context( context = req.session.eval_context(
common.nonliterals.CompoundContext( nonliterals.CompoundContext(
req.context or {}, action[ "context"])) req.context or {}, action[ "context"]))
report_data = {} report_data = {}

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -1,6 +1,16 @@
API changes from OpenERP Web 6.1 to 7.0 API changes from OpenERP Web 6.1 to 7.0
======================================= =======================================
Supported browsers
------------------
The OpenERP Web Client supports the following web browsers:
* Internet Explorer 9+
* Google Chrome 22+
* Firefox 13+
* Any browser using the latest version of Chrome Frame
DataSet -> Model DataSet -> Model
---------------- ----------------

View File

@ -6,15 +6,18 @@ import ast
import cgi import cgi
import contextlib import contextlib
import functools import functools
import getpass
import logging import logging
import mimetypes import mimetypes
import os import os
import pprint import pprint
import sys import sys
import tempfile
import threading import threading
import time import time
import traceback import traceback
import urllib import urllib
import urlparse
import uuid import uuid
import xmlrpclib import xmlrpclib
@ -26,18 +29,15 @@ import werkzeug.utils
import werkzeug.wrappers import werkzeug.wrappers
import werkzeug.wsgi import werkzeug.wsgi
from . import nonliterals import openerp
from . import session
from . import openerplib
import urlparse
__all__ = ['Root', 'jsonrequest', 'httprequest', 'Controller', import nonliterals
'WebRequest', 'JsonRequest', 'HttpRequest'] import session
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
#---------------------------------------------------------- #----------------------------------------------------------
# OpenERP Web RequestHandler # RequestHandler
#---------------------------------------------------------- #----------------------------------------------------------
class WebRequest(object): class WebRequest(object):
""" Parent class for all OpenERP Web request types, mostly deals with """ Parent class for all OpenERP Web request types, mostly deals with
@ -46,7 +46,6 @@ class WebRequest(object):
:param request: a wrapped werkzeug Request object :param request: a wrapped werkzeug Request object
:type request: :class:`werkzeug.wrappers.BaseRequest` :type request: :class:`werkzeug.wrappers.BaseRequest`
:param config: configuration object
.. attribute:: httprequest .. attribute:: httprequest
@ -58,10 +57,6 @@ class WebRequest(object):
a :class:`~collections.Mapping` holding the HTTP session data for the a :class:`~collections.Mapping` holding the HTTP session data for the
current http session current http session
.. attribute:: config
config parameter provided to the request object
.. attribute:: params .. attribute:: params
:class:`~collections.Mapping` of request parameters, not generally :class:`~collections.Mapping` of request parameters, not generally
@ -85,11 +80,10 @@ class WebRequest(object):
``bool``, indicates whether the debug mode is active on the client ``bool``, indicates whether the debug mode is active on the client
""" """
def __init__(self, request, config): def __init__(self, request):
self.httprequest = request self.httprequest = request
self.httpresponse = None self.httpresponse = None
self.httpsession = request.session self.httpsession = request.session
self.config = config
def init(self, params): def init(self, params):
self.params = dict(params) self.params = dict(params)
@ -98,7 +92,6 @@ class WebRequest(object):
self.session = self.httpsession.get(self.session_id) self.session = self.httpsession.get(self.session_id)
if not self.session: if not self.session:
self.httpsession[self.session_id] = self.session = session.OpenERPSession() self.httpsession[self.session_id] = self.session = session.OpenERPSession()
self.session.config = self.config
self.context = self.params.pop('context', None) self.context = self.params.pop('context', None)
self.debug = self.params.pop('debug', False) != False self.debug = self.params.pop('debug', False) != False
@ -180,7 +173,7 @@ class JsonRequest(WebRequest):
_logger.debug("--> %s.%s\n%s", controller.__class__.__name__, method.__name__, pprint.pformat(self.jsonrequest)) _logger.debug("--> %s.%s\n%s", controller.__class__.__name__, method.__name__, pprint.pformat(self.jsonrequest))
response['id'] = self.jsonrequest.get('id') response['id'] = self.jsonrequest.get('id')
response["result"] = method(controller, self, **self.params) response["result"] = method(controller, self, **self.params)
except openerplib.AuthenticationError: except session.AuthenticationError:
error = { error = {
'code': 100, 'code': 100,
'message': "OpenERP Session Invalid", 'message': "OpenERP Session Invalid",
@ -238,8 +231,8 @@ def jsonrequest(f):
beforehand) beforehand)
""" """
@functools.wraps(f) @functools.wraps(f)
def json_handler(controller, request, config): def json_handler(controller, request):
return JsonRequest(request, config).dispatch(controller, f) return JsonRequest(request).dispatch(controller, f)
json_handler.exposed = True json_handler.exposed = True
return json_handler return json_handler
@ -325,13 +318,13 @@ def httprequest(f):
and ``debug`` keys (which are stripped out beforehand) and ``debug`` keys (which are stripped out beforehand)
""" """
@functools.wraps(f) @functools.wraps(f)
def http_handler(controller, request, config): def http_handler(controller, request):
return HttpRequest(request, config).dispatch(controller, f) return HttpRequest(request).dispatch(controller, f)
http_handler.exposed = True http_handler.exposed = True
return http_handler return http_handler
#---------------------------------------------------------- #----------------------------------------------------------
# OpenERP Web Controller registration with a metaclass # Controller registration with a metaclass
#---------------------------------------------------------- #----------------------------------------------------------
addons_module = {} addons_module = {}
addons_manifest = {} addons_manifest = {}
@ -348,7 +341,7 @@ class Controller(object):
__metaclass__ = ControllerType __metaclass__ = ControllerType
#---------------------------------------------------------- #----------------------------------------------------------
# OpenERP Web Session context manager # Session context manager
#---------------------------------------------------------- #----------------------------------------------------------
STORES = {} STORES = {}
@ -419,12 +412,13 @@ def session_context(request, storage_path, session_cookie='httpsessionid'):
session_store.save(request.session) session_store.save(request.session)
#---------------------------------------------------------- #----------------------------------------------------------
# OpenERP Web WSGI Application # WSGI Application
#---------------------------------------------------------- #----------------------------------------------------------
# Add potentially missing (older ubuntu) font mime types # Add potentially missing (older ubuntu) font mime types
mimetypes.add_type('application/font-woff', '.woff') mimetypes.add_type('application/font-woff', '.woff')
mimetypes.add_type('application/vnd.ms-fontobject', '.eot') mimetypes.add_type('application/vnd.ms-fontobject', '.eot')
mimetypes.add_type('application/x-font-ttf', '.ttf') mimetypes.add_type('application/x-font-ttf', '.ttf')
class DisableCacheMiddleware(object): class DisableCacheMiddleware(object):
def __init__(self, app): def __init__(self, app):
self.app = app self.app = app
@ -449,45 +443,23 @@ class DisableCacheMiddleware(object):
class Root(object): class Root(object):
"""Root WSGI application for the OpenERP Web Client. """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): def __init__(self):
self.config = options
if not hasattr(self.config, 'connector'):
if self.config.backend == 'local':
self.config.connector = session.LocalConnector()
else:
self.config.connector = openerplib.get_connector(
hostname=self.config.server_host, port=self.config.server_port)
self.httpsession_cookie = 'httpsessionid' self.httpsession_cookie = 'httpsessionid'
self.addons = {} self.addons = {}
static_dirs = self._load_addons() static_dirs = self._load_addons()
if options.serve_static: app = werkzeug.wsgi.SharedDataMiddleware( self.dispatch, static_dirs)
app = werkzeug.wsgi.SharedDataMiddleware( self.dispatch, static_dirs) self.dispatch = DisableCacheMiddleware(app)
self.dispatch = DisableCacheMiddleware(app)
if options.session_storage: try:
if not os.path.exists(options.session_storage): username = getpass.getuser()
os.mkdir(options.session_storage, 0700) except Exception:
self.session_storage = options.session_storage 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) _logger.debug('HTTP sessions stored in: %s', self.session_storage)
def __call__(self, environ, start_response): def __call__(self, environ, start_response):
@ -512,7 +484,7 @@ class Root(object):
response = werkzeug.exceptions.NotFound() response = werkzeug.exceptions.NotFound()
else: else:
with session_context(request, self.session_storage, self.httpsession_cookie) as session: with session_context(request, self.session_storage, self.httpsession_cookie) as session:
result = handler( request, self.config) result = handler( request)
if isinstance(result, basestring): if isinstance(result, basestring):
headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))] headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))]
@ -531,7 +503,7 @@ class Root(object):
static URLs to the corresponding directories static URLs to the corresponding directories
""" """
statics = {} statics = {}
for addons_path in self.config.addons_path: for addons_path in openerp.modules.module.ad_paths:
for module in os.listdir(addons_path): for module in os.listdir(addons_path):
if module not in addons_module: if module not in addons_module:
manifest_path = os.path.join(addons_path, module, '__openerp__.py') manifest_path = os.path.join(addons_path, module, '__openerp__.py')
@ -579,4 +551,7 @@ class Root(object):
ps = '/' ps = '/'
return None return None
def wsgi_postload():
openerp.wsgi.register_wsgi_handler(Root())
# vim:et:ts=4:sw=4: # vim:et:ts=4:sw=4:

View File

@ -4,70 +4,58 @@ import babel
import dateutil.relativedelta import dateutil.relativedelta
import logging import logging
import time import time
import traceback
import sys import sys
import xmlrpclib
import openerplib import openerp
from . import nonliterals import nonliterals
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
#----------------------------------------------------------
# openerplib local connector
#----------------------------------------------------------
class LibException(Exception):
""" Base of all client lib exceptions """
def __init__(self,code=None,message=None):
self.code = code
self.message = message
class ApplicationError(LibException):
""" maps to code: 1, server side: Exception or openerp.exceptions.DeferredException"""
class Warning(LibException):
""" maps to code: 2, server side: openerp.exceptions.Warning"""
class AccessError(LibException):
""" maps to code: 3, server side: openerp.exceptions.AccessError"""
class AccessDenied(LibException):
""" maps to code: 4, server side: openerp.exceptions.AccessDenied"""
class LocalConnector(openerplib.Connector):
"""
A type of connector that uses the XMLRPC protocol.
"""
PROTOCOL = 'local'
def __init__(self):
pass
def send(self, service_name, method, *args):
import openerp
import traceback
import xmlrpclib
code_string = "warning -- %s\n\n%s"
try:
return openerp.netsvc.dispatch_rpc(service_name, method, args)
except openerp.osv.osv.except_osv, e:
# TODO change the except to raise LibException instead of their emulated xmlrpc fault
raise xmlrpclib.Fault(code_string % (e.name, e.value), '')
except openerp.exceptions.Warning, e:
raise xmlrpclib.Fault(code_string % ("Warning", e), '')
except openerp.exceptions.AccessError, e:
raise xmlrpclib.Fault(code_string % ("AccessError", e), '')
except openerp.exceptions.AccessDenied, e:
raise xmlrpclib.Fault('AccessDenied', str(e))
except openerp.exceptions.DeferredException, e:
formatted_info = "".join(traceback.format_exception(*e.traceback))
raise xmlrpclib.Fault(openerp.tools.ustr(e.message), formatted_info)
except Exception, e:
formatted_info = "".join(traceback.format_exception(*(sys.exc_info())))
raise xmlrpclib.Fault(openerp.tools.exception_to_unicode(e), formatted_info)
#---------------------------------------------------------- #----------------------------------------------------------
# OpenERPSession RPC openerp backend access # OpenERPSession RPC openerp backend access
#---------------------------------------------------------- #----------------------------------------------------------
class AuthenticationError(Exception):
pass
class Service(object):
def __init__(self, session, service_name):
self.session = session
self.service_name = service_name
def __getattr__(self, method):
def proxy_method(*args):
result = self.session.send(self.service_name, method, *args)
return result
return proxy_method
class Model(object):
def __init__(self, session, model):
self.session = session
self.model = model
self.proxy = self.session.proxy('object')
def __getattr__(self, method):
def proxy(*args, **kw):
result = self.proxy.execute_kw(self.session._db, self.session._uid, self.session._password, self.model, method, args, kw)
# reorder read
if method == "read":
if isinstance(result, list) and len(result) > 0 and "id" in result[0]:
index = {}
for r in result:
index[r['id']] = r
result = [index[x] for x in args[0] if x in index]
return result
return proxy
def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None, context=None):
record_ids = self.search(domain or [], offset, limit or False, order or False, context or {})
if not record_ids: return []
records = self.read(record_ids, fields or [], context or {})
return records
class OpenERPSession(object): class OpenERPSession(object):
""" """
An OpenERP RPC session, a given user can own multiple such sessions An OpenERP RPC session, a given user can own multiple such sessions
@ -87,7 +75,6 @@ class OpenERPSession(object):
""" """
def __init__(self): def __init__(self):
self._creation_time = time.time() self._creation_time = time.time()
self.config = None
self._db = False self._db = False
self._uid = False self._uid = False
self._login = False self._login = False
@ -98,19 +85,27 @@ class OpenERPSession(object):
self.domains_store = {} self.domains_store = {}
self.jsonp_requests = {} # FIXME use a LRU self.jsonp_requests = {} # FIXME use a LRU
def __getstate__(self): def send(self, service_name, method, *args):
state = dict(self.__dict__) code_string = "warning -- %s\n\n%s"
if "config" in state: try:
del state['config'] return openerp.netsvc.dispatch_rpc(service_name, method, args)
return state except openerp.osv.osv.except_osv, e:
raise xmlrpclib.Fault(code_string % (e.name, e.value), '')
def build_connection(self): except openerp.exceptions.Warning, e:
conn = openerplib.Connection(self.config.connector, database=self._db, login=self._login, raise xmlrpclib.Fault(code_string % ("Warning", e), '')
user_id=self._uid, password=self._password) except openerp.exceptions.AccessError, e:
return conn raise xmlrpclib.Fault(code_string % ("AccessError", e), '')
except openerp.exceptions.AccessDenied, e:
raise xmlrpclib.Fault('AccessDenied', str(e))
except openerp.exceptions.DeferredException, e:
formatted_info = "".join(traceback.format_exception(*e.traceback))
raise xmlrpclib.Fault(openerp.tools.ustr(e.message), formatted_info)
except Exception, e:
formatted_info = "".join(traceback.format_exception(*(sys.exc_info())))
raise xmlrpclib.Fault(openerp.tools.exception_to_unicode(e), formatted_info)
def proxy(self, service): def proxy(self, service):
return self.build_connection().get_service(service) return Service(self, service)
def bind(self, db, uid, login, password): def bind(self, db, uid, login, password):
self._db = db self._db = db
@ -119,7 +114,6 @@ class OpenERPSession(object):
self._password = password self._password = password
def authenticate(self, db, login, password, env=None): def authenticate(self, db, login, password, env=None):
# TODO use the openerplib API once it exposes authenticate()
uid = self.proxy('common').authenticate(db, login, password, env) uid = self.proxy('common').authenticate(db, login, password, env)
self.bind(db, uid, login, password) self.bind(db, uid, login, password)
@ -130,7 +124,12 @@ class OpenERPSession(object):
""" """
Ensures this session is valid (logged into the openerp server) Ensures this session is valid (logged into the openerp server)
""" """
self.build_connection().check_login(force) if self._uid and not force:
return
# TODO use authenticate instead of login
uid = self.proxy("common").login(self._db, self._login, self._password)
if not uid:
raise AuthenticationError("Authentication failure")
def ensure_valid(self): def ensure_valid(self):
if self._uid: if self._uid:
@ -141,7 +140,7 @@ class OpenERPSession(object):
def execute(self, model, func, *l, **d): def execute(self, model, func, *l, **d):
self.assert_valid() self.assert_valid()
model = self.build_connection().get_model(model) model = self.model(model)
r = getattr(model, func)(*l, **d) r = getattr(model, func)(*l, **d)
return r return r
@ -157,7 +156,8 @@ class OpenERPSession(object):
:type model: str :type model: str
:rtype: a model object :rtype: a model object
""" """
return self.build_connection().get_model(model)
return Model(self, model)
def get_context(self): def get_context(self):
""" Re-initializes the current user's session context (based on """ Re-initializes the current user's session context (based on
@ -167,7 +167,7 @@ class OpenERPSession(object):
:returns: the new context :returns: the new context
""" """
assert self._uid, "The user needs to be logged-in to initialize his context" assert self._uid, "The user needs to be logged-in to initialize his context"
self.context = self.build_connection().get_user_context() or {} self.context = self.model('res.users').context_get() or {}
self.context['uid'] = self._uid self.context['uid'] = self._uid
self._fix_lang(self.context) self._fix_lang(self.context)
return self.context return self.context

View File

@ -68,10 +68,13 @@
background-color: #f0f0f0; background-color: #f0f0f0;
} }
.openerp thead th { .openerp thead th {
border-right: 1px dotted #afafb6; border-left: 1px solid #dfdfdf;
} }
.openerp thead th:last-child { .openerp thead th:first-child {
border-right: none; border-left: none;
}
.openerp thead th.null {
border-left: none;
} }
.openerp th, .openerp td { .openerp th, .openerp td {
padding: 0; padding: 0;
@ -515,27 +518,11 @@
.openerp .oe_webclient .oe_star_on { .openerp .oe_webclient .oe_star_on {
color: gold; color: gold;
} }
.openerp header { .openerp p.oe_grey {
position: relative; max-width: 650px;
border-bottom: 1px solid #cacaca;
padding-left: 2px;
background-color: #fcfcfc;
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede));
background-image: -webkit-linear-gradient(top, #fcfcfc, #dedede);
background-image: -moz-linear-gradient(top, #fcfcfc, #dedede);
background-image: -ms-linear-gradient(top, #fcfcfc, #dedede);
background-image: -o-linear-gradient(top, #fcfcfc, #dedede);
background-image: linear-gradient(to bottom, #fcfcfc, #dedede);
} }
.openerp header > span { .openerp .oe_grey {
margin-left: 4px; color: #aaaaaa;
}
.openerp header ul {
display: inline-block;
float: right;
}
.openerp header .oe_button {
margin: 3px 2px 1px;
} }
.openerp .oe_tag { .openerp .oe_tag {
border: 1px solid #afafb6; border: 1px solid #afafb6;
@ -2037,6 +2024,31 @@
.openerp .oe_application .oe_form_sheet .oe_notebook_page { .openerp .oe_application .oe_form_sheet .oe_notebook_page {
padding: 0 16px; padding: 0 16px;
} }
.openerp .oe_form header {
position: relative;
border-bottom: 1px solid #cacaca;
padding-left: 2px;
background-color: #fcfcfc;
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede));
background-image: -webkit-linear-gradient(top, #fcfcfc, #dedede);
background-image: -moz-linear-gradient(top, #fcfcfc, #dedede);
background-image: -ms-linear-gradient(top, #fcfcfc, #dedede);
background-image: -o-linear-gradient(top, #fcfcfc, #dedede);
background-image: linear-gradient(to bottom, #fcfcfc, #dedede);
}
.openerp .oe_form header > span {
margin-left: 4px;
}
.openerp .oe_form header ul {
display: inline-block;
float: right;
}
.openerp .oe_form header .oe_button {
margin: 3px 2px 1px;
}
.openerp .oe_form header .oe_button:first-child {
margin-left: 6px;
}
.openerp .oe_form header .oe_tags { .openerp .oe_form header .oe_tags {
margin: 5px 0 0 5px; margin: 5px 0 0 5px;
width: 400px; width: 400px;
@ -2048,11 +2060,6 @@
margin: 0 auto; margin: 0 auto;
padding: 16px 0 48px; padding: 16px 0 48px;
} }
.openerp .oe_form header .oe_tags .oe_grey {
color: #aaaaaa;
max-width: 650px;
margin: 0 0 10px 0;
}
.openerp .oe_form header .oe_tags div.oe_form_configuration p, .openerp .oe_form header .oe_tags div.oe_form_configuration ul, .openerp .oe_form header .oe_tags div.oe_form_configuration ol { .openerp .oe_form header .oe_tags div.oe_form_configuration p, .openerp .oe_form header .oe_tags div.oe_form_configuration ul, .openerp .oe_form header .oe_tags div.oe_form_configuration ol {
color: #aaaaaa; color: #aaaaaa;
max-width: 650px; max-width: 650px;
@ -2246,7 +2253,7 @@
.openerp .oe_form .oe_datepicker_root { .openerp .oe_form .oe_datepicker_root {
display: inline-block; display: inline-block;
} }
.openerp .oe_form .oe_form_required input:not([disabled]), .openerp .oe_form .oe_form_required select:not([disabled]), .openerp .oe_form .oe_form_required textarea:not([disabled]) { .openerp .oe_form .oe_form_required input:not([disabled]):not([readonly]), .openerp .oe_form .oe_form_required select:not([disabled]):not([readonly]), .openerp .oe_form .oe_form_required textarea:not([disabled]):not([readonly]) {
background-color: #d2d2ff !important; background-color: #d2d2ff !important;
} }
.openerp .oe_form .oe_form_invalid input, .openerp .oe_form .oe_form_invalid select, .openerp .oe_form .oe_form_invalid textarea { .openerp .oe_form .oe_form_invalid input, .openerp .oe_form .oe_form_invalid select, .openerp .oe_form .oe_form_invalid textarea {
@ -2292,16 +2299,16 @@
margin-bottom: 32px; margin-bottom: 32px;
text-align: justify; text-align: justify;
} }
.openerp .oe_form_editable .oe_form .oe_form_field_integer { .openerp .oe_form_editable .oe_form .oe_form_field_integer input {
width: 6em !important; width: 6em !important;
} }
.openerp .oe_form_editable .oe_form .oe_form_field_float { .openerp .oe_form_editable .oe_form .oe_form_field_float input {
width: 7em !important; width: 7em !important;
} }
.openerp .oe_form_editable .oe_form .oe_form_field_date { .openerp .oe_form_editable .oe_form .oe_form_field_date input {
width: 7.5em !important; width: 7.5em !important;
} }
.openerp .oe_form_editable .oe_form .oe_form_field_datetime { .openerp .oe_form_editable .oe_form .oe_form_field_datetime input {
width: 11.5em !important; width: 11.5em !important;
} }
.openerp .oe_hidden_input_file { .openerp .oe_hidden_input_file {

View File

@ -180,9 +180,11 @@ $sheet-max-width: 860px
font-weight: bold font-weight: bold
background-color: #f0f0f0 background-color: #f0f0f0
th th
border-right: 1px dotted $tag-border border-left: 1px solid #dfdfdf
&:last-child &:first-child
border-right: none border-left: none
&.null
border-left: none
th, td th, td
padding: 0 padding: 0
text-align: left text-align: left
@ -450,21 +452,11 @@ $sheet-max-width: 860px
text-decoration: none text-decoration: none
.oe_star_on .oe_star_on
color: gold color: gold
// }}} p.oe_grey
max-width: 650px
.oe_grey
color: #aaa
// Generic blocks {{{
header
position: relative
border-bottom: 1px solid #cacaca
padding-left: 2px
@include vertical-gradient(#fcfcfc, #dedede)
> span
margin-left: 4px
ul
display: inline-block
float: right
.oe_button
margin: 3px 2px 1px
// }}} // }}}
// Tags (for many2many tags, among others) {{{ // Tags (for many2many tags, among others) {{{
@ -825,7 +817,7 @@ $sheet-max-width: 860px
vertical-align: top vertical-align: top
text-shadow: 0 1px 1px rgba(0,0,0,0.2) text-shadow: 0 1px 1px rgba(0,0,0,0.2)
@include transition(all 0.2s ease-out) @include transition(all 0.2s ease-out)
&:hover, &:hover
background: rgba(0,0,0,0.2) background: rgba(0,0,0,0.2)
text-shadow: black 0px 0px 3px text-shadow: black 0px 0px 3px
color: white color: white
@ -885,7 +877,7 @@ $sheet-max-width: 860px
vertical-align: top vertical-align: top
text-shadow: 0 1px 1px rgba(0,0,0,0.2) text-shadow: 0 1px 1px rgba(0,0,0,0.2)
@include transition(all 0.2s ease-out) @include transition(all 0.2s ease-out)
&:hover, &:hover
background: rgba(0,0,0,0.2) background: rgba(0,0,0,0.2)
text-shadow: black 0px 0px 3px text-shadow: black 0px 0px 3px
color: white color: white
@ -1613,6 +1605,22 @@ $sheet-max-width: 860px
.oe_notebook_page .oe_notebook_page
padding: 0 16px padding: 0 16px
// }}} // }}}
// FormView.header {{{
.oe_form header
position: relative
border-bottom: 1px solid #cacaca
padding-left: 2px
@include vertical-gradient(#fcfcfc, #dedede)
> span
margin-left: 4px
ul
display: inline-block
float: right
.oe_button,
margin: 3px 2px 1px
&:first-child
margin-left: 6px
// }}}
// FormView.custom tags and classes {{{ // FormView.custom tags and classes {{{
.oe_form header .oe_tags .oe_form header .oe_tags
margin: 5px 0 0 5px margin: 5px 0 0 5px
@ -1623,10 +1631,6 @@ $sheet-max-width: 860px
max-width: $sheet-max-width max-width: $sheet-max-width
margin: 0 auto margin: 0 auto
padding: 16px 0 48px padding: 16px 0 48px
.oe_grey
color: #aaa
max-width: 650px
margin: 0 0 10px 0
div.oe_form_configuration div.oe_form_configuration
p, ul, ol p, ul, ol
color: #aaa color: #aaa
@ -1780,7 +1784,7 @@ $sheet-max-width: 860px
.oe_datepicker_root .oe_datepicker_root
display: inline-block display: inline-block
.oe_form_required .oe_form_required
input:not([disabled]), select:not([disabled]), textarea:not([disabled]) input:not([disabled]):not([readonly]), select:not([disabled]):not([readonly]), textarea:not([disabled]):not([readonly])
background-color: #D2D2FF !important background-color: #D2D2FF !important
.oe_form_invalid .oe_form_invalid
input, select, textarea input, select, textarea
@ -1821,13 +1825,13 @@ $sheet-max-width: 860px
.oe_form_editable .oe_form_editable
.oe_form .oe_form
.oe_form_field_integer .oe_form_field_integer input
width: 6em !important width: 6em !important
.oe_form_field_float .oe_form_field_float input
width: 7em !important width: 7em !important
.oe_form_field_date .oe_form_field_date input
width: 7.5em !important width: 7.5em !important
.oe_form_field_datetime .oe_form_field_datetime input
width: 11.5em !important width: 11.5em !important
// }}} // }}}
// FormView.fields_binary {{{ // FormView.fields_binary {{{

View File

@ -181,7 +181,7 @@ instance.web.Dialog = instance.web.Widget.extend({
}); });
instance.web.CrashManager = instance.web.CallbackEnabled.extend({ instance.web.CrashManager = instance.web.CallbackEnabled.extend({
on_rpc_error: function(error) { rpc_error: function(error) {
if (error.data.fault_code) { if (error.data.fault_code) {
var split = ("" + error.data.fault_code).split('\n')[0].split(' -- '); var split = ("" + error.data.fault_code).split('\n')[0].split(' -- ');
if (split.length > 1) { if (split.length > 1) {
@ -190,12 +190,12 @@ instance.web.CrashManager = instance.web.CallbackEnabled.extend({
} }
} }
if (error.code === 200 && error.type) { if (error.code === 200 && error.type) {
this.on_managed_error(error); this.show_warning(error);
} else { } else {
this.on_traceback(error); this.show_error(error);
} }
}, },
on_managed_error: function(error) { show_warning: function(error) {
instance.web.dialog($('<div>' + QWeb.render('CrashManager.warning', {error: error}) + '</div>'), { instance.web.dialog($('<div>' + QWeb.render('CrashManager.warning', {error: error}) + '</div>'), {
title: "OpenERP " + _.str.capitalize(error.type), title: "OpenERP " + _.str.capitalize(error.type),
buttons: [ buttons: [
@ -203,7 +203,7 @@ instance.web.CrashManager = instance.web.CallbackEnabled.extend({
] ]
}); });
}, },
on_traceback: function(error) { show_error: function(error) {
var self = this; var self = this;
var buttons = {}; var buttons = {};
buttons[_t("Ok")] = function() { buttons[_t("Ok")] = function() {
@ -219,8 +219,8 @@ instance.web.CrashManager = instance.web.CallbackEnabled.extend({
}).open(); }).open();
dialog.$el.html(QWeb.render('CrashManager.error', {session: instance.session, error: error})); dialog.$el.html(QWeb.render('CrashManager.error', {session: instance.session, error: error}));
}, },
on_javascript_exception: function(exception) { show_message: function(exception) {
this.on_traceback({ this.show_error({
type: _t("Client Error"), type: _t("Client Error"),
message: exception, message: exception,
data: {debug: ""} data: {debug: ""}
@ -236,7 +236,7 @@ instance.web.Loading = instance.web.Widget.extend({
this.blocked_ui = false; this.blocked_ui = false;
this.session.on("request", this, this.request_call); this.session.on("request", this, this.request_call);
this.session.on("response", this, this.response_call); this.session.on("response", this, this.response_call);
this.session.on("error", this, this.response_call); this.session.on("response_failed", this, this.response_call);
}, },
destroy: function() { destroy: function() {
this.on_rpc_event(-this.count); this.on_rpc_event(-this.count);
@ -392,7 +392,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
do_create: function(form) { do_create: function(form) {
var self = this; var self = this;
var fields = $(form).serializeArray(); var fields = $(form).serializeArray();
self.rpc("/web/database/create", {'fields': fields}, function(result) { self.rpc("/web/database/create", {'fields': fields}).then(function(result) {
var form_obj = self.to_object(fields); var form_obj = self.to_object(fields);
var client_action = { var client_action = {
type: 'ir.actions.client', type: 'ir.actions.client',
@ -418,7 +418,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
if (!db || !confirm("Do you really want to delete the database: " + db + " ?")) { if (!db || !confirm("Do you really want to delete the database: " + db + " ?")) {
return; return;
} }
self.rpc("/web/database/drop", {'fields': fields}, function(result) { self.rpc("/web/database/drop", {'fields': fields}).then(function(result) {
if (result.error) { if (result.error) {
self.display_error(result); self.display_error(result);
return; return;
@ -481,7 +481,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
var self = this; var self = this;
self.rpc("/web/database/change_password", { self.rpc("/web/database/change_password", {
'fields': $(form).serializeArray() 'fields': $(form).serializeArray()
}, function(result) { }).then(function(result) {
if (result.error) { if (result.error) {
self.display_error(result); self.display_error(result);
return; return;
@ -627,6 +627,7 @@ instance.web.Reload = function(parent, params) {
hash = "#menu_id=" + menu_id; hash = "#menu_id=" + menu_id;
} }
var url = l.protocol + "//" + l.host + l.pathname + search + hash; var url = l.protocol + "//" + l.host + l.pathname + search + hash;
window.onerror = function() {};
window.location = url; window.location = url;
}; };
instance.web.client_actions.add("reload", "instance.web.Reload"); instance.web.client_actions.add("reload", "instance.web.Reload");
@ -660,7 +661,7 @@ instance.web.ChangePassword = instance.web.Widget.extend({
submitHandler: function (form) { submitHandler: function (form) {
self.rpc("/web/session/change_password",{ self.rpc("/web/session/change_password",{
'fields': $(form).serializeArray() 'fields': $(form).serializeArray()
}, function(result) { }).then(function(result) {
if (result.error) { if (result.error) {
self.display_error(result); self.display_error(result);
return; return;
@ -881,6 +882,7 @@ instance.web.UserMenu = instance.web.Widget.extend({
on_action: function() { on_action: function() {
}, },
on_menu_logout: function() { on_menu_logout: function() {
this.trigger('user_logout');
}, },
on_menu_settings: function() { on_menu_settings: function() {
var self = this; var self = this;
@ -957,7 +959,7 @@ instance.web.Client = instance.web.Widget.extend({
show_common: function() { show_common: function() {
var self = this; var self = this;
this.crashmanager = new instance.web.CrashManager(); this.crashmanager = new instance.web.CrashManager();
instance.session.on_rpc_error.add(this.crashmanager.on_rpc_error); instance.session.on('error', this.crashmanager, this.crashmanager.rpc_error);
self.notification = new instance.web.Notification(this); self.notification = new instance.web.Notification(this);
self.notification.appendTo(self.$el); self.notification.appendTo(self.$el);
self.loading = new instance.web.Loading(self); self.loading = new instance.web.Loading(self);
@ -1007,7 +1009,7 @@ instance.web.WebClient = instance.web.Client.extend({
var self = this; var self = this;
this._super(); this._super();
window.onerror = function (message, file, line) { window.onerror = function (message, file, line) {
self.crashmanager.on_traceback({ self.crashmanager.show_error({
type: _t("Client Error"), type: _t("Client Error"),
message: message, message: message,
data: {debug: file + ':' + line} data: {debug: file + ':' + line}
@ -1016,15 +1018,13 @@ instance.web.WebClient = instance.web.Client.extend({
}, },
show_login: function() { show_login: function() {
this.toggle_bars(false); this.toggle_bars(false);
var state = $.bbq.getState(true);
var action = { var action = {
'type': 'ir.actions.client', 'type': 'ir.actions.client',
'tag': 'login' 'tag': 'login',
'params': state
}; };
var state = $.bbq.getState(true);
if (state.action === "login") {
action.params = state;
}
this.action_manager.do_action(action); this.action_manager.do_action(action);
this.action_manager.inner_widget.on('login_successful', this, function() { this.action_manager.inner_widget.on('login_successful', this, function() {
@ -1041,7 +1041,7 @@ instance.web.WebClient = instance.web.Client.extend({
self.menu.on('menu_click', this, this.on_menu_action); self.menu.on('menu_click', this, this.on_menu_action);
self.user_menu = new instance.web.UserMenu(self); self.user_menu = new instance.web.UserMenu(self);
self.user_menu.replace(this.$el.find('.oe_user_menu_placeholder')); self.user_menu.replace(this.$el.find('.oe_user_menu_placeholder'));
self.user_menu.on_menu_logout.add(this.proxy('on_logout')); self.user_menu.on('user_logout', self, this.proxy('on_logout'));
self.user_menu.on_action.add(this.proxy('on_menu_action')); self.user_menu.on_action.add(this.proxy('on_menu_action'));
self.user_menu.do_update(); self.user_menu.do_update();
self.bind_hashchange(); self.bind_hashchange();
@ -1165,7 +1165,7 @@ instance.web.EmbeddedClient = instance.web.Client.extend({
var self = this; var self = this;
return $.when(this._super()).pipe(function() { return $.when(this._super()).pipe(function() {
return instance.session.session_authenticate(self.dbname, self.login, self.key, true).pipe(function() { return instance.session.session_authenticate(self.dbname, self.login, self.key, true).pipe(function() {
return self.rpc("/web/action/load", { action_id: self.action_id }, function(result) { return self.rpc("/web/action/load", { action_id: self.action_id }).then(function(result) {
var action = result; var action = result;
action.flags = _.extend({ action.flags = _.extend({
//views_switcher : false, //views_switcher : false,

View File

@ -121,6 +121,10 @@ openerp.web.corelib = function(instance) {
// The dummy class constructor // The dummy class constructor
function Class() { function Class() {
if(this.constructor !== instance.web.Class){
throw new Error("You can only instanciate objects with the 'new' operator");
return null;
}
// All construction is actually done in the init method // All construction is actually done in the init method
if (!initializing && this.init) { if (!initializing && this.init) {
var ret = this.init.apply(this, arguments); var ret = this.init.apply(this, arguments);
@ -842,7 +846,7 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
rpc: function(url, data, success, error) { rpc: function(url, data, success, error) {
var def = $.Deferred().then(success, error); var def = $.Deferred().then(success, error);
var self = this; var self = this;
instance.session.rpc(url, data). then(function() { instance.session.rpc(url, data).then(function() {
if (!self.isDestroyed()) if (!self.isDestroyed())
def.resolve.apply(def, arguments); def.resolve.apply(def, arguments);
}, function() { }, function() {
@ -976,7 +980,8 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
triggers: { triggers: {
'request': 'Request sent', 'request': 'Request sent',
'response': 'Response received', 'response': 'Response received',
'error': 'HTTP Error response or timeout received', 'response_failed': 'HTTP Error response or timeout received',
'error': 'The received response is an JSON-RPC error',
}, },
/** /**
* @constructs instance.web.JsonRPC * @constructs instance.web.JsonRPC
@ -1300,7 +1305,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
* @param {Function} error_callback function to execute on RPC call failure * @param {Function} error_callback function to execute on RPC call failure
* @returns {jQuery.Deferred} jquery-provided ajax deferred * @returns {jQuery.Deferred} jquery-provided ajax deferred
*/ */
rpc: function(url, params, success_callback, error_callback) { rpc: function(url, params) {
var self = this; var self = this;
// url can be an $.ajax option object // url can be an $.ajax option object
if (_.isString(url)) { if (_.isString(url)) {
@ -1317,8 +1322,6 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
}; };
var deferred = $.Deferred(); var deferred = $.Deferred();
this.trigger('request', url, payload); this.trigger('request', url, payload);
var aborter = params.aborter;
delete params.aborter;
var request = this.rpc_function(url, payload).then( var request = this.rpc_function(url, payload).then(
function (response, textStatus, jqXHR) { function (response, textStatus, jqXHR) {
self.trigger('response', response); self.trigger('response', response);
@ -1334,7 +1337,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
} }
}, },
function(jqXHR, textStatus, errorThrown) { function(jqXHR, textStatus, errorThrown) {
self.trigger('error'); self.trigger('response_failed', jqXHR);
var error = { var error = {
code: -32098, code: -32098,
message: "XmlHttpRequestError " + errorThrown, message: "XmlHttpRequestError " + errorThrown,
@ -1342,24 +1345,14 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
}; };
deferred.reject(error, $.Event()); deferred.reject(error, $.Event());
}); });
if (aborter) {
aborter.abort_last = function () {
if (!(request.isResolved() || request.isRejected())) {
deferred.fail(function (error, event) {
event.preventDefault();
});
request.abort();
}
};
}
// Allow deferred user to disable on_rpc_error in fail // Allow deferred user to disable on_rpc_error in fail
deferred.fail(function() { deferred.fail(function() {
deferred.fail(function(error, event) { deferred.fail(function(error, event) {
if (!event.isDefaultPrevented()) { if (!event.isDefaultPrevented()) {
self.on_rpc_error(error, event); self.trigger('error', error, event);
} }
}); });
}).then(success_callback, error_callback).promise(); });
return deferred; return deferred;
}, },
/** /**
@ -1442,13 +1435,15 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
return deferred; return deferred;
} }
}, },
on_rpc_error: function(error) {
},
get_url: function (file) { get_url: function (file) {
return this.prefix + file; return this.prefix + file;
}, },
}); });
instance.web.py_eval = function(expr, context) {
return py.eval(expr, _.extend({}, context || {}, {"true": true, "false": false, "null": null}));
};
} }
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:

View File

@ -19,9 +19,9 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
this.name = instance._session_id; this.name = instance._session_id;
this.qweb_mutex = new $.Mutex(); this.qweb_mutex = new $.Mutex();
}, },
rpc: function(url, params, success_callback, error_callback) { rpc: function(url, params) {
params.session_id = this.session_id; params.session_id = this.session_id;
return this._super(url, params, success_callback, error_callback); return this._super(url, params);
}, },
/** /**
* Setup a sessionm * Setup a sessionm

View File

@ -575,6 +575,7 @@ instance.web.DataSet = instance.web.CallbackEnabled.extend({
* @param {Number|String} ids identifier of the record to delete * @param {Number|String} ids identifier of the record to delete
*/ */
unlink: function(ids) { unlink: function(ids) {
this.trigger('unlink', ids);
return this._model.call('unlink', [ids], {context: this._model.context()}); return this._model.call('unlink', [ids], {context: this._model.context()});
}, },
/** /**
@ -586,26 +587,8 @@ instance.web.DataSet = instance.web.CallbackEnabled.extend({
* @param {Function} error_callback * @param {Function} error_callback
* @returns {$.Deferred} * @returns {$.Deferred}
*/ */
call: function (method, args, callback, error_callback) { call: function (method, args) {
return this._model.call(method, args).then(callback, error_callback); return this._model.call(method, args);
},
/**
* Calls an arbitrary method, with more crazy
*
* @param {String} method
* @param {Array} [args]
* @param {Number} [domain_index] index of a domain to evaluate in the args array
* @param {Number} [context_index] index of a context to evaluate in the args array
* @returns {$.Deferred}
*/
call_and_eval: function (method, args, domain_index, context_index) {
return instance.session.rpc('/web/dataset/call', {
model: this.model,
method: method,
domain_id: domain_index == undefined ? null : domain_index,
context_id: context_index == undefined ? null : context_index,
args: args || []
});
}, },
/** /**
* Calls a button method, usually returning some sort of action * Calls a button method, usually returning some sort of action
@ -708,6 +691,7 @@ instance.web.DataSet = instance.web.CallbackEnabled.extend({
instance.web.DataSetStatic = instance.web.DataSet.extend({ instance.web.DataSetStatic = instance.web.DataSet.extend({
init: function(parent, model, context, ids) { init: function(parent, model, context, ids) {
var self = this;
this._super(parent, model, context); this._super(parent, model, context);
// all local records // all local records
this.ids = ids || []; this.ids = ids || [];
@ -729,12 +713,10 @@ instance.web.DataSetStatic = instance.web.DataSet.extend({
} }
}, },
unlink: function(ids) { unlink: function(ids) {
this.on_unlink(ids); this.set_ids(_.without.apply(null, [this.ids].concat(ids)));
this.trigger('unlink', ids);
return $.Deferred().resolve({result: true}); return $.Deferred().resolve({result: true});
}, },
on_unlink: function(ids) {
this.set_ids(_.without.apply(null, [this.ids].concat(ids)));
}
}); });
instance.web.DataSetSearch = instance.web.DataSet.extend({ instance.web.DataSetSearch = instance.web.DataSet.extend({

View File

@ -51,7 +51,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
self.rpc("/web/export/get_fields", { self.rpc("/web/export/get_fields", {
model: self.dataset.model, model: self.dataset.model,
import_compat: Boolean(import_comp) import_compat: Boolean(import_comp)
}, function (records) { }).then(function (records) {
got_fields.resolve(); got_fields.resolve();
self.on_show_data(records); self.on_show_data(records);
}); });
@ -59,7 +59,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
return $.when( return $.when(
got_fields, got_fields,
this.rpc('/web/export/formats', {}, this.do_setup_export_formats), this.rpc('/web/export/formats', {}).then(this.do_setup_export_formats),
this.show_exports_list()); this.show_exports_list());
}, },
do_setup_export_formats: function (formats) { do_setup_export_formats: function (formats) {
@ -93,7 +93,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
self.$el.find('#fields_list option').remove(); self.$el.find('#fields_list option').remove();
var export_id = self.$el.find('#saved_export_list option:selected').val(); var export_id = self.$el.find('#saved_export_list option:selected').val();
if (export_id) { if (export_id) {
self.rpc('/web/export/namelist', {'model': self.dataset.model, export_id: parseInt(export_id)}, self.do_load_export_field); self.rpc('/web/export/namelist', {'model': self.dataset.model, export_id: parseInt(export_id)}).then(self.do_load_export_field);
} }
}); });
self.$el.find('#delete_export_list').click(function() { self.$el.find('#delete_export_list').click(function() {
@ -183,7 +183,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
import_compat: Boolean(import_comp), import_compat: Boolean(import_comp),
parent_field_type : record['field_type'], parent_field_type : record['field_type'],
exclude: exclude_fields exclude: exclude_fields
}, function(results) { }).then(function(results) {
record.loaded = true; record.loaded = true;
self.on_show_data(results, record.id); self.on_show_data(results, record.id);
}); });

View File

@ -1693,7 +1693,7 @@ instance.web.search.Advanced = instance.web.search.Input.extend({
}); });
return $.when( return $.when(
this._super(), this._super(),
this.rpc("/web/searchview/fields_get", {model: this.view.model}, function(data) { this.rpc("/web/searchview/fields_get", {model: this.view.model}).then(function(data) {
self.fields = _.extend({ self.fields = _.extend({
id: { string: 'ID', type: 'id' } id: { string: 'ID', type: 'id' }
}, data.fields); }, data.fields);

View File

@ -126,6 +126,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
self.on("change:actual_mode", self, self.init_pager); self.on("change:actual_mode", self, self.init_pager);
self.init_pager(); self.init_pager();
}); });
self.on("load_record", self, self.load_record);
instance.web.bus.on('clear_uncommitted_changes', this, function(e) { instance.web.bus.on('clear_uncommitted_changes', this, function(e) {
if (!this.can_be_discarded()) { if (!this.can_be_discarded()) {
e.preventDefault(); e.preventDefault();
@ -172,10 +173,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
} else { } else {
this.$el.find('.oe_form_buttons').replaceWith(this.$buttons); this.$el.find('.oe_form_buttons').replaceWith(this.$buttons);
} }
this.$buttons.on('click','.oe_form_button_create',this.on_button_create); this.$buttons.on('click', '.oe_form_button_create', this.on_button_create);
this.$buttons.on('click','.oe_form_button_edit',this.on_button_edit); this.$buttons.on('click', '.oe_form_button_edit', this.on_button_edit);
this.$buttons.on('click','.oe_form_button_save',this.on_button_save); this.$buttons.on('click', '.oe_form_button_save', this.on_button_save);
this.$buttons.on('click','.oe_form_button_cancel',this.on_button_cancel); this.$buttons.on('click', '.oe_form_button_cancel', this.on_button_cancel);
this.$sidebar = this.options.$sidebar || this.$el.find('.oe_form_sidebar'); this.$sidebar = this.options.$sidebar || this.$el.find('.oe_form_sidebar');
if (!this.sidebar && this.options.$sidebar) { if (!this.sidebar && this.options.$sidebar) {
@ -331,7 +332,9 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
fields.push('display_name'); fields.push('display_name');
return self.dataset.read_index(fields, { return self.dataset.read_index(fields, {
context: { 'bin_size': true, 'future_display_name' : true } context: { 'bin_size': true, 'future_display_name' : true }
}).pipe(self.on_record_loaded); }).pipe(function(r) {
self.trigger('load_record', r);
});
}); });
} }
return shown.pipe(function() { return shown.pipe(function() {
@ -354,7 +357,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
} }
this._super(); this._super();
}, },
on_record_loaded: function(record) { load_record: function(record) {
var self = this, set_values = []; var self = this, set_values = [];
if (!record) { if (!record) {
this.set({ 'title' : undefined }); this.set({ 'title' : undefined });
@ -413,12 +416,14 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
* @return {$.Deferred} * @return {$.Deferred}
*/ */
load_defaults: function () { load_defaults: function () {
var self = this;
var keys = _.keys(this.fields_view.fields); var keys = _.keys(this.fields_view.fields);
if (keys.length) { if (keys.length) {
return this.dataset.default_get(keys) return this.dataset.default_get(keys).pipe(function(r) {
.pipe(this.on_record_loaded); self.trigger('load_record', r);
});
} }
return this.on_record_loaded({}); return self.trigger('load_record', {});
}, },
on_form_changed: function() { on_form_changed: function() {
this.trigger("view_content_has_changed"); this.trigger("view_content_has_changed");
@ -426,7 +431,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
do_notify_change: function() { do_notify_change: function() {
this.$el.add(this.$buttons).addClass('oe_form_dirty'); this.$el.add(this.$buttons).addClass('oe_form_dirty');
}, },
on_pager_action: function(action) { execute_pager_action: function(action) {
if (this.can_be_discarded()) { if (this.can_be_discarded()) {
switch (action) { switch (action) {
case 'first': case 'first':
@ -443,6 +448,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
break; break;
} }
this.reload(); this.reload();
this.trigger('pager_action_executed');
} }
}, },
init_pager: function() { init_pager: function() {
@ -459,7 +465,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
} }
this.$pager.on('click','a[data-pager-action]',function() { this.$pager.on('click','a[data-pager-action]',function() {
var action = $(this).data('pager-action'); var action = $(this).data('pager-action');
self.on_pager_action(action); self.execute_pager_action(action);
}); });
this.do_update_pager(); this.do_update_pager();
}, },
@ -610,7 +616,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
return self.on_processed_onchange(response, processed); return self.on_processed_onchange(response, processed);
} catch(e) { } catch(e) {
console.error(e); console.error(e);
instance.webclient.crashmanager.on_javascript_exception(e); instance.webclient.crashmanager.show_message(e);
return $.Deferred().reject(); return $.Deferred().reject();
} }
}); });
@ -645,7 +651,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
return $.Deferred().resolve(); return $.Deferred().resolve();
} catch(e) { } catch(e) {
console.error(e); console.error(e);
instance.webclient.crashmanager.on_javascript_exception(e); instance.webclient.crashmanager.show_message(e);
return $.Deferred().reject(); return $.Deferred().reject();
} }
}, },
@ -742,7 +748,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
}, },
on_button_save: function() { on_button_save: function() {
var self = this; var self = this;
return this.do_save().then(function(result) { return this.save().then(function(result) {
self.trigger("save");
self.to_view_mode(); self.to_view_mode();
}); });
}, },
@ -752,9 +759,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
this.trigger('history_back'); this.trigger('history_back');
} else { } else {
this.to_view_mode(); this.to_view_mode();
this.on_record_loaded(this.datarecord); this.trigger('load_record', this.datarecord);
} }
} }
this.trigger('on_button_cancel');
return false; return false;
}, },
on_button_new: function() { on_button_new: function() {
@ -778,7 +786,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
var def = $.Deferred(); var def = $.Deferred();
$.when(this.has_been_loaded).then(function() { $.when(this.has_been_loaded).then(function() {
self.dataset.call('copy', [self.datarecord.id, {}, self.dataset.context]).then(function(new_id) { self.dataset.call('copy', [self.datarecord.id, {}, self.dataset.context]).then(function(new_id) {
return self.on_created({ result : new_id }); return self.record_created(new_id);
}).then(function() { }).then(function() {
return self.to_edit_mode(); return self.to_edit_mode();
}).then(function() { }).then(function() {
@ -793,7 +801,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
$.when(this.has_been_loaded).then(function() { $.when(this.has_been_loaded).then(function() {
if (self.datarecord.id && confirm(_t("Do you really want to delete this record?"))) { if (self.datarecord.id && confirm(_t("Do you really want to delete this record?"))) {
self.dataset.unlink([self.datarecord.id]).then(function() { self.dataset.unlink([self.datarecord.id]).then(function() {
self.on_pager_action('next'); self.execute_pager_action('next');
def.resolve(); def.resolve();
}); });
} else { } else {
@ -818,11 +826,11 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
* record or saving an existing one depending on whether the record * record or saving an existing one depending on whether the record
* already has an id property. * already has an id property.
* *
* @param {Boolean} [prepend_on_create=false] if ``do_save`` creates a new * @param {Boolean} [prepend_on_create=false] if ``save`` creates a new
* record, should that record be inserted at the start of the dataset (by * record, should that record be inserted at the start of the dataset (by
* default, records are added at the end) * default, records are added at the end)
*/ */
do_save: function(prepend_on_create) { save: function(prepend_on_create) {
var self = this; var self = this;
return this.mutating_mutex.exec(function() { return self.is_initialized.pipe(function() { return this.mutating_mutex.exec(function() { return self.is_initialized.pipe(function() {
try { try {
@ -846,10 +854,6 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
} }
if (form_invalid) { if (form_invalid) {
self.set({'display_invalid_fields': true}); self.set({'display_invalid_fields': true});
for (var g in self.fields) {
if (!self.fields.hasOwnProperty(g)) { continue; }
self.fields[g]._check_css_flags();
}
first_invalid_field.focus(); first_invalid_field.focus();
self.on_invalid(); self.on_invalid();
return $.Deferred().reject(); return $.Deferred().reject();
@ -859,7 +863,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
if (!self.datarecord.id) { if (!self.datarecord.id) {
// Creation save // Creation save
save_deferral = self.dataset.create(values).pipe(function(r) { save_deferral = self.dataset.create(values).pipe(function(r) {
return self.on_created(r, prepend_on_create); return self.record_created(r, prepend_on_create);
}, null); }, null);
} else if (_.isEmpty(values) && ! self.force_dirty) { } else if (_.isEmpty(values) && ! self.force_dirty) {
// Not dirty, noop save // Not dirty, noop save
@ -868,7 +872,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
self.force_dirty = false; self.force_dirty = false;
// Write save // Write save
save_deferral = self.dataset.write(self.datarecord.id, values, {}).pipe(function(r) { save_deferral = self.dataset.write(self.datarecord.id, values, {}).pipe(function(r) {
return self.on_saved(r); return self.record_saved(r);
}, null); }, null);
} }
return save_deferral; return save_deferral;
@ -895,12 +899,15 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
* *
* @param {Object} r result of the write function. * @param {Object} r result of the write function.
*/ */
on_saved: function(r) { record_saved: function(r) {
var self = this;
if (!r) { if (!r) {
// should not happen in the server, but may happen for internal purpose // should not happen in the server, but may happen for internal purpose
this.trigger('record_saved', r);
return $.Deferred().reject(); return $.Deferred().reject();
} else { } else {
return $.when(this.reload()).pipe(function () { return $.when(this.reload()).pipe(function () {
self.trigger('record_saved', r);
return r; return r;
}); });
} }
@ -918,9 +925,11 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
* @param {Boolean} [prepend_on_create=false] adds the newly created record * @param {Boolean} [prepend_on_create=false] adds the newly created record
* at the beginning of the dataset instead of the end * at the beginning of the dataset instead of the end
*/ */
on_created: function(r, prepend_on_create) { record_created: function(r, prepend_on_create) {
var self = this;
if (!r) { if (!r) {
// should not happen in the server, but may happen for internal purpose // should not happen in the server, but may happen for internal purpose
this.trigger('record_created', r);
return $.Deferred().reject(); return $.Deferred().reject();
} else { } else {
this.datarecord.id = r; this.datarecord.id = r;
@ -934,9 +943,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
this.do_update_pager(); this.do_update_pager();
if (this.sidebar) { if (this.sidebar) {
this.sidebar.do_attachement_update(this.dataset, this.datarecord.id); this.sidebar.do_attachement_update(this.dataset, this.datarecord.id);
} }
//openerp.log("The record has been created with id #" + this.datarecord.id); //openerp.log("The record has been created with id #" + this.datarecord.id);
return $.when(this.reload()).pipe(function () { return $.when(this.reload()).pipe(function () {
self.trigger('record_created', r);
return _.extend(r, {created: true}); return _.extend(r, {created: true});
}); });
} }
@ -948,7 +958,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
var self = this; var self = this;
return this.reload_mutex.exec(function() { return this.reload_mutex.exec(function() {
if (self.dataset.index == null) { if (self.dataset.index == null) {
self.do_prev_view(); self.trigger("previous_view");
return $.Deferred().reject().promise(); return $.Deferred().reject().promise();
} }
if (self.dataset.index == null || self.dataset.index < 0) { if (self.dataset.index == null || self.dataset.index < 0) {
@ -956,9 +966,15 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
} else { } else {
var fields = _.keys(self.fields_view.fields); var fields = _.keys(self.fields_view.fields);
fields.push('display_name'); fields.push('display_name');
return self.dataset.read_index(fields, { return self.dataset.read_index(fields,
context : { 'bin_size' : true, 'future_display_name' : true } {
}).pipe(self.on_record_loaded); context: {
'bin_size': true,
'future_display_name': true
}
}).pipe(function(r) {
self.trigger('load_record', r);
});
} }
}); });
}, },
@ -986,7 +1002,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
}, },
recursive_save: function() { recursive_save: function() {
var self = this; var self = this;
return $.when(this.do_save()).pipe(function(res) { return $.when(this.save()).pipe(function(res) {
if (self.dataset.parent_view) if (self.dataset.parent_view)
return self.dataset.parent_view.recursive_save(); return self.dataset.parent_view.recursive_save();
}); });
@ -1017,7 +1033,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
return true; return true;
}, },
sidebar_context: function () { sidebar_context: function () {
return this.do_save().pipe(_.bind(function() {return this.get_fields_values();}, this)); return this.save().pipe(_.bind(function() {return this.get_fields_values();}, this));
}, },
open_defaults_dialog: function () { open_defaults_dialog: function () {
var self = this; var self = this;
@ -1082,7 +1098,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
field_to_set, field_to_set,
self.fields[field_to_set].get_value(), self.fields[field_to_set].get_value(),
all_users, all_users,
false, true,
condition || false condition || false
]).then(function () { d.close(); }); ]).then(function () { d.close(); });
}} }}
@ -1212,7 +1228,7 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
this.$form.appendTo(this.$target); this.$form.appendTo(this.$target);
_.each(this.fields_to_init, function($elem) { var ws = _.map(this.fields_to_init, function($elem) {
var name = $elem.attr("name"); var name = $elem.attr("name");
if (!self.fvg.fields[name]) { if (!self.fvg.fields[name]) {
throw new Error("Field '" + name + "' specified in view could not be found."); throw new Error("Field '" + name + "' specified in view could not be found.");
@ -1228,7 +1244,10 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
} }
self.alter_field(w); self.alter_field(w);
self.view.register_field(w, $elem.attr("name")); self.view.register_field(w, $elem.attr("name"));
w.replace($elem); return [w, $elem];
});
_.each(ws, function(w) {
w[0].replace(w[1]);
}); });
_.each(this.tags_to_init, function($elem) { _.each(this.tags_to_init, function($elem) {
var tag_name = $elem[0].tagName.toLowerCase(); var tag_name = $elem[0].tagName.toLowerCase();
@ -1619,13 +1638,14 @@ instance.web.form.FormDialog = instance.web.Dialog.extend({
return this; return this;
}, },
start: function() { start: function() {
var self = this;
this._super(); this._super();
this.form = new instance.web.FormView(this, this.dataset, this.view_id, { this.form = new instance.web.FormView(this, this.dataset, this.view_id, {
pager: false pager: false
}); });
this.form.appendTo(this.$el); this.form.appendTo(this.$el);
this.form.on_created.add_last(this.on_form_dialog_saved); this.form.on('record_created', self, this.on_form_dialog_saved);
this.form.on_saved.add_last(this.on_form_dialog_saved); this.form.on('record_saved', this, this.on_form_dialog_saved);
return this; return this;
}, },
select_id: function(id) { select_id: function(id) {
@ -1642,6 +1662,8 @@ instance.web.form.FormDialog = instance.web.Dialog.extend({
}); });
instance.web.form.compute_domain = function(expr, fields) { instance.web.form.compute_domain = function(expr, fields) {
if (! (expr instanceof Array))
return !! expr;
var stack = []; var stack = [];
for (var i = expr.length - 1; i >= 0; i--) { for (var i = expr.length - 1; i >= 0; i--) {
var ex = expr[i]; var ex = expr[i];
@ -2069,7 +2091,7 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w
this.field = this.field_manager.get_field_desc(this.name); this.field = this.field_manager.get_field_desc(this.name);
this.widget = this.node.attrs.widget; this.widget = this.node.attrs.widget;
this.string = this.node.attrs.string || this.field.string || this.name; this.string = this.node.attrs.string || this.field.string || this.name;
this.options = JSON.parse(this.node.attrs.options || '{}'); this.options = instance.web.py_eval(this.node.attrs.options || '{}');
this.set({'value': false}); this.set({'value': false});
this.on("change:value", this, function() { this.on("change:value", this, function() {
@ -2099,6 +2121,8 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w
this._set_required(); this._set_required();
} }
this._check_visibility(); this._check_visibility();
this.field_manager.off("change:display_invalid_fields", this, this._check_css_flags);
this.field_manager.on("change:display_invalid_fields", this, this._check_css_flags);
this._check_css_flags(); this._check_css_flags();
}, },
/** /**
@ -2164,11 +2188,12 @@ instance.web.form.ReinitializeWidgetMixin = {
this.initialize_field(); this.initialize_field();
}, },
initialize_field: function() { initialize_field: function() {
this.on("change:effective_readonly", this, function() { this.on("change:effective_readonly", this, this.reinitialize);
this.destroy_content(); this.initialize_content();
this.renderElement(); },
this.initialize_content(); reinitialize: function() {
}); this.destroy_content();
this.renderElement();
this.initialize_content(); this.initialize_content();
}, },
/** /**
@ -2189,9 +2214,10 @@ instance.web.form.ReinitializeWidgetMixin = {
instance.web.form.ReinitializeFieldMixin = _.extend({}, instance.web.form.ReinitializeWidgetMixin, { instance.web.form.ReinitializeFieldMixin = _.extend({}, instance.web.form.ReinitializeWidgetMixin, {
initialize_field: function() { initialize_field: function() {
instance.web.form.ReinitializeWidgetMixin.initialize_field.call(this); instance.web.form.ReinitializeWidgetMixin.initialize_field.call(this);
this.on("change:effective_readonly", this, function() { this.render_value();
this.render_value(); },
}); reinitialize: function() {
instance.web.form.ReinitializeWidgetMixin.reinitialize.call(this);
this.render_value(); this.render_value();
}, },
/** /**
@ -2211,7 +2237,7 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.we
var self = this; var self = this;
var $input = this.$el.find('input'); var $input = this.$el.find('input');
$input.change(function() { $input.change(function() {
self.set({'value': instance.web.parse_value($input.val(), self)}); self.set({'value': self.parse_value($input.val())});
}); });
this.setupFocus($input); this.setupFocus($input);
}, },
@ -2220,20 +2246,20 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.we
this.render_value(); this.render_value();
}, },
render_value: function() { render_value: function() {
var show_value = instance.web.format_value(this.get('value'), this, ''); var show_value = this.format_value(this.get('value'), '');
if (!this.get("effective_readonly")) { if (!this.get("effective_readonly")) {
this.$el.find('input').val(show_value); this.$el.find('input').val(show_value);
} else { } else {
if (this.password) { if (this.password) {
show_value = new Array(show_value.length + 1).join('*'); show_value = new Array(show_value.length + 1).join('*');
} }
this.$el.text(show_value); this.$(".oe_form_char_content").text(show_value);
} }
}, },
is_syntax_valid: function() { is_syntax_valid: function() {
if (!this.get("effective_readonly")) { if (!this.get("effective_readonly")) {
try { try {
var value_ = instance.web.parse_value(this.$el.find('input').val(), this, ''); var value_ = this.parse_value(this.$el.find('input').val(), '');
return true; return true;
} catch(e) { } catch(e) {
return false; return false;
@ -2241,6 +2267,12 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.we
} }
return true; return true;
}, },
parse_value: function(val, def) {
return instance.web.parse_value(val, this, def);
},
format_value: function(val, def) {
return instance.web.format_value(val, this, def);
},
is_false: function() { is_false: function() {
return this.get('value') === '' || this._super(); return this.get('value') === '' || this._super();
}, },
@ -2347,7 +2379,10 @@ instance.web.DateTimeWidget = instance.web.Widget.extend({
var self = this; var self = this;
this.$input = this.$el.find('input.oe_datepicker_master'); this.$input = this.$el.find('input.oe_datepicker_master');
this.$input_picker = this.$el.find('input.oe_datepicker_container'); this.$input_picker = this.$el.find('input.oe_datepicker_container');
this.$input.change(this.on_change); this.$input.change(function(){
self.datetime_changed();
});
this.picker({ this.picker({
onClose: this.on_picker_select, onClose: this.on_picker_select,
onSelect: this.on_picker_select, onSelect: this.on_picker_select,
@ -2415,9 +2450,10 @@ instance.web.DateTimeWidget = instance.web.Widget.extend({
format_client: function(v) { format_client: function(v) {
return instance.web.format_value(v, {"widget": this.type_of_date}); return instance.web.format_value(v, {"widget": this.type_of_date});
}, },
on_change: function() { datetime_changed: function() {
if (this.is_valid_()) { if (this.is_valid_()) {
this.set_value_from_ui_(); this.set_value_from_ui_();
this.trigger("datetime_changed");
} }
} }
}); });
@ -2441,7 +2477,7 @@ instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(instanc
initialize_content: function() { initialize_content: function() {
if (!this.get("effective_readonly")) { if (!this.get("effective_readonly")) {
this.datewidget = this.build_widget(); this.datewidget = this.build_widget();
this.datewidget.on_change.add_last(_.bind(function() { this.datewidget.on('datetime_changed', this, _.bind(function() {
this.set({'value': this.datewidget.get_value()}); this.set({'value': this.datewidget.get_value()});
}, this)); }, this));
this.datewidget.appendTo(this.$el); this.datewidget.appendTo(this.$el);
@ -2845,7 +2881,7 @@ instance.web.form.CompletionFieldMixin = {
self.build_domain(), self.build_domain(),
new instance.web.CompoundContext(self.build_context(), context || {}) new instance.web.CompoundContext(self.build_context(), context || {})
); );
pop.on_select_elements.add(function(element_ids) { pop.on("select_elements",self,function(element_ids) {
self.add_id(element_ids[0]); self.add_id(element_ids[0]);
self.focus(); self.focus();
}); });
@ -2958,7 +2994,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
title: _t("Open: ") + self.string title: _t("Open: ") + self.string
} }
); );
pop.on_write_completed.add_last(function() { pop.on('write_completed', self, function(){
self.display_value = {}; self.display_value = {};
self.render_value(); self.render_value();
self.focus(); self.focus();
@ -3284,11 +3320,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
var views = []; var views = [];
_.each(modes, function(mode) { _.each(modes, function(mode) {
if (! _.include(["list", "tree", "graph", "kanban"], mode)) { if (! _.include(["list", "tree", "graph", "kanban"], mode)) {
try { throw new Error(_.str.sprintf("View type '%s' is not supported in One2Many.", mode));
throw new Error(_.str.sprintf("View type '%s' is not supported in One2Many.", mode));
} catch(e) {
instance.webclient.crashmanager.on_javascript_exception(e)
}
} }
var view = { var view = {
view_id: false, view_id: false,
@ -3344,7 +3376,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
var def = $.Deferred().then(function() { var def = $.Deferred().then(function() {
self.initial_is_loaded.resolve(); self.initial_is_loaded.resolve();
}); });
this.viewmanager.on_controller_inited.add_last(function(view_type, controller) { this.viewmanager.on("controller_inited", self, function(view_type, controller) {
controller.o2m = self; controller.o2m = self;
if (view_type == "list") { if (view_type == "list") {
if (self.get("effective_readonly")) { if (self.get("effective_readonly")) {
@ -3356,21 +3388,22 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
if (self.get("effective_readonly")) { if (self.get("effective_readonly")) {
$(".oe_form_buttons", controller.$el).children().remove(); $(".oe_form_buttons", controller.$el).children().remove();
} }
controller.on_record_loaded.add_last(function() { controller.on("load_record", self, function(){
once.resolve(); once.resolve();
}); });
controller.on_pager_action.add_first(function() { controller.on('pager_action_executed',self,self.save_any_view);
self.save_any_view();
});
} else if (view_type == "graph") { } else if (view_type == "graph") {
self.reload_current_view() self.reload_current_view()
} }
def.resolve(); def.resolve();
}); });
this.viewmanager.on_mode_switch.add_first(function(n_mode, b, c, d, e) { this.viewmanager.on("switch_mode", self, function(n_mode, b, c, d, e) {
$.when(self.save_any_view()).then(function() { $.when(self.save_any_view()).then(function() {
if(n_mode === "list") if (n_mode === "list") {
$.async_when().then(function() {self.reload_current_view();}); $.async_when().then(function() {
self.reload_current_view();
});
}
}); });
}); });
this.is_setted.then(function() { this.is_setted.then(function() {
@ -3493,7 +3526,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
if (!view.is_initialized.isResolved()) { if (!view.is_initialized.isResolved()) {
return false; return false;
} }
var res = $.when(view.do_save()); var res = $.when(view.save());
if (!res.isResolved() && !res.isRejected()) { if (!res.isResolved() && !res.isRejected()) {
console.warn("Asynchronous get_value() is not supported in form view."); console.warn("Asynchronous get_value() is not supported in form view.");
} }
@ -3538,7 +3571,7 @@ instance.web.form.One2ManyViewManager = instance.web.ViewManager.extend({
}); });
this.__ignore_blur = false; this.__ignore_blur = false;
}, },
switch_view: function(mode, unused) { switch_mode: function(mode, unused) {
if (mode !== 'form') { if (mode !== 'form') {
return this._super(mode, unused); return this._super(mode, unused);
} }
@ -3567,7 +3600,7 @@ instance.web.form.One2ManyViewManager = instance.web.ViewManager.extend({
form_view_options: {'not_interactible_on_create':true}, form_view_options: {'not_interactible_on_create':true},
readonly: self.o2m.get("effective_readonly") readonly: self.o2m.get("effective_readonly")
}); });
pop.on_select_elements.add_last(function() { pop.on("select_elements", self, function() {
self.o2m.reload_current_view(); self.o2m.reload_current_view();
}); });
}, },
@ -3653,7 +3686,7 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
self.o2m.build_domain(), self.o2m.build_domain(),
self.o2m.build_context() self.o2m.build_context()
); );
pop.on_select_elements.add_last(function() { pop.on("select_elements", self, function() {
self.o2m.reload_current_view(); self.o2m.reload_current_view();
}); });
} }
@ -3689,7 +3722,7 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
var self = this; var self = this;
this.ensure_saved().pipe(function () { this.ensure_saved().pipe(function () {
if (parent_form) if (parent_form)
return parent_form.do_save(); return parent_form.save();
else else
return $.when(); return $.when();
}).then(function () { }).then(function () {
@ -3792,7 +3825,7 @@ instance.web.form.One2ManyList = instance.web.ListView.List.extend({
colspan: columns, colspan: columns,
'class': 'oe_form_field_one2many_list_row_add' 'class': 'oe_form_field_one2many_list_row_add'
}).append( }).append(
$('<a>', {href: '#'}).text(_t("Add a row")) $('<a>', {href: '#'}).text(_t("Add an item"))
.mousedown(function () { .mousedown(function () {
// FIXME: needs to be an official API somehow // FIXME: needs to be an official API somehow
if (self.view.editor.is_editing()) { if (self.view.editor.is_editing()) {
@ -3828,7 +3861,7 @@ instance.web.form.One2ManyFormView = instance.web.FormView.extend({
this._super(data); this._super(data);
var self = this; var self = this;
this.$buttons.find('button.oe_form_button_create').click(function() { this.$buttons.find('button.oe_form_button_create').click(function() {
self.do_save().then(self.on_button_new); self.save().then(self.on_button_new);
}); });
}, },
do_notify_change: function() { do_notify_change: function() {
@ -3995,7 +4028,7 @@ instance.web.form.FieldMany2Many = instance.web.form.AbstractField.extend({
this.dataset = new instance.web.form.Many2ManyDataSet(this, this.field.relation); this.dataset = new instance.web.form.Many2ManyDataSet(this, this.field.relation);
this.dataset.m2m = this; this.dataset.m2m = this;
this.dataset.on_unlink.add_last(function(ids) { this.dataset.on('unlink', self, function(ids) {
self.dataset_changed(); self.dataset_changed();
}); });
@ -4090,7 +4123,7 @@ instance.web.form.Many2ManyListView = instance.web.ListView.extend(/** @lends in
this.m2m_field.build_context() this.m2m_field.build_context()
); );
var self = this; var self = this;
pop.on_select_elements.add(function(element_ids) { pop.on("select_elements", self, function(element_ids) {
_.each(element_ids, function(one_id) { _.each(element_ids, function(one_id) {
if(! _.detect(self.dataset.ids, function(x) {return x == one_id;})) { if(! _.detect(self.dataset.ids, function(x) {return x == one_id;})) {
self.dataset.set_ids([].concat(self.dataset.ids, [one_id])); self.dataset.set_ids([].concat(self.dataset.ids, [one_id]));
@ -4107,9 +4140,7 @@ instance.web.form.Many2ManyListView = instance.web.ListView.extend(/** @lends in
title: _t("Open: ") + this.m2m_field.string, title: _t("Open: ") + this.m2m_field.string,
readonly: this.getParent().get("effective_readonly") readonly: this.getParent().get("effective_readonly")
}); });
pop.on_write_completed.add_last(function() { pop.on('write_completed', self, self.reload_content);
self.reload_content();
});
} }
}); });
@ -4130,7 +4161,7 @@ instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend(
this.dataset = new instance.web.form.Many2ManyDataSet(this, this.field.relation); this.dataset = new instance.web.form.Many2ManyDataSet(this, this.field.relation);
this.dataset.m2m = this; this.dataset.m2m = this;
this.dataset.on_unlink.add_last(function(ids) { this.dataset.on('unlink', self, function(ids) {
self.dataset_changed(); self.dataset_changed();
}); });
@ -4178,7 +4209,7 @@ instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend(
self.initial_is_loaded.resolve(); self.initial_is_loaded.resolve();
loaded.resolve(); loaded.resolve();
}); });
this.kanban_view.do_switch_view.add_last(_.bind(this.open_popup, this)); this.kanban_view.on('switch_mode', this, this.open_popup);
$.async_when().then(function () { $.async_when().then(function () {
self.kanban_view.appendTo(self.$el); self.kanban_view.appendTo(self.$el);
}); });
@ -4207,7 +4238,7 @@ instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend(
new instance.web.CompoundDomain(this.build_domain(), ["!", ["id", "in", this.dataset.ids]]), new instance.web.CompoundDomain(this.build_domain(), ["!", ["id", "in", this.dataset.ids]]),
this.build_context() this.build_context()
); );
pop.on_select_elements.add(function(element_ids) { pop.on("select_elements", self, function(element_ids) {
_.each(element_ids, function(one_id) { _.each(element_ids, function(one_id) {
if(! _.detect(self.dataset.ids, function(x) {return x == one_id;})) { if(! _.detect(self.dataset.ids, function(x) {return x == one_id;})) {
self.dataset.set_ids([].concat(self.dataset.ids, [one_id])); self.dataset.set_ids([].concat(self.dataset.ids, [one_id]));
@ -4356,7 +4387,9 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
}; };
this.dataset.write_function = function(id, data, options, sup) { this.dataset.write_function = function(id, data, options, sup) {
var fct = self.options.write_function || sup; var fct = self.options.write_function || sup;
return fct.call(this, id, data, options).then(self.on_write_completed); return fct.call(this, id, data, options).then(function() {
self.trigger('write_completed');
});
}; };
this.dataset.parent_view = this.options.parent_view; this.dataset.parent_view = this.options.parent_view;
this.dataset.child_name = this.options.child_name; this.dataset.child_name = this.options.child_name;
@ -4376,7 +4409,6 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
this.$buttonpane = dialog.$el.dialog("widget").find(".ui-dialog-buttonpane").html(""); this.$buttonpane = dialog.$el.dialog("widget").find(".ui-dialog-buttonpane").html("");
this.start(); this.start();
}, },
on_write_completed: function() {},
setup_form_view: function() { setup_form_view: function() {
var self = this; var self = this;
if (this.row_id) { if (this.row_id) {
@ -4405,7 +4437,7 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
})); }));
var $snbutton = self.$buttonpane.find(".oe_abstractformpopup-form-save-new"); var $snbutton = self.$buttonpane.find(".oe_abstractformpopup-form-save-new");
$snbutton.click(function() { $snbutton.click(function() {
$.when(self.view_form.do_save()).then(function() { $.when(self.view_form.save()).then(function() {
self.view_form.reload_mutex.exec(function() { self.view_form.reload_mutex.exec(function() {
self.view_form.on_button_new(); self.view_form.on_button_new();
}); });
@ -4413,7 +4445,7 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
}); });
var $sbutton = self.$buttonpane.find(".oe_abstractformpopup-form-save"); var $sbutton = self.$buttonpane.find(".oe_abstractformpopup-form-save");
$sbutton.click(function() { $sbutton.click(function() {
$.when(self.view_form.do_save()).then(function() { $.when(self.view_form.save()).then(function() {
self.view_form.reload_mutex.exec(function() { self.view_form.reload_mutex.exec(function() {
self.check_exit(); self.check_exit();
}); });
@ -4426,11 +4458,12 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
self.view_form.do_show(); self.view_form.do_show();
}); });
}, },
on_select_elements: function(element_ids) { elements_selected: function(element_ids) {
this.trigger("select_elements",element_ids);
}, },
check_exit: function(no_destroy) { check_exit: function(no_destroy) {
if (this.created_elements.length > 0) { if (this.created_elements.length > 0) {
this.on_select_elements(this.created_elements); this.elements_selected(this.created_elements);
this.created_elements = []; this.created_elements = [];
} }
this.destroy(); this.destroy();
@ -4486,7 +4519,7 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend
self.rpc('/web/session/eval_domain_and_context', { self.rpc('/web/session/eval_domain_and_context', {
domains: [], domains: [],
contexts: [this.context] contexts: [this.context]
}, function (results) { }).then(function (results) {
var search_defaults = {}; var search_defaults = {};
_.each(results.context, function (value_, key) { _.each(results.context, function (value_, key) {
var match = /^search_default_(.*)$/.exec(key); var match = /^search_default_(.*)$/.exec(key);
@ -4541,7 +4574,7 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend
}); });
var $sbutton = self.$buttonpane.find(".oe_selectcreatepopup-search-select"); var $sbutton = self.$buttonpane.find(".oe_selectcreatepopup-search-select");
$sbutton.click(function() { $sbutton.click(function() {
self.on_select_elements(self.selected_ids); self.elements_selected(self.selected_ids);
self.destroy(); self.destroy();
}); });
}); });
@ -4554,7 +4587,7 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend
domains: domains || [], domains: domains || [],
contexts: contexts || [], contexts: contexts || [],
group_by_seq: groupbys || [] group_by_seq: groupbys || []
}, function (results) { }).then(function (results) {
self.view_list.do_search(results.domain, results.context, results.group_by); self.view_list.do_search(results.domain, results.context, results.group_by);
}); });
}, },
@ -4583,7 +4616,7 @@ instance.web.form.SelectCreateListView = instance.web.ListView.extend({
this.popup.new_object(); this.popup.new_object();
}, },
select_record: function(index) { select_record: function(index) {
this.popup.on_select_elements([this.dataset.ids[index]]); this.popup.elements_selected([this.dataset.ids[index]]);
this.popup.destroy(); this.popup.destroy();
}, },
do_select: function(ids, records) { do_select: function(ids, records) {
@ -4611,6 +4644,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan
destroy_content: function() { destroy_content: function() {
if (this.fm) { if (this.fm) {
this.fm.destroy(); this.fm.destroy();
this.fm = undefined;
} }
}, },
initialize_content: function() { initialize_content: function() {
@ -4632,9 +4666,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan
modifiers: JSON.stringify({readonly: this.get('effective_readonly')}), modifiers: JSON.stringify({readonly: this.get('effective_readonly')}),
}}); }});
this.selection.on("change:value", this, this.on_selection_changed); this.selection.on("change:value", this, this.on_selection_changed);
this.selection.setElement(this.$(".oe_form_view_reference_selection")); this.selection.appendTo(this.$(".oe_form_view_reference_selection"));
this.selection.renderElement();
this.selection.start();
this.selection this.selection
.on('focused', null, function () {self.trigger('focused')}) .on('focused', null, function () {self.trigger('focused')})
.on('blurred', null, function () {self.trigger('blurred')}); .on('blurred', null, function () {self.trigger('blurred')});
@ -4644,9 +4676,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan
modifiers: JSON.stringify({readonly: this.get('effective_readonly')}), modifiers: JSON.stringify({readonly: this.get('effective_readonly')}),
}}); }});
this.m2o.on("change:value", this, this.data_changed); this.m2o.on("change:value", this, this.data_changed);
this.m2o.setElement(this.$(".oe_form_view_reference_m2o")); this.m2o.appendTo(this.$(".oe_form_view_reference_m2o"));
this.m2o.renderElement();
this.m2o.start();
this.m2o this.m2o
.on('focused', null, function () {self.trigger('focused')}) .on('focused', null, function () {self.trigger('focused')})
.on('blurred', null, function () {self.trigger('blurred')}); .on('blurred', null, function () {self.trigger('blurred')});
@ -4790,9 +4820,9 @@ instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({
this._super(); this._super();
if (this.get("effective_readonly")) { if (this.get("effective_readonly")) {
var self = this; var self = this;
this.$el.find('a').click(function() { this.$el.find('a').click(function(ev) {
if (self.get('value')) { if (self.get('value')) {
self.on_save_as(); self.on_save_as(ev);
} }
return false; return false;
}); });
@ -4989,6 +5019,44 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({
}, },
}); });
instance.web.form.FieldMonetary = instance.web.form.FieldFloat.extend({
template: "FieldMonetary",
init: function() {
this._super.apply(this, arguments);
this.set({"currency": false});
if (this.options.currency_field) {
this.field_manager.on("field_changed:" + this.options.currency_field, this, function() {
this.set({"currency": this.field_manager.get_field_value(this.options.currency_field)});
});
}
this.on("change:currency", this, this.get_currency_info);
this.get_currency_info();
this.ci_dm = new instance.web.DropMisordered();
},
start: function() {
var tmp = this._super();
this.on("change:currency_info", this, this.reinitialize);
return tmp;
},
get_currency_info: function() {
var self = this;
if (this.get("currency") === false) {
this.set({"currency_info": null});
return;
}
return this.ci_dm.add(new instance.web.Model("res.currency").query(["symbol", "position"])
.filter([["id", "=", self.get("currency")]]).first()).pipe(function(res) {
self.set({"currency_info": res});
});
},
parse_value: function(val, def) {
return instance.web.parse_value(val, {type: "float"}, def);
},
format_value: function(val, def) {
return instance.web.format_value(val, {type: "float"}, def);
},
});
/** /**
* Registry of form fields, called by :js:`instance.web.FormView`. * Registry of form fields, called by :js:`instance.web.FormView`.
* *
@ -5019,7 +5087,8 @@ instance.web.form.widgets = new instance.web.Registry({
'progressbar': 'instance.web.form.FieldProgressBar', 'progressbar': 'instance.web.form.FieldProgressBar',
'image': 'instance.web.form.FieldBinaryImage', 'image': 'instance.web.form.FieldBinaryImage',
'binary': 'instance.web.form.FieldBinaryFile', 'binary': 'instance.web.form.FieldBinaryFile',
'statusbar': 'instance.web.form.FieldStatus' 'statusbar': 'instance.web.form.FieldStatus',
'monetary': 'instance.web.form.FieldMonetary',
}); });
/** /**

View File

@ -482,7 +482,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
view_type: "tree", view_type: "tree",
context: this.dataset.get_context(context), context: this.dataset.get_context(context),
toolbar: !!this.options.$sidebar toolbar: !!this.options.$sidebar
}, callback); }).then(callback);
} }
}, },
/** /**

View File

@ -626,7 +626,7 @@ openerp.web.list_editable = function (instance) {
}, },
start: function () { start: function () {
var self = this; var self = this;
var _super = this._super(); var _super = this._super();
this.form.embedded_view = this._validate_view( this.form.embedded_view = this._validate_view(
this.delegate.edition_view(this)); this.delegate.edition_view(this));
var form_ready = this.form.appendTo(this.$el).then( var form_ready = this.form.appendTo(this.$el).then(
@ -708,10 +708,9 @@ openerp.web.list_editable = function (instance) {
var self = this; var self = this;
var form = self.form; var form = self.form;
var loaded = record var loaded = record
? form.on_record_loaded(_.extend({}, record)) ? form.trigger('load_record', _.extend({}, record))
: form.load_defaults(); : form.load_defaults();
return $.when(loaded).pipe(function () {
return loaded.pipe(function () {
return form.do_show({reload: false}); return form.do_show({reload: false});
}).pipe(function () { }).pipe(function () {
self.record = form.datarecord; self.record = form.datarecord;
@ -725,7 +724,7 @@ openerp.web.list_editable = function (instance) {
save: function () { save: function () {
var self = this; var self = this;
return this.form return this.form
.do_save(this.delegate.prepends_on_create()) .save(this.delegate.prepends_on_create())
.pipe(function (result) { .pipe(function (result) {
var created = result.created && !self.record.id; var created = result.created && !self.record.id;
if (created) { if (created) {

View File

@ -45,7 +45,7 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
view_type: "tree", view_type: "tree",
toolbar: this.view_manager ? !!this.view_manager.sidebar : false, toolbar: this.view_manager ? !!this.view_manager.sidebar : false,
context: this.dataset.get_context() context: this.dataset.get_context()
}, this.on_loaded); }).then(this.on_loaded);
}, },
/** /**
* Returns the list of fields needed to correctly read objects. * Returns the list of fields needed to correctly read objects.

View File

@ -417,7 +417,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
this._super(); this._super();
var self = this; var self = this;
this.$el.find('.oe_view_manager_switch a').click(function() { this.$el.find('.oe_view_manager_switch a').click(function() {
self.on_mode_switch($(this).data('view-type')); self.switch_mode($(this).data('view-type'));
}).tipsy(); }).tipsy();
var views_ids = {}; var views_ids = {};
_.each(this.views_src, function(view) { _.each(this.views_src, function(view) {
@ -439,24 +439,17 @@ instance.web.ViewManager = instance.web.Widget.extend({
} }
// If no default view defined, switch to the first one in sequence // If no default view defined, switch to the first one in sequence
var default_view = this.flags.default_view || this.views_src[0].view_type; var default_view = this.flags.default_view || this.views_src[0].view_type;
return this.on_mode_switch(default_view); return this.switch_mode(default_view);
}, },
/** switch_mode: function(view_type, no_store, view_options) {
* Asks the view manager to switch visualization mode.
*
* @param {String} view_type type of view to display
* @param {Boolean} [no_store=false] don't store the view being switched to on the switch stack
* @returns {jQuery.Deferred} new view loading promise
*/
on_mode_switch: function(view_type, no_store, view_options) {
var self = this; var self = this;
var view = this.views[view_type]; var view = this.views[view_type];
var view_promise; var view_promise;
var form = this.views['form']; var form = this.views['form'];
if (!view || (form && form.controller && !form.controller.can_be_discarded())) { if (!view || (form && form.controller && !form.controller.can_be_discarded())) {
self.trigger('switch_mode', view_type, no_store, view_options);
return $.Deferred().reject(); return $.Deferred().reject();
} }
if (!no_store) { if (!no_store) {
this.views_history.push(view_type); this.views_history.push(view_type);
} }
@ -474,13 +467,12 @@ instance.web.ViewManager = instance.web.Widget.extend({
this.searchview[(view.controller.searchable === false || this.searchview.hidden) ? 'hide' : 'show'](); this.searchview[(view.controller.searchable === false || this.searchview.hidden) ? 'hide' : 'show']();
} }
this.$el this.$el.find('.oe_view_manager_switch a').parent().removeClass('active');
.find('.oe_view_manager_switch a').parent().removeClass('active');
this.$el this.$el
.find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]') .find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]')
.parent().addClass('active'); .parent().addClass('active');
return $.when(view_promise).then(function () { r = $.when(view_promise).then(function () {
_.each(_.keys(self.views), function(view_name) { _.each(_.keys(self.views), function(view_name) {
var controller = self.views[view_name].controller; var controller = self.views[view_name].controller;
if (controller) { if (controller) {
@ -501,7 +493,9 @@ instance.web.ViewManager = instance.web.Widget.extend({
} }
} }
}); });
self.trigger('switch_mode', view_type, no_store, view_options);
}); });
return r;
}, },
do_create_view: function(view_type) { do_create_view: function(view_type) {
// Lazy loading of views // Lazy loading of views
@ -530,20 +524,20 @@ instance.web.ViewManager = instance.web.Widget.extend({
if (view.embedded_view) { if (view.embedded_view) {
controller.set_embedded_view(view.embedded_view); controller.set_embedded_view(view.embedded_view);
} }
controller.do_switch_view.add_last(_.bind(this.switch_view, this)); controller.on('switch_mode', self, this.switch_mode);
controller.on('previous_view', self, this.prev_view);
controller.do_prev_view.add_last(this.on_prev_view);
var container = this.$el.find(".oe_view_manager_view_" + view_type); var container = this.$el.find(".oe_view_manager_view_" + view_type);
var view_promise = controller.appendTo(container); var view_promise = controller.appendTo(container);
this.views[view_type].controller = controller; this.views[view_type].controller = controller;
this.views[view_type].deferred.resolve(view_type); this.views[view_type].deferred.resolve(view_type);
return $.when(view_promise).then(function() { return $.when(view_promise).then(function() {
self.on_controller_inited(view_type, controller);
if (self.searchview if (self.searchview
&& self.flags.auto_search && self.flags.auto_search
&& view.controller.searchable !== false) { && view.controller.searchable !== false) {
self.searchview.ready.then(self.searchview.do_search); self.searchview.ready.then(self.searchview.do_search);
} }
self.trigger("controller_inited",view_type,controller);
}); });
}, },
set_title: function(title) { set_title: function(title) {
@ -552,7 +546,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
add_breadcrumb: function(on_reverse_breadcrumb) { add_breadcrumb: function(on_reverse_breadcrumb) {
var self = this; var self = this;
var views = [this.active_view || this.views_src[0].view_type]; var views = [this.active_view || this.views_src[0].view_type];
this.on_mode_switch.add(function(mode) { this.on('switch_mode', self, function(mode) {
var last = views.slice(-1)[0]; var last = views.slice(-1)[0];
if (mode !== last) { if (mode !== last) {
if (mode !== 'form') { if (mode !== 'form') {
@ -568,7 +562,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
var view_to_select = views[index]; var view_to_select = views[index];
self.$el.show(); self.$el.show();
if (self.active_view !== view_to_select) { if (self.active_view !== view_to_select) {
self.on_mode_switch(view_to_select); self.switch_mode(view_to_select);
} }
}, },
get_title: function() { get_title: function() {
@ -598,37 +592,29 @@ instance.web.ViewManager = instance.web.Widget.extend({
on_reverse_breadcrumb: on_reverse_breadcrumb, on_reverse_breadcrumb: on_reverse_breadcrumb,
}); });
}, },
/**
* Method used internally when a view asks to switch view. This method is meant
* to be extended by child classes to change the default behavior, which simply
* consist to switch to the asked view.
*/
switch_view: function(view_type, no_store, options) {
return this.on_mode_switch(view_type, no_store, options);
},
/** /**
* Returns to the view preceding the caller view in this manager's * Returns to the view preceding the caller view in this manager's
* navigation history (the navigation history is appended to via * navigation history (the navigation history is appended to via
* on_mode_switch) * switch_mode)
* *
* @param {Object} [options] * @param {Object} [options]
* @param {Boolean} [options.created=false] resource was created * @param {Boolean} [options.created=false] resource was created
* @param {String} [options.default=null] view to switch to if no previous view * @param {String} [options.default=null] view to switch to if no previous view
* @returns {$.Deferred} switching end signal * @returns {$.Deferred} switching end signal
*/ */
on_prev_view: function (options) { prev_view: function (options) {
options = options || {}; options = options || {};
var current_view = this.views_history.pop(); var current_view = this.views_history.pop();
var previous_view = this.views_history[this.views_history.length - 1] || options['default']; var previous_view = this.views_history[this.views_history.length - 1] || options['default'];
if (options.created && current_view === 'form' && previous_view === 'list') { if (options.created && current_view === 'form' && previous_view === 'list') {
// APR special case: "If creation mode from list (and only from a list), // APR special case: "If creation mode from list (and only from a list),
// after saving, go to page view (don't come back in list)" // after saving, go to page view (don't come back in list)"
return this.on_mode_switch('form'); return this.switch_mode('form');
} else if (options.created && !previous_view && this.action && this.action.flags.default_view === 'form') { } else if (options.created && !previous_view && this.action && this.action.flags.default_view === 'form') {
// APR special case: "If creation from dashboard, we have no previous view // APR special case: "If creation from dashboard, we have no previous view
return this.on_mode_switch('form'); return this.switch_mode('form');
} }
return this.on_mode_switch(previous_view, true); return this.switch_mode(previous_view, true);
}, },
/** /**
* Sets up the current viewmanager's search view. * Sets up the current viewmanager's search view.
@ -654,7 +640,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
domains: [this.action.domain || []].concat(domains || []), domains: [this.action.domain || []].concat(domains || []),
contexts: [action_context].concat(contexts || []), contexts: [action_context].concat(contexts || []),
group_by_seq: groupbys || [] group_by_seq: groupbys || []
}, function (results) { }).then(function (results) {
self.dataset._model = new instance.web.Model( self.dataset._model = new instance.web.Model(
self.dataset.model, results.context, results.domain); self.dataset.model, results.context, results.domain);
var groupby = results.group_by.length var groupby = results.group_by.length
@ -666,14 +652,6 @@ instance.web.ViewManager = instance.web.Widget.extend({
controller.do_search(results.domain, results.context, groupby || []); controller.do_search(results.domain, results.context, groupby || []);
}); });
}, },
/**
* 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 * Called when one of the view want to execute an action
*/ */
@ -813,13 +791,11 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
}); });
break; break;
case 'fields': case 'fields':
this.dataset.call_and_eval( this.dataset.call('fields_get', [false, {}]).then(function (fields) {
'fields_get', [false, {}], null, 1).then(function (fields) {
var $root = $('<dl>'); var $root = $('<dl>');
_(fields).each(function (attributes, name) { _(fields).each(function (attributes, name) {
$root.append($('<dt>').append($('<h4>').text(name))); $root.append($('<dt>').append($('<h4>').text(name)));
var $attrs = $('<dl>').appendTo( var $attrs = $('<dl>').appendTo($('<dd>').appendTo($root));
$('<dd>').appendTo($root));
_(attributes).each(function (def, name) { _(attributes).each(function (def, name) {
if (def instanceof Object) { if (def instanceof Object) {
def = JSON.stringify(def); def = JSON.stringify(def);
@ -899,7 +875,7 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
}, action || {}); }, action || {});
this.do_action(action); this.do_action(action);
}, },
on_mode_switch: function (view_type, no_store, options) { switch_mode: function (view_type, no_store, options) {
var self = this; var self = this;
return $.when(this._super.apply(this, arguments)).then(function () { return $.when(this._super.apply(this, arguments)).then(function () {
@ -934,7 +910,7 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
if (state.view_type && state.view_type !== this.active_view) { if (state.view_type && state.view_type !== this.active_view) {
defs.push( defs.push(
this.views[this.active_view].deferred.pipe(function() { this.views[this.active_view].deferred.pipe(function() {
return self.on_mode_switch(state.view_type, true); return self.switch_mode(state.view_type, true);
}) })
); );
} }
@ -1060,12 +1036,12 @@ instance.web.Sidebar = instance.web.Widget.extend({
self.rpc("/web/action/load", { self.rpc("/web/action/load", {
action_id: item.action.id, action_id: item.action.id,
context: additional_context context: additional_context
}, function(result) { }).then(function(result) {
result.result.context = _.extend(result.result.context || {}, result.context = _.extend(result.context || {},
additional_context); additional_context);
result.result.flags = result.result.flags || {}; result.flags = result.flags || {};
result.result.flags.new_window = true; result.flags.new_window = true;
self.do_action(result.result, function () { self.do_action(result, function () {
// reload view // reload view
self.getParent().reload(); self.getParent().reload();
}); });
@ -1239,7 +1215,7 @@ instance.web.View = instance.web.Widget.extend({
args.push(context); args.push(context);
return dataset.call_button(action_data.name, args).then(handler); return dataset.call_button(action_data.name, args).then(handler);
} else if (action_data.type=="action") { } else if (action_data.type=="action") {
return this.rpc('/web/action/load', { action_id: action_data.name, context: context, do_not_eval: true}, handler); return this.rpc('/web/action/load', { action_id: action_data.name, context: context, do_not_eval: true}).then(handler);
} else { } else {
return dataset.exec_workflow(record_id, action_data.name).then(handler); return dataset.exec_workflow(record_id, action_data.name).then(handler);
} }
@ -1273,6 +1249,7 @@ instance.web.View = instance.web.Widget.extend({
* @param {String} view view type to switch to * @param {String} view view type to switch to
*/ */
do_switch_view: function(view) { do_switch_view: function(view) {
this.trigger('switch_mode',view);
}, },
/** /**
* Cancels the switch to the current view, switches to the previous one * Cancels the switch to the current view, switches to the previous one
@ -1281,8 +1258,7 @@ instance.web.View = instance.web.Widget.extend({
* @param {Boolean} [options.created=false] resource was created * @param {Boolean} [options.created=false] resource was created
* @param {String} [options.default=null] view to switch to if no previous view * @param {String} [options.default=null] view to switch to if no previous view
*/ */
do_prev_view: function (options) {
},
do_search: function(view) { do_search: function(view) {
}, },
on_sidebar_export: function() { on_sidebar_export: function() {

View File

@ -548,7 +548,7 @@
<a class="oe_sidebar_action_a" t-att-title="item.title" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url" target="_blank"> <a class="oe_sidebar_action_a" t-att-title="item.title" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url" target="_blank">
<t t-raw="item.label"/> <t t-raw="item.label"/>
</a> </a>
<a t-if="section.name == 'files'" class="oe_sidebar_delete_item" t-att-data-id="item.id" title="Delete this attachment">x</a> <a t-if="section.name == 'files' and !item.callback" class="oe_sidebar_delete_item" t-att-data-id="item.id" title="Delete this attachment">x</a>
</li> </li>
<li t-if="section.name == 'files'" class="oe_sidebar_add_attachment"> <li t-if="section.name == 'files'" class="oe_sidebar_add_attachment">
<t t-call="HiddenInputFile"> <t t-call="HiddenInputFile">
@ -916,7 +916,7 @@
</t> </t>
<t t-name="FieldChar"> <t t-name="FieldChar">
<span t-att-class="'oe_form_field '+widget.widget_class" t-att-style="widget.node.attrs.style"> <span t-att-class="'oe_form_field '+widget.widget_class" t-att-style="widget.node.attrs.style">
<t t-if="!widget.get('effective_readonly')"> <t t-if="!widget.get('effective_readonly')">
<input t-att-type="widget.password ? 'password' : 'text'" <input t-att-type="widget.password ? 'password' : 'text'"
t-att-id="widget.id_for_label" t-att-id="widget.id_for_label"
t-att-tabindex="widget.node.attrs.tabindex" t-att-tabindex="widget.node.attrs.tabindex"
@ -925,6 +925,9 @@
t-att-maxlength="widget.field.size" t-att-maxlength="widget.field.size"
/><img class="oe_field_translate oe_input_icon" t-if="widget.field.translate" t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"/> /><img class="oe_field_translate oe_input_icon" t-if="widget.field.translate" t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"/>
</t> </t>
<t t-if="widget.get('effective_readonly')">
<span class="oe_form_char_content"></span>
</t>
</span> </span>
</t> </t>
<t t-name="FieldEmail"> <t t-name="FieldEmail">
@ -1534,7 +1537,7 @@
<td colspan="3"> <td colspan="3">
<label for="import_compat">Export Type:</label> <label for="import_compat">Export Type:</label>
<select id="import_compat" name="import_compat"> <select id="import_compat" name="import_compat">
<option value="yes">Import Compatible Export</option> <option value="yes">Import-Compatible Export</option>
<option value="">Export all Data</option> <option value="">Export all Data</option>
</select> </select>
@ -1717,4 +1720,16 @@
<button class="oe_form_m2o_sc_button oe_button">Add All Info...</button> <button class="oe_form_m2o_sc_button oe_button">Add All Info...</button>
<button class="oe_form_m2o_cancel_button oe_button">Cancel</button> <button class="oe_form_m2o_cancel_button oe_button">Cancel</button>
</t> </t>
<t t-name="FieldMonetary" t-extend="FieldChar">
<t t-jquery="t:first" t-operation="before">
<t t-if="widget.get('currency_info') and widget.get('currency_info').position === 'before'">
<t t-esc="widget.get('currency_info').symbol"/>
</t>
</t>
<t t-jquery="t:last" t-operation="after">
<t t-if="widget.get('currency_info') and widget.get('currency_info').position === 'after'">
<t t-esc="widget.get('currency_info').symbol"/>
</t>
</t>
</t>
</templates> </templates>

View File

@ -82,14 +82,14 @@ $(document).ready(function () {
}); });
t.test('call', function (openerp) { t.test('call', function (openerp) {
var ds = new openerp.web.DataSet({session: openerp.session}, 'mod'); var ds = new openerp.web.DataSet({session: openerp.session}, 'mod');
t.expect(ds.call('frob', ['a', 'b', 42]), function (r) { t.expect(ds.call('frob', ['a', 'b', 42]).then(function (r) {
strictEqual(r.method, 'frob'); strictEqual(r.method, 'frob');
strictEqual(r.args.length, 3); strictEqual(r.args.length, 3);
deepEqual(r.args, ['a', 'b', 42]); deepEqual(r.args, ['a', 'b', 42]);
ok(_.isEmpty(r.kwargs)); ok(_.isEmpty(r.kwargs));
}); }));
}); });
t.test('name_get').then(function (openerp) { t.test('name_get').then(function (openerp) {
var ds = new openerp.web.DataSet({session: openerp.session}, 'mod'); var ds = new openerp.web.DataSet({session: openerp.session}, 'mod');

View File

@ -4,7 +4,7 @@ import mock
import unittest2 import unittest2
from ..controllers import main from ..controllers import main
from ..common.session import OpenERPSession from ..session import OpenERPSession
class Placeholder(object): class Placeholder(object):
def __init__(self, **kwargs): def __init__(self, **kwargs):

View File

@ -6,7 +6,7 @@ import unittest2
import simplejson import simplejson
import web.controllers.main import web.controllers.main
from ..common import nonliterals, session as s from .. import nonliterals, session as s
def field_attrs(fields_view_get, fieldname): def field_attrs(fields_view_get, fieldname):
(field,) = filter(lambda f: f['attrs'].get('name') == fieldname, (field,) = filter(lambda f: f['attrs'].get('name') == fieldname,

View File

@ -997,7 +997,6 @@ div.openerp .dhx_cal_editor textarea {
} }
.openerp .dhx_cal_event_selected{ .openerp .dhx_cal_event_selected{
background-color: #757575; background-color: #757575;
color: ffffff;
} }
/* Agenda week end */ /* Agenda week end */
.openerp .dhx_scale_bar_header { .openerp .dhx_scale_bar_header {

View File

@ -41,7 +41,7 @@ instance.web_calendar.CalendarView = instance.web.View.extend({
}, },
start: function() { start: function() {
this._super(); this._super();
return this.rpc("/web/view/load", {"model": this.model, "view_id": this.view_id, "view_type":"calendar", 'toolbar': false}, this.on_loaded); return this.rpc("/web/view/load", {"model": this.model, "view_id": this.view_id, "view_type":"calendar", 'toolbar': false}).then(this.on_loaded);
}, },
destroy: function() { destroy: function() {
scheduler.clearAll(); scheduler.clearAll();
@ -458,8 +458,8 @@ instance.web_calendar.CalendarFormDialog = instance.web.Dialog.extend({
pager: false pager: false
}); });
var def = this.form.appendTo(this.$el); var def = this.form.appendTo(this.$el);
this.form.on_created.add_last(this.on_form_dialog_saved); this.form.on('record_created', self, this.on_form_dialog_saved);
this.form.on_saved.add_last(this.on_form_dialog_saved); this.form.on('record_saved', self, this.on_form_dialog_saved);
this.form.on_button_cancel = function() { this.form.on_button_cancel = function() {
self.close(); self.close();
} }

View File

@ -1,21 +1,14 @@
try: import openerp
# embedded
import openerp.addons.web.common.http as openerpweb
from openerp.addons.web.controllers.main import View
except ImportError:
# standalone
import web.common.http as openerpweb
from web.controllers.main import View
class DiagramView(View): class DiagramView(openerp.addons.web.controllers.main.View):
_cp_path = "/web_diagram/diagram" _cp_path = "/web_diagram/diagram"
@openerpweb.jsonrequest @openerp.addons.web.http.jsonrequest
def load(self, req, model, view_id): def load(self, req, model, view_id):
fields_view = self.fields_view_get(req, model, view_id, 'diagram') fields_view = self.fields_view_get(req, model, view_id, 'diagram')
return {'fields_view': fields_view} return {'fields_view': fields_view}
@openerpweb.jsonrequest @openerp.addons.web.http.jsonrequest
def get_diagram_info(self, req, id, model, node, connector, def get_diagram_info(self, req, id, model, node, connector,
src_node, des_node, label, **kw): src_node, des_node, label, **kw):

View File

@ -49,5 +49,5 @@
-ms-user-select: none; -ms-user-select: none;
-o-user-select: none; -o-user-select: none;
user-select: none; user-select: none;
}; }

View File

@ -57,7 +57,7 @@ instance.web.DiagramView = instance.web.View.extend({
this.$el.find('div.oe_diagram_pager button[data-pager-action]').click(function() { this.$el.find('div.oe_diagram_pager button[data-pager-action]').click(function() {
var action = $(this).data('pager-action'); var action = $(this).data('pager-action');
self.on_pager_action(action); self.execute_pager_action(action);
}); });
this.do_update_pager(); this.do_update_pager();
@ -105,8 +105,7 @@ instance.web.DiagramView = instance.web.View.extend({
}); });
this.rpc( this.rpc(
'/web_diagram/diagram/get_diagram_info',params, '/web_diagram/diagram/get_diagram_info',params).then(function(result) {
function(result) {
self.draw_diagram(result); self.draw_diagram(result);
} }
); );
@ -240,23 +239,23 @@ instance.web.DiagramView = instance.web.View.extend({
title: _t("Open: ") + title title: _t("Open: ") + title
} }
); );
pop.on('on_write_complete', self, function() {
pop.on_write.add(function() {
self.dataset.read_index(_.keys(self.fields_view.fields)).pipe(self.on_diagram_loaded); self.dataset.read_index(_.keys(self.fields_view.fields)).pipe(self.on_diagram_loaded);
}); });
var form_fields = [self.parent_field]; var form_fields = [self.parent_field];
var form_controller = pop.view_form; var form_controller = pop.view_form;
form_controller.on_record_loaded.add_first(function() { form_controller.on("load_record", self, function(){
_.each(form_fields, function(fld) { _.each(form_fields, function(fld) {
if (!(fld in form_controller.fields)) { return; } if (!(fld in form_controller.fields)) { return; }
var field = form_controller.fields[fld]; var field = form_controller.fields[fld];
field.$input.prop('disabled', true); field.$input.prop('disabled', true);
field.$drop_down.unbind(); field.$drop_down.unbind();
field.$menu_btn.unbind();
}); });
}); });
}, },
// Creates a popup to add a node to the diagram // Creates a popup to add a node to the diagram
@ -274,14 +273,14 @@ instance.web.DiagramView = instance.web.View.extend({
self.dataset.domain, self.dataset.domain,
self.context || self.dataset.context self.context || self.dataset.context
); );
pop.on_select_elements.add_last(function(element_ids) { pop.on("select_elements", self, function(element_ids) {
self.dataset.read_index(_.keys(self.fields_view.fields)).pipe(self.on_diagram_loaded); self.dataset.read_index(_.keys(self.fields_view.fields)).pipe(self.on_diagram_loaded);
}); });
var form_controller = pop.view_form; var form_controller = pop.view_form;
var form_fields = [this.parent_field]; var form_fields = [this.parent_field];
form_controller.on_record_loaded.add_last(function() { form_controller.on("load_record", self, function(){
_.each(form_fields, function(fld) { _.each(form_fields, function(fld) {
if (!(fld in form_controller.fields)) { return; } if (!(fld in form_controller.fields)) { return; }
var field = form_controller.fields[fld]; var field = form_controller.fields[fld];
@ -304,7 +303,7 @@ instance.web.DiagramView = instance.web.View.extend({
title: _t("Open: ") + title title: _t("Open: ") + title
} }
); );
pop.on_write.add(function() { pop.on('on_write_complete', self, function() {
self.dataset.read_index(_.keys(self.fields_view.fields)).pipe(self.on_diagram_loaded); self.dataset.read_index(_.keys(self.fields_view.fields)).pipe(self.on_diagram_loaded);
}); });
}, },
@ -326,8 +325,7 @@ instance.web.DiagramView = instance.web.View.extend({
this.dataset.domain, this.dataset.domain,
this.context || this.dataset.context this.context || this.dataset.context
); );
pop.on("select_elements", self, function(element_ids) {
pop.on_select_elements.add_last(function(element_ids) {
self.dataset.read_index(_.keys(self.fields_view.fields)).pipe(self.on_diagram_loaded); self.dataset.read_index(_.keys(self.fields_view.fields)).pipe(self.on_diagram_loaded);
}); });
// We want to destroy the dummy edge after a creation cancel. This destroys it even if we save the changes. // We want to destroy the dummy edge after a creation cancel. This destroys it even if we save the changes.
@ -340,15 +338,16 @@ instance.web.DiagramView = instance.web.View.extend({
var form_controller = pop.view_form; var form_controller = pop.view_form;
form_controller.on_record_loaded.add_last(function () {
form_controller.on("load_record", self, function(){
form_controller.fields[self.connectors.attrs.source].set_value(node_source_id); form_controller.fields[self.connectors.attrs.source].set_value(node_source_id);
form_controller.fields[self.connectors.attrs.source].dirty = true; form_controller.fields[self.connectors.attrs.source].dirty = true;
form_controller.fields[self.connectors.attrs.destination].set_value(node_dest_id); form_controller.fields[self.connectors.attrs.destination].set_value(node_dest_id);
form_controller.fields[self.connectors.attrs.destination].dirty = true; form_controller.fields[self.connectors.attrs.destination].dirty = true;
}); });
}, },
on_pager_action: function(action) { execute_pager_action: function(action) {
switch (action) { switch (action) {
case 'first': case 'first':
this.dataset.index = 0; this.dataset.index = 0;
@ -381,7 +380,7 @@ instance.web.DiagramView = instance.web.View.extend({
do_show: function() { do_show: function() {
this.do_push_state({}); this.do_push_state({});
return $.when(this._super(), this.on_pager_action('reload')); return $.when(this._super(), this.execute_pager_action('reload'));
} }
}); });
}; };

View File

@ -211,9 +211,7 @@ instance.web_gantt.GanttView = instance.web.View.extend({
on_task_display: function(task) { on_task_display: function(task) {
var self = this; var self = this;
var pop = new instance.web.form.FormOpenPopup(self); var pop = new instance.web.form.FormOpenPopup(self);
pop.on_write_completed.add_last(function() { pop.on('write_completed',self,self.reload);
self.reload();
});
pop.show_element( pop.show_element(
self.dataset.model, self.dataset.model,
task.id, task.id,
@ -224,7 +222,7 @@ instance.web_gantt.GanttView = instance.web.View.extend({
on_task_create: function() { on_task_create: function() {
var self = this; var self = this;
var pop = new instance.web.form.SelectCreatePopup(this); var pop = new instance.web.form.SelectCreatePopup(this);
pop.on_select_elements.add_last(function() { pop.on("select_elements", self, function() {
self.reload(); self.reload();
}); });
pop.select_element( pop.select_element(

View File

@ -1,19 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
try: import openerp
# embedded
import openerp.addons.web.common.http as openerpweb
from openerp.addons.web.controllers.main import View
except ImportError:
# standalone
import web.common.http as openerpweb
from web.controllers.main import View
from lxml import etree from lxml import etree
class GraphView(View): class GraphView(openerp.addons.web.controllers.main.View):
_cp_path = '/web_graph/graph' _cp_path = '/web_graph/graph'
@openerpweb.jsonrequest @openerp.addons.web.http.jsonrequest
def data_get(self, req, model=None, domain=[], context={}, group_by=[], view_id=False, orientation=False, stacked=False, mode="bar", **kwargs): def data_get(self, req, model=None, domain=[], context={}, group_by=[], view_id=False, orientation=False, stacked=False, mode="bar", **kwargs):
obj = req.session.model(model) obj = req.session.model(model)

View File

@ -194,9 +194,9 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
}); });
var am = instance.webclient.action_manager; var am = instance.webclient.action_manager;
var form = am.dialog_widget.views.form.controller; var form = am.dialog_widget.views.form.controller;
form.on_button_cancel.add_last(am.dialog.on_close); form.on("on_button_cancel", self, am.dialog.on_close);
form.on_created.add_last(function(r) { form.on('record_created', self, function(r) {
(new instance.web.DataSet(self, self.group_by_field.relation)).name_get([r.result]).then(function(new_record) { (new instance.web.DataSet(self, self.group_by_field.relation)).name_get([r]).then(function(new_record) {
am.dialog.on_close(); am.dialog.on_close();
var domain = self.dataset.domain.slice(0); var domain = self.dataset.domain.slice(0);
domain.push([self.group_by, '=', new_record[0][0]]); domain.push([self.group_by, '=', new_record[0][0]]);
@ -672,8 +672,8 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({
}); });
var am = instance.webclient.action_manager; var am = instance.webclient.action_manager;
var form = am.dialog_widget.views.form.controller; var form = am.dialog_widget.views.form.controller;
form.on_button_cancel.add_last(am.dialog.on_close); form.on("on_button_cancel", self, am.dialog.on_close);
form.on_saved.add_last(function() { form.on('record_saved', self, function() {
am.dialog.on_close(); am.dialog.on_close();
self.view.do_reload(); self.view.do_reload();
}); });
@ -1067,7 +1067,7 @@ instance.web_kanban.QuickCreate = instance.web.Widget.extend({
[], [],
{"default_name": self.$input.val()} {"default_name": self.$input.val()}
); );
pop.on_select_elements.add(function(element_ids) { pop.on("select_elements", self, function(element_ids) {
self.$input.val(""); self.$input.val("");
self.trigger('added', element_ids[0]); self.trigger('added', element_ids[0]);
}); });

View File

@ -23,14 +23,14 @@ openerp.web_tests = function (instance) {
on_everything_loaded: function (slice) { on_everything_loaded: function (slice) {
var records = slice[0].records; var records = slice[0].records;
if (!records.length) { if (!records.length) {
this.form.on_record_loaded({}); this.form.trigger("load_record", {});
return; return;
} }
this.form.on_record_loaded(records[0]); this.form.trigger("load_record", records[0]);
_(records.slice(1)).each(function (record, index) { _(records.slice(1)).each(function (record, index) {
this.dataset.index = index+1; this.dataset.index = index+1;
this.form.reposition($('<div>').appendTo(this.$el)); this.form.reposition($('<div>').appendTo(this.$el));
this.form.on_record_loaded(record); this.form.trigger("load_record", record);
}, this); }, this);
} }
}); });

View File

@ -136,7 +136,7 @@ instance.web_view_editor.ViewEditor = instance.web.Widget.extend({
var field_dataset = new instance.web.DataSetSearch(this, this.model, null, null); var field_dataset = new instance.web.DataSetSearch(this, this.model, null, null);
var model_dataset = new instance.web.DataSetSearch(this, 'ir.model', null, null); var model_dataset = new instance.web.DataSetSearch(this, 'ir.model', null, null);
var view_string = "", field_name = false, self = this; var view_string = "", field_name = false, self = this;
field_dataset.call( 'fields_get', [], function(fields) { field_dataset.call( 'fields_get', []).then(function(fields) {
_.each(['name', 'x_name'], function(value) { _.each(['name', 'x_name'], function(value) {
if (_.include(_.keys(fields), value)) { if (_.include(_.keys(fields), value)) {
field_name = value; field_name = value;
@ -539,7 +539,7 @@ instance.web_view_editor.ViewEditor = instance.web.Widget.extend({
var value = _.has(_CHILDREN, element) ? element : _.str.include(html_tag, element)?"html_tag":false; var value = _.has(_CHILDREN, element) ? element : _.str.include(html_tag, element)?"html_tag":false;
property_to_check.push(value); property_to_check.push(value);
}); });
field_dataset.call( 'fields_get', [], function(result) { field_dataset.call( 'fields_get', []).then(function(result) {
var fields = _.keys(result); var fields = _.keys(result);
fields.push(" "),fields.sort(); fields.push(" "),fields.sort();
self.on_add_node(property_to_check, fields); self.on_add_node(property_to_check, fields);
@ -1014,10 +1014,10 @@ instance.web_view_editor.ViewEditor = instance.web.Widget.extend({
var action_manager = new instance.web.ActionManager(self); var action_manager = new instance.web.ActionManager(self);
$.when(action_manager.do_action(action)).then(function() { $.when(action_manager.do_action(action)).then(function() {
var controller = action_manager.dialog_widget.views['form'].controller; var controller = action_manager.dialog_widget.views['form'].controller;
controller.on_button_cancel.add_last(function(){ controller.on("on_button_cancel", self, function(){
action_manager.destroy() action_manager.destroy();
}); });
controller.do_save.add_last(function(){ controller.on("save", self, function(){
action_manager.destroy(); action_manager.destroy();
var value =controller.fields.name.get('value'); var value =controller.fields.name.get('value');
self.add_node_dialog.$el.find('select[id=field_value]').append($("<option selected></option>").attr("value",value).text(value)); self.add_node_dialog.$el.find('select[id=field_value]').append($("<option selected></option>").attr("value",value).text(value));

View File

@ -1,27 +0,0 @@
{
"version": 1,
"formatters": {
"simple": {
"format": "[%(asctime)s] %(levelname)s:%(name)s:%(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "simple",
"stream": "ext://sys.stdout"
}
},
"loggers": {
"web": {
},
"web.common.openerplib": {
"level": "INFO"
}
},
"root": {
"level": "DEBUG",
"handlers": ["console"]
}
}

View File

@ -1,116 +0,0 @@
#!/usr/bin/env python
import json
import logging
import logging.config
import optparse
import os
import sys
import tempfile
import werkzeug.serving
import werkzeug.contrib.fixers
optparser = optparse.OptionParser()
optparser.add_option("-s", "--session-path", dest="session_storage",
default=os.path.join(tempfile.gettempdir(), "oe-sessions"),
help="Directory used for session storage", metavar="DIR")
optparser.add_option("--server-host", dest="server_host",
default='127.0.0.1', help="OpenERP server hostname", metavar="HOST")
optparser.add_option("--server-port", dest="server_port", default=8069,
help="OpenERP server port", type="int", metavar="NUMBER")
optparser.add_option("--db-filter", dest="dbfilter", default='.*',
help="Filter listed databases", metavar="REGEXP")
optparser.add_option('--addons-path', dest='addons_path', default=[], action='append',
help="Path to addons directory", metavar="PATH")
optparser.add_option('--load', dest='server_wide_modules', default=['web'], action='append',
help="Load an additional module before login (by default only 'web' is loaded)", metavar="MODULE")
server_options = optparse.OptionGroup(optparser, "Server configuration")
server_options.add_option("-p", "--port", dest="socket_port", default=8002,
help="listening port", type="int", metavar="NUMBER")
server_options.add_option('--reloader', dest='reloader',
default=False, action='store_true',
help="Reload application when python files change")
server_options.add_option('--no-serve-static', dest='serve_static',
default=True, action='store_false',
help="Do not serve static files via this server")
server_options.add_option('--multi-threaded', dest='threaded',
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="Enable correct behavior when behind a reverse proxy")
optparser.add_option_group(server_options)
logging_opts = optparse.OptionGroup(optparser, "Logging")
logging_opts.add_option("--log-level", dest="log_level", type="choice",
default='debug', help="Global logging level", metavar="LOG_LEVEL",
choices=['debug', 'info', 'warning', 'error', 'critical'])
logging_opts.add_option("--log-config", dest="log_config", default=os.path.join(os.path.dirname(__file__), "logging.json"),
help="Logging configuration file", metavar="FILE")
optparser.add_option_group(logging_opts)
testing_opts = optparse.OptionGroup(optparser, "Testing")
testing_opts.add_option('--test-mode', dest='test_mode',
action='store_true', default=False,
help="Starts test mode, which provides a few"
" (utterly unsafe) APIs for testing purposes and"
" sets up a special connector which always raises"
" errors on tentative server access. These errors"
" serialize RPC query information (service,"
" method, arguments list) in the fault_code"
" attribute of the error object returned to the"
" client. This lets javascript code assert the" \
" XMLRPC consequences of its queries.")
optparser.add_option_group(testing_opts)
if __name__ == "__main__":
(options, args) = optparser.parse_args(sys.argv[1:])
if not options.addons_path:
path_root = os.path.dirname(os.path.abspath(__file__))
path_addons = os.path.join(path_root, 'addons')
if os.path.exists(path_addons):
options.addons_path.append(path_addons)
options.addons_path = [
path[:-1] if path[-1] in r'\/' else path
for path in options.addons_path
if os.path.exists(path)
]
for path_addons in options.addons_path:
if path_addons not in sys.path:
sys.path.insert(0, path_addons)
try:
import web.common.http
except ImportError:
optparser.error('Error Importing base web module. Check correctness of --addons-path.')
options.backend = 'xmlrpc'
os.environ["TZ"] = "UTC"
if options.test_mode:
import web.test_support
import web.test_support.controllers
options.connector = web.test_support.TestConnector()
logging.getLogger('werkzeug').setLevel(logging.WARNING)
if sys.version_info >= (2, 7) and os.path.exists(options.log_config):
with open(options.log_config) as file:
dct = json.load(file)
logging.config.dictConfig(dct)
logging.getLogger().setLevel(getattr(logging, options.log_level.upper()))
else:
logging.basicConfig(level=getattr(logging, options.log_level.upper()))
app = web.common.http.Root(options)
if options.proxy_mode:
app = werkzeug.contrib.fixers.ProxyFix(app)
werkzeug.serving.run_simple(
'0.0.0.0', options.socket_port, app,
use_reloader=options.reloader, threaded=options.threaded)

View File

@ -1,3 +0,0 @@
[global]

124
setup.py
View File

@ -1,124 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# 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 glob, os, re, setuptools, sys
from os.path import join, isfile
# List all data files
def data():
files = []
for root, dirnames, filenames in os.walk('openerp'):
for filename in filenames:
if not re.match(r'.*(\.pyc|\.pyo|\~)$',filename):
files.append(os.path.join(root, filename))
d = {}
for v in files:
k=os.path.dirname(v)
if k in d:
d[k].append(v)
else:
d[k]=[v]
r = d.items()
if os.name == 'nt':
r.append(("Microsoft.VC90.CRT", glob.glob('C:\Microsoft.VC90.CRT\*.*')))
import babel
r.append(("localedata",
glob.glob(os.path.join(os.path.dirname(babel.__file__), "localedata" , '*'))))
return r
def gen_manifest():
file_list="\n".join(data())
open('MANIFEST','w').write(file_list)
if os.name == 'nt':
sys.path.append("C:\Microsoft.VC90.CRT")
def py2exe_options():
if os.name == 'nt':
import py2exe
return {
"console" : [ { "script": "openerp-server", "icon_resources": [(1, join("install","openerp-icon.ico"))], }],
'options' : {
"py2exe": {
"skip_archive": 1,
"optimize": 2,
"dist_dir": 'dist',
"packages": [ "DAV", "HTMLParser", "PIL", "asynchat", "asyncore", "commands", "dateutil", "decimal", "email", "encodings", "imaplib", "lxml", "lxml._elementpath", "lxml.builder", "lxml.etree", "lxml.objectify", "mako", "openerp", "poplib", "pychart", "pydot", "pyparsing", "reportlab", "select", "simplejson", "smtplib", "uuid", "vatnumber", "vobject", "xml", "xml.dom", "yaml", ],
"excludes" : ["Tkconstants","Tkinter","tcl"],
}
}
}
else:
return {}
execfile(join(os.path.dirname(__file__), 'openerp', 'release.py'))
setuptools.setup(
name = 'openerp',
version = version,
description = description,
long_description = long_desc,
url = url,
author = author,
author_email = author_email,
classifiers = filter(None, classifiers.split("\n")),
license = license,
scripts = ['openerp-server'],
data_files = data(),
packages = setuptools.find_packages(),
dependency_links = ['http://download.gna.org/pychart/'],
#include_package_data = True,
install_requires = [
'pychart',
'babel',
'docutils',
'feedparser',
'gdata',
'lxml < 3',
'mako',
'psutil',
'psycopg2',
'pydot',
'python-dateutil < 2',
'python-ldap',
'python-openid',
'pytz',
'pywebdav',
'pyyaml',
'reportlab',
'simplejson',
'vatnumber',
'vobject',
'werkzeug',
'xlwt',
'zsi',
],
extras_require = {
'SSL' : ['pyopenssl'],
},
**py2exe_options()
)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: