[MERGE] forward port of branch saas-4 up to 5739aebfb1

This commit is contained in:
Denis Ledoux 2014-07-23 13:16:59 +02:00
commit a5f7891b68
22 changed files with 126 additions and 56 deletions

View File

@ -1043,6 +1043,8 @@ class account_move_line(osv.osv):
all_moves = list(set(all_moves) - set(move_ids))
if unlink_ids:
if opening_reconciliation:
raise osv.except_osv(_('Warning!'),
_('Opening Entries have already been generated. Please run "Cancel Closing Entries" wizard to cancel those entries and then run this wizard.'))
obj_move_rec.write(cr, uid, unlink_ids, {'opening_reconciliation': False})
obj_move_rec.unlink(cr, uid, unlink_ids)
if len(all_moves) >= 2:

View File

@ -141,6 +141,7 @@ class crm_phonecall(osv.osv):
'partner_phone' : call.partner_phone,
'partner_mobile' : call.partner_mobile,
'priority': call.priority,
'opportunity_id': call.opportunity_id and call.opportunity_id.id or False,
}
new_id = self.create(cr, uid, vals, context=context)
if action == 'log':

View File

@ -385,7 +385,7 @@ class hr_payslip(osv.osv):
#OR if it starts between the given dates
clause_2 = ['&',('date_start', '<=', date_to),('date_start','>=', date_from)]
#OR if it starts before the date_from and finish after the date_end (or never finish)
clause_3 = [('date_start','<=', date_from),'|',('date_end', '=', False),('date_end','>=', date_to)]
clause_3 = ['&',('date_start','<=', date_from),'|',('date_end', '=', False),('date_end','>=', date_to)]
clause_final = [('employee_id', '=', employee.id),'|','|'] + clause_1 + clause_2 + clause_3
contract_ids = contract_obj.search(cr, uid, clause_final, context=context)
return contract_ids

View File

@ -131,10 +131,11 @@ class mail_notification(osv.Model):
company = "<a style='color:inherit' href='%s'>%s</a>" % (website_url, user.company_id.name)
else:
company = user.company_id.name
sent_by = _('Sent from %(company)s using %(openerp)s')
sent_by = _('Sent by %(company)s using %(odoo)s.')
signature_company = '<small>%s</small>' % (sent_by % {
'company': company,
'openerp': "<a style='color:inherit' href='https://www.openerp.com/'>OpenERP</a>"
'odoo': "<a style='color:inherit' href='https://www.odoo.com/'>Odoo</a>"
})
footer = tools.append_content_to_html(footer, signature_company, plaintext=False, container_tag='div')
@ -167,8 +168,9 @@ class mail_notification(osv.Model):
# compute email body (signature, company data)
body_html = message.body
user_id = message.author_id and message.author_id.user_ids and message.author_id.user_ids[0] and message.author_id.user_ids[0].id or None
if user_signature:
# add user signature except for mail groups, where users are usually adding their own signatures already
if user_signature and message.model != 'mail.group':
user_id = message.author_id and message.author_id.user_ids and message.author_id.user_ids[0] and message.author_id.user_ids[0].id or None
signature_company = self.get_signature_footer(cr, uid, user_id, res_model=message.model, res_id=message.res_id, context=context)
body_html = tools.append_content_to_html(body_html, signature_company, plaintext=False, container_tag='div')

View File

@ -221,12 +221,16 @@ class mail_group(osv.Model):
def message_get_email_values(self, cr, uid, id, notif_mail=None, context=None):
res = super(mail_group, self).message_get_email_values(cr, uid, id, notif_mail=notif_mail, context=context)
group = self.browse(cr, uid, id, context=context)
res.update({
'headers': {
'Precedence': 'list',
}
})
headers = res.setdefault('headers', {})
headers['Precedence'] = 'list'
# avoid out-of-office replies from MS Exchange
# http://blogs.technet.com/b/exchange/archive/2006/10/06/3395024.aspx
headers['X-Auto-Response-Suppress'] = 'OOF'
if group.alias_domain:
res['headers']['List-Id'] = '%s.%s' % (group.alias_name, group.alias_domain)
res['headers']['List-Post'] = '<mailto:%s@%s>' % (group.alias_name, group.alias_domain)
headers['List-Id'] = '%s.%s' % (group.alias_name, group.alias_domain)
headers['List-Post'] = '<mailto:%s@%s>' % (group.alias_name, group.alias_domain)
# Avoid users thinking it was a personal message
# X-Forge-To: will replace To: after SMTP envelope is determined by ir.mail.server
list_to = '"%s" <%s@%s>' % (group.name, group.alias_name, group.alias_domain)
headers['X-Forge-To'] = list_to
return res

View File

@ -468,14 +468,10 @@ class test_mail(TestMail):
'message_post: notification email subject incorrect')
self.assertIn(_body1, sent_email['body'],
'message_post: notification email body incorrect')
self.assertIn(user_raoul.signature, sent_email['body'],
'message_post: notification email body should contain the sender signature')
self.assertIn('Pigs rules', sent_email['body_alternative'],
'message_post: notification email body alternative should contain the body')
self.assertNotIn('<p>', sent_email['body_alternative'],
'message_post: notification email body alternative still contains html')
self.assertIn(user_raoul.signature, sent_email['body_alternative'],
'message_post: notification email body alternative should contain the sender signature')
self.assertFalse(sent_email['references'],
'message_post: references should be False when sending a message that is not a reply')
@ -539,14 +535,10 @@ class test_mail(TestMail):
'message_post: notification email subject incorrect')
self.assertIn(html_sanitize(_body2), sent_email['body'],
'message_post: notification email does not contain the body')
self.assertIn(user_raoul.signature, sent_email['body'],
'message_post: notification email body should contain the sender signature')
self.assertIn('Pigs rocks', sent_email['body_alternative'],
'message_post: notification email body alternative should contain the body')
self.assertNotIn('<p>', sent_email['body_alternative'],
'message_post: notification email body alternative still contains html')
self.assertIn(user_raoul.signature, sent_email['body_alternative'],
'message_post: notification email body alternative should contain the sender signature')
self.assertIn(msg_message_id, sent_email['references'],
'message_post: notification email references lacks parent message message_id')
# Test: attachments + download

View File

@ -19,10 +19,14 @@
#
##############################################################################
import logging
import threading
from openerp import pooler, SUPERUSER_ID, tools
from openerp.osv import osv
_logger = logging.getLogger(__name__)
class procurement_compute_all(osv.osv_memory):
_name = 'procurement.order.compute.all'
_description = 'Compute all schedulers'
@ -38,6 +42,16 @@ class procurement_compute_all(osv.osv_memory):
proc_obj = self.pool.get('procurement.order')
#As this function is in a new thread, i need to open a new cursor, because the old one may be closed
new_cr = self.pool.cursor()
scheduler_cron_id = self.pool['ir.model.data'].get_object_reference(new_cr, SUPERUSER_ID, 'procurement', 'ir_cron_scheduler_action')[1]
# Avoid to run the scheduler multiple times in the same time
try:
with tools.mute_logger('openerp.sql_db'):
new_cr.execute("SELECT id FROM ir_cron WHERE id = %s FOR UPDATE NOWAIT", (scheduler_cron_id,))
except Exception:
_logger.info('Attempt to run procurement scheduler aborted, as already running')
new_cr.rollback()
new_cr.close()
return {}
proc_obj.run_scheduler(new_cr, uid, use_new_cursor=new_cr.dbname, context=context)
#close the new cursor
new_cr.close()

View File

@ -389,6 +389,7 @@ class product_attribute_price(osv.osv):
class product_attribute_line(osv.osv):
_name = "product.attribute.line"
_rec_name = 'attribute_id'
_columns = {
'product_tmpl_id': fields.many2one('product.template', 'Product Template', required=True),
'attribute_id': fields.many2one('product.attribute', 'Attribute', required=True),

View File

@ -385,7 +385,7 @@
</h1>
<group>
<group>
<field name="project_id" domain="[('state', '!=', 'close')]" on_change="onchange_project(project_id)" context="{'default_use_tasks':1}"/>
<field name="project_id" domain="[('state','not in', ('close', 'cancelled'))]" on_change="onchange_project(project_id)" context="{'default_use_tasks':1}"/>
<field name="user_id"
options='{"no_open": True}'
context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'project.group_project_user']}"/>

View File

@ -634,7 +634,7 @@ class sale_order(osv.osv):
compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
except ValueError:
compose_form_id = False
ctx = dict(context)
ctx = dict()
ctx.update({
'default_model': 'sale.order',
'default_res_id': ids[0],

View File

@ -37,6 +37,7 @@ class sale_advance_payment_inv(osv.osv_memory):
Use Some Order Lines to invoice a selection of the sales order lines."""),
'qtty': fields.float('Quantity', digits=(16, 2), required=True),
'product_id': fields.many2one('product.product', 'Advance Product',
domain=[('type', '=', 'service')],
help="""Select a product of type service which is called 'Advance Product'.
You may have to create it and set it as a default value on this field."""),
'amount': fields.float('Advance Amount', digits_compute= dp.get_precision('Account'),

View File

@ -26,7 +26,11 @@ class contactus(http.Controller):
values.update(kwargs=kwargs.items())
return request.website.render("website.contactus", values)
@http.route(['/crm/contactus'], type='http', auth="public", website=True)
def create_lead(self, request, values):
""" Allow to be overrided """
return request.registry['crm.lead'].create(request.cr, SUPERUSER_ID, values, request.context)
@http.route(['/crm/contactus'], type='http', auth="public", website=True, multilang=True)
def contactus(self, **kwargs):
def dict_to_str(title, dictvar):
ret = "\n\n%s" % title
@ -42,12 +46,10 @@ class contactus(http.Controller):
post_description = [] # Info to add after the message
values = {}
lead_model = request.registry['crm.lead']
for field_name, field_value in kwargs.items():
if hasattr(field_value, 'filename'):
post_file.append(field_value)
elif field_name in lead_model._all_columns and field_name not in _BLACKLIST:
elif field_name in request.registry['crm.lead']._all_columns and field_name not in _BLACKLIST:
values[field_name] = field_value
elif field_name not in _TECHNICAL: # allow to add some free fields or blacklisted field like ID
post_description.append("%s: %s" % (field_name, field_value))
@ -81,7 +83,7 @@ class contactus(http.Controller):
post_description.append("%s: %s" % ("REFERER", environ.get("HTTP_REFERER")))
values['description'] += dict_to_str(_("Environ Fields: "), post_description)
lead_id = lead_model.create(request.cr, SUPERUSER_ID, dict(values, user_id=False), request.context)
lead_id = self.create_lead(request, dict(values, user_id=False))
if lead_id:
for field_value in post_file:
attachment_value = {

View File

@ -79,7 +79,7 @@ class WebsiteForum(http.Controller):
forum_id = request.registry['forum.forum'].create(request.cr, request.uid, {
'name': forum_name,
}, context=request.context)
return request.redirect("/forum/%s" % slug(forum_id))
return request.redirect("/forum/%s" % forum_id)
@http.route('/forum/notification_read', type='json', auth="user", methods=['POST'], website=True)
def notification_read(self, **kwargs):

View File

@ -254,6 +254,8 @@ class Post(osv.Model):
def vote(self, cr, uid, ids, upvote=True, context=None):
Vote = self.pool['forum.post.vote']
vote_ids = Vote.search(cr, uid, [('post_id', 'in', ids), ('user_id', '=', uid)], context=context)
new_vote = '1' if upvote else '-1'
voted_forum_ids = set()
if vote_ids:
for vote in Vote.browse(cr, uid, vote_ids, context=context):
if upvote:
@ -261,9 +263,9 @@ class Post(osv.Model):
else:
new_vote = '0' if vote.vote == '1' else '-1'
Vote.write(cr, uid, vote_ids, {'vote': new_vote}, context=context)
else:
voted_forum_ids.add(vote.post_id.id)
for post_id in set(ids) - voted_forum_ids:
for post_id in ids:
new_vote = '1' if upvote else '-1'
Vote.create(cr, uid, {'post_id': post_id, 'vote': new_vote}, context=context)
return {'vote_count': self._get_vote_count(cr, uid, ids, None, None, context=context)[ids[0]]}

View File

@ -40,17 +40,21 @@
}
this.$target.removeClass('has-error');
openerp.jsonRpc('/website_mail/follow', 'call', {
'id': +this.$target.data('id'),
'object': this.$target.data('object'),
'message_is_follower': this.$target.attr("data-follow") || "off",
'email': $email.length ? $email.val() : false,
}).then(function (follow) {
self.toggle_subscription(follow, self.email);
});
var email = $email.length ? $email.val() : false;
if (email) {
openerp.jsonRpc('/website_mail/follow', 'call', {
'id': +this.$target.data('id'),
'object': this.$target.data('object'),
'message_is_follower': this.$target.attr("data-follow") || "off",
'email': email,
}).then(function (follow) {
self.toggle_subscription(follow, email);
});
}
},
toggle_subscription: function(follow, email) {
console.log(follow, email);
follow = follow || (!email && this.$target.attr('data-unsubscribe'));
if (follow) {
this.$target.find(".js_follow_btn").addClass("hidden");
this.$target.find(".js_unfollow_btn").removeClass("hidden");
@ -60,8 +64,8 @@
this.$target.find(".js_unfollow_btn").addClass("hidden");
}
this.$target.find('input.js_follow_email')
.val(email ? email : "")
.attr("disabled", follow || (email.length && this.is_user) ? "disabled" : false);
.val(email || "")
.attr("disabled", email && (follow || this.is_user) ? "disabled" : false);
this.$target.attr("data-follow", follow ? 'on' : 'off');
},
});

View File

@ -5,7 +5,8 @@
<template id="follow">
<div class="input-group js_follow" t-att-data-id="object.id"
t-att-data-object="object._name"
t-att-data-follow="object.id and object.message_is_follower and 'on' or 'off'">
t-att-data-follow="object.id and object.message_is_follower and 'on' or 'off'"
t-att-data-unsubscribe="'unsubscribe' if 'unsubscribe' in request.params else None">
<input
type="email" name="email"
class="js_follow_email form-control"

View File

@ -1,7 +1,9 @@
# -*- coding: utf-8 -*-
from openerp.osv import osv
from openerp import tools
from openerp.tools.translate import _
from openerp.addons.website.models.website import slug
class MailGroup(osv.Model):
_inherit = 'mail.group'
@ -11,8 +13,40 @@ class MailGroup(osv.Model):
group = self.browse(cr, uid, id, context=context)
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
res['headers'].update({
'List-Archive': '<%s/groups/%s>' % (base_url, group.id),
'List-Archive': '<%s/groups/%s>' % (base_url, slug(group)),
'List-Subscribe': '<%s/groups>' % (base_url),
'List-Unsubscribe': '<%s/groups>' % (base_url),
'List-Unsubscribe': '<%s/groups?unsubscribe>' % (base_url,),
})
return res
class MailMail(osv.Model):
_inherit = 'mail.mail'
def send_get_mail_body(self, cr, uid, mail, partner=None, context=None):
""" Short-circuit parent method for mail groups, replace the default
footer with one appropriate for mailing-lists."""
if mail.model == 'mail.group' and mail.res_id:
# no super() call on purpose, no private links that could be quoted!
group = self.pool['mail.group'].browse(cr, uid, mail.res_id, context=context)
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
vals = {
'maillist': _('Mailing-List'),
'post_to': _('Post to'),
'unsub': _('Unsubscribe'),
'mailto': 'mailto:%s@%s' % (group.alias_name, group.alias_domain),
'group_url': '%s/groups/%s' % (base_url, slug(group)),
'unsub_url': '%s/groups?unsubscribe' % (base_url,),
}
footer = """_______________________________________________
%(maillist)s: %(group_url)s
%(post_to)s: %(mailto)s
%(unsub)s: %(unsub_url)s
""" % vals
body = tools.append_content_to_html(mail.body, footer, container_tag='div')
return body
else:
return super(MailMail, self).send_get_mail_body(cr, uid, mail,
partner=partner,
context=context)

View File

@ -31,6 +31,9 @@
</section>
</div>
<div class="container mt32">
<div t-if="'unsubscribe' in request.params" class="col-md-offset-9 col-md-3 alert alert-info">
<h3>Need to unsubscribe? It's right here! <span class="fa fa-2x fa-arrow-down pull-right"></span></h3>
</div>
<div class="row mt8" t-foreach="groups" t-as="group">
<div class="col-md-3">
<img t-att-src="'/website/image?model=mail.group&amp;field=image_small&amp;id='+str(group['id'])" class="pull-left"/>

View File

@ -605,7 +605,7 @@ class website_sale(http.Controller):
if not order:
return {
'state': 'error',
'message': '<p>There seems to be an error with your request.</p>',
'message': '<p>%s</p>' % _('There seems to be an error with your request.'),
}
tx_ids = request.registry['payment.transaction'].search(
@ -617,7 +617,7 @@ class website_sale(http.Controller):
if order.amount_total:
return {
'state': 'error',
'message': '<p>There seems to be an error with your request.</p>',
'message': '<p>%s</p>' % _('There seems to be an error with your request.'),
}
else:
state = 'done'
@ -627,15 +627,15 @@ class website_sale(http.Controller):
tx = request.registry['payment.transaction'].browse(cr, uid, tx_ids[0], context=context)
state = tx.state
if state == 'done':
message = '<p>Your payment has been received.</p>'
message = '<p>%s</p>' % _('Your payment has been received.')
elif state == 'cancel':
message = '<p>The payment seems to have been canceled.</p>'
message = '<p>%s</p>' % _('The payment seems to have been canceled.')
elif state == 'pending' and tx.acquirer_id.validation == 'manual':
message = '<p>Your transaction is waiting confirmation.</p>'
message = '<p>%s</p>' % _('Your transaction is waiting confirmation.')
if tx.acquirer_id.post_msg:
message += tx.acquirer_id.post_msg
else:
message = '<p>Your transaction is waiting confirmation.</p>'
message = '<p>%s</p>' % _('Your transaction is waiting confirmation.')
validation = tx.acquirer_id.validation
return {

View File

@ -876,14 +876,14 @@
<div class="clearfix"/>
<div class="form-group col-lg-6" groups="sale.group_delivery_invoice_address">
<div class="form-group col-lg-6">
<label>
<input type="checkbox" name="shipping_different" t-att-checked="checkout.get('shipping_different')"/>
<span>Ship to a different address</span>
</label>
</div>
</div>
<div class="js_shipping row mb16" t-att-style="not checkout.get('shipping_different') and 'display:none' or ''" groups="sale.group_delivery_invoice_address">
<div class="js_shipping row mb16" t-att-style="not checkout.get('shipping_different') and 'display:none' or ''">
<h3 class="oe_shipping col-lg-12 mt16">Shipping Information</h3>
<div t-attf-class="form-group #{error.get('shipping_name') and 'has-error' or ''} col-lg-6">
@ -1026,7 +1026,7 @@
<div>
<a href="/shop/checkout"><span class="fa fa-arrow-right"/> Change Address</a>
</div>
<t groups="sale.group_delivery_invoice_address">
<t>
<h4 class="mt32">Ship To:</h4>
<t t-if="website_sale_order.partner_shipping_id and website_sale_order.partner_shipping_id.id != website_sale_order.partner_invoice_id.id">
<div t-field="order.partner_shipping_id" t-field-options='{
@ -1116,7 +1116,7 @@
"widget": "contact",
"fields": ["address", "name", "phone", "email"]
}'/>
<t groups="sale.group_delivery_invoice_address">
<t>
<h4 class="mt32">Ship To:</h4>
<t t-if="order.partner_shipping_id and order.partner_shipping_id.id != order.partner_invoice_id.id">
<div t-field="order.partner_shipping_id" t-field-options='{

View File

@ -415,6 +415,13 @@ class ir_mail_server(osv.osv):
smtp_to_list = filter(None, tools.flatten(map(extract_rfc2822_addresses,[email_to, email_cc, email_bcc])))
assert smtp_to_list, "At least one valid recipient address should be specified for outgoing emails (To/Cc/Bcc)"
x_forge_to = message['X-Forge-To']
if x_forge_to:
# `To:` header forged, e.g. for posting on mail.groups, to avoid confusion
del message['X-Forge-To']
del message['To'] # avoid multiple To: headers!
message['To'] = x_forge_to
# Do not actually send emails in testing mode!
if getattr(threading.currentThread(), 'testing', False):
_test_logger.info("skip sending email in test mode")

View File

@ -303,10 +303,10 @@ class ir_values(osv.osv):
(SELECT company_id from res_users where id = %%s)
)
%s
ORDER BY v.user_id, u.company_id"""
ORDER BY v.user_id, u.company_id, v.key2"""
params = ('default', model, uid, uid)
if condition:
query %= 'AND v.key2 = %s'
query %= 'AND (v.key2 = %s OR v.key2 IS NULL)'
params += (condition[:200],)
else:
query %= 'AND v.key2 is NULL'