Преглед изворни кода

Closes #69: Send email on account creation

Bastien Sevajol (Algoo) пре 8 година
родитељ
комит
b94fce8dcd

+ 3 - 0
tracim/development.ini.base Прегледај датотеку

@@ -174,8 +174,11 @@ email.notification.activated = False
174 174
 email.notification.from = Tracim Notification <noreply@trac.im>
175 175
 email.notification.content_update.template.html = ./tracim/templates/mail/content_update_body_html.mak
176 176
 email.notification.content_update.template.text = ./tracim/templates/mail/content_update_body_text.mak
177
+email.notification.created_account.template.html = ./tracim/templates/mail/created_account_body_html.mak
178
+email.notification.created_account.template.text = ./tracim/templates/mail/created_account_body_text.mak
177 179
 # Note: items between { and } are variable names. Do not remove / rename them
178 180
 email.notification.content_update.subject = [{website_title}] [{workspace_label}] {content_label} ({content_status_label})
181
+email.notification.created_account.subject = [{website_title}] Created account
179 182
 # processing_mode may be sync or async
180 183
 email.notification.processing_mode = sync
181 184
 email.notification.smtp.server = your_smtp_server

+ 17 - 0
tracim/tracim/command/user.py Прегледај датотеку

@@ -5,6 +5,7 @@ from tg import config
5 5
 
6 6
 from tracim.command import AppContextCommand, Extender
7 7
 from tracim.lib.auth.ldap import LDAPAuth
8
+from tracim.lib.email import get_email_manager
8 9
 from tracim.lib.exception import AlreadyExistError, CommandAbortedError
9 10
 from tracim.lib.group import GroupApi
10 11
 from tracim.lib.user import UserApi
@@ -68,6 +69,15 @@ class UserCommand(AppContextCommand):
68 69
             default=[],
69 70
         )
70 71
 
72
+        parser.add_argument(
73
+            "--send-email",
74
+            help='Send mail to user',
75
+            dest='send_email',
76
+            required=False,
77
+            action='store_true',
78
+            default=False,
79
+        )
80
+
71 81
         return parser
72 82
 
73 83
     def _user_exist(self, login):
@@ -126,6 +136,13 @@ class UserCommand(AppContextCommand):
126 136
                 user = self._create_user(login=parsed_args.login, password=parsed_args.password)
127 137
             except AlreadyExistError:
128 138
                 raise CommandAbortedError("Error: User already exist (use `user update` command instead)")
139
+            if parsed_args.send_email:
140
+                email_manager = get_email_manager()
141
+                email_manager.notify_created_account(
142
+                    user=user,
143
+                    password=parsed_args.password,
144
+                )
145
+
129 146
         else:
130 147
             if parsed_args.password:
131 148
                 self._update_password_for_login(login=parsed_args.login, password=parsed_args.password)

+ 12 - 0
tracim/tracim/config/app_cfg.py Прегледај датотеку

@@ -186,7 +186,19 @@ class CFG(object):
186 186
         self.EMAIL_NOTIFICATION_FROM = tg.config.get('email.notification.from')
187 187
         self.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML = tg.config.get('email.notification.content_update.template.html')
188 188
         self.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT = tg.config.get('email.notification.content_update.template.text')
189
+        self.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_HTML = tg.config.get(
190
+            'email.notification.created_account.template.html',
191
+            './tracim/templates/mail/created_account_body_html.mak',
192
+        )
193
+        self.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_TEXT = tg.config.get(
194
+            'email.notification.created_account.template.text',
195
+            './tracim/templates/mail/created_account_body_text.mak',
196
+        )
189 197
         self.EMAIL_NOTIFICATION_CONTENT_UPDATE_SUBJECT = tg.config.get('email.notification.content_update.subject')
198
+        self.EMAIL_NOTIFICATION_CREATED_ACCOUNT_SUBJECT = tg.config.get(
199
+            'email.notification.created_account.subject',
200
+            '[{website_title}] Created account',
201
+        )
190 202
         self.EMAIL_NOTIFICATION_PROCESSING_MODE = tg.config.get('email.notification.processing_mode')
191 203
 
192 204
 

+ 20 - 1
tracim/tracim/controllers/admin/user.py Прегледај датотеку

@@ -1,4 +1,5 @@
1 1
 # -*- coding: utf-8 -*-
2
+import uuid
2 3
 
3 4
 from tracim import model  as pm
4 5
 
@@ -23,6 +24,7 @@ from tracim.controllers.user import UserWorkspaceRestController
23 24
 from tracim.lib import CST
24 25
 from tracim.lib import helpers as h
25 26
 from tracim.lib.base import logger
27
+from tracim.lib.email import get_email_manager
26 28
 from tracim.lib.user import UserApi
27 29
 from tracim.lib.group import GroupApi
28 30
 from tracim.lib.user import UserStaticApi
@@ -291,9 +293,18 @@ class UserRestController(TIMRestController):
291 293
 
292 294
     @tg.require(predicates.in_group(Group.TIM_MANAGER_GROUPNAME))
293 295
     @tg.expose()
294
-    def post(self, name, email, password, is_tracim_manager='off', is_tracim_admin='off'):
296
+    def post(
297
+            self,
298
+            name: str,
299
+            email: str,
300
+            password: str,
301
+            is_tracim_manager: str='off',
302
+            is_tracim_admin: str='off',
303
+            send_email: str='off',
304
+    ):
295 305
         is_tracim_manager = h.on_off_to_boolean(is_tracim_manager)
296 306
         is_tracim_admin = h.on_off_to_boolean(is_tracim_admin)
307
+        send_email = h.on_off_to_boolean(send_email)
297 308
         current_user = tmpl_context.current_user
298 309
 
299 310
         if current_user.profile.id < Group.TIM_ADMIN:
@@ -313,6 +324,10 @@ class UserRestController(TIMRestController):
313 324
         user.display_name = name
314 325
         if password:
315 326
             user.password = password
327
+        elif send_email:
328
+            # Setup a random password to send email at user
329
+            password = str(uuid.uuid4())
330
+            user.password = password
316 331
         api.save(user)
317 332
 
318 333
         # Now add the user to related groups
@@ -325,6 +340,10 @@ class UserRestController(TIMRestController):
325 340
 
326 341
         api.save(user)
327 342
 
343
+        if send_email:
344
+            email_manager = get_email_manager()
345
+            email_manager.notify_created_account(user, password=password)
346
+
328 347
         tg.flash(_('User {} created.').format(user.get_display_name()), CST.STATUS_OK)
329 348
         tg.redirect(self.url())
330 349
 

+ 121 - 0
tracim/tracim/lib/email.py Прегледај датотеку

@@ -2,8 +2,14 @@
2 2
 
3 3
 from email.mime.multipart import MIMEMultipart
4 4
 import smtplib
5
+from email.mime.text import MIMEText
6
+
7
+from mako.template import Template
8
+from tgext.asyncjob import asyncjob_perform
9
+from tg.i18n import ugettext as _
5 10
 
6 11
 from tracim.lib.base import logger
12
+from tracim.model import User
7 13
 
8 14
 
9 15
 class SmtpConfiguration(object):
@@ -59,3 +65,118 @@ class EmailSender(object):
59 65
             self.connect() # Acutally, this connects to SMTP only if required
60 66
             logger.info(self, 'Sending email to {}'.format(message['To']))
61 67
             self._smtp_connection.send_message(message)
68
+
69
+
70
+class EmailManager(object):
71
+    def __init__(self, smtp_config: SmtpConfiguration, global_config):
72
+        self._smtp_config = smtp_config
73
+        self._global_config = global_config
74
+
75
+    def notify_created_account(
76
+            self,
77
+            user: User,
78
+            password: str,
79
+    ) -> None:
80
+        """
81
+        Send created account email to given user
82
+        :param password: choosed password
83
+        :param user: user to notify
84
+        """
85
+        # TODO BS 20160712: Cyclic import
86
+        from tracim.lib.notifications import EST
87
+
88
+        logger.debug(self, 'user: {}'.format(user.user_id))
89
+        logger.info(self, 'Sending asynchronous email to 1 user ({0})'.format(
90
+            user.email,
91
+        ))
92
+
93
+        async_email_sender = EmailSender(
94
+            self._smtp_config,
95
+            self._global_config.EMAIL_NOTIFICATION_ACTIVATED
96
+        )
97
+
98
+        subject = \
99
+            self._global_config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_SUBJECT \
100
+            .replace(
101
+                EST.WEBSITE_TITLE,
102
+                self._global_config.WEBSITE_TITLE.__str__()
103
+            )
104
+        message = MIMEMultipart('alternative')
105
+        message['Subject'] = subject
106
+        message['From'] = self._global_config.EMAIL_NOTIFICATION_FROM
107
+        message['To'] = user.email
108
+
109
+        text_template_file_path = self._global_config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_TEXT  # nopep8
110
+        html_template_file_path = self._global_config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_HTML  # nopep8
111
+
112
+        body_text = self._render(
113
+            mako_template_filepath=text_template_file_path,
114
+            context={
115
+                'user': user,
116
+                'password': password,
117
+                'login_url': self._global_config.WEBSITE_BASE_URL,
118
+            }
119
+        )
120
+
121
+        body_html = self._render(
122
+            mako_template_filepath=html_template_file_path,
123
+            context={
124
+                'user': user,
125
+                'password': password,
126
+                'login_url': self._global_config.WEBSITE_BASE_URL,
127
+            }
128
+        )
129
+
130
+        part1 = MIMEText(body_text, 'plain', 'utf-8')
131
+        part2 = MIMEText(body_html, 'html', 'utf-8')
132
+
133
+        # Attach parts into message container.
134
+        # According to RFC 2046, the last part of a multipart message,
135
+        # in this case the HTML message, is best and preferred.
136
+        message.attach(part1)
137
+        message.attach(part2)
138
+
139
+        asyncjob_perform(async_email_sender.send_mail, message)
140
+
141
+        # Note: The following action allow to close the SMTP connection.
142
+        # This will work only if the async jobs are done in the right order
143
+        asyncjob_perform(async_email_sender.disconnect)
144
+
145
+    def _render(self, mako_template_filepath: str, context: dict):
146
+        """
147
+        Render mako template with all needed current variables
148
+        :param mako_template_filepath: file path of mako template
149
+        :param context: dict with template context
150
+        :return: template rendered string
151
+        """
152
+        # TODO - D.A. - 2014-11-06 - move this
153
+        # Import is here for circular import problem
154
+        import tracim.lib.helpers as helpers
155
+        from tracim.config.app_cfg import CFG
156
+
157
+        template = Template(filename=mako_template_filepath)
158
+        return template.render(
159
+            base_url=self._global_config.WEBSITE_BASE_URL,
160
+            _=_,
161
+            h=helpers,
162
+            CFG=CFG.get_instance(),
163
+            **context
164
+        )
165
+
166
+
167
+def get_email_manager():
168
+    """
169
+    :return: EmailManager instance
170
+    """
171
+    #  TODO: Find a way to import properly without cyclic import
172
+    from tracim.config.app_cfg import CFG
173
+
174
+    global_config = CFG.get_instance()
175
+    smtp_config = SmtpConfiguration(
176
+        global_config.EMAIL_NOTIFICATION_SMTP_SERVER,
177
+        global_config.EMAIL_NOTIFICATION_SMTP_PORT,
178
+        global_config.EMAIL_NOTIFICATION_SMTP_USER,
179
+        global_config.EMAIL_NOTIFICATION_SMTP_PASSWORD
180
+    )
181
+
182
+    return EmailManager(global_config=global_config, smtp_config=smtp_config)

+ 5 - 0
tracim/tracim/templates/admin/user_getall.mak Прегледај датотеку

@@ -61,6 +61,11 @@
61 61
                                         <input type="checkbox" class="checkbox" disabled name="is_tracim_admin" id="is-tracim-admin"> ${_('This user is an administrator')}
62 62
                                       </label>
63 63
                                     </div>
64
+                                    <div class="checkbox">
65
+                                      <label>
66
+                                        <input type="checkbox" class="checkbox" checked name="send_email" id="send-email"> ${_('Send email to user')}
67
+                                      </label>
68
+                                    </div>
64 69
                                         
65 70
                                     <span class="pull-right" style="margin-top: 0.5em;">
66 71
                                         <button type="submit" class="btn btn-small btn-success" title="Add first comment"><i class=" fa fa-check"></i> ${_('Validate')}</button>

+ 88 - 0
tracim/tracim/templates/mail/created_account_body_html.mak Прегледај датотеку

@@ -0,0 +1,88 @@
1
+## -*- coding: utf-8 -*-
2
+<html>
3
+  <head>
4
+    <style>
5
+      a { color: #3465af;}
6
+      a.call-to-action {
7
+        background: #3465af;
8
+        padding: 3px 4px 5px 4px;
9
+        border: 1px solid #12438d;
10
+        font-weight: bold;
11
+        color: #FFF;
12
+        text-decoration: none;
13
+        margin-left: 5px;
14
+      }
15
+      a.call-to-action img { vertical-align: middle;}
16
+      th { vertical-align: top;}
17
+      
18
+      #content-intro-username { font-size: 1.5em; color: #666; font-weight: bold; }
19
+      #content-intro { margin: 0; border: 1em solid #DDD; border-width: 0 0 0 0em; padding: 1em 1em 1em 1em; }
20
+      #content-body { margin: 0em; border: 2em solid #DDD; border-width: 0 0 0 4em; padding: 0.5em 2em 1em 1em; }
21
+      #content-body-intro { font-size: 2em; color: #666; }
22
+      #content-body-only-title { font-size: 1.5em; }
23
+
24
+      #content-body ins { background-color: #AFA; }
25
+      #content-body del { background-color: #FAA; }
26
+
27
+
28
+      #call-to-action-button { background-color: #5CB85C; border: 1px solid #4CAE4C; color: #FFF; text-decoration: none; font-weight: bold; border-radius: 3px; font-size: 2em; padding: 4px 0.3em;}
29
+      #call-to-action-container { text-align: right; margin-top: 2em; }
30
+
31
+      #footer hr { border: 0px solid #CCC; border-top-width: 1px; width: 8em; max-width:25%; margin-left: 0;}
32
+      #footer { color: #999; margin: 4em auto auto 0.5em; }
33
+      #footer a { color: #999; }
34
+    </style>
35
+  </head>
36
+  <body style="font-family: Arial; font-size: 12px; max-width: 600px; margin: 0; padding: 0;">
37
+
38
+    <table style="width: 100%; cell-padding: 0; border-collapse: collapse; margin: 0">
39
+      <tr style="background-color: F5F5F5; border-bottom: 1px solid #CCC;" >
40
+        <td style="background-color: #666;">
41
+          <img src="${base_url+'/assets/img/logo.png'}" style="vertical-align: middle;" alt=""/>
42
+        </td>
43
+        <td style="padding: 0.5em; background-color: #666; text-align: left;">
44
+          <span style="font-size: 1.3em; color: #FFF; font-weight: bold;">
45
+
46
+            ${CFG.WEBSITE_TITLE}: ${_('Created account')}
47
+
48
+          </span>
49
+        </td>
50
+      </tr>
51
+    </table>
52
+
53
+    <div id="content-body">
54
+        <div>
55
+            ${_('An administrator just create account for you on {website_title}'.format(
56
+                website_title=CFG.WEBSITE_TITLE
57
+            ))}
58
+
59
+            <ul>
60
+                <li>
61
+                    <b>${_('Login')}</b>: ${user.email}
62
+                </li>
63
+                <li>
64
+                    <b>${_('Password')}</b>: ${password}
65
+                </li>
66
+            </ul>
67
+
68
+        </div>
69
+        <div id="call-to-action-container">
70
+
71
+            ${_('To go to {website_title}, please click on following link'.format(
72
+                website_title=CFG.WEBSITE_TITLE
73
+            ))}
74
+
75
+            <span style="">
76
+                <a href="${login_url}" id='call-to-action-button'>${login_url}</a>
77
+            </span>
78
+        </div>
79
+    </div>
80
+    
81
+    <div id="footer">
82
+        <p>
83
+            ${_('This email was sent by <i>Tracim</i>, a collaborative software developped by Algoo.')}<br/>
84
+            Algoo SAS &mdash; 9 rue du rocher de Lorzier, 38430 Moirans, France &mdash; <a style="text-decoration: none;" href="http://algoo.fr">www.algoo.fr</a>
85
+        </p>
86
+    </div>
87
+  </body>
88
+</html>

+ 25 - 0
tracim/tracim/templates/mail/created_account_body_text.mak Прегледај датотеку

@@ -0,0 +1,25 @@
1
+## -*- coding: utf-8 -*-
2
+
3
+${_('An administrator just create account for you on {website_title}'.format(
4
+    website_title=CFG.WEBSITE_TITLE
5
+))}
6
+
7
+* ${_('Login')}: ${user.email}
8
+* ${_('Password')}: ${password}
9
+
10
+${_('To go to {website_title}, please click on following link'.format(
11
+    website_title=CFG.WEBSITE_TITLE
12
+))}
13
+
14
+${login_url}
15
+
16
+--------------------------------------------------------------------------------
17
+
18
+This email was sent by *Tracim*,
19
+a collaborative software developped by Algoo.
20
+
21
+**Algoo SAS**
22
+9 rue du rocher de Lorzier
23
+38430 Moirans
24
+France
25
+http://algoo.fr