Merge lp:~openerp-dev/openobject-addons/trunk-email-invitation-template-aja into lp:openobject-addons

Proposed by ajay javiya (OpenERP)
Status: Needs review
Proposed branch: lp:~openerp-dev/openobject-addons/trunk-email-invitation-template-aja
Merge into: lp:openobject-addons
Diff against target: 1729 lines (+803/-540)
20 files modified
base_calendar/__init__.py (+1/-0)
base_calendar/__openerp__.py (+8/-1)
base_calendar/base_calendar.py (+159/-526)
base_calendar/controllers/__init__.py (+3/-0)
base_calendar/controllers/main.py (+77/-0)
base_calendar/crm_meeting.py (+110/-3)
base_calendar/crm_meeting_data.xml (+128/-0)
base_calendar/crm_meeting_view.xml (+19/-7)
base_calendar/doc/calnedar_attendee.rst (+23/-0)
base_calendar/doc/changelog.rst (+29/-0)
base_calendar/doc/crm_meeting.rst (+38/-0)
base_calendar/security/calendar_security.xml (+1/-1)
base_calendar/security/ir.model.access.csv (+1/-1)
base_calendar/static/src/css/base_calender.css (+65/-0)
base_calendar/static/src/js/base_calendar.js (+80/-0)
base_calendar/static/src/xml/base_calendar.xml (+43/-0)
portal/security/ir.model.access.csv (+1/-1)
portal_crm/__openerp__.py (+1/-0)
portal_crm/security/ir.model.access.csv (+3/-0)
portal_hr_employees/hr_employee.py (+13/-0)
To merge this branch: bzr merge lp:~openerp-dev/openobject-addons/trunk-email-invitation-template-aja
Reviewer Review Type Date Requested Status
OpenERP Core Team Pending
Review via email: mp+190868@code.launchpad.net

This proposal supersedes a proposal from 2013-07-03.

Description of the change

Hello,
Email Template of Meeting Invitation:
---------------------------------------------------
    - REM : remove static HTML design of email of meeting invitation
    - ADD : new better layout of email of meeting invitation using MAKO Template.
     -ADD : Qweb template ,to directly allow any invited user to accept , decline a meeting, without log in to a system.
Calendar attendee:
-------------------
    - ADD : field access_token and new_invitation_token method to generate a unique token for new attendee.
    - REF : do_accept/do_decline method and create_attendees method.
Web Widget:
-----------------
    - Many2Many_invite widget, to display a status of every invited attendees of meeting.
crm_meeting:
-------------------
    -ADD : function field is_attendee,attendee_status,event_time.
    -ADD : _find_user_attendee method to check attendee is internal user.
    -ADD : _compute_time compute a time from date_start and duration to user's tz.
    -ADD : do_accept/do_decline , get_attendee , get_interval method.
crm_meeting_view:
----------------
    -ADD : do_accept abd do_decline button in meeting form view
    -ADD : add chatter , to show a log of meeting.
Calendar secutrity:
--------------------
    - ADD : record rule , to restrict an user to show persional invitation on meeting.

Thank You.

To post a comment you must log in.
8848. By ajay javiya (OpenERP)

[ADD]: change log

8849. By ajay javiya (OpenERP)

[MERGE]: with trunk

8850. By ajay javiya (OpenERP)

[IMP]: Fix trace back and improve emil template view

8851. By ajay javiya (OpenERP)

[IMP]:email template view for multiline description(what)

8852. By ajay javiya (OpenERP)

[IMP]:many2many widget for attendee status

8853. By ajay javiya (OpenERP)

[IMP]: code and allow to visible invitation tab to group with technical feature

8854. By ajay javiya (OpenERP)

[FIX]: Traceback , create an recurrent meeting and then accept/decline from invitation tab

8855. By ajay javiya (OpenERP)

[MERGE]: with trunk

8856. By ajay javiya (OpenERP)

[REM]: Rmove unwanted access rights and record rule

8857. By ajay javiya (OpenERP)

[IMP]: improve align in email template

8858. By ajay javiya (OpenERP)

[REV]: noupdate in data

8859. By ajay javiya (OpenERP)

[MERGE]: with trunk

8860. By ajay javiya (OpenERP)

[MERGE]: with trunk

8861. By ajay javiya (OpenERP)

[IMP]: if opt_out is checked invitation mail is not send to attendee

8862. By Antony Lesuisse (OpenERP)

slaughtering

8863. By Antony Lesuisse (OpenERP)

more slaughtering

Unmerged revisions

8863. By Antony Lesuisse (OpenERP)

more slaughtering

8862. By Antony Lesuisse (OpenERP)

slaughtering

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'base_calendar/__init__.py'
2--- base_calendar/__init__.py 2012-11-29 22:26:45 +0000
3+++ base_calendar/__init__.py 2013-11-07 16:05:43 +0000
4@@ -21,5 +21,6 @@
5
6 import base_calendar
7 import crm_meeting
8+import controllers
9
10 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
11
12=== modified file 'base_calendar/__openerp__.py'
13--- base_calendar/__openerp__.py 2013-08-13 09:55:46 +0000
14+++ base_calendar/__openerp__.py 2013-11-07 16:05:43 +0000
15@@ -47,11 +47,18 @@
16 'base_calendar_data.xml',
17 'crm_meeting_data.xml',
18 ],
19+ 'js': [
20+ 'static/src/js/*.js'
21+ ],
22+ 'qweb': ['static/src/xml/*.xml'],
23+ 'css': [
24+ 'static/src/css/base_calender.css'
25+ ],
26 'test' : ['test/base_calendar_test.yml'],
27 'installable': True,
28 'application': True,
29 'auto_install': False,
30- 'images': ['images/base_calendar1.jpeg','images/base_calendar2.jpeg','images/base_calendar3.jpeg','images/base_calendar4.jpeg',],
31+ 'images': ['images/base_calendar1.jpeg','images/base_calendar2.jpeg','images/base_calendar3.jpeg','images/base_calendar4.jpeg'],
32 }
33
34 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
35
36=== modified file 'base_calendar/base_calendar.py'
37--- base_calendar/base_calendar.py 2013-10-06 11:58:08 +0000
38+++ base_calendar/base_calendar.py 2013-11-07 16:05:43 +0000
39@@ -28,10 +28,9 @@
40 import pytz
41 import re
42 import time
43-
44+import hashlib
45 from openerp import tools, SUPERUSER_ID
46 import openerp.service.report
47-
48 months = {
49 1: "January", 2: "February", 3: "March", 4: "April", \
50 5: "May", 6: "June", 7: "July", 8: "August", 9: "September", \
51@@ -114,96 +113,6 @@
52 return '%d-%s' % (real_id, recurrent_date)
53 return real_id
54
55-html_invitation = """
56-<html>
57-<head>
58-<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
59-<title>%(name)s</title>
60-</head>
61-<body>
62-<table border="0" cellspacing="10" cellpadding="0" width="100%%"
63- style="font-family: Arial, Sans-serif; font-size: 14">
64- <tr>
65- <td width="100%%">Hello,</td>
66- </tr>
67- <tr>
68- <td width="100%%">You are invited for <i>%(company)s</i> Event.</td>
69- </tr>
70- <tr>
71- <td width="100%%">Below are the details of event. Hours and dates expressed in %(timezone)s time.</td>
72- </tr>
73-</table>
74-
75-<table cellspacing="0" cellpadding="5" border="0" summary=""
76- style="width: 90%%; font-family: Arial, Sans-serif; border: 1px Solid #ccc; background-color: #f6f6f6">
77- <tr valign="center" align="center">
78- <td bgcolor="DFDFDF">
79- <h3>%(name)s</h3>
80- </td>
81- </tr>
82- <tr>
83- <td>
84- <table cellpadding="8" cellspacing="0" border="0"
85- style="font-size: 14" summary="Eventdetails" bgcolor="f6f6f6"
86- width="90%%">
87- <tr>
88- <td width="21%%">
89- <div><b>Start Date</b></div>
90- </td>
91- <td><b>:</b></td>
92- <td>%(start_date)s</td>
93- <td width="15%%">
94- <div><b>End Date</b></div>
95- </td>
96- <td><b>:</b></td>
97- <td width="25%%">%(end_date)s</td>
98- </tr>
99- <tr valign="top">
100- <td><b>Description</b></td>
101- <td><b>:</b></td>
102- <td colspan="3">%(description)s</td>
103- </tr>
104- <tr valign="top">
105- <td>
106- <div><b>Location</b></div>
107- </td>
108- <td><b>:</b></td>
109- <td colspan="3">%(location)s</td>
110- </tr>
111- <tr valign="top">
112- <td>
113- <div><b>Event Attendees</b></div>
114- </td>
115- <td><b>:</b></td>
116- <td colspan="3">
117- <div>
118- <div>%(attendees)s</div>
119- </div>
120- </td>
121- </tr>
122- </table>
123- </td>
124- </tr>
125-</table>
126-<table border="0" cellspacing="10" cellpadding="0" width="100%%"
127- style="font-family: Arial, Sans-serif; font-size: 14">
128- <tr>
129- <td width="100%%">From:</td>
130- </tr>
131- <tr>
132- <td width="100%%">%(user)s</td>
133- </tr>
134- <tr valign="top">
135- <td width="100%%">-<font color="a7a7a7">-------------------------</font></td>
136- </tr>
137- <tr>
138- <td width="100%%"> <font color="a7a7a7">%(sign)s</font></td>
139- </tr>
140-</table>
141-</body>
142-</html>
143-"""
144-
145 class calendar_attendee(osv.osv):
146 """
147 Calendar Attendee Information
148@@ -313,13 +222,6 @@
149 ('group', 'Group'), ('resource', 'Resource'), \
150 ('room', 'Room'), ('unknown', 'Unknown') ], \
151 'Invite Type', help="Specify the type of Invitation"),
152- 'member': fields.char('Member', size=124,
153- help="Indicate the groups that the attendee belongs to"),
154- 'role': fields.selection([('req-participant', 'Participation required'), \
155- ('chair', 'Chair Person'), \
156- ('opt-participant', 'Optional Participation'), \
157- ('non-participant', 'For information Purpose')], 'Role', \
158- help='Participation role for the calendar user'),
159 'state': fields.selection([('needs-action', 'Needs Action'),
160 ('tentative', 'Uncertain'),
161 ('declined', 'Declined'),
162@@ -328,39 +230,13 @@
163 help="Status of the attendee's participation"),
164 'rsvp': fields.boolean('Required Reply?',
165 help="Indicats whether the favor of a reply is requested"),
166- 'delegated_to': fields.function(_compute_data, \
167- string='Delegated To', type="char", size=124, store=True, \
168- multi='delegated_to', help="The users that the original \
169-request was delegated to"),
170- 'delegated_from': fields.function(_compute_data, string=\
171- 'Delegated From', type="char", store=True, size=124, multi='delegated_from'),
172- 'parent_ids': fields.many2many('calendar.attendee', 'calendar_attendee_parent_rel', \
173- 'attendee_id', 'parent_id', 'Delegrated From'),
174- 'child_ids': fields.many2many('calendar.attendee', 'calendar_attendee_child_rel', \
175- 'attendee_id', 'child_id', 'Delegrated To'),
176- 'sent_by': fields.function(_compute_data, string='Sent By', \
177- type="char", multi='sent_by', store=True, size=124, \
178- help="Specify the user that is acting on behalf of the calendar user"),
179- 'sent_by_uid': fields.function(_compute_data, string='Sent By User', \
180- type="many2one", relation="res.users", multi='sent_by_uid'),
181 'cn': fields.function(_compute_data, string='Common name', \
182 type="char", size=124, multi='cn', store=True),
183- 'dir': fields.char('URI Reference', size=124, help="Reference to the URI\
184-that points to the directory information corresponding to the attendee."),
185- 'language': fields.function(_compute_data, string='Language', \
186- type="selection", selection=_lang_get, multi='language', \
187- store=True, help="To specify the language for text values in a\
188-property or property parameter."),
189- 'user_id': fields.many2one('res.users', 'User'),
190+ 'dir': fields.char('URI Reference', size=124, help="Reference to the URI that points to the directory information corresponding to the attendee."),
191 'partner_id': fields.many2one('res.partner', 'Contact'),
192 'email': fields.char('Email', size=124, help="Email of Invited Person"),
193- 'event_date': fields.function(_compute_data, string='Event Date', \
194- type="datetime", multi='event_date'),
195- 'event_end_date': fields.function(_compute_data, \
196- string='Event End Date', type="datetime", \
197- multi='event_end_date'),
198- 'ref': fields.reference('Event Ref', selection=openerp.addons.base.res.res_request.referencable_models, size=128),
199 'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"),
200+ 'access_token':fields.char('Invitation Token', size=256),
201 }
202 _defaults = {
203 'state': 'needs-action',
204@@ -369,10 +245,9 @@
205 'cutype': 'individual',
206 }
207
208-
209 def copy(self, cr, uid, id, default=None, context=None):
210 raise osv.except_osv(_('Warning!'), _('You cannot duplicate a calendar attendee.'))
211-
212+
213 def onchange_partner_id(self, cr, uid, ids, partner_id,context=None):
214 """
215 Make entry on email and availbility on change of partner_id field.
216@@ -388,7 +263,7 @@
217 return {'value': {'email': ''}}
218 partner = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
219 return {'value': {'email': partner.email}}
220-
221+
222 def get_ics_file(self, cr, uid, event_obj, context=None):
223 """
224 Returns iCalendar file for the event invitation.
225@@ -474,53 +349,43 @@
226 @param email_from: email address for user sending the mail
227 @return: True
228 """
229- company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
230- for att in self.browse(cr, uid, ids, context=context):
231- sign = att.sent_by_uid and att.sent_by_uid.signature or ''
232- sign = '<br>'.join(sign and sign.split('\n') or [])
233- res_obj = att.ref
234+ mail_id = []
235+ data_pool = self.pool.get('ir.model.data')
236+ mail_pool = self.pool.get('mail.mail')
237+ template_pool = self.pool.get('email.template')
238+ local_context = context.copy()
239+ color = {
240+ 'needs-action' : 'grey',
241+ 'accepted' :'green',
242+ 'tentative' :'#FFFF00',
243+ 'declined':'red',
244+ 'delegated':'grey'
245+ }
246+ for attendee in self.browse(cr, uid, ids, context=context):
247+ res_obj = attendee.ref
248 if res_obj:
249- att_infos = []
250- sub = res_obj.name
251- other_invitation_ids = self.search(cr, uid, [('ref', '=', res_obj._name + ',' + str(res_obj.id))])
252-
253- for att2 in self.browse(cr, uid, other_invitation_ids):
254- att_infos.append(((att2.user_id and att2.user_id.name) or \
255- (att2.partner_id and att2.partner_id.name) or \
256- att2.email) + ' - Status: ' + att2.state.title())
257- #dates and times are gonna be expressed in `tz` time (local timezone of the `uid`)
258- tz = context.get('tz', pytz.timezone('UTC'))
259- #res_obj.date and res_obj.date_deadline are in UTC in database so we use context_timestamp() to transform them in the `tz` timezone
260- date_start = fields.datetime.context_timestamp(cr, uid, datetime.strptime(res_obj.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context)
261- date_stop = False
262- if res_obj.date_deadline:
263- date_stop = fields.datetime.context_timestamp(cr, uid, datetime.strptime(res_obj.date_deadline, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context)
264- body_vals = {'name': res_obj.name,
265- 'start_date': date_start,
266- 'end_date': date_stop,
267- 'timezone': tz,
268- 'description': res_obj.description or '-',
269- 'location': res_obj.location or '-',
270- 'attendees': '<br>'.join(att_infos),
271- 'user': res_obj.user_id and res_obj.user_id.name or 'OpenERP User',
272- 'sign': sign,
273- 'company': company
274- }
275- body = html_invitation % body_vals
276- if mail_to and email_from:
277+ model,template_id = data_pool.get_object_reference(cr, uid, 'base_calendar', "crm_email_template_meeting_invitation")
278+ model,act_id = data_pool.get_object_reference(cr, uid, 'base_calendar', "view_crm_meeting_calendar")
279+ action_id = self.pool.get('ir.actions.act_window').search(cr, uid, [('view_id','=',act_id)], context=context)
280+ base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='http://localhost:8069', context=context)
281+ body = template_pool.browse(cr, uid, template_id, context=context).body_html
282+ if attendee.email and email_from:
283 ics_file = self.get_ics_file(cr, uid, res_obj, context=context)
284- vals = {'email_from': email_from,
285- 'email_to': mail_to,
286- 'state': 'outgoing',
287- 'subject': sub,
288- 'body_html': body,
289- 'auto_delete': True}
290+ local_context['att_obj'] = attendee
291+ local_context['color'] = color
292+ local_context['action_id'] = action_id[0]
293+ local_context['dbname'] = cr.dbname
294+ local_context['base_url'] = base_url
295+ vals = template_pool.generate_email(cr, uid, template_id, res_obj.id, context=local_context)
296 if ics_file:
297 vals['attachment_ids'] = [(0,0,{'name': 'invitation.ics',
298- 'datas_fname': 'invitation.ics',
299- 'datas': str(ics_file).encode('base64')})]
300- self.pool.get('mail.mail').create(cr, uid, vals, context=context)
301- return True
302+ 'datas_fname': 'invitation.ics',
303+ 'datas': str(ics_file).encode('base64')})]
304+ if not attendee.partner_id.opt_out:
305+ mail_id.append(mail_pool.create(cr, uid, vals, context=context))
306+ if mail_id:
307+ return mail_pool.send(cr, uid, mail_id, context=context)
308+ return False
309
310 def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
311 """
312@@ -561,8 +426,13 @@
313 """
314 if context is None:
315 context = {}
316-
317- return self.write(cr, uid, ids, {'state': 'accepted'}, context)
318+ meeting_obj = self.pool.get('crm.meeting')
319+ res = self.write(cr, uid, ids, {'state': 'accepted'}, context)
320+ for attandee in self.browse(cr, uid, ids, context=context):
321+ meeting_ids = meeting_obj.search(cr, uid, [('attendee_ids', '=', attandee.id)], context=context)
322+ if meeting_ids:
323+ meeting_obj.message_post(cr, uid, get_real_ids(meeting_ids), body=_(("%s has accepted invitation") % (attandee.cn)), context=context)
324+ return res
325
326 def do_decline(self, cr, uid, ids, context=None, *args):
327 """
328@@ -576,7 +446,13 @@
329 """
330 if context is None:
331 context = {}
332- return self.write(cr, uid, ids, {'state': 'declined'}, context)
333+ meeting_obj = self.pool.get('crm.meeting')
334+ res = self.write(cr, uid, ids, {'state': 'declined'}, context)
335+ for attandee in self.browse(cr, uid, ids, context=context):
336+ meeting_ids = meeting_obj.search(cr, uid, [('attendee_ids', '=', attandee.id)], context=context)
337+ if meeting_ids:
338+ meeting_obj.message_post(cr, uid, get_real_ids(meeting_ids), body=_(("%s has declined invitation") % (attandee.cn)), context=context)
339+ return res
340
341 def create(self, cr, uid, vals, context=None):
342 """
343@@ -598,291 +474,6 @@
344 return res
345
346
347-class res_alarm(osv.osv):
348- """Resource Alarm """
349- _name = 'res.alarm'
350- _description = 'Basic Alarm Information'
351-
352- _columns = {
353- 'name':fields.char('Name', size=256, required=True),
354- 'trigger_occurs': fields.selection([('before', 'Before'), \
355- ('after', 'After')], \
356- 'Triggers', required=True),
357- 'trigger_interval': fields.selection([('minutes', 'Minutes'), \
358- ('hours', 'Hours'), \
359- ('days', 'Days')], 'Interval', \
360- required=True),
361- 'trigger_duration': fields.integer('Duration', required=True),
362- 'trigger_related': fields.selection([('start', 'The event starts'), \
363- ('end', 'The event ends')], \
364- 'Related to', required=True),
365- 'duration': fields.integer('Duration', help="""Duration' and 'Repeat' \
366-are both optional, but if one occurs, so MUST the other"""),
367- 'repeat': fields.integer('Repeat'),
368- 'active': fields.boolean('Active', help="If the active field is set to \
369-true, it will allow you to hide the event alarm information without removing it.")
370- }
371- _defaults = {
372- 'trigger_interval': 'minutes',
373- 'trigger_duration': 5,
374- 'trigger_occurs': 'before',
375- 'trigger_related': 'start',
376- 'active': 1,
377- }
378-
379- def do_alarm_create(self, cr, uid, ids, model, date, context=None):
380- """
381- Create Alarm for event.
382- @param cr: the current row, from the database cursor,
383- @param uid: the current user's ID for security checks,
384- @param ids: List of res alarm's IDs.
385- @param model: Model name.
386- @param date: Event date
387- @param context: A standard dictionary for contextual values
388- @return: True
389- """
390- if context is None:
391- context = {}
392- alarm_obj = self.pool.get('calendar.alarm')
393- res_alarm_obj = self.pool.get('res.alarm')
394- ir_obj = self.pool.get('ir.model')
395- model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
396-
397- model_obj = self.pool[model]
398- for data in model_obj.browse(cr, uid, ids, context=context):
399-
400- basic_alarm = data.alarm_id
401- cal_alarm = data.base_calendar_alarm_id
402- if (not basic_alarm and cal_alarm) or (basic_alarm and cal_alarm):
403- new_res_alarm = None
404- # Find for existing res.alarm
405- duration = cal_alarm.trigger_duration
406- interval = cal_alarm.trigger_interval
407- occurs = cal_alarm.trigger_occurs
408- related = cal_alarm.trigger_related
409- domain = [('trigger_duration', '=', duration), ('trigger_interval', '=', interval), ('trigger_occurs', '=', occurs), ('trigger_related', '=', related)]
410- alarm_ids = res_alarm_obj.search(cr, uid, domain, context=context)
411- if not alarm_ids:
412- val = {
413- 'trigger_duration': duration,
414- 'trigger_interval': interval,
415- 'trigger_occurs': occurs,
416- 'trigger_related': related,
417- 'name': str(duration) + ' ' + str(interval) + ' ' + str(occurs)
418- }
419- new_res_alarm = res_alarm_obj.create(cr, uid, val, context=context)
420- else:
421- new_res_alarm = alarm_ids[0]
422- cr.execute('UPDATE %s ' % model_obj._table + \
423- ' SET base_calendar_alarm_id=%s, alarm_id=%s ' \
424- ' WHERE id=%s',
425- (cal_alarm.id, new_res_alarm, data.id))
426-
427- self.do_alarm_unlink(cr, uid, [data.id], model)
428- if basic_alarm:
429- vals = {
430- 'action': 'display',
431- 'description': data.description,
432- 'name': data.name,
433- 'attendee_ids': [(6, 0, map(lambda x:x.id, data.attendee_ids))],
434- 'trigger_related': basic_alarm.trigger_related,
435- 'trigger_duration': basic_alarm.trigger_duration,
436- 'trigger_occurs': basic_alarm.trigger_occurs,
437- 'trigger_interval': basic_alarm.trigger_interval,
438- 'duration': basic_alarm.duration,
439- 'repeat': basic_alarm.repeat,
440- 'state': 'run',
441- 'event_date': data[date],
442- 'res_id': data.id,
443- 'model_id': model_id,
444- 'user_id': uid
445- }
446- alarm_id = alarm_obj.create(cr, uid, vals)
447- cr.execute('UPDATE %s ' % model_obj._table + \
448- ' SET base_calendar_alarm_id=%s, alarm_id=%s '
449- ' WHERE id=%s', \
450- ( alarm_id, basic_alarm.id, data.id) )
451- return True
452-
453- def do_alarm_unlink(self, cr, uid, ids, model, context=None):
454- """
455- Delete alarm specified in ids
456- @param cr: the current row, from the database cursor,
457- @param uid: the current user's ID for security checks,
458- @param ids: List of res alarm's IDs.
459- @param model: Model name for which alarm is to be cleared.
460- @return: True
461- """
462- if context is None:
463- context = {}
464- alarm_obj = self.pool.get('calendar.alarm')
465- ir_obj = self.pool.get('ir.model')
466- model_id = ir_obj.search(cr, uid, [('model', '=', model)])[0]
467- model_obj = self.pool[model]
468- for data in model_obj.browse(cr, uid, ids, context=context):
469- alarm_ids = alarm_obj.search(cr, uid, [('model_id', '=', model_id), ('res_id', '=', data.id)])
470- if alarm_ids:
471- alarm_obj.unlink(cr, uid, alarm_ids)
472- cr.execute('Update %s set base_calendar_alarm_id=NULL, alarm_id=NULL\
473- where id=%%s' % model_obj._table,(data.id,))
474- return True
475-
476-
477-class calendar_alarm(osv.osv):
478- _name = 'calendar.alarm'
479- _description = 'Event alarm information'
480- _inherit = 'res.alarm'
481- __attribute__ = {}
482-
483- _columns = {
484- 'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'),
485- 'name': fields.char('Summary', size=124, help="""Contains the text to be \
486- used as the message subject for email \
487- or contains the text to be used for display"""),
488- 'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \
489- ('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \
490- required=True, help="Defines the action to be invoked when an alarm is triggered"),
491- 'description': fields.text('Description', help='Provides a more complete \
492- description of the calendar component, than that \
493- provided by the "SUMMARY" property'),
494- 'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \
495- 'alarm_id', 'attendee_id', 'Attendees', readonly=True),
496- 'attach': fields.binary('Attachment', help="""* Points to a sound resource,\
497- which is rendered when the alarm is triggered for audio,
498- * File which is intended to be sent as message attachments for email,
499- * Points to a procedure resource, which is invoked when\
500- the alarm is triggered for procedure."""),
501- 'res_id': fields.integer('Resource ID'),
502- 'model_id': fields.many2one('ir.model', 'Model'),
503- 'user_id': fields.many2one('res.users', 'Owner'),
504- 'event_date': fields.datetime('Event Date'),
505- 'event_end_date': fields.datetime('Event End Date'),
506- 'trigger_date': fields.datetime('Trigger Date', readonly="True"),
507- 'state':fields.selection([
508- ('draft', 'Draft'),
509- ('run', 'Run'),
510- ('stop', 'Stop'),
511- ('done', 'Done'),
512- ], 'Status', select=True, readonly=True),
513- }
514-
515- _defaults = {
516- 'action': 'email',
517- 'state': 'run',
518- }
519-
520- def create(self, cr, uid, vals, context=None):
521- """
522- Overrides orm create method.
523- @param self: The object pointer
524- @param cr: the current row, from the database cursor,
525- @param vals: dictionary of fields value.{'name_of_the_field': value, ...}
526- @param context: A standard dictionary for contextual values
527- @return: new record id for calendar_alarm.
528- """
529- if context is None:
530- context = {}
531- event_date = vals.get('event_date', False)
532- if event_date:
533- dtstart = datetime.strptime(vals['event_date'], "%Y-%m-%d %H:%M:%S")
534- if vals['trigger_interval'] == 'days':
535- delta = timedelta(days=vals['trigger_duration'])
536- if vals['trigger_interval'] == 'hours':
537- delta = timedelta(hours=vals['trigger_duration'])
538- if vals['trigger_interval'] == 'minutes':
539- delta = timedelta(minutes=vals['trigger_duration'])
540- trigger_date = dtstart + (vals['trigger_occurs'] == 'after' and delta or -delta)
541- vals['trigger_date'] = trigger_date
542- res = super(calendar_alarm, self).create(cr, uid, vals, context=context)
543- return res
544-
545- def do_run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, \
546- context=None):
547- """Scheduler for event reminder
548- @param self: The object pointer
549- @param cr: the current row, from the database cursor,
550- @param uid: the current user's ID for security checks,
551- @param ids: List of calendar alarm's IDs.
552- @param use_new_cursor: False or the dbname
553- @param context: A standard dictionary for contextual values
554- """
555- if context is None:
556- context = {}
557- current_datetime = datetime.now()
558- alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context)
559-
560- mail_to = ""
561-
562- for alarm in self.browse(cr, uid, alarm_ids, context=context):
563- next_trigger_date = None
564- update_vals = {}
565- model_obj = self.pool[alarm.model_id.model]
566- res_obj = model_obj.browse(cr, uid, alarm.res_id, context=context)
567- re_dates = []
568-
569- if hasattr(res_obj, 'rrule') and res_obj.rrule:
570- event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S')
571- #exdate is a string and we need a list
572- exdate = res_obj.exdate and res_obj.exdate.split(',') or []
573- recurrent_dates = get_recurrent_dates(res_obj.rrule, exdate, event_date, res_obj.exrule)
574-
575- trigger_interval = alarm.trigger_interval
576- if trigger_interval == 'days':
577- delta = timedelta(days=alarm.trigger_duration)
578- if trigger_interval == 'hours':
579- delta = timedelta(hours=alarm.trigger_duration)
580- if trigger_interval == 'minutes':
581- delta = timedelta(minutes=alarm.trigger_duration)
582- delta = alarm.trigger_occurs == 'after' and delta or -delta
583-
584- for rdate in recurrent_dates:
585- if rdate + delta > current_datetime:
586- break
587- if rdate + delta <= current_datetime:
588- re_dates.append(rdate.strftime("%Y-%m-%d %H:%M:%S"))
589- rest_dates = recurrent_dates[len(re_dates):]
590- next_trigger_date = rest_dates and rest_dates[0] or None
591-
592- else:
593- re_dates = [alarm.trigger_date]
594-
595- if re_dates:
596- if alarm.action == 'email':
597- sub = '[OpenERP Reminder] %s' % (alarm.name)
598- body = """<pre>
599-Event: %s
600-Event Date: %s
601-Description: %s
602-
603-From:
604- %s
605-
606-----
607-%s
608-</pre>
609-""" % (alarm.name, alarm.trigger_date, alarm.description, \
610- alarm.user_id.name, alarm.user_id.signature)
611- mail_to = alarm.user_id.email
612- for att in alarm.attendee_ids:
613- mail_to = mail_to + " " + att.user_id.email
614- if mail_to:
615- vals = {
616- 'state': 'outgoing',
617- 'subject': sub,
618- 'body_html': body,
619- 'email_to': mail_to,
620- 'email_from': tools.config.get('email_from', mail_to),
621- }
622- self.pool.get('mail.mail').create(cr, uid, vals, context=context)
623- if next_trigger_date:
624- update_vals.update({'trigger_date': next_trigger_date})
625- else:
626- update_vals.update({'state': 'done'})
627- self.write(cr, uid, [alarm.id], update_vals)
628- return True
629-
630-
631-
632 class calendar_event(osv.osv):
633 _name = "calendar.event"
634 _description = "Calendar Event"
635@@ -1011,12 +602,11 @@
636 'location': fields.char('Location', size=264, help="Location of Event", states={'done': [('readonly', True)]}),
637 'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], \
638 'Show Time as', states={'done': [('readonly', True)]}),
639- 'base_calendar_url': fields.char('Caldav URL', size=264),
640 'state': fields.selection([
641 ('tentative', 'Uncertain'),
642 ('cancelled', 'Cancelled'),
643 ('confirmed', 'Confirmed'),
644- ], 'Status', readonly=True),
645+ ],'Status', readonly=True),
646 'exdate': fields.text('Exception Date/Times', help="This property \
647 defines the list of date/time exceptions for a recurring calendar component."),
648 'exrule': fields.char('Exception Rule', size=352, help="Defines a \
649@@ -1030,15 +620,11 @@
650 ('yearly', 'Year(s)')
651 ], 'Recurrency', states={'done': [('readonly', True)]},
652 help="Let the event automatically repeat at that interval"),
653- 'alarm_id': fields.many2one('res.alarm', 'Reminder', states={'done': [('readonly', True)]},
654- help="Set an alarm at this time, before the event occurs" ),
655- 'base_calendar_alarm_id': fields.many2one('calendar.alarm', 'Alarm'),
656+ 'alarm_ids': fields.many2many('calendar.alarm', 'Remiders'),
657 'recurrent_id': fields.integer('Recurrent ID'),
658 'recurrent_id_date': fields.datetime('Recurrent ID date'),
659 'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'),
660 'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
661- 'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with organizer attribute of VEvent.
662- 'organizer_id': fields.many2one('res.users', 'Organizer', states={'done': [('readonly', True)]}),
663 'end_type' : fields.selection([('count', 'Number of repetitions'), ('end_date','End date')], 'Recurrence Termination'),
664 'interval': fields.integer('Repeat Every', help="Repeat every (Days/Week/Month/Year)"),
665 'count': fields.integer('Repeat', help="Repeat x times"),
666@@ -1078,6 +664,11 @@
667 'partner_ids': fields.many2many('res.partner', string='Attendees', states={'done': [('readonly', True)]}),
668 }
669
670+ def new_invitation_token(self, cr, uid, record, partner_id):
671+ db_uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid')
672+ invitation_token = hashlib.sha256('%s-%s-%s-%s-%s' % (time.time(), db_uuid, record._name, record.id, partner_id)).hexdigest()
673+ return invitation_token
674+
675 def create_attendees(self, cr, uid, ids, context):
676 att_obj = self.pool.get('calendar.attendee')
677 user_obj = self.pool.get('res.users')
678@@ -1091,34 +682,27 @@
679 for partner in event.partner_ids:
680 if partner.id in attendees:
681 continue
682- local_context = context.copy()
683- local_context.pop('default_state', None)
684+ access_token = self.new_invitation_token(cr, uid, event, partner.id)
685 att_id = self.pool.get('calendar.attendee').create(cr, uid, {
686 'partner_id': partner.id,
687 'user_id': partner.user_ids and partner.user_ids[0].id or False,
688 'ref': self._name+','+str(event.id),
689- 'email': partner.email
690- }, context=local_context)
691+ 'access_token': access_token,
692+ 'email': partner.email,
693+ }, context=context)
694 if partner.email:
695 mail_to = mail_to + " " + partner.email
696 self.write(cr, uid, [event.id], {
697 'attendee_ids': [(4, att_id)]
698 }, context=context)
699 new_attendees.append(att_id)
700-
701 if mail_to and current_user.email:
702- att_obj._send_mail(cr, uid, new_attendees, mail_to,
703+ is_sent_mail = att_obj._send_mail(cr, uid, new_attendees, mail_to,
704 email_from = current_user.email, context=context)
705+ if is_sent_mail:
706+ self.message_post(cr, uid, event.id, body=_("An invitation email has been sent to attendee(s)"), context=context)
707 return True
708
709- def default_organizer(self, cr, uid, context=None):
710- user_pool = self.pool.get('res.users')
711- user = user_pool.browse(cr, uid, uid, context=context)
712- res = user.name
713- if user.email:
714- res += " <%s>" %(user.email)
715- return res
716-
717 _defaults = {
718 'end_type': 'count',
719 'count': 1,
720@@ -1130,7 +714,6 @@
721 'interval': 1,
722 'active': 1,
723 'user_id': lambda self, cr, uid, ctx: uid,
724- 'organizer': default_organizer,
725 }
726
727 def _check_closing_date(self, cr, uid, ids, context=None):
728@@ -1623,53 +1206,103 @@
729 return self.write(cr, uid, ids, {'state': 'confirmed'}, context)
730
731
732-class calendar_todo(osv.osv):
733- """ Calendar Task """
734-
735- _name = "calendar.todo"
736- _inherit = "calendar.event"
737- _description = "Calendar Task"
738-
739- def _get_date(self, cr, uid, ids, name, arg, context=None):
740- """
741- Get Date
742- @param self: The object pointer
743- @param cr: the current row, from the database cursor,
744- @param uid: the current user's ID for security checks,
745- @param ids: List of calendar todo's IDs.
746- @param args: list of tuples of form [(‘name_of_the_field', ‘operator', value), ...].
747- @param context: A standard dictionary for contextual values
748- """
749-
750- res = {}
751- for event in self.browse(cr, uid, ids, context=context):
752- res[event.id] = event.date_start
753- return res
754-
755- def _set_date(self, cr, uid, id, name, value, arg, context=None):
756- """
757- Set Date
758- @param self: The object pointer
759- @param cr: the current row, from the database cursor,
760- @param uid: the current user's ID for security checks,
761- @param id: calendar's ID.
762- @param value: Get Value
763- @param args: list of tuples of form [('name_of_the_field', 'operator', value), ...].
764- @param context: A standard dictionary for contextual values
765- """
766-
767- assert name == 'date'
768- return self.write(cr, uid, id, { 'date_start': value }, context=context)
769+class calendar_alarm(osv.osv):
770+ _name = 'calendar.alarm'
771+ _description = 'Event alarm'
772
773 _columns = {
774- 'date': fields.function(_get_date, fnct_inv=_set_date, \
775- string='Duration', store=True, type='datetime'),
776- 'duration': fields.integer('Duration'),
777+ 'name':fields.char('Name', size=256, required=True), # fields function
778+ 'type': fields.selection([('notification', 'Notification'), ('email', 'Email')], 'Type', required=True),
779+ 'duration': fields.integer('Amount', required=True),
780+ 'interval': fields.selection([('minutes', 'Minutes'), ('hours', 'Hours'), ('days', 'Days')], 'Unit', required=True),
781+ }
782+ _defaults = {
783+ 'type': 'notification',
784+ 'duration': 1,
785+ 'interval': 'hours',
786 }
787
788- __attribute__ = {}
789-
790-
791+
792+class res_partner(osv.osv):
793+ _inherit = 'res.partner'
794+
795+ def get_attendee_detail(self, cr, uid, ids, meeting_id, context=None):
796+ datas = []
797+ meeting = False
798+ if meeting_id:
799+ meeting = self.pool.get('crm.meeting').browse(cr, uid, get_real_ids(meeting_id),context)
800+ for partner in self.browse(cr, uid, ids, context=context):
801+ data = self.name_get(cr, uid, [partner.id], context)[0]
802+ if meeting:
803+ for attendee in meeting.attendee_ids:
804+ if attendee.partner_id.id == partner.id:
805+ data = (data[0], data[1], attendee.state)
806+ datas.append(data)
807+ return datas
808+
809+ def do_run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
810+ if context is None:
811+ context = {}
812+ current_datetime = datetime.now()
813+ alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context)
814+
815+ mail_to = ""
816+
817+ for alarm in self.browse(cr, uid, alarm_ids, context=context):
818+ next_trigger_date = None
819+ update_vals = {}
820+ model_obj = self.pool[alarm.model_id.model]
821+ res_obj = model_obj.browse(cr, uid, alarm.res_id, context=context)
822+ re_dates = []
823+
824+ if hasattr(res_obj, 'rrule') and res_obj.rrule:
825+ event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S')
826+ #exdate is a string and we need a list
827+ exdate = res_obj.exdate and res_obj.exdate.split(',') or []
828+ recurrent_dates = get_recurrent_dates(res_obj.rrule, exdate, event_date, res_obj.exrule)
829+
830+ trigger_interval = alarm.trigger_interval
831+ if trigger_interval == 'days':
832+ delta = timedelta(days=alarm.trigger_duration)
833+ if trigger_interval == 'hours':
834+ delta = timedelta(hours=alarm.trigger_duration)
835+ if trigger_interval == 'minutes':
836+ delta = timedelta(minutes=alarm.trigger_duration)
837+ delta = alarm.trigger_occurs == 'after' and delta or -delta
838+
839+ for rdate in recurrent_dates:
840+ if rdate + delta > current_datetime:
841+ break
842+ if rdate + delta <= current_datetime:
843+ re_dates.append(rdate.strftime("%Y-%m-%d %H:%M:%S"))
844+ rest_dates = recurrent_dates[len(re_dates):]
845+ next_trigger_date = rest_dates and rest_dates[0] or None
846+
847+ else:
848+ re_dates = [alarm.trigger_date]
849+
850+ if re_dates:
851+ if alarm.action == 'email':
852+ sub = '[OpenERP Reminder] %s' % (alarm.name)
853+ body = """ """
854+ mail_to = alarm.user_id.email
855+ for att in alarm.attendee_ids:
856+ mail_to = mail_to + " " + att.user_id.email
857+ if mail_to:
858+ vals = {
859+ 'state': 'outgoing',
860+ 'subject': sub,
861+ 'body_html': body,
862+ 'email_to': mail_to,
863+ 'email_from': tools.config.get('email_from', mail_to),
864+ }
865+ self.pool.get('mail.mail').create(cr, uid, vals, context=context)
866+ if next_trigger_date:
867+ update_vals.update({'trigger_date': next_trigger_date})
868+ else:
869+ update_vals.update({'state': 'done'})
870+ self.write(cr, uid, [alarm.id], update_vals)
871+ return True
872
873
874 class ir_values(osv.osv):
875
876=== added directory 'base_calendar/controllers'
877=== added file 'base_calendar/controllers/__init__.py'
878--- base_calendar/controllers/__init__.py 1970-01-01 00:00:00 +0000
879+++ base_calendar/controllers/__init__.py 2013-11-07 16:05:43 +0000
880@@ -0,0 +1,3 @@
881+import main
882+
883+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
884
885=== added file 'base_calendar/controllers/main.py'
886--- base_calendar/controllers/main.py 1970-01-01 00:00:00 +0000
887+++ base_calendar/controllers/main.py 2013-11-07 16:05:43 +0000
888@@ -0,0 +1,77 @@
889+import simplejson
890+import urllib
891+import openerp
892+import openerp.addons.web.http as http
893+from openerp.addons.web.http import request
894+import openerp.addons.web.controllers.main as webmain
895+import json
896+from openerp.addons.web.http import SessionExpiredException
897+from werkzeug.exceptions import BadRequest
898+
899+class meetting_invitation(http.Controller):
900+
901+ def check_security(self, db, token):
902+ registry = openerp.modules.registry.RegistryManager.get(db)
903+ attendee_pool = registry.get('calendar.attendee')
904+ error_message = False
905+ with registry.cursor() as cr:
906+ attendee_id = attendee_pool.search(cr, openerp.SUPERUSER_ID, [('access_token','=',token)])
907+ if not attendee_id:
908+ # if token is not match
909+ error_message = """Invalid Invitation Token."""
910+ elif request.session.uid and request.session.login != 'anonymous':
911+ # if valid session but user is not match
912+ attendee = attendee_pool.browse(cr, openerp.SUPERUSER_ID, attendee_id[0])
913+ user = registry.get('res.users').browse(cr, openerp.SUPERUSER_ID, request.session.uid)
914+ if attendee.user_id.id != user.id:
915+ error_message = """Invitation cannot be forwarded via email. This event/meeting belongs to %s and you are logged in as %s. Please ask organizer to add you.""" % (attendee.email, user.email)
916+ if error_message:
917+ raise BadRequest(error_message)
918+ return True
919+
920+ @http.route('/meeting_invitation/accept', type='http', auth="none")
921+ def accept(self, db, token, action, id):
922+ # http://hostname:8069/meeting_invitation/accept?db=#token=&action=&id=
923+ self.check_security(db, token)
924+ registry = openerp.modules.registry.RegistryManager.get(db)
925+ attendee_pool = registry.get('calendar.attendee')
926+ with registry.cursor() as cr:
927+ attendee_id = attendee_pool.search(cr, openerp.SUPERUSER_ID, [('access_token','=',token),('state','!=', 'accepted')])
928+ if attendee_id:
929+ attendee_pool.do_accept(cr, openerp.SUPERUSER_ID, attendee_id)
930+ return self.view(db, token, action, id, view='form')
931+
932+ @http.route('/meeting_invitation/decline', type='http', auth="none")
933+ def declined(self, db, token, action, id):
934+ # http://hostname:8069/meeting_invitation/decline?db=#token=&action=&id=
935+ self.check_security(db, token)
936+ registry = openerp.modules.registry.RegistryManager.get(db)
937+ attendee_pool = registry.get('calendar.attendee')
938+ with registry.cursor() as cr:
939+ attendee_id = attendee_pool.search(cr, openerp.SUPERUSER_ID, [('access_token','=',token),('state','!=', 'declined')])
940+ if attendee_id:
941+ attendee_pool.do_decline(cr, openerp.SUPERUSER_ID, attendee_id)
942+ return self.view(db, token, action, id, view='form')
943+
944+ @http.route('/meeting_invitation/view', type='http', auth="none")
945+ def view(self, db, token, action, id, view='calendar'):
946+ # http://hostname:8069/meeting_invitation/view?db=#token=&action=&id=
947+ self.check_security(db, token)
948+ registry = openerp.modules.registry.RegistryManager.get(db)
949+ meeting_pool = registry.get('crm.meeting')
950+ attendee_pool = registry.get('calendar.attendee')
951+ with registry.cursor() as cr:
952+ attendee_data = meeting_pool.get_attendee(cr, openerp.SUPERUSER_ID, id);
953+ attendee = attendee_pool.search_read(cr, openerp.SUPERUSER_ID, [('access_token','=',token)],[])
954+ if attendee:
955+ attendee_data['current_attendee'] = attendee[0]
956+ js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in webmain.manifest_list('js', db=db))
957+ css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in webmain.manifest_list('css',db=db))
958+ return webmain.html_template % {
959+ 'js': js,
960+ 'css': css,
961+ 'modules': simplejson.dumps(webmain.module_boot(db)),
962+ 'init': "s.base_calendar.event('%s', '%s', '%s', '%s' , '%s');" % (db, action, id, view, json.dumps(attendee_data)),
963+ }
964+
965+
966
967=== modified file 'base_calendar/crm_meeting.py'
968--- base_calendar/crm_meeting.py 2013-08-13 09:47:19 +0000
969+++ base_calendar/crm_meeting.py 2013-11-07 16:05:43 +0000
970@@ -22,9 +22,14 @@
971 import time
972
973 from openerp.osv import fields, osv
974-from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
975+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
976 from openerp.tools.translate import _
977 from base_calendar import get_real_ids, base_calendar_id2real_id
978+from datetime import datetime, timedelta, date
979+import pytz
980+from openerp import tools
981+import openerp
982+
983 #
984 # crm.meeting is defined here so that it may be used by modules other than crm,
985 # without forcing the installation of crm.
986@@ -43,6 +48,55 @@
987 _description = "Meeting"
988 _order = "id desc"
989 _inherit = ["calendar.event", "mail.thread", "ir.needaction_mixin"]
990+
991+ def _find_user_attendee(self, cr, uid, meeting_ids, context=None):
992+ attendee_pool = self.pool.get('calendar.attendee')
993+ user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
994+ for meeting_id in meeting_ids:
995+ for attendee in self.browse(cr,uid,meeting_id,context).attendee_ids:
996+ if user.partner_id.id == attendee.partner_id.id:
997+ return attendee
998+ return False
999+
1000+ def _compute(self, cr, uid, ids, fields, arg, context=None):
1001+ res = {}
1002+ for meeting_id in ids:
1003+ res[meeting_id] = {}
1004+ attendee = self._find_user_attendee(cr, uid, [meeting_id], context)
1005+ for field in fields:
1006+ if field == 'is_attendee':
1007+ res[meeting_id][field] = True if attendee else False
1008+ elif field == 'attendee_status':
1009+ res[meeting_id][field] = attendee.state if attendee else 'needs-action'
1010+ elif field == 'event_time':
1011+ res[meeting_id][field] = self._compute_time(cr, uid, meeting_id, context=context)
1012+ return res
1013+
1014+
1015+ def _compute_time(self, cr, uid, meeting_id, context=None):
1016+ """
1017+ Return date and time (from to from) based on duration with timezone in string :
1018+ eg.
1019+ 1) if user add duration for 2 hours, return : August-23-2013 at ( 04-30 To 06-30) (Europe/Brussels)
1020+ 2) if event all day ,return : AllDay, July-31-2013
1021+ """
1022+ if context is None:
1023+ context = {}
1024+ tz = context.get('tz', pytz.timezone('UTC'))
1025+ meeting = self.browse(cr, uid, meeting_id, context=context)
1026+ date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(meeting.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context)
1027+ date_deadline = fields.datetime.context_timestamp(cr, uid, datetime.strptime(meeting.date_deadline, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context)
1028+ event_date = date.strftime('%B-%d-%Y')
1029+ event_time = date.strftime('%H-%M')
1030+ if meeting.allday:
1031+ time = _("AllDay , %s") % (event_date)
1032+ elif meeting.duration < 24:
1033+ duration = date + timedelta(hours= meeting.duration)
1034+ time = ("%s at ( %s To %s) (%s)") % (event_date, event_time, duration.strftime('%H-%M'), tz)
1035+ else :
1036+ time = ("%s at %s To\n %s at %s (%s)") % (event_date, event_time, date_deadline.strftime('%B-%d-%Y'), date_deadline.strftime('%H-%M'), tz)
1037+ return time
1038+
1039 _columns = {
1040 'create_date': fields.datetime('Creation Date', readonly=True),
1041 'write_date': fields.datetime('Write Date', readonly=True),
1042@@ -59,16 +113,29 @@
1043 'event_id', 'type_id', 'Tags'),
1044 'attendee_ids': fields.many2many('calendar.attendee', 'meeting_attendee_rel',\
1045 'event_id', 'attendee_id', 'Invited People', states={'done': [('readonly', True)]}),
1046+ 'is_attendee': fields.function(_compute, string='Attendee', \
1047+ type="boolean", multi='attendee'),
1048+ 'attendee_status': fields.function(_compute, string='Attendee Status', \
1049+ type="selection", multi='attendee'),
1050+ 'event_time': fields.function(_compute, string='Event Time', type="char", multi='attendee'),
1051 }
1052 _defaults = {
1053 'state': 'open',
1054 }
1055+
1056+ def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False):
1057+ if context is None:
1058+ context={}
1059+ if context.get('mymeetings',False):
1060+ partner_id = self.pool.get('res.users').browse(cr, uid, uid, context).partner_id.id
1061+ args += ['|', ('partner_ids', 'in', [partner_id]), ('user_id', '=', uid)]
1062+ return super(crm_meeting, self).search(cr, uid, args, offset=offset, limit=limit, order=order, context=context, count=count)
1063
1064- def message_get_subscription_data(self, cr, uid, ids, context=None):
1065+ def message_get_subscription_data(self, cr, uid, ids, user_pid=None, context=None):
1066 res = {}
1067 for virtual_id in ids:
1068 real_id = base_calendar_id2real_id(virtual_id)
1069- result = super(crm_meeting, self).message_get_subscription_data(cr, uid, [real_id], context=context)
1070+ result = super(crm_meeting, self).message_get_subscription_data(cr, uid, [real_id], user_pid=None, context=context)
1071 res[virtual_id] = result[real_id]
1072 return res
1073
1074@@ -123,8 +190,48 @@
1075 subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
1076 if isinstance(thread_id, str):
1077 thread_id = get_real_ids(thread_id)
1078+ if context.get('default_date'):
1079+ del context['default_date']
1080 return super(crm_meeting, self).message_post(cr, uid, thread_id, body=body, subject=subject, type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, **kwargs)
1081
1082+ def do_decline(self, cr, uid, ids, context=None):
1083+ attendee_pool = self.pool.get('calendar.attendee')
1084+ attendee = self._find_user_attendee(cr, uid, ids, context)
1085+ return attendee_pool.do_decline(cr, uid, [attendee.id], context=context)
1086+
1087+ def do_accept(self, cr, uid, ids, context=None):
1088+ attendee_pool = self.pool.get('calendar.attendee')
1089+ attendee = self._find_user_attendee(cr, uid, ids, context)
1090+ return attendee_pool.do_accept(cr, uid, [attendee.id], context=context)
1091+
1092+ def get_attendee(self, cr, uid, meeting_id, context=None):
1093+ invitation = {'meeting':{}, 'attendee': [], 'logo': ''}
1094+ attendee_pool = self.pool.get('calendar.attendee')
1095+ company_logo = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.logo
1096+ meeting = self.browse(cr, uid, int(meeting_id), context)
1097+ invitation['meeting'] = {
1098+ 'event':meeting.name,
1099+ 'organizer': meeting.organizer,
1100+ 'where': meeting.location,
1101+ 'when':meeting.event_time
1102+ }
1103+ invitation['logo'] = company_logo.replace('\n','\\n') if company_logo else ''
1104+ for attendee in meeting.attendee_ids:
1105+ invitation['attendee'].append({'name':attendee.cn,'status': attendee.state})
1106+ return invitation
1107+
1108+ def get_interval(self, cr, uid, ids, date, interval, context=None):
1109+ date = datetime.strptime(date, DEFAULT_SERVER_DATETIME_FORMAT)
1110+ if interval == 'day':
1111+ res = str(date.day)
1112+ elif interval == 'month':
1113+ res = date.strftime('%B') + " " + str(date.year)
1114+ elif interval == 'dayname':
1115+ res = date.strftime('%A')
1116+ elif interval == 'time':
1117+ res = date.strftime('%I:%M %p')
1118+ return res
1119+
1120 class mail_message(osv.osv):
1121 _inherit = "mail.message"
1122
1123
1124=== modified file 'base_calendar/crm_meeting_data.xml'
1125--- base_calendar/crm_meeting_data.xml 2012-11-29 22:26:45 +0000
1126+++ base_calendar/crm_meeting_data.xml 2013-11-07 16:05:43 +0000
1127@@ -28,5 +28,133 @@
1128 <field name="name">Meeting</field>
1129 <field name="object">crm.meeting</field>
1130 </record>
1131+ <record id="crm_email_template_meeting_invitation" model="email.template">
1132+ <field name="name">CRM Meeting Invitation</field>
1133+ <field name="email_from">${object.user_id.email or ''}</field>
1134+ <field name="subject">${object.name}</field>
1135+ <field name="email_to" >${ctx['att_obj'].email}</field>
1136+ <field name="model_id" ref="base_calendar.model_crm_meeting"/>
1137+
1138+ <field name="auto_delete" eval="True"/>
1139+ <field name="body_html"><![CDATA[
1140+ <html>
1141+ <head>
1142+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
1143+ <title>${object.name}</title>
1144+ </head>
1145+ <body>
1146+ <div style="border-radius: 2px; max-width: 1200px; height: auto;margin-left: auto;margin-right: auto;background-color:#f9f9f9;">
1147+ <div style="height:auto;text-align: center;font-size : 30px;color: #8A89BA;">
1148+ <strong>${object.name}</strong>
1149+ </div>
1150+ <div style="height: 50px;text-align: left;font-size : 14px;border-collapse: separate;margin-top:10px">
1151+ <strong style="margin-left:12px">Hello ${ctx['att_obj'].cn}</strong> ,<br/><p style="margin-left:12px">${object.organizer} invited you for the ${object.name} meeting of ${object.user_id.company_id.name}.</p>
1152+ </div>
1153+ <div style="height: auto;margin-left:12px;margin-top:30px;">
1154+ <table>
1155+ <tr>
1156+ <td>
1157+ <div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;border-color:#ffffff;background:#8a89ba;padding-top: 4px;">${object.get_interval(object.date, 'dayname')}</div>
1158+ <div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #E1E2F8;width: 130px;">
1159+ ${object.get_interval(object.date,'day')}
1160+ </div>
1161+ <div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#8a89ba'>${object.get_interval(object.date, 'month')}</div>
1162+ <div style="border-collapse:separate;color:#8a89ba;text-align:center;width: 128px;font-size:12px;border-bottom-right-radius:3px;font-weight:bold;border:1px solid;border-bottom-left-radius:3px;">${object.get_interval(object.date, 'time')}</div>
1163+ </td>
1164+ <td>
1165+ <table cellspacing="0" cellpadding="0" border="0" style="margin-top: 15px; margin-left: 10px;font-size: 16px;">
1166+ % if object.location :
1167+ <tr style=" height: 30px;">
1168+ <td style="vertical-align:top;">
1169+ <div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
1170+ Where
1171+ </div>
1172+ </td>
1173+ <td colspan="1" style="vertical-align:top;">
1174+ <div style = "font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 14px" >
1175+ : ${object.location}
1176+ <span style= "color:#A9A9A9; ">(<a href="http://maps.google.com/maps?oi=map&q=${object.location}">View Map</a>)
1177+ </span>
1178+ </div>
1179+ </td>
1180+ </tr>
1181+ % endif
1182+ % if not object.location :
1183+ <tr style=" height: 30px;color:#909090">
1184+ <td>
1185+ <div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
1186+ Where
1187+ </div>
1188+ </td>
1189+ <td colspan="1">
1190+ <div style = "font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 14px;" >
1191+ : -
1192+ </div>
1193+ </td>
1194+ </tr>
1195+ % endif
1196+ % if object.description :
1197+ <tr style=" height:auto;">
1198+ <td style="vertical-align:top;">
1199+ <div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
1200+ What
1201+ </div>
1202+ </td>
1203+ <td colspan="3" style="vertical-align:text-top;">
1204+ <div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
1205+ : ${object.description or ''}
1206+ </div>
1207+ </td>
1208+ </tr>
1209+ % endif
1210+ % if not object.description :
1211+ <tr style=" height: 30px;color:#909090">
1212+ <td style="vertical-align:top;">
1213+ <div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
1214+ What
1215+ </div>
1216+ </td>
1217+ <td colspan="3" style="vertical-align:text-top;">
1218+ <div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
1219+ : -
1220+ </div>
1221+ </td>
1222+ </tr>
1223+ % endif
1224+ <tr style=" height: 30px;">
1225+ <td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
1226+ <div>
1227+ Attendees
1228+ </div>
1229+ </td>
1230+ <td colspan="3">
1231+ : <div style='display:inline-block; border-radius: 50%; width:10px; height:10px;background:grey;'></div>
1232+ <span style="margin-left:5px">You</span>
1233+
1234+ % for attendee in object.attendee_ids:
1235+ % if attendee.cn != ctx['att_obj'].cn:
1236+ <div style='display:inline-block; border-radius: 50%; width:10px; height:10px;background:${ctx['color'][attendee.state]};'></div>
1237+ <span style="margin-left:5px">${attendee.cn}</span>
1238+ % endif
1239+ % endfor
1240+ </td>
1241+ </tr>
1242+ </table>
1243+ </td>
1244+ </tr>
1245+ </table>
1246+ </div>
1247+ <div style="height: auto;width:300px; margin:0 auto;padding-top:20px;">
1248+ <a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#8A89BA;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/meeting_invitation/accept?db=${ctx['dbname']}&token=${ctx['att_obj'].access_token}&action=${ctx['action_id']}&id=${object.id}">Accept</a>
1249+ <a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#808080;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/meeting_invitation/decline?db=${ctx['dbname']}&token=${ctx['att_obj'].access_token}&action=${ctx['action_id']}&id=${object.id}">Decline</a>
1250+ </div>
1251+ <div style="padding-top:10px;">
1252+ -- </br> Sent by ${object.user_id.name} from ${object.user_id.company_id.name}. View this meeting detail <a href="${ctx['base_url']}/meeting_invitation/view?db=${ctx['dbname']}&token=${ctx['att_obj'].access_token}&action=${ctx['action_id']}&id=${object.id}">directly in OpenERP.</a>
1253+ </div>
1254+ </div>
1255+ </body>
1256+ </html>
1257+ ]]></field>
1258+ </record>
1259 </data>
1260 </openerp>
1261
1262=== modified file 'base_calendar/crm_meeting_view.xml'
1263--- base_calendar/crm_meeting_view.xml 2013-09-04 14:11:22 +0000
1264+++ base_calendar/crm_meeting_view.xml 2013-11-07 16:05:43 +0000
1265@@ -32,8 +32,16 @@
1266 <field name="model">crm.meeting</field>
1267 <field name="arch" type="xml">
1268 <form string="Meetings" version="7.0">
1269- <field name="state" invisible="True"/>
1270+ <header>
1271+ <button name="do_accept" type="object"
1272+ string="Accept" attrs="{'invisible':['|',('is_attendee','=',False),('attendee_status','=','accepted')]}"/>
1273+ <button name="do_decline" type="object"
1274+ string="Decline" attrs="{'invisible':['|',('is_attendee','=',False),('attendee_status','=','declined')]}"/>
1275+ <field name="state" invisible="True"/>
1276+ </header>
1277 <sheet>
1278+ <field name="is_attendee" invisible="1"/>
1279+ <field name="attendee_status" invisible="1"/>
1280 <div class="oe_title">
1281 <div class="oe_edit_only">
1282 <label for="name"/>
1283@@ -43,7 +51,7 @@
1284 </h1>
1285 <label for="partner_ids" class="oe_edit_only"/>
1286 <h2>
1287- <field name="partner_ids" widget="many2many_tags"
1288+ <field name="partner_ids" widget="many2manyattendee"
1289 context="{'force_email':True}"
1290 on_change="onchange_partner_ids(partner_ids)"/>
1291 </h2>
1292@@ -133,16 +141,16 @@
1293 </group>
1294 </group>
1295 </page>
1296- <page string="Invitations">
1297+ <page string="Invitations" groups="base.group_no_one">
1298 <field name="attendee_ids" widget="one2many" mode="tree">
1299- <tree string="Invitation details" editable="top">
1300+ <tree string="Invitation details" editable="top" >
1301 <field name="partner_id" on_change="onchange_partner_id(partner_id)"/>
1302 <field name="email" string="Mail To"/>
1303 <field name="state"/>
1304 <button name="do_tentative"
1305 states="needs-action,declined,accepted"
1306 string="Uncertain" type="object"
1307- icon="terp-crm"/>
1308+ icon="terp-crm" />
1309 <button name="do_accept" string="Accept"
1310 states="needs-action,tentative,declined"
1311 type="object" icon="gtk-apply"/>
1312@@ -181,6 +189,10 @@
1313
1314 </notebook>
1315 </sheet>
1316+ <div class="oe_chatter">
1317+ <field name="message_follower_ids" widget="mail_followers"/>
1318+ <field name="message_ids" widget="mail_thread" />
1319+ </div>
1320 </form>
1321 </field>
1322 </record>
1323@@ -238,7 +250,7 @@
1324 <field name="categ_ids"/>
1325 <field name="user_id"/>
1326 <separator/>
1327- <filter string="My Meetings" help="My Meetings" domain="[('user_id','=',uid)]"/>
1328+ <filter string="My Meetings" help="My Meetings" name="mymeetings" context='{"mymeetings": 1}'/>
1329 <filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
1330 </search>
1331 </field>
1332@@ -252,7 +264,7 @@
1333 <field name="view_mode">calendar,tree,form,gantt</field>
1334 <field name="view_id" ref="view_crm_meeting_calendar"/>
1335 <field name="search_view_id" ref="view_crm_meeting_search"/>
1336- <field name="context">{"calendar_default_user_id": uid}</field>
1337+ <field name="context">{"search_default_mymeetings": 1}</field>
1338 <field name="help" type="html">
1339 <p class="oe_view_nocontent_create">
1340 Click to schedule a new meeting.
1341
1342=== added directory 'base_calendar/doc'
1343=== added file 'base_calendar/doc/calnedar_attendee.rst'
1344--- base_calendar/doc/calnedar_attendee.rst 1970-01-01 00:00:00 +0000
1345+++ base_calendar/doc/calnedar_attendee.rst 2013-11-07 16:05:43 +0000
1346@@ -0,0 +1,23 @@
1347+.. _calendar_attendee:
1348+calendar.attendee:
1349+=================
1350+
1351+Fields
1352+++++++
1353+ - ``access_token`` :
1354+ unique value(token) for every new attendee.
1355+
1356+Methods
1357++++++++
1358+ - ``do_accept``:
1359+ REF : post message in chatter when attendee accepted an invitation.
1360+ - ``do_decline``:
1361+ REF : post message in chatter when attendee declined an invitation.
1362+
1363+calendar.event:
1364+===============
1365+Methods
1366++++++++
1367+ - ``new_invitation_token``:
1368+ generate a unique token for every new attendee.
1369+
1370
1371=== added file 'base_calendar/doc/changelog.rst'
1372--- base_calendar/doc/changelog.rst 1970-01-01 00:00:00 +0000
1373+++ base_calendar/doc/changelog.rst 2013-11-07 16:05:43 +0000
1374@@ -0,0 +1,29 @@
1375+.. _changelog:
1376+
1377+Changelog
1378+=========
1379+
1380+Email Template of Meeting Invitation:
1381++++++++++++++++++++++++++++++++++++++
1382+ - remove static code of HTML design of email of meeting invitation
1383+ - added new better layout of email of meeting invitation using MAKO Template.
1384+
1385+Web controller:
1386++++++++++++++++
1387+ - ``accept`` :
1388+ handle request ('meeting_invitation/accept') ,when accepted an invitation it change the status of invitation as accepted , user do need to login in system.
1389+ - ``declined``:
1390+ handle request ('meeting_invitation/decline') ,when declined an invitation it change the status of invitation as declined , user do need to login in system.
1391+ - ``view``:
1392+ handle request ('meeting_invitation/view') ,when user click on accept,declined link button , it redirect user to form view if user is already login and if user has not been login it redirect to a simple qweb template to inform user has accepted/declined a meeting ,if user click on directly in openerp it redirect user to a meeting calendar view , if user is not login then it redirect to a qweb template.
1393+ - ``check_security``:
1394+ check token is valid and user is not allow to accept/decline invitation mail of other user from email template URL.
1395+
1396+Web Widget:
1397++++++++++++
1398+ - ``Field Many2Many_invite``(widget):
1399+ display a status button in left side of every invited attendees of meeting , in many2many.
1400+
1401+Qweb Template:
1402+++++++++++++++
1403+ - added template ,to directly allow any invited user to accept , decline a meeting , if user do not need to login in the system to accept or decline an invitation.
1404
1405=== added file 'base_calendar/doc/crm_meeting.rst'
1406--- base_calendar/doc/crm_meeting.rst 1970-01-01 00:00:00 +0000
1407+++ base_calendar/doc/crm_meeting.rst 2013-11-07 16:05:43 +0000
1408@@ -0,0 +1,38 @@
1409+.. _crm_meeting:
1410+
1411+Fields:
1412++++++++
1413+ - ``is_attendee`` :
1414+ function field , that defined whether loged in user is attendee or not.
1415+ - ``attendee_status``:
1416+ function field , that defined login user status, either accepted, declined or needs-action.
1417+ - ``event_time``:
1418+ function field, defined an event_time in user's tz.
1419+
1420+Methods:
1421+++++++++
1422+ - ``_find_user_attendee``:
1423+ return attendee if attendee is internal user else false.
1424+ - ``_compute_time``:
1425+ compute a time from date_start and duration with user's tz.
1426+ - ``search``:
1427+ search a current user's meetings
1428+ - ``do_accept/do_decline``:
1429+ trigger when ,user accept/decline from the meeting form view.
1430+ - ``get_attendee``:
1431+ get detail of attendees meeting.
1432+ - ``get_interval``:
1433+ call from email template that return formate of date, as per value pass from the email template.
1434+
1435+views:
1436+++++++
1437+ - ``do_accept``:
1438+ Accept button in meeting form view that is allow a user to accept a meeting ,that is visible to only attendee and if attendee state is other than accepted.
1439+ - ``do_decline``:
1440+ Decline button in meeting form view that is allow a user to accept a meeting ,that is visible to only attendee and if attendee state is other than declined.
1441+ - ``chatter(message_ids)``:
1442+ show a log of meeting.
1443+
1444+security:
1445++++++++++
1446+ - added record rule to restrict an user to show personal invitation on meeting , so user can't change other's status , from invitation tab.
1447
1448=== modified file 'base_calendar/security/calendar_security.xml'
1449--- base_calendar/security/calendar_security.xml 2012-06-28 06:40:05 +0000
1450+++ base_calendar/security/calendar_security.xml 2013-11-07 16:05:43 +0000
1451@@ -5,5 +5,5 @@
1452 <field name="name">Survey / User</field>
1453 <field name="users" eval="[(4, ref('base.user_root'))]"/>
1454 </record>
1455- </data>
1456+ </data>
1457 </openerp>
1458
1459=== modified file 'base_calendar/security/ir.model.access.csv'
1460--- base_calendar/security/ir.model.access.csv 2012-12-12 17:55:58 +0000
1461+++ base_calendar/security/ir.model.access.csv 2013-11-07 16:05:43 +0000
1462@@ -7,7 +7,7 @@
1463 access_calendar_attendee_survey_user,calendar.attendee,model_calendar_attendee,base.group_survey_user,1,0,0,0
1464 access_crm_meeting_manager,crm.meeting.manager,model_crm_meeting,base.group_sale_manager,1,1,1,1
1465 access_crm_meeting,crm.meeting,model_crm_meeting,base.group_sale_salesman,1,1,1,0
1466-access_crm_meeting_all,crm.meeting_allll,model_crm_meeting,base.group_user,1,0,0,0
1467+access_crm_meeting_all,crm.meeting_allll,model_crm_meeting,base.group_user,1,1,0,0
1468 access_crm_meeting_partner_manager,crm.meeting.partner.manager,model_crm_meeting,base.group_partner_manager,1,1,1,1
1469 access_crm_meeting_type_sale_manager,crm.meeting.type.manager,model_crm_meeting_type,base.group_sale_manager,1,1,1,0
1470 access_crm_meeting_type_sale_user,crm.meeting.type.user,model_crm_meeting_type,base.group_user,1,0,0,0
1471
1472=== added directory 'base_calendar/static/src/css'
1473=== added file 'base_calendar/static/src/css/base_calender.css'
1474--- base_calendar/static/src/css/base_calender.css 1970-01-01 00:00:00 +0000
1475+++ base_calendar/static/src/css/base_calender.css 2013-11-07 16:05:43 +0000
1476@@ -0,0 +1,65 @@
1477+.openerp .oe_invitation , .text-core .text-tag .oe_invitation{
1478+ width : 13px;
1479+ height : 13px;
1480+ margin-bottom : -4px;
1481+ display : inline-block;
1482+}
1483+.openerp .needs-action , .tentative,.text-core .text-tag .custom-edit, .text-core .text-tag .tentative {
1484+ background : url(/web/static/src/img/icons/gtk-normal.png) no-repeat;
1485+ background-size : 11px 11px;
1486+}
1487+.openerp .accepted , .text-core .text-tag .accepted {
1488+ background : url(/web/static/src/img/icons/gtk-yes.png) no-repeat;
1489+ background-size : 11px 11px;
1490+}
1491+.openerp .declined , .text-core .text-tag .declined {
1492+ background : url(/web/static/src/img/icons/gtk-no.png) no-repeat;
1493+ background-size : 11px 11px;
1494+}
1495+.cal_meeting {
1496+ font-size : 24px;
1497+ font-style: bold;
1498+ text-align : justify;
1499+ color : #8A89BA;
1500+}
1501+.cal_lable {
1502+ width: 50px;
1503+ color : #808080;
1504+}
1505+.invitation_block {
1506+ padding : 50px 0 0 30px;
1507+ font-size : 14px;
1508+ background : #f9f9f9;
1509+}
1510+.attendee_accepted {
1511+ background : url(/web/static/src/img/icons/gtk-apply.png) no-repeat;
1512+ background-size : 15px 15px;
1513+ padding-left: 20px;
1514+}
1515+.attendee_declined {
1516+ background : url(/web/static/src/img/icons/gtk-cancel.png) no-repeat;
1517+ background-size : 15px 15px;
1518+ padding-left: 20px;
1519+}
1520+.event_status {
1521+ border : 1px solid;
1522+ height : 20px;
1523+ width : auto;
1524+ background: #808080;
1525+ color : #FFFFFF;
1526+ padding: 5px 10px;
1527+ width: 400px;
1528+}
1529+.cal_inline {
1530+ display: inline;
1531+}
1532+.cal_tag {
1533+ padding-right : 10px;
1534+ font-style : italic;
1535+ font-size : 17px;
1536+ vertical-align:bottom;
1537+}
1538+.cal_image {
1539+ height: 30px;
1540+ width : 100px;
1541+}
1542
1543=== added directory 'base_calendar/static/src/js'
1544=== added file 'base_calendar/static/src/js/base_calendar.js'
1545--- base_calendar/static/src/js/base_calendar.js 1970-01-01 00:00:00 +0000
1546+++ base_calendar/static/src/js/base_calendar.js 2013-11-07 16:05:43 +0000
1547@@ -0,0 +1,80 @@
1548+openerp.base_calendar = function(instance) {
1549+var _t = instance.web._t;
1550+var QWeb = instance.web.qweb;
1551+instance.base_calendar = {}
1552+
1553+ instance.base_calendar.invitation = instance.web.Widget.extend({
1554+
1555+ init: function(parent, db, action, id, view, attendee_data) {
1556+ this._super();
1557+ this.db = db;
1558+ this.action = action;
1559+ this.id = id;
1560+ this.view = view;
1561+ this.attendee_data = attendee_data;
1562+ },
1563+ start: function() {
1564+ var self = this;
1565+ if(instance.session.session_is_valid(self.db) && instance.session.username != "anonymous") {
1566+ self.redirect_meeting_view(self.db,self.action,self.id,self.view);
1567+ } else {
1568+ self.open_invitation_form(self.attendee_data);
1569+ }
1570+ },
1571+ open_invitation_form : function(invitation){
1572+ this.$el.html(QWeb.render('invitation_view', {'invitation': JSON.parse(invitation)}));
1573+ },
1574+ redirect_meeting_view : function(db, action, meeting_id, view){
1575+ var self = this;
1576+ var action_url = '';
1577+ if(view == "form") {
1578+ action_url = _.str.sprintf('/?db=%s#id=%s&view_type=%s&model=crm.meeting', db, meeting_id, view, meeting_id);
1579+ } else {
1580+ action_url = _.str.sprintf('/?db=%s#view_type=%s&model=crm.meeting&action=%s',self.db,self.view,self.action);
1581+ }
1582+ var reload_page = function(){
1583+ return location.replace(action_url);
1584+ }
1585+ reload_page();
1586+ },
1587+ });
1588+
1589+ instance.web.form.Many2ManyAttendee = instance.web.form.FieldMany2ManyTags.extend({
1590+ tag_template: "many2manyattendee",
1591+ initialize_texttext: function() {
1592+ return _.extend(this._super(),{
1593+ html : {
1594+ tag: '<div class="text-tag"><div class="text-button"><a class="oe_invitation custom-edit"/><span class="text-label"/><a class="text-remove"/></div></div>'
1595+ }
1596+ });
1597+ },
1598+ map_tag: function(value){
1599+ return _.map(value, function(el) {return {name: el[1], id:el[0], state: el[2]};})
1600+ },
1601+ get_render_data: function(ids){
1602+ var self = this;
1603+ var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.build_context());
1604+ return dataset.call('get_attendee_detail',[ids, self.getParent().datarecord.id || false]);
1605+ },
1606+ render_tag: function(data){
1607+ this._super(data);
1608+ var self = this;
1609+ if (! self.get("effective_readonly")) {
1610+ var tag_element = self.tags.tagElements();
1611+ _.each(data,function(value, key){
1612+ $(tag_element[key]).find(".custom-edit").addClass(data[key][2])
1613+ });
1614+ }
1615+ }
1616+ });
1617+ instance.web.form.widgets = instance.web.form.widgets.extend({
1618+ 'many2manyattendee' : 'instance.web.form.Many2ManyAttendee',
1619+ });
1620+
1621+ instance.base_calendar.event = function (db, action, id, view, attendee_data) {
1622+ instance.session.session_bind(instance.session.origin).done(function () {
1623+ new instance.base_calendar.invitation(null,db,action,id,view,attendee_data).appendTo($("body").addClass('openerp'));
1624+ });
1625+ }
1626+};
1627+//vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
1628
1629=== added directory 'base_calendar/static/src/xml'
1630=== added file 'base_calendar/static/src/xml/base_calendar.xml'
1631--- base_calendar/static/src/xml/base_calendar.xml 1970-01-01 00:00:00 +0000
1632+++ base_calendar/static/src/xml/base_calendar.xml 2013-11-07 16:05:43 +0000
1633@@ -0,0 +1,43 @@
1634+<?xml version="1.0" encoding="UTF-8"?>
1635+<template>
1636+ <t t-name="many2manyattendee">
1637+ <t t-set="i" t-value="0"/>
1638+ <t t-foreach="elements" t-as="el">
1639+ <span class="oe_tag" t-att-data-index="i">
1640+ <a t-attf-class="oe_invitation #{el[2]}"/>
1641+ <t t-esc="el[1]"/>
1642+ </span>
1643+ <t t-set="i" t-value="i + 1"/>
1644+ </t>
1645+ </t>
1646+ <t t-name="invitation_view">
1647+ <div class="oe_right"><b><t t-esc="invitation['current_attendee'].cn"/> (<t t-esc="invitation['current_attendee'].email"/>)</b></div>
1648+ <div class="oe_left"><img class="cal_inline cal_image" t-attf-src="data:image/png;base64,#{invitation['logo']}"/><p class="cal_tag cal_inline">Calendar</p></div>
1649+ <div class="invitation_block">
1650+ <t t-if="invitation['current_attendee'].state != 'needs-action'">
1651+ <div class="event_status"><a t-attf-class="attendee_#{invitation['current_attendee'].state}"><b t-if="invitation['current_attendee'].state == 'accepted'">Yes I'm going.</b><b t-if="invitation['current_attendee'].state == 'declined'">No I'm not going.</b></a></div>
1652+ </t>
1653+ <div class="cal_meeting"><t t-esc="invitation['meeting'].event"/></div>
1654+ <table calss="invitation_block">
1655+ <tr>
1656+ <td class="cal_lable">When</td>
1657+ <td>: <t t-esc="invitation['meeting'].when"/></td>
1658+ </tr>
1659+ <tr>
1660+ <td class="cal_lable">Where</td>
1661+ <td>: <t t-esc="invitation['meeting'].where or '-'"/></td>
1662+ </tr>
1663+ <tr>
1664+ <td class="cal_lable">Who</td>
1665+ <td>
1666+ <span>: <t t-esc="invitation['meeting'].organizer"/> - <a class="cal_lable">Organizer</a></span>
1667+ <t t-foreach="invitation['attendee']" t-as="att">
1668+ <br/>
1669+ <span class="cal_status"><a t-attf-class="oe_invitation #{att.status}"/><t t-esc="att.name"/></span>
1670+ </t>
1671+ </td>
1672+ </tr>
1673+ </table>
1674+ </div>
1675+ </t>
1676+</template>
1677
1678=== modified file 'portal/security/ir.model.access.csv'
1679--- portal/security/ir.model.access.csv 2013-04-29 09:33:29 +0000
1680+++ portal/security/ir.model.access.csv 2013-11-07 16:05:43 +0000
1681@@ -6,4 +6,4 @@
1682 access_res_partner_portal,res.partner.portal,base.model_res_partner,portal.group_portal,1,0,0,0
1683 access_acquirer,portal.payment.acquirer,portal.model_portal_payment_acquirer,,1,0,0,0
1684 access_acquirer_all,portal.payment.acquirer,portal.model_portal_payment_acquirer,base.group_system,1,1,1,1
1685-access_ir_attachment_group_portal,ir.attachment group_portal,base.model_ir_attachment,portal.group_portal,1,0,1,0
1686\ No newline at end of file
1687+access_ir_attachment_group_portal,ir.attachment group_portal,base.model_ir_attachment,portal.group_portal,1,0,1,0
1688
1689=== modified file 'portal_crm/__openerp__.py'
1690--- portal_crm/__openerp__.py 2012-11-29 22:26:45 +0000
1691+++ portal_crm/__openerp__.py 2013-11-07 16:05:43 +0000
1692@@ -33,6 +33,7 @@
1693 'depends': ['crm','portal'],
1694 'data': [
1695 'contact_view.xml',
1696+ 'security/ir.model.access.csv',
1697 ],
1698 'test': [
1699 'test/contact_form.yml',
1700
1701=== added directory 'portal_crm/security'
1702=== added file 'portal_crm/security/ir.model.access.csv'
1703--- portal_crm/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
1704+++ portal_crm/security/ir.model.access.csv 2013-11-07 16:05:43 +0000
1705@@ -0,0 +1,3 @@
1706+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
1707+access_crm_meeting_portal,crm.meeting.portal,base_calendar.model_crm_meeting,portal.group_portal,1,1,0,0
1708+access_crm_meeting_type_portal,crm.meeting.type.portal,base_calendar.model_crm_meeting_type,portal.group_portal,1,0,0,0
1709
1710=== modified file 'portal_hr_employees/hr_employee.py'
1711--- portal_hr_employees/hr_employee.py 2012-12-10 11:16:54 +0000
1712+++ portal_hr_employees/hr_employee.py 2013-11-07 16:05:43 +0000
1713@@ -56,3 +56,16 @@
1714 _defaults = {
1715 'visibility': 'private',
1716 }
1717+
1718+class calendar_attendee(osv.osv):
1719+ _inherit = 'calendar.attendee'
1720+
1721+ def create(self, cr, uid, vals, context=None):
1722+ user_pool = self.pool.get('res.users')
1723+ partner_id = vals.get('partner_id')
1724+ users = user_pool.search_read(cr, uid, [('partner_id','=', partner_id)],['employee_ids'], context=context)
1725+ for user in users:
1726+ if user['employee_ids']:
1727+ vals['state'] = 'accepted'
1728+ return super(calendar_attendee, self).create(cr, uid, vals, context=context)
1729+

Subscribers

People subscribed via source and target branches

to all changes: