[MERGE] Sync with lp:openobject-addons.
bzr revid: psa@tinyerp.com-20130628051607-zi0xzbmhe6kly35t
This commit is contained in:
commit
a5a676ced9
|
@ -221,8 +221,8 @@ class account_invoice(osv.osv):
|
|||
'type': {
|
||||
},
|
||||
'state': {
|
||||
'account.mt_invoice_paid': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'paid' and obj['type'] in ('out_invoice', 'out_refund'),
|
||||
'account.mt_invoice_validated': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'open' and obj['type'] in ('out_invoice', 'out_refund'),
|
||||
'account.mt_invoice_paid': lambda self, cr, uid, obj, ctx=None: obj.state == 'paid' and obj.type in ('out_invoice', 'out_refund'),
|
||||
'account.mt_invoice_validated': lambda self, cr, uid, obj, ctx=None: obj.state == 'open' and obj.type in ('out_invoice', 'out_refund'),
|
||||
},
|
||||
}
|
||||
_columns = {
|
||||
|
|
|
@ -33,9 +33,9 @@ class account_analytic_account(osv.osv):
|
|||
_description = 'Analytic Account'
|
||||
_track = {
|
||||
'state': {
|
||||
'analytic.mt_account_pending': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'pending',
|
||||
'analytic.mt_account_closed': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'close',
|
||||
'analytic.mt_account_opened': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'open',
|
||||
'analytic.mt_account_pending': lambda self, cr, uid, obj, ctx=None: obj.state == 'pending',
|
||||
'analytic.mt_account_closed': lambda self, cr, uid, obj, ctx=None: obj.state == 'close',
|
||||
'analytic.mt_account_opened': lambda self, cr, uid, obj, ctx=None: obj.state == 'open',
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,8 @@ import openerp
|
|||
from openerp import SUPERUSER_ID
|
||||
from openerp.modules.registry import RegistryManager
|
||||
from openerp.addons.web.controllers.main import login_and_redirect, set_cookie_and_redirect
|
||||
import openerp.addons.web.http as http
|
||||
from openerp.addons.web.http import request
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
@ -88,20 +90,19 @@ class GoogleAppsAwareConsumer(consumer.GenericConsumer):
|
|||
return super(GoogleAppsAwareConsumer, self).complete(message, endpoint, return_to)
|
||||
|
||||
|
||||
class OpenIDController(openerp.addons.web.http.Controller):
|
||||
_cp_path = '/auth_openid/login'
|
||||
class OpenIDController(http.Controller):
|
||||
|
||||
_store = filestore.FileOpenIDStore(_storedir)
|
||||
|
||||
_REQUIRED_ATTRIBUTES = ['email']
|
||||
_OPTIONAL_ATTRIBUTES = 'nickname fullname postcode country language timezone'.split()
|
||||
|
||||
def _add_extensions(self, request):
|
||||
"""Add extensions to the request"""
|
||||
def _add_extensions(self, oidrequest):
|
||||
"""Add extensions to the oidrequest"""
|
||||
|
||||
sreg_request = sreg.SRegRequest(required=self._REQUIRED_ATTRIBUTES,
|
||||
optional=self._OPTIONAL_ATTRIBUTES)
|
||||
request.addExtension(sreg_request)
|
||||
oidrequest.addExtension(sreg_request)
|
||||
|
||||
ax_request = ax.FetchRequest()
|
||||
for alias in self._REQUIRED_ATTRIBUTES:
|
||||
|
@ -111,7 +112,7 @@ class OpenIDController(openerp.addons.web.http.Controller):
|
|||
uri = utils.SREG2AX[alias]
|
||||
ax_request.add(ax.AttrInfo(uri, required=False, alias=alias))
|
||||
|
||||
request.addExtension(ax_request)
|
||||
oidrequest.addExtension(ax_request)
|
||||
|
||||
def _get_attributes_from_success_response(self, success_response):
|
||||
attrs = {}
|
||||
|
@ -133,58 +134,58 @@ class OpenIDController(openerp.addons.web.http.Controller):
|
|||
attrs[attr] = value
|
||||
return attrs
|
||||
|
||||
def _get_realm(self, req):
|
||||
return req.httprequest.host_url
|
||||
def _get_realm(self):
|
||||
return request.httprequest.host_url
|
||||
|
||||
@openerp.addons.web.http.httprequest
|
||||
def verify_direct(self, req, db, url):
|
||||
result = self._verify(req, db, url)
|
||||
@http.route('/auth_openid/login/verify_direct', type='http', auth='none')
|
||||
def verify_direct(self, db, url):
|
||||
result = self._verify(db, url)
|
||||
if 'error' in result:
|
||||
return werkzeug.exceptions.BadRequest(result['error'])
|
||||
if result['action'] == 'redirect':
|
||||
return werkzeug.utils.redirect(result['value'])
|
||||
return result['value']
|
||||
|
||||
@openerp.addons.web.http.jsonrequest
|
||||
def verify(self, req, db, url):
|
||||
return self._verify(req, db, url)
|
||||
@http.route('/auth_openid/login/verify', type='json', auth='none')
|
||||
def verify(self, db, url):
|
||||
return self._verify(db, url)
|
||||
|
||||
def _verify(self, req, db, url):
|
||||
redirect_to = werkzeug.urls.Href(req.httprequest.host_url + 'auth_openid/login/process')(session_id=req.session_id)
|
||||
realm = self._get_realm(req)
|
||||
def _verify(self, db, url):
|
||||
redirect_to = werkzeug.urls.Href(request.httprequest.host_url + 'auth_openid/login/process')(session_id=request.session_id)
|
||||
realm = self._get_realm()
|
||||
|
||||
session = dict(dbname=db, openid_url=url) # TODO add origin page ?
|
||||
oidconsumer = consumer.Consumer(session, self._store)
|
||||
|
||||
try:
|
||||
request = oidconsumer.begin(url)
|
||||
oidrequest = oidconsumer.begin(url)
|
||||
except consumer.DiscoveryFailure, exc:
|
||||
fetch_error_string = 'Error in discovery: %s' % (str(exc[0]),)
|
||||
return {'error': fetch_error_string, 'title': 'OpenID Error'}
|
||||
|
||||
if request is None:
|
||||
if oidrequest is None:
|
||||
return {'error': 'No OpenID services found', 'title': 'OpenID Error'}
|
||||
|
||||
req.session.openid_session = session
|
||||
self._add_extensions(request)
|
||||
request.session.openid_session = session
|
||||
self._add_extensions(oidrequest)
|
||||
|
||||
if request.shouldSendRedirect():
|
||||
redirect_url = request.redirectURL(realm, redirect_to)
|
||||
return {'action': 'redirect', 'value': redirect_url, 'session_id': req.session_id}
|
||||
if oidrequest.shouldSendRedirect():
|
||||
redirect_url = oidrequest.redirectURL(realm, redirect_to)
|
||||
return {'action': 'redirect', 'value': redirect_url, 'session_id': request.session_id}
|
||||
else:
|
||||
form_html = request.htmlMarkup(realm, redirect_to)
|
||||
return {'action': 'post', 'value': form_html, 'session_id': req.session_id}
|
||||
form_html = oidrequest.htmlMarkup(realm, redirect_to)
|
||||
return {'action': 'post', 'value': form_html, 'session_id': request.session_id}
|
||||
|
||||
@openerp.addons.web.http.httprequest
|
||||
def process(self, req, **kw):
|
||||
session = getattr(req.session, 'openid_session', None)
|
||||
@http.route('/auth_openid/login/process', type='http', auth='none')
|
||||
def process(self, **kw):
|
||||
session = getattr(request.session, 'openid_session', None)
|
||||
if not session:
|
||||
return set_cookie_and_redirect(req, '/')
|
||||
return set_cookie_and_redirect('/')
|
||||
|
||||
oidconsumer = consumer.Consumer(session, self._store, consumer_class=GoogleAppsAwareConsumer)
|
||||
|
||||
query = req.httprequest.args
|
||||
info = oidconsumer.complete(query, req.httprequest.base_url)
|
||||
query = request.httprequest.args
|
||||
info = oidconsumer.complete(query, request.httprequest.base_url)
|
||||
display_identifier = info.getDisplayIdentifier()
|
||||
|
||||
session['status'] = info.status
|
||||
|
@ -225,7 +226,7 @@ class OpenIDController(openerp.addons.web.http.Controller):
|
|||
# TODO fill empty fields with the ones from sreg/ax
|
||||
cr.commit()
|
||||
|
||||
return login_and_redirect(req, dbname, login, key)
|
||||
return login_and_redirect(dbname, login, key)
|
||||
|
||||
session['message'] = 'This OpenID identifier is not associated to any active users'
|
||||
|
||||
|
@ -241,11 +242,11 @@ class OpenIDController(openerp.addons.web.http.Controller):
|
|||
# information in a log.
|
||||
session['message'] = 'Verification failed.'
|
||||
|
||||
return set_cookie_and_redirect(req, '/#action=login&loginerror=1')
|
||||
return set_cookie_and_redirect('/#action=login&loginerror=1')
|
||||
|
||||
@openerp.addons.web.http.jsonrequest
|
||||
def status(self, req):
|
||||
session = getattr(req.session, 'openid_session', {})
|
||||
@http.route('/auth_openid/login/status', type='json', auth='none')
|
||||
def status(self):
|
||||
session = getattr(request.session, 'openid_session', {})
|
||||
return {'status': session.get('status'), 'message': session.get('message')}
|
||||
|
||||
|
||||
|
|
|
@ -36,6 +36,8 @@ class base_config_settings(osv.osv_memory):
|
|||
help="""Enable the public part of openerp, openerp becomes a public website."""),
|
||||
'module_auth_oauth': fields.boolean('Use external authentication providers, sign in with google, facebook, ...'),
|
||||
'module_base_import': fields.boolean("Allow users to import data from CSV files"),
|
||||
'module_google_drive': fields.boolean('Attach Google documents to any record',
|
||||
help="""This installs the module google_docs."""),
|
||||
}
|
||||
|
||||
def open_company(self, cr, uid, ids, context=None):
|
||||
|
|
|
@ -81,6 +81,15 @@
|
|||
</div>
|
||||
</div>
|
||||
</group>
|
||||
<group>
|
||||
<label for="id" string="Google Drive"/>
|
||||
<div name="google_drive">
|
||||
<div name="module_google_drive">
|
||||
<field name="module_google_drive" class="oe_inline"/>
|
||||
<label for="module_google_drive"/>
|
||||
</div>
|
||||
</div>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
@ -222,17 +222,13 @@ class crm_case_section(osv.osv):
|
|||
return res
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
mail_alias = self.pool.get('mail.alias')
|
||||
if not vals.get('alias_id'):
|
||||
alias_name = vals.pop('alias_name', None) or vals.get('name') # prevent errors during copy()
|
||||
alias_id = mail_alias.create_unique_alias(cr, uid,
|
||||
{'alias_name': alias_name},
|
||||
model_name="crm.lead",
|
||||
context=context)
|
||||
vals['alias_id'] = alias_id
|
||||
res = super(crm_case_section, self).create(cr, uid, vals, context)
|
||||
mail_alias.write(cr, uid, [vals['alias_id']], {'alias_defaults': {'section_id': res, 'type': 'lead'}}, context)
|
||||
return res
|
||||
if context is None:
|
||||
context = {}
|
||||
create_context = dict(context, alias_model_name='crm.lead', alias_parent_model_name=self._name)
|
||||
section_id = super(crm_case_section, self).create(cr, uid, vals, context=create_context)
|
||||
section = self.browse(cr, uid, section_id, context=context)
|
||||
self.pool.get('mail.alias').write(cr, uid, [section.alias_id.id], {'alias_parent_thread_id': section_id, 'alias_defaults': {'section_id': section_id, 'type': 'lead'}}, context=context)
|
||||
return section_id
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
# Cascade-delete mail aliases as well, as they should not exist without the sales team.
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
<div class="oe_kanban_content">
|
||||
<h4 class="oe_center"><field name="name"/></h4>
|
||||
<div class="oe_kanban_alias oe_center" t-if="record.use_leads.raw_value and record.alias_id.value">
|
||||
<small><span class="oe_e" style="float: none;">%%</span><t t-raw="record.alias_id.raw_value[1]"/></small>
|
||||
<small><span class="oe_e oe_e_alias" style="float: none;">%%</span><t t-raw="record.alias_id.raw_value[1]"/></small>
|
||||
</div>
|
||||
<div class="oe_items_list">
|
||||
<div class="oe_salesteams_leads" t-if="record.use_leads.raw_value">
|
||||
|
@ -168,17 +168,6 @@
|
|||
<h1>
|
||||
<field name="name" string="Salesteam"/>
|
||||
</h1>
|
||||
<div name="group_alias"
|
||||
attrs="{'invisible': [('alias_domain', '=', False)]}">
|
||||
<label for="alias_id" string="Email Alias"/>
|
||||
<field name="alias_id" class="oe_inline oe_read_only" required="0" nolabel="1"/>
|
||||
<span name="edit_alias" class="oe_edit_only">
|
||||
<field name="alias_name" class="oe_inline"
|
||||
attrs="{'required': [('use_leads', '=', True), ('alias_id', '!=', False)]}"/>
|
||||
@
|
||||
<field name="alias_domain" class="oe_inline" readonly="1"/>
|
||||
</span>
|
||||
</div>
|
||||
<div name="options_active">
|
||||
<field name="use_leads" class="oe_inline"/><label for="use_leads"/>
|
||||
</div>
|
||||
|
@ -187,12 +176,25 @@
|
|||
<group>
|
||||
<field name="user_id"/>
|
||||
<field name="code"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="parent_id"/>
|
||||
<field name="change_responsible"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
<group>
|
||||
<label for="alias_name" string="Email Alias"
|
||||
attrs="{'invisible': [('alias_domain', '=', False)]}"/>
|
||||
<div name="alias_def"
|
||||
attrs="{'invisible': [('alias_domain', '=', False)]}">
|
||||
<field name="alias_id" class="oe_read_only oe_inline"
|
||||
string="Email Alias" required="0"/>
|
||||
<div class="oe_edit_only oe_inline" name="edit_alias" style="display: inline;" >
|
||||
<field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline" readonly="1"/>
|
||||
</div>
|
||||
</div>
|
||||
<field name="alias_contact" class="oe_inline"
|
||||
string="Accept Emails From"
|
||||
attrs="{'invisible': [('alias_domain', '=', False)]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook colspan="4">
|
||||
<page string="Team Members">
|
||||
|
|
|
@ -77,12 +77,12 @@ class crm_lead(base_stage, format_address, osv.osv):
|
|||
|
||||
_track = {
|
||||
'state': {
|
||||
'crm.mt_lead_create': lambda self, cr, uid, obj, ctx=None: obj['state'] in ['new', 'draft'],
|
||||
'crm.mt_lead_won': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'done',
|
||||
'crm.mt_lead_lost': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'cancel',
|
||||
'crm.mt_lead_create': lambda self, cr, uid, obj, ctx=None: obj.state in ['new', 'draft'],
|
||||
'crm.mt_lead_won': lambda self, cr, uid, obj, ctx=None: obj.state == 'done',
|
||||
'crm.mt_lead_lost': lambda self, cr, uid, obj, ctx=None: obj.state == 'cancel',
|
||||
},
|
||||
'stage_id': {
|
||||
'crm.mt_lead_stage': lambda self, cr, uid, obj, ctx=None: obj['state'] not in ['new', 'draft', 'cancel', 'done'],
|
||||
'crm.mt_lead_stage': lambda self, cr, uid, obj, ctx=None: obj.state not in ['new', 'draft', 'cancel', 'done'],
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -709,5 +709,6 @@ Andrew</field>
|
|||
eval="[ ref('msg_case18_1'), ref('msg_case18_2')], True, {}"
|
||||
/>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -172,6 +172,12 @@ class event_event(osv.osv):
|
|||
continue
|
||||
return res
|
||||
|
||||
def _get_visibility_selection(self, cr, uid, context=None):
|
||||
return [('public', 'All Users'),
|
||||
('employees', 'Employees Only')]
|
||||
# Lambda indirection method to avoid passing a copy of the overridable method when declaring the field
|
||||
_visibility_selection = lambda self, *args, **kwargs: self._get_visibility_selection(*args, **kwargs)
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=64, required=True, translate=True, readonly=False, states={'done': [('readonly', True)]}),
|
||||
'user_id': fields.many2one('res.users', 'Responsible User', readonly=False, states={'done': [('readonly', True)]}),
|
||||
|
@ -209,11 +215,14 @@ class event_event(osv.osv):
|
|||
'note': fields.text('Description', readonly=False, states={'done': [('readonly', True)]}),
|
||||
'company_id': fields.many2one('res.company', 'Company', required=False, change_default=True, readonly=False, states={'done': [('readonly', True)]}),
|
||||
'is_subscribed' : fields.function(_subscribe_fnc, type="boolean", string='Subscribed'),
|
||||
'visibility': fields.selection(_visibility_selection, 'Privacy / Visibility',
|
||||
select=True, required=True),
|
||||
}
|
||||
_defaults = {
|
||||
'state': 'draft',
|
||||
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'event.event', context=c),
|
||||
'user_id': lambda obj, cr, uid, context: uid,
|
||||
'visibility': 'employees',
|
||||
}
|
||||
|
||||
def subscribe_to_event(self, cr, uid, ids, context=None):
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name"/></h1>
|
||||
<field name="visibility"/>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
|
|
|
@ -25,25 +25,35 @@
|
|||
<data noupdate="1">
|
||||
|
||||
<!-- Multi - Company Rules -->
|
||||
<record model="ir.rule" id="event_event_comp_rule">
|
||||
<field name="name">Event multi-company</field>
|
||||
<record model="ir.rule" id="event_event_company_rule">
|
||||
<field name="name">Event: multi-company</field>
|
||||
<field name="model_id" ref="model_event_event"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
|
||||
<field name="domain_force">['|',
|
||||
('company_id', '=', False),
|
||||
('company_id', 'child_of', [user.company_id.id]),
|
||||
]
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.rule" id="event_registration_comp_rule">
|
||||
<field name="name">Event Registration multi-company</field>
|
||||
<record model="ir.rule" id="event_registration_company_rule">
|
||||
<field name="name">Event/Registration: multi-company</field>
|
||||
<field name="model_id" ref="model_event_registration"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
|
||||
<field name="domain_force">['|',
|
||||
('company_id', '=', False),
|
||||
('company_id', 'child_of', [user.company_id.id]),
|
||||
]
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.rule" id="report_event_registration_comp_rule">
|
||||
<field name="name">Report Event Registration multi-company</field>
|
||||
<record model="ir.rule" id="report_event_registration_company_rule">
|
||||
<field name="name">Event/Report Registration: multi-company</field>
|
||||
<field name="model_id" ref="model_report_event_registration"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
|
||||
<field name="domain_force">['|',
|
||||
('company_id', '=', False),
|
||||
('company_id', 'child_of', [user.company_id.id]),
|
||||
]
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
##############################################################################
|
||||
|
||||
import google_base_account
|
||||
import wizard
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
|
|
@ -30,10 +30,9 @@ The module adds google user in res user.
|
|||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'website': 'http://www.openerp.com',
|
||||
'depends': ['base'],
|
||||
'depends': ['base_setup'],
|
||||
'data': [
|
||||
'google_base_account_view.xml',
|
||||
'wizard/google_login_view.xml',
|
||||
'google_base_account_data.xml',
|
||||
],
|
||||
'demo': [],
|
||||
'installable': True,
|
||||
|
|
|
@ -19,14 +19,48 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import fields,osv
|
||||
from openerp.osv import osv
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.tools.translate import _
|
||||
|
||||
class res_users(osv.osv):
|
||||
_inherit = "res.users"
|
||||
_columns = {
|
||||
'gmail_user': fields.char('Username', size=64,),
|
||||
'gmail_password': fields.char('Password', size=64),
|
||||
import urllib
|
||||
import urllib2
|
||||
import simplejson
|
||||
|
||||
|
||||
class google_service(osv.osv):
|
||||
_name = 'google.service'
|
||||
|
||||
def generate_refresh_token(self, cr, uid, service, authorization_code, context=None):
|
||||
if authorization_code:
|
||||
ir_config = self.pool['ir.config_parameter']
|
||||
client_id = ir_config.get_param(cr, SUPERUSER_ID, 'google_%s_client_id' % service)
|
||||
client_secret = ir_config.get_param(cr, SUPERUSER_ID, 'google_%s_client_secret' % service)
|
||||
redirect_uri = ir_config.get_param(cr, SUPERUSER_ID, 'google_redirect_uri')
|
||||
|
||||
#Get the Refresh Token From Google And store it in ir.config_parameter
|
||||
headers = {"Content-type": "application/x-www-form-urlencoded"}
|
||||
data = dict(code=authorization_code, client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, grant_type="authorization_code")
|
||||
data = urllib.urlencode(data)
|
||||
try:
|
||||
req = urllib2.Request("https://accounts.google.com/o/oauth2/token", data, headers)
|
||||
content = urllib2.urlopen(req).read()
|
||||
except urllib2.HTTPError:
|
||||
raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired"), context=context)
|
||||
|
||||
content = simplejson.loads(content)
|
||||
return content.get('refresh_token')
|
||||
|
||||
def _get_google_token_uri(self, cr, uid, service, context=None):
|
||||
ir_config = self.pool['ir.config_parameter']
|
||||
params = {
|
||||
'scope': 'https://www.googleapis.com/auth/drive',
|
||||
'redirect_uri': ir_config.get_param(cr, SUPERUSER_ID, 'google_redirect_uri'),
|
||||
'client_id': ir_config.get_param(cr, SUPERUSER_ID, 'google_%s_client_id' % service),
|
||||
'response_type': 'code',
|
||||
'client_id': ir_config.get_param(cr, SUPERUSER_ID, 'google_%s_client_id' % service),
|
||||
}
|
||||
uri = 'https://accounts.google.com/o/oauth2/auth?%s' % urllib.urlencode(params)
|
||||
return uri
|
||||
|
||||
# vim:expandtab:smartindent:toabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
<record id="config_google_redirect_uri" model="ir.config_parameter">
|
||||
<field name="key">google_redirect_uri</field>
|
||||
<field name="value">urn:ietf:wg:oauth:2.0:oob</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
|
@ -1,20 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="view_users_gogole_form" model="ir.ui.view">
|
||||
<field name="name">res.users.google.form1</field>
|
||||
<field name="model">res.users</field>
|
||||
<field name="inherit_id" ref="base.view_users_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//notebook" position="inside">
|
||||
<page string="Synchronization">
|
||||
<group string="Google Account" colspan="4">
|
||||
<field name="gmail_user"/>
|
||||
<field name="gmail_password" password="True"/>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
|
@ -1,85 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import fields,osv
|
||||
from openerp.tools.translate import _
|
||||
try:
|
||||
import gdata.contacts.service
|
||||
import gdata.contacts.client
|
||||
import gdata.calendar.service
|
||||
except ImportError:
|
||||
raise osv.except_osv(_('Google Contacts Import Error!'), _('Please install gdata-python-client from http://code.google.com/p/gdata-python-client/downloads/list'))
|
||||
|
||||
class google_login(osv.osv_memory):
|
||||
_description ='Google Contact'
|
||||
_name = 'google.login'
|
||||
_columns = {
|
||||
'user': fields.char('Google Username', size=64, required=True),
|
||||
'password': fields.char('Google Password', size=64),
|
||||
}
|
||||
|
||||
def google_login(self, user, password, type='', context=None):
|
||||
if type == 'group':
|
||||
gd_client = gdata.contacts.service.ContactsService()
|
||||
elif type == 'contact':
|
||||
gd_client = gdata.contacts.service.ContactsService()
|
||||
elif type == 'calendar':
|
||||
gd_client = gdata.calendar.service.CalendarService()
|
||||
elif type =='docs_client':
|
||||
gd_client = gdata.docs.client.DocsClient()
|
||||
else:
|
||||
gd_client = gdata.contacts.service.ContactsService()
|
||||
try:
|
||||
gd_client.ClientLogin(user, password, gd_client.source)
|
||||
except Exception:
|
||||
return False
|
||||
return gd_client
|
||||
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
res = super(google_login, self).default_get(cr, uid, fields, context=context)
|
||||
user_obj = self.pool.get('res.users').browse(cr, uid, uid)
|
||||
if 'user' in fields:
|
||||
res.update({'user': user_obj.gmail_user})
|
||||
if 'password' in fields:
|
||||
res.update({'password': user_obj.gmail_password})
|
||||
return res
|
||||
|
||||
def login(self, cr, uid, ids, context=None):
|
||||
data = self.read(cr, uid, ids)[0]
|
||||
user = data['user']
|
||||
password = data['password']
|
||||
if self.google_login(user, password):
|
||||
res = {
|
||||
'gmail_user': user,
|
||||
'gmail_password': password
|
||||
}
|
||||
self.pool.get('res.users').write(cr, uid, uid, res, context=context)
|
||||
else:
|
||||
raise osv.except_osv(_('Error!'), _("Authentication failed. Check the user and password."))
|
||||
|
||||
return self._get_next_action(cr, uid, context=context)
|
||||
|
||||
def _get_next_action(self, cr, uid, context=None):
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -1,35 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="view_google_login_form">
|
||||
<field name="name">google.login.form</field>
|
||||
<field name="model">google.login</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Google login" version="7.0">
|
||||
<group>
|
||||
<field name="user" placeholder="e.g. user@gmail.com"/>
|
||||
<field name="password" password="True"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="login" string="_Login" type="object" class="oe_highlight"/>
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
Login Action
|
||||
-->
|
||||
<record model="ir.actions.act_window" id="act_google_login_form">
|
||||
<field name="name">Google Login</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">google.login</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="view_id" ref="view_google_login_form" />
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
|
@ -1 +0,0 @@
|
|||
import google_docs
|
|
@ -1,193 +0,0 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2012 OpenERP SA (<http://www.openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import gdata.docs.data
|
||||
import gdata.docs.client
|
||||
|
||||
# API breakage madness in the gdata API - those guys are insane.
|
||||
try:
|
||||
# gdata 2.0.15+
|
||||
gdata.docs.client.DocsClient.copy_resource
|
||||
except AttributeError:
|
||||
# gdata 2.0.14- : copy_resource() was copy()
|
||||
gdata.docs.client.DocsClient.copy_resource = gdata.docs.client.DocsClient.copy
|
||||
|
||||
try:
|
||||
# gdata 2.0.16+
|
||||
gdata.docs.client.DocsClient.get_resource_by_id
|
||||
except AttributeError:
|
||||
try:
|
||||
# gdata 2.0.15+
|
||||
gdata.docs.client.DocsClient.get_resource_by_self_link
|
||||
def get_resource_by_id_2_0_16(self, resource_id, **kwargs):
|
||||
return self.GetResourceBySelfLink(
|
||||
gdata.docs.client.RESOURCE_FEED_URI + ('/%s' % resource_id), **kwargs)
|
||||
gdata.docs.client.DocsClient.get_resource_by_id = get_resource_by_id_2_0_16
|
||||
except AttributeError:
|
||||
# gdata 2.0.14- : alias get_resource_by_id()
|
||||
gdata.docs.client.DocsClient.get_resource_by_id = gdata.docs.client.DocsClient.get_doc
|
||||
|
||||
try:
|
||||
import atom.http_interface
|
||||
_logger.info('GData lib version `%s` detected' % atom.http_interface.USER_AGENT)
|
||||
except (ImportError, AttributeError):
|
||||
_logger.debug('GData lib version could not be detected', exc_info=True)
|
||||
|
||||
except ImportError:
|
||||
_logger.warning("Please install latest gdata-python-client from http://code.google.com/p/gdata-python-client/downloads/list")
|
||||
|
||||
|
||||
class google_docs_ir_attachment(osv.osv):
|
||||
_inherit = 'ir.attachment'
|
||||
|
||||
def _auth(self, cr, uid, context=None):
|
||||
'''
|
||||
Connexion with google base account
|
||||
@return client object for connexion
|
||||
'''
|
||||
#pool the google.login in google_base_account
|
||||
google_pool = self.pool.get('google.login')
|
||||
#get gmail password and login. We use default_get() instead of a create() followed by a read() on the
|
||||
# google.login object, because it is easier. The keys 'user' and 'password' ahve to be passed in the dict
|
||||
# but the values will be replaced by the user gmail password and login.
|
||||
user_config = google_pool.default_get(cr, uid, {'user' : '' , 'password' : ''}, context=context)
|
||||
#login gmail account
|
||||
client = google_pool.google_login(user_config['user'], user_config['password'], type='docs_client', context=context)
|
||||
if not client:
|
||||
raise osv.except_osv(_('Google Docs Error!'), _("Check your google configuration in Users/Users/Synchronization tab."))
|
||||
_logger.info('Logged into google docs as %s', user_config['user'])
|
||||
return client
|
||||
|
||||
def create_empty_google_doc(self, cr, uid, res_model, res_id, context=None):
|
||||
'''Create a new google document, empty and with a default type (txt)
|
||||
:param res_model: the object for which the google doc is created
|
||||
:param res_id: the Id of the object for which the google doc is created
|
||||
:return: the ID of the google document object created
|
||||
'''
|
||||
#login with the base account google module
|
||||
client = self._auth(cr, uid, context=context)
|
||||
# create the document in google docs
|
||||
title = "%s %s" % (context.get("name","Untitled Document."), datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT))
|
||||
local_resource = gdata.docs.data.Resource(gdata.docs.data.DOCUMENT_LABEL,title=title)
|
||||
#create a new doc in Google Docs
|
||||
gdocs_resource = client.post(entry=local_resource, uri='https://docs.google.com/feeds/default/private/full/')
|
||||
# create an ir.attachment into the db
|
||||
self.create(cr, uid, {
|
||||
'res_model': res_model,
|
||||
'res_id': res_id,
|
||||
'type': 'url',
|
||||
'name': title,
|
||||
'url': gdocs_resource.get_alternate_link().href,
|
||||
}, context=context)
|
||||
return {'resource_id': gdocs_resource.resource_id.text,
|
||||
'title': title,
|
||||
'url': gdocs_resource.get_alternate_link().href}
|
||||
|
||||
def copy_gdoc(self, cr, uid, res_model, res_id, name_gdocs, gdoc_template_id, context=None):
|
||||
'''
|
||||
copy an existing document in google docs
|
||||
:param res_model: the object for which the google doc is created
|
||||
:param res_id: the Id of the object for which the google doc is created
|
||||
:param name_gdocs: the name of the future ir.attachment that will be created. Based on the google doc template foun.
|
||||
:param gdoc_template_id: the id of the google doc document to copy
|
||||
:return: the ID of the google document object created
|
||||
'''
|
||||
#login with the base account google module
|
||||
client = self._auth(cr, uid)
|
||||
# fetch and copy the original document
|
||||
try:
|
||||
doc = client.get_resource_by_id(gdoc_template_id)
|
||||
#copy the document you choose in the configuration
|
||||
copy_resource = client.copy_resource(doc, name_gdocs)
|
||||
except:
|
||||
raise osv.except_osv(_('Google Docs Error!'), _("Your resource id is not correct. You can find the id in the google docs URL."))
|
||||
# create an ir.attachment
|
||||
self.create(cr, uid, {
|
||||
'res_model': res_model,
|
||||
'res_id': res_id,
|
||||
'type': 'url',
|
||||
'name': name_gdocs,
|
||||
'url': copy_resource.get_alternate_link().href
|
||||
}, context=context)
|
||||
return copy_resource.resource_id.text
|
||||
|
||||
def google_doc_get(self, cr, uid, res_model, ids, context=None):
|
||||
'''
|
||||
Function called by the js, when no google doc are yet associated with a record, with the aim to create one. It
|
||||
will first seek for a google.docs.config associated with the model `res_model` to find out what's the template
|
||||
of google doc to copy (this is usefull if you want to start with a non-empty document, a type or a name
|
||||
different than the default values). If no config is associated with the `res_model`, then a blank text document
|
||||
with a default name is created.
|
||||
:param res_model: the object for which the google doc is created
|
||||
:param ids: the list of ids of the objects for which the google doc is created. This list is supposed to have
|
||||
a length of 1 element only (batch processing is not supported in the code, though nothing really prevent it)
|
||||
:return: the google document object created
|
||||
'''
|
||||
if len(ids) != 1:
|
||||
raise osv.except_osv(_('Google Docs Error!'), _("Creating google docs may only be done by one at a time."))
|
||||
res_id = ids[0]
|
||||
pool_ir_attachment = self.pool.get('ir.attachment')
|
||||
pool_gdoc_config = self.pool.get('google.docs.config')
|
||||
name_gdocs = ''
|
||||
model_fields_dic = self.pool[res_model].read(cr, uid, res_id, [], context=context)
|
||||
|
||||
# check if a model is configured with a template
|
||||
google_docs_config = pool_gdoc_config.search(cr, uid, [('model_id', '=', res_model)], context=context)
|
||||
if google_docs_config:
|
||||
name_gdocs = pool_gdoc_config.browse(cr, uid, google_docs_config, context=context)[0].name_template
|
||||
try:
|
||||
name_gdocs = name_gdocs % model_fields_dic
|
||||
except:
|
||||
raise osv.except_osv(_('Key Error!'), _("Your Google Doc Name Pattern's key does not found in object."))
|
||||
google_template_id = pool_gdoc_config.browse(cr, uid, google_docs_config[0], context=context).gdocs_resource_id
|
||||
google_document = pool_ir_attachment.copy_gdoc(cr, uid, res_model, res_id, name_gdocs, google_template_id, context=context)
|
||||
else:
|
||||
google_document = pool_ir_attachment.create_empty_google_doc(cr, uid, res_model, res_id, context=context)
|
||||
return google_document
|
||||
|
||||
class config(osv.osv):
|
||||
_name = 'google.docs.config'
|
||||
_description = "Google Docs templates config"
|
||||
|
||||
_columns = {
|
||||
'model_id': fields.many2one('ir.model', 'Model', required=True),
|
||||
'gdocs_resource_id': fields.char('Google Resource ID to Use as Template', size=64, help='''
|
||||
This is the id of the template document, on google side. You can find it thanks to its URL:
|
||||
*for a text document with url like `https://docs.google.com/a/openerp.com/document/d/123456789/edit`, the ID is `document:123456789`
|
||||
*for a spreadsheet document with url like `https://docs.google.com/a/openerp.com/spreadsheet/ccc?key=123456789#gid=0`, the ID is `spreadsheet:123456789`
|
||||
*for a presentation (slide show) document with url like `https://docs.google.com/a/openerp.com/presentation/d/123456789/edit#slide=id.p`, the ID is `presentation:123456789`
|
||||
*for a drawing document with url like `https://docs.google.com/a/openerp.com/drawings/d/123456789/edit`, the ID is `drawings:123456789`
|
||||
...
|
||||
''', required=True),
|
||||
'name_template': fields.char('Google Doc Name Pattern', size=64, help='Choose how the new google docs will be named, on google side. Eg. gdoc_%(field_name)s', required=True),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'name_template': 'gdoc_%(name)s',
|
||||
}
|
|
@ -1,159 +0,0 @@
|
|||
# Translation of OpenERP Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * google_docs
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: OpenERP Server 7.0alpha\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
|
||||
"PO-Revision-Date: 2012-12-21 17:05+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: google_docs
|
||||
#: code:addons/google_docs/google_docs.py:139
|
||||
#, python-format
|
||||
msgid "Key Error!"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: view:google.docs.config:0
|
||||
msgid "for a presentation (slide show) document with url like `https://docs.google.com/a/openerp.com/presentation/d/123456789/edit#slide=id.p`, the ID is `presentation:123456789`"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: view:google.docs.config:0
|
||||
msgid "for a text document with url like `https://docs.google.com/a/openerp.com/document/d/123456789/edit`, the ID is `document:123456789`"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: field:google.docs.config,gdocs_resource_id:0
|
||||
msgid "Google Resource ID to Use as Template"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: view:google.docs.config:0
|
||||
msgid "for a drawing document with url like `https://docs.google.com/a/openerp.com/drawings/d/123456789/edit`, the ID is `drawings:123456789`"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#. openerp-web
|
||||
#: code:addons/google_docs/static/src/xml/gdocs.xml:6
|
||||
#, python-format
|
||||
msgid "Add Google Doc..."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: view:google.docs.config:0
|
||||
msgid "This is the id of the template document, on google side. You can find it thanks to its URL:"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: model:ir.model,name:google_docs.model_google_docs_config
|
||||
msgid "Google Docs templates config"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#. openerp-web
|
||||
#: code:addons/google_docs/static/src/js/gdocs.js:25
|
||||
#, python-format
|
||||
msgid "The user google credentials are not set yet. Contact your administrator for help."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: view:google.docs.config:0
|
||||
msgid "for a spreadsheet document with url like `https://docs.google.com/a/openerp.com/spreadsheet/ccc?key=123456789#gid=0`, the ID is `spreadsheet:123456789`"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: code:addons/google_docs/google_docs.py:101
|
||||
#, python-format
|
||||
msgid "Your resource id is not correct. You can find the id in the google docs URL."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: code:addons/google_docs/google_docs.py:125
|
||||
#, python-format
|
||||
msgid "Creating google docs may only be done by one at a time."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: code:addons/google_docs/google_docs.py:56
|
||||
#: code:addons/google_docs/google_docs.py:101
|
||||
#: code:addons/google_docs/google_docs.py:125
|
||||
#, python-format
|
||||
msgid "Google Docs Error!"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: code:addons/google_docs/google_docs.py:56
|
||||
#, python-format
|
||||
msgid "Check your google configuration in Users/Users/Synchronization tab."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: model:ir.ui.menu,name:google_docs.menu_gdocs_config
|
||||
msgid "Google Docs configuration"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: model:ir.actions.act_window,name:google_docs.action_google_docs_users_config
|
||||
#: model:ir.ui.menu,name:google_docs.menu_gdocs_model_config
|
||||
msgid "Models configuration"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: field:google.docs.config,model_id:0
|
||||
msgid "Model"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#. openerp-web
|
||||
#: code:addons/google_docs/static/src/js/gdocs.js:28
|
||||
#, python-format
|
||||
msgid "User Google credentials are not yet set."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: code:addons/google_docs/google_docs.py:139
|
||||
#, python-format
|
||||
msgid "Your Google Doc Name Pattern's key does not found in object."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: help:google.docs.config,name_template:0
|
||||
msgid "Choose how the new google docs will be named, on google side. Eg. gdoc_%(field_name)s"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: view:google.docs.config:0
|
||||
msgid "Google Docs Configuration"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: help:google.docs.config,gdocs_resource_id:0
|
||||
msgid "\n"
|
||||
"This is the id of the template document, on google side. You can find it thanks to its URL: \n"
|
||||
"*for a text document with url like `https://docs.google.com/a/openerp.com/document/d/123456789/edit`, the ID is `document:123456789`\n"
|
||||
"*for a spreadsheet document with url like `https://docs.google.com/a/openerp.com/spreadsheet/ccc?key=123456789#gid=0`, the ID is `spreadsheet:123456789`\n"
|
||||
"*for a presentation (slide show) document with url like `https://docs.google.com/a/openerp.com/presentation/d/123456789/edit#slide=id.p`, the ID is `presentation:123456789`\n"
|
||||
"*for a drawing document with url like `https://docs.google.com/a/openerp.com/drawings/d/123456789/edit`, the ID is `drawings:123456789`\n"
|
||||
"...\n"
|
||||
""
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: model:ir.model,name:google_docs.model_ir_attachment
|
||||
msgid "ir.attachment"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_docs
|
||||
#: field:google.docs.config,name_template:0
|
||||
msgid "Google Doc Name Pattern"
|
||||
msgstr ""
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- add google docs config field in user form -->
|
||||
|
||||
<record model="ir.ui.view" id="view_google_docs_config_tree">
|
||||
<field name="name">google_docs.config.tree</field>
|
||||
<field name="model">google.docs.config</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Google Docs Configuration">
|
||||
<field name="model_id"/>
|
||||
<field name="name_template"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_google_docs_config_form">
|
||||
<field name="name">google_docs.config.form</field>
|
||||
<field name="model">google.docs.config</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Google Docs Configuration" version="7.0">
|
||||
<group>
|
||||
<field name="model_id"/>
|
||||
<label for='gdocs_resource_id'/>
|
||||
<div>
|
||||
<field name='gdocs_resource_id'/>
|
||||
<p class="oe_grey">
|
||||
This is the id of the template document, on google side. You can find it thanks to its URL:
|
||||
<ul>
|
||||
<li>for a text document with url like `https://docs.google.com/a/openerp.com/document/d/123456789/edit`, the ID is `document:123456789`</li>
|
||||
<li>for a spreadsheet document with url like `https://docs.google.com/a/openerp.com/spreadsheet/ccc?key=123456789#gid=0`, the ID is `spreadsheet:123456789`</li>
|
||||
<li>for a presentation (slide show) document with url like `https://docs.google.com/a/openerp.com/presentation/d/123456789/edit#slide=id.p`, the ID is `presentation:123456789`</li>
|
||||
<li>for a drawing document with url like `https://docs.google.com/a/openerp.com/drawings/d/123456789/edit`, the ID is `drawings:123456789`</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
<field name='name_template'/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model='ir.actions.act_window' id='action_google_docs_users_config'>
|
||||
<field name='name'>Models configuration</field>
|
||||
<field name='res_model'>google.docs.config</field>
|
||||
<field name='type'>ir.actions.act_window</field>
|
||||
<field name='view_type'>form</field>
|
||||
<field name='view_id' ref='view_google_docs_config_tree'/>
|
||||
</record>
|
||||
<menuitem name='Google Docs configuration' id='menu_gdocs_config' parent='base.menu_administration'/>
|
||||
<menuitem name='Models configuration' id='menu_gdocs_model_config' parent='menu_gdocs_config' action='action_google_docs_users_config'/>
|
||||
</data>
|
||||
</openerp>
|
|
@ -1,3 +0,0 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_google_docs,google.docs.config,model_google_docs_config,,1,0,0,0
|
||||
access_google_docs,google.docs.config,model_google_docs_config,base.group_system,1,1,1,1
|
|
|
@ -1,40 +0,0 @@
|
|||
openerp.google_docs = function(instance, m) {
|
||||
var _t = instance.web._t,
|
||||
QWeb = instance.web.qweb;
|
||||
|
||||
instance.web.Sidebar.include({
|
||||
redraw: function() {
|
||||
var self = this;
|
||||
this._super.apply(this, arguments);
|
||||
self.$el.find('.oe_sidebar_add_attachment').after(QWeb.render('AddGoogleDocumentItem', {widget: self}))
|
||||
self.$el.find('.oe_sidebar_add_google_doc').on('click', function (e) {
|
||||
self.on_google_doc();
|
||||
});
|
||||
},
|
||||
on_google_doc: function() {
|
||||
var self = this;
|
||||
var view = self.getParent();
|
||||
var ids = ( view.fields_view.type != "form" )? view.groups.get_selection().ids : [ view.datarecord.id ];
|
||||
if( !_.isEmpty(ids) ){
|
||||
view.sidebar_eval_context().done(function (context) {
|
||||
var ds = new instance.web.DataSet(this, 'ir.attachment', context);
|
||||
ds.call('google_doc_get', [view.dataset.model, ids, context]).done(function(r) {
|
||||
if (r == 'False') {
|
||||
var params = {
|
||||
error: response,
|
||||
message: _t("The user google credentials are not set yet. Contact your administrator for help.")
|
||||
}
|
||||
$(openerp.web.qweb.render("DialogWarning", params)).dialog({
|
||||
title: _t("User Google credentials are not yet set."),
|
||||
modal: true,
|
||||
});
|
||||
}
|
||||
}).done(function(r){
|
||||
window.open(r.url,"_blank");
|
||||
view.reload();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
import google_drive
|
|
@ -20,22 +20,31 @@
|
|||
##############################################################################
|
||||
|
||||
{
|
||||
'name': 'Google Docs integration',
|
||||
'name': 'Google Drive™ integration',
|
||||
'version': '0.2',
|
||||
'author': 'OpenERP SA',
|
||||
'website': 'http://openerp.com',
|
||||
'category': 'Tools',
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'js': ['static/src/js/gdocs.js'],
|
||||
'qweb': ['static/src/xml/gdocs.xml'],
|
||||
'js': [
|
||||
'static/lib/gapi/client.js',
|
||||
'static/src/js/gdrive.js',
|
||||
],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'res_config_user_view.xml'
|
||||
'res_config_user_view.xml',
|
||||
'google_drive_data.xml'
|
||||
],
|
||||
'depends': ['google_base_account','document'],
|
||||
'demo': [
|
||||
'google_drive_demo.xml'
|
||||
],
|
||||
'depends': ['base_setup', 'google_base_account'],
|
||||
'description': """
|
||||
Module to attach a google document to any model.
|
||||
================================================
|
||||
Integrate google document to OpenERP record.
|
||||
============================================
|
||||
|
||||
This module allows you to integrate google documents to any of your OpenERP record quickly and easily using OAuth 2.0 for Installed Applications,
|
||||
You can configure your google Authorization Code from Settings > Configuration > General Settings by clicking on "Generate Google Authorization Code"
|
||||
"""
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2012 OpenERP SA (<http://www.openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import logging
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
|
||||
import urllib
|
||||
import urllib2
|
||||
import json
|
||||
import re
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class config(osv.osv):
|
||||
_name = 'google.drive.config'
|
||||
_description = "Google Drive templates config"
|
||||
|
||||
def get_google_drive_url(self, cr, uid, config_id, res_id, template_id, context=None):
|
||||
config = self.browse(cr, SUPERUSER_ID, config_id, context=context)
|
||||
model = config.model_id
|
||||
filter_name = config.filter_id and config.filter_id.name or False
|
||||
record = self.pool.get(model.model).read(cr, uid, res_id, [], context=context)
|
||||
record.update({'model': model.name, 'filter': filter_name})
|
||||
name_gdocs = config.name_template
|
||||
try:
|
||||
name_gdocs = name_gdocs % record
|
||||
except:
|
||||
raise osv.except_osv(_('Key Error!'), _("At least one key cannot be found in your Google Drive name pattern"))
|
||||
|
||||
attach_pool = self.pool.get("ir.attachment")
|
||||
attach_ids = attach_pool.search(cr, uid, [('res_model', '=', model.model), ('name', '=', name_gdocs), ('res_id', '=', res_id)])
|
||||
url = False
|
||||
if attach_ids:
|
||||
attachment = attach_pool.browse(cr, uid, attach_ids[0], context)
|
||||
url = attachment.url
|
||||
else:
|
||||
url = self.copy_doc(cr, uid, res_id, template_id, name_gdocs, model.model, context)
|
||||
return url
|
||||
|
||||
def copy_doc(self, cr, uid, res_id, template_id, name_gdocs, res_model, context=None):
|
||||
ir_config = self.pool['ir.config_parameter']
|
||||
google_drive_refresh_token = ir_config.get_param(cr, SUPERUSER_ID, 'google_drive_refresh_token')
|
||||
if not google_drive_refresh_token:
|
||||
raise self.pool.get('res.config.settings').get_config_warning(cr, _("You haven't configured 'Authorization Code' generated from google, Please generate and configure it in %(menu:base_setup.menu_general_configuration)s."), context=context)
|
||||
google_drive_client_id = ir_config.get_param(cr, SUPERUSER_ID, 'google_drive_client_id')
|
||||
google_drive_client_secret = ir_config.get_param(cr, SUPERUSER_ID, 'google_drive_client_secret')
|
||||
google_web_base_url = ir_config.get_param(cr, SUPERUSER_ID, 'web.base.url')
|
||||
|
||||
#For Getting New Access Token With help of old Refresh Token
|
||||
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept-Encoding": "gzip, deflate"}
|
||||
data = dict(client_id=google_drive_client_id,
|
||||
refresh_token=google_drive_refresh_token,
|
||||
client_secret=google_drive_client_secret,
|
||||
grant_type="refresh_token")
|
||||
|
||||
data = urllib.urlencode(data)
|
||||
try:
|
||||
req = urllib2.Request('https://accounts.google.com/o/oauth2/token', data, headers)
|
||||
content = urllib2.urlopen(req).read()
|
||||
except urllib2.HTTPError:
|
||||
raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong during the token generation. Please request again an authorization code in %(menu:base_setup.menu_general_configuration)s."), context=context)
|
||||
content = json.loads(content)
|
||||
|
||||
# Copy template in to drive with help of new access token
|
||||
if 'access_token' in content:
|
||||
request_url = "https://www.googleapis.com/drive/v2/files/%s?fields=parents/id&access_token=%s" % (template_id, content['access_token'])
|
||||
try:
|
||||
req = urllib2.Request(request_url, None, headers)
|
||||
parents = urllib2.urlopen(req).read()
|
||||
except urllib2.HTTPError:
|
||||
raise self.pool.get('res.config.settings').get_config_warning(cr, _("The Google Template cannot be found. Maybe it has been deleted."), context=context)
|
||||
parents_dict = json.loads(parents)
|
||||
|
||||
record_url = "Click on link to open Record in OpenERP\n %s/?db=%s#id=%s&model=%s" % (google_web_base_url, cr.dbname, res_id, res_model)
|
||||
data = {"title": name_gdocs, "description": record_url, "parents": parents_dict['parents']}
|
||||
request_url = "https://www.googleapis.com/drive/v2/files/%s/copy?access_token=%s" % (template_id, content['access_token'])
|
||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
||||
data_json = json.dumps(data)
|
||||
# resp, content = Http().request(request_url, "POST", data_json, headers)
|
||||
req = urllib2.Request(request_url, data_json, headers)
|
||||
content = urllib2.urlopen(req).read()
|
||||
content = json.loads(content)
|
||||
res = False
|
||||
if 'alternateLink' in content.keys():
|
||||
attach_pool = self.pool.get("ir.attachment")
|
||||
attach_vals = {'res_model': res_model, 'name': name_gdocs, 'res_id': res_id, 'type': 'url', 'url': content['alternateLink']}
|
||||
attach_pool.create(cr, uid, attach_vals)
|
||||
res = content['alternateLink']
|
||||
return res
|
||||
|
||||
def get_google_drive_config(self, cr, uid, res_model, res_id, context=None):
|
||||
'''
|
||||
Function called by the js, when no google doc are yet associated with a record, with the aim to create one. It
|
||||
will first seek for a google.docs.config associated with the model `res_model` to find out what's the template
|
||||
of google doc to copy (this is usefull if you want to start with a non-empty document, a type or a name
|
||||
different than the default values). If no config is associated with the `res_model`, then a blank text document
|
||||
with a default name is created.
|
||||
:param res_model: the object for which the google doc is created
|
||||
:param ids: the list of ids of the objects for which the google doc is created. This list is supposed to have
|
||||
a length of 1 element only (batch processing is not supported in the code, though nothing really prevent it)
|
||||
:return: the config id and config name
|
||||
'''
|
||||
if not res_id:
|
||||
raise osv.except_osv(_('Google Drive Error!'), _("Creating google drive may only be done by one at a time."))
|
||||
# check if a model is configured with a template
|
||||
config_ids = self.search(cr, uid, [('model_id', '=', res_model)], context=context)
|
||||
configs = []
|
||||
for config in self.browse(cr, uid, config_ids, context=context):
|
||||
if config.filter_id:
|
||||
if (config.filter_id.user_id and config.filter_id.user_id.id != uid):
|
||||
#Private
|
||||
continue
|
||||
domain = [('id', 'in', [res_id])] + eval(config.filter_id.domain)
|
||||
local_context = context and context.copy() or {}
|
||||
local_context.update(eval(config.filter_id.context))
|
||||
google_doc_configs = self.pool.get(config.filter_id.model_id).search(cr, uid, domain, context=local_context)
|
||||
if google_doc_configs:
|
||||
configs.append({'id': config.id, 'name': config.name})
|
||||
else:
|
||||
configs.append({'id': config.id, 'name': config.name})
|
||||
return configs
|
||||
|
||||
def _resource_get(self, cr, uid, ids, name, arg, context=None):
|
||||
result = {}
|
||||
for data in self.browse(cr, uid, ids, context):
|
||||
mo = re.search("(key=|/d/)([A-Za-z0-9-_]+)", data.google_drive_template_url)
|
||||
if mo:
|
||||
result[data.id] = mo.group(2)
|
||||
else:
|
||||
raise osv.except_osv(_('Incorrect URL!'), _("Please enter a valid Google Document URL."))
|
||||
return result
|
||||
|
||||
def _client_id_get(self, cr, uid, ids, name, arg, context=None):
|
||||
result = {}
|
||||
client_id = self.pool['ir.config_parameter'].get_param(cr, SUPERUSER_ID, 'google_drive_client_id')
|
||||
for config_id in ids:
|
||||
result[config_id] = client_id
|
||||
return result
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Template Name', required=True, size=1024),
|
||||
'model_id': fields.many2one('ir.model', 'Model', ondelete='set null', required=True),
|
||||
'model': fields.related('model_id', 'model', type='char', string='Model', readonly=True),
|
||||
'filter_id': fields.many2one('ir.filters', 'Filter', domain="[('model_id', '=', model)]"),
|
||||
'google_drive_template_url': fields.char('Template URL', required=True, size=1024),
|
||||
'google_drive_resource_id': fields.function(_resource_get, type="char", string='Resource Id'),
|
||||
'google_drive_client_id': fields.function(_client_id_get, type="char", string='Google Client '),
|
||||
'name_template': fields.char('Google Drive Name Pattern', size=64, help='Choose how the new google drive will be named, on google side. Eg. gdoc_%(field_name)s', required=True),
|
||||
}
|
||||
|
||||
def onchange_model_id(self, cr, uid, ids, model_id, context=None):
|
||||
res = {}
|
||||
if model_id:
|
||||
model = self.pool['ir.model'].browse(cr, uid, model_id, context=context)
|
||||
res['value'] = {'model': model.model}
|
||||
else:
|
||||
res['value'] = {'filter_id': False, 'model': False}
|
||||
return res
|
||||
|
||||
_defaults = {
|
||||
'name_template': 'Document %(name)s',
|
||||
}
|
||||
|
||||
def _check_model_id(self, cr, uid, ids, context=None):
|
||||
config_id = self.browse(cr, uid, ids[0], context=context)
|
||||
if config_id.filter_id and config_id.model_id.model != config_id.filter_id.model_id:
|
||||
return False
|
||||
return True
|
||||
|
||||
_constraints = [
|
||||
(_check_model_id, 'Model of selected filter is not matching with model of current template.', ['model_id', 'filter_id']),
|
||||
]
|
||||
|
||||
config()
|
||||
|
||||
|
||||
class base_config_settings(osv.osv):
|
||||
_inherit = "base.config.settings"
|
||||
|
||||
_columns = {
|
||||
'google_drive_authorization_code': fields.char('Authorization Code', size=124),
|
||||
'google_drive_uri': fields.char('URI', readonly=True, help="The URL to generate the authorization code from Google"),
|
||||
}
|
||||
_defaults = {
|
||||
'google_drive_uri': lambda s, cr, uid, c: s.pool['google.service']._get_google_token_uri(cr, uid, 'drive', context=c),
|
||||
}
|
||||
|
||||
def set_google_authorization_code(self, cr, uid, ids, context=None):
|
||||
config = self.browse(cr, uid, ids[0], context)
|
||||
refresh_token = self.pool['google.service'].generate_refresh_token(cr, uid, 'drive', config.google_drive_authorization_code, context=context)
|
||||
self.pool['ir.config_parameter'].set_param(cr, uid, 'google_drive_refresh_token', refresh_token)
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="config_google_drive_client_id" model="ir.config_parameter">
|
||||
<field name="key">google_drive_client_id</field>
|
||||
<field name="value">39623646228-eg3ggo3mk6o40m7rguobi3rkl9frh4tb.apps.googleusercontent.com</field>
|
||||
</record>
|
||||
|
||||
<record id="config_google_drive_client_secret" model="ir.config_parameter">
|
||||
<field name="key">google_drive_client_secret</field>
|
||||
<field name="value">Ul-PtmnSWs3euWs20fdono0e</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- filter demo -->
|
||||
<record id="filter_partner" model="ir.filters">
|
||||
<field name="name">Customer</field>
|
||||
<field name="model_id">res.partner</field>
|
||||
<field name="domain">[['customer', '=', 1]]</field>
|
||||
<field name="user_id" eval="False" />
|
||||
</record>
|
||||
|
||||
<!-- template demo -->
|
||||
<record id="template_partner" model="google.drive.config">
|
||||
<field name="name">Partner Review</field>
|
||||
<field name="model_id" ref="base.model_res_partner"/>
|
||||
<field name="filter_id" ref="filter_partner"/>
|
||||
<field name="google_drive_template_url">https://docs.google.com/spreadsheet/ccc?key=0Ah2qnrLAoZmUdGRvdVdmS1VoSDctWk1kd18taGZ4ckE#gid=0</field>
|
||||
<field name="name_template">Partner Review %(name)s</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
# Translation of OpenERP Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * google_drive
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: OpenERP Server 8.0alpha1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2013-06-27 16:03+0000\n"
|
||||
"PO-Revision-Date: 2013-06-27 16:03+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: google_drive
|
||||
#: model:ir.ui.menu,name:google_drive.menu_google_drive_config
|
||||
msgid "Google Drive configuration"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: code:addons/google_drive/google_drive.py:48
|
||||
#, python-format
|
||||
msgid "Key Error!"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: view:google.drive.config:0
|
||||
msgid "The name of the attached document can use fixed or variable data. To distinguish between documents in\n"
|
||||
" Google Drive, use fixed words and fields. For instance, in the example above, if you wrote Agrolait_%(name)s_Sales\n"
|
||||
" in the Google Drive name field, the document in your Google Drive and in OpenERP attachment will be named\n"
|
||||
" 'Agrolait_SO0001_Sales'."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: view:google.drive.config:0
|
||||
msgid "- If filter is not specified, link of google document will appear in \"More\" option for all users for all opportunities."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: view:google.drive.config:0
|
||||
msgid "To create a new filter:"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: model:ir.model,name:google_drive.model_base_config_settings
|
||||
msgid "base.config.settings"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: model:ir.actions.act_window,help:google_drive.action_google_drive_users_config
|
||||
msgid "<p class=\"oe_view_nocontent_create\">\n"
|
||||
" Click to add a new template.\n"
|
||||
" </p>\n"
|
||||
" <p>\n"
|
||||
" Link your own google drive templates to any record of OpenERP. If you have really specific documents you want your collaborator fill in, e.g. Use a spreadsheet to control the quality of your product or review the delivery checklist for each order in a foreign country, ... Its very easy to manage them, link them to OpenERP and use them to collaborate with your employees.\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: code:addons/google_drive/google_drive.py:150
|
||||
#, python-format
|
||||
msgid "Incorrect URL!"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: view:base.config.settings:0
|
||||
msgid "Configure your templates"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: help:google.drive.config,name_template:0
|
||||
msgid "Choose how the new google drive will be named, on google side. Eg. gdoc_%(field_name)s"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: view:google.drive.config:0
|
||||
msgid "- Go to the OpenERP document you want to filter. For instance, go to Opportunities and search on Sales Department."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: view:google.drive.config:0
|
||||
msgid "- In this \"Search\" view, select the option \"Save Current Filter\", enter the name (Ex: Sales Department)"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: view:google.drive.config:0
|
||||
msgid "- If you select \"Share with all users\", link of google document in \"More\" options will appear for all users in opportunities of Sales Department."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: view:google.drive.config:0
|
||||
msgid "- If you don't select \"Share with all users\", link of google document in \"More\" options will not appear for other users in opportunities of Sales Department."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: code:addons/google_drive/google_drive.py:48
|
||||
#, python-format
|
||||
msgid "At least one key cannot be found in your Google Drive name pattern"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: code:addons/google_drive/google_drive.py:150
|
||||
#, python-format
|
||||
msgid "Please enter a valid Google Document URL."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: field:google.drive.config,google_drive_client_id:0
|
||||
msgid "Google Client "
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: view:google.drive.config:0
|
||||
msgid "https://docs.google.com/document/d/1vOtpJK9scIQz6taD9tJRIETWbEw3fSiaQHArsJYcua4/edit"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: field:google.drive.config,filter_id:0
|
||||
msgid "Filter"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: field:google.drive.config,name_template:0
|
||||
msgid "Google Drive Name Pattern"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: help:base.config.settings,google_drive_uri:0
|
||||
msgid "The URL to generate the authorization code from Google"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: model:ir.filters,name:google_drive.filter_partner
|
||||
msgid "Customer"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: field:google.drive.config,google_drive_resource_id:0
|
||||
msgid "Resource Id"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: code:addons/google_drive/google_drive.py:91
|
||||
#, python-format
|
||||
msgid "The Google Template cannot be found. Maybe it has been deleted."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: model:ir.actions.act_window,name:google_drive.action_google_drive_users_config
|
||||
#: model:ir.ui.menu,name:google_drive.menu_google_drive_model_config
|
||||
msgid "Google Drive Templates"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: code:addons/google_drive/google_drive.py:81
|
||||
#, python-format
|
||||
msgid "Something went wrong during the token generation. Please request again an authorization code in %(menu:base_setup.menu_general_configuration)s."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: code:addons/google_drive/google_drive.py:124
|
||||
#, python-format
|
||||
msgid "Google Drive Error!"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: field:base.config.settings,google_drive_uri:0
|
||||
msgid "URI"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: code:addons/google_drive/google_drive.py:124
|
||||
#, python-format
|
||||
msgid "Creating google drive may only be done by one at a time."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: field:google.drive.config,model:0
|
||||
#: field:google.drive.config,model_id:0
|
||||
msgid "Model"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: view:google.drive.config:0
|
||||
msgid "Google Drive Configuration"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: field:google.drive.config,name:0
|
||||
msgid "Template Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: constraint:google.drive.config:0
|
||||
msgid "Model of selected filter is not matching with model of current template."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: field:google.drive.config,google_drive_template_url:0
|
||||
msgid "Template URL"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: view:base.config.settings:0
|
||||
msgid "and paste it here"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: field:base.config.settings,google_drive_authorization_code:0
|
||||
msgid "Authorization Code"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: model:ir.model,name:google_drive.model_google_drive_config
|
||||
msgid "Google Drive templates config"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_drive
|
||||
#: code:addons/google_drive/google_drive.py:64
|
||||
#, python-format
|
||||
msgid "You haven't configured 'Authorization Code' generated from google, Please generate and configure it in %(menu:base_setup.menu_general_configuration)s."
|
||||
msgstr ""
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- add google drive config field in user form -->
|
||||
|
||||
<record model="ir.ui.view" id="view_google_drive_config_tree">
|
||||
<field name="name">google_drive.config.tree</field>
|
||||
<field name="model">google.drive.config</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Google Drive Configuration">
|
||||
<field name="name" />
|
||||
<field name="model_id" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_google_drive_config_form">
|
||||
<field name="name">google_drive.config.form</field>
|
||||
<field name="model">google.drive.config</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Google Drive Configuration" version="7.0">
|
||||
<field name="model" invisible="1" />
|
||||
<group>
|
||||
<field name="name" />
|
||||
<field name="model_id" on_change="onchange_model_id(model_id)" />
|
||||
<label for='filter_id' />
|
||||
<div>
|
||||
<field name='filter_id' />
|
||||
<p class="oe_grey">
|
||||
<b>To create a new filter:</b><br/>
|
||||
- Go to the OpenERP document you want to filter. For instance, go to Opportunities and search on Sales Department.<br/>
|
||||
- In this "Search" view, select the option "Save Current Filter", enter the name (Ex: Sales Department)<br/>
|
||||
- If you select "Share with all users", link of google document in "More" options will appear for all users in opportunities of Sales Department.<br/>
|
||||
- If you don't select "Share with all users", link of google document in "More" options will not appear for other users in opportunities of Sales Department.<br/>
|
||||
- If filter is not specified, link of google document will appear in "More" option for all users for all opportunities.
|
||||
</p>
|
||||
</div>
|
||||
<field name='google_drive_template_url' placeholder="https://docs.google.com/document/d/1vOtpJK9scIQz6taD9tJRIETWbEw3fSiaQHArsJYcua4/edit" required="1" />
|
||||
<field name='google_drive_resource_id' invisible="1" />
|
||||
<label for='name_template' />
|
||||
<div>
|
||||
<field name='name_template' />
|
||||
<p class="oe_grey">
|
||||
The name of the attached document can use fixed or variable data. To distinguish between documents in
|
||||
Google Drive, use fixed words and fields. For instance, in the example above, if you wrote Agrolait_%%(name)s_Sales
|
||||
in the Google Drive name field, the document in your Google Drive and in OpenERP attachment will be named
|
||||
'Agrolait_SO0001_Sales'.
|
||||
</p>
|
||||
</div>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model='ir.actions.act_window' id='action_google_drive_users_config'>
|
||||
<field name='name'>Google Drive Templates</field>
|
||||
<field name='res_model'>google.drive.config</field>
|
||||
<field name='type'>ir.actions.act_window</field>
|
||||
<field name='view_type'>form</field>
|
||||
<field name='view_id' ref='view_google_drive_config_tree' />
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to add a new template.
|
||||
</p>
|
||||
<p>
|
||||
Link your own google drive templates to any record of OpenERP. If you have really specific documents you want your collaborator fill in, e.g. Use a spreadsheet to control the quality of your product or review the delivery checklist for each order in a foreign country, ... Its very easy to manage them, link them to OpenERP and use them to collaborate with your employees.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="inherited_google_view_general_configuration" model="ir.ui.view">
|
||||
<field name="name">General Settings</field>
|
||||
<field name="model">base.config.settings</field>
|
||||
<field name="inherit_id" ref="base_setup.view_general_configuration" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='module_google_drive']" position="after">
|
||||
<div attrs="{'invisible': [('module_google_drive','=',False)]}">
|
||||
<div class="oe_inline">
|
||||
<field name="google_drive_uri" widget="url" text="Generate Google Authorization Code" class="oe_inline oe_bold"/>
|
||||
and paste it here
|
||||
<field name="google_drive_authorization_code" class="oe_inline" />
|
||||
</div>
|
||||
<button type="action" name="%(google_drive.action_google_drive_users_config)d" string="Configure your templates" class="oe_link" />
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem name='Google Drive configuration' id='menu_google_drive_config' parent='base.menu_administration' />
|
||||
<menuitem id='menu_google_drive_model_config' parent='menu_google_drive_config' action='action_google_drive_users_config' />
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,3 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_google_drive_all,google.drive.config,model_google_drive_config,,1,0,0,0
|
||||
access_google_drive,google.drive.config,model_google_drive_config,base.group_system,1,1,1,1
|
|
|
@ -0,0 +1,7 @@
|
|||
var gapi=window.gapi=window.gapi||{};gapi._bs=new Date().getTime();(function(){var f=null,g=encodeURIComponent,k=window,m=decodeURIComponent,n="push",r="test",t="shift",u="replace",y="length",B="split",C="join";var D=k,E=document,aa=D.location,ba=function(){},ca=/\[native code\]/,G=function(a,b,c){return a[b]=a[b]||c},da=function(a){for(var b=0;b<this[y];b++)if(this[b]===a)return b;return-1},ea=function(a){a=a.sort();for(var b=[],c=void 0,d=0;d<a[y];d++){var e=a[d];e!=c&&b[n](e);c=e}return b},H=function(){var a;if((a=Object.create)&&ca[r](a))a=a(f);else{a={};for(var b in a)a[b]=void 0}return a},I=G(D,"gapi",{});var J;J=G(D,"___jsl",H());G(J,"I",0);G(J,"hel",10);var K=function(){var a=aa.href,b;if(J.dpo)b=J.h;else{b=J.h;var c=RegExp("([#].*&|[#])jsh=([^&#]*)","g"),d=RegExp("([?#].*&|[?#])jsh=([^&#]*)","g");if(a=a&&(c.exec(a)||d.exec(a)))try{b=m(a[2])}catch(e){}}return b},fa=function(a){var b=G(J,"PQ",[]);J.PQ=[];var c=b[y];if(0===c)a();else for(var d=0,e=function(){++d===c&&a()},h=0;h<c;h++)b[h](e)},L=function(a){return G(G(J,"H",H()),a,H())};var M=G(J,"perf",H()),N=G(M,"g",H()),ga=G(M,"i",H());G(M,"r",[]);H();H();var O=function(a,b,c){var d=M.r;"function"===typeof d?d(a,b,c):d[n]([a,b,c])},Q=function(a,b,c){b&&0<b[y]&&(b=P(b),c&&0<c[y]&&(b+="___"+P(c)),28<b[y]&&(b=b.substr(0,28)+(b[y]-28)),c=b,b=G(ga,"_p",H()),G(b,c,H())[a]=(new Date).getTime(),O(a,"_p",c))},P=function(a){return a[C]("__")[u](/\./g,"_")[u](/\-/g,"_")[u](/\,/g,"_")};var S=H(),T=[],U=function(a){throw Error("Bad hint"+(a?": "+a:""));};T[n](["jsl",function(a){for(var b in a)if(Object.prototype.hasOwnProperty.call(a,b)){var c=a[b];"object"==typeof c?J[b]=G(J,b,[]).concat(c):G(J,b,c)}if(b=a.u)a=G(J,"us",[]),a[n](b),(b=/^https:(.*)$/.exec(b))&&a[n]("http:"+b[1])}]);var ha=/^(\/[a-zA-Z0-9_\-]+)+$/,ia=/^[a-zA-Z0-9\-_\.!]+$/,ja=/^gapi\.loaded_[0-9]+$/,ka=/^[a-zA-Z0-9,._-]+$/,oa=function(a,b,c,d){var e=a[B](";"),h=S[e[t]()],l=f;h&&(l=h(e,b,c,d));if(!(b=!l))b=l,c=b.match(la),d=b.match(ma),b=!(d&&1===d[y]&&na[r](b)&&c&&1===c[y]);b&&U(a);return l},qa=function(a,b,c,d){a=pa(a);ja[r](c)||U("invalid_callback");b=V(b);d=d&&d[y]?V(d):f;var e=function(a){return g(a)[u](/%2C/g,",")};return[g(a.d)[u](/%2C/g,",")[u](/%2F/g,"/"),"/k=",e(a.version),"/m=",e(b),d?"/exm="+e(d):
|
||||
"","/rt=j/sv=1/d=1/ed=1",a.a?"/am="+e(a.a):"",a.b?"/rs="+e(a.b):"","/cb=",e(c)][C]("")},pa=function(a){"/"!==a.charAt(0)&&U("relative path");for(var b=a.substring(1)[B]("/"),c=[];b[y];){a=b[t]();if(!a[y]||0==a.indexOf("."))U("empty/relative directory");else if(0<a.indexOf("=")){b.unshift(a);break}c[n](a)}a={};for(var d=0,e=b[y];d<e;++d){var h=b[d][B]("="),l=m(h[0]),p=m(h[1]);2!=h[y]||(!l||!p)||(a[l]=a[l]||p)}b="/"+c[C]("/");ha[r](b)||U("invalid_prefix");c=W(a,"k",!0);d=W(a,"am");a=W(a,"rs");return{d:b,
|
||||
version:c,a:d,b:a}},V=function(a){for(var b=[],c=0,d=a[y];c<d;++c){var e=a[c][u](/\./g,"_")[u](/-/g,"_");ka[r](e)&&b[n](e)}return b[C](",")},W=function(a,b,c){a=a[b];!a&&c&&U("missing: "+b);if(a){if(ia[r](a))return a;U("invalid: "+b)}return f},na=/^https?:\/\/[a-z0-9_.-]+\.google\.com(:\d+)?\/[a-zA-Z0-9_.,!=\-\/]+$/,ma=/\/cb=/g,la=/\/\//g,ra=function(){var a=K();if(!a)throw Error("Bad hint");return a};S.m=function(a,b,c,d){(a=a[0])||U("missing_hint");return"https://apis.google.com"+qa(a,b,c,d)};var X=decodeURI("%73cript"),Y=function(a,b){for(var c=[],d=0;d<a[y];++d){var e=a[d];e&&0>da.call(b,e)&&c[n](e)}return c},sa=function(a){"loading"!=E.readyState?Z(a):E.write("<"+X+' src="'+encodeURI(a)+'"></'+X+">")},Z=function(a){var b=E.createElement(X);b.setAttribute("src",a);b.async="true";(a=E.getElementsByTagName(X)[0])?a.parentNode.insertBefore(b,a):(E.head||E.body||E.documentElement).appendChild(b)},ta=function(a,b){var c=b&&b._c;if(c)for(var d=0;d<T[y];d++){var e=T[d][0],h=T[d][1];h&&Object.prototype.hasOwnProperty.call(c,
|
||||
e)&&h(c[e],a,b)}},ua=function(a,b){$(function(){var c;c=b===K()?G(I,"_",H()):H();c=G(L(b),"_",c);a(c)})},wa=function(a,b){var c=b||{};"function"==typeof b&&(c={},c.callback=b);ta(a,c);var d=a?a[B](":"):[],e=c.h||ra(),h=G(J,"ah",H());if(!h["::"]||!d[y])va(d||[],c,e);else{for(var l=[],p=f;p=d[t]();){var v=p[B]("."),v=h[p]||h[v[1]&&"ns:"+v[0]||""]||e,s=l[y]&&l[l[y]-1]||f,z=s;if(!s||s.hint!=v)z={hint:v,c:[]},l[n](z);z.c[n](p)}var A=l[y];if(1<A){var F=c.callback;F&&(c.callback=function(){0==--A&&F()})}for(;d=
|
||||
l[t]();)va(d.c,c,d.hint)}},va=function(a,b,c){a=ea(a)||[];var d=b.callback,e=b.config,h=b.timeout,l=b.ontimeout,p=f,v=!1;if(h&&!l||!h&&l)throw"Timeout requires both the timeout parameter and ontimeout parameter to be set";var s=G(L(c),"r",[]).sort(),z=G(L(c),"L",[]).sort(),A=[].concat(s),F=function(a,b){if(v)return 0;D.clearTimeout(p);z[n].apply(z,q);var d=((I||{}).config||{}).update;d?d(e):e&&G(J,"cu",[])[n](e);if(b){Q("me0",a,A);try{ua(b,c)}finally{Q("me1",a,A)}}return 1};0<h&&(p=D.setTimeout(function(){v=
|
||||
!0;l()},h));var q=Y(a,z);if(q[y]){var q=Y(a,s),w=G(J,"CP",[]),x=w[y];w[x]=function(a){if(!a)return 0;Q("ml1",q,A);var b=function(b){w[x]=f;F(q,a)&&fa(function(){d&&d();b()})},c=function(){var a=w[x+1];a&&a()};0<x&&w[x-1]?w[x]=function(){b(c)}:b(c)};if(q[y]){var R="loaded_"+J.I++;I[R]=function(a){w[x](a);I[R]=f};a=oa(c,q,"gapi."+R,s);s[n].apply(s,q);Q("ml0",q,A);b.sync||D.___gapisync?sa(a):Z(a)}else w[x](ba)}else F(q)&&d&&d()};var $=function(a){if(J.hee&&0<J.hel)try{return a()}catch(b){J.hel--,wa("debug_error",function(){k.___jsl.hefn(b)})}else return a()};I.load=function(a,b){return $(function(){return wa(a,b)})};N.bs0=k.gapi._bs||(new Date).getTime();O("bs0");N.bs1=(new Date).getTime();O("bs1");delete k.gapi._bs;})();
|
||||
gapi.load("client",{callback:window["gapi_onload"],_c:{"jsl":{"ci":{"services":{},"deviceType":"desktop","lexps":[102,103,100,71,98,96,110,108,79,106,45,17,86,81,112,61,30],"inline":{"css":1},"report":{},"oauth-flow":{"disableOpt":true,"authUrl":"https://accounts.google.com/o/oauth2/auth","proxyUrl":"https://accounts.google.com/o/oauth2/postmessageRelay","persist":true},"isLoggedIn":true,"isPlusUser":true,"iframes":{"additnow":{"methods":["launchurl"],"url":"https://apis.google.com/additnow/additnow.html?bsv"},"shortlists":{"url":"?bsv"},"plus":{"methods":["onauth"],"url":":socialhost:/u/:session_index:/_/pages/badge?bsv"},":socialhost:":"https://plusone.google.com","recobox":{"params":{"url":""},"url":":socialhost:/:session_prefix:_/widget/render/recobox?bsv"},"plus_followers":{"params":{"url":""},"url":":socialhost:/_/im/_/widget/render/plus/followers?bsv"},"autocomplete":{"params":{"url":""},"url":":socialhost:/:session_prefix:_/widget/render/autocomplete?bsv"},"plus_share":{"params":{"url":""},"url":":socialhost:/:session_prefix:_/+1/sharebutton?plusShare\u003dtrue\u0026bsv"},"savetowallet":{"url":"https://clients5.google.com/s2w/o/savetowallet?bsv"},"panoembed":{"url":"https://ssl.gstatic.com/pano/embed/?bsv"},"signin":{"methods":["onauth"],"params":{"url":""},"url":":socialhost:/:session_prefix:_/widget/render/signin?bsv"},"appcirclepicker":{"url":":socialhost:/:session_prefix:_/widget/render/appcirclepicker?bsv"},"commentcount":{"url":":socialhost:/:session_prefix:_/widget/render/commentcount?bsv"},"hangout":{"url":"https://talkgadget.google.com/:session_prefix:talkgadget/_/widget?bsv"},"plus_circle":{"params":{"url":""},"url":":socialhost:/:session_prefix:_/widget/plus/circle?bsv"},"savetodrive":{"methods":["save"],"url":"https://drive.google.com/savetodrivebutton?usegapi\u003d1\u0026bsv"},"card":{"url":":socialhost:/:session_prefix:_/hovercard/card?bsv"},"evwidget":{"params":{"url":""},"url":":socialhost:/:session_prefix:_/events/widget?bsv"},"zoomableimage":{"url":"https://ssl.gstatic.com/microscope/embed/?bsv"},":signuphost:":"https://plus.google.com","plusone":{"preloadUrl":["https://ssl.gstatic.com/s2/oz/images/stars/po/Publisher/sprite4-a67f741843ffc4220554c34bd01bb0bb.png"],"params":{"count":"","size":"","url":""},"url":":socialhost:/:session_prefix:_/+1/fastbutton?bsv"},"comments":{"methods":["scroll","openwindow"],"params":{"location":["search","hash"]},"url":":socialhost:/:session_prefix:_/widget/render/comments?bsv"}},"debug":{"host":"https://plusone.google.com","reportExceptionRate":0.05,"rethrowException":true},"csi":{"rate":0.01},"googleapis.config":{"mobilesignupurl":"https://m.google.com/app/plus/oob?"}},"h":"m;/_/scs/apps-static/_/js/k\u003doz.gapi.en.02N985CHyyc.O/m\u003d__features__/am\u003dIQ/rt\u003dj/d\u003d1/rs\u003dAItRSTPZZ0JVQCv9Qljsu0NQlsb1ZzD2zQ","u":"https://apis.google.com/js/client.js","hee":true,"fp":"e2aa6cd0095417dbec61deca3abed1394160dab3","dpo":false},"fp":"e2aa6cd0095417dbec61deca3abed1394160dab3","annotation":["autocomplete","profile","interactivepost"],"bimodal":["signin"]}});
|
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 479 B |
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
|
@ -0,0 +1,71 @@
|
|||
openerp.google_drive = function (instance, m) {
|
||||
var _t = instance.web._t,
|
||||
QWeb = instance.web.qweb;
|
||||
|
||||
instance.web.Sidebar.include({
|
||||
start: function () {
|
||||
var self = this;
|
||||
var ids
|
||||
this._super.apply(this, arguments);
|
||||
var view = self.getParent();
|
||||
var result;
|
||||
if (view.fields_view.type == "form") {
|
||||
ids = []
|
||||
view.on("load_record", self, function (r) {
|
||||
ids = [r.id]
|
||||
self.add_gdoc_items(view, r.id)
|
||||
});
|
||||
}
|
||||
},
|
||||
add_gdoc_items: function (view, res_id) {
|
||||
var self = this;
|
||||
var gdoc_item = _.indexOf(_.pluck(self.items.other, 'classname'), 'oe_share_gdoc');
|
||||
if (gdoc_item !== -1) {
|
||||
self.items.other.splice(gdoc_item, 1);
|
||||
}
|
||||
if (res_id) {
|
||||
view.sidebar_eval_context().done(function (context) {
|
||||
var ds = new instance.web.DataSet(this, 'google.drive.config', context);
|
||||
ds.call('get_google_drive_config', [view.dataset.model, res_id, context]).done(function (r) {
|
||||
if (!_.isEmpty(r)) {
|
||||
_.each(r, function (res) {
|
||||
var g_item = _.indexOf(_.pluck(self.items.other, 'label'), res.name);
|
||||
if (g_item !== -1) {
|
||||
self.items.other.splice(g_item, 1);
|
||||
}
|
||||
self.add_items('other', [{
|
||||
label: res.name+ '<img style="position:absolute;right:5px;height:20px;width:20px;" title="Google Drive" src="google_drive/static/src/img/drive_icon.png"/>',
|
||||
config_id: res.id,
|
||||
res_id: res_id,
|
||||
res_model: view.dataset.model,
|
||||
callback: self.on_google_doc,
|
||||
classname: 'oe_share_gdoc'
|
||||
},
|
||||
]);
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
fetch: function (model, fields, domain, ctx) {
|
||||
return new instance.web.Model(model).query(fields).filter(domain).context(ctx).all()
|
||||
},
|
||||
|
||||
on_google_doc: function (doc_item) {
|
||||
var self = this;
|
||||
self.config = doc_item;
|
||||
var loaded = self.fetch('google.drive.config', ['google_drive_resource_id', 'google_drive_client_id'], [['id', '=', doc_item.config_id]])
|
||||
.then(function (configs) {
|
||||
var ds = new instance.web.DataSet(self, 'google.drive.config');
|
||||
ds.call('get_google_drive_url', [doc_item.config_id, doc_item.res_id,configs[0].google_drive_resource_id]).done(function (url) {
|
||||
if (url){
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
};
|
|
@ -348,12 +348,10 @@
|
|||
<h1><field name="name" class="oe_inline"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<group name="job_data">
|
||||
<field name="no_of_employee" groups="base.group_user"/>
|
||||
<field name="no_of_recruitment" on_change="on_change_expected_employee(no_of_recruitment,no_of_employee)"/>
|
||||
<field name="expected_employees" groups="base.group_user"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="company_id" widget="selection" groups="base.group_multi_company"/>
|
||||
<field name="department_id"/>
|
||||
</group>
|
||||
|
|
|
@ -65,9 +65,9 @@ class hr_expense_expense(osv.osv):
|
|||
_order = "id desc"
|
||||
_track = {
|
||||
'state': {
|
||||
'hr_expense.mt_expense_approved': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'accepted',
|
||||
'hr_expense.mt_expense_refused': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'cancelled',
|
||||
'hr_expense.mt_expense_confirmed': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'confirm',
|
||||
'hr_expense.mt_expense_approved': lambda self, cr, uid, obj, ctx=None: obj.state == 'accepted',
|
||||
'hr_expense.mt_expense_refused': lambda self, cr, uid, obj, ctx=None: obj.state == 'cancelled',
|
||||
'hr_expense.mt_expense_confirmed': lambda self, cr, uid, obj, ctx=None: obj.state == 'confirm',
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -110,9 +110,9 @@ class hr_holidays(osv.osv):
|
|||
_inherit = ['mail.thread', 'ir.needaction_mixin']
|
||||
_track = {
|
||||
'state': {
|
||||
'hr_holidays.mt_holidays_approved': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'validate',
|
||||
'hr_holidays.mt_holidays_refused': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'refuse',
|
||||
'hr_holidays.mt_holidays_confirmed': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'confirm',
|
||||
'hr_holidays.mt_holidays_approved': lambda self, cr, uid, obj, ctx=None: obj.state == 'validate',
|
||||
'hr_holidays.mt_holidays_refused': lambda self, cr, uid, obj, ctx=None: obj.state == 'refuse',
|
||||
'hr_holidays.mt_holidays_confirmed': lambda self, cr, uid, obj, ctx=None: obj.state == 'confirm',
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -94,11 +94,11 @@ class hr_applicant(base_stage, osv.Model):
|
|||
_inherit = ['mail.thread', 'ir.needaction_mixin']
|
||||
_track = {
|
||||
'state': {
|
||||
'hr_recruitment.mt_applicant_hired': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'done',
|
||||
'hr_recruitment.mt_applicant_refused': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'cancel',
|
||||
'hr_recruitment.mt_applicant_hired': lambda self, cr, uid, obj, ctx=None: obj.state == 'done',
|
||||
'hr_recruitment.mt_applicant_refused': lambda self, cr, uid, obj, ctx=None: obj.state == 'cancel',
|
||||
},
|
||||
'stage_id': {
|
||||
'hr_recruitment.mt_stage_changed': lambda self, cr, uid, obj, ctx=None: obj['state'] not in ['done', 'cancel'],
|
||||
'hr_recruitment.mt_stage_changed': lambda self, cr, uid, obj, ctx=None: obj.state not in ['done', 'cancel'],
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -506,33 +506,18 @@ class hr_job(osv.osv):
|
|||
help="Email alias for this job position. New emails will automatically "
|
||||
"create new applicants for this job position."),
|
||||
}
|
||||
_defaults = {
|
||||
'alias_domain': False, # always hide alias during creation
|
||||
}
|
||||
|
||||
def _auto_init(self, cr, context=None):
|
||||
"""Installation hook to create aliases for all jobs and avoid constraint errors."""
|
||||
if context is None:
|
||||
context = {}
|
||||
alias_context = dict(context, alias_model_name='hr.applicant')
|
||||
res = self.pool.get('mail.alias').migrate_to_alias(cr, self._name, self._table, super(hr_job, self)._auto_init,
|
||||
self._columns['alias_id'], 'name', alias_prefix='job+', alias_defaults={'job_id': 'id'}, context=alias_context)
|
||||
return res
|
||||
return self.pool.get('mail.alias').migrate_to_alias(cr, self._name, self._table, super(hr_job, self)._auto_init,
|
||||
'hr.applicant', self._columns['alias_id'], 'name', alias_prefix='job+', alias_defaults={'job_id': 'id'}, context=context)
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
mail_alias = self.pool.get('mail.alias')
|
||||
if not vals.get('alias_id'):
|
||||
vals.pop('alias_name', None) # prevent errors during copy()
|
||||
alias_id = mail_alias.create_unique_alias(cr, uid,
|
||||
# Using '+' allows using subaddressing for those who don't
|
||||
# have a catchall domain setup.
|
||||
{'alias_name': 'jobs+'+vals['name']},
|
||||
model_name="hr.applicant",
|
||||
context=context)
|
||||
vals['alias_id'] = alias_id
|
||||
res = super(hr_job, self).create(cr, uid, vals, context)
|
||||
mail_alias.write(cr, uid, [vals['alias_id']], {"alias_defaults": {'job_id': res}}, context)
|
||||
return res
|
||||
alias_context = dict(context, alias_model_name='hr.applicant', alias_parent_model_name=self._name)
|
||||
job_id = super(hr_job, self).create(cr, uid, vals, context=alias_context)
|
||||
job = self.browse(cr, uid, job_id, context=context)
|
||||
self.pool.get('mail.alias').write(cr, uid, [job.alias_id.id], {'alias_parent_thread_id': job_id, "alias_defaults": {'job_id': job_id}}, context)
|
||||
return job_id
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
# Cascade-delete mail aliases as well, as they should not exist without the job position.
|
||||
|
@ -550,15 +535,16 @@ class hr_job(osv.osv):
|
|||
if record.survey_id:
|
||||
datas['ids'] = [record.survey_id.id]
|
||||
datas['model'] = 'survey.print'
|
||||
context.update({'response_id': [0], 'response_no': 0,})
|
||||
context.update({'response_id': [0], 'response_no': 0})
|
||||
return {
|
||||
'type': 'ir.actions.report.xml',
|
||||
'report_name': 'survey.form',
|
||||
'datas': datas,
|
||||
'context' : context,
|
||||
'nodestroy':True,
|
||||
'context': context,
|
||||
'nodestroy': True,
|
||||
}
|
||||
|
||||
|
||||
class applicant_category(osv.osv):
|
||||
""" Category of applicant """
|
||||
_name = "hr.applicant_category"
|
||||
|
|
|
@ -316,18 +316,19 @@
|
|||
attrs="{'invisible':[('survey_id','=',False)]}"/>
|
||||
</div>
|
||||
</field>
|
||||
<xpath expr="//div[@class='oe_title']//h1" position="after">
|
||||
<div name="group_alias"
|
||||
<xpath expr="//group[@name='job_data']" position="after">
|
||||
<group name="group_alias"
|
||||
attrs="{'invisible': [('alias_domain', '=', False)]}">
|
||||
<label for="alias_id" string="Email Alias"/>
|
||||
<field name="alias_id" class="oe_inline oe_read_only" required="0" nolabel="1"/>
|
||||
<span name="edit_alias" class="oe_edit_only">
|
||||
<field name="alias_name" class="oe_inline"
|
||||
attrs="{'required': [('alias_id', '!=', False)]}"/>
|
||||
@
|
||||
<field name="alias_domain" class="oe_inline" readonly="1"/>
|
||||
</span>
|
||||
<label for="alias_name" string="Email Alias"/>
|
||||
<div name="alias_def">
|
||||
<field name="alias_id" class="oe_read_only oe_inline"
|
||||
string="Email Alias" required="0"/>
|
||||
<div class="oe_edit_only oe_inline" name="edit_alias" style="display: inline;" >
|
||||
<field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline" readonly="1"/>
|
||||
</div>
|
||||
</div>
|
||||
<field name="alias_contact" class="oe_inline" string="Accept Emails From"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2004-Today OpenERP S.A. (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -19,5 +19,4 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import idea
|
||||
|
||||
import models
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2004-Today OpenERP S.A. (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -22,29 +22,39 @@
|
|||
|
||||
{
|
||||
'name': 'Ideas',
|
||||
'version': '0.1',
|
||||
'summary': 'Share and Discuss your Ideas',
|
||||
'version': '1.0',
|
||||
'category': 'Tools',
|
||||
'description': """
|
||||
This module allows user to easily and efficiently participate in enterprise innovation.
|
||||
=======================================================================================
|
||||
Share your ideas and participate in enterprise innovation
|
||||
=========================================================
|
||||
|
||||
It allows everybody to express ideas about different subjects.
|
||||
Then, other users can comment on these ideas and vote for particular ideas.
|
||||
Each idea has a score based on the different votes.
|
||||
The Ideas module give users a way to express and discuss ideas, allowing everybody
|
||||
to participate in enterprise innovation. Every user can suggest, comment ideas.
|
||||
The managers can obtain an easy view of best ideas from all the users.
|
||||
Once installed, check the menu 'Ideas' in the 'Tools' main menu.""",
|
||||
'author': 'OpenERP SA',
|
||||
'website': 'http://openerp.com',
|
||||
'website': 'http://www.openerp.com',
|
||||
'depends': ['mail'],
|
||||
'data': [
|
||||
'security/idea_security.xml',
|
||||
'security/idea.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'idea_view.xml',
|
||||
'idea_workflow.xml',
|
||||
'views/idea.xml',
|
||||
'views/category.xml',
|
||||
'data/idea.xml',
|
||||
'data/idea_workflow.xml',
|
||||
],
|
||||
'demo': [
|
||||
'demo/idea.xml',
|
||||
],
|
||||
'demo': ['idea_data.xml'],
|
||||
'test':[],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'images': [],
|
||||
'css': [
|
||||
'static/src/css/idea_idea.css',
|
||||
],
|
||||
'js': [],
|
||||
'qweb': [],
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record model="workflow" id="wkf_idea">
|
||||
<field name="name">idea.wkf</field>
|
||||
<field name="osv">idea.idea</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<record model="workflow.activity" id="act_normal">
|
||||
<field name="wkf_id" ref="wkf_idea" />
|
||||
<field name="flow_start">True</field>
|
||||
<field name="name">normal</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">idea_set_normal_priority()</field>
|
||||
</record>
|
||||
|
||||
<record model="workflow.activity" id="act_low">
|
||||
<field name="wkf_id" ref="wkf_idea" />
|
||||
<field name="name">low</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">idea_set_low_priority()</field>
|
||||
</record>
|
||||
|
||||
<record model="workflow.activity" id="act_high">
|
||||
<field name="wkf_id" ref="wkf_idea" />
|
||||
<field name="name">high</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">idea_set_high_priority()</field>
|
||||
</record>
|
||||
|
||||
<record model="workflow.transition" id="t1">
|
||||
<field name="act_from" ref="act_normal" />
|
||||
<field name="act_to" ref="act_low" />
|
||||
<field name="signal">idea_set_low_priority</field>
|
||||
</record>
|
||||
|
||||
<record model="workflow.transition" id="t2">
|
||||
<field name="act_from" ref="act_low" />
|
||||
<field name="act_to" ref="act_normal" />
|
||||
<field name="signal">idea_set_normal_priority</field>
|
||||
</record>
|
||||
|
||||
<record model="workflow.transition" id="t3">
|
||||
<field name="act_from" ref="act_normal" />
|
||||
<field name="act_to" ref="act_high" />
|
||||
<field name="signal">idea_set_high_priority</field>
|
||||
</record>
|
||||
|
||||
<record model="workflow.transition" id="t4">
|
||||
<field name="act_from" ref="act_high" />
|
||||
<field name="act_to" ref="act_normal" />
|
||||
<field name="signal">idea_set_normal_priority</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record model="idea.category" id="idea_cat_0">
|
||||
<field name="name">Sales</field>
|
||||
</record>
|
||||
<record model="idea.category" id="idea_cat_1">
|
||||
<field name="name">Organization</field>
|
||||
</record>
|
||||
<record model="idea.category" id="idea_cat_2">
|
||||
<field name="name">Technical</field>
|
||||
</record>
|
||||
|
||||
<record model="idea.idea" id="idea_idea_0">
|
||||
<field name="name">Docking station along with tablet PC</field>
|
||||
<field name="description">When you sell a tablet PC, maybe we could propose a docking station with it. I offer 20% on the docking stating (not the tablet).</field>
|
||||
<field name="user_id" eval="ref('base.user_demo')"/>
|
||||
<field name="category_ids" eval="[(6, 0, [ref('idea.idea_cat_0')])]"/>
|
||||
</record>
|
||||
|
||||
<record model="idea.idea" id="idea_idea_1">
|
||||
<field name="name">Communicate using emails</field>
|
||||
<field name="description">I start communicating with prospects more by email than phonecalls. I send an email to create a sense of emergency, like "can I call you this week about our quote?" and I call only those that answer this email.</field>
|
||||
<field name="user_id" eval="ref('base.user_demo')"/>
|
||||
<field name="state">open</field>
|
||||
<field name="category_ids" eval="[(6, 0, [ref('idea.idea_cat_0'), ref('idea.idea_cat_1')])]"/>
|
||||
</record>
|
||||
<workflow action="idea_set_high_priority" model="idea.idea" ref="idea_idea_1"/>
|
||||
|
||||
<record model="idea.idea" id="idea_idea_2">
|
||||
<field name="name">Use a two-stages testing phase</field>
|
||||
<field name="description">We should perform testing using two levels of validation.</field>
|
||||
<field name="user_id" eval="ref('base.user_root')"/>
|
||||
<field name="state">open</field>
|
||||
<field name="category_ids" eval="[(6, 0, [ref('idea.idea_cat_1'), ref('idea.idea_cat_2')])]"/>
|
||||
</record>
|
||||
<workflow action="idea_set_high_priority" model="idea.idea" ref="idea_idea_2"/>
|
||||
|
||||
<record model="idea.idea" id="idea_idea_3">
|
||||
<field name="name">Write some functional documentation about procurements</field>
|
||||
<field name="description">We receive many questions about OpenChatter. Maybe some functional doc could save us some time.</field>
|
||||
<field name="user_id" eval="ref('base.user_demo')"/>
|
||||
<field name="state">open</field>
|
||||
<field name="category_ids" eval="[(6, 0, [ref('idea.idea_cat_0'), ref('idea.idea_cat_1')])]"/>
|
||||
</record>
|
||||
|
||||
<record model="idea.idea" id="idea_idea_4">
|
||||
<field name="name">Better management of smtp errors</field>
|
||||
<field name="description">There should be away to store the reason why some emails are not sent.</field>
|
||||
<field name="user_id" eval="ref('base.user_root')"/>
|
||||
<field name="state">close</field>
|
||||
<field name="category_ids" eval="[(6, 0, [ref('idea.idea_cat_2')])]"/>
|
||||
</record>
|
||||
<workflow action="idea_set_low_priority" model="idea.idea" ref="idea_idea_4"/>
|
||||
|
||||
<record model="idea.idea" id="idea_idea_5">
|
||||
<field name="name">Kitten mode enabled by default</field>
|
||||
<field name="description">As this is the most loved feature, the kitten mode should be enabled by default. And maybe even impossible to remove.</field>
|
||||
<field name="user_id" eval="ref('base.user_root')"/>
|
||||
<field name="state">cancel</field>
|
||||
<field name="category_ids" eval="[(6, 0, [ref('idea.idea_cat_2')])]"/>
|
||||
</record>
|
||||
<workflow action="idea_set_low_priority" model="idea.idea" ref="idea_idea_4"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import osv
|
||||
from openerp.osv import fields
|
||||
from openerp.tools.translate import _
|
||||
import time
|
||||
|
||||
VoteValues = [('-1', 'Not Voted'), ('0', 'Very Bad'), ('25', 'Bad'), \
|
||||
('50', 'Normal'), ('75', 'Good'), ('100', 'Very Good') ]
|
||||
DefaultVoteValue = '50'
|
||||
|
||||
class idea_category(osv.osv):
|
||||
""" Category of Idea """
|
||||
_name = "idea.category"
|
||||
_description = "Idea Category"
|
||||
_columns = {
|
||||
'name': fields.char('Category Name', size=64, required=True),
|
||||
}
|
||||
_sql_constraints = [
|
||||
('name', 'unique(name)', 'The name of the category must be unique')
|
||||
]
|
||||
_order = 'name asc'
|
||||
|
||||
|
||||
class idea_idea(osv.osv):
|
||||
""" Idea """
|
||||
_name = 'idea.idea'
|
||||
_inherit = ['mail.thread']
|
||||
_columns = {
|
||||
'create_uid': fields.many2one('res.users', 'Creator', required=True, readonly=True),
|
||||
'name': fields.char('Idea Summary', size=64, required=True, readonly=True, oldname='title', states={'draft': [('readonly', False)]}),
|
||||
'description': fields.text('Description', help='Content of the idea', readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'category_ids': fields.many2many('idea.category', string='Tags', readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'state': fields.selection([('draft', 'New'),
|
||||
('open', 'Accepted'),
|
||||
('cancel', 'Refused'),
|
||||
('close', 'Done')],
|
||||
'Status', readonly=True, track_visibility='onchange',
|
||||
)
|
||||
}
|
||||
_sql_constraints = [
|
||||
('name', 'unique(name)', 'The name of the idea must be unique')
|
||||
]
|
||||
_defaults = {
|
||||
'state': lambda *a: 'draft',
|
||||
}
|
||||
_order = 'name asc'
|
||||
|
||||
def idea_cancel(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
|
||||
|
||||
def idea_open(self, cr, uid, ids, context={}):
|
||||
return self.write(cr, uid, ids, {'state': 'open'}, context=context)
|
||||
|
||||
def idea_close(self, cr, uid, ids, context={}):
|
||||
return self.write(cr, uid, ids, {'state': 'close'}, context=context)
|
||||
|
||||
def idea_draft(self, cr, uid, ids, context={}):
|
||||
return self.write(cr, uid, ids, {'state': 'draft'}, context=context)
|
|
@ -1,21 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record model="idea.category" id="idea_category_sales">
|
||||
<field name="name">Sales</field>
|
||||
</record>
|
||||
<record model="idea.category" id="idea_category_general">
|
||||
<field name="name">Organization</field>
|
||||
</record>
|
||||
<record model="idea.category" id="idea_category_technical">
|
||||
<field name="name">Technical</field>
|
||||
</record>
|
||||
|
||||
<record id="base.user_demo" model="res.users">
|
||||
<field name="groups_id" eval="[(4,ref('base.group_tool_user'))]"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<!-- Top menu item -->
|
||||
<menuitem name="Tools" id="base.menu_tools" sequence="120" groups="base.group_tool_user"/>
|
||||
<!-- Idea Categories Search View-->
|
||||
<record model="ir.ui.view" id="view_idea_category_search">
|
||||
<field name="name">idea.category.search</field>
|
||||
<field name="model">idea.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Ideas Categories">
|
||||
<field name="name" string="Category"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Idea Category Form View -->
|
||||
<record model="ir.ui.view" id="view_idea_category_form">
|
||||
<field name="name">idea.category.form</field>
|
||||
<field name="model">idea.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Category of Ideas" version="7.0">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Idea Category Tree View -->
|
||||
<record model="ir.ui.view" id="view_idea_category_tree">
|
||||
<field name="name">idea.category.tree</field>
|
||||
<field name="model">idea.category</field>
|
||||
<field name="field_parent"></field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Category of ideas">
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Idea Category Action -->
|
||||
|
||||
<record model="ir.actions.act_window" id="action_idea_category">
|
||||
<field name="name">Categories</field>
|
||||
<field name="res_model">idea.category</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="search_view_id" ref="view_idea_category_search"/>
|
||||
</record>
|
||||
|
||||
<menuitem name="Configuration" parent="base.menu_tools"
|
||||
id="base.menu_lunch_survey_root" sequence="20" />
|
||||
|
||||
<menuitem name="Ideas" parent="base.menu_lunch_survey_root" id="menu_ideas" sequence="3"/>
|
||||
|
||||
<menuitem name="Categories" parent="menu_ideas" id="menu_idea_category" action="action_idea_category" />
|
||||
|
||||
|
||||
<!-- New Idea Form View -->
|
||||
|
||||
<record model="ir.ui.view" id="view_idea_idea_form">
|
||||
<field name="name">idea.idea.form</field>
|
||||
<field name="model">idea.idea</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Idea" version="7.0">
|
||||
<header>
|
||||
<button name="idea_open" string="Open" states="draft" class="oe_highlight"/>
|
||||
<button name="idea_close" string="Accept" states="open" class="oe_highlight"/>
|
||||
<button name="idea_cancel" string="Refuse" states="open" class="oe_highlight"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,open,close"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name"/></h1>
|
||||
<label for="category_ids" class="oe_edit_only"/>
|
||||
<field name="category_ids" widget="many2many_tags"/>
|
||||
<label for="description"/><newline/>
|
||||
<field name="description"/>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- New Idea Tree View -->
|
||||
|
||||
<record model="ir.ui.view" id="view_idea_idea_tree">
|
||||
<field name="name">idea.idea.tree</field>
|
||||
<field name="model">idea.idea</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree colors="blue:state == 'draft';black:state in ('open','close');gray:state == 'cancel'" string="Ideas">
|
||||
<field name="name"/>
|
||||
<field name="create_uid"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Search Idea -->
|
||||
|
||||
<record model="ir.ui.view" id="view_idea_idea_search">
|
||||
<field name="name">idea.idea.search</field>
|
||||
<field name="model">idea.idea</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Ideas">
|
||||
<field name="name" string="Idea"/>
|
||||
<filter icon="terp-document-new" string="New" domain="[('state','=', 'draft')]" help="New Ideas"/>
|
||||
<filter icon="terp-camera_test" string="In Progress" domain="[('state','=', 'open')]" help="Open Ideas"/>
|
||||
<filter icon="terp-check" string="Accepted" domain="[('state','=','close')]" help="Accepted Ideas" />
|
||||
<field name="category_ids"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter icon="terp-personal" string="Creator" help="By Creators" context="{'group_by':'create_uid'}"/>
|
||||
<filter icon="terp-stock_symbol-selection" string="Category" help="By Idea Category" context="{'group_by':'category_ids'}"/>
|
||||
<filter icon="terp-stock_effects-object-colorize" string="Status" help="By States" context="{'group_by':'state'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_idea_idea">
|
||||
<field name="name">Ideas</field>
|
||||
<field name="res_model">idea.idea</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="search_view_id" ref="view_idea_idea_search"/>
|
||||
</record>
|
||||
|
||||
<menuitem name="Ideas" parent="menu_ideas" id="menu_idea_idea" action="action_idea_idea" sequence="1"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -1,60 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record model="workflow" id="wkf_idea">
|
||||
<field name="name">idea.wkf</field>
|
||||
<field name="osv">idea.idea</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<record model="workflow.activity" id="act_draft">
|
||||
<field name="wkf_id" ref="wkf_idea" />
|
||||
<field name="flow_start">True</field>
|
||||
<field name="name">draft</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">idea_draft()</field>
|
||||
</record>
|
||||
|
||||
<record model="workflow.activity" id="act_open">
|
||||
<field name="wkf_id" ref="wkf_idea" />
|
||||
<field name="name">open</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">idea_open()</field>
|
||||
</record>
|
||||
|
||||
<record model="workflow.activity" id="act_close">
|
||||
<field name="wkf_id" ref="wkf_idea" />
|
||||
<field name="name">close</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">idea_close()</field>
|
||||
<field name="flow_stop">True</field>
|
||||
</record>
|
||||
|
||||
<record model="workflow.activity" id="act_cancel">
|
||||
<field name="wkf_id" ref="wkf_idea" />
|
||||
<field name="name">cancel</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">idea_cancel()</field>
|
||||
<field name="flow_stop">True</field>
|
||||
</record>
|
||||
|
||||
<record model="workflow.transition" id="t1">
|
||||
<field name="act_from" ref="act_draft" />
|
||||
<field name="act_to" ref="act_open" />
|
||||
<field name="signal">idea_open</field>
|
||||
</record>
|
||||
|
||||
<record model="workflow.transition" id="t2">
|
||||
<field name="act_from" ref="act_open" />
|
||||
<field name="act_to" ref="act_close" />
|
||||
<field name="signal">idea_close</field>
|
||||
</record>
|
||||
|
||||
<record model="workflow.transition" id="t4">
|
||||
<field name="act_from" ref="act_open" />
|
||||
<field name="act_to" ref="act_cancel" />
|
||||
<field name="signal">idea_cancel</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2013-TODAY OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import idea
|
|
@ -0,0 +1,130 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-Today OpenERP S.A. (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import osv
|
||||
from openerp.osv import fields
|
||||
|
||||
|
||||
class IdeaCategory(osv.Model):
|
||||
""" Category of Idea """
|
||||
_name = "idea.category"
|
||||
_description = "Idea Category"
|
||||
|
||||
_order = 'name asc'
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Category Name', size=64, required=True),
|
||||
}
|
||||
|
||||
_sql_constraints = [
|
||||
('name', 'unique(name)', 'The name of the category must be unique')
|
||||
]
|
||||
|
||||
|
||||
class IdeaIdea(osv.Model):
|
||||
""" Model of an Idea """
|
||||
_name = 'idea.idea'
|
||||
_description = 'Propose and Share your Ideas'
|
||||
|
||||
_rec_name = 'name'
|
||||
_order = 'name asc'
|
||||
|
||||
def _get_state_list(self, cr, uid, context=None):
|
||||
return [('draft', 'New'),
|
||||
('open', 'In discussion'),
|
||||
('close', 'Accepted'),
|
||||
('cancel', 'Refused')]
|
||||
|
||||
def _get_color(self, cr, uid, ids, fields, args, context=None):
|
||||
res = dict.fromkeys(ids, 3)
|
||||
for idea in self.browse(cr, uid, ids, context=context):
|
||||
if idea.priority == 'low':
|
||||
res[idea.id] = 0
|
||||
elif idea.priority == 'high':
|
||||
res[idea.id] = 7
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'user_id': fields.many2one('res.users', 'Responsible', required=True),
|
||||
'name': fields.char('Summary', required=True, readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
oldname='title'),
|
||||
'description': fields.text('Description', required=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
help='Content of the idea'),
|
||||
'category_ids': fields.many2many('idea.category', string='Tags'),
|
||||
'state': fields.selection(_get_state_list, string='Status', required=True),
|
||||
'priority': fields.selection([('low', 'Low'), ('normal', 'Normal'), ('high', 'High')],
|
||||
string='Priority', required=True),
|
||||
'color': fields.function(_get_color, type='integer', string='Color Index'),
|
||||
}
|
||||
|
||||
_sql_constraints = [
|
||||
('name', 'unique(name)', 'The name of the idea must be unique')
|
||||
]
|
||||
|
||||
_defaults = {
|
||||
'user_id': lambda self, cr, uid, ctx=None: uid,
|
||||
'state': lambda self, cr, uid, ctx=None: self._get_state_list(cr, uid, ctx)[0][0],
|
||||
'priority': 'normal',
|
||||
}
|
||||
|
||||
#------------------------------------------------------
|
||||
# Technical stuff
|
||||
#------------------------------------------------------
|
||||
|
||||
def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
|
||||
""" Override read_group to always display all states. """
|
||||
if groupby and groupby[0] == "state":
|
||||
# Default result structure
|
||||
states = self._get_state_list(cr, uid, context=context)
|
||||
read_group_all_states = [{
|
||||
'__context': {'group_by': groupby[1:]},
|
||||
'__domain': domain + [('state', '=', state_value)],
|
||||
'state': state_value,
|
||||
'state_count': 0,
|
||||
} for state_value, state_name in states]
|
||||
# Get standard results
|
||||
read_group_res = super(IdeaIdea, self).read_group(cr, uid, domain, fields, groupby, offset, limit, context, orderby)
|
||||
# Update standard results with default results
|
||||
result = []
|
||||
for state_value, state_name in states:
|
||||
res = filter(lambda x: x['state'] == state_value, read_group_res)
|
||||
if not res:
|
||||
res = filter(lambda x: x['state'] == state_value, read_group_all_states)
|
||||
res[0]['state'] = [state_value, state_name]
|
||||
result.append(res[0])
|
||||
return result
|
||||
else:
|
||||
return super(IdeaIdea, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
|
||||
|
||||
#------------------------------------------------------
|
||||
# Workflow / Actions
|
||||
#------------------------------------------------------
|
||||
|
||||
def idea_set_low_priority(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'priority': 'low'}, context=context)
|
||||
|
||||
def idea_set_normal_priority(self, cr, uid, ids, context={}):
|
||||
return self.write(cr, uid, ids, {'priority': 'normal'}, context=context)
|
||||
|
||||
def idea_set_high_priority(self, cr, uid, ids, context={}):
|
||||
return self.write(cr, uid, ids, {'priority': 'high'}, context=context)
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record model="res.groups" id="base.group_tool_user">
|
||||
<field name="name">User</field>
|
||||
<field name="category_id" ref="base.module_category_tools"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_idea_category_user,idea.category user,model_idea_category,base.group_tool_user,1,1,1,1
|
||||
access_idea_idea_user,idea.idea user,model_idea_idea,base.group_tool_user,1,1,1,1
|
||||
access_idea_category_user,idea.category.user,model_idea_category,base.group_user,1,1,1,1
|
||||
access_idea_idea_user,idea.idea.user,model_idea_idea,base.group_user,1,1,1,1
|
||||
|
|
|
|
@ -0,0 +1,21 @@
|
|||
.openerp .oe_kanban_view .oe_kanban_idea_idea {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_idea_idea .oe_avatars {
|
||||
text-align: right;
|
||||
margin: -5px 0 -10px 0;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_idea_idea .oe_avatars img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding-left: 0px;
|
||||
margin-top: 3px;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
-moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 139 KiB |
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2013-TODAY OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -19,7 +19,10 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import google_login
|
||||
from openerp.addons.idea.tests import test_idea
|
||||
|
||||
checks = [
|
||||
test_idea,
|
||||
]
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2013-TODAY OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -19,21 +19,21 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tests import common
|
||||
|
||||
class event_event(osv.osv):
|
||||
_description = 'Portal event'
|
||||
_inherit = 'event.event'
|
||||
|
||||
"""
|
||||
``visibility``: defines if the event appears on the portal's event page
|
||||
- 'public' means the event will appear for everyone (anonymous)
|
||||
- 'private' means the event won't appear
|
||||
"""
|
||||
_columns = {
|
||||
'visibility': fields.selection([('public', 'Public'),('private', 'Private')],
|
||||
string='Visibility', help='Event\'s visibility in the portal\'s contact page'),
|
||||
}
|
||||
_defaults = {
|
||||
'visibility': 'private',
|
||||
}
|
||||
class TestIdeaBase(common.TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestIdeaBase, self).setUp()
|
||||
cr, uid = self.cr, self.uid
|
||||
|
||||
# Usefull models
|
||||
self.idea_category = self.registry('idea.category')
|
||||
self.idea_idea = self.registry('idea.idea')
|
||||
|
||||
def tearDown(self):
|
||||
super(TestIdeaBase, self).tearDown()
|
||||
|
||||
def test_OO(self):
|
||||
pass
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- VIEWS DEFINITION
|
||||
-->
|
||||
|
||||
<record model="ir.ui.view" id="view_idea_category_search">
|
||||
<field name="name">idea.category.search</field>
|
||||
<field name="model">idea.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Ideas Categories">
|
||||
<field name="name" string="Category"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_idea_category_form">
|
||||
<field name="name">idea.category.form</field>
|
||||
<field name="model">idea.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Category of Ideas" version="7.0">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_idea_category_tree">
|
||||
<field name="name">idea.category.tree</field>
|
||||
<field name="model">idea.category</field>
|
||||
<field name="field_parent"></field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Category of ideas">
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_idea_category">
|
||||
<field name="name">Categories</field>
|
||||
<field name="res_model">idea.category</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="search_view_id" ref="view_idea_category_search"/>
|
||||
</record>
|
||||
|
||||
<!-- MENUS
|
||||
-->
|
||||
|
||||
<menuitem name="Idea Tags" parent="mail.mail_my_stuff"
|
||||
id="menu_idea_category" action="action_idea_category" sequence="31"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,118 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record model="ir.ui.view" id="view_idea_idea_kanban">
|
||||
<field name="name">idea.idea.kanban</field>
|
||||
<field name="model">idea.idea</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban version="7.0" default_group_by="state" class="oe_background_grey">
|
||||
<field name="color"/>
|
||||
<field name="user_id"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_card oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_idea_idea oe_kanban_global_click">
|
||||
<div class="oe_dropdown_toggle oe_dropdown_kanban"
|
||||
groups="base.group_user">
|
||||
<span class="oe_e">í</span>
|
||||
<ul class="oe_dropdown_menu">
|
||||
<t t-if="widget.view.is_action_enabled('delete')">
|
||||
<li><a type="delete">Delete</a></li>
|
||||
</t>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="oe_kanban_content">
|
||||
<h4><field name="name"/></h4>
|
||||
<div class="oe_kanban_bottom_right">
|
||||
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-title="record.user_id.value" width="24" height="24" class="oe_kanban_avatar" t-if="record.user_id.value"/>
|
||||
</div>
|
||||
<field name="category_ids"/>
|
||||
</div>
|
||||
<div class="oe_clear"></div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_idea_idea_form">
|
||||
<field name="name">idea.idea.form</field>
|
||||
<field name="model">idea.idea</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Idea" version="7.0">
|
||||
<header>
|
||||
<button name="idea_set_low_priority" string="Set Low Priority" class="oe_highlight"
|
||||
attrs="{'invisible': [('priority', '!=', 'normal')]}"/>
|
||||
<button name="idea_set_normal_priority" string="Set Normal Priority" class="oe_highlight"
|
||||
attrs="{'invisible': [('priority', 'not in', ['low', 'high'])]}"/>
|
||||
<button name="idea_set_high_priority" string="Set High Priority" class="oe_highlight"
|
||||
attrs="{'invisible': [('priority', '!=', 'normal')]}"/>
|
||||
<field name="state" widget="statusbar" clickable="True"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name"/></h1>
|
||||
<group>
|
||||
<field name="user_id"/>
|
||||
<field name="priority" readonly="True"/>
|
||||
<field name="category_ids" widget="many2many_tags"/>
|
||||
<field name="description"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_idea_idea_tree">
|
||||
<field name="name">idea.idea.tree</field>
|
||||
<field name="model">idea.idea</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree colors="blue:state == 'draft';black:state in ('open', 'close'); gray:state == 'cancel'" string="Ideas">
|
||||
<field name="name"/>
|
||||
<field name="user_id"/>
|
||||
<field name="priority"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_idea_idea_search">
|
||||
<field name="name">idea.idea.search</field>
|
||||
<field name="model">idea.idea</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Ideas">
|
||||
<field name="name"/>
|
||||
<field name="user_id"/>
|
||||
<field name="category_ids"/>
|
||||
<filter string="New" domain="[('state', '=', 'draft')]"
|
||||
help="New Ideas"/>
|
||||
<filter string="In Progress" domain="[('state','=', 'open')]"
|
||||
help="Open Ideas"/>
|
||||
<filter string="Accepted" domain="[('state','=', 'close')]"
|
||||
help="Accepted Ideas" />
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Creator" help="By Responsible" context="{'group_by': 'user_id'}"/>
|
||||
<filter string="Category" help="By Category" context="{'group_by': 'category_ids'}"/>
|
||||
<filter string="Status" help="By State" context="{'group_by': 'state'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_idea_idea">
|
||||
<field name="name">Ideas</field>
|
||||
<field name="res_model">idea.idea</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="search_view_id" ref="view_idea_idea_search"/>
|
||||
</record>
|
||||
|
||||
<!-- MENUS
|
||||
-->
|
||||
|
||||
<menuitem name="Ideas" parent="mail.mail_my_stuff"
|
||||
id="menu_idea_idea" action="action_idea_idea" sequence="30"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
File diff suppressed because it is too large
Load Diff
|
@ -39,6 +39,7 @@ def remove_accents(input_str):
|
|||
nkfd_form = unicodedata.normalize('NFKD', input_str)
|
||||
return u''.join([c for c in nkfd_form if not unicodedata.combining(c)])
|
||||
|
||||
|
||||
class mail_alias(osv.Model):
|
||||
"""A Mail Alias is a mapping of an email address with a given OpenERP Document
|
||||
model. It is used by OpenERP's mail gateway when processing incoming emails
|
||||
|
@ -63,9 +64,8 @@ class mail_alias(osv.Model):
|
|||
return dict.fromkeys(ids, domain or "")
|
||||
|
||||
_columns = {
|
||||
'alias_name': fields.char('Alias', required=True,
|
||||
help="The name of the email alias, e.g. 'jobs' "
|
||||
"if you want to catch emails for <jobs@example.my.openerp.com>",),
|
||||
'alias_name': fields.char('Alias',
|
||||
help="The name of the email alias, e.g. 'jobs' if you want to catch emails for <jobs@example.my.openerp.com>",),
|
||||
'alias_model_id': fields.many2one('ir.model', 'Aliased Model', required=True, ondelete="cascade",
|
||||
help="The model (OpenERP Document Kind) to which this alias "
|
||||
"corresponds. Any incoming email that does not reply to an "
|
||||
|
@ -87,13 +87,29 @@ class mail_alias(osv.Model):
|
|||
"messages will be attached, even if they did not reply to it. "
|
||||
"If set, this will disable the creation of new records completely."),
|
||||
'alias_domain': fields.function(_get_alias_domain, string="Alias domain", type='char', size=None),
|
||||
'alias_parent_model_id': fields.many2one('ir.model', 'Parent Model',
|
||||
help="Parent model holding the alias. The model holding the alias reference\n"
|
||||
"is not necessarily the model given by alias_model_id\n"
|
||||
"(example: project (parent_model) and task (model))"),
|
||||
'alias_parent_thread_id': fields.integer('Parent Record Thread ID',
|
||||
help="ID of the parent record holding the alias (example: project holding the task creation alias)"),
|
||||
'alias_contact': fields.selection([
|
||||
('everyone', 'Everyone'),
|
||||
('partners', 'Authenticated Partners'),
|
||||
('followers', 'Followers only'),
|
||||
], string='Alias Contact Security', required=True,
|
||||
help="Policy to post a message on the document using the mailgateway.\n"
|
||||
"- everyone: everyone can post\n"
|
||||
"- partners: only authenticated partners\n"
|
||||
"- followers: only followers of the related document\n"),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'alias_defaults': '{}',
|
||||
'alias_user_id': lambda self,cr,uid,context: uid,
|
||||
'alias_user_id': lambda self, cr, uid, context: uid,
|
||||
# looks better when creating new aliases - even if the field is informative only
|
||||
'alias_domain': lambda self,cr,uid,context: self._get_alias_domain(cr, SUPERUSER_ID,[1],None,None)[1]
|
||||
'alias_domain': lambda self, cr, uid, context: self._get_alias_domain(cr, SUPERUSER_ID, [1], None, None)[1],
|
||||
'alias_contact': 'everyone',
|
||||
}
|
||||
|
||||
_sql_constraints = [
|
||||
|
@ -139,13 +155,15 @@ class mail_alias(osv.Model):
|
|||
return new_name
|
||||
|
||||
def migrate_to_alias(self, cr, child_model_name, child_table_name, child_model_auto_init_fct,
|
||||
alias_id_column, alias_key, alias_prefix='', alias_force_key='', alias_defaults={}, context=None):
|
||||
alias_model_name, alias_id_column, alias_key, alias_prefix='', alias_force_key='', alias_defaults={},
|
||||
alias_generate_name=False, context=None):
|
||||
""" Installation hook to create aliases for all users and avoid constraint errors.
|
||||
|
||||
:param child_model_name: model name of the child class (i.e. res.users)
|
||||
:param child_table_name: table name of the child class (i.e. res_users)
|
||||
:param child_model_auto_init_fct: pointer to the _auto_init function
|
||||
(i.e. super(res_users,self)._auto_init(cr, context=context))
|
||||
:param alias_model_name: name of the aliased model
|
||||
:param alias_id_column: alias_id column (i.e. self._columns['alias_id'])
|
||||
:param alias_key: name of the column used for the unique name (i.e. 'login')
|
||||
:param alias_prefix: prefix for the unique name (i.e. 'jobs' + ...)
|
||||
|
@ -153,6 +171,8 @@ class mail_alias(osv.Model):
|
|||
if empty string, not taken into account
|
||||
:param alias_defaults: dict, keys = mail.alias columns, values = child
|
||||
model column name used for default values (i.e. {'job_id': 'id'})
|
||||
:param alias_generate_name: automatically generate alias name using prefix / alias key;
|
||||
default alias_name value is False because since 8.0 it is not required anymore
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
|
@ -170,13 +190,17 @@ class mail_alias(osv.Model):
|
|||
no_alias_ids = child_class_model.search(cr, SUPERUSER_ID, [('alias_id', '=', False)], context={'active_test': False})
|
||||
# Use read() not browse(), to avoid prefetching uninitialized inherited fields
|
||||
for obj_data in child_class_model.read(cr, SUPERUSER_ID, no_alias_ids, [alias_key]):
|
||||
alias_vals = {'alias_name': '%s%s' % (alias_prefix, obj_data[alias_key])}
|
||||
alias_vals = {'alias_name': False}
|
||||
if alias_generate_name:
|
||||
alias_vals['alias_name'] = '%s%s' % (alias_prefix, obj_data[alias_key])
|
||||
if alias_force_key:
|
||||
alias_vals['alias_force_thread_id'] = obj_data[alias_force_key]
|
||||
alias_vals['alias_defaults'] = dict((k, obj_data[v]) for k, v in alias_defaults.iteritems())
|
||||
alias_id = mail_alias.create_unique_alias(cr, SUPERUSER_ID, alias_vals, model_name=context.get('alias_model_name', child_model_name))
|
||||
alias_vals['alias_parent_thread_id'] = obj_data['id']
|
||||
alias_create_ctx = dict(context, alias_model_name=alias_model_name, alias_parent_model_name=child_model_name)
|
||||
alias_id = mail_alias.create(cr, SUPERUSER_ID, alias_vals, context=alias_create_ctx)
|
||||
child_class_model.write(cr, SUPERUSER_ID, obj_data['id'], {'alias_id': alias_id})
|
||||
_logger.info('Mail alias created for %s %s (uid %s)', child_model_name, obj_data[alias_key], obj_data['id'])
|
||||
_logger.info('Mail alias created for %s %s (id %s)', child_model_name, obj_data[alias_key], obj_data['id'])
|
||||
|
||||
# Finally attempt to reinstate the missing constraint
|
||||
try:
|
||||
|
@ -189,16 +213,20 @@ class mail_alias(osv.Model):
|
|||
|
||||
# set back the unique alias_id constraint
|
||||
alias_id_column.required = True
|
||||
|
||||
return res
|
||||
|
||||
def create_unique_alias(self, cr, uid, vals, model_name=None, context=None):
|
||||
"""Creates an email.alias record according to the values provided in ``vals``,
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
""" Creates an email.alias record according to the values provided in ``vals``,
|
||||
with 2 alterations: the ``alias_name`` value may be suffixed in order to
|
||||
make it unique (and certain unsafe characters replaced), and
|
||||
he ``alias_model_id`` value will set to the model ID of the ``model_name``
|
||||
value, if provided,
|
||||
context value, if provided.
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
model_name = context.get('alias_model_name')
|
||||
parent_model_name = context.get('alias_parent_model_name')
|
||||
if vals.get('alias_name'):
|
||||
# when an alias name appears to already be an email, we keep the local part only
|
||||
alias_name = remove_accents(vals['alias_name']).lower().split('@')[0]
|
||||
alias_name = re.sub(r'[^\w+.]+', '-', alias_name)
|
||||
|
@ -207,4 +235,31 @@ class mail_alias(osv.Model):
|
|||
if model_name:
|
||||
model_id = self.pool.get('ir.model').search(cr, uid, [('model', '=', model_name)], context=context)[0]
|
||||
vals['alias_model_id'] = model_id
|
||||
return self.create(cr, uid, vals, context=context)
|
||||
if parent_model_name:
|
||||
model_id = self.pool.get('ir.model').search(cr, uid, [('model', '=', parent_model_name)], context=context)[0]
|
||||
vals['alias_parent_model_id'] = model_id
|
||||
return super(mail_alias, self).create(cr, uid, vals, context=context)
|
||||
|
||||
def open_document(self, cr, uid, ids, context=None):
|
||||
alias = self.browse(cr, uid, ids, context=context)[0]
|
||||
if not alias.alias_model_id or not alias.alias_force_thread_id:
|
||||
return False
|
||||
return {
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': alias.alias_model_id.model,
|
||||
'res_id': alias.alias_force_thread_id,
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
|
||||
def open_parent_document(self, cr, uid, ids, context=None):
|
||||
alias = self.browse(cr, uid, ids, context=context)[0]
|
||||
if not alias.alias_parent_model_id or not alias.alias_parent_thread_id:
|
||||
return False
|
||||
return {
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': alias.alias_parent_model_id.model,
|
||||
'res_id': alias.alias_parent_thread_id,
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
|
|
|
@ -9,13 +9,23 @@
|
|||
<field name="arch" type="xml">
|
||||
<form string="Alias" version="7.0">
|
||||
<sheet>
|
||||
<label for="alias_name" class="oe_edit_only"/>
|
||||
<h2><field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline"/></h2>
|
||||
<div class="oe_right oe_button_box">
|
||||
<button name="open_document" string="Open Document"
|
||||
type="object" class="oe_link"
|
||||
attrs="{'invisible': ['|', ('alias_model_id', '=', False), ('alias_force_thread_id', '=', 0)]}"/>
|
||||
<button name="open_parent_document" string="Open Parent Document"
|
||||
type="object" class="oe_link"
|
||||
attrs="{'invisible': ['|', ('alias_parent_model_id', '=', False), ('alias_parent_thread_id', '=', 0)]}"/>
|
||||
</div>
|
||||
<group>
|
||||
<field name="alias_model_id"/>
|
||||
<field name="alias_user_id"/>
|
||||
<field name="alias_force_thread_id"/>
|
||||
<field name="alias_defaults"/>
|
||||
<field name="alias_contact"/>
|
||||
<field name="alias_user_id"/>
|
||||
<field name="alias_parent_model_id"/>
|
||||
<field name="alias_parent_thread_id"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
|
@ -32,6 +42,7 @@
|
|||
<field name="alias_model_id"/>
|
||||
<field name="alias_user_id"/>
|
||||
<field name="alias_defaults"/>
|
||||
<field name="alias_contact"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -44,8 +55,13 @@
|
|||
<search string="Search Alias">
|
||||
<field name="alias_name"/>
|
||||
<field name="alias_model_id"/>
|
||||
<field name="alias_force_thread_id"/>
|
||||
<field name="alias_parent_model_id"/>
|
||||
<field name="alias_parent_thread_id"/>
|
||||
<separator/>
|
||||
<filter string="Active" name="active" domain="[('alias_name', '!=', False)]"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="User" name="User" icon="terp-personal" context="{'group_by':'alias_user_id'}"/>
|
||||
<filter string="User" name="User" context="{'group_by':'alias_user_id'}"/>
|
||||
<filter string="Model" name="Model" context="{'group_by':'alias_model_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
|
@ -55,6 +71,10 @@
|
|||
<record id="action_view_mail_alias" model="ir.actions.act_window">
|
||||
<field name="name">Aliases</field>
|
||||
<field name="res_model">mail.alias</field>
|
||||
<field name="context">{
|
||||
'search_default_active': True,
|
||||
}
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="mail_alias_menu"
|
||||
|
|
|
@ -93,17 +93,16 @@ class mail_group(osv.Model):
|
|||
'public': 'groups',
|
||||
'group_public_id': _get_default_employee_group,
|
||||
'image': _get_default_image,
|
||||
'alias_domain': False, # always hide alias during creation
|
||||
}
|
||||
|
||||
def _generate_header_description(self, cr, uid, group, context=None):
|
||||
header = ''
|
||||
if group.description:
|
||||
header = '%s' % group.description
|
||||
if group.alias_id and group.alias_id.alias_name and group.alias_id.alias_domain:
|
||||
if group.alias_id and group.alias_name and group.alias_domain:
|
||||
if header:
|
||||
header = '%s<br/>' % header
|
||||
return '%sGroup email gateway: %s@%s' % (header, group.alias_id.alias_name, group.alias_id.alias_domain)
|
||||
return '%sGroup email gateway: %s@%s' % (header, group.alias_name, group.alias_domain)
|
||||
return header
|
||||
|
||||
def _subscribe_users(self, cr, uid, ids, context=None):
|
||||
|
@ -114,15 +113,8 @@ class mail_group(osv.Model):
|
|||
self.message_subscribe(cr, uid, ids, partner_ids, context=context)
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
mail_alias = self.pool.get('mail.alias')
|
||||
if not vals.get('alias_id'):
|
||||
vals.pop('alias_name', None) # prevent errors during copy()
|
||||
alias_id = mail_alias.create_unique_alias(cr, uid,
|
||||
# Using '+' allows using subaddressing for those who don't
|
||||
# have a catchall domain setup.
|
||||
{'alias_name': "group+" + vals['name']},
|
||||
model_name=self._name, context=context)
|
||||
vals['alias_id'] = alias_id
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
# get parent menu
|
||||
menu_parent = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'mail_group_root')
|
||||
|
@ -134,8 +126,10 @@ class mail_group(osv.Model):
|
|||
vals['menu_id'] = menu_id
|
||||
|
||||
# Create group and alias
|
||||
mail_group_id = super(mail_group, self).create(cr, uid, vals, context=context)
|
||||
mail_alias.write(cr, uid, [vals['alias_id']], {"alias_force_thread_id": mail_group_id}, context)
|
||||
create_context = dict(context, alias_model_name=self._name, alias_parent_model_name=self._name)
|
||||
mail_group_id = super(mail_group, self).create(cr, uid, vals, context=create_context)
|
||||
group = self.browse(cr, uid, mail_group_id, context=context)
|
||||
self.pool.get('mail.alias').write(cr, uid, [group.alias_id.id], {"alias_force_thread_id": mail_group_id, 'alias_parent_thread_id': mail_group_id}, context)
|
||||
group = self.browse(cr, uid, mail_group_id, context=context)
|
||||
|
||||
# Create client action for this group and link the menu to it
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
<div class="oe_group_details">
|
||||
<h4><a type="open"><field name="name"/></a></h4>
|
||||
<div class="oe_kanban_alias" t-if="record.alias_id.value">
|
||||
<span class="oe_e">%%</span><small><field name="alias_id"/></small>
|
||||
<span class="oe_e oe_e_alias">%%</span><small><field name="alias_id"/></small>
|
||||
</div>
|
||||
<div class="oe_grey">
|
||||
<field name="description"/>
|
||||
|
@ -78,18 +78,20 @@
|
|||
<label for="name" string="Group Name"/>
|
||||
</div>
|
||||
<h1><field name="name" readonly="0"/></h1>
|
||||
<div name="group_alias"
|
||||
<group colspan="2" name="group_alias"
|
||||
attrs="{'invisible': [('alias_domain', '=', False)]}">
|
||||
<label for="alias_id" string="Email Alias"/>
|
||||
<field name="alias_id" class="oe_inline oe_read_only" required="0" nolabel="1"/>
|
||||
<span name="edit_alias" class="oe_edit_only">
|
||||
<field name="alias_name" class="oe_inline"
|
||||
attrs="{'required': [('alias_id', '!=', False)]}"/>
|
||||
@
|
||||
<field name="alias_domain" class="oe_inline" readonly="1"/>
|
||||
</span>
|
||||
<label for="alias_id" string="%%" class="oe_e oe_e_alias" style="min-width: 20px;"/>
|
||||
<div name="alias_def">
|
||||
<field name="alias_id" class="oe_read_only oe_inline"
|
||||
string="Email Alias" required="0"/>
|
||||
<div class="oe_edit_only oe_inline" name="edit_alias" style="display: inline;" >
|
||||
<field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline" readonly="1"/>
|
||||
</div>
|
||||
</div>
|
||||
<label for="alias_contact" string="V" class="oe_e oe_e_alias" style="min-width: 20px;"/>
|
||||
<field name="alias_contact" class="oe_inline" nolabel="1"/>
|
||||
</group>
|
||||
</div>
|
||||
<field name="description" placeholder="Topics discussed in this group..."/>
|
||||
<div class="oe_clear"/>
|
||||
<group class="oe_edit_only">
|
||||
|
|
|
@ -25,7 +25,6 @@ import dateutil
|
|||
import email
|
||||
import logging
|
||||
import pytz
|
||||
import re
|
||||
import time
|
||||
import xmlrpclib
|
||||
from email.message import Message
|
||||
|
@ -102,21 +101,22 @@ class mail_thread(osv.AbstractModel):
|
|||
if catchall_domain and model and res_id: # specific res_id -> find its alias (i.e. section_id specified)
|
||||
object_id = self.pool.get(model).browse(cr, uid, res_id, context=context)
|
||||
# check that the alias effectively creates new records
|
||||
if object_id.alias_id and object_id.alias_id.alias_model_id and \
|
||||
if object_id.alias_id and object_id.alias_id.alias_name and \
|
||||
object_id.alias_id.alias_model_id and \
|
||||
object_id.alias_id.alias_model_id.model == self._name and \
|
||||
object_id.alias_id.alias_force_thread_id == 0:
|
||||
alias = object_id.alias_id
|
||||
elif catchall_domain and model: # no specific res_id given -> generic help message, take an example alias (i.e. alias of some section_id)
|
||||
model_id = self.pool.get('ir.model').search(cr, uid, [("model", "=", self._name)], context=context)[0]
|
||||
alias_obj = self.pool.get('mail.alias')
|
||||
alias_ids = alias_obj.search(cr, uid, [("alias_model_id", "=", model_id), ('alias_force_thread_id', '=', 0)], context=context, order='id ASC')
|
||||
alias_ids = alias_obj.search(cr, uid, [("alias_model_id", "=", model_id), ("alias_name", "!=", False), ('alias_force_thread_id', '=', 0)], context=context, order='id ASC')
|
||||
if alias_ids and len(alias_ids) == 1: # if several aliases -> incoherent to propose one guessed from nowhere, therefore avoid if several aliases
|
||||
alias = alias_obj.browse(cr, uid, alias_ids[0], context=context)
|
||||
|
||||
if alias:
|
||||
alias_email = alias.name_get()[0][1]
|
||||
return _("""<p class='oe_view_nocontent_create'>
|
||||
Click here to add a new %(document)s or send an email to: <a href='mailto:%(email)s'>%(email)s</a>
|
||||
Click here to add new %(document)s or send an email to: <a href='mailto:%(email)s'>%(email)s</a>
|
||||
</p>
|
||||
%(static_help)s"""
|
||||
) % {
|
||||
|
@ -126,7 +126,7 @@ class mail_thread(osv.AbstractModel):
|
|||
}
|
||||
|
||||
if document_name != 'document' and help and help.find("oe_view_nocontent_create") == -1:
|
||||
return _("<p class='oe_view_nocontent_create'>Click here to add a new %(document)s</p>%(static_help)s") % {
|
||||
return _("<p class='oe_view_nocontent_create'>Click here to add new %(document)s</p>%(static_help)s") % {
|
||||
'document': document_name,
|
||||
'static_help': help or '',
|
||||
}
|
||||
|
@ -257,7 +257,6 @@ class mail_thread(osv.AbstractModel):
|
|||
|
||||
def _search_is_follower(self, cr, uid, obj, name, args, context):
|
||||
"""Search function for message_is_follower"""
|
||||
fol_obj = self.pool.get('mail.followers')
|
||||
res = []
|
||||
for field, operator, value in args:
|
||||
assert field == name
|
||||
|
@ -329,8 +328,8 @@ class mail_thread(osv.AbstractModel):
|
|||
# Track initial values of tracked fields
|
||||
tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=context)
|
||||
if tracked_fields:
|
||||
initial = self.read(cr, uid, ids, tracked_fields.keys(), context=context)
|
||||
initial_values = dict((item['id'], item) for item in initial)
|
||||
records = self.browse(cr, uid, ids, context=context)
|
||||
initial_values = dict((this.id, dict((key, getattr(this, key)) for key in tracked_fields.keys())) for this in records)
|
||||
|
||||
# Perform write, update followers
|
||||
result = super(mail_thread, self).write(cr, uid, ids, values, context=context)
|
||||
|
@ -389,7 +388,7 @@ class mail_thread(osv.AbstractModel):
|
|||
if not value:
|
||||
return ''
|
||||
if col_info['type'] == 'many2one':
|
||||
return value[1]
|
||||
return value.name_get()[0][1]
|
||||
if col_info['type'] == 'selection':
|
||||
return dict(col_info['selection'])[value]
|
||||
return value
|
||||
|
@ -408,23 +407,26 @@ class mail_thread(osv.AbstractModel):
|
|||
if not tracked_fields:
|
||||
return True
|
||||
|
||||
for record in self.read(cr, uid, ids, tracked_fields.keys(), context=context):
|
||||
initial = initial_values[record['id']]
|
||||
changes = []
|
||||
for browse_record in self.browse(cr, uid, ids, context=context):
|
||||
initial = initial_values[browse_record.id]
|
||||
changes = set()
|
||||
tracked_values = {}
|
||||
|
||||
# generate tracked_values data structure: {'col_name': {col_info, new_value, old_value}}
|
||||
for col_name, col_info in tracked_fields.items():
|
||||
if record[col_name] == initial[col_name] and getattr(self._all_columns[col_name].column, 'track_visibility', None) == 'always':
|
||||
initial_value = initial[col_name]
|
||||
record_value = getattr(browse_record, col_name)
|
||||
|
||||
if record_value == initial_value and getattr(self._all_columns[col_name].column, 'track_visibility', None) == 'always':
|
||||
tracked_values[col_name] = dict(col_info=col_info['string'],
|
||||
new_value=convert_for_display(record[col_name], col_info))
|
||||
elif record[col_name] != initial[col_name]:
|
||||
new_value=convert_for_display(record_value, col_info))
|
||||
elif record_value != initial_value and (record_value or initial_value): # because browse null != False
|
||||
if getattr(self._all_columns[col_name].column, 'track_visibility', None) in ['always', 'onchange']:
|
||||
tracked_values[col_name] = dict(col_info=col_info['string'],
|
||||
old_value=convert_for_display(initial[col_name], col_info),
|
||||
new_value=convert_for_display(record[col_name], col_info))
|
||||
old_value=convert_for_display(initial_value, col_info),
|
||||
new_value=convert_for_display(record_value, col_info))
|
||||
if col_name in tracked_fields:
|
||||
changes.append(col_name)
|
||||
changes.add(col_name)
|
||||
if not changes:
|
||||
continue
|
||||
|
||||
|
@ -434,7 +436,7 @@ class mail_thread(osv.AbstractModel):
|
|||
if field not in changes:
|
||||
continue
|
||||
for subtype, method in track_info.items():
|
||||
if method(self, cr, uid, record, context):
|
||||
if method(self, cr, uid, browse_record, context):
|
||||
subtypes.append(subtype)
|
||||
|
||||
posted = False
|
||||
|
@ -445,11 +447,11 @@ class mail_thread(osv.AbstractModel):
|
|||
_logger.debug('subtype %s not found, giving error "%s"' % (subtype, e))
|
||||
continue
|
||||
message = format_message(subtype_rec.description if subtype_rec.description else subtype_rec.name, tracked_values)
|
||||
self.message_post(cr, uid, record['id'], body=message, subtype=subtype, context=context)
|
||||
self.message_post(cr, uid, browse_record.id, body=message, subtype=subtype, context=context)
|
||||
posted = True
|
||||
if not posted:
|
||||
message = format_message('', tracked_values)
|
||||
self.message_post(cr, uid, record['id'], body=message, context=context)
|
||||
self.message_post(cr, uid, browse_record.id, body=message, context=context)
|
||||
return True
|
||||
|
||||
#------------------------------------------------------
|
||||
|
@ -564,6 +566,8 @@ class mail_thread(osv.AbstractModel):
|
|||
#------------------------------------------------------
|
||||
|
||||
def message_get_reply_to(self, cr, uid, ids, context=None):
|
||||
""" Returns the preferred reply-to email address that is basically
|
||||
the alias of the document, if it exists. """
|
||||
if not self._inherits.get('mail.alias'):
|
||||
return [False for id in ids]
|
||||
return ["%s@%s" % (record['alias_name'], record['alias_domain'])
|
||||
|
@ -587,27 +591,123 @@ class mail_thread(osv.AbstractModel):
|
|||
def _message_find_partners(self, cr, uid, message, header_fields=['From'], context=None):
|
||||
""" Find partners related to some header fields of the message.
|
||||
|
||||
TDE TODO: merge me with other partner finding methods in 8.0 """
|
||||
partner_obj = self.pool.get('res.partner')
|
||||
partner_ids = []
|
||||
:param string message: an email.message instance """
|
||||
s = ', '.join([decode(message.get(h)) for h in header_fields if message.get(h)])
|
||||
for email_address in tools.email_split(s):
|
||||
related_partners = partner_obj.search(cr, uid, [('email', 'ilike', email_address), ('user_ids', '!=', False)], limit=1, context=context)
|
||||
if not related_partners:
|
||||
related_partners = partner_obj.search(cr, uid, [('email', 'ilike', email_address)], limit=1, context=context)
|
||||
partner_ids += related_partners
|
||||
return partner_ids
|
||||
return filter(lambda x: x, self._find_partner_from_emails(cr, uid, None, tools.email_split(s), context=context))
|
||||
|
||||
def _message_find_user_id(self, cr, uid, message, context=None):
|
||||
""" TDE TODO: check and maybe merge me with other user finding methods in 8.0 """
|
||||
from_local_part = tools.email_split(decode(message.get('From')))[0]
|
||||
# FP Note: canonification required, the minimu: .lower()
|
||||
user_ids = self.pool.get('res.users').search(cr, uid, ['|',
|
||||
('login', '=', from_local_part),
|
||||
('email', '=', from_local_part)], context=context)
|
||||
return user_ids[0] if user_ids else uid
|
||||
def message_route_verify(self, cr, uid, message, message_dict, route, update_author=True, assert_model=True, create_fallback=True, context=None):
|
||||
""" Verify route validity. Check and rules:
|
||||
1 - if thread_id -> check that document effectively exists; otherwise
|
||||
fallback on a message_new by resetting thread_id
|
||||
2 - check that message_update exists if thread_id is set; or at least
|
||||
that message_new exist
|
||||
[ - find author_id if udpate_author is set]
|
||||
3 - if there is an alias, check alias_contact:
|
||||
'followers' and thread_id:
|
||||
check on target document that the author is in the followers
|
||||
'followers' and alias_parent_thread_id:
|
||||
check on alias parent document that the author is in the
|
||||
followers
|
||||
'partners': check that author_id id set
|
||||
"""
|
||||
|
||||
def message_route(self, cr, uid, message, model=None, thread_id=None,
|
||||
assert isinstance(route, (list, tuple)), 'A route should be a list or a tuple'
|
||||
assert len(route) == 5, 'A route should contain 5 elements: model, thread_id, custom_values, uid, alias record'
|
||||
|
||||
message_id = message.get('Message-Id')
|
||||
email_from = decode_header(message, 'From')
|
||||
author_id = message_dict.get('author_id')
|
||||
model, thread_id, alias = route[0], route[1], route[4]
|
||||
model_pool = None
|
||||
|
||||
def _create_bounce_email():
|
||||
mail_mail = self.pool.get('mail.mail')
|
||||
mail_id = mail_mail.create(cr, uid, {
|
||||
'body_html': '<div><p>Hello,</p>'
|
||||
'<p>The following email sent to %s cannot be accepted because this is '
|
||||
'a private email address. Only allowed people can contact us at this address.</p></div>'
|
||||
'<blockquote>%s</blockquote>' % (message.get('to'), message_dict.get('body')),
|
||||
'subject': 'Re: %s' % message.get('subject'),
|
||||
'email_to': message.get('from'),
|
||||
'auto_delete': True,
|
||||
}, context=context)
|
||||
mail_mail.send(cr, uid, [mail_id], context=context)
|
||||
|
||||
def _warn(message):
|
||||
_logger.warning('Routing mail with Message-Id %s: route %s: %s',
|
||||
message_id, route, message)
|
||||
|
||||
# Wrong model
|
||||
if model and not model in self.pool:
|
||||
if assert_model:
|
||||
assert model in self.pool, 'Routing: unknown target model %s' % model
|
||||
_warn('unknown target model %s' % model)
|
||||
return ()
|
||||
elif model:
|
||||
model_pool = self.pool[model]
|
||||
|
||||
# Private message: should not contain any thread_id
|
||||
if not model and thread_id:
|
||||
if assert_model:
|
||||
assert thread_id == 0, 'Routing: posting a message without model should be with a null res_id (private message).'
|
||||
_warn('posting a message without model should be with a null res_id (private message), resetting thread_id')
|
||||
thread_id = 0
|
||||
|
||||
# Existing Document: check if exists; if not, fallback on create if allowed
|
||||
if thread_id and not model_pool.exists(cr, uid, thread_id):
|
||||
if create_fallback:
|
||||
_warn('reply to missing document (%s,%s), fall back on new document creation' % (model, thread_id))
|
||||
thread_id = None
|
||||
elif assert_model:
|
||||
assert model_pool.exists(cr, uid, thread_id), 'Routing: reply to missing document (%s,%s)' % (model, thread_id)
|
||||
else:
|
||||
_warn('reply to missing document (%s,%s), skipping' % (model, thread_id))
|
||||
return ()
|
||||
|
||||
# Existing Document: check model accepts the mailgateway
|
||||
if thread_id and not hasattr(model_pool, 'message_update'):
|
||||
if create_fallback:
|
||||
_warn('model %s does not accept document update, fall back on document creation' % model)
|
||||
thread_id = None
|
||||
elif assert_model:
|
||||
assert hasattr(model_pool, 'message_update'), 'Routing: model %s does not accept document update, crashing' % model
|
||||
else:
|
||||
_warn('model %s does not accept document update, skipping' % model)
|
||||
return ()
|
||||
|
||||
# New Document: check model accepts the mailgateway
|
||||
if not thread_id and not hasattr(model_pool, 'message_new'):
|
||||
if assert_model:
|
||||
assert hasattr(model_pool, 'message_new'), 'Model %s does not accept document creation, crashing' % model
|
||||
_warn('model %s does not accept document creation, skipping' % model)
|
||||
return ()
|
||||
|
||||
# Update message author if asked
|
||||
# We do it now because we need it for aliases (contact settings)
|
||||
if not author_id and update_author:
|
||||
author_ids = self._find_partner_from_emails(cr, uid, thread_id, [email_from], model=model, context=context)
|
||||
if author_ids:
|
||||
author_id = author_ids[0]
|
||||
message_dict['author_id'] = author_id
|
||||
|
||||
# Alias: check alias_contact settings
|
||||
if alias and alias.alias_contact == 'followers' and (thread_id or alias.alias_parent_thread_id):
|
||||
if thread_id:
|
||||
obj = self.pool[model].browse(cr, uid, thread_id, context=context)
|
||||
else:
|
||||
obj = self.pool[alias.alias_parent_model_id.model].browse(cr, uid, alias.alias_parent_thread_id, context=context)
|
||||
if not author_id or not author_id in [fol.id for fol in obj.message_follower_ids]:
|
||||
_warn('alias %s restricted to internal followers, skipping' % alias.alias_name)
|
||||
_create_bounce_email()
|
||||
return ()
|
||||
elif alias and alias.alias_contact == 'partners' and not author_id:
|
||||
_warn('alias %s does not accept unknown author, skipping' % alias.alias_name)
|
||||
_create_bounce_email()
|
||||
return ()
|
||||
|
||||
return (model, thread_id, route[2], route[3], route[4])
|
||||
|
||||
def message_route(self, cr, uid, message, message_dict, model=None, thread_id=None,
|
||||
custom_values=None, context=None):
|
||||
"""Attempt to figure out the correct target model, thread_id,
|
||||
custom_values and user_id to use for an incoming message.
|
||||
|
@ -627,6 +727,7 @@ class mail_thread(osv.AbstractModel):
|
|||
4. If all the above fails, raise an exception.
|
||||
|
||||
:param string message: an email.message instance
|
||||
:param dict message_dict: dictionary holding message variables
|
||||
:param string model: the fallback model to use if the message
|
||||
does not match any of the currently configured mail aliases
|
||||
(may be None if a matching alias is supposed to be present)
|
||||
|
@ -637,9 +738,12 @@ class mail_thread(osv.AbstractModel):
|
|||
:param int thread_id: optional ID of the record/thread from ``model``
|
||||
to which this mail should be attached. Only used if the message
|
||||
does not reply to an existing thread and does not match any mail alias.
|
||||
:return: list of [model, thread_id, custom_values, user_id]
|
||||
:return: list of [model, thread_id, custom_values, user_id, alias]
|
||||
"""
|
||||
assert isinstance(message, Message), 'message must be an email.message.Message at this point'
|
||||
fallback_model = model
|
||||
|
||||
# Get email.message.Message variables for future processing
|
||||
message_id = message.get('Message-Id')
|
||||
email_from = decode_header(message, 'From')
|
||||
email_to = decode_header(message, 'To')
|
||||
|
@ -649,18 +753,20 @@ class mail_thread(osv.AbstractModel):
|
|||
# 1. Verify if this is a reply to an existing thread
|
||||
thread_references = references or in_reply_to
|
||||
ref_match = thread_references and tools.reference_re.search(thread_references)
|
||||
|
||||
if ref_match:
|
||||
thread_id = int(ref_match.group(1))
|
||||
model = ref_match.group(2) or model
|
||||
model = ref_match.group(2) or fallback_model
|
||||
if thread_id and model in self.pool:
|
||||
model_obj = self.pool[model]
|
||||
if model_obj.exists(cr, uid, thread_id) and hasattr(model_obj, 'message_update'):
|
||||
_logger.info('Routing mail from %s to %s with Message-Id %s: direct reply to model: %s, thread_id: %s, custom_values: %s, uid: %s',
|
||||
email_from, email_to, message_id, model, thread_id, custom_values, uid)
|
||||
return [(model, thread_id, custom_values, uid)]
|
||||
route = self.message_route_verify(cr, uid, message, message_dict,
|
||||
(model, thread_id, custom_values, uid, None),
|
||||
update_author=True, assert_model=True, create_fallback=True, context=context)
|
||||
return route and [route] or []
|
||||
|
||||
# Verify whether this is a reply to a private message
|
||||
# 2. Reply to a private message
|
||||
if in_reply_to:
|
||||
message_ids = self.pool.get('mail.message').search(cr, uid, [
|
||||
('message_id', '=', in_reply_to),
|
||||
|
@ -670,9 +776,12 @@ class mail_thread(osv.AbstractModel):
|
|||
message = self.pool.get('mail.message').browse(cr, uid, message_ids[0], context=context)
|
||||
_logger.info('Routing mail from %s to %s with Message-Id %s: direct reply to a private message: %s, custom_values: %s, uid: %s',
|
||||
email_from, email_to, message_id, message.id, custom_values, uid)
|
||||
return [(message.model, message.res_id, custom_values, uid)]
|
||||
route = self.message_route_verify(cr, uid, message, message_dict,
|
||||
(message.model, message.res_id, custom_values, uid, None),
|
||||
update_author=True, assert_model=True, create_fallback=True, context=context)
|
||||
return route and [route] or []
|
||||
|
||||
# 2. Look for a matching mail.alias entry
|
||||
# 3. Look for a matching mail.alias entry
|
||||
# Delivered-To is a safe bet in most modern MTAs, but we have to fallback on To + Cc values
|
||||
# for all the odd MTAs out there, as there is no standard header for the envelope's `rcpt_to` value.
|
||||
rcpt_tos = \
|
||||
|
@ -697,14 +806,16 @@ class mail_thread(osv.AbstractModel):
|
|||
# user_id = self._message_find_user_id(cr, uid, message, context=context)
|
||||
user_id = uid
|
||||
_logger.info('No matching user_id for the alias %s', alias.alias_name)
|
||||
routes.append((alias.alias_model_id.model, alias.alias_force_thread_id, \
|
||||
eval(alias.alias_defaults), user_id))
|
||||
route = (alias.alias_model_id.model, alias.alias_force_thread_id, eval(alias.alias_defaults), user_id, alias)
|
||||
_logger.info('Routing mail from %s to %s with Message-Id %s: direct alias match: %r',
|
||||
email_from, email_to, message_id, routes)
|
||||
email_from, email_to, message_id, route)
|
||||
route = self.message_route_verify(cr, uid, message, message_dict, route,
|
||||
update_author=True, assert_model=True, create_fallback=True, context=context)
|
||||
if route:
|
||||
routes.append(route)
|
||||
return routes
|
||||
|
||||
# 3. Fallback to the provided parameters, if they work
|
||||
model_pool = self.pool.get(model)
|
||||
# 4. Fallback to the provided parameters, if they work
|
||||
if not thread_id:
|
||||
# Legacy: fallback to matching [ID] in the Subject
|
||||
match = tools.res_re.search(decode_header(message, 'Subject'))
|
||||
|
@ -714,16 +825,18 @@ class mail_thread(osv.AbstractModel):
|
|||
thread_id = int(thread_id)
|
||||
except:
|
||||
thread_id = False
|
||||
assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \
|
||||
_logger.info('Routing mail from %s to %s with Message-Id %s: fallback to model:%s, thread_id:%s, custom_values:%s, uid:%s',
|
||||
email_from, email_to, message_id, fallback_model, thread_id, custom_values, uid)
|
||||
route = self.message_route_verify(cr, uid, message, message_dict,
|
||||
(fallback_model, thread_id, custom_values, uid, None),
|
||||
update_author=True, assert_model=True, context=context)
|
||||
if route:
|
||||
return [route]
|
||||
|
||||
# AssertionError if no routes found and if no bounce occured
|
||||
assert False, \
|
||||
"No possible route found for incoming message from %s to %s (Message-Id %s:)." \
|
||||
"Create an appropriate mail.alias or force the destination model." % (email_from, email_to, message_id)
|
||||
if thread_id and not model_pool.exists(cr, uid, thread_id):
|
||||
_logger.warning('Received mail reply to missing document %s! Ignoring and creating new document instead for Message-Id %s',
|
||||
thread_id, message_id)
|
||||
thread_id = None
|
||||
_logger.info('Routing mail from %s to %s with Message-Id %s: fallback to model:%s, thread_id:%s, custom_values:%s, uid:%s',
|
||||
email_from, email_to, message_id, model, thread_id, custom_values, uid)
|
||||
return [(model, thread_id, custom_values, uid)]
|
||||
|
||||
def message_process(self, cr, uid, model, message, custom_values=None,
|
||||
save_original=False, strip_attachments=False,
|
||||
|
@ -777,25 +890,21 @@ class mail_thread(osv.AbstractModel):
|
|||
msg = self.message_parse(cr, uid, msg_txt, save_original=save_original, context=context)
|
||||
if strip_attachments:
|
||||
msg.pop('attachments', None)
|
||||
# postpone setting msg.partner_ids after message_post, to avoid double notifications
|
||||
partner_ids = msg.pop('partner_ids', [])
|
||||
if msg.get('message_id'): # should always be True as message_parse generate one if missing
|
||||
existing_msg_ids = self.pool.get('mail.message').search(cr, SUPERUSER_ID, [
|
||||
('message_id', '=', msg.get('message_id')),
|
||||
], context=context)
|
||||
if existing_msg_ids:
|
||||
_logger.info('Ignored mail from %s to %s with Message-Id %s:: found duplicated Message-Id during processing',
|
||||
_logger.info('Ignored mail from %s to %s with Message-Id %s: found duplicated Message-Id during processing',
|
||||
msg.get('from'), msg.get('to'), msg.get('message_id'))
|
||||
return False
|
||||
|
||||
# find possible routes for the message
|
||||
routes = self.message_route(cr, uid, msg_txt, model,
|
||||
thread_id, custom_values,
|
||||
context=context)
|
||||
|
||||
# postpone setting msg.partner_ids after message_post, to avoid double notifications
|
||||
partner_ids = msg.pop('partner_ids', [])
|
||||
|
||||
routes = self.message_route(cr, uid, msg_txt, msg, model, thread_id, custom_values, context=context)
|
||||
thread_id = False
|
||||
for model, thread_id, custom_values, user_id in routes:
|
||||
for model, thread_id, custom_values, user_id, alias in routes:
|
||||
if self._name == 'mail.thread':
|
||||
context.update({'thread_model': model})
|
||||
if model:
|
||||
|
@ -806,11 +915,10 @@ class mail_thread(osv.AbstractModel):
|
|||
|
||||
# disabled subscriptions during message_new/update to avoid having the system user running the
|
||||
# email gateway become a follower of all inbound messages
|
||||
nosub_ctx = dict(context, mail_create_nosubscribe=True)
|
||||
nosub_ctx = dict(context, mail_create_nosubscribe=True, mail_create_nolog=True)
|
||||
if thread_id and hasattr(model_pool, 'message_update'):
|
||||
model_pool.message_update(cr, user_id, [thread_id], msg, context=nosub_ctx)
|
||||
else:
|
||||
nosub_ctx = dict(nosub_ctx, mail_create_nolog=True)
|
||||
thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=nosub_ctx)
|
||||
else:
|
||||
assert thread_id == 0, "Posting a message without model should be with a null res_id, to create a private message."
|
||||
|
@ -947,7 +1055,6 @@ class mail_thread(osv.AbstractModel):
|
|||
"""
|
||||
msg_dict = {
|
||||
'type': 'email',
|
||||
'author_id': False,
|
||||
}
|
||||
if not isinstance(message, Message):
|
||||
if isinstance(message, unicode):
|
||||
|
@ -970,11 +1077,6 @@ class mail_thread(osv.AbstractModel):
|
|||
msg_dict['from'] = decode(message.get('from'))
|
||||
msg_dict['to'] = decode(message.get('to'))
|
||||
msg_dict['cc'] = decode(message.get('cc'))
|
||||
|
||||
if message.get('From'):
|
||||
author_ids = self._message_find_partners(cr, uid, message, ['From'], context=context)
|
||||
if author_ids:
|
||||
msg_dict['author_id'] = author_ids[0]
|
||||
msg_dict['email_from'] = decode(message.get('from'))
|
||||
partner_ids = self._message_find_partners(cr, uid, message, ['To', 'Cc'], context=context)
|
||||
msg_dict['partner_ids'] = [(4, partner_id) for partner_id in partner_ids]
|
||||
|
@ -1029,7 +1131,7 @@ class mail_thread(osv.AbstractModel):
|
|||
partner_id, partner_name<partner_email> or partner_name, reason """
|
||||
if email and not partner:
|
||||
# get partner info from email
|
||||
partner_info = self.message_get_partner_info_from_emails(cr, uid, [email], context=context, res_id=obj.id)[0]
|
||||
partner_info = self.message_partner_info_from_emails(cr, uid, obj.id, [email], context=context)[0]
|
||||
if partner_info.get('partner_id'):
|
||||
partner = self.pool.get('res.partner').browse(cr, SUPERUSER_ID, [partner_info.get('partner_id')], context=context)[0]
|
||||
if email and email in [val[1] for val in result[obj.id]]: # already existing email -> skip
|
||||
|
@ -1057,53 +1159,76 @@ class mail_thread(osv.AbstractModel):
|
|||
self._message_add_suggested_recipient(cr, uid, result, obj, partner=obj.user_id.partner_id, reason=self._all_columns['user_id'].column.string, context=context)
|
||||
return result
|
||||
|
||||
def message_get_partner_info_from_emails(self, cr, uid, emails, link_mail=False, context=None, res_id=None):
|
||||
""" Wrapper with weird order parameter because of 7.0 fix.
|
||||
def _find_partner_from_emails(self, cr, uid, id, emails, model=None, context=None, check_followers=True):
|
||||
""" Utility method to find partners from email addresses. The rules are :
|
||||
1 - check in document (model | self, id) followers
|
||||
2 - try to find a matching partner that is also an user
|
||||
3 - try to find a matching partner
|
||||
|
||||
TDE TODO: remove me in 8.0 """
|
||||
return self.message_find_partner_from_emails(cr, uid, res_id, emails, link_mail=link_mail, context=context)
|
||||
|
||||
def message_find_partner_from_emails(self, cr, uid, id, emails, link_mail=False, context=None):
|
||||
""" Convert a list of emails into a list partner_ids and a list
|
||||
new_partner_ids. The return value is non conventional because
|
||||
it is meant to be used by the mail widget.
|
||||
|
||||
:return dict: partner_ids and new_partner_ids
|
||||
|
||||
TDE TODO: merge me with other partner finding methods in 8.0 """
|
||||
mail_message_obj = self.pool.get('mail.message')
|
||||
partner_obj = self.pool.get('res.partner')
|
||||
result = list()
|
||||
if id and self._name != 'mail.thread':
|
||||
obj = self.browse(cr, SUPERUSER_ID, id, context=context)
|
||||
else:
|
||||
:param list emails: list of email addresses
|
||||
:param string model: model to fetch related record; by default self
|
||||
is used.
|
||||
:param boolean check_followers: check in document followers
|
||||
"""
|
||||
partner_obj = self.pool['res.partner']
|
||||
partner_ids = []
|
||||
obj = None
|
||||
for email in emails:
|
||||
partner_info = {'full_name': email, 'partner_id': False}
|
||||
m = re.search(r"((.+?)\s*<)?([^<>]+@[^<>]+)>?", email, re.IGNORECASE | re.DOTALL)
|
||||
if not m:
|
||||
if id and (model or self._name != 'mail.thread') and check_followers:
|
||||
if model:
|
||||
obj = self.pool[model].browse(cr, uid, id, context=context)
|
||||
else:
|
||||
obj = self.browse(cr, uid, id, context=context)
|
||||
for contact in emails:
|
||||
partner_id = False
|
||||
email_address = tools.email_split(contact)
|
||||
if not email_address:
|
||||
partner_ids.append(partner_id)
|
||||
continue
|
||||
email_address = m.group(3)
|
||||
email_address = email_address[0]
|
||||
# first try: check in document's followers
|
||||
if obj:
|
||||
for follower in obj.message_follower_ids:
|
||||
if follower.email == email_address:
|
||||
partner_info['partner_id'] = follower.id
|
||||
# second try: check in partners
|
||||
if not partner_info.get('partner_id'):
|
||||
ids = partner_obj.search(cr, SUPERUSER_ID, [('email', 'ilike', email_address), ('user_ids', '!=', False)], limit=1, context=context)
|
||||
if not ids:
|
||||
ids = partner_obj.search(cr, SUPERUSER_ID, [('email', 'ilike', email_address)], limit=1, context=context)
|
||||
partner_id = follower.id
|
||||
# second try: check in partners that are also users
|
||||
if not partner_id:
|
||||
ids = partner_obj.search(cr, SUPERUSER_ID, [
|
||||
('email', 'ilike', email_address),
|
||||
('user_ids', '!=', False)
|
||||
], limit=1, context=context)
|
||||
if ids:
|
||||
partner_info['partner_id'] = ids[0]
|
||||
partner_id = ids[0]
|
||||
# third try: check in partners
|
||||
if not partner_id:
|
||||
ids = partner_obj.search(cr, SUPERUSER_ID, [
|
||||
('email', 'ilike', email_address)
|
||||
], limit=1, context=context)
|
||||
if ids:
|
||||
partner_id = ids[0]
|
||||
partner_ids.append(partner_id)
|
||||
return partner_ids
|
||||
|
||||
def message_partner_info_from_emails(self, cr, uid, id, emails, link_mail=False, context=None):
|
||||
""" Convert a list of emails into a list partner_ids and a list
|
||||
new_partner_ids. The return value is non conventional because
|
||||
it is meant to be used by the mail widget.
|
||||
|
||||
:return dict: partner_ids and new_partner_ids """
|
||||
mail_message_obj = self.pool.get('mail.message')
|
||||
partner_ids = self._find_partner_from_emails(cr, uid, id, emails, context=context)
|
||||
result = list()
|
||||
for idx in range(len(emails)):
|
||||
email_address = emails[idx]
|
||||
partner_id = partner_ids[idx]
|
||||
partner_info = {'full_name': email_address, 'partner_id': partner_id}
|
||||
result.append(partner_info)
|
||||
|
||||
# link mail with this from mail to the new partner id
|
||||
if link_mail and partner_info['partner_id']:
|
||||
message_ids = mail_message_obj.search(cr, SUPERUSER_ID, [
|
||||
'|',
|
||||
('email_from', '=', email),
|
||||
('email_from', 'ilike', '<%s>' % email),
|
||||
('email_from', '=', email_address),
|
||||
('email_from', 'ilike', '<%s>' % email_address),
|
||||
('author_id', '=', False)
|
||||
], context=context)
|
||||
if message_ids:
|
||||
|
@ -1156,18 +1281,7 @@ class mail_thread(osv.AbstractModel):
|
|||
del context['thread_model']
|
||||
return self.pool[model].message_post(cr, uid, thread_id, body=body, subject=subject, type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, content_subtype=content_subtype, **kwargs)
|
||||
|
||||
# 0: Parse email-from, try to find a better author_id based on document's followers for incoming emails
|
||||
email_from = kwargs.get('email_from')
|
||||
if email_from and thread_id and type == 'email' and kwargs.get('author_id'):
|
||||
email_list = tools.email_split(email_from)
|
||||
doc = self.browse(cr, uid, thread_id, context=context)
|
||||
if email_list and doc:
|
||||
author_ids = self.pool.get('res.partner').search(cr, uid, [
|
||||
('email', 'ilike', email_list[0]),
|
||||
('id', 'in', [f.id for f in doc.message_follower_ids])
|
||||
], limit=1, context=context)
|
||||
if author_ids:
|
||||
kwargs['author_id'] = author_ids[0]
|
||||
#0: Find the message's author, because we need it for private discussion
|
||||
author_id = kwargs.get('author_id')
|
||||
if author_id is None: # keep False values
|
||||
author_id = self.pool.get('mail.message')._get_default_author(cr, uid, context=context)
|
||||
|
@ -1278,21 +1392,6 @@ class mail_thread(osv.AbstractModel):
|
|||
self.message_subscribe(cr, uid, [thread_id], [message.author_id.id], context=context)
|
||||
return msg_id
|
||||
|
||||
#------------------------------------------------------
|
||||
# Compatibility methods: do not use
|
||||
# TDE TODO: remove me in 8.0
|
||||
#------------------------------------------------------
|
||||
|
||||
def message_create_partners_from_emails(self, cr, uid, emails, context=None):
|
||||
return {'partner_ids': [], 'new_partner_ids': []}
|
||||
|
||||
def message_post_user_api(self, cr, uid, thread_id, body='', parent_id=False,
|
||||
attachment_ids=None, content_subtype='plaintext',
|
||||
context=None, **kwargs):
|
||||
return self.message_post(cr, uid, thread_id, body=body, parent_id=parent_id,
|
||||
attachment_ids=attachment_ids, content_subtype=content_subtype,
|
||||
context=context, **kwargs)
|
||||
|
||||
#------------------------------------------------------
|
||||
# Followers API
|
||||
#------------------------------------------------------
|
||||
|
|
|
@ -23,6 +23,7 @@ from openerp.osv import fields, osv
|
|||
from openerp import SUPERUSER_ID
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
class res_users(osv.Model):
|
||||
""" Update of res.users class
|
||||
- add a preference about sending emails about notifications
|
||||
|
@ -42,7 +43,6 @@ class res_users(osv.Model):
|
|||
}
|
||||
|
||||
_defaults = {
|
||||
'alias_domain': False, # always hide alias during creation
|
||||
'display_groups_suggestions': True,
|
||||
}
|
||||
|
||||
|
@ -63,25 +63,20 @@ class res_users(osv.Model):
|
|||
def _auto_init(self, cr, context=None):
|
||||
""" Installation hook: aliases, partner following themselves """
|
||||
# create aliases for all users and avoid constraint errors
|
||||
res = self.pool.get('mail.alias').migrate_to_alias(cr, self._name, self._table, super(res_users, self)._auto_init,
|
||||
self._columns['alias_id'], 'login', alias_force_key='id', context=context)
|
||||
return res
|
||||
return self.pool.get('mail.alias').migrate_to_alias(cr, self._name, self._table, super(res_users, self)._auto_init,
|
||||
self._name, self._columns['alias_id'], 'login', alias_force_key='id', context=context)
|
||||
|
||||
def create(self, cr, uid, data, context=None):
|
||||
# create default alias same as the login
|
||||
if not data.get('login', False):
|
||||
raise osv.except_osv(_('Invalid Action!'), _('You may not create a user. To create new users, you should use the "Settings > Users" menu.'))
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
mail_alias = self.pool.get('mail.alias')
|
||||
alias_id = mail_alias.create_unique_alias(cr, uid, {'alias_name': data['login']}, model_name=self._name, context=context)
|
||||
data['alias_id'] = alias_id
|
||||
data.pop('alias_name', None) # prevent errors during copy()
|
||||
|
||||
# create user
|
||||
user_id = super(res_users, self).create(cr, uid, data, context=context)
|
||||
create_context = dict(context, alias_model_name=self._name, alias_parent_model_name=self._name)
|
||||
user_id = super(res_users, self).create(cr, uid, data, context=create_context)
|
||||
user = self.browse(cr, uid, user_id, context=context)
|
||||
# alias
|
||||
mail_alias.write(cr, SUPERUSER_ID, [alias_id], {"alias_force_thread_id": user_id}, context)
|
||||
self.pool.get('mail.alias').write(cr, SUPERUSER_ID, [user.alias_id.id], {"alias_force_thread_id": user_id, "alias_parent_thread_id": user_id}, context)
|
||||
|
||||
# create a welcome message
|
||||
self._create_welcome_message(cr, uid, user, context=context)
|
||||
return user_id
|
||||
|
@ -95,12 +90,6 @@ class res_users(osv.Model):
|
|||
return self.pool.get('res.partner').message_post(cr, SUPERUSER_ID, [user.partner_id.id],
|
||||
body=body, context=context)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
# User alias is sync'ed with login
|
||||
if vals.get('login'):
|
||||
vals['alias_name'] = vals['login']
|
||||
return super(res_users, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
# Cascade-delete mail aliases as well, as they should not exist without the user.
|
||||
alias_pool = self.pool.get('mail.alias')
|
||||
|
|
|
@ -27,15 +27,19 @@
|
|||
<field name="notification_email_send"/>
|
||||
</field>
|
||||
<field name="signature" position="before">
|
||||
<field name="alias_domain" invisible="1"/>
|
||||
<field name="alias_id" readonly="1" required="0" attrs="{'invisible': [('alias_domain', '=', False)]}"/>
|
||||
<label for="alias_id" string="Messaging Alias" class="oe_read_only"
|
||||
attrs="{'invisible': [('alias_domain', '=', False)]}"/>
|
||||
<field name="alias_id" class="oe_read_only" required="0" nolabel="1"
|
||||
attrs="{'invisible': [('alias_domain', '=', False)]}"/>
|
||||
<label for="alias_name" string="Messaging Alias" class="oe_edit_only"
|
||||
attrs="{'invisible': [('alias_domain', '=', False)]}"/>
|
||||
<div class="oe_edit_only" attrs="{'invisible': [('alias_domain', '=', False)]}">
|
||||
<field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline" readonly="1"/>
|
||||
</div>
|
||||
<field name="alias_contact" string="Alias Accepts Emails From"
|
||||
attrs="{'invisible': [('alias_domain', '=', False)]}"/>
|
||||
<field name="display_groups_suggestions" groups="base.group_no_one"/>
|
||||
</field>
|
||||
<group string="Email preferences" position="after">
|
||||
<group name="misc" string="Miscellaneous"
|
||||
groups="base.group_no_one">
|
||||
<field name="display_groups_suggestions"/>
|
||||
</group>
|
||||
</group>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
@ -26,6 +26,16 @@
|
|||
border-radius: 0px;
|
||||
}
|
||||
|
||||
/* ---- GENERIC FOR MAIL-RELATED STUFF ---- */
|
||||
.openerp .oe_e.oe_e_alias {
|
||||
font-size: 30px;
|
||||
line-height: 15px;
|
||||
vertical-align: top;
|
||||
margin-right: 3px;
|
||||
color: white;
|
||||
text-shadow: 0px 0px 2px black;
|
||||
}
|
||||
|
||||
/* ------------ MAIL WIDGET --------------- */
|
||||
.openerp .oe_mail, .openerp .oe_mail *{
|
||||
-webkit-box-sizing: border-box;
|
||||
|
|
|
@ -632,10 +632,7 @@ openerp.mail = function (session) {
|
|||
// have unknown names -> call message_get_partner_info_from_emails to try to find partner_id
|
||||
var find_done = $.Deferred();
|
||||
if (names_to_find.length > 0) {
|
||||
var values = {
|
||||
'res_id': this.context.default_res_id,
|
||||
}
|
||||
find_done = self.parent_thread.ds_thread._model.call('message_get_partner_info_from_emails', [names_to_find], values);
|
||||
find_done = self.parent_thread.ds_thread._model.call('message_partner_info_from_emails', [this.context.default_res_id, names_to_find]);
|
||||
}
|
||||
else {
|
||||
find_done.resolve([]);
|
||||
|
@ -681,11 +678,7 @@ openerp.mail = function (session) {
|
|||
var new_names_to_find = _.difference(names_to_find, names_to_remove);
|
||||
find_done = $.Deferred();
|
||||
if (new_names_to_find.length > 0) {
|
||||
var values = {
|
||||
'link_mail': true,
|
||||
'res_id': self.context.default_res_id,
|
||||
}
|
||||
find_done = self.parent_thread.ds_thread._model.call('message_get_partner_info_from_emails', [new_names_to_find], values);
|
||||
find_done = self.parent_thread.ds_thread._model.call('message_partner_info_from_emails', [self.context.default_res_id, new_names_to_find, true]);
|
||||
}
|
||||
else {
|
||||
find_done.resolve([]);
|
||||
|
|
|
@ -71,9 +71,9 @@ class TestMailBase(common.TransactionCase):
|
|||
# Test users to use through the various tests
|
||||
self.res_users.write(cr, uid, uid, {'name': 'Administrator'})
|
||||
self.user_raoul_id = self.res_users.create(cr, uid,
|
||||
{'name': 'Raoul Grosbedon', 'signature': 'SignRaoul', 'email': 'raoul@raoul.fr', 'login': 'raoul', 'groups_id': [(6, 0, [self.group_employee_id])]})
|
||||
{'name': 'Raoul Grosbedon', 'signature': 'SignRaoul', 'email': 'raoul@raoul.fr', 'login': 'raoul', 'alias_name': 'raoul', 'groups_id': [(6, 0, [self.group_employee_id])]})
|
||||
self.user_bert_id = self.res_users.create(cr, uid,
|
||||
{'name': 'Bert Tartignole', 'signature': 'SignBert', 'email': 'bert@bert.fr', 'login': 'bert', 'groups_id': [(6, 0, [])]})
|
||||
{'name': 'Bert Tartignole', 'signature': 'SignBert', 'email': 'bert@bert.fr', 'login': 'bert', 'alias_name': 'bert', 'groups_id': [(6, 0, [])]})
|
||||
self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
|
||||
self.user_bert = self.res_users.browse(cr, uid, self.user_bert_id)
|
||||
self.user_admin = self.res_users.browse(cr, uid, uid)
|
||||
|
@ -83,7 +83,7 @@ class TestMailBase(common.TransactionCase):
|
|||
|
||||
# Test 'pigs' group to use through the various tests
|
||||
self.group_pigs_id = self.mail_group.create(cr, uid,
|
||||
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'},
|
||||
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !', 'alias_name': 'group+pigs'},
|
||||
{'mail_create_nolog': True})
|
||||
self.group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
|
||||
|
||||
|
|
|
@ -32,17 +32,17 @@ class test_mail(TestMailBase):
|
|||
""" Test basic mail.alias setup works, before trying to use them for routing """
|
||||
cr, uid = self.cr, self.uid
|
||||
self.user_valentin_id = self.res_users.create(cr, uid,
|
||||
{'name': 'Valentin Cognito', 'email': 'valentin.cognito@gmail.com', 'login': 'valentin.cognito'})
|
||||
{'name': 'Valentin Cognito', 'email': 'valentin.cognito@gmail.com', 'login': 'valentin.cognito', 'alias_name': 'valentin.cognito'})
|
||||
self.user_valentin = self.res_users.browse(cr, uid, self.user_valentin_id)
|
||||
self.assertEquals(self.user_valentin.alias_name, self.user_valentin.login, "Login should be used as alias")
|
||||
|
||||
self.user_pagan_id = self.res_users.create(cr, uid,
|
||||
{'name': 'Pagan Le Marchant', 'email': 'plmarchant@gmail.com', 'login': 'plmarchant@gmail.com'})
|
||||
{'name': 'Pagan Le Marchant', 'email': 'plmarchant@gmail.com', 'login': 'plmarchant@gmail.com', 'alias_name': 'plmarchant@gmail.com'})
|
||||
self.user_pagan = self.res_users.browse(cr, uid, self.user_pagan_id)
|
||||
self.assertEquals(self.user_pagan.alias_name, 'plmarchant', "If login is an email, the alias should keep only the local part")
|
||||
|
||||
self.user_barty_id = self.res_users.create(cr, uid,
|
||||
{'name': 'Bartholomew Ironside', 'email': 'barty@gmail.com', 'login': 'b4r+_#_R3wl$$'})
|
||||
{'name': 'Bartholomew Ironside', 'email': 'barty@gmail.com', 'login': 'b4r+_#_R3wl$$', 'alias_name': 'b4r+_#_R3wl$$'})
|
||||
self.user_barty = self.res_users.browse(cr, uid, self.user_barty_id)
|
||||
self.assertEquals(self.user_barty.alias_name, 'b4r+_-_r3wl-', 'Disallowed chars should be replaced by hyphens')
|
||||
|
||||
|
@ -739,18 +739,21 @@ class test_mail(TestMailBase):
|
|||
self.ir_model_data.create(cr, uid, {'name': 'mt_private', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_private_id})
|
||||
mt_name_supername_id = self.mail_message_subtype.create(cr, uid, {'name': 'name_supername', 'description': 'Supername name'})
|
||||
self.ir_model_data.create(cr, uid, {'name': 'mt_name_supername', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_name_supername_id})
|
||||
mt_group_public_set_id = self.mail_message_subtype.create(cr, uid, {'name': 'group_public_set', 'description': 'Group set'})
|
||||
self.ir_model_data.create(cr, uid, {'name': 'mt_group_public_set', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_group_public_set_id})
|
||||
mt_group_public_id = self.mail_message_subtype.create(cr, uid, {'name': 'group_public', 'description': 'Group changed'})
|
||||
self.ir_model_data.create(cr, uid, {'name': 'mt_group_public', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_group_public_id})
|
||||
|
||||
# Data: alter mail_group model for testing purposes (test on classic, selection and many2one fields)
|
||||
self.mail_group._track = {
|
||||
'public': {
|
||||
'mail.mt_private': lambda self, cr, uid, obj, ctx=None: obj['public'] == 'private',
|
||||
'mail.mt_private': lambda self, cr, uid, obj, ctx=None: obj.public == 'private',
|
||||
},
|
||||
'name': {
|
||||
'mail.mt_name_supername': lambda self, cr, uid, obj, ctx=None: obj['name'] == 'supername',
|
||||
'mail.mt_name_supername': lambda self, cr, uid, obj, ctx=None: obj.name == 'supername',
|
||||
},
|
||||
'group_public_id': {
|
||||
'mail.mt_group_public_set': lambda self, cr, uid, obj, ctx=None: obj.group_public_id,
|
||||
'mail.mt_group_public': lambda self, cr, uid, obj, ctx=None: True,
|
||||
},
|
||||
}
|
||||
|
@ -787,21 +790,37 @@ class test_mail(TestMailBase):
|
|||
self.assertIn(u'Public\u2192Private', _strip_string_spaces(last_msg.body), 'tracked: message body incorrect')
|
||||
self.assertIn(u'Pigs\u2192supername', _strip_string_spaces(last_msg.body), 'tracked feature: message body does not hold always tracked field')
|
||||
|
||||
# Test: change public as public, group_public_id -> 1 subtype, name always tracked
|
||||
# Test: change public as public, group_public_id -> 2 subtypes, name always tracked
|
||||
self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'public': 'public', 'group_public_id': group_system_id})
|
||||
self.group_pigs.refresh()
|
||||
self.assertEqual(len(self.group_pigs.message_ids), 4, 'tracked: one message should have been produced')
|
||||
# Test: first produced message: mt_group_public_id, with name always tracked, public tracked on change
|
||||
self.assertEqual(len(self.group_pigs.message_ids), 5, 'tracked: one message should have been produced')
|
||||
# Test: first produced message: mt_group_public_set_id, with name always tracked, public tracked on change
|
||||
last_msg = self.group_pigs.message_ids[-4]
|
||||
self.assertEqual(last_msg.subtype_id.id, mt_group_public_id, 'tracked: message should not be linked to any subtype')
|
||||
self.assertEqual(last_msg.subtype_id.id, mt_group_public_set_id, 'tracked: message should be linked to mt_group_public_set_id')
|
||||
self.assertIn('Group set', last_msg.body, 'tracked: message body does not hold the subtype description')
|
||||
self.assertIn(u'Private\u2192Public', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold changed tracked field')
|
||||
self.assertIn(u'HumanResources/Employee\u2192Administration/Settings', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
|
||||
# Test: second produced message: mt_group_public_id, with name always tracked, public tracked on change
|
||||
last_msg = self.group_pigs.message_ids[-5]
|
||||
self.assertEqual(last_msg.subtype_id.id, mt_group_public_id, 'tracked: message should be linked to mt_group_public_id')
|
||||
self.assertIn('Group changed', last_msg.body, 'tracked: message body does not hold the subtype description')
|
||||
self.assertIn(u'Private\u2192Public', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold changed tracked field')
|
||||
self.assertIn(u'HumanResources/Employee\u2192Administration/Settings', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
|
||||
|
||||
# Test: change group_public_id to False -> 1 subtype, name always tracked
|
||||
self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'group_public_id': False})
|
||||
self.group_pigs.refresh()
|
||||
self.assertEqual(len(self.group_pigs.message_ids), 6, 'tracked: one message should have been produced')
|
||||
# Test: first produced message: mt_group_public_set_id, with name always tracked, public tracked on change
|
||||
last_msg = self.group_pigs.message_ids[-6]
|
||||
self.assertEqual(last_msg.subtype_id.id, mt_group_public_id, 'tracked: message should be linked to mt_group_public_id')
|
||||
self.assertIn('Group changed', last_msg.body, 'tracked: message body does not hold the subtype description')
|
||||
self.assertIn(u'Administration/Settings\u2192', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
|
||||
|
||||
# Test: change not tracked field, no tracking message
|
||||
self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'description': 'Dummy'})
|
||||
self.group_pigs.refresh()
|
||||
self.assertEqual(len(self.group_pigs.message_ids), 4, 'tracked: No message should have been produced')
|
||||
self.assertEqual(len(self.group_pigs.message_ids), 6, 'tracked: No message should have been produced')
|
||||
|
||||
# Data: removed changes
|
||||
public_col.track_visibility = None
|
||||
|
|
|
@ -99,20 +99,20 @@ class TestMailgateway(TestMailBase):
|
|||
# --------------------------------------------------
|
||||
|
||||
# Do: find partner with email -> first partner should be found
|
||||
partner_info = self.mail_thread.message_find_partner_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
partner_info = self.mail_thread.message_partner_info_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
self.assertEqual(partner_info['full_name'], 'Maybe Raoul <test@test.fr>',
|
||||
'mail_thread: message_find_partner_from_emails did not handle email')
|
||||
'mail_thread: message_partner_info_from_emails did not handle email')
|
||||
self.assertEqual(partner_info['partner_id'], p_a_id,
|
||||
'mail_thread: message_find_partner_from_emails wrong partner found')
|
||||
'mail_thread: message_partner_info_from_emails wrong partner found')
|
||||
|
||||
# Data: add some data about partners
|
||||
# 2 - User BRaoul
|
||||
p_b_id = self.res_partner.create(cr, uid, {'name': 'BRaoul', 'email': 'test@test.fr', 'user_ids': [(4, user_raoul.id)]})
|
||||
|
||||
# Do: find partner with email -> first user should be found
|
||||
partner_info = self.mail_thread.message_find_partner_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
partner_info = self.mail_thread.message_partner_info_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
self.assertEqual(partner_info['partner_id'], p_b_id,
|
||||
'mail_thread: message_find_partner_from_emails wrong partner found')
|
||||
'mail_thread: message_partner_info_from_emails wrong partner found')
|
||||
|
||||
# --------------------------------------------------
|
||||
# CASE1: with object
|
||||
|
@ -120,9 +120,9 @@ class TestMailgateway(TestMailBase):
|
|||
|
||||
# Do: find partner in group where there is a follower with the email -> should be taken
|
||||
self.mail_group.message_subscribe(cr, uid, [group_pigs.id], [p_b_id])
|
||||
partner_info = self.mail_group.message_find_partner_from_emails(cr, uid, group_pigs.id, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
partner_info = self.mail_group.message_partner_info_from_emails(cr, uid, group_pigs.id, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
self.assertEqual(partner_info['partner_id'], p_b_id,
|
||||
'mail_thread: message_find_partner_from_emails wrong partner found')
|
||||
'mail_thread: message_partner_info_from_emails wrong partner found')
|
||||
|
||||
def test_05_mail_message_mail_mail(self):
|
||||
""" Tests designed for testing email values based on mail.message, aliases, ... """
|
||||
|
@ -189,6 +189,7 @@ class TestMailgateway(TestMailBase):
|
|||
|
||||
# Data: set catchall domain
|
||||
self.registry('ir.config_parameter').set_param(cr, uid, 'mail.catchall.domain', alias_domain)
|
||||
self.registry('ir.config_parameter').unlink(cr, uid, self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.alias')]))
|
||||
|
||||
# Update message
|
||||
self.mail_message.write(cr, user_raoul_id, [msg_id], {'email_from': False, 'reply_to': False})
|
||||
|
@ -220,87 +221,6 @@ class TestMailgateway(TestMailBase):
|
|||
self.assertEqual(mail.reply_to, msg.email_from,
|
||||
'mail_mail: incorrect reply_to: should be message email_from')
|
||||
|
||||
def test_05_mail_message_mail_mail(self):
|
||||
""" Tests designed for testing email values based on mail.message, aliases, ... """
|
||||
cr, uid = self.cr, self.uid
|
||||
|
||||
# Data: clean catchall domain
|
||||
param_ids = self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.domain')])
|
||||
self.registry('ir.config_parameter').unlink(cr, uid, param_ids)
|
||||
|
||||
# Do: create a mail_message with a reply_to, without message-id
|
||||
msg_id = self.mail_message.create(cr, uid, {'subject': 'Subject', 'body': 'Body', 'reply_to': 'custom@example.com'})
|
||||
msg = self.mail_message.browse(cr, uid, msg_id)
|
||||
# Test: message content
|
||||
self.assertIn('reply_to', msg.message_id,
|
||||
'mail_message: message_id should be specific to a mail_message with a given reply_to')
|
||||
self.assertEqual('custom@example.com', msg.reply_to,
|
||||
'mail_message: incorrect reply_to')
|
||||
# Do: create a mail_mail with the previous mail_message and specified reply_to
|
||||
mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'reply_to': 'other@example.com', 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, uid, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, 'other@example.com',
|
||||
'mail_mail: reply_to should be equal to the one coming from creation values')
|
||||
# Do: create a mail_mail with the previous mail_message
|
||||
self.mail_message.write(cr, uid, [msg_id], {'reply_to': 'custom@example.com'})
|
||||
msg.refresh()
|
||||
mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, uid, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, msg.reply_to,
|
||||
'mail_mail: reply_to should be equal to the one coming from the mail_message')
|
||||
|
||||
# Do: create a mail_message without a reply_to
|
||||
msg_id = self.mail_message.create(cr, uid, {'subject': 'Subject', 'body': 'Body', 'model': 'mail.group', 'res_id': self.group_pigs_id, 'email_from': False})
|
||||
msg = self.mail_message.browse(cr, uid, msg_id)
|
||||
# Test: message content
|
||||
self.assertIn('mail.group', msg.message_id,
|
||||
'mail_message: message_id should contain model')
|
||||
self.assertIn('%s' % self.group_pigs_id, msg.message_id,
|
||||
'mail_message: message_id should contain res_id')
|
||||
self.assertFalse(msg.reply_to,
|
||||
'mail_message: should not generate a reply_to address when not specified')
|
||||
# Do: create a mail_mail based on the previous mail_message
|
||||
mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, uid, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertFalse(mail.reply_to,
|
||||
'mail_mail: reply_to should not have been guessed')
|
||||
# Update message
|
||||
self.mail_message.write(cr, uid, [msg_id], {'email_from': 'someone@example.com'})
|
||||
msg.refresh()
|
||||
# Do: create a mail_mail based on the previous mail_message
|
||||
mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, uid, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(email_split(mail.reply_to), email_split(msg.email_from),
|
||||
'mail_mail: reply_to should be equal to mail_message.email_from when having no document or default alias')
|
||||
|
||||
# Data: set catchall domain
|
||||
self.registry('ir.config_parameter').set_param(cr, uid, 'mail.catchall.domain', 'schlouby.fr')
|
||||
self.registry('ir.config_parameter').unlink(cr, uid, self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.alias')]))
|
||||
|
||||
# Update message
|
||||
self.mail_message.write(cr, uid, [msg_id], {'email_from': False, 'reply_to': False})
|
||||
msg.refresh()
|
||||
# Do: create a mail_mail based on the previous mail_message
|
||||
mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, uid, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, '"Followers of Pigs" <group+pigs@schlouby.fr>',
|
||||
'mail_mail: reply_to should equal the mail.group alias')
|
||||
|
||||
# Update message
|
||||
self.mail_message.write(cr, uid, [msg_id], {'res_id': False, 'email_from': 'someone@schlouby.fr', 'reply_to': False})
|
||||
msg.refresh()
|
||||
# Do: create a mail_mail based on the previous mail_message
|
||||
mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, uid, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, msg.email_from,
|
||||
'mail_mail: reply_to should equal the mail_message email_from')
|
||||
|
||||
# Data: set catchall alias
|
||||
self.registry('ir.config_parameter').set_param(self.cr, self.uid, 'mail.catchall.alias', 'gateway')
|
||||
|
||||
|
@ -310,7 +230,7 @@ class TestMailgateway(TestMailBase):
|
|||
# Do: create a mail_mail based on the previous mail_message
|
||||
mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, uid, mail_id)
|
||||
# Test: mail_mail content
|
||||
# Test: mail_mail Content-Type
|
||||
self.assertEqual(mail.reply_to, 'gateway@schlouby.fr',
|
||||
'mail_mail: reply_to should equal the catchall email alias')
|
||||
|
||||
|
@ -351,7 +271,10 @@ class TestMailgateway(TestMailBase):
|
|||
alias_id = self.mail_alias.create(cr, uid, {
|
||||
'alias_name': 'groups',
|
||||
'alias_user_id': False,
|
||||
'alias_model_id': self.mail_group_model_id})
|
||||
'alias_model_id': self.mail_group_model_id,
|
||||
'alias_parent_model_id': self.mail_group_model_id,
|
||||
'alias_parent_thread_id': self.group_pigs_id,
|
||||
'alias_contact': 'everyone'})
|
||||
|
||||
# --------------------------------------------------
|
||||
# Test1: new record creation
|
||||
|
@ -392,12 +315,42 @@ class TestMailgateway(TestMailBase):
|
|||
# Data: unlink group
|
||||
frog_group.unlink()
|
||||
|
||||
# Do: incoming email from a known partner on an alias with known recipients, alias is owned by user that can create a group
|
||||
self.mail_alias.write(cr, uid, [alias_id], {'alias_user_id': self.user_raoul_id})
|
||||
p1id = self.res_partner.create(cr, uid, {'name': 'Sylvie Lelitre', 'email': 'test.sylvie.lelitre@agrolait.com'})
|
||||
p2id = self.res_partner.create(cr, uid, {'name': 'Other Poilvache', 'email': 'other@gmail.com'})
|
||||
# Do: incoming email from an unknown partner on a Partners only alias -> bounce
|
||||
self._init_mock_build_email()
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other@gmail.com')
|
||||
self.mail_alias.write(cr, uid, [alias_id], {'alias_contact': 'partners'})
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other2@gmail.com')
|
||||
# Test: no group created
|
||||
self.assertTrue(len(frog_groups) == 0)
|
||||
# Test: email bounced
|
||||
sent_emails = self._build_email_kwargs_list
|
||||
self.assertEqual(len(sent_emails), 1,
|
||||
'message_process: incoming email on Partners alias should send a bounce email')
|
||||
self.assertIn('Frogs', sent_emails[0].get('subject'),
|
||||
'message_process: bounce email on Partners alias should contain the original subject')
|
||||
self.assertIn('test.sylvie.lelitre@agrolait.com', sent_emails[0].get('email_to'),
|
||||
'message_process: bounce email on Partners alias should have original email sender as recipient')
|
||||
|
||||
# Do: incoming email from an unknown partner on a Followers only alias -> bounce
|
||||
self._init_mock_build_email()
|
||||
self.mail_alias.write(cr, uid, [alias_id], {'alias_contact': 'followers'})
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other3@gmail.com')
|
||||
# Test: no group created
|
||||
self.assertTrue(len(frog_groups) == 0)
|
||||
# Test: email bounced
|
||||
sent_emails = self._build_email_kwargs_list
|
||||
self.assertEqual(len(sent_emails), 1,
|
||||
'message_process: incoming email on Followers alias should send a bounce email')
|
||||
self.assertIn('Frogs', sent_emails[0].get('subject'),
|
||||
'message_process: bounce email on Followers alias should contain the original subject')
|
||||
self.assertIn('test.sylvie.lelitre@agrolait.com', sent_emails[0].get('email_to'),
|
||||
'message_process: bounce email on Followers alias should have original email sender as recipient')
|
||||
|
||||
# Do: incoming email from a known partner on a Partners alias -> ok (+ test on alias.user_id)
|
||||
self.mail_alias.write(cr, uid, [alias_id], {'alias_user_id': self.user_raoul_id, 'alias_contact': 'partners'})
|
||||
p1id = self.res_partner.create(cr, uid, {'name': 'Sylvie Lelitre', 'email': 'test.sylvie.lelitre@agrolait.com'})
|
||||
p2id = self.res_partner.create(cr, uid, {'name': 'Other Poilvache', 'email': 'other4@gmail.com'})
|
||||
self._init_mock_build_email()
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other4@gmail.com')
|
||||
sent_emails = self._build_email_kwargs_list
|
||||
# Test: one group created by Raoul
|
||||
self.assertEqual(len(frog_groups), 1, 'message_process: a new mail.group should have been created')
|
||||
|
@ -409,24 +362,37 @@ class TestMailgateway(TestMailBase):
|
|||
self.assertEqual(len(frog_group.message_ids), 1,
|
||||
'message_process: newly created group should have the incoming email in message_ids')
|
||||
msg = frog_group.message_ids[0]
|
||||
# Test: message: unknown email address -> message has email_from, not author_id
|
||||
# Test: message: author found
|
||||
self.assertEqual(p1id, msg.author_id.id,
|
||||
'message_process: message on created group should have Sylvie as author_id')
|
||||
self.assertIn('Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>', msg.email_from,
|
||||
'message_process: message on created group should have have an email_from')
|
||||
# Test: author (not recipient and not raoul (as alias owner)) added as follower
|
||||
# Test: author (not recipient and not Raoul (as alias owner)) added as follower
|
||||
frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
|
||||
self.assertEqual(frog_follower_ids, set([p1id]),
|
||||
'message_process: newly created group should have 1 follower (author, not creator, not recipients)')
|
||||
# Test: sent emails: no-one, no bounce effet
|
||||
sent_emails = self._build_email_kwargs_list
|
||||
self.assertEqual(len(sent_emails), 0,
|
||||
'message_process: should not bounce incoming emails')
|
||||
# Data: unlink group
|
||||
frog_group.unlink()
|
||||
|
||||
# Do: incoming email from a known partner that is also an user that can create a mail.group
|
||||
self.res_users.create(cr, uid, {'partner_id': p1id, 'login': 'sylvie', 'groups_id': [(6, 0, [self.group_employee_id])]})
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other@gmail.com')
|
||||
# Do: incoming email from a not follower Partner on a Followers only alias -> bounce
|
||||
self._init_mock_build_email()
|
||||
self.mail_alias.write(cr, uid, [alias_id], {'alias_user_id': False, 'alias_contact': 'followers'})
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other5@gmail.com')
|
||||
# Test: no group created
|
||||
self.assertTrue(len(frog_groups) == 0)
|
||||
# Test: email bounced
|
||||
sent_emails = self._build_email_kwargs_list
|
||||
self.assertEqual(len(sent_emails), 1,
|
||||
'message_process: incoming email on Partners alias should send a bounce email')
|
||||
|
||||
# Do: incoming email from a parent document follower on a Followers only alias -> ok
|
||||
self._init_mock_build_email()
|
||||
self.mail_group.message_subscribe(cr, uid, [self.group_pigs_id], [p1id])
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other6@gmail.com')
|
||||
# Test: one group created by Raoul (or Sylvie maybe, if we implement it)
|
||||
self.assertEqual(len(frog_groups), 1, 'message_process: a new mail.group should have been created')
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
|
@ -438,15 +404,63 @@ class TestMailgateway(TestMailBase):
|
|||
self.assertEqual(frog_follower_ids, set([p1id]),
|
||||
'message_process: newly created group should have 1 follower (author, not creator, not recipients)')
|
||||
# Test: sent emails: no-one, no bounce effet
|
||||
sent_emails = self._build_email_kwargs_list
|
||||
self.assertEqual(len(sent_emails), 0,
|
||||
'message_process: should not bounce incoming emails')
|
||||
|
||||
# --------------------------------------------------
|
||||
# Test2: discussion update
|
||||
# Test2: update-like alias
|
||||
# --------------------------------------------------
|
||||
|
||||
# Do: Pigs alias is restricted, should bounce
|
||||
self._init_mock_build_email()
|
||||
self.mail_group.write(cr, uid, [frog_group.id], {'alias_name': 'frogs', 'alias_contact': 'followers', 'alias_force_thread_id': frog_group.id})
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
|
||||
msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
|
||||
to='frogs@example.com>', subject='Re: news')
|
||||
# Test: no group 'Re: news' created, still only 1 Frogs group
|
||||
self.assertEqual(len(frog_groups), 0,
|
||||
'message_process: reply on Frogs should not have created a new group with new subject')
|
||||
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
||||
self.assertEqual(len(frog_groups), 1,
|
||||
'message_process: reply on Frogs should not have created a duplicate group with old subject')
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: email bounced
|
||||
sent_emails = self._build_email_kwargs_list
|
||||
self.assertEqual(len(sent_emails), 1,
|
||||
'message_process: incoming email on Followers alias should send a bounce email')
|
||||
self.assertIn('Re: news', sent_emails[0].get('subject'),
|
||||
'message_process: bounce email on Followers alias should contain the original subject')
|
||||
|
||||
# Do: Pigs alias is restricted, should accept Followers
|
||||
self._init_mock_build_email()
|
||||
self.mail_group.message_subscribe(cr, uid, [frog_group.id], [p2id])
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
|
||||
msg_id='<1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>',
|
||||
to='frogs@example.com>', subject='Re: cats')
|
||||
# Test: no group 'Re: news' created, still only 1 Frogs group
|
||||
self.assertEqual(len(frog_groups), 0,
|
||||
'message_process: reply on Frogs should not have created a new group with new subject')
|
||||
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
||||
self.assertEqual(len(frog_groups), 1,
|
||||
'message_process: reply on Frogs should not have created a duplicate group with old subject')
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: one new message
|
||||
self.assertEqual(len(frog_group.message_ids), 2, 'message_process: group should contain 2 messages after reply')
|
||||
# Test: sent emails: 1 (Sylvie copy of the incoming email, but no bounce)
|
||||
sent_emails = self._build_email_kwargs_list
|
||||
self.assertEqual(len(sent_emails), 1,
|
||||
'message_process: one email should have been generated')
|
||||
self.assertIn('test.sylvie.lelitre@agrolait.com', sent_emails[0].get('email_to')[0],
|
||||
'message_process: email should be sent to Sylvie')
|
||||
self.mail_group.message_unsubscribe(cr, uid, [frog_group.id], [p2id])
|
||||
|
||||
# --------------------------------------------------
|
||||
# Test3: discussion and replies
|
||||
# --------------------------------------------------
|
||||
|
||||
# Do: even with a wrong destination, a reply should end up in the correct thread
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other@gmail.com',
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
|
||||
msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
|
||||
to='erroneous@example.com>', subject='Re: news',
|
||||
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
|
||||
|
@ -458,14 +472,14 @@ class TestMailgateway(TestMailBase):
|
|||
'message_process: reply on Frogs should not have created a duplicate group with old subject')
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: one new message
|
||||
self.assertEqual(len(frog_group.message_ids), 2, 'message_process: group should contain 2 messages after reply')
|
||||
self.assertEqual(len(frog_group.message_ids), 3, 'message_process: group should contain 2 messages after reply')
|
||||
# Test: author (and not recipient) added as follower
|
||||
frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
|
||||
self.assertEqual(frog_follower_ids, set([p1id, p2id]),
|
||||
'message_process: after reply, group should have 2 followers')
|
||||
|
||||
# Do: due to some issue, same email goes back into the mailgateway
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other@gmail.com',
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
|
||||
msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
|
||||
subject='Re: news', extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
|
||||
# Test: no group 'Re: news' created, still only 1 Frogs group
|
||||
|
@ -476,20 +490,18 @@ class TestMailgateway(TestMailBase):
|
|||
'message_process: reply on Frogs should not have created a duplicate group with old subject')
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: no new message
|
||||
self.assertEqual(len(frog_group.message_ids), 2, 'message_process: message with already existing message_id should not have been duplicated')
|
||||
self.assertEqual(len(frog_group.message_ids), 3, 'message_process: message with already existing message_id should not have been duplicated')
|
||||
# Test: message_id is still unique
|
||||
msg_ids = self.mail_message.search(cr, uid, [('message_id', 'ilike', '<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>')])
|
||||
self.assertEqual(len(msg_ids), 1,
|
||||
'message_process: message with already existing message_id should not have been duplicated')
|
||||
|
||||
# --------------------------------------------------
|
||||
# Test3: email_from and partner finding
|
||||
# Test4: email_from and partner finding
|
||||
# --------------------------------------------------
|
||||
|
||||
# Data: extra partner with Raoul's email -> test the 'better author finding'
|
||||
extra_partner_id = self.res_partner.create(cr, uid, {'name': 'A-Raoul', 'email': 'test_raoul@email.com'})
|
||||
# extra_user_id = self.res_users.create(cr, uid, {'name': 'B-Raoul', 'email': self.user_raoul.email})
|
||||
# extra_user_pid = self.res_users.browse(cr, uid, extra_user_id).partner_id.id
|
||||
|
||||
# Do: post a new message, with a known partner -> duplicate emails -> partner
|
||||
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
|
||||
|
@ -534,7 +546,7 @@ class TestMailgateway(TestMailBase):
|
|||
self.res_users.write(cr, uid, self.user_raoul_id, {'email': raoul_email})
|
||||
|
||||
# --------------------------------------------------
|
||||
# Test4: misc gateway features
|
||||
# Test5: misc gateway features
|
||||
# --------------------------------------------------
|
||||
|
||||
# Do: incoming email with model that does not accepts incoming emails must raise
|
||||
|
@ -568,7 +580,7 @@ class TestMailgateway(TestMailBase):
|
|||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
msg = frog_group.message_ids[0]
|
||||
# Test: plain text content should be wrapped and stored as html
|
||||
self.assertEqual(msg.body, '<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>',
|
||||
self.assertIn('<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>', msg.body,
|
||||
'message_process: plaintext incoming email incorrectly parsed')
|
||||
|
||||
@mute_logger('openerp.addons.mail.mail_thread', 'openerp.osv.orm')
|
||||
|
|
|
@ -14,7 +14,7 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-06-24 04:43+0000\n"
|
||||
"X-Launchpad-Export-Date: 2013-06-25 05:14+0000\n"
|
||||
"X-Generator: Launchpad (build 16677)\n"
|
||||
|
||||
#. module: portal_anonymous
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2004-TODAY OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -18,5 +18,3 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import event
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue