Browse Source

Merge branch 'develop' of github.com:tracim/tracim_backend into feature/611_workspace_and_workspace_member_action_endpoints

Guénaël Muller 6 years ago
parent
commit
c542931ea4

+ 4 - 0
tracim/exceptions.py View File

174
 
174
 
175
 class UserCreationFailed(TracimException):
175
 class UserCreationFailed(TracimException):
176
     pass
176
     pass
177
+
178
+
179
+class ParentNotFound(NotFound):
180
+    pass

+ 50 - 8
tracim/lib/webdav/dav_provider.py View File

70
 
70
 
71
         # If the requested path is the root, then we return a RootResource resource
71
         # If the requested path is the root, then we return a RootResource resource
72
         if path == root_path:
72
         if path == root_path:
73
-            return resources.RootResource(path, environ, user=user, session=session)
73
+            return resources.RootResource(
74
+                path=path,
75
+                environ=environ,
76
+                user=user,
77
+                session=session
78
+            )
74
 
79
 
75
         workspace_api = WorkspaceApi(
80
         workspace_api = WorkspaceApi(
76
             current_user=user,
81
             current_user=user,
111
 
116
 
112
         # Easy cases : path either end with /.deleted, /.archived or /.history, then we return corresponding resources
117
         # Easy cases : path either end with /.deleted, /.archived or /.history, then we return corresponding resources
113
         if path.endswith(SpecialFolderExtension.Archived) and self._show_archive:
118
         if path.endswith(SpecialFolderExtension.Archived) and self._show_archive:
114
-            return resources.ArchivedFolderResource(path, environ, workspace, content)
119
+            return resources.ArchivedFolderResource(
120
+                path=path,
121
+                environ=environ,
122
+                workspace=workspace,
123
+                user=user,
124
+                content=content,
125
+                session=session,
126
+            )
115
 
127
 
116
         if path.endswith(SpecialFolderExtension.Deleted) and self._show_delete:
128
         if path.endswith(SpecialFolderExtension.Deleted) and self._show_delete:
117
-            return resources.DeletedFolderResource(path, environ, workspace, content)
129
+            return resources.DeletedFolderResource(
130
+                path=path,
131
+                environ=environ,
132
+                workspace=workspace,
133
+                user=user,
134
+                content=content,
135
+                session=session,
136
+            )
118
 
137
 
119
         if path.endswith(SpecialFolderExtension.History) and self._show_history:
138
         if path.endswith(SpecialFolderExtension.History) and self._show_history:
120
             is_deleted_folder = re.search(r'/\.deleted/\.history$', path) is not None
139
             is_deleted_folder = re.search(r'/\.deleted/\.history$', path) is not None
124
                 else HistoryType.Archived if is_archived_folder \
143
                 else HistoryType.Archived if is_archived_folder \
125
                 else HistoryType.Standard
144
                 else HistoryType.Standard
126
 
145
 
127
-            return resources.HistoryFolderResource(path, environ, workspace, content, type)
146
+            return resources.HistoryFolderResource(
147
+                path=path,
148
+                environ=environ,
149
+                workspace=workspace,
150
+                user=user,
151
+                content=content,
152
+                session=session,
153
+                type=type
154
+            )
128
 
155
 
129
         # Now that's more complicated, we're trying to find out if the path end with /.history/file_name
156
         # Now that's more complicated, we're trying to find out if the path end with /.history/file_name
130
         is_history_file_folder = re.search(r'/\.history/([^/]+)$', path) is not None
157
         is_history_file_folder = re.search(r'/\.history/([^/]+)$', path) is not None
133
             return resources.HistoryFileFolderResource(
160
             return resources.HistoryFileFolderResource(
134
                 path=path,
161
                 path=path,
135
                 environ=environ,
162
                 environ=environ,
136
-                content=content
163
+                user=user,
164
+                content=content,
165
+                session=session,
137
             )
166
             )
138
-
139
         # And here next step :
167
         # And here next step :
140
         is_history_file = re.search(r'/\.history/[^/]+/\((\d+) - [a-zA-Z]+\) .+', path) is not None
168
         is_history_file = re.search(r'/\.history/[^/]+/\((\d+) - [a-zA-Z]+\) .+', path) is not None
141
 
169
 
147
             content = self.get_content_from_revision(content_revision, content_api)
175
             content = self.get_content_from_revision(content_revision, content_api)
148
 
176
 
149
             if content.type == ContentType.File:
177
             if content.type == ContentType.File:
150
-                return resources.HistoryFileResource(path, environ, content, content_revision)
178
+                return resources.HistoryFileResource(
179
+                    path=path,
180
+                    environ=environ,
181
+                    user=user,
182
+                    content=content,
183
+                    content_revision=content_revision,
184
+                    session=session,
185
+                )
151
             else:
186
             else:
152
-                return resources.HistoryOtherFile(path, environ, content, content_revision)
187
+                return resources.HistoryOtherFile(
188
+                    path=path,
189
+                    environ=environ,
190
+                    user=user,
191
+                    content=content,
192
+                    content_revision=content_revision,
193
+                    session=session,
194
+                )
153
 
195
 
154
         # And if we're still going, the client is asking for a standard Folder/File/Page/Thread so we check the type7
196
         # And if we're still going, the client is asking for a standard Folder/File/Page/Thread so we check the type7
155
         # and return the corresponding resource
197
         # and return the corresponding resource

+ 2 - 0
tracim/lib/webdav/resources.py View File

516
         workspace_api = WorkspaceApi(
516
         workspace_api = WorkspaceApi(
517
             current_user=self.user,
517
             current_user=self.user,
518
             session=self.session,
518
             session=self.session,
519
+            config=self.provider.app_config,
519
         )
520
         )
520
         workspace = self.provider.get_workspace_from_path(
521
         workspace = self.provider.get_workspace_from_path(
521
             normpath(destpath), workspace_api
522
             normpath(destpath), workspace_api
1308
         workspace_api = WorkspaceApi(
1309
         workspace_api = WorkspaceApi(
1309
             current_user=self.user,
1310
             current_user=self.user,
1310
             session=self.session,
1311
             session=self.session,
1312
+            config=self.provider.app_config,
1311
         )
1313
         )
1312
         content_api = ContentApi(
1314
         content_api = ContentApi(
1313
             current_user=self.user,
1315
             current_user=self.user,

+ 2 - 0
tracim/models/context_models.py View File

134
             self,
134
             self,
135
             label: str,
135
             label: str,
136
             content_type: str,
136
             content_type: str,
137
+            parent_id: typing.Optional[int] = None,
137
     ) -> None:
138
     ) -> None:
138
         self.label = label
139
         self.label = label
139
         self.content_type = content_type
140
         self.content_type = content_type
141
+        self.parent_id = parent_id
140
 
142
 
141
 
143
 
142
 class CommentCreation(object):
144
 class CommentCreation(object):

+ 43 - 0
tracim/tests/functional/test_workspaces.py View File

1219
         active_contents = self.testapp.get('/api/v2/workspaces/1/contents', params=params_active, status=200).json_body  # nopep8
1219
         active_contents = self.testapp.get('/api/v2/workspaces/1/contents', params=params_active, status=200).json_body  # nopep8
1220
         assert res.json_body in active_contents
1220
         assert res.json_body in active_contents
1221
 
1221
 
1222
+    def test_api__post_content_create_generic_content__ok_200__in_folder(self) -> None:  # nopep8
1223
+        """
1224
+        Create generic content in folder
1225
+        """
1226
+        self.testapp.authorization = (
1227
+            'Basic',
1228
+            (
1229
+                'admin@admin.admin',
1230
+                'admin@admin.admin'
1231
+            )
1232
+        )
1233
+        params = {
1234
+            'label': 'GenericCreatedContent',
1235
+            'content_type': 'markdownpage',
1236
+            'parent_id': 10,
1237
+        }
1238
+        res = self.testapp.post_json(
1239
+            '/api/v2/workspaces/1/contents',
1240
+            params=params,
1241
+            status=200
1242
+        )
1243
+        assert res
1244
+        assert res.json_body
1245
+        assert res.json_body['status'] == 'open'
1246
+        assert res.json_body['content_id']
1247
+        assert res.json_body['content_type'] == 'markdownpage'
1248
+        assert res.json_body['is_archived'] is False
1249
+        assert res.json_body['is_deleted'] is False
1250
+        assert res.json_body['workspace_id'] == 1
1251
+        assert res.json_body['slug'] == 'genericcreatedcontent'
1252
+        assert res.json_body['parent_id'] == 10
1253
+        assert res.json_body['show_in_ui'] is True
1254
+        assert res.json_body['sub_content_types']
1255
+        params_active = {
1256
+            'parent_id': 10,
1257
+            'show_archived': 0,
1258
+            'show_deleted': 0,
1259
+            'show_active': 1,
1260
+        }
1261
+        # INFO - G.M - 2018-06-165 - Verify if new content is correctly created
1262
+        active_contents = self.testapp.get('/api/v2/workspaces/1/contents', params=params_active, status=200).json_body  # nopep8
1263
+        assert res.json_body in active_contents
1264
+
1222
     def test_api__post_content_create_generic_content__err_400__empty_label(self) -> None:  # nopep8
1265
     def test_api__post_content_create_generic_content__err_400__empty_label(self) -> None:  # nopep8
1223
         """
1266
         """
1224
         Create generic content
1267
         Create generic content

+ 5 - 0
tracim/views/core_api/schemas.py View File

448
         example='html-documents',
448
         example='html-documents',
449
         validate=OneOf(ContentType.allowed_types_for_folding()),  # nopep8
449
         validate=OneOf(ContentType.allowed_types_for_folding()),  # nopep8
450
     )
450
     )
451
+    parent_id = marshmallow.fields.Integer(
452
+        example=35,
453
+        description='content_id of parent content, if content should be placed in a folder, this should be folder content_id.'
454
+    )
455
+
451
 
456
 
452
     @post_load
457
     @post_load
453
     def make_content_filter(self, data):
458
     def make_content_filter(self, data):

+ 12 - 1
tracim/views/core_api/workspace_controller.py View File

27
 from tracim.exceptions import EmailValidationFailed
27
 from tracim.exceptions import EmailValidationFailed
28
 from tracim.exceptions import UserCreationFailed
28
 from tracim.exceptions import UserCreationFailed
29
 from tracim.exceptions import UserDoesNotExist
29
 from tracim.exceptions import UserDoesNotExist
30
+from tracim.exceptions import ContentNotFound
30
 from tracim.exceptions import WorkspacesDoNotMatch
31
 from tracim.exceptions import WorkspacesDoNotMatch
32
+from tracim.exceptions import ParentNotFound
31
 from tracim.views.controllers import Controller
33
 from tracim.views.controllers import Controller
32
 from tracim.views.core_api.schemas import FilterContentQuerySchema
34
 from tracim.views.core_api.schemas import FilterContentQuerySchema
33
 from tracim.views.core_api.schemas import WorkspaceMemberCreationSchema
35
 from tracim.views.core_api.schemas import WorkspaceMemberCreationSchema
283
         api = ContentApi(
285
         api = ContentApi(
284
             current_user=request.current_user,
286
             current_user=request.current_user,
285
             session=request.dbsession,
287
             session=request.dbsession,
286
-            config=app_config,
288
+            config=app_config
287
         )
289
         )
290
+        parent = None
291
+        if creation_data.parent_id:
292
+            try:
293
+                parent = api.get_one(content_id=creation_data.parent_id, content_type=ContentType.Any)  # nopep8
294
+            except ContentNotFound as exc:
295
+                raise ParentNotFound(
296
+                    'Parent with content_id {} not found'.format(creation_data.parent_id)
297
+                ) from exc
288
         content = api.create(
298
         content = api.create(
289
             label=creation_data.label,
299
             label=creation_data.label,
290
             content_type=creation_data.content_type,
300
             content_type=creation_data.content_type,
291
             workspace=request.current_workspace,
301
             workspace=request.current_workspace,
302
+            parent=parent,
292
         )
303
         )
293
         api.save(content, ActionDescription.CREATION)
304
         api.save(content, ActionDescription.CREATION)
294
         content = api.get_content_in_context(content)
305
         content = api.get_content_in_context(content)