Browse Source

Merge branch 'develop' into feature/612_get_known_user

inkhey 6 years ago
parent
commit
db17eacbaa
No account linked to committer's email
100 changed files with 4256 additions and 1655 deletions
  1. 74 0
      README_traduction.md
  2. 12 0
      backend/doc/roles.md
  3. 7 6
      backend/tracim_backend/config.py
  4. 18 18
      backend/tracim_backend/fixtures/content.py
  5. 74 103
      backend/tracim_backend/lib/core/content.py
  6. 10 2
      backend/tracim_backend/lib/core/workspace.py
  7. 10 10
      backend/tracim_backend/lib/mail_notifier/notifier.py
  8. 6 6
      backend/tracim_backend/lib/utils/authorization.py
  9. 3 3
      backend/tracim_backend/lib/utils/request.py
  10. 7 5
      backend/tracim_backend/lib/webdav/dav_provider.py
  11. 3 2
      backend/tracim_backend/lib/webdav/design.py
  12. 37 34
      backend/tracim_backend/lib/webdav/resources.py
  13. 5 3
      backend/tracim_backend/lib/webdav/utils.py
  14. 4 4
      backend/tracim_backend/models/applications.py
  15. 109 144
      backend/tracim_backend/models/contents.py
  16. 6 10
      backend/tracim_backend/models/context_models.py
  17. 26 28
      backend/tracim_backend/models/data.py
  18. 4 4
      backend/tracim_backend/tests/__init__.py
  19. 46 47
      backend/tracim_backend/tests/functional/test_contents.py
  20. 5 5
      backend/tracim_backend/tests/functional/test_mail_notification.py
  21. 9 8
      backend/tracim_backend/tests/functional/test_system.py
  22. 1859 224
      backend/tracim_backend/tests/functional/test_user.py
  23. 125 60
      backend/tracim_backend/tests/functional/test_workspaces.py
  24. 119 119
      backend/tracim_backend/tests/library/test_content_api.py
  25. 6 6
      backend/tracim_backend/tests/models/test_content.py
  26. 4 3
      backend/tracim_backend/tests/models/test_content_revision.py
  27. 5 5
      backend/tracim_backend/views/contents_api/comment_controller.py
  28. 14 14
      backend/tracim_backend/views/contents_api/file_controller.py
  29. 5 5
      backend/tracim_backend/views/contents_api/html_document_controller.py
  30. 5 5
      backend/tracim_backend/views/contents_api/threads_controller.py
  31. 24 18
      backend/tracim_backend/views/core_api/schemas.py
  32. 3 3
      backend/tracim_backend/views/core_api/system_controller.py
  33. 11 2
      backend/tracim_backend/views/core_api/user_controller.py
  34. 11 11
      backend/tracim_backend/views/core_api/workspace_controller.py
  35. 59 0
      build_full_frontend.sh
  36. 1 1
      frontend/README.md
  37. 25 6
      frontend/dist/appInterface.js
  38. 2 0
      frontend/dist/index.html
  39. 16 0
      frontend/i18next.scanner.js
  40. 68 0
      frontend/i18next.scanner/en/translation.json
  41. 68 0
      frontend/i18next.scanner/fr/translation.json
  42. 6 6
      frontend/jsonserver/static_db.json
  43. 5 1
      frontend/package.json
  44. 40 8
      frontend/src/action-creator.async.js
  45. 45 28
      frontend/src/action-creator.sync.js
  46. 24 8
      frontend/src/appFactory.js
  47. 2 2
      frontend/src/component/Account/Calendar.jsx
  48. 4 4
      frontend/src/component/Account/Notification.jsx
  49. 15 6
      frontend/src/component/Account/Password.jsx
  50. 22 7
      frontend/src/component/Account/PersonalData.jsx
  51. 3 2
      frontend/src/component/Account/Timezone.jsx
  52. 1 1
      frontend/src/component/FlashMessage.jsx
  53. 1 1
      frontend/src/component/Footer.jsx
  54. 4 4
      frontend/src/component/Header/MenuActionListItem/DropdownLang.jsx
  55. 4 3
      frontend/src/component/Header/MenuActionListItem/MenuProfil.jsx
  56. 5 4
      frontend/src/component/Header/MenuActionListItem/Notification.jsx
  57. 1 1
      frontend/src/component/Header/MenuActionListItem/Search.jsx
  58. 7 6
      frontend/src/component/Workspace/BtnExtandedAction.jsx
  59. 2 0
      frontend/src/component/Workspace/ContentItem.jsx
  60. 3 3
      frontend/src/component/Workspace/ContentItemHeader.jsx
  61. 1 1
      frontend/src/component/Workspace/Folder.jsx
  62. 8 7
      frontend/src/component/Workspace/OpenContentApp.jsx
  63. 2 1
      frontend/src/component/Workspace/OpenCreateContentApp.jsx
  64. 4 1
      frontend/src/component/common/Input/SubDropdownCreateButton.jsx
  65. 14 9
      frontend/src/container/Account.jsx
  66. 47 0
      frontend/src/container/AppFullscreenManager.jsx
  67. 189 377
      frontend/src/container/Dashboard.jsx
  68. 571 0
      frontend/src/container/Dashboard_old.jsx
  69. 13 8
      frontend/src/container/Header.jsx
  70. 3 3
      frontend/src/container/Login.jsx
  71. 47 9
      frontend/src/container/Sidebar.jsx
  72. 38 5
      frontend/src/container/Tracim.jsx
  73. 24 58
      frontend/src/container/WorkspaceContent.jsx
  74. 8 22
      frontend/src/css/Dashboard.styl
  75. 6 2
      frontend/src/css/Generic.styl
  76. 0 1
      frontend/src/css/Header.styl
  77. 3 3
      frontend/src/css/Login.styl
  78. 1 1
      frontend/src/css/Workspace.styl
  79. 17 1
      frontend/src/helper.js
  80. 19 9
      frontend/src/i18n.js
  81. 0 0
      frontend/src/img/flag_en.png
  82. 0 0
      frontend/src/img/flag_fr.png
  83. 2 2
      frontend/src/reducer/appList.js
  84. 2 2
      frontend/src/reducer/contentType.js
  85. 46 0
      frontend/src/reducer/currentWorkspace.js
  86. 3 5
      frontend/src/reducer/flashMessage.js
  87. 16 5
      frontend/src/reducer/lang.js
  88. 4 3
      frontend/src/reducer/root.js
  89. 2 2
      frontend/src/reducer/timezone.js
  90. 14 5
      frontend/src/reducer/user.js
  91. 8 5
      frontend/src/reducer/workspaceContentList.js
  92. 8 7
      frontend/src/reducer/workspaceList.js
  93. 0 41
      frontend/src/translate/en.js
  94. 0 41
      frontend/src/translate/fr.js
  95. 13 0
      frontend_app_admin_workspace_user/.editorconfig
  96. 5 0
      frontend_app_admin_workspace_user/.gitignore
  97. 1 0
      frontend_app_admin_workspace_user/README.md
  98. 17 0
      frontend_app_admin_workspace_user/build_admin_workspace_user.sh
  99. 1 0
      frontend_app_admin_workspace_user/dist/asset
  100. 0 0
      frontend_app_admin_workspace_user/dist/dev

+ 74 - 0
README_traduction.md View File

1
+# Tracim internationalization
2
+
3
+## How to for Frontend part
4
+
5
+In each frontend repo (frontend, frontend_app_..., frontend_lib), there is a folder i18next.scanner that holds every translation files in JSON.
6
+
7
+___
8
+
9
+### I have found a translation error, how do I fix it ?
10
+
11
+**If the error is in any language other than english:**
12
+
13
+a) you can edit the values of the json files.
14
+
15
+b) Then commit/push your changes
16
+
17
+**If the error is in en.json:**
18
+
19
+1) You must find the key in the according .jsx file of that same repo.
20
+
21
+2) Fix the error
22
+ 
23
+3) Rebuild the translation files with:
24
+
25
+`npm run build-translation`
26
+
27
+This will add your new key in the translation files and remove the old one.
28
+
29
+4) Add translations for your new key in other .json files.
30
+
31
+5) commit/push your changes
32
+
33
+___
34
+
35
+### I have found an untranslated key in a language, how do I fix it ?
36
+
37
+It means you have found an english text even though you have selected another language. 
38
+
39
+Do task a) and b) in the according .json file, in folder i18next.scanner.
40
+
41
+___
42
+
43
+### I have found an untranslated key in a language but the key does not appear in the .json file.
44
+
45
+Do step 3).
46
+
47
+If the key still isn't in the .json file, it means the text in the .jsx file does not implement the translation process.
48
+
49
+So you must:
50
+
51
+I) Find the according .jsx file that have your untranslated key
52
+
53
+II) wrap your untranslated key in the translation function `t`:
54
+
55
+Exemple: `<div>My untranslated key</div>` will become `<div>{this.props.t('My untranslated key')}</div>`
56
+
57
+III) Check that `t` in available in your component, meanings your component must be wraped in the `translate()` higher order function
58
+
59
+``` javascript
60
+import React from 'react'
61
+import { translate } from 'react-i18next'
62
+
63
+class MyComponent extends React.Component {
64
+  render () {
65
+    return (<div>{this.props.t('My untranslated key')}</div>)
66
+  }
67
+}
68
+
69
+export default translate()(MyComponent)
70
+```
71
+
72
+IV) You can destruct `t` from `this.props` in the `render()` like it is done in most components.
73
+
74
+V) Do steps 3), 4), 5)

+ 12 - 0
backend/doc/roles.md View File

7
 
7
 
8
 ## Global profile
8
 ## Global profile
9
 
9
 
10
+|                               | Normal User | Managers    | Admin          |
11
+|-------------------------------|-------------|-------------|----------------|
12
+| slug                            | users       | managers    | administrators |
13
+|-------------------------------|-------------|-------------|---------|
14
+
10
 
15
 
11
 |                               | Normal User | Managers    | Admin   |
16
 |                               | Normal User | Managers    | Admin   |
12
 |-------------------------------|-------------|-------------|---------|
17
 |-------------------------------|-------------|-------------|---------|
22
 | access to all user data (/users/{user_id} endpoints) |personal-only|personal-only| yes     |
27
 | access to all user data (/users/{user_id} endpoints) |personal-only|personal-only| yes     |
23
 
28
 
24
 
29
 
30
+
31
+
25
 ## Workspace Roles
32
 ## Workspace Roles
26
 
33
 
27
 
34
 
28
 |                              | Reader | Contributor | Content Manager | Workspace Manager |
35
 |                              | Reader | Contributor | Content Manager | Workspace Manager |
29
 |------------------------------|--------|-------------|-----------------|-------------------|
36
 |------------------------------|--------|-------------|-----------------|-------------------|
37
+| slug                         | reader | contributor | content-manager |  workspace-manager|
38
+|------------------------------|--------|-------------|-----------------|-------------------|
39
+
40
+|                              | Reader | Contributor | Content Manager | Workspace Manager |
41
+|------------------------------|--------|-------------|-----------------|-------------------|
30
 | read content                 |  yes   | yes         | yes             | yes               |
42
 | read content                 |  yes   | yes         | yes             | yes               |
31
 |------------------------------|--------|-------------|-----------------|-------------------|
43
 |------------------------------|--------|-------------|-----------------|-------------------|
32
 | create content               |  no    | yes         | yes             | yes               |
44
 | create content               |  no    | yes         | yes             | yes               |

+ 7 - 6
backend/tracim_backend/config.py View File

3
 from paste.deploy.converters import asbool
3
 from paste.deploy.converters import asbool
4
 from tracim_backend.lib.utils.logger import logger
4
 from tracim_backend.lib.utils.logger import logger
5
 from depot.manager import DepotManager
5
 from depot.manager import DepotManager
6
+from tracim_backend.models.contents import CONTENT_TYPES
7
+from tracim_backend.models.data import ActionDescription
6
 
8
 
7
-from tracim_backend.models.data import ActionDescription, ContentType
8
 
9
 
9
 
10
 
10
 class CFG(object):
11
 class CFG(object):
145
         ]
146
         ]
146
 
147
 
147
         self.EMAIL_NOTIFICATION_NOTIFIED_CONTENTS = [
148
         self.EMAIL_NOTIFICATION_NOTIFIED_CONTENTS = [
148
-            ContentType.Page,
149
-            ContentType.Thread,
150
-            ContentType.File,
151
-            ContentType.Comment,
152
-            # ContentType.Folder -- Folder is skipped
149
+            CONTENT_TYPES.Page.slug,
150
+            CONTENT_TYPES.Thread.slug,
151
+            CONTENT_TYPES.File.slug,
152
+            CONTENT_TYPES.Comment.slug,
153
+            # CONTENT_TYPES.Folder.slug -- Folder is skipped
153
         ]
154
         ]
154
         if settings.get('email.notification.from'):
155
         if settings.get('email.notification.from'):
155
             raise Exception(
156
             raise Exception(

+ 18 - 18
backend/tracim_backend/fixtures/content.py View File

8
 from tracim_backend.lib.core.content import ContentApi
8
 from tracim_backend.lib.core.content import ContentApi
9
 from tracim_backend.lib.core.userworkspace import RoleApi
9
 from tracim_backend.lib.core.userworkspace import RoleApi
10
 from tracim_backend.lib.core.workspace import WorkspaceApi
10
 from tracim_backend.lib.core.workspace import WorkspaceApi
11
-from tracim_backend.models.data import ContentType
11
+from tracim_backend.models.contents import CONTENT_TYPES
12
 from tracim_backend.models.data import UserRoleInWorkspace
12
 from tracim_backend.models.data import UserRoleInWorkspace
13
 from tracim_backend.models.revision_protection import new_revision
13
 from tracim_backend.models.revision_protection import new_revision
14
 
14
 
91
         # Folders
91
         # Folders
92
 
92
 
93
         tool_workspace = content_api.create(
93
         tool_workspace = content_api.create(
94
-            content_type=ContentType.Folder,
94
+            content_type_slug=CONTENT_TYPES.Folder.slug,
95
             workspace=business_workspace,
95
             workspace=business_workspace,
96
             label='Tools',
96
             label='Tools',
97
             do_save=True,
97
             do_save=True,
98
             do_notify=False,
98
             do_notify=False,
99
         )
99
         )
100
         menu_workspace = content_api.create(
100
         menu_workspace = content_api.create(
101
-            content_type=ContentType.Folder,
101
+            content_type_slug=CONTENT_TYPES.Folder.slug,
102
             workspace=business_workspace,
102
             workspace=business_workspace,
103
             label='Menus',
103
             label='Menus',
104
             do_save=True,
104
             do_save=True,
106
         )
106
         )
107
 
107
 
108
         dessert_folder = content_api.create(
108
         dessert_folder = content_api.create(
109
-            content_type=ContentType.Folder,
109
+            content_type_slug=CONTENT_TYPES.Folder.slug,
110
             workspace=recipe_workspace,
110
             workspace=recipe_workspace,
111
             label='Desserts',
111
             label='Desserts',
112
             do_save=True,
112
             do_save=True,
113
             do_notify=False,
113
             do_notify=False,
114
         )
114
         )
115
         salads_folder = content_api.create(
115
         salads_folder = content_api.create(
116
-            content_type=ContentType.Folder,
116
+            content_type_slug=CONTENT_TYPES.Folder.slug,
117
             workspace=recipe_workspace,
117
             workspace=recipe_workspace,
118
             label='Salads',
118
             label='Salads',
119
             do_save=True,
119
             do_save=True,
120
             do_notify=False,
120
             do_notify=False,
121
         )
121
         )
122
         other_folder = content_api.create(
122
         other_folder = content_api.create(
123
-            content_type=ContentType.Folder,
123
+            content_type_slug=CONTENT_TYPES.Folder.slug,
124
             workspace=other_workspace,
124
             workspace=other_workspace,
125
             label='Infos',
125
             label='Infos',
126
             do_save=True,
126
             do_save=True,
129
 
129
 
130
         # Pages, threads, ..
130
         # Pages, threads, ..
131
         tiramisu_page = content_api.create(
131
         tiramisu_page = content_api.create(
132
-            content_type=ContentType.Page,
132
+            content_type_slug=CONTENT_TYPES.Page.slug,
133
             workspace=recipe_workspace,
133
             workspace=recipe_workspace,
134
             parent=dessert_folder,
134
             parent=dessert_folder,
135
             label='Tiramisu Recipes!!!',
135
             label='Tiramisu Recipes!!!',
149
             content_api.save(tiramisu_page)
149
             content_api.save(tiramisu_page)
150
 
150
 
151
         best_cake_thread = content_api.create(
151
         best_cake_thread = content_api.create(
152
-            content_type=ContentType.Thread,
152
+            content_type_slug=CONTENT_TYPES.Thread.slug,
153
             workspace=recipe_workspace,
153
             workspace=recipe_workspace,
154
             parent=dessert_folder,
154
             parent=dessert_folder,
155
             label='Best Cake',
155
             label='Best Cake',
159
         best_cake_thread.description = 'Which is the best cake?'
159
         best_cake_thread.description = 'Which is the best cake?'
160
         self._session.add(best_cake_thread)
160
         self._session.add(best_cake_thread)
161
         apple_pie_recipe = content_api.create(
161
         apple_pie_recipe = content_api.create(
162
-            content_type=ContentType.File,
162
+            content_type_slug=CONTENT_TYPES.File.slug,
163
             workspace=recipe_workspace,
163
             workspace=recipe_workspace,
164
             parent=dessert_folder,
164
             parent=dessert_folder,
165
             label='Apple_Pie',
165
             label='Apple_Pie',
174
         )
174
         )
175
         self._session.add(apple_pie_recipe)
175
         self._session.add(apple_pie_recipe)
176
         Brownie_recipe = content_api.create(
176
         Brownie_recipe = content_api.create(
177
-            content_type=ContentType.File,
177
+            content_type_slug=CONTENT_TYPES.File.slug,
178
             workspace=recipe_workspace,
178
             workspace=recipe_workspace,
179
             parent=dessert_folder,
179
             parent=dessert_folder,
180
             label='Brownie Recipe',
180
             label='Brownie Recipe',
189
         )
189
         )
190
         self._session.add(Brownie_recipe)
190
         self._session.add(Brownie_recipe)
191
         fruits_desserts_folder = content_api.create(
191
         fruits_desserts_folder = content_api.create(
192
-            content_type=ContentType.Folder,
192
+            content_type_slug=CONTENT_TYPES.Folder.slug,
193
             workspace=recipe_workspace,
193
             workspace=recipe_workspace,
194
             label='Fruits Desserts',
194
             label='Fruits Desserts',
195
             parent=dessert_folder,
195
             parent=dessert_folder,
197
         )
197
         )
198
 
198
 
199
         menu_page = content_api.create(
199
         menu_page = content_api.create(
200
-            content_type=ContentType.Page,
200
+            content_type_slug=CONTENT_TYPES.Page.slug,
201
             workspace=business_workspace,
201
             workspace=business_workspace,
202
             parent=menu_workspace,
202
             parent=menu_workspace,
203
             label='Current Menu',
203
             label='Current Menu',
205
         )
205
         )
206
 
206
 
207
         new_fruit_salad = content_api.create(
207
         new_fruit_salad = content_api.create(
208
-            content_type=ContentType.Page,
208
+            content_type_slug=CONTENT_TYPES.Page.slug,
209
             workspace=recipe_workspace,
209
             workspace=recipe_workspace,
210
             parent=fruits_desserts_folder,
210
             parent=fruits_desserts_folder,
211
             label='New Fruit Salad',
211
             label='New Fruit Salad',
212
             do_save=True,
212
             do_save=True,
213
         )
213
         )
214
         old_fruit_salad = content_api.create(
214
         old_fruit_salad = content_api.create(
215
-            content_type=ContentType.Page,
215
+            content_type_slug=CONTENT_TYPES.Page.slug,
216
             workspace=recipe_workspace,
216
             workspace=recipe_workspace,
217
             parent=fruits_desserts_folder,
217
             parent=fruits_desserts_folder,
218
             label='Fruit Salad',
218
             label='Fruit Salad',
228
         content_api.save(old_fruit_salad)
228
         content_api.save(old_fruit_salad)
229
 
229
 
230
         bad_fruit_salad = content_api.create(
230
         bad_fruit_salad = content_api.create(
231
-            content_type=ContentType.Page,
231
+            content_type_slug=CONTENT_TYPES.Page.slug,
232
             workspace=recipe_workspace,
232
             workspace=recipe_workspace,
233
             parent=fruits_desserts_folder,
233
             parent=fruits_desserts_folder,
234
             label='Bad Fruit Salad',
234
             label='Bad Fruit Salad',
245
 
245
 
246
         # File at the root for test
246
         # File at the root for test
247
         new_fruit_salad = content_api.create(
247
         new_fruit_salad = content_api.create(
248
-            content_type=ContentType.Page,
248
+            content_type_slug=CONTENT_TYPES.Page.slug,
249
             workspace=other_workspace,
249
             workspace=other_workspace,
250
             label='New Fruit Salad',
250
             label='New Fruit Salad',
251
             do_save=True,
251
             do_save=True,
252
         )
252
         )
253
         old_fruit_salad = content_api.create(
253
         old_fruit_salad = content_api.create(
254
-            content_type=ContentType.Page,
254
+            content_type_slug=CONTENT_TYPES.Page.slug,
255
             workspace=other_workspace,
255
             workspace=other_workspace,
256
             label='Fruit Salad',
256
             label='Fruit Salad',
257
             do_save=True,
257
             do_save=True,
265
         content_api.save(old_fruit_salad)
265
         content_api.save(old_fruit_salad)
266
 
266
 
267
         bad_fruit_salad = content_api.create(
267
         bad_fruit_salad = content_api.create(
268
-            content_type=ContentType.Page,
268
+            content_type_slug=CONTENT_TYPES.Page.slug,
269
             workspace=other_workspace,
269
             workspace=other_workspace,
270
             label='Bad Fruit Salad',
270
             label='Bad Fruit Salad',
271
             do_save=True,
271
             do_save=True,

+ 74 - 103
backend/tracim_backend/lib/core/content.py View File

34
 from tracim_backend.exceptions import ContentNotFound
34
 from tracim_backend.exceptions import ContentNotFound
35
 from tracim_backend.exceptions import WorkspacesDoNotMatch
35
 from tracim_backend.exceptions import WorkspacesDoNotMatch
36
 from tracim_backend.lib.utils.utils import current_date_for_filename
36
 from tracim_backend.lib.utils.utils import current_date_for_filename
37
+from tracim_backend.models.contents import CONTENT_STATUS
38
+from tracim_backend.models.contents import ContentType
39
+from tracim_backend.models.contents import CONTENT_TYPES
37
 from tracim_backend.models.revision_protection import new_revision
40
 from tracim_backend.models.revision_protection import new_revision
38
 from tracim_backend.models.auth import User
41
 from tracim_backend.models.auth import User
39
 from tracim_backend.models.data import ActionDescription
42
 from tracim_backend.models.data import ActionDescription
40
-from tracim_backend.models.data import ContentStatus
41
 from tracim_backend.models.data import ContentRevisionRO
43
 from tracim_backend.models.data import ContentRevisionRO
42
 from tracim_backend.models.data import Content
44
 from tracim_backend.models.data import Content
43
-from tracim_backend.models.data import ContentType
45
+
44
 from tracim_backend.models.data import NodeTreeItem
46
 from tracim_backend.models.data import NodeTreeItem
45
 from tracim_backend.models.data import RevisionReadStatus
47
 from tracim_backend.models.data import RevisionReadStatus
46
 from tracim_backend.models.data import UserRoleInWorkspace
48
 from tracim_backend.models.data import UserRoleInWorkspace
75
     else:
77
     else:
76
         # TODO - D.A. - 2014-12-02 - Manage Content Types Dynamically
78
         # TODO - D.A. - 2014-12-02 - Manage Content Types Dynamically
77
         content_type_order = [
79
         content_type_order = [
78
-            ContentType.Folder,
79
-            ContentType.Page,
80
-            ContentType.Thread,
81
-            ContentType.File,
80
+            CONTENT_TYPES.Folder.slug,
81
+            CONTENT_TYPES.Page.slug,
82
+            CONTENT_TYPES.Thread.slug,
83
+            CONTENT_TYPES.File.slug,
82
         ]
84
         ]
83
 
85
 
84
         content_1_type_index = content_type_order.index(content1.type)
86
         content_1_type_index = content_type_order.index(content1.type)
105
     SEARCH_SEPARATORS = ',| '
107
     SEARCH_SEPARATORS = ',| '
106
     SEARCH_DEFAULT_RESULT_NB = 50
108
     SEARCH_DEFAULT_RESULT_NB = 50
107
 
109
 
108
-    DISPLAYABLE_CONTENTS = (
109
-        ContentType.Folder,
110
-        ContentType.File,
111
-        ContentType.Comment,
112
-        ContentType.Thread,
113
-        ContentType.Page,
114
-        ContentType.PageLegacy,
115
-        ContentType.MarkdownPage,
116
-    )
110
+    # DISPLAYABLE_CONTENTS = (
111
+    #     CONTENT_TYPES.Folder.slug,
112
+    #     CONTENT_TYPES.File.slug,
113
+    #     CONTENT_TYPES.Comment.slug,
114
+    #     CONTENT_TYPES.Thread.slug,
115
+    #     CONTENT_TYPES.Page.slug,
116
+    #     CONTENT_TYPES.Page.slugLegacy,
117
+    #     ContentType.MarkdownPage,
118
+    # )
117
 
119
 
118
     def __init__(
120
     def __init__(
119
             self,
121
             self,
230
 
232
 
231
         # Exclude non displayable types
233
         # Exclude non displayable types
232
         if not self._force_show_all_types:
234
         if not self._force_show_all_types:
233
-            result = result.filter(Content.type.in_(self.DISPLAYABLE_CONTENTS))
235
+            result = result.filter(Content.type.in_(CONTENT_TYPES.query_allowed_types_slugs()))
234
 
236
 
235
         if workspace:
237
         if workspace:
236
             result = result.filter(Content.workspace_id == workspace.workspace_id)
238
             result = result.filter(Content.workspace_id == workspace.workspace_id)
369
     #     removed_item_ids = removed_item_ids or []  # FDV
371
     #     removed_item_ids = removed_item_ids or []  # FDV
370
     # 
372
     # 
371
     #     if not allowed_node_types:
373
     #     if not allowed_node_types:
372
-    #         allowed_node_types = [ContentType.Folder]
373
-    #     elif allowed_node_types==ContentType.Any:
374
+    #         allowed_node_types = [CONTENT_TYPES.Folder.slug]
375
+    #     elif allowed_node_types==CONTENT_TYPES.Any_SLUG:
374
     #         allowed_node_types = ContentType.all()
376
     #         allowed_node_types = ContentType.all()
375
     # 
377
     # 
376
     #     parent_id = parent.content_id if parent else None
378
     #     parent_id = parent.content_id if parent else None
395
     #     for folder in folders:
397
     #     for folder in folders:
396
     #         for allowed_content_type in filter_by_allowed_content_types:
398
     #         for allowed_content_type in filter_by_allowed_content_types:
397
     # 
399
     # 
398
-    #             is_folder = folder.type == ContentType.Folder
400
+    #             is_folder = folder.type == CONTENT_TYPES.Folder.slug
399
     #             content_type__allowed = folder.properties['allowed_content'][allowed_content_type] == True
401
     #             content_type__allowed = folder.properties['allowed_content'][allowed_content_type] == True
400
     # 
402
     # 
401
     #             if is_folder and content_type__allowed:
403
     #             if is_folder and content_type__allowed:
404
     # 
406
     # 
405
     #     return result
407
     #     return result
406
 
408
 
407
-    def create(self, content_type: str, workspace: Workspace, parent: Content=None, label: str ='', filename: str = '', do_save=False, is_temporary: bool=False, do_notify=True) -> Content:
409
+    def create(self, content_type_slug: str, workspace: Workspace, parent: Content=None, label: str = '', filename: str = '', do_save=False, is_temporary: bool=False, do_notify=True) -> Content:
408
         # TODO - G.M - 2018-07-16 - raise Exception instead of assert
410
         # TODO - G.M - 2018-07-16 - raise Exception instead of assert
409
-        assert content_type in ContentType.allowed_types()
411
+        assert content_type_slug in CONTENT_TYPES.query_allowed_types_slugs()
412
+        assert content_type_slug != CONTENT_TYPES.Any_SLUG
410
         assert not (label and filename)
413
         assert not (label and filename)
411
 
414
 
412
-        if content_type == ContentType.Folder and not label:
415
+        if content_type_slug == CONTENT_TYPES.Folder.slug and not label:
413
             label = self.generate_folder_label(workspace, parent)
416
             label = self.generate_folder_label(workspace, parent)
414
 
417
 
415
         content = Content()
418
         content = Content()
421
         elif label:
424
         elif label:
422
             content.label = label
425
             content.label = label
423
         else:
426
         else:
424
-            if content_type == ContentType.Comment:
427
+            if content_type_slug == CONTENT_TYPES.Comment.slug:
425
                 # INFO - G.M - 2018-07-16 - Default label for comments is
428
                 # INFO - G.M - 2018-07-16 - Default label for comments is
426
                 # empty string.
429
                 # empty string.
427
                 content.label = ''
430
                 content.label = ''
431
         content.owner = self._user
434
         content.owner = self._user
432
         content.parent = parent
435
         content.parent = parent
433
         content.workspace = workspace
436
         content.workspace = workspace
434
-        content.type = content_type
437
+        content.type = content_type_slug
435
         content.is_temporary = is_temporary
438
         content.is_temporary = is_temporary
436
         content.revision_type = ActionDescription.CREATION
439
         content.revision_type = ActionDescription.CREATION
437
 
440
 
438
         if content.type in (
441
         if content.type in (
439
-                ContentType.Page,
440
-                ContentType.Thread,
442
+                CONTENT_TYPES.Page.slug,
443
+                CONTENT_TYPES.Thread.slug,
441
         ):
444
         ):
442
             content.file_extension = '.html'
445
             content.file_extension = '.html'
443
 
446
 
447
         return content
450
         return content
448
 
451
 
449
     def create_comment(self, workspace: Workspace=None, parent: Content=None, content:str ='', do_save=False) -> Content:
452
     def create_comment(self, workspace: Workspace=None, parent: Content=None, content:str ='', do_save=False) -> Content:
450
-        assert parent and parent.type != ContentType.Folder
453
+        assert parent and parent.type != CONTENT_TYPES.Folder.slug
451
         if not content:
454
         if not content:
452
             raise EmptyCommentContentNotAllowed()
455
             raise EmptyCommentContentNotAllowed()
453
         item = Content()
456
         item = Content()
456
         if not workspace:
459
         if not workspace:
457
             workspace = item.parent.workspace
460
             workspace = item.parent.workspace
458
         item.workspace = workspace
461
         item.workspace = workspace
459
-        item.type = ContentType.Comment
462
+        item.type = CONTENT_TYPES.Comment.slug
460
         item.description = content
463
         item.description = content
461
         item.label = ''
464
         item.label = ''
462
         item.revision_type = ActionDescription.COMMENT
465
         item.revision_type = ActionDescription.COMMENT
492
 
495
 
493
         base_request = self._base_query(workspace).filter(Content.content_id==content_id)
496
         base_request = self._base_query(workspace).filter(Content.content_id==content_id)
494
 
497
 
495
-        if content_type!=ContentType.Any:
498
+        if content_type!=CONTENT_TYPES.Any_SLUG:
496
             base_request = base_request.filter(Content.type==content_type)
499
             base_request = base_request.filter(Content.type==content_type)
497
 
500
 
498
         if parent:
501
         if parent:
577
         return query.filter(
580
         return query.filter(
578
             or_(
581
             or_(
579
                 and_(
582
                 and_(
580
-                    Content.type == ContentType.File,
583
+                    Content.type == CONTENT_TYPES.File.slug,
581
                     Content.label == file_name,
584
                     Content.label == file_name,
582
                     Content.file_extension == file_extension,
585
                     Content.file_extension == file_extension,
583
                 ),
586
                 ),
584
                 and_(
587
                 and_(
585
-                    Content.type == ContentType.Thread,
588
+                    Content.type == CONTENT_TYPES.Thread.slug,
586
                     Content.label == file_name,
589
                     Content.label == file_name,
587
                 ),
590
                 ),
588
                 and_(
591
                 and_(
589
-                    Content.type == ContentType.Page,
592
+                    Content.type == CONTENT_TYPES.Page.slug,
590
                     Content.label == file_name,
593
                     Content.label == file_name,
591
                 ),
594
                 ),
592
                 and_(
595
                 and_(
593
-                    Content.type == ContentType.Folder,
596
+                    Content.type == CONTENT_TYPES.Folder.slug,
594
                     Content.label == content_label,
597
                     Content.label == content_label,
595
                 ),
598
                 ),
596
             )
599
             )
671
             # Filter query on label
674
             # Filter query on label
672
             folder_query = query \
675
             folder_query = query \
673
                 .filter(
676
                 .filter(
674
-                    Content.type == ContentType.Folder,
677
+                    Content.type == CONTENT_TYPES.Folder.slug,
675
                     Content.label == label,
678
                     Content.label == label,
676
                     Content.workspace_id == workspace.workspace_id,
679
                     Content.workspace_id == workspace.workspace_id,
677
                 )
680
                 )
723
 
726
 
724
         return query.filter(or_(
727
         return query.filter(or_(
725
             and_(
728
             and_(
726
-                Content.type == ContentType.File,
729
+                Content.type == CONTENT_TYPES.File.slug,
727
                 file_name_filter,
730
                 file_name_filter,
728
                 file_extension_filter,
731
                 file_extension_filter,
729
             ),
732
             ),
730
             and_(
733
             and_(
731
-                Content.type == ContentType.Thread,
734
+                Content.type == CONTENT_TYPES.Thread.slug,
732
                 file_name_filter,
735
                 file_name_filter,
733
                 file_extension_filter,
736
                 file_extension_filter,
734
             ),
737
             ),
735
             and_(
738
             and_(
736
-                Content.type == ContentType.Page,
739
+                Content.type == CONTENT_TYPES.Page.slug,
737
                 file_name_filter,
740
                 file_name_filter,
738
                 file_extension_filter,
741
                 file_extension_filter,
739
             ),
742
             ),
740
             and_(
743
             and_(
741
-                Content.type == ContentType.Folder,
744
+                Content.type == CONTENT_TYPES.Folder.slug,
742
                 label_filter,
745
                 label_filter,
743
             ),
746
             ),
744
         ))
747
         ))
844
     def _get_all_query(
847
     def _get_all_query(
845
         self,
848
         self,
846
         parent_id: int = None,
849
         parent_id: int = None,
847
-        content_type: str = ContentType.Any,
850
+        content_type_slug: str = CONTENT_TYPES.Any_SLUG,
848
         workspace: Workspace = None
851
         workspace: Workspace = None
849
     ) -> Query:
852
     ) -> Query:
850
         """
853
         """
851
         Extended filter for better "get all data" query
854
         Extended filter for better "get all data" query
852
         :param parent_id: filter by parent_id
855
         :param parent_id: filter by parent_id
853
-        :param content_type: filter by content_type slug
856
+        :param content_type_slug: filter by content_type slug
854
         :param workspace: filter by workspace
857
         :param workspace: filter by workspace
855
         :return:
858
         :return:
856
         """
859
         """
857
         assert parent_id is None or isinstance(parent_id, int)
860
         assert parent_id is None or isinstance(parent_id, int)
858
-        assert content_type is not None
861
+        assert content_type_slug is not None
859
         resultset = self._base_query(workspace)
862
         resultset = self._base_query(workspace)
860
 
863
 
861
-        if content_type!=ContentType.Any:
864
+        if content_type_slug != CONTENT_TYPES.Any_SLUG:
862
             # INFO - G.M - 2018-07-05 - convert with
865
             # INFO - G.M - 2018-07-05 - convert with
863
             #  content type object to support legacy slug
866
             #  content type object to support legacy slug
864
-            content_type_object = ContentType(content_type)
865
-            resultset = resultset.filter(Content.type.in_(content_type_object.get_slug_aliases()))
867
+            content_type_object = CONTENT_TYPES.get_one_by_slug(content_type_slug)
868
+            all_slug_alias = [content_type_object.slug]
869
+            if content_type_object.slug_alias:
870
+                all_slug_alias.extend(content_type_object.slug_alias)
871
+            resultset = resultset.filter(Content.type.in_(all_slug_alias))
866
 
872
 
867
         if parent_id:
873
         if parent_id:
868
             resultset = resultset.filter(Content.parent_id==parent_id)
874
             resultset = resultset.filter(Content.parent_id==parent_id)
871
 
877
 
872
         return resultset
878
         return resultset
873
 
879
 
874
-    def get_all(self, parent_id: int=None, content_type: str=ContentType.Any, workspace: Workspace=None) -> typing.List[Content]:
880
+    def get_all(self, parent_id: int=None, content_type: str=CONTENT_TYPES.Any_SLUG, workspace: Workspace=None) -> typing.List[Content]:
875
         return self._get_all_query(parent_id, content_type, workspace).all()
881
         return self._get_all_query(parent_id, content_type, workspace).all()
876
 
882
 
877
     # TODO - G.M - 2018-07-17 - [Cleanup] Drop this method if unneeded
883
     # TODO - G.M - 2018-07-17 - [Cleanup] Drop this method if unneeded
895
 
901
 
896
     # TODO - G.M - 2018-07-17 - [Cleanup] Drop this method if unneeded
902
     # TODO - G.M - 2018-07-17 - [Cleanup] Drop this method if unneeded
897
     # TODO find an other name to filter on is_deleted / is_archived
903
     # TODO find an other name to filter on is_deleted / is_archived
898
-    def get_all_with_filter(self, parent_id: int=None, content_type: str=ContentType.Any, workspace: Workspace=None) -> typing.List[Content]:
904
+    def get_all_with_filter(self, parent_id: int=None, content_type: str=CONTENT_TYPES.Any_SLUG, workspace: Workspace=None) -> typing.List[Content]:
899
         assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
905
         assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
900
         assert content_type is not None# DYN_REMOVE
906
         assert content_type is not None# DYN_REMOVE
901
         assert isinstance(content_type, str) # DYN_REMOVE
907
         assert isinstance(content_type, str) # DYN_REMOVE
902
 
908
 
903
         resultset = self._base_query(workspace)
909
         resultset = self._base_query(workspace)
904
 
910
 
905
-        if content_type != ContentType.Any:
911
+        if content_type != CONTENT_TYPES.Any_SLUG:
906
             resultset = resultset.filter(Content.type==content_type)
912
             resultset = resultset.filter(Content.type==content_type)
907
 
913
 
908
         resultset = resultset.filter(Content.is_deleted == self._show_deleted)
914
         resultset = resultset.filter(Content.is_deleted == self._show_deleted)
919
 
925
 
920
         resultset = self._base_query(workspace)
926
         resultset = self._base_query(workspace)
921
 
927
 
922
-        if content_type != ContentType.Any:
928
+        if content_type != CONTENT_TYPES.Any_SLUG:
923
             resultset = resultset.filter(Content.type==content_type)
929
             resultset = resultset.filter(Content.type==content_type)
924
 
930
 
925
         return resultset.all()
931
         return resultset.all()
926
 
932
 
927
-    def get_last_active(self, parent_id: int, content_type: str, workspace: Workspace=None, limit=10) -> typing.List[Content]:
928
-        assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
929
-        assert content_type is not None# DYN_REMOVE
930
-        assert isinstance(content_type, str) # DYN_REMOVE
931
-
932
-        resultset = self._base_query(workspace) \
933
-            .filter(Content.workspace_id == Workspace.workspace_id) \
934
-            .filter(Workspace.is_deleted.is_(False)) \
935
-            .order_by(desc(Content.updated))
936
-
937
-        if content_type!=ContentType.Any:
938
-            resultset = resultset.filter(Content.type==content_type)
939
-
940
-        if parent_id:
941
-            resultset = resultset.filter(Content.parent_id==parent_id)
942
-
943
-        result = []
944
-        for item in resultset:
945
-            new_item = None
946
-            if ContentType.Comment == item.type:
947
-                new_item = item.parent
948
-            else:
949
-                new_item = item
950
-
951
-            # INFO - D.A. - 2015-05-20
952
-            # We do not want to show only one item if the last 10 items are
953
-            # comments about one thread for example
954
-            if new_item not in result:
955
-                result.append(new_item)
956
-
957
-            if len(result) >= limit:
958
-                break
959
-
960
-        return result
961
-
962
     def get_last_unread(self, parent_id: int, content_type: str,
933
     def get_last_unread(self, parent_id: int, content_type: str,
963
                         workspace: Workspace=None, limit=10) -> typing.List[Content]:
934
                         workspace: Workspace=None, limit=10) -> typing.List[Content]:
964
         assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
935
         assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
980
     #
951
     #
981
     #     resultset = self._base_query(workspace)
952
     #     resultset = self._base_query(workspace)
982
     #
953
     #
983
-    #     if content_type != ContentType.Any:
954
+    #     if content_type != CONTENT_TYPES.Any_SLUG:
984
     #         resultset = resultset.filter(Content.type==content_type)
955
     #         resultset = resultset.filter(Content.type==content_type)
985
     #
956
     #
986
     #     return resultset.all()
957
     #     return resultset.all()
989
             self,
960
             self,
990
             workspace: Workspace=None,
961
             workspace: Workspace=None,
991
             limit: typing.Optional[int]=None,
962
             limit: typing.Optional[int]=None,
992
-            before_datetime: typing.Optional[datetime.datetime]= None,
963
+            before_content: typing.Optional[Content]= None,
993
             content_ids: typing.Optional[typing.List[int]] = None,
964
             content_ids: typing.Optional[typing.List[int]] = None,
994
     ) -> typing.List[Content]:
965
     ) -> typing.List[Content]:
995
         """
966
         """
997
         (last modification of content itself or one of this comment)
968
         (last modification of content itself or one of this comment)
998
         :param workspace: Workspace to check
969
         :param workspace: Workspace to check
999
         :param limit: maximum number of elements to return
970
         :param limit: maximum number of elements to return
1000
-        :param before_datetime: date from where we check older content.
971
+        :param before_content: last_active content are only those updated
972
+         before this content given.
1001
         :param content_ids: restrict selection to some content ids and
973
         :param content_ids: restrict selection to some content ids and
1002
         related Comments
974
         related Comments
1003
         :return: list of content
975
         :return: list of content
1012
                     Content.content_id.in_(content_ids),
984
                     Content.content_id.in_(content_ids),
1013
                     and_(
985
                     and_(
1014
                         Content.parent_id.in_(content_ids),
986
                         Content.parent_id.in_(content_ids),
1015
-                        Content.type == ContentType.Comment
987
+                        Content.type == CONTENT_TYPES.Comment.slug
1016
                     )
988
                     )
1017
                 )
989
                 )
1018
             )
990
             )
1021
 
993
 
1022
         active_contents = []
994
         active_contents = []
1023
         too_recent_content = []
995
         too_recent_content = []
996
+        before_content_find = False
1024
         for content in resultset:
997
         for content in resultset:
1025
             related_active_content = None
998
             related_active_content = None
1026
-            if ContentType.Comment == content.type:
999
+            if CONTENT_TYPES.Comment.slug == content.type:
1027
                 related_active_content = content.parent
1000
                 related_active_content = content.parent
1028
             else:
1001
             else:
1029
                 related_active_content = content
1002
                 related_active_content = content
1030
 
1003
 
1031
-            if not before_datetime:
1032
-                before_datetime = datetime.datetime.now()
1033
-            # INFO - D.A. - 2015-05-20
1034
-            # We do not want to show only one item if the last 10 items are
1035
-            # comments about one thread for example
1036
             if related_active_content not in active_contents and related_active_content not in too_recent_content:  # nopep8
1004
             if related_active_content not in active_contents and related_active_content not in too_recent_content:  # nopep8
1037
-                # we verify that content is old enough
1038
-                if content.updated < before_datetime:
1005
+
1006
+                if not before_content or before_content_find:
1039
                     active_contents.append(related_active_content)
1007
                     active_contents.append(related_active_content)
1040
                 else:
1008
                 else:
1041
                     too_recent_content.append(related_active_content)
1009
                     too_recent_content.append(related_active_content)
1042
 
1010
 
1011
+                if before_content and related_active_content == before_content:
1012
+                    before_content_find = True
1013
+
1043
             if limit and len(active_contents) >= limit:
1014
             if limit and len(active_contents) >= limit:
1044
                 break
1015
                 break
1045
 
1016
 
1075
     #         .filter(Content.content_id.in_(not_read_content_ids)) \
1046
     #         .filter(Content.content_id.in_(not_read_content_ids)) \
1076
     #         .order_by(desc(Content.updated))
1047
     #         .order_by(desc(Content.updated))
1077
     #
1048
     #
1078
-    #     if content_type != ContentType.Any:
1049
+    #     if content_type != CONTENT_TYPES.Any_SLUG:
1079
     #         not_read_contents = not_read_contents.filter(
1050
     #         not_read_contents = not_read_contents.filter(
1080
     #             Content.type==content_type)
1051
     #             Content.type==content_type)
1081
     #     else:
1052
     #     else:
1082
     #         not_read_contents = not_read_contents.filter(
1053
     #         not_read_contents = not_read_contents.filter(
1083
-    #             Content.type!=ContentType.Folder)
1054
+    #             Content.type!=CONTENT_TYPES.Folder.slug)
1084
     #
1055
     #
1085
     #     if parent_id:
1056
     #     if parent_id:
1086
     #         not_read_contents = not_read_contents.filter(
1057
     #         not_read_contents = not_read_contents.filter(
1089
     #     result = []
1060
     #     result = []
1090
     #     for item in not_read_contents:
1061
     #     for item in not_read_contents:
1091
     #         new_item = None
1062
     #         new_item = None
1092
-    #         if ContentType.Comment == item.type:
1063
+    #         if CONTENT_TYPES.Comment.slug == item.type:
1093
     #             new_item = item.parent
1064
     #             new_item = item.parent
1094
     #         else:
1065
     #         else:
1095
     #             new_item = item
1066
     #             new_item = item
1121
         folder.properties = properties
1092
         folder.properties = properties
1122
 
1093
 
1123
     def set_status(self, content: Content, new_status: str):
1094
     def set_status(self, content: Content, new_status: str):
1124
-        if new_status in ContentStatus.allowed_values():
1095
+        if new_status in CONTENT_STATUS.get_all_slugs_values():
1125
             content.status = new_status
1096
             content.status = new_status
1126
             content.revision_type = ActionDescription.STATUS_UPDATE
1097
             content.revision_type = ActionDescription.STATUS_UPDATE
1127
         else:
1098
         else:
1331
                 self.mark_read(child, read_datetime=read_datetime,
1302
                 self.mark_read(child, read_datetime=read_datetime,
1332
                                do_flush=False)
1303
                                do_flush=False)
1333
 
1304
 
1334
-            if ContentType.Comment == content.type:
1305
+            if CONTENT_TYPES.Comment.slug == content.type:
1335
                 self.mark_read(content.parent, read_datetime=read_datetime,
1306
                 self.mark_read(content.parent, read_datetime=read_datetime,
1336
                                do_flush=False, recursive=False)
1307
                                do_flush=False, recursive=False)
1337
                 for comment in content.parent.get_comments():
1308
                 for comment in content.parent.get_comments():
1451
         return title_keyworded_items
1422
         return title_keyworded_items
1452
 
1423
 
1453
     def get_all_types(self) -> typing.List[ContentType]:
1424
     def get_all_types(self) -> typing.List[ContentType]:
1454
-        labels = ContentType.all()
1425
+        labels = CONTENT_TYPES.endpoint_allowed_types_slug()
1455
         content_types = []
1426
         content_types = []
1456
         for label in labels:
1427
         for label in labels:
1457
-            content_types.append(ContentType(label))
1428
+            content_types.append(CONTENT_TYPES.get_one_by_slug(label))
1458
 
1429
 
1459
-        return ContentType.sorted(content_types)
1430
+        return content_types
1460
 
1431
 
1461
     # TODO - G.M - 2018-07-24 - [Cleanup] Is this method already needed ?
1432
     # TODO - G.M - 2018-07-24 - [Cleanup] Is this method already needed ?
1462
     def exclude_unavailable(
1433
     def exclude_unavailable(

+ 10 - 2
backend/tracim_backend/lib/core/workspace.py View File

3
 
3
 
4
 from sqlalchemy.orm import Query
4
 from sqlalchemy.orm import Query
5
 from sqlalchemy.orm import Session
5
 from sqlalchemy.orm import Session
6
+from sqlalchemy.orm.exc import NoResultFound
6
 
7
 
7
 from tracim_backend import CFG
8
 from tracim_backend import CFG
8
 from tracim_backend.exceptions import EmptyLabelNotAllowed
9
 from tracim_backend.exceptions import EmptyLabelNotAllowed
10
+from tracim_backend.exceptions import WorkspaceNotFound
9
 from tracim_backend.lib.utils.translation import fake_translator as _
11
 from tracim_backend.lib.utils.translation import fake_translator as _
10
 
12
 
11
 from tracim_backend.lib.core.userworkspace import RoleApi
13
 from tracim_backend.lib.core.userworkspace import RoleApi
132
         return workspace
134
         return workspace
133
 
135
 
134
     def get_one(self, id):
136
     def get_one(self, id):
135
-        return self._base_query().filter(Workspace.workspace_id == id).one()
137
+        try:
138
+            return self._base_query().filter(Workspace.workspace_id == id).one()
139
+        except NoResultFound as exc:
140
+            raise WorkspaceNotFound('workspace {} does not exist or not visible for user'.format(id)) from exc  # nopep8
136
 
141
 
137
     def get_one_by_label(self, label: str) -> Workspace:
142
     def get_one_by_label(self, label: str) -> Workspace:
138
-        return self._base_query().filter(Workspace.label == label).one()
143
+        try:
144
+            return self._base_query().filter(Workspace.label == label).one()
145
+        except NoResultFound as exc:
146
+            raise WorkspaceNotFound('workspace {} does not exist or not visible for user'.format(id)) from exc  # nopep8
139
 
147
 
140
     """
148
     """
141
     def get_one_for_current_user(self, id):
149
     def get_one_for_current_user(self, id):

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

19
 from tracim_backend.lib.utils.logger import logger
19
 from tracim_backend.lib.utils.logger import logger
20
 from tracim_backend.models import User
20
 from tracim_backend.models import User
21
 from tracim_backend.models.auth import User
21
 from tracim_backend.models.auth import User
22
+from tracim_backend.models.contents import CONTENT_TYPES
22
 from tracim_backend.models.data import ActionDescription
23
 from tracim_backend.models.data import ActionDescription
23
 from tracim_backend.models.data import Content
24
 from tracim_backend.models.data import Content
24
-from tracim_backend.models.data import ContentType
25
 from tracim_backend.models.data import UserRoleInWorkspace
25
 from tracim_backend.models.data import UserRoleInWorkspace
26
 from tracim_backend.lib.utils.translation import fake_translator as l_, \
26
 from tracim_backend.lib.utils.translation import fake_translator as l_, \
27
     fake_translator as _
27
     fake_translator as _
233
             config=self.config,
233
             config=self.config,
234
             show_archived=True,
234
             show_archived=True,
235
             show_deleted=True,
235
             show_deleted=True,
236
-        ).get_one(event_content_id, ContentType.Any)
237
-        main_content = content.parent if content.type == ContentType.Comment else content
236
+        ).get_one(event_content_id, CONTENT_TYPES.Any_SLUG)
237
+        main_content = content.parent if content.type == CONTENT_TYPES.Comment.slug else content
238
         notifiable_roles = WorkspaceApi(
238
         notifiable_roles = WorkspaceApi(
239
             current_user=user,
239
             current_user=user,
240
             session=self.session,
240
             session=self.session,
455
             content_text = content.description
455
             content_text = content.description
456
             call_to_action_text = l_('View online')
456
             call_to_action_text = l_('View online')
457
 
457
 
458
-            if ContentType.Thread == content.type:
458
+            if CONTENT_TYPES.Thread.slug == content.type:
459
                 call_to_action_text = l_('Answer')
459
                 call_to_action_text = l_('Answer')
460
                 content_intro = l_('<span id="content-intro-username">{}</span> started a thread entitled:').format(actor.display_name)
460
                 content_intro = l_('<span id="content-intro-username">{}</span> started a thread entitled:').format(actor.display_name)
461
                 content_text = '<p id="content-body-intro">{}</p>'.format(content.label) + \
461
                 content_text = '<p id="content-body-intro">{}</p>'.format(content.label) + \
462
                                content.get_last_comment_from(actor).description
462
                                content.get_last_comment_from(actor).description
463
 
463
 
464
-            elif ContentType.File == content.type:
464
+            elif CONTENT_TYPES.File.slug == content.type:
465
                 content_intro = l_('<span id="content-intro-username">{}</span> added a file entitled:').format(actor.display_name)
465
                 content_intro = l_('<span id="content-intro-username">{}</span> added a file entitled:').format(actor.display_name)
466
                 if content.description:
466
                 if content.description:
467
                     content_text = content.description
467
                     content_text = content.description
468
                 else:
468
                 else:
469
                     content_text = '<span id="content-body-only-title">{}</span>'.format(content.label)
469
                     content_text = '<span id="content-body-only-title">{}</span>'.format(content.label)
470
 
470
 
471
-            elif ContentType.Page == content.type:
471
+            elif CONTENT_TYPES.Page.slug == content.type:
472
                 content_intro = l_('<span id="content-intro-username">{}</span> added a page entitled:').format(actor.display_name)
472
                 content_intro = l_('<span id="content-intro-username">{}</span> added a page entitled:').format(actor.display_name)
473
                 content_text = '<span id="content-body-only-title">{}</span>'.format(content.label)
473
                 content_text = '<span id="content-body-only-title">{}</span>'.format(content.label)
474
 
474
 
476
             content_text = content.description
476
             content_text = content.description
477
             call_to_action_text = l_('View online')
477
             call_to_action_text = l_('View online')
478
 
478
 
479
-            if ContentType.File == content.type:
479
+            if CONTENT_TYPES.File.slug == content.type:
480
                 content_intro = l_('<span id="content-intro-username">{}</span> uploaded a new revision.').format(actor.display_name)
480
                 content_intro = l_('<span id="content-intro-username">{}</span> uploaded a new revision.').format(actor.display_name)
481
                 content_text = ''
481
                 content_text = ''
482
 
482
 
483
-            elif ContentType.Page == content.type:
483
+            elif CONTENT_TYPES.Page.slug == content.type:
484
                 content_intro = l_('<span id="content-intro-username">{}</span> updated this page.').format(actor.display_name)
484
                 content_intro = l_('<span id="content-intro-username">{}</span> updated this page.').format(actor.display_name)
485
                 previous_revision = content.get_previous_revision()
485
                 previous_revision = content.get_previous_revision()
486
                 title_diff = ''
486
                 title_diff = ''
490
                     title_diff + \
490
                     title_diff + \
491
                     htmldiff(previous_revision.description, content.description)
491
                     htmldiff(previous_revision.description, content.description)
492
 
492
 
493
-            elif ContentType.Thread == content.type:
493
+            elif CONTENT_TYPES.Thread.slug == content.type:
494
                 content_intro = l_('<span id="content-intro-username">{}</span> updated the thread description.').format(actor.display_name)
494
                 content_intro = l_('<span id="content-intro-username">{}</span> updated the thread description.').format(actor.display_name)
495
                 previous_revision = content.get_previous_revision()
495
                 previous_revision = content.get_previous_revision()
496
                 title_diff = ''
496
                 title_diff = ''
503
         elif ActionDescription.EDITION == action:
503
         elif ActionDescription.EDITION == action:
504
             call_to_action_text = l_('View online')
504
             call_to_action_text = l_('View online')
505
 
505
 
506
-            if ContentType.File == content.type:
506
+            if CONTENT_TYPES.File.slug == content.type:
507
                 content_intro = l_('<span id="content-intro-username">{}</span> updated the file description.').format(actor.display_name)
507
                 content_intro = l_('<span id="content-intro-username">{}</span> updated the file description.').format(actor.display_name)
508
                 content_text = '<p id="content-body-intro">{}</p>'.format(content.get_label()) + \
508
                 content_text = '<p id="content-body-intro">{}</p>'.format(content.get_label()) + \
509
                     content.description
509
                     content.description

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

5
 from pyramid.interfaces import IAuthorizationPolicy
5
 from pyramid.interfaces import IAuthorizationPolicy
6
 from zope.interface import implementer
6
 from zope.interface import implementer
7
 
7
 
8
-from tracim_backend.models.contents import NewContentType
9
-from tracim_backend.models.context_models import ContentInContext
8
+from tracim_backend.models.contents import ContentType
9
+from tracim_backend.models.contents import CONTENT_TYPES
10
 
10
 
11
 try:
11
 try:
12
     from json.decoder import JSONDecodeError
12
     from json.decoder import JSONDecodeError
13
 except ImportError:  # python3.4
13
 except ImportError:  # python3.4
14
     JSONDecodeError = ValueError
14
     JSONDecodeError = ValueError
15
 
15
 
16
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
16
+from tracim_backend.models.contents import ContentType
17
 from tracim_backend.exceptions import InsufficientUserRoleInWorkspace
17
 from tracim_backend.exceptions import InsufficientUserRoleInWorkspace
18
 from tracim_backend.exceptions import ContentTypeNotAllowed
18
 from tracim_backend.exceptions import ContentTypeNotAllowed
19
 from tracim_backend.exceptions import InsufficientUserProfile
19
 from tracim_backend.exceptions import InsufficientUserProfile
133
     return decorator
133
     return decorator
134
 
134
 
135
 
135
 
136
-def require_content_types(content_types: typing.List['NewContentType']) -> typing.Callable:  # nopep8
136
+def require_content_types(content_types: typing.List['ContentType']) -> typing.Callable:  # nopep8
137
     """
137
     """
138
     Restricts access to specific file type or raise an exception.
138
     Restricts access to specific file type or raise an exception.
139
     Check role for candidate_workspace.
139
     Check role for candidate_workspace.
140
-    :param content_types: list of NewContentType object
140
+    :param content_types: list of ContentType object
141
     :return: decorator
141
     :return: decorator
142
     """
142
     """
143
     def decorator(func: typing.Callable) -> typing.Callable:
143
     def decorator(func: typing.Callable) -> typing.Callable:
144
         @functools.wraps(func)
144
         @functools.wraps(func)
145
         def wrapper(self, context, request: 'TracimRequest') -> typing.Callable:
145
         def wrapper(self, context, request: 'TracimRequest') -> typing.Callable:
146
             content = request.current_content
146
             content = request.current_content
147
-            current_content_type_slug = ContentType(content.type).slug
147
+            current_content_type_slug = CONTENT_TYPES.get_one_by_slug(content.type).slug
148
             content_types_slug = [content_type.slug for content_type in content_types]  # nopep8
148
             content_types_slug = [content_type.slug for content_type in content_types]  # nopep8
149
             if current_content_type_slug in content_types_slug:
149
             if current_content_type_slug in content_types_slug:
150
                 return func(self, context, request)
150
                 return func(self, context, request)

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

15
 from tracim_backend.exceptions import UserDoesNotExist
15
 from tracim_backend.exceptions import UserDoesNotExist
16
 from tracim_backend.exceptions import WorkspaceNotFound
16
 from tracim_backend.exceptions import WorkspaceNotFound
17
 from tracim_backend.exceptions import ImmutableAttribute
17
 from tracim_backend.exceptions import ImmutableAttribute
18
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
18
+from tracim_backend.models.contents import CONTENT_TYPES
19
 from tracim_backend.lib.core.content import ContentApi
19
 from tracim_backend.lib.core.content import ContentApi
20
 from tracim_backend.lib.core.user import UserApi
20
 from tracim_backend.lib.core.user import UserApi
21
 from tracim_backend.lib.core.workspace import WorkspaceApi
21
 from tracim_backend.lib.core.workspace import WorkspaceApi
233
             )
233
             )
234
             comment = api.get_one(
234
             comment = api.get_one(
235
                 comment_id,
235
                 comment_id,
236
-                content_type=ContentType.Comment,
236
+                content_type=CONTENT_TYPES.Comment.slug,
237
                 workspace=workspace,
237
                 workspace=workspace,
238
                 parent=content,
238
                 parent=content,
239
             )
239
             )
271
                 session=request.dbsession,
271
                 session=request.dbsession,
272
                 config=request.registry.settings['CFG']
272
                 config=request.registry.settings['CFG']
273
             )
273
             )
274
-            content = api.get_one(content_id=content_id, workspace=workspace, content_type=ContentType.Any)  # nopep8
274
+            content = api.get_one(content_id=content_id, workspace=workspace, content_type=CONTENT_TYPES.Any_SLUG)  # nopep8
275
         except NoResultFound as exc:
275
         except NoResultFound as exc:
276
             raise ContentNotFound(
276
             raise ContentNotFound(
277
                 'Content {} does not exist '
277
                 'Content {} does not exist '

+ 7 - 5
backend/tracim_backend/lib/webdav/dav_provider.py View File

8
 from tracim_backend import CFG
8
 from tracim_backend import CFG
9
 from tracim_backend.lib.webdav.utils import transform_to_bdd, HistoryType, \
9
 from tracim_backend.lib.webdav.utils import transform_to_bdd, HistoryType, \
10
     SpecialFolderExtension
10
     SpecialFolderExtension
11
+from tracim_backend.models.contents import CONTENT_TYPES
11
 
12
 
12
 from wsgidav.dav_provider import DAVProvider
13
 from wsgidav.dav_provider import DAVProvider
13
 from wsgidav.lock_manager import LockManager
14
 from wsgidav.lock_manager import LockManager
19
 from tracim_backend.lib.core.workspace import WorkspaceApi
20
 from tracim_backend.lib.core.workspace import WorkspaceApi
20
 from tracim_backend.lib.webdav import resources
21
 from tracim_backend.lib.webdav import resources
21
 from tracim_backend.lib.webdav.utils import normpath
22
 from tracim_backend.lib.webdav.utils import normpath
22
-from tracim_backend.models.data import ContentType, Content, Workspace
23
+from tracim_backend.models.data import Content
24
+from tracim_backend.models.data import Workspace
23
 
25
 
24
 
26
 
25
 class Provider(DAVProvider):
27
 class Provider(DAVProvider):
174
             content_revision = content_api.get_one_revision(revision_id)
176
             content_revision = content_api.get_one_revision(revision_id)
175
             content = self.get_content_from_revision(content_revision, content_api)
177
             content = self.get_content_from_revision(content_revision, content_api)
176
 
178
 
177
-            if content.type == ContentType.File:
179
+            if content.type == CONTENT_TYPES.File.slug:
178
                 return resources.HistoryFileResource(
180
                 return resources.HistoryFileResource(
179
                     path=path,
181
                     path=path,
180
                     environ=environ,
182
                     environ=environ,
198
 
200
 
199
         if content is None:
201
         if content is None:
200
             return None
202
             return None
201
-        if content.type == ContentType.Folder:
203
+        if content.type == CONTENT_TYPES.Folder.slug:
202
             return resources.FolderResource(
204
             return resources.FolderResource(
203
                 path=path,
205
                 path=path,
204
                 environ=environ,
206
                 environ=environ,
207
                 session=session,
209
                 session=session,
208
                 user=user,
210
                 user=user,
209
             )
211
             )
210
-        elif content.type == ContentType.File:
212
+        elif content.type == CONTENT_TYPES.File.slug:
211
             return resources.FileResource(
213
             return resources.FileResource(
212
                 path=path,
214
                 path=path,
213
                 environ=environ,
215
                 environ=environ,
356
 
358
 
357
     def get_content_from_revision(self, revision: ContentRevisionRO, api: ContentApi) -> Content:
359
     def get_content_from_revision(self, revision: ContentRevisionRO, api: ContentApi) -> Content:
358
         try:
360
         try:
359
-            return api.get_one(revision.content_id, ContentType.Any)
361
+            return api.get_one(revision.content_id, CONTENT_TYPES.Any_SLUG)
360
         except NoResultFound:
362
         except NoResultFound:
361
             return None
363
             return None
362
 
364
 

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

1
 #coding: utf8
1
 #coding: utf8
2
 from datetime import datetime
2
 from datetime import datetime
3
 
3
 
4
+from tracim_backend.models.contents import CONTENT_TYPES
4
 from tracim_backend.models.data import VirtualEvent
5
 from tracim_backend.models.data import VirtualEvent
5
-from tracim_backend.models.data import ContentType
6
 from tracim_backend.models import data
6
 from tracim_backend.models import data
7
 
7
 
8
 # FIXME: fix temporaire ...
8
 # FIXME: fix temporaire ...
237
 
237
 
238
     return page
238
     return page
239
 
239
 
240
+
240
 def designThread(content: data.Content, content_revision: data.ContentRevisionRO, comments) -> str:
241
 def designThread(content: data.Content, content_revision: data.ContentRevisionRO, comments) -> str:
241
         hist = content.get_history(drop_empty_revision=False)
242
         hist = content.get_history(drop_empty_revision=False)
242
 
243
 
248
         disc = ''
249
         disc = ''
249
         participants = {}
250
         participants = {}
250
         for t in allT:
251
         for t in allT:
251
-            if t.type == ContentType.Comment:
252
+            if t.type == CONTENT_TYPES.Comment.slug:
252
                 disc += '''
253
                 disc += '''
253
                     <div class="row comment comment-row">
254
                     <div class="row comment comment-row">
254
                         <i class="fa fa-comment-o comment-icon"></i>
255
                         <i class="fa fa-comment-o comment-icon"></i>

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

19
     FakeFileStream
19
     FakeFileStream
20
 from tracim_backend.lib.webdav.utils import transform_to_bdd
20
 from tracim_backend.lib.webdav.utils import transform_to_bdd
21
 from tracim_backend.lib.core.workspace import WorkspaceApi
21
 from tracim_backend.lib.core.workspace import WorkspaceApi
22
+from tracim_backend.models.contents import CONTENT_TYPES
22
 from tracim_backend.models.data import User, ContentRevisionRO
23
 from tracim_backend.models.data import User, ContentRevisionRO
23
 from tracim_backend.models.data import Workspace
24
 from tracim_backend.models.data import Workspace
24
-from tracim_backend.models.data import Content, ActionDescription
25
-from tracim_backend.models.data import ContentType
25
+from tracim_backend.models.data import Content
26
+from tracim_backend.models.data import ActionDescription
26
 from tracim_backend.lib.webdav.design import designThread, designPage
27
 from tracim_backend.lib.webdav.design import designThread, designPage
27
 
28
 
28
 from wsgidav import compat
29
 from wsgidav import compat
236
 
237
 
237
         for content in children:
238
         for content in children:
238
             # the purpose is to display .history only if there's at least one content's type that has a history
239
             # the purpose is to display .history only if there's at least one content's type that has a history
239
-            if content.type != ContentType.Folder:
240
+            if content.type != CONTENT_TYPES.Folder.slug:
240
                 self._file_count += 1
241
                 self._file_count += 1
241
             retlist.append(content.get_label_as_file())
242
             retlist.append(content.get_label_as_file())
242
 
243
 
288
             raise DAVError(HTTP_FORBIDDEN)
289
             raise DAVError(HTTP_FORBIDDEN)
289
 
290
 
290
         folder = self.content_api.create(
291
         folder = self.content_api.create(
291
-            content_type=ContentType.Folder,
292
+            content_type_slug=CONTENT_TYPES.Folder.slug,
292
             workspace=self.workspace,
293
             workspace=self.workspace,
293
             label=label,
294
             label=label,
294
             parent=self.content
295
             parent=self.content
331
     def getMemberList(self) -> [_DAVResource]:
332
     def getMemberList(self) -> [_DAVResource]:
332
         members = []
333
         members = []
333
 
334
 
334
-        children = self.content_api.get_all(False, ContentType.Any, self.workspace)
335
+        children = self.content_api.get_all(False, CONTENT_TYPES.Any_SLUG, self.workspace)
335
 
336
 
336
         for content in children:
337
         for content in children:
337
             content_path = '%s/%s' % (self.path, transform_to_display(content.get_label_as_file()))
338
             content_path = '%s/%s' % (self.path, transform_to_display(content.get_label_as_file()))
338
 
339
 
339
-            if content.type == ContentType.Folder:
340
+            if content.type == CONTENT_TYPES.Folder.slug:
340
                 members.append(
341
                 members.append(
341
                     FolderResource(
342
                     FolderResource(
342
                         path=content_path,
343
                         path=content_path,
347
                         session=self.session,
348
                         session=self.session,
348
                     )
349
                     )
349
                 )
350
                 )
350
-            elif content.type == ContentType.File:
351
+            elif content.type == CONTENT_TYPES.File.slug:
351
                 self._file_count += 1
352
                 self._file_count += 1
352
                 members.append(
353
                 members.append(
353
                     FileResource(
354
                     FileResource(
553
         )
554
         )
554
         visible_children = content_api.get_all(
555
         visible_children = content_api.get_all(
555
             self.content.content_id,
556
             self.content.content_id,
556
-            ContentType.Any,
557
+            CONTENT_TYPES.Any_SLUG,
557
             self.workspace,
558
             self.workspace,
558
         )
559
         )
559
 
560
 
561
             content_path = '%s/%s' % (self.path, transform_to_display(content.get_label_as_file()))
562
             content_path = '%s/%s' % (self.path, transform_to_display(content.get_label_as_file()))
562
 
563
 
563
             try:
564
             try:
564
-                if content.type == ContentType.Folder:
565
+                if content.type == CONTENT_TYPES.Folder.slug:
565
                     members.append(
566
                     members.append(
566
                         FolderResource(
567
                         FolderResource(
567
                             path=content_path,
568
                             path=content_path,
572
                             session=self.session,
573
                             session=self.session,
573
                         )
574
                         )
574
                     )
575
                     )
575
-                elif content.type == ContentType.File:
576
+                elif content.type == CONTENT_TYPES.File.slug:
576
                     self._file_count += 1
577
                     self._file_count += 1
577
                     members.append(
578
                     members.append(
578
                         FileResource(
579
                         FileResource(
592
                             user=self.user,
593
                             user=self.user,
593
                             session=self.session,
594
                             session=self.session,
594
                         ))
595
                         ))
595
-            except Exception as exc:
596
-                logger.exception(
597
-                    'Unable to construct member {}'.format(
598
-                        content_path,
599
-                    ),
600
-                    exc_info=True,
601
-                )
596
+            except NotImplementedError as exc:
597
+                pass
598
+            # except Exception as exc:
599
+            #     logger.exception(
600
+            #         'Unable to construct member {}'.format(
601
+            #             content_path,
602
+            #         ),
603
+            #         exc_info=True,
604
+            #     )
602
 
605
 
603
         if self._file_count > 0 and self.provider.show_history():
606
         if self._file_count > 0 and self.provider.show_history():
604
             members.append(
607
             members.append(
708
         ret = []
711
         ret = []
709
 
712
 
710
         content_id = None if self.content is None else self.content.id
713
         content_id = None if self.content is None else self.content.id
711
-        for content in self.content_api.get_all(content_id, ContentType.Any, self.workspace):
714
+        for content in self.content_api.get_all(content_id, CONTENT_TYPES.Any_SLUG, self.workspace):
712
             if (self._is_archived and content.is_archived or
715
             if (self._is_archived and content.is_archived or
713
                 self._is_deleted and content.is_deleted or
716
                 self._is_deleted and content.is_deleted or
714
                 not (content.is_archived or self._is_archived or content.is_deleted or self._is_deleted))\
717
                 not (content.is_archived or self._is_archived or content.is_deleted or self._is_deleted))\
715
-                    and content.type != ContentType.Folder:
718
+                    and content.type != CONTENT_TYPES.Folder.slug:
716
                 ret.append(content.get_label_as_file())
719
                 ret.append(content.get_label_as_file())
717
 
720
 
718
         return ret
721
         return ret
741
         if self.content:
744
         if self.content:
742
             children = self.content.children
745
             children = self.content.children
743
         else:
746
         else:
744
-            children = self.content_api.get_all(False, ContentType.Any, self.workspace)
747
+            children = self.content_api.get_all(False, CONTENT_TYPES.Any_SLUG, self.workspace)
745
         
748
         
746
         for content in children:
749
         for content in children:
747
             if content.is_archived == self._is_archived and content.is_deleted == self._is_deleted:
750
             if content.is_archived == self._is_archived and content.is_deleted == self._is_deleted:
812
         if self.content:
815
         if self.content:
813
             children = self.content.children
816
             children = self.content.children
814
         else:
817
         else:
815
-            children = self.content_api.get_all(False, ContentType.Any, self.workspace)
818
+            children = self.content_api.get_all(False, CONTENT_TYPES.Any_SLUG, self.workspace)
816
 
819
 
817
         for content in children:
820
         for content in children:
818
             if content.is_deleted:
821
             if content.is_deleted:
819
                 retlist.append(content.get_label_as_file())
822
                 retlist.append(content.get_label_as_file())
820
 
823
 
821
-                if content.type != ContentType.Folder:
824
+                if content.type != CONTENT_TYPES.Folder.slug:
822
                     self._file_count += 1
825
                     self._file_count += 1
823
 
826
 
824
         return retlist
827
         return retlist
829
         if self.content:
832
         if self.content:
830
             children = self.content.children
833
             children = self.content.children
831
         else:
834
         else:
832
-            children = self.content_api.get_all(False, ContentType.Any, self.workspace)
835
+            children = self.content_api.get_all(False, CONTENT_TYPES.Any_SLUG, self.workspace)
833
 
836
 
834
         for content in children:
837
         for content in children:
835
             if content.is_deleted:
838
             if content.is_deleted:
836
                 content_path = '%s/%s' % (self.path, transform_to_display(content.get_label_as_file()))
839
                 content_path = '%s/%s' % (self.path, transform_to_display(content.get_label_as_file()))
837
 
840
 
838
-                if content.type == ContentType.Folder:
841
+                if content.type == CONTENT_TYPES.Folder.slug:
839
                     members.append(
842
                     members.append(
840
                         FolderResource(
843
                         FolderResource(
841
                             content_path,
844
                             content_path,
845
                             user=self.user,
848
                             user=self.user,
846
                             session=self.session,
849
                             session=self.session,
847
                         ))
850
                         ))
848
-                elif content.type == ContentType.File:
851
+                elif content.type == CONTENT_TYPES.File.slug:
849
                     self._file_count += 1
852
                     self._file_count += 1
850
                     members.append(
853
                     members.append(
851
                         FileResource(
854
                         FileResource(
936
         retlist = []
939
         retlist = []
937
 
940
 
938
         for content in self.content_api.get_all_with_filter(
941
         for content in self.content_api.get_all_with_filter(
939
-                self.content if self.content is None else self.content.id, ContentType.Any):
942
+                self.content if self.content is None else self.content.id, CONTENT_TYPES.Any_SLUG):
940
             retlist.append(content.get_label_as_file())
943
             retlist.append(content.get_label_as_file())
941
 
944
 
942
-            if content.type != ContentType.Folder:
945
+            if content.type != CONTENT_TYPES.Folder.slug:
943
                 self._file_count += 1
946
                 self._file_count += 1
944
 
947
 
945
         return retlist
948
         return retlist
950
         if self.content:
953
         if self.content:
951
             children = self.content.children
954
             children = self.content.children
952
         else:
955
         else:
953
-            children = self.content_api.get_all(False, ContentType.Any, self.workspace)
956
+            children = self.content_api.get_all(False, CONTENT_TYPES.Any_SLUG, self.workspace)
954
 
957
 
955
         for content in children:
958
         for content in children:
956
             if content.is_archived:
959
             if content.is_archived:
957
                 content_path = '%s/%s' % (self.path, transform_to_display(content.get_label_as_file()))
960
                 content_path = '%s/%s' % (self.path, transform_to_display(content.get_label_as_file()))
958
 
961
 
959
-                if content.type == ContentType.Folder:
962
+                if content.type == CONTENT_TYPES.Folder.slug:
960
                     members.append(
963
                     members.append(
961
                         FolderResource(
964
                         FolderResource(
962
                             content_path,
965
                             content_path,
966
                             user=self.user,
969
                             user=self.user,
967
                             session=self.session,
970
                             session=self.session,
968
                         ))
971
                         ))
969
-                elif content.type == ContentType.File:
972
+                elif content.type == CONTENT_TYPES.File.slug:
970
                     self._file_count += 1
973
                     self._file_count += 1
971
                     members.append(
974
                     members.append(
972
                         FileResource(
975
                         FileResource(
1053
 
1056
 
1054
         left_side = '%s/(%d - %s) ' % (self.path, revision.revision_id, revision.revision_type)
1057
         left_side = '%s/(%d - %s) ' % (self.path, revision.revision_id, revision.revision_type)
1055
 
1058
 
1056
-        if self.content.type == ContentType.File:
1059
+        if self.content.type == CONTENT_TYPES.File.slug:
1057
             return HistoryFileResource(
1060
             return HistoryFileResource(
1058
                 path='%s%s' % (left_side, transform_to_display(revision.file_name)),
1061
                 path='%s%s' % (left_side, transform_to_display(revision.file_name)),
1059
                 environ=self.environ,
1062
                 environ=self.environ,
1079
 
1082
 
1080
             left_side = '%s/(%d - %s) ' % (self.path, content.revision_id, content.revision_type)
1083
             left_side = '%s/(%d - %s) ' % (self.path, content.revision_id, content.revision_type)
1081
 
1084
 
1082
-            if self.content.type == ContentType.File:
1085
+            if self.content.type == CONTENT_TYPES.File.slug:
1083
                 members.append(HistoryFileResource(
1086
                 members.append(HistoryFileResource(
1084
                     path='%s%s' % (left_side, transform_to_display(content.file_name)),
1087
                     path='%s%s' % (left_side, transform_to_display(content.file_name)),
1085
                     environ=self.environ,
1088
                     environ=self.environ,
1425
         return filestream
1428
         return filestream
1426
 
1429
 
1427
     def design(self):
1430
     def design(self):
1428
-        if self.content.type == ContentType.Page:
1431
+        if self.content.type == CONTENT_TYPES.Page.slug:
1429
             return designPage(self.content, self.content_revision)
1432
             return designPage(self.content, self.content_revision)
1430
         else:
1433
         else:
1431
             return designThread(
1434
             return designThread(
1432
                 self.content,
1435
                 self.content,
1433
                 self.content_revision,
1436
                 self.content_revision,
1434
-                self.content_api.get_all(self.content.content_id, ContentType.Comment)
1437
+                self.content_api.get_all(self.content.content_id, CONTENT_TYPES.Comment.slug)
1435
             )
1438
             )
1436
 
1439
 
1437
 
1440
 

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

4
 from os.path import normpath as base_normpath
4
 from os.path import normpath as base_normpath
5
 
5
 
6
 from sqlalchemy.orm import Session
6
 from sqlalchemy.orm import Session
7
+from tracim_backend.models.contents import CONTENT_TYPES
7
 from wsgidav import util
8
 from wsgidav import util
8
 from wsgidav import compat
9
 from wsgidav import compat
9
 
10
 
10
 from tracim_backend.lib.core.content import ContentApi
11
 from tracim_backend.lib.core.content import ContentApi
11
-from tracim_backend.models.data import Workspace, Content, ContentType, \
12
-    ActionDescription
12
+from tracim_backend.models.data import Workspace
13
+from tracim_backend.models.data import Content
14
+from tracim_backend.models.data import ActionDescription
13
 from tracim_backend.models.revision_protection import new_revision
15
 from tracim_backend.models.revision_protection import new_revision
14
 
16
 
15
 
17
 
177
 
179
 
178
         file = self._api.create(
180
         file = self._api.create(
179
             filename=self._file_name,
181
             filename=self._file_name,
180
-            content_type=ContentType.File,
182
+            content_type_slug=CONTENT_TYPES.File.slug,
181
             workspace=self._workspace,
183
             workspace=self._workspace,
182
             parent=self._parent,
184
             parent=self._parent,
183
             is_temporary=is_temporary
185
             is_temporary=is_temporary

+ 4 - 4
backend/tracim_backend/models/applications.py View File

50
 
50
 
51
 thread = Application(
51
 thread = Application(
52
     label='Threads',
52
     label='Threads',
53
-    slug='contents/threads',
53
+    slug='contents/thread',
54
     fa_icon='comments-o',
54
     fa_icon='comments-o',
55
     hexcolor='#ad4cf9',
55
     hexcolor='#ad4cf9',
56
     is_active=True,
56
     is_active=True,
61
 
61
 
62
 _file = Application(
62
 _file = Application(
63
     label='Files',
63
     label='Files',
64
-    slug='contents/files',
64
+    slug='contents/file',
65
     fa_icon='paperclip',
65
     fa_icon='paperclip',
66
     hexcolor='#FF9900',
66
     hexcolor='#FF9900',
67
     is_active=True,
67
     is_active=True,
81
 
81
 
82
 html_documents = Application(
82
 html_documents = Application(
83
     label='Text Documents',  # TODO - G.M - 24-05-2018 - Check label
83
     label='Text Documents',  # TODO - G.M - 24-05-2018 - Check label
84
-    slug='contents/html-documents',
84
+    slug='contents/html-document',
85
     fa_icon='file-text-o',
85
     fa_icon='file-text-o',
86
     hexcolor='#3f52e3',
86
     hexcolor='#3f52e3',
87
     is_active=True,
87
     is_active=True,
88
     config={},
88
     config={},
89
-    main_route='/#/workspaces/{workspace_id}/contents?type=html-documents',
89
+    main_route='/#/workspaces/{workspace_id}/contents?type=html-document',
90
 )
90
 )
91
 # TODO - G.M - 08-06-2018 - This is hardcoded lists of app, make this dynamic.
91
 # TODO - G.M - 08-06-2018 - This is hardcoded lists of app, make this dynamic.
92
 # List of applications
92
 # List of applications

+ 109 - 144
backend/tracim_backend/models/contents.py View File

19
     CLOSED = 'closed'
19
     CLOSED = 'closed'
20
 
20
 
21
 
21
 
22
-class NewContentStatus(object):
22
+class ContentStatus(object):
23
     """
23
     """
24
-    Future ContentStatus object class
24
+    ContentStatus object class
25
     """
25
     """
26
     def __init__(
26
     def __init__(
27
             self,
27
             self,
38
         self.hexcolor = hexcolor
38
         self.hexcolor = hexcolor
39
 
39
 
40
 
40
 
41
-open_status = NewContentStatus(
41
+open_status = ContentStatus(
42
     slug='open',
42
     slug='open',
43
     global_status=GlobalStatus.OPEN.value,
43
     global_status=GlobalStatus.OPEN.value,
44
     label='Open',
44
     label='Open',
46
     hexcolor='#3f52e3',
46
     hexcolor='#3f52e3',
47
 )
47
 )
48
 
48
 
49
-closed_validated_status = NewContentStatus(
49
+closed_validated_status = ContentStatus(
50
     slug='closed-validated',
50
     slug='closed-validated',
51
     global_status=GlobalStatus.CLOSED.value,
51
     global_status=GlobalStatus.CLOSED.value,
52
     label='Validated',
52
     label='Validated',
54
     hexcolor='#008000',
54
     hexcolor='#008000',
55
 )
55
 )
56
 
56
 
57
-closed_unvalidated_status = NewContentStatus(
57
+closed_unvalidated_status = ContentStatus(
58
     slug='closed-unvalidated',
58
     slug='closed-unvalidated',
59
     global_status=GlobalStatus.CLOSED.value,
59
     global_status=GlobalStatus.CLOSED.value,
60
     label='Cancelled',
60
     label='Cancelled',
62
     hexcolor='#f63434',
62
     hexcolor='#f63434',
63
 )
63
 )
64
 
64
 
65
-closed_deprecated_status = NewContentStatus(
65
+closed_deprecated_status = ContentStatus(
66
     slug='closed-deprecated',
66
     slug='closed-deprecated',
67
     global_status=GlobalStatus.CLOSED.value,
67
     global_status=GlobalStatus.CLOSED.value,
68
     label='Deprecated',
68
     label='Deprecated',
71
 )
71
 )
72
 
72
 
73
 
73
 
74
-CONTENT_DEFAULT_STATUS = [
75
-    open_status,
76
-    closed_validated_status,
77
-    closed_unvalidated_status,
78
-    closed_deprecated_status,
79
-]
74
+class ContentStatusList(object):
80
 
75
 
76
+    OPEN = open_status
81
 
77
 
82
-class ContentStatusLegacy(NewContentStatus):
83
-    """
84
-    Temporary remplacement object for Legacy ContentStatus Object
85
-    """
86
-    OPEN = open_status.slug
87
-    CLOSED_VALIDATED = closed_validated_status.slug
88
-    CLOSED_UNVALIDATED = closed_unvalidated_status.slug
89
-    CLOSED_DEPRECATED = closed_deprecated_status.slug
90
-
91
-    def __init__(self, slug: str):
92
-        for status in CONTENT_DEFAULT_STATUS:
93
-            if slug == status.slug:
94
-                super(ContentStatusLegacy, self).__init__(
95
-                    slug=status.slug,
96
-                    global_status=status.global_status,
97
-                    label=status.label,
98
-                    fa_icon=status.fa_icon,
99
-                    hexcolor=status.hexcolor,
100
-                )
101
-                return
78
+    def __init__(self, extend_content_status: typing.List[ContentStatus]):
79
+        self._content_status = [self.OPEN]
80
+        self._content_status.extend(extend_content_status)
81
+
82
+    def get_one_by_slug(self, slug: str) -> ContentStatus:
83
+        for item in self._content_status:
84
+            if item.slug == slug:
85
+                return item
102
         raise ContentStatusNotExist()
86
         raise ContentStatusNotExist()
103
 
87
 
104
-    @classmethod
105
-    def all(cls, type='') -> ['NewContentStatus']:
106
-        return CONTENT_DEFAULT_STATUS
88
+    def get_all_slugs_values(self) -> typing.List[str]:
89
+        """ Get alls slugs"""
90
+        return [item.slug for item in self._content_status]
91
+
92
+    def get_all(self) -> typing.List[ContentStatus]:
93
+        """ Get all status"""
94
+        return [item for item in self._content_status]
107
 
95
 
108
-    @classmethod
109
-    def allowed_values(cls):
110
-        return [status.slug for status in CONTENT_DEFAULT_STATUS]
96
+    def get_default_status(self) -> ContentStatus:
97
+        return self.OPEN
111
 
98
 
112
 
99
 
100
+CONTENT_STATUS = ContentStatusList(
101
+    [
102
+        closed_validated_status,
103
+        closed_unvalidated_status,
104
+        closed_deprecated_status,
105
+    ]
106
+)
113
 ####
107
 ####
114
 # ContentType
108
 # ContentType
115
 
109
 
116
 
110
 
117
-class NewContentType(object):
111
+class ContentType(object):
118
     """
112
     """
119
     Future ContentType object class
113
     Future ContentType object class
120
     """
114
     """
125
             hexcolor: str,
119
             hexcolor: str,
126
             label: str,
120
             label: str,
127
             creation_label: str,
121
             creation_label: str,
128
-            available_statuses: typing.List[NewContentStatus],
129
-
122
+            available_statuses: typing.List[ContentStatus],
123
+            slug_alias: typing.List[str] = None,
130
     ):
124
     ):
131
         self.slug = slug
125
         self.slug = slug
132
         self.fa_icon = fa_icon
126
         self.fa_icon = fa_icon
134
         self.label = label
128
         self.label = label
135
         self.creation_label = creation_label
129
         self.creation_label = creation_label
136
         self.available_statuses = available_statuses
130
         self.available_statuses = available_statuses
131
+        self.slug_alias = slug_alias
137
 
132
 
138
 
133
 
139
-thread_type = NewContentType(
134
+thread_type = ContentType(
140
     slug='thread',
135
     slug='thread',
141
     fa_icon=thread.fa_icon,
136
     fa_icon=thread.fa_icon,
142
     hexcolor=thread.hexcolor,
137
     hexcolor=thread.hexcolor,
143
     label='Thread',
138
     label='Thread',
144
     creation_label='Discuss about a topic',
139
     creation_label='Discuss about a topic',
145
-    available_statuses=CONTENT_DEFAULT_STATUS,
140
+    available_statuses=CONTENT_STATUS.get_all(),
146
 )
141
 )
147
 
142
 
148
-file_type = NewContentType(
143
+file_type = ContentType(
149
     slug='file',
144
     slug='file',
150
     fa_icon=_file.fa_icon,
145
     fa_icon=_file.fa_icon,
151
     hexcolor=_file.hexcolor,
146
     hexcolor=_file.hexcolor,
152
     label='File',
147
     label='File',
153
     creation_label='Upload a file',
148
     creation_label='Upload a file',
154
-    available_statuses=CONTENT_DEFAULT_STATUS,
149
+    available_statuses=CONTENT_STATUS.get_all(),
155
 )
150
 )
156
 
151
 
157
-markdownpluspage_type = NewContentType(
152
+markdownpluspage_type = ContentType(
158
     slug='markdownpage',
153
     slug='markdownpage',
159
     fa_icon=markdownpluspage.fa_icon,
154
     fa_icon=markdownpluspage.fa_icon,
160
     hexcolor=markdownpluspage.hexcolor,
155
     hexcolor=markdownpluspage.hexcolor,
161
     label='Rich Markdown File',
156
     label='Rich Markdown File',
162
     creation_label='Create a Markdown document',
157
     creation_label='Create a Markdown document',
163
-    available_statuses=CONTENT_DEFAULT_STATUS,
158
+    available_statuses=CONTENT_STATUS.get_all(),
164
 )
159
 )
165
 
160
 
166
-html_documents_type = NewContentType(
167
-    slug='html-documents',
161
+html_documents_type = ContentType(
162
+    slug='html-document',
168
     fa_icon=html_documents.fa_icon,
163
     fa_icon=html_documents.fa_icon,
169
     hexcolor=html_documents.hexcolor,
164
     hexcolor=html_documents.hexcolor,
170
     label='Text Document',
165
     label='Text Document',
171
     creation_label='Write a document',
166
     creation_label='Write a document',
172
-    available_statuses=CONTENT_DEFAULT_STATUS,
167
+    available_statuses=CONTENT_STATUS.get_all(),
168
+    slug_alias=['page']
173
 )
169
 )
174
 
170
 
175
 # TODO - G.M - 31-05-2018 - Set Better folder params
171
 # TODO - G.M - 31-05-2018 - Set Better folder params
176
-folder_type = NewContentType(
172
+folder_type = ContentType(
177
     slug='folder',
173
     slug='folder',
178
     fa_icon=thread.fa_icon,
174
     fa_icon=thread.fa_icon,
179
     hexcolor=thread.hexcolor,
175
     hexcolor=thread.hexcolor,
180
     label='Folder',
176
     label='Folder',
181
     creation_label='Create collection of any documents',
177
     creation_label='Create collection of any documents',
182
-    available_statuses=CONTENT_DEFAULT_STATUS,
178
+    available_statuses=CONTENT_STATUS.get_all(),
183
 )
179
 )
184
 
180
 
185
-CONTENT_DEFAULT_TYPE = [
186
-    thread_type,
187
-    file_type,
188
-    markdownpluspage_type,
189
-    html_documents_type,
190
-    folder_type,
191
-]
192
 
181
 
193
 # TODO - G.M - 31-05-2018 - Set Better Event params
182
 # TODO - G.M - 31-05-2018 - Set Better Event params
194
-event_type = NewContentType(
183
+event_type = ContentType(
195
     slug='event',
184
     slug='event',
196
     fa_icon=thread.fa_icon,
185
     fa_icon=thread.fa_icon,
197
     hexcolor=thread.hexcolor,
186
     hexcolor=thread.hexcolor,
198
     label='Event',
187
     label='Event',
199
     creation_label='Event',
188
     creation_label='Event',
200
-    available_statuses=CONTENT_DEFAULT_STATUS,
189
+    available_statuses=CONTENT_STATUS.get_all(),
201
 )
190
 )
202
 
191
 
203
 # TODO - G.M - 31-05-2018 - Set Better Event params
192
 # TODO - G.M - 31-05-2018 - Set Better Event params
204
-comment_type = NewContentType(
193
+comment_type = ContentType(
205
     slug='comment',
194
     slug='comment',
206
     fa_icon=thread.fa_icon,
195
     fa_icon=thread.fa_icon,
207
     hexcolor=thread.hexcolor,
196
     hexcolor=thread.hexcolor,
208
     label='Comment',
197
     label='Comment',
209
     creation_label='Comment',
198
     creation_label='Comment',
210
-    available_statuses=CONTENT_DEFAULT_STATUS,
199
+    available_statuses=CONTENT_STATUS.get_all(),
211
 )
200
 )
212
 
201
 
213
-CONTENT_DEFAULT_TYPE_SPECIAL = [
214
-    event_type,
215
-    comment_type,
216
-]
217
-
218
-ALL_CONTENTS_DEFAULT_TYPES = CONTENT_DEFAULT_TYPE + CONTENT_DEFAULT_TYPE_SPECIAL
219
 
202
 
220
-
221
-class ContentTypeLegacy(NewContentType):
203
+class ContentTypeList(object):
222
     """
204
     """
223
-    Temporary remplacement object for Legacy ContentType Object
205
+    ContentType List
224
     """
206
     """
225
-
226
-    # special type
227
-    Any = 'any'
228
-    Folder = 'folder'
229
-    Event = 'event'
230
-    Comment = 'comment'
231
-
232
-    File = file_type.slug
233
-    Thread = thread_type.slug
234
-    Page = html_documents_type.slug
235
-    PageLegacy = 'page'
236
-    MarkdownPage = markdownpluspage_type.slug
237
-
238
-    def __init__(self, slug: str):
239
-        if slug == 'page':
240
-            slug = ContentTypeLegacy.Page
241
-        for content_type in ALL_CONTENTS_DEFAULT_TYPES:
242
-            if slug == content_type.slug:
243
-                super(ContentTypeLegacy, self).__init__(
244
-                    slug=content_type.slug,
245
-                    fa_icon=content_type.fa_icon,
246
-                    hexcolor=content_type.hexcolor,
247
-                    label=content_type.label,
248
-                    creation_label=content_type.creation_label,
249
-                    available_statuses=content_type.available_statuses
250
-                )
251
-                return
207
+    Any_SLUG = 'any'
208
+    Folder = folder_type
209
+    Comment = comment_type
210
+    Event = event_type
211
+    File = file_type
212
+    Page = html_documents_type
213
+    Thread = thread_type
214
+
215
+    def __init__(self, extend_content_status: typing.List[ContentType]):
216
+        self._content_types = [self.Folder]
217
+        self._content_types.extend(extend_content_status)
218
+        self._special_contents_types = [self.Comment]
219
+        self._extra_slugs = [self.Any_SLUG]
220
+
221
+    def get_one_by_slug(self, slug: str) -> ContentType:
222
+        """
223
+        Get ContentType object according to slug
224
+        match for both slug and slug_alias
225
+        """
226
+        content_types = self._content_types.copy()
227
+        content_types.extend(self._special_contents_types)
228
+        for item in content_types:
229
+            if item.slug == slug or (item.slug_alias and slug in item.slug_alias):  # nopep8
230
+                return item
252
         raise ContentTypeNotExist()
231
         raise ContentTypeNotExist()
253
 
232
 
254
-    def get_slug_aliases(self) -> typing.List[str]:
233
+    def endpoint_allowed_types_slug(self) -> typing.List[str]:
255
         """
234
         """
256
-        Get all slug aliases of a content,
257
-        useful for legacy code convertion
235
+        Return restricted list of content_type:
236
+        dont return special content_type like  comment, don't return
237
+        "any" slug, dont return content type slug alias , don't return event.
238
+        Useful to restrict slug param in schema.
258
         """
239
         """
259
-        # TODO - G.M - 2018-07-05 - Remove this legacy compat code
260
-        # when possible.
261
-        page_alias = [self.Page, self.PageLegacy]
262
-        if self.slug in page_alias:
263
-            return page_alias
264
-        else:
265
-            return [self.slug]
266
-
267
-    @classmethod
268
-    def all(cls) -> typing.List[str]:
269
-        return cls.allowed_types()
270
-
271
-    @classmethod
272
-    def allowed_types(cls) -> typing.List[str]:
273
-        contents_types = [status.slug for status in ALL_CONTENTS_DEFAULT_TYPES]
274
-        return contents_types
275
-
276
-    @classmethod
277
-    def allowed_type_values(cls) -> typing.List[str]:
240
+        allowed_type_slug = [contents_type.slug for contents_type in self._content_types]  # nopep8
241
+        return allowed_type_slug
242
+
243
+    def query_allowed_types_slugs(self) -> typing.List[str]:
278
         """
244
         """
279
-        All content type slug + special values like any
245
+        Return alls allowed types slug : content_type slug + all alias, any
246
+        and special content_type like comment. Do not return event.
247
+        Usefull allowed value to perform query to database.
280
         """
248
         """
281
-        content_types = cls.allowed_types()
282
-        content_types.append(ContentTypeLegacy.Any)
283
-        return content_types
284
-
285
-    @classmethod
286
-    def allowed_types_for_folding(cls):
287
-        # This method is used for showing only "main"
288
-        # types in the left-side treeview
289
-        contents_types = [status.slug for status in CONTENT_DEFAULT_TYPE]
290
-        return contents_types
291
-
292
-    # TODO - G.M - 30-05-2018 - This method don't do anything.
293
-    @classmethod
294
-    def sorted(cls, types: ['ContentType']) -> ['ContentType']:
295
-        return types
296
-
297
-    @property
298
-    def id(self):
299
-        return self.slug
300
-
301
-    def toDict(self):
302
-        raise NotImplementedError()
249
+        allowed_types_slug = []
250
+        for content_type in self._content_types:
251
+            allowed_types_slug.append(content_type.slug)
252
+            if content_type.slug_alias:
253
+                allowed_types_slug.extend(content_type.slug_alias)
254
+        for content_type in self._special_contents_types:
255
+            allowed_types_slug.append(content_type.slug)
256
+        allowed_types_slug.extend(self._extra_slugs)
257
+        return allowed_types_slug
258
+
259
+
260
+CONTENT_TYPES = ContentTypeList(
261
+    [
262
+        thread_type,
263
+        file_type,
264
+        markdownpluspage_type,
265
+        html_documents_type,
266
+    ]
267
+)

+ 6 - 10
backend/tracim_backend/models/context_models.py View File

16
 from tracim_backend.models.roles import WorkspaceRoles
16
 from tracim_backend.models.roles import WorkspaceRoles
17
 from tracim_backend.models.workspace_menu_entries import default_workspace_menu_entry
17
 from tracim_backend.models.workspace_menu_entries import default_workspace_menu_entry
18
 from tracim_backend.models.workspace_menu_entries import WorkspaceMenuEntry
18
 from tracim_backend.models.workspace_menu_entries import WorkspaceMenuEntry
19
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
19
+from tracim_backend.models.contents import CONTENT_TYPES
20
 
20
 
21
 
21
 
22
 class PreviewAllowedDim(object):
22
 class PreviewAllowedDim(object):
234
     def __init__(
234
     def __init__(
235
             self,
235
             self,
236
             limit: int = None,
236
             limit: int = None,
237
-            before_datetime: datetime = None,
237
+            before_content_id: datetime = None,
238
     ):
238
     ):
239
         self.limit = limit
239
         self.limit = limit
240
-        self.before_datetime = before_datetime
240
+        self.before_content_id = before_content_id
241
 
241
 
242
 
242
 
243
 class ContentIdsQuery(object):
243
 class ContentIdsQuery(object):
299
     ) -> None:
299
     ) -> None:
300
         self.label = label
300
         self.label = label
301
         self.content_type = content_type
301
         self.content_type = content_type
302
-        self.parent_id = parent_id
302
+        self.parent_id = parent_id or None
303
 
303
 
304
 
304
 
305
 class CommentCreation(object):
305
 class CommentCreation(object):
586
 
586
 
587
     @property
587
     @property
588
     def content_type(self) -> str:
588
     def content_type(self) -> str:
589
-        content_type = ContentType(self.content.type)
589
+        content_type = CONTENT_TYPES.get_one_by_slug(self.content.type)
590
         return content_type.slug
590
         return content_type.slug
591
 
591
 
592
     @property
592
     @property
698
 
698
 
699
     @property
699
     @property
700
     def content_type(self) -> str:
700
     def content_type(self) -> str:
701
-        content_type = ContentType(self.revision.type)
702
-        if content_type:
703
-            return content_type.slug
704
-        else:
705
-            return None
701
+        return CONTENT_TYPES.get_one_by_slug(self.revision.type).slug
706
 
702
 
707
     @property
703
     @property
708
     def sub_content_types(self) -> typing.List[str]:
704
     def sub_content_types(self) -> typing.List[str]:

+ 26 - 28
backend/tracim_backend/models/data.py View File

96
 
96
 
97
     def get_allowed_content_types(self):
97
     def get_allowed_content_types(self):
98
         # @see Content.get_allowed_content_types()
98
         # @see Content.get_allowed_content_types()
99
-        return [ContentType('folder')]
99
+        return CONTENT_TYPES.endpoint_allowed_types_slug()
100
 
100
 
101
     def get_valid_children(
101
     def get_valid_children(
102
             self,
102
             self,
291
                 ]
291
                 ]
292
 
292
 
293
 
293
 
294
-from tracim_backend.models.contents import ContentStatusLegacy as ContentStatus
295
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
294
+from tracim_backend.models.contents import CONTENT_STATUS
295
+from tracim_backend.models.contents import ContentStatus
296
+from tracim_backend.models.contents import CONTENT_TYPES
296
 # TODO - G.M - 30-05-2018 - Drop this old code when whe are sure nothing
297
 # TODO - G.M - 30-05-2018 - Drop this old code when whe are sure nothing
297
 # is lost .
298
 # is lost .
298
 
299
 
351
 #         # self.icon = ContentStatus._ICONS[id]
352
 #         # self.icon = ContentStatus._ICONS[id]
352
 #         # self.css = ContentStatus._CSS[id]
353
 #         # self.css = ContentStatus._CSS[id]
353
 #         #
354
 #         #
354
-#         # if type==ContentType.Thread:
355
+#         # if type==CONTENT_TYPES.Thread.slug:
355
 #         #     self.label = ContentStatus._LABELS_THREAD[id]
356
 #         #     self.label = ContentStatus._LABELS_THREAD[id]
356
-#         # elif type==ContentType.File:
357
+#         # elif type==CONTENT_TYPES.File.slug:
357
 #         #     self.label = ContentStatus._LABELS_FILE[id]
358
 #         #     self.label = ContentStatus._LABELS_FILE[id]
358
 #         # else:
359
 #         # else:
359
 #         #     self.label = ContentStatus._LABELS[id]
360
 #         #     self.label = ContentStatus._LABELS[id]
499
 #     #     # TODO - DYNDATATYPE - D.A. - 2014-12-02
500
 #     #     # TODO - DYNDATATYPE - D.A. - 2014-12-02
500
 #     #     # Make this code dynamic loading data types
501
 #     #     # Make this code dynamic loading data types
501
 #     #
502
 #     #
502
-#     #     if content.type==ContentType.Folder:
503
+#     #     if content.type==CONTENT_TYPES.Folder.slug:
503
 #     #         return '/workspaces/{}/folders/{}'.format(content.workspace_id, content.content_id)
504
 #     #         return '/workspaces/{}/folders/{}'.format(content.workspace_id, content.content_id)
504
-#     #     elif content.type==ContentType.File:
505
+#     #     elif content.type==CONTENT_TYPES.File.slug:
505
 #     #         return '/workspaces/{}/folders/{}/files/{}'.format(content.workspace_id, content.parent_id, content.content_id)
506
 #     #         return '/workspaces/{}/folders/{}/files/{}'.format(content.workspace_id, content.parent_id, content.content_id)
506
-#     #     elif content.type==ContentType.Thread:
507
+#     #     elif content.type==CONTENT_TYPES.Thread.slug:
507
 #     #         return '/workspaces/{}/folders/{}/threads/{}'.format(content.workspace_id, content.parent_id, content.content_id)
508
 #     #         return '/workspaces/{}/folders/{}/threads/{}'.format(content.workspace_id, content.parent_id, content.content_id)
508
-#     #     elif content.type==ContentType.Page:
509
+#     #     elif content.type==CONTENT_TYPES.Page.slug:
509
 #     #         return '/workspaces/{}/folders/{}/pages/{}'.format(content.workspace_id, content.parent_id, content.content_id)
510
 #     #         return '/workspaces/{}/folders/{}/pages/{}'.format(content.workspace_id, content.parent_id, content.content_id)
510
 #     #
511
 #     #
511
 #     # @classmethod
512
 #     # @classmethod
544
 
545
 
545
     @classmethod
546
     @classmethod
546
     def check_properties(cls, item):
547
     def check_properties(cls, item):
547
-        if item.type == ContentType.Folder:
548
+        if item.type == CONTENT_TYPES.Folder.slug:
548
             properties = item.properties
549
             properties = item.properties
549
             if 'allowed_content' not in properties.keys():
550
             if 'allowed_content' not in properties.keys():
550
                 return False
551
                 return False
558
                 return False
559
                 return False
559
             return True
560
             return True
560
 
561
 
561
-        if item.type == ContentType.Event:
562
+        if item.type == CONTENT_TYPES.Event.slug:
562
             properties = item.properties
563
             properties = item.properties
563
             if 'name' not in properties.keys():
564
             if 'name' not in properties.keys():
564
                 return False
565
                 return False
572
 
573
 
573
         # TODO - G.M - 15-03-2018 - Choose only correct Content-type for origin
574
         # TODO - G.M - 15-03-2018 - Choose only correct Content-type for origin
574
         # Only content who can be copied need this
575
         # Only content who can be copied need this
575
-        if item.type == ContentType.Any:
576
+        if item.type == CONTENT_TYPES.Any_SLUG:
576
             properties = item.properties
577
             properties = item.properties
577
             if 'origin' in properties.keys():
578
             if 'origin' in properties.keys():
578
                 return True
579
                 return True
580
 
581
 
581
     @classmethod
582
     @classmethod
582
     def reset_properties(cls, item):
583
     def reset_properties(cls, item):
583
-        if item.type == ContentType.Folder:
584
+        if item.type == CONTENT_TYPES.Folder.slug:
584
             item.properties = DEFAULT_PROPERTIES
585
             item.properties = DEFAULT_PROPERTIES
585
             return
586
             return
586
 
587
 
616
     properties = Column('properties', Text(), unique=False, nullable=False, default='')
617
     properties = Column('properties', Text(), unique=False, nullable=False, default='')
617
 
618
 
618
     type = Column(Unicode(32), unique=False, nullable=False)
619
     type = Column(Unicode(32), unique=False, nullable=False)
619
-    status = Column(Unicode(32), unique=False, nullable=False, default=ContentStatus.OPEN)
620
+    status = Column(Unicode(32), unique=False, nullable=False, default=str(CONTENT_STATUS.get_default_status().slug))
620
     created = Column(DateTime, unique=False, nullable=False, default=datetime.utcnow)
621
     created = Column(DateTime, unique=False, nullable=False, default=datetime.utcnow)
621
     updated = Column(DateTime, unique=False, nullable=False, default=datetime.utcnow)
622
     updated = Column(DateTime, unique=False, nullable=False, default=datetime.utcnow)
622
     is_deleted = Column(Boolean, unique=False, nullable=False, default=False)
623
     is_deleted = Column(Boolean, unique=False, nullable=False, default=False)
754
         super().__setattr__(key, value)
755
         super().__setattr__(key, value)
755
 
756
 
756
     def get_status(self) -> ContentStatus:
757
     def get_status(self) -> ContentStatus:
757
-        return ContentStatus(self.status)
758
+        return CONTENT_STATUS.get_one_by_slug(self.status)
758
 
759
 
759
     def get_label(self) -> str:
760
     def get_label(self) -> str:
760
         return self.label or self.file_name or ''
761
         return self.label or self.file_name or ''
779
     def get_label_as_file(self):
780
     def get_label_as_file(self):
780
         file_extension = self.file_extension or ''
781
         file_extension = self.file_extension or ''
781
 
782
 
782
-        if self.type == ContentType.Thread:
783
+        if self.type == CONTENT_TYPES.Thread.slug:
783
             file_extension = '.html'
784
             file_extension = '.html'
784
-        elif self.type == ContentType.Page:
785
+        elif self.type == CONTENT_TYPES.Page.slug:
785
             file_extension = '.html'
786
             file_extension = '.html'
786
 
787
 
787
         return '{0}{1}'.format(
788
         return '{0}{1}'.format(
1227
         return format_timedelta(delta_from_datetime - datetime_object,
1228
         return format_timedelta(delta_from_datetime - datetime_object,
1228
                                 locale=get_locale())
1229
                                 locale=get_locale())
1229
 
1230
 
1230
-    def get_child_nb(self, content_type: ContentType, content_status = ''):
1231
+    def get_child_nb(self, content_type: str, content_status = ''):
1231
         child_nb = 0
1232
         child_nb = 0
1232
         for child in self.get_valid_children():
1233
         for child in self.get_valid_children():
1233
-            if child.type == content_type or content_type == ContentType.Any:
1234
+            if child.type == content_type or content_type.slug == CONTENT_TYPES.Any_SLUG:
1234
                 if not content_status:
1235
                 if not content_status:
1235
                     child_nb = child_nb+1
1236
                     child_nb = child_nb+1
1236
                 elif content_status==child.status:
1237
                 elif content_status==child.status:
1247
         return self.revision.get_label_as_file()
1248
         return self.revision.get_label_as_file()
1248
 
1249
 
1249
     def get_status(self) -> ContentStatus:
1250
     def get_status(self) -> ContentStatus:
1250
-        return ContentStatus(
1251
+        return CONTENT_STATUS.get_one_by_slug(
1251
             self.status,
1252
             self.status,
1252
-            # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
1253
-            # self.type.__str__()
1254
         )
1253
         )
1255
 
1254
 
1256
     def get_last_action(self) -> ActionDescription:
1255
     def get_last_action(self) -> ActionDescription:
1307
     def get_comments(self):
1306
     def get_comments(self):
1308
         children = []
1307
         children = []
1309
         for child in self.children:
1308
         for child in self.children:
1310
-            if ContentType.Comment==child.type and not child.is_deleted and not child.is_archived:
1309
+            if CONTENT_TYPES.Comment.slug == child.type and not child.is_deleted and not child.is_archived:
1311
                 children.append(child.node)
1310
                 children.append(child.node)
1312
         return children
1311
         return children
1313
 
1312
 
1348
             allowed_types = self.properties['allowed_content']
1347
             allowed_types = self.properties['allowed_content']
1349
             for type_label, is_allowed in allowed_types.items():
1348
             for type_label, is_allowed in allowed_types.items():
1350
                 if is_allowed:
1349
                 if is_allowed:
1351
-                    types.append(ContentType(type_label))
1350
+                    types.append(CONTENT_TYPES.get_one_by_slug(type_label))
1352
         except Exception as e:
1351
         except Exception as e:
1353
             print(e.__str__())
1352
             print(e.__str__())
1354
             print('----- /*\ *****')
1353
             print('----- /*\ *****')
1355
             raise ValueError('Not allowed content property')
1354
             raise ValueError('Not allowed content property')
1356
 
1355
 
1357
-        return ContentType.sorted(types)
1356
+        return types
1358
 
1357
 
1359
     def get_history(self, drop_empty_revision=False) -> '[VirtualEvent]':
1358
     def get_history(self, drop_empty_revision=False) -> '[VirtualEvent]':
1360
         events = []
1359
         events = []
1447
 
1446
 
1448
     @classmethod
1447
     @classmethod
1449
     def create_from_content(cls, content: Content):
1448
     def create_from_content(cls, content: Content):
1450
-        content_type = ContentType(content.type)
1451
 
1449
 
1452
         label = content.get_label()
1450
         label = content.get_label()
1453
-        if content.type == ContentType.Comment:
1451
+        if content.type == CONTENT_TYPES.Comment.slug:
1454
             # TODO - G.M  - 10-04-2018 - [Cleanup] Remove label param
1452
             # TODO - G.M  - 10-04-2018 - [Cleanup] Remove label param
1455
             # from this object ?
1453
             # from this object ?
1456
             label = l_('<strong>{}</strong> wrote:').format(content.owner.get_display_name())
1454
             label = l_('<strong>{}</strong> wrote:').format(content.owner.get_display_name())
1458
         return VirtualEvent(id=content.content_id,
1456
         return VirtualEvent(id=content.content_id,
1459
                             created=content.created,
1457
                             created=content.created,
1460
                             owner=content.owner,
1458
                             owner=content.owner,
1461
-                            type=content_type,
1459
+                            type=ActionDescription(content.revision_type),
1462
                             label=label,
1460
                             label=label,
1463
                             content=content.description,
1461
                             content=content.description,
1464
                             ref_object=content)
1462
                             ref_object=content)

+ 4 - 4
backend/tracim_backend/tests/__init__.py View File

14
 from tracim_backend.models import DeclarativeBase
14
 from tracim_backend.models import DeclarativeBase
15
 from tracim_backend.models import get_session_factory
15
 from tracim_backend.models import get_session_factory
16
 from tracim_backend.models import get_tm_session
16
 from tracim_backend.models import get_tm_session
17
+from tracim_backend.models.contents import CONTENT_TYPES
17
 from tracim_backend.models.data import Workspace
18
 from tracim_backend.models.data import Workspace
18
-from tracim_backend.models.data import ContentType
19
 from tracim_backend.models.data import ContentRevisionRO
19
 from tracim_backend.models.data import ContentRevisionRO
20
 from tracim_backend.models.data import Content
20
 from tracim_backend.models.data import Content
21
 from tracim_backend.lib.utils.logger import logger
21
 from tracim_backend.lib.utils.logger import logger
50
     )
50
     )
51
     content_query = dbsession.query(ContentRevisionRO).filter(ContentRevisionRO.type == 'page').filter(ContentRevisionRO.content_id == 6)  # nopep8
51
     content_query = dbsession.query(ContentRevisionRO).filter(ContentRevisionRO.type == 'page').filter(ContentRevisionRO.content_id == 6)  # nopep8
52
     assert content_query.count() == 0
52
     assert content_query.count() == 0
53
-    html_documents_query = dbsession.query(ContentRevisionRO).filter(ContentRevisionRO.type == 'html-documents')  # nopep8
53
+    html_documents_query = dbsession.query(ContentRevisionRO).filter(ContentRevisionRO.type == 'html-document')  # nopep8
54
     html_documents_query.update({ContentRevisionRO.type: 'page'})
54
     html_documents_query.update({ContentRevisionRO.type: 'page'})
55
     transaction.commit()
55
     transaction.commit()
56
     assert content_query.count() > 0
56
     assert content_query.count() > 0
268
         workspace = self._create_workspace_and_test(workspace_name, user)
268
         workspace = self._create_workspace_and_test(workspace_name, user)
269
         folder = self._create_content_and_test(
269
         folder = self._create_content_and_test(
270
             folder_name, workspace,
270
             folder_name, workspace,
271
-            type=ContentType.Folder,
271
+            type=CONTENT_TYPES.Folder.slug,
272
             owner=user
272
             owner=user
273
         )
273
         )
274
         thread = self._create_content_and_test(
274
         thread = self._create_content_and_test(
275
             thread_name,
275
             thread_name,
276
             workspace,
276
             workspace,
277
-            type=ContentType.Thread,
277
+            type=CONTENT_TYPES.Thread.slug,
278
             parent=folder,
278
             parent=folder,
279
             owner=user
279
             owner=user
280
         )
280
         )

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

5
 from tracim_backend.lib.core.content import ContentApi
5
 from tracim_backend.lib.core.content import ContentApi
6
 from tracim_backend.lib.core.workspace import WorkspaceApi
6
 from tracim_backend.lib.core.workspace import WorkspaceApi
7
 from tracim_backend.models import get_tm_session
7
 from tracim_backend.models import get_tm_session
8
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
8
+from tracim_backend.models.contents import CONTENT_TYPES
9
 from tracim_backend.models.revision_protection import new_revision
9
 from tracim_backend.models.revision_protection import new_revision
10
 import io
10
 import io
11
 
11
 
16
 from tracim_backend import models
16
 from tracim_backend import models
17
 from tracim_backend.lib.core.content import ContentApi
17
 from tracim_backend.lib.core.content import ContentApi
18
 from tracim_backend.lib.core.workspace import WorkspaceApi
18
 from tracim_backend.lib.core.workspace import WorkspaceApi
19
-from tracim_backend.models.data import ContentType
20
 from tracim_backend.models import get_tm_session
19
 from tracim_backend.models import get_tm_session
21
 from tracim_backend.models.revision_protection import new_revision
20
 from tracim_backend.models.revision_protection import new_revision
22
 from tracim_backend.tests import FunctionalTest
21
 from tracim_backend.tests import FunctionalTest
51
             status=200
50
             status=200
52
         )
51
         )
53
         content = res.json_body
52
         content = res.json_body
54
-        assert content['content_type'] == 'html-documents'
53
+        assert content['content_type'] == 'html-document'
55
         assert content['content_id'] == 6
54
         assert content['content_id'] == 6
56
         assert content['is_archived'] is False
55
         assert content['is_archived'] is False
57
         assert content['is_deleted'] is False
56
         assert content['is_deleted'] is False
92
             status=200
91
             status=200
93
         )
92
         )
94
         content = res.json_body
93
         content = res.json_body
95
-        assert content['content_type'] == 'html-documents'
94
+        assert content['content_type'] == 'html-document'
96
         assert content['content_id'] == 6
95
         assert content['content_id'] == 6
97
         assert content['is_archived'] is False
96
         assert content['is_archived'] is False
98
         assert content['is_deleted'] is False
97
         assert content['is_deleted'] is False
255
             status=200
254
             status=200
256
         )
255
         )
257
         content = res.json_body
256
         content = res.json_body
258
-        assert content['content_type'] == 'html-documents'
257
+        assert content['content_type'] == 'html-document'
259
         assert content['content_id'] == 6
258
         assert content['content_id'] == 6
260
         assert content['is_archived'] is False
259
         assert content['is_archived'] is False
261
         assert content['is_deleted'] is False
260
         assert content['is_deleted'] is False
282
             status=200
281
             status=200
283
         )
282
         )
284
         content = res.json_body
283
         content = res.json_body
285
-        assert content['content_type'] == 'html-documents'
284
+        assert content['content_type'] == 'html-document'
286
         assert content['content_id'] == 6
285
         assert content['content_id'] == 6
287
         assert content['is_archived'] is False
286
         assert content['is_archived'] is False
288
         assert content['is_deleted'] is False
287
         assert content['is_deleted'] is False
324
         revisions = res.json_body
323
         revisions = res.json_body
325
         assert len(revisions) == 3
324
         assert len(revisions) == 3
326
         revision = revisions[0]
325
         revision = revisions[0]
327
-        assert revision['content_type'] == 'html-documents'
326
+        assert revision['content_type'] == 'html-document'
328
         assert revision['content_id'] == 6
327
         assert revision['content_id'] == 6
329
         assert revision['is_archived'] is False
328
         assert revision['is_archived'] is False
330
         assert revision['is_deleted'] is False
329
         assert revision['is_deleted'] is False
346
         assert revision['author']['avatar_url'] is None
345
         assert revision['author']['avatar_url'] is None
347
         assert revision['author']['public_name'] == 'Global manager'
346
         assert revision['author']['public_name'] == 'Global manager'
348
         revision = revisions[1]
347
         revision = revisions[1]
349
-        assert revision['content_type'] == 'html-documents'
348
+        assert revision['content_type'] == 'html-document'
350
         assert revision['content_id'] == 6
349
         assert revision['content_id'] == 6
351
         assert revision['is_archived'] is False
350
         assert revision['is_archived'] is False
352
         assert revision['is_deleted'] is False
351
         assert revision['is_deleted'] is False
368
         assert revision['author']['avatar_url'] is None
367
         assert revision['author']['avatar_url'] is None
369
         assert revision['author']['public_name'] == 'Global manager'
368
         assert revision['author']['public_name'] == 'Global manager'
370
         revision = revisions[2]
369
         revision = revisions[2]
371
-        assert revision['content_type'] == 'html-documents'
370
+        assert revision['content_type'] == 'html-document'
372
         assert revision['content_id'] == 6
371
         assert revision['content_id'] == 6
373
         assert revision['is_archived'] is False
372
         assert revision['is_archived'] is False
374
         assert revision['is_deleted'] is False
373
         assert revision['is_deleted'] is False
411
             status=200
410
             status=200
412
         )
411
         )
413
         content = res.json_body
412
         content = res.json_body
414
-        assert content['content_type'] == 'html-documents'
413
+        assert content['content_type'] == 'html-document'
415
         assert content['content_id'] == 6
414
         assert content['content_id'] == 6
416
         assert content['status'] == 'open'
415
         assert content['status'] == 'open'
417
 
416
 
428
             status=200
427
             status=200
429
         )
428
         )
430
         content = res.json_body
429
         content = res.json_body
431
-        assert content['content_type'] == 'html-documents'
430
+        assert content['content_type'] == 'html-document'
432
         assert content['content_id'] == 6
431
         assert content['content_id'] == 6
433
         assert content['status'] == 'closed-deprecated'
432
         assert content['status'] == 'closed-deprecated'
434
 
433
 
480
             config=self.app_config
479
             config=self.app_config
481
         )
480
         )
482
         business_workspace = workspace_api.get_one(1)
481
         business_workspace = workspace_api.get_one(1)
483
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
482
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
484
         test_file = content_api.create(
483
         test_file = content_api.create(
485
-            content_type=ContentType.File,
484
+            content_type_slug=CONTENT_TYPES.File.slug,
486
             workspace=business_workspace,
485
             workspace=business_workspace,
487
             parent=tool_folder,
486
             parent=tool_folder,
488
             label='Test file',
487
             label='Test file',
648
             config=self.app_config
647
             config=self.app_config
649
         )
648
         )
650
         business_workspace = workspace_api.get_one(1)
649
         business_workspace = workspace_api.get_one(1)
651
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
650
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
652
         test_file = content_api.create(
651
         test_file = content_api.create(
653
-            content_type=ContentType.File,
652
+            content_type_slug=CONTENT_TYPES.File.slug,
654
             workspace=business_workspace,
653
             workspace=business_workspace,
655
             parent=tool_folder,
654
             parent=tool_folder,
656
             label='Test file',
655
             label='Test file',
703
             config=self.app_config
702
             config=self.app_config
704
         )
703
         )
705
         business_workspace = workspace_api.get_one(1)
704
         business_workspace = workspace_api.get_one(1)
706
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
705
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
707
         test_file = content_api.create(
706
         test_file = content_api.create(
708
-            content_type=ContentType.File,
707
+            content_type_slug=CONTENT_TYPES.File.slug,
709
             workspace=business_workspace,
708
             workspace=business_workspace,
710
             parent=tool_folder,
709
             parent=tool_folder,
711
             label='Test file',
710
             label='Test file',
809
             config=self.app_config
808
             config=self.app_config
810
         )
809
         )
811
         business_workspace = workspace_api.get_one(1)
810
         business_workspace = workspace_api.get_one(1)
812
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
811
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
813
         test_file = content_api.create(
812
         test_file = content_api.create(
814
-            content_type=ContentType.File,
813
+            content_type_slug=CONTENT_TYPES.File.slug,
815
             workspace=business_workspace,
814
             workspace=business_workspace,
816
             parent=tool_folder,
815
             parent=tool_folder,
817
             label='Test file',
816
             label='Test file',
882
             config=self.app_config
881
             config=self.app_config
883
         )
882
         )
884
         business_workspace = workspace_api.get_one(1)
883
         business_workspace = workspace_api.get_one(1)
885
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
884
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
886
         test_file = content_api.create(
885
         test_file = content_api.create(
887
-            content_type=ContentType.File,
886
+            content_type_slug=CONTENT_TYPES.File.slug,
888
             workspace=business_workspace,
887
             workspace=business_workspace,
889
             parent=tool_folder,
888
             parent=tool_folder,
890
             label='Test file',
889
             label='Test file',
978
             config=self.app_config
977
             config=self.app_config
979
         )
978
         )
980
         business_workspace = workspace_api.get_one(1)
979
         business_workspace = workspace_api.get_one(1)
981
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
980
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
982
         test_file = content_api.create(
981
         test_file = content_api.create(
983
-            content_type=ContentType.File,
982
+            content_type_slug=CONTENT_TYPES.File.slug,
984
             workspace=business_workspace,
983
             workspace=business_workspace,
985
             parent=tool_folder,
984
             parent=tool_folder,
986
             label='Test file',
985
             label='Test file',
1031
             config=self.app_config
1030
             config=self.app_config
1032
         )
1031
         )
1033
         business_workspace = workspace_api.get_one(1)
1032
         business_workspace = workspace_api.get_one(1)
1034
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1033
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1035
         test_file = content_api.create(
1034
         test_file = content_api.create(
1036
-            content_type=ContentType.File,
1035
+            content_type_slug=CONTENT_TYPES.File.slug,
1037
             workspace=business_workspace,
1036
             workspace=business_workspace,
1038
             parent=tool_folder,
1037
             parent=tool_folder,
1039
             label='Test file',
1038
             label='Test file',
1082
             config=self.app_config
1081
             config=self.app_config
1083
         )
1082
         )
1084
         business_workspace = workspace_api.get_one(1)
1083
         business_workspace = workspace_api.get_one(1)
1085
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1084
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1086
         test_file = content_api.create(
1085
         test_file = content_api.create(
1087
-            content_type=ContentType.File,
1086
+            content_type_slug=CONTENT_TYPES.File.slug,
1088
             workspace=business_workspace,
1087
             workspace=business_workspace,
1089
             parent=tool_folder,
1088
             parent=tool_folder,
1090
             label='Test file',
1089
             label='Test file',
1137
             config=self.app_config
1136
             config=self.app_config
1138
         )
1137
         )
1139
         business_workspace = workspace_api.get_one(1)
1138
         business_workspace = workspace_api.get_one(1)
1140
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1139
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1141
         test_file = content_api.create(
1140
         test_file = content_api.create(
1142
-            content_type=ContentType.File,
1141
+            content_type_slug=CONTENT_TYPES.File.slug,
1143
             workspace=business_workspace,
1142
             workspace=business_workspace,
1144
             parent=tool_folder,
1143
             parent=tool_folder,
1145
             label='Test file',
1144
             label='Test file',
1196
             config=self.app_config
1195
             config=self.app_config
1197
         )
1196
         )
1198
         business_workspace = workspace_api.get_one(1)
1197
         business_workspace = workspace_api.get_one(1)
1199
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1198
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1200
         test_file = content_api.create(
1199
         test_file = content_api.create(
1201
-            content_type=ContentType.File,
1200
+            content_type_slug=CONTENT_TYPES.File.slug,
1202
             workspace=business_workspace,
1201
             workspace=business_workspace,
1203
             parent=tool_folder,
1202
             parent=tool_folder,
1204
             label='Test file',
1203
             label='Test file',
1251
             config=self.app_config
1250
             config=self.app_config
1252
         )
1251
         )
1253
         business_workspace = workspace_api.get_one(1)
1252
         business_workspace = workspace_api.get_one(1)
1254
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1253
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1255
         test_file = content_api.create(
1254
         test_file = content_api.create(
1256
-            content_type=ContentType.File,
1255
+            content_type_slug=CONTENT_TYPES.File.slug,
1257
             workspace=business_workspace,
1256
             workspace=business_workspace,
1258
             parent=tool_folder,
1257
             parent=tool_folder,
1259
             label='Test file',
1258
             label='Test file',
1302
             config=self.app_config
1301
             config=self.app_config
1303
         )
1302
         )
1304
         business_workspace = workspace_api.get_one(1)
1303
         business_workspace = workspace_api.get_one(1)
1305
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1304
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1306
         test_file = content_api.create(
1305
         test_file = content_api.create(
1307
-            content_type=ContentType.File,
1306
+            content_type_slug=CONTENT_TYPES.File.slug,
1308
             workspace=business_workspace,
1307
             workspace=business_workspace,
1309
             parent=tool_folder,
1308
             parent=tool_folder,
1310
             label='Test file',
1309
             label='Test file',
1375
             config=self.app_config
1374
             config=self.app_config
1376
         )
1375
         )
1377
         business_workspace = workspace_api.get_one(1)
1376
         business_workspace = workspace_api.get_one(1)
1378
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1377
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1379
         test_file = content_api.create(
1378
         test_file = content_api.create(
1380
-            content_type=ContentType.File,
1379
+            content_type_slug=CONTENT_TYPES.File.slug,
1381
             workspace=business_workspace,
1380
             workspace=business_workspace,
1382
             parent=tool_folder,
1381
             parent=tool_folder,
1383
             label='Test file',
1382
             label='Test file',
1438
             config=self.app_config
1437
             config=self.app_config
1439
         )
1438
         )
1440
         business_workspace = workspace_api.get_one(1)
1439
         business_workspace = workspace_api.get_one(1)
1441
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1440
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1442
         test_file = content_api.create(
1441
         test_file = content_api.create(
1443
-            content_type=ContentType.File,
1442
+            content_type_slug=CONTENT_TYPES.File.slug,
1444
             workspace=business_workspace,
1443
             workspace=business_workspace,
1445
             parent=tool_folder,
1444
             parent=tool_folder,
1446
             label='Test file',
1445
             label='Test file',
1489
             config=self.app_config
1488
             config=self.app_config
1490
         )
1489
         )
1491
         business_workspace = workspace_api.get_one(1)
1490
         business_workspace = workspace_api.get_one(1)
1492
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1491
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1493
         test_file = content_api.create(
1492
         test_file = content_api.create(
1494
-            content_type=ContentType.File,
1493
+            content_type_slug=CONTENT_TYPES.File.slug,
1495
             workspace=business_workspace,
1494
             workspace=business_workspace,
1496
             parent=tool_folder,
1495
             parent=tool_folder,
1497
             label='Test file',
1496
             label='Test file',
1554
             config=self.app_config
1553
             config=self.app_config
1555
         )
1554
         )
1556
         business_workspace = workspace_api.get_one(1)
1555
         business_workspace = workspace_api.get_one(1)
1557
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1556
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1558
         test_file = content_api.create(
1557
         test_file = content_api.create(
1559
-            content_type=ContentType.File,
1558
+            content_type_slug=CONTENT_TYPES.File.slug,
1560
             workspace=business_workspace,
1559
             workspace=business_workspace,
1561
             parent=tool_folder,
1560
             parent=tool_folder,
1562
             label='Test file',
1561
             label='Test file',
1618
             config=self.app_config
1617
             config=self.app_config
1619
         )
1618
         )
1620
         business_workspace = workspace_api.get_one(1)
1619
         business_workspace = workspace_api.get_one(1)
1621
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1620
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1622
         test_file = content_api.create(
1621
         test_file = content_api.create(
1623
-            content_type=ContentType.File,
1622
+            content_type_slug=CONTENT_TYPES.File.slug,
1624
             workspace=business_workspace,
1623
             workspace=business_workspace,
1625
             parent=tool_folder,
1624
             parent=tool_folder,
1626
             label='Test file',
1625
             label='Test file',
1989
             session=dbsession,
1988
             session=dbsession,
1990
             config=self.app_config
1989
             config=self.app_config
1991
         )
1990
         )
1992
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1991
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1993
         test_thread = content_api.create(
1992
         test_thread = content_api.create(
1994
-            content_type=ContentType.Thread,
1993
+            content_type_slug=CONTENT_TYPES.Thread.slug,
1995
             workspace=business_workspace,
1994
             workspace=business_workspace,
1996
             parent=tool_folder,
1995
             parent=tool_folder,
1997
             label='Test Thread',
1996
             label='Test Thread',

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

11
 from tracim_backend.fixtures.content import Content as ContentFixture
11
 from tracim_backend.fixtures.content import Content as ContentFixture
12
 from tracim_backend.lib.utils.utils import get_redis_connection
12
 from tracim_backend.lib.utils.utils import get_redis_connection
13
 from tracim_backend.lib.utils.utils import get_rq_queue
13
 from tracim_backend.lib.utils.utils import get_rq_queue
14
-from tracim_backend.models.data import ContentType
14
+from tracim_backend.models.contents import CONTENT_TYPES
15
 
15
 
16
 from tracim_backend.lib.core.content import ContentApi
16
 from tracim_backend.lib.core.content import ContentApi
17
 from tracim_backend.lib.core.user import UserApi
17
 from tracim_backend.lib.core.user import UserApi
139
             config=self.app_config,
139
             config=self.app_config,
140
         )
140
         )
141
         item = api.create(
141
         item = api.create(
142
-            ContentType.Folder,
142
+            CONTENT_TYPES.Folder.slug,
143
             workspace,
143
             workspace,
144
             None,
144
             None,
145
             'parent',
145
             'parent',
147
             do_notify=False,
147
             do_notify=False,
148
         )
148
         )
149
         item2 = api.create(
149
         item2 = api.create(
150
-            ContentType.File,
150
+            CONTENT_TYPES.File.slug,
151
             workspace,
151
             workspace,
152
             item,
152
             item,
153
             'file1',
153
             'file1',
231
             config=self.app_config,
231
             config=self.app_config,
232
         )
232
         )
233
         item = api.create(
233
         item = api.create(
234
-            ContentType.Folder,
234
+            CONTENT_TYPES.Folder.slug,
235
             workspace,
235
             workspace,
236
             None,
236
             None,
237
             'parent',
237
             'parent',
239
             do_notify=False,
239
             do_notify=False,
240
         )
240
         )
241
         item2 = api.create(
241
         item2 = api.create(
242
-            ContentType.File,
242
+            CONTENT_TYPES.File.slug,
243
             workspace,
243
             workspace,
244
             item,
244
             item,
245
             'file1',
245
             'file1',

+ 9 - 8
backend/tracim_backend/tests/functional/test_system.py View File

5
 Tests for /api/v2/system subpath endpoints.
5
 Tests for /api/v2/system subpath endpoints.
6
 """
6
 """
7
 
7
 
8
+
8
 class TestApplicationEndpoint(FunctionalTest):
9
 class TestApplicationEndpoint(FunctionalTest):
9
     """
10
     """
10
     Tests for /api/v2/system/applications
11
     Tests for /api/v2/system/applications
25
         res = res.json_body
26
         res = res.json_body
26
         application = res[0]
27
         application = res[0]
27
         assert application['label'] == "Text Documents"
28
         assert application['label'] == "Text Documents"
28
-        assert application['slug'] == 'contents/html-documents'
29
+        assert application['slug'] == 'contents/html-document'
29
         assert application['fa_icon'] == 'file-text-o'
30
         assert application['fa_icon'] == 'file-text-o'
30
         assert application['hexcolor'] == '#3f52e3'
31
         assert application['hexcolor'] == '#3f52e3'
31
         assert application['is_active'] is True
32
         assert application['is_active'] is True
39
         assert 'config' in application
40
         assert 'config' in application
40
         application = res[2]
41
         application = res[2]
41
         assert application['label'] == "Files"
42
         assert application['label'] == "Files"
42
-        assert application['slug'] == 'contents/files'
43
+        assert application['slug'] == 'contents/file'
43
         assert application['fa_icon'] == 'paperclip'
44
         assert application['fa_icon'] == 'paperclip'
44
         assert application['hexcolor'] == '#FF9900'
45
         assert application['hexcolor'] == '#FF9900'
45
         assert application['is_active'] is True
46
         assert application['is_active'] is True
46
         assert 'config' in application
47
         assert 'config' in application
47
         application = res[3]
48
         application = res[3]
48
         assert application['label'] == "Threads"
49
         assert application['label'] == "Threads"
49
-        assert application['slug'] == 'contents/threads'
50
+        assert application['slug'] == 'contents/thread'
50
         assert application['fa_icon'] == 'comments-o'
51
         assert application['fa_icon'] == 'comments-o'
51
         assert application['hexcolor'] == '#ad4cf9'
52
         assert application['hexcolor'] == '#ad4cf9'
52
         assert application['is_active'] is True
53
         assert application['is_active'] is True
96
         res = self.testapp.get('/api/v2/system/content_types', status=200)
97
         res = self.testapp.get('/api/v2/system/content_types', status=200)
97
         res = res.json_body
98
         res = res.json_body
98
 
99
 
99
-        content_type = res[0]
100
+        content_type = res[1]
100
         assert content_type['slug'] == 'thread'
101
         assert content_type['slug'] == 'thread'
101
         assert content_type['fa_icon'] == 'comments-o'
102
         assert content_type['fa_icon'] == 'comments-o'
102
         assert content_type['hexcolor'] == '#ad4cf9'
103
         assert content_type['hexcolor'] == '#ad4cf9'
105
         assert 'available_statuses' in content_type
106
         assert 'available_statuses' in content_type
106
         assert len(content_type['available_statuses']) == 4
107
         assert len(content_type['available_statuses']) == 4
107
 
108
 
108
-        content_type = res[1]
109
+        content_type = res[2]
109
         assert content_type['slug'] == 'file'
110
         assert content_type['slug'] == 'file'
110
         assert content_type['fa_icon'] == 'paperclip'
111
         assert content_type['fa_icon'] == 'paperclip'
111
         assert content_type['hexcolor'] == '#FF9900'
112
         assert content_type['hexcolor'] == '#FF9900'
114
         assert 'available_statuses' in content_type
115
         assert 'available_statuses' in content_type
115
         assert len(content_type['available_statuses']) == 4
116
         assert len(content_type['available_statuses']) == 4
116
 
117
 
117
-        content_type = res[2]
118
+        content_type = res[3]
118
         assert content_type['slug'] == 'markdownpage'
119
         assert content_type['slug'] == 'markdownpage'
119
         assert content_type['fa_icon'] == 'file-code-o'
120
         assert content_type['fa_icon'] == 'file-code-o'
120
         assert content_type['hexcolor'] == '#f12d2d'
121
         assert content_type['hexcolor'] == '#f12d2d'
123
         assert 'available_statuses' in content_type
124
         assert 'available_statuses' in content_type
124
         assert len(content_type['available_statuses']) == 4
125
         assert len(content_type['available_statuses']) == 4
125
 
126
 
126
-        content_type = res[3]
127
-        assert content_type['slug'] == 'html-documents'
127
+        content_type = res[4]
128
+        assert content_type['slug'] == 'html-document'
128
         assert content_type['fa_icon'] == 'file-text-o'
129
         assert content_type['fa_icon'] == 'file-text-o'
129
         assert content_type['hexcolor'] == '#3f52e3'
130
         assert content_type['hexcolor'] == '#3f52e3'
130
         assert content_type['label'] == 'Text Document'
131
         assert content_type['label'] == 'Text Document'

File diff suppressed because it is too large
+ 1859 - 224
backend/tracim_backend/tests/functional/test_user.py


+ 125 - 60
backend/tracim_backend/tests/functional/test_workspaces.py View File

10
 from tracim_backend.lib.core.content import ContentApi
10
 from tracim_backend.lib.core.content import ContentApi
11
 from tracim_backend.lib.core.workspace import WorkspaceApi
11
 from tracim_backend.lib.core.workspace import WorkspaceApi
12
 from tracim_backend.models import get_tm_session
12
 from tracim_backend.models import get_tm_session
13
-from tracim_backend.models.data import ContentType
13
+from tracim_backend.models.contents import CONTENT_TYPES
14
 from tracim_backend.tests import FunctionalTest
14
 from tracim_backend.tests import FunctionalTest
15
 from tracim_backend.tests import set_html_document_slug_to_legacy
15
 from tracim_backend.tests import set_html_document_slug_to_legacy
16
 from tracim_backend.fixtures.content import Content as ContentFixtures
16
 from tracim_backend.fixtures.content import Content as ContentFixtures
58
         assert sidebar_entry['fa_icon'] == "th"
58
         assert sidebar_entry['fa_icon'] == "th"
59
 
59
 
60
         sidebar_entry = workspace['sidebar_entries'][2]
60
         sidebar_entry = workspace['sidebar_entries'][2]
61
-        assert sidebar_entry['slug'] == 'contents/html-documents'
61
+        assert sidebar_entry['slug'] == 'contents/html-document'
62
         assert sidebar_entry['label'] == 'Text Documents'
62
         assert sidebar_entry['label'] == 'Text Documents'
63
-        assert sidebar_entry['route'] == '/#/workspaces/1/contents?type=html-documents'  # nopep8
63
+        assert sidebar_entry['route'] == '/#/workspaces/1/contents?type=html-document'  # nopep8
64
         assert sidebar_entry['hexcolor'] == "#3f52e3"
64
         assert sidebar_entry['hexcolor'] == "#3f52e3"
65
         assert sidebar_entry['fa_icon'] == "file-text-o"
65
         assert sidebar_entry['fa_icon'] == "file-text-o"
66
 
66
 
72
         assert sidebar_entry['fa_icon'] == "file-code-o"
72
         assert sidebar_entry['fa_icon'] == "file-code-o"
73
 
73
 
74
         sidebar_entry = workspace['sidebar_entries'][4]
74
         sidebar_entry = workspace['sidebar_entries'][4]
75
-        assert sidebar_entry['slug'] == 'contents/files'
75
+        assert sidebar_entry['slug'] == 'contents/file'
76
         assert sidebar_entry['label'] == 'Files'
76
         assert sidebar_entry['label'] == 'Files'
77
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
77
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
78
         assert sidebar_entry['hexcolor'] == "#FF9900"
78
         assert sidebar_entry['hexcolor'] == "#FF9900"
79
         assert sidebar_entry['fa_icon'] == "paperclip"
79
         assert sidebar_entry['fa_icon'] == "paperclip"
80
 
80
 
81
         sidebar_entry = workspace['sidebar_entries'][5]
81
         sidebar_entry = workspace['sidebar_entries'][5]
82
-        assert sidebar_entry['slug'] == 'contents/threads'
82
+        assert sidebar_entry['slug'] == 'contents/thread'
83
         assert sidebar_entry['label'] == 'Threads'
83
         assert sidebar_entry['label'] == 'Threads'
84
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
84
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
85
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
85
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
643
         assert content['show_in_ui'] is True
643
         assert content['show_in_ui'] is True
644
         assert content['slug'] == 'tools'
644
         assert content['slug'] == 'tools'
645
         assert content['status'] == 'open'
645
         assert content['status'] == 'open'
646
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
646
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
647
         assert content['workspace_id'] == 1
647
         assert content['workspace_id'] == 1
648
         content = res[1]
648
         content = res[1]
649
         assert content['content_id'] == 2
649
         assert content['content_id'] == 2
655
         assert content['show_in_ui'] is True
655
         assert content['show_in_ui'] is True
656
         assert content['slug'] == 'menus'
656
         assert content['slug'] == 'menus'
657
         assert content['status'] == 'open'
657
         assert content['status'] == 'open'
658
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
658
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
659
         assert content['workspace_id'] == 1
659
         assert content['workspace_id'] == 1
660
         content = res[2]
660
         content = res[2]
661
         assert content['content_id'] == 11
661
         assert content['content_id'] == 11
662
-        assert content['content_type'] == 'html-documents'
662
+        assert content['content_type'] == 'html-document'
663
         assert content['is_archived'] is False
663
         assert content['is_archived'] is False
664
         assert content['is_deleted'] is False
664
         assert content['is_deleted'] is False
665
         assert content['label'] == 'Current Menu'
665
         assert content['label'] == 'Current Menu'
667
         assert content['show_in_ui'] is True
667
         assert content['show_in_ui'] is True
668
         assert content['slug'] == 'current-menu'
668
         assert content['slug'] == 'current-menu'
669
         assert content['status'] == 'open'
669
         assert content['status'] == 'open'
670
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
670
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
671
         assert content['workspace_id'] == 1
671
         assert content['workspace_id'] == 1
672
 
672
 
673
     def test_api__get_workspace_content__ok_200__get_default_html_documents(self):
673
     def test_api__get_workspace_content__ok_200__get_default_html_documents(self):
682
             )
682
             )
683
         )
683
         )
684
         params = {
684
         params = {
685
-            'content_type': 'html-documents',
685
+            'content_type': 'html-document',
686
         }
686
         }
687
         res = self.testapp.get('/api/v2/workspaces/1/contents', status=200, params=params).json_body   # nopep8
687
         res = self.testapp.get('/api/v2/workspaces/1/contents', status=200, params=params).json_body   # nopep8
688
         assert len(res) == 1
688
         assert len(res) == 1
689
         content = res[0]
689
         content = res[0]
690
         assert content
690
         assert content
691
         assert content['content_id'] == 11
691
         assert content['content_id'] == 11
692
-        assert content['content_type'] == 'html-documents'
692
+        assert content['content_type'] == 'html-document'
693
         assert content['is_archived'] is False
693
         assert content['is_archived'] is False
694
         assert content['is_deleted'] is False
694
         assert content['is_deleted'] is False
695
         assert content['label'] == 'Current Menu'
695
         assert content['label'] == 'Current Menu'
697
         assert content['show_in_ui'] is True
697
         assert content['show_in_ui'] is True
698
         assert content['slug'] == 'current-menu'
698
         assert content['slug'] == 'current-menu'
699
         assert content['status'] == 'open'
699
         assert content['status'] == 'open'
700
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
700
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
701
         assert content['workspace_id'] == 1
701
         assert content['workspace_id'] == 1
702
 
702
 
703
     # Root related
703
     # Root related
727
         # TODO - G.M - 30-05-2018 - Check this test
727
         # TODO - G.M - 30-05-2018 - Check this test
728
         assert len(res) == 4
728
         assert len(res) == 4
729
         content = res[1]
729
         content = res[1]
730
-        assert content['content_type'] == 'html-documents'
730
+        assert content['content_type'] == 'html-document'
731
         assert content['content_id'] == 15
731
         assert content['content_id'] == 15
732
         assert content['is_archived'] is False
732
         assert content['is_archived'] is False
733
         assert content['is_deleted'] is False
733
         assert content['is_deleted'] is False
736
         assert content['show_in_ui'] is True
736
         assert content['show_in_ui'] is True
737
         assert content['slug'] == 'new-fruit-salad'
737
         assert content['slug'] == 'new-fruit-salad'
738
         assert content['status'] == 'open'
738
         assert content['status'] == 'open'
739
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
739
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
740
         assert content['workspace_id'] == 3
740
         assert content['workspace_id'] == 3
741
 
741
 
742
         content = res[2]
742
         content = res[2]
743
-        assert content['content_type'] == 'html-documents'
743
+        assert content['content_type'] == 'html-document'
744
         assert content['content_id'] == 16
744
         assert content['content_id'] == 16
745
         assert content['is_archived'] is True
745
         assert content['is_archived'] is True
746
         assert content['is_deleted'] is False
746
         assert content['is_deleted'] is False
749
         assert content['show_in_ui'] is True
749
         assert content['show_in_ui'] is True
750
         assert content['slug'].startswith('fruit-salad')
750
         assert content['slug'].startswith('fruit-salad')
751
         assert content['status'] == 'open'
751
         assert content['status'] == 'open'
752
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
752
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
753
         assert content['workspace_id'] == 3
753
         assert content['workspace_id'] == 3
754
 
754
 
755
         content = res[3]
755
         content = res[3]
756
-        assert content['content_type'] == 'html-documents'
756
+        assert content['content_type'] == 'html-document'
757
         assert content['content_id'] == 17
757
         assert content['content_id'] == 17
758
         assert content['is_archived'] is False
758
         assert content['is_archived'] is False
759
         assert content['is_deleted'] is True
759
         assert content['is_deleted'] is True
762
         assert content['show_in_ui'] is True
762
         assert content['show_in_ui'] is True
763
         assert content['slug'].startswith('bad-fruit-salad')
763
         assert content['slug'].startswith('bad-fruit-salad')
764
         assert content['status'] == 'open'
764
         assert content['status'] == 'open'
765
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
765
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
766
         assert content['workspace_id'] == 3
766
         assert content['workspace_id'] == 3
767
 
767
 
768
     def test_api__get_workspace_content__ok_200__get_all_root_content(self):
768
     def test_api__get_workspace_content__ok_200__get_all_root_content(self):
790
         # TODO - G.M - 30-05-2018 - Check this test
790
         # TODO - G.M - 30-05-2018 - Check this test
791
         assert len(res) == 4
791
         assert len(res) == 4
792
         content = res[1]
792
         content = res[1]
793
-        assert content['content_type'] == 'html-documents'
793
+        assert content['content_type'] == 'html-document'
794
         assert content['content_id'] == 15
794
         assert content['content_id'] == 15
795
         assert content['is_archived'] is False
795
         assert content['is_archived'] is False
796
         assert content['is_deleted'] is False
796
         assert content['is_deleted'] is False
799
         assert content['show_in_ui'] is True
799
         assert content['show_in_ui'] is True
800
         assert content['slug'] == 'new-fruit-salad'
800
         assert content['slug'] == 'new-fruit-salad'
801
         assert content['status'] == 'open'
801
         assert content['status'] == 'open'
802
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
802
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
803
         assert content['workspace_id'] == 3
803
         assert content['workspace_id'] == 3
804
 
804
 
805
         content = res[2]
805
         content = res[2]
806
-        assert content['content_type'] == 'html-documents'
806
+        assert content['content_type'] == 'html-document'
807
         assert content['content_id'] == 16
807
         assert content['content_id'] == 16
808
         assert content['is_archived'] is True
808
         assert content['is_archived'] is True
809
         assert content['is_deleted'] is False
809
         assert content['is_deleted'] is False
812
         assert content['show_in_ui'] is True
812
         assert content['show_in_ui'] is True
813
         assert content['slug'].startswith('fruit-salad')
813
         assert content['slug'].startswith('fruit-salad')
814
         assert content['status'] == 'open'
814
         assert content['status'] == 'open'
815
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
815
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
816
         assert content['workspace_id'] == 3
816
         assert content['workspace_id'] == 3
817
 
817
 
818
         content = res[3]
818
         content = res[3]
819
-        assert content['content_type'] == 'html-documents'
819
+        assert content['content_type'] == 'html-document'
820
         assert content['content_id'] == 17
820
         assert content['content_id'] == 17
821
         assert content['is_archived'] is False
821
         assert content['is_archived'] is False
822
         assert content['is_deleted'] is True
822
         assert content['is_deleted'] is True
825
         assert content['show_in_ui'] is True
825
         assert content['show_in_ui'] is True
826
         assert content['slug'].startswith('bad-fruit-salad')
826
         assert content['slug'].startswith('bad-fruit-salad')
827
         assert content['status'] == 'open'
827
         assert content['status'] == 'open'
828
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
828
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
829
         assert content['workspace_id'] == 3
829
         assert content['workspace_id'] == 3
830
 
830
 
831
     def test_api__get_workspace_content__ok_200__get_only_active_root_content(self):  # nopep8
831
     def test_api__get_workspace_content__ok_200__get_only_active_root_content(self):  # nopep8
853
         # TODO - G.M - 30-05-2018 - Check this test
853
         # TODO - G.M - 30-05-2018 - Check this test
854
         assert len(res) == 2
854
         assert len(res) == 2
855
         content = res[1]
855
         content = res[1]
856
-        assert content['content_type'] == 'html-documents'
856
+        assert content['content_type'] == 'html-document'
857
         assert content['content_id'] == 15
857
         assert content['content_id'] == 15
858
         assert content['is_archived'] is False
858
         assert content['is_archived'] is False
859
         assert content['is_deleted'] is False
859
         assert content['is_deleted'] is False
862
         assert content['show_in_ui'] is True
862
         assert content['show_in_ui'] is True
863
         assert content['slug'] == 'new-fruit-salad'
863
         assert content['slug'] == 'new-fruit-salad'
864
         assert content['status'] == 'open'
864
         assert content['status'] == 'open'
865
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
865
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
866
         assert content['workspace_id'] == 3
866
         assert content['workspace_id'] == 3
867
 
867
 
868
     def test_api__get_workspace_content__ok_200__get_only_archived_root_content(self):  # nopep8
868
     def test_api__get_workspace_content__ok_200__get_only_archived_root_content(self):  # nopep8
889
         ).json_body   # nopep8
889
         ).json_body   # nopep8
890
         assert len(res) == 1
890
         assert len(res) == 1
891
         content = res[0]
891
         content = res[0]
892
-        assert content['content_type'] == 'html-documents'
892
+        assert content['content_type'] == 'html-document'
893
         assert content['content_id'] == 16
893
         assert content['content_id'] == 16
894
         assert content['is_archived'] is True
894
         assert content['is_archived'] is True
895
         assert content['is_deleted'] is False
895
         assert content['is_deleted'] is False
898
         assert content['show_in_ui'] is True
898
         assert content['show_in_ui'] is True
899
         assert content['slug'].startswith('fruit-salad')
899
         assert content['slug'].startswith('fruit-salad')
900
         assert content['status'] == 'open'
900
         assert content['status'] == 'open'
901
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
901
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
902
         assert content['workspace_id'] == 3
902
         assert content['workspace_id'] == 3
903
 
903
 
904
     def test_api__get_workspace_content__ok_200__get_only_deleted_root_content(self):  # nopep8
904
     def test_api__get_workspace_content__ok_200__get_only_deleted_root_content(self):  # nopep8
927
 
927
 
928
         assert len(res) == 1
928
         assert len(res) == 1
929
         content = res[0]
929
         content = res[0]
930
-        assert content['content_type'] == 'html-documents'
930
+        assert content['content_type'] == 'html-document'
931
         assert content['content_id'] == 17
931
         assert content['content_id'] == 17
932
         assert content['is_archived'] is False
932
         assert content['is_archived'] is False
933
         assert content['is_deleted'] is True
933
         assert content['is_deleted'] is True
936
         assert content['show_in_ui'] is True
936
         assert content['show_in_ui'] is True
937
         assert content['slug'].startswith('bad-fruit-salad')
937
         assert content['slug'].startswith('bad-fruit-salad')
938
         assert content['status'] == 'open'
938
         assert content['status'] == 'open'
939
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
939
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
940
         assert content['workspace_id'] == 3
940
         assert content['workspace_id'] == 3
941
 
941
 
942
     def test_api__get_workspace_content__ok_200__get_nothing_root_content(self):
942
     def test_api__get_workspace_content__ok_200__get_nothing_root_content(self):
983
             session=dbsession,
983
             session=dbsession,
984
             config=self.app_config
984
             config=self.app_config
985
         )
985
         )
986
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
986
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
987
         test_thread = content_api.create(
987
         test_thread = content_api.create(
988
-            content_type=ContentType.Thread,
988
+            content_type_slug=CONTENT_TYPES.Thread.slug,
989
             workspace=business_workspace,
989
             workspace=business_workspace,
990
             parent=tool_folder,
990
             parent=tool_folder,
991
             label='Test Thread',
991
             label='Test Thread',
995
         test_thread.description = 'Thread description'
995
         test_thread.description = 'Thread description'
996
         dbsession.add(test_thread)
996
         dbsession.add(test_thread)
997
         test_file = content_api.create(
997
         test_file = content_api.create(
998
-            content_type=ContentType.File,
998
+            content_type_slug=CONTENT_TYPES.File.slug,
999
             workspace=business_workspace,
999
             workspace=business_workspace,
1000
             parent=tool_folder,
1000
             parent=tool_folder,
1001
             label='Test file',
1001
             label='Test file',
1009
             'text/plain',
1009
             'text/plain',
1010
         )
1010
         )
1011
         test_page_legacy = content_api.create(
1011
         test_page_legacy = content_api.create(
1012
-            content_type=ContentType.Page,
1012
+            content_type_slug=CONTENT_TYPES.Page.slug,
1013
             workspace=business_workspace,
1013
             workspace=business_workspace,
1014
             label='test_page',
1014
             label='test_page',
1015
             do_save=False,
1015
             do_save=False,
1016
             do_notify=False,
1016
             do_notify=False,
1017
         )
1017
         )
1018
-        test_page_legacy.type = ContentType.PageLegacy
1018
+        test_page_legacy.type = 'page'
1019
         content_api.update_content(test_page_legacy, 'test_page', '<p>PAGE</p>')
1019
         content_api.update_content(test_page_legacy, 'test_page', '<p>PAGE</p>')
1020
         test_html_document = content_api.create(
1020
         test_html_document = content_api.create(
1021
-            content_type=ContentType.Page,
1021
+            content_type_slug=CONTENT_TYPES.Page.slug,
1022
             workspace=business_workspace,
1022
             workspace=business_workspace,
1023
             label='test_html_page',
1023
             label='test_html_page',
1024
             do_save=False,
1024
             do_save=False,
1058
         assert content['show_in_ui'] is True
1058
         assert content['show_in_ui'] is True
1059
         assert content['slug'] == 'test-thread'
1059
         assert content['slug'] == 'test-thread'
1060
         assert content['status'] == 'open'
1060
         assert content['status'] == 'open'
1061
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1061
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1062
         assert content['workspace_id'] == 1
1062
         assert content['workspace_id'] == 1
1063
 
1063
 
1064
     def test_api__get_workspace_content__ok_200__get_all_filter_content_html_and_legacy_page(self):  # nopep8
1064
     def test_api__get_workspace_content__ok_200__get_all_filter_content_html_and_legacy_page(self):  # nopep8
1078
             session=dbsession,
1078
             session=dbsession,
1079
             config=self.app_config
1079
             config=self.app_config
1080
         )
1080
         )
1081
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1081
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1082
         test_thread = content_api.create(
1082
         test_thread = content_api.create(
1083
-            content_type=ContentType.Thread,
1083
+            content_type_slug=CONTENT_TYPES.Thread.slug,
1084
             workspace=business_workspace,
1084
             workspace=business_workspace,
1085
             parent=tool_folder,
1085
             parent=tool_folder,
1086
             label='Test Thread',
1086
             label='Test Thread',
1090
         test_thread.description = 'Thread description'
1090
         test_thread.description = 'Thread description'
1091
         dbsession.add(test_thread)
1091
         dbsession.add(test_thread)
1092
         test_file = content_api.create(
1092
         test_file = content_api.create(
1093
-            content_type=ContentType.File,
1093
+            content_type_slug=CONTENT_TYPES.File.slug,
1094
             workspace=business_workspace,
1094
             workspace=business_workspace,
1095
             parent=tool_folder,
1095
             parent=tool_folder,
1096
             label='Test file',
1096
             label='Test file',
1104
             'text/plain',
1104
             'text/plain',
1105
         )
1105
         )
1106
         test_page_legacy = content_api.create(
1106
         test_page_legacy = content_api.create(
1107
-            content_type=ContentType.Page,
1107
+            content_type_slug=CONTENT_TYPES.Page.slug,
1108
             workspace=business_workspace,
1108
             workspace=business_workspace,
1109
             parent=tool_folder,
1109
             parent=tool_folder,
1110
             label='test_page',
1110
             label='test_page',
1111
             do_save=False,
1111
             do_save=False,
1112
             do_notify=False,
1112
             do_notify=False,
1113
         )
1113
         )
1114
-        test_page_legacy.type = ContentType.PageLegacy
1114
+        test_page_legacy.type = 'page'
1115
         content_api.update_content(test_page_legacy, 'test_page', '<p>PAGE</p>')
1115
         content_api.update_content(test_page_legacy, 'test_page', '<p>PAGE</p>')
1116
         test_html_document = content_api.create(
1116
         test_html_document = content_api.create(
1117
-            content_type=ContentType.Page,
1117
+            content_type_slug=CONTENT_TYPES.Page.slug,
1118
             workspace=business_workspace,
1118
             workspace=business_workspace,
1119
             parent=tool_folder,
1119
             parent=tool_folder,
1120
             label='test_html_page',
1120
             label='test_html_page',
1130
             'show_archived': 1,
1130
             'show_archived': 1,
1131
             'show_deleted': 1,
1131
             'show_deleted': 1,
1132
             'show_active': 1,
1132
             'show_active': 1,
1133
-            'content_type': 'html-documents',
1133
+            'content_type': 'html-document',
1134
         }
1134
         }
1135
         self.testapp.authorization = (
1135
         self.testapp.authorization = (
1136
             'Basic',
1136
             'Basic',
1146
         ).json_body
1146
         ).json_body
1147
         assert len(res) == 2
1147
         assert len(res) == 2
1148
         content = res[0]
1148
         content = res[0]
1149
-        assert content['content_type'] == 'html-documents'
1149
+        assert content['content_type'] == 'html-document'
1150
         assert content['content_id']
1150
         assert content['content_id']
1151
         assert content['is_archived'] is False
1151
         assert content['is_archived'] is False
1152
         assert content['is_deleted'] is False
1152
         assert content['is_deleted'] is False
1155
         assert content['show_in_ui'] is True
1155
         assert content['show_in_ui'] is True
1156
         assert content['slug'] == 'test-page'
1156
         assert content['slug'] == 'test-page'
1157
         assert content['status'] == 'open'
1157
         assert content['status'] == 'open'
1158
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1158
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1159
         assert content['workspace_id'] == 1
1159
         assert content['workspace_id'] == 1
1160
         content = res[1]
1160
         content = res[1]
1161
-        assert content['content_type'] == 'html-documents'
1161
+        assert content['content_type'] == 'html-document'
1162
         assert content['content_id']
1162
         assert content['content_id']
1163
         assert content['is_archived'] is False
1163
         assert content['is_archived'] is False
1164
         assert content['is_deleted'] is False
1164
         assert content['is_deleted'] is False
1167
         assert content['show_in_ui'] is True
1167
         assert content['show_in_ui'] is True
1168
         assert content['slug'] == 'test-html-page'
1168
         assert content['slug'] == 'test-html-page'
1169
         assert content['status'] == 'open'
1169
         assert content['status'] == 'open'
1170
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1170
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1171
         assert content['workspace_id'] == 1
1171
         assert content['workspace_id'] == 1
1172
         assert res[0]['content_id'] != res[1]['content_id']
1172
         assert res[0]['content_id'] != res[1]['content_id']
1173
 
1173
 
1180
             'show_archived': 1,
1180
             'show_archived': 1,
1181
             'show_deleted': 1,
1181
             'show_deleted': 1,
1182
             'show_active': 1,
1182
             'show_active': 1,
1183
-            'content_type': 'any'
1183
+         #   'content_type': 'any'
1184
         }
1184
         }
1185
         self.testapp.authorization = (
1185
         self.testapp.authorization = (
1186
             'Basic',
1186
             'Basic',
1196
         ).json_body   # nopep8
1196
         ).json_body   # nopep8
1197
         assert len(res) == 3
1197
         assert len(res) == 3
1198
         content = res[0]
1198
         content = res[0]
1199
-        assert content['content_type'] == 'html-documents'
1199
+        assert content['content_type'] == 'html-document'
1200
         assert content['content_id'] == 12
1200
         assert content['content_id'] == 12
1201
         assert content['is_archived'] is False
1201
         assert content['is_archived'] is False
1202
         assert content['is_deleted'] is False
1202
         assert content['is_deleted'] is False
1205
         assert content['show_in_ui'] is True
1205
         assert content['show_in_ui'] is True
1206
         assert content['slug'] == 'new-fruit-salad'
1206
         assert content['slug'] == 'new-fruit-salad'
1207
         assert content['status'] == 'open'
1207
         assert content['status'] == 'open'
1208
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1208
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1209
         assert content['workspace_id'] == 2
1209
         assert content['workspace_id'] == 2
1210
 
1210
 
1211
         content = res[1]
1211
         content = res[1]
1212
-        assert content['content_type'] == 'html-documents'
1212
+        assert content['content_type'] == 'html-document'
1213
         assert content['content_id'] == 13
1213
         assert content['content_id'] == 13
1214
         assert content['is_archived'] is True
1214
         assert content['is_archived'] is True
1215
         assert content['is_deleted'] is False
1215
         assert content['is_deleted'] is False
1218
         assert content['show_in_ui'] is True
1218
         assert content['show_in_ui'] is True
1219
         assert content['slug'].startswith('fruit-salad')
1219
         assert content['slug'].startswith('fruit-salad')
1220
         assert content['status'] == 'open'
1220
         assert content['status'] == 'open'
1221
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1221
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1222
         assert content['workspace_id'] == 2
1222
         assert content['workspace_id'] == 2
1223
 
1223
 
1224
         content = res[2]
1224
         content = res[2]
1225
-        assert content['content_type'] == 'html-documents'
1225
+        assert content['content_type'] == 'html-document'
1226
         assert content['content_id'] == 14
1226
         assert content['content_id'] == 14
1227
         assert content['is_archived'] is False
1227
         assert content['is_archived'] is False
1228
         assert content['is_deleted'] is True
1228
         assert content['is_deleted'] is True
1231
         assert content['show_in_ui'] is True
1231
         assert content['show_in_ui'] is True
1232
         assert content['slug'].startswith('bad-fruit-salad')
1232
         assert content['slug'].startswith('bad-fruit-salad')
1233
         assert content['status'] == 'open'
1233
         assert content['status'] == 'open'
1234
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1234
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1235
         assert content['workspace_id'] == 2
1235
         assert content['workspace_id'] == 2
1236
 
1236
 
1237
     def test_api__get_workspace_content__ok_200__get_only_active_folder_content(self):  # nopep8
1237
     def test_api__get_workspace_content__ok_200__get_only_active_folder_content(self):  # nopep8
1267
         assert content['show_in_ui'] is True
1267
         assert content['show_in_ui'] is True
1268
         assert content['slug'] == 'new-fruit-salad'
1268
         assert content['slug'] == 'new-fruit-salad'
1269
         assert content['status'] == 'open'
1269
         assert content['status'] == 'open'
1270
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1270
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1271
         assert content['workspace_id'] == 2
1271
         assert content['workspace_id'] == 2
1272
 
1272
 
1273
     def test_api__get_workspace_content__ok_200__get_only_archived_folder_content(self):  # nopep8
1273
     def test_api__get_workspace_content__ok_200__get_only_archived_folder_content(self):  # nopep8
1294
         ).json_body   # nopep8
1294
         ).json_body   # nopep8
1295
         assert len(res) == 1
1295
         assert len(res) == 1
1296
         content = res[0]
1296
         content = res[0]
1297
-        assert content['content_type'] == 'html-documents'
1297
+        assert content['content_type'] == 'html-document'
1298
         assert content['content_id'] == 13
1298
         assert content['content_id'] == 13
1299
         assert content['is_archived'] is True
1299
         assert content['is_archived'] is True
1300
         assert content['is_deleted'] is False
1300
         assert content['is_deleted'] is False
1303
         assert content['show_in_ui'] is True
1303
         assert content['show_in_ui'] is True
1304
         assert content['slug'].startswith('fruit-salad')
1304
         assert content['slug'].startswith('fruit-salad')
1305
         assert content['status'] == 'open'
1305
         assert content['status'] == 'open'
1306
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1306
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1307
         assert content['workspace_id'] == 2
1307
         assert content['workspace_id'] == 2
1308
 
1308
 
1309
     def test_api__get_workspace_content__ok_200__get_only_deleted_folder_content(self):  # nopep8
1309
     def test_api__get_workspace_content__ok_200__get_only_deleted_folder_content(self):  # nopep8
1331
 
1331
 
1332
         assert len(res) == 1
1332
         assert len(res) == 1
1333
         content = res[0]
1333
         content = res[0]
1334
-        assert content['content_type'] == 'html-documents'
1334
+        assert content['content_type'] == 'html-document'
1335
         assert content['content_id'] == 14
1335
         assert content['content_id'] == 14
1336
         assert content['is_archived'] is False
1336
         assert content['is_archived'] is False
1337
         assert content['is_deleted'] is True
1337
         assert content['is_deleted'] is True
1340
         assert content['show_in_ui'] is True
1340
         assert content['show_in_ui'] is True
1341
         assert content['slug'].startswith('bad-fruit-salad')
1341
         assert content['slug'].startswith('bad-fruit-salad')
1342
         assert content['status'] == 'open'
1342
         assert content['status'] == 'open'
1343
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1343
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1344
         assert content['workspace_id'] == 2
1344
         assert content['workspace_id'] == 2
1345
 
1345
 
1346
     def test_api__get_workspace_content__ok_200__get_nothing_folder_content(self):  # nopep8
1346
     def test_api__get_workspace_content__ok_200__get_nothing_folder_content(self):  # nopep8
1436
             )
1436
             )
1437
         )
1437
         )
1438
         params = {
1438
         params = {
1439
+            'parent_id': None,
1439
             'label': 'GenericCreatedContent',
1440
             'label': 'GenericCreatedContent',
1440
             'content_type': 'markdownpage',
1441
             'content_type': 'markdownpage',
1441
         }
1442
         }
1466
         active_contents = self.testapp.get('/api/v2/workspaces/1/contents', params=params_active, status=200).json_body  # nopep8
1467
         active_contents = self.testapp.get('/api/v2/workspaces/1/contents', params=params_active, status=200).json_body  # nopep8
1467
         assert res.json_body in active_contents
1468
         assert res.json_body in active_contents
1468
 
1469
 
1470
+    def test_api__post_content_create_generic_content__ok_200__no_parent_id_param(self) -> None:  # nopep8
1471
+        """
1472
+        Create generic content
1473
+        """
1474
+        self.testapp.authorization = (
1475
+            'Basic',
1476
+            (
1477
+                'admin@admin.admin',
1478
+                'admin@admin.admin'
1479
+            )
1480
+        )
1481
+        params = {
1482
+            'label': 'GenericCreatedContent',
1483
+            'content_type': 'markdownpage',
1484
+        }
1485
+        res = self.testapp.post_json(
1486
+            '/api/v2/workspaces/1/contents',
1487
+            params=params,
1488
+            status=200
1489
+        )
1490
+        assert res
1491
+        assert res.json_body
1492
+        assert res.json_body['status'] == 'open'
1493
+        assert res.json_body['content_id']
1494
+        assert res.json_body['content_type'] == 'markdownpage'
1495
+        assert res.json_body['is_archived'] is False
1496
+        assert res.json_body['is_deleted'] is False
1497
+        assert res.json_body['workspace_id'] == 1
1498
+        assert res.json_body['slug'] == 'genericcreatedcontent'
1499
+        assert res.json_body['parent_id'] is None
1500
+        assert res.json_body['show_in_ui'] is True
1501
+        assert res.json_body['sub_content_types']
1502
+        params_active = {
1503
+            'parent_id': 0,
1504
+            'show_archived': 0,
1505
+            'show_deleted': 0,
1506
+            'show_active': 1,
1507
+        }
1508
+        # INFO - G.M - 2018-06-165 - Verify if new content is correctly created
1509
+        active_contents = self.testapp.get('/api/v2/workspaces/1/contents', params=params_active, status=200).json_body  # nopep8
1510
+        assert res.json_body in active_contents
1511
+
1512
+    def test_api__post_content_create_generic_content__err_400__parent_id_0(self) -> None:  # nopep8
1513
+        """
1514
+        Create generic content
1515
+        """
1516
+        self.testapp.authorization = (
1517
+            'Basic',
1518
+            (
1519
+                'admin@admin.admin',
1520
+                'admin@admin.admin'
1521
+            )
1522
+        )
1523
+        params = {
1524
+            'parent_id': 0,
1525
+            'label': 'GenericCreatedContent',
1526
+            'content_type': 'markdownpage',
1527
+        }
1528
+        res = self.testapp.post_json(
1529
+            '/api/v2/workspaces/1/contents',
1530
+            params=params,
1531
+            status=400
1532
+        )
1533
+
1469
     def test_api__post_content_create_generic_content__ok_200__in_folder(self) -> None:  # nopep8
1534
     def test_api__post_content_create_generic_content__ok_200__in_folder(self) -> None:  # nopep8
1470
         """
1535
         """
1471
         Create generic content in folder
1536
         Create generic content in folder

+ 119 - 119
backend/tracim_backend/tests/library/test_content_api.py View File

14
 from tracim_backend.lib.core.workspace import RoleApi
14
 from tracim_backend.lib.core.workspace import RoleApi
15
 # TODO - G.M - 28-03-2018 - [WorkspaceApi] Re-enable WorkspaceApi
15
 # TODO - G.M - 28-03-2018 - [WorkspaceApi] Re-enable WorkspaceApi
16
 from tracim_backend.lib.core.workspace import WorkspaceApi
16
 from tracim_backend.lib.core.workspace import WorkspaceApi
17
+from tracim_backend.models.contents import CONTENT_TYPES
17
 from tracim_backend.models.revision_protection import new_revision
18
 from tracim_backend.models.revision_protection import new_revision
18
 from tracim_backend.models.auth import User
19
 from tracim_backend.models.auth import User
19
 from tracim_backend.models.auth import Group
20
 from tracim_backend.models.auth import Group
22
 from tracim_backend.models.data import ContentRevisionRO
23
 from tracim_backend.models.data import ContentRevisionRO
23
 from tracim_backend.models.data import Workspace
24
 from tracim_backend.models.data import Workspace
24
 from tracim_backend.models.data import Content
25
 from tracim_backend.models.data import Content
25
-from tracim_backend.models.data import ContentType
26
 from tracim_backend.models.data import UserRoleInWorkspace
26
 from tracim_backend.models.data import UserRoleInWorkspace
27
 from tracim_backend.fixtures.users_and_groups import Test as FixtureTest
27
 from tracim_backend.fixtures.users_and_groups import Test as FixtureTest
28
 from tracim_backend.tests import DefaultTest
28
 from tracim_backend.tests import DefaultTest
129
             config=self.app_config,
129
             config=self.app_config,
130
         )
130
         )
131
         item = api.create(
131
         item = api.create(
132
-            content_type=ContentType.Folder,
132
+            content_type_slug=CONTENT_TYPES.Folder.slug,
133
             workspace=workspace,
133
             workspace=workspace,
134
             parent=None,
134
             parent=None,
135
             label='not_deleted',
135
             label='not_deleted',
136
             do_save=True
136
             do_save=True
137
         )
137
         )
138
         item2 = api.create(
138
         item2 = api.create(
139
-            content_type=ContentType.Folder,
139
+            content_type_slug=CONTENT_TYPES.Folder.slug,
140
             workspace=workspace,
140
             workspace=workspace,
141
             parent=None,
141
             parent=None,
142
             label='to_delete',
142
             label='to_delete',
159
             session=self.session,
159
             session=self.session,
160
             config=self.app_config,
160
             config=self.app_config,
161
         )
161
         )
162
-        items = api.get_all(None, ContentType.Any, workspace)
162
+        items = api.get_all(None, CONTENT_TYPES.Any_SLUG, workspace)
163
         eq_(2, len(items))
163
         eq_(2, len(items))
164
 
164
 
165
-        items = api.get_all(None, ContentType.Any, workspace)
165
+        items = api.get_all(None, CONTENT_TYPES.Any_SLUG, workspace)
166
         with new_revision(
166
         with new_revision(
167
                 session=self.session,
167
                 session=self.session,
168
                 tm=transaction.manager,
168
                 tm=transaction.manager,
184
             session=self.session,
184
             session=self.session,
185
             config=self.app_config,
185
             config=self.app_config,
186
         )
186
         )
187
-        items = api.get_all(None, ContentType.Any, workspace)
187
+        items = api.get_all(None, CONTENT_TYPES.Any_SLUG, workspace)
188
         eq_(1, len(items))
188
         eq_(1, len(items))
189
         transaction.commit()
189
         transaction.commit()
190
 
190
 
202
             config=self.app_config,
202
             config=self.app_config,
203
             show_deleted=True,
203
             show_deleted=True,
204
         )
204
         )
205
-        items = api.get_all(None, ContentType.Any, workspace)
205
+        items = api.get_all(None, CONTENT_TYPES.Any_SLUG, workspace)
206
         eq_(2, len(items))
206
         eq_(2, len(items))
207
 
207
 
208
     def test_archive(self):
208
     def test_archive(self):
240
             config=self.app_config,
240
             config=self.app_config,
241
         )
241
         )
242
         item = api.create(
242
         item = api.create(
243
-            content_type=ContentType.Folder,
243
+            content_type_slug=CONTENT_TYPES.Folder.slug,
244
             workspace=workspace,
244
             workspace=workspace,
245
             parent=None,
245
             parent=None,
246
             label='not_archived',
246
             label='not_archived',
247
             do_save=True
247
             do_save=True
248
         )
248
         )
249
         item2 = api.create(
249
         item2 = api.create(
250
-            content_type=ContentType.Folder,
250
+            content_type_slug=CONTENT_TYPES.Folder.slug,
251
             workspace=workspace,
251
             workspace=workspace,
252
             parent=None,
252
             parent=None,
253
             label='to_archive',
253
             label='to_archive',
269
             config=self.app_config,
269
             config=self.app_config,
270
         )
270
         )
271
 
271
 
272
-        items = api.get_all(None, ContentType.Any, workspace)
272
+        items = api.get_all(None, CONTENT_TYPES.Any_SLUG, workspace)
273
         eq_(2, len(items))
273
         eq_(2, len(items))
274
 
274
 
275
-        items = api.get_all(None, ContentType.Any, workspace)
275
+        items = api.get_all(None, CONTENT_TYPES.Any_SLUG, workspace)
276
         with new_revision(
276
         with new_revision(
277
                 session=self.session,
277
                 session=self.session,
278
                 tm=transaction.manager,
278
                 tm=transaction.manager,
295
             config=self.app_config,
295
             config=self.app_config,
296
         )
296
         )
297
 
297
 
298
-        items = api.get_all(None, ContentType.Any, workspace)
298
+        items = api.get_all(None, CONTENT_TYPES.Any_SLUG, workspace)
299
         eq_(1, len(items))
299
         eq_(1, len(items))
300
         transaction.commit()
300
         transaction.commit()
301
 
301
 
320
             config=self.app_config,
320
             config=self.app_config,
321
             show_archived=True,
321
             show_archived=True,
322
         )
322
         )
323
-        items = api.get_all(None, ContentType.Any, workspace)
323
+        items = api.get_all(None, CONTENT_TYPES.Any_SLUG, workspace)
324
         eq_(2, len(items))
324
         eq_(2, len(items))
325
 
325
 
326
     def test_get_all_with_filter(self):
326
     def test_get_all_with_filter(self):
358
             config=self.app_config,
358
             config=self.app_config,
359
         )
359
         )
360
         item = api.create(
360
         item = api.create(
361
-            content_type=ContentType.Folder,
361
+            content_type_slug=CONTENT_TYPES.Folder.slug,
362
             workspace=workspace,
362
             workspace=workspace,
363
             parent=None,
363
             parent=None,
364
             label='thefolder',
364
             label='thefolder',
365
             do_save=True
365
             do_save=True
366
         )
366
         )
367
         item2 = api.create(
367
         item2 = api.create(
368
-            content_type=ContentType.File,
368
+            content_type_slug=CONTENT_TYPES.File.slug,
369
             workspace=workspace,
369
             workspace=workspace,
370
             parent=None,
370
             parent=None,
371
             label='thefile',
371
             label='thefile',
389
             config=self.app_config,
389
             config=self.app_config,
390
         )
390
         )
391
 
391
 
392
-        items = api.get_all(None, ContentType.Any, workspace)
392
+        items = api.get_all(None, CONTENT_TYPES.Any_SLUG, workspace)
393
         eq_(2, len(items))
393
         eq_(2, len(items))
394
 
394
 
395
-        items2 = api.get_all(None, ContentType.File, workspace)
395
+        items2 = api.get_all(None, CONTENT_TYPES.File.slug, workspace)
396
         eq_(1, len(items2))
396
         eq_(1, len(items2))
397
         eq_('thefile', items2[0].label)
397
         eq_('thefile', items2[0].label)
398
 
398
 
399
-        items3 = api.get_all(None, ContentType.Folder, workspace)
399
+        items3 = api.get_all(None, CONTENT_TYPES.Folder.slug, workspace)
400
         eq_(1, len(items3))
400
         eq_(1, len(items3))
401
         eq_('thefolder', items3[0].label)
401
         eq_('thefolder', items3[0].label)
402
 
402
 
428
             config=self.app_config,
428
             config=self.app_config,
429
         )
429
         )
430
         item = api.create(
430
         item = api.create(
431
-            ContentType.Folder,
431
+            CONTENT_TYPES.Folder.slug,
432
             workspace,
432
             workspace,
433
             None,
433
             None,
434
             'parent',
434
             'parent',
435
             do_save=True,
435
             do_save=True,
436
         )
436
         )
437
         item2 = api.create(
437
         item2 = api.create(
438
-            ContentType.File,
438
+            CONTENT_TYPES.File.slug,
439
             workspace,
439
             workspace,
440
             item,
440
             item,
441
             'file1',
441
             'file1',
442
             do_save=True,
442
             do_save=True,
443
         )
443
         )
444
         item3 = api.create(
444
         item3 = api.create(
445
-            ContentType.File,
445
+            CONTENT_TYPES.File.slug,
446
             workspace,
446
             workspace,
447
             None,
447
             None,
448
             'file2',
448
             'file2',
468
             config=self.app_config,
468
             config=self.app_config,
469
         )
469
         )
470
 
470
 
471
-        items = api.get_all(None, ContentType.Any, workspace)
471
+        items = api.get_all(None, CONTENT_TYPES.Any_SLUG, workspace)
472
         eq_(3, len(items))
472
         eq_(3, len(items))
473
 
473
 
474
-        items2 = api.get_all(parent_id, ContentType.File, workspace)
474
+        items2 = api.get_all(parent_id, CONTENT_TYPES.File.slug, workspace)
475
         eq_(1, len(items2))
475
         eq_(1, len(items2))
476
         eq_(child_id, items2[0].content_id)
476
         eq_(child_id, items2[0].content_id)
477
 
477
 
506
             session=self.session,
506
             session=self.session,
507
             config=self.app_config,
507
             config=self.app_config,
508
         )
508
         )
509
-        c = api.create(ContentType.Folder, workspace, None, 'parent', '', True)
509
+        c = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'parent', '', True)
510
         with new_revision(
510
         with new_revision(
511
             session=self.session,
511
             session=self.session,
512
             tm=transaction.manager,
512
             tm=transaction.manager,
546
             session=self.session,
546
             session=self.session,
547
             config=self.app_config,
547
             config=self.app_config,
548
         )
548
         )
549
-        c = api.create(ContentType.Folder, workspace, None, 'parent', '', True)
549
+        c = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'parent', '', True)
550
         with new_revision(
550
         with new_revision(
551
             session=self.session,
551
             session=self.session,
552
             tm=transaction.manager,
552
             tm=transaction.manager,
591
             session=self.session,
591
             session=self.session,
592
             config=self.app_config,
592
             config=self.app_config,
593
         )
593
         )
594
-        p = api.create(ContentType.Page, workspace, None, 'this_is_a_page')
594
+        p = api.create(CONTENT_TYPES.Page.slug, workspace, None, 'this_is_a_page')
595
         c = api.create_comment(workspace, p, 'this is the comment', True)
595
         c = api.create_comment(workspace, p, 'this is the comment', True)
596
 
596
 
597
         eq_(Content, c.__class__)
597
         eq_(Content, c.__class__)
598
         eq_(p.content_id, c.parent_id)
598
         eq_(p.content_id, c.parent_id)
599
         eq_(user, c.owner)
599
         eq_(user, c.owner)
600
         eq_(workspace, c.workspace)
600
         eq_(workspace, c.workspace)
601
-        eq_(ContentType.Comment, c.type)
601
+        eq_(CONTENT_TYPES.Comment.slug, c.type)
602
         eq_('this is the comment', c.description)
602
         eq_('this is the comment', c.description)
603
         eq_('', c.label)
603
         eq_('', c.label)
604
         eq_(ActionDescription.COMMENT, c.revision_type)
604
         eq_(ActionDescription.COMMENT, c.revision_type)
652
             config=self.app_config,
652
             config=self.app_config,
653
         )
653
         )
654
         foldera = api.create(
654
         foldera = api.create(
655
-            ContentType.Folder,
655
+            CONTENT_TYPES.Folder.slug,
656
             workspace,
656
             workspace,
657
             None,
657
             None,
658
             'folder a',
658
             'folder a',
661
         )
661
         )
662
         with self.session.no_autoflush:
662
         with self.session.no_autoflush:
663
             text_file = api.create(
663
             text_file = api.create(
664
-                content_type=ContentType.File,
664
+                content_type_slug=CONTENT_TYPES.File.slug,
665
                 workspace=workspace,
665
                 workspace=workspace,
666
                 parent=foldera,
666
                 parent=foldera,
667
                 label='test_file',
667
                 label='test_file',
689
             save_now=True
689
             save_now=True
690
         )
690
         )
691
         folderb = api2.create(
691
         folderb = api2.create(
692
-            ContentType.Folder,
692
+            CONTENT_TYPES.Folder.slug,
693
             workspace2,
693
             workspace2,
694
             None,
694
             None,
695
             'folder b',
695
             'folder b',
773
             config=self.app_config,
773
             config=self.app_config,
774
         )
774
         )
775
         foldera = api.create(
775
         foldera = api.create(
776
-            ContentType.Folder,
776
+            CONTENT_TYPES.Folder.slug,
777
             workspace,
777
             workspace,
778
             None,
778
             None,
779
             'folder a',
779
             'folder a',
782
         )
782
         )
783
         with self.session.no_autoflush:
783
         with self.session.no_autoflush:
784
             text_file = api.create(
784
             text_file = api.create(
785
-                content_type=ContentType.File,
785
+                content_type_slug=CONTENT_TYPES.File.slug,
786
                 workspace=workspace,
786
                 workspace=workspace,
787
                 parent=foldera,
787
                 parent=foldera,
788
                 label='test_file',
788
                 label='test_file',
810
             save_now=True
810
             save_now=True
811
         )
811
         )
812
         folderb = api2.create(
812
         folderb = api2.create(
813
-            ContentType.Folder,
813
+            CONTENT_TYPES.Folder.slug,
814
             workspace2,
814
             workspace2,
815
             None,
815
             None,
816
             'folder b',
816
             'folder b',
891
             config=self.app_config,
891
             config=self.app_config,
892
         )
892
         )
893
         foldera = api.create(
893
         foldera = api.create(
894
-            ContentType.Folder,
894
+            CONTENT_TYPES.Folder.slug,
895
             workspace,
895
             workspace,
896
             None,
896
             None,
897
             'folder a',
897
             'folder a',
900
         )
900
         )
901
         with self.session.no_autoflush:
901
         with self.session.no_autoflush:
902
             text_file = api.create(
902
             text_file = api.create(
903
-                content_type=ContentType.File,
903
+                content_type_slug=CONTENT_TYPES.File.slug,
904
                 workspace=workspace,
904
                 workspace=workspace,
905
                 parent=foldera,
905
                 parent=foldera,
906
                 label='test_file',
906
                 label='test_file',
1014
 
1014
 
1015
         # Creates page_1 & page_2 in workspace 1
1015
         # Creates page_1 & page_2 in workspace 1
1016
         #     and page_3 & page_4 in workspace 2
1016
         #     and page_3 & page_4 in workspace 2
1017
-        page_1 = cont_api_a.create(ContentType.Page, workspace1, None,
1017
+        page_1 = cont_api_a.create(CONTENT_TYPES.Page.slug, workspace1, None,
1018
                                    'this is a page', do_save=True)
1018
                                    'this is a page', do_save=True)
1019
-        page_2 = cont_api_a.create(ContentType.Page, workspace1, None,
1019
+        page_2 = cont_api_a.create(CONTENT_TYPES.Page.slug, workspace1, None,
1020
                                    'this is page1', do_save=True)
1020
                                    'this is page1', do_save=True)
1021
-        page_3 = cont_api_a.create(ContentType.Thread, workspace2, None,
1021
+        page_3 = cont_api_a.create(CONTENT_TYPES.Thread.slug, workspace2, None,
1022
                                    'this is page2', do_save=True)
1022
                                    'this is page2', do_save=True)
1023
-        page_4 = cont_api_a.create(ContentType.File, workspace2, None,
1023
+        page_4 = cont_api_a.create(CONTENT_TYPES.File.slug, workspace2, None,
1024
                                    'this is page3', do_save=True)
1024
                                    'this is page3', do_save=True)
1025
 
1025
 
1026
         for rev in page_1.revisions:
1026
         for rev in page_1.revisions:
1118
             config=self.app_config,
1118
             config=self.app_config,
1119
         )
1119
         )
1120
 
1120
 
1121
-        page_1 = cont_api_a.create(ContentType.Page, workspace, None,
1121
+        page_1 = cont_api_a.create(CONTENT_TYPES.Page.slug, workspace, None,
1122
                                    'this is a page', do_save=True)
1122
                                    'this is a page', do_save=True)
1123
 
1123
 
1124
         for rev in page_1.revisions:
1124
         for rev in page_1.revisions:
1187
         )
1187
         )
1188
 
1188
 
1189
         page_2 = cont_api_a.create(
1189
         page_2 = cont_api_a.create(
1190
-            ContentType.Page,
1190
+            CONTENT_TYPES.Page.slug,
1191
             workspace,
1191
             workspace,
1192
             None,
1192
             None,
1193
             'this is page1',
1193
             'this is page1',
1194
             do_save=True
1194
             do_save=True
1195
         )
1195
         )
1196
         page_3 = cont_api_a.create(
1196
         page_3 = cont_api_a.create(
1197
-            ContentType.Thread,
1197
+            CONTENT_TYPES.Thread.slug,
1198
             workspace,
1198
             workspace,
1199
             None,
1199
             None,
1200
             'this is page2',
1200
             'this is page2',
1201
             do_save=True
1201
             do_save=True
1202
         )
1202
         )
1203
         page_4 = cont_api_a.create(
1203
         page_4 = cont_api_a.create(
1204
-            ContentType.File,
1204
+            CONTENT_TYPES.File.slug,
1205
             workspace,
1205
             workspace,
1206
             None,
1206
             None,
1207
             'this is page3',
1207
             'this is page3',
1285
         )
1285
         )
1286
 
1286
 
1287
         p = api.create(
1287
         p = api.create(
1288
-            content_type=ContentType.Page,
1288
+            content_type_slug=CONTENT_TYPES.Page.slug,
1289
             workspace=workspace,
1289
             workspace=workspace,
1290
             parent=None,
1290
             parent=None,
1291
             label='this_is_a_page',
1291
             label='this_is_a_page',
1312
             config=self.app_config,
1312
             config=self.app_config,
1313
         )
1313
         )
1314
 
1314
 
1315
-        content = api.get_one(pcid, ContentType.Any, workspace)
1315
+        content = api.get_one(pcid, CONTENT_TYPES.Any_SLUG, workspace)
1316
         eq_(u1id, content.owner_id)
1316
         eq_(u1id, content.owner_id)
1317
         eq_(poid, content.owner_id)
1317
         eq_(poid, content.owner_id)
1318
 
1318
 
1326
             session=self.session,
1326
             session=self.session,
1327
             config=self.app_config,
1327
             config=self.app_config,
1328
         )
1328
         )
1329
-        content2 = api2.get_one(pcid, ContentType.Any, workspace)
1329
+        content2 = api2.get_one(pcid, CONTENT_TYPES.Any_SLUG, workspace)
1330
         with new_revision(
1330
         with new_revision(
1331
            session=self.session,
1331
            session=self.session,
1332
            tm=transaction.manager,
1332
            tm=transaction.manager,
1353
             config=self.app_config,
1353
             config=self.app_config,
1354
         )
1354
         )
1355
 
1355
 
1356
-        updated = api.get_one(pcid, ContentType.Any, workspace)
1356
+        updated = api.get_one(pcid, CONTENT_TYPES.Any_SLUG, workspace)
1357
         eq_(u2id, updated.owner_id,
1357
         eq_(u2id, updated.owner_id,
1358
             'the owner id should be {} (found {})'.format(u2id,
1358
             'the owner id should be {} (found {})'.format(u2id,
1359
                                                           updated.owner_id))
1359
                                                           updated.owner_id))
1412
         )
1412
         )
1413
         with self.session.no_autoflush:
1413
         with self.session.no_autoflush:
1414
             page = api.create(
1414
             page = api.create(
1415
-                content_type=ContentType.Page,
1415
+                content_type_slug=CONTENT_TYPES.Page.slug,
1416
                 workspace=workspace,
1416
                 workspace=workspace,
1417
                 label="same_content",
1417
                 label="same_content",
1418
                 do_save=False
1418
                 do_save=False
1426
             session=self.session,
1426
             session=self.session,
1427
             config=self.app_config,
1427
             config=self.app_config,
1428
         )
1428
         )
1429
-        content2 = api2.get_one(page.content_id, ContentType.Any, workspace)
1429
+        content2 = api2.get_one(page.content_id, CONTENT_TYPES.Any_SLUG, workspace)
1430
         with new_revision(
1430
         with new_revision(
1431
            session=self.session,
1431
            session=self.session,
1432
            tm=transaction.manager,
1432
            tm=transaction.manager,
1495
             config=self.app_config,
1495
             config=self.app_config,
1496
         )
1496
         )
1497
         p = api.create(
1497
         p = api.create(
1498
-            content_type=ContentType.File,
1498
+            content_type_slug=CONTENT_TYPES.File.slug,
1499
             workspace=workspace,
1499
             workspace=workspace,
1500
             parent=None,
1500
             parent=None,
1501
             label='this_is_a_page',
1501
             label='this_is_a_page',
1524
             config=self.app_config,
1524
             config=self.app_config,
1525
         )
1525
         )
1526
 
1526
 
1527
-        content = api.get_one(pcid, ContentType.Any, workspace)
1527
+        content = api.get_one(pcid, CONTENT_TYPES.Any_SLUG, workspace)
1528
         eq_(u1id, content.owner_id)
1528
         eq_(u1id, content.owner_id)
1529
         eq_(poid, content.owner_id)
1529
         eq_(poid, content.owner_id)
1530
 
1530
 
1538
             session=self.session,
1538
             session=self.session,
1539
             config=self.app_config,
1539
             config=self.app_config,
1540
         )
1540
         )
1541
-        content2 = api2.get_one(pcid, ContentType.Any, workspace)
1541
+        content2 = api2.get_one(pcid, CONTENT_TYPES.Any_SLUG, workspace)
1542
         with new_revision(
1542
         with new_revision(
1543
             session=self.session,
1543
             session=self.session,
1544
             tm=transaction.manager,
1544
             tm=transaction.manager,
1561
             config=self.app_config,
1561
             config=self.app_config,
1562
         ).get_one(wid)
1562
         ).get_one(wid)
1563
 
1563
 
1564
-        updated = api.get_one(pcid, ContentType.Any, workspace)
1564
+        updated = api.get_one(pcid, CONTENT_TYPES.Any_SLUG, workspace)
1565
         eq_(u2id, updated.owner_id,
1565
         eq_(u2id, updated.owner_id,
1566
             'the owner id should be {} (found {})'.format(u2id,
1566
             'the owner id should be {} (found {})'.format(u2id,
1567
                                                           updated.owner_id))
1567
                                                           updated.owner_id))
1622
         )
1622
         )
1623
         with self.session.no_autoflush:
1623
         with self.session.no_autoflush:
1624
             page = api.create(
1624
             page = api.create(
1625
-                content_type=ContentType.Page,
1625
+                content_type_slug=CONTENT_TYPES.Page.slug,
1626
                 workspace=workspace,
1626
                 workspace=workspace,
1627
                 label="same_content",
1627
                 label="same_content",
1628
                 do_save=False
1628
                 do_save=False
1641
             session=self.session,
1641
             session=self.session,
1642
             config=self.app_config,
1642
             config=self.app_config,
1643
         )
1643
         )
1644
-        content2 = api2.get_one(page.content_id, ContentType.Any, workspace)
1644
+        content2 = api2.get_one(page.content_id, CONTENT_TYPES.Any_SLUG, workspace)
1645
         with new_revision(
1645
         with new_revision(
1646
             session=self.session,
1646
             session=self.session,
1647
             tm=transaction.manager,
1647
             tm=transaction.manager,
1713
             config=self.app_config,
1713
             config=self.app_config,
1714
         )
1714
         )
1715
         p = api.create(
1715
         p = api.create(
1716
-            content_type=ContentType.File,
1716
+            content_type_slug=CONTENT_TYPES.File.slug,
1717
             workspace=workspace,
1717
             workspace=workspace,
1718
             parent=None,
1718
             parent=None,
1719
             label='this_is_a_page',
1719
             label='this_is_a_page',
1741
             config=self.app_config,
1741
             config=self.app_config,
1742
         ).get_one(wid)
1742
         ).get_one(wid)
1743
 
1743
 
1744
-        content = api.get_one(pcid, ContentType.Any, workspace)
1744
+        content = api.get_one(pcid, CONTENT_TYPES.Any_SLUG, workspace)
1745
         eq_(u1id, content.owner_id)
1745
         eq_(u1id, content.owner_id)
1746
         eq_(poid, content.owner_id)
1746
         eq_(poid, content.owner_id)
1747
 
1747
 
1757
             config=self.app_config,
1757
             config=self.app_config,
1758
             show_archived=True,
1758
             show_archived=True,
1759
         )
1759
         )
1760
-        content2 = api2.get_one(pcid, ContentType.Any, workspace)
1760
+        content2 = api2.get_one(pcid, CONTENT_TYPES.Any_SLUG, workspace)
1761
         with new_revision(
1761
         with new_revision(
1762
                 session=self.session,
1762
                 session=self.session,
1763
                 tm=transaction.manager,
1763
                 tm=transaction.manager,
1796
             show_archived=True,
1796
             show_archived=True,
1797
         )
1797
         )
1798
 
1798
 
1799
-        updated = api2.get_one(pcid, ContentType.Any, workspace)
1799
+        updated = api2.get_one(pcid, CONTENT_TYPES.Any_SLUG, workspace)
1800
         eq_(u2id, updated.owner_id,
1800
         eq_(u2id, updated.owner_id,
1801
             'the owner id should be {} (found {})'.format(u2id,
1801
             'the owner id should be {} (found {})'.format(u2id,
1802
                                                           updated.owner_id))
1802
                                                           updated.owner_id))
1805
 
1805
 
1806
         ####
1806
         ####
1807
 
1807
 
1808
-        updated2 = api.get_one(pcid, ContentType.Any, workspace)
1808
+        updated2 = api.get_one(pcid, CONTENT_TYPES.Any_SLUG, workspace)
1809
         with new_revision(
1809
         with new_revision(
1810
             session=self.session,
1810
             session=self.session,
1811
             tm=transaction.manager,
1811
             tm=transaction.manager,
1874
             show_deleted=True,
1874
             show_deleted=True,
1875
         )
1875
         )
1876
         p = api.create(
1876
         p = api.create(
1877
-            content_type=ContentType.File,
1877
+            content_type_slug=CONTENT_TYPES.File.slug,
1878
             workspace=workspace,
1878
             workspace=workspace,
1879
             parent=None,
1879
             parent=None,
1880
             label='this_is_a_page',
1880
             label='this_is_a_page',
1900
             config=self.app_config,
1900
             config=self.app_config,
1901
         ).get_one(wid)
1901
         ).get_one(wid)
1902
 
1902
 
1903
-        content = api.get_one(pcid, ContentType.Any, workspace)
1903
+        content = api.get_one(pcid, CONTENT_TYPES.Any_SLUG, workspace)
1904
         eq_(u1id, content.owner_id)
1904
         eq_(u1id, content.owner_id)
1905
         eq_(poid, content.owner_id)
1905
         eq_(poid, content.owner_id)
1906
 
1906
 
1915
             config=self.app_config,
1915
             config=self.app_config,
1916
             show_deleted=True,
1916
             show_deleted=True,
1917
         )
1917
         )
1918
-        content2 = api2.get_one(pcid, ContentType.Any, workspace)
1918
+        content2 = api2.get_one(pcid, CONTENT_TYPES.Any_SLUG, workspace)
1919
         with new_revision(
1919
         with new_revision(
1920
                 session=self.session,
1920
                 session=self.session,
1921
                 tm=transaction.manager,
1921
                 tm=transaction.manager,
1956
             show_deleted=True
1956
             show_deleted=True
1957
         )
1957
         )
1958
 
1958
 
1959
-        updated = api2.get_one(pcid, ContentType.Any, workspace)
1959
+        updated = api2.get_one(pcid, CONTENT_TYPES.Any_SLUG, workspace)
1960
         eq_(u2id, updated.owner_id,
1960
         eq_(u2id, updated.owner_id,
1961
             'the owner id should be {} (found {})'.format(u2id,
1961
             'the owner id should be {} (found {})'.format(u2id,
1962
                                                           updated.owner_id))
1962
                                                           updated.owner_id))
1965
 
1965
 
1966
         ####
1966
         ####
1967
 
1967
 
1968
-        updated2 = api.get_one(pcid, ContentType.Any, workspace)
1968
+        updated2 = api.get_one(pcid, CONTENT_TYPES.Any_SLUG, workspace)
1969
         with new_revision(
1969
         with new_revision(
1970
             tm=transaction.manager,
1970
             tm=transaction.manager,
1971
             session=self.session,
1971
             session=self.session,
2016
             session=self.session,
2016
             session=self.session,
2017
             config=self.app_config,
2017
             config=self.app_config,
2018
         )
2018
         )
2019
-        main_folder_workspace2 = api.create(ContentType.Folder, workspace2, None, 'Hepla', '', True)  # nopep8
2020
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
2019
+        main_folder_workspace2 = api.create(CONTENT_TYPES.Folder.slug, workspace2, None, 'Hepla', '', True)  # nopep8
2020
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
2021
         # creation order test
2021
         # creation order test
2022
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2023
-        secondly_created = api.create(ContentType.Page, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
2022
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2023
+        secondly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
2024
         # update order test
2024
         # update order test
2025
-        firstly_created_but_recently_updated = api.create(ContentType.Page, workspace, main_folder, 'update_order_test', '', True)  # nopep8
2026
-        secondly_created_but_not_updated = api.create(ContentType.Page, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
2025
+        firstly_created_but_recently_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'update_order_test', '', True)  # nopep8
2026
+        secondly_created_but_not_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
2027
         with new_revision(
2027
         with new_revision(
2028
             session=self.session,
2028
             session=self.session,
2029
             tm=transaction.manager,
2029
             tm=transaction.manager,
2032
             firstly_created_but_recently_updated.description = 'Just an update'
2032
             firstly_created_but_recently_updated.description = 'Just an update'
2033
         api.save(firstly_created_but_recently_updated)
2033
         api.save(firstly_created_but_recently_updated)
2034
         # comment change order
2034
         # comment change order
2035
-        firstly_created_but_recently_commented = api.create(ContentType.Page, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
2036
-        secondly_created_but_not_commented = api.create(ContentType.Page, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
2035
+        firstly_created_but_recently_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
2036
+        secondly_created_but_not_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
2037
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
2037
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
2038
 
2038
 
2039
-        content_workspace_2 = api.create(ContentType.Page, workspace2 ,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
2039
+        content_workspace_2 = api.create(CONTENT_TYPES.Page.slug, workspace2 ,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
2040
         last_actives = api.get_last_active()
2040
         last_actives = api.get_last_active()
2041
         assert len(last_actives) == 9
2041
         assert len(last_actives) == 9
2042
         # workspace_2 content
2042
         # workspace_2 content
2088
             session=self.session,
2088
             session=self.session,
2089
             config=self.app_config,
2089
             config=self.app_config,
2090
         )
2090
         )
2091
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
2091
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
2092
         # creation order test
2092
         # creation order test
2093
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2094
-        secondly_created = api.create(ContentType.Page, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
2093
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2094
+        secondly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
2095
         # update order test
2095
         # update order test
2096
-        firstly_created_but_recently_updated = api.create(ContentType.Page, workspace, main_folder, 'update_order_test', '', True)  # nopep8
2097
-        secondly_created_but_not_updated = api.create(ContentType.Page, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
2096
+        firstly_created_but_recently_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'update_order_test', '', True)  # nopep8
2097
+        secondly_created_but_not_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
2098
         with new_revision(
2098
         with new_revision(
2099
             session=self.session,
2099
             session=self.session,
2100
             tm=transaction.manager,
2100
             tm=transaction.manager,
2103
             firstly_created_but_recently_updated.description = 'Just an update'
2103
             firstly_created_but_recently_updated.description = 'Just an update'
2104
         api.save(firstly_created_but_recently_updated)
2104
         api.save(firstly_created_but_recently_updated)
2105
         # comment change order
2105
         # comment change order
2106
-        firstly_created_but_recently_commented = api.create(ContentType.Page, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
2107
-        secondly_created_but_not_commented = api.create(ContentType.Page, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
2106
+        firstly_created_but_recently_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
2107
+        secondly_created_but_not_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
2108
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
2108
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
2109
 
2109
 
2110
         last_actives = api.get_last_active(workspace=workspace)
2110
         last_actives = api.get_last_active(workspace=workspace)
2153
             session=self.session,
2153
             session=self.session,
2154
             config=self.app_config,
2154
             config=self.app_config,
2155
         )
2155
         )
2156
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
2156
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
2157
         # creation order test
2157
         # creation order test
2158
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2159
-        secondly_created = api.create(ContentType.Page, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
2158
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2159
+        secondly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
2160
         # update order test
2160
         # update order test
2161
-        firstly_created_but_recently_updated = api.create(ContentType.Page, workspace, main_folder, 'update_order_test', '', True)  # nopep8
2162
-        secondly_created_but_not_updated = api.create(ContentType.Page, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
2161
+        firstly_created_but_recently_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'update_order_test', '', True)  # nopep8
2162
+        secondly_created_but_not_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
2163
         with new_revision(
2163
         with new_revision(
2164
             session=self.session,
2164
             session=self.session,
2165
             tm=transaction.manager,
2165
             tm=transaction.manager,
2168
             firstly_created_but_recently_updated.description = 'Just an update'
2168
             firstly_created_but_recently_updated.description = 'Just an update'
2169
         api.save(firstly_created_but_recently_updated)
2169
         api.save(firstly_created_but_recently_updated)
2170
         # comment change order
2170
         # comment change order
2171
-        firstly_created_but_recently_commented = api.create(ContentType.Page, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
2172
-        secondly_created_but_not_commented = api.create(ContentType.Page, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
2171
+        firstly_created_but_recently_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
2172
+        secondly_created_but_not_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
2173
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
2173
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
2174
 
2174
 
2175
         selected_contents = [
2175
         selected_contents = [
2228
             session=self.session,
2228
             session=self.session,
2229
             config=self.app_config,
2229
             config=self.app_config,
2230
         )
2230
         )
2231
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
2231
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
2232
         # creation order test
2232
         # creation order test
2233
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2234
-        secondly_created = api.create(ContentType.Page, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
2233
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2234
+        secondly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
2235
         # update order test
2235
         # update order test
2236
-        firstly_created_but_recently_updated = api.create(ContentType.Page, workspace, main_folder, 'update_order_test', '', True)  # nopep8
2237
-        secondly_created_but_not_updated = api.create(ContentType.Page, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
2236
+        firstly_created_but_recently_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'update_order_test', '', True)  # nopep8
2237
+        secondly_created_but_not_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
2238
         with new_revision(
2238
         with new_revision(
2239
             session=self.session,
2239
             session=self.session,
2240
             tm=transaction.manager,
2240
             tm=transaction.manager,
2243
             firstly_created_but_recently_updated.description = 'Just an update'
2243
             firstly_created_but_recently_updated.description = 'Just an update'
2244
         api.save(firstly_created_but_recently_updated)
2244
         api.save(firstly_created_but_recently_updated)
2245
         # comment change order
2245
         # comment change order
2246
-        firstly_created_but_recently_commented = api.create(ContentType.Page, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
2247
-        secondly_created_but_not_commented = api.create(ContentType.Page, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
2246
+        firstly_created_but_recently_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
2247
+        secondly_created_but_not_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
2248
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
2248
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
2249
 
2249
 
2250
-        last_actives = api.get_last_active(workspace=workspace, limit=2, before_datetime=datetime.datetime.now())  # nopep8
2250
+        last_actives = api.get_last_active(workspace=workspace, limit=2)  # nopep8
2251
         assert len(last_actives) == 2
2251
         assert len(last_actives) == 2
2252
         # comment is newest than page2
2252
         # comment is newest than page2
2253
         assert last_actives[0] == firstly_created_but_recently_commented
2253
         assert last_actives[0] == firstly_created_but_recently_commented
2254
         assert last_actives[1] == secondly_created_but_not_commented
2254
         assert last_actives[1] == secondly_created_but_not_commented
2255
 
2255
 
2256
-        last_actives = api.get_last_active(workspace=workspace, limit=2, before_datetime=last_actives[1].get_simple_last_activity_date())  # nopep8
2256
+        last_actives = api.get_last_active(workspace=workspace, limit=2, before_content=last_actives[1])  # nopep8
2257
         assert len(last_actives) == 2
2257
         assert len(last_actives) == 2
2258
         # last updated content is newer than other one despite creation
2258
         # last updated content is newer than other one despite creation
2259
         # of the other is more recent
2259
         # of the other is more recent
2260
         assert last_actives[0] == firstly_created_but_recently_updated
2260
         assert last_actives[0] == firstly_created_but_recently_updated
2261
         assert last_actives[1] == secondly_created_but_not_updated
2261
         assert last_actives[1] == secondly_created_but_not_updated
2262
 
2262
 
2263
-        last_actives = api.get_last_active(workspace=workspace, limit=2, before_datetime=last_actives[1].get_simple_last_activity_date())  # nopep8
2263
+        last_actives = api.get_last_active(workspace=workspace, limit=2, before_content=last_actives[1])  # nopep8
2264
         assert len(last_actives) == 2
2264
         assert len(last_actives) == 2
2265
         # creation order is inverted here as last created is last active
2265
         # creation order is inverted here as last created is last active
2266
         assert last_actives[0] == secondly_created
2266
         assert last_actives[0] == secondly_created
2267
         assert last_actives[1] == firstly_created
2267
         assert last_actives[1] == firstly_created
2268
 
2268
 
2269
-        last_actives = api.get_last_active(workspace=workspace, limit=2, before_datetime=last_actives[1].get_simple_last_activity_date())  # nopep8
2269
+        last_actives = api.get_last_active(workspace=workspace, limit=2, before_content=last_actives[1])  # nopep8
2270
         assert len(last_actives) == 1
2270
         assert len(last_actives) == 1
2271
         # folder subcontent modification does not change folder order
2271
         # folder subcontent modification does not change folder order
2272
         assert last_actives[0] == main_folder
2272
         assert last_actives[0] == main_folder
2309
             session=self.session,
2309
             session=self.session,
2310
             config=self.app_config,
2310
             config=self.app_config,
2311
         )
2311
         )
2312
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
2312
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
2313
         # creation order test
2313
         # creation order test
2314
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2315
-        secondly_created = api.create(ContentType.Page, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
2314
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2315
+        secondly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
2316
         # update order test
2316
         # update order test
2317
-        firstly_created_but_recently_updated = api.create(ContentType.Page, workspace, main_folder, 'update_order_test', '', True)  # nopep8
2318
-        secondly_created_but_not_updated = api.create(ContentType.Page, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
2317
+        firstly_created_but_recently_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'update_order_test', '', True)  # nopep8
2318
+        secondly_created_but_not_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
2319
         with new_revision(
2319
         with new_revision(
2320
             session=self.session,
2320
             session=self.session,
2321
             tm=transaction.manager,
2321
             tm=transaction.manager,
2324
             firstly_created_but_recently_updated.description = 'Just an update'
2324
             firstly_created_but_recently_updated.description = 'Just an update'
2325
         api.save(firstly_created_but_recently_updated)
2325
         api.save(firstly_created_but_recently_updated)
2326
         # comment change order
2326
         # comment change order
2327
-        firstly_created_but_recently_commented = api.create(ContentType.Page, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
2328
-        secondly_created_but_not_commented = api.create(ContentType.Page, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
2327
+        firstly_created_but_recently_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
2328
+        secondly_created_but_not_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
2329
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
2329
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
2330
 
2330
 
2331
         last_actives = api.get_last_active(workspace=workspace2)
2331
         last_actives = api.get_last_active(workspace=workspace2)
2366
             session=self.session,
2366
             session=self.session,
2367
             config=self.app_config,
2367
             config=self.app_config,
2368
         )
2368
         )
2369
-        a = api.create(ContentType.Folder, workspace, None,
2369
+        a = api.create(CONTENT_TYPES.Folder.slug, workspace, None,
2370
                        'this is randomized folder', '', True)
2370
                        'this is randomized folder', '', True)
2371
-        p = api.create(ContentType.Page, workspace, a,
2371
+        p = api.create(CONTENT_TYPES.Page.slug, workspace, a,
2372
                        'this is randomized label content', '', True)
2372
                        'this is randomized label content', '', True)
2373
 
2373
 
2374
         with new_revision(
2374
         with new_revision(
2422
             session=self.session,
2422
             session=self.session,
2423
             config=self.app_config,
2423
             config=self.app_config,
2424
         )
2424
         )
2425
-        a = api.create(ContentType.Folder, workspace, None,
2425
+        a = api.create(CONTENT_TYPES.Folder.slug, workspace, None,
2426
                        'this is randomized folder', '', True)
2426
                        'this is randomized folder', '', True)
2427
-        p = api.create(ContentType.Page, workspace, a,
2427
+        p = api.create(CONTENT_TYPES.Page.slug, workspace, a,
2428
                        'this is dummy label content', '', True)
2428
                        'this is dummy label content', '', True)
2429
 
2429
 
2430
         with new_revision(
2430
         with new_revision(
2476
             config=self.app_config,
2476
             config=self.app_config,
2477
         )
2477
         )
2478
         a = api.create(
2478
         a = api.create(
2479
-            content_type=ContentType.Folder,
2479
+            content_type_slug=CONTENT_TYPES.Folder.slug,
2480
             workspace=workspace,
2480
             workspace=workspace,
2481
             parent=None,
2481
             parent=None,
2482
             label='this is randomized folder',
2482
             label='this is randomized folder',
2483
             do_save=True
2483
             do_save=True
2484
         )
2484
         )
2485
         p1 = api.create(
2485
         p1 = api.create(
2486
-            content_type=ContentType.Page,
2486
+            content_type_slug=CONTENT_TYPES.Page.slug,
2487
             workspace=workspace,
2487
             workspace=workspace,
2488
             parent=a,
2488
             parent=a,
2489
             label='this is dummy label content',
2489
             label='this is dummy label content',
2490
             do_save=True
2490
             do_save=True
2491
         )
2491
         )
2492
         p2 = api.create(
2492
         p2 = api.create(
2493
-            content_type=ContentType.Page,
2493
+            content_type_slug=CONTENT_TYPES.Page.slug,
2494
             workspace=workspace,
2494
             workspace=workspace,
2495
             parent=a,
2495
             parent=a,
2496
             label='Hey ! Jon !',
2496
             label='Hey ! Jon !',
2540
         folder_1 = self._create_content_and_test(
2540
         folder_1 = self._create_content_and_test(
2541
             'folder_1',
2541
             'folder_1',
2542
             workspace=workspace,
2542
             workspace=workspace,
2543
-            type=ContentType.Folder
2543
+            type=CONTENT_TYPES.Folder.slug
2544
         )
2544
         )
2545
         folder_2 = self._create_content_and_test(
2545
         folder_2 = self._create_content_and_test(
2546
             'folder_2',
2546
             'folder_2',
2547
             workspace=workspace,
2547
             workspace=workspace,
2548
-            type=ContentType.Folder
2548
+            type=CONTENT_TYPES.Folder.slug
2549
         )
2549
         )
2550
         page_1 = self._create_content_and_test(
2550
         page_1 = self._create_content_and_test(
2551
             'foo', workspace=workspace,
2551
             'foo', workspace=workspace,
2552
-            type=ContentType.Page,
2552
+            type=CONTENT_TYPES.Page.slug,
2553
             parent=folder_1
2553
             parent=folder_1
2554
         )
2554
         )
2555
         page_2 = self._create_content_and_test(
2555
         page_2 = self._create_content_and_test(
2556
             'bar',
2556
             'bar',
2557
             workspace=workspace,
2557
             workspace=workspace,
2558
-            type=ContentType.Page,
2558
+            type=CONTENT_TYPES.Page.slug,
2559
             parent=folder_2
2559
             parent=folder_2
2560
         )
2560
         )
2561
 
2561
 
2636
             session=self.session,
2636
             session=self.session,
2637
             config=self.app_config,
2637
             config=self.app_config,
2638
         ).create(
2638
         ).create(
2639
-            content_type=ContentType.Page,
2639
+            content_type_slug=CONTENT_TYPES.Page.slug,
2640
             workspace=bob_workspace,
2640
             workspace=bob_workspace,
2641
             label='bob_page',
2641
             label='bob_page',
2642
             do_save=True,
2642
             do_save=True,
2647
             session=self.session,
2647
             session=self.session,
2648
             config=self.app_config,
2648
             config=self.app_config,
2649
         ).create(
2649
         ).create(
2650
-            content_type=ContentType.Page,
2650
+            content_type_slug=CONTENT_TYPES.Page.slug,
2651
             workspace=admin_workspace,
2651
             workspace=admin_workspace,
2652
             label='admin_page',
2652
             label='admin_page',
2653
             do_save=True,
2653
             do_save=True,

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

15
 from tracim_backend.models import User
15
 from tracim_backend.models import User
16
 from tracim_backend.models.data import ActionDescription
16
 from tracim_backend.models.data import ActionDescription
17
 from tracim_backend.models.data import ContentRevisionRO
17
 from tracim_backend.models.data import ContentRevisionRO
18
-from tracim_backend.models.data import ContentType
18
+from tracim_backend.models.contents import CONTENT_TYPES
19
 from tracim_backend.models.data import Workspace
19
 from tracim_backend.models.data import Workspace
20
 from tracim_backend.tests import StandardTest
20
 from tracim_backend.tests import StandardTest
21
 
21
 
95
 
95
 
96
         content1_from_api = api.get_one(
96
         content1_from_api = api.get_one(
97
             content1.id,
97
             content1.id,
98
-            ContentType.Page,
98
+            CONTENT_TYPES.Page.slug,
99
             workspace1
99
             workspace1
100
         )
100
         )
101
 
101
 
196
         first_content = self._create_content(
196
         first_content = self._create_content(
197
             owner=user_admin,
197
             owner=user_admin,
198
             workspace=workspace,
198
             workspace=workspace,
199
-            type=ContentType.Page,
199
+            type=CONTENT_TYPES.Page.slug,
200
             label='TEST_CONTENT_1',
200
             label='TEST_CONTENT_1',
201
             description='TEST_CONTENT_DESCRIPTION_1',
201
             description='TEST_CONTENT_DESCRIPTION_1',
202
             revision_type=ActionDescription.CREATION,
202
             revision_type=ActionDescription.CREATION,
221
         second_content = self._create_content(
221
         second_content = self._create_content(
222
             owner=user_admin,
222
             owner=user_admin,
223
             workspace=workspace,
223
             workspace=workspace,
224
-            type=ContentType.Page,
224
+            type=CONTENT_TYPES.Page.slug,
225
             label='TEST_CONTENT_2',
225
             label='TEST_CONTENT_2',
226
             description='TEST_CONTENT_DESCRIPTION_2',
226
             description='TEST_CONTENT_DESCRIPTION_2',
227
             revision_type=ActionDescription.CREATION
227
             revision_type=ActionDescription.CREATION
268
         created_content = self._create_content(
268
         created_content = self._create_content(
269
             owner=user_admin,
269
             owner=user_admin,
270
             workspace=workspace,
270
             workspace=workspace,
271
-            type=ContentType.Page,
271
+            type=CONTENT_TYPES.Page.slug,
272
             label='TEST_CONTENT_%s' % key,
272
             label='TEST_CONTENT_%s' % key,
273
             description='TEST_CONTENT_DESCRIPTION_%s' % key,
273
             description='TEST_CONTENT_DESCRIPTION_%s' % key,
274
             revision_type=ActionDescription.CREATION
274
             revision_type=ActionDescription.CREATION
308
         content = self._create_content(
308
         content = self._create_content(
309
             owner=user_admin,
309
             owner=user_admin,
310
             workspace=workspace,
310
             workspace=workspace,
311
-            type=ContentType.File,
311
+            type=CONTENT_TYPES.File.slug,
312
             label='TEST_CONTENT_1',
312
             label='TEST_CONTENT_1',
313
             description='TEST_CONTENT_DESCRIPTION_1',
313
             description='TEST_CONTENT_DESCRIPTION_1',
314
             revision_type=ActionDescription.CREATION,
314
             revision_type=ActionDescription.CREATION,

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

5
 
5
 
6
 from tracim_backend.models import ContentRevisionRO
6
 from tracim_backend.models import ContentRevisionRO
7
 from tracim_backend.models import User
7
 from tracim_backend.models import User
8
-from tracim_backend.models.data import ContentType
8
+from tracim_backend.models.contents import CONTENT_TYPES
9
 from tracim_backend.tests import DefaultTest
9
 from tracim_backend.tests import DefaultTest
10
 from tracim_backend.tests import eq_
10
 from tracim_backend.tests import eq_
11
 
11
 
12
+
12
 class TestContentRevision(DefaultTest):
13
 class TestContentRevision(DefaultTest):
13
 
14
 
14
     def _new_from(self, revision):
15
     def _new_from(self, revision):
47
         folder = self._create_content_and_test(
48
         folder = self._create_content_and_test(
48
             name='folder_1',
49
             name='folder_1',
49
             workspace=workspace,
50
             workspace=workspace,
50
-            type=ContentType.Folder
51
+            type=CONTENT_TYPES.Folder.slug
51
         )
52
         )
52
         page = self._create_content_and_test(
53
         page = self._create_content_and_test(
53
             workspace=workspace,
54
             workspace=workspace,
54
             parent=folder,
55
             parent=folder,
55
             name='file_1',
56
             name='file_1',
56
             description='content of file_1',
57
             description='content of file_1',
57
-            type=ContentType.Page,
58
+            type=CONTENT_TYPES.Page.slug,
58
             owner=admin
59
             owner=admin
59
         )
60
         )
60
 
61
 

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

20
 from tracim_backend.views.core_api.schemas import WorkspaceAndContentIdPathSchema
20
 from tracim_backend.views.core_api.schemas import WorkspaceAndContentIdPathSchema
21
 from tracim_backend.views.core_api.schemas import NoContentSchema
21
 from tracim_backend.views.core_api.schemas import NoContentSchema
22
 from tracim_backend.exceptions import EmptyCommentContentNotAllowed
22
 from tracim_backend.exceptions import EmptyCommentContentNotAllowed
23
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
23
+from tracim_backend.models.contents import CONTENT_TYPES
24
 from tracim_backend.models.revision_protection import new_revision
24
 from tracim_backend.models.revision_protection import new_revision
25
 from tracim_backend.models.data import UserRoleInWorkspace
25
 from tracim_backend.models.data import UserRoleInWorkspace
26
 
26
 
47
         )
47
         )
48
         content = api.get_one(
48
         content = api.get_one(
49
             hapic_data.path.content_id,
49
             hapic_data.path.content_id,
50
-            content_type=ContentType.Any
50
+            content_type=CONTENT_TYPES.Any_SLUG
51
         )
51
         )
52
         comments = content.get_comments()
52
         comments = content.get_comments()
53
         comments.sort(key=lambda comment: comment.created)
53
         comments.sort(key=lambda comment: comment.created)
74
         )
74
         )
75
         content = api.get_one(
75
         content = api.get_one(
76
             hapic_data.path.content_id,
76
             hapic_data.path.content_id,
77
-            content_type=ContentType.Any
77
+            content_type=CONTENT_TYPES.Any_SLUG
78
         )
78
         )
79
         comment = api.create_comment(
79
         comment = api.create_comment(
80
             content.workspace,
80
             content.workspace,
109
         workspace = wapi.get_one(hapic_data.path.workspace_id)
109
         workspace = wapi.get_one(hapic_data.path.workspace_id)
110
         parent = api.get_one(
110
         parent = api.get_one(
111
             hapic_data.path.content_id,
111
             hapic_data.path.content_id,
112
-            content_type=ContentType.Any,
112
+            content_type=CONTENT_TYPES.Any_SLUG,
113
             workspace=workspace
113
             workspace=workspace
114
         )
114
         )
115
         comment = api.get_one(
115
         comment = api.get_one(
116
             hapic_data.path.comment_id,
116
             hapic_data.path.comment_id,
117
-            content_type=ContentType.Comment,
117
+            content_type=CONTENT_TYPES.Comment.slug,
118
             workspace=workspace,
118
             workspace=workspace,
119
             parent=parent,
119
             parent=parent,
120
         )
120
         )

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

32
 from tracim_backend.models.data import UserRoleInWorkspace
32
 from tracim_backend.models.data import UserRoleInWorkspace
33
 from tracim_backend.models.context_models import ContentInContext
33
 from tracim_backend.models.context_models import ContentInContext
34
 from tracim_backend.models.context_models import RevisionInContext
34
 from tracim_backend.models.context_models import RevisionInContext
35
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
35
+from tracim_backend.models.contents import CONTENT_TYPES
36
 from tracim_backend.models.contents import file_type
36
 from tracim_backend.models.contents import file_type
37
 from tracim_backend.models.revision_protection import new_revision
37
 from tracim_backend.models.revision_protection import new_revision
38
 from tracim_backend.exceptions import EmptyLabelNotAllowed
38
 from tracim_backend.exceptions import EmptyLabelNotAllowed
67
         )
67
         )
68
         content = api.get_one(
68
         content = api.get_one(
69
             hapic_data.path.content_id,
69
             hapic_data.path.content_id,
70
-            content_type=ContentType.Any
70
+            content_type=CONTENT_TYPES.Any_SLUG
71
         )
71
         )
72
         file = request.POST['files']
72
         file = request.POST['files']
73
         with new_revision(
73
         with new_revision(
101
         )
101
         )
102
         content = api.get_one(
102
         content = api.get_one(
103
             hapic_data.path.content_id,
103
             hapic_data.path.content_id,
104
-            content_type=ContentType.Any
104
+            content_type=CONTENT_TYPES.Any_SLUG
105
         )
105
         )
106
         file = DepotManager.get().get(content.depot_file)
106
         file = DepotManager.get().get(content.depot_file)
107
         response = request.response
107
         response = request.response
126
         )
126
         )
127
         content = api.get_one(
127
         content = api.get_one(
128
             hapic_data.path.content_id,
128
             hapic_data.path.content_id,
129
-            content_type=ContentType.Any
129
+            content_type=CONTENT_TYPES.Any_SLUG
130
         )
130
         )
131
         revision = api.get_one_revision(
131
         revision = api.get_one_revision(
132
             revision_id=hapic_data.path.revision_id,
132
             revision_id=hapic_data.path.revision_id,
160
         )
160
         )
161
         content = api.get_one(
161
         content = api.get_one(
162
             hapic_data.path.content_id,
162
             hapic_data.path.content_id,
163
-            content_type=ContentType.Any
163
+            content_type=CONTENT_TYPES.Any_SLUG
164
         )
164
         )
165
         pdf_preview_path = api.get_pdf_preview_path(
165
         pdf_preview_path = api.get_pdf_preview_path(
166
             content.content_id,
166
             content.content_id,
187
         )
187
         )
188
         content = api.get_one(
188
         content = api.get_one(
189
             hapic_data.path.content_id,
189
             hapic_data.path.content_id,
190
-            content_type=ContentType.Any
190
+            content_type=CONTENT_TYPES.Any_SLUG
191
         )
191
         )
192
         pdf_preview_path = api.get_full_pdf_preview_path(content.revision_id)
192
         pdf_preview_path = api.get_full_pdf_preview_path(content.revision_id)
193
         return FileResponse(pdf_preview_path)
193
         return FileResponse(pdf_preview_path)
211
         )
211
         )
212
         content = api.get_one(
212
         content = api.get_one(
213
             hapic_data.path.content_id,
213
             hapic_data.path.content_id,
214
-            content_type=ContentType.Any
214
+            content_type=CONTENT_TYPES.Any_SLUG
215
         )
215
         )
216
         revision = api.get_one_revision(
216
         revision = api.get_one_revision(
217
             revision_id=hapic_data.path.revision_id,
217
             revision_id=hapic_data.path.revision_id,
244
         )
244
         )
245
         content = api.get_one(
245
         content = api.get_one(
246
             hapic_data.path.content_id,
246
             hapic_data.path.content_id,
247
-            content_type=ContentType.Any
247
+            content_type=CONTENT_TYPES.Any_SLUG
248
         )
248
         )
249
         allowed_dim = api.get_jpg_preview_allowed_dim()
249
         allowed_dim = api.get_jpg_preview_allowed_dim()
250
         jpg_preview_path = api.get_jpg_preview_path(
250
         jpg_preview_path = api.get_jpg_preview_path(
276
         )
276
         )
277
         content = api.get_one(
277
         content = api.get_one(
278
             hapic_data.path.content_id,
278
             hapic_data.path.content_id,
279
-            content_type=ContentType.Any
279
+            content_type=CONTENT_TYPES.Any_SLUG
280
         )
280
         )
281
         jpg_preview_path = api.get_jpg_preview_path(
281
         jpg_preview_path = api.get_jpg_preview_path(
282
             content_id=content.content_id,
282
             content_id=content.content_id,
307
         )
307
         )
308
         content = api.get_one(
308
         content = api.get_one(
309
             hapic_data.path.content_id,
309
             hapic_data.path.content_id,
310
-            content_type=ContentType.Any
310
+            content_type=CONTENT_TYPES.Any_SLUG
311
         )
311
         )
312
         revision = api.get_one_revision(
312
         revision = api.get_one_revision(
313
             revision_id=hapic_data.path.revision_id,
313
             revision_id=hapic_data.path.revision_id,
358
         )
358
         )
359
         content = api.get_one(
359
         content = api.get_one(
360
             hapic_data.path.content_id,
360
             hapic_data.path.content_id,
361
-            content_type=ContentType.Any
361
+            content_type=CONTENT_TYPES.Any_SLUG
362
         )
362
         )
363
         return api.get_content_in_context(content)
363
         return api.get_content_in_context(content)
364
 
364
 
381
         )
381
         )
382
         content = api.get_one(
382
         content = api.get_one(
383
             hapic_data.path.content_id,
383
             hapic_data.path.content_id,
384
-            content_type=ContentType.Any
384
+            content_type=CONTENT_TYPES.Any_SLUG
385
         )
385
         )
386
         with new_revision(
386
         with new_revision(
387
                 session=request.dbsession,
387
                 session=request.dbsession,
419
         )
419
         )
420
         content = api.get_one(
420
         content = api.get_one(
421
             hapic_data.path.content_id,
421
             hapic_data.path.content_id,
422
-            content_type=ContentType.Any
422
+            content_type=CONTENT_TYPES.Any_SLUG
423
         )
423
         )
424
         revisions = content.revisions
424
         revisions = content.revisions
425
         return [
425
         return [
446
         )
446
         )
447
         content = api.get_one(
447
         content = api.get_one(
448
             hapic_data.path.content_id,
448
             hapic_data.path.content_id,
449
-            content_type=ContentType.Any
449
+            content_type=CONTENT_TYPES.Any_SLUG
450
         )
450
         )
451
         with new_revision(
451
         with new_revision(
452
                 session=request.dbsession,
452
                 session=request.dbsession,

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

26
 from tracim_backend.exceptions import EmptyLabelNotAllowed
26
 from tracim_backend.exceptions import EmptyLabelNotAllowed
27
 from tracim_backend.models.context_models import ContentInContext
27
 from tracim_backend.models.context_models import ContentInContext
28
 from tracim_backend.models.context_models import RevisionInContext
28
 from tracim_backend.models.context_models import RevisionInContext
29
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
29
+from tracim_backend.models.contents import CONTENT_TYPES
30
 from tracim_backend.models.contents import html_documents_type
30
 from tracim_backend.models.contents import html_documents_type
31
 from tracim_backend.models.revision_protection import new_revision
31
 from tracim_backend.models.revision_protection import new_revision
32
 
32
 
52
         )
52
         )
53
         content = api.get_one(
53
         content = api.get_one(
54
             hapic_data.path.content_id,
54
             hapic_data.path.content_id,
55
-            content_type=ContentType.Any
55
+            content_type=CONTENT_TYPES.Any_SLUG
56
         )
56
         )
57
         return api.get_content_in_context(content)
57
         return api.get_content_in_context(content)
58
 
58
 
75
         )
75
         )
76
         content = api.get_one(
76
         content = api.get_one(
77
             hapic_data.path.content_id,
77
             hapic_data.path.content_id,
78
-            content_type=ContentType.Any
78
+            content_type=CONTENT_TYPES.Any_SLUG
79
         )
79
         )
80
         with new_revision(
80
         with new_revision(
81
                 session=request.dbsession,
81
                 session=request.dbsession,
113
         )
113
         )
114
         content = api.get_one(
114
         content = api.get_one(
115
             hapic_data.path.content_id,
115
             hapic_data.path.content_id,
116
-            content_type=ContentType.Any
116
+            content_type=CONTENT_TYPES.Any_SLUG
117
         )
117
         )
118
         revisions = content.revisions
118
         revisions = content.revisions
119
         return [
119
         return [
144
         )
144
         )
145
         content = api.get_one(
145
         content = api.get_one(
146
             hapic_data.path.content_id,
146
             hapic_data.path.content_id,
147
-            content_type=ContentType.Any
147
+            content_type=CONTENT_TYPES.Any_SLUG
148
         )
148
         )
149
         with new_revision(
149
         with new_revision(
150
                 session=request.dbsession,
150
                 session=request.dbsession,

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

25
 from tracim_backend.exceptions import EmptyLabelNotAllowed
25
 from tracim_backend.exceptions import EmptyLabelNotAllowed
26
 from tracim_backend.models.context_models import ContentInContext
26
 from tracim_backend.models.context_models import ContentInContext
27
 from tracim_backend.models.context_models import RevisionInContext
27
 from tracim_backend.models.context_models import RevisionInContext
28
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
28
+from tracim_backend.models.contents import CONTENT_TYPES
29
 from tracim_backend.models.contents import thread_type
29
 from tracim_backend.models.contents import thread_type
30
 from tracim_backend.models.revision_protection import new_revision
30
 from tracim_backend.models.revision_protection import new_revision
31
 
31
 
51
         )
51
         )
52
         content = api.get_one(
52
         content = api.get_one(
53
             hapic_data.path.content_id,
53
             hapic_data.path.content_id,
54
-            content_type=ContentType.Any
54
+            content_type=CONTENT_TYPES.Any_SLUG
55
         )
55
         )
56
         return api.get_content_in_context(content)
56
         return api.get_content_in_context(content)
57
 
57
 
74
         )
74
         )
75
         content = api.get_one(
75
         content = api.get_one(
76
             hapic_data.path.content_id,
76
             hapic_data.path.content_id,
77
-            content_type=ContentType.Any
77
+            content_type=CONTENT_TYPES.Any_SLUG
78
         )
78
         )
79
         with new_revision(
79
         with new_revision(
80
                 session=request.dbsession,
80
                 session=request.dbsession,
112
         )
112
         )
113
         content = api.get_one(
113
         content = api.get_one(
114
             hapic_data.path.content_id,
114
             hapic_data.path.content_id,
115
-            content_type=ContentType.Any
115
+            content_type=CONTENT_TYPES.Any_SLUG
116
         )
116
         )
117
         revisions = content.revisions
117
         revisions = content.revisions
118
         return [
118
         return [
138
         )
138
         )
139
         content = api.get_one(
139
         content = api.get_one(
140
             hapic_data.path.content_id,
140
             hapic_data.path.content_id,
141
-            content_type=ContentType.Any
141
+            content_type=CONTENT_TYPES.Any_SLUG
142
         )
142
         )
143
         with new_revision(
143
         with new_revision(
144
                 session=request.dbsession,
144
                 session=request.dbsession,

+ 24 - 18
backend/tracim_backend/views/core_api/schemas.py View File

8
 from tracim_backend.lib.utils.utils import DATETIME_FORMAT
8
 from tracim_backend.lib.utils.utils import DATETIME_FORMAT
9
 from tracim_backend.models.auth import Profile
9
 from tracim_backend.models.auth import Profile
10
 from tracim_backend.models.contents import GlobalStatus
10
 from tracim_backend.models.contents import GlobalStatus
11
+from tracim_backend.models.contents import CONTENT_STATUS
12
+from tracim_backend.models.contents import CONTENT_TYPES
11
 from tracim_backend.models.contents import open_status
13
 from tracim_backend.models.contents import open_status
12
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
13
-from tracim_backend.models.contents import ContentStatusLegacy as ContentStatus
14
 from tracim_backend.models.context_models import ActiveContentFilter
14
 from tracim_backend.models.context_models import ActiveContentFilter
15
 from tracim_backend.models.context_models import AutocompleteQuery
15
 from tracim_backend.models.context_models import AutocompleteQuery
16
 from tracim_backend.models.context_models import ContentIdsQuery
16
 from tracim_backend.models.context_models import ContentIdsQuery
355
         validate=Range(min=0, max=1, error="Value must be 0 or 1"),
355
         validate=Range(min=0, max=1, error="Value must be 0 or 1"),
356
     )
356
     )
357
     content_type = marshmallow.fields.String(
357
     content_type = marshmallow.fields.String(
358
-        example=ContentType.Any,
359
-        default=ContentType.Any,
360
-        validate=OneOf(ContentType.allowed_type_values())
358
+        example=CONTENT_TYPES.Any_SLUG,
359
+        default=CONTENT_TYPES.Any_SLUG,
360
+        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug())
361
     )
361
     )
362
 
362
 
363
     @post_load
363
     @post_load
373
                     'the first limit elem (according to offset)',
373
                     'the first limit elem (according to offset)',
374
         validate=Range(min=0, error="Value must be positive or 0"),
374
         validate=Range(min=0, error="Value must be positive or 0"),
375
     )
375
     )
376
-    before_datetime = marshmallow.fields.DateTime(
377
-        format=DATETIME_FORMAT,
378
-        description='return only content lastly updated before this date',
376
+    before_content_id = marshmallow.fields.Int(
377
+        example=41,
378
+        default=None,
379
+        allow_none=True,
380
+        description='return only content updated before this content',
379
     )
381
     )
380
     @post_load
382
     @post_load
381
     def make_content_filter(self, data):
383
     def make_content_filter(self, data):
605
 class ContentTypeSchema(marshmallow.Schema):
607
 class ContentTypeSchema(marshmallow.Schema):
606
     slug = marshmallow.fields.String(
608
     slug = marshmallow.fields.String(
607
         example='pagehtml',
609
         example='pagehtml',
608
-        validate=OneOf(ContentType.allowed_types()),
610
+        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),
609
     )
611
     )
610
     fa_icon = marshmallow.fields.String(
612
     fa_icon = marshmallow.fields.String(
611
         example='fa-file-text-o',
613
         example='fa-file-text-o',
658
         description='Title of the content to create'
660
         description='Title of the content to create'
659
     )
661
     )
660
     content_type = marshmallow.fields.String(
662
     content_type = marshmallow.fields.String(
661
-        example='html-documents',
662
-        validate=OneOf(ContentType.allowed_types_for_folding()),  # nopep8
663
+        example='html-document',
664
+        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),
663
     )
665
     )
664
     parent_id = marshmallow.fields.Integer(
666
     parent_id = marshmallow.fields.Integer(
665
         example=35,
667
         example=35,
666
-        description='content_id of parent content, if content should be placed in a folder, this should be folder content_id.'
668
+        description='content_id of parent content, if content should be placed in a folder, this should be folder content_id.', # nopep8
669
+        allow_none=True,
670
+        default=None,
671
+        validate=Range(min=1, error="Value must be positive"),
667
     )
672
     )
668
 
673
 
669
 
674
 
670
     @post_load
675
     @post_load
671
-    def make_content_filter(self, data):
676
+    def make_content_creation(self, data):
672
         return ContentCreation(**data)
677
         return ContentCreation(**data)
673
 
678
 
674
 
679
 
690
     )
695
     )
691
     label = marshmallow.fields.Str(example='Intervention Report 12')
696
     label = marshmallow.fields.Str(example='Intervention Report 12')
692
     content_type = marshmallow.fields.Str(
697
     content_type = marshmallow.fields.Str(
693
-        example='html-documents',
694
-        validate=OneOf(ContentType.allowed_types()),
698
+        example='html-document',
699
+        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),
695
     )
700
     )
696
     sub_content_types = marshmallow.fields.List(
701
     sub_content_types = marshmallow.fields.List(
697
         marshmallow.fields.String(
702
         marshmallow.fields.String(
698
             example='html-content',
703
             example='html-content',
699
-            validate=OneOf(ContentType.allowed_types())
704
+            validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug())
700
         ),
705
         ),
701
         description='list of content types allowed as sub contents. '
706
         description='list of content types allowed as sub contents. '
702
                     'This field is required for folder contents, '
707
                     'This field is required for folder contents, '
704
     )
709
     )
705
     status = marshmallow.fields.Str(
710
     status = marshmallow.fields.Str(
706
         example='closed-deprecated',
711
         example='closed-deprecated',
707
-        validate=OneOf(ContentStatus.allowed_values()),
712
+        validate=OneOf(CONTENT_STATUS.get_all_slugs_values()),
708
         description='this slug is found in content_type available statuses',
713
         description='this slug is found in content_type available statuses',
709
         default=open_status
714
         default=open_status
710
     )
715
     )
729
 # Content
734
 # Content
730
 #####
735
 #####
731
 
736
 
737
+
732
 class ContentSchema(ContentDigestSchema):
738
 class ContentSchema(ContentDigestSchema):
733
     current_revision_id = marshmallow.fields.Int(example=12)
739
     current_revision_id = marshmallow.fields.Int(example=12)
734
     created = marshmallow.fields.DateTime(
740
     created = marshmallow.fields.DateTime(
848
 class SetContentStatusSchema(marshmallow.Schema):
854
 class SetContentStatusSchema(marshmallow.Schema):
849
     status = marshmallow.fields.Str(
855
     status = marshmallow.fields.Str(
850
         example='closed-deprecated',
856
         example='closed-deprecated',
851
-        validate=OneOf(ContentStatus.allowed_values()),
857
+        validate=OneOf(CONTENT_STATUS.get_all_slugs_values()),
852
         description='this slug is found in content_type available statuses',
858
         description='this slug is found in content_type available statuses',
853
         default=open_status,
859
         default=open_status,
854
         required=True,
860
         required=True,

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

5
 from tracim_backend.lib.utils.authorization import require_profile
5
 from tracim_backend.lib.utils.authorization import require_profile
6
 from tracim_backend.models import Group
6
 from tracim_backend.models import Group
7
 from tracim_backend.models.applications import applications
7
 from tracim_backend.models.applications import applications
8
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
8
+from tracim_backend.models.contents import CONTENT_TYPES
9
 
9
 
10
 try:  # Python 3.5+
10
 try:  # Python 3.5+
11
     from http import HTTPStatus
11
     from http import HTTPStatus
39
         """
39
         """
40
         Get list of alls content types availables in this tracim instance.
40
         Get list of alls content types availables in this tracim instance.
41
         """
41
         """
42
-        content_types_slugs = ContentType.allowed_types_for_folding()
43
-        content_types = [ContentType(slug) for slug in content_types_slugs]
42
+        content_types_slugs = CONTENT_TYPES.endpoint_allowed_types_slug()
43
+        content_types = [CONTENT_TYPES.get_one_by_slug(slug) for slug in content_types_slugs]
44
         return content_types
44
         return content_types
45
 
45
 
46
     def bind(self, configurator: Configurator) -> None:
46
     def bind(self, configurator: Configurator) -> None:

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

1
 from pyramid.config import Configurator
1
 from pyramid.config import Configurator
2
+
2
 try:  # Python 3.5+
3
 try:  # Python 3.5+
3
     from http import HTTPStatus
4
     from http import HTTPStatus
4
 except ImportError:
5
 except ImportError:
33
 from tracim_backend.views.core_api.schemas import ContentDigestSchema
34
 from tracim_backend.views.core_api.schemas import ContentDigestSchema
34
 from tracim_backend.views.core_api.schemas import ActiveContentFilterQuerySchema
35
 from tracim_backend.views.core_api.schemas import ActiveContentFilterQuerySchema
35
 from tracim_backend.views.core_api.schemas import WorkspaceDigestSchema
36
 from tracim_backend.views.core_api.schemas import WorkspaceDigestSchema
37
+from tracim_backend.models.contents import CONTENT_TYPES
36
 
38
 
37
 SWAGGER_TAG__USER_ENDPOINTS = 'Users'
39
 SWAGGER_TAG__USER_ENDPOINTS = 'Users'
38
 
40
 
308
         workspace = None
310
         workspace = None
309
         if hapic_data.path.workspace_id:
311
         if hapic_data.path.workspace_id:
310
             workspace = wapi.get_one(hapic_data.path.workspace_id)
312
             workspace = wapi.get_one(hapic_data.path.workspace_id)
313
+        before_content = None
314
+        if content_filter.before_content_id:
315
+            before_content = api.get_one(
316
+                content_id=content_filter.before_content_id,
317
+                workspace=workspace,
318
+                content_type=CONTENT_TYPES.Any_SLUG
319
+            )
311
         last_actives = api.get_last_active(
320
         last_actives = api.get_last_active(
312
             workspace=workspace,
321
             workspace=workspace,
313
             limit=content_filter.limit or None,
322
             limit=content_filter.limit or None,
314
-            before_datetime=content_filter.before_datetime or None,
323
+            before_content=before_content,
315
         )
324
         )
316
         return [
325
         return [
317
             api.get_content_in_context(content)
326
             api.get_content_in_context(content)
345
         last_actives = api.get_last_active(
354
         last_actives = api.get_last_active(
346
             workspace=workspace,
355
             workspace=workspace,
347
             limit=None,
356
             limit=None,
348
-            before_datetime=None,
357
+            before_content=None,
349
             content_ids=hapic_data.query.contents_ids or None
358
             content_ids=hapic_data.query.contents_ids or None
350
         )
359
         )
351
         return [
360
         return [

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

46
 from tracim_backend.views.core_api.schemas import WorkspaceSchema
46
 from tracim_backend.views.core_api.schemas import WorkspaceSchema
47
 from tracim_backend.views.core_api.schemas import WorkspaceIdPathSchema
47
 from tracim_backend.views.core_api.schemas import WorkspaceIdPathSchema
48
 from tracim_backend.views.core_api.schemas import WorkspaceMemberSchema
48
 from tracim_backend.views.core_api.schemas import WorkspaceMemberSchema
49
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
49
+from tracim_backend.models.contents import CONTENT_TYPES
50
 from tracim_backend.models.revision_protection import new_revision
50
 from tracim_backend.models.revision_protection import new_revision
51
 
51
 
52
 SWAGGER_TAG_WORKSPACE_ENDPOINTS = 'Workspaces'
52
 SWAGGER_TAG_WORKSPACE_ENDPOINTS = 'Workspaces'
259
         contents = api.get_all(
259
         contents = api.get_all(
260
             parent_id=content_filter.parent_id,
260
             parent_id=content_filter.parent_id,
261
             workspace=request.current_workspace,
261
             workspace=request.current_workspace,
262
-            content_type=content_filter.content_type or ContentType.Any,
262
+            content_type=content_filter.content_type or CONTENT_TYPES.Any_SLUG,
263
         )
263
         )
264
         contents = [
264
         contents = [
265
             api.get_content_in_context(content) for content in contents
265
             api.get_content_in_context(content) for content in contents
291
         parent = None
291
         parent = None
292
         if creation_data.parent_id:
292
         if creation_data.parent_id:
293
             try:
293
             try:
294
-                parent = api.get_one(content_id=creation_data.parent_id, content_type=ContentType.Any)  # nopep8
294
+                parent = api.get_one(content_id=creation_data.parent_id, content_type=CONTENT_TYPES.Any_SLUG)  # nopep8
295
             except ContentNotFound as exc:
295
             except ContentNotFound as exc:
296
                 raise ParentNotFound(
296
                 raise ParentNotFound(
297
                     'Parent with content_id {} not found'.format(creation_data.parent_id)
297
                     'Parent with content_id {} not found'.format(creation_data.parent_id)
298
                 ) from exc
298
                 ) from exc
299
         content = api.create(
299
         content = api.create(
300
             label=creation_data.label,
300
             label=creation_data.label,
301
-            content_type=creation_data.content_type,
301
+            content_type_slug=creation_data.content_type,
302
             workspace=request.current_workspace,
302
             workspace=request.current_workspace,
303
             parent=parent,
303
             parent=parent,
304
         )
304
         )
333
         )
333
         )
334
         content = api.get_one(
334
         content = api.get_one(
335
             path_data.content_id,
335
             path_data.content_id,
336
-            content_type=ContentType.Any
336
+            content_type=CONTENT_TYPES.Any_SLUG
337
         )
337
         )
338
         new_parent = api.get_one(
338
         new_parent = api.get_one(
339
-            move_data.new_parent_id, content_type=ContentType.Any
339
+            move_data.new_parent_id, content_type=CONTENT_TYPES.Any_SLUG
340
         )
340
         )
341
 
341
 
342
         new_workspace = request.candidate_workspace
342
         new_workspace = request.candidate_workspace
354
             )
354
             )
355
         updated_content = api.get_one(
355
         updated_content = api.get_one(
356
             path_data.content_id,
356
             path_data.content_id,
357
-            content_type=ContentType.Any
357
+            content_type=CONTENT_TYPES.Any_SLUG
358
         )
358
         )
359
         return api.get_content_in_context(updated_content)
359
         return api.get_content_in_context(updated_content)
360
 
360
 
380
         )
380
         )
381
         content = api.get_one(
381
         content = api.get_one(
382
             path_data.content_id,
382
             path_data.content_id,
383
-            content_type=ContentType.Any
383
+            content_type=CONTENT_TYPES.Any_SLUG
384
         )
384
         )
385
         with new_revision(
385
         with new_revision(
386
                 session=request.dbsession,
386
                 session=request.dbsession,
413
         )
413
         )
414
         content = api.get_one(
414
         content = api.get_one(
415
             path_data.content_id,
415
             path_data.content_id,
416
-            content_type=ContentType.Any
416
+            content_type=CONTENT_TYPES.Any_SLUG
417
         )
417
         )
418
         with new_revision(
418
         with new_revision(
419
                 session=request.dbsession,
419
                 session=request.dbsession,
443
             session=request.dbsession,
443
             session=request.dbsession,
444
             config=app_config,
444
             config=app_config,
445
         )
445
         )
446
-        content = api.get_one(path_data.content_id, content_type=ContentType.Any)  # nopep8
446
+        content = api.get_one(path_data.content_id, content_type=CONTENT_TYPES.Any_SLUG)  # nopep8
447
         with new_revision(
447
         with new_revision(
448
                 session=request.dbsession,
448
                 session=request.dbsession,
449
                 tm=transaction.manager,
449
                 tm=transaction.manager,
475
         )
475
         )
476
         content = api.get_one(
476
         content = api.get_one(
477
             path_data.content_id,
477
             path_data.content_id,
478
-            content_type=ContentType.Any
478
+            content_type=CONTENT_TYPES.Any_SLUG
479
         )
479
         )
480
         with new_revision(
480
         with new_revision(
481
                 session=request.dbsession,
481
                 session=request.dbsession,

+ 59 - 0
build_full_frontend.sh View File

9
 
9
 
10
 echo -e "\n${BROWN}/!\ ${NC}this script does not run 'npm install'\n${BROWN}/!\ ${NC}it also assumes your webpack dev server of frontend is running"
10
 echo -e "\n${BROWN}/!\ ${NC}this script does not run 'npm install'\n${BROWN}/!\ ${NC}it also assumes your webpack dev server of frontend is running"
11
 
11
 
12
+# Tracim Lib
13
+
12
 log "cd frontend_lib"
14
 log "cd frontend_lib"
13
 cd frontend_lib
15
 cd frontend_lib
14
 log "npm run buildtracimlib$windoz"
16
 log "npm run buildtracimlib$windoz"
15
 npm run buildtracimlib$windoz
17
 npm run buildtracimlib$windoz
16
 cd -
18
 cd -
17
 
19
 
20
+# app Html Document
21
+
18
 log "cd frontend_app_html-document"
22
 log "cd frontend_app_html-document"
19
 cd frontend_app_html-document
23
 cd frontend_app_html-document
24
+
20
 log "npm run build$windoz # for frontend_app_html-document"
25
 log "npm run build$windoz # for frontend_app_html-document"
21
 npm run build$windoz
26
 npm run build$windoz
27
+
22
 log "cp dist/html-document.app.js"
28
 log "cp dist/html-document.app.js"
23
 cp dist/html-document.app.js ../frontend/dist/app
29
 cp dist/html-document.app.js ../frontend/dist/app
30
+
31
+log "cp i18next.scanner/en/translation.json ../frontend/dist/app/tml-document_en_translation.json"
32
+cp i18next.scanner/en/translation.json ../frontend/dist/app/html-document_en_translation.json
33
+
34
+log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/html-document_fr_translation.json"
35
+cp i18next.scanner/fr/translation.json ../frontend/dist/app/html-document_fr_translation.json
24
 cd -
36
 cd -
25
 
37
 
38
+# app Thread
39
+
26
 log "cd frontend_app_thread"
40
 log "cd frontend_app_thread"
27
 cd frontend_app_thread
41
 cd frontend_app_thread
42
+
28
 log "npm run build$windoz # for frontend_app_thread"
43
 log "npm run build$windoz # for frontend_app_thread"
29
 npm run build$windoz
44
 npm run build$windoz
45
+
30
 log "cp dist/thread.app.js"
46
 log "cp dist/thread.app.js"
31
 cp dist/thread.app.js ../frontend/dist/app
47
 cp dist/thread.app.js ../frontend/dist/app
48
+
49
+log "cp i18next.scanner/en/translation.json ../frontend/dist/app/thread_en_translation.json"
50
+cp i18next.scanner/en/translation.json ../frontend/dist/app/thread_en_translation.json
51
+
52
+log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/thread_fr_translation.json"
53
+cp i18next.scanner/fr/translation.json ../frontend/dist/app/thread_fr_translation.json
54
+cd -
55
+
56
+# app Workspace
57
+
58
+log "cd frontend_app_workspace"
59
+cd frontend_app_workspace
60
+
61
+log "npm run build$windoz # for frontend_app_workspace"
62
+npm run build$windoz
63
+
64
+log "cp dist/workspace.app.js"
65
+cp dist/workspace.app.js ../frontend/dist/app
66
+
67
+log "cp i18next.scanner/en/translation.json ../frontend/dist/app/workspace_en_translation.json"
68
+cp i18next.scanner/en/translation.json ../frontend/dist/app/workspace_en_translation.json
69
+
70
+log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/workspace_fr_translation.json"
71
+cp i18next.scanner/fr/translation.json ../frontend/dist/app/workspace_fr_translation.json
72
+cd -
73
+
74
+# app Admin Workspace User
75
+
76
+log "cd frontend_app_admin_workspace_user"
77
+cd frontend_app_admin_workspace_user
78
+
79
+log "npm run build$windoz # for frontend_app_thread"
80
+npm run build$windoz
81
+
82
+log "cp dist/admin_workspace_user.app.js"
83
+cp dist/admin_workspace_user.app.js ../frontend/dist/app
84
+
85
+log "cp i18next.scanner/en/translation.json ../frontend/dist/app/admin_workspace_user_en_translation.json"
86
+cp i18next.scanner/en/translation.json ../frontend/dist/app/admin_workspace_user_en_translation.json
87
+
88
+log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/admin_workspace_user_fr_translation.json"
89
+cp i18next.scanner/fr/translation.json ../frontend/dist/app/admin_workspace_user_fr_translation.json
90
+cd -

+ 1 - 1
frontend/README.md View File

58
 You also need to make the mock api able to tell tracim_frontend that it handle you app :
58
 You also need to make the mock api able to tell tracim_frontend that it handle you app :
59
 - add an entry for you App in tracim_frontend/jsonserver/static_db.json in the `app_config` property
59
 - add an entry for you App in tracim_frontend/jsonserver/static_db.json in the `app_config` property
60
 - reload your mock api server
60
 - reload your mock api server
61
-- add the source of your app in tracim_frontend/dist/index.html and an entry to the switch case of the function `GLOBAL_renderAppFull`. All of this will be handled by backend later on, this is all work in progress stuffs.
61
+- add the source of your app in tracim_frontend/dist/index.html and an entry to the switch case of the function `GLOBAL_renderAppFeature`. All of this will be handled by backend later on, this is all work in progress stuffs.
62
 
62
 
63
 
63
 
64
 #### Urls list
64
 #### Urls list

+ 25 - 6
frontend/dist/appInterface.js View File

3
 
3
 
4
   getSelectedApp = name => {
4
   getSelectedApp = name => {
5
     switch (name) {
5
     switch (name) {
6
-      case 'html-documents':
6
+      case 'workspace':
7
+        return appWorkspace
8
+      case 'html-document':
7
         return appHtmlDocument
9
         return appHtmlDocument
8
       case 'thread':
10
       case 'thread':
9
         return appThread
11
         return appThread
10
       case 'file':
12
       case 'file':
11
         return appFile
13
         return appFile
14
+      case 'admin_workspace_user':
15
+        return appAdminWorkspaceUser
12
       default:
16
       default:
13
         return null
17
         return null
14
     }
18
     }
19
   // use module.export and require
23
   // use module.export and require
20
   // doesn't work, cant resolve a file outside of the build dir
24
   // doesn't work, cant resolve a file outside of the build dir
21
 
25
 
22
-  GLOBAL_renderAppFull = app => {
23
-    console.log('%cGLOBAL_renderAppFull', 'color: #5cebeb', app)
26
+  GLOBAL_renderAppFeature = app => {
27
+    console.log('%cGLOBAL_renderAppFeature', 'color: #5cebeb', app)
24
 
28
 
25
     const selectedApp = getSelectedApp(app.config.slug)
29
     const selectedApp = getSelectedApp(app.config.slug)
26
 
30
 
27
     if (selectedApp.isRendered) {
31
     if (selectedApp.isRendered) {
28
-      GLOBAL_dispatchEvent({type: `${app.config.slug}_showApp`, data: app}) // handled by html-documents:src/container/HtmlDocument.jsx
32
+      GLOBAL_dispatchEvent({type: `${app.config.slug}_showApp`, data: app}) // handled by html-documents:src/container/AdminWorkspaceUser.jsx
29
     } else {
33
     } else {
30
-      selectedApp.renderAppFull(app)
34
+      selectedApp.renderAppFeature(app)
35
+      selectedApp.isRendered = true
36
+      prevSelectedApp.isRendered = false
37
+      prevSelectedApp = selectedApp
38
+    }
39
+  }
40
+
41
+  GLOBAL_renderAppFullscreen = app => {
42
+    console.log('%cGLOBAL_renderAppFullscreen', 'color: #5cebeb', app)
43
+
44
+    const selectedApp = getSelectedApp(app.config.slug)
45
+
46
+    if (selectedApp.isRendered) {
47
+      GLOBAL_dispatchEvent({type: `${app.config.slug}_showApp`, data: app}) // handled by html-documents:src/container/AdminWorkspaceUser.jsx
48
+    } else {
49
+      selectedApp.renderAppFullscreen(app)
31
       selectedApp.isRendered = true
50
       selectedApp.isRendered = true
32
       prevSelectedApp.isRendered = false
51
       prevSelectedApp.isRendered = false
33
       prevSelectedApp = selectedApp
52
       prevSelectedApp = selectedApp
64
         console.log('%cGLOBAL_eventReducer Custom Event', 'color: #28a745', type, data)
83
         console.log('%cGLOBAL_eventReducer Custom Event', 'color: #28a745', type, data)
65
         if (prevSelectedApp.name === '') return
84
         if (prevSelectedApp.name === '') return
66
 
85
 
67
-        prevSelectedApp.unmountApp('appContainer')
86
+        prevSelectedApp.unmountApp('appFeatureContainer')
68
         prevSelectedApp.unmountApp('popupCreateContentContainer')
87
         prevSelectedApp.unmountApp('popupCreateContentContainer')
69
         prevSelectedApp.isRendered = false
88
         prevSelectedApp.isRendered = false
70
         break
89
         break

+ 2 - 0
frontend/dist/index.html View File

51
     <script src='/tracim.vendor.bundle.js'></script>
51
     <script src='/tracim.vendor.bundle.js'></script>
52
     <script src='/tracim.app.entry.js'></script>
52
     <script src='/tracim.app.entry.js'></script>
53
 
53
 
54
+    <script src='/app/workspace.app.js'></script>
54
     <script src='/app/html-document.app.js'></script>
55
     <script src='/app/html-document.app.js'></script>
55
     <script src='/app/thread.app.js'></script>
56
     <script src='/app/thread.app.js'></script>
56
     <!-- <script src='/app/file.app.js'></script> -->
57
     <!-- <script src='/app/file.app.js'></script> -->
58
+    <script src='/app/admin_workspace_user.app.js'></script>
57
 
59
 
58
     <script src="/dev/jquery-3.2.1.js"></script>
60
     <script src="/dev/jquery-3.2.1.js"></script>
59
     <script src="/dev/popper-1.12.3.js"></script>
61
     <script src="/dev/popper-1.12.3.js"></script>

+ 16 - 0
frontend/i18next.scanner.js View File

1
+const scanner = require('i18next-scanner')
2
+const vfs = require('vinyl-fs')
3
+
4
+const option = require('../i18next.option.js')
5
+
6
+// this script is run by npm run build-translation and generate i18next.scanner/**/*.json
7
+
8
+// --------------------
9
+// 2018/07/27 - currently, last version is 2.6.5 but a bug is spaming log with errors. So I'm using 2.6.1
10
+// this issue seems related : https://github.com/i18next/i18next-scanner/issues/88
11
+// --------------------
12
+
13
+vfs.src(['./src/**/*.jsx'])
14
+  // .pipe(sort()) // Sort files in stream by path
15
+  .pipe(scanner(option))
16
+  .pipe(vfs.dest('./i18next.scanner'))

+ 68 - 0
frontend/i18next.scanner/en/translation.json View File

1
+{
2
+  "Create your own collaborative workspaces on trac.im": "Create your own collaborative workspaces on trac.im",
3
+  "Copyright 2013 - 2017": "Copyright 2013 - 2017",
4
+  "Disconnection error": "Disconnection error",
5
+  "Error": "Error",
6
+  "Email or password invalid": "Email or password invalid",
7
+  "Type": "Type",
8
+  "Status": "Status",
9
+  "Create in folder...": "Create in folder...",
10
+  "Search...": "Search...",
11
+  "Create a workspace": "Create a workspace",
12
+  "Title": "Title",
13
+  "Change": "Change",
14
+  "Move": "Move",
15
+  "Download": "Download",
16
+  "Archive": "Archive",
17
+  "Delete": "Delete",
18
+  "My Account": "My Account",
19
+  "Logout": "Logout",
20
+  "Notification": "Notification",
21
+  "Archive Topic": "Archive Topic",
22
+  "Deleted File": "Deleted File",
23
+  "Send": "Send",
24
+  "Workspace and notifications": "Workspace and notifications",
25
+  "Workspace": "Workspace",
26
+  "Role": "Role",
27
+  "Account information": "Account information",
28
+  "Change your password": "Change your password",
29
+  "Password": "Password",
30
+  "Change your name": "Change your name",
31
+  "Change your email": "Change your email",
32
+  "Change your Timezone": "Change your Timezone",
33
+  "Calendar": "Calendar",
34
+  "Access your personal calendar": "Access your personal calendar",
35
+  "Dashboard": "Dashboard",
36
+  "Active advanced Dashboard": "Active advanced Dashboard",
37
+  "Change your status": "Change your status",
38
+  "subscriber": "subscriber",
39
+  "unsubscribed": "unsubscribed",
40
+  "Start a new Thread": "Start a new Thread",
41
+  "Writing a document": "Writing a document",
42
+  "Upload a file": "Upload a file",
43
+  "Start a videoconference": "Start a videoconference",
44
+  "View the Calendar": "View the Calendar",
45
+  "Recent activity": "Recent activity",
46
+  "Mark everything as read": "Mark everything as read",
47
+  "See more": "See more",
48
+  "Member List": "Member List",
49
+  "Add a member": "Add a member",
50
+  "Enter the name or email of the member": "Enter the name or email of the member",
51
+  "Create an account": "Create an account",
52
+  "Choose the role of the member": "Choose the role of the member",
53
+  "Supervisor": "Supervisor",
54
+  "Content Manager": "Content Manager",
55
+  "Contributor": "Contributor",
56
+  "Reader": "Reader",
57
+  "Validate": "Validate",
58
+  "Implement Tracim in your explorer": "Implement Tracim in your explorer",
59
+  "Find all your documents deposited online directly on your computer via the workstation, without going through the software.": "Find all your documents deposited online directly on your computer via the workstation, without going through the software.",
60
+  "Workspace Calendar": "Workspace Calendar",
61
+  "Each workspace has its own calendar.": "Each workspace has its own calendar.",
62
+  "Email Adress": "Email Adress",
63
+  "Explore the workspace": "Explore the workspace",
64
+  "Old password": "Old password",
65
+  "New password": "New password",
66
+  "Name:": "Name:",
67
+  "Email Adress:": "Email Adress:"
68
+}

+ 68 - 0
frontend/i18next.scanner/fr/translation.json View File

1
+{
2
+  "Create your own collaborative workspaces on trac.im": "Créer votre propre espace de travail sur trac.im",
3
+  "Copyright 2013 - 2017": "Copyright 2013 - 2017",
4
+  "Disconnection error": "Erreur de déconnexion",
5
+  "Error": "Erreur",
6
+  "Email or password invalid": "Email ou mot de passe invalid",
7
+  "Type": "Type",
8
+  "Status": "Statut",
9
+  "Create in folder...": "Créer dans le dossier ...",
10
+  "Search...": "Rechercher ...",
11
+  "Create a workspace": "Créer un espace de travail",
12
+  "Title": "Titre",
13
+  "Change": "Modifier",
14
+  "Move": "Déplacer",
15
+  "Download": "Télécharger",
16
+  "Archive": "Archiver",
17
+  "Delete": "Supprimer",
18
+  "My Account": "Mon Compte",
19
+  "Logout": "Se Déconnecter",
20
+  "Notification": "Notification",
21
+  "Archive Topic": "Conversation archivée",
22
+  "Deleted File": "Fichier supprimé",
23
+  "Send": "Envoyer",
24
+  "Workspace and notifications": "Espace de travail & Notifications",
25
+  "Workspace": "Espace de travail",
26
+  "Role": "Rôle",
27
+  "Account information": "Information du compte",
28
+  "Change your password": "Changer votre mot de passe",
29
+  "Password": "Mot de passe",
30
+  "Change your name": "Modifier votre nom",
31
+  "Change your email": "Modifier votre email",
32
+  "Change your Timezone": "Modifier votre fuseau horaire",
33
+  "Calendar": "Calendrier",
34
+  "Access your personal calendar": "Accéder à votre calendrier personnel",
35
+  "Dashboard": "Tableau de bord",
36
+  "Active advanced Dashboard": "Activer le mode avancé ",
37
+  "Change your status": "Changer de status",
38
+  "subscriber": "Abonné(e)",
39
+  "unsubscribed": "Non Abonné(e)",
40
+  "Start a new Thread": "Débuter une nouvelle discussion",
41
+  "Writing a document": "Rédiger un document",
42
+  "Upload a file": "Importer un fichier",
43
+  "Start a videoconference": "Débuter une visioconférence",
44
+  "View the Calendar": "Voir le Calendrier",
45
+  "Explore the workspace": "Explorer l'espace de travail",
46
+  "Recent activity": "Activité récente",
47
+  "Mark everything as read": "Tout marquer comme lu",
48
+  "See more": "Voir plus",
49
+  "Member List": "Liste des membres",
50
+  "Add a member": "Ajouter un membre",
51
+  "Enter the name or email of the member": "Renseigner le nom ou l'email de l'utilisateur",
52
+  "Create an account": "Créer un compte",
53
+  "Supervisor": "Responsable",
54
+  "Content Manager": "Gestionnaire de contenu",
55
+  "Contributor": "Contributeur",
56
+  "Reader": "Lecteur",
57
+  "Validate": "Validé",
58
+  "Implement Tracim in your explorer": "Implémenter Tracim dans votre explorateur",
59
+  "Find all your documents deposited online directly on your computer via the workstation, without going through the software.": "Retrouvez tous vos documents déposés en ligne directement sur votre ordinateur via le poste de travail, sans passer par le logiciel.",
60
+  "Workspace Calendar": "Calendrier de l'espace de travail",
61
+  "Each workspace has its own calendar.": "Chaque espace de travail à son propre calendrier",
62
+  "Choose the role of the member": "Choisissez le rôle du membre",
63
+  "Email Adress": "Adresse email",
64
+  "Old password": "Ancien mot de passe",
65
+  "New password": "Nouveau mot de passe",
66
+  "Name:": "Nom :",
67
+  "Email Adress:": "Adresse mail :"
68
+}

+ 6 - 6
frontend/jsonserver/static_db.json View File

35
     "customClass": "wsContentPageHtml",
35
     "customClass": "wsContentPageHtml",
36
     "icon": "fa fa-fw fa-file-text-o",
36
     "icon": "fa fa-fw fa-file-text-o",
37
     "color": "#3f52e3",
37
     "color": "#3f52e3",
38
-    "domContainer": "appContainer"
38
+    "domContainer": "appFeatureContainer"
39
   }, {
39
   }, {
40
     "name": "PageMarkdown",
40
     "name": "PageMarkdown",
41
     "label": {
41
     "label": {
47
     "customClass": "wsContentPageMarkdown",
47
     "customClass": "wsContentPageMarkdown",
48
     "icon": "fa fa-fw fa-file-code-o",
48
     "icon": "fa fa-fw fa-file-code-o",
49
     "color": "#e0082b",
49
     "color": "#e0082b",
50
-    "domContainer": "appContainer"
50
+    "domContainer": "appFeatureContainer"
51
   }, {
51
   }, {
52
     "name": "File",
52
     "name": "File",
53
     "label": {
53
     "label": {
59
     "customClass": "wsContentFile",
59
     "customClass": "wsContentFile",
60
     "icon": "fa fa-fw fa-file-image-o",
60
     "icon": "fa fa-fw fa-file-image-o",
61
     "color": "#263462",
61
     "color": "#263462",
62
-    "domContainer": "appContainer"
62
+    "domContainer": "appFeatureContainer"
63
   }, {
63
   }, {
64
     "name": "Thread",
64
     "name": "Thread",
65
     "label": {
65
     "label": {
71
     "customClass": "wsContentThread",
71
     "customClass": "wsContentThread",
72
     "icon": "fa fa-fw fa-comments-o",
72
     "icon": "fa fa-fw fa-comments-o",
73
     "color": "#2674d3",
73
     "color": "#2674d3",
74
-    "domContainer": "appContainer"
74
+    "domContainer": "appFeatureContainer"
75
   }, {
75
   }, {
76
     "name": "Task",
76
     "name": "Task",
77
     "label": {
77
     "label": {
83
     "customClass": "wsContentTask",
83
     "customClass": "wsContentTask",
84
     "icon": "fa fa-fw fa-list-ul",
84
     "icon": "fa fa-fw fa-list-ul",
85
     "color": "#2d5a88",
85
     "color": "#2d5a88",
86
-    "domContainer": "appContainer"
86
+    "domContainer": "appFeatureContainer"
87
   }, {
87
   }, {
88
     "name": "Issue",
88
     "name": "Issue",
89
     "label": {
89
     "label": {
95
     "customClass": "wsContentIssue",
95
     "customClass": "wsContentIssue",
96
     "icon": "fa fa-fw fa-ticket",
96
     "icon": "fa fa-fw fa-ticket",
97
     "color": "#a4835e",
97
     "color": "#a4835e",
98
-    "domContainer": "appContainer"
98
+    "domContainer": "appFeatureContainer"
99
   }],
99
   }],
100
   "workspace_detail": {
100
   "workspace_detail": {
101
     "id": 1,
101
     "id": 1,

+ 5 - 1
frontend/package.json View File

10
     "servdev-dashboard": "NODE_ENV=development webpack-dashboard -m -- webpack-dev-server --watch --colors --inline --hot --progress",
10
     "servdev-dashboard": "NODE_ENV=development webpack-dashboard -m -- webpack-dev-server --watch --colors --inline --hot --progress",
11
     "buildwindoz": "set NODE_ENV=production&& webpack -p",
11
     "buildwindoz": "set NODE_ENV=production&& webpack -p",
12
     "build": "NODE_ENV=production webpack -p",
12
     "build": "NODE_ENV=production webpack -p",
13
+    "build-translation": "node i18next.scanner.js",
13
     "test": "echo \"Error: no test specified\" && exit 1"
14
     "test": "echo \"Error: no test specified\" && exit 1"
14
   },
15
   },
15
   "author": "",
16
   "author": "",
31
     "js-cookie": "^2.2.0",
32
     "js-cookie": "^2.2.0",
32
     "prop-types": "^15.6.0",
33
     "prop-types": "^15.6.0",
33
     "query-string": "^6.1.0",
34
     "query-string": "^6.1.0",
35
+    "radium": "^0.24.1",
34
     "react": "^16.0.0",
36
     "react": "^16.0.0",
35
     "react-animate-height": "^0.10.10",
37
     "react-animate-height": "^0.10.10",
36
     "react-dom": "^16.0.0",
38
     "react-dom": "^16.0.0",
52
     "whatwg-fetch": "^2.0.3"
54
     "whatwg-fetch": "^2.0.3"
53
   },
55
   },
54
   "devDependencies": {
56
   "devDependencies": {
57
+    "i18next-scanner": "^2.6.1",
55
     "json-server": "^0.12.0",
58
     "json-server": "^0.12.0",
56
     "webpack-dashboard": "^1.0.2",
59
     "webpack-dashboard": "^1.0.2",
57
     "webpack-dev-server": "^2.9.2"
60
     "webpack-dev-server": "^2.9.2"
61
       "fetch",
64
       "fetch",
62
       "btoa",
65
       "btoa",
63
       "history",
66
       "history",
64
-      "GLOBAL_renderAppFull",
67
+      "GLOBAL_renderAppFeature",
68
+      "GLOBAL_renderAppFullscreen",
65
       "GLOBAL_renderAppPopupCreation",
69
       "GLOBAL_renderAppPopupCreation",
66
       "GLOBAL_dispatchEvent",
70
       "GLOBAL_dispatchEvent",
67
       "GLOBAL_hideApp"
71
       "GLOBAL_hideApp"

+ 40 - 8
frontend/src/action-creator.async.js View File

11
   setUserRole,
11
   setUserRole,
12
   WORKSPACE,
12
   WORKSPACE,
13
   WORKSPACE_LIST,
13
   WORKSPACE_LIST,
14
+  WORKSPACE_DETAIL,
15
+  WORKSPACE_MEMBER_LIST,
14
   FOLDER,
16
   FOLDER,
15
   setFolderData,
17
   setFolderData,
16
   APP_LIST,
18
   APP_LIST,
57
   })()
59
   })()
58
   if (debug) console.log(`fetch ${param.method}/${actionName} result: `, fetchResult)
60
   if (debug) console.log(`fetch ${param.method}/${actionName} result: `, fetchResult)
59
 
61
 
60
-  if ([200, 204, 304].includes(fetchResult.status)) dispatch({type: `${param.method}/${actionName}/SUCCESS`, data: fetchResult.json})
61
-  else if ([400, 404, 500].includes(fetchResult.status)) dispatch({type: `${param.method}/${actionName}/FAILED`, data: fetchResult.json})
62
+  // if ([200, 204, 304].includes(fetchResult.status)) dispatch({type: `${param.method}/${actionName}/SUCCESS`, data: fetchResult.json})
63
+  // else if ([400, 404, 500].includes(fetchResult.status)) dispatch({type: `${param.method}/${actionName}/FAILED`, data: fetchResult.json})
64
+  switch (fetchResult.status) {
65
+    case 200:
66
+    case 204:
67
+    case 304:
68
+      dispatch({type: `${param.method}/${actionName}/SUCCESS`, data: fetchResult.json})
69
+      break
70
+    case 400:
71
+    case 404:
72
+    case 500:
73
+      dispatch({type: `${param.method}/${actionName}/FAILED`, data: fetchResult.json})
74
+      break
75
+  }
62
 
76
 
63
   return fetchResult
77
   return fetchResult
64
 }
78
 }
161
   })
175
   })
162
 }
176
 }
163
 
177
 
164
-export const getWorkspaceContentList = (user, idWorkspace, idParent) => dispatch => {
178
+export const getWorkspaceDetail = (user, idWorkspace) => dispatch => {
165
   return fetchWrapper({
179
   return fetchWrapper({
166
-    url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}/contents?parent_id=${idParent}`,
180
+    url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}`,
167
     param: {
181
     param: {
168
       headers: {
182
       headers: {
169
         ...FETCH_CONFIG.headers,
183
         ...FETCH_CONFIG.headers,
171
       },
185
       },
172
       method: 'GET'
186
       method: 'GET'
173
     },
187
     },
174
-    actionName: WORKSPACE,
188
+    actionName: WORKSPACE_DETAIL,
175
     dispatch
189
     dispatch
176
   })
190
   })
177
 }
191
 }
178
 
192
 
179
-export const getWorkspaceContent = (idWorkspace, idContent) => dispatch => {
193
+export const getWorkspaceMemberList = (user, idWorkspace) => dispatch => {
180
   return fetchWrapper({
194
   return fetchWrapper({
181
-    url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}/contents/${idContent}`,
195
+    url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}/members`,
182
     param: {
196
     param: {
183
-      headers: {...FETCH_CONFIG.headers},
197
+      headers: {
198
+        ...FETCH_CONFIG.headers,
199
+        'Authorization': 'Basic ' + user.auth
200
+      },
201
+      method: 'GET'
202
+    },
203
+    actionName: WORKSPACE_MEMBER_LIST,
204
+    dispatch
205
+  })
206
+}
207
+
208
+export const getWorkspaceContentList = (user, idWorkspace, idParent) => dispatch => {
209
+  return fetchWrapper({
210
+    url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}/contents?parent_id=${idParent}`,
211
+    param: {
212
+      headers: {
213
+        ...FETCH_CONFIG.headers,
214
+        'Authorization': 'Basic ' + user.auth
215
+      },
184
       method: 'GET'
216
       method: 'GET'
185
     },
217
     },
186
     actionName: WORKSPACE,
218
     actionName: WORKSPACE,

+ 45 - 28
frontend/src/action-creator.sync.js View File

1
+export const SET = 'Set'
2
+export const UPDATE = 'Update'
3
+export const ADD = 'Add'
4
+export const REMOVE = 'Remove'
5
+
1
 export const TIMEZONE = 'Timezone'
6
 export const TIMEZONE = 'Timezone'
2
-export const setTimezone = timezone => ({ type: `Set/${TIMEZONE}`, timezone })
7
+export const setTimezone = timezone => ({ type: `${SET}/${TIMEZONE}`, timezone })
3
 
8
 
4
 export const FLASH_MESSAGE = 'FlashMessage'
9
 export const FLASH_MESSAGE = 'FlashMessage'
5
 export const newFlashMessage = (msgText = '', msgType = 'info', msgDelay = 5000) => dispatch => {
10
 export const newFlashMessage = (msgText = '', msgType = 'info', msgDelay = 5000) => dispatch => {
6
   msgDelay !== 0 && window.setTimeout(() => dispatch(removeFlashMessage(msgText)), msgDelay)
11
   msgDelay !== 0 && window.setTimeout(() => dispatch(removeFlashMessage(msgText)), msgDelay)
7
   return dispatch(addFlashMessage({message: msgText, type: msgType}))
12
   return dispatch(addFlashMessage({message: msgText, type: msgType}))
8
 }
13
 }
9
-export const addFlashMessage = msg => ({ type: `Add/${FLASH_MESSAGE}`, msg })
10
-export const removeFlashMessage = msg => ({ type: `Remove/${FLASH_MESSAGE}`, msg })
11
-
12
-export const USER_LOGIN = 'User/Login'
13
-export const USER_LOGOUT = 'User/Logout'
14
-export const USER_DATA = 'User/Data'
15
-export const USER_ROLE = 'User/Role'
16
-export const USER_CONNECTED = 'User/Connected'
17
-export const USER_DISCONNECTED = 'User/Disconnected'
18
-export const setUserConnected = user => ({ type: `Set/${USER_CONNECTED}`, user })
19
-export const setUserDisconnected = () => ({ type: `Set/${USER_DISCONNECTED}` })
20
-export const updateUserData = userData => ({ type: `Update/${USER_DATA}`, data: userData })
21
-export const setUserRole = userRole => ({ type: `Set/${USER_ROLE}`, userRole }) // this actually update workspaceList state
14
+export const addFlashMessage = msg => ({ type: `${ADD}/${FLASH_MESSAGE}`, msg })
15
+export const removeFlashMessage = msg => ({ type: `${REMOVE}/${FLASH_MESSAGE}`, msg })
16
+
17
+export const USER = 'User'
18
+export const USER_LOGIN = `${USER}/Login`
19
+export const USER_LOGOUT = `${USER}/Logout`
20
+export const USER_DATA = `${USER}/Data`
21
+export const USER_ROLE = `${USER}/Role`
22
+export const USER_CONNECTED = `${USER}/Connected`
23
+export const USER_DISCONNECTED = `${USER}/Disconnected`
24
+export const USER_LANG = `${USER}/Lang`
25
+export const setUserConnected = user => ({ type: `${SET}/${USER}/Connected`, user })
26
+export const setUserDisconnected = () => ({ type: `${SET}/${USER}/Disconnected` })
27
+export const updateUserData = userData => ({ type: `${UPDATE}/${USER}/Data`, data: userData })
28
+export const setUserRole = userRole => ({ type: `${SET}/${USER}/Role`, userRole }) // this actually update workspaceList state
29
+export const setUserLang = lang => ({ type: `${SET}/${USER}/Lang`, lang })
22
 export const updateUserWorkspaceSubscriptionNotif = (workspaceId, subscriptionNotif) =>
30
 export const updateUserWorkspaceSubscriptionNotif = (workspaceId, subscriptionNotif) =>
23
-  ({ type: `Update/${USER_ROLE}/SubscriptionNotif`, workspaceId, subscriptionNotif })
31
+  ({ type: `${UPDATE}/${USER_ROLE}/SubscriptionNotif`, workspaceId, subscriptionNotif })
24
 
32
 
25
 export const WORKSPACE = 'Workspace'
33
 export const WORKSPACE = 'Workspace'
26
-export const setWorkspaceContent = (workspaceContent, filterStr = '') => ({ type: `Set/${WORKSPACE}/Content`, workspaceContent, filterStr })
27
-export const updateWorkspaceFilter = filterList => ({ type: `Update/${WORKSPACE}/Filter`, filterList })
34
+export const WORKSPACE_CONTENT = `${WORKSPACE}/Content`
35
+export const setWorkspaceContentList = (workspaceContentList, filterStr = '') => ({ type: `${SET}/${WORKSPACE_CONTENT}`, workspaceContentList, filterStr })
36
+export const updateWorkspaceFilter = filterList => ({ type: `${UPDATE}/${WORKSPACE}/Filter`, filterList })
28
 
37
 
29
-export const FOLDER = 'Folder'
30
-export const setFolderData = (folderId, content) => ({ type: `Set/${WORKSPACE}/${FOLDER}/Content`, folderId, content })
38
+export const WORKSPACE_LIST = `${WORKSPACE}/List`
39
+export const updateWorkspaceListData = workspaceList => ({ type: `${UPDATE}/${WORKSPACE_LIST}`, workspaceList })
40
+export const setWorkspaceListIsOpenInSidebar = (workspaceId, isOpenInSidebar) => ({ type: `${SET}/${WORKSPACE_LIST}/isOpenInSidebar`, workspaceId, isOpenInSidebar })
41
+
42
+export const WORKSPACE_DETAIL = `${WORKSPACE}/Detail`
43
+export const setWorkspaceDetail = workspaceDetail => ({ type: `${SET}/${WORKSPACE_DETAIL}`, workspaceDetail })
31
 
44
 
32
-export const WORKSPACE_LIST = 'WorkspaceList'
33
-export const updateWorkspaceListData = workspaceList => ({ type: `Update/${WORKSPACE_LIST}`, workspaceList })
34
-export const setWorkspaceListIsOpenInSidebar = (workspaceId, isOpenInSidebar) => ({ type: `Set/${WORKSPACE_LIST}/isOpenInSidebar`, workspaceId, isOpenInSidebar })
45
+export const WORKSPACE_MEMBER = `${WORKSPACE}/Member`
46
+export const WORKSPACE_MEMBER_LIST = `${WORKSPACE_MEMBER}/List`
47
+export const setWorkspaceMemberList = workspaceMemberList => ({ type: `${SET}/${WORKSPACE_MEMBER_LIST}`, workspaceMemberList })
48
+
49
+export const FOLDER = 'Folder'
50
+export const setFolderData = (folderId, content) => ({ type: `${SET}/${WORKSPACE}/${FOLDER}/Content`, folderId, content })
35
 
51
 
36
-export const APP_LIST = 'App/List'
37
-export const setAppList = appList => ({ type: `Set/${APP_LIST}`, appList })
52
+export const APP = 'App'
53
+export const APP_LIST = `${APP}/List`
54
+export const setAppList = appList => ({ type: `${SET}/${APP_LIST}`, appList })
38
 
55
 
39
-export const CONTENT_TYPE_LIST = 'ContentType/List'
40
-export const setContentTypeList = contentTypeList => ({ type: `Set/${CONTENT_TYPE_LIST}`, contentTypeList })
56
+export const CONTENT_TYPE = 'ContentType'
57
+export const CONTENT_TYPE_LIST = `${CONTENT_TYPE}/List`
58
+export const setContentTypeList = contentTypeList => ({ type: `${SET}/${CONTENT_TYPE_LIST}`, contentTypeList })
41
 
59
 
42
 export const LANG = 'Lang'
60
 export const LANG = 'Lang'
43
-export const updateLangList = langList => ({ type: `Update/${LANG}`, langList })
44
-export const setLangActive = langId => ({ type: `Set/${LANG}/Active`, langId })
61
+export const updateLangList = langList => ({ type: `${UPDATE}/${LANG}`, langList })

+ 24 - 8
frontend/src/appFactory.js View File

1
 import React from 'react'
1
 import React from 'react'
2
 import { FETCH_CONFIG } from './helper.js'
2
 import { FETCH_CONFIG } from './helper.js'
3
+import i18n from './i18n.js'
3
 
4
 
4
 export function appFactory (WrappedComponent) {
5
 export function appFactory (WrappedComponent) {
5
   return class AppFactory extends React.Component {
6
   return class AppFactory extends React.Component {
6
-    renderAppFull = (appConfig, user, content) => GLOBAL_renderAppFull({
7
+    renderAppFeature = (appConfig, user, content) => GLOBAL_renderAppFeature({
7
       loggedUser: user.logged ? user : {},
8
       loggedUser: user.logged ? user : {},
8
       config: {
9
       config: {
9
         ...appConfig,
10
         ...appConfig,
10
-        domContainer: 'appContainer',
11
+        domContainer: 'appFeatureContainer',
11
         apiUrl: FETCH_CONFIG.apiUrl,
12
         apiUrl: FETCH_CONFIG.apiUrl,
12
-        mockApiUrl: FETCH_CONFIG.mockApiUrl,
13
-        apiHeader: FETCH_CONFIG.headers
13
+        mockApiUrl: FETCH_CONFIG.mockApiUrl, // Côme - 2018/07/31 - this should not be used, I deprecate it
14
+        apiHeader: FETCH_CONFIG.headers,
15
+        translation: i18n.store.data
16
+      },
17
+      content
18
+    })
19
+
20
+    renderAppFullscreen = (appConfig, user, content) => GLOBAL_renderAppFullscreen({
21
+      loggedUser: user.logged ? user : {},
22
+      config: {
23
+        ...appConfig,
24
+        domContainer: 'appFullscreenContainer',
25
+        apiUrl: FETCH_CONFIG.apiUrl,
26
+        apiHeader: FETCH_CONFIG.headers,
27
+        translation: i18n.store.data
14
       },
28
       },
15
       content
29
       content
16
     })
30
     })
22
         domContainer: 'popupCreateContentContainer',
36
         domContainer: 'popupCreateContentContainer',
23
         apiUrl: FETCH_CONFIG.apiUrl,
37
         apiUrl: FETCH_CONFIG.apiUrl,
24
         mockApiUrl: FETCH_CONFIG.mockApiUrl,
38
         mockApiUrl: FETCH_CONFIG.mockApiUrl,
25
-        apiHeader: FETCH_CONFIG.headers // should this be used by app ? right now, apps have their own headers
39
+        apiHeader: FETCH_CONFIG.headers, // should this be used by app ? right now, apps have their own headers
40
+        translation: i18n.store.data
26
       },
41
       },
27
       idWorkspace,
42
       idWorkspace,
28
       idFolder: idFolder === 'null' ? null : idFolder
43
       idFolder: idFolder === 'null' ? null : idFolder
29
     })
44
     })
30
 
45
 
31
-    emitEventApp = (type, data) => GLOBAL_dispatchEvent({ type, data })
46
+    dispatchCustomEvent = (type, data) => GLOBAL_dispatchEvent({ type, data })
32
 
47
 
33
     render () {
48
     render () {
34
       return (
49
       return (
35
         <WrappedComponent
50
         <WrappedComponent
36
           {...this.props}
51
           {...this.props}
37
-          renderAppFull={this.renderAppFull}
52
+          renderAppFeature={this.renderAppFeature}
53
+          renderAppFullscreen={this.renderAppFullscreen}
38
           renderAppPopupCreation={this.renderAppPopupCreation}
54
           renderAppPopupCreation={this.renderAppPopupCreation}
39
-          emitEventApp={this.emitEventApp}
55
+          dispatchCustomEvent={this.dispatchCustomEvent}
40
           // hideApp={this.hideApp}
56
           // hideApp={this.hideApp}
41
         />
57
         />
42
       )
58
       )

+ 2 - 2
frontend/src/component/Account/Calendar.jsx View File

5
     <div className='account__userpreference__setting__calendar'>
5
     <div className='account__userpreference__setting__calendar'>
6
 
6
 
7
       <div className='calendar__title subTitle ml-2 ml-sm-0'>
7
       <div className='calendar__title subTitle ml-2 ml-sm-0'>
8
-        Calendrier
8
+        {props.t('Calendar')}
9
       </div>
9
       </div>
10
 
10
 
11
       <div className='calendar__text ml-2 ml-sm-0'>
11
       <div className='calendar__text ml-2 ml-sm-0'>
13
       </div>
13
       </div>
14
 
14
 
15
       <div className='calendar__title ml-2 ml-sm-0'>
15
       <div className='calendar__title ml-2 ml-sm-0'>
16
-        Accèder à votre Calendrier personnel
16
+        {props.t('Access your personal calendar')}
17
       </div>
17
       </div>
18
       <div className='calendar__link ml-2 ml-sm-0'>
18
       <div className='calendar__link ml-2 ml-sm-0'>
19
         {props.user.caldavUrl}
19
         {props.user.caldavUrl}

+ 4 - 4
frontend/src/component/Account/Notification.jsx View File

9
   return (
9
   return (
10
     <div className='account__userpreference__setting__notification'>
10
     <div className='account__userpreference__setting__notification'>
11
       <div className='notification__sectiontitle subTitle ml-2 ml-sm-0'>
11
       <div className='notification__sectiontitle subTitle ml-2 ml-sm-0'>
12
-        Espace de Travail & Notification
12
+        {props.t('Workspace and notifications')}
13
       </div>
13
       </div>
14
 
14
 
15
       <div className='notification__text ml-2 ml-sm-0'>
15
       <div className='notification__text ml-2 ml-sm-0'>
20
         <table className='table'>
20
         <table className='table'>
21
           <thead>
21
           <thead>
22
             <tr>
22
             <tr>
23
-              <th>Espace de travail</th>
24
-              <th>Role</th>
25
-              <th>Notification</th>
23
+              <th>{props.t('Workspace')}</th>
24
+              <th>{props.t('Role')}</th>
25
+              <th>{props.t('Notification')}</th>
26
             </tr>
26
             </tr>
27
           </thead>
27
           </thead>
28
           <tbody>
28
           <tbody>

+ 15 - 6
frontend/src/component/Account/Password.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
+import { translate } from 'react-i18next'
2
 
3
 
3
 export const Password = props => {
4
 export const Password = props => {
4
   return (
5
   return (
5
     <div className='account__userpreference__setting__personaldata'>
6
     <div className='account__userpreference__setting__personaldata'>
6
       <div className='personaldata__sectiontitle subTitle ml-2 ml-sm-0'>
7
       <div className='personaldata__sectiontitle subTitle ml-2 ml-sm-0'>
7
-        Changer de mot de passe
8
+        {props.t('Change your password')}
8
       </div>
9
       </div>
9
 
10
 
10
       <div className='personaldata__text ml-2 ml-sm-0'>
11
       <div className='personaldata__text ml-2 ml-sm-0'>
13
 
14
 
14
       <form className='personaldata__form mr-5'>
15
       <form className='personaldata__form mr-5'>
15
         <div className='personaldata__form__title'>
16
         <div className='personaldata__form__title'>
16
-          Mot de passe :
17
+          {props.t('Password')}
17
         </div>
18
         </div>
18
-        <input className='personaldata__form__txtinput form-control' type='password' placeholder='Ancien mot de passe' />
19
-        <input className='personaldata__form__txtinput form-control mt-4' type='password' placeholder='Nouveau mot de passe' />
19
+        <input
20
+          className='personaldata__form__txtinput form-control'
21
+          type='password'
22
+          placeholder={props.t('Old password')}
23
+        />
24
+        <input
25
+          className='personaldata__form__txtinput form-control mt-4'
26
+          type='password'
27
+          placeholder={props.t('New password')}
28
+        />
20
         <button type='submit' className='personaldata__form__button btn btn-outline-primary mt-4'>
29
         <button type='submit' className='personaldata__form__button btn btn-outline-primary mt-4'>
21
-          Envoyer
30
+          {props.t('Send')}
22
         </button>
31
         </button>
23
       </form>
32
       </form>
24
 
33
 
26
   )
35
   )
27
 }
36
 }
28
 
37
 
29
-export default Password
38
+export default translate()(Password)

+ 22 - 7
frontend/src/component/Account/PersonalData.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
+import PropTypes from 'prop-types'
3
+import { translate } from 'react-i18next'
2
 
4
 
3
 export const PersonalData = props => {
5
 export const PersonalData = props => {
4
   return (
6
   return (
5
     <div className='account__userpreference__setting__personaldata'>
7
     <div className='account__userpreference__setting__personaldata'>
6
       <div className='personaldata__sectiontitle subTitle ml-2 ml-sm-0'>
8
       <div className='personaldata__sectiontitle subTitle ml-2 ml-sm-0'>
7
-        Information du compte
9
+        {props.t('Account information')}
8
       </div>
10
       </div>
9
 
11
 
10
       <div className='personaldata__text ml-2 ml-sm-0'>
12
       <div className='personaldata__text ml-2 ml-sm-0'>
13
 
15
 
14
       <form className='personaldata__form'>
16
       <form className='personaldata__form'>
15
         <div className='personaldata__form__title'>
17
         <div className='personaldata__form__title'>
16
-          Nom :
18
+          {props.t('Name:')}
17
         </div>
19
         </div>
18
         <div className='d-flex align-items-center justify-content-between flex-wrap mb-4'>
20
         <div className='d-flex align-items-center justify-content-between flex-wrap mb-4'>
19
-          <input className='personaldata__form__txtinput form-control mt-3 mt-sm-0' type='text' placeholder='Nom' />
21
+          <input
22
+            className='personaldata__form__txtinput form-control mt-3 mt-sm-0'
23
+            type='text'
24
+            placeholder={props.t('Change your name')}
25
+          />
20
         </div>
26
         </div>
21
         <div className='personaldata__form__title'>
27
         <div className='personaldata__form__title'>
22
-          Adresse mail :
28
+          {props.t('Email Adress:')}
23
         </div>
29
         </div>
24
         <div className='d-flex align-items-center justify-content-between flex-wrap mb-4'>
30
         <div className='d-flex align-items-center justify-content-between flex-wrap mb-4'>
25
-          <input className='personaldata__form__txtinput form-control mt-3 mt-sm-0' type='email' placeholder='Nouvelle adresse mail' />
31
+          <input
32
+            className='personaldata__form__txtinput form-control mt-3 mt-sm-0'
33
+            type='email'
34
+            placeholder={props.t('Change your email')}
35
+          />
26
         </div>
36
         </div>
27
         <button type='submit' className='personaldata__form__button btn btn-outline-primary'>
37
         <button type='submit' className='personaldata__form__button btn btn-outline-primary'>
28
-          Envoyer
38
+          {props.t('Send')}
29
         </button>
39
         </button>
30
       </form>
40
       </form>
31
     </div>
41
     </div>
32
   )
42
   )
33
 }
43
 }
34
 
44
 
35
-export default PersonalData
45
+PersonalData.propTypes = {
46
+  inputPlaceholderNameUser: PropTypes.string,
47
+  inputPlaceholderEmailUser: PropTypes.string
48
+}
49
+
50
+export default translate()(PersonalData)

+ 3 - 2
frontend/src/component/Account/Timezone.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
 import Select from 'react-select'
2
 import Select from 'react-select'
3
 import 'react-select/dist/react-select.css'
3
 import 'react-select/dist/react-select.css'
4
+import { translate } from 'react-i18next'
4
 
5
 
5
 export const Timezone = props => {
6
 export const Timezone = props => {
6
   const handleChangeTimezone = selectedTimezone => props.onChangeTimezone(props.timezone.find(t => t.place === selectedTimezone.place))
7
   const handleChangeTimezone = selectedTimezone => props.onChangeTimezone(props.timezone.find(t => t.place === selectedTimezone.place))
9
     <div className='account__userpreference__setting__timezone'>
10
     <div className='account__userpreference__setting__timezone'>
10
 
11
 
11
       <div className='timezone__title subTitle ml-2 ml-sm-0'>
12
       <div className='timezone__title subTitle ml-2 ml-sm-0'>
12
-        Changer de Fuseau Horaire
13
+        {props.t('Change your Timezone')}
13
       </div>
14
       </div>
14
 
15
 
15
       <div className='timezone__text ml-2 ml-sm-0'>
16
       <div className='timezone__text ml-2 ml-sm-0'>
32
   )
33
   )
33
 }
34
 }
34
 
35
 
35
-export default Timezone
36
+export default translate()(Timezone)

+ 1 - 1
frontend/src/component/FlashMessage.jsx View File

22
 
22
 
23
               <div className='flashmessage__container__content__text'>
23
               <div className='flashmessage__container__content__text'>
24
                 <div className='flashmessage__container__content__text__title'>
24
                 <div className='flashmessage__container__content__text__title'>
25
-                  {props.t('FlashMessage.error')}
25
+                  {props.t('Error')}
26
                 </div>
26
                 </div>
27
                 <div className='flashmessage__container__content__text__paragraph'>
27
                 <div className='flashmessage__container__content__text__paragraph'>
28
                   {props.flashMessage[0].message}
28
                   {props.flashMessage[0].message}

+ 1 - 1
frontend/src/component/Footer.jsx View File

7
   return (
7
   return (
8
     <footer className='footer text-right'>
8
     <footer className='footer text-right'>
9
       <div className='footer__text'>
9
       <div className='footer__text'>
10
-        {t('Footer.marketing_msg')} - {t('Footer.copyright')}
10
+        {t('Create your own collaborative workspaces on trac.im')} - {t('Copyright 2013 - 2017')}
11
       </div>
11
       </div>
12
       <img className='footer__logo' src={logoFooter} />
12
       <img className='footer__logo' src={logoFooter} />
13
     </footer>
13
     </footer>

+ 4 - 4
frontend/src/component/Header/MenuActionListItem/DropdownLang.jsx View File

2
 import PropTypes from 'prop-types'
2
 import PropTypes from 'prop-types'
3
 
3
 
4
 const DropdownLang = props => {
4
 const DropdownLang = props => {
5
-  const activeLang = props.langList.find(l => l.active) || {id: 'fr', name: 'Français', src: '', active: true}
6
   return (
5
   return (
7
     <li className='header__menu__rightside__itemlanguage'>
6
     <li className='header__menu__rightside__itemlanguage'>
8
       <div className='header__menu__rightside__itemlanguage__languagedropdown dropdown'>
7
       <div className='header__menu__rightside__itemlanguage__languagedropdown dropdown'>
14
           aria-haspopup='true'
13
           aria-haspopup='true'
15
           aria-expanded='false'
14
           aria-expanded='false'
16
         >
15
         >
17
-          <img className='languagedropdown__btnlanguage__imgselected' src={activeLang.src} />
16
+          <img className='languagedropdown__btnlanguage__imgselected' src={props.langList.find(l => l.id === props.idLangActive).icon} />
18
         </button>
17
         </button>
19
         <div className='languagedropdown__subdropdown dropdown-menu' aria-labelledby='headerDropdownMenuButton'>
18
         <div className='languagedropdown__subdropdown dropdown-menu' aria-labelledby='headerDropdownMenuButton'>
20
-          { props.langList.map((l, i) => l.active === false &&
19
+          { props.langList.filter(l => l.id !== props.idLangActive).map((l, i) =>
21
             <div className='subdropdown__link dropdown-item' onClick={() => props.onChangeLang(l.id)} key={i}>
20
             <div className='subdropdown__link dropdown-item' onClick={() => props.onChangeLang(l.id)} key={i}>
22
-              <img className='subdropdown__flag' src={l.src} />
21
+              <img className='subdropdown__flag' src={l.icon} />
23
             </div>
22
             </div>
24
           )}
23
           )}
25
         </div>
24
         </div>
31
 
30
 
32
 DropdownLang.propTypes = {
31
 DropdownLang.propTypes = {
33
   langList: PropTypes.array.isRequired,
32
   langList: PropTypes.array.isRequired,
33
+  idLangActive: PropTypes.string.isRequired,
34
   onChangeLang: PropTypes.func.isRequired
34
   onChangeLang: PropTypes.func.isRequired
35
 }
35
 }

+ 4 - 3
frontend/src/component/Header/MenuActionListItem/MenuProfil.jsx View File

2
 import { Link } from 'react-router-dom'
2
 import { Link } from 'react-router-dom'
3
 import PropTypes from 'prop-types'
3
 import PropTypes from 'prop-types'
4
 import { PAGE } from '../../../helper.js'
4
 import { PAGE } from '../../../helper.js'
5
+import { translate } from 'react-i18next'
5
 
6
 
6
 const MenuProfil = props => {
7
 const MenuProfil = props => {
7
   return props.user.logged
8
   return props.user.logged
17
           <div className='profilgroup__setting dropdown-menu' aria-labelledby='dropdownMenuButton'>
18
           <div className='profilgroup__setting dropdown-menu' aria-labelledby='dropdownMenuButton'>
18
             <Link className='setting__link dropdown-item' to={PAGE.ACCOUNT}>
19
             <Link className='setting__link dropdown-item' to={PAGE.ACCOUNT}>
19
               <i className='fa fa-fw fa-user-o mr-2' />
20
               <i className='fa fa-fw fa-user-o mr-2' />
20
-              Mon compte
21
+              {props.t('My Account')}
21
             </Link>
22
             </Link>
22
             {/* <div className='setting__link dropdown-item'>Mot de passe</div> */}
23
             {/* <div className='setting__link dropdown-item'>Mot de passe</div> */}
23
             <div className='setting__link dropdown-item' onClick={props.onClickLogout}>
24
             <div className='setting__link dropdown-item' onClick={props.onClickLogout}>
24
               <i className='fa fa-fw fa-sign-out mr-2' />
25
               <i className='fa fa-fw fa-sign-out mr-2' />
25
-              Se déconnecter
26
+              {props.t('Logout')}
26
             </div>
27
             </div>
27
           </div>
28
           </div>
28
         </div>
29
         </div>
30
     )
31
     )
31
     : ''
32
     : ''
32
 }
33
 }
33
-export default MenuProfil
34
+export default translate()(MenuProfil)
34
 
35
 
35
 MenuProfil.propTypes = {
36
 MenuProfil.propTypes = {
36
   user: PropTypes.object.isRequired,
37
   user: PropTypes.object.isRequired,

+ 5 - 4
frontend/src/component/Header/MenuActionListItem/Notification.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
+import { translate } from 'react-i18next'
2
 // import PropTypes from 'prop-types'
3
 // import PropTypes from 'prop-types'
3
 
4
 
4
 const Notification = props => {
5
 const Notification = props => {
13
           aria-haspopup='true'
14
           aria-haspopup='true'
14
           aria-expanded='false'
15
           aria-expanded='false'
15
         >
16
         >
16
-          Notification
17
+          {props.t('Notification')}
17
         </button>
18
         </button>
18
         <div className='timeline__subdropdown dropdown-menu' aria-labelledby='headerDropdownMenuButton'>
19
         <div className='timeline__subdropdown dropdown-menu' aria-labelledby='headerDropdownMenuButton'>
19
           <div className='timeline__subdropdown__text dropdown-item' >
20
           <div className='timeline__subdropdown__text dropdown-item' >
20
-            Conversation archivé
21
+            {props.t('Archive Topic')}
21
           </div>
22
           </div>
22
           <div className='timeline__subdropdown__text dropdown-item' >
23
           <div className='timeline__subdropdown__text dropdown-item' >
23
-            Fichier supprimé
24
+            {props.t('Deleted File')}
24
           </div>
25
           </div>
25
         </div>
26
         </div>
26
       </div>
27
       </div>
27
     </li>
28
     </li>
28
   )
29
   )
29
 }
30
 }
30
-export default Notification
31
+export default translate()(Notification)

+ 1 - 1
frontend/src/component/Header/MenuActionListItem/Search.jsx View File

9
         <input
9
         <input
10
           type='text'
10
           type='text'
11
           className='search__input form-control'
11
           className='search__input form-control'
12
-          placeholder={`${props.t('Header.Search')}...`}
12
+          placeholder={`${props.t('Search...')}`}
13
           aria-describedby='headerInputSearch'
13
           aria-describedby='headerInputSearch'
14
           onChange={props.onChangeInput}
14
           onChange={props.onChangeInput}
15
         />
15
         />

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

1
 import React from 'react'
1
 import React from 'react'
2
 import PropTypes from 'prop-types'
2
 import PropTypes from 'prop-types'
3
+import { translate } from 'react-i18next'
3
 
4
 
4
 const ExtandedAction = props => {
5
 const ExtandedAction = props => {
5
   return (
6
   return (
22
             <i className='fa fa-fw fa-pencil' />
23
             <i className='fa fa-fw fa-pencil' />
23
           </div>
24
           </div>
24
           <div className='subdropdown__item__text'>
25
           <div className='subdropdown__item__text'>
25
-            Modifier
26
+            {props.t('Change')}
26
           </div>
27
           </div>
27
         </div>
28
         </div>
28
 
29
 
31
             <i className='fa fa-fw fa-arrows-alt' />
32
             <i className='fa fa-fw fa-arrows-alt' />
32
           </div>
33
           </div>
33
           <div className='subdropdown__item__text'>
34
           <div className='subdropdown__item__text'>
34
-            Déplacer
35
+            {props.t('Move')}
35
           </div>
36
           </div>
36
         </div>
37
         </div>
37
 
38
 
40
             <i className='fa fa-fw fa-download' />
41
             <i className='fa fa-fw fa-download' />
41
           </div>
42
           </div>
42
           <div className='subdropdown__item__text'>
43
           <div className='subdropdown__item__text'>
43
-            Télécharger
44
+            {props.t('Download')}
44
           </div>
45
           </div>
45
         </div> */ }
46
         </div> */ }
46
 
47
 
49
             <i className='fa fa-fw fa-archive' />
50
             <i className='fa fa-fw fa-archive' />
50
           </div>
51
           </div>
51
           <div className='subdropdown__item__text'>
52
           <div className='subdropdown__item__text'>
52
-            Archiver
53
+            {props.t('Archive')}
53
           </div>
54
           </div>
54
         </div>
55
         </div>
55
 
56
 
58
             <i className='fa fa-fw fa-trash-o' />
59
             <i className='fa fa-fw fa-trash-o' />
59
           </div>
60
           </div>
60
           <div className='subdropdown__item__text'>
61
           <div className='subdropdown__item__text'>
61
-            Supprimer
62
+            {props.t('Delete')}
62
           </div>
63
           </div>
63
         </div>
64
         </div>
64
 
65
 
67
   )
68
   )
68
 }
69
 }
69
 
70
 
70
-export default ExtandedAction
71
+export default translate()(ExtandedAction)
71
 
72
 
72
 ExtandedAction.propTypes = {
73
 ExtandedAction.propTypes = {
73
   onClickExtendedAction: PropTypes.object.isRequired
74
   onClickExtendedAction: PropTypes.object.isRequired

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

4
 import BtnExtandedAction from './BtnExtandedAction.jsx'
4
 import BtnExtandedAction from './BtnExtandedAction.jsx'
5
 
5
 
6
 const ContentItem = props => {
6
 const ContentItem = props => {
7
+  if (props.contentType === null) return null // this means the endpoint system/content_type hasn't responded yet
8
+
7
   const status = props.contentType.availableStatuses.find(s => s.slug === props.statusSlug)
9
   const status = props.contentType.availableStatuses.find(s => s.slug === props.statusSlug)
8
   return (
10
   return (
9
     <div className={classnames('content', 'align-items-center', {'item-last': props.isLast}, props.customClass)} onClick={props.onClickItem}>
11
     <div className={classnames('content', 'align-items-center', {'item-last': props.isLast}, props.customClass)} onClick={props.onClickItem}>

+ 3 - 3
frontend/src/component/Workspace/ContentItemHeader.jsx View File

5
   return (
5
   return (
6
     <div className='content__header'>
6
     <div className='content__header'>
7
       <div className='content__header__type'>
7
       <div className='content__header__type'>
8
-        {props.t('FileItemHeader.type')}
8
+        {props.t('Type')}
9
       </div>
9
       </div>
10
       <div className='content__header__name'>
10
       <div className='content__header__name'>
11
-        {props.t('FileItemHeader.document_name')}
11
+        {props.t('Title')}
12
       </div>
12
       </div>
13
       <div className='content__header__status'>
13
       <div className='content__header__status'>
14
-        {props.t('FileItemHeader.status')}
14
+        {props.t('Status')}
15
       </div>
15
       </div>
16
     </div>
16
     </div>
17
   )
17
   )

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

65
                 aria-expanded='false'
65
                 aria-expanded='false'
66
                 onClick={e => e.stopPropagation()}
66
                 onClick={e => e.stopPropagation()}
67
               >
67
               >
68
-                {t('Folder.create')} ...
68
+                {t('Create in folder...')}
69
               </button>
69
               </button>
70
 
70
 
71
               <div className='addbtn__subdropdown dropdown-menu' aria-labelledby='dropdownMenuButton'>
71
               <div className='addbtn__subdropdown dropdown-menu' aria-labelledby='dropdownMenuButton'>

+ 8 - 7
frontend/src/component/Workspace/OpenContentApp.jsx View File

3
 import { withRouter } from 'react-router'
3
 import { withRouter } from 'react-router'
4
 import appFactory from '../../appFactory.js'
4
 import appFactory from '../../appFactory.js'
5
 
5
 
6
+// @FIXME Côme - 2018/07/31 - should this be in a component like AppFeatureManager ?
6
 export class OpenContentApp extends React.Component {
7
 export class OpenContentApp extends React.Component {
7
   openContentApp = () => {
8
   openContentApp = () => {
8
-    const { idWorkspace, appOpenedType, user, workspaceContent, contentType, renderAppFull, match } = this.props
9
+    const { idWorkspace, appOpenedType, user, workspaceContentList, contentType, renderAppFeature, dispatchCustomEvent, match } = this.props
9
 
10
 
10
     if (isNaN(idWorkspace) || idWorkspace === -1) return
11
     if (isNaN(idWorkspace) || idWorkspace === -1) return
11
 
12
 
12
-    if (['type', 'idcts'].every(p => p in match.params) && match.params.type !== 'contents' && workspaceContent.length) {
13
+    if (['type', 'idcts'].every(p => p in match.params) && match.params.type !== 'contents' && workspaceContentList.length) {
13
       if (isNaN(match.params.idcts) || !contentType.map(c => c.slug).includes(match.params.type)) return
14
       if (isNaN(match.params.idcts) || !contentType.map(c => c.slug).includes(match.params.type)) return
14
 
15
 
15
       const contentToOpen = {
16
       const contentToOpen = {
21
       console.log('%c<OpenContentApp> contentToOpen', 'color: #dcae84', contentToOpen)
22
       console.log('%c<OpenContentApp> contentToOpen', 'color: #dcae84', contentToOpen)
22
 
23
 
23
       if (appOpenedType === contentToOpen.type) { // app already open
24
       if (appOpenedType === contentToOpen.type) { // app already open
24
-        GLOBAL_dispatchEvent({
25
-          type: `${contentToOpen.type}_reloadContent`, // handled by html-documents:src/container/HtmlDocument.jsx
25
+        dispatchCustomEvent({
26
+          type: `${contentToOpen.type}_reloadContent`, // handled by html-document:src/container/AdminWorkspaceUser.jsx
26
           data: contentToOpen
27
           data: contentToOpen
27
         })
28
         })
28
       } else { // open another app
29
       } else { // open another app
29
         // if another app is already visible, hide it
30
         // if another app is already visible, hide it
30
-        if (appOpenedType !== false) GLOBAL_dispatchEvent({type: `${appOpenedType}_hideApp`})
31
+        if (appOpenedType !== false) dispatchCustomEvent({type: `${appOpenedType}_hideApp`})
31
         // open app
32
         // open app
32
-        renderAppFull(
33
+        renderAppFeature(
33
           contentType.find(ct => ct.slug === contentToOpen.type),
34
           contentType.find(ct => ct.slug === contentToOpen.type),
34
           user,
35
           user,
35
           contentToOpen
36
           contentToOpen
56
   }
57
   }
57
 }
58
 }
58
 
59
 
59
-const mapStateToProps = ({ user, workspaceContent, contentType }) => ({ user, workspaceContent, contentType })
60
+const mapStateToProps = ({ user, workspaceContentList, contentType }) => ({ user, workspaceContentList, contentType })
60
 export default withRouter(connect(mapStateToProps)(appFactory(OpenContentApp)))
61
 export default withRouter(connect(mapStateToProps)(appFactory(OpenContentApp)))

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

5
 
5
 
6
 const qs = require('query-string')
6
 const qs = require('query-string')
7
 
7
 
8
+// @FIXME Côme - 2018/07/31 - should this be in a component like AppFeatureManager ? (or AppCreateContentManager)
8
 export class OpenCreateContentApp extends React.Component {
9
 export class OpenCreateContentApp extends React.Component {
9
   openCreateContentApp = () => {
10
   openCreateContentApp = () => {
10
     const { idWorkspace, user, contentType, renderAppPopupCreation, match, location } = this.props
11
     const { idWorkspace, user, contentType, renderAppPopupCreation, match, location } = this.props
38
   }
39
   }
39
 }
40
 }
40
 
41
 
41
-const mapStateToProps = ({ user, workspaceContent, contentType }) => ({ user, workspaceContent, contentType })
42
+const mapStateToProps = ({ user, contentType }) => ({ user, contentType })
42
 export default withRouter(connect(mapStateToProps)(appFactory(OpenCreateContentApp)))
43
 export default withRouter(connect(mapStateToProps)(appFactory(OpenCreateContentApp)))

+ 4 - 1
frontend/src/component/common/Input/SubDropdownCreateButton.jsx View File

10
         <div className='subdropdown__link dropdown-item' onClick={e => props.onClickCreateContent(e, props.idFolder, app.slug)} key={app.slug}>
10
         <div className='subdropdown__link dropdown-item' onClick={e => props.onClickCreateContent(e, props.idFolder, app.slug)} key={app.slug}>
11
           <div className={`subdropdown__link__${app.slug} d-flex align-items-center`}>
11
           <div className={`subdropdown__link__${app.slug} d-flex align-items-center`}>
12
             <div className={`subdropdown__link__${app.slug}__icon mr-3`}>
12
             <div className={`subdropdown__link__${app.slug}__icon mr-3`}>
13
-              <i className={`fa fa-fw fa-${app.faIcon}`} />
13
+              <i
14
+                className={`fa fa-fw fa-${app.faIcon}`}
15
+                style={{color: app.hexcolor}}
16
+              />
14
             </div>
17
             </div>
15
             <div className='subdropdown__link__folder__text'>
18
             <div className='subdropdown__link__folder__text'>
16
               {app.creationLabel}
19
               {app.creationLabel}

+ 14 - 9
frontend/src/container/Account.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
 import { connect } from 'react-redux'
2
 import { connect } from 'react-redux'
3
 import Sidebar from './Sidebar.jsx'
3
 import Sidebar from './Sidebar.jsx'
4
-import PageWrapper from '../component/common/layout/PageWrapper.jsx'
5
-import PageTitle from '../component/common/layout/PageTitle.jsx'
6
-import PageContent from '../component/common/layout/PageContent.jsx'
7
 import UserInfo from '../component/Account/UserInfo.jsx'
4
 import UserInfo from '../component/Account/UserInfo.jsx'
8
 import MenuSubComponent from '../component/Account/MenuSubComponent.jsx'
5
 import MenuSubComponent from '../component/Account/MenuSubComponent.jsx'
9
 import PersonalData from '../component/Account/PersonalData.jsx'
6
 import PersonalData from '../component/Account/PersonalData.jsx'
11
 import Notification from '../component/Account/Notification.jsx'
8
 import Notification from '../component/Account/Notification.jsx'
12
 import Password from '../component/Account/Password.jsx'
9
 import Password from '../component/Account/Password.jsx'
13
 import Timezone from '../component/Account/Timezone.jsx'
10
 import Timezone from '../component/Account/Timezone.jsx'
14
-import { Delimiter } from 'tracim_frontend_lib'
11
+import {
12
+  Delimiter,
13
+  PageWrapper,
14
+  PageTitle,
15
+  PageContent
16
+} from 'tracim_frontend_lib'
15
 import { updateUserWorkspaceSubscriptionNotif } from '../action-creator.sync.js'
17
 import { updateUserWorkspaceSubscriptionNotif } from '../action-creator.sync.js'
16
 import {
18
 import {
17
   getTimezone,
19
   getTimezone,
18
   getUserRole
20
   getUserRole
19
 } from '../action-creator.async.js'
21
 } from '../action-creator.async.js'
22
+import { translate } from 'react-i18next'
20
 
23
 
21
 class Account extends React.Component {
24
 class Account extends React.Component {
22
   constructor (props) {
25
   constructor (props) {
77
     const subComponent = (() => {
80
     const subComponent = (() => {
78
       switch (this.state.subComponentMenu.find(({active}) => active).name) {
81
       switch (this.state.subComponentMenu.find(({active}) => active).name) {
79
         case 'personalData':
82
         case 'personalData':
80
-          return <PersonalData />
83
+          return <PersonalData
84
+          />
81
 
85
 
82
         // case 'calendar':
86
         // case 'calendar':
83
         //   return <Calendar user={this.props.user} />
87
         //   return <Calendar user={this.props.user} />
88
             onChangeSubscriptionNotif={this.handleChangeSubscriptionNotif}
92
             onChangeSubscriptionNotif={this.handleChangeSubscriptionNotif}
89
           />
93
           />
90
 
94
 
95
+        case 'password':
96
+          return <Password
97
+          />
98
+
91
         case 'timezone':
99
         case 'timezone':
92
           return <Timezone timezone={this.props.timezone} onChangeTimezone={this.handleChangeTimezone} />
100
           return <Timezone timezone={this.props.timezone} onChangeTimezone={this.handleChangeTimezone} />
93
-
94
-        case 'password':
95
-          return <Password />
96
       }
101
       }
97
     })()
102
     })()
98
 
103
 
127
 }
132
 }
128
 
133
 
129
 const mapStateToProps = ({ user, workspaceList, timezone }) => ({ user, workspaceList, timezone })
134
 const mapStateToProps = ({ user, workspaceList, timezone }) => ({ user, workspaceList, timezone })
130
-export default connect(mapStateToProps)(Account)
135
+export default connect(mapStateToProps)(translate()(Account))

+ 47 - 0
frontend/src/container/AppFullscreenManager.jsx View File

1
+import React from 'react'
2
+import { connect } from 'react-redux'
3
+import { withRouter } from 'react-router'
4
+import { Route } from 'react-router-dom'
5
+import { PAGE } from '../helper.js'
6
+import appFactory from '../appFactory.js'
7
+import Sidebar from './Sidebar.jsx'
8
+
9
+class AppFullscreenManager extends React.Component {
10
+  constructor (props) {
11
+    super(props)
12
+    this.state = {
13
+      AmIMounted: false
14
+    }
15
+  }
16
+
17
+  componentDidMount = () => this.setState({AmIMounted: true})
18
+
19
+  render () {
20
+    const { props } = this
21
+
22
+    return (
23
+      <div className='sidebarpagecontainer'>
24
+        <Sidebar />
25
+
26
+        <div id='appFullscreenContainer' />
27
+
28
+        {this.state.AmIMounted && (// we must wait for the component to be fully mounted to be sure the div#appFullscreenContainer exists in DOM
29
+          <div className='emptyDiForRoute'>
30
+            <Route path={PAGE.ADMIN.WORKSPACE} render={() => {
31
+              props.renderAppFullscreen({slug: 'admin_workspace_user', hexcolor: '#7d4e24', type: 'workspace'}, props.user, {})
32
+              return null
33
+            }} />
34
+
35
+            <Route path={PAGE.ADMIN.USER} render={() => {
36
+              props.renderAppFullscreen({slug: 'admin_workspace_user', hexcolor: '#7d4e24', type: 'user'}, props.user, {})
37
+              return null
38
+            }} />
39
+          </div>
40
+        )}
41
+      </div>
42
+    )
43
+  }
44
+}
45
+
46
+const mapStateToProps = ({ user }) => ({ user })
47
+export default connect(mapStateToProps)(withRouter(appFactory(AppFullscreenManager)))

+ 189 - 377
frontend/src/container/Dashboard.jsx View File

2
 import { connect } from 'react-redux'
2
 import { connect } from 'react-redux'
3
 import Sidebar from './Sidebar.jsx'
3
 import Sidebar from './Sidebar.jsx'
4
 import imgProfil from '../img/imgProfil.png'
4
 import imgProfil from '../img/imgProfil.png'
5
+import { translate } from 'react-i18next'
6
+import Radium from 'radium'
7
+import color from 'color'
5
 import {
8
 import {
6
-  getAppList,
7
-  getContentTypeList, getWorkspaceList
9
+  PageWrapper,
10
+  PageTitle,
11
+  PageContent
12
+} from 'tracim_frontend_lib'
13
+import {
14
+  getWorkspaceDetail,
15
+  getWorkspaceMemberList
8
 } from '../action-creator.async.js'
16
 } from '../action-creator.async.js'
9
 import {
17
 import {
10
-  setAppList,
11
-  setContentTypeList, setWorkspaceListIsOpenInSidebar, updateWorkspaceListData
18
+  addFlashMessage,
19
+  setWorkspaceDetail,
20
+  setWorkspaceMemberList
12
 } from '../action-creator.sync.js'
21
 } from '../action-creator.sync.js'
13
 
22
 
14
 class Dashboard extends React.Component {
23
 class Dashboard extends React.Component {
24
   }
33
   }
25
 
34
 
26
   async componentDidMount () {
35
   async componentDidMount () {
27
-    const { workspaceIdInUrl } = this.state
28
-    const { user, workspaceList, app, contentType, dispatch } = this.props
29
-
30
-    console.log('<Dashboard> componentDidMount')
31
-
32
-    if (app.length === 0) {
33
-      const fetchGetAppList = await dispatch(getAppList(user))
34
-      if (fetchGetAppList.status === 200) dispatch(setAppList(fetchGetAppList.json))
35
-    }
36
-
37
-    if (contentType.length === 0) {
38
-      const fetchGetContentTypeList = await dispatch(getContentTypeList(user))
39
-      if (fetchGetContentTypeList.status === 200) dispatch(setContentTypeList(fetchGetContentTypeList.json))
36
+    const { props, state } = this
37
+
38
+    const fetchWorkspaceDetail = await props.dispatch(getWorkspaceDetail(props.user, state.workspaceIdInUrl))
39
+    switch (fetchWorkspaceDetail.status) {
40
+      case 200:
41
+        props.dispatch(setWorkspaceDetail(fetchWorkspaceDetail.json))
42
+        break
43
+      case 400:
44
+      case 500:
45
+        props.dispatch(addFlashMessage(props.t('An error has happened'), 'warning'))
46
+        break
40
     }
47
     }
41
 
48
 
42
-    if (user.user_id !== -1 && workspaceList.length === 0) {
43
-      const fetchGetWorkspaceList = await dispatch(getWorkspaceList(user))
44
-
45
-      if (fetchGetWorkspaceList.status === 200) {
46
-        dispatch(updateWorkspaceListData(fetchGetWorkspaceList.json))
47
-        dispatch(setWorkspaceListIsOpenInSidebar(workspaceIdInUrl || fetchGetWorkspaceList.json[0].workspace_id, true))
48
-      }
49
+    const fetchWorkspaceMemberList = await props.dispatch(getWorkspaceMemberList(props.user, state.workspaceIdInUrl))
50
+    switch (fetchWorkspaceMemberList.status) {
51
+      case 200:
52
+        props.dispatch(setWorkspaceMemberList(fetchWorkspaceMemberList.json))
53
+        break
54
+      case 400:
55
+      case 500:
56
+        props.dispatch(addFlashMessage(props.t('An error has happened'), 'warning'))
57
+        break
49
     }
58
     }
50
   }
59
   }
51
 
60
 
66
   }))
75
   }))
67
 
76
 
68
   render () {
77
   render () {
78
+    const { props, state } = this
79
+
69
     return (
80
     return (
70
       <div className='sidebarpagecontainer'>
81
       <div className='sidebarpagecontainer'>
71
         <Sidebar />
82
         <Sidebar />
72
 
83
 
73
-        <div className='dashboard'>
74
-          <div className='container-fluid nopadding'>
75
-            <div className='dashboard__header mb-5'>
76
-              <div className='pageTitleGeneric dashboard__header__title d-flex align-items-center'>
77
-                <div className='pageTitleGeneric__title dashboard__header__title__text mr-3'>
78
-                  Dashboard
79
-                </div>
80
-                <div className='dashboard__header__acces' />
81
-              </div>
82
-              <div className='dashboard__header__advancedmode mr-3'>
83
-                <button type='button' className='btn btn-primary'>Activer édition avancé</button>
84
-              </div>
84
+        <PageWrapper customeClass='dashboard'>
85
+          <PageTitle
86
+            parentClass='dashboard__header'
87
+            title={props.t('Dashboard')}
88
+            subtitle={''}
89
+          >
90
+            <div className='dashboard__header__advancedmode mr-3'>
91
+              <button type='button' className='btn btn-primary'>
92
+                {props.t('Active advanced Dashboard')}
93
+              </button>
85
             </div>
94
             </div>
95
+          </PageTitle>
86
 
96
 
87
-            <div className='dashboard__wkswrapper'>
97
+          <PageContent>
98
+            <div className='dashboard__workspace-wrapper'>
88
               <div className='dashboard__workspace'>
99
               <div className='dashboard__workspace'>
89
                 <div className='dashboard__workspace__title'>
100
                 <div className='dashboard__workspace__title'>
90
-                  Développement tracim
101
+                  {props.curWs.label}
91
                 </div>
102
                 </div>
92
 
103
 
93
                 <div className='dashboard__workspace__detail'>
104
                 <div className='dashboard__workspace__detail'>
94
-                  Ligne directive pour le prochain design de Tracim et des futurs fonctionnalités.
105
+                  {props.curWs.description}
95
                 </div>
106
                 </div>
96
               </div>
107
               </div>
97
-              <div className='dashboard__userstatut'>
98
 
108
 
109
+              <div className='dashboard__userstatut'>
99
                 <div className='dashboard__userstatut__role'>
110
                 <div className='dashboard__userstatut__role'>
100
-                  <div className='dashboard__userstatut__role__text'>
101
-                    Hi ! Alexi, vous êtes actuellement
111
+                  <div className='dashboard__userstatut__role__msg'>
112
+                    {props.t(`Hi ! ${props.user.public_name}, vous êtes actuellement`)}
102
                   </div>
113
                   </div>
103
-                  <div className='dashboard__userstatut__role__rank'>
104
-                    <div className='dashboard__userstatut__role__rank__icon'>
114
+
115
+                  <div className='dashboard__userstatut__role__definition'>
116
+                    <div className='dashboard__userstatut__role__definition__icon'>
105
                       <i className='fa fa-graduation-cap' />
117
                       <i className='fa fa-graduation-cap' />
106
                     </div>
118
                     </div>
107
-                    <div className='dashboard__userstatut__role__rank__rolename'>
108
-                      Gestionnaire de projet
119
+
120
+                    <div className='dashboard__userstatut__role__definition__text'>
121
+                      {(member => member ? member.role : '')(props.curWs.member.find(m => m.id === props.user.user_id))}
109
                     </div>
122
                     </div>
110
                   </div>
123
                   </div>
111
                 </div>
124
                 </div>
112
 
125
 
113
                 <div className='dashboard__userstatut__notification'>
126
                 <div className='dashboard__userstatut__notification'>
114
                   <div className='dashboard__userstatut__notification__text'>
127
                   <div className='dashboard__userstatut__notification__text'>
115
-                    Vous êtes abonné(e) aux notifications de ce workspace
128
+                    {props.t("You have subscribed to this workspace's notifications")} (nyi)
116
                   </div>
129
                   </div>
117
-                  {this.state.displayNotifBtn === false &&
118
-                    <div
119
-                      className='dashboard__userstatut__notification__btn btn btn-outline-primary'
120
-                      onClick={this.handleToggleNotifBtn}
121
-                    >
122
-                      Changer de statut
123
-                    </div>
124
-                  }
125
 
130
 
126
-                  {this.state.displayNotifBtn === true &&
127
-                    <div className='dashboard__userstatut__notification__subscribe dropdown'>
128
-                      <button className='dashboard__userstatut__notification__subscribe__btn btn btn-outline-primary dropdown-toggle' type='button' id='dropdownMenuButton' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'>
129
-                        Abonné(e)
130
-                      </button>
131
-                      <div className='dashboard__userstatut__notification__subscribe__submenu dropdown-menu'>
132
-                        <div className='dashboard__userstatut__notification__subscribe__submenu__item dropdown-item'>Abonné(e)
133
-                        </div>
134
-                        <div className='dashboard__userstatut__notification__subscribe__submenu__item dropdown-item dropdown-item'>Non Abonné(e)
131
+                  {state.displayNotifBtn
132
+                    ? (
133
+                      <div className='dashboard__userstatut__notification__subscribe dropdown'>
134
+                        <button
135
+                          className='dashboard__userstatut__notification__subscribe__btn btn btn-outline-primary dropdown-toggle'
136
+                          type='button'
137
+                          id='dropdownMenuButton'
138
+                          data-toggle='dropdown'
139
+                          aria-haspopup='true'
140
+                          aria-expanded='false'
141
+                        >
142
+                          Abonné(e)
143
+                        </button>
144
+
145
+                        <div className='dashboard__userstatut__notification__subscribe__submenu dropdown-menu'>
146
+                          <div className='dashboard__userstatut__notification__subscribe__submenu__item dropdown-item'>
147
+                            {props.t('subscriber')}
148
+                          </div>
149
+                          <div className='dashboard__userstatut__notification__subscribe__submenu__item dropdown-item dropdown-item'>
150
+                            {props.t('unsubscribed')}
151
+                          </div>
135
                         </div>
152
                         </div>
136
                       </div>
153
                       </div>
137
-                    </div>
154
+                    )
155
+                    : (
156
+                      <div
157
+                        className='dashboard__userstatut__notification__btn btn btn-outline-primary'
158
+                        onClick={this.handleToggleNotifBtn}
159
+                      >
160
+                        {props.t('Change your status')}
161
+                      </div>
162
+                    )
138
                   }
163
                   }
139
                 </div>
164
                 </div>
140
               </div>
165
               </div>
141
             </div>
166
             </div>
142
 
167
 
143
             <div className='dashboard__calltoaction justify-content-xl-center'>
168
             <div className='dashboard__calltoaction justify-content-xl-center'>
144
-              <div className='dashboard__calltoaction__button btnaction btnthread'>
145
-                <div className='dashboard__calltoaction__button__text'>
146
-                  <div className='dashboard__calltoaction__button__text__icon'>
147
-                    <i className='fa fa-comments-o' />
148
-                  </div>
149
-                  <div className='dashboard__calltoaction__button__text__title'>
150
-                    Débuter une nouvelle discussion
151
-                  </div>
152
-                </div>
153
-              </div>
154
-
155
-              <div className='dashboard__calltoaction__button btnaction writefile'>
156
-                <div className='dashboard__calltoaction__button__text'>
157
-                  <div className='dashboard__calltoaction__button__text__icon'>
158
-                    <i className='fa fa-file-text-o' />
159
-                  </div>
160
-                  <div className='dashboard__calltoaction__button__text__title'>
161
-                    Rédiger un document
162
-                  </div>
163
-                </div>
164
-              </div>
165
-
166
-              <div className='dashboard__calltoaction__button btnaction importfile'>
167
-                <div className='dashboard__calltoaction__button__text'>
168
-                  <div className='dashboard__calltoaction__button__text__icon'>
169
-                    <i className='fa fa-paperclip' />
170
-                  </div>
171
-                  <div className='dashboard__calltoaction__button__text__title'>
172
-                    Importer un fichier
173
-                  </div>
174
-                </div>
175
-              </div>
176
-
177
-              {/*
178
-                <div className='dashboard__calltoaction__button btnaction visioconf'>
179
-                  <div className='dashboard__calltoaction__button__text'>
180
-                    <div className='dashboard__calltoaction__button__text__icon'>
181
-                      <i className='fa fa-video-camera' />
182
-                    </div>
183
-                    <div className='dashboard__calltoaction__button__text__title'>
184
-                      Débuter une visioconférence
185
-                    </div>
186
-                  </div>
187
-                </div>
188
-
189
-                <div className='dashboard__calltoaction__button btnaction calendar'>
169
+              {props.contentType.map(ct =>
170
+                <div
171
+                  className='dashboard__calltoaction__button btnaction'
172
+                  style={{
173
+                    backgroundColor: ct.hexcolor,
174
+                    ':hover': {
175
+                      backgroundColor: color(ct.hexcolor).darken(0.15).hexString()
176
+                    }
177
+                  }}
178
+                  key={ct.label}
179
+                >
190
                   <div className='dashboard__calltoaction__button__text'>
180
                   <div className='dashboard__calltoaction__button__text'>
191
                     <div className='dashboard__calltoaction__button__text__icon'>
181
                     <div className='dashboard__calltoaction__button__text__icon'>
192
-                      <i className='fa fa-calendar' />
182
+                      <i className={`fa fa-${ct.faIcon}`} />
193
                     </div>
183
                     </div>
194
                     <div className='dashboard__calltoaction__button__text__title'>
184
                     <div className='dashboard__calltoaction__button__text__title'>
195
-                      Voir le Calendrier
185
+                      {ct.creationLabel}
196
                     </div>
186
                     </div>
197
                   </div>
187
                   </div>
198
                 </div>
188
                 </div>
199
-              */ }
200
-
201
-              <div className='dashboard__calltoaction__button btnaction explore'>
202
-                <div className='dashboard__calltoaction__button__text'>
203
-                  <div className='dashboard__calltoaction__button__text__icon'>
204
-                    <i className='fa fa-folder-open-o' />
205
-                  </div>
206
-                  <div className='dashboard__calltoaction__button__text__title'>
207
-                    Explorer le Workspace
208
-                  </div>
209
-                </div>
210
-              </div>
189
+              )}
211
             </div>
190
             </div>
212
 
191
 
213
             <div className='dashboard__wksinfo'>
192
             <div className='dashboard__wksinfo'>
214
               <div className='dashboard__activity'>
193
               <div className='dashboard__activity'>
215
                 <div className='dashboard__activity__header'>
194
                 <div className='dashboard__activity__header'>
216
                   <div className='dashboard__activity__header__title subTitle'>
195
                   <div className='dashboard__activity__header__title subTitle'>
217
-                    Activité récente
196
+                    {this.props.t('Recent activity')}
218
                   </div>
197
                   </div>
219
 
198
 
220
                   <div className='dashboard__activity__header__allread btn btn-outline-primary'>
199
                   <div className='dashboard__activity__header__allread btn btn-outline-primary'>
221
-                    Tout marquer comme lu
200
+                    {this.props.t('Mark everything as read')}
222
                   </div>
201
                   </div>
223
                 </div>
202
                 </div>
224
                 <div className='dashboard__activity__wrapper'>
203
                 <div className='dashboard__activity__wrapper'>
225
-                  <div className='dashboard__activity__workspace'>
226
-                    <div className='dashboard__activity__workspace__icon'>
227
-                      <i className='fa fa-comments-o' />
228
-                    </div>
229
-                    <div className='dashboard__activity__workspace__name'>
230
-                      <span>Développement Tracim</span>
231
-                    </div>
232
-                  </div>
233
-
234
-                  <div className='dashboard__activity__workspace'>
235
-                    <div className='dashboard__activity__workspace__icon'>
236
-                      <i className='fa fa-list-ul' />
237
-                    </div>
238
-                    <div className='dashboard__activity__workspace__name'>
239
-                      Mission externe
240
-                    </div>
241
-                  </div>
242
-
243
-                  <div className='dashboard__activity__workspace'>
244
-                    <div className='dashboard__activity__workspace__icon'>
245
-                      <i className='fa fa-list-ul' />
246
-                    </div>
247
-                    <div className='dashboard__activity__workspace__name'>
248
-                      Recherche et developpement
249
-                    </div>
250
-                  </div>
251
-
252
-                  <div className='dashboard__activity__workspace'>
253
-                    <div className='dashboard__activity__workspace__icon'>
254
-                      <i className='fa fa-file-text-o' />
255
-                    </div>
256
-                    <div className='dashboard__activity__workspace__name'>
257
-                      <span>Marketing</span>
258
-                    </div>
259
-                  </div>
260
 
204
 
261
                   <div className='dashboard__activity__workspace'>
205
                   <div className='dashboard__activity__workspace'>
262
                     <div className='dashboard__activity__workspace__icon'>
206
                     <div className='dashboard__activity__workspace__icon'>
263
                       <i className='fa fa-comments-o' />
207
                       <i className='fa fa-comments-o' />
264
                     </div>
208
                     </div>
265
                     <div className='dashboard__activity__workspace__name'>
209
                     <div className='dashboard__activity__workspace__name'>
266
-                      <span>Évolution</span>
267
-                    </div>
268
-                  </div>
269
-
270
-                  <div className='dashboard__activity__workspace'>
271
-                    <div className='dashboard__activity__workspace__icon'>
272
-                      <i className='fa fa-file-text-o' />
273
-                    </div>
274
-                    <div className='dashboard__activity__workspace__name'>
275
-                      Commercialisation
210
+                      <span>Développement Tracim</span>
276
                     </div>
211
                     </div>
277
                   </div>
212
                   </div>
278
 
213
 
279
                   <div className='dashboard__activity__more d-flex flex-row-reverse'>
214
                   <div className='dashboard__activity__more d-flex flex-row-reverse'>
280
                     <div className='dashboard__activity__more__btn btn btn-outline-primary'>
215
                     <div className='dashboard__activity__more__btn btn btn-outline-primary'>
281
-                      Voir plus
216
+                      {this.props.t('See more')}
282
                     </div>
217
                     </div>
283
                   </div>
218
                   </div>
284
                 </div>
219
                 </div>
287
               <div className='dashboard__memberlist'>
222
               <div className='dashboard__memberlist'>
288
 
223
 
289
                 <div className='dashboard__memberlist__title subTitle'>
224
                 <div className='dashboard__memberlist__title subTitle'>
290
-                  Liste des membres
225
+                  {this.props.t('Member List')}
291
                 </div>
226
                 </div>
292
 
227
 
293
                 <div className='dashboard__memberlist__wrapper'>
228
                 <div className='dashboard__memberlist__wrapper'>
294
                   {this.state.displayNewMemberDashboard === false &&
229
                   {this.state.displayNewMemberDashboard === false &&
295
                     <div>
230
                     <div>
296
                       <ul className='dashboard__memberlist__list'>
231
                       <ul className='dashboard__memberlist__list'>
232
+
297
                         <li className='dashboard__memberlist__list__item'>
233
                         <li className='dashboard__memberlist__list__item'>
298
                           <div className='dashboard__memberlist__list__item__avatar'>
234
                           <div className='dashboard__memberlist__list__item__avatar'>
299
                             <img src={imgProfil} alt='avatar' />
235
                             <img src={imgProfil} alt='avatar' />
311
                           </div>
247
                           </div>
312
                         </li>
248
                         </li>
313
 
249
 
314
-                        <li className='dashboard__memberlist__list__item'>
315
-                          <div className='dashboard__memberlist__list__item__avatar'>
316
-                            <img src={imgProfil} alt='avatar' />
317
-                          </div>
318
-                          <div className='dashboard__memberlist__list__item__info mr-auto'>
319
-                            <div className='dashboard__memberlist__list__item__info__name'>
320
-                              Aldwin Vinel
321
-                            </div>
322
-                            <div className='dashboard__memberlist__list__item__info__role'>
323
-                              Lecteur
324
-                            </div>
325
-                          </div>
326
-                          <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
327
-                            <i className='fa fa-trash-o' />
328
-                          </div>
329
-                        </li>
330
-
331
-                        <li className='dashboard__memberlist__list__item'>
332
-                          <div className='dashboard__memberlist__list__item__avatar'>
333
-                            <img src={imgProfil} alt='avatar' />
334
-                          </div>
335
-                          <div className='dashboard__memberlist__list__item__info mr-auto'>
336
-                            <div className='dashboard__memberlist__list__item__info__name'>
337
-                              William Himme
338
-                            </div>
339
-                            <div className='dashboard__memberlist__list__item__info__role'>
340
-                              Contributeur
341
-                            </div>
342
-                          </div>
343
-                          <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
344
-                            <i className='fa fa-trash-o' />
345
-                          </div>
346
-                        </li>
347
-
348
-                        <li className='dashboard__memberlist__list__item'>
349
-                          <div className='dashboard__memberlist__list__item__avatar'>
350
-                            <img src={imgProfil} alt='avatar' />
351
-                          </div>
352
-                          <div className='dashboard__memberlist__list__item__info mr-auto'>
353
-                            <div className='dashboard__memberlist__list__item__info__name'>
354
-                              Yacine Lite
355
-                            </div>
356
-                            <div className='dashboard__memberlist__list__item__info__role'>
357
-                              Contributeur
358
-                            </div>
359
-                          </div>
360
-                          <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
361
-                            <i className='fa fa-trash-o' />
362
-                          </div>
363
-                        </li>
364
-
365
-                        <li className='dashboard__memberlist__list__item'>
366
-                          <div className='dashboard__memberlist__list__item__avatar'>
367
-                            <img src={imgProfil} alt='avatar' />
368
-                          </div>
369
-                          <div className='dashboard__memberlist__list__item__info mr-auto'>
370
-                            <div className='dashboard__memberlist__list__item__info__name'>
371
-                              Alexi Falcin
372
-                            </div>
373
-                            <div className='dashboard__memberlist__list__item__info__role'>
374
-                              Gestionnaire
375
-                            </div>
376
-                          </div>
377
-                          <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
378
-                            <i className='fa fa-trash-o' />
379
-                          </div>
380
-                        </li>
381
-
382
-                        <li className='dashboard__memberlist__list__item'>
383
-                          <div className='dashboard__memberlist__list__item__avatar'>
384
-                            <img src={imgProfil} alt='avatar' />
385
-                          </div>
386
-                          <div className='dashboard__memberlist__list__item__info mr-auto'>
387
-                            <div className='dashboard__memberlist__list__item__info__name'>
388
-                              Mickaël Fonati
389
-                            </div>
390
-                            <div className='dashboard__memberlist__list__item__info__role'>
391
-                              Gestionnaire
392
-                            </div>
393
-                          </div>
394
-                          <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
395
-                            <i className='fa fa-trash-o' />
396
-                          </div>
397
-                        </li>
398
-
399
-                        <li className='dashboard__memberlist__list__item'>
400
-                          <div className='dashboard__memberlist__list__item__avatar'>
401
-                            <img src={imgProfil} alt='avatar' />
402
-                          </div>
403
-                          <div className='dashboard__memberlist__list__item__info mr-auto'>
404
-                            <div className='dashboard__memberlist__list__item__info__name'>
405
-                              Eva Lonbard
406
-                            </div>
407
-                            <div className='dashboard__memberlist__list__item__info__role'>
408
-                              Gestionnaire
409
-                            </div>
410
-                          </div>
411
-                          <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
412
-                            <i className='fa fa-trash-o' />
413
-                          </div>
414
-                        </li>
415
                       </ul>
250
                       </ul>
416
 
251
 
417
-                      <div className='dashboard__memberlist__btnadd'>
252
+                      <div
253
+                        className='dashboard__memberlist__btnadd'
254
+                        onClick={this.handleToggleNewMemberDashboard}
255
+                      >
418
                         <div className='dashboard__memberlist__btnadd__button'>
256
                         <div className='dashboard__memberlist__btnadd__button'>
419
                           <div className='dashboard__memberlist__btnadd__button__avatar'>
257
                           <div className='dashboard__memberlist__btnadd__button__avatar'>
420
                             <div className='dashboard__memberlist__btnadd__button__avatar__icon'>
258
                             <div className='dashboard__memberlist__btnadd__button__avatar__icon'>
423
                           </div>
261
                           </div>
424
                           <div
262
                           <div
425
                             className='dashboard__memberlist__btnadd__button__text'
263
                             className='dashboard__memberlist__btnadd__button__text'
426
-                            onClick={this.handleToggleNewMemberDashboard}
427
                           >
264
                           >
428
-                             Ajouter un membre
265
+                            {this.props.t('Add a member')}
429
                           </div>
266
                           </div>
430
                         </div>
267
                         </div>
431
                       </div>
268
                       </div>
433
                   }
270
                   }
434
 
271
 
435
                   {this.state.displayNewMemberDashboard === true &&
272
                   {this.state.displayNewMemberDashboard === true &&
436
-                    <form className='dashboard__memberlist__form'>
437
-                      <div
438
-                        className='dashboard__memberlist__form__close d-flex justify-content-end'
439
-                      >
440
-                        <i className='fa fa-times' onClick={this.handleToggleNewMemberDashboard} />
273
+                  <form className='dashboard__memberlist__form'>
274
+                    <div
275
+                      className='dashboard__memberlist__form__close d-flex justify-content-end'
276
+                    >
277
+                      <i className='fa fa-times' onClick={this.handleToggleNewMemberDashboard} />
278
+                    </div>
279
+
280
+                    <div className='dashboard__memberlist__form__member'>
281
+                      <div className='dashboard__memberlist__form__member__name'>
282
+                        <label className='name__label' htmlFor='addmember'>
283
+                          {this.props.t('Enter the name or email of the member')}
284
+                        </label>
285
+                        <input type='text' id='addmember' className='name__input form-control' placeholder='Nom ou Email' />
441
                       </div>
286
                       </div>
442
-                      <div className='dashboard__memberlist__form__member'>
443
-                        <div className='dashboard__memberlist__form__member__name'>
444
-                          <label className='name__label' htmlFor='addmember'>Indiquer le nom ou l'email du membre</label>
445
-                          <input type='text' id='addmember' className='name__input form-control' placeholder='Nom ou Email' />
287
+
288
+                      <div className='dashboard__memberlist__form__member__create'>
289
+                        <div className='create__radiobtn mr-3'>
290
+                          <input type='radio' />
446
                         </div>
291
                         </div>
447
-                        <div className='dashboard__memberlist__form__member__create'>
448
-                          <div className='create__radiobtn mr-3'>
449
-                            <input type='radio' />
450
-                          </div>
451
-                          <div className='create__text'>
452
-                            Créer un compte
453
-                          </div>
292
+
293
+                        <div className='create__text'>
294
+                          {this.props.t('Create an account')}
454
                         </div>
295
                         </div>
455
                       </div>
296
                       </div>
456
-                      <div className='dashboard__memberlist__form__role'>
457
-                        <div className='dashboard__memberlist__form__role__text'>
458
-                          Choisissez le rôle du membre
459
-                        </div>
460
-                        <ul className='dashboard__memberlist__form__role__list'>
461
-                          <li className='dashboard__memberlist__form__role__list__item'>
462
-                            <div className='item__radiobtn mr-3'>
463
-                              <input type='radio' name='role' value='responsable' />
464
-                            </div>
465
-                            <div className='item__text'>
466
-                              <div className='item_text_icon mr-2'>
467
-                                <i className='fa fa-gavel' />
468
-                              </div>
469
-                              <div className='item__text__name'>
470
-                                Responsable
471
-                              </div>
472
-                            </div>
473
-                          </li>
474
-                          <li className='dashboard__memberlist__form__role__list__item'>
475
-                            <div className='item__radiobtn mr-3'>
476
-                              <input type='radio' name='role' value='gestionnaire' />
477
-                            </div>
478
-                            <div className='item__text'>
479
-                              <div className='item_text_icon mr-2'>
480
-                                <i className='fa fa-graduation-cap' />
481
-                              </div>
482
-                              <div className='item__text__name'>
483
-                                Gestionnaire de contenu
484
-                              </div>
485
-                            </div>
486
-                          </li>
487
-                          <li className='dashboard__memberlist__form__role__list__item'>
488
-                            <div className='item__radiobtn mr-3'>
489
-                              <input type='radio' name='role' value='contributeur' />
490
-                            </div>
491
-                            <div className='item__text'>
492
-                              <div className='item_text_icon mr-2'>
493
-                                <i className='fa fa-pencil' />
494
-                              </div>
495
-                              <div className='item__text__name'>
496
-                                Contributeur
497
-                              </div>
498
-                            </div>
499
-                          </li>
500
-                          <li className='dashboard__memberlist__form__role__list__item'>
501
-                            <div className='item__radiobtn mr-3'>
502
-                              <input type='radio' name='role' value='lecteur' />
297
+                    </div>
298
+
299
+                    <div className='dashboard__memberlist__form__role'>
300
+                      <div className='dashboard__memberlist__form__role__text'>
301
+                        {this.props.t('Choose the role of the member')}
302
+                      </div>
303
+
304
+                      <ul className='dashboard__memberlist__form__role__list'>
305
+
306
+                        <li className='dashboard__memberlist__form__role__list__item'>
307
+                          <div className='item__radiobtn mr-3'>
308
+                            <input type='radio' name='role' value='responsable' />
309
+                          </div>
310
+
311
+                          <div className='item__text'>
312
+                            <div className='item_text_icon mr-2'>
313
+                              <i className='fa fa-gavel' />
503
                             </div>
314
                             </div>
504
-                            <div className='item__text'>
505
-                              <div className='item_text_icon mr-2'>
506
-                                <i className='fa fa-eye' />
507
-                              </div>
508
-                              <div className='item__text__name'>
509
-                                Lecteur
510
-                              </div>
315
+
316
+                            <div className='item__text__name'>
317
+                              {this.props.t('Supervisor')}
511
                             </div>
318
                             </div>
512
-                          </li>
513
-                        </ul>
514
-                      </div>
515
-                      <div className='dashboard__memberlist__form__submitbtn'>
516
-                        <button className='btn btn-outline-primary'>Valider</button>
517
-                      </div>
518
-                    </form>
319
+                          </div>
320
+                        </li>
321
+
322
+                      </ul>
323
+                    </div>
324
+
325
+                    <div className='dashboard__memberlist__form__submitbtn'>
326
+                      <button className='btn btn-outline-primary'>
327
+                        {this.props.t('Validate')}
328
+                      </button>
329
+                    </div>
330
+                  </form>
519
                   }
331
                   }
520
                 </div>
332
                 </div>
521
               </div>
333
               </div>
532
                   </div>
344
                   </div>
533
 
345
 
534
                   <div className='dashboard__moreinfo__webdav__btn__text genericBtnInfoDashboard__btn__text'>
346
                   <div className='dashboard__moreinfo__webdav__btn__text genericBtnInfoDashboard__btn__text'>
535
-                    Implémenter Tracim dans votre explorateur
347
+                    {this.props.t('Implement Tracim in your explorer')}
536
                   </div>
348
                   </div>
537
                 </div>
349
                 </div>
538
                 {this.state.displayWebdavBtn === true &&
350
                 {this.state.displayWebdavBtn === true &&
539
-                  <div>
540
-                    <div className='dashboard__moreinfo__webdav__information genericBtnInfoDashboard__info'>
541
-                      <div className='dashboard__moreinfo__webdav__information__text genericBtnInfoDashboard__info__text'>
542
-                        Retrouvez tous vos documents déposés en ligne directement sur votre ordinateur via le poste de travail, sans passer par le logiciel.
543
-                      </div>
351
+                <div>
352
+                  <div className='dashboard__moreinfo__webdav__information genericBtnInfoDashboard__info'>
353
+                    <div className='dashboard__moreinfo__webdav__information__text genericBtnInfoDashboard__info__text'>
354
+                      {this.props.t('Find all your documents deposited online directly on your computer via the workstation, without going through the software.')}'
355
+                    </div>
544
 
356
 
545
-                      <div className='dashboard__moreinfo__webdav__information__link genericBtnInfoDashboard__info__link'>
546
-                        http://algoo.trac.im/webdav/
547
-                      </div>
357
+                    <div className='dashboard__moreinfo__webdav__information__link genericBtnInfoDashboard__info__link'>
358
+                      http://algoo.trac.im/webdav/
548
                     </div>
359
                     </div>
549
                   </div>
360
                   </div>
361
+                </div>
550
                 }
362
                 }
551
               </div>
363
               </div>
552
               <div className='dashboard__moreinfo__calendar genericBtnInfoDashboard'>
364
               <div className='dashboard__moreinfo__calendar genericBtnInfoDashboard'>
560
                     </div>
372
                     </div>
561
 
373
 
562
                     <div className='dashboard__moreinfo__calendar__btn__text genericBtnInfoDashboard__btn__text'>
374
                     <div className='dashboard__moreinfo__calendar__btn__text genericBtnInfoDashboard__btn__text'>
563
-                      Calendrier de l'espace de travail
375
+                      {this.props.t('Workspace Calendar')}
564
                     </div>
376
                     </div>
565
                   </div>
377
                   </div>
566
                 </div>
378
                 </div>
567
                 <div className='dashboard__moreinfo__calendar__wrapperText'>
379
                 <div className='dashboard__moreinfo__calendar__wrapperText'>
568
                   {this.state.displayCalendarBtn === true &&
380
                   {this.state.displayCalendarBtn === true &&
569
-                    <div>
570
-                      <div className='dashboard__moreinfo__calendar__information genericBtnInfoDashboard__info'>
571
-                        <div className='dashboard__moreinfo__calendar__information__text genericBtnInfoDashboard__info__text'>
572
-                          Chaque espace de travail possède son propre calendrier.
573
-                        </div>
381
+                  <div>
382
+                    <div className='dashboard__moreinfo__calendar__information genericBtnInfoDashboard__info'>
383
+                      <div className='dashboard__moreinfo__calendar__information__text genericBtnInfoDashboard__info__text'>
384
+                        {this.props.t('Each workspace has its own calendar.')}
385
+                      </div>
574
 
386
 
575
-                        <div className='dashboard__moreinfo__calendar__information__link genericBtnInfoDashboard__info__link'>
576
-                          http://algoo.trac.im/calendar/
577
-                        </div>
387
+                      <div className='dashboard__moreinfo__calendar__information__link genericBtnInfoDashboard__info__link'>
388
+                        http://algoo.trac.im/calendar/
578
                       </div>
389
                       </div>
579
                     </div>
390
                     </div>
391
+                  </div>
580
                   }
392
                   }
581
                 </div>
393
                 </div>
582
               </div>
394
               </div>
583
             </div>
395
             </div>
584
-          </div>
585
-        </div>
396
+          </PageContent>
397
+        </PageWrapper>
586
       </div>
398
       </div>
587
     )
399
     )
588
   }
400
   }
589
 }
401
 }
590
 
402
 
591
-const mapStateToProps = ({ user, app, contentType, workspaceList }) => ({ user, app, contentType, workspaceList })
592
-export default connect(mapStateToProps)(Dashboard)
403
+const mapStateToProps = ({ user, contentType, currentWorkspace }) => ({ user, contentType, curWs: currentWorkspace })
404
+export default connect(mapStateToProps)(translate()(Radium(Dashboard)))

+ 571 - 0
frontend/src/container/Dashboard_old.jsx View File

1
+import React from 'react'
2
+import { connect } from 'react-redux'
3
+import Sidebar from './Sidebar.jsx'
4
+import imgProfil from '../img/imgProfil.png'
5
+import { translate } from 'react-i18next'
6
+
7
+class Dashboard extends React.Component {
8
+  constructor (props) {
9
+    super(props)
10
+    this.state = {
11
+      workspaceIdInUrl: props.match.params.idws ? parseInt(props.match.params.idws) : null, // this is used to avoid handling the parseInt everytime
12
+      displayNewMemberDashboard: false,
13
+      displayNotifBtn: false,
14
+      displayWebdavBtn: false,
15
+      displayCalendarBtn: false
16
+    }
17
+  }
18
+
19
+  handleToggleNewMemberDashboard = () => this.setState(prevState => ({
20
+    displayNewMemberDashboard: !prevState.displayNewMemberDashboard
21
+  }))
22
+
23
+  handleToggleNotifBtn = () => this.setState(prevState => ({
24
+    displayNotifBtn: !prevState.displayNotifBtn
25
+  }))
26
+
27
+  handleToggleWebdavBtn = () => this.setState(prevState => ({
28
+    displayWebdavBtn: !prevState.displayWebdavBtn
29
+  }))
30
+
31
+  handleToggleCalendarBtn = () => this.setState(prevState => ({
32
+    displayCalendarBtn: !prevState.displayCalendarBtn
33
+  }))
34
+
35
+  render () {
36
+    return (
37
+      <div className='sidebarpagecontainer'>
38
+        <Sidebar />
39
+
40
+        <div className='dashboard'>
41
+          <div className='container-fluid nopadding'>
42
+            <div className='dashboard__header mb-5'>
43
+              <div className='pageTitleGeneric dashboard__header__title d-flex align-items-center'>
44
+                <div className='pageTitleGeneric__title dashboard__header__title__text mr-3'>
45
+                  {this.props.t('Dashboard')}
46
+                </div>
47
+                <div className='dashboard__header__acces' />
48
+              </div>
49
+
50
+              <div className='dashboard__header__advancedmode mr-3'>
51
+                <button type='button' className='btn btn-primary'>
52
+                  {this.props.t('Active advanced Dashboard')}
53
+                </button>
54
+              </div>
55
+            </div>
56
+
57
+            <div className='dashboard__workspace-wrapper'>
58
+              <div className='dashboard__workspace'>
59
+                <div className='dashboard__workspace__title'>
60
+                  Développement tracim
61
+                </div>
62
+
63
+                <div className='dashboard__workspace__detail'>
64
+                  Ligne directive pour le prochain design de Tracim et des futurs fonctionnalités.
65
+                </div>
66
+              </div>
67
+
68
+              <div className='dashboard__userstatut'>
69
+
70
+                <div className='dashboard__userstatut__role'>
71
+                  <div className='dashboard__userstatut__role__text'>
72
+                    Hi ! Alexi, vous êtes actuellement
73
+                  </div>
74
+                  <div className='dashboard__userstatut__role__rank'>
75
+                    <div className='dashboard__userstatut__role__rank__icon'>
76
+                      <i className='fa fa-graduation-cap' />
77
+                    </div>
78
+                    <div className='dashboard__userstatut__role__rank__rolename'>
79
+                      Gestionnaire de projet
80
+                    </div>
81
+                  </div>
82
+                </div>
83
+
84
+                <div className='dashboard__userstatut__notification'>
85
+                  <div className='dashboard__userstatut__notification__text'>
86
+                    Vous êtes abonné(e) aux notifications de ce workspace
87
+                  </div>
88
+                  {this.state.displayNotifBtn === false &&
89
+                  <div
90
+                    className='dashboard__userstatut__notification__btn btn btn-outline-primary'
91
+                    onClick={this.handleToggleNotifBtn}
92
+                  >
93
+                    {this.props.t('Change your status')}
94
+                  </div>
95
+                  }
96
+
97
+                  {this.state.displayNotifBtn === true &&
98
+                  <div className='dashboard__userstatut__notification__subscribe dropdown'>
99
+                    <button className='dashboard__userstatut__notification__subscribe__btn btn btn-outline-primary dropdown-toggle' type='button' id='dropdownMenuButton' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'>
100
+                      Abonné(e)
101
+                    </button>
102
+                    <div className='dashboard__userstatut__notification__subscribe__submenu dropdown-menu'>
103
+                      <div className='dashboard__userstatut__notification__subscribe__submenu__item dropdown-item'>
104
+                        {this.props.t('subscriber')}
105
+                      </div>
106
+                      <div className='dashboard__userstatut__notification__subscribe__submenu__item dropdown-item dropdown-item'>
107
+                        {this.props.t('unsubscribed')}
108
+                      </div>
109
+                    </div>
110
+                  </div>
111
+                  }
112
+                </div>
113
+              </div>
114
+            </div>
115
+
116
+            <div className='dashboard__calltoaction justify-content-xl-center'>
117
+              <div className='dashboard__calltoaction__button btnaction btnthread'>
118
+                <div className='dashboard__calltoaction__button__text'>
119
+                  <div className='dashboard__calltoaction__button__text__icon'>
120
+                    <i className='fa fa-comments-o' />
121
+                  </div>
122
+                  <div className='dashboard__calltoaction__button__text__title'>
123
+                    {this.props.t('Start a new Thread')}
124
+                  </div>
125
+                </div>
126
+              </div>
127
+
128
+              <div className='dashboard__calltoaction__button btnaction writefile'>
129
+                <div className='dashboard__calltoaction__button__text'>
130
+                  <div className='dashboard__calltoaction__button__text__icon'>
131
+                    <i className='fa fa-file-text-o' />
132
+                  </div>
133
+                  <div className='dashboard__calltoaction__button__text__title'>
134
+                    {this.props.t('Writing a document')}
135
+                  </div>
136
+                </div>
137
+              </div>
138
+
139
+              <div className='dashboard__calltoaction__button btnaction importfile'>
140
+                <div className='dashboard__calltoaction__button__text'>
141
+                  <div className='dashboard__calltoaction__button__text__icon'>
142
+                    <i className='fa fa-paperclip' />
143
+                  </div>
144
+                  <div className='dashboard__calltoaction__button__text__title'>
145
+                    {this.props.t('Upload a file')}
146
+                  </div>
147
+                </div>
148
+              </div>
149
+
150
+              {/*
151
+                <div className='dashboard__calltoaction__button btnaction visioconf'>
152
+                  <div className='dashboard__calltoaction__button__text'>
153
+                    <div className='dashboard__calltoaction__button__text__icon'>
154
+                      <i className='fa fa-video-camera' />
155
+                    </div>
156
+                    <div className='dashboard__calltoaction__button__text__title'>
157
+                      {this.props.t('Start a videoconference')}
158
+                    </div>
159
+                  </div>
160
+                </div>
161
+
162
+                <div className='dashboard__calltoaction__button btnaction calendar'>
163
+                  <div className='dashboard__calltoaction__button__text'>
164
+                    <div className='dashboard__calltoaction__button__text__icon'>
165
+                      <i className='fa fa-calendar' />
166
+                    </div>
167
+                    <div className='dashboard__calltoaction__button__text__title'>
168
+                      {this.props.t('View the Calendar')}
169
+                    </div>
170
+                  </div>
171
+                </div>
172
+              */ }
173
+
174
+              <div className='dashboard__calltoaction__button btnaction explore'>
175
+                <div className='dashboard__calltoaction__button__text'>
176
+                  <div className='dashboard__calltoaction__button__text__icon'>
177
+                    <i className='fa fa-folder-open-o' />
178
+                  </div>
179
+                  <div className='dashboard__calltoaction__button__text__title'>
180
+                    {this.props.t('Explore the workspace')}
181
+                  </div>
182
+                </div>
183
+              </div>
184
+            </div>
185
+
186
+            <div className='dashboard__wksinfo'>
187
+              <div className='dashboard__activity'>
188
+                <div className='dashboard__activity__header'>
189
+                  <div className='dashboard__activity__header__title subTitle'>
190
+                    {this.props.t('Recent activity')}
191
+                  </div>
192
+
193
+                  <div className='dashboard__activity__header__allread btn btn-outline-primary'>
194
+                    {this.props.t('Mark everything as read')}
195
+                  </div>
196
+                </div>
197
+                <div className='dashboard__activity__wrapper'>
198
+                  <div className='dashboard__activity__workspace'>
199
+                    <div className='dashboard__activity__workspace__icon'>
200
+                      <i className='fa fa-comments-o' />
201
+                    </div>
202
+                    <div className='dashboard__activity__workspace__name'>
203
+                      <span>Développement Tracim</span>
204
+                    </div>
205
+                  </div>
206
+
207
+                  <div className='dashboard__activity__workspace'>
208
+                    <div className='dashboard__activity__workspace__icon'>
209
+                      <i className='fa fa-list-ul' />
210
+                    </div>
211
+                    <div className='dashboard__activity__workspace__name'>
212
+                      Mission externe
213
+                    </div>
214
+                  </div>
215
+
216
+                  <div className='dashboard__activity__workspace'>
217
+                    <div className='dashboard__activity__workspace__icon'>
218
+                      <i className='fa fa-list-ul' />
219
+                    </div>
220
+                    <div className='dashboard__activity__workspace__name'>
221
+                      Recherche et developpement
222
+                    </div>
223
+                  </div>
224
+
225
+                  <div className='dashboard__activity__workspace'>
226
+                    <div className='dashboard__activity__workspace__icon'>
227
+                      <i className='fa fa-file-text-o' />
228
+                    </div>
229
+                    <div className='dashboard__activity__workspace__name'>
230
+                      <span>Marketing</span>
231
+                    </div>
232
+                  </div>
233
+
234
+                  <div className='dashboard__activity__workspace'>
235
+                    <div className='dashboard__activity__workspace__icon'>
236
+                      <i className='fa fa-comments-o' />
237
+                    </div>
238
+                    <div className='dashboard__activity__workspace__name'>
239
+                      <span>Évolution</span>
240
+                    </div>
241
+                  </div>
242
+
243
+                  <div className='dashboard__activity__workspace'>
244
+                    <div className='dashboard__activity__workspace__icon'>
245
+                      <i className='fa fa-file-text-o' />
246
+                    </div>
247
+                    <div className='dashboard__activity__workspace__name'>
248
+                      Commercialisation
249
+                    </div>
250
+                  </div>
251
+
252
+                  <div className='dashboard__activity__more d-flex flex-row-reverse'>
253
+                    <div className='dashboard__activity__more__btn btn btn-outline-primary'>
254
+                      {this.props.t('See more')}
255
+                    </div>
256
+                  </div>
257
+                </div>
258
+              </div>
259
+
260
+              <div className='dashboard__memberlist'>
261
+
262
+                <div className='dashboard__memberlist__title subTitle'>
263
+                  {this.props.t('Member List')}
264
+                </div>
265
+
266
+                <div className='dashboard__memberlist__wrapper'>
267
+                  {this.state.displayNewMemberDashboard === false &&
268
+                  <div>
269
+                    <ul className='dashboard__memberlist__list'>
270
+                      <li className='dashboard__memberlist__list__item'>
271
+                        <div className='dashboard__memberlist__list__item__avatar'>
272
+                          <img src={imgProfil} alt='avatar' />
273
+                        </div>
274
+                        <div className='dashboard__memberlist__list__item__info mr-auto'>
275
+                          <div className='dashboard__memberlist__list__item__info__name'>
276
+                            Jean Dupont
277
+                          </div>
278
+                          <div className='dashboard__memberlist__list__item__info__role'>
279
+                            Responsable
280
+                          </div>
281
+                        </div>
282
+                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
283
+                          <i className='fa fa-trash-o' />
284
+                        </div>
285
+                      </li>
286
+
287
+                      <li className='dashboard__memberlist__list__item'>
288
+                        <div className='dashboard__memberlist__list__item__avatar'>
289
+                          <img src={imgProfil} alt='avatar' />
290
+                        </div>
291
+                        <div className='dashboard__memberlist__list__item__info mr-auto'>
292
+                          <div className='dashboard__memberlist__list__item__info__name'>
293
+                            Aldwin Vinel
294
+                          </div>
295
+                          <div className='dashboard__memberlist__list__item__info__role'>
296
+                            Lecteur
297
+                          </div>
298
+                        </div>
299
+                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
300
+                          <i className='fa fa-trash-o' />
301
+                        </div>
302
+                      </li>
303
+
304
+                      <li className='dashboard__memberlist__list__item'>
305
+                        <div className='dashboard__memberlist__list__item__avatar'>
306
+                          <img src={imgProfil} alt='avatar' />
307
+                        </div>
308
+                        <div className='dashboard__memberlist__list__item__info mr-auto'>
309
+                          <div className='dashboard__memberlist__list__item__info__name'>
310
+                            William Himme
311
+                          </div>
312
+                          <div className='dashboard__memberlist__list__item__info__role'>
313
+                            Contributeur
314
+                          </div>
315
+                        </div>
316
+                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
317
+                          <i className='fa fa-trash-o' />
318
+                        </div>
319
+                      </li>
320
+
321
+                      <li className='dashboard__memberlist__list__item'>
322
+                        <div className='dashboard__memberlist__list__item__avatar'>
323
+                          <img src={imgProfil} alt='avatar' />
324
+                        </div>
325
+                        <div className='dashboard__memberlist__list__item__info mr-auto'>
326
+                          <div className='dashboard__memberlist__list__item__info__name'>
327
+                            Yacine Lite
328
+                          </div>
329
+                          <div className='dashboard__memberlist__list__item__info__role'>
330
+                            Contributeur
331
+                          </div>
332
+                        </div>
333
+                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
334
+                          <i className='fa fa-trash-o' />
335
+                        </div>
336
+                      </li>
337
+
338
+                      <li className='dashboard__memberlist__list__item'>
339
+                        <div className='dashboard__memberlist__list__item__avatar'>
340
+                          <img src={imgProfil} alt='avatar' />
341
+                        </div>
342
+                        <div className='dashboard__memberlist__list__item__info mr-auto'>
343
+                          <div className='dashboard__memberlist__list__item__info__name'>
344
+                            Alexi Falcin
345
+                          </div>
346
+                          <div className='dashboard__memberlist__list__item__info__role'>
347
+                            Gestionnaire
348
+                          </div>
349
+                        </div>
350
+                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
351
+                          <i className='fa fa-trash-o' />
352
+                        </div>
353
+                      </li>
354
+
355
+                      <li className='dashboard__memberlist__list__item'>
356
+                        <div className='dashboard__memberlist__list__item__avatar'>
357
+                          <img src={imgProfil} alt='avatar' />
358
+                        </div>
359
+                        <div className='dashboard__memberlist__list__item__info mr-auto'>
360
+                          <div className='dashboard__memberlist__list__item__info__name'>
361
+                            Mickaël Fonati
362
+                          </div>
363
+                          <div className='dashboard__memberlist__list__item__info__role'>
364
+                            Gestionnaire
365
+                          </div>
366
+                        </div>
367
+                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
368
+                          <i className='fa fa-trash-o' />
369
+                        </div>
370
+                      </li>
371
+
372
+                      <li className='dashboard__memberlist__list__item'>
373
+                        <div className='dashboard__memberlist__list__item__avatar'>
374
+                          <img src={imgProfil} alt='avatar' />
375
+                        </div>
376
+                        <div className='dashboard__memberlist__list__item__info mr-auto'>
377
+                          <div className='dashboard__memberlist__list__item__info__name'>
378
+                            Eva Lonbard
379
+                          </div>
380
+                          <div className='dashboard__memberlist__list__item__info__role'>
381
+                            Gestionnaire
382
+                          </div>
383
+                        </div>
384
+                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
385
+                          <i className='fa fa-trash-o' />
386
+                        </div>
387
+                      </li>
388
+                    </ul>
389
+
390
+                    <div
391
+                      className='dashboard__memberlist__btnadd'
392
+                      onClick={this.handleToggleNewMemberDashboard}
393
+                    >
394
+                      <div className='dashboard__memberlist__btnadd__button'>
395
+                        <div className='dashboard__memberlist__btnadd__button__avatar'>
396
+                          <div className='dashboard__memberlist__btnadd__button__avatar__icon'>
397
+                            <i className='fa fa-plus' />
398
+                          </div>
399
+                        </div>
400
+                        <div
401
+                          className='dashboard__memberlist__btnadd__button__text'
402
+                        >
403
+                          {this.props.t('Add a member')}
404
+                        </div>
405
+                      </div>
406
+                    </div>
407
+                  </div>
408
+                  }
409
+
410
+                  {this.state.displayNewMemberDashboard === true &&
411
+                  <form className='dashboard__memberlist__form'>
412
+                    <div
413
+                      className='dashboard__memberlist__form__close d-flex justify-content-end'
414
+                    >
415
+                      <i className='fa fa-times' onClick={this.handleToggleNewMemberDashboard} />
416
+                    </div>
417
+                    <div className='dashboard__memberlist__form__member'>
418
+                      <div className='dashboard__memberlist__form__member__name'>
419
+                        <label className='name__label' htmlFor='addmember'>
420
+                          {this.props.t('Enter the name or email of the member')}
421
+                        </label>
422
+                        <input type='text' id='addmember' className='name__input form-control' placeholder='Nom ou Email' />
423
+                      </div>
424
+                      <div className='dashboard__memberlist__form__member__create'>
425
+                        <div className='create__radiobtn mr-3'>
426
+                          <input type='radio' />
427
+                        </div>
428
+                        <div className='create__text'>
429
+                          {this.props.t('Create an account')}
430
+                        </div>
431
+                      </div>
432
+                    </div>
433
+                    <div className='dashboard__memberlist__form__role'>
434
+                      <div className='dashboard__memberlist__form__role__text'>
435
+                        {this.props.t('Choose the role of the member')}
436
+                      </div>
437
+                      <ul className='dashboard__memberlist__form__role__list'>
438
+                        <li className='dashboard__memberlist__form__role__list__item'>
439
+                          <div className='item__radiobtn mr-3'>
440
+                            <input type='radio' name='role' value='responsable' />
441
+                          </div>
442
+                          <div className='item__text'>
443
+                            <div className='item_text_icon mr-2'>
444
+                              <i className='fa fa-gavel' />
445
+                            </div>
446
+                            <div className='item__text__name'>
447
+                              {this.props.t('Supervisor')}
448
+                            </div>
449
+                          </div>
450
+                        </li>
451
+                        <li className='dashboard__memberlist__form__role__list__item'>
452
+                          <div className='item__radiobtn mr-3'>
453
+                            <input type='radio' name='role' value='gestionnaire' />
454
+                          </div>
455
+                          <div className='item__text'>
456
+                            <div className='item_text_icon mr-2'>
457
+                              <i className='fa fa-graduation-cap' />
458
+                            </div>
459
+                            <div className='item__text__name'>
460
+                              {this.props.t('Content Manager')}
461
+                            </div>
462
+                          </div>
463
+                        </li>
464
+                        <li className='dashboard__memberlist__form__role__list__item'>
465
+                          <div className='item__radiobtn mr-3'>
466
+                            <input type='radio' name='role' value='contributeur' />
467
+                          </div>
468
+                          <div className='item__text'>
469
+                            <div className='item_text_icon mr-2'>
470
+                              <i className='fa fa-pencil' />
471
+                            </div>
472
+                            <div className='item__text__name'>
473
+                              {this.props.t('Contributor')}
474
+                            </div>
475
+                          </div>
476
+                        </li>
477
+                        <li className='dashboard__memberlist__form__role__list__item'>
478
+                          <div className='item__radiobtn mr-3'>
479
+                            <input type='radio' name='role' value='lecteur' />
480
+                          </div>
481
+                          <div className='item__text'>
482
+                            <div className='item_text_icon mr-2'>
483
+                              <i className='fa fa-eye' />
484
+                            </div>
485
+                            <div className='item__text__name'>
486
+                              {this.props.t('Reader')}
487
+                            </div>
488
+                          </div>
489
+                        </li>
490
+                      </ul>
491
+                    </div>
492
+                    <div className='dashboard__memberlist__form__submitbtn'>
493
+                      <button className='btn btn-outline-primary'>
494
+                        {this.props.t('Validate')}
495
+                      </button>
496
+                    </div>
497
+                  </form>
498
+                  }
499
+                </div>
500
+              </div>
501
+            </div>
502
+
503
+            <div className='dashboard__moreinfo'>
504
+              <div className='dashboard__moreinfo__webdav genericBtnInfoDashboard'>
505
+                <div
506
+                  className='dashboard__moreinfo__webdav__btn genericBtnInfoDashboard__btn'
507
+                  onClick={this.handleToggleWebdavBtn}
508
+                >
509
+                  <div className='dashboard__moreinfo__webdav__btn__icon genericBtnInfoDashboard__btn__icon'>
510
+                    <i className='fa fa-windows' />
511
+                  </div>
512
+
513
+                  <div className='dashboard__moreinfo__webdav__btn__text genericBtnInfoDashboard__btn__text'>
514
+                    {this.props.t('Implement Tracim in your explorer')}
515
+                  </div>
516
+                </div>
517
+                {this.state.displayWebdavBtn === true &&
518
+                <div>
519
+                  <div className='dashboard__moreinfo__webdav__information genericBtnInfoDashboard__info'>
520
+                    <div className='dashboard__moreinfo__webdav__information__text genericBtnInfoDashboard__info__text'>
521
+                      {this.props.t('Find all your documents deposited online directly on your computer via the workstation, without going through the software.')}'
522
+                    </div>
523
+
524
+                    <div className='dashboard__moreinfo__webdav__information__link genericBtnInfoDashboard__info__link'>
525
+                      http://algoo.trac.im/webdav/
526
+                    </div>
527
+                  </div>
528
+                </div>
529
+                }
530
+              </div>
531
+              <div className='dashboard__moreinfo__calendar genericBtnInfoDashboard'>
532
+                <div className='dashboard__moreinfo__calendar__wrapperBtn'>
533
+                  <div
534
+                    className='dashboard__moreinfo__calendar__btn genericBtnInfoDashboard__btn'
535
+                    onClick={this.handleToggleCalendarBtn}
536
+                  >
537
+                    <div className='dashboard__moreinfo__calendar__btn__icon genericBtnInfoDashboard__btn__icon'>
538
+                      <i className='fa fa-calendar' />
539
+                    </div>
540
+
541
+                    <div className='dashboard__moreinfo__calendar__btn__text genericBtnInfoDashboard__btn__text'>
542
+                      {this.props.t('Workspace Calendar')}
543
+                    </div>
544
+                  </div>
545
+                </div>
546
+                <div className='dashboard__moreinfo__calendar__wrapperText'>
547
+                  {this.state.displayCalendarBtn === true &&
548
+                  <div>
549
+                    <div className='dashboard__moreinfo__calendar__information genericBtnInfoDashboard__info'>
550
+                      <div className='dashboard__moreinfo__calendar__information__text genericBtnInfoDashboard__info__text'>
551
+                        {this.props.t('Each workspace has its own calendar.')}
552
+                      </div>
553
+
554
+                      <div className='dashboard__moreinfo__calendar__information__link genericBtnInfoDashboard__info__link'>
555
+                        http://algoo.trac.im/calendar/
556
+                      </div>
557
+                    </div>
558
+                  </div>
559
+                  }
560
+                </div>
561
+              </div>
562
+            </div>
563
+          </div>
564
+        </div>
565
+      </div>
566
+    )
567
+  }
568
+}
569
+
570
+const mapStateToProps = ({ user, app, contentType, workspaceList }) => ({ user, app, contentType, workspaceList })
571
+export default connect(mapStateToProps)(translate()(Dashboard))

+ 13 - 8
frontend/src/container/Header.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
 import { connect } from 'react-redux'
2
 import { connect } from 'react-redux'
3
+import { withRouter } from 'react-router'
3
 import i18n from '../i18n.js'
4
 import i18n from '../i18n.js'
5
+import appFactory from '../appFactory.js'
4
 import { translate } from 'react-i18next'
6
 import { translate } from 'react-i18next'
5
 import Cookies from 'js-cookie'
7
 import Cookies from 'js-cookie'
6
 import Logo from '../component/Header/Logo.jsx'
8
 import Logo from '../component/Header/Logo.jsx'
14
 import logoHeader from '../img/logo-tracim.png'
16
 import logoHeader from '../img/logo-tracim.png'
15
 import {
17
 import {
16
   newFlashMessage,
18
   newFlashMessage,
17
-  setLangActive,
19
+  setUserLang,
18
   setUserDisconnected
20
   setUserDisconnected
19
 } from '../action-creator.sync.js'
21
 } from '../action-creator.sync.js'
20
 import {
22
 import {
21
   postUserLogout
23
   postUserLogout
22
 } from '../action-creator.async.js'
24
 } from '../action-creator.async.js'
23
-import { COOKIE } from '../helper.js'
25
+import { COOKIE, PAGE } from '../helper.js'
24
 
26
 
25
 class Header extends React.Component {
27
 class Header extends React.Component {
26
   handleClickLogo = () => {}
28
   handleClickLogo = () => {}
32
   handleChangeInput = e => this.setState({inputSearchValue: e.target.value})
34
   handleChangeInput = e => this.setState({inputSearchValue: e.target.value})
33
   handleClickSubmit = () => {}
35
   handleClickSubmit = () => {}
34
 
36
 
35
-  handleChangeLang = langId => {
36
-    this.props.dispatch(setLangActive(langId))
37
-    i18n.changeLanguage(langId)
37
+  handleChangeLang = idLang => {
38
+    this.props.dispatch(setUserLang(idLang))
39
+    i18n.changeLanguage(idLang)
40
+    this.props.dispatchCustomEvent('allApp_changeLang', idLang)
38
   }
41
   }
39
 
42
 
40
   handleClickHelp = () => {}
43
   handleClickHelp = () => {}
41
 
44
 
42
   handleClickLogout = async () => {
45
   handleClickLogout = async () => {
43
-    const { dispatch, t } = this.props
46
+    const { history, dispatch, t } = this.props
44
 
47
 
45
     const fetchPostUserLogout = await dispatch(postUserLogout())
48
     const fetchPostUserLogout = await dispatch(postUserLogout())
46
     if (fetchPostUserLogout.status === 204) {
49
     if (fetchPostUserLogout.status === 204) {
48
       Cookies.remove(COOKIE.USER_AUTH)
51
       Cookies.remove(COOKIE.USER_AUTH)
49
 
52
 
50
       dispatch(setUserDisconnected())
53
       dispatch(setUserDisconnected())
54
+      history.push(PAGE.LOGIN)
51
     } else {
55
     } else {
52
-      dispatch(newFlashMessage(t('Login.logout_error', 'danger')))
56
+      dispatch(newFlashMessage(t('Disconnection error', 'danger')))
53
     }
57
     }
54
   }
58
   }
55
 
59
 
82
 
86
 
83
               <MenuActionListItemDropdownLang
87
               <MenuActionListItemDropdownLang
84
                 langList={lang}
88
                 langList={lang}
89
+                idLangActive={user.lang}
85
                 onChangeLang={this.handleChangeLang}
90
                 onChangeLang={this.handleChangeLang}
86
               />
91
               />
87
 
92
 
104
 }
109
 }
105
 
110
 
106
 const mapStateToProps = ({ lang, user }) => ({ lang, user })
111
 const mapStateToProps = ({ lang, user }) => ({ lang, user })
107
-export default connect(mapStateToProps)(translate()(Header))
112
+export default withRouter(connect(mapStateToProps)(translate()(appFactory(Header))))

+ 3 - 3
frontend/src/container/Login.jsx View File

57
 
57
 
58
       history.push(PAGE.HOME)
58
       history.push(PAGE.HOME)
59
     } else if (fetchPostUserLogin.status === 400) {
59
     } else if (fetchPostUserLogin.status === 400) {
60
-      dispatch(newFlashMessage(t('Login.fail'), 'danger'))
60
+      dispatch(newFlashMessage(t('Email or password invalid'), 'danger'))
61
     }
61
     }
62
   }
62
   }
63
 
63
 
83
                         customClass='mb-3 mt-4'
83
                         customClass='mb-3 mt-4'
84
                         icon='fa-envelope-open-o'
84
                         icon='fa-envelope-open-o'
85
                         type='email'
85
                         type='email'
86
-                        placeHolder='Adresse Email'
86
+                        placeHolder={this.props.t('Email Adress')}
87
                         invalidMsg='Email invalide.'
87
                         invalidMsg='Email invalide.'
88
                         isInvalid={this.state.inputLogin.isInvalid}
88
                         isInvalid={this.state.inputLogin.isInvalid}
89
                         value={this.state.inputLogin.value}
89
                         value={this.state.inputLogin.value}
95
                         customClass=''
95
                         customClass=''
96
                         icon='fa-lock'
96
                         icon='fa-lock'
97
                         type='password'
97
                         type='password'
98
-                        placeHolder='Mot de passe'
98
+                        placeHolder={this.props.t('Password')}
99
                         invalidMsg='Mot de passe invalide.'
99
                         invalidMsg='Mot de passe invalide.'
100
                         isInvalid={this.state.inputPassword.isInvalid}
100
                         isInvalid={this.state.inputPassword.isInvalid}
101
                         value={this.state.inputPassword.value}
101
                         value={this.state.inputPassword.value}

+ 47 - 9
frontend/src/container/Sidebar.jsx View File

3
 import { withRouter } from 'react-router'
3
 import { withRouter } from 'react-router'
4
 import classnames from 'classnames'
4
 import classnames from 'classnames'
5
 import { translate } from 'react-i18next'
5
 import { translate } from 'react-i18next'
6
+import appFactory from '../appFactory.js'
6
 import WorkspaceListItem from '../component/Sidebar/WorkspaceListItem.jsx'
7
 import WorkspaceListItem from '../component/Sidebar/WorkspaceListItem.jsx'
7
 import {
8
 import {
8
   setWorkspaceListIsOpenInSidebar,
9
   setWorkspaceListIsOpenInSidebar,
9
-  updateWorkspaceFilter
10
+  updateWorkspaceFilter,
11
+  updateWorkspaceListData
10
 } from '../action-creator.sync.js'
12
 } from '../action-creator.sync.js'
11
-import { PAGE } from '../helper.js'
13
+import {
14
+  getWorkspaceList
15
+} from '../action-creator.async.js'
16
+import { PAGE, workspaceConfig } from '../helper.js'
12
 
17
 
13
 const qs = require('query-string')
18
 const qs = require('query-string')
14
 
19
 
19
       sidebarClose: false,
24
       sidebarClose: false,
20
       workspaceIdInUrl: props.match.params.idws ? parseInt(props.match.params.idws) : null
25
       workspaceIdInUrl: props.match.params.idws ? parseInt(props.match.params.idws) : null
21
     }
26
     }
27
+
28
+    document.addEventListener('appCustomEvent', this.customEventReducer)
29
+  }
30
+
31
+  customEventReducer = async ({ detail: { type, data } }) => {
32
+    switch (type) {
33
+      case 'refreshWorkspaceList':
34
+        console.log('%c<Sidebar> Custom event', 'color: #28a745', type, data)
35
+        this.loadWorkspaceList()
36
+        break
37
+    }
38
+  }
39
+
40
+  componentDidMount () {
41
+    this.loadWorkspaceList()
22
   }
42
   }
23
 
43
 
24
   componentDidUpdate (prevProps, prevState) {
44
   componentDidUpdate (prevProps, prevState) {
29
     if (prevState.workspaceIdInUrl !== newWorkspaceId) this.setState({workspaceIdInUrl: newWorkspaceId})
49
     if (prevState.workspaceIdInUrl !== newWorkspaceId) this.setState({workspaceIdInUrl: newWorkspaceId})
30
   }
50
   }
31
 
51
 
52
+  loadWorkspaceList = async () => {
53
+    const { workspaceIdInUrl } = this.state
54
+    const { user, dispatch } = this.props
55
+
56
+    if (user.user_id !== -1) {
57
+      const fetchGetWorkspaceList = await dispatch(getWorkspaceList(user))
58
+
59
+      if (fetchGetWorkspaceList.status === 200) {
60
+        dispatch(updateWorkspaceListData(fetchGetWorkspaceList.json))
61
+        dispatch(setWorkspaceListIsOpenInSidebar(workspaceIdInUrl || fetchGetWorkspaceList.json[0].workspace_id, true))
62
+      }
63
+    }
64
+  }
65
+
32
   handleClickWorkspace = (idWs, newIsOpenInSidebar) => this.props.dispatch(setWorkspaceListIsOpenInSidebar(idWs, newIsOpenInSidebar))
66
   handleClickWorkspace = (idWs, newIsOpenInSidebar) => this.props.dispatch(setWorkspaceListIsOpenInSidebar(idWs, newIsOpenInSidebar))
33
 
67
 
34
   handleClickAllContent = idWs => {
68
   handleClickAllContent = idWs => {
45
 
79
 
46
     history.push(`${PAGE.WORKSPACE.CONTENT_LIST(idWs)}?type=${newFilter.join(';')}`) // workspace.filter gets updated on react redraw from match.params
80
     history.push(`${PAGE.WORKSPACE.CONTENT_LIST(idWs)}?type=${newFilter.join(';')}`) // workspace.filter gets updated on react redraw from match.params
47
 
81
 
48
-    // obviously, it's ugly to use custom event to tell WorkspaceContent to refresh, but since WorkspaceContent
82
+    // obviously, it's ugly to use custom event to tell WorkspaceContentList to refresh, but since WorkspaceContentList
49
     // will end up being an App, it'll have to be that way. So it's fine
83
     // will end up being an App, it'll have to be that way. So it's fine
50
     GLOBAL_dispatchEvent({ type: 'refreshContentList', data: {} })
84
     GLOBAL_dispatchEvent({ type: 'refreshContentList', data: {} })
51
   }
85
   }
52
 
86
 
53
   handleClickToggleSidebar = () => this.setState(prev => ({sidebarClose: !prev.sidebarClose}))
87
   handleClickToggleSidebar = () => this.setState(prev => ({sidebarClose: !prev.sidebarClose}))
54
 
88
 
89
+  handleClickNewWorkspace = () => this.props.renderAppPopupCreation(workspaceConfig, this.props.user, null, null)
90
+
55
   render () {
91
   render () {
56
     const { sidebarClose, workspaceIdInUrl } = this.state
92
     const { sidebarClose, workspaceIdInUrl } = this.state
57
     const { activeLang, workspaceList, t } = this.props
93
     const { activeLang, workspaceList, t } = this.props
85
             </nav>
121
             </nav>
86
 
122
 
87
             <div className='sidebar__btnnewworkspace'>
123
             <div className='sidebar__btnnewworkspace'>
88
-              <button className='sidebar__btnnewworkspace__btn btn btn-primary primaryColorBg primaryColorBorder primaryColorBorderDarkenHover mb-5'>
89
-                {t('Sidebar.create_new_workspace')}
124
+              <button
125
+                className='sidebar__btnnewworkspace__btn btn btn-primary primaryColorBg primaryColorBorder primaryColorBorderDarkenHover mb-5'
126
+                onClick={this.handleClickNewWorkspace}
127
+              >
128
+                {t('Create a workspace')}
90
               </button>
129
               </button>
91
             </div>
130
             </div>
92
 
131
 
106
   }
145
   }
107
 }
146
 }
108
 
147
 
109
-const mapStateToProps = ({ lang, user, workspace, workspaceList, app }) => ({
148
+const mapStateToProps = ({ lang, user, workspace, workspaceList }) => ({
110
   activeLang: lang.find(l => l.active) || {id: 'en'},
149
   activeLang: lang.find(l => l.active) || {id: 'en'},
111
   user,
150
   user,
112
   workspace,
151
   workspace,
113
-  workspaceList,
114
-  app
152
+  workspaceList
115
 })
153
 })
116
-export default withRouter(connect(mapStateToProps)(translate()(Sidebar)))
154
+export default withRouter(connect(mapStateToProps)(appFactory(translate()(Sidebar))))

+ 38 - 5
frontend/src/container/Tracim.jsx View File

5
 import Login from './Login.jsx'
5
 import Login from './Login.jsx'
6
 import Dashboard from './Dashboard.jsx'
6
 import Dashboard from './Dashboard.jsx'
7
 import Account from './Account.jsx'
7
 import Account from './Account.jsx'
8
+import AppFullscreenManager from './AppFullscreenManager.jsx'
8
 import FlashMessage from '../component/FlashMessage.jsx'
9
 import FlashMessage from '../component/FlashMessage.jsx'
9
 import WorkspaceContent from './WorkspaceContent.jsx'
10
 import WorkspaceContent from './WorkspaceContent.jsx'
10
 import WIPcomponent from './WIPcomponent.jsx'
11
 import WIPcomponent from './WIPcomponent.jsx'
14
 import PrivateRoute from './PrivateRoute.jsx'
15
 import PrivateRoute from './PrivateRoute.jsx'
15
 import { COOKIE, PAGE } from '../helper.js'
16
 import { COOKIE, PAGE } from '../helper.js'
16
 import {
17
 import {
17
-  getUserIsConnected
18
+  getAppList,
19
+  getUserIsConnected,
20
+  getContentTypeList
18
 } from '../action-creator.async.js'
21
 } from '../action-creator.async.js'
19
 import {
22
 import {
20
   removeFlashMessage,
23
   removeFlashMessage,
21
-  setUserConnected
24
+  setAppList,
25
+  setUserConnected,
26
+  setContentTypeList
22
 } from '../action-creator.sync.js'
27
 } from '../action-creator.sync.js'
23
 import Cookies from 'js-cookie'
28
 import Cookies from 'js-cookie'
24
 
29
 
25
 class Tracim extends React.Component {
30
 class Tracim extends React.Component {
31
+  constructor (props) {
32
+    super(props)
33
+
34
+    document.addEventListener('appCustomEvent', this.customEventReducer)
35
+  }
36
+
37
+  customEventReducer = async ({ detail: { type, data } }) => {
38
+    switch (type) {
39
+      case 'redirect':
40
+        console.log('%c<Tracim> Custom event', 'color: #28a745', type, data)
41
+        this.props.history.push(data.url)
42
+        break
43
+    }
44
+  }
45
+
26
   async componentDidMount () {
46
   async componentDidMount () {
27
     const { dispatch } = this.props
47
     const { dispatch } = this.props
28
 
48
 
34
     const fetchGetUserIsConnected = await dispatch(getUserIsConnected(userFromCookies))
54
     const fetchGetUserIsConnected = await dispatch(getUserIsConnected(userFromCookies))
35
     switch (fetchGetUserIsConnected.status) {
55
     switch (fetchGetUserIsConnected.status) {
36
       case 200:
56
       case 200:
37
-        dispatch(setUserConnected({
57
+        const userLogged = {
38
           ...fetchGetUserIsConnected.json,
58
           ...fetchGetUserIsConnected.json,
39
           auth: userFromCookies.auth,
59
           auth: userFromCookies.auth,
40
           logged: true
60
           logged: true
41
-        }))
61
+        }
62
+
63
+        dispatch(setUserConnected(userLogged))
64
+
65
+        const fetchGetAppList = await dispatch(getAppList(userLogged))
66
+        if (fetchGetAppList.status === 200) dispatch(setAppList(fetchGetAppList.json))
67
+
68
+        const fetchGetContentTypeList = await dispatch(getContentTypeList(userLogged))
69
+        if (fetchGetContentTypeList.status === 200) dispatch(setContentTypeList(fetchGetContentTypeList.json))
42
         break
70
         break
71
+
43
       case 401:
72
       case 401:
44
         dispatch(setUserConnected({logged: false})); break
73
         dispatch(setUserConnected({logged: false})); break
74
+
45
       default:
75
       default:
46
         dispatch(setUserConnected({logged: null})); break
76
         dispatch(setUserConnected({logged: null})); break
47
     }
77
     }
69
           </Switch>
99
           </Switch>
70
 
100
 
71
           <PrivateRoute path={PAGE.ACCOUNT} component={Account} />
101
           <PrivateRoute path={PAGE.ACCOUNT} component={Account} />
102
+          <PrivateRoute path={PAGE.ADMIN.ROOT} component={AppFullscreenManager} />
72
           <PrivateRoute path={'/wip/:cp'} component={WIPcomponent} /> {/* for testing purpose only */}
103
           <PrivateRoute path={'/wip/:cp'} component={WIPcomponent} /> {/* for testing purpose only */}
104
+
105
+          <div id='appFeatureContainer' />
73
         </div>
106
         </div>
74
 
107
 
75
       </div>
108
       </div>
77
   }
110
   }
78
 }
111
 }
79
 
112
 
80
-const mapStateToProps = ({ flashMessage, user }) => ({ flashMessage, user })
113
+const mapStateToProps = ({ flashMessage }) => ({ flashMessage })
81
 export default withRouter(connect(mapStateToProps)(translate()(Tracim)))
114
 export default withRouter(connect(mapStateToProps)(translate()(Tracim)))

+ 24 - 58
frontend/src/container/WorkspaceContent.jsx View File

7
 import Folder from '../component/Workspace/Folder.jsx'
7
 import Folder from '../component/Workspace/Folder.jsx'
8
 import ContentItem from '../component/Workspace/ContentItem.jsx'
8
 import ContentItem from '../component/Workspace/ContentItem.jsx'
9
 import ContentItemHeader from '../component/Workspace/ContentItemHeader.jsx'
9
 import ContentItemHeader from '../component/Workspace/ContentItemHeader.jsx'
10
-import PageWrapper from '../component/common/layout/PageWrapper.jsx'
11
-import PageTitle from '../component/common/layout/PageTitle.jsx'
12
-import PageContent from '../component/common/layout/PageContent.jsx'
13
 import DropdownCreateButton from '../component/common/Input/DropdownCreateButton.jsx'
10
 import DropdownCreateButton from '../component/common/Input/DropdownCreateButton.jsx'
14
 import OpenContentApp from '../component/Workspace/OpenContentApp.jsx'
11
 import OpenContentApp from '../component/Workspace/OpenContentApp.jsx'
15
 import OpenCreateContentApp from '../component/Workspace/OpenCreateContentApp.jsx'
12
 import OpenCreateContentApp from '../component/Workspace/OpenCreateContentApp.jsx'
16
 import {
13
 import {
17
-  getAppList,
18
-  getContentTypeList,
14
+  PageWrapper,
15
+  PageTitle,
16
+  PageContent
17
+} from 'tracim_frontend_lib'
18
+import {
19
   getWorkspaceContentList,
19
   getWorkspaceContentList,
20
-  getFolderContent,
21
-  getWorkspaceList
20
+  getFolderContent
22
 } from '../action-creator.async.js'
21
 } from '../action-creator.async.js'
23
 import {
22
 import {
24
   newFlashMessage,
23
   newFlashMessage,
25
-  setAppList,
26
-  setContentTypeList,
27
-  setWorkspaceContent,
28
-  setWorkspaceListIsOpenInSidebar,
29
-  updateWorkspaceListData
24
+  setWorkspaceContentList
30
 } from '../action-creator.sync.js'
25
 } from '../action-creator.sync.js'
31
 
26
 
32
 const qs = require('query-string')
27
 const qs = require('query-string')
35
   constructor (props) {
30
   constructor (props) {
36
     super(props)
31
     super(props)
37
     this.state = {
32
     this.state = {
38
-      popupCreateContent: {
39
-        display: false,
40
-        type: undefined,
41
-        folder: undefined
42
-      },
43
       workspaceIdInUrl: props.match.params.idws ? parseInt(props.match.params.idws) : null, // this is used to avoid handling the parseInt every time
33
       workspaceIdInUrl: props.match.params.idws ? parseInt(props.match.params.idws) : null, // this is used to avoid handling the parseInt every time
44
       appOpenedType: false
34
       appOpenedType: false
45
     }
35
     }
69
   }
59
   }
70
 
60
 
71
   async componentDidMount () {
61
   async componentDidMount () {
72
-    const { workspaceIdInUrl } = this.state
73
-    const { user, workspaceList, app, contentType, match, dispatch } = this.props
62
+    const { workspaceList, match } = this.props
74
 
63
 
75
     console.log('%c<WorkspaceContent> componentDidMount', 'color: #c17838')
64
     console.log('%c<WorkspaceContent> componentDidMount', 'color: #c17838')
76
 
65
 
77
-    if (app.length === 0) { // @fixme shouldn't this be done by <Sidebar> ?
78
-      const fetchGetAppList = await dispatch(getAppList(user))
79
-      if (fetchGetAppList.status === 200) dispatch(setAppList(fetchGetAppList.json))
80
-    }
81
-
82
-    if (contentType.length === 0) {
83
-      const fetchGetContentTypeList = await dispatch(getContentTypeList(user))
84
-      if (fetchGetContentTypeList.status === 200) dispatch(setContentTypeList(fetchGetContentTypeList.json))
85
-    }
86
-
87
     let wsToLoad = null
66
     let wsToLoad = null
88
-    if (match.params.idws !== undefined) wsToLoad = match.params.idws
89
-
90
-    if (user.user_id !== -1 && workspaceList.length === 0) {
91
-      const fetchGetWorkspaceList = await dispatch(getWorkspaceList(user))
92
 
67
 
93
-      if (fetchGetWorkspaceList.status === 200) {
94
-        dispatch(updateWorkspaceListData(fetchGetWorkspaceList.json))
95
-        dispatch(setWorkspaceListIsOpenInSidebar(workspaceIdInUrl || fetchGetWorkspaceList.json[0].workspace_id, true))
96
-
97
-        if (match.params.idws === undefined && fetchGetWorkspaceList.json.length > 0) {
98
-          wsToLoad = fetchGetWorkspaceList.json[0].workspace_id // load first ws if none specified
99
-        }
100
-      }
101
-    }
102
-
103
-    if (wsToLoad === null) return // ws already loaded
68
+    if (match.params.idws === undefined) {
69
+      if (workspaceList.length > 0) wsToLoad = workspaceList[0].id
70
+      else return
71
+    } else wsToLoad = match.params.idws
104
 
72
 
105
     this.loadContentList(wsToLoad)
73
     this.loadContentList(wsToLoad)
106
   }
74
   }
111
     if (this.state.workspaceIdInUrl === null) return
79
     if (this.state.workspaceIdInUrl === null) return
112
 
80
 
113
     const idWorkspace = parseInt(this.props.match.params.idws)
81
     const idWorkspace = parseInt(this.props.match.params.idws)
114
-
115
     if (isNaN(idWorkspace)) return
82
     if (isNaN(idWorkspace)) return
116
 
83
 
117
     const prevFilter = qs.parse(prevProps.location.search).type
84
     const prevFilter = qs.parse(prevProps.location.search).type
126
   }
93
   }
127
 
94
 
128
   componentWillUnmount () {
95
   componentWillUnmount () {
129
-    this.props.emitEventApp('unmount_app')
96
+    this.props.dispatchCustomEvent('unmount_app')
130
     document.removeEventListener('appCustomEvent', this.customEventReducer)
97
     document.removeEventListener('appCustomEvent', this.customEventReducer)
131
   }
98
   }
132
 
99
 
135
 
102
 
136
     const wsContent = await dispatch(getWorkspaceContentList(user, idWorkspace, 0))
103
     const wsContent = await dispatch(getWorkspaceContentList(user, idWorkspace, 0))
137
 
104
 
138
-    if (wsContent.status === 200) dispatch(setWorkspaceContent(wsContent.json, qs.parse(location.search).type))
105
+    if (wsContent.status === 200) dispatch(setWorkspaceContentList(wsContent.json, qs.parse(location.search).type))
139
     else dispatch(newFlashMessage('Error while loading workspace', 'danger'))
106
     else dispatch(newFlashMessage('Error while loading workspace', 'danger'))
140
   }
107
   }
141
 
108
 
181
   handleUpdateAppOpenedType = openedAppType => this.setState({appOpenedType: openedAppType})
148
   handleUpdateAppOpenedType = openedAppType => this.setState({appOpenedType: openedAppType})
182
 
149
 
183
   render () {
150
   render () {
184
-    const { workspaceContent, contentType } = this.props
151
+    const { workspaceContentList, contentType } = this.props
185
 
152
 
186
     const filterWorkspaceContent = (contentList, filter) => {
153
     const filterWorkspaceContent = (contentList, filter) => {
187
       return filter.length === 0
154
       return filter.length === 0
194
 
161
 
195
     const urlFilter = qs.parse(this.props.location.search).type
162
     const urlFilter = qs.parse(this.props.location.search).type
196
 
163
 
197
-    const filteredWorkspaceContent = workspaceContent.length > 0
198
-      ? filterWorkspaceContent(workspaceContent, urlFilter ? [urlFilter] : [])
164
+    const filteredWorkspaceContentList = workspaceContentList.length > 0
165
+      ? filterWorkspaceContent(workspaceContentList, urlFilter ? [urlFilter] : [])
199
       : []
166
       : []
200
 
167
 
201
     return (
168
     return (
221
           <PageTitle
188
           <PageTitle
222
             parentClass='workspace__header'
189
             parentClass='workspace__header'
223
             customClass='justify-content-between'
190
             customClass='justify-content-between'
224
-            title={workspaceContent.label ? workspaceContent.label : ''}
191
+            title='Liste des Contenus'
192
+            subtitle={workspaceContentList.label ? workspaceContentList.label : ''}
225
           >
193
           >
226
             <DropdownCreateButton
194
             <DropdownCreateButton
227
               parentClass='workspace__header__btnaddworkspace'
195
               parentClass='workspace__header__btnaddworkspace'
237
             <div className='workspace__content__fileandfolder folder__content active'>
205
             <div className='workspace__content__fileandfolder folder__content active'>
238
               <ContentItemHeader />
206
               <ContentItemHeader />
239
 
207
 
240
-              { filteredWorkspaceContent.map((c, i) => c.type === 'folder'
208
+              { filteredWorkspaceContentList.map((c, i) => c.type === 'folder'
241
                 ? (
209
                 ? (
242
                   <Folder
210
                   <Folder
243
                     availableApp={contentType}
211
                     availableApp={contentType}
252
                     }}
220
                     }}
253
                     onClickFolder={this.handleClickFolder}
221
                     onClickFolder={this.handleClickFolder}
254
                     onClickCreateContent={this.handleClickCreateContent}
222
                     onClickCreateContent={this.handleClickCreateContent}
255
-                    isLast={i === filteredWorkspaceContent.length - 1}
223
+                    isLast={i === filteredWorkspaceContentList.length - 1}
256
                     key={c.id}
224
                     key={c.id}
257
                   />
225
                   />
258
                 )
226
                 )
262
                     type={c.type}
230
                     type={c.type}
263
                     faIcon={contentType.length ? contentType.find(a => a.slug === c.type).faIcon : ''}
231
                     faIcon={contentType.length ? contentType.find(a => a.slug === c.type).faIcon : ''}
264
                     statusSlug={c.statusSlug}
232
                     statusSlug={c.statusSlug}
265
-                    contentType={contentType.find(ct => ct.slug === c.type)}
233
+                    contentType={contentType.length ? contentType.find(ct => ct.slug === c.type) : null}
266
                     onClickItem={() => this.handleClickContentItem(c)}
234
                     onClickItem={() => this.handleClickContentItem(c)}
267
                     onClickExtendedAction={{
235
                     onClickExtendedAction={{
268
                       edit: e => this.handleClickEditContentItem(e, c),
236
                       edit: e => this.handleClickEditContentItem(e, c),
272
                       delete: e => this.handleClickDeleteContentItem(e, c)
240
                       delete: e => this.handleClickDeleteContentItem(e, c)
273
                     }}
241
                     }}
274
                     onClickCreateContent={this.handleClickCreateContent}
242
                     onClickCreateContent={this.handleClickCreateContent}
275
-                    isLast={i === filteredWorkspaceContent.length - 1}
243
+                    isLast={i === filteredWorkspaceContentList.length - 1}
276
                     key={c.id}
244
                     key={c.id}
277
                   />
245
                   />
278
                 )
246
                 )
285
               onClickCreateContent={this.handleClickCreateContent}
253
               onClickCreateContent={this.handleClickCreateContent}
286
               availableApp={contentType}
254
               availableApp={contentType}
287
             />
255
             />
288
-
289
-            <div id='appContainer' />
290
           </PageContent>
256
           </PageContent>
291
 
257
 
292
         </PageWrapper>
258
         </PageWrapper>
295
   }
261
   }
296
 }
262
 }
297
 
263
 
298
-const mapStateToProps = ({ user, workspaceContent, workspaceList, app, contentType }) => ({ user, workspaceContent, workspaceList, app, contentType })
264
+const mapStateToProps = ({ user, workspaceContentList, workspaceList, contentType }) => ({ user, workspaceContentList, workspaceList, contentType })
299
 export default withRouter(connect(mapStateToProps)(appFactory(WorkspaceContent)))
265
 export default withRouter(connect(mapStateToProps)(appFactory(WorkspaceContent)))

+ 8 - 22
frontend/src/css/Dashboard.styl View File

43
         margin 0
43
         margin 0
44
     &__advancedmode
44
     &__advancedmode
45
       cursor pointer
45
       cursor pointer
46
-  &__wkswrapper
46
+  &__workspace-wrapper
47
     flexwrap()
47
     flexwrap()
48
   &__workspace
48
   &__workspace
49
     margin-right 20px
49
     margin-right 20px
62
       flexwrap()
62
       flexwrap()
63
       margin 20px 0
63
       margin 20px 0
64
       font-size 18px
64
       font-size 18px
65
-      &__text
65
+      &__msg
66
         margin-right 15px
66
         margin-right 15px
67
-      &__rank
67
+      &__definition
68
         display flex
68
         display flex
69
         &__icon
69
         &__icon
70
           margin-right 15px
70
           margin-right 15px
88
       &:active
88
       &:active
89
         box-shadow inset 0px 0px 5px 2px #656565
89
         box-shadow inset 0px 0px 5px 2px #656565
90
       &__text
90
       &__text
91
+        color white
91
         &__icon
92
         &__icon
92
           font-size 30px
93
           font-size 30px
93
-          .fa-comments-o, .fa-paperclip, .fa-calendar, .fa-file-text-o, .fa-folder-open-o, .fa-video-camera
94
-              color white
95
         &__title
94
         &__title
96
           font-size 18px
95
           font-size 18px
97
-          color white
98
-    .btnthread
99
-      background-color threadColor
100
-    .writefile
101
-      background-color writefile
102
-    .importfile
103
-      background-color importfile
104
-    .visioconf
105
-      background-color grey-hover
106
-    .calendar
107
-      background-color calendar
108
-    .explore
109
-      background-color explore
110
   &__wksinfo
96
   &__wksinfo
111
     flexwrap()
97
     flexwrap()
112
     margin-top 150px
98
     margin-top 150px
270
     justify-content space-between
256
     justify-content space-between
271
     flexwrap wrap
257
     flexwrap wrap
272
     &__webdav
258
     &__webdav
273
-      margin-bottom 40px
259
+      margin 0 15px 40px 0
274
       &__btn
260
       &__btn
275
         width 300px
261
         width 300px
276
       &__information
262
       &__information
277
-        width 550px
263
+        width 300px
278
     &__calendar
264
     &__calendar
279
       margin-bottom 100px
265
       margin-bottom 100px
280
       &__wrapperBtn
266
       &__wrapperBtn
281
-        margin-right 300px
267
+        margin-right 290px
282
       &__btn
268
       &__btn
283
         width 300px
269
         width 300px
284
       &__information
270
       &__information
285
-        width 550px
271
+        width 300px
286
 
272
 
287
 /**** MEDIAQUERIES *****/
273
 /**** MEDIAQUERIES *****/
288
 
274
 

+ 6 - 2
frontend/src/css/Generic.styl View File

202
   display flex
202
   display flex
203
   flex-direction column
203
   flex-direction column
204
   justify-content center
204
   justify-content center
205
-  margin 20px 30px 20px 0
205
+  margin 0 15px
206
   border-radius 10px
206
   border-radius 10px
207
   padding 15px
207
   padding 15px
208
   width 230px
208
   width 230px
210
   box-shadow shadow-all
210
   box-shadow shadow-all
211
   text-align center
211
   text-align center
212
   cursor pointer
212
   cursor pointer
213
+  &:nth-child(1)
214
+    margin-left 0
215
+  &:nth-last-child
216
+    margin-right 0
213
 
217
 
214
 .genericBtnInfoDashboard
218
 .genericBtnInfoDashboard
215
   &__btn
219
   &__btn
237
       margin-bottom 15px
241
       margin-bottom 15px
238
       border 1px solid grey
242
       border 1px solid grey
239
       border-radius 10px
243
       border-radius 10px
240
-      padding 15px 25px
244
+      padding 15px

+ 0 - 1
frontend/src/css/Header.styl View File

40
         background-color transparent
40
         background-color transparent
41
         cursor pointer
41
         cursor pointer
42
       &__itemlanguage
42
       &__itemlanguage
43
-        display none
44
         &__languagedropdown
43
         &__languagedropdown
45
           .languagedropdown
44
           .languagedropdown
46
             &__btnlanguage
45
             &__btnlanguage

+ 3 - 3
frontend/src/css/Login.styl View File

65
         width 130px
65
         width 130px
66
   &__footer
66
   &__footer
67
     position fixed
67
     position fixed
68
-    top 95%
69
-    left calc(50% - 152px) // 152px => width of the text / 2
68
+    bottom 2%
69
+    left calc(50% - 155px) // 155px => width of the text / 2
70
     &__text
70
     &__text
71
-      width 305px
71
+      width 310px
72
       font-size 17px
72
       font-size 17px
73
 
73
 
74
 @media (min-width: min-lg) and (max-width: max-lg)
74
 @media (min-width: min-lg) and (max-width: max-lg)

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

8
     &__button
8
     &__button
9
       display flex
9
       display flex
10
       justify-content flex-end
10
       justify-content flex-end
11
-      margin 50px 15px 0 0
11
+      margin 45px 15px 45px 0
12
       width 100%
12
       width 100%
13
       &__btnaddworkspace
13
       &__btnaddworkspace
14
         margin-bottom 50px
14
         margin-bottom 50px

+ 17 - 1
frontend/src/helper.js View File

12
   USER_AUTH: 'user_auth'
12
   USER_AUTH: 'user_auth'
13
 }
13
 }
14
 
14
 
15
+// Côme - 2018/08/02 - shouldn't this come from api ?
16
+export const workspaceConfig = {
17
+  slug: 'workspace',
18
+  faIcon: 'space-shuttle',
19
+  hexcolor: '#7d4e24',
20
+  creationLabel: 'Create a workspace',
21
+  domContainer: 'appFeatureContainer'
22
+}
23
+
15
 export const PAGE = {
24
 export const PAGE = {
16
   HOME: '/',
25
   HOME: '/',
17
   WORKSPACE: {
26
   WORKSPACE: {
26
     ADMIN: (idws = ':idws') => `/workspaces/${idws}/admin`
35
     ADMIN: (idws = ':idws') => `/workspaces/${idws}/admin`
27
   },
36
   },
28
   LOGIN: '/login',
37
   LOGIN: '/login',
29
-  ACCOUNT: '/account'
38
+  ACCOUNT: '/account',
39
+  ADMIN: {
40
+    ROOT: '/admin',
41
+    WORKSPACE: '/admin/workspace',
42
+    USEr: '/admin/user'
43
+  }
30
 }
44
 }
31
 
45
 
32
 export const ROLE = [{
46
 export const ROLE = [{
50
   icon: 'fa-gavel',
64
   icon: 'fa-gavel',
51
   translationKey: 'role.manager'
65
   translationKey: 'role.manager'
52
 }]
66
 }]
67
+
68
+export const handleRouteFromApi = route => route.startsWith('/#') ? route.slice(2) : route

+ 19 - 9
frontend/src/i18n.js View File

1
 import i18n from 'i18next'
1
 import i18n from 'i18next'
2
 import { reactI18nextModule } from 'react-i18next'
2
 import { reactI18nextModule } from 'react-i18next'
3
-import { langFr, langEn } from 'tracim_frontend_lib'
4
-import fr from './translate/fr.js'
5
-import en from './translate/en.js'
3
+import { frLib, enLib } from 'tracim_frontend_lib'
4
+import en from '../i18next.scanner/en/translation.json'
5
+import fr from '../i18next.scanner/fr/translation.json'
6
+
7
+// get translation files of apps
8
+// theses files are generated by build_appname.sh
9
+const htmlDocEnTranslation = require('../dist/app/html-document_en_translation.json')
10
+const htmlDocFrTranslation = require('../dist/app/html-document_fr_translation.json')
6
 
11
 
7
 i18n
12
 i18n
8
   .use(reactI18nextModule)
13
   .use(reactI18nextModule)
9
   .init({
14
   .init({
10
-    fallbackLng: 'fr',
15
+    fallbackLng: 'en',
11
     // have a common namespace used around the full app
16
     // have a common namespace used around the full app
12
     ns: ['translation'], // namespace
17
     ns: ['translation'], // namespace
13
     defaultNS: 'translation',
18
     defaultNS: 'translation',
14
     debug: true,
19
     debug: true,
15
-    // interpolation: {
16
-    //   escapeValue: false, // not needed for react!!
17
-    // },
18
     react: {
20
     react: {
19
       wait: true
21
       wait: true
20
     },
22
     },
21
     resources: {
23
     resources: {
22
       en: {
24
       en: {
23
-        translation: {...langEn.translation, ...en.translation}
25
+        translation: {
26
+          ...enLib, // fronted_lib
27
+          ...en, // frontend
28
+          ...htmlDocEnTranslation // html-document
29
+        }
24
       },
30
       },
25
       fr: {
31
       fr: {
26
-        translation: {...langFr.translation, ...fr.translation}
32
+        translation: {
33
+          ...frLib, // fronted_lib
34
+          ...fr, // frontend
35
+          ...htmlDocFrTranslation // html-document
36
+        }
27
       }
37
       }
28
     }
38
     }
29
   })
39
   })

frontend/src/img/drapeau-en.png → frontend/src/img/flag_en.png View File


frontend/src/img/drapeau-fr.png → frontend/src/img/flag_fr.png View File


frontend/src/reducer/app.js → frontend/src/reducer/appList.js View File

1
-import { APP_LIST } from '../action-creator.sync.js'
1
+import { SET, APP_LIST } from '../action-creator.sync.js'
2
 
2
 
3
 export default function app (state = [], action) {
3
 export default function app (state = [], action) {
4
   switch (action.type) {
4
   switch (action.type) {
5
-    case `Set/${APP_LIST}`:
5
+    case `${SET}/${APP_LIST}`:
6
       return action.appList.map(a => ({
6
       return action.appList.map(a => ({
7
         label: a.label,
7
         label: a.label,
8
         slug: a.slug,
8
         slug: a.slug,

+ 2 - 2
frontend/src/reducer/contentType.js View File

1
-import { CONTENT_TYPE_LIST } from '../action-creator.sync.js'
1
+import { SET, CONTENT_TYPE_LIST } from '../action-creator.sync.js'
2
 
2
 
3
 export function contentType (state = [], action) {
3
 export function contentType (state = [], action) {
4
   switch (action.type) {
4
   switch (action.type) {
5
-    case `Set/${CONTENT_TYPE_LIST}`:
5
+    case `${SET}/${CONTENT_TYPE_LIST}`:
6
       return action.contentTypeList.map(ct => ({
6
       return action.contentTypeList.map(ct => ({
7
         label: ct.label,
7
         label: ct.label,
8
         slug: ct.slug,
8
         slug: ct.slug,

+ 46 - 0
frontend/src/reducer/currentWorkspace.js View File

1
+import {SET, WORKSPACE_DETAIL, WORKSPACE_MEMBER_LIST} from '../action-creator.sync.js'
2
+import { handleRouteFromApi } from '../helper.js'
3
+
4
+const defaultWorkspace = {
5
+  id: 0,
6
+  slug: '',
7
+  label: '',
8
+  description: '',
9
+  sidebarEntries: [],
10
+  member: []
11
+}
12
+
13
+export default function currentWorkspace (state = defaultWorkspace, action) {
14
+  switch (action.type) {
15
+    case `${SET}/${WORKSPACE_DETAIL}`:
16
+      return {
17
+        ...state,
18
+        id: action.workspaceDetail.workspace_id,
19
+        slug: action.workspaceDetail.slug,
20
+        label: action.workspaceDetail.label,
21
+        description: action.workspaceDetail.description,
22
+        sidebarEntries: action.workspaceDetail.sidebar_entries.map(sbe => ({
23
+          slug: sbe.slug,
24
+          route: handleRouteFromApi(sbe.route),
25
+          faIcon: sbe.fa_icon,
26
+          hexcolor: sbe.hexcolor,
27
+          label: sbe.label
28
+        }))
29
+      }
30
+
31
+    case `${SET}/${WORKSPACE_MEMBER_LIST}`:
32
+      return {
33
+        ...state,
34
+        member: action.workspaceMemberList.map(m => ({
35
+          id: m.user_id,
36
+          publicName: m.user.public_name,
37
+          avatarUrl: m.user.avatar_url,
38
+          role: m.role,
39
+          isActive: m.is_active,
40
+        }))
41
+      }
42
+
43
+    default:
44
+      return state
45
+  }
46
+}

+ 3 - 5
frontend/src/reducer/flashMessage.js View File

1
-import {
2
-  FLASH_MESSAGE
3
-} from '../action-creator.sync.js'
1
+import { ADD, REMOVE, FLASH_MESSAGE } from '../action-creator.sync.js'
4
 
2
 
5
 export default function flashMessage (state = [], action) {
3
 export default function flashMessage (state = [], action) {
6
   switch (action.type) {
4
   switch (action.type) {
7
-    case `Add/${FLASH_MESSAGE}`:
5
+    case `${ADD}/${FLASH_MESSAGE}`:
8
       return [...state, {
6
       return [...state, {
9
         message: action.msg.message,
7
         message: action.msg.message,
10
         type: action.msg.type || 'info' // may be info, success, danger
8
         type: action.msg.type || 'info' // may be info, success, danger
11
       }]
9
       }]
12
 
10
 
13
-    case `Remove/${FLASH_MESSAGE}`:
11
+    case `${REMOVE}/${FLASH_MESSAGE}`:
14
       return state.filter(fm => fm.message === action.message)
12
       return state.filter(fm => fm.message === action.message)
15
 
13
 
16
     default:
14
     default:

+ 16 - 5
frontend/src/reducer/lang.js View File

1
-import { LANG } from '../action-creator.sync.js'
1
+import { UPDATE, LANG } from '../action-creator.sync.js'
2
+import flagEn from '../img/flag_en.png'
3
+import flagFr from '../img/flag_fr.png'
2
 
4
 
3
-export function lang (state = [], action) {
5
+const defaultLang = [{
6
+  id: 'en',
7
+  icon: flagEn
8
+}, {
9
+  id: 'fr',
10
+  icon: flagFr
11
+}]
12
+
13
+export function lang (state = defaultLang, action) {
4
   switch (action.type) {
14
   switch (action.type) {
5
-    case `Update/${LANG}`:
15
+    case `${UPDATE}/${LANG}`:
6
       return action.langList
16
       return action.langList
7
 
17
 
8
-    case `Set/${LANG}/Active`:
9
-      return state.map(l => ({...l, active: l.id === action.langId}))
18
+    // Côme - 2018/07/30 - deprecated, lang active is saved in user reducer
19
+    // case `Set/${LANG}/Active`:
20
+    //   return state.map(l => ({...l, active: l.id === action.langId}))
10
 
21
 
11
     default:
22
     default:
12
       return state
23
       return state

+ 4 - 3
frontend/src/reducer/root.js View File

2
 import lang from './lang.js'
2
 import lang from './lang.js'
3
 import flashMessage from './flashMessage.js'
3
 import flashMessage from './flashMessage.js'
4
 import user from './user.js'
4
 import user from './user.js'
5
-import workspaceContent from './workspaceContent.js'
5
+import currentWorkspace from './currentWorkspace.js'
6
+import workspaceContentList from './workspaceContentList.js'
6
 import workspaceList from './workspaceList.js'
7
 import workspaceList from './workspaceList.js'
7
-import app from './app.js'
8
+import appList from './appList.js'
8
 import contentType from './contentType.js'
9
 import contentType from './contentType.js'
9
 import timezone from './timezone.js'
10
 import timezone from './timezone.js'
10
 
11
 
11
-const rootReducer = combineReducers({ lang, flashMessage, user, workspaceContent, workspaceList, app, contentType, timezone })
12
+const rootReducer = combineReducers({ lang, flashMessage, user, currentWorkspace, workspaceContentList, workspaceList, appList, contentType, timezone })
12
 
13
 
13
 export default rootReducer
14
 export default rootReducer

+ 2 - 2
frontend/src/reducer/timezone.js View File

1
-import { TIMEZONE } from '../action-creator.sync.js'
1
+import { SET, TIMEZONE } from '../action-creator.sync.js'
2
 
2
 
3
 export function timezone (state = [], action) {
3
 export function timezone (state = [], action) {
4
   switch (action.type) {
4
   switch (action.type) {
5
-    case `Set/${TIMEZONE}`:
5
+    case `${SET}/${TIMEZONE}`:
6
       return action.timezone
6
       return action.timezone
7
 
7
 
8
     default:
8
     default:

+ 14 - 5
frontend/src/reducer/user.js View File

1
 import {
1
 import {
2
+  SET,
3
+  UPDATE,
2
   USER_CONNECTED,
4
   USER_CONNECTED,
3
   USER_DISCONNECTED,
5
   USER_DISCONNECTED,
4
-  USER_DATA
6
+  USER_DATA,
7
+  USER_LANG
5
 } from '../action-creator.sync.js'
8
 } from '../action-creator.sync.js'
6
 
9
 
7
 const defaultUser = {
10
 const defaultUser = {
8
   user_id: -1,
11
   user_id: -1,
9
   logged: null, // null avoid to be redirected to /login while whoami ep has not responded yet
12
   logged: null, // null avoid to be redirected to /login while whoami ep has not responded yet
13
+  auth: '',
10
   timezone: '',
14
   timezone: '',
11
   profile: {
15
   profile: {
12
     id: 1,
16
     id: 1,
17
   caldav_url: null,
21
   caldav_url: null,
18
   avatar_url: null,
22
   avatar_url: null,
19
   created: '',
23
   created: '',
20
-  display_name: ''
24
+  public_name: '',
25
+  lang: 'en' // @FIXME Côme - 2018/07/30 - remove this line when api returns the lang (https://github.com/tracim/tracim/issues/734)
21
 }
26
 }
22
 
27
 
23
 export default function user (state = defaultUser, action) {
28
 export default function user (state = defaultUser, action) {
24
   switch (action.type) {
29
   switch (action.type) {
25
-    case `Set/${USER_CONNECTED}`:
30
+    case `${SET}/${USER_CONNECTED}`:
26
       return {
31
       return {
32
+        ...state,
27
         ...action.user,
33
         ...action.user,
28
         avatar_url: 'https://www.algoo.fr/static/images/people_images/PERSO_SEUL.png' // @FIXME use avatar from api when db handles it
34
         avatar_url: 'https://www.algoo.fr/static/images/people_images/PERSO_SEUL.png' // @FIXME use avatar from api when db handles it
29
       }
35
       }
30
 
36
 
31
-    case `Set/${USER_DISCONNECTED}`:
37
+    case `${SET}/${USER_DISCONNECTED}`:
32
       return defaultUser
38
       return defaultUser
33
 
39
 
34
-    case `Update/${USER_DATA}`:
40
+    case `${UPDATE}/${USER_DATA}`:
35
       return {...state, ...action.data}
41
       return {...state, ...action.data}
36
 
42
 
43
+    case `${SET}/${USER_LANG}`:
44
+      return {...state, lang: action.lang}
45
+
37
     default:
46
     default:
38
       return state
47
       return state
39
   }
48
   }

frontend/src/reducer/workspaceContent.js → frontend/src/reducer/workspaceContentList.js View File

1
 import {
1
 import {
2
+  SET,
3
+  UPDATE,
2
   WORKSPACE,
4
   WORKSPACE,
5
+  WORKSPACE_CONTENT,
3
   FOLDER
6
   FOLDER
4
 } from '../action-creator.sync.js'
7
 } from '../action-creator.sync.js'
5
 
8
 
6
-export default function workspace (state = [], action) {
9
+export default function workspaceContentList (state = [], action) {
7
   switch (action.type) {
10
   switch (action.type) {
8
-    case `Set/${WORKSPACE}/Content`:
9
-      return action.workspaceContent.map(wsc => ({
11
+    case `${SET}/${WORKSPACE_CONTENT}`:
12
+      return action.workspaceContentList.map(wsc => ({
10
         id: wsc.content_id,
13
         id: wsc.content_id,
11
         label: wsc.label,
14
         label: wsc.label,
12
         slug: wsc.slug,
15
         slug: wsc.slug,
20
         subContentTypeSlug: wsc.sub_content_type_slug
23
         subContentTypeSlug: wsc.sub_content_type_slug
21
       }))
24
       }))
22
 
25
 
23
-    case `Update/${WORKSPACE}/Filter`: // not used anymore ?
26
+    case `${UPDATE}/${WORKSPACE}/Filter`: // not used anymore ?
24
       return {...state, filter: action.filterList}
27
       return {...state, filter: action.filterList}
25
 
28
 
26
-    case `Set/${WORKSPACE}/${FOLDER}/Content`:
29
+    case `${SET}/${WORKSPACE}/${FOLDER}/Content`:
27
       const setFolderContent = (contentItem, action) => {
30
       const setFolderContent = (contentItem, action) => {
28
         if (contentItem.id === action.folderId) return {...contentItem, content: action.content}
31
         if (contentItem.id === action.folderId) return {...contentItem, content: action.content}
29
 
32
 

+ 8 - 7
frontend/src/reducer/workspaceList.js View File

1
 import {
1
 import {
2
+  SET,
3
+  UPDATE,
2
   WORKSPACE_LIST,
4
   WORKSPACE_LIST,
3
   USER_ROLE
5
   USER_ROLE
4
 } from '../action-creator.sync.js'
6
 } from '../action-creator.sync.js'
5
-
6
-const handleRouteFromApi = route => route.startsWith('/#') ? route.slice(2) : route
7
+import { handleRouteFromApi } from '../helper.js'
7
 
8
 
8
 export function workspaceList (state = [], action) {
9
 export function workspaceList (state = [], action) {
9
   switch (action.type) {
10
   switch (action.type) {
10
-    case `Update/${WORKSPACE_LIST}`:
11
+    case `${UPDATE}/${WORKSPACE_LIST}`:
11
       return action.workspaceList.map(ws => ({
12
       return action.workspaceList.map(ws => ({
12
         id: ws.workspace_id,
13
         id: ws.workspace_id,
13
         label: ws.label,
14
         label: ws.label,
14
         slug: ws.slug,
15
         slug: ws.slug,
15
-        description: ws.description,
16
+        // description: ws.description, // not returned by /api/v2/users/:idUser/workspaces
16
         sidebarEntry: ws.sidebar_entries.map(sbe => ({
17
         sidebarEntry: ws.sidebar_entries.map(sbe => ({
17
           slug: sbe.slug,
18
           slug: sbe.slug,
18
           route: handleRouteFromApi(sbe.route),
19
           route: handleRouteFromApi(sbe.route),
23
         isOpenInSidebar: false
24
         isOpenInSidebar: false
24
       }))
25
       }))
25
 
26
 
26
-    case `Set/${WORKSPACE_LIST}/isOpenInSidebar`:
27
+    case `${SET}/${WORKSPACE_LIST}/isOpenInSidebar`:
27
       return state.map(ws => ws.id === action.workspaceId
28
       return state.map(ws => ws.id === action.workspaceId
28
         ? {...ws, isOpenInSidebar: action.isOpenInSidebar}
29
         ? {...ws, isOpenInSidebar: action.isOpenInSidebar}
29
         : ws
30
         : ws
30
       )
31
       )
31
 
32
 
32
-    case `Set/${USER_ROLE}`: // not used yet
33
+    case `${SET}/${USER_ROLE}`: // not used yet
33
       return state.map(ws => {
34
       return state.map(ws => {
34
         const foundWorkspace = action.userRole.find(r => ws.id === r.workspace.id) || {role: '', subscribed_to_notif: ''}
35
         const foundWorkspace = action.userRole.find(r => ws.id === r.workspace.id) || {role: '', subscribed_to_notif: ''}
35
         return {
36
         return {
39
         }
40
         }
40
       })
41
       })
41
 
42
 
42
-    case `Update/${USER_ROLE}/SubscriptionNotif`: // not used yet
43
+    case `${UPDATE}/${USER_ROLE}/SubscriptionNotif`: // not used yet
43
       return state.map(ws => ws.id === action.workspaceId
44
       return state.map(ws => ws.id === action.workspaceId
44
         ? {...ws, notif: action.subscriptionNotif}
45
         ? {...ws, notif: action.subscriptionNotif}
45
         : ws
46
         : ws

+ 0 - 41
frontend/src/translate/en.js View File

1
-const en = {
2
-  translation: { // 'en' in the namespace 'translation'
3
-    Header: {
4
-      Search: 'Search'
5
-    },
6
-    Footer: {
7
-      marketing_msg: 'Create your own collaborative workspaces on trac.im',
8
-      copyright: 'Copyright 2013 - 2017'
9
-    },
10
-    FlashMessage: {
11
-      error: 'Error'
12
-    },
13
-    Login: {
14
-      fail: 'Unknown email or password',
15
-      logout_error: 'Disconnection error'
16
-    },
17
-    FileItemHeader: {
18
-      type: 'Type',
19
-      document_name: "Document's name",
20
-      status: 'Status'
21
-    },
22
-    Folder: {
23
-      create: 'Create in this folder',
24
-      content_type: 'Content type'
25
-    },
26
-    Sidebar: {
27
-      create_new_workspace: 'Create new workspace'
28
-    },
29
-    Account: {
30
-      title: 'My account'
31
-    },
32
-    role: {
33
-      reader: 'Reader',
34
-      contributor: 'Contributor',
35
-      content_manager: 'Content manager',
36
-      manager: 'Manager'
37
-    }
38
-  }
39
-}
40
-
41
-export default en

+ 0 - 41
frontend/src/translate/fr.js View File

1
-const fr = {
2
-  translation: { // 'fr' in the namespace 'translation'
3
-    Header: {
4
-      Search: 'Rechercher'
5
-    },
6
-    Footer: {
7
-      marketing_msg: 'Créer votre propre espace de travail collaboratif sur trac.im',
8
-      copyright: 'Copyright 2013 - 2017'
9
-    },
10
-    FlashMessage: {
11
-      error: 'Erreur'
12
-    },
13
-    Login: {
14
-      fail: 'Email ou mot de passe inconnu.',
15
-      logout_error: 'Erreur de déconnexion.'
16
-    },
17
-    FileItemHeader: {
18
-      type: 'Type',
19
-      document_name: 'Nom du document',
20
-      status: 'Statut'
21
-    },
22
-    Folder: {
23
-      create: 'Créer dans ce dossier',
24
-      content_type: 'Type de contenu'
25
-    },
26
-    Sidebar: {
27
-      create_new_workspace: 'Créer un workspace'
28
-    },
29
-    Account: {
30
-      title: 'Mon compte'
31
-    },
32
-    role: {
33
-      reader: 'Lecteur',
34
-      contributor: 'Contributeur',
35
-      content_manager: 'Gestionnaire de contenu',
36
-      manager: 'Responsable'
37
-    }
38
-  }
39
-}
40
-
41
-export default fr

+ 13 - 0
frontend_app_admin_workspace_user/.editorconfig View File

1
+# doc here : https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties
2
+root = true
3
+
4
+[*]
5
+indent_style = space
6
+indent_size = 2
7
+end_of_line = lf
8
+charset = utf-8
9
+insert_final_newline = true
10
+trim_trailing_whitespace = true
11
+
12
+[*.py]
13
+indent_size = 4

+ 5 - 0
frontend_app_admin_workspace_user/.gitignore View File

1
+# Created by .ignore support plugin (hsz.mobi)
2
+.idea/
3
+.git/
4
+dist/admin_workspace_user.app.js
5
+node_modules/

+ 1 - 0
frontend_app_admin_workspace_user/README.md View File

1
+# app Admin Workspace User

+ 17 - 0
frontend_app_admin_workspace_user/build_admin_workspace_user.sh View File

1
+#!/bin/bash
2
+
3
+. ../bash_library.sh # source bash_library.sh
4
+
5
+windoz=""
6
+if [[ $1 = "-w" ]]; then
7
+    windoz="windoz"
8
+fi
9
+
10
+log "npm run build$windoz"
11
+npm run build$windoz
12
+log "cp dist/admin_workspace_user.app.js ../frontend/dist/app"
13
+cp dist/admin_workspace_user.app.js ../frontend/dist/app
14
+log "cp i18next.scanner/en/translation.json ../frontend/dist/app/admin_workspace_user_en_translation.json"
15
+cp i18next.scanner/en/translation.json ../frontend/dist/app/admin_workspace_user_en_translation.json
16
+log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/admin_workspace_user_fr_translation.json"
17
+cp i18next.scanner/fr/translation.json ../frontend/dist/app/admin_workspace_user_fr_translation.json

+ 1 - 0
frontend_app_admin_workspace_user/dist/asset View File

1
+../../frontend/dist/asset/

+ 0 - 0
frontend_app_admin_workspace_user/dist/dev View File


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