Browse Source

Issue #85: Restore Archive buttons and permet display of archives and deleted contents

Bastien Sevajol (Algoo) 8 years ago
parent
commit
7d4d07b7a0

+ 10 - 2
tracim/tracim/controllers/__init__.py View File

73
 
73
 
74
     @classmethod
74
     @classmethod
75
     def current_folder(cls) -> Content:
75
     def current_folder(cls) -> Content:
76
-        content_api = ContentApi(tg.tmpl_context.current_user)
76
+        content_api = ContentApi(
77
+            tg.tmpl_context.current_user,
78
+            show_archived=True,
79
+            show_deleted=True,
80
+        )
77
         folder_id = int(tg.request.controller_state.routing_args.get('folder_id'))
81
         folder_id = int(tg.request.controller_state.routing_args.get('folder_id'))
78
         folder = content_api.get_one(folder_id, ContentType.Folder, tg.tmpl_context.workspace)
82
         folder = content_api.get_one(folder_id, ContentType.Folder, tg.tmpl_context.workspace)
79
 
83
 
149
         :param item_id: an item id (item may be normal content or folder
153
         :param item_id: an item id (item may be normal content or folder
150
         :return:
154
         :return:
151
         """
155
         """
152
-        return ContentApi(tmpl_context.current_user).build_breadcrumb(tmpl_context.workspace, item_id)
156
+        return ContentApi(
157
+            tmpl_context.current_user,
158
+            show_archived=True,
159
+            show_deleted=True,
160
+        ).build_breadcrumb(tmpl_context.workspace, item_id)
153
 
161
 
154
     def _struct_new_serialized(self, workspace_id, parent_id):
162
     def _struct_new_serialized(self, workspace_id, parent_id):
155
         print('values are: ', workspace_id, parent_id)
163
         print('values are: ', workspace_id, parent_id)

+ 46 - 11
tracim/tracim/controllers/content.py View File

174
         file_id = int(file_id)
174
         file_id = int(file_id)
175
         user = tmpl_context.current_user
175
         user = tmpl_context.current_user
176
         workspace = tmpl_context.workspace
176
         workspace = tmpl_context.workspace
177
-        workspace_id = tmpl_context.workspace_id
178
 
177
 
179
         current_user_content = Context(CTX.CURRENT_USER,
178
         current_user_content = Context(CTX.CURRENT_USER,
180
                                        current_user=user).toDict(user)
179
                                        current_user=user).toDict(user)
181
         current_user_content.roles.sort(key=lambda role: role.workspace.name)
180
         current_user_content.roles.sort(key=lambda role: role.workspace.name)
182
 
181
 
183
-        content_api = ContentApi(user)
182
+        content_api = ContentApi(
183
+            user,
184
+            show_archived=True,
185
+            show_deleted=True,
186
+        )
184
         if revision_id:
187
         if revision_id:
185
             file = content_api.get_one_from_revision(file_id,  self._item_type, workspace, revision_id)
188
             file = content_api.get_one_from_revision(file_id,  self._item_type, workspace, revision_id)
186
         else:
189
         else:
402
         current_user_content = Context(CTX.CURRENT_USER).toDict(user)
405
         current_user_content = Context(CTX.CURRENT_USER).toDict(user)
403
         current_user_content.roles.sort(key=lambda role: role.workspace.name)
406
         current_user_content.roles.sort(key=lambda role: role.workspace.name)
404
 
407
 
405
-        content_api = ContentApi(user)
408
+        content_api = ContentApi(
409
+            user,
410
+            show_deleted=True,
411
+            show_archived=True,
412
+        )
406
         if revision_id:
413
         if revision_id:
407
             page = content_api.get_one_from_revision(page_id, ContentType.Page, workspace, revision_id)
414
             page = content_api.get_one_from_revision(page_id, ContentType.Page, workspace, revision_id)
408
         else:
415
         else:
587
         current_user_content = Context(CTX.CURRENT_USER).toDict(user)
594
         current_user_content = Context(CTX.CURRENT_USER).toDict(user)
588
         current_user_content.roles.sort(key=lambda role: role.workspace.name)
595
         current_user_content.roles.sort(key=lambda role: role.workspace.name)
589
 
596
 
590
-        content_api = ContentApi(user)
597
+        content_api = ContentApi(
598
+            user,
599
+            show_deleted=True,
600
+            show_archived=True,
601
+        )
591
         thread = content_api.get_one(thread_id, ContentType.Thread, workspace)
602
         thread = content_api.get_one(thread_id, ContentType.Thread, workspace)
592
 
603
 
593
         fake_api_breadcrumb = self.get_breadcrumb(thread_id)
604
         fake_api_breadcrumb = self.get_breadcrumb(thread_id)
744
 
755
 
745
     @tg.require(current_user_is_reader())
756
     @tg.require(current_user_is_reader())
746
     @tg.expose('tracim.templates.folder.getone')
757
     @tg.expose('tracim.templates.folder.getone')
747
-    def get_one(self, folder_id):
758
+    def get_one(self, folder_id, **kwargs):
759
+        """
760
+        :param folder_id: Displayed folder id
761
+        :param kwargs:
762
+          * show_deleted: bool: Display deleted contents or hide them if False
763
+          * show_archived: bool: Display archived contents or hide them
764
+            if False
765
+        """
766
+        show_deleted = kwargs.get('show_deleted', False)
767
+        show_archived = kwargs.get('show_archived', False)
748
         folder_id = int(folder_id)
768
         folder_id = int(folder_id)
749
         user = tmpl_context.current_user
769
         user = tmpl_context.current_user
750
         workspace = tmpl_context.workspace
770
         workspace = tmpl_context.workspace
751
-        workspace_id = tmpl_context.workspace_id
752
 
771
 
753
         current_user_content = Context(CTX.CURRENT_USER,
772
         current_user_content = Context(CTX.CURRENT_USER,
754
                                        current_user=user).toDict(user)
773
                                        current_user=user).toDict(user)
755
         current_user_content.roles.sort(key=lambda role: role.workspace.name)
774
         current_user_content.roles.sort(key=lambda role: role.workspace.name)
756
 
775
 
757
-        content_api = ContentApi(user)
758
-        folder = content_api.get_one(folder_id, ContentType.Folder, workspace)
776
+        content_api = ContentApi(
777
+            user,
778
+            show_deleted=show_deleted,
779
+            show_archived=show_archived,
780
+        )
781
+        with content_api.show(show_deleted=True, show_archived=True):
782
+            folder = content_api.get_one(
783
+                folder_id,
784
+                ContentType.Folder,
785
+                workspace,
786
+            )
759
 
787
 
760
         fake_api_breadcrumb = self.get_breadcrumb(folder_id)
788
         fake_api_breadcrumb = self.get_breadcrumb(folder_id)
761
         fake_api_subfolders = self.get_all_fake(workspace, folder.content_id).result
789
         fake_api_subfolders = self.get_all_fake(workspace, folder.content_id).result
787
         fake_api.sub_items = Context(CTX.FOLDER_CONTENT_LIST).toDict(sub_items)
815
         fake_api.sub_items = Context(CTX.FOLDER_CONTENT_LIST).toDict(sub_items)
788
 
816
 
789
         fake_api.content_types = Context(CTX.DEFAULT).toDict(
817
         fake_api.content_types = Context(CTX.DEFAULT).toDict(
790
-            content_api.get_all_types())
818
+            content_api.get_all_types()
819
+        )
791
 
820
 
792
         dictified_folder = Context(CTX.FOLDER).toDict(folder, 'folder')
821
         dictified_folder = Context(CTX.FOLDER).toDict(folder, 'folder')
793
-        return DictLikeClass(result = dictified_folder, fake_api=fake_api)
822
+        return DictLikeClass(
823
+            result=dictified_folder,
824
+            fake_api=fake_api,
825
+            show_deleted=show_deleted,
826
+            show_archived=show_archived,
827
+        )
794
 
828
 
795
 
829
 
796
     def get_all_fake(self, context_workspace: Workspace, parent_id=None):
830
     def get_all_fake(self, context_workspace: Workspace, parent_id=None):
804
         """
838
         """
805
         workspace = context_workspace
839
         workspace = context_workspace
806
         content_api = ContentApi(tmpl_context.current_user)
840
         content_api = ContentApi(tmpl_context.current_user)
807
-        parent_folder = content_api.get_one(parent_id, ContentType.Folder)
841
+        with content_api.show(show_deleted=True, show_archived=True):
842
+            parent_folder = content_api.get_one(parent_id, ContentType.Folder)
808
         folders = content_api.get_child_folders(parent_folder, workspace)
843
         folders = content_api.get_child_folders(parent_folder, workspace)
809
 
844
 
810
         folders = Context(CTX.FOLDERS).toDict(folders)
845
         folders = Context(CTX.FOLDERS).toDict(folders)

+ 17 - 2
tracim/tracim/controllers/workspace.py View File

42
         tg.redirect(tg.url('/home'))
42
         tg.redirect(tg.url('/home'))
43
 
43
 
44
     @tg.expose('tracim.templates.workspace.getone')
44
     @tg.expose('tracim.templates.workspace.getone')
45
-    def get_one(self, workspace_id):
45
+    def get_one(self, workspace_id, **kwargs):
46
+        """
47
+        :param workspace_id: Displayed workspace id
48
+        :param kwargs:
49
+          * show_deleted: bool: Display deleted contents or hide them if False
50
+          * show_archived: bool: Display archived contents or hide them
51
+            if False
52
+        """
53
+        show_deleted = kwargs.get('show_deleted', False)
54
+        show_archived = kwargs.get('show_archived', False)
46
         user = tmpl_context.current_user
55
         user = tmpl_context.current_user
47
 
56
 
48
         current_user_content = Context(CTX.CURRENT_USER).toDict(user)
57
         current_user_content = Context(CTX.CURRENT_USER).toDict(user)
61
 
70
 
62
         fake_api.sub_items = Context(CTX.FOLDER_CONTENT_LIST).toDict(
71
         fake_api.sub_items = Context(CTX.FOLDER_CONTENT_LIST).toDict(
63
             # TODO BS 20161209: Is the correct way to grab folders? No use API?
72
             # TODO BS 20161209: Is the correct way to grab folders? No use API?
64
-            workspace.get_valid_children(ContentApi.DISPLAYABLE_CONTENTS)
73
+            workspace.get_valid_children(
74
+                ContentApi.DISPLAYABLE_CONTENTS,
75
+                show_deleted=show_deleted,
76
+                show_archived=show_archived,
77
+            )
65
         )
78
         )
66
 
79
 
67
         dictified_workspace = Context(CTX.WORKSPACE).toDict(workspace, 'workspace')
80
         dictified_workspace = Context(CTX.WORKSPACE).toDict(workspace, 'workspace')
71
             result=dictified_workspace,
84
             result=dictified_workspace,
72
             fake_api=fake_api,
85
             fake_api=fake_api,
73
             webdav_url=webdav_url,
86
             webdav_url=webdav_url,
87
+            show_deleted=show_deleted,
88
+            show_archived=show_archived,
74
         )
89
         )
75
 
90
 
76
     @tg.expose('json')
91
     @tg.expose('json')

+ 30 - 0
tracim/tracim/lib/content.py View File

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
+from contextlib import contextmanager
3
+
2
 import os
4
 import os
3
 
5
 
4
 from operator import itemgetter
6
 from operator import itemgetter
100
         self._force_show_all_types = force_show_all_types
102
         self._force_show_all_types = force_show_all_types
101
         self._disable_user_workspaces_filter = disable_user_workspaces_filter
103
         self._disable_user_workspaces_filter = disable_user_workspaces_filter
102
 
104
 
105
+    @contextmanager
106
+    def show(
107
+            self,
108
+            show_archived: bool=False,
109
+            show_deleted: bool=False,
110
+            show_temporary: bool=False,
111
+    ):
112
+        """
113
+        Use this method as context manager to update show_archived,
114
+        show_deleted and show_temporary properties during context.
115
+        :param show_archived: show archived contents
116
+        :param show_deleted:  show deleted contents
117
+        :param show_temporary:  show temporary contents
118
+        """
119
+        previous_show_archived = self._show_archived
120
+        previous_show_deleted = self._show_deleted
121
+        previous_show_temporary = self._show_temporary
122
+
123
+        try:
124
+            self._show_archived = show_archived
125
+            self._show_deleted = show_deleted
126
+            self._show_temporary = show_temporary
127
+            yield self
128
+        finally:
129
+            self._show_archived = previous_show_archived
130
+            self._show_deleted = previous_show_deleted
131
+            self._show_temporary = previous_show_temporary
132
+
103
     @classmethod
133
     @classmethod
104
     def get_revision_join(cls):
134
     def get_revision_join(cls):
105
         """
135
         """

+ 5 - 1
tracim/tracim/lib/helpers.py View File

167
     try:
167
     try:
168
         content_data = content_str.split(CST.TREEVIEW_MENU.ID_SEPARATOR)
168
         content_data = content_str.split(CST.TREEVIEW_MENU.ID_SEPARATOR)
169
         content_id = int(content_data[1])
169
         content_id = int(content_data[1])
170
-        content = ContentApi(tg.tmpl_context.current_user).get_one(content_id, ContentType.Any)
170
+        content = ContentApi(
171
+            tg.tmpl_context.current_user,
172
+            show_archived=True,
173
+            show_deleted=True,
174
+        ).get_one(content_id, ContentType.Any)
171
     except (IndexError, ValueError) as e:
175
     except (IndexError, ValueError) as e:
172
         content = None
176
         content = None
173
 
177
 

+ 12 - 3
tracim/tracim/model/data.py View File

97
         # @see Content.get_allowed_content_types()
97
         # @see Content.get_allowed_content_types()
98
         return [ContentType('folder')]
98
         return [ContentType('folder')]
99
 
99
 
100
-    def get_valid_children(self, content_types: list=None):
100
+    def get_valid_children(
101
+            self,
102
+            content_types: list=None,
103
+            show_deleted: bool=False,
104
+            show_archived: bool=False,
105
+    ):
101
         for child in self.contents:
106
         for child in self.contents:
102
             # we search only direct children
107
             # we search only direct children
103
             if not child.parent \
108
             if not child.parent \
104
-                    and not child.is_deleted \
105
-                    and not child.is_archived:
109
+                    and (show_deleted or not child.is_deleted) \
110
+                    and (show_archived or not child.is_archived):
106
                 if not content_types or child.type in content_types:
111
                 if not content_types or child.type in content_types:
107
                     yield child
112
                     yield child
108
 
113
 
1032
     def revision(self) -> ContentRevisionRO:
1037
     def revision(self) -> ContentRevisionRO:
1033
         return self.get_current_revision()
1038
         return self.get_current_revision()
1034
 
1039
 
1040
+    @property
1041
+    def is_editable(self) -> bool:
1042
+        return not self.is_archived and not self.is_deleted
1043
+
1035
     def get_current_revision(self) -> ContentRevisionRO:
1044
     def get_current_revision(self) -> ContentRevisionRO:
1036
         if not self.revisions:
1045
         if not self.revisions:
1037
             return self.new_revision()
1046
             return self.new_revision()

+ 20 - 3
tracim/tracim/model/serializers.py View File

265
         label = version.label,
265
         label = version.label,
266
         owner = context.toDict(version.owner),
266
         owner = context.toDict(version.owner),
267
         created = version.created,
267
         created = version.created,
268
-        action = context.toDict(version.get_last_action())
268
+        action = context.toDict(version.get_last_action()),
269
     )
269
     )
270
 
270
 
271
 
271
 
389
             revisions=context.toDict(sorted(content.revisions, key=lambda v: v.created, reverse=True)),
389
             revisions=context.toDict(sorted(content.revisions, key=lambda v: v.created, reverse=True)),
390
             selected_revision='latest' if content.revision_to_serialize<=0 else content.revision_to_serialize,
390
             selected_revision='latest' if content.revision_to_serialize<=0 else content.revision_to_serialize,
391
             history=Context(CTX.CONTENT_HISTORY).toDict(content.get_history()),
391
             history=Context(CTX.CONTENT_HISTORY).toDict(content.get_history()),
392
+            is_editable=content.is_editable,
393
+            is_deleted=content.is_deleted,
394
+            is_archived=content.is_archived,
392
             urls = context.toDict({
395
             urls = context.toDict({
393
                 'mark_read': context.url(Content.format_path('/workspaces/{wid}/folders/{fid}/{ctype}s/{cid}/put_read', content)),
396
                 'mark_read': context.url(Content.format_path('/workspaces/{wid}/folders/{fid}/{ctype}s/{cid}/put_read', content)),
394
                 'mark_unread': context.url(Content.format_path('/workspaces/{wid}/folders/{fid}/{ctype}s/{cid}/put_unread', content))
397
                 'mark_unread': context.url(Content.format_path('/workspaces/{wid}/folders/{fid}/{ctype}s/{cid}/put_unread', content))
458
             comments = reversed(context.toDict(item.get_comments())),
461
             comments = reversed(context.toDict(item.get_comments())),
459
             is_new=item.has_new_information_for(context.get_user()),
462
             is_new=item.has_new_information_for(context.get_user()),
460
             history = Context(CTX.CONTENT_HISTORY).toDict(item.get_history()),
463
             history = Context(CTX.CONTENT_HISTORY).toDict(item.get_history()),
464
+            is_editable=item.is_editable,
465
+            is_deleted=item.is_deleted,
466
+            is_archived=item.is_archived,
461
             urls = context.toDict({
467
             urls = context.toDict({
462
                 'mark_read': context.url(Content.format_path('/workspaces/{wid}/folders/{fid}/{ctype}s/{cid}/put_read', item)),
468
                 'mark_read': context.url(Content.format_path('/workspaces/{wid}/folders/{fid}/{ctype}s/{cid}/put_read', item)),
463
                 'mark_unread': context.url(Content.format_path('/workspaces/{wid}/folders/{fid}/{ctype}s/{cid}/put_unread', item))
469
                 'mark_unread': context.url(Content.format_path('/workspaces/{wid}/folders/{fid}/{ctype}s/{cid}/put_unread', item))
547
                 all = page_nb_all,
553
                 all = page_nb_all,
548
                 open = page_nb_open,
554
                 open = page_nb_open,
549
             ),
555
             ),
550
-            content_nb = DictLikeClass(all = content_nb_all)
556
+            content_nb = DictLikeClass(all = content_nb_all),
557
+            is_editable=content.is_editable,
551
         )
558
         )
552
 
559
 
553
     return result
560
     return result
595
                                     open=folder_nb_open),
602
                                     open=folder_nb_open),
596
             page_nb=DictLikeClass(all=page_nb_all,
603
             page_nb=DictLikeClass(all=page_nb_all,
597
                                   open=page_nb_open),
604
                                   open=page_nb_open),
598
-            content_nb=DictLikeClass(all = content_nb_all)
605
+            content_nb=DictLikeClass(all = content_nb_all),
606
+            is_archived=content.is_archived,
607
+            is_deleted=content.is_deleted,
608
+            is_editable=content.is_editable,
599
         )
609
         )
600
 
610
 
601
     elif content.type==ContentType.Page:
611
     elif content.type==ContentType.Page:
635
         url=ContentType.fill_url(content),
645
         url=ContentType.fill_url(content),
636
         type=DictLikeClass(content_type.toDict()),
646
         type=DictLikeClass(content_type.toDict()),
637
         status=context.toDict(content.get_status()),
647
         status=context.toDict(content.get_status()),
648
+        is_deleted=content.is_deleted,
649
+        is_archived=content.is_archived,
650
+        is_editable=content.is_editable,
638
         last_activity = DictLikeClass({'date': last_activity_date,
651
         last_activity = DictLikeClass({'date': last_activity_date,
639
                                        'label': last_activity_date_formatted,
652
                                        'label': last_activity_date_formatted,
640
                                        'delta': last_activity_label})
653
                                        'delta': last_activity_label})
706
         item = Context(CTX.CONTENT_LIST).toDict(content)
719
         item = Context(CTX.CONTENT_LIST).toDict(content)
707
         item.notes = ''
720
         item.notes = ''
708
 
721
 
722
+    item.is_deleted = content.is_deleted
723
+    item.is_archived = content.is_archived
724
+    item.is_editable = content.is_editable
725
+
709
     return item
726
     return item
710
 
727
 
711
 
728
 

+ 4 - 0
tracim/tracim/templates/file/getone.mak View File

36
 ##
36
 ##
37
 ############################################################################
37
 ############################################################################
38
 
38
 
39
+<div class="content-container ${'not-editable' if not result.file.is_editable else ''}">
40
+<!--# TODO BS 20161213: Indent content-->
41
+
39
 <div class="row t-page-header-row">
42
 <div class="row t-page-header-row">
40
     <div class="col-sm-7 col-sm-offset-3 main">
43
     <div class="col-sm-7 col-sm-offset-3 main">
41
         <h1 class="page-header t-file-color-border">
44
         <h1 class="page-header t-file-color-border">
167
     </div>
170
     </div>
168
 <div/>
171
 <div/>
169
 
172
 
173
+</div>

+ 33 - 8
tracim/tracim/templates/file/toolbar.mak View File

3
 <%namespace name="BUTTON" file="tracim.templates.widgets.button"/>
3
 <%namespace name="BUTTON" file="tracim.templates.widgets.button"/>
4
 
4
 
5
 <%def name="SECURED_FILE(user, workspace, file)">
5
 <%def name="SECURED_FILE(user, workspace, file)">
6
-    <div class="btn-group btn-group-vertical text-center">
7
-        ${BUTTON.MARK_CONTENT_READ_OR_UNREAD(user, workspace, file)}
8
-    </div>
9
-    <hr class="t-toolbar-btn-group-separator"/>
10
-    <p></p>
6
+
7
+    % if file.is_editable:
8
+        <div class="btn-group btn-group-vertical text-center">
9
+            ${BUTTON.MARK_CONTENT_READ_OR_UNREAD(user, workspace, file)}
10
+        </div>
11
+        <hr class="t-toolbar-btn-group-separator"/>
12
+        <p></p>
13
+    % endif
11
 
14
 
12
     <% download_url = tg.url('/workspaces/{}/folders/{}/files/{}/download?revision_id={}'.format(result.file.workspace.id, result.file.parent.id,result.file.id,result.file.selected_revision)) %>
15
     <% download_url = tg.url('/workspaces/{}/folders/{}/files/{}/download?revision_id={}'.format(result.file.workspace.id, result.file.parent.id,result.file.id,result.file.selected_revision)) %>
13
     <% edit_disabled = ('', 'disabled')[file.selected_revision!='latest' or file.status.id[:6]=='closed'] %>
16
     <% edit_disabled = ('', 'disabled')[file.selected_revision!='latest' or file.status.id[:6]=='closed'] %>
14
     <% delete_or_archive_disabled = ('', 'disabled')[file.selected_revision!='latest'] %>
17
     <% delete_or_archive_disabled = ('', 'disabled')[file.selected_revision!='latest'] %>
15
-    % if h.user_role(user, workspace)>1:
18
+    % if h.user_role(user, workspace)>1 and file.is_editable:
16
         <div class="btn-group btn-group-vertical">
19
         <div class="btn-group btn-group-vertical">
17
             <a title="${_('Edit current file')}" class="btn btn-default ${edit_disabled}" data-toggle="modal" data-target="#file-edit-modal-dialog" data-remote="${tg.url('/workspaces/{}/folders/{}/files/{}/edit'.format(file.workspace.id, file.parent.id, file.id))}" >${ICON.FA_FW('fa fa-edit t-less-visible')} ${_('Edit')}</a>
20
             <a title="${_('Edit current file')}" class="btn btn-default ${edit_disabled}" data-toggle="modal" data-target="#file-edit-modal-dialog" data-remote="${tg.url('/workspaces/{}/folders/{}/files/{}/edit'.format(file.workspace.id, file.parent.id, file.id))}" >${ICON.FA_FW('fa fa-edit t-less-visible')} ${_('Edit')}</a>
18
         </div>
21
         </div>
26
     </div>
29
     </div>
27
     <p></p>
30
     <p></p>
28
 
31
 
29
-    % if user.profile.id>=3 or h.user_role(user, workspace)>=4:
32
+    % if user.profile.id>=3 or h.user_role(user, workspace)>=4 and file.is_editable:
30
         ## if the user can see the toolbar, it means he is the workspace manager.
33
         ## if the user can see the toolbar, it means he is the workspace manager.
31
         ## So now, we need to know if he alsa has right to delete workspaces
34
         ## So now, we need to know if he alsa has right to delete workspaces
32
         <div class="btn-group btn-group-vertical">
35
         <div class="btn-group btn-group-vertical">
33
-            ## SHOW_ARCHIVE_BUTTON__BUG_#81
34
             <a title="${_('Archive file')}" class="btn btn-default ${delete_or_archive_disabled}" href="${tg.url('/workspaces/{}/folders/{}/files/{}/put_archive'.format(file.workspace.id, file.parent.id, file.id))}">
36
             <a title="${_('Archive file')}" class="btn btn-default ${delete_or_archive_disabled}" href="${tg.url('/workspaces/{}/folders/{}/files/{}/put_archive'.format(file.workspace.id, file.parent.id, file.id))}">
35
                 ${ICON.FA_FW('fa fa-archive t-less-visible')} ${_('Archive')}
37
                 ${ICON.FA_FW('fa fa-archive t-less-visible')} ${_('Archive')}
36
             </a>
38
             </a>
39
             </a>
41
             </a>
40
         </div>
42
         </div>
41
     % endif
43
     % endif
44
+
45
+    % if file.is_deleted or file.is_archived:
46
+        <div class="btn-group btn-group-vertical">
47
+            % if file.is_archived:
48
+                <a title="${_('Restore')}"
49
+                   class="btn btn-default"
50
+                   href="${tg.url('/workspaces/{}/folders/{}/files/{}/put_archive_undo'.format(file.workspace.id, file.parent.id, file.id))}">
51
+                    <i class="fa fa-archive fa-fw tracim-less-visible"></i>
52
+                    ${_('Restore')}
53
+                </a>
54
+            % endif
55
+            % if file.is_deleted:
56
+                <a title="${_('Restore')}"
57
+                   class="btn btn-default"
58
+                   href="${tg.url('/workspaces/{}/folders/{}/files/{}/put_delete_undo'.format(file.workspace.id, file.parent.id, file.id))}">
59
+                    <i class="fa fa-archive fa-fw tracim-less-visible"></i>
60
+                    ${_('Restore')}
61
+                </a>
62
+            % endif
63
+        </div>
64
+        <p></p>
65
+    % endif
66
+
42
 </%def>
67
 </%def>
43
 
68
 

+ 8 - 0
tracim/tracim/templates/folder/getone.mak View File

9
 <%namespace name="TABLE_ROW" file="tracim.templates.widgets.table_row"/>
9
 <%namespace name="TABLE_ROW" file="tracim.templates.widgets.table_row"/>
10
 <%namespace name="ICON" file="tracim.templates.widgets.icon"/>
10
 <%namespace name="ICON" file="tracim.templates.widgets.icon"/>
11
 <%namespace name="P" file="tracim.templates.widgets.paragraph"/>
11
 <%namespace name="P" file="tracim.templates.widgets.paragraph"/>
12
+<%namespace name="UI" file="tracim.templates.widgets.ui"/>
12
 
13
 
13
 <%def name="title()">${result.folder.label}</%def>
14
 <%def name="title()">${result.folder.label}</%def>
14
 
15
 
38
 ##
39
 ##
39
 ############################################################################
40
 ############################################################################
40
 
41
 
42
+<div class="folder-container ${'not-editable' if not result.folder.is_editable else ''}">
43
+<!--# TODO BS 20161213: Indent content-->
44
+
41
 <div class="row t-page-header-row">
45
 <div class="row t-page-header-row">
42
     <div class="col-sm-7 col-sm-offset-3 main">
46
     <div class="col-sm-7 col-sm-offset-3 main">
43
         <h1 class="page-header t-folder-color-border">
47
         <h1 class="page-header t-folder-color-border">
115
                 % endif
119
                 % endif
116
             % endif
120
             % endif
117
 
121
 
122
+            ${UI.GENERIC_DISPLAY_VIEW_BUTTONS_CONTAINER(tg.url('/workspaces/{}/folders/{}'.format(result.folder.workspace.id, result.folder.id)))}
123
+
118
             % if len(fake_api.sub_items) <= 0:
124
             % if len(fake_api.sub_items) <= 0:
119
                 ${P.EMPTY_CONTENT(_('This folder has not yet content.'))}
125
                 ${P.EMPTY_CONTENT(_('This folder has not yet content.'))}
120
             % else:
126
             % else:
176
         });
182
         });
177
     });
183
     });
178
 </script>
184
 </script>
185
+
186
+</div>

+ 26 - 5
tracim/tracim/templates/folder/toolbar.mak View File

28
     %>
28
     %>
29
     
29
     
30
     <% delete_or_archive_disabled = ('', 'disabled')[folder.selected_revision!='latest'] %> 
30
     <% delete_or_archive_disabled = ('', 'disabled')[folder.selected_revision!='latest'] %> 
31
-    % if h.user_role(user, workspace)>2:
31
+    % if h.user_role(user, workspace)>2 and folder.is_editable:
32
         <div class="btn-group btn-group-vertical">
32
         <div class="btn-group btn-group-vertical">
33
             ## This action is allowed for content managers only
33
             ## This action is allowed for content managers only
34
             <a title="${_('Edit current folder')}" class="btn btn-default ${edit_disabled}" data-toggle="modal" data-target="#folder-edit-modal-dialog" data-remote="${tg.url('/workspaces/{}/folders/{}/edit'.format(folder.workspace.id, folder.id))}" ><i class="fa fa-edit fa-fw tracim-less-visible"></i> ${_('Edit')}</a>
34
             <a title="${_('Edit current folder')}" class="btn btn-default ${edit_disabled}" data-toggle="modal" data-target="#folder-edit-modal-dialog" data-remote="${tg.url('/workspaces/{}/folders/{}/edit'.format(folder.workspace.id, folder.id))}" ><i class="fa fa-edit fa-fw tracim-less-visible"></i> ${_('Edit')}</a>
36
         <p></p>
36
         <p></p>
37
     % endif
37
     % endif
38
     
38
     
39
-    % if user.profile.id>=3 or h.user_role(user, workspace)>=4:
39
+    % if user.profile.id>=3 or h.user_role(user, workspace)>=4 and folder.is_editable:
40
         <div class="btn-group btn-group-vertical">
40
         <div class="btn-group btn-group-vertical">
41
             ## This action is allowed for content managers only
41
             ## This action is allowed for content managers only
42
             <a title="${_('Move current folder')}" class="btn btn-default ${move_disabled}" data-toggle="modal" data-target="#folder-move-modal-dialog" data-remote="${tg.url('/workspaces/{}/folders/{}/location/{}/edit'.format(folder.workspace.id, folder.id, folder.id))}" ><i class="fa fa-arrows fa-fw tracim-less-visible"></i> ${_('Move')}</a>
42
             <a title="${_('Move current folder')}" class="btn btn-default ${move_disabled}" data-toggle="modal" data-target="#folder-move-modal-dialog" data-remote="${tg.url('/workspaces/{}/folders/{}/location/{}/edit'.format(folder.workspace.id, folder.id, folder.id))}" ><i class="fa fa-arrows fa-fw tracim-less-visible"></i> ${_('Move')}</a>
44
         <p></p>
44
         <p></p>
45
     % endif
45
     % endif
46
 
46
 
47
-    % if user.profile.id>=3 or h.user_role(user, workspace)>=4:
47
+    % if user.profile.id>=3 or h.user_role(user, workspace)>=4 and folder.is_editable:
48
         ## if the user can see the toolbar, it means he is the workspace manager.
48
         ## if the user can see the toolbar, it means he is the workspace manager.
49
         ## So now, we need to know if he alsa has right to delete workspaces
49
         ## So now, we need to know if he alsa has right to delete workspaces
50
         <div class="btn-group btn-group-vertical">
50
         <div class="btn-group btn-group-vertical">
51
-## SHOW_ARCHIVE_BUTTON__BUG_#81            <a title="${_('Archive thread')}" class="btn btn-default ${delete_or_archive_disabled}" href="${tg.url('/workspaces/{}/folders/{}/put_archive'.format(folder.workspace.id, folder.id))}"><i class="fa fa-archive fa-fw tracim-less-visible"></i> ${_('Archive')}</a>
51
+            <a title="${_('Archive thread')}" class="btn btn-default ${delete_or_archive_disabled}" href="${tg.url('/workspaces/{}/folders/{}/put_archive'.format(folder.workspace.id, folder.id))}"><i class="fa fa-archive fa-fw tracim-less-visible"></i> ${_('Archive')}</a>
52
             <a title="${_('Delete thread')}" class="btn btn-default ${delete_or_archive_disabled}" href="${tg.url('/workspaces/{}/folders/{}/put_delete'.format(folder.workspace.id, folder.id))}"><i class="fa fa-trash-o fa-fw tracim-less-visible"></i> ${_('Delete')}</a>
52
             <a title="${_('Delete thread')}" class="btn btn-default ${delete_or_archive_disabled}" href="${tg.url('/workspaces/{}/folders/{}/put_delete'.format(folder.workspace.id, folder.id))}"><i class="fa fa-trash-o fa-fw tracim-less-visible"></i> ${_('Delete')}</a>
53
         </div>
53
         </div>
54
         <p></p>
54
         <p></p>
55
     % endif
55
     % endif
56
 
56
 
57
-</%def>
57
+    % if folder.is_deleted or folder.is_archived:
58
+        <div class="btn-group btn-group-vertical">
59
+            % if folder.is_archived:
60
+                <a title="${_('Restore')}"
61
+                   class="btn btn-default"
62
+                   href="${tg.url('/workspaces/{}/folders/{}/put_archive_undo'.format(folder.workspace.id, folder.id))}">
63
+                    <i class="fa fa-archive fa-fw tracim-less-visible"></i>
64
+                    ${_('Restore')}
65
+                </a>
66
+            % endif
67
+            % if folder.is_deleted:
68
+                <a title="${_('Restore')}"
69
+                   class="btn btn-default"
70
+                   href="${tg.url('/workspaces/{}/folders/{}/put_delete_undo'.format(folder.workspace.id, folder.id))}">
71
+                    <i class="fa fa-archive fa-fw tracim-less-visible"></i>
72
+                    ${_('Restore')}
73
+                </a>
74
+            % endif
75
+        </div>
76
+        <p></p>
77
+    % endif
58
 
78
 
79
+</%def>

+ 3 - 0
tracim/tracim/templates/page/getone.mak View File

34
 ##
34
 ##
35
 ############################################################################
35
 ############################################################################
36
 
36
 
37
+<div class="content-container ${'not-editable' if not result.page.is_editable else ''}">
38
+<!--# TODO BS 20161213: Indent content-->
37
 
39
 
38
 <div class="row t-page-header-row">
40
 <div class="row t-page-header-row">
39
     <div class="col-sm-7 col-sm-offset-3 main">
41
     <div class="col-sm-7 col-sm-offset-3 main">
111
     </div>
113
     </div>
112
 <div/>
114
 <div/>
113
 
115
 
116
+</div>

+ 33 - 8
tracim/tracim/templates/page/toolbar.mak View File

2
 <%namespace name="BUTTON" file="tracim.templates.widgets.button"/>
2
 <%namespace name="BUTTON" file="tracim.templates.widgets.button"/>
3
 
3
 
4
 <%def name="SECURED_PAGE(user, workspace, page)">
4
 <%def name="SECURED_PAGE(user, workspace, page)">
5
-    <div class="btn-group btn-group-vertical">
6
-        ${BUTTON.MARK_CONTENT_READ_OR_UNREAD(user, workspace, page)}
7
-    </div>
8
-    <hr class="t-toolbar-btn-group-separator"/>
9
-    <p></p>
5
+
6
+    % if page.is_editable:
7
+        <div class="btn-group btn-group-vertical">
8
+            ${BUTTON.MARK_CONTENT_READ_OR_UNREAD(user, workspace, page)}
9
+        </div>
10
+        <hr class="t-toolbar-btn-group-separator"/>
11
+        <p></p>
12
+    % endif
10
 
13
 
11
     <% edit_disabled = ('', 'disabled')[page.selected_revision!='latest' or page.status.id[:6]=='closed'] %>
14
     <% edit_disabled = ('', 'disabled')[page.selected_revision!='latest' or page.status.id[:6]=='closed'] %>
12
     <% delete_or_archive_disabled = ('', 'disabled')[page.selected_revision!='latest'] %> 
15
     <% delete_or_archive_disabled = ('', 'disabled')[page.selected_revision!='latest'] %> 
13
-    % if h.user_role(user, workspace)>1:
16
+    % if h.user_role(user, workspace)>1 and page.is_editable:
14
         <div class="btn-group btn-group-vertical">
17
         <div class="btn-group btn-group-vertical">
15
             <a title="${_('Edit')}" class="btn btn-default ${edit_disabled}" data-toggle="modal" data-target="#page-edit-modal-dialog" data-remote="${tg.url('/workspaces/{}/folders/{}/pages/{}/edit'.format(page.workspace.id, page.parent.id, page.id))}" >
18
             <a title="${_('Edit')}" class="btn btn-default ${edit_disabled}" data-toggle="modal" data-target="#page-edit-modal-dialog" data-remote="${tg.url('/workspaces/{}/folders/{}/pages/{}/edit'.format(page.workspace.id, page.parent.id, page.id))}" >
16
                 <i class="fa fa-edit fa-fw t-less-visible"></i> ${_('Edit')}
19
                 <i class="fa fa-edit fa-fw t-less-visible"></i> ${_('Edit')}
29
 
32
 
30
 ## TODO - D.A - 2014-09-16
33
 ## TODO - D.A - 2014-09-16
31
 ## Hide the delete button if the user is not a TIM Manager
34
 ## Hide the delete button if the user is not a TIM Manager
32
-    % if user.profile.id>=2 or h.user_role(user, workspace)>2:
35
+    % if (user.profile.id>=2 or h.user_role(user, workspace)>2) and page.is_editable:
33
         ## if the user can see the toolbar, it means he is the workspace manager.
36
         ## if the user can see the toolbar, it means he is the workspace manager.
34
         ## So now, we need to know if he alsa has right to delete workspaces
37
         ## So now, we need to know if he alsa has right to delete workspaces
35
         <div class="btn-group btn-group-vertical">
38
         <div class="btn-group btn-group-vertical">
36
-            ## SHOW_ARCHIVE_BUTTON__BUG_#81
37
             <a title="${_('Archive page')}" class="btn btn-default ${delete_or_archive_disabled}" href="${tg.url('/workspaces/{}/folders/{}/pages/{}/put_archive'.format(page.workspace.id, page.parent.id, page.id))}">
39
             <a title="${_('Archive page')}" class="btn btn-default ${delete_or_archive_disabled}" href="${tg.url('/workspaces/{}/folders/{}/pages/{}/put_archive'.format(page.workspace.id, page.parent.id, page.id))}">
38
                 <i class="fa fa-archive fa-fw t-less-visible"></i>
40
                 <i class="fa fa-archive fa-fw t-less-visible"></i>
39
                 ${_('Archive')}
41
                 ${_('Archive')}
45
 
47
 
46
         </div>
48
         </div>
47
     % endif
49
     % endif
50
+
51
+    % if page.is_deleted or page.is_archived:
52
+        <div class="btn-group btn-group-vertical">
53
+            % if page.is_archived:
54
+                <a title="${_('Restore')}"
55
+                   class="btn btn-default"
56
+                   href="${tg.url('/workspaces/{}/folders/{}/pages/{}/put_archive_undo'.format(page.workspace.id, page.parent.id, page.id))}">
57
+                    <i class="fa fa-archive fa-fw tracim-less-visible"></i>
58
+                    ${_('Restore')}
59
+                </a>
60
+            % endif
61
+            % if page.is_deleted:
62
+                <a title="${_('Restore')}"
63
+                   class="btn btn-default"
64
+                   href="${tg.url('/workspaces/{}/folders/{}/pages/{}/put_delete_undo'.format(page.workspace.id, page.parent.id, page.id))}">
65
+                    <i class="fa fa-archive fa-fw tracim-less-visible"></i>
66
+                    ${_('Restore')}
67
+                </a>
68
+            % endif
69
+        </div>
70
+        <p></p>
71
+    % endif
72
+
48
 </%def>
73
 </%def>
49
 
74
 

+ 5 - 0
tracim/tracim/templates/thread/getone.mak View File

36
 ##
36
 ##
37
 ############################################################################
37
 ############################################################################
38
 
38
 
39
+<div class="content-container ${'not-editable' if not result.thread.is_editable else ''}">
40
+<!--# TODO BS 20161213: Indent content-->
41
+
39
 <div class="row t-page-header-row">
42
 <div class="row t-page-header-row">
40
     <div class="col-sm-7 col-sm-offset-3 main">
43
     <div class="col-sm-7 col-sm-offset-3 main">
41
         <h1 class="page-header t-thread-color-border">
44
         <h1 class="page-header t-thread-color-border">
125
 ##     ${WIDGETS.SECURED_TIMELINE_ITEM(fake_api.current_user, comment)}
128
 ##     ${WIDGETS.SECURED_TIMELINE_ITEM(fake_api.current_user, comment)}
126
 ## % endfor
129
 ## % endfor
127
 ##
130
 ##
131
+
132
+</div>

+ 37 - 12
tracim/tracim/templates/thread/toolbar.mak View File

3
 <%namespace name="BUTTON" file="tracim.templates.widgets.button"/>
3
 <%namespace name="BUTTON" file="tracim.templates.widgets.button"/>
4
 
4
 
5
 <%def name="SECURED_THREAD(user, workspace, thread)">
5
 <%def name="SECURED_THREAD(user, workspace, thread)">
6
-    <div class="btn-group btn-group-vertical">
7
-        ${BUTTON.MARK_CONTENT_READ_OR_UNREAD(user, workspace, thread)}
8
-    </div>
9
-    <hr class="t-toolbar-btn-group-separator"/>
10
-    <p></p>
6
+
7
+    % if thread.is_editable:
8
+        <div class="btn-group btn-group-vertical">
9
+            ${BUTTON.MARK_CONTENT_READ_OR_UNREAD(user, workspace, thread)}
10
+        </div>
11
+        <hr class="t-toolbar-btn-group-separator"/>
12
+        <p></p>
13
+    % endif
11
 
14
 
12
     <% edit_disabled = ('', 'disabled')[thread.selected_revision!='latest' or thread.status.id[:6]=='closed'] %>
15
     <% edit_disabled = ('', 'disabled')[thread.selected_revision!='latest' or thread.status.id[:6]=='closed'] %>
13
     <% delete_or_archive_disabled = ('', 'disabled')[thread.selected_revision!='latest'] %> 
16
     <% delete_or_archive_disabled = ('', 'disabled')[thread.selected_revision!='latest'] %> 
14
-    % if h.user_role(user, workspace)>1:
17
+    % if h.user_role(user, workspace)>1 and thread.is_editable:
15
         <div class="btn-group btn-group-vertical">
18
         <div class="btn-group btn-group-vertical">
16
             <a title="${_('Edit current thread')}" class="btn btn-default ${edit_disabled}" data-toggle="modal" data-target="#thread-edit-modal-dialog" data-remote="${tg.url('/workspaces/{}/folders/{}/threads/{}/edit'.format(thread.workspace.id, thread.parent.id, thread.id))}" >
19
             <a title="${_('Edit current thread')}" class="btn btn-default ${edit_disabled}" data-toggle="modal" data-target="#thread-edit-modal-dialog" data-remote="${tg.url('/workspaces/{}/folders/{}/threads/{}/edit'.format(thread.workspace.id, thread.parent.id, thread.id))}" >
17
                 ${ICON.FA_FW('t-less-visible fa fa-edit')}
20
                 ${ICON.FA_FW('t-less-visible fa fa-edit')}
21
         <p></p>
24
         <p></p>
22
     % endif
25
     % endif
23
     
26
     
24
-    % if user.profile.id>=3 or h.user_role(user, workspace)>=4:
27
+    % if (user.profile.id>=3 or h.user_role(user, workspace)>=4) and thread.is_editable:
25
         ## if the user can see the toolbar, it means he is the workspace manager.
28
         ## if the user can see the toolbar, it means he is the workspace manager.
26
         ## So now, we need to know if he alsa has right to delete workspaces
29
         ## So now, we need to know if he alsa has right to delete workspaces
27
         <div class="btn-group btn-group-vertical">
30
         <div class="btn-group btn-group-vertical">
28
-## SHOW_ARCHIVE_BUTTON__BUG_#81
29
-##             <a title="${_('Archive thread')}" class="btn btn-default ${delete_or_archive_disabled}" href="${tg.url('/workspaces/{}/folders/{}/threads/{}/put_archive'.format(thread.workspace.id, thread.parent.id, thread.id))}">
30
-##                 ${ICON.FA_FW('t-less-visible fa fa-archive')}
31
-##                 ${_('Archive')}
32
-##             </a>
31
+            <a title="${_('Archive thread')}" class="btn btn-default ${delete_or_archive_disabled}" href="${tg.url('/workspaces/{}/folders/{}/threads/{}/put_archive'.format(thread.workspace.id, thread.parent.id, thread.id))}">
32
+                ${ICON.FA_FW('t-less-visible fa fa-archive')}
33
+                ${_('Archive')}
34
+            </a>
33
             <a title="${_('Delete thread')}" class="btn btn-default ${delete_or_archive_disabled}" href="${tg.url('/workspaces/{}/folders/{}/threads/{}/put_delete'.format(thread.workspace.id, thread.parent.id, thread.id))}">
35
             <a title="${_('Delete thread')}" class="btn btn-default ${delete_or_archive_disabled}" href="${tg.url('/workspaces/{}/folders/{}/threads/{}/put_delete'.format(thread.workspace.id, thread.parent.id, thread.id))}">
34
                 ${ICON.FA_FW('t-less-visible fa fa-trash')}
36
                 ${ICON.FA_FW('t-less-visible fa fa-trash')}
35
                 ${_('Delete')}
37
                 ${_('Delete')}
36
             </a>
38
             </a>
37
         </div>
39
         </div>
38
     % endif
40
     % endif
41
+
42
+    % if thread.is_deleted or thread.is_archived:
43
+        <div class="btn-group btn-group-vertical">
44
+            % if thread.is_archived:
45
+                <a title="${_('Restore')}"
46
+                   class="btn btn-default"
47
+                   href="${tg.url('/workspaces/{}/folders/{}/threads/{}/put_archive_undo'.format(thread.workspace.id, thread.parent.id, thread.id))}">
48
+                    <i class="fa fa-archive fa-fw tracim-less-visible"></i>
49
+                    ${_('Restore')}
50
+                </a>
51
+            % endif
52
+            % if thread.is_deleted:
53
+                <a title="${_('Restore')}"
54
+                   class="btn btn-default"
55
+                   href="${tg.url('/workspaces/{}/folders/{}/threads/{}/put_delete_undo'.format(thread.workspace.id, thread.parent.id, thread.id))}">
56
+                    <i class="fa fa-archive fa-fw tracim-less-visible"></i>
57
+                    ${_('Restore')}
58
+                </a>
59
+            % endif
60
+        </div>
61
+        <p></p>
62
+    % endif
63
+
39
 </%def>
64
 </%def>
40
 
65
 

+ 1 - 1
tracim/tracim/templates/widgets/table_row.mak View File

55
 </%def>
55
 </%def>
56
 
56
 
57
 <%def name="CONTENT(content)">
57
 <%def name="CONTENT(content)">
58
-    <tr class="t-table-row-${content.type.type}">
58
+    <tr class="t-table-row-${content.type.type} ${'archived' if content.is_archived else ''} ${'deleted' if content.is_deleted else ''}">
59
         <td>
59
         <td>
60
             <span class="${content.type.color}"><i class="fa-fw ${content.type.icon}"></i> ${content.type.label}</span>
60
             <span class="${content.type.color}"><i class="fa-fw ${content.type.icon}"></i> ${content.type.label}</span>
61
 
61
 

+ 32 - 0
tracim/tracim/templates/widgets/ui.mak View File

1
+<%namespace name="BUTTON" file="tracim.templates.widgets.button"/>
2
+
3
+<%def name="GENERIC_DISPLAY_VIEW_BUTTONS_CONTAINER(base_url)">
4
+    <div class="btn-group" role="group" aria-label="...">
5
+        ${BUTTON.TEXT('', 'btn btn-default disabled', _('display...'))}
6
+        <a href="${base_url}"
7
+           class="btn btn-default disabled-has-priority ${'t-inactive-color' if show_deleted or show_archived else ''}"
8
+        >
9
+            ${_('normal view')}
10
+        </a>
11
+
12
+        % if show_deleted:
13
+        <a href="${base_url}"
14
+           % else:
15
+        <a href="${base_url}?show_deleted=1"
16
+           % endif
17
+           class="btn btn-default disabled-has-priority ${'t-inactive-color' if not show_deleted else ''}"
18
+        >
19
+            ${_('deleted')}
20
+        </a>
21
+
22
+        % if show_archived:
23
+        <a href="${base_url}"
24
+           % else:
25
+        <a href="${base_url}?show_archived=1"
26
+           % endif
27
+           class="btn btn-default disabled-has-priority ${'t-inactive-color' if not show_archived else ''}"
28
+        >
29
+            ${_('archived')}
30
+        </a>
31
+    </div>
32
+</%def>

+ 3 - 0
tracim/tracim/templates/workspace/getone.mak View File

10
 <%namespace name="P" file="tracim.templates.widgets.paragraph"/>
10
 <%namespace name="P" file="tracim.templates.widgets.paragraph"/>
11
 <%namespace name="TITLE" file="tracim.templates.widgets.title"/>
11
 <%namespace name="TITLE" file="tracim.templates.widgets.title"/>
12
 <%namespace name="TABLE_ROW" file="tracim.templates.widgets.table_row"/>
12
 <%namespace name="TABLE_ROW" file="tracim.templates.widgets.table_row"/>
13
+<%namespace name="UI" file="tracim.templates.widgets.ui"/>
13
 
14
 
14
 <%def name="title()">${result.workspace.label}</%def>
15
 <%def name="title()">${result.workspace.label}</%def>
15
 
16
 
192
                 </div>
193
                 </div>
193
             % endif
194
             % endif
194
 
195
 
196
+            ${UI.GENERIC_DISPLAY_VIEW_BUTTONS_CONTAINER(tg.url('/workspaces/{}'.format(result.workspace.id)))}
197
+
195
         </div>
198
         </div>
196
         <div class="t-spacer-above">
199
         <div class="t-spacer-above">
197
 
200