-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathmodels_mail_thread.go
More file actions
executable file
·2254 lines (2220 loc) · 110 KB
/
models_mail_thread.go
File metadata and controls
executable file
·2254 lines (2220 loc) · 110 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package mail
import (
"net/http"
"github.com/hexya-erp/hexya/src/controllers"
"github.com/hexya-erp/hexya/src/models"
"github.com/hexya-erp/hexya/src/models/types"
"github.com/hexya-erp/hexya/src/models/types/dates"
"github.com/hexya-erp/pool/h"
"github.com/hexya-erp/pool/q"
)
//import base64
//import datetime
//import dateutil
//import email
//import hashlib
//import hmac
//import lxml
//import logging
//import pytz
//import re
//import socket
//import time
//import xmlrpclib
//_logger = logging.getLogger(__name__)
func init() {
h.MailThread().DeclareModel()
// _mail_flat_thread = True # flatten the discussino history
// _mail_post_access = 'write' # access required on the document to post on it
// _mail_mass_mailing = False # enable mass mailing on this model
// _Attachment = namedtuple('Attachment', ('fname', 'content', 'info'))
h.MailThread().AddFields(map[string]models.FieldDefinition{
"MessageIsFollower": models.BooleanField{
String: "Is Follower",
Compute: h.MailThread().Methods().ComputeIsFollower(),
//search='_search_is_follower'
},
"MessageFollowerIds": models.One2ManyField{
RelationModel: h.MailFollowers(),
ReverseFK: "",
String: "Followers",
Filter: q.ResModel().Equals("MailThread"),
},
"MessagePartnerIds": models.Many2ManyField{
RelationModel: h.Partner(),
String: "Followers (Partners)",
Compute: h.MailThread().Methods().GetFollowers(),
//search='_search_follower_partners'
},
"MessageChannelIds": models.Many2ManyField{
RelationModel: h.MailChannel(),
String: "Followers (Channels)",
Compute: h.MailThread().Methods().GetFollowers(),
//search='_search_follower_channels'
},
"MessageIds": models.One2ManyField{
RelationModel: h.MailMessage(),
ReverseFK: "",
String: "Messages",
Filter: q.Model().Equals("MailThread"),
},
"MessageLastPost": models.DateTimeField{
String: "Last Message Date",
Help: "Date of the last message posted on the record.",
},
"MessageUnread": models.BooleanField{
String: "Unread Messages",
Compute: h.MailThread().Methods().GetMessageUnread(),
Help: "If checked new messages require your attention.",
},
"MessageUnreadCounter": models.IntegerField{
String: "Unread Messages Counter",
Compute: h.MailThread().Methods().GetMessageUnread(),
Help: "Number of unread messages",
},
"MessageNeedaction": models.BooleanField{
String: "Action Needed",
Compute: h.MailThread().Methods().GetMessageNeedaction(),
//search='_search_message_needaction'
Help: "If checked, new messages require your attention.",
},
"MessageNeedactionCounter": models.IntegerField{
String: "Number of Actions",
Compute: h.MailThread().Methods().GetMessageNeedaction(),
Help: "Number of messages which requires an action",
},
})
h.MailThread().Methods().GetFollowers().DeclareMethod(
`GetFollowers`,
func(rs h.MailThreadSet) h.MailThreadData {
// self.message_partner_ids = self.message_follower_ids.mapped(
// 'partner_id')
// self.message_channel_ids = self.message_follower_ids.mapped(
// 'channel_id')
})
h.MailThread().Methods().SearchFollowerPartners().DeclareMethod(
`Search function for message_follower_ids
Do not use with operator 'not in'. Use instead
message_is_followers
`,
func(rs m.MailThreadSet, operator interface{}, operand interface{}) {
// assert operator != "not in", "Do not search message_follower_ids with 'not in'"
// followers = self.env['mail.followers'].sudo().search([
// ('res_model', '=', self._name),
// ('partner_id', operator, operand)])
// return [('id', 'in', followers.mapped('res_id'))]
})
h.MailThread().Methods().SearchFollowerChannels().DeclareMethod(
`Search function for message_follower_ids
Do not use with operator 'not in'. Use instead
message_is_followers
`,
func(rs m.MailThreadSet, operator interface{}, operand interface{}) {
// assert operator != "not in", "Do not search message_follower_ids with 'not in'"
// followers = self.env['mail.followers'].sudo().search([
// ('res_model', '=', self._name),
// ('channel_id', operator, operand)])
// return [('id', 'in', followers.mapped('res_id'))]
})
h.MailThread().Methods().ComputeIsFollower().DeclareMethod(
`ComputeIsFollower`,
func(rs h.MailThreadSet) h.MailThreadData {
// followers = self.env['mail.followers'].sudo().search([
// ('res_model', '=', self._name),
// ('res_id', 'in', self.ids),
// ('partner_id', '=', self.env.user.partner_id.id),
// ])
// following_ids = followers.mapped('res_id')
// for record in self:
// record.message_is_follower = record.id in following_ids
})
h.MailThread().Methods().SearchIsFollower().DeclareMethod(
`SearchIsFollower`,
func(rs m.MailThreadSet, operator interface{}, operand interface{}) {
// followers = self.env['mail.followers'].sudo().search([
// ('res_model', '=', self._name),
// ('partner_id', '=', self.env.user.partner_id.id),
// ])
// if (operator == '=' and operand) or (operator == '!=' and not operand):
// return [('id', 'in', followers.mapped('res_id'))]
// else:
// return [('id', 'not in', followers.mapped('res_id'))]
})
h.MailThread().Methods().GetMessageUnread().DeclareMethod(
`GetMessageUnread`,
func(rs h.MailThreadSet) h.MailThreadData {
// res = dict((res_id, 0) for res_id in self.ids)
// partner_id = self.env.user.partner_id.id
// self._cr.execute(""" SELECT msg.res_id FROM mail_message msg
// RIGHT JOIN mail_message_mail_channel_rel rel
// ON rel.mail_message_id = msg.id
// RIGHT JOIN mail_channel_partner cp
// ON (cp.channel_id = rel.mail_channel_id AND cp.partner_id = %s AND
// (cp.seen_message_id IS NULL OR cp.seen_message_id < msg.id))
// WHERE msg.model = %s AND msg.res_id = ANY(%s) AND
// (msg.author_id IS NULL OR msg.author_id != %s) AND
// (msg.message_type != 'notification' OR msg.model != 'mail.channel')""",
// (partner_id, self._name, list(self.ids), partner_id))
// for result in self._cr.fetchall():
// res[result[0]] += 1
// for record in self:
// record.message_unread_counter = res.get(record.id, 0)
// record.message_unread = bool(record.message_unread_counter)
})
h.MailThread().Methods().GetMessageNeedaction().DeclareMethod(
`GetMessageNeedaction`,
func(rs h.MailThreadSet) h.MailThreadData {
// res = dict((res_id, 0) for res_id in self.ids)
// self._cr.execute(""" SELECT msg.res_id FROM mail_message msg
// RIGHT JOIN mail_message_res_partner_needaction_rel rel
// ON rel.mail_message_id = msg.id AND rel.res_partner_id = %s AND (rel.is_read = false OR rel.is_read IS NULL)
// WHERE msg.model = %s AND msg.res_id in %s""",
// (self.env.user.partner_id.id, self._name, tuple(self.ids)))
// for result in self._cr.fetchall():
// res[result[0]] += 1
// for record in self:
// record.message_needaction_counter = res.get(record.id, 0)
// record.message_needaction = bool(record.message_needaction_counter)
})
h.MailThread().Methods().SearchMessageNeedaction().DeclareMethod(
`SearchMessageNeedaction`,
func(rs m.MailThreadSet, operator interface{}, operand interface{}) {
// return [('message_ids.needaction', operator, operand)]
})
h.MailThread().Methods().Create().Extend(
` Chatter override :
- subscribe uid
- subscribe followers of parent
- log a creation message
`,
func(rs m.MailThreadSet, values models.RecordData) {
// if self._context.get('tracking_disable'):
// return super(MailThread, self).create(values)
// if not self._context.get('mail_create_nosubscribe'):
// # webclient can send None or False
// message_follower_ids = values.get('message_follower_ids') or []
// message_follower_ids += self.env['mail.followers']._add_follower_command(
// self._name, [], {self.env.user.partner_id.id: None}, {}, force=True)[0]
// values['message_follower_ids'] = message_follower_ids
// thread = super(MailThread, self).create(values)
// if not self._context.get('mail_create_nolog'):
// doc_name = self.env['ir.model'].search(
// [('model', '=', self._name)]).read(['name'])[0]['name']
// thread.message_post(body=_('%s created') % doc_name)
// create_values = dict(values)
// for key, val in self._context.iteritems():
// if key.startswith('default_') and key[8:] not in create_values:
// create_values[key[8:]] = val
// thread.message_auto_subscribe(
// create_values.keys(), values=create_values)
// if not self._context.get('mail_notrack'):
// if 'lang' not in self._context:
// track_thread = thread.with_context(lang=self.env.user.lang)
// else:
// track_thread = thread
// tracked_fields = track_thread._get_tracked_fields(values.keys())
// if tracked_fields:
// initial_values = {
// thread.id: dict.fromkeys(tracked_fields, False)}
// track_thread.message_track(tracked_fields, initial_values)
// return thread
})
h.MailThread().Methods().Write().Extend(
`Write`,
func(rs m.MailThreadSet, values models.RecordData) {
// if self._context.get('tracking_disable'):
// return super(MailThread, self).write(values)
// if 'lang' not in self._context:
// track_self = self.with_context(lang=self.env.user.lang)
// else:
// track_self = self
// tracked_fields = None
// if not self._context.get('mail_notrack'):
// tracked_fields = track_self._get_tracked_fields(values.keys())
// if tracked_fields:
// initial_values = dict((record.id, dict((key, getattr(record, key)) for key in tracked_fields))
// for record in track_self)
// result = super(MailThread, self).write(values)
// self.message_auto_subscribe(values.keys(), values=values)
// if tracked_fields:
// track_self.message_track(tracked_fields, initial_values)
// return result
})
h.MailThread().Methods().Unlink().Extend(
` Override unlink to delete messages and followers. This cannot be
cascaded, because link is done through (res_model, res_id). `,
func(rs m.MailThreadSet) {
// self.env['mail.message'].search(
// [('model', '=', self._name), ('res_id', 'in', self.ids)]).unlink()
// res = super(MailThread, self).unlink()
// self.env['mail.followers'].sudo().search(
// [('res_model', '=', self._name), ('res_id', 'in', self.ids)]
// ).unlink()
// return res
})
h.MailThread().Methods().CopyData().DeclareMethod(
`CopyData`,
func(rs m.MailThreadSet, defaultName interface{}) {
// return super(MailThread, self.with_context(mail_notrack=True)).copy_data(default=default)
})
h.MailThread().Methods().GetEmptyListHelp().DeclareMethod(
` Override of BaseModel.get_empty_list_help() to generate an help message
that adds alias information. `,
func(rs m.MailThreadSet, help interface{}) {
// model = self._context.get('empty_list_help_model')
// res_id = self._context.get('empty_list_help_id')
// catchall_domain = self.env['ir.config_parameter'].sudo(
// ).get_param("mail.catchall.domain")
// document_name = self._context.get(
// 'empty_list_help_document_name', _('document'))
// add_arrow = not help or help.find("oe_view_nocontent_create") == -1
// alias = None
// if catchall_domain and model and res_id:
// record = self.env[model].sudo().browse(res_id)
// # check that the alias effectively creates new records
// if record.alias_id and record.alias_id.alias_name and \
// record.alias_id.alias_model_id and \
// record.alias_id.alias_model_id.model == self._name and \
// record.alias_id.alias_force_thread_id == 0:
// alias = record.alias_id
// if not alias and catchall_domain and model:
// Alias = self.env['mail.alias']
// aliases = Alias.search([
// ("alias_parent_model_id.model", "=", model),
// ("alias_name", "!=", False),
// ('alias_force_thread_id', '=', False),
// ('alias_parent_thread_id', '=', False)], order='id ASC')
// if aliases and len(aliases) == 1:
// alias = aliases[0]
// if alias:
// email_link = "<a href='mailto:%(email)s'>%(email)s</a>" % {
// 'email': alias.name_get()[0][1]}
// if add_arrow:
// return "<p class='oe_view_nocontent_create'>%(dyn_help)s</p>%(static_help)s" % {
// 'static_help': help or '',
// 'dyn_help': _("Click here to add new %(document)s or send an email to: %(email_link)s") % {
// 'document': document_name,
// 'email_link': email_link
// }
// }
// # do not add alias two times if it was added previously
// if not help or help.find("oe_view_nocontent_alias") == -1:
// return '%(static_help)s<p class="oe_view_nocontent_alias">%(dyn_help)s</p>' % {
// 'static_help': help or '',
// 'dyn_help': _("You could also add a new %(document)s by sending an email to: %(email_link)s.") % {
// 'document': document_name,
// 'email_link': email_link,
// }
// }
// if add_arrow:
// return "<p class='oe_view_nocontent_create'>%(dyn_help)s</p>%(static_help)s" % {
// 'static_help': help or '',
// 'dyn_help': _("Click here to add new %s") % document_name,
// }
// return help
})
h.MailThread().Methods().FieldsViewGet().Extend(
`FieldsViewGet`,
func(rs m.MailThreadSet, view_id webdata.FieldsViewGetParams, view_type interface{}, toolbar interface{}, submenu interface{}) {
// res = super(MailThread, self).fields_view_get(
// view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
// if view_type == 'form':
// doc = etree.XML(res['arch'])
// for node in doc.xpath("//field[@name='message_ids']"):
// # the 'Log a note' button is employee only
// options = safe_eval(node.get('options', '{}'))
// is_employee = self.env.user.has_group('base.group_user')
// options['display_log_button'] = is_employee
// # emoji list
// options['emoji_list'] = self.env['mail.shortcode'].search(
// [('shortcode_type', '=', 'image')]).read(['source', 'description', 'substitution'])
// # save options on the node
// node.set('options', repr(options))
// res['arch'] = etree.tostring(doc)
// return res
})
h.MailThread().Methods().GetTrackedFields().DeclareMethod(
` Return a structure of tracked fields for the current model.
:param list updated_fields: modified field names
:return dict: a dict mapping field name to
description, containing
always tracked fields and modified on_change fields
`,
func(rs m.MailThreadSet, updated_fields interface{}) {
// tracked_fields = []
// for name, field in self._fields.items():
// if getattr(field, 'track_visibility', False):
// tracked_fields.append(name)
// if tracked_fields:
// return self.fields_get(tracked_fields)
// return {}
})
h.MailThread().Methods().TrackSubtype().DeclareMethod(
` Give the subtypes triggered by the changes on the record according
to values that have been updated.
:param ids: list of a single ID, the ID of the
record being modified
:type ids: singleton list
:param init_values: the original values of the
record; only modified fields
are present in the dict
:type init_values: dict
:returns: a subtype xml_id or False if no subtype is trigerred
`,
func(rs m.MailThreadSet, init_values interface{}) {
// return False
})
h.MailThread().Methods().TrackTemplate().DeclareMethod(
`TrackTemplate`,
func(rs m.MailThreadSet, tracking interface{}) {
// return dict()
})
h.MailThread().Methods().MessageTrackPostTemplate().DeclareMethod(
`MessageTrackPostTemplate`,
func(rs m.MailThreadSet, tracking interface{}) {
// if not any(change for rec_id, (change, tracking_value_ids) in tracking.iteritems()):
// return True
// templates = self._track_template(tracking)
// for field_name, (template, post_kwargs) in templates.iteritems():
// if not template:
// continue
// if isinstance(template, basestring):
// self.message_post_with_view(template, **post_kwargs)
// else:
// self.message_post_with_template(template.id, **post_kwargs)
// return True
})
h.MailThread().Methods().MessageTrackGetChanges().DeclareMethod(
` Batch method of _message_track. `,
func(rs m.MailThreadSet, tracked_fields interface{}, initial_values interface{}) {
// result = dict()
// for record in self:
// result[record.id] = record._message_track(
// tracked_fields, initial_values[record.id])
// return result
})
h.MailThread().Methods().MessageTrack().DeclareMethod(
` For a given record, fields to check (tuple column name, column info)
and initial values, return a structure that is
a tuple containing :
- a set of updated column names
- a list of changes (initial value, new value,
column name, column info) `,
func(rs m.MailThreadSet, tracked_fields interface{}, initial interface{}) {
// self.ensure_one()
// changes = set() # contains always and onchange tracked fields that changed
// displays = set()
// tracking_value_ids = []
// display_values_ids = []
// for col_name, col_info in tracked_fields.items():
// track_visibility = getattr(
// self._fields[col_name], 'track_visibility', 'onchange')
// initial_value = initial[col_name]
// new_value = getattr(self, col_name)
//
// # because browse null != False
// if new_value != initial_value and (new_value or initial_value):
// tracking = self.env['mail.tracking.value'].create_tracking_values(
// initial_value, new_value, col_name, col_info)
// if tracking:
// tracking_value_ids.append([0, 0, tracking])
//
// if col_name in tracked_fields:
// changes.add(col_name)
// # 'always' tracked fields in separate variable; added if other changes
// elif new_value == initial_value and track_visibility == 'always' and col_name in tracked_fields:
// tracking = self.env['mail.tracking.value'].create_tracking_values(
// initial_value, initial_value, col_name, col_info)
// if tracking:
// display_values_ids.append([0, 0, tracking])
// displays.add(col_name)
// if changes and displays:
// tracking_value_ids = display_values_ids + tracking_value_ids
// return changes, tracking_value_ids
})
h.MailThread().Methods().MessageTrack().DeclareMethod(
` Track updated values. Comparing the initial and current values of
the fields given in tracked_fields, it generates
a message containing
the updated values. This message can be linked
to a mail.message.subtype
given by the ``_track_subtype`` method. `,
func(rs m.MailThreadSet, tracked_fields interface{}, initial_values interface{}) {
// if not tracked_fields:
// return True
// tracking = self._message_track_get_changes(
// tracked_fields, initial_values)
// for record in self:
// changes, tracking_value_ids = tracking[record.id]
// if not changes:
// continue
//
// # find subtypes and post messages or log if no subtype found
// subtype_xmlid = False
// # By passing this key, that allows to let the subtype empty and so don't sent email because partners_to_notify from mail_message._notify will be empty
// if not self._context.get('mail_track_log_only'):
// subtype_xmlid = record._track_subtype(
// dict((col_name, initial_values[record.id][col_name]) for col_name in changes))
//
// if subtype_xmlid:
// # TDE FIXME check for raise if not found
// subtype_rec = self.env.ref(subtype_xmlid)
// if not (subtype_rec and subtype_rec.exists()):
// _logger.debug('subtype %s not found' % subtype_xmlid)
// continue
// record.message_post(subtype=subtype_xmlid,
// tracking_value_ids=tracking_value_ids)
// elif tracking_value_ids:
// record.message_post(tracking_value_ids=tracking_value_ids)
// self._message_track_post_template(tracking)
// return True
})
h.MailThread().Methods().NeedactionDomainGet().DeclareMethod(
`NeedactionDomainGet`,
func(rs m.MailThreadSet) {
// if self._needaction:
// return [('message_needaction', '=', True)]
// return []
})
h.MailThread().Methods().GarbageCollectAttachments().DeclareMethod(
` Garbage collect lost mail attachments. Those are attachments
- linked to res_model 'mail.compose.message',
the composer wizard
- with res_id 0, because they were created
outside of an existing
wizard (typically user input through Chatter or reports
created on-the-fly by the templates)
- unused since at least one day (create_date and write_date)
`,
func(rs m.MailThreadSet) {
// limit_date = datetime.datetime.utcnow() - datetime.timedelta(days=1)
// limit_date_str = datetime.datetime.strftime(
// limit_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
// self.env['ir.attachment'].search([
// ('res_model', '=', 'mail.compose.message'),
// ('res_id', '=', 0),
// ('create_date', '<', limit_date_str),
// ('write_date', '<', limit_date_str)]
// ).unlink()
// return True
})
h.MailThread().Methods().CheckMailMessageAccess().DeclareMethod(
` mail.message check permission rules for related document.
This method is
meant to be inherited in order to implement
addons-specific behavior.
A common behavior would be to allow creating
messages when having read
access rule on the document, for portal document
such as issues. `,
func(rs m.MailThreadSet, res_ids interface{}, operation interface{}, model_name interface{}) {
// if model_name:
// DocModel = self.env[model_name]
// else:
// DocModel = self
// if hasattr(DocModel, '_mail_post_access'):
// create_allow = DocModel._mail_post_access
// else:
// create_allow = 'write'
// if operation in ['write', 'unlink']:
// check_operation = 'write'
// elif operation == 'create' and create_allow in ['create', 'read', 'write', 'unlink']:
// check_operation = create_allow
// elif operation == 'create':
// check_operation = 'write'
// else:
// check_operation = operation
// DocModel.check_access_rights(check_operation)
// DocModel.browse(res_ids).check_access_rule(check_operation)
})
h.MailThread().Methods().GetInboxActionXmlId().DeclareMethod(
` When redirecting towards the Inbox, choose which action xml_id has
to be fetched. This method is meant to be inherited,
at least in portal
because portal users have a different Inbox
action than classic users. `,
func(rs m.MailThreadSet) {
// return 'mail.mail_channel_action_client_chat'
})
h.MailThread().Methods().GenerateNotificationToken().DeclareMethod(
`GenerateNotificationToken`,
func(rs m.MailThreadSet, base_link interface{}, params interface{}) {
// secret = self.env['ir.config_parameter'].sudo(
// ).get_param('database.secret')
// token = '%s?%s' % (base_link, ' '.join('%s=%s' % (
// key, params[key]) for key in sorted(params.keys())))
// hm = hmac.new(str(secret), token, hashlib.sha1).hexdigest()
// return hm
})
h.MailThread().Methods().NotificationLinkHelper().DeclareMethod(
`NotificationLinkHelper`,
func(rs m.MailThreadSet, link_type interface{}) {
// if kwargs.get('message_id'):
// base_params = {
// 'message_id': kwargs.pop('message_id')
// }
// else:
// base_params = {
// 'model': kwargs.get('model', self._name),
// 'res_id': kwargs.get('res_id', self.ids and self.ids[0] or False),
// }
// if link_type in ['view', 'assign', 'follow', 'unfollow']:
// params = dict(base_params)
// base_link = '/mail/%s' % link_type
// elif link_type == 'new':
// params = dict(base_params, action_id=kwargs.get('action_id', ''))
// base_link = '/mail/new'
// elif link_type == 'controller':
// controller = kwargs.pop('controller')
// params = dict(base_params)
// params.pop('model')
// base_link = '%s' % controller
// else:
// return ''
// if link_type not in ['view', 'new']:
// token = self._generate_notification_token(base_link, params)
// params['token'] = token
// link = '%s?%s' % (base_link, url_encode(params))
// return link
})
h.MailThread().Methods().NotificationRecipients().DeclareMethod(
` Return groups used to classify recipients of a notification email.
Groups is a list of tuple containing of form (group_name,
group_func,
group_data) where
* group_name is an identifier used only to be
able to override and manipulate
groups. Default groups are user (recipients
linked to an employee user),
portal (recipients linked to a portal user)
and customer (recipients not
linked to any user). An example of override
use would be to add a group
linked to a res.groups like Hr Officers to set
specific action buttons to
them.
* group_func is a function pointer taking a partner
record as parameter. This
method will be applied on recipients to know
whether they belong to a given
group or not. Only first matching group is kept.
Evaluation order is the
list order.
* group_data is a dict containing parameters for
the notification email
* has_button_access: whether to display Access
<Document> in email. True
by default for new groups, False for portal / customer.
* button_access: dict with url and title of the button
* has_button_follow: whether to display Follow
in email (if recipient is
not currently following the thread). True by
default for new groups,
False for portal / customer.
* button_follow: dict with url adn title of the button
* has_button_unfollow: whether to display Unfollow
in email (if recipient
is currently following the thread). True by
default for new groups,
False for portal / customer.
* button_unfollow: dict with url and title of the button
* actions: list of action buttons to display
in the notification email.
Each action is a dict containing url and title
of the button.
Groups has a default value that you can find in mail_thread
_message_notification_recipients method.
`,
func(rs m.MailThreadSet, message interface{}, groups interface{}) {
// return groups
})
h.MailThread().Methods().MessageNotificationRecipients().DeclareMethod(
`MessageNotificationRecipients`,
func(rs m.MailThreadSet, message interface{}, recipients interface{}) {
// recipients_sudo = recipients.sudo()
// result = {}
// doc_followers = self.env['mail.followers']
// if message.model and message.res_id:
// doc_followers = self.env['mail.followers'].sudo().search([
// ('res_model', '=', message.model),
// ('res_id', '=', message.res_id),
// ('partner_id', 'in', recipients_sudo.ids)])
// partner_followers = doc_followers.mapped('partner_id')
// if self._context.get('auto_delete', False):
// access_link = self._notification_link_helper('view')
// else:
// access_link = self._notification_link_helper(
// 'view', message_id=message.id)
// if message.model:
// model_name = self.env['ir.model'].sudo().search(
// [('model', '=', self.env[message.model]._name)]).name_get()[0][1]
// view_title = '%s %s' % (_('View'), model_name)
// else:
// view_title = _('View')
// default_groups = [
// ('user', lambda partner: bool(partner.user_ids) and not any(
// user.share for user in partner.user_ids), {}),
// ('portal', lambda partner: bool(partner.user_ids) and all(user.share for user in partner.user_ids), {
// 'has_button_access': False,
// 'has_button_follow': False,
// 'has_button_unfollow': False,
// }),
// ('customer', lambda partner: True, {
// 'has_button_access': False,
// 'has_button_follow': False,
// 'has_button_unfollow': False,
// })
// ]
// groups = self._notification_recipients(message, default_groups)
// for group_name, group_func, group_data in groups:
// group_data.setdefault('has_button_access', True)
// group_data.setdefault('button_access', {
// 'url': access_link,
// 'title': view_title})
// group_data.setdefault('has_button_follow', True)
// group_data.setdefault('button_follow', {
// 'url': self._notification_link_helper('follow', model=message.model, res_id=message.res_id),
// 'title': _('Follow')})
// group_data.setdefault('has_button_unfollow', True)
// group_data.setdefault('button_unfollow', {
// 'url': self._notification_link_helper('unfollow', model=message.model, res_id=message.res_id),
// 'title': _('Unfollow')})
// group_data.setdefault('actions', list())
// group_data.setdefault('followers', self.env['res.partner'])
// group_data.setdefault('not_followers', self.env['res.partner'])
// for recipient in recipients:
// for group_name, group_func, group_data in groups:
// if group_func(recipient):
// if recipient in partner_followers:
// group_data['followers'] |= recipient
// else:
// group_data['not_followers'] |= recipient
// break
// for group_name, group_method, group_data in groups:
// result[group_name] = group_data
// return result
})
h.MailThread().Methods().MessageGetDefaultRecipients().DeclareMethod(
`MessageGetDefaultRecipients`,
func(rs m.MailThreadSet, res_model interface{}, res_ids interface{}) {
// if res_model and res_ids:
// if hasattr(self.env[res_model], 'message_get_default_recipients'):
// return self.env[res_model].browse(res_ids).message_get_default_recipients()
// records = self.env[res_model].sudo().browse(res_ids)
// else:
// records = self.sudo()
// res = {}
// for record in records:
// recipient_ids, email_to, email_cc = set(), False, False
// if 'partner_id' in self._fields and record.partner_id:
// recipient_ids.add(record.partner_id.id)
// elif 'email_from' in self._fields and record.email_from:
// email_to = record.email_from
// elif 'email' in self._fields:
// email_to = record.email
// res[record.id] = {'partner_ids': list(
// recipient_ids), 'email_to': email_to, 'email_cc': email_cc}
// return res
})
h.MailThread().Methods().MessageGetReplyTo().DeclareMethod(
` Returns the preferred reply-to email address that is basically the
alias of the document, if it exists. Override this
method to implement
a custom behavior about reply-to for generated emails. `,
func(rs m.MailThreadSet, res_ids interface{}, defaultName interface{}) {
// model_name = self.env.context.get('thread_model') or self._name
// alias_domain = self.env['ir.config_parameter'].get_param(
// "mail.catchall.domain")
// res = dict.fromkeys(res_ids, False)
// aliases = {}
// doc_names = {}
// if alias_domain:
// if model_name and model_name != 'mail.thread' and res_ids:
// mail_aliases = self.env['mail.alias'].sudo().search([
// ('alias_parent_model_id.model', '=', model_name),
// ('alias_parent_thread_id', 'in', res_ids),
// ('alias_name', '!=', False)])
// # take only first found alias for each thread_id, to match
// # order (1 found -> limit=1 for each res_id)
// for alias in mail_aliases:
// if alias.alias_parent_thread_id not in aliases:
// aliases[alias.alias_parent_thread_id] = '%s@%s' % (
// alias.alias_name, alias_domain)
// doc_names.update(
// dict((ng_res[0], ng_res[1])
// for ng_res in self.env[model_name].sudo().browse(aliases.keys()).name_get()))
// # left ids: use catchall
// left_ids = set(res_ids).difference(set(aliases.keys()))
// if left_ids:
// catchall_alias = self.env['ir.config_parameter'].get_param(
// "mail.catchall.alias")
// if catchall_alias:
// aliases.update(
// dict((res_id, '%s@%s' % (catchall_alias, alias_domain)) for res_id in left_ids))
// # compute name of reply-to
// company_name = self.env.user.company_id.name
// for res_id in aliases.keys():
// email_name = '%s%s' % (company_name, doc_names.get(
// res_id) and (' ' + doc_names[res_id]) or '')
// email_addr = aliases[res_id]
// res[res_id] = formataddr((email_name, email_addr))
// left_ids = set(res_ids).difference(set(aliases.keys()))
// if left_ids:
// res.update(dict((res_id, default) for res_id in res_ids))
// return res
})
h.MailThread().Methods().MessageGetEmailValues().DeclareMethod(
` Get specific notification email values to store on the notification
mail_mail. Void method, inherit it to add custom values. `,
func(rs m.MailThreadSet, notif_mail interface{}) {
// self.ensure_one()
// database_uuid = self.env['ir.config_parameter'].get_param(
// 'database.uuid')
// return {'headers': repr({
// 'X-Odoo-Objects': "%s-%s" % (self._name, self.id),
// 'X-Odoo-db-uuid': database_uuid
// })}
})
h.MailThread().Methods().MessageGetRecipientValues().DeclareMethod(
` Get specific notification recipient values to store on the notification
mail_mail. Basic method just set the recipient
partners as mail_mail
recipients. Inherit this method to add custom behavior
like using
recipient email_to to bypass the recipint_ids heuristics in the
mail sending mechanism. `,
func(rs m.MailThreadSet, notif_message interface{}, recipient_ids interface{}) {
// return {
// 'recipient_ids': [(4, pid) for pid in recipient_ids]
// }
})
h.MailThread().Methods().MessageCapableModels().DeclareMethod(
` Used by the plugin addon, based for plugin_outlook and others. `,
func(rs m.MailThreadSet) {
// ret_dict = {}
// for model_name, model in self.env.iteritems():
// if hasattr(model, "message_process") and hasattr(model, "message_post"):
// ret_dict[model_name] = model._description
// return ret_dict
})
h.MailThread().Methods().MessageFindPartners().DeclareMethod(
` Find partners related to some header fields of the message.
:param string message: an email.message instance `,
func(rs m.MailThreadSet, message interface{}, header_fields interface{}) {
// s = ', '.join([tools.decode_smtp_header(message.get(h))
// for h in header_fields if message.get(h)])
// return filter(lambda x: x, self._find_partner_from_emails(tools.email_split(s)))
})
h.MailThread().Methods().RoutingWarn().DeclareMethod(
` Tools method used in message_route_verify: whether to
log a warning or raise an error `,
func(rs m.MailThreadSet, error_message interface{}, warn_suffix interface{}, message_id interface{}, route interface{}, raise_exception interface{}) {
// full_message = _(
// 'Routing mail with Message-Id %s: route %s: %s') % (message_id, route, error_message)
// if raise_exception:
// raise ValueError(full_message)
// else:
// _logger.info(full_message + warn_suffix and '; %s' %
// warn_suffix or '')
})
h.MailThread().Methods().RoutingCreateBounceEmail().DeclareMethod(
`RoutingCreateBounceEmail`,
func(rs m.MailThreadSet, email_from interface{}, body_html interface{}, message interface{}) {
// bounce_to = tools.decode_message_header(
// message, 'Return-Path') or email_from
// bounce_mail_values = {
// 'body_html': body_html,
// 'subject': 'Re: %s' % message.get('subject'),
// 'email_to': bounce_to,
// 'auto_delete': True,
// }
// bounce_from = self.env['ir.mail_server']._get_default_bounce_address()
// if bounce_from:
// bounce_mail_values['email_from'] = 'MAILER-DAEMON <%s>' % bounce_from
// self.env['mail.mail'].create(bounce_mail_values).send()
})
h.MailThread().Methods().MessageRouteVerify().DeclareMethod(
` Verify route validity. Check and rules:
1 - if thread_id -> check that document effectively
exists; otherwise
fallback on a message_new by resetting thread_id
2 - check that message_update exists if thread_id
is set; or at least
that message_new exist
[ - find author_id if udpate_author is set]
3 - if there is an alias, check alias_contact:
'followers' and thread_id:
check on target document that the author
is in the followers
'followers' and alias_parent_thread_id:
check on alias parent document that
the author is in the
followers
'partners': check that author_id id set
:param message: an email.message instance
:param message_dict: dictionary of values that will be given to
mail_message.create()
:param route: route to check which is a tuple (model, thread_id,
custom_values, uid, alias)
:param update_author: update message_dict['author_id'].
TDE TODO: move me
:param assert_model: if an error occurs, tell whether
to raise an error
or just log a warning and
try other processing or
invalidate route
:param create_fallback: if the route aims at updating
a record and that
record does not exists
or does not support update
either fallback on creating
a new record in the
same model or raise / warn
:param allow_private: allow void model / thread_id
routes, aka private
discussions
`,
func(rs m.MailThreadSet, message interface{}, message_dict interface{}, route interface{}, update_author interface{}, assert_model interface{}, create_fallback interface{}, allow_private interface{}, drop_alias interface{}) {
// assert isinstance(route, (list, tuple)
// ), 'A route should be a list or a tuple'
// assert len(
// route) == 5, 'A route should contain 5 elements: model, thread_id, custom_values, uid, alias record'
// message_id = message.get('Message-Id')
// email_from = tools.decode_message_header(message, 'From')
// author_id = message_dict.get('author_id')
// model, thread_id, alias = route[0], route[1], route[4]
// record_set = None
// _generic_bounce_body_html = """<div>
//<p>Hello,</p>
//<p>The following email sent to %s cannot be accepted because this is a private email address.
// Only allowed people can contact us at this address.</p>
//</div><blockquote>%s</blockquote>""" % (message.get('to'), message_dict.get('body'))
// if model and model not in self.env:
// self._routing_warn(_('unknown target model %s') %
// model, '', message_id, route, assert_model)
// return ()
// if not model:
// # should not contain any thread_id
// if thread_id:
// self._routing_warn(_('posting a message without model should be with a null res_id (private message), received %s') % thread_id, _(
// 'resetting thread_id'), message_id, route, assert_model)
// thread_id = 0
// # should have a parent_id (only answers)
// if not message_dict.get('parent_id'):
// self._routing_warn(_('posting a message without model should be with a parent_id (private message)'), _(
// 'skipping'), message_id, route, assert_model)
// return False
// if model and thread_id:
// record_set = self.env[model].browse(thread_id)
// elif model:
// record_set = self.env[model]
// if thread_id:
// if not record_set.exists() and create_fallback:
// self._routing_warn(_('reply to missing document (%s,%s), fall back on new document creation') % (
// model, thread_id), '', message_id, route, False)
// thread_id = None
// elif not hasattr(record_set, 'message_update') and create_fallback:
// self._routing_warn(
// _('model %s does not accept document update, fall back on document creation') % model, '', message_id, route, False)
// thread_id = None
//
// if not record_set.exists():
// self._routing_warn(_('reply to missing document (%s,%s)') % (
// model, thread_id), _('skipping'), message_id, route, assert_model)
// return False
// elif not hasattr(record_set, 'message_update'):
// self._routing_warn(_('model %s does not accept document update') % model, _(
// 'skipping'), message_id, route, assert_model)
// return False
// if not thread_id and model and not hasattr(record_set, 'message_new'):
// self._routing_warn(_('model %s does not accept document creation') % model, _(
// 'skipping'), message_id, route, assert_model)
// return False
// if not author_id and update_author:
// author_ids = self.env['mail.thread']._find_partner_from_emails(
// [email_from], res_model=model, res_id=thread_id)
// if author_ids:
// author_id = author_ids[0]
// message_dict['author_id'] = author_id
// if alias and alias.alias_contact == 'followers' and (thread_id or alias.alias_parent_thread_id):
// if thread_id:
// obj = record_set[0]
// else:
// obj = self.env[alias.alias_parent_model_id.model].browse(
// alias.alias_parent_thread_id)
// accepted_partner_ids = list(
// set(partner.id for partner in obj.message_partner_ids) |
// set(partner.id for channel in obj.message_channel_ids for partner in channel.channel_partner_ids)
// )
// if not author_id or author_id not in accepted_partner_ids:
// self._routing_warn(_('alias %s restricted to internal followers') %
// alias.alias_name, _('skipping'), message_id, route, False)
// self._routing_create_bounce_email(
// email_from, _generic_bounce_body_html, message)
// return False
// elif alias and alias.alias_contact == 'partners' and not author_id:
// self._routing_warn(_('alias %s does not accept unknown author') %
// alias.alias_name, _('skipping'), message_id, route, False)
// self._routing_create_bounce_email(
// email_from, _generic_bounce_body_html, message)
// return False
// if not model and not thread_id and not alias and not allow_private:
// return False
// return (model, thread_id, route[2], route[3], None if drop_alias else route[4])
})
h.MailThread().Methods().MessageRoute().DeclareMethod(
` Attempt to figure out the correct target model, thread_id,
custom_values and user_id to use for an incoming message.
Multiple values may be returned, if a message had multiple
recipients matching existing mail.aliases, for example.
The following heuristics are used, in this order:
* if the message replies to an existing thread
by having a Message-Id
that matches an existing mail_message.message_id,
we take the original
message model/thread_id pair and ignore custom_value
as no creation will
take place
* if the message replies to an existing thread
by having In-Reply-To or
References matching odoo model/thread_id Message-Id
and if this thread
has messages without message_id, take this model/thread_id
pair and