Browse Source

Merge branch 'develop' into feature/734_add_lang_to_user

inkhey 6 years ago
parent
commit
3cc9bd525a
No account linked to committer's email
100 changed files with 1048 additions and 578 deletions
  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 View File

@@ -6,3 +6,11 @@ frontend_app_html-document/dist/html-document.app.js
6 6
 frontend_lib/dist/tracim_frontend_lib.js
7 7
 npm-debug.log
8 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 View File

@@ -3,6 +3,7 @@ develop branch status:
3 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 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 7
 ## Install Tracim on your server ##
7 8
 
8 9
 Following the installation documentation below, you'll be able to run your own instance on your server.
@@ -13,30 +14,40 @@ Following the installation documentation below, you'll be able to run your own i
13 14
 
14 15
 ## Get the source ##
15 16
 
16
-Get the sources from GitHub:
17
+Get the sources from GitHub (you need git):
17 18
 
18 19
     git clone https://github.com/tracim/tracim_v2.git
19 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 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 47
     ./install_frontend_dependencies.sh
37 48
     ./build_full_frontend.sh
38 49
 
39
-## Running Tracim  ##
50
+## Running Tracim using pserve ##
40 51
 
41 52
     cd backend/
42 53
     source env/bin/activate
@@ -45,7 +56,33 @@ If you use debugtoolbar, you can seen one red button on right of the Tracim web
45 56
 You can now enter the application at
46 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 View File

@@ -85,8 +85,11 @@ create wsgidav configuration file for webdav:
85 85
 
86 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 94
 #### Install Uwsgi
92 95
 

+ 6 - 0
backend/color-test.json View File

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

+ 9 - 0
backend/development.ini.sample View File

@@ -35,6 +35,8 @@ retry.attempts = 3
35 35
 
36 36
 ### Global
37 37
 
38
+# Enable debug mode
39
+# debug = True
38 40
 cache_dir = %(here)s/data
39 41
 # preview generator cache directory
40 42
 preview_cache_dir = /tmp/tracim/preview/
@@ -193,6 +195,13 @@ frontend.serve = True
193 195
 # organisation.
194 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 206
 # wsgi server configuration
198 207
 ###

+ 76 - 8
backend/doc/setting.md View File

@@ -1,8 +1,13 @@
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 12
 Default configuration is to listen on port 6534.
8 13
 If you want to adapt this to your environment, edit the `.ini` file and setup the port you want:
@@ -17,30 +22,93 @@ To allow other computer to access to this website, listen to "*" instead of loca
17 22
     ...
18 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 70
 To enable simple debug conf:
24 71
 
25
-    [app:main]
72
+    [app:tracim_web]
26 73
     ...
27 74
     pyramid.reload_templates = true
28 75
     pyramid.debug_all = true
29 76
     pyramid.includes =
30 77
         pyramid_debugtoolbar
31 78
 
79
+    [DEFAULT]
80
+    ...
81
+    debug = True
82
+
83
+
32 84
 production conf (no reload, no debugtoolbar):
33 85
 
34
-    [app:main]
86
+    [app:tracim_web]
35 87
     ...
36 88
     pyramid.reload_templates = false
37 89
     pyramid.debug_authorization = false
38 90
     pyramid.debug_notfound = false
39 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 100
     [logger_sqlalchemy]
45 101
     ...
46 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 View File

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

+ 19 - 0
backend/tests_configs.ini View File

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

@@ -0,0 +1,76 @@
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 View File

@@ -2,17 +2,15 @@
2 2
 import typing
3 3
 from enum import Enum
4 4
 
5
+from tracim_backend.extensions import app_list
5 6
 from tracim_backend.exceptions import ContentTypeNotExist
6 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 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 16
 class GlobalStatus(Enum):
@@ -134,60 +132,17 @@ class ContentType(object):
134 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 141
 # TODO - G.M - 31-05-2018 - Set Better Event params
187 142
 event_type = ContentType(
188 143
     slug='event',
189
-    fa_icon=thread.fa_icon,
190
-    hexcolor=thread.hexcolor,
144
+    fa_icon='',
145
+    hexcolor='',
191 146
     label='Event',
192 147
     creation_label='Event',
193 148
     available_statuses=CONTENT_STATUS.get_all(),
@@ -196,8 +151,8 @@ event_type = ContentType(
196 151
 # TODO - G.M - 31-05-2018 - Set Better Event params
197 152
 comment_type = ContentType(
198 153
     slug='comment',
199
-    fa_icon=thread.fa_icon,
200
-    hexcolor=thread.hexcolor,
154
+    fa_icon='',
155
+    hexcolor='',
201 156
     label='Comment',
202 157
     creation_label='Comment',
203 158
     available_statuses=CONTENT_STATUS.get_all(),
@@ -209,19 +164,36 @@ class ContentTypeList(object):
209 164
     ContentType List
210 165
     """
211 166
     Any_SLUG = 'any'
212
-    Folder = folder_type
213 167
     Comment = comment_type
214 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 188
         self._special_contents_types = [self.Comment]
223 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 197
     def get_one_by_slug(self, slug: str) -> ContentType:
226 198
         """
227 199
         Get ContentType object according to slug
@@ -235,21 +207,24 @@ class ContentTypeList(object):
235 207
                 return item
236 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 213
         "any" slug, dont return content type slug alias , don't return event.
243 214
         Useful to restrict slug param in schema.
244 215
         """
245 216
         allowed_type_slug = [contents_type.slug for contents_type in self._content_types]  # nopep8
246 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 229
     def query_allowed_types_slugs(self) -> typing.List[str]:
255 230
         """
@@ -258,19 +233,19 @@ class ContentTypeList(object):
258 233
         Usefull allowed value to perform query to database.
259 234
         """
260 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 239
             allowed_types_slug.append(content_type.slug)
263 240
             if content_type.slug_alias:
264 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 242
         allowed_types_slug.extend(self._extra_slugs)
268 243
         return allowed_types_slug
269 244
 
270 245
     def default_allowed_content_properties(self, slug) -> dict:
271 246
         content_type = self.get_one_by_slug(slug)
272 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 249
         else:
275 250
             sub_content_allowed = [self.Comment.slug]
276 251
 
@@ -280,12 +255,4 @@ class ContentTypeList(object):
280 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 View File

@@ -0,0 +1,11 @@
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 View File

@@ -0,0 +1,33 @@
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 View File

@@ -1,15 +1,19 @@
1 1
 # -*- coding: utf-8 -*-
2
+import json
2 3
 from urllib.parse import urlparse
3 4
 
4 5
 import os
5 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 9
 from tracim_backend.lib.utils.logger import logger
7 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 14
 from tracim_backend.models.data import ActionDescription
10 15
 
11 16
 
12
-
13 17
 class CFG(object):
14 18
     """Object used for easy access to config file parameters."""
15 19
 
@@ -41,7 +45,36 @@ class CFG(object):
41 45
         ###
42 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 78
         mandatory_msg = \
46 79
             'ERROR: {} configuration is mandatory. Set it before continuing.'
47 80
         self.DEPOT_STORAGE_DIR = settings.get(
@@ -450,8 +483,6 @@ class CFG(object):
450 483
         # INFO - G.M - 2018-08-06 - we pretend that frontend_dist_folder
451 484
         # is probably in frontend subfolder
452 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 486
         frontend_dist_folder = os.path.join(tracim_v2_folder, 'frontend', 'dist')  # nopep8
456 487
 
457 488
         self.FRONTEND_DIST_FOLDER_PATH = settings.get(
@@ -467,6 +498,10 @@ class CFG(object):
467 498
             )
468 499
 
469 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 505
         depot_storage_name = self.DEPOT_STORAGE_NAME
471 506
         depot_storage_path = self.DEPOT_STORAGE_DIR
472 507
         depot_storage_settings = {'depot.storage_path': depot_storage_path}
@@ -475,6 +510,116 @@ class CFG(object):
475 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 623
     class CST(object):
479 624
         ASYNC = 'ASYNC'
480 625
         SYNC = 'SYNC'

+ 6 - 0
backend/tracim_backend/exceptions.py View File

@@ -204,9 +204,11 @@ class PageOfPreviewNotFound(NotFound):
204 204
 class PreviewDimNotAllowed(TracimException):
205 205
     pass
206 206
 
207
+
207 208
 class UnallowedSubContent(TracimException):
208 209
     pass
209 210
 
211
+
210 212
 class TooShortAutocompleteString(TracimException):
211 213
     pass
212 214
 
@@ -215,5 +217,9 @@ class PageNotFound(TracimException):
215 217
     pass
216 218
 
217 219
 
220
+class AppDoesNotExist(TracimException):
221
+    pass
222
+
223
+
218 224
 class EmailAlreadyExistInDb(TracimException):
219 225
     pass

+ 11 - 0
backend/tracim_backend/extensions.py View File

@@ -1,3 +1,14 @@
1 1
 from hapic import Hapic
2 2
 
3 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 View File

@@ -8,7 +8,7 @@ from tracim_backend.fixtures.users_and_groups import Test
8 8
 from tracim_backend.lib.core.content import ContentApi
9 9
 from tracim_backend.lib.core.userworkspace import RoleApi
10 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 12
 from tracim_backend.models.data import UserRoleInWorkspace
13 13
 from tracim_backend.models.revision_protection import new_revision
14 14
 

+ 70 - 0
backend/tracim_backend/lib/core/application.py View File

@@ -0,0 +1,70 @@
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 View File

@@ -36,9 +36,9 @@ from tracim_backend.exceptions import EmptyLabelNotAllowed
36 36
 from tracim_backend.exceptions import ContentNotFound
37 37
 from tracim_backend.exceptions import WorkspacesDoNotMatch
38 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 42
 from tracim_backend.models.revision_protection import new_revision
43 43
 from tracim_backend.models.auth import User
44 44
 from tracim_backend.models.data import ActionDescription
@@ -1137,7 +1137,7 @@ class ContentApi(object):
1137 1137
         """
1138 1138
         allowed_content_dict = {}
1139 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 1141
                 raise ContentTypeNotExist('Content_type {} does not exist'.format(allowed_content_type_slug))  # nopep8
1142 1142
             allowed_content_dict[allowed_content_type_slug] = True
1143 1143
 

+ 1 - 1
backend/tracim_backend/lib/mail_notifier/notifier.py View File

@@ -20,7 +20,7 @@ from tracim_backend.lib.utils.logger import logger
20 20
 from tracim_backend.lib.utils.utils import get_login_frontend_url
21 21
 from tracim_backend.lib.utils.utils import get_email_logo_frontend_url
22 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 24
 from tracim_backend.models.context_models import ContentInContext
25 25
 from tracim_backend.models.context_models import WorkspaceInContext
26 26
 from tracim_backend.models.data import ActionDescription

+ 1 - 1
backend/tracim_backend/lib/utils/authentification.py View File

@@ -3,7 +3,7 @@ import typing
3 3
 from pyramid.request import Request
4 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 7
 from tracim_backend.exceptions import UserDoesNotExist
8 8
 from tracim_backend.lib.core.user import UserApi
9 9
 from tracim_backend.models import User

+ 5 - 7
backend/tracim_backend/lib/utils/authorization.py View File

@@ -5,15 +5,14 @@ import functools
5 5
 from pyramid.interfaces import IAuthorizationPolicy
6 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 11
 try:
12 12
     from json.decoder import JSONDecodeError
13 13
 except ImportError:  # python3.4
14 14
     JSONDecodeError = ValueError
15 15
 
16
-from tracim_backend.models.contents import ContentType
17 16
 from tracim_backend.exceptions import InsufficientUserRoleInWorkspace
18 17
 from tracim_backend.exceptions import ContentTypeNotAllowed
19 18
 from tracim_backend.exceptions import InsufficientUserProfile
@@ -167,19 +166,18 @@ def require_candidate_workspace_role(minimal_required_role: int) -> typing.Calla
167 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 171
     Restricts access to specific file type or raise an exception.
173 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 174
     :return: decorator
176 175
     """
177 176
     def decorator(func: typing.Callable) -> typing.Callable:
178 177
         @functools.wraps(func)
179 178
         def wrapper(self, context, request: 'TracimRequest') -> typing.Callable:
180 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 181
             if current_content_type_slug in content_types_slug:
184 182
                 return func(self, context, request)
185 183
             raise ContentTypeNotAllowed()

+ 1 - 1
backend/tracim_backend/lib/utils/request.py View File

@@ -15,7 +15,7 @@ from tracim_backend.exceptions import UserNotFoundInTracimRequest
15 15
 from tracim_backend.exceptions import UserDoesNotExist
16 16
 from tracim_backend.exceptions import WorkspaceNotFound
17 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 19
 from tracim_backend.lib.core.content import ContentApi
20 20
 from tracim_backend.lib.core.user import UserApi
21 21
 from tracim_backend.lib.core.workspace import WorkspaceApi

+ 67 - 6
backend/tracim_backend/lib/utils/utils.py View File

@@ -3,11 +3,13 @@ import datetime
3 3
 import random
4 4
 import string
5 5
 from enum import Enum
6
+import colorsys
6 7
 
7 8
 from redis import Redis
8 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 14
 DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
13 15
 DEFAULT_WEBDAV_CONFIG_FILE = "wsgidav.conf"
@@ -16,7 +18,7 @@ CONTENT_FRONTEND_URL_SCHEMA = 'workspaces/{workspace_id}/contents/{content_type}
16 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 23
     Return website base url with always '/' at the end
22 24
     """
@@ -28,19 +30,19 @@ def get_root_frontend_url(config: CFG) -> str:
28 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 35
     Return login page url
34 36
     """
35 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 41
     # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for email_logo_frontend_url  # nopep8
40 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 47
     :param config: current app_config
46 48
     :return: redis connection
@@ -125,3 +127,62 @@ def password_generator(
125 127
     """
126 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 View File

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

+ 1 - 1
backend/tracim_backend/lib/webdav/design.py View File

@@ -1,7 +1,7 @@
1 1
 #coding: utf8
2 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 5
 from tracim_backend.models.data import VirtualEvent
6 6
 from tracim_backend.models import data
7 7
 

+ 1 - 1
backend/tracim_backend/lib/webdav/resources.py View File

@@ -19,7 +19,7 @@ from tracim_backend.lib.webdav.utils import transform_to_display, HistoryType, \
19 19
     FakeFileStream
20 20
 from tracim_backend.lib.webdav.utils import transform_to_bdd
21 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 23
 from tracim_backend.models.data import User, ContentRevisionRO
24 24
 from tracim_backend.models.data import Workspace
25 25
 from tracim_backend.models.data import Content

+ 1 - 1
backend/tracim_backend/lib/webdav/utils.py View File

@@ -4,7 +4,7 @@ import transaction
4 4
 from os.path import normpath as base_normpath
5 5
 
6 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 8
 from wsgidav import util
9 9
 from wsgidav import compat
10 10
 

+ 1 - 1
backend/tracim_backend/models/__init__.py View File

@@ -5,7 +5,7 @@ from sqlalchemy.orm import sessionmaker
5 5
 from sqlalchemy.orm import configure_mappers
6 6
 import zope.sqlalchemy
7 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 9
 # import or define all models here to ensure they are attached to the
10 10
 # Base.metadata prior to any initialization routines
11 11
 from tracim_backend.models.auth import User, Group, Permission

+ 0 - 117
backend/tracim_backend/models/applications.py View File

@@ -1,117 +0,0 @@
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 View File

@@ -7,6 +7,8 @@ from slugify import slugify
7 7
 from sqlalchemy.orm import Session
8 8
 from tracim_backend.config import CFG
9 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 12
 from tracim_backend.lib.utils.utils import get_root_frontend_url
11 13
 from tracim_backend.lib.utils.utils import password_generator
12 14
 from tracim_backend.lib.utils.utils import CONTENT_FRONTEND_URL_SCHEMA
@@ -19,9 +21,8 @@ from tracim_backend.models.data import ContentRevisionRO
19 21
 from tracim_backend.models.data import Workspace
20 22
 from tracim_backend.models.data import UserRoleInWorkspace
21 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 28
 class PreviewAllowedDim(object):
@@ -500,7 +501,10 @@ class WorkspaceInContext(object):
500 501
         # order to not use hardcoded list
501 502
         # list should be able to change (depending on activated/disabled
502 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 509
     @property
506 510
     def frontend_url(self):

+ 5 - 5
backend/tracim_backend/models/data.py View File

@@ -87,7 +87,7 @@ class Workspace(DeclarativeBase):
87 87
 
88 88
     def get_allowed_content_types(self):
89 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 92
     def get_valid_children(
93 93
             self,
@@ -282,9 +282,9 @@ class ActionDescription(object):
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 288
 # TODO - G.M - 30-05-2018 - Drop this old code when whe are sure nothing
289 289
 # is lost .
290 290
 
@@ -552,7 +552,7 @@ class ContentChecker(object):
552 552
                 for content_slug, value in properties['allowed_content'].items():  # nopep8
553 553
                     if not isinstance(value, bool):
554 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 556
                         return False
557 557
             if 'origin' in properties.keys():
558 558
                 pass

+ 0 - 71
backend/tracim_backend/models/workspace_menu_entries.py View File

@@ -1,71 +0,0 @@
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 View File

@@ -14,7 +14,7 @@ from tracim_backend.models import get_engine
14 14
 from tracim_backend.models import DeclarativeBase
15 15
 from tracim_backend.models import get_session_factory
16 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 18
 from tracim_backend.models.data import Workspace
19 19
 from tracim_backend.models.data import ContentRevisionRO
20 20
 from tracim_backend.models.data import Content

+ 1 - 1
backend/tracim_backend/tests/functional/test_contents.py View File

@@ -5,7 +5,7 @@ from tracim_backend import models
5 5
 from tracim_backend.lib.core.content import ContentApi
6 6
 from tracim_backend.lib.core.workspace import WorkspaceApi
7 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 9
 from tracim_backend.models.revision_protection import new_revision
10 10
 import io
11 11
 

+ 1 - 1
backend/tracim_backend/tests/functional/test_mail_notification.py View File

@@ -11,7 +11,7 @@ from tracim_backend.fixtures.users_and_groups import Base as BaseFixture
11 11
 from tracim_backend.fixtures.content import Content as ContentFixture
12 12
 from tracim_backend.lib.utils.utils import get_redis_connection
13 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 16
 from tracim_backend.lib.core.content import ContentApi
17 17
 from tracim_backend.lib.core.user import UserApi

+ 10 - 2
backend/tracim_backend/tests/functional/test_system.py View File

@@ -1,7 +1,10 @@
1 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 7
 from tracim_backend.tests import FunctionalTest
4
-from tracim_backend.models.applications import applications
5 8
 
6 9
 """
7 10
 Tests for /api/v2/system subpath endpoints.
@@ -26,6 +29,11 @@ class TestApplicationEndpoint(FunctionalTest):
26 29
         )
27 30
         res = self.testapp.get('/api/v2/system/applications', status=200)
28 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 37
         assert len(res) == len(applications)
30 38
         for counter, application in enumerate(applications):
31 39
             assert res[counter]['label'] == application.label

+ 27 - 39
backend/tracim_backend/tests/functional/test_user.py View File

@@ -8,13 +8,15 @@ import requests
8 8
 import transaction
9 9
 
10 10
 from tracim_backend import models
11
+from tracim_backend.extensions import app_list
12
+from tracim_backend.lib.core.application import ApplicationApi
11 13
 from tracim_backend.lib.core.content import ContentApi
12 14
 from tracim_backend.lib.core.user import UserApi
13 15
 from tracim_backend.lib.core.group import GroupApi
14 16
 from tracim_backend.lib.core.userworkspace import RoleApi
15 17
 from tracim_backend.lib.core.workspace import WorkspaceApi
16 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 20
 from tracim_backend.models.data import UserRoleInWorkspace
19 21
 from tracim_backend.models.revision_protection import new_revision
20 22
 from tracim_backend.tests import FunctionalTest
@@ -2390,6 +2392,22 @@ class TestUserWorkspaceEndpoint(FunctionalTest):
2390 2392
         """
2391 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 2411
         self.testapp.authorization = (
2394 2412
             'Basic',
2395 2413
             (
@@ -2404,44 +2422,14 @@ class TestUserWorkspaceEndpoint(FunctionalTest):
2404 2422
         assert workspace['label'] == 'Business'
2405 2423
         assert workspace['slug'] == 'business'
2406 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 2434
     def test_api__get_user_workspaces__err_403__unallowed_user(self):
2447 2435
         """

+ 46 - 42
backend/tracim_backend/tests/functional/test_workspaces.py View File

@@ -7,13 +7,15 @@ import transaction
7 7
 from depot.io.utils import FileIntent
8 8
 
9 9
 from tracim_backend import models
10
+from tracim_backend.extensions import app_list
11
+from tracim_backend.lib.core.application import ApplicationApi
10 12
 from tracim_backend.lib.core.content import ContentApi
11 13
 from tracim_backend.lib.core.group import GroupApi
12 14
 from tracim_backend.lib.core.user import UserApi
13 15
 from tracim_backend.lib.core.userworkspace import RoleApi
14 16
 from tracim_backend.lib.core.workspace import WorkspaceApi
15 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 19
 from tracim_backend.models.data import UserRoleInWorkspace
18 20
 from tracim_backend.tests import FunctionalTest
19 21
 from tracim_backend.tests import set_html_document_slug_to_legacy
@@ -32,6 +34,22 @@ class TestWorkspaceEndpoint(FunctionalTest):
32 34
         """
33 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 53
         self.testapp.authorization = (
36 54
             'Basic',
37 55
             (
@@ -46,49 +64,35 @@ class TestWorkspaceEndpoint(FunctionalTest):
46 64
         assert workspace['label'] == 'Business'
47 65
         assert workspace['description'] == 'All importants documents'
48 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 76
     def test_api__update_workspace__ok_200__nominal_case(self) -> None:
89 77
         """
90 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 96
         self.testapp.authorization = (
93 97
             'Basic',
94 98
             (
@@ -111,8 +115,8 @@ class TestWorkspaceEndpoint(FunctionalTest):
111 115
         assert workspace['slug'] == 'business'
112 116
         assert workspace['label'] == 'Business'
113 117
         assert workspace['description'] == 'All importants documents'
118
+        assert len(workspace['sidebar_entries']) == len(default_sidebar_entry)
114 119
         assert workspace['is_deleted'] is False
115
-        assert len(workspace['sidebar_entries']) == 5
116 120
 
117 121
         # modify workspace
118 122
         res = self.testapp.put_json(
@@ -126,8 +130,8 @@ class TestWorkspaceEndpoint(FunctionalTest):
126 130
         assert workspace['slug'] == 'superworkspace'
127 131
         assert workspace['label'] == 'superworkspace'
128 132
         assert workspace['description'] == 'mysuperdescription'
133
+        assert len(workspace['sidebar_entries']) == len(default_sidebar_entry)
129 134
         assert workspace['is_deleted'] is False
130
-        assert len(workspace['sidebar_entries']) == 5
131 135
 
132 136
         # after
133 137
         res = self.testapp.get(
@@ -140,8 +144,8 @@ class TestWorkspaceEndpoint(FunctionalTest):
140 144
         assert workspace['slug'] == 'superworkspace'
141 145
         assert workspace['label'] == 'superworkspace'
142 146
         assert workspace['description'] == 'mysuperdescription'
147
+        assert len(workspace['sidebar_entries']) == len(default_sidebar_entry)
143 148
         assert workspace['is_deleted'] is False
144
-        assert len(workspace['sidebar_entries']) == 5
145 149
 
146 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 View File

@@ -16,7 +16,7 @@ from tracim_backend.exceptions import UnallowedSubContent
16 16
 from tracim_backend.lib.core.workspace import RoleApi
17 17
 # TODO - G.M - 28-03-2018 - [WorkspaceApi] Re-enable WorkspaceApi
18 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 20
 from tracim_backend.models.revision_protection import new_revision
21 21
 from tracim_backend.models.auth import User
22 22
 from tracim_backend.models.auth import Group

+ 3 - 10
backend/tracim_backend/tests/library/test_webdav.py View File

@@ -22,22 +22,15 @@ from unittest.mock import MagicMock
22 22
 
23 23
 class TestWebdavFactory(StandardTest):
24 24
 
25
+    config_section = 'webdav_test'
26
+
25 27
     def test_unit__initConfig__ok__nominal_case(self):
26 28
         """
27 29
         Check if config is correctly modify for wsgidav using mocked
28 30
         wsgidav and tracim conf (as dict)
29 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 34
         wsgidav_setting = DEFAULT_CONFIG.copy()
42 35
         wsgidav_setting.update(
43 36
             {

+ 1 - 1
backend/tracim_backend/tests/models/test_content.py View File

@@ -15,7 +15,7 @@ from tracim_backend.models.revision_protection import new_revision
15 15
 from tracim_backend.models import User
16 16
 from tracim_backend.models.data import ActionDescription
17 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 19
 from tracim_backend.models.data import Workspace
20 20
 from tracim_backend.tests import StandardTest
21 21
 

+ 1 - 1
backend/tracim_backend/tests/models/test_content_revision.py View File

@@ -5,7 +5,7 @@ from sqlalchemy import inspect
5 5
 
6 6
 from tracim_backend.models import ContentRevisionRO
7 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 9
 from tracim_backend.tests import DefaultTest
10 10
 from tracim_backend.tests import eq_
11 11
 

+ 2 - 2
backend/tracim_backend/views/contents_api/comment_controller.py View File

@@ -7,7 +7,7 @@ try:  # Python 3.5+
7 7
 except ImportError:
8 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 11
 from tracim_backend.extensions import hapic
12 12
 from tracim_backend.lib.core.content import ContentApi
13 13
 from tracim_backend.lib.core.workspace import WorkspaceApi
@@ -20,7 +20,7 @@ from tracim_backend.views.core_api.schemas import SetCommentSchema
20 20
 from tracim_backend.views.core_api.schemas import WorkspaceAndContentIdPathSchema
21 21
 from tracim_backend.views.core_api.schemas import NoContentSchema
22 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 24
 from tracim_backend.models.revision_protection import new_revision
25 25
 from tracim_backend.models.data import UserRoleInWorkspace
26 26
 

+ 17 - 17
backend/tracim_backend/views/contents_api/file_controller.py View File

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

+ 6 - 6
backend/tracim_backend/views/contents_api/folder_controller.py View File

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

+ 7 - 7
backend/tracim_backend/views/contents_api/html_document_controller.py View File

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

+ 7 - 7
backend/tracim_backend/views/contents_api/threads_controller.py View File

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

+ 12 - 10
backend/tracim_backend/views/core_api/schemas.py View File

@@ -7,11 +7,12 @@ from marshmallow.validate import Range
7 7
 
8 8
 from tracim_backend.lib.utils.utils import DATETIME_FORMAT
9 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 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 16
 from tracim_backend.models.context_models import ActiveContentFilter
16 17
 from tracim_backend.models.context_models import FolderContentUpdate
17 18
 from tracim_backend.models.context_models import AutocompleteQuery
@@ -41,6 +42,7 @@ from tracim_backend.models.context_models import ContentFilter
41 42
 from tracim_backend.models.context_models import LoginCredentials
42 43
 from tracim_backend.models.data import UserRoleInWorkspace
43 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 48
 class UserDigestSchema(marshmallow.Schema):
@@ -417,7 +419,7 @@ class FilterContentQuerySchema(marshmallow.Schema):
417 419
     content_type = marshmallow.fields.String(
418 420
         example=CONTENT_TYPES.Any_SLUG,
419 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 425
     @post_load
@@ -668,7 +670,7 @@ class StatusSchema(marshmallow.Schema):
668 670
 class ContentTypeSchema(marshmallow.Schema):
669 671
     slug = marshmallow.fields.String(
670 672
         example='pagehtml',
671
-        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),
673
+        validate=all_content_types_validator,
672 674
     )
673 675
     fa_icon = marshmallow.fields.String(
674 676
         example='fa-file-text-o',
@@ -722,7 +724,7 @@ class ContentCreationSchema(marshmallow.Schema):
722 724
     )
723 725
     content_type = marshmallow.fields.String(
724 726
         example='html-document',
725
-        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),
727
+        validate=all_content_types_validator,
726 728
     )
727 729
     parent_id = marshmallow.fields.Integer(
728 730
         example=35,
@@ -757,12 +759,12 @@ class ContentDigestSchema(marshmallow.Schema):
757 759
     label = marshmallow.fields.Str(example='Intervention Report 12')
758 760
     content_type = marshmallow.fields.Str(
759 761
         example='html-document',
760
-        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),
762
+        validate=all_content_types_validator,
761 763
     )
762 764
     sub_content_types = marshmallow.fields.List(
763 765
         marshmallow.fields.String(
764 766
             example='html-content',
765
-            validate=OneOf(CONTENT_TYPES.extended_endpoint_allowed_types_slug())
767
+            validate=all_content_types_validator
766 768
         ),
767 769
         description='list of content types allowed as sub contents. '
768 770
                     'This field is required for folder contents, '
@@ -912,7 +914,7 @@ class FolderContentModifySchema(ContentModifyAbstractSchema, TextBasedDataAbstra
912 914
     sub_content_types = marshmallow.fields.List(
913 915
         marshmallow.fields.String(
914 916
             example='html-document',
915
-            validate=OneOf(CONTENT_TYPES.extended_endpoint_allowed_types_slug())
917
+            validate=all_content_types_validator,
916 918
         ),
917 919
         description='list of content types allowed as sub contents. '
918 920
                     'This field is required for folder contents, '

+ 1 - 1
backend/tracim_backend/views/core_api/session_controller.py View File

@@ -5,7 +5,7 @@ try:  # Python 3.5+
5 5
 except ImportError:
6 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 9
 from tracim_backend.extensions import hapic
10 10
 from tracim_backend.lib.core.user import UserApi
11 11
 from tracim_backend.views.controllers import Controller

+ 9 - 4
backend/tracim_backend/views/core_api/system_controller.py View File

@@ -2,18 +2,19 @@
2 2
 from pyramid.config import Configurator
3 3
 from tracim_backend.exceptions import NotAuthenticated
4 4
 from tracim_backend.exceptions import InsufficientUserProfile
5
+from tracim_backend.lib.core.application import ApplicationApi
5 6
 from tracim_backend.lib.utils.authorization import require_profile
6 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 10
 try:  # Python 3.5+
11 11
     from http import HTTPStatus
12 12
 except ImportError:
13 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 16
 from tracim_backend.extensions import hapic
17
+from tracim_backend.extensions import app_list
17 18
 from tracim_backend.views.controllers import Controller
18 19
 from tracim_backend.views.core_api.schemas import ApplicationSchema
19 20
 from tracim_backend.views.core_api.schemas import ContentTypeSchema
@@ -30,7 +31,11 @@ class SystemController(Controller):
30 31
         """
31 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 40
     @hapic.with_api_doc(tags=[SWAGGER_TAG_SYSTEM_ENDPOINTS])
36 41
     @require_profile(Group.TIM_USER)

+ 2 - 2
backend/tracim_backend/views/core_api/user_controller.py View File

@@ -7,7 +7,7 @@ except ImportError:
7 7
     from http import client as HTTPStatus
8 8
 
9 9
 from tracim_backend import hapic
10
-from tracim_backend import TracimRequest
10
+from tracim_backend.lib.utils.request import TracimRequest
11 11
 from tracim_backend.models import Group
12 12
 from tracim_backend.lib.core.group import GroupApi
13 13
 from tracim_backend.lib.core.user import UserApi
@@ -36,7 +36,7 @@ from tracim_backend.views.core_api.schemas import UserWorkspaceAndContentIdPathS
36 36
 from tracim_backend.views.core_api.schemas import ContentDigestSchema
37 37
 from tracim_backend.views.core_api.schemas import ActiveContentFilterQuerySchema
38 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 41
 SWAGGER_TAG__USER_ENDPOINTS = 'Users'
42 42
 

+ 2 - 2
backend/tracim_backend/views/core_api/workspace_controller.py View File

@@ -12,8 +12,8 @@ except ImportError:
12 12
     from http import client as HTTPStatus
13 13
 
14 14
 from tracim_backend import hapic
15
+from tracim_backend.lib.utils.request import TracimRequest
15 16
 from tracim_backend import BASE_API_V2
16
-from tracim_backend import TracimRequest
17 17
 from tracim_backend.lib.core.workspace import WorkspaceApi
18 18
 from tracim_backend.lib.core.content import ContentApi
19 19
 from tracim_backend.lib.core.userworkspace import RoleApi
@@ -52,7 +52,7 @@ from tracim_backend.views.core_api.schemas import ContentDigestSchema
52 52
 from tracim_backend.views.core_api.schemas import WorkspaceSchema
53 53
 from tracim_backend.views.core_api.schemas import WorkspaceIdPathSchema
54 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 56
 from tracim_backend.models.revision_protection import new_revision
57 57
 
58 58
 SWAGGER_TAG_WORKSPACE_ENDPOINTS = 'Workspaces'

+ 9 - 3
backend/tracim_backend/views/frontend.py View File

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

+ 10 - 0
backend/wsgidav-test.conf View File

@@ -0,0 +1,10 @@
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 View File

@@ -37,6 +37,11 @@ function setup_config_file {
37 37
        log "generate missing wsgidav.conf ..."
38 38
        cp wsgidav.conf.sample wsgidav.conf
39 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 47
 function setup_db {

+ 5 - 0
bash_library.sh View File

@@ -2,8 +2,13 @@
2 2
 
3 3
 YELLOW='\033[1;33m'
4 4
 BROWN='\033[0;33m'
5
+GREEN='\033[1;32m'
5 6
 NC='\033[0m' # No Color
6 7
 
7 8
 function log {
8 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 View File

@@ -1,6 +1,6 @@
1 1
 {
2 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 View File

@@ -21,37 +21,37 @@
21 21
         param = 'color'
22 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 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 33
         html_class = '.primaryColorBg{state}'
34 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 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 45
         param = 'border-color'
46 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 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 55
     </style>
56 56
   </head>
57 57
 

+ 31 - 6
frontend/i18next.scanner/en/translation.json View File

@@ -10,7 +10,6 @@
10 10
   "Search...": "Search...",
11 11
   "Create a workspace": "Create a workspace",
12 12
   "Title": "Title",
13
-  "Change": "Change",
14 13
   "Move": "Move",
15 14
   "Download": "Download",
16 15
   "Archive": "Archive",
@@ -58,9 +57,35 @@
58 57
   "You have subscribed to this workspace's notifications": "You have subscribed to this workspace's notifications",
59 58
   "Connection": "Connection",
60 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 View File

@@ -10,7 +10,6 @@
10 10
   "Search...": "Rechercher ...",
11 11
   "Create a workspace": "Créer un espace de travail",
12 12
   "Title": "Titre",
13
-  "Change": "Modifier",
14 13
   "Move": "Déplacer",
15 14
   "Download": "Télécharger",
16 15
   "Archive": "Archiver",
@@ -58,9 +57,35 @@
58 57
   "You have subscribed to this workspace's notifications": "Vous êtes abonné aux notifications de cet espace de travail.",
59 58
   "Connection": "Connexion",
60 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 View File

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

+ 1 - 2
frontend/src/component/Dashboard/RecentActivity.styl View File

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

+ 11 - 4
frontend/src/component/Header/MenuLinkList.jsx View File

@@ -1,22 +1,29 @@
1 1
 import React from 'react'
2 2
 import PropTypes from 'prop-types'
3
+import { translate } from 'react-i18next'
3 4
 
4 5
 const MenuLinkList = props => {
5 6
   return (
6 7
     <ul className='header__menu__list text-center navbar-nav mr-auto'>
7 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 12
       </li>
10 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 17
       </li>
13 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 22
       </li>
16 23
     </ul>
17 24
   )
18 25
 }
19
-export default MenuLinkList
26
+export default translate()(MenuLinkList)
20 27
 
21 28
 MenuLinkList.propTypes = {
22 29
   onClickFeature: PropTypes.func.isRequired,

+ 1 - 1
frontend/src/component/Workspace/BtnExtandedAction.jsx View File

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

+ 1 - 1
frontend/src/component/Workspace/ContentItem.jsx View File

@@ -24,7 +24,7 @@ const ContentItem = props => {
24 24
       </div>
25 25
 
26 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 28
           {status.label}
29 29
           <i className={`fa fa-fw fa-${status.faIcon}`} />
30 30
         </div>

+ 3 - 2
frontend/src/component/common/Input/DropdownCreateButton.jsx View File

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

+ 20 - 8
frontend/src/container/AdminWorkspacePage.jsx View File

@@ -13,13 +13,13 @@ class AdminWorkspacePage extends React.Component {
13 13
       <PageWrapper customClass='adminWorkspacePage'>
14 14
         <PageTitle
15 15
           parentClass={'adminWorkspacePage'}
16
-          title={'Workspace management'}
16
+          title={this.props.t('Workspace management')}
17 17
         />
18 18
 
19 19
         <PageContent parentClass='adminWorkspacePage'>
20 20
 
21 21
           <div className='adminWorkspacePage__description'>
22
-            This page informs all workspaces of the instances
22
+            {this.props.t('This page list all workspaces')}
23 23
           </div>
24 24
 
25 25
           { /*
@@ -39,14 +39,26 @@ class AdminWorkspacePage extends React.Component {
39 39
             <table className='table'>
40 40
               <thead>
41 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 62
                 </tr>
51 63
               </thead>
52 64
               <tbody>

+ 6 - 5
frontend/src/container/Home.jsx View File

@@ -3,6 +3,7 @@ import Card from '../component/common/Card/Card.jsx'
3 3
 import CardHeader from '../component/common/Card/CardHeader.jsx'
4 4
 import CardBody from '../component/common/Card/CardBody.jsx'
5 5
 import LogoHomepage from '../img/logoHeader.svg'
6
+import { translate } from 'react-i18next'
6 7
 
7 8
 class Home extends Component {
8 9
   render () {
@@ -14,17 +15,17 @@ class Home extends Component {
14 15
             <CardBody customClass='homepagecard'>
15 16
               <div>
16 17
                 <div className='homepagecard__title text-center my-4'>
17
-                  Bienvenue sur Tracim
18
+                  {this.props.t('Welcome on Tracim')}
18 19
                 </div>
19 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 22
                 </div>
22 23
                 <div className='homepagecard__delimiter delimiter' />
23 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 26
                 </div>
26 27
                 <div className='homepagecard__btn btn btn-outline-primary'>
27
-                  Créer votre espace de travail
28
+                  {this.props.t('create a workspace')}
28 29
                 </div>
29 30
                 <div className='homepagecard__logo mt-5 mb-3'>
30 31
                   <img src={LogoHomepage} alt='logo homepage' />
@@ -38,4 +39,4 @@ class Home extends Component {
38 39
   }
39 40
 }
40 41
 
41
-export default Home
42
+export default translate()(Home)

+ 1 - 1
frontend/src/css/Generic.styl View File

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

+ 8 - 1
frontend_app_html-document/i18next.scanner/en/translation.json View File

@@ -2,5 +2,12 @@
2 2
   "Last version": "Last version",
3 3
   "Validate and create": "Validate and create",
4 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 View File

@@ -2,5 +2,12 @@
2 2
   "Last version": "Dernière version",
3 3
   "Validate and create": "Valider et créer",
4 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 View File

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

+ 6 - 1
frontend_lib/i18next.scanner/en/translation.json View File

@@ -1 +1,6 @@
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 View File

@@ -1 +1,6 @@
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 View File

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

+ 8 - 7
frontend_lib/src/component/Timeline/Timeline.jsx View File

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

+ 1 - 1
frontend_lib/src/component/Timeline/Timeline.styl View File

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

+ 6 - 0
functionnal_tests/cypress.json.sample View File

@@ -0,0 +1,6 @@
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 View File


functionnal_tests/account/content_account.js → functionnal_tests/cypress_test/account/content_account.js View File


functionnal_tests/admin/content_admin_user.js → functionnal_tests/cypress_test/admin/content_admin_user.js View File


functionnal_tests/admin/content_admin_workspace.js → functionnal_tests/cypress_test/admin/content_admin_workspace.js View File


functionnal_tests/admin/navigation_admin_user.js → functionnal_tests/cypress_test/admin/navigation_admin_user.js View File


functionnal_tests/admin/navigation_admin_workspace.js → functionnal_tests/cypress_test/admin/navigation_admin_workspace.js View File


functionnal_tests/app_file/navigation_create_file.js → functionnal_tests/cypress_test/app_file/navigation_create_file.js View File


functionnal_tests/app_folder/navigation_create_folder.js → functionnal_tests/cypress_test/app_folder/navigation_create_folder.js View File


functionnal_tests/app_html-document/navigation_create_html-document.js → functionnal_tests/cypress_test/app_html-document/navigation_create_html-document.js View File


functionnal_tests/app_html-document/operation_create_html-document.js → functionnal_tests/cypress_test/app_html-document/operation_create_html-document.js View File

@@ -33,8 +33,8 @@ describe('operation :: workspace > create_new > html-document', function () {
33 33
     it ('all content > header button ', function () {
34 34
         var titre2='all content button'
35 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 38
         cy.get('.workspace__header__btnaddcontent__setting div:nth-child(4).subdropdown__link').click()
39 39
         cy.get('.createcontent .createcontent__contentname').should('be.visible')
40 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 View File


functionnal_tests/app_thread/operation_create_thread.js → functionnal_tests/cypress_test/app_thread/operation_create_thread.js View File


functionnal_tests/home_page/content_home_page.js → functionnal_tests/cypress_test/home_page/content_home_page.js View File


functionnal_tests/interface/function_change_color.js → functionnal_tests/cypress_test/interface/function_change_color.js View File


functionnal_tests/login/content_login_page.js → functionnal_tests/cypress_test/login/content_login_page.js View File


functionnal_tests/login/navigation_from_home_page_to_login_page.js → functionnal_tests/cypress_test/login/navigation_from_home_page_to_login_page.js View File


functionnal_tests/login/navigation_from_login_page_to_home_page.js → functionnal_tests/cypress_test/login/navigation_from_login_page_to_home_page.js View File


functionnal_tests/login/navigation_redirect_login_page.js → functionnal_tests/cypress_test/login/navigation_redirect_login_page.js View File


functionnal_tests/workspace/content_workspace_dashboard.js → functionnal_tests/cypress_test/workspace/content_workspace_dashboard.js View File


functionnal_tests/workspace/navigation_create_workspace.js → functionnal_tests/cypress_test/workspace/navigation_create_workspace.js View File


functionnal_tests/workspace/navigation_dashbord_link-for-calendar.js → functionnal_tests/cypress_test/workspace/navigation_dashbord_link-for-calendar.js View File


functionnal_tests/workspace/navigation_dashbord_link-for-webdav.js → functionnal_tests/cypress_test/workspace/navigation_dashbord_link-for-webdav.js View File


Some files were not shown because too many files changed in this diff