[MERGE]Merge Trunk
bzr revid: vja@tinyerp.com-20121011102837-k0y1kvf901rp3l8u
This commit is contained in:
commit
ffe8dd604a
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
|
||||
|
|
@ -8,9 +8,4 @@ The OpenERP Web Client supports the following web browsers:
|
|||
* Firefox 13+
|
||||
* Any browser using the latest version of Chrome Frame
|
||||
|
||||
To build the documentation use:
|
||||
|
||||
$ make doc
|
||||
|
||||
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
|
||||
|
@ -612,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"
|
||||
|
||||
|
@ -746,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):
|
||||
|
@ -862,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)
|
||||
|
||||
|
@ -942,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 []):
|
||||
|
@ -1165,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()
|
||||
|
@ -1303,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
|
||||
|
@ -1436,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",
|
||||
|
@ -1932,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
|
|
@ -25,7 +25,6 @@
|
|||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.openerp.openerp_webclient_container {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
@ -46,7 +45,7 @@
|
|||
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5);
|
||||
/* http://www.quirksmode.org/dom/inputfile.html
|
||||
* http://stackoverflow.com/questions/2855589/replace-input-type-file-by-an-image
|
||||
*/ */
|
||||
*/
|
||||
}
|
||||
.openerp :-moz-placeholder {
|
||||
color: #afafb6 !important;
|
||||
|
@ -69,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;
|
||||
|
@ -522,28 +524,6 @@
|
|||
.openerp .oe_grey {
|
||||
color: #aaaaaa;
|
||||
}
|
||||
.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 header > span {
|
||||
margin-left: 4px;
|
||||
}
|
||||
.openerp header ul {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
}
|
||||
.openerp header .oe_button {
|
||||
margin: 3px 2px 1px;
|
||||
}
|
||||
.openerp .oe_tag {
|
||||
border: 1px solid #afafb6;
|
||||
font-size: 11px;
|
||||
|
@ -1204,7 +1184,7 @@
|
|||
color: white;
|
||||
padding: 2px 4px;
|
||||
margin: 1px 6px 0 0;
|
||||
border: 1px solid lightGray;
|
||||
border: 1px solid lightgrey;
|
||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
|
@ -1229,7 +1209,7 @@
|
|||
transform: scale(1.1);
|
||||
}
|
||||
.openerp .oe_secondary_submenu .oe_active {
|
||||
border-top: 1px solid lightGray;
|
||||
border-top: 1px solid lightgrey;
|
||||
border-bottom: 1px solid #dedede;
|
||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2), inset 0 -1px 3px rgba(40, 40, 40, 0.2);
|
||||
|
@ -2044,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;
|
||||
|
@ -2125,7 +2130,7 @@
|
|||
}
|
||||
.openerp .oe_form .oe_form_label_help[for] span, .openerp .oe_form .oe_form_label[for] span {
|
||||
font-size: 80%;
|
||||
color: darkGreen;
|
||||
color: darkgreen;
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
top: -4px;
|
||||
|
@ -2294,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
|
||||
|
@ -457,21 +459,6 @@ $sheet-max-width: 860px
|
|||
|
||||
// }}}
|
||||
|
||||
// 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) {{{
|
||||
.oe_tag
|
||||
border: 1px solid $tag-border
|
||||
|
@ -1618,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
|
||||
|
@ -1822,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);
|
||||
|
@ -957,7 +957,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 +1007,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 +1016,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() {
|
||||
|
|
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -1332,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,
|
||||
|
@ -1344,7 +1349,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
|||
deferred.fail(function() {
|
||||
deferred.fail(function(error, event) {
|
||||
if (!event.isDefaultPrevented()) {
|
||||
self.on_rpc_error(error, event);
|
||||
self.trigger('error', error, event);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -1430,8 +1435,6 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
|||
return deferred;
|
||||
}
|
||||
},
|
||||
on_rpc_error: function(error) {
|
||||
},
|
||||
get_url: function (file) {
|
||||
return this.prefix + file;
|
||||
},
|
||||
|
|
|
@ -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()});
|
||||
},
|
||||
/**
|
||||
|
@ -690,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 || [];
|
||||
|
@ -711,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({
|
||||
|
|
|
@ -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");
|
||||
|
@ -610,7 +615,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 +650,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();
|
||||
}
|
||||
},
|
||||
|
@ -752,9 +757,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 +784,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({ result : new_id });
|
||||
}).then(function() {
|
||||
return self.to_edit_mode();
|
||||
}).then(function() {
|
||||
|
@ -855,7 +861,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
|
||||
|
@ -864,7 +870,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;
|
||||
|
@ -891,12 +897,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;
|
||||
});
|
||||
}
|
||||
|
@ -914,9 +923,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;
|
||||
|
@ -930,9 +941,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});
|
||||
});
|
||||
}
|
||||
|
@ -952,9 +964,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);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -1618,13 +1636,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) {
|
||||
|
@ -2068,7 +2087,11 @@ 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 || '{}');
|
||||
try {
|
||||
this.options = JSON.parse(this.node.attrs.options || '{}');
|
||||
} catch (e) {
|
||||
throw new Error(_.str.sprintf(_t("Widget options for field '%s' are not valid JSON."), this.name));
|
||||
}
|
||||
this.set({'value': false});
|
||||
|
||||
this.on("change:value", this, function() {
|
||||
|
@ -2165,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();
|
||||
},
|
||||
/**
|
||||
|
@ -2190,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();
|
||||
},
|
||||
/**
|
||||
|
@ -2212,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);
|
||||
},
|
||||
|
@ -2221,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;
|
||||
|
@ -2242,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();
|
||||
},
|
||||
|
@ -2959,7 +2990,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
|
|||
title: _t("Open: ") + self.string
|
||||
}
|
||||
);
|
||||
pop.on('on_write_complete', self, function(){
|
||||
pop.on('write_completed', self, function(){
|
||||
self.display_value = {};
|
||||
self.render_value();
|
||||
self.focus();
|
||||
|
@ -3285,11 +3316,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,
|
||||
|
@ -3357,9 +3384,9 @@ 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("load_record", self, function(){
|
||||
once.resolve();
|
||||
});
|
||||
controller.on_pager_action.add_first(function() {
|
||||
self.save_any_view();
|
||||
});
|
||||
|
@ -3368,10 +3395,13 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
|
|||
}
|
||||
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() {
|
||||
|
@ -3539,7 +3569,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);
|
||||
}
|
||||
|
@ -3793,7 +3823,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()) {
|
||||
|
@ -3996,7 +4026,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();
|
||||
});
|
||||
|
||||
|
@ -4108,7 +4138,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('on_write_complete', self, self.reload_content);
|
||||
pop.on('write_completed', self, self.reload_content);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -4129,7 +4159,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();
|
||||
});
|
||||
|
||||
|
@ -4177,7 +4207,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);
|
||||
});
|
||||
|
@ -4356,7 +4386,7 @@ 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(function() {
|
||||
self.trigger('on_write_complete');
|
||||
self.trigger('write_completed');
|
||||
});
|
||||
};
|
||||
this.dataset.parent_view = this.options.parent_view;
|
||||
|
@ -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',
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,7 +524,7 @@ 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.on('switch_mode', self, this.switch_mode);
|
||||
|
||||
controller.do_prev_view.add_last(this.on_prev_view);
|
||||
var container = this.$el.find(".oe_view_manager_view_" + view_type);
|
||||
|
@ -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,18 +592,10 @@ 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
|
||||
|
@ -623,12 +609,12 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
|||
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.
|
||||
|
@ -897,7 +883,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 () {
|
||||
|
@ -932,7 +918,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);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -1271,6 +1257,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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -239,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
|
||||
|
@ -280,7 +280,7 @@ instance.web.DiagramView = instance.web.View.extend({
|
|||
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];
|
||||
|
@ -303,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);
|
||||
});
|
||||
},
|
||||
|
@ -339,12 +339,13 @@ 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) {
|
||||
|
|
|
@ -211,7 +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('on_write_complete',self,self.reload);
|
||||
pop.on('write_completed',self,self.reload);
|
||||
pop.show_element(
|
||||
self.dataset.model,
|
||||
task.id,
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1014,8 +1014,8 @@ 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(){
|
||||
action_manager.destroy();
|
||||
|
|
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