diff --git a/addons/google_base_account/wizard/google_login.py b/addons/google_base_account/wizard/google_login.py index 76888fae107..78f27e51da4 100644 --- a/addons/google_base_account/wizard/google_login.py +++ b/addons/google_base_account/wizard/google_login.py @@ -24,7 +24,7 @@ from 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')) diff --git a/addons/import_google_contact/__init__.py b/addons/import_google/__init__.py similarity index 96% rename from addons/import_google_contact/__init__.py rename to addons/import_google/__init__.py index 72009a6ca80..1889d262ff8 100644 --- a/addons/import_google_contact/__init__.py +++ b/addons/import_google/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- ############################################################################## -# +# # OpenERP, Open Source Management Solution # Copyright (C) 2004-2010 Tiny SPRL (). # @@ -15,11 +15,11 @@ # 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 . +# along with this program. If not, see . # ############################################################################## import import_google_contact +import sync_google_calendar import wizard # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/addons/import_google_contact/__openerp__.py b/addons/import_google/__openerp__.py similarity index 77% rename from addons/import_google_contact/__openerp__.py rename to addons/import_google/__openerp__.py index 6d19f2c6865..c5094c5cd02 100644 --- a/addons/import_google_contact/__openerp__.py +++ b/addons/import_google/__openerp__.py @@ -19,7 +19,6 @@ # ############################################################################## - { 'name': 'Google Contact Import', 'version': '1.0', @@ -27,15 +26,18 @@ 'description': """The module adds google contact in partner address""", 'author': 'OpenERP SA', 'website': 'http://www.openerp.com', - 'depends': ['base','google_base_account'], + 'depends': ['base', 'google_base_account','crm'], 'init_xml': [], 'update_xml': [ - 'wizard/google_contact_import_view.xml' - ], + 'sync_google_calendar_view.xml', + 'wizard/google_contact_import_view.xml', +# 'wizard/wizard_import_calendar_events_view.xml', + ], 'demo_xml': [], 'test': [ - 'test/test_sync_google_contact_import_partner.yml', - 'test/test_sync_google_contact_import_address.yml', +# 'test/test_sync_google_contact_import_partner.yml', +# 'test/test_sync_google_contact_import_address.yml', +# 'test/test_sync_google_calendar.yml', ], 'installable': True, 'active': False, diff --git a/addons/import_google_contact/import_google_contact.py b/addons/import_google/import_google_contact.py similarity index 100% rename from addons/import_google_contact/import_google_contact.py rename to addons/import_google/import_google_contact.py diff --git a/addons/import_google_contact/security/ir.model.access.csv b/addons/import_google/security/ir.model.access.csv similarity index 100% rename from addons/import_google_contact/security/ir.model.access.csv rename to addons/import_google/security/ir.model.access.csv diff --git a/addons/import_google/sync_google_calendar.py b/addons/import_google/sync_google_calendar.py new file mode 100644 index 00000000000..f61ac409fe6 --- /dev/null +++ b/addons/import_google/sync_google_calendar.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +from osv import osv, fields + +class crm_meeting(osv.osv): + _inherit = "crm.meeting" + + def unlink(self, cr, uid, ids, context=None): + ids = self.web_client_unfucking_timebomb(ids) + res = super(crm_meeting, self).unlink(cr, uid, ids, context=context) + ids_real = self.remove_virtual_id(ids) + + model_obj = self.pool.get('ir.model.data') + remain_ids = self.search(cr, uid, [('id','in',ids_real)]) + ids_to_remove = list(set(ids_real) - set(remain_ids)) + + model_ids = model_obj.search(cr, uid, [('res_id','in',ids_to_remove),('model','=','crm.meeting'),('module','=','sync_google_calendar')], context=context) + model_obj.unlink(cr, uid, model_ids, context=context) + return res + +crm_meeting() + +class crm_case_categ(osv.osv): + """ Category of Case """ + _inherit = "crm.case.categ" + _columns = { + 'user_id': fields.many2one('res.users', 'User') + } +crm_case_categ() + +# vim:expandtab:smartindent:toabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/import_google/sync_google_calendar_view.xml b/addons/import_google/sync_google_calendar_view.xml new file mode 100644 index 00000000000..58e1e55efd0 --- /dev/null +++ b/addons/import_google/sync_google_calendar_view.xml @@ -0,0 +1,32 @@ + + + + + CRM - Meetings Form (Inherited) + crm.meeting + form + + + + + + + + + + CRM - Meetings Tree (Inherited) + crm.meeting + tree + + + + + + + + + \ No newline at end of file diff --git a/addons/import_google/test/test_sync_google_calendar.yml b/addons/import_google/test/test_sync_google_calendar.yml new file mode 100644 index 00000000000..38287cb111e --- /dev/null +++ b/addons/import_google/test/test_sync_google_calendar.yml @@ -0,0 +1,36 @@ +- + In order to test google calendar feature with OpenERP, I import events from + a google account and store them in Meetings. +- | + I create a record for the gmail account for which I want to import the contacts. +- + !record {model: google.login, id: google_login_contact_id0}: + user: testmail.openerp + password: openerptiny +- | + I login into that account. +- + !python {model: google.login}: | + self.login(cr, uid, [ref('google_login_contact_id0')], context) +- | + Now I want to import all the events from all the calendars in the user account. +- | + I select all calendars. +- + !record {model: synchronize.google.calendar, id: synchronize_google_calendar_id0}: + calendar_name: all +- | + I import the events from the google calendar. +- + !python {model: synchronize.google.calendar}: | + self.import_calendar_events(cr, uid, [ref('synchronize_google_calendar_id0')], context) +- | + Now I check my meetings are created or not. +- + !python {model: crm.meeting}: | + model_obj = self.pool.get('ir.model.data') + meeting_ids = self.search(cr, uid, []) + model_ids = model_obj.search(cr, uid, [('res_id','in',meeting_ids),('model','=','crm.meeting'),('module','=','sync_google_calendar')]) + assert model_ids, 'Meetings not created !' + + \ No newline at end of file diff --git a/addons/import_google_contact/test/test_sync_google_contact_import_address.yml b/addons/import_google/test/test_sync_google_contact_import_address.yml similarity index 100% rename from addons/import_google_contact/test/test_sync_google_contact_import_address.yml rename to addons/import_google/test/test_sync_google_contact_import_address.yml diff --git a/addons/import_google_contact/test/test_sync_google_contact_import_partner.yml b/addons/import_google/test/test_sync_google_contact_import_partner.yml similarity index 100% rename from addons/import_google_contact/test/test_sync_google_contact_import_partner.yml rename to addons/import_google/test/test_sync_google_contact_import_partner.yml diff --git a/addons/import_google_contact/wizard/__init__.py b/addons/import_google/wizard/__init__.py similarity index 96% rename from addons/import_google_contact/wizard/__init__.py rename to addons/import_google/wizard/__init__.py index d92ae8c1588..5e475d6fc7b 100644 --- a/addons/import_google_contact/wizard/__init__.py +++ b/addons/import_google/wizard/__init__.py @@ -20,5 +20,5 @@ ############################################################################## import import_google import google_contact_import - +#import wizard_import_calendar_events # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/import_google_contact/wizard/google_contact_import.py b/addons/import_google/wizard/google_contact_import.py similarity index 53% rename from addons/import_google_contact/wizard/google_contact_import.py rename to addons/import_google/wizard/google_contact_import.py index b87c7502fed..b38d3b1ce3e 100644 --- a/addons/import_google_contact/wizard/google_contact_import.py +++ b/addons/import_google/wizard/google_contact_import.py @@ -22,6 +22,9 @@ import datetime import dateutil from dateutil.parser import * from pytz import timezone +import time +import re +import urllib try: import gdata @@ -29,11 +32,11 @@ try: import gdata.contacts 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')) - -from import_google import import_contact -from osv import fields,osv +from osv import fields +from osv import osv from tools.translate import _ import tools +from import_google import import_contact class google_login_contact(osv.osv_memory): _inherit = 'google.login' @@ -41,7 +44,7 @@ class google_login_contact(osv.osv_memory): def _get_next_action(self, cr, uid, context): data_obj = self.pool.get('ir.model.data') - data_id = data_obj._get_id(cr, uid, 'import_google_contact', 'view_synchronize_google_contact_import_form') + data_id = data_obj._get_id(cr, uid, 'import_google', 'view_synchronize_google_contact_import_form') view_id = False if data_id: @@ -134,6 +137,7 @@ class synchronize_google_contact(osv.osv_memory): imp = import_contact(self, cr, uid,'google', "synchronize_google_contact", context=context) imp.set_table_list(tables) imp.start() + return {'type': 'ir.actions.act_window_close'} # # if not gd_client: # raise osv.except_osv(_('Error'), _("Please specify correct user and password !")) @@ -278,5 +282,262 @@ class synchronize_google_contact(osv.osv_memory): # return {'type': 'ir.actions.act_window_close'} synchronize_google_contact() +def _get_tinydates(self, stime, etime): + stime = dateutil.parser.parse(stime) + etime = dateutil.parser.parse(etime) + try: + au_dt = au_tz.normalize(stime.astimezone(au_tz)) + timestring = datetime.datetime(*au_dt.timetuple()[:6]).strftime('%Y-%m-%d %H:%M:%S') + au_dt = au_tz.normalize(etime.astimezone(au_tz)) + timestring_end = datetime.datetime(*au_dt.timetuple()[:6]).strftime('%Y-%m-%d %H:%M:%S') + except: + timestring = datetime.datetime(*stime.timetuple()[:6]).strftime('%Y-%m-%d %H:%M:%S') + timestring_end = datetime.datetime(*etime.timetuple()[:6]).strftime('%Y-%m-%d %H:%M:%S') + return (timestring, timestring_end) + +def _get_rules(self, datas): + new_val = {} + if datas['FREQ'] == 'WEEKLY' and datas.get('BYDAY'): + for day in datas['BYDAY'].split(','): + new_val[day.lower()] = True + datas.pop('BYDAY') + + if datas.get('UNTIL'): + until = parser.parse(''.join((re.compile('\d')).findall(datas.get('UNTIL')))) + new_val['end_date'] = until.strftime('%Y-%m-%d') + new_val['end_type'] = 'end_date' + datas.pop('UNTIL') + + if datas.get('COUNT'): + new_val['count'] = datas.get('COUNT') + new_val['end_type'] = 'count' + datas.pop('COUNT') + + if datas.get('INTERVAL'): + new_val['interval'] = datas.get('INTERVAL') + else: + new_val['interval'] = 1 + + if datas.get('BYMONTHDAY'): + new_val['day'] = datas.get('BYMONTHDAY') + datas.pop('BYMONTHDAY') + new_val['select1'] = 'date' + + if datas.get('BYDAY'): + d = datas.get('BYDAY') + if '-' in d: + new_val['byday'] = d[:2] + new_val['week_list'] = d[2:4].upper() + else: + new_val['byday'] = d[:1] + new_val['week_list'] = d[1:3].upper() + new_val['select1'] = 'day' + + if datas.get('BYMONTH'): + new_val['month_list'] = datas.get('BYMONTH') + datas.pop('bymonth') + return new_val + + +def _get_repeat_status(self, str_google): + rrule = str_google[str_google.find('FREQ'):str_google.find('\nBEGIN')] + status = {} + for rule in rrule.split(';'): + status[rule.split('=')[0]] = rule.split('=')[-1:] and rule.split('=')[-1:][0] or '' + rules = _get_rules(self, status) + if status.get('FREQ') == 'WEEKLY': + status.update({'rrule_type': 'weekly'}) + status.pop('FREQ') + elif status.get('FREQ') == 'DAILY': + status.update({'rrule_type': 'daily'}) + status.pop('FREQ') + elif status.get('FREQ') == 'MONTHLY': + status.update({'rrule_type': 'monthly'}) + status.pop('FREQ') + elif status.get('FREQ') == 'YEARLY': + status.update({'rrule_type': 'yearly'}) + status.pop('FREQ') + status.update(rules) + return status + + +def _get_repeat_dates(self, x): + if len(x) > 4: + if x[3].startswith('BY'): + zone_time = x[4].split('+')[-1:][0].split(':')[0][:4] + else: + zone_time = x[3].split('+')[-1:][0].split(':')[0][:4] + else: + zone_time = x[2].split('+')[-1:][0].split(':')[0][:4] + tz_format = zone_time[:2]+':'+zone_time[2:] + repeat_start = x[1].split('\n')[0].split(':')[1] + repeat_end = x[2].split('\n')[0].split(':')[1] + o = repeat_start.split('T') + repeat_start = str(o[0][:4]) + '-' + str(o[0][4:6]) + '-' + str(o[0][6:8]) + if len(o) == 2: + repeat_start += ' ' + str(o[1][:2]) + ':' + str(o[1][2:4]) + ':' + str(o[1][4:6]) + else: + repeat_start += ' ' + '00' + ':' + '00' + ':' + '00' + p = repeat_end.split('T') + repeat_end = str(p[0][:4]) + '-' + str(p[0][4:6]) + '-' + str(p[0][6:8]) + if len(p) == 2: + repeat_end += ' ' + str(p[1][:2]) + ':' + str(p[1][2:4]) + ':' + str(p[1][4:6]) + else: + repeat_end += ' ' + '00' + ':' + '00' + ':' + '00' + return (repeat_start, repeat_end, tz_format) + +class google_login_calendar(osv.osv_memory): + _inherit = 'google.login' + _name = 'google.login.calendar' + + def _get_next_action(self, cr, uid, context): + data_obj = self.pool.get('ir.model.data') + data_id = data_obj._get_id(cr, uid, 'import_google', 'view_synchronize_google_calendar_import_form') + + view_id = False + if data_id: + view_id = data_obj.browse(cr, uid, data_id, context=context).res_id + value = { + 'name': _('Import Events'), + 'view_type': 'form', + 'view_mode': 'form,tree', + 'res_model': 'synchronize.google.calendar', + 'view_id': False, + 'context': context, + 'views': [(view_id, 'form')], + 'type': 'ir.actions.act_window', + 'target': 'new', + } + return value +google_login_calendar() + +class synchronize_google_calendar_events(osv.osv_memory): + _name = 'synchronize.google.calendar' + + def _get_calendars(self, cr, uid, context=None): + user_obj = self.pool.get('res.users').browse(cr, uid, uid) + google = self.pool.get('google.login') + res = [] + try: + gd_client = google.google_login(user_obj.gmail_user, user_obj.gmail_password, type='calendar') + + calendars = gd_client.GetAllCalendarsFeed() + + for cal in calendars.entry: + res.append((cal.id.text, cal.title.text)) + except Exception, e: + raise osv.except_osv('Error !', e.args[0].get('body')) + res.append(('all', 'All Calendars')) + return res + + _columns = { + 'calendar_name': fields.selection(_get_calendars, "Calendar Name", size=32), + } + + _defaults = { + 'calendar_name': 'all', + } + + def get_events(self, cr, uid, event_feed, context=None): + meeting_obj = self.pool.get('crm.meeting') + categ_obj = self.pool.get('crm.case.categ') + model_obj = self.pool.get('ir.model.data') + object_id = self.pool.get('ir.model').search(cr, uid, [('model', '=', 'crm.meeting')]) + meeting_ids = [] + categ_id = categ_obj.search(cr, uid, [('name', '=', event_feed.title.text), ('object_id', '=', object_id and object_id[0]), ('user_id', '=', uid)]) + if not categ_id: + categ_id.append(categ_obj.create(cr, uid, {'name': event_feed.title.text, + 'object_id': object_id and object_id[0], + 'user_id': uid})) + if 'tz' in context and context['tz']: + time_zone = context['tz'] + else: + time_zone = tools.get_server_timezone() + au_tz = timezone(time_zone) + + for feed in event_feed.entry: + google_id = feed.id.text + model_data = { + 'name': google_id, + 'model': 'crm.meeting', + 'module': 'sync_google_calendar', + 'noupdate': True, + } + vals = { + 'name': feed.title.text or '(No title)', + 'description': feed.content.text, + 'categ_id': categ_id and categ_id[0], + } + if feed.when: + timestring, timestring_end = _get_tinydates(self, feed.when[0].start_time, feed.when[0].end_time) + else: + x = feed.recurrence.text.split(';') + repeat_status = _get_repeat_status(self, feed.recurrence.text) + repeat_start, repeat_end, zone_time = _get_repeat_dates(self, x) + timestring = time.strftime('%Y-%m-%d %H:%M:%S', time.strptime(repeat_start, "%Y-%m-%d %H:%M:%S")) + timestring_end = time.strftime('%Y-%m-%d %H:%M:%S', time.strptime(repeat_end, "%Y-%m-%d %H:%M:%S")) + if repeat_status: + repeat_status.update({'recurrency': True}) + vals.update(repeat_status) + + vals.update({'date': timestring, 'date_deadline': timestring_end}) + data_ids = model_obj.search(cr, uid, [('model', '=', 'crm.meeting'), ('name', '=', google_id)]) + if data_ids: + res_id = model_obj.browse(cr, uid, data_ids[0], context=context).res_id + meeting = meeting_obj.browse(cr, uid, res_id, context=context) + google_updated = feed.updated.text + utime = dateutil.parser.parse(google_updated) + au_dt = au_tz.normalize(utime.astimezone(au_tz)) + updated_dt = datetime.datetime(*au_dt.timetuple()[:6]).strftime('%Y-%m-%d %H:%M:%S') + if meeting.write_date < updated_dt: + meeting_ids.append(res_id) + meeting_obj.write(cr, uid, [res_id], vals, context=context) + else: + res_id = meeting_obj.create(cr, uid, vals, context=context) + meeting_ids.append(res_id) + model_data.update({'res_id': res_id}) + model_obj.create(cr, uid, model_data, context=context) + return meeting_ids + + def import_calendar_events(self, cr, uid, ids, context=None): + obj = self.browse(cr, uid, ids, context=context)[0] + if not ids: + return {'type': 'ir.actions.act_window_close'} + + user_obj = self.pool.get('res.users').browse(cr, uid, uid) + gmail_user = user_obj.gmail_user + gamil_pwd = user_obj.gmail_password + + google = self.pool.get('google.login.calendar') + gd_client = google.google_login(gmail_user, gamil_pwd, type='calendar') + + if not gmail_user or not gamil_pwd: + raise osv.except_osv(_('Error'), _("Please specify the user and password !")) + + meetings = [] + if obj.calendar_name != 'all': + events_query = gdata.calendar.service.CalendarEventQuery(user=urllib.unquote(obj.calendar_name.split('/')[~0])) + events_query.start_index = 1 + events_query.max_results = 1000 + event_feed = gd_client.GetCalendarEventFeed(events_query.ToUri()) + meetings.append(self.get_events(cr, uid, event_feed, context=context)) + else: + calendars = map(lambda x: x[0], [cal for cal in self._get_calendars(cr, uid, context) if cal[0] != 'all']) + for cal in calendars: + events_query = gdata.calendar.service.CalendarEventQuery(user=urllib.unquote(cal.split('/')[~0])) + events_query.start_index = 1 + events_query.max_results = 1000 + event_feed = gd_client.GetCalendarEventFeed(events_query.ToUri()) + meetings.append(self.get_events(cr, uid, event_feed, context=context)) + + meeting_ids = [] + for meeting in meetings: + meeting_ids += meeting + + return { + 'type': 'ir.actions.act_window_close', + } + +synchronize_google_calendar_events() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/import_google_contact/wizard/google_contact_import_view.xml b/addons/import_google/wizard/google_contact_import_view.xml similarity index 55% rename from addons/import_google_contact/wizard/google_contact_import_view.xml rename to addons/import_google/wizard/google_contact_import_view.xml index fce2a8f2a53..2cefca6f6f9 100644 --- a/addons/import_google_contact/wizard/google_contact_import_view.xml +++ b/addons/import_google/wizard/google_contact_import_view.xml @@ -30,8 +30,43 @@ + + + + synchronize.google.calendar.form + synchronize.google.calendar + form + +
+ + + + + + + +