sma (Tiny) 2011-10-03 16:57:31 +05:30
commit 7e9e542418
54 changed files with 3332 additions and 1298 deletions

View File

@ -2,27 +2,26 @@ import common
import controllers
import common.dispatch
import logging
import optparse
_logger = logging.getLogger(__name__)
class Options(object):
pass
def wsgi_postload():
import openerp.wsgi
import openerp
import os
import tempfile
_logger.info("embedded mode")
class Options(object):
pass
o = Options()
o.dbfilter = '.*'
o.dbfilter = openerp.tools.config['dbfilter']
o.server_wide_modules = openerp.conf.server_wide_modules or ['web']
o.session_storage = os.path.join(tempfile.gettempdir(), "oe-sessions")
o.addons_path = os.path.dirname(os.path.dirname(__file__))
o.addons_path = openerp.modules.module.ad_paths
o.serve_static = True
o.backend = 'local'
app = common.dispatch.Root(o)
#import openerp.wsgi
openerp.wsgi.register_wsgi_handler(app)
# TODO
# if we detect that we are imported from the openerp server register common.Root() as a wsgi entry point

View File

@ -2,6 +2,7 @@
"name" : "web",
"depends" : [],
'active': True,
'post_load' : 'wsgi_postload',
'js' : [
"static/lib/datejs/globalization/en-US.js",
"static/lib/datejs/core.js",
@ -19,11 +20,13 @@
"static/lib/jquery.ui/js/jquery-ui-1.8.9.custom.min.js",
"static/lib/jquery.ui/js/jquery-ui-timepicker-addon.js",
"static/lib/jquery.ui.notify/js/jquery.notify.js",
"static/lib/jquery.deferred-queue/jquery.deferred-queue.js",
"static/lib/json/json2.js",
"static/lib/qweb/qweb2.js",
"static/lib/underscore/underscore.js",
"static/lib/underscore/underscore.string.js",
"static/lib/labjs/LAB.src.js",
"static/lib/py.parse/lib/py.js",
"static/src/js/boot.js",
"static/src/js/core.js",
"static/src/js/dates.js",
@ -32,6 +35,7 @@
"static/src/js/views.js",
"static/src/js/data.js",
"static/src/js/data_export.js",
"static/src/js/data_import.js",
"static/src/js/search.js",
"static/src/js/view_form.js",
"static/src/js/view_list.js",
@ -45,6 +49,6 @@
"static/lib/jquery.ui.notify/css/ui.notify.css",
"static/src/css/base.css",
"static/src/css/data_export.css",
"static/src/css/data_import.css",
],
'post_load' : 'wsgi_postload',
}

View File

@ -3,6 +3,7 @@ from __future__ import with_statement
import functools
import logging
import urllib
import os
import pprint
import sys
@ -13,7 +14,6 @@ import xmlrpclib
import simplejson
import werkzeug.datastructures
import werkzeug.exceptions
import werkzeug.urls
import werkzeug.utils
import werkzeug.wrappers
import werkzeug.wsgi
@ -21,8 +21,7 @@ import werkzeug.wsgi
import ast
import nonliterals
import http
# import backendlocal as backend
import session as backend
import session
import openerplib
__all__ = ['Root', 'jsonrequest', 'httprequest', 'Controller',
@ -34,7 +33,6 @@ _logger = logging.getLogger(__name__)
# Globals (wont move into a pool)
#-----------------------------------------------------------
applicationsession = {}
addons_module = {}
addons_manifest = {}
controllers_class = {}
@ -53,10 +51,6 @@ class WebRequest(object):
:type request: :class:`werkzeug.wrappers.BaseRequest`
:param config: configuration object
.. attribute:: applicationsession
an application-wide :class:`~collections.Mapping`
.. attribute:: httprequest
the original :class:`werkzeug.wrappers.Request` object provided to the
@ -79,12 +73,12 @@ class WebRequest(object):
.. attribute:: session_id
opaque identifier for the :class:`backend.OpenERPSession` instance of
opaque identifier for the :class:`session.OpenERPSession` instance of
the current request
.. attribute:: session
:class:`~backend.OpenERPSession` instance for the current request
:class:`~session.OpenERPSession` instance for the current request
.. attribute:: context
@ -95,18 +89,17 @@ class WebRequest(object):
``bool``, indicates whether the debug mode is active on the client
"""
def __init__(self, request, config):
self.applicationsession = applicationsession
self.httprequest = request
self.httpresponse = None
self.httpsession = request.session
self.config = config
def init(self, params):
self.params = dict(params)
# OpenERP session setup
self.session_id = self.params.pop("session_id", None) or uuid.uuid4().hex
self.session = self.httpsession.setdefault(
self.session_id, backend.OpenERPSession(
self.config.server_host, self.config.server_port))
self.session = self.httpsession.setdefault(self.session_id, session.OpenERPSession())
self.session.config = self.config
self.context = self.params.pop('context', None)
self.debug = self.params.pop('debug', False) != False
@ -317,9 +310,16 @@ class Root(object):
by the server, will be filtered by this pattern
"""
def __init__(self, options):
self.root = werkzeug.urls.Href('/web/webclient/home')
self.root = '/web/webclient/home'
self.config = options
if self.config.backend == 'local':
conn = openerplib.get_connector(protocol='local')
else:
conn = openerplib.get_connector(hostname=self.config.server_host,
port=self.config.server_port)
self.config.connector = conn
self.session_cookie = 'sessionid'
self.addons = {}
@ -349,13 +349,12 @@ class Root(object):
request.parameter_storage_class = werkzeug.datastructures.ImmutableDict
if request.path == '/':
return werkzeug.utils.redirect(
self.root(dict(request.args, debug='')), 301)(
environ, start_response)
params = urllib.urlencode(dict(request.args, debug=''))
return werkzeug.utils.redirect(self.root + '?' + params, 301)(
environ, start_response)
elif request.path == '/mobile':
return werkzeug.utils.redirect(
'/web_mobile/static/src/web_mobile.html', 301)(
environ, start_response)
'/web_mobile/static/src/web_mobile.html', 301)(environ, start_response)
handler = self.find_handler(*(request.path.split('/')[1:]))
@ -383,21 +382,21 @@ class Root(object):
static URLs to the corresponding directories
"""
statics = {}
addons_path = self.config.addons_path
if addons_path not in sys.path:
sys.path.insert(0, addons_path)
for module in os.listdir(addons_path):
if module not in addons_module:
manifest_path = os.path.join(addons_path, module, '__openerp__.py')
if os.path.isfile(manifest_path):
manifest = ast.literal_eval(open(manifest_path).read())
_logger.info("Loading %s", module)
m = __import__(module)
addons_module[module] = m
addons_manifest[module] = manifest
statics['/%s/static' % module] = \
os.path.join(addons_path, module, 'static')
for addons_path in self.config.addons_path:
if addons_path not in sys.path:
sys.path.insert(0, addons_path)
for module in os.listdir(addons_path):
if module not in addons_module:
manifest_path = os.path.join(addons_path, module, '__openerp__.py')
path_static = os.path.join(addons_path, module, 'static')
if os.path.isfile(manifest_path) and os.path.isdir(path_static):
manifest = ast.literal_eval(open(manifest_path).read())
manifest['addons_path'] = addons_path
_logger.info("Loading %s", module)
m = __import__(module)
addons_module[module] = m
addons_manifest[module] = manifest
statics['/%s/static' % module] = path_static
for k, v in controllers_class.items():
if k not in controllers_object:
o = v()

View File

@ -20,6 +20,7 @@ def session(request, storage_path, session_cookie='sessionid'):
else:
request.session = session_store.new()
yield request.session
session_store.save(request.session)
try:
yield request.session
finally:
session_store.save(request.session)

View File

@ -41,6 +41,9 @@ class NonLiteralEncoder(simplejson.encoder.JSONEncoder):
'__eval_context': object.get_eval_context()
}
raise TypeError('Could not encode unknown non-literal %s' % object)
_ALLOWED_KEYS = frozenset(['__ref', "__id", '__domains',
'__contexts', '__eval_context', 'own_values'])
def non_literal_decoder(dct):
""" Decodes JSON dicts into :class:`Domain` and :class:`Context` based on
@ -50,6 +53,9 @@ def non_literal_decoder(dct):
``own_values`` dict key.
"""
if '__ref' in dct:
for x in dct.keys():
if not x in _ALLOWED_KEYS:
raise ValueError("'%s' key not allowed in non literal domain/context" % x)
if dct['__ref'] == 'domain':
domain = Domain(None, key=dct['__id'])
if 'own_values' in dct:
@ -231,7 +237,10 @@ class CompoundContext(BaseContext):
def evaluate(self, context=None):
ctx = dict(context or {})
ctx.update(self.get_eval_context() or {})
eval_context = self.get_eval_context()
if eval_context:
eval_context = self.session.eval_context(eval_context)
ctx.update(eval_context)
final_context = {}
for context_to_eval in self.contexts:
if not isinstance(context_to_eval, (dict, BaseContext)):

View File

@ -38,6 +38,7 @@ Code repository: https://code.launchpad.net/~niv-openerp/openerp-client-lib/trun
import xmlrpclib
import logging
import socket
import sys
try:
import cPickle as pickle
@ -70,6 +71,14 @@ class Connector(object):
self.hostname = hostname
self.port = port
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.
@ -190,6 +199,31 @@ class NetRPCConnector(Connector):
socket.disconnect()
return result
class LocalConnector(Connector):
"""
A type of connector that uses the XMLRPC protocol.
"""
PROTOCOL = 'local'
__logger = _getChildLogger(_logger, 'connector.local')
def __init__(self):
pass
def send(self, service_name, method, *args):
import openerp
# TODO Exception handling
# This will be changed to be xmlrpc compatible
# OpenERPWarning code 1
# OpenERPException code 2
try:
result = openerp.netsvc.dispatch_rpc(service_name, method, args)
except:
exc_type, exc_value, exc_tb = sys.exc_info()
fault = xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value))
raise fault
return result
class Service(object):
"""
A class to execute RPC calls on a specific service of the remote server.
@ -295,7 +329,7 @@ class Connection(object):
:param service_name: The name of the service.
"""
return Service(self.connector, service_name)
return self.connector.get_service(service_name)
class AuthenticationError(Exception):
"""
@ -342,7 +376,7 @@ class Model(object):
index = {}
for r in result:
index[r['id']] = r
result = [index[x] for x in args[0]]
result = [index[x] for x in args[0] if x in index]
self.__logger.debug('result: %r', result)
return result
return proxy
@ -363,7 +397,7 @@ class Model(object):
records = self.read(record_ids, fields or [], context or {})
return records
def get_connector(hostname, protocol="xmlrpc", port="auto"):
def get_connector(hostname=None, protocol="xmlrpc", port="auto"):
"""
A shortcut method to easily create a connector to a remote server using XMLRPC or NetRPC.
@ -377,10 +411,12 @@ def get_connector(hostname, protocol="xmlrpc", port="auto"):
return XmlRPCConnector(hostname, port)
elif protocol == "netrpc":
return NetRPCConnector(hostname, port)
elif protocol == "local":
return LocalConnector()
else:
raise ValueError("You must choose xmlrpc or netrpc")
raise ValueError("You must choose xmlrpc or netrpc or local")
def get_connection(hostname, protocol="xmlrpc", port='auto', database=None,
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.

View File

@ -5,6 +5,9 @@ import time
import openerplib
import nonliterals
import logging
_logger = logging.getLogger(__name__)
#----------------------------------------------------------
# OpenERPSession RPC openerp backend access
#----------------------------------------------------------
@ -26,25 +29,26 @@ class OpenERPSession(object):
Used to store references to non-literal domains which need to be
round-tripped to the client browser.
"""
def __init__(self, server='127.0.0.1', port=8069):
self._server = server
self._port = port
def __init__(self):
self.config = None
self._db = False
self._uid = False
self._login = False
self._password = False
self._locale = 'en_US'
self.context = {}
self.contexts_store = {}
self.domains_store = {}
self._lang = {}
self.remote_timezone = 'utc'
self.client_timezone = False
def __getstate__(self):
state = dict(self.__dict__)
if "config" in state:
del state['config']
return state
def build_connection(self):
return openerplib.get_connection(hostname=self._server, port=self._port,
database=self._db, login=self._login,
user_id=self._uid, password=self._password)
conn = openerplib.Connection(self.config.connector, database=self._db, login=self._login,
user_id=self._uid, password=self._password)
return conn
def proxy(self, service):
return self.build_connection().get_service(service)
@ -99,15 +103,6 @@ class OpenERPSession(object):
self.context = self.model('res.users').context_get(self.context)
self.context = self.context or {}
self.client_timezone = self.context.get("tz", False)
# invalid code, anyway we decided the server will be in UTC
#if self.client_timezone:
# self.remote_timezone = self.execute('common', 'timezone_get')
self._locale = self.context.get('lang','en_US')
lang_ids = self.execute('res.lang','search', [('code', '=', self._locale)])
if lang_ids:
self._lang = self.execute('res.lang', 'read',lang_ids[0], [])
return self.context
@property

View File

@ -68,15 +68,7 @@ class Xml2Json:
# OpenERP Web web Controllers
#----------------------------------------------------------
def manifest_glob(addons_path, addons, key):
files = []
for addon in addons:
globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
for pattern in globlist:
for path in glob.glob(os.path.join(addons_path, addon, pattern)):
files.append(path[len(addons_path):])
return files
# TODO change into concat_file(addons,key) taking care of addons_path
def concat_files(addons_path, file_list):
""" Concatenate file content
return (concat,timestamp)
@ -104,7 +96,7 @@ home_template = textwrap.dedent("""<!DOCTYPE html>
%(javascript)s
<script type="text/javascript">
$(function() {
var c = new openerp.init();
var c = new openerp.init(%(modules)s);
var wc = new c.web.WebClient("oe");
wc.start();
});
@ -116,24 +108,45 @@ home_template = textwrap.dedent("""<!DOCTYPE html>
class WebClient(openerpweb.Controller):
_cp_path = "/web/webclient"
@openerpweb.jsonrequest
def csslist(self, req, mods='web'):
return manifest_glob(req.config.addons_path, mods.split(','), 'css')
def server_wide_modules(self, req):
addons = [i for i in req.config.server_wide_modules if i in openerpweb.addons_manifest]
return addons
def manifest_glob(self, req, addons, key):
if addons==None:
addons = self.server_wide_modules(req)
else:
addons = addons.split(',')
files = []
for addon in addons:
manifest = openerpweb.addons_manifest.get(addon, None)
if not manifest:
continue
addons_path = manifest['addons_path']
globlist = manifest.get(key, [])
for pattern in globlist:
for path in glob.glob(os.path.join(addons_path, addon, pattern)):
files.append(path[len(addons_path):])
return files
@openerpweb.jsonrequest
def jslist(self, req, mods='web'):
return manifest_glob(req.config.addons_path, mods.split(','), 'js')
def csslist(self, req, mods=None):
return self.manifest_glob(req, mods, 'css')
@openerpweb.jsonrequest
def jslist(self, req, mods=None):
return self.manifest_glob(req, mods, 'js')
@openerpweb.httprequest
def css(self, req, mods='web'):
files = manifest_glob(req.config.addons_path, mods.split(','), 'css')
def css(self, req, mods=None):
files = self.manifest_glob(req, mods, 'css')
content,timestamp = concat_files(req.config.addons_path, files)
# TODO request set the Date of last modif and Etag
return req.make_response(content, [('Content-Type', 'text/css')])
@openerpweb.httprequest
def js(self, req, mods='web'):
files = manifest_glob(req.config.addons_path, mods.split(','), 'js')
def js(self, req, mods=None):
files = self.manifest_glob(req, mods, 'js')
content,timestamp = concat_files(req.config.addons_path, files)
# TODO request set the Date of last modif and Etag
return req.make_response(content, [('Content-Type', 'application/javascript')])
@ -143,17 +156,19 @@ class WebClient(openerpweb.Controller):
# script tags
jslist = ['/web/webclient/js']
if req.debug:
jslist = [i + '?debug=' + str(time.time()) for i in manifest_glob(req.config.addons_path, ['web'], 'js')]
jslist = [i + '?debug=' + str(time.time()) for i in self.manifest_glob(req, None, 'js')]
js = "\n ".join(['<script type="text/javascript" src="%s"></script>'%i for i in jslist])
# css tags
csslist = ['/web/webclient/css']
if req.debug:
csslist = [i + '?debug=' + str(time.time()) for i in manifest_glob(req.config.addons_path, ['web'], 'css')]
csslist = [i + '?debug=' + str(time.time()) for i in self.manifest_glob(req, None, 'css')]
css = "\n ".join(['<link rel="stylesheet" href="%s">'%i for i in csslist])
r = home_template % {
'javascript': js,
'css': css
'css': css,
'modules': simplejson.dumps(self.server_wide_modules(req)),
}
return r
@ -166,20 +181,21 @@ class WebClient(openerpweb.Controller):
"grouping", "decimal_point", "thousands_sep"])
else:
lang_obj = None
if lang.count("_") > 0:
separator = "_"
else:
separator = "@"
langs = lang.split(separator)
langs = [separator.join(langs[:x]) for x in range(1, len(langs) + 1)]
transs = {}
for addon_name in mods:
transl = {"messages":[]}
transs[addon_name] = transl
for l in langs:
f_name = os.path.join(req.config.addons_path, addon_name, "po", l + ".po")
addons_path = openerpweb.addons_manifest[addon_name]['addons_path']
f_name = os.path.join(addons_path, addon_name, "po", l + ".po")
if not os.path.exists(f_name):
continue
try:
@ -240,7 +256,7 @@ class Database(openerpweb.Controller):
password, db = operator.itemgetter(
'drop_pwd', 'drop_db')(
dict(map(operator.itemgetter('name', 'value'), fields)))
try:
return req.session.proxy("db").drop(password, db)
except xmlrpclib.Fault, e:
@ -291,7 +307,7 @@ class Session(openerpweb.Controller):
@openerpweb.jsonrequest
def login(self, req, db, login, password):
req.session.login(db, login, password)
ctx = req.session.get_context()
ctx = req.session.get_context() if req.session._uid else {}
return {
"session_id": req.session_id,
@ -299,6 +315,7 @@ class Session(openerpweb.Controller):
"context": ctx,
"db": req.session._db
}
@openerpweb.jsonrequest
def get_session_info(self, req):
req.session.assert_valid(force=True)
@ -307,6 +324,7 @@ class Session(openerpweb.Controller):
"context": req.session.get_context() if req.session._uid else False,
"db": req.session._db
}
@openerpweb.jsonrequest
def change_password (self,req,fields):
old_password, new_password,confirm_password = operator.itemgetter('old_pwd', 'new_password','confirm_pwd')(
@ -322,6 +340,7 @@ class Session(openerpweb.Controller):
except:
return {'error': 'Original password incorrect, your password was not changed.', 'title': 'Change Password'}
return {'error': 'Error, password not changed !', 'title': 'Change Password'}
@openerpweb.jsonrequest
def sc_list(self, req):
return req.session.model('ir.ui.view_sc').get_sc(
@ -336,13 +355,14 @@ class Session(openerpweb.Controller):
}
except Exception, e:
return {"error": e, "title": "Languages"}
@openerpweb.jsonrequest
def modules(self, req):
# TODO query server for installed web modules
mods = []
for name, manifest in openerpweb.addons_manifest.items():
if name != 'web' and manifest.get('active', True):
# TODO replace by ir.module.module installed web
if name not in req.config.server_wide_modules and manifest.get('active', True):
mods.append(name)
return mods
@ -472,6 +492,9 @@ def clean_action(req, action):
if isinstance(action.get('domain'), basestring):
action['domain'] = eval( action['domain'], eval_ctx ) or []
if 'type' not in action:
action['type'] = 'ir.actions.act_window_close'
if action['type'] == 'ir.actions.act_window':
return fix_view_modes(action)
return action
@ -715,7 +738,7 @@ class DataSet(openerpweb.Controller):
args[domain_id] = d
if context_id and len(args) - 1 >= context_id:
args[context_id] = c
for i in xrange(len(args)):
if isinstance(args[i], web.common.nonliterals.BaseContext):
args[i] = req.session.eval_context(args[i])
@ -963,7 +986,7 @@ class SearchView(View):
if field.get('context'):
field["context"] = self.parse_domain(field["context"], req.session)
return {'fields': fields}
@openerpweb.jsonrequest
def get_filters(self, req, model):
Model = req.session.model("ir.filters")
@ -972,7 +995,7 @@ class SearchView(View):
filter["context"] = req.session.eval_context(self.parse_context(filter["context"], req.session))
filter["domain"] = req.session.eval_domain(self.parse_domain(filter["domain"], req.session))
return filters
@openerpweb.jsonrequest
def save_filter(self, req, model, name, context_to_save, domain):
Model = req.session.model("ir.filters")
@ -1382,7 +1405,7 @@ class Reports(View):
report_data['form'] = action['datas']['form']
if 'ids' in action['datas']:
report_ids = action['datas']['ids']
report_id = report_srv.report(
req.session._db, req.session._uid, req.session._password,
action["report_name"], report_ids,
@ -1394,6 +1417,7 @@ class Reports(View):
req.session._db, req.session._uid, req.session._password, report_id)
if report_struct["state"]:
break
time.sleep(self.POLLING_DELAY)
report = base64.b64decode(report_struct['result'])
@ -1407,3 +1431,108 @@ class Reports(View):
('Content-Type', report_mimetype),
('Content-Length', len(report))],
cookies={'fileToken': int(token)})
class Import(View):
_cp_path = "/web/import"
def fields_get(self, req, model):
Model = req.session.model(model)
fields = Model.fields_get(False, req.session.eval_context(req.context))
return fields
@openerpweb.httprequest
def detect_data(self, req, csvfile, csvsep, csvdel, csvcode, jsonp):
try:
data = list(csv.reader(
csvfile, quotechar=str(csvdel), delimiter=str(csvsep)))
except csv.Error, e:
csvfile.seek(0)
return '<script>window.top.%s(%s);</script>' % (
jsonp, simplejson.dumps({'error': {
'message': 'Error parsing CSV file: %s' % e,
# decodes each byte to a unicode character, which may or
# may not be printable, but decoding will succeed.
# Otherwise simplejson will try to decode the `str` using
# utf-8, which is very likely to blow up on characters out
# of the ascii range (in range [128, 256))
'preview': csvfile.read(200).decode('iso-8859-1')}}))
try:
return '<script>window.top.%s(%s);</script>' % (
jsonp, simplejson.dumps(
{'records': data[:10]}, encoding=csvcode))
except UnicodeDecodeError:
return '<script>window.top.%s(%s);</script>' % (
jsonp, simplejson.dumps({
'message': u"Failed to decode CSV file using encoding %s, "
u"try switching to a different encoding" % csvcode
}))
@openerpweb.httprequest
def import_data(self, req, model, csvfile, csvsep, csvdel, csvcode, jsonp,
meta):
modle_obj = req.session.model(model)
skip, indices, fields = operator.itemgetter('skip', 'indices', 'fields')(
simplejson.loads(meta))
error = None
if not (csvdel and len(csvdel) == 1):
error = u"The CSV delimiter must be a single character"
if not indices and fields:
error = u"You must select at least one field to import"
if error:
return '<script>window.top.%s(%s);</script>' % (
jsonp, simplejson.dumps({'error': {'message': error}}))
# skip ignored records
data_record = itertools.islice(
csv.reader(csvfile, quotechar=str(csvdel), delimiter=str(csvsep)),
skip, None)
# if only one index, itemgetter will return an atom rather than a tuple
if len(indices) == 1: mapper = lambda row: [row[indices[0]]]
else: mapper = operator.itemgetter(*indices)
data = None
error = None
try:
# decode each data row
data = [
[record.decode(csvcode) for record in row]
for row in itertools.imap(mapper, data_record)
# don't insert completely empty rows (can happen due to fields
# filtering in case of e.g. o2m content rows)
if any(row)
]
except UnicodeDecodeError:
error = u"Failed to decode CSV file using encoding %s" % csvcode
except csv.Error, e:
error = u"Could not process CSV file: %s" % e
# If the file contains nothing,
if not data:
error = u"File to import is empty"
if error:
return '<script>window.top.%s(%s);</script>' % (
jsonp, simplejson.dumps({'error': {'message': error}}))
try:
(code, record, message, _nope) = modle_obj.import_data(
fields, data, 'init', '', False,
req.session.eval_context(req.context))
except xmlrpclib.Fault, e:
error = {"message": u"%s, %s" % (e.faultCode, e.faultString)}
return '<script>window.top.%s(%s);</script>' % (
jsonp, simplejson.dumps({'error':error}))
if code != -1:
return '<script>window.top.%s(%s);</script>' % (
jsonp, simplejson.dumps({'success':True}))
msg = u"Error during import: %s\n\nTrying to import record %r" % (
message, record)
return '<script>window.top.%s(%s);</script>' % (
jsonp, simplejson.dumps({'error': {'message':msg}}))

View File

@ -0,0 +1,21 @@
Copyright 2011 Xavier Morel. 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 XAVIER MOREL ``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 <COPYRIGHT HOLDER> 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.

View File

@ -0,0 +1,59 @@
.. -*- restructuredtext -*-
In jQuery 1.5, jQuery has introduced a Deferred object in order to
better handle callbacks.
Along with Deferred, it introduced ``jQuery.when`` which allows, among
other things, for multiplexing deferreds (waiting on multiple
deferreds at the same time).
While this is very nice if all deferreds are available at the same
point, it doesn't really work if the resolution of a deferred can
generate more deferreds, or for collections of deferreds coming from
multiple sources (which may not be aware of one another).
Deferred.queue tries to be a solution to this. It is based on the
principle of FIFO multiple-producers multiple-consumers tasks queues,
such as Python's `queue.Queue`_. Any code with a reference to the
queue can add a promise to the queue, and the queue (which is a
promise itself) will only be resolved once all promises within are
resolved.
Quickstart
----------
Deferred.queue has a very simple life cycle: it is dormant when
created, it starts working as soon as promises get added to it (via
``.push``, or by providing them directly to its constructor), and as
soon as the last promise in the queue is resolved it resolves itself.
If any promise in the queue fails, the queue itself will be rejected
(without waiting for further promises).
Once a queue has been resolved or rejected, adding new promises to it
results in an error.
API
---
``jQuery.Deferred.queue([promises...])``
Creates a new deferred queue. Can be primed with a series of promise
objects.
``.push(promise...)``
Adds promises to the queue. Returns the queue itself (can be
chained).
If the queue has already been resolved or rejected, raises an error.
``.then([doneCallbacks][, failCallbacks])``
Promise/A ``then`` method.
``.done(doneCallbacks)``
jQuery ``done`` extension to promise objects
``.fail(failCallbacks)``
jQuery ``fail`` extension to promise objects
.. _queue.Queue:
http://docs.python.org/dev/library/queue.html

View File

@ -0,0 +1,34 @@
(function ($) {
"use strict";
$.extend($.Deferred, {
queue: function () {
var queueDeferred = $.Deferred();
var promises = 0;
function resolve() {
if (--promises > 0) {
return;
}
setTimeout($.proxy(queueDeferred, 'resolve'), 0);
}
var promise = $.extend(queueDeferred.promise(), {
push: function () {
if (this.isResolved() || this.isRejected()) {
throw new Error("Can not add promises to a resolved "
+ "or rejected promise queue");
}
promises += 1;
$.when.apply(null, arguments).then(
resolve, $.proxy(queueDeferred, 'reject'));
return this;
}
});
if (arguments.length) {
promise.push.apply(promise, arguments);
}
return promise;
}
});
})(jQuery)

View File

@ -0,0 +1,5 @@
repo: 076b192d0d8ab2b92d1dbcfa3da055382f30eaea
node: 87fb1b67d6a13f10a1a328104ee4d4b2c36801ec
branch: default
latesttag: 0.2
latesttagdistance: 1

View File

@ -0,0 +1 @@
Parser and evaluator of Python expressions

View File

@ -0,0 +1,14 @@
* Parser
since parsing expressions, try with a pratt parser
http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
http://effbot.org/zone/simple-top-down-parsing.htm
Evaluator
---------
* Stop busyworking trivial binary operator
* Make it *trivial* to build Python type-wrappers
* Implement Python's `data model
protocols<http://docs.python.org/reference/datamodel.html#basic-customization>`_
for *all* supported operations, optimizations can come later
* Automatically type-wrap everything (for now anyway)

View File

@ -0,0 +1,546 @@
var py = {};
(function (exports) {
var NUMBER = /^\d$/,
NAME_FIRST = /^[a-zA-Z_]$/,
NAME = /^[a-zA-Z0-9_]$/;
var create = function (o, props) {
function F() {}
F.prototype = o;
var inst = new F;
for(var name in props) {
if(!props.hasOwnProperty(name)) { continue; }
inst[name] = props[name];
}
return inst;
};
var symbols = {};
var comparators = {};
var Base = {
nud: function () { throw new Error(this.id + " undefined as prefix"); },
led: function (led) { throw new Error(this.id + " undefined as infix"); },
toString: function () {
if (this.id === '(constant)' || this.id === '(number)' || this.id === '(name)' || this.id === '(string)') {
return [this.id.slice(0, this.id.length-1), ' ', this.value, ')'].join('');
} else if (this.id === '(end)') {
return '(end)';
} else if (this.id === '(comparator)' ) {
var repr = ['(comparator', this.expressions[0]];
for (var i=0;i<this.operators.length; ++i) {
repr.push(this.operators[i], this.expressions[i+1]);
}
return repr.join(' ') + ')';
}
var out = [this.id, this.first, this.second, this.third]
.filter(function (r){return r}).join(' ');
return '(' + out + ')';
}
};
function symbol(id, bp) {
bp = bp || 0;
var s = symbols[id];
if (s) {
if (bp > s.lbp) {
s.lbp = bp;
}
return s;
}
return symbols[id] = create(Base, {
id: id,
lbp: bp
});
}
function constant(id) {
symbol(id).nud = function () {
this.id = "(constant)";
this.value = id;
return this;
};
}
function prefix(id, bp, nud) {
symbol(id).nud = nud || function () {
this.first = expression(bp);
return this
}
}
function infix(id, bp, led) {
symbol(id, bp).led = led || function (left) {
this.first = left;
this.second = expression(bp);
return this;
}
}
function infixr(id, bp) {
symbol(id, bp).led = function (left) {
this.first = left;
this.second = expression(bp - 1);
return this;
}
}
function comparator(id) {
comparators[id] = true;
var bp = 60;
infix(id, bp, function (left) {
this.id = '(comparator)';
this.operators = [id];
this.expressions = [left, expression(bp)];
while (token.id in comparators) {
this.operators.push(token.id);
advance();
this.expressions.push(
expression(bp));
}
return this;
});
}
constant('None'); constant('False'); constant('True');
symbol('(number)').nud = function () { return this; };
symbol('(name)').nud = function () { return this; };
symbol('(string)').nud = function () { return this; };
symbol('(end)');
symbol(':'); symbol(')'); symbol(']'); symbol('}'); symbol(',');
symbol('else');
symbol('lambda', 20).nud = function () {
this.first = [];
if (token.id !== ':') {
for(;;) {
if (token.id !== '(name)') {
throw new Error('Excepted an argument name');
}
this.first.push(token);
advance();
if (token.id !== ',') {
break;
}
advance(',');
}
}
advance(':');
this.second = expression();
return this;
};
infix('if', 20, function (left) {
this.first = left;
this.second = expression();
advance('else');
this.third = expression();
return this;
});
infixr('or', 30); infixr('and', 40); prefix('not', 50);
comparator('in'); comparator('not in');
comparator('is'); comparator('is not');
comparator('<'); comparator('<=');
comparator('>'); comparator('>=');
comparator('<>'); comparator('!='); comparator('==');
infix('|', 70); infix('^', 80), infix('&', 90);
infix('<<', 100); infix('>>', 100);
infix('+', 110); infix('-', 110);
infix('*', 120); infix('/', 120);
infix('//', 120), infix('%', 120);
prefix('-', 130); prefix('+', 130); prefix('~', 130);
infixr('**', 140);
infix('.', 150, function (left) {
if (token.id !== '(name)') {
throw new Error('Expected attribute name, got ', token.id);
}
this.first = left;
this.second = token;
advance();
return this;
});
symbol('(', 150).nud = function () {
this.first = [];
var comma = false;
if (token.id !== ')') {
while (true) {
if (token.id === ')') {
break;
}
this.first.push(expression());
if (token.id !== ',') {
break;
}
comma = true;
advance(',');
}
}
advance(')');
if (!this.first.length || comma) {
return this;
} else {
return this.first[0];
}
};
symbol('(').led = function (left) {
this.first = left;
this.second = [];
if (token.id !== ")") {
for(;;) {
this.second.push(expression());
if (token.id !== ',') {
break;
}
advance(',');
}
}
advance(")");
return this;
};
infix('[', 150, function (left) {
this.first = left;
this.second = expression();
advance("]");
return this;
});
symbol('[').nud = function () {
this.first = [];
if (token.id !== ']') {
for (;;) {
if (token.id === ']') {
break;
}
this.first.push(expression());
if (token.id !== ',') {
break;
}
advance(',');
}
}
advance(']');
return this;
};
symbol('{').nud = function () {
this.first = [];
if (token.id !== '}') {
for(;;) {
if (token.id === '}') {
break;
}
var key = expression();
advance(':');
var value = expression();
this.first.push([key, value]);
if (token.id !== ',') {
break;
}
advance(',');
}
}
advance('}');
return this;
};
var longops = {
'*': ['*'],
'<': ['<', '=', '>'],
'>': ['=', '>'],
'!': ['='],
'=': ['='],
'/': ['/']
};
function Tokenizer() {
this.states = ['initial'];
this.tokens = [];
}
Tokenizer.prototype = {
builder: function (empty) {
var key = this.states[0] + '_builder';
if (empty) {
var value = this[key];
delete this[key];
return value;
} else {
return this[key] = this[key] || [];
}
},
simple: function (type) {
this.tokens.push({type: type});
},
push: function (new_state) {
this.states.push(new_state);
},
pop: function () {
this.states.pop();
},
feed: function (str, index) {
var s = this.states;
return this[s[s.length - 1]](str, index);
},
initial: function (str, index) {
var character = str[index];
if (character in longops) {
var follow = longops[character];
for(var i=0, len=follow.length; i<len; ++i) {
if (str[index+1] === follow[i]) {
character += follow[i];
index++;
break;
}
}
}
if (character === ' ') {
return index+1;
} else if (character === '\0') {
this.tokens.push(symbols['(end)']);
return index + 1
} else if (character === '"' || character === "'") {
this.push('string');
return index + 1;
} else if (NUMBER.test(character)) {
this.push('number');
return index;
} else if (NAME_FIRST.test(character)) {
this.push('name');
return index;
} else if (character in symbols) {
this.tokens.push(create(symbols[character]));
return index + 1;
}
throw new Error("Tokenizing failure of <<" + str + ">> at index " + index
+ ", character [[" + character + "]]"
+ "; parsed so far: " + this.tokens);
},
string: function (str, index) {
var character = str[index];
if (character === '"' || character === "'") {
this.tokens.push(create(symbols['(string)'], {
value: this.builder(true).join('')
}));
this.pop();
return index + 1;
}
this.builder().push(character);
return index + 1;
},
number: function (str, index) {
var character = str[index];
if (!NUMBER.test(character)) {
this.tokens.push(create(symbols['(number)'], {
value: parseFloat(this.builder(true).join(''))
}));
this.pop();
return index;
}
this.builder().push(character);
return index + 1;
},
name: function (str, index) {
var character = str[index];
if (!NAME.test(character)) {
var name = this.builder(true).join('');
var symbol = symbols[name];
if (symbol) {
if (name === 'in' && this.tokens[this.tokens.length-1].id === 'not') {
symbol = symbols['not in'];
this.tokens.pop();
} else if (name === 'not' && this.tokens[this.tokens.length-1].id === 'is') {
symbol = symbols['is not'];
this.tokens.pop();
}
this.tokens.push(create(symbol));
} else {
this.tokens.push(create(symbols['(name)'], {
value: name
}));
}
this.pop();
return index;
}
this.builder().push(character);
return index + 1;
}
};
exports.tokenize = function tokenize(str) {
var index = 0,
tokenizer = new Tokenizer(str);
str += '\0';
do {
index = tokenizer.feed(str, index);
} while (index !== str.length);
return tokenizer.tokens;
};
var token, next;
function expression(rbp) {
rbp = rbp || 0;
var t = token;
token = next();
var left = t.nud();
while (rbp < token.lbp) {
t = token;
token = next();
left = t.led(left);
}
return left;
}
function advance(id) {
if (id && token.id !== id) {
throw new Error(
'Expected "' + id + '", got "' + token.id + '"');
}
token = next();
}
exports.object = create({}, {});
exports.bool = function (arg) { return !!arg; };
exports.tuple = create(exports.object, {
__contains__: function (value) {
for(var i=0, len=this.values.length; i<len; ++i) {
if (this.values[i] === value) {
return true;
}
}
return false;
},
toJSON: function () {
return this.values;
}
});
exports.list = exports.tuple;
exports.dict = create(exports.object, {
toJSON: function () {
return this.values;
}
});
exports.parse = function (toks) {
var index = 0;
token = toks[0];
next = function () { return toks[++index]; };
return expression();
};
var evaluate_operator = function (operator, a, b) {
switch (operator) {
case '==': case 'is': return a === b;
case '!=': case 'is not': return a !== b;
case '<': return a < b;
case '<=': return a <= b;
case '>': return a > b;
case '>=': return a >= b;
case 'in':
if (typeof b === 'string') {
return b.indexOf(a) !== -1;
}
return b.__contains__(a);
case 'not in':
if (typeof b === 'string') {
return b.indexOf(a) === -1;
}
return !b.__contains__(a);
}
throw new Error('SyntaxError: unknown comparator [[' + operator + ']]');
};
exports.evaluate = function (expr, context) {
switch (expr.id) {
case '(name)':
var val = context[expr.value];
if (val === undefined) {
throw new Error("NameError: name '" + expr.value + "' is not defined");
}
return val;
case '(string)':
case '(number)':
return expr.value;
case '(constant)':
if (expr.value === 'None')
return null;
else if (expr.value === 'False')
return false;
else if (expr.value === 'True')
return true;
throw new Error("SyntaxError: unknown constant '" + expr.value + "'");
case '(comparator)':
var result, left = exports.evaluate(expr.expressions[0], context);
for(var i=0; i<expr.operators.length; ++i) {
result = evaluate_operator(
expr.operators[i],
left,
left = exports.evaluate(expr.expressions[i+1], context));
if (!result) { return false; }
}
return true;
case '-':
if (expr.second) {
throw new Error('SyntaxError: binary [-] not implemented yet');
}
return -(exports.evaluate(expr.first, context));
case 'not':
return !(exports.evaluate(expr.first, context));
case 'and':
return (exports.evaluate(expr.first, context)
&& exports.evaluate(expr.second, context));
case 'or':
return (exports.evaluate(expr.first, context)
|| exports.evaluate(expr.second, context));
case '(':
if (expr.second) {
var fn = exports.evaluate(expr.first, context), args=[];
for (var jj=0; jj<expr.second.length; ++jj) {
args.push(exports.evaluate(
expr.second[jj], context));
}
return fn.apply(null, args);
}
var tuple_exprs = expr.first,
tuple_values = [];
for (var j=0, len=tuple_exprs.length; j<len; ++j) {
tuple_values.push(exports.evaluate(
tuple_exprs[j], context));
}
return create(exports.tuple, {values: tuple_values});
case '[':
if (expr.second) {
throw new Error('SyntaxError: indexing not implemented yet');
}
var list_exprs = expr.first, list_values = [];
for (var k=0; k<list_exprs.length; ++k) {
list_values.push(exports.evaluate(
list_exprs[k], context));
}
return create(exports.list, {values: list_values});
case '{':
var dict_exprs = expr.first, dict_values = {};
for(var l=0; l<dict_exprs.length; ++l) {
dict_values[exports.evaluate(dict_exprs[l][0], context)] =
exports.evaluate(dict_exprs[l][1], context);
}
return create(exports.dict, {values: dict_values});
case '.':
if (expr.second.id !== '(name)') {
throw new Error('SyntaxError: ' + expr);
}
return exports.evaluate(expr.first, context)[expr.second.value];
default:
throw new Error('SyntaxError: Unknown node [[' + expr.id + ']]');
}
};
exports.eval = function (str, context) {
return exports.evaluate(
exports.parse(
exports.tokenize(
str)),
context);
}
})(typeof exports === 'undefined' ? py : exports);

View File

@ -0,0 +1,90 @@
var py = require('../lib/py.js'),
assert = require('assert');
// Literals
assert.strictEqual(py.eval('1'), 1);
assert.strictEqual(py.eval('None'), null);
assert.strictEqual(py.eval('False'), false);
assert.strictEqual(py.eval('True'), true);
assert.strictEqual(py.eval('"somestring"'), 'somestring');
assert.strictEqual(py.eval("'somestring'"), 'somestring');
assert.deepEqual(py.eval("()").toJSON(), []);
assert.deepEqual(py.eval("[]").toJSON(), []);
assert.deepEqual(py.eval("{}").toJSON(), {});
assert.deepEqual(py.eval("(None, True, False, 0, 1, 'foo')").toJSON(),
[null, true, false, 0, 1, 'foo']);
assert.deepEqual(py.eval("[None, True, False, 0, 1, 'foo']").toJSON(),
[null, true, false, 0, 1, 'foo']);
assert.deepEqual(py.eval("{'foo': 1, foo: 2}", {foo: 'bar'}).toJSON(),
{foo: 1, bar: 2});
// Equality tests
assert.ok(py.eval(
"foo == 'foo'", {foo: 'foo'}));
// Inequality
assert.ok(py.eval(
"foo != bar", {foo: 'foo', bar: 'bar'}));
// Comparisons
assert.ok(py.eval('3 < 5'));
assert.ok(py.eval('5 >= 3'));
assert.ok(py.eval('3 >= 3'));
assert.ok(!py.eval('5 < 3'));
assert.ok(py.eval('1 < 3 < 5'));
assert.ok(py.eval('5 > 3 > 1'));
assert.ok(py.eval('1 < 3 > 2 == 2 > -2 not in (0, 1, 2)'));
// string rich comparisons
assert.ok(py.eval(
'date >= current', {date: '2010-06-08', current: '2010-06-05'}));
// Boolean operators
assert.ok(py.eval(
"foo == 'foo' or foo == 'bar'", {foo: 'bar'}));
assert.ok(py.eval(
"foo == 'foo' and bar == 'bar'", {foo: 'foo', bar: 'bar'}));
// - lazyness, second clauses NameError if not short-circuited
assert.ok(py.eval(
"foo == 'foo' or bar == 'bar'", {foo: 'foo'}));
assert.ok(!py.eval(
"foo == 'foo' and bar == 'bar'", {foo: 'bar'}));
// contains (in)
assert.ok(py.eval(
"foo in ('foo', 'bar')", {foo: 'bar'}));
assert.ok(py.eval('1 in (1, 2, 3, 4)'));
assert.ok(!py.eval('1 in (2, 3, 4)'));
assert.ok(py.eval('type in ("url",)', {type: 'url'}));
assert.ok(!py.eval('type in ("url",)', {type: 'ur'}));
assert.ok(py.eval('1 not in (2, 3, 4)'));
assert.ok(py.eval('type not in ("url",)', {type: 'ur'}));
assert.ok(py.eval(
"foo in ['foo', 'bar']", {foo: 'bar'}));
// string contains
assert.ok(py.eval('type in "view"', {type: 'view'}));
assert.ok(!py.eval('type in "view"', {type: 'bob'}));
assert.ok(py.eval('type in "url"', {type: 'ur'}));
// Literals
assert.strictEqual(py.eval('False'), false);
assert.strictEqual(py.eval('True'), true);
assert.strictEqual(py.eval('None'), null);
assert.ok(py.eval('foo == False', {foo: false}));
assert.ok(!py.eval('foo == False', {foo: true}));
// conversions
assert.strictEqual(
py.eval('bool(date_deadline)', {bool: py.bool, date_deadline: '2008'}),
true);
// getattr
assert.ok(py.eval('foo.bar', {foo: {bar: true}}));
assert.ok(!py.eval('foo.bar', {foo: {bar: false}}));
// complex expressions
assert.ok(py.eval(
"state=='pending' and not(date_deadline and (date_deadline < current_date))",
{state: 'pending', date_deadline: false}));
assert.ok(py.eval(
"state=='pending' and not(date_deadline and (date_deadline < current_date))",
{state: 'pending', date_deadline: '2010-05-08', current_date: '2010-05-08'}));;

View File

@ -4,301 +4,436 @@
// Documentation: https://github.com/edtsech/underscore.string
// Some code is borrowed from MooTools and Alexandru Marasteanu.
// Version 1.1.4
// Version 1.1.6
(function(){
// ------------------------- Baseline setup ---------------------------------
// Establish the root object, "window" in the browser, or "global" on the server.
var root = this;
(function(root){
'use strict';
var nativeTrim = String.prototype.trim;
if (typeof _ != 'undefined') {
var _reverse = _().reverse,
_include = _.include;
}
function str_repeat(i, m) {
for (var o = []; m > 0; o[--m] = i);
return o.join('');
// Defining helper functions.
var nativeTrim = String.prototype.trim;
var parseNumber = function(source) { return source * 1 || 0; };
var strRepeat = function(i, m) {
for (var o = []; m > 0; o[--m] = i);
return o.join('');
};
var slice = function(a){
return Array.prototype.slice.call(a);
};
var defaultToWhiteSpace = function(characters){
if (characters) {
return _s.escapeRegExp(characters);
}
return '\\s';
};
var sArgs = function(method){
return function(){
var args = slice(arguments);
for(var i=0; i<args.length; i++)
args[i] = args[i] == null ? '' : '' + args[i];
return method.apply(null, args);
};
};
// sprintf() for JavaScript 0.7-beta1
// http://www.diveintojavascript.com/projects/javascript-sprintf
//
// Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
// All rights reserved.
var sprintf = (function() {
function get_type(variable) {
return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
}
function defaultToWhiteSpace(characters){
if (characters) {
return _s.escapeRegExp(characters);
var str_repeat = strRepeat;
var str_format = function() {
if (!str_format.cache.hasOwnProperty(arguments[0])) {
str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
}
return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
};
str_format.format = function(parse_tree, argv) {
var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
for (i = 0; i < tree_length; i++) {
node_type = get_type(parse_tree[i]);
if (node_type === 'string') {
output.push(parse_tree[i]);
}
return '\\s';
}
var _s = {
isBlank: function(str){
return !!str.match(/^\s*$/);
},
capitalize : function(str) {
return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
},
chop: function(str, step){
step = step || str.length;
var arr = [];
for (var i = 0; i < str.length;) {
arr.push(str.slice(i,i + step));
i = i + step;
else if (node_type === 'array') {
match = parse_tree[i]; // convenience purposes only
if (match[2]) { // keyword argument
arg = argv[cursor];
for (k = 0; k < match[2].length; k++) {
if (!arg.hasOwnProperty(match[2][k])) {
throw(sprintf('[_.sprintf] property "%s" does not exist', match[2][k]));
}
arg = arg[match[2][k]];
}
return arr;
},
} else if (match[1]) { // positional argument (explicit)
arg = argv[match[1]];
}
else { // positional argument (implicit)
arg = argv[cursor++];
}
clean: function(str){
return _s.strip(str.replace(/\s+/g, ' '));
},
if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
throw(sprintf('[_.sprintf] expecting number but found %s', get_type(arg)));
}
switch (match[8]) {
case 'b': arg = arg.toString(2); break;
case 'c': arg = String.fromCharCode(arg); break;
case 'd': arg = parseInt(arg, 10); break;
case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
case 'o': arg = arg.toString(8); break;
case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
case 'u': arg = Math.abs(arg); break;
case 'x': arg = arg.toString(16); break;
case 'X': arg = arg.toString(16).toUpperCase(); break;
}
arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
pad_length = match[6] - String(arg).length;
pad = match[6] ? str_repeat(pad_character, pad_length) : '';
output.push(match[5] ? arg + pad : pad + arg);
}
}
return output.join('');
};
count: function(str, substr){
var count = 0, index;
for (var i=0; i < str.length;) {
index = str.indexOf(substr, i);
index >= 0 && count++;
i = i + (index >= 0 ? index : 0) + substr.length;
}
return count;
},
str_format.cache = {};
chars: function(str) {
return str.split('');
},
escapeHTML: function(str) {
return String(str||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
.replace(/"/g, '&quot;').replace(/'/g, "&apos;");
},
unescapeHTML: function(str) {
return String(str||'').replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
.replace(/&quot;/g, '"').replace(/&apos;/g, "'");
},
escapeRegExp: function(str){
// From MooTools core 1.2.4
return String(str||'').replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
},
insert: function(str, i, substr){
var arr = str.split('');
arr.splice(i, 0, substr);
return arr.join('');
},
includes: function(str, needle){
return str.indexOf(needle) !== -1;
},
join: function(sep) {
// TODO: Could this be faster by converting
// arguments to Array and using array.join(sep)?
sep = String(sep);
var str = "";
for (var i=1; i < arguments.length; i += 1) {
str += String(arguments[i]);
if ( i !== arguments.length-1 ) {
str += sep;
str_format.parse = function(fmt) {
var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
while (_fmt) {
if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
parse_tree.push(match[0]);
}
else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
parse_tree.push('%');
}
else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
if (match[2]) {
arg_names |= 1;
var field_list = [], replacement_field = match[2], field_match = [];
if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
}
}
return str;
},
lines: function(str) {
return str.split("\n");
},
// reverse: function(str){
// return Array.prototype.reverse.apply(str.split('')).join('');
// },
splice: function(str, i, howmany, substr){
var arr = str.split('');
arr.splice(i, howmany, substr);
return arr.join('');
},
startsWith: function(str, starts){
return str.length >= starts.length && str.substring(0, starts.length) === starts;
},
endsWith: function(str, ends){
return str.length >= ends.length && str.substring(str.length - ends.length) === ends;
},
succ: function(str){
var arr = str.split('');
arr.splice(str.length-1, 1, String.fromCharCode(str.charCodeAt(str.length-1) + 1));
return arr.join('');
},
titleize: function(str){
var arr = str.split(' '),
word;
for (var i=0; i < arr.length; i++) {
word = arr[i].split('');
if(typeof word[0] !== 'undefined') word[0] = word[0].toUpperCase();
i+1 === arr.length ? arr[i] = word.join('') : arr[i] = word.join('') + ' ';
}
return arr.join('');
},
camelize: function(str){
return _s.trim(str).replace(/(\-|_|\s)+(.)?/g, function(match, separator, chr) {
return chr ? chr.toUpperCase() : '';
});
},
underscored: function(str){
return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1_$2').replace(/\-|\s+/g, '_').toLowerCase();
},
dasherize: function(str){
return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1-$2').replace(/^([A-Z]+)/, '-$1').replace(/\_|\s+/g, '-').toLowerCase();
},
trim: function(str, characters){
if (!characters && nativeTrim) {
return nativeTrim.call(str);
}
characters = defaultToWhiteSpace(characters);
return str.replace(new RegExp('\^[' + characters + ']+|[' + characters + ']+$', 'g'), '');
},
ltrim: function(str, characters){
characters = defaultToWhiteSpace(characters);
return str.replace(new RegExp('\^[' + characters + ']+', 'g'), '');
},
rtrim: function(str, characters){
characters = defaultToWhiteSpace(characters);
return str.replace(new RegExp('[' + characters + ']+$', 'g'), '');
},
truncate: function(str, length, truncateStr){
truncateStr = truncateStr || '...';
return str.slice(0,length) + truncateStr;
},
words: function(str, delimiter) {
delimiter = delimiter || " ";
return str.split(delimiter);
},
pad: function(str, length, padStr, type) {
var padding = '';
var padlen = 0;
if (!padStr) { padStr = ' '; }
else if (padStr.length > 1) { padStr = padStr[0]; }
switch(type) {
case "right":
padlen = (length - str.length);
padding = str_repeat(padStr, padlen);
str = str+padding;
break;
case "both":
padlen = (length - str.length);
padding = {
'left' : str_repeat(padStr, Math.ceil(padlen/2)),
'right': str_repeat(padStr, Math.floor(padlen/2))
};
str = padding.left+str+padding.right;
break;
default: // "left"
padlen = (length - str.length);
padding = str_repeat(padStr, padlen);;
str = padding+str;
}
return str;
},
lpad: function(str, length, padStr) {
return _s.pad(str, length, padStr);
},
rpad: function(str, length, padStr) {
return _s.pad(str, length, padStr, 'right');
},
lrpad: function(str, length, padStr) {
return _s.pad(str, length, padStr, 'both');
},
/**
* Credits for this function goes to
* http://www.diveintojavascript.com/projects/sprintf-for-javascript
*
* Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
* All rights reserved.
* */
sprintf: function(){
var i = 0, a, f = arguments[i++], o = [], m, p, c, x, s = '';
while (f) {
if (m = /^[^\x25]+/.exec(f)) {
o.push(m[0]);
}
else if (m = /^\x25{2}/.exec(f)) {
o.push('%');
}
else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) {
throw('Too few arguments.');
}
if (/[^s]/.test(m[7]) && (typeof(a) != 'number')) {
throw('Expecting number but found ' + typeof(a));
}
switch (m[7]) {
case 'b': a = a.toString(2); break;
case 'c': a = String.fromCharCode(a); break;
case 'd': a = parseInt(a); break;
case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
case 'o': a = a.toString(8); break;
case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
case 'u': a = Math.abs(a); break;
case 'x': a = a.toString(16); break;
case 'X': a = a.toString(16).toUpperCase(); break;
}
a = (/[def]/.test(m[7]) && m[2] && a >= 0 ? '+'+ a : a);
c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
x = m[5] - String(a).length - s.length;
p = m[5] ? str_repeat(c, x) : '';
o.push(s + (m[4] ? a + p : p + a));
else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
}
else {
throw('Huh ?!');
throw('[_.sprintf] huh?');
}
f = f.substring(m[0].length);
}
}
return o.join('');
else {
throw('[_.sprintf] huh?');
}
match[2] = field_list;
}
else {
arg_names |= 2;
}
if (arg_names === 3) {
throw('[_.sprintf] mixing positional and named placeholders is not (yet) supported');
}
parse_tree.push(match);
}
}
else {
throw('[_.sprintf] huh?');
}
_fmt = _fmt.substring(match[0].length);
}
return parse_tree;
};
// Aliases
return str_format;
})();
_s.strip = _s.trim;
_s.lstrip = _s.ltrim;
_s.rstrip = _s.rtrim;
_s.center = _s.lrpad
_s.ljust = _s.lpad
_s.rjust = _s.rpad
// CommonJS module is defined
if (typeof window === 'undefined' && typeof module !== 'undefined') {
// Export module
module.exports = _s;
// Integrate with Underscore.js
} else if (typeof root._ !== 'undefined') {
root._.mixin(_s);
// Defining underscore.string
// Or define it
} else {
root._ = _s;
}
var _s = {
}());
isBlank: sArgs(function(str){
return (/^\s*$/).test(str);
}),
stripTags: sArgs(function(str){
return str.replace(/<\/?[^>]+>/ig, '');
}),
capitalize : sArgs(function(str) {
return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
}),
chop: sArgs(function(str, step){
step = parseNumber(step) || str.length;
var arr = [];
for (var i = 0; i < str.length;) {
arr.push(str.slice(i,i + step));
i = i + step;
}
return arr;
}),
clean: sArgs(function(str){
return _s.strip(str.replace(/\s+/g, ' '));
}),
count: sArgs(function(str, substr){
var count = 0, index;
for (var i=0; i < str.length;) {
index = str.indexOf(substr, i);
index >= 0 && count++;
i = i + (index >= 0 ? index : 0) + substr.length;
}
return count;
}),
chars: sArgs(function(str) {
return str.split('');
}),
escapeHTML: sArgs(function(str) {
return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
.replace(/"/g, '&quot;').replace(/'/g, "&apos;");
}),
unescapeHTML: sArgs(function(str) {
return str.replace(/&lt;/g, '<').replace(/&gt;/g, '>')
.replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, '&');
}),
escapeRegExp: sArgs(function(str){
// From MooTools core 1.2.4
return str.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
}),
insert: sArgs(function(str, i, substr){
var arr = str.split('');
arr.splice(parseNumber(i), 0, substr);
return arr.join('');
}),
includes: sArgs(function(str, needle){
return str.indexOf(needle) !== -1;
}),
include: function(obj, needle) {
if (!_include || (/string|number/).test(typeof obj)) {
return this.includes(obj, needle);
} else {
return _include(obj, needle);
}
},
join: sArgs(function(sep) {
var args = slice(arguments);
return args.join(args.shift());
}),
lines: sArgs(function(str) {
return str.split("\n");
}),
reverse: function(obj){
if (!_reverse || (/string|number/).test(typeof obj)) {
return Array.prototype.reverse.apply(String(obj).split('')).join('');
} else {
return _reverse.call(_(obj));
}
},
splice: sArgs(function(str, i, howmany, substr){
var arr = str.split('');
arr.splice(parseNumber(i), parseNumber(howmany), substr);
return arr.join('');
}),
startsWith: sArgs(function(str, starts){
return str.length >= starts.length && str.substring(0, starts.length) === starts;
}),
endsWith: sArgs(function(str, ends){
return str.length >= ends.length && str.substring(str.length - ends.length) === ends;
}),
succ: sArgs(function(str){
var arr = str.split('');
arr.splice(str.length-1, 1, String.fromCharCode(str.charCodeAt(str.length-1) + 1));
return arr.join('');
}),
titleize: sArgs(function(str){
var arr = str.split(' '),
word;
for (var i=0; i < arr.length; i++) {
word = arr[i].split('');
if(typeof word[0] !== 'undefined') word[0] = word[0].toUpperCase();
i+1 === arr.length ? arr[i] = word.join('') : arr[i] = word.join('') + ' ';
}
return arr.join('');
}),
camelize: sArgs(function(str){
return _s.trim(str).replace(/(\-|_|\s)+(.)?/g, function(match, separator, chr) {
return chr ? chr.toUpperCase() : '';
});
}),
underscored: function(str){
return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1_$2').replace(/\-|\s+/g, '_').toLowerCase();
},
dasherize: function(str){
return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1-$2').replace(/^([A-Z]+)/, '-$1').replace(/\_|\s+/g, '-').toLowerCase();
},
trim: sArgs(function(str, characters){
if (!characters && nativeTrim) {
return nativeTrim.call(str);
}
characters = defaultToWhiteSpace(characters);
return str.replace(new RegExp('\^[' + characters + ']+|[' + characters + ']+$', 'g'), '');
}),
ltrim: sArgs(function(str, characters){
characters = defaultToWhiteSpace(characters);
return str.replace(new RegExp('\^[' + characters + ']+', 'g'), '');
}),
rtrim: sArgs(function(str, characters){
characters = defaultToWhiteSpace(characters);
return str.replace(new RegExp('[' + characters + ']+$', 'g'), '');
}),
truncate: sArgs(function(str, length, truncateStr){
truncateStr = truncateStr || '...';
length = parseNumber(length);
return str.length > length ? str.slice(0,length) + truncateStr : str;
}),
words: function(str, delimiter) {
return String(str).split(delimiter || " ");
},
pad: sArgs(function(str, length, padStr, type) {
var padding = '',
padlen = 0;
length = parseNumber(length);
if (!padStr) { padStr = ' '; }
else if (padStr.length > 1) { padStr = padStr.charAt(0); }
switch(type) {
case 'right':
padlen = (length - str.length);
padding = strRepeat(padStr, padlen);
str = str+padding;
break;
case 'both':
padlen = (length - str.length);
padding = {
'left' : strRepeat(padStr, Math.ceil(padlen/2)),
'right': strRepeat(padStr, Math.floor(padlen/2))
};
str = padding.left+str+padding.right;
break;
default: // 'left'
padlen = (length - str.length);
padding = strRepeat(padStr, padlen);;
str = padding+str;
}
return str;
}),
lpad: function(str, length, padStr) {
return _s.pad(str, length, padStr);
},
rpad: function(str, length, padStr) {
return _s.pad(str, length, padStr, 'right');
},
lrpad: function(str, length, padStr) {
return _s.pad(str, length, padStr, 'both');
},
sprintf: sprintf,
vsprintf: function(fmt, argv){
argv.unshift(fmt);
return sprintf.apply(null, argv);
},
toNumber: function(str, decimals) {
var num = parseNumber(parseNumber(str).toFixed(parseNumber(decimals)));
return (!(num === 0 && (str !== "0" && str !== 0))) ? num : Number.NaN;
},
strRight: sArgs(function(sourceStr, sep){
var pos = (!sep) ? -1 : sourceStr.indexOf(sep);
return (pos != -1) ? sourceStr.slice(pos+sep.length, sourceStr.length) : sourceStr;
}),
strRightBack: sArgs(function(sourceStr, sep){
var pos = (!sep) ? -1 : sourceStr.lastIndexOf(sep);
return (pos != -1) ? sourceStr.slice(pos+sep.length, sourceStr.length) : sourceStr;
}),
strLeft: sArgs(function(sourceStr, sep){
var pos = (!sep) ? -1 : sourceStr.indexOf(sep);
return (pos != -1) ? sourceStr.slice(0, pos) : sourceStr;
}),
strLeftBack: sArgs(function(sourceStr, sep){
var pos = sourceStr.lastIndexOf(sep);
return (pos != -1) ? sourceStr.slice(0, pos) : sourceStr;
})
};
// Aliases
_s.strip = _s.trim;
_s.lstrip = _s.ltrim;
_s.rstrip = _s.rtrim;
_s.center = _s.lrpad;
_s.ljust = _s.lpad;
_s.rjust = _s.rpad;
// CommonJS module is defined
if (typeof module !== 'undefined' && module.exports) {
// Export module
module.exports = _s;
// Integrate with Underscore.js
} else if (typeof root._ !== 'undefined') {
root._.mixin(_s);
// Or define it
} else {
root._ = _s;
}
}(this || window));

View File

@ -125,7 +125,7 @@ body.openerp, .openerp textarea, .openerp input, .openerp select, .openerp optio
margin-top: 5px;
text-align: center;
}
.openerp .login.login_invalid .login_error_message {
.openerp .login .login_invalid .login_error_message {
display: block;
}
@ -221,17 +221,11 @@ label.error {
height: 100%;
background: #f0eeee;
}
.openerp .oe-application-container {
height: 100%;
}
/* Menu */
.openerp .sf-menu {
margin-bottom: 0;
}
/*
.sf-menu a {
padding: 5px 5px;
}
*/
.openerp .menu {
height: 34px;
background: #cc4e45; /* Old browsers */
@ -253,7 +247,7 @@ label.error {
height: 20px;
margin: 3px 2px;
padding: 0 8px;
background: #bd5e54; /* Old browsers */
background: -moz-linear-gradient(top, #bd5e54 0%, #90322a 60%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#bd5e54), color-stop(60%,#90322a)); /* Chrome,Safari4+ */
@ -291,13 +285,38 @@ label.error {
background: linear-gradient(top, #c6c6c6 0%,#5c5c5c 7%,#969595 86%); /* W3C */
/* for ie */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#5c5c5c', endColorstr='#969595',GradientType=0 ); /* IE6-9 */
color: #fff;
}
.openerp .oe-application-container {
height: 100%;
}
/* Secondary Menu */
.openerp .secondary_menu .oe_toggle_secondary_menu {
position: absolute;
cursor: pointer;
border-left: 1px solid #282828;
width: 21px;
height: 21px;
z-index: 10;
background: transparent;
color: white;
text-shadow: 0 1px 0 #333;
text-align: center;
font-size: 18px;
line-height: 14px;
right: 0;
}
.openerp .secondary_menu.oe_folded .oe_toggle_secondary_menu {
position: static;
border-left: none;
border-bottom: 1px solid #282828;
width: 21px;
height: 21px;
background: #818181;
}
.openerp .secondary_menu.oe_folded .oe_toggle_secondary_menu span.oe_menu_fold {
display: none;
}
.openerp .secondary_menu.oe_unfolded .oe_toggle_secondary_menu span.oe_menu_unfold {
display: none;
}
.openerp .secondary_menu {
width: 200px;
min-width: 200px;
@ -306,15 +325,34 @@ label.error {
background: #5A5858;
vertical-align: top;
height: 100%;
position: relative;
}
.openerp .secondary_menu .menu_content {
padding: 0;
border: none;
background: none;
overflow: hidden;
.openerp .secondary_menu.oe_folded {
width: 20px;
min-width: 20px;
position: static;
}
.openerp .secondary_menu h3 {
padding: 0 0 2px;
.openerp .secondary_menu.oe_folded .oe_secondary_menu.active {
position: absolute;
z-index: 100;
border: 4px solid #585858;
border: 4px solid rgba(88, 88, 88, .5);
border-radius: 4px;
min-width: 200px;
}
.openerp .secondary_menu a {
display: block;
padding: 0 5px 2px 5px;
line-height: 20px;
text-decoration: none;
white-space: nowrap;
color: white;
text-shadow: 0 1px 0 #333;
}
.openerp .oe_secondary_submenu {
background: #5A5858;
}
.openerp .secondary_menu a.oe_secondary_menu_item {
background: #949292; /* Old browsers */
background: -moz-linear-gradient(top, #949292 0%, #6d6b6b 87%, #282828 99%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#949292), color-stop(87%,#6d6b6b), color-stop(99%,#282828)); /* Chrome,Safari4+ */
@ -325,34 +363,13 @@ label.error {
background: linear-gradient(top, #949292 0%,#6d6b6b 87%,#282828 99%); /* W3C */
/* for ie9 */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#949292', endColorstr='#5B5A5A',GradientType=0 ); /* IE6-9 */
border: none;
/* overriding jquery ui */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
}
.openerp .secondary_menu h4 {
padding: 0 0 2px 10px;
border: none;
background: none;
}
.openerp .secondary_menu h3 span, .openerp .secondary_menu h4 span {
left: 0 !important;
}
.openerp .secondary_menu a {
display: block;
height: 20px;
padding: 0 5px;
line-height: 20px;
white-space: nowrap;
color: white;
text-decoration: none;
text-shadow: 0 1px 0 #333;
}
.openerp .secondary_menu a.leaf:hover,
.openerp .secondary_menu a.leaf:active,
.openerp .secondary_menu a.leaf.active,
.openerp .secondary_menu h4:hover,
.openerp .secondary_menu h4:active,
.openerp .secondary_menu h4.active {
.openerp a.oe_secondary_submenu_item:hover,
.openerp a.oe_secondary_submenu_item.leaf.active {
display: block;
background: #ffffff; /* Old browsers */
background: -moz-linear-gradient(top, #ffffff 0%, #d8d8d8 11%, #afafaf 86%, #333333 91%, #5a5858 96%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(11%,#d8d8d8), color-stop(86%,#afafaf), color-stop(91%,#333333), color-stop(96%,#5a5858)); /* Chrome,Safari4+ */
@ -361,22 +378,17 @@ label.error {
background: -ms-linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,#5a5858 96%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFFFFF', endColorstr='#5A5858',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,#5a5858 96%); /* W3C */
/* overriding jquery ui */
-moz-border-radius-topright: 0; -webkit-border-top-right-radius: 0; border-top-right-radius: 0;
padding: 0 5px 2px 5px;
line-height: 20px;
color: #3f3d3d;
text-decoration: none;
text-shadow: #fff 0 1px 0;
border: none !important;
}
.openerp .secondary_menu h4:hover a,
.openerp .secondary_menu h4:active a,
.openerp .secondary_menu h4.active a {
color: #3f3d3d;
text-shadow: #fff 0 1px 0;
border: none !important;
.openerp a.oe_secondary_submenu_item.submenu.opened span:before {
content: "\25be";
}
.openerp div.submenu_accordion div.menu_content a span {
padding-left: 20px;
.openerp a.oe_secondary_submenu_item.submenu span:before {
content: "\25b8";
}
/* Header */
@ -880,6 +892,9 @@ label.error {
.openerp .oe_forms input.field_datetime {
min-width: 11em;
}
.openerp .oe_forms.oe_frame .oe_datepicker_root {
width: 100%;
}
.openerp .oe_forms .button {
color: #4c4c4c;
white-space: nowrap;
@ -892,7 +907,14 @@ label.error {
position: absolute;
cursor: pointer;
right: 5px;
top: 5px;
top: 3px;
}
.openerp .oe_datepicker_root {
position: relative;
display: inline-block;
}
.openerp .oe_datepicker_root input[type="text"] {
min-width: 160px;
}
.openerp .oe_input_icon_disabled {
position: absolute;

View File

@ -0,0 +1,30 @@
.openerp .oe_import_grid {
border: none;
border-collapse: collapse;
}
.openerp .oe_import_grid-header .oe_import_grid-cell {
background: url(../img/gradientlinebg.gif) repeat-x #CCCCCC;
border-bottom: 1px solid #E3E3E3;
font-weight: bold;
text-align: left;
}
.openerp .oe_import_grid-row .oe_import_grid-cell {
border-bottom: 1px solid #E3E3E3;
}
.openerp .separator.horizontal {
font-weight: bold;
border-bottom-width: 1px;
margin: 6px 4px 6px 1px;
height: 20px;
}
.openerp .duplicate_fld{
background-color:#FF6666;
}
.openerp .select_fld{
background: none repeat scroll 0 0 white;
}
.openerp .ui-autocomplete {
max-height: 300px;
overflow-y: auto;
padding-right: 20px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

View File

@ -19,9 +19,9 @@
/**
* OpenERP instance constructor
*
* @param {Boolean} skip_init if true, skips the built-in initialization
* @param {Array} modules list of modules to initialize
*/
init: function(skip_init) {
init: function(modules) {
var new_instance = {
// links to the global openerp
_openerp: openerp,
@ -35,8 +35,9 @@
web_mobile: {}
};
openerp.sessions[new_instance._session_id] = new_instance;
if (!skip_init){
openerp.web(new_instance);
modules = modules || ["web"];
for(var i=0; i < modules.length; i++) {
openerp[modules[i]](new_instance);
}
return new_instance;
}

View File

@ -1,7 +1,6 @@
/*---------------------------------------------------------
* OpenERP Web chrome
*---------------------------------------------------------*/
openerp.web.chrome = function(openerp) {
var QWeb = openerp.web.qweb;
@ -129,6 +128,7 @@ openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog#
// Destroy widget
this.close();
this.$dialog.dialog('destroy');
this._super();
}
});
@ -218,9 +218,8 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database
this.$element.closest(".openerp")
.removeClass("login-mode")
.addClass("database_block");
var self = this;
var fetch_db = this.rpc("/web/database/get_list", {}, function(result) {
self.db_list = result.db_list;
});
@ -232,7 +231,7 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database
self.lang_list = result.lang_list;
});
$.when(fetch_db, fetch_langs).then(function () {self.do_create();});
this.$element.find('#db-create').click(this.do_create);
this.$element.find('#db-drop').click(this.do_drop);
this.$element.find('#db-backup').click(this.do_backup);
@ -254,7 +253,7 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database
.removeClass("database_block")
.end()
.empty();
this._super();
},
/**
* Converts a .serializeArray() result into a dict. Does not bother folding
@ -300,9 +299,9 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database
var admin = result[1][0];
setTimeout(function () {
self.stop();
self.widget_parent.do_login(
info.db, admin.login, admin.password);
self.stop();
$.unblockUI();
});
});
@ -399,7 +398,7 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database
do_restore: function() {
var self = this;
self.$option_id.html(QWeb.render("RestoreDB", self));
self.$option_id.find("form[name=restore_db_form]").validate({
submitHandler: function (form) {
$.blockUI({message:'<img src="/web/static/src/img/throbber2.gif">'});
@ -464,15 +463,19 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database
openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{
remember_creditentials: true,
template: "Login",
identifier_prefix: 'oe-app-login-',
/**
* @constructs openerp.web.Login
* @extends openerp.web.Widget
*
*
* @param parent
* @param element_id
*/
init: function(parent, element_id) {
this._super(parent, element_id);
init: function(parent) {
this._super(parent);
this.has_local_storage = typeof(localStorage) != 'undefined';
this.selected_db = null;
this.selected_login = null;
@ -487,17 +490,6 @@ openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{
},
start: function() {
var self = this;
this.rpc("/web/database/get_list", {}, function(result) {
self.db_list = result.db_list;
self.display();
}, function() {
self.display();
});
},
display: function() {
var self = this;
this.$element.html(QWeb.render("Login", this));
this.database = new openerp.web.Database(
this, "oe_database", "oe_db_options");
@ -506,6 +498,17 @@ openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{
});
this.$element.find("form").submit(this.on_submit);
this.rpc("/web/database/get_list", {}, function(result) {
var tpl = openerp.web.qweb.render('Login_dblist', {db_list: result.db_list, selected_db: self.selected_db});
self.$element.find("input[name=db]").replaceWith(tpl)
},
function(error, event) {
if (error.data.fault_code === 'AccessDenied') {
event.preventDefault();
}
});
},
on_login_invalid: function() {
this.$element.closest(".openerp").addClass("login-mode");
@ -574,14 +577,13 @@ openerp.web.Header = openerp.web.Widget.extend(/** @lends openerp.web.Header# *
/**
* @constructs openerp.web.Header
* @extends openerp.web.Widget
*
*
* @param parent
*/
init: function(parent) {
this._super(parent);
this.qs = "?" + jQuery.param.querystring();
this.$content = $();
console.debug("initializing header with id", this.element_id);
this.update_promise = $.Deferred().resolve();
},
start: function() {
@ -666,7 +668,7 @@ openerp.web.Header = openerp.web.Widget.extend(/** @lends openerp.web.Header# *
});
});
},
on_action: function(action) {
},
on_preferences: function(){
@ -702,10 +704,11 @@ openerp.web.Header = openerp.web.Widget.extend(/** @lends openerp.web.Header# *
},
Save: function(){
var inner_viewmanager = action_manager.inner_viewmanager;
inner_viewmanager.views[inner_viewmanager.active_view].controller.do_save(function(){
inner_viewmanager.start();
inner_viewmanager.views[inner_viewmanager.active_view].controller.do_save()
.then(function() {
self.dialog.stop();
window.location.reload();
});
$(this).dialog('destroy')
}
}
});
@ -713,7 +716,7 @@ openerp.web.Header = openerp.web.Widget.extend(/** @lends openerp.web.Header# *
action_manager.appendTo(this.dialog);
action_manager.render(this.dialog);
},
change_password :function() {
var self = this;
this.dialog = new openerp.web.Dialog(this,{
@ -728,21 +731,13 @@ openerp.web.Header = openerp.web.Widget.extend(/** @lends openerp.web.Header# *
submitHandler: function (form) {
self.rpc("/web/session/change_password",{
'fields': $(form).serializeArray()
}, function(result) {
if (result.error) {
self.display_error(result);
}, function(result) {
if (result.error) {
self.display_error(result);
return;
}
else {
if (result.new_password) {
self.session.password = result.new_password;
var session = new openerp.web.Session(self.session.server, self.session.port);
session.start();
session.session_login(self.session.db, self.session.login, self.session.password)
}
}
self.notification.notify("Changed Password", "Password has been changed successfully");
self.dialog.close();
} else {
self.session.logout();
}
});
}
});
@ -766,7 +761,7 @@ openerp.web.Menu = openerp.web.Widget.extend(/** @lends openerp.web.Menu# */{
/**
* @constructs openerp.web.Menu
* @extends openerp.web.Widget
*
*
* @param parent
* @param element_id
* @param secondary_menu_id
@ -776,74 +771,132 @@ openerp.web.Menu = openerp.web.Widget.extend(/** @lends openerp.web.Menu# */{
this.secondary_menu_id = secondary_menu_id;
this.$secondary_menu = $("#" + secondary_menu_id).hide();
this.menu = false;
this.folded = false;
if (window.localStorage) {
this.folded = localStorage.getItem('oe_menu_folded') === 'true';
}
this.float_timeout = 700;
},
start: function() {
this.$secondary_menu.addClass(this.folded ? 'oe_folded' : 'oe_unfolded');
this.reload();
},
reload: function() {
this.rpc("/web/menu/load", {}, this.on_loaded);
},
on_loaded: function(data) {
this.data = data;
this.$element.html(QWeb.render("Menu", this.data));
for (var i = 0; i < this.data.data.children.length; i++) {
var v = { menu : this.data.data.children[i] };
this.$secondary_menu.append(QWeb.render("Menu.secondary", v));
}
this.$secondary_menu.find("div.menu_accordion").accordion({
animated : false,
autoHeight : false,
icons : false
});
this.$secondary_menu.find("div.submenu_accordion").accordion({
animated : false,
autoHeight : false,
active: false,
collapsible: true,
header: 'h4'
});
this.$element.html(QWeb.render("Menu", { widget : this }));
this.$secondary_menu.html(QWeb.render("Menu.secondary", { widget : this }));
this.$element.add(this.$secondary_menu).find("a").click(this.on_menu_click);
this.$secondary_menu.find('.oe_toggle_secondary_menu').click(this.on_toggle_fold);
},
on_toggle_fold: function() {
this.$secondary_menu.toggleClass('oe_folded').toggleClass('oe_unfolded');
if (this.folded) {
this.$secondary_menu.find('.oe_secondary_menu.active').show();
} else {
this.$secondary_menu.find('.oe_secondary_menu').hide();
}
this.folded = !this.folded;
if (window.localStorage) {
localStorage.setItem('oe_menu_folded', this.folded.toString());
}
},
on_menu_click: function(ev, id) {
id = id || 0;
var $menu, $parent, $secondary;
var $clicked_menu, manual = false;
if (id) {
// We can manually activate a menu with it's id (for hash url mapping)
$menu = this.$element.find('a[data-menu=' + id + ']');
if (!$menu.length) {
$menu = this.$secondary_menu.find('a[data-menu=' + id + ']');
manual = true;
$clicked_menu = this.$element.find('a[data-menu=' + id + ']');
if (!$clicked_menu.length) {
$clicked_menu = this.$secondary_menu.find('a[data-menu=' + id + ']');
}
} else {
$menu = $(ev.currentTarget);
id = $menu.data('menu');
}
if (this.$secondary_menu.has($menu).length) {
$secondary = $menu.parents('.menu_accordion');
$parent = this.$element.find('a[data-menu=' + $secondary.data('menu-parent') + ']');
} else {
$parent = $menu;
$secondary = this.$secondary_menu.find('.menu_accordion[data-menu-parent=' + $menu.attr('data-menu') + ']');
$clicked_menu = $(ev.currentTarget);
id = $clicked_menu.data('menu');
}
this.$secondary_menu.find('.menu_accordion').hide();
// TODO: ui-accordion : collapse submenus and expand the good one
$secondary.show();
if (id) {
if (this.do_menu_click($clicked_menu, manual) && id) {
this.session.active_id = id;
this.rpc('/web/menu/action', {'menu_id': id},
this.on_menu_action_loaded);
this.rpc('/web/menu/action', {'menu_id': id}, this.on_menu_action_loaded);
}
ev.stopPropagation();
return false;
},
do_menu_click: function($clicked_menu, manual) {
var $sub_menu, $main_menu,
active = $clicked_menu.is('.active'),
sub_menu_visible = false;
if (this.$secondary_menu.has($clicked_menu).length) {
$sub_menu = $clicked_menu.parents('.oe_secondary_menu');
$main_menu = this.$element.find('a[data-menu=' + $sub_menu.data('menu-parent') + ']');
} else {
$sub_menu = this.$secondary_menu.find('.oe_secondary_menu[data-menu-parent=' + $clicked_menu.attr('data-menu') + ']');
$main_menu = $clicked_menu;
}
sub_menu_visible = $sub_menu.is(':visible');
this.$secondary_menu.find('.oe_secondary_menu').hide();
$('.active', this.$element.add(this.$secondary_menu.show())).removeClass('active');
$parent.addClass('active');
$menu.addClass('active');
$menu.parent('h4').addClass('active');
$main_menu.add($clicked_menu).add($sub_menu).addClass('active');
if (this.$secondary_menu.has($menu).length) {
return !$menu.is(".leaf");
} else {
return false;
if (!(this.folded && manual)) {
this.do_show_secondary($sub_menu, $main_menu);
}
if ($main_menu != $clicked_menu) {
if ($clicked_menu.is('.submenu')) {
$sub_menu.find('.submenu.opened').each(function() {
if (!$(this).next().has($clicked_menu).length && !$(this).is($clicked_menu)) {
$(this).removeClass('opened').next().hide();
}
});
$clicked_menu.toggleClass('opened').next().toggle();
} else if ($clicked_menu.is('.leaf')) {
$sub_menu.toggle(!this.folded);
return true;
}
} else if (this.folded) {
if (active && sub_menu_visible) {
$sub_menu.hide();
return true;
}
} else {
return true;
}
return false;
},
do_show_secondary: function($sub_menu, $main_menu) {
var self = this;
if (this.folded) {
var css = $main_menu.position(),
fold_width = this.$secondary_menu.width() + 2,
window_width = $(window).width();
css.top += 33;
css.left -= Math.round(($sub_menu.width() - $main_menu.width()) / 2);
css.left = css.left < fold_width ? fold_width : css.left;
if ((css.left + $sub_menu.width()) > window_width) {
delete(css.left);
css.right = 1;
}
$sub_menu.css(css);
$sub_menu.mouseenter(function() {
clearTimeout($sub_menu.data('timeoutId'));
}).mouseleave(function(evt) {
var timeoutId = setTimeout(function() {
if (self.folded) {
$sub_menu.hide();
}
}, self.float_timeout);
$sub_menu.data('timeoutId', timeoutId);
});
}
$sub_menu.show();
},
on_menu_action_loaded: function(data) {
var self = this;
@ -860,7 +913,7 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
/**
* @constructs openerp.web.WebClient
* @extends openerp.web.Widget
*
*
* @param element_id
*/
init: function(element_id) {
@ -883,7 +936,7 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
openerp.web.Widget.prototype.notification = new openerp.web.Notification(this, "oe_notification");
this.header = new openerp.web.Header(this);
this.login = new openerp.web.Login(this, "oe_login");
this.login = new openerp.web.Login(this);
this.header.on_logout.add(this.login.on_logout);
this.header.on_action.add(this.on_menu_action);
@ -903,9 +956,8 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
start: function() {
this.header.appendTo($("#oe_header"));
this.session.start();
this.login.start();
this.login.appendTo($('#oe_login'));
this.menu.start();
console.debug("The openerp client has been initialized.");
},
on_logged: function() {
if(this.action_manager)
@ -951,7 +1003,7 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
self.execute_home_action(home_action[0], ds);
})
},
default_home: function () {
default_home: function () {
},
/**
* Bundles the execution of the home action
@ -976,7 +1028,6 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
},
do_url_set_hash: function(url) {
if(!this.url_external_hashchange) {
console.log("url set #hash to",url);
this.url_internal_hashchange = true;
jQuery.bbq.pushState(url);
}
@ -984,10 +1035,8 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
on_url_hashchange: function() {
if(this.url_internal_hashchange) {
this.url_internal_hashchange = false;
console.log("url jump to FLAG OFF");
} else {
var url = jQuery.deparam.fragment();
console.log("url jump to",url);
this.url_external_hashchange = true;
this.action_manager.on_url_hashchange(url);
this.url_external_hashchange = false;

View File

@ -1,7 +1,10 @@
/*---------------------------------------------------------
* OpenERP Web core
*--------------------------------------------------------*/
var console;
if (!console) {
console = {log: function () {}};
}
if (!console.debug) {
console.debug = console.log;
}
@ -483,7 +486,6 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web
self.user_context = result.context;
self.db = result.db;
self.session_save();
self.on_session_valid();
return true;
}).then(success_callback);
},
@ -772,7 +774,6 @@ openerp.web.SessionAware = openerp.web.CallbackEnabled.extend(/** @lends openerp
* // stuff that you want to init before the rendering
* },
* start: function() {
* this._super();
* // stuff you want to make after the rendering, `this.$element` holds a correct value
* this.$element.find(".my_button").click(/* an example of event binding * /);
*
@ -916,6 +917,8 @@ openerp.web.Widget = openerp.web.SessionAware.extend(/** @lends openerp.web.Widg
* @returns {jQuery.Deferred}
*/
start: function() {
/* The default implementation is only useful for retro-compatibility, it is
not necessary to call it using _super() when using Widget for new components. */
if (!this.$element) {
var tmp = document.getElementById(this.element_id);
this.$element = tmp ? $(tmp) : undefined;
@ -923,7 +926,7 @@ openerp.web.Widget = openerp.web.SessionAware.extend(/** @lends openerp.web.Widg
return $.Deferred().done().promise();
},
/**
* Destroys the current widget, also destory all its children before destroying itself.
* Destroys the current widget, also destroy all its children before destroying itself.
*/
stop: function() {
_.each(_.clone(this.widget_children), function(el) {
@ -944,7 +947,6 @@ openerp.web.Widget = openerp.web.SessionAware.extend(/** @lends openerp.web.Widg
* If that's not the case this method will simply return `false`.
*/
do_action: function(action, on_finished) {
console.log('Widget.do_action', action, on_finished);
if (this.widget_parent) {
return this.widget_parent.do_action(action, on_finished);
}

View File

@ -233,6 +233,7 @@ openerp.web.StaticDataGroup = openerp.web.GrouplessDataGroup.extend( /** @lends
});
openerp.web.DataSet = openerp.web.Widget.extend( /** @lends openerp.web.DataSet# */{
identifier_prefix: "dataset",
/**
* DateaManagement interface between views and the collection of selected
* OpenERP records (represents the view's state?)
@ -242,16 +243,12 @@ openerp.web.DataSet = openerp.web.Widget.extend( /** @lends openerp.web.DataSet
*
* @param {String} model the OpenERP model this dataset will manage
*/
init: function(source_controller, model, context) {
// we don't want the dataset to be a child of anything!
this._super(null);
this.session = source_controller ? source_controller.session : undefined;
init: function(parent, model, context) {
this._super(parent);
this.model = model;
this.context = context || {};
this.index = null;
},
start: function() {
},
previous: function () {
this.index -= 1;
if (this.index < 0) {
@ -549,13 +546,11 @@ openerp.web.DataSetSearch = openerp.web.DataSet.extend(/** @lends openerp.web.D
sort: this.sort(),
offset: offset,
limit: options.limit || false
}, function (result) {
}).pipe(function (result) {
self.ids = result.ids;
self.offset = offset;
if (callback) {
callback(result.records);
}
});
return result.records;
}).then(callback);
},
get_domain: function (other_domain) {
if (other_domain) {
@ -603,20 +598,27 @@ openerp.web.DataSetSearch = openerp.web.DataSet.extend(/** @lends openerp.web.D
});
openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({
virtual_id_prefix: "one2many_v_id_",
virtual_id_regex: /one2many_v_id_.*/,
debug_mode: true,
init: function() {
this._super.apply(this, arguments);
this.reset_ids([]);
this.last_default_get = {};
},
default_get: function(fields, callback) {
return this._super(fields).then(this.on_default_get).then(callback);
},
on_default_get: function(res) {
this.last_default_get = res;
},
create: function(data, callback, error_callback) {
var cached = {id:_.uniqueId(this.virtual_id_prefix), values: data};
var cached = {id:_.uniqueId(this.virtual_id_prefix), values: data,
defaults: this.last_default_get};
this.to_create.push(cached);
this.cache.push(cached);
this.on_change();
var to_return = $.Deferred().then(callback);
to_return.resolve({result: cached.id});
return to_return.promise();
var prom = $.Deferred().then(callback);
setTimeout(function() {prom.resolve({result: cached.id});}, 0);
return prom.promise();
},
write: function (id, data, options, callback) {
var self = this;
@ -676,7 +678,8 @@ openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({
var cached = _.detect(self.cache, function(x) {return x.id === id;});
var created = _.detect(self.to_create, function(x) {return x.id === id;});
if (created) {
_.each(fields, function(x) {if (cached.values[x] === undefined) cached.values[x] = false;});
_.each(fields, function(x) {if (cached.values[x] === undefined)
cached.values[x] = created.defaults[x] || false;});
} else {
if (!cached || !_.all(fields, function(x) {return cached.values[x] !== undefined}))
to_get.push(id);
@ -715,7 +718,13 @@ openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({
return completion.promise();
}
});
openerp.web.BufferedDataSet.virtual_id_regex = /^one2many_v_id_.*$/;
openerp.web.ReadOnlyDataSetSearch = openerp.web.DataSetSearch.extend({
default_get: function(fields, callback) {
return this._super(fields, callback).then(this.on_default_get);
},
on_default_get: function(result) {},
create: function(data, callback, error_callback) {
this.on_create(data);
var to_return = $.Deferred().then(callback);

View File

@ -0,0 +1,289 @@
openerp.web.data_import = function(openerp) {
var QWeb = openerp.web.qweb;
/**
* Safari does not deal well at all with raw JSON data being returned. As a
* result, we're going to cheat by using a pseudo-jsonp: instead of getting
* JSON data in the iframe, we're getting a ``script`` tag which consists of a
* function call and the returned data (the json dump).
*
* The function is an auto-generated name bound to ``window``, which calls
* back into the callback provided here.
*
* @param {Object} form the form element (DOM or jQuery) to use in the call
* @param {Object} attributes jquery.form attributes object
* @param {Function} callback function to call with the returned data
*/
function jsonp(form, attributes, callback) {
attributes = attributes || {};
var options = {jsonp: _.uniqueId('import_callback_')};
window[options.jsonp] = function () {
delete window[options.jsonp];
callback.apply(null, arguments);
};
if ('data' in attributes) {
_.extend(attributes.data, options);
} else {
_.extend(attributes, {data: options});
}
$(form).ajaxSubmit(attributes);
}
openerp.web.DataImport = openerp.web.Dialog.extend({
template: 'ImportDataView',
dialog_title: "Import Data",
init: function(parent, dataset){
var self = this;
this._super(parent, {});
this.model = parent.model;
this.fields = [];
this.all_fields = [];
this.required_fields = null;
var convert_fields = function (root, prefix) {
prefix = prefix || '';
_(root.fields).each(function (f) {
self.all_fields.push(prefix + f.name);
if (f.fields) {
convert_fields(f, prefix + f.id + '/');
}
});
};
this.ready = $.Deferred.queue().then(function () {
self.required_fields = _(self.fields).chain()
.filter(function (field) { return field.required; })
.pluck('name')
.value();
convert_fields(self);
self.all_fields.sort();
});
},
start: function() {
var self = this;
this._super();
this.open({
modal: true,
width: '70%',
height: 'auto',
position: 'top',
buttons: [
{text: "Close", click: function() { self.stop(); }},
{text: "Import File", click: function() { self.do_import(); }, 'class': 'oe-dialog-import-button'}
],
close: function(event, ui) {
self.stop();
}
});
this.toggle_import_button(false);
this.$element.find('#csvfile').change(this.on_autodetect_data);
this.$element.find('fieldset').change(this.on_autodetect_data);
this.$element.find('fieldset legend').click(function() {
$(this).next().toggle();
});
this.ready.push(new openerp.web.DataSet(this, this.model).call(
'fields_get', [], function (fields) {
self.graft_fields(fields);
}));
},
graft_fields: function (fields, parent, level) {
parent = parent || this;
level = level || 0;
var self = this;
_(fields).each(function (field, field_name) {
var f = {
id: field_name,
name: field_name,
string: field.string,
required: field.required
};
switch (field.type) {
case 'many2many':
case 'many2one':
f.name += '/id';
break;
case 'one2many':
f.name += '/id';
f.fields = [];
// only fetch sub-fields to a depth of 2 levels
if (level < 2) {
self.ready.push(new openerp.web.DataSet(self, field.relation).call(
'fields_get', [], function (fields) {
self.graft_fields(fields, f, level+1);
}));
}
break;
}
parent.fields.push(f);
});
},
toggle_import_button: function (newstate) {
this.$dialog.dialog('widget')
.find('.oe-dialog-import-button')
.button('option', 'disabled', !newstate);
},
do_import: function() {
if(!this.$element.find('#csvfile').val()) { return; }
var lines_to_skip = parseInt(this.$element.find('#csv_skip').val(), 10);
var with_headers = this.$element.find('#file_has_headers').prop('checked');
if (!lines_to_skip && with_headers) {
lines_to_skip = 1;
}
var indices = [], fields = [];
this.$element.find(".sel_fields").each(function(index, element) {
var val = element.value;
if (!val) {
return;
}
indices.push(index);
fields.push(val);
});
jsonp(this.$element.find('#import_data'), {
url: '/web/import/import_data',
data: {
model: this.model,
meta: JSON.stringify({
skip: lines_to_skip,
indices: indices,
fields: fields
})
}
}, this.on_import_results);
},
on_autodetect_data: function() {
if(!this.$element.find('#csvfile').val()) { return; }
jsonp(this.$element.find('#import_data'), {
url: '/web/import/detect_data'
}, this.on_import_results);
},
on_import_results: function(results) {
this.$element.find('#result').empty();
var headers, result_node = this.$element.find("#result");
if (results['records']) {
var lines_to_skip = parseInt(this.$element.find('#csv_skip').val(), 10),
with_headers = this.$element.find('#file_has_headers').prop('checked');
headers = with_headers ? results.records[0] : null;
result_node.append(QWeb.render('ImportView.result', {
'headers': headers,
'records': lines_to_skip ? results.records.slice(lines_to_skip)
: with_headers ? results.records.slice(1)
: results.records
}));
} else if (results['error']) {
result_node.append(QWeb.render('ImportView.error', {
'error': results['error']}));
} else if (results['success']) {
if (this.widget_parent.widget_parent.active_view == "list") {
this.widget_parent.reload_content();
}
this.stop();
return;
}
var self = this;
this.ready.then(function () {
var $fields = self.$element.find('.sel_fields').bind('blur', function () {
if (this.value && !_(self.all_fields).contains(this.value)) {
this.value = '';
}
}).autocomplete({
minLength: 0,
source: self.all_fields,
change: self.on_check_field_values
}).focus(function () {
$(this).autocomplete('search');
});
// Column auto-detection
_(headers).each(function (header, index) {
var f =_(self.fields).detect(function (field) {
// TODO: levenshtein between header and field.string
return field.name === header || field.string.toLowerCase() === header;
});
if (f) {
$fields.eq(index).val(f.name);
}
});
self.on_check_field_values();
});
},
/**
* Looks through all the field selections, and tries to find if two
* (or more) columns were matched to the same model field.
*
* Returns a map of the multiply-mapped fields to an array of offending
* columns (not actually columns, but the inputs containing the same field
* names).
*
* Also has the side-effect of marking the discovered inputs with the class
* ``duplicate_fld``.
*
* @returns {Object<String, Array<String>>} map of duplicate field matches to same-valued inputs
*/
find_duplicate_fields: function() {
// Maps values to DOM nodes, in order to discover duplicates
var values = {}, duplicates = {};
this.$element.find(".sel_fields").each(function(index, element) {
var value = element.value;
var $element = $(element).removeClass('duplicate_fld');
if (!value) { return; }
if (!(value in values)) {
values[value] = element;
} else {
var same_valued_field = values[value];
if (value in duplicates) {
duplicates[value].push(element);
} else {
duplicates[value] = [same_valued_field, element];
}
$element.add(same_valued_field).addClass('duplicate_fld');
}
});
return duplicates;
},
on_check_field_values: function () {
this.$element.find("#message, #msg").remove();
var required_valid = this.check_required();
var duplicates = this.find_duplicate_fields();
if (_.isEmpty(duplicates)) {
this.toggle_import_button(required_valid);
} else {
var $err = $('<div id="msg" style="color: red;">Destination fields should only be selected once, some fields are selected more than once:</div>').insertBefore(this.$element.find('#result'));
var $dupes = $('<dl>').appendTo($err);
_(duplicates).each(function(elements, value) {
$('<dt>').text(value).appendTo($dupes);
_(elements).each(function(element) {
var cell = $(element).closest('td');
$('<dd>').text(cell.parent().children().index(cell)).appendTo($dupes);
});
});
this.toggle_import_button(false);
}
},
check_required: function() {
if (!this.required_fields.length) { return true; }
var selected_fields = _(this.$element.find('.sel_fields').get()).chain()
.pluck('value')
.compact()
.value();
var missing_fields = _.difference(this.required_fields, selected_fields);
if (missing_fields.length) {
this.$element.find("#result").before('<div id="message" style="color:red">*Required Fields are not selected : ' + missing_fields + '.</div>');
return false;
}
return true;
},
stop: function() {
$(this.$dialog).remove();
this._super();
}
});
};

View File

@ -82,7 +82,12 @@ openerp.web.parse_value = function (value, descriptor, value_if_empty) {
}
switch (descriptor.widget || descriptor.type) {
case 'integer':
var tmp = Number(value);
var tmp;
do {
tmp = value;
value = value.replace(openerp.web._t.database.parameters.thousands_sep, "");
} while(tmp !== value);
tmp = Number(value);
if (isNaN(tmp))
throw value + " is not a correct integer";
return tmp;
@ -113,26 +118,26 @@ openerp.web.parse_value = function (value, descriptor, value_if_empty) {
var tmp = Date.parseExact(value, _.sprintf("%s %s", Date.CultureInfo.formatPatterns.shortDate,
Date.CultureInfo.formatPatterns.longTime));
if (tmp !== null)
return tmp;
return openerp.web.datetime_to_str(tmp);
tmp = Date.parse(value);
if (tmp !== null)
return tmp;
return openerp.web.datetime_to_str(tmp);
throw value + " is not a valid datetime";
case 'date':
var tmp = Date.parseExact(value, Date.CultureInfo.formatPatterns.shortDate);
if (tmp !== null)
return tmp;
return openerp.web.date_to_str(tmp);
tmp = Date.parse(value);
if (tmp !== null)
return tmp;
return openerp.web.date_to_str(tmp);
throw value + " is not a valid date";
case 'time':
var tmp = Date.parseExact(value, Date.CultureInfo.formatPatterns.longTime);
if (tmp !== null)
return tmp;
return openerp.web.time_to_str(tmp);
tmp = Date.parse(value);
if (tmp !== null)
return tmp;
return openerp.web.time_to_str(tmp);
throw value + " is not a valid time";
}
return value;

View File

@ -327,7 +327,8 @@ openerp.web.SearchView = openerp.web.Widget.extend(/** @lends openerp.web.Search
this.notification.notify("Invalid Search", "triggered from search view");
},
do_clear: function () {
$('.filter_label').removeClass('enabled');
this.$element.find('.filter_label').removeClass('enabled');
this.enabled_filters.splice(0);
var string = $('a.searchview_group_string');
_.each(string, function(str){
$(str).closest('div.searchview_group').removeClass("expanded").addClass('folded');
@ -542,6 +543,7 @@ openerp.web.search.FilterGroup = openerp.web.search.Input.extend(/** @lends open
var domains = _(this.filters).chain()
.filter(function (filter) { return filter.is_enabled(); })
.map(function (filter) { return filter.attrs.domain; })
.reject(_.isEmpty)
.value();
if (!domains.length) { return; }
@ -720,7 +722,11 @@ openerp.web.search.NumberField = openerp.web.search.Field.extend(/** @lends open
openerp.web.search.IntegerField = openerp.web.search.NumberField.extend(/** @lends openerp.web.search.IntegerField# */{
error_message: "not a valid integer",
parse: function (value) {
return parseInt(value, 10);
try {
return openerp.web.parse_value(value, {'widget': 'integer'});
} catch (e) {
return NaN;
}
}
});
/**
@ -730,7 +736,11 @@ openerp.web.search.IntegerField = openerp.web.search.NumberField.extend(/** @len
openerp.web.search.FloatField = openerp.web.search.NumberField.extend(/** @lends openerp.web.search.FloatField# */{
error_message: "not a valid number",
parse: function (value) {
return parseFloat(value);
try {
return openerp.web.parse_value(value, {'widget': 'float'});
} catch (e) {
return NaN;
}
}
});
/**
@ -785,20 +795,18 @@ openerp.web.search.BooleanField = openerp.web.search.SelectionField.extend(/** @
* @extends openerp.web.search.DateField
*/
openerp.web.search.DateField = openerp.web.search.Field.extend(/** @lends openerp.web.search.DateField# */{
/**
* enables date picker on the HTML widgets
*/
template: "SearchView.date",
start: function () {
this._super();
this.$element.addClass('field_date').datepicker({
dateFormat: 'yy-mm-dd'
});
},
stop: function () {
this.$element.datepicker('destroy');
this.datewidget = new openerp.web.DateWidget(this);
this.datewidget.prependTo(this.$element);
this.datewidget.$element.find("input").attr("size", 15);
this.datewidget.$element.find("input").attr("autofocus",
this.attrs.default_focus === '1' ? 'autofocus' : undefined);
this.datewidget.set_value(this.defaults[this.attrs.name] || false);
},
get_value: function () {
return this.$element.val();
return this.datewidget.get_value() || null;
}
});
/**
@ -1113,7 +1121,7 @@ openerp.web.search.ExtendedSearchProposition.Char = openerp.web.OldWidget.extend
}
});
openerp.web.search.ExtendedSearchProposition.DateTime = openerp.web.OldWidget.extend({
template: 'SearchView.extended_search.proposition.datetime',
template: 'SearchView.extended_search.proposition.empty',
identifier_prefix: 'extended-search-proposition-datetime',
operators: [
{value: "=", text: "is equal to"},
@ -1124,18 +1132,16 @@ openerp.web.search.ExtendedSearchProposition.DateTime = openerp.web.OldWidget.ex
{value: "<=", text: "less or equal than"}
],
get_value: function() {
return this.$element.val();
return this.datewidget.get_value();
},
start: function() {
this._super();
this.$element.datetimepicker({
dateFormat: 'yy-mm-dd',
timeFormat: 'hh:mm:ss'
});
this.datewidget = new openerp.web.DateTimeWidget(this);
this.datewidget.prependTo(this.$element);
}
});
openerp.web.search.ExtendedSearchProposition.Date = openerp.web.OldWidget.extend({
template: 'SearchView.extended_search.proposition.date',
template: 'SearchView.extended_search.proposition.empty',
identifier_prefix: 'extended-search-proposition-date',
operators: [
{value: "=", text: "is equal to"},
@ -1146,14 +1152,12 @@ openerp.web.search.ExtendedSearchProposition.Date = openerp.web.OldWidget.extend
{value: "<=", text: "less or equal than"}
],
get_value: function() {
return this.$element.val();
return this.datewidget.get_value();
},
start: function() {
this._super();
this.$element.datepicker({
dateFormat: 'yy-mm-dd',
timeFormat: 'hh:mm:ss'
});
this.datewidget = new openerp.web.DateWidget(this);
this.datewidget.prependTo(this.$element);
}
});
openerp.web.search.ExtendedSearchProposition.Integer = openerp.web.OldWidget.extend({
@ -1168,11 +1172,11 @@ openerp.web.search.ExtendedSearchProposition.Integer = openerp.web.OldWidget.ext
{value: "<=", text: "less or equal than"}
],
get_value: function() {
var value = parseFloat(this.$element.val());
if(value != 0 && !value) {
try {
return openerp.web.parse_value(this.$element.val(), {'widget': 'integer'});
} catch (e) {
return "";
}
return Math.round(value);
}
});
openerp.web.search.ExtendedSearchProposition.Float = openerp.web.OldWidget.extend({
@ -1187,11 +1191,11 @@ openerp.web.search.ExtendedSearchProposition.Float = openerp.web.OldWidget.exten
{value: "<=", text: "less or equal than"}
],
get_value: function() {
var value = parseFloat(this.$element.val());
if(value != 0 && !value) {
try {
return openerp.web.parse_value(this.$element.val(), {'widget': 'float'});
} catch (e) {
return "";
}
return value;
}
});
openerp.web.search.ExtendedSearchProposition.Selection = openerp.web.OldWidget.extend({

View File

@ -26,7 +26,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
this.set_default_options(options);
this.dataset = dataset;
this.model = dataset.model;
this.view_id = view_id;
this.view_id = view_id || false;
this.fields_view = {};
this.widgets = {};
this.widgets_counter = 0;
@ -41,7 +41,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
this.has_been_loaded = $.Deferred();
this.$form_header = null;
this.translatable_fields = [];
_.defaults(this.options, {"always_show_new_button": true});
_.defaults(this.options, {"always_show_new_button": true,
"not_interactible_on_create": false});
},
start: function() {
this._super();
@ -72,17 +73,25 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
_.each(this.widgets, function(w) {
w.stop();
});
this._super();
},
reposition: function ($e) {
this.$element = $e;
this.on_loaded();
},
on_loaded: function(data) {
var self = this;
this.fields_view = data;
var frame = new (this.registry.get_object('frame'))(this, this.fields_view.arch);
if (data) {
this.fields_view = data;
var frame = new (this.registry.get_object('frame'))(this, this.fields_view.arch);
this.$element.html(QWeb.render(this.form_template, { 'frame': frame, 'view': this }));
this.rendered = QWeb.render(this.form_template, { 'frame': frame, 'view': this });
}
this.$element.html(this.rendered);
_.each(this.widgets, function(w) {
w.start();
});
this.$form_header = this.$element.find('#' + this.element_id + '_header');
this.$form_header = this.$element.find('.oe_form_header:first');
this.$form_header.find('div.oe_form_pager button[data-pager-action]').click(function() {
var action = $(this).data('pager-action');
self.on_pager_action(action);
@ -93,6 +102,17 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
this.$form_header.find('button.oe_form_button_cancel').click(this.do_cancel);
this.$form_header.find('button.oe_form_button_new').click(this.on_button_new);
this.$form_header.find('button.oe_form_button_duplicate').click(this.on_button_duplicate);
this.$form_header.find('button.oe_form_button_toggle').click(function () {
self.translatable_fields = [];
self.widgets = {};
self.fields = {};
self.$form_header.find('button').unbind('click');
self.registry = self.registry === openerp.web.form.widgets
? openerp.web.form.readonly
: openerp.web.form.widgets;
self.on_loaded(self.fields_view);
self.reload();
});
if (this.options.sidebar && this.options.sidebar_id) {
this.sidebar = new openerp.web.Sidebar(this, this.options.sidebar_id);
@ -196,7 +216,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
}
},
do_update_pager: function(hide_index) {
var $pager = this.$element.find('#' + this.element_id + '_header div.oe_form_pager');
var $pager = this.$form_header.find('div.oe_form_pager');
var index = hide_index ? '-' : this.dataset.index + 1;
$pager.find('span.oe_pager_index').html(index);
$pager.find('span.oe_pager_count').html(this.dataset.ids.length);
@ -304,9 +324,15 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
var def = $.Deferred();
$.when(this.has_been_loaded).then(function() {
if (self.can_be_discarded()) {
self.dataset.default_get(_.keys(self.fields_view.fields)).then(self.on_record_loaded).then(function() {
var keys = _.keys(self.fields_view.fields);
if (keys.length) {
self.dataset.default_get(keys).then(self.on_record_loaded).then(function() {
def.resolve();
});
} else {
self.on_record_loaded({});
def.resolve();
});
}
}
});
return def.promise();
@ -339,7 +365,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
do_save: function(success, prepend_on_create) {
var self = this;
if (!this.ready) {
return false;
return $.Deferred().reject();
}
var form_dirty = false,
form_invalid = false,
@ -361,23 +387,18 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
if (form_invalid) {
first_invalid_field.focus();
this.on_invalid();
return false;
} else if (form_dirty) {
return $.Deferred().reject();
} else {
console.log("About to save", values);
if (!this.datarecord.id) {
return this.dataset.create(values, function(r) {
self.on_created(r, success, prepend_on_create);
});
return this.dataset.create(values).pipe(function(r) {
return self.on_created(r, undefined, prepend_on_create);
}).then(success);
} else {
return this.dataset.write(this.datarecord.id, values, {}, function(r) {
self.on_saved(r, success);
});
return this.dataset.write(this.datarecord.id, values, {}).pipe(function(r) {
return self.on_saved(r);
}).then(success);
}
} else {
setTimeout(function() {
self.on_saved({ result: true }, success);
});
return true;
}
},
do_save_edit: function() {
@ -401,11 +422,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
on_saved: function(r, success) {
if (!r.result) {
// should not happen in the server, but may happen for internal purpose
return $.Deferred().reject();
} else {
if (success) {
success(r);
}
this.reload();
return $.Deferred().then(success).resolve(r);
}
},
/**
@ -424,6 +444,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
on_created: function(r, success, prepend_on_create) {
if (!r.result) {
// should not happen in the server, but may happen for internal purpose
return $.Deferred().reject();
} else {
this.datarecord.id = r.result;
if (!prepend_on_create) {
@ -438,10 +459,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
this.sidebar.attachments.do_update();
}
console.debug("The record has been created with id #" + this.datarecord.id);
if (success) {
success(_.extend(r, {created: true}));
}
this.reload();
return $.Deferred().then(success).resolve(_.extend(r, {created: true}));
}
},
do_search: function (domains, contexts, groupbys) {
@ -471,6 +490,24 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
get_selected_ids: function() {
var id = this.dataset.ids[this.dataset.index];
return id ? [id] : [];
},
recursive_save: function() {
var self = this;
return $.when(this.do_save()).pipe(function(res) {
if (self.dataset.parent_view)
return self.dataset.parent_view.recursive_save();
});
},
is_interactible_record: function() {
var id = this.datarecord.id;
if (!id) {
if (this.options.not_interactible_on_create)
return false;
} else if (typeof(id) === "string") {
if(openerp.web.BufferedDataSet.virtual_id_regex.test(id))
return false;
}
return true;
}
});
openerp.web.FormDialog = openerp.web.Dialog.extend({
@ -618,6 +655,7 @@ openerp.web.form.compute_domain = function(expr, fields) {
openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form.Widget# */{
template: 'Widget',
identifier_prefix: 'formview-widget-',
/**
* @constructs openerp.web.form.Widget
* @extends openerp.web.Widget
@ -631,11 +669,13 @@ openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form.
this.modifiers = JSON.parse(this.node.attrs.modifiers || '{}');
this.type = this.type || node.tag;
this.element_name = this.element_name || this.type;
this.element_id = [this.view.element_id, this.element_name, this.view.widgets_counter++].join("_");
this.element_class = [
'formview', this.view.view_id, this.element_name,
this.view.widgets_counter++].join("_");
this._super(view, this.element_id);
this._super(view);
this.view.widgets[this.element_id] = this;
this.view.widgets[this.element_class] = this;
this.children = node.children;
this.colspan = parseInt(node.attrs.colspan || 1, 10);
this.decrease_max_width = 0;
@ -648,12 +688,7 @@ openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form.
this.width = this.node.attrs.width;
},
start: function() {
this.$element = $('#' + this.element_id);
},
stop: function() {
if (this.$element) {
this.$element.remove();
}
this.$element = this.view.$element.find('.' + this.element_class);
},
process_modifiers: function() {
var compute_domain = openerp.web.form.compute_domain;
@ -757,13 +792,24 @@ openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({
for (var i = 0; i < node.children.length; i++) {
var n = node.children[i];
if (n.tag == "page") {
var page = new openerp.web.form.WidgetNotebookPage(this.view, n, this, this.pages.length);
var page = new openerp.web.form.WidgetNotebookPage(
this.view, n, this, this.pages.length);
this.pages.push(page);
}
}
},
start: function() {
var self = this;
this._super.apply(this, arguments);
this.$element.find('> ul > li').each(function (index, tab_li) {
var page = self.pages[index],
id = _.uniqueId(self.element_name + '-');
page.element_id = id;
$(tab_li).find('a').attr('href', '#' + id);
});
this.$element.find('> div').each(function (index, page) {
page.id = self.pages[index].element_id;
});
this.$element.tabs();
this.view.on_button_new.add_last(this.do_select_first_visible_tab);
},
@ -785,11 +831,11 @@ openerp.web.form.WidgetNotebookPage = openerp.web.form.WidgetFrame.extend({
this.index = index;
this.element_name = 'page_' + index;
this._super(view, node);
this.element_tab_id = this.element_id + '_tab';
},
start: function() {
this._super.apply(this, arguments);
this.$element_tab = $('#' + this.element_tab_id);
this.$element_tab = this.notebook.$element.find(
'> ul > li:eq(' + this.index + ')');
},
update_dom: function() {
if (this.invisible && this.index === this.notebook.$element.tabs('option', 'selected')) {
@ -801,9 +847,9 @@ openerp.web.form.WidgetNotebookPage = openerp.web.form.WidgetFrame.extend({
});
openerp.web.form.WidgetSeparator = openerp.web.form.Widget.extend({
template: 'WidgetSeparator',
init: function(view, node) {
this._super(view, node);
this.template = "WidgetSeparator";
this.orientation = node.attrs.orientation || 'horizontal';
if (this.orientation === 'vertical') {
this.width = '1';
@ -813,9 +859,10 @@ openerp.web.form.WidgetSeparator = openerp.web.form.Widget.extend({
});
openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
template: 'WidgetButton',
init: function(view, node) {
this._super(view, node);
this.template = "WidgetButton";
this.force_disabled = false;
if (this.string) {
// We don't have button key bindings in the webclient
this.string = this.string.replace(/_/g, '');
@ -827,45 +874,74 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
},
start: function() {
this._super.apply(this, arguments);
this.$element.click(this.on_click);
this.$element.find("button").click(this.on_click);
},
on_click: function(saved) {
on_click: function() {
var self = this;
if ((!this.node.attrs.special && this.view.dirty_for_user && saved !== true) || !this.view.datarecord.id) {
this.view.do_save(function() {
self.on_click(true);
});
} else {
if (this.node.attrs.confirm) {
var dialog = $('<div>' + this.node.attrs.confirm + '</div>').dialog({
this.force_disabled = true;
this.check_disable();
this.execute_action().always(function() {
self.force_disabled = false;
self.check_disable();
});
},
execute_action: function() {
var self = this;
var exec_action = function() {
if (self.node.attrs.confirm) {
var def = $.Deferred();
var dialog = $('<div>' + self.node.attrs.confirm + '</div>').dialog({
title: 'Confirm',
modal: true,
buttons: {
Ok: function() {
self.on_confirmed();
$(this).dialog("close");
self.on_confirmed().then(function() {
def.resolve();
});
$(self).dialog("close");
},
Cancel: function() {
$(this).dialog("close");
def.resolve();
$(self).dialog("close");
}
}
});
return def.promise();
} else {
this.on_confirmed();
return self.on_confirmed();
}
};
if ((!this.node.attrs.special && this.view.dirty_for_user) || !this.view.datarecord.id) {
return this.view.recursive_save().pipe(exec_action);
} else {
return exec_action();
}
},
on_confirmed: function() {
var self = this;
this.view.do_execute_action(
return this.view.do_execute_action(
this.node.attrs, this.view.dataset, this.view.datarecord.id, function () {
self.view.reload();
});
},
update_dom: function() {
this._super();
this.check_disable();
},
check_disable: function() {
if (this.force_disabled || !this.view.is_interactible_record()) {
this.$element.find("button").attr("disabled", "disabled");
this.$element.find("button").css("color", "grey");
} else {
this.$element.find("button").removeAttr("disabled");
this.$element.find("button").css("color", "");
}
}
});
openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({
template: 'WidgetLabel',
init: function(view, node) {
this.element_name = 'label_' + node.attrs.name;
@ -876,7 +952,6 @@ openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({
this.template = "WidgetParagraph";
this.colspan = parseInt(this.node.attrs.colspan || 1, 10);
} else {
this.template = "WidgetLabel";
this.colspan = 1;
this.width = '1%';
this.decrease_max_width = 1;
@ -895,7 +970,7 @@ openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({
var self = this;
this.$element.find("label").dblclick(function() {
var widget = self['for'] || self;
console.log(widget.element_id , widget);
console.log(widget.element_class , widget);
window.w = widget;
});
}
@ -960,7 +1035,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
return !this.invalid;
},
is_dirty: function() {
return this.dirty;
return this.dirty && !this.readonly;
},
get_on_change_value: function() {
return this.get_value();
@ -995,37 +1070,40 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
focus: function() {
},
_build_view_fields_values: function() {
var a_dataset = this.view.dataset || {};
var a_dataset = this.view.dataset;
var fields_values = this.view.get_fields_values();
var parent_values = a_dataset.parent_view ? a_dataset.parent_view.get_fields_values() : {};
fields_values.parent = parent_values;
return fields_values;
},
_build_eval_context: function() {
var a_dataset = this.view.dataset;
return new openerp.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values());
},
/**
* Builds a new context usable for operations related to fields by merging
* the fields'context with the action's context.
*/
build_context: function() {
// I previously belevied contexts should be herrited, but now I doubt it
//var a_context = this.view.dataset.get_context() || {};
var f_context = this.field.context || null;
// maybe the default_get should only be used when we do a default_get?
var v_context1 = this.node.attrs.default_get || {};
var v_context2 = this.node.attrs.context || {};
var v_context = new openerp.web.CompoundContext(v_context1, v_context2);
if (v_context1.__ref || v_context2.__ref || true) { //TODO niv: remove || true
var fields_values = this._build_view_fields_values();
var v_contexts = _.compact([this.node.attrs.default_get || null,
this.node.attrs.context || null]);
var v_context = new openerp.web.CompoundContext();
_.each(v_contexts, function(x) {v_context.add(x);});
if (_.detect(v_contexts, function(x) {return !!x.__ref;})) {
var fields_values = this._build_eval_context();
v_context.set_eval_context(fields_values);
}
// if there is a context on the node, overrides the model's context
var ctx = f_context || v_context;
var ctx = v_contexts.length > 0 ? v_context : f_context;
return ctx;
},
build_domain: function() {
var f_domain = this.field.domain || null;
var v_domain = this.node.attrs.domain || [];
if (!(v_domain instanceof Array) || true) { //TODO niv: remove || true
var fields_values = this._build_view_fields_values();
var fields_values = this._build_eval_context();
v_domain = new openerp.web.CompoundDomain(v_domain).set_eval_context(fields_values);
}
// if there is a domain on the node, overrides the model's domain
@ -1034,10 +1112,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
});
openerp.web.form.FieldChar = openerp.web.form.Field.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldChar";
},
template: 'FieldChar',
start: function() {
this._super.apply(this, arguments);
this.$element.find('input').change(this.on_ui_change);
@ -1046,6 +1121,7 @@ openerp.web.form.FieldChar = openerp.web.form.Field.extend({
this._super.apply(this, arguments);
var show_value = openerp.web.format_value(value, this, '');
this.$element.find('input').val(show_value);
return show_value;
},
update_dom: function() {
this._super.apply(this, arguments);
@ -1070,10 +1146,7 @@ openerp.web.form.FieldChar = openerp.web.form.Field.extend({
});
openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldEmail";
},
template: 'FieldEmail',
start: function() {
this._super.apply(this, arguments);
this.$element.find('button').click(this.on_button_clicked);
@ -1084,18 +1157,11 @@ openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({
} else {
location.href = 'mailto:' + this.value;
}
},
set_value: function(value) {
this._super.apply(this, arguments);
this.$element.find('a').attr('href', 'mailto:' + this.$element.find('input').val());
}
});
openerp.web.form.FieldUrl = openerp.web.form.FieldChar.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldUrl";
},
template: 'FieldUrl',
start: function() {
this._super.apply(this, arguments);
this.$element.find('button').click(this.on_button_clicked);
@ -1120,16 +1186,13 @@ openerp.web.form.FieldFloat = openerp.web.form.FieldChar.extend({
}
});
openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldDate";
this.jqueryui_object = 'datetimepicker';
},
openerp.web.DateTimeWidget = openerp.web.Widget.extend({
template: "web.datetimepicker",
jqueryui_object: 'datetimepicker',
type_of_date: "datetime",
start: function() {
var self = this;
this._super.apply(this, arguments);
this.$element.find('input').change(this.on_ui_change);
this.$element.find('input').change(this.on_change);
this.picker({
onSelect: this.on_picker_select,
changeMonth: true,
@ -1147,6 +1210,8 @@ openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({
this.$element.find('button.oe_datepicker_close').click(function() {
self.$element.find('.oe_datepicker').hide();
});
this.set_readonly(false);
this.value = false;
},
picker: function() {
return $.fn[this.jqueryui_object].apply(this.$element.find('.oe_datepicker_container'), arguments);
@ -1156,68 +1221,98 @@ openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({
this.$element.find('input').val(date ? this.format_client(date) : '').change();
},
set_value: function(value) {
value = this.parse(value);
this._super(value);
this.value = value;
this.$element.find('input').val(value ? this.format_client(value) : '');
},
get_value: function() {
return this.format(this.value);
return this.value;
},
set_value_from_ui: function() {
var value = this.$element.find('input').val() || false;
this.value = this.parse_client(value);
this._super();
},
update_dom: function() {
this._super.apply(this, arguments);
set_readonly: function(readonly) {
this.readonly = readonly;
this.$element.find('input').attr('disabled', this.readonly);
this.$element.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', this.readonly);
this.$element.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', readonly);
},
validate: function() {
this.invalid = false;
is_valid: function(required) {
var value = this.$element.find('input').val();
if (value === "") {
this.invalid = this.required;
return !required;
} else {
try {
this.parse_client(value);
this.invalid = false;
return true;
} catch(e) {
this.invalid = true;
return false;
}
}
},
focus: function() {
this.$element.find('input').focus();
},
parse: openerp.web.auto_str_to_date,
parse_client: function(v) {
return openerp.web.parse_value(v, this.field);
},
format: function(val) {
return openerp.web.auto_date_to_str(val, this.field.type);
return openerp.web.parse_value(v, {"widget": this.type_of_date});
},
format_client: function(v) {
return openerp.web.format_value(v, this.field);
return openerp.web.format_value(v, {"widget": this.type_of_date});
},
on_change: function() {
if (this.is_valid()) {
this.set_value_from_ui();
}
}
});
openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({
init: function(view, node) {
this._super(view, node);
this.jqueryui_object = 'datepicker';
},
openerp.web.DateWidget = openerp.web.DateTimeWidget.extend({
jqueryui_object: 'datepicker',
type_of_date: "date",
on_picker_select: function(text, instance) {
this._super(text, instance);
this.$element.find('.oe_datepicker').hide();
}
});
openerp.web.form.FieldText = openerp.web.form.Field.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldText";
openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({
template: "EmptyComponent",
build_widget: function() {
return new openerp.web.DateTimeWidget(this);
},
start: function() {
var self = this;
this._super.apply(this, arguments);
this.datewidget = this.build_widget();
this.datewidget.on_change.add(this.on_ui_change);
this.datewidget.appendTo(this.$element);
},
set_value: function(value) {
this._super(value);
this.datewidget.set_value(value);
},
get_value: function() {
return this.datewidget.get_value();
},
update_dom: function() {
this._super.apply(this, arguments);
this.datewidget.set_readonly(this.readonly);
},
validate: function() {
this.invalid = !this.datewidget.is_valid(this.required);
},
focus: function() {
this.datewidget.focus();
}
});
openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({
build_widget: function() {
return new openerp.web.DateWidget(this);
}
});
openerp.web.form.FieldText = openerp.web.form.Field.extend({
template: 'FieldText',
start: function() {
this._super.apply(this, arguments);
this.$element.find('textarea').change(this.on_ui_change);
@ -1250,10 +1345,7 @@ openerp.web.form.FieldText = openerp.web.form.Field.extend({
});
openerp.web.form.FieldBoolean = openerp.web.form.Field.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldBoolean";
},
template: 'FieldBoolean',
start: function() {
var self = this;
this._super.apply(this, arguments);
@ -1284,10 +1376,7 @@ openerp.web.form.FieldBoolean = openerp.web.form.Field.extend({
});
openerp.web.form.FieldProgressBar = openerp.web.form.Field.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldProgressBar";
},
template: 'FieldProgressBar',
start: function() {
this._super.apply(this, arguments);
this.$element.find('div').progressbar({
@ -1310,10 +1399,10 @@ openerp.web.form.FieldTextXml = openerp.web.form.Field.extend({
});
openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
template: 'FieldSelection',
init: function(view, node) {
var self = this;
this._super(view, node);
this.template = "FieldSelection";
this.values = this.field.selection;
_.each(this.values, function(v, i) {
if (v[0] === false && v[1] === '') {
@ -1420,9 +1509,9 @@ openerp.web.form.dialog = function(content, options) {
};
openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
template: 'FieldMany2One',
init: function(view, node) {
this._super(view, node);
this.template = "FieldMany2One";
this.limit = 7;
this.value = null;
this.cm_id = _.uniqueId('m2o_cm_');
@ -1435,7 +1524,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
this.$input = this.$element.find("input");
this.$drop_down = this.$element.find(".oe-m2o-drop-down-button");
this.$menu_btn = this.$element.find(".oe-m2o-cm-button");
// context menu
var init_context_menu_def = $.Deferred().then(function(e) {
var rdataset = new openerp.web.DataSetStatic(self, "ir.values", self.build_context());
@ -1443,7 +1532,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
[[self.field.relation, false]], false, rdataset.get_context()], false, 0)
.then(function(result) {
self.related_entries = result;
var $cmenu = $("#" + self.cm_id);
$cmenu.append(QWeb.render("FieldMany2One.context_menu", {widget: self}));
var bindings = {};
@ -1747,10 +1836,10 @@ var commands = {
}
};
openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
template: 'FieldOne2Many',
multi_selection: false,
init: function(view, node) {
this._super(view, node);
this.template = "FieldOne2Many";
this.is_started = $.Deferred();
this.form_last_update = $.Deferred();
this.disable_utility_classes = true;
@ -1781,6 +1870,8 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
}
if(view.view_type === "list") {
view.options.selectable = self.multi_selection;
} else if (view.view_type === "form") {
view.options.not_interactible_on_create = true;
}
views.push(view);
});
@ -1840,6 +1931,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
switch (command[0]) {
case commands.CREATE:
obj['id'] = _.uniqueId(self.dataset.virtual_id_prefix);
obj.defaults = {};
self.dataset.to_create.push(obj);
self.dataset.cache.push(_.clone(obj));
ids.push(obj.id);
@ -1869,6 +1961,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
_.each(value, function(command) {
var obj = {values: command};
obj['id'] = _.uniqueId(self.dataset.virtual_id_prefix);
obj.defaults = {};
self.dataset.to_create.push(obj);
self.dataset.cache.push(_.clone(obj));
ids.push(obj.id);
@ -1913,11 +2006,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
var view = this.viewmanager.views[this.viewmanager.active_view].controller;
if (this.viewmanager.active_view === "form") {
var res = $.when(view.do_save());
if (res === false) {
// ignore
} else if (res.isRejected()) {
throw "Save or create on one2many dataset is not supposed to fail.";
} else if (!res.isResolved()) {
if (!res.isResolved() && !res.isRejected()) {
throw "Asynchronous get_value() is not supported in form view.";
}
return res;
@ -1931,9 +2020,8 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
},
validate: function() {
this.invalid = false;
var self = this;
var view = self.viewmanager.views[self.viewmanager.active_view].controller;
if (self.viewmanager.active_view === "form") {
var view = this.viewmanager.views[this.viewmanager.active_view].controller;
if (this.viewmanager.active_view === "form") {
for (var f in view.fields) {
f = view.fields[f];
if (!f.is_valid()) {
@ -1970,6 +2058,7 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
} else {
var self = this;
var pop = new openerp.web.form.SelectCreatePopup(this);
pop.on_default_get.add(self.dataset.on_default_get);
pop.select_element(self.o2m.field.relation,{
initial_view: "form",
alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined,
@ -1979,7 +2068,8 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
self.o2m.dataset.on_change();
});
},
parent_view: self.o2m.view
parent_view: self.o2m.view,
form_view_options: {'not_interactible_on_create':true}
}, self.o2m.build_domain(), self.o2m.build_context());
pop.on_select_elements.add_last(function() {
self.o2m.reload_current_view();
@ -1995,7 +2085,8 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
parent_view: self.o2m.view,
read_function: function() {
return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments);
}
},
form_view_options: {'not_interactible_on_create':true}
});
pop.on_write.add(function(id, data) {
self.o2m.dataset.write(id, data, {}, function(r) {
@ -2006,10 +2097,10 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
});
openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
template: 'FieldMany2Many',
multi_selection: false,
init: function(view, node) {
this._super(view, node);
this.template = "FieldMany2Many";
this.list_id = _.uniqueId("many2many");
this.is_started = $.Deferred();
},
@ -2110,6 +2201,8 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
* - alternative_form_view
* - create_function (defaults to a naive saving behavior)
* - parent_view
* - form_view_options
* - list_view_options
*/
select_element: function(model, options, domain, context) {
var self = this;
@ -2131,6 +2224,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
this.dataset = new openerp.web.ReadOnlyDataSetSearch(this, this.model,
this.context);
this.dataset.parent_view = this.options.parent_view;
this.dataset.on_default_get.add(this.on_default_get);
if (this.options.initial_view == "search") {
this.setup_search_view();
} else { // "form"
@ -2173,7 +2267,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
});
self.view_list = new openerp.web.form.SelectCreateListView(self,
self.dataset, false,
{'deletable': false});
_.extend({'deletable': false}, self.options.list_view_options || {}));
self.view_list.popup = self;
self.view_list.appendTo($("#" + self.element_id + "_view_list")).pipe(function() {
self.view_list.do_show();
@ -2209,7 +2303,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
this.view_list.$element.hide();
}
this.dataset.index = null;
this.view_form = new openerp.web.FormView(this, this.dataset, false);
this.view_form = new openerp.web.FormView(this, this.dataset, false, self.options.form_view_options);
if (this.options.alternative_form_view) {
this.view_form.set_embedded_view(this.options.alternative_form_view);
}
@ -2253,7 +2347,8 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
this.on_select_elements(this.created_elements);
}
this.stop();
}
},
on_default_get: function(res) {}
});
openerp.web.form.SelectCreateListView = openerp.web.ListView.extend({
@ -2283,6 +2378,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp
* - auto_write (default true)
* - read_function
* - parent_view
* - form_view_options
*/
show_element: function(model, row_id, context, options) {
this.model = model;
@ -2318,7 +2414,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp
on_write_completed: function() {},
setup_form_view: function() {
var self = this;
this.view_form = new openerp.web.FormView(this, this.dataset, false);
this.view_form = new openerp.web.FormView(this, this.dataset, false, self.options.form_view_options);
if (this.options.alternative_form_view) {
this.view_form.set_embedded_view(this.options.alternative_form_view);
}
@ -2351,9 +2447,9 @@ openerp.web.form.FormOpenDataset = openerp.web.ReadOnlyDataSetSearch.extend({
});
openerp.web.form.FieldReference = openerp.web.form.Field.extend({
template: 'FieldReference',
init: function(view, node) {
this._super(view, node);
this.template = "FieldReference";
this.fields_view = {
fields: {
selection: {
@ -2488,10 +2584,7 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({
});
openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldBinaryFile";
},
template: 'FieldBinaryFile',
set_value: function(value) {
this._super.apply(this, arguments);
var show_value = (value != null && value !== false) ? value : '';
@ -2519,10 +2612,7 @@ openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({
});
openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldBinaryImage";
},
template: 'FieldBinaryImage',
start: function() {
this._super.apply(this, arguments);
this.$image = this.$element.find('img.oe-binary-image');
@ -2597,6 +2687,93 @@ openerp.web.form.FieldStatus = openerp.web.form.Field.extend({
}
});
openerp.web.form.WidgetNotebookReadonly = openerp.web.form.WidgetNotebook.extend({
template: 'WidgetNotebook.readonly'
});
openerp.web.form.FieldReadonly = openerp.web.form.Field.extend({
});
openerp.web.form.FieldCharReadonly = openerp.web.form.FieldReadonly.extend({
template: 'FieldChar.readonly',
set_value: function (value) {
this._super.apply(this, arguments);
var show_value = openerp.web.format_value(value, this, '');
this.$element.find('div').text(show_value);
return show_value;
}
});
openerp.web.form.FieldURIReadonly = openerp.web.form.FieldCharReadonly.extend({
template: 'FieldURI.readonly',
scheme: null,
set_value: function (value) {
var displayed = this._super.apply(this, arguments);
this.$element.find('a')
.attr('href', this.scheme + ':' + displayed)
.text(displayed);
}
});
openerp.web.form.FieldEmailReadonly = openerp.web.form.FieldURIReadonly.extend({
scheme: 'mailto'
});
openerp.web.form.FieldUrlReadonly = openerp.web.form.FieldURIReadonly.extend({
set_value: function (value) {
var s = /(\w+):(\.+)/.match(value);
if (!(s[0] === 'http' || s[0] === 'https')) { return; }
this.scheme = s[0];
this._super(s[1]);
}
});
openerp.web.form.FieldBooleanReadonly = openerp.web.form.FieldCharReadonly.extend({
set_value: function (value) {
this._super(value ? '\u2714' : '\u2718');
}
});
openerp.web.form.FieldSelectionReadonly = openerp.web.form.FieldReadonly.extend({
template: 'FieldChar.readonly',
init: function(view, node) {
// lifted straight from r/w version
var self = this;
this._super(view, node);
this.values = this.field.selection;
_.each(this.values, function(v, i) {
if (v[0] === false && v[1] === '') {
self.values.splice(i, 1);
}
});
this.values.unshift([false, '']);
},
set_value: function (value) {
value = value === null ? false : value;
value = value instanceof Array ? value[0] : value;
var option = _(this.values)
.detect(function (record) { return record[0] === value; });
this._super(value);
this.$element.find('div').text(option ? option[1] : this.values[0][1]);
}
});
openerp.web.form.FieldMany2OneReadonly = openerp.web.form.FieldCharReadonly.extend({
set_value: function (value) {
value = value || null;
this.invalid = false;
var self = this;
this.tmp_value = value;
self.update_dom();
self.on_value_changed();
var real_set_value = function(rval) {
self.$element.find('div').text(rval ? rval[1] : '');
};
if(typeof(value) === "number") {
var dataset = new openerp.web.DataSetStatic(
this, this.field.relation, self.build_context());
dataset.name_get([value], function(data) {
real_set_value(data[0]);
}).fail(function() {self.tmp_value = undefined;});
} else {
setTimeout(function() {real_set_value(value);}, 0);
}
}
});
/**
* Registry of form widgets, called by :js:`openerp.web.FormView`
*/
@ -2629,6 +2806,22 @@ openerp.web.form.widgets = new openerp.web.Registry({
'binary': 'openerp.web.form.FieldBinaryFile',
'statusbar': 'openerp.web.form.FieldStatus'
});
openerp.web.form.readonly = openerp.web.form.widgets.clone({
'notebook': 'openerp.web.form.WidgetNotebookReadonly',
'char': 'openerp.web.form.FieldCharReadonly',
'email': 'openerp.web.form.FieldEmailReadonly',
'url': 'openerp.web.form.FieldUrlReadonly',
'text': 'openerp.web.form.FieldCharReadonly',
'text_wiki' : 'openerp.web.form.FieldCharReadonly',
'date': 'openerp.web.form.FieldCharReadonly',
'datetime': 'openerp.web.form.FieldCharReadonly',
'selection' : 'openerp.web.form.FieldSelectionReadonly',
'many2one': 'openerp.web.form.FieldMany2OneReadonly',
'boolean': 'openerp.web.form.FieldBooleanReadonly',
'float': 'openerp.web.form.FieldCharReadonly',
'integer': 'openerp.web.form.FieldCharReadonly',
'float_time': 'openerp.web.form.FieldCharReadonly'
});
};

View File

@ -49,6 +49,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
this.model = dataset.model;
this.view_id = view_id;
this.previous_colspan = null;
this.colors = null;
this.columns = [];
@ -75,6 +76,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
}
self.compute_aggregates();
});
},
/**
* Retrieves the view's number of records per page (|| section)
@ -131,6 +133,31 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
this.$element.addClass('oe-listview');
return this.reload_view(null, null, true);
},
/**
* Returns the color for the provided record in the current view (from the
* ``@colors`` attribute)
*
* @param {Record} record record for the current row
* @returns {String} CSS color declaration
*/
color_for: function (record) {
if (!this.colors) { return ''; }
var context = _.extend({}, record.attributes, {
uid: this.session.uid,
current_date: new Date().toString('yyyy-MM-dd')
// TODO: time, datetime, relativedelta
});
for(var i=0, len=this.colors.length; i<len; ++i) {
var pair = this.colors[i],
color = pair[0],
expression = pair[1];
if (py.evaluate(expression, context)) {
return 'color: ' + color + ';';
}
// TODO: handle evaluation errors
}
return '';
},
/**
* Called after loading the list view's description, sets up such things
* as the view table's columns, renders the table itself and hooks up the
@ -159,6 +186,17 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
this.fields_view = data;
this.name = "" + this.fields_view.arch.attrs.string;
if (this.fields_view.arch.attrs.colors) {
this.colors = _(this.fields_view.arch.attrs.colors.split(';')).chain()
.compact()
.map(function(color_pair) {
var pair = color_pair.split(':'),
color = pair[0],
expr = pair[1];
return [color, py.parse(py.tokenize(expr)), expr];
}).value();
}
this.setup_columns(this.fields_view.fields, grouped);
this.$element.html(QWeb.render("ListView", this));
@ -415,8 +453,8 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
*/
do_search: function (domains, contexts, groupbys) {
return this.rpc('/web/session/eval_domain_and_context', {
domains: [this.dataset.get_domain()].concat(domains),
contexts: [this.dataset.get_context()].concat(contexts),
domains: _([this.dataset.get_domain()].concat(domains)).compact(),
contexts: _([this.dataset.get_context()].concat(contexts)).compact(),
group_by_seq: groupbys
}, $.proxy(this, 'do_actual_search'));
},
@ -541,6 +579,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
if (_.isEmpty(records)) {
records = this.groups.get_records();
}
records = _(records).compact();
var count = 0, sums = {};
_(columns).each(function (column) {
@ -1235,6 +1274,9 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L
},
get_records: function () {
if (_(this.children).isEmpty()) {
if (!this.datagroup.length) {
return;
}
return {
count: this.datagroup.length,
values: this.datagroup.aggregates
@ -1351,10 +1393,10 @@ var Record = openerp.web.Class.extend(/** @lends Record# */{
* @returns {Object} record displayable in a form view
*/
toForm: function () {
var form_data = {};
_(this.attributes).each(function (value, key) {
form_data[key] = {value: value};
});
var form_data = {}, attrs = this.attributes;
for(var k in attrs) {
form_data[k] = {value: attrs[k]};
}
return {data: form_data};
}

View File

@ -182,9 +182,11 @@ openerp.web.list_editable = function (openerp) {
self.edition_id = record_id;
self.edition_form = _.extend(new openerp.web.ListEditableFormView(self, self.dataset, false), {
form_template: 'ListView.row.form',
registry: openerp.web.list.form.widgets
registry: openerp.web.list.form.widgets,
$element: $new_row
});
self.edition_form.appendTo($("#" + $new_row.attr('id')));
// HA HA
self.edition_form.appendTo();
$.when(self.edition_form.on_loaded(self.get_form_fields_view())).then(function () {
// put in $.when just in case FormView.on_loaded becomes asynchronous
$new_row.find('td')
@ -335,6 +337,9 @@ openerp.web.list_editable = function (openerp) {
});
openerp.web.ListEditableFormView = openerp.web.FormView.extend({
init_view: function() {}
init_view: function() {},
_render_and_insert: function () {
return this.start();
}
});
};

View File

@ -86,7 +86,7 @@ db.web.ActionManager = db.web.Widget.extend({
console.log("Action manager can't handle action of type " + action.type, action);
return;
}
this[type](action, on_close);
return this[type](action, on_close);
},
ir_actions_act_window: function (action, on_close) {
if (action.target === 'new') {
@ -163,7 +163,7 @@ db.web.ViewManager = db.web.Widget.extend(/** @lends db.web.ViewManager# */{
*/
init: function(parent, dataset, views) {
this._super(parent);
this.model = dataset.model;
this.model = dataset ? dataset.model : undefined;
this.dataset = dataset;
this.searchview = null;
this.active_view = null;
@ -184,7 +184,6 @@ db.web.ViewManager = db.web.Widget.extend(/** @lends db.web.ViewManager# */{
start: function() {
this._super();
var self = this;
this.dataset.start();
this.$element.find('.oe_vm_switch button').click(function() {
self.on_mode_switch($(this).data('view-type'));
});
@ -332,6 +331,7 @@ db.web.ViewManagerAction = db.web.ViewManager.extend(/** @lends oepnerp.web.View
// dataset initialization will take the session from ``this``, so if we
// do not have it yet (and we don't, because we've not called our own
// ``_super()``) rpc requests will blow up.
this._super(parent, null, action.views);
this.session = parent.session;
this.action = action;
var dataset = new db.web.DataSetSearch(this, action.res_model, action.context, action.domain);
@ -339,7 +339,7 @@ db.web.ViewManagerAction = db.web.ViewManager.extend(/** @lends oepnerp.web.View
dataset.ids.push(action.res_id);
dataset.index = 0;
}
this._super(parent, dataset, action.views);
this.dataset = dataset;
this.flags = this.action.flags || {};
if (action.res_model == 'board.board' && action.views.length == 1 && action.views) {
// Not elegant but allows to avoid form chrome (pager, save/new
@ -470,7 +470,7 @@ db.web.ViewManagerAction = db.web.ViewManager.extend(/** @lends oepnerp.web.View
* Intercept do_action resolution from children views
*/
on_action_executed: function () {
new db.web.DataSet(this, 'res.log')
return new db.web.DataSet(this, 'res.log')
.call('get', [], this.do_display_log);
},
/**
@ -770,28 +770,31 @@ db.web.View = db.web.Widget.extend(/** @lends db.web.View# */{
var self = this;
var result_handler = function () {
if (on_closed) { on_closed.apply(null, arguments); }
self.widget_parent.on_action_executed.apply(null, arguments);
return self.widget_parent.on_action_executed.apply(null, arguments);
};
var handler = function (r) {
var action = r.result;
if (action && action.constructor == Object) {
action.context = action.context || {};
_.extend(action.context, {
active_id: record_id || false,
active_ids: [record_id || false],
active_model: dataset.model
return self.rpc('/web/session/eval_domain_and_context', {
contexts: [dataset.get_context(), action.context || {}, {
active_id: record_id || false,
active_ids: [record_id || false],
active_model: dataset.model
}],
domains: []
}).pipe(function (results) {
action.context = results.context
return self.do_action(action, result_handler);
});
action.context = new db.web.CompoundContext(dataset.get_context(), action.context);
self.do_action(action, result_handler);
} else {
result_handler();
return result_handler();
}
};
var context = new db.web.CompoundContext(dataset.get_context(), action_data.context || {});
if (action_data.special) {
handler({result: {"type":"ir.actions.act_window_close"}});
return handler({result: {"type":"ir.actions.act_window_close"}});
} else if (action_data.type=="object") {
return dataset.call_button(action_data.name, [[record_id], context], handler);
} else if (action_data.type=="action") {
@ -863,6 +866,8 @@ db.web.View = db.web.Widget.extend(/** @lends db.web.View# */{
console.log('Todo');
},
on_sidebar_import: function() {
var import_view = new db.web.DataImport(this, this.dataset);
import_view.start();
},
on_sidebar_export: function() {
var export_view = new db.web.DataExport(this, this.dataset);

View File

@ -38,11 +38,17 @@
</td>
</tr>
<tr>
<td valign="top" id="oe_secondary_menu" class="secondary_menu">
</td>
<td valign="top" class="oe-application-container">
<div id="oe_app" class="oe-application">
</div>
<td colspan="2" valign="top" height="100%">
<table cellspacing="0" cellpadding="0" border="0" height="100%" width="100%">
<tr>
<td valign="top" id="oe_secondary_menu" class="secondary_menu">
</td>
<td valign="top" class="oe-application-container">
<div id="oe_app" class="oe-application">
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
@ -236,7 +242,23 @@
</table>
</form>
</t>
<t t-name="Login_dblist">
<select name="db">
<t t-foreach="db_list" t-as="db">
<t t-if="selected_db === db">
<option t-att-value="db" selected="true">
<t t-esc="db"/></option>
</t>
<t t-if="selected_db !== db">
<option t-att-value="db"><t t-esc="db"/></option>
</t>
</t>
</select>
</t>
<t t-name="Login">
<div>
<form class="oe_forms">
<fieldset>
<legend style="">
@ -247,33 +269,18 @@
<tr>
<td><label for="db">Database:</label></td>
<td>
<t t-if="!db_list">
<input type="text" name="db" t-att-value="selected_db || ''" autofocus="true"/>
</t>
<t t-if="db_list">
<select name="db">
<t t-foreach="db_list" t-as="db">
<t t-if="selected_db === db">
<option t-att-value="db" selected="true">
<t t-esc="db"/></option>
</t>
<t t-if="selected_db !== db">
<option t-att-value="db"><t t-esc="db"/></option>
</t>
</t>
</select>
</t>
<input type="text" name="db" t-att-value="widget.selected_db || ''" autofocus="true"/>
</td>
</tr>
<tr>
<td><label for="login">User:</label></td>
<td><input type="text" name="login"
t-att-value="selected_login || ''" autofocus="true"/></td>
t-att-value="widget.selected_login || ''" autofocus="true"/></td>
</tr>
<tr>
<td><label for="password">Password:</label></td>
<td><input type="password" name="password"
t-att-value="selected_password || ''"/></td>
t-att-value="widget.selected_password || ''"/></td>
</tr>
<tr>
<td></td>
@ -324,6 +331,7 @@
</table>
</div>
</div>
</t>
<t t-name="Header">
<div>
@ -342,22 +350,12 @@
<li>
<a t-att-href="'/' + widget.qs" title="Home" class="home"><img src="/web/static/src/img/header-home.png" width="16" height="16" border="0"/></a>
</li>
<!--
<li>
<a href="#requests" title="Requests" class="requests"><img src="/web/static/src/img/header-requests.png" width="16" height="16" border="0"/><small>1</small></a>
</li>
-->
<li class="preferences">
<a href="#preferences" title="Preferences" class="preferences"><img src="/web/static/src/img/header-preferences.png" width="16" height="16" border="0"/></a>
<a href="javascript:void(0)" title="Preferences" class="preferences"><img src="/web/static/src/img/header-preferences.png" width="16" height="16" border="0"/></a>
</li>
<li>
<a href="#about" title="About" class="about"><img src="/web/static/src/img/header-about.png" width="16" height="16" border="0"/></a>
<a href="javascript:void(0)" title="About" class="about"><img src="/web/static/src/img/header-about.png" width="16" height="16" border="0"/></a>
</li>
<!--
<li>
<a href="http://doc.openerp.com/v6.0/book?version=$version" title="Help" target="_blank" class="help"><img src="/web/static/src/img/header-help.png" width="16" height="16" border="0"/></a>
</li>
-->
</ul>
<div class="block">
<a href="#logout" class="logout">LOGOUT</a>
@ -374,45 +372,39 @@
</ul>
<t t-name="Menu">
<table align="center">
<tr>
<t t-foreach="data.children" t-as="menu">
<td>
<a href="#" t-att-data-menu="menu.id">
<t t-esc="menu.name"/>
</a>
</td>
</t>
</tr>
<tr>
<td t-foreach="widget.data.data.children" t-as="menu">
<a href="#" t-att-data-menu="menu.id">
<t t-esc="menu.name"/>
</a>
</td>
</tr>
</table>
</t>
<t t-name="Menu.secondary">
<div style="display: none" class="menu_accordion" t-att-data-menu-parent="menu.id">
<div t-attf-class="oe_toggle_secondary_menu">
<span class="oe_menu_fold" title="Fold menu">&amp;laquo;</span>
<span class="oe_menu_unfold" title="Unfold menu">&amp;raquo;</span>
</div>
<div t-foreach="widget.data.data.children" t-as="menu" style="display: none" class="oe_secondary_menu" t-att-data-menu-parent="menu.id">
<t t-foreach="menu.children" t-as="menu">
<t t-set="header">h3</t>
<t t-set="classname">oe_secondary_menu_item</t>
<t t-set="level" t-value="0"/>
<t t-call="Menu.secondary.children"/>
</t>
</div>
</t>
<t t-name="Menu.secondary.children">
&lt;<t t-esc="header"/>&gt;
<a href="#" t-attf-id="menu_#{menu.id}">
<span><t t-esc="menu.name"/></span>
</a>
&lt;/<t t-esc="header"/>&gt;
<div class="menu_content">
<t t-set="level" t-value="level + 1"/>
<a href="#" t-att-id="menu.children.length ? 'menu_' + menu.id : undefined"
t-att-class="classname + (menu.children.length ? ' submenu' : ' leaf') + (menu_first and level == 1 ? ' opened' : '')"
t-att-data-menu="menu.children.length ? undefined : menu.id">
<span t-attf-style="padding-left: #{(level - 2) * 20}px"> <t t-esc="menu.name"/></span>
</a>
<div t-attf-class="oe_secondary_submenu" t-if="menu.children.length" t-att-style="menu_first and level == 1 ? undefined : 'display: none'">
<t t-foreach="menu.children" t-as="menu">
<t t-if="!menu.children.length">
<a href="#" class="leaf" t-att-data-menu="menu.id">
<span><t t-esc="menu.name"/></span>
</a>
</t>
<t t-if="menu.children.length">
<div class="submenu_accordion">
<t t-call="Menu.secondary.children">
<t t-set="header">h4</t>
</t>
</div>
</t>
<t t-set="classname">oe_secondary_submenu_item</t>
<t t-call="Menu.secondary.children"/>
</t>
</div>
</t>
@ -624,7 +616,8 @@
</t>
</t>
<tr t-name="ListView.row" t-att-class="row_parity"
t-att-data-id="record.get('id')">
t-att-data-id="record.get('id')"
t-att-style="view.color_for(record)">
<t t-foreach="columns" t-as="column">
<td t-if="column.meta">
@ -658,7 +651,7 @@
<t t-raw="frame.render()"/>
</t>
<t t-name="FormView">
<div class="oe_form_header" t-att-id="view.element_id + '_header'">
<div class="oe_form_header">
<div class="oe_form_buttons" t-if="view.options.action_buttons !== false">
<!--<button type="button" class="oe_form_button_save">
<span class="oe_form_on_update">Save</span>
@ -671,6 +664,7 @@
<!--<button type="button" class="oe_form_button_cancel">Cancel</button>-->
<button type="button" class="oe_form_button_new">New</button>
<button type="button" class="oe_form_button_duplicate oe_form_on_update">Duplicate</button>
<button type="button" class="oe_form_button_toggle">Readonly/Editable</button>
</div>
<div class="oe_form_pager" t-if="view.options.pager !== false">
<button type="button" data-pager-action="first">First</button>
@ -730,8 +724,7 @@
t-att-width="td.width"
t-att-nowrap="td.nowrap or td.is_field_m2o? 'true' : undefined"
t-att-valign="td.table ? 'top' : undefined"
t-att-id="td.element_id"
t-attf-class="oe_form_frame_cell #{td.classname}"
t-attf-class="oe_form_frame_cell #{td.classname} #{td.element_class}"
>
<t t-raw="td.render()"/>
</td>
@ -741,8 +734,8 @@
</t>
<t t-name="WidgetNotebook">
<ul>
<li t-foreach="widget.pages" t-as="page" t-att-id="page.element_tab_id">
<a t-att-href="'#' + page.element_id">
<li t-foreach="widget.pages" t-as="page">
<a href="#">
<t t-esc="page.string"/>
</a>
</li>
@ -752,17 +745,23 @@
</t>
</t>
<t t-name="WidgetNotebookPage">
<div t-att-id="widget.element_id">
<div>
<t t-call="WidgetFrame"/>
</div>
</t>
<t t-name="WidgetNotebook.readonly">
<t t-foreach="widget.pages" t-as="page">
<h3><t t-esc="page.string"/></h3>
<t t-raw="page.render()"/>
</t>
</t>
<t t-name="WidgetSeparator">
<div t-if="widget.orientation !== 'vertical'" t-att-class="'separator ' + widget.orientation">
<t t-esc="widget.string"/>
</div>
</t>
<t t-name="WidgetLabel">
<label t-att-for="widget.element_id + '_field'"
<label t-att-for="widget.element_id"
t-att-class="'oe_label' + (widget.help ? '_help' : '')"
t-att-title="widget.help">
<t t-esc="widget.string"/>
@ -776,12 +775,22 @@
<t t-name="FieldChar">
<input type="text" size="1"
t-att-name="widget.name"
t-att-id="widget.element_id + '_field'"
t-att-class="'field_' + widget.type"
t-att-id="widget.element_id"
t-attf-class="field_#{widget.type} #{widget.element_class}"
t-attf-style="width: #{widget.field.translate ? '99' : '100'}%"
/>
<img class="oe_field_translate" t-if="widget.field.translate" src="/web/static/src/img/icons/terp-translate.png" width="16" height="16" border="0"/>
</t>
<t t-name="FieldChar.readonly">
<div
t-att-id="widget.element_id"
t-attf-class="field_#{widget.type} #{widget.element_class}"
t-attf-style="width: #{widget.field.translate ? '99' : '100'}%">
</div>
</t>
<t t-name="FieldURI.readonly">
<a href="#">#</a>
</t>
<t t-name="FieldEmail">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<tr>
@ -813,26 +822,28 @@
<t t-name="FieldText">
<textarea rows="6"
t-att-name="widget.name"
t-att-id="widget.element_id + '_field'"
t-att-class="'field_' + widget.type"
t-att-id="widget.element_id"
t-attf-class="field_#{widget.type} #{widget.element_class}"
t-attf-style="width: #{widget.field.translate ? '99' : '100'}%"
></textarea>
<img class="oe_field_translate" t-if="widget.field.translate" src="/web/static/src/img/icons/terp-translate.png" width="16" height="16" border="0"/>
</t>
<t t-name="FieldDate">
<t t-call="FieldChar"/>
<img class="oe_input_icon oe_datepicker_trigger" src="/web/static/src/img/ui/field_calendar.png"
title="Select date" width="16" height="16" border="0"/>
<div class="oe_datepicker ui-widget-content ui-corner-all" style="display: none; position: absolute; z-index: 1;">
<div class="oe_datepicker_container"/>
<button type="button" class="oe_datepicker_close ui-state-default ui-priority-primary ui-corner-all" style="float: right;">Done</button>
<t t-name="web.datetimepicker">
<div class="oe_datepicker_root">
<input type="text" size="1" style="width: 100%"/>
<img class="oe_input_icon oe_datepicker_trigger" src="/web/static/src/img/ui/field_calendar.png"
title="Select date" width="16" height="16" border="0"/>
<div class="oe_datepicker ui-widget-content ui-corner-all" style="display: none; position: absolute; z-index: 1;">
<div class="oe_datepicker_container"/>
<button type="button" class="oe_datepicker_close ui-state-default ui-priority-primary ui-corner-all" style="float: right;">Done</button>
</div>
</div>
</t>
<t t-name="FieldSelection">
<select
t-att-name="widget.name"
t-att-id="widget.element_id + '_field'"
t-att-class="'field_' + widget.type"
t-attf-class="field_#{widget.type} #{widget.element_class}"
style="width: 100%">
<t t-foreach="widget.values" t-as="option">
<option><t t-esc="option[1]"/></option>
@ -840,9 +851,9 @@
</select>
</t>
<t t-name="FieldMany2One">
<div t-att-id="widget.element_id" class="oe-m2o">
<input t-att-id="widget.element_id + '_input'" type="text" size="1" style="width: 100%;"/>
<span class="oe-m2o-drop-down-button" t-att-id="widget.element_id + '_drop_down'">
<div t-att-class="widget.element_class" class="oe-m2o">
<input type="text" size="1" style="width: 100%;"/>
<span class="oe-m2o-drop-down-button">
<img src="/web/static/src/img/down-arrow.png" /></span>
<span class="oe-m2o-cm-button" t-att-id="widget.name + '_open'">
<img src="/web/static/src/img/icons/gtk-index.png"/></span>
@ -865,8 +876,6 @@
</ul>
</t>
<t t-name="FieldOne2Many">
<div t-att-id="widget.element_id">
</div>
</t>
<t t-name="FieldMany2Many">
<div t-att-id="widget.list_id"></div>
@ -874,10 +883,10 @@
<t t-name="FieldReference">
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="oe_frame oe_forms">
<tr>
<td t-att-id="widget.selection.element_id" class="oe_form_frame_cell oe_form_selection">
<td t-attf-class="oe_form_frame_cell oe_form_selection #{widget.selection.element_class}">
<t t-raw="widget.selection.render()"/>
</td>
<td t-att-id="widget.m2o.element_id" class="oe_form_frame_cell oe_form_many2one" nowrap="true">
<td class="oe_form_frame_cell oe_form_many2one #{widget.selection.element_class}" nowrap="true">
<t t-raw="widget.m2o.render()"/>
</td>
</tr>
@ -887,7 +896,7 @@
<input type="checkbox"
t-att-name="widget.name"
t-att-id="widget.element_id + '_field'"
t-att-class="'field_' + widget.type"/>
t-attf-class="field_#{widget.type} #{widget.element_class}"/>
</t>
<t t-name="FieldProgressBar">
<div t-opentag="true" class="oe-progressbar">
@ -902,7 +911,7 @@
t-att-border="widget.readonly ? 0 : 1"
t-att-id="widget.element_id + '_field'"
t-att-name="widget.name"
t-att-class="'field_' + widget.type"
t-attf-class="field_#{widget.type} #{widget.element_class}"
t-att-width="widget.node.attrs.img_width || widget.node.attrs.width"
t-att-height="widget.node.attrs.img_height || widget.node.attrs.height"
/>
@ -950,7 +959,7 @@
<input type="text" size="1"
t-att-name="widget.name"
t-att-id="widget.element_id + '_field'"
t-att-class="'field_' + widget.type" style="width: 100%"
t-attf-class="field_#{widget.type} #{widget.element_class}" style="width: 100%"
/>
</td>
<td class="oe-binary" nowrap="true">
@ -995,7 +1004,7 @@
</t>
<t t-name="WidgetButton">
<button type="button"
t-att-id="widget.element_id + '_button'"
t-attf-class="#{widget.element_class}"
t-att-title="widget.help"
style="width: 100%" class="button">
<img t-if="widget.node.attrs.icon" t-att-src="'/web/static/src/img/icons/' + widget.node.attrs.icon + '.png'" width="16" height="16"/>
@ -1070,6 +1079,18 @@
<t t-if="filters.length" t-raw="filters.render(defaults)"/>
</div>
</t>
<t t-name="SearchView.date">
<label t-att-class="'oe_label' + (attrs.help ? '_help' : '')"
t-att-title="attrs.help"
t-att-for="element_id">
<t t-esc="attrs.string || attrs.name"/>
<span t-if="attrs.help">?</span>
</label>
<div style="white-space: nowrap;">
<span t-att-id="element_id"></span>
<t t-if="filters.length" t-raw="filters.render(defaults)"/>
</div>
</t>
<t t-name="SearchView.field.selection">
<label t-att-title="attrs.help"
t-att-class="'oe_label' + (attrs.help ? '_help' : '')"
@ -1161,11 +1182,8 @@
<t t-name="SearchView.extended_search.proposition.char">
<input t-att-id="element_id" class="field_char"/>
</t>
<t t-name="SearchView.extended_search.proposition.datetime">
<input t-att-id="element_id" class="field_datetime"/>
</t>
<t t-name="SearchView.extended_search.proposition.date">
<input t-att-id="element_id" class="field_date"/>
<t t-name="SearchView.extended_search.proposition.empty">
<span t-att-id="element_id"></span>
</t>
<t t-name="SearchView.extended_search.proposition.integer">
<input type="number" t-att-id="element_id" class="field_integer" step="1"/>
@ -1437,6 +1455,79 @@
</table>
</form>
</t>
<t t-name="ImportView">
<a id="importview" href="javascript: void(0)" style="text-decoration: none;color: #3D3D3D;">Import</a>
</t>
<t t-name="ImportDataView">
<form name="import_data" id="import_data" action="" method="post" enctype="multipart/form-data">
<input type="hidden" name="session_id" t-att-value="session.session_id"/>
<h2 class="separator horizontal">1. Import a .CSV file</h2>
<p>Select a .CSV file to import. If you need a sample of file to import,
you should use the export tool with the "Import Compatible" option.
</p>
<p>
<label for="csvfile">CSV File:</label>
<input type="file" id="csvfile" size="50" name="csvfile"/>
</p>
<h2 class="separator horizontal">2. Check your file format</h2>
<div id="result"></div>
<fieldset>
<legend style="cursor:pointer;">Import Options</legend>
<table style="display:none">
<tr>
<td colspan="4">
<label for="file_has_headers">Does your file have titles?</label>
<input type="checkbox" checked="checked"
id="file_has_headers"/>
</td>
</tr>
<tr>
<td><label for="csv_separator">Separator:</label></td>
<td><input type="text" name="csvsep" id="csv_separator" value=","/></td>
<td><label for="csv_delimiter">Delimiter:</label></td>
<td><input type="text" name="csvdel" id="csv_delimiter" value='"'/></td>
</tr>
<tr>
<td><label for="csv_encoding">Encoding:</label></td>
<td>
<select name="csvcode" id="csv_encoding">
<option value="utf-8">UTF-8</option>
<option value="latin1">Latin 1</option>
</select>
</td>
<td><label for="csv_skip" title="For use if CSV files have titles on multiple lines, skips more than a single line during import">
Lines to skip<sup>?</sup>:</label></td>
<td><input type="number" id="csv_skip" value="0" min="0"/></td>
</tr>
</table>
</fieldset>
</form>
</t>
<table t-name="ImportView.result"
class="oe_import_grid" width="100%" style="margin: 5px 0;">
<tr t-if="headers" class="oe_import_grid-header">
<td t-foreach="headers" t-as="header" class="oe_import_grid-cell">
<t t-esc="header"/></td>
</tr>
<tr>
<td t-foreach="records[0]" t-as="column">
<input class="sel_fields"/>
</td>
</tr>
<tr t-foreach="records" t-as="record" class="oe_import_grid-row">
<td t-foreach="record" t-as="cell" class="oe_import_grid-cell">
<t t-esc="cell"/></td>
</tr>
</table>
<t t-name="ImportView.error">
<p style="white-space:pre-line;">The import failed due to:<t t-esc="error.message"/></p>
<t t-if="error.preview">
<p>Here is a preview of the file we could not import:</p>
<pre><t t-esc="error.preview"/></pre>
</t>
</t>
<t t-name="About-Page">
<div>
<h1>OpenERP Web</h1>

View File

@ -35,6 +35,7 @@ openerp.web_calendar.CalendarView = openerp.web.View.extend({
},
stop: function() {
scheduler.clearAll();
this._super();
},
on_loaded: function(data) {
this.fields_view = data;

View File

@ -301,7 +301,7 @@ openerp.web_dashboard.ConfigOverview = openerp.web.View.extend({
.then(this.on_records_loaded);
},
on_records_loaded: function (read_response, progress_response) {
var records = read_response[0].records,
var records = read_response,
progress = progress_response[0];
var grouped_todos = _(records).chain()

View File

@ -94,8 +94,10 @@ openerp.web_default_home = function (openerp) {
})
},
install_module: function (module_name) {
var self = this;
var Modules = new openerp.web.DataSetSearch(
this, 'ir.module.module', null, [['name', '=', module_name], ['state', '=', 'uninstalled']]);
this, 'ir.module.module', null,
[['name', '=', module_name], ['state', '=', 'uninstalled']]);
var Upgrade = new openerp.web.DataSet(this, 'base.module.upgrade');
$.blockUI({message:'<img src="/web/static/src/img/throbber2.gif">'});
@ -105,13 +107,21 @@ openerp.web_default_home = function (openerp) {
[_.pluck(records, 'id'), 'to install', ['uninstalled']],
function () {
Upgrade.call('upgrade_module', [[]], function () {
$.unblockUI();
// TODO: less brutal reloading
window.location.reload(true);
self.run_configuration_wizards();
});
}
)
});
},
run_configuration_wizards: function () {
var self = this;
new openerp.web.DataSet(this, 'res.config').call('start', [[]], function (action) {
$.unblockUI();
self.do_action(action, function () {
// TODO: less brutal reloading
window.location.reload(true);
});
});
}
});
};

View File

@ -1,2 +0,0 @@
#!/usr/bin/python
import controllers

View File

@ -3,7 +3,7 @@
"version": "2.0",
"depends": ['web'],
"js": [
"static/lib/dhtmlxGraph/codebase/dhtmlxchart.js",
"static/lib/dhtmlxGraph/codebase/dhtmlxchart_debug.js",
"static/src/js/graph.js"],
"css": ["static/lib/dhtmlxGraph/codebase/dhtmlxchart.css"],
"active": True

View File

@ -19,11 +19,14 @@ openerp.web_graph.GraphView = openerp.web.View.extend({
init: function(parent, dataset, view_id) {
this._super(parent);
this.view_manager = parent;
this.dataset = dataset;
this.dataset_index = 0;
this.model = this.dataset.model;
this.view_id = view_id;
this.first_field = null;
this.abscissa = null;
this.ordinate = null;
this.columns = [];
this.group_field = null;
},
do_show: function () {
// TODO: re-trigger search
@ -33,348 +36,265 @@ openerp.web_graph.GraphView = openerp.web.View.extend({
this.$element.hide();
},
start: function() {
var self = this;
this._super();
return this.rpc("/web_graph/graphview/load", {"model": this.model, "view_id": this.view_id}, this.on_loaded);
return $.when(
this.dataset.call('fields_get', []),
this.rpc('/web/view/load', {
model: this.dataset.model,
view_id: this.view_id,
view_type: 'graph'
})).then(function (fields_result, view_result) {
self.fields = fields_result[0];
self.fields_view = view_result[0];
self.on_loaded();
});
},
on_loaded: function(data) {
this.all_fields = data.all_fields;
this.fields_view = data.fields_view;
this.name = this.fields_view.name || this.fields_view.arch.attrs.string;
this.view_id = this.fields_view.view_id;
/**
* Returns all object fields involved in the graph view
*/
list_fields: function () {
var fs = [this.abscissa];
fs.push.apply(fs, _(this.columns).pluck('name'));
if (this.group_field) {
fs.push(this.group_field);
}
return fs;
},
on_loaded: function() {
this.chart = this.fields_view.arch.attrs.type || 'pie';
this.fields = this.fields_view.fields;
this.chart_info_fields = [];
this.operator_field = '';
this.operator_field_one = '';
this.operator = [];
this.group_field = [];
this.orientation = this.fields_view.arch.attrs.orientation || '';
this.elem_id = this.$element[0]['id'];
this.orientation = this.fields_view.arch.attrs.orientation || 'vertical';
_.each(this.fields_view.arch.children, function (field) {
if (field.attrs.operator) {
this.operator.push(field.attrs.name);
}
else if (field.attrs.group) {
this.group_field.push(field.attrs.name);
}
else {
this.chart_info_fields.push(field.attrs.name);
}
}, this);
this.operator_field = this.operator[0];
if(this.operator.length > 1){
this.operator_field_one = this.operator[1];
}
if(this.operator == ''){
this.operator_field = this.chart_info_fields[1];
}
this.chart_info = this.chart_info_fields[0];
this.x_title = this.fields[this.chart_info_fields[0]]['string'];
this.y_title = this.fields[this.operator_field]['string'];
this.load_chart();
},
load_chart: function(data) {
var self = this;
var domain = false;
if(data){
this.x_title = this.all_fields[this.chart_info_fields]['string'];
this.y_title = this.all_fields[this.operator_field]['string'];
self.schedule_chart(data);
}else{
if(! _.isEmpty(this.view_manager.dataset.domain)){
domain = this.view_manager.dataset.domain;
}else if(! _.isEmpty(this.view_manager.action.domain)){
domain = this.view_manager.action.domain;
}
this.dataset.domain = domain;
this.dataset.context = this.view_manager.dataset.context;
this.dataset.read_slice(_(this.fields).keys(),{}, function(res) {
self.schedule_chart(res);
});
}
},
schedule_chart: function(results) {
this.$element.html(QWeb.render("GraphView", {"fields_view": this.fields_view, "chart": this.chart,'elem_id': this.elem_id}));
_.each(results, function (result) {
_.each(result, function (field_value, field_name) {
if (typeof field_value == 'object') {
result[field_name] = field_value[field_value.length - 1];
}
if (typeof field_value == 'string') {
var choices = this.all_fields[field_name]['selection'];
_.each(choices, function (choice) {
if (field_value == choice[0]) {
result[field_name] = choice;
}
});
}
}, this);
}, this);
var graph_data = {};
_.each(results, function (result) {
var group_key = [];
if(this.group_field.length){
_.each(this.group_field, function (res) {
result[res] = (typeof result[res] == 'object') ? result[res][1] : result[res];
group_key.push(result[res]);
});
}else{
group_key.push(result[this.group_field]);
}
var column_key = result[this.chart_info_fields] + "_" + group_key;
var column_descriptor = {};
if (graph_data[column_key] == undefined) {
column_descriptor[this.operator_field] = result[this.operator_field];
if (this.operator.length > 1) {
column_descriptor[this.operator_field_one] = result[this.operator_field_one];
}
column_descriptor[this.chart_info_fields] = result[this.chart_info_fields];
if(this.group_field.length){
_.each(this.group_field, function (res) {
column_descriptor[res] = (typeof result[res] == 'object') ? result[res][1] : result[res];
});
}
var attrs = field.attrs;
if (attrs.group) {
this.group_field = attrs.name;
} else if(!this.abscissa) {
this.first_field = this.abscissa = attrs.name;
} else {
column_descriptor = graph_data[column_key];
column_descriptor[this.operator_field] += result[this.operator_field];
if (this.operator.length > 1) {
column_descriptor[this.operator_field_one] += result[this.operator_field_one];
}
this.columns.push({
name: attrs.name,
operator: attrs.operator || '+'
});
}
graph_data[column_key] = column_descriptor;
}, this);
this.ordinate = this.columns[0].name;
this.dataset.read_slice(
this.list_fields(), {}, $.proxy(this, 'schedule_chart'));
},
schedule_chart: function(results) {
var self = this;
this.$element.html(QWeb.render("GraphView", {
"fields_view": this.fields_view,
"chart": this.chart,
'element_id': this.element_id
}));
var fields = _(this.columns).pluck('name').concat([this.abscissa]);
if (this.group_field) { fields.push(this.group_field); }
// transform search result into usable records (convert from OpenERP
// value shapes to usable atomic types
var records = _(results).map(function (result) {
var point = {};
_(result).each(function (value, field) {
if (!_(fields).contains(field)) { return; }
if (value === false) { point[field] = false; return; }
switch (self.fields[field].type) {
case 'selection':
point[field] = _(self.fields[field].selection).detect(function (choice) {
return choice[0] === value;
})[1];
break;
case 'many2one':
point[field] = value[1];
break;
case 'integer': case 'float': case 'char':
case 'date': case 'datetime':
point[field] = value;
break;
default:
throw new Error(
"Unknown field type " + self.fields[field].type
+ "for field " + field + " (" + value + ")");
}
});
return point;
});
// aggregate data, because dhtmlx is crap. Aggregate on abscissa field,
// leave split on group field => max m*n records where m is the # of
// values for the abscissa and n is the # of values for the group field
var graph_data = [];
_(records).each(function (record) {
var abscissa = record[self.abscissa],
group = record[self.group_field];
var r = _(graph_data).detect(function (potential) {
return potential[self.abscissa] === abscissa
&& (!self.group_field
|| potential[self.group_field] === group);
});
var datapoint = r || {};
datapoint[self.abscissa] = abscissa;
if (self.group_field) { datapoint[self.group_field] = group; }
_(self.columns).each(function (column) {
var val = record[column.name],
aggregate = datapoint[column.name];
switch(column.operator) {
case '+':
datapoint[column.name] = (aggregate || 0) + val;
return;
case '*':
datapoint[column.name] = (aggregate || 1) * val;
return;
case 'min':
datapoint[column.name] = (aggregate || Infinity) > val
? val
: aggregate;
return;
case 'max':
datapoint[column.name] = (aggregate || -Infinity) < val
? val
: aggregate;
}
});
if (!r) { graph_data.push(datapoint); }
});
graph_data = _(graph_data).sortBy(function (point) {
return point[self.abscissa] + '[[--]]' + point[self.group_field];
});
if (this.chart == 'bar') {
return this.schedule_bar(_.values(graph_data));
return this.schedule_bar(graph_data);
} else if (this.chart == "pie") {
return this.schedule_pie(_.values(graph_data));
return this.schedule_pie(graph_data);
}
},
schedule_bar: function(results) {
var self = this;
var view_chart = '';
var group_list = [];
var legend_list = [];
var newkey = '', newkey_one;
var string_legend = '';
if((self.group_field.length) && (this.operator.length <= 1)){
view_chart = self.orientation == 'horizontal'? 'stackedBarH' : 'stackedBar';
}else{
view_chart = self.orientation == 'horizontal'? 'barH' : 'bar';
}
_.each(results, function (result) {
if ((self.group_field.length) && (this.operator.length <= 1)) {
var legend_key = '';
_.each(self.group_field, function (res) {
result[res] = (typeof result[res] == 'object') ? result[res][1] : result[res];
legend_key += result[res];
});
newkey = legend_key.replace(/\s+/g,'_').replace(/[^a-zA-Z 0-9]+/g,'_');
string_legend = legend_key;
} else {
newkey = string_legend = "val";
}
if (_.contains(group_list, newkey) && _.contains(legend_list, string_legend)) {
return;
}
group_list.push(newkey);
legend_list.push(string_legend);
if (this.operator.length > 1) {
newkey_one = "val1";
group_list.push(newkey_one);
legend_list.push(newkey_one);
}
}, this);
if (group_list.length <=1){
group_list = [];
legend_list = [];
newkey = string_legend = "val";
group_list.push(newkey);
legend_list.push(string_legend);
}
var abscissa_data = {};
_.each(results, function (result) {
var label = result[self.chart_info_fields],
section = {};
if ((self.group_field.length) && (group_list.length > 1) && (self.operator.length <= 1)){
var legend_key_two = '';
_.each(self.group_field, function (res) {
result[res] = (typeof result[res] == 'object') ? result[res][1] : result[res];
legend_key_two += result[res];
});
newkey = legend_key_two.replace(/\s+/g,'_').replace(/[^a-zA-Z 0-9]+/g,'_');
}else{
newkey = "val";
}
if (abscissa_data[label] == undefined){
section[self.chart_info_fields] = label;
_.each(group_list, function (group) {
section[group] = 0;
});
} else {
section = abscissa_data[label];
}
section[newkey] = result[self.operator_field];
if (self.operator.length > 1){
section[newkey_one] = result[self.operator_field_one];
}
abscissa_data[label] = section;
});
//for legend color
var grp_color = _.map(legend_list, function (group_legend, index) {
var legend = {color: COLOR_PALETTE[index]};
if (group_legend == "val"){
legend['text'] = self.fields[self.operator_field]['string']
}else if(group_legend == "val1"){
legend['text'] = self.fields[self.operator_field_one]['string']
}else{
legend['text'] = group_legend;
}
return legend;
});
//for axis's value and title
var max,min,step;
var maximum,minimum;
if(_.isEmpty(abscissa_data)){
max = 9;
min = 0;
step=1;
}else{
var max_min = [];
_.each(abscissa_data, function (abscissa_datas) {
_.each(group_list, function(res){
max_min.push(abscissa_datas[res]);
});
});
maximum = Math.max.apply(Math,max_min);
minimum = Math.min.apply(Math,max_min);
if (maximum == minimum){
if (maximum == 0){
max = 9;
min = 0;
step=1;
}else if(maximum > 0){
max = maximum + (10 - maximum % 10);
min = 0;
step = Math.round(max/10);
}else{
max = 0;
min = minimum - (10 + minimum % 10);
step = Math.round(Math.abs(min)/10);
var group_list, view_chart;
if (!this.group_field) {
view_chart = (this.orientation === 'horizontal') ? 'barH' : 'bar';
group_list = _(this.columns).map(function (column, index) {
return {
group: column.name,
text: self.fields[column.name].string,
color: COLOR_PALETTE[index % (COLOR_PALETTE.length)]
}
});
} else {
// dhtmlx handles clustered bar charts (> 1 column per abscissa
// value) and stacked bar charts (basically the same but with the
// columns on top of one another instead of side by side), but it
// does not handle clustered stacked bar charts
if (this.columns.length > 1) {
this.$element.text(
'OpenERP Web does not support combining grouping and '
+ 'multiple columns in graph at this time.');
throw new Error(
'dhtmlx can not handle columns counts of that magnitude');
}
// transform series for clustered charts into series for stacked
// charts
view_chart = (this.orientation === 'horizontal')
? 'stackedBarH' : 'stackedBar';
group_list = _(results).chain()
.pluck(this.group_field)
.uniq()
.map(function (value, index) {
return {
group: self.ordinate + '_' +
value.toLowerCase().replace(/\s/g, '_'),
text: value,
color: COLOR_PALETTE[index % COLOR_PALETTE.length]
};
}).value();
results = _(results).chain()
.groupBy(function (record) { return record[self.abscissa]; })
.map(function (records) {
var r = {};
// second argument is coerced to a str, no good for boolean
r[self.abscissa] = records[0][self.abscissa];
_(records).each(function (record) {
var key = _.sprintf('%s_%s',
self.ordinate,
record[self.group_field].toLowerCase().replace(/\s/g, '_'));
r[key] = record[self.ordinate];
});
return r;
})
.value();
}
var abscissa_description = {
template: self.chart_info_fields,
title: "<b>"+self.x_title+"</b>"
title: "<b>" + this.fields[this.abscissa].string + "</b>",
template: function (obj) {
return obj[self.abscissa] || 'Undefined';
}
};
var ordinate_description = {
lines: true,
title: "<b>"+self.y_title+"</b>",
start: min,
step: step,
end: max
title: "<b>" + this.fields[this.ordinate].string + "</b>"
};
var x_axis, y_axis, tooltip;
if (self.orientation == 'horizontal'){
x_axis = ordinate_description;
y_axis = abscissa_description;
}else{
x_axis = abscissa_description;
y_axis = ordinate_description;
var x_axis, y_axis;
if (self.orientation == 'horizontal') {
x_axis = ordinate_description;
y_axis = abscissa_description;
} else {
x_axis = abscissa_description;
y_axis = ordinate_description;
}
tooltip = self.chart_info_fields;
var bar_chart = new dhtmlXChart({
view: view_chart,
container: self.elem_id+"-barchart",
value:"#"+group_list[0]+"#",
container: this.element_id+"-barchart",
value:"#"+group_list[0].group+"#",
gradient: "3d",
border: false,
width: 1024,
tooltip:{
template:"#"+tooltip+"#"+","+grp_color[0]['text']+"="+"#"+group_list[0]+"#"
template: _.sprintf("#%s#, %s=#%s#",
this.abscissa, group_list[0].text, group_list[0].group)
},
radius: 0,
color:grp_color[0]['color'],
color:group_list[0].color,
origin:0,
xAxis:{
template:function(obj){
if(x_axis['template']){
var val = obj[x_axis['template']];
val = (typeof val == 'object')?val[1]:(!val?'Undefined':val);
if(val.length > 12){
val = val.substring(0,12);
}
return val;
}else{
return obj;
}
},
title:x_axis['title'],
lines:x_axis['lines']
},
yAxis:{
template:function(obj){
if(y_axis['template']){
var vals = obj[y_axis['template']];
vals = (typeof vals == 'object')?vals[1]:(!vals?'Undefined':vals);
if(vals.length > 12){
vals = vals.substring(0,12);
}
return vals;
}else{
return obj;
}
},
title:y_axis['title'],
lines: y_axis['lines'],
start:y_axis['start'],
step:y_axis['step'],
end:y_axis['end']
},
xAxis: x_axis,
yAxis: y_axis,
padding: {
left: 75
},
legend: {
values: grp_color,
values: group_list,
align:"left",
valign:"top",
layout: "x",
marker:{
marker: {
type:"round",
width:12
}
}
});
for (var m = 1; m<group_list.length;m++){
var column = group_list[m];
if (column.group === this.group_field) { continue; }
bar_chart.addSeries({
value: "#"+group_list[m]+"#",
value: "#"+column.group+"#",
tooltip:{
template:"#"+tooltip+"#"+","+grp_color[m]['text']+"="+"#"+group_list[m]+"#"
template: _.sprintf("#%s#, %s=#%s#",
this.abscissa, column.text, column.group)
},
color: grp_color[m]['color']
color: column.color
});
}
bar_chart.parse(_.values(abscissa_data), "json");
jQuery("#"+self.elem_id+"-barchart").height(jQuery("#"+self.elem_id+"-barchart").height()+50);
bar_chart.parse(results, "json");
jQuery("#"+this.element_id+"-barchart").height(jQuery("#"+this.element_id+"-barchart").height()+50);
bar_chart.attachEvent("onItemClick", function(id) {
self.open_list_view(bar_chart.get(id));
});
@ -383,15 +303,15 @@ openerp.web_graph.GraphView = openerp.web.View.extend({
var self = this;
var chart = new dhtmlXChart({
view:"pie3D",
container:self.elem_id+"-piechart",
value:"#"+self.operator_field+"#",
container:self.element_id+"-piechart",
value:"#"+self.ordinate+"#",
pieInnerText:function(obj) {
var sum = chart.sum("#"+self.operator_field+"#");
var val = obj[self.operator_field] / sum * 100 ;
var sum = chart.sum("#"+self.ordinate+"#");
var val = obj[self.ordinate] / sum * 100 ;
return val.toFixed(1) + "%";
},
tooltip:{
template:"#"+self.chart_info_fields+"#"+"="+"#"+self.operator_field+"#"
template:"#"+self.abscissa+"#"+"="+"#"+self.ordinate+"#"
},
gradient:"3d",
height: 20,
@ -406,9 +326,7 @@ openerp.web_graph.GraphView = openerp.web.View.extend({
width:12
},
template:function(obj){
var val = obj[self.chart_info_fields];
val = (typeof val == 'object')?val[1]:val;
return val;
return obj[self.abscissa] || 'Undefined';
}
}
});
@ -422,20 +340,13 @@ openerp.web_graph.GraphView = openerp.web.View.extend({
if($(".dhx_tooltip").is(":visible")) {
$(".dhx_tooltip").remove('div');
}
id = id[this.chart_info_fields];
id = id[this.abscissa];
if (typeof id == 'object'){
id = id[0];
}
var record_id = "";
this.dataset.model = this.model;
if (typeof this.chart_info_fields == 'object'){
record_id = this.chart_info_fields[0];
}else{
record_id = this.chart_info_fields;
}
this.dataset.domain = [[record_id, '=', id],['id','in',this.dataset.ids]];
var modes = !!modes ? modes.split(",") : ["list", "form", "graph"];
var record_id = this.abscissa;
var modes = ["list", "form", "graph"];
var views = [];
_.each(modes, function(mode) {
var view = [false, mode];
@ -446,7 +357,7 @@ openerp.web_graph.GraphView = openerp.web.View.extend({
});
this.do_action({
"res_model" : this.dataset.model,
"domain" : this.dataset.domain,
"domain" : [[record_id, '=', id], ['id','in',this.dataset.ids]],
"views" : views,
"type" : "ir.actions.act_window",
"auto_search" : true,
@ -462,18 +373,18 @@ openerp.web_graph.GraphView = openerp.web.View.extend({
contexts: contexts,
group_by_seq: groupbys
}, function (results) {
// TODO: handle non-empty results.group_by with read_group
if(results.group_by && results.group_by != ''){
self.chart_info_fields = results.group_by[0];
}else{
self.chart_info_fields = self.chart_info;
// TODO: handle non-empty results.group_by with read_group?
if(!_(results.group_by).isEmpty()){
self.abscissa = results.group_by[0];
} else {
self.abscissa = self.first_field;
}
self.dataset.context = results.context;
self.dataset.domain = results.domain;
self.dataset.read_slice([],{}, $.proxy(self, 'load_chart'));
self.dataset.read_slice(self.list_fields(), {
context: results.context,
domain: results.domain
}, $.proxy(self, 'schedule_chart'));
});
}
});
// here you may tweak globals object, if any, and play with on_* or do_* callbacks on them
};
// vim:et fdc=0 fdl=0:

View File

@ -1,4 +1,4 @@
<template>
<div t-name="GraphView" t-att-id="elem_id+'-'+chart+'chart'"
<div t-name="GraphView" t-att-id="element_id+'-'+chart+'chart'"
style="height:300px;position:relative;"/>
</template>

View File

@ -5,4 +5,5 @@
"js": ["static/*/*.js", "static/*/js/*.js"],
"css": [],
'active': False,
'web_preload': False,
}

View File

@ -13,6 +13,13 @@ openerp.web.SearchView = openerp.web.SearchView.extend({
// here you may tweak globals object, if any, and play with on_* or do_* callbacks on them
openerp.web.Login = openerp.web.Login.extend({
start: function() {
console.log('Hello there');
this._super.apply(this,arguments);
}
});
};
// vim:et fdc=0 fdl=0:

View File

@ -1,8 +1,21 @@
.openerp .oe_kanban_view .oe_column {
float: left;
width: 100%;
}
.openerp .oe_vertical_text {
writing-mode:tb-rl;
-webkit-transform:rotate(90deg);
-moz-transform:rotate(90deg);
-o-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
display:block;
width:30px;
height:20px;
align:center;
font-size:24px;
}
.openerp .oe_kanban_view .ui-sortable-placeholder {
border: 1px dotted black;
visibility: visible !important;
@ -14,6 +27,10 @@
font-size: 1.5em;
font-weight: bold;
}
.openerp .oe_kanban_view .fold-columns-icon {
float: left;
padding: 2px 2px 0 0;
}
.openerp .oe_kanban_action_button {
height: 22px;
margin: 0;
@ -200,4 +217,3 @@
.openerp .oe_kanban_color_alert .oe_kanban_color_border {
border-color: #c00 !important;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

View File

@ -18,6 +18,8 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
this.all_display_data = false;
this.groups = [];
this.qweb = new QWeb2.Engine();
this.aggregates = {};
this.NO_OF_COLUMNS = 3;
if (this.options.action_views_ids.form) {
this.form_dialog = new openerp.web.FormDialog(this, {}, this.options.action_views_ids.form, dataset).start();
this.form_dialog.on_form_dialog_saved.add_last(this.on_record_saved);
@ -36,8 +38,17 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
}
},
add_qweb_template: function() {
var group_operator = ["avg", "max", "min", "sum", "count"]
for (var i=0, ii=this.fields_view.arch.children.length; i < ii; i++) {
var child = this.fields_view.arch.children[i];
if (child.tag === "field") {
for(j=0, jj=group_operator.length; j < jj; j++) {
if (child.attrs[group_operator[j]]) {
this.aggregates[child.attrs.name] = child.attrs[group_operator[j]];
break;
}
}
}
if (child.tag === "templates") {
this.transform_qweb_template(child);
this.qweb.add_template(openerp.web.json_node_to_xml(child));
@ -118,14 +129,33 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
}
}
},
on_show_data: function(data) {
sort_group: function (first, second) {
if (first.header && second.header)
{
first = first.header.toLowerCase();
second = second.header.toLowerCase();
if (first > second) return 1;
else if (first < second) return -1;
else return 0;
}
else return 0;
},
on_show_data: function() {
var self = this;
this.$element.html(QWeb.render("KanbanView", {"data": data}));
if (!this.group_by.length) {
this.do_record_group();
}
self.all_display_data.sort(this.sort_group);
this.$element.html(QWeb.render("KanbanView", {"data": self.all_display_data}));
this.on_reload_kanban();
this.$element.find(".oe_vertical_text").hide();
var drag_handel = false;
if (this.$element.find(".oe_kanban_draghandle").length > 0) {
drag_handel = ".oe_kanban_draghandle";
}
if (!this.group_by.length) {
drag_handel = true;
}
this.$element.find(".oe_column").sortable({
connectWith: ".oe_column",
handle : drag_handel,
@ -134,36 +164,44 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
self.source_index['column'] = ui.item.parent().attr('id');
},
stop: self.on_receive_record,
scroll: false
});
this.$element.find(".oe_column").disableSelection()
this.$element.find('button.oe_kanban_button_new').click(this.do_add_record);
this.$element.find(".fold-columns-icon").click(function(event) {
self.do_fold_unfold_columns(event, this.id);
});
},
on_button_click: function (button_attrs, record_id) {
var self = this;
if (this.groups.length) {
_.each(this.groups, function (group) {
group.list([],
function (groups) {},
function (dataset) {
dataset.read_slice([], {}, function(records) {
var index = parseInt(_.indexOf(dataset.ids, record_id));
if(index >= 0) {
self.on_confirm_click(dataset, button_attrs, index, record_id);
}
});
}
);
});
} else {
var index = parseInt(_.indexOf(self.dataset.ids, record_id));
if (index >= 0) {
_.extend(self.dataset, {domain: self.domain, context: self.context});
self.on_confirm_click(self.dataset, button_attrs, index, record_id);
do_fold_unfold_columns: function(event, element_id) {
var column_id = "column_" + element_id;
var column_element = this.$element.find("#" + column_id + " .oe_fold_column");
if (column_element.is(":hidden")) {
this.$element.find("#" + column_id).find("img.fold-columns-icon").attr('src', '/web_kanban/static/src/img/minus-icon.png');
column_element.show();
this.$element.find("#" + column_id + ".oe_table_column").css("width",Math.round(99 / this.all_display_data.length) + "%");
this.$element.find("#" + column_id + ".oe_vertical_text").hide();
}
else{
this.$element.find("#" + column_id).find("img.fold-columns-icon").attr('src', '/web_kanban/static/src/img/plus-icon.png');
column_element.hide();
this.$element.find("#" + column_id + ".oe_table_column").css("width","0.5%");
this.$element.find("#" + column_id + ".oe_vertical_text").show();
}
},
do_record_group: function() {
if (this.NO_OF_COLUMNS && this.all_display_data.length > 0) {
var records = this.all_display_data[0].records;
var record_per_group = Math.round((records).length / this.NO_OF_COLUMNS);
this.all_display_data = [];
for (var i=0, ii=this.NO_OF_COLUMNS; i < ii; i++) {
this.all_display_data.push({'records': records.slice(0,record_per_group), 'value':i, 'header' : false, 'ids':[]});
records.splice(0,record_per_group);
}
}
},
on_confirm_click: function (dataset, button_attrs, index, record_id) {
this.on_execute_button_click(dataset, button_attrs, record_id);
on_button_click: function (button_attrs, record_id) {
this.on_execute_button_click(this.dataset, button_attrs, record_id);
},
do_add_record: function() {
this.dataset.index = null;
@ -179,8 +217,7 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
},
on_record_saved: function(r) {
var id = this.form_dialog.form.datarecord.id;
// TODO fme: reload record instead of all. need refactoring
this.do_actual_search();
this.on_reload_record(id);
},
do_change_color: function(record_id, $e) {
var self = this,
@ -198,12 +235,41 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
var data = {};
data[$e.data('name')] = $(this).data('color');
self.dataset.write(id, data, {}, function() {
// TODO fme: reload record instead of all. need refactoring
self.do_actual_search();
self.on_reload_record(id);
});
$cpicker.remove();
});
},
/**
Reload one record in view.
record_id : reload record id.
*/
on_reload_record: function (record_id){
var self = this;
this.dataset.read_ids([record_id], [], function (records) {
if (records.length > 0) {
for (var i=0, ii=self.all_display_data.length; i < ii; i++) {
for(j=0, jj=self.all_display_data[i].records.length; j < jj; j++) {
if (self.all_display_data[i].records[j].id == record_id) {
_.extend(self.all_display_data[i].records[j], records[0]);
self.$element.find("#main_" + record_id).children().remove();
self.$element.find("#main_" + record_id).append(self.qweb.render('kanban-box', {
record: self.do_transform_record(self.all_display_data[i].records[j]),
kanban_color: self.kanban_color,
kanban_gravatar: self.kanban_gravatar
}));
break;
}
}
}
self.$element.find("#main_" + record_id + " .oe_kanban_action").click(self.on_action_clicked);
self.$element.find("#main_" + record_id + " .oe_kanban_box_show_onclick_trigger").click(function() {
$(this).parent("#main_" + record_id + " .oe_kanban_box").find(".oe_kanban_box_show_onclick").toggle();
});
}
});
},
do_delete: function (id) {
var self = this;
return $.when(this.dataset.unlink([id])).then(function () {
@ -228,16 +294,7 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
this.do_execute_action(
button_attrs, dataset,
record_id, function () {
var count = 1;
_.each(self.all_display_data, function(data, index) {
self.dataset.read_ids( data.ids, [], function(records){
self.all_display_data[index].records = records;
if(self.all_display_data.length == count) {
self.do_actual_search();
}
count++;
});
});
self.do_actual_search();
}
);
},
@ -249,9 +306,8 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
if (!ui.item.attr("id")) {
return false;
}
// TODO fme: check what was this sequence
if (self.fields_view.fields.sequence && (self.source_index.index >= 0 && self.source_index.index != from) ||
(self.source_index.column && self.source_index.column != ui.item.parent().attr('id'))) {
if (self.fields_view.fields.sequence != undefined && ((self.source_index.index >= 0 && self.source_index.index != from) ||
(self.source_index.column && self.source_index.column != ui.item.parent().attr('id')))) {
var child_record = ui.item.parent().children();
var data, sequence = 1, index = to;
child_record.splice(0, to);
@ -338,9 +394,6 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
kanban_gravatar: self.kanban_gravatar
}));
});
} else {
self.$element.find("#column_" + data.value).remove();
self.all_display_data.splice(index, 1);
}
});
this.$element.find('.oe_kanban_action').click(this.on_action_clicked);
@ -377,8 +430,8 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
do_search: function (domains, contexts, group_by) {
var self = this;
this.rpc('/web/session/eval_domain_and_context', {
domains: domains,
contexts: contexts,
domains: [this.dataset.get_domain()].concat(domains),
contexts: [this.dataset.get_context()].concat(contexts),
group_by_seq: group_by
}, function (results) {
self.domain = results.domain;
@ -392,23 +445,27 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
group_by = self.group_by;
if (!group_by.length && this.fields_view.arch.attrs.default_group_by) {
group_by = [this.fields_view.arch.attrs.default_group_by];
self.group_by = group_by;
}
self.datagroup = new openerp.web.DataGroup(self, self.model, self.domain, self.context, group_by);
self.dataset.context = self.context;
self.dataset.domain = self.domain;
self.datagroup.list([],
self.datagroup.list(_.keys(self.fields_view.fields),
function (groups) {
self.groups = groups;
self.do_render_group(groups);
if (groups.length) {
self.do_render_group(groups);
}
else {
self.all_display_data = [];
self.on_show_data();
}
},
function (dataset) {
self.domain = dataset.domain;
self.context = dataset.context;
self.groups = [];
self.dataset.read_slice([], {}, function(records) {
self.all_display_data = [{'records': records, 'value':false, 'header' : false, 'ids': self.dataset.ids}];
self.dataset.read_slice([], {'domain': self.domain, 'context': self.context}, function(records) {
if (records.length) self.all_display_data = [{'records': records, 'value':false, 'header' : false, 'ids': self.dataset.ids}];
else self.all_display_data = [];
self.$element.find(".oe_kanban_view").remove();
self.on_show_data(self.all_display_data);
self.on_show_data();
});
}
);
@ -417,8 +474,6 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
this.all_display_data = [];
var self = this;
_.each(datagroups, function (group) {
self.dataset.context = group.context;
self.dataset.domain = group.domain;
var group_name = group.value;
var group_value = group.value;
if (!group.value) {
@ -428,11 +483,15 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
group_name = group.value[1];
group_value = group.value[0];
}
self.dataset.read_slice([], {}, function(records) {
self.all_display_data.push({"value" : group_value, "records": records, 'header':group_name, 'ids': self.dataset.ids});
var group_aggregates = {};
_.each(self.aggregates, function(value, key) {
group_aggregates[value] = group.aggregates[key];
});
self.dataset.read_slice([], {'domain': group.domain, 'context': group.context}, function(records) {
self.all_display_data.push({"value" : group_value, "records": records, 'header':group_name, 'ids': self.dataset.ids, 'aggregates': group_aggregates});
if (datagroups.length == self.all_display_data.length) {
self.$element.find(".oe_kanban_view").remove();
self.on_show_data(self.all_display_data);
self.on_show_data();
}
});
});

View File

@ -9,15 +9,22 @@
</td>
</tr>
<tr>
<td t-foreach="data" t-as="columns" class="oe_table_column oe_column_heading" t-att-id="'column_' + columns.value">
<t t-if="columns.value" t-esc="columns.header"/>
<td t-foreach="data" t-as="columns" class="oe_table_column" t-att-id="'column_' + columns.value">
<img t-att-id="columns.value" class="fold-columns-icon" src="/web_kanban/static/src/img/minus-icon.png"/>
<div class="oe_fold_column" t-att-id="'column_' + columns.value">
<div class="oe_column_heading" t-if="columns.value and columns.header">
<t t-esc="columns.header"/>
</div>
<div t-foreach="columns.aggregates or {}" t-as="aggregate">
<i><u><t t-esc="aggregate"/>:</u> <t t-esc="aggregate_value"/></i>
</div>
</div>
</td>
</tr>
<tr>
<td t-foreach="data" t-as="columns" class="oe_table_column" t-att-id="'column_' + columns.value" t-attf-style="width: #{Math.round(99 / data.length)}%">
<div class="oe_column" t-att-id="'column_' + columns.value">
<div t-foreach="columns.records" t-as="record" class="oe_kanban_record" t-att-id="'main_' + record.id"/>
</div>
<td t-foreach="data" t-as="columns" class="oe_column oe_table_column" t-att-id="'column_' + columns.value" t-attf-style="width: #{Math.round(99 / data.length)}%">
<p t-if="columns.header" class="oe_vertical_text" t-att-id="'column_' + columns.value"><t t-esc="columns.header"/></p>
<div t-foreach="columns.records" t-as="record" class="oe_fold_column oe_kanban_record" t-att-id="'main_' + record.id"/>
</td>
</tr>
</table>

View File

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@ -0,0 +1,8 @@
{
"name": "Tests",
"version": "2.0",
"depends": [],
"js": ["static/src/js/*.js"],
"css": ['static/src/css/*.css'],
'active': True,
}

View File

@ -0,0 +1,3 @@
.oe-bunchaforms > div {
float: left;
}

View File

@ -0,0 +1,37 @@
openerp.web_tests = function (db) {
db.web.client_actions.add(
'buncha-forms', 'instance.web_tests.BunchaForms');
db.web_tests = {};
db.web_tests.BunchaForms = db.web.Widget.extend({
init: function (parent) {
this._super(parent);
this.dataset = new db.web.DataSetSearch(this, 'test.listview.relations');
this.form = new db.web.FormView(this, this.dataset, false, {
action_buttons: false,
pager: false
});
this.form.registry = db.web.form.readonly;
},
render: function () {
return '<div class="oe-bunchaforms"></div>';
},
start: function () {
$.when(
this.dataset.read_slice(),
this.form.appendTo(this.$element)).then(this.on_everything_loaded);
},
on_everything_loaded: function (slice) {
var records = slice[0].records;
if (!records.length) {
this.form.on_record_loaded({});
return;
}
this.form.on_record_loaded(records[0]);
_(records.slice(1)).each(function (record, index) {
this.dataset.index = index+1;
this.form.reposition($('<div>').appendTo(this.$element));
this.form.on_record_loaded(record);
}, this);
}
});
};

View File

@ -282,6 +282,18 @@ view managers can correctly communicate with them:
defining e.g. the ``start`` method) or at the instance level (in the
class's ``init``), though you should generally set it on the class.
Frequent development tasks
--------------------------
There are a number of tasks which OpenERP Web developers do or will need to
perform quite regularly. To make these easier, we have written a few guides
to help you get started:
.. toctree::
:maxdepth: 1
guides/client-action.rst
Utility behaviors
-----------------

View File

@ -0,0 +1,92 @@
Creating a new client action
============================
Client actions are the client-side of OpenERP's "Server Actions": instead of
allowing for semi-arbitrary code to be executed in the server, they allow
for execution of client-customized code.
On the server side, a client action is an action of type ``ir.actions.client``,
which has (at most) two properties: a mandatory ``tag``, which is an arbitrary
string by which the client will identify the action, and an optional ``params``
which is simply a map of keys and values sent to the client as-is (this way,
client actions can be made generic and reused in multiple contexts).
General Structure
-----------------
In the OpenERP Web code, a client action only requires two pieces of
information:
* Mapping the action's ``tag`` to an OpenERP Web object
* The OpenERP Web object itself, which must inherit from
:js:class:`openerp.web.Widget`
Our example will be the actual code for the widgets client action (a client
action displaying a ``res.widget`` object, used in the homepage dashboard of
the web client):
.. code-block:: javascript
// Registers the object 'openerp.web_dashboard.Widget' to the client
// action tag 'board.home.widgets'
openerp.web.client_actions.add(
'board.home.widgets', 'openerp.web_dashboard.Widget');
// This object inherits from View, but only Widget is required
openerp.web_dashboard.Widget = openerp.web.View.extend({
template: 'HomeWidget'
});
At this point, the generic ``Widget`` lifecycle takes over, the template is
rendered, inserted in the client DOM, bound on the object's ``$element``
property and the object is started.
If the client action takes parameters, these parameters are passed in as a
second positional parameter to the constructor:
.. code-block:: javascript
init: function (parent, params) {
// execute the Widget's init
this._super(parent);
// board.home.widgets only takes a single param, the identifier of the
// res.widget object it should display. Store it for later
this.widget_id = params.widget_id;
}
More complex initialization (DOM manipulations, RPC requests, ...) should be
performed in the ``start()`` method.
.. note::
As required by ``Widget``'s contract, if ``start`` executes any
asynchronous code it should return a ``$.Deferred`` so callers know when
it's ready for interaction.
Although generally speaking client actions are not really interacted with.
.. code-block:: javascript
start: function () {
return $.when(
this._super(),
// Simply read the res.widget object this action should display
new openerp.web.DataSet(this, 'res.widget').read_ids(
[this.widget_id], ['title'], this.on_widget_loaded));
}
The client action can then behave exactly as it wishes to within its root
(``this.$element``). In this case, it performs further renderings once its
widget's content is retrieved:
.. code-block:: javascript
on_widget_loaded: function (widgets) {
var widget = widgets[0];
var url = _.sprintf(
'/web_dashboard/widgets/content?session_id=%s&widget_id=%d',
this.session.session_id, widget.id);
this.$element.html(QWeb.render('HomeWidget.content', {
widget: widget,
url: url
}));
}

27
logging.json Normal file
View File

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

View File

@ -2,11 +2,13 @@
import optparse
import os
import sys
import json
import tempfile
import logging
import logging.config
import werkzeug.serving
import werkzeug.contrib.fixers
path_root = os.path.dirname(os.path.abspath(__file__))
path_addons = os.path.join(path_root, 'addons')
@ -14,8 +16,6 @@ if path_addons not in sys.path:
sys.path.insert(0, path_addons)
optparser = optparse.OptionParser()
optparser.add_option("-p", "--port", dest="socket_port", default=8002,
help="listening port", type="int", metavar="NUMBER")
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")
@ -25,37 +25,57 @@ 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 database", metavar="REGEXP")
optparser.add_option('--addons-path', dest='addons_path', default=path_addons,
optparser.add_option('--addons-path', dest='addons_path', default=[path_addons], action='append',
help="Path do addons directory", metavar="PATH")
optparser.add_option('--no-serve-static', dest='serve_static',
default=True, action='store_false',
help="Do not serve static files via this server")
optparser.add_option('--reloader', dest='reloader',
default=False, action='store_true',
help="Reload application when python files change")
optparser.add_option("--log-level", dest="log_level",
default='debug', help="Log level", metavar="LOG_LEVEL")
optparser.add_option("--log-config", dest="log_config",
default='', help="Log config file", metavar="LOG_CONFIG")
optparser.add_option('--multi-threaded', dest='threaded',
default=False, action='store_true',
help="Use multiple threads to handle requests")
optparser.add_option('--load', dest='server_wide_modules', default=['web'], action='append',
help="Load a 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)
import web.common.dispatch
if __name__ == "__main__":
(options, args) = optparser.parse_args(sys.argv[1:])
options.backend = 'rpc'
options.backend = 'xmlrpc'
os.environ["TZ"] = "UTC"
if not options.log_config:
logging.basicConfig(level=getattr(logging, options.log_level.upper()))
if sys.version_info >= (2, 7):
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.config.fileConfig(options.log_config)
logging.basicConfig(level=getattr(logging, options.log_level.upper()))
app = web.common.dispatch.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)