zou 0.20.8__py3-none-any.whl → 0.20.12__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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.20.8"
1
+ __version__ = "0.20.12"
@@ -57,9 +57,7 @@ class DownloadAttachmentResource(Resource):
57
57
  )
58
58
  if attachment_file["comment_id"] is not None:
59
59
  comment = tasks_service.get_comment(attachment_file["comment_id"])
60
- task = tasks_service.get_task(comment["object_id"])
61
- user_service.check_project_access(task["project_id"])
62
- user_service.check_entity_access(task["entity_id"])
60
+ user_service.check_task_access(comment["object_id"])
63
61
  elif attachment_file["chat_message_id"] is not None:
64
62
  message = chats_service.get_chat_message(
65
63
  attachment_file["chat_message_id"]
@@ -122,9 +120,7 @@ class AckCommentResource(Resource):
122
120
  200:
123
121
  description: Comment acknowledged
124
122
  """
125
- task = tasks_service.get_task(task_id)
126
- user_service.check_project_access(task["project_id"])
127
- user_service.check_entity_access(task["entity_id"])
123
+ user_service.check_task_access(task_id)
128
124
  return comments_service.acknowledge_comment(comment_id)
129
125
 
130
126
 
@@ -193,9 +189,7 @@ class CommentTaskResource(Resource):
193
189
  links,
194
190
  ) = self.get_arguments()
195
191
 
196
- task = tasks_service.get_task(task_id)
197
- user_service.check_project_access(task["project_id"])
198
- user_service.check_entity_access(task["entity_id"])
192
+ user_service.check_task_access(task_id)
199
193
  user_service.check_task_status_access(task_status_id)
200
194
  files = request.files
201
195
 
@@ -496,9 +490,7 @@ class ReplyCommentResource(Resource, ArgsMixin):
496
490
  ]
497
491
  )
498
492
 
499
- task = tasks_service.get_task(task_id)
500
- user_service.check_project_access(task["project_id"])
501
- user_service.check_entity_access(task["entity_id"])
493
+ user_service.check_task_access(task_id)
502
494
  return comments_service.reply_comment(comment_id, args["text"])
503
495
 
504
496
 
@@ -537,9 +529,7 @@ class DeleteReplyCommentResource(Resource):
537
529
  200:
538
530
  description: Given comment reply deleted
539
531
  """
540
- task = tasks_service.get_task(task_id)
541
- user_service.check_project_access(task["project_id"])
542
- user_service.check_entity_access(task["entity_id"])
532
+ user_service.check_task_access(task_id)
543
533
  reply = comments_service.get_reply(comment_id, reply_id)
544
534
  current_user = persons_service.get_current_user()
545
535
  if reply["person_id"] != current_user["id"]:
@@ -20,15 +20,12 @@ class AttachmentFileResource(BaseModelResource):
20
20
  attachment_file = instance
21
21
  if attachment_file["comment_id"] is not None:
22
22
  comment = tasks_service.get_comment(attachment_file["comment_id"])
23
- task = tasks_service.get_task(comment["object_id"])
24
- user_service.check_project_access(task["project_id"])
25
- user_service.check_entity_access(task["entity_id"])
23
+ user_service.check_task_access(comment["object_id"])
26
24
  elif attachment_file["chat_message_id"] is not None:
27
25
  message = chats_service.get_chat_message(
28
26
  attachment_file["chat_message_id"]
29
27
  )
30
28
  chat = chats_service.get_chat(message["chat_id"])
31
- print(chat)
32
29
  user_service.check_entity_access(chat["object_id"])
33
30
  else:
34
31
  raise PermissionDenied()
@@ -207,6 +207,8 @@ class ProjectTaskTypeLinksResource(Resource, ArgsMixin):
207
207
  ]
208
208
  )
209
209
 
210
+ user_service.check_manager_project_access(args["project_id"])
211
+
210
212
  task_type_link = projects_service.create_project_task_type_link(
211
213
  args["project_id"],
212
214
  args["task_type_id"],
@@ -234,6 +236,8 @@ class ProjectTaskStatusLinksResource(Resource, ArgsMixin):
234
236
  ]
235
237
  )
236
238
 
239
+ user_service.check_manager_project_access(args["project_id"])
240
+
237
241
  task_status_link = projects_service.create_project_task_status_link(
238
242
  args["project_id"],
239
243
  args["task_status_id"],
@@ -8,7 +8,7 @@ from zou.app.blueprints.crud.base import BaseModelsResource, BaseModelResource
8
8
  from zou.app.models.entity import Entity
9
9
  from zou.app.models.project import Project
10
10
  from zou.app.models.working_file import WorkingFile
11
- from zou.app.services import user_service, tasks_service, files_service
11
+ from zou.app.services import user_service, files_service
12
12
  from zou.app.utils import permissions
13
13
 
14
14
 
@@ -46,16 +46,12 @@ class WorkingFileResource(BaseModelResource):
46
46
 
47
47
  def check_read_permissions(self, instance):
48
48
  working_file = files_service.get_working_file(instance["id"])
49
- task = tasks_service.get_task(working_file["task_id"])
50
- user_service.check_project_access(task["project_id"])
51
- user_service.check_entity_access(task["entity_id"])
49
+ user_service.check_task_access(working_file["task_id"])
52
50
  return True
53
51
 
54
52
  def check_update_permissions(self, instance, data):
55
53
  working_file = files_service.get_working_file(instance["id"])
56
- task = tasks_service.get_task(working_file["task_id"])
57
- user_service.check_project_access(task["project_id"])
58
- user_service.check_entity_access(task["entity_id"])
54
+ user_service.check_task_access(working_file["task_id"])
59
55
  return True
60
56
 
61
57
  @jwt_required()
@@ -91,9 +91,7 @@ class WorkingFileFileResource(Resource):
91
91
 
92
92
  def check_access(self, working_file_id):
93
93
  working_file = files_service.get_working_file(working_file_id)
94
- task = tasks_service.get_task(working_file["task_id"])
95
- user_service.check_project_access(task["project_id"])
96
- user_service.check_entity_access(task["entity_id"])
94
+ user_service.check_task_access(working_file["task_id"])
97
95
  return working_file
98
96
 
99
97
  def save_uploaded_file_in_temporary_folder(self, working_file_id):
@@ -556,10 +554,8 @@ class LastWorkingFilesResource(Resource):
556
554
  description: Last working files revision for each file name for given task
557
555
  """
558
556
  result = {}
559
- task = tasks_service.get_task(task_id)
560
- user_service.check_project_access(task["project_id"])
561
- user_service.check_entity_access(task["entity_id"])
562
- result = files_service.get_last_working_files_for_task(task["id"])
557
+ user_service.check_task_access(task_id)
558
+ result = files_service.get_last_working_files_for_task(task_id)
563
559
 
564
560
  return result
565
561
 
@@ -588,10 +584,8 @@ class TaskWorkingFilesResource(Resource):
588
584
  description: Last working files revision for each file name for given task
589
585
  """
590
586
  result = {}
591
- task = tasks_service.get_task(task_id)
592
- user_service.check_project_access(task["project_id"])
593
- user_service.check_entity_access(task["entity_id"])
594
- result = files_service.get_working_files_for_task(task["id"])
587
+ user_service.check_task_access(task_id)
588
+ result = files_service.get_working_files_for_task(task_id)
595
589
 
596
590
  return result
597
591
 
@@ -774,9 +768,7 @@ class ModifiedFileResource(Resource):
774
768
  description: Working file modification date updated
775
769
  """
776
770
  working_file = files_service.get_working_file(working_file_id)
777
- task = tasks_service.get_task(working_file["task_id"])
778
- user_service.check_project_access(task["project_id"])
779
- user_service.check_entity_access(task["entity_id"])
771
+ user_service.check_task_access(working_file["task_id"])
780
772
  working_file = files_service.update_working_file(
781
773
  working_file_id,
782
774
  {"updated_at": date_helpers.get_utc_now_datetime()},
@@ -827,9 +819,7 @@ class CommentWorkingFileResource(Resource, ArgsMixin):
827
819
  )
828
820
 
829
821
  working_file = files_service.get_working_file(working_file_id)
830
- task = tasks_service.get_task(working_file["task_id"])
831
- user_service.check_project_access(task["project_id"])
832
- user_service.check_entity_access(task["entity_id"])
822
+ user_service.check_task_access(working_file["task_id"])
833
823
  working_file = self.update_comment(working_file_id, args["comment"])
834
824
  return working_file
835
825
 
@@ -2,7 +2,8 @@ from flask import Blueprint
2
2
  from zou.app.utils.api import configure_api_from_blueprint
3
3
 
4
4
  from zou.app.blueprints.previews.resources import (
5
- AddCommentsPreviewsResource,
5
+ AddTaskBatchCommentResource,
6
+ AddTasksBatchCommentResource,
6
7
  AttachmentThumbnailResource,
7
8
  CreatePreviewFilePictureResource,
8
9
  PreviewFileLowMovieResource,
@@ -39,8 +40,12 @@ routes = [
39
40
  CreatePreviewFilePictureResource,
40
41
  ),
41
42
  (
42
- "/actions/tasks/<task_id>/add-comments-previews",
43
- AddCommentsPreviewsResource,
43
+ "/actions/tasks/<task_id>/batch-comment",
44
+ AddTaskBatchCommentResource,
45
+ ),
46
+ (
47
+ "/actions/tasks/batch-comment",
48
+ AddTasksBatchCommentResource,
44
49
  ),
45
50
  (
46
51
  "/movies/originals/preview-files/<instance_id>.mp4",
@@ -397,11 +397,8 @@ class CreatePreviewFilePictureResource(
397
397
  200:
398
398
  description: Preview added
399
399
  """
400
- if not self.is_exist(instance_id):
401
- abort(404)
402
-
403
- if not self.is_allowed(instance_id):
404
- abort(403)
400
+ self.is_exist(instance_id)
401
+ self.is_allowed(instance_id)
405
402
 
406
403
  return (
407
404
  self.process_uploaded_file(
@@ -421,10 +418,8 @@ class CreatePreviewFilePictureResource(
421
418
  )
422
419
  raise PreviewFileReuploadNotAllowedException
423
420
 
424
- task = tasks_service.get_task(preview_file["task_id"])
425
421
  try:
426
- user_service.check_project_access(task["project_id"])
427
- user_service.check_entity_access(task["entity_id"])
422
+ user_service.check_task_access(preview_file["task_id"])
428
423
  return True
429
424
  except permissions.PermissionDenied:
430
425
  return False
@@ -436,47 +431,17 @@ class CreatePreviewFilePictureResource(
436
431
  return files_service.get_preview_file(preview_file_id) is not None
437
432
 
438
433
 
439
- class AddCommentsPreviewsResource(
440
- BaseNewPreviewFilePicture, Resource, ArgsMixin
441
- ):
434
+ class BaseBatchComment(BaseNewPreviewFilePicture, ArgsMixin):
442
435
  """
443
- Creates new comments for given task. Each comments requires a text, a
444
- task_status and a person as arguments.
436
+ Base class to add comments/previews/attachments.
445
437
  """
446
438
 
447
- @jwt_required()
448
- def post(self, task_id):
439
+ def get_comments_args(self):
449
440
  """
450
- Creates new comments for given task. Each comments requires a text, a
451
- task_status and a person as arguments.
452
- ---
453
- tags:
454
- - Comments
455
- description: Creates new comments for given task. Each comments requires
456
- a text, a task_status and a person as arguments.
457
- parameters:
458
- - in: path
459
- name: task_id
460
- required: True
461
- type: string
462
- format: UUID
463
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
464
- - in: body
465
- name: Comment
466
- description: person ID, name, comment, revision and change status of task
467
- schema:
468
- type: object
469
- required:
470
- - comments
471
- properties:
472
- comments:
473
- type: string
474
- responses:
475
- 201:
476
- description: New comments created
441
+ Return comments arguments.
477
442
  """
478
443
  if request.is_json:
479
- args = self.get_args(
444
+ return self.get_args(
480
445
  [
481
446
  {
482
447
  "name": "comments",
@@ -500,22 +465,31 @@ class AddCommentsPreviewsResource(
500
465
  ],
501
466
  )
502
467
  args["comments"] = json.loads(args["comments"])
468
+ return args
503
469
 
504
- task = tasks_service.get_task(task_id)
505
- user_service.check_project_access(task["project_id"])
506
- user_service.check_entity_access(task["entity_id"])
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)
507
478
 
508
479
  new_comments = []
509
480
  for i, comment in enumerate(args["comments"]):
510
481
  user_service.check_task_status_access(comment["task_status_id"])
511
482
 
483
+ if task_id is None:
484
+ user_service.check_task_access(comment["task_id"])
485
+
512
486
  if not permissions.has_manager_permissions():
513
487
  comment["person_id"] = None
514
488
  comment["created_at"] = None
515
489
 
516
490
  new_comment = comments_service.create_comment(
517
491
  comment.get("person_id", None),
518
- task_id,
492
+ task_id or comment["task_id"],
519
493
  comment["task_status_id"],
520
494
  comment["text"],
521
495
  comment.get("checklist", []),
@@ -537,7 +511,7 @@ class AddCommentsPreviewsResource(
537
511
  new_preview_file = tasks_service.add_preview_file_to_comment(
538
512
  new_comment["id"],
539
513
  new_comment["person_id"],
540
- task_id,
514
+ task_id or comment["task_id"],
541
515
  )
542
516
  new_preview_file = self.process_uploaded_file(
543
517
  new_preview_file["id"],
@@ -552,6 +526,80 @@ class AddCommentsPreviewsResource(
552
526
  return new_comments, 201
553
527
 
554
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
+
555
603
  class BasePreviewFileResource(Resource):
556
604
  """
557
605
  Base class to download a preview file.
@@ -564,9 +612,7 @@ class BasePreviewFileResource(Resource):
564
612
 
565
613
  def is_allowed(self, preview_file_id):
566
614
  self.preview_file = files_service.get_preview_file(preview_file_id)
567
- task = tasks_service.get_task(self.preview_file["task_id"])
568
- user_service.check_project_access(task["project_id"])
569
- user_service.check_entity_access(task["entity_id"])
615
+ user_service.check_task_access(self.preview_file["task_id"])
570
616
  self.last_modified = date_helpers.get_datetime_from_string(
571
617
  self.preview_file["updated_at"]
572
618
  )
@@ -843,9 +889,7 @@ class AttachmentThumbnailResource(Resource):
843
889
  comment = tasks_service.get_comment(
844
890
  self.attachment_file["comment_id"]
845
891
  )
846
- task = tasks_service.get_task(comment["object_id"])
847
- user_service.check_project_access(task["project_id"])
848
- user_service.check_entity_access(task["entity_id"])
892
+ user_service.check_task_access(comment["object_id"])
849
893
  elif self.attachment_file["chat_message_id"] is not None:
850
894
  message = chats_service.get_chat_message(
851
895
  self.attachment_file["chat_message_id"]
@@ -1272,9 +1316,7 @@ class UpdatePreviewPositionResource(Resource, ArgsMixin):
1272
1316
  """
1273
1317
  args = self.get_args([{"name": "position", "default": 0, "type": int}])
1274
1318
  preview_file = files_service.get_preview_file(preview_file_id)
1275
- task = tasks_service.get_task(preview_file["task_id"])
1276
- user_service.check_manager_project_access(task["project_id"])
1277
- user_service.check_entity_access(task["entity_id"])
1319
+ user_service.check_task_access(preview_file["task_id"])
1278
1320
  return preview_files_service.update_preview_file_position(
1279
1321
  preview_file_id, args["position"]
1280
1322
  )
@@ -1424,9 +1466,7 @@ class ExtractTileFromPreview(Resource):
1424
1466
  @jwt_required()
1425
1467
  def get(self, preview_file_id):
1426
1468
  preview_file = files_service.get_preview_file(preview_file_id)
1427
- task = tasks_service.get_task(preview_file["task_id"])
1428
- user_service.check_manager_project_access(task["project_id"])
1429
- user_service.check_entity_access(task["entity_id"])
1469
+ user_service.check_task_access(preview_file["task_id"])
1430
1470
  extracted_tile_path = (
1431
1471
  preview_files_service.extract_tile_from_preview_file(preview_file)
1432
1472
  )
@@ -11,6 +11,7 @@ from zou.app.services import (
11
11
  )
12
12
  from zou.app.utils import permissions
13
13
  from zou.app.services.exception import WrongParameterException
14
+ from zou.app.models.metadata_descriptor import METADATA_DESCRIPTOR_TYPES
14
15
 
15
16
 
16
17
  class OpenProjectsResource(Resource, ArgsMixin):
@@ -715,6 +716,10 @@ class ProductionMetadataDescriptorsResource(Resource, ArgsMixin):
715
716
  if len(args["name"]) == 0:
716
717
  raise WrongParameterException("Name cannot be empty.")
717
718
 
719
+ types = [type_name for type_name, _ in METADATA_DESCRIPTOR_TYPES]
720
+ if args["data_type"] not in types:
721
+ raise WrongParameterException("Invalid data_type")
722
+
718
723
  return (
719
724
  projects_service.add_metadata_descriptor(
720
725
  project_id,
@@ -828,6 +833,10 @@ class ProductionMetadataDescriptorResource(Resource, ArgsMixin):
828
833
  if len(args["name"]) == 0:
829
834
  raise WrongParameterException("Name cannot be empty.")
830
835
 
836
+ types = [type_name for type_name, _ in METADATA_DESCRIPTOR_TYPES]
837
+ if args["data_type"] not in types:
838
+ raise WrongParameterException("Invalid data_type")
839
+
831
840
  args["for_client"] = args["for_client"] == "True"
832
841
 
833
842
  return projects_service.update_metadata_descriptor(descriptor_id, args)
@@ -69,9 +69,7 @@ class AddPreviewResource(Resource, ArgsMixin):
69
69
  """
70
70
  args = self.get_args([("revision", 0, False, int)])
71
71
 
72
- task = tasks_service.get_task(task_id)
73
- user_service.check_project_access(task["project_id"])
74
- user_service.check_entity_access(task["entity_id"])
72
+ user_service.check_task_access(task_id)
75
73
 
76
74
  person = persons_service.get_current_user()
77
75
  preview_file = tasks_service.add_preview_file_to_comment(
@@ -119,9 +117,7 @@ class AddExtraPreviewResource(Resource):
119
117
  201:
120
118
  description: Preview added to given comment
121
119
  """
122
- task = tasks_service.get_task(task_id)
123
- user_service.check_project_access(task["project_id"])
124
- user_service.check_entity_access(task["entity_id"])
120
+ user_service.check_task_access(task_id)
125
121
  tasks_service.get_comment(comment_id)
126
122
 
127
123
  person = persons_service.get_current_user()
@@ -191,9 +187,7 @@ class TaskPreviewsResource(Resource):
191
187
  200:
192
188
  description: Previews linked to given task
193
189
  """
194
- task = tasks_service.get_task(task_id)
195
- user_service.check_project_access(task["project_id"])
196
- user_service.check_entity_access(task["entity_id"])
190
+ user_service.check_task_access(task_id)
197
191
  return files_service.get_preview_files_for_task(task_id)
198
192
 
199
193
 
@@ -220,9 +214,7 @@ class TaskCommentsResource(Resource):
220
214
  200:
221
215
  description: Comments linked to given task
222
216
  """
223
- task = tasks_service.get_task(task_id)
224
- user_service.check_project_access(task["project_id"])
225
- user_service.check_entity_access(task["entity_id"])
217
+ user_service.check_task_access(task_id)
226
218
  is_client = permissions.has_client_permissions()
227
219
  is_manager = permissions.has_manager_permissions()
228
220
  is_supervisor = permissions.has_supervisor_permissions()
@@ -753,7 +745,7 @@ class ToReviewResource(Resource, ArgsMixin):
753
745
  try:
754
746
  task = tasks_service.get_task(task_id)
755
747
  user_service.check_project_access(task["project_id"])
756
- user_service.check_entity_access(task["project_id"])
748
+ user_service.check_entity_access(task["entity_id"])
757
749
 
758
750
  if person_id is not None:
759
751
  person = persons_service.get_person(person_id)
@@ -763,7 +755,7 @@ class ToReviewResource(Resource, ArgsMixin):
763
755
  preview_path = self.get_preview_path(task, name, revision)
764
756
 
765
757
  task = tasks_service.task_to_review(
766
- task["id"], person, comment, preview_path, change_status
758
+ task_id, person, comment, preview_path, change_status
767
759
  )
768
760
  except PersonNotFoundException:
769
761
  return {"error": True, "message": "Cannot find given person."}, 400
@@ -1274,9 +1266,7 @@ class GetTimeSpentResource(Resource):
1274
1266
  404:
1275
1267
  description: Wrong date format
1276
1268
  """
1277
- task = tasks_service.get_task(task_id)
1278
- user_service.check_project_access(task["project_id"])
1279
- user_service.check_entity_access(task["entity_id"])
1269
+ user_service.check_task_access(task_id)
1280
1270
  return tasks_service.get_time_spents(task_id)
1281
1271
 
1282
1272
 
@@ -1312,9 +1302,7 @@ class GetTimeSpentDateResource(Resource):
1312
1302
  description: Wrong date format
1313
1303
  """
1314
1304
  try:
1315
- task = tasks_service.get_task(task_id)
1316
- user_service.check_project_access(task["project_id"])
1317
- user_service.check_entity_access(task["entity_id"])
1305
+ user_service.check_task_access(task_id)
1318
1306
  return tasks_service.get_time_spents(task_id, date)
1319
1307
  except WrongDateFormatException:
1320
1308
  abort(404)
zou/app/models/person.py CHANGED
@@ -49,11 +49,21 @@ class DepartmentLink(db.Model):
49
49
  UUIDType(binary=False),
50
50
  db.ForeignKey("person.id"),
51
51
  primary_key=True,
52
+ index=True,
52
53
  )
53
54
  department_id = db.Column(
54
55
  UUIDType(binary=False),
55
56
  db.ForeignKey("department.id"),
56
57
  primary_key=True,
58
+ index=True,
59
+ )
60
+
61
+ __table_args__ = (
62
+ db.UniqueConstraint(
63
+ "person_id",
64
+ "department_id",
65
+ name="department_link_uc",
66
+ ),
57
67
  )
58
68
 
59
69
 
@@ -248,6 +248,18 @@ def get_assets_and_tasks(criterions={}, page=1, with_episode_ids=False):
248
248
  episode_links_query = episode_links_query.filter(
249
249
  Episode.project_id == criterions["project_id"]
250
250
  )
251
+ if "episode_id" in criterions and criterions["episode_id"] not in [
252
+ "main",
253
+ "all",
254
+ ]:
255
+ episode_links_query = episode_links_query.filter(
256
+ EntityLink.entity_in_id == criterions["episode_id"]
257
+ )
258
+ if "id" in criterions:
259
+ episode_links_query = episode_links_query.filter(
260
+ EntityLink.entity_out_id == criterions["id"]
261
+ )
262
+
251
263
  for link in episode_links_query.all():
252
264
  if str(link.entity_out_id) not in cast_in_episode_ids:
253
265
  cast_in_episode_ids[str(link.entity_out_id)] = []
@@ -100,7 +100,7 @@ def get_preview_file_fps(project, entity=None):
100
100
 
101
101
  if entity is not None:
102
102
  entity_data = entity.get("data", {}) or {}
103
- if entity_data.get("fps", None) is not None:
103
+ if entity_data.get("fps", None):
104
104
  fps = str(entity_data["fps"]).replace(",", ".")
105
105
 
106
106
  return "%.3f" % float(fps)
@@ -181,6 +181,10 @@ def prepare_and_store_movie(
181
181
  from zou.app import app as current_app
182
182
 
183
183
  with current_app.app_context():
184
+ if add_source_to_file_store:
185
+ file_store.add_movie(
186
+ "source", preview_file_id, uploaded_movie_path
187
+ )
184
188
  preview_file_raw = files_service.get_preview_file_raw(preview_file_id)
185
189
  normalized_movie_low_path = None
186
190
  try:
@@ -205,10 +209,6 @@ def prepare_and_store_movie(
205
209
  if normalize:
206
210
  current_app.logger.info("start normalization")
207
211
  try:
208
- if add_source_to_file_store:
209
- file_store.add_movie(
210
- "source", preview_file_id, uploaded_movie_path
211
- )
212
212
  if (
213
213
  config.ENABLE_JOB_QUEUE_REMOTE
214
214
  and len(config.JOB_QUEUE_NOMAD_NORMALIZE_JOB) > 0
@@ -478,6 +478,16 @@ def check_task_status_access(task_status_id):
478
478
  return True
479
479
 
480
480
 
481
+ def check_task_access(task_id):
482
+ """
483
+ Return true if current user can have access to a task.
484
+ """
485
+ task = tasks_service.get_task(task_id)
486
+ check_project_access(task["project_id"])
487
+ check_entity_access(task["entity_id"])
488
+ return True
489
+
490
+
481
491
  def check_comment_access(comment_id):
482
492
  """
483
493
  Return true if current user can have access to a comment.
@@ -1414,7 +1424,7 @@ def get_last_notifications(
1414
1424
  )
1415
1425
  reply_mentions = reply.get("mentions", []) or []
1416
1426
  reply_department_mentions = (
1417
- reply.get("departement_mentions", []) or []
1427
+ reply.get("department_mentions", []) or []
1418
1428
  )
1419
1429
  if reply is not None:
1420
1430
  reply_text = reply["text"]
zou/app/utils/commands.py CHANGED
@@ -774,20 +774,23 @@ def renormalize_movie_preview_files(
774
774
  config.TMP_DIR,
775
775
  f"{preview_file_id}.{extension}.tmp",
776
776
  )
777
- if config.FS_BACKEND == "local":
778
- shutil.copyfile(
779
- file_store.get_local_movie_path(
780
- "source", preview_file_id
781
- ),
782
- uploaded_movie_path,
783
- )
784
- else:
785
- sync_service.download_file(
786
- uploaded_movie_path,
787
- "source",
788
- file_store.open_movie,
789
- str(preview_file_id),
790
- )
777
+ try:
778
+ if config.FS_BACKEND == "local":
779
+ shutil.copyfile(
780
+ file_store.get_local_movie_path(
781
+ "source", preview_file_id
782
+ ),
783
+ uploaded_movie_path,
784
+ )
785
+ else:
786
+ sync_service.download_file(
787
+ uploaded_movie_path,
788
+ "source",
789
+ file_store.open_movie,
790
+ str(preview_file_id),
791
+ )
792
+ except:
793
+ pass
791
794
  if config.ENABLE_JOB_QUEUE:
792
795
  queue_store.job_queue.enqueue(
793
796
  preview_files_service.prepare_and_store_movie,
zou/event_stream.py CHANGED
@@ -7,18 +7,21 @@ from flask_jwt_extended import (
7
7
  get_jwt_identity,
8
8
  jwt_required,
9
9
  verify_jwt_in_request,
10
- JWTManager,
11
10
  )
12
11
  from flask_socketio import SocketIO, disconnect, join_room, emit
13
12
 
14
13
  from zou.app import config, app
15
- from zou.app.stores import auth_tokens_store
16
- from zou.app.services import persons_service
17
14
  from zou.app.utils.redis import get_redis_url
18
15
 
19
16
  server_stats = {"nb_connections": 0}
20
17
  rooms_data = {}
21
18
 
19
+ redis_url = get_redis_url(config.KV_EVENTS_DB_INDEX)
20
+ socketio = SocketIO(
21
+ logger=True, cors_allowed_origins=[], cors_credentials=False
22
+ )
23
+ socketio.init_app(app, message_queue=redis_url, async_mode="gevent")
24
+
22
25
 
23
26
  def _get_empty_room(current_frame=0):
24
27
  return {
@@ -75,155 +78,131 @@ def _update_room_playing_status(data, room):
75
78
  return room
76
79
 
77
80
 
78
- def set_info_routes(socketio, app):
79
- @app.route("/", methods=["GET"])
80
- def index():
81
- return jsonify({"name": "%s Event stream" % config.APP_NAME})
82
-
83
- @app.route("/stats", methods=["GET"])
84
- def stats():
85
- return jsonify(server_stats)
86
-
87
-
88
- def set_application_routes(socketio, app):
89
- @socketio.on("connect", namespace="/events")
90
- def connected():
91
- try:
92
- verify_jwt_in_request()
93
- server_stats["nb_connections"] += 1
94
- app.logger.info("New websocket client connected")
95
- except Exception:
96
- app.logger.info("New websocket client failed to connect")
97
- disconnect()
98
- return False
99
-
100
- @socketio.on("disconnect", namespace="/events")
101
- def disconnected():
102
- try:
103
- verify_jwt_in_request()
104
- user_id = get_jwt_identity()
105
- # needed to be able to clear empty rooms
106
- tmp_rooms_data = dict(rooms_data)
107
- for room_id in tmp_rooms_data:
108
- _leave_room(room_id, user_id)
109
- server_stats["nb_connections"] -= 1
110
- app.logger.info("Websocket client disconnected")
111
- except Exception:
112
- pass
113
-
114
- @socketio.on_error("/events")
115
- def on_error(error):
116
- server_stats["nb_connections"] -= 1
117
- if server_stats["nb_connections"] < 0:
118
- server_stats["nb_connections"] = 0
119
- app.logger.error(error)
120
-
121
-
122
- def set_playlist_room_routes(socketio, app):
123
- @app.route("/rooms", methods=["GET", "POST"])
124
- @jwt_required()
125
- def rooms():
126
- return jsonify({"name": "%s Review rooms" % config.APP_NAME})
127
-
128
- @socketio.on("preview-room:open-playlist", namespace="/events")
129
- @jwt_required()
130
- def on_open_playlist(data):
131
- """
132
- when a person opens the playlist page he immediately enters the
133
- websocket room. This way he can see in live which people are in the
134
- review room. The user still has to explicitly enter the review room
135
- to actually be in sync with the other users
136
- """
137
- room, room_id = _get_room_from_data(data)
138
- rooms_data[room_id] = room
139
- join_room(room_id)
140
- emit("preview-room:room-people-updated", room, room=room_id)
141
-
142
- @socketio.on("preview-room:join", namespace="/events")
143
- @jwt_required()
144
- def on_join(data):
145
- """
146
- When a person joins the review room, we notify all its members that a
147
- new person is added to the room.
148
- """
149
- user_id = get_jwt_identity()
150
- room, room_id = _get_room_from_data(data)
151
- if len(room["people"]) == 0:
152
- _update_room_playing_status(data, room)
153
- room["people"] = list(set(room["people"] + [user_id]))
154
- rooms_data[room_id] = room
155
- emit("preview-room:room-people-updated", room, room=room_id)
81
+ @app.route("/", methods=["GET"])
82
+ def index():
83
+ return jsonify({"name": "%s Event stream" % config.APP_NAME})
84
+
85
+
86
+ @app.route("/stats", methods=["GET"])
87
+ def stats():
88
+ return jsonify(server_stats)
156
89
 
157
- @socketio.on("preview-room:leave", namespace="/events")
158
- @jwt_required()
159
- def on_leave(data):
90
+
91
+ @socketio.on("connect", namespace="/events")
92
+ def connected(_):
93
+ try:
94
+ verify_jwt_in_request()
95
+ server_stats["nb_connections"] += 1
96
+ app.logger.info("New websocket client connected")
97
+ except Exception:
98
+ app.logger.info("New websocket client failed to connect")
99
+ disconnect()
100
+ return False
101
+
102
+
103
+ @socketio.on("disconnect", namespace="/events")
104
+ def disconnected(_):
105
+ try:
106
+ verify_jwt_in_request()
160
107
  user_id = get_jwt_identity()
161
- room_id = data["playlist_id"]
162
- _leave_room(room_id, user_id)
163
-
164
- @socketio.on("preview-room:update-playing-status", namespace="/events")
165
- @jwt_required()
166
- def on_playing_status_updated(data, only_newcomer=False):
167
- room, room_id = _get_room_from_data(data)
168
- rooms_data[room_id] = _update_room_playing_status(data, room)
169
- event_data = {"only_newcomer": only_newcomer, **rooms_data[room_id]}
170
- emit("preview-room:room-updated", event_data, room=room_id)
171
-
172
- @socketio.on("preview-room:add-annotation", namespace="/events")
173
- @jwt_required()
174
- def on_add_annotation(data):
175
- room_id = data["playlist_id"]
176
- emit("preview-room:add-annotation", data, room=room_id)
177
-
178
- @socketio.on("preview-room:remove-annotation", namespace="/events")
179
- @jwt_required()
180
- def on_remove_annotation(data):
181
- room_id = data["playlist_id"]
182
- emit("preview-room:remove-annotation", data, room=room_id)
183
-
184
- @socketio.on("preview-room:update-annotation", namespace="/events")
185
- @jwt_required()
186
- def on_update_annotation(data):
187
- room_id = data["playlist_id"]
188
- emit("preview-room:update-annotation", data, room=room_id)
189
-
190
- @socketio.on("preview-room:change-version", namespace="/events")
191
- @jwt_required()
192
- def on_change_version(data):
193
- room_id = data["playlist_id"]
194
- emit("preview-room:change-version", data, room=room_id)
195
-
196
- return app
197
-
198
-
199
- def create_app():
200
- redis_url = get_redis_url(config.KV_EVENTS_DB_INDEX)
201
- socketio = SocketIO(
202
- logger=True, cors_allowed_origins=[], cors_credentials=False
203
- )
204
- set_info_routes(socketio, app)
205
- set_application_routes(socketio, app)
206
- set_playlist_room_routes(socketio, app)
207
- socketio.init_app(app, message_queue=redis_url, async_mode="gevent")
208
- return (app, socketio)
108
+ # needed to be able to clear empty rooms
109
+ tmp_rooms_data = dict(rooms_data)
110
+ for room_id in tmp_rooms_data:
111
+ _leave_room(room_id, user_id)
112
+ server_stats["nb_connections"] -= 1
113
+ app.logger.info("Websocket client disconnected")
114
+ except Exception:
115
+ pass
116
+
117
+
118
+ @socketio.on_error("/events")
119
+ def on_error(error):
120
+ server_stats["nb_connections"] -= 1
121
+ if server_stats["nb_connections"] < 0:
122
+ server_stats["nb_connections"] = 0
123
+ app.logger.error(error)
124
+
125
+
126
+ @app.route("/rooms", methods=["GET", "POST"])
127
+ @jwt_required()
128
+ def rooms():
129
+ return jsonify({"name": "%s Review rooms" % config.APP_NAME})
130
+
131
+
132
+ @socketio.on("preview-room:open-playlist", namespace="/events")
133
+ @jwt_required()
134
+ def on_open_playlist(data):
135
+ """
136
+ when a person opens the playlist page he immediately enters the
137
+ websocket room. This way he can see in live which people are in the
138
+ review room. The user still has to explicitly enter the review room
139
+ to actually be in sync with the other users
140
+ """
141
+ room, room_id = _get_room_from_data(data)
142
+ rooms_data[room_id] = room
143
+ join_room(room_id)
144
+ emit("preview-room:room-people-updated", room, room=room_id)
209
145
 
210
146
 
211
- def set_auth(app):
212
- jwt = JWTManager(app) # JWT auth tokens
147
+ @socketio.on("preview-room:join", namespace="/events")
148
+ @jwt_required()
149
+ def on_join(data):
150
+ """
151
+ When a person joins the review room, we notify all its members that a
152
+ new person is added to the room.
153
+ """
154
+ user_id = get_jwt_identity()
155
+ room, room_id = _get_room_from_data(data)
156
+ if len(room["people"]) == 0:
157
+ _update_room_playing_status(data, room)
158
+ room["people"] = list(set(room["people"] + [user_id]))
159
+ rooms_data[room_id] = room
160
+ emit("preview-room:room-people-updated", room, room=room_id)
213
161
 
214
- @jwt.token_in_blocklist_loader
215
- def check_if_token_is_revoked(_, payload):
216
- identity_type = payload.get("identity_type")
217
- if identity_type == "person":
218
- return auth_tokens_store.is_revoked(payload["jti"])
219
- elif identity_type in ["bot", "person_api"]:
220
- return persons_service.is_jti_revoked(payload["jti"])
221
- else:
222
- return True
162
+
163
+ @socketio.on("preview-room:leave", namespace="/events")
164
+ @jwt_required()
165
+ def on_leave(data):
166
+ user_id = get_jwt_identity()
167
+ room_id = data["playlist_id"]
168
+ _leave_room(room_id, user_id)
169
+
170
+
171
+ @socketio.on("preview-room:update-playing-status", namespace="/events")
172
+ @jwt_required()
173
+ def on_playing_status_updated(data, only_newcomer=False):
174
+ room, room_id = _get_room_from_data(data)
175
+ rooms_data[room_id] = _update_room_playing_status(data, room)
176
+ event_data = {"only_newcomer": only_newcomer, **rooms_data[room_id]}
177
+ emit("preview-room:room-updated", event_data, room=room_id)
178
+
179
+
180
+ @socketio.on("preview-room:add-annotation", namespace="/events")
181
+ @jwt_required()
182
+ def on_add_annotation(data):
183
+ room_id = data["playlist_id"]
184
+ emit("preview-room:add-annotation", data, room=room_id)
223
185
 
224
186
 
225
- (app, socketio) = create_app()
226
- set_auth(app)
187
+ @socketio.on("preview-room:remove-annotation", namespace="/events")
188
+ @jwt_required()
189
+ def on_remove_annotation(data):
190
+ room_id = data["playlist_id"]
191
+ emit("preview-room:remove-annotation", data, room=room_id)
192
+
193
+
194
+ @socketio.on("preview-room:update-annotation", namespace="/events")
195
+ @jwt_required()
196
+ def on_update_annotation(data):
197
+ room_id = data["playlist_id"]
198
+ emit("preview-room:update-annotation", data, room=room_id)
199
+
200
+
201
+ @socketio.on("preview-room:change-version", namespace="/events")
202
+ @jwt_required()
203
+ def on_change_version(data):
204
+ room_id = data["playlist_id"]
205
+ emit("preview-room:change-version", data, room=room_id)
227
206
 
228
207
 
229
208
  if __name__ == "__main__":
@@ -0,0 +1,50 @@
1
+ """For DepartmentLink index person_id / department_id
2
+
3
+ Revision ID: 539a3a00c417
4
+ Revises: 9d3bb33c6fc6
5
+ Create Date: 2025-01-14 12:19:52.699322
6
+
7
+ """
8
+
9
+ from alembic import op
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = "539a3a00c417"
14
+ down_revision = "9d3bb33c6fc6"
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ # ### commands auto generated by Alembic - please adjust! ###
21
+ with op.batch_alter_table("department_link", schema=None) as batch_op:
22
+ batch_op.create_unique_constraint(
23
+ "department_link_uc", ["person_id", "department_id"]
24
+ )
25
+ batch_op.create_index(
26
+ batch_op.f("ix_department_link_department_id"),
27
+ ["department_id"],
28
+ unique=False,
29
+ )
30
+ batch_op.create_index(
31
+ batch_op.f("ix_department_link_person_id"),
32
+ ["person_id"],
33
+ unique=False,
34
+ )
35
+ batch_op.create_primary_key(
36
+ "department_link_pkey", ["person_id", "department_id"]
37
+ )
38
+
39
+ # ### end Alembic commands ###
40
+
41
+
42
+ def downgrade():
43
+ # ### commands auto generated by Alembic - please adjust! ###
44
+ with op.batch_alter_table("department_link", schema=None) as batch_op:
45
+ batch_op.drop_index(batch_op.f("ix_department_link_person_id"))
46
+ batch_op.drop_index(batch_op.f("ix_department_link_department_id"))
47
+ batch_op.drop_constraint("department_link_uc", type_="unique")
48
+ batch_op.drop_constraint("department_link_pkey", type_="primary")
49
+
50
+ # ### end Alembic commands ###
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: zou
3
- Version: 0.20.8
3
+ Version: 0.20.12
4
4
  Summary: API to store and manage the data of your animation production
5
5
  Home-page: https://zou.cg-wire.com
6
6
  Author: CG Wire
@@ -36,12 +36,12 @@ Requires-Dist: flask_mail==0.10.0
36
36
  Requires-Dist: flask_principal==0.4.0
37
37
  Requires-Dist: flask_restful==0.3.10
38
38
  Requires-Dist: flask_sqlalchemy==3.1.1
39
- Requires-Dist: flask-fs2[s3,swift]==0.7.27
39
+ Requires-Dist: flask-fs2[s3,swift]==0.7.28
40
40
  Requires-Dist: flask-jwt-extended==4.7.1
41
- Requires-Dist: flask-migrate==4.0.7
41
+ Requires-Dist: flask-migrate==4.1.0
42
42
  Requires-Dist: flask-socketio==5.5.1
43
43
  Requires-Dist: flask==3.1.0
44
- Requires-Dist: gazu==0.10.22
44
+ Requires-Dist: gazu==0.10.23
45
45
  Requires-Dist: gevent-websocket==0.10.1
46
46
  Requires-Dist: gevent==24.11.1
47
47
  Requires-Dist: gunicorn==23.0.0
@@ -52,14 +52,14 @@ Requires-Dist: ldap3==2.9.1
52
52
  Requires-Dist: matterhook==0.2
53
53
  Requires-Dist: meilisearch==0.33.1
54
54
  Requires-Dist: numpy==2.0.1; python_version == "3.9"
55
- Requires-Dist: numpy==2.2.1; python_version >= "3.10"
56
- Requires-Dist: opencv-python==4.10.0.84
55
+ Requires-Dist: numpy==2.2.2; python_version >= "3.10"
56
+ Requires-Dist: opencv-python==4.11.0.86
57
57
  Requires-Dist: OpenTimelineIO==0.17.0
58
58
  Requires-Dist: OpenTimelineIO-Plugins==0.17.0
59
- Requires-Dist: orjson==3.10.13
59
+ Requires-Dist: orjson==3.10.15
60
60
  Requires-Dist: pillow==11.1.0
61
61
  Requires-Dist: psutil==6.1.1
62
- Requires-Dist: psycopg[binary]==3.2.3
62
+ Requires-Dist: psycopg[binary]==3.2.4
63
63
  Requires-Dist: pyotp==2.9.0
64
64
  Requires-Dist: pysaml2==7.5.0
65
65
  Requires-Dist: python-nomad==2.0.1
@@ -71,7 +71,7 @@ Requires-Dist: requests==2.32.3
71
71
  Requires-Dist: rq==2.1.0
72
72
  Requires-Dist: slackclient==2.9.4
73
73
  Requires-Dist: sqlalchemy_utils==0.41.2
74
- Requires-Dist: sqlalchemy==2.0.36
74
+ Requires-Dist: sqlalchemy==2.0.37
75
75
  Requires-Dist: ua-parser==1.0.0
76
76
  Requires-Dist: werkzeug==3.1.3
77
77
  Provides-Extra: prod
@@ -87,11 +87,12 @@ Requires-Dist: pytest==8.3.4; extra == "test"
87
87
  Provides-Extra: monitoring
88
88
  Requires-Dist: prometheus-flask-exporter==0.23.1; extra == "monitoring"
89
89
  Requires-Dist: pygelf==0.4.2; extra == "monitoring"
90
- Requires-Dist: sentry-sdk==2.19.2; extra == "monitoring"
90
+ Requires-Dist: sentry-sdk==2.20.0; extra == "monitoring"
91
91
  Provides-Extra: lint
92
92
  Requires-Dist: autoflake==2.3.1; extra == "lint"
93
93
  Requires-Dist: black==24.10.0; extra == "lint"
94
94
  Requires-Dist: pre-commit==4.0.1; extra == "lint"
95
+ Dynamic: requires-python
95
96
 
96
97
  .. figure:: https://zou.cg-wire.com/kitsu.png
97
98
  :alt: Kitsu Logo
@@ -1,7 +1,7 @@
1
- zou/__init__.py,sha256=HWLVisnJfqhQIh-8EgcnY9aNGjARs6RPFHGRRgh8Gc4,23
1
+ zou/__init__.py,sha256=8CzByu23_8LJYiPVV52x1uqeLZvtCuW9qB1Gqau4d7I,24
2
2
  zou/cli.py,sha256=H18Wg-wqQOsv4F5_bZRDlxskjO-TRwaV1NmQMTH9mdg,18869
3
3
  zou/debug.py,sha256=1fawPbkD4wn0Y9Gk0BiBFSa-CQe5agFi8R9uJYl2Uyk,520
4
- zou/event_stream.py,sha256=UdlsiWfndwEy0cH8j8JaeN6sKKo6vmDweoFkIEDbi7U,7706
4
+ zou/event_stream.py,sha256=EpohqFJwWL0zs-Ic_W5dX5_XSDeCrqHQPL5Re39OnQ0,6382
5
5
  zou/job_settings.py,sha256=_aqBhujt2Q8sXRWIbgbDf-LUdXRdBimdtTc-fZbiXoY,202
6
6
  zou/app/__init__.py,sha256=7pRqdA79X-UI_kYt8Sz6lIdleNStBxHnXISr-tePVe8,6962
7
7
  zou/app/api.py,sha256=JTB_IMVO8EOoyqx9KdRkiIix0chOLi0yGDY-verUJXA,5127
@@ -18,12 +18,12 @@ zou/app/blueprints/breakdown/resources.py,sha256=pmGlHLiXFsPRbxf403SiVgGiaBbtK8G
18
18
  zou/app/blueprints/chats/__init__.py,sha256=YGmwGvddg3MgSYVIh-hmkX8t2em9_LblxBeJzFqFJD4,558
19
19
  zou/app/blueprints/chats/resources.py,sha256=4yLFermdwOsnBLs9nx8yxuHWLar24uQWQy0XgsUNDD0,5950
20
20
  zou/app/blueprints/comments/__init__.py,sha256=WqpJ7-_dK1cInGTFJAxQ7syZtPCotwq2oO20UEnk1h4,1532
21
- zou/app/blueprints/comments/resources.py,sha256=_8OoEl51UWSW5nciqiy3u9ZHztkGg_iuUJ29RhpASkM,19423
21
+ zou/app/blueprints/comments/resources.py,sha256=X1I0lu6iv1xS-5Tci9P9qnJzCy3jOpE9tDfcti3XwUA,18810
22
22
  zou/app/blueprints/concepts/__init__.py,sha256=sP_P4mfYvfMcgeE6MHZYP3eD0Lz0Lwit5-CFuVnA-Jg,894
23
23
  zou/app/blueprints/concepts/resources.py,sha256=maJNrBAWX0bKbDKtOZc3YFp4nTVtIdkkAA4H9WA9n1Y,10140
24
24
  zou/app/blueprints/crud/__init__.py,sha256=qn7xkEh2EG0mPS_RBmm0GgYr0O1jnmI8ymXZnFWZCz8,8361
25
25
  zou/app/blueprints/crud/asset_instance.py,sha256=va3mw79aPKry2m9PYAmjVePTScigewDjwD1c672f0y0,1335
26
- zou/app/blueprints/crud/attachment_file.py,sha256=FJA5nKli-9l4p-Mka-tNRNRFil6dNC7QMN3N2MBBN_w,1346
26
+ zou/app/blueprints/crud/attachment_file.py,sha256=-yur0V16BOTvpdqtNymDTHEugwRPgGtWccdXotpvYZ4,1193
27
27
  zou/app/blueprints/crud/base.py,sha256=HJcZKeNe3RVe_qEC9bSlpz4FRKhqavzrsfFLSZ8OmoY,15907
28
28
  zou/app/blueprints/crud/chat.py,sha256=Sq1r0y9ANjS113PUpwgAhnjYsxxLKMCM-a7DJ_icF00,344
29
29
  zou/app/blueprints/crud/chat_message.py,sha256=bEEUoV0sNzu5sntNS6fpLh5NC6wWiycWCXtTE-t4yG4,387
@@ -47,7 +47,7 @@ zou/app/blueprints/crud/person.py,sha256=k1EcYxD5C5p7tquAvJUpD2873-ITeF2ogMNIYRx
47
47
  zou/app/blueprints/crud/playlist.py,sha256=he8iXoWnjBVXzkB_y8aGnZ6vQ_7hGSf-ALofLFoqx1U,1890
48
48
  zou/app/blueprints/crud/preview_background_file.py,sha256=TRJlVQ3nGOYVkA6kxIlNrip9bjkUaknVNCIUYw8uJYk,2451
49
49
  zou/app/blueprints/crud/preview_file.py,sha256=j5tw7fW4m6QUMTg99cwQlq3yZ5WKHfRuUwRlssBkMDU,3845
50
- zou/app/blueprints/crud/project.py,sha256=nYCnD_0QHaUZsSNAeXmpsJAkNB_G1IWJzK1etfKWEis,8286
50
+ zou/app/blueprints/crud/project.py,sha256=g8kWGzgWyVk7s6Y-41DRAGjIegxS36W4SbP1FMJTQDM,8428
51
51
  zou/app/blueprints/crud/project_status.py,sha256=XIiIsAfaD5sLZA6wT3UL4FZQpnoOcqPzYfFIWrp9Svs,540
52
52
  zou/app/blueprints/crud/schedule_item.py,sha256=1dbNKbftNG3if38Hzh1uslCAu3BE4jOqWhLV0590A90,1400
53
53
  zou/app/blueprints/crud/search_filter.py,sha256=qR0crZFfaOd2YxFcPMKhAj1SXCc-YW9Wk4UA07dDgMQ,393
@@ -60,7 +60,7 @@ zou/app/blueprints/crud/task.py,sha256=7ZMetDs4Z-ZxO0IjyM4y1kmzQUGyLvf0ga6pxZOrN
60
60
  zou/app/blueprints/crud/task_status.py,sha256=roRbBJtyoaNrXv5nkVIPLH6K5vdJjGQtiWFvqlfbo9s,1482
61
61
  zou/app/blueprints/crud/task_type.py,sha256=9DUtBaNZqC0yHmunKX_CaUyo-bHjNTX3B-g4zyU5uKc,1815
62
62
  zou/app/blueprints/crud/time_spent.py,sha256=9tfoKJ77OEDTnpKHshO3UMDz_KCoyLq1CWquInm5DY0,3057
63
- zou/app/blueprints/crud/working_file.py,sha256=z-6x9Ef8RetQeivQ9zaXClge2UjaInZH99cYf15V3ac,3453
63
+ zou/app/blueprints/crud/working_file.py,sha256=5g7Ljsv_8cFYyhrtxqlAhL5lM_Li9CwrGF78LDuFiqs,3196
64
64
  zou/app/blueprints/edits/__init__.py,sha256=jR6dURRPHZJcU4DVFsNghLW1iGm3CwEzs1LPbdof158,1152
65
65
  zou/app/blueprints/edits/resources.py,sha256=IvgqEhIvjF6OO7VNK6i5w6NKEoFQyKfuZJ7aJxdt5rk,13249
66
66
  zou/app/blueprints/entities/__init__.py,sha256=v-qt2dl3s3tmK_ur-cpDHNPmcL0A6xCybczyuidjUCo,713
@@ -81,7 +81,7 @@ zou/app/blueprints/export/csv/task_types.py,sha256=PCEEhOQcdOJv_38i-KNxbkGeNzmQ8
81
81
  zou/app/blueprints/export/csv/tasks.py,sha256=CHFcs9S3eLIz6psE6Q6mZ-OSur_GrpBeLn98Nh9NNcA,4121
82
82
  zou/app/blueprints/export/csv/time_spents.py,sha256=yYPtilOxfQD5mBwyh9h-PbTQBpab-vMrec35tYUw4fQ,2984
83
83
  zou/app/blueprints/files/__init__.py,sha256=7Wty30JW2OXIn-tBFXOWWmPuHnsnxPpH3jNtHvvr9tY,3987
84
- zou/app/blueprints/files/resources.py,sha256=8SIV8kaqv3dxyL8nyqG3QiZmk5ZYIvUxw6k1ic-jhBs,69786
84
+ zou/app/blueprints/files/resources.py,sha256=kWqhPfi1SmVXE05G3sfR2aF4r6J8O7Tr5c0oKO9MQN0,69175
85
85
  zou/app/blueprints/index/__init__.py,sha256=Dh3oQiirpg8RCkfVOuk3irIjSvUvuRf0jPxE6oGubz0,828
86
86
  zou/app/blueprints/index/resources.py,sha256=NuqSq0rPlOpldRCwFhDqNW4UwSc8QdRRKcGn9S0FCrk,8635
87
87
  zou/app/blueprints/news/__init__.py,sha256=HxBXjC15dVbotNAZ0CLf02iwUjxJr20kgf8_kT_9nwM,505
@@ -90,10 +90,10 @@ zou/app/blueprints/persons/__init__.py,sha256=0cnHHw3K_8OEMm0qOi3wKVomSAg9IJSnVj
90
90
  zou/app/blueprints/persons/resources.py,sha256=PfK6epzRn_kbqN6g9qYiH9XWStFlccTVCYyKxs72Hu8,42764
91
91
  zou/app/blueprints/playlists/__init__.py,sha256=vuEk1F3hFHsmuKWhdepMoLyOzmNKDn1YrjjfcaIz0lQ,1596
92
92
  zou/app/blueprints/playlists/resources.py,sha256=alRlMHypUFErXLsEYxpFK84cdjFJ3YWwamZtW0KcwLY,17211
93
- zou/app/blueprints/previews/__init__.py,sha256=wz7k1H7VFhpcMhtvjFqfbtjMRZHBcP9qfIYpZPuzVyQ,4343
94
- zou/app/blueprints/previews/resources.py,sha256=vXkgQTmOaJBn8YN9aiyKsTb50eSKWpaAEvOUAWUdCA0,52312
93
+ zou/app/blueprints/previews/__init__.py,sha256=ihC6OQ9AUjnZ2JeMnjRh_tKGO0UmAjOwhZnOivc3BnQ,4460
94
+ zou/app/blueprints/previews/resources.py,sha256=22-ISgZba0SpQ2sS0l5WjVtLTaiLKoal4aLcqxLQX9s,53176
95
95
  zou/app/blueprints/projects/__init__.py,sha256=Pn3fA5bpNFEPBzxTKJ2foV6osZFflXXSM2l2uZh3ktM,3927
96
- zou/app/blueprints/projects/resources.py,sha256=E91Vj9EzId2pxiL50JRfrThiyif1PmzWS1oPeMThzQI,31544
96
+ zou/app/blueprints/projects/resources.py,sha256=1WBS2FyaY1RSA_T-BdPnc8X9myjTJ127bMDigyoAklk,31979
97
97
  zou/app/blueprints/search/__init__.py,sha256=QCjQIY_85l_orhdEiqav_GifjReuwsjZggN3V0GeUVY,356
98
98
  zou/app/blueprints/search/resources.py,sha256=_QgRlUuxCPgY-ip5r2lGFtXNcGSE579JsCSrVf8ajVU,3093
99
99
  zou/app/blueprints/shots/__init__.py,sha256=HfgLneZBYUMa2OGwIgEZTz8zrIEYFRiYmRbreBPYeYw,4076
@@ -127,7 +127,7 @@ zou/app/blueprints/source/shotgun/tasks.py,sha256=XXBRe9QhhS-kuZeV3HitOnpf7mmWVx
127
127
  zou/app/blueprints/source/shotgun/team.py,sha256=GF7y2BwDeFJCiidtG68icfCi-uV1-b96YKiH8KR54iE,1819
128
128
  zou/app/blueprints/source/shotgun/versions.py,sha256=8Mb35e5p3FLbbiu6AZb9tJErDKz2pPRBdIYu80Ayj7w,2292
129
129
  zou/app/blueprints/tasks/__init__.py,sha256=udtTZJVViawRAPu8dO_OoyVzQTheLYWTHeTnrC-2RDA,4331
130
- zou/app/blueprints/tasks/resources.py,sha256=pzLcbTk0UnM7ZJ4h4kAEElzS92fcvrT6KeFXyL6qEY4,55855
130
+ zou/app/blueprints/tasks/resources.py,sha256=eAcjUpkBvKwODOxJ04kFmp-jB6jSqqsRgGzsNm6_gVw,55117
131
131
  zou/app/blueprints/user/__init__.py,sha256=H9zCHcVobC6jq6dTToXKAjnZmDA0a9gChHiIP3BcZsc,4586
132
132
  zou/app/blueprints/user/resources.py,sha256=loCigQvPCoRw6nVu_9TIY7pjUByJgk6vutFPSo0MwzI,39891
133
133
  zou/app/file_trees/default.json,sha256=ryUrEmQYE8B_WkzCoQLgmem3N9yNwMIWx9G8p3HfG9o,2310
@@ -159,7 +159,7 @@ zou/app/models/notification.py,sha256=1ODOymGPeB4oxgX_3WhOgIL_Lsz-JR7miDkBS6W8t_
159
159
  zou/app/models/organisation.py,sha256=R69AR1JDZSs6YeXDalmz3ewmrSMDv9Mr8AZAHn09Iu0,1365
160
160
  zou/app/models/output_file.py,sha256=hyLGrpsgrk0aisDXppRQrB7ItCwyuyw-X0ZwVAHabsA,2569
161
161
  zou/app/models/output_type.py,sha256=us_lCUCEvuP4vi_XmmOcEl1J2MtZhMX5ZheBqEFCgWA,381
162
- zou/app/models/person.py,sha256=E6aTbN6HigVTh9_myMfYK6iqrdUfMRB6XBtowdA-qYQ,7511
162
+ zou/app/models/person.py,sha256=txHmSzokaS-tET_MIjGHxhNNS8CPt-GzUPIhp5baDmU,7714
163
163
  zou/app/models/playlist.py,sha256=YGgAk84u0_fdIEY02Dal4kfk8APVZvWFwWYV74qvrio,1503
164
164
  zou/app/models/preview_background_file.py,sha256=j8LgRmY7INnlB07hFwwB-8ssQrRC8vsb8VcpsTbt6tA,559
165
165
  zou/app/models/preview_file.py,sha256=Ur45Wau2X3qyKULh04EUcMbnBmaQc8y4IMs5NgELiAQ,3134
@@ -179,7 +179,7 @@ zou/app/models/task_type.py,sha256=IsixVAfz3pyMf0eQw8x-uFNM9OHNkZpsPLEz_VNQ0hA,1
179
179
  zou/app/models/time_spent.py,sha256=n7i3FO9g1eE_zATkItoCgrGVqq3iMSfdlKSveEZPloc,795
180
180
  zou/app/models/working_file.py,sha256=q0LM3s1ziw_9AmmPDCkwyf1-TJkWTBMgo2LdHyVRwxg,1509
181
181
  zou/app/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
182
- zou/app/services/assets_service.py,sha256=ACD9WW7EJ7lCZsUs2klnByuNYf0prhdKBitGKHVpIgE,23556
182
+ zou/app/services/assets_service.py,sha256=WOV0XTp5JKNclI9MnYH_4wcWk_u3UYObberaX78Q8u4,23995
183
183
  zou/app/services/auth_service.py,sha256=hQpNb21xlr5EiTrXnzpFb4W4GDtglubqA2z_-YIBfnk,22897
184
184
  zou/app/services/backup_service.py,sha256=_ZtZp6wkcVYnHxBosziwLGdrTvsUttXGphiydq53iy8,4840
185
185
  zou/app/services/base_service.py,sha256=OZd0STFh-DyBBdwsmA7DMMnrwv4C8wJUbShvZ1isndU,1383
@@ -202,7 +202,7 @@ zou/app/services/news_service.py,sha256=eOXkvLhOcgncI2NrgiJEccV28oxZX5CsZVqaE-l4
202
202
  zou/app/services/notifications_service.py,sha256=7GDRio_mGaRYV5BHOAdpxBZjA_LLYUfVpbwZqy1n9pI,15685
203
203
  zou/app/services/persons_service.py,sha256=ccPP1_anu2J4zH54LKeP7dt1VyVpiYvzfgOS1xXhLrQ,16564
204
204
  zou/app/services/playlists_service.py,sha256=pAlPHET4jNdST5jsmJrFUkf1SVhfSoML9zdNpZ_88l4,32439
205
- zou/app/services/preview_files_service.py,sha256=TKuUQ15kmf28ZcT2Ra08upGli-5flfLlvh-VTPV_rC0,35886
205
+ zou/app/services/preview_files_service.py,sha256=Yk-vwzHuKTzNkEZfl9DhQRdDuRU006uwZxJ-RKajEkI,35842
206
206
  zou/app/services/projects_service.py,sha256=aIbYaFomy7OX2Pxvkf9w5qauDvkjuc9ummSGNYIpQMY,21249
207
207
  zou/app/services/scenes_service.py,sha256=iXN19HU4njPF5VtZXuUrVJ-W23ZQuQNPC3ADXltbWtU,992
208
208
  zou/app/services/schedule_service.py,sha256=E99HKYsXgnK2sw58fw-NNHXWBgVJiA60upztjkNSCaM,6989
@@ -213,7 +213,7 @@ zou/app/services/sync_service.py,sha256=kJ1LGMNfPh9_BDwGTfoYWHrLZ8OlT0hWk-R8wNt0
213
213
  zou/app/services/tasks_service.py,sha256=Cv1iEdXwZommRkKh8wv7_hf6yaEDIL9SYWRNuoAmdhg,68683
214
214
  zou/app/services/telemetry_services.py,sha256=xQm1h1t_JxSFW59zQGf4NuNdUi1UfMa_6pQ-ytRbmGA,1029
215
215
  zou/app/services/time_spents_service.py,sha256=H9X-60s6oqtY9rtU-K2jKwUSljfkdGlf_9wMr3iVfIA,15158
216
- zou/app/services/user_service.py,sha256=csEwaX2VW-CTY4mexD0yTaNb2i0cN2LECeBnzjSyDUs,50036
216
+ zou/app/services/user_service.py,sha256=QlyckTfiCx-bzRewqVwHMJQXerg6BWXYWfURhvp65G4,50291
217
217
  zou/app/stores/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
218
218
  zou/app/stores/auth_tokens_store.py,sha256=-qOJPybLHvnMOq3PWk073OW9HJwOHGhFLZeOIlX1UVw,1290
219
219
  zou/app/stores/file_store.py,sha256=yLQDM6mNbj9oe0vsWdBqun7D8Dw-eSjD1yHCCftX0OI,4045
@@ -225,7 +225,7 @@ zou/app/utils/auth.py,sha256=DZfZSr1Ulge0UK3hfvOWsMo3_d7RVP_llV118u9BtUI,870
225
225
  zou/app/utils/cache.py,sha256=MRluTvGG67ybOkyzgD70B6PGKMdRyFdTc0AYy3dEQe8,1210
226
226
  zou/app/utils/chats.py,sha256=ORngxQ3IQQF0QcVFJLxJ-RaU4ksQ9-0M8cmPa0pc0Ho,4302
227
227
  zou/app/utils/colors.py,sha256=LaGV17NL_8xY0XSp8snGWz5UMwGnm0KPWXyE5BTMG6w,200
228
- zou/app/utils/commands.py,sha256=_ev6tS1bQVDkjIOWGsCTh0zUcJCCoBBcUQKuzOeSJFE,27456
228
+ zou/app/utils/commands.py,sha256=pEVJ0HEapuaJ_P8mRQRCATshN4y6ZUHtMYWh7sRjRJ0,27606
229
229
  zou/app/utils/csv_utils.py,sha256=GiI8SeUqmIh9o1JwhZGkQXU_0K0EcPrRHYIZ8bMoYzk,1228
230
230
  zou/app/utils/date_helpers.py,sha256=jFxDPCbAasg0I1gsC72AKEbGcx5c4pLqXZkSfZ4wLdQ,4724
231
231
  zou/app/utils/dbhelpers.py,sha256=RSJuoxLexGJyME16GQCs-euFLBR0u-XAFdJ1KMSv5M8,1143
@@ -300,6 +300,7 @@ zou/migrations/versions/4715c2586036_add_last_preview_file_fields.py,sha256=ez0H
300
300
  zou/migrations/versions/4e3738cdc34c_.py,sha256=0THgcNbLygTZb462fHZ9SK2EBq_j6_htcPXz4CZbVkg,2636
301
301
  zou/migrations/versions/4f2398ebcd49_.py,sha256=7ZYIHv3bjppg3dqvBHe_sBatzz7etQBpuRKY-DSvR_Q,683
302
302
  zou/migrations/versions/523ee9647bee_.py,sha256=2PbD0cNflQtHd7TLxyfSQDJ8gHybUYCKcPQw_XUivfU,1262
303
+ zou/migrations/versions/539a3a00c417_for_departmentlink_index_person_id_.py,sha256=6_nJuUQsSXvUbhUrzfyr7ACDZehxkfkm0tz_cxV773Y,1534
303
304
  zou/migrations/versions/54ee0d1d60ba_add_build_job_model.py,sha256=xePUVxovJ_EOVCdNPz9qoXR_mf89YOhl2IIwYaTQhzI,1889
304
305
  zou/migrations/versions/556526e47daa_.py,sha256=XvDBc0o4l76Rf1FBXb5sopNHiGEe-R8tbZ5C5LmLAOA,2052
305
306
  zou/migrations/versions/57222395f2be_add_statusautomation_import_last_revision.py,sha256=rPRvyxfrtEtFywGuBkfB8VLEs3EUytgUyA8xB8vtH4A,1039
@@ -414,9 +415,9 @@ zou/remote/normalize_movie.py,sha256=zNfEY3N1UbAHZfddGONTg2Sff3ieLVWd4dfZa1dpnes
414
415
  zou/remote/playlist.py,sha256=AsDo0bgYhDcd6DfNRV6r6Jj3URWwavE2ZN3VkKRPbLU,3293
415
416
  zou/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
416
417
  zou/utils/movie.py,sha256=d67fIL9dVBKt-E_qCGXRbNNdbJaJR5sHvZeX3hf8ldE,16559
417
- zou-0.20.8.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
418
- zou-0.20.8.dist-info/METADATA,sha256=a_jWhFb1JHHvAzztat5oL4jSW2wuDsz7TrYnvdRKz18,6708
419
- zou-0.20.8.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
420
- zou-0.20.8.dist-info/entry_points.txt,sha256=PelQoIx3qhQ_Tmne7wrLY-1m2izuzgpwokoURwSohy4,130
421
- zou-0.20.8.dist-info/top_level.txt,sha256=4S7G_jk4MzpToeDItHGjPhHx_fRdX52zJZWTD4SL54g,4
422
- zou-0.20.8.dist-info/RECORD,,
418
+ zou-0.20.12.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
419
+ zou-0.20.12.dist-info/METADATA,sha256=vhg2GEG6aD2iaBExk_Lx0R3yWOxtLpkYF_UgKg-UmWQ,6734
420
+ zou-0.20.12.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
421
+ zou-0.20.12.dist-info/entry_points.txt,sha256=PelQoIx3qhQ_Tmne7wrLY-1m2izuzgpwokoURwSohy4,130
422
+ zou-0.20.12.dist-info/top_level.txt,sha256=4S7G_jk4MzpToeDItHGjPhHx_fRdX52zJZWTD4SL54g,4
423
+ zou-0.20.12.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.7.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
File without changes