zou 0.19.44__py3-none-any.whl → 0.19.46__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.19.44"
1
+ __version__ = "0.19.46"
@@ -217,7 +217,6 @@ class LoginResource(Resource, ArgsMixin):
217
217
  "identity_type": "person",
218
218
  },
219
219
  )
220
- auth_service.register_tokens(app, access_token, refresh_token)
221
220
  identity_changed.send(
222
221
  current_app._get_current_object(),
223
222
  identity=Identity(user["id"], "person"),
@@ -383,7 +382,6 @@ class RefreshTokenResource(Resource):
383
382
  "identity_type": "person",
384
383
  },
385
384
  )
386
- auth_service.register_tokens(app, access_token)
387
385
  if is_from_browser(request.user_agent):
388
386
  response = jsonify({"refresh": True})
389
387
  set_access_cookies(response, access_token)
@@ -5,7 +5,7 @@ from flask_restful import Resource, reqparse
5
5
  from flask_jwt_extended import jwt_required
6
6
 
7
7
  from zou.app.mixin import ArgsMixin
8
- from zou.app.utils import permissions
8
+ from zou.app.utils import permissions, date_helpers
9
9
 
10
10
  from zou.app.services import (
11
11
  chats_service,
@@ -80,6 +80,9 @@ class DownloadAttachmentResource(Resource):
80
80
  as_attachment=False,
81
81
  download_name=attachment_file["name"],
82
82
  max_age=config.CLIENT_CACHE_MAX_AGE,
83
+ last_modified=date_helpers.get_datetime_from_string(
84
+ attachment_file["updated_at"]
85
+ ),
83
86
  )
84
87
  except Exception:
85
88
  current_app.logger.error(
@@ -23,7 +23,7 @@ from zou.app.services import (
23
23
  user_service,
24
24
  concepts_service,
25
25
  )
26
- from zou.app.utils import events, fields, date_helpers
26
+ from zou.app.utils import events, date_helpers
27
27
 
28
28
  from werkzeug.exceptions import NotFound
29
29
 
@@ -187,11 +187,11 @@ class EntityResource(BaseModelResource, EntityEventMixin):
187
187
  version = None
188
188
  if frame_in != pframe_in or frame_out != pframe_out or name != pname:
189
189
  current_user_id = persons_service.get_current_user()["id"]
190
- previous_updated_at = fields.get_date_object(
191
- previous_shot["updated_at"], date_format="%Y-%m-%dT%H:%M:%S"
190
+ previous_updated_at = date_helpers.get_datetime_from_string(
191
+ previous_shot["updated_at"]
192
192
  )
193
- updated_at = fields.get_date_object(
194
- shot["updated_at"], date_format="%Y-%m-%dT%H:%M:%S"
193
+ updated_at = date_helpers.get_datetime_from_string(
194
+ shot["updated_at"]
195
195
  )
196
196
  if (
197
197
  date_helpers.get_date_diff(previous_updated_at, updated_at)
@@ -2,7 +2,7 @@ from flask_restful import Resource
2
2
  from flask_jwt_extended import jwt_required
3
3
 
4
4
  from zou.app.mixin import ArgsMixin
5
- from zou.app.utils import fields, permissions
5
+ from zou.app.utils import fields, permissions, date_helpers
6
6
 
7
7
  from zou.app.services import events_service
8
8
  from zou.app.services.exception import WrongParameterException
@@ -105,8 +105,6 @@ class LoginLogsResource(Resource, ArgsMixin):
105
105
  permissions.check_manager_permissions()
106
106
  before = None
107
107
  if args["before"] is not None:
108
- before = fields.get_date_object(
109
- args["before"], "%Y-%m-%dT%H:%M:%S"
110
- )
108
+ before = date_helpers.get_datetime_from_string(args["before"])
111
109
  page_size = args["page_size"]
112
110
  return events_service.get_last_login_logs(before, page_size)
@@ -32,7 +32,10 @@ from zou.app.services.exception import (
32
32
 
33
33
 
34
34
  def send_storage_file(
35
- working_file_id, as_attachment=False, max_age=config.CLIENT_CACHE_MAX_AGE
35
+ working_file_id,
36
+ as_attachment=False,
37
+ max_age=config.CLIENT_CACHE_MAX_AGE,
38
+ last_modified=None,
36
39
  ):
37
40
  """
38
41
  Send file from storage. If it's not a local storage, cache the file in
@@ -60,6 +63,7 @@ def send_storage_file(
60
63
  as_attachment=as_attachment,
61
64
  download_name=download_name,
62
65
  max_age=max_age,
66
+ last_modified=last_modified,
63
67
  )
64
68
  except IOError as e:
65
69
  current_app.logger.error(e)
@@ -125,8 +129,13 @@ class WorkingFileFileResource(Resource):
125
129
  schema:
126
130
  type: file
127
131
  """
128
- self.check_access(working_file_id)
129
- return send_storage_file(working_file_id)
132
+ working_file = self.check_access(working_file_id)
133
+ return send_storage_file(
134
+ working_file_id,
135
+ last_modified=date_helpers.get_datetime_from_string(
136
+ working_file["updated_at"]
137
+ ),
138
+ )
130
139
 
131
140
  @jwt_required()
132
141
  def post(self, working_file_id):
@@ -14,14 +14,13 @@ from zou.app.blueprints.previews.resources import (
14
14
  PreviewFilePreviewResource,
15
15
  PreviewFileOriginalResource,
16
16
  PreviewFileTileResource,
17
- CreateOrganisationThumbnailResource,
18
17
  OrganisationThumbnailResource,
19
- CreateProjectThumbnailResource,
18
+ CreateOrganisationThumbnailResource,
20
19
  ProjectThumbnailResource,
21
- CreatePersonThumbnailResource,
20
+ CreateProjectThumbnailResource,
22
21
  PersonThumbnailResource,
22
+ CreatePersonThumbnailResource,
23
23
  RunningPreviewFiles,
24
- LegacySetMainPreviewResource,
25
24
  SetMainPreviewResource,
26
25
  UpdateAnnotationsResource,
27
26
  UpdatePreviewPositionResource,
@@ -118,10 +117,6 @@ routes = [
118
117
  "/pictures/preview-background-files/<instance_id>.<extension>",
119
118
  PreviewBackgroundFileResource,
120
119
  ),
121
- (
122
- "/actions/entities/<entity_id>/set-main-preview/<preview_file_id>",
123
- LegacySetMainPreviewResource,
124
- ),
125
120
  (
126
121
  "/actions/preview-files/<preview_file_id>/set-main-preview",
127
122
  SetMainPreviewResource,
@@ -32,6 +32,7 @@ from zou.app.utils import (
32
32
  events,
33
33
  permissions,
34
34
  thumbnail as thumbnail_utils,
35
+ date_helpers,
35
36
  )
36
37
  from zou.app.services.exception import (
37
38
  ArgumentsException,
@@ -86,6 +87,7 @@ def send_standard_file(
86
87
  extension,
87
88
  mimetype="application/octet-stream",
88
89
  as_attachment=False,
90
+ last_modified=None,
89
91
  ):
90
92
  return send_storage_file(
91
93
  file_store.get_local_file_path,
@@ -95,10 +97,13 @@ def send_standard_file(
95
97
  extension,
96
98
  mimetype=mimetype,
97
99
  as_attachment=as_attachment,
100
+ last_modified=last_modified,
98
101
  )
99
102
 
100
103
 
101
- def send_movie_file(preview_file_id, as_attachment=False, lowdef=False):
104
+ def send_movie_file(
105
+ preview_file_id, as_attachment=False, lowdef=False, last_modified=None
106
+ ):
102
107
  folder = "previews"
103
108
  if lowdef:
104
109
  folder = "lowdef"
@@ -110,6 +115,7 @@ def send_movie_file(preview_file_id, as_attachment=False, lowdef=False):
110
115
  "mp4",
111
116
  mimetype="video/mp4",
112
117
  as_attachment=as_attachment,
118
+ last_modified=last_modified,
113
119
  )
114
120
 
115
121
 
@@ -119,6 +125,7 @@ def send_picture_file(
119
125
  as_attachment=False,
120
126
  extension="png",
121
127
  download_name="",
128
+ last_modified=None,
122
129
  ):
123
130
  if extension == "png":
124
131
  mimetype = "image/png"
@@ -133,6 +140,7 @@ def send_picture_file(
133
140
  mimetype=mimetype,
134
141
  as_attachment=as_attachment,
135
142
  download_name=download_name,
143
+ last_modified=last_modified,
136
144
  )
137
145
 
138
146
 
@@ -146,6 +154,7 @@ def send_storage_file(
146
154
  as_attachment=False,
147
155
  max_age=config.CLIENT_CACHE_MAX_AGE,
148
156
  download_name="",
157
+ last_modified=None,
149
158
  ):
150
159
  """
151
160
  Send file from storage. If it's not a local storage, cache the file in
@@ -189,6 +198,7 @@ def send_storage_file(
189
198
  as_attachment=as_attachment,
190
199
  download_name=download_name,
191
200
  max_age=max_age,
201
+ last_modified=last_modified,
192
202
  )
193
203
  except IOError as e:
194
204
  current_app.logger.error(e)
@@ -410,26 +420,30 @@ class CreatePreviewFilePictureResource(Resource, ArgsMixin):
410
420
  return files_service.get_preview_file(preview_file_id) is not None
411
421
 
412
422
 
413
- class PreviewFileMovieResource(Resource):
423
+ class BasePreviewFileResource(Resource):
414
424
  """
415
- Allow to download a movie preview.
425
+ Base class to download a preview file.
416
426
  """
417
427
 
418
428
  def __init__(self):
419
429
  Resource.__init__(self)
420
-
421
- def is_exist(self, preview_file_id):
422
- return files_service.get_preview_file(preview_file_id) is not None
430
+ self.preview_file = None
431
+ self.last_modified = None
423
432
 
424
433
  def is_allowed(self, preview_file_id):
425
- preview_file = files_service.get_preview_file(preview_file_id)
426
- task = tasks_service.get_task(preview_file["task_id"])
427
- try:
428
- user_service.check_project_access(task["project_id"])
429
- user_service.check_entity_access(task["entity_id"])
430
- return True
431
- except permissions.PermissionDenied:
432
- return False
434
+ self.preview_file = files_service.get_preview_file(preview_file_id)
435
+ task = tasks_service.get_task(self.preview_file["task_id"])
436
+ user_service.check_project_access(task["project_id"])
437
+ user_service.check_entity_access(task["entity_id"])
438
+ self.last_modified = date_helpers.get_datetime_from_string(
439
+ self.preview_file["updated_at"]
440
+ )
441
+
442
+
443
+ class PreviewFileMovieResource(BasePreviewFileResource):
444
+ """
445
+ Allow to download a movie preview.
446
+ """
433
447
 
434
448
  @jwt_required()
435
449
  def get(self, instance_id):
@@ -454,14 +468,12 @@ class PreviewFileMovieResource(Resource):
454
468
  404:
455
469
  description: File not found
456
470
  """
457
- if not self.is_exist(instance_id):
458
- abort(404)
459
-
460
- if not self.is_allowed(instance_id):
461
- abort(403)
471
+ self.is_allowed(instance_id)
462
472
 
463
473
  try:
464
- return send_movie_file(instance_id)
474
+ return send_movie_file(
475
+ instance_id, last_modified=self.last_modified
476
+ )
465
477
  except FileNotFound:
466
478
  current_app.logger.error(
467
479
  "Movie file was not found for: %s" % instance_id
@@ -469,7 +481,7 @@ class PreviewFileMovieResource(Resource):
469
481
  abort(404)
470
482
 
471
483
 
472
- class PreviewFileLowMovieResource(PreviewFileMovieResource):
484
+ class PreviewFileLowMovieResource(BasePreviewFileResource):
473
485
  """
474
486
  Allow to download a lowdef movie preview.
475
487
  """
@@ -496,14 +508,17 @@ class PreviewFileLowMovieResource(PreviewFileMovieResource):
496
508
  404:
497
509
  description: File not found
498
510
  """
499
- if not self.is_allowed(instance_id):
500
- abort(403)
511
+ self.is_allowed(instance_id)
501
512
 
502
513
  try:
503
- return send_movie_file(instance_id, lowdef=True)
514
+ return send_movie_file(
515
+ instance_id, lowdef=True, last_modified=self.last_modified
516
+ )
504
517
  except Exception:
505
518
  try:
506
- return send_movie_file(instance_id)
519
+ return send_movie_file(
520
+ instance_id, last_modified=self.last_modified
521
+ )
507
522
  except FileNotFound:
508
523
  current_app.logger.error(
509
524
  "Movie file was not found for: %s" % instance_id
@@ -511,7 +526,7 @@ class PreviewFileLowMovieResource(PreviewFileMovieResource):
511
526
  abort(404)
512
527
 
513
528
 
514
- class PreviewFileMovieDownloadResource(PreviewFileMovieResource):
529
+ class PreviewFileMovieDownloadResource(BasePreviewFileResource):
515
530
  """
516
531
  Allow to download a movie preview.
517
532
  """
@@ -538,11 +553,14 @@ class PreviewFileMovieDownloadResource(PreviewFileMovieResource):
538
553
  404:
539
554
  description: File not found
540
555
  """
541
- if not self.is_allowed(instance_id):
542
- abort(403)
556
+ self.is_allowed(instance_id)
543
557
 
544
558
  try:
545
- return send_movie_file(instance_id, as_attachment=True)
559
+ return send_movie_file(
560
+ instance_id,
561
+ as_attachment=True,
562
+ last_modified=self.last_modified,
563
+ )
546
564
  except FileNotFound:
547
565
  current_app.logger.error(
548
566
  "Movie file was not found for: %s" % instance_id
@@ -550,30 +568,11 @@ class PreviewFileMovieDownloadResource(PreviewFileMovieResource):
550
568
  abort(404)
551
569
 
552
570
 
553
- class PreviewFileResource(Resource):
571
+ class PreviewFileResource(BasePreviewFileResource):
554
572
  """
555
573
  Allow to download a generic file preview.
556
574
  """
557
575
 
558
- def __init__(self):
559
- Resource.__init__(self)
560
-
561
- def is_exist(self, preview_file_id):
562
- return files_service.get_preview_file(preview_file_id) is not None
563
-
564
- def is_allowed(self, preview_file_id):
565
- if permissions.has_manager_permissions():
566
- return True
567
- else:
568
- preview_file = files_service.get_preview_file(preview_file_id)
569
- task = tasks_service.get_task(preview_file["task_id"])
570
- try:
571
- user_service.check_project_access(task["project_id"])
572
- user_service.check_entity_access(task["entity_id"])
573
- return True
574
- except permissions.PermissionDenied:
575
- return False
576
-
577
576
  @jwt_required()
578
577
  def get(self, instance_id, extension):
579
578
  """
@@ -601,21 +600,26 @@ class PreviewFileResource(Resource):
601
600
  404:
602
601
  description: Non-movie file not found
603
602
  """
604
- if not self.is_exist(instance_id):
605
- abort(404)
606
-
607
- if not self.is_allowed(instance_id):
608
- abort(403)
603
+ self.is_allowed(instance_id)
609
604
 
610
605
  try:
611
606
  extension = extension.lower()
612
607
  if extension == "png":
613
- return send_picture_file("original", instance_id)
608
+ return send_picture_file(
609
+ "original", instance_id, last_modified=self.last_modified
610
+ )
614
611
  elif extension == "pdf":
615
612
  mimetype = "application/pdf"
616
- return send_standard_file(instance_id, extension, mimetype)
613
+ return send_standard_file(
614
+ instance_id,
615
+ extension,
616
+ mimetype,
617
+ last_modified=self.last_modified,
618
+ )
617
619
  else:
618
- return send_standard_file(instance_id, extension)
620
+ return send_standard_file(
621
+ instance_id, extension, last_modified=self.last_modified
622
+ )
619
623
 
620
624
  except FileNotFound:
621
625
  current_app.logger.error(
@@ -624,14 +628,11 @@ class PreviewFileResource(Resource):
624
628
  abort(404)
625
629
 
626
630
 
627
- class PreviewFileDownloadResource(PreviewFileResource):
631
+ class PreviewFileDownloadResource(BasePreviewFileResource):
628
632
  """
629
633
  Allow to download a generic file preview as attachment.
630
634
  """
631
635
 
632
- def __init__(self):
633
- PreviewFileResource.__init__(self)
634
-
635
636
  @jwt_required()
636
637
  def get(self, instance_id):
637
638
  """
@@ -654,25 +655,40 @@ class PreviewFileDownloadResource(PreviewFileResource):
654
655
  404:
655
656
  description: Standard file not found
656
657
  """
657
- if not self.is_allowed(instance_id):
658
- abort(403)
658
+ self.is_allowed(instance_id)
659
659
 
660
- preview_file = files_service.get_preview_file(instance_id)
661
- extension = preview_file["extension"]
660
+ extension = self.preview_file["extension"]
662
661
 
663
662
  try:
664
663
  if extension == "png":
665
664
  return send_picture_file(
666
- "original", instance_id, as_attachment=True
665
+ "original",
666
+ instance_id,
667
+ as_attachment=True,
668
+ last_modified=self.last_modified,
667
669
  )
668
670
  elif extension == "pdf":
669
671
  mimetype = "application/pdf"
670
672
  return send_standard_file(
671
- instance_id, extension, mimetype, as_attachment=True
673
+ instance_id,
674
+ extension,
675
+ mimetype,
676
+ as_attachment=True,
677
+ last_modified=self.last_modified,
678
+ )
679
+ if extension == "mp4":
680
+ return send_picture_file(
681
+ "original",
682
+ instance_id,
683
+ as_attachment=True,
684
+ last_modified=self.last_modified,
672
685
  )
673
686
  else:
674
687
  return send_standard_file(
675
- instance_id, extension, as_attachment=True
688
+ instance_id,
689
+ extension,
690
+ as_attachment=True,
691
+ last_modified=self.last_modified,
676
692
  )
677
693
  except FileNotFound:
678
694
  current_app.logger.error(
@@ -683,26 +699,31 @@ class PreviewFileDownloadResource(PreviewFileResource):
683
699
 
684
700
  class AttachmentThumbnailResource(Resource):
685
701
 
686
- def is_exist(self, attachment_id):
687
- return comments_service.get_attachment_file(attachment_id) is not None
702
+ def __init__(self):
703
+ Resource.__init__(self)
704
+ self.attachment_file = None
688
705
 
689
706
  def is_allowed(self, attachment_id):
690
- attachment_file = comments_service.get_attachment_file(attachment_id)
691
- if attachment_file["comment_id"] is not None:
692
- comment = tasks_service.get_comment(attachment_file["comment_id"])
707
+ self.attachment_file = comments_service.get_attachment_file(
708
+ attachment_id
709
+ )
710
+ if self.attachment_file["comment_id"] is not None:
711
+ comment = tasks_service.get_comment(
712
+ self.attachment_file["comment_id"]
713
+ )
693
714
  task = tasks_service.get_task(comment["object_id"])
694
715
  user_service.check_project_access(task["project_id"])
695
716
  user_service.check_entity_access(task["entity_id"])
696
- elif attachment_file["chat_message_id"] is not None:
717
+ elif self.attachment_file["chat_message_id"] is not None:
697
718
  message = chats_service.get_chat_message(
698
- attachment_file["chat_message_id"]
719
+ self.attachment_file["chat_message_id"]
699
720
  )
700
721
  chat = chats_service.get_chat_by_id(message["chat_id"])
701
722
  entity = entities_service.get_entity(chat["object_id"])
702
723
  user_service.check_project_access(entity["project_id"])
703
724
  user_service.check_entity_access(chat["object_id"])
704
725
  else:
705
- return False
726
+ raise permissions.PermissionDenied
706
727
  return True
707
728
 
708
729
  @jwt_required()
@@ -727,14 +748,16 @@ class AttachmentThumbnailResource(Resource):
727
748
  404:
728
749
  description: Picture file not found
729
750
  """
730
- if not self.is_exist(attachment_file_id):
731
- abort(404)
732
-
733
- if not self.is_allowed(attachment_file_id):
734
- abort(403)
751
+ self.is_allowed(attachment_file_id)
735
752
 
736
753
  try:
737
- return send_picture_file("thumbnails", attachment_file_id)
754
+ return send_picture_file(
755
+ "thumbnails",
756
+ attachment_file_id,
757
+ last_modified=date_helpers.get_datetime_from_string(
758
+ self.attachment_file["updated_at"]
759
+ ),
760
+ )
738
761
  except FileNotFound:
739
762
  current_app.logger.error(
740
763
  "Picture file was not found for attachment: %s"
@@ -743,31 +766,15 @@ class AttachmentThumbnailResource(Resource):
743
766
  abort(404)
744
767
 
745
768
 
746
- class BasePreviewPictureResource(Resource):
769
+ class BasePreviewPictureResource(BasePreviewFileResource):
747
770
  """
748
771
  Base class to download a thumbnail.
749
772
  """
750
773
 
751
774
  def __init__(self, picture_type):
752
- Resource.__init__(self)
775
+ BasePreviewFileResource.__init__(self)
753
776
  self.picture_type = picture_type
754
777
 
755
- def is_exist(self, preview_file_id):
756
- return files_service.get_preview_file(preview_file_id) is not None
757
-
758
- def is_allowed(self, preview_file_id):
759
- if permissions.has_manager_permissions():
760
- return True
761
- else:
762
- preview_file = files_service.get_preview_file(preview_file_id)
763
- task = tasks_service.get_task(preview_file["task_id"])
764
- try:
765
- user_service.check_project_access(task["project_id"])
766
- user_service.check_entity_access(task["entity_id"])
767
- return True
768
- except permissions.PermissionDenied:
769
- return False
770
-
771
778
  @jwt_required()
772
779
  def get(self, instance_id):
773
780
  """
@@ -790,14 +797,14 @@ class BasePreviewPictureResource(Resource):
790
797
  404:
791
798
  description: Picture file not found
792
799
  """
793
- if not self.is_exist(instance_id):
794
- abort(404)
795
-
796
- if not self.is_allowed(instance_id):
797
- abort(403)
800
+ self.is_allowed(instance_id)
798
801
 
799
802
  try:
800
- return send_picture_file(self.picture_type, instance_id)
803
+ return send_picture_file(
804
+ self.picture_type,
805
+ instance_id,
806
+ last_modified=self.last_modified,
807
+ )
801
808
  except FileNotFound:
802
809
  current_app.logger.error(
803
810
  "Picture file was not found for: %s" % instance_id
@@ -834,21 +841,38 @@ class PreviewFileOriginalResource(BasePreviewPictureResource):
834
841
  BasePreviewPictureResource.__init__(self, "original")
835
842
 
836
843
 
837
- class BaseCreatePictureResource(Resource):
844
+ class BaseThumbnailResource(Resource):
838
845
  """
839
- Base class to create a thumbnail.
846
+ Base class to post and get a thumbnail.
840
847
  """
841
848
 
842
- def __init__(self, data_type, size=thumbnail_utils.RECTANGLE_SIZE):
849
+ def __init__(
850
+ self,
851
+ data_type,
852
+ get_model_func,
853
+ update_model_func,
854
+ size=thumbnail_utils.RECTANGLE_SIZE,
855
+ ):
843
856
  Resource.__init__(self)
844
857
  self.data_type = data_type
858
+ self.get_model_func = get_model_func
859
+ self.update_model_func = update_model_func
845
860
  self.size = size
861
+ self.model = None
862
+ self.last_modified = None
846
863
 
847
- def check_permissions(self, instance_id):
864
+ def is_exist(self, instance_id):
865
+ self.model = self.get_model_func(instance_id)
866
+
867
+ def check_allowed_to_post(self, instance_id):
848
868
  permissions.check_admin_permissions()
849
869
 
870
+ def check_allowed_to_get(self, instance_id):
871
+ if not self.model["has_avatar"]:
872
+ raise NotFound
873
+
850
874
  def prepare_creation(self, instance_id):
851
- pass
875
+ self.model = self.update_model_func(instance_id, {"has_avatar": True})
852
876
 
853
877
  def emit_event(self, instance_id):
854
878
  model_name = self.data_type[:-1]
@@ -885,10 +909,9 @@ class BaseCreatePictureResource(Resource):
885
909
  404:
886
910
  description: Cannot found related object.
887
911
  """
888
- if not self.is_exist(instance_id):
889
- abort(404)
912
+ self.is_exist(instance_id)
913
+ self.check_allowed_to_post(instance_id)
890
914
 
891
- self.check_permissions(instance_id)
892
915
  self.prepare_creation(instance_id)
893
916
 
894
917
  tmp_folder = config.TMP_DIR
@@ -911,18 +934,6 @@ class BaseCreatePictureResource(Resource):
911
934
  self.emit_event(instance_id)
912
935
  return {"thumbnail_path": thumbnail_url_path}, 201
913
936
 
914
-
915
- class BasePictureResource(Resource):
916
- """
917
- Base resource to download a thumbnail.
918
- """
919
-
920
- def is_exist(self, instance_id):
921
- return False
922
-
923
- def is_allowed(self, instance_id):
924
- return True
925
-
926
937
  @jwt_required()
927
938
  def get(self, instance_id):
928
939
  """
@@ -945,14 +956,17 @@ class BasePictureResource(Resource):
945
956
  404:
946
957
  description: Object instance not found
947
958
  """
948
- if not self.is_exist(instance_id):
949
- abort(404)
950
-
951
- if not self.is_allowed(instance_id):
952
- abort(403)
959
+ self.is_exist(instance_id)
960
+ self.check_allowed_to_get(instance_id)
953
961
 
954
962
  try:
955
- return send_picture_file("thumbnails", instance_id)
963
+ return send_picture_file(
964
+ "thumbnails",
965
+ instance_id,
966
+ last_modified=date_helpers.get_datetime_from_string(
967
+ self.model["updated_at"]
968
+ ),
969
+ )
956
970
  except FileNotFound:
957
971
  current_app.logger.error(
958
972
  "Thumbnail file was not found for: %s" % instance_id
@@ -965,114 +979,63 @@ class BasePictureResource(Resource):
965
979
  abort(404)
966
980
 
967
981
 
968
- class CreatePersonThumbnailResource(BaseCreatePictureResource):
982
+ class PersonThumbnailResource(BaseThumbnailResource):
969
983
  def __init__(self):
970
- BaseCreatePictureResource.__init__(
971
- self, "persons", thumbnail_utils.BIG_SQUARE_SIZE
984
+ BaseThumbnailResource.__init__(
985
+ self,
986
+ "persons",
987
+ persons_service.get_person,
988
+ persons_service.update_person,
989
+ thumbnail_utils.BIG_SQUARE_SIZE,
972
990
  )
973
991
 
974
- def is_exist(self, person_id):
975
- return persons_service.get_person(person_id) is not None
976
-
977
- def check_permissions(self, instance_id):
992
+ def check_allowed_to_post(self, instance_id):
978
993
  is_current_user = (
979
- persons_service.get_current_user()["id"] != instance_id
994
+ persons_service.get_current_user()["id"] == instance_id
980
995
  )
981
- if is_current_user and not permissions.has_admin_permissions():
996
+ if not is_current_user and not permissions.has_admin_permissions():
982
997
  raise permissions.PermissionDenied
983
998
 
984
- def prepare_creation(self, instance_id):
985
- return persons_service.update_person(instance_id, {"has_avatar": True})
986
999
 
1000
+ class CreatePersonThumbnailResource(PersonThumbnailResource):
1001
+ pass
987
1002
 
988
- class PersonThumbnailResource(BasePictureResource):
989
- def is_exist(self, person_id):
990
- person = persons_service.get_person(person_id)
991
- return person is not None and person["has_avatar"]
992
1003
 
1004
+ class OrganisationThumbnailResource(BaseThumbnailResource):
993
1005
 
994
- class CreateOrganisationThumbnailResource(BaseCreatePictureResource):
995
1006
  def __init__(self):
996
- BaseCreatePictureResource.__init__(
997
- self, "organisations", thumbnail_utils.BIG_SQUARE_SIZE
1007
+ BaseThumbnailResource.__init__(
1008
+ self,
1009
+ "organisations",
1010
+ persons_service.get_organisation,
1011
+ persons_service.update_organisation,
1012
+ thumbnail_utils.BIG_SQUARE_SIZE,
998
1013
  )
999
1014
 
1000
1015
  def is_exist(self, organisation_id):
1001
- return True
1016
+ self.model = persons_service.get_organisation(organisation_id)
1002
1017
 
1003
- def check_permissions(self, organisation_id):
1004
- if not permissions.has_admin_permissions():
1005
- raise permissions.PermissionDenied
1006
1018
 
1007
- def prepare_creation(self, organisation_id):
1008
- return persons_service.update_organisation(
1009
- organisation_id, {"has_avatar": True}
1010
- )
1019
+ class CreateOrganisationThumbnailResource(OrganisationThumbnailResource):
1020
+ pass
1011
1021
 
1012
1022
 
1013
- class OrganisationThumbnailResource(BasePictureResource):
1014
- def is_exist(self, organisation_id):
1015
- return True
1016
-
1017
-
1018
- class CreateProjectThumbnailResource(BaseCreatePictureResource):
1023
+ class ProjectThumbnailResource(BaseThumbnailResource):
1019
1024
  def __init__(self):
1020
- BaseCreatePictureResource.__init__(
1021
- self, "projects", thumbnail_utils.SQUARE_SIZE
1025
+ BaseThumbnailResource.__init__(
1026
+ self,
1027
+ "projects",
1028
+ projects_service.get_project,
1029
+ projects_service.update_project,
1022
1030
  )
1023
1031
 
1024
- def is_exist(self, project_id):
1025
- return projects_service.get_project(project_id) is not None
1026
-
1027
- def prepare_creation(self, instance_id):
1028
- return projects_service.update_project(
1029
- instance_id, {"has_avatar": True}
1030
- )
1032
+ def check_allowed_to_get(self, instance_id):
1033
+ super().check_allowed_to_get(instance_id)
1034
+ user_service.check_project_access(instance_id)
1031
1035
 
1032
1036
 
1033
- class ProjectThumbnailResource(BasePictureResource):
1034
- def is_exist(self, project_id):
1035
- return projects_service.get_project(project_id) is not None
1036
-
1037
- def is_allowed(self, project_id):
1038
- try:
1039
- user_service.check_project_access(project_id)
1040
- return True
1041
- except permissions.PermissionDenied:
1042
- return False
1043
-
1044
-
1045
- class LegacySetMainPreviewResource(Resource):
1046
- @jwt_required()
1047
- def put(self, entity_id, preview_file_id):
1048
- """
1049
- Set main preview to given file.
1050
- ---
1051
- tags:
1052
- - Previews
1053
- parameters:
1054
- - in: path
1055
- name: entity_id
1056
- required: True
1057
- type: string
1058
- format: UUID
1059
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
1060
- - in: path
1061
- name: preview_file_id
1062
- required: True
1063
- type: string
1064
- format: UUID
1065
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
1066
- responses:
1067
- 200:
1068
- description: Main preview set
1069
- """
1070
- preview_file = files_service.get_preview_file(preview_file_id)
1071
- task = tasks_service.get_task(preview_file["task_id"])
1072
- user_service.check_project_access(task["project_id"])
1073
- return entities_service.update_entity_preview(
1074
- entity_id, preview_file_id
1075
- )
1037
+ class CreateProjectThumbnailResource(ProjectThumbnailResource):
1038
+ pass
1076
1039
 
1077
1040
 
1078
1041
  class SetMainPreviewResource(Resource, ArgsMixin):
@@ -1512,6 +1475,9 @@ class PreviewBackgroundFileResource(Resource):
1512
1475
  instance_id,
1513
1476
  extension=extension,
1514
1477
  download_name=f"{preview_background_file['original_name']}.{extension}",
1478
+ last_modified=date_helpers.get_datetime_from_string(
1479
+ preview_background_file["updated_at"]
1480
+ ),
1515
1481
  )
1516
1482
  except FileNotFound:
1517
1483
  current_app.logger.error(
@@ -1520,11 +1486,17 @@ class PreviewBackgroundFileResource(Resource):
1520
1486
  raise PreviewBackgroundFileNotFoundException
1521
1487
 
1522
1488
 
1523
- class PreviewBackgroundFileThumbnailResource(BasePictureResource):
1524
- def is_exist(self, preview_background_file_id):
1525
- return (
1526
- files_service.get_preview_background_file(
1527
- preview_background_file_id
1528
- )
1529
- is not None
1489
+ class PreviewBackgroundFileThumbnailResource(BaseThumbnailResource):
1490
+ def __init__(self):
1491
+ BaseThumbnailResource.__init__(
1492
+ self,
1493
+ "preview-backgrounds",
1494
+ files_service.get_preview_background_file,
1495
+ files_service.update_preview_background_file,
1530
1496
  )
1497
+
1498
+ def check_allowed_to_get(self, preview_background_file_id):
1499
+ return True
1500
+
1501
+ def post(self, preview_background_file_id):
1502
+ raise AttributeError("Method not allowed")
@@ -98,7 +98,7 @@ routes = [
98
98
  (
99
99
  "/actions/projects/<project_id>/task-types/<task_type_id>/set-shot-nb-frames",
100
100
  SetShotsFramesResource,
101
- )
101
+ ),
102
102
  ]
103
103
 
104
104
 
@@ -1529,7 +1529,6 @@ class ProjectQuotasResource(Resource, ArgsMixin):
1529
1529
  )
1530
1530
 
1531
1531
 
1532
-
1533
1532
  class SetShotsFramesResource(Resource, ArgsMixin):
1534
1533
  @jwt_required()
1535
1534
  def post(self, project_id, task_type_id):
@@ -1558,13 +1557,13 @@ class SetShotsFramesResource(Resource, ArgsMixin):
1558
1557
  description: Frames set for given shots
1559
1558
  """
1560
1559
  user_service.check_manager_project_access(project_id)
1561
- if not fields.is_valid_id(task_type_id) or \
1562
- not fields.is_valid_id(project_id):
1560
+ if not fields.is_valid_id(task_type_id) or not fields.is_valid_id(
1561
+ project_id
1562
+ ):
1563
1563
  raise WrongParameterException("Invalid project or task type id")
1564
1564
 
1565
1565
  episode_id = self.get_episode_id()
1566
- if not episode_id in ["", None] and \
1567
- not fields.is_valid_id(episode_id):
1566
+ if not episode_id in ["", None] and not fields.is_valid_id(episode_id):
1568
1567
  raise WrongParameterException("Invalid episode id")
1569
1568
 
1570
1569
  if episode_id == "":
zou/app/mixin.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from flask_restful import reqparse
2
2
  from flask import request
3
3
 
4
- from zou.app.utils import fields
4
+ from zou.app.utils import date_helpers
5
5
  from zou.app.services.exception import WrongParameterException
6
6
 
7
7
 
@@ -146,10 +146,10 @@ class ArgsMixin(object):
146
146
  if param is None:
147
147
  return date
148
148
  try:
149
- date = fields.get_date_object(param, "%Y-%m-%dT%H:%M:%S")
149
+ date = date_helpers.get_datetime_from_string(param)
150
150
  except Exception:
151
151
  try:
152
- date = fields.get_date_object(param, "%Y-%m-%d")
152
+ date = date_helpers.get_date_from_string(param)
153
153
  except Exception:
154
154
  raise WrongParameterException(
155
155
  "Wrong date format for before argument."
@@ -9,7 +9,6 @@ from datetime import timedelta
9
9
  from flask import request, session, current_app
10
10
  from babel.dates import format_datetime
11
11
 
12
- from flask_jwt_extended import get_jti
13
12
  from ldap3 import Server, Connection, ALL, NTLM, SIMPLE
14
13
  from ldap3.core.exceptions import (
15
14
  LDAPSocketOpenError,
@@ -678,24 +677,6 @@ def generate_new_recovery_codes(person_id):
678
677
  return otp_recovery_codes
679
678
 
680
679
 
681
- def register_tokens(app, access_token, refresh_token=None):
682
- """
683
- Register access and refresh tokens to auth token store. That way they
684
- can be used like a session.
685
- """
686
- access_jti = get_jti(encoded_token=access_token)
687
-
688
- auth_tokens_store.add(
689
- access_jti, "false", app.config["JWT_ACCESS_TOKEN_EXPIRES"]
690
- )
691
-
692
- if refresh_token is not None:
693
- refresh_jti = get_jti(encoded_token=refresh_token)
694
- auth_tokens_store.add(
695
- refresh_jti, "false", app.config["JWT_REFRESH_TOKEN_EXPIRES"]
696
- )
697
-
698
-
699
680
  def revoke_tokens(app, jti):
700
681
  """
701
682
  Remove access and refresh tokens from auth token store.
@@ -306,8 +306,8 @@ def new_comment(
306
306
  )
307
307
  except ValueError:
308
308
  try:
309
- created_at_date = fields.get_date_object(
310
- created_at, date_format="%Y-%m-%dT%H:%M:%S"
309
+ created_at_date = date_helpers.get_datetime_from_string(
310
+ created_at
311
311
  )
312
312
  except ValueError:
313
313
  pass
@@ -1543,7 +1543,7 @@ def set_frames_from_task_type_preview_files(
1543
1543
  subquery = (
1544
1544
  db.session.query(
1545
1545
  Shot.id.label("entity_id"),
1546
- func.max(PreviewFile.created_at).label("max_created_at")
1546
+ func.max(PreviewFile.created_at).label("max_created_at"),
1547
1547
  )
1548
1548
  .join(Task, PreviewFile.task_id == Task.id)
1549
1549
  .join(Shot, Task.entity_id == Shot.id)
@@ -1559,7 +1559,7 @@ def set_frames_from_task_type_preview_files(
1559
1559
  subquery = (
1560
1560
  db.session.query(
1561
1561
  Shot.id.label("entity_id"),
1562
- func.max(PreviewFile.created_at).label("max_created_at")
1562
+ func.max(PreviewFile.created_at).label("max_created_at"),
1563
1563
  )
1564
1564
  .join(Task, PreviewFile.task_id == Task.id)
1565
1565
  .join(Shot, Task.entity_id == Shot.id)
@@ -1571,15 +1571,14 @@ def set_frames_from_task_type_preview_files(
1571
1571
  )
1572
1572
 
1573
1573
  query = (
1574
- db.session.query(
1575
- Shot,
1576
- PreviewFile.duration
1577
- )
1574
+ db.session.query(Shot, PreviewFile.duration)
1578
1575
  .join(Task, Task.entity_id == Shot.id)
1579
1576
  .join(subquery, (Shot.id == subquery.c.entity_id))
1580
- .join(PreviewFile,
1581
- (PreviewFile.task_id == Task.id) &
1582
- (PreviewFile.created_at == subquery.c.max_created_at))
1577
+ .join(
1578
+ PreviewFile,
1579
+ (PreviewFile.task_id == Task.id)
1580
+ & (PreviewFile.created_at == subquery.c.max_created_at),
1581
+ )
1583
1582
  .filter(Task.task_type_id == task_type_id)
1584
1583
  .filter(Shot.project_id == project_id)
1585
1584
  )
@@ -1587,12 +1586,14 @@ def set_frames_from_task_type_preview_files(
1587
1586
  results = query.all()
1588
1587
  project = projects_service.get_project(project_id)
1589
1588
  updates = []
1590
- for (shot, preview_duration) in results:
1589
+ for shot, preview_duration in results:
1591
1590
  nb_frames = round(preview_duration * int(project["fps"]))
1592
- updates.append({
1593
- "id": shot.id,
1594
- "nb_frames": nb_frames,
1595
- })
1591
+ updates.append(
1592
+ {
1593
+ "id": shot.id,
1594
+ "nb_frames": nb_frames,
1595
+ }
1596
+ )
1596
1597
  clear_shot_cache(str(shot.id))
1597
1598
 
1598
1599
  db.session.bulk_update_mappings(Shot, updates)
@@ -58,4 +58,4 @@ def is_revoked(jti):
58
58
  """
59
59
  Tell if a stored auth token is revoked or not.
60
60
  """
61
- return get(jti) in [None, "true"]
61
+ return get(jti) == "true"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zou
3
- Version: 0.19.44
3
+ Version: 0.19.46
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
@@ -35,12 +35,12 @@ Requires-Dist: flask-mail ==0.10.0
35
35
  Requires-Dist: flask-principal ==0.4.0
36
36
  Requires-Dist: flask-restful ==0.3.10
37
37
  Requires-Dist: flask-sqlalchemy ==3.1.1
38
- Requires-Dist: flask-fs2[s3,swift] ==0.7.26
38
+ Requires-Dist: flask-fs2[s3,swift] ==0.7.27
39
39
  Requires-Dist: flask-jwt-extended ==4.6.0
40
40
  Requires-Dist: flask-migrate ==4.0.7
41
41
  Requires-Dist: flask-socketio ==5.3.6
42
42
  Requires-Dist: flask ==3.0.3
43
- Requires-Dist: gazu ==0.10.10
43
+ Requires-Dist: gazu ==0.10.11
44
44
  Requires-Dist: gevent-websocket ==0.10.1
45
45
  Requires-Dist: gevent ==24.2.1
46
46
  Requires-Dist: gunicorn ==22.0.0
@@ -49,13 +49,14 @@ Requires-Dist: itsdangerous ==2.2.0
49
49
  Requires-Dist: Jinja2 ==3.1.4
50
50
  Requires-Dist: ldap3 ==2.9.1
51
51
  Requires-Dist: matterhook ==0.2
52
- Requires-Dist: meilisearch ==0.31.3
52
+ Requires-Dist: meilisearch ==0.31.4
53
53
  Requires-Dist: opencv-python ==4.10.0.84
54
54
  Requires-Dist: OpenTimelineIO ==0.17.0
55
- Requires-Dist: orjson ==3.10.5
56
- Requires-Dist: pillow ==10.3.0
55
+ Requires-Dist: OpenTimelineIO-Plugins ==0.17.0
56
+ Requires-Dist: orjson ==3.10.6
57
+ Requires-Dist: pillow ==10.4.0
57
58
  Requires-Dist: psutil ==6.0.0
58
- Requires-Dist: psycopg[binary] ==3.1.19
59
+ Requires-Dist: psycopg[binary] ==3.2.1
59
60
  Requires-Dist: pyotp ==2.9.0
60
61
  Requires-Dist: python-nomad ==2.0.1
61
62
  Requires-Dist: python-slugify ==8.0.4
@@ -78,14 +79,14 @@ Requires-Dist: autoflake ==2.3.1 ; extra == 'lint'
78
79
  Requires-Dist: black ==24.4.2 ; extra == 'lint'
79
80
  Requires-Dist: pre-commit ==3.7.1 ; (python_version >= "3.9") and extra == 'lint'
80
81
  Provides-Extra: monitoring
81
- Requires-Dist: prometheus-flask-exporter ==0.23.0 ; extra == 'monitoring'
82
+ Requires-Dist: prometheus-flask-exporter ==0.23.1 ; extra == 'monitoring'
82
83
  Requires-Dist: pygelf ==0.4.2 ; extra == 'monitoring'
83
- Requires-Dist: sentry-sdk ==2.7.1 ; extra == 'monitoring'
84
+ Requires-Dist: sentry-sdk ==2.10.0 ; extra == 'monitoring'
84
85
  Provides-Extra: prod
85
86
  Requires-Dist: gunicorn ; extra == 'prod'
86
87
  Requires-Dist: gevent ; extra == 'prod'
87
88
  Provides-Extra: test
88
- Requires-Dist: fakeredis ==2.23.2 ; extra == 'test'
89
+ Requires-Dist: fakeredis ==2.23.3 ; extra == 'test'
89
90
  Requires-Dist: mixer ==7.2.2 ; extra == 'test'
90
91
  Requires-Dist: pytest-cov ==5.0.0 ; extra == 'test'
91
92
  Requires-Dist: pytest ==8.2.2 ; extra == 'test'
@@ -1,4 +1,4 @@
1
- zou/__init__.py,sha256=7ILEIX1Fu8Phnz2rorDj-PAL_jriV_DkEc7iRWFS2Yc,24
1
+ zou/__init__.py,sha256=LvHLU-F515PGYfBGNu8GbSE2L78r7PFa-RIsPHqykKA,24
2
2
  zou/cli.py,sha256=2cDkbEOqp_m9hzBQf5wpxc_h0WjoH8KtxQQMNuREYlc,18201
3
3
  zou/debug.py,sha256=1fawPbkD4wn0Y9Gk0BiBFSa-CQe5agFi8R9uJYl2Uyk,520
4
4
  zou/event_stream.py,sha256=zgob2dZKray2lxPa11hdRNmOg8XRlKDdRcGdF80ylwg,8245
@@ -6,19 +6,19 @@ zou/job_settings.py,sha256=WB_RkYxmh4ffQHqg63_wsUDiS0zP3yxiwrxK7DhHG0g,150
6
6
  zou/app/__init__.py,sha256=aWh9K5n63TpXdLDxpfFtNNoCYZVE6OV3YLCXLuygWQg,6700
7
7
  zou/app/api.py,sha256=JTB_IMVO8EOoyqx9KdRkiIix0chOLi0yGDY-verUJXA,5127
8
8
  zou/app/config.py,sha256=J0jmGmyvk8JqcMPAEW8KfvwP8GwEoC9BxQG8AQV9aiY,6393
9
- zou/app/mixin.py,sha256=cVq7dUu6Zysv6STA_0-jIyQy4M_KpndXzn3nT21o_OQ,4897
9
+ zou/app/mixin.py,sha256=A4iIOwpkfYmRKXvF2Ykdc8BUQgufB0r_I4RdZYTiS3E,4896
10
10
  zou/app/swagger.py,sha256=UW9DSik3a8GuH1_-7F5P7EYgjZ9DA_kFjukO-6n4kgk,54682
11
11
  zou/app/blueprints/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  zou/app/blueprints/assets/__init__.py,sha256=tSRvVrnPj732F4k_lkxoTJBJNmIUAqKXPLJDHvOtAA4,2178
13
13
  zou/app/blueprints/assets/resources.py,sha256=qY4K890kTTRWUVnNomilEuJY7qBy4RWHG2XiTuWPfBA,18988
14
14
  zou/app/blueprints/auth/__init__.py,sha256=b8Syg8DHJn5s3avMZo5ukNQCzpX_2z9fmBSG3EIt5Yg,998
15
- zou/app/blueprints/auth/resources.py,sha256=MpijJdIszQQ9vVHdf6ZX4DL1LGgs42aLglPAfYFSSZQ,41891
15
+ zou/app/blueprints/auth/resources.py,sha256=b6zPYiBE_a1gE2ei3FFswy1Iml2r6TtMB5i48f9mPKk,41760
16
16
  zou/app/blueprints/breakdown/__init__.py,sha256=Dp6GWSGxxWIedpyzTTEKpCRUYEo8oVNVyQhwNvTMmQM,1888
17
17
  zou/app/blueprints/breakdown/resources.py,sha256=JY3j17kv7xPKV9S-XoOxNYivpGHlfZkXI5WuGtx3_Yk,13591
18
18
  zou/app/blueprints/chats/__init__.py,sha256=YGmwGvddg3MgSYVIh-hmkX8t2em9_LblxBeJzFqFJD4,558
19
19
  zou/app/blueprints/chats/resources.py,sha256=1THB-UNgMcedspNfB9-3-Tsr6rJ_PUl6Y2s_y0Ww0jk,5910
20
20
  zou/app/blueprints/comments/__init__.py,sha256=WqpJ7-_dK1cInGTFJAxQ7syZtPCotwq2oO20UEnk1h4,1532
21
- zou/app/blueprints/comments/resources.py,sha256=nKR_2XsxKIqmqpkf5LBI23O55t0JRc22q4Ow9Ue8gjM,19271
21
+ zou/app/blueprints/comments/resources.py,sha256=_WoAnnv2uRLbc_fzg6XCeXF06_KNFS08r84YVN858Mc,19423
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
@@ -31,7 +31,7 @@ zou/app/blueprints/crud/comments.py,sha256=YH4cllfGloPMcIq1tgpT5pSvKTwNJ1zpkUssr
31
31
  zou/app/blueprints/crud/custom_action.py,sha256=Nf6S6YBxePg4-nfaPTroIYZGWt9fJEHC719DOKJ8R98,978
32
32
  zou/app/blueprints/crud/day_off.py,sha256=y0vahwLa7F9u6K5KUQ_6vt7XxHWQ_9vBdzDxvEMGIKg,2496
33
33
  zou/app/blueprints/crud/department.py,sha256=AAN_qxZDQGS-mR1kRS_2qGx1D7PKT3h_Ke3IXtJ_F90,973
34
- zou/app/blueprints/crud/entity.py,sha256=u_yxAb42VlTCcXoWEyVXCBeszqawxhSChvv9WuAEMX4,8357
34
+ zou/app/blueprints/crud/entity.py,sha256=Vsa3PFTT8L2x-FP9JXo4fYecWx-dRCzd8049V3g865w,8313
35
35
  zou/app/blueprints/crud/entity_link.py,sha256=PlHGy3RPDUVdwWtOpQegz-wZCcxe9FZw_80nUVBy1Ac,819
36
36
  zou/app/blueprints/crud/entity_type.py,sha256=eiOwyoxwA2r857vX6Nni1ukXnNnV-6GnpjIGuTrRqrI,1927
37
37
  zou/app/blueprints/crud/event.py,sha256=bay2ggg5Oa69lOxNYGkHVQdSKuMxGhk2wqWP7QApKUg,589
@@ -66,7 +66,7 @@ zou/app/blueprints/edits/resources.py,sha256=IvgqEhIvjF6OO7VNK6i5w6NKEoFQyKfuZJ7
66
66
  zou/app/blueprints/entities/__init__.py,sha256=v-qt2dl3s3tmK_ur-cpDHNPmcL0A6xCybczyuidjUCo,713
67
67
  zou/app/blueprints/entities/resources.py,sha256=vyEs5ODyRqP9ICf_2KPVnwrGRCJQxZTgO5qFld0F3lg,3166
68
68
  zou/app/blueprints/events/__init__.py,sha256=Vb0gO7Bpj_2Dpx9hhKd2SZW2qqnJrFVoDpJjFOmoMZw,394
69
- zou/app/blueprints/events/resources.py,sha256=Q6CWPDIPyD-ceIGahoEcKpUofCgBJbvldmxWem6Fd-I,3260
69
+ zou/app/blueprints/events/resources.py,sha256=SSrfq5TioZj217Qzv8wxFTUzs3_3DP8yGDYUrkttMIs,3238
70
70
  zou/app/blueprints/export/__init__.py,sha256=W3U93VD-dHlozFVSt_RDvP7h7K_FqgHPLA__W5H1DkE,1574
71
71
  zou/app/blueprints/export/csv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
72
  zou/app/blueprints/export/csv/assets.py,sha256=QafmkM1BphI6wzbcqK6Hc1XBtisjgGTGYejOwJWwWW4,5561
@@ -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=WHdy9Wf2e-kvviYbNyUFr7xgg49h9f-Di12KIZx5ssk,2981
83
83
  zou/app/blueprints/files/__init__.py,sha256=7Wty30JW2OXIn-tBFXOWWmPuHnsnxPpH3jNtHvvr9tY,3987
84
- zou/app/blueprints/files/resources.py,sha256=miyUoT5Ys3deUXCzuJK2PYWZ7tob3YeBFH4LUH9JYds,69551
84
+ zou/app/blueprints/files/resources.py,sha256=8SIV8kaqv3dxyL8nyqG3QiZmk5ZYIvUxw6k1ic-jhBs,69786
85
85
  zou/app/blueprints/index/__init__.py,sha256=Dh3oQiirpg8RCkfVOuk3irIjSvUvuRf0jPxE6oGubz0,828
86
86
  zou/app/blueprints/index/resources.py,sha256=DsYpNSO_wCAxyjWOaB9QmEqOBcjn1miLiGnfY-ii1z8,8165
87
87
  zou/app/blueprints/news/__init__.py,sha256=HxBXjC15dVbotNAZ0CLf02iwUjxJr20kgf8_kT_9nwM,505
@@ -90,14 +90,14 @@ zou/app/blueprints/persons/__init__.py,sha256=0cnHHw3K_8OEMm0qOi3wKVomSAg9IJSnVj
90
90
  zou/app/blueprints/persons/resources.py,sha256=-5J8ZswusYEBTNmX4I7fLFDZVMKyO0qhO_dkMs9SkXA,41406
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=CazwYCIg-iGSLcOH2NYcgflH7zipHfCF9MD1fP93mOI,4363
94
- zou/app/blueprints/previews/resources.py,sha256=jrAxTUqdNfsYT9dYy5q6v-TgCFUgI3ze0nJ_cvPCvi4,48187
93
+ zou/app/blueprints/previews/__init__.py,sha256=qGohO6LRNZKXBAegINcUXuZlrtxobJKQg84-rQ1L3AU,4202
94
+ zou/app/blueprints/previews/resources.py,sha256=SRPlEQ4WKSPkOdBvvGDlXdoDaT_2stbJkfzkwN1G1rQ,47077
95
95
  zou/app/blueprints/projects/__init__.py,sha256=Pn3fA5bpNFEPBzxTKJ2foV6osZFflXXSM2l2uZh3ktM,3927
96
96
  zou/app/blueprints/projects/resources.py,sha256=v9_TLh3mujL-p7QcGkfSOJnNojzoJA15jhqAHz5kEIc,31552
97
97
  zou/app/blueprints/search/__init__.py,sha256=QCjQIY_85l_orhdEiqav_GifjReuwsjZggN3V0GeUVY,356
98
98
  zou/app/blueprints/search/resources.py,sha256=ni-dX8Xfib_0FonLttoXgntXBR957-xhifPSQHHnOnY,2696
99
- zou/app/blueprints/shots/__init__.py,sha256=ZFQIwwM0s3QDVble3oa9UxNCTmpJTZ1IXTHSsuBNYB4,4075
100
- zou/app/blueprints/shots/resources.py,sha256=9cx0uO36oUy2Q1co7qSZv6IyY1swzK2cKCczNwTTZVk,47743
99
+ zou/app/blueprints/shots/__init__.py,sha256=HfgLneZBYUMa2OGwIgEZTz8zrIEYFRiYmRbreBPYeYw,4076
100
+ zou/app/blueprints/shots/resources.py,sha256=NiuRZG2ltSn6KgaZvr_W7bHXJOQkBTFPUDYisShWZ6M,47738
101
101
  zou/app/blueprints/source/__init__.py,sha256=H7K-4TDs4pc5EJvcYTYMJBHesxyqsE5-xq7J8ckOS2g,6093
102
102
  zou/app/blueprints/source/kitsu.py,sha256=4lWdqxaKDzwx-5POAIHIgZ6ODbDMOOVRxaSb_FOLcCk,5012
103
103
  zou/app/blueprints/source/otio.py,sha256=WkzpKylVkNlbY_jwf6uV5-HPylrktQznOcbCs_p8TDQ,13391
@@ -180,12 +180,12 @@ zou/app/models/time_spent.py,sha256=n7i3FO9g1eE_zATkItoCgrGVqq3iMSfdlKSveEZPloc,
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
182
  zou/app/services/assets_service.py,sha256=Vea-Ra69-Zi0Q2S-Apb5fsZv5E9f_6XYVn2HxaGQBE8,21339
183
- zou/app/services/auth_service.py,sha256=7tcEE12AH1rlyEarvn6bYK_PfOsHO9_-M8rG_nldVM8,23811
183
+ zou/app/services/auth_service.py,sha256=AxKN_THkjN2tmOHTSyFspwwFHqGBnslXuidfbav8Rxk,23224
184
184
  zou/app/services/backup_service.py,sha256=_ZtZp6wkcVYnHxBosziwLGdrTvsUttXGphiydq53iy8,4840
185
185
  zou/app/services/base_service.py,sha256=OZd0STFh-DyBBdwsmA7DMMnrwv4C8wJUbShvZ1isndU,1383
186
186
  zou/app/services/breakdown_service.py,sha256=p93HncjC36qbmvZiB259QGRtciS37N-s_7IWVAQx5eE,26727
187
187
  zou/app/services/chats_service.py,sha256=V1RmQeQnsH1xvtbs6wcjEpudQS547eJBM7bgJr9-qYM,8270
188
- zou/app/services/comments_service.py,sha256=DiZmS8vpj1oCFddpSimqaE_ajHNSQeHQgDNyPPKQV2g,18607
188
+ zou/app/services/comments_service.py,sha256=CUr0CZGkR95OrHDRJnhAZAikBLtWhM-5uLIoxI4gNtA,18589
189
189
  zou/app/services/concepts_service.py,sha256=KGvk6lF5udj3SWn40X9KE8OAigrLCZUKEz9_CW7EMgQ,11440
190
190
  zou/app/services/custom_actions_service.py,sha256=fWISEOOdthadrxeHuacEel5Xj6msn0yWXJQDG1gzvsY,297
191
191
  zou/app/services/deletion_service.py,sha256=ddaup7i_CTugcJDrynczNcfhjSKgDeGhPQjQ68We_l8,17255
@@ -206,7 +206,7 @@ zou/app/services/preview_files_service.py,sha256=SIZ_SB1bWNRE_Zm7SuEpvFWqVpHKOgf
206
206
  zou/app/services/projects_service.py,sha256=_J8hIHy3MX5MsdEMRIKNfbyewwhxtMEcc_ymeHBsF38,21434
207
207
  zou/app/services/scenes_service.py,sha256=iXN19HU4njPF5VtZXuUrVJ-W23ZQuQNPC3ADXltbWtU,992
208
208
  zou/app/services/schedule_service.py,sha256=E99HKYsXgnK2sw58fw-NNHXWBgVJiA60upztjkNSCaM,6989
209
- zou/app/services/shots_service.py,sha256=TVOgIeWkTFLG8cVUmH41E15jAq2rl23fYuGVWefWEUQ,50291
209
+ zou/app/services/shots_service.py,sha256=w96EbNyXZNKmZg1eUMbaQPAEng3WkfJaPkqEr39F2ug,50310
210
210
  zou/app/services/stats_service.py,sha256=cAlc92i9d6eYtsuwe3hYHYwdytg8KEMi1-TADfysJwM,11733
211
211
  zou/app/services/status_automations_service.py,sha256=tVio7Sj7inhvKS4UOyRhcdpwr_KNP96hT1o0X7XcGF4,715
212
212
  zou/app/services/sync_service.py,sha256=EunfXlma_IIb7011A_xLQLVQGAi-MteKgm2Y2NAxvMs,41586
@@ -215,7 +215,7 @@ zou/app/services/telemetry_services.py,sha256=xQm1h1t_JxSFW59zQGf4NuNdUi1UfMa_6p
215
215
  zou/app/services/time_spents_service.py,sha256=TBLC1O9Dg_UbciG5Nw-dejqX2-5n6q44lACeN6OnUkQ,15206
216
216
  zou/app/services/user_service.py,sha256=BiOhPV7O-vowet7jOksjXk2V4yxZkdqsIyNboJ-Oz_A,46595
217
217
  zou/app/stores/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
218
- zou/app/stores/auth_tokens_store.py,sha256=01hX-y7C3ngCfbAlPXxYQmGKJmNd_I0WG_I-a5Hu8Rw,1245
218
+ zou/app/stores/auth_tokens_store.py,sha256=adcGe3SxfY4y_tP57HIR2SZbnu_Amqk-dAGVZIcJQ9c,1237
219
219
  zou/app/stores/file_store.py,sha256=yLQDM6mNbj9oe0vsWdBqun7D8Dw-eSjD1yHCCftX0OI,4045
220
220
  zou/app/stores/publisher_store.py,sha256=0cD9bwDdF2rOEeumLrxJx7xNPR_xvf_IAAk5JttDsO4,1006
221
221
  zou/app/stores/queue_store.py,sha256=Nyh8_nXilC1FHtJvj6nVVm4sNCEtmJcRRVK2cj2G22c,612
@@ -407,9 +407,9 @@ zou/remote/normalize_movie.py,sha256=zNfEY3N1UbAHZfddGONTg2Sff3ieLVWd4dfZa1dpnes
407
407
  zou/remote/playlist.py,sha256=AsDo0bgYhDcd6DfNRV6r6Jj3URWwavE2ZN3VkKRPbLU,3293
408
408
  zou/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
409
409
  zou/utils/movie.py,sha256=u9LCEOvmkxwm-KiZ6jKNdB9LSC6XXUDwJpVx8LkDwJg,16416
410
- zou-0.19.44.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
411
- zou-0.19.44.dist-info/METADATA,sha256=Smu6ULY5kl-lKmF1xRvKTRWKc-jd0ReEhEfPneNaWRM,6678
412
- zou-0.19.44.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
413
- zou-0.19.44.dist-info/entry_points.txt,sha256=PelQoIx3qhQ_Tmne7wrLY-1m2izuzgpwokoURwSohy4,130
414
- zou-0.19.44.dist-info/top_level.txt,sha256=4S7G_jk4MzpToeDItHGjPhHx_fRdX52zJZWTD4SL54g,4
415
- zou-0.19.44.dist-info/RECORD,,
410
+ zou-0.19.46.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
411
+ zou-0.19.46.dist-info/METADATA,sha256=VsjwOstLLDKC3fdmEmo8SUazHhPIDyDNWc6sF_kKpiw,6725
412
+ zou-0.19.46.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
413
+ zou-0.19.46.dist-info/entry_points.txt,sha256=PelQoIx3qhQ_Tmne7wrLY-1m2izuzgpwokoURwSohy4,130
414
+ zou-0.19.46.dist-info/top_level.txt,sha256=4S7G_jk4MzpToeDItHGjPhHx_fRdX52zJZWTD4SL54g,4
415
+ zou-0.19.46.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.2.0)
2
+ Generator: setuptools (70.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
File without changes