瀏覽代碼

Merge branch 'develop' into feature/734_add_lang_to_user

inkhey 6 年之前
父節點
當前提交
3cc9bd525a
沒有帳戶連結到提交者的電子郵件
共有 100 個文件被更改,包括 1048 次插入578 次删除
  1. 8 0
      .gitignore
  2. 50 13
      README.md
  3. 4 1
      backend/README.md
  4. 6 0
      backend/color-test.json
  5. 9 0
      backend/development.ini.sample
  6. 76 8
      backend/doc/setting.md
  7. 0 1
      backend/setup.py
  8. 19 0
      backend/tests_configs.ini
  9. 76 0
      backend/tracim_backend/app_models/applications.py
  10. 53 86
      backend/tracim_backend/app_models/contents.py
  11. 11 0
      backend/tracim_backend/app_models/validator.py
  12. 33 0
      backend/tracim_backend/app_models/workspace_menu_entries.py
  13. 149 4
      backend/tracim_backend/config.py
  14. 6 0
      backend/tracim_backend/exceptions.py
  15. 11 0
      backend/tracim_backend/extensions.py
  16. 1 1
      backend/tracim_backend/fixtures/content.py
  17. 70 0
      backend/tracim_backend/lib/core/application.py
  18. 4 4
      backend/tracim_backend/lib/core/content.py
  19. 1 1
      backend/tracim_backend/lib/mail_notifier/notifier.py
  20. 1 1
      backend/tracim_backend/lib/utils/authentification.py
  21. 5 7
      backend/tracim_backend/lib/utils/authorization.py
  22. 1 1
      backend/tracim_backend/lib/utils/request.py
  23. 67 6
      backend/tracim_backend/lib/utils/utils.py
  24. 1 1
      backend/tracim_backend/lib/webdav/dav_provider.py
  25. 1 1
      backend/tracim_backend/lib/webdav/design.py
  26. 1 1
      backend/tracim_backend/lib/webdav/resources.py
  27. 1 1
      backend/tracim_backend/lib/webdav/utils.py
  28. 1 1
      backend/tracim_backend/models/__init__.py
  29. 0 117
      backend/tracim_backend/models/applications.py
  30. 8 4
      backend/tracim_backend/models/context_models.py
  31. 5 5
      backend/tracim_backend/models/data.py
  32. 0 71
      backend/tracim_backend/models/workspace_menu_entries.py
  33. 1 1
      backend/tracim_backend/tests/__init__.py
  34. 1 1
      backend/tracim_backend/tests/functional/test_contents.py
  35. 1 1
      backend/tracim_backend/tests/functional/test_mail_notification.py
  36. 10 2
      backend/tracim_backend/tests/functional/test_system.py
  37. 27 39
      backend/tracim_backend/tests/functional/test_user.py
  38. 46 42
      backend/tracim_backend/tests/functional/test_workspaces.py
  39. 1 1
      backend/tracim_backend/tests/library/test_content_api.py
  40. 3 10
      backend/tracim_backend/tests/library/test_webdav.py
  41. 1 1
      backend/tracim_backend/tests/models/test_content.py
  42. 1 1
      backend/tracim_backend/tests/models/test_content_revision.py
  43. 2 2
      backend/tracim_backend/views/contents_api/comment_controller.py
  44. 17 17
      backend/tracim_backend/views/contents_api/file_controller.py
  45. 6 6
      backend/tracim_backend/views/contents_api/folder_controller.py
  46. 7 7
      backend/tracim_backend/views/contents_api/html_document_controller.py
  47. 7 7
      backend/tracim_backend/views/contents_api/threads_controller.py
  48. 12 10
      backend/tracim_backend/views/core_api/schemas.py
  49. 1 1
      backend/tracim_backend/views/core_api/session_controller.py
  50. 9 4
      backend/tracim_backend/views/core_api/system_controller.py
  51. 2 2
      backend/tracim_backend/views/core_api/user_controller.py
  52. 2 2
      backend/tracim_backend/views/core_api/workspace_controller.py
  53. 9 3
      backend/tracim_backend/views/frontend.py
  54. 10 0
      backend/wsgidav-test.conf
  55. 5 0
      backend_lib.sh
  56. 5 0
      bash_library.sh
  57. 3 3
      color.json.sample
  58. 18 18
      frontend/dist/index.mak
  59. 31 6
      frontend/i18next.scanner/en/translation.json
  60. 31 6
      frontend/i18next.scanner/fr/translation.json
  61. 3 4
      frontend/src/component/Dashboard/MemberList.styl
  62. 1 2
      frontend/src/component/Dashboard/RecentActivity.styl
  63. 11 4
      frontend/src/component/Header/MenuLinkList.jsx
  64. 1 1
      frontend/src/component/Workspace/BtnExtandedAction.jsx
  65. 1 1
      frontend/src/component/Workspace/ContentItem.jsx
  66. 3 2
      frontend/src/component/common/Input/DropdownCreateButton.jsx
  67. 20 8
      frontend/src/container/AdminWorkspacePage.jsx
  68. 6 5
      frontend/src/container/Home.jsx
  69. 1 1
      frontend/src/css/Generic.styl
  70. 8 1
      frontend_app_html-document/i18next.scanner/en/translation.json
  71. 8 1
      frontend_app_html-document/i18next.scanner/fr/translation.json
  72. 7 6
      frontend_app_html-document/src/component/HtmlDocument.jsx
  73. 6 1
      frontend_lib/i18next.scanner/en/translation.json
  74. 6 1
      frontend_lib/i18next.scanner/fr/translation.json
  75. 1 1
      frontend_lib/src/component/Input/BtnSwitch/BtnSwitch.jsx
  76. 8 7
      frontend_lib/src/component/Timeline/Timeline.jsx
  77. 1 1
      frontend_lib/src/component/Timeline/Timeline.styl
  78. 6 0
      functionnal_tests/cypress.json.sample
  79. 0 0
      functionnal_tests/cypress_test/account/account_main_page.js
  80. 0 0
      functionnal_tests/cypress_test/account/content_account.js
  81. 0 0
      functionnal_tests/cypress_test/admin/content_admin_user.js
  82. 0 0
      functionnal_tests/cypress_test/admin/content_admin_workspace.js
  83. 0 0
      functionnal_tests/cypress_test/admin/navigation_admin_user.js
  84. 0 0
      functionnal_tests/cypress_test/admin/navigation_admin_workspace.js
  85. 0 0
      functionnal_tests/cypress_test/app_file/navigation_create_file.js
  86. 0 0
      functionnal_tests/cypress_test/app_folder/navigation_create_folder.js
  87. 0 0
      functionnal_tests/cypress_test/app_html-document/navigation_create_html-document.js
  88. 2 2
      functionnal_tests/cypress_test/app_html-document/operation_create_html-document.js
  89. 0 0
      functionnal_tests/cypress_test/app_thread/navigation_create_thread.js
  90. 0 0
      functionnal_tests/cypress_test/app_thread/operation_create_thread.js
  91. 0 0
      functionnal_tests/cypress_test/home_page/content_home_page.js
  92. 0 0
      functionnal_tests/cypress_test/interface/function_change_color.js
  93. 0 0
      functionnal_tests/cypress_test/login/content_login_page.js
  94. 0 0
      functionnal_tests/cypress_test/login/navigation_from_home_page_to_login_page.js
  95. 0 0
      functionnal_tests/cypress_test/login/navigation_from_login_page_to_home_page.js
  96. 0 0
      functionnal_tests/cypress_test/login/navigation_redirect_login_page.js
  97. 0 0
      functionnal_tests/cypress_test/workspace/content_workspace_dashboard.js
  98. 0 0
      functionnal_tests/cypress_test/workspace/navigation_create_workspace.js
  99. 0 0
      functionnal_tests/cypress_test/workspace/navigation_dashbord_link-for-calendar.js
  100. 0 0
      functionnal_tests/cypress_test/workspace/navigation_dashbord_link-for-webdav.js

+ 8 - 0
.gitignore 查看文件

6
 frontend_lib/dist/tracim_frontend_lib.js
6
 frontend_lib/dist/tracim_frontend_lib.js
7
 npm-debug.log
7
 npm-debug.log
8
 package-lock.json
8
 package-lock.json
9
+color.json
10
+
11
+#ignore file or folder about cypress tests
12
+functionnal_tests/package.json
13
+functionnal_tests/cypress.json
14
+functionnal_tests/node_modules/
15
+functionnal_tests/cypress/
16
+

+ 50 - 13
README.md 查看文件

3
 [![Coverage Status](https://coveralls.io/repos/github/tracim/tracim_v2/badge.svg?branch=develop)](https://coveralls.io/github/tracim/tracim_v2?branch=develop)
3
 [![Coverage Status](https://coveralls.io/repos/github/tracim/tracim_v2/badge.svg?branch=develop)](https://coveralls.io/github/tracim/tracim_v2?branch=develop)
4
 [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/tracim/tracim_v2/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/tracim/tracim_v2/?branch=develop)
4
 [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/tracim/tracim_v2/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/tracim/tracim_v2/?branch=develop)
5
 
5
 
6
+
6
 ## Install Tracim on your server ##
7
 ## Install Tracim on your server ##
7
 
8
 
8
 Following the installation documentation below, you'll be able to run your own instance on your server.
9
 Following the installation documentation below, you'll be able to run your own instance on your server.
13
 
14
 
14
 ## Get the source ##
15
 ## Get the source ##
15
 
16
 
16
-Get the sources from GitHub:
17
+Get the sources from GitHub (you need git):
17
 
18
 
18
     git clone https://github.com/tracim/tracim_v2.git
19
     git clone https://github.com/tracim/tracim_v2.git
19
     cd tracim_v2/
20
     cd tracim_v2/
20
 
21
 
21
-## Install backend ##
22
+## Install backend
23
+### Option 1: Install backend manually ###
24
+
25
+see [Backend README](backend/README.md)
26
+
27
+### Option2: Install backend: Automated script for easy setup ###
28
+
29
+This script run backend with simple default conf: development.ini conf file, use
30
+default config file, sqlite database, etc...
22
 
31
 
23
     ./setup_default_backend.sh
32
     ./setup_default_backend.sh
24
 
33
 
25
-For debugging you can uncomment this 2 lines in '/backend/development.ini'
26
-    
27
-    ~~~
28
-    #pyramid.includes =
29
-    #    pyramid_debugtoolbar
30
-    ~~~
34
+For each conf file missing, this script will generated them from default conf.
35
+If sqlite default database is missing, script will generate it.
36
+This script is also able to serve for update. If you want to update a script
37
+generated tracim install, you can just update source code with git pull and
38
+rerun the same script to update database model, system deps and python deps.
39
+
40
+for more information about configuring tracim_backend, see [Backend README](backend/README.md)
41
+for more information about configuration file, see development.ini.sample documentation
42
+and [Backend setting file doc](backend/doc/setting.md).
31
 
43
 
32
-If you use debugtoolbar, you can seen one red button on right of the Tracim web interface.
33
 
44
 
34
-## Install frontend ##
45
+## Install frontend: Automated Script for easy setup ##
35
 
46
 
36
     ./install_frontend_dependencies.sh
47
     ./install_frontend_dependencies.sh
37
     ./build_full_frontend.sh
48
     ./build_full_frontend.sh
38
 
49
 
39
-## Running Tracim  ##
50
+## Running Tracim using pserve ##
40
 
51
 
41
     cd backend/
52
     cd backend/
42
     source env/bin/activate
53
     source env/bin/activate
45
 You can now enter the application at
56
 You can now enter the application at
46
 [http://127.0.0.1:6543](http://127.0.0.1:6543) and login with admin user:
57
 [http://127.0.0.1:6543](http://127.0.0.1:6543) and login with admin user:
47
 
58
 
48
- * user : `admin@admin.admin`
49
- * password : `admin@admin.admin`
59
+ * user: `admin@admin.admin`
60
+ * password: `admin@admin.admin`
61
+
62
+----
63
+
64
+## Running tests with cypress ##
65
+
66
+----
67
+
68
+## Installation of cypress: Automated script for easy setup ##
69
+
70
+This script check if nodejs is installed (npm is necessary to install Cypress), if file package.json and cypress.json exist in 'functionnal_tests' folder. if not the script install necessary file and install Cypress and his dependency's.
71
+
72
+    ./install_cypress.sh
73
+
74
+## Run tests with command line ##
75
+
76
+This command run all test present in 'cypress_test' folder.
77
+
78
+    cd functionnal_tests/
79
+    ./node_modules/.bin/cypress run
80
+
81
+## Run tests with cypressgui ##
82
+
83
+Open Cypress with graphical interface. You can show test running directly in web interface.
84
+
85
+    cd functionnal_tests/
86
+    ./node_modules/.bin/cypress open
50
 
87
 
51
 
88
 

+ 4 - 1
backend/README.md 查看文件

85
 
85
 
86
     cp wsgidav.conf.sample wsgidav.conf
86
     cp wsgidav.conf.sample wsgidav.conf
87
 
87
 
88
-## Run Tracim_backend With Uwsgi : great for production ##
88
+if not did before, you need to create a color.json file at root of tracim_v2 :
89
+   
90
+    cp ../color.json.sample ../color.json
89
 
91
 
92
+## Run Tracim_backend With Uwsgi : great for production ##
90
 
93
 
91
 #### Install Uwsgi
94
 #### Install Uwsgi
92
 
95
 

+ 6 - 0
backend/color-test.json 查看文件

1
+{
2
+  "primary": "#7d4e24",
3
+  "contents/html-document": "#3f52e3",
4
+  "contents/thread": "#ad4cf9",
5
+  "contents/file": "#ff9900"
6
+}

+ 9 - 0
backend/development.ini.sample 查看文件

35
 
35
 
36
 ### Global
36
 ### Global
37
 
37
 
38
+# Enable debug mode
39
+# debug = True
38
 cache_dir = %(here)s/data
40
 cache_dir = %(here)s/data
39
 # preview generator cache directory
41
 # preview generator cache directory
40
 preview_cache_dir = /tmp/tracim/preview/
42
 preview_cache_dir = /tmp/tracim/preview/
193
 # organisation.
195
 # organisation.
194
 # frontend.dist_folder_path = /home/user/tracim_v2/frontend/dist
196
 # frontend.dist_folder_path = /home/user/tracim_v2/frontend/dist
195
 
197
 
198
+### Color
199
+# check for color.json file in tracim_v2, check by default in tracim_v2 parent
200
+# dir of backend.
201
+# you can set a specific file path here
202
+# color.config_file_path = /home/user/tracim_v2/color.json
203
+
204
+
196
 ###
205
 ###
197
 # wsgi server configuration
206
 # wsgi server configuration
198
 ###
207
 ###

+ 76 - 8
backend/doc/setting.md 查看文件

1
-# Setting #
1
+# Settings #
2
 
2
 
3
-Here is a short description of settings available in the file `development.ini`.
3
+Here is a short description of settings available in backend config files.
4
 
4
 
5
-## Listening port ##
5
+# Tracim config ini file #
6
+
7
+This file is called'development.ini' file by default, it's located is backend
8
+subdir, default config is [development.ini.sample](../development.ini.sample) with some doc.
9
+
10
+## Listening port (for pserve only) ##
6
 
11
 
7
 Default configuration is to listen on port 6534.
12
 Default configuration is to listen on port 6534.
8
 If you want to adapt this to your environment, edit the `.ini` file and setup the port you want:
13
 If you want to adapt this to your environment, edit the `.ini` file and setup the port you want:
17
     ...
22
     ...
18
     listen = *:6534
23
     listen = *:6534
19
 
24
 
20
-## Prod/Debug configuration ##
25
+## Database path ##
26
+
27
+To configure a database, you need to provide a valid sqlalchemy url:
28
+
29
+for sqlite, a valid value is something like this:
30
+
31
+    sqlalchemy.url = sqlite:///%(here)s/tracim.sqlite
32
+
33
+to know more about this, see [sqlalchemy documentation](http://docs.sqlalchemy.org/en/latest/core/engines.html).
34
+
35
+Be carefull, if sqlalchemy support many kind of Database, Tracim support is **not** guarantee.
36
+Tracim is officially supporting sqlite, postgresql and mysql.
37
+
38
+## Debugging and Logs ##
39
+### Debug params ###
40
+
41
+
42
+For debugging you can uncomment this 2 lines in '/backend/development.ini' to
43
+enable pyramid debugtoolbar.
44
+If you use debugtoolbar, you can seen one red button on right of the Tracim web interface.
45
+
46
+    ~~~
47
+    #pyramid.includes =
48
+    #    pyramid_debugtoolbar
49
+    ~~~
50
+
51
+you can add this line to active pyramid debug mode for almost anything:
52
+
53
+    ~~~
54
+    pyramid.debug_all = true
55
+    ~~~
56
+
57
+
58
+Hapic debug mode: this line is needed for more explicit json error,
59
+raised error traceback will be send through json. you can uncomment it
60
+
61
+   ~~~
62
+   # debug = True
63
+   ~~~
64
+
65
+pyramid.reload_templates = true
66
+
67
+### Prod/Debug configuration example ###
21
 
68
 
22
 
69
 
23
 To enable simple debug conf:
70
 To enable simple debug conf:
24
 
71
 
25
-    [app:main]
72
+    [app:tracim_web]
26
     ...
73
     ...
27
     pyramid.reload_templates = true
74
     pyramid.reload_templates = true
28
     pyramid.debug_all = true
75
     pyramid.debug_all = true
29
     pyramid.includes =
76
     pyramid.includes =
30
         pyramid_debugtoolbar
77
         pyramid_debugtoolbar
31
 
78
 
79
+    [DEFAULT]
80
+    ...
81
+    debug = True
82
+
83
+
32
 production conf (no reload, no debugtoolbar):
84
 production conf (no reload, no debugtoolbar):
33
 
85
 
34
-    [app:main]
86
+    [app:tracim_web]
35
     ...
87
     ...
36
     pyramid.reload_templates = false
88
     pyramid.reload_templates = false
37
     pyramid.debug_authorization = false
89
     pyramid.debug_authorization = false
38
     pyramid.debug_notfound = false
90
     pyramid.debug_notfound = false
39
     pyramid.debug_routematch = false
91
     pyramid.debug_routematch = false
40
 
92
 
41
-You can, of course, also set level of one of the different logger to have more/less log
42
-about something.
93
+    [DEFAULT]
94
+    ...
95
+    debug = False
96
+
97
+You can, of course, also set level of one of the different logger
98
+to have more/less log about something.
43
 
99
 
44
     [logger_sqlalchemy]
100
     [logger_sqlalchemy]
45
     ...
101
     ...
46
     level = INFO
102
     level = INFO
103
+
104
+# Color File #
105
+
106
+You can change default color of apps by setting color.json file, by default,
107
+placed at root of tracim_v2 dir, see [color.json.sample](../../color.json.sample)
108
+for default config file.
109
+
110
+# Wsgidav File
111
+
112
+This file is by default placed in backend subdir,
113
+it by default called wsigdav.conf, for more information, see default
114
+conf file [wsigdav.conf.sample](../wsgidav.conf.sample).

+ 0 - 1
backend/setup.py 查看文件

43
     'rq',
43
     'rq',
44
     # frontend file serve
44
     # frontend file serve
45
     'pyramid_mako',
45
     'pyramid_mako',
46
-    'spectra',
47
 ]
46
 ]
48
 
47
 
49
 tests_require = [
48
 tests_require = [

+ 19 - 0
backend/tests_configs.ini 查看文件

5
 user.auth_token.validity = 604800
5
 user.auth_token.validity = 604800
6
 preview_cache_dir = /tmp/test/preview_cache
6
 preview_cache_dir = /tmp/test/preview_cache
7
 website.base_url = http://localhost:6543
7
 website.base_url = http://localhost:6543
8
+color.config_file_path = %(here)s/color-test.json
9
+
8
 [app:command_test]
10
 [app:command_test]
9
 use = egg:tracim_backend
11
 use = egg:tracim_backend
10
 sqlalchemy.url = sqlite:///tracim_test.sqlite
12
 sqlalchemy.url = sqlite:///tracim_test.sqlite
13
 user.auth_token.validity = 604800
15
 user.auth_token.validity = 604800
14
 preview_cache_dir = /tmp/test/preview_cache
16
 preview_cache_dir = /tmp/test/preview_cache
15
 website.base_url = http://localhost:6543
17
 website.base_url = http://localhost:6543
18
+color.config_file_path = %(here)s/color-test.json
16
 
19
 
17
 [mail_test]
20
 [mail_test]
18
 sqlalchemy.url = sqlite:///:memory:
21
 sqlalchemy.url = sqlite:///:memory:
39
 email.notification.smtp.user = test_user
42
 email.notification.smtp.user = test_user
40
 email.notification.smtp.password = just_a_password
43
 email.notification.smtp.password = just_a_password
41
 website.base_url = http://localhost:6543
44
 website.base_url = http://localhost:6543
45
+color.config_file_path = %(here)s/color-test.json
42
 
46
 
43
 [mail_test_async]
47
 [mail_test_async]
44
 sqlalchemy.url = sqlite:///:memory:
48
 sqlalchemy.url = sqlite:///:memory:
66
 email.notification.smtp.user = test_user
70
 email.notification.smtp.user = test_user
67
 email.notification.smtp.password = just_a_password
71
 email.notification.smtp.password = just_a_password
68
 website.base_url = http://localhost:6543
72
 website.base_url = http://localhost:6543
73
+color.config_file_path = %(here)s/color-test.json
69
 
74
 
70
 [functional_test]
75
 [functional_test]
71
 sqlalchemy.url = sqlite:///tracim_test.sqlite
76
 sqlalchemy.url = sqlite:///tracim_test.sqlite
76
 preview.jpg.restricted_dims = True
81
 preview.jpg.restricted_dims = True
77
 email.notification.activated = false
82
 email.notification.activated = false
78
 website.base_url = http://localhost:6543
83
 website.base_url = http://localhost:6543
84
+color.config_file_path = %(here)s/color-test.json
79
 
85
 
80
 [functional_test_no_db]
86
 [functional_test_no_db]
81
 sqlalchemy.url = sqlite://
87
 sqlalchemy.url = sqlite://
86
 preview.jpg.restricted_dims = True
92
 preview.jpg.restricted_dims = True
87
 email.notification.activated = false
93
 email.notification.activated = false
88
 website.base_url = http://localhost:6543
94
 website.base_url = http://localhost:6543
95
+color.config_file_path = %(here)s/color-test.json
89
 
96
 
90
 [functional_test_with_mail_test_sync]
97
 [functional_test_with_mail_test_sync]
91
 sqlalchemy.url = sqlite:///tracim_test.sqlite
98
 sqlalchemy.url = sqlite:///tracim_test.sqlite
111
 email.notification.smtp.user = test_user
118
 email.notification.smtp.user = test_user
112
 email.notification.smtp.password = just_a_password
119
 email.notification.smtp.password = just_a_password
113
 website.base_url = http://localhost:6543
120
 website.base_url = http://localhost:6543
121
+color.config_file_path = %(here)s/color-test.json
114
 
122
 
115
 [functional_test_with_mail_test_async]
123
 [functional_test_with_mail_test_async]
116
 sqlalchemy.url = sqlite:///tracim_test.sqlite
124
 sqlalchemy.url = sqlite:///tracim_test.sqlite
136
 email.notification.smtp.user = test_user
144
 email.notification.smtp.user = test_user
137
 email.notification.smtp.password = just_a_password
145
 email.notification.smtp.password = just_a_password
138
 website.base_url = http://localhost:6543
146
 website.base_url = http://localhost:6543
147
+color.config_file_path = %(here)s/color-test.json
148
+
149
+[webdav_test]
150
+website.base_url = http://localhost:6543
151
+sqlalchemy.url = sqlite:///:memory:
152
+user.auth_token.validity = 604800
153
+depot_storage_dir = /tmp/test/depot
154
+depot_storage_name = test
155
+preview_cache_dir = /tmp/test/preview_cache
156
+color.config_file_path = %(here)s/color-test.json
157
+wsgidav.config_path = %(here)s/wsgidav-test.conf

+ 76 - 0
backend/tracim_backend/app_models/applications.py 查看文件

1
+# coding=utf-8
2
+import typing
3
+
4
+from tracim_backend.app_models.contents import ContentType
5
+if typing.TYPE_CHECKING:
6
+    from tracim_backend.config import CFG
7
+    from tracim_backend.app_models.contents import ContentStatus
8
+
9
+
10
+class Application(object):
11
+    """
12
+    Application class with data needed for frontend
13
+    """
14
+    def __init__(
15
+            self,
16
+            label: str,
17
+            slug: str,
18
+            fa_icon: str,
19
+            is_active: bool,
20
+            config: typing.Dict[str, str],
21
+            main_route: str,
22
+            app_config: 'CFG',
23
+    ) -> None:
24
+        """
25
+        @param label: public label of application
26
+        @param slug: identifier of application
27
+        @param fa_icon: font awesome icon class
28
+        @param is_active: True if application enable, False if inactive
29
+        @param config: a dict with eventual application config
30
+        @param main_route: the route of the frontend "home" screen of
31
+        the application. For exemple, if you have an application
32
+        called "calendar", the main route will be something
33
+        like /#/workspace/{wid}/calendar.
34
+        """
35
+        self.label = label
36
+        self.slug = slug
37
+        self.fa_icon = fa_icon
38
+        self.hexcolor = self._get_hexcolor_or_default(slug, app_config)
39
+        self.is_active = is_active
40
+        self.config = config
41
+        self.main_route = main_route
42
+        self.content_types = []
43
+
44
+    # TODO - G.M - 2018-08-07 - Refactor slug coherence issue like this one.
45
+    # we probably should not have 2 kind of slug
46
+    @property
47
+    def minislug(self):
48
+        return self.slug.replace('contents/', '')
49
+
50
+    def add_content_type(
51
+            self,
52
+            label: str,
53
+            slug: str,
54
+            creation_label: str,
55
+            available_statuses: typing.List['ContentStatus'],
56
+            slug_alias: typing.List[str] = None,
57
+            allow_sub_content: bool = False,
58
+    ):
59
+        content_type = ContentType(
60
+            slug=slug,
61
+            fa_icon=self.fa_icon,
62
+            label=label,
63
+            hexcolor=self.hexcolor,
64
+            creation_label=creation_label,
65
+            available_statuses=available_statuses,
66
+            slug_alias=slug_alias,
67
+            allow_sub_content=allow_sub_content,
68
+        )
69
+        self.content_types.append(content_type)
70
+
71
+    def _get_hexcolor_or_default(self, slug: str, app_config: 'CFG') -> str:
72
+        assert app_config.APPS_COLORS
73
+        assert 'primary' in app_config.APPS_COLORS
74
+        if slug in app_config.APPS_COLORS:
75
+            return app_config.APPS_COLORS[slug]
76
+        return app_config.APPS_COLORS['primary']

backend/tracim_backend/models/contents.py → backend/tracim_backend/app_models/contents.py 查看文件

2
 import typing
2
 import typing
3
 from enum import Enum
3
 from enum import Enum
4
 
4
 
5
+from tracim_backend.extensions import app_list
5
 from tracim_backend.exceptions import ContentTypeNotExist
6
 from tracim_backend.exceptions import ContentTypeNotExist
6
 from tracim_backend.exceptions import ContentStatusNotExist
7
 from tracim_backend.exceptions import ContentStatusNotExist
7
-from tracim_backend.models.applications import html_documents
8
-from tracim_backend.models.applications import _file
9
-from tracim_backend.models.applications import folder
10
-from tracim_backend.models.applications import thread
11
-from tracim_backend.models.applications import markdownpluspage
12
-
13
 
8
 
14
 ####
9
 ####
15
 # Content Status
10
 # Content Status
11
+from tracim_backend.lib.core.application import ApplicationApi
12
+if typing.TYPE_CHECKING:
13
+    from tracim_backend.app_models.applications import Application
16
 
14
 
17
 
15
 
18
 class GlobalStatus(Enum):
16
 class GlobalStatus(Enum):
134
         self.allow_sub_content = allow_sub_content
132
         self.allow_sub_content = allow_sub_content
135
 
133
 
136
 
134
 
137
-thread_type = ContentType(
138
-    slug='thread',
139
-    fa_icon=thread.fa_icon,
140
-    hexcolor=thread.hexcolor,
141
-    label='Thread',
142
-    creation_label='Discuss about a topic',
143
-    available_statuses=CONTENT_STATUS.get_all(),
144
-)
145
-
146
-file_type = ContentType(
147
-    slug='file',
148
-    fa_icon=_file.fa_icon,
149
-    hexcolor=_file.hexcolor,
150
-    label='File',
151
-    creation_label='Upload a file',
152
-    available_statuses=CONTENT_STATUS.get_all(),
153
-)
154
-
155
-markdownpluspage_type = ContentType(
156
-    slug='markdownpage',
157
-    fa_icon=markdownpluspage.fa_icon,
158
-    hexcolor=markdownpluspage.hexcolor,
159
-    label='Rich Markdown File',
160
-    creation_label='Create a Markdown document',
161
-    available_statuses=CONTENT_STATUS.get_all(),
162
-)
163
-
164
-html_documents_type = ContentType(
165
-    slug='html-document',
166
-    fa_icon=html_documents.fa_icon,
167
-    hexcolor=html_documents.hexcolor,
168
-    label='Text Document',
169
-    creation_label='Write a document',
170
-    available_statuses=CONTENT_STATUS.get_all(),
171
-    slug_alias=['page']
172
-)
173
-
174
-# TODO - G.M - 31-05-2018 - Set Better folder params
175
-folder_type = ContentType(
176
-    slug='folder',
177
-    fa_icon=folder.fa_icon,
178
-    hexcolor=folder.hexcolor,
179
-    label='Folder',
180
-    creation_label='Create a folder',
181
-    available_statuses=CONTENT_STATUS.get_all(),
182
-    allow_sub_content=True,
183
-)
184
-
135
+THREAD_TYPE = 'thread'
136
+FILE_TYPE = 'file'
137
+MARKDOWNPLUSPAGE_TYPE = 'markdownpage'
138
+HTML_DOCUMENTS_TYPE = 'html-document'
139
+FOLDER_TYPE = 'folder'
185
 
140
 
186
 # TODO - G.M - 31-05-2018 - Set Better Event params
141
 # TODO - G.M - 31-05-2018 - Set Better Event params
187
 event_type = ContentType(
142
 event_type = ContentType(
188
     slug='event',
143
     slug='event',
189
-    fa_icon=thread.fa_icon,
190
-    hexcolor=thread.hexcolor,
144
+    fa_icon='',
145
+    hexcolor='',
191
     label='Event',
146
     label='Event',
192
     creation_label='Event',
147
     creation_label='Event',
193
     available_statuses=CONTENT_STATUS.get_all(),
148
     available_statuses=CONTENT_STATUS.get_all(),
196
 # TODO - G.M - 31-05-2018 - Set Better Event params
151
 # TODO - G.M - 31-05-2018 - Set Better Event params
197
 comment_type = ContentType(
152
 comment_type = ContentType(
198
     slug='comment',
153
     slug='comment',
199
-    fa_icon=thread.fa_icon,
200
-    hexcolor=thread.hexcolor,
154
+    fa_icon='',
155
+    hexcolor='',
201
     label='Comment',
156
     label='Comment',
202
     creation_label='Comment',
157
     creation_label='Comment',
203
     available_statuses=CONTENT_STATUS.get_all(),
158
     available_statuses=CONTENT_STATUS.get_all(),
209
     ContentType List
164
     ContentType List
210
     """
165
     """
211
     Any_SLUG = 'any'
166
     Any_SLUG = 'any'
212
-    Folder = folder_type
213
     Comment = comment_type
167
     Comment = comment_type
214
     Event = event_type
168
     Event = event_type
215
-    File = file_type
216
-    Page = html_documents_type
217
-    Thread = thread_type
218
 
169
 
219
-    def __init__(self, extend_content_status: typing.List[ContentType]):
220
-        self._content_types = [self.Folder]
221
-        self._content_types.extend(extend_content_status)
170
+    @property
171
+    def Folder(self):
172
+        return self.get_one_by_slug(FOLDER_TYPE)
173
+
174
+    @property
175
+    def File(self):
176
+        return self.get_one_by_slug(FILE_TYPE)
177
+
178
+    @property
179
+    def Page(self):
180
+        return self.get_one_by_slug(HTML_DOCUMENTS_TYPE)
181
+
182
+    @property
183
+    def Thread(self):
184
+        return self.get_one_by_slug(THREAD_TYPE)
185
+
186
+    def __init__(self, app_list: typing.List['Application']):
187
+        self.app_list = app_list
222
         self._special_contents_types = [self.Comment]
188
         self._special_contents_types = [self.Comment]
223
         self._extra_slugs = [self.Any_SLUG]
189
         self._extra_slugs = [self.Any_SLUG]
224
 
190
 
191
+    @property
192
+    def _content_types(self):
193
+        app_api = ApplicationApi(self.app_list)
194
+        content_types = app_api.get_content_types()
195
+        return content_types
196
+
225
     def get_one_by_slug(self, slug: str) -> ContentType:
197
     def get_one_by_slug(self, slug: str) -> ContentType:
226
         """
198
         """
227
         Get ContentType object according to slug
199
         Get ContentType object according to slug
235
                 return item
207
                 return item
236
         raise ContentTypeNotExist()
208
         raise ContentTypeNotExist()
237
 
209
 
238
-    def endpoint_allowed_types_slug(self) -> typing.List[str]:
210
+    def restricted_allowed_types_slug(self) -> typing.List[str]:
239
         """
211
         """
240
-        Return restricted list of content_type:
241
-        dont return special content_type like  comment, don't return
212
+        Return restricted list of content_type: don't return
242
         "any" slug, dont return content type slug alias , don't return event.
213
         "any" slug, dont return content type slug alias , don't return event.
243
         Useful to restrict slug param in schema.
214
         Useful to restrict slug param in schema.
244
         """
215
         """
245
         allowed_type_slug = [contents_type.slug for contents_type in self._content_types]  # nopep8
216
         allowed_type_slug = [contents_type.slug for contents_type in self._content_types]  # nopep8
246
         return allowed_type_slug
217
         return allowed_type_slug
247
 
218
 
248
-    def extended_endpoint_allowed_types_slug(self) -> typing.List[str]:
249
-        allowed_types_slug = self.endpoint_allowed_types_slug().copy()
250
-        for content_type in self._special_contents_types:
251
-            allowed_types_slug.append(content_type.slug)
252
-        return allowed_types_slug
219
+    def endpoint_allowed_types_slug(self) -> typing.List[str]:
220
+        """
221
+        Same as restricted_allowed_types_slug but with special content_type
222
+        included like comments.
223
+        """
224
+        content_types = self._content_types
225
+        content_types.extend(self._special_contents_types)
226
+        allowed_type_slug = [contents_type.slug for contents_type in content_types]  # nopep8
227
+        return allowed_type_slug
253
 
228
 
254
     def query_allowed_types_slugs(self) -> typing.List[str]:
229
     def query_allowed_types_slugs(self) -> typing.List[str]:
255
         """
230
         """
258
         Usefull allowed value to perform query to database.
233
         Usefull allowed value to perform query to database.
259
         """
234
         """
260
         allowed_types_slug = []
235
         allowed_types_slug = []
261
-        for content_type in self._content_types:
236
+        content_types = self._content_types
237
+        content_types.extend(self._special_contents_types)
238
+        for content_type in content_types:
262
             allowed_types_slug.append(content_type.slug)
239
             allowed_types_slug.append(content_type.slug)
263
             if content_type.slug_alias:
240
             if content_type.slug_alias:
264
                 allowed_types_slug.extend(content_type.slug_alias)
241
                 allowed_types_slug.extend(content_type.slug_alias)
265
-        for content_type in self._special_contents_types:
266
-            allowed_types_slug.append(content_type.slug)
267
         allowed_types_slug.extend(self._extra_slugs)
242
         allowed_types_slug.extend(self._extra_slugs)
268
         return allowed_types_slug
243
         return allowed_types_slug
269
 
244
 
270
     def default_allowed_content_properties(self, slug) -> dict:
245
     def default_allowed_content_properties(self, slug) -> dict:
271
         content_type = self.get_one_by_slug(slug)
246
         content_type = self.get_one_by_slug(slug)
272
         if content_type.allow_sub_content:
247
         if content_type.allow_sub_content:
273
-            sub_content_allowed = self.extended_endpoint_allowed_types_slug()
248
+            sub_content_allowed = self.endpoint_allowed_types_slug()
274
         else:
249
         else:
275
             sub_content_allowed = [self.Comment.slug]
250
             sub_content_allowed = [self.Comment.slug]
276
 
251
 
280
         return properties_dict
255
         return properties_dict
281
 
256
 
282
 
257
 
283
-CONTENT_TYPES = ContentTypeList(
284
-    [
285
-        thread_type,
286
-        file_type,
287
-        # TODO - G.M - 2018-08-02 - Restore markdown page content
288
-        #    markdownpluspage_type,
289
-        html_documents_type,
290
-    ]
291
-)
258
+CONTENT_TYPES = ContentTypeList(app_list)

+ 11 - 0
backend/tracim_backend/app_models/validator.py 查看文件

1
+from marshmallow.validate import OneOf
2
+from tracim_backend.app_models.contents import CONTENT_TYPES
3
+
4
+# TODO - G.M - 2018-08-08 - [GlobalVar] Refactor Global var
5
+# of tracim_backend, Be careful all_content_types_validator is a global_var !
6
+
7
+all_content_types_validator = OneOf(choices=[])
8
+
9
+
10
+def update_validators():
11
+    all_content_types_validator.choices = CONTENT_TYPES.endpoint_allowed_types_slug()  # nopep8

+ 33 - 0
backend/tracim_backend/app_models/workspace_menu_entries.py 查看文件

1
+class WorkspaceMenuEntry(object):
2
+    """
3
+    Application class with data needed for frontend
4
+    """
5
+    def __init__(
6
+            self,
7
+            label: str,
8
+            slug: str,
9
+            fa_icon: str,
10
+            hexcolor: str,
11
+            route: str,
12
+    ) -> None:
13
+        self.slug = slug
14
+        self.label = label
15
+        self.route = route
16
+        self.hexcolor = hexcolor
17
+        self.fa_icon = fa_icon
18
+
19
+
20
+dashboard_menu_entry = WorkspaceMenuEntry(
21
+  slug='dashboard',
22
+  label='Dashboard',
23
+  route='/#/workspaces/{workspace_id}/dashboard',
24
+  hexcolor='#252525',
25
+  fa_icon="signal",
26
+)
27
+all_content_menu_entry = WorkspaceMenuEntry(
28
+  slug="contents/all",
29
+  label="All Contents",
30
+  route="/#/workspaces/{workspace_id}/contents",
31
+  hexcolor="#fdfdfd",
32
+  fa_icon="th",
33
+)

+ 149 - 4
backend/tracim_backend/config.py 查看文件

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
+import json
2
 from urllib.parse import urlparse
3
 from urllib.parse import urlparse
3
 
4
 
4
 import os
5
 import os
5
 from paste.deploy.converters import asbool
6
 from paste.deploy.converters import asbool
7
+from tracim_backend.app_models.validator import update_validators
8
+from tracim_backend.extensions import app_list
6
 from tracim_backend.lib.utils.logger import logger
9
 from tracim_backend.lib.utils.logger import logger
7
 from depot.manager import DepotManager
10
 from depot.manager import DepotManager
8
-from tracim_backend.models.contents import CONTENT_TYPES
11
+from tracim_backend.app_models.applications import Application
12
+from tracim_backend.app_models.contents import CONTENT_TYPES
13
+from tracim_backend.app_models.contents import CONTENT_STATUS
9
 from tracim_backend.models.data import ActionDescription
14
 from tracim_backend.models.data import ActionDescription
10
 
15
 
11
 
16
 
12
-
13
 class CFG(object):
17
 class CFG(object):
14
     """Object used for easy access to config file parameters."""
18
     """Object used for easy access to config file parameters."""
15
 
19
 
41
         ###
45
         ###
42
         # General
46
         # General
43
         ###
47
         ###
48
+        backend_folder = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))  # nopep8
49
+        tracim_v2_folder = os.path.dirname(backend_folder)
50
+        default_color_config_file_path = os.path.join(tracim_v2_folder, 'color.json')  # nopep8
51
+        self.COLOR_CONFIG_FILE_PATH = settings.get(
52
+            'color.config_file_path', default_color_config_file_path
53
+        )
54
+        if not os.path.exists(self.COLOR_CONFIG_FILE_PATH):
55
+            raise Exception(
56
+                'ERROR: {} file does not exist. '
57
+                'please create it or set color.config_file_path'
58
+                'with a correct value'.format(self.COLOR_CONFIG_FILE_PATH)
59
+            )
60
+
61
+        try:
62
+            with open(self.COLOR_CONFIG_FILE_PATH) as json_file:
63
+                self.APPS_COLORS = json.load(json_file)
64
+        except Exception as e:
65
+            raise Exception(
66
+                'Error: {} file could not be load as json'.format(self.COLOR_CONFIG_FILE_PATH) # nopep8
67
+            ) from e
44
 
68
 
69
+        try:
70
+            self.APPS_COLORS['primary']
71
+        except KeyError as e:
72
+            raise Exception(
73
+                'Error: primary color is required in {} file'.format(
74
+                    self.COLOR_CONFIG_FILE_PATH)  # nopep8
75
+            ) from e
76
+
77
+        self._set_default_app()
45
         mandatory_msg = \
78
         mandatory_msg = \
46
             'ERROR: {} configuration is mandatory. Set it before continuing.'
79
             'ERROR: {} configuration is mandatory. Set it before continuing.'
47
         self.DEPOT_STORAGE_DIR = settings.get(
80
         self.DEPOT_STORAGE_DIR = settings.get(
450
         # INFO - G.M - 2018-08-06 - we pretend that frontend_dist_folder
483
         # INFO - G.M - 2018-08-06 - we pretend that frontend_dist_folder
451
         # is probably in frontend subfolder
484
         # is probably in frontend subfolder
452
         # of tracim_v2 parent of both backend and frontend
485
         # of tracim_v2 parent of both backend and frontend
453
-        backend_folder = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))  # nopep8
454
-        tracim_v2_folder = os.path.dirname(backend_folder)
455
         frontend_dist_folder = os.path.join(tracim_v2_folder, 'frontend', 'dist')  # nopep8
486
         frontend_dist_folder = os.path.join(tracim_v2_folder, 'frontend', 'dist')  # nopep8
456
 
487
 
457
         self.FRONTEND_DIST_FOLDER_PATH = settings.get(
488
         self.FRONTEND_DIST_FOLDER_PATH = settings.get(
467
             )
498
             )
468
 
499
 
469
     def configure_filedepot(self):
500
     def configure_filedepot(self):
501
+
502
+        # TODO - G.M - 2018-08-08 - [GlobalVar] Refactor Global var
503
+        # of tracim_backend, Be careful DepotManager is a Singleton !
504
+
470
         depot_storage_name = self.DEPOT_STORAGE_NAME
505
         depot_storage_name = self.DEPOT_STORAGE_NAME
471
         depot_storage_path = self.DEPOT_STORAGE_DIR
506
         depot_storage_path = self.DEPOT_STORAGE_DIR
472
         depot_storage_settings = {'depot.storage_path': depot_storage_path}
507
         depot_storage_settings = {'depot.storage_path': depot_storage_path}
475
             depot_storage_settings,
510
             depot_storage_settings,
476
         )
511
         )
477
 
512
 
513
+    def _set_default_app(self):
514
+        calendar = Application(
515
+            label='Calendar',
516
+            slug='calendar',
517
+            fa_icon='calendar',
518
+            is_active=False,
519
+            config={},
520
+            main_route='/#/workspaces/{workspace_id}/calendar',
521
+            app_config=self
522
+        )
523
+
524
+        thread = Application(
525
+            label='Threads',
526
+            slug='contents/thread',
527
+            fa_icon='comments-o',
528
+            is_active=True,
529
+            config={},
530
+            main_route='/#/workspaces/{workspace_id}/contents?type=thread',
531
+            app_config=self
532
+        )
533
+        thread.add_content_type(
534
+            slug='thread',
535
+            label='Thread',
536
+            creation_label='Discuss about a topic',
537
+            available_statuses=CONTENT_STATUS.get_all(),
538
+        )
539
+
540
+        folder = Application(
541
+            label='Folder',
542
+            slug='contents/folder',
543
+            fa_icon='folder-open-o',
544
+            is_active=True,
545
+            config={},
546
+            main_route='',
547
+            app_config=self
548
+        )
549
+        folder.add_content_type(
550
+            slug='folder',
551
+            label='Folder',
552
+            creation_label='Create a folder',
553
+            available_statuses=CONTENT_STATUS.get_all(),
554
+            allow_sub_content=True,
555
+        )
556
+
557
+        _file = Application(
558
+            label='Files',
559
+            slug='contents/file',
560
+            fa_icon='paperclip',
561
+            is_active=True,
562
+            config={},
563
+            main_route='/#/workspaces/{workspace_id}/contents?type=file',
564
+            app_config=self,
565
+        )
566
+        _file.add_content_type(
567
+            slug='file',
568
+            label='File',
569
+            creation_label='Upload a file',
570
+            available_statuses=CONTENT_STATUS.get_all(),
571
+        )
572
+
573
+        markdownpluspage = Application(
574
+            label='Markdown Plus Documents',
575
+            # TODO - G.M - 24-05-2018 - Check label
576
+            slug='content/markdownpluspage',
577
+            fa_icon='file-code-o',
578
+            is_active=False,
579
+            config={},
580
+            main_route='/#/workspaces/{workspace_id}/contents?type=markdownpluspage',
581
+            # nopep8
582
+            app_config=self,
583
+        )
584
+        markdownpluspage.add_content_type(
585
+            slug='markdownpage',
586
+            label='Rich Markdown File',
587
+            creation_label='Create a Markdown document',
588
+            available_statuses=CONTENT_STATUS.get_all(),
589
+        )
590
+
591
+        html_documents = Application(
592
+            label='Text Documents',  # TODO - G.M - 24-05-2018 - Check label
593
+            slug='contents/html-document',
594
+            fa_icon='file-text-o',
595
+            is_active=True,
596
+            config={},
597
+            main_route='/#/workspaces/{workspace_id}/contents?type=html-document',
598
+            app_config=self
599
+        )
600
+        html_documents.add_content_type(
601
+            slug='html-document',
602
+            label='Text Document',
603
+            creation_label='Write a document',
604
+            available_statuses=CONTENT_STATUS.get_all(),
605
+            slug_alias=['page']
606
+        )
607
+
608
+        # TODO - G.M - 2018-08-08 - [GlobalVar] Refactor Global var
609
+        # of tracim_backend, Be careful app_list is a global_var
610
+        app_list.clear()
611
+        app_list.extend([
612
+            html_documents,
613
+            markdownpluspage,
614
+            _file,
615
+            thread,
616
+            folder,
617
+            calendar,
618
+        ])
619
+        # TODO - G.M - 2018-08-08 - We need to update validators each time
620
+        # app_list is updated.
621
+        update_validators()
622
+
478
     class CST(object):
623
     class CST(object):
479
         ASYNC = 'ASYNC'
624
         ASYNC = 'ASYNC'
480
         SYNC = 'SYNC'
625
         SYNC = 'SYNC'

+ 6 - 0
backend/tracim_backend/exceptions.py 查看文件

204
 class PreviewDimNotAllowed(TracimException):
204
 class PreviewDimNotAllowed(TracimException):
205
     pass
205
     pass
206
 
206
 
207
+
207
 class UnallowedSubContent(TracimException):
208
 class UnallowedSubContent(TracimException):
208
     pass
209
     pass
209
 
210
 
211
+
210
 class TooShortAutocompleteString(TracimException):
212
 class TooShortAutocompleteString(TracimException):
211
     pass
213
     pass
212
 
214
 
215
     pass
217
     pass
216
 
218
 
217
 
219
 
220
+class AppDoesNotExist(TracimException):
221
+    pass
222
+
223
+
218
 class EmailAlreadyExistInDb(TracimException):
224
 class EmailAlreadyExistInDb(TracimException):
219
     pass
225
     pass

+ 11 - 0
backend/tracim_backend/extensions.py 查看文件

1
 from hapic import Hapic
1
 from hapic import Hapic
2
 
2
 
3
 hapic = Hapic()
3
 hapic = Hapic()
4
+
5
+# TODO - G.M - 2018-08-08 - [GlobalVar] Refactor Global var of tracim_backend
6
+
7
+# INFO - G.M - 2018-08-08 - app_list
8
+# app_list is one of the few "global_val" in tracim_backend, with hapic
9
+# and all_content_types_validator.
10
+# The goal of this is to be able to get current list of loaded app.
11
+# List is empty until config load apps.
12
+# If you need to update app_list, think about updating Content validator like
13
+# all_content_types_validator , see  update_validators() method.
14
+app_list = []

+ 1 - 1
backend/tracim_backend/fixtures/content.py 查看文件

8
 from tracim_backend.lib.core.content import ContentApi
8
 from tracim_backend.lib.core.content import ContentApi
9
 from tracim_backend.lib.core.userworkspace import RoleApi
9
 from tracim_backend.lib.core.userworkspace import RoleApi
10
 from tracim_backend.lib.core.workspace import WorkspaceApi
10
 from tracim_backend.lib.core.workspace import WorkspaceApi
11
-from tracim_backend.models.contents import CONTENT_TYPES
11
+from tracim_backend.app_models.contents import CONTENT_TYPES
12
 from tracim_backend.models.data import UserRoleInWorkspace
12
 from tracim_backend.models.data import UserRoleInWorkspace
13
 from tracim_backend.models.revision_protection import new_revision
13
 from tracim_backend.models.revision_protection import new_revision
14
 
14
 

+ 70 - 0
backend/tracim_backend/lib/core/application.py 查看文件

1
+import typing
2
+from copy import copy
3
+
4
+from tracim_backend.exceptions import AppDoesNotExist
5
+from tracim_backend.app_models.workspace_menu_entries import WorkspaceMenuEntry
6
+from tracim_backend.app_models.workspace_menu_entries import dashboard_menu_entry
7
+from tracim_backend.app_models.workspace_menu_entries import all_content_menu_entry
8
+
9
+
10
+class ApplicationApi(object):
11
+
12
+    def __init__(
13
+        self,
14
+        app_list,
15
+        show_all: bool = False,
16
+    ) ->  None:
17
+        self.apps = app_list
18
+        self.show_all = show_all
19
+
20
+    def get_one(self, slug):
21
+        for app in self.apps:
22
+            if app.slug == slug:
23
+                return app
24
+        raise AppDoesNotExist('Application {app} does not exist'.format(app=slug))  # nopep8
25
+
26
+    def get_all(self):
27
+        active_apps = []
28
+        for app in self.apps:
29
+            if self.show_all or app.is_active:
30
+                active_apps.append(app)
31
+
32
+        return active_apps
33
+
34
+    def get_content_types(self):
35
+        active_content_types = []
36
+        for app in self.get_all():
37
+            if app.content_types:
38
+                for content_type in app.content_types:
39
+                    active_content_types.append(content_type)
40
+        return active_content_types
41
+
42
+    def get_default_workspace_menu_entry(
43
+            self,
44
+            workspace: 'Workspace',
45
+    ) -> typing.List[WorkspaceMenuEntry]:
46
+        """
47
+        Get default menu entry for a workspace
48
+        """
49
+        menu_entries = [
50
+            copy(dashboard_menu_entry),
51
+            copy(all_content_menu_entry),
52
+        ]
53
+        for app in self.get_all():
54
+            if app.main_route:
55
+                new_entry = WorkspaceMenuEntry(
56
+                    slug=app.slug,
57
+                    label=app.label,
58
+                    hexcolor=app.hexcolor,
59
+                    fa_icon=app.fa_icon,
60
+                    route=app.main_route
61
+                )
62
+                menu_entries.append(new_entry)
63
+
64
+        for entry in menu_entries:
65
+            entry.route = entry.route.replace(
66
+                '{workspace_id}',
67
+                str(workspace.workspace_id)
68
+            )
69
+
70
+        return menu_entries

+ 4 - 4
backend/tracim_backend/lib/core/content.py 查看文件

36
 from tracim_backend.exceptions import ContentNotFound
36
 from tracim_backend.exceptions import ContentNotFound
37
 from tracim_backend.exceptions import WorkspacesDoNotMatch
37
 from tracim_backend.exceptions import WorkspacesDoNotMatch
38
 from tracim_backend.lib.utils.utils import current_date_for_filename
38
 from tracim_backend.lib.utils.utils import current_date_for_filename
39
-from tracim_backend.models.contents import CONTENT_STATUS
40
-from tracim_backend.models.contents import ContentType
41
-from tracim_backend.models.contents import CONTENT_TYPES
39
+from tracim_backend.app_models.contents import CONTENT_STATUS
40
+from tracim_backend.app_models.contents import ContentType
41
+from tracim_backend.app_models.contents import CONTENT_TYPES
42
 from tracim_backend.models.revision_protection import new_revision
42
 from tracim_backend.models.revision_protection import new_revision
43
 from tracim_backend.models.auth import User
43
 from tracim_backend.models.auth import User
44
 from tracim_backend.models.data import ActionDescription
44
 from tracim_backend.models.data import ActionDescription
1137
         """
1137
         """
1138
         allowed_content_dict = {}
1138
         allowed_content_dict = {}
1139
         for allowed_content_type_slug in allowed_content_type_slug_list:
1139
         for allowed_content_type_slug in allowed_content_type_slug_list:
1140
-            if allowed_content_type_slug not in CONTENT_TYPES.extended_endpoint_allowed_types_slug():
1140
+            if allowed_content_type_slug not in CONTENT_TYPES.endpoint_allowed_types_slug():
1141
                 raise ContentTypeNotExist('Content_type {} does not exist'.format(allowed_content_type_slug))  # nopep8
1141
                 raise ContentTypeNotExist('Content_type {} does not exist'.format(allowed_content_type_slug))  # nopep8
1142
             allowed_content_dict[allowed_content_type_slug] = True
1142
             allowed_content_dict[allowed_content_type_slug] = True
1143
 
1143
 

+ 1 - 1
backend/tracim_backend/lib/mail_notifier/notifier.py 查看文件

20
 from tracim_backend.lib.utils.utils import get_login_frontend_url
20
 from tracim_backend.lib.utils.utils import get_login_frontend_url
21
 from tracim_backend.lib.utils.utils import get_email_logo_frontend_url
21
 from tracim_backend.lib.utils.utils import get_email_logo_frontend_url
22
 from tracim_backend.models.auth import User
22
 from tracim_backend.models.auth import User
23
-from tracim_backend.models.contents import CONTENT_TYPES
23
+from tracim_backend.app_models.contents import CONTENT_TYPES
24
 from tracim_backend.models.context_models import ContentInContext
24
 from tracim_backend.models.context_models import ContentInContext
25
 from tracim_backend.models.context_models import WorkspaceInContext
25
 from tracim_backend.models.context_models import WorkspaceInContext
26
 from tracim_backend.models.data import ActionDescription
26
 from tracim_backend.models.data import ActionDescription

+ 1 - 1
backend/tracim_backend/lib/utils/authentification.py 查看文件

3
 from pyramid.request import Request
3
 from pyramid.request import Request
4
 from sqlalchemy.orm.exc import NoResultFound
4
 from sqlalchemy.orm.exc import NoResultFound
5
 
5
 
6
-from tracim_backend import TracimRequest
6
+from tracim_backend.lib.utils.request import TracimRequest
7
 from tracim_backend.exceptions import UserDoesNotExist
7
 from tracim_backend.exceptions import UserDoesNotExist
8
 from tracim_backend.lib.core.user import UserApi
8
 from tracim_backend.lib.core.user import UserApi
9
 from tracim_backend.models import User
9
 from tracim_backend.models import User

+ 5 - 7
backend/tracim_backend/lib/utils/authorization.py 查看文件

5
 from pyramid.interfaces import IAuthorizationPolicy
5
 from pyramid.interfaces import IAuthorizationPolicy
6
 from zope.interface import implementer
6
 from zope.interface import implementer
7
 
7
 
8
-from tracim_backend.models.contents import ContentType
9
-from tracim_backend.models.contents import CONTENT_TYPES
8
+from tracim_backend.app_models.contents import ContentType
9
+from tracim_backend.app_models.contents import CONTENT_TYPES
10
 
10
 
11
 try:
11
 try:
12
     from json.decoder import JSONDecodeError
12
     from json.decoder import JSONDecodeError
13
 except ImportError:  # python3.4
13
 except ImportError:  # python3.4
14
     JSONDecodeError = ValueError
14
     JSONDecodeError = ValueError
15
 
15
 
16
-from tracim_backend.models.contents import ContentType
17
 from tracim_backend.exceptions import InsufficientUserRoleInWorkspace
16
 from tracim_backend.exceptions import InsufficientUserRoleInWorkspace
18
 from tracim_backend.exceptions import ContentTypeNotAllowed
17
 from tracim_backend.exceptions import ContentTypeNotAllowed
19
 from tracim_backend.exceptions import InsufficientUserProfile
18
 from tracim_backend.exceptions import InsufficientUserProfile
167
     return decorator
166
     return decorator
168
 
167
 
169
 
168
 
170
-def require_content_types(content_types: typing.List['ContentType']) -> typing.Callable:  # nopep8
169
+def require_content_types(content_types_slug: typing.List[str]) -> typing.Callable:  # nopep8
171
     """
170
     """
172
     Restricts access to specific file type or raise an exception.
171
     Restricts access to specific file type or raise an exception.
173
     Check role for candidate_workspace.
172
     Check role for candidate_workspace.
174
-    :param content_types: list of ContentType object
173
+    :param content_types_slug: list of slug of content_types
175
     :return: decorator
174
     :return: decorator
176
     """
175
     """
177
     def decorator(func: typing.Callable) -> typing.Callable:
176
     def decorator(func: typing.Callable) -> typing.Callable:
178
         @functools.wraps(func)
177
         @functools.wraps(func)
179
         def wrapper(self, context, request: 'TracimRequest') -> typing.Callable:
178
         def wrapper(self, context, request: 'TracimRequest') -> typing.Callable:
180
             content = request.current_content
179
             content = request.current_content
181
-            current_content_type_slug = CONTENT_TYPES.get_one_by_slug(content.type).slug
182
-            content_types_slug = [content_type.slug for content_type in content_types]  # nopep8
180
+            current_content_type_slug = CONTENT_TYPES.get_one_by_slug(content.type).slug  # nopep8
183
             if current_content_type_slug in content_types_slug:
181
             if current_content_type_slug in content_types_slug:
184
                 return func(self, context, request)
182
                 return func(self, context, request)
185
             raise ContentTypeNotAllowed()
183
             raise ContentTypeNotAllowed()

+ 1 - 1
backend/tracim_backend/lib/utils/request.py 查看文件

15
 from tracim_backend.exceptions import UserDoesNotExist
15
 from tracim_backend.exceptions import UserDoesNotExist
16
 from tracim_backend.exceptions import WorkspaceNotFound
16
 from tracim_backend.exceptions import WorkspaceNotFound
17
 from tracim_backend.exceptions import ImmutableAttribute
17
 from tracim_backend.exceptions import ImmutableAttribute
18
-from tracim_backend.models.contents import CONTENT_TYPES
18
+from tracim_backend.app_models.contents import CONTENT_TYPES
19
 from tracim_backend.lib.core.content import ContentApi
19
 from tracim_backend.lib.core.content import ContentApi
20
 from tracim_backend.lib.core.user import UserApi
20
 from tracim_backend.lib.core.user import UserApi
21
 from tracim_backend.lib.core.workspace import WorkspaceApi
21
 from tracim_backend.lib.core.workspace import WorkspaceApi

+ 67 - 6
backend/tracim_backend/lib/utils/utils.py 查看文件

3
 import random
3
 import random
4
 import string
4
 import string
5
 from enum import Enum
5
 from enum import Enum
6
+import colorsys
6
 
7
 
7
 from redis import Redis
8
 from redis import Redis
8
 from rq import Queue
9
 from rq import Queue
9
-
10
-from tracim_backend.config import CFG
10
+import typing
11
+if typing.TYPE_CHECKING:
12
+    from tracim_backend.config import CFG
11
 
13
 
12
 DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
14
 DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
13
 DEFAULT_WEBDAV_CONFIG_FILE = "wsgidav.conf"
15
 DEFAULT_WEBDAV_CONFIG_FILE = "wsgidav.conf"
16
 WORKSPACE_FRONTEND_URL_SCHEMA = 'workspaces/{workspace_id}'  # nopep8
18
 WORKSPACE_FRONTEND_URL_SCHEMA = 'workspaces/{workspace_id}'  # nopep8
17
 
19
 
18
 
20
 
19
-def get_root_frontend_url(config: CFG) -> str:
21
+def get_root_frontend_url(config: 'CFG') -> str:
20
     """
22
     """
21
     Return website base url with always '/' at the end
23
     Return website base url with always '/' at the end
22
     """
24
     """
28
     return base_url
30
     return base_url
29
 
31
 
30
 
32
 
31
-def get_login_frontend_url(config: CFG):
33
+def get_login_frontend_url(config: 'CFG'):
32
     """
34
     """
33
     Return login page url
35
     Return login page url
34
     """
36
     """
35
     return get_root_frontend_url(config) + 'login'
37
     return get_root_frontend_url(config) + 'login'
36
 
38
 
37
 
39
 
38
-def get_email_logo_frontend_url(config: CFG):
40
+def get_email_logo_frontend_url(config: 'CFG'):
39
     # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for email_logo_frontend_url  # nopep8
41
     # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for email_logo_frontend_url  # nopep8
40
     return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4QUTDjMSlsws9AAAB89JREFUaN7tmWtwlFcZx3/PeXeXXLmKQJEGsFZApIUBnaqjUm4VgdYyCUib0cJoKLmHW5EWmlGoQkMICURgEPuhtiYDU7XcyVgKVugULZVCQAwUYyENYAmEXHb3PH7IFjYQNgnZHfnA+bKz+7573v//PPf/C/fWvdWhJWHfMbO4P479OpCAigeowWglPq3ARWfU9AJ7Cb/3MAXZn95dBLKKHsfIQmAUIq7rv6t6gU+BKCAGaEBkN9h88tLe7sgjTdjAJ5Y4OGYMIo80Aw8g4kakJyLxiDiIxABPoMwj5+XP3R0ESpP8WC1H1YuqBj4bUWwIB5gA0d/6/xFQFUpKHBJLHADyU9chTEc1BdEZqJ0O+ofb4xcPyuBIxoBQuHc4ov3w2zrwH0Nd0aBDccwgVPsjEg/4Uc4jegKfvkfMmQ9JSfECkFP0HGJeuu0TrJZhSacg9Xh4Caze2wsXC1B9GugO+BAqUIkC/QIinhYsYoEqYDt+fY2s8WXkFE5CnBIg+vaWtJuQukzy5te2l4Bz2ysTkxchshCRWERMU2BKT4RuiLT0vwagHsENPIyR7zAx2eGybws09AUZEQLHcNS5wMEdB8NjgaKyBNTuQuTLoWOAs6AHUd5DpRzxX0KMBe0M+gA4D4Hdw5mT72LJQORxkM6IfgxSC3wjyHpHEN8M8jKPtYeAq2VgvhHg9A4RvPUIKzCyBVe346SM9LZw1y5KShzOxfUhL60SmMfcNUXgisXYS/iNA/IzhNmBgH4I3E8Cxzpmgby9ffFoMSKTQxA4jZjRpI35qENZbF5Rb1SWgcwM7HsQr28KhZnVd55GPfqTkOCbaB+i2lR1uHa8nHYeda0EygJWGILHGXzndaBgz3CQ5Fb8/hSqr5E7uj4sBXBVSjlqN6FaixKHlS8BxCYtHhs/bUly9JT597U9C30/eRYiU4Nc5Q1gH+AG/gvsxJBH2vg3w9pDPTjqFDGxX0FkKOih2PtHOcbIBiPMMi5XT9eDo9/xlu+rDW2BpX+MAQnOCudRWcWFA3OwncYhrke5VpuK1SrW7+sTVgKv5NYjsr2p6TMY1QEGvhi4mmw89unWXaiX6QQEN1YHuHjpELm5lsxvV2MbhxAdWwCSiNfnCXsb7mc/Ih8hXED8J1T1k6awEAG+R+LCLqEJOL2vInwQOP46VHaSm9QYiI1nMU4xwv2oa3OHs0+LiaHuHKr/wOo/r1yuOQwcDEqVw2KdqITQBFJGelFWojITPz8kY+wmAAp3pWLkJVT7oGwkY3R5REar/Ln1qO7AyIfsLGxQ1eB60E3UDmi9kKWPOwmcvP69cM8kROYCXVB5FW18I4LToeLSbTGnKh2T9MIChLHBOI2lT9srMUDB9s7AM8CApj7H/yaZExsiOd9GnbngcjzOciPyVPOuW0RFB7ZvHnDck4FJgbOpwPoPRHpAdxs782bwQSwei3ty0aC2EVh61ANMutEy63Gi/l0VUfSJC7sIjAnhYYON4x7eNgI9KgeifDPIOyuuDygRWnGuKDcQF6roWrGxbSNgzDAgqBuV6ki7z9WT5y4DR0LdoqoVbSOgZhAi7hv4bXzEFarDG7yqbPmsgN2aovhdLdf+2jqBorIE4JGbIugZCvcsijQHr5ojN1tbVaustUWNNP6S0vy60AR+ut4Ndj7CYzdlgL7AixTtmRnRLOToVNAhQeDfR+yPr+iJrIbf/+pM62l0WP8RKNNCSCCTySuJjgT42MTnv4rqjEDvg6J+VH5T8/qynZSW+ts40EgCQtcQDcsDEN2d9IKe4U2hiR5jmG1ERt4QKrS4pkqK2zmRSQMaUsj6D5cuu3C5ppCY6IQLf2dn8CIjZk4Q+N961VnJvlxf+4Z6v/cojvsDYEQL4BXRHVTXfkysjqPfd3tA6YqOnXx293gnLgvV5xFBVa2iv/b6dEX91p+fbf9ImTnxX6gWAadbILARX+0rbEjxohwCFpBVlH1nyJea2KTFY+OduHUGeaFJoNN3gcwr1smu37qsze16y7pQ4a7pYFYj0iuAvho0jbTxJQBkrB6Ky70VGIDqamA9+WmnWtwrZ20/fN4uQA1rss52Slw8wIOZKg4/QIlTKBfY32DttobSZafDJS0Ka3YXYEx6UCXZid8/n6wJRwHIXrsEI7kB6+xHtQTDn8hLPQuipBb2oJOkgSQBPREuAlupq98cfa6mXsXnOFpfV5uQMBRxlMpP/kJpbmP4tNHC3SNBNiLycJAbleLlOXLGVTB33edRLUBkepAuegyRd1D7Pphh10Wr5mJuMT5fNo67L45NQSUlIBqsYlXqkqaiGy51es3eWQh5CDfmUeufTcaE9QBkFY7AyIuImXxT3bfI7doUvQayFbUDMSZYRHiLq64n2JByOXzvBzLGbkJ1GarnP4OPmBt60Or0v+E381Fdj+ILOhYT4p1ADOhTzcA3EdjeXvCtEwDIGNc0J6vNB/0FDXXbmk9uc05wMSYLq+nAgYArtWJ3keYW0eVUVheE9/3ArbYXkND+mbO2H8gk0AnAlGZAb3WlWpQ/I/o6q9JevWMxIyJdWWZ+V4xnOUaebQH4MVS2I/59eO3bFGbWdEiNiVhrmVncH+NfhMg0IA4RH1bfQnQlq9LKwiYnRbTB/9HmKLrWfg2j94HUoI1/Z3XOOe6te+vuWf8DkM0cb7DOQZgAAAAASUVORK5CYII='  # nopep8'
42
     return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4QUTDjMSlsws9AAAB89JREFUaN7tmWtwlFcZx3/PeXeXXLmKQJEGsFZApIUBnaqjUm4VgdYyCUib0cJoKLmHW5EWmlGoQkMICURgEPuhtiYDU7XcyVgKVugULZVCQAwUYyENYAmEXHb3PH7IFjYQNgnZHfnA+bKz+7573v//PPf/C/fWvdWhJWHfMbO4P479OpCAigeowWglPq3ARWfU9AJ7Cb/3MAXZn95dBLKKHsfIQmAUIq7rv6t6gU+BKCAGaEBkN9h88tLe7sgjTdjAJ5Y4OGYMIo80Aw8g4kakJyLxiDiIxABPoMwj5+XP3R0ESpP8WC1H1YuqBj4bUWwIB5gA0d/6/xFQFUpKHBJLHADyU9chTEc1BdEZqJ0O+ofb4xcPyuBIxoBQuHc4ov3w2zrwH0Nd0aBDccwgVPsjEg/4Uc4jegKfvkfMmQ9JSfECkFP0HGJeuu0TrJZhSacg9Xh4Caze2wsXC1B9GugO+BAqUIkC/QIinhYsYoEqYDt+fY2s8WXkFE5CnBIg+vaWtJuQukzy5te2l4Bz2ysTkxchshCRWERMU2BKT4RuiLT0vwagHsENPIyR7zAx2eGybws09AUZEQLHcNS5wMEdB8NjgaKyBNTuQuTLoWOAs6AHUd5DpRzxX0KMBe0M+gA4D4Hdw5mT72LJQORxkM6IfgxSC3wjyHpHEN8M8jKPtYeAq2VgvhHg9A4RvPUIKzCyBVe346SM9LZw1y5KShzOxfUhL60SmMfcNUXgisXYS/iNA/IzhNmBgH4I3E8Cxzpmgby9ffFoMSKTQxA4jZjRpI35qENZbF5Rb1SWgcwM7HsQr28KhZnVd55GPfqTkOCbaB+i2lR1uHa8nHYeda0EygJWGILHGXzndaBgz3CQ5Fb8/hSqr5E7uj4sBXBVSjlqN6FaixKHlS8BxCYtHhs/bUly9JT597U9C30/eRYiU4Nc5Q1gH+AG/gvsxJBH2vg3w9pDPTjqFDGxX0FkKOih2PtHOcbIBiPMMi5XT9eDo9/xlu+rDW2BpX+MAQnOCudRWcWFA3OwncYhrke5VpuK1SrW7+sTVgKv5NYjsr2p6TMY1QEGvhi4mmw89unWXaiX6QQEN1YHuHjpELm5lsxvV2MbhxAdWwCSiNfnCXsb7mc/Ih8hXED8J1T1k6awEAG+R+LCLqEJOL2vInwQOP46VHaSm9QYiI1nMU4xwv2oa3OHs0+LiaHuHKr/wOo/r1yuOQwcDEqVw2KdqITQBFJGelFWojITPz8kY+wmAAp3pWLkJVT7oGwkY3R5REar/Ln1qO7AyIfsLGxQ1eB60E3UDmi9kKWPOwmcvP69cM8kROYCXVB5FW18I4LToeLSbTGnKh2T9MIChLHBOI2lT9srMUDB9s7AM8CApj7H/yaZExsiOd9GnbngcjzOciPyVPOuW0RFB7ZvHnDck4FJgbOpwPoPRHpAdxs782bwQSwei3ty0aC2EVh61ANMutEy63Gi/l0VUfSJC7sIjAnhYYON4x7eNgI9KgeifDPIOyuuDygRWnGuKDcQF6roWrGxbSNgzDAgqBuV6ki7z9WT5y4DR0LdoqoVbSOgZhAi7hv4bXzEFarDG7yqbPmsgN2aovhdLdf+2jqBorIE4JGbIugZCvcsijQHr5ojN1tbVaustUWNNP6S0vy60AR+ut4Ndj7CYzdlgL7AixTtmRnRLOToVNAhQeDfR+yPr+iJrIbf/+pM62l0WP8RKNNCSCCTySuJjgT42MTnv4rqjEDvg6J+VH5T8/qynZSW+ts40EgCQtcQDcsDEN2d9IKe4U2hiR5jmG1ERt4QKrS4pkqK2zmRSQMaUsj6D5cuu3C5ppCY6IQLf2dn8CIjZk4Q+N961VnJvlxf+4Z6v/cojvsDYEQL4BXRHVTXfkysjqPfd3tA6YqOnXx293gnLgvV5xFBVa2iv/b6dEX91p+fbf9ImTnxX6gWAadbILARX+0rbEjxohwCFpBVlH1nyJea2KTFY+OduHUGeaFJoNN3gcwr1smu37qsze16y7pQ4a7pYFYj0iuAvho0jbTxJQBkrB6Ky70VGIDqamA9+WmnWtwrZ20/fN4uQA1rss52Slw8wIOZKg4/QIlTKBfY32DttobSZafDJS0Ka3YXYEx6UCXZid8/n6wJRwHIXrsEI7kB6+xHtQTDn8hLPQuipBb2oJOkgSQBPREuAlupq98cfa6mXsXnOFpfV5uQMBRxlMpP/kJpbmP4tNHC3SNBNiLycJAbleLlOXLGVTB33edRLUBkepAuegyRd1D7Pphh10Wr5mJuMT5fNo67L45NQSUlIBqsYlXqkqaiGy51es3eWQh5CDfmUeufTcaE9QBkFY7AyIuImXxT3bfI7doUvQayFbUDMSZYRHiLq64n2JByOXzvBzLGbkJ1GarnP4OPmBt60Or0v+E381Fdj+ILOhYT4p1ADOhTzcA3EdjeXvCtEwDIGNc0J6vNB/0FDXXbmk9uc05wMSYLq+nAgYArtWJ3keYW0eVUVheE9/3ArbYXkND+mbO2H8gk0AnAlGZAb3WlWpQ/I/o6q9JevWMxIyJdWWZ+V4xnOUaebQH4MVS2I/59eO3bFGbWdEiNiVhrmVncH+NfhMg0IA4RH1bfQnQlq9LKwiYnRbTB/9HmKLrWfg2j94HUoI1/Z3XOOe6te+vuWf8DkM0cb7DOQZgAAAAASUVORK5CYII='  # nopep8'
41
 
43
 
42
 
44
 
43
-def get_redis_connection(config: CFG) -> Redis:
45
+def get_redis_connection(config: 'CFG') -> Redis:
44
     """
46
     """
45
     :param config: current app_config
47
     :param config: current app_config
46
     :return: redis connection
48
     :return: redis connection
125
     """
127
     """
126
     return ''.join(random.choice(chars) for char_number in range(length))
128
     return ''.join(random.choice(chars) for char_number in range(length))
127
 
129
 
130
+
131
+def clamp(val: float, minimum: float = 0.0, maximum: float= 255.0) -> int:
132
+    """ Fix value between min an max"""
133
+    if val < minimum:
134
+        return minimum
135
+    if val > maximum:
136
+        return maximum
137
+    return int(val)
138
+
139
+
140
+COLOR_DARKEN_SCALE_FACTOR = 0.85
141
+COLOR_LIGHTEN_SCALE_FACTOR = 1.15
142
+
143
+
144
+class Color(object):
145
+    def __init__(self, base_hex_code: str):
146
+        """
147
+        :param base_hex_code: hex color like '#FFFFFF'
148
+        """
149
+
150
+        assert len(base_hex_code) == 7
151
+        self._base_hex_code = base_hex_code
152
+
153
+    # INFO - G.M - 2018-08-10 - get_hexcolor, inspired by
154
+    # https://thadeusb.com/weblog/2010/10/10/python_scale_hex_color/
155
+
156
+    def get_hexcolor(self, scalefactor: float) -> str:
157
+        """
158
+
159
+        :param scalefactor: factor of scaling,
160
+        value between 0 and 1 darken the color,
161
+        value >1 lighten the color.
162
+        :return: new hex_color
163
+        """
164
+
165
+        hex_color = self._base_hex_code.strip('#')
166
+        assert scalefactor > 0
167
+
168
+        r = int(hex_color[:2], 16)
169
+        g = int(hex_color[2:4], 16)
170
+        b = int(hex_color[4:], 16)
171
+
172
+        h, l, s = colorsys.rgb_to_hls(r, g, b)
173
+        l = scalefactor * l
174
+        r, g, b = colorsys.hls_to_rgb(h, l, s)
175
+
176
+        return "#%02x%02x%02x" % (clamp(r), clamp(g), clamp(b))
177
+
178
+    @property
179
+    def normal(self):
180
+        return self._base_hex_code
181
+
182
+    @property
183
+    def darken(self):
184
+        return self.get_hexcolor(COLOR_DARKEN_SCALE_FACTOR)
185
+
186
+    @property
187
+    def lighten(self):
188
+        return self.get_hexcolor(COLOR_LIGHTEN_SCALE_FACTOR)

+ 1 - 1
backend/tracim_backend/lib/webdav/dav_provider.py 查看文件

8
 from tracim_backend import CFG
8
 from tracim_backend import CFG
9
 from tracim_backend.lib.webdav.utils import transform_to_bdd, HistoryType, \
9
 from tracim_backend.lib.webdav.utils import transform_to_bdd, HistoryType, \
10
     SpecialFolderExtension
10
     SpecialFolderExtension
11
-from tracim_backend.models.contents import CONTENT_TYPES
11
+from tracim_backend.app_models.contents import CONTENT_TYPES
12
 
12
 
13
 from wsgidav.dav_provider import DAVProvider
13
 from wsgidav.dav_provider import DAVProvider
14
 from wsgidav.lock_manager import LockManager
14
 from wsgidav.lock_manager import LockManager

+ 1 - 1
backend/tracim_backend/lib/webdav/design.py 查看文件

1
 #coding: utf8
1
 #coding: utf8
2
 from datetime import datetime
2
 from datetime import datetime
3
 
3
 
4
-from tracim_backend.models.contents import CONTENT_TYPES
4
+from tracim_backend.app_models.contents import CONTENT_TYPES
5
 from tracim_backend.models.data import VirtualEvent
5
 from tracim_backend.models.data import VirtualEvent
6
 from tracim_backend.models import data
6
 from tracim_backend.models import data
7
 
7
 

+ 1 - 1
backend/tracim_backend/lib/webdav/resources.py 查看文件

19
     FakeFileStream
19
     FakeFileStream
20
 from tracim_backend.lib.webdav.utils import transform_to_bdd
20
 from tracim_backend.lib.webdav.utils import transform_to_bdd
21
 from tracim_backend.lib.core.workspace import WorkspaceApi
21
 from tracim_backend.lib.core.workspace import WorkspaceApi
22
-from tracim_backend.models.contents import CONTENT_TYPES
22
+from tracim_backend.app_models.contents import CONTENT_TYPES
23
 from tracim_backend.models.data import User, ContentRevisionRO
23
 from tracim_backend.models.data import User, ContentRevisionRO
24
 from tracim_backend.models.data import Workspace
24
 from tracim_backend.models.data import Workspace
25
 from tracim_backend.models.data import Content
25
 from tracim_backend.models.data import Content

+ 1 - 1
backend/tracim_backend/lib/webdav/utils.py 查看文件

4
 from os.path import normpath as base_normpath
4
 from os.path import normpath as base_normpath
5
 
5
 
6
 from sqlalchemy.orm import Session
6
 from sqlalchemy.orm import Session
7
-from tracim_backend.models.contents import CONTENT_TYPES
7
+from tracim_backend.app_models.contents import CONTENT_TYPES
8
 from wsgidav import util
8
 from wsgidav import util
9
 from wsgidav import compat
9
 from wsgidav import compat
10
 
10
 

+ 1 - 1
backend/tracim_backend/models/__init__.py 查看文件

5
 from sqlalchemy.orm import configure_mappers
5
 from sqlalchemy.orm import configure_mappers
6
 import zope.sqlalchemy
6
 import zope.sqlalchemy
7
 from .meta import DeclarativeBase
7
 from .meta import DeclarativeBase
8
-from .revision_protection import prevent_content_revision_delete
8
+from tracim_backend.models.revision_protection import prevent_content_revision_delete
9
 # import or define all models here to ensure they are attached to the
9
 # import or define all models here to ensure they are attached to the
10
 # Base.metadata prior to any initialization routines
10
 # Base.metadata prior to any initialization routines
11
 from tracim_backend.models.auth import User, Group, Permission
11
 from tracim_backend.models.auth import User, Group, Permission

+ 0 - 117
backend/tracim_backend/models/applications.py 查看文件

1
-# coding=utf-8
2
-import typing
3
-
4
-
5
-class Application(object):
6
-    """
7
-    Application class with data needed for frontend
8
-    """
9
-    def __init__(
10
-            self,
11
-            label: str,
12
-            slug: str,
13
-            fa_icon: str,
14
-            hexcolor: str,
15
-            is_active: bool,
16
-            config: typing.Dict[str, str],
17
-            main_route: str,
18
-    ) -> None:
19
-        """
20
-        @param label: public label of application
21
-        @param slug: identifier of application
22
-        @param icon: font awesome icon class
23
-        @param hexcolor: hexa color of application main color
24
-        @param is_active: True if application enable, False if inactive
25
-        @param config: a dict with eventual application config
26
-        @param main_route: the route of the frontend "home" screen of
27
-        the application. For exemple, if you have an application
28
-        called "calendar", the main route will be something
29
-        like /#/workspace/{wid}/calendar.
30
-        """
31
-        self.label = label
32
-        self.slug = slug
33
-        self.fa_icon = fa_icon
34
-        self.hexcolor = hexcolor
35
-        self.is_active = is_active
36
-        self.config = config
37
-        self.main_route = main_route
38
-
39
-    # TODO - G.M - 2018-08-07 - Refactor slug coherence issue like this one.
40
-    # we probably should not have 2 kind of slug
41
-    @property
42
-    def minislug(self):
43
-        return self.slug.replace('contents/', '')
44
-
45
-
46
-# default apps
47
-calendar = Application(
48
-    label='Calendar',
49
-    slug='calendar',
50
-    fa_icon='calendar',
51
-    hexcolor='#757575',
52
-    is_active=True,
53
-    config={},
54
-    main_route='/#/workspaces/{workspace_id}/calendar',
55
-)
56
-
57
-thread = Application(
58
-    label='Threads',
59
-    slug='contents/thread',
60
-    fa_icon='comments-o',
61
-    hexcolor='#ad4cf9',
62
-    is_active=True,
63
-    config={},
64
-    main_route='/#/workspaces/{workspace_id}/contents?type=thread',
65
-
66
-)
67
-
68
-folder = Application(
69
-    label='Folder',
70
-    slug='contents/folder',
71
-    fa_icon='folder-open-o',
72
-    hexcolor='#252525',
73
-    is_active=True,
74
-    config={},
75
-    main_route='',
76
-)
77
-
78
-_file = Application(
79
-    label='Files',
80
-    slug='contents/file',
81
-    fa_icon='paperclip',
82
-    hexcolor='#FF9900',
83
-    is_active=True,
84
-    config={},
85
-    main_route='/#/workspaces/{workspace_id}/contents?type=file',
86
-)
87
-
88
-markdownpluspage = Application(
89
-    label='Markdown Plus Documents',  # TODO - G.M - 24-05-2018 - Check label
90
-    slug='contents/markdownpluspage',
91
-    fa_icon='file-code-o',
92
-    hexcolor='#f12d2d',
93
-    is_active=True,
94
-    config={},
95
-    main_route='/#/workspaces/{workspace_id}/contents?type=markdownpluspage',
96
-)
97
-
98
-html_documents = Application(
99
-    label='Text Documents',  # TODO - G.M - 24-05-2018 - Check label
100
-    slug='contents/html-document',
101
-    fa_icon='file-text-o',
102
-    hexcolor='#3f52e3',
103
-    is_active=True,
104
-    config={},
105
-    main_route='/#/workspaces/{workspace_id}/contents?type=html-document',
106
-)
107
-# TODO - G.M - 08-06-2018 - This is hardcoded lists of app, make this dynamic.
108
-# List of applications
109
-applications = [
110
-    html_documents,
111
-    # TODO - G.M - 2018-08-02 - Restore markdownpage app
112
-    # markdownpluspage,
113
-    _file,
114
-    thread,
115
-    folder,
116
-    # calendar,
117
-]

+ 8 - 4
backend/tracim_backend/models/context_models.py 查看文件

7
 from sqlalchemy.orm import Session
7
 from sqlalchemy.orm import Session
8
 from tracim_backend.config import CFG
8
 from tracim_backend.config import CFG
9
 from tracim_backend.config import PreviewDim
9
 from tracim_backend.config import PreviewDim
10
+from tracim_backend.extensions import app_list
11
+from tracim_backend.lib.core.application import ApplicationApi
10
 from tracim_backend.lib.utils.utils import get_root_frontend_url
12
 from tracim_backend.lib.utils.utils import get_root_frontend_url
11
 from tracim_backend.lib.utils.utils import password_generator
13
 from tracim_backend.lib.utils.utils import password_generator
12
 from tracim_backend.lib.utils.utils import CONTENT_FRONTEND_URL_SCHEMA
14
 from tracim_backend.lib.utils.utils import CONTENT_FRONTEND_URL_SCHEMA
19
 from tracim_backend.models.data import Workspace
21
 from tracim_backend.models.data import Workspace
20
 from tracim_backend.models.data import UserRoleInWorkspace
22
 from tracim_backend.models.data import UserRoleInWorkspace
21
 from tracim_backend.models.roles import WorkspaceRoles
23
 from tracim_backend.models.roles import WorkspaceRoles
22
-from tracim_backend.models.workspace_menu_entries import default_workspace_menu_entry  # nopep8
23
-from tracim_backend.models.workspace_menu_entries import WorkspaceMenuEntry
24
-from tracim_backend.models.contents import CONTENT_TYPES
24
+from tracim_backend.app_models.workspace_menu_entries import WorkspaceMenuEntry
25
+from tracim_backend.app_models.contents import CONTENT_TYPES
25
 
26
 
26
 
27
 
27
 class PreviewAllowedDim(object):
28
 class PreviewAllowedDim(object):
500
         # order to not use hardcoded list
501
         # order to not use hardcoded list
501
         # list should be able to change (depending on activated/disabled
502
         # list should be able to change (depending on activated/disabled
502
         # apps)
503
         # apps)
503
-        return default_workspace_menu_entry(self.workspace)
504
+        app_api = ApplicationApi(
505
+            app_list
506
+        )
507
+        return app_api.get_default_workspace_menu_entry(self.workspace)
504
 
508
 
505
     @property
509
     @property
506
     def frontend_url(self):
510
     def frontend_url(self):

+ 5 - 5
backend/tracim_backend/models/data.py 查看文件

87
 
87
 
88
     def get_allowed_content_types(self):
88
     def get_allowed_content_types(self):
89
         # @see Content.get_allowed_content_types()
89
         # @see Content.get_allowed_content_types()
90
-        return CONTENT_TYPES.extended_endpoint_allowed_types_slug()
90
+        return CONTENT_TYPES.endpoint_allowed_types_slug()
91
 
91
 
92
     def get_valid_children(
92
     def get_valid_children(
93
             self,
93
             self,
282
                 ]
282
                 ]
283
 
283
 
284
 
284
 
285
-from tracim_backend.models.contents import CONTENT_STATUS
286
-from tracim_backend.models.contents import ContentStatus
287
-from tracim_backend.models.contents import CONTENT_TYPES
285
+from tracim_backend.app_models.contents import CONTENT_STATUS
286
+from tracim_backend.app_models.contents import ContentStatus
287
+from tracim_backend.app_models.contents import CONTENT_TYPES
288
 # TODO - G.M - 30-05-2018 - Drop this old code when whe are sure nothing
288
 # TODO - G.M - 30-05-2018 - Drop this old code when whe are sure nothing
289
 # is lost .
289
 # is lost .
290
 
290
 
552
                 for content_slug, value in properties['allowed_content'].items():  # nopep8
552
                 for content_slug, value in properties['allowed_content'].items():  # nopep8
553
                     if not isinstance(value, bool):
553
                     if not isinstance(value, bool):
554
                         return False
554
                         return False
555
-                    if not content_slug in CONTENT_TYPES.extended_endpoint_allowed_types_slug():  # nopep8
555
+                    if not content_slug in CONTENT_TYPES.endpoint_allowed_types_slug():  # nopep8
556
                         return False
556
                         return False
557
             if 'origin' in properties.keys():
557
             if 'origin' in properties.keys():
558
                 pass
558
                 pass

+ 0 - 71
backend/tracim_backend/models/workspace_menu_entries.py 查看文件

1
-# coding=utf-8
2
-import typing
3
-from copy import copy
4
-
5
-from tracim_backend.models.applications import applications
6
-from tracim_backend.models.data import Workspace
7
-
8
-
9
-class WorkspaceMenuEntry(object):
10
-    """
11
-    Application class with data needed for frontend
12
-    """
13
-    def __init__(
14
-            self,
15
-            label: str,
16
-            slug: str,
17
-            fa_icon: str,
18
-            hexcolor: str,
19
-            route: str,
20
-    ) -> None:
21
-        self.slug = slug
22
-        self.label = label
23
-        self.route = route
24
-        self.hexcolor = hexcolor
25
-        self.fa_icon = fa_icon
26
-
27
-dashboard_menu_entry = WorkspaceMenuEntry(
28
-  slug='dashboard',
29
-  label='Dashboard',
30
-  route='/#/workspaces/{workspace_id}/dashboard',
31
-  hexcolor='#252525',
32
-  fa_icon="signal",
33
-)
34
-all_content_menu_entry = WorkspaceMenuEntry(
35
-  slug="contents/all",
36
-  label="All Contents",
37
-  route="/#/workspaces/{workspace_id}/contents",
38
-  hexcolor="#fdfdfd",
39
-  fa_icon="th",
40
-)
41
-
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(
45
-    workspace: Workspace,
46
-)-> typing.List[WorkspaceMenuEntry]:
47
-    """
48
-    Get default menu entry for a workspace
49
-    """
50
-    menu_entries = [
51
-        copy(dashboard_menu_entry),
52
-        copy(all_content_menu_entry),
53
-    ]
54
-    for app in applications:
55
-        if app.main_route:
56
-            new_entry = WorkspaceMenuEntry(
57
-                slug=app.slug,
58
-                label=app.label,
59
-                hexcolor=app.hexcolor,
60
-                fa_icon=app.fa_icon,
61
-                route=app.main_route
62
-            )
63
-            menu_entries.append(new_entry)
64
-
65
-    for entry in menu_entries:
66
-        entry.route = entry.route.replace(
67
-            '{workspace_id}',
68
-            str(workspace.workspace_id)
69
-        )
70
-
71
-    return menu_entries

+ 1 - 1
backend/tracim_backend/tests/__init__.py 查看文件

14
 from tracim_backend.models import DeclarativeBase
14
 from tracim_backend.models import DeclarativeBase
15
 from tracim_backend.models import get_session_factory
15
 from tracim_backend.models import get_session_factory
16
 from tracim_backend.models import get_tm_session
16
 from tracim_backend.models import get_tm_session
17
-from tracim_backend.models.contents import CONTENT_TYPES
17
+from tracim_backend.app_models.contents import CONTENT_TYPES
18
 from tracim_backend.models.data import Workspace
18
 from tracim_backend.models.data import Workspace
19
 from tracim_backend.models.data import ContentRevisionRO
19
 from tracim_backend.models.data import ContentRevisionRO
20
 from tracim_backend.models.data import Content
20
 from tracim_backend.models.data import Content

+ 1 - 1
backend/tracim_backend/tests/functional/test_contents.py 查看文件

5
 from tracim_backend.lib.core.content import ContentApi
5
 from tracim_backend.lib.core.content import ContentApi
6
 from tracim_backend.lib.core.workspace import WorkspaceApi
6
 from tracim_backend.lib.core.workspace import WorkspaceApi
7
 from tracim_backend.models import get_tm_session
7
 from tracim_backend.models import get_tm_session
8
-from tracim_backend.models.contents import CONTENT_TYPES
8
+from tracim_backend.app_models.contents import CONTENT_TYPES
9
 from tracim_backend.models.revision_protection import new_revision
9
 from tracim_backend.models.revision_protection import new_revision
10
 import io
10
 import io
11
 
11
 

+ 1 - 1
backend/tracim_backend/tests/functional/test_mail_notification.py 查看文件

11
 from tracim_backend.fixtures.content import Content as ContentFixture
11
 from tracim_backend.fixtures.content import Content as ContentFixture
12
 from tracim_backend.lib.utils.utils import get_redis_connection
12
 from tracim_backend.lib.utils.utils import get_redis_connection
13
 from tracim_backend.lib.utils.utils import get_rq_queue
13
 from tracim_backend.lib.utils.utils import get_rq_queue
14
-from tracim_backend.models.contents import CONTENT_TYPES
14
+from tracim_backend.app_models.contents import CONTENT_TYPES
15
 
15
 
16
 from tracim_backend.lib.core.content import ContentApi
16
 from tracim_backend.lib.core.content import ContentApi
17
 from tracim_backend.lib.core.user import UserApi
17
 from tracim_backend.lib.core.user import UserApi

+ 10 - 2
backend/tracim_backend/tests/functional/test_system.py 查看文件

1
 # coding=utf-8
1
 # coding=utf-8
2
-from tracim_backend.models.contents import CONTENT_TYPES
2
+import transaction
3
+from tracim_backend.extensions import app_list
4
+from tracim_backend.lib.core.application import ApplicationApi
5
+from tracim_backend.models import get_tm_session
6
+from tracim_backend.app_models.contents import CONTENT_TYPES
3
 from tracim_backend.tests import FunctionalTest
7
 from tracim_backend.tests import FunctionalTest
4
-from tracim_backend.models.applications import applications
5
 
8
 
6
 """
9
 """
7
 Tests for /api/v2/system subpath endpoints.
10
 Tests for /api/v2/system subpath endpoints.
26
         )
29
         )
27
         res = self.testapp.get('/api/v2/system/applications', status=200)
30
         res = self.testapp.get('/api/v2/system/applications', status=200)
28
         res = res.json_body
31
         res = res.json_body
32
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
33
+        app_api = ApplicationApi(
34
+            app_list=app_list,
35
+        )
36
+        applications = app_api.get_all()
29
         assert len(res) == len(applications)
37
         assert len(res) == len(applications)
30
         for counter, application in enumerate(applications):
38
         for counter, application in enumerate(applications):
31
             assert res[counter]['label'] == application.label
39
             assert res[counter]['label'] == application.label

+ 27 - 39
backend/tracim_backend/tests/functional/test_user.py 查看文件

8
 import transaction
8
 import transaction
9
 
9
 
10
 from tracim_backend import models
10
 from tracim_backend import models
11
+from tracim_backend.extensions import app_list
12
+from tracim_backend.lib.core.application import ApplicationApi
11
 from tracim_backend.lib.core.content import ContentApi
13
 from tracim_backend.lib.core.content import ContentApi
12
 from tracim_backend.lib.core.user import UserApi
14
 from tracim_backend.lib.core.user import UserApi
13
 from tracim_backend.lib.core.group import GroupApi
15
 from tracim_backend.lib.core.group import GroupApi
14
 from tracim_backend.lib.core.userworkspace import RoleApi
16
 from tracim_backend.lib.core.userworkspace import RoleApi
15
 from tracim_backend.lib.core.workspace import WorkspaceApi
17
 from tracim_backend.lib.core.workspace import WorkspaceApi
16
 from tracim_backend.models import get_tm_session
18
 from tracim_backend.models import get_tm_session
17
-from tracim_backend.models.contents import CONTENT_TYPES
19
+from tracim_backend.app_models.contents import CONTENT_TYPES
18
 from tracim_backend.models.data import UserRoleInWorkspace
20
 from tracim_backend.models.data import UserRoleInWorkspace
19
 from tracim_backend.models.revision_protection import new_revision
21
 from tracim_backend.models.revision_protection import new_revision
20
 from tracim_backend.tests import FunctionalTest
22
 from tracim_backend.tests import FunctionalTest
2390
         """
2392
         """
2391
         Check obtain all workspaces reachables for user with user auth.
2393
         Check obtain all workspaces reachables for user with user auth.
2392
         """
2394
         """
2395
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2396
+        admin = dbsession.query(models.User) \
2397
+            .filter(models.User.email == 'admin@admin.admin') \
2398
+            .one()
2399
+
2400
+        workspace_api = WorkspaceApi(
2401
+            session=dbsession,
2402
+            current_user=admin,
2403
+            config=self.app_config,
2404
+        )
2405
+        workspace = workspace_api.get_one(1)
2406
+        app_api = ApplicationApi(
2407
+            app_list
2408
+        )
2409
+
2410
+        default_sidebar_entry = app_api.get_default_workspace_menu_entry(workspace=workspace)  # nope8
2393
         self.testapp.authorization = (
2411
         self.testapp.authorization = (
2394
             'Basic',
2412
             'Basic',
2395
             (
2413
             (
2404
         assert workspace['label'] == 'Business'
2422
         assert workspace['label'] == 'Business'
2405
         assert workspace['slug'] == 'business'
2423
         assert workspace['slug'] == 'business'
2406
         assert workspace['is_deleted'] is False
2424
         assert workspace['is_deleted'] is False
2407
-        assert len(workspace['sidebar_entries']) == 5
2408
-
2409
-        # TODO - G.M - 2018-08-02 - Better test for sidebar entry, make it
2410
-        # not fixed on active application/content-file
2411
-        sidebar_entry = workspace['sidebar_entries'][0]
2412
-        assert sidebar_entry['slug'] == 'dashboard'
2413
-        assert sidebar_entry['label'] == 'Dashboard'
2414
-        assert sidebar_entry['route'] == '/#/workspaces/1/dashboard'  # nopep8
2415
-        assert sidebar_entry['hexcolor'] == "#252525"
2416
-        assert sidebar_entry['fa_icon'] == "signal"
2417
-
2418
-        sidebar_entry = workspace['sidebar_entries'][1]
2419
-        assert sidebar_entry['slug'] == 'contents/all'
2420
-        assert sidebar_entry['label'] == 'All Contents'
2421
-        assert sidebar_entry['route'] == "/#/workspaces/1/contents"  # nopep8
2422
-        assert sidebar_entry['hexcolor'] == "#fdfdfd"
2423
-        assert sidebar_entry['fa_icon'] == "th"
2424
-
2425
-        sidebar_entry = workspace['sidebar_entries'][2]
2426
-        assert sidebar_entry['slug'] == 'contents/html-document'
2427
-        assert sidebar_entry['label'] == 'Text Documents'
2428
-        assert sidebar_entry['route'] == '/#/workspaces/1/contents?type=html-document'  # nopep8
2429
-        assert sidebar_entry['hexcolor'] == "#3f52e3"
2430
-        assert sidebar_entry['fa_icon'] == "file-text-o"
2431
-
2432
-        sidebar_entry = workspace['sidebar_entries'][3]
2433
-        assert sidebar_entry['slug'] == 'contents/file'
2434
-        assert sidebar_entry['label'] == 'Files'
2435
-        assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
2436
-        assert sidebar_entry['hexcolor'] == "#FF9900"
2437
-        assert sidebar_entry['fa_icon'] == "paperclip"
2438
-
2439
-        sidebar_entry = workspace['sidebar_entries'][4]
2440
-        assert sidebar_entry['slug'] == 'contents/thread'
2441
-        assert sidebar_entry['label'] == 'Threads'
2442
-        assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
2443
-        assert sidebar_entry['hexcolor'] == "#ad4cf9"
2444
-        assert sidebar_entry['fa_icon'] == "comments-o"
2425
+
2426
+        assert len(workspace['sidebar_entries']) == len(default_sidebar_entry)
2427
+        for counter, sidebar_entry in enumerate(default_sidebar_entry):
2428
+            workspace['sidebar_entries'][counter]['slug'] = sidebar_entry.slug
2429
+            workspace['sidebar_entries'][counter]['label'] = sidebar_entry.label
2430
+            workspace['sidebar_entries'][counter]['route'] = sidebar_entry.route
2431
+            workspace['sidebar_entries'][counter]['hexcolor'] = sidebar_entry.hexcolor  # nopep8
2432
+            workspace['sidebar_entries'][counter]['fa_icon'] = sidebar_entry.fa_icon  # nopep8
2445
 
2433
 
2446
     def test_api__get_user_workspaces__err_403__unallowed_user(self):
2434
     def test_api__get_user_workspaces__err_403__unallowed_user(self):
2447
         """
2435
         """

+ 46 - 42
backend/tracim_backend/tests/functional/test_workspaces.py 查看文件

7
 from depot.io.utils import FileIntent
7
 from depot.io.utils import FileIntent
8
 
8
 
9
 from tracim_backend import models
9
 from tracim_backend import models
10
+from tracim_backend.extensions import app_list
11
+from tracim_backend.lib.core.application import ApplicationApi
10
 from tracim_backend.lib.core.content import ContentApi
12
 from tracim_backend.lib.core.content import ContentApi
11
 from tracim_backend.lib.core.group import GroupApi
13
 from tracim_backend.lib.core.group import GroupApi
12
 from tracim_backend.lib.core.user import UserApi
14
 from tracim_backend.lib.core.user import UserApi
13
 from tracim_backend.lib.core.userworkspace import RoleApi
15
 from tracim_backend.lib.core.userworkspace import RoleApi
14
 from tracim_backend.lib.core.workspace import WorkspaceApi
16
 from tracim_backend.lib.core.workspace import WorkspaceApi
15
 from tracim_backend.models import get_tm_session
17
 from tracim_backend.models import get_tm_session
16
-from tracim_backend.models.contents import CONTENT_TYPES
18
+from tracim_backend.app_models.contents import CONTENT_TYPES
17
 from tracim_backend.models.data import UserRoleInWorkspace
19
 from tracim_backend.models.data import UserRoleInWorkspace
18
 from tracim_backend.tests import FunctionalTest
20
 from tracim_backend.tests import FunctionalTest
19
 from tracim_backend.tests import set_html_document_slug_to_legacy
21
 from tracim_backend.tests import set_html_document_slug_to_legacy
32
         """
34
         """
33
         Check obtain workspace reachable for user.
35
         Check obtain workspace reachable for user.
34
         """
36
         """
37
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
38
+        admin = dbsession.query(models.User) \
39
+            .filter(models.User.email == 'admin@admin.admin') \
40
+            .one()
41
+
42
+        workspace_api = WorkspaceApi(
43
+            session=dbsession,
44
+            current_user=admin,
45
+            config=self.app_config,
46
+        )
47
+        workspace = workspace_api.get_one(1)
48
+        app_api = ApplicationApi(
49
+            app_list
50
+        )
51
+        default_sidebar_entry = app_api.get_default_workspace_menu_entry(workspace=workspace)  # nope8
52
+
35
         self.testapp.authorization = (
53
         self.testapp.authorization = (
36
             'Basic',
54
             'Basic',
37
             (
55
             (
46
         assert workspace['label'] == 'Business'
64
         assert workspace['label'] == 'Business'
47
         assert workspace['description'] == 'All importants documents'
65
         assert workspace['description'] == 'All importants documents'
48
         assert workspace['is_deleted'] is False
66
         assert workspace['is_deleted'] is False
49
-        assert len(workspace['sidebar_entries']) == 5
50
-
51
-        # TODO - G.M - 2018-08-02 - Better test for sidebar entry, make it
52
-        # not fixed on active application/content-file
53
-        sidebar_entry = workspace['sidebar_entries'][0]
54
-        assert sidebar_entry['slug'] == 'dashboard'
55
-        assert sidebar_entry['label'] == 'Dashboard'
56
-        assert sidebar_entry['route'] == '/#/workspaces/1/dashboard'  # nopep8
57
-        assert sidebar_entry['hexcolor'] == "#252525"
58
-        assert sidebar_entry['fa_icon'] == "signal"
59
-
60
-        sidebar_entry = workspace['sidebar_entries'][1]
61
-        assert sidebar_entry['slug'] == 'contents/all'
62
-        assert sidebar_entry['label'] == 'All Contents'
63
-        assert sidebar_entry['route'] == "/#/workspaces/1/contents"  # nopep8
64
-        assert sidebar_entry['hexcolor'] == "#fdfdfd"
65
-        assert sidebar_entry['fa_icon'] == "th"
66
-
67
-        sidebar_entry = workspace['sidebar_entries'][2]
68
-        assert sidebar_entry['slug'] == 'contents/html-document'
69
-        assert sidebar_entry['label'] == 'Text Documents'
70
-        assert sidebar_entry['route'] == '/#/workspaces/1/contents?type=html-document'  # nopep8
71
-        assert sidebar_entry['hexcolor'] == "#3f52e3"
72
-        assert sidebar_entry['fa_icon'] == "file-text-o"
73
-
74
-        sidebar_entry = workspace['sidebar_entries'][3]
75
-        assert sidebar_entry['slug'] == 'contents/file'
76
-        assert sidebar_entry['label'] == 'Files'
77
-        assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
78
-        assert sidebar_entry['hexcolor'] == "#FF9900"
79
-        assert sidebar_entry['fa_icon'] == "paperclip"
80
-
81
-        sidebar_entry = workspace['sidebar_entries'][4]
82
-        assert sidebar_entry['slug'] == 'contents/thread'
83
-        assert sidebar_entry['label'] == 'Threads'
84
-        assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
85
-        assert sidebar_entry['hexcolor'] == "#ad4cf9"
86
-        assert sidebar_entry['fa_icon'] == "comments-o"
67
+
68
+        assert len(workspace['sidebar_entries']) == len(default_sidebar_entry)
69
+        for counter, sidebar_entry in enumerate(default_sidebar_entry):
70
+            workspace['sidebar_entries'][counter]['slug'] = sidebar_entry.slug
71
+            workspace['sidebar_entries'][counter]['label'] = sidebar_entry.label
72
+            workspace['sidebar_entries'][counter]['route'] = sidebar_entry.route
73
+            workspace['sidebar_entries'][counter]['hexcolor'] = sidebar_entry.hexcolor  # nopep8
74
+            workspace['sidebar_entries'][counter]['fa_icon'] = sidebar_entry.fa_icon  # nopep8
87
 
75
 
88
     def test_api__update_workspace__ok_200__nominal_case(self) -> None:
76
     def test_api__update_workspace__ok_200__nominal_case(self) -> None:
89
         """
77
         """
90
         Test update workspace
78
         Test update workspace
91
         """
79
         """
80
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
81
+        admin = dbsession.query(models.User) \
82
+            .filter(models.User.email == 'admin@admin.admin') \
83
+            .one()
84
+
85
+        workspace_api = WorkspaceApi(
86
+            session=dbsession,
87
+            current_user=admin,
88
+            config=self.app_config,
89
+        )
90
+        workspace = workspace_api.get_one(1)
91
+        app_api = ApplicationApi(
92
+            app_list
93
+        )
94
+        default_sidebar_entry = app_api.get_default_workspace_menu_entry(workspace=workspace)  # nope8
95
+
92
         self.testapp.authorization = (
96
         self.testapp.authorization = (
93
             'Basic',
97
             'Basic',
94
             (
98
             (
111
         assert workspace['slug'] == 'business'
115
         assert workspace['slug'] == 'business'
112
         assert workspace['label'] == 'Business'
116
         assert workspace['label'] == 'Business'
113
         assert workspace['description'] == 'All importants documents'
117
         assert workspace['description'] == 'All importants documents'
118
+        assert len(workspace['sidebar_entries']) == len(default_sidebar_entry)
114
         assert workspace['is_deleted'] is False
119
         assert workspace['is_deleted'] is False
115
-        assert len(workspace['sidebar_entries']) == 5
116
 
120
 
117
         # modify workspace
121
         # modify workspace
118
         res = self.testapp.put_json(
122
         res = self.testapp.put_json(
126
         assert workspace['slug'] == 'superworkspace'
130
         assert workspace['slug'] == 'superworkspace'
127
         assert workspace['label'] == 'superworkspace'
131
         assert workspace['label'] == 'superworkspace'
128
         assert workspace['description'] == 'mysuperdescription'
132
         assert workspace['description'] == 'mysuperdescription'
133
+        assert len(workspace['sidebar_entries']) == len(default_sidebar_entry)
129
         assert workspace['is_deleted'] is False
134
         assert workspace['is_deleted'] is False
130
-        assert len(workspace['sidebar_entries']) == 5
131
 
135
 
132
         # after
136
         # after
133
         res = self.testapp.get(
137
         res = self.testapp.get(
140
         assert workspace['slug'] == 'superworkspace'
144
         assert workspace['slug'] == 'superworkspace'
141
         assert workspace['label'] == 'superworkspace'
145
         assert workspace['label'] == 'superworkspace'
142
         assert workspace['description'] == 'mysuperdescription'
146
         assert workspace['description'] == 'mysuperdescription'
147
+        assert len(workspace['sidebar_entries']) == len(default_sidebar_entry)
143
         assert workspace['is_deleted'] is False
148
         assert workspace['is_deleted'] is False
144
-        assert len(workspace['sidebar_entries']) == 5
145
 
149
 
146
     def test_api__update_workspace__err_400__empty_label(self) -> None:
150
     def test_api__update_workspace__err_400__empty_label(self) -> None:
147
         """
151
         """

+ 1 - 1
backend/tracim_backend/tests/library/test_content_api.py 查看文件

16
 from tracim_backend.lib.core.workspace import RoleApi
16
 from tracim_backend.lib.core.workspace import RoleApi
17
 # TODO - G.M - 28-03-2018 - [WorkspaceApi] Re-enable WorkspaceApi
17
 # TODO - G.M - 28-03-2018 - [WorkspaceApi] Re-enable WorkspaceApi
18
 from tracim_backend.lib.core.workspace import WorkspaceApi
18
 from tracim_backend.lib.core.workspace import WorkspaceApi
19
-from tracim_backend.models.contents import CONTENT_TYPES
19
+from tracim_backend.app_models.contents import CONTENT_TYPES
20
 from tracim_backend.models.revision_protection import new_revision
20
 from tracim_backend.models.revision_protection import new_revision
21
 from tracim_backend.models.auth import User
21
 from tracim_backend.models.auth import User
22
 from tracim_backend.models.auth import Group
22
 from tracim_backend.models.auth import Group

+ 3 - 10
backend/tracim_backend/tests/library/test_webdav.py 查看文件

22
 
22
 
23
 class TestWebdavFactory(StandardTest):
23
 class TestWebdavFactory(StandardTest):
24
 
24
 
25
+    config_section = 'webdav_test'
26
+
25
     def test_unit__initConfig__ok__nominal_case(self):
27
     def test_unit__initConfig__ok__nominal_case(self):
26
         """
28
         """
27
         Check if config is correctly modify for wsgidav using mocked
29
         Check if config is correctly modify for wsgidav using mocked
28
         wsgidav and tracim conf (as dict)
30
         wsgidav and tracim conf (as dict)
29
         :return:
31
         :return:
30
         """
32
         """
31
-        tracim_settings = {
32
-            'website.base_url': 'http://localhost:6543',
33
-            'sqlalchemy.url': 'sqlite:///:memory:',
34
-            'user.auth_token.validity': '604800',
35
-            'depot_storage_dir': '/tmp/test/depot',
36
-            'depot_storage_name': 'test',
37
-            'preview_cache_dir': '/tmp/test/preview_cache',
38
-            'wsgidav.config_path': 'development.ini'
39
-
40
-        }
33
+        tracim_settings = self.settings
41
         wsgidav_setting = DEFAULT_CONFIG.copy()
34
         wsgidav_setting = DEFAULT_CONFIG.copy()
42
         wsgidav_setting.update(
35
         wsgidav_setting.update(
43
             {
36
             {

+ 1 - 1
backend/tracim_backend/tests/models/test_content.py 查看文件

15
 from tracim_backend.models import User
15
 from tracim_backend.models import User
16
 from tracim_backend.models.data import ActionDescription
16
 from tracim_backend.models.data import ActionDescription
17
 from tracim_backend.models.data import ContentRevisionRO
17
 from tracim_backend.models.data import ContentRevisionRO
18
-from tracim_backend.models.contents import CONTENT_TYPES
18
+from tracim_backend.app_models.contents import CONTENT_TYPES
19
 from tracim_backend.models.data import Workspace
19
 from tracim_backend.models.data import Workspace
20
 from tracim_backend.tests import StandardTest
20
 from tracim_backend.tests import StandardTest
21
 
21
 

+ 1 - 1
backend/tracim_backend/tests/models/test_content_revision.py 查看文件

5
 
5
 
6
 from tracim_backend.models import ContentRevisionRO
6
 from tracim_backend.models import ContentRevisionRO
7
 from tracim_backend.models import User
7
 from tracim_backend.models import User
8
-from tracim_backend.models.contents import CONTENT_TYPES
8
+from tracim_backend.app_models.contents import CONTENT_TYPES
9
 from tracim_backend.tests import DefaultTest
9
 from tracim_backend.tests import DefaultTest
10
 from tracim_backend.tests import eq_
10
 from tracim_backend.tests import eq_
11
 
11
 

+ 2 - 2
backend/tracim_backend/views/contents_api/comment_controller.py 查看文件

7
 except ImportError:
7
 except ImportError:
8
     from http import client as HTTPStatus
8
     from http import client as HTTPStatus
9
 
9
 
10
-from tracim_backend import TracimRequest
10
+from tracim_backend.lib.utils.request import TracimRequest
11
 from tracim_backend.extensions import hapic
11
 from tracim_backend.extensions import hapic
12
 from tracim_backend.lib.core.content import ContentApi
12
 from tracim_backend.lib.core.content import ContentApi
13
 from tracim_backend.lib.core.workspace import WorkspaceApi
13
 from tracim_backend.lib.core.workspace import WorkspaceApi
20
 from tracim_backend.views.core_api.schemas import WorkspaceAndContentIdPathSchema
20
 from tracim_backend.views.core_api.schemas import WorkspaceAndContentIdPathSchema
21
 from tracim_backend.views.core_api.schemas import NoContentSchema
21
 from tracim_backend.views.core_api.schemas import NoContentSchema
22
 from tracim_backend.exceptions import EmptyCommentContentNotAllowed
22
 from tracim_backend.exceptions import EmptyCommentContentNotAllowed
23
-from tracim_backend.models.contents import CONTENT_TYPES
23
+from tracim_backend.app_models.contents import CONTENT_TYPES
24
 from tracim_backend.models.revision_protection import new_revision
24
 from tracim_backend.models.revision_protection import new_revision
25
 from tracim_backend.models.data import UserRoleInWorkspace
25
 from tracim_backend.models.data import UserRoleInWorkspace
26
 
26
 

+ 17 - 17
backend/tracim_backend/views/contents_api/file_controller.py 查看文件

12
 except ImportError:
12
 except ImportError:
13
     from http import client as HTTPStatus
13
     from http import client as HTTPStatus
14
 
14
 
15
-from tracim_backend import TracimRequest
15
+from tracim_backend.lib.utils.request import TracimRequest
16
 from tracim_backend.extensions import hapic
16
 from tracim_backend.extensions import hapic
17
 from tracim_backend.lib.core.content import ContentApi
17
 from tracim_backend.lib.core.content import ContentApi
18
 from tracim_backend.views.controllers import Controller
18
 from tracim_backend.views.controllers import Controller
32
 from tracim_backend.models.data import UserRoleInWorkspace
32
 from tracim_backend.models.data import UserRoleInWorkspace
33
 from tracim_backend.models.context_models import ContentInContext
33
 from tracim_backend.models.context_models import ContentInContext
34
 from tracim_backend.models.context_models import RevisionInContext
34
 from tracim_backend.models.context_models import RevisionInContext
35
-from tracim_backend.models.contents import CONTENT_TYPES
36
-from tracim_backend.models.contents import file_type
35
+from tracim_backend.app_models.contents import CONTENT_TYPES
36
+from tracim_backend.app_models.contents import FILE_TYPE
37
 from tracim_backend.models.revision_protection import new_revision
37
 from tracim_backend.models.revision_protection import new_revision
38
 from tracim_backend.exceptions import EmptyLabelNotAllowed
38
 from tracim_backend.exceptions import EmptyLabelNotAllowed
39
 from tracim_backend.exceptions import PageOfPreviewNotFound
39
 from tracim_backend.exceptions import PageOfPreviewNotFound
50
     # File data
50
     # File data
51
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
51
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
52
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
52
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
53
-    @require_content_types([file_type])
53
+    @require_content_types([FILE_TYPE])
54
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
54
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
55
     # TODO - G.M - 2018-07-24 - Use hapic for input file
55
     # TODO - G.M - 2018-07-24 - Use hapic for input file
56
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
56
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
88
 
88
 
89
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
89
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
90
     @require_workspace_role(UserRoleInWorkspace.READER)
90
     @require_workspace_role(UserRoleInWorkspace.READER)
91
-    @require_content_types([file_type])
91
+    @require_content_types([FILE_TYPE])
92
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
92
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
93
     @hapic.output_file([])
93
     @hapic.output_file([])
94
     def download_file(self, context, request: TracimRequest, hapic_data=None):
94
     def download_file(self, context, request: TracimRequest, hapic_data=None):
115
 
115
 
116
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
116
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
117
     @require_workspace_role(UserRoleInWorkspace.READER)
117
     @require_workspace_role(UserRoleInWorkspace.READER)
118
-    @require_content_types([file_type])
118
+    @require_content_types([FILE_TYPE])
119
     @hapic.input_path(WorkspaceAndContentRevisionIdPathSchema())
119
     @hapic.input_path(WorkspaceAndContentRevisionIdPathSchema())
120
     @hapic.output_file([])
120
     @hapic.output_file([])
121
     def download_revisions_file(self, context, request: TracimRequest, hapic_data=None):  # nopep8
121
     def download_revisions_file(self, context, request: TracimRequest, hapic_data=None):  # nopep8
148
     # pdf
148
     # pdf
149
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
149
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
150
     @require_workspace_role(UserRoleInWorkspace.READER)
150
     @require_workspace_role(UserRoleInWorkspace.READER)
151
-    @require_content_types([file_type])
151
+    @require_content_types([FILE_TYPE])
152
     @hapic.handle_exception(UnavailablePreviewType, HTTPStatus.BAD_REQUEST)
152
     @hapic.handle_exception(UnavailablePreviewType, HTTPStatus.BAD_REQUEST)
153
     @hapic.handle_exception(PageOfPreviewNotFound, HTTPStatus.BAD_REQUEST)
153
     @hapic.handle_exception(PageOfPreviewNotFound, HTTPStatus.BAD_REQUEST)
154
     @hapic.input_query(PageQuerySchema())
154
     @hapic.input_query(PageQuerySchema())
179
 
179
 
180
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
180
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
181
     @require_workspace_role(UserRoleInWorkspace.READER)
181
     @require_workspace_role(UserRoleInWorkspace.READER)
182
-    @require_content_types([file_type])
182
+    @require_content_types([FILE_TYPE])
183
     @hapic.handle_exception(UnavailablePreviewType, HTTPStatus.BAD_REQUEST)
183
     @hapic.handle_exception(UnavailablePreviewType, HTTPStatus.BAD_REQUEST)
184
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
184
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
185
     @hapic.output_file([])
185
     @hapic.output_file([])
204
 
204
 
205
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
205
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
206
     @require_workspace_role(UserRoleInWorkspace.READER)
206
     @require_workspace_role(UserRoleInWorkspace.READER)
207
-    @require_content_types([file_type])
207
+    @require_content_types([FILE_TYPE])
208
     @hapic.handle_exception(UnavailablePreviewType, HTTPStatus.BAD_REQUEST)
208
     @hapic.handle_exception(UnavailablePreviewType, HTTPStatus.BAD_REQUEST)
209
     @hapic.input_path(WorkspaceAndContentRevisionIdPathSchema())
209
     @hapic.input_path(WorkspaceAndContentRevisionIdPathSchema())
210
     @hapic.input_query(PageQuerySchema())
210
     @hapic.input_query(PageQuerySchema())
239
     # jpg
239
     # jpg
240
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
240
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
241
     @require_workspace_role(UserRoleInWorkspace.READER)
241
     @require_workspace_role(UserRoleInWorkspace.READER)
242
-    @require_content_types([file_type])
242
+    @require_content_types([FILE_TYPE])
243
     @hapic.handle_exception(PageOfPreviewNotFound, HTTPStatus.BAD_REQUEST)
243
     @hapic.handle_exception(PageOfPreviewNotFound, HTTPStatus.BAD_REQUEST)
244
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
244
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
245
     @hapic.input_query(PageQuerySchema())
245
     @hapic.input_query(PageQuerySchema())
272
 
272
 
273
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
273
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
274
     @require_workspace_role(UserRoleInWorkspace.READER)
274
     @require_workspace_role(UserRoleInWorkspace.READER)
275
-    @require_content_types([file_type])
275
+    @require_content_types([FILE_TYPE])
276
     @hapic.handle_exception(PageOfPreviewNotFound, HTTPStatus.BAD_REQUEST)
276
     @hapic.handle_exception(PageOfPreviewNotFound, HTTPStatus.BAD_REQUEST)
277
     @hapic.handle_exception(PreviewDimNotAllowed, HTTPStatus.BAD_REQUEST)
277
     @hapic.handle_exception(PreviewDimNotAllowed, HTTPStatus.BAD_REQUEST)
278
     @hapic.input_query(PageQuerySchema())
278
     @hapic.input_query(PageQuerySchema())
305
 
305
 
306
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
306
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
307
     @require_workspace_role(UserRoleInWorkspace.READER)
307
     @require_workspace_role(UserRoleInWorkspace.READER)
308
-    @require_content_types([file_type])
308
+    @require_content_types([FILE_TYPE])
309
     @hapic.handle_exception(PageOfPreviewNotFound, HTTPStatus.BAD_REQUEST)
309
     @hapic.handle_exception(PageOfPreviewNotFound, HTTPStatus.BAD_REQUEST)
310
     @hapic.handle_exception(PreviewDimNotAllowed, HTTPStatus.BAD_REQUEST)
310
     @hapic.handle_exception(PreviewDimNotAllowed, HTTPStatus.BAD_REQUEST)
311
     @hapic.input_path(RevisionPreviewSizedPathSchema())
311
     @hapic.input_path(RevisionPreviewSizedPathSchema())
342
 
342
 
343
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
343
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
344
     @require_workspace_role(UserRoleInWorkspace.READER)
344
     @require_workspace_role(UserRoleInWorkspace.READER)
345
-    @require_content_types([file_type])
345
+    @require_content_types([FILE_TYPE])
346
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
346
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
347
     @hapic.output_body(AllowedJpgPreviewDimSchema())
347
     @hapic.output_body(AllowedJpgPreviewDimSchema())
348
     def allowed_dim_preview_jpg(self, context, request: TracimRequest, hapic_data=None):  # nopep8
348
     def allowed_dim_preview_jpg(self, context, request: TracimRequest, hapic_data=None):  # nopep8
363
     # File infos
363
     # File infos
364
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
364
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
365
     @require_workspace_role(UserRoleInWorkspace.READER)
365
     @require_workspace_role(UserRoleInWorkspace.READER)
366
-    @require_content_types([file_type])
366
+    @require_content_types([FILE_TYPE])
367
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
367
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
368
     @hapic.output_body(FileContentSchema())
368
     @hapic.output_body(FileContentSchema())
369
     def get_file_infos(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
369
     def get_file_infos(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
387
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
387
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
388
     @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
388
     @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
389
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
389
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
390
-    @require_content_types([file_type])
390
+    @require_content_types([FILE_TYPE])
391
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
391
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
392
     @hapic.input_body(FileContentModifySchema())
392
     @hapic.input_body(FileContentModifySchema())
393
     @hapic.output_body(FileContentSchema())
393
     @hapic.output_body(FileContentSchema())
423
 
423
 
424
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
424
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
425
     @require_workspace_role(UserRoleInWorkspace.READER)
425
     @require_workspace_role(UserRoleInWorkspace.READER)
426
-    @require_content_types([file_type])
426
+    @require_content_types([FILE_TYPE])
427
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
427
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
428
     @hapic.output_body(FileRevisionSchema(many=True))
428
     @hapic.output_body(FileRevisionSchema(many=True))
429
     def get_file_revisions(
429
     def get_file_revisions(
456
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
456
     @hapic.with_api_doc(tags=[SWAGGER_TAG__FILE_ENDPOINTS])
457
     @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
457
     @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
458
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
458
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
459
-    @require_content_types([file_type])
459
+    @require_content_types([FILE_TYPE])
460
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
460
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
461
     @hapic.input_body(SetContentStatusSchema())
461
     @hapic.input_body(SetContentStatusSchema())
462
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
462
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8

+ 6 - 6
backend/tracim_backend/views/contents_api/folder_controller.py 查看文件

25
 from tracim_backend.exceptions import EmptyLabelNotAllowed
25
 from tracim_backend.exceptions import EmptyLabelNotAllowed
26
 from tracim_backend.models.context_models import ContentInContext
26
 from tracim_backend.models.context_models import ContentInContext
27
 from tracim_backend.models.context_models import RevisionInContext
27
 from tracim_backend.models.context_models import RevisionInContext
28
-from tracim_backend.models.contents import CONTENT_TYPES
29
-from tracim_backend.models.contents import folder_type
28
+from tracim_backend.app_models.contents import CONTENT_TYPES
29
+from tracim_backend.app_models.contents import FOLDER_TYPE
30
 from tracim_backend.models.revision_protection import new_revision
30
 from tracim_backend.models.revision_protection import new_revision
31
 
31
 
32
 SWAGGER_TAG__Folders_ENDPOINTS = 'Folders'
32
 SWAGGER_TAG__Folders_ENDPOINTS = 'Folders'
36
 
36
 
37
     @hapic.with_api_doc(tags=[SWAGGER_TAG__Folders_ENDPOINTS])
37
     @hapic.with_api_doc(tags=[SWAGGER_TAG__Folders_ENDPOINTS])
38
     @require_workspace_role(UserRoleInWorkspace.READER)
38
     @require_workspace_role(UserRoleInWorkspace.READER)
39
-    @require_content_types([folder_type])
39
+    @require_content_types([FOLDER_TYPE])
40
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
40
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
41
     @hapic.output_body(TextBasedContentSchema())
41
     @hapic.output_body(TextBasedContentSchema())
42
     def get_folder(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
42
     def get_folder(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
60
     @hapic.with_api_doc(tags=[SWAGGER_TAG__Folders_ENDPOINTS])
60
     @hapic.with_api_doc(tags=[SWAGGER_TAG__Folders_ENDPOINTS])
61
     @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
61
     @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
62
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
62
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
63
-    @require_content_types([folder_type])
63
+    @require_content_types([FOLDER_TYPE])
64
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
64
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
65
     @hapic.input_body(FolderContentModifySchema())
65
     @hapic.input_body(FolderContentModifySchema())
66
     @hapic.output_body(TextBasedContentSchema())
66
     @hapic.output_body(TextBasedContentSchema())
100
 
100
 
101
     @hapic.with_api_doc(tags=[SWAGGER_TAG__Folders_ENDPOINTS])
101
     @hapic.with_api_doc(tags=[SWAGGER_TAG__Folders_ENDPOINTS])
102
     @require_workspace_role(UserRoleInWorkspace.READER)
102
     @require_workspace_role(UserRoleInWorkspace.READER)
103
-    @require_content_types([folder_type])
103
+    @require_content_types([FOLDER_TYPE])
104
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
104
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
105
     @hapic.output_body(TextBasedRevisionSchema(many=True))
105
     @hapic.output_body(TextBasedRevisionSchema(many=True))
106
     def get_folder_revisions(
106
     def get_folder_revisions(
132
 
132
 
133
     @hapic.with_api_doc(tags=[SWAGGER_TAG__Folders_ENDPOINTS])
133
     @hapic.with_api_doc(tags=[SWAGGER_TAG__Folders_ENDPOINTS])
134
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
134
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
135
-    @require_content_types([folder_type])
135
+    @require_content_types([FOLDER_TYPE])
136
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
136
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
137
     @hapic.input_body(SetContentStatusSchema())
137
     @hapic.input_body(SetContentStatusSchema())
138
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
138
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8

+ 7 - 7
backend/tracim_backend/views/contents_api/html_document_controller.py 查看文件

11
 except ImportError:
11
 except ImportError:
12
     from http import client as HTTPStatus
12
     from http import client as HTTPStatus
13
 
13
 
14
-from tracim_backend import TracimRequest
14
+from tracim_backend.lib.utils.request import TracimRequest
15
 from tracim_backend.extensions import hapic
15
 from tracim_backend.extensions import hapic
16
 from tracim_backend.lib.core.content import ContentApi
16
 from tracim_backend.lib.core.content import ContentApi
17
 from tracim_backend.views.controllers import Controller
17
 from tracim_backend.views.controllers import Controller
26
 from tracim_backend.exceptions import EmptyLabelNotAllowed
26
 from tracim_backend.exceptions import EmptyLabelNotAllowed
27
 from tracim_backend.models.context_models import ContentInContext
27
 from tracim_backend.models.context_models import ContentInContext
28
 from tracim_backend.models.context_models import RevisionInContext
28
 from tracim_backend.models.context_models import RevisionInContext
29
-from tracim_backend.models.contents import CONTENT_TYPES
30
-from tracim_backend.models.contents import html_documents_type
29
+from tracim_backend.app_models.contents import CONTENT_TYPES
30
+from tracim_backend.app_models.contents import HTML_DOCUMENTS_TYPE
31
 from tracim_backend.models.revision_protection import new_revision
31
 from tracim_backend.models.revision_protection import new_revision
32
 
32
 
33
 SWAGGER_TAG__HTML_DOCUMENT_ENDPOINTS = 'HTML documents'
33
 SWAGGER_TAG__HTML_DOCUMENT_ENDPOINTS = 'HTML documents'
37
 
37
 
38
     @hapic.with_api_doc(tags=[SWAGGER_TAG__HTML_DOCUMENT_ENDPOINTS])
38
     @hapic.with_api_doc(tags=[SWAGGER_TAG__HTML_DOCUMENT_ENDPOINTS])
39
     @require_workspace_role(UserRoleInWorkspace.READER)
39
     @require_workspace_role(UserRoleInWorkspace.READER)
40
-    @require_content_types([html_documents_type])
40
+    @require_content_types([HTML_DOCUMENTS_TYPE])
41
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
41
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
42
     @hapic.output_body(TextBasedContentSchema())
42
     @hapic.output_body(TextBasedContentSchema())
43
     def get_html_document(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
43
     def get_html_document(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
61
     @hapic.with_api_doc(tags=[SWAGGER_TAG__HTML_DOCUMENT_ENDPOINTS])
61
     @hapic.with_api_doc(tags=[SWAGGER_TAG__HTML_DOCUMENT_ENDPOINTS])
62
     @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
62
     @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
63
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
63
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
64
-    @require_content_types([html_documents_type])
64
+    @require_content_types([HTML_DOCUMENTS_TYPE])
65
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
65
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
66
     @hapic.input_body(TextBasedContentModifySchema())
66
     @hapic.input_body(TextBasedContentModifySchema())
67
     @hapic.output_body(TextBasedContentSchema())
67
     @hapic.output_body(TextBasedContentSchema())
97
 
97
 
98
     @hapic.with_api_doc(tags=[SWAGGER_TAG__HTML_DOCUMENT_ENDPOINTS])
98
     @hapic.with_api_doc(tags=[SWAGGER_TAG__HTML_DOCUMENT_ENDPOINTS])
99
     @require_workspace_role(UserRoleInWorkspace.READER)
99
     @require_workspace_role(UserRoleInWorkspace.READER)
100
-    @require_content_types([html_documents_type])
100
+    @require_content_types([HTML_DOCUMENTS_TYPE])
101
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
101
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
102
     @hapic.output_body(TextBasedRevisionSchema(many=True))
102
     @hapic.output_body(TextBasedRevisionSchema(many=True))
103
     def get_html_document_revisions(
103
     def get_html_document_revisions(
129
 
129
 
130
     @hapic.with_api_doc(tags=[SWAGGER_TAG__HTML_DOCUMENT_ENDPOINTS])
130
     @hapic.with_api_doc(tags=[SWAGGER_TAG__HTML_DOCUMENT_ENDPOINTS])
131
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
131
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
132
-    @require_content_types([html_documents_type])
132
+    @require_content_types([HTML_DOCUMENTS_TYPE])
133
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
133
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
134
     @hapic.input_body(SetContentStatusSchema())
134
     @hapic.input_body(SetContentStatusSchema())
135
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
135
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8

+ 7 - 7
backend/tracim_backend/views/contents_api/threads_controller.py 查看文件

10
 except ImportError:
10
 except ImportError:
11
     from http import client as HTTPStatus
11
     from http import client as HTTPStatus
12
 
12
 
13
-from tracim_backend import TracimRequest
13
+from tracim_backend.lib.utils.request import TracimRequest
14
 from tracim_backend.extensions import hapic
14
 from tracim_backend.extensions import hapic
15
 from tracim_backend.lib.core.content import ContentApi
15
 from tracim_backend.lib.core.content import ContentApi
16
 from tracim_backend.views.controllers import Controller
16
 from tracim_backend.views.controllers import Controller
25
 from tracim_backend.exceptions import EmptyLabelNotAllowed
25
 from tracim_backend.exceptions import EmptyLabelNotAllowed
26
 from tracim_backend.models.context_models import ContentInContext
26
 from tracim_backend.models.context_models import ContentInContext
27
 from tracim_backend.models.context_models import RevisionInContext
27
 from tracim_backend.models.context_models import RevisionInContext
28
-from tracim_backend.models.contents import CONTENT_TYPES
29
-from tracim_backend.models.contents import thread_type
28
+from tracim_backend.app_models.contents import CONTENT_TYPES
29
+from tracim_backend.app_models.contents import THREAD_TYPE
30
 from tracim_backend.models.revision_protection import new_revision
30
 from tracim_backend.models.revision_protection import new_revision
31
 
31
 
32
 SWAGGER_TAG__THREAD_ENDPOINTS = 'Threads'
32
 SWAGGER_TAG__THREAD_ENDPOINTS = 'Threads'
36
 
36
 
37
     @hapic.with_api_doc(tags=[SWAGGER_TAG__THREAD_ENDPOINTS])
37
     @hapic.with_api_doc(tags=[SWAGGER_TAG__THREAD_ENDPOINTS])
38
     @require_workspace_role(UserRoleInWorkspace.READER)
38
     @require_workspace_role(UserRoleInWorkspace.READER)
39
-    @require_content_types([thread_type])
39
+    @require_content_types([THREAD_TYPE])
40
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
40
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
41
     @hapic.output_body(TextBasedContentSchema())
41
     @hapic.output_body(TextBasedContentSchema())
42
     def get_thread(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
42
     def get_thread(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
60
     @hapic.with_api_doc(tags=[SWAGGER_TAG__THREAD_ENDPOINTS])
60
     @hapic.with_api_doc(tags=[SWAGGER_TAG__THREAD_ENDPOINTS])
61
     @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
61
     @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
62
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
62
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
63
-    @require_content_types([thread_type])
63
+    @require_content_types([THREAD_TYPE])
64
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
64
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
65
     @hapic.input_body(TextBasedContentModifySchema())
65
     @hapic.input_body(TextBasedContentModifySchema())
66
     @hapic.output_body(TextBasedContentSchema())
66
     @hapic.output_body(TextBasedContentSchema())
96
 
96
 
97
     @hapic.with_api_doc(tags=[SWAGGER_TAG__THREAD_ENDPOINTS])
97
     @hapic.with_api_doc(tags=[SWAGGER_TAG__THREAD_ENDPOINTS])
98
     @require_workspace_role(UserRoleInWorkspace.READER)
98
     @require_workspace_role(UserRoleInWorkspace.READER)
99
-    @require_content_types([thread_type])
99
+    @require_content_types([THREAD_TYPE])
100
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
100
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
101
     @hapic.output_body(TextBasedRevisionSchema(many=True))
101
     @hapic.output_body(TextBasedRevisionSchema(many=True))
102
     def get_thread_revisions(
102
     def get_thread_revisions(
128
 
128
 
129
     @hapic.with_api_doc(tags=[SWAGGER_TAG__THREAD_ENDPOINTS])
129
     @hapic.with_api_doc(tags=[SWAGGER_TAG__THREAD_ENDPOINTS])
130
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
130
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
131
-    @require_content_types([thread_type])
131
+    @require_content_types([THREAD_TYPE])
132
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
132
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
133
     @hapic.input_body(SetContentStatusSchema())
133
     @hapic.input_body(SetContentStatusSchema())
134
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
134
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8

+ 12 - 10
backend/tracim_backend/views/core_api/schemas.py 查看文件

7
 
7
 
8
 from tracim_backend.lib.utils.utils import DATETIME_FORMAT
8
 from tracim_backend.lib.utils.utils import DATETIME_FORMAT
9
 from tracim_backend.models.auth import Profile
9
 from tracim_backend.models.auth import Profile
10
+
11
+from tracim_backend.app_models.contents import GlobalStatus
12
+from tracim_backend.app_models.contents import CONTENT_STATUS
13
+from tracim_backend.app_models.contents import CONTENT_TYPES
14
+from tracim_backend.app_models.contents import open_status
10
 from tracim_backend.models.auth import Group
15
 from tracim_backend.models.auth import Group
11
-from tracim_backend.models.contents import GlobalStatus
12
-from tracim_backend.models.contents import CONTENT_STATUS
13
-from tracim_backend.models.contents import CONTENT_TYPES
14
-from tracim_backend.models.contents import open_status
15
 from tracim_backend.models.context_models import ActiveContentFilter
16
 from tracim_backend.models.context_models import ActiveContentFilter
16
 from tracim_backend.models.context_models import FolderContentUpdate
17
 from tracim_backend.models.context_models import FolderContentUpdate
17
 from tracim_backend.models.context_models import AutocompleteQuery
18
 from tracim_backend.models.context_models import AutocompleteQuery
41
 from tracim_backend.models.context_models import LoginCredentials
42
 from tracim_backend.models.context_models import LoginCredentials
42
 from tracim_backend.models.data import UserRoleInWorkspace
43
 from tracim_backend.models.data import UserRoleInWorkspace
43
 from tracim_backend.models.data import ActionDescription
44
 from tracim_backend.models.data import ActionDescription
45
+from tracim_backend.app_models.validator import all_content_types_validator
44
 
46
 
45
 
47
 
46
 class UserDigestSchema(marshmallow.Schema):
48
 class UserDigestSchema(marshmallow.Schema):
417
     content_type = marshmallow.fields.String(
419
     content_type = marshmallow.fields.String(
418
         example=CONTENT_TYPES.Any_SLUG,
420
         example=CONTENT_TYPES.Any_SLUG,
419
         default=CONTENT_TYPES.Any_SLUG,
421
         default=CONTENT_TYPES.Any_SLUG,
420
-        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug())
422
+        validate=all_content_types_validator
421
     )
423
     )
422
 
424
 
423
     @post_load
425
     @post_load
668
 class ContentTypeSchema(marshmallow.Schema):
670
 class ContentTypeSchema(marshmallow.Schema):
669
     slug = marshmallow.fields.String(
671
     slug = marshmallow.fields.String(
670
         example='pagehtml',
672
         example='pagehtml',
671
-        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),
673
+        validate=all_content_types_validator,
672
     )
674
     )
673
     fa_icon = marshmallow.fields.String(
675
     fa_icon = marshmallow.fields.String(
674
         example='fa-file-text-o',
676
         example='fa-file-text-o',
722
     )
724
     )
723
     content_type = marshmallow.fields.String(
725
     content_type = marshmallow.fields.String(
724
         example='html-document',
726
         example='html-document',
725
-        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),
727
+        validate=all_content_types_validator,
726
     )
728
     )
727
     parent_id = marshmallow.fields.Integer(
729
     parent_id = marshmallow.fields.Integer(
728
         example=35,
730
         example=35,
757
     label = marshmallow.fields.Str(example='Intervention Report 12')
759
     label = marshmallow.fields.Str(example='Intervention Report 12')
758
     content_type = marshmallow.fields.Str(
760
     content_type = marshmallow.fields.Str(
759
         example='html-document',
761
         example='html-document',
760
-        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),
762
+        validate=all_content_types_validator,
761
     )
763
     )
762
     sub_content_types = marshmallow.fields.List(
764
     sub_content_types = marshmallow.fields.List(
763
         marshmallow.fields.String(
765
         marshmallow.fields.String(
764
             example='html-content',
766
             example='html-content',
765
-            validate=OneOf(CONTENT_TYPES.extended_endpoint_allowed_types_slug())
767
+            validate=all_content_types_validator
766
         ),
768
         ),
767
         description='list of content types allowed as sub contents. '
769
         description='list of content types allowed as sub contents. '
768
                     'This field is required for folder contents, '
770
                     'This field is required for folder contents, '
912
     sub_content_types = marshmallow.fields.List(
914
     sub_content_types = marshmallow.fields.List(
913
         marshmallow.fields.String(
915
         marshmallow.fields.String(
914
             example='html-document',
916
             example='html-document',
915
-            validate=OneOf(CONTENT_TYPES.extended_endpoint_allowed_types_slug())
917
+            validate=all_content_types_validator,
916
         ),
918
         ),
917
         description='list of content types allowed as sub contents. '
919
         description='list of content types allowed as sub contents. '
918
                     'This field is required for folder contents, '
920
                     'This field is required for folder contents, '

+ 1 - 1
backend/tracim_backend/views/core_api/session_controller.py 查看文件

5
 except ImportError:
5
 except ImportError:
6
     from http import client as HTTPStatus
6
     from http import client as HTTPStatus
7
 
7
 
8
-from tracim_backend import TracimRequest
8
+from tracim_backend.lib.utils.request import TracimRequest
9
 from tracim_backend.extensions import hapic
9
 from tracim_backend.extensions import hapic
10
 from tracim_backend.lib.core.user import UserApi
10
 from tracim_backend.lib.core.user import UserApi
11
 from tracim_backend.views.controllers import Controller
11
 from tracim_backend.views.controllers import Controller

+ 9 - 4
backend/tracim_backend/views/core_api/system_controller.py 查看文件

2
 from pyramid.config import Configurator
2
 from pyramid.config import Configurator
3
 from tracim_backend.exceptions import NotAuthenticated
3
 from tracim_backend.exceptions import NotAuthenticated
4
 from tracim_backend.exceptions import InsufficientUserProfile
4
 from tracim_backend.exceptions import InsufficientUserProfile
5
+from tracim_backend.lib.core.application import ApplicationApi
5
 from tracim_backend.lib.utils.authorization import require_profile
6
 from tracim_backend.lib.utils.authorization import require_profile
6
 from tracim_backend.models import Group
7
 from tracim_backend.models import Group
7
-from tracim_backend.models.applications import applications
8
-from tracim_backend.models.contents import CONTENT_TYPES
8
+from tracim_backend.app_models.contents import CONTENT_TYPES
9
 
9
 
10
 try:  # Python 3.5+
10
 try:  # Python 3.5+
11
     from http import HTTPStatus
11
     from http import HTTPStatus
12
 except ImportError:
12
 except ImportError:
13
     from http import client as HTTPStatus
13
     from http import client as HTTPStatus
14
 
14
 
15
-from tracim_backend import TracimRequest
15
+from tracim_backend.lib.utils.request import TracimRequest
16
 from tracim_backend.extensions import hapic
16
 from tracim_backend.extensions import hapic
17
+from tracim_backend.extensions import app_list
17
 from tracim_backend.views.controllers import Controller
18
 from tracim_backend.views.controllers import Controller
18
 from tracim_backend.views.core_api.schemas import ApplicationSchema
19
 from tracim_backend.views.core_api.schemas import ApplicationSchema
19
 from tracim_backend.views.core_api.schemas import ContentTypeSchema
20
 from tracim_backend.views.core_api.schemas import ContentTypeSchema
30
         """
31
         """
31
         Get list of alls applications installed in this tracim instance.
32
         Get list of alls applications installed in this tracim instance.
32
         """
33
         """
33
-        return applications
34
+        app_config = request.registry.settings['CFG']
35
+        app_api = ApplicationApi(
36
+            app_list=app_list,
37
+        )
38
+        return app_api.get_all()
34
 
39
 
35
     @hapic.with_api_doc(tags=[SWAGGER_TAG_SYSTEM_ENDPOINTS])
40
     @hapic.with_api_doc(tags=[SWAGGER_TAG_SYSTEM_ENDPOINTS])
36
     @require_profile(Group.TIM_USER)
41
     @require_profile(Group.TIM_USER)

+ 2 - 2
backend/tracim_backend/views/core_api/user_controller.py 查看文件

7
     from http import client as HTTPStatus
7
     from http import client as HTTPStatus
8
 
8
 
9
 from tracim_backend import hapic
9
 from tracim_backend import hapic
10
-from tracim_backend import TracimRequest
10
+from tracim_backend.lib.utils.request import TracimRequest
11
 from tracim_backend.models import Group
11
 from tracim_backend.models import Group
12
 from tracim_backend.lib.core.group import GroupApi
12
 from tracim_backend.lib.core.group import GroupApi
13
 from tracim_backend.lib.core.user import UserApi
13
 from tracim_backend.lib.core.user import UserApi
36
 from tracim_backend.views.core_api.schemas import ContentDigestSchema
36
 from tracim_backend.views.core_api.schemas import ContentDigestSchema
37
 from tracim_backend.views.core_api.schemas import ActiveContentFilterQuerySchema
37
 from tracim_backend.views.core_api.schemas import ActiveContentFilterQuerySchema
38
 from tracim_backend.views.core_api.schemas import WorkspaceDigestSchema
38
 from tracim_backend.views.core_api.schemas import WorkspaceDigestSchema
39
-from tracim_backend.models.contents import CONTENT_TYPES
39
+from tracim_backend.app_models.contents import CONTENT_TYPES
40
 
40
 
41
 SWAGGER_TAG__USER_ENDPOINTS = 'Users'
41
 SWAGGER_TAG__USER_ENDPOINTS = 'Users'
42
 
42
 

+ 2 - 2
backend/tracim_backend/views/core_api/workspace_controller.py 查看文件

12
     from http import client as HTTPStatus
12
     from http import client as HTTPStatus
13
 
13
 
14
 from tracim_backend import hapic
14
 from tracim_backend import hapic
15
+from tracim_backend.lib.utils.request import TracimRequest
15
 from tracim_backend import BASE_API_V2
16
 from tracim_backend import BASE_API_V2
16
-from tracim_backend import TracimRequest
17
 from tracim_backend.lib.core.workspace import WorkspaceApi
17
 from tracim_backend.lib.core.workspace import WorkspaceApi
18
 from tracim_backend.lib.core.content import ContentApi
18
 from tracim_backend.lib.core.content import ContentApi
19
 from tracim_backend.lib.core.userworkspace import RoleApi
19
 from tracim_backend.lib.core.userworkspace import RoleApi
52
 from tracim_backend.views.core_api.schemas import WorkspaceSchema
52
 from tracim_backend.views.core_api.schemas import WorkspaceSchema
53
 from tracim_backend.views.core_api.schemas import WorkspaceIdPathSchema
53
 from tracim_backend.views.core_api.schemas import WorkspaceIdPathSchema
54
 from tracim_backend.views.core_api.schemas import WorkspaceMemberSchema
54
 from tracim_backend.views.core_api.schemas import WorkspaceMemberSchema
55
-from tracim_backend.models.contents import CONTENT_TYPES
55
+from tracim_backend.app_models.contents import CONTENT_TYPES
56
 from tracim_backend.models.revision_protection import new_revision
56
 from tracim_backend.models.revision_protection import new_revision
57
 
57
 
58
 SWAGGER_TAG_WORKSPACE_ENDPOINTS = 'Workspaces'
58
 SWAGGER_TAG_WORKSPACE_ENDPOINTS = 'Workspaces'

+ 9 - 3
backend/tracim_backend/views/frontend.py 查看文件

2
 
2
 
3
 from pyramid.renderers import render_to_response
3
 from pyramid.renderers import render_to_response
4
 from pyramid.config import Configurator
4
 from pyramid.config import Configurator
5
+from tracim_backend.extensions import app_list
5
 from tracim_backend.exceptions import PageNotFound
6
 from tracim_backend.exceptions import PageNotFound
6
-from tracim_backend.models.applications import applications
7
+from tracim_backend.lib.core.application import ApplicationApi
8
+from tracim_backend.lib.utils.utils import Color
7
 from tracim_backend.views import BASE_API_V2
9
 from tracim_backend.views import BASE_API_V2
8
 from tracim_backend.lib.utils.request import TracimRequest
10
 from tracim_backend.lib.utils.request import TracimRequest
9
 from tracim_backend.views.controllers import Controller
11
 from tracim_backend.views.controllers import Controller
10
-import spectra
11
 
12
 
12
 INDEX_PAGE_NAME = 'index.mak'
13
 INDEX_PAGE_NAME = 'index.mak'
13
 APP_FRONTEND_PATH = 'app/{minislug}.app.js'
14
 APP_FRONTEND_PATH = 'app/{minislug}.app.js'
34
         app_config = request.registry.settings['CFG']
35
         app_config = request.registry.settings['CFG']
35
         # TODO - G.M - 2018-08-07 - Refactor autogen valid app list for frontend
36
         # TODO - G.M - 2018-08-07 - Refactor autogen valid app list for frontend
36
         frontend_apps = []
37
         frontend_apps = []
38
+        app_api = ApplicationApi(
39
+            app_list=app_list,
40
+        )
41
+        applications = app_api.get_all()
37
         for app in applications:
42
         for app in applications:
38
             app_frontend_path = APP_FRONTEND_PATH.replace('{minislug}',
43
             app_frontend_path = APP_FRONTEND_PATH.replace('{minislug}',
39
                                                           app.minislug)  # nopep8
44
                                                           app.minislug)  # nopep8
41
                                     app_frontend_path)  # nopep8
46
                                     app_frontend_path)  # nopep8
42
             if os.path.exists(app_path):
47
             if os.path.exists(app_path):
43
                 frontend_apps.append(app)
48
                 frontend_apps.append(app)
49
+
44
         return render_to_response(
50
         return render_to_response(
45
             self._get_index_file_path(),
51
             self._get_index_file_path(),
46
             {
52
             {
47
                 'colors': {
53
                 'colors': {
48
-                    'primary': spectra.html('#7d4e24'),
54
+                    'primary': Color(app_config.APPS_COLORS['primary']),
49
                 },
55
                 },
50
                 'applications': frontend_apps,
56
                 'applications': frontend_apps,
51
             }
57
             }

+ 10 - 0
backend/wsgidav-test.conf 查看文件

1
+host  = "0.0.0.0"
2
+port = 3030
3
+show_history = True
4
+show_deleted = True
5
+show_archived = True
6
+manager_locks = True
7
+root_path = ''
8
+acceptbasic = True
9
+acceptdigest = False
10
+defaultdigest = False

+ 5 - 0
backend_lib.sh 查看文件

37
        log "generate missing wsgidav.conf ..."
37
        log "generate missing wsgidav.conf ..."
38
        cp wsgidav.conf.sample wsgidav.conf
38
        cp wsgidav.conf.sample wsgidav.conf
39
     fi
39
     fi
40
+
41
+    if [ ! -f ../color.json ]; then
42
+       log "generate missing color.json ..."
43
+       cp ../color.json.sample ../color.json
44
+    fi
40
 }
45
 }
41
 
46
 
42
 function setup_db {
47
 function setup_db {

+ 5 - 0
bash_library.sh 查看文件

2
 
2
 
3
 YELLOW='\033[1;33m'
3
 YELLOW='\033[1;33m'
4
 BROWN='\033[0;33m'
4
 BROWN='\033[0;33m'
5
+GREEN='\033[1;32m'
5
 NC='\033[0m' # No Color
6
 NC='\033[0m' # No Color
6
 
7
 
7
 function log {
8
 function log {
8
     echo -e "\n${YELLOW}[$(date +'%H:%M:%S')]${BROWN} $ $1${NC}"
9
     echo -e "\n${YELLOW}[$(date +'%H:%M:%S')]${BROWN} $ $1${NC}"
9
 }
10
 }
11
+
12
+function loggood {
13
+    echo -e "\n${YELLOW}[$(date +'%H:%M:%S')]${GREEN} $ $1${NC}"
14
+} 

+ 3 - 3
color.json.sample 查看文件

1
 {
1
 {
2
   "primary": "#7d4e24",
2
   "primary": "#7d4e24",
3
-  "html-document": "#3f52e3",
4
-  "thread": "#ad4cf9",
5
-  "file": "#ff9900"
3
+  "contents/html-document": "#3f52e3",
4
+  "contents/thread": "#ad4cf9",
5
+  "contents/file": "#ff9900"
6
 }
6
 }

+ 18 - 18
frontend/dist/index.mak 查看文件

21
         param = 'color'
21
         param = 'color'
22
         color_change_value = 15
22
         color_change_value = 15
23
       %>
23
       %>
24
-      ${html_class.replace('{state}', '')} { ${param}: ${primary.hexcode}; }
25
-      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken(color_change_value).hexcode}; }
26
-      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.brighten(color_change_value).hexcode}; }
24
+      ${html_class.replace('{state}', '')} { ${param}: ${primary.normal}; }
25
+      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken}; }
26
+      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.lighten}; }
27
       <% html_class = '.primaryColorFont{state}Hover:hover' %>
27
       <% html_class = '.primaryColorFont{state}Hover:hover' %>
28
-      ${html_class.replace('{state}', '')} { ${param}: ${primary.hexcode}; }
29
-      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken(color_change_value).hexcode}; }
30
-      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.brighten(color_change_value).hexcode}; }
28
+      ${html_class.replace('{state}', '')} { ${param}: ${primary.normal}; }
29
+      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken}; }
30
+      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.lighten}; }
31
 
31
 
32
       <%
32
       <%
33
         html_class = '.primaryColorBg{state}'
33
         html_class = '.primaryColorBg{state}'
34
         param = 'background-color'
34
         param = 'background-color'
35
       %>
35
       %>
36
-      ${html_class.replace('{state}', '')} { ${param}: ${primary.hexcode}; }
37
-      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken(color_change_value).hexcode}; }
38
-      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.brighten(color_change_value).hexcode}; }
36
+      ${html_class.replace('{state}', '')} { ${param}: ${primary.normal}; }
37
+      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken}; }
38
+      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.lighten}; }
39
       <% html_class = '.primaryColorBg{state}Hover:hover'%>
39
       <% html_class = '.primaryColorBg{state}Hover:hover'%>
40
-      ${html_class.replace('{state}', '')} { ${param}: ${primary.hexcode}; }
41
-      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken(color_change_value).hexcode}; }
42
-      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.brighten(color_change_value).hexcode}; }
40
+      ${html_class.replace('{state}', '')} { ${param}: ${primary.normal}; }
41
+      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken}; }
42
+      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.lighten}; }
43
 
43
 
44
       <%
44
       <%
45
         param = 'border-color'
45
         param = 'border-color'
46
         html_class = '.primaryColorBorder{state}'
46
         html_class = '.primaryColorBorder{state}'
47
       %>
47
       %>
48
-      ${html_class.replace('{state}', '')} { ${param}: ${primary.hexcode}; }
49
-      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken(color_change_value).hexcode}; }
50
-      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.brighten(color_change_value).hexcode}; }
48
+      ${html_class.replace('{state}', '')} { ${param}: ${primary.normal}; }
49
+      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken}; }
50
+      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.lighten}; }
51
       <% html_class = '.primaryColorBorder{state}Hover:hover' %>
51
       <% html_class = '.primaryColorBorder{state}Hover:hover' %>
52
-      ${html_class.replace('{state}', '')} { ${param}: ${primary.hexcode}; }
53
-      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken(color_change_value).hexcode}; }
54
-      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.brighten(color_change_value).hexcode}; }
52
+      ${html_class.replace('{state}', '')} { ${param}: ${primary.normal}; }
53
+      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken}; }
54
+      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.lighten}; }
55
     </style>
55
     </style>
56
   </head>
56
   </head>
57
 
57
 

+ 31 - 6
frontend/i18next.scanner/en/translation.json 查看文件

10
   "Search...": "Search...",
10
   "Search...": "Search...",
11
   "Create a workspace": "Create a workspace",
11
   "Create a workspace": "Create a workspace",
12
   "Title": "Title",
12
   "Title": "Title",
13
-  "Change": "Change",
14
   "Move": "Move",
13
   "Move": "Move",
15
   "Download": "Download",
14
   "Download": "Download",
16
   "Archive": "Archive",
15
   "Archive": "Archive",
58
   "You have subscribed to this workspace's notifications": "You have subscribed to this workspace's notifications",
57
   "You have subscribed to this workspace's notifications": "You have subscribed to this workspace's notifications",
59
   "Connection": "Connection",
58
   "Connection": "Connection",
60
   "Forgotten password ?": "Forgotten password ?",
59
   "Forgotten password ?": "Forgotten password ?",
61
-  "An error has happened when fetching workspace detail": "An error has happened when fetching workspace detail",
62
-  "An error has happened while fetching member list": "An error has happened while fetching member list",
63
-  "An error has happened while fetching recent activity list": "An error has happened while fetching recent activity list",
64
-  "An error has happened while fetching read status list": "An error has happened while fetching read status list",
65
-  "Hi {{name}} ! Currently, you are ": "Hi {{name}} ! Currently, you are "
60
+  "Hi {{name}} ! Currently, you are ": "Hi {{name}} ! Currently, you are ",
61
+  "Workspace management": "Workspace management",
62
+  "ID": "ID",
63
+  "Description": "Description",
64
+  "Member's number": "Member's number",
65
+  "Delete workspace": "Delete workspace",
66
+  "An error has happened while fetching": "An error has happened while fetching",
67
+  "workspace detail": "workspace detail",
68
+  "member list": "member list",
69
+  "recent activity list": "recent activity list",
70
+  "read status list": "read status list",
71
+  "An error has happened while fetching \"mark all as read\"": "An error has happened while fetching \"mark all as read\"",
72
+  "known members list": "known members list",
73
+  "Please set a name or email": "Please set a name or email",
74
+  "Please set a role": "Please set a role",
75
+  "An error has happened while adding the member": "An error has happened while adding the member",
76
+  "Welcome on Tracim": "Welcome on Tracim",
77
+  "Thank you for trusting us and using our collaborative tool": "Thank you for trusting us and using our collaborative tool",
78
+  "Error while archiving document": "Error while archiving document",
79
+  "Error while deleting document": "Error while deleting document",
80
+  "subscribed": "subscribed",
81
+  "Feature": "Feature",
82
+  "Explore": "Explore",
83
+  "About": "About",
84
+  "Admin workspace": "Admin workspace",
85
+  "Admin user": "Admin user",
86
+  "You will create your first workspace": "You will create your first workspace",
87
+  "create a workspace": "create a workspace",
88
+  "This page list all workspaces": "This page list all workspaces",
89
+  "Create ...": "Create ...",
90
+  "Edit": "Edit"
66
 }
91
 }

+ 31 - 6
frontend/i18next.scanner/fr/translation.json 查看文件

10
   "Search...": "Rechercher ...",
10
   "Search...": "Rechercher ...",
11
   "Create a workspace": "Créer un espace de travail",
11
   "Create a workspace": "Créer un espace de travail",
12
   "Title": "Titre",
12
   "Title": "Titre",
13
-  "Change": "Modifier",
14
   "Move": "Déplacer",
13
   "Move": "Déplacer",
15
   "Download": "Télécharger",
14
   "Download": "Télécharger",
16
   "Archive": "Archiver",
15
   "Archive": "Archiver",
58
   "You have subscribed to this workspace's notifications": "Vous êtes abonné aux notifications de cet espace de travail.",
57
   "You have subscribed to this workspace's notifications": "Vous êtes abonné aux notifications de cet espace de travail.",
59
   "Connection": "Connexion",
58
   "Connection": "Connexion",
60
   "Forgotten password ?": "Mot de passe oublié ?",
59
   "Forgotten password ?": "Mot de passe oublié ?",
61
-  "An error has happened when fetching workspace detail": "__NOT_TRANSLATED__",
62
-  "An error has happened while fetching member list": "__NOT_TRANSLATED__",
63
-  "An error has happened while fetching recent activity list": "__NOT_TRANSLATED__",
64
-  "An error has happened while fetching read status list": "__NOT_TRANSLATED__",
65
-  "Hi {{name}} ! Currently, you are ": "Bonjour {{name}} ! Vous êtes actuellement"
60
+  "Hi {{name}} ! Currently, you are ": "Bonjour {{name}} ! Vous êtes actuellement",
61
+  "Workspace management": "Gestion des espaces de travail",
62
+  "ID": "ID",
63
+  "Description": "Description",
64
+  "Member's number": "Nombre de membre",
65
+  "Delete workspace": "Supprimer un espace de travail",
66
+  "An error has happened while fetching": "Erreur lors de la récupération de données",
67
+  "workspace detail": "Detail de l'espace de travail",
68
+  "member list": "Liste des membres",
69
+  "recent activity list": "Liste des activités récente",
70
+  "read status list": "Lire la liste des statuts",
71
+  "An error has happened while fetching \"mark all as read\"": "__NOT_TRANSLATED__",
72
+  "known members list": "Liste des membres connus",
73
+  "Please set a name or email": "Entrez un nom, s'il vous plaît",
74
+  "Please set a role": "Choisissez un rôle s'il vous plaît",
75
+  "An error has happened while adding the member": "Une erreur s'est produite lors de l'ajout d'un membre",
76
+  "Welcome on Tracim": "Bienvenue sur Tracim",
77
+  "Thank you for trusting us and using our collaborative tool": "Merci de nous faire confiance et d'utiliser notre outil collaboratif.",
78
+  "Error while archiving document": "Erreur lors de l'archivage d'un document",
79
+  "Error while deleting document": "Erreur lors de la suppression d'un document",
80
+  "subscribed": "Abonné(e)",
81
+  "Feature": "Fonctionnalité",
82
+  "Explore": "Exploré",
83
+  "About": "A propos",
84
+  "Admin workspace": "Administration des espaces de travail",
85
+  "Admin user": "Administration des utilisateurs",
86
+  "You will create your first workspace": "Vous allez créer votre premier espace de travail",
87
+  "create a workspace": "créer un espace de travail",
88
+  "This page list all workspaces": "Cette page liste tous les espaces de travail",
89
+  "Create ...": "Créer ...",
90
+  "Edit": "Editer"
66
 }
91
 }

+ 3 - 4
frontend/src/component/Dashboard/MemberList.styl 查看文件

1
 @import "../../../node_modules/tracim_frontend_lib/src/css/Variable.styl"
1
 @import "../../../node_modules/tracim_frontend_lib/src/css/Variable.styl"
2
 
2
 
3
 .memberlist
3
 .memberlist
4
-  margin 0 0 50px 0
5
-  width 35%
4
+  margin 0 0 50px 10px
5
+  width 37%
6
   &__title
6
   &__title
7
     margin-bottom 20px
7
     margin-bottom 20px
8
     padding 6px
8
     padding 6px
10
   &__wrapper
10
   &__wrapper
11
     position relative
11
     position relative
12
     border 1px solid grey
12
     border 1px solid grey
13
-    height 480px
13
+    min-height 480px
14
   &__list
14
   &__list
15
     margin 0
15
     margin 0
16
     padding 0
16
     padding 0
17
     list-style none
17
     list-style none
18
     height 400px
18
     height 400px
19
-    overflow-Y scroll
20
     &__item
19
     &__item
21
       display flex
20
       display flex
22
       border-bottom 1px solid grey
21
       border-bottom 1px solid grey

+ 1 - 2
frontend/src/component/Dashboard/RecentActivity.styl 查看文件

5
   width 60%
5
   width 60%
6
   &__wrapper
6
   &__wrapper
7
     border 1px solid grey
7
     border 1px solid grey
8
-    height 480px
9
-    overflow-y scroll
8
+    min-height 480px
10
   &__header
9
   &__header
11
     display flex
10
     display flex
12
     justify-content space-between
11
     justify-content space-between

+ 11 - 4
frontend/src/component/Header/MenuLinkList.jsx 查看文件

1
 import React from 'react'
1
 import React from 'react'
2
 import PropTypes from 'prop-types'
2
 import PropTypes from 'prop-types'
3
+import { translate } from 'react-i18next'
3
 
4
 
4
 const MenuLinkList = props => {
5
 const MenuLinkList = props => {
5
   return (
6
   return (
6
     <ul className='header__menu__list text-center navbar-nav mr-auto'>
7
     <ul className='header__menu__list text-center navbar-nav mr-auto'>
7
       <li className='list__item nav-item'>
8
       <li className='list__item nav-item'>
8
-        <a className='list__item__link' href='' onClick={props.onClickFeature}>Fonctionnalité</a>
9
+        <a className='list__item__link' href='' onClick={props.onClickFeature}>
10
+          {props.t('Feature')}
11
+        </a>
9
       </li>
12
       </li>
10
       <li className='list__item nav-item'>
13
       <li className='list__item nav-item'>
11
-        <a className='list__item__link' href='' onClick={props.onClickExplore}>Explorer</a>
14
+        <a className='list__item__link' href='' onClick={props.onClickExplore}>
15
+          {props.t('Explore')}
16
+        </a>
12
       </li>
17
       </li>
13
       <li className='list__item nav-item'>
18
       <li className='list__item nav-item'>
14
-        <a className='list__item__link' href='' onClick={props.onClickAbout}>A Propos</a>
19
+        <a className='list__item__link' href='' onClick={props.onClickAbout}>
20
+          {props.t('About')}
21
+        </a>
15
       </li>
22
       </li>
16
     </ul>
23
     </ul>
17
   )
24
   )
18
 }
25
 }
19
-export default MenuLinkList
26
+export default translate()(MenuLinkList)
20
 
27
 
21
 MenuLinkList.propTypes = {
28
 MenuLinkList.propTypes = {
22
   onClickFeature: PropTypes.func.isRequired,
29
   onClickFeature: PropTypes.func.isRequired,

+ 1 - 1
frontend/src/component/Workspace/BtnExtandedAction.jsx 查看文件

23
             <i className='fa fa-fw fa-pencil' />
23
             <i className='fa fa-fw fa-pencil' />
24
           </div>
24
           </div>
25
           <div className='subdropdown__item__text'>
25
           <div className='subdropdown__item__text'>
26
-            {props.t('Change')}
26
+            {props.t('Edit')}
27
           </div>
27
           </div>
28
         </div>
28
         </div>
29
 
29
 

+ 1 - 1
frontend/src/component/Workspace/ContentItem.jsx 查看文件

24
       </div>
24
       </div>
25
 
25
 
26
       <div className={classnames('content__status')} style={{color: status.hexcolor}}>
26
       <div className={classnames('content__status')} style={{color: status.hexcolor}}>
27
-        <div className='content__status__text d-none d-xl-block'>
27
+        <div className='content__status__text d-none d-xl-flex align-items-center justify-content-between'>
28
           {status.label}
28
           {status.label}
29
           <i className={`fa fa-fw fa-${status.faIcon}`} />
29
           <i className={`fa fa-fw fa-${status.faIcon}`} />
30
         </div>
30
         </div>

+ 3 - 2
frontend/src/component/common/Input/DropdownCreateButton.jsx 查看文件

2
 import PropTypes from 'prop-types'
2
 import PropTypes from 'prop-types'
3
 import classnames from 'classnames'
3
 import classnames from 'classnames'
4
 import SubDropdownCreateButton from './SubDropdownCreateButton.jsx'
4
 import SubDropdownCreateButton from './SubDropdownCreateButton.jsx'
5
+import { translate } from 'react-i18next'
5
 
6
 
6
 const DropdownCreateButton = props => {
7
 const DropdownCreateButton = props => {
7
   return (
8
   return (
15
         aria-expanded='false'
16
         aria-expanded='false'
16
       >
17
       >
17
         <div className={classnames(`${props.parentClass}__label__text`, 'dropdownCreateBtn__label__text')}>
18
         <div className={classnames(`${props.parentClass}__label__text`, 'dropdownCreateBtn__label__text')}>
18
-          Créer ...
19
+          {props.t('Create ...')}
19
         </div>
20
         </div>
20
       </button>
21
       </button>
21
 
22
 
33
   )
34
   )
34
 }
35
 }
35
 
36
 
36
-export default DropdownCreateButton
37
+export default translate()(DropdownCreateButton)
37
 
38
 
38
 DropdownCreateButton.propTypes = {
39
 DropdownCreateButton.propTypes = {
39
   availableApp: PropTypes.array.isRequired,
40
   availableApp: PropTypes.array.isRequired,

+ 20 - 8
frontend/src/container/AdminWorkspacePage.jsx 查看文件

13
       <PageWrapper customClass='adminWorkspacePage'>
13
       <PageWrapper customClass='adminWorkspacePage'>
14
         <PageTitle
14
         <PageTitle
15
           parentClass={'adminWorkspacePage'}
15
           parentClass={'adminWorkspacePage'}
16
-          title={'Workspace management'}
16
+          title={this.props.t('Workspace management')}
17
         />
17
         />
18
 
18
 
19
         <PageContent parentClass='adminWorkspacePage'>
19
         <PageContent parentClass='adminWorkspacePage'>
20
 
20
 
21
           <div className='adminWorkspacePage__description'>
21
           <div className='adminWorkspacePage__description'>
22
-            This page informs all workspaces of the instances
22
+            {this.props.t('This page list all workspaces')}
23
           </div>
23
           </div>
24
 
24
 
25
           { /*
25
           { /*
39
             <table className='table'>
39
             <table className='table'>
40
               <thead>
40
               <thead>
41
                 <tr>
41
                 <tr>
42
-                  <th scope='col'>ID</th>
43
-                  <th scope='col'>Workspace</th>
44
-                  <th scope='col'>Description</th>
45
-                  <th scope='col'>Member's number</th>
42
+                  <th scope='col'>
43
+                    {this.props.t('ID')}
44
+                  </th>
45
+                  <th scope='col'>
46
+                    {this.props.t('Workspace')}
47
+                  </th>
48
+                  <th scope='col'>
49
+                    {this.props.t('Description')}
50
+                  </th>
51
+                  <th scope='col'>
52
+                    {this.props.t("Member's number")}
53
+                  </th>
46
                   { /*
54
                   { /*
47
-                    <th scope='col'>Calendar</th>
55
+                    <th scope='col'>
56
+                      {this.props.t('Calendar')}
57
+                    </th>
48
                   */ }
58
                   */ }
49
-                  <th scope='col'>Delete workspace</th>
59
+                  <th scope='col'>
60
+                    {this.props.t('Delete workspace')}
61
+                  </th>
50
                 </tr>
62
                 </tr>
51
               </thead>
63
               </thead>
52
               <tbody>
64
               <tbody>

+ 6 - 5
frontend/src/container/Home.jsx 查看文件

3
 import CardHeader from '../component/common/Card/CardHeader.jsx'
3
 import CardHeader from '../component/common/Card/CardHeader.jsx'
4
 import CardBody from '../component/common/Card/CardBody.jsx'
4
 import CardBody from '../component/common/Card/CardBody.jsx'
5
 import LogoHomepage from '../img/logoHeader.svg'
5
 import LogoHomepage from '../img/logoHeader.svg'
6
+import { translate } from 'react-i18next'
6
 
7
 
7
 class Home extends Component {
8
 class Home extends Component {
8
   render () {
9
   render () {
14
             <CardBody customClass='homepagecard'>
15
             <CardBody customClass='homepagecard'>
15
               <div>
16
               <div>
16
                 <div className='homepagecard__title text-center my-4'>
17
                 <div className='homepagecard__title text-center my-4'>
17
-                  Bienvenue sur Tracim
18
+                  {this.props.t('Welcome on Tracim')}
18
                 </div>
19
                 </div>
19
                 <div className='homepagecard__thanks text-center'>
20
                 <div className='homepagecard__thanks text-center'>
20
-                  Merci de nous faire confiance et d'utiliser notre outil collaboratif
21
+                  {this.props.t('Thank you for trusting us and using our collaborative tool')}
21
                 </div>
22
                 </div>
22
                 <div className='homepagecard__delimiter delimiter' />
23
                 <div className='homepagecard__delimiter delimiter' />
23
                 <div className='homepagecard__text text-center mb-5'>
24
                 <div className='homepagecard__text text-center mb-5'>
24
-                  Vous allez créez votre premier espace de travail
25
+                  {this.props.t('You will create your first workspace')}
25
                 </div>
26
                 </div>
26
                 <div className='homepagecard__btn btn btn-outline-primary'>
27
                 <div className='homepagecard__btn btn btn-outline-primary'>
27
-                  Créer votre espace de travail
28
+                  {this.props.t('create a workspace')}
28
                 </div>
29
                 </div>
29
                 <div className='homepagecard__logo mt-5 mb-3'>
30
                 <div className='homepagecard__logo mt-5 mb-3'>
30
                   <img src={LogoHomepage} alt='logo homepage' />
31
                   <img src={LogoHomepage} alt='logo homepage' />
38
   }
39
   }
39
 }
40
 }
40
 
41
 
41
-export default Home
42
+export default translate()(Home)

+ 1 - 1
frontend/src/css/Generic.styl 查看文件

111
 
111
 
112
 
112
 
113
 .pageContentGeneric
113
 .pageContentGeneric
114
-  margin 10px 15px 0 15px
114
+  margin 10px 30px 0 30px
115
   width 100%
115
   width 100%
116
   height 100%
116
   height 100%
117
 
117
 

+ 8 - 1
frontend_app_html-document/i18next.scanner/en/translation.json 查看文件

2
   "Last version": "Last version",
2
   "Last version": "Last version",
3
   "Validate and create": "Validate and create",
3
   "Validate and create": "Validate and create",
4
   "Document's title": "Document's title",
4
   "Document's title": "Document's title",
5
-  "New Document": "New Document"
5
+  "New Document": "New Document",
6
+  "Error while archiving document": "Error while archiving document",
7
+  "Error while deleting document": "Error while deleting document",
8
+  "Error while restoring document": "Error while restoring document",
9
+  "This content is archived.": "This content is archived.",
10
+  "Restore": "Restore",
11
+  "This content is deleted.": "This content is deleted.",
12
+  "latest version :": "latest version :"
6
 }
13
 }

+ 8 - 1
frontend_app_html-document/i18next.scanner/fr/translation.json 查看文件

2
   "Last version": "Dernière version",
2
   "Last version": "Dernière version",
3
   "Validate and create": "Valider et créer",
3
   "Validate and create": "Valider et créer",
4
   "Document's title": "Titre du document",
4
   "Document's title": "Titre du document",
5
-  "New Document": "Nouveau document"
5
+  "New Document": "Nouveau document",
6
+  "Error while archiving document": "Erreur lors de l'archivage d'un document",
7
+  "Error while deleting document": "Erreur lors de la suppression d'un document",
8
+  "Error while restoring document": "Erreur lors de la restauration du document",
9
+  "This content is archived.": "Ce contenu est archivé.",
10
+  "Restore": "Restaurer",
11
+  "This content is deleted.": "Ce contenu est supprimé.",
12
+  "latest version :": "Dernière version"
6
 }
13
 }

+ 7 - 6
frontend_app_html-document/src/component/HtmlDocument.jsx 查看文件

1
 import React from 'react'
1
 import React from 'react'
2
 import { TextAreaApp } from 'tracim_frontend_lib'
2
 import { TextAreaApp } from 'tracim_frontend_lib'
3
 import { MODE } from '../helper.js'
3
 import { MODE } from '../helper.js'
4
+import { translate } from 'react-i18next'
4
 
5
 
5
 const HtmlDocument = props => {
6
 const HtmlDocument = props => {
6
   return (
7
   return (
9
         <div className='html-document__contentpage__textnote__state'>
10
         <div className='html-document__contentpage__textnote__state'>
10
           <div className='html-document__contentpage__textnote__state__msg'>
11
           <div className='html-document__contentpage__textnote__state__msg'>
11
             <i className='fa fa-fw fa-archive' />
12
             <i className='fa fa-fw fa-archive' />
12
-            This content is archived.
13
+            {props.t('This content is archived.')}
13
           </div>
14
           </div>
14
 
15
 
15
           <button className='html-document__contentpage__textnote__state__btnrestore btn' onClick={props.onClickRestoreArchived}>
16
           <button className='html-document__contentpage__textnote__state__btnrestore btn' onClick={props.onClickRestoreArchived}>
16
             <i className='fa fa-fw fa-archive' />
17
             <i className='fa fa-fw fa-archive' />
17
-            Restore
18
+            {props.t('Restore')}
18
           </button>
19
           </button>
19
         </div>
20
         </div>
20
       }
21
       }
23
         <div className='html-document__contentpage__textnote__state'>
24
         <div className='html-document__contentpage__textnote__state'>
24
           <div className='html-document__contentpage__textnote__state__msg'>
25
           <div className='html-document__contentpage__textnote__state__msg'>
25
             <i className='fa fa-fw fa-trash' />
26
             <i className='fa fa-fw fa-trash' />
26
-            Ce contenu est supprimé.
27
+            {props.t('This content is deleted.')}
27
           </div>
28
           </div>
28
 
29
 
29
           <button className='html-document__contentpage__textnote__state__btnrestore btn' onClick={props.onClickRestoreDeleted}>
30
           <button className='html-document__contentpage__textnote__state__btnrestore btn' onClick={props.onClickRestoreDeleted}>
30
             <i className='fa fa-fw fa-trash' />
31
             <i className='fa fa-fw fa-trash' />
31
-            Restore
32
+            {props.t('Restore')}
32
           </button>
33
           </button>
33
         </div>
34
         </div>
34
       }
35
       }
40
             <div dangerouslySetInnerHTML={{__html: props.mode === MODE.VIEW ? props.lastVersion : props.version}} />
41
             <div dangerouslySetInnerHTML={{__html: props.mode === MODE.VIEW ? props.lastVersion : props.version}} />
41
             {props.mode === MODE.REVISION &&
42
             {props.mode === MODE.REVISION &&
42
               <div className='html-document__contentpage__textnote__lastversion'>
43
               <div className='html-document__contentpage__textnote__lastversion'>
43
-                (dernière version : {props.lastVersion})
44
+                ({props.t('latest version :')} {props.lastVersion})
44
               </div>
45
               </div>
45
             }
46
             }
46
           </div>
47
           </div>
64
   )
65
   )
65
 }
66
 }
66
 
67
 
67
-export default HtmlDocument
68
+export default translate()(HtmlDocument)

+ 6 - 1
frontend_lib/i18next.scanner/en/translation.json 查看文件

1
-{}
1
+{
2
+  "Timeline": "Timeline",
3
+  "This content is archived.": "This content is archived.",
4
+  "Restore": "Restore",
5
+  "This content is deleted.": "This content is deleted."
6
+}

+ 6 - 1
frontend_lib/i18next.scanner/fr/translation.json 查看文件

1
-{}
1
+{
2
+  "Timeline": "Historique",
3
+  "This content is archived.": "Ce contenu est archivé.",
4
+  "Restore": "Restaurer",
5
+  "This content is deleted.": "Ce contenu est supprimé."
6
+}

+ 1 - 1
frontend_lib/src/component/Input/BtnSwitch/BtnSwitch.jsx 查看文件

9
       <span className='slider round' />
9
       <span className='slider round' />
10
     </label>
10
     </label>
11
     <div className='btnswitch__text'>
11
     <div className='btnswitch__text'>
12
-      { props.checked ? 'actif' : 'inactif'}
12
+      { props.checked ? 'active' : 'inactive' }
13
     </div>
13
     </div>
14
   </div>
14
   </div>
15
 
15
 

+ 8 - 7
frontend_lib/src/component/Timeline/Timeline.jsx 查看文件

5
 import color from 'color'
5
 import color from 'color'
6
 import Comment from './Comment.jsx'
6
 import Comment from './Comment.jsx'
7
 import Revision from './Revision.jsx'
7
 import Revision from './Revision.jsx'
8
+import { translate } from 'react-i18next'
8
 
9
 
9
 require('./Timeline.styl')
10
 require('./Timeline.styl')
10
 
11
 
38
               <i className={classnames('fa fa-fw', {'fa-angle-double-right': props.rightPartOpen, 'fa-angle-double-left': !props.rightPartOpen})} />
39
               <i className={classnames('fa fa-fw', {'fa-angle-double-right': props.rightPartOpen, 'fa-angle-double-left': !props.rightPartOpen})} />
39
             </div>
40
             </div>
40
             <div className='timeline__header__title'>
41
             <div className='timeline__header__title'>
41
-              Timeline
42
+              {this.props.t('Timeline')}
42
             </div>
43
             </div>
43
             <div className='timeline__header__icon mb-3 mt-auto'>
44
             <div className='timeline__header__icon mb-3 mt-auto'>
44
               <i className={classnames('fa fa-fw', {'fa-angle-double-right': props.rightPartOpen, 'fa-angle-double-left': !props.rightPartOpen})} />
45
               <i className={classnames('fa fa-fw', {'fa-angle-double-right': props.rightPartOpen, 'fa-angle-double-left': !props.rightPartOpen})} />
50
           <div className='timeline__info'>
51
           <div className='timeline__info'>
51
             <div className='timeline__info__msg'>
52
             <div className='timeline__info__msg'>
52
               <i className='fa fa-fw fa-archive' />
53
               <i className='fa fa-fw fa-archive' />
53
-              This content is archived.
54
+              {this.props.t('This content is archived.')}
54
             </div>
55
             </div>
55
 
56
 
56
             <button className='timeline__info__btnrestore btn' onClick={props.onClickRestoreArchived}>
57
             <button className='timeline__info__btnrestore btn' onClick={props.onClickRestoreArchived}>
57
               <i className='fa fa-fw fa-archive' />
58
               <i className='fa fa-fw fa-archive' />
58
-              Restore
59
+              {this.props.t('Restore')}
59
             </button>
60
             </button>
60
           </div>
61
           </div>
61
         }
62
         }
64
           <div className='timeline__info'>
65
           <div className='timeline__info'>
65
             <div className='timeline__info__msg'>
66
             <div className='timeline__info__msg'>
66
               <i className='fa fa-fw fa-trash' />
67
               <i className='fa fa-fw fa-trash' />
67
-              This content is deleted.
68
+              {this.props.t('This content is deleted.')}
68
             </div>
69
             </div>
69
 
70
 
70
             <button className='timeline__info__btnrestore btn' onClick={props.onClickRestoreDeleted}>
71
             <button className='timeline__info__btnrestore btn' onClick={props.onClickRestoreDeleted}>
71
               <i className='fa fa-fw fa-trash' />
72
               <i className='fa fa-fw fa-trash' />
72
-              Restore
73
+              {this.props.t('Restore')}
73
             </button>
74
             </button>
74
           </div>
75
           </div>
75
         }
76
         }
135
                   }}
136
                   }}
136
                   key={'timeline__comment__advancedtext'}
137
                   key={'timeline__comment__advancedtext'}
137
                 >
138
                 >
138
-                  {props.wysiwyg ? 'Texte Simple' : 'Texte Riche'}
139
+                  {props.wysiwyg ? 'Texte simple' : 'Texte riche'}
139
                 </button>
140
                 </button>
140
               </div>
141
               </div>
141
 
142
 
169
   }
170
   }
170
 }
171
 }
171
 
172
 
172
-export default Radium(Timeline)
173
+export default Radium(translate()(Timeline))
173
 
174
 
174
 Timeline.propTypes = {
175
 Timeline.propTypes = {
175
   timelineData: PropTypes.array.isRequired,
176
   timelineData: PropTypes.array.isRequired,

+ 1 - 1
frontend_lib/src/component/Timeline/Timeline.styl 查看文件

50
       overflow-y auto
50
       overflow-y auto
51
       list-style none
51
       list-style none
52
       &__item
52
       &__item
53
-        padding 0 25px 0 35px
53
+        padding 0 25px 15px 35px
54
         &__avatar
54
         &__avatar
55
           position relative
55
           position relative
56
           top 60px
56
           top 60px

+ 6 - 0
functionnal_tests/cypress.json.sample 查看文件

1
+{
2
+  "baseUrl": "http://localhost:6543",
3
+  "viewportWidth": 1280,
4
+  "viewportHeight": 1024,
5
+  "integrationFolder": "{path_test_file}"
6
+}

functionnal_tests/account/account_main_page.js → functionnal_tests/cypress_test/account/account_main_page.js 查看文件


functionnal_tests/account/content_account.js → functionnal_tests/cypress_test/account/content_account.js 查看文件


functionnal_tests/admin/content_admin_user.js → functionnal_tests/cypress_test/admin/content_admin_user.js 查看文件


functionnal_tests/admin/content_admin_workspace.js → functionnal_tests/cypress_test/admin/content_admin_workspace.js 查看文件


functionnal_tests/admin/navigation_admin_user.js → functionnal_tests/cypress_test/admin/navigation_admin_user.js 查看文件


functionnal_tests/admin/navigation_admin_workspace.js → functionnal_tests/cypress_test/admin/navigation_admin_workspace.js 查看文件


functionnal_tests/app_file/navigation_create_file.js → functionnal_tests/cypress_test/app_file/navigation_create_file.js 查看文件


functionnal_tests/app_folder/navigation_create_folder.js → functionnal_tests/cypress_test/app_folder/navigation_create_folder.js 查看文件


functionnal_tests/app_html-document/navigation_create_html-document.js → functionnal_tests/cypress_test/app_html-document/navigation_create_html-document.js 查看文件


functionnal_tests/app_html-document/operation_create_html-document.js → functionnal_tests/cypress_test/app_html-document/operation_create_html-document.js 查看文件

33
     it ('all content > header button ', function () {
33
     it ('all content > header button ', function () {
34
         var titre2='all content button'
34
         var titre2='all content button'
35
         cy.url().should('include', '/workspaces/1/contents')
35
         cy.url().should('include', '/workspaces/1/contents')
36
-        cy.get('.workspace__header__btnaddcontent').should('be.visible')
37
-        cy.get('.workspace__header__btnaddcontent').click()
36
+        cy.get('.workspace__header__btnaddcontent__label').should('be.visible')
37
+        cy.get('.workspace__header__btnaddcontent__label').click()
38
         cy.get('.workspace__header__btnaddcontent__setting div:nth-child(4).subdropdown__link').click()
38
         cy.get('.workspace__header__btnaddcontent__setting div:nth-child(4).subdropdown__link').click()
39
         cy.get('.createcontent .createcontent__contentname').should('be.visible')
39
         cy.get('.createcontent .createcontent__contentname').should('be.visible')
40
         cy.get('.createcontent .createcontent__form__input').should('have.attr', 'placeholder')
40
         cy.get('.createcontent .createcontent__form__input').should('have.attr', 'placeholder')

functionnal_tests/app_thread/navigation_create_thread.js → functionnal_tests/cypress_test/app_thread/navigation_create_thread.js 查看文件


functionnal_tests/app_thread/operation_create_thread.js → functionnal_tests/cypress_test/app_thread/operation_create_thread.js 查看文件


functionnal_tests/home_page/content_home_page.js → functionnal_tests/cypress_test/home_page/content_home_page.js 查看文件


functionnal_tests/interface/function_change_color.js → functionnal_tests/cypress_test/interface/function_change_color.js 查看文件


functionnal_tests/login/content_login_page.js → functionnal_tests/cypress_test/login/content_login_page.js 查看文件


functionnal_tests/login/navigation_from_home_page_to_login_page.js → functionnal_tests/cypress_test/login/navigation_from_home_page_to_login_page.js 查看文件


functionnal_tests/login/navigation_from_login_page_to_home_page.js → functionnal_tests/cypress_test/login/navigation_from_login_page_to_home_page.js 查看文件


functionnal_tests/login/navigation_redirect_login_page.js → functionnal_tests/cypress_test/login/navigation_redirect_login_page.js 查看文件


functionnal_tests/workspace/content_workspace_dashboard.js → functionnal_tests/cypress_test/workspace/content_workspace_dashboard.js 查看文件


functionnal_tests/workspace/navigation_create_workspace.js → functionnal_tests/cypress_test/workspace/navigation_create_workspace.js 查看文件


functionnal_tests/workspace/navigation_dashbord_link-for-calendar.js → functionnal_tests/cypress_test/workspace/navigation_dashbord_link-for-calendar.js 查看文件


functionnal_tests/workspace/navigation_dashbord_link-for-webdav.js → functionnal_tests/cypress_test/workspace/navigation_dashbord_link-for-webdav.js 查看文件


部分文件因文件數量過多而無法顯示