Browse Source

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

Damien Accorsi 7 years ago
parent
commit
93c05a3366

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

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

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

8
 
8
 
9
 import shutil
9
 import shutil
10
 
10
 
11
+from alembic import context
11
 from alembic import op
12
 from alembic import op
12
 from depot.fields.sqlalchemy import UploadedFileField
13
 from depot.fields.sqlalchemy import UploadedFileField
13
 from depot.fields.upload import UploadedFile
14
 from depot.fields.upload import UploadedFile
14
 from depot.io.utils import FileIntent
15
 from depot.io.utils import FileIntent
15
 from depot.manager import DepotManager
16
 from depot.manager import DepotManager
16
 import sqlalchemy as sa
17
 import sqlalchemy as sa
18
+from sqlalchemy.sql.expression import func
17
 
19
 
18
 # revision identifiers, used by Alembic.
20
 # revision identifiers, used by Alembic.
19
 revision = '913efdf409e5'
21
 revision = '913efdf409e5'
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
 def delete_files_on_disk(connection: sa.engine.Connection):
49
 def delete_files_on_disk(connection: sa.engine.Connection):
37
     """Deletes files from disk and their references in database."""
50
     """Deletes files from disk and their references in database."""
38
     delete_query = revision_helper.update() \
51
     delete_query = revision_helper.update() \
40
         .where(revision_helper.c.depot_file.isnot(None)) \
53
         .where(revision_helper.c.depot_file.isnot(None)) \
41
         .values(depot_file=None)
54
         .values(depot_file=None)
42
     connection.execute(delete_query)
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
 def upgrade():
60
 def upgrade():
54
     - create all files on disk from database.
68
     - create all files on disk from database.
55
     """
69
     """
56
     # Creates files depot used in this migration
70
     # Creates files depot used in this migration
57
-    DepotManager.configure(
58
-        'tracim', {'depot.storage_path': 'depot/'},
59
-    )
71
+    configure_depot()
60
     connection = op.get_bind()
72
     connection = op.get_bind()
61
     delete_files_on_disk(connection=connection)
73
     delete_files_on_disk(connection=connection)
62
     select_query = revision_helper.select() \
74
     select_query = revision_helper.select() \
63
         .where(revision_helper.c.type == 'file') \
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
     files = connection.execute(select_query).fetchall()
78
     files = connection.execute(select_query).fetchall()
66
     for file in files:
79
     for file in files:
67
         file_filename = '{0}{1}'.format(
80
         file_filename = '{0}{1}'.format(
76
         depot_file_field = UploadedFile(depot_file_intent, 'tracim')
89
         depot_file_field = UploadedFile(depot_file_intent, 'tracim')
77
         update_query = revision_helper.update() \
90
         update_query = revision_helper.update() \
78
             .where(revision_helper.c.revision_id == file.revision_id) \
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
         connection.execute(update_query)
93
         connection.execute(update_query)
82
 
94
 
83
 
95
 

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

129
 
129
 
130
 def configure_depot():
130
 def configure_depot():
131
     """Configure Depot."""
131
     """Configure Depot."""
132
-    depot_storage_name = 'tracim'
132
+    depot_storage_name = CFG.get_instance().DEPOT_STORAGE_NAME
133
     depot_storage_path = CFG.get_instance().DEPOT_STORAGE_DIR
133
     depot_storage_path = CFG.get_instance().DEPOT_STORAGE_DIR
134
     depot_storage_settings = {'depot.storage_path': depot_storage_path}
134
     depot_storage_settings = {'depot.storage_path': depot_storage_path}
135
     DepotManager.configure(
135
     DepotManager.configure(
219
                 'ERROR: depot_storage_dir configuration is mandatory. '
219
                 'ERROR: depot_storage_dir configuration is mandatory. '
220
                 'Set it before continuing.'
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
         self.PREVIEW_CACHE_DIR = tg.config.get(
230
         self.PREVIEW_CACHE_DIR = tg.config.get(
223
             'preview_cache_dir',
231
             'preview_cache_dir',
224
         )
232
         )

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

456
         if next_url=='user':
456
         if next_url=='user':
457
             tg.redirect(self.url(id=user.user_id))
457
             tg.redirect(self.url(id=user.user_id))
458
         tg.redirect(self.url())
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
                                  file_data.file.read())
355
                                  file_data.file.read())
356
             # Display error page to user if chosen label is in conflict
356
             # Display error page to user if chosen label is in conflict
357
             if not self._path_validation.validate_new_content(file):
357
             if not self._path_validation.validate_new_content(file):
358
+                DBSession.rollback()
358
                 return render_invalid_integrity_chosen_path(
359
                 return render_invalid_integrity_chosen_path(
359
                     file.get_label_as_file(),
360
                     file.get_label_as_file(),
360
                 )
361
                 )
567
             page.description = content
568
             page.description = content
568
 
569
 
569
             if not self._path_validation.validate_new_content(page):
570
             if not self._path_validation.validate_new_content(page):
571
+                DBSession.rollback()
570
                 return render_invalid_integrity_chosen_path(
572
                 return render_invalid_integrity_chosen_path(
571
                     page.get_label(),
573
                     page.get_label(),
572
                 )
574
                 )
683
                                 workspace,
685
                                 workspace,
684
                                 tmpl_context.folder,
686
                                 tmpl_context.folder,
685
                                 label)
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
             if not self._path_validation.validate_new_content(thread):
689
             if not self._path_validation.validate_new_content(thread):
690
+                DBSession.rollback()
695
                 return render_invalid_integrity_chosen_path(
691
                 return render_invalid_integrity_chosen_path(
696
                     thread.get_label(),
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
         api.save(comment, ActionDescription.COMMENT, do_notify=False)
701
         api.save(comment, ActionDescription.COMMENT, do_notify=False)
700
         api.do_notify(thread)
702
         api.do_notify(thread)
701
 
703
 
1020
                 api.set_allowed_content(folder, subcontent)
1022
                 api.set_allowed_content(folder, subcontent)
1021
 
1023
 
1022
                 if not self._path_validation.validate_new_content(folder):
1024
                 if not self._path_validation.validate_new_content(folder):
1025
+                    DBSession.rollback()
1023
                     return render_invalid_integrity_chosen_path(
1026
                     return render_invalid_integrity_chosen_path(
1024
                         folder.get_label(),
1027
                         folder.get_label(),
1025
                     )
1028
                     )

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

375
                 content_text = '<p id="content-body-intro">{}</p>'.format(content.get_label()) + \
375
                 content_text = '<p id="content-body-intro">{}</p>'.format(content.get_label()) + \
376
                     content.description
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
         if '' == content_intro and content_text == '':
391
         if '' == content_intro and content_text == '':
380
             # Skip notification, but it's not normal
392
             # Skip notification, but it's not normal

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

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
-