Преглед на файлове

Merge branch 'develop' of github.com:tracim/tracim_v2 into feature/automated_setup_and_better_uwsgi_doc

Guénaël Muller преди 6 години
родител
ревизия
e6ea68699a
променени са 100 файла, в които са добавени 2098 реда и са изтрити 948 реда
  1. 4 0
      .gitignore
  2. 0 1
      .travis.yml
  3. 7 0
      backend/development.ini.sample
  4. 12 0
      backend/doc/roles.md
  5. 3 0
      backend/setup.py
  6. 74 1
      backend/tests_configs.ini
  7. 9 4
      backend/tracim_backend/__init__.py
  8. 41 7
      backend/tracim_backend/config.py
  9. 8 0
      backend/tracim_backend/exceptions.py
  10. 18 18
      backend/tracim_backend/fixtures/content.py
  11. 65 59
      backend/tracim_backend/lib/core/content.py
  12. 1 1
      backend/tracim_backend/lib/core/notifications.py
  13. 41 3
      backend/tracim_backend/lib/core/user.py
  14. 1 1
      backend/tracim_backend/lib/core/userworkspace.py
  15. 1 1
      backend/tracim_backend/lib/core/workspace.py
  16. 46 26
      backend/tracim_backend/lib/mail_notifier/notifier.py
  17. 6 6
      backend/tracim_backend/lib/utils/authorization.py
  18. 7 3
      backend/tracim_backend/lib/utils/request.py
  19. 53 0
      backend/tracim_backend/lib/utils/utils.py
  20. 7 5
      backend/tracim_backend/lib/webdav/dav_provider.py
  21. 3 2
      backend/tracim_backend/lib/webdav/design.py
  22. 37 34
      backend/tracim_backend/lib/webdav/resources.py
  23. 5 3
      backend/tracim_backend/lib/webdav/utils.py
  24. 20 2
      backend/tracim_backend/models/applications.py
  25. 113 146
      backend/tracim_backend/models/contents.py
  26. 34 9
      backend/tracim_backend/models/context_models.py
  27. 26 28
      backend/tracim_backend/models/data.py
  28. 3 1
      backend/tracim_backend/templates/mail/content_update_body_html.mak
  29. 1 2
      backend/tracim_backend/templates/mail/created_account_body_html.mak
  30. 11 14
      backend/tracim_backend/tests/__init__.py
  31. 85 38
      backend/tracim_backend/tests/functional/test_contents.py
  32. 5 5
      backend/tracim_backend/tests/functional/test_mail_notification.py
  33. 25 72
      backend/tracim_backend/tests/functional/test_system.py
  34. 525 127
      backend/tracim_backend/tests/functional/test_user.py
  35. 112 41
      backend/tracim_backend/tests/functional/test_workspaces.py
  36. 115 115
      backend/tracim_backend/tests/library/test_content_api.py
  37. 183 0
      backend/tracim_backend/tests/library/test_user_api.py
  38. 1 0
      backend/tracim_backend/tests/library/test_webdav.py
  39. 14 0
      backend/tracim_backend/tests/library/tests_utils.py
  40. 6 6
      backend/tracim_backend/tests/models/test_content.py
  41. 4 3
      backend/tracim_backend/tests/models/test_content_revision.py
  42. 11 5
      backend/tracim_backend/views/contents_api/comment_controller.py
  43. 42 14
      backend/tracim_backend/views/contents_api/file_controller.py
  44. 13 5
      backend/tracim_backend/views/contents_api/html_document_controller.py
  45. 13 5
      backend/tracim_backend/views/contents_api/threads_controller.py
  46. 26 12
      backend/tracim_backend/views/core_api/schemas.py
  47. 3 3
      backend/tracim_backend/views/core_api/system_controller.py
  48. 59 2
      backend/tracim_backend/views/core_api/user_controller.py
  49. 28 13
      backend/tracim_backend/views/core_api/workspace_controller.py
  50. 67 0
      backend/tracim_backend/views/frontend.py
  51. 1 1
      bash_library.sh
  52. 40 51
      build_full_frontend.sh
  53. 6 0
      color.json.sample
  54. 3 1
      frontend/.gitignore
  55. 0 0
      frontend/dist/asset/bootstrap/bootstrap-4.0.0-beta.2.js
  56. 0 0
      frontend/dist/asset/bootstrap/bootstrap-4.0.0-beta.css
  57. 0 0
      frontend/dist/asset/bootstrap/jquery-3.2.1.js
  58. 0 0
      frontend/dist/asset/bootstrap/popper-1.12.3.js
  59. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/HELP-US-OUT.txt
  60. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/css/font-awesome.css
  61. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/css/font-awesome.min.css
  62. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/FontAwesome.otf
  63. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.eot
  64. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.svg
  65. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf
  66. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff
  67. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2
  68. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/animated.less
  69. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/bordered-pulled.less
  70. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/core.less
  71. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/fixed-width.less
  72. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/font-awesome.less
  73. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/icons.less
  74. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/larger.less
  75. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/list.less
  76. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/mixins.less
  77. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/path.less
  78. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/rotated-flipped.less
  79. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/screen-reader.less
  80. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/stacked.less
  81. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/variables.less
  82. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_animated.scss
  83. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_bordered-pulled.scss
  84. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_core.scss
  85. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_fixed-width.scss
  86. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_icons.scss
  87. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_larger.scss
  88. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_list.scss
  89. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_mixins.scss
  90. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_path.scss
  91. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_rotated-flipped.scss
  92. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_screen-reader.scss
  93. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_stacked.scss
  94. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_variables.scss
  95. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/font-awesome.scss
  96. 3 0
      frontend/dist/asset/tracim/appInterface.js
  97. 1 0
      frontend/dist/asset/tracim/tinymceInit.js
  98. BIN
      frontend/dist/ecbb61e619a4d2801db1054c019316cc.jpg
  99. 50 52
      frontend/dist/index.html
  100. 0 0
      frontend/dist/index.mak

+ 4 - 0
.gitignore Целия файл

@@ -1,4 +1,8 @@
1 1
 .idea/
2 2
 frontend/dist/app/
3
+frontend/dist/tracim.app.entry.js
4
+frontend/dist/asset/tracim/tracim.vendor.bundle.js
3 5
 frontend_app_html-document/dist/html-document.app.js
4 6
 frontend_lib/dist/tracim_frontend_lib.js
7
+npm-debug.log
8
+package-lock.json

+ 0 - 1
.travis.yml Целия файл

@@ -3,7 +3,6 @@ matrix:
3 3
     - sudo: false
4 4
       language: python
5 5
       python:
6
-        - "3.4"
7 6
         - "3.5"
8 7
         - "3.6"
9 8
 

+ 7 - 0
backend/development.ini.sample Целия файл

@@ -186,6 +186,13 @@ wsgidav.config_path = %(here)s/wsgidav.conf
186 186
 ## return error
187 187
 # preview.jpg.restricted_dims = True
188 188
 
189
+### Frontend
190
+frontend.serve = True
191
+# You can set dist folder of tracim frontend. by default, system
192
+# will try to get it automatically according to tracim_v2 repository
193
+# organisation.
194
+# frontend.dist_folder_path = /home/user/tracim_v2/frontend/dist
195
+
189 196
 ###
190 197
 # wsgi server configuration
191 198
 ###

+ 12 - 0
backend/doc/roles.md Целия файл

@@ -7,6 +7,11 @@ The other is workspace related and is called "workspace role".
7 7
 
8 8
 ## Global profile
9 9
 
10
+|                               | Normal User | Managers    | Admin          |
11
+|-------------------------------|-------------|-------------|----------------|
12
+| slug                            | users       | managers    | administrators |
13
+|-------------------------------|-------------|-------------|---------|
14
+
10 15
 
11 16
 |                               | Normal User | Managers    | Admin   |
12 17
 |-------------------------------|-------------|-------------|---------|
@@ -22,11 +27,18 @@ The other is workspace related and is called "workspace role".
22 27
 | access to all user data (/users/{user_id} endpoints) |personal-only|personal-only| yes     |
23 28
 
24 29
 
30
+
31
+
25 32
 ## Workspace Roles
26 33
 
27 34
 
28 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 42
 | read content                 |  yes   | yes         | yes             | yes               |
31 43
 |------------------------------|--------|-------------|-----------------|-------------------|
32 44
 | create content               |  no    | yes         | yes             | yes               |

+ 3 - 0
backend/setup.py Целия файл

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

+ 74 - 1
backend/tests_configs.ini Целия файл

@@ -4,7 +4,7 @@ depot_storage_name = test
4 4
 depot_storage_dir = /tmp/test/depot
5 5
 user.auth_token.validity = 604800
6 6
 preview_cache_dir = /tmp/test/preview_cache
7
-
7
+website.base_url = http://localhost:6543
8 8
 [app:command_test]
9 9
 use = egg:tracim_backend
10 10
 sqlalchemy.url = sqlite:///tracim_test.sqlite
@@ -12,6 +12,7 @@ depot_storage_name = test
12 12
 depot_storage_dir = /tmp/test/depot
13 13
 user.auth_token.validity = 604800
14 14
 preview_cache_dir = /tmp/test/preview_cache
15
+website.base_url = http://localhost:6543
15 16
 
16 17
 [mail_test]
17 18
 sqlalchemy.url = sqlite:///:memory:
@@ -37,6 +38,7 @@ email.notification.smtp.server = 127.0.0.1
37 38
 email.notification.smtp.port = 1025
38 39
 email.notification.smtp.user = test_user
39 40
 email.notification.smtp.password = just_a_password
41
+website.base_url = http://localhost:6543
40 42
 
41 43
 [mail_test_async]
42 44
 sqlalchemy.url = sqlite:///:memory:
@@ -63,3 +65,74 @@ email.notification.smtp.server = 127.0.0.1
63 65
 email.notification.smtp.port = 1025
64 66
 email.notification.smtp.user = test_user
65 67
 email.notification.smtp.password = just_a_password
68
+website.base_url = http://localhost:6543
69
+
70
+[functional_test]
71
+sqlalchemy.url = sqlite:///tracim_test.sqlite
72
+depot_storage_name = test
73
+depot_storage_dir = /tmp/test/depot
74
+user.auth_token.validity = 604800
75
+preview_cache_dir = /tmp/test/preview_cache
76
+preview.jpg.restricted_dims = True
77
+email.notification.activated = false
78
+website.base_url = http://localhost:6543
79
+
80
+[functional_test_no_db]
81
+sqlalchemy.url = sqlite://
82
+depot_storage_name = test
83
+depot_storage_dir = /tmp/test/depot
84
+user.auth_token.validity = 604800
85
+preview_cache_dir = /tmp/test/preview_cache
86
+preview.jpg.restricted_dims = True
87
+email.notification.activated = false
88
+website.base_url = http://localhost:6543
89
+
90
+[functional_test_with_mail_test_sync]
91
+sqlalchemy.url = sqlite:///tracim_test.sqlite
92
+depot_storage_name = test
93
+depot_storage_dir = /tmp/test/depot
94
+user.auth_token.validity = 604800
95
+preview_cache_dir = /tmp/test/preview_cache
96
+preview.jpg.restricted_dims = True
97
+email.notification.activated = true
98
+email.notification.from.email = test_user_from+{user_id}@localhost
99
+email.notification.from.default_label = Tracim Notifications
100
+email.notification.reply_to.email = test_user_reply+{content_id}@localhost
101
+email.notification.references.email = test_user_refs+{content_id}@localhost
102
+email.notification.content_update.template.html = %(here)s/tracim_backend/templates/mail/content_update_body_html.mak
103
+email.notification.content_update.template.text = %(here)s/tracim_backend/templates/mail/content_update_body_text.mak
104
+email.notification.created_account.template.html = %(here)s/tracim_backend/templates/mail/created_account_body_html.mak
105
+email.notification.created_account.template.text = %(here)s/tracim_backend/templates/mail/created_account_body_text.mak
106
+email.notification.content_update.subject = [{website_title}] [{workspace_label}] {content_label} ({content_status_label})
107
+email.notification.created_account.subject = [{website_title}] Created account
108
+email.notification.processing_mode = sync
109
+email.notification.smtp.server = 127.0.0.1
110
+email.notification.smtp.port = 1025
111
+email.notification.smtp.user = test_user
112
+email.notification.smtp.password = just_a_password
113
+website.base_url = http://localhost:6543
114
+
115
+[functional_test_with_mail_test_async]
116
+sqlalchemy.url = sqlite:///tracim_test.sqlite
117
+depot_storage_name = test
118
+depot_storage_dir = /tmp/test/depot
119
+user.auth_token.validity = 604800
120
+preview_cache_dir = /tmp/test/preview_cache
121
+preview.jpg.restricted_dims = True
122
+email.notification.activated = true
123
+email.notification.from.email = test_user_from+{user_id}@localhost
124
+email.notification.from.default_label = Tracim Notifications
125
+email.notification.reply_to.email = test_user_reply+{content_id}@localhost
126
+email.notification.references.email = test_user_refs+{content_id}@localhost
127
+email.notification.content_update.template.html = %(here)s/tracim_backend/templates/mail/content_update_body_html.mak
128
+email.notification.content_update.template.text = %(here)s/tracim_backend/templates/mail/content_update_body_text.mak
129
+email.notification.created_account.template.html = %(here)s/tracim_backend/templates/mail/created_account_body_html.mak
130
+email.notification.created_account.template.text = %(here)s/tracim_backend/templates/mail/created_account_body_text.mak
131
+email.notification.content_update.subject = [{website_title}] [{workspace_label}] {content_label} ({content_status_label})
132
+email.notification.created_account.subject = [{website_title}] Created account
133
+email.notification.processing_mode = async
134
+email.notification.smtp.server = 127.0.0.1
135
+email.notification.smtp.port = 1025
136
+email.notification.smtp.user = test_user
137
+email.notification.smtp.password = just_a_password
138
+website.base_url = http://localhost:6543

+ 9 - 4
backend/tracim_backend/__init__.py Целия файл

@@ -1,6 +1,4 @@
1 1
 # -*- coding: utf-8 -*-
2
-
3
-
4 2
 try:  # Python 3.5+
5 3
     from http import HTTPStatus
6 4
 except ImportError:
@@ -9,7 +7,6 @@ except ImportError:
9 7
 from pyramid.config import Configurator
10 8
 from pyramid.authentication import BasicAuthAuthenticationPolicy
11 9
 from hapic.ext.pyramid import PyramidContext
12
-from pyramid.exceptions import NotFound
13 10
 from sqlalchemy.exc import OperationalError
14 11
 
15 12
 from tracim_backend.extensions import hapic
@@ -30,8 +27,10 @@ from tracim_backend.views.core_api.user_controller import UserController
30 27
 from tracim_backend.views.core_api.workspace_controller import WorkspaceController
31 28
 from tracim_backend.views.contents_api.comment_controller import CommentController
32 29
 from tracim_backend.views.contents_api.file_controller import FileController
30
+from tracim_backend.views.frontend import FrontendController
33 31
 from tracim_backend.views.errors import ErrorSchema
34 32
 from tracim_backend.exceptions import NotAuthenticated
33
+from tracim_backend.exceptions import PageNotFound
35 34
 from tracim_backend.exceptions import UserNotActive
36 35
 from tracim_backend.exceptions import InvalidId
37 36
 from tracim_backend.exceptions import InsufficientUserProfile
@@ -86,7 +85,7 @@ def web(global_config, **local_settings):
86 85
     hapic.set_context(context)
87 86
     # INFO - G.M - 2018-07-04 - global-context exceptions
88 87
     # Not found
89
-    context.handle_exception(NotFound, HTTPStatus.NOT_FOUND)
88
+    context.handle_exception(PageNotFound, HTTPStatus.NOT_FOUND)
90 89
     # Bad request
91 90
     context.handle_exception(WorkspaceNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)  # nopep8
92 91
     context.handle_exception(UserNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)  # nopep8
@@ -106,6 +105,7 @@ def web(global_config, **local_settings):
106 105
     context.handle_exception(OperationalError, HTTPStatus.INTERNAL_SERVER_ERROR)
107 106
     context.handle_exception(Exception, HTTPStatus.INTERNAL_SERVER_ERROR)
108 107
 
108
+
109 109
     # Add controllers
110 110
     session_controller = SessionController()
111 111
     system_controller = SystemController()
@@ -124,6 +124,11 @@ def web(global_config, **local_settings):
124 124
     configurator.include(thread_controller.bind, route_prefix=BASE_API_V2)
125 125
     configurator.include(file_controller.bind, route_prefix=BASE_API_V2)
126 126
 
127
+    if app_config.FRONTEND_SERVE:
128
+        configurator.include('pyramid_mako')
129
+        frontend_controller = FrontendController(app_config.FRONTEND_DIST_FOLDER_PATH)  # nopep8
130
+        configurator.include(frontend_controller.bind)
131
+
127 132
     hapic.add_documentation_view(
128 133
         '/api/v2/doc',
129 134
         'Tracim v2 API',

+ 41 - 7
backend/tracim_backend/config.py Целия файл

@@ -1,10 +1,13 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 from urllib.parse import urlparse
3
+
4
+import os
3 5
 from paste.deploy.converters import asbool
4 6
 from tracim_backend.lib.utils.logger import logger
5 7
 from depot.manager import DepotManager
8
+from tracim_backend.models.contents import CONTENT_TYPES
9
+from tracim_backend.models.data import ActionDescription
6 10
 
7
-from tracim_backend.models.data import ActionDescription, ContentType
8 11
 
9 12
 
10 13
 class CFG(object):
@@ -78,6 +81,12 @@ class CFG(object):
78 81
             'website.base_url',
79 82
             '',
80 83
         )
84
+        if not self.WEBSITE_BASE_URL:
85
+            raise Exception(
86
+                'website.base_url is needed in order to have correct path in'
87
+                'few place like in email.'
88
+                'You should set it with frontend root url.'
89
+            )
81 90
 
82 91
         # TODO - G.M - 26-03-2018 - [Cleanup] These params seems deprecated for tracimv2,  # nopep8
83 92
         # Verify this
@@ -145,11 +154,11 @@ class CFG(object):
145 154
         ]
146 155
 
147 156
         self.EMAIL_NOTIFICATION_NOTIFIED_CONTENTS = [
148
-            ContentType.Page,
149
-            ContentType.Thread,
150
-            ContentType.File,
151
-            ContentType.Comment,
152
-            # ContentType.Folder -- Folder is skipped
157
+            CONTENT_TYPES.Page.slug,
158
+            CONTENT_TYPES.Thread.slug,
159
+            CONTENT_TYPES.File.slug,
160
+            CONTENT_TYPES.Comment.slug,
161
+            # CONTENT_TYPES.Folder.slug -- Folder is skipped
153 162
         ]
154 163
         if settings.get('email.notification.from'):
155 164
             raise Exception(
@@ -160,9 +169,11 @@ class CFG(object):
160 169
 
161 170
         self.EMAIL_NOTIFICATION_FROM_EMAIL = settings.get(
162 171
             'email.notification.from.email',
172
+            'noreply+{user_id}@trac.im'
163 173
         )
164 174
         self.EMAIL_NOTIFICATION_FROM_DEFAULT_LABEL = settings.get(
165
-            'email.notification.from.default_label'
175
+            'email.notification.from.default_label',
176
+            'Tracim Notifications'
166 177
         )
167 178
         self.EMAIL_NOTIFICATION_REPLY_TO_EMAIL = settings.get(
168 179
             'email.notification.reply_to.email',
@@ -432,6 +443,29 @@ class CFG(object):
432 443
 
433 444
         self.PREVIEW_JPG_ALLOWED_DIMS = allowed_dims
434 445
 
446
+        self.FRONTEND_SERVE = asbool(settings.get(
447
+            'frontend.serve', False
448
+        ))
449
+
450
+        # INFO - G.M - 2018-08-06 - we pretend that frontend_dist_folder
451
+        # is probably in frontend subfolder
452
+        # of tracim_v2 parent of both backend and frontend
453
+        backend_folder = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))  # nopep8
454
+        tracim_v2_folder = os.path.dirname(backend_folder)
455
+        frontend_dist_folder = os.path.join(tracim_v2_folder, 'frontend', 'dist')  # nopep8
456
+
457
+        self.FRONTEND_DIST_FOLDER_PATH = settings.get(
458
+            'frontend.dist_folder_path', frontend_dist_folder
459
+        )
460
+
461
+        # INFO - G.M - 2018-08-06 - We check dist folder existence
462
+        if self.FRONTEND_SERVE and not os.path.isdir(self.FRONTEND_DIST_FOLDER_PATH):  # nopep8
463
+            raise Exception(
464
+                'ERROR: {} folder does not exist as folder. '
465
+                'please set frontend.dist_folder.path'
466
+                'with a correct value'.format(self.FRONTEND_DIST_FOLDER_PATH)
467
+            )
468
+
435 469
     def configure_filedepot(self):
436 470
         depot_storage_name = self.DEPOT_STORAGE_NAME
437 471
         depot_storage_path = self.DEPOT_STORAGE_DIR

+ 8 - 0
backend/tracim_backend/exceptions.py Целия файл

@@ -203,3 +203,11 @@ class PageOfPreviewNotFound(NotFound):
203 203
 
204 204
 class PreviewDimNotAllowed(TracimException):
205 205
     pass
206
+
207
+
208
+class TooShortAutocompleteString(TracimException):
209
+    pass
210
+
211
+
212
+class PageNotFound(TracimException):
213
+    pass

+ 18 - 18
backend/tracim_backend/fixtures/content.py Целия файл

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

+ 65 - 59
backend/tracim_backend/lib/core/content.py Целия файл

@@ -34,13 +34,15 @@ from tracim_backend.exceptions import EmptyLabelNotAllowed
34 34
 from tracim_backend.exceptions import ContentNotFound
35 35
 from tracim_backend.exceptions import WorkspacesDoNotMatch
36 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 40
 from tracim_backend.models.revision_protection import new_revision
38 41
 from tracim_backend.models.auth import User
39 42
 from tracim_backend.models.data import ActionDescription
40
-from tracim_backend.models.data import ContentStatus
41 43
 from tracim_backend.models.data import ContentRevisionRO
42 44
 from tracim_backend.models.data import Content
43
-from tracim_backend.models.data import ContentType
45
+
44 46
 from tracim_backend.models.data import NodeTreeItem
45 47
 from tracim_backend.models.data import RevisionReadStatus
46 48
 from tracim_backend.models.data import UserRoleInWorkspace
@@ -75,10 +77,10 @@ def compare_content_for_sorting_by_type_and_name(
75 77
     else:
76 78
         # TODO - D.A. - 2014-12-02 - Manage Content Types Dynamically
77 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 86
         content_1_type_index = content_type_order.index(content1.type)
@@ -105,15 +107,15 @@ class ContentApi(object):
105 107
     SEARCH_SEPARATORS = ',| '
106 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 120
     def __init__(
119 121
             self,
@@ -230,7 +232,7 @@ class ContentApi(object):
230 232
 
231 233
         # Exclude non displayable types
232 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 237
         if workspace:
236 238
             result = result.filter(Content.workspace_id == workspace.workspace_id)
@@ -369,8 +371,8 @@ class ContentApi(object):
369 371
     #     removed_item_ids = removed_item_ids or []  # FDV
370 372
     # 
371 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 376
     #         allowed_node_types = ContentType.all()
375 377
     # 
376 378
     #     parent_id = parent.content_id if parent else None
@@ -395,7 +397,7 @@ class ContentApi(object):
395 397
     #     for folder in folders:
396 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 401
     #             content_type__allowed = folder.properties['allowed_content'][allowed_content_type] == True
400 402
     # 
401 403
     #             if is_folder and content_type__allowed:
@@ -404,12 +406,13 @@ class ContentApi(object):
404 406
     # 
405 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 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 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 416
             label = self.generate_folder_label(workspace, parent)
414 417
 
415 418
         content = Content()
@@ -421,7 +424,7 @@ class ContentApi(object):
421 424
         elif label:
422 425
             content.label = label
423 426
         else:
424
-            if content_type == ContentType.Comment:
427
+            if content_type_slug == CONTENT_TYPES.Comment.slug:
425 428
                 # INFO - G.M - 2018-07-16 - Default label for comments is
426 429
                 # empty string.
427 430
                 content.label = ''
@@ -431,13 +434,13 @@ class ContentApi(object):
431 434
         content.owner = self._user
432 435
         content.parent = parent
433 436
         content.workspace = workspace
434
-        content.type = content_type
437
+        content.type = content_type_slug
435 438
         content.is_temporary = is_temporary
436 439
         content.revision_type = ActionDescription.CREATION
437 440
 
438 441
         if content.type in (
439
-                ContentType.Page,
440
-                ContentType.Thread,
442
+                CONTENT_TYPES.Page.slug,
443
+                CONTENT_TYPES.Thread.slug,
441 444
         ):
442 445
             content.file_extension = '.html'
443 446
 
@@ -447,7 +450,7 @@ class ContentApi(object):
447 450
         return content
448 451
 
449 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 454
         if not content:
452 455
             raise EmptyCommentContentNotAllowed()
453 456
         item = Content()
@@ -456,7 +459,7 @@ class ContentApi(object):
456 459
         if not workspace:
457 460
             workspace = item.parent.workspace
458 461
         item.workspace = workspace
459
-        item.type = ContentType.Comment
462
+        item.type = CONTENT_TYPES.Comment.slug
460 463
         item.description = content
461 464
         item.label = ''
462 465
         item.revision_type = ActionDescription.COMMENT
@@ -492,7 +495,7 @@ class ContentApi(object):
492 495
 
493 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 499
             base_request = base_request.filter(Content.type==content_type)
497 500
 
498 501
         if parent:
@@ -577,20 +580,20 @@ class ContentApi(object):
577 580
         return query.filter(
578 581
             or_(
579 582
                 and_(
580
-                    Content.type == ContentType.File,
583
+                    Content.type == CONTENT_TYPES.File.slug,
581 584
                     Content.label == file_name,
582 585
                     Content.file_extension == file_extension,
583 586
                 ),
584 587
                 and_(
585
-                    Content.type == ContentType.Thread,
588
+                    Content.type == CONTENT_TYPES.Thread.slug,
586 589
                     Content.label == file_name,
587 590
                 ),
588 591
                 and_(
589
-                    Content.type == ContentType.Page,
592
+                    Content.type == CONTENT_TYPES.Page.slug,
590 593
                     Content.label == file_name,
591 594
                 ),
592 595
                 and_(
593
-                    Content.type == ContentType.Folder,
596
+                    Content.type == CONTENT_TYPES.Folder.slug,
594 597
                     Content.label == content_label,
595 598
                 ),
596 599
             )
@@ -671,7 +674,7 @@ class ContentApi(object):
671 674
             # Filter query on label
672 675
             folder_query = query \
673 676
                 .filter(
674
-                    Content.type == ContentType.Folder,
677
+                    Content.type == CONTENT_TYPES.Folder.slug,
675 678
                     Content.label == label,
676 679
                     Content.workspace_id == workspace.workspace_id,
677 680
                 )
@@ -723,22 +726,22 @@ class ContentApi(object):
723 726
 
724 727
         return query.filter(or_(
725 728
             and_(
726
-                Content.type == ContentType.File,
729
+                Content.type == CONTENT_TYPES.File.slug,
727 730
                 file_name_filter,
728 731
                 file_extension_filter,
729 732
             ),
730 733
             and_(
731
-                Content.type == ContentType.Thread,
734
+                Content.type == CONTENT_TYPES.Thread.slug,
732 735
                 file_name_filter,
733 736
                 file_extension_filter,
734 737
             ),
735 738
             and_(
736
-                Content.type == ContentType.Page,
739
+                Content.type == CONTENT_TYPES.Page.slug,
737 740
                 file_name_filter,
738 741
                 file_extension_filter,
739 742
             ),
740 743
             and_(
741
-                Content.type == ContentType.Folder,
744
+                Content.type == CONTENT_TYPES.Folder.slug,
742 745
                 label_filter,
743 746
             ),
744 747
         ))
@@ -844,25 +847,28 @@ class ContentApi(object):
844 847
     def _get_all_query(
845 848
         self,
846 849
         parent_id: int = None,
847
-        content_type: str = ContentType.Any,
850
+        content_type_slug: str = CONTENT_TYPES.Any_SLUG,
848 851
         workspace: Workspace = None
849 852
     ) -> Query:
850 853
         """
851 854
         Extended filter for better "get all data" query
852 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 857
         :param workspace: filter by workspace
855 858
         :return:
856 859
         """
857 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 862
         resultset = self._base_query(workspace)
860 863
 
861
-        if content_type!=ContentType.Any:
864
+        if content_type_slug != CONTENT_TYPES.Any_SLUG:
862 865
             # INFO - G.M - 2018-07-05 - convert with
863 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 873
         if parent_id:
868 874
             resultset = resultset.filter(Content.parent_id==parent_id)
@@ -871,7 +877,7 @@ class ContentApi(object):
871 877
 
872 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 881
         return self._get_all_query(parent_id, content_type, workspace).all()
876 882
 
877 883
     # TODO - G.M - 2018-07-17 - [Cleanup] Drop this method if unneeded
@@ -895,14 +901,14 @@ class ContentApi(object):
895 901
 
896 902
     # TODO - G.M - 2018-07-17 - [Cleanup] Drop this method if unneeded
897 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 905
         assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
900 906
         assert content_type is not None# DYN_REMOVE
901 907
         assert isinstance(content_type, str) # DYN_REMOVE
902 908
 
903 909
         resultset = self._base_query(workspace)
904 910
 
905
-        if content_type != ContentType.Any:
911
+        if content_type != CONTENT_TYPES.Any_SLUG:
906 912
             resultset = resultset.filter(Content.type==content_type)
907 913
 
908 914
         resultset = resultset.filter(Content.is_deleted == self._show_deleted)
@@ -919,7 +925,7 @@ class ContentApi(object):
919 925
 
920 926
         resultset = self._base_query(workspace)
921 927
 
922
-        if content_type != ContentType.Any:
928
+        if content_type != CONTENT_TYPES.Any_SLUG:
923 929
             resultset = resultset.filter(Content.type==content_type)
924 930
 
925 931
         return resultset.all()
@@ -945,7 +951,7 @@ class ContentApi(object):
945 951
     #
946 952
     #     resultset = self._base_query(workspace)
947 953
     #
948
-    #     if content_type != ContentType.Any:
954
+    #     if content_type != CONTENT_TYPES.Any_SLUG:
949 955
     #         resultset = resultset.filter(Content.type==content_type)
950 956
     #
951 957
     #     return resultset.all()
@@ -978,7 +984,7 @@ class ContentApi(object):
978 984
                     Content.content_id.in_(content_ids),
979 985
                     and_(
980 986
                         Content.parent_id.in_(content_ids),
981
-                        Content.type == ContentType.Comment
987
+                        Content.type == CONTENT_TYPES.Comment.slug
982 988
                     )
983 989
                 )
984 990
             )
@@ -990,7 +996,7 @@ class ContentApi(object):
990 996
         before_content_find = False
991 997
         for content in resultset:
992 998
             related_active_content = None
993
-            if ContentType.Comment == content.type:
999
+            if CONTENT_TYPES.Comment.slug == content.type:
994 1000
                 related_active_content = content.parent
995 1001
             else:
996 1002
                 related_active_content = content
@@ -1040,12 +1046,12 @@ class ContentApi(object):
1040 1046
     #         .filter(Content.content_id.in_(not_read_content_ids)) \
1041 1047
     #         .order_by(desc(Content.updated))
1042 1048
     #
1043
-    #     if content_type != ContentType.Any:
1049
+    #     if content_type != CONTENT_TYPES.Any_SLUG:
1044 1050
     #         not_read_contents = not_read_contents.filter(
1045 1051
     #             Content.type==content_type)
1046 1052
     #     else:
1047 1053
     #         not_read_contents = not_read_contents.filter(
1048
-    #             Content.type!=ContentType.Folder)
1054
+    #             Content.type!=CONTENT_TYPES.Folder.slug)
1049 1055
     #
1050 1056
     #     if parent_id:
1051 1057
     #         not_read_contents = not_read_contents.filter(
@@ -1054,7 +1060,7 @@ class ContentApi(object):
1054 1060
     #     result = []
1055 1061
     #     for item in not_read_contents:
1056 1062
     #         new_item = None
1057
-    #         if ContentType.Comment == item.type:
1063
+    #         if CONTENT_TYPES.Comment.slug == item.type:
1058 1064
     #             new_item = item.parent
1059 1065
     #         else:
1060 1066
     #             new_item = item
@@ -1086,7 +1092,7 @@ class ContentApi(object):
1086 1092
         folder.properties = properties
1087 1093
 
1088 1094
     def set_status(self, content: Content, new_status: str):
1089
-        if new_status in ContentStatus.allowed_values():
1095
+        if new_status in CONTENT_STATUS.get_all_slugs_values():
1090 1096
             content.status = new_status
1091 1097
             content.revision_type = ActionDescription.STATUS_UPDATE
1092 1098
         else:
@@ -1296,7 +1302,7 @@ class ContentApi(object):
1296 1302
                 self.mark_read(child, read_datetime=read_datetime,
1297 1303
                                do_flush=False)
1298 1304
 
1299
-            if ContentType.Comment == content.type:
1305
+            if CONTENT_TYPES.Comment.slug == content.type:
1300 1306
                 self.mark_read(content.parent, read_datetime=read_datetime,
1301 1307
                                do_flush=False, recursive=False)
1302 1308
                 for comment in content.parent.get_comments():
@@ -1416,12 +1422,12 @@ class ContentApi(object):
1416 1422
         return title_keyworded_items
1417 1423
 
1418 1424
     def get_all_types(self) -> typing.List[ContentType]:
1419
-        labels = ContentType.all()
1425
+        labels = CONTENT_TYPES.endpoint_allowed_types_slug()
1420 1426
         content_types = []
1421 1427
         for label in labels:
1422
-            content_types.append(ContentType(label))
1428
+            content_types.append(CONTENT_TYPES.get_one_by_slug(label))
1423 1429
 
1424
-        return ContentType.sorted(content_types)
1430
+        return content_types
1425 1431
 
1426 1432
     # TODO - G.M - 2018-07-24 - [Cleanup] Is this method already needed ?
1427 1433
     def exclude_unavailable(

+ 1 - 1
backend/tracim_backend/lib/core/notifications.py Целия файл

@@ -1,7 +1,7 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 from sqlalchemy.orm import Session
3 3
 
4
-from tracim_backend import CFG
4
+from tracim_backend.config import CFG
5 5
 from tracim_backend.lib.utils.logger import logger
6 6
 from tracim_backend.models.auth import User
7 7
 from tracim_backend.models.data import Content

+ 41 - 3
backend/tracim_backend/lib/core/user.py Целия файл

@@ -3,13 +3,17 @@ from smtplib import SMTPException
3 3
 
4 4
 import transaction
5 5
 import typing as typing
6
+
7
+from sqlalchemy import or_
6 8
 from sqlalchemy.orm import Session
9
+from sqlalchemy.orm import Query
7 10
 from sqlalchemy.orm.exc import NoResultFound
8 11
 
9
-from tracim_backend import CFG
12
+from tracim_backend.config import CFG
10 13
 from tracim_backend.models.auth import User
11 14
 from tracim_backend.models.auth import Group
12 15
 from tracim_backend.exceptions import NoUserSetted
16
+from tracim_backend.exceptions import TooShortAutocompleteString
13 17
 from tracim_backend.exceptions import PasswordDoNotMatch
14 18
 from tracim_backend.exceptions import EmailValidationFailed
15 19
 from tracim_backend.exceptions import UserDoesNotExist
@@ -20,6 +24,7 @@ from tracim_backend.exceptions import UserNotActive
20 24
 from tracim_backend.models.context_models import UserInContext
21 25
 from tracim_backend.lib.mail_notifier.notifier import get_email_manager
22 26
 from tracim_backend.models.context_models import TypeUser
27
+from tracim_backend.models.data import UserRoleInWorkspace
23 28
 
24 29
 
25 30
 class UserApi(object):
@@ -94,8 +99,41 @@ class UserApi(object):
94 99
             raise UserDoesNotExist('There is no current user')
95 100
         return self._user
96 101
 
102
+    def _get_all_query(self) -> Query:
103
+        return self._session.query(User).order_by(User.display_name)
104
+
97 105
     def get_all(self) -> typing.Iterable[User]:
98
-        return self._session.query(User).order_by(User.display_name).all()
106
+        return self._get_all_query().all()
107
+
108
+    def get_known_user(
109
+            self,
110
+            acp: str,
111
+    ) -> typing.Iterable[User]:
112
+        """
113
+        Return list of know user by current UserApi user.
114
+        :param acp: autocomplete filter by name/email
115
+        :return: List of found users
116
+        """
117
+        if len(acp) < 2:
118
+            raise TooShortAutocompleteString(
119
+                '"{acp}" is a too short string, acp string need to have more than one character'.format(acp=acp)  # nopep8
120
+            )
121
+        query = self._get_all_query()
122
+        query = query.filter(or_(User.display_name.ilike('%{}%'.format(acp)), User.email.ilike('%{}%'.format(acp))))  # nopep8
123
+
124
+        # INFO - G.M - 2018-07-27 - if user is set and is simple user, we
125
+        # should show only user in same workspace as user
126
+        if self._user and self._user.profile.id <= Group.TIM_USER:
127
+            user_workspaces_id_query = self._session.\
128
+                query(UserRoleInWorkspace.workspace_id).\
129
+                distinct(UserRoleInWorkspace.workspace_id).\
130
+                filter(UserRoleInWorkspace.user_id == self._user.user_id)
131
+            users_in_workspaces = self._session.\
132
+                query(UserRoleInWorkspace.user_id).\
133
+                distinct(UserRoleInWorkspace.user_id).\
134
+                filter(UserRoleInWorkspace.workspace_id.in_(user_workspaces_id_query.subquery())).subquery()  # nopep8
135
+            query = query.filter(User.user_id.in_(users_in_workspaces))
136
+        return query.all()
99 137
 
100 138
     def find(
101 139
             self,
@@ -196,7 +234,7 @@ class UserApi(object):
196 234
         )
197 235
         if do_save:
198 236
             # TODO - G.M - 2018-07-24 - Check why commit is needed here
199
-            transaction.commit()
237
+            self.save(user)
200 238
         return user
201 239
 
202 240
     def set_email(

+ 1 - 1
backend/tracim_backend/lib/core/userworkspace.py Целия файл

@@ -1,7 +1,7 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 import typing
3 3
 
4
-from tracim_backend import CFG
4
+from tracim_backend.config import CFG
5 5
 from tracim_backend.models.context_models import UserRoleWorkspaceInContext
6 6
 from tracim_backend.models.roles import WorkspaceRoles
7 7
 

+ 1 - 1
backend/tracim_backend/lib/core/workspace.py Целия файл

@@ -5,7 +5,7 @@ from sqlalchemy.orm import Query
5 5
 from sqlalchemy.orm import Session
6 6
 from sqlalchemy.orm.exc import NoResultFound
7 7
 
8
-from tracim_backend import CFG
8
+from tracim_backend.config import CFG
9 9
 from tracim_backend.exceptions import EmptyLabelNotAllowed
10 10
 from tracim_backend.exceptions import WorkspaceNotFound
11 11
 from tracim_backend.lib.utils.translation import fake_translator as _

+ 46 - 26
backend/tracim_backend/lib/mail_notifier/notifier.py Целия файл

@@ -10,18 +10,21 @@ from lxml.html.diff import htmldiff
10 10
 from mako.template import Template
11 11
 from sqlalchemy.orm import Session
12 12
 
13
-from tracim_backend import CFG
13
+from tracim_backend.config import CFG
14 14
 from tracim_backend.lib.core.notifications import INotifier
15 15
 from tracim_backend.lib.mail_notifier.sender import EmailSender
16 16
 from tracim_backend.lib.mail_notifier.utils import SmtpConfiguration, EST
17 17
 from tracim_backend.lib.mail_notifier.sender import send_email_through
18 18
 from tracim_backend.lib.core.workspace import WorkspaceApi
19 19
 from tracim_backend.lib.utils.logger import logger
20
-from tracim_backend.models import User
20
+from tracim_backend.lib.utils.utils import get_login_frontend_url
21
+from tracim_backend.lib.utils.utils import get_email_logo_frontend_url
21 22
 from tracim_backend.models.auth import User
23
+from tracim_backend.models.contents import CONTENT_TYPES
24
+from tracim_backend.models.context_models import ContentInContext
25
+from tracim_backend.models.context_models import WorkspaceInContext
22 26
 from tracim_backend.models.data import ActionDescription
23 27
 from tracim_backend.models.data import Content
24
-from tracim_backend.models.data import ContentType
25 28
 from tracim_backend.models.data import UserRoleInWorkspace
26 29
 from tracim_backend.lib.utils.translation import fake_translator as l_, \
27 30
     fake_translator as _
@@ -233,8 +236,14 @@ class EmailManager(object):
233 236
             config=self.config,
234 237
             show_archived=True,
235 238
             show_deleted=True,
236
-        ).get_one(event_content_id, ContentType.Any)
237
-        main_content = content.parent if content.type == ContentType.Comment else content
239
+        ).get_one(event_content_id, CONTENT_TYPES.Any_SLUG)
240
+        workspace_api = WorkspaceApi(
241
+            session=self.session,
242
+            current_user=user,
243
+            config=self.config,
244
+        )
245
+        workpace_in_context = workspace_api.get_workspace_with_context(workspace_api.get_one(content.workspace_id))  # nopep8
246
+        main_content = content.parent if content.type == CONTENT_TYPES.Comment.slug else content  # nopep8
238 247
         notifiable_roles = WorkspaceApi(
239 248
             current_user=user,
240 249
             session=self.session,
@@ -265,7 +274,7 @@ class EmailManager(object):
265 274
             # INFO - G.M - 2017-11-15 - set content_id in header to permit reply
266 275
             # references can have multiple values, but only one in this case.
267 276
             replyto_addr = self.config.EMAIL_NOTIFICATION_REPLY_TO_EMAIL.replace( # nopep8
268
-                '{content_id}',str(content.content_id)
277
+                '{content_id}', str(content.content_id)
269 278
             )
270 279
 
271 280
             reference_addr = self.config.EMAIL_NOTIFICATION_REFERENCES_EMAIL.replace( #nopep8
@@ -297,8 +306,21 @@ class EmailManager(object):
297 306
             # To link this email to a content we create a virtual parent
298 307
             # in reference who contain the content_id.
299 308
             message['References'] = formataddr(('',reference_addr))
300
-            body_text = self._build_email_body_for_content(self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT, role, content, user)
301
-            body_html = self._build_email_body_for_content(self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML, role, content, user)
309
+            content_in_context = content_api.get_content_in_context(content)
310
+            body_text = self._build_email_body_for_content(
311
+                self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT,
312
+                role,
313
+                content_in_context,
314
+                workpace_in_context,
315
+                user
316
+            )
317
+            body_html = self._build_email_body_for_content(
318
+                self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML,
319
+                role,
320
+                content_in_context,
321
+                workpace_in_context,
322
+                user
323
+            )
302 324
 
303 325
             part1 = MIMEText(body_text, 'plain', 'utf-8')
304 326
             part2 = MIMEText(body_html, 'html', 'utf-8')
@@ -362,9 +384,9 @@ class EmailManager(object):
362 384
             'user': user,
363 385
             'password': password,
364 386
             # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for logo_url  # nopep8
365
-            'logo_url': '',
387
+            'logo_url': get_email_logo_frontend_url(self.config),
366 388
             # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for login_url  # nopep8
367
-            'login_url': self.config.WEBSITE_BASE_URL,
389
+            'login_url': get_login_frontend_url(self.config),
368 390
         }
369 391
         body_text = self._render_template(
370 392
             mako_template_filepath=text_template_file_path,
@@ -415,8 +437,9 @@ class EmailManager(object):
415 437
             self,
416 438
             mako_template_filepath: str,
417 439
             role: UserRoleInWorkspace,
418
-            content: Content,
419
-            actor: User
440
+            content_in_context: ContentInContext,
441
+            workspace_in_context: WorkspaceInContext,
442
+            actor: User,
420 443
     ) -> str:
421 444
         """
422 445
         Build an email body and return it as a string
@@ -424,24 +447,21 @@ class EmailManager(object):
424 447
         :param role: the role related to user to whom the email must be sent. The role is required (and not the user only) in order to show in the mail why the user receive the notification
425 448
         :param content: the content item related to the notification
426 449
         :param actor: the user at the origin of the action / notification (for example the one who wrote a comment
427
-        :param config: the global configuration
428 450
         :return: the built email body as string. In case of multipart email, this method must be called one time for text and one time for html
429 451
         """
430 452
         logger.debug(self, 'Building email content from MAKO template {}'.format(mako_template_filepath))
431
-
453
+        content = content_in_context.content
432 454
         main_title = content.label
433 455
         content_intro = ''
434 456
         content_text = ''
435 457
         call_to_action_text = ''
436 458
 
437
-        # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for call_to_action_url  # nopep8
438
-        call_to_action_url =''
459
+        call_to_action_url = content_in_context.frontend_url
439 460
         # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for status_icon_url  # nopep8
440 461
         status_icon_url = ''
441
-        # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for workspace_url  # nopep8
442
-        workspace_url = ''
462
+        workspace_url = workspace_in_context.frontend_url
443 463
         # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for logo_url  # nopep8
444
-        logo_url = ''
464
+        logo_url = get_email_logo_frontend_url(self.config)
445 465
 
446 466
         action = content.get_last_action().id
447 467
         if ActionDescription.COMMENT == action:
@@ -455,20 +475,20 @@ class EmailManager(object):
455 475
             content_text = content.description
456 476
             call_to_action_text = l_('View online')
457 477
 
458
-            if ContentType.Thread == content.type:
478
+            if CONTENT_TYPES.Thread.slug == content.type:
459 479
                 call_to_action_text = l_('Answer')
460 480
                 content_intro = l_('<span id="content-intro-username">{}</span> started a thread entitled:').format(actor.display_name)
461 481
                 content_text = '<p id="content-body-intro">{}</p>'.format(content.label) + \
462 482
                                content.get_last_comment_from(actor).description
463 483
 
464
-            elif ContentType.File == content.type:
484
+            elif CONTENT_TYPES.File.slug == content.type:
465 485
                 content_intro = l_('<span id="content-intro-username">{}</span> added a file entitled:').format(actor.display_name)
466 486
                 if content.description:
467 487
                     content_text = content.description
468 488
                 else:
469 489
                     content_text = '<span id="content-body-only-title">{}</span>'.format(content.label)
470 490
 
471
-            elif ContentType.Page == content.type:
491
+            elif CONTENT_TYPES.Page.slug == content.type:
472 492
                 content_intro = l_('<span id="content-intro-username">{}</span> added a page entitled:').format(actor.display_name)
473 493
                 content_text = '<span id="content-body-only-title">{}</span>'.format(content.label)
474 494
 
@@ -476,11 +496,11 @@ class EmailManager(object):
476 496
             content_text = content.description
477 497
             call_to_action_text = l_('View online')
478 498
 
479
-            if ContentType.File == content.type:
499
+            if CONTENT_TYPES.File.slug == content.type:
480 500
                 content_intro = l_('<span id="content-intro-username">{}</span> uploaded a new revision.').format(actor.display_name)
481 501
                 content_text = ''
482 502
 
483
-            elif ContentType.Page == content.type:
503
+            elif CONTENT_TYPES.Page.slug == content.type:
484 504
                 content_intro = l_('<span id="content-intro-username">{}</span> updated this page.').format(actor.display_name)
485 505
                 previous_revision = content.get_previous_revision()
486 506
                 title_diff = ''
@@ -490,7 +510,7 @@ class EmailManager(object):
490 510
                     title_diff + \
491 511
                     htmldiff(previous_revision.description, content.description)
492 512
 
493
-            elif ContentType.Thread == content.type:
513
+            elif CONTENT_TYPES.Thread.slug == content.type:
494 514
                 content_intro = l_('<span id="content-intro-username">{}</span> updated the thread description.').format(actor.display_name)
495 515
                 previous_revision = content.get_previous_revision()
496 516
                 title_diff = ''
@@ -503,7 +523,7 @@ class EmailManager(object):
503 523
         elif ActionDescription.EDITION == action:
504 524
             call_to_action_text = l_('View online')
505 525
 
506
-            if ContentType.File == content.type:
526
+            if CONTENT_TYPES.File.slug == content.type:
507 527
                 content_intro = l_('<span id="content-intro-username">{}</span> updated the file description.').format(actor.display_name)
508 528
                 content_text = '<p id="content-body-intro">{}</p>'.format(content.get_label()) + \
509 529
                     content.description

+ 6 - 6
backend/tracim_backend/lib/utils/authorization.py Целия файл

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

+ 7 - 3
backend/tracim_backend/lib/utils/request.py Целия файл

@@ -15,7 +15,7 @@ from tracim_backend.exceptions import UserNotFoundInTracimRequest
15 15
 from tracim_backend.exceptions import UserDoesNotExist
16 16
 from tracim_backend.exceptions import WorkspaceNotFound
17 17
 from tracim_backend.exceptions import ImmutableAttribute
18
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
18
+from tracim_backend.models.contents import CONTENT_TYPES
19 19
 from tracim_backend.lib.core.content import ContentApi
20 20
 from tracim_backend.lib.core.user import UserApi
21 21
 from tracim_backend.lib.core.workspace import WorkspaceApi
@@ -229,11 +229,13 @@ class TracimRequest(Request):
229 229
             api = ContentApi(
230 230
                 current_user=user,
231 231
                 session=request.dbsession,
232
+                show_deleted=True,
233
+                show_archived=True,
232 234
                 config=request.registry.settings['CFG']
233 235
             )
234 236
             comment = api.get_one(
235 237
                 comment_id,
236
-                content_type=ContentType.Comment,
238
+                content_type=CONTENT_TYPES.Comment.slug,
237 239
                 workspace=workspace,
238 240
                 parent=content,
239 241
             )
@@ -268,10 +270,12 @@ class TracimRequest(Request):
268 270
                 raise ContentNotFoundInTracimRequest('No content_id property found in request')  # nopep8
269 271
             api = ContentApi(
270 272
                 current_user=user,
273
+                show_deleted=True,
274
+                show_archived=True,
271 275
                 session=request.dbsession,
272 276
                 config=request.registry.settings['CFG']
273 277
             )
274
-            content = api.get_one(content_id=content_id, workspace=workspace, content_type=ContentType.Any)  # nopep8
278
+            content = api.get_one(content_id=content_id, workspace=workspace, content_type=CONTENT_TYPES.Any_SLUG)  # nopep8
275 279
         except NoResultFound as exc:
276 280
             raise ContentNotFound(
277 281
                 'Content {} does not exist '

+ 53 - 0
backend/tracim_backend/lib/utils/utils.py Целия файл

@@ -1,5 +1,9 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 import datetime
3
+import random
4
+import string
5
+from enum import Enum
6
+
3 7
 from redis import Redis
4 8
 from rq import Queue
5 9
 
@@ -8,6 +12,32 @@ from tracim_backend.config import CFG
8 12
 DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
9 13
 DEFAULT_WEBDAV_CONFIG_FILE = "wsgidav.conf"
10 14
 DEFAULT_TRACIM_CONFIG_FILE = "development.ini"
15
+CONTENT_FRONTEND_URL_SCHEMA = 'workspaces/{workspace_id}/contents/{content_type}/{content_id}'  # nopep8
16
+WORKSPACE_FRONTEND_URL_SCHEMA = 'workspaces/{workspace_id}'  # nopep8
17
+
18
+
19
+def get_root_frontend_url(config: CFG) -> str:
20
+    """
21
+    Return website base url with always '/' at the end
22
+    """
23
+    base_url = ''
24
+    if config.WEBSITE_BASE_URL[-1] == '/':
25
+        base_url = config.WEBSITE_BASE_URL
26
+    else:
27
+        base_url = config.WEBSITE_BASE_URL + '/'
28
+    return base_url
29
+
30
+
31
+def get_login_frontend_url(config: CFG):
32
+    """
33
+    Return login page url
34
+    """
35
+    return get_root_frontend_url(config) + 'login'
36
+
37
+
38
+def get_email_logo_frontend_url(config: CFG):
39
+    # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for email_logo_frontend_url  # nopep8
40
+    return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4QUTDjMSlsws9AAAB89JREFUaN7tmWtwlFcZx3/PeXeXXLmKQJEGsFZApIUBnaqjUm4VgdYyCUib0cJoKLmHW5EWmlGoQkMICURgEPuhtiYDU7XcyVgKVugULZVCQAwUYyENYAmEXHb3PH7IFjYQNgnZHfnA+bKz+7573v//PPf/C/fWvdWhJWHfMbO4P479OpCAigeowWglPq3ARWfU9AJ7Cb/3MAXZn95dBLKKHsfIQmAUIq7rv6t6gU+BKCAGaEBkN9h88tLe7sgjTdjAJ5Y4OGYMIo80Aw8g4kakJyLxiDiIxABPoMwj5+XP3R0ESpP8WC1H1YuqBj4bUWwIB5gA0d/6/xFQFUpKHBJLHADyU9chTEc1BdEZqJ0O+ofb4xcPyuBIxoBQuHc4ov3w2zrwH0Nd0aBDccwgVPsjEg/4Uc4jegKfvkfMmQ9JSfECkFP0HGJeuu0TrJZhSacg9Xh4Caze2wsXC1B9GugO+BAqUIkC/QIinhYsYoEqYDt+fY2s8WXkFE5CnBIg+vaWtJuQukzy5te2l4Bz2ysTkxchshCRWERMU2BKT4RuiLT0vwagHsENPIyR7zAx2eGybws09AUZEQLHcNS5wMEdB8NjgaKyBNTuQuTLoWOAs6AHUd5DpRzxX0KMBe0M+gA4D4Hdw5mT72LJQORxkM6IfgxSC3wjyHpHEN8M8jKPtYeAq2VgvhHg9A4RvPUIKzCyBVe346SM9LZw1y5KShzOxfUhL60SmMfcNUXgisXYS/iNA/IzhNmBgH4I3E8Cxzpmgby9ffFoMSKTQxA4jZjRpI35qENZbF5Rb1SWgcwM7HsQr28KhZnVd55GPfqTkOCbaB+i2lR1uHa8nHYeda0EygJWGILHGXzndaBgz3CQ5Fb8/hSqr5E7uj4sBXBVSjlqN6FaixKHlS8BxCYtHhs/bUly9JT597U9C30/eRYiU4Nc5Q1gH+AG/gvsxJBH2vg3w9pDPTjqFDGxX0FkKOih2PtHOcbIBiPMMi5XT9eDo9/xlu+rDW2BpX+MAQnOCudRWcWFA3OwncYhrke5VpuK1SrW7+sTVgKv5NYjsr2p6TMY1QEGvhi4mmw89unWXaiX6QQEN1YHuHjpELm5lsxvV2MbhxAdWwCSiNfnCXsb7mc/Ih8hXED8J1T1k6awEAG+R+LCLqEJOL2vInwQOP46VHaSm9QYiI1nMU4xwv2oa3OHs0+LiaHuHKr/wOo/r1yuOQwcDEqVw2KdqITQBFJGelFWojITPz8kY+wmAAp3pWLkJVT7oGwkY3R5REar/Ln1qO7AyIfsLGxQ1eB60E3UDmi9kKWPOwmcvP69cM8kROYCXVB5FW18I4LToeLSbTGnKh2T9MIChLHBOI2lT9srMUDB9s7AM8CApj7H/yaZExsiOd9GnbngcjzOciPyVPOuW0RFB7ZvHnDck4FJgbOpwPoPRHpAdxs782bwQSwei3ty0aC2EVh61ANMutEy63Gi/l0VUfSJC7sIjAnhYYON4x7eNgI9KgeifDPIOyuuDygRWnGuKDcQF6roWrGxbSNgzDAgqBuV6ki7z9WT5y4DR0LdoqoVbSOgZhAi7hv4bXzEFarDG7yqbPmsgN2aovhdLdf+2jqBorIE4JGbIugZCvcsijQHr5ojN1tbVaustUWNNP6S0vy60AR+ut4Ndj7CYzdlgL7AixTtmRnRLOToVNAhQeDfR+yPr+iJrIbf/+pM62l0WP8RKNNCSCCTySuJjgT42MTnv4rqjEDvg6J+VH5T8/qynZSW+ts40EgCQtcQDcsDEN2d9IKe4U2hiR5jmG1ERt4QKrS4pkqK2zmRSQMaUsj6D5cuu3C5ppCY6IQLf2dn8CIjZk4Q+N961VnJvlxf+4Z6v/cojvsDYEQL4BXRHVTXfkysjqPfd3tA6YqOnXx293gnLgvV5xFBVa2iv/b6dEX91p+fbf9ImTnxX6gWAadbILARX+0rbEjxohwCFpBVlH1nyJea2KTFY+OduHUGeaFJoNN3gcwr1smu37qsze16y7pQ4a7pYFYj0iuAvho0jbTxJQBkrB6Ky70VGIDqamA9+WmnWtwrZ20/fN4uQA1rss52Slw8wIOZKg4/QIlTKBfY32DttobSZafDJS0Ka3YXYEx6UCXZid8/n6wJRwHIXrsEI7kB6+xHtQTDn8hLPQuipBb2oJOkgSQBPREuAlupq98cfa6mXsXnOFpfV5uQMBRxlMpP/kJpbmP4tNHC3SNBNiLycJAbleLlOXLGVTB33edRLUBkepAuegyRd1D7Pphh10Wr5mJuMT5fNo67L45NQSUlIBqsYlXqkqaiGy51es3eWQh5CDfmUeufTcaE9QBkFY7AyIuImXxT3bfI7doUvQayFbUDMSZYRHiLq64n2JByOXzvBzLGbkJ1GarnP4OPmBt60Or0v+E381Fdj+ILOhYT4p1ADOhTzcA3EdjeXvCtEwDIGNc0J6vNB/0FDXXbmk9uc05wMSYLq+nAgYArtWJ3keYW0eVUVheE9/3ArbYXkND+mbO2H8gk0AnAlGZAb3WlWpQ/I/o6q9JevWMxIyJdWWZ+V4xnOUaebQH4MVS2I/59eO3bFGbWdEiNiVhrmVncH+NfhMg0IA4RH1bfQnQlq9LKwiYnRbTB/9HmKLrWfg2j94HUoI1/Z3XOOe6te+vuWf8DkM0cb7DOQZgAAAAASUVORK5CYII='  # nopep8'
11 41
 
12 42
 
13 43
 def get_redis_connection(config: CFG) -> Redis:
@@ -72,3 +102,26 @@ def current_date_for_filename() -> str:
72 102
     # webdav utils, it may cause trouble. So, it should be replaced to
73 103
     # a character which will not change in bdd.
74 104
     return datetime.datetime.now().isoformat().replace(':', '.')
105
+
106
+# INFO - G.M - 2018-08-02 - Simple password generator, inspired by
107
+# https://gist.github.com/23maverick23/4131896
108
+
109
+
110
+ALLOWED_AUTOGEN_PASSWORD_CHAR = string.ascii_letters + \
111
+                                string.digits + \
112
+                                string.punctuation
113
+
114
+DEFAULT_PASSWORD_GEN_CHAR_LENGTH = 12
115
+
116
+
117
+def password_generator(
118
+        length: int=DEFAULT_PASSWORD_GEN_CHAR_LENGTH,
119
+        chars: str=ALLOWED_AUTOGEN_PASSWORD_CHAR
120
+) -> str:
121
+    """
122
+    :param length: length of the new password
123
+    :param chars: characters allowed
124
+    :return: password as string
125
+    """
126
+    return ''.join(random.choice(chars) for char_number in range(length))
127
+

+ 7 - 5
backend/tracim_backend/lib/webdav/dav_provider.py Целия файл

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

+ 3 - 2
backend/tracim_backend/lib/webdav/design.py Целия файл

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

+ 37 - 34
backend/tracim_backend/lib/webdav/resources.py Целия файл

@@ -19,10 +19,11 @@ from tracim_backend.lib.webdav.utils import transform_to_display, HistoryType, \
19 19
     FakeFileStream
20 20
 from tracim_backend.lib.webdav.utils import transform_to_bdd
21 21
 from tracim_backend.lib.core.workspace import WorkspaceApi
22
+from tracim_backend.models.contents import CONTENT_TYPES
22 23
 from tracim_backend.models.data import User, ContentRevisionRO
23 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 27
 from tracim_backend.lib.webdav.design import designThread, designPage
27 28
 
28 29
 from wsgidav import compat
@@ -236,7 +237,7 @@ class WorkspaceResource(DAVCollection):
236 237
 
237 238
         for content in children:
238 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 241
                 self._file_count += 1
241 242
             retlist.append(content.get_label_as_file())
242 243
 
@@ -288,7 +289,7 @@ class WorkspaceResource(DAVCollection):
288 289
             raise DAVError(HTTP_FORBIDDEN)
289 290
 
290 291
         folder = self.content_api.create(
291
-            content_type=ContentType.Folder,
292
+            content_type_slug=CONTENT_TYPES.Folder.slug,
292 293
             workspace=self.workspace,
293 294
             label=label,
294 295
             parent=self.content
@@ -331,12 +332,12 @@ class WorkspaceResource(DAVCollection):
331 332
     def getMemberList(self) -> [_DAVResource]:
332 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 337
         for content in children:
337 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 341
                 members.append(
341 342
                     FolderResource(
342 343
                         path=content_path,
@@ -347,7 +348,7 @@ class WorkspaceResource(DAVCollection):
347 348
                         session=self.session,
348 349
                     )
349 350
                 )
350
-            elif content.type == ContentType.File:
351
+            elif content.type == CONTENT_TYPES.File.slug:
351 352
                 self._file_count += 1
352 353
                 members.append(
353 354
                     FileResource(
@@ -553,7 +554,7 @@ class FolderResource(WorkspaceResource):
553 554
         )
554 555
         visible_children = content_api.get_all(
555 556
             self.content.content_id,
556
-            ContentType.Any,
557
+            CONTENT_TYPES.Any_SLUG,
557 558
             self.workspace,
558 559
         )
559 560
 
@@ -561,7 +562,7 @@ class FolderResource(WorkspaceResource):
561 562
             content_path = '%s/%s' % (self.path, transform_to_display(content.get_label_as_file()))
562 563
 
563 564
             try:
564
-                if content.type == ContentType.Folder:
565
+                if content.type == CONTENT_TYPES.Folder.slug:
565 566
                     members.append(
566 567
                         FolderResource(
567 568
                             path=content_path,
@@ -572,7 +573,7 @@ class FolderResource(WorkspaceResource):
572 573
                             session=self.session,
573 574
                         )
574 575
                     )
575
-                elif content.type == ContentType.File:
576
+                elif content.type == CONTENT_TYPES.File.slug:
576 577
                     self._file_count += 1
577 578
                     members.append(
578 579
                         FileResource(
@@ -592,13 +593,15 @@ class FolderResource(WorkspaceResource):
592 593
                             user=self.user,
593 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 606
         if self._file_count > 0 and self.provider.show_history():
604 607
             members.append(
@@ -708,11 +711,11 @@ class HistoryFolderResource(FolderResource):
708 711
         ret = []
709 712
 
710 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 715
             if (self._is_archived and content.is_archived or
713 716
                 self._is_deleted and content.is_deleted or
714 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 719
                 ret.append(content.get_label_as_file())
717 720
 
718 721
         return ret
@@ -741,7 +744,7 @@ class HistoryFolderResource(FolderResource):
741 744
         if self.content:
742 745
             children = self.content.children
743 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 749
         for content in children:
747 750
             if content.is_archived == self._is_archived and content.is_deleted == self._is_deleted:
@@ -812,13 +815,13 @@ class DeletedFolderResource(HistoryFolderResource):
812 815
         if self.content:
813 816
             children = self.content.children
814 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 820
         for content in children:
818 821
             if content.is_deleted:
819 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 825
                     self._file_count += 1
823 826
 
824 827
         return retlist
@@ -829,13 +832,13 @@ class DeletedFolderResource(HistoryFolderResource):
829 832
         if self.content:
830 833
             children = self.content.children
831 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 837
         for content in children:
835 838
             if content.is_deleted:
836 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 842
                     members.append(
840 843
                         FolderResource(
841 844
                             content_path,
@@ -845,7 +848,7 @@ class DeletedFolderResource(HistoryFolderResource):
845 848
                             user=self.user,
846 849
                             session=self.session,
847 850
                         ))
848
-                elif content.type == ContentType.File:
851
+                elif content.type == CONTENT_TYPES.File.slug:
849 852
                     self._file_count += 1
850 853
                     members.append(
851 854
                         FileResource(
@@ -936,10 +939,10 @@ class ArchivedFolderResource(HistoryFolderResource):
936 939
         retlist = []
937 940
 
938 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 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 946
                 self._file_count += 1
944 947
 
945 948
         return retlist
@@ -950,13 +953,13 @@ class ArchivedFolderResource(HistoryFolderResource):
950 953
         if self.content:
951 954
             children = self.content.children
952 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 958
         for content in children:
956 959
             if content.is_archived:
957 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 963
                     members.append(
961 964
                         FolderResource(
962 965
                             content_path,
@@ -966,7 +969,7 @@ class ArchivedFolderResource(HistoryFolderResource):
966 969
                             user=self.user,
967 970
                             session=self.session,
968 971
                         ))
969
-                elif content.type == ContentType.File:
972
+                elif content.type == CONTENT_TYPES.File.slug:
970 973
                     self._file_count += 1
971 974
                     members.append(
972 975
                         FileResource(
@@ -1053,7 +1056,7 @@ class HistoryFileFolderResource(HistoryFolderResource):
1053 1056
 
1054 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 1060
             return HistoryFileResource(
1058 1061
                 path='%s%s' % (left_side, transform_to_display(revision.file_name)),
1059 1062
                 environ=self.environ,
@@ -1079,7 +1082,7 @@ class HistoryFileFolderResource(HistoryFolderResource):
1079 1082
 
1080 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 1086
                 members.append(HistoryFileResource(
1084 1087
                     path='%s%s' % (left_side, transform_to_display(content.file_name)),
1085 1088
                     environ=self.environ,
@@ -1425,13 +1428,13 @@ class OtherFileResource(FileResource):
1425 1428
         return filestream
1426 1429
 
1427 1430
     def design(self):
1428
-        if self.content.type == ContentType.Page:
1431
+        if self.content.type == CONTENT_TYPES.Page.slug:
1429 1432
             return designPage(self.content, self.content_revision)
1430 1433
         else:
1431 1434
             return designThread(
1432 1435
                 self.content,
1433 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 Целия файл

@@ -4,12 +4,14 @@ import transaction
4 4
 from os.path import normpath as base_normpath
5 5
 
6 6
 from sqlalchemy.orm import Session
7
+from tracim_backend.models.contents import CONTENT_TYPES
7 8
 from wsgidav import util
8 9
 from wsgidav import compat
9 10
 
10 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 15
 from tracim_backend.models.revision_protection import new_revision
14 16
 
15 17
 
@@ -177,7 +179,7 @@ class FakeFileStream(object):
177 179
 
178 180
         file = self._api.create(
179 181
             filename=self._file_name,
180
-            content_type=ContentType.File,
182
+            content_type_slug=CONTENT_TYPES.File.slug,
181 183
             workspace=self._workspace,
182 184
             parent=self._parent,
183 185
             is_temporary=is_temporary

+ 20 - 2
backend/tracim_backend/models/applications.py Целия файл

@@ -36,6 +36,12 @@ class Application(object):
36 36
         self.config = config
37 37
         self.main_route = main_route
38 38
 
39
+    # TODO - G.M - 2018-08-07 - Refactor slug coherence issue like this one.
40
+    # we probably should not have 2 kind of slug
41
+    @property
42
+    def minislug(self):
43
+        return self.slug.replace('contents/', '')
44
+
39 45
 
40 46
 # default apps
41 47
 calendar = Application(
@@ -59,6 +65,16 @@ thread = Application(
59 65
 
60 66
 )
61 67
 
68
+folder = Application(
69
+    label='Folder',
70
+    slug='contents/folder',
71
+    fa_icon='folder-open-o',
72
+    hexcolor='#252525',
73
+    is_active=True,
74
+    config={},
75
+    main_route='',
76
+)
77
+
62 78
 _file = Application(
63 79
     label='Files',
64 80
     slug='contents/file',
@@ -92,8 +108,10 @@ html_documents = Application(
92 108
 # List of applications
93 109
 applications = [
94 110
     html_documents,
95
-    markdownpluspage,
111
+    # TODO - G.M - 2018-08-02 - Restore markdownpage app
112
+    # markdownpluspage,
96 113
     _file,
97 114
     thread,
98
-    calendar,
115
+    folder,
116
+    # calendar,
99 117
 ]

+ 113 - 146
backend/tracim_backend/models/contents.py Целия файл

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

+ 34 - 9
backend/tracim_backend/models/context_models.py Целия файл

@@ -5,8 +5,11 @@ from enum import Enum
5 5
 
6 6
 from slugify import slugify
7 7
 from sqlalchemy.orm import Session
8
-from tracim_backend import CFG
8
+from tracim_backend.config import CFG
9 9
 from tracim_backend.config import PreviewDim
10
+from tracim_backend.lib.utils.utils import get_root_frontend_url
11
+from tracim_backend.lib.utils.utils import CONTENT_FRONTEND_URL_SCHEMA
12
+from tracim_backend.lib.utils.utils import WORKSPACE_FRONTEND_URL_SCHEMA
10 13
 from tracim_backend.models import User
11 14
 from tracim_backend.models.auth import Profile
12 15
 from tracim_backend.models.data import Content
@@ -14,9 +17,9 @@ from tracim_backend.models.data import ContentRevisionRO
14 17
 from tracim_backend.models.data import Workspace
15 18
 from tracim_backend.models.data import UserRoleInWorkspace
16 19
 from tracim_backend.models.roles import WorkspaceRoles
17
-from tracim_backend.models.workspace_menu_entries import default_workspace_menu_entry
20
+from tracim_backend.models.workspace_menu_entries import default_workspace_menu_entry  # nopep8
18 21
 from tracim_backend.models.workspace_menu_entries import WorkspaceMenuEntry
19
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
22
+from tracim_backend.models.contents import CONTENT_TYPES
20 23
 
21 24
 
22 25
 class PreviewAllowedDim(object):
@@ -186,6 +189,14 @@ class CommentPath(object):
186 189
         self.comment_id = comment_id
187 190
 
188 191
 
192
+class AutocompleteQuery(object):
193
+    """
194
+    Autocomplete query model
195
+    """
196
+    def __init__(self, acp: str):
197
+        self.acp = acp
198
+
199
+
189 200
 class PageQuery(object):
190 201
     """
191 202
     Page query model
@@ -454,6 +465,14 @@ class WorkspaceInContext(object):
454 465
         # apps)
455 466
         return default_workspace_menu_entry(self.workspace)
456 467
 
468
+    @property
469
+    def frontend_url(self):
470
+        root_frontend_url = get_root_frontend_url(self.config)
471
+        workspace_frontend_url = WORKSPACE_FRONTEND_URL_SCHEMA.format(
472
+            workspace_id=self.workspace_id,
473
+        )
474
+        return root_frontend_url + workspace_frontend_url
475
+
457 476
 
458 477
 class UserRoleWorkspaceInContext(object):
459 478
     """
@@ -578,7 +597,7 @@ class ContentInContext(object):
578 597
 
579 598
     @property
580 599
     def content_type(self) -> str:
581
-        content_type = ContentType(self.content.type)
600
+        content_type = CONTENT_TYPES.get_one_by_slug(self.content.type)
582 601
         return content_type.slug
583 602
 
584 603
     @property
@@ -652,6 +671,16 @@ class ContentInContext(object):
652 671
         assert self._user
653 672
         return not self.content.has_new_information_for(self._user)
654 673
 
674
+    @property
675
+    def frontend_url(self):
676
+        root_frontend_url = get_root_frontend_url(self.config)
677
+        content_frontend_url = CONTENT_FRONTEND_URL_SCHEMA.format(
678
+            workspace_id=self.workspace_id,
679
+            content_type=self.content_type,
680
+            content_id=self.content_id,
681
+        )
682
+        return root_frontend_url + content_frontend_url
683
+
655 684
 
656 685
 class RevisionInContext(object):
657 686
     """
@@ -690,11 +719,7 @@ class RevisionInContext(object):
690 719
 
691 720
     @property
692 721
     def content_type(self) -> str:
693
-        content_type = ContentType(self.revision.type)
694
-        if content_type:
695
-            return content_type.slug
696
-        else:
697
-            return None
722
+        return CONTENT_TYPES.get_one_by_slug(self.revision.type).slug
698 723
 
699 724
     @property
700 725
     def sub_content_types(self) -> typing.List[str]:

+ 26 - 28
backend/tracim_backend/models/data.py Целия файл

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

+ 3 - 1
backend/tracim_backend/templates/mail/content_update_body_html.mak Целия файл

@@ -45,7 +45,7 @@
45 45
             ${main_title}
46 46
             &mdash;&nbsp;<span style="font-weight: bold; color: #999; font-weight: bold;">
47 47
               ${status_label|n}
48
-              <img alt="status_icon" src="${status_icon_url}" style="vertical-align: middle;">
48
+              <img alt="" src="${status_icon_url}" style="vertical-align: middle;">
49 49
             </span>
50 50
         </td>
51 51
       </tr>
@@ -55,6 +55,8 @@
55 55
     <div id="content-body">
56 56
         <div>${content_text|n}</div>
57 57
         <div href='' id="call-to-action-container">
58
+            <span style=""> <a href="${call_to_action_url}"
59
+            id="call-to-action-button">${call_to_action_text}</a> </span> </div>
58 60
         </div>
59 61
     </div>
60 62
     

+ 1 - 2
backend/tracim_backend/templates/mail/created_account_body_html.mak Целия файл

@@ -68,10 +68,9 @@
68 68
         </div>
69 69
         <div id="call-to-action-container">
70 70
 
71
-            ${_('To go to {website_title}, please click on following link'.format(
71
+            ${_('To go to {website_title}, please click on following link :'.format(
72 72
                 website_title=config.WEBSITE_TITLE
73 73
             ))}
74
-
75 74
             <span style="">
76 75
                 <a href="${login_url}" id='call-to-action-button'>${login_url}</a>
77 76
             </span>

+ 11 - 14
backend/tracim_backend/tests/__init__.py Целия файл

@@ -14,8 +14,8 @@ from tracim_backend.models import get_engine
14 14
 from tracim_backend.models import DeclarativeBase
15 15
 from tracim_backend.models import get_session_factory
16 16
 from tracim_backend.models import get_tm_session
17
+from tracim_backend.models.contents import CONTENT_TYPES
17 18
 from tracim_backend.models.data import Workspace
18
-from tracim_backend.models.data import ContentType
19 19
 from tracim_backend.models.data import ContentRevisionRO
20 20
 from tracim_backend.models.data import Content
21 21
 from tracim_backend.lib.utils.logger import logger
@@ -68,20 +68,17 @@ def create_1000px_png_test_image():
68 68
 class FunctionalTest(unittest.TestCase):
69 69
 
70 70
     fixtures = [BaseFixture]
71
-    sqlalchemy_url = 'sqlite:///tracim_test.sqlite'
71
+    config_uri = 'tests_configs.ini'
72
+    config_section = 'functional_test'
72 73
 
73 74
     def setUp(self):
74 75
         logger._logger.setLevel('WARNING')
76
+
75 77
         DepotManager._clear()
76
-        self.settings = {
77
-            'sqlalchemy.url': self.sqlalchemy_url,
78
-            'user.auth_token.validity': '604800',
79
-            'depot_storage_dir': '/tmp/test/depot',
80
-            'depot_storage_name': 'test',
81
-            'preview_cache_dir': '/tmp/test/preview_cache',
82
-            'preview.jpg.restricted_dims': True,
83
-            'email.notification.activated': 'false',
84
-        }
78
+        self.settings = plaster.get_settings(
79
+            self.config_uri,
80
+            self.config_section
81
+        )
85 82
         hapic.reset_context()
86 83
         self.engine = get_engine(self.settings)
87 84
         DeclarativeBase.metadata.create_all(self.engine)
@@ -127,7 +124,7 @@ class FunctionalTestEmptyDB(FunctionalTest):
127 124
 
128 125
 
129 126
 class FunctionalTestNoDB(FunctionalTest):
130
-    sqlalchemy_url = 'sqlite://'
127
+    config_section = 'functional_test_no_db'
131 128
 
132 129
     def init_database(self, settings):
133 130
         self.engine = get_engine(settings)
@@ -268,13 +265,13 @@ class DefaultTest(StandardTest):
268 265
         workspace = self._create_workspace_and_test(workspace_name, user)
269 266
         folder = self._create_content_and_test(
270 267
             folder_name, workspace,
271
-            type=ContentType.Folder,
268
+            type=CONTENT_TYPES.Folder.slug,
272 269
             owner=user
273 270
         )
274 271
         thread = self._create_content_and_test(
275 272
             thread_name,
276 273
             workspace,
277
-            type=ContentType.Thread,
274
+            type=CONTENT_TYPES.Thread.slug,
278 275
             parent=folder,
279 276
             owner=user
280 277
         )

+ 85 - 38
backend/tracim_backend/tests/functional/test_contents.py Целия файл

@@ -5,7 +5,7 @@ from tracim_backend import models
5 5
 from tracim_backend.lib.core.content import ContentApi
6 6
 from tracim_backend.lib.core.workspace import WorkspaceApi
7 7
 from tracim_backend.models import get_tm_session
8
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
8
+from tracim_backend.models.contents import CONTENT_TYPES
9 9
 from tracim_backend.models.revision_protection import new_revision
10 10
 import io
11 11
 
@@ -16,7 +16,6 @@ from depot.io.utils import FileIntent
16 16
 from tracim_backend import models
17 17
 from tracim_backend.lib.core.content import ContentApi
18 18
 from tracim_backend.lib.core.workspace import WorkspaceApi
19
-from tracim_backend.models.data import ContentType
20 19
 from tracim_backend.models import get_tm_session
21 20
 from tracim_backend.models.revision_protection import new_revision
22 21
 from tracim_backend.tests import FunctionalTest
@@ -117,6 +116,54 @@ class TestHtmlDocuments(FunctionalTest):
117 116
         assert content['last_modifier']['avatar_url'] is None
118 117
         assert content['raw_content'] == '<p>To cook a great Tiramisu, you need many ingredients.</p>'  # nopep8
119 118
 
119
+    def test_api__get_html_document__ok_200__archived_content(self) -> None:
120
+        """
121
+        Get one html document of a content
122
+        """
123
+        self.testapp.authorization = (
124
+            'Basic',
125
+            (
126
+                'admin@admin.admin',
127
+                'admin@admin.admin'
128
+            )
129
+        )
130
+        res = self.testapp.put_json(
131
+            '/api/v2/workspaces/2/contents/6/archive',
132
+            status=204
133
+        )
134
+        res = self.testapp.get(
135
+            '/api/v2/workspaces/2/html-documents/6',
136
+            status=200
137
+        )
138
+        content = res.json_body
139
+        assert content['content_type'] == 'html-document'
140
+        assert content['content_id'] == 6
141
+        assert content['is_archived'] is True
142
+
143
+    def test_api__get_html_document__ok_200__deleted_content(self) -> None:
144
+        """
145
+        Get one html document of a content
146
+        """
147
+        self.testapp.authorization = (
148
+            'Basic',
149
+            (
150
+                'admin@admin.admin',
151
+                'admin@admin.admin'
152
+            )
153
+        )
154
+        res = self.testapp.put_json(
155
+            '/api/v2/workspaces/2/contents/6/delete',
156
+            status=204
157
+        )
158
+        res = self.testapp.get(
159
+            '/api/v2/workspaces/2/html-documents/6',
160
+            status=200
161
+        )
162
+        content = res.json_body
163
+        assert content['content_type'] == 'html-document'
164
+        assert content['content_id'] == 6
165
+        assert content['is_deleted'] is True
166
+
120 167
     def test_api__get_html_document__err_400__wrong_content_type(self) -> None:
121 168
         """
122 169
         Get one html document of a content content 7 is not html_document
@@ -480,9 +527,9 @@ class TestFiles(FunctionalTest):
480 527
             config=self.app_config
481 528
         )
482 529
         business_workspace = workspace_api.get_one(1)
483
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
530
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
484 531
         test_file = content_api.create(
485
-            content_type=ContentType.File,
532
+            content_type_slug=CONTENT_TYPES.File.slug,
486 533
             workspace=business_workspace,
487 534
             parent=tool_folder,
488 535
             label='Test file',
@@ -648,9 +695,9 @@ class TestFiles(FunctionalTest):
648 695
             config=self.app_config
649 696
         )
650 697
         business_workspace = workspace_api.get_one(1)
651
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
698
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
652 699
         test_file = content_api.create(
653
-            content_type=ContentType.File,
700
+            content_type_slug=CONTENT_TYPES.File.slug,
654 701
             workspace=business_workspace,
655 702
             parent=tool_folder,
656 703
             label='Test file',
@@ -703,9 +750,9 @@ class TestFiles(FunctionalTest):
703 750
             config=self.app_config
704 751
         )
705 752
         business_workspace = workspace_api.get_one(1)
706
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
753
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
707 754
         test_file = content_api.create(
708
-            content_type=ContentType.File,
755
+            content_type_slug=CONTENT_TYPES.File.slug,
709 756
             workspace=business_workspace,
710 757
             parent=tool_folder,
711 758
             label='Test file',
@@ -809,9 +856,9 @@ class TestFiles(FunctionalTest):
809 856
             config=self.app_config
810 857
         )
811 858
         business_workspace = workspace_api.get_one(1)
812
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
859
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
813 860
         test_file = content_api.create(
814
-            content_type=ContentType.File,
861
+            content_type_slug=CONTENT_TYPES.File.slug,
815 862
             workspace=business_workspace,
816 863
             parent=tool_folder,
817 864
             label='Test file',
@@ -882,9 +929,9 @@ class TestFiles(FunctionalTest):
882 929
             config=self.app_config
883 930
         )
884 931
         business_workspace = workspace_api.get_one(1)
885
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
932
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
886 933
         test_file = content_api.create(
887
-            content_type=ContentType.File,
934
+            content_type_slug=CONTENT_TYPES.File.slug,
888 935
             workspace=business_workspace,
889 936
             parent=tool_folder,
890 937
             label='Test file',
@@ -978,9 +1025,9 @@ class TestFiles(FunctionalTest):
978 1025
             config=self.app_config
979 1026
         )
980 1027
         business_workspace = workspace_api.get_one(1)
981
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1028
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
982 1029
         test_file = content_api.create(
983
-            content_type=ContentType.File,
1030
+            content_type_slug=CONTENT_TYPES.File.slug,
984 1031
             workspace=business_workspace,
985 1032
             parent=tool_folder,
986 1033
             label='Test file',
@@ -1031,9 +1078,9 @@ class TestFiles(FunctionalTest):
1031 1078
             config=self.app_config
1032 1079
         )
1033 1080
         business_workspace = workspace_api.get_one(1)
1034
-        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)
1035 1082
         test_file = content_api.create(
1036
-            content_type=ContentType.File,
1083
+            content_type_slug=CONTENT_TYPES.File.slug,
1037 1084
             workspace=business_workspace,
1038 1085
             parent=tool_folder,
1039 1086
             label='Test file',
@@ -1082,9 +1129,9 @@ class TestFiles(FunctionalTest):
1082 1129
             config=self.app_config
1083 1130
         )
1084 1131
         business_workspace = workspace_api.get_one(1)
1085
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1132
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1086 1133
         test_file = content_api.create(
1087
-            content_type=ContentType.File,
1134
+            content_type_slug=CONTENT_TYPES.File.slug,
1088 1135
             workspace=business_workspace,
1089 1136
             parent=tool_folder,
1090 1137
             label='Test file',
@@ -1137,9 +1184,9 @@ class TestFiles(FunctionalTest):
1137 1184
             config=self.app_config
1138 1185
         )
1139 1186
         business_workspace = workspace_api.get_one(1)
1140
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1187
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1141 1188
         test_file = content_api.create(
1142
-            content_type=ContentType.File,
1189
+            content_type_slug=CONTENT_TYPES.File.slug,
1143 1190
             workspace=business_workspace,
1144 1191
             parent=tool_folder,
1145 1192
             label='Test file',
@@ -1196,9 +1243,9 @@ class TestFiles(FunctionalTest):
1196 1243
             config=self.app_config
1197 1244
         )
1198 1245
         business_workspace = workspace_api.get_one(1)
1199
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1246
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1200 1247
         test_file = content_api.create(
1201
-            content_type=ContentType.File,
1248
+            content_type_slug=CONTENT_TYPES.File.slug,
1202 1249
             workspace=business_workspace,
1203 1250
             parent=tool_folder,
1204 1251
             label='Test file',
@@ -1251,9 +1298,9 @@ class TestFiles(FunctionalTest):
1251 1298
             config=self.app_config
1252 1299
         )
1253 1300
         business_workspace = workspace_api.get_one(1)
1254
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1301
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1255 1302
         test_file = content_api.create(
1256
-            content_type=ContentType.File,
1303
+            content_type_slug=CONTENT_TYPES.File.slug,
1257 1304
             workspace=business_workspace,
1258 1305
             parent=tool_folder,
1259 1306
             label='Test file',
@@ -1302,9 +1349,9 @@ class TestFiles(FunctionalTest):
1302 1349
             config=self.app_config
1303 1350
         )
1304 1351
         business_workspace = workspace_api.get_one(1)
1305
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1352
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1306 1353
         test_file = content_api.create(
1307
-            content_type=ContentType.File,
1354
+            content_type_slug=CONTENT_TYPES.File.slug,
1308 1355
             workspace=business_workspace,
1309 1356
             parent=tool_folder,
1310 1357
             label='Test file',
@@ -1375,9 +1422,9 @@ class TestFiles(FunctionalTest):
1375 1422
             config=self.app_config
1376 1423
         )
1377 1424
         business_workspace = workspace_api.get_one(1)
1378
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1425
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1379 1426
         test_file = content_api.create(
1380
-            content_type=ContentType.File,
1427
+            content_type_slug=CONTENT_TYPES.File.slug,
1381 1428
             workspace=business_workspace,
1382 1429
             parent=tool_folder,
1383 1430
             label='Test file',
@@ -1438,9 +1485,9 @@ class TestFiles(FunctionalTest):
1438 1485
             config=self.app_config
1439 1486
         )
1440 1487
         business_workspace = workspace_api.get_one(1)
1441
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1488
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1442 1489
         test_file = content_api.create(
1443
-            content_type=ContentType.File,
1490
+            content_type_slug=CONTENT_TYPES.File.slug,
1444 1491
             workspace=business_workspace,
1445 1492
             parent=tool_folder,
1446 1493
             label='Test file',
@@ -1489,9 +1536,9 @@ class TestFiles(FunctionalTest):
1489 1536
             config=self.app_config
1490 1537
         )
1491 1538
         business_workspace = workspace_api.get_one(1)
1492
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1539
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1493 1540
         test_file = content_api.create(
1494
-            content_type=ContentType.File,
1541
+            content_type_slug=CONTENT_TYPES.File.slug,
1495 1542
             workspace=business_workspace,
1496 1543
             parent=tool_folder,
1497 1544
             label='Test file',
@@ -1554,9 +1601,9 @@ class TestFiles(FunctionalTest):
1554 1601
             config=self.app_config
1555 1602
         )
1556 1603
         business_workspace = workspace_api.get_one(1)
1557
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1604
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1558 1605
         test_file = content_api.create(
1559
-            content_type=ContentType.File,
1606
+            content_type_slug=CONTENT_TYPES.File.slug,
1560 1607
             workspace=business_workspace,
1561 1608
             parent=tool_folder,
1562 1609
             label='Test file',
@@ -1618,9 +1665,9 @@ class TestFiles(FunctionalTest):
1618 1665
             config=self.app_config
1619 1666
         )
1620 1667
         business_workspace = workspace_api.get_one(1)
1621
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1668
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1622 1669
         test_file = content_api.create(
1623
-            content_type=ContentType.File,
1670
+            content_type_slug=CONTENT_TYPES.File.slug,
1624 1671
             workspace=business_workspace,
1625 1672
             parent=tool_folder,
1626 1673
             label='Test file',
@@ -1989,9 +2036,9 @@ class TestThreads(FunctionalTest):
1989 2036
             session=dbsession,
1990 2037
             config=self.app_config
1991 2038
         )
1992
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
2039
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1993 2040
         test_thread = content_api.create(
1994
-            content_type=ContentType.Thread,
2041
+            content_type_slug=CONTENT_TYPES.Thread.slug,
1995 2042
             workspace=business_workspace,
1996 2043
             parent=tool_folder,
1997 2044
             label='Test Thread',

+ 5 - 5
backend/tracim_backend/tests/functional/test_mail_notification.py Целия файл

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

+ 25 - 72
backend/tracim_backend/tests/functional/test_system.py Целия файл

@@ -1,10 +1,13 @@
1 1
 # coding=utf-8
2
+from tracim_backend.models.contents import CONTENT_TYPES
2 3
 from tracim_backend.tests import FunctionalTest
4
+from tracim_backend.models.applications import applications
3 5
 
4 6
 """
5 7
 Tests for /api/v2/system subpath endpoints.
6 8
 """
7 9
 
10
+
8 11
 class TestApplicationEndpoint(FunctionalTest):
9 12
     """
10 13
     Tests for /api/v2/system/applications
@@ -23,41 +26,14 @@ class TestApplicationEndpoint(FunctionalTest):
23 26
         )
24 27
         res = self.testapp.get('/api/v2/system/applications', status=200)
25 28
         res = res.json_body
26
-        application = res[0]
27
-        assert application['label'] == "Text Documents"
28
-        assert application['slug'] == 'contents/html-document'
29
-        assert application['fa_icon'] == 'file-text-o'
30
-        assert application['hexcolor'] == '#3f52e3'
31
-        assert application['is_active'] is True
32
-        assert 'config' in application
33
-        application = res[1]
34
-        assert application['label'] == "Markdown Plus Documents"
35
-        assert application['slug'] == 'contents/markdownpluspage'
36
-        assert application['fa_icon'] == 'file-code-o'
37
-        assert application['hexcolor'] == '#f12d2d'
38
-        assert application['is_active'] is True
39
-        assert 'config' in application
40
-        application = res[2]
41
-        assert application['label'] == "Files"
42
-        assert application['slug'] == 'contents/file'
43
-        assert application['fa_icon'] == 'paperclip'
44
-        assert application['hexcolor'] == '#FF9900'
45
-        assert application['is_active'] is True
46
-        assert 'config' in application
47
-        application = res[3]
48
-        assert application['label'] == "Threads"
49
-        assert application['slug'] == 'contents/thread'
50
-        assert application['fa_icon'] == 'comments-o'
51
-        assert application['hexcolor'] == '#ad4cf9'
52
-        assert application['is_active'] is True
53
-        assert 'config' in application
54
-        application = res[4]
55
-        assert application['label'] == "Calendar"
56
-        assert application['slug'] == 'calendar'
57
-        assert application['fa_icon'] == 'calendar'
58
-        assert application['hexcolor'] == '#757575'
59
-        assert application['is_active'] is True
60
-        assert 'config' in application
29
+        assert len(res) == len(applications)
30
+        for counter, application in enumerate(applications):
31
+            assert res[counter]['label'] == application.label
32
+            assert res[counter]['slug'] == application.slug
33
+            assert res[counter]['fa_icon'] == application.fa_icon
34
+            assert res[counter]['hexcolor'] == application.hexcolor
35
+            assert res[counter]['is_active'] == application.is_active
36
+            assert res[counter]['config'] == application.config
61 37
 
62 38
     def test_api__get_applications__err_401__unregistered_user(self):
63 39
         """
@@ -95,44 +71,21 @@ class TestContentsTypesEndpoint(FunctionalTest):
95 71
         )
96 72
         res = self.testapp.get('/api/v2/system/content_types', status=200)
97 73
         res = res.json_body
74
+        assert len(res) == len(CONTENT_TYPES.endpoint_allowed_types_slug())
75
+        content_types = CONTENT_TYPES.endpoint_allowed_types_slug()
98 76
 
99
-        content_type = res[0]
100
-        assert content_type['slug'] == 'thread'
101
-        assert content_type['fa_icon'] == 'comments-o'
102
-        assert content_type['hexcolor'] == '#ad4cf9'
103
-        assert content_type['label'] == 'Thread'
104
-        assert content_type['creation_label'] == 'Discuss about a topic'
105
-        assert 'available_statuses' in content_type
106
-        assert len(content_type['available_statuses']) == 4
107
-
108
-        content_type = res[1]
109
-        assert content_type['slug'] == 'file'
110
-        assert content_type['fa_icon'] == 'paperclip'
111
-        assert content_type['hexcolor'] == '#FF9900'
112
-        assert content_type['label'] == 'File'
113
-        assert content_type['creation_label'] == 'Upload a file'
114
-        assert 'available_statuses' in content_type
115
-        assert len(content_type['available_statuses']) == 4
116
-
117
-        content_type = res[2]
118
-        assert content_type['slug'] == 'markdownpage'
119
-        assert content_type['fa_icon'] == 'file-code-o'
120
-        assert content_type['hexcolor'] == '#f12d2d'
121
-        assert content_type['label'] == 'Rich Markdown File'
122
-        assert content_type['creation_label'] == 'Create a Markdown document'
123
-        assert 'available_statuses' in content_type
124
-        assert len(content_type['available_statuses']) == 4
125
-
126
-        content_type = res[3]
127
-        assert content_type['slug'] == 'html-document'
128
-        assert content_type['fa_icon'] == 'file-text-o'
129
-        assert content_type['hexcolor'] == '#3f52e3'
130
-        assert content_type['label'] == 'Text Document'
131
-        assert content_type['creation_label'] == 'Write a document'
132
-        assert 'available_statuses' in content_type
133
-        assert len(content_type['available_statuses']) == 4
134
-        # TODO - G.M - 31-05-2018 - Check Folder type
135
-        # TODO - G.M - 29-05-2018 - Better check for available_statuses
77
+        for counter, content_type_slug in enumerate(content_types):
78
+            content_type = CONTENT_TYPES.get_one_by_slug(content_type_slug)
79
+            assert res[counter]['slug'] == content_type.slug
80
+            assert res[counter]['fa_icon'] == content_type.fa_icon
81
+            assert res[counter]['hexcolor'] == content_type.hexcolor
82
+            assert res[counter]['label'] == content_type.label
83
+            assert res[counter]['creation_label'] == content_type.creation_label
84
+            for status_counter, status in enumerate(content_type.available_statuses):
85
+                assert res[counter]['available_statuses'][status_counter]['fa_icon'] == status.fa_icon  # nopep8
86
+                assert res[counter]['available_statuses'][status_counter]['global_status'] == status.global_status  # nopep8
87
+                assert res[counter]['available_statuses'][status_counter]['slug'] == status.slug  # nopep8
88
+                assert res[counter]['available_statuses'][status_counter]['hexcolor'] == status.hexcolor  # nopep8
136 89
 
137 90
     def test_api__get_content_types__err_401__unregistered_user(self):
138 91
         """

+ 525 - 127
backend/tracim_backend/tests/functional/test_user.py Целия файл

@@ -13,7 +13,7 @@ from tracim_backend.lib.core.group import GroupApi
13 13
 from tracim_backend.lib.core.userworkspace import RoleApi
14 14
 from tracim_backend.lib.core.workspace import WorkspaceApi
15 15
 from tracim_backend.models import get_tm_session
16
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
16
+from tracim_backend.models.contents import CONTENT_TYPES
17 17
 from tracim_backend.models.data import UserRoleInWorkspace
18 18
 from tracim_backend.models.revision_protection import new_revision
19 19
 from tracim_backend.tests import FunctionalTest
@@ -87,14 +87,14 @@ class TestUserRecentlyActiveContentEndpoint(FunctionalTest):
87 87
             session=dbsession,
88 88
             config=self.app_config,
89 89
         )
90
-        main_folder_workspace2 = api.create(ContentType.Folder, workspace2, None, 'Hepla', '', True)  # nopep8
91
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
90
+        main_folder_workspace2 = api.create(CONTENT_TYPES.Folder.slug, workspace2, None, 'Hepla', '', True)  # nopep8
91
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
92 92
         # creation order test
93
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
94
-        secondly_created = api.create(ContentType.Page, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
93
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
94
+        secondly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
95 95
         # update order test
96
-        firstly_created_but_recently_updated = api.create(ContentType.Page, workspace, main_folder, 'update_order_test', '', True)  # nopep8
97
-        secondly_created_but_not_updated = api.create(ContentType.Page, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
96
+        firstly_created_but_recently_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'update_order_test', '', True)  # nopep8
97
+        secondly_created_but_not_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
98 98
         with new_revision(
99 99
             session=dbsession,
100 100
             tm=transaction.manager,
@@ -103,10 +103,10 @@ class TestUserRecentlyActiveContentEndpoint(FunctionalTest):
103 103
             firstly_created_but_recently_updated.description = 'Just an update'
104 104
         api.save(firstly_created_but_recently_updated)
105 105
         # comment change order
106
-        firstly_created_but_recently_commented = api.create(ContentType.Page, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
107
-        secondly_created_but_not_commented = api.create(ContentType.Page, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
106
+        firstly_created_but_recently_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
107
+        secondly_created_but_not_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
108 108
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
109
-        content_workspace_2 = api.create(ContentType.Page, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
109
+        content_workspace_2 = api.create(CONTENT_TYPES.Page.slug, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
110 110
         dbsession.flush()
111 111
         transaction.commit()
112 112
 
@@ -205,14 +205,15 @@ class TestUserRecentlyActiveContentEndpoint(FunctionalTest):
205 205
             session=dbsession,
206 206
             config=self.app_config,
207 207
         )
208
-        main_folder_workspace2 = api.create(ContentType.Folder, workspace2, None, 'Hepla', '', True)  # nopep8
209
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
208
+        main_folder_workspace2 = api.create(CONTENT_TYPES.Folder.slug, workspace2, None, 'Hepla', '', True)  # nopep8
209
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
210 210
         # creation order test
211
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
212
-        secondly_created = api.create(ContentType.Page, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
211
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
212
+        secondly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
213 213
         # update order test
214
-        firstly_created_but_recently_updated = api.create(ContentType.Page, workspace, main_folder, 'update_order_test', '', True)  # nopep8
215
-        secondly_created_but_not_updated = api.create(ContentType.Page, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
214
+        firstly_created_but_recently_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'update_order_test', '', True)  # nopep8
215
+        secondly_created_but_not_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
216
+
216 217
         with new_revision(
217 218
             session=dbsession,
218 219
             tm=transaction.manager,
@@ -221,10 +222,10 @@ class TestUserRecentlyActiveContentEndpoint(FunctionalTest):
221 222
             firstly_created_but_recently_updated.description = 'Just an update'
222 223
         api.save(firstly_created_but_recently_updated)
223 224
         # comment change order
224
-        firstly_created_but_recently_commented = api.create(ContentType.Page, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
225
-        secondly_created_but_not_commented = api.create(ContentType.Page, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
225
+        firstly_created_but_recently_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
226
+        secondly_created_but_not_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
226 227
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
227
-        content_workspace_2 = api.create(ContentType.Page, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
228
+        content_workspace_2 = api.create(CONTENT_TYPES.Page.slug, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
228 229
         dbsession.flush()
229 230
         transaction.commit()
230 231
 
@@ -301,14 +302,14 @@ class TestUserRecentlyActiveContentEndpoint(FunctionalTest):
301 302
             session=dbsession,
302 303
             config=self.app_config,
303 304
         )
304
-        main_folder_workspace2 = api.create(ContentType.Folder, workspace2, None, 'Hepla', '', True)  # nopep8
305
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
305
+        main_folder_workspace2 = api.create(CONTENT_TYPES.Folder.slug, workspace2, None, 'Hepla', '', True)  # nopep8
306
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
306 307
         # creation order test
307
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
308
-        secondly_created = api.create(ContentType.Page, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
308
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
309
+        secondly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
309 310
         # update order test
310
-        firstly_created_but_recently_updated = api.create(ContentType.Page, workspace, main_folder, 'update_order_test', '', True)  # nopep8
311
-        secondly_created_but_not_updated = api.create(ContentType.Page, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
311
+        firstly_created_but_recently_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'update_order_test', '', True)  # nopep8
312
+        secondly_created_but_not_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
312 313
         with new_revision(
313 314
             session=dbsession,
314 315
             tm=transaction.manager,
@@ -317,10 +318,10 @@ class TestUserRecentlyActiveContentEndpoint(FunctionalTest):
317 318
             firstly_created_but_recently_updated.description = 'Just an update'
318 319
         api.save(firstly_created_but_recently_updated)
319 320
         # comment change order
320
-        firstly_created_but_recently_commented = api.create(ContentType.Page, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
321
-        secondly_created_but_not_commented = api.create(ContentType.Page, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
321
+        firstly_created_but_recently_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
322
+        secondly_created_but_not_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
322 323
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
323
-        content_workspace_2 = api.create(ContentType.Page, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
324
+        content_workspace_2 = api.create(CONTENT_TYPES.Page.slug, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
324 325
         dbsession.flush()
325 326
         transaction.commit()
326 327
 
@@ -426,14 +427,14 @@ class TestUserRecentlyActiveContentEndpoint(FunctionalTest):
426 427
             session=dbsession,
427 428
             config=self.app_config,
428 429
         )
429
-        main_folder_workspace2 = api.create(ContentType.Folder, workspace2, None, 'Hepla', '', True)  # nopep8
430
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
430
+        main_folder_workspace2 = api.create(CONTENT_TYPES.Folder.slug, workspace2, None, 'Hepla', '', True)  # nopep8
431
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
431 432
         # creation order test
432
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
433
-        secondly_created = api.create(ContentType.Page, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
433
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
434
+        secondly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
434 435
         # update order test
435
-        firstly_created_but_recently_updated = api.create(ContentType.Page, workspace, main_folder, 'update_order_test', '', True)  # nopep8
436
-        secondly_created_but_not_updated = api.create(ContentType.Page, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
436
+        firstly_created_but_recently_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'update_order_test', '', True)  # nopep8
437
+        secondly_created_but_not_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
437 438
         with new_revision(
438 439
             session=dbsession,
439 440
             tm=transaction.manager,
@@ -442,10 +443,10 @@ class TestUserRecentlyActiveContentEndpoint(FunctionalTest):
442 443
             firstly_created_but_recently_updated.description = 'Just an update'
443 444
         api.save(firstly_created_but_recently_updated)
444 445
         # comment change order
445
-        firstly_created_but_recently_commented = api.create(ContentType.Page, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
446
-        secondly_created_but_not_commented = api.create(ContentType.Page, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
446
+        firstly_created_but_recently_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
447
+        secondly_created_but_not_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
447 448
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
448
-        content_workspace_2 = api.create(ContentType.Page, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
449
+        content_workspace_2 = api.create(CONTENT_TYPES.Page.slug, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
449 450
         dbsession.flush()
450 451
         transaction.commit()
451 452
 
@@ -498,14 +499,14 @@ class TestUserRecentlyActiveContentEndpoint(FunctionalTest):
498 499
             session=dbsession,
499 500
             config=self.app_config,
500 501
         )
501
-        main_folder_workspace2 = api.create(ContentType.Folder, workspace2, None, 'Hepla', '', True)  # nopep8
502
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
502
+        main_folder_workspace2 = api.create(CONTENT_TYPES.Folder.slug, workspace2, None, 'Hepla', '', True)  # nopep8
503
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
503 504
         # creation order test
504
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
505
-        secondly_created = api.create(ContentType.Page, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
505
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
506
+        secondly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
506 507
         # update order test
507
-        firstly_created_but_recently_updated = api.create(ContentType.Page, workspace, main_folder, 'update_order_test', '', True)  # nopep8
508
-        secondly_created_but_not_updated = api.create(ContentType.Page, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
508
+        firstly_created_but_recently_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'update_order_test', '', True)  # nopep8
509
+        secondly_created_but_not_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
509 510
         with new_revision(
510 511
             session=dbsession,
511 512
             tm=transaction.manager,
@@ -514,10 +515,10 @@ class TestUserRecentlyActiveContentEndpoint(FunctionalTest):
514 515
             firstly_created_but_recently_updated.description = 'Just an update'
515 516
         api.save(firstly_created_but_recently_updated)
516 517
         # comment change order
517
-        firstly_created_but_recently_commented = api.create(ContentType.Page, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
518
-        secondly_created_but_not_commented = api.create(ContentType.Page, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
518
+        firstly_created_but_recently_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
519
+        secondly_created_but_not_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
519 520
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
520
-        content_workspace_2 = api.create(ContentType.Page, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
521
+        content_workspace_2 = api.create(CONTENT_TYPES.Page.slug, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
521 522
         dbsession.flush()
522 523
         transaction.commit()
523 524
 
@@ -610,14 +611,14 @@ class TestUserRecentlyActiveContentEndpoint(FunctionalTest):
610 611
             session=dbsession,
611 612
             config=self.app_config,
612 613
         )
613
-        main_folder_workspace2 = api.create(ContentType.Folder, workspace2, None, 'Hepla', '', True)  # nopep8
614
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
614
+        main_folder_workspace2 = api.create(CONTENT_TYPES.Folder.slug, workspace2, None, 'Hepla', '', True)  # nopep8
615
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
615 616
         # creation order test
616
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
617
-        secondly_created = api.create(ContentType.Page, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
617
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
618
+        secondly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
618 619
         # update order test
619
-        firstly_created_but_recently_updated = api.create(ContentType.Page, workspace, main_folder, 'update_order_test', '', True)  # nopep8
620
-        secondly_created_but_not_updated = api.create(ContentType.Page, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
620
+        firstly_created_but_recently_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'update_order_test', '', True)  # nopep8
621
+        secondly_created_but_not_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
621 622
         with new_revision(
622 623
             session=dbsession,
623 624
             tm=transaction.manager,
@@ -626,10 +627,10 @@ class TestUserRecentlyActiveContentEndpoint(FunctionalTest):
626 627
             firstly_created_but_recently_updated.description = 'Just an update'
627 628
         api.save(firstly_created_but_recently_updated)
628 629
         # comment change order
629
-        firstly_created_but_recently_commented = api.create(ContentType.Page, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
630
-        secondly_created_but_not_commented = api.create(ContentType.Page, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
630
+        firstly_created_but_recently_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
631
+        secondly_created_but_not_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
631 632
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
632
-        content_workspace_2 = api.create(ContentType.Page, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
633
+        content_workspace_2 = api.create(CONTENT_TYPES.Page.slug, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
633 634
         dbsession.flush()
634 635
         transaction.commit()
635 636
 
@@ -714,14 +715,14 @@ class TestUserReadStatusEndpoint(FunctionalTest):
714 715
             session=dbsession,
715 716
             config=self.app_config,
716 717
         )
717
-        main_folder_workspace2 = api.create(ContentType.Folder, workspace2, None, 'Hepla', '', True)  # nopep8
718
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
718
+        main_folder_workspace2 = api.create(CONTENT_TYPES.Folder.slug, workspace2, None, 'Hepla', '', True)  # nopep8
719
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
719 720
         # creation order test
720
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
721
-        secondly_created = api.create(ContentType.Page, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
721
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
722
+        secondly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
722 723
         # update order test
723
-        firstly_created_but_recently_updated = api.create(ContentType.Page, workspace, main_folder, 'update_order_test', '', True)  # nopep8
724
-        secondly_created_but_not_updated = api.create(ContentType.Page, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
724
+        firstly_created_but_recently_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'update_order_test', '', True)  # nopep8
725
+        secondly_created_but_not_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
725 726
         with new_revision(
726 727
             session=dbsession,
727 728
             tm=transaction.manager,
@@ -730,10 +731,10 @@ class TestUserReadStatusEndpoint(FunctionalTest):
730 731
             firstly_created_but_recently_updated.description = 'Just an update'
731 732
         api.save(firstly_created_but_recently_updated)
732 733
         # comment change order
733
-        firstly_created_but_recently_commented = api.create(ContentType.Page, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
734
-        secondly_created_but_not_commented = api.create(ContentType.Page, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
734
+        firstly_created_but_recently_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
735
+        secondly_created_but_not_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
735 736
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
736
-        content_workspace_2 = api.create(ContentType.Page, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
737
+        content_workspace_2 = api.create(CONTENT_TYPES.Page.slug, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
737 738
         dbsession.flush()
738 739
         transaction.commit()
739 740
 
@@ -826,14 +827,14 @@ class TestUserReadStatusEndpoint(FunctionalTest):
826 827
             session=dbsession,
827 828
             config=self.app_config,
828 829
         )
829
-        main_folder_workspace2 = api.create(ContentType.Folder, workspace2, None, 'Hepla', '', True)  # nopep8
830
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
830
+        main_folder_workspace2 = api.create(CONTENT_TYPES.Folder.slug, workspace2, None, 'Hepla', '', True)  # nopep8
831
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
831 832
         # creation order test
832
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
833
-        secondly_created = api.create(ContentType.Page, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
833
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
834
+        secondly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
834 835
         # update order test
835
-        firstly_created_but_recently_updated = api.create(ContentType.Page, workspace, main_folder, 'update_order_test', '', True)  # nopep8
836
-        secondly_created_but_not_updated = api.create(ContentType.Page, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
836
+        firstly_created_but_recently_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'update_order_test', '', True)  # nopep8
837
+        secondly_created_but_not_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
837 838
         with new_revision(
838 839
             session=dbsession,
839 840
             tm=transaction.manager,
@@ -842,10 +843,10 @@ class TestUserReadStatusEndpoint(FunctionalTest):
842 843
             firstly_created_but_recently_updated.description = 'Just an update'
843 844
         api.save(firstly_created_but_recently_updated)
844 845
         # comment change order
845
-        firstly_created_but_recently_commented = api.create(ContentType.Page, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
846
-        secondly_created_but_not_commented = api.create(ContentType.Page, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
846
+        firstly_created_but_recently_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
847
+        secondly_created_but_not_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
847 848
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
848
-        content_workspace_2 = api.create(ContentType.Page, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
849
+        content_workspace_2 = api.create(CONTENT_TYPES.Page.slug, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
849 850
         dbsession.flush()
850 851
         transaction.commit()
851 852
 
@@ -949,14 +950,14 @@ class TestUserReadStatusEndpoint(FunctionalTest):
949 950
             session=dbsession,
950 951
             config=self.app_config,
951 952
         )
952
-        main_folder_workspace2 = api.create(ContentType.Folder, workspace2, None, 'Hepla', '', True)  # nopep8
953
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
953
+        main_folder_workspace2 = api.create(CONTENT_TYPES.Folder.slug, workspace2, None, 'Hepla', '', True)  # nopep8
954
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
954 955
         # creation order test
955
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
956
-        secondly_created = api.create(ContentType.Page, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
956
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
957
+        secondly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another creation_order_test', '', True)  # nopep8
957 958
         # update order test
958
-        firstly_created_but_recently_updated = api.create(ContentType.Page, workspace, main_folder, 'update_order_test', '', True)  # nopep8
959
-        secondly_created_but_not_updated = api.create(ContentType.Page, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
959
+        firstly_created_but_recently_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'update_order_test', '', True)  # nopep8
960
+        secondly_created_but_not_updated = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'another update_order_test', '', True)  # nopep8
960 961
         with new_revision(
961 962
             session=dbsession,
962 963
             tm=transaction.manager,
@@ -965,10 +966,10 @@ class TestUserReadStatusEndpoint(FunctionalTest):
965 966
             firstly_created_but_recently_updated.description = 'Just an update'
966 967
         api.save(firstly_created_but_recently_updated)
967 968
         # comment change order
968
-        firstly_created_but_recently_commented = api.create(ContentType.Page, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
969
-        secondly_created_but_not_commented = api.create(ContentType.Page, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
969
+        firstly_created_but_recently_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is randomized label content', '', True)  # nopep8
970
+        secondly_created_but_not_commented = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'this is another randomized label content', '', True)  # nopep8
970 971
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
971
-        content_workspace_2 = api.create(ContentType.Page, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
972
+        content_workspace_2 = api.create(CONTENT_TYPES.Page.slug, workspace2,main_folder_workspace2, 'content_workspace_2', '',True)  # nopep8
972 973
         dbsession.flush()
973 974
         transaction.commit()
974 975
 
@@ -1059,9 +1060,9 @@ class TestUserSetContentAsRead(FunctionalTest):
1059 1060
             session=dbsession,
1060 1061
             config=self.app_config,
1061 1062
         )
1062
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
1063
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
1063 1064
         # creation order test
1064
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1065
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1065 1066
         api.mark_unread(firstly_created)
1066 1067
         api2.mark_unread(firstly_created)
1067 1068
         dbsession.flush()
@@ -1167,9 +1168,9 @@ class TestUserSetContentAsRead(FunctionalTest):
1167 1168
             session=dbsession,
1168 1169
             config=self.app_config,
1169 1170
         )
1170
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
1171
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
1171 1172
         # creation order test
1172
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1173
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1173 1174
         api.mark_unread(firstly_created)
1174 1175
         api2.mark_unread(firstly_created)
1175 1176
         dbsession.flush()
@@ -1248,9 +1249,9 @@ class TestUserSetContentAsRead(FunctionalTest):
1248 1249
             session=dbsession,
1249 1250
             config=self.app_config,
1250 1251
         )
1251
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
1252
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
1252 1253
         # creation order test
1253
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1254
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1254 1255
         api.mark_unread(firstly_created)
1255 1256
         api2.mark_unread(firstly_created)
1256 1257
         dbsession.flush()
@@ -1329,9 +1330,9 @@ class TestUserSetContentAsRead(FunctionalTest):
1329 1330
             session=dbsession,
1330 1331
             config=self.app_config,
1331 1332
         )
1332
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
1333
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
1333 1334
         # creation order test
1334
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1335
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1335 1336
         api.mark_unread(firstly_created)
1336 1337
         api2.mark_unread(firstly_created)
1337 1338
         dbsession.flush()
@@ -1428,9 +1429,9 @@ class TestUserSetContentAsRead(FunctionalTest):
1428 1429
             session=dbsession,
1429 1430
             config=self.app_config,
1430 1431
         )
1431
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
1432
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
1432 1433
         # creation order test
1433
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1434
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1434 1435
         api.mark_unread(firstly_created)
1435 1436
         api2.mark_unread(firstly_created)
1436 1437
         dbsession.flush()
@@ -1509,9 +1510,9 @@ class TestUserSetContentAsRead(FunctionalTest):
1509 1510
             session=dbsession,
1510 1511
             config=self.app_config,
1511 1512
         )
1512
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
1513
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
1513 1514
         # creation order test
1514
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1515
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1515 1516
         comments = api.create_comment(workspace, firstly_created, 'juste a super comment', True)  # nopep8
1516 1517
         api.mark_unread(firstly_created)
1517 1518
         api.mark_unread(comments)
@@ -1626,9 +1627,9 @@ class TestUserSetContentAsUnread(FunctionalTest):
1626 1627
             session=dbsession,
1627 1628
             config=self.app_config,
1628 1629
         )
1629
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
1630
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
1630 1631
         # creation order test
1631
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1632
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1632 1633
         api.mark_read(firstly_created)
1633 1634
         api2.mark_read(firstly_created)
1634 1635
         dbsession.flush()
@@ -1735,9 +1736,9 @@ class TestUserSetContentAsUnread(FunctionalTest):
1735 1736
             session=dbsession,
1736 1737
             config=self.app_config,
1737 1738
         )
1738
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
1739
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
1739 1740
         # creation order test
1740
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1741
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1741 1742
         api.mark_read(firstly_created)
1742 1743
         api2.mark_read(firstly_created)
1743 1744
         dbsession.flush()
@@ -1816,9 +1817,9 @@ class TestUserSetContentAsUnread(FunctionalTest):
1816 1817
             session=dbsession,
1817 1818
             config=self.app_config,
1818 1819
         )
1819
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
1820
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
1820 1821
         # creation order test
1821
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1822
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1822 1823
         api.mark_read(firstly_created)
1823 1824
         api2.mark_read(firstly_created)
1824 1825
         dbsession.flush()
@@ -1898,9 +1899,9 @@ class TestUserSetContentAsUnread(FunctionalTest):
1898 1899
             session=dbsession,
1899 1900
             config=self.app_config,
1900 1901
         )
1901
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
1902
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
1902 1903
         # creation order test
1903
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1904
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1904 1905
         api.mark_read(firstly_created)
1905 1906
         api2.mark_read(firstly_created)
1906 1907
         dbsession.flush()
@@ -1993,9 +1994,9 @@ class TestUserSetContentAsUnread(FunctionalTest):
1993 1994
             session=dbsession,
1994 1995
             config=self.app_config,
1995 1996
         )
1996
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
1997
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
1997 1998
         # creation order test
1998
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1999
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
1999 2000
         api.mark_read(firstly_created)
2000 2001
         api2.mark_read(firstly_created)
2001 2002
         dbsession.flush()
@@ -2044,9 +2045,9 @@ class TestUserSetContentAsUnread(FunctionalTest):
2044 2045
             session=dbsession,
2045 2046
             config=self.app_config,
2046 2047
         )
2047
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
2048
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
2048 2049
         # creation order test
2049
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2050
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2050 2051
         comments = api.create_comment(workspace, firstly_created, 'juste a super comment', True)  # nopep8
2051 2052
         api.mark_read(firstly_created)
2052 2053
         api.mark_read(comments)
@@ -2137,9 +2138,9 @@ class TestUserSetWorkspaceAsRead(FunctionalTest):
2137 2138
             session=dbsession,
2138 2139
             config=self.app_config,
2139 2140
         )
2140
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
2141
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
2141 2142
         # creation order test
2142
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2143
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2143 2144
         api.mark_unread(main_folder)
2144 2145
         api.mark_unread(firstly_created)
2145 2146
         api2.mark_unread(main_folder)
@@ -2234,9 +2235,9 @@ class TestUserSetWorkspaceAsRead(FunctionalTest):
2234 2235
             session=dbsession,
2235 2236
             config=self.app_config,
2236 2237
         )
2237
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
2238
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
2238 2239
         # creation order test
2239
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2240
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2240 2241
         api.mark_unread(main_folder)
2241 2242
         api.mark_unread(firstly_created)
2242 2243
         api2.mark_unread(main_folder)
@@ -2331,9 +2332,9 @@ class TestUserSetWorkspaceAsRead(FunctionalTest):
2331 2332
             session=dbsession,
2332 2333
             config=self.app_config,
2333 2334
         )
2334
-        main_folder = api.create(ContentType.Folder, workspace, None, 'this is randomized folder', '', True)  # nopep8
2335
+        main_folder = api.create(CONTENT_TYPES.Folder.slug, workspace, None, 'this is randomized folder', '', True)  # nopep8
2335 2336
         # creation order test
2336
-        firstly_created = api.create(ContentType.Page, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2337
+        firstly_created = api.create(CONTENT_TYPES.Page.slug, workspace, main_folder, 'creation_order_test', '', True)  # nopep8
2337 2338
         api.mark_unread(main_folder)
2338 2339
         api.mark_unread(firstly_created)
2339 2340
         api2.mark_unread(main_folder)
@@ -2381,8 +2382,10 @@ class TestUserWorkspaceEndpoint(FunctionalTest):
2381 2382
         assert workspace['workspace_id'] == 1
2382 2383
         assert workspace['label'] == 'Business'
2383 2384
         assert workspace['slug'] == 'business'
2384
-        assert len(workspace['sidebar_entries']) == 7
2385
+        assert len(workspace['sidebar_entries']) == 5
2385 2386
 
2387
+        # TODO - G.M - 2018-08-02 - Better test for sidebar entry, make it
2388
+        # not fixed on active application/content-file
2386 2389
         sidebar_entry = workspace['sidebar_entries'][0]
2387 2390
         assert sidebar_entry['slug'] == 'dashboard'
2388 2391
         assert sidebar_entry['label'] == 'Dashboard'
@@ -2405,32 +2408,19 @@ class TestUserWorkspaceEndpoint(FunctionalTest):
2405 2408
         assert sidebar_entry['fa_icon'] == "file-text-o"
2406 2409
 
2407 2410
         sidebar_entry = workspace['sidebar_entries'][3]
2408
-        assert sidebar_entry['slug'] == 'contents/markdownpluspage'
2409
-        assert sidebar_entry['label'] == 'Markdown Plus Documents'
2410
-        assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=markdownpluspage"    # nopep8
2411
-        assert sidebar_entry['hexcolor'] == "#f12d2d"
2412
-        assert sidebar_entry['fa_icon'] == "file-code-o"
2413
-
2414
-        sidebar_entry = workspace['sidebar_entries'][4]
2415 2411
         assert sidebar_entry['slug'] == 'contents/file'
2416 2412
         assert sidebar_entry['label'] == 'Files'
2417 2413
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
2418 2414
         assert sidebar_entry['hexcolor'] == "#FF9900"
2419 2415
         assert sidebar_entry['fa_icon'] == "paperclip"
2420 2416
 
2421
-        sidebar_entry = workspace['sidebar_entries'][5]
2417
+        sidebar_entry = workspace['sidebar_entries'][4]
2422 2418
         assert sidebar_entry['slug'] == 'contents/thread'
2423 2419
         assert sidebar_entry['label'] == 'Threads'
2424 2420
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
2425 2421
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
2426 2422
         assert sidebar_entry['fa_icon'] == "comments-o"
2427 2423
 
2428
-        sidebar_entry = workspace['sidebar_entries'][6]
2429
-        assert sidebar_entry['slug'] == 'calendar'
2430
-        assert sidebar_entry['label'] == 'Calendar'
2431
-        assert sidebar_entry['route'] == "/#/workspaces/1/calendar"  # nopep8
2432
-        assert sidebar_entry['hexcolor'] == "#757575"
2433
-        assert sidebar_entry['fa_icon'] == "calendar"
2434 2424
 
2435 2425
     def test_api__get_user_workspaces__err_403__unallowed_user(self):
2436 2426
         """
@@ -2645,6 +2635,390 @@ class TestUserEndpoint(FunctionalTest):
2645 2635
         )
2646 2636
 
2647 2637
 
2638
+class TestUsersEndpoint(FunctionalTest):
2639
+    # -*- coding: utf-8 -*-
2640
+    """
2641
+    Tests for GET /api/v2/users/{user_id}
2642
+    """
2643
+    fixtures = [BaseFixture]
2644
+
2645
+    def test_api__get_user__ok_200__admin(self):
2646
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2647
+        admin = dbsession.query(models.User) \
2648
+            .filter(models.User.email == 'admin@admin.admin') \
2649
+            .one()
2650
+        uapi = UserApi(
2651
+            current_user=admin,
2652
+            session=dbsession,
2653
+            config=self.app_config,
2654
+        )
2655
+        gapi = GroupApi(
2656
+            current_user=admin,
2657
+            session=dbsession,
2658
+            config=self.app_config,
2659
+        )
2660
+        groups = [gapi.get_one_with_name('users')]
2661
+        test_user = uapi.create_user(
2662
+            email='test@test.test',
2663
+            password='pass',
2664
+            name='bob',
2665
+            groups=groups,
2666
+            timezone='Europe/Paris',
2667
+            do_save=True,
2668
+            do_notify=False,
2669
+        )
2670
+        uapi.save(test_user)
2671
+        transaction.commit()
2672
+        user_id = int(test_user.user_id)
2673
+
2674
+        self.testapp.authorization = (
2675
+            'Basic',
2676
+            (
2677
+                'admin@admin.admin',
2678
+                'admin@admin.admin'
2679
+            )
2680
+        )
2681
+        res = self.testapp.get(
2682
+            '/api/v2/users',
2683
+            status=200
2684
+        )
2685
+        res = res.json_body
2686
+        assert len(res) == 2
2687
+        assert res[0]['user_id'] == admin.user_id
2688
+        assert res[0]['public_name'] == admin.display_name
2689
+        assert res[0]['avatar_url'] is None
2690
+
2691
+        assert res[1]['user_id'] == test_user.user_id
2692
+        assert res[1]['public_name'] == test_user.display_name
2693
+        assert res[1]['avatar_url'] is None
2694
+
2695
+    def test_api__get_user__err_403__normal_user(self):
2696
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2697
+        admin = dbsession.query(models.User) \
2698
+            .filter(models.User.email == 'admin@admin.admin') \
2699
+            .one()
2700
+        uapi = UserApi(
2701
+            current_user=admin,
2702
+            session=dbsession,
2703
+            config=self.app_config,
2704
+        )
2705
+        gapi = GroupApi(
2706
+            current_user=admin,
2707
+            session=dbsession,
2708
+            config=self.app_config,
2709
+        )
2710
+        groups = [gapi.get_one_with_name('users')]
2711
+        test_user = uapi.create_user(
2712
+            email='test@test.test',
2713
+            password='pass',
2714
+            name='bob',
2715
+            groups=groups,
2716
+            timezone='Europe/Paris',
2717
+            do_save=True,
2718
+            do_notify=False,
2719
+        )
2720
+        uapi.save(test_user)
2721
+        transaction.commit()
2722
+        user_id = int(test_user.user_id)
2723
+
2724
+        self.testapp.authorization = (
2725
+            'Basic',
2726
+            (
2727
+                'test@test.test',
2728
+                'pass'
2729
+            )
2730
+        )
2731
+        self.testapp.get(
2732
+            '/api/v2/users',
2733
+            status=403
2734
+        )
2735
+
2736
+
2737
+class TestKnownMembersEndpoint(FunctionalTest):
2738
+    # -*- coding: utf-8 -*-
2739
+    """
2740
+    Tests for GET /api/v2/users/{user_id}
2741
+    """
2742
+    fixtures = [BaseFixture]
2743
+
2744
+    def test_api__get_user__ok_200__admin__by_name(self):
2745
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2746
+        admin = dbsession.query(models.User) \
2747
+            .filter(models.User.email == 'admin@admin.admin') \
2748
+            .one()
2749
+        uapi = UserApi(
2750
+            current_user=admin,
2751
+            session=dbsession,
2752
+            config=self.app_config,
2753
+        )
2754
+        gapi = GroupApi(
2755
+            current_user=admin,
2756
+            session=dbsession,
2757
+            config=self.app_config,
2758
+        )
2759
+        groups = [gapi.get_one_with_name('users')]
2760
+        test_user = uapi.create_user(
2761
+            email='test@test.test',
2762
+            password='pass',
2763
+            name='bob',
2764
+            groups=groups,
2765
+            timezone='Europe/Paris',
2766
+            do_save=True,
2767
+            do_notify=False,
2768
+        )
2769
+        test_user2 = uapi.create_user(
2770
+            email='test2@test2.test2',
2771
+            password='pass',
2772
+            name='bob2',
2773
+            groups=groups,
2774
+            timezone='Europe/Paris',
2775
+            do_save=True,
2776
+            do_notify=False,
2777
+        )
2778
+        uapi.save(test_user)
2779
+        uapi.save(test_user2)
2780
+        transaction.commit()
2781
+        user_id = int(admin.user_id)
2782
+
2783
+        self.testapp.authorization = (
2784
+            'Basic',
2785
+            (
2786
+                'admin@admin.admin',
2787
+                'admin@admin.admin'
2788
+            )
2789
+        )
2790
+        params = {
2791
+            'acp': 'bob',
2792
+        }
2793
+        res = self.testapp.get(
2794
+            '/api/v2/users/{user_id}/known_members'.format(user_id=user_id),
2795
+            status=200,
2796
+            params=params,
2797
+        )
2798
+        res = res.json_body
2799
+        assert len(res) == 2
2800
+        assert res[0]['user_id'] == test_user.user_id
2801
+        assert res[0]['public_name'] == test_user.display_name
2802
+        assert res[0]['avatar_url'] is None
2803
+
2804
+        assert res[1]['user_id'] == test_user2.user_id
2805
+        assert res[1]['public_name'] == test_user2.display_name
2806
+        assert res[1]['avatar_url'] is None
2807
+
2808
+    def test_api__get_user__ok_200__admin__by_email(self):
2809
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2810
+        admin = dbsession.query(models.User) \
2811
+            .filter(models.User.email == 'admin@admin.admin') \
2812
+            .one()
2813
+        uapi = UserApi(
2814
+            current_user=admin,
2815
+            session=dbsession,
2816
+            config=self.app_config,
2817
+        )
2818
+        gapi = GroupApi(
2819
+            current_user=admin,
2820
+            session=dbsession,
2821
+            config=self.app_config,
2822
+        )
2823
+        groups = [gapi.get_one_with_name('users')]
2824
+        test_user = uapi.create_user(
2825
+            email='test@test.test',
2826
+            password='pass',
2827
+            name='bob',
2828
+            groups=groups,
2829
+            timezone='Europe/Paris',
2830
+            do_save=True,
2831
+            do_notify=False,
2832
+        )
2833
+        test_user2 = uapi.create_user(
2834
+            email='test2@test2.test2',
2835
+            password='pass',
2836
+            name='bob2',
2837
+            groups=groups,
2838
+            timezone='Europe/Paris',
2839
+            do_save=True,
2840
+            do_notify=False,
2841
+        )
2842
+        uapi.save(test_user)
2843
+        uapi.save(test_user2)
2844
+        transaction.commit()
2845
+        user_id = int(admin.user_id)
2846
+
2847
+        self.testapp.authorization = (
2848
+            'Basic',
2849
+            (
2850
+                'admin@admin.admin',
2851
+                'admin@admin.admin'
2852
+            )
2853
+        )
2854
+        params = {
2855
+            'acp': 'test',
2856
+        }
2857
+        res = self.testapp.get(
2858
+            '/api/v2/users/{user_id}/known_members'.format(user_id=user_id),
2859
+            status=200,
2860
+            params=params,
2861
+        )
2862
+        res = res.json_body
2863
+        assert len(res) == 2
2864
+        assert res[0]['user_id'] == test_user.user_id
2865
+        assert res[0]['public_name'] == test_user.display_name
2866
+        assert res[0]['avatar_url'] is None
2867
+
2868
+        assert res[1]['user_id'] == test_user2.user_id
2869
+        assert res[1]['public_name'] == test_user2.display_name
2870
+        assert res[1]['avatar_url'] is None
2871
+
2872
+    def test_api__get_user__err_403__admin__too_small_acp(self):
2873
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2874
+        admin = dbsession.query(models.User) \
2875
+            .filter(models.User.email == 'admin@admin.admin') \
2876
+            .one()
2877
+        uapi = UserApi(
2878
+            current_user=admin,
2879
+            session=dbsession,
2880
+            config=self.app_config,
2881
+        )
2882
+        gapi = GroupApi(
2883
+            current_user=admin,
2884
+            session=dbsession,
2885
+            config=self.app_config,
2886
+        )
2887
+        groups = [gapi.get_one_with_name('users')]
2888
+        test_user = uapi.create_user(
2889
+            email='test@test.test',
2890
+            password='pass',
2891
+            name='bob',
2892
+            groups=groups,
2893
+            timezone='Europe/Paris',
2894
+            do_save=True,
2895
+            do_notify=False,
2896
+        )
2897
+        test_user2 = uapi.create_user(
2898
+            email='test2@test2.test2',
2899
+            password='pass',
2900
+            name='bob2',
2901
+            groups=groups,
2902
+            timezone='Europe/Paris',
2903
+            do_save=True,
2904
+            do_notify=False,
2905
+        )
2906
+        uapi.save(test_user)
2907
+        transaction.commit()
2908
+        user_id = int(admin.user_id)
2909
+
2910
+        self.testapp.authorization = (
2911
+            'Basic',
2912
+            (
2913
+                'admin@admin.admin',
2914
+                'admin@admin.admin'
2915
+            )
2916
+        )
2917
+        params = {
2918
+            'acp': 't',
2919
+        }
2920
+        res = self.testapp.get(
2921
+            '/api/v2/users/{user_id}/known_members'.format(user_id=user_id),
2922
+            status=400,
2923
+            params=params
2924
+        )
2925
+
2926
+    def test_api__get_user__ok_200__normal_user_by_email(self):
2927
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2928
+        admin = dbsession.query(models.User) \
2929
+            .filter(models.User.email == 'admin@admin.admin') \
2930
+            .one()
2931
+        uapi = UserApi(
2932
+            current_user=admin,
2933
+            session=dbsession,
2934
+            config=self.app_config,
2935
+        )
2936
+        gapi = GroupApi(
2937
+            current_user=admin,
2938
+            session=dbsession,
2939
+            config=self.app_config,
2940
+        )
2941
+        groups = [gapi.get_one_with_name('users')]
2942
+        test_user = uapi.create_user(
2943
+            email='test@test.test',
2944
+            password='pass',
2945
+            name='bob',
2946
+            groups=groups,
2947
+            timezone='Europe/Paris',
2948
+            do_save=True,
2949
+            do_notify=False,
2950
+        )
2951
+        test_user2 = uapi.create_user(
2952
+            email='test2@test2.test2',
2953
+            password='pass',
2954
+            name='bob2',
2955
+            groups=groups,
2956
+            timezone='Europe/Paris',
2957
+            do_save=True,
2958
+            do_notify=False,
2959
+        )
2960
+        test_user3 = uapi.create_user(
2961
+            email='test3@test3.test3',
2962
+            password='pass',
2963
+            name='bob3',
2964
+            groups=groups,
2965
+            timezone='Europe/Paris',
2966
+            do_save=True,
2967
+            do_notify=False,
2968
+        )
2969
+        uapi.save(test_user)
2970
+        uapi.save(test_user2)
2971
+        uapi.save(test_user3)
2972
+        workspace_api = WorkspaceApi(
2973
+            current_user=admin,
2974
+            session=dbsession,
2975
+            config=self.app_config
2976
+
2977
+        )
2978
+        workspace = WorkspaceApi(
2979
+            current_user=admin,
2980
+            session=dbsession,
2981
+            config=self.app_config,
2982
+        ).create_workspace(
2983
+            'test workspace',
2984
+            save_now=True
2985
+        )
2986
+        role_api = RoleApi(
2987
+            current_user=admin,
2988
+            session=dbsession,
2989
+            config=self.app_config,
2990
+        )
2991
+        role_api.create_one(test_user, workspace, UserRoleInWorkspace.READER, False)
2992
+        role_api.create_one(test_user2, workspace, UserRoleInWorkspace.READER, False)
2993
+        transaction.commit()
2994
+        user_id = int(test_user.user_id)
2995
+
2996
+        self.testapp.authorization = (
2997
+            'Basic',
2998
+            (
2999
+                'test@test.test',
3000
+                'pass'
3001
+            )
3002
+        )
3003
+        params = {
3004
+            'acp': 'test',
3005
+        }
3006
+        res = self.testapp.get(
3007
+            '/api/v2/users/{user_id}/known_members'.format(user_id=user_id),
3008
+            status=200,
3009
+            params=params
3010
+        )
3011
+        res = res.json_body
3012
+        assert len(res) == 2
3013
+        assert res[0]['user_id'] == test_user.user_id
3014
+        assert res[0]['public_name'] == test_user.display_name
3015
+        assert res[0]['avatar_url'] is None
3016
+
3017
+        assert res[1]['user_id'] == test_user2.user_id
3018
+        assert res[1]['public_name'] == test_user2.display_name
3019
+        assert res[1]['avatar_url'] is None
3020
+
3021
+
2648 3022
 class TestSetEmailEndpoint(FunctionalTest):
2649 3023
     # -*- coding: utf-8 -*-
2650 3024
     """
@@ -3024,6 +3398,12 @@ class TestSetPasswordEndpoint(FunctionalTest):
3024 3398
             status=204,
3025 3399
         )
3026 3400
         # Check After
3401
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
3402
+        uapi = UserApi(
3403
+            current_user=admin,
3404
+            session=dbsession,
3405
+            config=self.app_config,
3406
+        )
3027 3407
         user = uapi.get_one(user_id)
3028 3408
         assert not user.validate_password('pass')
3029 3409
         assert user.validate_password('mynewpassword')
@@ -3079,6 +3459,12 @@ class TestSetPasswordEndpoint(FunctionalTest):
3079 3459
             params=params,
3080 3460
             status=403,
3081 3461
         )
3462
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
3463
+        uapi = UserApi(
3464
+            current_user=admin,
3465
+            session=dbsession,
3466
+            config=self.app_config,
3467
+        )
3082 3468
         # Check After
3083 3469
         user = uapi.get_one(user_id)
3084 3470
         assert user.validate_password('pass')
@@ -3137,6 +3523,12 @@ class TestSetPasswordEndpoint(FunctionalTest):
3137 3523
             status=400,
3138 3524
         )
3139 3525
         # Check After
3526
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
3527
+        uapi = UserApi(
3528
+            current_user=admin,
3529
+            session=dbsession,
3530
+            config=self.app_config,
3531
+        )
3140 3532
         user = uapi.get_one(user_id)
3141 3533
         assert user.validate_password('pass')
3142 3534
         assert not user.validate_password('mynewpassword')
@@ -3194,6 +3586,12 @@ class TestSetPasswordEndpoint(FunctionalTest):
3194 3586
             status=204,
3195 3587
         )
3196 3588
         # Check After
3589
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
3590
+        uapi = UserApi(
3591
+            current_user=admin,
3592
+            session=dbsession,
3593
+            config=self.app_config,
3594
+        )
3197 3595
         user = uapi.get_one(user_id)
3198 3596
         assert not user.validate_password('pass')
3199 3597
         assert user.validate_password('mynewpassword')

+ 112 - 41
backend/tracim_backend/tests/functional/test_workspaces.py Целия файл

@@ -2,7 +2,7 @@
2 2
 """
3 3
 Tests for /api/v2/workspaces subpath endpoints.
4 4
 """
5
-
5
+import requests
6 6
 import transaction
7 7
 from depot.io.utils import FileIntent
8 8
 
@@ -10,7 +10,7 @@ from tracim_backend import models
10 10
 from tracim_backend.lib.core.content import ContentApi
11 11
 from tracim_backend.lib.core.workspace import WorkspaceApi
12 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 14
 from tracim_backend.tests import FunctionalTest
15 15
 from tracim_backend.tests import set_html_document_slug_to_legacy
16 16
 from tracim_backend.fixtures.content import Content as ContentFixtures
@@ -41,8 +41,10 @@ class TestWorkspaceEndpoint(FunctionalTest):
41 41
         assert workspace['slug'] == 'business'
42 42
         assert workspace['label'] == 'Business'
43 43
         assert workspace['description'] == 'All importants documents'
44
-        assert len(workspace['sidebar_entries']) == 7
44
+        assert len(workspace['sidebar_entries']) == 5
45 45
 
46
+        # TODO - G.M - 2018-08-02 - Better test for sidebar entry, make it
47
+        # not fixed on active application/content-file
46 48
         sidebar_entry = workspace['sidebar_entries'][0]
47 49
         assert sidebar_entry['slug'] == 'dashboard'
48 50
         assert sidebar_entry['label'] == 'Dashboard'
@@ -65,33 +67,19 @@ class TestWorkspaceEndpoint(FunctionalTest):
65 67
         assert sidebar_entry['fa_icon'] == "file-text-o"
66 68
 
67 69
         sidebar_entry = workspace['sidebar_entries'][3]
68
-        assert sidebar_entry['slug'] == 'contents/markdownpluspage'
69
-        assert sidebar_entry['label'] == 'Markdown Plus Documents'
70
-        assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=markdownpluspage"    # nopep8
71
-        assert sidebar_entry['hexcolor'] == "#f12d2d"
72
-        assert sidebar_entry['fa_icon'] == "file-code-o"
73
-
74
-        sidebar_entry = workspace['sidebar_entries'][4]
75 70
         assert sidebar_entry['slug'] == 'contents/file'
76 71
         assert sidebar_entry['label'] == 'Files'
77 72
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
78 73
         assert sidebar_entry['hexcolor'] == "#FF9900"
79 74
         assert sidebar_entry['fa_icon'] == "paperclip"
80 75
 
81
-        sidebar_entry = workspace['sidebar_entries'][5]
76
+        sidebar_entry = workspace['sidebar_entries'][4]
82 77
         assert sidebar_entry['slug'] == 'contents/thread'
83 78
         assert sidebar_entry['label'] == 'Threads'
84 79
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
85 80
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
86 81
         assert sidebar_entry['fa_icon'] == "comments-o"
87 82
 
88
-        sidebar_entry = workspace['sidebar_entries'][6]
89
-        assert sidebar_entry['slug'] == 'calendar'
90
-        assert sidebar_entry['label'] == 'Calendar'
91
-        assert sidebar_entry['route'] == "/#/workspaces/1/calendar"  # nopep8
92
-        assert sidebar_entry['hexcolor'] == "#757575"
93
-        assert sidebar_entry['fa_icon'] == "calendar"
94
-
95 83
     def test_api__update_workspace__ok_200__nominal_case(self) -> None:
96 84
         """
97 85
         Test update workspace
@@ -118,7 +106,7 @@ class TestWorkspaceEndpoint(FunctionalTest):
118 106
         assert workspace['slug'] == 'business'
119 107
         assert workspace['label'] == 'Business'
120 108
         assert workspace['description'] == 'All importants documents'
121
-        assert len(workspace['sidebar_entries']) == 7
109
+        assert len(workspace['sidebar_entries']) == 5
122 110
 
123 111
         # modify workspace
124 112
         res = self.testapp.put_json(
@@ -132,7 +120,7 @@ class TestWorkspaceEndpoint(FunctionalTest):
132 120
         assert workspace['slug'] == 'superworkspace'
133 121
         assert workspace['label'] == 'superworkspace'
134 122
         assert workspace['description'] == 'mysuperdescription'
135
-        assert len(workspace['sidebar_entries']) == 7
123
+        assert len(workspace['sidebar_entries']) == 5
136 124
 
137 125
         # after
138 126
         res = self.testapp.get(
@@ -145,7 +133,7 @@ class TestWorkspaceEndpoint(FunctionalTest):
145 133
         assert workspace['slug'] == 'superworkspace'
146 134
         assert workspace['label'] == 'superworkspace'
147 135
         assert workspace['description'] == 'mysuperdescription'
148
-        assert len(workspace['sidebar_entries']) == 7
136
+        assert len(workspace['sidebar_entries']) == 5
149 137
 
150 138
     def test_api__update_workspace__err_400__empty_label(self) -> None:
151 139
         """
@@ -612,6 +600,89 @@ class TestWorkspaceMembersEndpoint(FunctionalTest):
612 600
         assert user_role['workspace_id'] == 1
613 601
 
614 602
 
603
+class TestUserInvitationWithMailActivatedSync(FunctionalTest):
604
+
605
+    fixtures = [BaseFixture, ContentFixtures]
606
+    config_section = 'functional_test_with_mail_test_sync'
607
+
608
+    def test_api__create_workspace_member_role__ok_200__new_user(self):  # nopep8
609
+        """
610
+        Create workspace member role
611
+        :return:
612
+        """
613
+        requests.delete('http://127.0.0.1:8025/api/v1/messages')
614
+        self.testapp.authorization = (
615
+            'Basic',
616
+            (
617
+                'admin@admin.admin',
618
+                'admin@admin.admin'
619
+            )
620
+        )
621
+        # create workspace role
622
+        params = {
623
+            'user_id': None,
624
+            'user_email_or_public_name': 'bob@bob.bob',
625
+            'role': 'content-manager',
626
+        }
627
+        res = self.testapp.post_json(
628
+            '/api/v2/workspaces/1/members',
629
+            status=200,
630
+            params=params,
631
+        )
632
+        user_role_found = res.json_body
633
+        assert user_role_found['role'] == 'content-manager'
634
+        assert user_role_found['user_id']
635
+        user_id = user_role_found['user_id']
636
+        assert user_role_found['workspace_id'] == 1
637
+        assert user_role_found['newly_created'] is True
638
+        assert user_role_found['email_sent'] is True
639
+
640
+        # check mail received
641
+        response = requests.get('http://127.0.0.1:8025/api/v1/messages')
642
+        response = response.json()
643
+        assert len(response) == 1
644
+        headers = response[0]['Content']['Headers']
645
+        assert headers['From'][0] == 'Tracim Notifications <test_user_from+0@localhost>'  # nopep8
646
+        assert headers['To'][0] == 'bob <bob@bob.bob>'
647
+        assert headers['Subject'][0] == '[TRACIM] Created account'
648
+
649
+        # TODO - G.M - 2018-08-02 - Place cleanup outside of the test
650
+        requests.delete('http://127.0.0.1:8025/api/v1/messages')
651
+
652
+
653
+class TestUserInvitationWithMailActivatedASync(FunctionalTest):
654
+
655
+    fixtures = [BaseFixture, ContentFixtures]
656
+    config_section = 'functional_test_with_mail_test_async'
657
+
658
+    def test_api__create_workspace_member_role__ok_200__new_user(self):  # nopep8
659
+        """
660
+        Create workspace member role
661
+        :return:
662
+        """
663
+        self.testapp.authorization = (
664
+            'Basic',
665
+            (
666
+                'admin@admin.admin',
667
+                'admin@admin.admin'
668
+            )
669
+        )
670
+        # create workspace role
671
+        params = {
672
+            'user_id': None,
673
+            'user_email_or_public_name': 'bob@bob.bob',
674
+            'role': 'content-manager',
675
+        }
676
+        res = self.testapp.post_json(
677
+            '/api/v2/workspaces/1/members',
678
+            status=200,
679
+            params=params,
680
+        )
681
+        user_role_found = res.json_body
682
+        assert user_role_found['newly_created'] is True
683
+        assert user_role_found['email_sent'] is False
684
+
685
+
615 686
 class TestWorkspaceContents(FunctionalTest):
616 687
     """
617 688
     Tests for /api/v2/workspaces/{workspace_id}/contents endpoint
@@ -983,9 +1054,9 @@ class TestWorkspaceContents(FunctionalTest):
983 1054
             session=dbsession,
984 1055
             config=self.app_config
985 1056
         )
986
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1057
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
987 1058
         test_thread = content_api.create(
988
-            content_type=ContentType.Thread,
1059
+            content_type_slug=CONTENT_TYPES.Thread.slug,
989 1060
             workspace=business_workspace,
990 1061
             parent=tool_folder,
991 1062
             label='Test Thread',
@@ -995,7 +1066,7 @@ class TestWorkspaceContents(FunctionalTest):
995 1066
         test_thread.description = 'Thread description'
996 1067
         dbsession.add(test_thread)
997 1068
         test_file = content_api.create(
998
-            content_type=ContentType.File,
1069
+            content_type_slug=CONTENT_TYPES.File.slug,
999 1070
             workspace=business_workspace,
1000 1071
             parent=tool_folder,
1001 1072
             label='Test file',
@@ -1009,16 +1080,16 @@ class TestWorkspaceContents(FunctionalTest):
1009 1080
             'text/plain',
1010 1081
         )
1011 1082
         test_page_legacy = content_api.create(
1012
-            content_type=ContentType.Page,
1083
+            content_type_slug=CONTENT_TYPES.Page.slug,
1013 1084
             workspace=business_workspace,
1014 1085
             label='test_page',
1015 1086
             do_save=False,
1016 1087
             do_notify=False,
1017 1088
         )
1018
-        test_page_legacy.type = ContentType.PageLegacy
1089
+        test_page_legacy.type = 'page'
1019 1090
         content_api.update_content(test_page_legacy, 'test_page', '<p>PAGE</p>')
1020 1091
         test_html_document = content_api.create(
1021
-            content_type=ContentType.Page,
1092
+            content_type_slug=CONTENT_TYPES.Page.slug,
1022 1093
             workspace=business_workspace,
1023 1094
             label='test_html_page',
1024 1095
             do_save=False,
@@ -1078,9 +1149,9 @@ class TestWorkspaceContents(FunctionalTest):
1078 1149
             session=dbsession,
1079 1150
             config=self.app_config
1080 1151
         )
1081
-        tool_folder = content_api.get_one(1, content_type=ContentType.Any)
1152
+        tool_folder = content_api.get_one(1, content_type=CONTENT_TYPES.Any_SLUG)
1082 1153
         test_thread = content_api.create(
1083
-            content_type=ContentType.Thread,
1154
+            content_type_slug=CONTENT_TYPES.Thread.slug,
1084 1155
             workspace=business_workspace,
1085 1156
             parent=tool_folder,
1086 1157
             label='Test Thread',
@@ -1090,7 +1161,7 @@ class TestWorkspaceContents(FunctionalTest):
1090 1161
         test_thread.description = 'Thread description'
1091 1162
         dbsession.add(test_thread)
1092 1163
         test_file = content_api.create(
1093
-            content_type=ContentType.File,
1164
+            content_type_slug=CONTENT_TYPES.File.slug,
1094 1165
             workspace=business_workspace,
1095 1166
             parent=tool_folder,
1096 1167
             label='Test file',
@@ -1104,17 +1175,17 @@ class TestWorkspaceContents(FunctionalTest):
1104 1175
             'text/plain',
1105 1176
         )
1106 1177
         test_page_legacy = content_api.create(
1107
-            content_type=ContentType.Page,
1178
+            content_type_slug=CONTENT_TYPES.Page.slug,
1108 1179
             workspace=business_workspace,
1109 1180
             parent=tool_folder,
1110 1181
             label='test_page',
1111 1182
             do_save=False,
1112 1183
             do_notify=False,
1113 1184
         )
1114
-        test_page_legacy.type = ContentType.PageLegacy
1185
+        test_page_legacy.type = 'page'
1115 1186
         content_api.update_content(test_page_legacy, 'test_page', '<p>PAGE</p>')
1116 1187
         test_html_document = content_api.create(
1117
-            content_type=ContentType.Page,
1188
+            content_type_slug=CONTENT_TYPES.Page.slug,
1118 1189
             workspace=business_workspace,
1119 1190
             parent=tool_folder,
1120 1191
             label='test_html_page',
@@ -1180,7 +1251,7 @@ class TestWorkspaceContents(FunctionalTest):
1180 1251
             'show_archived': 1,
1181 1252
             'show_deleted': 1,
1182 1253
             'show_active': 1,
1183
-            'content_type': 'any'
1254
+         #   'content_type': 'any'
1184 1255
         }
1185 1256
         self.testapp.authorization = (
1186 1257
             'Basic',
@@ -1438,7 +1509,7 @@ class TestWorkspaceContents(FunctionalTest):
1438 1509
         params = {
1439 1510
             'parent_id': None,
1440 1511
             'label': 'GenericCreatedContent',
1441
-            'content_type': 'markdownpage',
1512
+            'content_type': 'html-document',
1442 1513
         }
1443 1514
         res = self.testapp.post_json(
1444 1515
             '/api/v2/workspaces/1/contents',
@@ -1449,7 +1520,7 @@ class TestWorkspaceContents(FunctionalTest):
1449 1520
         assert res.json_body
1450 1521
         assert res.json_body['status'] == 'open'
1451 1522
         assert res.json_body['content_id']
1452
-        assert res.json_body['content_type'] == 'markdownpage'
1523
+        assert res.json_body['content_type'] == 'html-document'
1453 1524
         assert res.json_body['is_archived'] is False
1454 1525
         assert res.json_body['is_deleted'] is False
1455 1526
         assert res.json_body['workspace_id'] == 1
@@ -1480,7 +1551,7 @@ class TestWorkspaceContents(FunctionalTest):
1480 1551
         )
1481 1552
         params = {
1482 1553
             'label': 'GenericCreatedContent',
1483
-            'content_type': 'markdownpage',
1554
+            'content_type': 'html-document',
1484 1555
         }
1485 1556
         res = self.testapp.post_json(
1486 1557
             '/api/v2/workspaces/1/contents',
@@ -1491,7 +1562,7 @@ class TestWorkspaceContents(FunctionalTest):
1491 1562
         assert res.json_body
1492 1563
         assert res.json_body['status'] == 'open'
1493 1564
         assert res.json_body['content_id']
1494
-        assert res.json_body['content_type'] == 'markdownpage'
1565
+        assert res.json_body['content_type'] == 'html-document'
1495 1566
         assert res.json_body['is_archived'] is False
1496 1567
         assert res.json_body['is_deleted'] is False
1497 1568
         assert res.json_body['workspace_id'] == 1
@@ -1544,7 +1615,7 @@ class TestWorkspaceContents(FunctionalTest):
1544 1615
         )
1545 1616
         params = {
1546 1617
             'label': 'GenericCreatedContent',
1547
-            'content_type': 'markdownpage',
1618
+            'content_type': 'html-document',
1548 1619
             'parent_id': 10,
1549 1620
         }
1550 1621
         res = self.testapp.post_json(
@@ -1556,7 +1627,7 @@ class TestWorkspaceContents(FunctionalTest):
1556 1627
         assert res.json_body
1557 1628
         assert res.json_body['status'] == 'open'
1558 1629
         assert res.json_body['content_id']
1559
-        assert res.json_body['content_type'] == 'markdownpage'
1630
+        assert res.json_body['content_type'] == 'html-document'
1560 1631
         assert res.json_body['is_archived'] is False
1561 1632
         assert res.json_body['is_deleted'] is False
1562 1633
         assert res.json_body['workspace_id'] == 1
@@ -1587,7 +1658,7 @@ class TestWorkspaceContents(FunctionalTest):
1587 1658
         )
1588 1659
         params = {
1589 1660
             'label': '',
1590
-            'content_type': 'markdownpage',
1661
+            'content_type': 'html-document',
1591 1662
         }
1592 1663
         res = self.testapp.post_json(
1593 1664
             '/api/v2/workspaces/1/contents',

+ 115 - 115
backend/tracim_backend/tests/library/test_content_api.py Целия файл

@@ -14,6 +14,7 @@ from tracim_backend.exceptions import SameValueError
14 14
 from tracim_backend.lib.core.workspace import RoleApi
15 15
 # TODO - G.M - 28-03-2018 - [WorkspaceApi] Re-enable WorkspaceApi
16 16
 from tracim_backend.lib.core.workspace import WorkspaceApi
17
+from tracim_backend.models.contents import CONTENT_TYPES
17 18
 from tracim_backend.models.revision_protection import new_revision
18 19
 from tracim_backend.models.auth import User
19 20
 from tracim_backend.models.auth import Group
@@ -22,7 +23,6 @@ from tracim_backend.models.data import ActionDescription
22 23
 from tracim_backend.models.data import ContentRevisionRO
23 24
 from tracim_backend.models.data import Workspace
24 25
 from tracim_backend.models.data import Content
25
-from tracim_backend.models.data import ContentType
26 26
 from tracim_backend.models.data import UserRoleInWorkspace
27 27
 from tracim_backend.fixtures.users_and_groups import Test as FixtureTest
28 28
 from tracim_backend.tests import DefaultTest
@@ -129,14 +129,14 @@ class TestContentApi(DefaultTest):
129 129
             config=self.app_config,
130 130
         )
131 131
         item = api.create(
132
-            content_type=ContentType.Folder,
132
+            content_type_slug=CONTENT_TYPES.Folder.slug,
133 133
             workspace=workspace,
134 134
             parent=None,
135 135
             label='not_deleted',
136 136
             do_save=True
137 137
         )
138 138
         item2 = api.create(
139
-            content_type=ContentType.Folder,
139
+            content_type_slug=CONTENT_TYPES.Folder.slug,
140 140
             workspace=workspace,
141 141
             parent=None,
142 142
             label='to_delete',
@@ -159,10 +159,10 @@ class TestContentApi(DefaultTest):
159 159
             session=self.session,
160 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 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 166
         with new_revision(
167 167
                 session=self.session,
168 168
                 tm=transaction.manager,
@@ -184,7 +184,7 @@ class TestContentApi(DefaultTest):
184 184
             session=self.session,
185 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 188
         eq_(1, len(items))
189 189
         transaction.commit()
190 190
 
@@ -202,7 +202,7 @@ class TestContentApi(DefaultTest):
202 202
             config=self.app_config,
203 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 206
         eq_(2, len(items))
207 207
 
208 208
     def test_archive(self):
@@ -240,14 +240,14 @@ class TestContentApi(DefaultTest):
240 240
             config=self.app_config,
241 241
         )
242 242
         item = api.create(
243
-            content_type=ContentType.Folder,
243
+            content_type_slug=CONTENT_TYPES.Folder.slug,
244 244
             workspace=workspace,
245 245
             parent=None,
246 246
             label='not_archived',
247 247
             do_save=True
248 248
         )
249 249
         item2 = api.create(
250
-            content_type=ContentType.Folder,
250
+            content_type_slug=CONTENT_TYPES.Folder.slug,
251 251
             workspace=workspace,
252 252
             parent=None,
253 253
             label='to_archive',
@@ -269,10 +269,10 @@ class TestContentApi(DefaultTest):
269 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 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 276
         with new_revision(
277 277
                 session=self.session,
278 278
                 tm=transaction.manager,
@@ -295,7 +295,7 @@ class TestContentApi(DefaultTest):
295 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 299
         eq_(1, len(items))
300 300
         transaction.commit()
301 301
 
@@ -320,7 +320,7 @@ class TestContentApi(DefaultTest):
320 320
             config=self.app_config,
321 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 324
         eq_(2, len(items))
325 325
 
326 326
     def test_get_all_with_filter(self):
@@ -358,14 +358,14 @@ class TestContentApi(DefaultTest):
358 358
             config=self.app_config,
359 359
         )
360 360
         item = api.create(
361
-            content_type=ContentType.Folder,
361
+            content_type_slug=CONTENT_TYPES.Folder.slug,
362 362
             workspace=workspace,
363 363
             parent=None,
364 364
             label='thefolder',
365 365
             do_save=True
366 366
         )
367 367
         item2 = api.create(
368
-            content_type=ContentType.File,
368
+            content_type_slug=CONTENT_TYPES.File.slug,
369 369
             workspace=workspace,
370 370
             parent=None,
371 371
             label='thefile',
@@ -389,14 +389,14 @@ class TestContentApi(DefaultTest):
389 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 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 396
         eq_(1, len(items2))
397 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 400
         eq_(1, len(items3))
401 401
         eq_('thefolder', items3[0].label)
402 402
 
@@ -428,21 +428,21 @@ class TestContentApi(DefaultTest):
428 428
             config=self.app_config,
429 429
         )
430 430
         item = api.create(
431
-            ContentType.Folder,
431
+            CONTENT_TYPES.Folder.slug,
432 432
             workspace,
433 433
             None,
434 434
             'parent',
435 435
             do_save=True,
436 436
         )
437 437
         item2 = api.create(
438
-            ContentType.File,
438
+            CONTENT_TYPES.File.slug,
439 439
             workspace,
440 440
             item,
441 441
             'file1',
442 442
             do_save=True,
443 443
         )
444 444
         item3 = api.create(
445
-            ContentType.File,
445
+            CONTENT_TYPES.File.slug,
446 446
             workspace,
447 447
             None,
448 448
             'file2',
@@ -468,10 +468,10 @@ class TestContentApi(DefaultTest):
468 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 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 475
         eq_(1, len(items2))
476 476
         eq_(child_id, items2[0].content_id)
477 477
 
@@ -506,7 +506,7 @@ class TestContentApi(DefaultTest):
506 506
             session=self.session,
507 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 510
         with new_revision(
511 511
             session=self.session,
512 512
             tm=transaction.manager,
@@ -546,7 +546,7 @@ class TestContentApi(DefaultTest):
546 546
             session=self.session,
547 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 550
         with new_revision(
551 551
             session=self.session,
552 552
             tm=transaction.manager,
@@ -591,14 +591,14 @@ class TestContentApi(DefaultTest):
591 591
             session=self.session,
592 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 595
         c = api.create_comment(workspace, p, 'this is the comment', True)
596 596
 
597 597
         eq_(Content, c.__class__)
598 598
         eq_(p.content_id, c.parent_id)
599 599
         eq_(user, c.owner)
600 600
         eq_(workspace, c.workspace)
601
-        eq_(ContentType.Comment, c.type)
601
+        eq_(CONTENT_TYPES.Comment.slug, c.type)
602 602
         eq_('this is the comment', c.description)
603 603
         eq_('', c.label)
604 604
         eq_(ActionDescription.COMMENT, c.revision_type)
@@ -652,7 +652,7 @@ class TestContentApi(DefaultTest):
652 652
             config=self.app_config,
653 653
         )
654 654
         foldera = api.create(
655
-            ContentType.Folder,
655
+            CONTENT_TYPES.Folder.slug,
656 656
             workspace,
657 657
             None,
658 658
             'folder a',
@@ -661,7 +661,7 @@ class TestContentApi(DefaultTest):
661 661
         )
662 662
         with self.session.no_autoflush:
663 663
             text_file = api.create(
664
-                content_type=ContentType.File,
664
+                content_type_slug=CONTENT_TYPES.File.slug,
665 665
                 workspace=workspace,
666 666
                 parent=foldera,
667 667
                 label='test_file',
@@ -689,7 +689,7 @@ class TestContentApi(DefaultTest):
689 689
             save_now=True
690 690
         )
691 691
         folderb = api2.create(
692
-            ContentType.Folder,
692
+            CONTENT_TYPES.Folder.slug,
693 693
             workspace2,
694 694
             None,
695 695
             'folder b',
@@ -773,7 +773,7 @@ class TestContentApi(DefaultTest):
773 773
             config=self.app_config,
774 774
         )
775 775
         foldera = api.create(
776
-            ContentType.Folder,
776
+            CONTENT_TYPES.Folder.slug,
777 777
             workspace,
778 778
             None,
779 779
             'folder a',
@@ -782,7 +782,7 @@ class TestContentApi(DefaultTest):
782 782
         )
783 783
         with self.session.no_autoflush:
784 784
             text_file = api.create(
785
-                content_type=ContentType.File,
785
+                content_type_slug=CONTENT_TYPES.File.slug,
786 786
                 workspace=workspace,
787 787
                 parent=foldera,
788 788
                 label='test_file',
@@ -810,7 +810,7 @@ class TestContentApi(DefaultTest):
810 810
             save_now=True
811 811
         )
812 812
         folderb = api2.create(
813
-            ContentType.Folder,
813
+            CONTENT_TYPES.Folder.slug,
814 814
             workspace2,
815 815
             None,
816 816
             'folder b',
@@ -891,7 +891,7 @@ class TestContentApi(DefaultTest):
891 891
             config=self.app_config,
892 892
         )
893 893
         foldera = api.create(
894
-            ContentType.Folder,
894
+            CONTENT_TYPES.Folder.slug,
895 895
             workspace,
896 896
             None,
897 897
             'folder a',
@@ -900,7 +900,7 @@ class TestContentApi(DefaultTest):
900 900
         )
901 901
         with self.session.no_autoflush:
902 902
             text_file = api.create(
903
-                content_type=ContentType.File,
903
+                content_type_slug=CONTENT_TYPES.File.slug,
904 904
                 workspace=workspace,
905 905
                 parent=foldera,
906 906
                 label='test_file',
@@ -1014,13 +1014,13 @@ class TestContentApi(DefaultTest):
1014 1014
 
1015 1015
         # Creates page_1 & page_2 in workspace 1
1016 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 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 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 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 1024
                                    'this is page3', do_save=True)
1025 1025
 
1026 1026
         for rev in page_1.revisions:
@@ -1118,7 +1118,7 @@ class TestContentApi(DefaultTest):
1118 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 1122
                                    'this is a page', do_save=True)
1123 1123
 
1124 1124
         for rev in page_1.revisions:
@@ -1187,21 +1187,21 @@ class TestContentApi(DefaultTest):
1187 1187
         )
1188 1188
 
1189 1189
         page_2 = cont_api_a.create(
1190
-            ContentType.Page,
1190
+            CONTENT_TYPES.Page.slug,
1191 1191
             workspace,
1192 1192
             None,
1193 1193
             'this is page1',
1194 1194
             do_save=True
1195 1195
         )
1196 1196
         page_3 = cont_api_a.create(
1197
-            ContentType.Thread,
1197
+            CONTENT_TYPES.Thread.slug,
1198 1198
             workspace,
1199 1199
             None,
1200 1200
             'this is page2',
1201 1201
             do_save=True
1202 1202
         )
1203 1203
         page_4 = cont_api_a.create(
1204
-            ContentType.File,
1204
+            CONTENT_TYPES.File.slug,
1205 1205
             workspace,
1206 1206
             None,
1207 1207
             'this is page3',
@@ -1285,7 +1285,7 @@ class TestContentApi(DefaultTest):
1285 1285
         )
1286 1286
 
1287 1287
         p = api.create(
1288
-            content_type=ContentType.Page,
1288
+            content_type_slug=CONTENT_TYPES.Page.slug,
1289 1289
             workspace=workspace,
1290 1290
             parent=None,
1291 1291
             label='this_is_a_page',
@@ -1312,7 +1312,7 @@ class TestContentApi(DefaultTest):
1312 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 1316
         eq_(u1id, content.owner_id)
1317 1317
         eq_(poid, content.owner_id)
1318 1318
 
@@ -1326,7 +1326,7 @@ class TestContentApi(DefaultTest):
1326 1326
             session=self.session,
1327 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 1330
         with new_revision(
1331 1331
            session=self.session,
1332 1332
            tm=transaction.manager,
@@ -1353,7 +1353,7 @@ class TestContentApi(DefaultTest):
1353 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 1357
         eq_(u2id, updated.owner_id,
1358 1358
             'the owner id should be {} (found {})'.format(u2id,
1359 1359
                                                           updated.owner_id))
@@ -1412,7 +1412,7 @@ class TestContentApi(DefaultTest):
1412 1412
         )
1413 1413
         with self.session.no_autoflush:
1414 1414
             page = api.create(
1415
-                content_type=ContentType.Page,
1415
+                content_type_slug=CONTENT_TYPES.Page.slug,
1416 1416
                 workspace=workspace,
1417 1417
                 label="same_content",
1418 1418
                 do_save=False
@@ -1426,7 +1426,7 @@ class TestContentApi(DefaultTest):
1426 1426
             session=self.session,
1427 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 1430
         with new_revision(
1431 1431
            session=self.session,
1432 1432
            tm=transaction.manager,
@@ -1495,7 +1495,7 @@ class TestContentApi(DefaultTest):
1495 1495
             config=self.app_config,
1496 1496
         )
1497 1497
         p = api.create(
1498
-            content_type=ContentType.File,
1498
+            content_type_slug=CONTENT_TYPES.File.slug,
1499 1499
             workspace=workspace,
1500 1500
             parent=None,
1501 1501
             label='this_is_a_page',
@@ -1524,7 +1524,7 @@ class TestContentApi(DefaultTest):
1524 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 1528
         eq_(u1id, content.owner_id)
1529 1529
         eq_(poid, content.owner_id)
1530 1530
 
@@ -1538,7 +1538,7 @@ class TestContentApi(DefaultTest):
1538 1538
             session=self.session,
1539 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 1542
         with new_revision(
1543 1543
             session=self.session,
1544 1544
             tm=transaction.manager,
@@ -1561,7 +1561,7 @@ class TestContentApi(DefaultTest):
1561 1561
             config=self.app_config,
1562 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 1565
         eq_(u2id, updated.owner_id,
1566 1566
             'the owner id should be {} (found {})'.format(u2id,
1567 1567
                                                           updated.owner_id))
@@ -1622,7 +1622,7 @@ class TestContentApi(DefaultTest):
1622 1622
         )
1623 1623
         with self.session.no_autoflush:
1624 1624
             page = api.create(
1625
-                content_type=ContentType.Page,
1625
+                content_type_slug=CONTENT_TYPES.Page.slug,
1626 1626
                 workspace=workspace,
1627 1627
                 label="same_content",
1628 1628
                 do_save=False
@@ -1641,7 +1641,7 @@ class TestContentApi(DefaultTest):
1641 1641
             session=self.session,
1642 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 1645
         with new_revision(
1646 1646
             session=self.session,
1647 1647
             tm=transaction.manager,
@@ -1713,7 +1713,7 @@ class TestContentApi(DefaultTest):
1713 1713
             config=self.app_config,
1714 1714
         )
1715 1715
         p = api.create(
1716
-            content_type=ContentType.File,
1716
+            content_type_slug=CONTENT_TYPES.File.slug,
1717 1717
             workspace=workspace,
1718 1718
             parent=None,
1719 1719
             label='this_is_a_page',
@@ -1741,7 +1741,7 @@ class TestContentApi(DefaultTest):
1741 1741
             config=self.app_config,
1742 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 1745
         eq_(u1id, content.owner_id)
1746 1746
         eq_(poid, content.owner_id)
1747 1747
 
@@ -1757,7 +1757,7 @@ class TestContentApi(DefaultTest):
1757 1757
             config=self.app_config,
1758 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 1761
         with new_revision(
1762 1762
                 session=self.session,
1763 1763
                 tm=transaction.manager,
@@ -1796,7 +1796,7 @@ class TestContentApi(DefaultTest):
1796 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 1800
         eq_(u2id, updated.owner_id,
1801 1801
             'the owner id should be {} (found {})'.format(u2id,
1802 1802
                                                           updated.owner_id))
@@ -1805,7 +1805,7 @@ class TestContentApi(DefaultTest):
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 1809
         with new_revision(
1810 1810
             session=self.session,
1811 1811
             tm=transaction.manager,
@@ -1874,7 +1874,7 @@ class TestContentApi(DefaultTest):
1874 1874
             show_deleted=True,
1875 1875
         )
1876 1876
         p = api.create(
1877
-            content_type=ContentType.File,
1877
+            content_type_slug=CONTENT_TYPES.File.slug,
1878 1878
             workspace=workspace,
1879 1879
             parent=None,
1880 1880
             label='this_is_a_page',
@@ -1900,7 +1900,7 @@ class TestContentApi(DefaultTest):
1900 1900
             config=self.app_config,
1901 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 1904
         eq_(u1id, content.owner_id)
1905 1905
         eq_(poid, content.owner_id)
1906 1906
 
@@ -1915,7 +1915,7 @@ class TestContentApi(DefaultTest):
1915 1915
             config=self.app_config,
1916 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 1919
         with new_revision(
1920 1920
                 session=self.session,
1921 1921
                 tm=transaction.manager,
@@ -1956,7 +1956,7 @@ class TestContentApi(DefaultTest):
1956 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 1960
         eq_(u2id, updated.owner_id,
1961 1961
             'the owner id should be {} (found {})'.format(u2id,
1962 1962
                                                           updated.owner_id))
@@ -1965,7 +1965,7 @@ class TestContentApi(DefaultTest):
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 1969
         with new_revision(
1970 1970
             tm=transaction.manager,
1971 1971
             session=self.session,
@@ -2016,14 +2016,14 @@ class TestContentApi(DefaultTest):
2016 2016
             session=self.session,
2017 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 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 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 2027
         with new_revision(
2028 2028
             session=self.session,
2029 2029
             tm=transaction.manager,
@@ -2032,11 +2032,11 @@ class TestContentApi(DefaultTest):
2032 2032
             firstly_created_but_recently_updated.description = 'Just an update'
2033 2033
         api.save(firstly_created_but_recently_updated)
2034 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 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 2040
         last_actives = api.get_last_active()
2041 2041
         assert len(last_actives) == 9
2042 2042
         # workspace_2 content
@@ -2088,13 +2088,13 @@ class TestContentApi(DefaultTest):
2088 2088
             session=self.session,
2089 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 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 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 2098
         with new_revision(
2099 2099
             session=self.session,
2100 2100
             tm=transaction.manager,
@@ -2103,8 +2103,8 @@ class TestContentApi(DefaultTest):
2103 2103
             firstly_created_but_recently_updated.description = 'Just an update'
2104 2104
         api.save(firstly_created_but_recently_updated)
2105 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 2108
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
2109 2109
 
2110 2110
         last_actives = api.get_last_active(workspace=workspace)
@@ -2153,13 +2153,13 @@ class TestContentApi(DefaultTest):
2153 2153
             session=self.session,
2154 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 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 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 2163
         with new_revision(
2164 2164
             session=self.session,
2165 2165
             tm=transaction.manager,
@@ -2168,8 +2168,8 @@ class TestContentApi(DefaultTest):
2168 2168
             firstly_created_but_recently_updated.description = 'Just an update'
2169 2169
         api.save(firstly_created_but_recently_updated)
2170 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 2173
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
2174 2174
 
2175 2175
         selected_contents = [
@@ -2228,13 +2228,13 @@ class TestContentApi(DefaultTest):
2228 2228
             session=self.session,
2229 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 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 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 2238
         with new_revision(
2239 2239
             session=self.session,
2240 2240
             tm=transaction.manager,
@@ -2243,8 +2243,8 @@ class TestContentApi(DefaultTest):
2243 2243
             firstly_created_but_recently_updated.description = 'Just an update'
2244 2244
         api.save(firstly_created_but_recently_updated)
2245 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 2248
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
2249 2249
 
2250 2250
         last_actives = api.get_last_active(workspace=workspace, limit=2)  # nopep8
@@ -2309,13 +2309,13 @@ class TestContentApi(DefaultTest):
2309 2309
             session=self.session,
2310 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 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 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 2319
         with new_revision(
2320 2320
             session=self.session,
2321 2321
             tm=transaction.manager,
@@ -2324,8 +2324,8 @@ class TestContentApi(DefaultTest):
2324 2324
             firstly_created_but_recently_updated.description = 'Just an update'
2325 2325
         api.save(firstly_created_but_recently_updated)
2326 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 2329
         comments = api.create_comment(workspace, firstly_created_but_recently_commented, 'juste a super comment', True)  # nopep8
2330 2330
 
2331 2331
         last_actives = api.get_last_active(workspace=workspace2)
@@ -2366,9 +2366,9 @@ class TestContentApi(DefaultTest):
2366 2366
             session=self.session,
2367 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 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 2372
                        'this is randomized label content', '', True)
2373 2373
 
2374 2374
         with new_revision(
@@ -2422,9 +2422,9 @@ class TestContentApi(DefaultTest):
2422 2422
             session=self.session,
2423 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 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 2428
                        'this is dummy label content', '', True)
2429 2429
 
2430 2430
         with new_revision(
@@ -2476,21 +2476,21 @@ class TestContentApi(DefaultTest):
2476 2476
             config=self.app_config,
2477 2477
         )
2478 2478
         a = api.create(
2479
-            content_type=ContentType.Folder,
2479
+            content_type_slug=CONTENT_TYPES.Folder.slug,
2480 2480
             workspace=workspace,
2481 2481
             parent=None,
2482 2482
             label='this is randomized folder',
2483 2483
             do_save=True
2484 2484
         )
2485 2485
         p1 = api.create(
2486
-            content_type=ContentType.Page,
2486
+            content_type_slug=CONTENT_TYPES.Page.slug,
2487 2487
             workspace=workspace,
2488 2488
             parent=a,
2489 2489
             label='this is dummy label content',
2490 2490
             do_save=True
2491 2491
         )
2492 2492
         p2 = api.create(
2493
-            content_type=ContentType.Page,
2493
+            content_type_slug=CONTENT_TYPES.Page.slug,
2494 2494
             workspace=workspace,
2495 2495
             parent=a,
2496 2496
             label='Hey ! Jon !',
@@ -2540,22 +2540,22 @@ class TestContentApi(DefaultTest):
2540 2540
         folder_1 = self._create_content_and_test(
2541 2541
             'folder_1',
2542 2542
             workspace=workspace,
2543
-            type=ContentType.Folder
2543
+            type=CONTENT_TYPES.Folder.slug
2544 2544
         )
2545 2545
         folder_2 = self._create_content_and_test(
2546 2546
             'folder_2',
2547 2547
             workspace=workspace,
2548
-            type=ContentType.Folder
2548
+            type=CONTENT_TYPES.Folder.slug
2549 2549
         )
2550 2550
         page_1 = self._create_content_and_test(
2551 2551
             'foo', workspace=workspace,
2552
-            type=ContentType.Page,
2552
+            type=CONTENT_TYPES.Page.slug,
2553 2553
             parent=folder_1
2554 2554
         )
2555 2555
         page_2 = self._create_content_and_test(
2556 2556
             'bar',
2557 2557
             workspace=workspace,
2558
-            type=ContentType.Page,
2558
+            type=CONTENT_TYPES.Page.slug,
2559 2559
             parent=folder_2
2560 2560
         )
2561 2561
 
@@ -2636,7 +2636,7 @@ class TestContentApiSecurity(DefaultTest):
2636 2636
             session=self.session,
2637 2637
             config=self.app_config,
2638 2638
         ).create(
2639
-            content_type=ContentType.Page,
2639
+            content_type_slug=CONTENT_TYPES.Page.slug,
2640 2640
             workspace=bob_workspace,
2641 2641
             label='bob_page',
2642 2642
             do_save=True,
@@ -2647,7 +2647,7 @@ class TestContentApiSecurity(DefaultTest):
2647 2647
             session=self.session,
2648 2648
             config=self.app_config,
2649 2649
         ).create(
2650
-            content_type=ContentType.Page,
2650
+            content_type_slug=CONTENT_TYPES.Page.slug,
2651 2651
             workspace=admin_workspace,
2652 2652
             label='admin_page',
2653 2653
             do_save=True,

+ 183 - 0
backend/tracim_backend/tests/library/test_user_api.py Целия файл

@@ -1,14 +1,19 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 import pytest
3 3
 import transaction
4
+from tracim_backend import models
4 5
 
5 6
 from tracim_backend.exceptions import AuthenticationFailed
7
+from tracim_backend.exceptions import TooShortAutocompleteString
6 8
 from tracim_backend.exceptions import UserDoesNotExist
7 9
 from tracim_backend.exceptions import UserNotActive
8 10
 from tracim_backend.lib.core.group import GroupApi
9 11
 from tracim_backend.lib.core.user import UserApi
12
+from tracim_backend.lib.core.userworkspace import RoleApi
13
+from tracim_backend.lib.core.workspace import WorkspaceApi
10 14
 from tracim_backend.models import User
11 15
 from tracim_backend.models.context_models import UserInContext
16
+from tracim_backend.models.data import UserRoleInWorkspace
12 17
 from tracim_backend.tests import DefaultTest
13 18
 from tracim_backend.tests import eq_
14 19
 
@@ -107,6 +112,184 @@ class TestUserApi(DefaultTest):
107 112
         # u1 + Admin user from BaseFixture
108 113
         assert 2 == len(users)
109 114
 
115
+    def test_unit__get_known__user__admin__too_short_acp_str(self):
116
+        api = UserApi(
117
+            current_user=None,
118
+            session=self.session,
119
+            config=self.config,
120
+        )
121
+        u1 = api.create_user(
122
+            email='email@email',
123
+            name='name',
124
+            do_notify=False,
125
+            do_save=True,
126
+        )
127
+        with pytest.raises(TooShortAutocompleteString):
128
+            api.get_known_user('e')
129
+
130
+    def test_unit__get_known__user__admin__by_email(self):
131
+        api = UserApi(
132
+            current_user=None,
133
+            session=self.session,
134
+            config=self.config,
135
+        )
136
+        u1 = api.create_user(
137
+            email='email@email',
138
+            name='name',
139
+            do_notify=False,
140
+            do_save=True,
141
+        )
142
+
143
+        users = api.get_known_user('email')
144
+        assert len(users) == 1
145
+        assert users[0] == u1
146
+
147
+    def test_unit__get_known__user__user__no_workspace_empty_known_user(self):
148
+        admin = self.session.query(models.User) \
149
+            .filter(models.User.email == 'admin@admin.admin') \
150
+            .one()
151
+        api = UserApi(
152
+            current_user=admin,
153
+            session=self.session,
154
+            config=self.config,
155
+        )
156
+        u1 = api.create_user(
157
+            email='email@email',
158
+            name='name',
159
+            do_notify=False,
160
+            do_save=True,
161
+        )
162
+        api2 = UserApi(
163
+            current_user=u1,
164
+            session=self.session,
165
+            config=self.config,
166
+        )
167
+        users = api2.get_known_user('email')
168
+        assert len(users) == 0
169
+
170
+    def test_unit__get_known__user__same_workspaces_users_by_name(self):
171
+        admin = self.session.query(models.User) \
172
+            .filter(models.User.email == 'admin@admin.admin') \
173
+            .one()
174
+        api = UserApi(
175
+            current_user=None,
176
+            session=self.session,
177
+            config=self.config,
178
+        )
179
+        u1 = api.create_user(
180
+            email='email@email',
181
+            name='name',
182
+            do_notify=False,
183
+            do_save=True,
184
+        )
185
+        u2 = api.create_user(
186
+            email='email2@email2',
187
+            name='name2',
188
+            do_notify=False,
189
+            do_save=True,
190
+        )
191
+        u3 = api.create_user(
192
+            email='notfound@notfound',
193
+            name='notfound',
194
+            do_notify=False,
195
+            do_save=True,
196
+        )
197
+        wapi = WorkspaceApi(
198
+            current_user=admin,
199
+            session=self.session,
200
+            config=self.app_config,
201
+        )
202
+        workspace = wapi.create_workspace(
203
+            'test workspace n°1',
204
+            save_now=True)
205
+        role_api = RoleApi(
206
+            current_user=admin,
207
+            session=self.session,
208
+            config=self.app_config,
209
+        )
210
+        role_api.create_one(u1, workspace, UserRoleInWorkspace.READER, False)
211
+        role_api.create_one(u2, workspace, UserRoleInWorkspace.READER, False)
212
+        role_api.create_one(u3, workspace, UserRoleInWorkspace.READER, False)
213
+        api2 = UserApi(
214
+            current_user=u1,
215
+            session=self.session,
216
+            config=self.config,
217
+        )
218
+        users = api2.get_known_user('name')
219
+        assert len(users) == 2
220
+        assert users[0] == u1
221
+        assert users[1] == u2
222
+
223
+    def test_unit__get_known__user__same_workspaces_users_by_email(self):
224
+        admin = self.session.query(models.User) \
225
+            .filter(models.User.email == 'admin@admin.admin') \
226
+            .one()
227
+        api = UserApi(
228
+            current_user=None,
229
+            session=self.session,
230
+            config=self.config,
231
+        )
232
+        u1 = api.create_user(
233
+            email='email@email',
234
+            name='name',
235
+            do_notify=False,
236
+            do_save=True,
237
+        )
238
+        u2 = api.create_user(
239
+            email='email2@email2',
240
+            name='name2',
241
+            do_notify=False,
242
+            do_save=True,
243
+        )
244
+        u3 = api.create_user(
245
+            email='notfound@notfound',
246
+            name='notfound',
247
+            do_notify=False,
248
+            do_save=True,
249
+        )
250
+        wapi = WorkspaceApi(
251
+            current_user=admin,
252
+            session=self.session,
253
+            config=self.app_config,
254
+        )
255
+        workspace = wapi.create_workspace(
256
+            'test workspace n°1',
257
+            save_now=True)
258
+        role_api = RoleApi(
259
+            current_user=admin,
260
+            session=self.session,
261
+            config=self.app_config,
262
+        )
263
+        role_api.create_one(u1, workspace, UserRoleInWorkspace.READER, False)
264
+        role_api.create_one(u2, workspace, UserRoleInWorkspace.READER, False)
265
+        role_api.create_one(u3, workspace, UserRoleInWorkspace.READER, False)
266
+        api2 = UserApi(
267
+            current_user=u1,
268
+            session=self.session,
269
+            config=self.config,
270
+        )
271
+        users = api2.get_known_user('email')
272
+        assert len(users) == 2
273
+        assert users[0] == u1
274
+        assert users[1] == u2
275
+
276
+    def test_unit__get_known__user__admin__by_name(self):
277
+        api = UserApi(
278
+            current_user=None,
279
+            session=self.session,
280
+            config=self.config,
281
+        )
282
+        u1 = api.create_user(
283
+            email='email@email',
284
+            name='name',
285
+            do_notify=False,
286
+            do_save=True,
287
+        )
288
+
289
+        users = api.get_known_user('nam')
290
+        assert len(users) == 1
291
+        assert users[0] == u1
292
+
110 293
     def test_unit__get_one__ok__nominal_case(self):
111 294
         api = UserApi(
112 295
             current_user=None,

+ 1 - 0
backend/tracim_backend/tests/library/test_webdav.py Целия файл

@@ -29,6 +29,7 @@ class TestWebdavFactory(StandardTest):
29 29
         :return:
30 30
         """
31 31
         tracim_settings = {
32
+            'website.base_url': 'http://localhost:6543',
32 33
             'sqlalchemy.url': 'sqlite:///:memory:',
33 34
             'user.auth_token.validity': '604800',
34 35
             'depot_storage_dir': '/tmp/test/depot',

+ 14 - 0
backend/tracim_backend/tests/library/tests_utils.py Целия файл

@@ -0,0 +1,14 @@
1
+import string
2
+
3
+from tracim_backend.lib.utils.utils import password_generator
4
+from tracim_backend.lib.utils.utils import ALLOWED_AUTOGEN_PASSWORD_CHAR
5
+from tracim_backend.lib.utils.utils import DEFAULT_PASSWORD_GEN_CHAR_LENGTH
6
+
7
+
8
+class TestPasswordGenerator(object):
9
+
10
+    def test_password_generator_ok_nominal_case(self):
11
+        password = password_generator()
12
+        assert len(password) == DEFAULT_PASSWORD_GEN_CHAR_LENGTH
13
+        for char in password:
14
+            assert char in ALLOWED_AUTOGEN_PASSWORD_CHAR

+ 6 - 6
backend/tracim_backend/tests/models/test_content.py Целия файл

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

+ 4 - 3
backend/tracim_backend/tests/models/test_content_revision.py Целия файл

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

+ 11 - 5
backend/tracim_backend/views/contents_api/comment_controller.py Целия файл

@@ -20,7 +20,7 @@ from tracim_backend.views.core_api.schemas import SetCommentSchema
20 20
 from tracim_backend.views.core_api.schemas import WorkspaceAndContentIdPathSchema
21 21
 from tracim_backend.views.core_api.schemas import NoContentSchema
22 22
 from tracim_backend.exceptions import EmptyCommentContentNotAllowed
23
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
23
+from tracim_backend.models.contents import CONTENT_TYPES
24 24
 from tracim_backend.models.revision_protection import new_revision
25 25
 from tracim_backend.models.data import UserRoleInWorkspace
26 26
 
@@ -41,13 +41,15 @@ class CommentController(Controller):
41 41
         # login = hapic_data.body
42 42
         app_config = request.registry.settings['CFG']
43 43
         api = ContentApi(
44
+            show_archived=True,
45
+            show_deleted=True,
44 46
             current_user=request.current_user,
45 47
             session=request.dbsession,
46 48
             config=app_config,
47 49
         )
48 50
         content = api.get_one(
49 51
             hapic_data.path.content_id,
50
-            content_type=ContentType.Any
52
+            content_type=CONTENT_TYPES.Any_SLUG
51 53
         )
52 54
         comments = content.get_comments()
53 55
         comments.sort(key=lambda comment: comment.created)
@@ -68,13 +70,15 @@ class CommentController(Controller):
68 70
         # login = hapic_data.body
69 71
         app_config = request.registry.settings['CFG']
70 72
         api = ContentApi(
73
+            show_archived=True,
74
+            show_deleted=True,
71 75
             current_user=request.current_user,
72 76
             session=request.dbsession,
73 77
             config=app_config,
74 78
         )
75 79
         content = api.get_one(
76 80
             hapic_data.path.content_id,
77
-            content_type=ContentType.Any
81
+            content_type=CONTENT_TYPES.Any_SLUG
78 82
         )
79 83
         comment = api.create_comment(
80 84
             content.workspace,
@@ -97,6 +101,8 @@ class CommentController(Controller):
97 101
         """
98 102
         app_config = request.registry.settings['CFG']
99 103
         api = ContentApi(
104
+            show_archived=True,
105
+            show_deleted=True,
100 106
             current_user=request.current_user,
101 107
             session=request.dbsession,
102 108
             config=app_config,
@@ -109,12 +115,12 @@ class CommentController(Controller):
109 115
         workspace = wapi.get_one(hapic_data.path.workspace_id)
110 116
         parent = api.get_one(
111 117
             hapic_data.path.content_id,
112
-            content_type=ContentType.Any,
118
+            content_type=CONTENT_TYPES.Any_SLUG,
113 119
             workspace=workspace
114 120
         )
115 121
         comment = api.get_one(
116 122
             hapic_data.path.comment_id,
117
-            content_type=ContentType.Comment,
123
+            content_type=CONTENT_TYPES.Comment.slug,
118 124
             workspace=workspace,
119 125
             parent=parent,
120 126
         )

+ 42 - 14
backend/tracim_backend/views/contents_api/file_controller.py Целия файл

@@ -32,7 +32,7 @@ from tracim_backend.lib.utils.authorization import require_workspace_role
32 32
 from tracim_backend.models.data import UserRoleInWorkspace
33 33
 from tracim_backend.models.context_models import ContentInContext
34 34
 from tracim_backend.models.context_models import RevisionInContext
35
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
35
+from tracim_backend.models.contents import CONTENT_TYPES
36 36
 from tracim_backend.models.contents import file_type
37 37
 from tracim_backend.models.revision_protection import new_revision
38 38
 from tracim_backend.exceptions import EmptyLabelNotAllowed
@@ -61,13 +61,15 @@ class FileController(Controller):
61 61
         """
62 62
         app_config = request.registry.settings['CFG']
63 63
         api = ContentApi(
64
+            show_archived=True,
65
+            show_deleted=True,
64 66
             current_user=request.current_user,
65 67
             session=request.dbsession,
66 68
             config=app_config,
67 69
         )
68 70
         content = api.get_one(
69 71
             hapic_data.path.content_id,
70
-            content_type=ContentType.Any
72
+            content_type=CONTENT_TYPES.Any_SLUG
71 73
         )
72 74
         file = request.POST['files']
73 75
         with new_revision(
@@ -95,13 +97,15 @@ class FileController(Controller):
95 97
         """
96 98
         app_config = request.registry.settings['CFG']
97 99
         api = ContentApi(
100
+            show_archived=True,
101
+            show_deleted=True,
98 102
             current_user=request.current_user,
99 103
             session=request.dbsession,
100 104
             config=app_config,
101 105
         )
102 106
         content = api.get_one(
103 107
             hapic_data.path.content_id,
104
-            content_type=ContentType.Any
108
+            content_type=CONTENT_TYPES.Any_SLUG
105 109
         )
106 110
         file = DepotManager.get().get(content.depot_file)
107 111
         response = request.response
@@ -120,13 +124,15 @@ class FileController(Controller):
120 124
         """
121 125
         app_config = request.registry.settings['CFG']
122 126
         api = ContentApi(
127
+            show_archived=True,
128
+            show_deleted=True,
123 129
             current_user=request.current_user,
124 130
             session=request.dbsession,
125 131
             config=app_config,
126 132
         )
127 133
         content = api.get_one(
128 134
             hapic_data.path.content_id,
129
-            content_type=ContentType.Any
135
+            content_type=CONTENT_TYPES.Any_SLUG
130 136
         )
131 137
         revision = api.get_one_revision(
132 138
             revision_id=hapic_data.path.revision_id,
@@ -154,13 +160,15 @@ class FileController(Controller):
154 160
         """
155 161
         app_config = request.registry.settings['CFG']
156 162
         api = ContentApi(
163
+            show_archived=True,
164
+            show_deleted=True,
157 165
             current_user=request.current_user,
158 166
             session=request.dbsession,
159 167
             config=app_config,
160 168
         )
161 169
         content = api.get_one(
162 170
             hapic_data.path.content_id,
163
-            content_type=ContentType.Any
171
+            content_type=CONTENT_TYPES.Any_SLUG
164 172
         )
165 173
         pdf_preview_path = api.get_pdf_preview_path(
166 174
             content.content_id,
@@ -181,13 +189,15 @@ class FileController(Controller):
181 189
         """
182 190
         app_config = request.registry.settings['CFG']
183 191
         api = ContentApi(
192
+            show_archived=True,
193
+            show_deleted=True,
184 194
             current_user=request.current_user,
185 195
             session=request.dbsession,
186 196
             config=app_config,
187 197
         )
188 198
         content = api.get_one(
189 199
             hapic_data.path.content_id,
190
-            content_type=ContentType.Any
200
+            content_type=CONTENT_TYPES.Any_SLUG
191 201
         )
192 202
         pdf_preview_path = api.get_full_pdf_preview_path(content.revision_id)
193 203
         return FileResponse(pdf_preview_path)
@@ -205,13 +215,15 @@ class FileController(Controller):
205 215
         """
206 216
         app_config = request.registry.settings['CFG']
207 217
         api = ContentApi(
218
+            show_archived=True,
219
+            show_deleted=True,
208 220
             current_user=request.current_user,
209 221
             session=request.dbsession,
210 222
             config=app_config,
211 223
         )
212 224
         content = api.get_one(
213 225
             hapic_data.path.content_id,
214
-            content_type=ContentType.Any
226
+            content_type=CONTENT_TYPES.Any_SLUG
215 227
         )
216 228
         revision = api.get_one_revision(
217 229
             revision_id=hapic_data.path.revision_id,
@@ -238,13 +250,15 @@ class FileController(Controller):
238 250
         """
239 251
         app_config = request.registry.settings['CFG']
240 252
         api = ContentApi(
253
+            show_archived=True,
254
+            show_deleted=True,
241 255
             current_user=request.current_user,
242 256
             session=request.dbsession,
243 257
             config=app_config,
244 258
         )
245 259
         content = api.get_one(
246 260
             hapic_data.path.content_id,
247
-            content_type=ContentType.Any
261
+            content_type=CONTENT_TYPES.Any_SLUG
248 262
         )
249 263
         allowed_dim = api.get_jpg_preview_allowed_dim()
250 264
         jpg_preview_path = api.get_jpg_preview_path(
@@ -270,13 +284,15 @@ class FileController(Controller):
270 284
         """
271 285
         app_config = request.registry.settings['CFG']
272 286
         api = ContentApi(
287
+            show_archived=True,
288
+            show_deleted=True,
273 289
             current_user=request.current_user,
274 290
             session=request.dbsession,
275 291
             config=app_config,
276 292
         )
277 293
         content = api.get_one(
278 294
             hapic_data.path.content_id,
279
-            content_type=ContentType.Any
295
+            content_type=CONTENT_TYPES.Any_SLUG
280 296
         )
281 297
         jpg_preview_path = api.get_jpg_preview_path(
282 298
             content_id=content.content_id,
@@ -301,13 +317,15 @@ class FileController(Controller):
301 317
         """
302 318
         app_config = request.registry.settings['CFG']
303 319
         api = ContentApi(
320
+            show_archived=True,
321
+            show_deleted=True,
304 322
             current_user=request.current_user,
305 323
             session=request.dbsession,
306 324
             config=app_config,
307 325
         )
308 326
         content = api.get_one(
309 327
             hapic_data.path.content_id,
310
-            content_type=ContentType.Any
328
+            content_type=CONTENT_TYPES.Any_SLUG
311 329
         )
312 330
         revision = api.get_one_revision(
313 331
             revision_id=hapic_data.path.revision_id,
@@ -334,6 +352,8 @@ class FileController(Controller):
334 352
         """
335 353
         app_config = request.registry.settings['CFG']
336 354
         api = ContentApi(
355
+            show_archived=True,
356
+            show_deleted=True,
337 357
             current_user=request.current_user,
338 358
             session=request.dbsession,
339 359
             config=app_config,
@@ -352,13 +372,15 @@ class FileController(Controller):
352 372
         """
353 373
         app_config = request.registry.settings['CFG']
354 374
         api = ContentApi(
375
+            show_archived=True,
376
+            show_deleted=True,
355 377
             current_user=request.current_user,
356 378
             session=request.dbsession,
357 379
             config=app_config,
358 380
         )
359 381
         content = api.get_one(
360 382
             hapic_data.path.content_id,
361
-            content_type=ContentType.Any
383
+            content_type=CONTENT_TYPES.Any_SLUG
362 384
         )
363 385
         return api.get_content_in_context(content)
364 386
 
@@ -375,13 +397,15 @@ class FileController(Controller):
375 397
         """
376 398
         app_config = request.registry.settings['CFG']
377 399
         api = ContentApi(
400
+            show_archived=True,
401
+            show_deleted=True,
378 402
             current_user=request.current_user,
379 403
             session=request.dbsession,
380 404
             config=app_config,
381 405
         )
382 406
         content = api.get_one(
383 407
             hapic_data.path.content_id,
384
-            content_type=ContentType.Any
408
+            content_type=CONTENT_TYPES.Any_SLUG
385 409
         )
386 410
         with new_revision(
387 411
                 session=request.dbsession,
@@ -413,13 +437,15 @@ class FileController(Controller):
413 437
         """
414 438
         app_config = request.registry.settings['CFG']
415 439
         api = ContentApi(
440
+            show_archived=True,
441
+            show_deleted=True,
416 442
             current_user=request.current_user,
417 443
             session=request.dbsession,
418 444
             config=app_config,
419 445
         )
420 446
         content = api.get_one(
421 447
             hapic_data.path.content_id,
422
-            content_type=ContentType.Any
448
+            content_type=CONTENT_TYPES.Any_SLUG
423 449
         )
424 450
         revisions = content.revisions
425 451
         return [
@@ -440,13 +466,15 @@ class FileController(Controller):
440 466
         """
441 467
         app_config = request.registry.settings['CFG']
442 468
         api = ContentApi(
469
+            show_archived=True,
470
+            show_deleted=True,
443 471
             current_user=request.current_user,
444 472
             session=request.dbsession,
445 473
             config=app_config,
446 474
         )
447 475
         content = api.get_one(
448 476
             hapic_data.path.content_id,
449
-            content_type=ContentType.Any
477
+            content_type=CONTENT_TYPES.Any_SLUG
450 478
         )
451 479
         with new_revision(
452 480
                 session=request.dbsession,

+ 13 - 5
backend/tracim_backend/views/contents_api/html_document_controller.py Целия файл

@@ -26,7 +26,7 @@ from tracim_backend.lib.utils.authorization import require_workspace_role
26 26
 from tracim_backend.exceptions import EmptyLabelNotAllowed
27 27
 from tracim_backend.models.context_models import ContentInContext
28 28
 from tracim_backend.models.context_models import RevisionInContext
29
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
29
+from tracim_backend.models.contents import CONTENT_TYPES
30 30
 from tracim_backend.models.contents import html_documents_type
31 31
 from tracim_backend.models.revision_protection import new_revision
32 32
 
@@ -46,13 +46,15 @@ class HTMLDocumentController(Controller):
46 46
         """
47 47
         app_config = request.registry.settings['CFG']
48 48
         api = ContentApi(
49
+            show_archived=True,
50
+            show_deleted=True,
49 51
             current_user=request.current_user,
50 52
             session=request.dbsession,
51 53
             config=app_config,
52 54
         )
53 55
         content = api.get_one(
54 56
             hapic_data.path.content_id,
55
-            content_type=ContentType.Any
57
+            content_type=CONTENT_TYPES.Any_SLUG
56 58
         )
57 59
         return api.get_content_in_context(content)
58 60
 
@@ -69,13 +71,15 @@ class HTMLDocumentController(Controller):
69 71
         """
70 72
         app_config = request.registry.settings['CFG']
71 73
         api = ContentApi(
74
+            show_archived=True,
75
+            show_deleted=True,
72 76
             current_user=request.current_user,
73 77
             session=request.dbsession,
74 78
             config=app_config,
75 79
         )
76 80
         content = api.get_one(
77 81
             hapic_data.path.content_id,
78
-            content_type=ContentType.Any
82
+            content_type=CONTENT_TYPES.Any_SLUG
79 83
         )
80 84
         with new_revision(
81 85
                 session=request.dbsession,
@@ -107,13 +111,15 @@ class HTMLDocumentController(Controller):
107 111
         """
108 112
         app_config = request.registry.settings['CFG']
109 113
         api = ContentApi(
114
+            show_archived=True,
115
+            show_deleted=True,
110 116
             current_user=request.current_user,
111 117
             session=request.dbsession,
112 118
             config=app_config,
113 119
         )
114 120
         content = api.get_one(
115 121
             hapic_data.path.content_id,
116
-            content_type=ContentType.Any
122
+            content_type=CONTENT_TYPES.Any_SLUG
117 123
         )
118 124
         revisions = content.revisions
119 125
         return [
@@ -138,13 +144,15 @@ class HTMLDocumentController(Controller):
138 144
         """
139 145
         app_config = request.registry.settings['CFG']
140 146
         api = ContentApi(
147
+            show_archived=True,
148
+            show_deleted=True,
141 149
             current_user=request.current_user,
142 150
             session=request.dbsession,
143 151
             config=app_config,
144 152
         )
145 153
         content = api.get_one(
146 154
             hapic_data.path.content_id,
147
-            content_type=ContentType.Any
155
+            content_type=CONTENT_TYPES.Any_SLUG
148 156
         )
149 157
         with new_revision(
150 158
                 session=request.dbsession,

+ 13 - 5
backend/tracim_backend/views/contents_api/threads_controller.py Целия файл

@@ -25,7 +25,7 @@ from tracim_backend.lib.utils.authorization import require_workspace_role
25 25
 from tracim_backend.exceptions import EmptyLabelNotAllowed
26 26
 from tracim_backend.models.context_models import ContentInContext
27 27
 from tracim_backend.models.context_models import RevisionInContext
28
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
28
+from tracim_backend.models.contents import CONTENT_TYPES
29 29
 from tracim_backend.models.contents import thread_type
30 30
 from tracim_backend.models.revision_protection import new_revision
31 31
 
@@ -45,13 +45,15 @@ class ThreadController(Controller):
45 45
         """
46 46
         app_config = request.registry.settings['CFG']
47 47
         api = ContentApi(
48
+            show_archived=True,
49
+            show_deleted=True,
48 50
             current_user=request.current_user,
49 51
             session=request.dbsession,
50 52
             config=app_config,
51 53
         )
52 54
         content = api.get_one(
53 55
             hapic_data.path.content_id,
54
-            content_type=ContentType.Any
56
+            content_type=CONTENT_TYPES.Any_SLUG
55 57
         )
56 58
         return api.get_content_in_context(content)
57 59
 
@@ -68,13 +70,15 @@ class ThreadController(Controller):
68 70
         """
69 71
         app_config = request.registry.settings['CFG']
70 72
         api = ContentApi(
73
+            show_archived=True,
74
+            show_deleted=True,
71 75
             current_user=request.current_user,
72 76
             session=request.dbsession,
73 77
             config=app_config,
74 78
         )
75 79
         content = api.get_one(
76 80
             hapic_data.path.content_id,
77
-            content_type=ContentType.Any
81
+            content_type=CONTENT_TYPES.Any_SLUG
78 82
         )
79 83
         with new_revision(
80 84
                 session=request.dbsession,
@@ -106,13 +110,15 @@ class ThreadController(Controller):
106 110
         """
107 111
         app_config = request.registry.settings['CFG']
108 112
         api = ContentApi(
113
+            show_archived=True,
114
+            show_deleted=True,
109 115
             current_user=request.current_user,
110 116
             session=request.dbsession,
111 117
             config=app_config,
112 118
         )
113 119
         content = api.get_one(
114 120
             hapic_data.path.content_id,
115
-            content_type=ContentType.Any
121
+            content_type=CONTENT_TYPES.Any_SLUG
116 122
         )
117 123
         revisions = content.revisions
118 124
         return [
@@ -132,13 +138,15 @@ class ThreadController(Controller):
132 138
         """
133 139
         app_config = request.registry.settings['CFG']
134 140
         api = ContentApi(
141
+            show_archived=True,
142
+            show_deleted=True,
135 143
             current_user=request.current_user,
136 144
             session=request.dbsession,
137 145
             config=app_config,
138 146
         )
139 147
         content = api.get_one(
140 148
             hapic_data.path.content_id,
141
-            content_type=ContentType.Any
149
+            content_type=CONTENT_TYPES.Any_SLUG
142 150
         )
143 151
         with new_revision(
144 152
                 session=request.dbsession,

+ 26 - 12
backend/tracim_backend/views/core_api/schemas.py Целия файл

@@ -2,15 +2,17 @@
2 2
 import marshmallow
3 3
 from marshmallow import post_load
4 4
 from marshmallow.validate import OneOf
5
+from marshmallow.validate import Length
5 6
 from marshmallow.validate import Range
6 7
 
7 8
 from tracim_backend.lib.utils.utils import DATETIME_FORMAT
8 9
 from tracim_backend.models.auth import Profile
9 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
10 13
 from tracim_backend.models.contents import open_status
11
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
12
-from tracim_backend.models.contents import ContentStatusLegacy as ContentStatus
13 14
 from tracim_backend.models.context_models import ActiveContentFilter
15
+from tracim_backend.models.context_models import AutocompleteQuery
14 16
 from tracim_backend.models.context_models import ContentIdsQuery
15 17
 from tracim_backend.models.context_models import UserWorkspaceAndContentPath
16 18
 from tracim_backend.models.context_models import ContentCreation
@@ -46,7 +48,7 @@ class UserDigestSchema(marshmallow.Schema):
46 48
     user_id = marshmallow.fields.Int(dump_only=True, example=3)
47 49
     avatar_url = marshmallow.fields.Url(
48 50
         allow_none=True,
49
-        example="/api/v2/assets/avatars/suri-cate.jpg",
51
+        example="/api/v2/asset/avatars/suri-cate.jpg",
50 52
         description="avatar_url is the url to the image file. "
51 53
                     "If no avatar, then set it to null "
52 54
                     "(and frontend will interpret this with a default avatar)",
@@ -292,6 +294,17 @@ class CommentsPathSchema(WorkspaceAndContentIdPathSchema):
292 294
         return CommentPath(**data)
293 295
 
294 296
 
297
+class AutocompleteQuerySchema(marshmallow.Schema):
298
+    acp = marshmallow.fields.Str(
299
+        example='test',
300
+        description='search text to query',
301
+        validate=Length(min=2),
302
+    )
303
+    @post_load
304
+    def make_autocomplete(self, data):
305
+        return AutocompleteQuery(**data)
306
+
307
+
295 308
 class PageQuerySchema(marshmallow.Schema):
296 309
     page = marshmallow.fields.Int(
297 310
         example=2,
@@ -342,9 +355,9 @@ class FilterContentQuerySchema(marshmallow.Schema):
342 355
         validate=Range(min=0, max=1, error="Value must be 0 or 1"),
343 356
     )
344 357
     content_type = marshmallow.fields.String(
345
-        example=ContentType.Any,
346
-        default=ContentType.Any,
347
-        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())
348 361
     )
349 362
 
350 363
     @post_load
@@ -594,7 +607,7 @@ class StatusSchema(marshmallow.Schema):
594 607
 class ContentTypeSchema(marshmallow.Schema):
595 608
     slug = marshmallow.fields.String(
596 609
         example='pagehtml',
597
-        validate=OneOf(ContentType.allowed_types()),
610
+        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),
598 611
     )
599 612
     fa_icon = marshmallow.fields.String(
600 613
         example='fa-file-text-o',
@@ -648,7 +661,7 @@ class ContentCreationSchema(marshmallow.Schema):
648 661
     )
649 662
     content_type = marshmallow.fields.String(
650 663
         example='html-document',
651
-        validate=OneOf(ContentType.allowed_types_for_folding()),  # nopep8
664
+        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),
652 665
     )
653 666
     parent_id = marshmallow.fields.Integer(
654 667
         example=35,
@@ -683,12 +696,12 @@ class ContentDigestSchema(marshmallow.Schema):
683 696
     label = marshmallow.fields.Str(example='Intervention Report 12')
684 697
     content_type = marshmallow.fields.Str(
685 698
         example='html-document',
686
-        validate=OneOf(ContentType.allowed_types()),
699
+        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),
687 700
     )
688 701
     sub_content_types = marshmallow.fields.List(
689 702
         marshmallow.fields.String(
690 703
             example='html-content',
691
-            validate=OneOf(ContentType.allowed_types())
704
+            validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug())
692 705
         ),
693 706
         description='list of content types allowed as sub contents. '
694 707
                     'This field is required for folder contents, '
@@ -696,7 +709,7 @@ class ContentDigestSchema(marshmallow.Schema):
696 709
     )
697 710
     status = marshmallow.fields.Str(
698 711
         example='closed-deprecated',
699
-        validate=OneOf(ContentStatus.allowed_values()),
712
+        validate=OneOf(CONTENT_STATUS.get_all_slugs_values()),
700 713
         description='this slug is found in content_type available statuses',
701 714
         default=open_status
702 715
     )
@@ -721,6 +734,7 @@ class ReadStatusSchema(marshmallow.Schema):
721 734
 # Content
722 735
 #####
723 736
 
737
+
724 738
 class ContentSchema(ContentDigestSchema):
725 739
     current_revision_id = marshmallow.fields.Int(example=12)
726 740
     created = marshmallow.fields.DateTime(
@@ -840,7 +854,7 @@ class FileContentModifySchema(TextBasedContentModifySchema):
840 854
 class SetContentStatusSchema(marshmallow.Schema):
841 855
     status = marshmallow.fields.Str(
842 856
         example='closed-deprecated',
843
-        validate=OneOf(ContentStatus.allowed_values()),
857
+        validate=OneOf(CONTENT_STATUS.get_all_slugs_values()),
844 858
         description='this slug is found in content_type available statuses',
845 859
         default=open_status,
846 860
         required=True,

+ 3 - 3
backend/tracim_backend/views/core_api/system_controller.py Целия файл

@@ -5,7 +5,7 @@ from tracim_backend.exceptions import InsufficientUserProfile
5 5
 from tracim_backend.lib.utils.authorization import require_profile
6 6
 from tracim_backend.models import Group
7 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 10
 try:  # Python 3.5+
11 11
     from http import HTTPStatus
@@ -39,8 +39,8 @@ class SystemController(Controller):
39 39
         """
40 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 44
         return content_types
45 45
 
46 46
     def bind(self, configurator: Configurator) -> None:

+ 59 - 2
backend/tracim_backend/views/core_api/user_controller.py Целия файл

@@ -1,4 +1,5 @@
1 1
 from pyramid.config import Configurator
2
+
2 3
 try:  # Python 3.5+
3 4
     from http import HTTPStatus
4 5
 except ImportError:
@@ -11,13 +12,14 @@ from tracim_backend.lib.core.group import GroupApi
11 12
 from tracim_backend.lib.core.user import UserApi
12 13
 from tracim_backend.lib.core.workspace import WorkspaceApi
13 14
 from tracim_backend.lib.core.content import ContentApi
14
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
15 15
 from tracim_backend.views.controllers import Controller
16 16
 from tracim_backend.lib.utils.authorization import require_same_user_or_profile
17 17
 from tracim_backend.lib.utils.authorization import require_profile
18 18
 from tracim_backend.exceptions import WrongUserPassword
19 19
 from tracim_backend.exceptions import PasswordDoNotMatch
20 20
 from tracim_backend.views.core_api.schemas import UserSchema
21
+from tracim_backend.views.core_api.schemas import AutocompleteQuerySchema
22
+from tracim_backend.views.core_api.schemas import UserDigestSchema
21 23
 from tracim_backend.views.core_api.schemas import SetEmailSchema
22 24
 from tracim_backend.views.core_api.schemas import SetPasswordSchema
23 25
 from tracim_backend.views.core_api.schemas import UserInfosSchema
@@ -32,6 +34,7 @@ from tracim_backend.views.core_api.schemas import UserWorkspaceAndContentIdPathS
32 34
 from tracim_backend.views.core_api.schemas import ContentDigestSchema
33 35
 from tracim_backend.views.core_api.schemas import ActiveContentFilterQuerySchema
34 36
 from tracim_backend.views.core_api.schemas import WorkspaceDigestSchema
37
+from tracim_backend.models.contents import CONTENT_TYPES
35 38
 
36 39
 SWAGGER_TAG__USER_ENDPOINTS = 'Users'
37 40
 
@@ -76,6 +79,46 @@ class UserController(Controller):
76 79
         return uapi.get_user_with_context(request.candidate_user)
77 80
 
78 81
     @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
82
+    @require_profile(Group.TIM_ADMIN)
83
+    @hapic.output_body(UserDigestSchema(many=True))
84
+    def users(self, context, request: TracimRequest, hapic_data=None):
85
+        """
86
+        Get all users
87
+        """
88
+        app_config = request.registry.settings['CFG']
89
+        uapi = UserApi(
90
+            current_user=request.current_user,  # User
91
+            session=request.dbsession,
92
+            config=app_config,
93
+        )
94
+        users = uapi.get_all()
95
+        context_users = [
96
+            uapi.get_user_with_context(user) for user in users
97
+        ]
98
+        return context_users
99
+
100
+    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
101
+    @require_same_user_or_profile(Group.TIM_MANAGER)
102
+    @hapic.input_path(UserIdPathSchema())
103
+    @hapic.input_query(AutocompleteQuerySchema())
104
+    @hapic.output_body(UserDigestSchema(many=True))
105
+    def known_members(self, context, request: TracimRequest, hapic_data=None):
106
+        """
107
+        Get known users list
108
+        """
109
+        app_config = request.registry.settings['CFG']
110
+        uapi = UserApi(
111
+            current_user=request.candidate_user,  # User
112
+            session=request.dbsession,
113
+            config=app_config,
114
+        )
115
+        users = uapi.get_known_user(acp=hapic_data.query.acp)
116
+        context_users = [
117
+            uapi.get_user_with_context(user) for user in users
118
+        ]
119
+        return context_users
120
+
121
+    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
79 122
     @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN)
80 123
     @require_same_user_or_profile(Group.TIM_ADMIN)
81 124
     @hapic.input_body(SetEmailSchema())
@@ -271,7 +314,7 @@ class UserController(Controller):
271 314
             before_content = api.get_one(
272 315
                 content_id=content_filter.before_content_id,
273 316
                 workspace=workspace,
274
-                content_type=ContentType.Any
317
+                content_type=CONTENT_TYPES.Any_SLUG
275 318
             )
276 319
         last_actives = api.get_last_active(
277 320
             workspace=workspace,
@@ -328,6 +371,8 @@ class UserController(Controller):
328 371
         """
329 372
         app_config = request.registry.settings['CFG']
330 373
         api = ContentApi(
374
+            show_archived=True,
375
+            show_deleted=True,
331 376
             current_user=request.candidate_user,
332 377
             session=request.dbsession,
333 378
             config=app_config,
@@ -345,6 +390,8 @@ class UserController(Controller):
345 390
         """
346 391
         app_config = request.registry.settings['CFG']
347 392
         api = ContentApi(
393
+            show_archived=True,
394
+            show_deleted=True,
348 395
             current_user=request.candidate_user,
349 396
             session=request.dbsession,
350 397
             config=app_config,
@@ -362,6 +409,8 @@ class UserController(Controller):
362 409
         """
363 410
         app_config = request.registry.settings['CFG']
364 411
         api = ContentApi(
412
+            show_archived=True,
413
+            show_deleted=True,
365 414
             current_user=request.candidate_user,
366 415
             session=request.dbsession,
367 416
             config=app_config,
@@ -383,6 +432,14 @@ class UserController(Controller):
383 432
         configurator.add_route('user', '/users/{user_id}', request_method='GET')  # nopep8
384 433
         configurator.add_view(self.user, route_name='user')
385 434
 
435
+        # users lists
436
+        configurator.add_route('users', '/users', request_method='GET')  # nopep8
437
+        configurator.add_view(self.users, route_name='users')
438
+
439
+        # known members lists
440
+        configurator.add_route('known_members', '/users/{user_id}/known_members', request_method='GET')  # nopep8
441
+        configurator.add_view(self.known_members, route_name='known_members')
442
+
386 443
         # set user email
387 444
         configurator.add_route('set_user_email', '/users/{user_id}/email', request_method='PUT')  # nopep8
388 445
         configurator.add_view(self.set_user_email, route_name='set_user_email')

+ 28 - 13
backend/tracim_backend/views/core_api/workspace_controller.py Целия файл

@@ -31,6 +31,7 @@ from tracim_backend.exceptions import ContentNotFound
31 31
 from tracim_backend.exceptions import WorkspacesDoNotMatch
32 32
 from tracim_backend.exceptions import ParentNotFound
33 33
 from tracim_backend.views.controllers import Controller
34
+from tracim_backend.lib.utils.utils import password_generator
34 35
 from tracim_backend.views.core_api.schemas import FilterContentQuerySchema
35 36
 from tracim_backend.views.core_api.schemas import WorkspaceMemberCreationSchema
36 37
 from tracim_backend.views.core_api.schemas import WorkspaceMemberInviteSchema
@@ -46,7 +47,7 @@ from tracim_backend.views.core_api.schemas import ContentDigestSchema
46 47
 from tracim_backend.views.core_api.schemas import WorkspaceSchema
47 48
 from tracim_backend.views.core_api.schemas import WorkspaceIdPathSchema
48 49
 from tracim_backend.views.core_api.schemas import WorkspaceMemberSchema
49
-from tracim_backend.models.contents import ContentTypeLegacy as ContentType
50
+from tracim_backend.models.contents import CONTENT_TYPES
50 51
 from tracim_backend.models.revision_protection import new_revision
51 52
 
52 53
 SWAGGER_TAG_WORKSPACE_ENDPOINTS = 'Workspaces'
@@ -213,12 +214,18 @@ class WorkspaceController(Controller):
213 214
                 # TODO - G.M - 2018-07-05 - [UserCreation] Reenable email
214 215
                 # notification for creation
215 216
                 user = uapi.create_user(
216
-                    hapic_data.body.user_email_or_public_name,
217
-                    do_notify=False
217
+                    email=hapic_data.body.user_email_or_public_name,
218
+                    password= password_generator(),
219
+                    do_notify=True
218 220
                 )  # nopep8
219 221
                 newly_created = True
222
+                if app_config.EMAIL_NOTIFICATION_ACTIVATED and \
223
+                        app_config.EMAIL_NOTIFICATION_PROCESSING_MODE.lower() == 'sync':
224
+                    email_sent = True
225
+
220 226
             except EmailValidationFailed:
221 227
                 raise UserCreationFailed('no valid mail given')
228
+
222 229
         role = rapi.create_one(
223 230
             user=user,
224 231
             workspace=request.current_workspace,
@@ -259,7 +266,7 @@ class WorkspaceController(Controller):
259 266
         contents = api.get_all(
260 267
             parent_id=content_filter.parent_id,
261 268
             workspace=request.current_workspace,
262
-            content_type=content_filter.content_type or ContentType.Any,
269
+            content_type=content_filter.content_type or CONTENT_TYPES.Any_SLUG,
263 270
         )
264 271
         contents = [
265 272
             api.get_content_in_context(content) for content in contents
@@ -291,14 +298,14 @@ class WorkspaceController(Controller):
291 298
         parent = None
292 299
         if creation_data.parent_id:
293 300
             try:
294
-                parent = api.get_one(content_id=creation_data.parent_id, content_type=ContentType.Any)  # nopep8
301
+                parent = api.get_one(content_id=creation_data.parent_id, content_type=CONTENT_TYPES.Any_SLUG)  # nopep8
295 302
             except ContentNotFound as exc:
296 303
                 raise ParentNotFound(
297 304
                     'Parent with content_id {} not found'.format(creation_data.parent_id)
298 305
                 ) from exc
299 306
         content = api.create(
300 307
             label=creation_data.label,
301
-            content_type=creation_data.content_type,
308
+            content_type_slug=creation_data.content_type,
302 309
             workspace=request.current_workspace,
303 310
             parent=parent,
304 311
         )
@@ -327,16 +334,18 @@ class WorkspaceController(Controller):
327 334
         move_data = hapic_data.body
328 335
 
329 336
         api = ContentApi(
337
+            show_archived=True,
338
+            show_deleted=True,
330 339
             current_user=request.current_user,
331 340
             session=request.dbsession,
332 341
             config=app_config,
333 342
         )
334 343
         content = api.get_one(
335 344
             path_data.content_id,
336
-            content_type=ContentType.Any
345
+            content_type=CONTENT_TYPES.Any_SLUG
337 346
         )
338 347
         new_parent = api.get_one(
339
-            move_data.new_parent_id, content_type=ContentType.Any
348
+            move_data.new_parent_id, content_type=CONTENT_TYPES.Any_SLUG
340 349
         )
341 350
 
342 351
         new_workspace = request.candidate_workspace
@@ -354,7 +363,7 @@ class WorkspaceController(Controller):
354 363
             )
355 364
         updated_content = api.get_one(
356 365
             path_data.content_id,
357
-            content_type=ContentType.Any
366
+            content_type=CONTENT_TYPES.Any_SLUG
358 367
         )
359 368
         return api.get_content_in_context(updated_content)
360 369
 
@@ -374,13 +383,15 @@ class WorkspaceController(Controller):
374 383
         app_config = request.registry.settings['CFG']
375 384
         path_data = hapic_data.path
376 385
         api = ContentApi(
386
+            show_archived=True,
387
+            show_deleted=True,
377 388
             current_user=request.current_user,
378 389
             session=request.dbsession,
379 390
             config=app_config,
380 391
         )
381 392
         content = api.get_one(
382 393
             path_data.content_id,
383
-            content_type=ContentType.Any
394
+            content_type=CONTENT_TYPES.Any_SLUG
384 395
         )
385 396
         with new_revision(
386 397
                 session=request.dbsession,
@@ -410,10 +421,11 @@ class WorkspaceController(Controller):
410 421
             session=request.dbsession,
411 422
             config=app_config,
412 423
             show_deleted=True,
424
+            show_archived=True,
413 425
         )
414 426
         content = api.get_one(
415 427
             path_data.content_id,
416
-            content_type=ContentType.Any
428
+            content_type=CONTENT_TYPES.Any_SLUG
417 429
         )
418 430
         with new_revision(
419 431
                 session=request.dbsession,
@@ -439,11 +451,13 @@ class WorkspaceController(Controller):
439 451
         app_config = request.registry.settings['CFG']
440 452
         path_data = hapic_data.path
441 453
         api = ContentApi(
454
+            show_archived=True,
455
+            show_deleted=True,
442 456
             current_user=request.current_user,
443 457
             session=request.dbsession,
444 458
             config=app_config,
445 459
         )
446
-        content = api.get_one(path_data.content_id, content_type=ContentType.Any)  # nopep8
460
+        content = api.get_one(path_data.content_id, content_type=CONTENT_TYPES.Any_SLUG)  # nopep8
447 461
         with new_revision(
448 462
                 session=request.dbsession,
449 463
                 tm=transaction.manager,
@@ -472,10 +486,11 @@ class WorkspaceController(Controller):
472 486
             session=request.dbsession,
473 487
             config=app_config,
474 488
             show_archived=True,
489
+            show_deleted=True,
475 490
         )
476 491
         content = api.get_one(
477 492
             path_data.content_id,
478
-            content_type=ContentType.Any
493
+            content_type=CONTENT_TYPES.Any_SLUG
479 494
         )
480 495
         with new_revision(
481 496
                 session=request.dbsession,

+ 67 - 0
backend/tracim_backend/views/frontend.py Целия файл

@@ -0,0 +1,67 @@
1
+import os
2
+
3
+from pyramid.renderers import render_to_response
4
+from pyramid.config import Configurator
5
+from tracim_backend.exceptions import PageNotFound
6
+from tracim_backend.models.applications import applications
7
+from tracim_backend.views import BASE_API_V2
8
+from tracim_backend.lib.utils.request import TracimRequest
9
+from tracim_backend.views.controllers import Controller
10
+import spectra
11
+
12
+INDEX_PAGE_NAME = 'index.mak'
13
+APP_FRONTEND_PATH = 'app/{minislug}.app.js'
14
+
15
+
16
+class FrontendController(Controller):
17
+
18
+    def __init__(self, dist_folder_path: str):
19
+        self.dist_folder_path = dist_folder_path
20
+
21
+    def _get_index_file_path(self):
22
+        index_file_path = os.path.join(self.dist_folder_path, INDEX_PAGE_NAME)
23
+        if not os.path.exists(index_file_path):
24
+            raise FileNotFoundError()
25
+        return index_file_path
26
+
27
+    def not_found_view(self, context, request: TracimRequest):
28
+
29
+        if request.path.startswith(BASE_API_V2):
30
+            raise PageNotFound('{} is not a valid path'.format(request.path)) from context  # nopep8
31
+        return self.index(context, request)
32
+
33
+    def index(self, context, request: TracimRequest):
34
+        app_config = request.registry.settings['CFG']
35
+        # TODO - G.M - 2018-08-07 - Refactor autogen valid app list for frontend
36
+        frontend_apps = []
37
+        for app in applications:
38
+            app_frontend_path = APP_FRONTEND_PATH.replace('{minislug}',
39
+                                                          app.minislug)  # nopep8
40
+            app_path = os.path.join(self.dist_folder_path,
41
+                                    app_frontend_path)  # nopep8
42
+            if os.path.exists(app_path):
43
+                frontend_apps.append(app)
44
+        return render_to_response(
45
+            self._get_index_file_path(),
46
+            {
47
+                'colors': {
48
+                    'primary': spectra.html('#7d4e24'),
49
+                },
50
+                'applications': frontend_apps,
51
+            }
52
+        )
53
+
54
+    def bind(self, configurator: Configurator) -> None:
55
+
56
+        configurator.add_notfound_view(self.not_found_view)
57
+        # index.html for /index.html and /
58
+        configurator.add_route('root', '/', request_method='GET')
59
+        configurator.add_view(self.index, route_name='root')
60
+        configurator.add_route('index', INDEX_PAGE_NAME, request_method='GET')
61
+        configurator.add_view(self.index, route_name='index')
62
+
63
+        for dirname in os.listdir(self.dist_folder_path):
64
+            configurator.add_static_view(
65
+                name=dirname,
66
+                path=os.path.join(self.dist_folder_path, dirname)
67
+            )

+ 1 - 1
bash_library.sh Целия файл

@@ -5,5 +5,5 @@ BROWN='\033[0;33m'
5 5
 NC='\033[0m' # No Color
6 6
 
7 7
 function log {
8
-    echo -e "\n${YELLOW}[$(date +'%H:%M:%S')]${BROWN} $ $1${NC}\n"
8
+    echo -e "\n${YELLOW}[$(date +'%H:%M:%S')]${BROWN} $ $1${NC}"
9 9
 }

+ 40 - 51
build_full_frontend.sh Целия файл

@@ -1,5 +1,6 @@
1 1
 #!/bin/bash
2 2
 
3
+# shellcheck disable=SC1091
3 4
 . bash_library.sh # source bash_library.sh
4 5
 
5 6
 windoz=""
@@ -7,66 +8,54 @@ if  [[ $1 = "-w" ]]; then
7 8
     windoz="windoz"
8 9
 fi
9 10
 
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
+echo -e "\n${BROWN}/!\ ${NC}this script does not run 'npm install'\n${BROWN}/!\ ${NC}"
11 12
 
12
-# Tracim Lib
13
-
14
-log "cd frontend_lib"
15
-cd frontend_lib
16
-log "npm run buildtracimlib$windoz"
17
-npm run buildtracimlib$windoz
18
-cd -
19
-
20
-# app Html Document
13
+# get the new sources
14
+git pull origin develop
21 15
 
22
-log "cd frontend_app_html-document"
23
-cd frontend_app_html-document
16
+# create folder frontend/dist/app/ if no exists
17
+if [ ! -d "frontend/dist/app/" ]; then
18
+  mkdir frontend/dist/app/
19
+fi
24 20
 
25
-log "npm run build$windoz # for frontend_app_html-document"
26
-npm run build$windoz
21
+# Tracim Lib
22
+(
23
+  log "build frontend_lib"
24
+  cd frontend_lib || exit
25
+  npm run buildtracimlib$windoz
26
+)
27 27
 
28
-log "cp dist/html-document.app.js"
29
-cp dist/html-document.app.js ../frontend/dist/app
30 28
 
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
29
+# app Html Document
30
+(
31
+  cd frontend_app_html-document || exit
32
+  ./build_html-document.sh
33
+)
33 34
 
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
36
-cd -
37 35
 
38 36
 # app Thread
37
+(
38
+  cd frontend_app_thread || exit
39
+  ./build_thread.sh
40
+)
39 41
 
40
-log "cd frontend_app_thread"
41
-cd frontend_app_thread
42
-
43
-log "npm run build$windoz # for frontend_app_thread"
44
-npm run build$windoz
45 42
 
46
-log "cp dist/thread.app.js"
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 -
43
+# app Workspace
44
+(
45
+  cd frontend_app_workspace || exit
46
+  ./build_workspace.sh
47
+)
55 48
 
56 49
 # app Admin Workspace User
57
-
58
-log "cd frontend_app_admin_workspace_user"
59
-cd frontend_app_admin_workspace_user
60
-
61
-log "npm run build$windoz # for frontend_app_thread"
62
-npm run build$windoz
63
-
64
-log "cp dist/admin_workspace_user.app.js"
65
-cp dist/admin_workspace_user.app.js ../frontend/dist/app
66
-
67
-log "cp i18next.scanner/en/translation.json ../frontend/dist/app/admin_workspace_user_en_translation.json"
68
-cp i18next.scanner/en/translation.json ../frontend/dist/app/admin_workspace_user_en_translation.json
69
-
70
-log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/admin_workspace_user_fr_translation.json"
71
-cp i18next.scanner/fr/translation.json ../frontend/dist/app/admin_workspace_user_fr_translation.json
72
-cd -
50
+(
51
+  cd frontend_app_admin_workspace_user || exit
52
+  ./build_admin_workspace_user.sh
53
+)
54
+
55
+# build Tracim
56
+(
57
+  cd frontend || exit
58
+  npm run build
59
+)
60
+
61
+log "-- frontend build successful."

+ 6 - 0
color.json.sample Целия файл

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

+ 3 - 1
frontend/.gitignore Целия файл

@@ -1,4 +1,6 @@
1 1
 # Created by .ignore support plugin (hsz.mobi)
2
-.idea/
3 2
 .git/
4 3
 node_modules/
4
+dist/asset/tracim.app.entry.js
5
+dist/asset/tracim.vendor.bundle.js
6
+dist/asset/images/

frontend/dist/dev/bootstrap-4.0.0-beta.2.js → frontend/dist/asset/bootstrap/bootstrap-4.0.0-beta.2.js Целия файл


frontend/dist/dev/bootstrap-4.0.0-beta.css → frontend/dist/asset/bootstrap/bootstrap-4.0.0-beta.css Целия файл


frontend/dist/dev/jquery-3.2.1.js → frontend/dist/asset/bootstrap/jquery-3.2.1.js Целия файл


frontend/dist/dev/popper-1.12.3.js → frontend/dist/asset/bootstrap/popper-1.12.3.js Целия файл


frontend/dist/font/font-awesome-4.7.0/HELP-US-OUT.txt → frontend/dist/asset/font/font-awesome-4.7.0/HELP-US-OUT.txt Целия файл


frontend/dist/font/font-awesome-4.7.0/css/font-awesome.css → frontend/dist/asset/font/font-awesome-4.7.0/css/font-awesome.css Целия файл


frontend/dist/font/font-awesome-4.7.0/css/font-awesome.min.css → frontend/dist/asset/font/font-awesome-4.7.0/css/font-awesome.min.css Целия файл


frontend/dist/font/font-awesome-4.7.0/fonts/FontAwesome.otf → frontend/dist/asset/font/font-awesome-4.7.0/fonts/FontAwesome.otf Целия файл


frontend/dist/font/font-awesome-4.7.0/fonts/fontawesome-webfont.eot → frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.eot Целия файл


frontend/dist/font/font-awesome-4.7.0/fonts/fontawesome-webfont.svg → frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.svg Целия файл


frontend/dist/font/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf → frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf Целия файл


frontend/dist/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff → frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff Целия файл


frontend/dist/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 → frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 Целия файл


frontend/dist/font/font-awesome-4.7.0/less/animated.less → frontend/dist/asset/font/font-awesome-4.7.0/less/animated.less Целия файл


frontend/dist/font/font-awesome-4.7.0/less/bordered-pulled.less → frontend/dist/asset/font/font-awesome-4.7.0/less/bordered-pulled.less Целия файл


frontend/dist/font/font-awesome-4.7.0/less/core.less → frontend/dist/asset/font/font-awesome-4.7.0/less/core.less Целия файл


frontend/dist/font/font-awesome-4.7.0/less/fixed-width.less → frontend/dist/asset/font/font-awesome-4.7.0/less/fixed-width.less Целия файл


frontend/dist/font/font-awesome-4.7.0/less/font-awesome.less → frontend/dist/asset/font/font-awesome-4.7.0/less/font-awesome.less Целия файл


frontend/dist/font/font-awesome-4.7.0/less/icons.less → frontend/dist/asset/font/font-awesome-4.7.0/less/icons.less Целия файл


frontend/dist/font/font-awesome-4.7.0/less/larger.less → frontend/dist/asset/font/font-awesome-4.7.0/less/larger.less Целия файл


frontend/dist/font/font-awesome-4.7.0/less/list.less → frontend/dist/asset/font/font-awesome-4.7.0/less/list.less Целия файл


frontend/dist/font/font-awesome-4.7.0/less/mixins.less → frontend/dist/asset/font/font-awesome-4.7.0/less/mixins.less Целия файл


frontend/dist/font/font-awesome-4.7.0/less/path.less → frontend/dist/asset/font/font-awesome-4.7.0/less/path.less Целия файл


frontend/dist/font/font-awesome-4.7.0/less/rotated-flipped.less → frontend/dist/asset/font/font-awesome-4.7.0/less/rotated-flipped.less Целия файл


frontend/dist/font/font-awesome-4.7.0/less/screen-reader.less → frontend/dist/asset/font/font-awesome-4.7.0/less/screen-reader.less Целия файл


frontend/dist/font/font-awesome-4.7.0/less/stacked.less → frontend/dist/asset/font/font-awesome-4.7.0/less/stacked.less Целия файл


frontend/dist/font/font-awesome-4.7.0/less/variables.less → frontend/dist/asset/font/font-awesome-4.7.0/less/variables.less Целия файл


frontend/dist/font/font-awesome-4.7.0/scss/_animated.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_animated.scss Целия файл


frontend/dist/font/font-awesome-4.7.0/scss/_bordered-pulled.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_bordered-pulled.scss Целия файл


frontend/dist/font/font-awesome-4.7.0/scss/_core.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_core.scss Целия файл


frontend/dist/font/font-awesome-4.7.0/scss/_fixed-width.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_fixed-width.scss Целия файл


frontend/dist/font/font-awesome-4.7.0/scss/_icons.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_icons.scss Целия файл


frontend/dist/font/font-awesome-4.7.0/scss/_larger.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_larger.scss Целия файл


frontend/dist/font/font-awesome-4.7.0/scss/_list.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_list.scss Целия файл


frontend/dist/font/font-awesome-4.7.0/scss/_mixins.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_mixins.scss Целия файл


frontend/dist/font/font-awesome-4.7.0/scss/_path.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_path.scss Целия файл


frontend/dist/font/font-awesome-4.7.0/scss/_rotated-flipped.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_rotated-flipped.scss Целия файл


frontend/dist/font/font-awesome-4.7.0/scss/_screen-reader.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_screen-reader.scss Целия файл


frontend/dist/font/font-awesome-4.7.0/scss/_stacked.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_stacked.scss Целия файл


frontend/dist/font/font-awesome-4.7.0/scss/_variables.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_variables.scss Целия файл


frontend/dist/font/font-awesome-4.7.0/scss/font-awesome.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/font-awesome.scss Целия файл


frontend/dist/appInterface.js → frontend/dist/asset/tracim/appInterface.js Целия файл

@@ -3,6 +3,8 @@
3 3
 
4 4
   getSelectedApp = name => {
5 5
     switch (name) {
6
+      case 'workspace':
7
+        return appWorkspace
6 8
       case 'html-document':
7 9
         return appHtmlDocument
8 10
       case 'thread':
@@ -74,6 +76,7 @@
74 76
   GLOBAL_eventReducer = ({detail: {type, data}}) => {
75 77
     switch (type) {
76 78
       case 'hide_popupCreateContent':
79
+      case 'hide_popupCreateWorkspace':
77 80
         console.log('%cGLOBAL_eventReducer Custom Event', 'color: #28a745', type, data)
78 81
         getSelectedApp(data.name).unmountApp('popupCreateContentContainer')
79 82
         break

frontend/dist/tinymceInit.js → frontend/dist/asset/tracim/tinymceInit.js Целия файл

@@ -32,6 +32,7 @@
32 32
     });
33 33
 
34 34
     tinymce.init({
35
+      forced_root_block : "",
35 36
       selector: selector,
36 37
       menubar: false,
37 38
       resize: false,

BIN
frontend/dist/ecbb61e619a4d2801db1054c019316cc.jpg Целия файл


+ 50 - 52
frontend/dist/index.html Целия файл

@@ -1,69 +1,67 @@
1 1
 <!DOCTYPE html>
2 2
 <html>
3
-  <head>
4
-    <meta charset='utf-8' />
5
-    <meta name="viewport" content="width=device-width, user-scalable=no">
6
-    <title>Tracim</title>
7
-    <link rel='shortcut icon' type='image/x-icon' href='/asset/favicon.ico' >
3
+<head>
4
+  <meta charset='utf-8' />
5
+  <meta name="viewport" content="width=device-width, user-scalable=no">
6
+  <title>Tracim</title>
7
+  <link rel='shortcut icon' type='image/x-icon' href='/asset/favicon.ico' >
8 8
 
9
-    <link rel="stylesheet" type="text/css" href="/font/font-awesome-4.7.0/css/font-awesome.css">
10
-    <link href="https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700" rel="stylesheet">
11
-    <!--
12
-    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
13
-    -->
14
-    <link rel="stylesheet" type="text/css" href="/asset/hamburger/hamburgers.min.css">
15
-    <link rel="stylesheet" type="text/css" href="/dev/bootstrap-4.0.0-beta.css">
9
+  <link rel="stylesheet" type="text/css" href="/asset/font/font-awesome-4.7.0/css/font-awesome.css">
10
+  <link href="https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700" rel="stylesheet">
11
+  <!--
12
+  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
13
+  -->
14
+  <link rel="stylesheet" type="text/css" href="/asset/hamburger/hamburgers.min.css">
15
+  <link rel="stylesheet" type="text/css" href="/asset/bootstrap/bootstrap-4.0.0-beta.css">
16 16
 
17
-    <style>
18
-      /* code bellow will be generated by backend */
19
-      .primaryColorFont { color: #7d4e24; }
20
-      .primaryColorFontDarken { color: #572800; }
21
-      .primaryColorFontLighten { color: #a3744a; }
22
-      .whiteFontColor {color: #fdfdfd;}
17
+  <style>
18
+    .primaryColorFont { color: #7d4e24; }
19
+    .primaryColorFontDarken { color: #522c00; }
20
+    .primaryColorFontLighten { color: #a37346; }
23 21
 
24
-      .primaryColorFontHover:hover { color: #7d4e24; }
25
-      .primaryColorFontDarkenHover:hover { color: #572800; }
26
-      .primaryColorFontLightenHover:hover { color: #a3744a; }
22
+    .primaryColorFontHover:hover { color: #7d4e24; }
23
+    .primaryColorFontDarkenHover:hover { color: #522c00; }
24
+    .primaryColorFontLightenHover:hover { color: #a37346; }
27 25
 
28
-      .primaryColorBg { background-color: #7d4e24; }
29
-      .primaryColorBgDarken { background-color: #572800; }
30
-      .primaryColorBgLighten { background-color: #a3744a; }
31 26
 
32
-      .primaryColorBgHover:hover { background-color: #7d4e24; }
33
-      .primaryColorBgDarkenHover:hover { background-color: #572800; }
34
-      .primaryColorBgLightenHover:hover { background-color: #a3744a; }
27
+    .primaryColorBg { background-color: #7d4e24; }
28
+    .primaryColorBgDarken { background-color: #522c00; }
29
+    .primaryColorBgLighten { background-color: #a37346; }
35 30
 
36
-      .primaryColorBorder { border-color: #7d4e24; }
37
-      .primaryColorBorderDarken { border-color: #572800; }
38
-      .primaryColorBorderLighten { border-color: #a3744a; }
39
-      .whiteColorBorder { border-color: #fdfdfd; }
31
+    .primaryColorBgHover:hover { background-color: #7d4e24; }
32
+    .primaryColorBgDarkenHover:hover { background-color: #522c00; }
33
+    .primaryColorBgLightenHover:hover { background-color: #a37346; }
40 34
 
41
-      .primaryColorBorderHover:hover { border-color: #7d4e24; }
42
-      .primaryColorBorderDarkenHover:hover { border-color: #572800; }
43
-      .primaryColorBorderLightenHover:hover { border-color: #a3744a; }
44 35
 
45
-    </style>
46
-  </head>
36
+    .primaryColorBorder { border-color: #7d4e24; }
37
+    .primaryColorBorderDarken { border-color: #522c00; }
38
+    .primaryColorBorderLighten { border-color: #a37346; }
47 39
 
48
-  <body>
49
-    <div id='content'></div>
40
+    .primaryColorBorderHover:hover { border-color: #7d4e24; }
41
+    .primaryColorBorderDarkenHover:hover { border-color: #522c00; }
42
+    .primaryColorBorderLightenHover:hover { border-color: #a37346; }
43
+  </style>
44
+</head>
50 45
 
51
-    <script src='/tracim.vendor.bundle.js'></script>
52
-    <script src='/tracim.app.entry.js'></script>
46
+<body>
47
+<div id='content'></div>
53 48
 
54
-    <script src='/app/html-document.app.js'></script>
55
-    <script src='/app/thread.app.js'></script>
56
-    <!-- <script src='/app/file.app.js'></script> -->
57
-    <script src='/app/admin_workspace_user.app.js'></script>
49
+<script type='text/javascript' src='/asset/tracim.vendor.bundle.js'></script>
50
+<script type='text/javascript' src='/asset/tracim.app.entry.js'></script>
58 51
 
59
-    <script src="/dev/jquery-3.2.1.js"></script>
60
-    <script src="/dev/popper-1.12.3.js"></script>
61
-    <script src="/dev/bootstrap-4.0.0-beta.2.js"></script>
52
+<script type='text/javascript' src='/app/workspace.app.js'></script>
53
+<script type='text/javascript' src='/app/html-document.app.js'></script>
54
+<script type='text/javascript' src='/app/thread.app.js'></script>
55
+<script type='text/javascript' src='/app/admin_workspace_user.app.js'></script>
62 56
 
63
-    <script type="text/javascript" src="/asset/tinymce/js/tinymce/jquery.tinymce.min.js"></script>
64
-    <script type="text/javascript" src="/asset/tinymce/js/tinymce/tinymce.min.js"></script>
57
+<script type='text/javascript' src='/asset/bootstrap/jquery-3.2.1.js'></script>
58
+<script type='text/javascript' src='/asset/bootstrap/popper-1.12.3.js'></script>
59
+<script type='text/javascript' src='/asset/bootstrap/bootstrap-4.0.0-beta.2.js'></script>
65 60
 
66
-    <script type='text/javascript' src='/appInterface.js'></script>
67
-    <script type='text/javascript' src='/tinymceInit.js'></script>
68
-  </body>
61
+<script type='text/javascript' src='/asset/tinymce/js/tinymce/jquery.tinymce.min.js'></script>
62
+<script type='text/javascript' src='/asset/tinymce/js/tinymce/tinymce.min.js'></script>
63
+
64
+<script type='text/javascript' src='/asset/tracim/appInterface.js'></script>
65
+<script type='text/javascript' src='/asset/tracim/tinymceInit.js'></script>
66
+</body>
69 67
 </html>

+ 0 - 0
frontend/dist/index.mak Целия файл


Някои файлове не бяха показани, защото твърде много файлове са промени