Browse Source

Add workspace endpoint + some fixes

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

+ 9 - 6
tracim/__init__.py View File

@@ -17,6 +17,7 @@ from tracim.views import BASE_API_V2
17 17
 from tracim.views.core_api.session_controller import SessionController
18 18
 from tracim.views.core_api.system_controller import SystemController
19 19
 from tracim.views.core_api.user_controller import UserController
20
+from tracim.views.core_api.workspace_controller import WorkspaceController
20 21
 from tracim.views.errors import ErrorSchema
21 22
 from tracim.lib.utils.cors import add_cors_support
22 23
 
@@ -58,12 +59,14 @@ def main(global_config, **settings):
58 59
         )
59 60
     )
60 61
     # Add controllers
61
-    session_api = SessionController()
62
-    system_api = SystemController()
63
-    user_api = UserController()
64
-    configurator.include(session_api.bind, route_prefix=BASE_API_V2)
65
-    configurator.include(system_api.bind, route_prefix=BASE_API_V2)
66
-    configurator.include(user_api.bind, route_prefix=BASE_API_V2)
62
+    session_controller = SessionController()
63
+    system_controller = SystemController()
64
+    user_controller = UserController()
65
+    workspace_controller = WorkspaceController()
66
+    configurator.include(session_controller.bind, route_prefix=BASE_API_V2)
67
+    configurator.include(system_controller.bind, route_prefix=BASE_API_V2)
68
+    configurator.include(user_controller.bind, route_prefix=BASE_API_V2)
69
+    configurator.include(workspace_controller.bind, route_prefix=BASE_API_V2)
67 70
     hapic.add_documentation_view(
68 71
         '/api/v2/doc',
69 72
         'Tracim v2 API',

+ 1 - 1
tracim/lib/core/userworkspace.py View File

@@ -4,7 +4,7 @@ import typing
4 4
 __author__ = 'damien'
5 5
 
6 6
 from sqlalchemy.orm import Session
7
-from tracim.models.auth import User
7
+from tracim.models.auth import User, Group
8 8
 from tracim.models.data import Workspace
9 9
 from tracim.models.data import UserRoleInWorkspace
10 10
 from tracim.models.data import RoleType

+ 70 - 1
tracim/models/context_models.py View File

@@ -7,7 +7,7 @@ from sqlalchemy.orm import Session
7 7
 from tracim import CFG
8 8
 from tracim.models import User
9 9
 from tracim.models.auth import Profile
10
-from tracim.models.data import Workspace
10
+from tracim.models.data import Workspace, UserRoleInWorkspace
11 11
 from tracim.models.workspace_menu_entries import default_workspace_menu_entry, \
12 12
     WorkspaceMenuEntry
13 13
 
@@ -135,3 +135,72 @@ class WorkspaceInContext(object):
135 135
         # list should be able to change (depending on activated/disabled
136 136
         # apps)
137 137
         return default_workspace_menu_entry(self.workspace)
138
+
139
+
140
+class UserRoleWorkspaceInContext(object):
141
+    """
142
+    Interface to get UserRoleInWorkspace data and related content
143
+
144
+    """
145
+    def __init__(
146
+            self,
147
+            user_role: UserRoleInWorkspace,
148
+            dbsession: Session,
149
+            config: CFG,
150
+    )-> None:
151
+        self.user_role = user_role
152
+        self.dbsession = dbsession
153
+        self.config = config
154
+
155
+    @property
156
+    def user_id(self) -> int:
157
+        """
158
+        User who has the role has this id
159
+        :return: user id as integer
160
+        """
161
+        return self.user_role.user_id
162
+
163
+    @property
164
+    def workspace_id(self) -> int:
165
+        """
166
+        This role apply only on the workspace with this workspace_id
167
+        :return: workspace id as integer
168
+        """
169
+        return self.user_role.workspace_id
170
+
171
+    # TODO - G.M - 23-05-2018 - Check the API spec for this this !
172
+
173
+    @property
174
+    def slug(self) -> str:
175
+        """
176
+        simple name of the role of the user.
177
+        can be anything from UserRoleInWorkspace SLUG, like
178
+        'not_applicable', 'reader',
179
+        'contributor', 'content_manager', 'workspace_manager'
180
+        :return: user workspace role as slug.
181
+        """
182
+        return UserRoleInWorkspace.SLUG[self.user_role.role]
183
+
184
+    @property
185
+    def user(self) -> UserInContext:
186
+        """
187
+        User who has this role, with context data
188
+        :return: UserInContext object
189
+        """
190
+        return UserInContext(
191
+            self.user_role.user,
192
+            self.dbsession,
193
+            self.config
194
+        )
195
+
196
+    @property
197
+    def workspace(self) -> WorkspaceInContext:
198
+        """
199
+        Workspace related to this role, with his context data
200
+        :return: WorkspaceInContext object
201
+        """
202
+        return WorkspaceInContext(
203
+            self.user_role.workspace,
204
+            self.dbsession,
205
+            self.config
206
+        )

+ 6 - 0
tracim/models/data.py View File

@@ -131,6 +131,12 @@ class UserRoleInWorkspace(DeclarativeBase):
131 131
     WORKSPACE_MANAGER = 8
132 132
 
133 133
     # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
134
+    SLUG = dict()
135
+    SLUG[NOT_APPLICABLE] = 'not_applicable'
136
+    SLUG[READER] = 'reader'
137
+    SLUG[CONTRIBUTOR] = 'contributor'
138
+    SLUG[CONTENT_MANAGER] = 'content_manager'
139
+    SLUG[WORKSPACE_MANAGER] = 'workspace_manager'
134 140
     # LABEL = dict()
135 141
     # LABEL[0] = l_('N/A')
136 142
     # LABEL[1] = l_('Reader')

+ 0 - 2
tracim/tests/functional/test_user.py View File

@@ -20,9 +20,7 @@ class TestUserWorkspaceEndpoint(FunctionalTest):
20 20
         res = res.json_body
21 21
         workspace = res[0]
22 22
         assert workspace['id'] == 1
23
-        assert workspace['slug'] == 'w1'
24 23
         assert workspace['label'] == 'w1'
25
-        assert workspace['description'] == 'This is a workspace'
26 24
         assert len(workspace['sidebar_entries']) == 7  # TODO change this
27 25
 
28 26
         sidebar_entry = workspace['sidebar_entries'][0]

+ 97 - 60
tracim/tests/functional/test_workspaces.py View File

@@ -1,10 +1,14 @@
1 1
 # coding=utf-8
2
-
2
+from tracim.models.data import UserRoleInWorkspace
3 3
 from tracim.tests import FunctionalTest
4
+from tracim.fixtures.content import Content as ContentFixtures
5
+from tracim.fixtures.users_and_groups import Base as BaseFixture
4 6
 
5 7
 
6 8
 class TestWorkspaceEndpoint(FunctionalTest):
7 9
 
10
+    fixtures = [BaseFixture, ContentFixtures]
11
+
8 12
     def test_api__get_workspace__ok_200__nominal_case(self):
9 13
         self.testapp.authorization = (
10 14
             'Basic',
@@ -13,34 +17,77 @@ class TestWorkspaceEndpoint(FunctionalTest):
13 17
                 'admin@admin.admin'
14 18
             )
15 19
         )
16
-        workspace = self.testapp.post_json('/api/v2/workspaces/1', status=200)
20
+        res = self.testapp.get('/api/v2/workspaces/1', status=200)
21
+        workspace = res.json_body
17 22
         assert workspace['id'] == 1
18 23
         assert workspace['slug'] == 'w1'
19 24
         assert workspace['label'] == 'w1'
20
-        assert workspace['description'] == 'Just another description'
21
-        assert len(workspace['sidebar_entries']) == 3  # TODO change this
25
+        assert workspace['description'] == 'This is a workspace'
26
+        assert len(workspace['sidebar_entries']) == 7  # TODO change this
22 27
 
23 28
         sidebar_entry = workspace['sidebar_entries'][0]
24
-        assert sidebar_entry['slug'] == 'markdown-pages'
25
-        assert sidebar_entry['label'] == 'Document Markdown'
26
-        assert sidebar_entry['route'] == "/#/workspace/{workspace_id}/contents/?type=mardown-page"  # nopep8
27
-        assert sidebar_entry['hexcolor'] == "#F0F9DC"
29
+        assert sidebar_entry['slug'] == 'dashboard'
30
+        assert sidebar_entry['label'] == 'Dashboard'
31
+        assert sidebar_entry['route'] == '/#/workspaces/1/dashboard'  # nopep8
32
+        assert sidebar_entry['hexcolor'] == "#252525"
33
+        assert sidebar_entry['icon'] == ""
34
+
35
+        sidebar_entry = workspace['sidebar_entries'][1]
36
+        assert sidebar_entry['slug'] == 'contents/all'
37
+        assert sidebar_entry['label'] == 'All Contents'
38
+        assert sidebar_entry['route'] == "/#/workspaces/1/contents"  # nopep8
39
+        assert sidebar_entry['hexcolor'] == "#fdfdfd"
40
+        assert sidebar_entry['icon'] == ""
41
+
42
+        sidebar_entry = workspace['sidebar_entries'][2]
43
+        assert sidebar_entry['slug'] == 'contents/pagehtml'
44
+        assert sidebar_entry['label'] == 'Text Documents'
45
+        assert sidebar_entry['route'] == '/#/workspaces/1/contents?type=pagehtml'  # nopep8
46
+        assert sidebar_entry['hexcolor'] == "#3f52e3"
28 47
         assert sidebar_entry['icon'] == "file-text-o"
29
-        # TODO To this for the other
30 48
 
31
-    def test_api__get_workspace__err_403__unallowed_user(self):
32
-        self.testapp.authorization = (
33
-            'Basic',
34
-            (
35
-                'lawrence-not-real-email@fsf.local',
36
-                'foobarbaz'
37
-            )
38
-        )
39
-        res = self.testapp.post_json('/api/v2/workspaces/1', status=403)
40
-        assert isinstance(res.json, dict)
41
-        assert 'code' in res.json.keys()
42
-        assert 'message' in res.json.keys()
43
-        assert 'details' in res.json.keys()
49
+        sidebar_entry = workspace['sidebar_entries'][3]
50
+        assert sidebar_entry['slug'] == 'contents/pagemarkdownplus'
51
+        assert sidebar_entry['label'] == 'Rich Markdown Files'
52
+        assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=pagemarkdownplus"    # nopep8
53
+        assert sidebar_entry['hexcolor'] == "#f12d2d"
54
+        assert sidebar_entry['icon'] == "file-code"
55
+
56
+        sidebar_entry = workspace['sidebar_entries'][4]
57
+        assert sidebar_entry['slug'] == 'contents/files'
58
+        assert sidebar_entry['label'] == 'Files'
59
+        assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
60
+        assert sidebar_entry['hexcolor'] == "#FF9900"
61
+        assert sidebar_entry['icon'] == "paperclip"
62
+
63
+        sidebar_entry = workspace['sidebar_entries'][5]
64
+        assert sidebar_entry['slug'] == 'contents/threads'
65
+        assert sidebar_entry['label'] == 'Threads'
66
+        assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
67
+        assert sidebar_entry['hexcolor'] == "#ad4cf9"
68
+        assert sidebar_entry['icon'] == "comments-o"
69
+
70
+        sidebar_entry = workspace['sidebar_entries'][6]
71
+        assert sidebar_entry['slug'] == 'calendar'
72
+        assert sidebar_entry['label'] == 'Calendar'
73
+        assert sidebar_entry['route'] == "/#/workspaces/1/calendar"  # nopep8
74
+        assert sidebar_entry['hexcolor'] == "#757575"
75
+        assert sidebar_entry['icon'] == "calendar-alt"
76
+
77
+    # TODO - G.M - 22-05-2018 - Check if this feature is needed
78
+    # def test_api__get_workspace__err_403__unallowed_user(self):
79
+    #     self.testapp.authorization = (
80
+    #         'Basic',
81
+    #         (
82
+    #             'lawrence-not-real-email@fsf.local',
83
+    #             'foobarbaz'
84
+    #         )
85
+    #     )
86
+    #     res = self.testapp.get('/api/v2/workspaces/1', status=403)
87
+    #     assert isinstance(res.json, dict)
88
+    #     assert 'code' in res.json.keys()
89
+    #     assert 'message' in res.json.keys()
90
+    #     assert 'details' in res.json.keys()
44 91
 
45 92
     def test_api__get_workspace__err_401__unregistered_user(self):
46 93
         self.testapp.authorization = (
@@ -50,7 +97,7 @@ class TestWorkspaceEndpoint(FunctionalTest):
50 97
                 'lapin'
51 98
             )
52 99
         )
53
-        res = self.testapp.post_json('/api/v2/workspaces/1', status=401)
100
+        res = self.testapp.get('/api/v2/workspaces/1', status=401)
54 101
         assert isinstance(res.json, dict)
55 102
         assert 'code' in res.json.keys()
56 103
         assert 'message' in res.json.keys()
@@ -64,7 +111,7 @@ class TestWorkspaceEndpoint(FunctionalTest):
64 111
                 'admin@admin.admin'
65 112
             )
66 113
         )
67
-        res = self.testapp.post_json('/api/v2/workspaces/5', status=404)
114
+        res = self.testapp.get('/api/v2/workspaces/5', status=404)
68 115
         assert isinstance(res.json, dict)
69 116
         assert 'code' in res.json.keys()
70 117
         assert 'message' in res.json.keys()
@@ -73,6 +120,8 @@ class TestWorkspaceEndpoint(FunctionalTest):
73 120
 
74 121
 class TestWorkspaceMembersEndpoint(FunctionalTest):
75 122
 
123
+    fixtures = [BaseFixture, ContentFixtures]
124
+
76 125
     def test_api__get_workspace_members__ok_200__nominal_case(self):
77 126
         self.testapp.authorization = (
78 127
             'Basic',
@@ -81,42 +130,30 @@ class TestWorkspaceMembersEndpoint(FunctionalTest):
81 130
                 'admin@admin.admin'
82 131
             )
83 132
         )
84
-        res = self.testapp.post_json('/api/v2/workspaces/1/members', status=200)
133
+        res = self.testapp.get('/api/v2/workspaces/1/members', status=200).json_body
85 134
         assert len(res) == 2
86 135
         user_role = res[0]
87
-        assert user_role['role'] == 'administrator'
88
-        assert user_role['user_id'] == '1'
89
-        assert user_role['workspace_id'] == '1'
90
-        assert user_role['user']['label'] == 'Global manager'
91
-        assert user_role['user']['avatar_url'] == ''  # TODO
92
-
93
-        assert res['role'] == 1
94
-        assert res['slug'] == 'w1'
95
-        assert res['label'] == 'w1'
96
-        assert res['description'] == 'Just another description'
97
-        assert len(res['sidebar_entries']) == 3  # TODO change this
98
-
99
-        sidebar_entry = res['sidebar_entries'][0]
100
-        assert sidebar_entry['slug'] == 'markdown-pages'
101
-        assert sidebar_entry['label'] == 'Document Markdown'
102
-        assert sidebar_entry['route'] == "/#/workspace/{workspace_id}/contents/?type=mardown-page"  # nopep8
103
-        assert sidebar_entry['hexcolor'] == "#F0F9DC"
104
-        assert sidebar_entry['icon'] == "file-text-o"
105
-        # TODO Do this for the other
106
-
107
-    def test_api__get_workspace_members__err_400__unallowed_user(self):
108
-        self.testapp.authorization = (
109
-            'Basic',
110
-            (
111
-                'lawrence-not-real-email@fsf.local',
112
-                'foobarbaz'
113
-            )
114
-        )
115
-        res = self.testapp.post_json('/api/v2/workspaces/1/members', status=403)
116
-        assert isinstance(res.json, dict)
117
-        assert 'code' in res.json.keys()
118
-        assert 'message' in res.json.keys()
119
-        assert 'details' in res.json.keys()
136
+        assert user_role['slug'] == 'workspace_manager'
137
+        assert user_role['user_id'] == 1
138
+        assert user_role['workspace_id'] == 1
139
+        assert user_role['user']['display_name'] == 'Global manager'
140
+        assert user_role['user']['avatar_url'] is None  # TODO
141
+
142
+
143
+
144
+    # def test_api__get_workspace_members__err_400__unallowed_user(self):
145
+    #     self.testapp.authorization = (
146
+    #         'Basic',
147
+    #         (
148
+    #             'lawrence-not-real-email@fsf.local',
149
+    #             'foobarbaz'
150
+    #         )
151
+    #     )
152
+    #     res = self.testapp.get('/api/v2/workspaces/3/members', status=403)
153
+    #     assert isinstance(res.json, dict)
154
+    #     assert 'code' in res.json.keys()
155
+    #     assert 'message' in res.json.keys()
156
+    #     assert 'details' in res.json.keys()
120 157
 
121 158
     def test_api__get_workspace_members__err_401__unregistered_user(self):
122 159
         self.testapp.authorization = (
@@ -126,7 +163,7 @@ class TestWorkspaceMembersEndpoint(FunctionalTest):
126 163
                 'lapin'
127 164
             )
128 165
         )
129
-        res = self.testapp.post_json('/api/v2/workspaces/1/members', status=403)
166
+        res = self.testapp.get('/api/v2/workspaces/1/members', status=401)
130 167
         assert isinstance(res.json, dict)
131 168
         assert 'code' in res.json.keys()
132 169
         assert 'message' in res.json.keys()
@@ -140,7 +177,7 @@ class TestWorkspaceMembersEndpoint(FunctionalTest):
140 177
                 'admin@admin.admin'
141 178
             )
142 179
         )
143
-        res = self.testapp.post_json('/api/v2/workspaces/5/members', status=404)
180
+        res = self.testapp.get('/api/v2/workspaces/5/members', status=404)
144 181
         assert isinstance(res.json, dict)
145 182
         assert 'code' in res.json.keys()
146 183
         assert 'message' in res.json.keys()

+ 9 - 2
tracim/views/core_api/schemas.py View File

@@ -36,6 +36,10 @@ class UserIdPathSchema(marshmallow.Schema):
36 36
     user_id = marshmallow.fields.Int()
37 37
 
38 38
 
39
+class WorkspaceIdPathSchema(marshmallow.Schema):
40
+    workspace_id = marshmallow.fields.Int()
41
+
42
+
39 43
 class BasicAuthSchema(marshmallow.Schema):
40 44
 
41 45
     email = marshmallow.fields.Email(required=True)
@@ -72,6 +76,7 @@ class WorkspaceSchema(marshmallow.Schema):
72 76
         many=True,
73 77
     )
74 78
 
79
+
75 80
 class WorkspaceDigestSchema(marshmallow.Schema):
76 81
     id = marshmallow.fields.Int()
77 82
     label = marshmallow.fields.String()
@@ -82,10 +87,12 @@ class WorkspaceDigestSchema(marshmallow.Schema):
82 87
 
83 88
 
84 89
 class WorkspaceMemberSchema(marshmallow.Schema):
85
-    role = marshmallow.fields.String()
90
+    slug = marshmallow.fields.String()
86 91
     user_id = marshmallow.fields.Int()
87 92
     workspace_id = marshmallow.fields.Int()
88
-    # TODO user
93
+    user = marshmallow.fields.Nested(
94
+        UserSchema(only=('display_name', 'avatar_url'))
95
+    )
89 96
 
90 97
 
91 98
 class ApplicationConfigSchema(marshmallow.Schema):

+ 8 - 10
tracim/views/core_api/user_controller.py View File

@@ -14,8 +14,8 @@ from tracim.exceptions import NotAuthentificated, InsufficientUserProfile, \
14 14
 from tracim.lib.core.user import UserApi
15 15
 from tracim.lib.core.workspace import WorkspaceApi
16 16
 from tracim.views.controllers import Controller
17
-from tracim.views.core_api.schemas import WorkspaceSchema, UserSchema, \
18
-    UserIdPathSchema
17
+from tracim.views.core_api.schemas import UserIdPathSchema, \
18
+    WorkspaceDigestSchema
19 19
 
20 20
 
21 21
 class UserController(Controller):
@@ -25,7 +25,7 @@ class UserController(Controller):
25 25
     @hapic.handle_exception(NotAuthentificated, HTTPStatus.UNAUTHORIZED)
26 26
     @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
27 27
     @hapic.handle_exception(UserNotExist, HTTPStatus.NOT_FOUND)
28
-    @hapic.output_body(WorkspaceSchema(many=True),)
28
+    @hapic.output_body(WorkspaceDigestSchema(many=True),)
29 29
     def user_workspace(self, context, request: TracimRequest, hapic_data=None):
30 30
         """
31 31
         Get list of user workspaces
@@ -49,13 +49,11 @@ class UserController(Controller):
49 49
             raise UserNotExist()
50 50
         if not uapi.can_see_private_info_of_user(user):
51 51
             raise InsufficientUserProfile()
52
-        workspaces = wapi.get_all_for_user(user)
53
-        workspaces_in_context = []
54
-        for workspace in workspaces:
55
-            workspaces_in_context.append(
56
-                WorkspaceInContext(workspace, request.dbsession, app_config)
57
-            )
58
-        return workspaces_in_context
52
+
53
+        return [
54
+            WorkspaceInContext(workspace, request.dbsession, app_config)
55
+            for workspace in wapi.get_all_for_user(user)
56
+        ]
59 57
 
60 58
     def bind(self, configurator: Configurator) -> None:
61 59
         """

+ 94 - 0
tracim/views/core_api/workspace_controller.py View File

@@ -0,0 +1,94 @@
1
+from pyramid.config import Configurator
2
+from sqlalchemy.orm.exc import NoResultFound
3
+
4
+from tracim.lib.core.userworkspace import RoleApi
5
+from tracim.models.context_models import WorkspaceInContext, \
6
+    UserRoleWorkspaceInContext
7
+
8
+try:  # Python 3.5+
9
+    from http import HTTPStatus
10
+except ImportError:
11
+    from http import client as HTTPStatus
12
+
13
+from tracim import hapic, TracimRequest
14
+from tracim.exceptions import NotAuthentificated, InsufficientUserProfile, \
15
+    WorkspaceNotFound
16
+from tracim.lib.core.user import UserApi
17
+from tracim.lib.core.workspace import WorkspaceApi
18
+from tracim.views.controllers import Controller
19
+from tracim.views.core_api.schemas import WorkspaceSchema, UserSchema, \
20
+    WorkspaceIdPathSchema, WorkspaceMemberSchema
21
+
22
+
23
+class WorkspaceController(Controller):
24
+
25
+    @hapic.with_api_doc()
26
+    @hapic.input_path(WorkspaceIdPathSchema())
27
+    @hapic.handle_exception(NotAuthentificated, HTTPStatus.UNAUTHORIZED)
28
+    #@hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
29
+    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.NOT_FOUND)
30
+    @hapic.output_body(WorkspaceSchema())
31
+    def workspace(self, context, request: TracimRequest, hapic_data=None):
32
+        """
33
+        Get workspace information
34
+        """
35
+        wid = hapic_data.path['workspace_id']
36
+        app_config = request.registry.settings['CFG']
37
+        wapi = WorkspaceApi(
38
+            current_user=request.current_user,  # User
39
+            session=request.dbsession,
40
+        )
41
+        # TODO - G.M - 22-05-2018 - Refactor this in a more lib way( avoid
42
+        # try/catch and complex code here).
43
+        try:
44
+            workspace = wapi.get_one(wid)
45
+        except NoResultFound:
46
+            raise WorkspaceNotFound()
47
+        return WorkspaceInContext(workspace, request.dbsession, app_config)
48
+
49
+    @hapic.with_api_doc()
50
+    @hapic.input_path(WorkspaceIdPathSchema())
51
+    @hapic.handle_exception(NotAuthentificated, HTTPStatus.UNAUTHORIZED)
52
+    #@hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
53
+    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.NOT_FOUND)
54
+    @hapic.output_body(WorkspaceMemberSchema(many=True))
55
+    def workspaces_members(
56
+            self,
57
+            context,
58
+            request: TracimRequest,
59
+            hapic_data=None
60
+    ) -> None:
61
+        wid = hapic_data.path['workspace_id']
62
+        app_config = request.registry.settings['CFG']
63
+        rapi = RoleApi(
64
+            current_user=request.current_user,
65
+            session=request.dbsession,
66
+        )
67
+        wapi = WorkspaceApi(
68
+            current_user=request.current_user,
69
+            session=request.dbsession,
70
+        )
71
+        try:
72
+            wapi.get_one(wid)
73
+        except NoResultFound:
74
+            raise WorkspaceNotFound()
75
+        return [
76
+            UserRoleWorkspaceInContext(
77
+                user_role,
78
+                request.dbsession,
79
+                app_config
80
+            )
81
+            for user_role in rapi.get_all_for_workspace(wid)
82
+        ]
83
+
84
+    def bind(self, configurator: Configurator) -> None:
85
+        """
86
+        Create all routes and views using pyramid configurator
87
+        for this controller
88
+        """
89
+
90
+        # Applications
91
+        configurator.add_route('workspace', '/workspaces/{workspace_id}', request_method='GET')  # nopep8
92
+        configurator.add_view(self.workspace, route_name='workspace')
93
+        configurator.add_route('workspace_members', '/workspaces/{workspace_id}/members', request_method='GET')  # nopep8
94
+        configurator.add_view(self.workspaces_members, route_name='workspace_members')