diff --git a/addons/account/res_config.py b/addons/account/res_config.py index bf28e900378..8980a31927e 100644 --- a/addons/account/res_config.py +++ b/addons/account/res_config.py @@ -105,6 +105,9 @@ class account_config_settings(osv.osv_memory): 'module_account_followup': fields.boolean('Manage customer payment follow-ups', help='This allows to automate letters for unpaid invoices, with multi-level recalls.\n' '-This installs the module account_followup.'), + 'module_product_email_template': fields.boolean('Send products tools and information at the invoice confirmation', + help='With this module, link your products to a template to send complete information and tools to your customer.\n' + 'For instance when invoicing a training, the training agenda and materials will automatically be send to your customers.'), 'group_proforma_invoices': fields.boolean('Allow pro-forma invoices', implied_group='account.group_proforma_invoices', help="Allows you to put invoices in pro-forma state."), diff --git a/addons/account/res_config_view.xml b/addons/account/res_config_view.xml index 2ef3f058071..8a5b1978ecb 100644 --- a/addons/account/res_config_view.xml +++ b/addons/account/res_config_view.xml @@ -183,6 +183,10 @@ + Resource Leaves resource.calendar.leaves tree,form,calendar + - + + + diff --git a/addons/resource/test/resource.yml b/addons/resource/test/resource.yml index 28604518b7b..b085c0734c4 100644 --- a/addons/resource/test/resource.yml +++ b/addons/resource/test/resource.yml @@ -26,7 +26,7 @@ dt = now - timedelta(days=now.weekday()) for resource in resources: result = calendar_pool.working_hours_on_day(cr, uid, resource.calendar_id, dt, context) - assert result == 9.0, 'Wrong calculation of day work hour availability of the Resource.' + assert result == 9.0, 'Wrong calculation of day work hour availability of the Resource (found %d).' % result - Now, resource "Developer" drafted leave on Thursday in this week. - diff --git a/addons/website_blog/tests/test_controllers.py b/addons/resource/tests/__init__.py similarity index 73% rename from addons/website_blog/tests/test_controllers.py rename to addons/resource/tests/__init__.py index 33b37aede9d..f22158c04b8 100644 --- a/addons/website_blog/tests/test_controllers.py +++ b/addons/resource/tests/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- ############################################################################## # -# OpenERP, Open Source Management Solution -# Copyright (C) 2013-Today OpenERP SA (). +# OpenERP, Open Source Business Applications +# Copyright (c) 2013-TODAY OpenERP S.A. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -19,11 +19,10 @@ # ############################################################################## -from openerp.addons.mail.tests.common import TestMail -from openerp.tools import mute_logger, email_split +from openerp.addons.resource.tests import test_resource +checks = [ + test_resource, +] -class TestControllers(TestMail): - - def test_00(self): - cr, uid = self.cr, self.uid +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/resource/tests/common.py b/addons/resource/tests/common.py new file mode 100644 index 00000000000..74b91abde2e --- /dev/null +++ b/addons/resource/tests/common.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2013-TODAY OpenERP S.A. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from datetime import datetime + +from openerp.tests import common + + +class TestResourceCommon(common.TransactionCase): + + def setUp(self): + super(TestResourceCommon, self).setUp() + cr, uid = self.cr, self.uid + if not hasattr(self, 'context'): + self.context = {} + + # Usefull models + self.resource_resource = self.registry('resource.resource') + self.resource_calendar = self.registry('resource.calendar') + self.resource_attendance = self.registry('resource.calendar.attendance') + self.resource_leaves = self.registry('resource.calendar.leaves') + + # Some demo data + self.date1 = datetime.strptime('2013-02-12 09:08:07', '%Y-%m-%d %H:%M:%S') # weekday() returns 1, isoweekday() returns 2 + self.date2 = datetime.strptime('2013-02-15 10:11:12', '%Y-%m-%d %H:%M:%S') # weekday() returns 4, isoweekday() returns 5 + # Leave1: 19/02/2013, from 9 to 12, is a day 1 + self.leave1_start = datetime.strptime('2013-02-19 09:00:00', '%Y-%m-%d %H:%M:%S') + self.leave1_end = datetime.strptime('2013-02-19 12:00:00', '%Y-%m-%d %H:%M:%S') + # Leave2: 22/02/2013, from 9 to 15, is a day 4 + self.leave2_start = datetime.strptime('2013-02-22 09:00:00', '%Y-%m-%d %H:%M:%S') + self.leave2_end = datetime.strptime('2013-02-22 15:00:00', '%Y-%m-%d %H:%M:%S') + # Leave3: 25/02/2013 (day0) -> 01/03/2013 (day4) + self.leave3_start = datetime.strptime('2013-02-25 13:00:00', '%Y-%m-%d %H:%M:%S') + self.leave3_end = datetime.strptime('2013-03-01 11:30:00', '%Y-%m-%d %H:%M:%S') + + # Resource data + # Calendar working days: 1 (8-16 -> 8hours), 4 (8-13, 16-23 -> 12hours) + self.calendar_id = self.resource_calendar.create( + cr, uid, { + 'name': 'TestCalendar', + } + ) + self.att1_id = self.resource_attendance.create( + cr, uid, { + 'name': 'Att1', + 'dayofweek': '1', + 'hour_from': 8, + 'hour_to': 16, + 'calendar_id': self.calendar_id, + } + ) + self.att2_id = self.resource_attendance.create( + cr, uid, { + 'name': 'Att2', + 'dayofweek': '4', + 'hour_from': 8, + 'hour_to': 13, + 'calendar_id': self.calendar_id, + } + ) + self.att3_id = self.resource_attendance.create( + cr, uid, { + 'name': 'Att3', + 'dayofweek': '4', + 'hour_from': 16, + 'hour_to': 23, + 'calendar_id': self.calendar_id, + } + ) + self.resource1_id = self.resource_resource.create( + cr, uid, { + 'name': 'TestResource1', + 'resource_type': 'user', + 'time_efficiency': 150.0, + 'calendar_id': self.calendar_id, + } + ) + self.leave1_id = self.resource_leaves.create( + cr, uid, { + 'name': 'GenericLeave', + 'calendar_id': self.calendar_id, + 'date_from': self.leave1_start, + 'date_to': self.leave1_end, + } + ) + self.leave2_id = self.resource_leaves.create( + cr, uid, { + 'name': 'ResourceLeave', + 'calendar_id': self.calendar_id, + 'resource_id': self.resource1_id, + 'date_from': self.leave2_start, + 'date_to': self.leave2_end, + } + ) + self.leave3_id = self.resource_leaves.create( + cr, uid, { + 'name': 'ResourceLeave2', + 'calendar_id': self.calendar_id, + 'resource_id': self.resource1_id, + 'date_from': self.leave3_start, + 'date_to': self.leave3_end, + } + ) + # Some browse data + self.calendar = self.resource_calendar.browse(cr, uid, self.calendar_id) diff --git a/addons/resource/tests/test_resource.py b/addons/resource/tests/test_resource.py new file mode 100644 index 00000000000..346317449d3 --- /dev/null +++ b/addons/resource/tests/test_resource.py @@ -0,0 +1,445 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2013-TODAY OpenERP S.A. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from datetime import datetime, timedelta +from dateutil.relativedelta import relativedelta + +from openerp.addons.resource.tests.common import TestResourceCommon + + +class TestResource(TestResourceCommon): + + def test_00_intervals(self): + intervals = [ + ( + datetime.strptime('2013-02-04 09:00:00', '%Y-%m-%d %H:%M:%S'), + datetime.strptime('2013-02-04 11:00:00', '%Y-%m-%d %H:%M:%S') + ), ( + datetime.strptime('2013-02-04 08:00:00', '%Y-%m-%d %H:%M:%S'), + datetime.strptime('2013-02-04 12:00:00', '%Y-%m-%d %H:%M:%S') + ), ( + datetime.strptime('2013-02-04 11:00:00', '%Y-%m-%d %H:%M:%S'), + datetime.strptime('2013-02-04 14:00:00', '%Y-%m-%d %H:%M:%S') + ), ( + datetime.strptime('2013-02-04 17:00:00', '%Y-%m-%d %H:%M:%S'), + datetime.strptime('2013-02-04 21:00:00', '%Y-%m-%d %H:%M:%S') + ), ( + datetime.strptime('2013-02-03 08:00:00', '%Y-%m-%d %H:%M:%S'), + datetime.strptime('2013-02-03 10:00:00', '%Y-%m-%d %H:%M:%S') + ), ( + datetime.strptime('2013-02-04 18:00:00', '%Y-%m-%d %H:%M:%S'), + datetime.strptime('2013-02-04 19:00:00', '%Y-%m-%d %H:%M:%S') + ) + ] + + # Test: interval cleaning + cleaned_intervals = self.resource_calendar.interval_clean(intervals) + self.assertEqual(len(cleaned_intervals), 3, 'resource_calendar: wrong interval cleaning') + # First interval: 03, unchanged + self.assertEqual(cleaned_intervals[0][0], datetime.strptime('2013-02-03 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + self.assertEqual(cleaned_intervals[0][1], datetime.strptime('2013-02-03 10:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + # Second intreval: 04, 08-14, combining 08-12 and 11-14, 09-11 being inside 08-12 + self.assertEqual(cleaned_intervals[1][0], datetime.strptime('2013-02-04 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + self.assertEqual(cleaned_intervals[1][1], datetime.strptime('2013-02-04 14:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + # Third interval: 04, 17-21, 18-19 being inside 17-21 + self.assertEqual(cleaned_intervals[2][0], datetime.strptime('2013-02-04 17:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + self.assertEqual(cleaned_intervals[2][1], datetime.strptime('2013-02-04 21:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + + # Test: disjoint removal + working_interval = (datetime.strptime('2013-02-04 08:00:00', '%Y-%m-%d %H:%M:%S'), datetime.strptime('2013-02-04 18:00:00', '%Y-%m-%d %H:%M:%S')) + result = self.resource_calendar.interval_remove_leaves(working_interval, intervals) + self.assertEqual(len(result), 1, 'resource_calendar: wrong leave removal from interval') + # First interval: 04, 14-17 + self.assertEqual(result[0][0], datetime.strptime('2013-02-04 14:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + self.assertEqual(result[0][1], datetime.strptime('2013-02-04 17:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + + # Test: schedule hours on intervals + result = self.resource_calendar.interval_schedule_hours(cleaned_intervals, 5.5) + self.assertEqual(len(result), 2, 'resource_calendar: wrong hours scheduling in interval') + # First interval: 03, 8-10 untouches + self.assertEqual(result[0][0], datetime.strptime('2013-02-03 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + self.assertEqual(result[0][1], datetime.strptime('2013-02-03 10:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + # First interval: 04, 08-11:30 + self.assertEqual(result[1][0], datetime.strptime('2013-02-04 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + self.assertEqual(result[1][1], datetime.strptime('2013-02-04 11:30:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + + # Test: schedule hours on intervals, backwards + cleaned_intervals.reverse() + result = self.resource_calendar.interval_schedule_hours(cleaned_intervals, 5.5, remove_at_end=False) + self.assertEqual(len(result), 2, 'resource_calendar: wrong hours scheduling in interval') + # First interval: 03, 8-10 untouches + self.assertEqual(result[0][0], datetime.strptime('2013-02-04 17:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + self.assertEqual(result[0][1], datetime.strptime('2013-02-04 21:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + # First interval: 04, 08-11:30 + self.assertEqual(result[1][0], datetime.strptime('2013-02-04 12:30:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + self.assertEqual(result[1][1], datetime.strptime('2013-02-04 14:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + + def test_10_calendar_basics(self): + """ Testing basic method of resource.calendar """ + cr, uid = self.cr, self.uid + + # -------------------------------------------------- + # Test1: get_next_day + # -------------------------------------------------- + + # Test: next day: next day after day1 is day4 + date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date1.date()) + self.assertEqual(date, self.date2.date(), 'resource_calendar: wrong next day computing') + + # Test: next day: next day after day4 is (day1+7) + date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date2.date()) + self.assertEqual(date, self.date1.date() + relativedelta(days=7), 'resource_calendar: wrong next day computing') + + # Test: next day: next day after day4+1 is (day1+7) + date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date2.date() + relativedelta(days=1)) + self.assertEqual(date, self.date1.date() + relativedelta(days=7), 'resource_calendar: wrong next day computing') + + # Test: next day: next day after day1-1 is day1 + date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date1.date() + relativedelta(days=-1)) + self.assertEqual(date, self.date1.date(), 'resource_calendar: wrong next day computing') + + # -------------------------------------------------- + # Test2: get_previous_day + # -------------------------------------------------- + + # Test: previous day: previous day before day1 is (day4-7) + date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date1.date()) + self.assertEqual(date, self.date2.date() + relativedelta(days=-7), 'resource_calendar: wrong previous day computing') + + # Test: previous day: previous day before day4 is day1 + date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date2.date()) + self.assertEqual(date, self.date1.date(), 'resource_calendar: wrong previous day computing') + + # Test: previous day: previous day before day4+1 is day4 + date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date2.date() + relativedelta(days=1)) + self.assertEqual(date, self.date2.date(), 'resource_calendar: wrong previous day computing') + + # Test: previous day: previous day before day1-1 is (day4-7) + date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date1.date() + relativedelta(days=-1)) + self.assertEqual(date, self.date2.date() + relativedelta(days=-7), 'resource_calendar: wrong previous day computing') + + # -------------------------------------------------- + # Test3: misc + # -------------------------------------------------- + + weekdays = self.resource_calendar.get_weekdays(cr, uid, self.calendar_id) + self.assertEqual(weekdays, [1, 4], 'resource_calendar: wrong weekdays computing') + + attendances = self.resource_calendar.get_attendances_for_weekdays(cr, uid, self.calendar_id, [2, 3, 4, 5]) + self.assertEqual(set([att.id for att in attendances]), set([self.att2_id, self.att3_id]), + 'resource_calendar: wrong attendances filtering by weekdays computing') + + def test_20_calendar_working_intervals(self): + """ Testing working intervals computing method of resource.calendar """ + cr, uid = self.cr, self.uid + _format = '%Y-%m-%d %H:%M:%S' + + # Test: day0 without leaves: 1 interval + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date1) + self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-12 09:08:07', _format), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong working intervals') + + # Test: day3 without leaves: 2 interval + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date2) + self.assertEqual(len(intervals), 2, 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-15 10:11:12', _format), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-15 13:00:00', _format), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[1][0], datetime.strptime('2013-02-15 16:00:00', _format), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[1][1], datetime.strptime('2013-02-15 23:00:00', _format), 'resource_calendar: wrong working intervals') + + # Test: day0 with leaves outside range: 1 interval + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=0), compute_leaves=True) + self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-12 08:00:00', _format), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong working intervals') + + # Test: day0 with leaves: 2 intervals because of leave between 9 ans 12, ending at 15:45:30 + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, + start_dt=self.date1.replace(hour=8) + relativedelta(days=7), + end_dt=self.date1.replace(hour=15, minute=45, second=30) + relativedelta(days=7), + compute_leaves=True) + self.assertEqual(len(intervals), 2, 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-19 08:08:07', _format), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-19 09:00:00', _format), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[1][0], datetime.strptime('2013-02-19 12:00:00', _format), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[1][1], datetime.strptime('2013-02-19 15:45:30', _format), 'resource_calendar: wrong working intervals') + + def test_30_calendar_working_days(self): + """ Testing calendar hours computation on a working day """ + cr, uid = self.cr, self.uid + _format = '%Y-%m-%d %H:%M:%S' + + # Test: day1, beginning at 10:30 -> work from 10:30 (arrival) until 16:00 + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=10, minute=30, second=0)) + self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working interval / day computing') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-12 10:30:00', _format), 'resource_calendar: wrong working interval / day computing') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong working interval / day computing') + # Test: hour computation for same interval, should give 5.5 + wh = self.resource_calendar.get_working_hours_of_date(cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=10, minute=30, second=0)) + self.assertEqual(wh, 5.5, 'resource_calendar: wrong working interval / day time computing') + + # Test: day1+7 on leave, without leave computation + intervals = self.resource_calendar.get_working_intervals_of_day( + cr, uid, self.calendar_id, + start_dt=self.date1.replace(hour=7, minute=0, second=0) + relativedelta(days=7) + ) + # Result: day1 (08->16) + self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working interval/day computing') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-19 08:00:00', _format), 'resource_calendar: wrong working interval / day computing') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-19 16:00:00', _format), 'resource_calendar: wrong working interval / day computing') + + # Test: day1+7 on leave, with generic leave computation + intervals = self.resource_calendar.get_working_intervals_of_day( + cr, uid, self.calendar_id, + start_dt=self.date1.replace(hour=7, minute=0, second=0) + relativedelta(days=7), + compute_leaves=True + ) + # Result: day1 (08->09 + 12->16) + self.assertEqual(len(intervals), 2, 'resource_calendar: wrong working interval/day computing') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-19 08:00:00', _format), 'resource_calendar: wrong working interval / day computing') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-19 09:00:00', _format), 'resource_calendar: wrong working interval / day computing') + self.assertEqual(intervals[1][0], datetime.strptime('2013-02-19 12:00:00', _format), 'resource_calendar: wrong working interval / day computing') + self.assertEqual(intervals[1][1], datetime.strptime('2013-02-19 16:00:00', _format), 'resource_calendar: wrong working interval / day computing') + + # Test: day1+14 on leave, with generic leave computation + intervals = self.resource_calendar.get_working_intervals_of_day( + cr, uid, self.calendar_id, + start_dt=self.date1.replace(hour=7, minute=0, second=0) + relativedelta(days=14), + compute_leaves=True + ) + # Result: day1 (08->16) + self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working interval/day computing') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-26 08:00:00', _format), 'resource_calendar: wrong working interval / day computing') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-26 16:00:00', _format), 'resource_calendar: wrong working interval / day computing') + + # Test: day1+14 on leave, with resource leave computation + intervals = self.resource_calendar.get_working_intervals_of_day( + cr, uid, self.calendar_id, + start_dt=self.date1.replace(hour=7, minute=0, second=0) + relativedelta(days=14), + compute_leaves=True, + resource_id=self.resource1_id + ) + # Result: nothing, because on leave + self.assertEqual(len(intervals), 0, 'resource_calendar: wrong working interval/day computing') + + def test_40_calendar_hours_scheduling(self): + """ Testing calendar hours scheduling """ + cr, uid = self.cr, self.uid + _format = '%Y-%m-%d %H:%M:%S' + + # -------------------------------------------------- + # Test0: schedule hours backwards (old interval_min_get) + # Done without calendar + # -------------------------------------------------- + + # Done without calendar + # res = self.resource_calendar.interval_min_get(cr, uid, None, self.date1, 40, resource=False) + # res: (datetime.datetime(2013, 2, 7, 9, 8, 7), datetime.datetime(2013, 2, 12, 9, 8, 7)) + + # -------------------------------------------------- + # Test1: schedule hours backwards (old interval_min_get) + # -------------------------------------------------- + + # res = self.resource_calendar.interval_min_get(cr, uid, self.calendar_id, self.date1, 40, resource=False) + # (datetime.datetime(2013, 1, 29, 9, 0), datetime.datetime(2013, 1, 29, 16, 0)) + # (datetime.datetime(2013, 2, 1, 8, 0), datetime.datetime(2013, 2, 1, 13, 0)) + # (datetime.datetime(2013, 2, 1, 16, 0), datetime.datetime(2013, 2, 1, 23, 0)) + # (datetime.datetime(2013, 2, 5, 8, 0), datetime.datetime(2013, 2, 5, 16, 0)) + # (datetime.datetime(2013, 2, 8, 8, 0), datetime.datetime(2013, 2, 8, 13, 0)) + # (datetime.datetime(2013, 2, 8, 16, 0), datetime.datetime(2013, 2, 8, 23, 0)) + # (datetime.datetime(2013, 2, 12, 8, 0), datetime.datetime(2013, 2, 12, 9, 0)) + + res = self.resource_calendar.schedule_hours(cr, uid, self.calendar_id, -40, day_dt=self.date1.replace(minute=0, second=0)) + # current day, limited at 09:00 because of day_dt specified -> 1 hour + self.assertEqual(res[-1][0], datetime.strptime('2013-02-12 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-1][1], datetime.strptime('2013-02-12 09:00:00', _format), 'resource_calendar: wrong hours scheduling') + # previous days: 5+7 hours / 8 hours / 5+7 hours -> 32 hours + self.assertEqual(res[-2][0], datetime.strptime('2013-02-08 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-2][1], datetime.strptime('2013-02-08 23:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-3][0], datetime.strptime('2013-02-08 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-3][1], datetime.strptime('2013-02-08 13:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-4][0], datetime.strptime('2013-02-05 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-4][1], datetime.strptime('2013-02-05 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-5][0], datetime.strptime('2013-02-01 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-5][1], datetime.strptime('2013-02-01 23:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-6][0], datetime.strptime('2013-02-01 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-6][1], datetime.strptime('2013-02-01 13:00:00', _format), 'resource_calendar: wrong hours scheduling') + # 7 hours remaining + self.assertEqual(res[-7][0], datetime.strptime('2013-01-29 09:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-7][1], datetime.strptime('2013-01-29 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + # Compute scheduled hours + td = timedelta() + for item in res: + td += item[1] - item[0] + self.assertEqual(td.total_seconds() / 3600.0, 40.0, 'resource_calendar: wrong hours scheduling') + + # -------------------------------------------------- + # Test2: schedule hours forward (old interval_get) + # -------------------------------------------------- + + # res = self.resource_calendar.interval_get(cr, uid, self.calendar_id, self.date1, 40, resource=False, byday=True) + # (datetime.datetime(2013, 2, 12, 9, 0), datetime.datetime(2013, 2, 12, 16, 0)) + # (datetime.datetime(2013, 2, 15, 8, 0), datetime.datetime(2013, 2, 15, 13, 0)) + # (datetime.datetime(2013, 2, 15, 16, 0), datetime.datetime(2013, 2, 15, 23, 0)) + # (datetime.datetime(2013, 2, 22, 8, 0), datetime.datetime(2013, 2, 22, 13, 0)) + # (datetime.datetime(2013, 2, 22, 16, 0), datetime.datetime(2013, 2, 22, 23, 0)) + # (datetime.datetime(2013, 2, 26, 8, 0), datetime.datetime(2013, 2, 26, 16, 0)) + # (datetime.datetime(2013, 3, 1, 8, 0), datetime.datetime(2013, 3, 1, 9, 0)) + + res = self.resource_calendar.schedule_hours( + cr, uid, self.calendar_id, 40, + day_dt=self.date1.replace(minute=0, second=0) + ) + self.assertEqual(res[0][0], datetime.strptime('2013-02-12 09:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[1][0], datetime.strptime('2013-02-15 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[1][1], datetime.strptime('2013-02-15 13:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[2][0], datetime.strptime('2013-02-15 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[2][1], datetime.strptime('2013-02-15 23:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[3][0], datetime.strptime('2013-02-19 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[3][1], datetime.strptime('2013-02-19 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[4][0], datetime.strptime('2013-02-22 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[4][1], datetime.strptime('2013-02-22 13:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[5][0], datetime.strptime('2013-02-22 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[5][1], datetime.strptime('2013-02-22 23:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[6][0], datetime.strptime('2013-02-26 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[6][1], datetime.strptime('2013-02-26 09:00:00', _format), 'resource_calendar: wrong hours scheduling') + td = timedelta() + for item in res: + td += item[1] - item[0] + self.assertEqual(td.total_seconds() / 3600.0, 40.0, 'resource_calendar: wrong hours scheduling') + + # res = self.resource_calendar.interval_get(cr, uid, self.calendar_id, self.date1, 40, resource=self.resource1_id, byday=True) + # (datetime.datetime(2013, 2, 12, 9, 0), datetime.datetime(2013, 2, 12, 16, 0)) + # (datetime.datetime(2013, 2, 15, 8, 0), datetime.datetime(2013, 2, 15, 13, 0)) + # (datetime.datetime(2013, 2, 15, 16, 0), datetime.datetime(2013, 2, 15, 23, 0)) + # (datetime.datetime(2013, 3, 1, 8, 0), datetime.datetime(2013, 3, 1, 13, 0)) + # (datetime.datetime(2013, 3, 1, 16, 0), datetime.datetime(2013, 3, 1, 23, 0)) + # (datetime.datetime(2013, 3, 5, 8, 0), datetime.datetime(2013, 3, 5, 16, 0)) + # (datetime.datetime(2013, 3, 8, 8, 0), datetime.datetime(2013, 3, 8, 9, 0)) + + res = self.resource_calendar.schedule_hours( + cr, uid, self.calendar_id, 40, + day_dt=self.date1.replace(minute=0, second=0), + compute_leaves=True, + resource_id=self.resource1_id + ) + self.assertEqual(res[0][0], datetime.strptime('2013-02-12 09:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[1][0], datetime.strptime('2013-02-15 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[1][1], datetime.strptime('2013-02-15 13:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[2][0], datetime.strptime('2013-02-15 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[2][1], datetime.strptime('2013-02-15 23:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[3][0], datetime.strptime('2013-02-19 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[3][1], datetime.strptime('2013-02-19 09:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[4][0], datetime.strptime('2013-02-19 12:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[4][1], datetime.strptime('2013-02-19 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[5][0], datetime.strptime('2013-02-22 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[5][1], datetime.strptime('2013-02-22 09:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[6][0], datetime.strptime('2013-02-22 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[6][1], datetime.strptime('2013-02-22 23:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[7][0], datetime.strptime('2013-03-01 11:30:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[7][1], datetime.strptime('2013-03-01 13:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[8][0], datetime.strptime('2013-03-01 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[8][1], datetime.strptime('2013-03-01 22:30:00', _format), 'resource_calendar: wrong hours scheduling') + td = timedelta() + for item in res: + td += item[1] - item[0] + self.assertEqual(td.total_seconds() / 3600.0, 40.0, 'resource_calendar: wrong hours scheduling') + + # -------------------------------------------------- + # Test3: working hours (old _interval_hours_get) + # -------------------------------------------------- + + # old API: resource without leaves + # res: 2 weeks -> 40 hours + res = self.resource_calendar._interval_hours_get( + cr, uid, self.calendar_id, + self.date1.replace(hour=6, minute=0), + self.date2.replace(hour=23, minute=0) + relativedelta(days=7), + resource_id=self.resource1_id, exclude_leaves=True) + self.assertEqual(res, 40.0, 'resource_calendar: wrong _interval_hours_get compatibility computation') + + # new API: resource without leaves + # res: 2 weeks -> 40 hours + res = self.resource_calendar.get_working_hours( + cr, uid, self.calendar_id, + self.date1.replace(hour=6, minute=0), + self.date2.replace(hour=23, minute=0) + relativedelta(days=7), + compute_leaves=False, resource_id=self.resource1_id) + self.assertEqual(res, 40.0, 'resource_calendar: wrong get_working_hours computation') + + # old API: resource and leaves + # res: 2 weeks -> 40 hours - (3+4) leave hours + res = self.resource_calendar._interval_hours_get( + cr, uid, self.calendar_id, + self.date1.replace(hour=6, minute=0), + self.date2.replace(hour=23, minute=0) + relativedelta(days=7), + resource_id=self.resource1_id, exclude_leaves=False) + self.assertEqual(res, 33.0, 'resource_calendar: wrong _interval_hours_get compatibility computation') + + # new API: resource and leaves + # res: 2 weeks -> 40 hours - (3+4) leave hours + res = self.resource_calendar.get_working_hours( + cr, uid, self.calendar_id, + self.date1.replace(hour=6, minute=0), + self.date2.replace(hour=23, minute=0) + relativedelta(days=7), + compute_leaves=True, resource_id=self.resource1_id) + self.assertEqual(res, 33.0, 'resource_calendar: wrong get_working_hours computation') + + # -------------------------------------------------- + # Test4: misc + # -------------------------------------------------- + + # Test without calendar and default_interval + res = self.resource_calendar.get_working_hours( + cr, uid, None, + self.date1.replace(hour=6, minute=0), + self.date2.replace(hour=23, minute=0), + compute_leaves=True, resource_id=self.resource1_id, + default_interval=(8, 16)) + self.assertEqual(res, 32.0, 'resource_calendar: wrong get_working_hours computation') + + def test_50_calendar_schedule_days(self): + """ Testing calendar days scheduling """ + cr, uid = self.cr, self.uid + _format = '%Y-%m-%d %H:%M:%S' + + # -------------------------------------------------- + # Test1: with calendar + # -------------------------------------------------- + + res = self.resource_calendar.schedule_days_get_date(cr, uid, self.calendar_id, 5, day_date=self.date1) + self.assertEqual(res.date(), datetime.strptime('2013-02-26 00:0:00', _format).date(), 'resource_calendar: wrong days scheduling') + + res = self.resource_calendar.schedule_days_get_date( + cr, uid, self.calendar_id, 5, day_date=self.date1, + compute_leaves=True, resource_id=self.resource1_id) + self.assertEqual(res.date(), datetime.strptime('2013-03-01 00:0:00', _format).date(), 'resource_calendar: wrong days scheduling') + + # -------------------------------------------------- + # Test2: misc + # -------------------------------------------------- + + # Without calendar, should only count days -> 12 -> 16, 5 days with default intervals + res = self.resource_calendar.schedule_days_get_date(cr, uid, None, 5, day_date=self.date1, default_interval=(8, 16)) + self.assertEqual(res, datetime.strptime('2013-02-16 16:00:00', _format), 'resource_calendar: wrong days scheduling') diff --git a/addons/sale/res_config.py b/addons/sale/res_config.py index accbb8b04a9..3bc081c92cc 100644 --- a/addons/sale/res_config.py +++ b/addons/sale/res_config.py @@ -120,8 +120,8 @@ Example: 10% for retailers, promotion of 5 EUR on this product, etc."""), user = self.pool.get('res.users').browse(cr, uid, uid, context) res['time_unit'] = user.company_id.project_time_mode_id.id else: - product = ir_model_data.get_object(cr, uid, 'product', 'product_product_consultant', check=False) - if product.exists(): + product = ir_model_data.get_object(cr, uid, 'product', 'product_product_consultant', raise_exception=False) + if product and product.exists(): res['time_unit'] = product.uom_id.id return res @@ -138,8 +138,8 @@ Example: 10% for retailers, promotion of 5 EUR on this product, etc."""), wizard = self.browse(cr, uid, ids)[0] if wizard.time_unit: - product = ir_model_data.get_object(cr, uid, 'product', 'product_product_consultant', check=False) - if product.exists(): + product = ir_model_data.get_object(cr, uid, 'product', 'product_product_consultant', raise_exception=False) + if product and product.exists(): product.write({'uom_id': wizard.time_unit.id, 'uom_po_id': wizard.time_unit.id}) else: _logger.warning("Product with xml_id 'product.product_product_consultant' not found, UoMs not updated!") diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index a4c4d5e3dab..02e320deeb0 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -8,6 +8,7 @@ import werkzeug.routing import openerp from openerp.addons.base import ir +from openerp.addons.base.ir import ir_qweb from openerp.addons.website.models.website import slug from openerp.http import request from openerp.osv import orm @@ -109,21 +110,21 @@ class ir_http(orm.AbstractModel): traceback=traceback.format_exc(exception), ) if exception: - if isinstance(exception, openerp.exceptions.AccessError): + current_exception = exception + if isinstance(exception, ir_qweb.QWebException): + values.update(qweb_exception=exception) + if exception.inner: + current_exception = exception.inner + if isinstance(current_exception, openerp.exceptions.AccessError): code = 403 else: code = getattr(exception, 'code', code) - values.update( - qweb_template=getattr(exception, 'qweb_template', None), - qweb_node=getattr(exception, 'qweb_node', None), - qweb_eval=getattr(exception, 'qweb_eval', None), - ) if code == 500: logger.error("500 Internal Server Error:\n\n%s", values['traceback']) - if values['qweb_template']: + if values.get('qweb_exception'): view = request.registry.get("ir.ui.view") - views = view._views_get(request.cr, request.uid, values['qweb_template'], request.context) - to_reset = [view for view in views if view.model_data_id.noupdate == True] + views = view._views_get(request.cr, request.uid, values['qweb_exception'].template, request.context) + to_reset = [v for v in views if v.model_data_id.noupdate is True] values['views'] = to_reset elif code == 403: logger.warn("403 Forbidden:\n\n%s", values['traceback']) diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py index 02fa87e10aa..fadcc8e1276 100644 --- a/addons/website/models/ir_ui_view.py +++ b/addons/website/models/ir_ui_view.py @@ -1,11 +1,9 @@ # -*- coding: utf-8 -*- import copy -from urlparse import urlparse from lxml import etree, html from openerp.osv import osv, fields -from openerp.addons.base import ir class view(osv.osv): _inherit = "ir.ui.view" @@ -24,21 +22,25 @@ class view(osv.osv): # Returns all views (called and inherited) related to a view # Used by translation mechanism, SEO and optional templates def _views_get(self, cr, uid, view, options=True, context=None, root=True, stack_result=None): - if not context: + if not context: context = {} - if not stack_result: + if not stack_result: stack_result = [] def view_obj(view): - if type(view) in (str, unicode): + if isinstance(view, basestring): mod_obj = self.pool.get("ir.model.data") m, n = view.split('.') - _, view = mod_obj.get_object_reference(cr, uid, m, n) - if type(view) == int: - view_obj = self.pool.get("ir.ui.view") - view = view_obj.browse(cr, uid, view, context=context) + view = mod_obj.get_object(cr, uid, m, n, context=context) + elif isinstance(view, (int, long)): + view = self.pool.get("ir.ui.view").browse(cr, uid, view, context=context) return view - view = view_obj(view) + + try: + view = view_obj(view) + except ValueError: + # Shall we log that ? + return [] while root and view.inherit_id: view = view.inherit_id @@ -47,13 +49,18 @@ class view(osv.osv): todo = view.inherit_children_ids if options: todo += filter(lambda x: not x.inherit_id, view.inherited_option_ids) + # Keep options in a determinitic order whatever their enabled disabled status + todo.sort(lambda x,y:cmp(x.id,y.id)) for child_view in todo: - for r in self._views_get(cr, uid, child_view, options=options, context=context, root=False, stack_result=result): + for r in self._views_get(cr, uid, child_view, options=bool(child_view.inherit_id), context=context, root=False, stack_result=result): if r not in result: result.append(r) node = etree.fromstring(view.arch) for child in node.xpath("//t[@t-call]"): - call_view = view_obj(child.get('t-call')) + try: + call_view = view_obj(child.get('t-call')) + except ValueError: + continue if call_view not in result: result += self._views_get(cr, uid, call_view, options=options, context=context, stack_result=result) return result diff --git a/addons/website/static/description/blog.png b/addons/website/static/description/blog.png new file mode 100644 index 00000000000..45da50990f4 Binary files /dev/null and b/addons/website/static/description/blog.png differ diff --git a/addons/website/static/description/event.png b/addons/website/static/description/event.png new file mode 100644 index 00000000000..1bc349f3127 Binary files /dev/null and b/addons/website/static/description/event.png differ diff --git a/addons/website/static/description/index.html b/addons/website/static/description/index.html index 7bf1dc24246..80cb043b07e 100644 --- a/addons/website/static/description/index.html +++ b/addons/website/static/description/index.html @@ -15,11 +15,11 @@

Create enterprise grade website with our super easy builder. Use - finely designed building blocks and edit everything inline. -

- Benefit from out-of-the-box business features; e-Commerce, - events, blogs, jobs announces, customer references, - call-to-actions, etc. + finely designed building blocks and edit everything inline. +

+ Benefit from out-of-the-box business features; e-Commerce, + events, blogs, jobs announces, customer references, + call-to-actions, etc.

Create your free website @@ -37,7 +37,7 @@ Create beautiful websites with no technical knowledge. OpenERP's unique 'edit inline' approach makes website creation surprisingly easy. No more complex backend; just click anywhere to - change any content. + change any content.

"Want to change the price of a product? or put it in bold? Want to change a blog title?" Just click and change. What you see is what @@ -61,14 +61,14 @@

- OpenERP's building blocks allow to design modern - websites that are not possible with traditional WYSIWYG page - editors. -

- Whether it's for products descriptions, blogs or static pages, - you don't need to be a professioanl designer to create clean - contents. Just drag and drop and customize predefined building - blocks. + OpenERP's building blocks allow to design modern + websites that are not possible with traditional WYSIWYG page + editors. +

+ Whether it's for products descriptions, blogs or static pages, + you don't need to be a professioanl designer to create clean + contents. Just drag and drop and customize predefined building + blocks.

@@ -107,10 +107,10 @@

- Get a mobile friendly website thanks to our responsive design - based on bootstrap. All your pages adapt automatically to the - screen size. (mobile phones, tablets, desktop) You don't have to - worry about mobile contents, it works by default. + Get a mobile friendly website thanks to our responsive design + based on bootstrap. All your pages adapt automatically to the + screen size. (mobile phones, tablets, desktop) You don't have to + worry about mobile contents, it works by default.

@@ -122,11 +122,11 @@

SEO tools at your finger tips

- The Promote tool suggests keywords according to - Google most searched terms. Search Engine Optimization tools are - ready to use, with no configuration required. -

- Google Analytics tracks your shopping cart events by default. + The Promote tool suggests keywords according to + Google most searched terms. Search Engine Optimization tools are + ready to use, with no configuration required. +

+ Google Analytics tracks your shopping cart events by default. Sitemap and structured content are created automatically for Google indexation.

@@ -154,11 +154,11 @@ OpenERP proposes and propagates translations automatically across pages, following what you edit on the master page.

- Benefit from professional translators to translate all your - contents automatically with the Gengo integration. Update any part of - your website and the translated versions are pushed - automatically in a few hours. + Benefit from professional translators to translate all your + contents automatically with the Gengo integration. Update any part of + your website and the translated versions are pushed + automatically in a few hours.

@@ -175,8 +175,8 @@ HTML structure, a bootstrap CSS.

- Customize every page on the fly with the integrated template - editor. Distribute your work easily as an OpenERP module. + Customize every page on the fly with the integrated template + editor. Distribute your work easily as an OpenERP module.

@@ -198,12 +198,12 @@

- Design perfect pages by drag and dropping building blocks. Move - and scale them to fit the layout you are looking for. + Design perfect pages by drag and dropping building blocks. Move + and scale them to fit the layout you are looking for.

- Building blocks are based on a responsive, mobile friendly fluid - grid system that appropriately scales up to 12 columns as the - device or viewport size increases. + Building blocks are based on a responsive, mobile friendly fluid + grid system that appropriately scales up to 12 columns as the + device or viewport size increases.

@@ -215,11 +215,11 @@

Change Theme in Just a Click

- Design a custom theme or reuse pre-defined themes to - customize the look and feel of your website. + Design a custom theme or reuse pre-defined themes to + customize the look and feel of your website.

- Test new color scheme easily; you can change your theme - at any time in just a click. + Test new color scheme easily; you can change your theme + at any time in just a click.

@@ -245,11 +245,11 @@

- -

Blogs

+
+

Blogs

Write news, attract new visitors, build customer loyalty. @@ -257,10 +257,10 @@

-

Online Events

+

Online Events

Schedule, organize, promote or sell events online; conferences, diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index 824333ee5f8..7617500f565 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -455,7 +455,7 @@

-
+
-
+

Template fallback

-

An error occured while rendering the template .

+

An error occured while rendering the template .

If this error is caused by a change of yours in the templates, you have the possibility to reset one or more templates to their factory settings.

    diff --git a/addons/website_blog/static/description/index.html b/addons/website_blog/static/description/index.html index 8299000d901..d25e8f33cc1 100644 --- a/addons/website_blog/static/description/index.html +++ b/addons/website_blog/static/description/index.html @@ -6,7 +6,7 @@
- + diff --git a/addons/website_blog/tests/__init__.py b/addons/website_blog/tests/__init__.py index 5e7b52ddf04..157f4549988 100644 --- a/addons/website_blog/tests/__init__.py +++ b/addons/website_blog/tests/__init__.py @@ -19,12 +19,6 @@ # ############################################################################## -import test_controllers - import test_ui -checks = [ - test_controllers, -] - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/website_blog/tests/test_website_blog.yml b/addons/website_blog/tests/test_website_blog.yml index b043215e2c1..db70b286bc8 100644 --- a/addons/website_blog/tests/test_website_blog.yml +++ b/addons/website_blog/tests/test_website_blog.yml @@ -12,12 +12,6 @@ FAQs, quality manuals, technical references, etc.' -# - -# I check the blog index contains my page. -# - - # !python {model: blog.post}: | - # res = self.read(cr, uid, [ref('blog_blog_1')], ['display_content']) - # assert res[0]['display_content'].find('Test Page') > 1 - !record {model: blog.post, id: test_page0}: content: 'Test updated content @@ -37,18 +31,3 @@ hist_obj = model.pool.get('blog.post.history') ids = hist_obj.search(cr, uid, [('post_id', '=', ref("test_page0"))]) model.get_diff(cr, uid, {'active_ids': ids[:] }) -# - -# I click the "create menu" link and i fill the form. -# - - # !record {model: document.page.create.menu, id: test_create_menu0}: - # menu_name: Wiki Test menu - # menu_parent_id: base.menu_base_partner -# - -# I create a Menu by clicking on "create menu" -# - - # !python {model: document.page.create.menu}: | - # ids = [ref("test_create_menu0")] - # context['active_id'] = ref('test_page0') - # self.document_page_menu_create(cr, uid, ids, context) - - diff --git a/addons/website_blog/views/website_blog_templates.xml b/addons/website_blog/views/website_blog_templates.xml index f66f1d205b2..f0e090e1d15 100644 --- a/addons/website_blog/views/website_blog_templates.xml +++ b/addons/website_blog/views/website_blog_templates.xml @@ -100,7 +100,7 @@

No blog post found

-

There isn't available blog post right now, click here to contact us

+

Nothing published yet click here to contact us

Click on "Content" to define a new blog post or "Help" for more informations.

diff --git a/addons/website_blog/views/website_blog_views.xml b/addons/website_blog/views/website_blog_views.xml index 3f435ba8cbd..114c9d6961f 100644 --- a/addons/website_blog/views/website_blog_views.xml +++ b/addons/website_blog/views/website_blog_views.xml @@ -155,14 +155,5 @@ name="Page History" res_model="blog.post.history" src_model="blog.post"/> - - diff --git a/addons/website_crm/static/description/index.html b/addons/website_crm/static/description/index.html index 864297ea1c1..897bee4011e 100644 --- a/addons/website_crm/static/description/index.html +++ b/addons/website_crm/static/description/index.html @@ -60,7 +60,7 @@

- +

Blogs

diff --git a/addons/website_crm_partner_assign/controllers/main.py b/addons/website_crm_partner_assign/controllers/main.py index faceb692a38..8337484f948 100644 --- a/addons/website_crm_partner_assign/controllers/main.py +++ b/addons/website_crm_partner_assign/controllers/main.py @@ -37,7 +37,7 @@ class WebsiteCrmPartnerAssign(http.Controller): country = country_obj.browse(request.cr, request.uid, country_id, request.context) partner_domain += [('country_id', '=', country_id)] if post_name: - partner_domain += ['|', ('name', 'ilike', "%%%s%%" % post_name), ('website_description', 'ilike', "%%%s%%" % post_name)] + partner_domain += ['|', ('name', 'ilike', post_name), ('website_description', 'ilike', post_name)] # format pager partner_ids = partner_obj.search( diff --git a/addons/website_event/static/description/blog.png b/addons/website_event/static/description/blog.png new file mode 100644 index 00000000000..45da50990f4 Binary files /dev/null and b/addons/website_event/static/description/blog.png differ diff --git a/addons/website_event/static/description/ecommerce.png b/addons/website_event/static/description/ecommerce.png new file mode 100644 index 00000000000..3000b43c0b7 Binary files /dev/null and b/addons/website_event/static/description/ecommerce.png differ diff --git a/addons/website_event/static/description/index.html b/addons/website_event/static/description/index.html index b7fa4745d38..bf5f11ec300 100644 --- a/addons/website_event/static/description/index.html +++ b/addons/website_event/static/description/index.html @@ -217,21 +217,21 @@

Get hundreds of open source apps for free

-

eCommerce

- +
+

eCommerce

Promote products, sell online, optimize visitors' shopping experiences.

- -

Blogs

+
- +
+

Blogs

Write news, attract new visitors, build customer loyalty. @@ -239,10 +239,10 @@

-

Our Team

- +
+

Our Team

Create a great "About us" page by presenting your team diff --git a/addons/website_event/static/description/recruit.png b/addons/website_event/static/description/recruit.png new file mode 100644 index 00000000000..eb3a4f9a928 Binary files /dev/null and b/addons/website_event/static/description/recruit.png differ diff --git a/addons/website_event_track/models/event.py b/addons/website_event_track/models/event.py index c63d0205285..47711c3ae9e 100644 --- a/addons/website_event_track/models/event.py +++ b/addons/website_event_track/models/event.py @@ -127,7 +127,7 @@ class event_event(osv.osv): 'tag_ids': fields.many2many('event.tag', string='Tags'), 'track_ids': fields.one2many('event.track', 'event_id', 'Tracks'), 'sponsor_ids': fields.one2many('event.sponsor', 'event_id', 'Sponsorships'), - 'blog_id': fields.many2one('blog.category', 'Event Blog'), + 'blog_id': fields.many2one('blog.blog', 'Event Blog'), 'show_track_proposal': fields.boolean('Talks Proposals'), 'show_tracks': fields.boolean('Multiple Tracks'), 'show_blog': fields.boolean('News'), diff --git a/addons/website_event_track/static/description/blog.png b/addons/website_event_track/static/description/blog.png new file mode 100644 index 00000000000..45da50990f4 Binary files /dev/null and b/addons/website_event_track/static/description/blog.png differ diff --git a/addons/website_event_track/static/description/event.png b/addons/website_event_track/static/description/event.png new file mode 100644 index 00000000000..1bc349f3127 Binary files /dev/null and b/addons/website_event_track/static/description/event.png differ diff --git a/addons/website_event_track/static/description/index.html b/addons/website_event_track/static/description/index.html index a4e20a30262..2612f75d5d3 100644 --- a/addons/website_event_track/static/description/index.html +++ b/addons/website_event_track/static/description/index.html @@ -7,7 +7,7 @@

- +
@@ -42,7 +42,7 @@
- +
@@ -53,7 +53,7 @@

Agenda and List of Talks

A strong user interface

- +

@@ -79,7 +79,7 @@

- +
@@ -89,7 +89,7 @@

Communicate Efficiently

Activate a blog for some events

- +

diff --git a/addons/website_event_track/static/description/sponsor.png b/addons/website_event_track/static/description/sponsor.png new file mode 100644 index 00000000000..63fb9331e5d Binary files /dev/null and b/addons/website_event_track/static/description/sponsor.png differ diff --git a/addons/website_event_track/static/description/tracks.png b/addons/website_event_track/static/description/tracks.png new file mode 100644 index 00000000000..b74a144b006 Binary files /dev/null and b/addons/website_event_track/static/description/tracks.png differ diff --git a/addons/website_hr_recruitment/static/description/blog.png b/addons/website_hr_recruitment/static/description/blog.png new file mode 100644 index 00000000000..45da50990f4 Binary files /dev/null and b/addons/website_hr_recruitment/static/description/blog.png differ diff --git a/addons/website_hr_recruitment/static/description/events.png b/addons/website_hr_recruitment/static/description/events.png new file mode 100644 index 00000000000..62cff4396ec Binary files /dev/null and b/addons/website_hr_recruitment/static/description/events.png differ diff --git a/addons/website_hr_recruitment/static/description/hr_recruitment.png b/addons/website_hr_recruitment/static/description/hr_recruitment.png new file mode 100644 index 00000000000..55975f30219 Binary files /dev/null and b/addons/website_hr_recruitment/static/description/hr_recruitment.png differ diff --git a/addons/website_hr/static/description/index.html b/addons/website_hr_recruitment/static/description/index.html similarity index 92% rename from addons/website_hr/static/description/index.html rename to addons/website_hr_recruitment/static/description/index.html index 1c46b42e2e7..1a56da0e82c 100644 --- a/addons/website_hr/static/description/index.html +++ b/addons/website_hr_recruitment/static/description/index.html @@ -7,7 +7,7 @@

- +
@@ -42,7 +42,7 @@
- +
@@ -53,7 +53,7 @@

Post Your Jobs on Best Job Boards

LinkedIn, Monster, Kraigslist, Careerbuilder,...

- +

@@ -87,7 +87,7 @@

- +
@@ -121,7 +121,7 @@

Define your own online or offline surveys

- +
@@ -141,20 +141,20 @@

Get hundreds of open source apps for free

-

CMS

- +
+

CMS

Easily create awesome websites with no technical knowledge required.

- +

Blogs

- +

@@ -163,10 +163,10 @@

-

Online Events

- +
+

Online Events

Schedule, organize, promote or sell events online; conferences, diff --git a/addons/website_hr_recruitment/static/description/jobs.png b/addons/website_hr_recruitment/static/description/jobs.png new file mode 100644 index 00000000000..e4e531d3a7d Binary files /dev/null and b/addons/website_hr_recruitment/static/description/jobs.png differ diff --git a/addons/website_hr_recruitment/static/description/jobs2.png b/addons/website_hr_recruitment/static/description/jobs2.png new file mode 100644 index 00000000000..07185ed73c6 Binary files /dev/null and b/addons/website_hr_recruitment/static/description/jobs2.png differ diff --git a/addons/website_hr_recruitment/static/description/jobs3.png b/addons/website_hr_recruitment/static/description/jobs3.png new file mode 100644 index 00000000000..d52a650e91d Binary files /dev/null and b/addons/website_hr_recruitment/static/description/jobs3.png differ diff --git a/addons/website_hr_recruitment/static/description/jobs4.png b/addons/website_hr_recruitment/static/description/jobs4.png new file mode 100644 index 00000000000..55975f30219 Binary files /dev/null and b/addons/website_hr_recruitment/static/description/jobs4.png differ diff --git a/addons/website_hr_recruitment/static/description/jobs5.png b/addons/website_hr_recruitment/static/description/jobs5.png new file mode 100644 index 00000000000..9682bd3ae70 Binary files /dev/null and b/addons/website_hr_recruitment/static/description/jobs5.png differ diff --git a/addons/website_hr_recruitment/static/description/website.png b/addons/website_hr_recruitment/static/description/website.png new file mode 100644 index 00000000000..5d9f8634574 Binary files /dev/null and b/addons/website_hr_recruitment/static/description/website.png differ diff --git a/addons/website_quote/models/order.py b/addons/website_quote/models/order.py index ab2c1641fec..f515ef0fd05 100644 --- a/addons/website_quote/models/order.py +++ b/addons/website_quote/models/order.py @@ -78,14 +78,23 @@ class sale_order_line(osv.osv): _description = "Sales Order Line" _columns = { 'website_description': fields.html('Line Description'), - 'option_line_id':fields.one2many('sale.order.option', 'line_id', 'Optional Products Lines'), + 'option_line_id': fields.one2many('sale.order.option', 'line_id', 'Optional Products Lines'), } - def product_id_change(self, cr, uid, ids, pricelist, product, qty=0, uom=False, qty_uos=0, uos=False, name='', partner_id=False, lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False, context=None): - res = super(sale_order_line, self).product_id_change(cr, uid, ids, pricelist, product, qty, uom, qty_uos, uos, name, partner_id, lang, update_tax, date_order, packaging, fiscal_position, flag, context) - if product: - desc = self.pool.get('product.product').browse(cr, uid, product, context).website_description - res.get('value').update({'website_description': desc}) - return res + + def _inject_website_description(self, cr, uid, values, context=None): + values = dict(values or {}) + if not values.get('website_description') and values.get('product_id'): + product = self.pool['product.product'].browse(cr, uid, values['product_id'], context=context) + values['website_description'] = product.website_description + return values + + def create(self, cr, uid, values, context=None): + values = self._inject_website_description(cr, uid, values, context) + return super(sale_order_line, self).create(cr, uid, values, context=context) + + def write(self, cr, uid, ids, values, context=None): + values = self._inject_website_description(cr, uid, values, context) + return super(sale_order_line, self).write(cr, uid, ids, values, context=context) class sale_order(osv.osv): diff --git a/addons/website_sale/static/description/index.html b/addons/website_sale/static/description/index.html index 7011351d4bc..5fa05560836 100644 --- a/addons/website_sale/static/description/index.html +++ b/addons/website_sale/static/description/index.html @@ -312,7 +312,7 @@