[IMP] google oAuth2 + start synchro calendar (insert ok, bug with recurrent)
bzr revid: jke@openerp.com-20131202143133-u6vi1xw1oa9w8qpf
This commit is contained in:
parent
74d698f42f
commit
27a42cc6eb
|
@ -21,5 +21,6 @@
|
|||
|
||||
import base_calendar
|
||||
import controllers
|
||||
import res_config
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -43,7 +43,8 @@ If you need to manage your meetings, you should install the CRM module.
|
|||
'security/calendar_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'crm_meeting_view.xml',
|
||||
'base_calendar_data.xml'
|
||||
'base_calendar_data.xml',
|
||||
'res_config_view.xml',
|
||||
],
|
||||
'js': [
|
||||
'static/src/js/*.js'
|
||||
|
|
|
@ -1509,7 +1509,6 @@ class crm_meeting(osv.Model):
|
|||
self.message_post(cr, uid, event.id, body=_("An invitation email has been sent to attendee(s)"), subtype="base_calendar.subtype_invitation", context=context)
|
||||
return;
|
||||
|
||||
|
||||
def get_attendee(self, cr, uid, meeting_id, context=None):
|
||||
#Used for view in controller
|
||||
invitation = {'meeting':{}, 'attendee': [], 'logo': ''}
|
||||
|
@ -1650,13 +1649,11 @@ class crm_meeting(osv.Model):
|
|||
end_date = self._get_recurrency_end_date(data, context=context)
|
||||
super(crm_meeting, self).write(cr, uid, [data['id']], {'end_date': end_date}, context=context)
|
||||
|
||||
attendees_create = False
|
||||
|
||||
attendees_create = False
|
||||
if values.get('partner_ids', False):
|
||||
attendees_create = self.create_attendees(cr, uid, ids, context)
|
||||
|
||||
print values
|
||||
if values.get('date', False):
|
||||
|
||||
if values.get('date', False) and not context.get('install_mode',False): #Not send mail when install data
|
||||
print 'Id notified (ids|new_id) : ',ids,"|",new_id
|
||||
the_id = new_id or (ids and int(ids[0]));
|
||||
|
||||
|
@ -1799,7 +1796,8 @@ class crm_meeting(osv.Model):
|
|||
res = super(crm_meeting, self).unlink(cr, uid, ids, context=context)
|
||||
self.unlink_events(cr, uid, ids, context=context)
|
||||
return res
|
||||
|
||||
|
||||
|
||||
|
||||
# class mail_mail(osv.osv):
|
||||
# _inherit = "mail.mail"
|
||||
|
|
|
@ -17,35 +17,30 @@
|
|||
|
||||
<record model="calendar.alarm" id="alarm_notif_1">
|
||||
<field name="name">15 min notif</field>
|
||||
<field name="active" eval="1" />
|
||||
<field name="duration" eval="15" />
|
||||
<field name="interval">minutes</field>
|
||||
<field name="type">notification</field>
|
||||
<field name="type">notification</field>
|
||||
</record>
|
||||
<record model="calendar.alarm" id="alarm_notif_2">
|
||||
<field name="name">30 min notif</field>
|
||||
<field name="active" eval="1" />
|
||||
<field name="duration" eval="30" />
|
||||
<field name="interval">minutes</field>
|
||||
<field name="type">notification</field>
|
||||
</record>
|
||||
<record model="calendar.alarm" id="alarm_notif_3">
|
||||
<field name="name">1 hour notif</field>
|
||||
<field name="active" eval="1" />
|
||||
<field name="duration" eval="1" />
|
||||
<field name="interval">hours</field>
|
||||
<field name="type">notification</field>
|
||||
</record>
|
||||
<record model="calendar.alarm" id="alarm_notif_4">
|
||||
<field name="name">2 hours notif</field>
|
||||
<field name="active" eval="1" />
|
||||
<field name="duration" eval="2" />
|
||||
<field name="interval">hours</field>
|
||||
<field name="type">notification</field>
|
||||
</record>
|
||||
<record model="calendar.alarm" id="alarm_notif_5">
|
||||
<field name="name">1 day notif</field>
|
||||
<field name="active" eval="1" />
|
||||
<field name="duration" eval="1" />
|
||||
<field name="interval">days</field>
|
||||
<field name="type">notification</field>
|
||||
|
@ -54,35 +49,30 @@
|
|||
|
||||
<record model="calendar.alarm" id="alarm_mail_1">
|
||||
<field name="name">15 min mail</field>
|
||||
<field name="active" eval="1" />
|
||||
<field name="duration" eval="15" />
|
||||
<field name="interval">minutes</field>
|
||||
<field name="type">email</field>
|
||||
</record>
|
||||
<record model="calendar.alarm" id="alarm_mail_2">
|
||||
<field name="name">30 min mail</field>
|
||||
<field name="active" eval="1" />
|
||||
<field name="duration" eval="30" />
|
||||
<field name="interval">minutes</field>
|
||||
<field name="type">email</field>
|
||||
</record>
|
||||
<record model="calendar.alarm" id="alarm_mail_3">
|
||||
<field name="name">1 hour mail</field>
|
||||
<field name="active" eval="1" />
|
||||
<field name="duration" eval="1" />
|
||||
<field name="interval">hours</field>
|
||||
<field name="type">email</field>
|
||||
</record>
|
||||
<record model="calendar.alarm" id="alarm_mail_4">
|
||||
<field name="name">2 hours mail</field>
|
||||
<field name="active" eval="1" />
|
||||
<field name="duration" eval="2" />
|
||||
<field name="interval">hours</field>
|
||||
<field name="type">email</field>
|
||||
</record>
|
||||
<record model="calendar.alarm" id="alarm_mail_5">
|
||||
<field name="name">1 day mail</field>
|
||||
<field name="active" eval="1" />
|
||||
<field name="duration" eval="1" />
|
||||
<field name="interval">days</field>
|
||||
<field name="type">email</field>
|
||||
|
@ -349,10 +339,10 @@
|
|||
<body>
|
||||
<div style="border-radius: 2px; max-width: 1200px; height: auto;margin-left: auto;margin-right: auto;background-color:#f9f9f9;">
|
||||
<div style="height:auto;text-align: center;font-size : 30px;color: #8A89BA;">
|
||||
<strong>REMINDER : ${object.name}</strong>
|
||||
<strong>${object.name}</strong>
|
||||
</div>
|
||||
<div style="height: 50px;text-align: left;font-size : 14px;border-collapse: separate;margin-top:10px">
|
||||
<strong style="margin-left:12px">Hello ${ctx['att_obj'].cn}</strong> ,<br/><p style="margin-left:12px">${object.organizer} invited you for the ${object.name} meeting of ${object.user_id.company_id.name}.</p>
|
||||
<strong style="margin-left:12px">Hello ${ctx['att_obj'].cn}</strong> ,<br/><p style="margin-left:12px">${object.user_id.partner_id.name} invited you for the ${object.name} meeting of ${object.user_id.company_id.name}.</p>
|
||||
</div>
|
||||
<div style="height: auto;margin-left:12px;margin-top:30px;">
|
||||
<table>
|
||||
|
@ -367,7 +357,7 @@
|
|||
</td>
|
||||
<td>
|
||||
<table cellspacing="0" cellpadding="0" border="0" style="margin-top: 15px; margin-left: 10px;font-size: 16px;">
|
||||
% if object.location :
|
||||
% if object.location:
|
||||
<tr style=" height: 30px;">
|
||||
<td style="vertical-align:top;">
|
||||
<div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
|
@ -383,20 +373,6 @@
|
|||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
% if not object.location :
|
||||
<tr style=" height: 30px;color:#909090">
|
||||
<td>
|
||||
<div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
Where
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="1">
|
||||
<div style = "font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 14px;" >
|
||||
: -
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
% if object.description :
|
||||
<tr style=" height:auto;">
|
||||
<td style="vertical-align:top;">
|
||||
|
@ -410,21 +386,7 @@
|
|||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
% if not object.description :
|
||||
<tr style=" height: 30px;color:#909090">
|
||||
<td style="vertical-align:top;">
|
||||
<div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
What
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="3" style="vertical-align:text-top;">
|
||||
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
: -
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
% endif
|
||||
<tr style=" height: 30px;">
|
||||
<td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
<div>
|
||||
|
@ -448,6 +410,13 @@
|
|||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div style="height: auto;width:300px; margin:0 auto;padding-top:20px;">
|
||||
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#8A89BA;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/meeting_invitation/accept?db=${ctx['dbname']}&token=${ctx['att_obj'].access_token}&action=${ctx['action_id']}&id=${object.id}">Accept</a>
|
||||
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#808080;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/meeting_invitation/decline?db=${ctx['dbname']}&token=${ctx['att_obj'].access_token}&action=${ctx['action_id']}&id=${object.id}">Decline</a>
|
||||
</div>
|
||||
<div style="padding-top:10px;">
|
||||
-- </br> Sent by ${object.user_id.name} from ${object.user_id.company_id.name}. View this meeting detail <a href="${ctx['base_url']}/meeting_invitation/view?db=${ctx['dbname']}&token=${ctx['att_obj'].access_token}&action=${ctx['action_id']}&id=${object.id}">directly in OpenERP.</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<record id="crm_meeting_1" model="crm.meeting">
|
||||
<field eval="1" name="active"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="partner_ids" eval="[(6,0,[ref('base.partner_root')])]"/>
|
||||
<field name="name">Follow-up for Project proposal</field>
|
||||
<field name="description">Meeting to discuss project plan and hash out the details of implementation.</field>
|
||||
<field eval="time.strftime('%Y-%m-03 10:20:00')" name="date"/>
|
||||
|
@ -20,7 +21,7 @@
|
|||
|
||||
<record id="crm_meeting_2" model="crm.meeting">
|
||||
<field eval="1" name="active"/>
|
||||
<field name="partner_id" ref="base.partner_root"/>
|
||||
<field name="partner_ids" eval="[(6,0,[ref('base.partner_root')])]"/>
|
||||
<field name="name">Initial discussion</field>
|
||||
<field name="description">Discussion with partner for product.</field>
|
||||
<field name="categ_ids" eval="[(6,0,[ref('categ_meet3')])]"/>
|
||||
|
@ -32,7 +33,7 @@
|
|||
|
||||
<record id="crm_meeting_3" model="crm.meeting">
|
||||
<field eval="1" name="active"/>
|
||||
<field name="partner_id" ref="base.partner_root"/>
|
||||
<field name="partner_ids" eval="[(6,0,[ref('base.partner_root')])]"/>
|
||||
<field name="name">Pricing Discussion</field>
|
||||
<field name="description">Internal meeting for discussion for new pricing for product and services.</field>
|
||||
<field name="categ_ids" eval="[(6,0,[ref('categ_meet1'), ref('categ_meet2')])]"/>
|
||||
|
@ -44,7 +45,7 @@
|
|||
|
||||
<record id="crm_meeting_4" model="crm.meeting">
|
||||
<field eval="1" name="active"/>
|
||||
<field name="partner_id" ref="base.partner_root"/>
|
||||
<field name="partner_ids" eval="[(6,0,[ref('base.partner_root')])]"/>
|
||||
<field name="name">Requirements review</field>
|
||||
<field name="categ_ids" eval="[(6,0,[ref('categ_meet3')])]"/>
|
||||
<field eval="time.strftime('%Y-%m-20 8:00:00')" name="date"/>
|
||||
|
@ -55,7 +56,7 @@
|
|||
|
||||
<record id="crm_meeting_5" model="crm.meeting">
|
||||
<field eval="1" name="active"/>
|
||||
<field name="partner_id" ref="base.partner_root"/>
|
||||
<field name="partner_ids" eval="[(6,0,[ref('base.partner_root')])]"/>
|
||||
<field name="name">Changes in Designing</field>
|
||||
<field name="categ_ids" eval="[(6,0,[ref('categ_meet1')])]"/>
|
||||
<field eval="time.strftime('%Y-%m-22 11:05:00')" name="date"/>
|
||||
|
@ -66,7 +67,7 @@
|
|||
|
||||
<record id="crm_meeting_6" model="crm.meeting">
|
||||
<field eval="1" name="active"/>
|
||||
<field name="partner_id" ref="base.partner_root"/>
|
||||
<field name="partner_ids" eval="[(6,0,[ref('base.partner_root')])]"/>
|
||||
<field name="name">Presentation for new Services</field>
|
||||
<field name="categ_ids" eval="[(6,0,[ref('categ_meet1'), ref('categ_meet2')])]"/>
|
||||
<field eval="time.strftime('%Y-%m-18 2:00:00')" name="date"/>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<record model="ir.ui.view" id="view_crm_meeting_form">
|
||||
<field name="name">CRM - Meetings Form</field>
|
||||
<field name="model">crm.meeting</field>
|
||||
<field name="sequence">1</field>
|
||||
<field name="priority" eval="1"/>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Meetings" version="7.0">
|
||||
<sheet>
|
||||
|
@ -158,7 +158,7 @@
|
|||
<record model="ir.ui.view" id="view_crm_meeting_form_popup">
|
||||
<field name="name">Meetings Popup</field>
|
||||
<field name="model">crm.meeting</field>
|
||||
<field name="sequence">5</field>
|
||||
<field name="priority" eval="2"/>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Meetings" version="7.0">
|
||||
<field name="state" invisible="1"/>
|
||||
|
@ -310,7 +310,7 @@
|
|||
<field name="view_mode">form,calendar,tree,gantt</field>
|
||||
<field name="view_id" ref="action_view_crm_meeting_form"/>
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import google_base_account
|
||||
import controllers
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
import main
|
|
@ -0,0 +1,119 @@
|
|||
import simplejson
|
||||
import urllib
|
||||
import openerp
|
||||
import openerp.addons.web.http as http
|
||||
from openerp.addons.web.http import request
|
||||
import openerp.addons.web.controllers.main as webmain
|
||||
import json
|
||||
from openerp.addons.web.http import SessionExpiredException
|
||||
from werkzeug.exceptions import BadRequest
|
||||
import werkzeug.utils
|
||||
|
||||
class google_auth(http.Controller):
|
||||
|
||||
@http.route('/googleauth/oauth2callback', type='http', auth="none")
|
||||
def oauth2callback(self, **kw):
|
||||
|
||||
state = simplejson.loads(kw['state'])
|
||||
|
||||
#action = state.get('a')
|
||||
#menu = state.get('m')
|
||||
dbname = state.get('d')
|
||||
#service = state.get('s')
|
||||
url_return = state.get('from')
|
||||
|
||||
registry = openerp.modules.registry.RegistryManager.get(dbname)
|
||||
with registry.cursor() as cr:
|
||||
#TODO CHECK IF REQUEST OK
|
||||
registry.get('google.calendar').set_all_tokens(cr,request.session.uid,kw['code'])
|
||||
registry.get('google.calendar').set_primary_id(cr,request.session.uid)
|
||||
|
||||
return werkzeug.utils.redirect(url_return)
|
||||
|
||||
#@openerp.addons.web.http.route('/web_calendar_sync/sync_calendar/sync_data', type='json', auth='user')
|
||||
@http.route('/web_calendar_sync/sync_calendar/sync_data', type='json', auth='user')
|
||||
def sync_data(self, arch, fields, model,**kw):
|
||||
calendar_info = {
|
||||
'field_data':{},
|
||||
'date_start':arch['attrs'].get('date_start'),
|
||||
'date_stop':arch['attrs'].get('date_stop'),
|
||||
'calendar_string':arch['attrs'].get('string'),
|
||||
'model':model
|
||||
}
|
||||
for field, data in fields.items():
|
||||
calendar_info['field_data'][field] = {
|
||||
'type': data.get('type'),
|
||||
'string': data.get('string')
|
||||
}
|
||||
|
||||
if model == 'crm.meeting':
|
||||
model_obj = request.registry.get('crm.meeting.synchronize')
|
||||
gc_obj = request.registry.get('google.calendar')
|
||||
|
||||
#We check that user has already accepted openerp to acces his calendar !
|
||||
if not gc_obj.get_refresh_token(request.cr, request.uid,context=kw.get('LocalContext')):
|
||||
url = gc_obj.authorize_google_uri(request.cr, request.uid, from_url=kw.get('fromurl'),context=kw.get('LocalContext'))
|
||||
return {
|
||||
"status" : "NeedAuth",
|
||||
"url" : url
|
||||
}
|
||||
|
||||
#We lunch th synchronization
|
||||
print "ORI COONTEXT = ",kw.get('LocalContext')
|
||||
model_obj.synchronize_events(request.cr, request.uid, [], kw.get('LocalContext'))
|
||||
else:
|
||||
model_obj = request.registry.get('google.calendar')
|
||||
model_obj.synchronize_calendar(request.cr, request.uid, calendar_info, kw.get('LocalContext'))
|
||||
|
||||
|
||||
return { "status" : "SUCCESS" }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@http.route('/googleauth/AuthorizeMe', type='http', auth="none")
|
||||
def authorize_app(self,**val):
|
||||
if val.get('done'):
|
||||
return;
|
||||
registry = openerp.modules.registry.RegistryManager.get(request.session.get('db'))
|
||||
gs_pool = registry.get('google.service')
|
||||
with registry.cursor() as cr:
|
||||
url = gs_pool._get_authorize_uri(cr,request.session.uid,service='calendar',from_url='')
|
||||
return werkzeug.utils.redirect(url) ##REDIRECT WHERE THE USER WAS BEFORE (with state)
|
||||
|
||||
|
||||
@http.route('/googleauth/GiveMeAToken', type='http', auth="none")
|
||||
def authorize_me(self,**val):
|
||||
registry = openerp.modules.registry.RegistryManager.get(request.session.get('db'))
|
||||
gs_pool = registry.get('google.service')
|
||||
with registry.cursor() as cr:
|
||||
token = gs_pool._get_google_token_json(cr, request.session.uid, 'api_code')
|
||||
|
||||
print '#####################################'
|
||||
print '## YOUR TOKEN : ',token, " ##"
|
||||
print '#####################################'
|
||||
#return werkzeug.utils.redirect(url)
|
||||
return
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
from openerp.osv import osv
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.tools.translate import _
|
||||
from openerp.addons.web.http import request
|
||||
|
||||
import urllib
|
||||
import urllib2
|
||||
|
@ -57,9 +58,135 @@ class google_service(osv.osv_memory):
|
|||
'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),
|
||||
'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:
|
||||
|
||||
#If no scope is passed, we use service by default to get a default scope
|
||||
|
||||
def _get_authorize_uri(self, cr, uid, from_url, service, scope = False, context=None): #authorize_API
|
||||
if not service:
|
||||
print 'please specify a service (Calendar for example)!'
|
||||
|
||||
state_obj = {}
|
||||
state_obj['d'] = cr.dbname
|
||||
state_obj['a'] = request and request.params.get('action') or False
|
||||
state_obj['m'] = request and request.params.get('menu_id') or False
|
||||
state_obj['s'] = service
|
||||
state_obj['from'] = from_url
|
||||
|
||||
base_url = self.get_base_url(cr, uid, context)
|
||||
client_id = self.get_client_id(cr, uid, service, context)
|
||||
|
||||
params = {
|
||||
'response_type': 'code',
|
||||
'client_id': client_id,
|
||||
'state' : simplejson.dumps(state_obj),
|
||||
'scope': scope or 'https://www.googleapis.com/auth/%s' % (service,),
|
||||
'redirect_uri': base_url + '/googleauth/oauth2callback',
|
||||
'approval_prompt':'force',
|
||||
'access_type':'offline'
|
||||
}
|
||||
|
||||
uri = self.get_uri_oauth(a='auth') + "?%s" % urllib.urlencode(params)
|
||||
return uri
|
||||
|
||||
def _get_google_token_json(self, cr, uid, authorize_code, service, context=None): #exchange_AUTHORIZATION vs Token (service = calendar)
|
||||
res = False
|
||||
base_url = self.get_base_url(cr, uid, context)
|
||||
client_id = self.get_client_id(cr, uid, service, context)
|
||||
client_secret = self.get_client_secret(cr, uid, service, context)
|
||||
|
||||
params = {
|
||||
'code': authorize_code,
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret,
|
||||
'grant_type' : 'authorization_code',
|
||||
'redirect_uri': base_url + '/googleauth/oauth2callback'
|
||||
}
|
||||
|
||||
headers = {"content-type": "application/x-www-form-urlencoded"}
|
||||
|
||||
try:
|
||||
data = urllib.urlencode(params)
|
||||
req = urllib2.Request(self.get_uri_oauth(a='token'), data, headers)
|
||||
content = urllib2.urlopen(req).read()
|
||||
res = simplejson.loads(content)
|
||||
|
||||
except urllib2.HTTPError,e:
|
||||
print e
|
||||
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)
|
||||
|
||||
return res
|
||||
|
||||
def _refresh_google_token_json(self, cr, uid, refresh_token, service, context=None): #exchange_AUTHORIZATION vs Token (service = calendar)
|
||||
res = False
|
||||
base_url = self.get_base_url(cr, uid, context)
|
||||
client_id = self.get_client_id(cr, uid, service, context)
|
||||
client_secret = self.get_client_secret(cr, uid, service, context)
|
||||
|
||||
params = {
|
||||
'refresh_token': refresh_token,
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret,
|
||||
'grant_type' : 'refresh_token'
|
||||
}
|
||||
|
||||
headers = {"content-type": "application/x-www-form-urlencoded"}
|
||||
|
||||
try:
|
||||
data = urllib.urlencode(params)
|
||||
req = urllib2.Request(self.get_uri_oauth(a='token'), data, headers)
|
||||
content = urllib2.urlopen(req).read()
|
||||
res = simplejson.loads(content)
|
||||
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)
|
||||
|
||||
return res
|
||||
|
||||
def _do_request(self,cr,uid,uri,params={},headers={},type='POST', context=None):
|
||||
res = False
|
||||
|
||||
print "#########################################"
|
||||
print "### URI : %s ###" % (uri)
|
||||
print "### HEADERS : %s ###" % (headers)
|
||||
print "### METHOD : %s ###" % (type)
|
||||
if type=='GET':
|
||||
print "### PARAMS : %s ###" % urllib.urlencode(params)
|
||||
else:
|
||||
print "### PARAMS : %s ###" % (params)
|
||||
|
||||
print "#########################################"
|
||||
|
||||
try:
|
||||
if type.upper() == 'GET':
|
||||
data = urllib.urlencode(params)
|
||||
req = urllib2.Request(self.get_uri_api() + uri + "?" + data)#,headers)
|
||||
elif type.upper() == 'POST':
|
||||
req = urllib2.Request(self.get_uri_api() + uri, params, headers)
|
||||
else:
|
||||
raise ('Method not supported [GET or POST] not in [%s]!' % (type))
|
||||
|
||||
content = urllib2.urlopen(req).read()
|
||||
res = simplejson.loads(content)
|
||||
except urllib2.HTTPError,e:
|
||||
print e.read()
|
||||
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)
|
||||
|
||||
return res
|
||||
|
||||
def get_base_url(self, cr, uid, context=None):
|
||||
return self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url',default='http://www.openerp.com?NoBaseUrl',context=context)
|
||||
|
||||
def get_client_id(self, cr, uid, service, context=None):
|
||||
return self.pool.get('ir.config_parameter').get_param(cr, uid, 'google_%s_client_id' % (service,),context=context)
|
||||
|
||||
def get_client_secret(self, cr, uid, service, context=None):
|
||||
return self.pool.get('ir.config_parameter').get_param(cr, uid, 'google_%s_client_secret' % (service,),context=context)
|
||||
|
||||
def get_uri_oauth(self,a=''): #a = optionnal action
|
||||
return "https://accounts.google.com/o/oauth2/%s" % (a,)
|
||||
|
||||
def get_uri_api(self):
|
||||
return 'https://www.googleapis.com'
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
import google_calendar
|
||||
import wizard
|
Binary file not shown.
|
@ -0,0 +1,45 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
|
||||
{
|
||||
'name': 'Google Calendar',
|
||||
'version': '1.0',
|
||||
'category': 'Tools',
|
||||
'description': """
|
||||
The module adds the possibility to synchronize Google Calendar with OpenERP
|
||||
========================================
|
||||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'website': 'http://www.openerp.com',
|
||||
'depends': ['base_calendar'],
|
||||
'js': ['static/src/js/*.js'],
|
||||
'qweb': ['static/src/xml/*.xml'],
|
||||
'data': [
|
||||
'google_calendar_view.xml',
|
||||
'google_calendar_data.xml',
|
||||
'wizard/crm_meeting_view.xml',
|
||||
],
|
||||
'demo': [],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
##############################################################################
|
||||
#
|
||||
# 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 simplejson
|
||||
import re
|
||||
import urllib
|
||||
import urllib2
|
||||
|
||||
|
||||
from openerp import tools
|
||||
from openerp.tools.translate import _
|
||||
|
||||
from openerp.addons.web.http import request
|
||||
import werkzeug.utils
|
||||
|
||||
from datetime import datetime, timedelta, date
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.osv import osv
|
||||
|
||||
|
||||
google_state_mapping = {
|
||||
'needs-action':'needsAction',
|
||||
'declined': 'declined',
|
||||
'tentative':'tentative',
|
||||
'accepted':'accepted',
|
||||
'delegated':'declined',
|
||||
}
|
||||
oe_state_mapping = {
|
||||
'needsAction':'needs-action',
|
||||
'declined': 'declined',
|
||||
'tentative':'tentative',
|
||||
'accepted':'accepted',
|
||||
}
|
||||
|
||||
class google_calendar(osv.osv):
|
||||
_name = 'google.calendar'
|
||||
|
||||
STR_SERVICE = 'calendar'
|
||||
|
||||
def authorize_google_uri(self,cr,uid,from_url='http://www.openerp.com',context=None):
|
||||
url = self.pool.get('google.service')._get_authorize_uri(cr,uid,from_url,self.STR_SERVICE,scope=self.get_calendar_scope(),context=context)
|
||||
return url
|
||||
|
||||
def set_all_tokens(self,cr,uid,authorization_code,context=None):
|
||||
gs_pool = self.pool.get('google.service')
|
||||
all_token = gs_pool._get_google_token_json(cr, uid, authorization_code,self.STR_SERVICE,context=context)
|
||||
|
||||
vals = {}
|
||||
vals['google_%s_rtoken' % self.STR_SERVICE] = all_token.get('refresh_token')
|
||||
vals['google_%s_token_validity' % self.STR_SERVICE] = datetime.now() + timedelta(seconds=all_token.get('expires_in')) #NEED A CALCUL
|
||||
vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token')
|
||||
self.pool.get('res.users').write(cr,uid,uid,vals,context=context)
|
||||
|
||||
def set_primary_id(self,cr,uid,context=None):
|
||||
gs_pool = self.pool.get('google.service')
|
||||
|
||||
params = {
|
||||
'fields': 'id',
|
||||
'access_token': self.get_token(cr, uid, context=context)
|
||||
}
|
||||
|
||||
cal = gs_pool._do_request(cr, uid, "/calendar/v3/calendars/primary/", params, type='GET', context=context)
|
||||
|
||||
if cal.get('id',False):
|
||||
vals = {}
|
||||
vals['google_calendar_id']= cal.get('id')
|
||||
self.pool.get('res.users').write(cr,uid,uid,vals,context=context)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def generate_data(self, cr, uid, event, context=None):
|
||||
if event.allday:
|
||||
start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(hours=0), context=context).isoformat('T').split('T')[0]
|
||||
end_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(hours=24), context=context).isoformat('T').split('T')[0]
|
||||
type = 'date'
|
||||
else:
|
||||
start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
|
||||
end_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date_deadline, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
|
||||
type = 'dateTime'
|
||||
attendee_list = []
|
||||
|
||||
for attendee in event.attendee_ids:
|
||||
attendee_list.append({
|
||||
'email':attendee.email or 'NoEmail@mail.com',
|
||||
'displayName':attendee.partner_id.name,
|
||||
'responseStatus':google_state_mapping.get(attendee.state, 'needsAction'),
|
||||
})
|
||||
data = {
|
||||
"summary": event.name or '',
|
||||
"description": event.description or '',
|
||||
"start":{
|
||||
type:start_date,
|
||||
#'timeZone':context.get('tz') or 'UTC'
|
||||
},
|
||||
"end":{
|
||||
type:end_date,
|
||||
#'timeZone':context.get('tz') or 'UTC'
|
||||
},
|
||||
"attendees":attendee_list,
|
||||
"location":event.location or '',
|
||||
"visibility":event['class'] or 'public',
|
||||
}
|
||||
if event.recurrency and event.rrule:
|
||||
data["recurrence"]= []
|
||||
if event.exdate:
|
||||
data["recurrence"].append("EXDATE:"+event.exdate)
|
||||
|
||||
data["recurrence"]+=["RRULE:"+event.rrule]
|
||||
|
||||
|
||||
#if not recurrency and event.recurrent_id and event.recurrent_id != 0: ###" IMMUTABLE
|
||||
# data["recurringEventId"] = event.recurrent_id
|
||||
|
||||
print data
|
||||
return data
|
||||
|
||||
def create_event(self, cr, uid,event, context=None):
|
||||
gs_pool = self.pool.get('google.service')
|
||||
|
||||
print "CONTEXT : ",context
|
||||
data = self.generate_data(cr, uid,event, context=context)
|
||||
|
||||
|
||||
url = "/calendar/v3/calendars/%s/events?fields=%s&access_token=%s" % ('primary',urllib.quote('id,updated'),self.get_token(cr,uid,context))
|
||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
||||
data_json = simplejson.dumps(data)
|
||||
|
||||
response = gs_pool._do_request(cr, uid, url, data_json, headers, type='POST', context=context)
|
||||
#TODO Check response result
|
||||
|
||||
return response
|
||||
|
||||
def get_token(self,cr,uid,context=None):
|
||||
current_user = self.pool.get('res.users').browse(cr,uid,uid,context=context)
|
||||
if datetime.strptime(current_user.google_calendar_token_validity.split('.')[0], "%Y-%m-%d %H:%M:%S") < datetime.now() - timedelta(minutes=5):
|
||||
print "NEED TO RENEW TOKEN",current_user.google_calendar_token_validity , "<", datetime.now() - timedelta(minutes=5)
|
||||
self.do_refresh_token(cr,uid,context=context)
|
||||
current_user.refresh()
|
||||
else:
|
||||
print "KEEP OLD TOKEN",current_user.google_calendar_token_validity , "<", datetime.now() - timedelta(minutes=5)
|
||||
return current_user.google_calendar_token
|
||||
|
||||
def do_refresh_token(self,cr,uid,context=None):
|
||||
current_user = self.pool.get('res.users').browse(cr,uid,uid,context=context)
|
||||
gs_pool = self.pool.get('google.service')
|
||||
|
||||
refresh = current_user.google_calendar_rtoken
|
||||
all_token = gs_pool._refresh_google_token_json(cr, uid, current_user.google_calendar_rtoken,self.STR_SERVICE,context=context)
|
||||
|
||||
vals = {}
|
||||
vals['google_%s_token_validity' % self.STR_SERVICE] = datetime.now() + timedelta(seconds=all_token.get('expires_in'))
|
||||
vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token')
|
||||
|
||||
self.pool.get('res.users').write(cr,uid,uid,vals,context=context)
|
||||
|
||||
|
||||
def get_refresh_token(self,cr,uid,context=None):
|
||||
current_user = self.pool.get('res.users').browse(cr,uid,uid,context=context)
|
||||
return current_user.google_calendar_rtoken
|
||||
|
||||
def get_calendar_scope(self,RO=False):
|
||||
readonly = RO and '.readonly' or ''
|
||||
return 'https://www.googleapis.com/auth/calendar%s' % (readonly)
|
||||
|
||||
class res_users(osv.osv):
|
||||
_inherit = 'res.users'
|
||||
|
||||
_columns = {
|
||||
'google_calendar_id': fields.char('Primary Calendar ID'),
|
||||
'google_calendar_rtoken': fields.char('Refresh Token'),
|
||||
'google_calendar_token': fields.char('User token'),
|
||||
'google_calendar_token_validity': fields.datetime('Token Validity'),
|
||||
}
|
||||
|
||||
def get_cal_token_info(self, cr, uid, partner_id=False, context=None):
|
||||
if partner_id:
|
||||
user = self.pool.get('res.partner').browse(cr,uid,partner_id,context=context).user_id
|
||||
else:
|
||||
user = self.pool.get('res.users').browse(cr,uid,uid,context=context)
|
||||
|
||||
return dict(authcode=user.google_cal_authcode, token=user.google_cal_token, validity=user.google_cal_token_validity,)
|
||||
|
||||
def update_cal_token(self,cr,uid,jsontoken,context=None):
|
||||
print jsontoken
|
||||
import ipdb; ipdb.set_trace();
|
||||
self.write(cr,uid,uid,{'xxx' : datetime.now() } ,context=context)
|
||||
return "OK"
|
||||
|
||||
|
||||
|
||||
class crm_meeting(osv.osv):
|
||||
_inherit = "crm.meeting"
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
sync_fields = set(['name', 'description', 'date', 'date_closed', 'date_deadline', 'attendee_ids', 'location', 'class'])
|
||||
if (set(vals.keys()) & sync_fields) and 'oe_update_date' not in vals.keys():
|
||||
vals['oe_update_date'] = datetime.now()
|
||||
return super(crm_meeting, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
default = default or {}
|
||||
default['attendee_ids'] = False
|
||||
if default.get('write_type', False):
|
||||
del default['write_type']
|
||||
elif default.get('recurrent_id', False):
|
||||
default['oe_update_date'] = datetime.now()
|
||||
default['google_internal_event_id'] = False
|
||||
else:
|
||||
default['google_internal_event_id'] = False
|
||||
default['oe_update_date'] = False
|
||||
return super(crm_meeting, self).copy(cr, uid, id, default, context)
|
||||
|
||||
_columns = {
|
||||
'google_internal_event_id': fields.char('Google Calendar Event Id', size=124),
|
||||
'oe_update_date': fields.datetime('OpenERP Update Date'),
|
||||
}
|
||||
|
||||
# If attendees are updated, we need to specify that next synchro need an action
|
||||
class calendar_attendee(osv.osv):
|
||||
_inherit = 'calendar.attendee'
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
if isinstance(ids, list):
|
||||
cr.execute("SELECT crmmeeting_id FROM crmmeeting_attendee_rel WHERE attendee_id = %s"%(ids[0]))
|
||||
else:
|
||||
cr.execute("SELECT crmmeeting_id FROM crmmeeting_attendee_rel WHERE attendee_id = %s"%(ids))
|
||||
event_id = cr.fetchone()[0]
|
||||
if event_id:
|
||||
self.pool.get('crm.meeting').write(cr, uid, event_id, {'oe_update_date':datetime.now()})
|
||||
return super(calendar_attendee, self).write(cr, uid, ids, vals, context=context)
|
||||
|
Binary file not shown.
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<!--
|
||||
<record id="google_calendar_template" model="google.drive.config">
|
||||
<field name="name">Base Spreadsheet Template</field>
|
||||
<field name="model_id" ref="base.model_res_partner"/>
|
||||
<field name="google_drive_template_url">https://docs.google.com/spreadsheet/ccc?key=0ApGVjjwUC-ygdDZ0TG5EQnRlLVFQNlFGdFN5b1ZrY1E</field>
|
||||
<field name="name_template">Reporting %(name)s</field>
|
||||
<field name="active" eval="0" />
|
||||
</record>
|
||||
-->
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- add google drive config field in user form -->
|
||||
|
||||
<record model="ir.ui.view" id="view_ir_attachment_google_spreadsheet_tree">
|
||||
<field name="name">ir.attachment.google.spreadsheet.tree</field>
|
||||
<field name="model">ir.attachment</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Google Spreadsheets" version="7.0">
|
||||
<field name="name" string="Name"/>
|
||||
<field name="url" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_ir_attachment_google_spreadsheet_form">
|
||||
<field name="name">ir.attachment.google.spreadsheet.form</field>
|
||||
<field name="model">ir.attachment</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Google Spreadsheets" version="7.0">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="name" string="Name"/>
|
||||
<field name="url" widget="url"/>
|
||||
</group>
|
||||
<group colspan="2">
|
||||
<label for="description" colspan="2"/>
|
||||
<field name="description" nolabel="1" colspan="2"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_ir_attachment_google_spreadsheet_tree" model="ir.actions.act_window">
|
||||
<field name="name">Google Spreadsheets</field>
|
||||
<field name="res_model">ir.attachment</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="domain">[('url', 'like', '/spreadsheet/')]</field>
|
||||
<field name="help">Google Spreadsheets</field>
|
||||
</record>
|
||||
|
||||
<record id="action_ir_attachment_google_spreadsheet_tree_view" model="ir.actions.act_window.view">
|
||||
<field eval="1" name="sequence"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="view_ir_attachment_google_spreadsheet_tree"/>
|
||||
<field name="act_window_id" ref="action_ir_attachment_google_spreadsheet_tree"/>
|
||||
</record>
|
||||
|
||||
<record id="action_ir_attachment_google_spreadsheet_form_view" model="ir.actions.act_window.view">
|
||||
<field eval="2" name="sequence"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="view_ir_attachment_google_spreadsheet_form"/>
|
||||
<field name="act_window_id" ref="action_ir_attachment_google_spreadsheet_tree"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_reporting_dashboard_google_spreadsheets" parent="base.menu_reporting_dashboard" action="action_ir_attachment_google_spreadsheet_tree"/>
|
||||
</data>
|
||||
</openerp>
|
Binary file not shown.
After Width: | Height: | Size: 191 B |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,40 @@
|
|||
/*---------------------------------------------------------
|
||||
* OpenERP web_calendar
|
||||
*---------------------------------------------------------*/
|
||||
|
||||
openerp.google_calendar = function(instance) {
|
||||
var _t = instance.web._t,
|
||||
_lt = instance.web._lt;
|
||||
var QWeb = instance.web.qweb;
|
||||
|
||||
instance.web_calendar.FullCalendarView.include({
|
||||
view_loading: function(r) {
|
||||
var self = this;
|
||||
this.$el.on('click', 'div.oe_dhx_cal_sync_button', function() {
|
||||
self.sync_calendar(r);
|
||||
});
|
||||
return this._super(r);
|
||||
},
|
||||
sync_calendar: function(res) {
|
||||
var self = this;
|
||||
var context = instance.web.pyeval.eval('context');
|
||||
console.log("Model is..." + res.model);
|
||||
|
||||
self.rpc('/web_calendar_sync/sync_calendar/sync_data', {
|
||||
arch: res.arch,
|
||||
fields: res.fields,
|
||||
model:res.model,
|
||||
fromurl: window.location.href,
|
||||
LocalContext:context
|
||||
}).always(function(o) {
|
||||
if (o.status == "NeedAuth") {
|
||||
alert(_t("You will be redirected on gmail to authorize your OpenErp to access your calendar !"));
|
||||
window.location = o.url;
|
||||
}
|
||||
console.log(o);
|
||||
//self.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<t t-extend="CalendarView.sidebar">
|
||||
<t t-jquery="div.oe_calendar_mini" t-operation="after">
|
||||
|
||||
<div id="sync" class="oe_dhx_cal_sync_button dhx_cal_tab " >
|
||||
<br/>
|
||||
<center>
|
||||
<button class="oe_button">
|
||||
<span>
|
||||
<img src="/google_calendar/static/src/img/calendar_32.png"/> Sync with <b>Google</b>
|
||||
</span>
|
||||
</button>
|
||||
</center>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
|
@ -0,0 +1 @@
|
|||
import crm_meeting
|
|
@ -0,0 +1,269 @@
|
|||
# -*- 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, fields
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp import tools
|
||||
from openerp.tools.translate import _
|
||||
from datetime import datetime
|
||||
from dateutil import parser
|
||||
|
||||
|
||||
import pytz
|
||||
import urllib
|
||||
import urllib2
|
||||
import json
|
||||
import werkzeug.utils
|
||||
|
||||
class crm_meeting_synchronize(osv.osv_memory):
|
||||
_name = 'crm.meeting.synchronize'
|
||||
|
||||
|
||||
def synchronize_events(self, cr, uid, ids, context=None):
|
||||
gc_obj = self.pool.get('google.calendar')
|
||||
|
||||
self.create_new_events(cr, uid, context=context)
|
||||
#self.bind_recurring_events_to_google(cr, uid, context)
|
||||
#self.update_events(cr, uid, access_token, context)
|
||||
return True
|
||||
#
|
||||
# def generate_data(self, cr, uid, event, context):
|
||||
# if event.allday:
|
||||
# start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T').split('T')[0]
|
||||
# end_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T').split('T')[0]
|
||||
# type = 'date'
|
||||
# else:
|
||||
# start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
|
||||
# end_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date_deadline, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
|
||||
# type = 'dateTime'
|
||||
# attendee_list = []
|
||||
# for attendee in event.attendee_ids:
|
||||
# attendee_list.append({
|
||||
# 'email':attendee.email,
|
||||
# 'displayName':attendee.partner_id.name,
|
||||
# 'responseStatus':google_state_mapping.get(attendee.state, 'needsAction'),
|
||||
# })
|
||||
# data = {
|
||||
# "summary": event.name or '',
|
||||
# "description": event.description or '',
|
||||
# "start":{
|
||||
# type:start_date,
|
||||
# 'timeZone':context.get('tz')
|
||||
# },
|
||||
# "end":{
|
||||
# type:end_date,
|
||||
# 'timeZone':context.get('tz')
|
||||
# },
|
||||
# "attendees":attendee_list,
|
||||
# "colorId":4,
|
||||
# "location":event.location or '',
|
||||
# "visibility":event['class'] or 'public',
|
||||
# }
|
||||
# if event.rrule:
|
||||
# data["recurrence"]=["RRULE:"+event.rrule]
|
||||
# return data
|
||||
#
|
||||
def create_new_events(self, cr, uid, context):
|
||||
gc_pool = self.pool.get('google.calendar')
|
||||
|
||||
crm_meeting = self.pool['crm.meeting']
|
||||
user_obj = self.pool['res.users']
|
||||
|
||||
print "TO CHECK RESULT HERE >>>>>>>>>>>>>>>>>>>>"
|
||||
|
||||
|
||||
|
||||
|
||||
context_norecurrent = context.copy()
|
||||
context_norecurrent['virtual_id'] = False
|
||||
|
||||
new_events_ids = crm_meeting.search(cr, uid,[('partner_ids', 'in', user_obj.browse(cr,uid,uid,context=context).partner_id.id),('google_internal_event_id', '=', False)], context=context_norecurrent)
|
||||
#new_events_ids = [str(i).split('-')[0] for i in new_events_ids]
|
||||
#new_events_ids = base_calendar_id2real_id(new_events_ids,False); #[str(i).split('-')[0] for i in new_events_ids]
|
||||
|
||||
print 'Events ids [new_events_ids] : ', new_events_ids
|
||||
|
||||
for event in crm_meeting.browse(cr, uid, list(set(new_events_ids)), context):
|
||||
response = gc_pool.create_event(cr,uid,event,context=context)
|
||||
update_date = datetime.strptime(response['updated'],"%Y-%m-%dT%H:%M:%S.%fz")
|
||||
crm_meeting.write(cr, uid, event.id, {'google_internal_event_id': response['id'], 'oe_update_date':update_date})
|
||||
|
||||
#Check that response OK and return according to that
|
||||
|
||||
return True
|
||||
#
|
||||
# def bind_recurring_events_to_google(self, cr, uid, context):
|
||||
# crm_meeting = self.pool['crm.meeting']
|
||||
# new_events_ids = crm_meeting.search(cr, uid,[('user_id', '=', uid),('google_internal_event_id', '=', False),('recurrent_id', '>', 0)], context)
|
||||
# new_events_ids = base_calendar_id2real_id(new_events_ids,False); #[str(i).split('-')[0] for i in new_events_ids]
|
||||
#
|
||||
# for event in crm_meeting.browse(cr, uid, list(set(new_events_ids)), context):
|
||||
# source_record = crm_meeting.read(cr, uid ,event.recurrent_id,['allday', 'google_internal_event_id'],context)
|
||||
# new_google_internal_event_id = False
|
||||
# if event.recurrent_id_date and source_record.get('allday') and source_record.get('google_internal_event_id'):
|
||||
# new_google_internal_event_id = source_record.get('google_internal_event_id') +'_'+ event.recurrent_id_date.split(' ')[0].replace('-','') + 'Z'
|
||||
# elif event.recurrent_id_date and source_record.get('google_internal_event_id'):
|
||||
# new_google_internal_event_id = source_record.get('google_internal_event_id') +'_'+ event.recurrent_id_date.replace('-','').replace(' ','T').replace(':','') + 'Z'
|
||||
# if new_google_internal_event_id:
|
||||
# crm_meeting.write(cr, uid, [event.id], {'google_internal_event_id': new_google_internal_event_id})
|
||||
|
||||
def get_event_dict(self,access_token, nextPageToken):
|
||||
request_url = "https://www.googleapis.com/calendar/v3/calendars/%s/events?fields=%s&access_token=%s" % ('primary',urllib.quote('items,nextPageToken') ,access_token)
|
||||
if nextPageToken:
|
||||
request_url += "&pageToken=%s" %(nextPageToken)
|
||||
try:
|
||||
req = urllib2.Request(request_url)
|
||||
content = urllib2.urlopen(req).read()
|
||||
except urllib2.HTTPError,e:
|
||||
error_message = e.read()
|
||||
print error_message
|
||||
content = json.loads(content)
|
||||
google_events_dict = {}
|
||||
for google_event in content['items']:
|
||||
if google_event.get('updated',False):
|
||||
google_events_dict[google_event['id']] = google_event
|
||||
if content.get('nextPageToken', False):
|
||||
google_events_dict.update(self.get_event_dict(access_token,content['nextPageToken']))
|
||||
return google_events_dict
|
||||
|
||||
def update_events(self, cr, uid, access_token, context):
|
||||
crm_meeting = self.pool['crm.meeting']
|
||||
google_event_dict = self.get_event_dict(access_token, False)
|
||||
updated_events_ids = crm_meeting.search(cr, uid,[('partner_ids', 'in', user_obj.browse(cr,uid,uid,context=context)),('google_internal_event_id', '!=', False),('oe_update_date','!=', False)], context)
|
||||
for event in crm_meeting.browse(cr, uid, list(set(updated_events_ids)), context):
|
||||
if event.google_internal_event_id in google_event_dict:
|
||||
self.check_and_sync(cr, uid, access_token, event, google_event_dict[event.google_internal_event_id], context)
|
||||
del google_event_dict[event.google_internal_event_id]
|
||||
else:
|
||||
request_url = "https://www.googleapis.com/calendar/v3/calendars/%s/events/%s?access_token=%s" % ('primary', event.google_internal_event_id, access_token)
|
||||
content = {}
|
||||
try:
|
||||
req = urllib2.Request(request_url)
|
||||
content = urllib2.urlopen(req).read()
|
||||
except urllib2.HTTPError,e:
|
||||
error_message = e.read()
|
||||
print error_message
|
||||
if e.code == 404:
|
||||
print "Need DELETE"
|
||||
if content:
|
||||
content = json.loads(content)
|
||||
self.check_and_sync(cr, uid, access_token, event, content, context)
|
||||
for new_google_event in google_event_dict.values():
|
||||
if new_google_event.get('recurringEventId',False):
|
||||
reccurent_event = crm_meeting.search(cr, uid, [('google_internal_event_id', '=', new_google_event['recurringEventId'])])
|
||||
new_google_event_id = new_google_event['id'].split('_')[1].replace('T','')[:-1]
|
||||
for event_id in reccurent_event:
|
||||
if isinstance(event_id, str) and len(event_id.split('-'))>1 and event_id.split('-')[1] == new_google_event_id:
|
||||
reccurnt_event_id = int(event_id.split('-')[0].strip())
|
||||
parent_event = crm_meeting.read(cr,uid, reccurnt_event_id, [], context)
|
||||
parent_event['id'] = event_id
|
||||
#reccurent update from google
|
||||
self.update_from_google(cr, uid, parent_event, new_google_event, "copy", context)
|
||||
else:
|
||||
#new event from google
|
||||
self.update_from_google(cr, uid, False, new_google_event, "create", context)
|
||||
# del google_events_dict[new_google_event['id']]
|
||||
|
||||
def check_and_sync(self, cr, uid, access_token, oe_event, google_event, context):
|
||||
if datetime.strptime(oe_event.oe_update_date,"%Y-%m-%d %H:%M:%S.%f") > datetime.strptime(google_event['updated'],"%Y-%m-%dT%H:%M:%S.%fz"):
|
||||
#update to google
|
||||
self.update_to_google(cr, uid, access_token, oe_event, google_event, context)
|
||||
elif datetime.strptime(oe_event.oe_update_date,"%Y-%m-%d %H:%M:%S.%f") < datetime.strptime(google_event['updated'],"%Y-%m-%dT%H:%M:%S.%fz"):
|
||||
#update from google
|
||||
self.update_from_google(cr, uid, oe_event, google_event, 'write', context)
|
||||
pass
|
||||
|
||||
def update_to_google(self, cr, uid, access_token, oe_event, google_event, context):
|
||||
crm_meeting = self.pool['crm.meeting']
|
||||
request_url = "https://www.googleapis.com/calendar/v3/calendars/%s/events/%s?fields=%s" % ('primary', google_event['id'], urllib.quote('id,updated'))
|
||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
||||
data = self.generate_data(cr,uid ,oe_event, context)
|
||||
data['sequence'] = google_event.get('sequence', 0)
|
||||
data_json = json.dumps(data)
|
||||
try:
|
||||
req = urllib2.Request(request_url, data_json, headers)
|
||||
req.get_method = lambda: 'PATCH'
|
||||
content = urllib2.urlopen(req).read()
|
||||
except urllib2.HTTPError,e:
|
||||
error_message = json.loads(e.read())
|
||||
|
||||
content = json.loads(content)
|
||||
update_date = datetime.strptime(content['updated'],"%Y-%m-%dT%H:%M:%S.%fz")
|
||||
crm_meeting.write(cr, uid, [oe_event.id], {'oe_update_date':update_date})
|
||||
|
||||
def update_from_google(self, cr, uid, event, single_event_dict, type, context):
|
||||
crm_meeting = self.pool['crm.meeting']
|
||||
res_partner_obj = self.pool['res.partner']
|
||||
calendar_attendee_obj = self.pool['calendar.attendee']
|
||||
attendee_record= []
|
||||
result = {}
|
||||
if single_event_dict.get('attendees',False):
|
||||
for google_attendee in single_event_dict['attendees']:
|
||||
if type == "write":
|
||||
for oe_attendee in event['attendee_ids']:
|
||||
if calendar_attendee_obj.browse(cr, uid ,oe_attendee,context=context).email == google_attendee['email']:
|
||||
calendar_attendee_obj.write(cr, uid,[oe_attendee] ,{'state' : oe_state_mapping[google_attendee['responseStatus']]})
|
||||
google_attendee['found'] = True
|
||||
if google_attendee.get('found',False):
|
||||
continue
|
||||
attendee_id = res_partner_obj.search(cr, uid,[('email', '=', google_attendee['email'])], context=context)
|
||||
if not attendee_id:
|
||||
attendee_id = [res_partner_obj.create(cr, uid,{'email': google_attendee['email'], 'name': google_attendee.get("displayName",False) or google_attendee['email'] }, context=context)]
|
||||
attendee = res_partner_obj.read(cr, uid, attendee_id[0], ['email'], context=context)
|
||||
attendee['partner_id'] = attendee.pop('id')
|
||||
attendee['state'] = oe_state_mapping[google_attendee['responseStatus']]
|
||||
attendee_record.append((0, 0, attendee))
|
||||
if single_event_dict['start'].get('dateTime',False) and single_event_dict['end'].get('dateTime',False):
|
||||
UTC = pytz.timezone('UTC')
|
||||
date = parser.parse(single_event_dict['start']['dateTime'])
|
||||
date_deadline = parser.parse(single_event_dict['end']['dateTime'])
|
||||
delta = date_deadline.astimezone(UTC) - date.astimezone(UTC)
|
||||
result['duration'] = (delta.seconds / 60) / 60.0 + delta.days *24
|
||||
date = str(date.astimezone(UTC))[:-6]
|
||||
date_deadline = str(date_deadline.astimezone(UTC))[:-6]
|
||||
allday = False
|
||||
else:
|
||||
date = single_event_dict['start']['date'] + ' 12:00:00'
|
||||
date_deadline = single_event_dict['start']['date'] + ' 12:00:00'
|
||||
allday = True
|
||||
update_date = datetime.strptime(single_event_dict['updated'],"%Y-%m-%dT%H:%M:%S.%fz")
|
||||
result.update({
|
||||
'attendee_ids': attendee_record,
|
||||
'date': date,
|
||||
'date_deadline': date_deadline,
|
||||
'allday': allday,
|
||||
'name': single_event_dict.get('summary','Event'),
|
||||
'description': single_event_dict.get('description',''),
|
||||
'location':single_event_dict.get('location',''),
|
||||
'class':single_event_dict.get('visibility','public'),
|
||||
'oe_update_date':update_date,
|
||||
'google_internal_event_id': single_event_dict.get('id',''),
|
||||
})
|
||||
if single_event_dict.get("recurrence",False):
|
||||
rrule = [rule for rule in single_event_dict["recurrence"] if rule.startswith("RRULE:")][0][6:]
|
||||
result['rrule']=rrule
|
||||
if type == "write":
|
||||
crm_meeting.write(cr, uid, event['id'], result, context=context)
|
||||
elif type == "copy":
|
||||
result['write_type'] = "copy"
|
||||
crm_meeting.write(cr, uid, event['id'], result, context=context)
|
||||
elif type == "create":
|
||||
crm_meeting.create(cr, uid, result, context=context)
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="view_crm_meeting_google_synchronize" model="ir.ui.view">
|
||||
<field name="name">Synchronize Events to Google</field>
|
||||
<field name="model">crm.meeting.synchronize</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Synchronize Events to Google" version="7.0">
|
||||
<group string="This wizard will synchronize Events to Google Calendar">
|
||||
</group>
|
||||
<footer>
|
||||
<button name="synchronize_events" string="Synchronize" type="object" class="oe_highlight"/>
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_crm_meeting_google_synchronize" model="ir.actions.act_window">
|
||||
<field name="name">Synchronize Events to Google</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">crm.meeting.synchronize</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="view_crm_meeting_google_synchronize"/>
|
||||
<field name="context">{}</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="Synchronize with Google"
|
||||
id="mail_menu_calendar_synch" parent="mail.mail_my_stuff"
|
||||
sequence="10" action="action_crm_meeting_google_synchronize"/>
|
||||
</data>
|
||||
</openerp>
|
Loading…
Reference in New Issue