Browse Source

Merge pull request #70 from buxx/69/dev/email_at_account_creation

Tracim 8 years ago
parent
commit
732125139e

+ 3 - 0
tracim/development.ini.base View File

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

+ 17 - 0
tracim/tracim/command/user.py View File

5
 
5
 
6
 from tracim.command import AppContextCommand, Extender
6
 from tracim.command import AppContextCommand, Extender
7
 from tracim.lib.auth.ldap import LDAPAuth
7
 from tracim.lib.auth.ldap import LDAPAuth
8
+from tracim.lib.email import get_email_manager
8
 from tracim.lib.exception import AlreadyExistError, CommandAbortedError
9
 from tracim.lib.exception import AlreadyExistError, CommandAbortedError
9
 from tracim.lib.group import GroupApi
10
 from tracim.lib.group import GroupApi
10
 from tracim.lib.user import UserApi
11
 from tracim.lib.user import UserApi
68
             default=[],
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
         return parser
81
         return parser
72
 
82
 
73
     def _user_exist(self, login):
83
     def _user_exist(self, login):
126
                 user = self._create_user(login=parsed_args.login, password=parsed_args.password)
136
                 user = self._create_user(login=parsed_args.login, password=parsed_args.password)
127
             except AlreadyExistError:
137
             except AlreadyExistError:
128
                 raise CommandAbortedError("Error: User already exist (use `user update` command instead)")
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
         else:
146
         else:
130
             if parsed_args.password:
147
             if parsed_args.password:
131
                 self._update_password_for_login(login=parsed_args.login, password=parsed_args.password)
148
                 self._update_password_for_login(login=parsed_args.login, password=parsed_args.password)

+ 12 - 0
tracim/tracim/config/app_cfg.py View File

197
         self.EMAIL_NOTIFICATION_FROM = tg.config.get('email.notification.from')
197
         self.EMAIL_NOTIFICATION_FROM = tg.config.get('email.notification.from')
198
         self.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML = tg.config.get('email.notification.content_update.template.html')
198
         self.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML = tg.config.get('email.notification.content_update.template.html')
199
         self.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT = tg.config.get('email.notification.content_update.template.text')
199
         self.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT = tg.config.get('email.notification.content_update.template.text')
200
+        self.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_HTML = tg.config.get(
201
+            'email.notification.created_account.template.html',
202
+            './tracim/templates/mail/created_account_body_html.mak',
203
+        )
204
+        self.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_TEXT = tg.config.get(
205
+            'email.notification.created_account.template.text',
206
+            './tracim/templates/mail/created_account_body_text.mak',
207
+        )
200
         self.EMAIL_NOTIFICATION_CONTENT_UPDATE_SUBJECT = tg.config.get('email.notification.content_update.subject')
208
         self.EMAIL_NOTIFICATION_CONTENT_UPDATE_SUBJECT = tg.config.get('email.notification.content_update.subject')
209
+        self.EMAIL_NOTIFICATION_CREATED_ACCOUNT_SUBJECT = tg.config.get(
210
+            'email.notification.created_account.subject',
211
+            '[{website_title}] Created account',
212
+        )
201
         self.EMAIL_NOTIFICATION_PROCESSING_MODE = tg.config.get('email.notification.processing_mode')
213
         self.EMAIL_NOTIFICATION_PROCESSING_MODE = tg.config.get('email.notification.processing_mode')
202
 
214
 
203
 
215
 

+ 20 - 1
tracim/tracim/controllers/admin/user.py View File

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
+import uuid
2
 
3
 
3
 from tracim import model  as pm
4
 from tracim import model  as pm
4
 
5
 
23
 from tracim.lib import CST
24
 from tracim.lib import CST
24
 from tracim.lib import helpers as h
25
 from tracim.lib import helpers as h
25
 from tracim.lib.base import logger
26
 from tracim.lib.base import logger
27
+from tracim.lib.email import get_email_manager
26
 from tracim.lib.user import UserApi
28
 from tracim.lib.user import UserApi
27
 from tracim.lib.group import GroupApi
29
 from tracim.lib.group import GroupApi
28
 from tracim.lib.user import UserStaticApi
30
 from tracim.lib.user import UserStaticApi
291
 
293
 
292
     @tg.require(predicates.in_group(Group.TIM_MANAGER_GROUPNAME))
294
     @tg.require(predicates.in_group(Group.TIM_MANAGER_GROUPNAME))
293
     @tg.expose()
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
         is_tracim_manager = h.on_off_to_boolean(is_tracim_manager)
305
         is_tracim_manager = h.on_off_to_boolean(is_tracim_manager)
296
         is_tracim_admin = h.on_off_to_boolean(is_tracim_admin)
306
         is_tracim_admin = h.on_off_to_boolean(is_tracim_admin)
307
+        send_email = h.on_off_to_boolean(send_email)
297
         current_user = tmpl_context.current_user
308
         current_user = tmpl_context.current_user
298
 
309
 
299
         if current_user.profile.id < Group.TIM_ADMIN:
310
         if current_user.profile.id < Group.TIM_ADMIN:
313
         user.display_name = name
324
         user.display_name = name
314
         if password:
325
         if password:
315
             user.password = password
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
         api.save(user)
331
         api.save(user)
317
 
332
 
318
         # Now add the user to related groups
333
         # Now add the user to related groups
325
 
340
 
326
         api.save(user)
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
         tg.flash(_('User {} created.').format(user.get_display_name()), CST.STATUS_OK)
347
         tg.flash(_('User {} created.').format(user.get_display_name()), CST.STATUS_OK)
329
         tg.redirect(self.url())
348
         tg.redirect(self.url())
330
 
349
 

+ 121 - 0
tracim/tracim/lib/email.py View File

2
 
2
 
3
 from email.mime.multipart import MIMEMultipart
3
 from email.mime.multipart import MIMEMultipart
4
 import smtplib
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
 from tracim.lib.base import logger
11
 from tracim.lib.base import logger
12
+from tracim.model import User
7
 
13
 
8
 
14
 
9
 class SmtpConfiguration(object):
15
 class SmtpConfiguration(object):
59
             self.connect() # Acutally, this connects to SMTP only if required
65
             self.connect() # Acutally, this connects to SMTP only if required
60
             logger.info(self, 'Sending email to {}'.format(message['To']))
66
             logger.info(self, 'Sending email to {}'.format(message['To']))
61
             self._smtp_connection.send_message(message)
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 View File

61
                                         <input type="checkbox" class="checkbox" disabled name="is_tracim_admin" id="is-tracim-admin"> ${_('This user is an administrator')}
61
                                         <input type="checkbox" class="checkbox" disabled name="is_tracim_admin" id="is-tracim-admin"> ${_('This user is an administrator')}
62
                                       </label>
62
                                       </label>
63
                                     </div>
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
                                     <span class="pull-right" style="margin-top: 0.5em;">
70
                                     <span class="pull-right" style="margin-top: 0.5em;">
66
                                         <button type="submit" class="btn btn-small btn-success" title="Add first comment"><i class=" fa fa-check"></i> ${_('Validate')}</button>
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 View File

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 View File

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