zou 0.20.21__py3-none-any.whl → 0.20.23__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
zou/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.20.21"
1
+ __version__ = "0.20.23"
@@ -491,7 +491,13 @@ class ReplyCommentResource(Resource, ArgsMixin):
491
491
  ]
492
492
  )
493
493
 
494
- user_service.check_task_action_access(task_id)
494
+ comment = tasks_service.get_comment(comment_id)
495
+ current_user = persons_service.get_current_user()
496
+ if comment["person_id"] != current_user["id"]:
497
+ if permissions.has_client_permissions():
498
+ raise permissions.PermissionDenied()
499
+ user_service.check_task_action_access(task_id)
500
+
495
501
  return comments_service.reply_comment(comment_id, args["text"])
496
502
 
497
503
 
@@ -418,11 +418,8 @@ class CreatePreviewFilePictureResource(
418
418
  )
419
419
  raise PreviewFileReuploadNotAllowedException
420
420
 
421
- try:
422
- user_service.check_task_action_access(preview_file["task_id"])
423
- return True
424
- except permissions.PermissionDenied:
425
- return False
421
+ user_service.check_task_action_access(preview_file["task_id"])
422
+ return True
426
423
 
427
424
  def is_exist(self, preview_file_id):
428
425
  """
@@ -42,6 +42,7 @@ from zou.app.blueprints.shots.resources import (
42
42
  EpisodeAssetTasksResource,
43
43
  SequenceShotTasksResource,
44
44
  ProjectQuotasResource,
45
+ ProjectPersonQuotasResource,
45
46
  SetShotsFramesResource,
46
47
  )
47
48
 
@@ -95,6 +96,10 @@ routes = [
95
96
  "/data/projects/<project_id>/quotas/<task_type_id>",
96
97
  ProjectQuotasResource,
97
98
  ),
99
+ (
100
+ "/data/projects/<project_id>/quotas/persons/<person_id>",
101
+ ProjectPersonQuotasResource,
102
+ ),
98
103
  (
99
104
  "/actions/projects/<project_id>/task-types/<task_type_id>/set-shot-nb-frames",
100
105
  SetShotsFramesResource,
@@ -1607,6 +1607,80 @@ class ProjectQuotasResource(Resource, ArgsMixin):
1607
1607
  )
1608
1608
 
1609
1609
 
1610
+ class ProjectPersonQuotasResource(Resource, ArgsMixin):
1611
+
1612
+ @jwt_required()
1613
+ def get(self, project_id, person_id):
1614
+ """
1615
+ Retrieve quotas statistics for shots.
1616
+ ---
1617
+ tags:
1618
+ - Shots
1619
+ parameters:
1620
+ - in: path
1621
+ name: project_id
1622
+ required: True
1623
+ type: string
1624
+ format: UUID
1625
+ x-example: a24a6ea4-ce75-4665-a070-57453082c25
1626
+ - in: path
1627
+ name: person_id
1628
+ required: True
1629
+ type: string
1630
+ format: UUID
1631
+ x-example: a24a6ea4-ce75-4665-a070-57453082c25
1632
+ - in: query
1633
+ name: count_mode
1634
+ required: True
1635
+ type: string
1636
+ enum: [weighted, weigtheddone, feedback, done]
1637
+ x-example: weighted
1638
+ - in: query
1639
+ name: studio_id
1640
+ required: False
1641
+ type: string
1642
+ format: UUID
1643
+ x-example: a24a6ea4-ce75-4665-a070-57453082c25
1644
+ responses:
1645
+ 200:
1646
+ description: Quotas statistics for shots
1647
+ """
1648
+ projects_service.get_project(project_id)
1649
+ user_service.check_project_access(project_id)
1650
+ args = self.get_args(
1651
+ [
1652
+ ("count_mode", "weighted", False, str),
1653
+ ("studio_id", None, False, str),
1654
+ ]
1655
+ )
1656
+ count_mode = args["count_mode"]
1657
+ studio_id = args["studio_id"]
1658
+
1659
+ if count_mode not in ["weighted", "weighteddone", "feedback", "done"]:
1660
+ raise WrongParameterException(
1661
+ "count_mode must be equal to weighted, weigtheddone, feedback"
1662
+ ", or done"
1663
+ )
1664
+
1665
+ feedback = "done" not in count_mode
1666
+ weighted = "weighted" in count_mode
1667
+
1668
+ if weighted:
1669
+ return shots_service.get_weighted_quotas(
1670
+ project_id,
1671
+ person_id=person_id,
1672
+ feedback=feedback,
1673
+ studio_id=studio_id,
1674
+ )
1675
+ else:
1676
+ return shots_service.get_raw_quotas(
1677
+ project_id,
1678
+ person_id=person_id,
1679
+ feedback=feedback,
1680
+ studio_id=studio_id,
1681
+ )
1682
+
1683
+
1610
1684
  class SetShotsFramesResource(Resource, ArgsMixin):
1611
1685
  @jwt_required()
1612
1686
  def post(self, project_id, task_type_id):
zou/app/models/project.py CHANGED
@@ -10,6 +10,7 @@ from zou.app.models.person import ROLE_TYPES
10
10
 
11
11
  PROJECT_STYLES = [
12
12
  ("2d", "2D Animation"),
13
+ ("2dpaper", "2D Animation (Paper)"),
13
14
  ("3d", "3D Animation"),
14
15
  ("2d3d", "2D/3D Animation"),
15
16
  ("ar", "Augmented Reality"),
zou/app/models/task.py CHANGED
@@ -47,6 +47,8 @@ class Task(db.Model, BaseMixin, SerializerMixin):
47
47
  last_comment_date = db.Column(db.DateTime)
48
48
  nb_assets_ready = db.Column(db.Integer, default=0)
49
49
  data = db.Column(JSONB)
50
+ nb_drawings = db.Column(db.Integer, default=0)
51
+
50
52
  shotgun_id = db.Column(db.Integer)
51
53
  last_preview_file_id = db.Column(UUIDType(binary=False))
52
54
 
@@ -348,7 +348,9 @@ def get_assets_and_tasks(criterions={}, with_episode_ids=False):
348
348
  "last_comment_date": fields.serialize_value(
349
349
  task_last_comment_date
350
350
  ),
351
- "last_preview_file_id": str(task_last_preview_file_id or ""),
351
+ "last_preview_file_id": str(
352
+ task_last_preview_file_id or ""
353
+ ),
352
354
  "priority": task_priority or 0,
353
355
  "real_start_date": fields.serialize_value(
354
356
  task_real_start_date
@@ -256,6 +256,7 @@ def get_shots_and_tasks(criterions={}):
256
256
  Task.last_preview_file_id,
257
257
  Task.nb_assets_ready,
258
258
  Task.difficulty,
259
+ Task.nb_drawings,
259
260
  assignees_table.columns.person,
260
261
  Project.id,
261
262
  Project.name,
@@ -308,6 +309,7 @@ def get_shots_and_tasks(criterions={}):
308
309
  task_last_preview_file_id,
309
310
  task_nb_assets_ready,
310
311
  task_difficulty,
312
+ task_nb_drawings,
311
313
  person_id,
312
314
  project_id,
313
315
  project_name,
@@ -373,6 +375,7 @@ def get_shots_and_tasks(criterions={}):
373
375
  "retake_count": task_retake_count,
374
376
  "start_date": task_start_date,
375
377
  "difficulty": task_difficulty,
378
+ "nb_drawings": task_nb_drawings,
376
379
  "task_status_id": task_status_id,
377
380
  "task_type_id": task_type_id,
378
381
  "assignees": [],
@@ -1117,7 +1120,11 @@ def get_base_entity_type_name(entity_dict):
1117
1120
 
1118
1121
 
1119
1122
  def get_weighted_quotas(
1120
- project_id, task_type_id, studio_id=None, feedback=True
1123
+ project_id,
1124
+ task_type_id=None,
1125
+ person_id=None,
1126
+ studio_id=None,
1127
+ feedback=True,
1121
1128
  ):
1122
1129
  """
1123
1130
  Build quota statistics. It counts the number of frames done for each day.
@@ -1140,7 +1147,6 @@ def get_weighted_quotas(
1140
1147
  query = (
1141
1148
  Task.query.filter(Entity.entity_type_id == shot_type["id"])
1142
1149
  .filter(Task.project_id == project_id)
1143
- .filter(Task.task_type_id == task_type_id)
1144
1150
  .join(Entity, Entity.id == Task.entity_id)
1145
1151
  .join(Project, Project.id == Task.project_id)
1146
1152
  .join(TimeSpent, Task.id == TimeSpent.task_id)
@@ -1152,6 +1158,12 @@ def get_weighted_quotas(
1152
1158
  )
1153
1159
  )
1154
1160
 
1161
+ if task_type_id is not None:
1162
+ query = query.filter(Task.task_type_id == task_type_id)
1163
+
1164
+ if person_id is not None:
1165
+ query = query.filter(TimeSpent.person_id == person_id)
1166
+
1155
1167
  if feedback:
1156
1168
  query = query.filter(Task.end_date != None)
1157
1169
  else:
@@ -1166,11 +1178,21 @@ def get_weighted_quotas(
1166
1178
  )
1167
1179
  result = query.all()
1168
1180
 
1169
- for task, nb_frames, date, duration, person_id in result:
1170
- person_id = str(person_id)
1171
- if task.duration > 0 and nb_frames is not None:
1181
+ for task, nb_frames, date, duration, task_person_id in result:
1182
+ task_person_id = str(task_person_id)
1183
+ nb_drawings = task.nb_drawings or 0
1184
+ nb_frames = nb_frames or 0
1185
+ if task.duration > 0:
1172
1186
  nb_frames = round(nb_frames * (duration / task.duration))
1173
- _add_quota_entry(quotas, person_id, date, timezone, nb_frames, fps)
1187
+ nb_drawings = round(nb_drawings * (duration / task.duration))
1188
+ entry_id = str(task_person_id)
1189
+ # We get quotas for a specific person split by task types
1190
+ if person_id is not None:
1191
+ entry_id = str(task.task_type_id)
1192
+ for entry in [entry_id, "total"]:
1193
+ _add_quota_entry(
1194
+ quotas, entry, date, timezone, nb_frames, nb_drawings, fps
1195
+ )
1174
1196
 
1175
1197
  query = (
1176
1198
  Task.query.filter(Task.project_id == project_id)
@@ -1185,6 +1207,13 @@ def get_weighted_quotas(
1185
1207
  .add_columns(Entity.nb_frames, Person.id)
1186
1208
  )
1187
1209
 
1210
+ if task_type_id is not None:
1211
+ query = query.filter(Task.task_type_id == task_type_id)
1212
+
1213
+ if person_id is not None:
1214
+ person = persons_service.get_person_raw(person_id)
1215
+ query = query.filter(Task.assignees.contains(person))
1216
+
1188
1217
  if feedback:
1189
1218
  query = query.filter(Task.end_date != None)
1190
1219
  else:
@@ -1196,7 +1225,7 @@ def get_weighted_quotas(
1196
1225
  )
1197
1226
  result = query.all()
1198
1227
 
1199
- for task, nb_frames, person_id in result:
1228
+ for task, nb_frames, task_person_id in result:
1200
1229
  date = task.done_date
1201
1230
  if feedback:
1202
1231
  date = task.end_date
@@ -1209,16 +1238,36 @@ def get_weighted_quotas(
1209
1238
  else:
1210
1239
  nb_frames = 0
1211
1240
 
1241
+ nb_drawings = task.nb_drawings or 0
1242
+
1212
1243
  for x in range((date - task.real_start_date).days + 1):
1213
1244
  if date.weekday() < 5:
1214
- _add_quota_entry(
1215
- quotas, str(person_id), date, timezone, nb_frames, fps
1216
- )
1245
+ entry_id = str(task_person_id)
1246
+ # We get quotas for a specific person split by task types
1247
+ if person_id is not None:
1248
+ entry_id = str(task.task_type_id)
1249
+
1250
+ for entry in [entry_id, "total"]:
1251
+ _add_quota_entry(
1252
+ quotas,
1253
+ entry,
1254
+ date,
1255
+ timezone,
1256
+ nb_frames,
1257
+ nb_drawings,
1258
+ fps,
1259
+ )
1217
1260
  date = date + timedelta(1)
1218
1261
  return quotas
1219
1262
 
1220
1263
 
1221
- def get_raw_quotas(project_id, task_type_id, studio_id=None, feedback=True):
1264
+ def get_raw_quotas(
1265
+ project_id,
1266
+ task_type_id=None,
1267
+ person_id=None,
1268
+ studio_id=None,
1269
+ feedback=True,
1270
+ ):
1222
1271
  """
1223
1272
  Build quota statistics in a raw way. It counts the number of frames done
1224
1273
  for each day. A shot is considered done at the first feedback request (end
@@ -1234,13 +1283,19 @@ def get_raw_quotas(project_id, task_type_id, studio_id=None, feedback=True):
1234
1283
  query = (
1235
1284
  Task.query.filter(Task.project_id == project_id)
1236
1285
  .filter(Entity.entity_type_id == shot_type["id"])
1237
- .filter(Task.task_type_id == task_type_id)
1238
1286
  .join(Entity, Entity.id == Task.entity_id)
1239
1287
  .join(Project, Project.id == Task.project_id)
1240
1288
  .join(Task.assignees)
1241
1289
  .add_columns(Entity.nb_frames, Person.id)
1242
1290
  )
1243
1291
 
1292
+ if task_type_id is not None:
1293
+ query = query.filter(Task.task_type_id == task_type_id)
1294
+
1295
+ if person_id is not None:
1296
+ person = persons_service.get_person_raw(person_id)
1297
+ query = query.filter(Task.assignees.contains(person))
1298
+
1244
1299
  if feedback:
1245
1300
  query = query.filter(Task.end_date != None)
1246
1301
  else:
@@ -1256,20 +1311,30 @@ def get_raw_quotas(project_id, task_type_id, studio_id=None, feedback=True):
1256
1311
 
1257
1312
  result = query.all()
1258
1313
 
1259
- for task, nb_frames, person_id in result:
1314
+ for task, nb_frames, task_person_id in result:
1260
1315
  date = task.done_date
1261
1316
  if feedback:
1262
1317
  date = task.end_date
1263
1318
 
1264
1319
  if nb_frames is None:
1265
1320
  nb_frames = 0
1266
- _add_quota_entry(
1267
- quotas, str(person_id), date, timezone, nb_frames, fps
1268
- )
1321
+
1322
+ nb_drawings = task.nb_drawings or 0
1323
+
1324
+ entry_id = str(task_person_id)
1325
+ if person_id is not None:
1326
+ entry_id = str(task.task_type_id)
1327
+
1328
+ for entry in [entry_id, "total"]:
1329
+ _add_quota_entry(
1330
+ quotas, entry, date, timezone, nb_frames, nb_drawings, fps
1331
+ )
1269
1332
  return quotas
1270
1333
 
1271
1334
 
1272
- def _add_quota_entry(quotas, person_id, date, timezone, nb_frames, fps):
1335
+ def _add_quota_entry(
1336
+ quotas, entry_id, date, timezone, nb_frames, nb_drawings, fps
1337
+ ):
1273
1338
  nb_seconds = nb_frames / fps
1274
1339
  date_str = date_helpers.get_simple_string_with_timezone_from_date(
1275
1340
  date, timezone
@@ -1277,59 +1342,84 @@ def _add_quota_entry(quotas, person_id, date, timezone, nb_frames, fps):
1277
1342
  year = date_str[:4]
1278
1343
  week = year + "-" + str(date.isocalendar()[1])
1279
1344
  month = date_str[:7]
1280
- if person_id not in quotas:
1281
- _init_quota_person(quotas, person_id)
1282
- _init_quota_date(quotas, person_id, date_str, week, month)
1283
- quotas[person_id]["day"]["frames"][date_str] += nb_frames
1284
- quotas[person_id]["day"]["seconds"][date_str] += nb_seconds
1285
- quotas[person_id]["day"]["count"][date_str] += 1
1286
- quotas[person_id]["week"]["frames"][week] += nb_frames
1287
- quotas[person_id]["week"]["seconds"][week] += nb_seconds
1288
- quotas[person_id]["week"]["count"][week] += 1
1289
- quotas[person_id]["month"]["frames"][month] += nb_frames
1290
- quotas[person_id]["month"]["seconds"][month] += nb_seconds
1291
- quotas[person_id]["month"]["count"][month] += 1
1292
- quotas[person_id]["year"]["frames"][year] += nb_frames
1293
- quotas[person_id]["year"]["seconds"][year] += nb_seconds
1294
- quotas[person_id]["year"]["count"][year] += 1
1295
-
1296
-
1297
- def _init_quota_date(quotas, person_id, date_str, week, month):
1345
+ if entry_id not in quotas:
1346
+ _init_quota_entry(quotas, entry_id)
1347
+ _init_quota_date(quotas, entry_id, date_str, week, month)
1348
+ quotas[entry_id]["day"]["frames"][date_str] += nb_frames
1349
+ quotas[entry_id]["day"]["seconds"][date_str] += nb_seconds
1350
+ quotas[entry_id]["day"]["drawings"][date_str] += nb_drawings
1351
+ quotas[entry_id]["day"]["count"][date_str] += 1
1352
+ quotas[entry_id]["week"]["frames"][week] += nb_frames
1353
+ quotas[entry_id]["week"]["seconds"][week] += nb_seconds
1354
+ quotas[entry_id]["week"]["drawings"][week] += nb_drawings
1355
+ quotas[entry_id]["week"]["count"][week] += 1
1356
+ quotas[entry_id]["month"]["frames"][month] += nb_frames
1357
+ quotas[entry_id]["month"]["seconds"][month] += nb_seconds
1358
+ quotas[entry_id]["month"]["drawings"][month] += nb_drawings
1359
+ quotas[entry_id]["month"]["count"][month] += 1
1360
+ quotas[entry_id]["year"]["frames"][year] += nb_frames
1361
+ quotas[entry_id]["year"]["drawings"][year] += nb_drawings
1362
+ quotas[entry_id]["year"]["seconds"][year] += nb_seconds
1363
+ quotas[entry_id]["year"]["count"][year] += 1
1364
+
1365
+
1366
+ def _init_quota_date(quotas, entry_id, date_str, week, month):
1298
1367
  year = week[:4]
1299
- if date_str not in quotas[person_id]["day"]["frames"]:
1300
- quotas[person_id]["day"]["frames"][date_str] = 0
1301
- quotas[person_id]["day"]["seconds"][date_str] = 0
1302
- quotas[person_id]["day"]["count"][date_str] = 0
1303
- if month not in quotas[person_id]["day"]["entries"]:
1304
- quotas[person_id]["day"]["entries"][month] = 0
1305
- quotas[person_id]["day"]["entries"][month] += 1
1306
- if week not in quotas[person_id]["week"]["frames"]:
1307
- quotas[person_id]["week"]["frames"][week] = 0
1308
- quotas[person_id]["week"]["seconds"][week] = 0
1309
- quotas[person_id]["week"]["count"][week] = 0
1310
- if year not in quotas[person_id]["week"]["entries"]:
1311
- quotas[person_id]["week"]["entries"][year] = 0
1312
- quotas[person_id]["week"]["entries"][year] += 1
1313
- if month not in quotas[person_id]["month"]["frames"]:
1314
- quotas[person_id]["month"]["frames"][month] = 0
1315
- quotas[person_id]["month"]["seconds"][month] = 0
1316
- quotas[person_id]["month"]["count"][month] = 0
1317
- if year not in quotas[person_id]["month"]["entries"]:
1318
- quotas[person_id]["month"]["entries"][year] = 0
1319
- quotas[person_id]["month"]["entries"][year] += 1
1320
- if year not in quotas[person_id]["year"]["frames"]:
1321
- quotas[person_id]["year"]["frames"][year] = 0
1322
- quotas[person_id]["year"]["seconds"][year] = 0
1323
- quotas[person_id]["year"]["count"][year] = 0
1324
-
1325
-
1326
- def _init_quota_person(quotas, person_id):
1327
- quotas[person_id] = {}
1328
- quotas[person_id] = {
1329
- "day": {"frames": {}, "seconds": {}, "count": {}, "entries": {}},
1330
- "week": {"frames": {}, "seconds": {}, "count": {}, "entries": {}},
1331
- "month": {"frames": {}, "seconds": {}, "count": {}, "entries": {}},
1332
- "year": {"frames": {}, "seconds": {}, "count": {}},
1368
+ if date_str not in quotas[entry_id]["day"]["frames"]:
1369
+ quotas[entry_id]["day"]["frames"][date_str] = 0
1370
+ quotas[entry_id]["day"]["seconds"][date_str] = 0
1371
+ quotas[entry_id]["day"]["count"][date_str] = 0
1372
+ quotas[entry_id]["day"]["drawings"][date_str] = 0
1373
+ if month not in quotas[entry_id]["day"]["entries"]:
1374
+ quotas[entry_id]["day"]["entries"][month] = 0
1375
+ quotas[entry_id]["day"]["entries"][month] += 1
1376
+ if week not in quotas[entry_id]["week"]["frames"]:
1377
+ quotas[entry_id]["week"]["frames"][week] = 0
1378
+ quotas[entry_id]["week"]["seconds"][week] = 0
1379
+ quotas[entry_id]["week"]["count"][week] = 0
1380
+ quotas[entry_id]["week"]["drawings"][week] = 0
1381
+ if year not in quotas[entry_id]["week"]["entries"]:
1382
+ quotas[entry_id]["week"]["entries"][year] = 0
1383
+ quotas[entry_id]["week"]["entries"][year] += 1
1384
+ if month not in quotas[entry_id]["month"]["frames"]:
1385
+ quotas[entry_id]["month"]["frames"][month] = 0
1386
+ quotas[entry_id]["month"]["seconds"][month] = 0
1387
+ quotas[entry_id]["month"]["count"][month] = 0
1388
+ quotas[entry_id]["month"]["drawings"][month] = 0
1389
+ if year not in quotas[entry_id]["month"]["entries"]:
1390
+ quotas[entry_id]["month"]["entries"][year] = 0
1391
+ quotas[entry_id]["month"]["entries"][year] += 1
1392
+ if year not in quotas[entry_id]["year"]["frames"]:
1393
+ quotas[entry_id]["year"]["frames"][year] = 0
1394
+ quotas[entry_id]["year"]["seconds"][year] = 0
1395
+ quotas[entry_id]["year"]["count"][year] = 0
1396
+ quotas[entry_id]["year"]["drawings"][year] = 0
1397
+
1398
+
1399
+ def _init_quota_entry(quotas, entry_id):
1400
+ quotas[entry_id] = {
1401
+ "day": {
1402
+ "frames": {},
1403
+ "seconds": {},
1404
+ "count": {},
1405
+ "entries": {},
1406
+ "drawings": {},
1407
+ },
1408
+ "week": {
1409
+ "frames": {},
1410
+ "seconds": {},
1411
+ "count": {},
1412
+ "entries": {},
1413
+ "drawings": {},
1414
+ },
1415
+ "month": {
1416
+ "frames": {},
1417
+ "seconds": {},
1418
+ "count": {},
1419
+ "entries": {},
1420
+ "drawings": {},
1421
+ },
1422
+ "year": {"frames": {}, "seconds": {}, "count": {}, "drawings": {}},
1333
1423
  }
1334
1424
 
1335
1425
 
@@ -1487,7 +1577,6 @@ def get_weighted_quota_shots_between(
1487
1577
  shot = already_listed[shot["id"]]
1488
1578
  shot["weight"] += round(duration / task_duration, 2)
1489
1579
 
1490
- print(start, end)
1491
1580
  if type(start) is str:
1492
1581
  start = date_helpers.get_datetime_from_string(start)
1493
1582
  if type(end) is str:
@@ -19,22 +19,25 @@ DEFAULT_RETAKE_STATS = {
19
19
  "done": {
20
20
  "count": 0,
21
21
  "frames": 0,
22
+ "drawings": 0,
22
23
  },
23
24
  "retake": {
24
25
  "count": 0,
25
26
  "frames": 0,
27
+ "drawings": 0,
26
28
  },
27
29
  "other": {
28
30
  "count": 0,
29
31
  "frames": 0,
32
+ "drawings": 0,
30
33
  },
31
34
  }
32
35
 
33
36
 
34
37
  DEFAULT_EVOLUTION_STATS = {
35
- "done": {"count": 0, "frames": 0},
36
- "retake": {"count": 0, "frames": 0},
37
- "other": {"count": 0, "frames": 0},
38
+ "done": {"count": 0, "frames": 0, "drawings": 0},
39
+ "retake": {"count": 0, "frames": 0, "drawings": 0},
40
+ "other": {"count": 0, "frames": 0, "drawings": 0},
38
41
  }
39
42
 
40
43
 
@@ -94,6 +97,7 @@ def _get_episode_counts(project_id, only_assigned=False):
94
97
  TaskStatus.color,
95
98
  )
96
99
  .add_columns(func.count(Task.id))
100
+ .add_columns(func.sum(Task.nb_drawings))
97
101
  .add_columns(func.sum(Entity.nb_frames))
98
102
  )
99
103
 
@@ -112,6 +116,7 @@ def add_entry_to_stats(
112
116
  task_status_short_name,
113
117
  task_status_color,
114
118
  task_count,
119
+ task_nb_drawings,
115
120
  entity_nb_frames,
116
121
  ):
117
122
  """
@@ -129,6 +134,7 @@ def add_entry_to_stats(
129
134
  "color": task_status_color,
130
135
  "count": task_count,
131
136
  "frames": entity_nb_frames or 0,
137
+ "drawings": task_nb_drawings or 0,
132
138
  }
133
139
 
134
140
  # Aggregate for episode
@@ -140,12 +146,16 @@ def add_entry_to_stats(
140
146
  "color": task_status_color,
141
147
  "count": 0,
142
148
  "frames": 0,
149
+ "drawings": 0,
143
150
  },
144
151
  )
145
152
  results[episode_id]["all"][task_status_id]["count"] += task_count or 0
146
153
  results[episode_id]["all"][task_status_id]["frames"] += (
147
154
  entity_nb_frames or 0
148
155
  )
156
+ results[episode_id]["all"][task_status_id]["drawings"] += (
157
+ task_nb_drawings or 0
158
+ )
149
159
 
150
160
 
151
161
  def add_entry_to_all_stats(
@@ -157,6 +167,7 @@ def add_entry_to_all_stats(
157
167
  task_status_short_name,
158
168
  task_status_color,
159
169
  task_count,
170
+ task_nb_drawings,
160
171
  entity_nb_frames,
161
172
  ):
162
173
  """
@@ -175,9 +186,13 @@ def add_entry_to_all_stats(
175
186
  "color": task_status_color,
176
187
  "count": 0,
177
188
  "frames": 0,
189
+ "drawings": 0,
178
190
  },
179
191
  )
180
192
  results["all"][task_type_id][task_status_id]["count"] += task_count or 0
193
+ results["all"][task_type_id][task_status_id]["drawings"] += (
194
+ task_nb_drawings or 0
195
+ )
181
196
  results["all"][task_type_id][task_status_id]["frames"] += (
182
197
  entity_nb_frames or 0
183
198
  )
@@ -189,10 +204,12 @@ def add_entry_to_all_stats(
189
204
  "color": task_status_color,
190
205
  "count": 0,
191
206
  "frames": 0,
207
+ "drawings": 0,
192
208
  },
193
209
  )
194
210
  results["all"]["all"][task_status_id]["count"] += task_count or 0
195
211
  results["all"]["all"][task_status_id]["frames"] += entity_nb_frames or 0
212
+ results["all"]["all"][task_status_id]["drawings"] += task_nb_drawings or 0
196
213
 
197
214
 
198
215
  def get_episode_retake_stats_for_project(project_id, only_assigned=False):
@@ -207,11 +224,13 @@ def get_episode_retake_stats_for_project(project_id, only_assigned=False):
207
224
  "1": {
208
225
  "retake": {
209
226
  "count": 80,
210
- "frames": 7900
227
+ "frames": 7900,
228
+ "drawings": 8000
211
229
  },
212
230
  "done": {
213
231
  "count": 117,
214
232
  "frames": 3900
233
+ "drawings": 8000
215
234
  }
216
235
  },
217
236
  "2": {
@@ -226,15 +245,18 @@ def get_episode_retake_stats_for_project(project_id, only_assigned=False):
226
245
  },
227
246
  "done": {
228
247
  "count": 197,
229
- "frames": 16090
248
+ "frames": 16090,
249
+ "drawings": 16090
230
250
  },
231
251
  "retake": {
232
252
  "count": 0,
233
- "frames": 0
253
+ "frames": 0,
254
+ "drawings": 0
234
255
  },
235
256
  "other": {
236
257
  "count": 5,
237
- "frames": 185
258
+ "frames": 185,
259
+ "drawings": 185
238
260
  }
239
261
 
240
262
  },
@@ -244,6 +266,7 @@ def get_episode_retake_stats_for_project(project_id, only_assigned=False):
244
266
  query_results = query.all()
245
267
  for (
246
268
  episode_id,
269
+ nb_drawings,
247
270
  nb_frames,
248
271
  task_type_id,
249
272
  retake_count,
@@ -262,12 +285,14 @@ def get_episode_retake_stats_for_project(project_id, only_assigned=False):
262
285
  is_done,
263
286
  retake_count,
264
287
  nb_frames,
288
+ nb_drawings,
265
289
  )
266
290
 
267
291
  # Another loop is needed because we need to know the max retake count
268
292
  # for each entries prior to build evolution stats.
269
293
  for (
270
294
  episode_id,
295
+ nb_drawings,
271
296
  nb_frames,
272
297
  task_type_id,
273
298
  retake_count,
@@ -282,6 +307,7 @@ def get_episode_retake_stats_for_project(project_id, only_assigned=False):
282
307
  is_done,
283
308
  retake_count,
284
309
  nb_frames,
310
+ nb_drawings,
285
311
  )
286
312
  return results
287
313
 
@@ -292,6 +318,7 @@ def _get_retake_stats_query(project_id, only_assigned):
292
318
  query = (
293
319
  Task.query.with_entities(
294
320
  Episode.id,
321
+ Task.nb_drawings,
295
322
  Entity.nb_frames,
296
323
  Task.task_type_id,
297
324
  Task.retake_count,
@@ -328,6 +355,7 @@ def _add_stats(
328
355
  is_done,
329
356
  retake_count,
330
357
  nb_frames,
358
+ nb_drawings,
331
359
  ):
332
360
  for key1, key2 in [
333
361
  ("all", "all"),
@@ -345,12 +373,15 @@ def _add_stats(
345
373
  # For the "current" stats we prioritize `is_done` over `is_retake`
346
374
  results[key1][key2]["done"]["count"] += 1
347
375
  results[key1][key2]["done"]["frames"] += nb_frames or 0
376
+ results[key1][key2]["done"]["drawings"] += nb_drawings or 0
348
377
  elif is_retake:
349
378
  results[key1][key2]["retake"]["count"] += 1
350
379
  results[key1][key2]["retake"]["frames"] += nb_frames or 0
380
+ results[key1][key2]["retake"]["drawings"] += nb_drawings or 0
351
381
  else:
352
382
  results[key1][key2]["other"]["count"] += 1
353
383
  results[key1][key2]["other"]["frames"] += nb_frames or 0
384
+ results[key1][key2]["other"]["drawings"] += nb_drawings or 0
354
385
  return results
355
386
 
356
387
 
@@ -362,6 +393,7 @@ def _add_evolution_stats(
362
393
  is_done,
363
394
  retake_count,
364
395
  nb_frames,
396
+ nb_drawings,
365
397
  ):
366
398
  for key1, key2 in [(episode_id, "all"), (episode_id, task_type_id)]:
367
399
  # In this loop we compute the "evolution" statistics
@@ -382,12 +414,18 @@ def _add_evolution_stats(
382
414
  evolution_data[take_number]["retake"]["frames"] += (
383
415
  nb_frames or 0
384
416
  )
417
+ evolution_data[take_number]["retake"]["drawings"] += (
418
+ nb_drawings or 0
419
+ )
385
420
  elif is_done:
386
421
  evolution_data[take_number]["done"]["count"] += 1
387
422
  evolution_data[take_number]["done"]["frames"] += nb_frames or 0
423
+ evolution_data[take_number]["done"]["drawings"] += (
424
+ nb_drawings or 0
425
+ )
388
426
  else:
389
427
  evolution_data[take_number]["other"]["count"] += 1
390
- evolution_data[take_number]["other"]["frames"] += (
391
- nb_frames or 0
428
+ evolution_data[take_number]["other"]["drawings"] += (
429
+ nb_drawings or 0
392
430
  )
393
431
  return results
@@ -497,11 +497,15 @@ def check_task_action_access(task_id):
497
497
  if permissions.has_admin_permissions():
498
498
  is_allowed = True
499
499
  elif check_belong_to_project(task["project_id"]):
500
- if permissions.has_manager_permissions():
500
+ if (
501
+ permissions.has_manager_permissions()
502
+ or permissions.has_client_permissions()
503
+ ):
501
504
  is_allowed = True
502
505
  else:
503
506
  user = persons_service.get_current_user(relations=True)
504
- if permissions.has_supervisor_permissions():
507
+ is_allowed = user["id"] in task["assignees"]
508
+ if not is_allowed and permissions.has_supervisor_permissions():
505
509
  is_allowed = (
506
510
  user["departments"] == []
507
511
  or tasks_service.get_task_type(task["task_type_id"])[
@@ -509,8 +513,6 @@ def check_task_action_access(task_id):
509
513
  ]
510
514
  in user["departments"]
511
515
  )
512
- else:
513
- is_allowed = user["id"] in task["assignees"]
514
516
  else:
515
517
  is_allowed = False
516
518
 
zou/app/utils/commands.py CHANGED
@@ -324,7 +324,7 @@ def sync_with_ldap_server():
324
324
  {
325
325
  "first_name": clean_value(entry.givenName or entry.cn),
326
326
  "last_name": clean_value(entry.sn),
327
- "email": emails[0],
327
+ "email": emails[0].lower(),
328
328
  "emails": emails,
329
329
  "desktop_login": desktop_login,
330
330
  "thumbnail": thumbnail,
@@ -0,0 +1,35 @@
1
+ """allow to manage drawings instead of frames
2
+
3
+ Revision ID: addbad59c706
4
+ Revises: 20a8ad264659
5
+ Create Date: 2025-02-24 11:06:34.925091
6
+
7
+ """
8
+
9
+ from alembic import op
10
+ import sqlalchemy as sa
11
+
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "addbad59c706"
15
+ down_revision = "06552e22f9e7"
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade():
21
+ # ### commands auto generated by Alembic - please adjust! ###
22
+ with op.batch_alter_table("task", schema=None) as batch_op:
23
+ batch_op.add_column(
24
+ sa.Column("nb_drawings", sa.Integer(), nullable=True)
25
+ )
26
+
27
+ # ### end Alembic commands ###
28
+
29
+
30
+ def downgrade():
31
+ # ### commands auto generated by Alembic - please adjust! ###
32
+ with op.batch_alter_table("task", schema=None) as batch_op:
33
+ batch_op.drop_column("nb_drawings")
34
+
35
+ # ### end Alembic commands ###
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: zou
3
- Version: 0.20.21
3
+ Version: 0.20.23
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
@@ -23,7 +23,7 @@ Requires-Python: >=3.9, <3.14
23
23
  License-File: LICENSE
24
24
  Requires-Dist: babel==2.17.0
25
25
  Requires-Dist: click==8.1.8
26
- Requires-Dist: discord.py==2.5.0
26
+ Requires-Dist: discord.py==2.5.2
27
27
  Requires-Dist: email-validator==2.2.0
28
28
  Requires-Dist: ffmpeg-python==0.2.0
29
29
  Requires-Dist: fido2==1.2.0
@@ -40,13 +40,13 @@ Requires-Dist: flask-jwt-extended==4.7.1
40
40
  Requires-Dist: flask-migrate==4.1.0
41
41
  Requires-Dist: flask-socketio==5.5.1
42
42
  Requires-Dist: flask==3.1.0
43
- Requires-Dist: gazu==0.10.29
43
+ Requires-Dist: gazu==0.10.30
44
44
  Requires-Dist: gevent-websocket==0.10.1
45
45
  Requires-Dist: gevent==24.11.1
46
46
  Requires-Dist: gunicorn==23.0.0
47
47
  Requires-Dist: isoweek==1.3.3
48
48
  Requires-Dist: itsdangerous==2.2.0
49
- Requires-Dist: Jinja2==3.1.5
49
+ Requires-Dist: Jinja2==3.1.6
50
50
  Requires-Dist: ldap3==2.9.1
51
51
  Requires-Dist: matterhook==0.2
52
52
  Requires-Dist: meilisearch==0.34.0
@@ -82,7 +82,7 @@ Provides-Extra: test
82
82
  Requires-Dist: fakeredis==2.27.0; extra == "test"
83
83
  Requires-Dist: mixer==7.2.2; extra == "test"
84
84
  Requires-Dist: pytest-cov==6.0.0; extra == "test"
85
- Requires-Dist: pytest==8.3.4; extra == "test"
85
+ Requires-Dist: pytest==8.3.5; extra == "test"
86
86
  Provides-Extra: monitoring
87
87
  Requires-Dist: prometheus-flask-exporter==0.23.1; extra == "monitoring"
88
88
  Requires-Dist: pygelf==0.4.2; extra == "monitoring"
@@ -1,4 +1,4 @@
1
- zou/__init__.py,sha256=q-f3cQMtYC1aJHq5j-sBq9YJTtfqyzELRPrqcQwksrA,24
1
+ zou/__init__.py,sha256=x4_zHRUIBHYylJp20ojxmrTuyJId9rcESyxmdsUn08Y,24
2
2
  zou/cli.py,sha256=HuYi2Ma7SP2SD7C9d9dwpZ49BHpytKIoyJP_su9JwZY,18755
3
3
  zou/debug.py,sha256=1fawPbkD4wn0Y9Gk0BiBFSa-CQe5agFi8R9uJYl2Uyk,520
4
4
  zou/event_stream.py,sha256=EpohqFJwWL0zs-Ic_W5dX5_XSDeCrqHQPL5Re39OnQ0,6382
@@ -18,7 +18,7 @@ zou/app/blueprints/breakdown/resources.py,sha256=pmGlHLiXFsPRbxf403SiVgGiaBbtK8G
18
18
  zou/app/blueprints/chats/__init__.py,sha256=YGmwGvddg3MgSYVIh-hmkX8t2em9_LblxBeJzFqFJD4,558
19
19
  zou/app/blueprints/chats/resources.py,sha256=4yLFermdwOsnBLs9nx8yxuHWLar24uQWQy0XgsUNDD0,5950
20
20
  zou/app/blueprints/comments/__init__.py,sha256=WqpJ7-_dK1cInGTFJAxQ7syZtPCotwq2oO20UEnk1h4,1532
21
- zou/app/blueprints/comments/resources.py,sha256=MMYfJN0wERVXJ1WcsKFS_WXQTw26YmEUwv0-NzIWefk,18830
21
+ zou/app/blueprints/comments/resources.py,sha256=o_izuBb4Z2yNMpRa1rfS8Shf4PTFP5zuSbw51I-wS8g,19110
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
@@ -91,13 +91,13 @@ zou/app/blueprints/persons/resources.py,sha256=PfK6epzRn_kbqN6g9qYiH9XWStFlccTVC
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=ihC6OQ9AUjnZ2JeMnjRh_tKGO0UmAjOwhZnOivc3BnQ,4460
94
- zou/app/blueprints/previews/resources.py,sha256=sZEFwSExmwXbF92ajWcLQGCl3VDCWdLfPhRyiPro9AE,53642
94
+ zou/app/blueprints/previews/resources.py,sha256=i_BwkcPLKh-ktxckwcesXddgXmA9D6JnSTZjkEnL8uE,53551
95
95
  zou/app/blueprints/projects/__init__.py,sha256=Pn3fA5bpNFEPBzxTKJ2foV6osZFflXXSM2l2uZh3ktM,3927
96
96
  zou/app/blueprints/projects/resources.py,sha256=1WBS2FyaY1RSA_T-BdPnc8X9myjTJ127bMDigyoAklk,31979
97
97
  zou/app/blueprints/search/__init__.py,sha256=QCjQIY_85l_orhdEiqav_GifjReuwsjZggN3V0GeUVY,356
98
98
  zou/app/blueprints/search/resources.py,sha256=_QgRlUuxCPgY-ip5r2lGFtXNcGSE579JsCSrVf8ajVU,3093
99
- zou/app/blueprints/shots/__init__.py,sha256=HfgLneZBYUMa2OGwIgEZTz8zrIEYFRiYmRbreBPYeYw,4076
100
- zou/app/blueprints/shots/resources.py,sha256=6YUtyDE-auVwm5DTeD84sB9w2R15KnN9wBY4aIKmz2Q,49936
99
+ zou/app/blueprints/shots/__init__.py,sha256=EcG9qmAchlucqg1M6-RqWGfuKpa5Kq6RgyLZNSsjUr4,4225
100
+ zou/app/blueprints/shots/resources.py,sha256=WOWvYOEVxv3mu1PKTFg6lGoTfM_UQ5n-iJTUFuZKMh8,52162
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=nTXQEauFinPv2QBXziJW83rSrB_qzIbkFQ_qgxbJynA,13419
@@ -163,7 +163,7 @@ zou/app/models/person.py,sha256=txHmSzokaS-tET_MIjGHxhNNS8CPt-GzUPIhp5baDmU,7714
163
163
  zou/app/models/playlist.py,sha256=YGgAk84u0_fdIEY02Dal4kfk8APVZvWFwWYV74qvrio,1503
164
164
  zou/app/models/preview_background_file.py,sha256=j8LgRmY7INnlB07hFwwB-8ssQrRC8vsb8VcpsTbt6tA,559
165
165
  zou/app/models/preview_file.py,sha256=eDPXw0QIdJze_E4kAS8SsyabrefWhIIdwuGmjss7iXo,3282
166
- zou/app/models/project.py,sha256=m9Rhlfa8K21-xuNJUIadaYGuj5hMnC0yzJcxXcqoCsE,9162
166
+ zou/app/models/project.py,sha256=mXoLK7fEwDRGd21AoQadgV9K0AgoxYwTb5Sdf_cezlY,9203
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
169
  zou/app/models/search_filter.py,sha256=Q699mJa3GGwFEva93Y_nHiIcx3BDht9VsAe2zeQL0FA,1242
@@ -173,13 +173,13 @@ zou/app/models/software.py,sha256=3y9xEOS8BBxqgTaRrCaSYHaB6uOdJ88uU5i-cACoCSk,51
173
173
  zou/app/models/status_automation.py,sha256=95c0lmOetujyGWViLd_qsR4GWPhrmlvi9ZfkC0x1NuM,1391
174
174
  zou/app/models/studio.py,sha256=cXnYFh-8m5Kw05QawYcCr59eeLp25NDI0VIb77oqUOs,384
175
175
  zou/app/models/subscription.py,sha256=kMfPPe8uFJMDWB4B9MpWaXZD86FFZlj7ty0FkVyqE_4,1054
176
- zou/app/models/task.py,sha256=kC0ufy82n9Vj5f9r_pbx74szSJMM0LPIlrWIlAnTkD8,3615
176
+ zou/app/models/task.py,sha256=luZNdb30rnRPampD-_oxuhi_rZ8ECXC0v2RwChNKAjo,3667
177
177
  zou/app/models/task_status.py,sha256=J7mNWIFd9KMWfUOLHjmuxIupjs59iSh8aQ_9LEFXqH4,1227
178
178
  zou/app/models/task_type.py,sha256=IsixVAfz3pyMf0eQw8x-uFNM9OHNkZpsPLEz_VNQ0hA,1005
179
179
  zou/app/models/time_spent.py,sha256=n7i3FO9g1eE_zATkItoCgrGVqq3iMSfdlKSveEZPloc,795
180
180
  zou/app/models/working_file.py,sha256=q0LM3s1ziw_9AmmPDCkwyf1-TJkWTBMgo2LdHyVRwxg,1509
181
181
  zou/app/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
182
- zou/app/services/assets_service.py,sha256=Hj1dznCFM86HxAIRIRXEcGwI9lCtYpzurhusVqOVrMc,24139
182
+ zou/app/services/assets_service.py,sha256=l4RiMKLmxiiK8hWC10lAXnH-G_7rXcbAZwOTE16pEr8,24185
183
183
  zou/app/services/auth_service.py,sha256=hQpNb21xlr5EiTrXnzpFb4W4GDtglubqA2z_-YIBfnk,22897
184
184
  zou/app/services/backup_service.py,sha256=_ZtZp6wkcVYnHxBosziwLGdrTvsUttXGphiydq53iy8,4840
185
185
  zou/app/services/base_service.py,sha256=OZd0STFh-DyBBdwsmA7DMMnrwv4C8wJUbShvZ1isndU,1383
@@ -206,14 +206,14 @@ zou/app/services/preview_files_service.py,sha256=Yk-vwzHuKTzNkEZfl9DhQRdDuRU006u
206
206
  zou/app/services/projects_service.py,sha256=aIbYaFomy7OX2Pxvkf9w5qauDvkjuc9ummSGNYIpQMY,21249
207
207
  zou/app/services/scenes_service.py,sha256=iXN19HU4njPF5VtZXuUrVJ-W23ZQuQNPC3ADXltbWtU,992
208
208
  zou/app/services/schedule_service.py,sha256=E99HKYsXgnK2sw58fw-NNHXWBgVJiA60upztjkNSCaM,6989
209
- zou/app/services/shots_service.py,sha256=JEBywQGSKz1R7uXM8wtQQebMhWQZbGrWmTS5nOyn_Js,52191
210
- zou/app/services/stats_service.py,sha256=cAlc92i9d6eYtsuwe3hYHYwdytg8KEMi1-TADfysJwM,11733
209
+ zou/app/services/shots_service.py,sha256=UW7QcW2Gi_OsbBuR_96FCjNKnsm3SH2R1AcBRnL9pzo,54803
210
+ zou/app/services/stats_service.py,sha256=e9h090eZWADtzXycy1WOup_jlxGwQojrr1y_PDcVatc,13156
211
211
  zou/app/services/status_automations_service.py,sha256=tVio7Sj7inhvKS4UOyRhcdpwr_KNP96hT1o0X7XcGF4,715
212
212
  zou/app/services/sync_service.py,sha256=iWxx1kOGEXympHmSBBQWtDZWNtumdxp8kppee0OefMo,41811
213
213
  zou/app/services/tasks_service.py,sha256=yysijKuLm2WtkCI6WvfLLYiwoM91TB_N0ZazoxEfco4,69825
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=krNiMcp7PL_Hs-m-YAiLJ9m0pzedgKz7MmW4qG4_EJQ,51289
216
+ zou/app/services/user_service.py,sha256=SKW6n3eMRDUJljUE893cYUrg00xyUwUkxLHzQHjIZT0,51362
217
217
  zou/app/stores/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
218
218
  zou/app/stores/auth_tokens_store.py,sha256=-qOJPybLHvnMOq3PWk073OW9HJwOHGhFLZeOIlX1UVw,1290
219
219
  zou/app/stores/file_store.py,sha256=yLQDM6mNbj9oe0vsWdBqun7D8Dw-eSjD1yHCCftX0OI,4045
@@ -225,7 +225,7 @@ zou/app/utils/auth.py,sha256=DZfZSr1Ulge0UK3hfvOWsMo3_d7RVP_llV118u9BtUI,870
225
225
  zou/app/utils/cache.py,sha256=MRluTvGG67ybOkyzgD70B6PGKMdRyFdTc0AYy3dEQe8,1210
226
226
  zou/app/utils/chats.py,sha256=ORngxQ3IQQF0QcVFJLxJ-RaU4ksQ9-0M8cmPa0pc0Ho,4302
227
227
  zou/app/utils/colors.py,sha256=LaGV17NL_8xY0XSp8snGWz5UMwGnm0KPWXyE5BTMG6w,200
228
- zou/app/utils/commands.py,sha256=ztGq2x5GQJWXDFyHrBV9Cm0cBRWxzEH3zFkPeOD3D0U,27943
228
+ zou/app/utils/commands.py,sha256=7PeiQ9YFeta4onlsho4Cabjt34SUkmy1w3KW0v_gphs,27951
229
229
  zou/app/utils/csv_utils.py,sha256=GiI8SeUqmIh9o1JwhZGkQXU_0K0EcPrRHYIZ8bMoYzk,1228
230
230
  zou/app/utils/date_helpers.py,sha256=jFxDPCbAasg0I1gsC72AKEbGcx5c4pLqXZkSfZ4wLdQ,4724
231
231
  zou/app/utils/dbhelpers.py,sha256=RSJuoxLexGJyME16GQCs-euFLBR0u-XAFdJ1KMSv5M8,1143
@@ -369,6 +369,7 @@ zou/migrations/versions/a66508788c53_add_nb_assets_ready.py,sha256=ph1xtl0uBhGXj
369
369
  zou/migrations/versions/a6c25eed3ea1_add_login_failed_attemps_and_last_login_.py,sha256=r2vhaMGmG9xMAcmCBpWujtI3sMebc_XXFuX8_6qybpg,908
370
370
  zou/migrations/versions/a7c43f3fbc76_add_duration_column_to_the_preview_file.py,sha256=9GgEM7GXZcCrX0TF8MYM7uSCR5sJJTmCP2hCaCqRP6w,825
371
371
  zou/migrations/versions/aa0a60033106_feedback_request.py,sha256=okfgw-Rvyfs1RQDolkx3yQHI9K5tMuks8W7mwvzqw_c,1002
372
+ zou/migrations/versions/addbad59c706_allow_to_manage_drawings_instead_of_.py,sha256=ip3RWFUJ5fqJDu99UoCu0Dk8fRrD7x_K5a7qbaYKCNA,844
372
373
  zou/migrations/versions/addbbefa7028_add_departments_link_to_metadata_.py,sha256=XV1r9S5ua7xNYI9UgrTKEkBLsBIGDbfQa2-mMYNIgEo,1359
373
374
  zou/migrations/versions/ae0127f2fc56_add_previewfile_width_and_previewfile_.py,sha256=DdbGfFcTTESmWrR77GmrJ75c7nHLbGj0-3ZecdINqKU,945
374
375
  zou/migrations/versions/af1790868e2c_.py,sha256=BsoY8uQXFcZDyipc69K4yzaeYz3j0Z6KhUCXQz8zn2s,673
@@ -418,9 +419,9 @@ zou/remote/normalize_movie.py,sha256=zNfEY3N1UbAHZfddGONTg2Sff3ieLVWd4dfZa1dpnes
418
419
  zou/remote/playlist.py,sha256=AsDo0bgYhDcd6DfNRV6r6Jj3URWwavE2ZN3VkKRPbLU,3293
419
420
  zou/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
420
421
  zou/utils/movie.py,sha256=d67fIL9dVBKt-E_qCGXRbNNdbJaJR5sHvZeX3hf8ldE,16559
421
- zou-0.20.21.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
422
- zou-0.20.21.dist-info/METADATA,sha256=AGg5JHLs6j1WpwQtvy8P7Ho2XoG6uI1hS3U3hYP9aYc,6673
423
- zou-0.20.21.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
424
- zou-0.20.21.dist-info/entry_points.txt,sha256=PelQoIx3qhQ_Tmne7wrLY-1m2izuzgpwokoURwSohy4,130
425
- zou-0.20.21.dist-info/top_level.txt,sha256=4S7G_jk4MzpToeDItHGjPhHx_fRdX52zJZWTD4SL54g,4
426
- zou-0.20.21.dist-info/RECORD,,
422
+ zou-0.20.23.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
423
+ zou-0.20.23.dist-info/METADATA,sha256=ogjGsLoBuHL-ZAf11x2IJZGr1SYLisEwb3RuVs12vFI,6673
424
+ zou-0.20.23.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
425
+ zou-0.20.23.dist-info/entry_points.txt,sha256=PelQoIx3qhQ_Tmne7wrLY-1m2izuzgpwokoURwSohy4,130
426
+ zou-0.20.23.dist-info/top_level.txt,sha256=4S7G_jk4MzpToeDItHGjPhHx_fRdX52zJZWTD4SL54g,4
427
+ zou-0.20.23.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
File without changes