[MERGE] forward port of branch saas-5 up to adf07a9

This commit is contained in:
Christophe Simonis 2014-06-19 16:23:32 +02:00
commit eef6330c55
54 changed files with 295 additions and 128 deletions

View File

@ -295,7 +295,8 @@ class account_invoice(osv.osv):
}, },
multi='all'), multi='all'),
'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}, track_visibility='always'), 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}, track_visibility='always'),
'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}), 'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]},
domain="[('type', 'in', {'out_invoice': ['sale'], 'out_refund': ['sale_refund'], 'in_refund': ['purchase_refund'], 'in_invoice': ['purchase']}.get(type, [])), ('company_id', '=', company_id)]"),
'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True, readonly=True, states={'draft':[('readonly',False)]}), 'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True, readonly=True, states={'draft':[('readonly',False)]}),
'check_total': fields.float('Verification Total', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}), 'check_total': fields.float('Verification Total', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
'reconciled': fields.function(_reconciled, string='Paid/Reconciled', type='boolean', 'reconciled': fields.function(_reconciled, string='Paid/Reconciled', type='boolean',

View File

@ -99,7 +99,7 @@
</tr> </tr>
<tr t-foreach="get_lines_with_out_partner(data['form'])" t-as="not_partner"> <tr t-foreach="get_lines_with_out_partner(data['form'])" t-as="not_partner">
<td> <td>
<span t-esc="partner['name']"/> <span t-esc="not_partner['name']"/>
</td> </td>
<td class="text-right"> <td class="text-right">
<span t-esc="formatLang(not_partner['direction'], currency_obj=res_company.currency_id)"/> <span t-esc="formatLang(not_partner['direction'], currency_obj=res_company.currency_id)"/>

View File

@ -22,7 +22,6 @@ from dateutil.relativedelta import relativedelta
import datetime import datetime
import logging import logging
import time import time
import traceback
from openerp.osv import osv, fields from openerp.osv import osv, fields
from openerp.osv.orm import intersect, except_orm from openerp.osv.orm import intersect, except_orm
@ -73,6 +72,7 @@ class account_analytic_invoice_line(osv.osv):
result = {} result = {}
res = self.pool.get('product.product').browse(cr, uid, product, context=local_context) res = self.pool.get('product.product').browse(cr, uid, product, context=local_context)
price = False
if price_unit is not False: if price_unit is not False:
price = price_unit price = price_unit
elif pricelist_id: elif pricelist_id:
@ -746,29 +746,32 @@ class account_analytic_account(osv.osv):
contract_ids = ids contract_ids = ids
else: else:
contract_ids = self.search(cr, uid, [('recurring_next_date','<=', current_date), ('state','=', 'open'), ('recurring_invoices','=', True), ('type', '=', 'contract')]) contract_ids = self.search(cr, uid, [('recurring_next_date','<=', current_date), ('state','=', 'open'), ('recurring_invoices','=', True), ('type', '=', 'contract')])
for contract in self.browse(cr, uid, contract_ids, context=context): if contract_ids:
try: cr.execute('SELECT company_id, array_agg(id) as ids FROM account_analytic_account WHERE id IN %s GROUP BY company_id', (tuple(contract_ids),))
invoice_values = self._prepare_invoice(cr, uid, contract, context=context) for company_id, ids in cr.fetchall():
invoice_ids.append(self.pool['account.invoice'].create(cr, uid, invoice_values, context=context)) for contract in self.browse(cr, uid, ids, context=dict(context, company_id=company_id, force_company=company_id)):
next_date = datetime.datetime.strptime(contract.recurring_next_date or current_date, "%Y-%m-%d") try:
interval = contract.recurring_interval invoice_values = self._prepare_invoice(cr, uid, contract, context=context)
if contract.recurring_rule_type == 'daily': invoice_ids.append(self.pool['account.invoice'].create(cr, uid, invoice_values, context=context))
new_date = next_date+relativedelta(days=+interval) next_date = datetime.datetime.strptime(contract.recurring_next_date or current_date, "%Y-%m-%d")
elif contract.recurring_rule_type == 'weekly': interval = contract.recurring_interval
new_date = next_date+relativedelta(weeks=+interval) if contract.recurring_rule_type == 'daily':
elif contract.recurring_rule_type == 'monthly': new_date = next_date+relativedelta(days=+interval)
new_date = next_date+relativedelta(months=+interval) elif contract.recurring_rule_type == 'weekly':
else: new_date = next_date+relativedelta(weeks=+interval)
new_date = next_date+relativedelta(years=+interval) elif contract.recurring_rule_type == 'monthly':
self.write(cr, uid, [contract.id], {'recurring_next_date': new_date.strftime('%Y-%m-%d')}, context=context) new_date = next_date+relativedelta(months=+interval)
if automatic: else:
cr.commit() new_date = next_date+relativedelta(years=+interval)
except Exception: self.write(cr, uid, [contract.id], {'recurring_next_date': new_date.strftime('%Y-%m-%d')}, context=context)
if automatic: if automatic:
cr.rollback() cr.commit()
_logger.error(traceback.format_exc()) except Exception:
else: if automatic:
raise cr.rollback()
_logger.exception('Fail to create recurring invoice for contract %s', contract.code)
else:
raise
return invoice_ids return invoice_ids
class account_analytic_account_summary_user(osv.osv): class account_analytic_account_summary_user(osv.osv):

View File

@ -248,7 +248,7 @@
<page string="Bill Information"> <page string="Bill Information">
<field name="line_dr_ids" on_change="onchange_price(line_dr_ids, tax_id, partner_id)" context="{'journal_id':journal_id,'partner_id':partner_id}"> <field name="line_dr_ids" on_change="onchange_price(line_dr_ids, tax_id, partner_id)" context="{'journal_id':journal_id,'partner_id':partner_id}">
<tree string="Expense Lines" editable="bottom"> <tree string="Expense Lines" editable="bottom">
<field name="account_id" widget="selection" domain="[('user_type.report_type','=','expense'), ('type','!=','view')]"/> <field name="account_id" domain="[('user_type.report_type','=','expense'), ('type','!=','view')]"/>
<field name="name"/> <field name="name"/>
<field name="amount"/> <field name="amount"/>
<field name="account_analytic_id" groups="analytic.group_analytic_accounting"/> <field name="account_analytic_id" groups="analytic.group_analytic_accounting"/>

View File

@ -74,7 +74,7 @@ class OAuthLogin(Home):
state = dict( state = dict(
d=request.session.db, d=request.session.db,
p=provider['id'], p=provider['id'],
r=redirect, r=werkzeug.url_quote_plus(redirect),
) )
token = request.params.get('token') token = request.params.get('token')
if token: if token:
@ -143,7 +143,7 @@ class OAuthController(http.Controller):
cr.commit() cr.commit()
action = state.get('a') action = state.get('a')
menu = state.get('m') menu = state.get('m')
redirect = state.get('r') redirect = werkzeug.url_unquote_plus(state['r']) if state.get('r') else False
url = '/web' url = '/web'
if redirect: if redirect:
url = redirect url = redirect

View File

@ -102,7 +102,7 @@ openerp.calendar = function(instance) {
var self = this; var self = this;
var action_url = ''; var action_url = '';
action_url = _.str.sprintf('/?db=%s#id=%s&view_type=form&model=calendar.event', db, meeting_id); action_url = _.str.sprintf('/web?db=%s#id=%s&view_type=form&model=calendar.event', db, meeting_id);
var reload_page = function(){ var reload_page = function(){
return location.replace(action_url); return location.replace(action_url);

View File

@ -32,7 +32,7 @@ class report_event_registration(osv.osv):
'draft_state': fields.integer(' # No of Draft Registrations', size=20), 'draft_state': fields.integer(' # No of Draft Registrations', size=20),
'confirm_state': fields.integer(' # No of Confirmed Registrations', size=20), 'confirm_state': fields.integer(' # No of Confirmed Registrations', size=20),
'seats_max': fields.integer('Max Seats'), 'seats_max': fields.integer('Max Seats'),
'nbevent': fields.integer('Number Of Events'), 'nbevent': fields.integer('Number of Registrations'),
'event_type': fields.many2one('event.type', 'Event Type'), 'event_type': fields.many2one('event.type', 'Event Type'),
'registration_state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Attended'), ('cancel', 'Cancelled')], 'Registration State', readonly=True, required=True), 'registration_state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Attended'), ('cancel', 'Cancelled')], 'Registration State', readonly=True, required=True),
'event_state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Event State', readonly=True, required=True), 'event_state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Event State', readonly=True, required=True),
@ -59,7 +59,7 @@ class report_event_registration(osv.osv):
r.name AS name_registration, r.name AS name_registration,
e.company_id AS company_id, e.company_id AS company_id,
e.date_begin AS event_date, e.date_begin AS event_date,
count(e.id) AS nbevent, count(r.id) AS nbevent,
CASE WHEN r.state IN ('draft') THEN r.nb_register ELSE 0 END AS draft_state, CASE WHEN r.state IN ('draft') THEN r.nb_register ELSE 0 END AS draft_state,
CASE WHEN r.state IN ('open','done') THEN r.nb_register ELSE 0 END AS confirm_state, CASE WHEN r.state IN ('open','done') THEN r.nb_register ELSE 0 END AS confirm_state,
e.type AS event_type, e.type AS event_type,

View File

@ -1,7 +1,7 @@
.oe_event_date{ .oe_event_date{
border-top-left-radius:3px; border-top-left-radius:3px;
border-top-right-radius:3px; border-top-right-radius:3px;
font-size: 48px; font-size: 36px;
height: auto; height: auto;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;

View File

@ -245,7 +245,8 @@ class event_ticket(osv.osv):
] ]
def onchange_product_id(self, cr, uid, ids, product_id=False, context=None): def onchange_product_id(self, cr, uid, ids, product_id=False, context=None):
return {'value': {'price': self.pool.get("product.product").browse(cr, uid, product_id).list_price or 0}} price = self.pool.get("product.product").browse(cr, uid, product_id).list_price if product_id else 0
return {'value': {'price': price}}
class event_registration(osv.osv): class event_registration(osv.osv):

View File

@ -58,7 +58,7 @@ def start_end_date_for_period(period, default_start_date=False, default_end_date
end_date = default_end_date end_date = default_end_date
if start_date and end_date: if start_date and end_date:
return (start_date.strftime(DF), end_date.strftime(DF)) return (datetime.strftime(start_date, DF), datetime.strftime(end_date, DF))
else: else:
return (start_date, end_date) return (start_date, end_date)

View File

@ -699,7 +699,7 @@ class google_calendar(osv.AbstractModel):
for att in att_obj.browse(cr, uid, my_att_ids, context=context): for att in att_obj.browse(cr, uid, my_att_ids, context=context):
event = att.event_id event = att.event_id
base_event_id = att.google_internal_event_id.split('_')[0] base_event_id = att.google_internal_event_id.rsplit('_', 1)[0]
if base_event_id not in event_to_synchronize: if base_event_id not in event_to_synchronize:
event_to_synchronize[base_event_id] = {} event_to_synchronize[base_event_id] = {}
@ -721,7 +721,7 @@ class google_calendar(osv.AbstractModel):
for event in all_event_from_google.values(): for event in all_event_from_google.values():
event_id = event.get('id') event_id = event.get('id')
base_event_id = event_id.split('_')[0] base_event_id = event_id.rsplit('_', 1)[0]
if base_event_id not in event_to_synchronize: if base_event_id not in event_to_synchronize:
event_to_synchronize[base_event_id] = {} event_to_synchronize[base_event_id] = {}
@ -786,7 +786,7 @@ class google_calendar(osv.AbstractModel):
if actSrc == 'OE': if actSrc == 'OE':
self.delete_an_event(cr, uid, current_event[0], context=context) self.delete_an_event(cr, uid, current_event[0], context=context)
elif actSrc == 'GG': elif actSrc == 'GG':
new_google_event_id = event.GG.event['id'].split('_')[1] new_google_event_id = event.GG.event['id'].rsplit('_', 1)[1]
if 'T' in new_google_event_id: if 'T' in new_google_event_id:
new_google_event_id = new_google_event_id.replace('T', '')[:-1] new_google_event_id = new_google_event_id.replace('T', '')[:-1]
else: else:
@ -795,7 +795,8 @@ class google_calendar(osv.AbstractModel):
if event.GG.status: if event.GG.status:
parent_event = {} parent_event = {}
if not event_to_synchronize[base_event][0][1].OE.event_id: if not event_to_synchronize[base_event][0][1].OE.event_id:
event_to_synchronize[base_event][0][1].OE.event_id = att_obj.search_read(cr, uid, [('google_internal_event_id', '=', event.GG.event['id'].split('_')[0])], ['event_id'], context=context_novirtual)[0].get('event_id')[0] main_ev = att_obj.search_read(cr, uid, [('google_internal_event_id', '=', event.GG.event['id'].rsplit('_', 1)[0])], fields=['event_id'], context=context_novirtual)
event_to_synchronize[base_event][0][1].OE.event_id = main_ev[0].get('event_id')[0]
parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id, new_google_event_id) parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id, new_google_event_id)
res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context) res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context)

View File

@ -225,7 +225,7 @@ class hr_employee(osv.osv):
"resized as a 128x128px image, with aspect ratio preserved. "\ "resized as a 128x128px image, with aspect ratio preserved. "\
"Use this field in form views or some kanban views."), "Use this field in form views or some kanban views."),
'image_small': fields.function(_get_image, fnct_inv=_set_image, 'image_small': fields.function(_get_image, fnct_inv=_set_image,
string="Smal-sized photo", type="binary", multi="_get_image", string="Small-sized photo", type="binary", multi="_get_image",
store = { store = {
'hr.employee': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10), 'hr.employee': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
}, },

View File

@ -40,7 +40,9 @@
<page string="Public Information"> <page string="Public Information">
<group> <group>
<group string="Contact Information"> <group string="Contact Information">
<field name="address_id" on_change="onchange_address_id(address_id)" context="{'show_address': 1}" options='{"always_reload": True, "highlight_first_line": True}'/> <field name="address_id" on_change="onchange_address_id(address_id)"
context="{'show_address': 1, 'default_customer': False}"
options='{"always_reload": True, "highlight_first_line": True}'/>
<field name="mobile_phone"/> <field name="mobile_phone"/>
<field name="work_location"/> <field name="work_location"/>
</group> </group>
@ -68,7 +70,9 @@
<field name="otherid" groups="base.group_hr_user"/> <field name="otherid" groups="base.group_hr_user"/>
</group> </group>
<group string="Contact Information"> <group string="Contact Information">
<field name="address_home_id" context="{'show_address': 1}" options='{"always_reload": True, "highlight_first_line": True}'/> <field name="address_home_id"
context="{'show_address': 1, 'default_customer': False}"
options='{"always_reload": True, "highlight_first_line": True}'/>
</group> </group>
<group string="Status"> <group string="Status">
<field name="gender"/> <field name="gender"/>

View File

@ -99,7 +99,7 @@ class hr_holidays_status(osv.osv):
for record in self.browse(cr, uid, ids, context=context): for record in self.browse(cr, uid, ids, context=context):
name = record.name name = record.name
if not record.limit: if not record.limit:
name = name + (' (%d/%d)' % (record.leaves_taken or 0.0, record.max_leaves or 0.0)) name = name + (' (%g/%g)' % (record.leaves_taken or 0.0, record.max_leaves or 0.0))
res.append((record.id, name)) res.append((record.id, name))
return res return res

View File

@ -141,7 +141,7 @@ class account_invoice(osv.osv):
elif algorithm == 'random': elif algorithm == 'random':
if not self.check_bbacomm(reference): if not self.check_bbacomm(reference):
base = random.randint(1, 9999999999) base = random.randint(1, 9999999999)
bbacomm = str(base).rjust(7, '0') bbacomm = str(base).rjust(10, '0')
base = int(bbacomm) base = int(bbacomm)
mod = base % 97 or 97 mod = base % 97 or 97
mod = str(mod).rjust(2, '0') mod = str(mod).rjust(2, '0')

View File

@ -176,7 +176,7 @@ class mail_notification(osv.Model):
references = message.parent_id.message_id if message.parent_id else False references = message.parent_id.message_id if message.parent_id else False
# create email values # create email values
max_recipients = 100 max_recipients = 50
chunks = [email_pids[x:x + max_recipients] for x in xrange(0, len(email_pids), max_recipients)] chunks = [email_pids[x:x + max_recipients] for x in xrange(0, len(email_pids), max_recipients)]
email_ids = [] email_ids = []
for chunk in chunks: for chunk in chunks:
@ -188,7 +188,7 @@ class mail_notification(osv.Model):
'references': references, 'references': references,
} }
email_ids.append(self.pool.get('mail.mail').create(cr, uid, mail_values, context=context)) email_ids.append(self.pool.get('mail.mail').create(cr, uid, mail_values, context=context))
if force_send and len(chunks) < 6: # for more than 500 followers, use the queue system if force_send and len(chunks) < 2: # for more than 50 followers, use the queue system
self.pool.get('mail.mail').send(cr, uid, email_ids, context=context) self.pool.get('mail.mail').send(cr, uid, email_ids, context=context)
return True return True

View File

@ -211,3 +211,16 @@ class mail_group(osv.Model):
return [] return []
else: else:
return super(mail_group, self).get_suggested_thread(cr, uid, removed_suggested_threads, context) return super(mail_group, self).get_suggested_thread(cr, uid, removed_suggested_threads, context)
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',
}
})
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)
return res

View File

@ -204,12 +204,15 @@ class mail_mail(osv.Model):
""" """
body = self.send_get_mail_body(cr, uid, mail, partner=partner, context=context) body = self.send_get_mail_body(cr, uid, mail, partner=partner, context=context)
body_alternative = tools.html2plaintext(body) body_alternative = tools.html2plaintext(body)
return { res = {
'body': body, 'body': body,
'body_alternative': body_alternative, 'body_alternative': body_alternative,
'subject': self.send_get_mail_subject(cr, uid, mail, partner=partner, context=context), 'subject': self.send_get_mail_subject(cr, uid, mail, partner=partner, context=context),
'email_to': self.send_get_mail_to(cr, uid, mail, partner=partner, context=context), 'email_to': self.send_get_mail_to(cr, uid, mail, partner=partner, context=context),
} }
if mail.model and mail.res_id and self.pool.get(mail.model) and hasattr(self.pool[mail.model], 'message_get_email_values'):
res.update(self.pool[mail.model].message_get_email_values(cr, uid, mail.res_id, mail, context=context))
return res
def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None): def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None):
""" Sends the selected emails immediately, ignoring their current """ Sends the selected emails immediately, ignoring their current
@ -268,6 +271,9 @@ class mail_mail(osv.Model):
# build an RFC2822 email.message.Message object and send it without queuing # build an RFC2822 email.message.Message object and send it without queuing
res = None res = None
for email in email_list: for email in email_list:
email_headers = dict(headers)
if email.get('headers'):
email_headers.update(email['headers'])
msg = ir_mail_server.build_email( msg = ir_mail_server.build_email(
email_from=mail.email_from, email_from=mail.email_from,
email_to=email.get('email_to'), email_to=email.get('email_to'),
@ -282,7 +288,7 @@ class mail_mail(osv.Model):
object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)), object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
subtype='html', subtype='html',
subtype_alternative='plain', subtype_alternative='plain',
headers=headers) headers=email_headers)
res = ir_mail_server.send_email(cr, uid, msg, res = ir_mail_server.send_email(cr, uid, msg,
mail_server_id=mail.mail_server_id.id, mail_server_id=mail.mail_server_id.id,
context=context) context=context)

View File

@ -34,6 +34,7 @@ import pytz
import socket import socket
import time import time
import xmlrpclib import xmlrpclib
import re
from email.message import Message from email.message import Message
from urllib import urlencode from urllib import urlencode
@ -48,6 +49,8 @@ from openerp.tools.translate import _
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
mail_header_msgid_re = re.compile('<[^<>]+>')
def decode_header(message, header, separator=' '): def decode_header(message, header, separator=' '):
return separator.join(map(decode, filter(None, message.get_all(header, [])))) return separator.join(map(decode, filter(None, message.get_all(header, []))))
@ -694,6 +697,16 @@ class mail_thread(osv.AbstractModel):
if record.alias_domain and record.alias_name else False if record.alias_domain and record.alias_name else False
for record in self.browse(cr, SUPERUSER_ID, ids, context=context)] for record in self.browse(cr, SUPERUSER_ID, ids, context=context)]
def message_get_email_values(self, cr, uid, id, notif_mail=None, context=None):
""" Temporary method to create custom notification email values for a given
model and document. This should be better to have a headers field on
the mail.mail model, computed when creating the notification email, but
this cannot be done in a stable version.
TDE FIXME: rethink this ulgy thing. """
res = dict()
return res
#------------------------------------------------------ #------------------------------------------------------
# Mail gateway # Mail gateway
#------------------------------------------------------ #------------------------------------------------------
@ -1301,13 +1314,13 @@ class mail_thread(osv.AbstractModel):
msg_dict['date'] = stored_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT) msg_dict['date'] = stored_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
if message.get('In-Reply-To'): if message.get('In-Reply-To'):
parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', '=', decode(message['In-Reply-To']))]) parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', '=', decode(message['In-Reply-To'].strip()))])
if parent_ids: if parent_ids:
msg_dict['parent_id'] = parent_ids[0] msg_dict['parent_id'] = parent_ids[0]
if message.get('References') and 'parent_id' not in msg_dict: if message.get('References') and 'parent_id' not in msg_dict:
parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', 'in', msg_list = mail_header_msgid_re.findall(decode(message['References']))
[x.strip() for x in decode(message['References']).split()])]) parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', 'in', [x.strip() for x in msg_list])])
if parent_ids: if parent_ids:
msg_dict['parent_id'] = parent_ids[0] msg_dict['parent_id'] = parent_ids[0]

View File

@ -267,10 +267,7 @@ class mail_compose_message(osv.TransientModel):
# mass mailing: rendering override wizard static values # mass mailing: rendering override wizard static values
if mass_mail_mode and wizard.model: if mass_mail_mode and wizard.model:
# always keep a copy, reset record name (avoid browsing records) # always keep a copy, reset record name (avoid browsing records)
mail_values.update(notification=True, record_name=False) mail_values.update(notification=True, model=wizard.model, res_id=res_id, record_name=False)
if hasattr(self.pool[wizard.model], 'message_new'):
mail_values['model'] = wizard.model
mail_values['res_id'] = res_id
# auto deletion of mail_mail # auto deletion of mail_mail
if 'mail_auto_delete' in context: if 'mail_auto_delete' in context:
mail_values['auto_delete'] = context.get('mail_auto_delete') mail_values['auto_delete'] = context.get('mail_auto_delete')

View File

@ -84,7 +84,8 @@ class MailMail(osv.Model):
def send_get_email_dict(self, cr, uid, mail, partner=None, context=None): def send_get_email_dict(self, cr, uid, mail, partner=None, context=None):
res = super(MailMail, self).send_get_email_dict(cr, uid, mail, partner, context=context) res = super(MailMail, self).send_get_email_dict(cr, uid, mail, partner, context=context)
if mail.mailing_id and res.get('body') and res.get('email_to'): if mail.mailing_id and res.get('body') and res.get('email_to'):
email_to = tools.email_split(res.get('email_to')[0]) emails = tools.email_split(res.get('email_to')[0])
email_to = emails and emails[0] or False
unsubscribe_url = self._get_unsubscribe_url(cr, uid, mail, email_to, context=context) unsubscribe_url = self._get_unsubscribe_url(cr, uid, mail, email_to, context=context)
if unsubscribe_url: if unsubscribe_url:
res['body'] = tools.append_content_to_html(res['body'], unsubscribe_url, plaintext=False, container_tag='p') res['body'] = tools.append_content_to_html(res['body'], unsubscribe_url, plaintext=False, container_tag='p')

View File

@ -591,6 +591,7 @@
<field name="mail_mail_id"/> <field name="mail_mail_id"/>
<field name="message_id"/> <field name="message_id"/>
<field name="sent"/> <field name="sent"/>
<field name="exception"/>
<field name="opened"/> <field name="opened"/>
<field name="replied"/> <field name="replied"/>
<field name="bounced"/> <field name="bounced"/>

View File

@ -1071,8 +1071,8 @@ class mrp_production(osv.osv):
return False return False
# Take routing location as a Source Location. # Take routing location as a Source Location.
source_location_id = production.location_src_id.id source_location_id = production.location_src_id.id
if production.bom_id.routing_id and production.bom_id.routing_id.location_id: if production.routing_id and production.routing_id.location_id:
source_location_id = production.bom_id.routing_id.location_id.id source_location_id = production.routing_id.location_id.id
destination_location_id = production.product_id.property_stock_production.id destination_location_id = production.product_id.property_stock_production.id
if not source_location_id: if not source_location_id:

View File

@ -678,6 +678,17 @@ class product_template(osv.osv):
if not context or "create_product_product" not in context: if not context or "create_product_product" not in context:
self.create_variant_ids(cr, uid, [product_template_id], context=context) self.create_variant_ids(cr, uid, [product_template_id], context=context)
self._set_standard_price(cr, uid, product_template_id, vals.get('standard_price', 0.0), context=context) self._set_standard_price(cr, uid, product_template_id, vals.get('standard_price', 0.0), context=context)
# TODO: this is needed to set given values to first variant after creation
# these fields should be moved to product as lead to confusion
related_vals = {}
if vals.get('ean13'):
related_vals['ean13'] = vals['ean13']
if vals.get('default_code'):
related_vals['default_code'] = vals['default_code']
if related_vals:
self.write(cr, uid, product_template_id, related_vals, context=context)
return product_template_id return product_template_id
def write(self, cr, uid, ids, vals, context=None): def write(self, cr, uid, ids, vals, context=None):

View File

@ -27,7 +27,7 @@ class account_invoice(osv.Model):
template_values = Composer.onchange_template_id( template_values = Composer.onchange_template_id(
cr, uid, composer_id, line.product_id.email_template_id.id, 'comment', 'account.invoice', invoice.id cr, uid, composer_id, line.product_id.email_template_id.id, 'comment', 'account.invoice', invoice.id
)['value'] )['value']
template_values['attachment_ids'] = [(4, id) for id in template_values.get('attachment_ids', '[]')] template_values['attachment_ids'] = [(4, id) for id in template_values.get('attachment_ids', [])]
Composer.write(cr, uid, [composer_id], template_values, context=context) Composer.write(cr, uid, [composer_id], template_values, context=context)
Composer.send_mail(cr, uid, [composer_id], context=context) Composer.send_mail(cr, uid, [composer_id], context=context)
return True return True

View File

@ -50,8 +50,8 @@
<field name="user_id" ref="base.user_demo"/> <field name="user_id" ref="base.user_demo"/>
<field name="alias_model">project.task</field> <field name="alias_model">project.task</field>
<field name="message_follower_ids" eval="[(6, 0, [ <field name="message_follower_ids" eval="[(6, 0, [
ref('base.user_root'), ref('base.partner_root'),
ref('base.user_demo')])]"/> ref('base.partner_demo')])]"/>
</record> </record>
<!-- We assign after so that default values applies --> <!-- We assign after so that default values applies -->

View File

@ -749,7 +749,7 @@
</tree> </tree>
</field> </field>
--> -->
<field name="move_lines" context="{'address_in_id': partner_id, 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'view_move_picking_tree', 'default_picking_type_id': picking_type_id,'default_picking_id': active_id}"/> <field name="move_lines" context="{'address_in_id': partner_id, 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'stock.view_move_picking_tree', 'default_picking_type_id': picking_type_id,'default_picking_id': active_id}"/>
<field name="pack_operation_exist" invisible="1"/> <field name="pack_operation_exist" invisible="1"/>
<field name="note" placeholder="Add an internal note..." class="oe_inline"/> <field name="note" placeholder="Add an internal note..." class="oe_inline"/>
</page> </page>
@ -1288,7 +1288,7 @@
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>
<field name="view_type">form</field> <field name="view_type">form</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="domain" eval="['|','&amp;',('picking_id','=',False),('location_dest_id.usage', 'in', ['customer','supplier']),'&amp;',('picking_id','!=',False),('picking_id.picking_type_id.code','=','outgoing')]"/> <field name="domain" eval="[('picking_id.picking_type_id.code','=','incoming'), ('location_id.usage','!=','internal'), ('location_dest_id.usage', '=', 'internal')]"/>
<field name="view_id" ref="view_move_tree_reception_picking"/> <field name="view_id" ref="view_move_tree_reception_picking"/>
<field name="context">{'product_receive': True, 'search_default_future': True}</field> <field name="context">{'product_receive': True, 'search_default_future': True}</field>
<field name="help" type="html"> <field name="help" type="html">

View File

@ -2373,7 +2373,7 @@
} }
.openerp .oe_fileupload .oe_add button.oe_attach .oe_e { .openerp .oe_fileupload .oe_add button.oe_attach .oe_e {
position: relative; position: relative;
top: -1px; top: -10px;
left: -9px; left: -9px;
} }
.openerp .oe_fileupload .oe_add input.oe_form_binary_file { .openerp .oe_fileupload .oe_add input.oe_form_binary_file {
@ -3324,6 +3324,9 @@ body.oe_single_form .oe_single_form_container {
.openerp_ie ul.oe_form_status li.oe_active > .arrow span, .openerp_ie ul.oe_form_status_clickable li.oe_active > .arrow span { .openerp_ie ul.oe_form_status li.oe_active > .arrow span, .openerp_ie ul.oe_form_status_clickable li.oe_active > .arrow span {
background-color: #729fcf !important; background-color: #729fcf !important;
} }
}
.openerp_ie .oe_webclient {
height: auto !important;
@media print { @media print {
.openerp { .openerp {

View File

@ -1939,7 +1939,7 @@ $sheet-padding: 16px
text-shadow: none text-shadow: none
.oe_e .oe_e
position: relative position: relative
top: -1px top: -10px
left: -9px left: -9px
input.oe_form_binary_file input.oe_form_binary_file
display: inline-block display: inline-block
@ -2691,6 +2691,8 @@ body.oe_single_form
> .arrow span > .arrow span
background-color: #729fcf !important background-color: #729fcf !important
.oe_webclient
height: auto !important
// }}} // }}}
// @media print {{{ // @media print {{{

View File

@ -233,7 +233,8 @@ instance.web.parse_value = function (value, descriptor, value_if_empty) {
value = value.replace(instance.web._t.database.parameters.thousands_sep, ""); value = value.replace(instance.web._t.database.parameters.thousands_sep, "");
} while(tmp !== value); } while(tmp !== value);
tmp = Number(value); tmp = Number(value);
if (isNaN(tmp)) // do not accept not numbers or float values
if (isNaN(tmp) || tmp % 1)
throw new Error(_.str.sprintf(_t("'%s' is not a correct integer"), value)); throw new Error(_.str.sprintf(_t("'%s' is not a correct integer"), value));
return tmp; return tmp;
case 'float': case 'float':
@ -268,6 +269,11 @@ instance.web.parse_value = function (value, descriptor, value_if_empty) {
case 'datetime': case 'datetime':
var datetime = Date.parseExact( var datetime = Date.parseExact(
value, (date_pattern + ' ' + time_pattern)); value, (date_pattern + ' ' + time_pattern));
if (datetime !== null)
return instance.web.datetime_to_str(datetime);
datetime = Date.parseExact(value.replace(/\d+/g, function(m){
return m.length === 1 ? "0" + m : m ;
}), (date_pattern + ' ' + time_pattern));
if (datetime !== null) if (datetime !== null)
return instance.web.datetime_to_str(datetime); return instance.web.datetime_to_str(datetime);
datetime = Date.parse(value); datetime = Date.parse(value);
@ -276,6 +282,11 @@ instance.web.parse_value = function (value, descriptor, value_if_empty) {
throw new Error(_.str.sprintf(_t("'%s' is not a correct datetime"), value)); throw new Error(_.str.sprintf(_t("'%s' is not a correct datetime"), value));
case 'date': case 'date':
var date = Date.parseExact(value, date_pattern); var date = Date.parseExact(value, date_pattern);
if (date !== null)
return instance.web.date_to_str(date);
date = Date.parseExact(value.replace(/\d+/g, function(m){
return m.length === 1 ? "0" + m : m ;
}), date_pattern);
if (date !== null) if (date !== null)
return instance.web.date_to_str(date); return instance.web.date_to_str(date);
date = Date.parse(value); date = Date.parse(value);

View File

@ -346,11 +346,11 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
'keydown .oe_searchview_input, .oe_searchview_facet': function (e) { 'keydown .oe_searchview_input, .oe_searchview_facet': function (e) {
switch(e.which) { switch(e.which) {
case $.ui.keyCode.LEFT: case $.ui.keyCode.LEFT:
this.focusPreceding(this); this.focusPreceding(e.target);
e.preventDefault(); e.preventDefault();
break; break;
case $.ui.keyCode.RIGHT: case $.ui.keyCode.RIGHT:
this.focusFollowing(this); this.focusFollowing(e.target);
e.preventDefault(); e.preventDefault();
break; break;
} }

View File

@ -2632,6 +2632,7 @@ instance.web.DateTimeWidget = instance.web.Widget.extend({
type_of_date: "datetime", type_of_date: "datetime",
events: { events: {
'change .oe_datepicker_master': 'change_datetime', 'change .oe_datepicker_master': 'change_datetime',
'keypress .oe_datepicker_master': 'change_datetime',
}, },
init: function(parent) { init: function(parent) {
this._super(parent); this._super(parent);
@ -2750,8 +2751,8 @@ instance.web.DateTimeWidget = instance.web.Widget.extend({
format_client: function(v) { format_client: function(v) {
return instance.web.format_value(v, {"widget": this.type_of_date}); return instance.web.format_value(v, {"widget": this.type_of_date});
}, },
change_datetime: function() { change_datetime: function(e) {
if (this.is_valid_()) { if ((e.type !== "keypress" || e.which === 13) && this.is_valid_()) {
this.set_value_from_ui_(); this.set_value_from_ui_();
this.trigger("datetime_changed"); this.trigger("datetime_changed");
} }

View File

@ -130,15 +130,7 @@
if (this.editable()) { if (this.editable()) {
this.$el.find('table:first').show(); this.$el.find('table:first').show();
this.$el.find('.oe_view_nocontent').remove(); this.$el.find('.oe_view_nocontent').remove();
this.start_edition().then(function(){ this.start_edition();
var fields = self.editor.form.fields;
self.editor.form.fields_order.some(function(field){
if (fields[field].$el.is(':visible')){
fields[field].$el.find("input").select();
return true;
}
});
});
} else { } else {
this._super(); this._super();
} }
@ -243,6 +235,7 @@
return this.ensure_saved().then(function () { return this.ensure_saved().then(function () {
var $recordRow = self.groups.get_row_for(record); var $recordRow = self.groups.get_row_for(record);
var cells = self.get_cells_for($recordRow); var cells = self.get_cells_for($recordRow);
var fields = {};
self.fields_for_resize.splice(0, self.fields_for_resize.length); self.fields_for_resize.splice(0, self.fields_for_resize.length);
return self.with_event('edit', { return self.with_event('edit', {
record: record.attributes, record: record.attributes,
@ -256,10 +249,16 @@
// FIXME: need better way to get the field back from bubbling (delegated) DOM events somehow // FIXME: need better way to get the field back from bubbling (delegated) DOM events somehow
field.$el.attr('data-fieldname', field_name); field.$el.attr('data-fieldname', field_name);
fields[field_name] = field;
self.fields_for_resize.push({field: field, cell: cell}); self.fields_for_resize.push({field: field, cell: cell});
}, options).then(function () { }, options).then(function () {
$recordRow.addClass('oe_edition'); $recordRow.addClass('oe_edition');
self.resize_fields(); self.resize_fields();
var focus_field = options && options.focus_field ? options.focus_field : undefined;
if (!focus_field){
focus_field = _.find(self.editor.form.fields_order, function(field){ return fields[field] && fields[field].$el.is(':visible:has(input)'); });
}
if (focus_field) fields[focus_field].$el.find('input').select();
return record.attributes; return record.attributes;
}); });
}).fail(function () { }).fail(function () {
@ -749,31 +748,6 @@
throw new Error("is_editing's state filter must be either `new` or" + throw new Error("is_editing's state filter must be either `new` or" +
" `edit` if provided"); " `edit` if provided");
}, },
_focus_setup: function (focus_field) {
var form = this.form;
var field;
// If a field to focus was specified
if (focus_field
// Is actually in the form
&& (field = form.fields[focus_field])
// And is visible
&& field.$el.is(':visible')) {
// focus it
field.focus();
return;
}
_(form.fields_order).detect(function (name) {
// look for first visible field in fields_order, focus it
var field = form.fields[name];
if (!field.$el.is(':visible')) {
return false;
}
// Stop as soon as a field got focused
return field.focus() !== false;
});
},
edit: function (record, configureField, options) { edit: function (record, configureField, options) {
// TODO: specify sequence of edit calls // TODO: specify sequence of edit calls
var self = this; var self = this;
@ -788,7 +762,6 @@
_(form.fields).each(function (field, name) { _(form.fields).each(function (field, name) {
configureField(name, field); configureField(name, field);
}); });
self._focus_setup(options && options.focus_field);
return form; return form;
}); });
}, },

View File

@ -218,7 +218,12 @@ openerp.web_calendar = function(instance) {
this.info_fields.push(fv.arch.children[fld].attrs.name); this.info_fields.push(fv.arch.children[fld].attrs.name);
} }
return (new instance.web.Model(this.dataset.model)) var edit_check = new instance.web.Model(this.dataset.model)
.call("check_access_rights", ["write", false])
.then(function (write_right) {
self.write_right = write_right;
});
var init = new instance.web.Model(this.dataset.model)
.call("check_access_rights", ["create", false]) .call("check_access_rights", ["create", false])
.then(function (create_right) { .then(function (create_right) {
self.create_right = create_right; self.create_right = create_right;
@ -228,6 +233,7 @@ openerp.web_calendar = function(instance) {
self.ready.resolve(); self.ready.resolve();
}); });
}); });
return $.when(edit_check, init);
}, },
get_fc_init_options: function () { get_fc_init_options: function () {
@ -841,7 +847,11 @@ openerp.web_calendar = function(instance) {
if (! this.open_popup_action) { if (! this.open_popup_action) {
var index = this.dataset.get_id_index(id); var index = this.dataset.get_id_index(id);
this.dataset.index = index; this.dataset.index = index;
this.do_switch_view('form', null, { mode: "edit" }); if (this.write_right) {
this.do_switch_view('form', null, { mode: "edit" });
} else {
this.do_switch_view('form', null, { mode: "view" });
}
} }
else { else {
var pop = new instance.web.form.FormOpenPopup(this); var pop = new instance.web.form.FormOpenPopup(this);

View File

@ -469,7 +469,7 @@
} }
); );
}); });
menu.on('click', 'a[data-action!=ace]', function (event) { menu.on('click', 'a[data-view-id]', function (event) {
var view_id = $(event.currentTarget).data('view-id'); var view_id = $(event.currentTarget).data('view-id');
return openerp.jsonRpc('/web/dataset/call_kw', 'call', { return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
model: 'ir.ui.view', model: 'ir.ui.view',
@ -1516,7 +1516,7 @@
url: this.link url: this.link
}); });
this.media.renameNode("img"); this.media.renameNode("img");
this.media.$.attributes.src = this.link; $(this.media).attr('src', this.link);
return this._super(); return this._super();
}, },
clear: function () { clear: function () {
@ -2000,6 +2000,11 @@
// a/@href, ...) // a/@href, ...)
_(mutations).chain() _(mutations).chain()
.filter(function (m) { .filter(function (m) {
// ignore any SVG target, these blokes are like weird mon
if (m.target && m.target instanceof SVGElement) {
return false;
}
// ignore any change related to mundane image-edit-button // ignore any change related to mundane image-edit-button
if (m.target && m.target.className if (m.target && m.target.className
&& m.target.className.indexOf('image-edit-button') !== -1) { && m.target.className.indexOf('image-edit-button') !== -1) {

View File

@ -20,7 +20,7 @@
if (!window.location.origin) { // fix for ie9 if (!window.location.origin) { // fix for ie9
window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: ''); window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
} }
document.getElementById("mobile-viewport").src = window.location.origin + window.location.pathname + "#mobile-preview"; document.getElementById("mobile-viewport").src = window.location.origin + window.location.pathname + window.location.search + "#mobile-preview";
this.$el.modal(); this.$el.modal();
}, },
destroy: function () { destroy: function () {

View File

@ -120,7 +120,7 @@ class WebsiteForum(http.Controller):
question_count = Post.search(cr, uid, domain, count=True, context=context) question_count = Post.search(cr, uid, domain, count=True, context=context)
if tag: if tag:
url = "/forum/%s/%s/questions" % (slug(forum), slug(tag)) url = "/forum/%s/tag/%s/questions" % (slug(forum), slug(tag))
else: else:
url = "/forum/%s" % slug(forum) url = "/forum/%s" % slug(forum)

View File

@ -148,7 +148,7 @@ function IsKarmaValid(eventNumber,minKarma){
CKEDITOR.tools.callFunction(eventNumber,this); CKEDITOR.tools.callFunction(eventNumber,this);
return false; return false;
} else { } else {
alert("Sorry you need more than 30 Karma."); alert("Sorry you need more than " + minKarma + " Karma.");
} }
} }

View File

@ -92,9 +92,8 @@ class WebsiteMail(http.Controller):
values['is_follower'] = len( values['is_follower'] = len(
request.registry['mail.followers'].search( request.registry['mail.followers'].search(
cr, SUPERUSER_ID, [ cr, SUPERUSER_ID, [
('res_model', '=', 'mail.group'), ('res_model', '=', model),
('res_id', '=', obj_ids[0]), ('res_id', '=', obj_ids[0]),
('partner_id', '=', partner_id.id) ('partner_id', '=', partner_id.id)
], context=context)) == 1 ], context=context)) == 1
return values return values

View File

@ -0,0 +1,68 @@
(function () {
'use strict';
var website = openerp.website;
website.snippet.animationRegistry.follow = website.snippet.Animation.extend({
selector: ".js_follow",
start: function (editable_mode) {
var self = this;
this.is_user = false;
openerp.jsonRpc('/website_mail/is_follower', 'call', {
model: this.$target.data('object'),
id: this.$target.data('id'),
}).always(function (data) {
self.is_user = data.is_user;
self.email = data.email;
self.toggle_subscription(data.is_follower, data.email);
self.$target.removeClass("hidden");
});
// not if editable mode to allow designer to edit alert field
if (!editable_mode) {
$('.js_follow > .alert').addClass("hidden");
$('.js_follow > .input-group-btn.hidden').removeClass("hidden");
this.$target.find('.js_follow_btn, .js_unfollow_btn').on('click', function (event) {
event.preventDefault();
self.on_click();
});
}
return;
},
on_click: function () {
var self = this;
var $email = this.$target.find(".js_follow_email");
if ($email.length && !$email.val().match(/.+@.+/)) {
this.$target.addClass('has-error');
return false;
}
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);
});
},
toggle_subscription: function(follow, email) {
console.log(follow, email);
if (follow) {
this.$target.find(".js_follow_btn").addClass("hidden");
this.$target.find(".js_unfollow_btn").removeClass("hidden");
}
else {
this.$target.find(".js_follow_btn").removeClass("hidden");
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);
this.$target.attr("data-follow", follow ? 'on' : 'off');
},
});
})();

View File

@ -21,6 +21,7 @@
<template id="head" inherit_id="website.assets_frontend" name="Mail customization"> <template id="head" inherit_id="website.assets_frontend" name="Mail customization">
<xpath expr="/t" position="inside"> <xpath expr="/t" position="inside">
<script type="text/javascript" src="/website_mail/static/src/js/follow.js"></script>
<link rel='stylesheet' href='/website_mail/static/src/css/website_mail.css'/> <link rel='stylesheet' href='/website_mail/static/src/css/website_mail.css'/>
</xpath> </xpath>
</template> </template>

View File

@ -1 +1,2 @@
import controllers import controllers
import models

View File

@ -1,8 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import datetime import datetime
from dateutil import relativedelta
from openerp import tools from openerp import tools, SUPERUSER_ID
from openerp.addons.web import http from openerp.addons.web import http
from openerp.addons.website.models.website import slug from openerp.addons.website.models.website import slug
from openerp.addons.web.http import request from openerp.addons.web.http import request
@ -28,12 +29,23 @@ class MailGroup(http.Controller):
def view(self, **post): def view(self, **post):
cr, uid, context = request.cr, request.uid, request.context cr, uid, context = request.cr, request.uid, request.context
group_obj = request.registry.get('mail.group') group_obj = request.registry.get('mail.group')
mail_message_obj = request.registry.get('mail.message')
group_ids = group_obj.search(cr, uid, [('alias_id', '!=', False), ('alias_id.alias_name', '!=', False)], context=context) group_ids = group_obj.search(cr, uid, [('alias_id', '!=', False), ('alias_id.alias_name', '!=', False)], context=context)
values = {'groups': group_obj.browse(cr, uid, group_ids, context)} groups = group_obj.browse(cr, uid, group_ids, context)
# compute statistics
month_date = datetime.datetime.today() - relativedelta.relativedelta(months=1)
group_data = dict.fromkeys(group_ids, dict())
for group in groups:
group_data[group.id]['monthly_message_nbr'] = mail_message_obj.search(
cr, SUPERUSER_ID,
[('model', '=', 'mail.group'), ('res_id', '=', group.id), ('date', '>=', month_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT))],
count=True, context=context)
values = {'groups': groups, 'group_data': group_data}
return request.website.render('website_mail_group.mail_groups', values) return request.website.render('website_mail_group.mail_groups', values)
@http.route(["/groups/subscription/"], type='json', auth="user") @http.route(["/groups/subscription/"], type='json', auth="user")
def subscription(self, group_id=0, action=False, **post): def subscription(self, group_id=0, action=False, **post):
""" TDE FIXME: seems dead code """
cr, uid, context = request.cr, request.uid, request.context cr, uid, context = request.cr, request.uid, request.context
group_obj = request.registry.get('mail.group') group_obj = request.registry.get('mail.group')
if action: if action:

View File

@ -0,0 +1 @@
import mail_group

View File

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from openerp.osv import osv
class MailGroup(osv.Model):
_inherit = 'mail.group'
def message_get_email_values(self, cr, uid, id, notif_mail=None, context=None):
res = super(MailGroup, self).message_get_email_values(cr, uid, id, notif_mail=notif_mail, context=context)
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-Subscribe': '<%s/groups>' % (base_url),
'List-Unsubscribe': '<%s/groups>' % (base_url),
})
return res

View File

@ -3,8 +3,8 @@
var website = openerp.website; var website = openerp.website;
website.snippet.animationRegistry.follow = website.snippet.Animation.extend({ website.snippet.animationRegistry.follow_alias = website.snippet.Animation.extend({
selector: ".js_follow", selector: ".js_follow_alias",
start: function (editable_mode) { start: function (editable_mode) {
var self = this; var self = this;
this.is_user = false; this.is_user = false;
@ -23,8 +23,8 @@
// not if editable mode to allow designer to edit alert field // not if editable mode to allow designer to edit alert field
if (!editable_mode) { if (!editable_mode) {
$('.js_follow > .alert').addClass("hidden"); $('.js_follow_alias > .alert').addClass("hidden");
$('.js_follow > .input-group-btn.hidden').removeClass("hidden"); $('.js_follow_alias > .input-group-btn.hidden').removeClass("hidden");
this.$target.find('.js_follow_btn, .js_unfollow_btn').on('click', function (event) { this.$target.find('.js_follow_btn, .js_unfollow_btn').on('click', function (event) {
event.preventDefault(); event.preventDefault();
self.on_click(); self.on_click();

View File

@ -11,7 +11,7 @@
<span class="oe_snippet_thumbnail_title">Discussion Group</span> <span class="oe_snippet_thumbnail_title">Discussion Group</span>
</div> </div>
<div class="oe_snippet_body js_follow" <div class="oe_snippet_body js_follow_alias"
data-id="0" data-id="0"
data-object="mail.group" data-object="mail.group"
data-follow="off"> data-follow="off">
@ -38,7 +38,7 @@
<xpath expr="//div[@id='snippet_options']" position="inside"> <xpath expr="//div[@id='snippet_options']" position="inside">
<div data-snippet-option-id='subscribe' <div data-snippet-option-id='subscribe'
data-selector=".js_follow" data-selector=".js_follow_alias"
data-selector-siblings="p, h1, h2, h3, blockquote, .well, .panel" data-selector-siblings="p, h1, h2, h3, blockquote, .well, .panel"
> >
<li> <li>

View File

@ -45,7 +45,7 @@
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
<i class='fa fa-user'/> <t t-esc="len(group.message_follower_ids)"/> participants<br /> <i class='fa fa-user'/> <t t-esc="len(group.message_follower_ids)"/> participants<br />
<i class='fa fa-envelope-o'/> <t t-esc="len(group.message_ids)"/> messages <i class='fa fa-envelope-o'/> <t t-raw="group_data[group.id]['monthly_message_nbr']"/> messages / month
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<t t-call="website_mail.follow"><t t-set="object" t-value="group"/></t> <t t-call="website_mail.follow"><t t-set="object" t-value="group"/></t>

View File

@ -55,7 +55,7 @@ class table_compute(object):
self.table[(pos/PPR)+y2][(pos%PPR)+x2] = False self.table[(pos/PPR)+y2][(pos%PPR)+x2] = False
self.table[pos/PPR][pos%PPR] = { self.table[pos/PPR][pos%PPR] = {
'product': p, 'x':x, 'y': y, 'product': p, 'x':x, 'y': y,
'class': " ".join(map(lambda x: x.html_class, p.website_style_ids)) 'class': " ".join(map(lambda x: x.html_class or '', p.website_style_ids))
} }
if index<=PPG: if index<=PPG:
maxy=max(maxy,y+(pos/PPR)) maxy=max(maxy,y+(pos/PPR))
@ -169,7 +169,7 @@ class website_sale(http.Controller):
values = { values = {
'search': search, 'search': search,
'category': category and int(category), 'category': category,
'attrib_values': attrib_values, 'attrib_values': attrib_values,
'attrib_set': attrib_set, 'attrib_set': attrib_set,
'pager': pager, 'pager': pager,

View File

@ -240,7 +240,7 @@
<!-- Add to cart button--> <!-- Add to cart button-->
<template id="categories_recursive" name="Category list"> <template id="categories_recursive" name="Category list">
<li t-att-class="'active' if c.id == category else ''"> <li t-att-class="'active' if c.id == int(category or 0) else ''">
<a t-att-href="keep('/shop/category/' + slug(c), category=0)" t-field="c.name"></a> <a t-att-href="keep('/shop/category/' + slug(c), category=0)" t-field="c.name"></a>
<ul t-if="c.child_id" class="nav nav-pills nav-stacked nav-hierarchy"> <ul t-if="c.child_id" class="nav nav-pills nav-stacked nav-hierarchy">
<t t-foreach="c.child_id" t-as="c"> <t t-foreach="c.child_id" t-as="c">

View File

@ -491,6 +491,8 @@ class ir_actions_server(osv.osv):
"based on the sequence. Low number means high priority."), "based on the sequence. Low number means high priority."),
'model_id': fields.many2one('ir.model', 'Base Model', required=True, ondelete='cascade', 'model_id': fields.many2one('ir.model', 'Base Model', required=True, ondelete='cascade',
help="Base model on which the server action runs."), help="Base model on which the server action runs."),
'model_name': fields.related('model_id', 'model', type='char',
string='Model Name', readonly=True),
'menu_ir_values_id': fields.many2one('ir.values', 'More Menu entry', readonly=True, 'menu_ir_values_id': fields.many2one('ir.values', 'More Menu entry', readonly=True,
help='More menu entry.'), help='More menu entry.'),
# Client Action # Client Action
@ -650,6 +652,10 @@ class ir_actions_server(osv.osv):
'wkf_field_id': False, 'wkf_field_id': False,
'crud_model_id': model_id, 'crud_model_id': model_id,
} }
if model_id:
values['model_name'] = self.pool.get('ir.model').browse(cr, uid, model_id, context).model
return {'value': values} return {'value': values}
def on_change_wkf_wonfig(self, cr, uid, ids, use_relational_model, wkf_field_id, wkf_model_id, model_id, context=None): def on_change_wkf_wonfig(self, cr, uid, ids, use_relational_model, wkf_field_id, wkf_model_id, model_id, context=None):
@ -753,6 +759,7 @@ class ir_actions_server(osv.osv):
crud_model_name = False crud_model_name = False
if crud_model_id: if crud_model_id:
crud_model_name = self.pool.get('ir.model').browse(cr, uid, crud_model_id, context).model crud_model_name = self.pool.get('ir.model').browse(cr, uid, crud_model_id, context).model
values = {'link_field_id': False, 'crud_model_name': crud_model_name} values = {'link_field_id': False, 'crud_model_name': crud_model_name}
return {'value': values} return {'value': values}

View File

@ -349,8 +349,9 @@
Check to attach the newly created record to the record on which the server action runs. Check to attach the newly created record to the record on which the server action runs.
</p> </p>
<group> <group>
<field name="model_name" invisible="1"/>
<field name="link_field_id" <field name="link_field_id"
domain="[('model_id', '=', model_id), ('relation', '=', crud_model_name), ('ttype', 'in', ['many2one'])]" domain="[('model_id', '=', crud_model_id), ('relation', '=', model_name), ('ttype', 'in', ['many2one'])]"
attrs="{'required': [('state', '=', 'object_create'), ('link_new_record', '=', True)], attrs="{'required': [('state', '=', 'object_create'), ('link_new_record', '=', True)],
'invisible': ['|', ('state', '!=', 'object_create'), ('link_new_record', '=', False)]}"/> 'invisible': ['|', ('state', '!=', 'object_create'), ('link_new_record', '=', False)]}"/>
</group> </group>

View File

@ -561,7 +561,7 @@ class many2one(_column):
# we use uid=1 because the visibility of a many2one field value (just id and name) # we use uid=1 because the visibility of a many2one field value (just id and name)
# must be the access right of the parent form and not the linked object itself. # must be the access right of the parent form and not the linked object itself.
records = dict(obj.name_get(cr, SUPERUSER_ID, records = dict(obj.name_get(cr, SUPERUSER_ID,
list(set([x for x in res.values() if isinstance(x, (int,long))])), list(set([x for x in res.values() if x and isinstance(x, (int,long))])),
context=context)) context=context))
for id in res: for id in res:
if res[id] in records: if res[id] in records:

View File

@ -3348,6 +3348,8 @@ class BaseModel(object):
return [] return []
if fields_to_read is None: if fields_to_read is None:
fields_to_read = self._columns.keys() fields_to_read = self._columns.keys()
else:
fields_to_read = list(set(fields_to_read))
# all inherited fields + all non inherited fields for which the attribute whose name is in load is True # all inherited fields + all non inherited fields for which the attribute whose name is in load is True
fields_pre = [f for f in fields_to_read if fields_pre = [f for f in fields_to_read if