zou 0.19.15__py3-none-any.whl → 0.20.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. zou/__init__.py +1 -1
  2. zou/app/__init__.py +10 -2
  3. zou/app/api.py +2 -0
  4. zou/app/blueprints/assets/__init__.py +22 -0
  5. zou/app/blueprints/assets/resources.py +241 -4
  6. zou/app/blueprints/auth/__init__.py +4 -0
  7. zou/app/blueprints/auth/resources.py +154 -22
  8. zou/app/blueprints/breakdown/resources.py +4 -4
  9. zou/app/blueprints/chats/__init__.py +22 -0
  10. zou/app/blueprints/chats/resources.py +199 -0
  11. zou/app/blueprints/comments/resources.py +36 -19
  12. zou/app/blueprints/crud/__init__.py +12 -0
  13. zou/app/blueprints/crud/attachment_file.py +14 -5
  14. zou/app/blueprints/crud/base.py +29 -28
  15. zou/app/blueprints/crud/chat.py +13 -0
  16. zou/app/blueprints/crud/chat_message.py +13 -0
  17. zou/app/blueprints/crud/comments.py +85 -29
  18. zou/app/blueprints/crud/custom_action.py +1 -1
  19. zou/app/blueprints/crud/day_off.py +47 -9
  20. zou/app/blueprints/crud/department.py +1 -25
  21. zou/app/blueprints/crud/entity.py +46 -5
  22. zou/app/blueprints/crud/entity_type.py +13 -1
  23. zou/app/blueprints/crud/event.py +1 -1
  24. zou/app/blueprints/crud/file_status.py +1 -1
  25. zou/app/blueprints/crud/metadata_descriptor.py +24 -10
  26. zou/app/blueprints/crud/organisation.py +22 -5
  27. zou/app/blueprints/crud/output_file.py +1 -1
  28. zou/app/blueprints/crud/output_type.py +1 -1
  29. zou/app/blueprints/crud/person.py +32 -24
  30. zou/app/blueprints/crud/playlist.py +1 -1
  31. zou/app/blueprints/crud/preview_background_file.py +6 -7
  32. zou/app/blueprints/crud/preview_file.py +1 -1
  33. zou/app/blueprints/crud/project.py +14 -6
  34. zou/app/blueprints/crud/project_status.py +1 -1
  35. zou/app/blueprints/crud/schedule_item.py +4 -2
  36. zou/app/blueprints/crud/software.py +1 -1
  37. zou/app/blueprints/crud/status_automation.py +1 -1
  38. zou/app/blueprints/crud/studio.py +33 -0
  39. zou/app/blueprints/crud/task.py +47 -3
  40. zou/app/blueprints/crud/task_status.py +1 -1
  41. zou/app/blueprints/crud/task_type.py +4 -4
  42. zou/app/blueprints/crud/working_file.py +4 -8
  43. zou/app/blueprints/events/resources.py +13 -12
  44. zou/app/blueprints/export/csv/assets.py +15 -6
  45. zou/app/blueprints/export/csv/edits.py +15 -5
  46. zou/app/blueprints/export/csv/playlists.py +1 -1
  47. zou/app/blueprints/export/csv/shots.py +15 -5
  48. zou/app/blueprints/export/csv/time_spents.py +1 -1
  49. zou/app/blueprints/files/resources.py +22 -23
  50. zou/app/blueprints/index/resources.py +38 -29
  51. zou/app/blueprints/news/resources.py +25 -11
  52. zou/app/blueprints/persons/__init__.py +5 -2
  53. zou/app/blueprints/persons/resources.py +126 -120
  54. zou/app/blueprints/previews/__init__.py +18 -8
  55. zou/app/blueprints/previews/resources.py +569 -328
  56. zou/app/blueprints/projects/resources.py +1 -1
  57. zou/app/blueprints/search/resources.py +18 -6
  58. zou/app/blueprints/shots/__init__.py +5 -0
  59. zou/app/blueprints/shots/resources.py +134 -4
  60. zou/app/blueprints/source/__init__.py +6 -6
  61. zou/app/blueprints/source/csv/assets.py +10 -3
  62. zou/app/blueprints/source/csv/base.py +1 -1
  63. zou/app/blueprints/source/csv/edits.py +10 -3
  64. zou/app/blueprints/source/csv/shots.py +10 -3
  65. zou/app/blueprints/source/{edl.py → otio.py} +82 -41
  66. zou/app/blueprints/tasks/__init__.py +3 -2
  67. zou/app/blueprints/tasks/resources.py +83 -52
  68. zou/app/blueprints/user/__init__.py +9 -0
  69. zou/app/blueprints/user/resources.py +170 -12
  70. zou/app/config.py +10 -0
  71. zou/app/mixin.py +6 -5
  72. zou/app/models/attachment_file.py +10 -4
  73. zou/app/models/base.py +18 -13
  74. zou/app/models/build_job.py +7 -4
  75. zou/app/models/chat.py +44 -0
  76. zou/app/models/chat_message.py +37 -0
  77. zou/app/models/comment.py +1 -0
  78. zou/app/models/day_off.py +3 -0
  79. zou/app/models/entity.py +4 -6
  80. zou/app/models/entity_type.py +2 -0
  81. zou/app/models/organisation.py +14 -15
  82. zou/app/models/person.py +6 -1
  83. zou/app/models/project.py +3 -0
  84. zou/app/models/search_filter.py +11 -0
  85. zou/app/models/search_filter_group.py +10 -0
  86. zou/app/models/serializer.py +17 -17
  87. zou/app/models/status_automation.py +2 -0
  88. zou/app/models/studio.py +13 -0
  89. zou/app/models/subscription.py +2 -2
  90. zou/app/models/task.py +6 -1
  91. zou/app/models/task_status.py +1 -0
  92. zou/app/models/task_type.py +1 -0
  93. zou/app/models/working_file.py +1 -1
  94. zou/app/services/assets_service.py +101 -14
  95. zou/app/services/auth_service.py +17 -44
  96. zou/app/services/breakdown_service.py +37 -5
  97. zou/app/services/chats_service.py +279 -0
  98. zou/app/services/comments_service.py +110 -65
  99. zou/app/services/concepts_service.py +4 -12
  100. zou/app/services/deletion_service.py +43 -30
  101. zou/app/services/edits_service.py +5 -11
  102. zou/app/services/emails_service.py +4 -4
  103. zou/app/services/entities_service.py +17 -2
  104. zou/app/services/events_service.py +12 -4
  105. zou/app/services/exception.py +5 -5
  106. zou/app/services/names_service.py +7 -2
  107. zou/app/services/news_service.py +17 -9
  108. zou/app/services/persons_service.py +38 -21
  109. zou/app/services/playlists_service.py +8 -7
  110. zou/app/services/preview_files_service.py +137 -10
  111. zou/app/services/projects_service.py +5 -14
  112. zou/app/services/shots_service.py +221 -49
  113. zou/app/services/sync_service.py +46 -42
  114. zou/app/services/tasks_service.py +185 -46
  115. zou/app/services/time_spents_service.py +67 -20
  116. zou/app/services/user_service.py +350 -107
  117. zou/app/stores/auth_tokens_store.py +2 -1
  118. zou/app/stores/file_store.py +18 -0
  119. zou/app/stores/publisher_store.py +7 -7
  120. zou/app/stores/queue_store.py +1 -0
  121. zou/app/swagger.py +36 -20
  122. zou/app/utils/cache.py +2 -0
  123. zou/app/utils/commands.py +104 -7
  124. zou/app/utils/csv_utils.py +1 -4
  125. zou/app/utils/date_helpers.py +33 -17
  126. zou/app/utils/dbhelpers.py +14 -1
  127. zou/app/utils/emails.py +2 -2
  128. zou/app/utils/fido.py +22 -0
  129. zou/app/utils/query.py +54 -6
  130. zou/app/utils/redis.py +11 -0
  131. zou/app/utils/saml.py +51 -0
  132. zou/app/utils/string.py +2 -0
  133. zou/app/utils/thumbnail.py +4 -2
  134. zou/cli.py +76 -18
  135. zou/debug.py +4 -2
  136. zou/event_stream.py +122 -165
  137. zou/job_settings.py +1 -0
  138. zou/migrations/env.py +0 -0
  139. zou/migrations/utils/base.py +6 -6
  140. zou/migrations/versions/1bb55759146f_add_table_studio.py +67 -0
  141. zou/migrations/versions/1fab8c420678_add_attachments_to_message_chats.py +56 -0
  142. zou/migrations/versions/23122f290ca2_add_entity_chat_models.py +149 -0
  143. zou/migrations/versions/32f134ff1201_add_is_shared_flag_to_filters.py +33 -0
  144. zou/migrations/versions/57222395f2be_add_statusautomation_import_last_revision.py +41 -0
  145. zou/migrations/versions/59a7445a966c_add_entity_is_shared.py +41 -0
  146. zou/migrations/versions/5b980f0dc365_add_comment_links.py +35 -0
  147. zou/migrations/versions/680c64565f9d_for_searchfiltergroup_is_shared.py +35 -0
  148. zou/migrations/versions/8e67c183bed7_add_preference_fields.py +71 -0
  149. zou/migrations/versions/92b40d79ad3f_allow_message_attachments.py +38 -0
  150. zou/migrations/versions/971dbf5a0faf_add_short_name_for_asset_type_entity_.py +33 -0
  151. zou/migrations/versions/9b85c14fa8a7_add_day_off_new_columns.py +68 -0
  152. zou/migrations/versions/9d3bb33c6fc6_add_department_keys_to_filter_models.py +73 -0
  153. zou/migrations/versions/a252a094e977_add_descriptions_for_entities_tasks_and_.py +40 -0
  154. zou/migrations/versions/be56dc0fb760_for_is_shared_disallow_nullable.py +102 -0
  155. zou/migrations/versions/ca28796a2a62_add_is_done_field_to_the_task_model.py +108 -0
  156. zou/migrations/versions/f344b867a911_for_description_of_entity_task_working_.py +75 -0
  157. zou/remote/config_payload.py +2 -1
  158. zou/utils/movie.py +14 -4
  159. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/METADATA +75 -69
  160. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/RECORD +163 -134
  161. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/WHEEL +1 -1
  162. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/LICENSE +0 -0
  163. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/entry_points.txt +0 -0
  164. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,22 @@
1
+ from flask import Blueprint
2
+
3
+ from zou.app.utils.api import configure_api_from_blueprint
4
+
5
+ from zou.app.blueprints.chats.resources import (
6
+ ChatResource,
7
+ ChatMessagesResource,
8
+ ChatMessageResource,
9
+ )
10
+
11
+
12
+ routes = [
13
+ ("/data/entities/<entity_id>/chat", ChatResource),
14
+ ("/data/entities/<entity_id>/chat/messages", ChatMessagesResource),
15
+ (
16
+ "/data/entities/<entity_id>/chat/messages/<chat_message_id>",
17
+ ChatMessageResource,
18
+ ),
19
+ ]
20
+
21
+ blueprint = Blueprint("chats", "chats")
22
+ api = configure_api_from_blueprint(blueprint, routes)
@@ -0,0 +1,199 @@
1
+ from flask import request
2
+ from flask_restful import Resource, reqparse
3
+ from flask_jwt_extended import jwt_required
4
+
5
+ from zou.app.utils import permissions
6
+
7
+ from zou.app.services import (
8
+ chats_service,
9
+ entities_service,
10
+ persons_service,
11
+ user_service,
12
+ )
13
+ from zou.app.services.exception import WrongParameterException
14
+
15
+
16
+ class ChatResource(Resource):
17
+
18
+ @jwt_required()
19
+ def get(self, entity_id):
20
+ """
21
+ Get chat details.
22
+ ---
23
+ tags:
24
+ - Chat
25
+ parameters:
26
+ - in: path
27
+ name: entity_id
28
+ description: ID of the entity related to the chat
29
+ type: integer
30
+ required: true
31
+ x-example: a24a6ea4-ce75-4665-a070-57453082c25
32
+ responses:
33
+ 200:
34
+ description: Chat information
35
+ """
36
+ entity = entities_service.get_entity(entity_id)
37
+ user_service.check_project_access(entity["project_id"])
38
+ user_service.check_entity_access(entity["id"])
39
+ chat = chats_service.get_chat(entity_id)
40
+ chat["messages"] = chats_service.get_chat_messages(entity_id)
41
+ return chat
42
+
43
+
44
+ class ChatMessagesResource(Resource):
45
+
46
+ @jwt_required()
47
+ def get(self, entity_id):
48
+ """
49
+ Get chat messages for an entity.
50
+ ---
51
+ tags:
52
+ - Chat
53
+ parameters:
54
+ - in: path
55
+ name: entity_id
56
+ description: ID of the entity related to the chat
57
+ type: integer
58
+ required: true
59
+ x-example: a24a6ea4-ce75-4665-a070-57453082c25
60
+ responses:
61
+ 200:
62
+ description: Chat messages
63
+ """
64
+ entity = entities_service.get_entity(entity_id)
65
+ user_service.check_project_access(entity["project_id"])
66
+ user_service.check_entity_access(entity["id"])
67
+ return chats_service.get_chat_messages_for_entity(entity_id)
68
+
69
+ @jwt_required()
70
+ def post(self, entity_id):
71
+ """
72
+ Create a new chat message.
73
+ ---
74
+ tags:
75
+ - Chat
76
+ parameters:
77
+ - in: path
78
+ name: entity_id
79
+ description: ID of the entity related to the chat
80
+ type: integer
81
+ required: true
82
+ x-example: a24a6ea4-ce75-4665-a070-57453082c25
83
+ - in: body
84
+ name: message
85
+ description: Message to send
86
+ type: string
87
+ required: true
88
+ x-example: Hello, world!
89
+ - in: formData
90
+ name: files
91
+ description: Files to attach
92
+ type: file
93
+ required: false
94
+ responses:
95
+ 201:
96
+ description: Chat message created
97
+ 400:
98
+ description: Not participant of the chat
99
+ """
100
+ entity = entities_service.get_entity(entity_id)
101
+ user_service.check_project_access(entity["project_id"])
102
+ user_service.check_entity_access(entity["id"])
103
+
104
+ person = persons_service.get_current_user()
105
+ if request.is_json:
106
+ location = ["values", "json"]
107
+ else:
108
+ location = ["values", "form"]
109
+ parser = reqparse.RequestParser()
110
+ parser.add_argument(
111
+ "message", type=str, required=True, location=location
112
+ )
113
+ args = parser.parse_args()
114
+ message = args["message"]
115
+ files = request.files
116
+
117
+ chat = chats_service.get_chat(entity_id)
118
+ if person["id"] not in chat["participants"]:
119
+ raise WrongParameterException(
120
+ "You are not a participant of this chat"
121
+ )
122
+
123
+ return (
124
+ chats_service.create_chat_message(
125
+ chat["id"], person["id"], message, files=files
126
+ ),
127
+ 201,
128
+ )
129
+
130
+
131
+ class ChatMessageResource(Resource):
132
+
133
+ @jwt_required()
134
+ def get(self, entity_id, chat_message_id):
135
+ """
136
+ Get chat message.
137
+ ---
138
+ tags:
139
+ - Chat
140
+ parameters:
141
+ - in: path
142
+ name: entity_id
143
+ description: ID of the entity related to the chat
144
+ type: integer
145
+ required: true
146
+ x-example: a24a6ea4-ce75-4665-a070-57453082c25
147
+ - in: path
148
+ name: chat_message_id
149
+ description: ID of the chat message
150
+ type: integer
151
+ required: true
152
+ x-example: 1
153
+ responses:
154
+ 200:
155
+ description: Chat message
156
+ """
157
+ entity = entities_service.get_entity(entity_id)
158
+ user_service.check_project_access(entity["project_id"])
159
+ user_service.check_entity_access(entity["id"])
160
+ return chats_service.get_chat_message(chat_message_id)
161
+
162
+ @jwt_required()
163
+ def delete(self, entity_id, chat_message_id):
164
+ """
165
+ Delete chat message.
166
+ ---
167
+ tags:
168
+ - Chat
169
+ parameters:
170
+ - in: path
171
+ name: entity_id
172
+ description: ID of the entity related to the chat
173
+ type: integer
174
+ required: true
175
+ x-example: a24a6ea4-ce75-4665-a070-57453082c25
176
+ - in: path
177
+ name: chat_message_id
178
+ description: ID of the chat message
179
+ type: integer
180
+ required: true
181
+ x-example: 1
182
+ responses:
183
+ 204:
184
+ description: Empty response
185
+ """
186
+ entity = entities_service.get_entity(entity_id)
187
+ user_service.check_project_access(entity["project_id"])
188
+ user_service.check_entity_access(entity["id"])
189
+
190
+ chat_message = chats_service.get_chat_message(chat_message_id)
191
+ current_user = persons_service.get_current_user()
192
+ if (
193
+ chat_message["person_id"] != current_user["id"]
194
+ or not permissions.has_admin_permissions
195
+ ):
196
+ raise permissions.PermissionDenied
197
+ chats_service.delete_chat_message(chat_message_id)
198
+
199
+ return "", 204
@@ -5,11 +5,13 @@ from flask_restful import Resource, reqparse
5
5
  from flask_jwt_extended import jwt_required
6
6
 
7
7
  from zou.app.mixin import ArgsMixin
8
- from zou.app.utils import permissions
8
+ from zou.app.utils import permissions, date_helpers
9
9
 
10
10
  from zou.app.services import (
11
+ chats_service,
11
12
  comments_service,
12
13
  deletion_service,
14
+ entities_service,
13
15
  persons_service,
14
16
  tasks_service,
15
17
  user_service,
@@ -53,10 +55,18 @@ class DownloadAttachmentResource(Resource):
53
55
  attachment_file = comments_service.get_attachment_file(
54
56
  attachment_file_id
55
57
  )
56
- comment = tasks_service.get_comment(attachment_file["comment_id"])
57
- task = tasks_service.get_task(comment["object_id"])
58
- user_service.check_project_access(task["project_id"])
59
- user_service.check_entity_access(task["entity_id"])
58
+ if attachment_file["comment_id"] is not None:
59
+ comment = tasks_service.get_comment(attachment_file["comment_id"])
60
+ user_service.check_task_access(comment["object_id"])
61
+ elif attachment_file["chat_message_id"] is not None:
62
+ message = chats_service.get_chat_message(
63
+ attachment_file["chat_message_id"]
64
+ )
65
+ chat = chats_service.get_chat_by_id(message["chat_id"])
66
+ entity = entities_service.get_entity(chat["object_id"])
67
+ user_service.check_project_access(entity["project_id"])
68
+ else:
69
+ raise permissions.PermissionDenied()
60
70
  try:
61
71
  file_path = comments_service.get_attachment_file_path(
62
72
  attachment_file
@@ -68,6 +78,9 @@ class DownloadAttachmentResource(Resource):
68
78
  as_attachment=False,
69
79
  download_name=attachment_file["name"],
70
80
  max_age=config.CLIENT_CACHE_MAX_AGE,
81
+ last_modified=date_helpers.get_datetime_from_string(
82
+ attachment_file["updated_at"]
83
+ ),
71
84
  )
72
85
  except Exception:
73
86
  current_app.logger.error(
@@ -107,9 +120,7 @@ class AckCommentResource(Resource):
107
120
  200:
108
121
  description: Comment acknowledged
109
122
  """
110
- task = tasks_service.get_task(task_id)
111
- user_service.check_project_access(task["project_id"])
112
- user_service.check_entity_access(task["entity_id"])
123
+ user_service.check_task_access(task_id)
113
124
  return comments_service.acknowledge_comment(comment_id)
114
125
 
115
126
 
@@ -175,11 +186,10 @@ class CommentTaskResource(Resource):
175
186
  person_id,
176
187
  created_at,
177
188
  checklist,
189
+ links,
178
190
  ) = self.get_arguments()
179
191
 
180
- task = tasks_service.get_task(task_id)
181
- user_service.check_project_access(task["project_id"])
182
- user_service.check_entity_access(task["entity_id"])
192
+ user_service.check_task_access(task_id)
183
193
  user_service.check_task_status_access(task_status_id)
184
194
  files = request.files
185
195
 
@@ -194,6 +204,7 @@ class CommentTaskResource(Resource):
194
204
  checklist,
195
205
  files,
196
206
  created_at,
207
+ links,
197
208
  )
198
209
  return comment, 201
199
210
 
@@ -208,9 +219,17 @@ class CommentTaskResource(Resource):
208
219
  default=[],
209
220
  location=location,
210
221
  )
222
+ parser.add_argument(
223
+ "links",
224
+ type=str,
225
+ action="append",
226
+ default=[],
227
+ location=location,
228
+ )
211
229
  else:
212
230
  location = "values"
213
231
  parser.add_argument("checklist", default="[]", location=location)
232
+ parser.add_argument("links", default="[]", location=location)
214
233
  parser.add_argument(
215
234
  "task_status_id",
216
235
  required=True,
@@ -231,6 +250,7 @@ class CommentTaskResource(Resource):
231
250
  if request.is_json
232
251
  else json.loads(args["checklist"])
233
252
  ),
253
+ (args["links"] if request.is_json else json.loads(args["links"])),
234
254
  )
235
255
 
236
256
 
@@ -398,6 +418,7 @@ class CommentManyTasksResource(Resource):
398
418
  [],
399
419
  {},
400
420
  None,
421
+ comment.get("links", []),
401
422
  )
402
423
  result.append(comment)
403
424
  except KeyError:
@@ -408,8 +429,8 @@ class CommentManyTasksResource(Resource):
408
429
  allowed_comments = []
409
430
  for comment in comments:
410
431
  try:
411
- task = tasks_service.get_task_with_relations(
412
- comment["object_id"],
432
+ task = tasks_service.get_task(
433
+ comment["object_id"], relations=True
413
434
  )
414
435
  if (
415
436
  person["role"] == "supervisor"
@@ -469,9 +490,7 @@ class ReplyCommentResource(Resource, ArgsMixin):
469
490
  ]
470
491
  )
471
492
 
472
- task = tasks_service.get_task(task_id)
473
- user_service.check_project_access(task["project_id"])
474
- user_service.check_entity_access(task["entity_id"])
493
+ user_service.check_task_access(task_id)
475
494
  return comments_service.reply_comment(comment_id, args["text"])
476
495
 
477
496
 
@@ -510,9 +529,7 @@ class DeleteReplyCommentResource(Resource):
510
529
  200:
511
530
  description: Given comment reply deleted
512
531
  """
513
- task = tasks_service.get_task(task_id)
514
- user_service.check_project_access(task["project_id"])
515
- user_service.check_entity_access(task["entity_id"])
532
+ user_service.check_task_access(task_id)
516
533
  reply = comments_service.get_reply(comment_id, reply_id)
517
534
  current_user = persons_service.get_current_user()
518
535
  if reply["person_id"] != current_user["id"]:
@@ -123,6 +123,12 @@ from zou.app.blueprints.crud.preview_background_file import (
123
123
  PreviewBackgroundFileResource,
124
124
  PreviewBackgroundFilesResource,
125
125
  )
126
+ from zou.app.blueprints.crud.chat import ChatResource, ChatsResource
127
+ from zou.app.blueprints.crud.chat_message import (
128
+ ChatMessageResource,
129
+ ChatMessagesResource,
130
+ )
131
+ from zou.app.blueprints.crud.studio import StudioResource, StudiosResource
126
132
 
127
133
  routes = [
128
134
  ("/data/persons", PersonsResource),
@@ -195,11 +201,17 @@ routes = [
195
201
  ("/data/subscriptions/<instance_id>", SubscriptionResource),
196
202
  ("/data/entity-links/", EntityLinksResource),
197
203
  ("/data/entity-links/<instance_id>", EntityLinkResource),
204
+ ("/data/chats/", ChatsResource),
205
+ ("/data/chats/<instance_id>", ChatResource),
206
+ ("/data/chat-messages/", ChatMessagesResource),
207
+ ("/data/chat-messages/<instance_id>", ChatMessageResource),
198
208
  ("/data/preview-background-files", PreviewBackgroundFilesResource),
199
209
  (
200
210
  "/data/preview-background-files/<instance_id>",
201
211
  PreviewBackgroundFileResource,
202
212
  ),
213
+ ("/data/studios", StudiosResource),
214
+ ("/data/studios/<instance_id>", StudioResource),
203
215
  ]
204
216
 
205
217
  blueprint = Blueprint("/data", "data")
@@ -2,7 +2,9 @@ from zou.app.models.attachment_file import AttachmentFile
2
2
 
3
3
  from zou.app.blueprints.crud.base import BaseModelResource, BaseModelsResource
4
4
 
5
- from zou.app.services import tasks_service, user_service
5
+ from zou.app.services import chats_service, tasks_service, user_service
6
+
7
+ from zou.app.utils.permissions import PermissionDenied
6
8
 
7
9
 
8
10
  class AttachmentFilesResource(BaseModelsResource):
@@ -16,8 +18,15 @@ class AttachmentFileResource(BaseModelResource):
16
18
 
17
19
  def check_read_permissions(self, instance):
18
20
  attachment_file = instance
19
- comment = tasks_service.get_comment(attachment_file["comment_id"])
20
- task = tasks_service.get_task(comment["object_id"])
21
- user_service.check_project_access(task["project_id"])
22
- user_service.check_entity_access(task["entity_id"])
21
+ if attachment_file["comment_id"] is not None:
22
+ comment = tasks_service.get_comment(attachment_file["comment_id"])
23
+ user_service.check_task_access(comment["object_id"])
24
+ elif attachment_file["chat_message_id"] is not None:
25
+ message = chats_service.get_chat_message(
26
+ attachment_file["chat_message_id"]
27
+ )
28
+ chat = chats_service.get_chat(message["chat_id"])
29
+ user_service.check_entity_access(chat["object_id"])
30
+ else:
31
+ raise PermissionDenied()
23
32
  return True
@@ -12,7 +12,6 @@ from sqlalchemy.inspection import inspect
12
12
  from zou.app.mixin import ArgsMixin
13
13
  from zou.app.utils import events, fields, permissions, query
14
14
  from zou.app.services.exception import (
15
- ArgumentsException,
16
15
  WrongParameterException,
17
16
  )
18
17
 
@@ -27,7 +26,10 @@ class BaseModelsResource(Resource, ArgsMixin):
27
26
  if query is None:
28
27
  query = self.model.query
29
28
 
30
- return self.model.serialize_list(query.all(), relations=relations)
29
+ return self.serialize_list(query.all(), relations=relations)
30
+
31
+ def serialize_list(self, entries, relations=False):
32
+ return self.model.serialize_list(entries, relations=relations)
31
33
 
32
34
  def paginated_entries(self, query, page, limit=None, relations=False):
33
35
  total = query.count()
@@ -35,7 +37,11 @@ class BaseModelsResource(Resource, ArgsMixin):
35
37
  offset = (page - 1) * limit
36
38
 
37
39
  nb_pages = int(math.ceil(total / float(limit)))
38
- query = query.order_by(self.model.updated_at.desc())
40
+ query = query.order_by(
41
+ self.model.updated_at.desc(),
42
+ self.model.created_at.desc(),
43
+ self.model.id,
44
+ )
39
45
  query = query.limit(limit)
40
46
  query = query.offset(offset)
41
47
 
@@ -75,7 +81,11 @@ class BaseModelsResource(Resource, ArgsMixin):
75
81
  ) and isinstance(
76
82
  field_key.property, orm.properties.RelationshipProperty
77
83
  )
78
- value_is_list = len(value) > 0 and value[0] == "["
84
+ value_is_list = (
85
+ hasattr(value, "__len__")
86
+ and len(value) > 0
87
+ and value[0] == "["
88
+ )
79
89
 
80
90
  if key == "name" and field_key is not None:
81
91
  name_filter.append(value)
@@ -119,7 +129,7 @@ class BaseModelsResource(Resource, ArgsMixin):
119
129
 
120
130
  return query
121
131
 
122
- def check_read_permissions(self):
132
+ def check_read_permissions(self, options=None):
123
133
  return permissions.check_admin_permissions()
124
134
 
125
135
  def add_project_permission_filter(self, query):
@@ -157,13 +167,14 @@ class BaseModelsResource(Resource, ArgsMixin):
157
167
  description: Permission denied
158
168
  """
159
169
  try:
160
- self.check_read_permissions()
161
170
  query = self.model.query
162
171
  if not request.args:
172
+ self.check_read_permissions()
163
173
  query = self.add_project_permission_filter(query)
164
174
  return self.all_entries(query)
165
175
  else:
166
176
  options = request.args
177
+ self.check_read_permissions(options)
167
178
  query = self.apply_filters(query, options)
168
179
  query = self.add_project_permission_filter(query)
169
180
  page = int(options.get("page", "-1"))
@@ -229,7 +240,7 @@ class BaseModelsResource(Resource, ArgsMixin):
229
240
  try:
230
241
  data = request.json
231
242
  if data is None:
232
- raise ArgumentsException(
243
+ raise WrongParameterException(
233
244
  "Data are empty. Please verify that you sent JSON data and"
234
245
  " that you set the right headers."
235
246
  )
@@ -245,18 +256,11 @@ class BaseModelsResource(Resource, ArgsMixin):
245
256
  TypeError,
246
257
  IntegrityError,
247
258
  StatementError,
259
+ KeyError,
248
260
  ) as exception:
249
261
  current_app.logger.error(str(exception), exc_info=1)
250
262
  return {"message": str(exception)}, 400
251
263
 
252
- except ArgumentsException as exception:
253
- current_app.logger.error(str(exception), exc_info=1)
254
- return (
255
- exception.dict
256
- if exception.dict is not None
257
- else {"message": str(exception)}
258
- ), 400
259
-
260
264
  def emit_create_event(self, instance_dict):
261
265
  return events.emit(
262
266
  "%s:new" % self.model.__tablename__.replace("_", "-"),
@@ -280,10 +284,14 @@ class BaseModelResource(Resource, ArgsMixin):
280
284
  abort(404)
281
285
  return instance
282
286
 
283
- def check_read_permissions(self, instance):
287
+ def get_serialized_instance(self, instance_id, relations=True):
288
+ instance = self.get_model_or_404(instance_id)
289
+ return self.serialize_instance(instance, relations=relations)
290
+
291
+ def check_read_permissions(self, instance_dict):
284
292
  return permissions.check_admin_permissions()
285
293
 
286
- def check_update_permissions(self, instance, data):
294
+ def check_update_permissions(self, instance_dict, data):
287
295
  return permissions.check_admin_permissions()
288
296
 
289
297
  def check_delete_permissions(self, instance_dict):
@@ -328,8 +336,9 @@ class BaseModelResource(Resource, ArgsMixin):
328
336
  """
329
337
  relations = self.get_bool_parameter("relations", "true")
330
338
  try:
331
- instance = self.get_model_or_404(instance_id)
332
- result = self.serialize_instance(instance, relations=relations)
339
+ result = self.get_serialized_instance(
340
+ instance_id, relations=relations
341
+ )
333
342
  self.check_read_permissions(result)
334
343
  result = self.clean_get_result(result)
335
344
 
@@ -397,7 +406,7 @@ class BaseModelResource(Resource, ArgsMixin):
397
406
  try:
398
407
  data = self.get_arguments()
399
408
  if data is None:
400
- raise ArgumentsException(
409
+ raise WrongParameterException(
401
410
  "Data are empty. Please verify that you sent JSON data and"
402
411
  " that you set the right headers."
403
412
  )
@@ -420,14 +429,6 @@ class BaseModelResource(Resource, ArgsMixin):
420
429
  current_app.logger.error(str(exception), exc_info=1)
421
430
  return {"message": str(exception)}, 400
422
431
 
423
- except ArgumentsException as exception:
424
- current_app.logger.error(str(exception), exc_info=1)
425
- return (
426
- exception.dict
427
- if exception.dict is not None
428
- else {"message": str(exception)}
429
- ), 400
430
-
431
432
  @jwt_required()
432
433
  def delete(self, instance_id):
433
434
  """
@@ -0,0 +1,13 @@
1
+ from zou.app.models.chat import Chat
2
+
3
+ from zou.app.blueprints.crud.base import BaseModelResource, BaseModelsResource
4
+
5
+
6
+ class ChatsResource(BaseModelsResource):
7
+ def __init__(self):
8
+ BaseModelsResource.__init__(self, Chat)
9
+
10
+
11
+ class ChatResource(BaseModelResource):
12
+ def __init__(self):
13
+ BaseModelResource.__init__(self, Chat)
@@ -0,0 +1,13 @@
1
+ from zou.app.models.chat_message import ChatMessage
2
+
3
+ from zou.app.blueprints.crud.base import BaseModelResource, BaseModelsResource
4
+
5
+
6
+ class ChatMessagesResource(BaseModelsResource):
7
+ def __init__(self):
8
+ BaseModelsResource.__init__(self, ChatMessage)
9
+
10
+
11
+ class ChatMessageResource(BaseModelResource):
12
+ def __init__(self):
13
+ BaseModelResource.__init__(self, ChatMessage)