[MERGE] import google project + calendar cleaning v1
bzr revid: tfr@openerp.com-20110627100343-msagwsa1nba29497
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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)]}),
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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 !'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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
|
||||
|
||||
|
@@ -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 !'
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user