zou 0.19.15__py3-none-any.whl → 0.20.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. zou/__init__.py +1 -1
  2. zou/app/__init__.py +10 -2
  3. zou/app/api.py +2 -0
  4. zou/app/blueprints/assets/__init__.py +22 -0
  5. zou/app/blueprints/assets/resources.py +241 -4
  6. zou/app/blueprints/auth/__init__.py +4 -0
  7. zou/app/blueprints/auth/resources.py +154 -22
  8. zou/app/blueprints/breakdown/resources.py +4 -4
  9. zou/app/blueprints/chats/__init__.py +22 -0
  10. zou/app/blueprints/chats/resources.py +199 -0
  11. zou/app/blueprints/comments/resources.py +36 -19
  12. zou/app/blueprints/crud/__init__.py +12 -0
  13. zou/app/blueprints/crud/attachment_file.py +14 -5
  14. zou/app/blueprints/crud/base.py +29 -28
  15. zou/app/blueprints/crud/chat.py +13 -0
  16. zou/app/blueprints/crud/chat_message.py +13 -0
  17. zou/app/blueprints/crud/comments.py +85 -29
  18. zou/app/blueprints/crud/custom_action.py +1 -1
  19. zou/app/blueprints/crud/day_off.py +47 -9
  20. zou/app/blueprints/crud/department.py +1 -25
  21. zou/app/blueprints/crud/entity.py +46 -5
  22. zou/app/blueprints/crud/entity_type.py +13 -1
  23. zou/app/blueprints/crud/event.py +1 -1
  24. zou/app/blueprints/crud/file_status.py +1 -1
  25. zou/app/blueprints/crud/metadata_descriptor.py +24 -10
  26. zou/app/blueprints/crud/organisation.py +22 -5
  27. zou/app/blueprints/crud/output_file.py +1 -1
  28. zou/app/blueprints/crud/output_type.py +1 -1
  29. zou/app/blueprints/crud/person.py +32 -24
  30. zou/app/blueprints/crud/playlist.py +1 -1
  31. zou/app/blueprints/crud/preview_background_file.py +6 -7
  32. zou/app/blueprints/crud/preview_file.py +1 -1
  33. zou/app/blueprints/crud/project.py +14 -6
  34. zou/app/blueprints/crud/project_status.py +1 -1
  35. zou/app/blueprints/crud/schedule_item.py +4 -2
  36. zou/app/blueprints/crud/software.py +1 -1
  37. zou/app/blueprints/crud/status_automation.py +1 -1
  38. zou/app/blueprints/crud/studio.py +33 -0
  39. zou/app/blueprints/crud/task.py +47 -3
  40. zou/app/blueprints/crud/task_status.py +1 -1
  41. zou/app/blueprints/crud/task_type.py +4 -4
  42. zou/app/blueprints/crud/working_file.py +4 -8
  43. zou/app/blueprints/events/resources.py +13 -12
  44. zou/app/blueprints/export/csv/assets.py +15 -6
  45. zou/app/blueprints/export/csv/edits.py +15 -5
  46. zou/app/blueprints/export/csv/playlists.py +1 -1
  47. zou/app/blueprints/export/csv/shots.py +15 -5
  48. zou/app/blueprints/export/csv/time_spents.py +1 -1
  49. zou/app/blueprints/files/resources.py +22 -23
  50. zou/app/blueprints/index/resources.py +38 -29
  51. zou/app/blueprints/news/resources.py +25 -11
  52. zou/app/blueprints/persons/__init__.py +5 -2
  53. zou/app/blueprints/persons/resources.py +126 -120
  54. zou/app/blueprints/previews/__init__.py +18 -8
  55. zou/app/blueprints/previews/resources.py +569 -328
  56. zou/app/blueprints/projects/resources.py +1 -1
  57. zou/app/blueprints/search/resources.py +18 -6
  58. zou/app/blueprints/shots/__init__.py +5 -0
  59. zou/app/blueprints/shots/resources.py +134 -4
  60. zou/app/blueprints/source/__init__.py +6 -6
  61. zou/app/blueprints/source/csv/assets.py +10 -3
  62. zou/app/blueprints/source/csv/base.py +1 -1
  63. zou/app/blueprints/source/csv/edits.py +10 -3
  64. zou/app/blueprints/source/csv/shots.py +10 -3
  65. zou/app/blueprints/source/{edl.py → otio.py} +82 -41
  66. zou/app/blueprints/tasks/__init__.py +3 -2
  67. zou/app/blueprints/tasks/resources.py +83 -52
  68. zou/app/blueprints/user/__init__.py +9 -0
  69. zou/app/blueprints/user/resources.py +170 -12
  70. zou/app/config.py +10 -0
  71. zou/app/mixin.py +6 -5
  72. zou/app/models/attachment_file.py +10 -4
  73. zou/app/models/base.py +18 -13
  74. zou/app/models/build_job.py +7 -4
  75. zou/app/models/chat.py +44 -0
  76. zou/app/models/chat_message.py +37 -0
  77. zou/app/models/comment.py +1 -0
  78. zou/app/models/day_off.py +3 -0
  79. zou/app/models/entity.py +4 -6
  80. zou/app/models/entity_type.py +2 -0
  81. zou/app/models/organisation.py +14 -15
  82. zou/app/models/person.py +6 -1
  83. zou/app/models/project.py +3 -0
  84. zou/app/models/search_filter.py +11 -0
  85. zou/app/models/search_filter_group.py +10 -0
  86. zou/app/models/serializer.py +17 -17
  87. zou/app/models/status_automation.py +2 -0
  88. zou/app/models/studio.py +13 -0
  89. zou/app/models/subscription.py +2 -2
  90. zou/app/models/task.py +6 -1
  91. zou/app/models/task_status.py +1 -0
  92. zou/app/models/task_type.py +1 -0
  93. zou/app/models/working_file.py +1 -1
  94. zou/app/services/assets_service.py +101 -14
  95. zou/app/services/auth_service.py +17 -44
  96. zou/app/services/breakdown_service.py +37 -5
  97. zou/app/services/chats_service.py +279 -0
  98. zou/app/services/comments_service.py +110 -65
  99. zou/app/services/concepts_service.py +4 -12
  100. zou/app/services/deletion_service.py +43 -30
  101. zou/app/services/edits_service.py +5 -11
  102. zou/app/services/emails_service.py +4 -4
  103. zou/app/services/entities_service.py +17 -2
  104. zou/app/services/events_service.py +12 -4
  105. zou/app/services/exception.py +5 -5
  106. zou/app/services/names_service.py +7 -2
  107. zou/app/services/news_service.py +17 -9
  108. zou/app/services/persons_service.py +38 -21
  109. zou/app/services/playlists_service.py +8 -7
  110. zou/app/services/preview_files_service.py +137 -10
  111. zou/app/services/projects_service.py +5 -14
  112. zou/app/services/shots_service.py +221 -49
  113. zou/app/services/sync_service.py +46 -42
  114. zou/app/services/tasks_service.py +185 -46
  115. zou/app/services/time_spents_service.py +67 -20
  116. zou/app/services/user_service.py +350 -107
  117. zou/app/stores/auth_tokens_store.py +2 -1
  118. zou/app/stores/file_store.py +18 -0
  119. zou/app/stores/publisher_store.py +7 -7
  120. zou/app/stores/queue_store.py +1 -0
  121. zou/app/swagger.py +36 -20
  122. zou/app/utils/cache.py +2 -0
  123. zou/app/utils/commands.py +104 -7
  124. zou/app/utils/csv_utils.py +1 -4
  125. zou/app/utils/date_helpers.py +33 -17
  126. zou/app/utils/dbhelpers.py +14 -1
  127. zou/app/utils/emails.py +2 -2
  128. zou/app/utils/fido.py +22 -0
  129. zou/app/utils/query.py +54 -6
  130. zou/app/utils/redis.py +11 -0
  131. zou/app/utils/saml.py +51 -0
  132. zou/app/utils/string.py +2 -0
  133. zou/app/utils/thumbnail.py +4 -2
  134. zou/cli.py +76 -18
  135. zou/debug.py +4 -2
  136. zou/event_stream.py +122 -165
  137. zou/job_settings.py +1 -0
  138. zou/migrations/env.py +0 -0
  139. zou/migrations/utils/base.py +6 -6
  140. zou/migrations/versions/1bb55759146f_add_table_studio.py +67 -0
  141. zou/migrations/versions/1fab8c420678_add_attachments_to_message_chats.py +56 -0
  142. zou/migrations/versions/23122f290ca2_add_entity_chat_models.py +149 -0
  143. zou/migrations/versions/32f134ff1201_add_is_shared_flag_to_filters.py +33 -0
  144. zou/migrations/versions/57222395f2be_add_statusautomation_import_last_revision.py +41 -0
  145. zou/migrations/versions/59a7445a966c_add_entity_is_shared.py +41 -0
  146. zou/migrations/versions/5b980f0dc365_add_comment_links.py +35 -0
  147. zou/migrations/versions/680c64565f9d_for_searchfiltergroup_is_shared.py +35 -0
  148. zou/migrations/versions/8e67c183bed7_add_preference_fields.py +71 -0
  149. zou/migrations/versions/92b40d79ad3f_allow_message_attachments.py +38 -0
  150. zou/migrations/versions/971dbf5a0faf_add_short_name_for_asset_type_entity_.py +33 -0
  151. zou/migrations/versions/9b85c14fa8a7_add_day_off_new_columns.py +68 -0
  152. zou/migrations/versions/9d3bb33c6fc6_add_department_keys_to_filter_models.py +73 -0
  153. zou/migrations/versions/a252a094e977_add_descriptions_for_entities_tasks_and_.py +40 -0
  154. zou/migrations/versions/be56dc0fb760_for_is_shared_disallow_nullable.py +102 -0
  155. zou/migrations/versions/ca28796a2a62_add_is_done_field_to_the_task_model.py +108 -0
  156. zou/migrations/versions/f344b867a911_for_description_of_entity_task_working_.py +75 -0
  157. zou/remote/config_payload.py +2 -1
  158. zou/utils/movie.py +14 -4
  159. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/METADATA +75 -69
  160. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/RECORD +163 -134
  161. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/WHEEL +1 -1
  162. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/LICENSE +0 -0
  163. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/entry_points.txt +0 -0
  164. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/top_level.txt +0 -0
@@ -17,7 +17,7 @@ class WorkingFile(db.Model, BaseMixin, SerializerMixin):
17
17
  shotgun_id = db.Column(db.Integer(), index=True)
18
18
 
19
19
  name = db.Column(db.String(250))
20
- description = db.Column(db.String(200))
20
+ description = db.Column(db.Text())
21
21
  comment = db.Column(db.Text())
22
22
  revision = db.Column(db.Integer())
23
23
  size = db.Column(db.Integer())
@@ -40,7 +40,7 @@ from zou.app.services.exception import (
40
40
 
41
41
  def clear_asset_cache(asset_id):
42
42
  cache.cache.delete_memoized(get_asset, asset_id)
43
- cache.cache.delete_memoized(get_asset_with_relations, asset_id)
43
+ cache.cache.delete_memoized(get_asset, asset_id, True)
44
44
  cache.cache.delete_memoized(get_full_asset, asset_id)
45
45
 
46
46
 
@@ -84,7 +84,7 @@ def build_entity_type_asset_type_filter():
84
84
  return ~EntityType.id.in_(ids_to_exclude)
85
85
 
86
86
 
87
- def get_assets(criterions={}):
87
+ def get_assets(criterions={}, is_admin=False):
88
88
  """
89
89
  Get all assets for given criterions.
90
90
  """
@@ -102,6 +102,12 @@ def get_assets(criterions={}):
102
102
  query = query.outerjoin(Task)
103
103
  query = query.filter(user_service.build_assignee_filter())
104
104
 
105
+ if "is_shared" in criterions:
106
+ if not is_admin:
107
+ query = query.join(Project).filter(
108
+ user_service.build_team_filter()
109
+ )
110
+
105
111
  if episode_id is not None:
106
112
  # Filter based on main episode.
107
113
  query = query.filter(Entity.source_id == episode_id)
@@ -121,7 +127,7 @@ def get_assets(criterions={}):
121
127
  result += [a for a in query.all() if a.source_id != episode_id]
122
128
  else:
123
129
  result = query.all()
124
- return EntityType.serialize_list(result, obj_type="Asset")
130
+ return Entity.serialize_list(result, obj_type="Asset")
125
131
 
126
132
 
127
133
  def get_all_raw_assets():
@@ -194,7 +200,9 @@ def get_assets_and_tasks(criterions={}, page=1, with_episode_ids=False):
194
200
  Task.end_date,
195
201
  Task.start_date,
196
202
  Task.due_date,
203
+ Task.done_date,
197
204
  Task.last_comment_date,
205
+ Task.difficulty,
198
206
  assignees_table.columns.person,
199
207
  ).order_by(EntityType.name, Entity.name)
200
208
 
@@ -240,6 +248,18 @@ def get_assets_and_tasks(criterions={}, page=1, with_episode_ids=False):
240
248
  episode_links_query = episode_links_query.filter(
241
249
  Episode.project_id == criterions["project_id"]
242
250
  )
251
+ if "episode_id" in criterions and criterions["episode_id"] not in [
252
+ "main",
253
+ "all",
254
+ ]:
255
+ episode_links_query = episode_links_query.filter(
256
+ EntityLink.entity_in_id == criterions["episode_id"]
257
+ )
258
+ if "id" in criterions:
259
+ episode_links_query = episode_links_query.filter(
260
+ EntityLink.entity_out_id == criterions["id"]
261
+ )
262
+
243
263
  for link in episode_links_query.all():
244
264
  if str(link.entity_out_id) not in cast_in_episode_ids:
245
265
  cast_in_episode_ids[str(link.entity_out_id)] = []
@@ -272,7 +292,9 @@ def get_assets_and_tasks(criterions={}, page=1, with_episode_ids=False):
272
292
  task_end_date,
273
293
  task_start_date,
274
294
  task_due_date,
295
+ task_done_date,
275
296
  task_last_comment_date,
297
+ task_difficulty,
276
298
  person_id,
277
299
  ) in query_result:
278
300
  if asset.source_id is None:
@@ -304,6 +326,7 @@ def get_assets_and_tasks(criterions={}, page=1, with_episode_ids=False):
304
326
  "episode_id": source_id,
305
327
  "casting_episode_ids": cast_in_episode_ids.get(asset_id, []),
306
328
  "is_casting_standby": asset.is_casting_standby,
329
+ "is_shared": asset.is_shared,
307
330
  "data": data,
308
331
  "tasks": [],
309
332
  }
@@ -314,6 +337,7 @@ def get_assets_and_tasks(criterions={}, page=1, with_episode_ids=False):
314
337
  task_dict = {
315
338
  "id": task_id,
316
339
  "due_date": fields.serialize_value(task_due_date),
340
+ "done_date": fields.serialize_value(task_done_date),
317
341
  "duration": task_duration,
318
342
  "entity_id": asset_id,
319
343
  "estimation": task_estimation,
@@ -328,6 +352,7 @@ def get_assets_and_tasks(criterions={}, page=1, with_episode_ids=False):
328
352
  ),
329
353
  "retake_count": task_retake_count,
330
354
  "start_date": fields.serialize_value(task_start_date),
355
+ "difficulty": task_difficulty,
331
356
  "task_status_id": str(task_status_id),
332
357
  "task_type_id": str(task_type_id),
333
358
  "assignees": [],
@@ -403,19 +428,13 @@ def get_asset_raw(entity_id):
403
428
 
404
429
 
405
430
  @cache.memoize_function(120)
406
- def get_asset(entity_id):
407
- """
408
- Return a given asset as a dict.
409
- """
410
- return get_asset_raw(entity_id).serialize(obj_type="Asset")
411
-
412
-
413
- @cache.memoize_function(120)
414
- def get_asset_with_relations(entity_id):
431
+ def get_asset(entity_id, relations=False):
415
432
  """
416
433
  Return a given asset as a dict.
417
434
  """
418
- return get_asset_raw(entity_id).serialize(obj_type="Asset", relations=True)
435
+ return get_asset_raw(entity_id).serialize(
436
+ obj_type="Asset", relations=relations
437
+ )
419
438
 
420
439
 
421
440
  def get_asset_by_shotgun_id(shotgun_id):
@@ -445,7 +464,7 @@ def get_full_asset(asset_id):
445
464
  """
446
465
  assets = get_assets_and_tasks({"id": asset_id}, with_episode_ids=True)
447
466
  if len(assets) > 0:
448
- asset = get_asset_with_relations(asset_id)
467
+ asset = get_asset(asset_id, relations=True)
449
468
  asset_type_id = asset["entity_type_id"]
450
469
  asset_type = get_asset_type(asset_type_id)
451
470
  project = Project.get(asset["project_id"])
@@ -568,6 +587,7 @@ def create_asset(
568
587
  name,
569
588
  description,
570
589
  data,
590
+ is_shared=False,
571
591
  source_id=None,
572
592
  created_by=None,
573
593
  ):
@@ -584,6 +604,7 @@ def create_asset(
584
604
  name=name,
585
605
  description=description,
586
606
  data=data,
607
+ is_shared=is_shared,
587
608
  source_id=source_id,
588
609
  created_by=created_by,
589
610
  )
@@ -703,3 +724,69 @@ def cancel_asset(asset_id, force=True):
703
724
  project_id=str(asset.project_id),
704
725
  )
705
726
  return asset_dict
727
+
728
+
729
+ def set_shared_assets(
730
+ is_shared=True,
731
+ project_id=None,
732
+ asset_type_id=None,
733
+ asset_ids=None,
734
+ with_events=False,
735
+ ):
736
+ """
737
+ Set all assets of a project to is_shared=True or False.
738
+ """
739
+
740
+ query = Entity.query.filter(build_asset_type_filter()).filter()
741
+
742
+ if project_id is not None:
743
+ query = query.filter(Entity.project_id == project_id)
744
+
745
+ if asset_type_id is not None:
746
+ query = query.filter(Entity.entity_type_id == asset_type_id)
747
+
748
+ if asset_ids is not None:
749
+ query = query.filter(Entity.id.in_(asset_ids))
750
+
751
+ assets = query.all()
752
+
753
+ for asset in assets:
754
+ asset.update_no_commit({"is_shared": is_shared})
755
+
756
+ Entity.commit()
757
+
758
+ for asset in assets:
759
+ asset_id = str(asset.id)
760
+ clear_asset_cache(asset_id)
761
+ if with_events:
762
+ events.emit(
763
+ "asset:update",
764
+ {"asset_id": asset_id},
765
+ project_id=project_id,
766
+ )
767
+
768
+ return Entity.serialize_list(assets, obj_type="Asset")
769
+
770
+
771
+ def get_shared_assets_used_in_project(project_id, episode_id=None):
772
+ """
773
+ Get all shared assets used in a project.
774
+ """
775
+ Shot = aliased(Entity, name="shot")
776
+ Sequence = aliased(Entity, name="sequence")
777
+
778
+ assets = (
779
+ Entity.query.filter(build_asset_type_filter())
780
+ .filter(Entity.is_shared == True)
781
+ .join(EntityLink, EntityLink.entity_out_id == Entity.id)
782
+ .join(Shot, EntityLink.entity_in_id == Shot.id)
783
+ .join(Sequence, Shot.parent_id == Sequence.id)
784
+ .filter(Shot.project_id == project_id)
785
+ .filter(Entity.canceled != True)
786
+ .filter(Entity.project_id != project_id)
787
+ )
788
+
789
+ if episode_id is not None and episode_id not in ["main", "all"]:
790
+ assets = assets.filter(Sequence.parent_id == episode_id)
791
+
792
+ return Entity.serialize_list(assets.all(), obj_type="Asset")
@@ -2,13 +2,12 @@ import pyotp
2
2
  import random
3
3
  import string
4
4
  import flask_bcrypt
5
- import fido2.features
6
- from datetime import datetime, timedelta
5
+
6
+ from datetime import timedelta
7
7
 
8
8
  from flask import request, session, current_app
9
9
  from babel.dates import format_datetime
10
10
 
11
- from flask_jwt_extended import get_jti
12
11
  from ldap3 import Server, Connection, ALL, NTLM, SIMPLE
13
12
  from ldap3.core.exceptions import (
14
13
  LDAPSocketOpenError,
@@ -37,29 +36,15 @@ from zou.app.services.exception import (
37
36
  )
38
37
  from zou.app.stores import auth_tokens_store
39
38
  from zou.app.utils import date_helpers, emails
40
- from zou.app import config
41
39
 
42
40
  from fido2.webauthn import (
43
- PublicKeyCredentialRpEntity,
44
41
  PublicKeyCredentialUserEntity,
45
42
  )
46
- from fido2.server import Fido2Server
47
43
  from sqlalchemy.orm.attributes import flag_modified
48
- from urllib.parse import urlparse
44
+
49
45
  from fido2.utils import bytes2int, int2bytes
50
46
  from fido2.webauthn import AttestedCredentialData
51
47
 
52
- fido2.features.webauthn_json_mapping.enabled = True
53
-
54
- fido_server = Fido2Server(
55
- PublicKeyCredentialRpEntity(
56
- name="Kitsu", id=urlparse(f"https://{config.DOMAIN_NAME}").hostname
57
- ),
58
- verify_origin=(
59
- None if config.DOMAIN_NAME != "localhost:8080" else lambda a: True
60
- ),
61
- )
62
-
63
48
 
64
49
  def check_auth(
65
50
  app,
@@ -103,7 +88,9 @@ def check_auth(
103
88
  raise NoAuthStrategyConfigured()
104
89
  except WrongPasswordException:
105
90
  update_login_failed_attemps(
106
- person["id"], login_failed_attemps + 1, datetime.utcnow()
91
+ person["id"],
92
+ login_failed_attemps + 1,
93
+ date_helpers.get_utc_now_datetime(),
107
94
  )
108
95
  raise WrongPasswordException()
109
96
 
@@ -116,7 +103,9 @@ def check_auth(
116
103
  recovery_code,
117
104
  ):
118
105
  update_login_failed_attemps(
119
- person["id"], login_failed_attemps + 1, datetime.utcnow()
106
+ person["id"],
107
+ login_failed_attemps + 1,
108
+ date_helpers.get_utc_now_datetime(),
120
109
  )
121
110
  raise WrongOTPException()
122
111
 
@@ -341,7 +330,7 @@ def check_fido(person, authentication_response):
341
330
  except KeyError:
342
331
  return False
343
332
  try:
344
- fido_server.authenticate_complete(
333
+ current_app.extensions["fido_server"].authenticate_complete(
345
334
  state,
346
335
  get_fido_attested_credential_data_from_person(
347
336
  person["fido_credentials"],
@@ -488,7 +477,7 @@ def send_email_otp(person):
488
477
  )
489
478
  organisation = persons_service.get_organisation()
490
479
  time_string = format_datetime(
491
- datetime.utcnow(),
480
+ date_helpers.get_utc_now_datetime(),
492
481
  tzinfo=person["timezone"],
493
482
  locale=person["locale"],
494
483
  )
@@ -547,7 +536,7 @@ def pre_register_fido(person_id):
547
536
  Pre-register FIDO device for a person.
548
537
  """
549
538
  person = Person.get(person_id)
550
- options, state = fido_server.register_begin(
539
+ options, state = current_app.extensions["fido_server"].register_begin(
551
540
  PublicKeyCredentialUserEntity(
552
541
  id=str(person.id).encode(),
553
542
  name=person.email,
@@ -573,7 +562,9 @@ def register_fido(person_id, registration_response, device_name):
573
562
  except KeyError:
574
563
  raise FIDONoPreregistrationException()
575
564
  try:
576
- auth_data = fido_server.register_complete(state, registration_response)
565
+ auth_data = current_app.extensions["fido_server"].register_complete(
566
+ state, registration_response
567
+ )
577
568
  except BaseException:
578
569
  raise FIDOServerException()
579
570
  credential_data = {
@@ -634,7 +625,7 @@ def get_challenge_fido(person_id):
634
625
  Get new FIDO challenge for a person.
635
626
  """
636
627
  person = Person.get(person_id)
637
- options, state = fido_server.authenticate_begin(
628
+ options, state = current_app.extensions["fido_server"].authenticate_begin(
638
629
  credentials=get_fido_attested_credential_data_from_person(
639
630
  person.fido_credentials
640
631
  ),
@@ -673,24 +664,6 @@ def generate_new_recovery_codes(person_id):
673
664
  return otp_recovery_codes
674
665
 
675
666
 
676
- def register_tokens(app, access_token, refresh_token=None):
677
- """
678
- Register access and refresh tokens to auth token store. That way they
679
- can be used like a session.
680
- """
681
- access_jti = get_jti(encoded_token=access_token)
682
-
683
- auth_tokens_store.add(
684
- access_jti, "false", app.config["JWT_ACCESS_TOKEN_EXPIRES"]
685
- )
686
-
687
- if refresh_token is not None:
688
- refresh_jti = get_jti(encoded_token=refresh_token)
689
- auth_tokens_store.add(
690
- refresh_jti, "false", app.config["JWT_REFRESH_TOKEN_EXPIRES"]
691
- )
692
-
693
-
694
667
  def revoke_tokens(app, jti):
695
668
  """
696
669
  Remove access and refresh tokens from auth token store.
@@ -754,7 +727,7 @@ def check_login_failed_attemps(person):
754
727
  login_failed_attemps >= 5
755
728
  and date_helpers.get_datetime_from_string(person["last_login_failed"])
756
729
  + timedelta(minutes=1)
757
- > datetime.utcnow()
730
+ > date_helpers.get_utc_now_datetime()
758
731
  ):
759
732
  raise TooMuchLoginFailedAttemps()
760
733
  return login_failed_attemps
@@ -50,6 +50,8 @@ def get_casting(shot_id):
50
50
  Entity.preview_file_id,
51
51
  Entity.source_id,
52
52
  Entity.ready_for,
53
+ Entity.is_shared,
54
+ Entity.project_id,
53
55
  )
54
56
  .order_by(EntityType.name, Entity.name)
55
57
  )
@@ -61,6 +63,8 @@ def get_casting(shot_id):
61
63
  entity_preview_file_id,
62
64
  episode_id,
63
65
  entity_ready_for,
66
+ entity_is_shared,
67
+ entity_project_id,
64
68
  ) in links:
65
69
  casting.append(
66
70
  {
@@ -74,6 +78,8 @@ def get_casting(shot_id):
74
78
  ),
75
79
  "nb_occurences": link.nb_occurences,
76
80
  "label": link.label,
81
+ "is_shared": entity_is_shared,
82
+ "project_id": entity_project_id,
77
83
  }
78
84
  )
79
85
  return casting
@@ -93,11 +99,27 @@ def get_production_episodes_casting(project_id):
93
99
  .join(EntityType, Entity.entity_type_id == EntityType.id)
94
100
  .filter(Episode.project_id == project_id)
95
101
  .filter(Entity.canceled != True)
96
- .add_columns(Entity.name, EntityType.name, Entity.preview_file_id)
97
- .order_by(EntityType.name, Entity.name)
102
+ .add_columns(
103
+ Entity.name,
104
+ EntityType.name,
105
+ Entity.preview_file_id,
106
+ Entity.is_shared,
107
+ Entity.project_id,
108
+ )
109
+ .order_by(
110
+ EntityType.name,
111
+ Entity.name,
112
+ )
98
113
  )
99
114
 
100
- for link, entity_name, entity_type_name, entity_preview_file_id in links:
115
+ for (
116
+ link,
117
+ entity_name,
118
+ entity_type_name,
119
+ entity_preview_file_id,
120
+ entity_is_shared,
121
+ entity_project_id,
122
+ ) in links:
101
123
  episode_id = str(link.entity_in_id)
102
124
  if episode_id not in castings:
103
125
  castings[episode_id] = []
@@ -112,6 +134,8 @@ def get_production_episodes_casting(project_id):
112
134
  ),
113
135
  "nb_occurences": link.nb_occurences,
114
136
  "label": link.label,
137
+ "is_shared": entity_is_shared,
138
+ "project_id": entity_project_id,
115
139
  }
116
140
  )
117
141
  return castings
@@ -139,6 +163,8 @@ def get_sequence_casting(sequence_id, project_id=None, episode_id=None):
139
163
  Entity.name,
140
164
  EntityType.name,
141
165
  Entity.preview_file_id,
166
+ Entity.is_shared,
167
+ Entity.project_id,
142
168
  Sequence.name,
143
169
  )
144
170
  )
@@ -163,6 +189,8 @@ def get_sequence_casting(sequence_id, project_id=None, episode_id=None):
163
189
  entity_name,
164
190
  entity_type_name,
165
191
  entity_preview_file_id,
192
+ entity_is_shared,
193
+ entity_project_id,
166
194
  sequence_name,
167
195
  ) in links:
168
196
  shot_id = str(link.entity_in_id)
@@ -180,6 +208,8 @@ def get_sequence_casting(sequence_id, project_id=None, episode_id=None):
180
208
  ),
181
209
  "nb_occurences": link.nb_occurences,
182
210
  "label": link.label,
211
+ "is_shared": entity_is_shared,
212
+ "project_id": entity_project_id,
183
213
  }
184
214
  )
185
215
  return castings
@@ -384,7 +414,7 @@ def _create_episode_casting_link(entity, asset_id, nb_occurences=1, label=""):
384
414
  link = EntityLink.create(
385
415
  entity_in_id=sequence["parent_id"],
386
416
  entity_out_id=asset_id,
387
- nb_occurences=1,
417
+ nb_occurences=nb_occurences,
388
418
  label=label,
389
419
  )
390
420
  events.emit(
@@ -799,7 +829,9 @@ def _get_task_type_priority_map(project_id):
799
829
 
800
830
  def _is_asset_ready(asset, task, priority_map):
801
831
  is_ready = False
802
- if "ready_for" in asset and asset["ready_for"] is not None:
832
+ if asset["is_shared"] and asset["project_id"] != str(task.project_id):
833
+ is_ready = True
834
+ elif "ready_for" in asset and asset["ready_for"] is not None:
803
835
  priority_ready = priority_map.get(asset["ready_for"], -1) or -1
804
836
  priority_task = priority_map.get(str(task.task_type_id), 0) or 0
805
837
  is_ready = priority_task <= priority_ready