merge upstream
bzr revid: chs@openerp.com-20110926112623-q68d3tebr80qt6l9
This commit is contained in:
commit
4261f1ad11
|
@ -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.tools
|
||||
import os
|
||||
import tempfile
|
||||
_logger.info("embedded mode")
|
||||
class Options(object):
|
||||
pass
|
||||
o = Options()
|
||||
o.dbfilter = '.*'
|
||||
o.dbfilter = openerp.tools.config['dbfilter']
|
||||
o.session_storage = os.path.join(tempfile.gettempdir(), "oe-sessions")
|
||||
o.addons_path = os.path.dirname(os.path.dirname(__file__))
|
||||
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
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"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",
|
||||
|
@ -33,6 +34,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 +47,7 @@
|
|||
"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',
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
@ -79,12 +78,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
|
||||
|
||||
|
@ -100,13 +99,12 @@ class WebRequest(object):
|
|||
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.config))
|
||||
self.context = self.params.pop('context', None)
|
||||
self.debug = self.params.pop('debug', False) != False
|
||||
|
||||
|
@ -389,15 +387,14 @@ class Root(object):
|
|||
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):
|
||||
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())
|
||||
_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')
|
||||
statics['/%s/static' % module] = path_static
|
||||
for k, v in controllers_class.items():
|
||||
if k not in controllers_object:
|
||||
o = v()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -190,6 +190,34 @@ 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, None)
|
||||
except openerp.netsvc.OpenERPDispatcherException, e:
|
||||
fault = xmlrpclib.Fault(openerp.tools.exception_to_unicode(e.exception), e.traceback)
|
||||
raise fault
|
||||
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.
|
||||
|
@ -363,7 +391,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 +405,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.
|
||||
|
|
|
@ -26,9 +26,8 @@ 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, config):
|
||||
self.config = config
|
||||
self._db = False
|
||||
self._uid = False
|
||||
self._login = False
|
||||
|
@ -40,11 +39,16 @@ class OpenERPSession(object):
|
|||
self._lang = {}
|
||||
self.remote_timezone = 'utc'
|
||||
self.client_timezone = False
|
||||
|
||||
|
||||
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)
|
||||
if self.config.backend == 'local':
|
||||
conn = openerplib.get_connection(protocol='local', database=self._db,
|
||||
login=self._login, user_id=self._uid, password=self._password)
|
||||
else:
|
||||
conn = openerplib.get_connection(hostname=self.config.server_host,
|
||||
port=self.config.server_port, 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)
|
||||
|
|
|
@ -172,14 +172,14 @@ 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":[]}
|
||||
|
@ -246,7 +246,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:
|
||||
|
@ -297,7 +297,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,
|
||||
|
@ -342,7 +342,7 @@ 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
|
||||
|
@ -721,7 +721,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])
|
||||
|
@ -969,7 +969,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")
|
||||
|
@ -978,7 +978,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")
|
||||
|
@ -1388,7 +1388,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,
|
||||
|
@ -1400,6 +1400,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'])
|
||||
|
@ -1413,3 +1414,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}}))
|
||||
|
|
|
@ -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.
|
|
@ -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
|
|
@ -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)
|
|
@ -892,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;
|
||||
|
@ -904,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;
|
||||
|
|
|
@ -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 |
|
@ -58,7 +58,7 @@ openerp.web = function(instance) {
|
|||
openerp.web.formats(instance);
|
||||
openerp.web.chrome(instance);
|
||||
openerp.web.data(instance);
|
||||
var files = ["views","search","list","form","list_editable","web_mobile","view_tree","data_export"];
|
||||
var files = ["views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import"];
|
||||
for(var i=0; i<files.length; i++) {
|
||||
if(openerp.web[files[i]]) {
|
||||
openerp.web[files[i]](instance);
|
||||
|
|
|
@ -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);
|
||||
|
@ -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">'});
|
||||
|
@ -467,10 +466,11 @@ openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{
|
|||
/**
|
||||
* @constructs openerp.web.Login
|
||||
* @extends openerp.web.Widget
|
||||
*
|
||||
*
|
||||
* @param parent
|
||||
* @param element_id
|
||||
*/
|
||||
|
||||
init: function(parent, element_id) {
|
||||
this._super(parent, element_id);
|
||||
this.has_local_storage = typeof(localStorage) != 'undefined';
|
||||
|
@ -496,7 +496,7 @@ openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{
|
|||
},
|
||||
display: function() {
|
||||
var self = this;
|
||||
|
||||
|
||||
this.$element.html(QWeb.render("Login", this));
|
||||
this.database = new openerp.web.Database(
|
||||
this, "oe_database", "oe_db_options");
|
||||
|
@ -574,14 +574,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 +665,7 @@ openerp.web.Header = openerp.web.Widget.extend(/** @lends openerp.web.Header# *
|
|||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
on_action: function(action) {
|
||||
},
|
||||
on_preferences: function(){
|
||||
|
@ -702,10 +701,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 +713,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 +728,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 +758,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
|
||||
|
@ -918,7 +910,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) {
|
||||
|
@ -963,7 +955,6 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
|
|||
this.session.start();
|
||||
this.login.start();
|
||||
this.menu.start();
|
||||
console.debug("The openerp client has been initialized.");
|
||||
},
|
||||
on_logged: function() {
|
||||
if(this.action_manager)
|
||||
|
@ -1009,7 +1000,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
|
||||
|
@ -1034,7 +1025,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);
|
||||
}
|
||||
|
@ -1042,10 +1032,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;
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
/*---------------------------------------------------------
|
||||
* OpenERP Web core
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
var console;
|
||||
if (!console) {
|
||||
console = {log: function () {}};
|
||||
}
|
||||
if (!console.debug) {
|
||||
console.debug = console.log;
|
||||
}
|
||||
|
@ -483,7 +486,8 @@ 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();
|
||||
if (self.uid)
|
||||
self.on_session_valid();
|
||||
return true;
|
||||
}).then(success_callback);
|
||||
},
|
||||
|
@ -771,7 +775,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 * /);
|
||||
*
|
||||
|
@ -915,6 +918,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;
|
||||
|
@ -922,7 +927,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) {
|
||||
|
@ -943,7 +948,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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
};
|
|
@ -118,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;
|
||||
|
|
|
@ -795,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;
|
||||
}
|
||||
});
|
||||
/**
|
||||
|
@ -1123,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"},
|
||||
|
@ -1134,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"},
|
||||
|
@ -1156,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({
|
||||
|
|
|
@ -1007,19 +1007,18 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
|
|||
* 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 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_view_fields_values();
|
||||
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() {
|
||||
|
@ -1121,16 +1120,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,
|
||||
|
@ -1148,6 +1144,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);
|
||||
|
@ -1157,60 +1155,93 @@ 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.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.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({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.jqueryui_object = 'datepicker';
|
||||
},
|
||||
on_picker_select: function(text, instance) {
|
||||
this._super(text, instance);
|
||||
this.$element.find('.oe_datepicker').hide();
|
||||
build_widget: function() {
|
||||
return new openerp.web.DateWidget(this);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1436,7 +1467,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());
|
||||
|
@ -1444,7 +1475,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 = {};
|
||||
|
|
|
@ -415,8 +415,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 +541,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 +1236,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
|
||||
|
|
|
@ -862,6 +862,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);
|
||||
|
|
|
@ -800,13 +800,15 @@
|
|||
></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">
|
||||
|
@ -1051,6 +1053,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' : '')"
|
||||
|
@ -1142,11 +1156,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"/>
|
||||
|
@ -1372,6 +1383,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>
|
||||
|
|
|
@ -7,6 +7,7 @@ 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 +15,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")
|
||||
|
@ -27,25 +26,37 @@ 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,
|
||||
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")
|
||||
|
||||
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",
|
||||
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"
|
||||
|
||||
|
@ -56,6 +67,9 @@ if __name__ == "__main__":
|
|||
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue