|
@@ -1,7 +1,7 @@
|
1
|
1
|
import typing
|
2
|
2
|
|
|
3
|
+import transaction
|
3
|
4
|
from pyramid.config import Configurator
|
4
|
|
-
|
5
|
5
|
try: # Python 3.5+
|
6
|
6
|
from http import HTTPStatus
|
7
|
7
|
except ImportError:
|
|
@@ -12,7 +12,7 @@ from tracim.lib.core.workspace import WorkspaceApi
|
12
|
12
|
from tracim.lib.core.content import ContentApi
|
13
|
13
|
from tracim.lib.core.userworkspace import RoleApi
|
14
|
14
|
from tracim.lib.utils.authorization import require_workspace_role
|
15
|
|
-from tracim.models.data import UserRoleInWorkspace
|
|
15
|
+from tracim.models.data import UserRoleInWorkspace, ActionDescription
|
16
|
16
|
from tracim.models.context_models import UserRoleWorkspaceInContext
|
17
|
17
|
from tracim.models.context_models import ContentInContext
|
18
|
18
|
from tracim.exceptions import NotAuthentificated
|
|
@@ -20,10 +20,16 @@ from tracim.exceptions import InsufficientUserProfile
|
20
|
20
|
from tracim.exceptions import WorkspaceNotFound
|
21
|
21
|
from tracim.views.controllers import Controller
|
22
|
22
|
from tracim.views.core_api.schemas import FilterContentQuerySchema
|
|
23
|
+from tracim.views.core_api.schemas import ContentMoveSchema
|
|
24
|
+from tracim.views.core_api.schemas import NoContentSchema
|
|
25
|
+from tracim.views.core_api.schemas import ContentCreationSchema
|
|
26
|
+from tracim.views.core_api.schemas import WorkspaceAndContentIdPathSchema
|
23
|
27
|
from tracim.views.core_api.schemas import ContentDigestSchema
|
24
|
28
|
from tracim.views.core_api.schemas import WorkspaceSchema
|
25
|
29
|
from tracim.views.core_api.schemas import WorkspaceIdPathSchema
|
26
|
30
|
from tracim.views.core_api.schemas import WorkspaceMemberSchema
|
|
31
|
+from tracim.models.data import ContentType
|
|
32
|
+from tracim.models.revision_protection import new_revision
|
27
|
33
|
|
28
|
34
|
|
29
|
35
|
class WorkspaceController(Controller):
|
|
@@ -113,6 +119,218 @@ class WorkspaceController(Controller):
|
113
|
119
|
]
|
114
|
120
|
return contents
|
115
|
121
|
|
|
122
|
+ @hapic.with_api_doc()
|
|
123
|
+ @hapic.handle_exception(NotAuthentificated, HTTPStatus.UNAUTHORIZED)
|
|
124
|
+ @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
|
|
125
|
+ @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
|
|
126
|
+ @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
|
|
127
|
+ @hapic.input_path(WorkspaceIdPathSchema())
|
|
128
|
+ @hapic.input_body(ContentCreationSchema())
|
|
129
|
+ @hapic.output_body(ContentDigestSchema())
|
|
130
|
+ def create_generic_empty_content(
|
|
131
|
+ self,
|
|
132
|
+ context,
|
|
133
|
+ request: TracimRequest,
|
|
134
|
+ hapic_data=None,
|
|
135
|
+ ) -> typing.List[ContentInContext]:
|
|
136
|
+ """
|
|
137
|
+ create a generic empty content
|
|
138
|
+ """
|
|
139
|
+ app_config = request.registry.settings['CFG']
|
|
140
|
+ creation_data = hapic_data.body
|
|
141
|
+ api = ContentApi(
|
|
142
|
+ current_user=request.current_user,
|
|
143
|
+ session=request.dbsession,
|
|
144
|
+ config=app_config,
|
|
145
|
+ )
|
|
146
|
+ content = api.create(
|
|
147
|
+ label=creation_data.label,
|
|
148
|
+ content_type=creation_data.content_type_slug,
|
|
149
|
+ workspace=request.current_workspace,
|
|
150
|
+ )
|
|
151
|
+ api.save(content, ActionDescription.CREATION)
|
|
152
|
+ content = api.get_content_in_context(content)
|
|
153
|
+ return content
|
|
154
|
+
|
|
155
|
+ @hapic.with_api_doc()
|
|
156
|
+ @hapic.handle_exception(NotAuthentificated, HTTPStatus.UNAUTHORIZED)
|
|
157
|
+ @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
|
|
158
|
+ @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
|
|
159
|
+ @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
|
|
160
|
+ @hapic.input_path(WorkspaceAndContentIdPathSchema())
|
|
161
|
+ @hapic.input_body(ContentMoveSchema())
|
|
162
|
+ @hapic.output_body(NoContentSchema())
|
|
163
|
+ def move_content(
|
|
164
|
+ self,
|
|
165
|
+ context,
|
|
166
|
+ request: TracimRequest,
|
|
167
|
+ hapic_data=None,
|
|
168
|
+ ) -> typing.List[ContentInContext]:
|
|
169
|
+ """
|
|
170
|
+ move a content
|
|
171
|
+ """
|
|
172
|
+ app_config = request.registry.settings['CFG']
|
|
173
|
+ path_data = hapic_data.path
|
|
174
|
+ move_data = hapic_data.body
|
|
175
|
+ api = ContentApi(
|
|
176
|
+ current_user=request.current_user,
|
|
177
|
+ session=request.dbsession,
|
|
178
|
+ config=app_config,
|
|
179
|
+ )
|
|
180
|
+ content = api.get_one(
|
|
181
|
+ path_data.content_id,
|
|
182
|
+ content_type=ContentType.Any
|
|
183
|
+ )
|
|
184
|
+ new_parent = api.get_one(
|
|
185
|
+ move_data.new_parent_id, content_type=ContentType.Any
|
|
186
|
+ )
|
|
187
|
+ with new_revision(
|
|
188
|
+ session=request.dbsession,
|
|
189
|
+ tm=transaction.manager,
|
|
190
|
+ content=content
|
|
191
|
+ ):
|
|
192
|
+ api.move(content, new_parent=new_parent)
|
|
193
|
+ return
|
|
194
|
+
|
|
195
|
+ @hapic.with_api_doc()
|
|
196
|
+ @hapic.handle_exception(NotAuthentificated, HTTPStatus.UNAUTHORIZED)
|
|
197
|
+ @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
|
|
198
|
+ @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
|
|
199
|
+ @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
|
|
200
|
+ @hapic.input_path(WorkspaceAndContentIdPathSchema())
|
|
201
|
+ @hapic.output_body(NoContentSchema())
|
|
202
|
+ def delete_content(
|
|
203
|
+ self,
|
|
204
|
+ context,
|
|
205
|
+ request: TracimRequest,
|
|
206
|
+ hapic_data=None,
|
|
207
|
+ ) -> typing.List[ContentInContext]:
|
|
208
|
+ """
|
|
209
|
+ delete a content
|
|
210
|
+ """
|
|
211
|
+ app_config = request.registry.settings['CFG']
|
|
212
|
+ path_data = hapic_data.path
|
|
213
|
+ api = ContentApi(
|
|
214
|
+ current_user=request.current_user,
|
|
215
|
+ session=request.dbsession,
|
|
216
|
+ config=app_config,
|
|
217
|
+ )
|
|
218
|
+ content = api.get_one(
|
|
219
|
+ path_data.content_id,
|
|
220
|
+ content_type=ContentType.Any
|
|
221
|
+ )
|
|
222
|
+ with new_revision(
|
|
223
|
+ session=request.dbsession,
|
|
224
|
+ tm=transaction.manager,
|
|
225
|
+ content=content
|
|
226
|
+ ):
|
|
227
|
+ api.delete(content)
|
|
228
|
+ return
|
|
229
|
+
|
|
230
|
+ @hapic.with_api_doc()
|
|
231
|
+ @hapic.handle_exception(NotAuthentificated, HTTPStatus.UNAUTHORIZED)
|
|
232
|
+ @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
|
|
233
|
+ @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
|
|
234
|
+ @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
|
|
235
|
+ @hapic.input_path(WorkspaceAndContentIdPathSchema())
|
|
236
|
+ @hapic.output_body(NoContentSchema())
|
|
237
|
+ def undelete_content(
|
|
238
|
+ self,
|
|
239
|
+ context,
|
|
240
|
+ request: TracimRequest,
|
|
241
|
+ hapic_data=None,
|
|
242
|
+ ) -> typing.List[ContentInContext]:
|
|
243
|
+ """
|
|
244
|
+ undelete a content
|
|
245
|
+ """
|
|
246
|
+ app_config = request.registry.settings['CFG']
|
|
247
|
+ path_data = hapic_data.path
|
|
248
|
+ api = ContentApi(
|
|
249
|
+ current_user=request.current_user,
|
|
250
|
+ session=request.dbsession,
|
|
251
|
+ config=app_config,
|
|
252
|
+ show_deleted=True,
|
|
253
|
+ )
|
|
254
|
+ content = api.get_one(
|
|
255
|
+ path_data.content_id,
|
|
256
|
+ content_type=ContentType.Any
|
|
257
|
+ )
|
|
258
|
+ with new_revision(
|
|
259
|
+ session=request.dbsession,
|
|
260
|
+ tm=transaction.manager,
|
|
261
|
+ content=content
|
|
262
|
+ ):
|
|
263
|
+ api.undelete(content)
|
|
264
|
+ return
|
|
265
|
+
|
|
266
|
+ @hapic.with_api_doc()
|
|
267
|
+ @hapic.handle_exception(NotAuthentificated, HTTPStatus.UNAUTHORIZED)
|
|
268
|
+ @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
|
|
269
|
+ @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
|
|
270
|
+ @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
|
|
271
|
+ @hapic.input_path(WorkspaceAndContentIdPathSchema())
|
|
272
|
+ @hapic.output_body(NoContentSchema())
|
|
273
|
+ def archive_content(
|
|
274
|
+ self,
|
|
275
|
+ context,
|
|
276
|
+ request: TracimRequest,
|
|
277
|
+ hapic_data=None,
|
|
278
|
+ ) -> typing.List[ContentInContext]:
|
|
279
|
+ """
|
|
280
|
+ archive a content
|
|
281
|
+ """
|
|
282
|
+ app_config = request.registry.settings['CFG']
|
|
283
|
+ path_data = hapic_data.path
|
|
284
|
+ api = ContentApi(
|
|
285
|
+ current_user=request.current_user,
|
|
286
|
+ session=request.dbsession,
|
|
287
|
+ config=app_config,
|
|
288
|
+ )
|
|
289
|
+ content = api.get_one(path_data.content_id, content_type=ContentType.Any)
|
|
290
|
+ with new_revision(
|
|
291
|
+ session=request.dbsession,
|
|
292
|
+ tm=transaction.manager,
|
|
293
|
+ content=content
|
|
294
|
+ ):
|
|
295
|
+ api.archive(content)
|
|
296
|
+ return
|
|
297
|
+
|
|
298
|
+ @hapic.with_api_doc()
|
|
299
|
+ @hapic.handle_exception(NotAuthentificated, HTTPStatus.UNAUTHORIZED)
|
|
300
|
+ @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
|
|
301
|
+ @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
|
|
302
|
+ @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
|
|
303
|
+ @hapic.input_path(WorkspaceAndContentIdPathSchema())
|
|
304
|
+ @hapic.output_body(NoContentSchema())
|
|
305
|
+ def unarchive_content(
|
|
306
|
+ self,
|
|
307
|
+ context,
|
|
308
|
+ request: TracimRequest,
|
|
309
|
+ hapic_data=None,
|
|
310
|
+ ) -> typing.List[ContentInContext]:
|
|
311
|
+ """
|
|
312
|
+ unarchive a content
|
|
313
|
+ """
|
|
314
|
+ app_config = request.registry.settings['CFG']
|
|
315
|
+ path_data = hapic_data.path
|
|
316
|
+ api = ContentApi(
|
|
317
|
+ current_user=request.current_user,
|
|
318
|
+ session=request.dbsession,
|
|
319
|
+ config=app_config,
|
|
320
|
+ show_archived=True,
|
|
321
|
+ )
|
|
322
|
+ content = api.get_one(
|
|
323
|
+ path_data.content_id,
|
|
324
|
+ content_type=ContentType.Any
|
|
325
|
+ )
|
|
326
|
+ with new_revision(
|
|
327
|
+ session=request.dbsession,
|
|
328
|
+ tm=transaction.manager,
|
|
329
|
+ content=content
|
|
330
|
+ ):
|
|
331
|
+ api.unarchive(content)
|
|
332
|
+ return
|
|
333
|
+
|
116
|
334
|
def bind(self, configurator: Configurator) -> None:
|
117
|
335
|
"""
|
118
|
336
|
Create all routes and views using
|
|
@@ -127,4 +345,20 @@ class WorkspaceController(Controller):
|
127
|
345
|
configurator.add_view(self.workspaces_members, route_name='workspace_members') # nopep8
|
128
|
346
|
# Workspace Content
|
129
|
347
|
configurator.add_route('workspace_content', '/workspaces/{workspace_id}/contents', request_method='GET') # nopep8
|
130
|
|
- configurator.add_view(self.workspace_content, route_name='workspace_content') # nopep8
|
|
348
|
+ configurator.add_view(self.workspace_content, route_name='workspace_content') # nopep8
|
|
349
|
+ # Create Generic Content
|
|
350
|
+ configurator.add_route('create_generic_content', '/workspaces/{workspace_id}/contents', request_method='POST') # nopep8
|
|
351
|
+ configurator.add_view(self.create_generic_empty_content, route_name='create_generic_content') # nopep8
|
|
352
|
+ # Move Content
|
|
353
|
+ configurator.add_route('move_content', '/workspaces/{workspace_id}/contents/{content_id}/move', request_method='PUT') # nopep8
|
|
354
|
+ configurator.add_view(self.move_content, route_name='move_content') # nopep8
|
|
355
|
+ # Delete/Undelete Content
|
|
356
|
+ configurator.add_route('delete_content', '/workspaces/{workspace_id}/contents/{content_id}/delete', request_method='PUT') # nopep8
|
|
357
|
+ configurator.add_view(self.delete_content, route_name='delete_content') # nopep8
|
|
358
|
+ configurator.add_route('undelete_content', '/workspaces/{workspace_id}/contents/{content_id}/undelete', request_method='PUT') # nopep8
|
|
359
|
+ configurator.add_view(self.undelete_content, route_name='undelete_content') # nopep8
|
|
360
|
+ # # Archive/Unarchive Content
|
|
361
|
+ configurator.add_route('archive_content', '/workspaces/{workspace_id}/contents/{content_id}/archive', request_method='PUT') # nopep8
|
|
362
|
+ configurator.add_view(self.archive_content, route_name='archive_content') # nopep8
|
|
363
|
+ configurator.add_route('unarchive_content', '/workspaces/{workspace_id}/contents/{content_id}/unarchive', request_method='PUT') # nopep8
|
|
364
|
+ configurator.add_view(self.unarchive_content, route_name='unarchive_content') # nopep8
|