[MERGE] forward port branch 9.0 up to c578fb7
This commit is contained in:
@@ -1132,7 +1132,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)
|
||||
|
||||
@@ -123,11 +123,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>
|
||||
|
||||
@@ -139,5 +139,4 @@
|
||||
// Favorites menu in control panel
|
||||
.o_add_to_dashboard {
|
||||
display: none; // hidden by default
|
||||
.o-search-options-dropdown-custom-li;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -206,6 +206,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;
|
||||
}
|
||||
|
||||
@@ -623,7 +639,7 @@ var chat_manager = {
|
||||
// post a message in a channel
|
||||
return ChannelModel.call('message_post', [options.channel_id], _.extend(msg, {
|
||||
message_type: 'comment',
|
||||
content_subtype: 'html',
|
||||
content_subtype: 'plaintext',
|
||||
subtype: 'mail.mt_comment',
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import account_invoice
|
||||
import paypal
|
||||
import res_company
|
||||
|
||||
@@ -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)
|
||||
@@ -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])
|
||||
|
||||
@@ -747,8 +747,8 @@
|
||||
<field name="location_dest_id" domain="[('id', 'child_of', picking_destination_location_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>
|
||||
@@ -959,7 +959,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>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"/>
|
||||
|
||||
@@ -303,7 +303,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:
|
||||
@@ -374,12 +378,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)
|
||||
|
||||
@@ -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__)
|
||||
@@ -415,10 +415,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,
|
||||
@@ -941,6 +937,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>',
|
||||
@@ -999,8 +1000,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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -132,10 +132,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
|
||||
|
||||
@@ -249,7 +249,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):
|
||||
|
||||
@@ -451,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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -396,7 +396,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&model=%s&id=%s&action=%s&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&model=%s&id=%s&action=%s&view_type=form' % (quotation._name, quotation.id, action)">Back to Sale Order</a>
|
||||
</li>
|
||||
|
||||
@@ -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
|
||||
@@ -22,7 +22,8 @@ import openerp
|
||||
import openerp.exceptions
|
||||
import openerp.models
|
||||
from openerp import http
|
||||
from openerp.http import request, STATIC_CACHE
|
||||
from openerp.tools.mimetypes import guess_mimetype
|
||||
from openerp.http import request, STATIC_CACHE, content_disposition
|
||||
from openerp.modules.module import get_resource_path, get_module_path
|
||||
from openerp.osv import osv, orm
|
||||
|
||||
@@ -209,16 +210,7 @@ class ir_http(osv.AbstractModel):
|
||||
return self._routing_map
|
||||
|
||||
def content_disposition(self, 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
|
||||
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
|
||||
@@ -304,7 +296,8 @@ class ir_http(osv.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
|
||||
@@ -317,7 +310,6 @@ class ir_http(osv.AbstractModel):
|
||||
# content-disposition default name
|
||||
if download:
|
||||
headers.append(('Content-Disposition', self.content_disposition(filename)))
|
||||
|
||||
return (status, headers, content)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import sys
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
import urllib2
|
||||
import urlparse
|
||||
import warnings
|
||||
from zlib import adler32
|
||||
@@ -1808,6 +1809,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
|
||||
#----------------------------------------------------------
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user