|
@@ -49,7 +49,7 @@ from tracim.models.data import Workspace
|
49
|
49
|
def compare_content_for_sorting_by_type_and_name(
|
50
|
50
|
content1: Content,
|
51
|
51
|
content2: Content
|
52
|
|
-):
|
|
52
|
+) -> int:
|
53
|
53
|
"""
|
54
|
54
|
:param content1:
|
55
|
55
|
:param content2:
|
|
@@ -58,7 +58,7 @@ def compare_content_for_sorting_by_type_and_name(
|
58
|
58
|
0 if content1 = content2
|
59
|
59
|
"""
|
60
|
60
|
|
61
|
|
- if content1.type==content2.type:
|
|
61
|
+ if content1.type == content2.type:
|
62
|
62
|
if content1.get_label().lower()>content2.get_label().lower():
|
63
|
63
|
return 1
|
64
|
64
|
elif content1.get_label().lower()<content2.get_label().lower():
|
|
@@ -66,11 +66,20 @@ def compare_content_for_sorting_by_type_and_name(
|
66
|
66
|
return 0
|
67
|
67
|
else:
|
68
|
68
|
# TODO - D.A. - 2014-12-02 - Manage Content Types Dynamically
|
69
|
|
- content_type_order = [ContentType.Folder, ContentType.Page, ContentType.Thread, ContentType.File]
|
70
|
|
- result = content_type_order.index(content1.type)-content_type_order.index(content2.type)
|
71
|
|
- if result<0:
|
|
69
|
+ content_type_order = [
|
|
70
|
+ ContentType.Folder,
|
|
71
|
+ ContentType.Page,
|
|
72
|
+ ContentType.Thread,
|
|
73
|
+ ContentType.File,
|
|
74
|
+ ]
|
|
75
|
+
|
|
76
|
+ content_1_type_index = content_type_order.index(content1.type)
|
|
77
|
+ content_2_type_index = content_type_order.index(content2.type)
|
|
78
|
+ result = content_1_type_index - content_2_type_index
|
|
79
|
+
|
|
80
|
+ if result < 0:
|
72
|
81
|
return -1
|
73
|
|
- elif result>0:
|
|
82
|
+ elif result > 0:
|
74
|
83
|
return 1
|
75
|
84
|
else:
|
76
|
85
|
return 0
|
|
@@ -79,7 +88,7 @@ def compare_content_for_sorting_by_type_and_name(
|
79
|
88
|
def compare_tree_items_for_sorting_by_type_and_name(
|
80
|
89
|
item1: NodeTreeItem,
|
81
|
90
|
item2: NodeTreeItem
|
82
|
|
-):
|
|
91
|
+) -> int:
|
83
|
92
|
return compare_content_for_sorting_by_type_and_name(item1.node, item2.node)
|
84
|
93
|
|
85
|
94
|
|
|
@@ -101,13 +110,13 @@ class ContentApi(object):
|
101
|
110
|
session: Session,
|
102
|
111
|
current_user: typing.Optional[User],
|
103
|
112
|
config,
|
104
|
|
- show_archived=False,
|
105
|
|
- show_deleted=False,
|
106
|
|
- show_temporary=False,
|
107
|
|
- all_content_in_treeview=True,
|
108
|
|
- force_show_all_types=False,
|
109
|
|
- disable_user_workspaces_filter=False,
|
110
|
|
- ):
|
|
113
|
+ show_archived: bool = False,
|
|
114
|
+ show_deleted: bool = False,
|
|
115
|
+ show_temporary: bool = False,
|
|
116
|
+ all_content_in_treeview: bool = True,
|
|
117
|
+ force_show_all_types: bool = False,
|
|
118
|
+ disable_user_workspaces_filter: bool = False,
|
|
119
|
+ ) -> None:
|
111
|
120
|
self._session = session
|
112
|
121
|
self._user = current_user
|
113
|
122
|
self._config = config
|
|
@@ -125,7 +134,7 @@ class ContentApi(object):
|
125
|
134
|
show_archived: bool=False,
|
126
|
135
|
show_deleted: bool=False,
|
127
|
136
|
show_temporary: bool=False,
|
128
|
|
- ):
|
|
137
|
+ ) -> typing.Generator['ContentApi', None, None]:
|
129
|
138
|
"""
|
130
|
139
|
Use this method as context manager to update show_archived,
|
131
|
140
|
show_deleted and show_temporary properties during context.
|
|
@@ -147,11 +156,10 @@ class ContentApi(object):
|
147
|
156
|
self._show_deleted = previous_show_deleted
|
148
|
157
|
self._show_temporary = previous_show_temporary
|
149
|
158
|
|
150
|
|
- def get_revision_join(self):
|
|
159
|
+ def get_revision_join(self) -> sqlalchemy.sql.elements.BooleanClauseList:
|
151
|
160
|
"""
|
152
|
161
|
Return the Content/ContentRevision query join condition
|
153
|
162
|
:return: Content/ContentRevision query join condition
|
154
|
|
- :rtype sqlalchemy.sql.elements.BooleanClauseList
|
155
|
163
|
"""
|
156
|
164
|
return and_(Content.id == ContentRevisionRO.content_id,
|
157
|
165
|
ContentRevisionRO.revision_id == self._session.query(
|
|
@@ -161,32 +169,41 @@ class ContentApi(object):
|
161
|
169
|
.limit(1)
|
162
|
170
|
.correlate(Content))
|
163
|
171
|
|
164
|
|
-
|
165
|
|
- def get_canonical_query(self):
|
|
172
|
+ def get_canonical_query(self) -> Query:
|
166
|
173
|
"""
|
167
|
174
|
Return the Content/ContentRevision base query who join these table on the last revision.
|
168
|
175
|
:return: Content/ContentRevision Query
|
169
|
|
- :rtype sqlalchemy.orm.query.Query
|
170
|
176
|
"""
|
171
|
|
- return self._session.query(Content).join(ContentRevisionRO, self.get_revision_join())
|
|
177
|
+ return self._session.query(Content)\
|
|
178
|
+ .join(ContentRevisionRO, self.get_revision_join())
|
172
|
179
|
|
173
|
180
|
@classmethod
|
174
|
|
- def sort_tree_items(cls, content_list: [NodeTreeItem])-> [Content]:
|
|
181
|
+ def sort_tree_items(
|
|
182
|
+ cls,
|
|
183
|
+ content_list: typing.List[NodeTreeItem],
|
|
184
|
+ )-> typing.List[NodeTreeItem]:
|
175
|
185
|
news = []
|
176
|
186
|
for item in content_list:
|
177
|
187
|
news.append(item)
|
178
|
188
|
|
179
|
|
- content_list.sort(key=cmp_to_key(compare_tree_items_for_sorting_by_type_and_name))
|
|
189
|
+ content_list.sort(key=cmp_to_key(
|
|
190
|
+ compare_tree_items_for_sorting_by_type_and_name,
|
|
191
|
+ ))
|
180
|
192
|
|
181
|
193
|
return content_list
|
182
|
194
|
|
183
|
|
-
|
184
|
195
|
@classmethod
|
185
|
|
- def sort_content(cls, content_list: [Content])-> [Content]:
|
|
196
|
+ def sort_content(
|
|
197
|
+ cls,
|
|
198
|
+ content_list: typing.List[Content],
|
|
199
|
+ ) -> typing.List[Content]:
|
186
|
200
|
content_list.sort(key=cmp_to_key(compare_content_for_sorting_by_type_and_name))
|
187
|
201
|
return content_list
|
188
|
202
|
|
189
|
|
- def __real_base_query(self, workspace: Workspace=None):
|
|
203
|
+ def __real_base_query(
|
|
204
|
+ self,
|
|
205
|
+ workspace: Workspace = None,
|
|
206
|
+ ) -> Query:
|
190
|
207
|
result = self.get_canonical_query()
|
191
|
208
|
|
192
|
209
|
# Exclude non displayable types
|
|
@@ -214,7 +231,7 @@ class ContentApi(object):
|
214
|
231
|
|
215
|
232
|
return result
|
216
|
233
|
|
217
|
|
- def _base_query(self, workspace: Workspace=None):
|
|
234
|
+ def _base_query(self, workspace: Workspace=None) -> Query:
|
218
|
235
|
result = self.__real_base_query(workspace)
|
219
|
236
|
|
220
|
237
|
if not self._show_deleted:
|
|
@@ -228,7 +245,10 @@ class ContentApi(object):
|
228
|
245
|
|
229
|
246
|
return result
|
230
|
247
|
|
231
|
|
- def __revisions_real_base_query(self, workspace: Workspace=None):
|
|
248
|
+ def __revisions_real_base_query(
|
|
249
|
+ self,
|
|
250
|
+ workspace: Workspace=None,
|
|
251
|
+ ) -> Query:
|
232
|
252
|
result = self._session.query(ContentRevisionRO)
|
233
|
253
|
|
234
|
254
|
# Exclude non displayable types
|
|
@@ -247,7 +267,10 @@ class ContentApi(object):
|
247
|
267
|
|
248
|
268
|
return result
|
249
|
269
|
|
250
|
|
- def _revisions_base_query(self, workspace: Workspace=None):
|
|
270
|
+ def _revisions_base_query(
|
|
271
|
+ self,
|
|
272
|
+ workspace: Workspace=None,
|
|
273
|
+ ) -> Query:
|
251
|
274
|
result = self.__revisions_real_base_query(workspace)
|
252
|
275
|
|
253
|
276
|
if not self._show_deleted:
|
|
@@ -261,7 +284,10 @@ class ContentApi(object):
|
261
|
284
|
|
262
|
285
|
return result
|
263
|
286
|
|
264
|
|
- def _hard_filtered_base_query(self, workspace: Workspace=None):
|
|
287
|
+ def _hard_filtered_base_query(
|
|
288
|
+ self,
|
|
289
|
+ workspace: Workspace=None,
|
|
290
|
+ ) -> Query:
|
265
|
291
|
"""
|
266
|
292
|
If set to True, then filterign on is_deleted and is_archived will also
|
267
|
293
|
filter parent properties. This is required for search() function which
|
|
@@ -293,10 +319,13 @@ class ContentApi(object):
|
293
|
319
|
|
294
|
320
|
return result
|
295
|
321
|
|
296
|
|
- def get_base_query(self, workspace: Workspace) -> Query:
|
|
322
|
+ def get_base_query(
|
|
323
|
+ self,
|
|
324
|
+ workspace: Workspace,
|
|
325
|
+ ) -> Query:
|
297
|
326
|
return self._base_query(workspace)
|
298
|
327
|
|
299
|
|
- def get_child_folders(self, parent: Content=None, workspace: Workspace=None, filter_by_allowed_content_types: list=[], removed_item_ids: list=[], allowed_node_types=None) -> [Content]:
|
|
328
|
+ def get_child_folders(self, parent: Content=None, workspace: Workspace=None, filter_by_allowed_content_types: list=[], removed_item_ids: list=[], allowed_node_types=None) -> typing.List[Content]:
|
300
|
329
|
"""
|
301
|
330
|
This method returns child items (folders or items) for left bar treeview.
|
302
|
331
|
|
|
@@ -308,6 +337,9 @@ class ContentApi(object):
|
308
|
337
|
For example, if you want to move a Page from a folder to another, you should show only folders that accept pages
|
309
|
338
|
:return:
|
310
|
339
|
"""
|
|
340
|
+ filter_by_allowed_content_types = filter_by_allowed_content_types or [] # FDV
|
|
341
|
+ removed_item_ids = removed_item_ids or [] # FDV
|
|
342
|
+
|
311
|
343
|
if not allowed_node_types:
|
312
|
344
|
allowed_node_types = [ContentType.Folder]
|
313
|
345
|
elif allowed_node_types==ContentType.Any:
|
|
@@ -334,7 +366,11 @@ class ContentApi(object):
|
334
|
366
|
result = []
|
335
|
367
|
for folder in folders:
|
336
|
368
|
for allowed_content_type in filter_by_allowed_content_types:
|
337
|
|
- if folder.type==ContentType.Folder and folder.properties['allowed_content'][allowed_content_type]==True:
|
|
369
|
+
|
|
370
|
+ is_folder = folder.type == ContentType.Folder
|
|
371
|
+ content_type__allowed = folder.properties['allowed_content'][allowed_content_type] == True
|
|
372
|
+
|
|
373
|
+ if is_folder and content_type__allowed:
|
338
|
374
|
result.append(folder)
|
339
|
375
|
break
|
340
|
376
|
|
|
@@ -638,7 +674,7 @@ class ContentApi(object):
|
638
|
674
|
),
|
639
|
675
|
))
|
640
|
676
|
|
641
|
|
- def get_all(self, parent_id: int=None, content_type: str=ContentType.Any, workspace: Workspace=None) -> [Content]:
|
|
677
|
+ def get_all(self, parent_id: int=None, content_type: str=ContentType.Any, workspace: Workspace=None) -> typing.List[Content]:
|
642
|
678
|
assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
|
643
|
679
|
assert content_type is not None# DYN_REMOVE
|
644
|
680
|
assert isinstance(content_type, str) # DYN_REMOVE
|
|
@@ -655,7 +691,7 @@ class ContentApi(object):
|
655
|
691
|
|
656
|
692
|
return resultset.all()
|
657
|
693
|
|
658
|
|
- def get_children(self, parent_id: int, content_types: list, workspace: Workspace=None) -> [Content]:
|
|
694
|
+ def get_children(self, parent_id: int, content_types: list, workspace: Workspace=None) -> typing.List[Content]:
|
659
|
695
|
"""
|
660
|
696
|
Return parent_id childs of given content_types
|
661
|
697
|
:param parent_id: parent id
|
|
@@ -674,7 +710,7 @@ class ContentApi(object):
|
674
|
710
|
return resultset.all()
|
675
|
711
|
|
676
|
712
|
# TODO find an other name to filter on is_deleted / is_archived
|
677
|
|
- def get_all_with_filter(self, parent_id: int=None, content_type: str=ContentType.Any, workspace: Workspace=None) -> [Content]:
|
|
713
|
+ def get_all_with_filter(self, parent_id: int=None, content_type: str=ContentType.Any, workspace: Workspace=None) -> typing.List[Content]:
|
678
|
714
|
assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
|
679
|
715
|
assert content_type is not None# DYN_REMOVE
|
680
|
716
|
assert isinstance(content_type, str) # DYN_REMOVE
|
|
@@ -692,7 +728,7 @@ class ContentApi(object):
|
692
|
728
|
|
693
|
729
|
return resultset.all()
|
694
|
730
|
|
695
|
|
- def get_all_without_exception(self, content_type: str, workspace: Workspace=None) -> [Content]:
|
|
731
|
+ def get_all_without_exception(self, content_type: str, workspace: Workspace=None) -> typing.List[Content]:
|
696
|
732
|
assert content_type is not None# DYN_REMOVE
|
697
|
733
|
|
698
|
734
|
resultset = self._base_query(workspace)
|
|
@@ -702,7 +738,7 @@ class ContentApi(object):
|
702
|
738
|
|
703
|
739
|
return resultset.all()
|
704
|
740
|
|
705
|
|
- def get_last_active(self, parent_id: int, content_type: str, workspace: Workspace=None, limit=10) -> [Content]:
|
|
741
|
+ def get_last_active(self, parent_id: int, content_type: str, workspace: Workspace=None, limit=10) -> typing.List[Content]:
|
706
|
742
|
assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
|
707
|
743
|
assert content_type is not None# DYN_REMOVE
|
708
|
744
|
assert isinstance(content_type, str) # DYN_REMOVE
|
|
@@ -738,7 +774,7 @@ class ContentApi(object):
|
738
|
774
|
return result
|
739
|
775
|
|
740
|
776
|
def get_last_unread(self, parent_id: int, content_type: str,
|
741
|
|
- workspace: Workspace=None, limit=10) -> [Content]:
|
|
777
|
+ workspace: Workspace=None, limit=10) -> typing.List[Content]:
|
742
|
778
|
assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
|
743
|
779
|
assert content_type is not None# DYN_REMOVE
|
744
|
780
|
assert isinstance(content_type, str) # DYN_REMOVE
|
|
@@ -1109,7 +1145,7 @@ class ContentApi(object):
|
1109
|
1145
|
|
1110
|
1146
|
return keywords
|
1111
|
1147
|
|
1112
|
|
- def search(self, keywords: [str]) -> sqlalchemy.orm.query.Query:
|
|
1148
|
+ def search(self, keywords: [str]) -> Query:
|
1113
|
1149
|
"""
|
1114
|
1150
|
:return: a sorted list of Content items
|
1115
|
1151
|
"""
|
|
@@ -1126,7 +1162,7 @@ class ContentApi(object):
|
1126
|
1162
|
|
1127
|
1163
|
return title_keyworded_items
|
1128
|
1164
|
|
1129
|
|
- def get_all_types(self) -> [ContentType]:
|
|
1165
|
+ def get_all_types(self) -> typing.List[ContentType]:
|
1130
|
1166
|
labels = ContentType.all()
|
1131
|
1167
|
content_types = []
|
1132
|
1168
|
for label in labels:
|
|
@@ -1134,7 +1170,10 @@ class ContentApi(object):
|
1134
|
1170
|
|
1135
|
1171
|
return ContentType.sorted(content_types)
|
1136
|
1172
|
|
1137
|
|
- def exclude_unavailable(self, contents: [Content]) -> [Content]:
|
|
1173
|
+ def exclude_unavailable(
|
|
1174
|
+ self,
|
|
1175
|
+ contents: typing.List[Content],
|
|
1176
|
+ ) -> typing.List[Content]:
|
1138
|
1177
|
"""
|
1139
|
1178
|
Update and return list with content under archived/deleted removed.
|
1140
|
1179
|
:param contents: List of contents to parse
|