Merge with trunk
bzr revid: rgaopenerp-20121012115527-xn5iqpctzbb9qmkv
This commit is contained in:
commit
7b41571e32
34
Makefile
34
Makefile
|
@ -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
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
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
|
||||
|
||||
|
|
|
@ -1,31 +1,4 @@
|
|||
import logging
|
||||
|
||||
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)
|
||||
import http
|
||||
import controllers
|
||||
|
||||
wsgi_postload = http.wsgi_postload
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
from . import http
|
||||
from . import nonliterals
|
||||
from . import release
|
||||
from . import session
|
||||
from . import xml2json
|
|
@ -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 *
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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"
|
|
@ -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
|
|
@ -27,8 +27,11 @@ try:
|
|||
except ImportError:
|
||||
xlwt = None
|
||||
|
||||
from .. import common
|
||||
openerpweb = common.http
|
||||
import openerp
|
||||
|
||||
from .. import http
|
||||
from .. import nonliterals
|
||||
openerpweb = http
|
||||
|
||||
#----------------------------------------------------------
|
||||
# OpenERP Web helpers
|
||||
|
@ -135,7 +138,7 @@ def db_list(req):
|
|||
dbs = proxy.list()
|
||||
h = req.httprequest.environ['HTTP_HOST'].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)]
|
||||
return dbs
|
||||
|
||||
|
@ -227,11 +230,12 @@ def module_installed_bypass_session(dbname):
|
|||
return sorted_modules
|
||||
|
||||
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
|
||||
serverside = []
|
||||
dbside = []
|
||||
for i in req.config.server_wide_modules:
|
||||
for i in server_wide_modules:
|
||||
if i in openerpweb.addons_manifest:
|
||||
serverside.append(i)
|
||||
# if only one db load every module at boot
|
||||
|
@ -502,12 +506,12 @@ def fix_view_modes(action):
|
|||
|
||||
def parse_domain(domain, session):
|
||||
""" 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
|
||||
is assumed to be a literal domain and is returned as-is
|
||||
:param session: Current OpenERP session
|
||||
:type session: openerpweb.openerpweb.OpenERPSession
|
||||
:type session: openerpweb.OpenERPSession
|
||||
"""
|
||||
if not isinstance(domain, basestring):
|
||||
return domain
|
||||
|
@ -515,24 +519,23 @@ def parse_domain(domain, session):
|
|||
return ast.literal_eval(domain)
|
||||
except ValueError:
|
||||
# not a literal
|
||||
return common.nonliterals.Domain(session, domain)
|
||||
return nonliterals.Domain(session, domain)
|
||||
|
||||
def parse_context(context, session):
|
||||
""" 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
|
||||
is assumed to be a literal domain and is returned as-is
|
||||
:param session: Current OpenERP session
|
||||
:type session: openerpweb.openerpweb.OpenERPSession
|
||||
:type session: openerpweb.OpenERPSession
|
||||
"""
|
||||
if not isinstance(context, basestring):
|
||||
return context
|
||||
try:
|
||||
return ast.literal_eval(context)
|
||||
except ValueError:
|
||||
return common.nonliterals.Context(session, context)
|
||||
|
||||
return nonliterals.Context(session, context)
|
||||
|
||||
def _local_web_translations(trans_file):
|
||||
messages = []
|
||||
|
@ -546,6 +549,31 @@ def _local_web_translations(trans_file):
|
|||
messages.append({'id': x.id, 'string': x.string})
|
||||
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
|
||||
|
@ -568,7 +596,27 @@ html_template = """<!DOCTYPE html>
|
|||
});
|
||||
</script>
|
||||
</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>
|
||||
"""
|
||||
|
||||
|
@ -592,7 +640,6 @@ class Home(openerpweb.Controller):
|
|||
def login(self, req, db, login, key):
|
||||
return login_and_redirect(req, db, login, key)
|
||||
|
||||
|
||||
class WebClient(openerpweb.Controller):
|
||||
_cp_path = "/web/webclient"
|
||||
|
||||
|
@ -726,7 +773,7 @@ class WebClient(openerpweb.Controller):
|
|||
@openerpweb.jsonrequest
|
||||
def version_info(self, req):
|
||||
return {
|
||||
"version": common.release.version
|
||||
"version": openerp.release.version
|
||||
}
|
||||
|
||||
class Proxy(openerpweb.Controller):
|
||||
|
@ -842,12 +889,10 @@ class Session(openerpweb.Controller):
|
|||
@openerpweb.jsonrequest
|
||||
def authenticate(self, req, db, login, password, base_location=None):
|
||||
wsgienv = req.httprequest.environ
|
||||
release = common.release
|
||||
env = dict(
|
||||
base_location=base_location,
|
||||
HTTP_HOST=wsgienv['HTTP_HOST'],
|
||||
REMOTE_ADDR=wsgienv['REMOTE_ADDR'],
|
||||
user_agent="%s / %s" % (release.name, release.version),
|
||||
)
|
||||
req.session.authenticate(db, login, password, env)
|
||||
|
||||
|
@ -922,8 +967,8 @@ class Session(openerpweb.Controller):
|
|||
no group by should be performed)
|
||||
"""
|
||||
context, domain = eval_context_and_domain(req.session,
|
||||
common.nonliterals.CompoundContext(*(contexts or [])),
|
||||
common.nonliterals.CompoundDomain(*(domains or [])))
|
||||
nonliterals.CompoundContext(*(contexts or [])),
|
||||
nonliterals.CompoundDomain(*(domains or [])))
|
||||
|
||||
group_by_sequence = []
|
||||
for candidate in (group_by_seq or []):
|
||||
|
@ -1145,14 +1190,14 @@ class DataSet(openerpweb.Controller):
|
|||
|
||||
def _call_kw(self, req, model, method, args, kwargs):
|
||||
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])
|
||||
elif isinstance(args[i], common.nonliterals.BaseDomain):
|
||||
elif isinstance(args[i], nonliterals.BaseDomain):
|
||||
args[i] = req.session.eval_domain(args[i])
|
||||
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])
|
||||
elif isinstance(kwargs[k], common.nonliterals.BaseDomain):
|
||||
elif isinstance(kwargs[k], nonliterals.BaseDomain):
|
||||
kwargs[k] = req.session.eval_domain(kwargs[k])
|
||||
|
||||
# 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)
|
||||
else:
|
||||
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']:
|
||||
# Special case for id's
|
||||
|
@ -1416,12 +1461,12 @@ class SearchView(View):
|
|||
try:
|
||||
parsed_context = parse_context(filter["context"], req.session)
|
||||
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))
|
||||
|
||||
parsed_domain = parse_domain(filter["domain"], req.session)
|
||||
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))
|
||||
except Exception:
|
||||
logger.exception("Failed to parse custom filter %s in %s",
|
||||
|
@ -1912,7 +1957,7 @@ class Reports(View):
|
|||
|
||||
report_srv = req.session.proxy("report")
|
||||
context = req.session.eval_context(
|
||||
common.nonliterals.CompoundContext(
|
||||
nonliterals.CompoundContext(
|
||||
req.context or {}, action[ "context"]))
|
||||
|
||||
report_data = {}
|
||||
|
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
@ -1,6 +1,16 @@
|
|||
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
|
||||
----------------
|
||||
|
|
@ -6,15 +6,18 @@ import ast
|
|||
import cgi
|
||||
import contextlib
|
||||
import functools
|
||||
import getpass
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import pprint
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
import urllib
|
||||
import urlparse
|
||||
import uuid
|
||||
import xmlrpclib
|
||||
|
||||
|
@ -26,18 +29,15 @@ import werkzeug.utils
|
|||
import werkzeug.wrappers
|
||||
import werkzeug.wsgi
|
||||
|
||||
from . import nonliterals
|
||||
from . import session
|
||||
from . import openerplib
|
||||
import urlparse
|
||||
import openerp
|
||||
|
||||
__all__ = ['Root', 'jsonrequest', 'httprequest', 'Controller',
|
||||
'WebRequest', 'JsonRequest', 'HttpRequest']
|
||||
import nonliterals
|
||||
import session
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
#----------------------------------------------------------
|
||||
# OpenERP Web RequestHandler
|
||||
# RequestHandler
|
||||
#----------------------------------------------------------
|
||||
class WebRequest(object):
|
||||
""" 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
|
||||
:type request: :class:`werkzeug.wrappers.BaseRequest`
|
||||
:param config: configuration object
|
||||
|
||||
.. attribute:: httprequest
|
||||
|
||||
|
@ -58,10 +57,6 @@ class WebRequest(object):
|
|||
a :class:`~collections.Mapping` holding the HTTP session data for the
|
||||
current http session
|
||||
|
||||
.. attribute:: config
|
||||
|
||||
config parameter provided to the request object
|
||||
|
||||
.. attribute:: params
|
||||
|
||||
:class:`~collections.Mapping` of request parameters, not generally
|
||||
|
@ -85,11 +80,10 @@ class WebRequest(object):
|
|||
|
||||
``bool``, indicates whether the debug mode is active on the client
|
||||
"""
|
||||
def __init__(self, request, config):
|
||||
def __init__(self, request):
|
||||
self.httprequest = request
|
||||
self.httpresponse = None
|
||||
self.httpsession = request.session
|
||||
self.config = config
|
||||
|
||||
def init(self, params):
|
||||
self.params = dict(params)
|
||||
|
@ -98,7 +92,6 @@ class WebRequest(object):
|
|||
self.session = self.httpsession.get(self.session_id)
|
||||
if not self.session:
|
||||
self.httpsession[self.session_id] = self.session = session.OpenERPSession()
|
||||
self.session.config = self.config
|
||||
self.context = self.params.pop('context', None)
|
||||
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))
|
||||
response['id'] = self.jsonrequest.get('id')
|
||||
response["result"] = method(controller, self, **self.params)
|
||||
except openerplib.AuthenticationError:
|
||||
except session.AuthenticationError:
|
||||
error = {
|
||||
'code': 100,
|
||||
'message': "OpenERP Session Invalid",
|
||||
|
@ -238,8 +231,8 @@ def jsonrequest(f):
|
|||
beforehand)
|
||||
"""
|
||||
@functools.wraps(f)
|
||||
def json_handler(controller, request, config):
|
||||
return JsonRequest(request, config).dispatch(controller, f)
|
||||
def json_handler(controller, request):
|
||||
return JsonRequest(request).dispatch(controller, f)
|
||||
json_handler.exposed = True
|
||||
return json_handler
|
||||
|
||||
|
@ -325,13 +318,13 @@ def httprequest(f):
|
|||
and ``debug`` keys (which are stripped out beforehand)
|
||||
"""
|
||||
@functools.wraps(f)
|
||||
def http_handler(controller, request, config):
|
||||
return HttpRequest(request, config).dispatch(controller, f)
|
||||
def http_handler(controller, request):
|
||||
return HttpRequest(request).dispatch(controller, f)
|
||||
http_handler.exposed = True
|
||||
return http_handler
|
||||
|
||||
#----------------------------------------------------------
|
||||
# OpenERP Web Controller registration with a metaclass
|
||||
# Controller registration with a metaclass
|
||||
#----------------------------------------------------------
|
||||
addons_module = {}
|
||||
addons_manifest = {}
|
||||
|
@ -348,7 +341,7 @@ class Controller(object):
|
|||
__metaclass__ = ControllerType
|
||||
|
||||
#----------------------------------------------------------
|
||||
# OpenERP Web Session context manager
|
||||
# Session context manager
|
||||
#----------------------------------------------------------
|
||||
STORES = {}
|
||||
|
||||
|
@ -419,12 +412,13 @@ def session_context(request, storage_path, session_cookie='httpsessionid'):
|
|||
session_store.save(request.session)
|
||||
|
||||
#----------------------------------------------------------
|
||||
# OpenERP Web WSGI Application
|
||||
# WSGI Application
|
||||
#----------------------------------------------------------
|
||||
# Add potentially missing (older ubuntu) font mime types
|
||||
mimetypes.add_type('application/font-woff', '.woff')
|
||||
mimetypes.add_type('application/vnd.ms-fontobject', '.eot')
|
||||
mimetypes.add_type('application/x-font-ttf', '.ttf')
|
||||
|
||||
class DisableCacheMiddleware(object):
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
@ -449,45 +443,23 @@ class DisableCacheMiddleware(object):
|
|||
|
||||
class Root(object):
|
||||
"""Root WSGI application for the OpenERP Web Client.
|
||||
|
||||
:param options: mandatory initialization options object, must provide
|
||||
the following attributes:
|
||||
|
||||
``server_host`` (``str``)
|
||||
hostname of the OpenERP server to dispatch RPC to
|
||||
``server_port`` (``int``)
|
||||
RPC port of the OpenERP server
|
||||
``serve_static`` (``bool | None``)
|
||||
whether this application should serve the various
|
||||
addons's static files
|
||||
``storage_path`` (``str``)
|
||||
filesystem path where HTTP session data will be stored
|
||||
``dbfilter`` (``str``)
|
||||
only used in case the list of databases is requested
|
||||
by the server, will be filtered by this pattern
|
||||
"""
|
||||
def __init__(self, options):
|
||||
self.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)
|
||||
|
||||
def __init__(self):
|
||||
self.httpsession_cookie = 'httpsessionid'
|
||||
self.addons = {}
|
||||
|
||||
static_dirs = self._load_addons()
|
||||
if options.serve_static:
|
||||
app = werkzeug.wsgi.SharedDataMiddleware( self.dispatch, static_dirs)
|
||||
self.dispatch = DisableCacheMiddleware(app)
|
||||
app = werkzeug.wsgi.SharedDataMiddleware( self.dispatch, static_dirs)
|
||||
self.dispatch = DisableCacheMiddleware(app)
|
||||
|
||||
if options.session_storage:
|
||||
if not os.path.exists(options.session_storage):
|
||||
os.mkdir(options.session_storage, 0700)
|
||||
self.session_storage = options.session_storage
|
||||
try:
|
||||
username = getpass.getuser()
|
||||
except Exception:
|
||||
username = "unknown"
|
||||
self.session_storage = os.path.join(tempfile.gettempdir(), "oe-sessions-" + username)
|
||||
|
||||
if not os.path.exists(self.session_storage):
|
||||
os.mkdir(self.session_storage, 0700)
|
||||
_logger.debug('HTTP sessions stored in: %s', self.session_storage)
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
|
@ -512,7 +484,7 @@ class Root(object):
|
|||
response = werkzeug.exceptions.NotFound()
|
||||
else:
|
||||
with session_context(request, self.session_storage, self.httpsession_cookie) as session:
|
||||
result = handler( request, self.config)
|
||||
result = handler( request)
|
||||
|
||||
if isinstance(result, basestring):
|
||||
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
|
||||
"""
|
||||
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):
|
||||
if module not in addons_module:
|
||||
manifest_path = os.path.join(addons_path, module, '__openerp__.py')
|
||||
|
@ -579,4 +551,7 @@ class Root(object):
|
|||
ps = '/'
|
||||
return None
|
||||
|
||||
def wsgi_postload():
|
||||
openerp.wsgi.register_wsgi_handler(Root())
|
||||
|
||||
# vim:et:ts=4:sw=4:
|
|
@ -4,70 +4,58 @@ import babel
|
|||
import dateutil.relativedelta
|
||||
import logging
|
||||
import time
|
||||
import traceback
|
||||
import sys
|
||||
import xmlrpclib
|
||||
|
||||
import openerplib
|
||||
import openerp
|
||||
|
||||
from . import nonliterals
|
||||
import nonliterals
|
||||
|
||||
_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
|
||||
#----------------------------------------------------------
|
||||
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):
|
||||
"""
|
||||
An OpenERP RPC session, a given user can own multiple such sessions
|
||||
|
@ -87,7 +75,6 @@ class OpenERPSession(object):
|
|||
"""
|
||||
def __init__(self):
|
||||
self._creation_time = time.time()
|
||||
self.config = None
|
||||
self._db = False
|
||||
self._uid = False
|
||||
self._login = False
|
||||
|
@ -98,19 +85,27 @@ class OpenERPSession(object):
|
|||
self.domains_store = {}
|
||||
self.jsonp_requests = {} # FIXME use a LRU
|
||||
|
||||
def __getstate__(self):
|
||||
state = dict(self.__dict__)
|
||||
if "config" in state:
|
||||
del state['config']
|
||||
return state
|
||||
|
||||
def build_connection(self):
|
||||
conn = openerplib.Connection(self.config.connector, database=self._db, login=self._login,
|
||||
user_id=self._uid, password=self._password)
|
||||
return conn
|
||||
def send(self, service_name, method, *args):
|
||||
code_string = "warning -- %s\n\n%s"
|
||||
try:
|
||||
return openerp.netsvc.dispatch_rpc(service_name, method, args)
|
||||
except openerp.osv.osv.except_osv, e:
|
||||
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)
|
||||
|
||||
def proxy(self, service):
|
||||
return self.build_connection().get_service(service)
|
||||
return Service(self, service)
|
||||
|
||||
def bind(self, db, uid, login, password):
|
||||
self._db = db
|
||||
|
@ -119,7 +114,6 @@ class OpenERPSession(object):
|
|||
self._password = password
|
||||
|
||||
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)
|
||||
self.bind(db, uid, login, password)
|
||||
|
||||
|
@ -130,7 +124,12 @@ class OpenERPSession(object):
|
|||
"""
|
||||
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):
|
||||
if self._uid:
|
||||
|
@ -141,7 +140,7 @@ class OpenERPSession(object):
|
|||
|
||||
def execute(self, model, func, *l, **d):
|
||||
self.assert_valid()
|
||||
model = self.build_connection().get_model(model)
|
||||
model = self.model(model)
|
||||
r = getattr(model, func)(*l, **d)
|
||||
return r
|
||||
|
||||
|
@ -157,7 +156,8 @@ class OpenERPSession(object):
|
|||
:type model: str
|
||||
:rtype: a model object
|
||||
"""
|
||||
return self.build_connection().get_model(model)
|
||||
|
||||
return Model(self, model)
|
||||
|
||||
def get_context(self):
|
||||
""" Re-initializes the current user's session context (based on
|
||||
|
@ -167,7 +167,7 @@ class OpenERPSession(object):
|
|||
:returns: the new 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._fix_lang(self.context)
|
||||
return self.context
|
|
@ -68,10 +68,13 @@
|
|||
background-color: #f0f0f0;
|
||||
}
|
||||
.openerp thead th {
|
||||
border-right: 1px dotted #afafb6;
|
||||
border-left: 1px solid #dfdfdf;
|
||||
}
|
||||
.openerp thead th:last-child {
|
||||
border-right: none;
|
||||
.openerp thead th:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
.openerp thead th.null {
|
||||
border-left: none;
|
||||
}
|
||||
.openerp th, .openerp td {
|
||||
padding: 0;
|
||||
|
@ -515,27 +518,11 @@
|
|||
.openerp .oe_webclient .oe_star_on {
|
||||
color: gold;
|
||||
}
|
||||
.openerp 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 p.oe_grey {
|
||||
max-width: 650px;
|
||||
}
|
||||
.openerp header > span {
|
||||
margin-left: 4px;
|
||||
}
|
||||
.openerp header ul {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
}
|
||||
.openerp header .oe_button {
|
||||
margin: 3px 2px 1px;
|
||||
.openerp .oe_grey {
|
||||
color: #aaaaaa;
|
||||
}
|
||||
.openerp .oe_tag {
|
||||
border: 1px solid #afafb6;
|
||||
|
@ -2037,6 +2024,31 @@
|
|||
.openerp .oe_application .oe_form_sheet .oe_notebook_page {
|
||||
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 {
|
||||
margin: 5px 0 0 5px;
|
||||
width: 400px;
|
||||
|
@ -2048,11 +2060,6 @@
|
|||
margin: 0 auto;
|
||||
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 {
|
||||
color: #aaaaaa;
|
||||
max-width: 650px;
|
||||
|
@ -2246,7 +2253,7 @@
|
|||
.openerp .oe_form .oe_datepicker_root {
|
||||
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;
|
||||
}
|
||||
.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;
|
||||
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;
|
||||
}
|
||||
.openerp .oe_form_editable .oe_form .oe_form_field_float {
|
||||
.openerp .oe_form_editable .oe_form .oe_form_field_float input {
|
||||
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;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.openerp .oe_hidden_input_file {
|
||||
|
|
|
@ -180,9 +180,11 @@ $sheet-max-width: 860px
|
|||
font-weight: bold
|
||||
background-color: #f0f0f0
|
||||
th
|
||||
border-right: 1px dotted $tag-border
|
||||
&:last-child
|
||||
border-right: none
|
||||
border-left: 1px solid #dfdfdf
|
||||
&:first-child
|
||||
border-left: none
|
||||
&.null
|
||||
border-left: none
|
||||
th, td
|
||||
padding: 0
|
||||
text-align: left
|
||||
|
@ -450,21 +452,11 @@ $sheet-max-width: 860px
|
|||
text-decoration: none
|
||||
.oe_star_on
|
||||
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) {{{
|
||||
|
@ -825,7 +817,7 @@ $sheet-max-width: 860px
|
|||
vertical-align: top
|
||||
text-shadow: 0 1px 1px rgba(0,0,0,0.2)
|
||||
@include transition(all 0.2s ease-out)
|
||||
&:hover,
|
||||
&:hover
|
||||
background: rgba(0,0,0,0.2)
|
||||
text-shadow: black 0px 0px 3px
|
||||
color: white
|
||||
|
@ -885,7 +877,7 @@ $sheet-max-width: 860px
|
|||
vertical-align: top
|
||||
text-shadow: 0 1px 1px rgba(0,0,0,0.2)
|
||||
@include transition(all 0.2s ease-out)
|
||||
&:hover,
|
||||
&:hover
|
||||
background: rgba(0,0,0,0.2)
|
||||
text-shadow: black 0px 0px 3px
|
||||
color: white
|
||||
|
@ -1613,6 +1605,22 @@ $sheet-max-width: 860px
|
|||
.oe_notebook_page
|
||||
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 {{{
|
||||
.oe_form header .oe_tags
|
||||
margin: 5px 0 0 5px
|
||||
|
@ -1623,10 +1631,6 @@ $sheet-max-width: 860px
|
|||
max-width: $sheet-max-width
|
||||
margin: 0 auto
|
||||
padding: 16px 0 48px
|
||||
.oe_grey
|
||||
color: #aaa
|
||||
max-width: 650px
|
||||
margin: 0 0 10px 0
|
||||
div.oe_form_configuration
|
||||
p, ul, ol
|
||||
color: #aaa
|
||||
|
@ -1780,7 +1784,7 @@ $sheet-max-width: 860px
|
|||
.oe_datepicker_root
|
||||
display: inline-block
|
||||
.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
|
||||
.oe_form_invalid
|
||||
input, select, textarea
|
||||
|
@ -1821,13 +1825,13 @@ $sheet-max-width: 860px
|
|||
|
||||
.oe_form_editable
|
||||
.oe_form
|
||||
.oe_form_field_integer
|
||||
.oe_form_field_integer input
|
||||
width: 6em !important
|
||||
.oe_form_field_float
|
||||
.oe_form_field_float input
|
||||
width: 7em !important
|
||||
.oe_form_field_date
|
||||
.oe_form_field_date input
|
||||
width: 7.5em !important
|
||||
.oe_form_field_datetime
|
||||
.oe_form_field_datetime input
|
||||
width: 11.5em !important
|
||||
// }}}
|
||||
// FormView.fields_binary {{{
|
||||
|
|
|
@ -181,7 +181,7 @@ instance.web.Dialog = instance.web.Widget.extend({
|
|||
});
|
||||
|
||||
instance.web.CrashManager = instance.web.CallbackEnabled.extend({
|
||||
on_rpc_error: function(error) {
|
||||
rpc_error: function(error) {
|
||||
if (error.data.fault_code) {
|
||||
var split = ("" + error.data.fault_code).split('\n')[0].split(' -- ');
|
||||
if (split.length > 1) {
|
||||
|
@ -190,12 +190,12 @@ instance.web.CrashManager = instance.web.CallbackEnabled.extend({
|
|||
}
|
||||
}
|
||||
if (error.code === 200 && error.type) {
|
||||
this.on_managed_error(error);
|
||||
this.show_warning(error);
|
||||
} 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>'), {
|
||||
title: "OpenERP " + _.str.capitalize(error.type),
|
||||
buttons: [
|
||||
|
@ -203,7 +203,7 @@ instance.web.CrashManager = instance.web.CallbackEnabled.extend({
|
|||
]
|
||||
});
|
||||
},
|
||||
on_traceback: function(error) {
|
||||
show_error: function(error) {
|
||||
var self = this;
|
||||
var buttons = {};
|
||||
buttons[_t("Ok")] = function() {
|
||||
|
@ -219,8 +219,8 @@ instance.web.CrashManager = instance.web.CallbackEnabled.extend({
|
|||
}).open();
|
||||
dialog.$el.html(QWeb.render('CrashManager.error', {session: instance.session, error: error}));
|
||||
},
|
||||
on_javascript_exception: function(exception) {
|
||||
this.on_traceback({
|
||||
show_message: function(exception) {
|
||||
this.show_error({
|
||||
type: _t("Client Error"),
|
||||
message: exception,
|
||||
data: {debug: ""}
|
||||
|
@ -236,7 +236,7 @@ instance.web.Loading = instance.web.Widget.extend({
|
|||
this.blocked_ui = false;
|
||||
this.session.on("request", this, this.request_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() {
|
||||
this.on_rpc_event(-this.count);
|
||||
|
@ -392,7 +392,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
|
|||
do_create: function(form) {
|
||||
var self = this;
|
||||
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 client_action = {
|
||||
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 + " ?")) {
|
||||
return;
|
||||
}
|
||||
self.rpc("/web/database/drop", {'fields': fields}, function(result) {
|
||||
self.rpc("/web/database/drop", {'fields': fields}).then(function(result) {
|
||||
if (result.error) {
|
||||
self.display_error(result);
|
||||
return;
|
||||
|
@ -481,7 +481,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
|
|||
var self = this;
|
||||
self.rpc("/web/database/change_password", {
|
||||
'fields': $(form).serializeArray()
|
||||
}, function(result) {
|
||||
}).then(function(result) {
|
||||
if (result.error) {
|
||||
self.display_error(result);
|
||||
return;
|
||||
|
@ -627,6 +627,7 @@ instance.web.Reload = function(parent, params) {
|
|||
hash = "#menu_id=" + menu_id;
|
||||
}
|
||||
var url = l.protocol + "//" + l.host + l.pathname + search + hash;
|
||||
window.onerror = function() {};
|
||||
window.location = url;
|
||||
};
|
||||
instance.web.client_actions.add("reload", "instance.web.Reload");
|
||||
|
@ -660,7 +661,7 @@ instance.web.ChangePassword = instance.web.Widget.extend({
|
|||
submitHandler: function (form) {
|
||||
self.rpc("/web/session/change_password",{
|
||||
'fields': $(form).serializeArray()
|
||||
}, function(result) {
|
||||
}).then(function(result) {
|
||||
if (result.error) {
|
||||
self.display_error(result);
|
||||
return;
|
||||
|
@ -881,6 +882,7 @@ instance.web.UserMenu = instance.web.Widget.extend({
|
|||
on_action: function() {
|
||||
},
|
||||
on_menu_logout: function() {
|
||||
this.trigger('user_logout');
|
||||
},
|
||||
on_menu_settings: function() {
|
||||
var self = this;
|
||||
|
@ -957,7 +959,7 @@ instance.web.Client = instance.web.Widget.extend({
|
|||
show_common: function() {
|
||||
var self = this;
|
||||
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.appendTo(self.$el);
|
||||
self.loading = new instance.web.Loading(self);
|
||||
|
@ -1007,7 +1009,7 @@ instance.web.WebClient = instance.web.Client.extend({
|
|||
var self = this;
|
||||
this._super();
|
||||
window.onerror = function (message, file, line) {
|
||||
self.crashmanager.on_traceback({
|
||||
self.crashmanager.show_error({
|
||||
type: _t("Client Error"),
|
||||
message: message,
|
||||
data: {debug: file + ':' + line}
|
||||
|
@ -1016,15 +1018,13 @@ instance.web.WebClient = instance.web.Client.extend({
|
|||
},
|
||||
show_login: function() {
|
||||
this.toggle_bars(false);
|
||||
|
||||
|
||||
var state = $.bbq.getState(true);
|
||||
var action = {
|
||||
'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.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.user_menu = new instance.web.UserMenu(self);
|
||||
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.do_update();
|
||||
self.bind_hashchange();
|
||||
|
@ -1165,7 +1165,7 @@ instance.web.EmbeddedClient = instance.web.Client.extend({
|
|||
var self = this;
|
||||
return $.when(this._super()).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;
|
||||
action.flags = _.extend({
|
||||
//views_switcher : false,
|
||||
|
|
|
@ -121,6 +121,10 @@ openerp.web.corelib = function(instance) {
|
|||
|
||||
// The dummy class constructor
|
||||
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
|
||||
if (!initializing && this.init) {
|
||||
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) {
|
||||
var def = $.Deferred().then(success, error);
|
||||
var self = this;
|
||||
instance.session.rpc(url, data). then(function() {
|
||||
instance.session.rpc(url, data).then(function() {
|
||||
if (!self.isDestroyed())
|
||||
def.resolve.apply(def, arguments);
|
||||
}, function() {
|
||||
|
@ -976,7 +980,8 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
|||
triggers: {
|
||||
'request': 'Request sent',
|
||||
'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
|
||||
|
@ -1300,7 +1305,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
|||
* @param {Function} error_callback function to execute on RPC call failure
|
||||
* @returns {jQuery.Deferred} jquery-provided ajax deferred
|
||||
*/
|
||||
rpc: function(url, params, success_callback, error_callback) {
|
||||
rpc: function(url, params) {
|
||||
var self = this;
|
||||
// url can be an $.ajax option object
|
||||
if (_.isString(url)) {
|
||||
|
@ -1317,8 +1322,6 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
|||
};
|
||||
var deferred = $.Deferred();
|
||||
this.trigger('request', url, payload);
|
||||
var aborter = params.aborter;
|
||||
delete params.aborter;
|
||||
var request = this.rpc_function(url, payload).then(
|
||||
function (response, textStatus, jqXHR) {
|
||||
self.trigger('response', response);
|
||||
|
@ -1334,7 +1337,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
|||
}
|
||||
},
|
||||
function(jqXHR, textStatus, errorThrown) {
|
||||
self.trigger('error');
|
||||
self.trigger('response_failed', jqXHR);
|
||||
var error = {
|
||||
code: -32098,
|
||||
message: "XmlHttpRequestError " + errorThrown,
|
||||
|
@ -1342,24 +1345,14 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
|||
};
|
||||
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
|
||||
deferred.fail(function() {
|
||||
deferred.fail(function(error, event) {
|
||||
if (!event.isDefaultPrevented()) {
|
||||
self.on_rpc_error(error, event);
|
||||
self.trigger('error', error, event);
|
||||
}
|
||||
});
|
||||
}).then(success_callback, error_callback).promise();
|
||||
});
|
||||
return deferred;
|
||||
},
|
||||
/**
|
||||
|
@ -1442,13 +1435,15 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
|||
return deferred;
|
||||
}
|
||||
},
|
||||
on_rpc_error: function(error) {
|
||||
},
|
||||
get_url: function (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:
|
||||
|
|
|
@ -19,9 +19,9 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
|
|||
this.name = instance._session_id;
|
||||
this.qweb_mutex = new $.Mutex();
|
||||
},
|
||||
rpc: function(url, params, success_callback, error_callback) {
|
||||
rpc: function(url, params) {
|
||||
params.session_id = this.session_id;
|
||||
return this._super(url, params, success_callback, error_callback);
|
||||
return this._super(url, params);
|
||||
},
|
||||
/**
|
||||
* Setup a sessionm
|
||||
|
|
|
@ -575,6 +575,7 @@ instance.web.DataSet = instance.web.CallbackEnabled.extend({
|
|||
* @param {Number|String} ids identifier of the record to delete
|
||||
*/
|
||||
unlink: function(ids) {
|
||||
this.trigger('unlink', ids);
|
||||
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
|
||||
* @returns {$.Deferred}
|
||||
*/
|
||||
call: function (method, args, callback, error_callback) {
|
||||
return this._model.call(method, args).then(callback, error_callback);
|
||||
},
|
||||
/**
|
||||
* 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 || []
|
||||
});
|
||||
call: function (method, args) {
|
||||
return this._model.call(method, args);
|
||||
},
|
||||
/**
|
||||
* 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({
|
||||
init: function(parent, model, context, ids) {
|
||||
var self = this;
|
||||
this._super(parent, model, context);
|
||||
// all local records
|
||||
this.ids = ids || [];
|
||||
|
@ -729,12 +713,10 @@ instance.web.DataSetStatic = instance.web.DataSet.extend({
|
|||
}
|
||||
},
|
||||
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});
|
||||
},
|
||||
on_unlink: function(ids) {
|
||||
this.set_ids(_.without.apply(null, [this.ids].concat(ids)));
|
||||
}
|
||||
});
|
||||
|
||||
instance.web.DataSetSearch = instance.web.DataSet.extend({
|
||||
|
|
|
@ -51,7 +51,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
|
|||
self.rpc("/web/export/get_fields", {
|
||||
model: self.dataset.model,
|
||||
import_compat: Boolean(import_comp)
|
||||
}, function (records) {
|
||||
}).then(function (records) {
|
||||
got_fields.resolve();
|
||||
self.on_show_data(records);
|
||||
});
|
||||
|
@ -59,7 +59,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
|
|||
|
||||
return $.when(
|
||||
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());
|
||||
},
|
||||
do_setup_export_formats: function (formats) {
|
||||
|
@ -93,7 +93,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
|
|||
self.$el.find('#fields_list option').remove();
|
||||
var export_id = self.$el.find('#saved_export_list option:selected').val();
|
||||
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() {
|
||||
|
@ -183,7 +183,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
|
|||
import_compat: Boolean(import_comp),
|
||||
parent_field_type : record['field_type'],
|
||||
exclude: exclude_fields
|
||||
}, function(results) {
|
||||
}).then(function(results) {
|
||||
record.loaded = true;
|
||||
self.on_show_data(results, record.id);
|
||||
});
|
||||
|
|
|
@ -1693,7 +1693,7 @@ instance.web.search.Advanced = instance.web.search.Input.extend({
|
|||
});
|
||||
return $.when(
|
||||
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({
|
||||
id: { string: 'ID', type: 'id' }
|
||||
}, data.fields);
|
||||
|
|
|
@ -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.init_pager();
|
||||
});
|
||||
self.on("load_record", self, self.load_record);
|
||||
instance.web.bus.on('clear_uncommitted_changes', this, function(e) {
|
||||
if (!this.can_be_discarded()) {
|
||||
e.preventDefault();
|
||||
|
@ -172,10 +173,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
} else {
|
||||
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_edit',this.on_button_edit);
|
||||
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_create', this.on_button_create);
|
||||
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_cancel', this.on_button_cancel);
|
||||
|
||||
this.$sidebar = this.options.$sidebar || this.$el.find('.oe_form_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');
|
||||
return self.dataset.read_index(fields, {
|
||||
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() {
|
||||
|
@ -354,7 +357,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
}
|
||||
this._super();
|
||||
},
|
||||
on_record_loaded: function(record) {
|
||||
load_record: function(record) {
|
||||
var self = this, set_values = [];
|
||||
if (!record) {
|
||||
this.set({ 'title' : undefined });
|
||||
|
@ -413,12 +416,14 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
* @return {$.Deferred}
|
||||
*/
|
||||
load_defaults: function () {
|
||||
var self = this;
|
||||
var keys = _.keys(this.fields_view.fields);
|
||||
if (keys.length) {
|
||||
return this.dataset.default_get(keys)
|
||||
.pipe(this.on_record_loaded);
|
||||
return this.dataset.default_get(keys).pipe(function(r) {
|
||||
self.trigger('load_record', r);
|
||||
});
|
||||
}
|
||||
return this.on_record_loaded({});
|
||||
return self.trigger('load_record', {});
|
||||
},
|
||||
on_form_changed: function() {
|
||||
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() {
|
||||
this.$el.add(this.$buttons).addClass('oe_form_dirty');
|
||||
},
|
||||
on_pager_action: function(action) {
|
||||
execute_pager_action: function(action) {
|
||||
if (this.can_be_discarded()) {
|
||||
switch (action) {
|
||||
case 'first':
|
||||
|
@ -443,6 +448,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
break;
|
||||
}
|
||||
this.reload();
|
||||
this.trigger('pager_action_executed');
|
||||
}
|
||||
},
|
||||
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() {
|
||||
var action = $(this).data('pager-action');
|
||||
self.on_pager_action(action);
|
||||
self.execute_pager_action(action);
|
||||
});
|
||||
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);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
instance.webclient.crashmanager.on_javascript_exception(e);
|
||||
instance.webclient.crashmanager.show_message(e);
|
||||
return $.Deferred().reject();
|
||||
}
|
||||
});
|
||||
|
@ -645,7 +651,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
return $.Deferred().resolve();
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
instance.webclient.crashmanager.on_javascript_exception(e);
|
||||
instance.webclient.crashmanager.show_message(e);
|
||||
return $.Deferred().reject();
|
||||
}
|
||||
},
|
||||
|
@ -742,7 +748,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
},
|
||||
on_button_save: function() {
|
||||
var self = this;
|
||||
return this.do_save().then(function(result) {
|
||||
return this.save().then(function(result) {
|
||||
self.trigger("save");
|
||||
self.to_view_mode();
|
||||
});
|
||||
},
|
||||
|
@ -752,9 +759,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
this.trigger('history_back');
|
||||
} else {
|
||||
this.to_view_mode();
|
||||
this.on_record_loaded(this.datarecord);
|
||||
this.trigger('load_record', this.datarecord);
|
||||
}
|
||||
}
|
||||
this.trigger('on_button_cancel');
|
||||
return false;
|
||||
},
|
||||
on_button_new: function() {
|
||||
|
@ -778,7 +786,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
var def = $.Deferred();
|
||||
$.when(this.has_been_loaded).then(function() {
|
||||
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() {
|
||||
return self.to_edit_mode();
|
||||
}).then(function() {
|
||||
|
@ -793,7 +801,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
$.when(this.has_been_loaded).then(function() {
|
||||
if (self.datarecord.id && confirm(_t("Do you really want to delete this record?"))) {
|
||||
self.dataset.unlink([self.datarecord.id]).then(function() {
|
||||
self.on_pager_action('next');
|
||||
self.execute_pager_action('next');
|
||||
def.resolve();
|
||||
});
|
||||
} 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
|
||||
* 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
|
||||
* default, records are added at the end)
|
||||
*/
|
||||
do_save: function(prepend_on_create) {
|
||||
save: function(prepend_on_create) {
|
||||
var self = this;
|
||||
return this.mutating_mutex.exec(function() { return self.is_initialized.pipe(function() {
|
||||
try {
|
||||
|
@ -846,10 +854,6 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
}
|
||||
if (form_invalid) {
|
||||
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();
|
||||
self.on_invalid();
|
||||
return $.Deferred().reject();
|
||||
|
@ -859,7 +863,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
if (!self.datarecord.id) {
|
||||
// Creation save
|
||||
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);
|
||||
} else if (_.isEmpty(values) && ! self.force_dirty) {
|
||||
// Not dirty, noop save
|
||||
|
@ -868,7 +872,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
self.force_dirty = false;
|
||||
// Write save
|
||||
save_deferral = self.dataset.write(self.datarecord.id, values, {}).pipe(function(r) {
|
||||
return self.on_saved(r);
|
||||
return self.record_saved(r);
|
||||
}, null);
|
||||
}
|
||||
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.
|
||||
*/
|
||||
on_saved: function(r) {
|
||||
record_saved: function(r) {
|
||||
var self = this;
|
||||
if (!r) {
|
||||
// should not happen in the server, but may happen for internal purpose
|
||||
this.trigger('record_saved', r);
|
||||
return $.Deferred().reject();
|
||||
} else {
|
||||
return $.when(this.reload()).pipe(function () {
|
||||
self.trigger('record_saved', 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
|
||||
* 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) {
|
||||
// should not happen in the server, but may happen for internal purpose
|
||||
this.trigger('record_created', r);
|
||||
return $.Deferred().reject();
|
||||
} else {
|
||||
this.datarecord.id = r;
|
||||
|
@ -934,9 +943,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
this.do_update_pager();
|
||||
if (this.sidebar) {
|
||||
this.sidebar.do_attachement_update(this.dataset, this.datarecord.id);
|
||||
}
|
||||
}
|
||||
//openerp.log("The record has been created with id #" + this.datarecord.id);
|
||||
return $.when(this.reload()).pipe(function () {
|
||||
self.trigger('record_created', r);
|
||||
return _.extend(r, {created: true});
|
||||
});
|
||||
}
|
||||
|
@ -948,7 +958,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
var self = this;
|
||||
return this.reload_mutex.exec(function() {
|
||||
if (self.dataset.index == null) {
|
||||
self.do_prev_view();
|
||||
self.trigger("previous_view");
|
||||
return $.Deferred().reject().promise();
|
||||
}
|
||||
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 {
|
||||
var fields = _.keys(self.fields_view.fields);
|
||||
fields.push('display_name');
|
||||
return self.dataset.read_index(fields, {
|
||||
context : { 'bin_size' : true, 'future_display_name' : true }
|
||||
}).pipe(self.on_record_loaded);
|
||||
return self.dataset.read_index(fields,
|
||||
{
|
||||
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() {
|
||||
var self = this;
|
||||
return $.when(this.do_save()).pipe(function(res) {
|
||||
return $.when(this.save()).pipe(function(res) {
|
||||
if (self.dataset.parent_view)
|
||||
return self.dataset.parent_view.recursive_save();
|
||||
});
|
||||
|
@ -1017,7 +1033,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
return true;
|
||||
},
|
||||
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 () {
|
||||
var self = this;
|
||||
|
@ -1082,7 +1098,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
field_to_set,
|
||||
self.fields[field_to_set].get_value(),
|
||||
all_users,
|
||||
false,
|
||||
true,
|
||||
condition || false
|
||||
]).then(function () { d.close(); });
|
||||
}}
|
||||
|
@ -1212,7 +1228,7 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
|
|||
|
||||
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");
|
||||
if (!self.fvg.fields[name]) {
|
||||
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.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) {
|
||||
var tag_name = $elem[0].tagName.toLowerCase();
|
||||
|
@ -1619,13 +1638,14 @@ instance.web.form.FormDialog = instance.web.Dialog.extend({
|
|||
return this;
|
||||
},
|
||||
start: function() {
|
||||
var self = this;
|
||||
this._super();
|
||||
this.form = new instance.web.FormView(this, this.dataset, this.view_id, {
|
||||
pager: false
|
||||
});
|
||||
this.form.appendTo(this.$el);
|
||||
this.form.on_created.add_last(this.on_form_dialog_saved);
|
||||
this.form.on_saved.add_last(this.on_form_dialog_saved);
|
||||
this.form.on('record_created', self, this.on_form_dialog_saved);
|
||||
this.form.on('record_saved', this, this.on_form_dialog_saved);
|
||||
return this;
|
||||
},
|
||||
select_id: function(id) {
|
||||
|
@ -1642,6 +1662,8 @@ instance.web.form.FormDialog = instance.web.Dialog.extend({
|
|||
});
|
||||
|
||||
instance.web.form.compute_domain = function(expr, fields) {
|
||||
if (! (expr instanceof Array))
|
||||
return !! expr;
|
||||
var stack = [];
|
||||
for (var i = expr.length - 1; i >= 0; 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.widget = this.node.attrs.widget;
|
||||
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.on("change:value", this, function() {
|
||||
|
@ -2099,6 +2121,8 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w
|
|||
this._set_required();
|
||||
}
|
||||
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();
|
||||
},
|
||||
/**
|
||||
|
@ -2164,11 +2188,12 @@ instance.web.form.ReinitializeWidgetMixin = {
|
|||
this.initialize_field();
|
||||
},
|
||||
initialize_field: function() {
|
||||
this.on("change:effective_readonly", this, function() {
|
||||
this.destroy_content();
|
||||
this.renderElement();
|
||||
this.initialize_content();
|
||||
});
|
||||
this.on("change:effective_readonly", this, this.reinitialize);
|
||||
this.initialize_content();
|
||||
},
|
||||
reinitialize: function() {
|
||||
this.destroy_content();
|
||||
this.renderElement();
|
||||
this.initialize_content();
|
||||
},
|
||||
/**
|
||||
|
@ -2189,9 +2214,10 @@ instance.web.form.ReinitializeWidgetMixin = {
|
|||
instance.web.form.ReinitializeFieldMixin = _.extend({}, instance.web.form.ReinitializeWidgetMixin, {
|
||||
initialize_field: function() {
|
||||
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();
|
||||
},
|
||||
/**
|
||||
|
@ -2211,7 +2237,7 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.we
|
|||
var self = this;
|
||||
var $input = this.$el.find('input');
|
||||
$input.change(function() {
|
||||
self.set({'value': instance.web.parse_value($input.val(), self)});
|
||||
self.set({'value': self.parse_value($input.val())});
|
||||
});
|
||||
this.setupFocus($input);
|
||||
},
|
||||
|
@ -2220,20 +2246,20 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.we
|
|||
this.render_value();
|
||||
},
|
||||
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")) {
|
||||
this.$el.find('input').val(show_value);
|
||||
} else {
|
||||
if (this.password) {
|
||||
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() {
|
||||
if (!this.get("effective_readonly")) {
|
||||
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;
|
||||
} catch(e) {
|
||||
return false;
|
||||
|
@ -2241,6 +2267,12 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.we
|
|||
}
|
||||
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() {
|
||||
return this.get('value') === '' || this._super();
|
||||
},
|
||||
|
@ -2347,7 +2379,10 @@ instance.web.DateTimeWidget = instance.web.Widget.extend({
|
|||
var self = this;
|
||||
this.$input = this.$el.find('input.oe_datepicker_master');
|
||||
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({
|
||||
onClose: this.on_picker_select,
|
||||
onSelect: this.on_picker_select,
|
||||
|
@ -2415,9 +2450,10 @@ instance.web.DateTimeWidget = instance.web.Widget.extend({
|
|||
format_client: function(v) {
|
||||
return instance.web.format_value(v, {"widget": this.type_of_date});
|
||||
},
|
||||
on_change: function() {
|
||||
datetime_changed: function() {
|
||||
if (this.is_valid_()) {
|
||||
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() {
|
||||
if (!this.get("effective_readonly")) {
|
||||
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));
|
||||
this.datewidget.appendTo(this.$el);
|
||||
|
@ -2845,7 +2881,7 @@ instance.web.form.CompletionFieldMixin = {
|
|||
self.build_domain(),
|
||||
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.focus();
|
||||
});
|
||||
|
@ -2958,7 +2994,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
|
|||
title: _t("Open: ") + self.string
|
||||
}
|
||||
);
|
||||
pop.on_write_completed.add_last(function() {
|
||||
pop.on('write_completed', self, function(){
|
||||
self.display_value = {};
|
||||
self.render_value();
|
||||
self.focus();
|
||||
|
@ -3284,11 +3320,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
|
|||
var views = [];
|
||||
_.each(modes, function(mode) {
|
||||
if (! _.include(["list", "tree", "graph", "kanban"], mode)) {
|
||||
try {
|
||||
throw new Error(_.str.sprintf("View type '%s' is not supported in One2Many.", mode));
|
||||
} catch(e) {
|
||||
instance.webclient.crashmanager.on_javascript_exception(e)
|
||||
}
|
||||
throw new Error(_.str.sprintf("View type '%s' is not supported in One2Many.", mode));
|
||||
}
|
||||
var view = {
|
||||
view_id: false,
|
||||
|
@ -3344,7 +3376,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
|
|||
var def = $.Deferred().then(function() {
|
||||
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;
|
||||
if (view_type == "list") {
|
||||
if (self.get("effective_readonly")) {
|
||||
|
@ -3356,21 +3388,22 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
|
|||
if (self.get("effective_readonly")) {
|
||||
$(".oe_form_buttons", controller.$el).children().remove();
|
||||
}
|
||||
controller.on_record_loaded.add_last(function() {
|
||||
once.resolve();
|
||||
});
|
||||
controller.on_pager_action.add_first(function() {
|
||||
self.save_any_view();
|
||||
});
|
||||
controller.on("load_record", self, function(){
|
||||
once.resolve();
|
||||
});
|
||||
controller.on('pager_action_executed',self,self.save_any_view);
|
||||
} else if (view_type == "graph") {
|
||||
self.reload_current_view()
|
||||
}
|
||||
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() {
|
||||
if(n_mode === "list")
|
||||
$.async_when().then(function() {self.reload_current_view();});
|
||||
if (n_mode === "list") {
|
||||
$.async_when().then(function() {
|
||||
self.reload_current_view();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
this.is_setted.then(function() {
|
||||
|
@ -3493,7 +3526,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
|
|||
if (!view.is_initialized.isResolved()) {
|
||||
return false;
|
||||
}
|
||||
var res = $.when(view.do_save());
|
||||
var res = $.when(view.save());
|
||||
if (!res.isResolved() && !res.isRejected()) {
|
||||
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;
|
||||
},
|
||||
switch_view: function(mode, unused) {
|
||||
switch_mode: function(mode, unused) {
|
||||
if (mode !== 'form') {
|
||||
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},
|
||||
readonly: self.o2m.get("effective_readonly")
|
||||
});
|
||||
pop.on_select_elements.add_last(function() {
|
||||
pop.on("select_elements", self, function() {
|
||||
self.o2m.reload_current_view();
|
||||
});
|
||||
},
|
||||
|
@ -3653,7 +3686,7 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
|
|||
self.o2m.build_domain(),
|
||||
self.o2m.build_context()
|
||||
);
|
||||
pop.on_select_elements.add_last(function() {
|
||||
pop.on("select_elements", self, function() {
|
||||
self.o2m.reload_current_view();
|
||||
});
|
||||
}
|
||||
|
@ -3689,7 +3722,7 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
|
|||
var self = this;
|
||||
this.ensure_saved().pipe(function () {
|
||||
if (parent_form)
|
||||
return parent_form.do_save();
|
||||
return parent_form.save();
|
||||
else
|
||||
return $.when();
|
||||
}).then(function () {
|
||||
|
@ -3792,7 +3825,7 @@ instance.web.form.One2ManyList = instance.web.ListView.List.extend({
|
|||
colspan: columns,
|
||||
'class': 'oe_form_field_one2many_list_row_add'
|
||||
}).append(
|
||||
$('<a>', {href: '#'}).text(_t("Add a row"))
|
||||
$('<a>', {href: '#'}).text(_t("Add an item"))
|
||||
.mousedown(function () {
|
||||
// FIXME: needs to be an official API somehow
|
||||
if (self.view.editor.is_editing()) {
|
||||
|
@ -3828,7 +3861,7 @@ instance.web.form.One2ManyFormView = instance.web.FormView.extend({
|
|||
this._super(data);
|
||||
var self = this;
|
||||
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() {
|
||||
|
@ -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.m2m = this;
|
||||
this.dataset.on_unlink.add_last(function(ids) {
|
||||
this.dataset.on('unlink', self, function(ids) {
|
||||
self.dataset_changed();
|
||||
});
|
||||
|
||||
|
@ -4090,7 +4123,7 @@ instance.web.form.Many2ManyListView = instance.web.ListView.extend(/** @lends in
|
|||
this.m2m_field.build_context()
|
||||
);
|
||||
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) {
|
||||
if(! _.detect(self.dataset.ids, function(x) {return x == 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,
|
||||
readonly: this.getParent().get("effective_readonly")
|
||||
});
|
||||
pop.on_write_completed.add_last(function() {
|
||||
self.reload_content();
|
||||
});
|
||||
pop.on('write_completed', self, 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.m2m = this;
|
||||
this.dataset.on_unlink.add_last(function(ids) {
|
||||
this.dataset.on('unlink', self, function(ids) {
|
||||
self.dataset_changed();
|
||||
});
|
||||
|
||||
|
@ -4178,7 +4209,7 @@ instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend(
|
|||
self.initial_is_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 () {
|
||||
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]]),
|
||||
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) {
|
||||
if(! _.detect(self.dataset.ids, function(x) {return x == 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) {
|
||||
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.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.start();
|
||||
},
|
||||
on_write_completed: function() {},
|
||||
setup_form_view: function() {
|
||||
var self = this;
|
||||
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");
|
||||
$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.on_button_new();
|
||||
});
|
||||
|
@ -4413,7 +4445,7 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
|
|||
});
|
||||
var $sbutton = self.$buttonpane.find(".oe_abstractformpopup-form-save");
|
||||
$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.check_exit();
|
||||
});
|
||||
|
@ -4426,11 +4458,12 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
|
|||
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) {
|
||||
if (this.created_elements.length > 0) {
|
||||
this.on_select_elements(this.created_elements);
|
||||
this.elements_selected(this.created_elements);
|
||||
this.created_elements = [];
|
||||
}
|
||||
this.destroy();
|
||||
|
@ -4486,7 +4519,7 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend
|
|||
self.rpc('/web/session/eval_domain_and_context', {
|
||||
domains: [],
|
||||
contexts: [this.context]
|
||||
}, function (results) {
|
||||
}).then(function (results) {
|
||||
var search_defaults = {};
|
||||
_.each(results.context, function (value_, 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");
|
||||
$sbutton.click(function() {
|
||||
self.on_select_elements(self.selected_ids);
|
||||
self.elements_selected(self.selected_ids);
|
||||
self.destroy();
|
||||
});
|
||||
});
|
||||
|
@ -4554,7 +4587,7 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend
|
|||
domains: domains || [],
|
||||
contexts: contexts || [],
|
||||
group_by_seq: groupbys || []
|
||||
}, function (results) {
|
||||
}).then(function (results) {
|
||||
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();
|
||||
},
|
||||
select_record: function(index) {
|
||||
this.popup.on_select_elements([this.dataset.ids[index]]);
|
||||
this.popup.elements_selected([this.dataset.ids[index]]);
|
||||
this.popup.destroy();
|
||||
},
|
||||
do_select: function(ids, records) {
|
||||
|
@ -4611,6 +4644,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan
|
|||
destroy_content: function() {
|
||||
if (this.fm) {
|
||||
this.fm.destroy();
|
||||
this.fm = undefined;
|
||||
}
|
||||
},
|
||||
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')}),
|
||||
}});
|
||||
this.selection.on("change:value", this, this.on_selection_changed);
|
||||
this.selection.setElement(this.$(".oe_form_view_reference_selection"));
|
||||
this.selection.renderElement();
|
||||
this.selection.start();
|
||||
this.selection.appendTo(this.$(".oe_form_view_reference_selection"));
|
||||
this.selection
|
||||
.on('focused', null, function () {self.trigger('focused')})
|
||||
.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')}),
|
||||
}});
|
||||
this.m2o.on("change:value", this, this.data_changed);
|
||||
this.m2o.setElement(this.$(".oe_form_view_reference_m2o"));
|
||||
this.m2o.renderElement();
|
||||
this.m2o.start();
|
||||
this.m2o.appendTo(this.$(".oe_form_view_reference_m2o"));
|
||||
this.m2o
|
||||
.on('focused', null, function () {self.trigger('focused')})
|
||||
.on('blurred', null, function () {self.trigger('blurred')});
|
||||
|
@ -4790,9 +4820,9 @@ instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({
|
|||
this._super();
|
||||
if (this.get("effective_readonly")) {
|
||||
var self = this;
|
||||
this.$el.find('a').click(function() {
|
||||
this.$el.find('a').click(function(ev) {
|
||||
if (self.get('value')) {
|
||||
self.on_save_as();
|
||||
self.on_save_as(ev);
|
||||
}
|
||||
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`.
|
||||
*
|
||||
|
@ -5019,7 +5087,8 @@ instance.web.form.widgets = new instance.web.Registry({
|
|||
'progressbar': 'instance.web.form.FieldProgressBar',
|
||||
'image': 'instance.web.form.FieldBinaryImage',
|
||||
'binary': 'instance.web.form.FieldBinaryFile',
|
||||
'statusbar': 'instance.web.form.FieldStatus'
|
||||
'statusbar': 'instance.web.form.FieldStatus',
|
||||
'monetary': 'instance.web.form.FieldMonetary',
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
@ -482,7 +482,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
|
|||
view_type: "tree",
|
||||
context: this.dataset.get_context(context),
|
||||
toolbar: !!this.options.$sidebar
|
||||
}, callback);
|
||||
}).then(callback);
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
|
|
@ -626,7 +626,7 @@ openerp.web.list_editable = function (instance) {
|
|||
},
|
||||
start: function () {
|
||||
var self = this;
|
||||
var _super = this._super();
|
||||
var _super = this._super();
|
||||
this.form.embedded_view = this._validate_view(
|
||||
this.delegate.edition_view(this));
|
||||
var form_ready = this.form.appendTo(this.$el).then(
|
||||
|
@ -708,10 +708,9 @@ openerp.web.list_editable = function (instance) {
|
|||
var self = this;
|
||||
var form = self.form;
|
||||
var loaded = record
|
||||
? form.on_record_loaded(_.extend({}, record))
|
||||
? form.trigger('load_record', _.extend({}, record))
|
||||
: form.load_defaults();
|
||||
|
||||
return loaded.pipe(function () {
|
||||
return $.when(loaded).pipe(function () {
|
||||
return form.do_show({reload: false});
|
||||
}).pipe(function () {
|
||||
self.record = form.datarecord;
|
||||
|
@ -725,7 +724,7 @@ openerp.web.list_editable = function (instance) {
|
|||
save: function () {
|
||||
var self = this;
|
||||
return this.form
|
||||
.do_save(this.delegate.prepends_on_create())
|
||||
.save(this.delegate.prepends_on_create())
|
||||
.pipe(function (result) {
|
||||
var created = result.created && !self.record.id;
|
||||
if (created) {
|
||||
|
|
|
@ -45,7 +45,7 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
|
|||
view_type: "tree",
|
||||
toolbar: this.view_manager ? !!this.view_manager.sidebar : false,
|
||||
context: this.dataset.get_context()
|
||||
}, this.on_loaded);
|
||||
}).then(this.on_loaded);
|
||||
},
|
||||
/**
|
||||
* Returns the list of fields needed to correctly read objects.
|
||||
|
|
|
@ -417,7 +417,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
|||
this._super();
|
||||
var self = this;
|
||||
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();
|
||||
var views_ids = {};
|
||||
_.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
|
||||
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);
|
||||
},
|
||||
/**
|
||||
* 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) {
|
||||
switch_mode: function(view_type, no_store, view_options) {
|
||||
var self = this;
|
||||
var view = this.views[view_type];
|
||||
var view_promise;
|
||||
var form = this.views['form'];
|
||||
if (!view || (form && form.controller && !form.controller.can_be_discarded())) {
|
||||
self.trigger('switch_mode', view_type, no_store, view_options);
|
||||
return $.Deferred().reject();
|
||||
}
|
||||
|
||||
if (!no_store) {
|
||||
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.$el
|
||||
.find('.oe_view_manager_switch a').parent().removeClass('active');
|
||||
this.$el.find('.oe_view_manager_switch a').parent().removeClass('active');
|
||||
this.$el
|
||||
.find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]')
|
||||
.parent().addClass('active');
|
||||
|
||||
return $.when(view_promise).then(function () {
|
||||
r = $.when(view_promise).then(function () {
|
||||
_.each(_.keys(self.views), function(view_name) {
|
||||
var controller = self.views[view_name].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) {
|
||||
// Lazy loading of views
|
||||
|
@ -530,20 +524,20 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
|||
if (view.embedded_view) {
|
||||
controller.set_embedded_view(view.embedded_view);
|
||||
}
|
||||
controller.do_switch_view.add_last(_.bind(this.switch_view, this));
|
||||
|
||||
controller.do_prev_view.add_last(this.on_prev_view);
|
||||
controller.on('switch_mode', self, this.switch_mode);
|
||||
controller.on('previous_view', self, this.prev_view);
|
||||
|
||||
var container = this.$el.find(".oe_view_manager_view_" + view_type);
|
||||
var view_promise = controller.appendTo(container);
|
||||
this.views[view_type].controller = controller;
|
||||
this.views[view_type].deferred.resolve(view_type);
|
||||
return $.when(view_promise).then(function() {
|
||||
self.on_controller_inited(view_type, controller);
|
||||
if (self.searchview
|
||||
&& self.flags.auto_search
|
||||
&& view.controller.searchable !== false) {
|
||||
self.searchview.ready.then(self.searchview.do_search);
|
||||
}
|
||||
self.trigger("controller_inited",view_type,controller);
|
||||
});
|
||||
},
|
||||
set_title: function(title) {
|
||||
|
@ -552,7 +546,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
|||
add_breadcrumb: function(on_reverse_breadcrumb) {
|
||||
var self = this;
|
||||
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];
|
||||
if (mode !== last) {
|
||||
if (mode !== 'form') {
|
||||
|
@ -568,7 +562,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
|||
var view_to_select = views[index];
|
||||
self.$el.show();
|
||||
if (self.active_view !== view_to_select) {
|
||||
self.on_mode_switch(view_to_select);
|
||||
self.switch_mode(view_to_select);
|
||||
}
|
||||
},
|
||||
get_title: function() {
|
||||
|
@ -598,37 +592,29 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
|||
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
|
||||
* navigation history (the navigation history is appended to via
|
||||
* on_mode_switch)
|
||||
* switch_mode)
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.created=false] resource was created
|
||||
* @param {String} [options.default=null] view to switch to if no previous view
|
||||
* @returns {$.Deferred} switching end signal
|
||||
*/
|
||||
on_prev_view: function (options) {
|
||||
prev_view: function (options) {
|
||||
options = options || {};
|
||||
var current_view = this.views_history.pop();
|
||||
var previous_view = this.views_history[this.views_history.length - 1] || options['default'];
|
||||
if (options.created && current_view === 'form' && previous_view === '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)"
|
||||
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') {
|
||||
// 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.
|
||||
|
@ -654,7 +640,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
|||
domains: [this.action.domain || []].concat(domains || []),
|
||||
contexts: [action_context].concat(contexts || []),
|
||||
group_by_seq: groupbys || []
|
||||
}, function (results) {
|
||||
}).then(function (results) {
|
||||
self.dataset._model = new instance.web.Model(
|
||||
self.dataset.model, results.context, results.domain);
|
||||
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 || []);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
@ -813,13 +791,11 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
|
|||
});
|
||||
break;
|
||||
case 'fields':
|
||||
this.dataset.call_and_eval(
|
||||
'fields_get', [false, {}], null, 1).then(function (fields) {
|
||||
this.dataset.call('fields_get', [false, {}]).then(function (fields) {
|
||||
var $root = $('<dl>');
|
||||
_(fields).each(function (attributes, name) {
|
||||
$root.append($('<dt>').append($('<h4>').text(name)));
|
||||
var $attrs = $('<dl>').appendTo(
|
||||
$('<dd>').appendTo($root));
|
||||
var $attrs = $('<dl>').appendTo($('<dd>').appendTo($root));
|
||||
_(attributes).each(function (def, name) {
|
||||
if (def instanceof Object) {
|
||||
def = JSON.stringify(def);
|
||||
|
@ -899,7 +875,7 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
|
|||
}, 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;
|
||||
|
||||
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) {
|
||||
defs.push(
|
||||
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", {
|
||||
action_id: item.action.id,
|
||||
context: additional_context
|
||||
}, function(result) {
|
||||
result.result.context = _.extend(result.result.context || {},
|
||||
}).then(function(result) {
|
||||
result.context = _.extend(result.context || {},
|
||||
additional_context);
|
||||
result.result.flags = result.result.flags || {};
|
||||
result.result.flags.new_window = true;
|
||||
self.do_action(result.result, function () {
|
||||
result.flags = result.flags || {};
|
||||
result.flags.new_window = true;
|
||||
self.do_action(result, function () {
|
||||
// reload view
|
||||
self.getParent().reload();
|
||||
});
|
||||
|
@ -1239,7 +1215,7 @@ instance.web.View = instance.web.Widget.extend({
|
|||
args.push(context);
|
||||
return dataset.call_button(action_data.name, args).then(handler);
|
||||
} 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 {
|
||||
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
|
||||
*/
|
||||
do_switch_view: function(view) {
|
||||
this.trigger('switch_mode',view);
|
||||
},
|
||||
/**
|
||||
* 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 {String} [options.default=null] view to switch to if no previous view
|
||||
*/
|
||||
do_prev_view: function (options) {
|
||||
},
|
||||
|
||||
do_search: function(view) {
|
||||
},
|
||||
on_sidebar_export: function() {
|
||||
|
|
|
@ -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">
|
||||
<t t-raw="item.label"/>
|
||||
</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 t-if="section.name == 'files'" class="oe_sidebar_add_attachment">
|
||||
<t t-call="HiddenInputFile">
|
||||
|
@ -916,7 +916,7 @@
|
|||
</t>
|
||||
<t t-name="FieldChar">
|
||||
<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'"
|
||||
t-att-id="widget.id_for_label"
|
||||
t-att-tabindex="widget.node.attrs.tabindex"
|
||||
|
@ -925,6 +925,9 @@
|
|||
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"/>
|
||||
</t>
|
||||
<t t-if="widget.get('effective_readonly')">
|
||||
<span class="oe_form_char_content"></span>
|
||||
</t>
|
||||
</span>
|
||||
</t>
|
||||
<t t-name="FieldEmail">
|
||||
|
@ -1534,7 +1537,7 @@
|
|||
<td colspan="3">
|
||||
<label for="import_compat">Export Type:</label>
|
||||
<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>
|
||||
</select>
|
||||
|
||||
|
@ -1717,4 +1720,16 @@
|
|||
<button class="oe_form_m2o_sc_button oe_button">Add All Info...</button>
|
||||
<button class="oe_form_m2o_cancel_button oe_button">Cancel</button>
|
||||
</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>
|
||||
|
|
|
@ -82,14 +82,14 @@ $(document).ready(function () {
|
|||
});
|
||||
t.test('call', function (openerp) {
|
||||
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.args.length, 3);
|
||||
deepEqual(r.args, ['a', 'b', 42]);
|
||||
|
||||
ok(_.isEmpty(r.kwargs));
|
||||
});
|
||||
}));
|
||||
});
|
||||
t.test('name_get').then(function (openerp) {
|
||||
var ds = new openerp.web.DataSet({session: openerp.session}, 'mod');
|
||||
|
|
|
@ -4,7 +4,7 @@ import mock
|
|||
import unittest2
|
||||
|
||||
from ..controllers import main
|
||||
from ..common.session import OpenERPSession
|
||||
from ..session import OpenERPSession
|
||||
|
||||
class Placeholder(object):
|
||||
def __init__(self, **kwargs):
|
||||
|
|
|
@ -6,7 +6,7 @@ import unittest2
|
|||
import simplejson
|
||||
|
||||
import web.controllers.main
|
||||
from ..common import nonliterals, session as s
|
||||
from .. import nonliterals, session as s
|
||||
|
||||
def field_attrs(fields_view_get, fieldname):
|
||||
(field,) = filter(lambda f: f['attrs'].get('name') == fieldname,
|
||||
|
|
|
@ -997,7 +997,6 @@ div.openerp .dhx_cal_editor textarea {
|
|||
}
|
||||
.openerp .dhx_cal_event_selected{
|
||||
background-color: #757575;
|
||||
color: ffffff;
|
||||
}
|
||||
/* Agenda week end */
|
||||
.openerp .dhx_scale_bar_header {
|
||||
|
|
|
@ -41,7 +41,7 @@ instance.web_calendar.CalendarView = instance.web.View.extend({
|
|||
},
|
||||
start: function() {
|
||||
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() {
|
||||
scheduler.clearAll();
|
||||
|
@ -458,8 +458,8 @@ instance.web_calendar.CalendarFormDialog = instance.web.Dialog.extend({
|
|||
pager: false
|
||||
});
|
||||
var def = this.form.appendTo(this.$el);
|
||||
this.form.on_created.add_last(this.on_form_dialog_saved);
|
||||
this.form.on_saved.add_last(this.on_form_dialog_saved);
|
||||
this.form.on('record_created', self, this.on_form_dialog_saved);
|
||||
this.form.on('record_saved', self, this.on_form_dialog_saved);
|
||||
this.form.on_button_cancel = function() {
|
||||
self.close();
|
||||
}
|
||||
|
|
|
@ -1,21 +1,14 @@
|
|||
try:
|
||||
# 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
|
||||
import openerp
|
||||
|
||||
class DiagramView(View):
|
||||
class DiagramView(openerp.addons.web.controllers.main.View):
|
||||
_cp_path = "/web_diagram/diagram"
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
@openerp.addons.web.http.jsonrequest
|
||||
def load(self, req, model, view_id):
|
||||
fields_view = self.fields_view_get(req, model, view_id, 'diagram')
|
||||
return {'fields_view': fields_view}
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
@openerp.addons.web.http.jsonrequest
|
||||
def get_diagram_info(self, req, id, model, node, connector,
|
||||
src_node, des_node, label, **kw):
|
||||
|
||||
|
|
|
@ -49,5 +49,5 @@
|
|||
-ms-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ instance.web.DiagramView = instance.web.View.extend({
|
|||
|
||||
this.$el.find('div.oe_diagram_pager button[data-pager-action]').click(function() {
|
||||
var action = $(this).data('pager-action');
|
||||
self.on_pager_action(action);
|
||||
self.execute_pager_action(action);
|
||||
});
|
||||
|
||||
this.do_update_pager();
|
||||
|
@ -105,8 +105,7 @@ instance.web.DiagramView = instance.web.View.extend({
|
|||
});
|
||||
|
||||
this.rpc(
|
||||
'/web_diagram/diagram/get_diagram_info',params,
|
||||
function(result) {
|
||||
'/web_diagram/diagram/get_diagram_info',params).then(function(result) {
|
||||
self.draw_diagram(result);
|
||||
}
|
||||
);
|
||||
|
@ -240,23 +239,23 @@ instance.web.DiagramView = instance.web.View.extend({
|
|||
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);
|
||||
});
|
||||
|
||||
|
||||
var form_fields = [self.parent_field];
|
||||
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) {
|
||||
if (!(fld in form_controller.fields)) { return; }
|
||||
var field = form_controller.fields[fld];
|
||||
field.$input.prop('disabled', true);
|
||||
field.$drop_down.unbind();
|
||||
field.$menu_btn.unbind();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
},
|
||||
|
||||
// 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.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);
|
||||
});
|
||||
|
||||
var form_controller = pop.view_form;
|
||||
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) {
|
||||
if (!(fld in form_controller.fields)) { return; }
|
||||
var field = form_controller.fields[fld];
|
||||
|
@ -304,7 +303,7 @@ instance.web.DiagramView = instance.web.View.extend({
|
|||
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);
|
||||
});
|
||||
},
|
||||
|
@ -326,8 +325,7 @@ instance.web.DiagramView = instance.web.View.extend({
|
|||
this.dataset.domain,
|
||||
this.context || this.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);
|
||||
});
|
||||
// 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;
|
||||
|
||||
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].dirty = true;
|
||||
form_controller.fields[self.connectors.attrs.destination].set_value(node_dest_id);
|
||||
form_controller.fields[self.connectors.attrs.destination].dirty = true;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
on_pager_action: function(action) {
|
||||
execute_pager_action: function(action) {
|
||||
switch (action) {
|
||||
case 'first':
|
||||
this.dataset.index = 0;
|
||||
|
@ -381,7 +380,7 @@ instance.web.DiagramView = instance.web.View.extend({
|
|||
|
||||
do_show: function() {
|
||||
this.do_push_state({});
|
||||
return $.when(this._super(), this.on_pager_action('reload'));
|
||||
return $.when(this._super(), this.execute_pager_action('reload'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -211,9 +211,7 @@ instance.web_gantt.GanttView = instance.web.View.extend({
|
|||
on_task_display: function(task) {
|
||||
var self = this;
|
||||
var pop = new instance.web.form.FormOpenPopup(self);
|
||||
pop.on_write_completed.add_last(function() {
|
||||
self.reload();
|
||||
});
|
||||
pop.on('write_completed',self,self.reload);
|
||||
pop.show_element(
|
||||
self.dataset.model,
|
||||
task.id,
|
||||
|
@ -224,7 +222,7 @@ instance.web_gantt.GanttView = instance.web.View.extend({
|
|||
on_task_create: function() {
|
||||
var self = this;
|
||||
var pop = new instance.web.form.SelectCreatePopup(this);
|
||||
pop.on_select_elements.add_last(function() {
|
||||
pop.on("select_elements", self, function() {
|
||||
self.reload();
|
||||
});
|
||||
pop.select_element(
|
||||
|
|
|
@ -1,19 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
try:
|
||||
# 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
|
||||
import openerp
|
||||
|
||||
from lxml import etree
|
||||
|
||||
class GraphView(View):
|
||||
class GraphView(openerp.addons.web.controllers.main.View):
|
||||
_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):
|
||||
obj = req.session.model(model)
|
||||
|
||||
|
|
|
@ -194,9 +194,9 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
|
|||
});
|
||||
var am = instance.webclient.action_manager;
|
||||
var form = am.dialog_widget.views.form.controller;
|
||||
form.on_button_cancel.add_last(am.dialog.on_close);
|
||||
form.on_created.add_last(function(r) {
|
||||
(new instance.web.DataSet(self, self.group_by_field.relation)).name_get([r.result]).then(function(new_record) {
|
||||
form.on("on_button_cancel", self, am.dialog.on_close);
|
||||
form.on('record_created', self, function(r) {
|
||||
(new instance.web.DataSet(self, self.group_by_field.relation)).name_get([r]).then(function(new_record) {
|
||||
am.dialog.on_close();
|
||||
var domain = self.dataset.domain.slice(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 form = am.dialog_widget.views.form.controller;
|
||||
form.on_button_cancel.add_last(am.dialog.on_close);
|
||||
form.on_saved.add_last(function() {
|
||||
form.on("on_button_cancel", self, am.dialog.on_close);
|
||||
form.on('record_saved', self, function() {
|
||||
am.dialog.on_close();
|
||||
self.view.do_reload();
|
||||
});
|
||||
|
@ -1067,7 +1067,7 @@ instance.web_kanban.QuickCreate = instance.web.Widget.extend({
|
|||
[],
|
||||
{"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.trigger('added', element_ids[0]);
|
||||
});
|
||||
|
|
|
@ -23,14 +23,14 @@ openerp.web_tests = function (instance) {
|
|||
on_everything_loaded: function (slice) {
|
||||
var records = slice[0].records;
|
||||
if (!records.length) {
|
||||
this.form.on_record_loaded({});
|
||||
this.form.trigger("load_record", {});
|
||||
return;
|
||||
}
|
||||
this.form.on_record_loaded(records[0]);
|
||||
this.form.trigger("load_record", records[0]);
|
||||
_(records.slice(1)).each(function (record, index) {
|
||||
this.dataset.index = index+1;
|
||||
this.form.reposition($('<div>').appendTo(this.$el));
|
||||
this.form.on_record_loaded(record);
|
||||
this.form.trigger("load_record", record);
|
||||
}, this);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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 model_dataset = new instance.web.DataSetSearch(this, 'ir.model', null, null);
|
||||
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) {
|
||||
if (_.include(_.keys(fields), 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;
|
||||
property_to_check.push(value);
|
||||
});
|
||||
field_dataset.call( 'fields_get', [], function(result) {
|
||||
field_dataset.call( 'fields_get', []).then(function(result) {
|
||||
var fields = _.keys(result);
|
||||
fields.push(" "),fields.sort();
|
||||
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);
|
||||
$.when(action_manager.do_action(action)).then(function() {
|
||||
var controller = action_manager.dialog_widget.views['form'].controller;
|
||||
controller.on_button_cancel.add_last(function(){
|
||||
action_manager.destroy()
|
||||
controller.on("on_button_cancel", self, function(){
|
||||
action_manager.destroy();
|
||||
});
|
||||
controller.do_save.add_last(function(){
|
||||
controller.on("save", self, function(){
|
||||
action_manager.destroy();
|
||||
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));
|
||||
|
|
27
logging.json
27
logging.json
|
@ -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"]
|
||||
}
|
||||
}
|
116
openerp-web
116
openerp-web
|
@ -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)
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[global]
|
||||
|
||||
|
124
setup.py
124
setup.py
|
@ -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:
|
Loading…
Reference in New Issue