diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py index cab6b226479..b66d15cc165 100644 --- a/addons/account/models/partner.py +++ b/addons/account/models/partner.py @@ -123,6 +123,8 @@ class AccountFiscalPosition(models.Model): if not country_id: return False base_domain = [('auto_apply', '=', True), ('vat_required', '=', vat_required)] + if self.env.context.get('force_company'): + base_domain.append(('company_id', '=', self.env.context.get('force_company'))) null_state_dom = state_domain = [('state_ids', '=', False)] null_zip_dom = zip_domain = [('zip_from', '=', 0), ('zip_to', '=', 0)] null_country_dom = [('country_id', '=', False), ('country_group_id', '=', False)] diff --git a/addons/account/views/account_journal_dashboard_view.xml b/addons/account/views/account_journal_dashboard_view.xml index e00f0cdc0f6..51b87edf0c6 100644 --- a/addons/account/views/account_journal_dashboard_view.xml +++ b/addons/account/views/account_journal_dashboard_view.xml @@ -234,7 +234,7 @@
- Balance in Odoo + Balance in GL
diff --git a/addons/base_import/controllers.py b/addons/base_import/controllers.py index 9a49384900f..84b645f149f 100644 --- a/addons/base_import/controllers.py +++ b/addons/base_import/controllers.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import cgi import json from openerp.http import Controller, route @@ -15,4 +16,4 @@ class ImportController(Controller): }, req.context) return 'window.top.%s(%s)' % ( - jsonp, json.dumps({'result': written})) + cgi.escape(jsonp), json.dumps({'result': written})) diff --git a/addons/base_import/static/csv/database_import_test.sql b/addons/base_import/static/csv/database_import_test.sql index 1f79179145e..13a735d106d 100644 --- a/addons/base_import/static/csv/database_import_test.sql +++ b/addons/base_import/static/csv/database_import_test.sql @@ -2,12 +2,30 @@ -- PostgreSQL database dump -- +-- Dumped from database version 9.5.2 +-- Dumped by pg_dump version 9.5.2 + SET statement_timeout = 0; +SET lock_timeout = 0; SET client_encoding = 'UTF8'; -SET standard_conforming_strings = off; +SET standard_conforming_strings = on; SET check_function_bodies = false; SET client_min_messages = warning; -SET escape_string_warning = off; +SET row_security = off; + +-- +-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; + + +-- +-- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: - +-- + +COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; + SET search_path = public, pg_catalog; @@ -16,7 +34,7 @@ SET default_tablespace = ''; SET default_with_oids = false; -- --- Name: companies; Type: TABLE; Schema: public; Owner: fp; Tablespace: +-- Name: companies; Type: TABLE; Schema: public; Owner: - -- CREATE TABLE companies ( @@ -25,38 +43,27 @@ CREATE TABLE companies ( ); -ALTER TABLE public.companies OWNER TO fp; - -- --- Name: companies_id_seq; Type: SEQUENCE; Schema: public; Owner: fp +-- Name: companies_id_seq; Type: SEQUENCE; Schema: public; Owner: - -- CREATE SEQUENCE companies_id_seq START WITH 1 INCREMENT BY 1 - NO MAXVALUE NO MINVALUE + NO MAXVALUE CACHE 1; -ALTER TABLE public.companies_id_seq OWNER TO fp; - -- --- Name: companies_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: fp +-- Name: companies_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - -- ALTER SEQUENCE companies_id_seq OWNED BY companies.id; -- --- Name: companies_id_seq; Type: SEQUENCE SET; Schema: public; Owner: fp --- - -SELECT pg_catalog.setval('companies_id_seq', 3, true); - - --- --- Name: persons; Type: TABLE; Schema: public; Owner: fp; Tablespace: +-- Name: persons; Type: TABLE; Schema: public; Owner: - -- CREATE TABLE persons ( @@ -66,52 +73,41 @@ CREATE TABLE persons ( ); -ALTER TABLE public.persons OWNER TO fp; - -- --- Name: persons_id_seq; Type: SEQUENCE; Schema: public; Owner: fp +-- Name: persons_id_seq; Type: SEQUENCE; Schema: public; Owner: - -- CREATE SEQUENCE persons_id_seq START WITH 1 INCREMENT BY 1 - NO MAXVALUE NO MINVALUE + NO MAXVALUE CACHE 1; -ALTER TABLE public.persons_id_seq OWNER TO fp; - -- --- Name: persons_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: fp +-- Name: persons_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - -- ALTER SEQUENCE persons_id_seq OWNED BY persons.id; -- --- Name: persons_id_seq; Type: SEQUENCE SET; Schema: public; Owner: fp --- - -SELECT pg_catalog.setval('persons_id_seq', 4, true); - - --- --- Name: id; Type: DEFAULT; Schema: public; Owner: fp +-- Name: id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY companies ALTER COLUMN id SET DEFAULT nextval('companies_id_seq'::regclass); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: fp +-- Name: id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY persons ALTER COLUMN id SET DEFAULT nextval('persons_id_seq'::regclass); -- --- Data for Name: companies; Type: TABLE DATA; Schema: public; Owner: fp +-- Data for Name: companies; Type: TABLE DATA; Schema: public; Owner: - -- COPY companies (id, company_name) FROM stdin; @@ -122,19 +118,33 @@ COPY companies (id, company_name) FROM stdin; -- --- Data for Name: persons; Type: TABLE DATA; Schema: public; Owner: fp +-- Name: companies_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('companies_id_seq', 3, true); + + +-- +-- Data for Name: persons; Type: TABLE DATA; Schema: public; Owner: - -- COPY persons (id, company_id, person_name) FROM stdin; 1 1 Fabien 2 1 Laurence 3 2 Eric -4 3 Ramsy +4 3 Ramzy \. -- --- Name: companies_pkey; Type: CONSTRAINT; Schema: public; Owner: fp; Tablespace: +-- Name: persons_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('persons_id_seq', 4, true); + + +-- +-- Name: companies_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY companies @@ -142,13 +152,23 @@ ALTER TABLE ONLY companies -- --- Name: persons_company_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: fp +-- Name: persons_company_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY persons ADD CONSTRAINT persons_company_id_fkey FOREIGN KEY (company_id) REFERENCES companies(id); +-- +-- Name: public; Type: ACL; Schema: -; Owner: - +-- + +REVOKE ALL ON SCHEMA public FROM PUBLIC; +REVOKE ALL ON SCHEMA public FROM postgres; +GRANT ALL ON SCHEMA public TO postgres; +GRANT ALL ON SCHEMA public TO PUBLIC; + + -- -- PostgreSQL database dump complete -- diff --git a/addons/base_import/static/csv/o2m_purchase_order_lines.csv b/addons/base_import/static/csv/o2m_purchase_order_lines.csv index 4778b99cb81..744f13f8fab 100644 --- a/addons/base_import/static/csv/o2m_purchase_order_lines.csv +++ b/addons/base_import/static/csv/o2m_purchase_order_lines.csv @@ -1,10 +1,6 @@ -Order Date,Order Reference,Supplier,Destination,Pricelist,Order Lines / Product,Order Lines / Quantity -2012-12-15,PO00008,ASUSTeK,Stock,Default Purchase Pricelist,ADPT,20 -,,,,,CARD,30 -,,,,,C-Case,40 -2012-12-15,PO00009,Axelor,Stock,Default Purchase Pricelist,CD,5 -,,,,,CPUa8,15 -2012-12-15,PO000010,China Export,Stock,Default Purchase Pricelist,HDD SH-1,10 -,,,,,HDD SH-2,20 -,,,,,LAP-CUS,35 -,,,,,LAP-E5,40 +"Order Date","Order Reference",Vendor,"Order Lines / Product","Order Lines / Description","Order Lines / Quantity","Order Lines / Product Unit of Measure","Order Lines / Unit Price","Order Lines / Scheduled Date" +2016-12-15,PO00008,ASUSTeK,ADPT,ADPT,20.0,"Unit(s)",13.0,2016-12-15 +,,,CARD,CARD,30,"Unit(s)",876.0,2016-12-15 +,,,C-Case,C-Case,40,"Unit(s)",20.0,2016-12-15 +2016-12-15,PO00009,Camptocamp,CD,CD,5,"Unit(s)",18.4,2016-12-15 +,,,CPUa8,CPUa8,15,"Unit(s)",1910.0,2016-12-15 diff --git a/addons/base_import/static/csv/purchase.order_functional_error_line_cant_adpat.csv b/addons/base_import/static/csv/purchase.order_functional_error_line_cant_adpat.csv index 3b428763431..35c07c08257 100644 --- a/addons/base_import/static/csv/purchase.order_functional_error_line_cant_adpat.csv +++ b/addons/base_import/static/csv/purchase.order_functional_error_line_cant_adpat.csv @@ -1,6 +1,3 @@ -"Order Reference","Supplier","Destination","Pricelist","Order Lines / Product","Order Lines / Quantity" -"PO000020","ASUSTeK","Stock","Default Purchase Pricelist","ADPT",20 -,,,,"CARD",30 -,,,,"C-Case",40 -"PO000021","Axelor","Stock","Default Purchase Pricelist","CD",5 -,,,,"CPUa8",15 +"Order Reference","Vendor" +"PO000020","ASUSTeK" +"PO000021","Camptocamp" diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py index 091eddd18a6..72cdb2b0348 100644 --- a/addons/calendar/calendar.py +++ b/addons/calendar/calendar.py @@ -1372,6 +1372,30 @@ class calendar_event(osv.Model): ('user_id', '=', uid), ] + @api.multi + def _get_message_unread(self): + id_map = {x: calendar_id2real_id(x) for x in self.ids} + real = self.browse(set(id_map.values())) + super(calendar_event, real)._get_message_unread() + for event in self: + if event.id == id_map[event.id]: + continue + rec = self.browse(id_map[event.id]) + event.message_unread_counter = rec.message_unread_counter + event.message_unread = rec.message_unread + + @api.multi + def _get_message_needaction(self): + id_map = {x: calendar_id2real_id(x) for x in self.ids} + real = self.browse(set(id_map.values())) + super(calendar_event, real)._get_message_needaction() + for event in self: + if event.id == id_map[event.id]: + continue + rec = self.browse(id_map[event.id]) + event.message_needaction_counter = rec.message_needaction_counter + event.message_needaction = rec.message_needaction + @api.cr_uid_ids_context def message_post(self, cr, uid, thread_id, context=None, **kwargs): if isinstance(thread_id, basestring): diff --git a/addons/fleet/fleet.py b/addons/fleet/fleet.py index 4565e1eefd1..e3564a0eaed 100644 --- a/addons/fleet/fleet.py +++ b/addons/fleet/fleet.py @@ -142,30 +142,24 @@ class fleet_vehicle_model_brand(osv.Model): image = openerp.fields.Binary("Logo", attachment=True, help="This field holds the image used as logo for the brand, limited to 1024x1024px.") - image_medium = openerp.fields.Binary("Medium-sized image", - compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True, + image_medium = openerp.fields.Binary("Medium-sized image", attachment=True, help="Medium-sized logo of the brand. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views.") - image_small = openerp.fields.Binary("Small-sized image", - compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True, + image_small = openerp.fields.Binary("Small-sized image", attachment=True, help="Small-sized logo of the brand. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") - @openerp.api.depends('image') - def _compute_images(self): - for rec in self: - rec.image_medium = tools.image_resize_image_medium(rec.image) - rec.image_small = tools.image_resize_image_small(rec.image) + @openerp.api.model + def create(self, vals): + tools.image_resize_images(vals) + return super(fleet_vehicle_model_brand, self).create(vals) - def _inverse_image_medium(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_medium) - - def _inverse_image_small(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_small) + @openerp.api.multi + def write(self, vals): + tools.image_resize_images(vals) + return super(fleet_vehicle_model_brand, self).write(vals) class fleet_vehicle(osv.Model): diff --git a/addons/hr/hr.py b/addons/hr/hr.py index cdd88e67d0c..54b45dea605 100644 --- a/addons/hr/hr.py +++ b/addons/hr/hr.py @@ -172,31 +172,15 @@ class hr_employee(osv.osv): # image: all image fields are base64 encoded and PIL-supported image = openerp.fields.Binary("Photo", attachment=True, help="This field holds the image used as photo for the employee, limited to 1024x1024px.") - image_medium = openerp.fields.Binary("Medium-sized photo", - compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True, + image_medium = openerp.fields.Binary("Medium-sized photo", attachment=True, help="Medium-sized photo of the employee. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views.") - image_small = openerp.fields.Binary("Small-sized photo", - compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True, + image_small = openerp.fields.Binary("Small-sized photo", attachment=True, help="Small-sized photo of the employee. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") - @api.depends('image') - def _compute_images(self): - for rec in self: - rec.image_medium = tools.image_resize_image_medium(rec.image) - rec.image_small = tools.image_resize_image_small(rec.image) - - def _inverse_image_medium(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_medium) - - def _inverse_image_small(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_small) - def _get_default_image(self, cr, uid, context=None): image_path = get_module_resource('hr', 'static/src/img', 'default_image.png') return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64')) @@ -207,6 +191,16 @@ class hr_employee(osv.osv): 'color': 0, } + @api.model + def create(self, vals): + tools.image_resize_images(vals) + return super(hr_employee, self).create(vals) + + @api.multi + def write(self, vals): + tools.image_resize_images(vals) + return super(hr_employee, self).write(vals) + def unlink(self, cr, uid, ids, context=None): resource_ids = [] for employee in self.browse(cr, uid, ids, context=context): diff --git a/addons/im_livechat/models/im_livechat_channel.py b/addons/im_livechat/models/im_livechat_channel.py index c4f66518c50..cc2bd7bc729 100644 --- a/addons/im_livechat/models/im_livechat_channel.py +++ b/addons/im_livechat/models/im_livechat_channel.py @@ -48,13 +48,11 @@ class ImLivechatChannel(models.Model): # images fields image = fields.Binary('Image', default=_default_image, attachment=True, help="This field holds the image used as photo for the group, limited to 1024x1024px.") - image_medium = fields.Binary('Medium', - compute='_compute_image', store=True, attachment=True, + image_medium = fields.Binary('Medium', attachment=True, help="Medium-sized photo of the group. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views.") - image_small = fields.Binary('Thumbnail', - compute='_compute_image', store=True, attachment=True, + image_small = fields.Binary('Thumbnail', attachment=True, help="Small-sized photo of the group. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") @@ -69,12 +67,6 @@ class ImLivechatChannel(models.Model): def _are_you_inside(self): self.are_you_inside = bool(self.env.uid in [u.id for u in self.user_ids]) - @api.one - @api.depends('image') - def _compute_image(self): - self.image_medium = tools.image_resize_image_medium(self.image) - self.image_small = tools.image_resize_image_small(self.image) - @api.multi def _compute_script_external(self): view = self.env['ir.model.data'].get_object('im_livechat', 'external_loader') @@ -111,6 +103,16 @@ class ImLivechatChannel(models.Model): else: record.rating_percentage_satisfaction = -1 + @api.model + def create(self, vals): + tools.image_resize_images(vals) + return super(ImLivechatChannel, self).create(vals) + + @api.multi + def write(self, vals): + tools.image_resize_images(vals) + return super(ImLivechatChannel, self).write(vals) + # -------------------------- # Action Methods # -------------------------- diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index 98ace2efd3a..41fc8e0d551 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -69,13 +69,11 @@ class Channel(models.Model): # image: all image fields are base64 encoded and PIL-supported image = fields.Binary("Photo", default=_get_default_image, attachment=True, help="This field holds the image used as photo for the group, limited to 1024x1024px.") - image_medium = fields.Binary('Medium-sized photo', - compute='_get_image', inverse='_set_image_medium', store=True, attachment=True, + image_medium = fields.Binary('Medium-sized photo', attachment=True, help="Medium-sized photo of the group. It is automatically " "resized as a 128x128px image, with aspect ratio preserved. " "Use this field in form views or some kanban views.") - image_small = fields.Binary('Small-sized photo', - compute='_get_image', inverse='_set_image_small', store=True, attachment=True, + image_small = fields.Binary('Small-sized photo', attachment=True, help="Small-sized photo of the group. It is automatically " "resized as a 64x64px image, with aspect ratio preserved. " "Use this field anywhere a small image is required.") @@ -100,20 +98,9 @@ class Channel(models.Model): for record in self: record.is_member = record in membership_ids - @api.one - @api.depends('image') - def _get_image(self): - self.image_medium = tools.image_resize_image_medium(self.image) - self.image_small = tools.image_resize_image_small(self.image) - - def _set_image_medium(self): - self.image = tools.image_resize_image_big(self.image_medium) - - def _set_image_small(self): - self.image = tools.image_resize_image_big(self.image_small) - @api.model def create(self, vals): + tools.image_resize_images(vals) # Create channel and alias channel = super(Channel, self.with_context( alias_model_name=self._name, alias_parent_model_name=self._name, mail_create_nolog=True, mail_create_nosubscribe=True) @@ -147,6 +134,7 @@ class Channel(models.Model): @api.multi def write(self, vals): + tools.image_resize_images(vals) result = super(Channel, self).write(vals) if vals.get('group_ids'): self._subscribe_users() diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py index fece7588eac..3c4188ef047 100644 --- a/addons/payment/models/payment_acquirer.py +++ b/addons/payment/models/payment_acquirer.py @@ -3,7 +3,7 @@ import logging import openerp from openerp.osv import osv, fields -from openerp.tools import float_round, float_repr, image_get_resized_images, image_resize_image_big +from openerp.tools import float_round, float_repr, image_resize_images from openerp.tools.translate import _ _logger = logging.getLogger(__name__) @@ -97,31 +97,15 @@ class PaymentAcquirer(osv.Model): image = openerp.fields.Binary("Image", attachment=True, help="This field holds the image used for this provider, limited to 1024x1024px") - image_medium = openerp.fields.Binary("Medium-sized image", - compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True, + image_medium = openerp.fields.Binary("Medium-sized image", attachment=True, help="Medium-sized image of this provider. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views.") - image_small = openerp.fields.Binary("Small-sized image", - compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True, + image_small = openerp.fields.Binary("Small-sized image", attachment=True, help="Small-sized image of this provider. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") - @openerp.api.depends('image') - def _compute_images(self): - for rec in self: - rec.image_medium = openerp.tools.image_resize_image_medium(rec.image) - rec.image_small = openerp.tools.image_resize_image_small(rec.image) - - def _inverse_image_medium(self): - for rec in self: - rec.image = openerp.tools.image_resize_image_big(rec.image_medium) - - def _inverse_image_small(self): - for rec in self: - rec.image = openerp.tools.image_resize_image_big(rec.image_small) - _defaults = { 'company_id': lambda self, cr, uid, obj, ctx=None: self.pool['res.users'].browse(cr, uid, uid).company_id.id, 'environment': 'test', @@ -145,6 +129,16 @@ class PaymentAcquirer(osv.Model): (_check_required_if_provider, 'Required fields not filled', ['required for this provider']), ] + @openerp.api.model + def create(self, vals): + image_resize_images(vals) + return super(PaymentAcquirer, self).create(vals) + + @openerp.api.multi + def write(self, vals): + image_resize_images(vals) + return super(PaymentAcquirer, self).write(vals) + def get_form_action_url(self, cr, uid, id, context=None): """ Returns the form action URL, for form-based acquirer implementations. """ acquirer = self.browse(cr, uid, id, context=context) diff --git a/addons/point_of_sale/models/pos_category.py b/addons/point_of_sale/models/pos_category.py index ff68328cbaa..515c188f10f 100644 --- a/addons/point_of_sale/models/pos_category.py +++ b/addons/point_of_sale/models/pos_category.py @@ -2,7 +2,6 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, fields, models, tools, _ - class PosCategory(models.Model): _name = "pos.category" _description = "Public Category" @@ -23,30 +22,24 @@ class PosCategory(models.Model): # buttons have consistent styling. image = fields.Binary(attachment=True, help="This field holds the image used as image for the cateogry, limited to 1024x1024px.") - image_medium = fields.Binary(string="Medium-sized image", - compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True, + image_medium = fields.Binary(string="Medium-sized image", attachment=True, help="Medium-sized image of the category. It is automatically " "resized as a 128x128px image, with aspect ratio preserved. " "Use this field in form views or some kanban views.") - image_small = fields.Binary(string="Small-sized image", - compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True, + image_small = fields.Binary(string="Small-sized image", attachment=True, help="Small-sized image of the category. It is automatically " "resized as a 64x64px image, with aspect ratio preserved. " "Use this field anywhere a small image is required.") - @api.depends('image') - def _compute_images(self): - for rec in self: - rec.image_medium = tools.image_resize_image_medium(rec.image) - rec.image_small = tools.image_resize_image_small(rec.image) + @api.model + def create(self, vals): + tools.image_resize_images(vals) + return super(PosCategory, self).create(vals) - def _inverse_image_medium(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_medium) - - def _inverse_image_small(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_small) + @api.multi + def write(self, vals): + tools.image_resize_images(vals) + return super(PosCategory, self).write(vals) @api.multi def name_get(self): diff --git a/addons/product/product.py b/addons/product/product.py index ea3f69ac5d8..f537c6e3d55 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -537,31 +537,15 @@ class product_template(osv.osv): # image: all image fields are base64 encoded and PIL-supported image = openerp.fields.Binary("Image", attachment=True, help="This field holds the image used as image for the product, limited to 1024x1024px.") - image_medium = openerp.fields.Binary("Medium-sized image", - compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True, + image_medium = openerp.fields.Binary("Medium-sized image", attachment=True, help="Medium-sized image of the product. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved, "\ "only when the image exceeds one of those sizes. Use this field in form views or some kanban views.") - image_small = openerp.fields.Binary("Small-sized image", - compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True, + image_small = openerp.fields.Binary("Small-sized image", attachment=True, help="Small-sized image of the product. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") - @api.depends('image') - def _compute_images(self): - for rec in self: - rec.image_medium = tools.image_resize_image_medium(rec.image, avoid_if_small=True) - rec.image_small = tools.image_resize_image_small(rec.image) - - def _inverse_image_medium(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_medium) - - def _inverse_image_small(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_small) - def _price_get(self, cr, uid, products, ptype='list_price', context=None): if context is None: context = {} @@ -687,6 +671,7 @@ class product_template(osv.osv): def create(self, cr, uid, vals, context=None): ''' Store the initial standard price in order to be able to retrieve the cost of a product template for a given date''' + tools.image_resize_images(vals) product_template_id = super(product_template, self).create(cr, uid, vals, context=context) if not context or "create_product_product" not in context: self.create_variant_ids(cr, uid, [product_template_id], context=context) @@ -710,6 +695,7 @@ class product_template(osv.osv): return product_template_id def write(self, cr, uid, ids, vals, context=None): + tools.image_resize_images(vals) res = super(product_template, self).write(cr, uid, ids, vals, context=context) if 'attribute_line_ids' in vals or vals.get('active'): self.create_variant_ids(cr, uid, ids, context=context) diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 97d1866295d..3abc4ce88f9 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -348,14 +348,17 @@ class PurchaseOrder(models.Model): # this product. We limit to 10 the number of suppliers for a product to avoid the mess that # could be caused for some generic products ("Miscellaneous"). for line in self.order_line: - if self.partner_id not in line.product_id.seller_ids.mapped('name') and len(line.product_id.seller_ids) <= 10: + # Do not add a contact as a supplier + partner = self.partner_id if not self.partner_id.parent_id else self.partner_id.parent_id + if partner not in line.product_id.seller_ids.mapped('name') and len(line.product_id.seller_ids) <= 10: + currency = partner.property_purchase_currency_id or self.env.user.company_id.currency_id supplierinfo = { - 'name': self.partner_id.id, + 'name': partner.id, 'sequence': max(line.product_id.seller_ids.mapped('sequence')) + 1 if line.product_id.seller_ids else 1, 'product_uom': line.product_uom.id, 'min_qty': 0.0, - 'price': line.price_unit, - 'currency_id': self.partner_id.currency_id.id, + 'price': self.currency_id.compute(line.price_unit, currency), + 'currency_id': currency.id, 'delay': 0, } vals = { diff --git a/addons/sales_team/models/crm_team.py b/addons/sales_team/models/crm_team.py index fb371740f7e..fb01d618331 100644 --- a/addons/sales_team/models/crm_team.py +++ b/addons/sales_team/models/crm_team.py @@ -15,13 +15,15 @@ class CrmTeam(models.Model): def _get_default_team_id(self, user_id=None): if not user_id: user_id = self.env.uid - team_id = self.env['crm.team'].search([ - '|', - ('user_id', '=', user_id), - ('member_ids', 'in', [user_id])], - limit=1) or self.env.ref('sales_team.team_sales_department', raise_if_not_found=False) - if not team_id and self.env.context.get('default_team_id'): + team_id = self.env['crm.team'].search( + ['|', ('user_id', '=', user_id), ('member_ids', 'in', [user_id])], + limit=1) + if not team_id and 'default_team_id' in self.env.context: team_id = self.env['crm.team'].browse(self.env.context.get('default_team_id')) + if not team_id and 'default_team_id' not in self.env.context: + default_team_id = self.env.ref('sales_team.team_sales_department', raise_if_not_found=False) + if default_team_id and (self.env.context.get('default_type') != 'lead' or default_team_id.use_leads): + team_id = default_team_id return team_id name = fields.Char('Sales Team', required=True, translate=True) diff --git a/addons/web/static/src/js/framework/mixins.js b/addons/web/static/src/js/framework/mixins.js index 40bdaeb51b4..05d4105e466 100644 --- a/addons/web/static/src/js/framework/mixins.js +++ b/addons/web/static/src/js/framework/mixins.js @@ -336,9 +336,8 @@ var PropertiesMixin = _.extend({}, EventDispatcherMixin, { // remove this, or move it elsewhere. Also, learn OO programming. if (key === 'value' && self.field && self.field.type === 'float' && tmp && val){ var digits = self.field.digits; - if (digits !== 0){ - digits = digits ? digits[1] : 2; - if (utils.float_is_zero(tmp - val, digits)) { + if (_.isArray(digits)) { + if (utils.float_is_zero(tmp - val, digits[1])) { return; } } @@ -366,4 +365,3 @@ return { }; }); - diff --git a/addons/web/static/src/js/views/list_view.js b/addons/web/static/src/js/views/list_view.js index 6c3de0f408a..b8d0c569609 100644 --- a/addons/web/static/src/js/views/list_view.js +++ b/addons/web/static/src/js/views/list_view.js @@ -1155,7 +1155,7 @@ ListView.List = Class.extend( /** @lends instance.web.ListView.List# */{ record.set(column.id, names[0]); }); } - } else if (column.type === 'many2many' || column.type === 'one2many') { + } else if (column.type === 'many2many') { value = record.get(column.id); // non-resolved (string) m2m values are arrays if (value instanceof Array && !_.isEmpty(value) diff --git a/addons/web_calendar/static/src/js/web_calendar.js b/addons/web_calendar/static/src/js/web_calendar.js index 7b03e1b73d7..b42bdd5c2d9 100644 --- a/addons/web_calendar/static/src/js/web_calendar.js +++ b/addons/web_calendar/static/src/js/web_calendar.js @@ -86,6 +86,9 @@ var CalendarView = View.extend({ this.title = (this.options.action)? this.options.action.name : ''; this.shown = $.Deferred(); + self.current_start = null; + self.current_end = null; + self.previous_ids = []; }, set_default_options: function(options) { @@ -729,11 +732,25 @@ var CalendarView = View.extend({ ); } } + + // read_slice is launched uncoditionally, when quickly + // changing the range in the calender view, all of + // these RPC calls will race each other. Because of + // this we keep track of the current range of the + // calendar view. + self.current_start = start; + self.current_end = end; self.dataset.read_slice(_.keys(self.fields), { offset: 0, domain: event_domain, context: context, }).done(function(events) { + // undo the read_slice if it the range has changed since it launched + if (self.current_start.getTime() != start.getTime() || self.current_end.getTime() != end.getTime()) { + self.dataset.ids = self.previous_ids; + return; + } + self.previous_ids = self.dataset.ids.slice(); if (self.dataset.index === null) { if (events.length) { self.dataset.index = 0; diff --git a/addons/website_sale/models/product.py b/addons/website_sale/models/product.py index 9fb470c2f79..c7b2ec373f2 100644 --- a/addons/website_sale/models/product.py +++ b/addons/website_sale/models/product.py @@ -60,30 +60,24 @@ class product_public_category(osv.osv): # In this case, the default image is set by the js code. image = openerp.fields.Binary("Image", attachment=True, help="This field holds the image used as image for the category, limited to 1024x1024px.") - image_medium = openerp.fields.Binary("Medium-sized image", - compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True, + image_medium = openerp.fields.Binary("Medium-sized image", attachment=True, help="Medium-sized image of the category. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views.") - image_small = openerp.fields.Binary("Small-sized image", - compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True, + image_small = openerp.fields.Binary("Small-sized image", attachment=True, help="Small-sized image of the category. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") - @openerp.api.depends('image') - def _compute_images(self): - for rec in self: - rec.image_medium = tools.image_resize_image_medium(rec.image) - rec.image_small = tools.image_resize_image_small(rec.image) + @openerp.api.model + def create(self, vals): + tools.image_resize_images(vals) + return super(product_public_category, self).create(vals) - def _inverse_image_medium(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_medium) - - def _inverse_image_small(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_small) + @openerp.api.multi + def write(self, vals): + tools.image_resize_images(vals) + return super(product_public_category, self).write(vals) class product_template(osv.Model): _inherit = ["product.template", "website.seo.metadata", 'website.published.mixin', 'rating.mixin'] diff --git a/openerp/addons/base/ir/ir_attachment.py b/openerp/addons/base/ir/ir_attachment.py index 2488726ff48..c94a3743830 100644 --- a/openerp/addons/base/ir/ir_attachment.py +++ b/openerp/addons/base/ir/ir_attachment.py @@ -12,7 +12,7 @@ from openerp import SUPERUSER_ID, tools from openerp.exceptions import AccessError from openerp.osv import fields,osv from openerp.tools.translate import _ -from openerp.tools.misc import ustr +from openerp.tools.misc import ustr, html_escape from openerp.tools.mimetypes import guess_mimetype _logger = logging.getLogger(__name__) @@ -199,14 +199,26 @@ class ir_attachment(osv.osv): :param values : dict of values to create or write an ir_attachment :return mime : string indicating the mimetype, or application/octet-stream by default """ - mimetype = 'application/octet-stream' - if values.get('datas_fname'): + mimetype = None + if values.get('mimetype'): + mimetype = values['mimetype'] + if not mimetype and values.get('datas_fname'): mimetype = mimetypes.guess_type(values['datas_fname'])[0] - if values.get('url'): + if not mimetype and values.get('url'): mimetype = mimetypes.guess_type(values['url'])[0] if values.get('datas') and (not mimetype or mimetype == 'application/octet-stream'): mimetype = guess_mimetype(values['datas'].decode('base64')) - return mimetype + return mimetype or 'application/octet-stream' + + def _check_contents(self, cr, uid, values, context=None): + mimetype = values['mimetype'] = self._compute_mimetype(values) + needs_escape = 'htm' in mimetype or '/ht' in mimetype # hta, html, xhtml, etc. + if needs_escape and not self.pool['res.users']._is_admin(cr, uid, [uid]): + if 'datas' in values: + values['datas'] = html_escape(values['datas'].decode('base64')).encode('base64') + else: + values['mimetype'] = 'text/plain' + return values def _index(self, cr, uid, bin_data, datas_fname, file_type): """ compute the index content of the given filename, or binary data. @@ -382,6 +394,8 @@ class ir_attachment(osv.osv): # remove computed field depending of datas for field in ['file_size', 'checksum']: vals.pop(field, False) + if 'mimetype' in vals or 'datas' in vals: + vals = self._check_contents(cr, uid, vals, context=context) return super(ir_attachment, self).write(cr, uid, ids, vals, context) def copy(self, cr, uid, id, default=None, context=None): @@ -409,9 +423,7 @@ class ir_attachment(osv.osv): # remove computed field depending of datas for field in ['file_size', 'checksum']: values.pop(field, False) - # if mimetype not given, compute it ! - if 'mimetype' not in values: - values['mimetype'] = self._compute_mimetype(values) + values = self._check_contents(cr, uid, values, context=context) self.check(cr, uid, [], mode='write', context=context, values=values) return super(ir_attachment, self).create(cr, uid, values, context) diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index d3eab722c19..3a85aeccdb1 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -198,13 +198,11 @@ class Partner(models.Model, FormatAddress): # image: all image fields are base64 encoded and PIL-supported image = fields.Binary("Image", attachment=True, help="This field holds the image used as avatar for this contact, limited to 1024x1024px",) - image_medium = fields.Binary("Medium-sized image", - compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True, + image_medium = fields.Binary("Medium-sized image", attachment=True, help="Medium-sized image of this contact. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views.") - image_small = fields.Binary("Small-sized image", - compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True, + image_small = fields.Binary("Small-sized image", attachment=True, help="Small-sized image of this contact. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") @@ -238,20 +236,6 @@ class Partner(models.Model, FormatAddress): else: partner.commercial_partner_id = partner.parent_id.commercial_partner_id - @api.depends('image') - def _compute_images(self): - for rec in self: - rec.image_medium = tools.image_resize_image_medium(rec.image) - rec.image_small = tools.image_resize_image_small(rec.image) - - def _inverse_image_medium(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_medium) - - def _inverse_image_small(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_small) - @api.model def _get_default_image(self, partner_type, is_company, parent_id): if getattr(threading.currentThread(), 'testing', False) or self._context.get('install_mode'): @@ -484,10 +468,13 @@ class Partner(models.Model, FormatAddress): companies = set(user.company_id for user in partner.user_ids) if len(companies) > 1 or company not in companies: raise UserError(_("You can not change the company as the partner/user has multiple user linked with different companies.")) + tools.image_resize_images(vals) result = super(Partner, self).write(vals) for partner in self: partner._fields_sync(vals) + if any(u.has_group('base.group_user') for u in partner.user_ids): + self.env['res.users'].check_access_rights('write') return result @api.model @@ -501,6 +488,7 @@ class Partner(models.Model, FormatAddress): partner = super(Partner, self).create(vals) partner._fields_sync(vals) partner._handle_first_contact_creation() + tools.image_resize_images(vals) return partner @api.multi diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index b698db693c7..876598ffb0a 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -174,7 +174,7 @@ class Users(models.Model): __uid_cache = defaultdict(dict) # {dbname: {uid: password}} # User can write on a few of his own fields (but not his groups for example) - SELF_WRITEABLE_FIELDS = ['password', 'signature', 'action_id', 'company_id', 'email', 'name', 'image', 'image_medium', 'image_small', 'lang', 'tz'] + SELF_WRITEABLE_FIELDS = ['signature', 'action_id', 'company_id', 'email', 'name', 'image', 'image_medium', 'image_small', 'lang', 'tz'] # User can read a few of his own fields SELF_READABLE_FIELDS = ['signature', 'company_id', 'login', 'email', 'name', 'image', 'image_medium', 'image_small', 'lang', 'tz', 'tz_offset', 'groups_id', 'partner_id', '__last_update', 'action_id'] @@ -510,8 +510,8 @@ class Users(models.Model): """ self.check(self._cr.dbname, self._uid, old_passwd) if new_passwd: - # do not use self.env.user here, since it has uid=SUPERUSER_ID - return self.browse(self._uid).write({'password': new_passwd}) + # use self.env.user here, because it has uid=SUPERUSER_ID + return self.env.user.write({'password': new_passwd}) raise UserError(_("Setting empty passwords is not allowed for security reasons!")) @api.multi diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index 0ccb599b226..017fd411ea1 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -418,7 +418,10 @@ def load_modules(db, force_demo=False, status=None, update_module=False): t0 = time.time() t0_sql = openerp.sql_db.sql_counter if openerp.tools.config['test_enable']: - cr.execute("SELECT name FROM ir_module_module WHERE state='installed'") + if update_module: + cr.execute("SELECT name FROM ir_module_module WHERE state='installed' and name = ANY(%s)", (processed_modules,)) + else: + cr.execute("SELECT name FROM ir_module_module WHERE state='installed'") for module_name in cr.fetchall(): report.record_result(openerp.modules.module.run_unit_tests(module_name[0], cr.dbname, position=runs_post_install)) _logger.log(25, "All post-tested in %.2fs, %s queries", time.time() - t0, openerp.sql_db.sql_counter - t0_sql) diff --git a/openerp/tools/image.py b/openerp/tools/image.py index c6d0ad9aa43..f17623cbe6e 100644 --- a/openerp/tools/image.py +++ b/openerp/tools/image.py @@ -256,6 +256,24 @@ def image_get_resized_images(base64_source, return_big=False, return_medium=True return_dict[small_name] = image_resize_image_small(base64_source, avoid_if_small=avoid_resize_small) return return_dict +def image_resize_images(vals, big_name='image', medium_name='image_medium', small_name='image_small'): + """ Update ``vals`` with image fields resized as expected. """ + if big_name in vals: + vals.update(image_get_resized_images(vals[big_name], + return_big=True, return_medium=True, return_small=True, + big_name=big_name, medium_name=medium_name, small_name=small_name, + avoid_resize_big=True, avoid_resize_medium=False, avoid_resize_small=False)) + elif medium_name in vals: + vals.update(image_get_resized_images(vals[medium_name], + return_big=True, return_medium=True, return_small=True, + big_name=big_name, medium_name=medium_name, small_name=small_name, + avoid_resize_big=True, avoid_resize_medium=True, avoid_resize_small=False)) + elif small_name in vals: + vals.update(image_get_resized_images(vals[small_name], + return_big=True, return_medium=True, return_small=True, + big_name=big_name, medium_name=medium_name, small_name=small_name, + avoid_resize_big=True, avoid_resize_medium=True, avoid_resize_small=True)) + if __name__=="__main__": import sys