[MERGE]:trunk
bzr revid: apa@tinyerp.com-20121030045711-c88fjivi8qwek8m8
This commit is contained in:
commit
139bd09fd9
|
@ -39,7 +39,6 @@ class account_analytic_line(osv.osv):
|
|||
}
|
||||
|
||||
_defaults = {
|
||||
'date': fields.date.context_today,
|
||||
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.analytic.line', context=c),
|
||||
}
|
||||
_order = 'date desc'
|
||||
|
|
|
@ -865,8 +865,11 @@ class account_invoice(osv.osv):
|
|||
self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj)
|
||||
|
||||
# I disabled the check_total feature
|
||||
#if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
|
||||
# raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
|
||||
group_check_total_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'group_supplier_inv_check_total')[1]
|
||||
group_check_total = self.pool.get('res.groups').browse(cr, uid, group_check_total_id, context=context)
|
||||
if group_check_total and uid in [x.id for x in group_check_total.users]:
|
||||
if (inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0)):
|
||||
raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe encoded total does not match the computed total.'))
|
||||
|
||||
if inv.payment_term:
|
||||
total_fixed = total_percent = 0
|
||||
|
|
|
@ -186,6 +186,7 @@
|
|||
<field name="journal_id" groups="account.group_account_user"
|
||||
on_change="onchange_journal_id(journal_id, context)" widget="selection"/>
|
||||
<field name="currency_id" groups="base.group_multi_currency"/>
|
||||
<field name="check_total" groups="account.group_supplier_inv_check_total"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
|
@ -279,8 +280,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
@ -436,8 +437,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<group col="4">
|
||||
<field name="date1"/>
|
||||
<field name="date2"/>
|
||||
<field name="analytic_account_journal_id" widget="many2many_tags" class="oe_inline" required="1"/>
|
||||
<field name="analytic_account_journal_id" widget="many2many_tags" class="oe_inline" required="1" colspan="4"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="check_report" string="Print" type="object" class="oe_highlight"/>
|
||||
|
|
|
@ -120,6 +120,8 @@ class account_config_settings(osv.osv_memory):
|
|||
'group_analytic_accounting': fields.boolean('Analytic accounting',
|
||||
implied_group='analytic.group_analytic_accounting',
|
||||
help="Allows you to use the analytic accounting."),
|
||||
'group_check_supplier_invoice_total': fields.boolean('Check the total of supplier invoices',
|
||||
implied_group="account.group_supplier_inv_check_total"),
|
||||
}
|
||||
|
||||
def _default_company(self, cr, uid, context=None):
|
||||
|
|
|
@ -220,6 +220,10 @@
|
|||
<field name="module_account_check_writing" class="oe_inline"/>
|
||||
<label for="module_account_check_writing"/>
|
||||
</div>
|
||||
<div>
|
||||
<field name="group_check_supplier_invoice_total" class="oe_inline"/>
|
||||
<label for="group_check_supplier_invoice_total"/>
|
||||
</div>
|
||||
</div>
|
||||
</group>
|
||||
<separator string="Bank & Cash"/>
|
||||
|
|
|
@ -26,6 +26,11 @@
|
|||
<field name="category_id" ref="base.module_category_hidden"/>
|
||||
</record>
|
||||
|
||||
<record id="group_supplier_inv_check_total" model="res.groups">
|
||||
<field name="name">Check Total on supplier invoices</field>
|
||||
<field name="category_id" ref="base.module_category_hidden"/>
|
||||
</record>
|
||||
|
||||
<record id="account_move_comp_rule" model="ir.rule">
|
||||
<field name="name">Account Entry</field>
|
||||
<field name="model_id" ref="model_account_move"/>
|
||||
|
|
|
@ -988,11 +988,11 @@ class account_voucher(osv.osv):
|
|||
if amount_residual > 0:
|
||||
account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
|
||||
if not account_id:
|
||||
raise osv.except_osv(_('Warning!'),_("First you have to configure the 'Expense Currency Rate' on the company, then create accounting entry for currency rate difference."))
|
||||
raise osv.except_osv(_('Insufficient Configuration!'),_("You should configure the 'Loss Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates."))
|
||||
else:
|
||||
account_id = line.voucher_id.company_id.income_currency_exchange_account_id
|
||||
if not account_id:
|
||||
raise osv.except_osv(_('Warning!'),_("First you have to configure the 'Income Currency Rate' on the company, then create accounting entry for currency rate difference."))
|
||||
raise osv.except_osv(_('Insufficient Configuration!'),_("You should configure the 'Gain Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates."))
|
||||
# Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
|
||||
# the receivable/payable account may have a secondary currency, which render this field mandatory
|
||||
account_currency_id = company_currency <> current_currency and current_currency or False
|
||||
|
|
|
@ -109,8 +109,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -240,8 +240,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
@ -512,8 +512,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -147,8 +147,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
@ -303,8 +303,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -96,9 +96,29 @@ class account_analytic_account(osv.osv):
|
|||
res[row['id']][field] = row[field]
|
||||
return self._compute_level_tree(cr, uid, ids, child_ids, res, fields, context)
|
||||
|
||||
def _complete_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
|
||||
res = self.name_get(cr, uid, ids)
|
||||
return dict(res)
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
res = []
|
||||
for id in ids:
|
||||
elmt = self.browse(cr, uid, id, context=context)
|
||||
res.append((id, self._get_one_full_name(elmt)))
|
||||
return res
|
||||
|
||||
def _get_full_name(self, cr, uid, ids, name=None, args=None, context=None):
|
||||
if context == None:
|
||||
context = {}
|
||||
res = {}
|
||||
for elmt in self.browse(cr, uid, ids, context=context):
|
||||
res[elmt.id] = self._get_one_full_name(elmt)
|
||||
return res
|
||||
|
||||
def _get_one_full_name(self, elmt, level=6):
|
||||
if level<=0:
|
||||
return '...'
|
||||
if elmt.parent_id:
|
||||
parent_path = self._get_one_full_name(elmt.parent_id, level-1) + "/"
|
||||
else:
|
||||
parent_path = ''
|
||||
return parent_path + elmt.name
|
||||
|
||||
def _child_compute(self, cr, uid, ids, name, arg, context=None):
|
||||
result = {}
|
||||
|
@ -139,7 +159,7 @@ class account_analytic_account(osv.osv):
|
|||
|
||||
_columns = {
|
||||
'name': fields.char('Account/Contract Name', size=128, required=True),
|
||||
'complete_name': fields.function(_complete_name_calc, type='char', string='Full Account Name'),
|
||||
'complete_name': fields.function(_get_full_name, type='char', string='Full Account Name'),
|
||||
'code': fields.char('Reference', size=24, select=True),
|
||||
'type': fields.selection([('view','Analytic View'), ('normal','Analytic Account'),('contract','Contract or Project'),('template','Template of Contract')], 'Type of Account', required=True,
|
||||
help="If you select the View Type, it means you won\'t allow to create journal entries using that account.\n"\
|
||||
|
@ -305,8 +325,15 @@ class account_analytic_line(osv.osv):
|
|||
'company_id': fields.related('account_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
|
||||
|
||||
}
|
||||
|
||||
def _get_default_date(self, cr, uid, context=None):
|
||||
return fields.date.context_today(self, cr, uid, context=context)
|
||||
|
||||
def __get_default_date(self, cr, uid, context=None):
|
||||
return self._get_default_date(cr, uid, context=context)
|
||||
|
||||
_defaults = {
|
||||
'date': lambda *a: time.strftime('%Y-%m-%d'),
|
||||
'date': __get_default_date,
|
||||
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.analytic.line', context=c),
|
||||
'amount': 0.00
|
||||
}
|
||||
|
|
|
@ -56,8 +56,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import getpass
|
||||
import urllib
|
||||
|
||||
import werkzeug.urls
|
||||
|
@ -43,7 +44,16 @@ from .. import utils
|
|||
_logger = logging.getLogger(__name__)
|
||||
oidutil.log = _logger.debug
|
||||
|
||||
_storedir = os.path.join(tempfile.gettempdir(), 'openerp-auth_openid-store')
|
||||
def get_system_user():
|
||||
"""Return system user info string, such as USERNAME-EUID"""
|
||||
info = getpass.getuser()
|
||||
euid = getattr(os, 'geteuid', None) # Non available on some platforms
|
||||
if euid is not None:
|
||||
info = '%s-%d' % (info, euid())
|
||||
return info
|
||||
|
||||
_storedir = os.path.join(tempfile.gettempdir(),
|
||||
'openerp-auth_openid-%s-store' % get_system_user())
|
||||
|
||||
class GoogleAppsAwareConsumer(consumer.GenericConsumer):
|
||||
def complete(self, message, endpoint, return_to):
|
||||
|
|
|
@ -1151,7 +1151,7 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
context = {}
|
||||
|
||||
result = []
|
||||
for data in super(calendar_event, self).read(cr, uid, select, context=context):
|
||||
for data in super(calendar_event, self).read(cr, uid, select, ['rrule', 'exdate', 'exrule', 'date'], context=context):
|
||||
if not data['rrule']:
|
||||
result.append(data['id'])
|
||||
continue
|
||||
|
|
|
@ -219,8 +219,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -223,8 +223,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
@ -525,8 +525,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -150,8 +150,8 @@
|
|||
<field name="description" placeholder="Description..."/>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -132,8 +132,8 @@
|
|||
</page>
|
||||
</notebook>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers" help="Followers of this salesteam follow automatically all opportunities related to this salesteam."/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -179,8 +179,8 @@
|
|||
</group>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -165,11 +165,8 @@ class test_message_compose(test_mail.TestMailMockups):
|
|||
self.assertEqual(message_pigs.body, _body_html1, 'mail.message body on Pigs incorrect')
|
||||
self.assertEqual(message_bird.body, _body_html2, 'mail.message body on Bird incorrect')
|
||||
# Test: partner_ids: p_a_id (default) + 3 newly created partners
|
||||
message_pigs_pids = [partner.id for partner in message_pigs.partner_ids]
|
||||
message_bird_pids = [partner.id for partner in message_bird.partner_ids]
|
||||
message_pigs_pids = [partner.id for partner in message_pigs.notified_partner_ids]
|
||||
message_bird_pids = [partner.id for partner in message_bird.notified_partner_ids]
|
||||
partner_ids = self.res_partner.search(cr, uid, [('email', 'in', ['b@b.b', 'c@c.c', 'd@d.d'])])
|
||||
self.assertEqual(len(message_pigs_pids), len(partner_ids), 'mail.message on pigs incorrect number of partner_ids')
|
||||
self.assertEqual(set(message_pigs_pids), set(partner_ids), 'mail.message on pigs incorrect number of partner_ids')
|
||||
|
||||
self.assertEqual(len(message_bird_pids), len(partner_ids), 'mail.message on bird partner_ids incorrect')
|
||||
self.assertEqual(set(message_bird_pids), set(partner_ids), 'mail.message on bird partner_ids incorrect')
|
||||
self.assertEqual(set(message_pigs_pids), set(partner_ids), 'mail.message on pigs incorrect number of notified_partner_ids')
|
||||
self.assertEqual(set(message_bird_pids), set(partner_ids), 'mail.message on bird notified_partner_ids incorrect')
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<field name="inherit_id" ref="mail.email_compose_message_wizard_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//form/notebook" position="after">
|
||||
<xpath expr="//form/group" position="after">
|
||||
<group attrs="{'invisible':[('use_template','=',False)]}">
|
||||
<field name="use_template" invisible="1"
|
||||
on_change="onchange_use_template(use_template, template_id, composition_mode, model, res_id, context)"/>
|
||||
|
|
|
@ -204,8 +204,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
@ -472,8 +472,8 @@
|
|||
</group>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -359,8 +359,8 @@
|
|||
</div>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -42,7 +42,7 @@ class hr_config_settings(osv.osv_memory):
|
|||
help ="""This installs the module hr_contract."""),
|
||||
'module_hr_evaluation': fields.boolean('Organize employees periodic evaluation',
|
||||
help ="""This installs the module hr_evaluation."""),
|
||||
'module_account_analytic_analysis': fields.boolean('Allow invoicing based on timesheets (will install the sale application)',
|
||||
'module_account_analytic_analysis': fields.boolean('Allow invoicing based on timesheets (the sale application will be installed)',
|
||||
help ="""This installs the module account_analytic_analysis, which will install sales management too."""),
|
||||
'module_hr_payroll': fields.boolean('Manage payroll',
|
||||
help ="""This installs the module hr_payroll."""),
|
||||
|
|
|
@ -210,8 +210,8 @@
|
|||
</group>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -139,8 +139,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -121,8 +121,8 @@
|
|||
</group>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
@ -158,8 +158,8 @@
|
|||
<field name="notes" nolabel="1" colspan="4" placeholder="Add a reason..."/>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -183,8 +183,8 @@
|
|||
<field name="description" placeholder="Feedback of interviews..."/>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -79,7 +79,7 @@ class hr_timesheet_sheet(osv.osv):
|
|||
if not new_user_id:
|
||||
raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must assign it to a user.'))
|
||||
if not self._sheet_date(cr, uid, ids, forced_user_id=new_user_id):
|
||||
raise osv.except_osv(_('Error!'), _('You cannot have 2 timesheets that overlaps!\nYou should use the menu \'My Timesheet\' to avoid this problem.'))
|
||||
raise osv.except_osv(_('Error!'), _('You cannot have 2 timesheets that overlap!\nYou should use the menu \'My Timesheet\' to avoid this problem.'))
|
||||
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).product_id:
|
||||
raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must link the employee to a product.'))
|
||||
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).journal_id:
|
||||
|
@ -187,7 +187,7 @@ class hr_timesheet_sheet(osv.osv):
|
|||
|
||||
|
||||
_constraints = [
|
||||
(_sheet_date, 'You cannot have 2 timesheets that overlaps !\nPlease use the menu \'My Current Timesheet\' to avoid this problem.', ['date_from','date_to']),
|
||||
(_sheet_date, 'You cannot have 2 timesheets that overlap!\nPlease use the menu \'My Current Timesheet\' to avoid this problem.', ['date_from','date_to']),
|
||||
]
|
||||
|
||||
def action_set_to_draft(self, cr, uid, ids, *args):
|
||||
|
@ -223,16 +223,26 @@ class hr_timesheet_sheet(osv.osv):
|
|||
|
||||
hr_timesheet_sheet()
|
||||
|
||||
|
||||
class hr_timesheet_line(osv.osv):
|
||||
_inherit = "hr.analytic.timesheet"
|
||||
class account_analytic_line(osv.osv):
|
||||
_inherit = "account.analytic.line"
|
||||
|
||||
def _get_default_date(self, cr, uid, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if 'date' in context:
|
||||
return context['date']
|
||||
return time.strftime('%Y-%m-%d')
|
||||
#get the default date (should be: today)
|
||||
res = super(account_analytic_line, self)._get_default_date(cr, uid, context=context)
|
||||
#if we got the dates from and to from the timesheet and if the default date is in between, we use the default
|
||||
#but if the default isn't included in those dates, we use the date start of the timesheet as default
|
||||
if context.get('timesheet_date_from') and context.get('timesheet_date_to'):
|
||||
if context['timesheet_date_from'] <= res <= context['timesheet_date_to']:
|
||||
return res
|
||||
return context.get('timesheet_date_from')
|
||||
#if we don't get the dates from the timesheet, we return the default value from super()
|
||||
return res
|
||||
|
||||
|
||||
class hr_timesheet_line(osv.osv):
|
||||
_inherit = "hr.analytic.timesheet"
|
||||
|
||||
def _sheet(self, cursor, user, ids, name, args, context=None):
|
||||
sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
|
||||
|
@ -278,9 +288,6 @@ class hr_timesheet_line(osv.osv):
|
|||
},
|
||||
),
|
||||
}
|
||||
_defaults = {
|
||||
'date': _get_default_date,
|
||||
}
|
||||
|
||||
def _check_sheet_state(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
|
|
|
@ -103,7 +103,7 @@
|
|||
</widget>
|
||||
</page>
|
||||
<page string="Details">
|
||||
<field context="{'user_id':user_id}" name="timesheet_ids" nolabel="1">
|
||||
<field context="{'user_id':user_id, 'timesheet_date_from': date_from, 'timesheet_date_to': date_to}" name="timesheet_ids" nolabel="1">
|
||||
<tree editable="top" string="Timesheet Activities">
|
||||
<field name="date"/>
|
||||
<field domain="[('type','in',['normal', 'contract']), ('state', '<>', 'close'),('use_timesheets','=',1)]" name="account_id" on_change="on_change_account_id(account_id, user_id)" context="{'default_use_timesheets': 1}"/>
|
||||
|
@ -303,18 +303,21 @@
|
|||
</record>
|
||||
|
||||
<act_window
|
||||
context="{'search_default_sheet_id': [active_id]}"
|
||||
id="act_hr_timesheet_sheet_sheet_by_account"
|
||||
name="Timesheet by Account"
|
||||
res_model="hr_timesheet_sheet.sheet.account"
|
||||
src_model="hr_timesheet_sheet.sheet"/>
|
||||
context="{'search_default_sheet_id': [active_id]}"
|
||||
id="act_hr_timesheet_sheet_sheet_by_account"
|
||||
name="Timesheet by Account"
|
||||
groups="base.group_hr_attendance"
|
||||
res_model="hr_timesheet_sheet.sheet.account"
|
||||
src_model="hr_timesheet_sheet.sheet"/>
|
||||
|
||||
<act_window
|
||||
context="{'search_default_sheet_id': [active_id]}"
|
||||
id="act_hr_timesheet_sheet_sheet_by_day"
|
||||
name="Timesheet by Day"
|
||||
res_model="hr_timesheet_sheet.sheet.day"
|
||||
src_model="hr_timesheet_sheet.sheet"/>
|
||||
context="{'search_default_sheet_id': [active_id]}"
|
||||
id="act_hr_timesheet_sheet_sheet_by_day"
|
||||
name="Timesheet by Day"
|
||||
groups="base.group_hr_attendance"
|
||||
res_model="hr_timesheet_sheet.sheet.day"
|
||||
src_model="hr_timesheet_sheet.sheet"/>
|
||||
|
||||
<record id="hr_timesheet_sheet_tree_simplified" model="ir.ui.view">
|
||||
<field name="name">hr.timesheet.sheet.tree</field>
|
||||
<field name="model">hr_timesheet_sheet.sheet</field>
|
||||
|
|
|
@ -79,8 +79,8 @@
|
|||
<field name="description"/>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -84,7 +84,6 @@ Main Features
|
|||
'css': [
|
||||
'static/src/css/mail.css',
|
||||
'static/src/css/mail_group.css',
|
||||
'static/src/css/mail_compose_message.css',
|
||||
],
|
||||
'js': [
|
||||
'static/lib/jquery.expander/jquery.expander.js',
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<field name="body">Your monthly meal vouchers arrived. You can get them at Christine's office.
|
||||
This month you also get 250 EUR of eco-vouchers if you have been in the company for more than a year.</field>
|
||||
<field name="type">comment</field>
|
||||
<field name="subtype_id" ref="mt_comment"/>
|
||||
</record>
|
||||
|
||||
<record id="message_blogpost0_comment0" model="mail.message">
|
||||
|
@ -16,6 +17,7 @@ This month you also get 250 EUR of eco-vouchers if you have been in the company
|
|||
<field name="body"><![CDATA[Great.]]></field>
|
||||
<field name="parent_id" ref="message_blogpost0"/>
|
||||
<field name="type">comment</field>
|
||||
<field name="subtype_id" ref="mt_comment"/>
|
||||
</record>
|
||||
|
||||
<record id="message_blogpost0_comment1" model="mail.message">
|
||||
|
@ -24,22 +26,25 @@ This month you also get 250 EUR of eco-vouchers if you have been in the company
|
|||
<field name="body">Thanks, but where is Christine's office, if I may ask? (I'm new here)</field>
|
||||
<field name="parent_id" ref="message_blogpost0"/>
|
||||
<field name="type">comment</field>
|
||||
<field name="subtype_id" ref="mt_comment"/>
|
||||
</record>
|
||||
|
||||
<record id="message_blogpost0_comment2" model="mail.message">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="group_all_employees"/>
|
||||
<field name="body">Building B3, second floor on the right :-)</field>
|
||||
<field name="parent_id" ref="message_blogpost0"/>
|
||||
<field name="type">comment</field>
|
||||
<field name="subtype_id" ref="mt_comment"/>
|
||||
</record>
|
||||
|
||||
<record id="message_blogpost0_comment3" model="mail.message">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="group_all_employees"/>
|
||||
<field name="body">Great news, I need to buy a new fridge, I think I can pay it with the eco-vouchers!</field>
|
||||
<field name="parent_id" ref="message_blogpost0"/>
|
||||
<field name="type">comment</field>
|
||||
</record>
|
||||
|
||||
<record id="message_blogpost0_comment1_2" model="mail.message">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="group_all_employees"/>
|
||||
<field name="body">Building B3, second floor on the right :-)</field>
|
||||
<field name="parent_id" ref="message_blogpost0_comment1"/>
|
||||
<field name="type">comment</field>
|
||||
<field name="subtype_id" ref="mt_comment"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
|
|
|
@ -84,9 +84,16 @@ class mail_notification(osv.Model):
|
|||
return False
|
||||
|
||||
def set_message_read(self, cr, uid, msg_ids, read=None, context=None):
|
||||
""" TDE note: add a comment, verify method calls, because js seems obfuscated. """
|
||||
""" Set a message and its child messages as (un)read for uid.
|
||||
|
||||
:param bool read: read / unread
|
||||
"""
|
||||
# TDE note: use child_of or front-end send correct values ?
|
||||
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
||||
notif_ids = self.search(cr, uid, [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)], context=context)
|
||||
notif_ids = self.search(cr, uid, [
|
||||
('partner_id', '=', user_pid),
|
||||
('message_id', 'in', msg_ids)
|
||||
], context=context)
|
||||
|
||||
# all message have notifications: already set them as (un)read
|
||||
if len(notif_ids) == len(msg_ids):
|
||||
|
@ -130,7 +137,8 @@ class mail_notification(osv.Model):
|
|||
|
||||
def _notify(self, cr, uid, msg_id, context=None):
|
||||
""" Send by email the notification depending on the user preferences """
|
||||
context = context or {}
|
||||
if context is None:
|
||||
context = {}
|
||||
# mail_noemail (do not send email) or no partner_ids: do not send, return
|
||||
if context.get('mail_noemail'):
|
||||
return True
|
||||
|
@ -140,9 +148,15 @@ class mail_notification(osv.Model):
|
|||
if not notify_partner_ids:
|
||||
return True
|
||||
|
||||
# add the context in the email
|
||||
# TDE FIXME: commented, to be improved in a future branch
|
||||
# quote_context = self.pool.get('mail.message').message_quote_context(cr, uid, msg_id, context=context)
|
||||
|
||||
mail_mail = self.pool.get('mail.mail')
|
||||
# add signature
|
||||
body_html = msg.body
|
||||
# if quote_context:
|
||||
# body_html = tools.append_content_to_html(body_html, quote_context, plaintext=False)
|
||||
signature = msg.author_id and msg.author_id.user_ids[0].signature or ''
|
||||
if signature:
|
||||
body_html = tools.append_content_to_html(body_html, signature)
|
||||
|
|
|
@ -82,9 +82,8 @@
|
|||
</group>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"
|
||||
options='{"thread_level": 1}'/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread" options='{"thread_level": 1}'/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -24,11 +24,17 @@ import tools
|
|||
|
||||
from email.header import decode_header
|
||||
from openerp import SUPERUSER_ID
|
||||
from osv import osv, orm, fields
|
||||
from tools.translate import _
|
||||
from openerp.osv import osv, orm, fields
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from mako.template import Template as MakoTemplate
|
||||
except ImportError:
|
||||
_logger.warning("payment_acquirer: mako templates not available, payment acquirer will not work!")
|
||||
|
||||
|
||||
""" Some tools for parsing / creating email fields """
|
||||
def decode(text):
|
||||
"""Returns unicode() string conversion of the the given encoded smtp header text"""
|
||||
|
@ -46,7 +52,7 @@ class mail_message(osv.Model):
|
|||
_order = 'id desc'
|
||||
|
||||
_message_read_limit = 10
|
||||
_message_read_fields = ['id', 'parent_id', 'model', 'res_id', 'body', 'subject', 'date', 'to_read',
|
||||
_message_read_fields = ['id', 'parent_id', 'model', 'res_id', 'body', 'subject', 'date', 'to_read', 'email_from',
|
||||
'type', 'vote_user_ids', 'attachment_ids', 'author_id', 'partner_ids', 'record_name', 'favorite_user_ids']
|
||||
_message_record_name_length = 18
|
||||
_message_read_more_limit = 1024
|
||||
|
@ -57,17 +63,14 @@ class mail_message(osv.Model):
|
|||
return name[:self._message_record_name_length] + '...'
|
||||
|
||||
def _get_record_name(self, cr, uid, ids, name, arg, context=None):
|
||||
""" Return the related document name, using name_get. It is included in
|
||||
a try/except statement, because if uid cannot read the related
|
||||
document, he should see a void string instead of crashing. """
|
||||
""" Return the related document name, using name_get. It is done using
|
||||
SUPERUSER_ID, to be sure to have the record name correctly stored. """
|
||||
# TDE note: regroup by model/ids, to have less queries to perform
|
||||
result = dict.fromkeys(ids, False)
|
||||
for message in self.read(cr, uid, ids, ['model', 'res_id'], context=context):
|
||||
if not message['model'] or not message['res_id']:
|
||||
if not message.get('model') or not message.get('res_id'):
|
||||
continue
|
||||
try:
|
||||
result[message['id']] = self._shorten_name(self.pool.get(message['model']).name_get(cr, uid, [message['res_id']], context=context)[0][1])
|
||||
except (orm.except_orm, osv.except_osv):
|
||||
pass
|
||||
result[message['id']] = self._shorten_name(self.pool.get(message['model']).name_get(cr, SUPERUSER_ID, [message['res_id']], context=context)[0][1])
|
||||
return result
|
||||
|
||||
def _get_to_read(self, cr, uid, ids, name, arg, context=None):
|
||||
|
@ -78,10 +81,10 @@ class mail_message(osv.Model):
|
|||
notif_ids = notif_obj.search(cr, uid, [
|
||||
('partner_id', 'in', [partner_id]),
|
||||
('message_id', 'in', ids),
|
||||
('read', '=', False)
|
||||
('read', '=', False),
|
||||
], context=context)
|
||||
for notif in notif_obj.browse(cr, uid, notif_ids, context=context):
|
||||
res[notif.message_id.id] = not notif.read
|
||||
res[notif.message_id.id] = True
|
||||
return res
|
||||
|
||||
def _search_to_read(self, cr, uid, obj, name, domain, context=None):
|
||||
|
@ -115,16 +118,21 @@ class mail_message(osv.Model):
|
|||
], 'Type',
|
||||
help="Message type: email for email message, notification for system "\
|
||||
"message, comment for other messages such as user replies"),
|
||||
'author_id': fields.many2one('res.partner', 'Author', required=True),
|
||||
'partner_ids': fields.many2many('res.partner', 'mail_notification', 'message_id', 'partner_id', 'Recipients'),
|
||||
'email_from': fields.char('From',
|
||||
help="Email address of the sender. This field is set when no matching partner is found for incoming emails."),
|
||||
'author_id': fields.many2one('res.partner', 'Author',
|
||||
help="Author of the message. If not set, email_from may hold an email address that did not match any partner."),
|
||||
'partner_ids': fields.many2many('res.partner', string='Recipients'),
|
||||
'notified_partner_ids': fields.many2many('res.partner', 'mail_notification',
|
||||
'message_id', 'partner_id', 'Recipients'),
|
||||
'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel',
|
||||
'message_id', 'attachment_id', 'Attachments'),
|
||||
'parent_id': fields.many2one('mail.message', 'Parent Message', select=True, ondelete='set null', help="Initial thread message."),
|
||||
'child_ids': fields.one2many('mail.message', 'parent_id', 'Child Messages'),
|
||||
'model': fields.char('Related Document Model', size=128, select=1),
|
||||
'res_id': fields.integer('Related Document ID', select=1),
|
||||
'record_name': fields.function(_get_record_name, type='string',
|
||||
string='Message Record Name',
|
||||
'record_name': fields.function(_get_record_name, type='char',
|
||||
store=True, string='Message Record Name',
|
||||
help="Name get of the related document."),
|
||||
'notification_ids': fields.one2many('mail.notification', 'message_id', 'Notifications'),
|
||||
'subject': fields.char('Subject'),
|
||||
|
@ -198,19 +206,33 @@ class mail_message(osv.Model):
|
|||
|
||||
:param dict message: read result of a mail.message
|
||||
"""
|
||||
# TDE note: this method should be optimized, to lessen the number of queries, will be done ASAP
|
||||
is_author = False
|
||||
if message['author_id']:
|
||||
is_author = message['author_id'][0] == self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
|
||||
author_id = message['author_id']
|
||||
elif message['email_from']:
|
||||
author_id = (0, message['email_from'])
|
||||
|
||||
has_voted = False
|
||||
if uid in message['vote_user_ids']:
|
||||
if uid in message.get('vote_user_ids'):
|
||||
has_voted = True
|
||||
|
||||
is_favorite = False
|
||||
if uid in message['favorite_user_ids']:
|
||||
if uid in message.get('favorite_user_ids'):
|
||||
is_favorite = True
|
||||
|
||||
is_private = True
|
||||
if message.get('model') and message.get('res_id'):
|
||||
is_private = False
|
||||
|
||||
try:
|
||||
attachment_ids = [{'id': attach[0], 'name': attach[1]} for attach in self.pool.get('ir.attachment').name_get(cr, uid, message['attachment_ids'], context=context)]
|
||||
except (orm.except_orm, osv.except_osv):
|
||||
attachment_ids = []
|
||||
|
||||
# TDE note: should we send partner_ids ?
|
||||
# TDE note: shouldn't we separated followers and other partners ? costly to compute maybe ,
|
||||
try:
|
||||
partner_ids = self.pool.get('res.partner').name_get(cr, uid, message['partner_ids'], context=context)
|
||||
except (orm.except_orm, osv.except_osv):
|
||||
|
@ -226,95 +248,106 @@ class mail_message(osv.Model):
|
|||
'record_name': message['record_name'],
|
||||
'subject': message['subject'],
|
||||
'date': message['date'],
|
||||
'author_id': message['author_id'],
|
||||
'is_author': message['author_id'] and message['author_id'][0] == uid,
|
||||
# TDE note: is this useful ? to check
|
||||
'author_id': author_id,
|
||||
'is_author': is_author,
|
||||
'partner_ids': partner_ids,
|
||||
'parent_id': message['parent_id'] and message['parent_id'][0] or False,
|
||||
# TDE note: see with CHM about votes, how they are displayed (only number, or name_get ?)
|
||||
# vote: should only use number of votes
|
||||
'parent_id': False,
|
||||
'vote_nb': len(message['vote_user_ids']),
|
||||
'has_voted': has_voted,
|
||||
'is_private': message['model'] and message['res_id'],
|
||||
'is_private': is_private,
|
||||
'is_favorite': is_favorite,
|
||||
'to_read': message['to_read'],
|
||||
}
|
||||
|
||||
def _message_read_expandable(self, cr, uid, message_list, read_messages,
|
||||
message_loaded_ids=[], domain=[], context=None, parent_id=False, limit=None):
|
||||
""" Create the expandable message for all parent message read
|
||||
this function is used by message_read
|
||||
def _message_read_add_expandables(self, cr, uid, message_list, read_messages,
|
||||
thread_level=0, message_loaded_ids=[], domain=[], parent_id=False, context=None, limit=None):
|
||||
""" Create expandables for message_read, to load new messages.
|
||||
1. get the expandable for new threads
|
||||
if display is flat (thread_level == 0):
|
||||
fetch message_ids < min(already displayed ids), because we
|
||||
want a flat display, ordered by id
|
||||
else:
|
||||
fetch message_ids that are not childs of already displayed
|
||||
messages
|
||||
2. get the expandables for new messages inside threads if display
|
||||
is not flat
|
||||
for each thread header, search for its childs
|
||||
for each hole in the child list based on message displayed,
|
||||
create an expandable
|
||||
|
||||
:param list message_list: list of messages given by message_read to
|
||||
which we have to add expandables
|
||||
:param list message_list:list of message structure for the Chatter
|
||||
widget to which expandables are added
|
||||
:param dict read_messages: dict [id]: read result of the messages to
|
||||
easily have access to their values, given their ID
|
||||
:return bool: True
|
||||
"""
|
||||
# sort for group items / TDE: move to message_read
|
||||
# result = sorted(result, key=lambda k: k['id'])
|
||||
tree_not = []
|
||||
# expandable for not show message
|
||||
def _get_expandable(domain, message_nb, parent_id, id, model):
|
||||
return {
|
||||
'domain': domain,
|
||||
'nb_messages': message_nb,
|
||||
'type': 'expandable',
|
||||
'parent_id': parent_id,
|
||||
'id': id,
|
||||
# TDE note: why do we need model sometimes, and sometimes not ???
|
||||
'model': model,
|
||||
}
|
||||
|
||||
# all_not_loaded_ids = []
|
||||
id_list = sorted(read_messages.keys())
|
||||
if not id_list:
|
||||
return message_list
|
||||
|
||||
# 1. get the expandable for new threads
|
||||
if thread_level == 0:
|
||||
exp_domain = domain + [('id', '<', min(message_loaded_ids + id_list))]
|
||||
else:
|
||||
exp_domain = domain + ['!', ('id', 'child_of', message_loaded_ids + id_list)]
|
||||
ids = self.search(cr, uid, exp_domain, context=context, limit=1)
|
||||
if ids:
|
||||
message_list.append(_get_expandable(exp_domain, -1, parent_id, -1, None))
|
||||
|
||||
# 2. get the expandables for new messages inside threads if display is not flat
|
||||
if thread_level == 0:
|
||||
return True
|
||||
for message_id in id_list:
|
||||
message = read_messages[message_id]
|
||||
|
||||
# message is not a thread header (has a parent_id)
|
||||
# TDE note: parent_id is false is there is a parent we can not see -> ok
|
||||
if message.get('parent_id'):
|
||||
continue
|
||||
|
||||
# TDE note: check search is correctly implemented in mail.message
|
||||
not_loaded_ids = self.search(cr, uid, [
|
||||
('parent_id', '=', message['id']),
|
||||
('id', 'child_of', message['id']),
|
||||
('id', 'not in', message_loaded_ids),
|
||||
], context=context, limit=self._message_read_more_limit)
|
||||
# group childs not read
|
||||
id_min = None
|
||||
id_max = None
|
||||
nb = 0
|
||||
if not not_loaded_ids:
|
||||
continue
|
||||
|
||||
# all_not_loaded_ids += not_loaded_ids
|
||||
# group childs not read
|
||||
id_min, id_max, nb = max(not_loaded_ids), 0, 0
|
||||
for not_loaded_id in not_loaded_ids:
|
||||
if not read_messages.get(not_loaded_id):
|
||||
nb += 1
|
||||
if id_min == None or id_min > not_loaded_id:
|
||||
if id_min > not_loaded_id:
|
||||
id_min = not_loaded_id
|
||||
if id_max == None or id_max < not_loaded_id:
|
||||
if id_max < not_loaded_id:
|
||||
id_max = not_loaded_id
|
||||
tree_not.append(not_loaded_id)
|
||||
elif nb > 0:
|
||||
exp_domain = [('id', '>=', id_min), ('id', '<=', id_max), ('id', 'child_of', message_id)]
|
||||
message_list.append(_get_expandable(exp_domain, nb, message_id, id_min, message.get('model')))
|
||||
id_min, id_max, nb = max(not_loaded_ids), 0, 0
|
||||
else:
|
||||
if nb > 0:
|
||||
message_list.append({
|
||||
'domain': [('id', '>=', id_min), ('id', '<=', id_max), ('parent_id', '=', message_id)],
|
||||
'nb_messages': nb,
|
||||
'type': 'expandable',
|
||||
'parent_id': message_id,
|
||||
'id': id_min,
|
||||
'model': message['model']
|
||||
})
|
||||
id_min = None
|
||||
id_max = None
|
||||
nb = 0
|
||||
id_min, id_max, nb = max(not_loaded_ids), 0, 0
|
||||
if nb > 0:
|
||||
message_list.append({
|
||||
'domain': [('id', '>=', id_min), ('id', '<=', id_max), ('parent_id', '=', message_id)],
|
||||
'nb_messages': nb,
|
||||
'type': 'expandable',
|
||||
'parent_id': message_id,
|
||||
'id': id_min,
|
||||
'model': message['model'],
|
||||
})
|
||||
exp_domain = [('id', '>=', id_min), ('id', '<=', id_max), ('id', 'child_of', message_id)]
|
||||
message_list.append(_get_expandable(exp_domain, nb, message_id, id_min, message.get('model')))
|
||||
|
||||
for msg_id in read_messages.keys() + tree_not:
|
||||
message_loaded_ids.append(msg_id)
|
||||
# message_loaded_ids = list(set(message_loaded_ids + read_messages.keys() + all_not_loaded_ids))
|
||||
|
||||
# expandable for limit max
|
||||
ids = self.search(cr, uid, domain + [('id', 'not in', message_loaded_ids)], context=context, limit=1)
|
||||
if len(ids) > 0:
|
||||
message_list.append({
|
||||
'domain': domain,
|
||||
'nb_messages': 0,
|
||||
'type': 'expandable',
|
||||
'parent_id': parent_id,
|
||||
'id': -1,
|
||||
'max_limit': True,
|
||||
})
|
||||
|
||||
return message_list
|
||||
return True
|
||||
|
||||
def _get_parent(self, cr, uid, message, context=None):
|
||||
""" Tools method that tries to get the parent of a mail.message. If
|
||||
|
@ -331,57 +364,70 @@ class mail_message(osv.Model):
|
|||
except (orm.except_orm, osv.except_osv):
|
||||
return False
|
||||
|
||||
def message_read(self, cr, uid, ids=False, domain=[], message_loaded_ids=[], context=None, parent_id=False, limit=None):
|
||||
""" Read messages from mail.message, and get back a structured tree
|
||||
of messages to be displayed as discussion threads. If IDs is set,
|
||||
def message_read(self, cr, uid, ids=None, domain=None, message_unload_ids=None, thread_level=0, context=None, parent_id=False, limit=None):
|
||||
""" Read messages from mail.message, and get back a list of structured
|
||||
messages to be displayed as discussion threads. If IDs is set,
|
||||
fetch these records. Otherwise use the domain to fetch messages.
|
||||
After having fetch messages, their parents & child will be added to obtain
|
||||
well formed threads.
|
||||
After having fetch messages, their ancestors will be added to obtain
|
||||
well formed threads, if uid has access to them.
|
||||
|
||||
TDE note: update this comment after final method implementation
|
||||
After reading the messages, expandable messages are added in the
|
||||
message list (see ``_message_read_add_expandables``). It consists
|
||||
in messages holding the 'read more' data: number of messages to
|
||||
read, domain to apply.
|
||||
|
||||
:param domain: optional domain for searching ids
|
||||
:param limit: number of messages to fetch
|
||||
:param parent_id: if parent_id reached, stop searching for
|
||||
further parents
|
||||
:return list: list of trees of messages
|
||||
:param list ids: optional IDs to fetch
|
||||
:param list domain: optional domain for searching ids if ids not set
|
||||
:param list message_unload_ids: optional ids we do not want to fetch,
|
||||
because i.e. they are already displayed somewhere
|
||||
:param int parent_id: context of parent_id
|
||||
- if parent_id reached when adding ancestors, stop going further
|
||||
in the ancestor search
|
||||
- if set in flat mode, ancestor_id is set to parent_id
|
||||
:param int limit: number of messages to fetch, before adding the
|
||||
ancestors and expandables
|
||||
:return list: list of message structure for the Chatter widget
|
||||
"""
|
||||
if message_loaded_ids:
|
||||
domain += [('id', 'not in', message_loaded_ids)]
|
||||
# print 'message_read', ids, domain, message_unload_ids, thread_level, context, parent_id, limit
|
||||
assert thread_level in [0, 1], 'message_read() thread_level should be 0 (flat) or 1 (1 level of thread); given %s.' % thread_level
|
||||
domain = domain if domain is not None else []
|
||||
message_unload_ids = message_unload_ids if message_unload_ids is not None else []
|
||||
if message_unload_ids:
|
||||
domain += [('id', 'not in', message_unload_ids)]
|
||||
limit = limit or self._message_read_limit
|
||||
read_messages = {}
|
||||
message_list = []
|
||||
|
||||
# specific IDs given: fetch those ids and return directly the message list
|
||||
if ids:
|
||||
for message in self.read(cr, uid, ids, self._message_read_fields, context=context):
|
||||
message_list.append(self._message_get_dict(cr, uid, message, context=context))
|
||||
message_list = sorted(message_list, key=lambda k: k['id'])
|
||||
return message_list
|
||||
|
||||
# TDE FIXME: check access rights on search are implemented for mail.message
|
||||
# fetch messages according to the domain, add their parents if uid has access to
|
||||
ids = self.search(cr, uid, domain, context=context, limit=limit)
|
||||
# no specific IDS given: fetch messages according to the domain, add their parents if uid has access to
|
||||
if ids is None:
|
||||
ids = self.search(cr, uid, domain, context=context, limit=limit)
|
||||
for message in self.read(cr, uid, ids, self._message_read_fields, context=context):
|
||||
# if not in tree and not in message_loded list
|
||||
if not read_messages.get(message.get('id')) and message.get('id') not in message_loaded_ids:
|
||||
read_messages[message.get('id')] = message
|
||||
message_id = message['id']
|
||||
|
||||
# if not in tree and not in message_loaded list
|
||||
if not message_id in read_messages and not message_id in message_unload_ids:
|
||||
read_messages[message_id] = message
|
||||
message_list.append(self._message_get_dict(cr, uid, message, context=context))
|
||||
|
||||
# get all parented message if the user have the access
|
||||
# get the older ancestor the user can read, update its ancestor field
|
||||
if not thread_level:
|
||||
message_list[-1]['parent_id'] = parent_id
|
||||
continue
|
||||
parent = self._get_parent(cr, uid, message, context=context)
|
||||
while parent and parent.get('id') != parent_id:
|
||||
if not read_messages.get(parent.get('id')) and parent.get('id') not in message_loaded_ids:
|
||||
read_messages[parent.get('id')] = parent
|
||||
message_list.append(self._message_get_dict(cr, uid, parent, context=context))
|
||||
parent = self._get_parent(cr, uid, parent, context=context)
|
||||
message_list[-1]['parent_id'] = parent.get('id')
|
||||
message = parent
|
||||
parent = self._get_parent(cr, uid, message, context=context)
|
||||
# if in thread: add its ancestor to the list of messages
|
||||
if not message['id'] in read_messages and not message['id'] in message_unload_ids:
|
||||
read_messages[message['id']] = message
|
||||
message_list.append(self._message_get_dict(cr, uid, message, context=context))
|
||||
|
||||
# get the child expandable messages for the tree
|
||||
message_list = sorted(message_list, key=lambda k: k['id'])
|
||||
message_list = self._message_read_expandable(cr, uid, message_list, read_messages,
|
||||
message_loaded_ids=message_loaded_ids, domain=domain, context=context, parent_id=parent_id, limit=limit)
|
||||
self._message_read_add_expandables(cr, uid, message_list, read_messages, thread_level=thread_level,
|
||||
message_loaded_ids=message_unload_ids, domain=domain, parent_id=parent_id, context=context, limit=limit)
|
||||
|
||||
# message_list = sorted(message_list, key=lambda k: k['id'])
|
||||
return message_list
|
||||
|
||||
# TDE Note: do we need this ?
|
||||
|
@ -394,7 +440,7 @@ class mail_message(osv.Model):
|
|||
# return attachment_list
|
||||
|
||||
#------------------------------------------------------
|
||||
# Email api
|
||||
# mail_message internals
|
||||
#------------------------------------------------------
|
||||
|
||||
def init(self, cr):
|
||||
|
@ -402,23 +448,75 @@ class mail_message(osv.Model):
|
|||
if not cr.fetchone():
|
||||
cr.execute("""CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id)""")
|
||||
|
||||
def _search(self, cr, uid, args, offset=0, limit=None, order=None,
|
||||
context=None, count=False, access_rights_uid=None):
|
||||
""" Override that adds specific access rights of mail.message, to remove
|
||||
ids uid could not see according to our custom rules. Please refer
|
||||
to check_access_rule for more details about those rules.
|
||||
|
||||
After having received ids of a classic search, keep only:
|
||||
- if author_id == pid, uid is the author, OR
|
||||
- a notification (id, pid) exists, uid has been notified, OR
|
||||
- uid have read access to the related document is model, res_id
|
||||
- otherwise: remove the id
|
||||
"""
|
||||
# Rules do not apply to administrator
|
||||
# print '_search', uid, args
|
||||
if uid == SUPERUSER_ID:
|
||||
return super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
|
||||
context=context, count=count, access_rights_uid=access_rights_uid)
|
||||
# Perform a super with count as False, to have the ids, not a counter
|
||||
ids = super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
|
||||
context=context, count=False, access_rights_uid=access_rights_uid)
|
||||
if not ids and count:
|
||||
return 0
|
||||
elif not ids:
|
||||
return ids
|
||||
|
||||
pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'])['partner_id'][0]
|
||||
author_ids, partner_ids, allowed_ids = set([]), set([]), set([])
|
||||
model_ids = {}
|
||||
|
||||
messages = super(mail_message, self).read(cr, uid, ids, ['author_id', 'model', 'res_id', 'notified_partner_ids'], context=context)
|
||||
for message in messages:
|
||||
if message.get('author_id') and message.get('author_id')[0] == pid:
|
||||
author_ids.add(message.get('id'))
|
||||
elif pid in message.get('notified_partner_ids'):
|
||||
partner_ids.add(message.get('id'))
|
||||
elif message.get('model') and message.get('res_id'):
|
||||
model_ids.setdefault(message.get('model'), {}).setdefault(message.get('res_id'), set()).add(message.get('id'))
|
||||
|
||||
model_access_obj = self.pool.get('ir.model.access')
|
||||
for doc_model, doc_dict in model_ids.iteritems():
|
||||
if not model_access_obj.check(cr, uid, doc_model, 'read', False):
|
||||
continue
|
||||
doc_ids = doc_dict.keys()
|
||||
allowed_doc_ids = self.pool.get(doc_model).search(cr, uid, [('id', 'in', doc_ids)], context=context)
|
||||
allowed_ids |= set([message_id for allowed_doc_id in allowed_doc_ids for message_id in doc_dict[allowed_doc_id]])
|
||||
|
||||
final_ids = author_ids | partner_ids | allowed_ids
|
||||
if count:
|
||||
return len(final_ids)
|
||||
else:
|
||||
return list(final_ids)
|
||||
|
||||
def check_access_rule(self, cr, uid, ids, operation, context=None):
|
||||
""" Access rules of mail.message:
|
||||
- read: if
|
||||
- notification exist (I receive pushed message) OR
|
||||
- author_id = pid (I am the author) OR
|
||||
- I can read the related document if res_model, res_id
|
||||
- Otherwise: raise
|
||||
- author_id == pid, uid is the author, OR
|
||||
- mail_notification (id, pid) exists, uid has been notified, OR
|
||||
- uid have read access to the related document if model, res_id
|
||||
- otherwise: raise
|
||||
- create: if
|
||||
- I am in the document message_follower_ids OR
|
||||
- I can write on the related document if res_model, res_id OR
|
||||
- I create a private message (no model, no res_id)
|
||||
- Otherwise: raise
|
||||
- no model, no res_id, I create a private message
|
||||
- pid in message_follower_ids if model, res_id OR
|
||||
- uid have write access on the related document if model, res_id, OR
|
||||
- otherwise: raise
|
||||
- write: if
|
||||
- I can write on the related document if res_model, res_id
|
||||
- uid has write access on the related document if model, res_id
|
||||
- Otherwise: raise
|
||||
- unlink: if
|
||||
- I can write on the related document if res_model, res_id
|
||||
- uid has write access on the related document if model, res_id
|
||||
- Otherwise: raise
|
||||
"""
|
||||
if uid == SUPERUSER_ID:
|
||||
|
@ -428,15 +526,25 @@ class mail_message(osv.Model):
|
|||
partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
|
||||
|
||||
# Read mail_message.ids to have their values
|
||||
model_record_ids = {}
|
||||
message_values = dict.fromkeys(ids)
|
||||
model_record_ids = {}
|
||||
cr.execute('SELECT DISTINCT id, model, res_id, author_id FROM "%s" WHERE id = ANY (%%s)' % self._table, (ids,))
|
||||
for id, rmod, rid, author_id in cr.fetchall():
|
||||
message_values[id] = {'res_model': rmod, 'res_id': rid, 'author_id': author_id}
|
||||
if rmod:
|
||||
model_record_ids.setdefault(rmod, set()).add(rid)
|
||||
model_record_ids.setdefault(rmod, dict()).setdefault(rid, set()).add(id)
|
||||
|
||||
# Read: Check for received notifications -> could become an ir.rule, but not till we do not have a many2one variable field
|
||||
# Author condition, for read and create (private message) -> could become an ir.rule, but not till we do not have a many2one variable field
|
||||
if operation == 'read':
|
||||
author_ids = [mid for mid, message in message_values.iteritems()
|
||||
if message.get('author_id') and message.get('author_id') == partner_id]
|
||||
elif operation == 'create':
|
||||
author_ids = [mid for mid, message in message_values.iteritems()
|
||||
if not message.get('model') and not message.get('res_id')]
|
||||
else:
|
||||
author_ids = []
|
||||
|
||||
# Notification condition, for read (check for received notifications and create (in message_follower_ids)) -> could become an ir.rule, but not till we do not have a many2one variable field
|
||||
if operation == 'read':
|
||||
not_obj = self.pool.get('mail.notification')
|
||||
not_ids = not_obj.search(cr, SUPERUSER_ID, [
|
||||
|
@ -444,38 +552,24 @@ class mail_message(osv.Model):
|
|||
('message_id', 'in', ids),
|
||||
], context=context)
|
||||
notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, SUPERUSER_ID, not_ids, context=context)]
|
||||
else:
|
||||
notified_ids = []
|
||||
# Read: Check messages you are author -> could become an ir.rule, but not till we do not have a many2one variable field
|
||||
if operation == 'read':
|
||||
author_ids = [mid for mid, message in message_values.iteritems()
|
||||
if message.get('author_id') and message.get('author_id') == partner_id]
|
||||
# Create: Check messages you create that are private messages -> ir.rule ?
|
||||
elif operation == 'create':
|
||||
author_ids = [mid for mid, message in message_values.iteritems()
|
||||
if not message.get('model') and not message.get('res_id')]
|
||||
else:
|
||||
author_ids = []
|
||||
|
||||
# Create: Check message_follower_ids
|
||||
if operation == 'create':
|
||||
doc_follower_ids = []
|
||||
for model, mids in model_record_ids.items():
|
||||
notified_ids = []
|
||||
for doc_model, doc_dict in model_record_ids.items():
|
||||
fol_obj = self.pool.get('mail.followers')
|
||||
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [
|
||||
('res_model', '=', model),
|
||||
('res_id', 'in', list(mids)),
|
||||
('res_model', '=', doc_model),
|
||||
('res_id', 'in', list(doc_dict.keys())),
|
||||
('partner_id', '=', partner_id),
|
||||
], context=context)
|
||||
fol_mids = [follower.res_id for follower in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context)]
|
||||
doc_follower_ids += [mid for mid, message in message_values.iteritems()
|
||||
if message.get('res_model') == model and message.get('res_id') in fol_mids]
|
||||
notified_ids += [mid for mid, message in message_values.iteritems()
|
||||
if message.get('res_model') == doc_model and message.get('res_id') in fol_mids]
|
||||
else:
|
||||
doc_follower_ids = []
|
||||
notified_ids = []
|
||||
|
||||
# Calculate remaining ids, and related model/res_ids
|
||||
model_record_ids = {}
|
||||
other_ids = set(ids).difference(set(notified_ids), set(author_ids), set(doc_follower_ids))
|
||||
other_ids = set(ids).difference(set(author_ids), set(notified_ids))
|
||||
for id in other_ids:
|
||||
if message_values[id]['res_model']:
|
||||
model_record_ids.setdefault(message_values[id]['res_model'], set()).add(message_values[id]['res_id'])
|
||||
|
@ -495,7 +589,7 @@ class mail_message(osv.Model):
|
|||
if message.get('res_model') == model and message.get('res_id') in mids]
|
||||
|
||||
# Calculate remaining ids: if not void, raise an error
|
||||
other_ids = set(ids).difference(set(notified_ids), set(author_ids), set(doc_follower_ids), set(document_related_ids))
|
||||
other_ids = other_ids - set(document_related_ids)
|
||||
if not other_ids:
|
||||
return
|
||||
raise orm.except_orm(_('Access Denied'),
|
||||
|
@ -529,50 +623,123 @@ class mail_message(osv.Model):
|
|||
self.pool.get('ir.attachment').unlink(cr, uid, attachments_to_delete, context=context)
|
||||
return super(mail_message, self).unlink(cr, uid, ids, context=context)
|
||||
|
||||
def _notify_followers(self, cr, uid, newid, message, context=None):
|
||||
""" Add the related record followers to the destination partner_ids.
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
""" Overridden to avoid duplicating fields that are unique to each email """
|
||||
if default is None:
|
||||
default = {}
|
||||
default.update(message_id=False, headers=False)
|
||||
return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
|
||||
|
||||
#------------------------------------------------------
|
||||
# Messaging API
|
||||
#------------------------------------------------------
|
||||
|
||||
# TDE note: this code is not used currently, will be improved in a future merge, when quoted context
|
||||
# will be added to email send for notifications. Currently only WIP.
|
||||
MAIL_TEMPLATE = """<div>
|
||||
% if message:
|
||||
${display_message(message)}
|
||||
% endif
|
||||
% for ctx_msg in context_messages:
|
||||
${display_message(ctx_msg)}
|
||||
% endfor
|
||||
% if add_expandable:
|
||||
${display_expandable()}
|
||||
% endif
|
||||
${display_message(header_message)}
|
||||
</div>
|
||||
|
||||
<%def name="display_message(message)">
|
||||
<div>
|
||||
Subject: ${message.subject}<br />
|
||||
Body: ${message.body}
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="display_expandable()">
|
||||
<div>This is an expandable.</div>
|
||||
</%def>
|
||||
"""
|
||||
|
||||
def message_quote_context(self, cr, uid, id, context=None, limit=3, add_original=False):
|
||||
"""
|
||||
partners_to_notify = set([])
|
||||
# message has no subtype_id: pure log message -> no partners, no one notified
|
||||
if not message.subtype_id:
|
||||
message.write({'partner_ids': [5]})
|
||||
return True
|
||||
# all partner_ids of the mail.message have to be notified
|
||||
if message.partner_ids:
|
||||
partners_to_notify |= set(partner.id for partner in message.partner_ids)
|
||||
# all followers of the mail.message document have to be added as partners and notified
|
||||
if message.model and message.res_id:
|
||||
fol_obj = self.pool.get("mail.followers")
|
||||
fol_ids = fol_obj.search(cr, uid, [('res_model', '=', message.model), ('res_id', '=', message.res_id), ('subtype_ids', 'in', message.subtype_id.id)], context=context)
|
||||
fol_objs = fol_obj.browse(cr, uid, fol_ids, context=context)
|
||||
extra_notified = set(fol.partner_id.id for fol in fol_objs)
|
||||
missing_notified = extra_notified - partners_to_notify
|
||||
if missing_notified:
|
||||
self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(4, p_id) for p_id in missing_notified]}, context=context)
|
||||
1. message.parent_id = False: new thread, no quote_context
|
||||
2. get the lasts messages in the thread before message
|
||||
3. get the message header
|
||||
4. add an expandable between them
|
||||
|
||||
:param dict quote_context: options for quoting
|
||||
:return string: html quote
|
||||
"""
|
||||
add_expandable = False
|
||||
|
||||
message = self.browse(cr, uid, id, context=context)
|
||||
if not message.parent_id:
|
||||
return ''
|
||||
context_ids = self.search(cr, uid, [
|
||||
('parent_id', '=', message.parent_id.id),
|
||||
('id', '<', message.id),
|
||||
], limit=limit, context=context)
|
||||
|
||||
if len(context_ids) >= limit:
|
||||
add_expandable = True
|
||||
context_ids = context_ids[0:-1]
|
||||
|
||||
context_ids.append(message.parent_id.id)
|
||||
context_messages = self.browse(cr, uid, context_ids, context=context)
|
||||
header_message = context_messages.pop()
|
||||
|
||||
try:
|
||||
if not add_original:
|
||||
message = False
|
||||
result = MakoTemplate(self.MAIL_TEMPLATE).render_unicode(message=message,
|
||||
context_messages=context_messages,
|
||||
header_message=header_message,
|
||||
add_expandable=add_expandable,
|
||||
# context kw would clash with mako internals
|
||||
ctx=context,
|
||||
format_exceptions=True)
|
||||
result = result.strip()
|
||||
return result
|
||||
except Exception:
|
||||
_logger.exception("failed to render mako template for quoting message")
|
||||
return ''
|
||||
return result
|
||||
|
||||
def _notify(self, cr, uid, newid, context=None):
|
||||
""" Add the related record followers to the destination partner_ids if is not a private message.
|
||||
Call mail_notification.notify to manage the email sending
|
||||
"""
|
||||
message = self.browse(cr, uid, newid, context=context)
|
||||
if message.model and message.res_id:
|
||||
self._notify_followers(cr, uid, newid, message, context=context)
|
||||
message = self.read(cr, uid, newid, ['model', 'res_id', 'author_id', 'subtype_id', 'partner_ids'], context=context)
|
||||
|
||||
# add myself if I wrote on my wall, otherwise remove myself author
|
||||
if ((message.model == "res.partner" and message.res_id == message.author_id.id)):
|
||||
self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(4, message.author_id.id)]}, context=context)
|
||||
else:
|
||||
self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(3, message.author_id.id)]}, context=context)
|
||||
partners_to_notify = set([])
|
||||
# message has no subtype_id: pure log message -> no partners, no one notified
|
||||
if not message.get('subtype_id'):
|
||||
return True
|
||||
# all partner_ids of the mail.message have to be notified
|
||||
if message.get('partner_ids'):
|
||||
partners_to_notify |= set(message.get('partner_ids'))
|
||||
# all followers of the mail.message document have to be added as partners and notified
|
||||
if message.get('model') and message.get('res_id'):
|
||||
fol_obj = self.pool.get("mail.followers")
|
||||
fol_ids = fol_obj.search(cr, uid, [
|
||||
('res_model', '=', message.get('model')),
|
||||
('res_id', '=', message.get('res_id')),
|
||||
('subtype_ids', 'in', message.get('subtype_id')[0])
|
||||
], context=context)
|
||||
fol_objs = fol_obj.read(cr, uid, fol_ids, ['partner_id'], context=context)
|
||||
partners_to_notify |= set(fol['partner_id'][0] for fol in fol_objs)
|
||||
# when writing to a wall
|
||||
if message.get('author_id') and message.get('model') == "res.partner" and message.get('res_id') == message.get('author_id')[0]:
|
||||
partners_to_notify |= set([message.get('author_id')[0]])
|
||||
elif message.get('author_id'):
|
||||
partners_to_notify = partners_to_notify - set([message.get('author_id')[0]])
|
||||
|
||||
if partners_to_notify:
|
||||
self.write(cr, SUPERUSER_ID, [newid], {'notified_partner_ids': [(4, p_id) for p_id in partners_to_notify]}, context=context)
|
||||
|
||||
self.pool.get('mail.notification')._notify(cr, uid, newid, context=context)
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
"""Overridden to avoid duplicating fields that are unique to each email"""
|
||||
if default is None:
|
||||
default = {}
|
||||
default.update(message_id=False, headers=False)
|
||||
return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
|
||||
|
||||
#------------------------------------------------------
|
||||
# Tools
|
||||
#------------------------------------------------------
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<group>
|
||||
<field name="subject"/>
|
||||
<field name="author_id"/>
|
||||
<field name="email_from"/>
|
||||
<field name="date"/>
|
||||
<field name="type"/>
|
||||
<field name="subtype_id"/>
|
||||
|
@ -38,6 +39,7 @@
|
|||
<field name="res_id"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="partner_ids" widget="many2many_tags"/>
|
||||
<field name="notified_partner_ids" widget="many2many_tags"/>
|
||||
</group>
|
||||
</group>
|
||||
<field name="body"/>
|
||||
|
@ -63,6 +65,9 @@
|
|||
<filter string="Comments"
|
||||
name="comments" help="Comments"
|
||||
domain="[('type', '=', 'comment')]"/>
|
||||
<filter string="Has attachments"
|
||||
name="attachments"
|
||||
domain="[('attachment_ids', '!=', False)]"/>
|
||||
<filter string="Notifications"
|
||||
name="notifications" help="Notifications"
|
||||
domain="[('type', '=', 'notification')]"/>
|
||||
|
|
|
@ -554,7 +554,11 @@ class mail_thread(osv.AbstractModel):
|
|||
('file2', 'bytes')}
|
||||
}
|
||||
"""
|
||||
msg_dict = {}
|
||||
msg_dict = {
|
||||
'type': 'email',
|
||||
'subtype': 'mail.mt_comment',
|
||||
'author_id': False,
|
||||
}
|
||||
if not isinstance(message, Message):
|
||||
if isinstance(message, unicode):
|
||||
# Warning: message_from_string doesn't always work correctly on unicode,
|
||||
|
@ -572,7 +576,7 @@ class mail_thread(osv.AbstractModel):
|
|||
if 'Subject' in message:
|
||||
msg_dict['subject'] = decode(message.get('Subject'))
|
||||
|
||||
# Envelope fields not stored in mail.message but made available for message_new()
|
||||
# Envelope fields not stored in mail.message but made available for message_new()
|
||||
msg_dict['from'] = decode(message.get('from'))
|
||||
msg_dict['to'] = decode(message.get('to'))
|
||||
msg_dict['cc'] = decode(message.get('cc'))
|
||||
|
@ -581,6 +585,8 @@ class mail_thread(osv.AbstractModel):
|
|||
author_ids = self._message_find_partners(cr, uid, message, ['From'], context=context)
|
||||
if author_ids:
|
||||
msg_dict['author_id'] = author_ids[0]
|
||||
else:
|
||||
msg_dict['email_from'] = message.get('from')
|
||||
partner_ids = self._message_find_partners(cr, uid, message, ['From', 'To', 'Cc'], context=context)
|
||||
msg_dict['partner_ids'] = partner_ids
|
||||
|
||||
|
@ -670,6 +676,18 @@ class mail_thread(osv.AbstractModel):
|
|||
if self._mail_flat_thread and not parent_id and thread_id:
|
||||
message_ids = mail_message.search(cr, uid, ['&', ('res_id', '=', thread_id), ('model', '=', model)], context=context, order="id ASC", limit=1)
|
||||
parent_id = message_ids and message_ids[0] or False
|
||||
# we want to set a parent: force to set the parent_id to the oldest ancestor, to avoid having more than 1 level of thread
|
||||
elif parent_id:
|
||||
message_ids = mail_message.search(cr, SUPERUSER_ID, [('id', '=', parent_id), ('parent_id', '!=', False)], context=context)
|
||||
# avoid loops when finding ancestors
|
||||
processed_list = []
|
||||
if message_ids:
|
||||
_counter, _counter_max = 0, 200
|
||||
message = mail_message.browse(cr, SUPERUSER_ID, message_ids[0], context=context)
|
||||
while (message.parent_id and message.parent_id.id not in processed_list):
|
||||
processed_list.append(message.parent_id.id)
|
||||
message = message.parent_id
|
||||
parent_id = message.id
|
||||
|
||||
values = kwargs
|
||||
values.update({
|
||||
|
@ -689,27 +707,28 @@ class mail_thread(osv.AbstractModel):
|
|||
|
||||
return mail_message.create(cr, uid, values, context=context)
|
||||
|
||||
def message_post_api(self, cr, uid, thread_id, body='', subject=False, type='notification',
|
||||
subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
|
||||
# TDE FIXME: body is plaintext: convert it into html
|
||||
# when writing on res.partner, without specific thread_id -> redirect to the user's partner
|
||||
if self._name == 'res.partner' and not thread_id:
|
||||
thread_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
||||
def message_post_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False, attachment_ids=None, context=None):
|
||||
""" Wrapper on message_post, used only in Chatter (JS). The purpose is
|
||||
to handle attachments.
|
||||
# TDE FIXME: body is plaintext: convert it into html
|
||||
"""
|
||||
new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type='comment',
|
||||
subtype='mail.mt_comment', parent_id=parent_id, context=context)
|
||||
|
||||
new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type=type,
|
||||
subtype=subtype, parent_id=parent_id, context=context)
|
||||
|
||||
# Chatter: attachments linked to the document (not done JS-side), load the message
|
||||
if attachments:
|
||||
# HACK FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
|
||||
if attachment_ids:
|
||||
ir_attachment = self.pool.get('ir.attachment')
|
||||
mail_message = self.pool.get('mail.message')
|
||||
attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [('res_model', '=', 'mail.message'), ('res_id', '=', 0), ('create_uid', '=', uid), ('id', 'in', attachments)], context=context)
|
||||
if attachment_ids:
|
||||
filtered_attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [
|
||||
('res_model', '=', 'mail.compose.message'),
|
||||
('res_id', '=', 0),
|
||||
('create_uid', '=', uid),
|
||||
('id', 'in', attachment_ids)], context=context)
|
||||
if filtered_attachment_ids:
|
||||
ir_attachment.write(cr, SUPERUSER_ID, attachment_ids, {'res_model': self._name, 'res_id': thread_id}, context=context)
|
||||
mail_message.write(cr, SUPERUSER_ID, [new_message_id], {'attachment_ids': [(6, 0, [pid for pid in attachment_ids])]}, context=context)
|
||||
|
||||
new_message = self.pool.get('mail.message').message_read(cr, uid, [new_message_id], context=context)
|
||||
return new_message
|
||||
return new_message_id
|
||||
|
||||
#------------------------------------------------------
|
||||
# Followers API
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<!-- MENU -->
|
||||
|
||||
<!-- Top menu item -->
|
||||
<menuitem name="Mails"
|
||||
<menuitem name="Emails"
|
||||
id="mail.mail_feeds_main"
|
||||
groups="base.group_user"
|
||||
sequence="10"/>
|
||||
|
|
|
@ -9,9 +9,8 @@
|
|||
<field name="arch" type="xml">
|
||||
<xpath expr="//sheet" position="after">
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"
|
||||
options='{"thread_level": 1}'/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread" options='{"thread_level": 1}'/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
|
|
|
@ -111,8 +111,7 @@ class res_users(osv.Model):
|
|||
alias_pool.unlink(cr, uid, alias_ids, context=context)
|
||||
return res
|
||||
|
||||
def message_post_api(self, cr, uid, thread_id, body='', subject=False, type='notification',
|
||||
subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
|
||||
def message_post_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False, attachment_ids=None, context=None):
|
||||
""" Redirect the posting of message on res.users to the related partner.
|
||||
This is done because when giving the context of Chatter on the
|
||||
various mailboxes, we do not have access to the current partner_id.
|
||||
|
@ -124,7 +123,7 @@ class res_users(osv.Model):
|
|||
thread_id = thread_id[0]
|
||||
partner_id = self.pool.get('res.users').read(cr, uid, thread_id, ['partner_id'], context=context)['partner_id'][0]
|
||||
return self.pool.get('res.partner').message_post_api(cr, uid, partner_id, body=body, subject=subject,
|
||||
type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, **kwargs)
|
||||
parent_id=parent_id, attachment_ids=attachment_ids, context=context)
|
||||
|
||||
def message_post(self, cr, uid, thread_id, context=None, **kwargs):
|
||||
""" Redirect the posting of message on res.users to the related partner.
|
||||
|
|
|
@ -2,6 +2,7 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
|||
access_mail_message_all,mail.message.all,model_mail_message,,1,0,1,0
|
||||
access_mail_message_group_user,mail.message.group.user,model_mail_message,base.group_user,1,1,1,1
|
||||
access_mail_mail_all,mail.mail.all,model_mail_mail,,0,0,1,0
|
||||
access_mail_mail_user,mail.mail,model_mail_mail,base.group_user,1,1,1,0
|
||||
access_mail_mail_system,mail.mail.system,model_mail_mail,base.group_system,1,1,1,1
|
||||
access_mail_followers_all,mail.followers.all,model_mail_followers,,1,0,0,0
|
||||
access_mail_followers_system,mail.followers.system,model_mail_followers,base.group_system,1,1,1,1
|
||||
|
@ -12,6 +13,6 @@ access_mail_group_user,mail.group.user,model_mail_group,base.group_user,1,1,1,1
|
|||
access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0
|
||||
access_mail_alias_user,mail.alias,model_mail_alias,base.group_user,1,1,1,0
|
||||
access_mail_alias_system,mail.alias,model_mail_alias,base.group_system,1,1,1,1
|
||||
access_mail_message_subtype,mail.message.subtype,model_mail_message_subtype,,1,1,1,1
|
||||
access_mail_mail_user,mail.mail,model_mail_mail,base.group_user,1,1,1,0
|
||||
access_mail_message_subtype_all,mail.message.subtype.all,model_mail_message_subtype,,1,0,0,0
|
||||
access_mail_vote_all,mail.vote.all,model_mail_vote,,1,1,1,1
|
||||
access_mail_favorite_all,mail.favorite.all,model_mail_favorite,,1,1,1,1
|
|
|
@ -1,439 +1,382 @@
|
|||
/* ------------------------------------------------------------ */
|
||||
/* Reset because of ugly display of end of August
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
.openerp .oe_mail_wall ul, .openerp .oe_mail_wall li {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
/* ------------ MAIL WIDGET --------------- */
|
||||
.openerp .oe_mail, .openerp .oe_mail *{
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.openerp .oe_chatter ul, .openerp .oe_chatter li {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* Wall
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
.openerp div.oe_mail_wall {
|
||||
overflow: auto;
|
||||
padding: 0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.openerp div.oe_mail_wall div.oe_mail_wall_aside {
|
||||
margin-left: 565px;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.openerp div.oe_mail_wall ul.oe_mail_wall_threads {
|
||||
float: left;
|
||||
width: 560px;
|
||||
margin: 8px;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* Followers
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
.openerp div.oe_mail_recthread_aside h4 {
|
||||
display: inline-block;
|
||||
}
|
||||
.openerp div.oe_mail_recthread_aside button {
|
||||
.openerp .oe_mail {
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 0px;
|
||||
}
|
||||
.openerp div.oe_mail_recthread_aside label,
|
||||
.openerp div.oe_mail_recthread_aside input {
|
||||
cursor:pointer;
|
||||
.openerp .oe_mail .oe_thread{
|
||||
margin-left: 32px;
|
||||
}
|
||||
.openerp .oe_mail > .oe_thread{
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
/* Specific display of threads in the wall */
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ---------------- MESSAGES ------------------ */
|
||||
|
||||
.openerp ul.oe_mail_wall_threads .oe_msg_content textarea.oe_mail_compose_textarea {
|
||||
width: 434px;
|
||||
height: 30px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.openerp li.oe_mail_wall_thread:first .oe_msg_notification {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.openerp div.oe_thread_placeholder img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.openerp div.oe_thread_placeholder div.oe_msg_content {
|
||||
width: 440px;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* RecordThread
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
.openerp .oe_form div.oe_chatter {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.openerp .oe_mail_record_wall {
|
||||
margin: auto;
|
||||
width: 560px;
|
||||
}
|
||||
|
||||
.openerp .oe_mail_record_wall > .oe_mail_wall_threads {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.openerp div.oe_mail_recthread_aside {
|
||||
float: right;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.openerp div.oe_mail_recthread_actions {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.openerp div.oe_mail_recthread_actions button {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.openerp .oe_mail_recthread_aside .oe_follower.oe_follow {
|
||||
color: white;
|
||||
background-color: #8a89ba;
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#8a89ba), to(#807fb4));
|
||||
background-image: -webkit-linear-gradient(top, #8a89ba, #807fb4);
|
||||
background-image: -moz-linear-gradient(top, #8a89ba, #807fb4);
|
||||
background-image: -ms-linear-gradient(top, #8a89ba, #807fb4);
|
||||
background-image: -o-linear-gradient(top, #8a89ba, #807fb4);
|
||||
background-image: linear-gradient(to bottom, #8a89ba, #807fb4);
|
||||
}
|
||||
.openerp .oe_mail_recthread_aside .oe_follower.oe_following {
|
||||
color: white;
|
||||
background-color: #dc5f59;
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630));
|
||||
background-image: -webkit-linear-gradient(top, #dc5f59, #b33630);
|
||||
background-image: -moz-linear-gradient(top, #dc5f59, #b33630);
|
||||
background-image: -ms-linear-gradient(top, #dc5f59, #b33630);
|
||||
background-image: -o-linear-gradient(top, #dc5f59, #b33630);
|
||||
background-image: linear-gradient(to bottom, #dc5f59, #b33630);
|
||||
}
|
||||
|
||||
|
||||
.openerp .oe_mail_recthread_aside .oe_follower span {
|
||||
display:none;
|
||||
}
|
||||
.openerp .oe_mail_recthread_aside .oe_following span.oe_following,
|
||||
.openerp .oe_mail_recthread_aside .oe_notfollow span.oe_follow {
|
||||
display:block;
|
||||
}
|
||||
|
||||
.openerp div.oe_mail_recthread_followers {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* subtypes
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
.openerp .oe_mail_subtypes {
|
||||
display:inline-block;
|
||||
.openerp .oe_mail .oe_msg{
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
background: #F4F5FA;
|
||||
border-radius: 2px;
|
||||
margin-bottom: 2px;
|
||||
min-height: 42px;
|
||||
border: solid 1px rgba(0,0,0,0.03);
|
||||
}
|
||||
.openerp .oe_mail_subtypes .oe_recthread_subtypes {
|
||||
background: #fff;
|
||||
padding: 2px;
|
||||
border: 1px solid #aaaaaa;
|
||||
border-top: 0px;
|
||||
.openerp .oe_mail .oe_msg .oe_msg_left{
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
left:0; top: 0; bottom: 0; width: 40px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.openerp .oe_mail_subtypes.oe_mouseout .oe_recthread_subtypes {
|
||||
display: none;
|
||||
.openerp .oe_mail .oe_msg .oe_msg_icon{
|
||||
width: 32px;
|
||||
margin: 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.openerp .oe_mail_subtypes.oe_mouseover .oe_recthread_subtypes {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* Thread
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
.openerp div.oe_mail_thread_action {
|
||||
white-space: normal;
|
||||
padding: 8px;
|
||||
z-index:5;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.openerp div.oe_mail_thread_action:after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* default textarea (oe_mail_compose_textarea), and body textarea for compose form view */
|
||||
.openerp .oe_msg_content textarea.oe_mail_compose_textarea:focus,
|
||||
.openerp .oe_msg_content div.oe_mail_compose_message_body textarea:focus {
|
||||
outline: 0;
|
||||
border-color: rgba(82, 168, 236, 0.8);
|
||||
-moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
|
||||
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
|
||||
-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
|
||||
}
|
||||
|
||||
.openerp .oe_mail_vote_count,
|
||||
.openerp .oe_msg_vote{
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.openerp button.oe_mail_starbox{
|
||||
background: #ff0000;
|
||||
}
|
||||
.openerp button.oe_mail_starbox.oe_stared{
|
||||
background: #00FF00;
|
||||
}
|
||||
|
||||
.openerp div.oe_mail_thread_display {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.openerp div.oe_thread_placeholder {
|
||||
margin-left: 66px;
|
||||
}
|
||||
|
||||
.openerp li.oe_mail_thread_msg {
|
||||
width: 560px;
|
||||
}
|
||||
|
||||
.openerp div.oe_thread_placeholder li.oe_mail_thread_msg:last-child {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.openerp div.oe_mail_thread_more {
|
||||
display: none;
|
||||
border-bottom: 1px solid #D2D9E7;
|
||||
}
|
||||
|
||||
.openerp li.oe_mail_thread_msg:after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.openerp li.oe_mail_thread_msg.oe_mail_read,
|
||||
.openerp li.oe_mail_thread_msg.oe_mail_read div {
|
||||
border-left: #F0F0F0;
|
||||
}
|
||||
.openerp li.oe_mail_thread_msg.oe_mail_read li.oe_mail_thread_msg.oe_mail_unread,
|
||||
.openerp li.oe_mail_thread_msg.oe_mail_read li.oe_mail_thread_msg.oe_mail_unread div {
|
||||
background-color: #F6F6F6;
|
||||
}
|
||||
|
||||
.openerp li.oe_mail_thread_msg.oe_mail_unread>div>ul>li.oe_unread,
|
||||
.openerp li.oe_mail_thread_msg.oe_mail_read>div>ul>li.oe_read {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.openerp li.oe_mail_thread_msg > div:after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.openerp div.oe_mail_msg {
|
||||
padding: 0;
|
||||
margin: 0 0 4px 0;
|
||||
}
|
||||
|
||||
.openerp .oe_msg_notification,
|
||||
.openerp .oe_msg_expandable,
|
||||
.openerp .oe_msg_comment,
|
||||
.openerp .oe_msg_email {
|
||||
padding: 8px;
|
||||
background: white;
|
||||
.openerp .oe_mail .oe_msg .oe_msg_center{
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.openerp .oe_msg_notification:after,
|
||||
.openerp .oe_msg_comment:after,
|
||||
.openerp .oe_msg_email:after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
margin-left: 40px;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg .oe_msg_footer{
|
||||
padding-left: 4px;
|
||||
overflow: hidden;
|
||||
opacity:0.8;
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg .oe_msg_content{
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
padding: 4px;
|
||||
padding-bottom:1px;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg .oe_msg_content .oe_msg_title{
|
||||
font-size: 13px;
|
||||
margin-bottom: 0px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.openerp div.oe_msg_content {
|
||||
float: left;
|
||||
/* a) Indented Messages */
|
||||
|
||||
.openerp .oe_mail .oe_msg_indented{
|
||||
background: #FFF;
|
||||
border: none;
|
||||
margin-bottom:0px;
|
||||
min-height:38px;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg.oe_msg_indented .oe_msg_icon{
|
||||
width:32px;
|
||||
margin:2px;
|
||||
border-radius:2px;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg .oe_subtle{
|
||||
color: #B7B7D5;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg_indented .oe_msg_center{
|
||||
margin-left:34px;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg.oe_msg_indented .oe_msg_content{
|
||||
padding-top:2px;
|
||||
}
|
||||
/* b) Votes (likes) */
|
||||
.openerp .oe_mail .oe_mail_vote_count{
|
||||
display: inline;
|
||||
position: relative;
|
||||
width: 486px;
|
||||
}
|
||||
|
||||
.openerp div.oe_msg_content > li {
|
||||
float: left;
|
||||
background: #7C7BAD;
|
||||
color: white;
|
||||
text-shadow: none;
|
||||
border-radius: 3px;
|
||||
margin: 0px;
|
||||
padding-left: 3px;
|
||||
padding-right: 18px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.openerp .oe_msg_content:after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
.openerp .oe_mail .oe_mail_vote_count .oe_e{
|
||||
position: absolute;
|
||||
bottom: 1px;
|
||||
right: 2px;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.openerp .oe_chatter a {
|
||||
cursor: pointer;
|
||||
}
|
||||
/* c) Message action icons */
|
||||
|
||||
.openerp img.oe_mail_icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
.openerp .oe_mail .oe_msg.oe_msg_unread .oe_unread{
|
||||
display:none;
|
||||
}
|
||||
|
||||
.openerp img.oe_mail_thumbnail {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin: 4px;
|
||||
.openerp .oe_mail .oe_msg.oe_msg_read .oe_read{
|
||||
display:none;
|
||||
}
|
||||
|
||||
.openerp img.oe_mail_frame {
|
||||
.openerp .oe_mail .oe_msg .oe_msg_icons{
|
||||
float: right;
|
||||
margin-top: 4px;
|
||||
margin-right: 8px;
|
||||
margin-left: 8px;
|
||||
height: 24px;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg .oe_msg_icons span{
|
||||
float:right;
|
||||
width:24px;
|
||||
height:24px;
|
||||
line-height:24px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
-moz-border-radius: 3px;
|
||||
-webkit-border-radius: 3px;
|
||||
-o-border-radius: 3px;
|
||||
-ms-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
-moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
||||
-webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
||||
-o-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
||||
-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
||||
clip: rect(5px, 40px, 45px, 0px);
|
||||
}
|
||||
.openerp .oe_mail .oe_msg .oe_msg_icons a {
|
||||
text-decoration: none;
|
||||
color: #FFF;
|
||||
text-shadow: 0px 1px #AAA,0px -1px #AAA, -1px 0px #AAA, 1px 0px #AAA, 0px 3px 3px rgba(0,0,0,0.1);
|
||||
-webkit-transition: all 0.2s linear;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg:hover .oe_msg_icons a{
|
||||
opacity: 1;
|
||||
-webkit-transition: all 0.1s linear;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg .oe_msg_icons .oe_star:hover a{
|
||||
color: #FFF6C0;
|
||||
text-shadow: 0px 1px #FFA162,0px -1px #FFA162, -1px 0px #FFA162, 1px 0px #FFA162, 0px 3px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.openerp .oe_mail .oe_msg .oe_msg_icons .oe_star.oe_starred a{
|
||||
color: #FFE41F;
|
||||
text-shadow: 0px 1px #DF6200,0px -1px #DF6200, -1px 0px #DF6200, 1px 0px #DF6200, 0px 3px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.openerp .oe_mail .oe_msg .oe_msg_icons .oe_reply:hover a{
|
||||
color: #1fc0ff;
|
||||
text-shadow: 0px 1px #184fc5,0px -1px #184fc5, -1px 0px #184fc5, 1px 0px #184fc5, 0px 3px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.openerp .oe_mail .oe_msg .oe_msg_icons .oe_read:hover a{
|
||||
color: #bbbaff;
|
||||
text-shadow: 0px 1px #7c7bad,0px -1px #7c7bad, -1px 0px #7c7bad, 1px 0px #7c7bad, 0px 3px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.openerp .oe_mail .oe_msg .oe_msg_icons .oe_unread:hover a{
|
||||
color: #c2ff00;
|
||||
text-shadow: 0px 1px #009441,0px -1px #009441, -1px 0px #009441, 1px 0px #009441, 0px 3px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.openerp .oe_mail .oe_msg .oe_msg_content textarea{
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
resize: vertical;
|
||||
|
||||
padding: 4px;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg.oe_msg_composer_compact, .openerp .oe_mail .oe_msg.oe_msg_expandable{
|
||||
padding:4px;
|
||||
min-height:0px;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg.oe_msg_composer_compact textarea{
|
||||
height: 24px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.openerp .oe_mail_invisible {
|
||||
/* ---------------- MESSAGE QUICK COMPOSER --------------- */
|
||||
|
||||
.openerp .oe_mail .oe_msg_composer .oe_msg_footer{
|
||||
padding-right:4px;
|
||||
padding-top: 2px;
|
||||
padding-bottom:6px;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg_attachments.oe_hidden,
|
||||
.openerp .oe_mail .oe_msg_images.oe_hidden{
|
||||
margin:0px;
|
||||
border: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* Messages layout
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
.openerp .oe_mail_msg .oe_msg_title {
|
||||
margin: 0;
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
.openerp .oe_mail .oe_msg_attachments{
|
||||
margin-bottom: 4px;
|
||||
margin-right: 0px;
|
||||
font-size: 12px;
|
||||
border-radius: 2px;
|
||||
border: solid 1px rgba(124,123,173,0.14);
|
||||
}
|
||||
.openerp .oe_mail_msg .oe_msg_title a:link,
|
||||
.openerp .oe_mail_msg .oe_msg_title a:visited {
|
||||
color: #4C4C4C;
|
||||
.openerp .oe_mail .oe_msg_attachments .oe_attachment{
|
||||
padding: 2px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg_attachments .oe_attachment .oe_e{
|
||||
font-size: 23px;
|
||||
margin-top: -5px;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg_attachments .oe_attachment .oe_e:hover{
|
||||
text-decoration: none;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg_attachments .oe_attachment:nth-child(odd){
|
||||
background:white;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg_attachments .oe_attachment:nth-child(even){
|
||||
background: #F4F5FA;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg_images {
|
||||
display: block;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg_footer button{
|
||||
display: inline;
|
||||
height: 24px;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg_footer button.oe_attach{
|
||||
width: 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg_footer button.oe_attach .oe_e{
|
||||
position: relative;
|
||||
top: -1px;
|
||||
left: -9px;
|
||||
}
|
||||
.openerp .oe_mail .oe_hidden_input_file, .openerp .oe_mail .oe_hidden_input_file form{
|
||||
display:inline;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg_footer button.oe_full{
|
||||
width:24px;
|
||||
overflow:hidden;
|
||||
float: right;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg_footer button.oe_full .oe_e{
|
||||
position: relative;
|
||||
top: -1px;
|
||||
left: -9px;
|
||||
}
|
||||
.openerp .oe_mail button.oe_attach, .openerp .oe_mail button.oe_full{
|
||||
background: transparent;
|
||||
color: #7C7BAD;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
text-shadow: none;
|
||||
}
|
||||
.openerp .oe_mail .oe_attach_label{
|
||||
color: #7C7BAD;
|
||||
margin-left: -3px;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg_footer .oe_attachment_file .oe_form_binary_file{
|
||||
display: inline-block;
|
||||
margin-left: -47px;
|
||||
height: 28px;
|
||||
width: 52px;
|
||||
margin-top: -6px;
|
||||
}
|
||||
.openerp .oe_mail .oe_mail_list_recipients{
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
/* ---------------- HIDDEN MESSAGES ------------------ */
|
||||
|
||||
.openerp .oe_mail .oe_msg_content.oe_msg_more_message{
|
||||
text-align: right;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg_content.oe_msg_more_message .oe_separator{
|
||||
height: 0;
|
||||
border-bottom: dashed 1px #e6e6e6;
|
||||
margin-left: -4px;
|
||||
margin-right: 8px;
|
||||
margin-top: 6px;
|
||||
margin-bottom: -9px;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg_more_message .oe_msg_fetch_more {
|
||||
background: white;
|
||||
margin-right: 280px;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
text-decoration: none;
|
||||
color: #b4b4b4;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg_more_message .oe_msg_fetch_more:hover{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.openerp .oe_mail_msg .oe_msg_body {
|
||||
margin-bottom: .5em;
|
||||
text-align: justify;
|
||||
/* ---------------- FOLLOWERS ------------------ */
|
||||
|
||||
.openerp .oe_followers{
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding-top: 5px;
|
||||
width: 160px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.openerp .oe_mail_msg .oe_msg_body pre {
|
||||
font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif;
|
||||
margin: 0px;
|
||||
white-space: pre-wrap;
|
||||
/* a) THE FOLLOW BUTTON */
|
||||
|
||||
.openerp .oe_followers button.oe_follower{
|
||||
display: block;
|
||||
text-align: center;
|
||||
width:100%;
|
||||
}
|
||||
.openerp .oe_followers button.oe_follower.oe_following{
|
||||
background-color: #3465A4;
|
||||
background-image: -webkit-linear-gradient(top, #729FCF, #3465A4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.openerp .oe_followers button.oe_follower .oe_follow,
|
||||
.openerp .oe_followers button.oe_follower .oe_unfollow,
|
||||
.openerp .oe_followers button.oe_follower .oe_following{
|
||||
display: none;
|
||||
}
|
||||
/* a.1) when following, show 'following' */
|
||||
.openerp .oe_followers button.oe_follower.oe_following .oe_following{
|
||||
display: inline;
|
||||
}
|
||||
/* a.2) when following and hovering, show 'unfollow' */
|
||||
.openerp .oe_followers button.oe_follower.oe_following:hover .oe_following{
|
||||
display: none;
|
||||
}
|
||||
.openerp .oe_followers button.oe_follower.oe_following:hover .oe_unfollow{
|
||||
display: inline;
|
||||
}
|
||||
/* a.3) when not following show 'follow' */
|
||||
.openerp .oe_followers button.oe_follower.oe_notfollow .oe_follow{
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Read more/less link */
|
||||
.openerp .oe_mail_msg span.oe_mail_reduce {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
.openerp .oe_followers .oe_subtype_list{
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Dropdown menu */
|
||||
|
||||
.openerp .oe_mail ul.oe_mail_thread_display ul.oe_mail_thread_display {
|
||||
position: relative;
|
||||
border-left: 1px #DDD dashed;
|
||||
/* b) THE FOLLOWERS */
|
||||
.openerp .oe_followers .oe_follower_title{
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.openerp .oe_mail ul.oe_header {
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
top: -6px;
|
||||
z-index: 10;
|
||||
.openerp .oe_followers .oe_follower_title_box{
|
||||
margin-top: 12px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.openerp .oe_mail ul.oe_header a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.openerp .oe_mail ul.oe_header>li {
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Message footer */
|
||||
.openerp .oe_mail_msg .oe_msg_footer {
|
||||
color: #888;
|
||||
}
|
||||
.openerp .oe_mail_msg .oe_msg_footer li {
|
||||
float: left;
|
||||
margin-right: 3px;
|
||||
}
|
||||
.openerp .oe_mail_msg .oe_msg_footer li:after {
|
||||
content: " · ";
|
||||
}
|
||||
.openerp .oe_mail_msg .oe_msg_footer li:last-child:after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
/* Attachments list */
|
||||
.openerp .oe_msg_content ul.oe_msg_attachments {
|
||||
width: 100%;
|
||||
margin: .5em 0 0 0;
|
||||
padding: .5em 0;
|
||||
list-style-position: inside;
|
||||
}
|
||||
.openerp .oe_msg_content ul.oe_msg_attachments.oe_hidden {
|
||||
display: none;
|
||||
}
|
||||
.openerp .oe_msg_content ul.oe_msg_attachments li {
|
||||
float: none;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: square;
|
||||
}
|
||||
.openerp .oe_msg_content ul.oe_msg_attachments .oe_upload_in_process {
|
||||
.openerp .oe_followers .oe_invite{
|
||||
float: right;
|
||||
width: 200px;
|
||||
height: 16px;
|
||||
}
|
||||
.openerp .oe_msg_content ul.oe_msg_attachments .oe_upload_in_process div {
|
||||
float: left;
|
||||
width: 38px;
|
||||
height: 16px;
|
||||
margin-right: 2px;
|
||||
background: #66FF66;
|
||||
.openerp .oe_followers .oe_partner {
|
||||
height: 32px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.openerp .oe_msg_content ul.oe_msg_attachments .oe_upload_in_process span {
|
||||
color: #aaaaaa;
|
||||
position: absolute;
|
||||
.openerp .oe_followers .oe_partner img{
|
||||
width: 32px;
|
||||
margin-right:4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* Topbar button
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ---------------- MESSAGES BODY ------------------ */
|
||||
.openerp .oe_mail .oe_msg_content .oe_blockquote,
|
||||
.openerp .oe_mail .oe_msg_content blockquote {
|
||||
padding: 4px;
|
||||
border-radius: 2px;
|
||||
border: solid 1px rgba(124,123,173,0.14);
|
||||
}
|
||||
|
||||
.openerp .oe_topbar .oe_topbar_compose_full_email {
|
||||
float: right;
|
||||
margin: 3px 25px 0 0;
|
||||
}
|
||||
/* ----------- FORM INTEGRATION ------------ */
|
||||
|
||||
.openerp .oe_record_thread{
|
||||
display: block;
|
||||
margin-right: 180px;
|
||||
}
|
||||
|
||||
/* ----------- INBOX INTEGRATION ----------- */
|
||||
|
||||
.openerp .oe_mail_wall .oe_mail{
|
||||
margin: 16px;
|
||||
width: 720px;
|
||||
}
|
||||
|
|
|
@ -1,166 +0,0 @@
|
|||
/* ------------------------------ */
|
||||
/* Compose Message */
|
||||
/* ------------------------------ */
|
||||
|
||||
.openerp .oe_msg_content .oe_mail_compose_message_footer {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.openerp .oe_msg_content .oe_mail_compose_message_footer button.oe_mail_compose_message_button_send {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.openerp .oe_mail .oe_mail_compose_textarea
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.openerp .oe_mail .oe_mail_compose_textarea .oe_mail_post_header,
|
||||
.openerp .oe_mail .oe_mail_compose_textarea .oe_mail_post_footer,
|
||||
{
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.openerp .oe_mail .oe_mail_compose_textarea a.oe_cancel {
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
top: -8px;
|
||||
}
|
||||
.openerp .oe_mail .oe_mail_compose_textarea a.oe_cancel:first-of-type {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.openerp .oe_mail .oe_mail_compose_textarea button.oe_full {
|
||||
float: right;
|
||||
position: relative;
|
||||
right: -10px;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* mail.compose.message : list_recipients
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
.openerp .oe_mail .oe_mail_list_recipients {
|
||||
display: inline;
|
||||
}
|
||||
.openerp .oe_mail .oe_mail_list_recipients .oe_all_follower {
|
||||
color: blue;
|
||||
}
|
||||
.openerp .oe_mail .oe_mail_list_recipients .oe_partner_follower a {
|
||||
color: red;
|
||||
}
|
||||
.openerp .oe_mail .oe_mail_list_recipients .oe_hidden,
|
||||
.openerp .oe_mail .oe_mail_list_recipients .oe_more_hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* mail.compose.message : attachment
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
.openerp .oe_mail .oe_attachment_file {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.openerp .oe_mail .oe_attachment_file .oe_add {
|
||||
float: left;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
left: +2px;
|
||||
top: +7px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* attachment button: override of openerp values */
|
||||
.openerp .oe_mail .oe_attachment_file .oe_add button,
|
||||
.openerp .oe_mail .oe_attachment_file .oe_add input.oe_insert_file {
|
||||
position: absolute;
|
||||
bottom: +0px;
|
||||
left: +0px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
.openerp .oe_mail .oe_attachment_file .oe_add input.oe_insert_file {
|
||||
z-index:2;
|
||||
width: 300px;
|
||||
left: -100px;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: transparent;
|
||||
}
|
||||
.openerp .oe_mail .oe_attachment_file .oe_add button span {
|
||||
position: relative;
|
||||
bottom: +4px;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.openerp .oe_mail .oe_msg_attachments input {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.openerp .oe_mail .oe_mail_compose_attachment_list {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* mail.compose.message
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
|
||||
/* default textarea (oe_mail_compose_textarea), and body textarea for compose form view */
|
||||
.openerp .oe_mail.oe_semantic_html_override .oe_mail_compose_textarea textarea.field_text,
|
||||
.openerp .oe_mail div.oe_mail_compose_message_body textarea.field_text {
|
||||
width: 100%;
|
||||
min-height: 120px;
|
||||
height: auto;
|
||||
padding: 4px;
|
||||
font-size: 12px;
|
||||
border: 1px solid #cccccc;
|
||||
}
|
||||
|
||||
/* not top textarea */
|
||||
.openerp .oe_mail.oe_semantic_html_override .oe_semantic_html_override .oe_mail_compose_textarea textarea.field_text {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
/* form_view: delete white background */
|
||||
.openerp .oe_msg_content div.oe_formview {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.openerp .oe_msg_content div.oe_form_nosheet {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.openerp .oe_msg_content table.oe_form_group {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.openerp .oe_msg_content table.oe_form_field,
|
||||
.openerp .oe_msg_content div.oe_form_field {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.openerp .oe_msg_content td.oe_form_group_cell {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
/* subject: change width */
|
||||
.openerp .oe_msg_content .oe_form .oe_form_field input[type='text'] {
|
||||
width: 472px;
|
||||
}
|
||||
|
||||
/* body_html: cleditor */
|
||||
.openerp .oe_msg_content div.cleditorMain {
|
||||
border: 1px solid #cccccc;
|
||||
}
|
||||
|
||||
/* destination_partner_ids */
|
||||
.openerp .oe_msg_content div.text-core {
|
||||
height: 22px !important;
|
||||
width: 472px;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -24,10 +24,9 @@ openerp_mail_followers = function(session, mail) {
|
|||
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.options.image = this.node.attrs.image || 'image_small';
|
||||
this.options.title = this.node.attrs.title || 'Followers';
|
||||
this.options.comment = this.node.attrs.help || false;
|
||||
this.options.displayed_nb = this.node.attrs.displayed_nb || 10;
|
||||
this.image = this.node.attrs.image || 'image_small';
|
||||
this.comment = this.node.attrs.help || false;
|
||||
this.displayed_nb = this.node.attrs.displayed_nb || 10;
|
||||
this.ds_model = new session.web.DataSetSearch(this, this.view.model);
|
||||
this.ds_follow = new session.web.DataSetSearch(this, this.field.relation);
|
||||
this.ds_users = new session.web.DataSetSearch(this, 'res.users');
|
||||
|
@ -61,7 +60,7 @@ openerp_mail_followers = function(session, mail) {
|
|||
self.do_unfollow();
|
||||
});
|
||||
// event: click on a subtype, that (un)subscribe for this subtype
|
||||
this.$el.on('click', 'ul.oe_subtypes input', self.do_update_subscription);
|
||||
this.$el.on('click', '.oe_subtype_list input', self.do_update_subscription);
|
||||
// event: click on 'invite' button, that opens the invite wizard
|
||||
this.$('.oe_invite').on('click', function (event) {
|
||||
action = {
|
||||
|
@ -115,20 +114,24 @@ openerp_mail_followers = function(session, mail) {
|
|||
self.message_is_follower = (_.indexOf(self.get('value'), pid) != -1);
|
||||
}).pipe(self.proxy('display_generic'));
|
||||
},
|
||||
|
||||
_format_followers: function(count){
|
||||
// TDE note: why redefining _t ?
|
||||
function _t(str) { return str; }
|
||||
var str = '';
|
||||
if(count <= 0){
|
||||
str = _t('No followers');
|
||||
}else if(count === 1){
|
||||
str = _t('One follower');
|
||||
}else{
|
||||
str = ''+count+' '+_t('followers');
|
||||
}
|
||||
return str;
|
||||
},
|
||||
/* Display generic info about follower, for people not having access to res_partner */
|
||||
display_generic: function () {
|
||||
var self = this;
|
||||
var node_user_list = this.$('ul.oe_mail_followers_display').empty();
|
||||
// format content: Followers (You and 0 other) // Followers (3)
|
||||
var content = this.options.title;
|
||||
if (this.message_is_follower) {
|
||||
content += ' (You and ' + (this.get('value').length-1) + ' other)';
|
||||
}
|
||||
else {
|
||||
content += ' (' + this.get('value').length + ')'
|
||||
}
|
||||
this.$('div.oe_mail_recthread_followers h4').html(content);
|
||||
var node_user_list = this.$('.oe_follower_list').empty();
|
||||
this.$('.oe_follower_title').html(this._format_followers(this.get('value').length));
|
||||
},
|
||||
|
||||
/** Display the followers */
|
||||
|
@ -137,16 +140,17 @@ openerp_mail_followers = function(session, mail) {
|
|||
records = records || [];
|
||||
this.message_is_follower = this.set_is_follower(records);
|
||||
// clean and display title
|
||||
var node_user_list = this.$('ul.oe_mail_followers_display').empty();
|
||||
this.$('div.oe_mail_recthread_followers h4').html(this.options.title + ' (' + records.length + ')');
|
||||
var node_user_list = this.$('.oe_follower_list').empty();
|
||||
this.$('.oe_follower_title').html(this._format_followers(records.length));
|
||||
// truncate number of displayed followers
|
||||
truncated = records.splice(0, this.options.displayed_nb);
|
||||
truncated = records.splice(0, this.displayed_nb);
|
||||
_(truncated).each(function (record) {
|
||||
record.avatar_url = mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record.id);
|
||||
$(session.web.qweb.render('mail.followers.partner', {'record': record})).appendTo(node_user_list);
|
||||
});
|
||||
// FVA note: be sure it is correctly translated
|
||||
if (truncated.length < records.length) {
|
||||
$('<li>And ' + (records.length - truncated.length) + ' more.</li>').appendTo(node_user_list);
|
||||
$('<div class="oe_partner">And ' + (records.length - truncated.length) + ' more.</div>').appendTo(node_user_list);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -172,7 +176,7 @@ openerp_mail_followers = function(session, mail) {
|
|||
|
||||
/** Fetch subtypes, only if current user is follower */
|
||||
fetch_subtypes: function () {
|
||||
var subtype_list_ul = this.$('.oe_subtypes').empty();
|
||||
var subtype_list_ul = this.$('.oe_subtype_list').empty();
|
||||
if (! this.message_is_follower) return;
|
||||
var context = new session.web.CompoundContext(this.build_context(), {});
|
||||
this.ds_model.call('message_get_subscription_data', [[this.view.datarecord.id], context]).pipe(this.proxy('display_subtypes'));
|
||||
|
@ -181,13 +185,12 @@ openerp_mail_followers = function(session, mail) {
|
|||
/** Display subtypes: {'name': default, followed} */
|
||||
display_subtypes:function (data) {
|
||||
var self = this;
|
||||
var subtype_list_ul = this.$('.oe_subtypes');
|
||||
var records = data[this.view.datarecord.id].message_subtype_data;
|
||||
|
||||
var subtype_list_ul = this.$('.oe_subtype_list');
|
||||
var records = data[this.view.datarecord.id || this.view.dataset.ids[0]].message_subtype_data;
|
||||
_(records).each(function (record, record_name) {
|
||||
record.name = record_name;
|
||||
record.followed = record.followed || undefined;
|
||||
$(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo( self.$('ul.oe_subtypes') );
|
||||
$(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo( self.$('.oe_subtype_list') );
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -210,7 +213,7 @@ openerp_mail_followers = function(session, mail) {
|
|||
var self = this;
|
||||
|
||||
var checklist = new Array();
|
||||
_(this.$('.oe_mail_recthread_actions input[type="checkbox"]')).each(function (record) {
|
||||
_(this.$('.oe_actions input[type="checkbox"]')).each(function (record) {
|
||||
if ($(record).is(':checked')) {
|
||||
checklist.push(parseInt($(record).data('id')));
|
||||
}
|
||||
|
|
|
@ -1,33 +1,46 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<template>
|
||||
|
||||
<!-- this template contains the mail widget and is used to namespace the css -->
|
||||
<t t-name="mail.Widget">
|
||||
<div class="oe_mail">
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<!--
|
||||
mail.compose_message template
|
||||
This template holds the composition form to write a note or send
|
||||
an e-mail. It contains by default a textarea, that will be replaced
|
||||
by another composition form in the main wall composition form, or
|
||||
for main thread composition form in document form view.
|
||||
|
||||
mail.compose_message.compact template
|
||||
This template holds the composition form to write a message, this box is converted into
|
||||
mail.compose_message when focus on textarea
|
||||
-->
|
||||
<t t-name="mail.compose_message">
|
||||
<div class="oe_mail_compose_textarea">
|
||||
<img class="oe_mail_icon oe_mail_frame oe_left" alt="User img"/>
|
||||
<div class="oe_msg_content">
|
||||
<!-- contains the composition form -->
|
||||
<!-- default content: old basic textarea -->
|
||||
<div class="oe_mail_post_header">
|
||||
<div t-if="widget.show_composer" t-attf-class="oe_msg oe_msg_composer #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''}">
|
||||
<div class="oe_msg_left">
|
||||
<img class="oe_msg_icon" alt="User img" t-attf-src="#{widget.avatar}"/>
|
||||
</div>
|
||||
<div class="oe_msg_center">
|
||||
<div class="oe_msg_content">
|
||||
<t t-call="mail.thread.list_recipients"/>
|
||||
<a class="oe_cancel oe_e">X</a>
|
||||
<textarea class="field_text"></textarea>
|
||||
</div>
|
||||
<textarea class="field_text" placeholder="Add your comment here..."/>
|
||||
<div class="oe_mail_post_footer">
|
||||
<div class="oe_mail_compose_attachment_list"/>
|
||||
<button class="oe_full">Full mail message</button>
|
||||
<button class="oe_post">Post message</button>
|
||||
<div class="oe_msg_footer">
|
||||
<div class="oe_msg_attachment_list"></div>
|
||||
<button class="oe_post">Post</button>
|
||||
<t t-call="mail.compose_message.add_attachment"/>
|
||||
<!--<a class="oe_cancel oe_e">X</a>-->
|
||||
<button class="oe_full"><span class='oe_e'>&ograve</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_clear"/>
|
||||
</div>
|
||||
<div t-if="widget.show_compact_message and !widget.show_composer" t-attf-class="oe_msg oe_msg_composer_compact #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''}">
|
||||
<textarea class="field_text oe_compact" placeholder="Write a reply..."/>
|
||||
</div>
|
||||
<span t-if="!(widget.show_compact_message and !widget.show_composer) and !widget.show_composer" class="oe_placeholder_compose"></span>
|
||||
</t>
|
||||
|
||||
<!--
|
||||
|
@ -35,19 +48,20 @@
|
|||
Small template to be inserted in the composition for add attachments
|
||||
-->
|
||||
<t t-name="mail.compose_message.add_attachment">
|
||||
<div class="oe_attachment_file">
|
||||
<div class="oe_add">
|
||||
<span class="oe_attachment_file">
|
||||
<span class="oe_add">
|
||||
<!-- uploader of file -->
|
||||
<button><span class="oe_e">p</span></button>
|
||||
<button class="oe_attach"><span class="oe_e">'</span></button>
|
||||
<span class='oe_attach_label'>File</span>
|
||||
<t t-call="HiddenInputFile">
|
||||
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
|
||||
<t t-set="fileupload_action">/web/binary/upload_attachment</t>
|
||||
<input type="hidden" name="model" value="mail.message"/>
|
||||
<input type="hidden" name="model" value="mail.compose.message"/>
|
||||
<input type="hidden" name="id" value="0"/>
|
||||
<input type="hidden" name="session_id" t-att-value="widget.session.session_id"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
<!--
|
||||
|
@ -55,25 +69,33 @@
|
|||
Template used to display attachments in a mail.message
|
||||
-->
|
||||
<t t-name="mail.thread.message.attachments">
|
||||
<ul t-attf-class="oe_msg_attachments #{widget.datasets.attachment_ids[0] and widget.options.thread.show_attachment_link?'':'oe_hidden'}">
|
||||
<t t-foreach="widget.datasets.attachment_ids" t-as="attachment">
|
||||
<li>
|
||||
<span t-if="(attachment.upload or attachment.percent_loaded<100)" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}" t-attf-name="{attachment.name || attachment.filename}">
|
||||
<span class="oe_msg_attachments">
|
||||
<t t-foreach="widget.attachment_ids" t-as="attachment" t-if="!attachment.is_image">
|
||||
<div class="oe_attachment">
|
||||
<span t-if="(attachment.upload and attachment.percent_loaded<100)" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}" t-attf-name="{attachment.name || attachment.filename}">
|
||||
<div class="oe_upload_in_process">
|
||||
<span>...Upload in progress...</span>
|
||||
</div>
|
||||
<t t-raw="attachment.name || attachment.filename"/>
|
||||
</span>
|
||||
<a t-if="(!attachment.upload or attachment.percent_loaded>=100)" t-att-href="attachment.url" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}">
|
||||
<t t-raw="attachment.name || attachment.filename"/>
|
||||
</a>
|
||||
<t t-if="widget.options.thread.show_attachment_delete and (!attachment.upload or attachment.percent_loaded>=100)">
|
||||
<a class="oe_right oe_mail_attachment_delete" title="Delete this attachment" t-attf-data-id="{attachment.id}">x</a>
|
||||
<t t-if="(!attachment.upload or attachment.percent_loaded>=100)">
|
||||
<a t-att-href="attachment.url" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}">
|
||||
<t t-raw="attachment.name || attachment.filename"/>
|
||||
</a>
|
||||
</t>
|
||||
|
||||
</li>
|
||||
<t t-if="(widget.show_delete_attachment and (!attachment.upload or attachment.percent_loaded>=100))">
|
||||
<a class="oe_right oe_mail_attachment_delete oe_e" title="Delete this attachment" t-attf-data-id="{attachment.id}">[</a>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</ul>
|
||||
</span>
|
||||
<span class="oe_msg_images">
|
||||
<t t-foreach="widget.attachment_ids" t-as="attachment" t-if="attachment.is_image">
|
||||
<a t-att-href="attachment.url" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}">
|
||||
<img t-if="attachment.is_image" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}" t-att-src="attachment.url"/>
|
||||
</a>
|
||||
</t>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
<t t-name="mail.thread.message.private">
|
||||
|
@ -89,14 +111,14 @@
|
|||
-->
|
||||
<t t-name="mail.thread.list_recipients">
|
||||
<div class="oe_mail_list_recipients">
|
||||
Post to:
|
||||
<span t-if="!widget.datasets.is_private" class="oe_all_follower">All Followers</span>
|
||||
<t t-if="!widget.datasets.is_private and widget.datasets.partner_ids.length"> and </t>
|
||||
To:
|
||||
<span t-if="!widget.is_private" class="oe_all_follower">Everyone</span>
|
||||
<t t-if="!widget.is_private and widget.partner_ids.length"> and </t>
|
||||
<t t-set="inc" t-value="0"/>
|
||||
<t t-if="widget.datasets.partner_ids.length" t-foreach="widget.datasets.partner_ids" t-as="partner"><span t-attf-class="oe_partner_follower #{inc>=3?'oe_hidden':''}"><t t-if="inc" t-raw="', '"/><a t-attf-href="#model=res.partner&id=#{partner[0]}"><t t-raw="partner[1]"/></a></span><t t-set="inc" t-value="inc+1"/>
|
||||
<t t-if="widget.partner_ids.length" t-foreach="widget.partner_ids" t-as="partner"><span t-attf-class="oe_partner_follower #{inc>=3?'oe_hidden':''}"><t t-if="inc" t-raw="', '"/><a t-attf-href="#model=res.partner&id=#{partner[0]}"><t t-raw="partner[1]"/></a></span><t t-set="inc" t-value="inc+1"/>
|
||||
</t>
|
||||
<t t-if="widget.datasets.partner_ids.length>=3">
|
||||
<span class="oe_more">, <a><t t-raw="widget.datasets.partner_ids.length-3"/> others...</a></span>
|
||||
<t t-if="widget.partner_ids.length>=3">
|
||||
<span class="oe_more">, <a><t t-raw="widget.partner_ids.length-3"/> others...</a></span>
|
||||
<a class="oe_more_hidden"><<<</a>
|
||||
</t>
|
||||
</div>
|
||||
|
@ -131,122 +153,95 @@
|
|||
<button type="button" class="oe_write_full oe_highlight">
|
||||
Compose a new message
|
||||
</button>
|
||||
<button type="button" class="oe_write_onwall" help="Your followers can read this message">
|
||||
Write to your followers
|
||||
</button>
|
||||
<span class='oe_alternative'>
|
||||
or
|
||||
<a href='#' class='oe_write_onwall oe_bold' help='Your followers can read this message'>Write to your followers</a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- placeholder for the wall threads -->
|
||||
<div class="oe_mail_wall_threads"/>
|
||||
<div class="oe_mail_wall_aside">
|
||||
<!-- contains currently nothing -->
|
||||
</div>
|
||||
<div class="oe_mail-placeholder"></div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
display message on the wall when there are no message
|
||||
-->
|
||||
<li t-name="mail.wall_no_message" class="oe_wall_no_message">
|
||||
You have no messages
|
||||
</li>
|
||||
<t t-name="mail.wall_no_message">
|
||||
<div class="oe_wall_no_message">You have no messages</div>
|
||||
</t>
|
||||
|
||||
<!--
|
||||
record_thread main template
|
||||
Template used to display the communication history in documents
|
||||
form view.
|
||||
-->
|
||||
<div t-name="mail.record_thread" class="oe_mail_record_wall">
|
||||
<!-- <h4>History and Comments</h4> -->
|
||||
<ul class="oe_mail_wall_threads">
|
||||
<!-- contains the document thread -->
|
||||
</ul>
|
||||
<div t-name="mail.record_thread" class="oe_record_thread">
|
||||
<div class="oe_mail-placeholder">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
thread template
|
||||
This template holds a thread of comments. It begins with an actions
|
||||
container, holding the composition form. Then come the various
|
||||
messages. Then comes the 'more' button.
|
||||
-->
|
||||
<div t-name="mail.thread" class="oe_mail oe_mail_thread oe_semantic_html_override">
|
||||
<div class="oe_mail_thread_action">
|
||||
<!-- contains the composition box (form + image) -->
|
||||
</div>
|
||||
<ul class="oe_mail_thread_display">
|
||||
<!-- contains the threads -->
|
||||
</ul>
|
||||
</div>
|
||||
<t t-name="mail.thread">
|
||||
<div t-attf-class="oe_thread #{widget.root?'oe_root_thread':''}"/>
|
||||
</t>
|
||||
|
||||
<!-- default layout -->
|
||||
<li t-name="mail.thread.message" t-attf-class="oe_mail oe_mail_thread_msg #{widget.datasets.to_read ?'oe_mail_unread':'oe_mail_read'}">
|
||||
<div t-attf-class="oe_msg_#{widget.datasets.type} oe_semantic_html_override">
|
||||
<!-- message actions (read/unread, reply, delete...) -->
|
||||
<ul class="oe_header">
|
||||
<li class="placeholder-mail-vote"><t t-call="mail.thread.message.vote"/></li>
|
||||
<li class="placeholder-mail-star"><t t-call="mail.thread.message.star"/></li>
|
||||
<li t-if="widget.datasets.show_read_unread" title="Read" class="oe_read"><a class="oe_read oe_e">W</a></li>
|
||||
<li t-if="widget.datasets.show_read_unread" title="Set back to unread" class="oe_unread"><a class="oe_unread oe_e">h</a></li>
|
||||
<li title="Quick reply" t-if="widget.datasets.show_reply"><a class="oe_reply oe_e">)</a></li>
|
||||
<t t-if="(widget.datasets.is_author and widget.options.message.show_dd_delete) or widget.datasets.type == 'email'">
|
||||
<li>
|
||||
<span class="oe_dropdown_toggle">
|
||||
<a class="oe_e" title="More options">í</a>
|
||||
<ul class="oe_dropdown_menu">
|
||||
<li t-if="widget.datasets.is_author and widget.options.message.show_dd_delete"><a class="oe_msg_delete">Delete</a></li>
|
||||
<li t-if="widget.datasets.type == 'email'"><a class="oe_msg_details" t-attf-href="#model=mail.message&id=#{widget.datasets.id}" >Details</a></li>
|
||||
</ul>
|
||||
</span>
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
<t t-name="mail.thread.message">
|
||||
<div t-attf-class="oe_msg #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''} oe_msg_#{widget.type} oe_msg_#{widget.to_read?'unread':'read'}">
|
||||
|
||||
<a t-attf-href="#model=res.partner&id=#{widget.datasets.author_id[0]}" t-att-title="widget.datasets.author_id[1]">
|
||||
<img class="oe_mail_icon oe_mail_frame oe_left" t-att-src="widget.datasets.avatar"/>
|
||||
</a>
|
||||
<div class='oe_msg_left'>
|
||||
<a t-attf-href="#model=res.partner&id=#{widget.author_id[0]}" t-att-title="widget.author_id[1]">
|
||||
<img class="oe_msg_icon" t-att-src="widget.avatar"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="oe_msg_content">
|
||||
<div class="oe_msg_center">
|
||||
<div class='oe_msg_icons'>
|
||||
<span class='oe_read' t-if="widget.show_read_unread_button"><a title="Read" class="oe_e">X</a></span>
|
||||
<span class='oe_unread' t-if="widget.show_read_unread_button"><a title="Set back to unread" class="oe_e">v</a></span>
|
||||
<span class='oe_reply' t-if="widget.show_reply_button"><a title="Reply" class="oe_e">(</a></span>
|
||||
<span t-attf-class="oe_star #{widget.is_favorite?'oe_starred':''}"><a title="Add To Favorites" class="oe_e">7</a></span>
|
||||
</div>
|
||||
<!-- message itself -->
|
||||
<div class="oe_mail_msg">
|
||||
<h1 t-if="widget.datasets.subject" class="oe_msg_title">
|
||||
<t t-raw="widget.datasets.subject"/>
|
||||
<div class="oe_msg_content">
|
||||
<h1 t-if="widget.subject and !widget.thread_level" class="oe_msg_title">
|
||||
<t t-raw="widget.subject"/>
|
||||
</h1>
|
||||
<ul class="oe_msg_footer">
|
||||
<li t-if="widget.datasets.author_id"><a t-attf-href="#model=res.partner&id=#{widget.datasets.author_id[0]}"><t t-raw="widget.datasets.author_id[1]"/></a></li>
|
||||
<li><span t-att-title="widget.datasets.date"><t t-raw="widget.datasets.timerelative"/></span></li>
|
||||
<li t-if="widget.datasets.attachment_ids.length > 0">
|
||||
<a class="oe_msg_view_attachments">
|
||||
<t t-if="widget.datasets.attachment_ids.length == 1">1 Attachment</t>
|
||||
<t t-if="widget.datasets.attachment_ids.length > 1"><t t-raw="widget.datasets.attachment_ids.length"/> Attachments</t>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="oe_clear"/>
|
||||
<div class="oe_msg_body">
|
||||
<t t-if="widget.options.message.show_record_name and widget.datasets.record_name and (!widget.datasets.subject) and !widget.options.thread.thread_level and !widget.options.thread.display_on_thread[0] and widget.datasets.model!='res.partner'">
|
||||
<a class="oe_mail_action_model" t-attf-href="#model=#{widget.datasets.model}&id=#{widget.res_id}"><t t-raw="widget.datasets.record_name"/></a>
|
||||
<t t-if="widget.options.show_record_name and widget.record_name and (!widget.subject) and !widget.options.thread_level and !widget.options.display_on_thread[0] and widget.model!='res.partner'">
|
||||
<a class="oe_mail_action_model" t-attf-href="#model=#{widget.model}&id=#{widget.res_id}"><t t-raw="widget.record_name"/></a>
|
||||
</t>
|
||||
<t t-raw="widget.datasets.body"/>
|
||||
<t t-raw="widget.body"/>
|
||||
</div>
|
||||
<t t-if="widget.datasets.attachment_ids.length > 0">
|
||||
<div class="oe_clear"></div>
|
||||
<t t-if="widget.attachment_ids.length > 0">
|
||||
<t t-call="mail.thread.message.attachments"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="oe_msg_footer">
|
||||
<a t-if="widget.author_id" t-attf-href="#model=res.partner&id=#{widget.author_id[0]}"><t t-raw="widget.author_id[1]"/></a>
|
||||
<span class='oe_subtle'>•</span>
|
||||
<span t-att-title="widget.date"><t t-raw="widget.timerelative"/></span>
|
||||
<a t-if="widget.attachment_ids.length > 0" class="oe_mail_msg_view_attachments">
|
||||
<t t-if="widget.attachment_ids.length == 1">1 Attachment</t>
|
||||
<t t-if="widget.attachment_ids.length > 1"><t t-raw="widget.attachment_ids.length"/> Attachments</t>
|
||||
</a>
|
||||
<span class='oe_subtle'>•</span>
|
||||
<t t-call="mail.thread.message.vote"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_thread_placeholder"></div>
|
||||
</li>
|
||||
</t>
|
||||
|
||||
<!-- expandable message layout -->
|
||||
<li t-name="mail.thread.expandable" class="oe_mail oe_mail_thread_msg oe_mail_unread">
|
||||
<div t-attf-class="oe_msg_#{widget.datasets.type} oe_semantic_html_override">
|
||||
<t t-name="mail.thread.expandable">
|
||||
<div t-attf-class="oe_msg oe_msg_#{widget.type} #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''} oe_msg_unread">
|
||||
<div class="oe_msg_content oe_msg_more_message">
|
||||
<a class="oe_mail_fetch_more">Load more messages <span t-if="widget.datasets.nb_messages>0">(<t t-raw="widget.datasets.nb_messages"/> messages not display)</span>...</a>
|
||||
<div class='oe_separator'></div>
|
||||
<a t-if="widget.nb_messages === 1" class="oe_msg_fetch_more">show one more message</a>
|
||||
<a t-if="widget.nb_messages !== 1" class="oe_msg_fetch_more">show <t t-raw="widget.nb_messages" /> more messages</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</t>
|
||||
|
||||
<!--
|
||||
mail.compose_message.button_top_bar
|
||||
|
@ -254,7 +249,7 @@
|
|||
-->
|
||||
<t t-name="mail.compose_message.button_top_bar">
|
||||
<div class="oe_topbar_compose_full_email">
|
||||
<button class="oe_button oe_highlight">Write an email</button>
|
||||
<!-- <button class="oe_button oe_highlight">Write an email</button> -->
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
@ -262,33 +257,18 @@
|
|||
Template used to display Like/Unlike in a mail.message
|
||||
-->
|
||||
<span t-name="mail.thread.message.vote">
|
||||
<span class="oe_left oe_mail_vote_count">
|
||||
<t t-if='widget.datasets.has_voted'>
|
||||
You
|
||||
</t>
|
||||
<t t-if='(widget.datasets.vote_user_ids.length-(widget.datasets.has_voted?1:0)) > 0'>
|
||||
<t t-if='widget.datasets.has_voted'> and </t>
|
||||
<t t-esc="widget.datasets.vote_user_ids.length"/> people
|
||||
</t>
|
||||
<t t-if='widget.datasets.vote_user_ids.length > 0'>
|
||||
agree
|
||||
</t>
|
||||
|
||||
<span class="oe_mail_vote_count" t-if='widget.vote_nb > 0'>
|
||||
<t t-raw='widget.vote_nb' />
|
||||
<span class='oe_e'>8</span>
|
||||
</span>
|
||||
<button t-attf-class="oe_msg_vote oe_tag">
|
||||
<span>
|
||||
<t t-if="!widget.has_voted">Agree</t>
|
||||
<t t-if="widget.has_voted">Undo</t>
|
||||
</span>
|
||||
</button>
|
||||
<a href='#' t-attf-class="oe_msg_vote">
|
||||
<t t-if="!widget.has_voted">like</t>
|
||||
<t t-if="widget.has_voted">unlike</t>
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<!-- mail.thread.message.star
|
||||
Template used to display stared/unstared message in a mail.message
|
||||
-->
|
||||
<span t-name="mail.thread.message.star">
|
||||
<span class="oe_left">
|
||||
<button t-attf-class="oe_mail_starbox oe_tag #{widget.datasets.is_favorite?'oe_stared':''}">*</button>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
</template>
|
||||
|
|
|
@ -5,51 +5,45 @@
|
|||
followers main template
|
||||
Template used to display the followers, the actions and the subtypes in a record.
|
||||
-->
|
||||
<div t-name="mail.followers" class="oe_mail_recthread_aside oe_semantic_html_override">
|
||||
<div class="oe_mail_recthread_actions">
|
||||
<div class="oe_mail_subtypes">
|
||||
<button type="button" class="oe_follower oe_notfollow">
|
||||
<span class="oe_follow">Follow</span>
|
||||
<span class="oe_unfollow">Unfollow</span>
|
||||
<span class="oe_following">Following</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oe_recthread_subtypes">
|
||||
<ul class="oe_subtypes"></ul>
|
||||
</div>
|
||||
<div t-name="mail.followers" class="oe_followers">
|
||||
<div class="oe_actions">
|
||||
<button type="button" class="oe_follower oe_notfollow">
|
||||
<span class="oe_follow">Follow</span>
|
||||
<span class="oe_unfollow">Unfollow</span>
|
||||
<span class="oe_following">Following</span>
|
||||
</button>
|
||||
<div class="oe_subtype_list"></div>
|
||||
</div>
|
||||
<t t-if="widget.options.comment">
|
||||
<h5 class="oe_grey"><t t-raw="widget.options.comment"/></h5>
|
||||
<t t-if="widget.comment">
|
||||
<h5 class="oe_comment"><t t-raw="widget.comment"/></h5>
|
||||
</t>
|
||||
<div class="oe_mail_recthread_followers">
|
||||
<button type="button" class="oe_invite"><span>Invite</span></button>
|
||||
<t t-if="widget.options.title">
|
||||
<h4><t t-raw="widget.options.title"/></h4>
|
||||
</t>
|
||||
<ul class="oe_mail_followers_display"></ul>
|
||||
<div class='oe_follower_title_box'>
|
||||
<h4 class='oe_follower_title'>Followers</h4>
|
||||
<a href='#' class="oe_invite">Invite others</a>
|
||||
</div>
|
||||
<div class="oe_follower_list"></div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
followers.partner template
|
||||
Template used to display a partner following the record
|
||||
-->
|
||||
<li t-name="mail.followers.partner">
|
||||
<div t-name="mail.followers.partner" class='oe_partner'>
|
||||
<img class="oe_mail_thumbnail oe_mail_frame" t-attf-src="{record.avatar_url}"/>
|
||||
<a t-attf-href="#model=res.partner&id=#{record.id}"><t t-raw="record.name"/></a>
|
||||
</li>\
|
||||
</div>
|
||||
|
||||
<!--
|
||||
followers.subtype template
|
||||
Template used to display message subtypes of a follower subscription
|
||||
-->
|
||||
<li t-name="mail.followers.subtype">
|
||||
<table width="50%">
|
||||
<t t-name="mail.followers.subtype">
|
||||
<table class='oe_subtype'>
|
||||
<tr>
|
||||
<td><label t-att-for="'input_mail_followers_subtype_'+record.id"><t t-raw="record.name"/></label></td>
|
||||
<td width="10%"><input type="checkbox" t-att-checked="record.followed" t-att-id="'input_mail_followers_subtype_'+record.id" t-att-data-id="record.id" t-att-name="record.name" class="oe_msg_subtype_check"/></td>
|
||||
<td><label t-att-for="'input_mail_followers_subtype_'+record.id"><t t-raw="record.name"/></label></td>
|
||||
</tr>
|
||||
</table>
|
||||
</li>
|
||||
</t>
|
||||
|
||||
</template>
|
||||
|
|
|
@ -94,6 +94,8 @@ class TestMailMockups(common.TransactionCase):
|
|||
self._build_email_kwargs_list = []
|
||||
|
||||
def _mock_build_email(self, *args, **kwargs):
|
||||
""" Mock build_email to be able to test its values. Store them into
|
||||
some internal variable for latter processing. """
|
||||
self._build_email_args_list.append(args)
|
||||
self._build_email_kwargs_list.append(kwargs)
|
||||
return self._build_email(*args, **kwargs)
|
||||
|
@ -123,6 +125,7 @@ class test_mail(TestMailMockups):
|
|||
|
||||
def setUp(self):
|
||||
super(test_mail, self).setUp()
|
||||
cr, uid = self.cr, self.uid
|
||||
self.ir_model = self.registry('ir.model')
|
||||
self.mail_alias = self.registry('mail.alias')
|
||||
self.mail_thread = self.registry('mail.thread')
|
||||
|
@ -135,22 +138,27 @@ class test_mail(TestMailMockups):
|
|||
self.res_users = self.registry('res.users')
|
||||
self.res_partner = self.registry('res.partner')
|
||||
|
||||
# Find Employee group
|
||||
group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
|
||||
group_employee_id = group_employee_ref and group_employee_ref[1] or False
|
||||
# Test users
|
||||
self.user_demo_id = self.registry('ir.model.data').get_object_reference(self.cr, self.uid, 'base', 'user_demo')[1]
|
||||
self.user_admin = self.res_users.browse(self.cr, self.uid, self.uid)
|
||||
self.user_raoul_id = self.res_users.create(cr, uid,
|
||||
{'name': 'Raoul Grosbedon', 'email': 'raoul@raoul.fr', 'login': 'raoul', 'groups_id': [(6, 0, [group_employee_id])]})
|
||||
self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
|
||||
self.user_admin = self.res_users.browse(cr, uid, uid)
|
||||
|
||||
# Mock send_get_mail_body to test its functionality without other addons override
|
||||
self._send_get_mail_body = self.registry('mail.mail').send_get_mail_body
|
||||
self.registry('mail.mail').send_get_mail_body = self._mock_send_get_mail_body
|
||||
|
||||
# groups@.. will cause the creation of new mail groups
|
||||
self.mail_group_model_id = self.ir_model.search(self.cr, self.uid, [('model', '=', 'mail.group')])[0]
|
||||
self.mail_alias.create(self.cr, self.uid, {'alias_name': 'groups',
|
||||
self.mail_group_model_id = self.ir_model.search(cr, uid, [('model', '=', 'mail.group')])[0]
|
||||
self.mail_alias.create(cr, uid, {'alias_name': 'groups',
|
||||
'alias_model_id': self.mail_group_model_id})
|
||||
# create a 'pigs' group that will be used through the various tests
|
||||
self.group_pigs_id = self.mail_group.create(self.cr, self.uid,
|
||||
self.group_pigs_id = self.mail_group.create(cr, uid,
|
||||
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
|
||||
self.group_pigs = self.mail_group.browse(self.cr, self.uid, self.group_pigs_id)
|
||||
self.group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
|
||||
|
||||
def tearDown(self):
|
||||
# Remove mocks
|
||||
|
@ -159,7 +167,7 @@ class test_mail(TestMailMockups):
|
|||
|
||||
def test_00_message_process(self):
|
||||
""" Testing incoming emails processing. """
|
||||
cr, uid = self.cr, self.uid
|
||||
cr, uid, user_raoul = self.cr, self.uid, self.user_raoul
|
||||
# Incoming mail creates a new mail_group "frogs"
|
||||
self.assertEqual(self.mail_group.search(cr, uid, [('name', '=', 'frogs')]), [])
|
||||
mail_frogs = MAIL_TEMPLATE.format(to='groups@example.com, other@gmail.com', subject='frogs', extra='')
|
||||
|
@ -197,6 +205,26 @@ class test_mail(TestMailMockups):
|
|||
self.assertEqual(new_mail.body, '\n<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>\n',
|
||||
'plaintext mail incorrectly parsed')
|
||||
|
||||
# Do: post a new message, with a known partner
|
||||
test_msg_id = '<deadcafe.1337-2@smtp.agrolait.com>'
|
||||
TEMPLATE_MOD = MAIL_TEMPLATE_PLAINTEXT.replace('Sylvie Lelitre <sylvie.lelitre@agrolait.com>', user_raoul.email)
|
||||
mail_new = TEMPLATE_MOD.format(to='Friendly Frogs <group+frogs@example.com>', subject='extra news', extra='', msg_id=test_msg_id)
|
||||
self.mail_thread.message_process(cr, uid, None, mail_new)
|
||||
new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id', '=', test_msg_id)])[0])
|
||||
# Test: author_id set, not email_from
|
||||
self.assertEqual(new_mail.author_id, user_raoul.partner_id, 'message process wrong author found')
|
||||
self.assertFalse(new_mail.email_from, 'message process should not set the email_from when an author is found')
|
||||
|
||||
# Do: post a new message, with a unknown partner
|
||||
test_msg_id = '<deadcafe.1337-3@smtp.agrolait.com>'
|
||||
TEMPLATE_MOD = MAIL_TEMPLATE_PLAINTEXT.replace('Sylvie Lelitre <sylvie.lelitre@agrolait.com>', '_abcd_')
|
||||
mail_new = TEMPLATE_MOD.format(to='Friendly Frogs <group+frogs@example.com>', subject='super news', extra='', msg_id=test_msg_id)
|
||||
self.mail_thread.message_process(cr, uid, None, mail_new)
|
||||
new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id', '=', test_msg_id)])[0])
|
||||
# Test: author_id set, not email_from
|
||||
self.assertFalse(new_mail.author_id, 'message process shnould not have found a partner for _abcd_ email address')
|
||||
self.assertIn('_abcd_', new_mail.email_from, 'message process should set en email_from when not finding a partner_id')
|
||||
|
||||
def test_10_followers_function_field(self):
|
||||
""" Tests designed for the many2many function field 'follower_ids'.
|
||||
We will test to perform writes using the many2many commands 0, 3, 4,
|
||||
|
@ -260,7 +288,7 @@ class test_mail(TestMailMockups):
|
|||
""" Tests designed for the subscriber API as well as message subtypes """
|
||||
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
|
||||
# Data: user Raoul
|
||||
user_raoul_id = self.res_users.create(cr, uid, {'name': 'Raoul Grosbedon', 'login': 'raoul'})
|
||||
user_raoul_id = self.user_raoul_id
|
||||
user_raoul = self.res_users.browse(cr, uid, user_raoul_id)
|
||||
# Data: message subtypes
|
||||
self.mail_message_subtype.create(cr, uid, {'name': 'mt_mg_def', 'default': True, 'res_model': 'mail.group'})
|
||||
|
@ -311,7 +339,25 @@ class test_mail(TestMailMockups):
|
|||
self.assertTrue(subtype_data['mt_mg_nodef']['followed'], 'Admin should follow mt_mg_nodef in pigs')
|
||||
self.assertTrue(subtype_data['mt_all_nodef']['followed'], 'Admin should follow mt_all_nodef in pigs')
|
||||
|
||||
def test_20_message_post(self):
|
||||
def test_20_message_quote_context(self):
|
||||
""" Tests designed for message_post. """
|
||||
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
|
||||
|
||||
msg1_id = self.mail_message.create(cr, uid, {'body': 'Thread header about Zap Brannigan', 'subject': 'My subject'})
|
||||
msg2_id = self.mail_message.create(cr, uid, {'body': 'First answer, should not be displayed', 'subject': 'Re: My subject', 'parent_id': msg1_id})
|
||||
msg3_id = self.mail_message.create(cr, uid, {'body': 'Second answer', 'subject': 'Re: My subject', 'parent_id': msg1_id})
|
||||
msg4_id = self.mail_message.create(cr, uid, {'body': 'Third answer', 'subject': 'Re: My subject', 'parent_id': msg1_id})
|
||||
msg_new_id = self.mail_message.create(cr, uid, {'body': 'My answer I am propagating', 'subject': 'Re: My subject', 'parent_id': msg1_id})
|
||||
|
||||
result = self.mail_message.message_quote_context(cr, uid, msg_new_id, limit=3)
|
||||
self.assertIn('Thread header about Zap Brannigan', result, 'Thread header content should be in quote.')
|
||||
self.assertIn('Second answer', result, 'Answer should be in quote.')
|
||||
self.assertIn('Third answer', result, 'Answer should be in quote.')
|
||||
self.assertIn('expandable', result, 'Expandable should be present.')
|
||||
self.assertNotIn('First answer, should not be displayed', result, 'Old answer should not be in quote.')
|
||||
self.assertNotIn('My answer I am propagating', result, 'Thread header content should be in quote.')
|
||||
|
||||
def test_21_message_post(self):
|
||||
""" Tests designed for message_post. """
|
||||
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
|
||||
self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'})
|
||||
|
@ -357,11 +403,11 @@ class test_mail(TestMailMockups):
|
|||
# the html2plaintext uses etree or beautiful soup, so the result may be slighly different
|
||||
# depending if you have installed beautiful soup.
|
||||
self.assertIn(sent_email['body_alternative'], _mail_bodyalt1 + '\nBert Tartopoils\n', 'sent_email body_alternative is incorrect')
|
||||
# Test: mail_message: partner_ids = group followers
|
||||
message_pids = set([partner.id for partner in message.partner_ids])
|
||||
# Test: mail_message: notified_partner_ids = group followers
|
||||
message_pids = set([partner.id for partner in message.notified_partner_ids])
|
||||
test_pids = set([p_b_id, p_c_id])
|
||||
self.assertEqual(test_pids, message_pids, 'mail.message partners incorrect')
|
||||
# Test: notification linked to this message = group followers = partner_ids
|
||||
# Test: notification linked to this message = group followers = notified_partner_ids
|
||||
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
|
||||
notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
|
||||
self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
|
||||
|
@ -390,11 +436,11 @@ class test_mail(TestMailMockups):
|
|||
self.assertEqual(sent_email['subject'], _mail_subject, 'sent_email subject incorrect')
|
||||
self.assertIn(_mail_body2, sent_email['body'], 'sent_email body incorrect')
|
||||
self.assertIn(_mail_bodyalt2, sent_email['body_alternative'], 'sent_email body_alternative incorrect')
|
||||
# Test: mail_message: partner_ids = group followers
|
||||
message_pids = set([partner.id for partner in message.partner_ids])
|
||||
# Test: mail_message: notified_partner_ids = group followers
|
||||
message_pids = set([partner.id for partner in message.notified_partner_ids])
|
||||
test_pids = set([p_b_id, p_c_id, p_d_id])
|
||||
self.assertEqual(message_pids, test_pids, 'mail.message partners incorrect')
|
||||
# Test: notifications linked to this message = group followers = partner_ids
|
||||
# Test: notifications linked to this message = group followers = notified_partner_ids
|
||||
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
|
||||
notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
|
||||
self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
|
||||
|
@ -408,6 +454,11 @@ class test_mail(TestMailMockups):
|
|||
self.assertIn((attach.name, attach.datas.decode('base64')), _attachments,
|
||||
'mail.message attachment name / data incorrect')
|
||||
|
||||
# 3. Reply to the last message, check that its parent will be the first message
|
||||
msg_id3 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Test', parent_id=msg_id2)
|
||||
message = self.mail_message.browse(cr, uid, msg_id3)
|
||||
self.assertEqual(message.parent_id.id, msg_id, 'message_post did not flatten the thread structure')
|
||||
|
||||
def test_25_message_compose_wizard(self):
|
||||
""" Tests designed for the mail.compose.message wizard. """
|
||||
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
|
||||
|
@ -458,12 +509,12 @@ class test_mail(TestMailMockups):
|
|||
# Test: mail.message: subject, body inside pre
|
||||
self.assertEqual(message.subject, False, 'mail.message incorrect subject')
|
||||
self.assertEqual(message.body, _msg_body, 'mail.message incorrect body')
|
||||
# Test: mail.message: partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d)
|
||||
msg_pids = [partner.id for partner in message.partner_ids]
|
||||
# Test: mail.message: notified_partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d)
|
||||
msg_pids = [partner.id for partner in message.notified_partner_ids]
|
||||
test_pids = [p_b_id, p_c_id, p_d_id]
|
||||
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
|
||||
self.assertEqual(len(notif_ids), 3, 'mail.message: too much notifications created')
|
||||
self.assertEqual(set(msg_pids), set(test_pids), 'mail.message partner_ids incorrect')
|
||||
self.assertEqual(set(msg_pids), set(test_pids), 'mail.message notified_partner_ids incorrect')
|
||||
|
||||
# ----------------------------------------
|
||||
# CASE2: reply to last comment with attachments
|
||||
|
@ -482,7 +533,7 @@ class test_mail(TestMailMockups):
|
|||
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message incorrect content_subtype')
|
||||
# Test: mail.message: subject as Re:.., body in html, parent_id
|
||||
self.assertEqual(compose.subject, _msg_reply, 'mail.message incorrect subject')
|
||||
self.assertIn('Administrator wrote:<blockquote><pre>Pigs rules</pre></blockquote>', compose.body, 'mail.message body is incorrect')
|
||||
# self.assertIn('Administrator wrote:<blockquote><pre>Pigs rules</pre></blockquote>', compose.body, 'mail.message body is incorrect')
|
||||
self.assertEqual(compose.parent_id and compose.parent_id.id, message.id, 'mail.message parent_id incorrect')
|
||||
# Test: mail.message: attachments
|
||||
for attach in compose.attachment_ids:
|
||||
|
@ -522,15 +573,158 @@ class test_mail(TestMailMockups):
|
|||
|
||||
def test_30_message_read(self):
|
||||
""" Tests for message_read and expandables. """
|
||||
self.assertTrue(1 == 1, 'Test not implemented, do not replace by return True')
|
||||
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
|
||||
pigs_domain = [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)]
|
||||
|
||||
# Data: create a discussion in Pigs (2 messages, one with 2 and one with 3 answers)
|
||||
msg_id0 = self.group_pigs.message_post(body='0', subtype='mt_comment')
|
||||
msg_id1 = self.group_pigs.message_post(body='1', subtype='mt_comment')
|
||||
msg_id2 = self.group_pigs.message_post(body='2', subtype='mt_comment')
|
||||
msg_id3 = self.group_pigs.message_post(body='1-1', subtype='mt_comment', parent_id=msg_id1)
|
||||
msg_id4 = self.group_pigs.message_post(body='2-1', subtype='mt_comment', parent_id=msg_id2)
|
||||
msg_id5 = self.group_pigs.message_post(body='1-2', subtype='mt_comment', parent_id=msg_id1)
|
||||
msg_id6 = self.group_pigs.message_post(body='2-2', subtype='mt_comment', parent_id=msg_id2)
|
||||
msg_id7 = self.group_pigs.message_post(body='1-1-1', subtype='mt_comment', parent_id=msg_id3)
|
||||
msg_id8 = self.group_pigs.message_post(body='2-1-1', subtype='mt_comment', parent_id=msg_id4)
|
||||
msg_id9 = self.group_pigs.message_post(body='1-1-1', subtype='mt_comment', parent_id=msg_id3)
|
||||
msg_id10 = self.group_pigs.message_post(body='2-1-1', subtype='mt_comment', parent_id=msg_id4)
|
||||
msg_ids = [msg_id0, msg_id1, msg_id2, msg_id3, msg_id4, msg_id5, msg_id6, msg_id7, msg_id8, msg_id9, msg_id10]
|
||||
|
||||
# Test: read some specific ids
|
||||
read_msg_list = self.mail_message.message_read(cr, uid, ids=msg_ids[2:4], domain=[('body', 'like', 'dummy')])
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list]
|
||||
self.assertEqual(msg_ids[2:4], read_msg_ids, 'message_read with direct ids should read only the requested ids')
|
||||
|
||||
# Test: read messages of Pigs through a domain, being thread or not threaded
|
||||
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=200)
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list]
|
||||
self.assertEqual(msg_ids, read_msg_ids, 'message_read flat with domain on Pigs should equal all messages of Pigs')
|
||||
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=200, thread_level=1)
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list]
|
||||
self.assertEqual(msg_ids, read_msg_ids, 'message_read threaded with domain on Pigs should equal all messages of Pigs')
|
||||
|
||||
# ----------------------------------------
|
||||
# CASE1: message_read with domain, threaded
|
||||
# We simulate an entire flow, using the expandables to test them
|
||||
# ----------------------------------------
|
||||
|
||||
# Do: read last message, threaded
|
||||
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=1, thread_level=1)
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
|
||||
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
|
||||
self.assertEqual(len(read_msg_list), 4, 'message_read on last Pigs message should return 2 messages and 2 expandables')
|
||||
self.assertEqual(set([msg_id2, msg_id10]), set(read_msg_ids), 'message_read on the last Pigs message should also get its parent')
|
||||
self.assertEqual(read_msg_list[1].get('parent_id'), read_msg_list[0].get('id'), 'message_read should set the ancestor to the thread header')
|
||||
# Data: get expandables
|
||||
new_threads_exp, new_msg_exp = None, None
|
||||
for msg in read_msg_list:
|
||||
if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('id') == -1:
|
||||
new_threads_exp = msg
|
||||
elif msg.get('type') == 'expandable':
|
||||
new_msg_exp = msg
|
||||
|
||||
# Do: fetch new messages in first thread, domain from expandable
|
||||
self.assertIsNotNone(new_msg_exp, 'message_read on last Pigs message should have returned a new messages expandable')
|
||||
domain = new_msg_exp.get('domain', [])
|
||||
# Test: expandable, conditions in domain
|
||||
self.assertIn(('id', 'child_of', msg_id2), domain, 'new messages expandable domain should contain a child_of condition')
|
||||
self.assertIn(('id', '>=', msg_id4), domain, 'new messages expandable domain should contain an id greater than condition')
|
||||
self.assertIn(('id', '<=', msg_id8), domain, 'new messages expandable domain should contain an id less than condition')
|
||||
self.assertEqual(new_msg_exp.get('parent_id'), msg_id2, 'new messages expandable should have ancestor_id set to the thread header')
|
||||
# Do: message_read with domain, thread_level=0, parent_id=msg_id2 (should be imposed by JS)
|
||||
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=200, thread_level=0, parent_id=msg_id2)
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
|
||||
# Test: other message in thread have been fetch
|
||||
self.assertEqual(set([msg_id4, msg_id6, msg_id8]), set(read_msg_ids), 'message_read in Pigs thread should return all the previous messages')
|
||||
|
||||
# Do: fetch a new thread, domain from expandable
|
||||
self.assertIsNotNone(new_threads_exp, 'message_read on last Pigs message should have returned a new threads expandable')
|
||||
domain = new_threads_exp.get('domain', [])
|
||||
# Test: expandable, conditions in domain
|
||||
for condition in pigs_domain:
|
||||
self.assertIn(condition, domain, 'new threads expandable domain should contain the message_read domain parameter')
|
||||
self.assertFalse(new_threads_exp.get('parent_id'), 'new threads expandable should not have an ancestor_id')
|
||||
# Do: message_read with domain, thread_level=1 (should be imposed by JS)
|
||||
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=1, thread_level=1)
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
|
||||
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
|
||||
self.assertEqual(len(read_msg_list), 4, 'message_read on Pigs should return 2 messages and 2 expandables')
|
||||
self.assertEqual(set([msg_id1, msg_id9]), set(read_msg_ids), 'message_read on a Pigs message should also get its parent')
|
||||
self.assertEqual(read_msg_list[1].get('parent_id'), read_msg_list[0].get('id'), 'message_read should set the ancestor to the thread header')
|
||||
# Data: get expandables
|
||||
new_threads_exp, new_msg_exp = None, None
|
||||
for msg in read_msg_list:
|
||||
if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('id') == -1:
|
||||
new_threads_exp = msg
|
||||
elif msg.get('type') == 'expandable':
|
||||
new_msg_exp = msg
|
||||
|
||||
# Do: fetch new messages in second thread, domain from expandable
|
||||
self.assertIsNotNone(new_msg_exp, 'message_read on Pigs message should have returned a new messages expandable')
|
||||
domain = new_msg_exp.get('domain', [])
|
||||
# Test: expandable, conditions in domain
|
||||
self.assertIn(('id', 'child_of', msg_id1), domain, 'new messages expandable domain should contain a child_of condition')
|
||||
self.assertIn(('id', '>=', msg_id3), domain, 'new messages expandable domain should contain an id greater than condition')
|
||||
self.assertIn(('id', '<=', msg_id7), domain, 'new messages expandable domain should contain an id less than condition')
|
||||
self.assertEqual(new_msg_exp.get('parent_id'), msg_id1, 'new messages expandable should have ancestor_id set to the thread header')
|
||||
# Do: message_read with domain, thread_level=0, parent_id=msg_id1 (should be imposed by JS)
|
||||
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=200, thread_level=0, parent_id=msg_id1)
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
|
||||
# Test: other message in thread have been fetch
|
||||
self.assertEqual(set([msg_id3, msg_id5, msg_id7]), set(read_msg_ids), 'message_read on the last Pigs message should also get its parent')
|
||||
|
||||
# Test: fetch a new thread, domain from expandable
|
||||
self.assertIsNotNone(new_threads_exp, 'message_read should have returned a new threads expandable')
|
||||
domain = new_threads_exp.get('domain', [])
|
||||
# Test: expandable, conditions in domain
|
||||
for condition in pigs_domain:
|
||||
self.assertIn(condition, domain, 'general expandable domain should contain the message_read domain parameter')
|
||||
# Do: message_read with domain, thread_level=1 (should be imposed by JS)
|
||||
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=1, thread_level=1)
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
|
||||
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
|
||||
self.assertEqual(len(read_msg_list), 1, 'message_read on Pigs should return 1 message because everything else has been fetched')
|
||||
self.assertEqual([msg_id0], read_msg_ids, 'message_read after 2 More should return only 1 last message')
|
||||
|
||||
# ----------------------------------------
|
||||
# CASE2: message_read with domain, flat
|
||||
# ----------------------------------------
|
||||
|
||||
# Do: read 2 lasts message, flat
|
||||
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=2, thread_level=0)
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
|
||||
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is not set, 1 expandable
|
||||
self.assertEqual(len(read_msg_list), 3, 'message_read on last Pigs message should return 2 messages and 1 expandable')
|
||||
self.assertEqual(set([msg_id9, msg_id10]), set(read_msg_ids), 'message_read flat on Pigs last messages should only return those messages')
|
||||
self.assertFalse(read_msg_list[0].get('parent_id'), 'message_read flat should set the ancestor as False')
|
||||
self.assertFalse(read_msg_list[1].get('parent_id'), 'message_read flat should set the ancestor as False')
|
||||
# Data: get expandables
|
||||
new_threads_exp, new_msg_exp = None, None
|
||||
for msg in read_msg_list:
|
||||
if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('id') == -1:
|
||||
new_threads_exp = msg
|
||||
|
||||
# Do: fetch new messages, domain from expandable
|
||||
self.assertIsNotNone(new_threads_exp, 'message_read flat on the 2 last Pigs messages should have returns a new threads expandable')
|
||||
domain = new_threads_exp.get('domain', [])
|
||||
# Test: expandable, conditions in domain
|
||||
for condition in pigs_domain:
|
||||
self.assertIn(condition, domain, 'new threads expandable domain should contain the message_read domain parameter')
|
||||
# Do: message_read with domain, thread_level=0 (should be imposed by JS)
|
||||
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=20, thread_level=0)
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
|
||||
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
|
||||
self.assertEqual(len(read_msg_list), 9, 'message_read on Pigs should return 9 messages and 0 expandable')
|
||||
self.assertEqual([msg_id0, msg_id1, msg_id2, msg_id3, msg_id4, msg_id5, msg_id6, msg_id7, msg_id8], read_msg_ids,
|
||||
'message_read, More on flat, should return all remaning messages')
|
||||
|
||||
def test_40_needaction(self):
|
||||
""" Tests for mail.message needaction. """
|
||||
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
|
||||
user_demo = self.res_users.browse(cr, uid, self.user_demo_id)
|
||||
group_pigs_demo = self.mail_group.browse(cr, self.user_demo_id, self.group_pigs_id)
|
||||
user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
|
||||
group_pigs_demo = self.mail_group.browse(cr, self.user_raoul_id, self.group_pigs_id)
|
||||
na_admin_base = self.mail_message._needaction_count(cr, uid, domain=[])
|
||||
na_demo_base = self.mail_message._needaction_count(cr, user_demo.id, domain=[])
|
||||
na_demo_base = self.mail_message._needaction_count(cr, user_raoul.id, domain=[])
|
||||
|
||||
# Test: number of unread notification = needaction on mail.message
|
||||
notif_ids = self.mail_notification.search(cr, uid, [
|
||||
|
@ -558,12 +752,12 @@ class test_mail(TestMailMockups):
|
|||
self.assertEqual(na_admin_group, 3, 'Admin should have 3 needaction related to Pigs')
|
||||
# Test: demo has 0 new notifications (not a follower, not receiving its own messages), and 0 new needaction
|
||||
notif_ids = self.mail_notification.search(cr, uid, [
|
||||
('partner_id', '=', user_demo.partner_id.id),
|
||||
('partner_id', '=', user_raoul.partner_id.id),
|
||||
('read', '=', False)
|
||||
])
|
||||
self.assertEqual(len(notif_ids), na_demo_base + 0, 'Demo should have 0 new unread notifications')
|
||||
na_demo = self.mail_message._needaction_count(cr, user_demo.id, domain=[])
|
||||
na_demo_group = self.mail_message._needaction_count(cr, user_demo.id, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
|
||||
na_demo = self.mail_message._needaction_count(cr, user_raoul.id, domain=[])
|
||||
na_demo_group = self.mail_message._needaction_count(cr, user_raoul.id, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
|
||||
self.assertEqual(na_demo, na_demo_base + 0, 'Demo should have 0 new needaction')
|
||||
self.assertEqual(na_demo_group, 0, 'Demo should have 0 needaction related to Pigs')
|
||||
|
||||
|
@ -581,19 +775,11 @@ class test_mail(TestMailMockups):
|
|||
reply_msg = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: 1',
|
||||
extra='In-Reply-To: %s' % msg1.message_id)
|
||||
self.mail_group.message_process(cr, uid, None, reply_msg)
|
||||
# TDE note: temp various asserts because of the random bug about msg1.child_ids
|
||||
msg_ids = self.mail_message.search(cr, uid, [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], limit=1)
|
||||
new_msg = self.mail_message.browse(cr, uid, msg_ids[0])
|
||||
self.assertEqual(new_msg.parent_id, msg1, 'Newly processed mail_message (%d) should have msg1 as parent' % (new_msg.id))
|
||||
|
||||
# 2. References header
|
||||
reply_msg2 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: Re: 1',
|
||||
extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % msg1.message_id)
|
||||
self.mail_group.message_process(cr, uid, None, reply_msg2)
|
||||
# TDE note: temp various asserts because of the random bug about msg1.child_ids
|
||||
msg_ids = self.mail_message.search(cr, uid, [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], limit=1)
|
||||
new_msg = self.mail_message.browse(cr, uid, msg_ids[0])
|
||||
self.assertEqual(new_msg.parent_id, msg1, 'Newly processed mail_message should have msg1 as parent')
|
||||
|
||||
# 3. Subject contains [<ID>] + model passed to message+process -> only attached to group, not to mail
|
||||
reply_msg3 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com',
|
||||
|
@ -613,34 +799,46 @@ class test_mail(TestMailMockups):
|
|||
|
||||
def test_60_message_vote(self):
|
||||
""" Test designed for the vote/unvote feature. """
|
||||
cr, uid = self.cr, self.uid
|
||||
user_admin = self.res_users.browse(cr, uid, uid)
|
||||
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
|
||||
msg1 = group_pigs.message_post(body='My Body', subject='1')
|
||||
msg1 = self.mail_message.browse(cr, uid, msg1)
|
||||
cr, uid, user_admin, user_raoul, group_pigs = self.cr, self.uid, self.user_admin, self.user_raoul, self.group_pigs
|
||||
# Data: post a message on Pigs
|
||||
msg_id = group_pigs.message_post(body='My Body', subject='1')
|
||||
msg = self.mail_message.browse(cr, uid, msg_id)
|
||||
|
||||
# Create user Bert Tartopoils
|
||||
user_bert_id = self.res_users.create(cr, uid, {'name': 'Bert', 'login': 'bert'})
|
||||
user_bert = self.res_users.browse(cr, uid, user_bert_id)
|
||||
|
||||
# Test: msg1 and msg2 have void vote_user_ids
|
||||
self.assertFalse(msg1.vote_user_ids, 'newly created message msg1 has not void vote_user_ids')
|
||||
# Do: Admin vote for msg1
|
||||
self.mail_message.vote_toggle(cr, uid, [msg1.id])
|
||||
msg1.refresh()
|
||||
# Test: msg1 has Admin as voter
|
||||
self.assertEqual(set(msg1.vote_user_ids), set([user_admin]), 'after voting, Admin is not the voter')
|
||||
# Do: Bert vote for msg1
|
||||
self.mail_message.vote_toggle(cr, user_bert_id, [msg1.id])
|
||||
msg1.refresh()
|
||||
# Test: msg1 has Admin and Bert as voters
|
||||
self.assertEqual(set(msg1.vote_user_ids), set([user_admin, user_bert]), 'after voting, Admin and Bert are not the voters')
|
||||
# Do: Admin unvote for msg1
|
||||
self.mail_message.vote_toggle(cr, uid, [msg1.id])
|
||||
msg1.refresh()
|
||||
# Test: msg1 has Bert as voter
|
||||
self.assertEqual(set(msg1.vote_user_ids), set([user_bert]), 'after unvoting for Admin, Bert is not the voter')
|
||||
# Do: Admin vote for msg
|
||||
self.mail_message.vote_toggle(cr, uid, [msg.id])
|
||||
msg.refresh()
|
||||
# Test: msg has Admin as voter
|
||||
self.assertEqual(set(msg.vote_user_ids), set([user_admin]), 'mail_message vote: after voting, Admin should be in the voter')
|
||||
# Do: Bert vote for msg
|
||||
self.mail_message.vote_toggle(cr, user_raoul.id, [msg.id])
|
||||
msg.refresh()
|
||||
# Test: msg has Admin and Bert as voters
|
||||
self.assertEqual(set(msg.vote_user_ids), set([user_admin, user_raoul]), 'mail_message vote: after voting, Admin and Bert should be in the voters')
|
||||
# Do: Admin unvote for msg
|
||||
self.mail_message.vote_toggle(cr, uid, [msg.id])
|
||||
msg.refresh()
|
||||
# Test: msg has Bert as voter
|
||||
self.assertEqual(set(msg.vote_user_ids), set([user_raoul]), 'mail_message vote: after unvoting, Bert should be in the voter')
|
||||
|
||||
def test_70_message_favorite(self):
|
||||
""" Tests for favorites. """
|
||||
self.assertTrue(1 == 1, 'Test not implemented, do not replace by return True')
|
||||
cr, uid, user_admin, user_raoul, group_pigs = self.cr, self.uid, self.user_admin, self.user_raoul, self.group_pigs
|
||||
# Data: post a message on Pigs
|
||||
msg_id = group_pigs.message_post(body='My Body', subject='1')
|
||||
msg = self.mail_message.browse(cr, uid, msg_id)
|
||||
|
||||
# Do: Admin stars msg
|
||||
self.mail_message.favorite_toggle(cr, uid, [msg.id])
|
||||
msg.refresh()
|
||||
# Test: msg starred by Admin
|
||||
self.assertEqual(set(msg.favorite_user_ids), set([user_admin]), 'mail_message favorite: after starring, Admin should be in favorite_user_ids')
|
||||
# Do: Bert stars msg
|
||||
self.mail_message.favorite_toggle(cr, user_raoul.id, [msg.id])
|
||||
msg.refresh()
|
||||
# Test: msg starred by Admin and Raoul
|
||||
self.assertEqual(set(msg.favorite_user_ids), set([user_admin, user_raoul]), 'mail_message favorite: after starring, Admin and Raoul should be in favorite_user_ids')
|
||||
# Do: Admin unvote for msg
|
||||
self.mail_message.favorite_toggle(cr, uid, [msg.id])
|
||||
msg.refresh()
|
||||
# Test: msg starred by Raoul
|
||||
self.assertEqual(set(msg.favorite_user_ids), set([user_raoul]), 'mail_message favorite: after unstarring, Raoul should be in favorite_user_ids')
|
||||
|
|
|
@ -51,7 +51,40 @@ class test_mail_access_rights(test_mail.TestMailMockups):
|
|||
self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
|
||||
self.partner_raoul_id = self.user_raoul.partner_id.id
|
||||
|
||||
def test_00_mail_message_read_access_rights(self):
|
||||
def test_00_mail_message_search_access_rights(self):
|
||||
""" Test mail_message search override about access rights. """
|
||||
cr, uid, group_pigs_id = self.cr, self.uid, self.group_pigs_id
|
||||
partner_bert_id, partner_raoul_id = self.partner_bert_id, self.partner_raoul_id
|
||||
user_bert_id, user_raoul_id = self.user_bert_id, self.user_raoul_id
|
||||
# Data: comment subtype for mail.message creation
|
||||
ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'mail', 'mt_comment')
|
||||
subtype_id = ref and ref[1] or False
|
||||
|
||||
# Data: Birds group, private
|
||||
group_birds_id = self.mail_group.create(self.cr, self.uid, {'name': 'Birds', 'public': 'private'})
|
||||
# Data: raoul is member of Pigs
|
||||
self.mail_group.message_subscribe(cr, uid, [group_pigs_id], [partner_raoul_id])
|
||||
# Data: various author_ids, partner_ids, documents
|
||||
msg_id1 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A', 'subtype_id': subtype_id})
|
||||
msg_id2 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A+B', 'partner_ids': [(6, 0, [partner_bert_id])], 'subtype_id': subtype_id})
|
||||
msg_id3 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A Pigs', 'model': 'mail.group', 'res_id': group_pigs_id, 'subtype_id': subtype_id})
|
||||
msg_id4 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A+B Pigs', 'model': 'mail.group', 'res_id': group_pigs_id, 'partner_ids': [(6, 0, [partner_bert_id])], 'subtype_id': subtype_id})
|
||||
msg_id5 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A+R Pigs', 'model': 'mail.group', 'res_id': group_pigs_id, 'partner_ids': [(6, 0, [partner_raoul_id])], 'subtype_id': subtype_id})
|
||||
msg_id6 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A Birds', 'model': 'mail.group', 'res_id': group_birds_id, 'subtype_id': subtype_id})
|
||||
msg_id7 = self.mail_message.create(cr, user_bert_id, {'subject': '_Test', 'body': 'B', 'subtype_id': subtype_id})
|
||||
msg_id8 = self.mail_message.create(cr, user_bert_id, {'subject': '_Test', 'body': 'B+R', 'partner_ids': [(6, 0, [partner_raoul_id])], 'subtype_id': subtype_id})
|
||||
|
||||
# Test: Bert: 2 messages that have Bert in partner_ids + 2 messages as author
|
||||
msg_ids = self.mail_message.search(cr, user_bert_id, [('subject', 'like', '_Test')])
|
||||
self.assertEqual(set([msg_id2, msg_id4, msg_id7, msg_id8]), set(msg_ids), 'mail_message search failed')
|
||||
# Test: Raoul: 3 messages on Pigs Raoul can read (employee can read group with default values), 0 on Birds (private group)
|
||||
msg_ids = self.mail_message.search(cr, user_raoul_id, [('subject', 'like', '_Test'), ('body', 'like', 'A')])
|
||||
self.assertEqual(set([msg_id3, msg_id4, msg_id5]), set(msg_ids), 'mail_message search failed')
|
||||
# Test: Admin: all messages
|
||||
msg_ids = self.mail_message.search(cr, uid, [('subject', 'like', '_Test')])
|
||||
self.assertEqual(set([msg_id1, msg_id2, msg_id3, msg_id4, msg_id5, msg_id6, msg_id7, msg_id8]), set(msg_ids), 'mail_message search failed')
|
||||
|
||||
def test_05_mail_message_read_access_rights(self):
|
||||
""" Test basic mail_message read access rights. """
|
||||
cr, uid = self.cr, self.uid
|
||||
partner_bert_id, partner_raoul_id = self.partner_bert_id, self.partner_raoul_id
|
||||
|
@ -98,10 +131,6 @@ class test_mail_access_rights(test_mail.TestMailMockups):
|
|||
self.assertRaises(except_orm, self.mail_message.read,
|
||||
cr, user_bert_id, message_id)
|
||||
|
||||
def test_05_mail_message_search_access_rights(self):
|
||||
""" Test mail_message search override about access rights. """
|
||||
self.assertTrue(1 == 1, 'Test not implemented, do not replace by return True')
|
||||
|
||||
def test_10_mail_flow_access_rights(self):
|
||||
""" Test a Chatter-looks alike flow. """
|
||||
cr, uid = self.cr, self.uid
|
||||
|
|
|
@ -156,12 +156,6 @@ class mail_compose_message(osv.TransientModel):
|
|||
reply_subject = tools.ustr(message_data.subject or '')
|
||||
if not (reply_subject.startswith('Re:') or reply_subject.startswith(re_prefix)) and message_data.subject:
|
||||
reply_subject = "%s %s" % (re_prefix, reply_subject)
|
||||
# create the reply in the body
|
||||
reply_body = _('<div>On %(date)s, %(sender_name)s wrote:<blockquote>%(body)s</blockquote></div>') % {
|
||||
'date': message_data.date if message_data.date else '',
|
||||
'sender_name': message_data.author_id.name,
|
||||
'body': message_data.body,
|
||||
}
|
||||
# get partner_ids from original message
|
||||
partner_ids = [partner.id for partner in message_data.partner_ids] if message_data.partner_ids else []
|
||||
|
||||
|
@ -170,7 +164,6 @@ class mail_compose_message(osv.TransientModel):
|
|||
'model': message_data.model,
|
||||
'res_id': message_data.res_id,
|
||||
'parent_id': message_data.id,
|
||||
'body': reply_body,
|
||||
'subject': reply_subject,
|
||||
'partner_ids': partner_ids,
|
||||
'content_subtype': 'html',
|
||||
|
|
|
@ -14,22 +14,16 @@
|
|||
<field name="parent_id" invisible="1"/>
|
||||
<field name="content_subtype" invisible="1"/>
|
||||
<!-- visible wizard -->
|
||||
<field name="subject" placeholder="Subject..."
|
||||
attrs="{'invisible':[('content_subtype', '=', 'plain')]}"/>/>
|
||||
<field name="partner_ids" widget="many2many_tags" placeholder="Add contacts to notify..."
|
||||
context="{'force_email':True}"
|
||||
on_change="onchange_partner_ids(partner_ids)"/>
|
||||
on_change="onchange_partner_ids(partner_ids)" required="1"/>
|
||||
<field name="subject" placeholder="Subject..."
|
||||
attrs="{'invisible':[('content_subtype', '=', 'plain')]}"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Body">
|
||||
<field name="body" nolabel="1"/>
|
||||
</page>
|
||||
<page string="Attachments">
|
||||
<field name="attachment_ids" colspan="4" nolabel="1"/>
|
||||
</page>
|
||||
</notebook>
|
||||
<field name="body"/>
|
||||
<field name="attachment_ids" widget="one2many_binary" blockui="0"/>
|
||||
<footer>
|
||||
<button string="Send" name="send_mail" type="object" class="oe_highlight" />
|
||||
<button string="Send" name="send_mail" type="object" class="oe_highlight"/>
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel" />
|
||||
</footer>
|
||||
|
|
|
@ -409,8 +409,8 @@
|
|||
</page>
|
||||
</notebook>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
@ -797,8 +797,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -107,8 +107,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -189,8 +189,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -42,7 +42,7 @@ Notes can be found in the 'Home' menu.
|
|||
'mail',
|
||||
],
|
||||
'data': [
|
||||
'security/res.groups.csv',
|
||||
'security/note_security.xml',
|
||||
'security/ir.rule.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'note_data.xml',
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="group_note_fancy" model="res.groups">
|
||||
<field name="name">Notes / Fancy mode</field>
|
||||
<field name="category_id" ref="base.module_category_hidden"/>
|
||||
</record>
|
||||
|
||||
<record id="base.group_user" model="res.groups">
|
||||
<field name="implied_ids" eval="[(4, ref('group_note_fancy'))]"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -1,2 +0,0 @@
|
|||
id,name,implied_ids/id,category_id/id
|
||||
group_note_fancy,Notes / Fancy mode,,base.module_category_hidden
|
|
|
@ -6,6 +6,7 @@ openerp.pad = function(instance) {
|
|||
content: "",
|
||||
render_value: function() {
|
||||
var self = this;
|
||||
var _super = _.bind(this._super, this);
|
||||
if (this.get("value") === false || this.get("value") === "") {
|
||||
self.view.dataset.call('pad_generate_url',{context:{
|
||||
model: self.view.model,
|
||||
|
@ -13,7 +14,7 @@ openerp.pad = function(instance) {
|
|||
object_id: self.view.datarecord.id
|
||||
}}).then(function(data) {
|
||||
if(data&&data.url){
|
||||
_super.apply(self,[data.url]);
|
||||
_super(data.url);
|
||||
self.renderElement();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -103,8 +103,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -166,8 +166,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
<menuitem id="menu_project_management" name="Project" parent="base.menu_main_pm" sequence="1"/>
|
||||
<menuitem id="base.menu_definitions" name="Configuration" parent="base.menu_main_pm" sequence="60"/>
|
||||
|
||||
|
||||
<record id="act_project_project_2_project_task_all" model="ir.actions.act_window">
|
||||
<field name="res_model">project.task</field>
|
||||
<field name="view_type">form</field>
|
||||
|
@ -141,9 +141,8 @@
|
|||
<field name="date" string="End Date"/>
|
||||
<field name="priority" groups="base.group_no_one"/>
|
||||
<field name="active" attrs="{'invisible':[('state','in',['open', 'pending', 'template'])]}"/>
|
||||
|
||||
<field name="currency_id" groups="base.group_multi_currency" required="1"/>
|
||||
<field name="parent_id" domain="[('id','!=',analytic_account_id)]" context="{'current_model': 'project.project'}"/>
|
||||
<field name="parent_id" string="Parent" help="Append this project to another one using analytic accounts hierarchy" domain="[('id','!=',analytic_account_id)]" context="{'current_model': 'project.project'}" />
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
|
@ -153,14 +152,14 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers" help="Follow this project to automatically follow all related tasks and issues."/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_project_project_filter" model="ir.ui.view">
|
||||
<record id="view_project_project_filter" model="ir.ui.view">
|
||||
<field name="name">project.project.select</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="arch" type="xml">
|
||||
|
@ -230,7 +229,7 @@
|
|||
<div class="oe_dropdown_toggle oe_dropdown_kanban">
|
||||
<span class="oe_e">í</span>
|
||||
<ul class="oe_dropdown_menu">
|
||||
<t t-if="widget.view.is_action_enabled('edit')"><li><a type="edit">Edit...</a></li></t>
|
||||
<t t-if="widget.view.is_action_enabled('edit')"><li><a type="edit">Project Settings</a></li></t>
|
||||
<t t-if="widget.view.is_action_enabled('delete')"><li><a type="delete">Delete</a></li></t>
|
||||
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
|
||||
</ul>
|
||||
|
@ -468,14 +467,14 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Project Task Kanban View -->
|
||||
<!-- Project Task Kanban View -->
|
||||
<record model="ir.ui.view" id="view_task_kanban">
|
||||
<field name="name">project.task.kanban</field>
|
||||
<field name="model">project.task</field>
|
||||
|
|
|
@ -159,8 +159,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<field name="partner_id"/>
|
||||
</field>
|
||||
<xpath expr="//div[contains(@class, 'oe_kanban_project_list')]" position="inside">
|
||||
<a t-if="record.use_timesheets.raw_value"
|
||||
<a t-if="record.use_timesheets.raw_value" style="margin-right: 10px"
|
||||
name="open_timesheets" type="object"><field name="hours_quantity"/> Hours</a>
|
||||
</xpath>
|
||||
</field>
|
||||
|
|
|
@ -287,8 +287,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -102,8 +102,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -69,7 +69,7 @@ class sale_configuration(osv.osv_memory):
|
|||
But the possibility to change these values is still available.
|
||||
This installs the module analytic_user_function."""),
|
||||
'module_project': fields.boolean("Project"),
|
||||
'module_sale_stock': fields.boolean("Sale and Warehouse Management",
|
||||
'module_sale_stock': fields.boolean("Trigger delivery orders automatically from sale orders",
|
||||
help="""Allows you to Make Quotation, Sale Order using different Order policy and Manage Related Stock.
|
||||
This installs the module sale_stock."""),
|
||||
}
|
||||
|
|
|
@ -309,8 +309,8 @@
|
|||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -37,7 +37,7 @@ modules.
|
|||
'author': 'OpenERP SA',
|
||||
'website': 'http://www.openerp.com',
|
||||
'images': ['images/crm_statistics_dashboard.jpeg', 'images/opportunity_to_quote.jpeg'],
|
||||
'depends': ['sale_stock', 'crm'],
|
||||
'depends': ['sale', 'crm'],
|
||||
'data': [
|
||||
'wizard/crm_make_sale_view.xml',
|
||||
'sale_crm_view.xml',
|
||||
|
|
|
@ -31,7 +31,7 @@ Price and Cost Price.
|
|||
""",
|
||||
'author':'OpenERP SA',
|
||||
'images':['images/sale_margin.jpeg'],
|
||||
'depends':['sale_stock'],
|
||||
'depends':['sale'],
|
||||
'demo':['sale_margin_demo.xml'],
|
||||
'test': ['test/sale_margin.yml'],
|
||||
'data':['security/ir.model.access.csv','sale_margin_view.xml'],
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 100.0
|
||||
state: draft
|
||||
delay: 7.0
|
||||
product_id: product.product_product_24
|
||||
product_uos_qty: 100.0
|
||||
th_weight: 0.0
|
||||
|
@ -23,7 +22,6 @@
|
|||
partner_id: base.res_partner_4
|
||||
partner_invoice_id: base.res_partner_address_7
|
||||
partner_shipping_id: base.res_partner_address_7
|
||||
picking_policy: direct
|
||||
pricelist_id: product.list0
|
||||
shop_id: sale.sale_shop_1
|
||||
-
|
||||
|
|
|
@ -618,7 +618,7 @@ class stock_picking(osv.osv):
|
|||
|
||||
def create(self, cr, user, vals, context=None):
|
||||
if ('name' not in vals) or (vals.get('name')=='/'):
|
||||
seq_obj_name = 'stock.picking.' + vals['type']
|
||||
seq_obj_name = self._name
|
||||
vals['name'] = self.pool.get('ir.sequence').get(cr, user, seq_obj_name)
|
||||
new_id = super(stock_picking, self).create(cr, user, vals, context)
|
||||
if new_id:
|
||||
|
|
|
@ -912,8 +912,8 @@
|
|||
</xpath>
|
||||
<xpath expr="/form/sheet" position="after">
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</data>
|
||||
|
@ -1038,8 +1038,8 @@
|
|||
</xpath>
|
||||
<xpath expr="/form/sheet" position="after">
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</data>
|
||||
|
|
|
@ -82,7 +82,7 @@ instance.web_shortcuts.Shortcuts = instance.web.Widget.extend({
|
|||
// TODO: Use do_action({menu_id: id, type: 'ir.actions.menu'})
|
||||
self.rpc('/web/menu/action', {'menu_id': id}).then(function(ir_menu_data) {
|
||||
if (ir_menu_data.action.length){
|
||||
instance.webclient.user_menu.on_action({action_id: ir_menu_data.action[0][2].id});
|
||||
instance.webclient.on_menu_action({action_id: ir_menu_data.action[0][2].id});
|
||||
}
|
||||
});
|
||||
this.$el.find('.oe_systray_shortcuts').trigger('mouseout');
|
||||
|
|
Loading…
Reference in New Issue