[MERGE] forward port branch saas-12 up to b1b700a

This commit is contained in:
Christophe Simonis
2016-09-22 15:23:45 +02:00
32 changed files with 1030 additions and 972 deletions
+5 -1
View File
@@ -1062,11 +1062,15 @@ class AccountInvoice(models.Model):
if payment_method not in journal_payment_methods:
raise UserError(_('No appropriate payment method enabled on journal %s') % pay_journal.name)
communication = self.type in ('in_invoice', 'in_refund') and self.reference or self.number
if self.origin:
communication = '%s (%s)' % (communication, self.origin)
payment = self.env['account.payment'].create({
'invoice_ids': [(6, 0, self.ids)],
'amount': pay_amount or self.residual,
'payment_date': date or fields.Date.context_today(self),
'communication': self.type in ('in_invoice', 'in_refund') and self.reference or self.number,
'communication': communication,
'partner_id': self.partner_id.id,
'partner_type': self.type in ('out_invoice', 'out_refund') and 'customer' or 'supplier',
'journal_id': pay_journal.id,
@@ -50,14 +50,17 @@ class IrModelFieldsAnonymizeWizard(models.TransientModel):
summary = u''
for anon_field in self.env['ir.model.fields.anonymization'].search([('state', '!=', 'not_existing')]):
field = anon_field.field_id
values = {
'model_name': field.model_id.name,
'model_code': field.model_id.model,
'field_code': field.name,
'field_name': field.field_description,
'state': anon_field.state,
}
summary += u" * %(model_name)s (%(model_code)s) -> %(field_name)s (%(field_code)s): state: (%(state)s)\n" % values
if field:
values = {
'model_name': field.model_id.name,
'model_code': field.model_id.model,
'field_code': field.name,
'field_name': field.field_description,
'state': anon_field.state,
}
summary += u" * %(model_name)s (%(model_code)s) -> %(field_name)s (%(field_code)s): state: (%(state)s)\n" % values
else:
summary += u"* Missing local model (%s) and field (%s): state: (%s) \n" % (anon_field.model_name, anon_field.field_name, anon_field.state)
return summary
@api.model
+2 -2
View File
@@ -673,8 +673,8 @@ class Lead(FormatAddress, models.Model):
'partner_id': customer.id if customer else False,
'type': 'opportunity',
'date_open': fields.Datetime.now(),
'email_from': customer.email if customer else self.email_from,
'phone': customer.phone if customer else self.phone,
'email_from': customer and customer.email or self.email_from,
'phone': customer and customer.phone or self.phone,
'date_conversion': fields.Datetime.now(),
}
if not self.stage_id:
+1 -1
View File
@@ -568,7 +568,7 @@
<field name="name">Departments</field>
<field name="res_model">hr.department</field>
<field name="view_type">form</field>
<field name="view_mode">kanban,form</field>
<field name="view_mode">kanban,tree,form</field>
<field name="search_view_id" ref="view_department_filter"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
+37 -37
View File
@@ -7,13 +7,13 @@ msgstr ""
"Project-Id-Version: Odoo Server 9.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-09-18 16:56+0200\n"
"PO-Revision-Date: 2015-09-18 17:09+0200\n"
"PO-Revision-Date: 2016-09-19 16:51+0200\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 1.8.4\n"
"X-Generator: Poedit 1.8.9\n"
"Last-Translator: \n"
"Language: nl_BE\n"
@@ -495,17 +495,17 @@ msgstr "Resultaten uit de omrekening van vreemde valuta"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a175111
msgid "Effets à payer \\ Fournisseurs C.E.E."
msgstr ""
msgstr "Te betalen wissels: Leveranciers van de EEG"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a175110
msgid "Effets à payer \\ Fournisseurs belges"
msgstr ""
msgstr "Te betalen wissels: Belgische leveranciers"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a175112
msgid "Effets à payer \\ Fournisseurs importation"
msgstr ""
msgstr "Te betalen wissels: Import leveranciers"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a4166
@@ -521,7 +521,7 @@ msgstr "Bedienden"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a3200
msgid "En cours de fabrication"
msgstr ""
msgstr "Goederen in bewerking"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a440
@@ -571,7 +571,7 @@ msgstr "Leveranciers"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a612
msgid "Fournitures"
msgstr ""
msgstr "Diverse leveringen"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a260
@@ -596,22 +596,22 @@ msgstr "Kosten van onderzoek en ontwikkeling"
#. module: l10n_be
#: model:account.tax.template,name:l10n_be.attn_VAT-IN-V82-CAR-EXC-C2
msgid "Frais de voiture - TVA 50% Deductible (Prix Excl.)"
msgstr ""
msgstr "Auto kosten - BTW 50% aftrekbaar (exl. prijs)"
#. module: l10n_be
#: model:account.tax.template,name:l10n_be.attn_VAT-IN-V82-CAR-EXC-C1
msgid "Frais de voiture - TVA 50% Non Deductible"
msgstr ""
msgstr "Auto kosten - BTW 50% niet aftrekbaar"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a3500
msgid "Immeubles destinés à la vente"
msgstr ""
msgstr "Onroerende goederen bestemd voor verkoop"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a2800
msgid "Immobilisations Financières"
msgstr ""
msgstr "Financiële vaste activa"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a4120
@@ -641,17 +641,17 @@ msgstr "Goederen"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a300
msgid "Matières premières"
msgstr ""
msgstr "Grondstoffen"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a24011
msgid "Matériel de bureau de bâtiments administratifs et commerciaux"
msgstr ""
msgstr "Kantooruitrusting voor gebouwen met adminestratieve en commerciële functie"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a24010
msgid "Matériel de bureau de bâtiments industriels"
msgstr ""
msgstr "Kantooruitrusting voor gebouwen met industriële functie"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a2521
@@ -681,22 +681,22 @@ msgstr "Bedragen komende van het resultaat"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a4541
msgid "ONSS. 1er trimestre"
msgstr ""
msgstr "R.S.Z. 1ste kwartaal"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a4542
msgid "ONSS. 2ème trimestre"
msgstr ""
msgstr "R.S.Z. 2de kwartaal"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a4543
msgid "ONSS. 3ème trimestre"
msgstr ""
msgstr "R.S.Z. 3de kwartaal"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a4544
msgid "ONSS. 4ème trimestre"
msgstr ""
msgstr "R.S.Z. 4de kwartaal"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a4540
@@ -762,7 +762,7 @@ msgstr "Dienstverlening buiten de E.E.G. (export)"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a11
msgid "Primes d'émission"
msgstr ""
msgstr "Uitgifte premies"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a4930
@@ -782,12 +782,12 @@ msgstr "Diverse financiële opbrengsten"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a7600
msgid "Produits exceptionnels"
msgstr ""
msgstr "Uitzonderlijke opbrengsten"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a3300
msgid "Produits finis"
msgstr ""
msgstr "Gereed product"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a404
@@ -803,7 +803,7 @@ msgstr "Voorziening voor eindejaarspremie"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a1600
msgid "Provisions pour risques et charges"
msgstr ""
msgstr "Voorzieningen voor risico's en kosten"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a4533
@@ -823,12 +823,12 @@ msgstr "Onttrekking aan het kapitaal en aan de uitgiftepremies"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a780
msgid "Prélèvements sur les impôts différés"
msgstr ""
msgstr "Onttrekking aan de uitgestelde belastingen"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a789
msgid "Prélèvements sur les réserves immunisées"
msgstr ""
msgstr "Onttrekking aan de reserves"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a706
@@ -909,27 +909,27 @@ msgstr "Regularisering van geraamde belastingen"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a6200
msgid "Rémunérations: Administrateurs ou gérants"
msgstr ""
msgstr "Bezoldigingen: Bestuurders of zaakvoerders"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a6204
msgid "Rémunérations: Autres membres du personnel"
msgstr ""
msgstr "Bezoldigingen: Andere personeelsleden, verbonden met een arbeidsovereenkomst"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a6202
msgid "Rémunérations: Employés"
msgstr ""
msgstr "Bezoldigingen: Arbeiders"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a6203
msgid "Rémunérations: Ouvriers"
msgstr ""
msgstr "Bezoldigingen: Bedienden"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a6201
msgid "Rémunérations: Personnel de direction"
msgstr ""
msgstr "Bezoldigingen: Directiepersoneel"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a130
@@ -959,7 +959,7 @@ msgstr "Kapitaal- en interestsubsidies"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a150
msgid "Subsides obtenus"
msgstr ""
msgstr "Verkregen subsidies"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a41670
@@ -1204,7 +1204,7 @@ msgstr "Terreinen"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.trans
msgid "Transferts"
msgstr ""
msgstr "Transferten"
#. module: l10n_be
#: model:account.tax.template,name:l10n_be.attn_VAT-IN-V81-06-CC-C1
@@ -1509,32 +1509,32 @@ msgstr "VAT-IN-V83-21-ROW-CC-C2"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a7001
msgid "Ventes dans les pays membres de la C.E.E. (marchandises)"
msgstr ""
msgstr "Verkopen: leden van de Europese gemeenschap (handelsgoederen)"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a7011
msgid "Ventes dans les pays membres de la C.E.E. (produits finis)"
msgstr ""
msgstr "Verkopen: leden van de Europese gemeenschap (gereed product)"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a7000
msgid "Ventes en Belgique (marchandises)"
msgstr ""
msgstr "Verkopen: België (handelsgoederen)"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a7010
msgid "Ventes en Belgique (produits finis)"
msgstr ""
msgstr "Verkopen: België (gereed product)"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a7002
msgid "Ventes à l'exportation (marchandises)"
msgstr ""
msgstr "Verkopen: export (handelsgoederen)"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a7012
msgid "Ventes à l'exportation (produits finis)"
msgstr ""
msgstr "Verkopen: export (gereed product)"
#. module: l10n_be
#: model:account.account.template,name:l10n_be.a24100
File diff suppressed because it is too large Load Diff
+14 -1
View File
@@ -98,7 +98,20 @@ class PadCommon(models.AbstractModel):
@api.model
def create(self, vals):
self._set_pad_value(vals)
return super(PadCommon, self).create(vals)
pad = super(PadCommon, self).create(vals)
# In case the pad is created programmatically, the content is not filled in yet since it is
# normally initialized by the JS layer
for k, field in self._fields.iteritems():
if hasattr(field, 'pad_content_field') and k not in vals:
ctx = {
'model': self._name,
'field_name': k,
'object_id': pad.id,
}
pad_info = self.with_context(**ctx).pad_generate_url()
pad[k] = pad_info.get('url')
return pad
# Set the pad content in vals
def _set_pad_value(self, vals):
+1 -1
View File
@@ -571,7 +571,7 @@ class PosOrder(models.Model):
Move += Move.create({
'name': line.name,
'product_uom': line.product_id.uom_id.id,
'picking_id': picking_id.id,
'picking_id': picking_id and picking_id.id or False,
'picking_type_id': picking_type.id,
'product_id': line.product_id.id,
'product_uom_qty': abs(line.qty),
+5 -5
View File
@@ -1081,9 +1081,9 @@ class MailComposeMessage(models.TransientModel):
@api.multi
def send_mail(self, auto_commit=False):
compose_internal = self.filtered('subtype_id.internal')
if self._context.get('default_model') == 'purchase.order' and self._context.get('default_res_id') and not compose_internal:
order = self.env['purchase.order'].browse([self._context['default_res_id']])
if order.state == 'draft':
order.state = 'sent'
if self._context.get('default_model') == 'purchase.order' and self._context.get('default_res_id'):
if not self.filtered('subtype_id.internal'):
order = self.env['purchase.order'].browse([self._context['default_res_id']])
if order.state == 'draft':
order.state = 'sent'
return super(MailComposeMessage, self.with_context(mail_post_autofollow=True)).send_mail(auto_commit=auto_commit)
+3 -2
View File
@@ -136,8 +136,7 @@ class Report(models.Model):
'doc_model': report.model,
'docs': docs,
}
return self.render(report.report_name, docargs).encode('utf-8')
return self.render(report.report_name, docargs)
@api.model
def get_pdf(self, docids, report_name, html=None, data=None):
"""This method generates and returns pdf version of a report.
@@ -172,6 +171,8 @@ class Report(models.Model):
if isinstance(self.env.cr, TestCursor):
return html
html = html.decode('utf-8') # Ensure the current document is utf-8 encoded.
# Get the ir.actions.report.xml record we are working on.
report = self._get_report_from_name(report_name)
# Check if we have to save the report or if we have to get one from the db.
+2 -1
View File
@@ -6,6 +6,7 @@ import json
from odoo import http
from odoo.http import request
from odoo.tools import ustr
from odoo.tools.misc import xlwt
@@ -69,7 +70,7 @@ class TableExporter(http.Controller):
# Step 3: writing data
x = 0
for row in jdata['rows']:
worksheet.write(y, x, row['indent'] * ' ' + row['title'], header_plain)
worksheet.write(y, x, row['indent'] * ' ' + ustr(row['title']), header_plain)
for cell in row['values']:
x = x + 1
if cell.get('is_bold', False):
@@ -129,7 +129,7 @@ ExtendedSearchProposition.Field = Widget.extend({
}
},
show_inputs: function ($operator) {
var $value = this.$('.o_searchview_extended_prop_value');
var $value = this.$el.parent();
switch ($operator.val()) {
case '∃':
case '∄':
+1 -1
View File
@@ -3,7 +3,7 @@
<!-- These templates are accessible in backend and frontend -->
<div t-name="Dialog" class="modal" tabindex="-1" data-backdrop="static" role="dialog" aria-hidden="true">
<div t-name="Dialog" class="modal" tabindex="-1" data-backdrop="static" role="dialog" aria-hidden="true" t-att-id="_.uniqueId('modal_')">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
@@ -616,7 +616,6 @@ define([
var linkUrl = linkInfo.url;
var linkText = linkInfo.text;
var isNewWindow = linkInfo.isNewWindow;
var className = linkInfo.className || null; // ODOO: addition
var rng = linkInfo.range || this.createRange($editable);
var isTextChanged = rng.toString() !== linkText;
@@ -647,7 +646,8 @@ define([
$.each(anchors, function (idx, anchor) {
$(anchor).attr('href', linkUrl);
$(anchor).attr('class', className); // ODOO: addition
$(anchor).attr('class', linkInfo.className || null); // ODOO: addition
$(anchor).css(linkInfo.style || {}); // ODOO: addition
if (isNewWindow) {
$(anchor).attr('target', '_blank');
} else {
+1 -1
View File
@@ -509,7 +509,7 @@ var RTE = Widget.extend({
*/
$target.attr('contenteditable', true);
_.defer(function () {
$editable.attr('contenteditable', false);
$editable.not($target).attr('contenteditable', false);
$target.focus();
});
@@ -1001,10 +1001,12 @@ data.Editor = Class.extend({
},
on_remove: function (event) {
event.preventDefault();
this.on_blur();
if (event !== undefined) {
event.preventDefault();
this.buildingBlock.getParent().rte.historyRecordUndo(this.$target);
}
this.buildingBlock.getParent().rte.historyRecordUndo(this.$target);
this.on_blur();
var index = _.indexOf(this.buildingBlock.snippets, this.$target.get(0));
this.buildingBlock.call_for_all_snippets(this.$target, function (editor, $snippet) {
@@ -1037,7 +1039,7 @@ data.Editor = Class.extend({
}
if ($parent.children().length === 0 && $parent.text().trim() === "") {
_.defer(function () {
$parent.data("snippet-editor").on_remove(event);
$parent.data("snippet-editor").on_remove();
});
}
}
+13 -3
View File
@@ -767,7 +767,9 @@ var fontIconsDialog = Widget.extend({
});
function createVideoNode(url) {
function createVideoNode(url, options) {
options = options || {};
// video url patterns(youtube, instagram, vimeo, dailymotion, youku)
var ytRegExp = /^(?:(?:https?:)?\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/;
var ytMatch = url.match(ytRegExp);
@@ -837,6 +839,10 @@ function createVideoNode(url) {
});
}
if (options.autoplay) {
$video.attr("src", $video.attr("src") + "?autoplay=1");
}
$video.attr('frameborder', 0);
return $video;
@@ -851,6 +857,7 @@ var VideoDialog = Widget.extend({
events : _.extend({}, Dialog.prototype.events, {
'click input#urlvideo ~ button': 'get_video',
'click input#embedvideo ~ button': 'get_embed_video',
'change input#autoplay': 'get_video',
'change input#urlvideo': 'change_input',
'keyup input#urlvideo': 'change_input',
'change input#embedvideo': 'change_input',
@@ -869,6 +876,7 @@ var VideoDialog = Widget.extend({
if ($media.hasClass("media_iframe_video")) {
var src = $media.data('src');
this.$("input#urlvideo").val(src);
this.$("input#autoplay").prop("checked", (src || "").indexOf("autoplay") >= 0);
this.get_video();
}
return this._super.apply(this, arguments);
@@ -893,7 +901,7 @@ var VideoDialog = Widget.extend({
},
get_video: function (event) {
if (event) event.preventDefault();
var $video = createVideoNode(this.$("input#urlvideo").val());
var $video = createVideoNode(this.$("input#urlvideo").val(), {autoplay: this.$("input#autoplay").is(":checked")});
this.$iframe.replaceWith($video);
this.$iframe = $video;
return false;
@@ -1084,7 +1092,9 @@ var LinkDialog = Dialog.extend({
self.data.isNewWindow = new_window;
self.data.text = label;
self.data.className = classes.replace(/\s+/gi, ' ').replace(/^\s+|\s+$/gi, '');
if (classes.replace(/(^|[ ])(btn-default|btn-success|btn-primary|btn-info|btn-warning|btn-danger)([ ]|$)/gi, ' ')) {
self.data.style = {'background-color': '', 'color': ''};
}
self.trigger("save", self.data);
}).then(_super);
},
+1 -1
View File
@@ -210,7 +210,7 @@
</div>
<div class="text-center mt32">
<div class="checkbox">
<label>
<label title="The feature might not be supported for every video type">
<input id="autoplay" type="checkbox"/>Autoplay</label>
</div>
</div>
+1 -1
View File
@@ -122,7 +122,7 @@ class WebsiteBlog(http.Controller):
blog_posts = BlogPost.search(domain, order="post_date desc")
pager = request.website.pager(
url=request.httprequest.path,
url=request.httprequest.path.partition('/page/')[0],
total=len(blog_posts),
page=page,
step=self._blog_post_per_page,
@@ -4,7 +4,6 @@
<xpath expr="//div[@name='mail_button']" position="replace">
<div>
<form action="/website_form/" method="post" data-model_name="crm.lead" data-success_page="/page/website_crm.contactus_thanks" class="s_website_form form-horizontal container-fluid mt32" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="form-group form-field o_website_form_required_custom">
<label class="col-md-3 col-sm-4 control-label" for="contact_name">Your Name</label>
<div class="col-md-7 col-sm-8">
@@ -204,8 +204,8 @@ class CrmLead(models.Model):
else:
message = _('<p>I am not interested by this lead. I have not contacted the lead.</p>')
partner_ids = self.env['res.partner'].search(
[('id', 'child_of', self.env.uid.partner_id.commercial_partner_id.id)])
self.sudo().message_unsubscribe(partner_ids)
[('id', 'child_of', self.env.user.partner_id.commercial_partner_id.id)])
self.sudo().message_unsubscribe(partner_ids=partner_ids.ids)
if comment:
message += '<p>%s</p>' % comment
self.message_post(body=message, subtype="mail.mt_note")
@@ -213,8 +213,8 @@ class CrmLead(models.Model):
'partner_assigned_id': False
}
if partner_ids:
values['partner_declined_ids'] = map(lambda p: (4, p, 0), partner_ids)
self.write(values)
values['partner_declined_ids'] = map(lambda p: (4, p, 0), partner_ids.ids)
self.sudo().write(values)
@api.multi
def update_lead_portal(self, values):
@@ -35,7 +35,7 @@ $('.interested_partner_assign_confirm').on('click',function(e){
$('.desinterested_partner_assign_confirm').on('click',function(e){
new Model('crm.lead')
.call("partner_desinterested",
[[$('.desinterested_partner_assign_form .assign_lead_id').val()],
[[parseInt($('.desinterested_partner_assign_form .assign_lead_id').val())],
$('.desinterested_partner_assign_form .comment_desinterested').val(),
$('.desinterested_partner_assign_form .contacted_desinterested').prop('checked')])
.then(function(){
@@ -132,7 +132,6 @@
<div class="row">
<section id="forms">
<form action="/website_form/" method="post" class="s_website_form form-horizontal container-fluid mt32" enctype="multipart/form-data" data-model_name="hr.applicant" data-success_page="/page/website_hr_recruitment.thankyou" t-att-data-form_field_department_id="job and job.department_id.id or False" t-att-data-form_field_job_id="job and job.id or False">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="form-group form-field o_website_form_required_custom">
<div class="col-md-3 col-sm-4 text-right">
<label class="control-label" for="partner_name">Your Name</label>
+4
View File
@@ -130,6 +130,10 @@ preserve order.
* ``record in set`` returns whether ``record`` (which must be a 1-element
recordset) is present in ``set``. ``record not in set`` is the inverse
operation
* ``set1 <= set2`` and ``set1 < set2`` return whether ``set1`` is a subset
of ``set2`` (resp. strict)
* ``set1 >= set2`` and ``set1 > set2`` return whether ``set1`` is a superset
of ``set2`` (resp. strict)
* ``set1 | set2`` returns the union of the two recordsets, a new recordset
containing all records present in either source
* ``set1 & set2`` returns the intersection of two recordsets, a new recordset
+1 -1
View File
@@ -341,7 +341,7 @@ The ``request_sftp_access`` method returns a JSON dictionary containing the foll
SSH_KEYS=/path/to/your/authorized_keys
KEY="Aw7pItGVKFuZ_FOR3U8VFQ=="
REQUEST_ID="10534"
URL_PARAMS="key=${KEY}&request=${REQUEST}"
URL_PARAMS="key=${KEY}&request=${REQUEST_ID}"
curl -sS "${REQUEST_SFTP_ACCESS_URL}?${URL_PARAMS}" -F ssh_keys=@${SSH_KEYS} > request_sftp_result.json
+1 -1
View File
@@ -8826,7 +8826,7 @@ msgstr ""
#. module: base
#: code:addons/base/res/res_users.py:316
#, python-format
msgid "Invalid group by"
msgid "Invalid 'group by' parameter"
msgstr ""
#. module: base
+8 -3
View File
@@ -414,6 +414,7 @@ class IrModelFields(models.Model):
any(field.state != 'manual' for field in self):
raise UserError(_("This column contains module data and cannot be removed!"))
model_names = self.mapped('model')
self._drop_column()
res = super(IrModelFields, self).unlink()
@@ -423,6 +424,8 @@ class IrModelFields(models.Model):
self._cr.commit()
api.Environment.reset()
registry = Registry.new(self._cr.dbname)
models = registry.descendants(model_names, '_inherits')
registry.init_models(self._cr, models, dict(self._context, update_custom_fields=True))
registry.signal_registry_change()
return res
@@ -455,8 +458,9 @@ class IrModelFields(models.Model):
if vals['model'] in self.pool:
# setup models; this re-initializes model in registry
self.pool.setup_models(self._cr, partial=(not self.pool.ready))
# update database schema
self.pool.init_models(self._cr, [vals['model']], dict(self._context, update_custom_fields=True))
# update database schema of model and its descendant models
models = self.pool.descendants([vals['model']], '_inherits')
self.pool.init_models(self._cr, models, dict(self._context, update_custom_fields=True))
self.pool.signal_registry_change()
return res
@@ -538,7 +542,8 @@ class IrModelFields(models.Model):
if patched_models:
# update the database schema of the models to patch
self.pool.init_models(self._cr, patched_models, dict(self._context, update_custom_fields=True))
models = self.pool.descendants(patched_models, '_inherits')
self.pool.init_models(self._cr, models, dict(self._context, update_custom_fields=True))
if column_rename or patched_models:
self.pool.signal_registry_change()
+1 -1
View File
@@ -246,7 +246,7 @@ class QWeb(object):
"""
body = []
self.compile(template, options)(self, body.append, values or {})
return u''.join(body)
return u''.join(body).encode('utf8')
def compile(self, template, options):
""" Compile the given template into a rendering function::
+1 -1
View File
@@ -313,7 +313,7 @@ class Users(models.Model):
if self._uid != SUPERUSER_ID:
groupby_fields = set([groupby] if isinstance(groupby, basestring) else groupby)
if groupby_fields.intersection(USER_PRIVATE_FIELDS):
raise AccessError(_('Invalid group by'))
raise AccessError(_("Invalid 'group by' parameter"))
return super(Users, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
@api.model
+5 -5
View File
@@ -25,7 +25,7 @@ class TestQWebTField(TransactionCase):
field = etree.Element('span', {'t-field': u'company.name'})
company = self.env['res.company'].create({'name': "My Test Company"})
result = self.engine.render(field, {'company': company}).encode('utf-8')
result = self.engine.render(field, {'company': company})
self.assertEqual(
result,
'<span data-oe-model="res.company" data-oe-id="%d" '
@@ -41,7 +41,7 @@ class TestQWebTField(TransactionCase):
s = u"Testing «ταБЬℓσ»: 1<2 & 4+1>3, now 20% off!"
company = self.env['res.company'].create({'name': s})
result = self.engine.render(field, {'company': company}).encode('utf-8')
result = self.engine.render(field, {'company': company})
self.assertEqual(
result,
'<span data-oe-model="res.company" data-oe-id="%d" '
@@ -56,13 +56,13 @@ class TestQWebTField(TransactionCase):
field = etree.Element('td', {'t-field': u'company.name'})
with self.assertRaisesRegexp(QWebException, r'^RTE widgets do not work correctly'):
self.engine.render(field, {'company': None}).encode('utf-8')
self.engine.render(field, {'company': None})
def test_reject_t_tag(self):
field = etree.Element('t', {'t-field': u'company.name'})
with self.assertRaisesRegexp(QWebException, r'^t-field can not be used on a t element'):
self.engine.render(field, {'company': None}).encode('utf-8')
self.engine.render(field, {'company': None})
from copy import deepcopy
@@ -128,7 +128,7 @@ class TestQWeb(TransactionCase):
result = doc.find('result[@id="{}"]'.format(template)).text
self.assertEqual(
qweb.render(template, values=params, load=loader).strip().encode('utf-8'),
qweb.render(template, values=params, load=loader).strip(),
(result or u'').strip().encode('utf-8'),
template
)
+9 -3
View File
@@ -377,6 +377,7 @@ class BaseModel(object):
cr.execute(query, vals)
self.invalidate_cache()
@api.model
def _add_field(self, name, field):
""" Add the given ``field`` under the given ``name`` in the class """
@@ -536,6 +537,7 @@ class BaseModel(object):
'_register': False,
'_original_module': cls._module,
'_inherit_children': OrderedSet(), # names of children models
'_inherits_children': set(), # names of children models
'_fields': {}, # populated in _setup_base()
})
check_parent = cls._build_model_check_parent
@@ -633,7 +635,11 @@ class BaseModel(object):
cls._sequence = cls._sequence or (cls._table + '_id_seq')
cls._constraints = cls._constraints.values()
# recompute attributes of children models
# update _inherits_children of parent models
for parent_name in cls._inherits:
pool[parent_name]._inherits_children.add(cls._name)
# recompute attributes of _inherit_children models
for child_name in cls._inherit_children:
child_class = pool[child_name]
child_class._build_model_attributes(pool)
@@ -1933,7 +1939,7 @@ class BaseModel(object):
"""
result = self._read_group_raw(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
groupby = [groupby] if isinstance(groupby, basestring) else groupby
groupby = [groupby] if isinstance(groupby, basestring) else list(OrderedSet(groupby))
dt = [
f for f in groupby
if self._fields[f.split(':')[0]].type in ('date', 'datetime')
@@ -1957,7 +1963,7 @@ class BaseModel(object):
query = self._where_calc(domain)
fields = fields or [f.name for f in self._fields.itervalues() if f.store]
groupby = [groupby] if isinstance(groupby, basestring) else groupby
groupby = [groupby] if isinstance(groupby, basestring) else list(OrderedSet(groupby))
groupby_list = groupby[:1] if lazy else groupby
annotated_groupbys = [self._read_group_process_groupby(gb, query) for gb in groupby_list]
groupby_fields = [g['field'] for g in annotated_groupbys]
+23 -12
View File
@@ -4,16 +4,17 @@
""" Models registries.
"""
from collections import Mapping, defaultdict
from collections import Mapping, defaultdict, deque
from contextlib import closing
from operator import attrgetter
import logging
import os
import threading
import odoo
from .. import SUPERUSER_ID
from odoo.tools import assertion_report, lazy_classproperty, config, \
lazy_property, topological_sort, OrderedSet
from odoo.tools import (assertion_report, lazy_classproperty, config,
lazy_property, topological_sort, OrderedSet)
from odoo.tools.lru import LRU
_logger = logging.getLogger(__name__)
@@ -222,6 +223,22 @@ class Registry(Mapping):
env[model_name]._parent_store_compute()
self._init = False
def descendants(self, model_names, *kinds):
""" Return the models corresponding to ``model_names`` and all those
that inherit/inherits from them.
"""
assert all(kind in ('_inherit', '_inherits') for kind in kinds)
funcs = [attrgetter(kind + '_children') for kind in kinds]
models = OrderedSet()
queue = deque(model_names)
while queue:
model = self[queue.popleft()]
models.add(model)
for func in funcs:
queue.extend(func(model))
return models
def load(self, cr, module):
""" Load a given module in the registry, and return the names of the
modified models.
@@ -234,23 +251,17 @@ class Registry(Mapping):
"""
from .. import models
loaded_models = OrderedSet()
def mark_loaded(model):
# recursively mark model and its children
loaded_models.add(model._name)
for child_name in model._inherit_children:
mark_loaded(self[child_name])
lazy_property.reset_all(self)
# Instantiate registered classes (via the MetaModel automatic discovery
# or via explicit constructor call), and add them to the pool.
model_names = []
for cls in models.MetaModel.module_to_models.get(module.name, []):
# models register themselves in self.models
model = cls._build_model(self, cr)
mark_loaded(model)
model_names.append(model._name)
return list(loaded_models)
return self.descendants(model_names, '_inherit')
def setup_models(self, cr, partial=False):
""" Complete the setup of models.