summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2016-08-26 14:57:43 +0200
committerChristophe Simonis <chs@odoo.com>2016-08-26 14:57:43 +0200
commit717a4a51a5bf03f853aa2e4c229b929e2b87ea2d (patch)
treebc0a9d2c758e7055cb0cd23f12c1c33b04b2a872
parent438a3742e238ba074b880d5d563f2c7151a767c6 (diff)
parent7a4116cd03c56d694a1b6d39a523ac1cc3306f52 (diff)
[MERGE] forward port branch saas-10 up to 7a4116c
-rw-r--r--addons/account/models/account_invoice.py5
-rw-r--r--addons/account_asset/views/account_asset_view.xml6
-rw-r--r--addons/account_budget/account_budget.py7
-rw-r--r--addons/board/static/src/less/dashboard.less1
-rw-r--r--addons/hr_holidays/hr_holidays_view.xml2
-rw-r--r--addons/mail/static/src/js/chat_manager.js18
-rw-r--r--addons/mail/static/src/js/thread.js14
-rw-r--r--addons/mass_mailing/static/src/js/mass_mailing.js18
-rw-r--r--addons/mrp/report/bom_structure.py6
-rw-r--r--addons/payment_paypal/models/__init__.py1
-rw-r--r--addons/payment_paypal/models/account_invoice.py27
-rw-r--r--addons/point_of_sale/models/pos_order.py4
-rw-r--r--addons/sale/sales_team.py3
-rw-r--r--addons/stock/views/stock_pack_operation_views.xml6
-rw-r--r--addons/stock/views/stock_picking_views.xml2
-rw-r--r--addons/stock_account/stock_account.py19
-rw-r--r--addons/web/controllers/main.py20
-rw-r--r--addons/web_editor/static/src/js/backend.js11
-rw-r--r--addons/web_kanban/static/src/js/kanban_widgets.js2
-rw-r--r--addons/website/views/website_templates.xml5
-rw-r--r--addons/website_blog/controllers/main.py3
-rw-r--r--addons/website_blog/models/website_blog.py2
-rw-r--r--addons/website_event/views/website_event_templates.xml8
-rw-r--r--addons/website_portal/controllers/main.py10
-rw-r--r--addons/website_quote/views/website_quotation.xml2
-rw-r--r--doc/cla/corporate/pine-view-buildings.md15
-rw-r--r--openerp/addons/base/ir/ir_http.py18
-rw-r--r--openerp/addons/base/res/res_users.py3
-rw-r--r--openerp/addons/base/tests/__init__.py4
-rw-r--r--openerp/addons/base/tests/test_ir_http.py81
-rw-r--r--openerp/addons/base/tests/test_mimetypes.py52
-rw-r--r--openerp/addons/base/tests/test_uninstall.py2
-rw-r--r--openerp/http.py13
-rw-r--r--openerp/tools/mimetypes.py7
34 files changed, 314 insertions, 83 deletions
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index d75d0bbe546..f7b45c2d037 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -1156,7 +1156,10 @@ class AccountInvoiceLine(models.Model):
doc = etree.XML(res['arch'])
for node in doc.xpath("//field[@name='product_id']"):
if self._context['type'] in ('in_invoice', 'in_refund'):
- node.set('domain', "[('purchase_ok', '=', True)]")
+ # Hack to fix the stable version 8.0 -> saas-12
+ # purchase_ok will be moved from purchase to product in master #13271
+ if 'purchase_ok' in self.env['product.template']._fields:
+ node.set('domain', "[('purchase_ok', '=', True)]")
else:
node.set('domain', "[('sale_ok', '=', True)]")
res['arch'] = etree.tostring(doc)
diff --git a/addons/account_asset/views/account_asset_view.xml b/addons/account_asset/views/account_asset_view.xml
index e2c5bf87360..1ae2ef9a0af 100644
--- a/addons/account_asset/views/account_asset_view.xml
+++ b/addons/account_asset/views/account_asset_view.xml
@@ -131,11 +131,7 @@
<field name="value" widget="monetary" options="{'currency_field': 'currency_id'}" help="Gross value of asset"/>
<field name="salvage_value" widget="monetary" options="{'currency_field': 'currency_id'}" attrs="{'invisible': [('type','=','sale')]}"/>
<field name="value_residual" widget="monetary" options="{'currency_field': 'currency_id'}"/>
- <div>
- <label for="partner_id" string="Customer" attrs="{'invisible': [('type','==','purchase')]}"/>
- <label for="partner_id" string="Vendor" attrs="{'invisible': [('type','!=','purchase')]}"/>
- </div>
- <field name="partner_id" string="Partner" nolabel="1"/>
+ <field name="partner_id" string="Vendor" domain="[('supplier', '=', True)]"/>
<field name="invoice_id" string="Invoice" options="{'no_create': True}"/>
</group>
</group>
diff --git a/addons/account_budget/account_budget.py b/addons/account_budget/account_budget.py
index 727c7c0db89..d67a73565e6 100644
--- a/addons/account_budget/account_budget.py
+++ b/addons/account_budget/account_budget.py
@@ -95,7 +95,6 @@ class crossovered_budget_lines(osv.osv):
def _prac_amt(self, cr, uid, ids, context=None):
res = {}
- result = 0.0
if context is None:
context = {}
for line in self.browse(cr, uid, ids, context=context):
@@ -109,8 +108,10 @@ class crossovered_budget_lines(osv.osv):
"between to_date(%s,'yyyy-mm-dd') AND to_date(%s,'yyyy-mm-dd')) AND "
"general_account_id=ANY(%s)", (line.analytic_account_id.id, date_from, date_to,acc_ids,))
result = cr.fetchone()[0]
- if result is None:
- result = 0.00
+ if result is None:
+ result = 0.0
+ else:
+ result = 0.0
res[line.id] = result
return res
diff --git a/addons/board/static/src/less/dashboard.less b/addons/board/static/src/less/dashboard.less
index b0b41e65e34..36f0727c0ac 100644
--- a/addons/board/static/src/less/dashboard.less
+++ b/addons/board/static/src/less/dashboard.less
@@ -139,5 +139,4 @@
// Favorites menu in control panel
.o_add_to_dashboard {
display: none; // hidden by default
- .o-search-options-dropdown-custom-li;
}
diff --git a/addons/hr_holidays/hr_holidays_view.xml b/addons/hr_holidays/hr_holidays_view.xml
index bfe827774b2..3af60c1e3c7 100644
--- a/addons/hr_holidays/hr_holidays_view.xml
+++ b/addons/hr_holidays/hr_holidays_view.xml
@@ -250,7 +250,7 @@
<field name="view_type">form</field>
<field name="view_mode">calendar</field>
<field name="context">{'search_default_year': 1, 'search_default_group_employee': 1}</field>
- <field name="domain">[('holiday_type','=','employee'), ('type', '=', 'remove')]</field>
+ <field name="domain">[('holiday_type','=','employee'), ('type', '=', 'remove'), ('state', '!=', 'refuse')]</field>
<field name="search_view_id" ref="view_hr_holidays_filter"/>
</record>
diff --git a/addons/mail/static/src/js/chat_manager.js b/addons/mail/static/src/js/chat_manager.js
index e3a11b19197..4e0df85c703 100644
--- a/addons/mail/static/src/js/chat_manager.js
+++ b/addons/mail/static/src/js/chat_manager.js
@@ -213,6 +213,22 @@ function make_message (data) {
a.url = '/web/content/' + a.id + '?download=true';
});
+ // format date to the local only once by message
+ // can not be done in preprocess, since it alter the original value
+ if (msg.tracking_value_ids && msg.tracking_value_ids.length) {
+ _.each(msg.tracking_value_ids, function(f) {
+ if (_.contains(['date', 'datetime'], f.field_type)) {
+ var format = (f.field_type === 'date') ? 'LL' : 'LLL';
+ if (f.old_value) {
+ f.old_value = moment.utc(f.old_value).local().format(format);
+ }
+ if (f.new_value) {
+ f.new_value = moment.utc(f.new_value).local().format(format);
+ }
+ }
+ });
+ }
+
return msg;
}
@@ -653,7 +669,7 @@ var chat_manager = {
// post a message in a channel or execute a command
return ChannelModel.call(data.command ? 'execute_command' : 'message_post', [options.channel_id], _.extend(msg, {
message_type: 'comment',
- content_subtype: 'html',
+ content_subtype: 'plaintext',
subtype: 'mail.mt_comment',
command: data.command,
}));
diff --git a/addons/mail/static/src/js/thread.js b/addons/mail/static/src/js/thread.js
index 17b21c39a5b..ea593d21dfa 100644
--- a/addons/mail/static/src/js/thread.js
+++ b/addons/mail/static/src/js/thread.js
@@ -234,20 +234,6 @@ var Thread = Widget.extend({
msg.day = msg.date.format('LL');
}
- if (message.tracking_value_ids && message.tracking_value_ids.length) {
- _.each(message.tracking_value_ids, function(f) {
- if (_.contains(['date', 'datetime'], f.field_type)) {
- var format = (f.field_type === 'date') ? 'LL' : 'LLL';
- if (f.old_value) {
- f.old_value = moment.utc(f.old_value).local().format(format);
- }
- if (f.new_value) {
- f.new_value = moment.utc(f.new_value).local().format(format);
- }
- }
- });
- }
-
if (_.contains(this.expanded_msg_ids, message.id)) {
msg.expanded = true;
}
diff --git a/addons/mass_mailing/static/src/js/mass_mailing.js b/addons/mass_mailing/static/src/js/mass_mailing.js
index 3785cf87070..e2bcb8a09ad 100644
--- a/addons/mass_mailing/static/src/js/mass_mailing.js
+++ b/addons/mass_mailing/static/src/js/mass_mailing.js
@@ -1,6 +1,7 @@
odoo.define('mass_mailing.mass_mailing', function (require) {
var core = require('web.core');
+var FieldTextHtml = require('web_editor.backend').FieldTextHtml;
var KanbanRecord = require('web_kanban.Record');
var KanbanView = require('web_kanban.KanbanView');
@@ -25,4 +26,21 @@ KanbanView.include({
},
});
+FieldTextHtml.include({
+ get_datarecord: function() {
+ /* Avoid extremely long URIs by whitelisting fields in the datarecord
+ that get set as a get parameter */
+ var datarecord = this._super();
+ if (this.view.model === 'mail.mass_mailing') {
+ // these fields can potentially get very long, let's remove them
+ var blacklist = ['mailing_domain', 'contact_list_ids'];
+ for (var k in blacklist) {
+ delete datarecord[blacklist[k]];
+ }
+ delete datarecord[this.name];
+ }
+ return datarecord;
+ },
+});
+
});
diff --git a/addons/mrp/report/bom_structure.py b/addons/mrp/report/bom_structure.py
index 2618de3994b..f3ee9173ade 100644
--- a/addons/mrp/report/bom_structure.py
+++ b/addons/mrp/report/bom_structure.py
@@ -15,12 +15,12 @@ class bom_structure(report_sxw.rml_parse):
def get_children(self, object, level=0):
result = []
- def _get_rec(object, level):
+ def _get_rec(object, level, qty=1.0):
for l in object:
res = {}
res['pname'] = l.product_id.name_get()[0][1]
res['pcode'] = l.product_id.default_code
- res['pqty'] = l.product_qty
+ res['pqty'] = l.product_qty * qty
res['uname'] = l.product_uom.name
res['level'] = level
res['code'] = l.bom_id.code
@@ -28,7 +28,7 @@ class bom_structure(report_sxw.rml_parse):
if l.child_line_ids:
if level<6:
level += 1
- _get_rec(l.child_line_ids,level)
+ _get_rec(l.child_line_ids, level, qty=res['pqty'])
if level>0 and level<6:
level -= 1
return result
diff --git a/addons/payment_paypal/models/__init__.py b/addons/payment_paypal/models/__init__.py
index a1fe670f56f..e85dd257cf2 100644
--- a/addons/payment_paypal/models/__init__.py
+++ b/addons/payment_paypal/models/__init__.py
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
+import account_invoice
import paypal
diff --git a/addons/payment_paypal/models/account_invoice.py b/addons/payment_paypal/models/account_invoice.py
new file mode 100644
index 00000000000..7b7f3e377b7
--- /dev/null
+++ b/addons/payment_paypal/models/account_invoice.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from openerp import models, fields, api
+
+import werkzeug.urls
+
+class AccountInvoice(models.Model):
+ _inherit = 'account.invoice'
+
+ paypal_url = fields.Char('Paypal Url', compute='_compute_paypal_url')
+
+ @api.depends('type', 'number', 'company_id', 'currency_id')
+ def _compute_paypal_url(self):
+ for inv in self:
+ if inv.type == 'out_invoice' and inv.company_id.paypal_account:
+ params = {
+ "cmd": "_xclick",
+ "business": inv.company_id.paypal_account,
+ "item_name": "%s Invoice %s" % (inv.company_id.name, inv.number or ''),
+ "invoice": inv.number,
+ "amount": inv.residual,
+ "currency_code": inv.currency_id.name,
+ "button_subtype": "services",
+ "no_note": "1",
+ "bn": "Odoo_Invoice_PayNow_" + inv.currency_id.name,
+ }
+ inv.paypal_url = "https://www.paypal.com/cgi-bin/webscr?" + werkzeug.url_encode(params)
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index 19288db4696..2c4a644b421 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -223,7 +223,7 @@ class PosOrder(models.Model):
})
if data_type == 'product':
- key = ('product', values['partner_id'], (values['product_id'], values['name']), values['analytic_account_id'], values['debit'] > 0)
+ key = ('product', values['partner_id'], (values['product_id'], tuple(values['tax_ids'][0][2]), values['name']), values['analytic_account_id'], values['debit'] > 0)
elif data_type == 'tax':
key = ('tax', values['partner_id'], values['tax_line_id'], values['debit'] > 0)
elif data_type == 'counter_part':
@@ -287,7 +287,7 @@ class PosOrder(models.Model):
'analytic_account_id': self._prepare_analytic_account(line),
'credit': ((amount > 0) and amount) or 0.0,
'debit': ((amount < 0) and -amount) or 0.0,
- 'tax_ids': [(6, 0, line.tax_ids.ids)],
+ 'tax_ids': [(6, 0, line.tax_ids_after_fiscal_position.ids)],
'partner_id': partner_id
})
diff --git a/addons/sale/sales_team.py b/addons/sale/sales_team.py
index 309419eb97f..a234f867cf4 100644
--- a/addons/sale/sales_team.py
+++ b/addons/sale/sales_team.py
@@ -35,7 +35,8 @@ class crm_team(osv.Model):
('state', 'in', ['open', 'paid']),
('team_id', '=', team),
('date', '<=', date.today()),
- ('date', '>=', date.today().replace(day=1))
+ ('date', '>=', date.today().replace(day=1)),
+ ('type', 'in', ['out_invoice', 'out_refund']),
]
invoices = obj_inv.search_read(cr, uid, domain, ['amount_untaxed_signed'], context=context)
res[team] = sum([inv['amount_untaxed_signed'] for inv in invoices])
diff --git a/addons/stock/views/stock_pack_operation_views.xml b/addons/stock/views/stock_pack_operation_views.xml
index 3a0aab844e2..5f7e46850e6 100644
--- a/addons/stock/views/stock_pack_operation_views.xml
+++ b/addons/stock/views/stock_pack_operation_views.xml
@@ -25,8 +25,8 @@
<field name="location_dest_id"/>
</group>
<group string="Packages" groups="stock.group_tracking_lot">
- <field name="package_id" attrs="{'required': [('product_id', '=', False)]}" context="{'location_id': location_id}"/>
- <field name="result_package_id" context="{'location_id': location_dest_id}"/>
+ <field name="package_id" attrs="{'required': [('product_id', '=', False)]}" domain="[('location_id', '=', location_id)]" context="{'location_id': location_id}"/>
+ <field name="result_package_id" domain="[('location_id', '=', location_dest_id)]" context="{'location_id': location_dest_id}"/>
</group>
</group>
</form>
@@ -115,4 +115,4 @@
</form>
</field>
</record>
-</odoo> \ No newline at end of file
+</odoo>
diff --git a/addons/stock/views/stock_picking_views.xml b/addons/stock/views/stock_picking_views.xml
index 53f51c8f35d..cbfb63d4324 100644
--- a/addons/stock/views/stock_picking_views.xml
+++ b/addons/stock/views/stock_picking_views.xml
@@ -315,7 +315,7 @@
<field name="picking_type_entire_packs" invisible="1"/>
<field name="pack_operation_pack_ids" options="{'reload_on_button': True}" attrs="{'invisible': [('pack_operation_pack_ids', '=', []), ('picking_type_entire_packs', '=', False)]}" context="{'default_picking_id': id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_picking_id': id}" groups="stock.group_tracking_lot">
<tree editable="bottom" decoration-muted="qty_done&gt;0">
- <field name="package_id" required="1" string="Package To Move"/>
+ <field name="package_id" required="1" string="Package To Move" domain="[('location_id', '=', location_id)]"/>
<field name="location_id" domain="[('id', 'child_of', parent.location_id)]" invisible="1"/>
<field name="from_loc" groups="stock.group_stock_multi_locations,stock.group_tracking_lot"/>
<field name="to_loc" groups="stock.group_stock_multi_locations,stock.group_tracking_lot"/>
diff --git a/addons/stock_account/stock_account.py b/addons/stock_account/stock_account.py
index 798e8fdda5d..1b59afdc74b 100644
--- a/addons/stock_account/stock_account.py
+++ b/addons/stock_account/stock_account.py
@@ -309,7 +309,11 @@ class stock_quant(osv.osv):
debit_value = currency_obj.round(cr, uid, move.company_id.currency_id, valuation_amount * qty)
#check that all data is correct
if move.company_id.currency_id.is_zero(debit_value):
- raise UserError(_("The found valuation amount for product %s is zero. Which means there is probably a configuration error. Check the costing method and the standard price") % (move.product_id.name,))
+ if move.product_id.cost_method == 'standard':
+ raise UserError(_("The found valuation amount for product %s is zero. Which means there is probably a configuration error. Check the costing method and the standard price") % (move.product_id.name,))
+ else:
+ return []
+
credit_value = debit_value
if move.product_id.cost_method == 'average' and move.company_id.anglo_saxon_accounting:
@@ -380,12 +384,13 @@ class stock_quant(osv.osv):
move_obj = self.pool.get('account.move')
for cost, qty in quant_cost_qty.items():
move_lines = self._prepare_account_move_line(cr, uid, move, qty, cost, credit_account_id, debit_account_id, context=context)
- date = context.get('force_period_date', fields.date.context_today(self, cr, uid, context=context))
- new_move = move_obj.create(cr, uid, {'journal_id': journal_id,
- 'line_ids': move_lines,
- 'date': date,
- 'ref': move.picking_id.name}, context=context)
- move_obj.post(cr, uid, [new_move], context=context)
+ if move_lines:
+ date = context.get('force_period_date', fields.date.context_today(self, cr, uid, context=context))
+ new_move = move_obj.create(cr, uid, {'journal_id': journal_id,
+ 'line_ids': move_lines,
+ 'date': date,
+ 'ref': move.picking_id.name}, context=context)
+ move_obj.post(cr, uid, [new_move], context=context)
#def _reconcile_single_negative_quant(self, cr, uid, to_solve_quant, quant, quant_neg, qty, context=None):
# move = self._get_latest_move(cr, uid, to_solve_quant, context=context)
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index d9774cc5c55..902e7500650 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -34,7 +34,7 @@ from openerp.tools.translate import _
from openerp.tools import ustr
from openerp.tools.misc import str2bool, xlwt
from openerp import http
-from openerp.http import request, serialize_exception as _serialize_exception
+from openerp.http import request, serialize_exception as _serialize_exception, content_disposition
from openerp.exceptions import AccessError
_logger = logging.getLogger(__name__)
@@ -413,10 +413,6 @@ def xml2json_from_elementtree(el, preserve_whitespaces=False):
res["children"] = kids
return res
-def content_disposition(filename):
- return request.registry['ir.http'].content_disposition(filename)
-
-
def binary_content(xmlid=None, model='ir.attachment', id=None, field='datas', unique=False, filename=None, filename_field='datas_fname', download=False, mimetype=None, default_mimetype='application/octet-stream', env=None):
return request.registry['ir.http'].binary_content(
xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, filename_field=filename_field,
@@ -937,6 +933,11 @@ class Binary(http.Controller):
addons_path = http.addons_manifest['web']['addons_path']
return open(os.path.join(addons_path, 'web', 'static', 'src', 'img', image), 'rb').read()
+ def force_contenttype(self, headers, contenttype='image/png'):
+ dictheaders = dict(headers)
+ dictheaders['Content-Type'] = contenttype
+ return dictheaders.items()
+
@http.route(['/web/content',
'/web/content/<string:xmlid>',
'/web/content/<string:xmlid>/<string:filename>',
@@ -995,8 +996,15 @@ class Binary(http.Controller):
if height > 500:
height = 500
content = openerp.tools.image_resize_image(base64_source=content, size=(width or None, height or None), encoding='base64', filetype='PNG')
+ # resize force png as filetype
+ headers = self.force_contenttype(headers, contenttype='image/png')
+
+ if content:
+ image_base64 = base64.b64decode(content)
+ else:
+ image_base64 = self.placeholder(image='placeholder.png') # could return (contenttype, content) in master
+ headers = self.force_contenttype(headers, contenttype='image/png')
- image_base64 = content and base64.b64decode(content) or self.placeholder()
headers.append(('Content-Length', len(image_base64)))
response = request.make_response(image_base64, headers)
response.status_code = status
diff --git a/addons/web_editor/static/src/js/backend.js b/addons/web_editor/static/src/js/backend.js
index 38f25e42103..468fab0c728 100644
--- a/addons/web_editor/static/src/js/backend.js
+++ b/addons/web_editor/static/src/js/backend.js
@@ -220,9 +220,12 @@ var FieldTextHtml = widget.extend({
this.$translate = $();
return def;
},
+ get_datarecord: function() {
+ return this.view.get_fields_values();
+ },
get_url: function (_attr) {
var src = this.options.editor_url || "/web_editor/field/html";
- var datarecord = this.view.get_fields_values();
+ var datarecord = this.get_datarecord();
var attr = {
'model': this.view.model,
@@ -269,7 +272,6 @@ var FieldTextHtml = widget.extend({
delete datarecord[this.name];
src += "&datarecord="+ encodeURIComponent(JSON.stringify(datarecord));
-
return src;
},
initialize_content: function () {
@@ -384,4 +386,9 @@ core.form_widget_registry
.add('html', FieldTextHtmlSimple)
.add('html_frame', FieldTextHtml);
+return {
+ FieldTextHtmlSimple: FieldTextHtmlSimple,
+ FieldTextHtml: FieldTextHtml,
+};
+
});
diff --git a/addons/web_kanban/static/src/js/kanban_widgets.js b/addons/web_kanban/static/src/js/kanban_widgets.js
index 1c6e5532aa5..a29105ed9b5 100644
--- a/addons/web_kanban/static/src/js/kanban_widgets.js
+++ b/addons/web_kanban/static/src/js/kanban_widgets.js
@@ -139,7 +139,7 @@ var KanbanSelection = AbstractField.extend({
var current_state = _.find(this.states, function(state) {
return state.name === self.get('value');
- });
+ }) || {state_class: ''};
self.$el = $(QWeb.render("KanbanSelection", {
current_state_class: current_state.state_class,
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml
index d73a0d9de9c..73008844b7c 100644
--- a/addons/website/views/website_templates.xml
+++ b/addons/website/views/website_templates.xml
@@ -910,9 +910,8 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml
"widget": "contact",
"fields": ["name", "address", "phone", "mobile", "fax", "email"]}'/>
</address>
- <a t-att-href="res_company.google_map_link()" target="_BLANK">
- <img class="thumbnail img-responsive" t-att-src="res_company.google_map_img()" />
- </a>
+ <!-- FORWARD-PORT UP TO SAAS-12 -->
+ <a t-att-href="res_company.google_map_link()" target="_BLANK">Visualize on Google Maps</a>
</template>
<template id="contactus" name="Contact us" page="True">
diff --git a/addons/website_blog/controllers/main.py b/addons/website_blog/controllers/main.py
index 824abc407d7..281e2292f9d 100644
--- a/addons/website_blog/controllers/main.py
+++ b/addons/website_blog/controllers/main.py
@@ -142,10 +142,11 @@ class WebsiteBlog(http.Controller):
blog_posts = blog_post_obj.browse(cr, uid, blog_post_ids, context=context)
pager = request.website.pager(
- url=blog_url(),
+ url=request.httprequest.path,
total=len(blog_posts),
page=page,
step=self._blog_post_per_page,
+ url_args=opt,
)
pager_begin = (page - 1) * self._blog_post_per_page
pager_end = page * self._blog_post_per_page
diff --git a/addons/website_blog/models/website_blog.py b/addons/website_blog/models/website_blog.py
index 8bccf9d2866..b14039a5df7 100644
--- a/addons/website_blog/models/website_blog.py
+++ b/addons/website_blog/models/website_blog.py
@@ -266,7 +266,7 @@ class BlogPost(osv.Model):
'type': 'ir.actions.act_url',
'url': '/blog/%s/post/%s' % (post.blog_id.id, post.id),
'target': 'self',
- 'res_id': self.id,
+ 'res_id': post.id,
}
def _notification_get_recipient_groups(self, cr, uid, ids, message, recipients, context=None):
diff --git a/addons/website_event/views/website_event_templates.xml b/addons/website_event/views/website_event_templates.xml
index b56bb25dfde..c5f28beb6e5 100644
--- a/addons/website_event/views/website_event_templates.xml
+++ b/addons/website_event/views/website_event_templates.xml
@@ -334,9 +334,8 @@
<h4>Where</h4>
</div>
<div class="panel-body">
- <a t-att-href="event.google_map_link()" target="_BLANK">
- <img t-att-src="event.google_map_img()" width="100%%"/>
- </a>
+ <!-- FORWARD-PORT UP TO SAAS-12 -->
+ <a t-att-href="event.google_map_link()" target="_BLANK">Visualize on Google Maps</a>
<div itemprop="location" class="mt16 mb8" t-field="event.address_id" t-field-options='{
"widget": "contact",
"fields": ["address", "phone", "mobile", "fax", "email"]
@@ -452,8 +451,7 @@
<strong>
Ticket Type #<t t-raw="counter_type"/>: <t t-esc="ticket['name']"/> (
<t t-if="ticket['price'] > 0">
- <t t-esc="ticket['price']"/>
- <t t-esc="website.pricelist_id.currency_id.symbol"/>
+ <t t-esc="ticket['price']" t-esc-options='{"widget": "monetary", "display_currency": "website.pricelist_id.currency_id"}'/>
</t>
<t t-if="ticket['price'] == 0">Free</t>)
</strong>
diff --git a/addons/website_portal/controllers/main.py b/addons/website_portal/controllers/main.py
index dc900002be7..cf8e818fc3b 100644
--- a/addons/website_portal/controllers/main.py
+++ b/addons/website_portal/controllers/main.py
@@ -37,6 +37,16 @@ class website_account(http.Controller):
values.update(post)
if not error:
post.update({'zip': post.pop('zipcode', '')})
+ if partner.type == "contact":
+ address_fields = {
+ 'city': post.pop('city'),
+ 'street': post.pop('street'),
+ 'street2': post.pop('street2'),
+ 'zip': post.pop('zip'),
+ 'country_id': post.pop('country_id'),
+ 'state_id': post.pop('state_id')
+ }
+ partner.commercial_partner_id.write(address_fields)
partner.sudo().write(post)
if redirect:
return request.redirect(redirect)
diff --git a/addons/website_quote/views/website_quotation.xml b/addons/website_quote/views/website_quotation.xml
index 75b1086e6e9..ee48f5397d2 100644
--- a/addons/website_quote/views/website_quotation.xml
+++ b/addons/website_quote/views/website_quotation.xml
@@ -446,7 +446,7 @@
<small t-field="quotation.state"/>
<div t-ignore="true" class="pull-right css_editable_mode_hidden hidden-print">
<ul class="list-inline">
- <li groups="base.group_website_publisher">
+ <li groups="base.group_sale_salesman">
<a t-if="quotation.state not in ('manual')" class="btn btn-info" t-att-href="'/web#return_label=Website&amp;model=%s&amp;id=%s&amp;action=%s&amp;view_type=form' % (quotation._name, quotation.id, action)">Edit Quote</a>
<a t-if="quotation.state in ('manual')" class=" btn btn-info" t-att-href="'/web#return_label=Website&amp;model=%s&amp;id=%s&amp;action=%s&amp;view_type=form' % (quotation._name, quotation.id, action)">Back to Sale Order</a>
</li>
diff --git a/doc/cla/corporate/pine-view-buildings.md b/doc/cla/corporate/pine-view-buildings.md
new file mode 100644
index 00000000000..78caa110a70
--- /dev/null
+++ b/doc/cla/corporate/pine-view-buildings.md
@@ -0,0 +1,15 @@
+USA, 08/13/2016
+
+Pine View Buildings agrees to the terms of the Odoo Corporate Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Freeman Helmuth freeman@pineview.biz https://github.com/busyfritz
+
+List of contributors:
+
+Freeman Helmuth freeman@pineview.biz https://github.com/busyfritz
diff --git a/openerp/addons/base/ir/ir_http.py b/openerp/addons/base/ir/ir_http.py
index 6e6cb250312..4907ce131a7 100644
--- a/openerp/addons/base/ir/ir_http.py
+++ b/openerp/addons/base/ir/ir_http.py
@@ -21,7 +21,8 @@ import werkzeug.utils
import odoo
from odoo import api, http, models, tools, SUPERUSER_ID
from odoo.exceptions import AccessDenied, AccessError
-from odoo.http import request, STATIC_CACHE
+from odoo.http import request, STATIC_CACHE, content_disposition
+from odoo.tools.mimetypes import guess_mimetype
from odoo.modules.module import get_resource_path, get_module_path
_logger = logging.getLogger(__name__)
@@ -210,16 +211,7 @@ class IrHttp(models.AbstractModel):
return self._routing_map
def content_disposition(self, filename):
- filename = tools.ustr(filename)
- escaped = urllib2.quote(filename.encode('utf8'))
- browser = request.httprequest.user_agent.browser
- version = int((request.httprequest.user_agent.version or '0').split('.')[0])
- if browser == 'msie' and version < 9:
- return "attachment; filename=%s" % escaped
- elif browser == 'safari' and version < 537:
- return u"attachment; filename=%s" % filename.encode('ascii', 'replace')
- else:
- return "attachment; filename*=UTF-8''%s" % escaped
+ return content_disposition(filename)
def binary_content(self, xmlid=None, model='ir.attachment', id=None, field='datas', unique=False, filename=None, filename_field='datas_fname', download=False, mimetype=None, default_mimetype='application/octet-stream', env=None):
""" Get file, attachment or downloadable content
@@ -305,7 +297,8 @@ class IrHttp(models.AbstractModel):
attach_mimetype = env['ir.attachment'].search_read(domain=[('res_model', '=', model), ('res_id', '=', id), ('res_field', '=', field)], fields=['mimetype'], limit=1)
mimetype = attach_mimetype and attach_mimetype[0]['mimetype']
if not mimetype:
- mimetype = default_mimetype
+ mimetype = guess_mimetype(base64.b64decode(content), default=default_mimetype)
+
headers += [('Content-Type', mimetype), ('X-Content-Type-Options', 'nosniff')]
# cache
@@ -318,7 +311,6 @@ class IrHttp(models.AbstractModel):
# content-disposition default name
if download:
headers.append(('Content-Disposition', self.content_disposition(filename)))
-
return (status, headers, content)
diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py
index 775384ccbb7..73b909607d3 100644
--- a/openerp/addons/base/res/res_users.py
+++ b/openerp/addons/base/res/res_users.py
@@ -678,8 +678,7 @@ class GroupsView(models.Model):
@api.multi
def write(self, values):
res = super(GroupsView, self).write(values)
- if 'category_id' in values:
- self._update_user_groups_view()
+ self._update_user_groups_view()
# ir_values.get_actions() depends on action records
self.env['ir.values'].clear_caches()
return res
diff --git a/openerp/addons/base/tests/__init__.py b/openerp/addons/base/tests/__init__.py
index e9414be9011..2325c11aa8e 100644
--- a/openerp/addons/base/tests/__init__.py
+++ b/openerp/addons/base/tests/__init__.py
@@ -7,12 +7,14 @@ import test_expression
import test_func
import test_ir_actions
import test_ir_attachment
+import test_ir_http
import test_ir_filters
import test_ir_sequence
import test_ir_sequence_date_range
import test_ir_values
import test_mail
import test_menu
+import test_mimetypes
import test_orm
import test_ormcache
import test_osv
@@ -21,7 +23,7 @@ import test_res_config
import test_res_lang
import test_search
import test_translate
-#import test_uninstall
+#import test_uninstall # loop
import test_user_has_group
import test_view_validation
import test_views
diff --git a/openerp/addons/base/tests/test_ir_http.py b/openerp/addons/base/tests/test_ir_http.py
new file mode 100644
index 00000000000..649cd069816
--- /dev/null
+++ b/openerp/addons/base/tests/test_ir_http.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+from openerp.tests import common
+import openerp
+
+GIF = "R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs="
+
+
+class test_ir_http_mimetype(common.TransactionCase):
+
+ def test_ir_http_mimetype_attachment(self):
+ """ Test mimetype for attachment """
+ attachment = self.env['ir.attachment'].create({
+ 'datas': GIF,
+ 'name': 'Test mimetype gif',
+ 'datas_fname': 'file.gif'})
+
+ status, headers, content = self.env['ir.http'].binary_content(
+ id=attachment.id,
+ mimetype=None,
+ default_mimetype='application/octet-stream',
+ env=self.env
+ )
+ mimetype = dict(headers).get('Content-Type')
+ self.assertEqual(mimetype, 'image/gif')
+
+ def test_ir_http_mimetype_attachment_name(self):
+ """ Test mimetype for attachment with bad name"""
+ attachment = self.env['ir.attachment'].create({
+ 'datas': GIF,
+ 'name': 'Test mimetype gif with png name',
+ 'datas_fname': 'file.png'})
+
+ status, headers, content = self.env['ir.http'].binary_content(
+ id=attachment.id,
+ mimetype=None,
+ default_mimetype='application/octet-stream',
+ env=self.env
+ )
+ mimetype = dict(headers).get('Content-Type')
+ # TODO: fix and change it in master, should be image/gif
+ self.assertEqual(mimetype, 'image/png')
+
+ def test_ir_http_mimetype_basic_field(self):
+ """ Test mimetype for classic field """
+ partner = self.env['res.partner'].create({
+ 'image': GIF,
+ 'name': 'Test mimetype basic field',
+ })
+
+ status, headers, content = self.env['ir.http'].binary_content(
+ model='res.partner',
+ id=partner.id,
+ field='image',
+ default_mimetype='application/octet-stream',
+ env=self.env
+ )
+ mimetype = dict(headers).get('Content-Type')
+ self.assertEqual(mimetype, 'image/gif')
+
+ def test_ir_http_mimetype_computed_field(self):
+ """ Test mimetype for computed field wich resize picture"""
+ prop = self.env['ir.property'].create({
+ 'fields_id': self.env['ir.model.fields'].search([], limit=1).id,
+ 'name': "Property binary",
+ 'value_binary': GIF,
+ 'type': 'binary',
+ })
+
+ resized = openerp.tools.image_get_resized_images(prop.value_binary, return_big=True, avoid_resize_medium=True)['image_small']
+ # Simul computed field which resize and that is not attachement=True (E.G. on product)
+ prop.write({'value_binary': resized})
+ status, headers, content = self.env['ir.http'].binary_content(
+ model='ir.property',
+ id=prop.id,
+ field='value_binary',
+ default_mimetype='application/octet-stream',
+ env=self.env
+ )
+ mimetype = dict(headers).get('Content-Type')
+ self.assertEqual(mimetype, 'image/gif')
diff --git a/openerp/addons/base/tests/test_mimetypes.py b/openerp/addons/base/tests/test_mimetypes.py
new file mode 100644
index 00000000000..0d3ce6b71fc
--- /dev/null
+++ b/openerp/addons/base/tests/test_mimetypes.py
@@ -0,0 +1,52 @@
+import base64
+import unittest
+
+from openerp.tools.mimetypes import guess_mimetype
+
+PNG = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC'
+GIF = "R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs="
+BMP = """Qk1+AAAAAAAAAHoAAABsAAAAAQAAAAEAAAABABgAAAAAAAQAAAATCwAAEwsAAAAAAAAAAAAAQkdScwAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAD///8A"""
+JPG = """/9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAP
+//////////////////////////////////////////////////////////////////////////////////////2wBDAf///////
+///////////////////////////////////////////////////////////////////////////////wgARCAABAAEDAREAAhEB
+AxEB/8QAFAABAAAAAAAAAAAAAAAAAAAAAv/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAUf/xAAUEAEAAAAAAAA
+AAAAAAAAAAAAA/9oACAEBAAEFAn//xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAEDAQE/AX//xAAUEQEAAAAAAAAAAAAAAAAAAA
+AA/9oACAECAQE/AX//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAY/An//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBA
+AE/IX//2gAMAwEAAgADAAAAEB//xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAEDAQE/EH//xAAUEQEAAAAAAAAAAAAAAAAAAAAA
+/9oACAECAQE/EH//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAE/EH//2Q=="""
+
+
+class test_guess_mimetype(unittest.TestCase):
+
+ def test_default_mimetype_empty(self):
+ mimetype = guess_mimetype('')
+ self.assertEqual(mimetype, 'application/octet-stream')
+
+ def test_default_mimetype(self):
+ mimetype = guess_mimetype('', default='test')
+ self.assertEqual(mimetype, 'test')
+
+ def test_mimetype_png(self):
+ content = base64.b64decode(PNG)
+ mimetype = guess_mimetype(content, default='test')
+ self.assertEqual(mimetype, 'image/png')
+
+ def test_mimetype_bmp(self):
+ content = base64.b64decode(BMP)
+ mimetype = guess_mimetype(content, default='test')
+ self.assertEqual(mimetype, 'image/bmp')
+
+ def test_mimetype_jpg(self):
+ content = base64.b64decode(JPG)
+ mimetype = guess_mimetype(content, default='test')
+ self.assertEqual(mimetype, 'image/jpeg')
+
+ def test_mimetype_gif(self):
+ content = base64.b64decode(GIF)
+ mimetype = guess_mimetype(content, default='test')
+ self.assertEqual(mimetype, 'image/gif')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/openerp/addons/base/tests/test_uninstall.py b/openerp/addons/base/tests/test_uninstall.py
index 8bb658d61ba..a99240d703b 100644
--- a/openerp/addons/base/tests/test_uninstall.py
+++ b/openerp/addons/base/tests/test_uninstall.py
@@ -4,7 +4,7 @@ import unittest
import openerp
from openerp import SUPERUSER_ID
-import common
+from openerp.tests import common
ADMIN_USER_ID = common.ADMIN_USER_ID
diff --git a/openerp/http.py b/openerp/http.py
index d837131d52d..45db589aa28 100644
--- a/openerp/http.py
+++ b/openerp/http.py
@@ -20,6 +20,7 @@ import sys
import threading
import time
import traceback
+import urllib2
import urlparse
import warnings
from zlib import adler32
@@ -1809,6 +1810,18 @@ def send_file(filepath_or_fp, mimetype=None, as_attachment=False, filename=None,
rv.headers.pop('x-sendfile', None)
return rv
+def content_disposition(filename):
+ filename = openerp.tools.ustr(filename)
+ escaped = urllib2.quote(filename.encode('utf8'))
+ browser = request.httprequest.user_agent.browser
+ version = int((request.httprequest.user_agent.version or '0').split('.')[0])
+ if browser == 'msie' and version < 9:
+ return "attachment; filename=%s" % escaped
+ elif browser == 'safari' and version < 537:
+ return u"attachment; filename=%s" % filename.encode('ascii', 'replace')
+ else:
+ return "attachment; filename*=UTF-8''%s" % escaped
+
#----------------------------------------------------------
# RPC controller
#----------------------------------------------------------
diff --git a/openerp/tools/mimetypes.py b/openerp/tools/mimetypes.py
index cf5a9debc90..581dcb27bd6 100644
--- a/openerp/tools/mimetypes.py
+++ b/openerp/tools/mimetypes.py
@@ -103,10 +103,11 @@ _Entry = collections.namedtuple('_Entry', ['mimetype', 'signatures', 'discrimina
_mime_mappings = (
# pdf
_Entry('application/pdf', ['%PDF'], []),
- # jpg, jpeg, png, gif
+ # jpg, jpeg, png, gif, bmp
_Entry('image/jpeg', ['\xFF\xD8\xFF\xE0', '\xFF\xD8\xFF\xE2', '\xFF\xD8\xFF\xE3', '\xFF\xD8\xFF\xE1'], []),
_Entry('image/png', ['\x89PNG\r\n\x1A\n'], []),
_Entry('image/gif', ['GIF87a', 'GIF89a'], []),
+ _Entry('image/bmp', ['BM'], []),
# OLECF files in general (Word, Excel, PPT, default to word because why not?)
_Entry('application/msword', ['\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1', '\x0D\x44\x4F\x43'], [
_check_olecf
@@ -114,7 +115,7 @@ _mime_mappings = (
# zip, but will include jar, odt, ods, odp, docx, xlsx, pptx, apk
_Entry('application/zip', ['PK\x03\x04'], [_check_ooxml, _check_open_container_format]),
)
-def guess_mimetype(bin_data):
+def guess_mimetype(bin_data, default='application/octet-stream'):
""" Attempts to guess the mime type of the provided binary data, similar
to but significantly more limited than libmagic
@@ -140,7 +141,7 @@ def guess_mimetype(bin_data):
# if no discriminant or no discriminant matches, return
# primary mime type
return entry.mimetype
- return 'application/octet-stream'
+ return default
try: