1
+ import copy
2
+ import contextlib
3
+ import six
4
+
1
5
from django .conf import settings
6
+ from django .contrib .auth .models import AbstractBaseUser
2
7
from django .contrib .sites .shortcuts import get_current_site
3
8
from django .core import mail
9
+ from django .utils import translation
4
10
from django .template .context import make_context
5
11
from django .template .loader import get_template
6
12
from django .template .loader_tags import BlockNode , ExtendsNode
7
13
from django .views .generic .base import ContextMixin
8
14
9
15
16
+ LOCALE_FIELD = getattr (
17
+ settings , 'TEMPLATED_MAIL' , {}
18
+ ).get ('locale_field' , None )
19
+
20
+
21
+ @contextlib .contextmanager
22
+ def translation_context (language ):
23
+ prev_language = translation .get_language ()
24
+ try :
25
+ translation .activate (language )
26
+ yield
27
+ finally :
28
+ translation .activate (prev_language )
29
+
30
+
31
+ @contextlib .contextmanager
32
+ def nullcontext ():
33
+ yield
34
+
35
+
10
36
class BaseEmailMessage (mail .EmailMultiAlternatives , ContextMixin ):
11
37
_node_map = {
12
38
'subject' : 'subject' ,
@@ -25,8 +51,9 @@ def __init__(self, request=None, context=None, template_name=None,
25
51
26
52
if template_name is not None :
27
53
self .template_name = template_name
54
+ self .is_rendered = False
28
55
29
- def get_context_data (self , ** kwargs ):
56
+ def get_context_data (self , user = None , ** kwargs ):
30
57
ctx = super (BaseEmailMessage , self ).get_context_data (** kwargs )
31
58
context = dict (ctx , ** self .context )
32
59
if self .request :
@@ -40,14 +67,14 @@ def get_context_data(self, **kwargs):
40
67
site_name = context .get ('site_name' ) or (
41
68
getattr (settings , 'SITE_NAME' , '' ) or site .name
42
69
)
43
- user = context .get ('user' ) or self .request .user
70
+ user = user or context .get ('user' ) or self .request .user
44
71
else :
45
72
domain = context .get ('domain' ) or getattr (settings , 'DOMAIN' , '' )
46
73
protocol = context .get ('protocol' ) or 'http'
47
74
site_name = context .get ('site_name' ) or getattr (
48
75
settings , 'SITE_NAME' , ''
49
76
)
50
- user = context .get ('user' )
77
+ user = user or context .get ('user' )
51
78
52
79
context .update ({
53
80
'domain' : domain ,
@@ -57,27 +84,62 @@ def get_context_data(self, **kwargs):
57
84
})
58
85
return context
59
86
60
- def render (self ):
61
- context = make_context (self .get_context_data (), request = self .request )
87
+ def render (self , user = None ):
88
+ if self .is_rendered :
89
+ return
90
+ context = make_context (
91
+ self .get_context_data (user ),
92
+ request = self .request ,
93
+ )
94
+ if user is None or LOCALE_FIELD is None :
95
+ language_context = nullcontext ()
96
+ else :
97
+ language_context = translation_context (
98
+ getattr (user , LOCALE_FIELD )
99
+ )
62
100
template = get_template (self .template_name )
63
- with context .bind_template (template .template ):
101
+ with language_context , context .bind_template (template .template ):
64
102
blocks = self ._get_blocks (template .template .nodelist , context )
65
103
for block_node in blocks .values ():
66
104
self ._process_block (block_node , context )
67
105
self ._attach_body ()
106
+ self .is_rendered = True
68
107
69
- def send (self , to , * args , ** kwargs ):
70
- self .render ()
71
-
72
- self .to = to
73
- self .cc = kwargs .pop ('cc' , [])
74
- self .bcc = kwargs .pop ('bcc' , [])
75
- self .reply_to = kwargs .pop ('reply_to' , [])
76
- self .from_email = kwargs .pop (
77
- 'from_email' , settings .DEFAULT_FROM_EMAIL
78
- )
79
-
80
- super (BaseEmailMessage , self ).send (* args , ** kwargs )
108
+ def send (self , to , single_email = True , * args , ** kwargs ):
109
+ if single_email :
110
+ self .render ()
111
+ to_emails = []
112
+ for recipient in to :
113
+ if isinstance (recipient , AbstractBaseUser ):
114
+ to_emails .append (recipient .email )
115
+ elif isinstance (recipient , six .string_types ):
116
+ to_emails .append (recipient )
117
+ else :
118
+ raise TypeError (
119
+ 'The `to` argument should contain strings or users'
120
+ )
121
+ self .to = to_emails
122
+ self .cc = kwargs .pop ('cc' , [])
123
+ self .bcc = kwargs .pop ('bcc' , [])
124
+ self .reply_to = kwargs .pop ('reply_to' , [])
125
+ self .from_email = kwargs .pop (
126
+ 'from_email' , settings .DEFAULT_FROM_EMAIL
127
+ )
128
+ super (BaseEmailMessage , self ).send (* args , ** kwargs )
129
+ else :
130
+ for recipient in to :
131
+ email = copy .copy (self )
132
+ if isinstance (recipient , AbstractBaseUser ):
133
+ email_to = [recipient .email ]
134
+ email .render (user = recipient )
135
+ elif isinstance (recipient , six .string_types ):
136
+ email_to = [recipient ]
137
+ email .render ()
138
+ else :
139
+ raise TypeError (
140
+ 'The `to` argument should contain strings or users'
141
+ )
142
+ email .send (to = email_to , * args , ** kwargs )
81
143
82
144
def _process_block (self , block_node , context ):
83
145
attr = self ._node_map .get (block_node .name )
0 commit comments