[MERGE] from trunk
bzr revid: chm@openerp.com-20130326090427-g78bigwi30rzbthd
This commit is contained in:
commit
7e895d90cb
|
@ -26,6 +26,7 @@ This module provides the core of the OpenERP Web Client.
|
|||
"static/lib/spinjs/spin.js",
|
||||
"static/lib/jquery.autosize/jquery.autosize.js",
|
||||
"static/lib/jquery.blockUI/jquery.blockUI.js",
|
||||
"static/lib/jquery.placeholder/jquery.placeholder.js",
|
||||
"static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js",
|
||||
"static/lib/jquery.ui.timepicker/js/jquery-ui-timepicker-addon.js",
|
||||
"static/lib/jquery.ui.notify/js/jquery.notify.js",
|
||||
|
|
|
@ -15,6 +15,8 @@ import simplejson
|
|||
import time
|
||||
import urllib
|
||||
import urllib2
|
||||
import urlparse
|
||||
import xmlrpclib
|
||||
import zlib
|
||||
from xml.etree import ElementTree
|
||||
from cStringIO import StringIO
|
||||
|
@ -91,16 +93,50 @@ def db_list(req):
|
|||
dbs = [i for i in dbs if re.match(r, i)]
|
||||
return dbs
|
||||
|
||||
def db_monodb(req):
|
||||
# if only one db exists, return it else return False
|
||||
def db_monodb_redirect(req):
|
||||
db = False
|
||||
redirect = False
|
||||
|
||||
# 1 try the db in the url
|
||||
db_url = req.params.get('db')
|
||||
if db_url:
|
||||
return (db_url, False)
|
||||
|
||||
try:
|
||||
dbs = db_list(req)
|
||||
if len(dbs) == 1:
|
||||
return dbs[0]
|
||||
except Exception:
|
||||
# ignore access denied
|
||||
pass
|
||||
return False
|
||||
dbs = []
|
||||
|
||||
# 2 use the database from the cookie if it's listable and still listed
|
||||
cookie_db = req.httprequest.cookies.get('last_used_database')
|
||||
if cookie_db in dbs:
|
||||
db = cookie_db
|
||||
|
||||
# 3 use the first db
|
||||
if dbs and not db:
|
||||
db = dbs[0]
|
||||
|
||||
# redirect to the chosen db if multiple are available
|
||||
if db and len(dbs) > 1:
|
||||
query = dict(urlparse.parse_qsl(req.httprequest.query_string, keep_blank_values=True))
|
||||
query.update({ 'db': db })
|
||||
redirect = req.httprequest.path + '?' + urllib.urlencode(query)
|
||||
return (db, redirect)
|
||||
|
||||
def db_monodb(req):
|
||||
# if only one db exists, return it else return False
|
||||
return db_monodb_redirect(req)[0]
|
||||
|
||||
def redirect_with_hash(req, url, code=303):
|
||||
if req.httprequest.user_agent.browser == 'msie':
|
||||
try:
|
||||
version = float(req.httprequest.user_agent.version)
|
||||
if version < 10:
|
||||
return "<html><head><script>window.location = '%s#' + location.hash;</script></head></html>" % url
|
||||
except Exception:
|
||||
pass
|
||||
return werkzeug.utils.redirect(url, code)
|
||||
|
||||
def module_topological_sort(modules):
|
||||
""" Return a list of module names sorted so that their dependencies of the
|
||||
|
@ -291,6 +327,10 @@ def manifest_glob(req, extension, addons=None, db=None):
|
|||
return r
|
||||
|
||||
def manifest_list(req, extension, mods=None, db=None):
|
||||
""" list ressources to load specifying either:
|
||||
mods: a comma separated string listing modules
|
||||
db: a database name (return all installed modules in that database)
|
||||
"""
|
||||
if not req.debug:
|
||||
path = '/web/webclient/' + extension
|
||||
if mods is not None:
|
||||
|
@ -299,12 +339,7 @@ def manifest_list(req, extension, mods=None, db=None):
|
|||
path += '?' + urllib.urlencode({'db': db})
|
||||
return [path]
|
||||
files = manifest_glob(req, extension, addons=mods, db=db)
|
||||
i_am_diabetic = req.httprequest.environ["QUERY_STRING"].count("no_sugar") >= 1 or \
|
||||
req.httprequest.environ.get('HTTP_REFERER', '').count("no_sugar") >= 1
|
||||
if i_am_diabetic:
|
||||
return [wp for _fp, wp in files]
|
||||
else:
|
||||
return ['%s?debug=%s' % (wp, os.path.getmtime(fp)) for fp, wp in files]
|
||||
return [wp for _fp, wp in files]
|
||||
|
||||
def get_last_modified(files):
|
||||
""" Returns the modification time of the most recently modified
|
||||
|
@ -534,6 +569,10 @@ class Home(openerpweb.Controller):
|
|||
|
||||
@openerpweb.httprequest
|
||||
def index(self, req, s_action=None, db=None, **kw):
|
||||
db, redir = db_monodb_redirect(req)
|
||||
if redir:
|
||||
return redirect_with_hash(req, redir)
|
||||
|
||||
js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in manifest_list(req, 'js', db=db))
|
||||
css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in manifest_list(req, 'css', db=db))
|
||||
|
||||
|
@ -869,7 +908,7 @@ class Session(openerpweb.Controller):
|
|||
"""
|
||||
saved_actions = req.httpsession.get('saved_actions')
|
||||
if not saved_actions:
|
||||
saved_actions = {"next":0, "actions":{}}
|
||||
saved_actions = {"next":1, "actions":{}}
|
||||
req.httpsession['saved_actions'] = saved_actions
|
||||
# we don't allow more than 10 stored actions
|
||||
if len(saved_actions["actions"]) >= 10:
|
||||
|
|
|
@ -107,6 +107,12 @@ formatted differently). If an input *may* fetch multiple completion
|
|||
items, it *should* prefix those with a section title using its own
|
||||
name. This has no technical consequence but is clearer for users.
|
||||
|
||||
.. note::
|
||||
|
||||
If a field is :js:func:`invisible
|
||||
<openerp.web.search.Input.visible>`, its completion function will
|
||||
*not* be called.
|
||||
|
||||
Providing drawer/supplementary UI
|
||||
+++++++++++++++++++++++++++++++++
|
||||
|
||||
|
@ -145,6 +151,11 @@ started only once (per view).
|
|||
dynamically collects, lays out and renders filters? =>
|
||||
exercises drawer thingies
|
||||
|
||||
.. note::
|
||||
|
||||
An :js:func:`invisible <openerp.web.search.Input.visible>` input
|
||||
will not be inserted into the drawer.
|
||||
|
||||
Converting from facet objects
|
||||
+++++++++++++++++++++++++++++
|
||||
|
||||
|
|
|
@ -92,6 +92,14 @@ class WebRequest(object):
|
|||
if not self.session:
|
||||
self.session = session.OpenERPSession()
|
||||
self.httpsession[self.session_id] = self.session
|
||||
|
||||
# set db/uid trackers - they're cleaned up at the WSGI
|
||||
# dispatching phase in openerp.service.wsgi_server.application
|
||||
if self.session._db:
|
||||
threading.current_thread().dbname = self.session._db
|
||||
if self.session._uid:
|
||||
threading.current_thread().uid = self.session._uid
|
||||
|
||||
self.context = self.params.pop('context', {})
|
||||
self.debug = self.params.pop('debug', False) is not False
|
||||
# Determine self.lang
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -11,8 +11,8 @@ Date.CultureInfo = {
|
|||
firstLetterDayNames: ["أ", "ا", "ث", "أ", "خ", "ج", "س"],
|
||||
|
||||
/* Month Name Strings */
|
||||
monthNames: ["كانون الثاني", "شباط", "آذار", "نيسان", "أيار", "حزيران", "تموز", "آب", "أيلول", "تشرين الأول", "تشرين الثاني", "كانون الأول"],
|
||||
abbreviatedMonthNames: ["كانون الثاني", "شباط", "آذار", "نيسان", "أيار", "حزيران", "تموز", "آب", "أيلول", "تشرين الأول", "تشرين الثاني", "كانون الأول"],
|
||||
monthNames: ["يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"],
|
||||
abbreviatedMonthNames: ["يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"],
|
||||
|
||||
/* AM/PM Designators */
|
||||
amDesignator: "ص",
|
||||
|
@ -82,18 +82,18 @@ Date.CultureInfo = {
|
|||
* As well, please review the list of "Future Strings" section below.
|
||||
*/
|
||||
regexPatterns: {
|
||||
jan: /^كانون الثاني/i,
|
||||
feb: /^شباط/i,
|
||||
mar: /^آذار/i,
|
||||
apr: /^نيسان/i,
|
||||
may: /^أيار/i,
|
||||
jun: /^حزيران/i,
|
||||
jul: /^تموز/i,
|
||||
aug: /^آب/i,
|
||||
sep: /^أيلول/i,
|
||||
oct: /^تشرين الأول/i,
|
||||
nov: /^تشرين الثاني/i,
|
||||
dec: /^كانون الأول/i,
|
||||
jan: /^يناير/i,
|
||||
feb: /^فبراير/i,
|
||||
mar: /^مارس/i,
|
||||
apr: /^أبريل/i,
|
||||
may: /^مايو/i,
|
||||
jun: /^يونيو/i,
|
||||
jul: /^يوليو/i,
|
||||
aug: /^أغسطس/i,
|
||||
sep: /^سبتمبر/i,
|
||||
oct: /^أكتوبر/i,
|
||||
nov: /^نوفمبر/i,
|
||||
dec: /^ديسمبر/i,
|
||||
|
||||
sun: /^الاحد/i,
|
||||
mon: /^ا(1)?/i,
|
||||
|
@ -192,4 +192,4 @@ Date.CultureInfo = {
|
|||
* end end
|
||||
* long long
|
||||
* short short
|
||||
*/
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
/*! http://mths.be/placeholder v2.0.7 by @mathias */
|
||||
;(function(window, document, $) {
|
||||
|
||||
var isInputSupported = 'placeholder' in document.createElement('input'),
|
||||
isTextareaSupported = 'placeholder' in document.createElement('textarea'),
|
||||
prototype = $.fn,
|
||||
valHooks = $.valHooks,
|
||||
hooks,
|
||||
placeholder;
|
||||
|
||||
if (isInputSupported && isTextareaSupported) {
|
||||
|
||||
placeholder = prototype.placeholder = function() {
|
||||
return this;
|
||||
};
|
||||
|
||||
placeholder.input = placeholder.textarea = true;
|
||||
|
||||
} else {
|
||||
|
||||
placeholder = prototype.placeholder = function() {
|
||||
var $this = this;
|
||||
$this
|
||||
.filter((isInputSupported ? 'textarea' : ':input') + '[placeholder]')
|
||||
.not('.placeholder')
|
||||
.bind({
|
||||
'focus.placeholder': clearPlaceholder,
|
||||
'blur.placeholder': setPlaceholder
|
||||
})
|
||||
.data('placeholder-enabled', true)
|
||||
.trigger('blur.placeholder');
|
||||
return $this;
|
||||
};
|
||||
|
||||
placeholder.input = isInputSupported;
|
||||
placeholder.textarea = isTextareaSupported;
|
||||
|
||||
hooks = {
|
||||
'get': function(element) {
|
||||
var $element = $(element);
|
||||
return $element.data('placeholder-enabled') && $element.hasClass('placeholder') ? '' : element.value;
|
||||
},
|
||||
'set': function(element, value) {
|
||||
var $element = $(element);
|
||||
if (!$element.data('placeholder-enabled')) {
|
||||
return element.value = value;
|
||||
}
|
||||
if (value == '') {
|
||||
element.value = value;
|
||||
// Issue #56: Setting the placeholder causes problems if the element continues to have focus.
|
||||
if (element != document.activeElement) {
|
||||
// We can't use `triggerHandler` here because of dummy text/password inputs :(
|
||||
setPlaceholder.call(element);
|
||||
}
|
||||
} else if ($element.hasClass('placeholder')) {
|
||||
clearPlaceholder.call(element, true, value) || (element.value = value);
|
||||
} else {
|
||||
element.value = value;
|
||||
}
|
||||
// `set` can not return `undefined`; see http://jsapi.info/jquery/1.7.1/val#L2363
|
||||
return $element;
|
||||
}
|
||||
};
|
||||
|
||||
isInputSupported || (valHooks.input = hooks);
|
||||
isTextareaSupported || (valHooks.textarea = hooks);
|
||||
|
||||
$(function() {
|
||||
// Look for forms
|
||||
$(document).delegate('form', 'submit.placeholder', function() {
|
||||
// Clear the placeholder values so they don't get submitted
|
||||
var $inputs = $('.placeholder', this).each(clearPlaceholder);
|
||||
setTimeout(function() {
|
||||
$inputs.each(setPlaceholder);
|
||||
}, 10);
|
||||
});
|
||||
});
|
||||
|
||||
// Clear placeholder values upon page reload
|
||||
$(window).bind('beforeunload.placeholder', function() {
|
||||
$('.placeholder').each(function() {
|
||||
this.value = '';
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function args(elem) {
|
||||
// Return an object of element attributes
|
||||
var newAttrs = {},
|
||||
rinlinejQuery = /^jQuery\d+$/;
|
||||
$.each(elem.attributes, function(i, attr) {
|
||||
if (attr.specified && !rinlinejQuery.test(attr.name)) {
|
||||
newAttrs[attr.name] = attr.value;
|
||||
}
|
||||
});
|
||||
return newAttrs;
|
||||
}
|
||||
|
||||
function clearPlaceholder(event, value) {
|
||||
var input = this,
|
||||
$input = $(input);
|
||||
if (input.value == $input.attr('placeholder') && $input.hasClass('placeholder')) {
|
||||
if ($input.data('placeholder-password')) {
|
||||
$input = $input.hide().next().show().attr('id', $input.removeAttr('id').data('placeholder-id'));
|
||||
// If `clearPlaceholder` was called from `$.valHooks.input.set`
|
||||
if (event === true) {
|
||||
return $input[0].value = value;
|
||||
}
|
||||
$input.focus();
|
||||
} else {
|
||||
input.value = '';
|
||||
$input.removeClass('placeholder');
|
||||
input == document.activeElement && input.select();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setPlaceholder() {
|
||||
var $replacement,
|
||||
input = this,
|
||||
$input = $(input),
|
||||
$origInput = $input,
|
||||
id = this.id;
|
||||
if (input.value == '') {
|
||||
if (input.type == 'password') {
|
||||
if (!$input.data('placeholder-textinput')) {
|
||||
try {
|
||||
$replacement = $input.clone().attr({ 'type': 'text' });
|
||||
} catch(e) {
|
||||
$replacement = $('<input>').attr($.extend(args(this), { 'type': 'text' }));
|
||||
}
|
||||
$replacement
|
||||
.removeAttr('name')
|
||||
.data({
|
||||
'placeholder-password': true,
|
||||
'placeholder-id': id
|
||||
})
|
||||
.bind('focus.placeholder', clearPlaceholder);
|
||||
$input
|
||||
.data({
|
||||
'placeholder-textinput': $replacement,
|
||||
'placeholder-id': id
|
||||
})
|
||||
.before($replacement);
|
||||
}
|
||||
$input = $input.removeAttr('id').hide().prev().attr('id', id).show();
|
||||
// Note: `$input[0] != input` now!
|
||||
}
|
||||
$input.addClass('placeholder');
|
||||
$input[0].value = $input.attr('placeholder');
|
||||
} else {
|
||||
$input.removeClass('placeholder');
|
||||
}
|
||||
}
|
||||
|
||||
}(this, document, jQuery));
|
|
@ -355,6 +355,12 @@
|
|||
float: right;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.openerp .oe_text_center {
|
||||
text-align: center;
|
||||
}
|
||||
.openerp .oe_text_left {
|
||||
text-align: left;
|
||||
}
|
||||
.openerp .oe_text_right {
|
||||
text-align: right;
|
||||
}
|
||||
|
@ -1806,7 +1812,7 @@
|
|||
}
|
||||
.openerp .oe_searchview .oe_searchview_drawer {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
z-index: 2;
|
||||
margin-top: 4px;
|
||||
top: 100%;
|
||||
right: -1px;
|
||||
|
@ -2228,6 +2234,26 @@
|
|||
.openerp .oe_form .oe_subtotal_footer label.oe_form_label_help {
|
||||
font-weight: normal;
|
||||
}
|
||||
.openerp .oe_form .oe_form_box_info {
|
||||
background: #ffee99;
|
||||
border-bottom: 1px solid #ccbb66;
|
||||
padding: 4px;
|
||||
}
|
||||
.openerp .oe_form .oe_form_box_info > p {
|
||||
margin: auto;
|
||||
}
|
||||
.openerp .oe_form .oe_form_box_warning {
|
||||
background: #bd362f;
|
||||
border-bottom: 1px solid #990000;
|
||||
padding: 4px;
|
||||
}
|
||||
.openerp .oe_form .oe_form_box_warning * {
|
||||
color: white;
|
||||
text-shadow: none;
|
||||
}
|
||||
.openerp .oe_form .oe_form_box_warning > p {
|
||||
margin: auto;
|
||||
}
|
||||
.openerp .oe_form .oe_form_button {
|
||||
margin: 2px;
|
||||
}
|
||||
|
@ -2330,6 +2356,13 @@
|
|||
.openerp .oe_form .oe_form_field_text {
|
||||
width: 100%;
|
||||
}
|
||||
.openerp .oe_form .oe_form_field_text .oe_form_text_content {
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
white-space: pre-wrap;
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
.openerp .oe_form .oe_form_field_char input,
|
||||
.openerp .oe_form .oe_form_field_url input,
|
||||
.openerp .oe_form .oe_form_field_email input,
|
||||
|
@ -3187,6 +3220,14 @@ div.ui-widget-overlay {
|
|||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.openerp .db_option_table td {
|
||||
padding-bottom: 10px !important;
|
||||
}
|
||||
|
||||
.openerp_ie .placeholder {
|
||||
color: #afafb6 !important;
|
||||
font-style: italic !important;
|
||||
}
|
||||
.openerp_ie .oe_form_field_boolean input {
|
||||
background: white;
|
||||
}
|
||||
|
@ -3205,6 +3246,9 @@ div.ui-widget-overlay {
|
|||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.openerp_ie .oe_view_manager_view_kanban {
|
||||
display: table-cell;
|
||||
}
|
||||
.openerp_ie .oe_view_manager_buttons button.oe_write_full {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
|
@ -3353,7 +3397,6 @@ div.ui-widget-overlay {
|
|||
overflow: hidden !important;
|
||||
}
|
||||
}
|
||||
|
||||
.blockUI.blockOverlay {
|
||||
background-color: black;
|
||||
opacity: 0.6;
|
||||
|
|
|
@ -370,6 +370,10 @@ $sheet-padding: 16px
|
|||
.oe_right
|
||||
float: right
|
||||
margin-left: 8px
|
||||
.oe_text_center
|
||||
text-align: center
|
||||
.oe_text_left
|
||||
text-align: left
|
||||
.oe_text_right
|
||||
text-align: right
|
||||
.oe_clear
|
||||
|
@ -1453,7 +1457,7 @@ $sheet-padding: 16px
|
|||
|
||||
.oe_searchview_drawer
|
||||
position: absolute
|
||||
z-index: 100
|
||||
z-index: 2
|
||||
// detach drawer from field slightly
|
||||
margin-top: 4px
|
||||
top: 100%
|
||||
|
@ -1777,7 +1781,21 @@ $sheet-padding: 16px
|
|||
padding: 2px 11px 2px 0px !important
|
||||
label.oe_form_label_help
|
||||
font-weight: normal
|
||||
|
||||
.oe_form_box_info
|
||||
background: #fe9
|
||||
border-bottom: 1px solid #cb6
|
||||
padding: 4px
|
||||
> p
|
||||
margin: auto
|
||||
.oe_form_box_warning
|
||||
background: #bd362f
|
||||
border-bottom: 1px solid #900
|
||||
padding: 4px
|
||||
*
|
||||
color: white
|
||||
text-shadow: none
|
||||
> p
|
||||
margin: auto
|
||||
// }}}
|
||||
// FormView.group {{{
|
||||
.oe_form
|
||||
|
@ -1849,6 +1867,12 @@ $sheet-padding: 16px
|
|||
.oe_form
|
||||
.oe_form_field_text
|
||||
width: 100%
|
||||
.oe_form_text_content
|
||||
text-overflow: ellipsis
|
||||
display: inline-block
|
||||
white-space: pre-wrap
|
||||
overflow-x: hidden
|
||||
width: 100%
|
||||
.oe_form_field_char input,
|
||||
.oe_form_field_url input,
|
||||
.oe_form_field_email input,
|
||||
|
@ -2512,8 +2536,16 @@ div.ui-widget-overlay
|
|||
@include radius(3px)
|
||||
// }}}
|
||||
|
||||
.openerp
|
||||
.db_option_table
|
||||
td
|
||||
padding-bottom: 10px !important
|
||||
|
||||
// Internet Explorer 9+ specifics {{{
|
||||
.openerp_ie
|
||||
.placeholder
|
||||
color: $tag-border !important
|
||||
font-style: italic !important
|
||||
.oe_form_field_boolean input
|
||||
background: #fff
|
||||
.db_option_table .oe_form_field_selection
|
||||
|
@ -2529,6 +2561,8 @@ div.ui-widget-overlay
|
|||
button.oe_highlight
|
||||
padding-top: 0
|
||||
padding-bottom: 0
|
||||
.oe_view_manager_view_kanban
|
||||
display: table-cell
|
||||
.oe_view_manager_buttons
|
||||
button.oe_write_full
|
||||
padding-top: 0
|
||||
|
|
|
@ -530,7 +530,16 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
|
|||
'login': 'admin',
|
||||
'password': form_obj['create_admin_pwd'],
|
||||
'login_successful': function() {
|
||||
self.do_action("reload");
|
||||
var action = {
|
||||
type: "ir.actions.client",
|
||||
tag: 'reload',
|
||||
params: {
|
||||
url_search : {
|
||||
db: form_obj['db_name'],
|
||||
},
|
||||
}
|
||||
};
|
||||
self.do_action(action);
|
||||
},
|
||||
},
|
||||
_push_me: false,
|
||||
|
@ -641,6 +650,11 @@ instance.web.client_actions.add("database_manager", "instance.web.DatabaseManage
|
|||
instance.web.Login = instance.web.Widget.extend({
|
||||
template: "Login",
|
||||
remember_credentials: true,
|
||||
events: {
|
||||
'change input[name=db],select[name=db]': function(ev) {
|
||||
this.set('database_selector', $(ev.currentTarget).val());
|
||||
},
|
||||
},
|
||||
|
||||
init: function(parent, action) {
|
||||
this._super(parent);
|
||||
|
@ -652,18 +666,18 @@ instance.web.Login = instance.web.Widget.extend({
|
|||
if (_.isEmpty(this.params)) {
|
||||
this.params = $.bbq.getState(true);
|
||||
}
|
||||
if (action && action.params && action.params.db) {
|
||||
this.params.db = action.params.db;
|
||||
} else if ($.deparam.querystring().db) {
|
||||
this.params.db = $.deparam.querystring().db;
|
||||
}
|
||||
if (this.params.db) {
|
||||
this.selected_db = this.params.db;
|
||||
}
|
||||
|
||||
if (this.params.login_successful) {
|
||||
this.on('login_successful', this, this.params.login_successful);
|
||||
}
|
||||
|
||||
if (this.has_local_storage && this.remember_credentials) {
|
||||
this.selected_db = localStorage.getItem('last_db_login_success');
|
||||
this.selected_login = localStorage.getItem('last_login_login_success');
|
||||
if (jQuery.deparam(jQuery.param.querystring()).debug !== undefined) {
|
||||
this.selected_password = localStorage.getItem('last_password_login_success');
|
||||
}
|
||||
}
|
||||
},
|
||||
start: function() {
|
||||
var self = this;
|
||||
|
@ -671,10 +685,10 @@ instance.web.Login = instance.web.Widget.extend({
|
|||
self.$el.find('.oe_login_manage_db').click(function() {
|
||||
self.do_action("database_manager");
|
||||
});
|
||||
self.on('change:database_selector', this, function() {
|
||||
this.database_selected(this.get('database_selector'));
|
||||
});
|
||||
var d = $.when();
|
||||
if ($.deparam.querystring().db) {
|
||||
self.params.db = $.deparam.querystring().db;
|
||||
}
|
||||
if ($.param.fragment().token) {
|
||||
self.params.token = $.param.fragment().token;
|
||||
}
|
||||
|
@ -682,16 +696,44 @@ instance.web.Login = instance.web.Widget.extend({
|
|||
if (self.params.db && self.params.login && self.params.password) {
|
||||
d = self.do_login(self.params.db, self.params.login, self.params.password);
|
||||
} else {
|
||||
if (self.params.db) {
|
||||
self.on_db_loaded([self.params.db])
|
||||
} else {
|
||||
d = self.rpc("/web/database/get_list", {}).done(self.on_db_loaded).fail(self.on_db_failed);
|
||||
}
|
||||
d = self.rpc("/web/database/get_list", {})
|
||||
.done(self.on_db_loaded)
|
||||
.fail(self.on_db_failed)
|
||||
.always(function() {
|
||||
if (self.selected_db && self.has_local_storage && self.remember_credentials) {
|
||||
self.$("[name=login]").val(localStorage.getItem(self.selected_db + '|last_login') || '');
|
||||
if (self.session.debug) {
|
||||
self.$("[name=password]").val(localStorage.getItem(self.selected_db + '|last_password') || '');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return d;
|
||||
},
|
||||
remember_last_used_database: function(db) {
|
||||
// This cookie will be used server side in order to avoid db reloading on first visit
|
||||
var ttl = 24 * 60 * 60 * 365;
|
||||
document.cookie = [
|
||||
'last_used_database=' + db,
|
||||
'path=/',
|
||||
'max-age=' + ttl,
|
||||
'expires=' + new Date(new Date().getTime() + ttl * 1000).toGMTString()
|
||||
].join(';');
|
||||
},
|
||||
database_selected: function(db) {
|
||||
var params = $.deparam.querystring();
|
||||
params.db = db;
|
||||
this.remember_last_used_database(db);
|
||||
this.$('.oe_login_dbpane').empty().text(_t('Loading...'));
|
||||
this.$('[name=login], [name=password]').prop('readonly', true);
|
||||
instance.web.redirect('/?' + $.param(params));
|
||||
},
|
||||
on_db_loaded: function (result) {
|
||||
var self = this;
|
||||
this.db_list = result;
|
||||
if (!this.selected_db) {
|
||||
this.selected_db = result[0];
|
||||
}
|
||||
this.$("[name=db]").replaceWith(QWeb.render('Login.dblist', { db_list: this.db_list, selected_db: this.selected_db}));
|
||||
if(this.db_list.length === 0) {
|
||||
this.do_action("database_manager");
|
||||
|
@ -732,17 +774,11 @@ instance.web.Login = instance.web.Widget.extend({
|
|||
self.hide_error();
|
||||
self.$(".oe_login_pane").fadeOut("slow");
|
||||
return this.session.session_authenticate(db, login, password).then(function() {
|
||||
if (self.has_local_storage) {
|
||||
if(self.remember_credentials) {
|
||||
localStorage.setItem('last_db_login_success', db);
|
||||
localStorage.setItem('last_login_login_success', login);
|
||||
if (jQuery.deparam(jQuery.param.querystring()).debug !== undefined) {
|
||||
localStorage.setItem('last_password_login_success', password);
|
||||
}
|
||||
} else {
|
||||
localStorage.setItem('last_db_login_success', '');
|
||||
localStorage.setItem('last_login_login_success', '');
|
||||
localStorage.setItem('last_password_login_success', '');
|
||||
self.remember_last_used_database(db);
|
||||
if (self.has_local_storage && self.remember_credentials) {
|
||||
localStorage.setItem(db + '|last_login', login);
|
||||
if (self.session.debug) {
|
||||
localStorage.setItem(db + '|last_password', password);
|
||||
}
|
||||
}
|
||||
self.trigger('login_successful');
|
||||
|
@ -800,6 +836,9 @@ instance.web.Reload = function(parent, action) {
|
|||
|
||||
var sobj = $.deparam(l.search.substr(1));
|
||||
sobj.ts = new Date().getTime();
|
||||
if (params.url_search) {
|
||||
sobj = _.extend(sobj, params.url_search);
|
||||
}
|
||||
var search = '?' + $.param(sobj);
|
||||
|
||||
var hash = l.hash;
|
||||
|
@ -1303,7 +1342,7 @@ instance.web.WebClient = instance.web.Client.extend({
|
|||
},
|
||||
logo_edit: function(ev) {
|
||||
var self = this;
|
||||
new self.alive(instance.web.Model("res.users").get_func("read")(this.session.uid, ["company_id"])).then(function(res) {
|
||||
self.alive(new instance.web.Model("res.users").get_func("read")(this.session.uid, ["company_id"])).then(function(res) {
|
||||
self.rpc("/web/action/load", { action_id: "base.action_res_company_form" }).done(function(result) {
|
||||
result.res_id = res['company_id'][0];
|
||||
result.target = "new";
|
||||
|
@ -1500,8 +1539,9 @@ instance.web.EmbeddedClient = instance.web.Client.extend({
|
|||
});
|
||||
},
|
||||
|
||||
do_action: function(action) {
|
||||
return this.action_manager.do_action(action);
|
||||
do_action: function(/*...*/) {
|
||||
var am = this.action_manager;
|
||||
return am.do_action.apply(am, arguments);
|
||||
},
|
||||
|
||||
authenticate: function() {
|
||||
|
|
|
@ -232,13 +232,17 @@ instance.web.ParentedMixin = {
|
|||
Utility method to only execute asynchronous actions if the current
|
||||
object has not been destroyed.
|
||||
|
||||
@param {Promise} promise The promise representing the asynchronous action.
|
||||
@param {bool} reject Defaults to false. If true, the returned promise will be
|
||||
rejected with no arguments if the current object is destroyed. If false,
|
||||
the returned promise will never be resolved nor rejected.
|
||||
@returns {Promise} A promise that will mirror the given promise if everything goes
|
||||
fine but will either be rejected with no arguments or never resolved if the
|
||||
current object is destroyed.
|
||||
@param {$.Deferred} promise The promise representing the asynchronous
|
||||
action.
|
||||
@param {bool} [reject=false] If true, the returned promise will be
|
||||
rejected with no arguments if the current
|
||||
object is destroyed. If false, the
|
||||
returned promise will never be resolved
|
||||
or rejected.
|
||||
@returns {$.Deferred} A promise that will mirror the given promise if
|
||||
everything goes fine but will either be rejected
|
||||
with no arguments or never resolved if the
|
||||
current object is destroyed.
|
||||
*/
|
||||
alive: function(promise, reject) {
|
||||
var def = $.Deferred();
|
||||
|
@ -697,10 +701,10 @@ instance.web.Widget = instance.web.Controller.extend({
|
|||
* Method called after rendering. Mostly used to bind actions, perform asynchronous
|
||||
* calls, etc...
|
||||
*
|
||||
* By convention, the method should return a promise to inform the caller when
|
||||
* this widget has been initialized.
|
||||
* By convention, this method should return an object that can be passed to $.when()
|
||||
* to inform the caller when this widget has been initialized.
|
||||
*
|
||||
* @returns {jQuery.Deferred}
|
||||
* @returns {jQuery.Deferred or any}
|
||||
*/
|
||||
start: function() {
|
||||
return $.when();
|
||||
|
|
|
@ -89,6 +89,10 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
|
|||
});
|
||||
},
|
||||
session_is_valid: function() {
|
||||
var db = $.deparam.querystring().db;
|
||||
if (db && this.db !== db) {
|
||||
return false;
|
||||
}
|
||||
return !!this.uid;
|
||||
},
|
||||
/**
|
||||
|
|
|
@ -481,9 +481,12 @@ instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin,
|
|||
* Creates a new record in db
|
||||
*
|
||||
* @param {Object} data field values to set on the new record
|
||||
* @param {Object} options Dictionary that can contain the following keys:
|
||||
* - readonly_fields: Values from readonly fields that were updated by
|
||||
* on_changes. Only used by the BufferedDataSet to make the o2m work correctly.
|
||||
* @returns {$.Deferred}
|
||||
*/
|
||||
create: function(data) {
|
||||
create: function(data, options) {
|
||||
return this._model.call('create', [data], {context: this.get_context()});
|
||||
},
|
||||
/**
|
||||
|
@ -491,8 +494,10 @@ instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin,
|
|||
*
|
||||
* @param {Number|String} id identifier for the record to alter
|
||||
* @param {Object} data field values to write into the record
|
||||
* @param {Function} callback function called with operation result
|
||||
* @param {Function} error_callback function called in case of write error
|
||||
* @param {Object} options Dictionary that can contain the following keys:
|
||||
* - context: The context to use in the server-side call.
|
||||
* - readonly_fields: Values from readonly fields that were updated by
|
||||
* on_changes. Only used by the BufferedDataSet to make the o2m work correctly.
|
||||
* @returns {$.Deferred}
|
||||
*/
|
||||
write: function (id, data, options) {
|
||||
|
@ -732,10 +737,13 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
|
|||
self.last_default_get = res;
|
||||
});
|
||||
},
|
||||
create: function(data) {
|
||||
var cached = {id:_.uniqueId(this.virtual_id_prefix), values: data,
|
||||
defaults: this.last_default_get};
|
||||
this.to_create.push(_.extend(_.clone(cached), {values: _.clone(cached.values)}));
|
||||
create: function(data, options) {
|
||||
var cached = {
|
||||
id:_.uniqueId(this.virtual_id_prefix),
|
||||
values: _.extend({}, data, (options || {}).readonly_fields || {}),
|
||||
defaults: this.last_default_get
|
||||
};
|
||||
this.to_create.push(_.extend(_.clone(cached), {values: _.clone(data)}));
|
||||
this.cache.push(cached);
|
||||
return $.Deferred().resolve(cached.id).promise();
|
||||
},
|
||||
|
@ -762,7 +770,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
|
|||
cached = {id: id, values: {}};
|
||||
this.cache.push(cached);
|
||||
}
|
||||
$.extend(cached.values, record.values);
|
||||
$.extend(cached.values, _.extend({}, record.values, (options || {}).readonly_fields || {}));
|
||||
if (dirty)
|
||||
this.trigger("dataset_changed", id, data, options);
|
||||
return $.Deferred().resolve(true).promise();
|
||||
|
@ -860,8 +868,16 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
|
|||
}
|
||||
return completion.promise();
|
||||
},
|
||||
call_button: function (method, args) {
|
||||
var id = args[0][0], index;
|
||||
/**
|
||||
* Invalidates caching of a record in the dataset to ensure the next read
|
||||
* of that record will hit the server.
|
||||
*
|
||||
* Of use when an action is going to remote-alter a record which will then
|
||||
* need to be reloaded, e.g. action button.
|
||||
*
|
||||
* @param {Object} id record to remove from the BDS's cache
|
||||
*/
|
||||
evict_record: function (id) {
|
||||
for(var i=0, len=this.cache.length; i<len; ++i) {
|
||||
var record = this.cache[i];
|
||||
// if record we call the button upon is in the cache
|
||||
|
@ -871,8 +887,15 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
|
|||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
call_button: function (method, args) {
|
||||
this.evict_record(args[0][0]);
|
||||
return this._super(method, args);
|
||||
},
|
||||
exec_workflow: function (id, signal) {
|
||||
this.evict_record(id);
|
||||
return this._super(id, signal);
|
||||
},
|
||||
alter_ids: function(n_ids) {
|
||||
this._super(n_ids);
|
||||
this.trigger("dataset_changed", n_ids);
|
||||
|
@ -903,9 +926,9 @@ instance.web.ProxyDataSet = instance.web.DataSetSearch.extend({
|
|||
return this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
create: function(data) {
|
||||
create: function(data, options) {
|
||||
if (this.create_function) {
|
||||
return this.create_function(data, this._super);
|
||||
return this.create_function(data, options, this._super);
|
||||
} else {
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
|
@ -946,7 +969,10 @@ instance.web.CompoundContext = instance.web.Class.extend({
|
|||
},
|
||||
get_eval_context: function () {
|
||||
return this.__eval_context;
|
||||
}
|
||||
},
|
||||
eval: function() {
|
||||
return instance.web.pyeval.eval('context', this, undefined, {no_user_context: true});
|
||||
},
|
||||
});
|
||||
|
||||
instance.web.CompoundDomain = instance.web.Class.extend({
|
||||
|
@ -969,7 +995,10 @@ instance.web.CompoundDomain = instance.web.Class.extend({
|
|||
},
|
||||
get_eval_context: function() {
|
||||
return this.__eval_context;
|
||||
}
|
||||
},
|
||||
eval: function() {
|
||||
return instance.web.pyeval.eval('domain', this);
|
||||
},
|
||||
});
|
||||
|
||||
instance.web.DropMisordered = instance.web.Class.extend({
|
||||
|
|
|
@ -502,12 +502,15 @@ openerp.web.pyeval = function (instance) {
|
|||
return py.PY_call(py.PY_getAttr(d, 'strftime'), [args.format]);
|
||||
});
|
||||
|
||||
var args = _.map(('year month day hour minute second microsecond '
|
||||
+ 'years months weeks days hours minutes secondes microseconds '
|
||||
+ 'weekday leakdays yearday nlyearday').split(' '), function (arg) {
|
||||
return [arg, null]
|
||||
});
|
||||
args.unshift('*');
|
||||
var relativedelta = py.type('relativedelta', null, {
|
||||
__init__: function () {
|
||||
this.ops = py.PY_parseArgs(arguments,
|
||||
'* year month day hour minute second microsecond '
|
||||
+ 'years months weeks days hours minutes secondes microseconds '
|
||||
+ 'weekday leakdays yearday nlyearday');
|
||||
this.ops = py.PY_parseArgs(arguments, args);
|
||||
},
|
||||
__add__: function (other) {
|
||||
if (!py.PY_isInstance(other, datetime.date)) {
|
||||
|
@ -600,7 +603,7 @@ openerp.web.pyeval = function (instance) {
|
|||
});
|
||||
|
||||
var eval_contexts = function (contexts, evaluation_context) {
|
||||
evaluation_context = evaluation_context || {};
|
||||
evaluation_context = _.extend(instance.web.pyeval.context(), evaluation_context || {});
|
||||
return _(contexts).reduce(function (result_context, ctx) {
|
||||
// __eval_context evaluations can lead to some of `contexts`'s
|
||||
// values being null, skip them as well as empty contexts
|
||||
|
@ -625,9 +628,10 @@ openerp.web.pyeval = function (instance) {
|
|||
// siblings
|
||||
_.extend(evaluation_context, evaluated);
|
||||
return _.extend(result_context, evaluated);
|
||||
}, _.extend({}, instance.session.user_context));
|
||||
}, {});
|
||||
};
|
||||
var eval_domains = function (domains, evaluation_context) {
|
||||
evaluation_context = _.extend(instance.web.pyeval.context(), evaluation_context || {});
|
||||
var result_domain = [];
|
||||
_(domains).each(function (domain) {
|
||||
if (_.isString(domain)) {
|
||||
|
@ -654,6 +658,7 @@ openerp.web.pyeval = function (instance) {
|
|||
return result_domain;
|
||||
};
|
||||
var eval_groupbys = function (contexts, evaluation_context) {
|
||||
evaluation_context = _.extend(instance.web.pyeval.context(), evaluation_context || {});
|
||||
var result_group = [];
|
||||
_(contexts).each(function (ctx) {
|
||||
if (_.isString(ctx)) {
|
||||
|
@ -704,14 +709,15 @@ openerp.web.pyeval = function (instance) {
|
|||
* @param {Array} object domains or contexts to evaluate
|
||||
* @param {Object} [context] evaluation context
|
||||
*/
|
||||
instance.web.pyeval.eval = function (type, object, context) {
|
||||
instance.web.pyeval.eval = function (type, object, context, options) {
|
||||
options = options || {};
|
||||
context = _.extend(instance.web.pyeval.context(), context || {});
|
||||
context['context'] = py.dict.fromJSON(context);
|
||||
|
||||
//noinspection FallthroughInSwitchStatementJS
|
||||
switch(type) {
|
||||
case 'context': object = [object];
|
||||
case 'contexts': return eval_contexts(object, context);
|
||||
case 'contexts': return eval_contexts((options.no_user_context ? [] : [instance.session.user_context]).concat(object), context);
|
||||
case 'domain': object = [object];
|
||||
case 'domains': return eval_domains(object, context);
|
||||
case 'groupbys': return eval_groupbys(object, context);
|
||||
|
|
|
@ -324,7 +324,10 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
'autocompleteopen': function () {
|
||||
this.$el.autocomplete('widget').css('z-index', 3);
|
||||
},
|
||||
},
|
||||
/**
|
||||
* @constructs instance.web.SearchView
|
||||
|
@ -352,7 +355,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
this.has_defaults = !_.isEmpty(this.defaults);
|
||||
|
||||
this.inputs = [];
|
||||
this.controls = {};
|
||||
this.controls = [];
|
||||
|
||||
this.headless = this.options.hidden && !this.has_defaults;
|
||||
|
||||
|
@ -379,6 +382,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
model: this.dataset._model,
|
||||
view_id: this.view_id,
|
||||
view_type: 'search',
|
||||
context: this.dataset.get_context(),
|
||||
});
|
||||
|
||||
$.when(load_view).then(function (r) {
|
||||
|
@ -492,6 +496,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
*/
|
||||
complete_global_search: function (req, resp) {
|
||||
$.when.apply(null, _(this.inputs).chain()
|
||||
.filter(function (input) { return input.visible(); })
|
||||
.invoke('complete', req.term)
|
||||
.value()).then(function () {
|
||||
resp(_(_(arguments).compact()).flatten(true));
|
||||
|
@ -580,18 +585,18 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
*
|
||||
* @param {Array} items a list of nodes to convert to widgets
|
||||
* @param {Object} fields a mapping of field names to (ORM) field attributes
|
||||
* @param {String} [group_name] name of the group to put the new controls in
|
||||
* @param {Object} [group] group to put the new controls in
|
||||
*/
|
||||
make_widgets: function (items, fields, group_name) {
|
||||
group_name = group_name || null;
|
||||
if (!(group_name in this.controls)) {
|
||||
this.controls[group_name] = [];
|
||||
make_widgets: function (items, fields, group) {
|
||||
if (!group) {
|
||||
group = new instance.web.search.Group(
|
||||
this, 'q', {attrs: {string: _t("Filters")}});
|
||||
}
|
||||
var self = this, group = this.controls[group_name];
|
||||
var self = this;
|
||||
var filters = [];
|
||||
_.each(items, function (item) {
|
||||
if (filters.length && item.tag !== 'filter') {
|
||||
group.push(new instance.web.search.FilterGroup(filters, this));
|
||||
group.push(new instance.web.search.FilterGroup(filters, group));
|
||||
filters = [];
|
||||
}
|
||||
|
||||
|
@ -599,15 +604,18 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
case 'separator': case 'newline':
|
||||
break;
|
||||
case 'filter':
|
||||
filters.push(new instance.web.search.Filter(item, this));
|
||||
filters.push(new instance.web.search.Filter(item, group));
|
||||
break;
|
||||
case 'group':
|
||||
self.make_widgets(item.children, fields, item.attrs.string);
|
||||
self.make_widgets(item.children, fields,
|
||||
new instance.web.search.Group(group, 'w', item));
|
||||
break;
|
||||
case 'field':
|
||||
group.push(this.make_field(item, fields[item['attrs'].name]));
|
||||
var field = this.make_field(
|
||||
item, fields[item['attrs'].name], group);
|
||||
group.push(field);
|
||||
// filters
|
||||
self.make_widgets(item.children, fields, group_name);
|
||||
self.make_widgets(item.children, fields, group);
|
||||
break;
|
||||
}
|
||||
}, this);
|
||||
|
@ -622,12 +630,13 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
*
|
||||
* @param {Object} item fields_view_get node for the field
|
||||
* @param {Object} field fields_get result for the field
|
||||
* @param {Object} [parent]
|
||||
* @returns instance.web.search.Field
|
||||
*/
|
||||
make_field: function (item, field) {
|
||||
make_field: function (item, field, parent) {
|
||||
var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]);
|
||||
if(obj) {
|
||||
return new (obj) (item, field, this);
|
||||
return new (obj) (item, field, parent || this);
|
||||
} else {
|
||||
console.group('Unknown field type ' + field.type);
|
||||
console.error('View node', item);
|
||||
|
@ -862,13 +871,18 @@ instance.web.search.Widget = instance.web.Widget.extend( /** @lends instance.web
|
|||
* @constructs instance.web.search.Widget
|
||||
* @extends instance.web.Widget
|
||||
*
|
||||
* @param view the ancestor view of this widget
|
||||
* @param parent parent of this widget
|
||||
*/
|
||||
init: function (view) {
|
||||
this._super(view);
|
||||
this.view = view;
|
||||
init: function (parent) {
|
||||
this._super(parent);
|
||||
var ancestor = parent;
|
||||
do {
|
||||
this.view = ancestor;
|
||||
} while (!(ancestor instanceof instance.web.SearchView)
|
||||
&& (ancestor = (ancestor.getParent && ancestor.getParent())));
|
||||
}
|
||||
});
|
||||
|
||||
instance.web.search.add_expand_listener = function($root) {
|
||||
$root.find('a.searchview_group_string').click(function (e) {
|
||||
$root.toggleClass('folded expanded');
|
||||
|
@ -877,13 +891,24 @@ instance.web.search.add_expand_listener = function($root) {
|
|||
});
|
||||
};
|
||||
instance.web.search.Group = instance.web.search.Widget.extend({
|
||||
template: 'SearchView.group',
|
||||
init: function (view_section, view, fields) {
|
||||
this._super(view);
|
||||
this.attrs = view_section.attrs;
|
||||
this.lines = view.make_widgets(
|
||||
view_section.children, fields);
|
||||
}
|
||||
init: function (parent, icon, node) {
|
||||
this._super(parent);
|
||||
var attrs = node.attrs;
|
||||
this.modifiers = attrs.modifiers =
|
||||
attrs.modifiers ? JSON.parse(attrs.modifiers) : {};
|
||||
this.attrs = attrs;
|
||||
this.icon = icon;
|
||||
this.name = attrs.string;
|
||||
this.children = [];
|
||||
|
||||
this.view.controls.push(this);
|
||||
},
|
||||
push: function (input) {
|
||||
this.children.push(input);
|
||||
},
|
||||
visible: function () {
|
||||
return !this.modifiers.invisible;
|
||||
},
|
||||
});
|
||||
|
||||
instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instance.web.search.Input# */{
|
||||
|
@ -892,12 +917,12 @@ instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instan
|
|||
* @constructs instance.web.search.Input
|
||||
* @extends instance.web.search.Widget
|
||||
*
|
||||
* @param view
|
||||
* @param parent
|
||||
*/
|
||||
init: function (view) {
|
||||
this._super(view);
|
||||
init: function (parent) {
|
||||
this._super(parent);
|
||||
this.load_attrs({});
|
||||
this.view.inputs.push(this);
|
||||
this.style = undefined;
|
||||
},
|
||||
/**
|
||||
* Fetch auto-completion values for the widget.
|
||||
|
@ -945,15 +970,30 @@ instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instan
|
|||
"get_domain not implemented for widget " + this.attrs.type);
|
||||
},
|
||||
load_attrs: function (attrs) {
|
||||
if (attrs.modifiers) {
|
||||
attrs.modifiers = JSON.parse(attrs.modifiers);
|
||||
attrs.invisible = attrs.modifiers.invisible || false;
|
||||
if (attrs.invisible) {
|
||||
this.style = 'display: none;'
|
||||
attrs.modifiers = attrs.modifiers ? JSON.parse(attrs.modifiers) : {};
|
||||
this.attrs = attrs;
|
||||
},
|
||||
/**
|
||||
* Returns whether the input is "visible". The default behavior is to
|
||||
* query the ``modifiers.invisible`` flag on the input's description or
|
||||
* view node.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
visible: function () {
|
||||
if (this.attrs.modifiers.invisible) {
|
||||
return false;
|
||||
}
|
||||
var parent = this;
|
||||
while ((parent = parent.getParent()) &&
|
||||
( (parent instanceof instance.web.search.Group)
|
||||
|| (parent instanceof instance.web.search.Input))) {
|
||||
if (!parent.visible()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.attrs = attrs;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
});
|
||||
instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends instance.web.search.FilterGroup# */{
|
||||
template: 'SearchView.filters',
|
||||
|
@ -967,17 +1007,19 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
|
|||
* @extends instance.web.search.Input
|
||||
*
|
||||
* @param {Array<instance.web.search.Filter>} filters elements of the group
|
||||
* @param {instance.web.SearchView} view view in which the filters are contained
|
||||
* @param {instance.web.SearchView} parent parent in which the filters are contained
|
||||
*/
|
||||
init: function (filters, view) {
|
||||
init: function (filters, parent) {
|
||||
// If all filters are group_by and we're not initializing a GroupbyGroup,
|
||||
// create a GroupbyGroup instead of the current FilterGroup
|
||||
if (!(this instanceof instance.web.search.GroupbyGroup) &&
|
||||
_(filters).all(function (f) {
|
||||
return f.attrs.context && f.attrs.context.group_by; })) {
|
||||
return new instance.web.search.GroupbyGroup(filters, view);
|
||||
if (!f.attrs.context) { return false; }
|
||||
var c = instance.web.pyeval.eval('context', f.attrs.context);
|
||||
return !_.isEmpty(c.group_by);})) {
|
||||
return new instance.web.search.GroupbyGroup(filters, parent);
|
||||
}
|
||||
this._super(view);
|
||||
this._super(parent);
|
||||
this.filters = filters;
|
||||
this.view.query.on('add remove change reset', this.proxy('search_change'));
|
||||
},
|
||||
|
@ -1096,6 +1138,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
|
|||
var self = this;
|
||||
item = item.toLowerCase();
|
||||
var facet_values = _(this.filters).chain()
|
||||
.filter(function (filter) { return filter.visible(); })
|
||||
.filter(function (filter) {
|
||||
var at = {
|
||||
string: filter.attrs.string || '',
|
||||
|
@ -1122,8 +1165,8 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
|
|||
instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({
|
||||
icon: 'w',
|
||||
completion_label: _lt("Group by: %s"),
|
||||
init: function (filters, view) {
|
||||
this._super(filters, view);
|
||||
init: function (filters, parent) {
|
||||
this._super(filters, parent);
|
||||
// Not flanders: facet unicity is handled through the
|
||||
// (category, field) pair of facet attributes. This is all well and
|
||||
// good for regular filter groups where a group matches a facet, but for
|
||||
|
@ -1131,8 +1174,8 @@ instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({
|
|||
// view which proxies to the first GroupbyGroup, so it can be used
|
||||
// for every GroupbyGroup and still provides the various methods needed
|
||||
// by the search view. Use weirdo name to avoid risks of conflicts
|
||||
if (!this.getParent()._s_groupby) {
|
||||
this.getParent()._s_groupby = {
|
||||
if (!this.view._s_groupby) {
|
||||
this.view._s_groupby = {
|
||||
help: "See GroupbyGroup#init",
|
||||
get_context: this.proxy('get_context'),
|
||||
get_domain: this.proxy('get_domain'),
|
||||
|
@ -1141,14 +1184,14 @@ instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({
|
|||
}
|
||||
},
|
||||
match_facet: function (facet) {
|
||||
return facet.get('field') === this.getParent()._s_groupby;
|
||||
return facet.get('field') === this.view._s_groupby;
|
||||
},
|
||||
make_facet: function (values) {
|
||||
return {
|
||||
category: _t("GroupBy"),
|
||||
icon: this.icon,
|
||||
values: values,
|
||||
field: this.getParent()._s_groupby
|
||||
field: this.view._s_groupby
|
||||
};
|
||||
}
|
||||
});
|
||||
|
@ -1166,10 +1209,10 @@ instance.web.search.Filter = instance.web.search.Input.extend(/** @lends instanc
|
|||
* @extends instance.web.search.Input
|
||||
*
|
||||
* @param node
|
||||
* @param view
|
||||
* @param parent
|
||||
*/
|
||||
init: function (node, view) {
|
||||
this._super(view);
|
||||
init: function (node, parent) {
|
||||
this._super(parent);
|
||||
this.load_attrs(node.attrs);
|
||||
},
|
||||
facet_for: function () { return $.when(null); },
|
||||
|
@ -1185,10 +1228,10 @@ instance.web.search.Field = instance.web.search.Input.extend( /** @lends instanc
|
|||
*
|
||||
* @param view_section
|
||||
* @param field
|
||||
* @param view
|
||||
* @param parent
|
||||
*/
|
||||
init: function (view_section, field, view) {
|
||||
this._super(view);
|
||||
init: function (view_section, field, parent) {
|
||||
this._super(parent);
|
||||
this.load_attrs(_.extend({}, field, view_section.attrs));
|
||||
},
|
||||
facet_for: function (value) {
|
||||
|
@ -1228,7 +1271,7 @@ instance.web.search.Field = instance.web.search.Input.extend( /** @lends instanc
|
|||
*
|
||||
* @param {String} name the field's name
|
||||
* @param {String} operator the field's operator (either attribute-specified or default operator for the field
|
||||
* @param {Number|String} value parsed value for the field
|
||||
* @param {Number|String} facet parsed value for the field
|
||||
* @returns {Array<Array>} domain to include in the resulting search
|
||||
*/
|
||||
make_domain: function (name, operator, facet) {
|
||||
|
@ -1460,8 +1503,8 @@ instance.web.search.DateTimeField = instance.web.search.DateField.extend(/** @le
|
|||
});
|
||||
instance.web.search.ManyToOneField = instance.web.search.CharField.extend({
|
||||
default_operator: {},
|
||||
init: function (view_section, field, view) {
|
||||
this._super(view_section, field, view);
|
||||
init: function (view_section, field, parent) {
|
||||
this._super(view_section, field, parent);
|
||||
this.model = new instance.web.Model(this.attrs.relation);
|
||||
},
|
||||
complete: function (needle) {
|
||||
|
@ -1673,6 +1716,13 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
|
|||
if (!_.isEmpty(results.group_by)) {
|
||||
results.context.group_by = results.group_by;
|
||||
}
|
||||
// Don't save user_context keys in the custom filter, otherwise end
|
||||
// up with e.g. wrong uid or lang stored *and used in subsequent
|
||||
// reqs*
|
||||
var ctx = results.context;
|
||||
_(_.keys(instance.session.user_context)).each(function (key) {
|
||||
delete ctx[key];
|
||||
});
|
||||
var filter = {
|
||||
name: $name.val(),
|
||||
user_id: private_filter ? instance.session.uid : false,
|
||||
|
@ -1702,22 +1752,28 @@ instance.web.search.Filters = instance.web.search.Input.extend({
|
|||
var running_count = 0;
|
||||
// get total filters count
|
||||
var is_group = function (i) { return i instanceof instance.web.search.FilterGroup; };
|
||||
var filters_count = _(this.view.controls).chain()
|
||||
var visible_filters = _(this.view.controls).chain().reject(function (group) {
|
||||
return _(_(group.children).filter(is_group)).isEmpty()
|
||||
|| group.modifiers.invisible;
|
||||
});
|
||||
var filters_count = visible_filters
|
||||
.pluck('children')
|
||||
.flatten()
|
||||
.filter(is_group)
|
||||
.map(function (i) { return i.filters.length; })
|
||||
.sum()
|
||||
.value();
|
||||
|
||||
var col1 = [], col2 = _(this.view.controls).map(function (inputs, group) {
|
||||
var filters = _(inputs).filter(is_group);
|
||||
return {
|
||||
name: group === 'null' ? "<span class='oe_i'>q</span> " + _t("Filters") : "<span class='oe_i'>w</span> " + group,
|
||||
filters: filters,
|
||||
length: _(filters).chain().map(function (i) {
|
||||
return i.filters.length; }).sum().value()
|
||||
};
|
||||
});
|
||||
var col1 = [], col2 = visible_filters.map(function (group) {
|
||||
var filters = _(group.children).filter(is_group);
|
||||
return {
|
||||
name: _.str.sprintf("<span class='oe_i'>%s</span> %s",
|
||||
group.icon, group.name),
|
||||
filters: filters,
|
||||
length: _(filters).chain().map(function (i) {
|
||||
return i.filters.length; }).sum().value()
|
||||
};
|
||||
}).value();
|
||||
|
||||
while (col2.length) {
|
||||
// col1 + group should be smaller than col2 + group
|
||||
|
|
|
@ -810,7 +810,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
try {
|
||||
var form_invalid = false,
|
||||
values = {},
|
||||
first_invalid_field = null;
|
||||
first_invalid_field = null,
|
||||
readonly_values = {};
|
||||
for (var f in self.fields) {
|
||||
if (!self.fields.hasOwnProperty(f)) { continue; }
|
||||
f = self.fields[f];
|
||||
|
@ -819,11 +820,15 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
if (!first_invalid_field) {
|
||||
first_invalid_field = f;
|
||||
}
|
||||
} else if (f.name !== 'id' && !f.get("readonly") && (!self.datarecord.id || f._dirty_flag)) {
|
||||
} else if (f.name !== 'id' && (!self.datarecord.id || f._dirty_flag)) {
|
||||
// Special case 'id' field, do not save this field
|
||||
// on 'create' : save all non readonly fields
|
||||
// on 'edit' : save non readonly modified fields
|
||||
values[f.name] = f.get_value();
|
||||
if (!f.get("readonly")) {
|
||||
values[f.name] = f.get_value();
|
||||
} else {
|
||||
readonly_values[f.name] = f.get_value();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (form_invalid) {
|
||||
|
@ -836,7 +841,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
var save_deferral;
|
||||
if (!self.datarecord.id) {
|
||||
// Creation save
|
||||
save_deferral = self.dataset.create(values).then(function(r) {
|
||||
save_deferral = self.dataset.create(values, {readonly_fields: readonly_values}).then(function(r) {
|
||||
return self.record_created(r, prepend_on_create);
|
||||
}, null);
|
||||
} else if (_.isEmpty(values)) {
|
||||
|
@ -844,7 +849,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
save_deferral = $.Deferred().resolve({}).promise();
|
||||
} else {
|
||||
// Write save
|
||||
save_deferral = self.dataset.write(self.datarecord.id, values, {}).then(function(r) {
|
||||
save_deferral = self.dataset.write(self.datarecord.id, values, {readonly_fields: readonly_values}).then(function(r) {
|
||||
return self.record_saved(r);
|
||||
}, null);
|
||||
}
|
||||
|
@ -1887,10 +1892,11 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
|
|||
if (this.node.attrs.icon && (! /\//.test(this.node.attrs.icon))) {
|
||||
this.node.attrs.icon = '/web/static/src/img/icons/' + this.node.attrs.icon + '.png';
|
||||
}
|
||||
this.view.on('view_content_has_changed', this, this.check_disable);
|
||||
},
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.view.on('view_content_has_changed', this, this.check_disable);
|
||||
this.check_disable();
|
||||
this.$el.click(this.on_click);
|
||||
if (this.node.attrs.help || instance.session.debug) {
|
||||
this.do_attach_tooltip();
|
||||
|
@ -1916,18 +1922,20 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
|
|||
modal: true,
|
||||
buttons: [
|
||||
{text: _t("Cancel"), click: function() {
|
||||
def.resolve();
|
||||
$(this).dialog("close");
|
||||
}
|
||||
},
|
||||
{text: _t("Ok"), click: function() {
|
||||
self.on_confirmed().done(function() {
|
||||
def.resolve();
|
||||
var self2 = this;
|
||||
self.on_confirmed().always(function() {
|
||||
$(self2).dialog("close");
|
||||
});
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
beforeClose: function() {
|
||||
def.resolve();
|
||||
},
|
||||
});
|
||||
return def.promise();
|
||||
} else {
|
||||
|
@ -2218,6 +2226,18 @@ instance.web.form.ReinitializeFieldMixin = _.extend({}, instance.web.form.Reini
|
|||
},
|
||||
});
|
||||
|
||||
/**
|
||||
Some hack to make placeholders work in ie9.
|
||||
*/
|
||||
if ($.browser.msie && $.browser.version === "9.0") {
|
||||
document.addEventListener("DOMNodeInserted",function(event){
|
||||
var nodename = event.target.nodeName.toLowerCase();
|
||||
if ( nodename === "input" || nodename == "textarea" ) {
|
||||
$(event.target).placeholder();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
|
||||
template: 'FieldChar',
|
||||
widget_class: 'oe_form_field_char',
|
||||
|
@ -2337,7 +2357,8 @@ instance.web.form.FieldUrl = instance.web.form.FieldChar.extend({
|
|||
if (!s) {
|
||||
tmp = "http://" + this.get('value');
|
||||
}
|
||||
this.$el.find('a').attr('href', tmp).text(this.get('value') ? tmp : '');
|
||||
var text = this.get('value') ? this.node.attrs.text || tmp : '';
|
||||
this.$el.find('a').attr('href', tmp).text(text);
|
||||
}
|
||||
},
|
||||
on_button_clicked: function() {
|
||||
|
@ -2382,7 +2403,6 @@ instance.web.DateTimeWidget = instance.web.Widget.extend({
|
|||
type_of_date: "datetime",
|
||||
events: {
|
||||
'change .oe_datepicker_master': 'change_datetime',
|
||||
'dragstart img.oe_datepicker_trigger': function () { return false; },
|
||||
},
|
||||
init: function(parent) {
|
||||
this._super(parent);
|
||||
|
@ -2546,42 +2566,45 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.we
|
|||
},
|
||||
'change textarea': 'store_dom_value',
|
||||
},
|
||||
init: function (field_manager, node) {
|
||||
this._super(field_manager, node);
|
||||
},
|
||||
initialize_content: function() {
|
||||
var self = this;
|
||||
this.$textarea = this.$el.find('textarea');
|
||||
this.auto_sized = false;
|
||||
this.default_height = this.$textarea.css('height');
|
||||
if (this.get("effective_readonly")) {
|
||||
this.$textarea.attr('disabled', 'disabled');
|
||||
if (! this.get("effective_readonly")) {
|
||||
this.$textarea = this.$el.find('textarea');
|
||||
this.auto_sized = false;
|
||||
this.default_height = this.$textarea.css('height');
|
||||
if (this.get("effective_readonly")) {
|
||||
this.$textarea.attr('disabled', 'disabled');
|
||||
}
|
||||
this.setupFocus(this.$textarea);
|
||||
} else {
|
||||
this.$textarea = undefined;
|
||||
}
|
||||
this.setupFocus(this.$textarea);
|
||||
},
|
||||
commit_value: function () {
|
||||
this.store_dom_value();
|
||||
if (! this.get("effective_readonly") && this.$textarea) {
|
||||
this.store_dom_value();
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
store_dom_value: function () {
|
||||
if (!this.get('effective_readonly') && this.$('textarea').length) {
|
||||
this.internal_set_value(
|
||||
instance.web.parse_value(
|
||||
this.$textarea.val(),
|
||||
this));
|
||||
}
|
||||
this.internal_set_value(instance.web.parse_value(this.$textarea.val(), this));
|
||||
},
|
||||
render_value: function() {
|
||||
var show_value = instance.web.format_value(this.get('value'), this, '');
|
||||
if (show_value === '') {
|
||||
this.$textarea.css('height', parseInt(this.default_height)+"px");
|
||||
}
|
||||
this.$textarea.val(show_value);
|
||||
if (! this.auto_sized) {
|
||||
this.auto_sized = true;
|
||||
this.$textarea.autosize();
|
||||
if (! this.get("effective_readonly")) {
|
||||
var show_value = instance.web.format_value(this.get('value'), this, '');
|
||||
if (show_value === '') {
|
||||
this.$textarea.css('height', parseInt(this.default_height)+"px");
|
||||
}
|
||||
this.$textarea.val(show_value);
|
||||
if (! this.auto_sized) {
|
||||
this.auto_sized = true;
|
||||
this.$textarea.autosize();
|
||||
} else {
|
||||
this.$textarea.trigger("autosize");
|
||||
}
|
||||
} else {
|
||||
this.$textarea.trigger("autosize");
|
||||
var txt = this.get("value") || '';
|
||||
this.$(".oe_form_text_content").text(txt);
|
||||
}
|
||||
},
|
||||
is_syntax_valid: function() {
|
||||
|
@ -2599,14 +2622,18 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.we
|
|||
return this.get('value') === '' || this._super();
|
||||
},
|
||||
focus: function($el) {
|
||||
this.$textarea[0].focus();
|
||||
if (!this.get("effective_readonly") && this.$textarea) {
|
||||
this.$textarea[0].focus();
|
||||
}
|
||||
},
|
||||
set_dimensions: function (height, width) {
|
||||
this._super(height, width);
|
||||
this.$textarea.css({
|
||||
width: width,
|
||||
minHeight: height
|
||||
});
|
||||
if (!this.get("effective_readonly") && this.$textarea) {
|
||||
this.$textarea.css({
|
||||
width: width,
|
||||
minHeight: height
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -2637,7 +2664,7 @@ instance.web.form.FieldTextHtml = instance.web.form.AbstractField.extend(instanc
|
|||
"| removeformat | bullets numbering | outdent " +
|
||||
"indent | link unlink | source",
|
||||
bodyStyle: // style to assign to document body contained within the editor
|
||||
"margin:4px; color:#4c4c4c; font-size:13px; font-family:\"Lucida Grande\",Helvetica,Verdana,Arial,sans-serif; cursor:text"
|
||||
"margin:4px; color:#4c4c4c; font-size:13px; font-family:'Lucida Grande',Helvetica,Verdana,Arial,sans-serif; cursor:text"
|
||||
});
|
||||
this.$cleditor = this.$textarea.cleditor()[0];
|
||||
this.$cleditor.change(function() {
|
||||
|
@ -3004,8 +3031,6 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
|
|||
e.stopPropagation();
|
||||
}
|
||||
},
|
||||
'dragstart .oe_m2o_drop_down_button img': function () { return false; },
|
||||
'dragstart .oe_m2o_cm_button': function () { return false; }
|
||||
},
|
||||
init: function(field_manager, node) {
|
||||
this._super(field_manager, node);
|
||||
|
@ -3036,6 +3061,26 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
|
|||
if (!this.get("effective_readonly"))
|
||||
this.render_editable();
|
||||
},
|
||||
destroy_content: function () {
|
||||
if (this.$drop_down) {
|
||||
this.$drop_down.off('click');
|
||||
delete this.$drop_down;
|
||||
}
|
||||
if (this.$input) {
|
||||
this.$input.closest(".ui-dialog .ui-dialog-content").off('scroll');
|
||||
this.$input.off('keyup blur autocompleteclose autocompleteopen ' +
|
||||
'focus focusout change keydown');
|
||||
delete this.$input;
|
||||
}
|
||||
if (this.$follow_button) {
|
||||
this.$follow_button.off('blur focus click');
|
||||
delete this.$follow_button;
|
||||
}
|
||||
},
|
||||
destroy: function () {
|
||||
this.destroy_content();
|
||||
return this._super();
|
||||
},
|
||||
init_error_displayer: function() {
|
||||
// nothing
|
||||
},
|
||||
|
@ -3266,7 +3311,8 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
|
|||
res_model: self.field.relation,
|
||||
res_id: self.get("value"),
|
||||
views: [[false, 'form']],
|
||||
target: 'current'
|
||||
target: 'current',
|
||||
context: self.build_context().eval(),
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
@ -3297,7 +3343,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
|
|||
},
|
||||
focus: function () {
|
||||
if (!this.get('effective_readonly')) {
|
||||
this.$input[0].focus();
|
||||
this.$input && this.$input[0].focus();
|
||||
}
|
||||
},
|
||||
_quick_create: function() {
|
||||
|
@ -3716,8 +3762,8 @@ instance.web.form.One2ManyViewManager = instance.web.ViewManager.extend({
|
|||
var pop = new instance.web.form.FormOpenPopup(this);
|
||||
pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), {
|
||||
title: _t("Open: ") + self.o2m.string,
|
||||
create_function: function(data) {
|
||||
return self.o2m.dataset.create(data).done(function(r) {
|
||||
create_function: function(data, options) {
|
||||
return self.o2m.dataset.create(data, options).done(function(r) {
|
||||
self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r]));
|
||||
self.o2m.dataset.trigger("dataset_changed", r);
|
||||
});
|
||||
|
@ -3776,8 +3822,12 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
|
|||
this.o2m.trigger_on_change();
|
||||
},
|
||||
is_valid: function () {
|
||||
var form = this.editor.form;
|
||||
|
||||
var editor = this.editor;
|
||||
var form = editor.form;
|
||||
// If no edition is pending, the listview can not be invalid (?)
|
||||
if (!editor.record) {
|
||||
return true
|
||||
}
|
||||
// If the form has not been modified, the view can only be valid
|
||||
// NB: is_dirty will also be set on defaults/onchanges/whatever?
|
||||
// oe_form_dirty seems to only be set on actual user actions
|
||||
|
@ -3807,11 +3857,11 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
|
|||
title: _t("Create: ") + self.o2m.string,
|
||||
initial_view: "form",
|
||||
alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined,
|
||||
create_function: function(data, callback, error_callback) {
|
||||
return self.o2m.dataset.create(data).done(function(r) {
|
||||
create_function: function(data, options) {
|
||||
return self.o2m.dataset.create(data, options).done(function(r) {
|
||||
self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r]));
|
||||
self.o2m.dataset.trigger("dataset_changed", r);
|
||||
}).done(callback).fail(error_callback);
|
||||
});
|
||||
},
|
||||
read_function: function() {
|
||||
return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments);
|
||||
|
@ -4024,6 +4074,7 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
|
|||
if (this.get("effective_readonly"))
|
||||
return;
|
||||
var self = this;
|
||||
var ignore_blur = false;
|
||||
self.$text = this.$("textarea");
|
||||
self.$text.textext({
|
||||
plugins : 'tags arrow autocomplete',
|
||||
|
@ -4042,6 +4093,7 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
|
|||
if (data.id) {
|
||||
self.add_id(data.id);
|
||||
} else {
|
||||
ignore_blur = true;
|
||||
data.action();
|
||||
}
|
||||
},
|
||||
|
@ -4092,10 +4144,13 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
|
|||
self.$text
|
||||
.focusin(function () {
|
||||
self.trigger('focused');
|
||||
ignore_blur = false;
|
||||
})
|
||||
.focusout(function() {
|
||||
self.$text.trigger("setInputData", "");
|
||||
self.trigger('blurred');
|
||||
if (!ignore_blur) {
|
||||
self.trigger('blurred');
|
||||
}
|
||||
}).keydown(function(e) {
|
||||
if (e.which === $.ui.keyCode.TAB && self._drop_shown) {
|
||||
self.$text.textext()[0].autocomplete().selectFromDropdown();
|
||||
|
@ -4302,6 +4357,7 @@ instance.web.form.Many2ManyListView = instance.web.ListView.extend(/** @lends in
|
|||
});
|
||||
}
|
||||
},
|
||||
is_action_enabled: function () { return true; },
|
||||
});
|
||||
|
||||
instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, {
|
||||
|
@ -4536,9 +4592,9 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
|
|||
this.created_elements = [];
|
||||
this.dataset = new instance.web.ProxyDataSet(this, this.model, this.context);
|
||||
this.dataset.read_function = this.options.read_function;
|
||||
this.dataset.create_function = function(data, sup) {
|
||||
this.dataset.create_function = function(data, options, sup) {
|
||||
var fct = self.options.create_function || sup;
|
||||
return fct.call(this, data).done(function(r) {
|
||||
return fct.call(this, data, options).done(function(r) {
|
||||
self.trigger('create_completed saved', r);
|
||||
self.created_elements.push(r);
|
||||
});
|
||||
|
@ -5250,11 +5306,11 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({
|
|||
this.calc_domain();
|
||||
this.on("change:value", this, this.get_selection);
|
||||
this.on("change:evaluated_selection_domain", this, this.get_selection);
|
||||
this.get_selection();
|
||||
this.on("change:selection", this, function() {
|
||||
this.selection = this.get("selection");
|
||||
this.render_value();
|
||||
});
|
||||
this.get_selection();
|
||||
if (this.options.clickable) {
|
||||
this.$el.on('click','li',this.on_click_stage);
|
||||
}
|
||||
|
@ -5281,7 +5337,12 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({
|
|||
},
|
||||
calc_domain: function() {
|
||||
var d = instance.web.pyeval.eval('domain', this.build_domain());
|
||||
domain = ['|', ['id', '=', this.get('value')]].concat(d);
|
||||
var domain = []; //if there is no domain defined, fetch all the records
|
||||
|
||||
if (d.length) {
|
||||
domain = ['|',['id', '=', this.get('value')]].concat(d);
|
||||
}
|
||||
|
||||
if (! _.isEqual(domain, this.get("evaluated_selection_domain"))) {
|
||||
this.set("evaluated_selection_domain", domain);
|
||||
}
|
||||
|
@ -5373,10 +5434,10 @@ instance.web.form.FieldMonetary = instance.web.form.FieldFloat.extend({
|
|||
});
|
||||
},
|
||||
parse_value: function(val, def) {
|
||||
return instance.web.parse_value(val, {type: "float"}, def);
|
||||
return instance.web.parse_value(val, {type: "float", digits: (this.node.attrs || {}).digits || this.field.digits}, def);
|
||||
},
|
||||
format_value: function(val, def) {
|
||||
return instance.web.format_value(val, {type: "float"}, def);
|
||||
return instance.web.format_value(val, {type: "float", digits: (this.node.attrs || {}).digits || this.field.digits}, def);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -321,9 +321,9 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
|
|||
.appendTo($this.empty())
|
||||
.click(function (e) {e.stopPropagation();})
|
||||
.append('<option value="80">80</option>' +
|
||||
'<option value="100">100</option>' +
|
||||
'<option value="200">200</option>' +
|
||||
'<option value="500">500</option>' +
|
||||
'<option value="2000">2000</option>' +
|
||||
'<option value="NaN">' + _t("Unlimited") + '</option>')
|
||||
.change(function () {
|
||||
var val = parseInt($select.val(), 10);
|
||||
|
|
|
@ -96,7 +96,8 @@ openerp.web.list_editable = function (instance) {
|
|||
});
|
||||
},
|
||||
editable: function () {
|
||||
return !this.options.disable_editable_mode
|
||||
return !this.grouped
|
||||
&& !this.options.disable_editable_mode
|
||||
&& (this.fields_view.arch.attrs.editable
|
||||
|| this._context_editable
|
||||
|| this.options.editable);
|
||||
|
|
|
@ -149,6 +149,9 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
|||
for (var i = 0; i < this.breadcrumbs.length; i += 1) {
|
||||
var item = this.breadcrumbs[i];
|
||||
var tit = item.get_title();
|
||||
if (item.hide_breadcrumb) {
|
||||
continue;
|
||||
}
|
||||
if (!_.isArray(tit)) {
|
||||
tit = [tit];
|
||||
}
|
||||
|
@ -282,6 +285,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
|||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.clear_breadcrumbs=false] Clear the breadcrumbs history list
|
||||
* @param {Function} [options.on_reverse_breadcrumb] Callback to be executed whenever an anterior breadcrumb item is clicked on.
|
||||
* @param {Function} [options.hide_breadcrumb] Do not display this widget's title in the breadcrumb
|
||||
* @param {Function} [options.on_close] Callback to be executed when the dialog is closed (only relevant for target=new actions)
|
||||
* @param {Function} [options.action_menu_id] Manually set the menu id on the fly.
|
||||
* @param {Object} [options.additional_context] Additional context to be merged with the action's context.
|
||||
|
@ -291,6 +295,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
|||
options = _.defaults(options || {}, {
|
||||
clear_breadcrumbs: false,
|
||||
on_reverse_breadcrumb: function() {},
|
||||
hide_breadcrumb: false,
|
||||
on_close: function() {},
|
||||
action_menu_id: null,
|
||||
additional_context: {},
|
||||
|
@ -403,7 +408,12 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
|||
widget: function () { return new instance.web.ViewManagerAction(self, action); },
|
||||
action: action,
|
||||
klass: 'oe_act_window',
|
||||
post_process: function (widget) { widget.add_breadcrumb(options.on_reverse_breadcrumb); }
|
||||
post_process: function (widget) {
|
||||
widget.add_breadcrumb({
|
||||
on_reverse_breadcrumb: options.on_reverse_breadcrumb,
|
||||
hide_breadcrumb: options.hide_breadcrumb,
|
||||
});
|
||||
},
|
||||
}, options);
|
||||
},
|
||||
ir_actions_client: function (action, options) {
|
||||
|
@ -427,6 +437,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
|||
widget: widget,
|
||||
title: action.name,
|
||||
on_reverse_breadcrumb: options.on_reverse_breadcrumb,
|
||||
hide_breadcrumb: options.hide_breadcrumb,
|
||||
});
|
||||
if (action.tag !== 'reload') {
|
||||
self.do_push_state({});
|
||||
|
@ -649,7 +660,15 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
|||
set_title: function(title) {
|
||||
this.$el.find('.oe_view_title_text:first').text(title);
|
||||
},
|
||||
add_breadcrumb: function(on_reverse_breadcrumb) {
|
||||
add_breadcrumb: function(options) {
|
||||
var options = options || {};
|
||||
// 7.0 backward compatibility
|
||||
if (typeof options == 'function') {
|
||||
options = {
|
||||
on_reverse_breadcrumb: options
|
||||
};
|
||||
}
|
||||
// end of 7.0 backward compatibility
|
||||
var self = this;
|
||||
var views = [this.active_view || this.views_src[0].view_type];
|
||||
this.on('switch_mode', self, function(mode) {
|
||||
|
@ -661,7 +680,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
|||
views.push(mode);
|
||||
}
|
||||
});
|
||||
this.getParent().push_breadcrumb({
|
||||
var item = _.extend({
|
||||
widget: this,
|
||||
action: this.action,
|
||||
show: function(index) {
|
||||
|
@ -695,9 +714,9 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
|||
titles.pop();
|
||||
}
|
||||
return titles;
|
||||
},
|
||||
on_reverse_breadcrumb: on_reverse_breadcrumb,
|
||||
});
|
||||
}
|
||||
}, options);
|
||||
this.getParent().push_breadcrumb(item);
|
||||
},
|
||||
/**
|
||||
* Returns to the view preceding the caller view in this manager's
|
||||
|
@ -1263,6 +1282,7 @@ instance.web.View = instance.web.Widget.extend({
|
|||
"view_id": this.view_id,
|
||||
"view_type": this.view_type,
|
||||
"toolbar": !!this.options.$sidebar,
|
||||
"context": this.dataset.get_context(),
|
||||
});
|
||||
}
|
||||
return view_loaded_def.then(function(r) {
|
||||
|
@ -1446,11 +1466,11 @@ instance.web.View = instance.web.Widget.extend({
|
|||
* Performs a fields_view_get and apply postprocessing.
|
||||
* return a {$.Deferred} resolved with the fvg
|
||||
*
|
||||
* @param {Object} [args]
|
||||
* @param {Object} args
|
||||
* @param {String|Object} args.model instance.web.Model instance or string repr of the model
|
||||
* @param {null|Object} args.context context if args.model is a string
|
||||
* @param {null|Number} args.view_id id of the view to be loaded, default view if null
|
||||
* @param {null|String} args.view_type type of view to be loaded if view_id is null
|
||||
* @param {Object} [args.context] context if args.model is a string
|
||||
* @param {Number} [args.view_id] id of the view to be loaded, default view if null
|
||||
* @param {String} [args.view_type] type of view to be loaded if view_id is null
|
||||
* @param {Boolean} [args.toolbar=false] get the toolbar definition
|
||||
*/
|
||||
instance.web.fields_view_get = function(args) {
|
||||
|
@ -1477,7 +1497,7 @@ instance.web.fields_view_get = function(args) {
|
|||
if (typeof model === 'string') {
|
||||
model = new instance.web.Model(args.model, args.context);
|
||||
}
|
||||
return args.model.call('fields_view_get', [args.view_id, args.view_type, model.context(), args.toolbar]).then(function(fvg) {
|
||||
return args.model.call('fields_view_get', [args.view_id, args.view_type, args.context, args.toolbar]).then(function(fvg) {
|
||||
return postprocess(fvg);
|
||||
});
|
||||
};
|
||||
|
@ -1555,12 +1575,17 @@ instance.web.xml_to_str = function(node) {
|
|||
} else {
|
||||
throw new Error(_t("Could not serialize XML"));
|
||||
}
|
||||
// Browsers won't deal with self closing tags except br, hr, input, ...
|
||||
// http://stackoverflow.com/questions/97522/what-are-all-the-valid-self-closing-elements-in-xhtml-as-implemented-by-the-maj
|
||||
//
|
||||
// Browsers won't deal with self closing tags except void elements:
|
||||
// http://www.w3.org/TR/html-markup/syntax.html
|
||||
var void_elements = 'area base br col command embed hr img input keygen link meta param source track wbr'.split(' ');
|
||||
|
||||
// The following regex is a bit naive but it's ok for the xmlserializer output
|
||||
str = str.replace(/<([a-z]+)([^<>]*)\s*\/\s*>/g, function(match, tag, attrs) {
|
||||
return "<" + tag + attrs + "></" + tag + ">";
|
||||
if (void_elements.indexOf(tag) < 0) {
|
||||
return "<" + tag + attrs + "></" + tag + ">";
|
||||
} else {
|
||||
return match;
|
||||
}
|
||||
});
|
||||
return str;
|
||||
};
|
||||
|
|
|
@ -40,11 +40,11 @@
|
|||
<td>
|
||||
<p>
|
||||
<t t-js="d">
|
||||
var message = d.message ? d.message : d.error.data.message;
|
||||
d.html_error = context.engine.tools.html_escape(message)
|
||||
var message = d.error.data.message || d.error.data.fault_code;
|
||||
d.error.html_error = context.engine.tools.html_escape(message)
|
||||
.replace(/\n/g, '<br/>');
|
||||
</t>
|
||||
<t t-raw="html_error"/>
|
||||
<t t-raw="error.html_error"/>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -71,9 +71,9 @@
|
|||
</div>
|
||||
<ul>
|
||||
<li>Username</li>
|
||||
<li><input name="login" type="text" t-att-value="widget.selected_login || ''" autofocus="autofocus"/></li>
|
||||
<li><input name="login" type="text" value="" autofocus="autofocus"/></li>
|
||||
<li>Password</li>
|
||||
<li><input name="password" type="password" t-att-value="widget.selected_password || ''"/></li>
|
||||
<li><input name="password" type="password" value=""/></li>
|
||||
<li><button name="submit">Log in</button></li>
|
||||
</ul>
|
||||
</form>
|
||||
|
@ -106,33 +106,47 @@
|
|||
<div class="oe_view_manager_header" style="padding: 8px;">
|
||||
<div class="oe_header_row">
|
||||
<h2 class="oe_view_title">
|
||||
<span class="oe_view_title_text oe_breadcrumb_title">Create Database</span>
|
||||
<span class="oe_view_title_text oe_breadcrumb_title">Create a New Database</span>
|
||||
</h2>
|
||||
<button type="submit" class="oe_button oe_highlight db_create">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table align="center" class="db_option_table">
|
||||
<p class="oe_grey" style="margin: 10px">
|
||||
Fill in this form to create an OpenERP database. You can
|
||||
create databases for different companies or for different
|
||||
goals (testing, production). Once the database is created,
|
||||
you will be able to install your first application.
|
||||
</p>
|
||||
<p class="oe_grey" style="margin: 10px">
|
||||
By default, the master password is 'admin'. This password
|
||||
is required to created, delete dump or restore databases.
|
||||
</p>
|
||||
<table class="db_option_table" style="margin: 10px">
|
||||
<tr>
|
||||
<td><label for="super_admin_pwd">Master password:</label></td>
|
||||
<td><input type="password" name="super_admin_pwd" class="required" value="admin" /></td>
|
||||
<td>
|
||||
<input type="password" name="super_admin_pwd" class="required" value="admin"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="db_name">New database name:</label></td>
|
||||
<td><input type="text" name="db_name" class="required" matches="^[a-zA-Z0-9][a-zA-Z0-9_-]+$" autofocus="true"/></td>
|
||||
<td><label for="db_name">Select a database name:</label></td>
|
||||
<td>
|
||||
<input type="text" name="db_name" class="required" matches="^[a-zA-Z0-9][a-zA-Z0-9_-]+$" autofocus="true" placeholder="e.g. mycompany"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="demo_data">Load Demonstration data:</label></td>
|
||||
<td><label for="demo_data">Load demonstration data:</label></td>
|
||||
<td class="oe_form_group_cell">
|
||||
<span class="oe_form_field oe_form_field_boolean">
|
||||
<span class="oe_form_field oe_form_field_boolean oe_grey" >
|
||||
<input type="checkbox" name="demo_data" />
|
||||
Check this box to evaluate OpenERP.
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="db_lang">Default language:</label></td>
|
||||
<td class="oe_form_field oe_form_field_selection ">
|
||||
<select name="db_lang" t-if="widget.lang_list">
|
||||
<td class="oe_form_field oe_form_field_selection">
|
||||
<select name="db_lang" t-if="widget.lang_list" class="oe_inline">
|
||||
<t t-foreach="widget.lang_list" t-as="lang">
|
||||
<option t-att-value="lang[0]" t-att-selected="lang[0] === 'en_US' ? 'selected' : undefined">
|
||||
<t t-esc="lang[1]" />
|
||||
|
@ -142,13 +156,17 @@
|
|||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="create_admin_pwd">Admin password:</label></td>
|
||||
<td><label for="create_admin_pwd">Choose a password:</label></td>
|
||||
<td><input type="password" name="create_admin_pwd" class="required" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="create_confirm_pwd">Confirm password:</label></td>
|
||||
<td><input type="password" name="create_confirm_pwd" class="required" equalTo="input[name=create_admin_pwd]"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><button type="submit" class="oe_button oe_highlight db_create">Create Database</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
<form id="db_duplicate" name="duplicate_db_form" style="display: none;">
|
||||
|
@ -1032,17 +1050,22 @@
|
|||
</t>
|
||||
<t t-name="FieldText">
|
||||
<div class="oe_form_field oe_form_field_text" t-att-style="widget.node.attrs.style">
|
||||
<textarea rows="6"
|
||||
t-att-name="widget.name"
|
||||
class="field_text"
|
||||
t-att-tabindex="widget.node.attrs.tabindex"
|
||||
t-att-autofocus="widget.node.attrs.autofocus"
|
||||
t-att-placeholder="! widget.get('effective_readonly') ? widget.node.attrs.placeholder : ''"
|
||||
t-att-maxlength="widget.field.size"
|
||||
></textarea><img class="oe_field_translate oe_input_icon"
|
||||
t-if="widget.field.translate and !widget.get('effective_readonly')"
|
||||
t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"
|
||||
/>
|
||||
<t t-if="!widget.get('effective_readonly')">
|
||||
<textarea rows="6"
|
||||
t-att-name="widget.name"
|
||||
class="field_text"
|
||||
t-att-tabindex="widget.node.attrs.tabindex"
|
||||
t-att-autofocus="widget.node.attrs.autofocus"
|
||||
t-att-placeholder="! widget.get('effective_readonly') ? widget.node.attrs.placeholder : ''"
|
||||
t-att-maxlength="widget.field.size"
|
||||
></textarea><img class="oe_field_translate oe_input_icon"
|
||||
t-if="widget.field.translate and !widget.get('effective_readonly')"
|
||||
t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"
|
||||
/>
|
||||
</t>
|
||||
<t t-if="widget.get('effective_readonly')">
|
||||
<span class="oe_form_text_content"></span>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="FieldTextHtml">
|
||||
|
@ -1061,8 +1084,9 @@
|
|||
t-att-name="widget.name"
|
||||
t-att-placeholder="placeholder"
|
||||
class="oe_datepicker_master"
|
||||
/><img class="oe_input_icon oe_datepicker_trigger" t-att-src='_s + "/web/static/src/img/ui/field_calendar.png"'
|
||||
title="Select date" width="16" height="16" border="0"/>
|
||||
/><img class="oe_input_icon oe_datepicker_trigger" draggable="false"
|
||||
t-att-src='_s + "/web/static/src/img/ui/field_calendar.png"'
|
||||
title="Select date" width="16" height="16" border="0"/>
|
||||
</span>
|
||||
</t>
|
||||
<t t-name="FieldDate">
|
||||
|
@ -1119,7 +1143,7 @@
|
|||
</t>
|
||||
<t t-if="!widget.get('effective_readonly')">
|
||||
<a t-if="! widget.options.no_open" href="#" tabindex="-1"
|
||||
class="oe_m2o_cm_button oe_e">/</a>
|
||||
class="oe_m2o_cm_button oe_e" draggable="false">/</a>
|
||||
<div>
|
||||
<input type="text"
|
||||
t-att-id="widget.id_for_label"
|
||||
|
@ -1128,7 +1152,7 @@
|
|||
t-att-placeholder="widget.node.attrs.placeholder"
|
||||
/>
|
||||
<span class="oe_m2o_drop_down_button">
|
||||
<img t-att-src='_s + "/web/static/src/img/down-arrow.png"'/>
|
||||
<img t-att-src='_s + "/web/static/src/img/down-arrow.png"' draggable="false"/>
|
||||
</span>
|
||||
</div>
|
||||
</t>
|
||||
|
@ -1324,7 +1348,7 @@
|
|||
<div class="oe_add" t-if="!widget.get('effective_readonly')">
|
||||
<!-- uploader of file -->
|
||||
<button class="oe_attach"><span class="oe_e">'</span></button>
|
||||
<span class='oe_attach_label'>File</span>
|
||||
<span class='oe_attach_label'><t t-esc="widget.string"/></span>
|
||||
<t t-call="HiddenInputFile">
|
||||
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
|
||||
<t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
|
||||
|
@ -1523,7 +1547,7 @@
|
|||
<t t-esc="attrs.string"/>
|
||||
</button>
|
||||
<ul t-name="SearchView.filters">
|
||||
<li t-foreach="widget.filters" t-as="filter"
|
||||
<li t-foreach="widget.filters" t-as="filter" t-if="filter.visible()"
|
||||
t-att-title="filter.attrs.string ? filter.attrs.help : undefined">
|
||||
<t t-esc="filter.attrs.string or filter.attrs.help or filter.attrs.name or 'Ω'"/>
|
||||
</li>
|
||||
|
@ -1611,15 +1635,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="SearchView.group">
|
||||
<t t-call="SearchView.util.expand">
|
||||
<t t-set="expand" t-value="attrs.expand"/>
|
||||
<t t-set="label" t-value="attrs.string"/>
|
||||
<t t-set="content">
|
||||
<t t-call="SearchView.render_lines"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
<div t-name="SearchView.Filters" class="oe_searchview_filters oe_searchview_section">
|
||||
|
||||
</div>
|
||||
|
@ -1850,7 +1865,7 @@
|
|||
<tr>
|
||||
<td t-foreach="records[0]" t-as="column">
|
||||
<input class="sel_fields" placeholder="--- Don't Import ---"/><span class="oe_m2o_drop_down_button">
|
||||
<img t-att-src='_s + "/web/static/src/img/down-arrow.png"' /></span>
|
||||
<img t-att-src='_s + "/web/static/src/img/down-arrow.png"' draggable="false"/></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr t-foreach="records" t-as="record" class="oe_import_grid-row">
|
||||
|
|
|
@ -259,6 +259,12 @@ openerp.testing.section('eval.types', {
|
|||
py.eval('a + a', ctx);
|
||||
}, /^Error: TypeError:/);
|
||||
});
|
||||
test('relastivedelta', function (instance) {
|
||||
strictEqual(
|
||||
py.eval("(datetime.date(2012, 2, 15) + relativedelta(days=-1)).strftime('%Y-%m-%d 23:59:59')",
|
||||
instance.web.pyeval.context()),
|
||||
"2012-02-14 23:59:59");
|
||||
})
|
||||
});
|
||||
openerp.testing.section('eval.edc', {
|
||||
dependencies: ['web.data'],
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
openerp.testing.section('query', {
|
||||
openerp.testing.section('search.query', {
|
||||
dependencies: ['web.search']
|
||||
}, function (test) {
|
||||
test('Adding a facet to the query creates a facet and a value', function (instance) {
|
||||
|
@ -180,7 +180,7 @@ var makeSearchView = function (instance, dummy_widget_attributes, defaults) {
|
|||
});
|
||||
return view;
|
||||
};
|
||||
openerp.testing.section('defaults', {
|
||||
openerp.testing.section('search.defaults', {
|
||||
dependencies: ['web.search'],
|
||||
rpc: 'mock',
|
||||
templates: true,
|
||||
|
@ -362,7 +362,7 @@ openerp.testing.section('defaults', {
|
|||
"should not accept multiple default values");
|
||||
})
|
||||
});
|
||||
openerp.testing.section('completions', {
|
||||
openerp.testing.section('search.completions', {
|
||||
dependencies: ['web.search'],
|
||||
rpc: 'mock',
|
||||
templates: true
|
||||
|
@ -615,7 +615,7 @@ openerp.testing.section('completions', {
|
|||
return f.complete("bob");
|
||||
});
|
||||
});
|
||||
openerp.testing.section('search-serialization', {
|
||||
openerp.testing.section('search.serialization', {
|
||||
dependencies: ['web.search'],
|
||||
rpc: 'mock',
|
||||
templates: true
|
||||
|
@ -855,11 +855,11 @@ openerp.testing.section('search-serialization', {
|
|||
test('FilterGroup', {asserts: 6}, function (instance) {
|
||||
var view = {inputs: [], query: {on: function () {}}};
|
||||
var filter_a = new instance.web.search.Filter(
|
||||
{attrs: {name: 'a', context: 'c1', domain: 'd1'}}, view);
|
||||
{attrs: {name: 'a', context: '{"c1": True}', domain: 'd1'}}, view);
|
||||
var filter_b = new instance.web.search.Filter(
|
||||
{attrs: {name: 'b', context: 'c2', domain: 'd2'}}, view);
|
||||
{attrs: {name: 'b', context: '{"c2": True}', domain: 'd2'}}, view);
|
||||
var filter_c = new instance.web.search.Filter(
|
||||
{attrs: {name: 'c', context: 'c3', domain: 'd3'}}, view);
|
||||
{attrs: {name: 'c', context: '{"c3": True}', domain: 'd3'}}, view);
|
||||
var group = new instance.web.search.FilterGroup(
|
||||
[filter_a, filter_b, filter_c], view);
|
||||
return group.facet_for_defaults({a: true, c: true})
|
||||
|
@ -880,7 +880,7 @@ openerp.testing.section('search-serialization', {
|
|||
equal(context.__ref, 'compound_context',
|
||||
"context should be compound");
|
||||
deepEqual(context.__contexts, [
|
||||
'c1', 'c3'
|
||||
'{"c1": True}', '{"c3": True}'
|
||||
], "context should merge all filter contexts");
|
||||
ok(!context.get_eval_context(), "context should have no evaluation context");
|
||||
});
|
||||
|
@ -922,7 +922,7 @@ openerp.testing.section('search-serialization', {
|
|||
return $.when(t1, t2);
|
||||
});
|
||||
});
|
||||
openerp.testing.section('removal', {
|
||||
openerp.testing.section('search.removal', {
|
||||
dependencies: ['web.search'],
|
||||
rpc: 'mock',
|
||||
templates: true
|
||||
|
@ -945,7 +945,7 @@ openerp.testing.section('removal', {
|
|||
});
|
||||
});
|
||||
});
|
||||
openerp.testing.section('drawer', {
|
||||
openerp.testing.section('search.drawer', {
|
||||
dependencies: ['web.search'],
|
||||
rpc: 'mock',
|
||||
templates: true
|
||||
|
@ -961,7 +961,7 @@ openerp.testing.section('drawer', {
|
|||
});
|
||||
});
|
||||
});
|
||||
openerp.testing.section('filters', {
|
||||
openerp.testing.section('search.filters', {
|
||||
dependencies: ['web.search'],
|
||||
rpc: 'mock',
|
||||
templates: true,
|
||||
|
@ -1046,7 +1046,104 @@ openerp.testing.section('filters', {
|
|||
});
|
||||
});
|
||||
});
|
||||
openerp.testing.section('saved_filters', {
|
||||
openerp.testing.section('search.groupby', {
|
||||
dependencies: ['web.search'],
|
||||
rpc: 'mock',
|
||||
templates: true,
|
||||
}, function (test) {
|
||||
test('basic', {
|
||||
asserts: 7,
|
||||
setup: function (instance, $s, mock) {
|
||||
mock('dummy.model:fields_view_get', function () {
|
||||
return {
|
||||
type: 'search',
|
||||
fields: {},
|
||||
arch: [
|
||||
'<search>',
|
||||
'<filter string="Foo" context="{\'group_by\': \'foo\'}"/>',
|
||||
'<filter string="Bar" context="{\'group_by\': \'bar\'}"/>',
|
||||
'<filter string="Baz" context="{\'group_by\': \'baz\'}"/>',
|
||||
'</search>'
|
||||
].join(''),
|
||||
}
|
||||
});
|
||||
}
|
||||
}, function (instance, $fix) {
|
||||
var view = makeSearchView(instance);
|
||||
return view.appendTo($fix)
|
||||
.done(function () {
|
||||
// 3 filters, 1 filtergroup group, 1 custom filter, 1 advanced, 1 Filters
|
||||
equal(view.inputs.length, 7,
|
||||
'should have 7 inputs total');
|
||||
var group = _.find(view.inputs, function (f) {
|
||||
return f instanceof instance.web.search.GroupbyGroup
|
||||
});
|
||||
ok(group, "should have a GroupbyGroup input");
|
||||
strictEqual(group.getParent(), view,
|
||||
"group's parent should be view");
|
||||
|
||||
group.toggle(group.filters[0]);
|
||||
group.toggle(group.filters[2]);
|
||||
|
||||
var results = view.build_search_data();
|
||||
deepEqual(results.errors, [], "should have no errors");
|
||||
deepEqual(results.domains, [], "should have no domain");
|
||||
deepEqual(results.contexts, [
|
||||
new instance.web.CompoundContext(
|
||||
"{'group_by': 'foo'}", "{'group_by': 'baz'}")
|
||||
], "should have compound contexts");
|
||||
deepEqual(results.groupbys, [
|
||||
"{'group_by': 'foo'}",
|
||||
"{'group_by': 'baz'}"
|
||||
], "should have sequence of contexts")
|
||||
});
|
||||
});
|
||||
test('unified multiple groupby groups', {
|
||||
asserts: 4,
|
||||
setup: function (instance, $s, mock) {
|
||||
mock('dummy.model:fields_view_get', function () {
|
||||
return {
|
||||
type: 'search',
|
||||
fields: {},
|
||||
arch: [
|
||||
'<search>',
|
||||
'<filter string="Foo" context="{\'group_by\': \'foo\'}"/>',
|
||||
'<separator/>',
|
||||
'<filter string="Bar" context="{\'group_by\': \'bar\'}"/>',
|
||||
'<separator/>',
|
||||
'<filter string="Baz" context="{\'group_by\': \'baz\'}"/>',
|
||||
'</search>'
|
||||
].join(''),
|
||||
}
|
||||
});
|
||||
}
|
||||
}, function (instance, $fix) {
|
||||
var view = makeSearchView(instance);
|
||||
return view.appendTo($fix)
|
||||
.done(function () {
|
||||
// 3 filters, 3 filtergroups, 1 custom filter, 1 advanced, 1 Filters
|
||||
equal(view.inputs.length, 9, "should have 9 inputs total");
|
||||
|
||||
var groups = _.filter(view.inputs, function (f) {
|
||||
return f instanceof instance.web.search.GroupbyGroup
|
||||
});
|
||||
equal(groups.length, 3, "should have 3 GroupbyGroups");
|
||||
|
||||
groups[0].toggle(groups[0].filters[0]);
|
||||
groups[2].toggle(groups[2].filters[0]);
|
||||
equal(view.query.length, 1,
|
||||
"should have unified groupby groups in single facet");
|
||||
deepEqual(view.build_search_data(), {
|
||||
errors: [],
|
||||
domains: [],
|
||||
contexts: [new instance.web.CompoundContext(
|
||||
"{'group_by': 'foo'}", "{'group_by': 'baz'}")],
|
||||
groupbys: [ "{'group_by': 'foo'}", "{'group_by': 'baz'}" ],
|
||||
}, "should only have contexts & groupbys in search data");
|
||||
});
|
||||
});
|
||||
});
|
||||
openerp.testing.section('search.filters.saved', {
|
||||
dependencies: ['web.search'],
|
||||
rpc: 'mock',
|
||||
templates: true
|
||||
|
@ -1127,8 +1224,30 @@ openerp.testing.section('saved_filters', {
|
|||
"should have selected second filter");
|
||||
});
|
||||
});
|
||||
test('creation', {asserts: 2}, function (instance, $fix, mock) {
|
||||
// force a user context
|
||||
instance.session.user_context = {foo: 'bar'};
|
||||
|
||||
var view = makeSearchView(instance);
|
||||
var done = $.Deferred();
|
||||
mock('ir.filters:get_filters', function () { return []; });
|
||||
mock('ir.filters:create_or_replace', function (args) {
|
||||
var filter = args[0];
|
||||
deepEqual(filter.context, {}, "should have empty context");
|
||||
deepEqual(filter.domain, [], "should have empty domain");
|
||||
done.resolve();
|
||||
});
|
||||
return view.appendTo($fix)
|
||||
.then(function () {
|
||||
$fix.find('.oe_searchview_custom input#oe_searchview_custom_input')
|
||||
.text("filter name")
|
||||
.end()
|
||||
.find('.oe_searchview_custom button').click();
|
||||
return done.promise();
|
||||
});
|
||||
});
|
||||
});
|
||||
openerp.testing.section('advanced', {
|
||||
openerp.testing.section('search.advanced', {
|
||||
dependencies: ['web.search'],
|
||||
rpc: 'mock',
|
||||
templates: true
|
||||
|
@ -1209,3 +1328,159 @@ openerp.testing.section('advanced', {
|
|||
});
|
||||
// TODO: UI tests?
|
||||
});
|
||||
openerp.testing.section('search.invisible', {
|
||||
dependencies: ['web.search'],
|
||||
rpc: 'mock',
|
||||
templates: true,
|
||||
}, function (test) {
|
||||
var registerTestField = function (instance, methods) {
|
||||
instance.web.search.fields.add('test', 'instance.testing.TestWidget');
|
||||
instance.testing = {
|
||||
TestWidget: instance.web.search.Field.extend(methods),
|
||||
};
|
||||
};
|
||||
var makeView = function (instance, mock, fields, arch, defaults) {
|
||||
mock('ir.filters:get_filters', function () { return []; });
|
||||
mock('test.model:fields_get', function () { return fields; });
|
||||
mock('test.model:fields_view_get', function () {
|
||||
return { type: 'search', fields: fields, arch: arch };
|
||||
});
|
||||
var ds = new instance.web.DataSet(null, 'test.model');
|
||||
return new instance.web.SearchView(null, ds, false, defaults);
|
||||
};
|
||||
// Invisible fields should not auto-complete
|
||||
test('invisible-field-no-autocomplete', {asserts: 1}, function (instance, $fix, mock) {
|
||||
registerTestField(instance, {
|
||||
complete: function () {
|
||||
return $.when([{label: this.attrs.string}]);
|
||||
},
|
||||
});
|
||||
var view = makeView(instance, mock, {
|
||||
field0: {type: 'test', string: 'Field 0'},
|
||||
field1: {type: 'test', string: 'Field 1'},
|
||||
}, ['<search>',
|
||||
'<field name="field0"/>',
|
||||
'<field name="field1" modifiers="{"invisible": true}"/>',
|
||||
'</search>'].join());
|
||||
return view.appendTo($fix)
|
||||
.then(function () {
|
||||
var done = $.Deferred();
|
||||
view.complete_global_search({term: 'test'}, function (comps) {
|
||||
done.resolve(comps);
|
||||
});
|
||||
return done;
|
||||
}).then(function (completions) {
|
||||
deepEqual(completions, [{label: 'Field 0'}],
|
||||
"should only complete the visible field");
|
||||
});
|
||||
});
|
||||
// Invisible filters should not appear in the drawer
|
||||
test('invisible-filter-no-drawer', {asserts: 4}, function (instance, $fix, mock) {
|
||||
var view = makeView(instance, mock, {}, [
|
||||
'<search>',
|
||||
'<filter string="filter 0"/>',
|
||||
'<filter string="filter 1" modifiers="{"invisible": true}"/>',
|
||||
'</search>'].join());
|
||||
return view.appendTo($fix)
|
||||
.then(function () {
|
||||
var $fs = $fix.find('.oe_searchview_filters ul');
|
||||
strictEqual($fs.children().length,
|
||||
1,
|
||||
"should only display one filter");
|
||||
strictEqual(_.str.trim($fs.children().text()),
|
||||
"filter 0",
|
||||
"should only display filter 0");
|
||||
var done = $.Deferred();
|
||||
view.complete_global_search({term: 'filter'}, function (comps) {
|
||||
done.resolve();
|
||||
strictEqual(comps.length, 1, "should only complete visible filter");
|
||||
strictEqual(comps[0].label, "Filter on: filter 0",
|
||||
"should complete filter 0");
|
||||
});
|
||||
return done;
|
||||
});
|
||||
});
|
||||
// Invisible filter groups should not appear in the drawer
|
||||
// Group invisibility should be inherited by children
|
||||
test('group-invisibility', {asserts: 6}, function (instance, $fix, mock) {
|
||||
registerTestField(instance, {
|
||||
complete: function () {
|
||||
return $.when([{label: this.attrs.string}]);
|
||||
},
|
||||
});
|
||||
var view = makeView(instance, mock, {
|
||||
field0: {type: 'test', string: 'Field 0'},
|
||||
field1: {type: 'test', string: 'Field 1'},
|
||||
}, [
|
||||
'<search>',
|
||||
'<group string="Visibles">',
|
||||
'<field name="field0"/>',
|
||||
'<filter string="Filter 0"/>',
|
||||
'</group>',
|
||||
'<group string="Invisibles" modifiers="{"invisible": true}">',
|
||||
'<field name="field1"/>',
|
||||
'<filter string="Filter 1"/>',
|
||||
'</group>',
|
||||
'</search>'
|
||||
].join(''));
|
||||
return view.appendTo($fix)
|
||||
.then(function () {
|
||||
strictEqual($fix.find('.oe_searchview_filters h3').length,
|
||||
1,
|
||||
"should only display one group");
|
||||
strictEqual($fix.find('.oe_searchview_filters h3').text(),
|
||||
'w Visibles',
|
||||
"should only display the Visibles group (and its icon char)");
|
||||
|
||||
var $fs = $fix.find('.oe_searchview_filters ul');
|
||||
strictEqual($fs.children().length, 1,
|
||||
"should only have one filter in the drawer");
|
||||
strictEqual(_.str.trim($fs.text()), "Filter 0",
|
||||
"should have filter 0 as sole filter");
|
||||
|
||||
var done = $.Deferred();
|
||||
view.complete_global_search({term: 'filter'}, function (compls) {
|
||||
done.resolve();
|
||||
strictEqual(compls.length, 2,
|
||||
"should have 2 completions");
|
||||
deepEqual(_.pluck(compls, 'label'),
|
||||
['Field 0', 'Filter on: Filter 0'],
|
||||
"should complete on field 0 and filter 0");
|
||||
});
|
||||
return done;
|
||||
});
|
||||
});
|
||||
// Default on invisible fields should still work, for fields and filters both
|
||||
test('invisible-defaults', {asserts: 1}, function (instance, $fix, mock) {
|
||||
var view = makeView(instance, mock, {
|
||||
field: {type: 'char', string: "Field"},
|
||||
field2: {type: 'char', string: "Field 2"},
|
||||
}, [
|
||||
'<search>',
|
||||
'<field name="field2"/>',
|
||||
'<filter name="filter2" string="Filter"',
|
||||
' domain="[[\'qwa\', \'=\', 42]]"/>',
|
||||
'<group string="Invisibles" modifiers="{"invisible": true}">',
|
||||
'<field name="field"/>',
|
||||
'<filter name="filter" string="Filter"',
|
||||
' domain="[[\'whee\', \'=\', \'42\']]"/>',
|
||||
'</group>',
|
||||
'</search>'
|
||||
].join(''), {field: "foo", filter: true});
|
||||
|
||||
return view.appendTo($fix)
|
||||
.then(function () {
|
||||
deepEqual(view.build_search_data(), {
|
||||
errors: [],
|
||||
groupbys: [],
|
||||
contexts: [],
|
||||
domains: [
|
||||
// Generated from field
|
||||
[['field', 'ilike', 'foo']],
|
||||
// generated from filter
|
||||
"[['whee', '=', '42']]"
|
||||
],
|
||||
}, "should yield invisible fields selected by defaults");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
"X-Poedit-Language: Czech\n"
|
||||
|
||||
#. module: web_calendar
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:09+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:09+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:09+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:09+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -15,8 +15,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:09+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
"Language: es\n"
|
||||
|
||||
#. module: web_calendar
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:09+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:09+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:09+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:09+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:09+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-02-07 05:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16477)\n"
|
||||
"X-Launchpad-Export-Date: 2013-03-19 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: web_calendar
|
||||
#. openerp-web
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue