[MERGE] import google project + calendar cleaning v1

bzr revid: tfr@openerp.com-20110627100343-msagwsa1nba29497
This commit is contained in:
tfr@openerp.com
2011-06-27 12:03:43 +02:00
48 changed files with 2470 additions and 854 deletions
+52 -8
View File
@@ -1,11 +1,55 @@
# -*- 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/>.
#
##############################################################################
{
"name": "Base calendar",
"version": "2.0",
"depends": ['base'],
"js": [
'static/lib/dhtmlxScheduler/codebase/dhtmlxscheduler.js',
'static/src/js/calendar.js'
"name" : "Basic Calendar Functionality",
"version" : "1.0",
"depends" : ["base"],
'description': """
This is a full-featured calendar system.
========================================
It supports:
- Calendar of events
- Alerts (create requests)
- Recurring events
- Invitations to people""",
"author" : "OpenERP SA",
'category': 'Tools',
'website': 'http://www.openerp.com',
"init_xml" : [
'base_calendar_data.xml'
],
"css": ['static/lib/dhtmlxScheduler/codebase/dhtmlxscheduler.css'],
'active': True
"demo_xml" : [],
"update_xml" : [
'security/calendar_security.xml',
'security/ir.model.access.csv',
'wizard/base_calendar_invite_attendee_view.xml',
'base_calendar_view.xml'
],
"test" : ['test/base_calendar_test.yml'],
"installable" : True,
"active" : False,
"certificate" : "00694071962960352821",
'images': ['images/base_calendar1.jpeg','images/base_calendar2.jpeg','images/base_calendar3.jpeg','images/base_calendar4.jpeg',],
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+177 -341
View File
@@ -22,6 +22,7 @@
from datetime import datetime, timedelta, date
from dateutil import parser
from dateutil import rrule
from dateutil.relativedelta import relativedelta
from osv import fields, osv
from service import web_services
from tools.translate import _
@@ -47,20 +48,22 @@ def get_recurrent_dates(rrulestring, exdate, startdate=None, exrule=None):
def todate(date):
val = parser.parse(''.join((re.compile('\d')).findall(date)))
return val
if not startdate:
startdate = datetime.now()
if not exdate:
exdate = []
rset1 = rrule.rrulestr(str(rrulestring), dtstart=startdate, forceset=True)
for date in exdate:
datetime_obj = todate(date)
rset1._exdate.append(datetime_obj)
if exrule:
rset1.exrule(rrule.rrulestr(str(exrule), dtstart=startdate))
return list(rset1._iter())
return list(rset1)
def base_calendar_id2real_id(base_calendar_id=None, with_date=False):
"""
@@ -373,7 +376,8 @@ property or property parameter."),
multi='event_end_date'),
'ref': fields.reference('Event Ref', selection=_links_get, size=128),
'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"),
}
}
_defaults = {
'state': 'needs-action',
'role': 'req-participant',
@@ -411,7 +415,7 @@ property or property parameter."),
cal = vobject.iCalendar()
event = cal.add('vevent')
if not event_obj.date_deadline or not event_obj.date:
raise osv.except_osv(_('Warning !'),_("Couldn't Invite because date is not specified!"))
raise osv.except_osv(_('Warning !'),_("Couldn't Invite because date is not specified!"))
event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S'))
event.add('dtstart').value = ics_datetime(event_obj.date)
event.add('dtend').value = ics_datetime(event_obj.date_deadline)
@@ -456,7 +460,7 @@ property or property parameter."),
trigger.value = delta
# Compute other details
valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP'
for attendee in event_obj.attendee_ids:
attendee_add = event.add('attendee')
attendee_add.params['CUTYPE'] = [str(attendee.cutype)]
@@ -674,7 +678,7 @@ true, it will allow you to hide the event alarm information without removing it.
new_res_alarm = alarm_ids[0]
cr.execute('UPDATE %s ' % model_obj._table + \
' SET base_calendar_alarm_id=%s, alarm_id=%s ' \
' WHERE id=%s',
' WHERE id=%s',
(cal_alarm.id, new_res_alarm, data.id))
self.do_alarm_unlink(cr, uid, [data.id], model)
@@ -914,22 +918,6 @@ class calendar_event(osv.osv):
def _tz_get(self, cr, uid, context=None):
return [(x.lower(), x) for x in pytz.all_timezones]
def onchange_allday(self, cr, uid, ids, allday, context=None):
"""Sets duration as 24 Hours if event is selected for all day
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param ids: List of calendar event’s IDs.
@param allday: Value of allday boolean
@param context: A standard dictionary for contextual values
"""
if not allday or not ids:
return {}
value = {
'duration': 24
}
return {'value': value}
def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None):
"""Returns duration and/or end date based on values passed
@param self: The object pointer
@@ -952,8 +940,13 @@ class calendar_event(osv.osv):
value['duration'] = duration
if allday: # For all day event
value = {'duration': 24}
value = {'duration': 24.0}
duration = 24.0
if start_date:
start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
start_date = datetime.strftime(datetime(start.year, start.month, start.day, 0,0,0), "%Y-%m-%d %H:%M:%S")
value['date'] = start_date
start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
if end_date and not duration:
@@ -966,7 +959,7 @@ class calendar_event(osv.osv):
value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S")
elif end_date and duration and not allday:
# we have both, keep them synchronized:
# set duration based on end_date (arbitrary decision: this avoid
# set duration based on end_date (arbitrary decision: this avoid
# getting dates like 06:31:48 instead of 06:32:00)
end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
diff = end - start
@@ -982,88 +975,10 @@ class calendar_event(osv.osv):
"""
if context is None:
context = {}
cr.execute('select id from '+self._table+' where recurrent_uid in %s', (tuple(ids),))
r_ids = map(lambda x: x[0], cr.fetchall())
self.unlink(cr, uid, r_ids, context=context)
return True
def _set_rrulestring(self, cr, uid, id, name, value, arg, context=None):
"""
Sets values of fields that defines event recurrence from the value of rrule string
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param id: List of calendar event's ids.
@param context: A standard dictionary for contextual values
@return: dictionary of rrule value.
"""
if context is None:
context = {}
cr.execute("UPDATE %s set freq='None',interval=0,count=0,end_date=Null,\
mo=False,tu=False,we=False,th=False,fr=False,sa=False,su=False,\
day=0,select1='date',month_list=Null ,byday=Null where id=%%s" % (self._table), (id,))
if not value:
cr.execute("UPDATE %s set rrule_type='none' where id=%%s" % self._table,(id,))
return True
val = {}
for part in value.split(';'):
if part.lower().__contains__('freq') and len(value.split(';')) <=2:
rrule_type = part.lower()[5:]
break
else:
rrule_type = 'custom'
break
ans = value.split(';')
for i in ans:
val[i.split('=')[0].lower()] = i.split('=')[1].lower()
if not val.get('interval'):
rrule_type = 'custom'
elif int(val.get('interval')) > 1: #If interval is other than 1 rule is custom
rrule_type = 'custom'
qry = "UPDATE \"%s\" set rrule_type=%%s " % self._table
qry_args = [ rrule_type, ]
new_val = val.copy()
for k, v in val.items():
if val['freq'] == 'weekly' and val.get('byday'):
for day in val['byday'].split(','):
new_val[day] = True
val.pop('byday')
if val.get('until'):
until = parser.parse(''.join((re.compile('\d')).findall(val.get('until'))))
new_val['end_date'] = until.strftime('%Y-%m-%d')
val.pop('until')
new_val.pop('until')
if val.get('bymonthday'):
new_val['day'] = val.get('bymonthday')
val.pop('bymonthday')
new_val['select1'] = 'date'
new_val.pop('bymonthday')
if val.get('byday'):
d = val.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 val.get('bymonth'):
new_val['month_list'] = val.get('bymonth')
val.pop('bymonth')
new_val.pop('bymonth')
for k, v in new_val.items():
qry += ", %s=%%s" % k
qry_args.append(v)
qry = qry + " where id=%s"
qry_args.append(id)
cr.execute(qry, qry_args)
for event_id in ids:
cr.execute("select id from %s where recurrent_uid=%%s" % (self._table), (event_id,))
r_ids = map(lambda x: x[0], cr.fetchall())
self.unlink(cr, uid, r_ids, context=context)
return True
def _get_rulestring(self, cr, uid, ids, name, arg, context=None):
@@ -1075,28 +990,15 @@ class calendar_event(osv.osv):
@param context: A standard dictionary for contextual values
@return: dictionary of rrule value.
"""
result = {}
for datas in self.read(cr, uid, ids, context=context):
for datas in self.read(cr, uid, ids, ['id','byday','recurrency', 'month_list','end_date', 'rrule_type', 'select1', 'interval', 'count', 'end_type', 'mo', 'tu', 'we', 'th', 'fr', 'sa', 'su', 'exrule', 'day', 'week_list' ], context=context):
event = datas['id']
if datas.get('rrule_type'):
if datas.get('rrule_type') == 'none':
result[event] = False
cr.execute("UPDATE %s set exrule=Null where id=%%s" % self._table,( event,))
if datas.get('rrule_type') :
if datas.get('interval', 0) < 0:
raise osv.except_osv(_('Warning!'), _('Interval can not be Negative'))
if datas.get('count', 0) < 0:
raise osv.except_osv(_('Warning!'), _('Count can not be Negative'))
rrule_custom = self.compute_rule_string(cr, uid, datas, \
context=context)
result[event] = rrule_custom
else:
result[event] = self.compute_rule_string(cr, uid, {'freq': datas.get('rrule_type').upper(), 'interval': 1}, context=context)
for id, myrule in result.items():
#Remove the events generated from recurrent event
if not myrule:
self.unlink_events(cr, uid, [id], context=context)
if datas.get('interval', 0) < 0:
raise osv.except_osv(_('Warning!'), _('Interval can not be Negative'))
if datas.get('count', 0) < 0:
raise osv.except_osv(_('Warning!'), _('Count can not be Negative'))
result[event] = self.compute_rule_string(datas)
return result
_columns = {
@@ -1122,14 +1024,10 @@ defines the list of date/time exceptions for a recurring calendar component."),
'exrule': fields.char('Exception Rule', size=352, help="Defines a \
rule or repeating pattern of time to exclude from the recurring rule."),
'rrule': fields.function(_get_rulestring, type='char', size=124, method=True, \
string='Recurrent Rule', store=True, \
fnct_inv=_set_rrulestring, help='Defines a\
rule or repeating pattern for recurring events\n\
e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=-1SU'),
string='Recurrent Rule'),
'rrule_type': fields.selection([('none', ''), ('daily', 'Daily'), \
('weekly', 'Weekly'), ('monthly', 'Monthly'), \
('yearly', 'Yearly'),],
('yearly', 'Yearly'),],
'Recurrency', states={'done': [('readonly', True)]},
help="Let the event automatically repeat at that interval"),
'alarm_id': fields.many2one('res.alarm', 'Alarm', states={'done': [('readonly', True)]},
@@ -1141,14 +1039,7 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with Organizer Attribure of VEvent.
'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}),
'freq': fields.selection([('None', 'No Repeat'),
('hourly', 'Hours'),
('daily', 'Days'),
('weekly', 'Weeks'),
('monthly', 'Months'),
('yearly', 'Years'), ], 'Frequency'),
'end_type' : fields.selection([('forever', 'Forever'), ('count', 'Fix amout of times'), ('end_date','End date')], 'Way to end reccurency'),
'end_type' : fields.selection([('count', 'Fix amout of times'), ('end_date','End date')], 'Way to end reccurency'),
'interval': fields.integer('Repeat every', help="Repeat every (Days/Week/Month/Year)"),
'count': fields.integer('Repeat', help="Repeat x times"),
'mo': fields.boolean('Mon'),
@@ -1158,7 +1049,7 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
'fr': fields.boolean('Fri'),
'sa': fields.boolean('Sat'),
'su': fields.boolean('Sun'),
'select1': fields.selection([('date', 'Date of month'),
'select1': fields.selection([('date', 'Date of month'),
('day', 'Day of month')], 'Option'),
'day': fields.integer('Date of month'),
'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
@@ -1175,8 +1066,8 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}),
'active': fields.boolean('Active', help="If the active field is set to \
true, it will allow you to hide the event alarm information without removing it."),
'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
'edit_all': fields.boolean('Edit All', help="Edit all Occurrences of recurrent Meeting."),
'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
'edit_all': fields.boolean('Edit All', help="Edit all Occurrences of recurrent Meeting."),
}
def default_organizer(self, cr, uid, context=None):
user_pool = self.pool.get('res.users')
@@ -1187,11 +1078,12 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
return res
_defaults = {
'end_type' : 'forever',
'end_type' : 'count',
'count' : 1,
'rrule_type' : 'none',
'state': 'tentative',
'class': 'public',
'show_as': 'busy',
'freq': 'None',
'select1': 'date',
'interval': 1,
'active': 1,
@@ -1200,43 +1092,7 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
'edit_all' : False,
}
def onchange_edit_all(self, cr, uid, ids, rrule_type,edit_all, context=None):
if not context:
context = {}
value = {}
if edit_all and rrule_type:
for id in ids:
base_calendar_id2real_id(id)
return value
def modify_all(self, cr, uid, event_ids, defaults, context=None, *args):
"""
Modifies the recurring event
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param event_ids: List of crm meeting’s IDs.
@param context: A standard dictionary for contextual values
@return: True
"""
for event_id in event_ids:
event_id = base_calendar_id2real_id(event_id)
defaults.pop('id')
defaults.update({'table': self._table})
qry = "UPDATE %(table)s set name = '%(name)s', \
date = '%(date)s', date_deadline = '%(date_deadline)s'"
if defaults.get('alarm_id'):
qry += ", alarm_id = %(alarm_id)s"
if defaults.get('location'):
qry += ", location = '%(location)s'"
qry += "WHERE id = %s" % (event_id)
cr.execute(qry, defaults)
return True
def get_recurrent_ids(self, cr, uid, select, base_start_date, base_until_date, limit=100):
def get_recurrent_ids(self, cr, uid, select, base_start_date, base_until_date, limit=100, context=None):
"""Gives virtual event ids for recurring events based on value of Recurrence Rule
This method gives ids of dates that comes between start date and end date of calendar views
@param self: The object pointer
@@ -1245,46 +1101,33 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
@param base_start_date: Get Start Date
@param base_until_date: Get End Date
@param limit: The Number of Results to Return """
if not context:
context = {}
virtual_id = context and context.get('virtual_id', False) or False
if not limit:
limit = 100
if isinstance(select, (str, int, long)):
ids = [select]
else:
ids = select
result = []
recur_dict = []
if ids and (base_start_date or base_until_date):
cr.execute("select m.id, m.rrule, m.date, m.date_deadline, m.duration, \
m.exdate, m.exrule, m.recurrent_id, m.recurrent_uid from " + self._table + \
" m where m.id = ANY(%s)", (ids,) )
count = 0
for data in cr.dictfetchall():
if ids and virtual_id:
for data in super(calendar_event, self).read(cr, uid, ids, context=context):
start_date = base_start_date and datetime.strptime(base_start_date[:10]+ ' 00:00:00' , "%Y-%m-%d %H:%M:%S") or False
until_date = base_until_date and datetime.strptime(base_until_date[:10]+ ' 23:59:59', "%Y-%m-%d %H:%M:%S") or False
if count > limit:
break
event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
# To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
start_date = event_date
if not data['rrule']:
if start_date and (event_date < start_date):
continue
if until_date and (event_date > until_date):
continue
idval = real_id2base_calendar_id(data['id'], data['date'])
if not data['recurrent_id']:
result.append(idval)
count += 1
else:
ex_id = real_id2base_calendar_id(data['recurrent_uid'], data['recurrent_id'])
ls = base_calendar_id2real_id(ex_id, with_date=data and data.get('duration', 0) or 0)
if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
if ls[1] == data['recurrent_id']:
result.append(idval)
recur_dict.append(ex_id)
idval = data['id']
result.append(idval)
else:
start_date = event_date
exdate = data['exdate'] and data['exdate'].split(',') or []
rrule_str = data['rrule']
new_rrule_str = []
@@ -1300,6 +1143,8 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
until_date = rrule_until_date
if until_date:
value = until_date.strftime("%Y%m%d%H%M%S")
else:
value = value.strftime("%Y%m%d%H%M%S")
new_rule = '%s=%s' % (name, value)
new_rrule_str.append(new_rule)
if not is_until and until_date:
@@ -1309,6 +1154,7 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
new_rrule_str.append(new_rule)
new_rrule_str = ';'.join(new_rrule_str)
rdates = get_recurrent_dates(str(new_rrule_str), exdate, start_date, data['exrule'])
for r_date in rdates:
if start_date and r_date < start_date:
continue
@@ -1316,77 +1162,75 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
continue
idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S"))
result.append(idval)
count += 1
if result:
ids = list(set(result)-set(recur_dict))
ids = list(set(result))
if isinstance(select, (str, int, long)):
return ids and ids[0] or False
return ids
def compute_rule_string(self, cr, uid, datas, context=None, *args):
def compute_rule_string(self, datas):
"""
Compute rule string according to value type RECUR of iCalendar from the values given.
@param self: the object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param datas: dictionary of freq and interval value.
@param context: A standard dictionary for contextual values
@return: String value of the format RECUR of iCalendar
"""
weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
weekstring = ''
monthstring = ''
yearstring = ''
freq=datas.get('rrule_type')
if freq == 'none':
def get_week_string(freq, datas):
weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
if freq == 'weekly':
byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
if byday:
return ';BYDAY=' + ','.join(byday)
return ''
def get_month_string(freq, datas):
if freq == 'monthly':
if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
if datas.get('select1')=='day':
return ';BYDAY=' + datas.get('byday') + datas.get('week_list')
elif datas.get('select1')=='date':
return ';BYMONTHDAY=' + str(datas.get('day'))
return ''
def get_end_date(datas):
if datas.get('end_date'):
datas['end_date_new'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
if freq == 'weekly':
byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
if byday:
weekstring = ';BYDAY=' + ','.join(byday)
elif freq == 'monthly':
if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
if datas.get('select1')=='day':
monthstring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
elif datas.get('select1')=='date':
monthstring = ';BYMONTHDAY=' + str(datas.get('day'))
return (datas.get('end_type') == 'count' and (';COUNT=' + str(datas.get('count'))) or '') +\
((datas.get('end_date_new') and datas.get('end_type') == 'end_date' and (';UNTIL=' + datas.get('end_date_new'))) or '')
if datas.get('end_date'):
datas['end_date'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + 'T235959Z'
enddate = (datas.get('count') and (';COUNT=' + str(datas.get('count'))) or '') +\
((datas.get('end_date') and (';UNTIL=' + datas.get('end_date'))) or '')
freq=datas.get('rrule_type')
if freq == 'none':
return ''
interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
return 'FREQ=' + freq.upper() + get_week_string(freq, datas) + interval_srting + get_end_date(datas) + get_month_string(freq, datas)
rrule_string = 'FREQ=' + freq.upper() + weekstring + interval_srting \
+ enddate + monthstring + yearstring
return rrule_string
def search(self, cr, uid, args, offset=0, limit=100, order=None,
def remove_virtual_id(self, ids):
if isinstance(ids, (str, int)):
return base_calendar_id2real_id(ids)
if isinstance(ids, (list, tuple)):
res = []
for id in ids:
res.append(base_calendar_id2real_id(id))
return res
def search(self, cr, uid, args, offset=0, limit=0, order=None,
context=None, count=False):
"""
Overrides orm search method.
@param cr: the current row, from the database cursor,
@param user: the current user’s ID for security checks,
@param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
@param offset: The Number of Results to Pass
@param limit: The Number of Results to Return
@param context: A standard dictionary for contextual values
@param count: If its True the method returns number of records instead of ids
@return: List of id
"""
args_without_date = []
start_date = False
until_date = False
for arg in args:
if arg[0] not in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
if arg[0] == "id":
new_id = self.remove_virtual_id(arg[2])
new_arg = (arg[0], arg[1], new_id)
args_without_date.append(new_arg)
elif arg[0] not in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
args_without_date.append(arg)
else:
if arg[1] in ('>', '>='):
@@ -1397,12 +1241,17 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
if until_date:
continue
until_date = arg[2]
res = super(calendar_event, self).search(cr, uid, args_without_date, \
offset, limit, order, context, count)
0, 0, order, context, count=False)
res = self.get_recurrent_ids(cr, uid, res, start_date, until_date, limit, context=context)
if count:
return len(res)
elif limit:
return res[offset:offset+limit]
else:
return res
res = self.get_recurrent_ids(cr, uid, res, start_date, until_date, limit)
return res
def get_edit_all(self, cr, uid, id, vals=None):
"""
@@ -1413,22 +1262,25 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
if(vals and 'edit_all' in vals): #we jsut check edit_all
return vals['edit_all']
else: #it's a recurrent event and edit_all is already check
return meeting['recurrency'] and meeting['edit_all']
return meeting['recurrency'] and meeting['edit_all']
def _get_data(self, cr, uid, id, context=None):
res = self.read(cr, uid, [id],['date', 'date_deadline'])
return res[0]
def need_to_update(self, event_id, vals):
split_id = str(event_id).split("-")
if len(split_id) < 2:
return False
else:
date_start = vals.get('date', '')
try:
date_start = datetime.strptime(date_start, '%Y-%m-%d %H:%M:%S').strftime("%Y%m%d%H%M%S")
return date_start == split_id[1]
except Exception:
return True
def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
"""
Overrides orm write method.
@param self: the object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param ids: List of crm meeting's ids
@param vals: Dictionary of field value.
@param context: A standard dictionary for contextual values
@return: True
"""
if context is None:
context = {}
if isinstance(ids, (str, int, long)):
@@ -1440,12 +1292,15 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
for event_id in select:
real_event_id = base_calendar_id2real_id(event_id)
if(self.get_edit_all(cr, uid, event_id, vals=vals)):
edit_all = self.get_edit_all(cr, uid, event_id, vals=vals)
if edit_all:
if self.need_to_update(event_id, vals):
res = self._get_data(cr, uid, real_event_id, context=context)
vals.update(res)
event_id = real_event_id
if len(str(event_id).split('-')) > 1:
#if edit one instance of a reccurrent id
if len(str(event_id).split('-')) > 1 and not edit_all:
data = self.read(cr, uid, event_id, ['date', 'date_deadline', \
'rrule', 'duration', 'exdate'])
if data.get('rrule'):
@@ -1458,15 +1313,15 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
'edit_all': False,
'recurrency' : False,
})
new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
date_new = event_id.split('-')[1]
date_new = time.strftime("%Y%m%dT%H%M%S", \
time.strptime(date_new, "%Y%m%d%H%M%S"))
exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
res = self.write(cr, uid, [real_event_id], {'exdate': exdate})
context.update({'active_id': new_id, 'active_ids': [new_id]})
continue
if not real_event_id in new_ids:
@@ -1482,10 +1337,6 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
vals.get('allday', False),
context=context)
vals.update(updated_vals.get('value', {}))
if not 'edit_all' in vals:
vals['edit_all'] = False
if new_ids:
res = super(calendar_event, self).write(cr, uid, new_ids, vals, context=context)
@@ -1494,18 +1345,9 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
# change alarm details
alarm_obj = self.pool.get('res.alarm')
alarm_obj.do_alarm_create(cr, uid, new_ids, self._name, 'date', context=context)
return res
return res or True and False
def browse(self, cr, uid, ids, context=None, list_class=None, fields_process=None):
"""
Overrides orm browse method.
@param self: the object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param ids: List of crm meeting's ids
@param context: A standard dictionary for contextual values
@return: the object list.
"""
if isinstance(ids, (str, int, long)):
select = [ids]
else:
@@ -1518,19 +1360,29 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
return res
def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
if not context:
context = {}
if 'date' in groupby:
raise osv.except_osv(_('Warning !'), _('Group by date not supported, use the calendar view instead'))
virtual_id = context.get('virtual_id', False)
context.update({'virtual_id': False})
res = super(calendar_event, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
for re in res:
#remove the count, since the value is not consistent with the result of the search when expand the group
for groupname in groupby:
if re.get(groupname + "_count"):
del re[groupname + "_count"]
re.get('__context').update({'virtual_id' : virtual_id})
return res
def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
"""
Overrides orm Read method.Read List of fields for calendar event.
@param cr: the current row, from the database cursor,
@param user: the current user’s ID for security checks,
@param ids: List of calendar event's id.
@param fields: List of fields.
@param context: A standard dictionary for contextual values
@return: List of Dictionary of form [{‘name_of_the_field’: value, ...}, ...]
"""
# FIXME This whole id mangling has to go!
if context is None:
context = {}
if isinstance(ids, (str, int, long)):
select = [ids]
@@ -1544,9 +1396,10 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
fields.append('duration')
for base_calendar_id, real_id in select:
for base_calendar_id, real_id in select:
#REVET: Revision ID: olt@tinyerp.com-20100924131709-cqsd1ut234ni6txn
res = super(calendar_event, self).read(cr, uid, real_id, fields=fields, context=context, load=load)
if not res :
continue
ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0)
@@ -1558,41 +1411,31 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
result.append(res)
if isinstance(ids, (str, int, long)):
return result and result[0] or False
return result
def copy(self, cr, uid, id, default=None, context=None):
"""
Duplicate record on specified id.
@param self: the object pointer.
@param cr: the current row, from the database cursor,
@param id: id of record from which we duplicated.
@param context: A standard dictionary for contextual values
@return: Duplicate record id.
"""
if context is None:
context = {}
res = super(calendar_event, self).copy(cr, uid, base_calendar_id2real_id(id), default, context)
alarm_obj = self.pool.get('res.alarm')
alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
return res
def unlink(self, cr, uid, ids, context=None):
"""
Deletes records specified in ids.
@param self: the object pointer.
@param cr: the current row, from the database cursor,
@param id: List of calendar event's id.
@param context: A standard dictionary for contextual values
@return: True
"""
res = False
for event_datas in self.read(cr, uid, ids, ['date', 'rrule', 'exdate'], context=context):
event_id = event_datas['id']
if not isinstance(ids, list):
ids = [ids]
res = False
for id in ids:
event_datas = self.read(cr, uid, [id], ['date', 'rrule', 'exdate'], context=context)[0]
event_id = event_datas['id']
if self.get_edit_all(cr, uid, event_id, vals=None):
event_id = base_calendar_id2real_id(event_id)
if isinstance(event_id, (int, long)):
res = super(calendar_event, self).unlink(cr, uid, event_id, context=context)
self.pool.get('res.alarm').do_alarm_unlink(cr, uid, [event_id], self._name)
@@ -1613,15 +1456,6 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
return res
def create(self, cr, uid, vals, context=None):
"""
Create new record.
@param self: the object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param vals: dictionary of every field value.
@param context: A standard dictionary for contextual values
@return: new created record id.
"""
if context is None:
context = {}
@@ -1635,11 +1469,13 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
vals.get('allday', False),
context=context)
vals.update(updated_vals.get('value', {}))
res = super(calendar_event, self).create(cr, uid, vals, context)
alarm_obj = self.pool.get('res.alarm')
alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
return res
def do_tentative(self, cr, uid, ids, context=None, *args):
""" Makes event invitation as Tentative
@@ -1710,7 +1546,7 @@ class calendar_todo(osv.osv):
@param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
@param context: A standard dictionary for contextual values
"""
assert name == 'date'
return self.write(cr, uid, id, { 'date_start': value }, context=context)
@@ -1742,9 +1578,9 @@ class ir_attachment(osv.osv):
for arg in args:
args1.append(map(lambda x:str(x).split('-')[0], arg))
return super(ir_attachment, self).search_count(cr, user, args1, context)
def create(self, cr, uid, vals, context=None):
if context:
id = context.get('default_res_id', False)
+1 -2
View File
@@ -242,8 +242,7 @@
widget="selection" />
<group colspan="2" col="4" attrs="{'readonly': [('state','=','done')]}">
<field name="recurrency"/>
<field name="edit_all" attrs="{'invisible':[('recurrency','=', False)]}"
on_change="onchange_edit_all(rrule_type,edit_all)"/>
<field name="edit_all" attrs="{'invisible':[('recurrency','=', False)]}" />
</group>
</group>
<notebook colspan="4">
@@ -15,7 +15,8 @@
Now I will set recurrence for this event to occur monday and friday of week
-
!python {model: calendar.event}: |
self.write(cr, uid, [ref("calendar_event_technicalpresentation0")], {'fr': 1, 'mo': 1, 'interval': 1, 'rrule_type': 'weekly'})
data = {'fr': 1, 'mo': 1, 'interval': 1, 'rrule_type': 'weekly', 'end_type': 'end_date', 'end_date': '2011-05-31 00:00:00'}
self.write(cr, uid, [ref("calendar_event_technicalpresentation0")], data)
- |
In order to check that recurrent events are views successfully in calendar view,
I will open calendar view of events
@@ -27,7 +28,7 @@
I will search for one of the recurrent event and count the number of events
-
!python {model: calendar.event}: |
ids = self.search(cr, uid, [('date', '>=', '2011-04-30 16:00:00'), ('date', '<=', '2011-05-31 00:00:00')] )
ids = self.search(cr, uid, [('date', '>=', '2011-04-30 16:00:00'), ('date', '<=', '2011-05-31 00:00:00')], context={'virtual_id': True} )
assert len(ids) == 9
- |
Now I will make All day event and test it
-2
View File
@@ -19,9 +19,7 @@
#
##############################################################################
import calendar_event_edit_all
import base_calendar_invite_attendee
import base_calendar_set_exrule
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -1,163 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 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 base_calendar import base_calendar
from osv import fields, osv
from tools.translate import _
import tools
import re
months = {
1: "January", 2: "February", 3: "March", 4: "April", \
5: "May", 6: "June", 7: "July", 8: "August", 9: "September", \
10: "October", 11: "November", 12: "December"
}
class base_calendar_set_exrule(osv.osv_memory):
"""
Set Exrule.
"""
_name = "base.calendar.set.exrule"
_description = "Set Exrule"
_columns = {
'freq': fields.selection([('None', 'No Repeat'), \
('secondly', 'Secondly'), \
('minutely', 'Minutely'), \
('hourly', 'Hourly'), \
('daily', 'Daily'), \
('weekly', 'Weekly'), \
('monthly', 'Monthly'), \
('yearly', 'Yearly')], 'Frequency',required=True),
'interval': fields.integer('Interval'),
'count': fields.integer('Count'),
'mo': fields.boolean('Mon'),
'tu': fields.boolean('Tue'),
'we': fields.boolean('Wed'),
'th': fields.boolean('Thu'),
'fr': fields.boolean('Fri'),
'sa': fields.boolean('Sat'),
'su': fields.boolean('Sun'),
'select1': fields.selection([('date', 'Date of month'), \
('day', 'Day of month')], 'Option'),
'day': fields.integer('Date of month'),
'week_list': fields.selection([('MO', 'Monday'), ('TU', 'Tuesday'), \
('WE', 'Wednesday'), ('TH', 'Thursday'), \
('FR', 'Friday'), ('SA', 'Saturday'), \
('SU', 'Sunday')], 'Weekday'),
'byday': fields.selection([('1', 'First'), ('2', 'Second'), \
('3', 'Third'), ('4', 'Fourth'), \
('5', 'Fifth'), ('-1', 'Last')], 'By day'),
'month_list': fields.selection(months.items(),'Month'),
'end_date': fields.date('Repeat Until'),
}
def view_init(self, cr, uid, fields, context=None):
"""
This function checks for precondition before wizard executes
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param fields: List of fields for default value
@param context: A standard dictionary for contextual values
"""
if context is None:
context = {}
event_obj = self.pool.get(context.get('active_model'))
for event in event_obj.browse(cr, uid, context.get('active_ids', []), context=context):
if not event.rrule:
raise osv.except_osv(_("Warning !"), _("Please Apply Recurrency before applying Exception Rule."))
return False
def compute_exrule_string(self, cr, uid, ids, context=None):
"""
Compute rule string.
@param self: the object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param datas: dictionary of freq and interval value.
@return: string value which compute FREQILY;INTERVAL
"""
weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
weekstring = ''
monthstring = ''
yearstring = ''
if context is None:
context = {}
ex_id = base_calendar.base_calendar_id2real_id(context.get('active_id', False))
model = context.get('model', False)
model_obj = self.pool.get(model)
for datas in self.read(cr, uid, ids, context=context):
freq = datas.get('freq')
if freq == 'None':
model_obj.write(cr, uid, ex_id,{'exrule': ''})
return{}
interval_srting = datas.get('interval') and (';INTERVAL=' + str(datas.get('interval'))) or ''
if freq == 'weekly':
byday = map(lambda x: x.upper(), filter(lambda x: datas.get(x) and x in weekdays, datas))
if byday:
weekstring = ';BYDAY=' + ','.join(byday)
elif freq == 'monthly':
if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
if datas.get('select1')=='day':
monthstring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
elif datas.get('select1')=='date':
monthstring = ';BYMONTHDAY=' + str(datas.get('day'))
elif freq == 'yearly':
if datas.get('select1')=='date' and (datas.get('day') < 1 or datas.get('day') > 31):
raise osv.except_osv(_('Error!'), ("Please select proper Day of month"))
bymonth = ';BYMONTH=' + str(datas.get('month_list'))
if datas.get('select1')=='day':
bystring = ';BYDAY=' + datas.get('byday') + datas.get('week_list')
elif datas.get('select1')=='date':
bystring = ';BYMONTHDAY=' + str(datas.get('day'))
yearstring = bymonth + bystring
if datas.get('end_date'):
datas['end_date'] = ''.join((re.compile('\d')).findall(datas.get('end_date'))) + '235959Z'
enddate = (datas.get('count') and (';COUNT=' + str(datas.get('count'))) or '') +\
((datas.get('end_date') and (';UNTIL=' + datas.get('end_date'))) or '')
exrule_string = 'FREQ=' + freq.upper() + weekstring + interval_srting \
+ enddate + monthstring + yearstring
model_obj.write(cr, uid, ex_id,{'exrule': exrule_string})
return {'type': 'ir.actions.act_window_close'}
_defaults = {
'freq': lambda *x: 'None',
'select1': lambda *x: 'date',
'interval': lambda *x: 1,
}
base_calendar_set_exrule()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -1,71 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_base_calendar_set_exrule" model="ir.ui.view">
<field name="name">base.calendar.set.exrule.form</field>
<field name="model">base.calendar.set.exrule</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Set Exrule">
<separator string="Select range to Exclude" colspan="8"/>
<group col="8" colspan="6">
<field name="interval" />
<field name="freq" />
<field name="count" />
<field name="end_date" />
</group>
<group col="8" colspan="8" name="Select weekdays"
attrs="{'invisible' : [('freq','!=','weekly')]}">
<field name="mo" colspan="1" />
<field name="tu" colspan="1" />
<field name="we" colspan="1" />
<field name="th" colspan="1" />
<field name="fr" colspan="1" />
<field name="sa" colspan="1" />
<field name="su" colspan="1" />
<newline />
</group>
<group col="8" colspan="6"
attrs="{'invisible' : [('freq','!=','monthly'), ('freq','!=','yearly')]}">
<group col="2" colspan="1">
<field name="select1" />
</group>
<group col="2" colspan="1"
attrs="{'invisible' : [('select1','=','day')]}">
<field name="day"
attrs="{'required' : [('select1','=','date')]}"/>
</group>
<group col="3" colspan="1"
attrs="{'invisible' : [('select1','=','date')]}">
<field name="byday" string="The"
attrs="{'required' : [('select1','=','day')]}"/>
<field name="week_list" nolabel="1"
attrs="{'required' : [('select1','=','day')]}"/>
</group>
<group col="1" colspan="1"
attrs="{'invisible' : [('freq','!=','yearly')]}">
<field name="month_list" string="of"
colspan="1"
attrs="{'required' : [('freq','=','yearly')]}"/>
</group>
</group>
<group colspan="4" col="6">
<separator string="" colspan="8"/>
<label string="" colspan="2"/>
<button icon="gtk-cancel" special="cancel" string="_Cancel"/>
<button icon="gtk-ok" string="_Ok" name="compute_exrule_string" type="object"/>
</group>
</form>
</field>
</record>
<record id="action_base_calendar_set_exrule" model="ir.actions.act_window">
<field name="name">Set Exclude range</field>
<field name="res_model">base.calendar.set.exrule</field>
<field name="view_type">form</field>
<field name="view_mode">form,tree</field>
<field name="target">new</field>
</record>
</data>
</openerp>
@@ -1,98 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 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 osv import osv
from osv import fields
class calendar_event_edit_all(osv.osv_memory):
def _default_values(self, cr, uid, context=None):
""" Get Default value for Start Date
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param context: A standard dictionary for contextual values
@Return: Get Default value for Start Date
"""
context_id = context and context.get('active_id', False) or False
if context_id:
if context.get('date'):
return context.get('date')
else:
model = context.get('model', False)
model_obj = self.pool.get(model)
event = model_obj.read(cr, uid, context_id, ['name', 'location', 'alarm_id'])
return event['date']
def _default_deadline(self, cr, uid, context=None):
""" Get Default value for End Date
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param context: A standard dictionary for contextual values
@return: Get Default value for End Date
"""
context_id = context and context.get('active_id', False) or False
if context_id:
if context.get('date_deadline'):
return context.get('date_deadline')
else:
model = context.get('model', False)
model_obj = self.pool.get(model)
event = model_obj.read(cr, uid, context_id, ['name', 'location', 'alarm_id'])
return event['date_deadline']
def modify_this(self, cr, uid, ids, context=None):
"""
Modify All event for Crm Meeting.
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param ids: List of calendar event edit all’s IDs
@return: dictionary {}
"""
if context is None:
context = {}
context_id = context and context.get('active_id', False) or False
if context_id:
for datas in self.read(cr, uid, ids):
model = context.get('model', False)
model_obj = self.pool.get(model)
model_obj.modify_all(cr, uid, [context_id], datas, context=context)
return {'type': 'ir.actions.act_window_close'}
_name = "calendar.event.edit.all"
_description = "Calendar Edit all event"
_columns = {
'name': fields.char('Title', size=64, required=True),
'date': fields.datetime('Start Date', required=True),
'date_deadline': fields.datetime('End Date', required=True),
'location': fields.char('Location', size=124),
'alarm_id': fields.many2one('res.alarm', 'Reminder'),
}
_defaults = {
'date': _default_values,
'date_deadline': _default_deadline
}
calendar_event_edit_all()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_calendar_event_edit_all" model="ir.ui.view">
<field name="name">calendar.event.edit.all.form</field>
<field name="model">calendar.event.edit.all</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Edit all Occurrences">
<group colspan="4" >
<separator string="" colspan="4" />
<newline/>
<field name='name' colspan="4" />
<newline />
<field name='location' colspan="4" />
<newline />
<field name='date' />
<field name='date_deadline' />
<newline />
<field name='alarm_id'/>
</group>
<separator string="" colspan="4" />
<group colspan="4" col="6">
<button icon="gtk-cancel" special="cancel" string="Cancel"/>
<button icon="gtk-save" string="_Save" name="modify_this" type="object"/>
</group>
</form>
</field>
</record>
<record id="action_calendar_event_edit_all" model="ir.actions.act_window">
<field name="name">Edit all events</field>
<field name="res_model">calendar.event.edit.all</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_calendar_event_edit_all"/>
<field name="target">new</field>
</record>
</data>
</openerp>
+1
View File
@@ -129,6 +129,7 @@ Creates a dashboard for CRM that includes:
'test/test_crm_phonecall.yml',
'test/test_crm_recurrent_meeting.yml',
'test/test_crm_stage_changes.yml',
'test/test_crm_recurrent_meeting_case2.yml',
],
'installable': True,
'active': False,
+222 -104
View File
@@ -44,8 +44,221 @@ AVAILABLE_PRIORITIES = [
('5', 'Lowest'),
]
class crm_case(object):
"""A simple python class to be used for common functions """
class crm_base(object):
"""
Base classe for crm object,
Object that inherit from this class should have
date_open
date_closed
user_id
partner_id
partner_address_id
as field to be compatible with this class
"""
def _get_default_partner_address(self, cr, uid, context=None):
"""Gives id of default address for current user
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param context: A standard dictionary for contextual values
"""
if context is None:
context = {}
if not context.get('portal', False):
return False
return self.pool.get('res.users').browse(cr, uid, uid, context).address_id.id
def _get_default_partner(self, cr, uid, context=None):
"""Gives id of partner for current user
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param context: A standard dictionary for contextual values
"""
if context is None:
context = {}
if not context.get('portal', False):
return False
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
if not user.address_id:
return False
return user.address_id.partner_id.id
def _get_default_email(self, cr, uid, context=None):
"""Gives default email address for current user
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param context: A standard dictionary for contextual values
"""
if not context.get('portal', False):
return False
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
if not user.address_id:
return False
return user.address_id.email
def _get_default_user(self, cr, uid, context=None):
"""Gives current user id
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param context: A standard dictionary for contextual values
"""
if context and context.get('portal', False):
return False
return uid
def _get_section(self, cr, uid, context=None):
"""Gives section id for current User
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param context: A standard dictionary for contextual values
"""
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
return user.context_section_id.id or False
def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
"""This function returns value of partner email based on Partner Address
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param ids: List of case IDs
@param add: Id of Partner's address
@email: Partner's email ID
"""
if not add:
return {'value': {'email_from': False}}
address = self.pool.get('res.partner.address').browse(cr, uid, add)
if address.email:
return {'value': {'email_from': address.email, 'phone': address.phone}}
else:
return {'value': {'phone': address.phone}}
def onchange_partner_id(self, cr, uid, ids, part, email=False):
"""This function returns value of partner address based on partner
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param ids: List of case IDs
@param part: Partner's id
@email: Partner's email ID
"""
data={}
if part:
addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['contact'])
data = {'partner_address_id': addr['contact']}
data.update(self.onchange_partner_address_id(cr, uid, ids, addr['contact'])['value'])
return {'value': data}
def case_open(self, cr, uid, ids, *args):
"""Opens Case
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
cases = self.browse(cr, uid, ids)
for case in cases:
data = {'state': 'open', 'active': True}
if not case.user_id:
data['user_id'] = uid
self.write(cr, uid, case.id, data)
self._action(cr, uid, cases, 'open')
return True
def case_close(self, cr, uid, ids, *args):
"""Closes Case
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
cases = self.browse(cr, uid, ids)
cases[0].state # to fill the browse record cache
self.write(cr, uid, ids, {'state': 'done',
'date_closed': time.strftime('%Y-%m-%d %H:%M:%S'),
})
#
# We use the cache of cases to keep the old case state
#
self._action(cr, uid, cases, 'done')
return True
def case_cancel(self, cr, uid, ids, *args):
"""Cancels Case
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
cases = self.browse(cr, uid, ids)
cases[0].state # to fill the browse record cache
self.write(cr, uid, ids, {'state': 'cancel',
'active': True})
self._action(cr, uid, cases, 'cancel')
for case in cases:
message = _("The case '%s' has been cancelled.") % (case.name,)
self.log(cr, uid, case.id, message)
return True
def case_pending(self, cr, uid, ids, *args):
"""Marks case as pending
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
cases = self.browse(cr, uid, ids)
cases[0].state # to fill the browse record cache
self.write(cr, uid, ids, {'state': 'pending', 'active': True})
self._action(cr, uid, cases, 'pending')
return True
def case_reset(self, cr, uid, ids, *args):
"""Resets case as draft
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
cases = self.browse(cr, uid, ids)
cases[0].state # to fill the browse record cache
self._history(cr, uid, cases, _('Draft'))
self.write(cr, uid, ids, {'state': 'draft', 'active': True})
self._action(cr, uid, cases, 'draft')
return True
def _action(self, cr, uid, cases, state_to, scrit=None, context=None):
if context is None:
context = {}
context['state_to'] = state_to
rule_obj = self.pool.get('base.action.rule')
model_obj = self.pool.get('ir.model')
model_ids = model_obj.search(cr, uid, [('model','=',self._name)])
rule_ids = rule_obj.search(cr, uid, [('model_id','=',model_ids[0])])
return rule_obj._action(cr, uid, rule_ids, cases, scrit=scrit, context=context)
class crm_case(crm_base):
"""
A simple python class to be used for common functions
Object that inherit from this class should inherit from mailgate.thread
And need a stage_id field
And object that inherit (orm inheritance) from a class the overwrite copy
"""
def _find_lost_stage(self, cr, uid, type, section_id):
return self._find_percent_stage(cr, uid, 0.0, type, section_id)
@@ -105,35 +318,7 @@ class crm_case(object):
return {'value':{}}
return {'value':{'probability': stage.probability}}
def _get_default_partner_address(self, cr, uid, context=None):
"""Gives id of default address for current user
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param context: A standard dictionary for contextual values
"""
if context is None:
context = {}
if not context.get('portal', False):
return False
return self.pool.get('res.users').browse(cr, uid, uid, context).address_id.id
def _get_default_partner(self, cr, uid, context=None):
"""Gives id of partner for current user
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param context: A standard dictionary for contextual values
"""
if context is None:
context = {}
if not context.get('portal', False):
return False
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
if not user.address_id:
return False
return user.address_id.partner_id.id
def copy(self, cr, uid, id, default=None, context=None):
"""
@@ -145,6 +330,7 @@ class crm_case(object):
@param default: Dictionary of default values for copy.
@param context: A standard dictionary for contextual values
"""
if context is None:
context = {}
if default is None:
@@ -164,40 +350,9 @@ class crm_case(object):
})
return super(osv.osv, self).copy(cr, uid, id, default, context=context)
def _get_default_email(self, cr, uid, context=None):
"""Gives default email address for current user
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param context: A standard dictionary for contextual values
"""
if not context.get('portal', False):
return False
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
if not user.address_id:
return False
return user.address_id.email
def _get_default_user(self, cr, uid, context=None):
"""Gives current user id
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param context: A standard dictionary for contextual values
"""
if context and context.get('portal', False):
return False
return uid
def _get_section(self, cr, uid, context=None):
"""Gives section id for current User
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param context: A standard dictionary for contextual values
"""
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
return user.context_section_id.id or False
def _find_next_stage(self, cr, uid, stage_list, index, current_seq, stage_pool, context=None):
if index + 1 == len(stage_list):
@@ -274,38 +429,9 @@ class crm_case(object):
@param context: A standard dictionary for contextual values"""
return self.stage_change(cr, uid, ids, context=context, order='sequence desc')
def onchange_partner_id(self, cr, uid, ids, part, email=False):
"""This function returns value of partner address based on partner
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param ids: List of case IDs
@param part: Partner's id
@email: Partner's email ID
"""
data={}
if part:
addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['contact'])
data = {'partner_address_id': addr['contact']}
data.update(self.onchange_partner_address_id(cr, uid, ids, addr['contact'])['value'])
return {'value': data}
def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
"""This function returns value of partner email based on Partner Address
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param ids: List of case IDs
@param add: Id of Partner's address
@email: Partner's email ID
"""
if not add:
return {'value': {'email_from': False}}
address = self.pool.get('res.partner.address').browse(cr, uid, add)
if address.email:
return {'value': {'email_from': address.email, 'phone': address.phone}}
else:
return {'value': {'phone': address.phone}}
def _history(self, cr, uid, cases, keyword, history=False, subject=None, email=False, details=None, email_from=False, message_id=False, attach=[], context=None):
mailgate_pool = self.pool.get('mailgate.thread')
@@ -522,15 +648,7 @@ class crm_case(object):
cases = self.browse(cr, uid, ids2, context=context)
return self._action(cr, uid, cases, False, context=context)
def _action(self, cr, uid, cases, state_to, scrit=None, context=None):
if context is None:
context = {}
context['state_to'] = state_to
rule_obj = self.pool.get('base.action.rule')
model_obj = self.pool.get('ir.model')
model_ids = model_obj.search(cr, uid, [('model','=',self._name)])
rule_ids = rule_obj.search(cr, uid, [('model_id','=',model_ids[0])])
return rule_obj._action(cr, uid, rule_ids, cases, scrit=scrit, context=context)
def format_body(self, body):
return self.pool.get('base.action.rule').format_body(body)
+1
View File
@@ -37,6 +37,7 @@ class crm_installer(osv.osv_memory):
'thunderbird': fields.boolean('Thunderbird', help="Allows you to link your e-mail to OpenERP's documents. You can attach it to any existing one in OpenERP or create a new one."),
'outlook': fields.boolean('MS-Outlook', help="Allows you to link your e-mail to OpenERP's documents. You can attach it to any existing one in OpenERP or create a new one."),
'wiki_sale_faq': fields.boolean('Sale FAQ', help="Helps you manage wiki pages for Frequently Asked Questions on Sales Application."),
'import_google': fields.boolean('Google Import', help="Imports contacts and events from your google account."),
}
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
+2 -1
View File
@@ -19,7 +19,7 @@
<attribute name="string">Enhance your core CRM Application with additional functionalities.</attribute>
</xpath>
<xpath expr='//separator[@string="vsep"]' position='attributes'>
<attribute name='rowspan'>13</attribute>
<attribute name='rowspan'>8</attribute>
<attribute name='string'></attribute>
</xpath>
<xpath expr="//button[@string='Install Modules']" position="attributes">
@@ -37,6 +37,7 @@
<separator string="Synchronization" colspan="4" />
<field name="crm_caldav" />
<field name="fetchmail" />
<field name="import_google"/>
</group>
<group colspan="2" col="2">
<separator string="Plug-In" colspan="4" />
-1
View File
@@ -270,7 +270,6 @@ class crm_lead(crm_case, osv.osv):
data_obj = self.pool.get('ir.model.data')
value = {}
view_id = False
for case in self.browse(cr, uid, ids, context=context):
context.update({'active_id': case.id})
+3 -3
View File
@@ -20,7 +20,7 @@
##############################################################################
from base_calendar import base_calendar
from crm import crm_case
from crm import crm_base, crm_case
from osv import fields, osv
from tools.translate import _
import logging
@@ -36,13 +36,13 @@ class crm_phonecall(crm_case, osv.osv):
crm_phonecall()
class crm_meeting(crm_case, osv.osv):
class crm_meeting(crm_base, osv.osv):
""" CRM Meeting Cases """
_name = 'crm.meeting'
_description = "Meeting"
_order = "id desc"
_inherit = ['mailgate.thread',"calendar.event"]
_inherit = "calendar.event"
_columns = {
# From crm.case
'name': fields.char('Summary', size=124, required=True, states={'done': [('readonly', True)]}),
+1 -1
View File
@@ -51,7 +51,7 @@
<field name="res_model">crm.meeting</field>
<field name="view_mode">calendar,tree,form,gantt</field>
<field name="view_id" ref="crm_case_calendar_view_meet"/>
<field name="context">{"search_default_user_id":uid, 'search_default_section_id': section_id}</field>
<field name="context">{"search_default_user_id":uid, 'search_default_section_id': section_id, 'virtual_id' : True}</field>
<field name="search_view_id" ref="view_crm_case_meetings_filter"/>
<field name="help">The meeting calendar is shared between the sales teams and fully integrated with other applications such as the employee holidays or the business opportunities. You can also synchronize meetings with your mobile phone using the caldav interface.
</field>
+1 -2
View File
@@ -45,8 +45,7 @@
widget="selection" />
<group colspan="2" col="4" attrs="{'readonly': ['|', ('recurrent_uid','!=',False), ('state','=','done')]}">
<field name="recurrency"/>
<field name="edit_all" attrs="{'invisible':[('recurrency','=', False)]}"
on_change="onchange_edit_all(rrule_type,edit_all)"/>
<field name="edit_all" attrs="{'invisible':[('recurrency','=', False)]}" />
</group>
</group>
<notebook colspan="4">
+2 -3
View File
@@ -19,20 +19,19 @@
#
##############################################################################
from crm import crm_case
from crm import crm_base
from osv import fields, osv
from tools.translate import _
import crm
import time
from datetime import datetime
class crm_phonecall(crm_case, osv.osv):
class crm_phonecall(crm_base, osv.osv):
""" Phonecall Cases """
_name = "crm.phonecall"
_description = "Phonecall"
_order = "id desc"
_inherit = ['mailgate.thread']
_columns = {
# From crm.case
'id': fields.integer('ID'),
+1 -1
View File
@@ -57,7 +57,7 @@
!python {model: crm.meeting}: |
import time
from datetime import datetime, date, timedelta
ids = self.search(cr, uid, [('date', '>=', time.strftime('%Y-%m-%d 00:00:00')), ('date', '<=', (datetime.now()+timedelta(31)).strftime('%Y-%m-%d 00:00:00')), ('name', '=', 'Regarding Presentation')] )
ids = self.search(cr, uid, [('date', '>=', time.strftime('%Y-%m-%d 00:00:00')), ('date', '<=', (datetime.now()+timedelta(31)).strftime('%Y-%m-%d 00:00:00')), ('name', '=', 'Regarding Presentation')], context={'virtual_id': True})
assert len(ids) == 10
- |
@@ -0,0 +1,98 @@
-
In order to test recurrent meetings in OpenERP, I create meetings with different recurrency using different test cases.
-
I create a recurrent meeting with daily recurrency and fixed amount of time.
-
!record {model: crm.meeting, id: crm_meeting_testmeeting0}:
count: 5
date: '2011-04-13 11:04:00'
date_deadline: '2011-04-13 12:04:00'
day: 0.0
duration: 1.0
name: Test Meeting
recurrency: true
recurrent_uid: 0.0
rrule_type: daily
sequence: 0.0
-
I search for all the recurrent meetings.
-
!python {model: crm.meeting}: |
meeting_ids = self.search(cr, uid, [('id', 'in', [ref('crm_meeting_testmeeting0')]),('date','>=','2011-03-13'), ('date_deadline', '<=', '2011-05-13')], context={'virtual_id': True})
assert len(meeting_ids) == 5, 'Recurrent daily meetings are not created !'
-
Now I create a weekly meeting till a particular end date.
-
!record {model: crm.meeting, id: crm_meeting_reviewcodewithprogrammer0}:
date: '2011-04-18 11:47:00'
date_deadline: '2011-04-18 12:47:00'
day: 0.0
duration: 1.0
end_date: '2011-04-30'
end_type: end_date
fr: true
mo: true
th: true
tu: true
we: true
name: Review code with programmer
recurrency: true
recurrent_uid: 0.0
rrule_type: weekly
sequence: 0.0
-
I search for all the recurrent weekly meetings.
-
!python {model: crm.meeting}: |
meeting_ids = self.search(cr, uid, [('id', 'in', [ref('crm_meeting_reviewcodewithprogrammer0')]),('date','>=','2011-03-13'), ('date_deadline', '<=', '2011-05-13')], context={'virtual_id': True})
assert len(meeting_ids) == 10, 'Recurrent weekly meetings are not created !'
-
I want to schedule a meeting every month for Sprint review.
-
!record {model: crm.meeting, id: crm_meeting_sprintreview0}:
count: 12
date: '2011-04-01 12:01:00'
date_deadline: '2011-04-01 13:01:00'
day: 1
duration: 1.0
name: Sprint Review
recurrency: true
recurrent_uid: 0.0
rrule_type: monthly
sequence: 0.0
-
I search for all the recurrent monthly meetings.
-
!python {model: crm.meeting}: |
meeting_ids = self.search(cr, uid, [('id', 'in', [ref('crm_meeting_sprintreview0')]),('date','>=','2011-03-01'), ('date_deadline', '<=', '2012-05-13')], context={'virtual_id': True})
assert len(meeting_ids) == 12, 'Recurrent monthly meetings are not created !'
-
I change name of my monthly Sprint Review meeting.
-
!python {model: crm.meeting}: |
from base_calendar import base_calendar
base_cal_id = base_calendar.real_id2base_calendar_id(ref('crm_meeting_sprintreview0'), '2011-09-01 13:01:00')
self.write(cr, uid, [base_cal_id], {'name': 'Sprint Review for google modules', 'edit_all': True})
-
I check whether all the records are edited or not.
-
!python {model: crm.meeting}: |
meeting_ids = self.search(cr, uid, [('id', 'in', [ref('crm_meeting_sprintreview0')]),('date','>=','2011-03-01'), ('date_deadline', '<=', '2012-05-13')], context={'virtual_id': True})
meetings = self.browse(cr, uid, meeting_ids, context)
for meeting in meetings:
assert meeting.name == 'Sprint Review for google modules', 'Name not changed for id: %s' %meeting.id
-
I change description of my weekly meeting Review code with programmer.
-
!python {model: crm.meeting}: |
from base_calendar import base_calendar
base_cal_id = base_calendar.real_id2base_calendar_id(ref('crm_meeting_reviewcodewithprogrammer0'), '2011-04-25 12:47:00')
self.write(cr, uid, [base_cal_id], {'description': 'Review code of the module: sync_google_calendar.'})
-
I check whether the record is edited perfectly or not.
-
!python {model: crm.meeting}: |
meeting_ids = self.search(cr, uid, [('recurrent_uid', '=', ref('crm_meeting_reviewcodewithprogrammer0')), ('recurrent_id','=','2011-04-25 12:47:00')], context)
assert meeting_ids, 'Meeting is not edited !'
+1 -3
View File
@@ -149,7 +149,6 @@ class crm_lead2partner(osv.osv_memory):
contact_obj = self.pool.get('res.partner.address')
partner_ids = []
partner_id = False
contact_id = False
rec_ids = context and context.get('active_ids', [])
for data in self.browse(cr, uid, ids, context=context):
@@ -160,7 +159,7 @@ class crm_lead2partner(osv.osv_memory):
'user_id': lead.user_id.id,
'comment': lead.description,
})
contact_id = contact_obj.create(cr, uid, {
contact_obj.create(cr, uid, {
'partner_id': partner_id,
'name': lead.contact_name,
'phone': lead.phone,
@@ -180,7 +179,6 @@ class crm_lead2partner(osv.osv_memory):
else:
if data.partner_id:
partner_id = data.partner_id.id
contact_id = partner_obj.address_get(cr, uid, [partner_id])['default']
self.assign_partner(cr, uid, lead.id, partner_id)
partner_ids.append(partner_id)
return partner_ids
+2 -5
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
@@ -141,7 +140,7 @@ class crm_merge_opportunity(osv.osv_memory):
attach_ids = self.get_attachments(cr, uid, opp, context=context)
self.set_attachements_res_id(cr, uid, first_opportunity.id, attach_ids)
for history in opp.message_ids:
new_history = message_obj.write(cr, uid, history.id, {'res_id': first_opportunity.id, 'name' : _("From %s : %s") % (opp.name, history.name) }, context=context)
message_obj.write(cr, uid, history.id, {'res_id': first_opportunity.id, 'name' : _("From %s : %s") % (opp.name, history.name) }, context=context)
#Notification about loss of information
details = []
@@ -184,8 +183,6 @@ class crm_merge_opportunity(osv.osv_memory):
# Get Opportunity views
result = models_data._get_id(
cr, uid, 'crm', 'view_crm_case_opportunities_filter')
opportunity_view_form = models_data._get_id(
cr, uid, 'crm', 'crm_case_form_view_oppor')
opportunity_view_tree = models_data._get_id(
@@ -229,7 +226,7 @@ class crm_merge_opportunity(osv.osv_memory):
This function gets default values
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param uid: the current users ID for security checks,
@param fields: List of fields for default value
@param context: A standard dictionary for contextual values
@@ -20,11 +20,9 @@
#
##############################################################################
import base64
import time
import re
from osv import osv, fields
import tools
from tools.translate import _
class crm_lead_forward_to_partner(osv.osv_memory):
+26
View File
@@ -0,0 +1,26 @@
# -*- 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/>.
#
##############################################################################
import google_base_account
import wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+41
View File
@@ -0,0 +1,41 @@
# -*- 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/>.
#
##############################################################################
{
'name': 'Google user',
'version': '1.0',
'category': 'Generic Modules/Others',
'description': """The module adds google user in res user""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['base'],
'init_xml': [],
'update_xml': [
'google_base_account_view.xml',
'wizard/google_login_view.xml',
],
'demo_xml': [],
'installable': True,
'active': False,
'certificate': '',
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -0,0 +1,33 @@
# -*- 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 osv import fields,osv
class res_users(osv.osv):
_inherit = "res.users"
_columns = {
'gmail_user': fields.char('Username', size=64,),
'gmail_password': fields.char('Password', size=64),
}
res_users()
# vim:expandtab:smartindent:toabstop=4:softtabstop=4:shiftwidth=4:
@@ -0,0 +1,20 @@
<?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="type">form</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<xpath expr="//notebook[last()]" position="inside">
<page string=" Synchronization ">
<separator string="Google Account" colspan="4" />
<field name="gmail_user"/>
<field name="gmail_password" password="True"/>
</page>
</xpath>
</field>
</record>
</data>
</openerp>
@@ -0,0 +1,25 @@
# -*- 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/>.
#
##############################################################################
import google_login
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -0,0 +1,84 @@
# -*- 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 osv import fields,osv
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'))
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.client.ContactsClient(source='OpenERP')
if type == 'contact' :
gd_client = gdata.contacts.service.ContactsService()
if type == 'calendar':
gd_client = gdata.calendar.service.CalendarService()
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 fail 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'}
google_login()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -0,0 +1,42 @@
<?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="type">form</field>
<field name="arch" type="xml">
<form string="Google login" >
<group colspan="4" col="4" width="300">
<field name="user" />
<newline/>
<label string="ex: user@gmail.com" align="1.0" colspan="2"/>
<newline/>
<field name="password" password="True"/>
</group>
<separator string="" colspan="4"/>
<group colspan="4" col="4">
<group colspan="2"/>
<group colspan="2">
<button special="cancel" string="_Cancel" icon="gtk-cancel"/>
<button name="login" string="_Login" type="object" icon="terp-check"/>
</group>
</group>
</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>
+23
View File
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Openerp sa (<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 import_framework
import mapper
+39
View File
@@ -0,0 +1,39 @@
# -*- 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/>.
#
##############################################################################
{
'name': 'Base framework for module that need to import complex data',
'version': '0.9',
'category': 'Generic Modules',
'description': """
This module provide a class import_framework to help importing
complex data from other software
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['base'],
'init_xml': [],
'update_xml': [],
'demo_xml': [],
'test': [], #TODO provide test
'installable': True,
'active': False,
}
+432
View File
@@ -0,0 +1,432 @@
# -*- 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/>.
#
##############################################################################
import pprint
import mapper
import pooler
import tools
from threading import Thread
import datetime
import logging
pp = pprint.PrettyPrinter(indent=4)
class import_framework(Thread):
"""
This class should be extends,
get_data and get_mapping have to extends
get_state_map and initialize can be extended
for advanced purpose get_default_hook can also be extended
@see dummy import for a minimal exemple
"""
"""
for import_object, this domain will avoid to find an already existing object
"""
DO_NOT_FIND_DOMAIN = [('id', '=', 0)]
def __init__(self, obj, cr, uid, instance_name, module_name, email_to_notify=False, context=None):
Thread.__init__(self)
self.obj = obj
self.cr = cr
self.uid = uid
self.instance_name = instance_name
self.module_name = module_name
self.context = context or {}
self.email = email_to_notify
self.table_list = []
self.initialize()
"""
Abstract Method to be implemented in
the real instance
"""
def initialize(self):
"""
init before import
usually for the login
"""
pass
def get_data(self, table):
"""
@return: a list of dictionaries
each dictionnaries contains the list of pair external_field_name : value
"""
return [{}]
def get_mapping(self):
"""
@return: { TABLE_NAME : {
'model' : 'openerp.model.name',
#if true import the table if not just resolve dependencies, use for meta package, by default => True
#Not required
'import' : True or False,
#Not required
'dependencies' : [TABLE_1, TABLE_2],
#Not required
'hook' : self.function_name, #get the val dict of the object, return the same val dict or False
'map' : { @see mapper
'openerp_field_name' : 'external_field_name', or val('external_field_name')
'openerp_field_id/id' : ref(TABLE_1, 'external_id_field'), #make the mapping between the external id and the xml on the right
'openerp_field2_id/id_parent' : ref(TABLE_1,'external_id_field') #indicate a self dependencies on openerp_field2_id
'state' : map_val('state_equivalent_field', mapping), # use get_state_map to make the mapping between the value of the field and the value of the state
'text_field' : concat('field_1', 'field_2', .., delimiter=':'), #concat the value of the list of field in one
'description_field' : ppconcat('field_1', 'field_2', .., delimiter='\n\t'), #same as above but with a prettier formatting
'field' : call(callable, arg1, arg2, ..), #call the function with all the value, the function should send the value : self.callable
'field' : callable
'field' : call(method, val('external_field') interface of method is self, val where val is the value of the field
'field' : const(value) #always set this field to value
+ any custom mapper that you will define
}
},
}
"""
return {}
def default_hook(self, val):
"""
this hook will be apply on each table that don't have hook
here we define the identity hook
"""
return val
def _import_table(self, table):
data = self.get_data(table)
map = self.get_mapping()[table]['map']
hook = self.get_mapping()[table].get('hook', self.default_hook)
model = self.get_mapping()[table]['model']
final_data = []
for val in data:
res = hook(val)
if res:
final_data.append(res)
return self._save_data(model, dict(map), final_data, table)
def _save_data(self, model, mapping, datas, table):
"""
@param model: the model of the object to import
@param table : the external table where the data come from
@param mapping : definition of the mapping
@see: get_mapping
@param datas : list of dictionnaries
datas = [data_1, data_2, ..]
data_i is a map external field_name => value
and each data_i have a external id => in data_id['id']
"""
if not datas:
return (0, 'No data in this table')
mapping['id'] = 'id_new'
res = []
self_dependencies = []
for k in mapping.keys():
if '_parent' in k:
self_dependencies.append((k[:-7], mapping.pop(k)))
for data in datas:
for k, field_name in self_dependencies:
data[k] = data.get(field_name) and self._generate_xml_id(data.get(field_name), table)
data['id_new'] = self._generate_xml_id(data['id'], table)
fields, values = self._fields_mapp(data, mapping, table)
res.append(values)
model_obj = self.obj.pool.get(model)
if not model_obj:
raise ValueError("%s is not a valid model name" % model)
(p, r, warning, s) = model_obj.import_data(self.cr, self.uid, fields, res, mode='update', current_module=self.module_name, noupdate=True, context=self.context)
for (field, field_name) in self_dependencies:
self._import_self_dependencies(model_obj, field, datas)
return (len(res), warning)
def _import_self_dependencies(self, obj, parent_field, datas):
"""
@param parent_field: the name of the field that generate a self_dependencies, we call the object referenced in this
field the parent of the object
@param datas: a list of dictionnaries
Dictionnaries need to contains
id_new : the xml_id of the object
field_new : the xml_id of the parent
"""
fields = ['id', parent_field]
for data in datas:
if data.get(parent_field):
values = [data['id_new'], data[parent_field]]
obj.import_data(self.cr, self.uid, fields, [values], mode='update', current_module=self.module_name, noupdate=True, context=self.context)
def _preprocess_mapping(self, mapping):
"""
Preprocess the mapping :
after the preprocces, everything is
callable in the val of the dictionary
use to allow syntaxical sugar like 'field': 'external_field'
instead of 'field' : value('external_field')
"""
map = dict(mapping)
for key, value in map.items():
if isinstance(value, basestring):
map[key] = mapper.value(value)
#set parent for instance of dbmapper
elif isinstance(value, mapper.dbmapper):
value.set_parent(self)
return map
def _fields_mapp(self,dict_sugar, openerp_dict, table):
"""
call all the mapper and transform data
to be compatible with import_data
"""
fields=[]
data_lst = []
mapping = self._preprocess_mapping(openerp_dict)
for key,val in mapping.items():
if key not in fields and dict_sugar:
fields.append(key)
value = val(dict(dict_sugar))
data_lst.append(value)
return fields, data_lst
def _generate_xml_id(self, name, table):
"""
@param name: name of the object, has to be unique in for a given table
@param table : table where the record we want generate come from
@return: a unique xml id for record, the xml_id will be the same given the same table and same name
To be used to avoid duplication of data that don't have ids
"""
sugar_instance = self.instance_name
name = name.replace('.', '_').replace(',', '_')
return sugar_instance + "_" + table + "_" + name
"""
Public interface of the framework
those function can be use in the callable function defined in the mapping
"""
def xml_id_exist(self, table, external_id):
"""
Check if the external id exist in the openerp database
in order to check if the id exist the table where it come from
should be provide
@return the xml_id generated if the external_id exist in the database or false
"""
if not external_id:
return False
xml_id = self._generate_xml_id(external_id, table)
id = self.obj.pool.get('ir.model.data').search(self.cr, self.uid, [('name', '=', xml_id), ('module', '=', self.module_name)])
return id and xml_id or False
def name_exist(self, table, name, model):
"""
Check if the object with the name exist in the openerp database
in order to check if the id exist the table where it come from
should be provide and the model of the object
"""
fields = ['name']
data = [name]
return self.import_object(fields, data, model, table, name, [('name', '=', name)])
def get_mapped_id(self, table, external_id, context=None):
"""
@return return the databse id linked with the external_id
"""
if not external_id:
return False
xml_id = self._generate_xml_id(external_id, table)
return self.obj.pool.get('ir.model.data').get_object_reference(self.cr, self.uid, self.module_name, xml_id)[1]
def import_object_mapping(self, mapping, data, model, table, name, domain_search=False):
"""
same as import_objects but instead of two list fields and data,
this method take a dictionnaries : external_field : value
and the mapping similar to the one define in 'map' key
@see import_object, get_mapping
"""
fields, datas = self._fields_mapp(data, mapping, table)
return self.import_object(fields, datas, model, table, name, domain_search)
def import_object(self, fields, data, model, table, name, domain_search=False):
"""
This method will import an object in the openerp, usefull for field that is only a char in sugar and is an object in openerp
use import_data that will take care to create/update or do nothing with the data
this method return the xml_id
To be use, when you want to create an object or link if already exist
use DO_NOT_LINK_DOMAIN to create always a new object
@param fields: list of fields needed to create the object without id
@param data: the list of the data, in the same order as the field
ex : fields = ['firstname', 'lastname'] ; data = ['John', 'Mc donalds']
@param model: the openerp's model of the create/update object
@param table: the table where data come from in sugarcrm, no need to fit the real name of openerp name, just need to be unique
@param unique_name: the name of the object that we want to create/update get the id
@param domain_search : the domain that should find the unique existing record
@return: the xml_id of the ressources
"""
domain_search = not domain_search and [('name', 'ilike', name)] or domain_search
obj = self.obj.pool.get(model)
xml_id = self._generate_xml_id(name, table)
xml_ref = self.mapped_id_if_exist(model, domain_search, table, name)
fields.append('id')
data.append(xml_id)
obj.import_data(self.cr, self.uid, fields, [data], mode='update', current_module=self.module_name, noupdate=True, context=self.context)
return xml_ref or xml_id
def mapped_id_if_exist(self, model, domain, table, name):
"""
To be use when we want link with and existing object, if the object don't exist
just ignore.
@param domain : search domain to find existing record, should return a unique record
@param xml_id: xml_id give to the mapping
@param name: external_id or name of the object to create if there is no id
@param table: the name of the table of the object to map
@return : the xml_id if the record exist in the db, False otherwise
"""
obj = self.obj.pool.get(model)
ids = obj.search(self.cr, self.uid, domain, context=self.context)
if ids:
xml_id = self._generate_xml_id(name, table)
ir_model_data_obj = obj.pool.get('ir.model.data')
id = ir_model_data_obj._update(self.cr, self.uid, model,
self.module_name, {}, mode='update', xml_id=xml_id,
noupdate=True, res_id=ids[0], context=self.context)
return xml_id
return False
def set_table_list(self, table_list):
"""
Set the list of table to import, this method should be call before run
@param table_list: the list of external table to import
['Leads', 'Opportunity']
"""
self.table_list = table_list
def run(self):
"""
Import all data into openerp,
this is the Entry point to launch the process of import
"""
self.data_started = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.cr = pooler.get_db(self.cr.dbname).cursor()
try:
result = []
imported = set() #to invoid importing 2 times the sames modules
for table in self.table_list:
to_import = self.get_mapping()[table].get('import', True)
if not table in imported:
res = self._resolve_dependencies(self.get_mapping()[table].get('dependencies', []), imported)
result.extend(res)
if to_import:
(position, warning) = self._import_table(table)
result.append((table, position, warning))
imported.add(table)
self.cr.commit()
finally:
self.cr.close()
self.date_ended = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self._send_notification_email(result)
def _resolve_dependencies(self, dep, imported):
"""
import dependencies recursively
and avoid to import twice the same table
"""
result = []
for dependency in dep:
if not dependency in imported:
to_import = self.get_mapping()[dependency].get('import', True)
res = self._resolve_dependencies(self.get_mapping()[dependency].get('dependencies', []), imported)
result.extend(res)
if to_import:
r = self._import_table(dependency)
(position, warning) = r
result.append((dependency, position, warning))
imported.add(dependency)
return result
def _send_notification_email(self, result):
if not self.email:
return
tools.email_send(
'import_sugarcrm@module.openerp',
self.email,
self.get_email_subject(result),
self.get_email_body(result),
)
logger = logging.getLogger('import_sugarcam')
logger.info("Import finished, notification email sended")
def get_email_subject(self, result):
"""
This method define the subject of the email send by openerp at the end of import
@param result: a list of tuple
(table_name, number_of_record_created/updated, warning) for each table
@return the subject of the mail
"""
return "Import of your data finished at %s" % self.date_ended
def get_email_body(self, result):
"""
This method define the body of the email send by openerp at the end of import. The body is separated in two part
the header (@see get_body_header), and the generate body with the list of table and number of record imported.
If you want to keep this it's better to overwrite get_body_header
@param result: a list of tuple
(table_name, number_of_record_created/updated, warning) for each table
@return the subject of the mail
"""
body = "started at %s and finished at %s \n" % (self.data_started, self.date_ended)
for (table, nb, warning) in result:
if not warning:
warning = "with no warning"
else:
warning = "with warning : %s" % warning
body += "%s records were imported from table %s, %s \n" % (nb, table, warning)
return self.get_body_header(result) + "\n\n" + body
def get_body_header(self, result):
"""
@return the first sentences written in the mail's body
"""
return "The import of data \n instance name : %s \n" % self.instance_name
#For example of use see import_sugarcrm
+173
View File
@@ -0,0 +1,173 @@
# -*- 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/>.
#
##############################################################################
class mapper(object):
"""
super class for all mapper class
They are call before import data
to transform the mapping into real value that we
will import
the call function receive a dictionary with external data
'external_field' : value
"""
def __call__(self, external_values):
raise NotImplementedError()
class dbmapper(mapper):
"""
Super class for mapper that need to access to
data base or any function of the import_framework
self.parent contains a reference to the instance of
the import framework
"""
def set_parent(self, parent):
self.parent = parent
class concat(mapper):
"""
Use : contact('field_name1', 'field_name2', delimiter='_')
concat value of fields using the delimiter, delimiter is optional
and by default is a space
"""
def __init__(self, *arg, **delimiter):
self.arg = arg
self.delimiter = delimiter and delimiter.get('delimiter', ' ') or ' '
def __call__(self, external_values):
return self.delimiter.join(map(lambda x : external_values.get(x,''), self.arg))
class ppconcat(mapper):
"""
Use : contact('field_name1', 'field_name2', delimiter='_')
concat external field name and value of fields using the delimiter,
delimiter is optional and by default is a two line feeds
"""
def __init__(self, *arg, **delimiter):
self.arg = arg
self.delimiter = delimiter and delimiter.get('delimiter', ' ') or '\n\n'
def __call__(self, external_values):
return self.delimiter.join(map(lambda x : x + ": " + external_values.get(x,''), self.arg))
class const(mapper):
"""
Use : const(arg)
return always arg
"""
def __init__(self, val):
self.val = val
def __call__(self, external_values):
return self.val
class value(mapper):
"""
Use : value(external_field_name)
Return the value of the external field name
this is equivalent to the a single string
usefull for call if you want your call get the value
and don't care about the name of the field
call(self.method, value('field1'))
"""
def __init__(self, val, default='', fallback=False):
self.val = val
self.default = default
self.fallback = fallback
def __call__(self, external_values):
val = external_values.get(self.val, self.default)
if self.fallback and (not val or val == self.default):
val = external_values.get(self.fallback, self.default)
return val
class map_val(mapper):
"""
Use : map_val(external_field, val_mapping)
where val_mapping is a dictionary
with external_val : openerp_val
usefull for selection field like state
to map value
"""
def __init__(self, val, map, default='draft'):
self.val = value(val)
self.map = map
self.default = default
def __call__(self, external_values):
return self.map.get(self.val(external_values), self.default)
class ref(dbmapper):
"""
Use : ref(table_name, external_id)
return the xml_id of the ressource
to associate an already imported object with the current object
"""
def __init__(self, table, field_name):
self.table = table
self.field_name = field_name
def __call__(self, external_values):
return self.parent.xml_id_exist(self.table, external_values.get(self.field_name))
class refbyname(dbmapper):
"""
Use : refbyname(table_name, external_name, res.model)
same as ref but use the name of the ressource to find it
"""
def __init__(self, table, field_name, model):
self.table = table
self.field_name = field_name
self.model = model
def __call__(self, external_values):
v = external_values.get(self.field_name, '')
return self.parent.name_exist(self.table, v , self.model)
class call(mapper):
"""
Use : call(function, arg1, arg2)
to call the function with external val follow by the arg specified
"""
def __init__(self, fun, *arg):
self.fun = fun
self.arg = arg
def __call__(self, external_values):
args = []
for arg in self.arg:
if isinstance(arg, mapper):
args.append(arg(external_values))
else:
args.append(arg)
return self.fun(external_values, *args)
+24
View File
@@ -0,0 +1,24 @@
# -*- 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/>.
#
##############################################################################
import partner
import wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+47
View File
@@ -0,0 +1,47 @@
# -*- 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/>.
#
##############################################################################
{
'name': 'Google Import',
'version': '1.0',
'category': 'Generic Modules/Others',
'description': """The module adds google contact in partner address and add google calendar events details in Meeting""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['base', 'google_base_account','crm'],
'init_xml': [],
'update_xml': ['security/ir.model.access.csv',
'sync_google_calendar_view.xml',
'wizard/import_google_data_view.xml',
'wizard/google_import_message_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_calendar.yml',
],
'installable': True,
'active': False,
'certificate': '',
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+31
View File
@@ -0,0 +1,31 @@
# -*- 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 osv import fields,osv
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:
@@ -0,0 +1,5 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_res_users_partner_manager","google.res.partner.manager","base.model_ir_model_data","base.group_partner_manager",1,1,1,0
"access_ir_model_data_partner_manager","ir.model.data.partner.manager","base.model_res_users","base.group_partner_manager",1,1,0,0
"access_google_import_message_partner_manager","google.import.message.partner.manager","model_google_import_message","base.group_partner_manager",1,1,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_res_users_partner_manager google.res.partner.manager base.model_ir_model_data base.group_partner_manager 1 1 1 0
3 access_ir_model_data_partner_manager ir.model.data.partner.manager base.model_res_users base.group_partner_manager 1 1 0 0
4 access_google_import_message_partner_manager google.import.message.partner.manager model_google_import_message base.group_partner_manager 1 1 0 0
@@ -0,0 +1,32 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.ui.view" id="crm_case_form_view_meet_inherit">
<field name="name">CRM - Meetings Form (Inherited)</field>
<field name="model">crm.meeting</field>
<field name="type">form</field>
<field name="inherit_id" ref="crm.crm_case_form_view_meet"/>
<field name="arch" type="xml">
<field name="categ_id" position="replace">
<field name="categ_id" widget="selection"
string="Meeting Type" groups="base.group_extended"
domain="[('object_id.model', '=', 'crm.meeting'),('user_id','in',[uid, False])]" />
</field>
</field>
</record>
<record model="ir.ui.view" id="crm_case_tree_view_meet_inherit">
<field name="name">CRM - Meetings Tree (Inherited)</field>
<field name="model">crm.meeting</field>
<field name="type">tree</field>
<field name="inherit_id" ref="crm.crm_case_tree_view_meet"/>
<field name="arch" type="xml">
<field name="categ_id" position="replace">
<field name="categ_id" widget="selection"
string="Meeting Type" groups="base.group_extended"
domain="[('object_id.model', '=', 'crm.meeting'),('user_id','in',[uid, False])]" />
</field>
</field>
</record>
</data>
</openerp>
@@ -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_id0}:
user: testmail.openerp@gmail.com
password: openerptiny
- |
I login into that account.
-
!python {model: google.login}: |
self.login(cr, uid, [ref('google_login_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.import, id: synchronize_google_import_id0}:
calendar_name: all
- |
I import the events from the google calendar.
-
!python {model: synchronize.google.import}: |
self.import_google(cr, uid, [ref('synchronize_google_import_id0')], context={'calendar':True})
- |
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','=','synchronize_google')])
assert model_ids, 'Meetings not created !'
@@ -0,0 +1,34 @@
-
In order to test Importing contacts from any google account into OpenERP,
I use a gmail account and import the contact details and store them as Partner addresses.
- |
I create a record for the gmail account for which I want to import the contacts.
-
!record {model: google.login, id: google_login_id0}:
user: testmail.openerp@gmail.com
password: openerptiny
- |
I login into that account.
-
!python {model: google.login}: |
self.login(cr, uid, [ref('google_login_id0')], context)
- |
Now I select from which group I want to get the contact details.
-
!record {model: synchronize.google.import, id: synchronize_google_import_id0}:
create_partner: create_address
group_name: all
calendar_name: all
- |
I import the contacts and I also check if the contact already exists in db and updates the address.
-
!python {model: synchronize.google.import}: |
self.import_google(cr, uid, [ref('synchronize_google_import_id0')], context={'contact':True})
- |
I check whether the Contacts are created in Partner address or not.
-
!python {model: res.partner.address}: |
addr_obj = self.pool.get('ir.model.data')
data_ids = addr_obj.search(cr, uid, [('model','=','res.partner.address'),('module','=','synchronize_google')])
assert data_ids, 'Addresses not created !'
@@ -0,0 +1,36 @@
-
In order to test Importing contacts from any google account into OpenERP,
I use a gmail account and import the contact details and create Partners.
- |
I create a record for the gmail account for which I want to import the contacts.
-
!record {model: google.login, id: google_login_id1}:
user: testmail.openerp@gmail.com
password: openerptiny
- |
I login into that account.
-
!python {model: google.login}: |
self.login(cr, uid, [ref('google_login_id1')], context)
- |
Now I select from which group I want to get the contact details and I want to create partner for all contacts.
-
!record {model: synchronize.google.import, id: synchronize_google_import_id1}:
create_partner: create_all
group_name: all
- |
I import the contacts.
-
!python {model: synchronize.google.import}: |
self.import_google(cr, uid, [ref('synchronize_google_import_id1')], context={'contact':True})
- |
I check whether the Partners are created or not.
-
!python {model: res.partner.address }: |
addr_obj = self.pool.get('ir.model.data')
addr_ids = addr_obj.search(cr, uid, [])
data_ids = addr_obj.search(cr, uid, [('model','=','res.partner.address'),('module','=','synchronize_google')])
address_ids = map(lambda x: x.res_id, [child for child in addr_obj.browse(cr, uid, data_ids) if child.res_id])
partner_ids = map(lambda x: x.partner_id.id, [addr for addr in self.browse(cr, uid, address_ids) if addr.partner_id])
assert partner_ids, 'Partners not created !'
+24
View File
@@ -0,0 +1,24 @@
# -*- 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/>.
#
##############################################################################
import import_google
import import_google_data
import google_import_message
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -0,0 +1,42 @@
# -*- 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 osv import osv
from osv import fields
class google_import_message(osv.osv):
"""Import Message"""
_name = "google.import.message"
_description = "Import Message"
_columns = {
'name': fields.text('Message', readonly=True),
}
def default_get(self, cr, uid, fields, context=None):
if context == None:
context = {}
res = super(google_import_message, self).default_get(cr, uid, fields, context=context)
res.update({'name': context.get('message')})
return res
google_import_message()
@@ -0,0 +1,25 @@
<?xml version="1.0"?>
<openerp>
<data>
<!-- Import Message Form View -->
<record model="ir.ui.view" id="view_google_import_message_form">
<field name="name">google.import.message.form</field>
<field name="model">google.import.message</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Import Google Data">
<group colspan="4" col="4" width="300">
<field name='name' nolabel='1'/>
</group>
<separator string="" colspan="4"/>
<group colspan="4">
<group colspan="2"/>
<group colspan="2">
<button icon="gtk-ok" special="cancel" string="_Ok"/>
</group>
</group>
</form>
</field>
</record>
</data>
</openerp>
@@ -0,0 +1,355 @@
# -*- 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/>.
#
##############################################################################
import re
import urllib
import dateutil
import pytz
from dateutil import *
from pytz import timezone
from datetime import datetime
import time
from osv import *
try:
import gdata
import gdata.contacts.service
import gdata.calendar.service
import gdata.contacts
import gdata.calendar
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_base.import_framework import *
from import_base.mapper import *
class google_import(import_framework):
gd_client = False
calendars = False
contact = False
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
TABLE_CONTACT = 'Contact'
TABLE_ADDRESS ='Address'
TABLE_EVENT = 'Events'
def initialize(self):
google = self.obj.pool.get('google.login')
self.external_id_field = 'Id'
self.gd_client = google.google_login(self.context.get('user'),
self.context.get('password'),
self.context.get('instance'))
if self.context.get('instance') and self.context.get('instance') == 'contact':
self.contact = self.context.get('contact')
if self.context.get('instance') and self.context.get('instance') == 'calendar':
self.calendars = self.context.get('calendars')
def get_mapping(self):
return {
self.TABLE_EVENT: self.get_event_mapping(),
self.TABLE_CONTACT: self.get_contact_mapping(),
self.TABLE_ADDRESS: self.get_address_mapping(),
}
def get_data(self, table):
data_fetching_function = {
self.TABLE_CONTACT: self.get_contact,
self.TABLE_ADDRESS: self.get_contact,
self.TABLE_EVENT: self.get_events,
}
return data_fetching_function.get(table)()
def _get_tinydates(self, stime, etime, au_tz):
stime = dateutil.parser.parse(stime)
etime = dateutil.parser.parse(etime)
try:
au_dt = au_tz.normalize(stime.replace(tzinfo=pytz.utc).astimezone(au_tz))
timestring = datetime.datetime(*au_dt.timetuple()[:6]).strftime('%Y-%m-%d %H:%M:%S')
au_dt = au_tz.normalize(etime.replace(tzinfo=pytz.utc).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 = self._get_rules(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)
def get_events(self):
if 'tz' in self.context and self.context['tz']:
time_zone = self.context['tz']
else:
time_zone = tools.get_server_timezone()
au_tz = timezone(time_zone)
event_vals = []
for cal in self.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 = self.gd_client.GetCalendarEventFeed(events_query.ToUri())
for feed in event_feed.entry:
event = {
'recurrency': False,
'end_date' : False,
'end_type' : False,
'byday': 0,
'count' : 0,
'interval': 1,
'day': False,
'select1': False,
'week_list': "",
'month_list': False,
'rrule_type': False,
}
timestring = timestring_end = datetime.datetime.now().strftime(self.DATETIME_FORMAT)
if feed.when:
timestring, timestring_end = self._get_tinydates(feed.when[0].start_time, feed.when[0].end_time, au_tz)
else:
x = feed.recurrence.text.split(';')
repeat_status = self._get_repeat_status(feed.recurrence.text)
repeat_start, repeat_end, zone_time = self._get_repeat_dates(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})
event.update(repeat_status)
event.update({'id' : feed.id.text,
'DateStart': timestring,
'DateEnd': timestring_end,
'Category':event_feed.title.text,
'Name': feed.title.text or 'No title',
'Description': feed.content.text,
})
event_vals.append(event)
return event_vals
def get_event_category(self, val, name):
fields = ['name', 'object_id']
nameid = 'event_category_'+name
data = [name, 'crm.meeting']
return self.import_object(fields, data, 'crm.case.categ', "crm_case_categ", nameid, [('name', 'ilike', name)])
def get_rec(self, val):
if val.get("recurrency"):
return "1"
return "0"
def get_event_mapping(self):
return {
'model': 'crm.meeting',
'map': {
'id': 'id',
'name': 'Name',
'description': 'Description',
'email_from': 'Email',
'date': 'DateStart',
'date_deadline': 'DateEnd',
'categ_id/id': call(self.get_event_category, value('Category')),
'recurrency': self.get_rec,
'end_date' : 'end_date',
'end_type' : 'end_type',
'byday':'byday',
'count' : 'count',
'interval': 'interval',
'day': 'day',
'select1': 'date',
'week_list': 'week_list',
'month_list':'month_list',
'rrule_type': 'rrule_type',
}
}
def get_contact(self):
table = self.context.get('table')[0]
datas = []
if self.context.get('group_name'):
query = gdata.contacts.service.ContactsQuery()
query.group = self.context.get('group_name')
self.contact = self.gd_client.GetContactsFeed(query.ToUri())
else:
self.contact = self.gd_client.GetContactsFeed()
while self.contact:
for entry in self.contact.entry:
data = {}
data['id'] = entry.id.text
name = tools.ustr(entry.title.text)
if name == "None":
name = entry.email[0].address
data['name'] = name
emails = ','.join(email.address for email in entry.email)
data['email'] = emails
if table == 'Contact':
data.update({'customer': str(self.context.get('customer')),
'supplier': str(self.context.get('supplier'))})
if entry.organization:
if entry.organization.org_name:
data.update({'company': entry.organization.org_name.text})
if entry.organization.org_title:
data.update ({'function': entry.organization.org_title.text})
if entry.phone_number:
for phone in entry.phone_number:
if phone.rel == gdata.contacts.REL_WORK:
data['phone'] = phone.text
if phone.rel == gdata.contacts.PHONE_MOBILE:
data['mobile'] = phone.text
if phone.rel == gdata.contacts.PHONE_WORK_FAX:
data['fax'] = phone.text
datas.append(data)
next = self.contact.GetNextLink()
self.contact = next and self.gd_client.GetContactsFeed(next.href) or None
return datas
def get_contact_mapping(self):
return {
'model': 'res.partner',
'dependencies': [self.TABLE_ADDRESS],
'map': {
'id':'id',
'name': value('company', fallback='name'),
'customer': 'customer',
'supplier': 'supplier',
'address/id': ref(self.TABLE_ADDRESS, 'id'),
}
}
def get_partner_id(self, val):
partner_id = False
company_pool = self.obj.pool.get('res.partner')
if 'company' in val:
partner_ids = company_pool.search(self.cr, self.uid, [('name', '=', val.get('company'))])
return partner_ids and partner_ids[0] or False
contact = self.xml_id_exist(self.TABLE_CONTACT, val.get('id'))
if contact:
partner_id = self.get_mapped_id(self.TABLE_CONTACT, val.get('id'))
return partner_id
def get_address_mapping(self):
return {
'model': 'res.partner.address',
'dependencies': [],
'map': {
'id':'id',
'partner_id/.id': self.get_partner_id,
'name': 'name',
'city': 'city',
'phone': 'phone',
'mobile': 'mobile',
'email': 'email',
'fax': 'fax',
'function': 'function'
}
}
@@ -0,0 +1,182 @@
# -*- 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/>.
#
##############################################################################
import datetime
try:
import gdata
import gdata.contacts.service
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 osv import fields
from osv import osv
from tools.translate import _
from import_google import google_import
class google_login_contact(osv.osv_memory):
_inherit = 'google.login'
def _get_next_action(self, cr, uid, context=None):
data_obj = self.pool.get('ir.model.data')
data_id = data_obj._get_id(cr, uid, 'import_google', 'view_synchronize_google_contact_import_form')
view_id = False
if context.get('contact'):
data_id = data_obj._get_id(cr, uid, 'import_google', 'view_synchronize_google_contact_import_form')
if context.get('calendar'):
data_id = data_obj._get_id(cr, uid, 'import_google', 'view_synchronize_google_calendar_import_form')
if data_id:
view_id = data_obj.browse(cr, uid, data_id, context=context).res_id
value = {
'name': _('Import google'),
'view_type': 'form',
'view_mode': 'form,tree',
'res_model': 'synchronize.google.import',
'view_id': False,
'context': context,
'views': [(view_id, 'form')],
'type': 'ir.actions.act_window',
'target': 'new',
}
return value
google_login_contact()
class synchronize_google(osv.osv_memory):
_name = 'synchronize.google.import'
def _get_group(self, cr, uid, context=None):
user_obj = self.pool.get('res.users').browse(cr, uid, uid)
google=self.pool.get('google.login')
if not user_obj.gmail_user or not user_obj.gmail_password:
raise osv.except_osv(_('Warning !'), _("No Google Username or password Defined for user.\nPlease define in user view"))
gd_client = google.google_login(user_obj.gmail_user,user_obj.gmail_password,type='group')
if not gd_client:
raise osv.except_osv(_('Error'), _("Authentication fail check the user and password !"))
res = []
query = gdata.contacts.service.GroupsQuery(feed='/m8/feeds/groups/default/full')
if gd_client:
groups = gd_client.GetFeed(query.ToUri())
for grp in groups.entry:
res.append((grp.id.text, grp.title.text))
res.append(('all','All Groups'))
return res
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 = {
'create_partner': fields.selection([('create_all','Create partner for each contact'),('create_address','Import only address')],'Options'),
'customer': fields.boolean('Customer', help="Check this box to set newly created partner as Customer."),
'supplier': fields.boolean('Supplier', help="Check this box to set newly created partner as Supplier."),
'group_name': fields.selection(_get_group, "Group Name", size=32,help="Choose which group to import, By default it takes all."),
'calendar_name': fields.selection(_get_calendars, "Calendar Name", size=32),
}
_defaults = {
'create_partner': 'create_all',
'group_name': 'all',
'calendar_name': 'all',
}
def import_google(self, cr, uid, ids, context=None):
if context == None:
context = {}
if not ids:
return {'type': 'ir.actions.act_window_close'}
obj = self.browse(cr, uid, ids, context=context)[0]
cust = obj.customer
sup = obj.supplier
tables=[]
user_obj = self.pool.get('res.users').browse(cr, uid, uid)
gmail_user = user_obj.gmail_user
gmail_pwd = user_obj.gmail_password
google = self.pool.get('google.login')
if not gmail_user or not gmail_pwd:
raise osv.except_osv(_('Error'), _("Invalid login detail !\n Specify Username/Password."))
if context.get('contact'):
msg = " You're Contact are import in background, a email will be send when the process is finished to %s"%(user_obj.gmail_user)
gd_client = google.google_login(gmail_user, gmail_pwd, type='contact')
if not gd_client:
raise osv.except_osv(_('Error'), _("Please specify correct user and password !"))
if obj.group_name not in ['all']:
context.update({ 'group_name': obj.group_name})
if obj.create_partner=='create_all':
tables.append('Contact')
else:
tables.append('Address')
context.update({'user': gmail_user,
'password': gmail_pwd,
'instance': 'contact',
'table':tables,
'customer':cust,
'supplier':sup})
elif context.get('calendar'):
msg = " You're Meeting are import in background, a email will be send when the process is finished to %s"%(user_obj.gmail_user)
tables.append('Events')
current_rec = self.browse(cr, uid, ids, context=context)
calendars = False
for rec in current_rec:
if rec.calendar_name != 'all':
calendars = [rec.calendar_name]
else:
calendars = map(lambda x: x[0], [cal for cal in self._get_calendars(cr, uid, context) if cal[0] != 'all'])
context.update({'user': gmail_user,
'password': gmail_pwd,
'calendars': calendars,
'instance': 'calendar'})
imp = google_import(self, cr, uid,'import_google' , "synchronize_google", gmail_user, context)
imp.set_table_list(tables)
imp.start()
context.update({'message': msg})
obj_model = self.pool.get('ir.model.data')
model_data_ids = obj_model.search(cr,uid,[('model','=','ir.ui.view'),('name','=','view_google_import_message_form')])
resource_id = obj_model.read(cr, uid, model_data_ids, fields=['res_id'], context=context)[0]['res_id']
return {
'view_type': 'form',
'view_mode': 'form',
'res_model': 'google.import.message',
'views': [(resource_id,'form')],
'type': 'ir.actions.act_window',
'target': 'new',
'context': context,
}
synchronize_google()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -0,0 +1,91 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_synchronize_google_contact_import_form">
<field name="name">synchronize.base.form</field>
<field name="model">synchronize.google.import</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Import contacts from a google account">
<group colspan="4" col="4" width="430">
<field name="group_name" colspan="4"/>
<separator string="Import Options" colspan="4"/>
<field name="create_partner" colspan="4"/>
<newline/>
<group attrs="{'invisible': [('create_partner','!=','create_all')]}" colspan="4" col="4">
<separator string="Partner status for this group:" colspan="4"/>
<field name="customer" colspan="4"/>
<field name="supplier" colspan="4"/>
</group>
</group>
<separator string="" colspan="4"/>
<group colspan="4" col="4">
<group colspan="2" col="2"/>
<group colspan="2" col="2">
<button special="cancel" string="_Cancel" icon="gtk-cancel"/>
<button name="import_google" string="_Import Contacts" type="object" icon="terp-personal+"/>
</group>
</group>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_synchronize_google_calendar_import_form">
<field name="name">synchronize.google.calendar.form</field>
<field name="model">synchronize.google.import</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Import Google Calendar Events">
<group colspan="4" col="4" width="320">
<field name="calendar_name" />
</group>
<separator string="" colspan="4"/>
<group colspan="4" col="4">
<group colspan="2" col="2"/>
<group colspan="2" col="2">
<button special="cancel" string="_Cancel" icon="gtk-cancel"/>
<button name="import_google" string="_Import Events" type="object" icon="terp-calendar"/>
</group>
</group>
</form>
</field>
</record>
<!--
Login Action
-->
<record model="ir.actions.act_window" id="act_google_login_form">
<field name="name">Import Google Calendar</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="context">{'calendar':True}</field>
<field name="view_id" ref="google_base_account.view_google_login_form" />
</record>
<record model="ir.actions.act_window" id="act_google_login_contact_form">
<field name="name">Import Google Contacts</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="context">{'contact':True}</field>
<field name="view_id" ref="google_base_account.view_google_login_form" />
</record>
<menuitem id="menu_sync_contact"
parent="base.menu_address_book"
action="act_google_login_contact_form"
sequence="40" />
<menuitem id="menu_sync_calendar"
parent="crm.menu_meeting_sale"
action="act_google_login_form"
sequence="20" />
</data>
</openerp>