zou 0.19.14__py3-none-any.whl → 0.20.11__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.
Files changed (165) hide show
  1. zou/__init__.py +1 -1
  2. zou/app/__init__.py +10 -2
  3. zou/app/api.py +2 -0
  4. zou/app/blueprints/assets/__init__.py +22 -0
  5. zou/app/blueprints/assets/resources.py +241 -4
  6. zou/app/blueprints/auth/__init__.py +4 -0
  7. zou/app/blueprints/auth/resources.py +154 -22
  8. zou/app/blueprints/breakdown/resources.py +4 -4
  9. zou/app/blueprints/chats/__init__.py +22 -0
  10. zou/app/blueprints/chats/resources.py +199 -0
  11. zou/app/blueprints/comments/resources.py +36 -19
  12. zou/app/blueprints/crud/__init__.py +12 -0
  13. zou/app/blueprints/crud/attachment_file.py +14 -5
  14. zou/app/blueprints/crud/base.py +29 -28
  15. zou/app/blueprints/crud/chat.py +13 -0
  16. zou/app/blueprints/crud/chat_message.py +13 -0
  17. zou/app/blueprints/crud/comments.py +85 -29
  18. zou/app/blueprints/crud/custom_action.py +1 -1
  19. zou/app/blueprints/crud/day_off.py +47 -9
  20. zou/app/blueprints/crud/department.py +1 -25
  21. zou/app/blueprints/crud/entity.py +46 -5
  22. zou/app/blueprints/crud/entity_type.py +13 -1
  23. zou/app/blueprints/crud/event.py +1 -1
  24. zou/app/blueprints/crud/file_status.py +1 -1
  25. zou/app/blueprints/crud/metadata_descriptor.py +24 -10
  26. zou/app/blueprints/crud/organisation.py +22 -5
  27. zou/app/blueprints/crud/output_file.py +1 -1
  28. zou/app/blueprints/crud/output_type.py +1 -1
  29. zou/app/blueprints/crud/person.py +32 -24
  30. zou/app/blueprints/crud/playlist.py +1 -1
  31. zou/app/blueprints/crud/preview_background_file.py +6 -7
  32. zou/app/blueprints/crud/preview_file.py +1 -1
  33. zou/app/blueprints/crud/project.py +14 -6
  34. zou/app/blueprints/crud/project_status.py +1 -1
  35. zou/app/blueprints/crud/schedule_item.py +4 -2
  36. zou/app/blueprints/crud/software.py +1 -1
  37. zou/app/blueprints/crud/status_automation.py +1 -1
  38. zou/app/blueprints/crud/studio.py +33 -0
  39. zou/app/blueprints/crud/task.py +47 -3
  40. zou/app/blueprints/crud/task_status.py +1 -1
  41. zou/app/blueprints/crud/task_type.py +4 -4
  42. zou/app/blueprints/crud/working_file.py +4 -8
  43. zou/app/blueprints/events/resources.py +13 -12
  44. zou/app/blueprints/export/csv/assets.py +15 -6
  45. zou/app/blueprints/export/csv/edits.py +15 -5
  46. zou/app/blueprints/export/csv/playlists.py +1 -1
  47. zou/app/blueprints/export/csv/shots.py +15 -5
  48. zou/app/blueprints/export/csv/time_spents.py +1 -1
  49. zou/app/blueprints/files/resources.py +22 -23
  50. zou/app/blueprints/index/resources.py +38 -29
  51. zou/app/blueprints/news/resources.py +25 -11
  52. zou/app/blueprints/persons/__init__.py +5 -2
  53. zou/app/blueprints/persons/resources.py +126 -120
  54. zou/app/blueprints/previews/__init__.py +18 -8
  55. zou/app/blueprints/previews/resources.py +569 -328
  56. zou/app/blueprints/projects/resources.py +1 -1
  57. zou/app/blueprints/search/resources.py +18 -6
  58. zou/app/blueprints/shots/__init__.py +5 -0
  59. zou/app/blueprints/shots/resources.py +134 -4
  60. zou/app/blueprints/source/__init__.py +6 -6
  61. zou/app/blueprints/source/csv/assets.py +10 -3
  62. zou/app/blueprints/source/csv/base.py +1 -1
  63. zou/app/blueprints/source/csv/edits.py +10 -3
  64. zou/app/blueprints/source/csv/shots.py +10 -3
  65. zou/app/blueprints/source/{edl.py → otio.py} +84 -41
  66. zou/app/blueprints/tasks/__init__.py +3 -2
  67. zou/app/blueprints/tasks/resources.py +83 -52
  68. zou/app/blueprints/user/__init__.py +9 -0
  69. zou/app/blueprints/user/resources.py +170 -12
  70. zou/app/config.py +10 -0
  71. zou/app/mixin.py +6 -5
  72. zou/app/models/attachment_file.py +10 -4
  73. zou/app/models/base.py +18 -13
  74. zou/app/models/build_job.py +7 -4
  75. zou/app/models/chat.py +44 -0
  76. zou/app/models/chat_message.py +37 -0
  77. zou/app/models/comment.py +1 -0
  78. zou/app/models/day_off.py +3 -0
  79. zou/app/models/entity.py +4 -6
  80. zou/app/models/entity_type.py +2 -0
  81. zou/app/models/organisation.py +14 -15
  82. zou/app/models/person.py +6 -1
  83. zou/app/models/project.py +3 -0
  84. zou/app/models/search_filter.py +11 -0
  85. zou/app/models/search_filter_group.py +10 -0
  86. zou/app/models/serializer.py +17 -17
  87. zou/app/models/status_automation.py +2 -0
  88. zou/app/models/studio.py +13 -0
  89. zou/app/models/subscription.py +2 -2
  90. zou/app/models/task.py +6 -1
  91. zou/app/models/task_status.py +1 -0
  92. zou/app/models/task_type.py +1 -0
  93. zou/app/models/working_file.py +1 -1
  94. zou/app/services/assets_service.py +101 -14
  95. zou/app/services/auth_service.py +17 -44
  96. zou/app/services/breakdown_service.py +37 -5
  97. zou/app/services/chats_service.py +279 -0
  98. zou/app/services/comments_service.py +110 -65
  99. zou/app/services/concepts_service.py +4 -12
  100. zou/app/services/deletion_service.py +43 -30
  101. zou/app/services/edits_service.py +5 -11
  102. zou/app/services/emails_service.py +4 -4
  103. zou/app/services/entities_service.py +17 -2
  104. zou/app/services/events_service.py +12 -4
  105. zou/app/services/exception.py +5 -5
  106. zou/app/services/names_service.py +7 -2
  107. zou/app/services/news_service.py +17 -9
  108. zou/app/services/persons_service.py +38 -21
  109. zou/app/services/playlists_service.py +8 -7
  110. zou/app/services/preview_files_service.py +137 -10
  111. zou/app/services/projects_service.py +5 -14
  112. zou/app/services/shots_service.py +221 -49
  113. zou/app/services/sync_service.py +46 -42
  114. zou/app/services/tasks_service.py +185 -46
  115. zou/app/services/time_spents_service.py +67 -20
  116. zou/app/services/user_service.py +350 -107
  117. zou/app/stores/auth_tokens_store.py +2 -1
  118. zou/app/stores/file_store.py +18 -0
  119. zou/app/stores/publisher_store.py +7 -7
  120. zou/app/stores/queue_store.py +1 -0
  121. zou/app/swagger.py +36 -20
  122. zou/app/utils/cache.py +2 -0
  123. zou/app/utils/commands.py +104 -7
  124. zou/app/utils/csv_utils.py +1 -4
  125. zou/app/utils/date_helpers.py +33 -17
  126. zou/app/utils/dbhelpers.py +14 -1
  127. zou/app/utils/emails.py +2 -2
  128. zou/app/utils/fido.py +22 -0
  129. zou/app/utils/flask.py +1 -0
  130. zou/app/utils/query.py +54 -6
  131. zou/app/utils/redis.py +11 -0
  132. zou/app/utils/saml.py +51 -0
  133. zou/app/utils/string.py +2 -0
  134. zou/app/utils/thumbnail.py +4 -2
  135. zou/cli.py +76 -18
  136. zou/debug.py +4 -2
  137. zou/event_stream.py +122 -165
  138. zou/job_settings.py +1 -0
  139. zou/migrations/env.py +0 -0
  140. zou/migrations/utils/base.py +6 -6
  141. zou/migrations/versions/1bb55759146f_add_table_studio.py +67 -0
  142. zou/migrations/versions/1fab8c420678_add_attachments_to_message_chats.py +56 -0
  143. zou/migrations/versions/23122f290ca2_add_entity_chat_models.py +149 -0
  144. zou/migrations/versions/32f134ff1201_add_is_shared_flag_to_filters.py +33 -0
  145. zou/migrations/versions/57222395f2be_add_statusautomation_import_last_revision.py +41 -0
  146. zou/migrations/versions/59a7445a966c_add_entity_is_shared.py +41 -0
  147. zou/migrations/versions/5b980f0dc365_add_comment_links.py +35 -0
  148. zou/migrations/versions/680c64565f9d_for_searchfiltergroup_is_shared.py +35 -0
  149. zou/migrations/versions/8e67c183bed7_add_preference_fields.py +71 -0
  150. zou/migrations/versions/92b40d79ad3f_allow_message_attachments.py +38 -0
  151. zou/migrations/versions/971dbf5a0faf_add_short_name_for_asset_type_entity_.py +33 -0
  152. zou/migrations/versions/9b85c14fa8a7_add_day_off_new_columns.py +68 -0
  153. zou/migrations/versions/9d3bb33c6fc6_add_department_keys_to_filter_models.py +73 -0
  154. zou/migrations/versions/a252a094e977_add_descriptions_for_entities_tasks_and_.py +40 -0
  155. zou/migrations/versions/be56dc0fb760_for_is_shared_disallow_nullable.py +102 -0
  156. zou/migrations/versions/ca28796a2a62_add_is_done_field_to_the_task_model.py +108 -0
  157. zou/migrations/versions/f344b867a911_for_description_of_entity_task_working_.py +75 -0
  158. zou/remote/config_payload.py +2 -1
  159. zou/utils/movie.py +14 -4
  160. {zou-0.19.14.dist-info → zou-0.20.11.dist-info}/METADATA +75 -69
  161. {zou-0.19.14.dist-info → zou-0.20.11.dist-info}/RECORD +164 -135
  162. {zou-0.19.14.dist-info → zou-0.20.11.dist-info}/WHEEL +1 -1
  163. {zou-0.19.14.dist-info → zou-0.20.11.dist-info}/LICENSE +0 -0
  164. {zou-0.19.14.dist-info → zou-0.20.11.dist-info}/entry_points.txt +0 -0
  165. {zou-0.19.14.dist-info → zou-0.20.11.dist-info}/top_level.txt +0 -0
zou/app/config.py CHANGED
@@ -7,6 +7,7 @@ from zou.app.utils.env import envtobool, env_with_semicolon_to_list
7
7
 
8
8
  PROPAGATE_EXCEPTIONS = True
9
9
  DEBUG = envtobool("DEBUG", False)
10
+ DEBUG_HOST = os.getenv("DEBUG_HOST", "127.0.0.1")
10
11
  DEBUG_PORT = int(os.getenv("DEBUG_PORT", 5000))
11
12
 
12
13
  APP_NAME = "Zou"
@@ -18,6 +19,7 @@ AUTH_STRATEGY = os.getenv("AUTH_STRATEGY", "auth_local_classic")
18
19
  KEY_VALUE_STORE = {
19
20
  "host": os.getenv("KV_HOST", "localhost"),
20
21
  "port": os.getenv("KV_PORT", "6379"),
22
+ "password": os.getenv("KV_PASSWORD", None),
21
23
  }
22
24
  AUTH_TOKEN_BLACKLIST_KV_INDEX = 0
23
25
  MEMOIZE_DB_INDEX = 1
@@ -80,6 +82,7 @@ MAIL_PORT = os.getenv("MAIL_PORT", 25)
80
82
  MAIL_USERNAME = os.getenv("MAIL_USERNAME", "")
81
83
  MAIL_PASSWORD = os.getenv("MAIL_PASSWORD", "")
82
84
  MAIL_DEBUG = envtobool("MAIL_DEBUG", False)
85
+ MAIL_DEBUG_BODY = envtobool("MAIL_DEBUG_BODY", False)
83
86
  MAIL_USE_TLS = envtobool("MAIL_USE_TLS", False)
84
87
  MAIL_USE_SSL = envtobool("MAIL_USE_SSL", False)
85
88
  MAIL_DEFAULT_SENDER = os.getenv(
@@ -131,6 +134,10 @@ LDAP_IS_AD = envtobool("LDAP_IS_AD", False)
131
134
  LDAP_IS_AD_SIMPLE = envtobool("LDAP_IS_AD_SIMPLE", False)
132
135
  LDAP_SSL = envtobool("LDAP_SSL", False)
133
136
 
137
+ SAML_ENABLED = envtobool("SAML_ENABLED", False)
138
+ SAML_IDP_NAME = os.getenv("SAML_IDP_NAME", "")
139
+ SAML_METADATA_URL = os.getenv("SAML_METADATA_URL", "")
140
+
134
141
  LOGS_MODE = os.getenv("LOGS_MODE", "default")
135
142
  LOGS_HOST = os.getenv("LOGS_HOST", "localhost")
136
143
  LOGS_PORT = os.getenv("LOGS_PORT", 2202)
@@ -151,6 +158,7 @@ CRISP_TOKEN = os.getenv("CRISP_TOKEN", "")
151
158
  IS_SELF_HOSTED = envtobool("IS_SELF_HOSTED", True)
152
159
 
153
160
  DEFAULT_TIMEZONE = os.getenv("DEFAULT_TIMEZONE", "Europe/Paris")
161
+ DEFAULT_LOCALE = os.getenv("DEFAULT_LOCALE", "en_US")
154
162
 
155
163
  USER_LIMIT = int(os.getenv("USER_LIMIT", "100"))
156
164
  MIN_PASSWORD_LENGTH = int(os.getenv("MIN_PASSWORD_LENGTH", 8))
@@ -161,6 +169,8 @@ TELEMETRY_URL = os.getenv(
161
169
  f"{'http://localhost:8000' if DEBUG else 'https://account.cg-wire.com'}/api/selfhosted/telemetry/new/",
162
170
  )
163
171
 
172
+ REMOVE_FILES = envtobool("REMOVE_FILES", False)
173
+
164
174
  # Deprecated
165
175
  TO_REVIEW_TASK_STATUS = "To review"
166
176
  DEFAULT_FILE_STATUS = "To review"
zou/app/mixin.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from flask_restful import reqparse
2
2
  from flask import request
3
3
 
4
- from zou.app.utils import fields
4
+ from zou.app.utils import date_helpers
5
5
  from zou.app.services.exception import WrongParameterException
6
6
 
7
7
 
@@ -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
@@ -121,12 +122,12 @@ class ArgsMixin(object):
121
122
  """
122
123
  return self.get_bool_parameter("no_job")
123
124
 
124
- def get_text_parameter(self, field_name):
125
+ def get_text_parameter(self, field_name, default=None):
125
126
  """
126
127
  Returns text parameter value matching `field_name`.
127
128
  """
128
129
  options = request.args
129
- return options.get(field_name, None)
130
+ return options.get(field_name, default)
130
131
 
131
132
  def get_bool_parameter(self, field_name, default="false"):
132
133
  """
@@ -146,10 +147,10 @@ class ArgsMixin(object):
146
147
  if param is None:
147
148
  return date
148
149
  try:
149
- date = fields.get_date_object(param, "%Y-%m-%dT%H:%M:%S")
150
+ date = date_helpers.get_datetime_from_string(param)
150
151
  except Exception:
151
152
  try:
152
- date = fields.get_date_object(param, "%Y-%m-%d")
153
+ date = date_helpers.get_date_from_string(param)
153
154
  except Exception:
154
155
  raise WrongParameterException(
155
156
  "Wrong date format for before argument."
@@ -15,11 +15,16 @@ class AttachmentFile(db.Model, BaseMixin, SerializerMixin):
15
15
  extension = db.Column(db.String(6))
16
16
  mimetype = db.Column(db.String(255))
17
17
  comment_id = db.Column(
18
- UUIDType(binary=False), db.ForeignKey("comment.id"), index=True
18
+ UUIDType(binary=False),
19
+ db.ForeignKey("comment.id"),
20
+ index=True,
21
+ nullable=True,
19
22
  )
20
-
21
- __table_args__ = (
22
- db.UniqueConstraint("name", "comment_id", name="attachment_uc"),
23
+ chat_message_id = db.Column(
24
+ UUIDType(binary=False),
25
+ db.ForeignKey("chat_message.id"),
26
+ index=True,
27
+ nullable=True,
23
28
  )
24
29
 
25
30
  def __repr__(self):
@@ -37,6 +42,7 @@ class AttachmentFile(db.Model, BaseMixin, SerializerMixin):
37
42
  def create_from_import(cls, data):
38
43
  data.pop("type", None)
39
44
  data.pop("comment", None)
45
+ data.pop("chat_message", None)
40
46
  previous_data = cls.get(data["id"])
41
47
  if previous_data is None:
42
48
  return cls.create(**data)
zou/app/models/base.py CHANGED
@@ -1,10 +1,8 @@
1
- import datetime
2
-
3
1
  from sqlalchemy_utils import UUIDType
4
2
  from sqlalchemy import func
5
3
  from sqlalchemy import orm
6
4
  from zou.app import db
7
- from zou.app.utils import fields
5
+ from zou.app.utils import fields, date_helpers
8
6
 
9
7
 
10
8
  class BaseMixin(object):
@@ -13,11 +11,13 @@ class BaseMixin(object):
13
11
  )
14
12
 
15
13
  # Audit fields
16
- created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow)
14
+ created_at = db.Column(
15
+ db.DateTime, default=date_helpers.get_utc_now_datetime
16
+ )
17
17
  updated_at = db.Column(
18
18
  db.DateTime,
19
- default=datetime.datetime.utcnow,
20
- onupdate=datetime.datetime.utcnow,
19
+ default=date_helpers.get_utc_now_datetime,
20
+ onupdate=date_helpers.get_utc_now_datetime,
21
21
  )
22
22
 
23
23
  def __repr__(self):
@@ -41,12 +41,12 @@ class BaseMixin(object):
41
41
  return db.session.get(cls, id)
42
42
 
43
43
  @classmethod
44
- def get_by(cls, **kw):
44
+ def get_by(cls, *criterions, **kw):
45
45
  """
46
46
  Shorthand to retrieve data by using filters. It returns the first
47
47
  element of the returned data.
48
48
  """
49
- return cls.query.filter_by(**kw).first()
49
+ return cls.query.filter(*criterions).filter_by(**kw).first()
50
50
 
51
51
  @classmethod
52
52
  def get_by_case_insensitive(cls, **kw):
@@ -126,11 +126,11 @@ class BaseMixin(object):
126
126
  return instance
127
127
 
128
128
  @classmethod
129
- def delete_all_by(cls, **kw):
129
+ def delete_all_by(cls, *criterions, **kw):
130
130
  """
131
131
  Shorthand to delete data by using filters.
132
132
  """
133
- result = cls.query.filter_by(**kw).delete()
133
+ result = cls.query.filter(*criterions).filter_by(**kw).delete()
134
134
  db.session.commit()
135
135
  return result
136
136
 
@@ -184,7 +184,12 @@ class BaseMixin(object):
184
184
 
185
185
  @classmethod
186
186
  def commit(cls):
187
- db.session.commit()
187
+ try:
188
+ db.session.commit()
189
+ except BaseException:
190
+ db.session.rollback()
191
+ db.session.remove()
192
+ raise
188
193
 
189
194
  def save(self):
190
195
  """
@@ -192,7 +197,7 @@ class BaseMixin(object):
192
197
  instance fields.
193
198
  """
194
199
  try:
195
- self.updated_at = datetime.datetime.utcnow()
200
+ self.updated_at = date_helpers.get_utc_now_datetime()
196
201
  db.session.add(self)
197
202
  db.session.commit()
198
203
  except BaseException:
@@ -239,7 +244,7 @@ class BaseMixin(object):
239
244
  Shorthand to update an entry via the database session based on current
240
245
  instance fields. It doesn't generate a commit.
241
246
  """
242
- self.updated_at = datetime.datetime.utcnow()
247
+ self.updated_at = date_helpers.get_utc_now_datetime()
243
248
  for key, value in data.items():
244
249
  if hasattr(self.__class__, key):
245
250
  field_key = getattr(self.__class__, key)
@@ -1,11 +1,9 @@
1
- import datetime
2
-
3
1
  from sqlalchemy_utils import UUIDType, ChoiceType
4
2
 
5
3
  from zou.app import db
6
4
  from zou.app.models.serializer import SerializerMixin
7
5
  from zou.app.models.base import BaseMixin
8
- from zou.app.utils import fields
6
+ from zou.app.utils import fields, date_helpers
9
7
 
10
8
  STATUSES = [
11
9
  ("running", "Running"),
@@ -34,7 +32,12 @@ class BuildJob(db.Model, BaseMixin, SerializerMixin):
34
32
  )
35
33
 
36
34
  def end(self, status):
37
- self.update({"status": status, "ended_at": datetime.datetime.utcnow()})
35
+ self.update(
36
+ {
37
+ "status": status,
38
+ "ended_at": date_helpers.get_utc_now_datetime(),
39
+ }
40
+ )
38
41
 
39
42
  def present(self):
40
43
  return fields.serialize_dict(
zou/app/models/chat.py ADDED
@@ -0,0 +1,44 @@
1
+ from sqlalchemy_utils import UUIDType
2
+
3
+ from zou.app import db
4
+ from zou.app.models.serializer import SerializerMixin
5
+ from zou.app.models.base import BaseMixin
6
+
7
+
8
+ class ChatParticipant(db.Model):
9
+ __tablename__ = "chat_participant"
10
+ chat_id = db.Column(
11
+ UUIDType(binary=False),
12
+ db.ForeignKey("chat.id"),
13
+ primary_key=True,
14
+ )
15
+ person_id = db.Column(
16
+ UUIDType(binary=False),
17
+ db.ForeignKey("person.id"),
18
+ primary_key=True,
19
+ )
20
+
21
+
22
+ class Chat(db.Model, BaseMixin, SerializerMixin):
23
+ """
24
+ Message shared in the entity chat feeds.
25
+ """
26
+
27
+ object_id = db.Column(UUIDType(binary=False), nullable=False, index=True)
28
+ object_type = db.Column(
29
+ db.String(80), nullable=False, index=True, default="entity"
30
+ )
31
+ last_message = db.Column(db.DateTime, nullable=True)
32
+ participants = db.relationship(
33
+ "Person", secondary="chat_participant", lazy="joined"
34
+ )
35
+
36
+ def __repr__(self):
37
+ return "<Message of %s>" % self.object_id
38
+
39
+ def present(self):
40
+ return {
41
+ "id": str(self.id),
42
+ "object_id": str(self.object_id),
43
+ "last_message": self.last_message,
44
+ }
@@ -0,0 +1,37 @@
1
+ from sqlalchemy_utils import UUIDType
2
+
3
+ from zou.app import db
4
+ from zou.app.models.serializer import SerializerMixin
5
+ from zou.app.models.base import BaseMixin
6
+
7
+
8
+ class ChatMessage(db.Model, BaseMixin, SerializerMixin):
9
+ """
10
+ Message shared in the entity chat feeds.
11
+ """
12
+
13
+ chat_id = db.Column(
14
+ UUIDType(binary=False),
15
+ db.ForeignKey("chat.id"),
16
+ nullable=False,
17
+ index=True,
18
+ )
19
+ person_id = db.Column(
20
+ UUIDType(binary=False),
21
+ db.ForeignKey("person.id"),
22
+ nullable=False,
23
+ index=True,
24
+ )
25
+ text = db.Column(db.Text())
26
+ attachment_files = db.relationship(
27
+ "AttachmentFile", backref="chat_message", lazy="joined"
28
+ )
29
+
30
+ # TODO
31
+ # * mentions
32
+ # * reactions
33
+ # * concept links ?
34
+ # * task links ?
35
+
36
+ def __repr__(self):
37
+ return "<Message of %s>" % self.object_id
zou/app/models/comment.py CHANGED
@@ -92,6 +92,7 @@ class Comment(db.Model, BaseMixin, SerializerMixin):
92
92
  replies = db.Column(JSONB, default=[])
93
93
  checklist = db.Column(JSONB)
94
94
  pinned = db.Column(db.Boolean)
95
+ links = db.Column(db.ARRAY(db.String()))
95
96
 
96
97
  task_status_id = db.Column(
97
98
  UUIDType(binary=False), db.ForeignKey("task_status.id"), index=True
zou/app/models/day_off.py CHANGED
@@ -10,9 +10,12 @@ class DayOff(db.Model, BaseMixin, SerializerMixin):
10
10
  """
11
11
 
12
12
  date = db.Column(db.Date, nullable=False)
13
+ end_date = db.Column(db.Date, nullable=False)
14
+ description = db.Column(db.Text)
13
15
  person_id = db.Column(
14
16
  UUIDType(binary=False), db.ForeignKey("person.id"), index=True
15
17
  )
16
18
  __table_args__ = (
17
19
  db.UniqueConstraint("person_id", "date", name="day_off_uc"),
20
+ db.CheckConstraint("date <= end_date", name="day_off_date_check"),
18
21
  )
zou/app/models/entity.py CHANGED
@@ -3,7 +3,6 @@ from sqlalchemy_utils import UUIDType, ChoiceType
3
3
  from zou.app import db
4
4
  from zou.app.models.serializer import SerializerMixin
5
5
  from zou.app.models.base import BaseMixin
6
- from zou.app.utils import fields
7
6
 
8
7
  from sqlalchemy.dialects.postgresql import JSONB
9
8
 
@@ -90,13 +89,10 @@ class Entity(db.Model, BaseMixin, SerializerMixin):
90
89
  tasks and files.
91
90
  """
92
91
 
93
- id = db.Column(
94
- UUIDType(binary=False), primary_key=True, default=fields.gen_uuid
95
- )
96
-
92
+ id = BaseMixin.id
97
93
  name = db.Column(db.String(160), nullable=False)
98
94
  code = db.Column(db.String(160)) # To store sanitized version of name
99
- description = db.Column(db.String(1200))
95
+ description = db.Column(db.Text())
100
96
  shotgun_id = db.Column(db.Integer)
101
97
  canceled = db.Column(db.Boolean, default=False)
102
98
 
@@ -104,6 +100,8 @@ class Entity(db.Model, BaseMixin, SerializerMixin):
104
100
  nb_entities_out = db.Column(db.Integer, default=0)
105
101
  is_casting_standby = db.Column(db.Boolean, default=False)
106
102
 
103
+ is_shared = db.Column(db.Boolean, default=False, nullable=False)
104
+
107
105
  status = db.Column(
108
106
  ChoiceType(ENTITY_STATUSES), default="running", nullable=False
109
107
  )
@@ -27,6 +27,8 @@ class EntityType(db.Model, BaseMixin, SerializerMixin):
27
27
  """
28
28
 
29
29
  name = db.Column(db.String(30), unique=True, nullable=False, index=True)
30
+ short_name = db.Column(db.String(20))
31
+ description = db.Column(db.Text())
30
32
  task_types = db.relationship(
31
33
  "TaskType", secondary=task_type_link, lazy="joined"
32
34
  )
@@ -1,7 +1,6 @@
1
1
  from zou.app import db
2
2
  from zou.app.models.serializer import SerializerMixin
3
3
  from zou.app.models.base import BaseMixin
4
- from zou.app.utils import fields
5
4
 
6
5
 
7
6
  class Organisation(db.Model, BaseMixin, SerializerMixin):
@@ -14,23 +13,23 @@ class Organisation(db.Model, BaseMixin, SerializerMixin):
14
13
  has_avatar = db.Column(db.Boolean(), default=False)
15
14
  use_original_file_name = db.Column(db.Boolean(), default=False)
16
15
  timesheets_locked = db.Column(db.Boolean(), default=False)
16
+ format_duration_in_hours = db.Column(db.Boolean(), default=False)
17
17
  hd_by_default = db.Column(db.Boolean(), default=False)
18
18
  chat_token_slack = db.Column(db.String(80), default="")
19
19
  chat_webhook_mattermost = db.Column(db.String(80), default="")
20
20
  chat_token_discord = db.Column(db.String(80), default="")
21
+ dark_theme_by_default = db.Column(db.Boolean(), default=False)
22
+ format_duration_in_hours = db.Column(db.Boolean(), default=False)
21
23
 
22
- def present(self):
23
- return fields.serialize_dict(
24
- {
25
- "id": self.id,
26
- "chat_token_slack": self.chat_token_slack,
27
- "chat_webhook_mattermost": self.chat_webhook_mattermost,
28
- "chat_token_discord": self.chat_token_discord,
29
- "name": self.name,
30
- "has_avatar": self.has_avatar,
31
- "hours_by_day": self.hours_by_day,
32
- "hd_by_default": self.hd_by_default,
33
- "use_original_file_name": self.use_original_file_name,
34
- "timesheets_locked": self.timesheets_locked,
35
- }
24
+ def present(self, sensitive=False):
25
+ return self.serialize(
26
+ ignored_attrs=(
27
+ []
28
+ if sensitive
29
+ else [
30
+ "chat_token_slack",
31
+ "chat_webhook_mattermost",
32
+ "chat_token_discord",
33
+ ]
34
+ )
36
35
  )
zou/app/models/person.py CHANGED
@@ -94,7 +94,7 @@ class Person(db.Model, BaseMixin, SerializerMixin):
94
94
  TimezoneType(backend="pytz"),
95
95
  default=pytz_timezone(config.DEFAULT_TIMEZONE),
96
96
  )
97
- locale = db.Column(LocaleType, default=Locale("en", "US"))
97
+ locale = db.Column(LocaleType, default=Locale(config.DEFAULT_LOCALE))
98
98
  data = db.Column(JSONB)
99
99
  role = db.Column(ChoiceType(ROLE_TYPES), default="user", nullable=False)
100
100
  has_avatar = db.Column(db.Boolean(), default=False)
@@ -114,6 +114,9 @@ class Person(db.Model, BaseMixin, SerializerMixin):
114
114
  departments = db.relationship(
115
115
  "Department", secondary="department_link", lazy="joined"
116
116
  )
117
+ studio_id = db.Column(
118
+ UUIDType(binary=False), db.ForeignKey("studio.id"), index=True
119
+ )
117
120
 
118
121
  is_generated_from_ldap = db.Column(db.Boolean(), default=False)
119
122
  ldap_uid = db.Column(db.String(60), unique=True, default=None)
@@ -179,12 +182,14 @@ class Person(db.Model, BaseMixin, SerializerMixin):
179
182
  )
180
183
  return {
181
184
  "id": data["id"],
185
+ "type": data["type"],
182
186
  "first_name": data["first_name"],
183
187
  "last_name": data["last_name"],
184
188
  "full_name": self.full_name,
185
189
  "has_avatar": data["has_avatar"],
186
190
  "active": data["active"],
187
191
  "departments": data.get("departments", []),
192
+ "studio_id": data["studio_id"],
188
193
  "role": data["role"],
189
194
  "desktop_login": data["desktop_login"],
190
195
  "is_bot": data["is_bot"],
zou/app/models/project.py CHANGED
@@ -142,6 +142,9 @@ class Project(db.Model, BaseMixin, SerializerMixin):
142
142
  is_preview_download_allowed = db.Column(db.Boolean(), default=False)
143
143
  is_set_preview_automated = db.Column(db.Boolean(), default=False)
144
144
  homepage = db.Column(db.String(80), default="assets")
145
+ is_publish_default_for_artists = db.Column(db.Boolean(), default=False)
146
+ hd_bitrate_compression = db.Column(db.Integer, default=28)
147
+ ld_bitrate_compression = db.Column(db.Integer, default=6)
145
148
 
146
149
  project_status_id = db.Column(
147
150
  UUIDType(binary=False), db.ForeignKey("project_status.id"), index=True
@@ -3,6 +3,7 @@ from sqlalchemy_utils import UUIDType
3
3
  from zou.app import db
4
4
  from zou.app.models.serializer import SerializerMixin
5
5
  from zou.app.models.base import BaseMixin
6
+ from sqlalchemy.sql import expression
6
7
 
7
8
 
8
9
  class SearchFilter(db.Model, BaseMixin, SerializerMixin):
@@ -15,7 +16,17 @@ class SearchFilter(db.Model, BaseMixin, SerializerMixin):
15
16
  entity_type = db.Column(db.String(80))
16
17
  name = db.Column(db.String(200), nullable=False, default="")
17
18
  search_query = db.Column(db.String(500), nullable=False, default="")
19
+ is_shared = db.Column(
20
+ db.Boolean,
21
+ server_default=expression.false(),
22
+ default=False,
23
+ nullable=False,
24
+ )
18
25
 
26
+ department_id = db.Column(
27
+ UUIDType(binary=False),
28
+ db.ForeignKey("department.id"),
29
+ )
19
30
  search_filter_group_id = db.Column(
20
31
  UUIDType(binary=False),
21
32
  db.ForeignKey("search_filter_group.id"),
@@ -3,6 +3,7 @@ from sqlalchemy_utils import UUIDType
3
3
  from zou.app import db
4
4
  from zou.app.models.serializer import SerializerMixin
5
5
  from zou.app.models.base import BaseMixin
6
+ from sqlalchemy.sql import expression
6
7
 
7
8
 
8
9
  class SearchFilterGroup(db.Model, BaseMixin, SerializerMixin):
@@ -14,6 +15,15 @@ class SearchFilterGroup(db.Model, BaseMixin, SerializerMixin):
14
15
  entity_type = db.Column(db.String(80))
15
16
  name = db.Column(db.String(200), nullable=False, default="")
16
17
  color = db.Column(db.String(8), nullable=False, default="")
18
+ is_shared = db.Column(
19
+ db.Boolean,
20
+ server_default=expression.false(),
21
+ default=False,
22
+ nullable=False,
23
+ )
17
24
 
18
25
  person_id = db.Column(UUIDType(binary=False), db.ForeignKey("person.id"))
19
26
  project_id = db.Column(UUIDType(binary=False), db.ForeignKey("project.id"))
27
+ department_id = db.Column(
28
+ UUIDType(binary=False), db.ForeignKey("department.id")
29
+ )
@@ -14,35 +14,35 @@ class SerializerMixin(object):
14
14
  orm.attributes.CollectionAttributeImpl,
15
15
  )
16
16
 
17
- def serialize(self, obj_type=None, relations=False, milliseconds=False):
17
+ def serialize(
18
+ self,
19
+ obj_type=None,
20
+ relations=False,
21
+ milliseconds=False,
22
+ ignored_attrs=[],
23
+ ):
18
24
  attrs = inspect(self.__class__).all_orm_descriptors.keys()
19
- if relations:
20
- obj_dict = {
21
- attr: serialize_value(
22
- getattr(self, attr), milliseconds=milliseconds
23
- )
24
- for attr in attrs
25
- }
26
- else:
27
- obj_dict = {
28
- attr: serialize_value(
29
- getattr(self, attr), milliseconds=milliseconds
30
- )
31
- for attr in attrs
32
- if not self.is_join(attr)
33
- }
25
+ obj_dict = {
26
+ attr: serialize_value(
27
+ getattr(self, attr), milliseconds=milliseconds
28
+ )
29
+ for attr in attrs
30
+ if attr not in ignored_attrs
31
+ and (relations or not self.is_join(attr))
32
+ }
34
33
  obj_dict["type"] = obj_type or type(self).__name__
35
34
  return obj_dict
36
35
 
37
36
  @staticmethod
38
37
  def serialize_list(
39
- models, obj_type=None, relations=False, milliseconds=False
38
+ models, obj_type=None, relations=False, milliseconds=False, **kwargs
40
39
  ):
41
40
  return [
42
41
  model.serialize(
43
42
  obj_type=obj_type,
44
43
  relations=relations,
45
44
  milliseconds=milliseconds,
45
+ **kwargs
46
46
  )
47
47
  for model in models
48
48
  ]
@@ -37,4 +37,6 @@ class StatusAutomation(db.Model, BaseMixin, SerializerMixin):
37
37
  index=True,
38
38
  nullable=True,
39
39
  )
40
+ import_last_revision = db.Column(db.Boolean(), default=False)
41
+
40
42
  archived = db.Column(db.Boolean(), default=False)
@@ -0,0 +1,13 @@
1
+ from zou.app import db
2
+ from zou.app.models.serializer import SerializerMixin
3
+ from zou.app.models.base import BaseMixin
4
+
5
+
6
+ class Studio(db.Model, BaseMixin, SerializerMixin):
7
+ """
8
+ Describe a studio.
9
+ """
10
+
11
+ name = db.Column(db.String(80), unique=True, nullable=False)
12
+ color = db.Column(db.String(7), nullable=False)
13
+ archived = db.Column(db.Boolean(), default=False)
@@ -22,10 +22,10 @@ class Subscription(db.Model, BaseMixin, SerializerMixin):
22
22
 
23
23
  entity_id = db.Column(
24
24
  UUIDType(binary=False), db.ForeignKey("entity.id"), index=True
25
- )
25
+ ) # Deprecated
26
26
  task_type_id = db.Column(
27
27
  UUIDType(binary=False), db.ForeignKey("task_type.id"), index=True
28
- )
28
+ ) # Deprecated
29
29
 
30
30
  __table_args__ = (
31
31
  db.UniqueConstraint(
zou/app/models/task.py CHANGED
@@ -30,9 +30,10 @@ class Task(db.Model, BaseMixin, SerializerMixin):
30
30
  """
31
31
 
32
32
  name = db.Column(db.String(80), nullable=False)
33
- description = db.Column(db.String(200))
33
+ description = db.Column(db.Text())
34
34
 
35
35
  priority = db.Column(db.Integer, default=0)
36
+ difficulty = db.Column(db.Integer, default=3, nullable=False)
36
37
  duration = db.Column(db.Float, default=0)
37
38
  estimation = db.Column(db.Float, default=0)
38
39
  completion_rate = db.Column(db.Integer, default=0)
@@ -42,6 +43,7 @@ class Task(db.Model, BaseMixin, SerializerMixin):
42
43
  due_date = db.Column(db.DateTime)
43
44
  real_start_date = db.Column(db.DateTime)
44
45
  end_date = db.Column(db.DateTime)
46
+ done_date = db.Column(db.DateTime)
45
47
  last_comment_date = db.Column(db.DateTime)
46
48
  nb_assets_ready = db.Column(db.Integer, default=0)
47
49
  data = db.Column(JSONB)
@@ -69,6 +71,9 @@ class Task(db.Model, BaseMixin, SerializerMixin):
69
71
  db.UniqueConstraint(
70
72
  "name", "project_id", "task_type_id", "entity_id", name="task_uc"
71
73
  ),
74
+ db.CheckConstraint(
75
+ "difficulty > 0 AND difficulty < 6", name="check_difficulty"
76
+ ),
72
77
  )
73
78
 
74
79
  def assignees_as_string(self):
@@ -15,6 +15,7 @@ class TaskStatus(db.Model, BaseMixin, SerializerMixin):
15
15
  short_name = db.Column(
16
16
  db.String(10), unique=True, nullable=False, index=True
17
17
  )
18
+ description = db.Column(db.Text())
18
19
  color = db.Column(db.String(7), nullable=False)
19
20
  priority = db.Column(db.Integer, default=1)
20
21
 
@@ -12,6 +12,7 @@ class TaskType(db.Model, BaseMixin, SerializerMixin):
12
12
 
13
13
  name = db.Column(db.String(40), nullable=False)
14
14
  short_name = db.Column(db.String(20))
15
+ description = db.Column(db.Text())
15
16
  color = db.Column(db.String(7), default="#FFFFFF")
16
17
  priority = db.Column(db.Integer, default=1)
17
18
  for_entity = db.Column(db.String(30), default="Asset")