From d42af010ce3cc439523adc0625e9c380469d306c Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Mon, 9 Feb 2015 16:51:57 +0100 Subject: [PATCH 01/12] [IMP] Do manual query in search for negative quants to avoid ORM ordering by id and as such always using the wrong index [IMP] Simplify code [IMP] Negative quant search should include child locations also [IMP] Comment [IMP] Could be child_of also --- addons/stock/stock.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 2392136f9cc..5478b151d2a 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -411,7 +411,12 @@ class stock_quant(osv.osv): self.move_quants_write(cr, uid, to_move_quants, move, location_to, dest_package_id, context=context) self.pool.get('stock.move').recalculate_move_state(cr, uid, to_recompute_move_ids, context=context) if location_to.usage == 'internal': - if self.search(cr, uid, [('product_id', '=', move.product_id.id), ('qty','<', 0)], limit=1, context=context): + # Do manual search for quant to avoid full table scan (order by id) + cr.execute(""" + SELECT 0 FROM stock_quant, stock_location WHERE product_id = %s AND stock_location.id = stock_quant.location_id AND + ((stock_location.parent_left >= %s AND stock_location.parent_left < %s) OR stock_location.id = %s) AND qty < 0.0 LIMIT 1 + """, (move.product_id.id, location_to.parent_left, location_to.parent_right, location_to.id)) + if cr.fetchone(): for quant in quants_reconcile: self._quant_reconcile_negative(cr, uid, quant, move, context=context) From 30b5285c9df1062d0fd539e777463b8c25dc11ae Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Mon, 9 Feb 2015 17:37:47 +0100 Subject: [PATCH 02/12] [IMP] Add composite indexes for stock_move and stock_quant and do manual query for history_ids when splitting quants to avoid unnecessary full table scan on stock_move [IMP] Improve indexes [IMP] history_ids opti [IMP] Clean and document [IMP] Clean --- addons/stock/stock.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 5478b151d2a..cdb86552cc5 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -320,6 +320,11 @@ class stock_quant(osv.osv): 'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.quant', context=c), } + def init(self, cr): + cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('stock_quant_product_location_index',)) + if not cr.fetchone(): + cr.execute('CREATE INDEX stock_quant_product_location_index ON stock_quant (product_id, location_id, company_id, qty, in_date, reservation_id)') + def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False, lazy=True): ''' Overwrite the read_group in order to sum the function field 'inventory_value' in group by''' res = super(stock_quant, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby, lazy=lazy) @@ -530,7 +535,10 @@ class stock_quant(osv.osv): return False qty_round = float_round(qty, precision_rounding=rounding) new_qty_round = float_round(quant.qty - qty, precision_rounding=rounding) - new_quant = self.copy(cr, SUPERUSER_ID, quant.id, default={'qty': new_qty_round, 'history_ids': [(4, x.id) for x in quant.history_ids]}, context=context) + # Fetch the history_ids manually as it will not do a join with the stock moves then (=> a lot faster) + cr.execute("""SELECT move_id FROM stock_quant_move_rel WHERE quant_id = %s""", (quant.id,)) + res = cr.fetchall() + new_quant = self.copy(cr, SUPERUSER_ID, quant.id, default={'qty': new_qty_round, 'history_ids': [(4, x[0]) for x in res]}, context=context) self.write(cr, SUPERUSER_ID, quant.id, {'qty': qty_round}, context=context) return self.browse(cr, uid, new_quant, context=context) @@ -1838,6 +1846,10 @@ class stock_move(osv.osv): 'You try to move a product using a UoM that is not compatible with the UoM of the product moved. Please use an UoM in the same UoM category.', ['product_uom']), ] + def init(self, cr): + cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('stock_move_product_location_index',)) + if not cr.fetchone(): + cr.execute('CREATE INDEX stock_move_product_location_index ON stock_move (product_id, location_id, location_dest_id, company_id, state)') @api.cr_uid_ids_context def do_unreserve(self, cr, uid, move_ids, context=None): From 0b5271e90d210da92b4da97de1f894d2ce6834e0 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Thu, 12 Feb 2015 11:02:52 +0100 Subject: [PATCH 03/12] [FIX] account: always use a copy when altering a context To avoid wrong context propagation --- addons/account/account_move_line.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index 9f85f58fb00..edd0778eedb 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -1053,9 +1053,10 @@ class account_move_line(osv.osv): move_ids = set() for line in self.browse(cr, uid, ids, context=context): move_ids.add(line.move_id.id) - context['journal_id'] = line.journal_id.id - context['period_id'] = line.period_id.id - result = super(account_move_line, self).unlink(cr, uid, [line.id], context=context) + localcontext = dict(context) + localcontext['journal_id'] = line.journal_id.id + localcontext['period_id'] = line.period_id.id + result = super(account_move_line, self).unlink(cr, uid, [line.id], context=localcontext) move_ids = list(move_ids) if check and move_ids: move_obj.validate(cr, uid, move_ids, context=context) From 3d542b1199124225505090df79cae5a1be8e0d57 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Thu, 12 Feb 2015 11:05:17 +0100 Subject: [PATCH 04/12] [FIX] CONTRIBUTING:.md emphasize the fact that PRs supersede issues --- CONTRIBUTING.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 40daace4eda..91e960c97be 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,10 @@ Contributing to Odoo TL;DR -* Use this [template](https://github.com/odoo/odoo/wiki/Contributing#reporting-issues) when reporting issues, and please search for duplicates first! -* The issues not linked to pull requests will be handled with a low priority. +* If you [make a pull request](https://github.com/odoo/odoo/wiki/Contributing#making-pull-requests), + do not create an issue! Use the PR description for that +* Issues are handled with a much lower priority than pull requests +* Use this [template](https://github.com/odoo/odoo/wiki/Contributing#reporting-issues) + when reporting issues. Please search for duplicates first! * Pull requests must be made against the [correct version](https://github.com/odoo/odoo/wiki/Contributing#against-which-version-should-i-submit-a-patch) * There are restrictions on the kind of [changes allowed in stable series](https://github.com/odoo/odoo/wiki/Contributing#what-does-stable-mean) From 314a0358b7a100d05d5c8504953499faf1316c8a Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Thu, 12 Feb 2015 11:54:43 +0100 Subject: [PATCH 05/12] [FIX] website_membership: include free members in members page This is possible to set a partner as a free member simply by checking the "free member" box, in the partner form This leads to the fact there is no membership lines for this partner Before this rev., only partners having membership lines were displayed on the website members page. --- addons/website_membership/controllers/main.py | 50 +++++++++++++------ .../views/website_membership.xml | 45 +++++++++-------- 2 files changed, 59 insertions(+), 36 deletions(-) diff --git a/addons/website_membership/controllers/main.py b/addons/website_membership/controllers/main.py index c88fb43da9d..942c3f13267 100644 --- a/addons/website_membership/controllers/main.py +++ b/addons/website_membership/controllers/main.py @@ -14,18 +14,18 @@ class WebsiteMembership(http.Controller): @http.route([ '/members', '/members/page/', - '/members/association/', - '/members/association//page/', + '/members/association/', + '/members/association//page/', '/members/country/', '/members/country/-', '/members/country//page/', '/members/country/-/page/', - '/members/association//country/-', - '/members/association//country/', - '/members/association//country/-/page/', - '/members/association//country//page/', + '/members/association//country/-', + '/members/association//country/', + '/members/association//country/-/page/', + '/members/association//country//page/', ], type='http', auth="public", website=True) def members(self, membership_id=None, country_name=None, country_id=0, page=0, **post): cr, uid, context = request.cr, request.uid, request.context @@ -38,7 +38,8 @@ class WebsiteMembership(http.Controller): # base domain for groupby / searches base_line_domain = [("partner.website_published", "=", True), ('state', 'in', ['free', 'paid'])] - if membership_id: + if membership_id and membership_id != 'free': + membership_id = int(membership_id) base_line_domain.append(('membership_id', '=', membership_id)) membership = product_obj.browse(cr, uid, membership_id, context=context) else: @@ -48,9 +49,14 @@ class WebsiteMembership(http.Controller): ('partner.website_description', 'ilike', post_name)] # group by country, based on all customers (base domain) - membership_line_ids = membership_line_obj.search(cr, SUPERUSER_ID, base_line_domain, context=context) + if membership_id != 'free': + membership_line_ids = membership_line_obj.search(cr, SUPERUSER_ID, base_line_domain, context=context) + country_domain = ('member_lines', 'in', membership_line_ids) + else: + membership_line_ids = [] + country_domain = ('free_member', '=', True) countries = partner_obj.read_group( - cr, uid, [('member_lines', 'in', membership_line_ids), ("website_published", "=", True)], ["id", "country_id"], + cr, uid, [country_domain, ("website_published", "=", True)], ["id", "country_id"], groupby="country_id", orderby="country_id", context=request.context) countries_total = sum(country_dict['country_id_count'] for country_dict in countries) @@ -77,22 +83,38 @@ class WebsiteMembership(http.Controller): line_domain.append(('membership_id', 'in', membership_ids)) # displayed membership lines - membership_line_ids = membership_line_obj.search(cr, SUPERUSER_ID, line_domain, context=context) + if membership_id != 'free': + membership_line_ids = membership_line_obj.search(cr, SUPERUSER_ID, line_domain, context=context) membership_lines = membership_line_obj.browse(cr, uid, membership_line_ids, context=context) + # TODO: Following line can be deleted in master. Kept for retrocompatibility. membership_lines = sorted(membership_lines, key=lambda x: x.membership_id.website_sequence) partner_ids = [m.partner.id for m in membership_lines] + free_partner_ids = partner_obj.search(cr, uid, [('free_member', '=', True), ('website_published', '=', True)], context=context) google_map_partner_ids = ",".join(map(str, partner_ids)) + memberships_data = [] + for membership_record in memberships: + memberships_data.append({'id': membership_record.id, 'name': membership_record.name}) + memberships_partner_ids = {} + for line in membership_lines: + memberships_partner_ids.setdefault(line.membership_id.id, []).append(line.partner.id) + if free_partner_ids: + memberships_data.append({'id': 'free', 'name': _('Free Members')}) + if not membership_id or membership_id == 'free': + memberships_partner_ids['free'] = free_partner_ids - partners = dict((p.id, p) for p in partner_obj.browse(request.cr, SUPERUSER_ID, partner_ids, request.context)) + partners = dict((p.id, p) for p in partner_obj.browse(request.cr, SUPERUSER_ID, list(set(partner_ids + free_partner_ids)), request.context)) # request pager for lines pager = request.website.pager(url="/members", total=len(membership_line_ids), page=page, step=self._references_per_page, scope=7, url_args=post) values = { 'partners': partners, - 'membership_lines': membership_lines, - 'memberships': memberships, - 'membership': membership, + 'membership_lines': membership_lines, # TODO: This line can be deleted in master. Kept for retrocompatibility. + 'memberships': memberships, # TODO: This line too. + 'membership': membership, # TODO: This line too. + 'memberships_data': memberships_data, + 'memberships_partner_ids': memberships_partner_ids, + 'membership_id': membership_id, 'countries': countries, 'current_country': current_country and [current_country['id'], current_country['name']] or None, 'current_country_id': current_country and current_country['id'] or 0, diff --git a/addons/website_membership/views/website_membership.xml b/addons/website_membership/views/website_membership.xml index 3f4e6b0a49d..40bf6fcb028 100644 --- a/addons/website_membership/views/website_membership.xml +++ b/addons/website_membership/views/website_membership.xml @@ -31,10 +31,10 @@
@@ -51,27 +51,28 @@
- +

No result found.

- - - -

+ + +

+ + +
+ +
+ + + +
+
+
+ - -
- -
- - - -
-
-
From f540c2c5862dbaf2f09422dd7e458ac9c1d2baef Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Wed, 11 Feb 2015 21:25:10 +0100 Subject: [PATCH 06/12] [FIX] calendar: add missing menu to manage calendar alarm --- addons/calendar/calendar_view.xml | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/addons/calendar/calendar_view.xml b/addons/calendar/calendar_view.xml index 155292c3e4e..725a7b7f48e 100644 --- a/addons/calendar/calendar_view.xml +++ b/addons/calendar/calendar_view.xml @@ -21,6 +21,27 @@ form + + + + Calendar Alarm Tree + calendar.alarm + + + + + + + + + + + + Calendar Alarm + calendar.alarm + form + + @@ -226,7 +247,6 @@ - @@ -311,10 +331,13 @@ + + - + + Meetings From 064f18e6b6003b7fcb3aa5f5072e8e00d18acbb0 Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Wed, 11 Feb 2015 21:31:20 +0100 Subject: [PATCH 07/12] [FIX] calendar: stop to think that cron will be always on time Fix error where if no email_from was in openerp.tool.config, notif by mail was not sent Add optionnal param 'missing' to allow to have missing alarm. Missing field is the date from the last time that cron had run. Now we stop to process the next 30 minutes, but we process all alarm since the next cron. So, an email alarm will be always in delay, but a mail will be sent ! In v8 we use ICP to save the date, but it should be changed in master, maybe we should save the last execution time in the ir_cron model directly ? --- addons/calendar/calendar.py | 91 ++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 41 deletions(-) diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py index 8943fe4c03f..2e8513fc5c2 100644 --- a/addons/calendar/calendar.py +++ b/addons/calendar/calendar.py @@ -373,12 +373,12 @@ class calendar_alarm_manager(osv.AbstractModel): tuple_params += (partner_id, ) #Add filter on hours - tuple_params += (seconds, seconds,) + tuple_params += (seconds,) cr.execute("""SELECT * FROM ( %s WHERE cal.active = True ) AS ALL_EVENTS WHERE ALL_EVENTS.first_alarm < (now() at time zone 'utc' + interval '%%s' second ) - AND ALL_EVENTS.last_alarm > (now() at time zone 'utc' - interval '%%s' second ) + AND ALL_EVENTS.last_event_date > (now() at time zone 'utc') """ % base_request, tuple_params) for event_id, first_alarm, last_alarm, first_meeting, last_meeting, min_duration, max_duration, rule in cr.fetchall(): @@ -395,20 +395,30 @@ class calendar_alarm_manager(osv.AbstractModel): return res - def do_check_alarm_for_one_date(self, cr, uid, one_date, event, event_maxdelta, in_the_next_X_seconds, after=False, notif=True, mail=True, context=None): - res = [] - alarm_type = [] + def do_check_alarm_for_one_date(self, cr, uid, one_date, event, event_maxdelta, in_the_next_X_seconds, after=False, notif=True, mail=True, missing=False, context=None): + # one_date: date of the event to check (not the same that in the event browse if recurrent) + # event: Event browse record + # event_maxdelta: biggest duration from alarms for this event + # in_the_next_X_seconds: looking in the future (in seconds) + # after: if not False: will return alert if after this date (date as string - todo: change in master) + # missing: if not False: will return alert even if we are too late + # notif: Looking for type notification + # mail: looking for type email + res = [] + + # TODO: replace notif and email in master by alarm_type + remove event_maxdelta and if using it + alarm_type = [] if notif: alarm_type.append('notification') if mail: alarm_type.append('email') - if one_date - timedelta(minutes=event_maxdelta) < datetime.now() + timedelta(seconds=in_the_next_X_seconds): # if an alarm is possible for this date + if one_date - timedelta(minutes=(missing and 0 or event_maxdelta)) < datetime.now() + timedelta(seconds=in_the_next_X_seconds): # if an alarm is possible for this date for alarm in event.alarm_ids: if alarm.type in alarm_type and \ - one_date - timedelta(minutes=alarm.duration_minutes) < datetime.now() + timedelta(seconds=in_the_next_X_seconds) and \ - (not after or one_date - timedelta(minutes=alarm.duration_minutes) > datetime.strptime(after.split('.')[0], DEFAULT_SERVER_DATETIME_FORMAT)): + one_date - timedelta(minutes=(missing and 0 or alarm.duration_minutes)) < datetime.now() + timedelta(seconds=in_the_next_X_seconds) and \ + (not after or one_date - timedelta(minutes=alarm.duration_minutes) > openerp.fields.Datetime.from_string(after)): alert = { 'alarm_id': alarm.id, 'event_id': event.id, @@ -418,55 +428,53 @@ class calendar_alarm_manager(osv.AbstractModel): return res def get_next_mail(self, cr, uid, context=None): + now = openerp.fields.Datetime.to_string(datetime.now()) + + icp = self.pool['ir.config_parameter'] + last_notif_mail = icp.get_param(cr, SUPERUSER_ID, 'calendar.last_notif_mail', default=False) or now + try: - cron = self.pool['ir.model.data'].get_object( - cr, uid, 'calendar', 'ir_cron_scheduler_alarm', context=context) + cron = self.pool['ir.model.data'].get_object(cr, uid, 'calendar', 'ir_cron_scheduler_alarm', context=context) except ValueError: _logger.error("Cron for " + self._name + " can not be identified !") return False - if cron.interval_type == "weeks": - cron_interval = cron.interval_number * 7 * 24 * 60 * 60 - elif cron.interval_type == "days": - cron_interval = cron.interval_number * 24 * 60 * 60 - elif cron.interval_type == "hours": - cron_interval = cron.interval_number * 60 * 60 - elif cron.interval_type == "minutes": - cron_interval = cron.interval_number * 60 - elif cron.interval_type == "seconds": - cron_interval = cron.interval_number - else: - cron_interval = False + interval_to_second = { + "weeks": 7 * 24 * 60 * 60, + "days": 24 * 60 * 60, + "hours": 60 * 60, + "minutes": 60, + "seconds": 1 + } - if not cron_interval: + if cron.interval_type not in interval_to_second.keys(): _logger.error("Cron delay can not be computed !") return False + cron_interval = cron.interval_number * interval_to_second[cron.interval_type] + all_events = self.get_next_potential_limit_alarm(cr, uid, cron_interval, notif=False, context=context) - for event in all_events: # .values() - max_delta = all_events[event]['max_duration'] - curEvent = self.pool.get('calendar.event').browse(cr, uid, event, context=context) + for curEvent in self.pool.get('calendar.event').browse(cr, uid, all_events.keys(), context=context): + max_delta = all_events[curEvent.id]['max_duration'] + if curEvent.recurrency: - bFound = False - LastFound = False + at_least_one = False + last_found = False for one_date in self.pool.get('calendar.event').get_recurrent_date_by_event(cr, uid, curEvent, context=context): in_date_format = one_date.replace(tzinfo=None) - LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, cron_interval, notif=False, context=context) - if LastFound: - for alert in LastFound: - self.do_mail_reminder(cr, uid, alert, context=context) - - if not bFound: # if it's the first alarm for this recurrent event - bFound = True - if bFound and not LastFound: # if the precedent event had an alarm but not this one, we can stop the search for this event + last_found = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, 0, after=last_notif_mail, notif=False, missing=True, context=context) + for alert in last_found: + self.do_mail_reminder(cr, uid, alert, context=context) + at_least_one = True # if it's the first alarm for this recurrent event + if at_least_one and not last_found: # if the precedent event had an alarm but not this one, we can stop the search for this event break else: in_date_format = datetime.strptime(curEvent.start, DEFAULT_SERVER_DATETIME_FORMAT) - LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, cron_interval, notif=False, context=context) - if LastFound: - for alert in LastFound: - self.do_mail_reminder(cr, uid, alert, context=context) + last_found = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, 0, after=last_notif_mail, notif=False, missing=True, context=context) + for alert in last_found: + self.do_mail_reminder(cr, uid, alert, context=context) + icp.set_param(cr, SUPERUSER_ID, 'calendar.last_notif_mail', now) def get_next_notif(self, cr, uid, context=None): ajax_check_every_seconds = 300 @@ -496,7 +504,7 @@ class calendar_alarm_manager(osv.AbstractModel): break else: in_date_format = datetime.strptime(curEvent.start, DEFAULT_SERVER_DATETIME_FORMAT) - LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, ajax_check_every_seconds, partner.calendar_last_notif_ack, mail=False, context=context) + LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, ajax_check_every_seconds, after=partner.calendar_last_notif_ack, mail=False, context=context) if LastFound: for alert in LastFound: all_notif.append(self.do_notif_reminder(cr, uid, alert, context=context)) @@ -515,6 +523,7 @@ class calendar_alarm_manager(osv.AbstractModel): cr, uid, [att.id for att in event.attendee_ids], + email_from=event.user_id.partner_id.email, template_xmlid='calendar_template_meeting_reminder', force=True, context=context From 8ef8cf7dba3310fbce347a7882e43fa1ca03fd73 Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Wed, 11 Feb 2015 21:28:38 +0100 Subject: [PATCH 08/12] [IMP] calendar: use from_string and to_string from new api to convert datetime/string --- addons/calendar/calendar.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py index 2e8513fc5c2..7de68bfde07 100644 --- a/addons/calendar/calendar.py +++ b/addons/calendar/calendar.py @@ -124,9 +124,9 @@ class calendar_attendee(osv.Model): def ics_datetime(idate, allday=False): if idate: if allday: - return datetime.strptime(idate.split(' ')[0], DEFAULT_SERVER_DATE_FORMAT).replace(tzinfo=pytz.timezone('UTC')) + return openerp.fields.Date.from_string(idate) else: - return datetime.strptime(idate.split('.')[0], DEFAULT_SERVER_DATETIME_FORMAT).replace(tzinfo=pytz.timezone('UTC')) + return openerp.fields.Datetime.from_string(idate).replace(tzinfo=pytz.timezone('UTC')) return False try: @@ -893,12 +893,12 @@ class calendar_event(osv.Model): stop_date = values.get('stop_date') or event.stop_date start_date = values.get('start_date') or event.start_date if stop_date and start_date: - diff = datetime.strptime(stop_date.split(' ')[0], DEFAULT_SERVER_DATE_FORMAT) - datetime.strptime(start_date.split(' ')[0], DEFAULT_SERVER_DATE_FORMAT) + diff = openerp.fields.Date.from_string(stop_date) - openerp.fields.Date.from_string(start_date) elif values.get('stop_datetime') or values.get('start_datetime'): stop_datetime = values.get('stop_datetime') or event.stop_datetime start_datetime = values.get('start_datetime') or event.start_datetime if stop_datetime and start_datetime: - diff = datetime.strptime(stop_datetime.split('.')[0], DEFAULT_SERVER_DATETIME_FORMAT) - datetime.strptime(start_datetime.split('.')[0], DEFAULT_SERVER_DATETIME_FORMAT) + diff = openerp.fields.Datetime.from_string(stop_datetime) - openerp.fields.Datetime.from_string(start_datetime) if diff: duration = float(diff.days) * 24 + (float(diff.seconds) / 3600) values['duration'] = round(duration, 2) @@ -1020,7 +1020,7 @@ class calendar_event(osv.Model): tz = pytz.timezone(user.tz) if user.tz else pytz.utc if starttime: - start = datetime.strptime(starttime.split(' ')[0], DEFAULT_SERVER_DATE_FORMAT) + start = openerp.fields.Datetime.from_string(starttime) startdate = tz.localize(start) # Add "+hh:mm" timezone startdate = startdate.replace(hour=8) # Set 8 AM in localtime startdate = startdate.astimezone(pytz.utc) # Convert to UTC @@ -1441,7 +1441,7 @@ class calendar_event(osv.Model): def get_interval(self, cr, uid, ids, date, interval, tz=None, context=None): #Function used only in calendar_event_data.xml for email template - date = datetime.strptime(date.split('.')[0], DEFAULT_SERVER_DATETIME_FORMAT) + date = openerp.fields.Datetime.from_string(date) if tz: timezone = pytz.timezone(tz or 'UTC') From 6740cdaff56e653a21dfa1e189cc265076d8f6b0 Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Thu, 12 Feb 2015 10:32:45 +0100 Subject: [PATCH 09/12] [IMP] calendar: change default data for calendar alarm --- addons/calendar/calendar_data.xml | 32 ++++++------------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/addons/calendar/calendar_data.xml b/addons/calendar/calendar_data.xml index e13edaf6312..f41a34955e4 100644 --- a/addons/calendar/calendar_data.xml +++ b/addons/calendar/calendar_data.xml @@ -38,39 +38,19 @@ days notification - - - + - 15 min mail - - minutes + 2~3 hours mail + + hours email - 30 min mail - - minutes - email - - - 1 hour mail - + 5~6 hours mail + hours email - - 2 hours mail - - hours - email - - - 1 day mail - - days - email - Customer Meeting From 7c751e92436c9546fb01d71ea142322efd268cda Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Thu, 12 Feb 2015 11:39:53 +0100 Subject: [PATCH 10/12] [FIX] web: insert notification container into the .openerp container During the refactoring of the web client, the self.$el has been changed from .openerp to body, so the notification was no more into the .openerp container. Custom css (eg .highlight) was no more applicated. --- addons/web/static/src/js/chrome.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index b18debb1d96..fc40b1e7bf2 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -1172,7 +1172,7 @@ instance.web.Client = instance.web.Widget.extend({ this.crashmanager = new instance.web.CrashManager(); instance.session.on('error', this.crashmanager, this.crashmanager.rpc_error); self.notification = new instance.web.Notification(this); - self.notification.appendTo(self.$el); + self.notification.appendTo(self.$el.find('.openerp')); self.loading = new instance.web.Loading(self); self.loading.appendTo(self.$('.openerp_webclient_container')); self.action_manager = new instance.web.ActionManager(self); From 9fbdd2d83df002e2cba9b02a96cbd43d2996d42d Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Wed, 11 Feb 2015 18:13:49 +0100 Subject: [PATCH 11/12] [IMP] website: improve speed of test crawl and load only one slug by route If they are this routes: /partner/p-1 /partner/p-2 ... /partner/grade-1/p-1 /partner/grade-1/p-2 ... /partner/grade-2/p-1 /partner/grade-2/p-2 ... We want test only one time the routes: /partner/p-1 /partner/grade-1/p-1 --- addons/website/tests/test_crawl.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/addons/website/tests/test_crawl.py b/addons/website/tests/test_crawl.py index 56cd6f181ca..a548cb05af0 100644 --- a/addons/website/tests/test_crawl.py +++ b/addons/website/tests/test_crawl.py @@ -1,15 +1,12 @@ # -*- coding: utf-8 -*- import logging import urlparse -import unittest2 -import urllib2 import time -import werkzeug.urls import lxml.html import openerp -from openerp import tools +import re _logger = logging.getLogger(__name__) @@ -25,12 +22,15 @@ class Crawler(openerp.tests.HttpCase): post_install = True def crawl(self, url, seen=None, msg=''): - if seen == None: + if seen == None: seen = set() - if url in seen: + + url_slug = re.sub(r"[/](([^/=?&]+-)?[0-9]+)([/]|$)", '//', url) + url_slug = re.sub(r"([^/=?&]+)=[^/=?&]+", '\g<1>=param', url_slug) + if url_slug in seen: return seen else: - seen.add(url) + seen.add(url_slug) _logger.info("%s %s", msg, url) r = self.url_open(url) From c7648a10260b47973fe788fdbdbb1053506b39d7 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Thu, 12 Feb 2015 13:20:25 +0100 Subject: [PATCH 12/12] [FIX] event_sale: data have to be in `noupdate` --- addons/event_sale/event_sale_data.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/event_sale/event_sale_data.xml b/addons/event_sale/event_sale_data.xml index 9c5dae6042b..38e1f2434fb 100644 --- a/addons/event_sale/event_sale_data.xml +++ b/addons/event_sale/event_sale_data.xml @@ -1,6 +1,6 @@ - + Generic Events