Sfoglia il codice sorgente

- fixes search result filtering by allowed content

Damien ACCORSI 10 anni fa
parent
commit
b67ad8598a

+ 17 - 0
tracim/tracim/config/app_cfg.py Vedi File

@@ -25,6 +25,8 @@ from tg.i18n import lazy_ugettext as l_
25 25
 import tracim
26 26
 from tracim import model
27 27
 from tracim.lib.base import logger
28
+from tracim.model.data import ActionDescription
29
+from tracim.model.data import ContentType
28 30
 
29 31
 base_config = AppConfig()
30 32
 base_config.renderers = []
@@ -235,6 +237,21 @@ class CFG(object):
235 237
 
236 238
         self.WEBSITE_TREEVIEW_CONTENT = tg.config.get('website.treeview.content')
237 239
 
240
+        self.EMAIL_NOTIFICATION_NOTIFIED_EVENTS = [
241
+            ActionDescription.COMMENT,
242
+            ActionDescription.CREATION,
243
+            ActionDescription.EDITION,
244
+            ActionDescription.REVISION
245
+        ]
246
+
247
+        self.EMAIL_NOTIFICATION_NOTIFIED_CONTENTS = [
248
+            ContentType.Page,
249
+            ContentType.Thread,
250
+            ContentType.File,
251
+            ContentType.Comment,
252
+            # ContentType.Folder -- Folder is skipped
253
+        ]
254
+
238 255
 
239 256
     def get_tracker_js_content(self, js_tracker_file_path = None):
240 257
         js_tracker_file_path = tg.config.get('js_tracker_path', None)

+ 61 - 11
tracim/tracim/controllers/content.py Vedi File

@@ -267,18 +267,45 @@ class UserWorkspaceFolderFileRestController(TIMWorkspaceContentRestController):
267 267
         workspace = tmpl_context.workspace
268 268
 
269 269
         try:
270
+            item_saved = False
270 271
             api = ContentApi(tmpl_context.current_user)
271 272
             item = api.get_one(int(item_id), self._item_type, workspace)
272
-            if comment or label:
273
-                api.update_content(item, label if label else item.label, comment if comment else '')
274
-                action_description = ActionDescription.EDITION
275
-                # The action_description is overwritten by ActionDescription.REVISION if the file content is also updated
276 273
 
277
-            if isinstance(file_data, FieldStorage):
278
-                api.update_file_data(item, file_data.filename, file_data.type, file_data.file.read())
279
-                action_description = ActionDescription.REVISION
274
+            # TODO - D.A. - 2015-03-19
275
+            # refactor this method in order to make code easier to understand
280 276
 
281
-            api.save(item, action_description)
277
+            if comment and label:
278
+                updated_item = api.update_content(
279
+                    item, label if label else item.label,
280
+                    comment if comment else ''
281
+                )
282
+                api.save(updated_item, ActionDescription.EDITION)
283
+
284
+                # This case is the default "file title and description update"
285
+                # In this case the file itself is not revisionned
286
+
287
+            else:
288
+                # So, now we may have a comment and/or a file revision
289
+                if comment and ''==label:
290
+                    comment_item = api.create_comment(workspace,
291
+                                                      item, comment,
292
+                                                      do_save=False)
293
+
294
+                    if not isinstance(file_data, FieldStorage):
295
+                        api.save(comment_item, ActionDescription.COMMENT)
296
+                    else:
297
+                        # The notification is only sent
298
+                        # if the file is NOT updated
299
+                        #
300
+                        #  If the file is also updated,
301
+                        #  then a 'file revision' notification will be sent.
302
+                        api.save(comment_item,
303
+                                 ActionDescription.COMMENT,
304
+                                 do_notify=False)
305
+
306
+                if isinstance(file_data, FieldStorage):
307
+                    api.update_file_data(item, file_data.filename, file_data.type, file_data.file.read())
308
+                    api.save(item, ActionDescription.REVISION)
282 309
 
283 310
             msg = _('{} updated').format(self._item_type_label)
284 311
             tg.flash(msg, CST.STATUS_OK)
@@ -376,12 +403,34 @@ class UserWorkspaceFolderPageRestController(TIMWorkspaceContentRestController):
376 403
 
377 404
         page = api.create(ContentType.Page, workspace, tmpl_context.folder, label)
378 405
         page.description = content
379
-        api.save(page, ActionDescription.CREATION)
406
+        api.save(page, ActionDescription.CREATION, do_notify=True)
380 407
 
381 408
         tg.flash(_('Page created'), CST.STATUS_OK)
382 409
         tg.redirect(tg.url('/workspaces/{}/folders/{}/pages/{}').format(tmpl_context.workspace_id, tmpl_context.folder_id, page.content_id))
383 410
 
384 411
 
412
+    @tg.require(current_user_is_contributor())
413
+    @tg.expose()
414
+    def put(self, item_id, label='',content=''):
415
+        # INFO - D.A. This method is a raw copy of
416
+        # TODO - SECURE THIS
417
+        workspace = tmpl_context.workspace
418
+
419
+        try:
420
+            api = ContentApi(tmpl_context.current_user)
421
+            item = api.get_one(int(item_id), self._item_type, workspace)
422
+            api.update_content(item, label, content)
423
+            api.save(item, ActionDescription.REVISION)
424
+
425
+            msg = _('{} updated').format(self._item_type_label)
426
+            tg.flash(msg, CST.STATUS_OK)
427
+            tg.redirect(self._std_url.format(tmpl_context.workspace_id, tmpl_context.folder_id, item.content_id))
428
+
429
+        except ValueError as e:
430
+            msg = _('{} not updated - error: {}').format(self._item_type_label, str(e))
431
+            tg.flash(msg, CST.STATUS_ERROR)
432
+            tg.redirect(self._err_url.format(tmpl_context.workspace_id, tmpl_context.folder_id, item_id))
433
+
385 434
 
386 435
 class UserWorkspaceFolderThreadRestController(TIMWorkspaceContentRestController):
387 436
     """
@@ -452,12 +501,13 @@ class UserWorkspaceFolderThreadRestController(TIMWorkspaceContentRestController)
452 501
 
453 502
         thread = api.create(ContentType.Thread, workspace, tmpl_context.folder, label)
454 503
         # FIXME - DO NOT DUPLCIATE FIRST MESSAGE thread.description = content
455
-        api.save(thread, ActionDescription.CREATION)
504
+        api.save(thread, ActionDescription.CREATION, do_notify=False)
456 505
 
457 506
         comment = api.create(ContentType.Comment, workspace, thread, label)
458 507
         comment.label = ''
459 508
         comment.description = content
460
-        api.save(comment, ActionDescription.COMMENT)
509
+        api.save(comment, ActionDescription.COMMENT, do_notify=False)
510
+        api.do_notify(thread)
461 511
 
462 512
         tg.flash(_('Thread created'), CST.STATUS_OK)
463 513
         tg.redirect(self._std_url.format(tmpl_context.workspace_id, tmpl_context.folder_id, thread.content_id))

BIN
tracim/tracim/i18n/fr/LC_MESSAGES/tracim.mo Vedi File


+ 156 - 101
tracim/tracim/i18n/fr/LC_MESSAGES/tracim.po Vedi File

@@ -7,8 +7,8 @@ msgid ""
7 7
 msgstr ""
8 8
 "Project-Id-Version: pod 0.1\n"
9 9
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
10
-"POT-Creation-Date: 2015-03-10 18:35+0100\n"
11
-"PO-Revision-Date: 2015-03-10 18:32+0100\n"
10
+"POT-Creation-Date: 2015-03-20 00:44+0100\n"
11
+"PO-Revision-Date: 2015-03-20 00:37+0100\n"
12 12
 "Last-Translator: Damien Accorsi <damien.accorsi@free.fr>\n"
13 13
 "Language-Team: fr_FR <LL@li.org>\n"
14 14
 "Plural-Forms: nplurals=2; plural=(n > 1)\n"
@@ -17,11 +17,11 @@ msgstr ""
17 17
 "Content-Transfer-Encoding: 8bit\n"
18 18
 "Generated-By: Babel 1.3\n"
19 19
 
20
-#: tracim/config/app_cfg.py:135
20
+#: tracim/config/app_cfg.py:137
21 21
 msgid "Password reset request"
22 22
 msgstr "Réinitialisation du mot de passe"
23 23
 
24
-#: tracim/config/app_cfg.py:136 tracim/config/app_cfg.py:165
24
+#: tracim/config/app_cfg.py:138 tracim/config/app_cfg.py:167
25 25
 #, python-format
26 26
 msgid ""
27 27
 "\n"
@@ -44,48 +44,50 @@ msgstr ""
44 44
 " à l'originie de cette requête, merci d'ignorer et/ou supprimer cet "
45 45
 "e-mail.\n"
46 46
 
47
-#: tracim/config/app_cfg.py:152 tracim/templates/user_workspace_forms.mak:251
47
+#: tracim/config/app_cfg.py:154 tracim/templates/user_workspace_forms.mak:251
48 48
 #: tracim/templates/user_workspace_forms.mak:252
49 49
 msgid "New password"
50 50
 msgstr "Nouveau mot de passe"
51 51
 
52
-#: tracim/config/app_cfg.py:153
52
+#: tracim/config/app_cfg.py:155
53 53
 msgid "Confirm new password"
54 54
 msgstr "Confirmer le nouveau mot de passe"
55 55
 
56
-#: tracim/config/app_cfg.py:154
56
+#: tracim/config/app_cfg.py:156
57 57
 msgid "Save new password"
58 58
 msgstr "Enregistrer le nouveau mot de passe"
59 59
 
60
-#: tracim/config/app_cfg.py:155 tracim/templates/user_get_all.mak:34
60
+#: tracim/config/app_cfg.py:157 tracim/templates/user_get_all.mak:34
61 61
 msgid "Email address"
62 62
 msgstr "Adresse email"
63 63
 
64
-#: tracim/config/app_cfg.py:156
64
+#: tracim/config/app_cfg.py:158
65 65
 msgid "Send Request"
66 66
 msgstr "Valider"
67 67
 
68
-#: tracim/config/app_cfg.py:159
68
+#: tracim/config/app_cfg.py:161
69 69
 msgid "Password reset request sent"
70 70
 msgstr "Requête de réinitialisation du mot de passe envoyée"
71 71
 
72
-#: tracim/config/app_cfg.py:160 tracim/config/app_cfg.py:162
72
+#: tracim/config/app_cfg.py:162 tracim/config/app_cfg.py:164
73 73
 msgid "Invalid password reset request"
74 74
 msgstr "Requête de réinitialisation du mot de passe invalide"
75 75
 
76
-#: tracim/config/app_cfg.py:161
76
+#: tracim/config/app_cfg.py:163
77 77
 msgid "Password reset request timed out"
78 78
 msgstr "Echec de la requête de réinitialisation du mot de passe"
79 79
 
80
-#: tracim/config/app_cfg.py:163
80
+#: tracim/config/app_cfg.py:165
81 81
 msgid "Password changed successfully"
82 82
 msgstr "Mot de passe changé"
83 83
 
84
-#: tracim/controllers/__init__.py:247 tracim/controllers/content.py:283
84
+#: tracim/controllers/__init__.py:247 tracim/controllers/content.py:310
85
+#: tracim/controllers/content.py:425
85 86
 msgid "{} updated"
86 87
 msgstr "{} mis(e) à jour"
87 88
 
88
-#: tracim/controllers/__init__.py:252 tracim/controllers/content.py:288
89
+#: tracim/controllers/__init__.py:252 tracim/controllers/content.py:315
90
+#: tracim/controllers/content.py:430
89 91
 msgid "{} not updated - error: {}"
90 92
 msgstr "{} pas mis(e) à jour - erreur : {}"
91 93
 
@@ -97,41 +99,41 @@ msgstr "Statut de {} mis(e) à jour"
97 99
 msgid "{} status not updated: {}"
98 100
 msgstr "Statut de {} non mis(e) à jour : {}"
99 101
 
100
-#: tracim/controllers/__init__.py:302 tracim/controllers/content.py:748
102
+#: tracim/controllers/__init__.py:302 tracim/controllers/content.py:798
101 103
 msgid "{} archived. <a class=\"alert-link\" href=\"{}\">Cancel action</a>"
102 104
 msgstr "{} archivé(e). <a class=\"alert-link\" href=\"{}\">Annuler l'opération</a>"
103 105
 
104
-#: tracim/controllers/__init__.py:311 tracim/controllers/content.py:757
106
+#: tracim/controllers/__init__.py:311 tracim/controllers/content.py:807
105 107
 msgid "{} not archived: {}"
106 108
 msgstr "{} non archivé(e) : {}"
107 109
 
108
-#: tracim/controllers/__init__.py:325 tracim/controllers/content.py:771
110
+#: tracim/controllers/__init__.py:325 tracim/controllers/content.py:821
109 111
 msgid "{} unarchived."
110 112
 msgstr "{} désarchivé(e)"
111 113
 
112
-#: tracim/controllers/__init__.py:333 tracim/controllers/content.py:779
114
+#: tracim/controllers/__init__.py:333 tracim/controllers/content.py:829
113 115
 msgid "{} not un-archived: {}"
114 116
 msgstr "{} non désarchivé(e) : {}"
115 117
 
116 118
 #: tracim/controllers/__init__.py:351 tracim/controllers/content.py:88
117
-#: tracim/controllers/content.py:796
119
+#: tracim/controllers/content.py:846
118 120
 msgid "{} deleted. <a class=\"alert-link\" href=\"{}\">Cancel action</a>"
119 121
 msgstr ""
120 122
 "{} supprimé(e). <a class=\"alert-link\" href=\"{}\">Annuler "
121 123
 "l'opération</a>"
122 124
 
123 125
 #: tracim/controllers/__init__.py:360 tracim/controllers/content.py:99
124
-#: tracim/controllers/content.py:805
126
+#: tracim/controllers/content.py:855
125 127
 msgid "{} not deleted: {}"
126 128
 msgstr "{} non supprimé(e) : {}"
127 129
 
128 130
 #: tracim/controllers/__init__.py:375 tracim/controllers/content.py:116
129
-#: tracim/controllers/content.py:820
131
+#: tracim/controllers/content.py:870
130 132
 msgid "{} undeleted."
131 133
 msgstr "{} restauré(e)."
132 134
 
133 135
 #: tracim/controllers/__init__.py:385 tracim/controllers/content.py:128
134
-#: tracim/controllers/content.py:830
136
+#: tracim/controllers/content.py:880
135 137
 msgid "{} not un-deleted: {}"
136 138
 msgstr "{} non restauré(e) : {}"
137 139
 
@@ -152,47 +154,47 @@ msgstr "Fichier"
152 154
 msgid "File created"
153 155
 msgstr "Fichier créé"
154 156
 
155
-#: tracim/controllers/content.py:313
157
+#: tracim/controllers/content.py:340
156 158
 msgid "Page"
157 159
 msgstr "Page"
158 160
 
159
-#: tracim/controllers/content.py:381
161
+#: tracim/controllers/content.py:408
160 162
 msgid "Page created"
161 163
 msgstr "Page créée"
162 164
 
163
-#: tracim/controllers/content.py:421
165
+#: tracim/controllers/content.py:470
164 166
 msgid "Thread"
165 167
 msgstr "Discussion"
166 168
 
167
-#: tracim/controllers/content.py:462
169
+#: tracim/controllers/content.py:512
168 170
 msgid "Thread created"
169 171
 msgstr "Discussion créée"
170 172
 
171
-#: tracim/controllers/content.py:547
173
+#: tracim/controllers/content.py:597
172 174
 msgid "Item moved to {}"
173 175
 msgstr "Element déplacé vers {}"
174 176
 
175
-#: tracim/controllers/content.py:549
177
+#: tracim/controllers/content.py:599
176 178
 msgid "Item moved to workspace root"
177 179
 msgstr "Element déplacé à la racine de l'espace de travail"
178 180
 
179
-#: tracim/controllers/content.py:669
181
+#: tracim/controllers/content.py:719
180 182
 msgid "Folder created"
181 183
 msgstr "Dossier créé"
182 184
 
183
-#: tracim/controllers/content.py:675
185
+#: tracim/controllers/content.py:725
184 186
 msgid "Folder not created: {}"
185 187
 msgstr "Dossier non créé : {}"
186 188
 
187
-#: tracim/controllers/content.py:710
189
+#: tracim/controllers/content.py:760
188 190
 msgid "Folder updated"
189 191
 msgstr "Dossier mis à jour"
190 192
 
191
-#: tracim/controllers/content.py:715
193
+#: tracim/controllers/content.py:765
192 194
 msgid "Folder not updated: {}"
193 195
 msgstr "Dossier non mis à jour : {}"
194 196
 
195
-#: tracim/controllers/content.py:730
197
+#: tracim/controllers/content.py:780
196 198
 msgid "Folder"
197 199
 msgstr "Dossiers"
198 200
 
@@ -340,7 +342,7 @@ msgstr "Espace de travail {} restauré."
340 342
 msgid "%B %d at %I:%M%p"
341 343
 msgstr "le %d %B à %H:%M"
342 344
 
343
-#: tracim/lib/content.py:98 tracim/templates/dashboard.mak:36
345
+#: tracim/lib/content.py:100 tracim/templates/dashboard.mak:36
344 346
 #: tracim/templates/master_authenticated.mak:94
345 347
 #: tracim/templates/master_no_toolbar_no_login.mak:112
346 348
 #: tracim/templates/search.mak:11 tracim/templates/user_get_one.mak:39
@@ -355,6 +357,67 @@ msgstr "le %d %B à %H:%M"
355 357
 msgid "Workspaces"
356 358
 msgstr "Espaces de travail"
357 359
 
360
+#: tracim/lib/notifications.py:289
361
+msgid "<span id=\"content-intro-username\">{}</span> added a comment:"
362
+msgstr "<span id=\"content-intro-username\">{}</span> a ajouté un commentaire :"
363
+
364
+#: tracim/lib/notifications.py:291 tracim/lib/notifications.py:300
365
+msgid "Answer"
366
+msgstr "Répondre"
367
+
368
+#: tracim/lib/notifications.py:297 tracim/lib/notifications.py:321
369
+#: tracim/lib/notifications.py:347
370
+msgid "View online"
371
+msgstr "Voir en ligne"
372
+
373
+#: tracim/lib/notifications.py:301
374
+msgid "<span id=\"content-intro-username\">{}</span> started a thread entitled:"
375
+msgstr ""
376
+"<span id=\"content-intro-username\">{}</span> a lancé une discussion "
377
+"intitulée :"
378
+
379
+#: tracim/lib/notifications.py:306
380
+msgid "<span id=\"content-intro-username\">{}</span> added a file entitled:"
381
+msgstr ""
382
+"<span id=\"content-intro-username\">{}</span> a ajouté un fichier "
383
+"intitulé :"
384
+
385
+#: tracim/lib/notifications.py:316
386
+msgid "<span id=\"content-intro-username\">{}</span> added a page entitled:"
387
+msgstr ""
388
+"<span id=\"content-intro-username\">{}</span> a ajouté une page intitulée"
389
+" :"
390
+
391
+#: tracim/lib/notifications.py:324
392
+msgid "<span id=\"content-intro-username\">{}</span> uploaded a new revision."
393
+msgstr ""
394
+"<span id=\"content-intro-username\">{}</span> a téléchargé une nouvelle "
395
+"version."
396
+
397
+#: tracim/lib/notifications.py:328
398
+msgid "<span id=\"content-intro-username\">{}</span> updated this page."
399
+msgstr "<span id=\"content-intro-username\">{}</span> a mis à jour cette page."
400
+
401
+#: tracim/lib/notifications.py:333
402
+msgid "<p id=\"content-body-intro\">Here is an overview of the changes:</p>"
403
+msgstr "<p id=\"content-body-intro\">Voici un aperçu des modifications :</p>"
404
+
405
+#: tracim/lib/notifications.py:338
406
+msgid ""
407
+"<span id=\"content-intro-username\">{}</span> updated the thread "
408
+"description."
409
+msgstr ""
410
+"<span id=\"content-intro-username\">{}</span> a mis à jour la description"
411
+" de la discussion."
412
+
413
+#: tracim/lib/notifications.py:350
414
+msgid ""
415
+"<span id=\"content-intro-username\">{}</span> updated the file "
416
+"description."
417
+msgstr ""
418
+"<span id=\"content-intro-username\">{}</span> a mis à jour la description"
419
+" du fichier."
420
+
358 421
 #: tracim/lib/predicates.py:17
359 422
 msgid "You are not authorized to access this resource"
360 423
 msgstr "Vous n'êtes pas autorisé à accéder à cette ressource"
@@ -442,55 +505,55 @@ msgstr "Elément désarchivé"
442 505
 msgid "Item undeleted"
443 506
 msgstr "Elément restauré"
444 507
 
445
-#: tracim/model/data.py:202 tracim/model/data.py:212
508
+#: tracim/model/data.py:201 tracim/model/data.py:211
446 509
 msgid "work in progress"
447 510
 msgstr "travail en cours"
448 511
 
449
-#: tracim/model/data.py:203 tracim/model/data.py:213
512
+#: tracim/model/data.py:202 tracim/model/data.py:212
450 513
 msgid "closed — validated"
451 514
 msgstr "clos(e) — validé(e)"
452 515
 
453
-#: tracim/model/data.py:204 tracim/model/data.py:214
516
+#: tracim/model/data.py:203 tracim/model/data.py:213
454 517
 msgid "closed — cancelled"
455 518
 msgstr "clos(e) — annulé(e)"
456 519
 
457
-#: tracim/model/data.py:205 tracim/model/data.py:210 tracim/model/data.py:215
520
+#: tracim/model/data.py:204 tracim/model/data.py:209 tracim/model/data.py:214
458 521
 msgid "deprecated"
459 522
 msgstr "obsolète"
460 523
 
461
-#: tracim/model/data.py:207
524
+#: tracim/model/data.py:206
462 525
 msgid "subject in progress"
463 526
 msgstr "discussion en cours"
464 527
 
465
-#: tracim/model/data.py:208
528
+#: tracim/model/data.py:207
466 529
 msgid "subject closed — resolved"
467 530
 msgstr "discussion close — résolue"
468 531
 
469
-#: tracim/model/data.py:209
532
+#: tracim/model/data.py:208
470 533
 msgid "subject closed — cancelled"
471 534
 msgstr "discussion close — annulée"
472 535
 
473
-#: tracim/model/data.py:292
536
+#: tracim/model/data.py:291
474 537
 msgid "Delete this workspace"
475 538
 msgstr "Supprimer l'espace de travail"
476 539
 
477
-#: tracim/model/data.py:293
540
+#: tracim/model/data.py:292
478 541
 msgid "Delete this folder"
479 542
 msgstr "Supprimer ce dossier"
480 543
 
481
-#: tracim/model/data.py:294
544
+#: tracim/model/data.py:293
482 545
 msgid "Delete this file"
483 546
 msgstr "Supprimer le fichier"
484 547
 
485
-#: tracim/model/data.py:295
548
+#: tracim/model/data.py:294
486 549
 msgid "Delete this page"
487 550
 msgstr "Supprimer cette page"
488 551
 
489
-#: tracim/model/data.py:296
552
+#: tracim/model/data.py:295
490 553
 msgid "Delete this thread"
491 554
 msgstr "Supprimer cette discussion"
492 555
 
493
-#: tracim/model/data.py:297
556
+#: tracim/model/data.py:296
494 557
 msgid "Delete this comment"
495 558
 msgstr "Supprimer ce commentaire"
496 559
 
@@ -1483,58 +1546,24 @@ msgstr ""
1483 1546
 "Comme <span style=\"color: #ea983d;\">gestionnaire de contenu</span> + "
1484 1547
 "modification de l'espace de travail."
1485 1548
 
1486
-#: tracim/templates/mail/content_update_body_html.mak:52
1487
-msgid "Some activity has been detected"
1488
-msgstr "Il se passe des choses"
1489
-
1490
-#: tracim/templates/mail/content_update_body_html.mak:60
1491
-msgid "<span style=\"{style}\">&mdash; by {actor_name}</span>"
1492
-msgstr "<span style=\"{style}\">&mdash; par {actor_name}</span>"
1493
-
1494
-#: tracim/templates/mail/content_update_body_html.mak:65
1495
-#: tracim/templates/mail/content_update_body_text.mak:16
1496
-msgid "This item has been deleted."
1497
-msgstr "Cet élément a été supprimé"
1498
-
1499
-#: tracim/templates/mail/content_update_body_html.mak:68
1500
-#: tracim/templates/mail/content_update_body_text.mak:18
1501
-msgid "This item has been archived."
1502
-msgstr "Cet élément a été archivé."
1503
-
1504
-#: tracim/templates/mail/content_update_body_html.mak:72
1505
-msgid "Go to information"
1506
-msgstr "Voir en ligne"
1507
-
1508
-#: tracim/templates/mail/content_update_body_html.mak:82
1549
+#: tracim/templates/mail/content_update_body_html.mak:66
1509 1550
 msgid ""
1510
-"{user_display_name}, you receive this email because you are "
1511
-"<b>{user_role_label}</b> in the workspace <a "
1512
-"href=\"{workspace_url}\">{workspace_label}</a>"
1551
+"{user_display_name}, you receive this email because you are registered on"
1552
+" <i>{website_title}</i> and you are <i>{user_role_label}</i> in the "
1553
+"workspace <a href=\"{workspace_url}\">{workspace_label}</a>."
1513 1554
 msgstr ""
1514
-"{user_display_name}, vous recevez cet email car vous êtes "
1515
-"<b>{user_role_label}</b> dans l'espace de travail <a "
1516
-"href=\"{workspace_url}\">{workspace_label}</a>"
1555
+"{user_display_name}, vous recevez cet email car vous êtes enregistré sur "
1556
+"le site web <i>{website_title}</i> et vous êtes <i>{user_role_label}</i> "
1557
+"dans l'espace de travail <a "
1558
+"href=\"{workspace_url}\">{workspace_label}</a>."
1517 1559
 
1518
-#: tracim/templates/mail/content_update_body_text.mak:8
1519
-msgid "Some activity has been detected on the item above"
1520
-msgstr "Il se passe des choses"
1521
-
1522
-#: tracim/templates/mail/content_update_body_text.mak:8
1523
-msgid "-- by {actor_name}"
1524
-msgstr "-- par {actor_name}"
1525
-
1526
-#: tracim/templates/mail/content_update_body_text.mak:20
1527
-msgid "Go to information:"
1528
-msgstr "Voir en ligne :"
1529
-
1530
-#: tracim/templates/mail/content_update_body_text.mak:25
1560
+#: tracim/templates/mail/content_update_body_html.mak:70
1531 1561
 msgid ""
1532
-"*{user_display_name}*, you receive this email because you are "
1533
-"*{user_role_label}* in the workspace {workspace_label} - {workspace_url}"
1562
+"This email was automatically sent by <i>Tracim</i>, a collaborative "
1563
+"software developped by Algoo."
1534 1564
 msgstr ""
1535
-"*{user_display_name}*, vous recevez cet email car vous êtes "
1536
-"*{user_role_label}* dans l'espace de travail {workspace_label} - "
1537
-"{workspace_url}"
1565
+"Cet email a été envoyé automatiquement par <i>Tracim</i>, un logiciel "
1566
+"collaboratif développé par Algoo."
1538 1567
 
1539 1568
 #~ msgid "You have no document yet."
1540 1569
 #~ msgstr "Vous n'avez pas de document pour le moment."
@@ -2094,12 +2123,38 @@ msgstr ""
2094 2123
 #~ msgid "Create a topic"
2095 2124
 #~ msgstr "Créer un sujet"
2096 2125
 
2126
+#~ msgid "Some activity has been detected"
2127
+#~ msgstr "Il se passe des choses"
2128
+
2129
+#~ msgid "<span style=\"{style}\">&mdash; by {actor_name}</span>"
2130
+#~ msgstr "<span style=\"{style}\">&mdash; par {actor_name}</span>"
2131
+
2132
+#~ msgid "Go to information"
2133
+#~ msgstr "Voir en ligne"
2134
+
2135
+#~ msgid "Some activity has been detected on the item above"
2136
+#~ msgstr "Il se passe des choses"
2137
+
2138
+#~ msgid "-- by {actor_name}"
2139
+#~ msgstr "-- par {actor_name}"
2140
+
2141
+#~ msgid "This item has been deleted."
2142
+#~ msgstr "Cet élément a été supprimé"
2143
+
2144
+#~ msgid "This item has been archived."
2145
+#~ msgstr "Cet élément a été archivé."
2146
+
2147
+#~ msgid "Go to information:"
2148
+#~ msgstr "Voir en ligne :"
2149
+
2097 2150
 #~ msgid ""
2098
-#~ "This user has no password. Visit "
2099
-#~ "<a href=\"{}\">Reset password</a> page for "
2100
-#~ "init."
2151
+#~ "*{user_display_name}*, you receive this email"
2152
+#~ " because you are *{user_role_label}* in "
2153
+#~ "the workspace {workspace_label} - "
2154
+#~ "{workspace_url}"
2101 2155
 #~ msgstr ""
2102
-#~ "Cet utilisateur n'a pas de mot de"
2103
-#~ " passe. Visitez <a href=\"{}\">cette "
2104
-#~ "page</a> pour l'initialiser."
2156
+#~ "*{user_display_name}*, vous recevez cet email"
2157
+#~ " car vous êtes *{user_role_label}* dans "
2158
+#~ "l'espace de travail {workspace_label} - "
2159
+#~ "{workspace_url}"
2105 2160
 

+ 31 - 7
tracim/tracim/lib/content.py Vedi File

@@ -25,8 +25,10 @@ from tracim.model.data import Content
25 25
 from tracim.model.data import ContentType
26 26
 from tracim.model.data import NodeTreeItem
27 27
 from tracim.model.data import Workspace
28
+from tracim.model.data import UserRoleInWorkspace
28 29
 
29
-def compare_content_for_sorting_by_type_and_name(content1: Content, content2: Content):
30
+def compare_content_for_sorting_by_type_and_name(content1: Content,
31
+                                                 content2: Content):
30 32
     """
31 33
     :param content1:
32 34
     :param content2:
@@ -131,6 +133,12 @@ class ContentApi(object):
131 133
         if workspace:
132 134
             result = result.filter(Content.workspace_id==workspace.workspace_id)
133 135
 
136
+        if self._user:
137
+            # Filter according to user workspaces
138
+            workspace_ids = [r.workspace_id for r in self._user.roles \
139
+                             if r.role>=UserRoleInWorkspace.READER]
140
+            result = result.filter(Content.workspace_id.in_(workspace_ids))
141
+
134 142
         return result
135 143
 
136 144
     def _base_query(self, workspace: Workspace=None):
@@ -178,7 +186,8 @@ class ContentApi(object):
178 186
         :param workspace:
179 187
         :param filter_by_allowed_content_types:
180 188
         :param removed_item_ids:
181
-        :param allowed_node_types:
189
+        :param allowed_node_types: This parameter allow to hide folders for which the given type of content is not allowed.
190
+               For example, if you want to move a Page from a folder to another, you should show only folders that accept pages
182 191
         :return:
183 192
         """
184 193
         if not allowed_node_types:
@@ -193,10 +202,17 @@ class ContentApi(object):
193 202
             filter(Content.content_id.notin_(removed_item_ids)).\
194 203
             all()
195 204
 
196
-        if not filter_by_allowed_content_types or len(filter_by_allowed_content_types)<=0:
197
-            filter_by_allowed_content_types = ContentType.allowed_types_for_folding()
198
-
199
-        # Now, the case is to filter folders by the content that they are allowed to contain
205
+        if not filter_by_allowed_content_types or \
206
+                        len(filter_by_allowed_content_types)<=0:
207
+            # Standard case for the left treeview: we want to show all contents
208
+            # in the left treeview... so we still filter because for example
209
+            # comments must not appear in the treeview
210
+            return [folder for folder in folders \
211
+                    if folder.type in ContentType.allowed_types_for_folding()]
212
+
213
+        # Now this is a case of Folders only (used for moving content)
214
+        # When moving a content, you must get only folders that allow to be filled
215
+        # with the type of content you want to move
200 216
         result = []
201 217
         for folder in folders:
202 218
             for allowed_content_type in filter_by_allowed_content_types:
@@ -379,8 +395,16 @@ class ContentApi(object):
379 395
             DBSession.flush()
380 396
 
381 397
         if do_notify:
382
-            NotifierFactory.create(self._user).notify_content_update(content)
398
+            self.do_notify(content)
383 399
 
400
+    def do_notify(self, content: Content):
401
+        """
402
+        Allow to force notification for a given content. By default, it is
403
+        called during the .save() operation
404
+        :param content:
405
+        :return:
406
+        """
407
+        NotifierFactory.create(self._user).notify_content_update(content)
384 408
 
385 409
     def get_keywords(self, search_string, search_string_separators=None) -> [str]:
386 410
         """

+ 151 - 9
tracim/tracim/lib/notifications.py Vedi File

@@ -3,6 +3,8 @@
3 3
 from email.mime.multipart import MIMEMultipart
4 4
 from email.mime.text import MIMEText
5 5
 
6
+import lxml
7
+from lxml.html.diff import htmldiff
6 8
 
7 9
 from mako.template import Template
8 10
 
@@ -21,7 +23,8 @@ from tracim.model.serializers import Context
21 23
 from tracim.model.serializers import CTX
22 24
 from tracim.model.serializers import DictLikeClass
23 25
 
24
-from tracim.model.data import Content, UserRoleInWorkspace, ContentType
26
+from tracim.model.data import Content, UserRoleInWorkspace, ContentType, \
27
+    ActionDescription
25 28
 from tracim.model.auth import User
26 29
 
27 30
 
@@ -75,14 +78,59 @@ class RealNotifier(object):
75 78
                                        cfg.EMAIL_NOTIFICATION_SMTP_PASSWORD)
76 79
 
77 80
     def notify_content_update(self, content: Content):
78
-        logger.info(self, 'About to email-notify update of content {} by user {}'.format(content.content_id, self._user.user_id if self._user else 0)) # 0 means "no user"
79
-
80 81
         global_config = CFG.get_instance()
81 82
 
83
+        if content.get_last_action().id not \
84
+                in global_config.EMAIL_NOTIFICATION_NOTIFIED_EVENTS:
85
+            logger.info(
86
+                self,
87
+                'Skip email notification for update of content {}'
88
+                'by user {} (the action is {})'.format(
89
+                    content.content_id,
90
+                    # below: 0 means "no user"
91
+                    self._user.user_id if self._user else 0,
92
+                    content.get_last_action().id
93
+                )
94
+            )
95
+            return
96
+
97
+        logger.info(self,
98
+                    'About to email-notify update'
99
+                    'of content {} by user {}'.format(
100
+                        content.content_id,
101
+                        # Below: 0 means "no user"
102
+                        self._user.user_id if self._user else 0
103
+                    )
104
+        )
105
+
106
+        if content.type not \
107
+                in global_config.EMAIL_NOTIFICATION_NOTIFIED_CONTENTS:
108
+            logger.info(
109
+                self,
110
+                'Skip email notification for update of content {}'
111
+                'by user {} (the content type is {})'.format(
112
+                    content.type,
113
+                    # below: 0 means "no user"
114
+                    self._user.user_id if self._user else 0,
115
+                    content.get_last_action().id
116
+                )
117
+            )
118
+            return
119
+
120
+        logger.info(self,
121
+                    'About to email-notify update'
122
+                    'of content {} by user {}'.format(
123
+                        content.content_id,
124
+                        # Below: 0 means "no user"
125
+                        self._user.user_id if self._user else 0
126
+                    )
127
+        )
128
+
82 129
         ####
83 130
         #
84
-        # INFO - D.A. - 2014-11-05 - Emails are sent through asynchronous jobs. For that reason, we do not
85
-        # give SQLAlchemy objects but ids only (SQLA objects are related to a given thread/session)
131
+        # INFO - D.A. - 2014-11-05 - Emails are sent through asynchronous jobs.
132
+        # For that reason, we do not give SQLAlchemy objects but ids only
133
+        # (SQLA objects are related to a given thread/session)
86 134
         #
87 135
         try:
88 136
             if global_config.EMAIL_NOTIFICATION_PROCESSING_MODE.lower()==global_config.CST.ASYNC.lower():
@@ -146,7 +194,7 @@ class EmailNotifier(object):
146 194
         logger.debug(self, 'Content: {}'.format(event_content_id))
147 195
 
148 196
         content = ContentApi(user, show_archived=True, show_deleted=True).get_one(event_content_id, ContentType.Any) # TODO - use a system user instead of the user that has triggered the event
149
-        content = content.parent if content.type==ContentType.Comment else content
197
+        main_content = content.parent if content.type==ContentType.Comment else content
150 198
         notifiable_roles = WorkspaceApi(user).get_notifiable_roles(content.workspace)
151 199
 
152 200
         if len(notifiable_roles)<=0:
@@ -175,9 +223,9 @@ class EmailNotifier(object):
175 223
             #
176 224
             subject = self._global_config.EMAIL_NOTIFICATION_CONTENT_UPDATE_SUBJECT
177 225
             subject = subject.replace(EST.WEBSITE_TITLE, self._global_config.WEBSITE_TITLE.__str__())
178
-            subject = subject.replace(EST.WORKSPACE_LABEL, content.workspace.label.__str__())
179
-            subject = subject.replace(EST.CONTENT_LABEL, content.label.__str__() if content.label else content.file_name.__str__())
180
-            subject = subject.replace(EST.CONTENT_STATUS_LABEL, content.get_status().label.__str__())
226
+            subject = subject.replace(EST.WORKSPACE_LABEL, main_content.workspace.label.__str__())
227
+            subject = subject.replace(EST.CONTENT_LABEL, main_content.label.__str__() if main_content.label else main_content.file_name.__str__())
228
+            subject = subject.replace(EST.CONTENT_STATUS_LABEL, main_content.get_status().label.__str__())
181 229
 
182 230
             message = MIMEMultipart('alternative')
183 231
             message['Subject'] = subject
@@ -185,7 +233,11 @@ class EmailNotifier(object):
185 233
             message['To'] = to_addr
186 234
 
187 235
             body_text = self._build_email_body(self._global_config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT, role, content, user)
236
+
237
+
238
+
188 239
             body_html = self._build_email_body(self._global_config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML, role, content, user)
240
+
189 241
             part1 = MIMEText(body_text, 'plain', 'utf-8')
190 242
             part2 = MIMEText(body_html, 'html', 'utf-8')
191 243
             # Attach parts into message container.
@@ -223,12 +275,102 @@ class EmailNotifier(object):
223 275
         dictified_item = Context(CTX.EMAIL_NOTIFICATION, self._global_config.WEBSITE_BASE_URL).toDict(content)
224 276
         dictified_actor = Context(CTX.DEFAULT).toDict(actor)
225 277
 
278
+        main_title = dictified_item.label
279
+        content_intro = ''
280
+        content_text = ''
281
+        call_to_action_text = ''
282
+
283
+        action = content.get_last_action().id
284
+        if ActionDescription.COMMENT == action:
285
+            content_intro = _('<span id="content-intro-username">{}</span> added a comment:'.format(actor.display_name))
286
+            content_text = content.description
287
+            call_to_action_text = _('Answer')
288
+
289
+        elif ActionDescription.CREATION == action:
290
+
291
+            # Default values (if not overriden)
292
+            content_text = content.description
293
+            call_to_action_text = _('View online')
294
+
295
+            if ContentType.Thread == content.type:
296
+                call_to_action_text = _('Answer')
297
+                content_intro = _('<span id="content-intro-username">{}</span> started a thread entitled:').format(actor.display_name)
298
+                content_text = '<p id="content-body-intro">{}</p>'.format(content.label) + \
299
+                               content.get_last_comment_from(actor).description
300
+
301
+            elif ContentType.File == content.type:
302
+                content_intro = _('<span id="content-intro-username">{}</span> added a file entitled:').format(actor.display_name)
303
+                if content.description:
304
+                    content_text = content.description
305
+                elif content.label:
306
+                    content_text = '<span id="content-body-only-title">{}</span>'.format(content.label)
307
+                else:
308
+                    content_text = '<span id="content-body-only-title">{}</span>'.format(content.file_name)
309
+
310
+
311
+            elif ContentType.Page == content.type:
312
+                content_intro = _('<span id="content-intro-username">{}</span> added a page entitled:').format(actor.display_name)
313
+                content_text = '<span id="content-body-only-title">{}</span>'.format(content.label)
314
+
315
+        elif ActionDescription.REVISION == action:
316
+            content_text = content.description
317
+            call_to_action_text = _('View online')
318
+
319
+            if ContentType.File == content.type:
320
+                content_intro = _('<span id="content-intro-username">{}</span> uploaded a new revision.').format(actor.display_name)
321
+                content_text = ''
322
+
323
+            elif ContentType.Page == content.type:
324
+                content_intro = _('<span id="content-intro-username">{}</span> updated this page.').format(actor.display_name)
325
+                previous_revision = content.get_previous_revision()
326
+                title_diff = ''
327
+                if previous_revision.label != content.label:
328
+                    title_diff = htmldiff(previous_revision.label, content.label)
329
+                content_text = _('<p id="content-body-intro">Here is an overview of the changes:</p>')+ \
330
+                    title_diff + \
331
+                    htmldiff(previous_revision.description, content.description)
332
+
333
+            elif ContentType.Thread == content.type:
334
+                content_intro = _('<span id="content-intro-username">{}</span> updated the thread description.').format(actor.display_name)
335
+
336
+            # elif ContentType.Thread == content.type:
337
+            #     content_intro = _('<span id="content-intro-username">{}</span> updated this page.').format(actor.display_name)
338
+            #     previous_revision = content.get_previous_revision()
339
+            #     content_text = _('<p id="content-body-intro">Here is an overview of the changes:</p>')+ \
340
+            #         htmldiff(previous_revision.description, content.description)
341
+
342
+        elif ActionDescription.EDITION == action:
343
+            call_to_action_text = _('View online')
344
+
345
+            if ContentType.File == content.type:
346
+                content_intro = _('<span id="content-intro-username">{}</span> updated the file description.').format(actor.display_name)
347
+                content_text = '<p id="content-body-intro">{}</p>'.format(content.get_label()) + \
348
+                    content.description
349
+
350
+
351
+        if '' == content_intro and content_text == '':
352
+            # Skip notification, but it's not normal
353
+            logger.error(
354
+                self, 'A notification is being sent but no content. '
355
+                      'Here are some debug informations: [content_id: {cid}]'
356
+                      '[action: {act}][author: {actor}]'.format(
357
+                    cid=content.content_id, act=action, actor=actor
358
+                )
359
+            )
360
+            raise ValueError('Unexpected empty notification')
361
+
362
+        # Thread - create
363
+        # logger.debug(self, 'This is a NOT comment <--------------------- {}'.format(content.type))
226 364
         body_content = template.render(base_url=self._global_config.WEBSITE_BASE_URL,
227 365
                                _=_,
228 366
                                h=helpers,
229 367
                                user_display_name=role.user.display_name,
230 368
                                user_role_label=role.role_as_label(),
231 369
                                workspace_label=role.workspace.label,
370
+                               content_intro=content_intro,
371
+                               content_text=content_text,
372
+                               main_title=main_title,
373
+                               call_to_action_text=call_to_action_text,
232 374
                                result = DictLikeClass(item=dictified_item, actor=dictified_actor))
233 375
 
234 376
         return body_content

+ 27 - 1
tracim/tracim/model/data.py Vedi File

@@ -165,7 +165,6 @@ class ActionDescription(object):
165 165
         'undeletion': l_('Item undeleted'),
166 166
     }
167 167
 
168
-
169 168
     def __init__(self, id):
170 169
         assert id in ActionDescription.allowed_values()
171 170
         self.id = id
@@ -448,6 +447,33 @@ class Content(DeclarativeBase):
448 447
                 children.append(child)
449 448
         return children
450 449
 
450
+    def get_last_comment_from(self, user: User) -> 'Content':
451
+        # TODO - Make this more efficient
452
+        last_comment_updated = None
453
+        last_comment = None
454
+        for comment in self.get_comments():
455
+            if user.user_id==comment.owner.user_id:
456
+                if not last_comment or last_comment_updated<comment.updated:
457
+                    # take only the latest comment !
458
+                    last_comment = comment
459
+                    last_comment_updated = comment.updated
460
+
461
+        return last_comment
462
+
463
+
464
+    def get_previous_revision(self) -> 'ContentRevisionRO':
465
+        rev_ids = [revision.revision_id for revision in self.revisions]
466
+        rev_ids.sort()
467
+
468
+        if len(rev_ids)>=2:
469
+            revision_rev_id = rev_ids[-2]
470
+
471
+            for revision in self.revisions:
472
+                if revision.revision_id == revision_rev_id:
473
+                    return revision
474
+
475
+        return None
476
+
451 477
 
452 478
 
453 479
 class ContentChecker(object):

+ 8 - 2
tracim/tracim/model/serializers.py Vedi File

@@ -259,7 +259,10 @@ def serialize_breadcrumb_item(content: Content, context: Context):
259 259
 
260 260
 @pod_serializer(Content, CTX.EMAIL_NOTIFICATION)
261 261
 def serialize_item(content: Content, context: Context):
262
-    return DictLikeClass(
262
+    if ContentType.Comment==content.type:
263
+        content = content.parent
264
+
265
+    result = DictLikeClass(
263 266
         id = content.content_id,
264 267
         label = content.label if content.label else content.file_name,
265 268
         icon = ContentType.icon(content.type),
@@ -268,9 +271,12 @@ def serialize_item(content: Content, context: Context):
268 271
         workspace = context.toDict(content.workspace),
269 272
         is_deleted = content.is_deleted,
270 273
         is_archived = content.is_archived,
271
-        url = context.url('/workspaces/{wid}/folders/{fid}/{ctype}/{cid}'.format(wid = content.workspace_id, fid=content.parent_id, ctype=content.type+'s', cid=content.content_id))
274
+        url = context.url('/workspaces/{wid}/folders/{fid}/{ctype}/{cid}'.format(wid = content.workspace_id, fid=content.parent_id, ctype=content.type+'s', cid=content.content_id)),
275
+        last_action = context.toDict(content.get_last_action())
272 276
     )
273 277
 
278
+    return result
279
+
274 280
 
275 281
 @pod_serializer(Content, CTX.MENU_API)
276 282
 def serialize_content_for_menu_api(content: Content, context: Context):

+ 45 - 55
tracim/tracim/templates/mail/content_update_body_html.mak Vedi File

@@ -14,72 +14,62 @@
14 14
       }
15 15
       a.call-to-action img { vertical-align: middle;}
16 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; padding: 4px; border-radius: 3px; font-size: 2em; padding-right: 0.5em;}
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; }
17 34
     </style>
18 35
   </head>
19
-  <body style="font-family: Arial; font-size: 12px; width: 600px; margin: 0; padding: 0;">
36
+  <body style="font-family: Arial; font-size: 12px; max-width: 600px; margin: 0; padding: 0;">
20 37
 
21
-    <table style="width: 600px; cell-padding: 0; 	border-collapse: collapse;">
38
+    <table style="width: 100%; cell-padding: 0; border-collapse: collapse; margin: 0">
22 39
       <tr style="background-color: F5F5F5; border-bottom: 1px solid #CCC;" >
23
-        <td>
24
-          <img src="${base_url+'/assets/img/logo.png'}" style="vertical-align: middle;"/>
25
-          <span style="font-weight: bold; padding-left: 0.5em; font-size: 1.5em; vertical-align: middle;">${h.CFG.WEBSITE_TITLE}</span>
26
-        </td>
27
-      </tr>
28
-      <tr>
29
-        <td style="padding: 5px 5px 5px 2em;">
30
-          <img style="vertical-align: middle;" src="${base_url+'/assets/icons/32x32/places/folder-remote.png'}"/>
31
-          <span style="font-weight: bold; padding-left: 0.5em; font-size: 1.2em; vertical-align: middle;">${result.item.workspace.label}</span>
40
+        <td style="background-color: #666;">
41
+          <img src="http://team.trac.im/assets/img/logo.png" style="vertical-align: middle;"/>
32 42
         </td>
33
-      </tr>
34
-      <tr>
35
-        <td style="padding: 5px 5px 5px 4em;">
36
-          <img style="vertical-align: middle;" src="${base_url+'/assets/icons/32x32/'+result.item.icon+'.png'}"/>
37
-          <span style="font-weight: bold; padding-left: 0.5em; font-size: 1em; vertical-align: middle;">
38
-            ${result.item.label} &mdash;
39
-            <span style="font-weight: bold; color: #999; font-weight: bold;">
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
+            ${main_title}
46
+            &mdash;&nbsp;<span style="font-weight: bold; color: #999; font-weight: bold;">
40 47
                 ${result.item.status.label}
41
-                <img src="${base_url+'/assets/icons/16x16/{}.png'.format(result.item.status.icon)}" style="vertical-align: middle;">
48
+                <img alt="" src="${base_url+'/assets/icons/16x16/{}.png'.format(result.item.status.icon)}" style="vertical-align: middle;">
42 49
             </span>
43
-          </span>
44 50
         </td>
45 51
       </tr>
46 52
     </table>
47 53
 
48
-    <hr style="border: 0px solid #CCC; border-width: 1px 0 0 0;">
49
-
50
-    <div style="margin-left: 0.5em; border: 1em solid #DDD; border-width: 0 0 0 1em; padding-left: 1em;">
51
-      <p>
52
-        ${_('Some activity has been detected')|n}
53
-        ##
54
-        ## TODO - D.A. - Show last action in the notification message
55
-        ##
56
-        ## &mdash;
57
-        ## <img style="vertical-align: middle; " src="${base_url+'/assets/icons/16x16/'+result.item.last_action.icon+'.png'}"/>
58
-        ## ${result.item.last_action.label}
59
-        ##
60
-        ${_('<span style="{style}">&mdash; by {actor_name}</span>').format(style='color: #666; font-weight: bold;', actor_name=result.actor.name)}
61
-      </p>
62
-      <p>
63
-        % if result.item.is_deleted:
64
-            <img style="vertical-align: middle; " src="${base_url+'/assets/icons/16x16/status/user-trash-full.png'}"/>
65
-            ${_('This item has been deleted.')}
66
-        % elif result.item.is_archived:
67
-            <img style="vertical-align: middle; " src="${base_url+'/assets/icons/16x16/mimetypes/package-x-generic.png'}"/>
68
-            ${_('This item has been archived.')}
69
-        % else:
70
-            <a href="${result.item.url}" style="background-color: #5CB85C; border: 1px solid #4CAE4C; color: #FFF; text-decoration: none; font-weight: bold; padding: 4px; border-radius: 3px;">
71
-              <img style="vertical-align: middle; " src="${base_url+'/assets/icons/16x16/actions/system-search.png'}"/>
72
-              ${_('Go to information')}
73
-            </a>
74
-        % endif
75
-        <div style="clear:both;"></div>
76
-      </p>
54
+    <p id="content-intro">${content_intro|n}</p>
55
+    <div id="content-body">
56
+        <div>${content_text|n}</div>
57
+        <div id="call-to-action-container">
58
+            <span style="">
59
+                <a href="${result.item.url}" id='call-to-action-button'>${call_to_action_text}</a>
60
+            </span>
61
+        </div>
62
+    </div>
63
+    
64
+    <div id="footer">
65
+        <p>
66
+            ${_('{user_display_name}, you receive this email because you are registered on <i>{website_title}</i> and you are <i>{user_role_label}</i> in the workspace <a href="{workspace_url}">{workspace_label}</a>.').format(user_display_name=user_display_name, user_role_label=user_role_label, workspace_url=result.item.workspace.url, workspace_label=workspace_label, website_title=h.CFG.WEBSITE_TITLE)|n}
67
+        </p>
68
+        <hr/>
69
+        <p>
70
+            ${_('This email was automatically sent by <i>Tracim</i>, a collaborative software developped by Algoo.')}<br/>
71
+            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>
72
+        </p>
77 73
     </div>
78
-
79
-    <hr style="border: 0px solid #CCC; border-width: 1px 0 0 0;">
80
-
81
-    <p style="color: #999; margin-left: 0.5em;">
82
-      ${_('{user_display_name}, you receive this email because you are <b>{user_role_label}</b> in the workspace <a href="{workspace_url}">{workspace_label}</a>').format(user_display_name=user_display_name, user_role_label=user_role_label, workspace_url=result.item.workspace.url, workspace_label=workspace_label)|n}
83
-    </p>
84 74
   </body>
85 75
 </html>

+ 30 - 24
tracim/tracim/templates/mail/content_update_body_text.mak Vedi File

@@ -1,26 +1,32 @@
1 1
 ## -*- coding: utf-8 -*-
2
-${h.CFG.WEBSITE_TITLE}
3
--> ${result.item.workspace.label}
4
--> ${result.item.label} - ${result.item.status.label}
5
-
6
-==============================================================================
7
- 
8
-${_('Some activity has been detected on the item above')} ${_('-- by {actor_name}').format(actor_name=result.actor.name)}
9
-##
10
-## TODO - D.A. - Show last action in the notification message
11
-##
12
-##${_('{last_action} -- by {actor_name}').format(last_action=result.item.last_action.label, actor_name=result.actor.name)}
13
-##
14
-
15
-% if result.item.is_deleted:
16
-${_('This item has been deleted.')}
17
-% elif result.item.is_archived:
18
-${_('This item has been archived.')}
19
-% else:
20
-${_('Go to information:')} ${result.item.url}
21
-% endif
22
-
23
-==============================================================================
24
-
25
-${_('*{user_display_name}*, you receive this email because you are *{user_role_label}* in the workspace {workspace_label} - {workspace_url}').format(user_display_name=user_display_name, user_role_label=user_role_label, workspace_url=result.item.workspace.url, workspace_label=workspace_label)}
2
+
3
+Dear ${user_display_name},
4
+
5
+This email is intended to be read as HTML content.
6
+Please configure your email client to get the best of Tracim notifications.
7
+
8
+We understand that Email was originally intended to carry raw text only.
9
+And you probably understand on your own that we are a decades after email
10
+was created ;)
11
+
12
+Hope you'll switch your mail client configuration and enjoy Tracim :)
13
+
14
+
15
+--------------------------------------------------------------------------------
16
+
17
+
18
+You receive this email because you are registered on /${h.CFG.WEBSITE_TITLE}/
19
+and you are /${user_role_label}/ in the workspace /${workspace_label}/
20
+${result.item.workspace.url}
21
+
22
+----
23
+
24
+This email was automatically sent by *Tracim*,
25
+a collaborative software developped by Algoo.
26
+
27
+**Algoo SAS**
28
+9 rue du rocher de Lorzier
29
+38430 Moirans
30
+France
31
+http://algoo.fr
26 32