diff --git a/addons/account/account.py b/addons/account/account.py index 3537c4f4c55..b87caf547ab 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -840,16 +840,11 @@ class account_journal(osv.osv): def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100): if not args: args = [] - if context is None: - context = {} - ids = [] - if context.get('journal_type', False): - args += [('type','=',context.get('journal_type'))] - if name: - ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit, context=context) - if not ids: - ids = self.search(cr, user, [('name', 'ilike', name)]+ args, limit=limit, context=context)#fix it ilike should be replace with operator - + if operator in expression.NEGATIVE_TERM_OPERATORS: + domain = [('code', operator, name), ('name', operator, name)] + else: + domain = ['|', ('code', operator, name), ('name', operator, name)] + ids = self.search(cr, user, expression.AND([domain, args]), limit=limit, context=context) return self.name_get(cr, user, ids, context=context) @@ -938,13 +933,11 @@ class account_fiscalyear(osv.osv): def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80): if args is None: args = [] - if context is None: - context = {} - ids = [] - if name: - ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit) - if not ids: - ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit) + if operator in expression.NEGATIVE_TERM_OPERATORS: + domain = [('code', operator, name), ('name', operator, name)] + else: + domain = ['|', ('code', operator, name), ('name', operator, name)] + ids = self.search(cr, user, expression.AND([domain, args]), limit=limit, context=context) return self.name_get(cr, user, ids, context=context) @@ -1040,19 +1033,11 @@ class account_period(osv.osv): def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100): if args is None: args = [] - if context is None: - context = {} - ids = [] - if name: - ids = self.search(cr, user, - [('code', 'ilike', name)] + args, - limit=limit, - context=context) - if not ids: - ids = self.search(cr, user, - [('name', operator, name)] + args, - limit=limit, - context=context) + if operator in expression.NEGATIVE_TERM_OPERATORS: + domain = [('code', operator, name), ('name', operator, name)] + else: + domain = ['|', ('code', operator, name), ('name', operator, name)] + ids = self.search(cr, user, expression.AND([domain, args]), limit=limit, context=context) return self.name_get(cr, user, ids, context=context) def write(self, cr, uid, ids, vals, context=None): @@ -1187,36 +1172,6 @@ class account_move(osv.osv): 'company_id': company_id, } - def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80): - """ - Returns a list of tupples containing id, name, as internally it is called {def name_get} - result format: {[(id, name), (id, name), ...]} - - @param cr: A database cursor - @param user: ID of the user currently logged in - @param name: name to search - @param args: other arguments - @param operator: default operator is 'ilike', it can be changed - @param context: context arguments, like lang, time zone - @param limit: Returns first 'n' ids of complete result, default is 80. - - @return: Returns a list of tuples containing id and name - """ - - if not args: - args = [] - ids = [] - if name: - ids += self.search(cr, user, [('name','ilike',name)]+args, limit=limit, context=context) - - if not ids and name and type(name) == int: - ids += self.search(cr, user, [('id','=',name)]+args, limit=limit, context=context) - - if not ids: - ids += self.search(cr, user, args, limit=limit, context=context) - - return self.name_get(cr, user, ids, context=context) - def name_get(self, cursor, user, ids, context=None): if isinstance(ids, (int, long)): ids = [ids] @@ -1842,10 +1797,12 @@ class account_tax_code(osv.osv): def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80): if not args: args = [] - if context is None: - context = {} - ids = self.search(cr, user, ['|',('name',operator,name),('code',operator,name)] + args, limit=limit, context=context) - return self.name_get(cr, user, ids, context) + if operator in expression.NEGATIVE_TERM_OPERATORS: + domain = [('code', operator, name), ('name', operator, name)] + else: + domain = ['|', ('code', operator, name), ('name', operator, name)] + ids = self.search(cr, user, expression.AND([domain, args]), limit=limit, context=context) + return self.name_get(cr, user, ids, context=context) def name_get(self, cr, uid, ids, context=None): if isinstance(ids, (int, long)): @@ -1974,15 +1931,11 @@ class account_tax(osv.osv): """ if not args: args = [] - if context is None: - context = {} - ids = [] - if name: - ids = self.search(cr, user, [('description', '=', name)] + args, limit=limit, context=context) - if not ids: - ids = self.search(cr, user, [('name', operator, name)] + args, limit=limit, context=context) + if operator in expression.NEGATIVE_TERM_OPERATORS: + domain = [('description', operator, name), ('name', operator, name)] else: - ids = self.search(cr, user, args, limit=limit, context=context or {}) + domain = ['|', ('description', operator, name), ('name', operator, name)] + ids = self.search(cr, user, expression.AND([domain, args]), limit=limit, context=context) return self.name_get(cr, user, ids, context=context) def write(self, cr, uid, ids, vals, context=None): diff --git a/addons/account/wizard/account_invoice_refund.py b/addons/account/wizard/account_invoice_refund.py index 8a583b79383..02b046edfcf 100644 --- a/addons/account/wizard/account_invoice_refund.py +++ b/addons/account/wizard/account_invoice_refund.py @@ -165,7 +165,7 @@ class account_invoice_refund(osv.osv_memory): to_reconcile_ids = {} for line in movelines: if line.account_id.id == inv.account_id.id: - to_reconcile_ids[line.account_id.id] = [line.id] + to_reconcile_ids.setdefault(line.account_id.id, []).append(line.id) if line.reconcile_id: line.reconcile_id.unlink() inv_obj.signal_invoice_open(cr, uid, [refund.id]) diff --git a/addons/account_budget/account_budget.py b/addons/account_budget/account_budget.py index e6fc3a29668..753a5df79f4 100644 --- a/addons/account_budget/account_budget.py +++ b/addons/account_budget/account_budget.py @@ -162,7 +162,7 @@ class crossovered_budget_lines(osv.osv): elapsed = strToDate(date_to) - strToDate(date_to) if total.days: - theo_amt = float(elapsed.days / float(total.days)) * line.planned_amount + theo_amt = float((elapsed.days + 1) / float(total.days + 1)) * line.planned_amount else: theo_amt = line.planned_amount diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py index add0030d76c..6dff8ee2e8b 100644 --- a/addons/calendar/calendar.py +++ b/addons/calendar/calendar.py @@ -197,6 +197,10 @@ class calendar_attendee(osv.Model): @param email_from: email address for user sending the mail """ res = False + + if self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.block_mail', default=False): + return res + mail_ids = [] data_pool = self.pool['ir.model.data'] mailmess_pool = self.pool['mail.message'] @@ -431,7 +435,7 @@ class calendar_alarm_manager(osv.AbstractModel): if cron and len(cron) == 1: cron = self.pool.get('ir.cron').browse(cr, uid, cron[0], context=context) else: - raise ("Cron for " + self._name + " not identified :( !") + _logger.exception("Cron for " + self._name + " can not be identified !") if cron.interval_type == "weeks": cron_interval = cron.interval_number * 7 * 24 * 60 * 60 @@ -445,7 +449,7 @@ class calendar_alarm_manager(osv.AbstractModel): cron_interval = cron.interval_number if not cron_interval: - raise ("Cron delay for " + self._name + " can not be calculated :( !") + _logger.exception("Cron delay can not be computed !") all_events = self.get_next_potential_limit_alarm(cr, uid, cron_interval, notif=False, context=context) @@ -649,7 +653,7 @@ class calendar_event(osv.Model): _inherit = ["mail.thread", "ir.needaction_mixin"] def do_run_scheduler(self, cr, uid, id, context=None): - self.pool['calendar.alarm_manager'].do_run_scheduler(cr, uid, context=context) + self.pool['calendar.alarm_manager'].get_next_mail(cr, uid, context=context) def get_recurrent_date_by_event(self, cr, uid, event, context=None): """Get recurrent dates based on Rule string and all event where recurrent_id is child diff --git a/addons/gamification/models/goal.py b/addons/gamification/models/goal.py index 6ab4259cca2..af9a763236d 100644 --- a/addons/gamification/models/goal.py +++ b/addons/gamification/models/goal.py @@ -287,30 +287,33 @@ class gamification_goal(osv.Model): field_date_name = definition.field_date_id and definition.field_date_id.name or False if definition.computation_mode == 'count' and definition.batch_mode: - + # batch mode, trying to do as much as possible in one request general_domain = safe_eval(definition.domain) - # goal_distinct_values = {goal.id: safe_eval(definition.batch_user_expression, {'user': goal.user_id}) for goal in goals} field_name = definition.batch_distinctive_field.name - # general_domain.append((field_name, 'in', list(set(goal_distinct_values.keys())))) subqueries = {} for goal in goals: start_date = field_date_name and goal.start_date or False end_date = field_date_name and goal.end_date or False subqueries.setdefault((start_date, end_date), {}).update({goal.id:safe_eval(definition.batch_user_expression, {'user': goal.user_id})}) + # the global query should be split by time periods (especially for recurrent goals) for (start_date, end_date), query_goals in subqueries.items(): subquery_domain = list(general_domain) subquery_domain.append((field_name, 'in', list(set(query_goals.values())))) if start_date: subquery_domain.append((field_date_name, '>=', start_date)) if end_date: - subquery_domain.append((field_date_name, '>=', end_date)) - - user_values = obj.read_group(cr, uid, subquery_domain, fields=[field_name], groupby=[field_name], context=context) + subquery_domain.append((field_date_name, '<=', end_date)) + if field_name == 'id': + # grouping on id does not work and is similar to search anyway + user_ids = obj.search(cr, uid, subquery_domain, context=context) + user_values = [{'id': user_id, 'id_count': 1} for user_id in user_ids] + else: + user_values = obj.read_group(cr, uid, subquery_domain, fields=[field_name], groupby=[field_name], context=context) + # user_values has format of read_group: [{'partner_id': 42, 'partner_id_count': 3},...] for goal in [g for g in goals if g.id in query_goals.keys()]: for user_value in user_values: - # return format of read_group: [{'partner_id': 42, 'partner_id_count': 3},...] queried_value = field_name in user_value and user_value[field_name] or False if isinstance(queried_value, tuple) and len(queried_value) == 2 and isinstance(queried_value[0], (int, long)): queried_value = queried_value[0] diff --git a/addons/gamification/models/res_users.py b/addons/gamification/models/res_users.py index 85cc18636f0..faca484dfb3 100644 --- a/addons/gamification/models/res_users.py +++ b/addons/gamification/models/res_users.py @@ -61,13 +61,12 @@ class res_users_gamification_group(osv.Model): challenge_obj.generate_goals_from_challenge(cr, SUPERUSER_ID, challenge_ids, context=context) return write_res - # def get_goals_todo_info(self, cr, uid, context=None): + def get_serialised_gamification_summary(self, cr, uid, excluded_categories=None, context=None): + return self._serialised_goals_summary(cr, uid, user_id=uid, excluded_categories=excluded_categories, context=context) - def get_serialised_gamification_summary(self, cr, uid, context=None): - return self._serialised_goals_summary(cr, uid, user_id=uid, context=context) - - def _serialised_goals_summary(self, cr, uid, user_id, context=None): + def _serialised_goals_summary(self, cr, uid, user_id, excluded_categories=None, context=None): """Return a serialised list of goals assigned to the user, grouped by challenge + :excluded_categories: list of challenge categories to exclude in search [ { @@ -81,9 +80,11 @@ class res_users_gamification_group(osv.Model): """ all_goals_info = [] challenge_obj = self.pool.get('gamification.challenge') - + domain = [('user_ids', 'in', uid), ('state', '=', 'inprogress')] + if excluded_categories and isinstance(excluded_categories, list): + domain.append(('category', 'not in', excluded_categories)) user = self.browse(cr, uid, uid, context=context) - challenge_ids = challenge_obj.search(cr, uid, [('user_ids', 'in', uid), ('state', '=', 'inprogress')], context=context) + challenge_ids = challenge_obj.search(cr, uid, domain, context=context) for challenge in challenge_obj.browse(cr, uid, challenge_ids, context=context): # serialize goals info to be able to use it in javascript lines = challenge_obj._get_serialized_challenge_lines(cr, uid, challenge, user_id, restrict_top=MAX_VISIBILITY_RANKING, context=context) diff --git a/addons/gamification/views/challenge.xml b/addons/gamification/views/challenge.xml index b37ef0eb0d8..f177a55ac5d 100644 --- a/addons/gamification/views/challenge.xml +++ b/addons/gamification/views/challenge.xml @@ -81,12 +81,12 @@ - + - +

Badges are granted when a challenge is finished. This is either at the end of a running period (eg: end of the month for a monthly challenge), at the end date of a challenge (if no periodicity is set) or when the challenge is manually closed.

diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 73588207d6a..812ff91be90 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -31,6 +31,7 @@ except ImportError: from lxml import etree import logging import pytz +import socket import time import xmlrpclib from email.message import Message @@ -880,25 +881,30 @@ class mail_thread(osv.AbstractModel): # 2. message is a reply to an existign thread (6.1 compatibility) ref_match = thread_references and tools.reference_re.search(thread_references) if ref_match: - thread_id = int(ref_match.group(1)) - model = ref_match.group(2) or fallback_model - if thread_id and model in self.pool: - model_obj = self.pool[model] - compat_mail_msg_ids = mail_msg_obj.search( - cr, uid, [ - ('message_id', '=', False), - ('model', '=', model), - ('res_id', '=', thread_id), - ], context=context) - if compat_mail_msg_ids and model_obj.exists(cr, uid, thread_id) and hasattr(model_obj, 'message_update'): - _logger.info( - 'Routing mail from %s to %s with Message-Id %s: direct thread reply (compat-mode) to model: %s, thread_id: %s, custom_values: %s, uid: %s', - email_from, email_to, message_id, model, thread_id, custom_values, uid) - route = self.message_route_verify( - cr, uid, message, message_dict, - (model, thread_id, custom_values, uid, None), - update_author=True, assert_model=True, create_fallback=True, context=context) - return route and [route] or [] + reply_thread_id = int(ref_match.group(1)) + reply_model = ref_match.group(2) or fallback_model + reply_hostname = ref_match.group(3) + local_hostname = socket.gethostname() + # do not match forwarded emails from another OpenERP system (thread_id collision!) + if local_hostname == reply_hostname: + thread_id, model = reply_thread_id, reply_model + if thread_id and model in self.pool: + model_obj = self.pool[model] + compat_mail_msg_ids = mail_msg_obj.search( + cr, uid, [ + ('message_id', '=', False), + ('model', '=', model), + ('res_id', '=', thread_id), + ], context=context) + if compat_mail_msg_ids and model_obj.exists(cr, uid, thread_id) and hasattr(model_obj, 'message_update'): + _logger.info( + 'Routing mail from %s to %s with Message-Id %s: direct thread reply (compat-mode) to model: %s, thread_id: %s, custom_values: %s, uid: %s', + email_from, email_to, message_id, model, thread_id, custom_values, uid) + route = self.message_route_verify( + cr, uid, message, message_dict, + (model, thread_id, custom_values, uid, None), + update_author=True, assert_model=True, create_fallback=True, context=context) + return route and [route] or [] # 2. Reply to a private message if in_reply_to: diff --git a/addons/mail/tests/test_mail_gateway.py b/addons/mail/tests/test_mail_gateway.py index cb735ec098e..3fbf3d40405 100644 --- a/addons/mail/tests/test_mail_gateway.py +++ b/addons/mail/tests/test_mail_gateway.py @@ -21,6 +21,7 @@ from openerp.addons.mail.tests.common import TestMail from openerp.tools import mute_logger +import socket MAIL_TEMPLATE = """Return-Path: To: {to} @@ -400,13 +401,15 @@ class TestMailgateway(TestMail): to='noone@example.com', subject='spam', extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>' % frog_group.id, msg_id='<1.1.JavaMail.new@agrolait.com>') - # There are 6.1 messages, activate compat mode + + # When 6.1 messages are present, compat mode is available + # Create a fake 6.1 message tmp_msg_id = self.mail_message.create(cr, uid, {'message_id': False, 'model': 'mail.group', 'res_id': frog_group.id}) # Do: compat mode accepts partial-matching emails frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other5@gmail.com', msg_id='<1.2.JavaMail.new@agrolait.com>', to='noone@example.com>', subject='spam', - extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>' % frog_group.id) + extra='In-Reply-To: <12321321-openerp-%d-mail.group@%s>' % (frog_group.id, socket.gethostname())) self.mail_message.unlink(cr, uid, [tmp_msg_id]) # Test: no group 'Re: news' created, still only 1 Frogs group self.assertEqual(len(frog_groups), 0, @@ -418,6 +421,17 @@ class TestMailgateway(TestMail): # Test: one new message self.assertEqual(len(frog_group.message_ids), 4, 'message_process: group should contain 4 messages after reply') + # 6.1 compat mode should not work if hostname does not match! + tmp_msg_id = self.mail_message.create(cr, uid, {'message_id': False, 'model': 'mail.group', 'res_id': frog_group.id}) + self.assertRaises(ValueError, + format_and_process, + MAIL_TEMPLATE, email_from='other5@gmail.com', + msg_id='<1.3.JavaMail.new@agrolait.com>', + to='noone@example.com>', subject='spam', + extra='In-Reply-To: <12321321-openerp-%d-mail.group@neighbor.com>' % frog_group.id) + self.mail_message.unlink(cr, uid, [tmp_msg_id]) + + # Do: due to some issue, same email goes back into the mailgateway frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com', msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>', @@ -445,7 +459,7 @@ class TestMailgateway(TestMail): # Do: post a new message, with a known partner -> duplicate emails -> partner format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik ', - to='erroneous@example.com>', subject='Re: news (2)', + subject='Re: news (2)', msg_id='<1198923581.41972151344608186760.JavaMail.new1@agrolait.com>', extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>') frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')]) @@ -456,10 +470,9 @@ class TestMailgateway(TestMail): # Do: post a new message, with a known partner -> duplicate emails -> user frog_group.message_unsubscribe([extra_partner_id]) - raoul_email = self.user_raoul.email self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'}) format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik ', - to='erroneous@example.com>', subject='Re: news (3)', + to='groups@example.com', subject='Re: news (3)', msg_id='<1198923581.41972151344608186760.JavaMail.new2@agrolait.com>', extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>') frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')]) @@ -474,7 +487,7 @@ class TestMailgateway(TestMail): raoul_email = self.user_raoul.email self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'}) format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik ', - to='erroneous@example.com>', subject='Re: news (3)', + to='groups@example.com', subject='Re: news (3)', msg_id='<1198923581.41972151344608186760.JavaMail.new3@agrolait.com>', extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>') frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')]) diff --git a/addons/mass_mailing/mail_thread.py b/addons/mass_mailing/mail_thread.py index a4ff15a1a3f..524341f88d7 100644 --- a/addons/mass_mailing/mail_thread.py +++ b/addons/mass_mailing/mail_thread.py @@ -29,7 +29,7 @@ from openerp.osv import osv _logger = logging.getLogger(__name__) -class MailThread(osv.Model): +class MailThread(osv.AbstractModel): """ Update MailThread to add the feature of bounced emails and replied emails in message_process. """ _name = 'mail.thread' diff --git a/addons/product_email_template/views/email_template_view.xml b/addons/product_email_template/views/email_template_view.xml index 5b4600b9719..c1312d31132 100644 --- a/addons/product_email_template/views/email_template_view.xml +++ b/addons/product_email_template/views/email_template_view.xml @@ -12,8 +12,7 @@

Body

- + diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index ed131670737..c27cbad8d86 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -133,10 +133,7 @@ class purchase_order(osv.osv): def _invoiced(self, cursor, user, ids, name, arg, context=None): res = {} for purchase in self.browse(cursor, user, ids, context=context): - invoiced = False - if purchase.invoiced_rate == 100.00: - invoiced = True - res[purchase.id] = invoiced + res[purchase.id] = all(line.invoiced for line in purchase.order_line) return res def _get_journal(self, cr, uid, context=None): @@ -542,7 +539,7 @@ class purchase_order(osv.osv): inv_line_id = inv_line_obj.create(cr, uid, inv_line_data, context=context) inv_lines.append(inv_line_id) - po_line.write({'invoiced': True, 'invoice_lines': [(4, inv_line_id)]}, context=context) + po_line.write({'invoice_lines': [(4, inv_line_id)]}, context=context) # get invoice data and create invoice inv_data = { @@ -1290,9 +1287,15 @@ class account_invoice(osv.Model): else: user_id = uid po_ids = purchase_order_obj.search(cr, user_id, [('invoice_ids', 'in', ids)], context=context) - for po_id in po_ids: - purchase_order_obj.message_post(cr, user_id, po_id, body=_("Invoice received"), context=context) - workflow.trg_write(uid, 'purchase.order', po_id, cr) + for order in purchase_order_obj.browse(cr, uid, po_ids, context=context): + purchase_order_obj.message_post(cr, user_id, order.id, body=_("Invoice received"), context=context) + invoiced = [] + for po_line in order.order_line: + if any(line.invoice_id.state not in ['draft', 'cancel'] for line in po_line.invoice_lines): + invoiced.append(po_line.id) + if invoiced: + self.pool['purchase.order.line'].write(cr, uid, invoiced, {'invoiced': True}) + workflow.trg_write(uid, 'purchase.order', order.id, cr) return res def confirm_paid(self, cr, uid, ids, context=None): diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py index 7671c4b3d86..2d5dea89d35 100644 --- a/addons/purchase/stock.py +++ b/addons/purchase/stock.py @@ -110,7 +110,6 @@ class stock_picking(osv.osv): invoice_line_obj = self.pool.get('account.invoice.line') purchase_line_obj = self.pool.get('purchase.order.line') purchase_line_obj.write(cursor, user, [move_line.purchase_line_id.id], { - 'invoiced': True, 'invoice_lines': [(4, invoice_line_id)], }) return super(stock_picking, self)._invoice_line_hook(cursor, user, move_line, invoice_line_id) diff --git a/addons/website/models/ir_qweb.py b/addons/website/models/ir_qweb.py index 8916968f7ca..ad2b63a6dd9 100644 --- a/addons/website/models/ir_qweb.py +++ b/addons/website/models/ir_qweb.py @@ -383,6 +383,10 @@ class Contact(orm.AbstractModel): _name = 'website.qweb.field.contact' _inherit = ['ir.qweb.field.contact', 'website.qweb.field.many2one'] +class QwebView(orm.AbstractModel): + _name = 'website.qweb.field.qweb' + _inherit = ['ir.qweb.field.qweb'] + def html_to_text(element): """ Converts HTML content with HTML-specified line breaks (br, p, div, ...) diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py index c9fadade6ed..f67f6ada4b9 100644 --- a/addons/website/models/ir_ui_view.py +++ b/addons/website/models/ir_ui_view.py @@ -128,8 +128,6 @@ class view(osv.osv): if isinstance(id_or_xml_id, list): id_or_xml_id = id_or_xml_id[0] - if isinstance(id_or_xml_id, (int, long)): - id_or_xml_id = self.get_view_xmlid(cr, uid, id_or_xml_id) if not context: context = {} diff --git a/addons/website/models/website.py b/addons/website/models/website.py index a0a6d1fb28b..51dda75f516 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -221,10 +221,13 @@ class website(osv.osv): ) def get_template(self, cr, uid, ids, template, context=None): - if '.' not in template: - template = 'website.%s' % template - module, xmlid = template.split('.', 1) - model, view_id = request.registry["ir.model.data"].get_object_reference(cr, uid, module, xmlid) + if isinstance(template, (int, long)): + view_id = template + else: + if '.' not in template: + template = 'website.%s' % template + module, xmlid = template.split('.', 1) + model, view_id = request.registry["ir.model.data"].get_object_reference(cr, uid, module, xmlid) return self.pool["ir.ui.view"].browse(cr, uid, view_id, context=context) def _render(self, cr, uid, ids, template, values=None, context=None): diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js index e87de996a05..f59f2cf07ca 100644 --- a/addons/website/static/src/js/website.snippets.editor.js +++ b/addons/website/static/src/js/website.snippets.editor.js @@ -906,6 +906,7 @@ var self = this; var bg = self.$target.css("background-image"); this.$el.find('li').removeClass("active"); + this.$el.find('li').removeClass("btn-primary"); var $active = this.$el.find('li[data-value]') .filter(function () { var $li = $(this); @@ -918,8 +919,13 @@ this.$el.find('li[data-value].oe_custom_bg') : this.$el.find('li[data-value=""]'); } - $active.addClass("active"); - this.$el.find('li:has(li[data-value].active)').addClass("active"); + + //don't set active on an OpenDialog link, else it not possible to click on it again after. + // TODO in Saas-4 - Once bootstrap is in less + // - add a class active-style to get the same display but without the active behaviour used by bootstrap in JS. + var classStr = _.string.contains($active[0].className, "oe_custom_bg") ? "btn-primary" : "active"; + $active.addClass(classStr); + this.$el.find('li:has(li[data-value].active)').addClass(classStr); } }); diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index 5ea48d92a31..324ae70f9e6 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -43,6 +43,7 @@ t-att-data-view-xmlid="xmlid if editable else None" t-att-data-main-object="repr(main_object) if editable else None"> + @@ -74,6 +75,7 @@ // Bootstrap and jQuery UI conflicts $.fn.bstooltip = $.fn.tooltip; $.fn.bsbutton = $.fn.button; + diff --git a/addons/website_forum/controllers/main.py b/addons/website_forum/controllers/main.py index 90cbf0ba722..354aa52c8b3 100644 --- a/addons/website_forum/controllers/main.py +++ b/addons/website_forum/controllers/main.py @@ -348,7 +348,7 @@ class WebsiteForum(http.Controller): return {'error': 'own_post'} user = request.registry['res.users'].browse(request.cr, SUPERUSER_ID, request.uid, context=request.context) if user.karma <= 5: - return {'error': 'not_enough_karma', 'karma': 5} + return {'error': 'not_enough_karma', 'karma': 1} return request.registry['forum.post'].vote(request.cr, request.uid, [post.id], upvote=True, context=request.context) @http.route('/forum//post//downvote', type='json', auth="public", multilang=True, website=True) diff --git a/addons/website_forum/data/badges_answer.xml b/addons/website_forum/data/badges_answer.xml index 8b56a5ac02e..9aed157df21 100644 --- a/addons/website_forum/data/badges_answer.xml +++ b/addons/website_forum/data/badges_answer.xml @@ -4,7 +4,7 @@ - + - + - + - + - + - + - + \ No newline at end of file diff --git a/addons/website_forum/data/badges_moderation.xml b/addons/website_forum/data/badges_moderation.xml index c169e5bc668..0a6f2d9925d 100644 --- a/addons/website_forum/data/badges_moderation.xml +++ b/addons/website_forum/data/badges_moderation.xml @@ -3,38 +3,15 @@ + - + - + - + - - - - - - - - - - - - - - Supporter First upvote gold - --> - - + - - + \ No newline at end of file diff --git a/addons/website_forum/data/badges_participation.xml b/addons/website_forum/data/badges_participation.xml index 9d0c2139abe..04c2ab7e4d9 100644 --- a/addons/website_forum/data/badges_participation.xml +++ b/addons/website_forum/data/badges_participation.xml @@ -3,7 +3,7 @@ - + - + - + - + - + 1 + \ No newline at end of file diff --git a/addons/website_forum/data/badges_question.xml b/addons/website_forum/data/badges_question.xml index 02e9a9f306e..fd9c56636ff 100644 --- a/addons/website_forum/data/badges_question.xml +++ b/addons/website_forum/data/badges_question.xml @@ -4,19 +4,22 @@ - + + - + - + - + - + - + - + - + - + - + - + \ No newline at end of file diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py index 74f4d9e5393..031ae4b33e1 100644 --- a/addons/website_forum/models/forum.py +++ b/addons/website_forum/models/forum.py @@ -97,6 +97,12 @@ class Post(osv.Model): res[post.id] = any(answer.create_uid.id == uid for answer in post.child_ids) return res + def _is_self_reply(self, cr, uid, ids, field_name, arg, context=None): + res = dict.fromkeys(ids, False) + for post in self.browse(cr, uid, ids, context=context): + res[post.id] = post.parent_id and post.parent_id.create_uid == post.create_uid or False + return res + _columns = { 'name': fields.char('Title', size=128), 'forum_id': fields.many2one('forum.forum', 'Forum', required=True), @@ -137,6 +143,10 @@ class Post(osv.Model): }), # hierarchy 'parent_id': fields.many2one('forum.post', 'Question', ondelete='cascade'), + 'self_reply': fields.function(_is_self_reply, 'Reply to own question', type='boolean', + store={ + 'forum.post': (lambda self, cr, uid, ids, c={}: ids, ['parent_id', 'create_uid'], 10), + }), 'child_ids': fields.one2many('forum.post', 'parent_id', 'Answers'), 'child_count': fields.function( _get_child_count, string="Answers", type='integer', @@ -166,15 +176,14 @@ class Post(osv.Model): context = {} create_context = dict(context, mail_create_nolog=True) post_id = super(Post, self).create(cr, uid, vals, context=create_context) - post = self.browse(cr, uid, post_id, context=context) # post message + subtype depending on parent_id if vals.get("parent_id"): parent = self.browse(cr, SUPERUSER_ID, vals['parent_id'], context=context) body = _('

New Answer Posted

' % (slug(parent.forum_id), slug(parent))) self.message_post(cr, uid, parent.id, subject=_('Re: %s') % parent.name, body=body, subtype='website_forum.mt_answer_new', context=context) else: - self.message_post(cr, uid, post.id, subject=post.name, body=_('New Question Created'), subtype='website_forum.mt_question_new', context=context) - self.pool['res.users'].write(cr, SUPERUSER_ID, [post.create_uid.id], {'karma': 2}, context=context) + self.message_post(cr, uid, post_id, subject=vals.get('name', ''), body=_('New Question Created'), subtype='website_forum.mt_question_new', context=context) + self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [uid], 2, context=context) return post_id def write(self, cr, uid, ids, vals, context=None): @@ -193,7 +202,7 @@ class Post(osv.Model): if 'correct' in vals: for post in self.browse(cr, uid, ids, context=context): karma_value = 15 if vals.get('correct') else -15 - self.pool['res.users'].write(cr, SUPERUSER_ID, [post.create_uid.id], {'karma': karma_value}, context=context) + self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id], {'karma': karma_value}, context=context) return res def vote(self, cr, uid, ids, upvote=True, context=None): @@ -231,7 +240,7 @@ class Vote(osv.Model): _description = 'Vote' _columns = { 'post_id': fields.many2one('forum.post', 'Post', ondelete='cascade', required=True), - 'user_id': fields.many2one('res.users', 'User'), + 'user_id': fields.many2one('res.users', 'User', required=True), 'vote': fields.selection([('1', '1'), ('-1', '-1'), ('0', '0')], 'Vote', required=True), 'create_date': fields.datetime('Create Date', select=True, readonly=True), } @@ -240,23 +249,19 @@ class Vote(osv.Model): 'vote': lambda *args: '1', } - def update_karma(self, cr, uid, ids, new_vote='0', old_vote='0', context=None): - karma_value = (int(new_vote) - int(old_vote)) * 10 - if karma_value: - for vote in self.browse(cr, uid, ids, context=context): - self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [vote.post_id.create_uid.id], karma_value, context=context) - return True - def create(self, cr, uid, vals, context=None): vote_id = super(Vote, self).create(cr, uid, vals, context=context) - self.update_karma(cr, uid, [vote_id], new_vote=vals.get('vote', '1'), context=context) + karma_value = int(vals.get('vote', '1')) * 10 + post = self.pool['forum.post'].browse(cr, uid, vals.get('post_id'), context=context) + self.pool['res.users'].add_karma(cr, SUPERUSER_ID, post.create_uid.id, karma_value, context=context) return vote_id def write(self, cr, uid, ids, values, context=None): - res = super(Vote, self).write(cr, uid, ids, values, context=context) if 'vote' in values: for vote in self.browse(cr, uid, ids, context=context): - self.update_karma(cr, uid, ids, new_vote=values['vote'], old_vote=vote.vote, context=context) + karma_value = (int(values.get('vote')) - int(vote.vote)) * 10 + self.pool['res.users'].add_karma(cr, SUPERUSER_ID, vote.post_id.create_uid.id, karma_value, context=context) + res = super(Vote, self).write(cr, uid, ids, values, context=context) return res @@ -283,4 +288,5 @@ class Tags(osv.Model): 'forum.post': (_get_tag_from_post, ['tag_ids'], 10), } ), + 'create_uid': fields.many2one('res.users', 'Created by', readonly=True), } diff --git a/addons/website_forum/models/res_users.py b/addons/website_forum/models/res_users.py index 89a23cc2901..58d43a58191 100644 --- a/addons/website_forum/models/res_users.py +++ b/addons/website_forum/models/res_users.py @@ -32,6 +32,16 @@ class Users(osv.Model): } def add_karma(self, cr, uid, ids, karma, context=None): + if isinstance(ids, (int, long)): + ids = [ids] for user in self.browse(cr, uid, ids, context=context): self.write(cr, uid, [user.id], {'karma': user.karma + karma}, context=context) return True + + def get_serialised_gamification_summary(self, cr, uid, excluded_categories=None, context=None): + if isinstance(excluded_categories, list): + if 'forum' not in excluded_categories: + excluded_categories.append('forum') + else: + excluded_categories = ['forum'] + return super(Users, self).get_serialised_gamification_summary(cr, uid, excluded_categories=excluded_categories, context=context) \ No newline at end of file diff --git a/addons/website_quote/controllers/main.py b/addons/website_quote/controllers/main.py index f9010c2b7b2..03ba273ea31 100644 --- a/addons/website_quote/controllers/main.py +++ b/addons/website_quote/controllers/main.py @@ -129,7 +129,7 @@ class sale_quote(http.Controller): order_line_obj.write(request.cr, SUPERUSER_ID, [line_id], {'product_uom_qty': (quantity)}, context=request.context) return [str(quantity), str(order.amount_total)] - @http.route(["/quote/template/"], type='http', auth="user", website=True) + @http.route(["/quote/template/"], type='http', auth="user", website=True, multilang=True) def template_view(self, quote, **post): values = { 'template': quote } return request.website.render('website_quote.so_template', values) diff --git a/addons/website_quote/models/order.py b/addons/website_quote/models/order.py index 3614fd63919..c031baf7632 100644 --- a/addons/website_quote/models/order.py +++ b/addons/website_quote/models/order.py @@ -133,7 +133,12 @@ class sale_order(osv.osv): def onchange_template_id(self, cr, uid, ids, template_id, partner=False, fiscal_position=False, context=None): if not template_id: return True - lines = [] + + if context is None: + context = {} + context = dict(context, lang=self.pool.get('res.partner').browse(cr, uid, partner, context).lang) + + lines = [(5,)] quote_template = self.pool.get('sale.quote.template').browse(cr, uid, template_id, context=context) for line in quote_template.quote_line: res = self.pool.get('sale.order.line').product_id_change(cr, uid, False, diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index 374f17dcf06..e0a4be47780 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -411,8 +411,10 @@ class Ecommerce(http.Controller): quantity = request.registry['website']._ecommerce_add_product_to_cart(request.cr, request.uid, product_id=product_id, order_line_id=order_line_id, set_number=set_number, context=request.context) - return quantity - + order = self.get_order() + return [quantity, + order.get_number_of_products()] + @http.route(['/shop/checkout'], type='http', auth="public", website=True, multilang=True) def checkout(self, **post): cr, uid, context, registry = request.cr, request.uid, request.context, request.registry @@ -514,14 +516,7 @@ class Ecommerce(http.Controller): if error: return request.website.render("website_sale.checkout", values) - company_name = checkout['company'] - company_id = None - if post['company']: - company_ids = orm_partner.search(cr, SUPERUSER_ID, [("name", "ilike", company_name), ('is_company', '=', True)], context=context) - company_id = (company_ids and company_ids[0]) or orm_partner.create(cr, SUPERUSER_ID, {'name': company_name, 'is_company': True}, context) - billing_info = dict((k, v) for k,v in checkout.items() if "shipping_" not in k and k != "company") - billing_info['parent_id'] = company_id partner_id = None public_id = request.registry['website'].get_public_user(cr, uid, context) @@ -542,7 +537,8 @@ class Ecommerce(http.Controller): shipping_info = { 'phone': post['shipping_phone'], 'zip': post['shipping_zip'], - 'street': post['shipping_street'], + 'street': checkout['company'], + 'street2': post['shipping_street'], 'city': post['shipping_city'], 'name': post['shipping_name'], 'email': post['email'],