zou 0.19.48__py3-none-any.whl → 0.19.50__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.48"
1
+ __version__ = "0.19.50"
@@ -308,7 +308,7 @@ class PersonDurationTimeSpentsResource(Resource, ArgsMixin):
308
308
  raise permissions.PermissionDenied
309
309
  if permissions.has_supervisor_permissions():
310
310
  department_ids = persons_service.get_current_user(
311
- True
311
+ relations=True
312
312
  )["departments"]
313
313
  else:
314
314
  raise permissions.PermissionDenied
@@ -533,7 +533,24 @@ class PersonDayTimeSpentsResource(PersonDurationTimeSpentsResource):
533
533
  abort(404)
534
534
 
535
535
 
536
- class PersonMonthQuotaShotsResource(Resource, ArgsMixin):
536
+ class PersonQuotaMixin:
537
+
538
+ def get_quota_arguments(self):
539
+ project_id = self.get_project_id()
540
+ task_type_id = self.get_task_type_id()
541
+ count_mode = self.get_text_parameter("count_mode", default="weigthed")
542
+ if count_mode not in ["weighted", "weighteddone", "feedback", "done"]:
543
+ raise WrongParameterException(
544
+ "count_mode must be equal to weighted, weigtheddone, feedback"
545
+ ", or done"
546
+ )
547
+ feedback = "done" not in count_mode
548
+ weighted = "weighted" in count_mode
549
+
550
+ return (project_id, task_type_id, count_mode, feedback, weighted)
551
+
552
+
553
+ class PersonMonthQuotaShotsResource(Resource, ArgsMixin, PersonQuotaMixin):
537
554
  """
538
555
  Get ended shots used for quota calculation of this month.
539
556
  """
@@ -564,6 +581,12 @@ class PersonMonthQuotaShotsResource(Resource, ArgsMixin):
564
581
  x-example: 07
565
582
  minimum: 1
566
583
  maximum: 12
584
+ - in: query
585
+ name: count_mode
586
+ required: True
587
+ type: string
588
+ enum: [weighted, weigtheddone, feedback, done]
589
+ x-example: weighted
567
590
  responses:
568
591
  200:
569
592
  description: Ended shots used for quota calculation of this month
@@ -571,10 +594,11 @@ class PersonMonthQuotaShotsResource(Resource, ArgsMixin):
571
594
  description: Wrong date format
572
595
  """
573
596
  user_service.check_person_is_not_bot(person_id)
574
- project_id = self.get_project_id()
575
- task_type_id = self.get_task_type_id()
576
597
  user_service.check_person_access(person_id)
577
- weighted = self.get_bool_parameter("weighted", default="true")
598
+ (project_id, task_type_id, count_mode, feedback, weighted) = (
599
+ self.get_quota_arguments()
600
+ )
601
+
578
602
  try:
579
603
  return shots_service.get_month_quota_shots(
580
604
  person_id,
@@ -583,12 +607,13 @@ class PersonMonthQuotaShotsResource(Resource, ArgsMixin):
583
607
  project_id=project_id,
584
608
  task_type_id=task_type_id,
585
609
  weighted=weighted,
610
+ feedback=feedback,
586
611
  )
587
612
  except WrongDateFormatException:
588
613
  abort(404)
589
614
 
590
615
 
591
- class PersonWeekQuotaShotsResource(Resource, ArgsMixin):
616
+ class PersonWeekQuotaShotsResource(Resource, ArgsMixin, PersonQuotaMixin):
592
617
  """
593
618
  Get ended shots used for quota calculation of this week.
594
619
  """
@@ -619,6 +644,12 @@ class PersonWeekQuotaShotsResource(Resource, ArgsMixin):
619
644
  x-example: 35
620
645
  minimum: 1
621
646
  maximum: 52
647
+ - in: query
648
+ name: count_mode
649
+ required: True
650
+ type: string
651
+ enum: [weighted, weigtheddone, feedback, done]
652
+ x-example: weighted
622
653
  responses:
623
654
  200:
624
655
  description: Ended shots used for quota calculation of this week
@@ -626,10 +657,11 @@ class PersonWeekQuotaShotsResource(Resource, ArgsMixin):
626
657
  description: Wrong date format
627
658
  """
628
659
  user_service.check_person_is_not_bot(person_id)
629
- project_id = self.get_project_id()
630
- task_type_id = self.get_task_type_id()
631
660
  user_service.check_person_access(person_id)
632
- weighted = self.get_bool_parameter("weighted", default="true")
661
+ (project_id, task_type_id, count_mode, feedback, weighted) = (
662
+ self.get_quota_arguments()
663
+ )
664
+
633
665
  try:
634
666
  return shots_service.get_week_quota_shots(
635
667
  person_id,
@@ -638,12 +670,13 @@ class PersonWeekQuotaShotsResource(Resource, ArgsMixin):
638
670
  project_id=project_id,
639
671
  task_type_id=task_type_id,
640
672
  weighted=weighted,
673
+ feedback=feedback,
641
674
  )
642
675
  except WrongDateFormatException:
643
676
  abort(404)
644
677
 
645
678
 
646
- class PersonDayQuotaShotsResource(Resource, ArgsMixin):
679
+ class PersonDayQuotaShotsResource(Resource, ArgsMixin, PersonQuotaMixin):
647
680
  """
648
681
  Get ended shots used for quota calculation of this day.
649
682
  """
@@ -681,6 +714,12 @@ class PersonDayQuotaShotsResource(Resource, ArgsMixin):
681
714
  x-example: 12
682
715
  minimum: 1
683
716
  maximum: 31
717
+ - in: query
718
+ name: count_mode
719
+ required: True
720
+ type: string
721
+ enum: [weighted, weigtheddone, feedback, done]
722
+ x-example: weighted
684
723
  responses:
685
724
  200:
686
725
  description: Ended shots used for quota calculation of this day
@@ -688,10 +727,11 @@ class PersonDayQuotaShotsResource(Resource, ArgsMixin):
688
727
  description: Wrong date format
689
728
  """
690
729
  user_service.check_person_is_not_bot(person_id)
691
- project_id = self.get_project_id()
692
- task_type_id = self.get_task_type_id()
693
730
  user_service.check_person_access(person_id)
694
- weighted = self.get_bool_parameter("weighted", default="true")
731
+ (project_id, task_type_id, count_mode, feedback, weighted) = (
732
+ self.get_quota_arguments()
733
+ )
734
+
695
735
  try:
696
736
  return shots_service.get_day_quota_shots(
697
737
  person_id,
@@ -701,6 +741,7 @@ class PersonDayQuotaShotsResource(Resource, ArgsMixin):
701
741
  project_id=project_id,
702
742
  task_type_id=task_type_id,
703
743
  weighted=weighted,
744
+ feedback=feedback,
704
745
  )
705
746
  except WrongDateFormatException:
706
747
  abort(404)
@@ -1507,6 +1507,18 @@ class ProjectQuotasResource(Resource, ArgsMixin):
1507
1507
  type: string
1508
1508
  format: UUID
1509
1509
  x-example: a24a6ea4-ce75-4665-a070-57453082c25
1510
+ - in: query
1511
+ name: count_mode
1512
+ required: True
1513
+ type: string
1514
+ enum: [weighted, weigtheddone, feedback, done]
1515
+ x-example: weighted
1516
+ - in: query
1517
+ name: studio_id
1518
+ required: False
1519
+ type: string
1520
+ format: UUID
1521
+ x-example: a24a6ea4-ce75-4665-a070-57453082c25
1510
1522
  responses:
1511
1523
  200:
1512
1524
  description: Quotas statistics for shots
@@ -1515,17 +1527,35 @@ class ProjectQuotasResource(Resource, ArgsMixin):
1515
1527
  user_service.check_project_access(project_id)
1516
1528
  args = self.get_args(
1517
1529
  [
1518
- ("weighted", False, False, bool),
1530
+ ("count_mode", "weighted", False, str),
1519
1531
  ("studio_id", None, False, str),
1520
1532
  ]
1521
1533
  )
1522
- if args["weighted"]:
1534
+ count_mode = args["count_mode"]
1535
+ studio_id = args["studio_id"]
1536
+
1537
+ if count_mode not in ["weighted", "weighteddone", "feedback", "done"]:
1538
+ raise WrongParameterException(
1539
+ "count_mode must be equal to weighted, weigtheddone, feedback"
1540
+ ", or done"
1541
+ )
1542
+
1543
+ feedback = "done" not in count_mode
1544
+ weighted = "weighted" in count_mode
1545
+
1546
+ if weighted:
1523
1547
  return shots_service.get_weighted_quotas(
1524
- project_id, task_type_id, args["studio_id"]
1548
+ project_id,
1549
+ task_type_id,
1550
+ feedback=feedback,
1551
+ studio_id=studio_id,
1525
1552
  )
1526
1553
  else:
1527
1554
  return shots_service.get_raw_quotas(
1528
- project_id, task_type_id, args["studio_id"]
1555
+ project_id,
1556
+ task_type_id,
1557
+ feedback=feedback,
1558
+ studio_id=studio_id,
1529
1559
  )
1530
1560
 
1531
1561
 
zou/app/mixin.py CHANGED
@@ -14,6 +14,7 @@ class ArgsMixin(object):
14
14
  parser = reqparse.RequestParser()
15
15
  if location is None:
16
16
  location = ["values", "json"] if request.is_json else ["values"]
17
+
17
18
  for descriptor in descriptors:
18
19
  action = None
19
20
  data_type = str
zou/app/models/task.py CHANGED
@@ -42,6 +42,7 @@ class Task(db.Model, BaseMixin, SerializerMixin):
42
42
  due_date = db.Column(db.DateTime)
43
43
  real_start_date = db.Column(db.DateTime)
44
44
  end_date = db.Column(db.DateTime)
45
+ done_date = db.Column(db.DateTime)
45
46
  last_comment_date = db.Column(db.DateTime)
46
47
  nb_assets_ready = db.Column(db.Integer, default=0)
47
48
  data = db.Column(JSONB)
@@ -194,6 +194,7 @@ def get_assets_and_tasks(criterions={}, page=1, with_episode_ids=False):
194
194
  Task.end_date,
195
195
  Task.start_date,
196
196
  Task.due_date,
197
+ Task.done_date,
197
198
  Task.last_comment_date,
198
199
  assignees_table.columns.person,
199
200
  ).order_by(EntityType.name, Entity.name)
@@ -272,6 +273,7 @@ def get_assets_and_tasks(criterions={}, page=1, with_episode_ids=False):
272
273
  task_end_date,
273
274
  task_start_date,
274
275
  task_due_date,
276
+ task_done_date,
275
277
  task_last_comment_date,
276
278
  person_id,
277
279
  ) in query_result:
@@ -314,6 +316,7 @@ def get_assets_and_tasks(criterions={}, page=1, with_episode_ids=False):
314
316
  task_dict = {
315
317
  "id": task_id,
316
318
  "due_date": fields.serialize_value(task_due_date),
319
+ "done_date": fields.serialize_value(task_done_date),
317
320
  "duration": task_duration,
318
321
  "entity_id": asset_id,
319
322
  "estimation": task_estimation,
@@ -240,6 +240,7 @@ def get_entities_and_tasks(criterions={}):
240
240
  Task.end_date,
241
241
  Task.start_date,
242
242
  Task.due_date,
243
+ Task.done_date,
243
244
  Task.last_comment_date,
244
245
  assignees_table.columns.person,
245
246
  )
@@ -269,6 +270,7 @@ def get_entities_and_tasks(criterions={}):
269
270
  task_end_date,
270
271
  task_start_date,
271
272
  task_due_date,
273
+ task_done_date,
272
274
  task_last_comment_date,
273
275
  person_id,
274
276
  ) in query.all():
@@ -305,6 +307,7 @@ def get_entities_and_tasks(criterions={}):
305
307
  "entity_id": entity_id,
306
308
  "end_date": task_end_date,
307
309
  "due_date": task_due_date,
310
+ "done_date": task_done_date,
308
311
  "duration": task_duration,
309
312
  "is_subscribed": subscription_map.get(task_id, False),
310
313
  "last_comment_date": task_last_comment_date,
@@ -251,6 +251,7 @@ def get_shots_and_tasks(criterions={}):
251
251
  Task.end_date,
252
252
  Task.start_date,
253
253
  Task.due_date,
254
+ Task.done_date,
254
255
  Task.last_comment_date,
255
256
  Task.nb_assets_ready,
256
257
  assignees_table.columns.person,
@@ -300,6 +301,7 @@ def get_shots_and_tasks(criterions={}):
300
301
  task_end_date,
301
302
  task_start_date,
302
303
  task_due_date,
304
+ task_done_date,
303
305
  task_last_comment_date,
304
306
  task_nb_assets_ready,
305
307
  person_id,
@@ -355,6 +357,7 @@ def get_shots_and_tasks(criterions={}):
355
357
  "duration": task_duration,
356
358
  "due_date": task_due_date,
357
359
  "end_date": task_end_date,
360
+ "done_date": task_done_date,
358
361
  "entity_id": shot_id,
359
362
  "estimation": task_estimation,
360
363
  "is_subscribed": subscription_map.get(task_id, False),
@@ -1113,24 +1116,31 @@ def get_base_entity_type_name(entity_dict):
1113
1116
  return type_name
1114
1117
 
1115
1118
 
1116
- def get_weighted_quotas(project_id, task_type_id, studio_id=None):
1119
+ def get_weighted_quotas(
1120
+ project_id, task_type_id, studio_id=None, feedback=True
1121
+ ):
1117
1122
  """
1118
1123
  Build quota statistics. It counts the number of frames done for each day.
1119
- A shot is considered done at the first feedback request. If time spent is
1120
- filled for it, it weights the result with the frame number with the time
1121
- spents. If there is no time spent, it considers that the work was done
1122
- from the wip date to the feedback date.
1124
+ A shot is considered done at the first feedback request or at last
1125
+ approval.
1126
+
1127
+ If time spent is filled for it, it weights the result with the frame
1128
+ number with the time spents. If there is no time spent, it considers that
1129
+ the work was done from the wip date to the feedback date (or approval date).
1123
1130
  It computes the shot count and the number of seconds too.
1131
+
1132
+ If the `feedback` flag is set to True, it uses the feedback date
1133
+ (real_end_date), if feedback is set to False, it uses the approval date
1134
+ (done_date).
1124
1135
  """
1125
1136
  fps = projects_service.get_project_fps(project_id)
1126
1137
  timezone = user_service.get_timezone()
1127
1138
  shot_type = get_shot_type()
1128
1139
  quotas = {}
1129
1140
  query = (
1130
- Task.query.filter(Task.project_id == project_id)
1131
- .filter(Entity.entity_type_id == shot_type["id"])
1141
+ Task.query.filter(Entity.entity_type_id == shot_type["id"])
1142
+ .filter(Task.project_id == project_id)
1132
1143
  .filter(Task.task_type_id == task_type_id)
1133
- .filter(Task.end_date != None)
1134
1144
  .join(Entity, Entity.id == Task.entity_id)
1135
1145
  .join(Project, Project.id == Task.project_id)
1136
1146
  .join(TimeSpent, Task.id == TimeSpent.task_id)
@@ -1142,6 +1152,11 @@ def get_weighted_quotas(project_id, task_type_id, studio_id=None):
1142
1152
  )
1143
1153
  )
1144
1154
 
1155
+ if feedback:
1156
+ query = query.filter(Task.end_date != None)
1157
+ else:
1158
+ query = query.filter(Task.done_date != None)
1159
+
1145
1160
  if studio_id is not None:
1146
1161
  persons_from_studio = Person.query.filter(
1147
1162
  Person.studio_id == studio_id
@@ -1162,7 +1177,6 @@ def get_weighted_quotas(project_id, task_type_id, studio_id=None):
1162
1177
  .filter(Entity.entity_type_id == shot_type["id"])
1163
1178
  .filter(Task.task_type_id == task_type_id)
1164
1179
  .filter(Task.real_start_date != None)
1165
- .filter(Task.end_date != None)
1166
1180
  .filter(TimeSpent.id == None)
1167
1181
  .join(Entity, Entity.id == Task.entity_id)
1168
1182
  .join(Project, Project.id == Task.project_id)
@@ -1171,6 +1185,11 @@ def get_weighted_quotas(project_id, task_type_id, studio_id=None):
1171
1185
  .add_columns(Entity.nb_frames, Person.id)
1172
1186
  )
1173
1187
 
1188
+ if feedback:
1189
+ query = query.filter(Task.end_date != None)
1190
+ else:
1191
+ query = query.filter(Task.done_date != None)
1192
+
1174
1193
  if studio_id is not None:
1175
1194
  query = query.filter(
1176
1195
  or_(*[Task.assignees.contains(p) for p in persons_from_studio])
@@ -1178,16 +1197,19 @@ def get_weighted_quotas(project_id, task_type_id, studio_id=None):
1178
1197
  result = query.all()
1179
1198
 
1180
1199
  for task, nb_frames, person_id in result:
1200
+ date = task.done_date
1201
+ if feedback:
1202
+ date = task.end_date
1203
+
1181
1204
  business_days = (
1182
- date_helpers.get_business_days(task.real_start_date, task.end_date)
1183
- + 1
1205
+ date_helpers.get_business_days(task.real_start_date, date) + 1
1184
1206
  )
1185
1207
  if nb_frames is not None:
1186
1208
  nb_frames = round(nb_frames / business_days) or 0
1187
1209
  else:
1188
1210
  nb_frames = 0
1189
- date = task.real_start_date
1190
- for x in range((task.end_date - task.real_start_date).days + 1):
1211
+
1212
+ for x in range((date - task.real_start_date).days + 1):
1191
1213
  if date.weekday() < 5:
1192
1214
  _add_quota_entry(
1193
1215
  quotas, str(person_id), date, timezone, nb_frames, fps
@@ -1196,11 +1218,13 @@ def get_weighted_quotas(project_id, task_type_id, studio_id=None):
1196
1218
  return quotas
1197
1219
 
1198
1220
 
1199
- def get_raw_quotas(project_id, task_type_id, studio_id=None):
1221
+ def get_raw_quotas(project_id, task_type_id, studio_id=None, feedback=True):
1200
1222
  """
1201
1223
  Build quota statistics in a raw way. It counts the number of frames done
1202
1224
  for each day. A shot is considered done at the first feedback request (end
1203
- date). It considers that all the work was done at the end date.
1225
+ date) or approval date (done_date).
1226
+
1227
+ It considers that all the work was done at the end date.
1204
1228
  It computes the shot count and the number of seconds too.
1205
1229
  """
1206
1230
  fps = projects_service.get_project_fps(project_id)
@@ -1211,13 +1235,17 @@ def get_raw_quotas(project_id, task_type_id, studio_id=None):
1211
1235
  Task.query.filter(Task.project_id == project_id)
1212
1236
  .filter(Entity.entity_type_id == shot_type["id"])
1213
1237
  .filter(Task.task_type_id == task_type_id)
1214
- .filter(Task.end_date != None)
1215
1238
  .join(Entity, Entity.id == Task.entity_id)
1216
1239
  .join(Project, Project.id == Task.project_id)
1217
1240
  .join(Task.assignees)
1218
1241
  .add_columns(Entity.nb_frames, Person.id)
1219
1242
  )
1220
1243
 
1244
+ if feedback:
1245
+ query = query.filter(Task.end_date != None)
1246
+ else:
1247
+ query = query.filter(Task.done_date != None)
1248
+
1221
1249
  if studio_id is not None:
1222
1250
  persons_from_studio = Person.query.filter(
1223
1251
  Person.studio_id == studio_id
@@ -1229,7 +1257,10 @@ def get_raw_quotas(project_id, task_type_id, studio_id=None):
1229
1257
  result = query.all()
1230
1258
 
1231
1259
  for task, nb_frames, person_id in result:
1232
- date = task.end_date
1260
+ date = task.done_date
1261
+ if feedback:
1262
+ date = task.end_date
1263
+
1233
1264
  if nb_frames is None:
1234
1265
  nb_frames = 0
1235
1266
  _add_quota_entry(
@@ -1303,7 +1334,13 @@ def _init_quota_person(quotas, person_id):
1303
1334
 
1304
1335
 
1305
1336
  def get_month_quota_shots(
1306
- person_id, year, month, project_id=None, task_type_id=None, weighted=True
1337
+ person_id,
1338
+ year,
1339
+ month,
1340
+ project_id=None,
1341
+ task_type_id=None,
1342
+ weighted=True,
1343
+ feedback=True,
1307
1344
  ):
1308
1345
  """
1309
1346
  Return shots that are included in quota comptutation for given
@@ -1318,6 +1355,7 @@ def get_month_quota_shots(
1318
1355
  end,
1319
1356
  project_id=project_id,
1320
1357
  task_type_id=task_type_id,
1358
+ feedback=feedback,
1321
1359
  )
1322
1360
  else:
1323
1361
  return get_raw_quota_shots_between(
@@ -1326,11 +1364,18 @@ def get_month_quota_shots(
1326
1364
  end,
1327
1365
  project_id=project_id,
1328
1366
  task_type_id=task_type_id,
1367
+ feedback=feedback,
1329
1368
  )
1330
1369
 
1331
1370
 
1332
1371
  def get_week_quota_shots(
1333
- person_id, year, week, project_id=None, task_type_id=None, weighted=True
1372
+ person_id,
1373
+ year,
1374
+ week,
1375
+ project_id=None,
1376
+ task_type_id=None,
1377
+ weighted=True,
1378
+ feedback=True,
1334
1379
  ):
1335
1380
  """
1336
1381
  Return shots that are included in quota comptutation for given
@@ -1345,6 +1390,7 @@ def get_week_quota_shots(
1345
1390
  end,
1346
1391
  project_id=project_id,
1347
1392
  task_type_id=task_type_id,
1393
+ feedback=feedback,
1348
1394
  )
1349
1395
  else:
1350
1396
  return get_raw_quota_shots_between(
@@ -1353,6 +1399,7 @@ def get_week_quota_shots(
1353
1399
  end,
1354
1400
  project_id=project_id,
1355
1401
  task_type_id=task_type_id,
1402
+ feedback=feedback,
1356
1403
  )
1357
1404
 
1358
1405
 
@@ -1364,13 +1411,14 @@ def get_day_quota_shots(
1364
1411
  project_id=None,
1365
1412
  task_type_id=None,
1366
1413
  weighted=True,
1414
+ feedback=True,
1367
1415
  ):
1368
1416
  """
1369
1417
  Return shots that are included in quota comptutation for given
1370
1418
  person and day.
1371
1419
  """
1372
1420
  start, end = date_helpers.get_day_interval(year, month, day)
1373
- start, end = _get_timezoned_interval(start, end)
1421
+ # start, end = _get_timezoned_interval(start, end)
1374
1422
  if weighted:
1375
1423
  return get_weighted_quota_shots_between(
1376
1424
  person_id,
@@ -1378,6 +1426,7 @@ def get_day_quota_shots(
1378
1426
  end,
1379
1427
  project_id=project_id,
1380
1428
  task_type_id=task_type_id,
1429
+ feedback=feedback,
1381
1430
  )
1382
1431
  else:
1383
1432
  return get_raw_quota_shots_between(
@@ -1386,11 +1435,12 @@ def get_day_quota_shots(
1386
1435
  end,
1387
1436
  project_id=project_id,
1388
1437
  task_type_id=task_type_id,
1438
+ feedback=feedback,
1389
1439
  )
1390
1440
 
1391
1441
 
1392
1442
  def get_weighted_quota_shots_between(
1393
- person_id, start, end, project_id=None, task_type_id=None
1443
+ person_id, start, end, project_id=None, task_type_id=None, feedback=True
1394
1444
  ):
1395
1445
  """
1396
1446
  Get all shots leading to a quota computation during the given period.
@@ -1410,7 +1460,6 @@ def get_weighted_quota_shots_between(
1410
1460
  Entity.query.filter(Entity.entity_type_id == shot_type["id"])
1411
1461
  .filter(Task.project_id == project_id)
1412
1462
  .filter(Task.task_type_id == task_type_id)
1413
- .filter(Task.end_date != None)
1414
1463
  .filter(TimeSpent.person_id == person_id)
1415
1464
  .filter(TimeSpent.date >= func.cast(start, TimeSpent.date.type))
1416
1465
  .filter(TimeSpent.date < func.cast(end, TimeSpent.date.type))
@@ -1419,6 +1468,12 @@ def get_weighted_quota_shots_between(
1419
1468
  .join(TimeSpent, Task.id == TimeSpent.task_id)
1420
1469
  .add_columns(Task.duration, TimeSpent.duration)
1421
1470
  )
1471
+
1472
+ if feedback:
1473
+ query = query.filter(Task.end_date != None)
1474
+ else:
1475
+ query = query.filter(Task.done_date != None)
1476
+
1422
1477
  query_shots = query.all()
1423
1478
  for entity, task_duration, duration in query_shots:
1424
1479
  shot = entity.serialize()
@@ -1432,22 +1487,36 @@ def get_weighted_quota_shots_between(
1432
1487
  shot = already_listed[shot["id"]]
1433
1488
  shot["weight"] += round(duration / task_duration, 2)
1434
1489
 
1435
- start = date_helpers.get_datetime_from_string(start)
1436
- end = date_helpers.get_datetime_from_string(end)
1490
+ print(start, end)
1491
+ if type(start) is str:
1492
+ start = date_helpers.get_datetime_from_string(start)
1493
+ if type(end) is str:
1494
+ end = date_helpers.get_datetime_from_string(end)
1437
1495
  query = (
1438
1496
  Entity.query.filter(Entity.entity_type_id == shot_type["id"])
1439
1497
  .filter(Task.project_id == project_id)
1440
1498
  .filter(Task.task_type_id == task_type_id)
1441
- .filter(Task.end_date != None)
1442
1499
  .filter(Task.real_start_date != None)
1443
1500
  .filter(Task.assignees.contains(person))
1444
- .filter((Task.real_start_date <= end) & (Task.end_date >= start))
1445
1501
  .filter(TimeSpent.id == None)
1446
1502
  .join(Task, Entity.id == Task.entity_id)
1447
1503
  .join(Project, Project.id == Task.project_id)
1448
1504
  .outerjoin(TimeSpent, TimeSpent.task_id == Task.id)
1449
- .add_columns(Task.real_start_date, Task.end_date)
1450
1505
  )
1506
+
1507
+ if feedback:
1508
+ query = (
1509
+ query.filter(Task.end_date != None)
1510
+ .filter((Task.real_start_date <= end) & (Task.end_date >= start))
1511
+ .add_columns(Task.real_start_date, Task.end_date)
1512
+ )
1513
+ else:
1514
+ query = (
1515
+ query.filter(Task.done_date != None)
1516
+ .filter((Task.real_start_date <= end) & (Task.done_date >= start))
1517
+ .add_columns(Task.real_start_date, Task.done_date)
1518
+ )
1519
+
1451
1520
  query_shots = query.all()
1452
1521
 
1453
1522
  for entity, task_start, task_end in query_shots:
@@ -1477,7 +1546,7 @@ def get_weighted_quota_shots_between(
1477
1546
 
1478
1547
 
1479
1548
  def get_raw_quota_shots_between(
1480
- person_id, start, end, project_id=None, task_type_id=None
1549
+ person_id, start, end, project_id=None, task_type_id=None, feedback=True
1481
1550
  ):
1482
1551
  """
1483
1552
  Get all shots leading to a quota computation during the given period.
@@ -1490,16 +1559,26 @@ def get_raw_quota_shots_between(
1490
1559
  Entity.query.filter(Entity.entity_type_id == shot_type["id"])
1491
1560
  .filter(Task.project_id == project_id)
1492
1561
  .filter(Task.task_type_id == task_type_id)
1493
- .filter(
1562
+ .filter(Task.assignees.contains(person))
1563
+ .join(Task, Entity.id == Task.entity_id)
1564
+ .join(Project, Project.id == Task.project_id)
1565
+ )
1566
+
1567
+ if feedback:
1568
+ query = query.filter(
1494
1569
  Task.end_date.between(
1495
1570
  func.cast(start, Task.end_date.type),
1496
1571
  func.cast(end, Task.end_date.type),
1497
1572
  )
1498
1573
  )
1499
- .filter(Task.assignees.contains(person))
1500
- .join(Task, Entity.id == Task.entity_id)
1501
- .join(Project, Project.id == Task.project_id)
1502
- )
1574
+ else:
1575
+ query = query.filter(
1576
+ Task.done_date.between(
1577
+ func.cast(start, Task.done_date.type),
1578
+ func.cast(end, Task.done_date.type),
1579
+ )
1580
+ )
1581
+
1503
1582
  query_shots = query.all()
1504
1583
 
1505
1584
  for entity in query_shots:
@@ -1297,6 +1297,9 @@ def update_task(task_id, data):
1297
1297
  if is_finished(task, data):
1298
1298
  data["end_date"] = date_helpers.get_utc_now_datetime()
1299
1299
 
1300
+ if is_done(task, data):
1301
+ data["done_date"] = date_helpers.get_utc_now_datetime()
1302
+
1300
1303
  task.update(data)
1301
1304
  clear_task_cache(task_id)
1302
1305
  events.emit(
@@ -1490,7 +1493,7 @@ def delete_time_spent(task_id, person_id, date):
1490
1493
 
1491
1494
  def is_finished(task, data):
1492
1495
  """
1493
- Return True if task status is set to done.
1496
+ Return True if task status is set to feedback request.
1494
1497
  """
1495
1498
  if "task_status_id" in data:
1496
1499
  task_status = get_task_status_raw(task.task_status_id)
@@ -1503,6 +1506,18 @@ def is_finished(task, data):
1503
1506
  return False
1504
1507
 
1505
1508
 
1509
+ def is_done(task, data):
1510
+ """
1511
+ Return True if task status is set to done.
1512
+ """
1513
+ if "task_status_id" in data:
1514
+ task_status = get_task_status_raw(task.task_status_id)
1515
+ new_task_status = get_task_status_raw(data["task_status_id"])
1516
+ return new_task_status.id != task_status.id and new_task_status.is_done
1517
+ else:
1518
+ return False
1519
+
1520
+
1506
1521
  def clear_assignation(task_id, person_id=None):
1507
1522
  """
1508
1523
  Clear task assignation and emit a *task:unassign* event.
@@ -1758,6 +1773,7 @@ def reset_task_data(task_id):
1758
1773
  real_start_date = None
1759
1774
  last_comment_date = None
1760
1775
  end_date = None
1776
+ done_date = None
1761
1777
  entity = entities_service.get_entity(task.entity_id)
1762
1778
  task_status_id = get_default_status(
1763
1779
  for_concept=entity["entity_type_id"]
@@ -1770,6 +1786,7 @@ def reset_task_data(task_id):
1770
1786
  .add_columns(
1771
1787
  TaskStatus.is_retake,
1772
1788
  TaskStatus.is_feedback_request,
1789
+ TaskStatus.is_done,
1773
1790
  TaskStatus.short_name,
1774
1791
  )
1775
1792
  .all()
@@ -1780,6 +1797,7 @@ def reset_task_data(task_id):
1780
1797
  comment,
1781
1798
  task_status_is_retake,
1782
1799
  task_status_is_feedback_request,
1800
+ task_status_is_done,
1783
1801
  task_status_short_name,
1784
1802
  ) in comments:
1785
1803
  if task_status_is_retake and not previous_is_retake:
@@ -1792,6 +1810,11 @@ def reset_task_data(task_id):
1792
1810
  if task_status_is_feedback_request:
1793
1811
  end_date = comment.created_at
1794
1812
 
1813
+ print("ok", task_status_is_done)
1814
+ if task_status_is_done:
1815
+ done_date = comment.created_at
1816
+ print(done_date)
1817
+
1795
1818
  task_status_id = comment.task_status_id
1796
1819
  last_comment_date = comment.created_at
1797
1820
 
@@ -1807,6 +1830,7 @@ def reset_task_data(task_id):
1807
1830
  "real_start_date": real_start_date,
1808
1831
  "last_comment_date": last_comment_date,
1809
1832
  "end_date": end_date,
1833
+ "done_date": done_date,
1810
1834
  "task_status_id": task_status_id,
1811
1835
  }
1812
1836
  )
@@ -2036,6 +2060,7 @@ def get_open_tasks(
2036
2060
  "duration": task.duration,
2037
2061
  "start_date": fields.serialize_value(task.start_date),
2038
2062
  "due_date": fields.serialize_value(task.due_date),
2063
+ "done_date": fields.serialize_value(task.done_date),
2039
2064
  "type_name": task_type_name,
2040
2065
  "task_type_for_entity": task_type_for_entity,
2041
2066
  "status_name": task_status_name,
@@ -0,0 +1,108 @@
1
+ """add is_done field to the task model
2
+
3
+ Revision ID: ca28796a2a62
4
+ Revises: 971dbf5a0faf
5
+ Create Date: 2024-07-19 15:24:21.064991
6
+
7
+ """
8
+
9
+ from alembic import op
10
+ import sqlalchemy as sa
11
+ from sqlalchemy import orm
12
+ from sqlalchemy.ext.declarative import declarative_base
13
+ from zou.migrations.utils.base import BaseMixin
14
+ from sqlalchemy_utils import UUIDType, ChoiceType
15
+
16
+
17
+ # revision identifiers, used by Alembic.
18
+ revision = "ca28796a2a62"
19
+ down_revision = "971dbf5a0faf"
20
+ branch_labels = None
21
+ depends_on = None
22
+
23
+
24
+ def upgrade():
25
+ base = declarative_base()
26
+
27
+ TYPES = [
28
+ ("comment", "Comment"),
29
+ ("mention", "Mention"),
30
+ ("assignation", "Assignation"),
31
+ ("reply", "Reply"),
32
+ ("reply-mention", "Reply Mention"),
33
+ ]
34
+
35
+ class Notification(base, BaseMixin):
36
+ """
37
+ A notification is stored each time a comment is posted.
38
+ """
39
+
40
+ __tablename__ = "notification"
41
+ read = sa.Column(sa.Boolean, nullable=False, default=False)
42
+ change = sa.Column(sa.Boolean, nullable=False, default=False)
43
+ type = sa.Column(ChoiceType(TYPES), nullable=False)
44
+ person_id = sa.Column(
45
+ UUIDType(binary=False),
46
+ nullable=False,
47
+ index=True,
48
+ )
49
+ author_id = sa.Column(
50
+ UUIDType(binary=False),
51
+ nullable=False,
52
+ index=True,
53
+ )
54
+ comment_id = sa.Column(
55
+ UUIDType(binary=False),
56
+ nullable=True,
57
+ index=True,
58
+ )
59
+ task_id = sa.Column(
60
+ UUIDType(binary=False),
61
+ nullable=False,
62
+ index=True,
63
+ )
64
+ reply_id = sa.Column(UUIDType(binary=False), nullable=True, index=True)
65
+
66
+ __table_args__ = (
67
+ sa.UniqueConstraint(
68
+ "person_id",
69
+ "author_id",
70
+ "comment_id",
71
+ "reply_id",
72
+ "type",
73
+ name="notification_uc",
74
+ ),
75
+ )
76
+
77
+ bind = op.get_bind()
78
+ session = orm.Session(bind=bind)
79
+ session.query(Notification).where(Notification.type == None).update(
80
+ {Notification.type: "comment"}
81
+ )
82
+ session.commit()
83
+
84
+ # ### commands auto generated by Alembic - please adjust! ###
85
+ with op.batch_alter_table("notification", schema=None) as batch_op:
86
+ batch_op.alter_column(
87
+ "type", existing_type=sa.VARCHAR(length=255), nullable=False
88
+ )
89
+
90
+ with op.batch_alter_table("task", schema=None) as batch_op:
91
+ batch_op.add_column(
92
+ sa.Column("done_date", sa.DateTime(), nullable=True)
93
+ )
94
+
95
+ # ### end Alembic commands ###
96
+
97
+
98
+ def downgrade():
99
+ # ### commands auto generated by Alembic - please adjust! ###
100
+ with op.batch_alter_table("task", schema=None) as batch_op:
101
+ batch_op.drop_column("done_date")
102
+
103
+ with op.batch_alter_table("notification", schema=None) as batch_op:
104
+ batch_op.alter_column(
105
+ "type", existing_type=sa.VARCHAR(length=255), nullable=True
106
+ )
107
+
108
+ # ### end Alembic commands ###
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zou
3
- Version: 0.19.48
3
+ Version: 0.19.50
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
@@ -40,7 +40,7 @@ Requires-Dist: flask-jwt-extended ==4.6.0
40
40
  Requires-Dist: flask-migrate ==4.0.7
41
41
  Requires-Dist: flask-socketio ==5.3.6
42
42
  Requires-Dist: flask ==3.0.3
43
- Requires-Dist: gazu ==0.10.11
43
+ Requires-Dist: gazu ==0.10.13
44
44
  Requires-Dist: gevent-websocket ==0.10.1
45
45
  Requires-Dist: gevent ==24.2.1
46
46
  Requires-Dist: gunicorn ==22.0.0
@@ -77,11 +77,11 @@ Requires-Dist: wheel ; extra == 'dev'
77
77
  Provides-Extra: lint
78
78
  Requires-Dist: autoflake ==2.3.1 ; extra == 'lint'
79
79
  Requires-Dist: black ==24.4.2 ; extra == 'lint'
80
- Requires-Dist: pre-commit ==3.7.1 ; (python_version >= "3.9") and extra == 'lint'
80
+ Requires-Dist: pre-commit ==3.8.0 ; (python_version >= "3.9") and extra == 'lint'
81
81
  Provides-Extra: monitoring
82
82
  Requires-Dist: prometheus-flask-exporter ==0.23.1 ; extra == 'monitoring'
83
83
  Requires-Dist: pygelf ==0.4.2 ; extra == 'monitoring'
84
- Requires-Dist: sentry-sdk ==2.10.0 ; extra == 'monitoring'
84
+ Requires-Dist: sentry-sdk ==2.11.0 ; extra == 'monitoring'
85
85
  Provides-Extra: prod
86
86
  Requires-Dist: gunicorn ; extra == 'prod'
87
87
  Requires-Dist: gevent ; extra == 'prod'
@@ -89,7 +89,7 @@ Provides-Extra: test
89
89
  Requires-Dist: fakeredis ==2.23.3 ; extra == 'test'
90
90
  Requires-Dist: mixer ==7.2.2 ; extra == 'test'
91
91
  Requires-Dist: pytest-cov ==5.0.0 ; extra == 'test'
92
- Requires-Dist: pytest ==8.3.1 ; extra == 'test'
92
+ Requires-Dist: pytest ==8.3.2 ; extra == 'test'
93
93
 
94
94
  .. figure:: https://zou.cg-wire.com/kitsu.png
95
95
  :alt: Kitsu Logo
@@ -1,4 +1,4 @@
1
- zou/__init__.py,sha256=UqE6NTTLYFAIm4eEbzfIb6fa4GOIKkSgT4bob7C4OsQ,24
1
+ zou/__init__.py,sha256=_Gy_AT8DQ-udpLfSZgx_ouKt88H4OjNcTtS35Oi8b7I,24
2
2
  zou/cli.py,sha256=2cDkbEOqp_m9hzBQf5wpxc_h0WjoH8KtxQQMNuREYlc,18201
3
3
  zou/debug.py,sha256=1fawPbkD4wn0Y9Gk0BiBFSa-CQe5agFi8R9uJYl2Uyk,520
4
4
  zou/event_stream.py,sha256=zgob2dZKray2lxPa11hdRNmOg8XRlKDdRcGdF80ylwg,8245
@@ -6,7 +6,7 @@ zou/job_settings.py,sha256=WB_RkYxmh4ffQHqg63_wsUDiS0zP3yxiwrxK7DhHG0g,150
6
6
  zou/app/__init__.py,sha256=aWh9K5n63TpXdLDxpfFtNNoCYZVE6OV3YLCXLuygWQg,6700
7
7
  zou/app/api.py,sha256=JTB_IMVO8EOoyqx9KdRkiIix0chOLi0yGDY-verUJXA,5127
8
8
  zou/app/config.py,sha256=J0jmGmyvk8JqcMPAEW8KfvwP8GwEoC9BxQG8AQV9aiY,6393
9
- zou/app/mixin.py,sha256=A4iIOwpkfYmRKXvF2Ykdc8BUQgufB0r_I4RdZYTiS3E,4896
9
+ zou/app/mixin.py,sha256=eYwfS_CUFvNmldaQXrjsN5mK_gX0wYrBFykfx60uUM8,4897
10
10
  zou/app/swagger.py,sha256=UW9DSik3a8GuH1_-7F5P7EYgjZ9DA_kFjukO-6n4kgk,54682
11
11
  zou/app/blueprints/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  zou/app/blueprints/assets/__init__.py,sha256=tSRvVrnPj732F4k_lkxoTJBJNmIUAqKXPLJDHvOtAA4,2178
@@ -87,7 +87,7 @@ zou/app/blueprints/index/resources.py,sha256=DsYpNSO_wCAxyjWOaB9QmEqOBcjn1miLiGn
87
87
  zou/app/blueprints/news/__init__.py,sha256=HxBXjC15dVbotNAZ0CLf02iwUjxJr20kgf8_kT_9nwM,505
88
88
  zou/app/blueprints/news/resources.py,sha256=6e3Ex_Q-djxWDjQQ-eD1fBcylea6XaJphO7Tl9zWKcY,7057
89
89
  zou/app/blueprints/persons/__init__.py,sha256=0cnHHw3K_8OEMm0qOi3wKVomSAg9IJSnVjAXabMeHks,3893
90
- zou/app/blueprints/persons/resources.py,sha256=-5J8ZswusYEBTNmX4I7fLFDZVMKyO0qhO_dkMs9SkXA,41406
90
+ zou/app/blueprints/persons/resources.py,sha256=wYM5xcjP7Q0-ETZTom4yjvOnTO9kYzfjYP8wEa6X030,42674
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
@@ -97,7 +97,7 @@ zou/app/blueprints/projects/resources.py,sha256=v9_TLh3mujL-p7QcGkfSOJnNojzoJA15
97
97
  zou/app/blueprints/search/__init__.py,sha256=QCjQIY_85l_orhdEiqav_GifjReuwsjZggN3V0GeUVY,356
98
98
  zou/app/blueprints/search/resources.py,sha256=ni-dX8Xfib_0FonLttoXgntXBR957-xhifPSQHHnOnY,2696
99
99
  zou/app/blueprints/shots/__init__.py,sha256=HfgLneZBYUMa2OGwIgEZTz8zrIEYFRiYmRbreBPYeYw,4076
100
- zou/app/blueprints/shots/resources.py,sha256=NiuRZG2ltSn6KgaZvr_W7bHXJOQkBTFPUDYisShWZ6M,47738
100
+ zou/app/blueprints/shots/resources.py,sha256=H9-TbHod5e7w6fO-1-C81Npk2reJkfiqTjRe7KgjaLI,48669
101
101
  zou/app/blueprints/source/__init__.py,sha256=H7K-4TDs4pc5EJvcYTYMJBHesxyqsE5-xq7J8ckOS2g,6093
102
102
  zou/app/blueprints/source/kitsu.py,sha256=4lWdqxaKDzwx-5POAIHIgZ6ODbDMOOVRxaSb_FOLcCk,5012
103
103
  zou/app/blueprints/source/otio.py,sha256=WkzpKylVkNlbY_jwf6uV5-HPylrktQznOcbCs_p8TDQ,13391
@@ -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=0GDQTHUThnydD4VwZg6YW2daC31zsu16j3ik4Im8d6g,1026
176
- zou/app/models/task.py,sha256=UDUhR_L1s29Lo2mIak8ihGOhGlx3sASAq_698Jp-Yek,3398
176
+ zou/app/models/task.py,sha256=DiDCXdAOuKwR6LO8_ECZHunuZ5a19GVqRC5SA-8cdcs,3437
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=Vea-Ra69-Zi0Q2S-Apb5fsZv5E9f_6XYVn2HxaGQBE8,21339
182
+ zou/app/services/assets_service.py,sha256=BvAi5mnvZvMJJXnxFF3bofu4JZjiPvdlPrgUyW8PuKY,21460
183
183
  zou/app/services/auth_service.py,sha256=AxKN_THkjN2tmOHTSyFspwwFHqGBnslXuidfbav8Rxk,23224
184
184
  zou/app/services/backup_service.py,sha256=_ZtZp6wkcVYnHxBosziwLGdrTvsUttXGphiydq53iy8,4840
185
185
  zou/app/services/base_service.py,sha256=OZd0STFh-DyBBdwsmA7DMMnrwv4C8wJUbShvZ1isndU,1383
@@ -191,7 +191,7 @@ zou/app/services/custom_actions_service.py,sha256=fWISEOOdthadrxeHuacEel5Xj6msn0
191
191
  zou/app/services/deletion_service.py,sha256=ddaup7i_CTugcJDrynczNcfhjSKgDeGhPQjQ68We_l8,17255
192
192
  zou/app/services/edits_service.py,sha256=KTk5KUJx1yB-mRka8rQXMgQLIcWq4mNYZfnVhVlKwyQ,12122
193
193
  zou/app/services/emails_service.py,sha256=ZSwfL3iO9mIyHttqH61vI4WA-DTVdBk7lo6t9zbvfDA,11922
194
- zou/app/services/entities_service.py,sha256=IqDnI-mPdPqCJ-t6_O-M_IXyiNCamm2moCCKGJCNLrM,16222
194
+ zou/app/services/entities_service.py,sha256=j_dyMHUVISqXFkxm2ydqvAo0nbcEE7_5v9jFauZhSe4,16327
195
195
  zou/app/services/events_service.py,sha256=_gU19herxiAo47WRxVNhvl7v-RKhp-8wZ3gAiHKpOzI,2716
196
196
  zou/app/services/exception.py,sha256=OBhVlLel081vNUUVywJkY4_8lJE8pFbWa-mrSKrtZuc,4288
197
197
  zou/app/services/file_tree_service.py,sha256=8JNBDgnXtV-AmSJ3gnUGB4oSwLjPgi1WYyL0Kc98JRE,33875
@@ -206,11 +206,11 @@ zou/app/services/preview_files_service.py,sha256=SIZ_SB1bWNRE_Zm7SuEpvFWqVpHKOgf
206
206
  zou/app/services/projects_service.py,sha256=_J8hIHy3MX5MsdEMRIKNfbyewwhxtMEcc_ymeHBsF38,21434
207
207
  zou/app/services/scenes_service.py,sha256=iXN19HU4njPF5VtZXuUrVJ-W23ZQuQNPC3ADXltbWtU,992
208
208
  zou/app/services/schedule_service.py,sha256=E99HKYsXgnK2sw58fw-NNHXWBgVJiA60upztjkNSCaM,6989
209
- zou/app/services/shots_service.py,sha256=w96EbNyXZNKmZg1eUMbaQPAEng3WkfJaPkqEr39F2ug,50310
209
+ zou/app/services/shots_service.py,sha256=Y1oTewc2TNxwPzU5CZFfEhjJznjR54a9GTirFZxBhv4,52078
210
210
  zou/app/services/stats_service.py,sha256=cAlc92i9d6eYtsuwe3hYHYwdytg8KEMi1-TADfysJwM,11733
211
211
  zou/app/services/status_automations_service.py,sha256=tVio7Sj7inhvKS4UOyRhcdpwr_KNP96hT1o0X7XcGF4,715
212
212
  zou/app/services/sync_service.py,sha256=EunfXlma_IIb7011A_xLQLVQGAi-MteKgm2Y2NAxvMs,41586
213
- zou/app/services/tasks_service.py,sha256=05Vy7cBhHFr7PYBuTNiggw1TydtOhP5ekuBoTZLTLOA,67797
213
+ zou/app/services/tasks_service.py,sha256=Y7VlDJon41m3wjMSOqvoxo0y7UDxlI7NuFPprRMySU0,68602
214
214
  zou/app/services/telemetry_services.py,sha256=xQm1h1t_JxSFW59zQGf4NuNdUi1UfMa_6pQ-ytRbmGA,1029
215
215
  zou/app/services/time_spents_service.py,sha256=TBLC1O9Dg_UbciG5Nw-dejqX2-5n6q44lACeN6OnUkQ,15206
216
216
  zou/app/services/user_service.py,sha256=BiOhPV7O-vowet7jOksjXk2V4yxZkdqsIyNboJ-Oz_A,46595
@@ -374,6 +374,7 @@ zou/migrations/versions/c49e41f1298b_add_previewbackground.py,sha256=7TOXMsEK9YW
374
374
  zou/migrations/versions/c68c2a62cfac_add_mimetype_column_to_attachment.py,sha256=cEFnRMYHurag8D9-7ycE34gnVH5TdsDfRYQqs5v_g8M,727
375
375
  zou/migrations/versions/c726b98be194_.py,sha256=nbqWFmPg4OUCL-txrVjWyQiFm_PuuaDZeE2gOB-DM3w,701
376
376
  zou/migrations/versions/c81f3e83bdb5_.py,sha256=eGoGIOImywjBRSqe-isbt_N_o3zK1ktrPWSo0nyAXgA,720
377
+ zou/migrations/versions/ca28796a2a62_add_is_done_field_to_the_task_model.py,sha256=adalMYr3AvOEpPR5V1nj6QxsVJUz-44VYaGnHM_tQyA,3046
377
378
  zou/migrations/versions/cf3d365de164_add_entity_version_model.py,sha256=n3k3ojRQAk0tJAOBIUgDEoZbNFERx4vs3hg5hrFm7v4,1763
378
379
  zou/migrations/versions/cf6cec6d6bf5_add_status_field_to_preview_file.py,sha256=0c8_OghAaaM0R6bKbs7MADvOkZ1sFE3SVp_nNB2ACgQ,950
379
380
  zou/migrations/versions/d80267806131_task_status_new_column_is_default.py,sha256=7HtK0bfBUh9MrJIbpUgz6S-Ye_R_4DbHILpODMBVVwE,2610
@@ -407,9 +408,9 @@ zou/remote/normalize_movie.py,sha256=zNfEY3N1UbAHZfddGONTg2Sff3ieLVWd4dfZa1dpnes
407
408
  zou/remote/playlist.py,sha256=AsDo0bgYhDcd6DfNRV6r6Jj3URWwavE2ZN3VkKRPbLU,3293
408
409
  zou/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
409
410
  zou/utils/movie.py,sha256=u9LCEOvmkxwm-KiZ6jKNdB9LSC6XXUDwJpVx8LkDwJg,16416
410
- zou-0.19.48.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
411
- zou-0.19.48.dist-info/METADATA,sha256=f9e2kAoECSKBCRjpIPUVerRJK6wmTEqlwXGYCVhEbIo,6725
412
- zou-0.19.48.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
413
- zou-0.19.48.dist-info/entry_points.txt,sha256=PelQoIx3qhQ_Tmne7wrLY-1m2izuzgpwokoURwSohy4,130
414
- zou-0.19.48.dist-info/top_level.txt,sha256=4S7G_jk4MzpToeDItHGjPhHx_fRdX52zJZWTD4SL54g,4
415
- zou-0.19.48.dist-info/RECORD,,
411
+ zou-0.19.50.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
412
+ zou-0.19.50.dist-info/METADATA,sha256=QQwGEWHpVwuuwp5RgWo0k6rFY2flwv1AHlRLtDDJ4xo,6725
413
+ zou-0.19.50.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
414
+ zou-0.19.50.dist-info/entry_points.txt,sha256=PelQoIx3qhQ_Tmne7wrLY-1m2izuzgpwokoURwSohy4,130
415
+ zou-0.19.50.dist-info/top_level.txt,sha256=4S7G_jk4MzpToeDItHGjPhHx_fRdX52zJZWTD4SL54g,4
416
+ zou-0.19.50.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (71.1.0)
2
+ Generator: setuptools (72.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
File without changes