zou 0.20.75__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.
zou/app/models/person.py CHANGED
@@ -183,13 +183,10 @@ class Person(db.Model, BaseMixin, SerializerMixin):
183
183
  for credential in self.fido_credentials
184
184
  ]
185
185
 
186
- def serialize(
187
- self, obj_type="Person", relations=False, milliseconds=False
188
- ):
189
- data = SerializerMixin.serialize(
190
- self, obj_type, relations=relations, milliseconds=milliseconds
191
- )
192
- data["fido_devices"] = self.fido_devices()
186
+ def serialize(self, **kwargs):
187
+ data = super().serialize(**kwargs)
188
+ if "fido_devices" not in kwargs.get("ignored_attrs", []):
189
+ data["fido_devices"] = self.fido_devices()
193
190
  return data
194
191
 
195
192
  def serialize_safe(self, **kwargs):
@@ -200,6 +197,7 @@ class Person(db.Model, BaseMixin, SerializerMixin):
200
197
  "email_otp_secret",
201
198
  "otp_recovery_codes",
202
199
  "fido_credentials",
200
+ "fido_devices",
203
201
  "jti",
204
202
  ],
205
203
  **kwargs,
@@ -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 ###