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
@@ -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
- assets_service,
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
- ArgumentsException,
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(preview_file_id, as_attachment=False, lowdef=False):
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 CreatePreviewFilePictureResource(Resource, ArgsMixin):
211
+ class BaseNewPreviewFilePicture:
196
212
  """
197
- Main resource to add a preview. It stores the preview file and generates
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.check_project_access(task["project_id"])
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 PreviewFileMovieResource(Resource):
434
+ class BaseBatchComment(BaseNewPreviewFilePicture, ArgsMixin):
411
435
  """
412
- Allow to download a movie preview.
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
- def is_exist(self, preview_file_id):
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
- task = tasks_service.get_task(preview_file["task_id"])
424
- try:
425
- user_service.check_project_access(task["project_id"])
426
- user_service.check_entity_access(task["entity_id"])
427
- return True
428
- except permissions.PermissionDenied:
429
- return False
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
- if not self.is_exist(instance_id):
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(instance_id)
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(PreviewFileMovieResource):
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
- if not self.is_allowed(instance_id):
497
- abort(403)
689
+ self.is_allowed(instance_id)
498
690
 
499
691
  try:
500
- return send_movie_file(instance_id, lowdef=True)
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(instance_id)
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(PreviewFileMovieResource):
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
- if not self.is_allowed(instance_id):
539
- abort(403)
734
+ self.is_allowed(instance_id)
540
735
 
541
736
  try:
542
- return send_movie_file(instance_id, as_attachment=True)
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(Resource):
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
- if not self.is_exist(instance_id):
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("original", instance_id)
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(instance_id, extension, mimetype)
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(instance_id, extension)
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(PreviewFileResource):
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
- if not self.is_allowed(instance_id):
655
- abort(403)
836
+ self.is_allowed(instance_id)
656
837
 
657
- preview_file = files_service.get_preview_file(instance_id)
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", instance_id, as_attachment=True
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, extension, mimetype, as_attachment=True
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, extension, as_attachment=True
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 BasePreviewPictureResource(Resource):
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
- Resource.__init__(self)
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
- if not self.is_exist(instance_id):
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(self.picture_type, instance_id)
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 PreviewFileThumbnailResource(BasePreviewPictureResource):
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(BasePreviewPictureResource):
1032
+ class PreviewFileThumbnailSquareResource(BasePreviewFileThumbnailResource):
763
1033
  def __init__(self):
764
1034
  BasePreviewPictureResource.__init__(self, "thumbnails-square")
765
1035
 
766
1036
 
767
- class PreviewFileOriginalResource(BasePreviewPictureResource):
1037
+ class PreviewFileOriginalResource(BasePreviewFileThumbnailResource):
768
1038
  def __init__(self):
769
1039
  BasePreviewPictureResource.__init__(self, "original")
770
1040
 
771
1041
 
772
- class BaseCreatePictureResource(Resource):
1042
+ class BaseThumbnailResource(Resource):
773
1043
  """
774
- Base class to create a thumbnail.
1044
+ Base class to post and get a thumbnail.
775
1045
  """
776
1046
 
777
- def __init__(self, data_type, size=thumbnail_utils.RECTANGLE_SIZE):
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 check_permissions(self, instance_id):
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
- pass
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
- if not self.is_exist(instance_id):
824
- abort(404)
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
- if not self.is_exist(instance_id):
884
- abort(404)
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("thumbnails", instance_id)
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 CreatePersonThumbnailResource(BaseCreatePictureResource):
1180
+ class PersonThumbnailResource(BaseThumbnailResource):
1181
+
904
1182
  def __init__(self):
905
- BaseCreatePictureResource.__init__(
906
- self, "persons", thumbnail_utils.BIG_SQUARE_SIZE
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 is_exist(self, person_id):
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"] != instance_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
- return persons_service.update_person(instance_id, {"has_avatar": True})
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
- BaseCreatePictureResource.__init__(
932
- self, "organisations", thumbnail_utils.BIG_SQUARE_SIZE
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
- return True
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
- def prepare_creation(self, organisation_id):
943
- return persons_service.update_organisation(
944
- organisation_id, {"has_avatar": True}
945
- )
1223
+ class CreateOrganisationThumbnailResource(OrganisationThumbnailResource):
1224
+ pass
946
1225
 
947
1226
 
948
- class OrganisationThumbnailResource(BasePictureResource):
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
- BaseCreatePictureResource.__init__(
956
- self, "projects", thumbnail_utils.SQUARE_SIZE
957
- )
958
-
959
- def is_exist(self, project_id):
960
- return projects_service.get_project(project_id) is not None
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
- def is_allowed(self, project_id):
973
- try:
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 ArgumentsException(
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
- asset = entities_service.update_entity_preview(
1286
+ entity = entities_service.update_entity_preview(
1053
1287
  task["entity_id"],
1054
1288
  preview_file_id,
1055
1289
  )
1056
- assets_service.clear_asset_cache(asset["id"])
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
- task = tasks_service.get_task(preview_file["task_id"])
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
- task = tasks_service.get_task(preview_file["task_id"])
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(instance_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(instance_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(BasePictureResource):
1455
- def is_exist(self, preview_background_file_id):
1456
- return (
1457
- files_service.get_preview_background_file(
1458
- preview_background_file_id
1459
- )
1460
- is not None
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")