浏览代码

Merge branch 'master' of github.com:tracim/tracim

Bastien Sevajol (Algoo) 8 年前
父节点
当前提交
7eabe5d67c

+ 8 - 4
tracim/development.ini.base 查看文件

169
 # The following base_url is used for links and icons
169
 # The following base_url is used for links and icons
170
 # integrated in the email notifcations
170
 # integrated in the email notifcations
171
 website.base_url = http://127.0.0.1:8080
171
 website.base_url = http://127.0.0.1:8080
172
+# If config not provided, it will be extracted from website.base_url
173
+website.server_name = 127.0.0.1
172
     
174
     
173
 email.notification.activated = False
175
 email.notification.activated = False
174
 email.notification.from = Tracim Notification <noreply@trac.im>
176
 email.notification.from = Tracim Notification <noreply@trac.im>
175
 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
176
 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
177
 # 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
178
 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
179
 # processing_mode may be sync or async
184
 # processing_mode may be sync or async
180
 email.notification.processing_mode = sync
185
 email.notification.processing_mode = sync
181
 email.notification.smtp.server = your_smtp_server
186
 email.notification.smtp.server = your_smtp_server
188
 # radicale.server.port = 5232
193
 # radicale.server.port = 5232
189
 # radicale.server.ssl = false
194
 # radicale.server.ssl = false
190
 # radicale.server.filesystem.folder = ~/.config/radicale/collections
195
 # radicale.server.filesystem.folder = ~/.config/radicale/collections
191
-## If '', current host will be used
192
-# radicale.client.host = ''
193
-# radicale.client.port = 5232
194
-# radicale.client.ssl = false
196
+## url can be extended like http://127.0.0.1:5232/calendar
197
+## in this case, you have to create your own proxy behind this url.
198
+# radicale.client.base_url = http://127.0.0.1:5232
195
 
199
 
196
 #####
200
 #####
197
 #
201
 #

+ 17 - 0
tracim/tracim/command/user.py 查看文件

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)

+ 39 - 5
tracim/tracim/config/app_cfg.py 查看文件

12
     setting = asbool(global_conf.get('the_setting'))
12
     setting = asbool(global_conf.get('the_setting'))
13
  
13
  
14
 """
14
 """
15
+from urllib.parse import urlparse
15
 
16
 
16
 import tg
17
 import tg
17
 from paste.deploy.converters import asbool
18
 from paste.deploy.converters import asbool
177
         self.WEBSITE_HOME_IMAGE_URL = tg.lurl('/assets/img/home_illustration.jpg')
178
         self.WEBSITE_HOME_IMAGE_URL = tg.lurl('/assets/img/home_illustration.jpg')
178
         self.WEBSITE_HOME_BACKGROUND_IMAGE_URL = tg.lurl('/assets/img/bg.jpg')
179
         self.WEBSITE_HOME_BACKGROUND_IMAGE_URL = tg.lurl('/assets/img/bg.jpg')
179
         self.WEBSITE_BASE_URL = tg.config.get('website.base_url', '')
180
         self.WEBSITE_BASE_URL = tg.config.get('website.base_url', '')
181
+        self.WEBSITE_SERVER_NAME = tg.config.get('website.server_name', None)
182
+
183
+        if not self.WEBSITE_SERVER_NAME:
184
+            self.WEBSITE_SERVER_NAME = urlparse(self.WEBSITE_BASE_URL).hostname
185
+            logger.warning(
186
+                self,
187
+                'NOTE: Generated website.server_name parameter from '
188
+                'website.base_url parameter -> {0}'
189
+                .format(self.WEBSITE_SERVER_NAME)
190
+            )
180
 
191
 
181
         self.WEBSITE_HOME_TAG_LINE = tg.config.get('website.home.tag_line', '')
192
         self.WEBSITE_HOME_TAG_LINE = tg.config.get('website.home.tag_line', '')
182
         self.WEBSITE_SUBTITLE = tg.config.get('website.home.subtitle', '')
193
         self.WEBSITE_SUBTITLE = tg.config.get('website.home.subtitle', '')
186
         self.EMAIL_NOTIFICATION_FROM = tg.config.get('email.notification.from')
197
         self.EMAIL_NOTIFICATION_FROM = tg.config.get('email.notification.from')
187
         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')
188
         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
+        )
189
         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
+        )
190
         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')
191
 
214
 
192
 
215
 
227
             '~/.config/radicale/collections'
250
             '~/.config/radicale/collections'
228
         )
251
         )
229
 
252
 
230
-        # If None, current host will be used
231
-        self.RADICALE_CLIENT_HOST = tg.config.get('radicale.client.host', None)
232
-        self.RADICALE_CLIENT_PORT = tg.config.get('radicale.client.port', 5232)
233
-        self.RADICALE_CLIENT_SSL = asbool(tg.config.get('radicale.client.ssl', False))
234
-
253
+        self.RADICALE_CLIENT_BASE_URL_TEMPLATE = \
254
+            tg.config.get('radicale.client.base_url', None)
255
+
256
+        if not self.RADICALE_CLIENT_BASE_URL_TEMPLATE:
257
+            self.RADICALE_CLIENT_BASE_URL_TEMPLATE = \
258
+                'http://{0}:{1}'.format(
259
+                    self.WEBSITE_SERVER_NAME,
260
+                    self.RADICALE_SERVER_PORT,
261
+                )
262
+            logger.warning(
263
+                self,
264
+                'NOTE: Generated radicale.client.base_url parameter with '
265
+                'followings parameters: website.server_name, '
266
+                'radicale.server.port -> {0}'
267
+                .format(self.RADICALE_CLIENT_BASE_URL_TEMPLATE)
268
+            )
235
 
269
 
236
     def get_tracker_js_content(self, js_tracker_file_path = None):
270
     def get_tracker_js_content(self, js_tracker_file_path = None):
237
         js_tracker_file_path = tg.config.get('js_tracker_path', None)
271
         js_tracker_file_path = tg.config.get('js_tracker_path', None)

+ 20 - 1
tracim/tracim/controllers/admin/user.py 查看文件

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
 

+ 16 - 36
tracim/tracim/lib/calendar.py 查看文件

1
+import os
2
+
1
 import re
3
 import re
2
 import transaction
4
 import transaction
3
 
5
 
24
 CALENDAR_TYPE_USER = UserCalendar
26
 CALENDAR_TYPE_USER = UserCalendar
25
 CALENDAR_TYPE_WORKSPACE = WorkspaceCalendar
27
 CALENDAR_TYPE_WORKSPACE = WorkspaceCalendar
26
 
28
 
27
-CALENDAR_BASE_URL_TEMPLATE = '{proto}://{domain}:{port}'
28
-CALENDAR_USER_URL_TEMPLATE = \
29
-    CALENDAR_BASE_URL_TEMPLATE + '/user/{id}.ics{extra}/'
30
-CALENDAR_WORKSPACE_URL_TEMPLATE = \
31
-    CALENDAR_BASE_URL_TEMPLATE + '/workspace/{id}.ics{extra}/'
29
+CALENDAR_USER_URL_TEMPLATE = 'user/{id}.ics/'
30
+CALENDAR_WORKSPACE_URL_TEMPLATE = 'workspace/{id}.ics/'
32
 
31
 
33
 
32
 
34
 class CalendarManager(object):
33
 class CalendarManager(object):
35
-    @staticmethod
36
-    def get_base_url():
37
-        from tracim.config.app_cfg import CFG
38
-        cfg = CFG.get_instance()
39
-
40
-        return CALENDAR_BASE_URL_TEMPLATE.format(
41
-            proto='https' if cfg.RADICALE_CLIENT_SSL else 'http',
42
-            domain=cfg.RADICALE_CLIENT_HOST or '127.0.0.1',
43
-            port=str(cfg.RADICALE_CLIENT_PORT)
44
-        )
45
-
46
-    @staticmethod
47
-    def get_user_calendar_url(user_id: int, extra: str=''):
34
+    @classmethod
35
+    def get_base_url(cls):
48
         from tracim.config.app_cfg import CFG
36
         from tracim.config.app_cfg import CFG
49
         cfg = CFG.get_instance()
37
         cfg = CFG.get_instance()
38
+        return cfg.RADICALE_CLIENT_BASE_URL_TEMPLATE
50
 
39
 
51
-        return CALENDAR_USER_URL_TEMPLATE.format(
52
-            proto='https' if cfg.RADICALE_CLIENT_SSL else 'http',
53
-            domain=cfg.RADICALE_CLIENT_HOST or '127.0.0.1',
54
-            port=str(cfg.RADICALE_CLIENT_PORT),
55
-            id=str(user_id),
56
-            extra=extra,
57
-        )
58
-
59
-    @staticmethod
60
-    def get_workspace_calendar_url(workspace_id: int, extra: str=''):
61
-        from tracim.config.app_cfg import CFG
62
-        cfg = CFG.get_instance()
40
+    @classmethod
41
+    def get_user_calendar_url(cls, user_id: int):
42
+        user_path = CALENDAR_USER_URL_TEMPLATE.format(id=str(user_id))
43
+        return os.path.join(cls.get_base_url(), user_path)
63
 
44
 
64
-        return CALENDAR_WORKSPACE_URL_TEMPLATE.format(
65
-            proto='https' if cfg.RADICALE_CLIENT_SSL else 'http',
66
-            domain=cfg.RADICALE_CLIENT_HOST or '127.0.0.1',
67
-            port=str(cfg.RADICALE_CLIENT_PORT),
68
-            id=str(workspace_id),
69
-            extra=extra,
45
+    @classmethod
46
+    def get_workspace_calendar_url(cls, workspace_id: int):
47
+        workspace_path = CALENDAR_WORKSPACE_URL_TEMPLATE.format(
48
+            id=str(workspace_id)
70
         )
49
         )
50
+        return os.path.join(cls.get_base_url(), workspace_path)
71
 
51
 
72
     def __init__(self, user: User):
52
     def __init__(self, user: User):
73
         self._user = user
53
         self._user = user

+ 121 - 0
tracim/tracim/lib/email.py 查看文件

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)

+ 4 - 12
tracim/tracim/model/auth.py 查看文件

151
     @property
151
     @property
152
     def calendar_url(self) -> str:
152
     def calendar_url(self) -> str:
153
         # TODO - 20160531 - Bastien: Cyclic import if import in top of file
153
         # TODO - 20160531 - Bastien: Cyclic import if import in top of file
154
-        from tracim.config.app_cfg import CFG
155
-        from tracim.lib.calendar import CALENDAR_USER_URL_TEMPLATE
156
-        cfg = CFG.get_instance()
157
-        return CALENDAR_USER_URL_TEMPLATE.format(
158
-            proto='https' if cfg.RADICALE_CLIENT_SSL else 'http',
159
-            domain=cfg.RADICALE_CLIENT_HOST or request.domain,
160
-            port=cfg.RADICALE_CLIENT_PORT,
161
-            id=self.user_id,
162
-            extra='#' + slugify(self.get_display_name(
163
-                remove_email_part=True
164
-            ), only_ascii=True)
165
-        )
154
+        from tracim.lib.calendar import CalendarManager
155
+        calendar_manager = CalendarManager(None)
156
+
157
+        return calendar_manager.get_user_calendar_url(self.user_id)
166
 
158
 
167
     @classmethod
159
     @classmethod
168
     def by_email_address(cls, email):
160
     def by_email_address(cls, email):

+ 4 - 10
tracim/tracim/model/data.py 查看文件

70
     @property
70
     @property
71
     def calendar_url(self) -> str:
71
     def calendar_url(self) -> str:
72
         # TODO - 20160531 - Bastien: Cyclic import if import in top of file
72
         # TODO - 20160531 - Bastien: Cyclic import if import in top of file
73
-        from tracim.config.app_cfg import CFG
74
-        from tracim.lib.calendar import CALENDAR_WORKSPACE_URL_TEMPLATE
75
-        cfg = CFG.get_instance()
76
-        return CALENDAR_WORKSPACE_URL_TEMPLATE.format(
77
-            proto='https' if cfg.RADICALE_CLIENT_SSL else 'http',
78
-            domain=cfg.RADICALE_CLIENT_HOST or tg.request.domain,
79
-            port=cfg.RADICALE_CLIENT_PORT,
80
-            id=self.workspace_id,
81
-            extra='#' + slugify(self.label),
82
-        )
73
+        from tracim.lib.calendar import CalendarManager
74
+        calendar_manager = CalendarManager(None)
75
+
76
+        return calendar_manager.get_workspace_calendar_url(self.workspace_id)
83
 
77
 
84
     def get_user_role(self, user: User) -> int:
78
     def get_user_role(self, user: User) -> int:
85
         for role in user.roles:
79
         for role in user.roles:

+ 5 - 0
tracim/tracim/templates/admin/user_getall.mak 查看文件

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 查看文件

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 查看文件

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