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.
- zou/__init__.py +1 -1
- zou/app/__init__.py +10 -2
- zou/app/api.py +2 -0
- zou/app/blueprints/assets/__init__.py +22 -0
- zou/app/blueprints/assets/resources.py +241 -4
- zou/app/blueprints/auth/__init__.py +4 -0
- zou/app/blueprints/auth/resources.py +154 -22
- zou/app/blueprints/breakdown/resources.py +4 -4
- zou/app/blueprints/chats/__init__.py +22 -0
- zou/app/blueprints/chats/resources.py +199 -0
- zou/app/blueprints/comments/resources.py +36 -19
- zou/app/blueprints/crud/__init__.py +12 -0
- zou/app/blueprints/crud/attachment_file.py +14 -5
- zou/app/blueprints/crud/base.py +29 -28
- zou/app/blueprints/crud/chat.py +13 -0
- zou/app/blueprints/crud/chat_message.py +13 -0
- zou/app/blueprints/crud/comments.py +85 -29
- zou/app/blueprints/crud/custom_action.py +1 -1
- zou/app/blueprints/crud/day_off.py +47 -9
- zou/app/blueprints/crud/department.py +1 -25
- zou/app/blueprints/crud/entity.py +46 -5
- zou/app/blueprints/crud/entity_type.py +13 -1
- zou/app/blueprints/crud/event.py +1 -1
- zou/app/blueprints/crud/file_status.py +1 -1
- zou/app/blueprints/crud/metadata_descriptor.py +24 -10
- zou/app/blueprints/crud/organisation.py +22 -5
- zou/app/blueprints/crud/output_file.py +1 -1
- zou/app/blueprints/crud/output_type.py +1 -1
- zou/app/blueprints/crud/person.py +32 -24
- zou/app/blueprints/crud/playlist.py +1 -1
- zou/app/blueprints/crud/preview_background_file.py +6 -7
- zou/app/blueprints/crud/preview_file.py +1 -1
- zou/app/blueprints/crud/project.py +14 -6
- zou/app/blueprints/crud/project_status.py +1 -1
- zou/app/blueprints/crud/schedule_item.py +4 -2
- zou/app/blueprints/crud/software.py +1 -1
- zou/app/blueprints/crud/status_automation.py +1 -1
- zou/app/blueprints/crud/studio.py +33 -0
- zou/app/blueprints/crud/task.py +47 -3
- zou/app/blueprints/crud/task_status.py +1 -1
- zou/app/blueprints/crud/task_type.py +4 -4
- zou/app/blueprints/crud/working_file.py +4 -8
- zou/app/blueprints/events/resources.py +13 -12
- zou/app/blueprints/export/csv/assets.py +15 -6
- zou/app/blueprints/export/csv/edits.py +15 -5
- zou/app/blueprints/export/csv/playlists.py +1 -1
- zou/app/blueprints/export/csv/shots.py +15 -5
- zou/app/blueprints/export/csv/time_spents.py +1 -1
- zou/app/blueprints/files/resources.py +22 -23
- zou/app/blueprints/index/resources.py +38 -29
- zou/app/blueprints/news/resources.py +25 -11
- zou/app/blueprints/persons/__init__.py +5 -2
- zou/app/blueprints/persons/resources.py +126 -120
- zou/app/blueprints/previews/__init__.py +18 -8
- zou/app/blueprints/previews/resources.py +569 -328
- zou/app/blueprints/projects/resources.py +1 -1
- zou/app/blueprints/search/resources.py +18 -6
- zou/app/blueprints/shots/__init__.py +5 -0
- zou/app/blueprints/shots/resources.py +134 -4
- zou/app/blueprints/source/__init__.py +6 -6
- zou/app/blueprints/source/csv/assets.py +10 -3
- zou/app/blueprints/source/csv/base.py +1 -1
- zou/app/blueprints/source/csv/edits.py +10 -3
- zou/app/blueprints/source/csv/shots.py +10 -3
- zou/app/blueprints/source/{edl.py → otio.py} +82 -41
- zou/app/blueprints/tasks/__init__.py +3 -2
- zou/app/blueprints/tasks/resources.py +83 -52
- zou/app/blueprints/user/__init__.py +9 -0
- zou/app/blueprints/user/resources.py +170 -12
- zou/app/config.py +10 -0
- zou/app/mixin.py +6 -5
- zou/app/models/attachment_file.py +10 -4
- zou/app/models/base.py +18 -13
- zou/app/models/build_job.py +7 -4
- zou/app/models/chat.py +44 -0
- zou/app/models/chat_message.py +37 -0
- zou/app/models/comment.py +1 -0
- zou/app/models/day_off.py +3 -0
- zou/app/models/entity.py +4 -6
- zou/app/models/entity_type.py +2 -0
- zou/app/models/organisation.py +14 -15
- zou/app/models/person.py +6 -1
- zou/app/models/project.py +3 -0
- zou/app/models/search_filter.py +11 -0
- zou/app/models/search_filter_group.py +10 -0
- zou/app/models/serializer.py +17 -17
- zou/app/models/status_automation.py +2 -0
- zou/app/models/studio.py +13 -0
- zou/app/models/subscription.py +2 -2
- zou/app/models/task.py +6 -1
- zou/app/models/task_status.py +1 -0
- zou/app/models/task_type.py +1 -0
- zou/app/models/working_file.py +1 -1
- zou/app/services/assets_service.py +101 -14
- zou/app/services/auth_service.py +17 -44
- zou/app/services/breakdown_service.py +37 -5
- zou/app/services/chats_service.py +279 -0
- zou/app/services/comments_service.py +110 -65
- zou/app/services/concepts_service.py +4 -12
- zou/app/services/deletion_service.py +43 -30
- zou/app/services/edits_service.py +5 -11
- zou/app/services/emails_service.py +4 -4
- zou/app/services/entities_service.py +17 -2
- zou/app/services/events_service.py +12 -4
- zou/app/services/exception.py +5 -5
- zou/app/services/names_service.py +7 -2
- zou/app/services/news_service.py +17 -9
- zou/app/services/persons_service.py +38 -21
- zou/app/services/playlists_service.py +8 -7
- zou/app/services/preview_files_service.py +137 -10
- zou/app/services/projects_service.py +5 -14
- zou/app/services/shots_service.py +221 -49
- zou/app/services/sync_service.py +46 -42
- zou/app/services/tasks_service.py +185 -46
- zou/app/services/time_spents_service.py +67 -20
- zou/app/services/user_service.py +350 -107
- zou/app/stores/auth_tokens_store.py +2 -1
- zou/app/stores/file_store.py +18 -0
- zou/app/stores/publisher_store.py +7 -7
- zou/app/stores/queue_store.py +1 -0
- zou/app/swagger.py +36 -20
- zou/app/utils/cache.py +2 -0
- zou/app/utils/commands.py +104 -7
- zou/app/utils/csv_utils.py +1 -4
- zou/app/utils/date_helpers.py +33 -17
- zou/app/utils/dbhelpers.py +14 -1
- zou/app/utils/emails.py +2 -2
- zou/app/utils/fido.py +22 -0
- zou/app/utils/query.py +54 -6
- zou/app/utils/redis.py +11 -0
- zou/app/utils/saml.py +51 -0
- zou/app/utils/string.py +2 -0
- zou/app/utils/thumbnail.py +4 -2
- zou/cli.py +76 -18
- zou/debug.py +4 -2
- zou/event_stream.py +122 -165
- zou/job_settings.py +1 -0
- zou/migrations/env.py +0 -0
- zou/migrations/utils/base.py +6 -6
- zou/migrations/versions/1bb55759146f_add_table_studio.py +67 -0
- zou/migrations/versions/1fab8c420678_add_attachments_to_message_chats.py +56 -0
- zou/migrations/versions/23122f290ca2_add_entity_chat_models.py +149 -0
- zou/migrations/versions/32f134ff1201_add_is_shared_flag_to_filters.py +33 -0
- zou/migrations/versions/57222395f2be_add_statusautomation_import_last_revision.py +41 -0
- zou/migrations/versions/59a7445a966c_add_entity_is_shared.py +41 -0
- zou/migrations/versions/5b980f0dc365_add_comment_links.py +35 -0
- zou/migrations/versions/680c64565f9d_for_searchfiltergroup_is_shared.py +35 -0
- zou/migrations/versions/8e67c183bed7_add_preference_fields.py +71 -0
- zou/migrations/versions/92b40d79ad3f_allow_message_attachments.py +38 -0
- zou/migrations/versions/971dbf5a0faf_add_short_name_for_asset_type_entity_.py +33 -0
- zou/migrations/versions/9b85c14fa8a7_add_day_off_new_columns.py +68 -0
- zou/migrations/versions/9d3bb33c6fc6_add_department_keys_to_filter_models.py +73 -0
- zou/migrations/versions/a252a094e977_add_descriptions_for_entities_tasks_and_.py +40 -0
- zou/migrations/versions/be56dc0fb760_for_is_shared_disallow_nullable.py +102 -0
- zou/migrations/versions/ca28796a2a62_add_is_done_field_to_the_task_model.py +108 -0
- zou/migrations/versions/f344b867a911_for_description_of_entity_task_working_.py +75 -0
- zou/remote/config_payload.py +2 -1
- zou/utils/movie.py +14 -4
- {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/METADATA +75 -69
- {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/RECORD +163 -134
- {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/WHEEL +1 -1
- {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/LICENSE +0 -0
- {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/entry_points.txt +0 -0
- {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import orjson as json
|
|
2
3
|
|
|
3
4
|
from flask import abort, request, current_app
|
|
4
5
|
from flask import send_file as flask_send_file
|
|
@@ -11,7 +12,8 @@ from zou.app import config
|
|
|
11
12
|
from zou.app.mixin import ArgsMixin
|
|
12
13
|
from zou.app.stores import file_store
|
|
13
14
|
from zou.app.services import (
|
|
14
|
-
|
|
15
|
+
comments_service,
|
|
16
|
+
chats_service,
|
|
15
17
|
deletion_service,
|
|
16
18
|
entities_service,
|
|
17
19
|
files_service,
|
|
@@ -19,7 +21,6 @@ from zou.app.services import (
|
|
|
19
21
|
persons_service,
|
|
20
22
|
projects_service,
|
|
21
23
|
preview_files_service,
|
|
22
|
-
shots_service,
|
|
23
24
|
tasks_service,
|
|
24
25
|
user_service,
|
|
25
26
|
)
|
|
@@ -30,9 +31,10 @@ from zou.app.utils import (
|
|
|
30
31
|
events,
|
|
31
32
|
permissions,
|
|
32
33
|
thumbnail as thumbnail_utils,
|
|
34
|
+
date_helpers,
|
|
33
35
|
)
|
|
34
36
|
from zou.app.services.exception import (
|
|
35
|
-
|
|
37
|
+
WrongParameterException,
|
|
36
38
|
PreviewBackgroundFileNotFoundException,
|
|
37
39
|
PreviewFileReuploadNotAllowedException,
|
|
38
40
|
)
|
|
@@ -62,16 +64,21 @@ ALLOWED_FILE_EXTENSION = [
|
|
|
62
64
|
"glb",
|
|
63
65
|
"gltf",
|
|
64
66
|
"hip",
|
|
67
|
+
"kra",
|
|
65
68
|
"ma",
|
|
66
69
|
"mb",
|
|
67
70
|
"mp3",
|
|
68
71
|
"obj",
|
|
69
72
|
"pdf",
|
|
70
73
|
"psd",
|
|
74
|
+
"psb",
|
|
71
75
|
"rar",
|
|
76
|
+
"sai",
|
|
77
|
+
"sai2",
|
|
72
78
|
"sbbkp",
|
|
73
79
|
"svg",
|
|
74
80
|
"swf",
|
|
81
|
+
"tvpp",
|
|
75
82
|
"wav",
|
|
76
83
|
"zip",
|
|
77
84
|
]
|
|
@@ -83,6 +90,7 @@ def send_standard_file(
|
|
|
83
90
|
extension,
|
|
84
91
|
mimetype="application/octet-stream",
|
|
85
92
|
as_attachment=False,
|
|
93
|
+
last_modified=None,
|
|
86
94
|
):
|
|
87
95
|
return send_storage_file(
|
|
88
96
|
file_store.get_local_file_path,
|
|
@@ -92,10 +100,13 @@ def send_standard_file(
|
|
|
92
100
|
extension,
|
|
93
101
|
mimetype=mimetype,
|
|
94
102
|
as_attachment=as_attachment,
|
|
103
|
+
last_modified=last_modified,
|
|
95
104
|
)
|
|
96
105
|
|
|
97
106
|
|
|
98
|
-
def send_movie_file(
|
|
107
|
+
def send_movie_file(
|
|
108
|
+
preview_file_id, as_attachment=False, lowdef=False, last_modified=None
|
|
109
|
+
):
|
|
99
110
|
folder = "previews"
|
|
100
111
|
if lowdef:
|
|
101
112
|
folder = "lowdef"
|
|
@@ -107,6 +118,7 @@ def send_movie_file(preview_file_id, as_attachment=False, lowdef=False):
|
|
|
107
118
|
"mp4",
|
|
108
119
|
mimetype="video/mp4",
|
|
109
120
|
as_attachment=as_attachment,
|
|
121
|
+
last_modified=last_modified,
|
|
110
122
|
)
|
|
111
123
|
|
|
112
124
|
|
|
@@ -116,6 +128,7 @@ def send_picture_file(
|
|
|
116
128
|
as_attachment=False,
|
|
117
129
|
extension="png",
|
|
118
130
|
download_name="",
|
|
131
|
+
last_modified=None,
|
|
119
132
|
):
|
|
120
133
|
if extension == "png":
|
|
121
134
|
mimetype = "image/png"
|
|
@@ -130,6 +143,7 @@ def send_picture_file(
|
|
|
130
143
|
mimetype=mimetype,
|
|
131
144
|
as_attachment=as_attachment,
|
|
132
145
|
download_name=download_name,
|
|
146
|
+
last_modified=last_modified,
|
|
133
147
|
)
|
|
134
148
|
|
|
135
149
|
|
|
@@ -143,6 +157,7 @@ def send_storage_file(
|
|
|
143
157
|
as_attachment=False,
|
|
144
158
|
max_age=config.CLIENT_CACHE_MAX_AGE,
|
|
145
159
|
download_name="",
|
|
160
|
+
last_modified=None,
|
|
146
161
|
):
|
|
147
162
|
"""
|
|
148
163
|
Send file from storage. If it's not a local storage, cache the file in
|
|
@@ -186,111 +201,18 @@ def send_storage_file(
|
|
|
186
201
|
as_attachment=as_attachment,
|
|
187
202
|
download_name=download_name,
|
|
188
203
|
max_age=max_age,
|
|
204
|
+
last_modified=last_modified,
|
|
189
205
|
)
|
|
190
206
|
except IOError as e:
|
|
191
207
|
current_app.logger.error(e)
|
|
192
208
|
raise FileNotFound
|
|
193
209
|
|
|
194
210
|
|
|
195
|
-
class
|
|
211
|
+
class BaseNewPreviewFilePicture:
|
|
196
212
|
"""
|
|
197
|
-
|
|
198
|
-
three picture files matching preview when it's possible: a square thumbnail,
|
|
199
|
-
a rectangle thumbnail and a midsize file.
|
|
213
|
+
Base class to add previews.
|
|
200
214
|
"""
|
|
201
215
|
|
|
202
|
-
@jwt_required()
|
|
203
|
-
def post(self, instance_id):
|
|
204
|
-
"""
|
|
205
|
-
Main resource to add a preview.
|
|
206
|
-
---
|
|
207
|
-
tags:
|
|
208
|
-
- Previews
|
|
209
|
-
description: "It stores the preview file and generates three picture files matching preview when it's possible: a square thumbnail, a rectangle thumbnail and a midsize file."
|
|
210
|
-
consumes:
|
|
211
|
-
- multipart/form-data
|
|
212
|
-
- image/png
|
|
213
|
-
- application/pdf
|
|
214
|
-
parameters:
|
|
215
|
-
- in: path
|
|
216
|
-
name: instance_id
|
|
217
|
-
required: True
|
|
218
|
-
type: string
|
|
219
|
-
format: UUID
|
|
220
|
-
x-example: a24a6ea4-ce75-4665-a070-57453082c25
|
|
221
|
-
- in: formData
|
|
222
|
-
name: file
|
|
223
|
-
required: True
|
|
224
|
-
type: file
|
|
225
|
-
responses:
|
|
226
|
-
200:
|
|
227
|
-
description: Preview added
|
|
228
|
-
"""
|
|
229
|
-
if not self.is_exist(instance_id):
|
|
230
|
-
abort(404)
|
|
231
|
-
|
|
232
|
-
if not self.is_allowed(instance_id):
|
|
233
|
-
abort(403)
|
|
234
|
-
|
|
235
|
-
uploaded_file = request.files["file"]
|
|
236
|
-
|
|
237
|
-
file_name_parts = uploaded_file.filename.split(".")
|
|
238
|
-
extension = file_name_parts.pop().lower()
|
|
239
|
-
original_file_name = ".".join(file_name_parts)
|
|
240
|
-
|
|
241
|
-
if extension in ALLOWED_PICTURE_EXTENSION:
|
|
242
|
-
metadada = self.save_picture_preview(instance_id, uploaded_file)
|
|
243
|
-
preview_file = preview_files_service.update_preview_file(
|
|
244
|
-
instance_id,
|
|
245
|
-
{
|
|
246
|
-
"extension": "png",
|
|
247
|
-
"original_name": original_file_name,
|
|
248
|
-
"width": metadada["width"],
|
|
249
|
-
"height": metadada["height"],
|
|
250
|
-
"file_size": metadada["file_size"],
|
|
251
|
-
"status": "ready",
|
|
252
|
-
},
|
|
253
|
-
)
|
|
254
|
-
tasks_service.update_preview_file_info(preview_file)
|
|
255
|
-
self.emit_app_preview_event(instance_id)
|
|
256
|
-
return preview_file, 201
|
|
257
|
-
|
|
258
|
-
elif extension in ALLOWED_MOVIE_EXTENSION:
|
|
259
|
-
try:
|
|
260
|
-
normalize = self.get_bool_parameter("normalize", "true")
|
|
261
|
-
self.save_movie_preview(instance_id, uploaded_file, normalize)
|
|
262
|
-
except Exception as e:
|
|
263
|
-
current_app.logger.error(e, exc_info=1)
|
|
264
|
-
current_app.logger.error("Normalization failed.")
|
|
265
|
-
deletion_service.remove_preview_file_by_id(instance_id)
|
|
266
|
-
abort(400, "Normalization failed.")
|
|
267
|
-
preview_file = preview_files_service.update_preview_file(
|
|
268
|
-
instance_id,
|
|
269
|
-
{"extension": "mp4", "original_name": original_file_name},
|
|
270
|
-
)
|
|
271
|
-
self.emit_app_preview_event(instance_id)
|
|
272
|
-
return preview_file, 201
|
|
273
|
-
|
|
274
|
-
elif extension in ALLOWED_FILE_EXTENSION:
|
|
275
|
-
self.save_file_preview(instance_id, uploaded_file, extension)
|
|
276
|
-
preview_file = preview_files_service.update_preview_file(
|
|
277
|
-
instance_id,
|
|
278
|
-
{
|
|
279
|
-
"extension": extension,
|
|
280
|
-
"original_name": original_file_name,
|
|
281
|
-
"status": "ready",
|
|
282
|
-
},
|
|
283
|
-
)
|
|
284
|
-
self.emit_app_preview_event(instance_id)
|
|
285
|
-
return preview_file, 201
|
|
286
|
-
|
|
287
|
-
else:
|
|
288
|
-
current_app.logger.info(
|
|
289
|
-
"Wrong file format, extension: %s", extension
|
|
290
|
-
)
|
|
291
|
-
deletion_service.remove_preview_file_by_id(instance_id)
|
|
292
|
-
abort(400, "Wrong file format, extension: %s" % extension)
|
|
293
|
-
|
|
294
216
|
def save_picture_preview(self, instance_id, uploaded_file):
|
|
295
217
|
"""
|
|
296
218
|
Get uploaded picture, build thumbnails then save everything in the file
|
|
@@ -333,8 +255,6 @@ class CreatePreviewFilePictureResource(Resource, ArgsMixin):
|
|
|
333
255
|
preview_files_service.prepare_and_store_movie(
|
|
334
256
|
preview_file_id, uploaded_movie_path, normalize=normalize
|
|
335
257
|
)
|
|
336
|
-
preview_file = files_service.get_preview_file(preview_file_id)
|
|
337
|
-
tasks_service.update_preview_file_info(preview_file)
|
|
338
258
|
return preview_file_id
|
|
339
259
|
|
|
340
260
|
def save_file_preview(self, instance_id, uploaded_file, extension):
|
|
@@ -381,6 +301,112 @@ class CreatePreviewFilePictureResource(Resource, ArgsMixin):
|
|
|
381
301
|
project_id=task["project_id"],
|
|
382
302
|
)
|
|
383
303
|
|
|
304
|
+
def process_uploaded_file(
|
|
305
|
+
self, instance_id, uploaded_file, abort_on_failed=False
|
|
306
|
+
):
|
|
307
|
+
file_name_parts = uploaded_file.filename.split(".")
|
|
308
|
+
extension = file_name_parts.pop().lower()
|
|
309
|
+
original_file_name = ".".join(file_name_parts)
|
|
310
|
+
preview_file = None
|
|
311
|
+
if extension in ALLOWED_PICTURE_EXTENSION:
|
|
312
|
+
metadada = self.save_picture_preview(instance_id, uploaded_file)
|
|
313
|
+
preview_file = preview_files_service.update_preview_file(
|
|
314
|
+
instance_id,
|
|
315
|
+
{
|
|
316
|
+
"extension": "png",
|
|
317
|
+
"original_name": original_file_name,
|
|
318
|
+
"width": metadada["width"],
|
|
319
|
+
"height": metadada["height"],
|
|
320
|
+
"file_size": metadada["file_size"],
|
|
321
|
+
"status": "ready",
|
|
322
|
+
},
|
|
323
|
+
)
|
|
324
|
+
tasks_service.update_preview_file_info(preview_file)
|
|
325
|
+
elif extension in ALLOWED_MOVIE_EXTENSION:
|
|
326
|
+
try:
|
|
327
|
+
normalize = self.get_bool_parameter("normalize", "true")
|
|
328
|
+
self.save_movie_preview(instance_id, uploaded_file, normalize)
|
|
329
|
+
except Exception as e:
|
|
330
|
+
current_app.logger.error(e, exc_info=1)
|
|
331
|
+
current_app.logger.error("Normalization failed.")
|
|
332
|
+
deletion_service.remove_preview_file_by_id(
|
|
333
|
+
instance_id, force=True
|
|
334
|
+
)
|
|
335
|
+
if abort_on_failed:
|
|
336
|
+
abort(400, "Normalization failed.")
|
|
337
|
+
preview_file = preview_files_service.update_preview_file(
|
|
338
|
+
instance_id,
|
|
339
|
+
{"extension": "mp4", "original_name": original_file_name},
|
|
340
|
+
)
|
|
341
|
+
elif extension in ALLOWED_FILE_EXTENSION:
|
|
342
|
+
self.save_file_preview(instance_id, uploaded_file, extension)
|
|
343
|
+
preview_file = preview_files_service.update_preview_file(
|
|
344
|
+
instance_id,
|
|
345
|
+
{
|
|
346
|
+
"extension": extension,
|
|
347
|
+
"original_name": original_file_name,
|
|
348
|
+
"status": "ready",
|
|
349
|
+
},
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
if preview_file is None:
|
|
353
|
+
current_app.logger.info(
|
|
354
|
+
"Wrong file format, extension: %s", extension
|
|
355
|
+
)
|
|
356
|
+
deletion_service.remove_preview_file_by_id(instance_id)
|
|
357
|
+
if abort_on_failed:
|
|
358
|
+
abort(400, "Wrong file format, extension: %s" % extension)
|
|
359
|
+
else:
|
|
360
|
+
self.emit_app_preview_event(instance_id)
|
|
361
|
+
return preview_file
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
class CreatePreviewFilePictureResource(
|
|
365
|
+
BaseNewPreviewFilePicture, Resource, ArgsMixin
|
|
366
|
+
):
|
|
367
|
+
"""
|
|
368
|
+
Main resource to add a preview. It stores the preview file and generates
|
|
369
|
+
three picture files matching preview when it's possible: a square thumbnail,
|
|
370
|
+
a rectangle thumbnail and a midsize file.
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
@jwt_required()
|
|
374
|
+
def post(self, instance_id):
|
|
375
|
+
"""
|
|
376
|
+
Main resource to add a preview.
|
|
377
|
+
---
|
|
378
|
+
tags:
|
|
379
|
+
- Previews
|
|
380
|
+
description: "It stores the preview file and generates three picture files matching preview when it's possible: a square thumbnail, a rectangle thumbnail and a midsize file."
|
|
381
|
+
consumes:
|
|
382
|
+
- multipart/form-data
|
|
383
|
+
- image/png
|
|
384
|
+
- application/pdf
|
|
385
|
+
parameters:
|
|
386
|
+
- in: path
|
|
387
|
+
name: instance_id
|
|
388
|
+
required: True
|
|
389
|
+
type: string
|
|
390
|
+
format: UUID
|
|
391
|
+
x-example: a24a6ea4-ce75-4665-a070-57453082c25
|
|
392
|
+
- in: formData
|
|
393
|
+
name: file
|
|
394
|
+
required: True
|
|
395
|
+
type: file
|
|
396
|
+
responses:
|
|
397
|
+
200:
|
|
398
|
+
description: Preview added
|
|
399
|
+
"""
|
|
400
|
+
self.is_exist(instance_id)
|
|
401
|
+
self.is_allowed(instance_id)
|
|
402
|
+
|
|
403
|
+
return (
|
|
404
|
+
self.process_uploaded_file(
|
|
405
|
+
instance_id, request.files["file"], abort_on_failed=True
|
|
406
|
+
),
|
|
407
|
+
201,
|
|
408
|
+
)
|
|
409
|
+
|
|
384
410
|
def is_allowed(self, preview_file_id):
|
|
385
411
|
"""
|
|
386
412
|
Return true if user is allowed to add a preview.
|
|
@@ -392,10 +418,8 @@ class CreatePreviewFilePictureResource(Resource, ArgsMixin):
|
|
|
392
418
|
)
|
|
393
419
|
raise PreviewFileReuploadNotAllowedException
|
|
394
420
|
|
|
395
|
-
task = tasks_service.get_task(preview_file["task_id"])
|
|
396
421
|
try:
|
|
397
|
-
user_service.
|
|
398
|
-
user_service.check_entity_access(task["entity_id"])
|
|
422
|
+
user_service.check_task_access(preview_file["task_id"])
|
|
399
423
|
return True
|
|
400
424
|
except permissions.PermissionDenied:
|
|
401
425
|
return False
|
|
@@ -407,26 +431,197 @@ class CreatePreviewFilePictureResource(Resource, ArgsMixin):
|
|
|
407
431
|
return files_service.get_preview_file(preview_file_id) is not None
|
|
408
432
|
|
|
409
433
|
|
|
410
|
-
class
|
|
434
|
+
class BaseBatchComment(BaseNewPreviewFilePicture, ArgsMixin):
|
|
411
435
|
"""
|
|
412
|
-
|
|
436
|
+
Base class to add comments/previews/attachments.
|
|
437
|
+
"""
|
|
438
|
+
|
|
439
|
+
def get_comments_args(self):
|
|
440
|
+
"""
|
|
441
|
+
Return comments arguments.
|
|
442
|
+
"""
|
|
443
|
+
if request.is_json:
|
|
444
|
+
return self.get_args(
|
|
445
|
+
[
|
|
446
|
+
{
|
|
447
|
+
"name": "comments",
|
|
448
|
+
"required": True,
|
|
449
|
+
"default": [],
|
|
450
|
+
"type": dict,
|
|
451
|
+
"action": "append",
|
|
452
|
+
"help": "List of comments to add",
|
|
453
|
+
}
|
|
454
|
+
],
|
|
455
|
+
)
|
|
456
|
+
else:
|
|
457
|
+
args = self.get_args(
|
|
458
|
+
[
|
|
459
|
+
{
|
|
460
|
+
"name": "comments",
|
|
461
|
+
"required": True,
|
|
462
|
+
"default": "[]",
|
|
463
|
+
"help": "List of comments to add",
|
|
464
|
+
}
|
|
465
|
+
],
|
|
466
|
+
)
|
|
467
|
+
args["comments"] = json.loads(args["comments"])
|
|
468
|
+
return args
|
|
469
|
+
|
|
470
|
+
def process_comments(self, task_id=None):
|
|
471
|
+
"""
|
|
472
|
+
Process comments.
|
|
473
|
+
"""
|
|
474
|
+
args = self.get_comments_args()
|
|
475
|
+
|
|
476
|
+
if task_id is not None:
|
|
477
|
+
user_service.check_task_access(task_id)
|
|
478
|
+
|
|
479
|
+
new_comments = []
|
|
480
|
+
for i, comment in enumerate(args["comments"]):
|
|
481
|
+
user_service.check_task_status_access(comment["task_status_id"])
|
|
482
|
+
|
|
483
|
+
if task_id is None:
|
|
484
|
+
user_service.check_task_access(comment["task_id"])
|
|
485
|
+
|
|
486
|
+
if not permissions.has_manager_permissions():
|
|
487
|
+
comment["person_id"] = None
|
|
488
|
+
comment["created_at"] = None
|
|
489
|
+
|
|
490
|
+
new_comment = comments_service.create_comment(
|
|
491
|
+
comment.get("person_id", None),
|
|
492
|
+
task_id or comment["task_id"],
|
|
493
|
+
comment["task_status_id"],
|
|
494
|
+
comment["text"],
|
|
495
|
+
comment.get("checklist", []),
|
|
496
|
+
{
|
|
497
|
+
k: v
|
|
498
|
+
for (k, v) in request.files.items()
|
|
499
|
+
if f"attachment_file-{i}" in k
|
|
500
|
+
},
|
|
501
|
+
comment.get("created_at", None),
|
|
502
|
+
comment.get("links", []),
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
new_comment["preview_files"] = []
|
|
506
|
+
for uploaded_preview_file in {
|
|
507
|
+
k: v
|
|
508
|
+
for (k, v) in request.files.items()
|
|
509
|
+
if f"preview_file-{i}" in k
|
|
510
|
+
}.values():
|
|
511
|
+
new_preview_file = tasks_service.add_preview_file_to_comment(
|
|
512
|
+
new_comment["id"],
|
|
513
|
+
new_comment["person_id"],
|
|
514
|
+
task_id or comment["task_id"],
|
|
515
|
+
)
|
|
516
|
+
new_preview_file = self.process_uploaded_file(
|
|
517
|
+
new_preview_file["id"],
|
|
518
|
+
uploaded_preview_file,
|
|
519
|
+
abort_on_failed=False,
|
|
520
|
+
)
|
|
521
|
+
if new_preview_file:
|
|
522
|
+
new_comment["preview_files"].append(new_preview_file)
|
|
523
|
+
|
|
524
|
+
new_comments.append(new_comment)
|
|
525
|
+
|
|
526
|
+
return new_comments, 201
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
class AddTaskBatchCommentResource(BaseBatchComment, Resource):
|
|
530
|
+
"""
|
|
531
|
+
Creates new comments for given task. Each comments requires a text, a
|
|
532
|
+
task_status and a person as arguments.
|
|
533
|
+
"""
|
|
534
|
+
|
|
535
|
+
@jwt_required()
|
|
536
|
+
def post(self, task_id):
|
|
537
|
+
"""
|
|
538
|
+
Creates new comments for given task. Each comments requires a text, a
|
|
539
|
+
task_status and a person as arguments.
|
|
540
|
+
---
|
|
541
|
+
tags:
|
|
542
|
+
- Comments
|
|
543
|
+
description: Creates new comments for given task. Each comments requires
|
|
544
|
+
a text, a task_status and a person as arguments.
|
|
545
|
+
parameters:
|
|
546
|
+
- in: path
|
|
547
|
+
name: task_id
|
|
548
|
+
required: True
|
|
549
|
+
type: string
|
|
550
|
+
format: UUID
|
|
551
|
+
x-example: a24a6ea4-ce75-4665-a070-57453082c25
|
|
552
|
+
- in: body
|
|
553
|
+
name: Comment
|
|
554
|
+
description: person ID, name, comment, revision and change status of task
|
|
555
|
+
schema:
|
|
556
|
+
type: object
|
|
557
|
+
required:
|
|
558
|
+
- comments
|
|
559
|
+
properties:
|
|
560
|
+
comments:
|
|
561
|
+
type: string
|
|
562
|
+
responses:
|
|
563
|
+
201:
|
|
564
|
+
description: New comments created
|
|
565
|
+
"""
|
|
566
|
+
return self.process_comments(task_id)
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
class AddTasksBatchCommentResource(BaseBatchComment, Resource):
|
|
570
|
+
"""
|
|
571
|
+
Creates new comments for given tasks. Each comments requires a task_id,
|
|
572
|
+
text, a task_status and a person as arguments.
|
|
573
|
+
"""
|
|
574
|
+
|
|
575
|
+
@jwt_required()
|
|
576
|
+
def post(self):
|
|
577
|
+
"""
|
|
578
|
+
Creates new comments for given task. Each comments requires a task_id,
|
|
579
|
+
text, a task_status and a person as arguments.
|
|
580
|
+
---
|
|
581
|
+
tags:
|
|
582
|
+
- Comments
|
|
583
|
+
description: Creates new comments for given task. Each comments requires
|
|
584
|
+
a task_id, a text, a task_status and a person as arguments.
|
|
585
|
+
parameters:
|
|
586
|
+
- in: body
|
|
587
|
+
name: Comment
|
|
588
|
+
description: person ID, name, comment, revision and change status of task
|
|
589
|
+
schema:
|
|
590
|
+
type: object
|
|
591
|
+
required:
|
|
592
|
+
- comments
|
|
593
|
+
properties:
|
|
594
|
+
comments:
|
|
595
|
+
type: string
|
|
596
|
+
responses:
|
|
597
|
+
201:
|
|
598
|
+
description: New comments created
|
|
599
|
+
"""
|
|
600
|
+
return self.process_comments()
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
class BasePreviewFileResource(Resource):
|
|
604
|
+
"""
|
|
605
|
+
Base class to download a preview file.
|
|
413
606
|
"""
|
|
414
607
|
|
|
415
608
|
def __init__(self):
|
|
416
609
|
Resource.__init__(self)
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
return files_service.get_preview_file(preview_file_id) is not None
|
|
610
|
+
self.preview_file = None
|
|
611
|
+
self.last_modified = None
|
|
420
612
|
|
|
421
613
|
def is_allowed(self, preview_file_id):
|
|
422
|
-
preview_file = files_service.get_preview_file(preview_file_id)
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
614
|
+
self.preview_file = files_service.get_preview_file(preview_file_id)
|
|
615
|
+
user_service.check_task_access(self.preview_file["task_id"])
|
|
616
|
+
self.last_modified = date_helpers.get_datetime_from_string(
|
|
617
|
+
self.preview_file["updated_at"]
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
class PreviewFileMovieResource(BasePreviewFileResource):
|
|
622
|
+
"""
|
|
623
|
+
Allow to download a movie preview.
|
|
624
|
+
"""
|
|
430
625
|
|
|
431
626
|
@jwt_required()
|
|
432
627
|
def get(self, instance_id):
|
|
@@ -451,14 +646,12 @@ class PreviewFileMovieResource(Resource):
|
|
|
451
646
|
404:
|
|
452
647
|
description: File not found
|
|
453
648
|
"""
|
|
454
|
-
|
|
455
|
-
abort(404)
|
|
456
|
-
|
|
457
|
-
if not self.is_allowed(instance_id):
|
|
458
|
-
abort(403)
|
|
649
|
+
self.is_allowed(instance_id)
|
|
459
650
|
|
|
460
651
|
try:
|
|
461
|
-
return send_movie_file(
|
|
652
|
+
return send_movie_file(
|
|
653
|
+
instance_id, last_modified=self.last_modified
|
|
654
|
+
)
|
|
462
655
|
except FileNotFound:
|
|
463
656
|
current_app.logger.error(
|
|
464
657
|
"Movie file was not found for: %s" % instance_id
|
|
@@ -466,7 +659,7 @@ class PreviewFileMovieResource(Resource):
|
|
|
466
659
|
abort(404)
|
|
467
660
|
|
|
468
661
|
|
|
469
|
-
class PreviewFileLowMovieResource(
|
|
662
|
+
class PreviewFileLowMovieResource(BasePreviewFileResource):
|
|
470
663
|
"""
|
|
471
664
|
Allow to download a lowdef movie preview.
|
|
472
665
|
"""
|
|
@@ -493,14 +686,17 @@ class PreviewFileLowMovieResource(PreviewFileMovieResource):
|
|
|
493
686
|
404:
|
|
494
687
|
description: File not found
|
|
495
688
|
"""
|
|
496
|
-
|
|
497
|
-
abort(403)
|
|
689
|
+
self.is_allowed(instance_id)
|
|
498
690
|
|
|
499
691
|
try:
|
|
500
|
-
return send_movie_file(
|
|
692
|
+
return send_movie_file(
|
|
693
|
+
instance_id, lowdef=True, last_modified=self.last_modified
|
|
694
|
+
)
|
|
501
695
|
except Exception:
|
|
502
696
|
try:
|
|
503
|
-
return send_movie_file(
|
|
697
|
+
return send_movie_file(
|
|
698
|
+
instance_id, last_modified=self.last_modified
|
|
699
|
+
)
|
|
504
700
|
except FileNotFound:
|
|
505
701
|
current_app.logger.error(
|
|
506
702
|
"Movie file was not found for: %s" % instance_id
|
|
@@ -508,7 +704,7 @@ class PreviewFileLowMovieResource(PreviewFileMovieResource):
|
|
|
508
704
|
abort(404)
|
|
509
705
|
|
|
510
706
|
|
|
511
|
-
class PreviewFileMovieDownloadResource(
|
|
707
|
+
class PreviewFileMovieDownloadResource(BasePreviewFileResource):
|
|
512
708
|
"""
|
|
513
709
|
Allow to download a movie preview.
|
|
514
710
|
"""
|
|
@@ -535,11 +731,14 @@ class PreviewFileMovieDownloadResource(PreviewFileMovieResource):
|
|
|
535
731
|
404:
|
|
536
732
|
description: File not found
|
|
537
733
|
"""
|
|
538
|
-
|
|
539
|
-
abort(403)
|
|
734
|
+
self.is_allowed(instance_id)
|
|
540
735
|
|
|
541
736
|
try:
|
|
542
|
-
return send_movie_file(
|
|
737
|
+
return send_movie_file(
|
|
738
|
+
instance_id,
|
|
739
|
+
as_attachment=True,
|
|
740
|
+
last_modified=self.last_modified,
|
|
741
|
+
)
|
|
543
742
|
except FileNotFound:
|
|
544
743
|
current_app.logger.error(
|
|
545
744
|
"Movie file was not found for: %s" % instance_id
|
|
@@ -547,30 +746,11 @@ class PreviewFileMovieDownloadResource(PreviewFileMovieResource):
|
|
|
547
746
|
abort(404)
|
|
548
747
|
|
|
549
748
|
|
|
550
|
-
class PreviewFileResource(
|
|
749
|
+
class PreviewFileResource(BasePreviewFileResource):
|
|
551
750
|
"""
|
|
552
751
|
Allow to download a generic file preview.
|
|
553
752
|
"""
|
|
554
753
|
|
|
555
|
-
def __init__(self):
|
|
556
|
-
Resource.__init__(self)
|
|
557
|
-
|
|
558
|
-
def is_exist(self, preview_file_id):
|
|
559
|
-
return files_service.get_preview_file(preview_file_id) is not None
|
|
560
|
-
|
|
561
|
-
def is_allowed(self, preview_file_id):
|
|
562
|
-
if permissions.has_manager_permissions():
|
|
563
|
-
return True
|
|
564
|
-
else:
|
|
565
|
-
preview_file = files_service.get_preview_file(preview_file_id)
|
|
566
|
-
task = tasks_service.get_task(preview_file["task_id"])
|
|
567
|
-
try:
|
|
568
|
-
user_service.check_project_access(task["project_id"])
|
|
569
|
-
user_service.check_entity_access(task["entity_id"])
|
|
570
|
-
return True
|
|
571
|
-
except permissions.PermissionDenied:
|
|
572
|
-
return False
|
|
573
|
-
|
|
574
754
|
@jwt_required()
|
|
575
755
|
def get(self, instance_id, extension):
|
|
576
756
|
"""
|
|
@@ -598,21 +778,26 @@ class PreviewFileResource(Resource):
|
|
|
598
778
|
404:
|
|
599
779
|
description: Non-movie file not found
|
|
600
780
|
"""
|
|
601
|
-
|
|
602
|
-
abort(404)
|
|
603
|
-
|
|
604
|
-
if not self.is_allowed(instance_id):
|
|
605
|
-
abort(403)
|
|
781
|
+
self.is_allowed(instance_id)
|
|
606
782
|
|
|
607
783
|
try:
|
|
608
784
|
extension = extension.lower()
|
|
609
785
|
if extension == "png":
|
|
610
|
-
return send_picture_file(
|
|
786
|
+
return send_picture_file(
|
|
787
|
+
"original", instance_id, last_modified=self.last_modified
|
|
788
|
+
)
|
|
611
789
|
elif extension == "pdf":
|
|
612
790
|
mimetype = "application/pdf"
|
|
613
|
-
return send_standard_file(
|
|
791
|
+
return send_standard_file(
|
|
792
|
+
instance_id,
|
|
793
|
+
extension,
|
|
794
|
+
mimetype,
|
|
795
|
+
last_modified=self.last_modified,
|
|
796
|
+
)
|
|
614
797
|
else:
|
|
615
|
-
return send_standard_file(
|
|
798
|
+
return send_standard_file(
|
|
799
|
+
instance_id, extension, last_modified=self.last_modified
|
|
800
|
+
)
|
|
616
801
|
|
|
617
802
|
except FileNotFound:
|
|
618
803
|
current_app.logger.error(
|
|
@@ -621,14 +806,11 @@ class PreviewFileResource(Resource):
|
|
|
621
806
|
abort(404)
|
|
622
807
|
|
|
623
808
|
|
|
624
|
-
class PreviewFileDownloadResource(
|
|
809
|
+
class PreviewFileDownloadResource(BasePreviewFileResource):
|
|
625
810
|
"""
|
|
626
811
|
Allow to download a generic file preview as attachment.
|
|
627
812
|
"""
|
|
628
813
|
|
|
629
|
-
def __init__(self):
|
|
630
|
-
PreviewFileResource.__init__(self)
|
|
631
|
-
|
|
632
814
|
@jwt_required()
|
|
633
815
|
def get(self, instance_id):
|
|
634
816
|
"""
|
|
@@ -651,25 +833,40 @@ class PreviewFileDownloadResource(PreviewFileResource):
|
|
|
651
833
|
404:
|
|
652
834
|
description: Standard file not found
|
|
653
835
|
"""
|
|
654
|
-
|
|
655
|
-
abort(403)
|
|
836
|
+
self.is_allowed(instance_id)
|
|
656
837
|
|
|
657
|
-
|
|
658
|
-
extension = preview_file["extension"]
|
|
838
|
+
extension = self.preview_file["extension"]
|
|
659
839
|
|
|
660
840
|
try:
|
|
661
841
|
if extension == "png":
|
|
662
842
|
return send_picture_file(
|
|
663
|
-
"original",
|
|
843
|
+
"original",
|
|
844
|
+
instance_id,
|
|
845
|
+
as_attachment=True,
|
|
846
|
+
last_modified=self.last_modified,
|
|
664
847
|
)
|
|
665
848
|
elif extension == "pdf":
|
|
666
849
|
mimetype = "application/pdf"
|
|
667
850
|
return send_standard_file(
|
|
668
|
-
instance_id,
|
|
851
|
+
instance_id,
|
|
852
|
+
extension,
|
|
853
|
+
mimetype,
|
|
854
|
+
as_attachment=True,
|
|
855
|
+
last_modified=self.last_modified,
|
|
856
|
+
)
|
|
857
|
+
if extension == "mp4":
|
|
858
|
+
return send_picture_file(
|
|
859
|
+
"original",
|
|
860
|
+
instance_id,
|
|
861
|
+
as_attachment=True,
|
|
862
|
+
last_modified=self.last_modified,
|
|
669
863
|
)
|
|
670
864
|
else:
|
|
671
865
|
return send_standard_file(
|
|
672
|
-
instance_id,
|
|
866
|
+
instance_id,
|
|
867
|
+
extension,
|
|
868
|
+
as_attachment=True,
|
|
869
|
+
last_modified=self.last_modified,
|
|
673
870
|
)
|
|
674
871
|
except FileNotFound:
|
|
675
872
|
current_app.logger.error(
|
|
@@ -678,31 +875,82 @@ class PreviewFileDownloadResource(PreviewFileResource):
|
|
|
678
875
|
abort(404)
|
|
679
876
|
|
|
680
877
|
|
|
681
|
-
class
|
|
878
|
+
class AttachmentThumbnailResource(Resource):
|
|
879
|
+
|
|
880
|
+
def __init__(self):
|
|
881
|
+
Resource.__init__(self)
|
|
882
|
+
self.attachment_file = None
|
|
883
|
+
|
|
884
|
+
def is_allowed(self, attachment_id):
|
|
885
|
+
self.attachment_file = comments_service.get_attachment_file(
|
|
886
|
+
attachment_id
|
|
887
|
+
)
|
|
888
|
+
if self.attachment_file["comment_id"] is not None:
|
|
889
|
+
comment = tasks_service.get_comment(
|
|
890
|
+
self.attachment_file["comment_id"]
|
|
891
|
+
)
|
|
892
|
+
user_service.check_task_access(comment["object_id"])
|
|
893
|
+
elif self.attachment_file["chat_message_id"] is not None:
|
|
894
|
+
message = chats_service.get_chat_message(
|
|
895
|
+
self.attachment_file["chat_message_id"]
|
|
896
|
+
)
|
|
897
|
+
chat = chats_service.get_chat_by_id(message["chat_id"])
|
|
898
|
+
entity = entities_service.get_entity(chat["object_id"])
|
|
899
|
+
user_service.check_project_access(entity["project_id"])
|
|
900
|
+
user_service.check_entity_access(chat["object_id"])
|
|
901
|
+
else:
|
|
902
|
+
raise permissions.PermissionDenied
|
|
903
|
+
return True
|
|
904
|
+
|
|
905
|
+
@jwt_required()
|
|
906
|
+
def get(self, attachment_file_id):
|
|
907
|
+
"""
|
|
908
|
+
Download the thumbnail representing given attachment file.
|
|
909
|
+
---
|
|
910
|
+
tags:
|
|
911
|
+
- Previews
|
|
912
|
+
parameters:
|
|
913
|
+
- in: path
|
|
914
|
+
name: attachment_file_id
|
|
915
|
+
required: True
|
|
916
|
+
type: string
|
|
917
|
+
format: UUID
|
|
918
|
+
x-example: a24a6ea4-ce75-4665-a070-57453082c25
|
|
919
|
+
responses:
|
|
920
|
+
200:
|
|
921
|
+
description: Thumbnail downloaded
|
|
922
|
+
403:
|
|
923
|
+
description: Instance not allowed
|
|
924
|
+
404:
|
|
925
|
+
description: Picture file not found
|
|
926
|
+
"""
|
|
927
|
+
self.is_allowed(attachment_file_id)
|
|
928
|
+
|
|
929
|
+
try:
|
|
930
|
+
return send_picture_file(
|
|
931
|
+
"thumbnails",
|
|
932
|
+
attachment_file_id,
|
|
933
|
+
last_modified=date_helpers.get_datetime_from_string(
|
|
934
|
+
self.attachment_file["updated_at"]
|
|
935
|
+
),
|
|
936
|
+
)
|
|
937
|
+
except FileNotFound:
|
|
938
|
+
current_app.logger.error(
|
|
939
|
+
"Picture file was not found for attachment: %s"
|
|
940
|
+
% (attachment_file_id)
|
|
941
|
+
)
|
|
942
|
+
abort(404)
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
class BasePreviewPictureResource(BasePreviewFileResource):
|
|
682
946
|
"""
|
|
683
947
|
Base class to download a thumbnail.
|
|
684
948
|
"""
|
|
685
949
|
|
|
686
950
|
def __init__(self, picture_type):
|
|
687
|
-
|
|
951
|
+
BasePreviewFileResource.__init__(self)
|
|
688
952
|
self.picture_type = picture_type
|
|
689
953
|
|
|
690
|
-
def is_exist(self, preview_file_id):
|
|
691
|
-
return files_service.get_preview_file(preview_file_id) is not None
|
|
692
|
-
|
|
693
|
-
def is_allowed(self, preview_file_id):
|
|
694
|
-
if permissions.has_manager_permissions():
|
|
695
|
-
return True
|
|
696
|
-
else:
|
|
697
|
-
preview_file = files_service.get_preview_file(preview_file_id)
|
|
698
|
-
task = tasks_service.get_task(preview_file["task_id"])
|
|
699
|
-
try:
|
|
700
|
-
user_service.check_project_access(task["project_id"])
|
|
701
|
-
user_service.check_entity_access(task["entity_id"])
|
|
702
|
-
return True
|
|
703
|
-
except permissions.PermissionDenied:
|
|
704
|
-
return False
|
|
705
|
-
|
|
706
954
|
@jwt_required()
|
|
707
955
|
def get(self, instance_id):
|
|
708
956
|
"""
|
|
@@ -725,14 +973,14 @@ class BasePreviewPictureResource(Resource):
|
|
|
725
973
|
404:
|
|
726
974
|
description: Picture file not found
|
|
727
975
|
"""
|
|
728
|
-
|
|
729
|
-
abort(404)
|
|
730
|
-
|
|
731
|
-
if not self.is_allowed(instance_id):
|
|
732
|
-
abort(403)
|
|
976
|
+
self.is_allowed(instance_id)
|
|
733
977
|
|
|
734
978
|
try:
|
|
735
|
-
return send_picture_file(
|
|
979
|
+
return send_picture_file(
|
|
980
|
+
self.picture_type,
|
|
981
|
+
instance_id,
|
|
982
|
+
last_modified=self.last_modified,
|
|
983
|
+
)
|
|
736
984
|
except FileNotFound:
|
|
737
985
|
current_app.logger.error(
|
|
738
986
|
"Picture file was not found for: %s" % instance_id
|
|
@@ -740,7 +988,29 @@ class BasePreviewPictureResource(Resource):
|
|
|
740
988
|
abort(404)
|
|
741
989
|
|
|
742
990
|
|
|
743
|
-
class
|
|
991
|
+
class BasePreviewFileThumbnailResource(BasePreviewPictureResource):
|
|
992
|
+
"""
|
|
993
|
+
Base class to download a thumbnail for a preview file.
|
|
994
|
+
"""
|
|
995
|
+
|
|
996
|
+
def is_allowed(self, preview_file_id):
|
|
997
|
+
self.preview_file = files_service.get_preview_file(preview_file_id)
|
|
998
|
+
task = tasks_service.get_task(self.preview_file["task_id"])
|
|
999
|
+
entity = entities_service.get_entity(task["entity_id"])
|
|
1000
|
+
if (
|
|
1001
|
+
entity["preview_file_id"] != preview_file_id
|
|
1002
|
+
or not entity["is_shared"]
|
|
1003
|
+
or permissions.has_vendor_permissions()
|
|
1004
|
+
):
|
|
1005
|
+
user_service.check_project_access(task["project_id"])
|
|
1006
|
+
user_service.check_entity_access(task["entity_id"])
|
|
1007
|
+
self.last_modified = date_helpers.get_datetime_from_string(
|
|
1008
|
+
self.preview_file["updated_at"]
|
|
1009
|
+
)
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
class PreviewFileThumbnailResource(BasePreviewFileThumbnailResource):
|
|
1013
|
+
|
|
744
1014
|
def __init__(self):
|
|
745
1015
|
BasePreviewPictureResource.__init__(self, "thumbnails")
|
|
746
1016
|
|
|
@@ -759,31 +1029,48 @@ class PreviewFilePreviewResource(BasePreviewPictureResource):
|
|
|
759
1029
|
BasePreviewPictureResource.__init__(self, "previews")
|
|
760
1030
|
|
|
761
1031
|
|
|
762
|
-
class PreviewFileThumbnailSquareResource(
|
|
1032
|
+
class PreviewFileThumbnailSquareResource(BasePreviewFileThumbnailResource):
|
|
763
1033
|
def __init__(self):
|
|
764
1034
|
BasePreviewPictureResource.__init__(self, "thumbnails-square")
|
|
765
1035
|
|
|
766
1036
|
|
|
767
|
-
class PreviewFileOriginalResource(
|
|
1037
|
+
class PreviewFileOriginalResource(BasePreviewFileThumbnailResource):
|
|
768
1038
|
def __init__(self):
|
|
769
1039
|
BasePreviewPictureResource.__init__(self, "original")
|
|
770
1040
|
|
|
771
1041
|
|
|
772
|
-
class
|
|
1042
|
+
class BaseThumbnailResource(Resource):
|
|
773
1043
|
"""
|
|
774
|
-
Base class to
|
|
1044
|
+
Base class to post and get a thumbnail.
|
|
775
1045
|
"""
|
|
776
1046
|
|
|
777
|
-
def __init__(
|
|
1047
|
+
def __init__(
|
|
1048
|
+
self,
|
|
1049
|
+
data_type,
|
|
1050
|
+
get_model_func,
|
|
1051
|
+
update_model_func,
|
|
1052
|
+
size=thumbnail_utils.RECTANGLE_SIZE,
|
|
1053
|
+
):
|
|
778
1054
|
Resource.__init__(self)
|
|
779
1055
|
self.data_type = data_type
|
|
1056
|
+
self.get_model_func = get_model_func
|
|
1057
|
+
self.update_model_func = update_model_func
|
|
780
1058
|
self.size = size
|
|
1059
|
+
self.model = None
|
|
1060
|
+
self.last_modified = None
|
|
781
1061
|
|
|
782
|
-
def
|
|
1062
|
+
def is_exist(self, instance_id):
|
|
1063
|
+
self.model = self.get_model_func(instance_id)
|
|
1064
|
+
|
|
1065
|
+
def check_allowed_to_post(self, instance_id):
|
|
783
1066
|
permissions.check_admin_permissions()
|
|
784
1067
|
|
|
1068
|
+
def check_allowed_to_get(self, instance_id):
|
|
1069
|
+
if not self.model["has_avatar"]:
|
|
1070
|
+
raise NotFound
|
|
1071
|
+
|
|
785
1072
|
def prepare_creation(self, instance_id):
|
|
786
|
-
|
|
1073
|
+
self.model = self.update_model_func(instance_id, {"has_avatar": True})
|
|
787
1074
|
|
|
788
1075
|
def emit_event(self, instance_id):
|
|
789
1076
|
model_name = self.data_type[:-1]
|
|
@@ -820,10 +1107,9 @@ class BaseCreatePictureResource(Resource):
|
|
|
820
1107
|
404:
|
|
821
1108
|
description: Cannot found related object.
|
|
822
1109
|
"""
|
|
823
|
-
|
|
824
|
-
|
|
1110
|
+
self.is_exist(instance_id)
|
|
1111
|
+
self.check_allowed_to_post(instance_id)
|
|
825
1112
|
|
|
826
|
-
self.check_permissions(instance_id)
|
|
827
1113
|
self.prepare_creation(instance_id)
|
|
828
1114
|
|
|
829
1115
|
tmp_folder = config.TMP_DIR
|
|
@@ -846,18 +1132,6 @@ class BaseCreatePictureResource(Resource):
|
|
|
846
1132
|
self.emit_event(instance_id)
|
|
847
1133
|
return {"thumbnail_path": thumbnail_url_path}, 201
|
|
848
1134
|
|
|
849
|
-
|
|
850
|
-
class BasePictureResource(Resource):
|
|
851
|
-
"""
|
|
852
|
-
Base resource to download a thumbnail.
|
|
853
|
-
"""
|
|
854
|
-
|
|
855
|
-
def is_exist(self, instance_id):
|
|
856
|
-
return False
|
|
857
|
-
|
|
858
|
-
def is_allowed(self, instance_id):
|
|
859
|
-
return True
|
|
860
|
-
|
|
861
1135
|
@jwt_required()
|
|
862
1136
|
def get(self, instance_id):
|
|
863
1137
|
"""
|
|
@@ -880,14 +1154,17 @@ class BasePictureResource(Resource):
|
|
|
880
1154
|
404:
|
|
881
1155
|
description: Object instance not found
|
|
882
1156
|
"""
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
if not self.is_allowed(instance_id):
|
|
887
|
-
abort(403)
|
|
1157
|
+
self.is_exist(instance_id)
|
|
1158
|
+
self.check_allowed_to_get(instance_id)
|
|
888
1159
|
|
|
889
1160
|
try:
|
|
890
|
-
return send_picture_file(
|
|
1161
|
+
return send_picture_file(
|
|
1162
|
+
"thumbnails",
|
|
1163
|
+
instance_id,
|
|
1164
|
+
last_modified=date_helpers.get_datetime_from_string(
|
|
1165
|
+
self.model["updated_at"]
|
|
1166
|
+
),
|
|
1167
|
+
)
|
|
891
1168
|
except FileNotFound:
|
|
892
1169
|
current_app.logger.error(
|
|
893
1170
|
"Thumbnail file was not found for: %s" % instance_id
|
|
@@ -900,114 +1177,71 @@ class BasePictureResource(Resource):
|
|
|
900
1177
|
abort(404)
|
|
901
1178
|
|
|
902
1179
|
|
|
903
|
-
class
|
|
1180
|
+
class PersonThumbnailResource(BaseThumbnailResource):
|
|
1181
|
+
|
|
904
1182
|
def __init__(self):
|
|
905
|
-
|
|
906
|
-
self,
|
|
1183
|
+
BaseThumbnailResource.__init__(
|
|
1184
|
+
self,
|
|
1185
|
+
"persons",
|
|
1186
|
+
persons_service.get_person,
|
|
1187
|
+
persons_service.update_person,
|
|
1188
|
+
thumbnail_utils.BIG_SQUARE_SIZE,
|
|
907
1189
|
)
|
|
908
1190
|
|
|
909
|
-
def
|
|
910
|
-
return persons_service.get_person(person_id) is not None
|
|
911
|
-
|
|
912
|
-
def check_permissions(self, instance_id):
|
|
1191
|
+
def check_allowed_to_post(self, instance_id):
|
|
913
1192
|
is_current_user = (
|
|
914
|
-
persons_service.get_current_user()["id"]
|
|
1193
|
+
persons_service.get_current_user()["id"] == instance_id
|
|
915
1194
|
)
|
|
916
|
-
if is_current_user and not permissions.has_admin_permissions():
|
|
1195
|
+
if not is_current_user and not permissions.has_admin_permissions():
|
|
917
1196
|
raise permissions.PermissionDenied
|
|
918
1197
|
|
|
919
1198
|
def prepare_creation(self, instance_id):
|
|
920
|
-
|
|
1199
|
+
self.model = self.update_model_func(
|
|
1200
|
+
instance_id, {"has_avatar": True}, bypass_protected_accounts=True
|
|
1201
|
+
)
|
|
1202
|
+
|
|
921
1203
|
|
|
1204
|
+
class CreatePersonThumbnailResource(PersonThumbnailResource):
|
|
1205
|
+
pass
|
|
922
1206
|
|
|
923
|
-
class PersonThumbnailResource(BasePictureResource):
|
|
924
|
-
def is_exist(self, person_id):
|
|
925
|
-
person = persons_service.get_person(person_id)
|
|
926
|
-
return person is not None and person["has_avatar"]
|
|
927
1207
|
|
|
1208
|
+
class OrganisationThumbnailResource(BaseThumbnailResource):
|
|
928
1209
|
|
|
929
|
-
class CreateOrganisationThumbnailResource(BaseCreatePictureResource):
|
|
930
1210
|
def __init__(self):
|
|
931
|
-
|
|
932
|
-
self,
|
|
1211
|
+
BaseThumbnailResource.__init__(
|
|
1212
|
+
self,
|
|
1213
|
+
"organisations",
|
|
1214
|
+
persons_service.get_organisation,
|
|
1215
|
+
persons_service.update_organisation,
|
|
1216
|
+
thumbnail_utils.BIG_SQUARE_SIZE,
|
|
933
1217
|
)
|
|
934
1218
|
|
|
935
1219
|
def is_exist(self, organisation_id):
|
|
936
|
-
|
|
1220
|
+
self.model = persons_service.get_organisation()
|
|
937
1221
|
|
|
938
|
-
def check_permissions(self, organisation_id):
|
|
939
|
-
if not permissions.has_admin_permissions():
|
|
940
|
-
raise permissions.PermissionDenied
|
|
941
1222
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
organisation_id, {"has_avatar": True}
|
|
945
|
-
)
|
|
1223
|
+
class CreateOrganisationThumbnailResource(OrganisationThumbnailResource):
|
|
1224
|
+
pass
|
|
946
1225
|
|
|
947
1226
|
|
|
948
|
-
class
|
|
949
|
-
def is_exist(self, organisation_id):
|
|
950
|
-
return True
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
class CreateProjectThumbnailResource(BaseCreatePictureResource):
|
|
1227
|
+
class ProjectThumbnailResource(BaseThumbnailResource):
|
|
954
1228
|
def __init__(self):
|
|
955
|
-
|
|
956
|
-
self,
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
def prepare_creation(self, instance_id):
|
|
963
|
-
return projects_service.update_project(
|
|
964
|
-
instance_id, {"has_avatar": True}
|
|
1229
|
+
BaseThumbnailResource.__init__(
|
|
1230
|
+
self,
|
|
1231
|
+
"projects",
|
|
1232
|
+
projects_service.get_project,
|
|
1233
|
+
projects_service.update_project,
|
|
1234
|
+
thumbnail_utils.BIG_SQUARE_SIZE,
|
|
965
1235
|
)
|
|
966
1236
|
|
|
1237
|
+
def check_allowed_to_get(self, instance_id):
|
|
1238
|
+
super().check_allowed_to_get(instance_id)
|
|
1239
|
+
if not permissions.has_manager_permissions():
|
|
1240
|
+
user_service.check_project_access(instance_id)
|
|
967
1241
|
|
|
968
|
-
class ProjectThumbnailResource(BasePictureResource):
|
|
969
|
-
def is_exist(self, project_id):
|
|
970
|
-
return projects_service.get_project(project_id) is not None
|
|
971
1242
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
user_service.check_project_access(project_id)
|
|
975
|
-
return True
|
|
976
|
-
except permissions.PermissionDenied:
|
|
977
|
-
return False
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
class LegacySetMainPreviewResource(Resource):
|
|
981
|
-
@jwt_required()
|
|
982
|
-
def put(self, entity_id, preview_file_id):
|
|
983
|
-
"""
|
|
984
|
-
Set main preview to given file.
|
|
985
|
-
---
|
|
986
|
-
tags:
|
|
987
|
-
- Previews
|
|
988
|
-
parameters:
|
|
989
|
-
- in: path
|
|
990
|
-
name: entity_id
|
|
991
|
-
required: True
|
|
992
|
-
type: string
|
|
993
|
-
format: UUID
|
|
994
|
-
x-example: a24a6ea4-ce75-4665-a070-57453082c25
|
|
995
|
-
- in: path
|
|
996
|
-
name: preview_file_id
|
|
997
|
-
required: True
|
|
998
|
-
type: string
|
|
999
|
-
format: UUID
|
|
1000
|
-
x-example: a24a6ea4-ce75-4665-a070-57453082c25
|
|
1001
|
-
responses:
|
|
1002
|
-
200:
|
|
1003
|
-
description: Main preview set
|
|
1004
|
-
"""
|
|
1005
|
-
preview_file = files_service.get_preview_file(preview_file_id)
|
|
1006
|
-
task = tasks_service.get_task(preview_file["task_id"])
|
|
1007
|
-
user_service.check_project_access(task["project_id"])
|
|
1008
|
-
return entities_service.update_entity_preview(
|
|
1009
|
-
entity_id, preview_file_id
|
|
1010
|
-
)
|
|
1243
|
+
class CreateProjectThumbnailResource(ProjectThumbnailResource):
|
|
1244
|
+
pass
|
|
1011
1245
|
|
|
1012
1246
|
|
|
1013
1247
|
class SetMainPreviewResource(Resource, ArgsMixin):
|
|
@@ -1043,19 +1277,17 @@ class SetMainPreviewResource(Resource, ArgsMixin):
|
|
|
1043
1277
|
user_service.check_entity_access(task["entity_id"])
|
|
1044
1278
|
if frame_number is not None:
|
|
1045
1279
|
if preview_file["extension"] != "mp4":
|
|
1046
|
-
raise
|
|
1280
|
+
raise WrongParameterException(
|
|
1047
1281
|
"Can't use a given frame on non movie preview"
|
|
1048
1282
|
)
|
|
1049
1283
|
preview_files_service.replace_extracted_frame_for_preview_file(
|
|
1050
1284
|
preview_file, frame_number
|
|
1051
1285
|
)
|
|
1052
|
-
|
|
1286
|
+
entity = entities_service.update_entity_preview(
|
|
1053
1287
|
task["entity_id"],
|
|
1054
1288
|
preview_file_id,
|
|
1055
1289
|
)
|
|
1056
|
-
|
|
1057
|
-
shots_service.clear_shot_cache(asset["id"])
|
|
1058
|
-
return asset
|
|
1290
|
+
return entity
|
|
1059
1291
|
|
|
1060
1292
|
|
|
1061
1293
|
class UpdatePreviewPositionResource(Resource, ArgsMixin):
|
|
@@ -1084,9 +1316,7 @@ class UpdatePreviewPositionResource(Resource, ArgsMixin):
|
|
|
1084
1316
|
"""
|
|
1085
1317
|
args = self.get_args([{"name": "position", "default": 0, "type": int}])
|
|
1086
1318
|
preview_file = files_service.get_preview_file(preview_file_id)
|
|
1087
|
-
|
|
1088
|
-
user_service.check_manager_project_access(task["project_id"])
|
|
1089
|
-
user_service.check_entity_access(task["entity_id"])
|
|
1319
|
+
user_service.check_task_access(preview_file["task_id"])
|
|
1090
1320
|
return preview_files_service.update_preview_file_position(
|
|
1091
1321
|
preview_file_id, args["position"]
|
|
1092
1322
|
)
|
|
@@ -1236,9 +1466,7 @@ class ExtractTileFromPreview(Resource):
|
|
|
1236
1466
|
@jwt_required()
|
|
1237
1467
|
def get(self, preview_file_id):
|
|
1238
1468
|
preview_file = files_service.get_preview_file(preview_file_id)
|
|
1239
|
-
|
|
1240
|
-
user_service.check_manager_project_access(task["project_id"])
|
|
1241
|
-
user_service.check_entity_access(task["entity_id"])
|
|
1469
|
+
user_service.check_task_access(preview_file["task_id"])
|
|
1242
1470
|
extracted_tile_path = (
|
|
1243
1471
|
preview_files_service.extract_tile_from_preview_file(preview_file)
|
|
1244
1472
|
)
|
|
@@ -1320,7 +1548,9 @@ class CreatePreviewBackgroundFileResource(Resource):
|
|
|
1320
1548
|
current_app.logger.info(
|
|
1321
1549
|
f"Wrong file format, extension: {extension}"
|
|
1322
1550
|
)
|
|
1323
|
-
deletion_service.remove_preview_background_file_by_id(
|
|
1551
|
+
deletion_service.remove_preview_background_file_by_id(
|
|
1552
|
+
instance_id, force=True
|
|
1553
|
+
)
|
|
1324
1554
|
abort(400, f"Wrong file format, extension: {extension}")
|
|
1325
1555
|
|
|
1326
1556
|
def check_permissions(self, instance_id):
|
|
@@ -1367,7 +1597,9 @@ class CreatePreviewBackgroundFileResource(Resource):
|
|
|
1367
1597
|
current_app.logger.error(
|
|
1368
1598
|
f"Error while saving preview background file and thumbnail: {instance_id}"
|
|
1369
1599
|
)
|
|
1370
|
-
deletion_service.remove_preview_background_file_by_id(
|
|
1600
|
+
deletion_service.remove_preview_background_file_by_id(
|
|
1601
|
+
instance_id, force=True
|
|
1602
|
+
)
|
|
1371
1603
|
abort(
|
|
1372
1604
|
400,
|
|
1373
1605
|
f"Error while saving preview background file and thumbnail: {instance_id}",
|
|
@@ -1443,6 +1675,9 @@ class PreviewBackgroundFileResource(Resource):
|
|
|
1443
1675
|
instance_id,
|
|
1444
1676
|
extension=extension,
|
|
1445
1677
|
download_name=f"{preview_background_file['original_name']}.{extension}",
|
|
1678
|
+
last_modified=date_helpers.get_datetime_from_string(
|
|
1679
|
+
preview_background_file["updated_at"]
|
|
1680
|
+
),
|
|
1446
1681
|
)
|
|
1447
1682
|
except FileNotFound:
|
|
1448
1683
|
current_app.logger.error(
|
|
@@ -1451,11 +1686,17 @@ class PreviewBackgroundFileResource(Resource):
|
|
|
1451
1686
|
raise PreviewBackgroundFileNotFoundException
|
|
1452
1687
|
|
|
1453
1688
|
|
|
1454
|
-
class PreviewBackgroundFileThumbnailResource(
|
|
1455
|
-
def
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1689
|
+
class PreviewBackgroundFileThumbnailResource(BaseThumbnailResource):
|
|
1690
|
+
def __init__(self):
|
|
1691
|
+
BaseThumbnailResource.__init__(
|
|
1692
|
+
self,
|
|
1693
|
+
"preview-backgrounds",
|
|
1694
|
+
files_service.get_preview_background_file,
|
|
1695
|
+
files_service.update_preview_background_file,
|
|
1461
1696
|
)
|
|
1697
|
+
|
|
1698
|
+
def check_allowed_to_get(self, preview_background_file_id):
|
|
1699
|
+
return True
|
|
1700
|
+
|
|
1701
|
+
def post(self, preview_background_file_id):
|
|
1702
|
+
raise AttributeError("Method not allowed")
|