zou 0.19.45__py3-none-any.whl → 0.19.47__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.45"
1
+ __version__ = "0.19.47"
@@ -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)
@@ -1,6 +1,8 @@
1
1
  from zou.app.models.organisation import Organisation
2
2
  from zou.app.blueprints.crud.base import BaseModelResource, BaseModelsResource
3
3
 
4
+ from zou.app.services import persons_service
5
+
4
6
 
5
7
  class OrganisationsResource(BaseModelsResource):
6
8
  def __init__(self):
@@ -21,3 +23,7 @@ class OrganisationResource(BaseModelResource):
21
23
  if "hours_by_day" in data:
22
24
  data["hours_by_day"] = float(data["hours_by_day"])
23
25
  return data
26
+
27
+ def post_update(self, instance_dict, data):
28
+ persons_service.clear_oranisation_cache()
29
+ return instance_dict
@@ -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,29 +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,
672
678
  )
673
679
  if extension == "mp4":
674
680
  return send_picture_file(
675
- "original", instance_id, as_attachment=True
681
+ "original",
682
+ instance_id,
683
+ as_attachment=True,
684
+ last_modified=self.last_modified,
676
685
  )
677
686
  else:
678
687
  return send_standard_file(
679
- instance_id, extension, as_attachment=True
688
+ instance_id,
689
+ extension,
690
+ as_attachment=True,
691
+ last_modified=self.last_modified,
680
692
  )
681
693
  except FileNotFound:
682
694
  current_app.logger.error(
@@ -687,26 +699,31 @@ class PreviewFileDownloadResource(PreviewFileResource):
687
699
 
688
700
  class AttachmentThumbnailResource(Resource):
689
701
 
690
- def is_exist(self, attachment_id):
691
- return comments_service.get_attachment_file(attachment_id) is not None
702
+ def __init__(self):
703
+ Resource.__init__(self)
704
+ self.attachment_file = None
692
705
 
693
706
  def is_allowed(self, attachment_id):
694
- attachment_file = comments_service.get_attachment_file(attachment_id)
695
- if attachment_file["comment_id"] is not None:
696
- 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
+ )
697
714
  task = tasks_service.get_task(comment["object_id"])
698
715
  user_service.check_project_access(task["project_id"])
699
716
  user_service.check_entity_access(task["entity_id"])
700
- elif attachment_file["chat_message_id"] is not None:
717
+ elif self.attachment_file["chat_message_id"] is not None:
701
718
  message = chats_service.get_chat_message(
702
- attachment_file["chat_message_id"]
719
+ self.attachment_file["chat_message_id"]
703
720
  )
704
721
  chat = chats_service.get_chat_by_id(message["chat_id"])
705
722
  entity = entities_service.get_entity(chat["object_id"])
706
723
  user_service.check_project_access(entity["project_id"])
707
724
  user_service.check_entity_access(chat["object_id"])
708
725
  else:
709
- return False
726
+ raise permissions.PermissionDenied
710
727
  return True
711
728
 
712
729
  @jwt_required()
@@ -731,14 +748,16 @@ class AttachmentThumbnailResource(Resource):
731
748
  404:
732
749
  description: Picture file not found
733
750
  """
734
- if not self.is_exist(attachment_file_id):
735
- abort(404)
736
-
737
- if not self.is_allowed(attachment_file_id):
738
- abort(403)
751
+ self.is_allowed(attachment_file_id)
739
752
 
740
753
  try:
741
- 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
+ )
742
761
  except FileNotFound:
743
762
  current_app.logger.error(
744
763
  "Picture file was not found for attachment: %s"
@@ -747,31 +766,15 @@ class AttachmentThumbnailResource(Resource):
747
766
  abort(404)
748
767
 
749
768
 
750
- class BasePreviewPictureResource(Resource):
769
+ class BasePreviewPictureResource(BasePreviewFileResource):
751
770
  """
752
771
  Base class to download a thumbnail.
753
772
  """
754
773
 
755
774
  def __init__(self, picture_type):
756
- Resource.__init__(self)
775
+ BasePreviewFileResource.__init__(self)
757
776
  self.picture_type = picture_type
758
777
 
759
- def is_exist(self, preview_file_id):
760
- return files_service.get_preview_file(preview_file_id) is not None
761
-
762
- def is_allowed(self, preview_file_id):
763
- if permissions.has_manager_permissions():
764
- return True
765
- else:
766
- preview_file = files_service.get_preview_file(preview_file_id)
767
- task = tasks_service.get_task(preview_file["task_id"])
768
- try:
769
- user_service.check_project_access(task["project_id"])
770
- user_service.check_entity_access(task["entity_id"])
771
- return True
772
- except permissions.PermissionDenied:
773
- return False
774
-
775
778
  @jwt_required()
776
779
  def get(self, instance_id):
777
780
  """
@@ -794,14 +797,14 @@ class BasePreviewPictureResource(Resource):
794
797
  404:
795
798
  description: Picture file not found
796
799
  """
797
- if not self.is_exist(instance_id):
798
- abort(404)
799
-
800
- if not self.is_allowed(instance_id):
801
- abort(403)
800
+ self.is_allowed(instance_id)
802
801
 
803
802
  try:
804
- 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
+ )
805
808
  except FileNotFound:
806
809
  current_app.logger.error(
807
810
  "Picture file was not found for: %s" % instance_id
@@ -838,21 +841,38 @@ class PreviewFileOriginalResource(BasePreviewPictureResource):
838
841
  BasePreviewPictureResource.__init__(self, "original")
839
842
 
840
843
 
841
- class BaseCreatePictureResource(Resource):
844
+ class BaseThumbnailResource(Resource):
842
845
  """
843
- Base class to create a thumbnail.
846
+ Base class to post and get a thumbnail.
844
847
  """
845
848
 
846
- 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
+ ):
847
856
  Resource.__init__(self)
848
857
  self.data_type = data_type
858
+ self.get_model_func = get_model_func
859
+ self.update_model_func = update_model_func
849
860
  self.size = size
861
+ self.model = None
862
+ self.last_modified = None
850
863
 
851
- 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):
852
868
  permissions.check_admin_permissions()
853
869
 
870
+ def check_allowed_to_get(self, instance_id):
871
+ if not self.model["has_avatar"]:
872
+ raise NotFound
873
+
854
874
  def prepare_creation(self, instance_id):
855
- pass
875
+ self.model = self.update_model_func(instance_id, {"has_avatar": True})
856
876
 
857
877
  def emit_event(self, instance_id):
858
878
  model_name = self.data_type[:-1]
@@ -889,10 +909,9 @@ class BaseCreatePictureResource(Resource):
889
909
  404:
890
910
  description: Cannot found related object.
891
911
  """
892
- if not self.is_exist(instance_id):
893
- abort(404)
912
+ self.is_exist(instance_id)
913
+ self.check_allowed_to_post(instance_id)
894
914
 
895
- self.check_permissions(instance_id)
896
915
  self.prepare_creation(instance_id)
897
916
 
898
917
  tmp_folder = config.TMP_DIR
@@ -915,18 +934,6 @@ class BaseCreatePictureResource(Resource):
915
934
  self.emit_event(instance_id)
916
935
  return {"thumbnail_path": thumbnail_url_path}, 201
917
936
 
918
-
919
- class BasePictureResource(Resource):
920
- """
921
- Base resource to download a thumbnail.
922
- """
923
-
924
- def is_exist(self, instance_id):
925
- return False
926
-
927
- def is_allowed(self, instance_id):
928
- return True
929
-
930
937
  @jwt_required()
931
938
  def get(self, instance_id):
932
939
  """
@@ -949,14 +956,17 @@ class BasePictureResource(Resource):
949
956
  404:
950
957
  description: Object instance not found
951
958
  """
952
- if not self.is_exist(instance_id):
953
- abort(404)
954
-
955
- if not self.is_allowed(instance_id):
956
- abort(403)
959
+ self.is_exist(instance_id)
960
+ self.check_allowed_to_get(instance_id)
957
961
 
958
962
  try:
959
- 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
+ )
960
970
  except FileNotFound:
961
971
  current_app.logger.error(
962
972
  "Thumbnail file was not found for: %s" % instance_id
@@ -969,114 +979,63 @@ class BasePictureResource(Resource):
969
979
  abort(404)
970
980
 
971
981
 
972
- class CreatePersonThumbnailResource(BaseCreatePictureResource):
982
+ class PersonThumbnailResource(BaseThumbnailResource):
973
983
  def __init__(self):
974
- BaseCreatePictureResource.__init__(
975
- 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,
976
990
  )
977
991
 
978
- def is_exist(self, person_id):
979
- return persons_service.get_person(person_id) is not None
980
-
981
- def check_permissions(self, instance_id):
992
+ def check_allowed_to_post(self, instance_id):
982
993
  is_current_user = (
983
- persons_service.get_current_user()["id"] != instance_id
994
+ persons_service.get_current_user()["id"] == instance_id
984
995
  )
985
- if is_current_user and not permissions.has_admin_permissions():
996
+ if not is_current_user and not permissions.has_admin_permissions():
986
997
  raise permissions.PermissionDenied
987
998
 
988
- def prepare_creation(self, instance_id):
989
- return persons_service.update_person(instance_id, {"has_avatar": True})
990
999
 
1000
+ class CreatePersonThumbnailResource(PersonThumbnailResource):
1001
+ pass
991
1002
 
992
- class PersonThumbnailResource(BasePictureResource):
993
- def is_exist(self, person_id):
994
- person = persons_service.get_person(person_id)
995
- return person is not None and person["has_avatar"]
996
1003
 
1004
+ class OrganisationThumbnailResource(BaseThumbnailResource):
997
1005
 
998
- class CreateOrganisationThumbnailResource(BaseCreatePictureResource):
999
1006
  def __init__(self):
1000
- BaseCreatePictureResource.__init__(
1001
- 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,
1002
1013
  )
1003
1014
 
1004
1015
  def is_exist(self, organisation_id):
1005
- return True
1006
-
1007
- def check_permissions(self, organisation_id):
1008
- if not permissions.has_admin_permissions():
1009
- raise permissions.PermissionDenied
1010
-
1011
- def prepare_creation(self, organisation_id):
1012
- return persons_service.update_organisation(
1013
- organisation_id, {"has_avatar": True}
1014
- )
1016
+ self.model = persons_service.get_organisation()
1015
1017
 
1016
1018
 
1017
- class OrganisationThumbnailResource(BasePictureResource):
1018
- def is_exist(self, organisation_id):
1019
- return True
1019
+ class CreateOrganisationThumbnailResource(OrganisationThumbnailResource):
1020
+ pass
1020
1021
 
1021
1022
 
1022
- class CreateProjectThumbnailResource(BaseCreatePictureResource):
1023
+ class ProjectThumbnailResource(BaseThumbnailResource):
1023
1024
  def __init__(self):
1024
- BaseCreatePictureResource.__init__(
1025
- self, "projects", thumbnail_utils.SQUARE_SIZE
1025
+ BaseThumbnailResource.__init__(
1026
+ self,
1027
+ "projects",
1028
+ projects_service.get_project,
1029
+ projects_service.update_project,
1026
1030
  )
1027
1031
 
1028
- def is_exist(self, project_id):
1029
- return projects_service.get_project(project_id) is not None
1030
-
1031
- def prepare_creation(self, instance_id):
1032
- return projects_service.update_project(
1033
- instance_id, {"has_avatar": True}
1034
- )
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)
1035
1035
 
1036
1036
 
1037
- class ProjectThumbnailResource(BasePictureResource):
1038
- def is_exist(self, project_id):
1039
- return projects_service.get_project(project_id) is not None
1040
-
1041
- def is_allowed(self, project_id):
1042
- try:
1043
- user_service.check_project_access(project_id)
1044
- return True
1045
- except permissions.PermissionDenied:
1046
- return False
1047
-
1048
-
1049
- class LegacySetMainPreviewResource(Resource):
1050
- @jwt_required()
1051
- def put(self, entity_id, preview_file_id):
1052
- """
1053
- Set main preview to given file.
1054
- ---
1055
- tags:
1056
- - Previews
1057
- parameters:
1058
- - in: path
1059
- name: entity_id
1060
- required: True
1061
- type: string
1062
- format: UUID
1063
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
1064
- - in: path
1065
- name: preview_file_id
1066
- required: True
1067
- type: string
1068
- format: UUID
1069
- x-example: a24a6ea4-ce75-4665-a070-57453082c25
1070
- responses:
1071
- 200:
1072
- description: Main preview set
1073
- """
1074
- preview_file = files_service.get_preview_file(preview_file_id)
1075
- task = tasks_service.get_task(preview_file["task_id"])
1076
- user_service.check_project_access(task["project_id"])
1077
- return entities_service.update_entity_preview(
1078
- entity_id, preview_file_id
1079
- )
1037
+ class CreateProjectThumbnailResource(ProjectThumbnailResource):
1038
+ pass
1080
1039
 
1081
1040
 
1082
1041
  class SetMainPreviewResource(Resource, ArgsMixin):
@@ -1516,6 +1475,9 @@ class PreviewBackgroundFileResource(Resource):
1516
1475
  instance_id,
1517
1476
  extension=extension,
1518
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
+ ),
1519
1481
  )
1520
1482
  except FileNotFound:
1521
1483
  current_app.logger.error(
@@ -1524,11 +1486,17 @@ class PreviewBackgroundFileResource(Resource):
1524
1486
  raise PreviewBackgroundFileNotFoundException
1525
1487
 
1526
1488
 
1527
- class PreviewBackgroundFileThumbnailResource(BasePictureResource):
1528
- def is_exist(self, preview_background_file_id):
1529
- return (
1530
- files_service.get_preview_background_file(
1531
- preview_background_file_id
1532
- )
1533
- 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,
1534
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."
@@ -32,5 +32,7 @@ class Organisation(db.Model, BaseMixin, SerializerMixin):
32
32
  "hd_by_default": self.hd_by_default,
33
33
  "use_original_file_name": self.use_original_file_name,
34
34
  "timesheets_locked": self.timesheets_locked,
35
+ "updated_at": self.updated_at,
36
+ "created_at": self.created_at,
35
37
  }
36
38
  )
@@ -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.45
3
+ Version: 0.19.47
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,9 +49,10 @@ 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: OpenTimelineIO-Plugins ==0.17.0
55
56
  Requires-Dist: orjson ==3.10.6
56
57
  Requires-Dist: pillow ==10.4.0
57
58
  Requires-Dist: psutil ==6.0.0
@@ -78,9 +79,9 @@ 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.8.0 ; 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'
@@ -1,4 +1,4 @@
1
- zou/__init__.py,sha256=Tv6q0iYoqN9oDIxMPDw4V79Q7KHIfjKuzDdzglh8VUA,24
1
+ zou/__init__.py,sha256=63wiU79B4fTMnEP5X5faQDDk5_7C1AbAa4PkU1j3tGg,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
@@ -40,7 +40,7 @@ zou/app/blueprints/crud/metadata_descriptor.py,sha256=6sNsebjbXnUnlflG4biB0M-C2n
40
40
  zou/app/blueprints/crud/milestone.py,sha256=UIMsyCNS7ZRePjo08yyzubLEo6fcK-RAsVvrfjXoclA,839
41
41
  zou/app/blueprints/crud/news.py,sha256=0baKn4OLEVY7ljWgpJEypo2Pgzhlp8GJxjim78oqLtg,344
42
42
  zou/app/blueprints/crud/notification.py,sha256=A-KNH0IDNCXlE4AddNxvmsD_7a9HqHGo_rI-Z_pdzus,530
43
- zou/app/blueprints/crud/organisation.py,sha256=JbgTOxmt1rSddRSGzylDeBIgjCqbeQ-a3WYnL8m9-ts,685
43
+ zou/app/blueprints/crud/organisation.py,sha256=bNGo3Dtir2o_cHk0vDPs4cJw7mRWlSwPf2oA4lDQUyU,859
44
44
  zou/app/blueprints/crud/output_file.py,sha256=jWSagq4LxwtFQH0XL_LjXaiW9FRn8T4k0tv1EbmPfg0,3954
45
45
  zou/app/blueprints/crud/output_type.py,sha256=eYJXLPkUCRj9-v5mc3rxqEHM3yycmf6rVAoaOfbnAcU,1998
46
46
  zou/app/blueprints/crud/person.py,sha256=7masu1UxnSJT7KZ4zVUvUA7B2_zdY8ZtcEtqEiSIEfc,9201
@@ -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=GTluK0FV_SG_bG33yOyJLjOsXgsDJjtnHbkm315E9RE,48346
93
+ zou/app/blueprints/previews/__init__.py,sha256=qGohO6LRNZKXBAegINcUXuZlrtxobJKQg84-rQ1L3AU,4202
94
+ zou/app/blueprints/previews/resources.py,sha256=SrbAcsHizZZiWOjnDGurYmuQlxkj1KDyXKE85x_Wz5M,47062
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
@@ -156,7 +156,7 @@ zou/app/models/metadata_descriptor.py,sha256=LRCJ7NKsy771kMSLeZDrCON7jRk76J-JTFW
156
156
  zou/app/models/milestone.py,sha256=ZwJ7_PbXT_LeUZKv3l5DLPM7ByFQEccpEeWFq4-IM-Q,927
157
157
  zou/app/models/news.py,sha256=UPX0ojWF-leAGZNKqlo7KL0s0lp2tbKMJl4N5lGbouo,1583
158
158
  zou/app/models/notification.py,sha256=1ODOymGPeB4oxgX_3WhOgIL_Lsz-JR7miDkBS6W8t_s,2563
159
- zou/app/models/organisation.py,sha256=Gj_25U92GS--_flXssPl0gwaeu7HmlEaLdSgRztjUtU,1493
159
+ zou/app/models/organisation.py,sha256=3nUJq9TBJsBs1t3h4OHFBUrvn_FGOnBsiofcdH3tdzk,1587
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
162
  zou/app/models/person.py,sha256=-JpYBpzqZ3LDD6HW1YgjrD7szhWSxQ0ti4GrrGvja2w,7466
@@ -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.45.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
411
- zou-0.19.45.dist-info/METADATA,sha256=-qqX8Imqao6HziAEsLUxOo0HY66ob65LWyQFmthuY7U,6677
412
- zou-0.19.45.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
413
- zou-0.19.45.dist-info/entry_points.txt,sha256=PelQoIx3qhQ_Tmne7wrLY-1m2izuzgpwokoURwSohy4,130
414
- zou-0.19.45.dist-info/top_level.txt,sha256=4S7G_jk4MzpToeDItHGjPhHx_fRdX52zJZWTD4SL54g,4
415
- zou-0.19.45.dist-info/RECORD,,
410
+ zou-0.19.47.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
411
+ zou-0.19.47.dist-info/METADATA,sha256=SATF4GA5kYKo1-waJHtsylYRNspz_qCFLxX3R3E5MqA,6725
412
+ zou-0.19.47.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
413
+ zou-0.19.47.dist-info/entry_points.txt,sha256=PelQoIx3qhQ_Tmne7wrLY-1m2izuzgpwokoURwSohy4,130
414
+ zou-0.19.47.dist-info/top_level.txt,sha256=4S7G_jk4MzpToeDItHGjPhHx_fRdX52zJZWTD4SL54g,4
415
+ zou-0.19.47.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