zou 0.20.76__py3-none-any.whl → 0.20.77__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.
@@ -6,12 +6,14 @@ from zou.app.services import (
6
6
  names_service,
7
7
  persons_service,
8
8
  projects_service,
9
+ shots_service,
9
10
  tasks_service,
10
11
  )
11
12
  from zou.app.stores import queue_store
13
+ from zou.app.services.template_services import generate_html_body
12
14
 
13
15
 
14
- def send_notification(person_id, subject, messages):
16
+ def send_notification(person_id, subject, messages, title=""):
15
17
  """
16
18
  Send email notification to given person. Use the job queue if it is
17
19
  activated.
@@ -21,20 +23,20 @@ def send_notification(person_id, subject, messages):
21
23
  slack_message = messages["slack_message"]
22
24
  mattermost_message = messages["mattermost_message"]
23
25
  discord_message = messages["discord_message"]
26
+ email_html_body = generate_html_body(title, email_message)
27
+
24
28
  if person["notifications_enabled"]:
25
29
  if config.ENABLE_JOB_QUEUE:
26
30
  queue_store.job_queue.enqueue(
27
31
  emails.send_email,
28
32
  args=(
29
33
  subject,
30
- email_message + get_signature(),
34
+ email_html_body,
31
35
  person["email"],
32
36
  ),
33
37
  )
34
38
  else:
35
- emails.send_email(
36
- subject, email_message + get_signature(), person["email"]
37
- )
39
+ emails.send_email(subject, email_html_body, person["email"])
38
40
 
39
41
  if person["notifications_slack_enabled"]:
40
42
  organisation = persons_service.get_organisation(sensitive=True)
@@ -152,6 +154,7 @@ _%s_
152
154
  task_url,
153
155
  task_status_name,
154
156
  )
157
+
155
158
  messages = {
156
159
  "email_message": email_message,
157
160
  "slack_message": slack_message,
@@ -260,6 +263,7 @@ def send_assignation_notification(person_id, author_id, task):
260
263
  task_name,
261
264
  task_url,
262
265
  )
266
+
263
267
  messages = {
264
268
  "email_message": email_message,
265
269
  "slack_message": slack_message,
@@ -273,20 +277,6 @@ def send_assignation_notification(person_id, author_id, task):
273
277
  return True
274
278
 
275
279
 
276
- def get_signature():
277
- """
278
- Build signature for Zou emails.
279
- """
280
- organisation = persons_service.get_organisation()
281
- return (
282
- """
283
- <p>Best,</p>
284
-
285
- <p>%s Team</p>"""
286
- % organisation["name"]
287
- )
288
-
289
-
290
280
  def get_task_descriptors(person_id, task):
291
281
  """
292
282
  Build task information needed to write notification emails: author object,
@@ -334,6 +324,8 @@ def send_reply_notification(person_id, author_id, comment, task, reply):
334
324
  if (
335
325
  person["notifications_enabled"]
336
326
  or person["notifications_slack_enabled"]
327
+ or person["notifications_mattermost_enabled"]
328
+ or person["notifications_discord_enabled"]
337
329
  ):
338
330
  tasks_service.get_task_status(task["task_status_id"])
339
331
  project = projects_service.get_project(task["project_id"])
@@ -382,3 +374,55 @@ _%s_
382
374
  }
383
375
  send_notification(person_id, subject, messages)
384
376
  return True
377
+
378
+
379
+ def send_playlist_ready_notification(person_id, author_id, playlist):
380
+ """
381
+ Send a notification email telling that a new playlist is ready to person
382
+ matching given person id.
383
+ """
384
+ person = persons_service.get_person(person_id)
385
+ author = persons_service.get_person(author_id)
386
+ project = projects_service.get_project(playlist["project_id"])
387
+ episode = None
388
+ try:
389
+ episode = shots_service.get_episode(playlist["episode_id"])
390
+ except:
391
+ pass
392
+
393
+ if (
394
+ True
395
+ or person["notifications_enabled"]
396
+ or person["notifications_slack_enabled"]
397
+ or person["notifications_mattermost_enabled"]
398
+ or person["notifications_discord_enabled"]
399
+ ):
400
+ if episode is not None:
401
+ playlist_url = f"{config.DOMAIN_PROTOCOL}://{config.DOMAIN_NAME}/productions/{playlist['project_id']}/episodes/{episode['id']}/playlists/{playlist['id']}"
402
+ else:
403
+ playlist_url = f"{config.DOMAIN_PROTOCOL}://{config.DOMAIN_NAME}/productions/{playlist['project_id']}/playlists/{playlist['id']}"
404
+
405
+ title = "Playlist Ready"
406
+ episode_segment = ""
407
+ if episode is not None:
408
+ episode_segment = f"the episode {episode['name']} of"
409
+ subject = "[Kitsu] A new playlist is ready"
410
+
411
+ email_message = f"""<p><strong>{author["full_name"]}</strong> notifies you that playlist <a href="{playlist_url}">{playlist["name"]}</a> is ready for a review under {episode_segment} the project {project["name"]}.</p>
412
+
413
+ {len(playlist["shots"])} elements are listed in the playlist.
414
+ """
415
+
416
+ slack_message = f"*{author['full_name']}* notifies you that a playlist <{playlist_url}|{playlist['name']}> is ready for a review under {episode_segment} the project {project['name']}."
417
+
418
+ discord_message = f"*{author['full_name']}* notifies you that a playlist [{playlist['name']}]({playlist_url}) is ready for a review under {episode_segment} the project {project['name']}."
419
+ messages = {
420
+ "email_message": email_message,
421
+ "slack_message": slack_message,
422
+ "mattermost_message": {
423
+ "message": slack_message,
424
+ "project_name": project["name"],
425
+ },
426
+ "discord_message": discord_message,
427
+ }
428
+ send_notification(person_id, subject, messages, title)
@@ -135,9 +135,7 @@ def get_last_news_for_project(
135
135
  )
136
136
 
137
137
  if task_status_id is not None:
138
- query = query.filter(Comment.task_status_id == task_status_id).filter(
139
- News.change == True
140
- )
138
+ query = query.filter(Comment.task_status_id == task_status_id)
141
139
 
142
140
  if task_type_id is not None:
143
141
  query = query.filter(Task.task_type_id == task_type_id)
@@ -1,8 +1,9 @@
1
1
  from sqlalchemy.exc import StatementError
2
2
 
3
- from zou.app.models.project import Project
3
+ from zou.app.models.project import Project, ProjectPersonLink
4
4
  from zou.app.models.entity import Entity
5
5
  from zou.app.models.notification import Notification
6
+ from zou.app.models.person import Person
6
7
  from zou.app.models.subscription import Subscription
7
8
  from zou.app.models.task import Task
8
9
  from zou.app.models.task_type import TaskType
@@ -15,7 +16,7 @@ from zou.app.services import (
15
16
  persons_service,
16
17
  )
17
18
  from zou.app.services.exception import PersonNotFoundException
18
- from zou.app.utils import events, fields, query as query_utils
19
+ from zou.app.utils import date_helpers, events, fields, query as query_utils
19
20
 
20
21
  from zou.app.utils import cache
21
22
 
@@ -39,6 +40,7 @@ def create_notification(
39
40
  change=False,
40
41
  type="comment",
41
42
  created_at=None,
43
+ playlist_id=None,
42
44
  ):
43
45
  """
44
46
  Create a new notification for given person and comment.
@@ -52,6 +54,7 @@ def create_notification(
52
54
  task_id=task_id,
53
55
  comment_id=comment_id,
54
56
  reply_id=reply_id,
57
+ playlist_id=playlist_id,
55
58
  type=type,
56
59
  created_at=creation_date,
57
60
  )
@@ -492,3 +495,37 @@ def get_subscriptions_for_user(project_id, entity_type_id=None):
492
495
  for subscription in subscriptions:
493
496
  subscription_map[str(subscription.task_id)] = True
494
497
  return subscription_map
498
+
499
+
500
+ def notify_clients_playlist_ready(playlist, studio_id=None):
501
+ """
502
+ Notify clients that given playlist is ready.
503
+ """
504
+
505
+ author = persons_service.get_current_user()
506
+ project_id = playlist["project_id"]
507
+ query = (
508
+ Person.query
509
+ .join(ProjectPersonLink)
510
+ .filter(Person.is_bot == False)
511
+ .filter(Person.role == "client")
512
+ .filter(ProjectPersonLink.project_id == project_id)
513
+ )
514
+
515
+ if studio_id is not None and studio_id != "":
516
+ query = query.filter(Person.studio_id == studio_id)
517
+
518
+ for client in query.all():
519
+ recipient_id = str(client.id)
520
+ author_id = author["id"]
521
+ created_at = date_helpers.get_now()
522
+ notification = create_notification(
523
+ recipient_id,
524
+ author_id=author_id,
525
+ playlist_id=playlist["id"],
526
+ type="playlist-ready",
527
+ created_at=created_at,
528
+ )
529
+ emails_service.send_playlist_ready_notification(
530
+ recipient_id, author_id, playlist
531
+ )
@@ -0,0 +1,129 @@
1
+ from zou.app.services import persons_service
2
+
3
+ notification_template_begin = """
4
+ <!DOCTYPE html>
5
+ <html>
6
+ <head>
7
+ <style>
8
+ body {
9
+ background-color: #f3f3f3;
10
+ color: #333333;
11
+ font-family: Lato, Arial, sans-serif;
12
+ line-height: 1.6;
13
+ }
14
+
15
+ a {
16
+ color: #6B6;
17
+ font-weight: bold;
18
+ text-decoration: none;
19
+ }
20
+
21
+ .email-container {
22
+ background-color: #ffffff;
23
+ border: 1px solid #dddddd;
24
+ border-radius: 20px;
25
+ margin: 30px auto;
26
+ max-width: 600px;
27
+ width: 100%;
28
+ }
29
+
30
+ .header {
31
+ border-radius: 20px 20px 0 0;
32
+ padding: 40px 20px 0 20px;
33
+ text-align: center;
34
+ }
35
+
36
+ .content {
37
+ padding: 40px;
38
+ }
39
+
40
+ .image {
41
+ height: auto;
42
+ max-width: 100%;
43
+ }
44
+
45
+ .cta {
46
+ margin: 40px 0;
47
+ text-align: center;
48
+ }
49
+
50
+ .cta a {
51
+ background-color: #00b242;
52
+ border-radius: 20px;
53
+ color: white;
54
+ font-size: 16px;
55
+ font-weight: bold;
56
+ max-with: 300px;
57
+ padding: 10px 20px;
58
+ text-align: center;
59
+ text-decoration: none;
60
+ }
61
+
62
+ .footer {
63
+ color: #666;
64
+ font-size: 12px;
65
+ margin-left: 10px;
66
+ padding: 10px;
67
+ text-align: center;
68
+
69
+ a {
70
+ color: #666;
71
+ }
72
+ }
73
+
74
+ .address {
75
+ margin-top: 20px;
76
+ line-height: 1.5;
77
+ }
78
+ </style>
79
+ </head>
80
+ <body>
81
+ <div class="email-container">
82
+ <div class="header">
83
+ <h1>
84
+ """
85
+
86
+
87
+ notification_template_end = """
88
+ </div>
89
+ <div class="footer">
90
+ <img
91
+ width="30"
92
+ src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAAH0CAYAAADL1t+KAAABg2lDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TpSIVBzuIdMhQnSyISnHUKhShQqgVWnUwufQLmjQkKS6OgmvBwY/FqoOLs64OroIg+AHi6OSk6CIl/i8ptIjx4Lgf7+497t4BQrPKNKtnAtB028ykkmIuvyqGXhGCgDCiSMjMMuYkKQ3f8XWPAF/v4jzL/9yfY0AtWAwIiMSzzDBt4g3ixKZtcN4njrCyrBKfE4+bdEHiR64rHr9xLrks8MyImc3ME0eIxVIXK13MyqZGPE0cUzWd8oWcxyrnLc5atc7a9+QvDBf0lWWu04wihUUsQYIIBXVUUIWNOK06KRYytJ/08Y+4folcCrkqYORYQA0aZNcP/ge/u7WKU5NeUjgJ9L44zscoENoFWg3H+T52nNYJEHwGrvSOv9YEZj5Jb3S02BEwuA1cXHc0ZQ+43AGGnwzZlF0pSFMoFoH3M/qmPDB0C/Sveb2193H6AGSpq/QNcHAIjJUoe93n3X3dvf17pt3fD3Akcqag56+0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH5gcCDB0Y8fpLigAAIABJREFUeNrs3XecFdd5P/7PmZlb997tsGwvbKMJiSokQBRV1JAlWb2jpVnNdtxtIbfEXbZiS2Ancb6W47jna/++cRxbjhQLVVYFVGCpS13KLtvLLXN+fyxYjYUt90y583m/Xv4nQTN3Z87MM8+ZZ54jQGlvbX1xXjxpXCIgZ0OIsyBlKYSYAMAA0A/IbkDsBbAVAs/LpPn0hh37dvLIETlTQ3XpRKFri6SJ84RAHYByABkAQgDiAA4B2A8pNwPaS4Ye/+/vbz3QyiOX3gQPQXq6r7o6ENfj1wG4V0rMB6CPcBOvS4mfxuLihz/es6edR5TIXqunleXIGO6VUtwMYPoI//MkJP4qgR8GpO/Xj+3YMcAjyoBOTn9ynznTp3W23m0K+QUBFKVgk91Sih8EpPGVx3bs6OQRJrL4mq6qyhK+5GchsRpAJAWbPADIL8rouH/Z0NgY5xFmQCcHure27EIB8T0BTFKw+SOAuH99056f80gTWWNlTcVNQsjvSmBcqrctgbcl5P0/bNr7Zx5pBnRyzhN8GXzJbwuJa9UPGPmvoV5z9Xf27+/jkSdSdE0XFYUR8T0hgNuU70ziVzKpf2zDrl17eeQZ0Mkm10+Z4s9NdK2GFF9GaqbihutFkTSuemLnziM8C0Sptba+OC9hGv8BYL6Fu+0TEF/vj+EffrxnTz/PAgM6Weje2rILNYjHANTb8gMk3jD0xCJWzhKlPJj/L4DJNv2EnVLIT2/YtveXPBsM6KTYykklNUjqjwJY5oCf88JATCzmEz3R2D1UUhLqCxvPSMjZDvg5/09PJh/8wc79O3hm3EPnIXCHhqKi8OzCvM9Cip9CTdHbaJT4dEzY1Nrxe54horGZXpizAcDlDvk5tVLTVs/Oz8k/K79g46utrTGeIWbolAKr6so+LKX4FoASJ/4+KeSHOUVHNIZrvLbiRgn5M2f+OrlPCHz8iW17f8EzxYBOo7S6rrLONJPfhRCXOPyntsiEXr9h164OnjWikbm7ri5qyP6tKeoboTCu42mpJ+/fsHX/Fp41Z+KUuwM1VFVlzcqP/oMEfgwhal3wkyNCl3pja8efePaIRmZOfuTLArjUBelfhZDaipl52Tkzs/JeaDx+nN3mmKHT6c7Hytqy2wDxdQAFLvvt/Yau1X//7d3NPI1Ew7OiurhE14wmDPZgd5NWCPmlwm17H1sHmDyTzqDxEDjDquryc1bWlv8VEP/qwmAOAMFEUn6RZ5JoBDdgzfiqC4M5AORBikcP1Za/2FBTMZdnkhk64eSCC9o6KeVauP8ViClNzN6wo/kVnlmi01tZWzkdMF9Jg8TKlMBP46bvY/+yY8dRnln78B26TdYBWl1t2W1Iit9LYAnSY7ZECE1WNrZ2PMkzTHR6s/KyfwKgOi2ue2C6LswVs/Kz+qtaOza9BUieYWbonnBvbelsDeIfATEnLf9AKS9ev30vC+SIhtBQV75YSPwlTf+8V4WQ9z2xbe9GnmkG9LS1ZkrFhGQcjwByBdK7fuH1wqbmGetYLEP0AesA7VBd+cuQmJHGf6aUwJOGT3ziB2/uaeFZtwan3K24gBfBqAuU3SeT+A2A8zzwIDWhOz9rR2Nrx2aefaL3qq8tuxUQa9I9WRTAdGli9ey8HH/lhOLn3jp6NMmzzwzd1VbVVyySSfkYBKZ66e+WQHMsJurZ553oHddPmeLPjXe/DaDKY3/6NqGJB57YuuePHAXq8LM1VYG8tqS4obb8/0hT/o/XgvmJJ8XygE9+hCOB6B05se4HPBjMAaBOmvK/VtaW/37tpMpyjgRm6K7QMHOmT3QfXWPDGuUjEvYJTC3U8dLehMrdtCd8yYn/9Ob+No4M8ro7KyqyA365E0Cuqn3MKTPwxqEkeuMOLjIX6BVSfINrr6ce36Gn0L21ZRdqsb7fA+JWAH5HXksCOKtQxw1n+zG5QMO+DhPH+5Rd/EFNahpbwhIBcwuyvgRgqartl+douO4sH2aU6EiYAoc6Tad+O+YDsEjXcePs3Kzdm9o6mjg6mKE7JyuvLp0oNO1RAFc4+XcWZ2q4rN6Hoqx3TntLl8QPXxyAVHflsyUseZ7qFq8CwIpz/SiMau+5tv9raxx72539sYkA/mxC3Lehac9WjhRm6PYF8pNrlAvxbwAmO/V3RvwCF9UaWDbJh8zge5/hIgGBtl6JI93KIrqRlDKvsbXjtxwx5FWz83O/D2Cmqu1Pm6BjdqnxgWt7erGO8RGB/R0mBhKOPTxVAmiYnZ8zbs4E37MvH+3l2uvM0C0O5oONIf4ZQIVTf6MmBGaX6lg00UDAGPrftfdLfH/jAJLqHuRNU4qZP9y+5zWOHPKae2sqztaEbISiImRdA9bMCyAnPPTtPJaUeL45iWd3J1Re56mwS4N55+NN+/7KkcMMXX0gnznTNysS+LYAfgAgx6m/szxHw43n+DC9UIdxhttI0BDoi0sc6FCWpQuhySq2hCVPZufjMv8VEMpavM4t0zF1gn6GoC9QkaNhygQdx3sl2vocWzSXIyHumJWfHblyWsf/PL2HzamYoSty21kFGeH+4C8ALHPqb4wGBJbWGDircGTPav0JiceejaFPZXUsW8KS1xKA6sqLhWYq+/Y66APuOz+AkG9kt/LtR5P4w9YE2vudXA0vfi+7YjduOHiwlyOJGXpK3V1XFw0m5FOAWOTE36eJwU9Wrj/bj+LMkc/sGdrgDWF3m6nyAp16ZWvHD5/mwg3kAesArTs/6xcAClXtY9FEA1V5I7+N52VomFGiQ9cEDnSYMJ15RdYJv75ocnHw55sP98Q5ohjQU+J6QI/kR3+BwVXRHKcqT8ONZ/tx1jCm10+nKFPD5pakyuKZQraEJa9Q3eI1GhC4ZqoPuja6idaT0/BTJ+g43g+09Toyqpf5Esb0qtaOn3MFNwb0lFhQW/5dALc57XflhAWumOTD0hofwv6xvz3RBBAyBLYdVZelS4iZ06I5T7zW3p7gyKJ0dWdFRVDT8RsBZKvax+AnqGOvswv5BKZN0FGareFgp4le5+XCtaH87BD7WTCgj9mquvIlAP4RDqo3MHRgQZWBD03zoyCa2sLZgoiGpmNJdA+o+e0CyDY0dDS2dTzH0UXpas64rIeEwPWqtj8+ouHyST6IFN6VcsICM0sMhHzAvnaJpLPy4XmzcnOfbmxrZz+L099faSgniuC2AKh0zKPqOB2X1hvIDqo7dduPmfjZq0o/BW3TArL68S17j3OUUbpZPa0sxxwQO6CwxevNM/yozlO3FEfXgMRT2xPYfMhBC6RJuT3cZ07/zv79fRxlp8bFWU4j1B+6zynBPC8scPMMP24826c0mANATb6GqjylQyPXjIlPc4RROjJj4jMqg3l5jqY0mAOD7+eXT/Xh9ll+jM9wSJgQoqYnw1jJEcYMfcQaiorCIuLbDWC8nb/DrwvMq9Axv8KAbuF1dbhLYoPalrAxDXLS4017d3G0UbpYO6myPJE0twIIqrphv7/Fq/IHFCnx8r4knt6ZcEK3uZZwb7KKWToz9JFdOBn+2+0O5rXjdKw5L4ALqqwN5gBQEBVnbFYx1meVJMQ6jjRKJ/Gk+SVVwRwApk7QLQ3mwGDHybllBtaeH8BZhbrdWeCE3pBxI0caA/oII7p5k127LogK3Dl7cHo9M2jfIVhcrfZBQgC3NFSXz+Bgo3SwsrZyugBuUbV9XRv87twuEf/gNPzdc/3vWeDJ+qhlMqAzoA9fw6TyQkDMt3q/QR9wSZ2Be+cGUJZt/6nJDgrMKlGapWtCl3/PEUfpkQQkv67ynjq7VD9tv3arFGdquGd2AMun+hD22fB7pFjSUFuUzwHHgD68g5IUF1h5bAQG1yhfe14Qc8sMaA6qbLhgojHitpIjvDgvXllTdhFHHbk6CaiuvBhSXKzyYX9BpeGYv1eIE/es8/123LMMIX0LOeoY0IcZY+RMq/ZVGNVw12w/lk/1IcPvvGMRNATOq9BV3x2+sY5jkVxqHaAJ3VQ60zS/QvGD9SiFfAKX1Bm4Z44fpVnWXcJSA1/VMaAPO6JPt+pCWDHXj5JsZ5+GuWUGskJKbybTW2rLbuHAIzdqqS27BVJdgIkGBpdBdrLCTA13zhlMTCIB9Q8eQuJsjjwG9OGaoC4Zfe9UlXDBh4OGBiyqUjvdJyG++lBJSYhDj9zkzoqKoAnxJZX7WFJtwKc7/0Zx8tXhmnmBE9PwSn/zBI4+BvRhMpX0Xy7PEVh5ro3FJGNwVqGOCZlKf3NJb0hfy7FHbuL3yfsEUK5q++Mj2oiXQrbbyeLeVfP8mKiuAU4ORx8D+nCfNaOp3uK8Cg23zwpgfMSdvXyEABZP9Kl+xP/M6mllvFDJFVZPK8sRAko7Hi6tcccs3qnkZwx2t5xXoSTMZHEEMqAPP7SkmO6L45XDPU5dd3hYLGgJm3OibSaR48kB8VmVmWJ5joaafPfeoqUEXj/cC92nZPk2djllQLfXoe44XjjYjZiLo/pFNT61GYPE/atry6o4WsjJ1k6qLJeAsldEAsDFdYZrj0/ClHjhUDf2d8c4WBjQ01dbfwIbD3SjJ5Z05e9nS1giIJE0v4w0a/GaKr0JExsPdKG1L8GBwoCe/npiSWw80I22fncOeLaEJS9bWVs5HcDNqrZvd4vXsWgfGLy3dcVMDhQGdO+ImRIvHuxBS0/cdb/dkpawGr7JUULOJL8BD7R4HakjvXG8cKAbAwkGcwZ0D0pKicaWHuzuGHDdb1feEhZY3FBdeTFHCTnJqvqKSwCprFWx01q8Dldz5wA2tfQiISUHCQO6h5/1Abx5rA9vHuuDmy4FS1rCaiZbwpJjrAM0KeVXVe7DqS1eT6eprR9bjvbBZDBnQKdBuzsG0NjSg6SLLgrVLWEFcBZbwpJTHKotu9XrLV7fzZRA4+EeNB3v5+BgQKf3a+mJ43kXvYNiS1jyijsrKoIS4osq9+GWFq8AEEtKvHCwG4e64xwcDOg0lPaBJDYe7EZ33B1BnS1hyQvY4vUdvfHBe5Rbv9JhQCeLLxgTG/e74ztOi1rCfnZtfXEeRwbZ4USL10+p3MeFte5o8drWn8Cz+93bR4MBnWwRP9lpqcv5nZYsaAmbnZDGpzgqyA4nWrzmqtp+eY6G6jzn34rTodMlAzrZdyORwGtHetHU5vyiE7aEpXR0osXrR1Rt3y0tXne3D6DR5WtRMKCTIzQd78drR3rh5AJ4toSldHSixWtA1fad3uLVlBKbj/bhzdY+DgYGdEqV/V0xvHCoGwkHPyJb0RJ2dX3ZTI4GsoLXW7wmpMSmlh7s7RzgYGBAp1Rr7Rtc2KXPoZ+1WdES1jTFNzgSyBrebfHanzTx3P5uHOllJTsDOinTFUti4/5utA84s8qULWEpHXi5xWtXLIln93ehk5XsDOhkzdPz8we7cdiBC7uwJSy53ToPt3g92hvHcwe60Z9g9RsDOlkmaQ6+33Liwi5sCUtu5tUWr3u7Yni5pRdxlrIzoJP1nLqwC1vCklt5tcVrU1s/Nh/p5QIrDOhkNycu7MKWsORG6lu8Cke1eDUl8AoXWGFAJ2dp6Ynj+YPOWdhFCGBJNVvCkntY0eL1olqfY1q8nlxg5SAXWGFAJ+dp7z+xsEvMGUG9Oo8tYck9rGjxOtEhLV5740lsPMAFVhjQydF64yY2HuhCq0MuVLaEJTfwUovX4/0JPHugGz1xfpbGgE6OFzcHp9KcsLALW8KSG3ilxeuh7sFXc7Eki98Y0Mk1nLSwC1vCkpN5pcXr7vYBvMIFVhjQyb3eWdjFvquYLWHJ4Y+/ad3i1ZQSW44NLrDCWM6ATi63vyuGFw/12LqwC1vCkhOle4vXxIkGVM0dXGCFAZ3SxrG+BJ470I2+uD0V8GwJS06zLs1bvA4kBltEc4EVBnRKQ52xJJ490I0OmxZ2YUtYcpJ0bvHaFUvi2QNdtl3rxIBOVjy1J008d7AbR2xY2IUtYckp0rnF69G+wQVW+rjACgM6pb+kKfFySw/2dFj/WZsVLWF7QvpHeJbpdIIB8/50bPG6tzOGlw5xgRUGdPIUCeCNY72WL+xiRUtYIfAZtoSloayeVpYjpfikyn3Y0eK1qa0fm4/a+0ULMaCTjXZ3DOCVll5Y2WeCLWHJ1ofZNGvxakrg1cO9XGCFGNAJONQTw/MHujBgYVRnS1iyw6r6iop0avEaT0q8cKgLB7pjPLnEgE6D2geSeG5/l2ULu1jREtaEeIRnlt7znJeUadPi9eS6DW19rGQnBnR6n56EiY0Hu9Dab80NQnVLWAA3syUsnbSytnI6BG5StX0rW7y29yfw7IEudMdNnlhiQKdTiyclXjzYhQMWLOySHVT+nS5bwtK783OlLV7nlBqWtHg91B3jAivEgE7DY0rgVYsWdllYxZawpJ4VLV7nV6r/TG1wgRVri1iJAZ3SQNPxfryueGEXtoQl1dalQYtXCWDLUS6wQgzoNAb7umJ4sUXtwi5WtIQ9VFt2K8+mNx2srbjNzS1eE6bEy4e60dzJBVaIAZ3G6Fiv2oVdrGgJC7aE9aQ7KyqCQkilLV6X1qhr8coFVogBnVJO9cIuFrSELWZLWO8JBsz7IVGmavvjIwLTFH1+2RVL4tmD3VxghRjQSUG2kFSXLbAlLKWam1u8Hu2NY6ONyx0TAzp5wDvv81L/WZsVLWGTpvFpnkVvMAfE5+DCFq/7umJ4qaVXad0KMaATAThZcatmYRfVLWElcB9bwqa/VfUVFQDWqtq+qhavTW3qvywhBnSiD9jdkfpvYtkSllLy4OayFq9/6/3ABVaIAZ3scqg7hucPpLZrlQUtYW9hS9j05bYWr1Z2ZyQGdKLTah9IbV9pC1rCCraETev83DUtXnsSgwusWLV+AjGgE53ROys/paYC3oqWsIPtQCmtsvO6ikvd0uK1vT8xuMIhK9mJAZ2cZnBt5u6UrM1sRUtY05RfX8drIG2sAzS4pMXroZ7BBVYG2JSdGNDJqUwJvHo4NQu7sCUsjcTB2orbAJyjavupavG6u30Ar7RwgRViQCeXaDrej81Hx/b5jVUtYRuKisI8Y+7mhhavEsAbx3q5wAoxoJP77O2M4aVDvYiPoUGGFS1hkeFby7Plbk5v8ZowJV5u6cGeDlayEwM6udTRvvjgwi6J0QV1toSlM3F6i9e/tUzuifNkEQM6uVtXLIlnD3SNepEJtoSl03Fyi1fVixoRMaCT5ca6DCRbwtKpOLnFq+plh4kY0Mk2CVNiU0sPmjsGRvzfFkQFphawJSy970FMcYvXaYWja/G6rzOGF1t6uMAKMaBT+jKlxJZjfaNa2GVxDVvC0jusaPF6wShavDa19eP1o1xghaxn8BCQHXZ3DKA/YeLsggwM90ugky1hX2hW9j5SmKb4/cra8oM8Q654PCyC6havI+iDICXw+tFe7GdPdmJAJ6851BNH/8FuzJ6QAf8wo/rCKgOvHzTRF1eW/RSe+B952EhbvMZNiU2HetDan+DBI9twyp1sdbx/cGGXnvjwsm4rWsISjaTF68l1DBjMiQGdPG/whtiNtmHeEFW3hCVvG0mL1/aBJDYe6EJ3jJXsxIBOBACIJSVeONiNg91nbr5hTUtY8qrhtnht6Ynj+QNcYIUY0Ik+wJTAK4d7hrWwiwUtYcmDhtvidXf7ABpbepBkJTsxoBMN7eTCLuZpbpZWtIQl7zlTi9fBBVb6uMAKMaATDdfezhhebjn9wi4WtIQlDzlTi9fkiUr2PaNojETEgE6edrR3cGGX/tMs7KK6JSx5w5lavJ5cYOVwLxdYIQZ0olHpiiXx7P4udMZO/VmbBS1hyQNO1+J1cAx2o50LrBADOtHY9CdNPLd/6IVdLGgJS2nsdC1ej/UlsPFAN/oS/CyNGNCJUiIhTyzs0vnB95cnW8ISjcZQLV73d8Xw4iEusEIM6EQpZ0qJLUcHF3Z5v4VVw+/sRXTSUC1em9r68doRLrBCDOhESu3uGEDj4R68O3EKGgLnsyUsjdD7W7xKCbx2pBdNx/t5cIgBncgKh7rjeP5gN2Lviupz2BKWRuD9LV7jpsQLh7q5WhoxoBNZ7Xh/As/u70bPiQp4toSlkXh3i9feuImN+7vQ2scFVogBncgWvfEkNh58Z2EXtoSl4Xh3i9f2/sEx1B1nJTsxoBPZ6t0Lu7AlLA3HyRavLT2Dr24G+FkaMaATOcO7F3ZhS1g6nZMtXrnACjGgEzlY0/F+bDnahwtrDLaEpQ842eL1TS6wQgzoRM7X3DmAvd19mDqBw5ve66xCHQd6+rCbC6xQGmJJMKWlI71xlOQJZAb8PBj0N5mRGFp6uMAKMaATuUZJ1I9p40LQOe9O72JKA2+3CmboxIBO5HSaAKbmh1GWycycTj0+puSHkBXQseVYH5Ls004M6ETOE/FpmDkhA1E/W8DS6ZVE/cgOGHjlcM+QS/MSue6BlYeA0kFhhh/zS6IM5jT8B0C/hvOKIyiOcjaHmKET2f9EKoBJuSFUZgd4MGjkN0BN4JzxYYwLG9hypI/fpBMDOpEdQsbgFHt2gFk5jU1JxI+oT0fj4V70xjkFTy5NcHgIyI0KMnxYWBplMKeUyQroWFASQWGErYOJAZ1I/YAVAvV5IcyekAGfxk/SKLV8msDMggxMGxdip0FyHU65k2sEjcGbbU6Qw5bUKs8MIOtEFXwvV2EjZuhEqZMfMrCgOMpgTpbJDuiYXxzF+DDHHDGgE42ZAFCbE8TcoggCBocrWcuvC8wpjGBKXgiCc/DkcHz0JMcK6AJnjw9jXJhFSmSvyuwAMoODU/BcO52YoRONQF7QwMLSKIM5OWhM6rigJIpxIY5JYkAnGl42lBXA3KIMBHQOT3IWvy4wpygDtTlBcAKenIZT7uScm6UmcHZBBouQyNEEgNrcIPJCBl450sspeGKGTvRu2QEd80tZUUzukXfiy4tcfnlBDOhEgyqzAji/OIowq9jJZYKGwLmcgieH4KMl2canCZw1LsxWm+TurEgI1OYGkRXU8drhXsS5xjoxQycvGeybHWUwp7RREPZhYUkU2QHmScSATh5REvXjvOIowj4OP0ovId/gGuuVWVzOl6zHR0mybrBpAtPGhVAc8fNgUPpmSQKYkh9CXsjAa0d6keAUPDFDp3QS8Ws4vzjCYE6eMSHDhwUlEUT9XOKXGNApTZRE/VhQHOWNjTwnwzdYK1KeySl4Uo9T7qTuaVEAk3JDqMzmzYy8fR1MGxdCTkjHlqN9SHIKnhjQyW2ZyawJYWblRCeURPzIDhhobOlBVyzJA0Kpf3jkIaBU47tDolOL+DScX8JaEmKGTk5/OuQUO9GZb7pC4JyCMMZlGNhypBdJzsATAzo5ScinYeb4MLLZ15poWEoifkR9OhoP96A3zgVeKAVJFQ8BjVVB2IeFxVEGc6IR+lvHxAx2TCQGdLJz8AiB2pwgZhVmwKdzaQqi0fBpAjMnZGBKXgiClxGNAVMqGpWgrmHGhDCXjiRKkcrsAHJCBl5p6UEv11gnZuhkhbyQgfmlEQZzohTLDuiYXxrF+DCn4IkBnRSrzg7g3KIIgjqHDpEKfk1gTmEG6vNCXGOdRoQpFg3vJqOf+NQmxMyByKqH55yAjlcO92IgySl4YoZOKZAX1HFBSZTBnMjqay9kYGFpFPlh5l7EgE5jVJk1OMUeMDhUiOwQ0AXmFkZQmxPkFDydFh/76JT8usDZ4zMwnpkBke0EgNrcIHJDBl493IMBtpcjZug0HNkBHfOLowzmRA6THzIwvyQTOfzChBjQ6UzKMwM4vziKsI9Dg8iJQobAvKIMVHPNBHofPubR4EAQAtPHh1EYYeEbkeMzMSFQnxdCdtDA60d6Eeca68SATsBgP+mZBWGEfVzulMhNJmT4kFUaRePhHrT3c411zz/o8RB4W0nUj/OKIwzmRC4VMjScVxRFZRan4JmhkyfpApiSH0ZZpp8Hg8jtmZkApuSHkB3UseVoHxKcgmdAJ2+I+DXMKMhApp9ZOVE6KY74kRUw0NjSg64Yp+A992DHQ+AtJVE/FhRHGcyJ0vWB3adhfkmUs2/M0Cltn9wEMCk3hEp+6kKU9nQBnDUujNyQgS1H+pCUnIJnQKe0kOEfrGJnVk7kLSURP7L8Bl453I2uGBd4SfvEjYcgvU3I8GFBcYTBnMijon4N55dEUcQeE8zQyaVPakKgNjfIblJEBEMIzCjIQF5oAG8c6wNn4BnQySVCxuDFy37PRPRu5ZkBZAcMvNLSg54Ep+DTLpHjIUgv48MGFnLxBiIaQlZAx4LSKAojrIJnQCdHEgBqc4KYXRiBT+eqyUQ0NEMTmFkQxpS8EITg/SJtzisPgfsFdIFzxmcgn8udEtEIVGYHkBXQ0XikFwOcgmeGTvbKCxlYUBplMCeiUckNGbigNIpxYVbBM6CTbaqzAzi3KIKgztNIRKPn1wTmFmagPi8ETsC7F9M6l158ZxeEMZ5P1ESU4iQhJ6Dj1cO96E9yCp4ZOimVHRysUGUwJyIV8kIG5pdGkMsvZRjQSZ3KrADOL4ogZPC0EZE6QV3DuUUZqM0JcgreRfgI5gI+TeCscSF+N0pE1mV7J7pNZgd0vHq0F/Ek28sxQ6cxyQromM8mEERkk/EZPjarYkCnsSrPDGB+cRQZnGInIhuFDIF5RRlcG8Lh+MjlxJMiBM4aH+bqSETknOxPCNTnhZAdNPDakV4kTE7BM0On04r4uNQhETnX35ZkDnBJZgZ0GlJJ1I8FpVFE/TwtRORcGX4d84ujqMziFLyTcMrdCU9VApiaH0ZZJgvfiMg9960p+SFkBXRsOdaHJKfgGdC9LuLTMHNCBqJ+Tl8RkfuURP2Da6wf7kFnLMkDYudDFg+BfQoz/JhfEmUwJyJ3JyZ+DecVR1Ac5SwjM3SvPUUJYFJuCJX8BISI0iWYaALnjA9jXNjAliN9SEpOwTOgp7kQY0eLAAAgAElEQVSQMTjFns0KUSJKQyURP6I+HY2He9Eb5xS8pckiD4F1CjJ8WFgaZTAnorSWFdCxoCSCQn5+y4CejjJ0H2ZPyIBP41IHRJT+fJrAzIIMZOgM6gzoacYQPNRExHsfMaATERERAzoREREDOhERETGgExEREQM6ERERMaATERExoBMREREDOhERETGgExEREQM6ERERAzoRERExoBMREREDOhERETGgExERMaATERERAzoRERExoBMREREDOhEREQM6ERERMaATERERAzoRERExoBMRETGgExEREQM6ERERMaATERERAzoRERExoBMRETGgExEREQM6ERERMaATERERAzoREREDOhERETGgExEREQM6ERERMaATERExoBMREREDOhERETGgExEREQM6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"
93
+ />
94
+ <br>
95
+ <strong>Better, Faster, Together</strong><br><br>
96
+ </div>
97
+ </div>
98
+ </body>
99
+ </html>
100
+ """
101
+
102
+ notification_template_end_title = """
103
+ </h1>
104
+ </div>
105
+
106
+ <div class="content">
107
+ """
108
+
109
+
110
+
111
+ def get_signature():
112
+ """
113
+ Build signature for Zou emails.
114
+ """
115
+ organisation = persons_service.get_organisation()
116
+ return (
117
+ """
118
+ <p>Best,</p>
119
+
120
+ <p>%s Team</p>"""
121
+ % organisation["name"]
122
+ )
123
+
124
+
125
+ def generate_html_body(title, message):
126
+ return notification_template_begin + \
127
+ title + notification_template_end_title + \
128
+ message + get_signature () + \
129
+ notification_template_end
@@ -633,9 +633,9 @@ def get_day_offs_between_for_project(
633
633
  for day_off in days_offs:
634
634
  day_off_person_id = str(day_off.person_id)
635
635
  result[day_off_person_id].append(
636
- day_off.serialize_safe()
637
- if safe and current_user_id != day_off_person_id
638
- else day_off.serialize()
636
+ day_off.serialize()()
637
+ if safe or current_user_id == day_off_person_id
638
+ else day_off.serialize_safe()
639
639
  )
640
640
  except DataError:
641
641
  raise WrongDateFormatException
@@ -24,6 +24,7 @@ from zou.app.services import (
24
24
  notifications_service,
25
25
  names_service,
26
26
  persons_service,
27
+ playlists_service,
27
28
  projects_service,
28
29
  shots_service,
29
30
  status_automations_service,
@@ -1384,8 +1385,8 @@ def get_last_notifications(
1384
1385
  Notification.query.filter_by(person_id=current_user["id"])
1385
1386
  .order_by(Notification.created_at.desc())
1386
1387
  .join(Author, Author.id == Notification.author_id)
1387
- .join(Task, Task.id == Notification.task_id)
1388
- .join(Project, Project.id == Task.project_id)
1388
+ .outerjoin(Task, Task.id == Notification.task_id)
1389
+ .outerjoin(Project, Project.id == Task.project_id)
1389
1390
  .outerjoin(
1390
1391
  Subscription,
1391
1392
  and_(
@@ -1442,6 +1443,7 @@ def get_last_notifications(
1442
1443
  query = query.filter(Subscription.id == None)
1443
1444
 
1444
1445
  notifications = query.limit(100).all()
1446
+ print(len(notifications))
1445
1447
 
1446
1448
  for (
1447
1449
  notification,
@@ -1456,9 +1458,22 @@ def get_last_notifications(
1456
1458
  subscription_id,
1457
1459
  role,
1458
1460
  ) in notifications:
1459
- (full_entity_name, episode_id, entity_preview_file_id) = (
1460
- names_service.get_full_entity_name(task_entity_id)
1461
- )
1461
+ full_entity_name, episode_id, entity_preview_file_id = "", None, None
1462
+ playlist_id = notification.playlist_id
1463
+ playlist_name = ""
1464
+ if notification.playlist_id is None:
1465
+ (full_entity_name, episode_id, entity_preview_file_id) = (
1466
+ names_service.get_full_entity_name(task_entity_id)
1467
+ )
1468
+ else:
1469
+ playlist = playlists_service.get_playlist(
1470
+ notification.playlist_id
1471
+ )
1472
+ episode_id = playlist.get("episode_id", None)
1473
+ project = projects_service.get_project(playlist["project_id"])
1474
+ project_id = project["id"]
1475
+ project_name = project["name"]
1476
+ playlist_name = playlist["name"]
1462
1477
  preview_file_id = None
1463
1478
  mentions = []
1464
1479
  department_mentions = []
@@ -1522,6 +1537,8 @@ def get_last_notifications(
1522
1537
  "episode_id": episode_id,
1523
1538
  "entity_preview_file_id": entity_preview_file_id,
1524
1539
  "subscription_id": subscription_id,
1540
+ "playlist_id": playlist_id,
1541
+ "playlist_name": playlist_name,
1525
1542
  }
1526
1543
  )
1527
1544
  )
zou/app/utils/commands.py CHANGED
@@ -663,7 +663,7 @@ def reset_search_index():
663
663
  with app.app_context():
664
664
  print("Resetting search index.")
665
665
  index_service.reset_index()
666
- print("Search index resetted.")
666
+ print("Search index reset.")
667
667
 
668
668
 
669
669
  def search_asset(query):
@@ -0,0 +1,35 @@
1
+ """add performance indexes
2
+
3
+ Revision ID: 0c05b22194f3
4
+ Revises: 1b0ab951adca
5
+ Create Date: 2025-09-12 16:03:33.551737
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+ import sqlalchemy_utils
11
+
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = '0c05b22194f3'
15
+ down_revision = '1b0ab951adca'
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('entity', schema=None) as batch_op:
23
+ batch_op.create_index('ix_entity_entity_type_parent', ['entity_type_id', 'parent_id'], unique=False)
24
+ batch_op.create_index('ix_entity_entity_type_project', ['entity_type_id', 'project_id'], unique=False)
25
+
26
+ # ### end Alembic commands ###
27
+
28
+
29
+ def downgrade():
30
+ # ### commands auto generated by Alembic - please adjust! ###
31
+ with op.batch_alter_table('entity', schema=None) as batch_op:
32
+ batch_op.drop_index('ix_entity_entity_type_project')
33
+ batch_op.drop_index('ix_entity_entity_type_parent')
34
+
35
+ # ### end Alembic commands ###
@@ -0,0 +1,46 @@
1
+ """add playlist_id field to notification
2
+
3
+ Revision ID: 5f1620d191af
4
+ Revises: 71d546ace0ee
5
+ Create Date: 2025-10-01 17:25:30.645533
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 = '5f1620d191af'
16
+ down_revision = '71d546ace0ee'
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('notification', schema=None) as batch_op:
24
+ batch_op.add_column(sa.Column('playlist_id', sqlalchemy_utils.types.uuid.UUIDType(binary=False), default=uuid.uuid4, nullable=True))
25
+ batch_op.alter_column('task_id',
26
+ existing_type=sa.UUID(),
27
+ nullable=True)
28
+ batch_op.drop_constraint('notification_uc', type_='unique')
29
+ batch_op.create_unique_constraint('notification_uc', ['person_id', 'author_id', 'comment_id', 'reply_id', 'playlist_id', 'type'])
30
+ batch_op.create_index(batch_op.f('ix_notification_playlist_id'), ['playlist_id'], unique=False)
31
+ batch_op.create_foreign_key(None, 'playlist', ['playlist_id'], ['id'])
32
+ # ### end Alembic commands ###
33
+
34
+
35
+ def downgrade():
36
+ # ### commands auto generated by Alembic - please adjust! ###
37
+ with op.batch_alter_table('notification', schema=None) as batch_op:
38
+ batch_op.drop_constraint(None, type_='foreignkey')
39
+ batch_op.drop_index(batch_op.f('ix_notification_playlist_id'))
40
+ batch_op.drop_constraint('notification_uc', type_='unique')
41
+ batch_op.create_unique_constraint('notification_uc', ['person_id', 'author_id', 'comment_id', 'reply_id', 'type'], postgresql_nulls_not_distinct=False)
42
+ batch_op.alter_column('task_id',
43
+ existing_type=sa.UUID(),
44
+ nullable=False)
45
+ batch_op.drop_column('playlist_id')
46
+ # ### end Alembic commands ###
@@ -0,0 +1,32 @@
1
+ """add performance indexes
2
+
3
+ Revision ID: 71d546ace0ee
4
+ Revises: 0c05b22194f3
5
+ Create Date: 2025-09-12 16:07:53.775943
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+ import sqlalchemy_utils
11
+
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = '71d546ace0ee'
15
+ down_revision = '0c05b22194f3'
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.create_index('ix_task_entity_project', ['entity_id', 'project_id'], unique=False)
24
+
25
+ # ### end Alembic commands ###
26
+
27
+
28
+ def downgrade():
29
+ # ### commands auto generated by Alembic - please adjust! ###
30
+ with op.batch_alter_table('task', schema=None) as batch_op:
31
+ batch_op.drop_index('ix_task_entity_project')
32
+ # ### end Alembic commands ###
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zou
3
- Version: 0.20.76
3
+ Version: 0.20.77
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
@@ -58,10 +58,10 @@ Requires-Dist: pillow==11.3.0
58
58
  Requires-Dist: psutil==7.1.0
59
59
  Requires-Dist: psycopg[binary]==3.2.10
60
60
  Requires-Dist: pyotp==2.9.0
61
- Requires-Dist: pysaml2==7.5.2
61
+ Requires-Dist: pysaml2==7.5.3
62
62
  Requires-Dist: python-nomad==2.1.0
63
63
  Requires-Dist: python-slugify==8.0.4
64
- Requires-Dist: python-socketio==5.13.0
64
+ Requires-Dist: python-socketio==5.14.1
65
65
  Requires-Dist: pytz==2025.2
66
66
  Requires-Dist: redis==5.2.1
67
67
  Requires-Dist: requests==2.32.5
@@ -88,7 +88,7 @@ Requires-Dist: pytest==8.4.2; extra == "test"
88
88
  Provides-Extra: monitoring
89
89
  Requires-Dist: prometheus-flask-exporter==0.23.2; extra == "monitoring"
90
90
  Requires-Dist: pygelf==0.4.3; extra == "monitoring"
91
- Requires-Dist: sentry-sdk==2.39.0; extra == "monitoring"
91
+ Requires-Dist: sentry-sdk==2.40.0; extra == "monitoring"
92
92
  Provides-Extra: lint
93
93
  Requires-Dist: autoflake==2.3.1; extra == "lint"
94
94
  Requires-Dist: black==25.9.0; extra == "lint"