浏览代码

Partial Support for get content in api (without full filter support)

Guénaël Muller 6 年前
父节点
当前提交
7034d8efd9

+ 12 - 1
tracim/lib/core/content.py 查看文件

9
 from sqlalchemy import func
9
 from sqlalchemy import func
10
 from sqlalchemy.orm import Query
10
 from sqlalchemy.orm import Query
11
 
11
 
12
+from tracim.models.context_models import ContentInContext
13
+
12
 __author__ = 'damien'
14
 __author__ = 'damien'
13
 
15
 
14
 import datetime
16
 import datetime
113
             show_archived: bool = False,
115
             show_archived: bool = False,
114
             show_deleted: bool = False,
116
             show_deleted: bool = False,
115
             show_temporary: bool = False,
117
             show_temporary: bool = False,
118
+            show_active: bool = True,
116
             all_content_in_treeview: bool = True,
119
             all_content_in_treeview: bool = True,
117
             force_show_all_types: bool = False,
120
             force_show_all_types: bool = False,
118
             disable_user_workspaces_filter: bool = False,
121
             disable_user_workspaces_filter: bool = False,
124
         self._show_archived = show_archived
127
         self._show_archived = show_archived
125
         self._show_deleted = show_deleted
128
         self._show_deleted = show_deleted
126
         self._show_temporary = show_temporary
129
         self._show_temporary = show_temporary
130
+        self._show_active = show_active
127
         self._show_all_type_of_contents_in_treeview = all_content_in_treeview
131
         self._show_all_type_of_contents_in_treeview = all_content_in_treeview
128
         self._force_show_all_types = force_show_all_types
132
         self._force_show_all_types = force_show_all_types
129
         self._disable_user_workspaces_filter = disable_user_workspaces_filter
133
         self._disable_user_workspaces_filter = disable_user_workspaces_filter
156
             self._show_deleted = previous_show_deleted
160
             self._show_deleted = previous_show_deleted
157
             self._show_temporary = previous_show_temporary
161
             self._show_temporary = previous_show_temporary
158
 
162
 
163
+    def get_content_in_context(self, content: Content):
164
+        return ContentInContext(content, self._session, self._config)
165
+
159
     def get_revision_join(self) -> sqlalchemy.sql.elements.BooleanClauseList:
166
     def get_revision_join(self) -> sqlalchemy.sql.elements.BooleanClauseList:
160
         """
167
         """
161
         Return the Content/ContentRevision query join condition
168
         Return the Content/ContentRevision query join condition
243
         if not self._show_temporary:
250
         if not self._show_temporary:
244
             result = result.filter(Content.is_temporary==False)
251
             result = result.filter(Content.is_temporary==False)
245
 
252
 
253
+        if not self._show_active:
254
+            result = result.filter(Content.is_active==False)
255
+
246
         return result
256
         return result
247
 
257
 
248
     def __revisions_real_base_query(
258
     def __revisions_real_base_query(
686
 
696
 
687
         if parent_id:
697
         if parent_id:
688
             resultset = resultset.filter(Content.parent_id==parent_id)
698
             resultset = resultset.filter(Content.parent_id==parent_id)
689
-        if parent_id is False:
699
+        if parent_id == 0 or parent_id is False:
690
             resultset = resultset.filter(Content.parent_id == None)
700
             resultset = resultset.filter(Content.parent_id == None)
701
+        # parent_id == None give all contents
691
 
702
 
692
         return resultset.all()
703
         return resultset.all()
693
 
704
 

+ 12 - 2
tracim/models/contents.py 查看文件

87
     def __init__(self, slug: str):
87
     def __init__(self, slug: str):
88
         for status in CONTENT_DEFAULT_STATUS:
88
         for status in CONTENT_DEFAULT_STATUS:
89
             if slug == status.slug:
89
             if slug == status.slug:
90
-                super(ContentStatusLegacy).__init__(
90
+                super(ContentStatusLegacy, self).__init__(
91
                     slug=status.slug,
91
                     slug=status.slug,
92
                     global_status=status.global_status,
92
                     global_status=status.global_status,
93
                     label=status.label,
93
                     label=status.label,
168
     available_statuses=CONTENT_DEFAULT_STATUS,
168
     available_statuses=CONTENT_DEFAULT_STATUS,
169
 )
169
 )
170
 
170
 
171
+# TODO - G.M - 31-05-2018 - Set Better folder params
172
+folder_type = NewContentType(
173
+    slug='folder',
174
+    icon=thread.icon,
175
+    hexcolor=thread.hexcolor,
176
+    label='Folder',
177
+    creation_label='Create collection of any documents',
178
+    available_statuses=CONTENT_DEFAULT_STATUS,
179
+)
171
 
180
 
172
 CONTENT_DEFAULT_TYPE = [
181
 CONTENT_DEFAULT_TYPE = [
173
     thread_type,
182
     thread_type,
174
     file_type,
183
     file_type,
175
     pagemarkdownplus_type,
184
     pagemarkdownplus_type,
176
     pagehtml_type,
185
     pagehtml_type,
186
+    folder_type,
177
 ]
187
 ]
178
 
188
 
179
 
189
 
195
     def __init__(self, slug: str):
205
     def __init__(self, slug: str):
196
         for content_type in CONTENT_DEFAULT_TYPE:
206
         for content_type in CONTENT_DEFAULT_TYPE:
197
             if slug == content_type.slug:
207
             if slug == content_type.slug:
198
-                super(ContentTypeLegacy).__init__(
208
+                super(ContentTypeLegacy, self).__init__(
199
                     slug=content_type.slug,
209
                     slug=content_type.slug,
200
                     icon=content_type.icon,
210
                     icon=content_type.icon,
201
                     hexcolor=content_type.hexcolor,
211
                     hexcolor=content_type.hexcolor,

+ 80 - 1
tracim/models/context_models.py 查看文件

7
 from tracim import CFG
7
 from tracim import CFG
8
 from tracim.models import User
8
 from tracim.models import User
9
 from tracim.models.auth import Profile
9
 from tracim.models.auth import Profile
10
-from tracim.models.data import Workspace, UserRoleInWorkspace
10
+from tracim.models.data import Workspace, UserRoleInWorkspace, Content
11
 from tracim.models.workspace_menu_entries import default_workspace_menu_entry, \
11
 from tracim.models.workspace_menu_entries import default_workspace_menu_entry, \
12
     WorkspaceMenuEntry
12
     WorkspaceMenuEntry
13
 
13
 
22
         self.password = password
22
         self.password = password
23
 
23
 
24
 
24
 
25
+class ContentFilter(object):
26
+    """
27
+    Content filter model
28
+    """
29
+    def __init__(self,
30
+                 parent_id: int = None,
31
+                 show_archived: int = 0,
32
+                 show_deleted: int = 0,
33
+                 show_active: int = 1,
34
+                 ):
35
+        self.parent_id = parent_id
36
+        self.show_archived = bool(show_archived)
37
+        self.show_deleted = bool(show_deleted)
38
+        self.show_active = bool(show_active)
39
+
25
 class UserInContext(object):
40
 class UserInContext(object):
26
     """
41
     """
27
     Interface to get User data and User data related to context.
42
     Interface to get User data and User data related to context.
211
             self.dbsession,
226
             self.dbsession,
212
             self.config
227
             self.config
213
         )
228
         )
229
+
230
+
231
+class ContentInContext(object):
232
+    """
233
+    Interface to get Content data and Content data related to context.
234
+    """
235
+
236
+    def __init__(self, content: Content, dbsession: Session, config: CFG):
237
+        self.content = content
238
+        self.dbsession = dbsession
239
+        self.config = config
240
+
241
+    # Default
242
+
243
+    @property
244
+    def id(self) -> int:
245
+        return self.content.content_id
246
+
247
+    @property
248
+    def parent_id(self) -> int:
249
+        return self.content.parent_id
250
+
251
+    @property
252
+    def workspace_id(self) -> int:
253
+        return self.content.workspace_id
254
+
255
+    @property
256
+    def label(self) -> str:
257
+        return self.content.label
258
+
259
+    @property
260
+    def content_type_slug(self) -> str:
261
+        return self.content.type
262
+
263
+    @property
264
+    def sub_content_type_slug(self) -> typing.List[str]:
265
+        return [type.slug for type in self.content.get_allowed_content_types()]
266
+
267
+    @property
268
+    def status_slug(self) -> str:
269
+        return self.content.status
270
+
271
+    @property
272
+    def is_archived(self):
273
+        return self.content.is_archived
274
+
275
+    @property
276
+    def is_deleted(self):
277
+        return self.content.is_deleted
278
+
279
+    # Context-related
280
+
281
+    @property
282
+    def show_in_ui(self):
283
+        # TODO - G.M - 31-05-2018 - Enable Show_in_ui params
284
+        # if false, then do not show content in the treeview.
285
+        # This may his maybe used for specific contents or for sub-contents.
286
+        # Default is True.
287
+        # In first version of the API, this field is always True
288
+        return True
289
+
290
+    @property
291
+    def slug(self):
292
+        return slugify(self.content.label)

+ 4 - 0
tracim/models/data.py 查看文件

1148
         return not self.is_archived and not self.is_deleted
1148
         return not self.is_archived and not self.is_deleted
1149
 
1149
 
1150
     @property
1150
     @property
1151
+    def is_active(self) -> bool:
1152
+        return self.is_editable
1153
+
1154
+    @property
1151
     def depot_file(self) -> UploadedFile:
1155
     def depot_file(self) -> UploadedFile:
1152
         return self.revision.depot_file
1156
         return self.revision.depot_file
1153
 
1157
 

+ 1 - 1
tracim/tests/functional/test_system.py 查看文件

131
         assert content_type['creation_label'] == 'Write a document'
131
         assert content_type['creation_label'] == 'Write a document'
132
         assert 'available_statuses' in content_type
132
         assert 'available_statuses' in content_type
133
         assert len(content_type['available_statuses']) == 4
133
         assert len(content_type['available_statuses']) == 4
134
-
134
+        # TODO - G.M - 31-05-2018 - Check Folder type
135
         # TODO - G.M - 29-05-2018 - Better check for available_statuses
135
         # TODO - G.M - 29-05-2018 - Better check for available_statuses
136
 
136
 
137
     def test_api__get_content_types__err_401__unregistered_user(self):
137
     def test_api__get_content_types__err_401__unregistered_user(self):

+ 79 - 12
tracim/tests/functional/test_workspaces.py 查看文件

219
         assert 'details' in res.json.keys()
219
         assert 'details' in res.json.keys()
220
 
220
 
221
 
221
 
222
-@pytest.mark.xfail()
223
 class TestWorkspaceContents(FunctionalTest):
222
 class TestWorkspaceContents(FunctionalTest):
224
     """
223
     """
225
     Tests for /api/v2/workspaces/{workspace_id}/contents endpoint
224
     Tests for /api/v2/workspaces/{workspace_id}/contents endpoint
240
         )
239
         )
241
         res = self.testapp.get('/api/v2/workspaces/1/contents', status=200).json_body   # nopep8
240
         res = self.testapp.get('/api/v2/workspaces/1/contents', status=200).json_body   # nopep8
242
         # TODO - G.M - 30-05-2018 - Check this test
241
         # TODO - G.M - 30-05-2018 - Check this test
243
-        raise NotImplementedError()
242
+        assert len(res) == 3
243
+        content = res[0]
244
+        assert content['id'] == 1
245
+        assert content['is_archived'] is False
246
+        assert content['is_deleted'] is False
247
+        assert content['label'] == 'Tools'
248
+        assert content['parent_id'] is None
249
+        assert content['show_in_ui'] == True
250
+        assert content['slug'] == 'tools'
251
+        assert content['status_slug'] == 'open'
252
+        assert set(content['sub_content_type_slug']) == set(['thread', 'page', 'folder', 'file'])
253
+        assert content['workspace_id'] == 1
254
+        content = res[1]
255
+        assert content['id'] == 2
256
+        assert content['is_archived'] is False
257
+        assert content['is_deleted'] is False
258
+        assert content['label'] == 'Menus'
259
+        assert content['parent_id'] is None
260
+        assert content['show_in_ui'] == True
261
+        assert content['slug'] == 'menus'
262
+        assert content['status_slug'] == 'open'
263
+        assert set(content['sub_content_type_slug']) == set(['thread', 'page', 'folder', 'file'])
264
+        assert content['workspace_id'] == 1
265
+        content = res[2]
266
+        assert content['id'] == 11
267
+        assert content['is_archived'] is False
268
+        assert content['is_deleted'] is False
269
+        assert content['label'] == 'Current Menu'
270
+        assert content['parent_id'] == 2
271
+        assert content['show_in_ui'] == True
272
+        assert content['slug'] == 'current-menu'
273
+        assert content['status_slug'] == 'open'
274
+        assert set(content['sub_content_type_slug']) == set(['thread', 'page', 'folder', 'file'])
275
+        assert content['workspace_id'] == 1
244
 
276
 
245
     # Root related
277
     # Root related
246
 
278
 
267
             params=params,
299
             params=params,
268
         ).json_body  # nopep8
300
         ).json_body  # nopep8
269
         # TODO - G.M - 30-05-2018 - Check this test
301
         # TODO - G.M - 30-05-2018 - Check this test
270
-        raise NotImplementedError()
271
-
302
+        assert len(res) == 2
303
+        content = res[0]
304
+        assert content['id'] == 1
305
+        assert content['is_archived'] is False
306
+        assert content['is_deleted'] is False
307
+        assert content['label'] == 'Tools'
308
+        assert content['parent_id'] is None
309
+        assert content['show_in_ui'] == True
310
+        assert content['slug'] == 'tools'
311
+        assert content['status_slug'] == 'open'
312
+        assert set(content['sub_content_type_slug']) == set(['thread', 'page', 'folder', 'file'])
313
+        assert content['workspace_id'] == 1
314
+        content = res[1]
315
+        assert content['id'] == 2
316
+        assert content['is_archived'] is False
317
+        assert content['is_deleted'] is False
318
+        assert content['label'] == 'Menus'
319
+        assert content['parent_id'] is None
320
+        assert content['show_in_ui'] == True
321
+        assert content['slug'] == 'menus'
322
+        assert content['status_slug'] == 'open'
323
+        assert set(content['sub_content_type_slug']) == set(['thread', 'page', 'folder', 'file'])
324
+        assert content['workspace_id'] == 1
325
+
326
+    @pytest.mark.xfail()
272
     def test_api__get_workspace_content__ok_200__get_only_active_root_content(self):
327
     def test_api__get_workspace_content__ok_200__get_only_active_root_content(self):
273
         """
328
         """
274
         Check obtain workspace root active contents
329
         Check obtain workspace root active contents
294
         # TODO - G.M - 30-05-2018 - Check this test
349
         # TODO - G.M - 30-05-2018 - Check this test
295
         raise NotImplementedError()
350
         raise NotImplementedError()
296
 
351
 
352
+    @pytest.mark.xfail()
297
     def test_api__get_workspace_content__ok_200__get_only_archived_root_content(self):
353
     def test_api__get_workspace_content__ok_200__get_only_archived_root_content(self):
298
         """
354
         """
299
         Check obtain workspace root archived contents
355
         Check obtain workspace root archived contents
319
         # TODO - G.M - 30-05-2018 - Check this test
375
         # TODO - G.M - 30-05-2018 - Check this test
320
         raise NotImplementedError()
376
         raise NotImplementedError()
321
 
377
 
378
+    @pytest.mark.xfail()
322
     def test_api__get_workspace_content__ok_200__get_only_deleted_root_content(self):
379
     def test_api__get_workspace_content__ok_200__get_only_deleted_root_content(self):
323
         """
380
         """
324
          Check obtain workspace root deleted contents
381
          Check obtain workspace root deleted contents
368
             params=params,
425
             params=params,
369
         ).json_body  # nopep8
426
         ).json_body  # nopep8
370
         # TODO - G.M - 30-05-2018 - Check this test
427
         # TODO - G.M - 30-05-2018 - Check this test
371
-        raise NotImplementedError()
428
+        assert res == []
372
 
429
 
373
     # Folder related
430
     # Folder related
374
 
431
 
395
             params=params,
452
             params=params,
396
         ).json_body   # nopep8
453
         ).json_body   # nopep8
397
         # TODO - G.M - 30-05-2018 - Check this test
454
         # TODO - G.M - 30-05-2018 - Check this test
398
-        raise NotImplementedError()
399
-
455
+        assert len(res) == 1
456
+        content = res[0]
457
+        assert content['id'] == 11
458
+        assert content['is_archived'] is False
459
+        assert content['is_deleted'] is False
460
+        assert content['label'] == 'Current Menu'
461
+        assert content['parent_id'] == 2
462
+        assert content['show_in_ui'] == True
463
+        assert content['slug'] == 'current-menu'
464
+        assert content['status_slug'] == 'open'
465
+        assert set(content['sub_content_type_slug']) == set(['thread', 'page', 'folder', 'file'])
466
+        assert content['workspace_id'] == 1
467
+
468
+    @pytest.mark.xfail()
400
     def test_api__get_workspace_content__ok_200__get_only_active_folder_content(self):
469
     def test_api__get_workspace_content__ok_200__get_only_active_folder_content(self):
401
         """
470
         """
402
          Check obtain workspace folder active contents
471
          Check obtain workspace folder active contents
422
         # TODO - G.M - 30-05-2018 - Check this test
491
         # TODO - G.M - 30-05-2018 - Check this test
423
         raise NotImplementedError()
492
         raise NotImplementedError()
424
 
493
 
494
+    @pytest.mark.xfail()
425
     def test_api__get_workspace_content__ok_200__get_only_archived_folder_content(self):
495
     def test_api__get_workspace_content__ok_200__get_only_archived_folder_content(self):
426
         """
496
         """
427
          Check obtain workspace folder archived contents
497
          Check obtain workspace folder archived contents
447
         # TODO - G.M - 30-05-2018 - Check this test
517
         # TODO - G.M - 30-05-2018 - Check this test
448
         raise NotImplementedError()
518
         raise NotImplementedError()
449
 
519
 
520
+    @pytest.mark.xfail()
450
     def test_api__get_workspace_content__ok_200__get_only_deleted_folder_content(self):
521
     def test_api__get_workspace_content__ok_200__get_only_deleted_folder_content(self):
451
         """
522
         """
452
          Check obtain workspace folder deleted contents
523
          Check obtain workspace folder deleted contents
496
             params=params,
567
             params=params,
497
         ).json_body   # nopep8
568
         ).json_body   # nopep8
498
         # TODO - G.M - 30-05-2018 - Check this test
569
         # TODO - G.M - 30-05-2018 - Check this test
499
-        raise NotImplementedError()
570
+        assert res == []
500
 
571
 
501
     # Error case
572
     # Error case
502
 
573
 
552
         assert 'code' in res.json.keys()
623
         assert 'code' in res.json.keys()
553
         assert 'message' in res.json.keys()
624
         assert 'message' in res.json.keys()
554
         assert 'details' in res.json.keys()
625
         assert 'details' in res.json.keys()
555
-
556
-    def test_api_get_workspace_content__err_404__parent_id_does_not_exist(self):
557
-        # TODO - G.M - 30-05-2018 - Check this test
558
-        raise NotImplementedError()

+ 7 - 7
tracim/views/core_api/schemas.py 查看文件

5
 
5
 
6
 from tracim.models.auth import Profile
6
 from tracim.models.auth import Profile
7
 from tracim.models.contents import CONTENT_DEFAULT_TYPE, GlobalStatus, CONTENT_DEFAULT_STATUS
7
 from tracim.models.contents import CONTENT_DEFAULT_TYPE, GlobalStatus, CONTENT_DEFAULT_STATUS
8
-from tracim.models.context_models import LoginCredentials
8
+from tracim.models.context_models import LoginCredentials, ContentFilter
9
 from tracim.models.data import UserRoleInWorkspace
9
 from tracim.models.data import UserRoleInWorkspace
10
 
10
 
11
 
11
 
85
     pass
85
     pass
86
 
86
 
87
 
87
 
88
-class FilterContentPathSchema(marshmallow.Schema):
89
-    workspace_id = marshmallow.fields.Int(example=4, required=True)
88
+class FilterContentQuerySchema(marshmallow.Schema):
90
     parent_id = workspace_id = marshmallow.fields.Int(
89
     parent_id = workspace_id = marshmallow.fields.Int(
91
         example=2,
90
         example=2,
92
         default=None,
91
         default=None,
118
                     'The reason for this parameter to exist is for example '
117
                     'The reason for this parameter to exist is for example '
119
                     'to allow to show only archived documents'
118
                     'to allow to show only archived documents'
120
     )
119
     )
121
-
120
+    @post_load
121
+    def make_content_filter(self, data):
122
+        return ContentFilter(**data)
122
 ###
123
 ###
123
 
124
 
124
 
125
 
325
         example='htmlpage',
326
         example='htmlpage',
326
         validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
327
         validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
327
     )
328
     )
328
-    sub_content_type_slug = marshmallow.fields.Nested(
329
-        ContentTypeSchema(only=('slug',)),
330
-        many=True,
329
+    sub_content_type_slug = marshmallow.fields.List(
330
+        marshmallow.fields.Str,
331
         description='list of content types allowed as sub contents. '
331
         description='list of content types allowed as sub contents. '
332
                     'This field is required for folder contents, '
332
                     'This field is required for folder contents, '
333
                     'set it to empty list in other cases'
333
                     'set it to empty list in other cases'

+ 47 - 3
tracim/views/core_api/workspace_controller.py 查看文件

3
 from pyramid.config import Configurator
3
 from pyramid.config import Configurator
4
 from sqlalchemy.orm.exc import NoResultFound
4
 from sqlalchemy.orm.exc import NoResultFound
5
 
5
 
6
+from tracim.lib.core.content import ContentApi
6
 from tracim.lib.core.userworkspace import RoleApi
7
 from tracim.lib.core.userworkspace import RoleApi
7
 from tracim.lib.utils.authorization import require_workspace_role
8
 from tracim.lib.utils.authorization import require_workspace_role
8
 from tracim.models.context_models import WorkspaceInContext, \
9
 from tracim.models.context_models import WorkspaceInContext, \
9
-    UserRoleWorkspaceInContext
10
+    UserRoleWorkspaceInContext, ContentInContext
10
 from tracim.models.data import UserRoleInWorkspace
11
 from tracim.models.data import UserRoleInWorkspace
11
 
12
 
12
 try:  # Python 3.5+
13
 try:  # Python 3.5+
21
 from tracim.lib.core.workspace import WorkspaceApi
22
 from tracim.lib.core.workspace import WorkspaceApi
22
 from tracim.views.controllers import Controller
23
 from tracim.views.controllers import Controller
23
 from tracim.views.core_api.schemas import WorkspaceSchema, UserSchema, \
24
 from tracim.views.core_api.schemas import WorkspaceSchema, UserSchema, \
24
-    WorkspaceIdPathSchema, WorkspaceMemberSchema
25
+    WorkspaceIdPathSchema, WorkspaceMemberSchema, \
26
+    WorkspaceAndContentIdPathSchema, FilterContentQuerySchema, \
27
+    ContentDigestSchema
25
 
28
 
26
 
29
 
27
 class WorkspaceController(Controller):
30
 class WorkspaceController(Controller):
73
             for user_role in rapi.get_all_for_workspace(request.current_workspace)
76
             for user_role in rapi.get_all_for_workspace(request.current_workspace)
74
         ]
77
         ]
75
 
78
 
79
+    @hapic.with_api_doc()
80
+    @hapic.handle_exception(NotAuthentificated, HTTPStatus.UNAUTHORIZED)
81
+    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
82
+    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
83
+    @require_workspace_role(UserRoleInWorkspace.READER)
84
+    @hapic.input_path(WorkspaceIdPathSchema())
85
+    @hapic.input_query(FilterContentQuerySchema())
86
+    @hapic.output_body(ContentDigestSchema(many=True))
87
+    def workspace_content(
88
+            self,
89
+            context,
90
+            request: TracimRequest,
91
+            hapic_data=None,
92
+    ) -> typing.List[ContentInContext]:
93
+        """
94
+        return list of contents found in the workspace
95
+        """
96
+        #hapic_data.query=
97
+        app_config = request.registry.settings['CFG']
98
+        content_filter = hapic_data.query
99
+        api = ContentApi(
100
+            current_user=request.current_user,
101
+            session=request.dbsession,
102
+            config=app_config,
103
+            show_archived=content_filter.show_archived,
104
+            show_deleted=content_filter.show_deleted,
105
+            show_active=content_filter.show_active,
106
+        )
107
+        contents = api.get_all(
108
+            parent_id=content_filter.parent_id,
109
+            workspace=request.current_workspace,
110
+        )
111
+        contents = [
112
+            api.get_content_in_context(content) for content in contents
113
+        ]
114
+        return contents
115
+
76
     def bind(self, configurator: Configurator) -> None:
116
     def bind(self, configurator: Configurator) -> None:
77
         """
117
         """
78
         Create all routes and views using pyramid configurator
118
         Create all routes and views using pyramid configurator
79
         for this controller
119
         for this controller
80
         """
120
         """
81
 
121
 
82
-        # Applications
122
+        # Workspace
83
         configurator.add_route('workspace', '/workspaces/{workspace_id}', request_method='GET')  # nopep8
123
         configurator.add_route('workspace', '/workspaces/{workspace_id}', request_method='GET')  # nopep8
84
         configurator.add_view(self.workspace, route_name='workspace')
124
         configurator.add_view(self.workspace, route_name='workspace')
125
+        # Workspace Members (Roles)
85
         configurator.add_route('workspace_members', '/workspaces/{workspace_id}/members', request_method='GET')  # nopep8
126
         configurator.add_route('workspace_members', '/workspaces/{workspace_id}/members', request_method='GET')  # nopep8
86
         configurator.add_view(self.workspaces_members, route_name='workspace_members')  # nopep8
127
         configurator.add_view(self.workspaces_members, route_name='workspace_members')  # nopep8
128
+        # Workspace Content
129
+        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