瀏覽代碼

merge with better_api_doc

Guénaël Muller 6 年之前
父節點
當前提交
adb6e8d958

+ 259 - 0
development.ini.old 查看文件

1
+###
2
+# app configuration
3
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
4
+###
5
+[app:main]
6
+use = egg:tracim_backend
7
+
8
+pyramid.reload_templates = true
9
+pyramid.debug_authorization = false
10
+pyramid.debug_notfound = false
11
+pyramid.debug_routematch = false
12
+pyramid.default_locale_name = en
13
+pyramid.includes =
14
+    pyramid_debugtoolbar
15
+
16
+sqlalchemy.url = sqlite:///%(here)s/tracim.sqlite
17
+
18
+retry.attempts = 3
19
+
20
+# By default, the toolbar only appears for clients from IP addresses
21
+# '127.0.0.1' and '::1'.
22
+# debugtoolbar.hosts = 127.0.0.1 ::1
23
+
24
+###
25
+# TRACIM SPECIFIC CONF
26
+###
27
+
28
+### Global
29
+debug = false
30
+cache_dir = %(here)s/data
31
+# preview generator cache directory
32
+preview_cache_dir = /tmp/tracim/preview/
33
+# file depot storage
34
+depot_storage_name = tracim
35
+depot_storage_dir = %(here)s/depot/
36
+
37
+# The following parameters allow to personalize the home page
38
+# They are html ready (you can put html tags they will be interpreted)
39
+website.title = TRACIM
40
+website.title.color = #555
41
+website.home.subtitle = Default login: email: admin@admin.admin (password: admin@admin.admin)
42
+website.home.tag_line = <div class="text-center" style="font-weight: bold;">Collaboration, versionning and traceability</div>
43
+website.home.below_login_form = in case of problem, please contact the administrator.
44
+# Values may be 'all' or 'folders'
45
+website.treeview.content = all
46
+# The following base_url is used for links and icons
47
+# integrated in the email notifcations
48
+website.base_url = http://127.0.0.1:8080
49
+# If config not provided, it will be extracted from website.base_url
50
+website.server_name = 127.0.0.1
51
+
52
+# Specifies if the update of comments and attached files is allowed (by the owner only).
53
+# Examples:
54
+#    600 means 10 minutes (ie 600 seconds)
55
+#   3600 means 1 hour (60x60 seconds)
56
+#
57
+# Allowed values:
58
+#  -1 means that content update is allowed for ever
59
+#   0 means that content update is not allowed
60
+#   x means that content update is allowed for x seconds (with x>0)
61
+content.update.allowed.duration = 3600
62
+
63
+# Auth type (internal or ldap)
64
+auth_type = internal
65
+# If auth_type is ldap, uncomment following ldap_* parameters
66
+# LDAP server address
67
+# ldap_url = ldap://localhost:389
68
+# Base dn to make queries
69
+# ldap_base_dn = dc=directory,dc=fsf,dc=org
70
+# Bind dn to identify the search
71
+# ldap_bind_dn = cn=admin,dc=directory,dc=fsf,dc=org
72
+# The bind password
73
+# ldap_bind_pass = toor
74
+# Attribute name of user record who contain user login (email)
75
+# ldap_ldap_naming_attribute = uid
76
+# Matching between ldap attribute and ldap user field (ldap_attr1=user_field1,ldap_attr2=user_field2,...)
77
+# ldap_user_attributes = mail=email
78
+# TLS usage to communicate with your LDAP server
79
+# ldap_tls = False
80
+# If True, LDAP own tracim group managment (not available for now!)
81
+# ldap_group_enabled = False
82
+# User auth token validity in seconds (used to interfaces like web calendars)
83
+user.auth_token.validity = 604800
84
+
85
+### Mail
86
+
87
+# Reset password through email related configuration.
88
+# These emails will be sent through SMTP
89
+#
90
+resetpassword.email_sender = email@sender.com
91
+resetpassword.smtp_host = smtp.sender
92
+resetpassword.smtp_port = 25
93
+resetpassword.smtp_login = smtp.login
94
+resetpassword.smtp_passwd = smtp.password
95
+
96
+email.notification.activated = False
97
+# email.notification.log_file_path = /tmp/mail-notifications.log
98
+# email notifications can be sent with the user_id added as an identifier
99
+# this way email clients like Thunderbird will be able to distinguish
100
+# notifications generated by a user or another one
101
+email.notification.from.email = noreply+{user_id}@trac.im
102
+email.notification.from.default_label = Tracim Notifications
103
+email.notification.reply_to.email = reply+{content_id}@trac.im
104
+email.notification.references.email = thread+{content_id}@trac.im
105
+email.notification.content_update.template.html = %(here)s/tracim/templates/mail/content_update_body_html.mak
106
+email.notification.content_update.template.text = %(here)s/tracim/templates/mail/content_update_body_text.mak
107
+email.notification.created_account.template.html = %(here)s/tracim/templates/mail/created_account_body_html.mak
108
+email.notification.created_account.template.text = %(here)s/tracim/templates/mail/created_account_body_text.mak
109
+# Note: items between { and } are variable names. Do not remove / rename them
110
+email.notification.content_update.subject = [{website_title}] [{workspace_label}] {content_label} ({content_status_label})
111
+email.notification.created_account.subject = [{website_title}] Created account
112
+# processing_mode may be sync or async
113
+email.notification.processing_mode = sync
114
+email.notification.smtp.server = your_smtp_server
115
+email.notification.smtp.port = 25
116
+email.notification.smtp.user = your_smtp_user
117
+email.notification.smtp.password = your_smtp_password
118
+
119
+## Email sending configuration
120
+# processing_mode may be sync or async,
121
+# with async, please configure redis below
122
+email.processing_mode = sync
123
+# email.async.redis.host = localhost
124
+# email.async.redis.port = 6379
125
+# email.async.redis.db = 0
126
+
127
+# Email reply configuration
128
+email.reply.activated = False
129
+email.reply.imap.server = your_imap_server
130
+email.reply.imap.port = 993
131
+email.reply.imap.user = your_imap_user
132
+email.reply.imap.password = your_imap_password
133
+email.reply.imap.folder = INBOX
134
+email.reply.imap.use_ssl = true
135
+email.reply.imap.use_idle = true
136
+# Re-new connection each 10 minutes
137
+email.reply.connection.max_lifetime = 600
138
+# Token for communication between mail fetcher and tracim controller
139
+email.reply.token = mysecuretoken
140
+# Delay in seconds between each check
141
+email.reply.check.heartbeat = 60
142
+email.reply.use_html_parsing = true
143
+email.reply.use_txt_parsing = true
144
+# Lockfile path is required for email_reply feature,
145
+# it's just an empty file use to prevent concurrent access to imap unseen mail
146
+email.reply.lockfile_path = %(here)s/email_fetcher.lock
147
+
148
+### Radical (CalDav server) configuration
149
+
150
+# radicale.server.host = 0.0.0.0
151
+# radicale.server.port = 5232
152
+# radicale.server.ssl = false
153
+radicale.server.filesystem.folder = %(here)s/radicale/collections/
154
+# radicale.server.allow_origin = *
155
+# radicale.server.realm_message = Tracim Calendar - Password Required
156
+## url can be extended like http://127.0.0.1:5232/calendar
157
+## in this case, you have to create your own proxy behind this url.
158
+## and update following parameters
159
+# radicale.client.base_url.host = http://127.0.0.1:5232
160
+# radicale.client.base_url.prefix = /
161
+
162
+### WSGIDAV
163
+
164
+wsgidav.config_path = %(here)s/wsgidav.conf
165
+## url can be extended like 127.0.0.1/webdav
166
+## in this case, you have to create your own proxy behind this url.
167
+## Do not set http:// prefix.
168
+# wsgidav.client.base_url = 127.0.0.1:<WSGIDAV_PORT>
169
+
170
+###
171
+# wsgi server configuration
172
+###
173
+
174
+[server:main]
175
+use = egg:waitress#main
176
+listen = localhost:6543
177
+
178
+[alembic]
179
+# path to migration scripts
180
+script_location = tracim/migration
181
+
182
+# template used to generate migration files
183
+# file_template = %%(rev)s_%%(slug)s
184
+
185
+# timezone to use when rendering the date
186
+# within the migration file as well as the filename.
187
+# string value is passed to dateutil.tz.gettz()
188
+# leave blank for localtime
189
+# timezone =
190
+
191
+# max length of characters to apply to the
192
+# "slug" field
193
+#truncate_slug_length = 40
194
+
195
+# set to 'true' to run the environment during
196
+# the 'revision' command, regardless of autogenerate
197
+# revision_environment = false
198
+
199
+# set to 'true' to allow .pyc and .pyo files without
200
+# a source .py file to be detected as revisions in the
201
+# versions/ directory
202
+# sourceless = false
203
+
204
+# version location specification; this defaults
205
+# to migrate/versions.  When using multiple version
206
+# directories, initial revisions must be specified with --version-path
207
+# version_locations = %(here)s/bar %(here)s/bat migrate/versions
208
+
209
+# the output encoding used when revision files
210
+# are written from script.py.mako
211
+# output_encoding = utf-8
212
+
213
+sqlalchemy.url = sqlite:///%(here)s/tracim.sqlite
214
+
215
+###
216
+# logging configuration
217
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
218
+###
219
+
220
+[loggers]
221
+keys = root, tracim, sqlalchemy, alembic
222
+
223
+[handlers]
224
+keys = console
225
+
226
+[formatters]
227
+keys = generic
228
+
229
+[logger_root]
230
+level = INFO
231
+handlers = console
232
+
233
+[logger_tracim]
234
+level = DEBUG
235
+handlers =
236
+qualname = tracim
237
+
238
+[logger_sqlalchemy]
239
+level = INFO
240
+handlers =
241
+qualname = sqlalchemy.engine
242
+# "level = INFO" logs SQL queries.
243
+# "level = DEBUG" logs SQL queries and results.
244
+# "level = WARN" logs neither.  (Recommended for production systems.)
245
+
246
+[logger_alembic]
247
+level = INFO
248
+handlers =
249
+qualname = alembic
250
+
251
+[handler_console]
252
+class = StreamHandler
253
+args = (sys.stderr,)
254
+level = NOTSET
255
+formatter = generic
256
+
257
+[formatter_generic]
258
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
259
+datefmt = %H:%M:%S

+ 2 - 2
tracim/command/user.py 查看文件

12
 #from tracim.lib.daemons import DaemonsManager
12
 #from tracim.lib.daemons import DaemonsManager
13
 #from tracim.lib.daemons import RadicaleDaemon
13
 #from tracim.lib.daemons import RadicaleDaemon
14
 #from tracim.lib.email import get_email_manager
14
 #from tracim.lib.email import get_email_manager
15
-from tracim.exceptions import UserAlreadyExistError, GroupNotExist
15
+from tracim.exceptions import UserAlreadyExistError, GroupDoesNotExist
16
 from tracim.exceptions import NotificationNotSend
16
 from tracim.exceptions import NotificationNotSend
17
 from tracim.exceptions import BadCommandError
17
 from tracim.exceptions import BadCommandError
18
 from tracim.lib.core.group import GroupApi
18
 from tracim.lib.core.group import GroupApi
93
             for group in groups_availables:
93
             for group in groups_availables:
94
                 msg+= "'{}',".format(group)
94
                 msg+= "'{}',".format(group)
95
             self._session.rollback()
95
             self._session.rollback()
96
-            raise GroupNotExist(msg)
96
+            raise GroupDoesNotExist(msg)
97
         return self._group_api.get_one_with_name(name)
97
         return self._group_api.get_one_with_name(name)
98
 
98
 
99
     def _add_user_to_named_group(
99
     def _add_user_to_named_group(

+ 7 - 3
tracim/exceptions.py 查看文件

89
     pass
89
     pass
90
 
90
 
91
 
91
 
92
-class UserNotExist(TracimException):
92
+class NotificationNotSend(TracimException):
93
     pass
93
     pass
94
 
94
 
95
 
95
 
96
-class NotificationNotSend(TracimException):
96
+class GroupDoesNotExist(TracimError):
97
+    pass
98
+
99
+
100
+class UserDoesNotExist(TracimException):
97
     pass
101
     pass
98
 
102
 
99
 
103
 
100
-class GroupNotExist(TracimError):
104
+class UserNotFoundInTracimRequest(TracimException):
101
     pass
105
     pass

+ 3 - 4
tracim/lib/core/group.py 查看文件

3
 
3
 
4
 from sqlalchemy.orm.exc import NoResultFound
4
 from sqlalchemy.orm.exc import NoResultFound
5
 
5
 
6
-from tracim.exceptions import GroupNotExist
6
+from tracim.exceptions import GroupDoesNotExist
7
 from tracim import CFG
7
 from tracim import CFG
8
 
8
 
9
 
9
 
21
             session: Session,
21
             session: Session,
22
             current_user: typing.Optional[User],
22
             current_user: typing.Optional[User],
23
             config: CFG
23
             config: CFG
24
-
25
     ):
24
     ):
26
         self._user = current_user
25
         self._user = current_user
27
         self._session = session
26
         self._session = session
35
             group = self._base_query().filter(Group.group_id == group_id).one()
34
             group = self._base_query().filter(Group.group_id == group_id).one()
36
             return group
35
             return group
37
         except NoResultFound:
36
         except NoResultFound:
38
-            raise GroupNotExist()
37
+            raise GroupDoesNotExist()
39
 
38
 
40
     def get_one_with_name(self, group_name) -> Group:
39
     def get_one_with_name(self, group_name) -> Group:
41
         try:
40
         try:
42
             group = self._base_query().filter(Group.group_name == group_name).one()
41
             group = self._base_query().filter(Group.group_name == group_name).one()
43
             return group
42
             return group
44
         except NoResultFound:
43
         except NoResultFound:
45
-            raise GroupNotExist()
44
+            raise GroupDoesNotExist()
46
 
45
 
47
     def get_all(self):
46
     def get_all(self):
48
         return self._base_query().order_by(Group.group_id).all()
47
         return self._base_query().order_by(Group.group_id).all()

+ 11 - 10
tracim/lib/core/user.py 查看文件

10
 from sqlalchemy.orm import Session
10
 from sqlalchemy.orm import Session
11
 
11
 
12
 from tracim import CFG
12
 from tracim import CFG
13
-from tracim.models.auth import User, Group
13
+from tracim.models.auth import User
14
+from tracim.models.auth import Group
14
 from sqlalchemy.orm.exc import NoResultFound
15
 from sqlalchemy.orm.exc import NoResultFound
15
-from tracim.exceptions import WrongUserPassword, UserNotExist
16
+from tracim.exceptions import WrongUserPassword, UserDoesNotExist
16
 from tracim.exceptions import AuthenticationFailed
17
 from tracim.exceptions import AuthenticationFailed
17
 from tracim.models.context_models import UserInContext
18
 from tracim.models.context_models import UserInContext
18
 
19
 
51
         """
52
         """
52
         try:
53
         try:
53
             user = self._base_query().filter(User.user_id == user_id).one()
54
             user = self._base_query().filter(User.user_id == user_id).one()
54
-        except NoResultFound:
55
-            raise UserNotExist()
55
+        except NoResultFound as exc:
56
+            raise UserDoesNotExist('User "{}" not found in database'.format(user_id)) from exc  # nopep8
56
         return user
57
         return user
57
 
58
 
58
     def get_one_by_email(self, email: str) -> User:
59
     def get_one_by_email(self, email: str) -> User:
63
         """
64
         """
64
         try:
65
         try:
65
             user = self._base_query().filter(User.email == email).one()
66
             user = self._base_query().filter(User.email == email).one()
66
-        except NoResultFound:
67
-            raise UserNotExist()
67
+        except NoResultFound as exc:
68
+            raise UserDoesNotExist('User "{}" not found in database'.format(email)) from exc  # nopep8
68
         return user
69
         return user
69
 
70
 
70
     # FIXME - G.M - 24-04-2018 - Duplicate method with get_one.
71
     # FIXME - G.M - 24-04-2018 - Duplicate method with get_one.
76
         Get current_user
77
         Get current_user
77
         """
78
         """
78
         if not self._user:
79
         if not self._user:
79
-            raise UserNotExist()
80
+            raise UserDoesNotExist('There is no current user')
80
         return self._user
81
         return self._user
81
 
82
 
82
     def get_all(self) -> typing.Iterable[User]:
83
     def get_all(self) -> typing.Iterable[User]:
105
             if user.validate_password(password):
106
             if user.validate_password(password):
106
                 return user
107
                 return user
107
             else:
108
             else:
108
-                raise WrongUserPassword()
109
-        except (WrongUserPassword, NoResultFound, UserNotExist):
110
-            raise AuthenticationFailed()
109
+                raise WrongUserPassword('User "{}" password is incorrect'.format(email))  # nopep8
110
+        except (WrongUserPassword, UserDoesNotExist) as exc:
111
+            raise AuthenticationFailed('User "{}" authentication failed'.format(email)) from exc  # nopep8
111
 
112
 
112
     # Actions
113
     # Actions
113
 
114
 

+ 29 - 22
tracim/lib/core/userworkspace.py 查看文件

7
 __author__ = 'damien'
7
 __author__ = 'damien'
8
 
8
 
9
 from sqlalchemy.orm import Session
9
 from sqlalchemy.orm import Session
10
-from tracim.models.auth import User, Group
10
+from sqlalchemy.orm import Query
11
+from tracim.models.auth import User
11
 from tracim.models.data import Workspace
12
 from tracim.models.data import Workspace
12
 from tracim.models.data import UserRoleInWorkspace
13
 from tracim.models.data import UserRoleInWorkspace
13
 from tracim.models.data import RoleType
14
 from tracim.models.data import RoleType
73
 
74
 
74
         return role
75
         return role
75
 
76
 
76
-    def __init__(self,
77
-                 session: Session,
78
-                 current_user: typing.Optional[User],
79
-                 config: CFG):
77
+    def __init__(
78
+        self,
79
+        session: Session,
80
+        current_user: typing.Optional[User],
81
+        config: CFG,
82
+    )-> None:
80
         self._session = session
83
         self._session = session
81
         self._user = current_user
84
         self._user = current_user
82
         self._config = config
85
         self._config = config
83
 
86
 
84
-    def _get_one_rsc(self, user_id, workspace_id):
87
+    def _get_one_rsc(self, user_id: int, workspace_id: int) -> Query:
85
         """
88
         """
86
         :param user_id:
89
         :param user_id:
87
         :param workspace_id:
90
         :param workspace_id:
91
             filter(UserRoleInWorkspace.workspace_id == workspace_id).\
94
             filter(UserRoleInWorkspace.workspace_id == workspace_id).\
92
             filter(UserRoleInWorkspace.user_id == user_id)
95
             filter(UserRoleInWorkspace.user_id == user_id)
93
 
96
 
94
-    def get_one(self, user_id, workspace_id):
97
+    def get_one(self, user_id: int, workspace_id: int) -> UserRoleInWorkspace:
95
         return self._get_one_rsc(user_id, workspace_id).one()
98
         return self._get_one_rsc(user_id, workspace_id).one()
96
 
99
 
97
     def create_one(
100
     def create_one(
98
-            self,
99
-            user: User,
100
-            workspace: Workspace,
101
-            role_level: int,
102
-            with_notif: bool,
103
-            flush: bool=True
101
+        self,
102
+        user: User,
103
+        workspace: Workspace,
104
+        role_level: int,
105
+        with_notif: bool,
106
+        flush: bool=True
104
     ) -> UserRoleInWorkspace:
107
     ) -> UserRoleInWorkspace:
105
         role = self.create_role()
108
         role = self.create_role()
106
         role.user_id = user.user_id
109
         role.user_id = user.user_id
111
             self._session.flush()
114
             self._session.flush()
112
         return role
115
         return role
113
 
116
 
114
-    def delete_one(self, user_id, workspace_id, flush=True):
117
+    def delete_one(self, user_id: int, workspace_id: int, flush=True) -> None:
115
         self._get_one_rsc(user_id, workspace_id).delete()
118
         self._get_one_rsc(user_id, workspace_id).delete()
116
         if flush:
119
         if flush:
117
             self._session.flush()
120
             self._session.flush()
118
 
121
 
119
-    def _get_all_for_user(self, user_id):
122
+    def _get_all_for_user(self, user_id) -> typing.List[UserRoleInWorkspace]:
120
         return self._session.query(UserRoleInWorkspace)\
123
         return self._session.query(UserRoleInWorkspace)\
121
             .filter(UserRoleInWorkspace.user_id == user_id)
124
             .filter(UserRoleInWorkspace.user_id == user_id)
122
 
125
 
123
-    def get_all_for_user(self, user: User):
126
+    def get_all_for_user(self, user: User) -> typing.List[UserRoleInWorkspace]:
124
         return self._get_all_for_user(user.user_id).all()
127
         return self._get_all_for_user(user.user_id).all()
125
 
128
 
126
     def get_all_for_user_order_by_workspace(
129
     def get_all_for_user_order_by_workspace(
127
-            self,
128
-            user_id: int
129
-    ) -> UserRoleInWorkspace:
130
+        self,
131
+        user_id: int
132
+    ) -> typing.List[UserRoleInWorkspace]:
130
         return self._get_all_for_user(user_id)\
133
         return self._get_all_for_user(user_id)\
131
             .join(UserRoleInWorkspace.workspace).order_by(Workspace.label).all()
134
             .join(UserRoleInWorkspace.workspace).order_by(Workspace.label).all()
132
 
135
 
133
-    def get_all_for_workspace(self, workspace:Workspace):
136
+    def get_all_for_workspace(
137
+        self,
138
+        workspace:Workspace
139
+    ) -> typing.List[UserRoleInWorkspace]:
134
         return self._session.query(UserRoleInWorkspace)\
140
         return self._session.query(UserRoleInWorkspace)\
135
             .filter(UserRoleInWorkspace.workspace_id == workspace.workspace_id).all()  # nopep8
141
             .filter(UserRoleInWorkspace.workspace_id == workspace.workspace_id).all()  # nopep8
136
 
142
 
137
-    def save(self, role: UserRoleInWorkspace):
143
+    def save(self, role: UserRoleInWorkspace) -> None:
138
         self._session.flush()
144
         self._session.flush()
139
 
145
 
146
+    # TODO - G.M - 07-06-2018 - [Cleanup] Check if this method is already needed
140
     @classmethod
147
     @classmethod
141
-    def get_roles_for_select_field(cls):
148
+    def get_roles_for_select_field(cls) -> typing.List[RoleType]:
142
         """
149
         """
143
 
150
 
144
         :return: list of DictLikeClass instances representing available Roles
151
         :return: list of DictLikeClass instances representing available Roles

+ 2 - 2
tracim/lib/utils/authentification.py 查看文件

4
 from sqlalchemy.orm.exc import NoResultFound
4
 from sqlalchemy.orm.exc import NoResultFound
5
 
5
 
6
 from tracim import TracimRequest
6
 from tracim import TracimRequest
7
-from tracim.exceptions import UserNotExist
7
+from tracim.exceptions import UserDoesNotExist
8
 from tracim.lib.core.user import UserApi
8
 from tracim.lib.core.user import UserApi
9
 from tracim.models import User
9
 from tracim.models import User
10
 
10
 
51
         if not login:
51
         if not login:
52
             return None
52
             return None
53
         user = uapi.get_one_by_email(login)
53
         user = uapi.get_one_by_email(login)
54
-    except (NoResultFound, UserNotExist):
54
+    except (NoResultFound, UserDoesNotExist):
55
         return None
55
         return None
56
     return user
56
     return user

+ 1 - 1
tracim/lib/utils/authorization.py 查看文件

100
             raise InsufficientUserWorkspaceRole()
100
             raise InsufficientUserWorkspaceRole()
101
 
101
 
102
         return wrapper
102
         return wrapper
103
-    return decorator
103
+    return decorator

+ 10 - 8
tracim/lib/utils/request.py 查看文件

5
 from pyramid.request import Request
5
 from pyramid.request import Request
6
 from sqlalchemy.orm.exc import NoResultFound
6
 from sqlalchemy.orm.exc import NoResultFound
7
 
7
 
8
-from tracim.exceptions import NotAuthentificated, UserNotExist
8
+
9
+from tracim.exceptions import NotAuthentificated
10
+from tracim.exceptions import UserNotFoundInTracimRequest
11
+from tracim.exceptions import UserDoesNotExist
9
 from tracim.exceptions import WorkspaceNotFound
12
 from tracim.exceptions import WorkspaceNotFound
10
 from tracim.exceptions import ImmutableAttribute
13
 from tracim.exceptions import ImmutableAttribute
11
 from tracim.lib.core.user import UserApi
14
 from tracim.lib.core.user import UserApi
142
         if 'user_id' in request.matchdict:
145
         if 'user_id' in request.matchdict:
143
             login = request.matchdict['user_id']
146
             login = request.matchdict['user_id']
144
         if not login:
147
         if not login:
145
-            raise UserNotExist('no user_id found, incorrect request ?')
148
+            raise UserNotFoundInTracimRequest('You request a candidate user but the context not permit to found one')  # nopep8
146
         user = uapi.get_one(login)
149
         user = uapi.get_one(login)
147
-    except NoResultFound:
148
-        raise NotAuthentificated('User not found')
150
+    except UserNotFoundInTracimRequest as exc:
151
+        raise UserDoesNotExist('User {} not found'.format(login)) from exc
149
     return user
152
     return user
150
 
153
 
151
 
154
 
162
     try:
165
     try:
163
         login = request.authenticated_userid
166
         login = request.authenticated_userid
164
         if not login:
167
         if not login:
165
-            raise NotAuthentificated('not authenticated user_id,'
166
-                                     'Failed Authentification ?')
168
+            raise UserNotFoundInTracimRequest('You request a current user but the context not permit to found one')  # nopep8
167
         user = uapi.get_one_by_email(login)
169
         user = uapi.get_one_by_email(login)
168
-    except NoResultFound:
169
-        raise NotAuthentificated('User not found')
170
+    except (UserDoesNotExist, UserNotFoundInTracimRequest) as exc:
171
+        raise NotAuthentificated('User {} not found'.format(login)) from exc
170
     return user
172
     return user
171
 
173
 
172
 
174
 

+ 10 - 10
tracim/models/applications.py 查看文件

25
         self.main_route = main_route
25
         self.main_route = main_route
26
 
26
 
27
 
27
 
28
-# TODO - G.M - 21-05-2018 Do not rely on hardcoded app list
29
 # default apps
28
 # default apps
30
 calendar = Application(
29
 calendar = Application(
31
     label='Calendar',
30
     label='Calendar',
58
     main_route='/#/workspaces/{workspace_id}/contents?type=file',
57
     main_route='/#/workspaces/{workspace_id}/contents?type=file',
59
 )
58
 )
60
 
59
 
61
-pagemarkdownplus = Application(
62
-    label='Rich Markdown Files',  # TODO - G.M - 24-05-2018 - Check label
63
-    slug='contents/pagemarkdownplus',
60
+markdownpluspage = Application(
61
+    label='Markdown Plus Documents',  # TODO - G.M - 24-05-2018 - Check label
62
+    slug='contents/markdownpluspage',
64
     icon='file-code',
63
     icon='file-code',
65
     hexcolor='#f12d2d',
64
     hexcolor='#f12d2d',
66
     is_active=True,
65
     is_active=True,
67
     config={},
66
     config={},
68
-    main_route='/#/workspaces/{workspace_id}/contents?type=pagemarkdownplus',
67
+    main_route='/#/workspaces/{workspace_id}/contents?type=markdownpluspage',
69
 )
68
 )
70
 
69
 
71
-pagehtml = Application(
70
+htmlpage = Application(
72
     label='Text Documents',  # TODO - G.M - 24-05-2018 - Check label
71
     label='Text Documents',  # TODO - G.M - 24-05-2018 - Check label
73
-    slug='contents/pagehtml',
72
+    slug='contents/htmlpage',
74
     icon='file-text-o',
73
     icon='file-text-o',
75
     hexcolor='#3f52e3',
74
     hexcolor='#3f52e3',
76
     is_active=True,
75
     is_active=True,
77
     config={},
76
     config={},
78
-    main_route='/#/workspaces/{workspace_id}/contents?type=pagehtml',
77
+    main_route='/#/workspaces/{workspace_id}/contents?type=htmlpage',
79
 )
78
 )
79
+# TODO - G.M - 08-06-2018 - This is hardcoded lists of app, make this dynamic.
80
 # List of applications
80
 # List of applications
81
 applications = [
81
 applications = [
82
-    pagehtml,
83
-    pagemarkdownplus,
82
+    htmlpage,
83
+    markdownpluspage,
84
     file,
84
     file,
85
     thread,
85
     thread,
86
     calendar,
86
     calendar,

+ 13 - 9
tracim/models/auth.py 查看文件

91
 class Profile(object):
91
 class Profile(object):
92
     """This model is the "max" group associated to a given user."""
92
     """This model is the "max" group associated to a given user."""
93
 
93
 
94
-    _NAME = [Group.TIM_NOBODY_GROUPNAME,
95
-             Group.TIM_USER_GROUPNAME,
96
-             Group.TIM_MANAGER_GROUPNAME,
97
-             Group.TIM_ADMIN_GROUPNAME]
98
-
99
-    _IDS = [Group.TIM_NOBODY,
100
-            Group.TIM_USER,
101
-            Group.TIM_MANAGER,
102
-            Group.TIM_ADMIN]
94
+    _NAME = [
95
+        Group.TIM_NOBODY_GROUPNAME,
96
+        Group.TIM_USER_GROUPNAME,
97
+        Group.TIM_MANAGER_GROUPNAME,
98
+        Group.TIM_ADMIN_GROUPNAME,
99
+    ]
100
+
101
+    _IDS = [
102
+        Group.TIM_NOBODY,
103
+        Group.TIM_USER,
104
+        Group.TIM_MANAGER,
105
+        Group.TIM_ADMIN,
106
+    ]
103
 
107
 
104
     # TODO - G.M - 18-04-2018 [Cleanup] Drop this
108
     # TODO - G.M - 18-04-2018 [Cleanup] Drop this
105
     # _LABEL = [l_('Nobody'),
109
     # _LABEL = [l_('Nobody'),

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

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
11
-from tracim.models.workspace_menu_entries import default_workspace_menu_entry, \
12
-    WorkspaceMenuEntry
11
+from tracim.models.workspace_menu_entries import default_workspace_menu_entry
12
+from tracim.models.workspace_menu_entries import WorkspaceMenuEntry
13
 
13
 
14
 
14
 
15
 class LoginCredentials(object):
15
 class LoginCredentials(object):

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

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

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

24
         self.hexcolor = hexcolor
24
         self.hexcolor = hexcolor
25
         self.icon = icon
25
         self.icon = icon
26
 
26
 
27
-
28
 dashboard_menu_entry = WorkspaceMenuEntry(
27
 dashboard_menu_entry = WorkspaceMenuEntry(
29
   slug='dashboard',
28
   slug='dashboard',
30
   label='Dashboard',
29
   label='Dashboard',
40
   icon="",
39
   icon="",
41
 )
40
 )
42
 
41
 
43
-
42
+# TODO - G.M - 08-06-2018 - This is hardcoded default menu entry,
43
+#  of app, make this dynamic (and loaded from application system)
44
 def default_workspace_menu_entry(
44
 def default_workspace_menu_entry(
45
     workspace: Workspace,
45
     workspace: Workspace,
46
 )-> typing.List[WorkspaceMenuEntry]:
46
 )-> typing.List[WorkspaceMenuEntry]:

+ 7 - 11
tracim/tests/commands/test_commands.py 查看文件

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 import os
2
 import os
3
 import subprocess
3
 import subprocess
4
-
5
 import pytest
4
 import pytest
6
-import transaction
7
-from pkg_resources import load_entry_point
8
-from sqlalchemy.orm.exc import NoResultFound
9
-
10
 import tracim
5
 import tracim
11
 from tracim.command import TracimCLI
6
 from tracim.command import TracimCLI
12
-from tracim.command.user import UserCommand
13
-from tracim.exceptions import UserAlreadyExistError, BadCommandError, \
14
-    GroupNotExist, UserNotExist
7
+from tracim.exceptions import UserAlreadyExistError
8
+from tracim.exceptions import BadCommandError
9
+from tracim.exceptions import GroupDoesNotExist
10
+from tracim.exceptions import UserDoesNotExist
15
 from tracim.lib.core.user import UserApi
11
 from tracim.lib.core.user import UserApi
16
 from tracim.tests import CommandFunctionalTest
12
 from tracim.tests import CommandFunctionalTest
17
 
13
 
48
             session=self.session,
44
             session=self.session,
49
             config=self.app_config,
45
             config=self.app_config,
50
         )
46
         )
51
-        with pytest.raises(UserNotExist):
47
+        with pytest.raises(UserDoesNotExist):
52
             api.get_one_by_email('command_test@user')
48
             api.get_one_by_email('command_test@user')
53
         app = TracimCLI()
49
         app = TracimCLI()
54
         result = app.run([
50
         result = app.run([
72
             session=self.session,
68
             session=self.session,
73
             config=self.app_config,
69
             config=self.app_config,
74
         )
70
         )
75
-        with pytest.raises(UserNotExist):
71
+        with pytest.raises(UserDoesNotExist):
76
             api.get_one_by_email('command_test@user')
72
             api.get_one_by_email('command_test@user')
77
         app = TracimCLI()
73
         app = TracimCLI()
78
         result = app.run([
74
         result = app.run([
98
             config=self.app_config,
94
             config=self.app_config,
99
         )
95
         )
100
         app = TracimCLI()
96
         app = TracimCLI()
101
-        with pytest.raises(GroupNotExist):
97
+        with pytest.raises(GroupDoesNotExist):
102
             result = app.run([
98
             result = app.run([
103
                 'user', 'create',
99
                 'user', 'create',
104
                 '-c', 'tests_configs.ini#command_test',
100
                 '-c', 'tests_configs.ini#command_test',

+ 0 - 2
tracim/tests/functional/test_session.py 查看文件

50
         assert res.json_body['created']
50
         assert res.json_body['created']
51
         assert res.json_body['is_active']
51
         assert res.json_body['is_active']
52
         assert res.json_body['profile']
52
         assert res.json_body['profile']
53
-        assert isinstance(res.json_body['profile']['id'], int)
54
         assert res.json_body['profile']['slug'] == 'administrators'
53
         assert res.json_body['profile']['slug'] == 'administrators'
55
         assert res.json_body['caldav_url'] is None
54
         assert res.json_body['caldav_url'] is None
56
         assert res.json_body['avatar_url'] is None
55
         assert res.json_body['avatar_url'] is None
109
         assert res.json_body['created']
108
         assert res.json_body['created']
110
         assert res.json_body['is_active']
109
         assert res.json_body['is_active']
111
         assert res.json_body['profile']
110
         assert res.json_body['profile']
112
-        assert isinstance(res.json_body['profile']['id'], int)
113
         assert res.json_body['profile']['slug'] == 'administrators'
111
         assert res.json_body['profile']['slug'] == 'administrators'
114
         assert res.json_body['caldav_url'] is None
112
         assert res.json_body['caldav_url'] is None
115
         assert res.json_body['avatar_url'] is None
113
         assert res.json_body['avatar_url'] is None

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

25
         res = res.json_body
25
         res = res.json_body
26
         application = res[0]
26
         application = res[0]
27
         assert application['label'] == "Text Documents"
27
         assert application['label'] == "Text Documents"
28
-        assert application['slug'] == 'contents/pagehtml'
28
+        assert application['slug'] == 'contents/htmlpage'
29
         assert application['icon'] == 'file-text-o'
29
         assert application['icon'] == 'file-text-o'
30
         assert application['hexcolor'] == '#3f52e3'
30
         assert application['hexcolor'] == '#3f52e3'
31
         assert application['is_active'] is True
31
         assert application['is_active'] is True
32
         assert 'config' in application
32
         assert 'config' in application
33
         application = res[1]
33
         application = res[1]
34
-        assert application['label'] == "Rich Markdown Files"
35
-        assert application['slug'] == 'contents/pagemarkdownplus'
34
+        assert application['label'] == "Markdown Plus Documents"
35
+        assert application['slug'] == 'contents/markdownpluspage'
36
         assert application['icon'] == 'file-code'
36
         assert application['icon'] == 'file-code'
37
         assert application['hexcolor'] == '#f12d2d'
37
         assert application['hexcolor'] == '#f12d2d'
38
         assert application['is_active'] is True
38
         assert application['is_active'] is True

+ 5 - 5
tracim/tests/functional/test_user.py 查看文件

47
         assert sidebar_entry['icon'] == ""
47
         assert sidebar_entry['icon'] == ""
48
 
48
 
49
         sidebar_entry = workspace['sidebar_entries'][2]
49
         sidebar_entry = workspace['sidebar_entries'][2]
50
-        assert sidebar_entry['slug'] == 'contents/pagehtml'
50
+        assert sidebar_entry['slug'] == 'contents/htmlpage'
51
         assert sidebar_entry['label'] == 'Text Documents'
51
         assert sidebar_entry['label'] == 'Text Documents'
52
-        assert sidebar_entry['route'] == '/#/workspaces/1/contents?type=pagehtml'  # nopep8
52
+        assert sidebar_entry['route'] == '/#/workspaces/1/contents?type=htmlpage'  # nopep8
53
         assert sidebar_entry['hexcolor'] == "#3f52e3"
53
         assert sidebar_entry['hexcolor'] == "#3f52e3"
54
         assert sidebar_entry['icon'] == "file-text-o"
54
         assert sidebar_entry['icon'] == "file-text-o"
55
 
55
 
56
         sidebar_entry = workspace['sidebar_entries'][3]
56
         sidebar_entry = workspace['sidebar_entries'][3]
57
-        assert sidebar_entry['slug'] == 'contents/pagemarkdownplus'
58
-        assert sidebar_entry['label'] == 'Rich Markdown Files'
59
-        assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=pagemarkdownplus"    # nopep8
57
+        assert sidebar_entry['slug'] == 'contents/markdownpluspage'
58
+        assert sidebar_entry['label'] == 'Markdown Plus Documents'
59
+        assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=markdownpluspage"    # nopep8
60
         assert sidebar_entry['hexcolor'] == "#f12d2d"
60
         assert sidebar_entry['hexcolor'] == "#f12d2d"
61
         assert sidebar_entry['icon'] == "file-code"
61
         assert sidebar_entry['icon'] == "file-code"
62
 
62
 

+ 5 - 6
tracim/tests/functional/test_workspaces.py 查看文件

48
         assert sidebar_entry['icon'] == ""
48
         assert sidebar_entry['icon'] == ""
49
 
49
 
50
         sidebar_entry = workspace['sidebar_entries'][2]
50
         sidebar_entry = workspace['sidebar_entries'][2]
51
-        assert sidebar_entry['slug'] == 'contents/pagehtml'
51
+        assert sidebar_entry['slug'] == 'contents/htmlpage'
52
         assert sidebar_entry['label'] == 'Text Documents'
52
         assert sidebar_entry['label'] == 'Text Documents'
53
-        assert sidebar_entry['route'] == '/#/workspaces/1/contents?type=pagehtml'  # nopep8
53
+        assert sidebar_entry['route'] == '/#/workspaces/1/contents?type=htmlpage'  # nopep8
54
         assert sidebar_entry['hexcolor'] == "#3f52e3"
54
         assert sidebar_entry['hexcolor'] == "#3f52e3"
55
         assert sidebar_entry['icon'] == "file-text-o"
55
         assert sidebar_entry['icon'] == "file-text-o"
56
 
56
 
57
         sidebar_entry = workspace['sidebar_entries'][3]
57
         sidebar_entry = workspace['sidebar_entries'][3]
58
-        assert sidebar_entry['slug'] == 'contents/pagemarkdownplus'
59
-        assert sidebar_entry['label'] == 'Rich Markdown Files'
60
-        assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=pagemarkdownplus"    # nopep8
58
+        assert sidebar_entry['slug'] == 'contents/markdownpluspage'
59
+        assert sidebar_entry['label'] == 'Markdown Plus Documents'
60
+        assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=markdownpluspage"    # nopep8
61
         assert sidebar_entry['hexcolor'] == "#f12d2d"
61
         assert sidebar_entry['hexcolor'] == "#f12d2d"
62
         assert sidebar_entry['icon'] == "file-code"
62
         assert sidebar_entry['icon'] == "file-code"
63
 
63
 
156
         assert len(res) == 2
156
         assert len(res) == 2
157
         user_role = res[0]
157
         user_role = res[0]
158
         assert user_role['role_slug'] == 'workspace_manager'
158
         assert user_role['role_slug'] == 'workspace_manager'
159
-        assert user_role['role_id'] == 8
160
         assert user_role['user_id'] == 1
159
         assert user_role['user_id'] == 1
161
         assert user_role['workspace_id'] == 1
160
         assert user_role['workspace_id'] == 1
162
         assert user_role['user']['display_name'] == 'Global manager'
161
         assert user_role['user']['display_name'] == 'Global manager'

+ 3 - 3
tracim/tests/library/test_group_api.py 查看文件

1
 # coding=utf-8
1
 # coding=utf-8
2
 import pytest
2
 import pytest
3
 
3
 
4
-from tracim.exceptions import GroupNotExist
4
+from tracim.exceptions import GroupDoesNotExist
5
 from tracim.lib.core.group import GroupApi
5
 from tracim.lib.core.group import GroupApi
6
 from tracim.tests import DefaultTest
6
 from tracim.tests import DefaultTest
7
 from tracim.fixtures.users_and_groups import Base as BaseFixture
7
 from tracim.fixtures.users_and_groups import Base as BaseFixture
33
             session=self.session,
33
             session=self.session,
34
             config=self.app_config,
34
             config=self.app_config,
35
         )
35
         )
36
-        with pytest.raises(GroupNotExist):
36
+        with pytest.raises(GroupDoesNotExist):
37
             group = api.get_one(10)
37
             group = api.get_one(10)
38
 
38
 
39
     def test_unit__get_one_group_with_name__nominal_case(self) -> None:
39
     def test_unit__get_one_group_with_name__nominal_case(self) -> None:
58
             session=self.session,
58
             session=self.session,
59
             config=self.app_config,
59
             config=self.app_config,
60
         )
60
         )
61
-        with pytest.raises(GroupNotExist):
61
+        with pytest.raises(GroupDoesNotExist):
62
             group = api.get_one_with_name('unknown_group')
62
             group = api.get_one_with_name('unknown_group')
63
 
63
 
64
     def test_unit__get_all__ok__nominal_case(self):
64
     def test_unit__get_all__ok__nominal_case(self):

+ 3 - 3
tracim/tests/library/test_user_api.py 查看文件

4
 
4
 
5
 import transaction
5
 import transaction
6
 
6
 
7
-from tracim.exceptions import UserNotExist, AuthenticationFailed
7
+from tracim.exceptions import UserDoesNotExist, AuthenticationFailed
8
 from tracim.lib.core.user import UserApi
8
 from tracim.lib.core.user import UserApi
9
 from tracim.models import User
9
 from tracim.models import User
10
 from tracim.models.context_models import UserInContext
10
 from tracim.models.context_models import UserInContext
91
             session=self.session,
91
             session=self.session,
92
             config=self.config,
92
             config=self.config,
93
         )
93
         )
94
-        with pytest.raises(UserNotExist):
94
+        with pytest.raises(UserDoesNotExist):
95
             api.get_one_by_email('unknown')
95
             api.get_one_by_email('unknown')
96
 
96
 
97
     def test_unit__get_all__ok__nominal_case(self):
97
     def test_unit__get_all__ok__nominal_case(self):
158
             session=self.session,
158
             session=self.session,
159
             config=self.config,
159
             config=self.config,
160
         )
160
         )
161
-        with pytest.raises(UserNotExist):
161
+        with pytest.raises(UserDoesNotExist):
162
             api.get_current_user()
162
             api.get_current_user()
163
 
163
 
164
     def test_unit__authenticate_user___ok__nominal_case(self):
164
     def test_unit__authenticate_user___ok__nominal_case(self):

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

9
 
9
 
10
 
10
 
11
 class ProfileSchema(marshmallow.Schema):
11
 class ProfileSchema(marshmallow.Schema):
12
-    id = marshmallow.fields.Int(dump_only=True, validate=OneOf(Profile._IDS))
13
-    slug = marshmallow.fields.String(attribute='name', validate=OneOf(Profile._NAME))
12
+    slug = marshmallow.fields.String(
13
+        attribute='name',
14
+        validate=OneOf(Profile._NAME),
15
+        example='managers',
16
+    )
17
+
18
+    class Meta:
19
+        description = 'User Profile, give user right on whole Tracim instance.'
14
 
20
 
15
 
21
 
16
 class UserSchema(marshmallow.Schema):
22
 class UserSchema(marshmallow.Schema):
17
 
23
 
18
-    user_id = marshmallow.fields.Int(dump_only=True)
19
-    email = marshmallow.fields.Email(required=True)
20
-    display_name = marshmallow.fields.String()
21
-    created = marshmallow.fields.DateTime(format='iso8601')
22
-    is_active = marshmallow.fields.Bool()
24
+    user_id = marshmallow.fields.Int(dump_only=True, example=3)
25
+    email = marshmallow.fields.Email(
26
+        required=True,
27
+        example='suri.cate@algoo.fr'
28
+    )
29
+    display_name = marshmallow.fields.String(
30
+        example='Suri Cate',
31
+    )
32
+    created = marshmallow.fields.DateTime(
33
+        format='iso8601',
34
+        description='User account creation date (iso8601 format).',
35
+    )
36
+    is_active = marshmallow.fields.Bool(
37
+        example=True,
38
+         # TODO - G.M - Explains this value.
39
+    )
23
     # TODO - G.M - 17-04-2018 - Restrict timezone values
40
     # TODO - G.M - 17-04-2018 - Restrict timezone values
24
-    timezone = marshmallow.fields.String()
41
+    timezone = marshmallow.fields.String(
42
+        example="Paris/Europe",
43
+    )
25
     # TODO - G.M - 17-04-2018 - check this, relative url allowed ?
44
     # TODO - G.M - 17-04-2018 - check this, relative url allowed ?
26
     caldav_url = marshmallow.fields.Url(
45
     caldav_url = marshmallow.fields.Url(
27
         allow_none=True,
46
         allow_none=True,
28
         relative=True,
47
         relative=True,
29
-        attribute='calendar_url'
48
+        attribute='calendar_url',
49
+        example="/api/v2/calendar/user/3.ics/",
50
+        description="The url for calendar CalDAV direct access",
51
+    )
52
+    avatar_url = marshmallow.fields.Url(
53
+        allow_none=True,
54
+        example="/api/v2/assets/avatars/suri-cate.jpg",
55
+        description="avatar_url is the url to the image file. "
56
+                    "If no avatar, then set it to null "
57
+                    "(and frontend will interpret this with a default avatar)",
30
     )
58
     )
31
-    avatar_url = marshmallow.fields.Url(allow_none=True)
32
     profile = marshmallow.fields.Nested(
59
     profile = marshmallow.fields.Nested(
33
         ProfileSchema,
60
         ProfileSchema,
34
         many=False,
61
         many=False,
35
     )
62
     )
36
 
63
 
64
+    class Meta:
65
+        description = 'User account of Tracim'
66
+
37
 
67
 
38
 class UserIdPathSchema(marshmallow.Schema):
68
 class UserIdPathSchema(marshmallow.Schema):
39
-    user_id = marshmallow.fields.Int()
69
+    user_id = marshmallow.fields.Int(example=3)
40
 
70
 
41
 
71
 
42
 class WorkspaceIdPathSchema(marshmallow.Schema):
72
 class WorkspaceIdPathSchema(marshmallow.Schema):
43
-    workspace_id = marshmallow.fields.Int()
73
+    workspace_id = marshmallow.fields.Int(example=4)
44
 
74
 
45
 
75
 
46
 class BasicAuthSchema(marshmallow.Schema):
76
 class BasicAuthSchema(marshmallow.Schema):
47
 
77
 
48
-    email = marshmallow.fields.Email(required=True)
49
-    password = marshmallow.fields.String(required=True, load_only=True)
78
+    email = marshmallow.fields.Email(
79
+        example='suri.cate@algoo.fr',
80
+        required=True
81
+    )
82
+    password = marshmallow.fields.String(
83
+        example='8QLa$<w',
84
+        required=True,
85
+        load_only=True,
86
+    )
87
+
88
+    class Meta:
89
+        description = 'Entry for HTTP Basic Auth'
50
 
90
 
51
     @post_load
91
     @post_load
52
     def make_login(self, data):
92
     def make_login(self, data):
58
 
98
 
59
 
99
 
60
 class NoContentSchema(marshmallow.Schema):
100
 class NoContentSchema(marshmallow.Schema):
101
+
102
+    class Meta:
103
+        description = 'Empty Schema'
61
     pass
104
     pass
62
 
105
 
63
 
106
 
64
 class WorkspaceMenuEntrySchema(marshmallow.Schema):
107
 class WorkspaceMenuEntrySchema(marshmallow.Schema):
65
-    slug = marshmallow.fields.String()
66
-    label = marshmallow.fields.String()
67
-    route = marshmallow.fields.String()
68
-    hexcolor = marshmallow.fields.String()
69
-    icon = marshmallow.fields.String()
70
-
71
-
72
-class WorkspaceSchema(marshmallow.Schema):
73
-    id = marshmallow.fields.Int()
74
-    slug = marshmallow.fields.String()
75
-    label = marshmallow.fields.String()
76
-    description = marshmallow.fields.String()
77
-    sidebar_entries = marshmallow.fields.Nested(
78
-        WorkspaceMenuEntrySchema,
79
-        many=True,
108
+    slug = marshmallow.fields.String(example='markdown-pages')
109
+    label = marshmallow.fields.String(example='Markdown Documents')
110
+    route = marshmallow.fields.String(
111
+        example='/#/workspace/{workspace_id}/contents/?type=mardown-page',
112
+        description='the route is the frontend route. '
113
+                    'It may include workspace_id '
114
+                    'which must be replaced on backend size '
115
+                    '(the route must be ready-to-use)'
116
+    )
117
+    icon = marshmallow.fields.String(
118
+        example='file-text-o',
119
+        description='CSS class of the icon. Example: file-o for using Fontawesome file-text-o icon',  # nopep8
120
+    )
121
+    hexcolor = marshmallow.fields.String(
122
+        example='#F0F9DC',
123
+        description='Hexadecimal color of the entry.'
80
     )
124
     )
81
 
125
 
126
+    class Meta:
127
+        description = 'Entry element of a workspace menu'
128
+
82
 
129
 
83
 class WorkspaceDigestSchema(marshmallow.Schema):
130
 class WorkspaceDigestSchema(marshmallow.Schema):
84
-    id = marshmallow.fields.Int()
85
-    label = marshmallow.fields.String()
131
+    id = marshmallow.fields.Int(example=4)
132
+    label = marshmallow.fields.String(example='Intranet')
86
     sidebar_entries = marshmallow.fields.Nested(
133
     sidebar_entries = marshmallow.fields.Nested(
87
         WorkspaceMenuEntrySchema,
134
         WorkspaceMenuEntrySchema,
88
         many=True,
135
         many=True,
89
     )
136
     )
90
 
137
 
138
+    class Meta:
139
+        description = 'Digest of workspace informations'
140
+
141
+
142
+class WorkspaceSchema(WorkspaceDigestSchema):
143
+    slug = marshmallow.fields.String(example='intranet')
144
+    description = marshmallow.fields.String(example='All intranet data.')
145
+
146
+    class Meta:
147
+        description = 'Full workspace informations'
148
+
91
 
149
 
92
 class WorkspaceMemberSchema(marshmallow.Schema):
150
 class WorkspaceMemberSchema(marshmallow.Schema):
93
-    role_id = marshmallow.fields.Int(validate=OneOf(UserRoleInWorkspace.get_all_role_values()))  # nopep8
94
-    role_slug = marshmallow.fields.String(validate=OneOf(UserRoleInWorkspace.get_all_role_slug()))  # nopep8
95
-    user_id = marshmallow.fields.Int()
96
-    workspace_id = marshmallow.fields.Int()
151
+    role_slug = marshmallow.fields.String(
152
+        example='contributor',
153
+        validate=OneOf(UserRoleInWorkspace.get_all_role_slug())
154
+    )
155
+    user_id = marshmallow.fields.Int(example=3)
156
+    workspace_id = marshmallow.fields.Int(example=4)
97
     user = marshmallow.fields.Nested(
157
     user = marshmallow.fields.Nested(
98
         UserSchema(only=('display_name', 'avatar_url'))
158
         UserSchema(only=('display_name', 'avatar_url'))
99
     )
159
     )
100
 
160
 
161
+    class Meta:
162
+        description = 'Workspace Member information'
163
+
101
 
164
 
102
 class ApplicationConfigSchema(marshmallow.Schema):
165
 class ApplicationConfigSchema(marshmallow.Schema):
103
     pass
166
     pass
105
 
168
 
106
 
169
 
107
 class ApplicationSchema(marshmallow.Schema):
170
 class ApplicationSchema(marshmallow.Schema):
108
-    label = marshmallow.fields.String()
109
-    slug = marshmallow.fields.String()
110
-    icon = marshmallow.fields.String()
111
-    hexcolor = marshmallow.fields.String()
112
-    is_active = marshmallow.fields.Boolean()
171
+    label = marshmallow.fields.String(example='Calendar')
172
+    slug = marshmallow.fields.String(example='calendar')
173
+    icon = marshmallow.fields.String(
174
+        example='file-o',
175
+        description='CSS class of the icon. Example: file-o for using Fontawesome file-o icon',  # nopep8
176
+    )
177
+    hexcolor = marshmallow.fields.String(
178
+        example='#FF0000',
179
+        description='HTML encoded color associated to the application. Example:#FF0000 for red'  # nopep8
180
+    )
181
+    is_active = marshmallow.fields.Boolean(
182
+        example=True,
183
+        description='if true, the application is in use in the context',
184
+    )
113
     config = marshmallow.fields.Nested(
185
     config = marshmallow.fields.Nested(
114
         ApplicationConfigSchema,
186
         ApplicationConfigSchema,
115
     )
187
     )
188
+
189
+    class Meta:
190
+        description = 'Tracim Application informations'

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

11
     from http import client as HTTPStatus
11
     from http import client as HTTPStatus
12
 
12
 
13
 from tracim import hapic, TracimRequest
13
 from tracim import hapic, TracimRequest
14
-from tracim.exceptions import NotAuthentificated, InsufficientUserProfile, \
15
-    UserNotExist
16
-from tracim.lib.core.user import UserApi
14
+
15
+from tracim.exceptions import NotAuthentificated
16
+from tracim.exceptions import InsufficientUserProfile
17
+from tracim.exceptions import UserDoesNotExist
17
 from tracim.lib.core.workspace import WorkspaceApi
18
 from tracim.lib.core.workspace import WorkspaceApi
18
 from tracim.views.controllers import Controller
19
 from tracim.views.controllers import Controller
19
-from tracim.views.core_api.schemas import UserIdPathSchema, \
20
-    WorkspaceDigestSchema
20
+from tracim.views.core_api.schemas import UserIdPathSchema
21
+from tracim.views.core_api.schemas import WorkspaceDigestSchema
21
 
22
 
22
 
23
 
23
 class UserController(Controller):
24
 class UserController(Controller):
25
     @hapic.with_api_doc()
26
     @hapic.with_api_doc()
26
     @hapic.handle_exception(NotAuthentificated, HTTPStatus.UNAUTHORIZED)
27
     @hapic.handle_exception(NotAuthentificated, HTTPStatus.UNAUTHORIZED)
27
     @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
28
     @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
28
-    @hapic.handle_exception(UserNotExist, HTTPStatus.NOT_FOUND)
29
+    @hapic.handle_exception(UserDoesNotExist, HTTPStatus.NOT_FOUND)
29
     @require_same_user_or_profile(Group.TIM_ADMIN)
30
     @require_same_user_or_profile(Group.TIM_ADMIN)
30
     @hapic.input_path(UserIdPathSchema())
31
     @hapic.input_path(UserIdPathSchema())
31
     @hapic.output_body(WorkspaceDigestSchema(many=True),)
32
     @hapic.output_body(WorkspaceDigestSchema(many=True),)
39
             session=request.dbsession,
40
             session=request.dbsession,
40
             config=app_config,
41
             config=app_config,
41
         )
42
         )
43
+        
44
+        workspaces = wapi.get_all_for_user(request.candidate_user)
42
         return [
45
         return [
43
             WorkspaceInContext(workspace, request.dbsession, app_config)
46
             WorkspaceInContext(workspace, request.dbsession, app_config)
44
-            for workspace in wapi.get_all_for_user(request.candidate_user)
47
+            for workspace in workspaces
45
         ]
48
         ]
46
 
49
 
47
     def bind(self, configurator: Configurator) -> None:
50
     def bind(self, configurator: Configurator) -> None:

+ 14 - 10
tracim/views/core_api/workspace_controller.py 查看文件

5
 
5
 
6
 from tracim.lib.core.userworkspace import RoleApi
6
 from tracim.lib.core.userworkspace import RoleApi
7
 from tracim.lib.utils.authorization import require_workspace_role
7
 from tracim.lib.utils.authorization import require_workspace_role
8
-from tracim.models.context_models import WorkspaceInContext, \
9
-    UserRoleWorkspaceInContext
8
+from tracim.models.context_models import WorkspaceInContext
9
+from tracim.models.context_models import UserRoleWorkspaceInContext
10
 from tracim.models.data import UserRoleInWorkspace
10
 from tracim.models.data import UserRoleInWorkspace
11
 
11
 
12
 try:  # Python 3.5+
12
 try:  # Python 3.5+
15
     from http import client as HTTPStatus
15
     from http import client as HTTPStatus
16
 
16
 
17
 from tracim import hapic, TracimRequest
17
 from tracim import hapic, TracimRequest
18
-from tracim.exceptions import NotAuthentificated, InsufficientUserProfile, \
19
-    WorkspaceNotFound
18
+from tracim.exceptions import NotAuthentificated
19
+from tracim.exceptions import InsufficientUserProfile
20
+from tracim.exceptions import WorkspaceNotFound
20
 from tracim.lib.core.user import UserApi
21
 from tracim.lib.core.user import UserApi
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
-    WorkspaceIdPathSchema, WorkspaceMemberSchema
25
-
24
+from tracim.views.core_api.schemas import WorkspaceSchema
25
+from tracim.views.core_api.schemas import UserSchema
26
+from tracim.views.core_api.schemas import WorkspaceIdPathSchema
27
+from tracim.views.core_api.schemas import WorkspaceMemberSchema
26
 
28
 
27
 class WorkspaceController(Controller):
29
 class WorkspaceController(Controller):
28
 
30
 
68
             session=request.dbsession,
70
             session=request.dbsession,
69
             config=app_config,
71
             config=app_config,
70
         )
72
         )
73
+        
74
+        roles = rapi.get_all_for_workspace(request.current_workspace)
71
         return [
75
         return [
72
             rapi.get_user_role_workspace_with_context(user_role)
76
             rapi.get_user_role_workspace_with_context(user_role)
73
-            for user_role in rapi.get_all_for_workspace(request.current_workspace)
77
+            for user_role in roles
74
         ]
78
         ]
75
 
79
 
76
     def bind(self, configurator: Configurator) -> None:
80
     def bind(self, configurator: Configurator) -> None:
77
         """
81
         """
78
-        Create all routes and views using pyramid configurator
79
-        for this controller
82
+        Create all routes and views using
83
+        pyramid configurator for this controller
80
         """
84
         """
81
 
85
 
82
         # Applications
86
         # Applications