Browse Source

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

Damien Accorsi 6 years ago
parent
commit
93c05a3366

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

@@ -33,12 +33,13 @@ cookie_secret = 3283411b-1904-4554-b0e1-883863b53080
33 33
 use = egg:tracim
34 34
 full_stack = true
35 35
 # You can set french as default language by uncommenting next line
36
-# lang = fr
36
+# i18n.lang = fr
37 37
 
38 38
 cache_dir = %(here)s/data
39 39
 # preview generator cache directory
40 40
 preview_cache_dir = /tmp/tracim/preview/
41
-# file depot storage directory
41
+# file depot storage
42
+depot_storage_name = tracim
42 43
 depot_storage_dir = %(here)s/depot/
43 44
 
44 45
 beaker.session.key = tracim

+ 19 - 7
tracim/migration/versions/913efdf409e5_all_files_also_on_disk.py View File

@@ -8,12 +8,14 @@ Create Date: 2017-07-12 15:44:20.568447
8 8
 
9 9
 import shutil
10 10
 
11
+from alembic import context
11 12
 from alembic import op
12 13
 from depot.fields.sqlalchemy import UploadedFileField
13 14
 from depot.fields.upload import UploadedFile
14 15
 from depot.io.utils import FileIntent
15 16
 from depot.manager import DepotManager
16 17
 import sqlalchemy as sa
18
+from sqlalchemy.sql.expression import func
17 19
 
18 20
 # revision identifiers, used by Alembic.
19 21
 revision = '913efdf409e5'
@@ -33,6 +35,17 @@ revision_helper = sa.Table(
33 35
 )
34 36
 
35 37
 
38
+def configure_depot():
39
+    """Configure Depot."""
40
+    depot_storage_name = context.config.get_main_option('depot_storage_name')
41
+    depot_storage_path = context.config.get_main_option('depot_storage_dir')
42
+    depot_storage_settings = {'depot.storage_path': depot_storage_path}
43
+    DepotManager.configure(
44
+        depot_storage_name,
45
+        depot_storage_settings,
46
+    )
47
+
48
+
36 49
 def delete_files_on_disk(connection: sa.engine.Connection):
37 50
     """Deletes files from disk and their references in database."""
38 51
     delete_query = revision_helper.update() \
@@ -40,7 +53,8 @@ def delete_files_on_disk(connection: sa.engine.Connection):
40 53
         .where(revision_helper.c.depot_file.isnot(None)) \
41 54
         .values(depot_file=None)
42 55
     connection.execute(delete_query)
43
-    shutil.rmtree('depot/', ignore_errors=True)
56
+    depot_storage_path = context.config.get_main_option('depot_storage_dir')
57
+    shutil.rmtree(depot_storage_path, ignore_errors=True)
44 58
 
45 59
 
46 60
 def upgrade():
@@ -54,14 +68,13 @@ def upgrade():
54 68
     - create all files on disk from database.
55 69
     """
56 70
     # Creates files depot used in this migration
57
-    DepotManager.configure(
58
-        'tracim', {'depot.storage_path': 'depot/'},
59
-    )
71
+    configure_depot()
60 72
     connection = op.get_bind()
61 73
     delete_files_on_disk(connection=connection)
62 74
     select_query = revision_helper.select() \
63 75
         .where(revision_helper.c.type == 'file') \
64
-        .where(revision_helper.c.depot_file.is_(None))
76
+        .where(revision_helper.c.depot_file.is_(None)) \
77
+        .where(func.length(revision_helper.c.file_content) > 0)
65 78
     files = connection.execute(select_query).fetchall()
66 79
     for file in files:
67 80
         file_filename = '{0}{1}'.format(
@@ -76,8 +89,7 @@ def upgrade():
76 89
         depot_file_field = UploadedFile(depot_file_intent, 'tracim')
77 90
         update_query = revision_helper.update() \
78 91
             .where(revision_helper.c.revision_id == file.revision_id) \
79
-            .values(depot_file=depot_file_field) \
80
-            .return_defaults()
92
+            .values(depot_file=depot_file_field)
81 93
         connection.execute(update_query)
82 94
 
83 95
 

+ 9 - 1
tracim/tracim/config/app_cfg.py View File

@@ -129,7 +129,7 @@ def start_daemons(manager: DaemonsManager):
129 129
 
130 130
 def configure_depot():
131 131
     """Configure Depot."""
132
-    depot_storage_name = 'tracim'
132
+    depot_storage_name = CFG.get_instance().DEPOT_STORAGE_NAME
133 133
     depot_storage_path = CFG.get_instance().DEPOT_STORAGE_DIR
134 134
     depot_storage_settings = {'depot.storage_path': depot_storage_path}
135 135
     DepotManager.configure(
@@ -219,6 +219,14 @@ class CFG(object):
219 219
                 'ERROR: depot_storage_dir configuration is mandatory. '
220 220
                 'Set it before continuing.'
221 221
             )
222
+        self.DEPOT_STORAGE_NAME = tg.config.get(
223
+            'depot_storage_name',
224
+        )
225
+        if not self.DEPOT_STORAGE_NAME:
226
+            raise Exception(
227
+                'ERROR: depot_storage_name configuration is mandatory. '
228
+                'Set it before continuing.'
229
+            )
222 230
         self.PREVIEW_CACHE_DIR = tg.config.get(
223 231
             'preview_cache_dir',
224 232
         )

+ 0 - 11
tracim/tracim/controllers/admin/user.py View File

@@ -456,14 +456,3 @@ class UserRestController(TIMRestController):
456 456
         if next_url=='user':
457 457
             tg.redirect(self.url(id=user.user_id))
458 458
         tg.redirect(self.url())
459
-
460
-
461
-    @tg.require(predicates.in_group(Group.TIM_USER_GROUPNAME))
462
-    @tg.expose('tracim.templates.user_profile')
463
-    def me(self):
464
-        current_user = tmpl_context.current_user
465
-
466
-        current_user_content = Context(CTX.CURRENT_USER).toDict(current_user)
467
-        fake_api = Context(CTX.ADMIN_WORKSPACE).toDict({'current_user': current_user_content})
468
-
469
-        return DictLikeClass(fake_api=fake_api)

+ 10 - 7
tracim/tracim/controllers/content.py View File

@@ -355,6 +355,7 @@ class UserWorkspaceFolderFileRestController(TIMWorkspaceContentRestController):
355 355
                                  file_data.file.read())
356 356
             # Display error page to user if chosen label is in conflict
357 357
             if not self._path_validation.validate_new_content(file):
358
+                DBSession.rollback()
358 359
                 return render_invalid_integrity_chosen_path(
359 360
                     file.get_label_as_file(),
360 361
                 )
@@ -567,6 +568,7 @@ class UserWorkspaceFolderPageRestController(TIMWorkspaceContentRestController):
567 568
             page.description = content
568 569
 
569 570
             if not self._path_validation.validate_new_content(page):
571
+                DBSession.rollback()
570 572
                 return render_invalid_integrity_chosen_path(
571 573
                     page.get_label(),
572 574
                 )
@@ -683,19 +685,19 @@ class UserWorkspaceFolderThreadRestController(TIMWorkspaceContentRestController)
683 685
                                 workspace,
684 686
                                 tmpl_context.folder,
685 687
                                 label)
686
-            # FIXME - DO NOT DUPLCIATE FIRST MESSAGE
687
-            # thread.description = content
688
-            api.save(thread, ActionDescription.CREATION, do_notify=False)
689
-
690
-            comment = api.create(ContentType.Comment, workspace, thread, label)
691
-            comment.label = ''
692
-            comment.description = content
693 688
 
694 689
             if not self._path_validation.validate_new_content(thread):
690
+                DBSession.rollback()
695 691
                 return render_invalid_integrity_chosen_path(
696 692
                     thread.get_label(),
697 693
                 )
698 694
 
695
+        # FIXME - DO NOT DUPLICATE FIRST MESSAGE
696
+        # thread.description = content
697
+        api.save(thread, ActionDescription.CREATION, do_notify=False)
698
+        comment = api.create(ContentType.Comment, workspace, thread, label)
699
+        comment.label = ''
700
+        comment.description = content
699 701
         api.save(comment, ActionDescription.COMMENT, do_notify=False)
700 702
         api.do_notify(thread)
701 703
 
@@ -1020,6 +1022,7 @@ class UserWorkspaceFolderRestController(TIMRestControllerWithBreadcrumb):
1020 1022
                 api.set_allowed_content(folder, subcontent)
1021 1023
 
1022 1024
                 if not self._path_validation.validate_new_content(folder):
1025
+                    DBSession.rollback()
1023 1026
                     return render_invalid_integrity_chosen_path(
1024 1027
                         folder.get_label(),
1025 1028
                     )

+ 12 - 0
tracim/tracim/lib/notifications.py View File

@@ -375,6 +375,18 @@ class EmailNotifier(object):
375 375
                 content_text = '<p id="content-body-intro">{}</p>'.format(content.get_label()) + \
376 376
                     content.description
377 377
 
378
+        elif ActionDescription.STATUS_UPDATE == action:
379
+            call_to_action_text = l_('View online')
380
+            intro_user_msg = l_(
381
+                '<span id="content-intro-username">{}</span> '
382
+                'updated the following status:'
383
+            )
384
+            content_intro = intro_user_msg.format(actor.display_name)
385
+            intro_body_msg = '<p id="content-body-intro">{}: {}</p>'
386
+            content_text = intro_body_msg.format(
387
+                content.get_label(),
388
+                content.get_status().label,
389
+            )
378 390
 
379 391
         if '' == content_intro and content_text == '':
380 392
             # Skip notification, but it's not normal

+ 0 - 70
tracim/tracim/templates/user_profile.mak View File

@@ -1,70 +0,0 @@
1
-<%inherit file="local:templates.master_authenticated"/>
2
-<%namespace name="TIM" file="tracim.templates.pod"/>
3
-<%namespace name="TOOLBAR" file="tracim.templates.user_toolbars"/>
4
-<%namespace name="WIDGETS" file="tracim.templates.user_workspace_widgets"/>
5
-<%def name="title()">${_('My profile')}</%def>
6
-
7
-<div class="container-fluid">
8
-    <div class="row-fluid">
9
-        ${TOOLBAR.USER(fake_api.current_user, fake_api.current_user)}
10
-        <div>
11
-            <div class="row">
12
-                <h3 class="col-sm-11">${TIM.ICO(32, 'actions/contact-new')} ${_("User profile")}</h3>
13
-            </div>
14
-            <div class="row">
15
-                <div class="col-sm-4" id='user-profile-global-info'>
16
-                    <div class="well well-sm">
17
-                        <h3>
18
-                            ${fake_api.current_user.name}
19
-                        </h3>
20
-                        <p>
21
-                            ${TIM.ICO(16, 'apps/internet-mail')}
22
-                            <a href="mailto:${fake_api.current_user.email}">${fake_api.current_user.email}</a>
23
-                        </p>
24
-                        <p>
25
-                            % if fake_api.current_user.profile.id>=2:
26
-                                <span>${TIM.ICO(16, 'emblems/emblem-checked')} ${_('I can create workspaces.')}</span><br/>
27
-                            % endif
28
-                            % if fake_api.current_user.profile.id>=3:
29
-                                <span>${TIM.ICO(16, 'emblems/emblem-checked')} ${_('I\'m an administrator.')}</span><br/>
30
-                            % endif
31
-                        </p>
32
-                    </div>
33
-                </div>
34
-
35
-                <div class="col-sm-4" id='user-profile-global-info'>
36
-                    <div class="well well-sm">
37
-                        <h3>
38
-                            ${TIM.ICO(22, 'places/folder-remote')}
39
-                            ${_('Workspaces')}
40
-                        </h3>
41
-                        % if len(fake_api.current_user.roles)<=0:
42
-                            ${WIDGETS.EMPTY_CONTENT(_('This user is not member of any workspace.'))}
43
-                        % else:
44
-                            <table class="table">
45
-                                % for role in fake_api.current_user.roles:
46
-                                    <tr><td>${role.workspace.name}</td><td><span style="${role.style}">${role.label}</span></td></tr>
47
-                                % endfor
48
-                            </table>
49
-                        % endif
50
-                    </div>
51
-                </div>
52
-            </div>
53
-        </div>
54
-    </div>
55
-</div>
56
-
57
-<div id="user-edit-modal-dialog" class="modal" tabindex="-1" role="dialog" aria-hidden="true">
58
-  <div class="modal-dialog modal-sm">
59
-    <div class="modal-content">
60
-    </div>
61
-  </div>
62
-</div>
63
-
64
-<div id="user-edit-password-modal-dialog" class="modal" tabindex="-1" role="dialog" aria-hidden="true">
65
-  <div class="modal-dialog modal-sm">
66
-    <div class="modal-content">
67
-    </div>
68
-  </div>
69
-</div>
70
-