bzr revid: nicolas.vanhoren@openerp.com-20110915093144-0ix1tdc8mcmx6l9p
This commit is contained in:
niv-openerp
2011-09-15 11:31:44 +02:00
11 changed files with 141 additions and 638 deletions
+3 -3
View File
@@ -32,10 +32,10 @@
"static/src/js/views.js",
"static/src/js/data.js",
"static/src/js/data_export.js",
"static/src/js/form.js",
"static/src/js/list.js",
"static/src/js/list-editable.js",
"static/src/js/search.js",
"static/src/js/view_form.js",
"static/src/js/view_list.js",
"static/src/js/view_list_editable.js",
"static/src/js/view_tree.js",
],
'css' : [
@@ -1,35 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) Stephane Wirtel
# Copyright (C) 2011 Nicolas Vanhoren
# Copyright (C) 2011 OpenERP s.a. (<http://openerp.com>).
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * 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.
# * Neither the name of the <organization> nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##############################################################################
from main import *
@@ -1,99 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) Stephane Wirtel
# Copyright (C) 2011 Nicolas Vanhoren
# Copyright (C) 2011 OpenERP s.a. (<http://openerp.com>).
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * 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.
# * Neither the name of the <organization> nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##############################################################################
import datetime
DEFAULT_SERVER_DATE_FORMAT = "%Y-%m-%d"
DEFAULT_SERVER_TIME_FORMAT = "%H:%M:%S"
DEFAULT_SERVER_DATETIME_FORMAT = "%s %s" % (
DEFAULT_SERVER_DATE_FORMAT,
DEFAULT_SERVER_TIME_FORMAT)
def str_to_datetime(str):
"""
Converts a string to a datetime object using OpenERP's
datetime string format (exemple: '2011-12-01 15:12:35').
No timezone information is added, the datetime is a naive instance, but
according to OpenERP 6.1 specification the timezone is always UTC.
"""
if not str:
return str
return datetime.datetime.strptime(str, DEFAULT_SERVER_DATETIME_FORMAT)
def str_to_date(str):
"""
Converts a string to a date object using OpenERP's
date string format (exemple: '2011-12-01').
"""
if not str:
return str
return datetime.datetime.strptime(str, DEFAULT_SERVER_DATE_FORMAT).date()
def str_to_time(str):
"""
Converts a string to a time object using OpenERP's
time string format (exemple: '15:12:35').
"""
if not str:
return str
return datetime.datetime.strptime(str, DEFAULT_SERVER_TIME_FORMAT).time()
def datetime_to_str(obj):
"""
Converts a datetime object to a string using OpenERP's
datetime string format (exemple: '2011-12-01 15:12:35').
The datetime instance should not have an attached timezone and be in UTC.
"""
if not obj:
return False
return obj.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
def date_to_str(obj):
"""
Converts a date object to a string using OpenERP's
date string format (exemple: '2011-12-01').
"""
if not obj:
return False
return obj.strftime(DEFAULT_SERVER_DATE_FORMAT)
def time_to_str(obj):
"""
Converts a time object to a string using OpenERP's
time string format (exemple: '15:12:35').
"""
if not obj:
return False
return obj.strftime(DEFAULT_SERVER_TIME_FORMAT)
@@ -1,397 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) Stephane Wirtel
# Copyright (C) 2011 Nicolas Vanhoren
# Copyright (C) 2011 OpenERP s.a. (<http://openerp.com>).
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * 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.
# * Neither the name of the <organization> nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##############################################################################
"""
OpenERP Client Library
"""
import xmlrpclib
import logging
import socket
try:
import cPickle as pickle
pickle.__name__
except:
import pickle
try:
import cStringIO as StringIO
StringIO.__name__
except:
import StringIO
_logger = logging.getLogger(__name__)
def _getChildLogger(logger, subname):
return logging.getLogger(logger.name + "." + subname)
class Connector(object):
"""
The base abstract class representing a connection to an OpenERP Server.
"""
__logger = _getChildLogger(_logger, 'connector')
def __init__(self, hostname, port):
"""
Initilize by specifying an hostname and a port.
:param hostname: Host name of the server.
:param port: Port for the connection to the server.
"""
self.hostname = hostname
self.port = port
class XmlRPCConnector(Connector):
"""
A type of connector that uses the XMLRPC protocol.
"""
PROTOCOL = 'xmlrpc'
__logger = _getChildLogger(_logger, 'connector.xmlrpc')
def __init__(self, hostname, port=8069):
"""
Initialize by specifying the hostname and the port.
:param hostname: The hostname of the computer holding the instance of OpenERP.
:param port: The port used by the OpenERP instance for XMLRPC (default to 8069).
"""
Connector.__init__(self, hostname, port)
self.url = 'http://%s:%d/xmlrpc' % (self.hostname, self.port)
def send(self, service_name, method, *args):
url = '%s/%s' % (self.url, service_name)
service = xmlrpclib.ServerProxy(url)
return getattr(service, method)(*args)
class NetRPC_Exception(Exception):
"""
Exception for NetRPC errors.
"""
def __init__(self, faultCode, faultString):
self.faultCode = faultCode
self.faultString = faultString
self.args = (faultCode, faultString)
class NetRPC:
"""
Low level class for NetRPC protocol.
"""
def __init__(self, sock=None):
if sock is None:
self.sock = socket.socket(
socket.AF_INET, socket.SOCK_STREAM)
else:
self.sock = sock
self.sock.settimeout(120)
def connect(self, host, port=False):
if not port:
buf = host.split('//')[1]
host, port = buf.split(':')
self.sock.connect((host, int(port)))
def disconnect(self):
self.sock.shutdown(socket.SHUT_RDWR)
self.sock.close()
def mysend(self, msg, exception=False, traceback=None):
msg = pickle.dumps([msg,traceback])
size = len(msg)
self.sock.send('%8d' % size)
self.sock.send(exception and "1" or "0")
totalsent = 0
while totalsent < size:
sent = self.sock.send(msg[totalsent:])
if sent == 0:
raise RuntimeError, "socket connection broken"
totalsent = totalsent + sent
def myreceive(self):
buf=''
while len(buf) < 8:
chunk = self.sock.recv(8 - len(buf))
if chunk == '':
raise RuntimeError, "socket connection broken"
buf += chunk
size = int(buf)
buf = self.sock.recv(1)
if buf != "0":
exception = buf
else:
exception = False
msg = ''
while len(msg) < size:
chunk = self.sock.recv(size-len(msg))
if chunk == '':
raise RuntimeError, "socket connection broken"
msg = msg + chunk
msgio = StringIO.StringIO(msg)
unpickler = pickle.Unpickler(msgio)
unpickler.find_global = None
res = unpickler.load()
if isinstance(res[0],Exception):
if exception:
raise NetRPC_Exception(str(res[0]), str(res[1]))
raise res[0]
else:
return res[0]
class NetRPCConnector(Connector):
"""
A type of connector that uses the NetRPC protocol.
"""
PROTOCOL = 'netrpc'
__logger = _getChildLogger(_logger, 'connector.netrpc')
def __init__(self, hostname, port=8070):
"""
Initialize by specifying the hostname and the port.
:param hostname: The hostname of the computer holding the instance of OpenERP.
:param port: The port used by the OpenERP instance for NetRPC (default to 8070).
"""
Connector.__init__(self, hostname, port)
def send(self, service_name, method, *args):
socket = NetRPC()
socket.connect(self.hostname, self.port)
socket.mysend((service_name, method, )+args)
result = socket.myreceive()
socket.disconnect()
return result
class Service:
"""
A class to execute RPC calls on a specific service of the remote server.
"""
def __init__(self, connector, service_name):
"""
:param connector: A valid Connector instance.
:param service_name: The name of the service on the remote server.
"""
self.connector = connector
self.service_name = service_name
self.__logger = _getChildLogger(_getChildLogger(_logger, 'service'),service_name)
def __getattr__(self, method):
"""
:param method: The name of the method to execute on the service.
"""
self.__logger.debug('method: %r', method)
def proxy(*args):
"""
:param args: A list of values for the method
"""
self.__logger.debug('args: %r', args)
result = self.connector.send(self.service_name, method, *args)
self.__logger.debug('result: %r' % result)
return result
return proxy
class Connection(object):
"""
A class to represent a connection with authentification to an OpenERP Server.
It also provides utility methods to interact with the server more easily.
"""
__logger = _getChildLogger(_logger, 'connection')
def __init__(self, connector,
database=None,
login=None,
password=None,
user_id=None):
"""
Initialize with login information. The login information is facultative to allow specifying
it after the initialization of this object.
:param connector: A valid Connector instance to send messages to the remote server.
:param database: The name of the database to work on.
:param login: The login of the user.
:param password: The password of the user.
:param user_id: The user id is a number identifying the user. This is only useful if you
already know it, in most cases you don't need to specify it.
"""
self.connector = connector
self.set_login_info(database, login, password, user_id)
def set_login_info(self, database, login, password, user_id=None):
"""
Set login information after the initialisation of this object.
:param connector: A valid Connector instance to send messages to the remote server.
:param database: The name of the database to work on.
:param login: The login of the user.
:param password: The password of the user.
:param user_id: The user id is a number identifying the user. This is only useful if you
already know it, in most cases you don't need to specify it.
"""
self.database, self.login, self.password = database, login, password
self.user_id = user_id
def check_login(self, force=True):
"""
Checks that the login information is valid. Throws an AuthentificationError if the
authentification fails.
:param force: Force to re-check even if this Connection was already validated previously.
Default to True.
"""
if self.user_id and not force:
return
self.user_id = self.get_service("common").login(self.database, self.login, self.password)
if not self.user_id:
raise AuthentificationError("Authentification failure")
self.__logger.debug("Authentified with user id %s" % self.user_id)
"""
Returns a Model instance to allow easy remote manipulation of an OpenERP model.
:param model_name: The name of the model.
"""
def get_model(self, model_name):
return Model(self, model_name)
"""
Returns a Service instance to allow easy manipulation of one of the services offered by the remote server.
Please note this Connection instance does not need to have valid authenfication information since authentification
is only necessary for the "object" service that handles models.
:param service_name: The name of the service.
"""
def get_service(self, service_name):
return Service(self.connector, service_name)
class AuthentificationError(Exception):
"""
An error thrown when an authentification to an OpenERP server failed.
"""
pass
class Model(object):
"""
Useful class to dialog with one of the models provided by an OpenERP server.
An instance of this class depends on a Connection instance with valid authenfication information.
"""
def __init__(self, connection, model_name):
"""
:param connection: A valid Connection instance with correct authentification information.
:param model_name: The name of the model.
"""
self.connection = connection
self.model_name = model_name
self.__logger = _getChildLogger(_getChildLogger(_logger, 'object'), model_name)
def __getattr__(self, method):
"""
Provides proxy methods that will forward calls to the model on the remote OpenERP server.
:param method: The method for the linked model (search, read, write, unlink, create, ...)
"""
def proxy(*args):
"""
:param args: A list of values for the method
"""
self.connection.check_login(False)
self.__logger.debug(args)
result = self.connection.get_service('object').execute(
self.connection.database,
self.connection.user_id,
self.connection.password,
self.model_name,
method,
*args)
if method == "read":
if isinstance(result, list) and len(result) > 0 and "id" in result[0]:
index = {}
for i in xrange(len(result)):
index[result[i]["id"]] = result[i]
result = [index[x] for x in args[0]]
self.__logger.debug('result: %r' % result)
return result
return proxy
def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None, context=None):
"""
A shortcut method to combine a search() and a read().
:param domain: The domain for the search.
:param fields: The fields to extract (can be None or [] to extract all fields).
:param offset: The offset for the rows to read.
:param limit: The maximum number of rows to read.
:param order: The order to class the rows.
:param context: The context.
:return: A list of dictionaries containing all the specified fields.
"""
record_ids = self.search(domain or [], offset, limit or False, order or False, context or {})
records = self.read(record_ids, fields or [], context or {})
return records
def get_connector(hostname, protocol="xmlrpc", port="auto"):
"""
A shortcut method to easily create a connector to a remote server using XMLRPC or NetRPC.
:param hostname: The hostname to the remote server.
:param protocol: The name of the protocol, must be "xmlrpc" or "netrpc".
:param port: The number of the port. Defaults to auto.
"""
if port == 'auto':
port = 8069 if protocol=="xmlrpc" else 8070
if protocol == "xmlrpc":
return XmlRPCConnector(hostname, port)
elif protocol == "netrpc":
return NetRPCConnector(hostname, port)
else:
raise ValueError("You must choose xmlrpc or netrpc")
def get_connection(hostname, protocol="xmlrpc", port='auto', database=None,
login=None, password=None, user_id=None):
"""
A shortcut method to easily create a connection to a remote OpenERP server.
:param hostname: The hostname to the remote server.
:param protocol: The name of the protocol, must be "xmlrpc" or "netrpc".
:param port: The number of the port. Defaults to auto.
:param connector: A valid Connector instance to send messages to the remote server.
:param database: The name of the database to work on.
:param login: The login of the user.
:param password: The password of the user.
:param user_id: The user id is a number identifying the user. This is only useful if you
already know it, in most cases you don't need to specify it.
"""
return Connection(get_connector(hostname, protocol, port), database, login, password, user_id)
+2 -2
View File
@@ -842,8 +842,8 @@ label.error {
.openerp label.oe_label_help {
cursor: help;
}
.openerp label.oe_label,
.openerp label.oe_label_help {
.openerp .oe_form_field label.oe_label, .openerp .oe_form_field label.oe_label_help {
text-align: right;
margin: 3px 0 0 10px;
}
+48 -30
View File
@@ -463,30 +463,6 @@ openerp.web.search.Widget = openerp.web.Widget.extend( /** @lends openerp.web.se
}));
}
});
openerp.web.search.FilterGroup = openerp.web.search.Widget.extend(/** @lends openerp.web.search.FilterGroup# */{
template: 'SearchView.filters',
/**
* Inclusive group of filters, creates a continuous "button" with clickable
* sections (the normal display for filters is to be a self-contained button)
*
* @constructs openerp.web.search.FilterGroup
* @extends openerp.web.search.Widget
*
* @param {Array<openerp.web.search.Filter>} filters elements of the group
* @param {openerp.web.SearchView} view view in which the filters are contained
*/
init: function (filters, view) {
this._super(view);
this.filters = filters;
this.length = filters.length;
},
start: function () {
this._super();
_.each(this.filters, function (filter) {
filter.start();
});
}
});
openerp.web.search.add_expand_listener = function($root) {
$root.find('a.searchview_group_string').click(function (e) {
$root.toggleClass('folded expanded');
@@ -533,6 +509,49 @@ openerp.web.search.Input = openerp.web.search.Widget.extend( /** @lends openerp.
"get_domain not implemented for widget " + this.attrs.type);
}
});
openerp.web.search.FilterGroup = openerp.web.search.Input.extend(/** @lends openerp.web.search.FilterGroup# */{
template: 'SearchView.filters',
/**
* Inclusive group of filters, creates a continuous "button" with clickable
* sections (the normal display for filters is to be a self-contained button)
*
* @constructs openerp.web.search.FilterGroup
* @extends openerp.web.search.Input
*
* @param {Array<openerp.web.search.Filter>} filters elements of the group
* @param {openerp.web.SearchView} view view in which the filters are contained
*/
init: function (filters, view) {
this._super(view);
this.filters = filters;
this.length = filters.length;
},
start: function () {
this._super();
_.each(this.filters, function (filter) {
filter.start();
});
},
get_context: function () { },
/**
* Handles domains-fetching for all the filters within it: groups them.
*/
get_domain: function () {
var domains = _(this.filters).chain()
.filter(function (filter) { return filter.is_enabled(); })
.map(function (filter) { return filter.attrs.domain; })
.value();
if (!domains.length) { return; }
if (domains.length === 1) { return domains[0]; }
for (var i=domains.length; --i;) {
domains.unshift(['|']);
}
return _.extend(new openerp.web.CompoundDomain(), {
__domains: domains
});
}
});
openerp.web.search.Filter = openerp.web.search.Input.extend(/** @lends openerp.web.search.Filter# */{
template: 'SearchView.filter',
/**
@@ -586,12 +605,11 @@ openerp.web.search.Filter = openerp.web.search.Input.extend(/** @lends openerp.w
}
return this.attrs.context;
},
get_domain: function () {
if (!this.is_enabled()) {
return;
}
return this.attrs.domain;
}
/**
* Does not return anything: filter domain is handled at the FilterGroup
* level
*/
get_domain: function () { }
});
openerp.web.search.Field = openerp.web.search.Input.extend( /** @lends openerp.web.search.Field# */ {
template: 'SearchView.field',
@@ -35,7 +35,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
this.datarecord = {};
this.ready = false;
this.show_invalid = true;
this.dirty = false;
this.dirty_for_user = false;
this.default_focus_field = null;
this.default_focus_button = null;
this.registry = openerp.web.form.widgets;
@@ -93,6 +93,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
this.$form_header.find('button.oe_form_button_save_edit').click(this.do_save_edit);
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);
if (this.options.sidebar && this.options.sidebar_id) {
this.sidebar = new openerp.web.Sidebar(this, this.options.sidebar_id);
@@ -139,7 +140,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
this.$form_header.find('.oe_form_on_update').show();
this.$form_header.find('button.oe_form_button_new').show();
}
this.dirty = false;
this.dirty_for_user = false;
this.datarecord = record;
for (var f in this.fields) {
var field = this.fields[f];
@@ -149,7 +150,6 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
}
if (!record.id) {
// New record: Second pass in order to trigger the onchanges
this.dirty = true;
this.show_invalid = false;
for (var f in record) {
var field = this.fields[f];
@@ -178,21 +178,23 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
}
},
on_pager_action: function(action) {
switch (action) {
case 'first':
this.dataset.index = 0;
break;
case 'previous':
this.dataset.previous();
break;
case 'next':
this.dataset.next();
break;
case 'last':
this.dataset.index = this.dataset.ids.length - 1;
break;
if (this.can_be_discarded()) {
switch (action) {
case 'first':
this.dataset.index = 0;
break;
case 'previous':
this.dataset.previous();
break;
case 'next':
this.dataset.next();
break;
case 'last':
this.dataset.index = this.dataset.ids.length - 1;
break;
}
this.reload();
}
this.reload();
},
do_update_pager: function(hide_index) {
var $pager = this.$element.find('#' + this.element_id + '_header div.oe_form_pager');
@@ -274,7 +276,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
processed.push(field.name);
if (field.get_value() != value) {
field.set_value(value);
field.dirty = true;
field.dirty = this.dirty_for_user = true;
if (_.indexOf(processed, field.name) < 0) {
this.do_onchange(field, processed);
}
@@ -302,13 +304,31 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
var self = this;
var def = $.Deferred();
$.when(this.has_been_loaded).then(function() {
self.dataset.default_get(
_.keys(self.fields_view.fields)).then(self.on_record_loaded).then(function() {
if (self.can_be_discarded()) {
self.dataset.default_get(_.keys(self.fields_view.fields)).then(self.on_record_loaded).then(function() {
def.resolve();
});
});
}
});
return def.promise();
},
on_button_duplicate: function() {
var self = this;
var def = $.Deferred();
$.when(this.has_been_loaded).then(function() {
if (self.can_be_discarded()) {
self.dataset.call('copy', [self.datarecord.id, {}, self.dataset.context]).then(function(new_id) {
return self.on_created({ result : new_id });
}).then(function() {
def.resolve();
});
}
});
return def.promise();
},
can_be_discarded: function() {
return !this.dirty_for_user || confirm(_t("Warning, the record has been modified, your changes will be discarded."));
},
/**
* Triggers saving the form's record. Chooses between creating a new
* record or saving an existing one depending on whether the record
@@ -814,7 +834,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
},
on_click: function(saved) {
var self = this;
if (!this.node.attrs.special && this.view.dirty && saved !== true) {
if ((!this.node.attrs.special && this.view.dirty_for_user && saved !== true) || !this.view.recordcount.id) {
this.view.do_save(function() {
self.on_click(true);
});
@@ -962,7 +982,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
}
},
on_ui_change: function() {
this.dirty = this.view.dirty = true;
this.dirty = this.view.dirty_for_user = true;
this.validate();
if (this.is_valid()) {
this.set_value_from_ui();
+44 -49
View File
@@ -2,17 +2,21 @@
* OpenERP web library
*---------------------------------------------------------*/
openerp.web.views = function(openerp) {
openerp.web.views = function(db) {
var _t = openerp.web._t;
var QWeb = openerp.web.qweb;
var _t = db.web._t;
/**
* Registry for all the client actions key: tag value: widget
*/
openerp.web.client_actions = new openerp.web.Registry();
db.web.client_actions = new db.web.Registry();
openerp.web.ActionManager = openerp.web.Widget.extend({
/**
* Registry for all the main views
*/
db.web.views = new db.web.Registry();
db.web.ActionManager = db.web.Widget.extend({
identifier_prefix: "actionmanager",
init: function(parent) {
this._super(parent);
@@ -87,20 +91,20 @@ openerp.web.ActionManager = openerp.web.Widget.extend({
ir_actions_act_window: function (action, on_close) {
if (action.target === 'new') {
if (this.dialog == null) {
this.dialog = new openerp.web.Dialog(this, { title: action.name, width: '80%' });
this.dialog = new db.web.Dialog(this, { title: action.name, width: '80%' });
if(on_close)
this.dialog.on_close.add(on_close);
this.dialog.start();
} else {
this.dialog_viewmanager.stop();
}
this.dialog_viewmanager = new openerp.web.ViewManagerAction(this, action);
this.dialog_viewmanager = new db.web.ViewManagerAction(this, action);
this.dialog_viewmanager.appendTo(this.dialog.$element);
this.dialog.open();
} else {
this.dialog_stop();
this.content_stop();
this.inner_viewmanager = new openerp.web.ViewManagerAction(this, action);
this.inner_viewmanager = new db.web.ViewManagerAction(this, action);
this.inner_viewmanager.appendTo(this.$element);
this.url_update(action);
}
@@ -125,7 +129,7 @@ openerp.web.ActionManager = openerp.web.Widget.extend({
},
ir_actions_client: function (action) {
this.content_stop();
var ClientWidget = openerp.web.client_actions.get_object(action.tag);
var ClientWidget = db.web.client_actions.get_object(action.tag);
(this.client_widget = new ClientWidget(this, action.params)).appendTo(this);
},
ir_actions_report_xml: function(action) {
@@ -138,12 +142,12 @@ openerp.web.ActionManager = openerp.web.Widget.extend({
}
});
openerp.web.ViewManager = openerp.web.Widget.extend(/** @lends openerp.web.ViewManager# */{
db.web.ViewManager = db.web.Widget.extend(/** @lends db.web.ViewManager# */{
identifier_prefix: "viewmanager",
template: "ViewManager",
/**
* @constructs openerp.web.ViewManager
* @extends openerp.web.Widget
* @constructs db.web.ViewManager
* @extends db.web.Widget
*
* @param parent
* @param dataset
@@ -158,10 +162,10 @@ openerp.web.ViewManager = openerp.web.Widget.extend(/** @lends openerp.web.View
this.views_src = _.map(views, function(x) {return x instanceof Array? {view_id: x[0], view_type: x[1]} : x;});
this.views = {};
this.flags = this.flags || {};
this.registry = openerp.web.views;
this.registry = db.web.views;
},
render: function() {
return QWeb.render(this.template, {
return db.web.qweb.render(this.template, {
self: this,
prefix: this.element_id,
views: this.views_src});
@@ -280,7 +284,7 @@ openerp.web.ViewManager = openerp.web.Widget.extend(/** @lends openerp.web.View
if (this.searchview) {
this.searchview.stop();
}
this.searchview = new openerp.web.SearchView(
this.searchview = new db.web.SearchView(
this, this.element_id + "_search", this.dataset,
view_id, search_defaults);
@@ -307,13 +311,13 @@ openerp.web.ViewManager = openerp.web.Widget.extend(/** @lends openerp.web.View
on_action_executed: function () {}
});
openerp.web.ViewManagerAction = openerp.web.ViewManager.extend(/** @lends oepnerp.web.ViewManagerAction# */{
db.web.ViewManagerAction = db.web.ViewManager.extend(/** @lends oepnerp.web.ViewManagerAction# */{
template:"ViewManagerAction",
/**
* @constructs openerp.web.ViewManagerAction
* @extends openerp.web.ViewManager
* @constructs db.web.ViewManagerAction
* @extends db.web.ViewManager
*
* @param {openerp.web.ActionManager} parent parent object/widget
* @param {db.web.ActionManager} parent parent object/widget
* @param {Object} action descriptor for the action this viewmanager needs to manage its views.
*/
init: function(parent, action) {
@@ -322,7 +326,7 @@ openerp.web.ViewManagerAction = openerp.web.ViewManager.extend(/** @lends oepner
// ``_super()``) rpc requests will blow up.
this.session = parent.session;
this.action = action;
var dataset = new openerp.web.DataSetSearch(this, action.res_model, action.context, action.domain);
var dataset = new db.web.DataSetSearch(this, action.res_model, action.context, action.domain);
if (action.res_id) {
dataset.ids.push(action.res_id);
dataset.index = 0;
@@ -376,12 +380,12 @@ openerp.web.ViewManagerAction = openerp.web.ViewManager.extend(/** @lends oepner
this.$element.find('.oe_get_xml_view').click(function () {
// TODO: add search view?
$('<pre>').text(openerp.web.json_node_to_xml(
$('<pre>').text(db.web.json_node_to_xml(
self.views[self.active_view].controller.fields_view.arch, true))
.dialog({ width: '95%'});
});
if (this.action.help && !this.flags.low_profile) {
var Users = new openerp.web.DataSet(self, 'res.users'),
var Users = new db.web.DataSet(self, 'res.users'),
header = this.$element.find('.oe-view-manager-header');
header.delegate('blockquote button', 'click', function() {
var $this = $(this);
@@ -421,7 +425,7 @@ openerp.web.ViewManagerAction = openerp.web.ViewManager.extend(/** @lends oepner
var grandparent = this.widget_parent && this.widget_parent.widget_parent;
// display shortcuts if on the first view for the action
var $shortcut_toggle = this.$element.find('.oe-shortcut-toggle');
if (!(grandparent instanceof openerp.web.WebClient) ||
if (!(grandparent instanceof db.web.WebClient) ||
!(view.view_type === this.views_src[0].view_type
&& view.view_id === this.views_src[0].view_id)) {
$shortcut_toggle.hide();
@@ -458,7 +462,7 @@ openerp.web.ViewManagerAction = openerp.web.ViewManager.extend(/** @lends oepner
* Intercept do_action resolution from children views
*/
on_action_executed: function () {
new openerp.web.DataSet(this, 'res.log')
new db.web.DataSet(this, 'res.log')
.call('get', [], this.do_display_log);
},
/**
@@ -485,7 +489,7 @@ openerp.web.ViewManagerAction = openerp.web.ViewManager.extend(/** @lends oepner
}
});
openerp.web.Sidebar = openerp.web.Widget.extend({
db.web.Sidebar = db.web.Widget.extend({
init: function(parent, element_id) {
this._super(parent, element_id);
this.items = {};
@@ -494,7 +498,7 @@ openerp.web.Sidebar = openerp.web.Widget.extend({
start: function() {
this._super(this);
var self = this;
this.$element.html(QWeb.render('Sidebar'));
this.$element.html(db.web.qweb.render('Sidebar'));
this.$element.find(".toggle-sidebar").click(function(e) {
self.do_toggle();
});
@@ -536,7 +540,7 @@ openerp.web.Sidebar = openerp.web.Widget.extend({
this.items[items[i].element_id] = items[i];
}
}
var $section = $(QWeb.render("Sidebar.section", {
var $section = $(db.web.qweb.render("Sidebar.section", {
section_id: section_id,
name: name,
classname: 'oe_sidebar_' + code,
@@ -592,7 +596,7 @@ openerp.web.Sidebar = openerp.web.Widget.extend({
}
});
openerp.web.TranslateDialog = openerp.web.Dialog.extend({
db.web.TranslateDialog = db.web.Dialog.extend({
dialog_title: _t("Translations"),
init: function(view) {
// TODO fme: should add the language to fields_view_get because between the fields view get
@@ -612,14 +616,14 @@ openerp.web.TranslateDialog = openerp.web.Dialog.extend({
this.translatable_fields_keys = _.map(this.view.translatable_fields || [], function(i) { return i.name });
this.languages = null;
this.languages_loaded = $.Deferred();
(new openerp.web.DataSetSearch(this, 'res.lang', this.view.dataset.get_context(),
(new db.web.DataSetSearch(this, 'res.lang', this.view.dataset.get_context(),
[['translatable', '=', '1']])).read_slice(['code', 'name'], { sort: 'id' }, this.on_languages_loaded);
},
start: function() {
var self = this;
this._super();
$.when(this.languages_loaded).then(function() {
self.$element.html(QWeb.render('TranslateDialog', { widget: self }));
self.$element.html(db.web.qweb.render('TranslateDialog', { widget: self }));
self.$element.tabs();
if (!(self.view.translatable_fields && self.view.translatable_fields.length)) {
self.hide_tabs('fields');
@@ -724,11 +728,7 @@ openerp.web.TranslateDialog = openerp.web.Dialog.extend({
}
});
/**
* @class
* @extends openerp.web.Widget
*/
openerp.web.View = openerp.web.Widget.extend(/** @lends openerp.web.View# */{
db.web.View = db.web.Widget.extend(/** @lends db.web.View# */{
set_default_options: function(options) {
this.options = options || {};
_.defaults(this.options, {
@@ -741,7 +741,7 @@ openerp.web.View = openerp.web.Widget.extend(/** @lends openerp.web.View# */{
},
open_translate_dialog: function(field) {
if (!this.translate_dialog) {
this.translate_dialog = new openerp.web.TranslateDialog(this).start();
this.translate_dialog = new db.web.TranslateDialog(this).start();
}
this.translate_dialog.open(field);
},
@@ -753,7 +753,7 @@ openerp.web.View = openerp.web.Widget.extend(/** @lends openerp.web.View# */{
* @param {String} [action_data.special=null] special action handlers (currently: only ``'cancel'``)
* @param {String} [action_data.type='workflow'] the action type, if present, one of ``'object'``, ``'action'`` or ``'workflow'``
* @param {Object} [action_data.context=null] additional action context, to add to the current context
* @param {openerp.web.DataSet} dataset a dataset object used to communicate with the server
* @param {db.web.DataSet} dataset a dataset object used to communicate with the server
* @param {Object} [record_id] the identifier of the object on which the action is to be applied
* @param {Function} on_closed callback to execute when dialog is closed or when the action does not generate any result (no new action)
*/
@@ -772,14 +772,14 @@ openerp.web.View = openerp.web.Widget.extend(/** @lends openerp.web.View# */{
active_ids: [record_id || false],
active_model: dataset.model
});
action.context = new openerp.web.CompoundContext(dataset.get_context(), action.context);
action.context = new db.web.CompoundContext(dataset.get_context(), action.context);
self.do_action(action, result_handler);
} else {
result_handler();
}
};
var context = new openerp.web.CompoundContext(dataset.get_context(), action_data.context || {});
var context = new db.web.CompoundContext(dataset.get_context(), action_data.context || {});
if (action_data.special) {
handler({result: {"type":"ir.actions.act_window_close"}});
@@ -794,7 +794,7 @@ openerp.web.View = openerp.web.Widget.extend(/** @lends openerp.web.View# */{
/**
* Directly set a view to use instead of calling fields_view_get. This method must
* be called before start(). When an embedded view is set, underlying implementations
* of openerp.web.View must use the provided view instead of any other one.
* of db.web.View must use the provided view instead of any other one.
*
* @param embedded_view A view.
*/
@@ -841,7 +841,7 @@ openerp.web.View = openerp.web.Widget.extend(/** @lends openerp.web.View# */{
},
on_sidebar_manage_view: function() {
if (this.fields_view && this.fields_view.arch) {
$('<xmp>' + openerp.web.json_node_to_xml(this.fields_view.arch, true) + '</xmp>').dialog({ width: '95%', height: 600});
$('<xmp>' + db.web.json_node_to_xml(this.fields_view.arch, true) + '</xmp>').dialog({ width: '95%', height: 600});
} else {
this.notification.warn("Manage Views", "Could not find current view declaration");
}
@@ -855,7 +855,7 @@ openerp.web.View = openerp.web.Widget.extend(/** @lends openerp.web.View# */{
on_sidebar_import: function() {
},
on_sidebar_export: function() {
var export_view = new openerp.web.DataExport(this, this.dataset);
var export_view = new db.web.DataExport(this, this.dataset);
export_view.start();
},
on_sidebar_translate: function() {
@@ -865,12 +865,7 @@ openerp.web.View = openerp.web.Widget.extend(/** @lends openerp.web.View# */{
}
});
/**
* Registry for all the main views
*/
openerp.web.views = new openerp.web.Registry();
openerp.web.json_node_to_xml = function(node, single_quote, indent) {
db.web.json_node_to_xml = function(node, single_quote, indent) {
// For debugging purpose, this function will convert a json node back to xml
// Maybe usefull for xml view editor
@@ -899,7 +894,7 @@ openerp.web.json_node_to_xml = function(node, single_quote, indent) {
r += '>\n';
var childs = [];
for (var i = 0, ii = node.children.length; i < ii; i++) {
childs.push(openerp.web.json_node_to_xml(node.children[i], single_quote, indent + 1));
childs.push(db.web.json_node_to_xml(node.children[i], single_quote, indent + 1));
}
r += childs.join('\n');
r += '\n' + sindent + '</' + node.tag + '>';
+1
View File
@@ -661,6 +661,7 @@
</button>
<!--<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>
</div>
<div class="oe_form_pager" t-if="view.options.pager !== false">
<button type="button" data-pager-action="first">First</button>