zou 0.19.58__py3-none-any.whl → 0.19.60__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.58"
1
+ __version__ = "0.19.60"
@@ -1369,7 +1369,9 @@ class SAMLSSOResource(Resource, ArgsMixin):
1369
1369
  user = persons_service.get_person_by_email(email)
1370
1370
  for k, v in person_info.items():
1371
1371
  if user.get(k) != v:
1372
- persons_service.update_person(user["id"], person_info)
1372
+ persons_service.update_person(
1373
+ user["id"], person_info, bypass_protected_accounts=True
1374
+ )
1373
1375
  break
1374
1376
  except PersonNotFoundException:
1375
1377
  user = persons_service.create_person(
@@ -203,7 +203,10 @@ class PersonResource(BaseModelResource, ArgsMixin):
203
203
  and persons_service.is_user_limit_reached()
204
204
  ):
205
205
  raise WrongParameterException("User limit reached.")
206
- if instance_dict["email"] in config.PROTECTED_ACCOUNTS:
206
+ if (
207
+ instance_dict["email"] in config.PROTECTED_ACCOUNTS
208
+ and instance_dict["id"] != persons_service.get_current_user()["id"]
209
+ ):
207
210
  message = None
208
211
  if data.get("active") is False:
209
212
  message = "Can't set this person as inactive it's a protected account."
@@ -9,7 +9,7 @@ from zou import __version__
9
9
 
10
10
  from zou.app import app, config
11
11
  from zou.app.utils import permissions, shell, date_helpers
12
- from zou.app.services import projects_service, stats_service
12
+ from zou.app.services import projects_service, stats_service, persons_service
13
13
 
14
14
  from flask_jwt_extended import jwt_required
15
15
 
@@ -276,17 +276,21 @@ class StatsResource(Resource):
276
276
  class ConfigResource(Resource):
277
277
  def get(self):
278
278
  """
279
- Get crisp token.
279
+ Get basic configuration for the current instance.
280
280
  ---
281
281
  tags:
282
282
  - Index
283
283
  responses:
284
284
  200:
285
- description: Crisp token
285
+ description: Configuration object including self-hosted status,
286
+ Crisp token, indexer configuration, SAML status, and dark theme
287
+ status
286
288
  """
289
+ organisation = persons_service.get_organisation()
287
290
  conf = {
288
291
  "is_self_hosted": config.IS_SELF_HOSTED,
289
292
  "crisp_token": config.CRISP_TOKEN,
293
+ "dark_theme_by_default": organisation["dark_theme_by_default"],
290
294
  "indexer_configured": (
291
295
  len(config.INDEXER["key"]) > 0
292
296
  and config.INDEXER["key"] != "masterkey"
@@ -1255,7 +1255,10 @@ class ChangePasswordForPersonResource(Resource, ArgsMixin):
1255
1255
  permissions.check_admin_permissions()
1256
1256
  try:
1257
1257
  person = persons_service.get_person(person_id)
1258
- if person["email"] in config.PROTECTED_ACCOUNTS:
1258
+ if (
1259
+ person["email"] in config.PROTECTED_ACCOUNTS
1260
+ and person["id"] != persons_service.get_current_user()["id"]
1261
+ ):
1259
1262
  raise PersonInProtectedAccounts()
1260
1263
  current_user = persons_service.get_current_user()
1261
1264
  auth.validate_password(password, password_2)
@@ -1023,6 +1023,11 @@ class PersonThumbnailResource(BaseThumbnailResource):
1023
1023
  if not is_current_user and not permissions.has_admin_permissions():
1024
1024
  raise permissions.PermissionDenied
1025
1025
 
1026
+ def prepare_creation(self, instance_id):
1027
+ self.model = self.update_model_func(
1028
+ instance_id, {"has_avatar": True}, bypass_protected_accounts=True
1029
+ )
1030
+
1026
1031
 
1027
1032
  class CreatePersonThumbnailResource(PersonThumbnailResource):
1028
1033
  pass
@@ -544,6 +544,7 @@ class FiltersResource(Resource, ArgsMixin):
544
544
  arguments["entity_type"],
545
545
  arguments["is_shared"],
546
546
  arguments["search_filter_group_id"],
547
+ department_id=arguments["department_id"],
547
548
  ),
548
549
  201,
549
550
  )
@@ -558,6 +559,7 @@ class FiltersResource(Resource, ArgsMixin):
558
559
  ("entity_type", None, False),
559
560
  ("is_shared", False, False, inputs.boolean),
560
561
  ("search_filter_group_id", None, False),
562
+ ("department_id", None, False),
561
563
  ]
562
564
  )
563
565
 
@@ -591,6 +593,7 @@ class FilterResource(Resource, ArgsMixin):
591
593
  ("search_filter_group_id", None, False),
592
594
  ("is_shared", None, False, inputs.boolean),
593
595
  ("project_id", None, None),
596
+ ("department_id", None, None),
594
597
  ]
595
598
  )
596
599
  data = self.clear_empty_fields(
@@ -663,19 +666,27 @@ class FilterGroupsResource(Resource, ArgsMixin):
663
666
  name: entity_type
664
667
  required: False
665
668
  type: string
669
+ - in: formData
670
+ name: is_shared
671
+ required: False
672
+ type: boolean
666
673
  - in: formData
667
674
  name: project_id
668
675
  required: True
669
676
  type: string
670
677
  format: UUID
671
678
  example: a24a6ea4-ce75-4665-a070-57453082c25
679
+ - in: formData
680
+ name: department_id
681
+ required: False
682
+ format: UUID
683
+ example: a24a6ea4-ce75-4665-a070-57453082c25
672
684
  responses:
673
685
  201:
674
686
  description: Filter groups for the current user and only for
675
687
  open projects created.
676
688
  """
677
689
  arguments = self.get_arguments()
678
-
679
690
  return (
680
691
  user_service.create_filter_group(
681
692
  arguments["list_type"],
@@ -684,6 +695,7 @@ class FilterGroupsResource(Resource, ArgsMixin):
684
695
  arguments["project_id"],
685
696
  arguments["entity_type"],
686
697
  arguments["is_shared"],
698
+ arguments["department_id"],
687
699
  ),
688
700
  201,
689
701
  )
@@ -697,6 +709,7 @@ class FilterGroupsResource(Resource, ArgsMixin):
697
709
  ("project_id", None, False),
698
710
  ("is_shared", False, False, inputs.boolean),
699
711
  ("entity_type", None, False),
712
+ ("department_id", None, False),
700
713
  ]
701
714
  )
702
715
 
@@ -733,6 +746,28 @@ class FilterGroupResource(Resource, ArgsMixin):
733
746
  type: string
734
747
  format: UUID
735
748
  x-example: a24a6ea4-ce75-4665-a070-57453082c25
749
+ - in: formData
750
+ name: name
751
+ type: string
752
+ x-example: Name of the filter group
753
+ - in: formData
754
+ name: color
755
+ type: string
756
+ x-example: Color of the filter group
757
+ - in: formData
758
+ name: is_shared
759
+ type: boolean
760
+ x-example: True
761
+ - in: formData
762
+ name: project_id
763
+ type: string
764
+ format: UUID
765
+ x-example: a24a6ea4-ce75-4665-a070-57453082c25
766
+ - in: formData
767
+ name: department_id
768
+ type: string
769
+ format: UUID
770
+ x-example: a24a6ea4-ce75-4665-a070-57453082c25
736
771
  responses:
737
772
  200:
738
773
  description: Given filter group with updated data
@@ -743,10 +778,12 @@ class FilterGroupResource(Resource, ArgsMixin):
743
778
  ("color", None, False),
744
779
  ("is_shared", None, False, inputs.boolean),
745
780
  ("project_id", None, None),
781
+ ("department_id", None, None),
746
782
  ]
747
783
  )
748
784
 
749
785
  data = self.clear_empty_fields(data)
786
+ print("data", data)
750
787
  user_filter = user_service.update_filter_group(filter_group_id, data)
751
788
  return user_filter, 200
752
789
 
zou/app/config.py CHANGED
@@ -81,7 +81,8 @@ MAIL_SERVER = os.getenv("MAIL_SERVER", "localhost")
81
81
  MAIL_PORT = os.getenv("MAIL_PORT", 25)
82
82
  MAIL_USERNAME = os.getenv("MAIL_USERNAME", "")
83
83
  MAIL_PASSWORD = os.getenv("MAIL_PASSWORD", "")
84
- MAIL_DEBUG = envtobool("MAIL_DEBUG", False)
84
+ MAIL_DEBUG = int(os.getenv("MAIL_DEBUG", "0"))
85
+ MAIL_DEBUG_BODY = envtobool("MAIL_DEBUG_BODY", False)
85
86
  MAIL_USE_TLS = envtobool("MAIL_USE_TLS", False)
86
87
  MAIL_USE_SSL = envtobool("MAIL_USE_SSL", False)
87
88
  MAIL_DEFAULT_SENDER = os.getenv(
@@ -23,6 +23,10 @@ class SearchFilter(db.Model, BaseMixin, SerializerMixin):
23
23
  nullable=False,
24
24
  )
25
25
 
26
+ department_id = db.Column(
27
+ UUIDType(binary=False),
28
+ db.ForeignKey("department.id"),
29
+ )
26
30
  search_filter_group_id = db.Column(
27
31
  UUIDType(binary=False),
28
32
  db.ForeignKey("search_filter_group.id"),
@@ -24,3 +24,6 @@ class SearchFilterGroup(db.Model, BaseMixin, SerializerMixin):
24
24
 
25
25
  person_id = db.Column(UUIDType(binary=False), db.ForeignKey("person.id"))
26
26
  project_id = db.Column(UUIDType(binary=False), db.ForeignKey("project.id"))
27
+ department_id = db.Column(
28
+ UUIDType(binary=False), db.ForeignKey("department.id")
29
+ )
@@ -381,7 +381,9 @@ def update_person_last_presence(person_id):
381
381
  date = log.date
382
382
  elif time_spent is not None:
383
383
  date = time_spent.date
384
- return update_person(person_id, {"last_presence": date})
384
+ return update_person(
385
+ person_id, {"last_presence": date}, bypass_protected_accounts=True
386
+ )
385
387
 
386
388
 
387
389
  def get_presence_logs(year, month):
@@ -889,20 +889,30 @@ def get_user_filters(current_user_id):
889
889
  .all()
890
890
  )
891
891
 
892
+ current_user = persons_service.get_current_user(relations=True)
893
+ is_manager = permissions.has_manager_permissions()
894
+
892
895
  for search_filter in filters:
893
- if search_filter.list_type not in result:
894
- result[search_filter.list_type] = {}
895
- subresult = result[search_filter.list_type]
896
+ department_id = search_filter.department_id
897
+ is_in_departments = (
898
+ department_id is not None and \
899
+ str(department_id) in current_user["departments"]
900
+ )
896
901
 
897
- if search_filter.project_id is None:
898
- project_id = "all"
899
- else:
900
- project_id = str(search_filter.project_id)
902
+ if department_id is None or is_manager or is_in_departments:
903
+ if search_filter.list_type not in result:
904
+ result[search_filter.list_type] = {}
905
+ subresult = result[search_filter.list_type]
906
+
907
+ if search_filter.project_id is None:
908
+ project_id = "all"
909
+ else:
910
+ project_id = str(search_filter.project_id)
901
911
 
902
- if project_id not in subresult:
903
- subresult[project_id] = []
912
+ if project_id not in subresult:
913
+ subresult[project_id] = []
904
914
 
905
- subresult[project_id].append(search_filter.serialize())
915
+ subresult[project_id].append(search_filter.serialize())
906
916
 
907
917
  return result
908
918
 
@@ -915,6 +925,7 @@ def create_filter(
915
925
  entity_type=None,
916
926
  is_shared=False,
917
927
  search_filter_group_id=None,
928
+ department_id=None,
918
929
  ):
919
930
  """
920
931
  Add a new search filter to the database.
@@ -937,6 +948,13 @@ def create_filter(
937
948
  "A search filter should have the same value for is_shared than its search filter group."
938
949
  )
939
950
 
951
+ if department_id is not None:
952
+ department = tasks_service.get_department(department_id)
953
+ if department is None:
954
+ raise WrongParameterException(
955
+ f"No department found with id: {department_id}"
956
+ )
957
+
940
958
  search_filter = SearchFilter.create(
941
959
  list_type=list_type,
942
960
  name=name,
@@ -946,6 +964,7 @@ def create_filter(
946
964
  entity_type=entity_type,
947
965
  is_shared=is_shared,
948
966
  search_filter_group_id=search_filter_group_id,
967
+ department_id=department_id,
949
968
  )
950
969
  search_filter.serialize()
951
970
  if search_filter.is_shared:
@@ -969,10 +988,23 @@ def update_filter(search_filter_id, data):
969
988
  if search_filter is None:
970
989
  raise SearchFilterNotFoundException
971
990
 
972
- if data.get("project_id", None) is None or (
973
- data["project_id"] is not None
974
- and not has_manager_project_access(data["project_id"])
975
- ):
991
+ department_id = data.get("department_id", None)
992
+ if department_id is not None:
993
+ department = tasks_service.get_department(department_id)
994
+ if department is None:
995
+ raise WrongParameterException(
996
+ f"No department found with id: {department_id}"
997
+ )
998
+
999
+ if data.get("is_shared", None) is not None and \
1000
+ search_filter.is_shared != data["is_shared"] and \
1001
+ (
1002
+ data.get("project_id", None) is None or
1003
+ (
1004
+ data["project_id"] is not None
1005
+ and not has_manager_project_access(data["project_id"])
1006
+ )
1007
+ ):
976
1008
  data["is_shared"] = False
977
1009
 
978
1010
  if (
@@ -992,6 +1024,15 @@ def update_filter(search_filter_id, data):
992
1024
  raise WrongParameterException(
993
1025
  "A search filter should have the same value for is_shared than its search filter group."
994
1026
  )
1027
+
1028
+ department_id = data.get("department_id", None)
1029
+ if department_id is not None:
1030
+ department = tasks_service.get_department(department_id)
1031
+ if department is None:
1032
+ raise WrongParameterException(
1033
+ f"No department found with id: {department_id}"
1034
+ )
1035
+
995
1036
  search_filter.update(data)
996
1037
  if search_filter.is_shared:
997
1038
  clear_filter_cache()
@@ -1028,7 +1069,7 @@ def get_filter_groups():
1028
1069
  return get_user_filter_groups(current_user["id"])
1029
1070
 
1030
1071
 
1031
- @cache.memoize_function(120)
1072
+ @cache.memoize_function(10)
1032
1073
  def get_user_filter_groups(current_user_id):
1033
1074
  """
1034
1075
  Retrieve search filter groups used for given user. It groups them by
@@ -1055,26 +1096,41 @@ def get_user_filter_groups(current_user_id):
1055
1096
  .all()
1056
1097
  )
1057
1098
 
1099
+ current_user = persons_service.get_current_user(relations=True)
1100
+ is_manager = permissions.has_manager_permissions()
1101
+
1058
1102
  for search_filter_group in filter_groups:
1059
1103
  if search_filter_group.list_type not in result:
1060
1104
  result[search_filter_group.list_type] = {}
1061
- subresult = result[search_filter_group.list_type]
1062
1105
 
1063
- if search_filter_group.project_id is None:
1064
- project_id = "all"
1065
- else:
1066
- project_id = str(search_filter_group.project_id)
1106
+ department_id = search_filter_group.department_id
1107
+ is_in_departments = (
1108
+ department_id is not None and \
1109
+ str(department_id) in current_user["departments"]
1110
+ )
1111
+ if department_id is None or is_manager or is_in_departments:
1112
+ subresult = result[search_filter_group.list_type]
1067
1113
 
1068
- if project_id not in subresult:
1069
- subresult[project_id] = []
1114
+ if search_filter_group.project_id is None:
1115
+ project_id = "all"
1116
+ else:
1117
+ project_id = str(search_filter_group.project_id)
1070
1118
 
1071
- subresult[project_id].append(search_filter_group.serialize())
1119
+ if project_id not in subresult:
1120
+ subresult[project_id] = []
1121
+ subresult[project_id].append(search_filter_group.serialize())
1072
1122
 
1073
1123
  return result
1074
1124
 
1075
1125
 
1076
1126
  def create_filter_group(
1077
- list_type, name, color, project_id=None, entity_type=None, is_shared=False
1127
+ list_type,
1128
+ name,
1129
+ color,
1130
+ project_id=None,
1131
+ entity_type=None,
1132
+ is_shared=False,
1133
+ department_id=None
1078
1134
  ):
1079
1135
  """
1080
1136
  Add a new search filter group to the database.
@@ -1084,6 +1140,14 @@ def create_filter_group(
1084
1140
  project_id is not None and not has_manager_project_access(project_id)
1085
1141
  ):
1086
1142
  is_shared = False
1143
+
1144
+ if department_id is not None:
1145
+ department = tasks_service.get_department(department_id)
1146
+ if department is None:
1147
+ raise WrongParameterException(
1148
+ f"No department found with id: {department_id}"
1149
+ )
1150
+
1087
1151
  search_filter_group = SearchFilterGroup.create(
1088
1152
  list_type=list_type,
1089
1153
  name=name,
@@ -1092,12 +1156,15 @@ def create_filter_group(
1092
1156
  person_id=current_user["id"],
1093
1157
  entity_type=entity_type,
1094
1158
  is_shared=is_shared,
1159
+ department_id=department_id,
1095
1160
  )
1096
1161
  search_filter_group.serialize()
1162
+
1097
1163
  if search_filter_group.is_shared:
1098
1164
  clear_filter_group_cache()
1099
1165
  else:
1100
1166
  clear_filter_group_cache(current_user["id"])
1167
+
1101
1168
  return search_filter_group.serialize()
1102
1169
 
1103
1170
 
@@ -1122,16 +1189,26 @@ def update_filter_group(search_filter_group_id, data):
1122
1189
  search_filter_group = SearchFilterGroup.get_by(
1123
1190
  id=search_filter_group_id, person_id=current_user["id"]
1124
1191
  )
1192
+
1125
1193
  if search_filter_group is None:
1126
1194
  raise SearchFilterGroupNotFoundException
1127
- if data.get("project_id", None) is None or (
1128
- data["project_id"] is not None
1129
- and not has_manager_project_access(data["project_id"])
1130
- ):
1131
- data["is_shared"] = False
1195
+
1196
+ if data.get("is_shared", None) is not None and \
1197
+ search_filter_group.is_shared != data["is_shared"] and \
1198
+ (
1199
+ data.get("project_id", None) is None or
1200
+ (
1201
+ data["project_id"] is not None
1202
+ and not has_manager_project_access(data["project_id"])
1203
+ )
1204
+ ):
1205
+ data["is_shared"] = False
1206
+
1132
1207
  search_filter_group.update(data)
1133
1208
 
1134
- if data.get("is_shared", None) is not None:
1209
+ if data.get("is_shared", None) is not None \
1210
+ and data.get("project_id", None) is not None \
1211
+ and has_manager_project_access(data["project_id"]):
1135
1212
  if (
1136
1213
  SearchFilter.query.filter_by(
1137
1214
  search_filter_group_id=search_filter_group_id
@@ -1284,7 +1361,6 @@ def get_last_notifications(
1284
1361
  query = query.filter(Notification.read == read)
1285
1362
 
1286
1363
  if watching is not None:
1287
- print(watching)
1288
1364
  if watching:
1289
1365
  query = query.filter(Subscription.id != None)
1290
1366
  else:
zou/app/utils/commands.py CHANGED
@@ -394,7 +394,9 @@ def sync_with_ldap_server():
394
394
  )
395
395
  .all()
396
396
  ):
397
- persons_service.update_person(person.id, {"active": False})
397
+ persons_service.update_person(
398
+ person.id, {"active": False}, bypass_protected_accounts=True
399
+ )
398
400
  print(
399
401
  "User %s disabled (not found in LDAP)." % person.desktop_login
400
402
  )
@@ -418,6 +420,7 @@ def sync_with_ldap_server():
418
420
  "desktop_login": user["desktop_login"],
419
421
  "ldap_uid": user["ldap_uid"],
420
422
  },
423
+ bypass_protected_accounts=True,
421
424
  )
422
425
  print(f"User {user['desktop_login']} updated.")
423
426
  except IsUserLimitReachedException:
@@ -470,7 +473,9 @@ def sync_with_ldap_server():
470
473
  )
471
474
  file_store.add_picture("thumbnails", person["id"], thumbnail_png_path)
472
475
  os.remove(thumbnail_png_path)
473
- persons_service.update_person(person["id"], {"has_avatar": True})
476
+ persons_service.update_person(
477
+ person["id"], {"has_avatar": True}, bypass_protected_accounts=True
478
+ )
474
479
 
475
480
  ldap_users = get_ldap_users()
476
481
  update_person_list_with_ldap_users(ldap_users)
zou/app/utils/emails.py CHANGED
@@ -12,9 +12,9 @@ def send_email(subject, html, recipient_email, body=None):
12
12
  """
13
13
  if body is None:
14
14
  body = strip_html_tags(html)
15
- if app.config["MAIL_DEBUG"]:
15
+ if app.config["MAIL_DEBUG_BODY"]:
16
16
  print(body)
17
- elif app.config["MAIL_ENABLED"]:
17
+ if app.config["MAIL_ENABLED"]:
18
18
  with app.app_context():
19
19
  try:
20
20
  mail_default_sender = app.config["MAIL_DEFAULT_SENDER"]
zou/app/utils/saml.py CHANGED
@@ -15,8 +15,9 @@ def saml_client_for(metadata_url):
15
15
  Given the name of an IdP, return a configuation.
16
16
  The configuration is a hash for use by saml2.config.Config
17
17
  """
18
- acs_url = f"http://{config.DOMAIN_NAME}/api/auth/saml/sso"
19
- https_acs_url = f"https://{config.DOMAIN_NAME}/api/auth/saml/sso"
18
+ acs_url = (
19
+ f"{config.DOMAIN_PROTOCOL}://{config.DOMAIN_NAME}/api/auth/saml/sso"
20
+ )
20
21
 
21
22
  rv = requests.get(metadata_url)
22
23
 
@@ -29,8 +30,6 @@ def saml_client_for(metadata_url):
29
30
  "assertion_consumer_service": [
30
31
  (acs_url, BINDING_HTTP_REDIRECT),
31
32
  (acs_url, BINDING_HTTP_POST),
32
- (https_acs_url, BINDING_HTTP_REDIRECT),
33
- (https_acs_url, BINDING_HTTP_POST),
34
33
  ],
35
34
  },
36
35
  # Don't verify that the incoming requests originate from us via
@@ -0,0 +1,44 @@
1
+ """add department keys to filter models
2
+
3
+ Revision ID: 9d3bb33c6fc6
4
+ Revises: 8e67c183bed7
5
+ Create Date: 2024-10-11 15:52:29.740222
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+ import sqlalchemy_utils
11
+ import sqlalchemy_utils
12
+ import uuid
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision = '9d3bb33c6fc6'
16
+ down_revision = '8e67c183bed7'
17
+ branch_labels = None
18
+ depends_on = None
19
+
20
+
21
+ def upgrade():
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ with op.batch_alter_table('search_filter', schema=None) as batch_op:
24
+ batch_op.add_column(sa.Column('department_id', sqlalchemy_utils.types.uuid.UUIDType(binary=False), default=uuid.uuid4, nullable=True))
25
+ batch_op.create_foreign_key(None, 'department', ['department_id'], ['id'])
26
+
27
+ with op.batch_alter_table('search_filter_group', schema=None) as batch_op:
28
+ batch_op.add_column(sa.Column('department_id', sqlalchemy_utils.types.uuid.UUIDType(binary=False), default=uuid.uuid4, nullable=True))
29
+ batch_op.create_foreign_key(None, 'department', ['department_id'], ['id'])
30
+
31
+ # ### end Alembic commands ###
32
+
33
+
34
+ def downgrade():
35
+ # ### commands auto generated by Alembic - please adjust! ###
36
+ with op.batch_alter_table('search_filter_group', schema=None) as batch_op:
37
+ batch_op.drop_constraint(None, type_='foreignkey')
38
+ batch_op.drop_column('department_id')
39
+
40
+ with op.batch_alter_table('search_filter', schema=None) as batch_op:
41
+ batch_op.drop_constraint(None, type_='foreignkey')
42
+ batch_op.drop_column('department_id')
43
+
44
+ # ### end Alembic commands ###
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zou
3
- Version: 0.19.58
3
+ Version: 0.19.60
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
@@ -1,18 +1,18 @@
1
- zou/__init__.py,sha256=jW9ZGh5Op9utKMQzRf38ogtGfjS5IWX7r0EtOXzcwd0,24
1
+ zou/__init__.py,sha256=Q-R4YnG0E1Dy8riIkq9R2p9ONeBkY4iPua_nw1RTmDA,24
2
2
  zou/cli.py,sha256=DHHf4mz33Bi0gGFJ0GdRUpjrMrMSQYyVubJFppHPZlU,18914
3
3
  zou/debug.py,sha256=1fawPbkD4wn0Y9Gk0BiBFSa-CQe5agFi8R9uJYl2Uyk,520
4
4
  zou/event_stream.py,sha256=_tue9Ry3aqCniZpKGhWJaY1Eo_fd6zOAfnzPvh_mJzU,8489
5
5
  zou/job_settings.py,sha256=_aqBhujt2Q8sXRWIbgbDf-LUdXRdBimdtTc-fZbiXoY,202
6
6
  zou/app/__init__.py,sha256=AJEGrLTirr5zZmA8DVSjZedtTuDg6eg9rG4kXMimPnQ,6966
7
7
  zou/app/api.py,sha256=JTB_IMVO8EOoyqx9KdRkiIix0chOLi0yGDY-verUJXA,5127
8
- zou/app/config.py,sha256=z6fhXvTKuyCXAJDPtLGrnJ7aUie-5JTwN8n_Z_oX0HI,6592
8
+ zou/app/config.py,sha256=V6kV3JzaR2yGz8F3vcsb-v4EB9VvvirEPtGeQMot9bI,6649
9
9
  zou/app/mixin.py,sha256=eYwfS_CUFvNmldaQXrjsN5mK_gX0wYrBFykfx60uUM8,4897
10
10
  zou/app/swagger.py,sha256=Jr7zsMqJi0V4FledODOdu-aqqVE02jMFzhqVxHK0_2c,54158
11
11
  zou/app/blueprints/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  zou/app/blueprints/assets/__init__.py,sha256=T2zhDagHjXF6jRwOQ8vqokZTkBHyY7XtTI0Rlooamjs,2931
13
13
  zou/app/blueprints/assets/resources.py,sha256=ff7g8_FzZC6dLvMxcYYffsIOy0CqKqdkZibGm2-qgko,25599
14
14
  zou/app/blueprints/auth/__init__.py,sha256=xP874bMWUnLIirOPSEbpe-Q2fBBQrxZGKd0tLZmNJXk,1128
15
- zou/app/blueprints/auth/resources.py,sha256=CAqlC8Q2Mw3grjL2bU_aZJQ18huwa1Kf3ym2sueH8Bg,45136
15
+ zou/app/blueprints/auth/resources.py,sha256=ujdYPdz-anqIjK2Iepk5Yg4dRXJcHmc8sGpo6omG73A,45214
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
@@ -43,7 +43,7 @@ zou/app/blueprints/crud/notification.py,sha256=A-KNH0IDNCXlE4AddNxvmsD_7a9HqHGo_
43
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
- zou/app/blueprints/crud/person.py,sha256=qKgko2ZlGCHj_7SC9NW9hFHrjblk18khS_7bGKheyIs,9177
46
+ zou/app/blueprints/crud/person.py,sha256=FPvMt4ChXnfH6DheQL_jagakO6ic123UkOmsIM0cG_s,9281
47
47
  zou/app/blueprints/crud/playlist.py,sha256=ZXaz-hHPej6XYKo_RUfJ2QnGOh2W8-y9hTkPZCUp7Zk,1876
48
48
  zou/app/blueprints/crud/preview_background_file.py,sha256=WzOfag5qhz5B3-XDObYRC8TO4X-kIo1sHOnMCV62IEo,2437
49
49
  zou/app/blueprints/crud/preview_file.py,sha256=252EDEdF-wQaL9ZhqQv5j9z_JKK5WeYwidJfFafArjo,3831
@@ -83,15 +83,15 @@ zou/app/blueprints/export/csv/time_spents.py,sha256=yYPtilOxfQD5mBwyh9h-PbTQBpab
83
83
  zou/app/blueprints/files/__init__.py,sha256=7Wty30JW2OXIn-tBFXOWWmPuHnsnxPpH3jNtHvvr9tY,3987
84
84
  zou/app/blueprints/files/resources.py,sha256=8SIV8kaqv3dxyL8nyqG3QiZmk5ZYIvUxw6k1ic-jhBs,69786
85
85
  zou/app/blueprints/index/__init__.py,sha256=Dh3oQiirpg8RCkfVOuk3irIjSvUvuRf0jPxE6oGubz0,828
86
- zou/app/blueprints/index/resources.py,sha256=lGPUGZPla59mjXXsDW4DB04uE_rI4fhMqm8J48Nxf4s,8407
86
+ zou/app/blueprints/index/resources.py,sha256=UUBHWGDIyx6xgk2Jt_uwvpSETB0xeIjQ3quYVN0uOlc,8734
87
87
  zou/app/blueprints/news/__init__.py,sha256=HxBXjC15dVbotNAZ0CLf02iwUjxJr20kgf8_kT_9nwM,505
88
88
  zou/app/blueprints/news/resources.py,sha256=SL0RYo-fs23GjQU1IW4kuEBg2SHdaXaonrOoOnWafdw,7183
89
89
  zou/app/blueprints/persons/__init__.py,sha256=0cnHHw3K_8OEMm0qOi3wKVomSAg9IJSnVjAXabMeHks,3893
90
- zou/app/blueprints/persons/resources.py,sha256=RsN8ix67zRU7NNeMY7uFCXkU2ntJlmKF6DizmGVYdE0,42684
90
+ zou/app/blueprints/persons/resources.py,sha256=0EBI6w63WhjqDSj-COYGk4NO6V1oRyAzfH4f7dXNYoE,42793
91
91
  zou/app/blueprints/playlists/__init__.py,sha256=vuEk1F3hFHsmuKWhdepMoLyOzmNKDn1YrjjfcaIz0lQ,1596
92
92
  zou/app/blueprints/playlists/resources.py,sha256=alRlMHypUFErXLsEYxpFK84cdjFJ3YWwamZtW0KcwLY,17211
93
93
  zou/app/blueprints/previews/__init__.py,sha256=qGohO6LRNZKXBAegINcUXuZlrtxobJKQg84-rQ1L3AU,4202
94
- zou/app/blueprints/previews/resources.py,sha256=Mg2FVVJdb5_Fqr8OAWl2i46qgSASe9b4lzYnFjGeFRQ,48062
94
+ zou/app/blueprints/previews/resources.py,sha256=yuSs7-VOYdeSrTDwktx_3uyugxMN1r73FG0kNH-qNXg,48241
95
95
  zou/app/blueprints/projects/__init__.py,sha256=Pn3fA5bpNFEPBzxTKJ2foV6osZFflXXSM2l2uZh3ktM,3927
96
96
  zou/app/blueprints/projects/resources.py,sha256=E91Vj9EzId2pxiL50JRfrThiyif1PmzWS1oPeMThzQI,31544
97
97
  zou/app/blueprints/search/__init__.py,sha256=QCjQIY_85l_orhdEiqav_GifjReuwsjZggN3V0GeUVY,356
@@ -129,7 +129,7 @@ zou/app/blueprints/source/shotgun/versions.py,sha256=8Mb35e5p3FLbbiu6AZb9tJErDKz
129
129
  zou/app/blueprints/tasks/__init__.py,sha256=pNUqVEVX1KVu1IzRRJNsziPkhWKXqgyvQsNp7LbmIxo,4342
130
130
  zou/app/blueprints/tasks/resources.py,sha256=b5z4OolidyKdKTP5kuZLk7tDLhgDs5W3rkH4ZWlwB0Y,56020
131
131
  zou/app/blueprints/user/__init__.py,sha256=H9zCHcVobC6jq6dTToXKAjnZmDA0a9gChHiIP3BcZsc,4586
132
- zou/app/blueprints/user/resources.py,sha256=tfdNIIwClACEfmvu8SgQs1Q3oD9nVT0P2IpdcglmITo,38646
132
+ zou/app/blueprints/user/resources.py,sha256=07TXMEJG__aBRotgKnduoDf2Y59J-9VcMzUDXVUHzCI,39919
133
133
  zou/app/file_trees/default.json,sha256=ryUrEmQYE8B_WkzCoQLgmem3N9yNwMIWx9G8p3HfG9o,2310
134
134
  zou/app/file_trees/simple.json,sha256=VBI43Z3rjQxtTpVCq3ktUgS0UB8x-aTowKL9LXuXCFI,3149
135
135
  zou/app/indexer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -166,8 +166,8 @@ zou/app/models/preview_file.py,sha256=Ur45Wau2X3qyKULh04EUcMbnBmaQc8y4IMs5NgELiA
166
166
  zou/app/models/project.py,sha256=T2gTImmbd5O-U5GySWSwzkR0r_cY2RDr1niwSfOPqeA,8900
167
167
  zou/app/models/project_status.py,sha256=pExaHH7x4Eu8xOqD3_mRRbMzjT2jJZ8rm-9jo7yUGXA,390
168
168
  zou/app/models/schedule_item.py,sha256=coY5uDDuBoctaMICnfCzx4zT5om2E1uu4iq5MDWAPlY,1444
169
- zou/app/models/search_filter.py,sha256=3xcFemJ9poIBAHNd50pv9U9eP_n74xJCxW2JBDcRuNo,1133
170
- zou/app/models/search_filter_group.py,sha256=BWZR-Wkr7YzKRNU8GfIGlnz4AGZjD17_wK4tWwabNxQ,880
169
+ zou/app/models/search_filter.py,sha256=Q699mJa3GGwFEva93Y_nHiIcx3BDht9VsAe2zeQL0FA,1242
170
+ zou/app/models/search_filter_group.py,sha256=LGT2zn1lk3dCC-Fu7NU7VFD6MWprmBz7saQhS2ST2gA,980
171
171
  zou/app/models/serializer.py,sha256=VKN5hHJ6M_jsvrbKe5SonnTeg5dxXSYqb_4JDVWIQwA,1477
172
172
  zou/app/models/software.py,sha256=3y9xEOS8BBxqgTaRrCaSYHaB6uOdJ88uU5i-cACoCSk,517
173
173
  zou/app/models/status_automation.py,sha256=95c0lmOetujyGWViLd_qsR4GWPhrmlvi9ZfkC0x1NuM,1391
@@ -200,7 +200,7 @@ zou/app/services/index_service.py,sha256=1w8rGJ7ArYC13B43hD4JOPtVhaRsbseJVVY-GnU
200
200
  zou/app/services/names_service.py,sha256=TOSrintROmxcAlcFQE0i2E3PBLnw81GAztNselpTn18,2947
201
201
  zou/app/services/news_service.py,sha256=g-QlITBbEvhvm-pLv7GtGACKX4uBPa-IKxfNOgMgaHo,8768
202
202
  zou/app/services/notifications_service.py,sha256=7GDRio_mGaRYV5BHOAdpxBZjA_LLYUfVpbwZqy1n9pI,15685
203
- zou/app/services/persons_service.py,sha256=06CyF--QnGVkjvZPkAYIBz8V-P_Nbb4Iv1lkHQWqh4k,16310
203
+ zou/app/services/persons_service.py,sha256=0CKYrmHDUq34VNNgLSWDKN37EgmD68ZqyrUmm4ABj18,16356
204
204
  zou/app/services/playlists_service.py,sha256=pAlPHET4jNdST5jsmJrFUkf1SVhfSoML9zdNpZ_88l4,32439
205
205
  zou/app/services/preview_files_service.py,sha256=M1kXryARUKwCDXsr0hOfUUyGn75y-qXIVIwlMOKWWHs,35609
206
206
  zou/app/services/projects_service.py,sha256=_J8hIHy3MX5MsdEMRIKNfbyewwhxtMEcc_ymeHBsF38,21434
@@ -213,7 +213,7 @@ zou/app/services/sync_service.py,sha256=EunfXlma_IIb7011A_xLQLVQGAi-MteKgm2Y2NAx
213
213
  zou/app/services/tasks_service.py,sha256=IoogHBoZhbJegnl5SYGgOaBp2YVqrL22Gccjfp8dPn8,69335
214
214
  zou/app/services/telemetry_services.py,sha256=xQm1h1t_JxSFW59zQGf4NuNdUi1UfMa_6pQ-ytRbmGA,1029
215
215
  zou/app/services/time_spents_service.py,sha256=H9X-60s6oqtY9rtU-K2jKwUSljfkdGlf_9wMr3iVfIA,15158
216
- zou/app/services/user_service.py,sha256=rI1eLdccb4PpD6tqjQnQtu_sY5h3T9FMbk5GZ6RReg4,47916
216
+ zou/app/services/user_service.py,sha256=O6KdOVqJBGHCzRqykqnrqWgH1ijRZLtxqA6QFaNaZ5g,50471
217
217
  zou/app/stores/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
218
218
  zou/app/stores/auth_tokens_store.py,sha256=-qOJPybLHvnMOq3PWk073OW9HJwOHGhFLZeOIlX1UVw,1290
219
219
  zou/app/stores/file_store.py,sha256=yLQDM6mNbj9oe0vsWdBqun7D8Dw-eSjD1yHCCftX0OI,4045
@@ -225,11 +225,11 @@ zou/app/utils/auth.py,sha256=DZfZSr1Ulge0UK3hfvOWsMo3_d7RVP_llV118u9BtUI,870
225
225
  zou/app/utils/cache.py,sha256=MRluTvGG67ybOkyzgD70B6PGKMdRyFdTc0AYy3dEQe8,1210
226
226
  zou/app/utils/chats.py,sha256=ORngxQ3IQQF0QcVFJLxJ-RaU4ksQ9-0M8cmPa0pc0Ho,4302
227
227
  zou/app/utils/colors.py,sha256=LaGV17NL_8xY0XSp8snGWz5UMwGnm0KPWXyE5BTMG6w,200
228
- zou/app/utils/commands.py,sha256=3TxXFOQxsWBT85DG1dVnuqdMbjFp58XJEtgYxhxbhhs,27304
228
+ zou/app/utils/commands.py,sha256=z1tpCRY6wOH_uSrjPTS5kRkhYwW8SEA-pKhreaXLu0I,27472
229
229
  zou/app/utils/csv_utils.py,sha256=difqBp9xp3nHvaOjW6B64A4X3Uyfe_vKGw10i6SJ5_E,1291
230
230
  zou/app/utils/date_helpers.py,sha256=jFxDPCbAasg0I1gsC72AKEbGcx5c4pLqXZkSfZ4wLdQ,4724
231
231
  zou/app/utils/dbhelpers.py,sha256=RSJuoxLexGJyME16GQCs-euFLBR0u-XAFdJ1KMSv5M8,1143
232
- zou/app/utils/emails.py,sha256=6LgzRSV3wf2rkJiplOD2c79-o75X_ddpe63ht2xhTME,1433
232
+ zou/app/utils/emails.py,sha256=hc4HWvVYsG1xoP3sR0Zig0sB0k0y7bNxuNxxBreN5Yg,1436
233
233
  zou/app/utils/env.py,sha256=haveriMt1rDeLOosAPMm0IblF_QpMViEsjg7BJqD-rw,711
234
234
  zou/app/utils/events.py,sha256=a_W70v0Oi4QJwJzCLVUQ8RTwjlWo-VzAT_hQZCz2HQU,3024
235
235
  zou/app/utils/fido.py,sha256=qhUWZdDCvgWqydYS4DN1SVkNwdAQkRSV9zs0Xw9O15E,536
@@ -242,7 +242,7 @@ zou/app/utils/monitoring.py,sha256=xOwyfM-7ZKWNtOxmX2WB1fOav5NpY6k8Tmuhli0nMBs,2
242
242
  zou/app/utils/permissions.py,sha256=Oq91C_lN6aGVCtCVUqQhijMQEjXOiMezbngpjybzzQk,3426
243
243
  zou/app/utils/query.py,sha256=q8ETGPAqnz0Pt9xWoQt5o7FFAVYUKVCJiWpwefIr-iU,4592
244
244
  zou/app/utils/remote_job.py,sha256=QPxcCWEv-NM1Q4IQawAyJAiSORwkMeOlByQb9OCShEw,2522
245
- zou/app/utils/saml.py,sha256=b869bvaTR4E4CzOK2gbaN9h6JVkU6aVb1v8_F_ofWDY,1837
245
+ zou/app/utils/saml.py,sha256=m8wt_2RnMEHSrFvqCRGF91U5mwkqC6W-iBcx8N0LAqs,1679
246
246
  zou/app/utils/shell.py,sha256=D30NuOzr1D6jsa9OF69JYXyqYNhtIdnfKSVYW4irbz8,405
247
247
  zou/app/utils/string.py,sha256=ZIJuQRe7ml669-T1o9keQtk_lV2D1DkS9ARQdexlQ6Y,404
248
248
  zou/app/utils/thumbnail.py,sha256=eUb25So1fbjxZKYRpTOxLcZ0vww5zXNVkfVIa_tu5Dk,6625
@@ -354,6 +354,7 @@ zou/migrations/versions/99825b9cc778_.py,sha256=vleoLh_fCLwNnCmgZkW5x1-lJ09CgjPd
354
354
  zou/migrations/versions/9a09467f9b2c_.py,sha256=cjSO3cXzHHhlcaQhShF9WlxmBWIfwLYSyIb9C6B5Dwc,1246
355
355
  zou/migrations/versions/9b85c14fa8a7_add_day_off_new_columns.py,sha256=u6n5X_hwoeH9XnkWHB4PvVvGVrkddgDamweMrlPFQ9I,2106
356
356
  zou/migrations/versions/9bd17364fc18_.py,sha256=nruaGMczzkWkya_D11QEU3uiU1UVEUItM6svSBjOw2c,1890
357
+ zou/migrations/versions/9d3bb33c6fc6_add_department_keys_to_filter_models.py,sha256=iWcJUdkPThoWONEHQ64hpQYSVcax0lO7rHJ3DVZYRDs,1587
357
358
  zou/migrations/versions/9e5b3a9b0cee_add_new_column_metadatadescriptor_data_type_.py,sha256=HT34AjM9yN1IvLrCzx4-YOiocFDnPQ-FJ05icPCBnNo,5517
358
359
  zou/migrations/versions/9f8445f9b42c_add_man_days_fields.py,sha256=muFsM2g_m-L5Zss36roTsjCoSDdhu_e4hVQntiNHyiY,828
359
360
  zou/migrations/versions/a23682ccc1f1_.py,sha256=dVOUWldUZyLuLc6_HrgKUI5XH5-6Vjwai6Ew9kygkHw,1663
@@ -412,9 +413,9 @@ zou/remote/normalize_movie.py,sha256=zNfEY3N1UbAHZfddGONTg2Sff3ieLVWd4dfZa1dpnes
412
413
  zou/remote/playlist.py,sha256=AsDo0bgYhDcd6DfNRV6r6Jj3URWwavE2ZN3VkKRPbLU,3293
413
414
  zou/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
414
415
  zou/utils/movie.py,sha256=u9LCEOvmkxwm-KiZ6jKNdB9LSC6XXUDwJpVx8LkDwJg,16416
415
- zou-0.19.58.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
416
- zou-0.19.58.dist-info/METADATA,sha256=llQSr1UHhwtI7Eq69K7WIu7Z9eYVDG02fdAbnB1vvnY,6676
417
- zou-0.19.58.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
418
- zou-0.19.58.dist-info/entry_points.txt,sha256=PelQoIx3qhQ_Tmne7wrLY-1m2izuzgpwokoURwSohy4,130
419
- zou-0.19.58.dist-info/top_level.txt,sha256=4S7G_jk4MzpToeDItHGjPhHx_fRdX52zJZWTD4SL54g,4
420
- zou-0.19.58.dist-info/RECORD,,
416
+ zou-0.19.60.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
417
+ zou-0.19.60.dist-info/METADATA,sha256=EqDZA8e8KYpG4COrPyaVuxsMAeFLd9pHuzKCtAY605Q,6676
418
+ zou-0.19.60.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
419
+ zou-0.19.60.dist-info/entry_points.txt,sha256=PelQoIx3qhQ_Tmne7wrLY-1m2izuzgpwokoURwSohy4,130
420
+ zou-0.19.60.dist-info/top_level.txt,sha256=4S7G_jk4MzpToeDItHGjPhHx_fRdX52zJZWTD4SL54g,4
421
+ zou-0.19.60.dist-info/RECORD,,
File without changes
File without changes